Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
142 commits
Select commit Hold shift + click to select a range
cef36ba
+data_world
JamesMcClung Jul 14, 2026
acf4a96
data_world: +active_data
JamesMcClung Jul 14, 2026
bc2a9c3
data_world: is frozen
JamesMcClung Jul 14, 2026
a53333e
data_world: +post_init
JamesMcClung Jul 14, 2026
d48c62b
data_world: +frozendict todo
JamesMcClung Jul 14, 2026
074cac3
data_world: +with_active_data
JamesMcClung Jul 14, 2026
69f70a3
data_world: fix datas default
JamesMcClung Jul 14, 2026
db3097f
adaptor: +apply_world
JamesMcClung Jul 14, 2026
f6a713f
+node
JamesMcClung Jul 14, 2026
475edb3
node: +name_fragments
JamesMcClung Jul 14, 2026
1a0ac48
node: switch adaptornode ctor params
JamesMcClung Jul 15, 2026
1af1786
node: +PlotNode
JamesMcClung Jul 15, 2026
abbb430
node: fix loader pull
JamesMcClung Jul 15, 2026
40b0183
node: +get_save_file_stem
JamesMcClung Jul 15, 2026
b4b11ed
*: use nodes
JamesMcClung Jul 15, 2026
58fde8c
compile; cli: mv resolve_save_format
JamesMcClung Jul 15, 2026
a8cef92
compile; *: ->compile_plot_node
JamesMcClung Jul 15, 2026
b3cf76c
node: +Save/ShowPlotNode
JamesMcClung Jul 15, 2026
ca56e45
compile: +compile_action_nodes
JamesMcClung Jul 15, 2026
7b25c8c
cli: use action nodes
JamesMcClung Jul 15, 2026
d6b544f
node: +DaskGraphNode
JamesMcClung Jul 15, 2026
9e6c7f6
compile: handle dask graph too
JamesMcClung Jul 15, 2026
9eb664a
cli: dask graph via node
JamesMcClung Jul 15, 2026
267cd96
compile: don't bother warning about save-format
JamesMcClung Jul 15, 2026
3893cdc
data_world: active_data can be None to switch keys
JamesMcClung Jul 15, 2026
db7923a
parse_util; *: ->parse_\*
JamesMcClung Jul 15, 2026
72a42e2
with: support switching prefixes
JamesMcClung Jul 15, 2026
4f432a5
parse: don't restrict initial prefix
JamesMcClung Jul 15, 2026
6de8013
parse: don't pass prefix dict
JamesMcClung Jul 15, 2026
f39b4d6
adaptor: +WorldAdaptor
JamesMcClung Jul 15, 2026
76491f5
loader: is WorldAdaptor
JamesMcClung Jul 15, 2026
ea1b716
with_: allow no key
JamesMcClung Jul 15, 2026
e9f0bc3
node; compile: ->RootNode; use initial With
JamesMcClung Jul 15, 2026
c524aec
parse: don't set loader
JamesMcClung Jul 15, 2026
892d372
args: -loader
JamesMcClung Jul 15, 2026
15087c5
parse; *: ->parse_args
JamesMcClung Jul 15, 2026
f4e5f85
with_: fix fragments
JamesMcClung Jul 15, 2026
ea2519d
parse_util: allow . in identifiers
JamesMcClung Jul 15, 2026
064ac05
with: ->prefix_or_key
JamesMcClung Jul 15, 2026
d2dd818
compile: use Loader instead of With initially
JamesMcClung Jul 15, 2026
3df81c9
loader; *: +get_loader
JamesMcClung Jul 15, 2026
9389690
config: ->from_env
JamesMcClung Jul 15, 2026
6401ca2
data_world: +config
JamesMcClung Jul 15, 2026
3c0f62f
config; *: -CONFIG
JamesMcClung Jul 15, 2026
7549950
plot; *: set fig, ax in Plot
JamesMcClung Jul 15, 2026
255d3ac
plot; *: mv _initialize
JamesMcClung Jul 15, 2026
b107c48
plot; *: mv show impl
JamesMcClung Jul 15, 2026
0bf447a
frame_data_traits: improve err message
JamesMcClung Jul 16, 2026
2b70b03
var_info: mv latex in axis label
JamesMcClung Jul 16, 2026
1d241d8
tests: update baselines
JamesMcClung Jul 16, 2026
4339e5d
+plot_info; *: partially refactor plotting
JamesMcClung Jul 16, 2026
ede020b
plot_info, field_1d; baselines: fix x bounds
JamesMcClung Jul 16, 2026
50b529a
scale; plt_util: mv scale/norm types
JamesMcClung Jul 16, 2026
08c6f6e
-parsing/init
JamesMcClung Jul 16, 2026
c38fccf
set_scale; *: refactor SetScale into an adaptor
JamesMcClung Jul 16, 2026
61eefd2
renderer; *: don't pass init_data
JamesMcClung Jul 17, 2026
dc44a9e
renderer; *: -init
JamesMcClung Jul 17, 2026
7b6a2c2
renderer; *: -draw
JamesMcClung Jul 17, 2026
584023b
frame_data_traits; *: -HasColorNorm
JamesMcClung Jul 17, 2026
74245b2
frame_data_traits; *: -HasSpatialScales
JamesMcClung Jul 17, 2026
06773c1
renderer; *: don't pass update_data to update_plot_info
JamesMcClung Jul 17, 2026
b3f8bdf
renderer; *: -make_update_data
JamesMcClung Jul 17, 2026
a7543b9
renderer; *: -subplot_kw
JamesMcClung Jul 17, 2026
4bf273f
+plot_target
JamesMcClung Jul 17, 2026
954fb0f
data_world: +plot_targets
JamesMcClung Jul 17, 2026
7080124
versus: set plot target
JamesMcClung Jul 17, 2026
70a8fc0
adaptor: fix format message
JamesMcClung Jul 17, 2026
10039b8
get_plot, renderer; *: base renderer on plot_target
JamesMcClung Jul 17, 2026
1711698
get_plot: -old get_renderer
JamesMcClung Jul 17, 2026
76274ab
renderer: +get_data_at_frame
JamesMcClung Jul 17, 2026
e293e11
renderer; *: don't pass data to info methods
JamesMcClung Jul 17, 2026
a584fd8
frame_data_traits; *: ->line_style
JamesMcClung Jul 17, 2026
90bae10
+setup_fig; *: mv setup_fig
JamesMcClung Jul 17, 2026
00addbe
hook: +DrawMessage
JamesMcClung Jul 17, 2026
5fc2ead
plot_info; *: +time_dim
JamesMcClung Jul 17, 2026
8ca5ff3
plot_info: +get_coord_label
JamesMcClung Jul 17, 2026
f73d915
hook; *: take DrawMessage
JamesMcClung Jul 17, 2026
37d4c78
plot; *: don't store data
JamesMcClung Jul 17, 2026
f17e5aa
*: rm debug prints
JamesMcClung Jul 17, 2026
d6805e8
renderer; *: -make_init_data
JamesMcClung Jul 17, 2026
be1c2ae
renderer; *: set plot_info in base init
JamesMcClung Jul 17, 2026
38c1f88
plot: not generic over data
JamesMcClung Jul 17, 2026
0ad1dbf
hook; plot: -post_add_hook
JamesMcClung Jul 17, 2026
8b39bfd
animated_plot: -UpdateData
JamesMcClung Jul 17, 2026
5f9b97a
-frame_data_traits
JamesMcClung Jul 17, 2026
35f2fc4
renderer: +get_n_frames
JamesMcClung Jul 17, 2026
605601e
renderer: accept frame that's too high
JamesMcClung Jul 17, 2026
0862858
plot_info; *: +axes_index
JamesMcClung Jul 17, 2026
9928da5
*: allow multiple axes (1 plot/axis still)
JamesMcClung Jul 17, 2026
e05c86c
versus: set axes_index
JamesMcClung Jul 17, 2026
e663b27
renderer: select the right full_data
JamesMcClung Jul 17, 2026
982719b
plot_target: +unpack
JamesMcClung Jul 17, 2026
f9add7e
renderers: use plot target for xy dims
JamesMcClung Jul 17, 2026
b000e98
versus: don't set dim metadata
JamesMcClung Jul 17, 2026
8c500be
data_with_attrs: -dim metadata
JamesMcClung Jul 17, 2026
3368ba4
setup_fig: +UpdateTitle fix
JamesMcClung Jul 17, 2026
d83d9a4
setup_fig: don't callback label yet
JamesMcClung Jul 17, 2026
97e2f08
setup_fig: don't callback others yet
JamesMcClung Jul 17, 2026
fd1c3ff
setup_fig: +_flatten_idx
JamesMcClung Jul 17, 2026
c2a00df
setup_fig: multiple titles ok
JamesMcClung Jul 17, 2026
e2ae73a
latex: +hash
JamesMcClung Jul 21, 2026
f7ea13f
latex: +maybe_with_dollars
JamesMcClung Jul 21, 2026
ef1047a
setup_fig: +one_or_none
JamesMcClung Jul 21, 2026
97801a6
setup_fig: +setup_title
JamesMcClung Jul 21, 2026
1b4026e
setup_fig: +AxesManager
JamesMcClung Jul 21, 2026
b3eb1cb
setup_fig: +setup_labels
JamesMcClung Jul 21, 2026
5f1a20e
setup_fig: +setup_data
JamesMcClung Jul 21, 2026
dbd77e2
setup_fig: generic over Axes type
JamesMcClung Jul 21, 2026
0173c71
setup_fig: +setup_bounds,scales
JamesMcClung Jul 21, 2026
0eb256c
setup_fig: set aspect in manager
JamesMcClung Jul 21, 2026
32cc608
setup_fig: +setup
JamesMcClung Jul 21, 2026
e277b74
setup_fig: +setup_scales in polar
JamesMcClung Jul 21, 2026
3710283
setup_fig: ->UpdateText
JamesMcClung Jul 21, 2026
d9ada62
setup_fig: enforce scales, bounds
JamesMcClung Jul 21, 2026
d07de7d
scale: make dataclass
JamesMcClung Jul 21, 2026
d115662
setup_fig: +find_widest_bounds
JamesMcClung Jul 21, 2026
2505eb2
setup_fig: simpler one_or_none
JamesMcClung Jul 21, 2026
7a9a4ed
setup_fig: one_or_none works with unhashables
JamesMcClung Jul 21, 2026
f35cf1b
setup_fig: +AxesManagerMultiLine
JamesMcClung Jul 21, 2026
2dc6b36
data_with_attrs; *: Field stores dict
JamesMcClung Jul 21, 2026
6e22d11
compile: +compile_data_node
JamesMcClung Jul 22, 2026
844cb6e
derived_field_variable; *: act on Field
JamesMcClung Jul 22, 2026
971ad4d
derived_field_variable: set dim var info too
JamesMcClung Jul 22, 2026
3c11c04
setup_fig: +common_subject
JamesMcClung Jul 22, 2026
023868d
test_plots; baselines: +test_multiline
JamesMcClung Jul 22, 2026
ad5cd2b
setup_fig: fix err
JamesMcClung Jul 22, 2026
fc6b475
*; baselines: change to constrained layout
JamesMcClung Jul 22, 2026
9046460
setup_fig: +AxesManagerImageAndLines
JamesMcClung Jul 22, 2026
217a99c
setup_fig: set lims, not bounds
JamesMcClung Jul 22, 2026
074be2f
CLAUDE.md: rewrite Architecture for split-vars
JamesMcClung Jul 22, 2026
cb2608b
test_plots: +test_image_and_line
JamesMcClung Jul 22, 2026
b538209
-compatability
JamesMcClung Jul 22, 2026
3a18a9e
var_info; *: ->check_unit_compatibility
JamesMcClung Jul 22, 2026
63459c7
versus: rm stray f-string prefix
JamesMcClung Jul 22, 2026
ce4cde5
animated_plot: rename renderer param to renderers
JamesMcClung Jul 22, 2026
80b831a
node: unshadow builtin 'format' and fix save_dpi type in SavePlotNode
JamesMcClung Jul 22, 2026
75ca1b9
plot; *: take config
JamesMcClung Jul 22, 2026
415c905
animated_plot: don't allow mp4 when no ffmpeg
JamesMcClung Jul 22, 2026
4220447
animated_plot; node: set ffmpeg bin when saving
JamesMcClung Jul 22, 2026
6a9f4ac
compile: -_resolve_save_format
JamesMcClung Jul 22, 2026
70fcc30
plot_info: ->xy_data
JamesMcClung Jul 22, 2026
dfd5baa
data_world: py3.13 annotations import
JamesMcClung Jul 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 33 additions & 18 deletions CLAUDE.md

Large diffs are not rendered by default.

120 changes: 14 additions & 106 deletions src/lib/cli.py
Original file line number Diff line number Diff line change
@@ -1,117 +1,25 @@
import sys
import warnings
import webbrowser
from pathlib import Path

import dask
import matplotlib.pyplot as plt

from lib import parsing
from lib.config import CONFIG
from lib.parsing.args import Args
from lib.plotting.plot import SaveFormat


def _resolve_save_format(args: Args) -> SaveFormat | None:
if args.save is None:
if args.save_format is not None:
print("error: --save-format requires --save", file=sys.stderr)
sys.exit(1)
return None

if args.save_format == "mp4":
if not CONFIG.ffmpeg_bin:
print("error: --save-format mp4 requires ffmpeg", file=sys.stderr)
sys.exit(1)
return "mp4"

if args.save_format == "gif":
return "gif"

# save_format is None: try mp4, fall back to gif
if CONFIG.ffmpeg_bin:
return "mp4"

message = "ffmpeg not found; will save animations as gif instead of mp4"
warnings.warn(message)
return "gif"


def _run_dask_graph(args: Args) -> None:
data = args.get_data()

collections = data.dask_collections()
if not collections:
print(
f"error: --dask-graph requires dask-backed data; pipeline produced eager {type(data).__name__}",
file=sys.stderr,
)
sys.exit(1)

try:
import graphviz # noqa: F401
except ImportError:
print(
"error: --dask-graph requires the 'graphviz' package; install with `pip install -e \".[dask-graph]\"`",
file=sys.stderr,
)
sys.exit(1)

save_dir = args.save or Path.cwd()
save_dir.mkdir(exist_ok=True, parents=True)
path = save_dir / f"{args.get_save_file_stem()}.daskgraph.svg"
# dask.visualize's optimize_graph flag only lowers legacy HLG collections
# (e.g. dask Arrays), not new-style Expr ones (dask DataFrames) — without
# pre-optimizing the latter, un-lowered nodes (e.g. Concat from dd.concat)
# fail with NotImplementedError in _layer.
collections = [c.optimize() if hasattr(c, "optimize") else c for c in collections]
dask.visualize(*collections, filename=str(path), optimize_graph=True)
print(f"wrote to {path}")

if args.show:
webbrowser.open(path.absolute().as_uri())
from lib.config import PscPlotConfig
from lib.data.compile import compile_action_nodes
from lib.parsing.parse import parse_args


def main():
dask.config.set(num_workers=CONFIG.dask_num_workers)
if CONFIG.dask_scheduler == "distributed":
config = PscPlotConfig.from_env()

dask.config.set(num_workers=config.dask_num_workers)
if config.dask_scheduler == "distributed":
from dask.distributed import Client, LocalCluster

cluster = LocalCluster(n_workers=CONFIG.dask_num_workers, threads_per_worker=1, processes=True)
cluster = LocalCluster(n_workers=config.dask_num_workers, threads_per_worker=1, processes=True)
Client(cluster)
elif CONFIG.dask_scheduler:
dask.config.set(scheduler=CONFIG.dask_scheduler)

args = parsing.get_parsed_args()

if args.dask_graph:
if args.save_format is not None:
warnings.warn("--save-format is ignored with --dask-graph")
_run_dask_graph(args)
return

# resolve format BEFORE applying pipeline in order to fail early
format = _resolve_save_format(args)

if format == "mp4":
plt.rcParams["animation.ffmpeg_path"] = str(CONFIG.ffmpeg_bin)

plot = args.get_animation()

if args.show:
plot.show()
if args.save is not None:
args.save.mkdir(exist_ok=True, parents=True)
elif config.dask_scheduler:
dask.config.set(scheduler=config.dask_scheduler)

if format not in plot.allowed_save_formats():
if format == args.save_format: # user actually specified this format
message = f"{format} is incompatible with the data; reverting to default ({plot.default_save_format()})"
warnings.warn(message)
else:
assert args.save_format is None
args = parse_args()

format = plot.default_save_format()
actions = compile_action_nodes(args, config)

path = args.save / f"{args.get_save_file_stem()}.{format}"
plot.save_to_path(path, dpi=args.save_dpi)
print(f"wrote to {path}")
for action in actions:
action.pull()
41 changes: 15 additions & 26 deletions src/lib/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os
import shutil
from dataclasses import dataclass
from dataclasses import KW_ONLY, dataclass, field
from pathlib import Path
from typing import Callable, Self

Expand All @@ -19,32 +19,21 @@ def parse_optional[T](s: str | None, parser: Callable[[str], T]) -> T | None:

@dataclass
class PscPlotConfig:
data_dir: Path
ffmpeg_bin: Path | None
dask_num_workers: int
dask_chunk_size: int
dask_scheduler: str | None
_: KW_ONLY
data_dir: Path = field(default_factory=Path.cwd)
ffmpeg_bin: Path | None = None
dask_num_workers: int = 1
dask_chunk_size: int = 1_000_000
dask_scheduler: str | None = None

@classmethod
def _load(cls) -> Self:
data_dir = parse_optional(os.environ.get(_DATA_DIR_KEY), Path)
if not data_dir:
message = f"Path to data not specified. Set the {_DATA_DIR_KEY} environment variable to specify."
raise RuntimeError(message)
def from_env(cls) -> Self:
config = cls()

ffmpeg_bin = parse_optional(os.environ.get(_FFMPEG_BIN_KEY, shutil.which("ffmpeg")), Path)
config.data_dir = parse_optional(os.environ.get(_DATA_DIR_KEY), Path) or config.data_dir
config.ffmpeg_bin = parse_optional(os.environ.get(_FFMPEG_BIN_KEY, shutil.which("ffmpeg")), Path) or config.ffmpeg_bin
config.dask_num_workers = parse_optional(os.environ.get(_DASK_NUM_WORKERS_KEY), int) or os.cpu_count() or config.dask_num_workers
config.dask_chunk_size = parse_optional(os.environ.get(_DASK_CHUNK_SIZE_KEY), int) or config.dask_chunk_size
config.dask_scheduler = os.environ.get(_DASK_SCHEDULER_KEY) or config.dask_scheduler

dask_num_workers = parse_optional(os.environ.get(_DASK_NUM_WORKERS_KEY), int)
if not dask_num_workers:
dask_num_workers = os.cpu_count() or 1

dask_chunk_size = parse_optional(os.environ.get(_DASK_CHUNK_SIZE_KEY), int)
if not dask_chunk_size:
dask_chunk_size = 1_000_000

dask_scheduler = os.environ.get(_DASK_SCHEDULER_KEY) or None

return cls(data_dir, ffmpeg_bin, dask_num_workers, dask_chunk_size, dask_scheduler)


CONFIG = PscPlotConfig._load()
return config
15 changes: 13 additions & 2 deletions src/lib/data/adaptor.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from __future__ import annotations

from abc import ABC, abstractmethod

import dask.dataframe as dd
import pandas as pd
import xarray as xr

from lib.data.data_with_attrs import DataWithAttrs, Field, List, Metadata
from lib.data.data_world import DataWorld
from lib.has_name_fragments import HasNameFragments
from lib.latex import Latex

Expand All @@ -19,14 +22,22 @@ def _fail_apply_list(adaptor_type: type[Adaptor]):
raise RuntimeError(message)


class Adaptor(HasNameFragments):
class WorldAdaptor(ABC, HasNameFragments):
@abstractmethod
def apply_world(self, world: DataWorld) -> DataWorld: ...


class Adaptor(WorldAdaptor):
def apply_world(self, world: DataWorld) -> DataWorld:
return world.with_active_data(self.apply(world.active_data))

def apply(self, data: DataWithAttrs) -> DataWithAttrs:
if isinstance(data, List):
return self.apply_list(data)
elif isinstance(data, Field):
return self.apply_field(data)
else:
message = f"unrecognized data type: {data.__class__:r}"
message = f"unrecognized data type: {data.__class__!r}"
raise Exception(message)

def apply_list(self, data: List) -> DataWithAttrs:
Expand Down
11 changes: 4 additions & 7 deletions src/lib/data/adaptors/bin.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from lib.data.data_with_attrs import Field, FieldMetadata, List
from lib.parsing import parse_util
from lib.parsing.args_registry import arg_parser
from lib.var_info import VarInfo


def _guess_bin_edgess(data: List, varname_to_nbins: dict[str, int | None]) -> list:
Expand Down Expand Up @@ -81,7 +80,6 @@ def __init__(self, varname_to_nbins: dict[str, int | None]):
self.varname_to_nbins = varname_to_nbins

def apply_field(self, data: Field) -> Field:
da = data.data
dim_names_to_bin_size = {}
for dim_name, nbins in self.varname_to_nbins.items():
if not nbins:
Expand All @@ -95,8 +93,7 @@ def apply_field(self, data: Field) -> Field:

dim_names_to_bin_size[dim_name] = bin_size

da = da.coarsen(dim_names_to_bin_size, boundary="pad").mean()
return data.assign_data(da)
return data.with_active_data(data.active_data.coarsen(dim_names_to_bin_size, boundary="pad").mean())

def apply_list(self, data: List) -> Field:
bin_edgess = _guess_bin_edgess(data, self.varname_to_nbins)
Expand Down Expand Up @@ -137,7 +134,7 @@ def apply_list(self, data: List) -> Field:
# want: psc-plot prt.i --derive K="ux^2+uy^2+uz^2" --bin K=128 -v K --scale log

new_var_infos["f"] = f_dim
return Field(da.to_dataset(name="f"), FieldMetadata.create_from(data.metadata, active_key="f", var_infos=new_var_infos))
return Field({"f": da}, FieldMetadata.create_from(data.metadata, active_key="f", var_infos=new_var_infos))

def get_name_fragments(self) -> list[str]:
subfrags = "_".join(f"{varname}={nbins}" if nbins else varname for varname, nbins in self.varname_to_nbins.items())
Expand All @@ -163,15 +160,15 @@ def parse_bin(args: list[str]) -> Bin:

if len(split_arg) == 2 and not split_arg[1]:
# arg is "t=", i.e., disable implicit binning along t
parse_util.check_value(split_arg[0], "active_key", ["t"])
parse_util.parse_value(split_arg[0], "active_key", ["t"])
insert_bin_t = False
continue
elif len(split_arg) > 2:
parse_util.fail_format(arg, _BIN_FORMAT)

[active_key, nbins_arg, *_] = split_arg + [""]

parse_util.check_identifier(active_key, "active_key")
parse_util.parse_identifier(active_key, "active_key")
nbins = parse_util.parse_optional_number(nbins_arg, "nbins", int)

varname_to_nbins[active_key] = nbins
Expand Down
2 changes: 1 addition & 1 deletion src/lib/data/adaptors/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ def apply_list(self, data: List) -> FullList:
return data.compute()

def apply_field(self, data: Field) -> Field:
return data.map_data(lambda da: da.compute())
return data.with_active_data(data.active_data.compute())
4 changes: 2 additions & 2 deletions src/lib/data/adaptors/derive.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def variable(self, toks: list):
[tok] = toks
name = str(tok)
ds = self._data.data
if name not in ds.variables:
if name not in ds:
self._resolve_from_registry(name)
return self._data.data[name]

Expand Down Expand Up @@ -129,7 +129,7 @@ def assign_default(self, toks: list):

def assignment(self, toks: list):
[new_variable, val] = toks
new_ds = self._data.data.assign({new_variable: val})
new_ds = self._data.data | {new_variable: val}
dim = var_info_registry.lookup(self._data.metadata.prefix, new_variable)
new_var_infos = {**self._data.metadata.var_infos, new_variable: dim}
return self._data.assign(new_ds, active_key=new_variable, var_infos=new_var_infos)
Expand Down
4 changes: 2 additions & 2 deletions src/lib/data/adaptors/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,11 @@ def parse(args: list[str]) -> Diff:

dims_arg, dir_arg = parse_util.parse_assignment(arg, DIFF_FORMAT)

parse_util.check_value(dir_arg, "dir", DIR_TO_SHIFT.keys())
parse_util.parse_value(dir_arg, "dir", DIR_TO_SHIFT.keys())
dir = DIR_TO_SHIFT[dir_arg]

for dim in parse_util.parse_comma_separated_list(dims_arg):
parse_util.check_identifier(dim, "dim_key")
parse_util.parse_identifier(dim, "dim_key")
diffs_1d.append(_Diff1d(dim, dir, boundary))

return Diff(diffs_1d)
2 changes: 1 addition & 1 deletion src/lib/data/adaptors/downsample.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def parse(args: list[str]) -> Downsample:
for arg in args:
[dim_name, bin_size_arg] = parse_util.parse_assignment(arg, DOWNSAMPLE_FORMAT)

parse_util.check_identifier(dim_name, "dim_name")
parse_util.parse_identifier(dim_name, "dim_name")
bin_size = parse_util.parse_number(bin_size_arg, BIN_SIZE, int)

dim_names_to_bin_size[dim_name] = bin_size
Expand Down
39 changes: 19 additions & 20 deletions src/lib/data/adaptors/fourier.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@
from lib.var_info import VarInfo


def toggle_fourier(da: xr.DataArray, dim: VarInfo) -> xr.DataArray:
def toggle_fourier(da: xr.DataArray, info: VarInfo) -> xr.DataArray:
temp_prefix = "temp_"
f_dim = dim.toggle_fourier()
f_info = info.toggle_fourier()

# multiply/or divide coords by 2pi to go from frequency <-> angular frequency

if dim.is_fourier():
da = da.assign_coords({dim.key: da.coords[dim.key] / (2 * np.pi)})
da = xrft.ifft(da, dim=dim.key, prefix=temp_prefix, lag=0.0)
da = da.rename({temp_prefix + dim.key: f_dim.key})
if info.is_fourier():
da = da.assign_coords({info.key: da.coords[info.key] / (2 * np.pi)})
da = xrft.ifft(da, dim=info.key, prefix=temp_prefix, lag=0.0)
da = da.rename({temp_prefix + info.key: f_info.key})
else:
da = xrft.fft(da, dim=dim.key, prefix=temp_prefix)
da = da.rename({temp_prefix + dim.key: f_dim.key})
da = da.assign_coords({f_dim.key: da.coords[f_dim.key] * (2 * np.pi)})
da = xrft.fft(da, dim=info.key, prefix=temp_prefix)
da = da.rename({temp_prefix + info.key: f_info.key})
da = da.assign_coords({f_info.key: da.coords[f_info.key] * (2 * np.pi)})

return da

Expand All @@ -37,18 +37,17 @@ def apply_field(self, data: Field) -> Field:
pre_dim_latexs = [data.metadata.var_infos[key].display.latex for key in self.dim_keys]

da = data.active_data
new_var_infos = dict(data.metadata.var_infos)
new_var_infos = data.metadata.var_infos.copy()

for key in self.dim_keys:
dim = new_var_infos[key]
f_dim = dim.toggle_fourier()
da = toggle_fourier(da, dim)
del new_var_infos[key]
new_var_infos[f_dim.key] = f_dim
info = new_var_infos[key]
f_info = info.toggle_fourier()
new_var_infos[f_info.key] = f_info
da = toggle_fourier(da, info)

if data.metadata.active_key is not None and data.metadata.active_key in new_var_infos:
old_active = new_var_infos[data.metadata.active_key]
new_display = f"\\mathcal{{F}}_{{{','.join(pre_dim_latexs)}}}[{old_active.display}]"
new_var_infos[data.metadata.active_key] = old_active.assign(display=new_display)
old_active_info = data.metadata.active_var_info
new_display = f"\\mathcal{{F}}_{{{','.join(pre_dim_latexs)}}}[{old_active_info.display}]"
new_var_infos[data.metadata.active_key] = old_active_info.assign(display=new_display)

return data.with_active_data(da).assign_metadata(var_infos=new_var_infos)

Expand All @@ -68,6 +67,6 @@ def get_name_fragments(self) -> list[str]:
)
def parse_fourier(args: list[str]) -> Fourier:
for dim_name in args:
parse_util.check_identifier(dim_name, "dim_name")
parse_util.parse_identifier(dim_name, "dim_name")

return Fourier(args)
Loading
Loading