Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
224 commits
Select commit Hold shift + click to select a range
1179b16
Added outer_boundary_mesh method
max-models Sep 5, 2026
ea3fa55
Clean up equations in vlasov_ampere_one_species.py
max-models Sep 5, 2026
2fe9a03
Added a few helpers
max-models Sep 5, 2026
0389afb
Added some slider and time series plotters
max-models Sep 5, 2026
a3f06c0
Log a warning if scalar doesn't exist
max-models Sep 8, 2026
c1c015e
Merge remote-tracking branch 'origin/devel' into postprocessing
max-models Sep 10, 2026
423d89e
cleanup
max-models Sep 13, 2026
3929d3b
Added a plotter for the scalars
max-models Sep 13, 2026
90e005b
Update docstring
max-models Sep 13, 2026
2b80efc
Merge branch 'devel' into postprocessing
max-models Sep 14, 2026
cf4421e
Restore .github to devel version
max-models Sep 14, 2026
5ee4389
Restored various other files (only aestetic changes
max-models Sep 14, 2026
943d9aa
formatting
max-models Sep 14, 2026
29aa51a
Merge branch 'devel' into postprocessing
max-models Sep 14, 2026
9b30e75
Added post_process() helper and load=False to sim.pproc()
max-models Sep 14, 2026
2ccbfce
Added tutorial
max-models Sep 14, 2026
92ceb23
Simplify postprocessing
max-models Sep 14, 2026
48fe9a9
Finish redesign of plotting
max-models Sep 14, 2026
baab9a6
changed the API to hierarchical namespaces
max-models Sep 14, 2026
c0a3e15
Update postprocessing tutorial for RunOutput API
max-models Sep 15, 2026
a9f67df
New post processing API
max-models Sep 15, 2026
89145e2
Updated the API
max-models Sep 15, 2026
e53b9ee
Remove extra .fig
max-models Sep 15, 2026
2e9fcd5
Write config.json and then use it for postprocessing
max-models Sep 16, 2026
04cab24
Rename run to output
max-models Sep 16, 2026
5df6b24
Removed the rel error subplot
max-models Sep 16, 2026
5504b19
Restored old tutorials
max-models Sep 16, 2026
d87a64a
formatting
max-models Sep 16, 2026
c577b2e
added remaining plots to tutorial
max-models Sep 16, 2026
56a0779
Extend tutorial to show all the plots
max-models Sep 16, 2026
e25b221
Remove the isel term
max-models Sep 16, 2026
7e9d0ba
Updated accessors
max-models Sep 16, 2026
919fe94
formatting
max-models Sep 16, 2026
2a4f4b1
Improve accessors
max-models Sep 16, 2026
48a54fc
Updated examples
max-models Sep 16, 2026
008f6ec
Cleanup
max-models Sep 16, 2026
f6929c2
Added ProductCatalog
max-models Sep 16, 2026
0673c06
Added ProductCatalog
max-models Sep 16, 2026
c8eaf4c
Merge commit '0673c06c87505bb4f46fbdc0557daf3010c41b62' into postproc…
max-models Sep 16, 2026
f2d6e39
Fixes
max-models Sep 17, 2026
674d2ee
PostProcessor.from_output
max-models Sep 17, 2026
edfce66
Removed redundant config.json
max-models Sep 17, 2026
0241851
Updated examples
max-models Sep 17, 2026
fec081d
Output no longer stores or reconstructs a Simulation
max-models Sep 17, 2026
dc4c470
Set default output path to Path(__file__)
max-models Sep 17, 2026
822d929
formatting
max-models Sep 17, 2026
8347c5c
Update docs and skill
max-models Sep 17, 2026
5358055
Support legacy plotting
max-models Sep 17, 2026
c2043dd
Improved Output.info()
max-models Sep 17, 2026
4209285
update tutorial
max-models Sep 17, 2026
4c7a4bc
pproc
max-models Sep 17, 2026
a30125a
Update managed dependency bounds (#374)
max-models Sep 16, 2026
df67469
fix pproc to proceed create_vtk without any saved data (#381)
bkna0327 Sep 18, 2026
25df651
Implemented the lazy SimulationOutput class
max-models Sep 18, 2026
eacb9fe
Move plotters to the Output class
max-models Sep 18, 2026
2712968
Added as_numpy=True to evaluate()
max-models Sep 18, 2026
f0674d2
return fig, ax
max-models Sep 18, 2026
29ebcdc
Added doc/markdown/output-api.md
max-models Sep 18, 2026
763e3d2
Added data-oriented helpers
max-models Sep 18, 2026
051ab66
Added cli commands
max-models Sep 18, 2026
3402259
fix path
max-models Sep 18, 2026
c5fab2d
Improve the report
max-models Sep 18, 2026
886a5f2
Added some helpers
max-models Sep 18, 2026
e2187b2
Added reductions and profiling data
max-models Sep 19, 2026
e01be49
Merge branch 'devel' into postprocessing
max-models Sep 19, 2026
019be8e
formatting
max-models Sep 19, 2026
b78e72d
Formatting
max-models Sep 19, 2026
405df8d
ruff check --fix
max-models Sep 19, 2026
0f337a9
Support the legacy get_plot_data() method
max-models Sep 19, 2026
0118957
Added hint on migration postprocessing
max-models Sep 19, 2026
b58e09b
mat_w = loc_weight.copy() since assembly can be repeated as density c…
max-models Sep 19, 2026
49f5e44
Bugfixes, make sure scalars are up to date and M2Bn built B_eq as cu…
max-models Sep 19, 2026
911d419
Fix vlasov maxwell one species equation alignment
max-models Sep 20, 2026
e5868c0
bugfix: Fix nested scalar sums in Scalars.update()
max-models Sep 20, 2026
3fbb653
preserve uniform periodic fields in M2Bn
max-models Sep 20, 2026
5df96de
Merge remote-tracking branch 'origin/bugfix-M2Bn' into postprocessing
max-models Sep 20, 2026
d7fdd0b
Merge remote-tracking branch 'origin/bugfix-scalars' into postprocessing
max-models Sep 20, 2026
1f9b177
Read marker arrays at every kinetic-energy update
max-models Sep 20, 2026
cbb769a
Merge branch 'bugfix-scalars' into postprocessing
max-models Sep 20, 2026
bf68b24
Implemented the quadratic thermal channel
max-models Sep 20, 2026
fa73728
Merge remote-tracking branch 'origin/add-en_thermal' into postprocessing
max-models Sep 20, 2026
ce57a9f
bugfix: do not divide the kinetic-energy scalars by Np twice
max-models Sep 20, 2026
cfe381f
Merge branch 'bugfix-kinetic-energy-normalization' into postprocessing
max-models Sep 20, 2026
a6e44e6
Print a line in the logger
max-models Sep 20, 2026
3553c64
Hasegawa–Wakatani bugfixes
max-models Sep 20, 2026
cd9ba6f
Merge branch 'bugfix-hasegawa-wakatani' into postprocessing
max-models Sep 20, 2026
15639c1
Fix Hasegawa–Wakatani coupling and linear delta-f initialization
max-models Sep 20, 2026
63d8cc1
Merge branch 'bugfix-hasegawa-wakatani' into postprocessing
max-models Sep 20, 2026
cf2db04
Add newline to timestep print
max-models Sep 20, 2026
cc7031f
Fix verification test
max-models Sep 20, 2026
9e69bed
Merge branch 'bugfix-hasegawa-wakatani' into postprocessing
max-models Sep 20, 2026
8839520
Merge branch 'devel' into bugfix-kinetic-energy-normalization
max-models Sep 21, 2026
012163e
Merge branch 'bugfix-kinetic-energy-normalization' into postprocessing
max-models Sep 21, 2026
16b79ec
Update pitagora modules
max-models Sep 21, 2026
5c3892b
Merge branch 'devel' into postprocessing
max-models Sep 21, 2026
e5ec3d9
Replace lists with plain scalar selection in mass_kernels.py
max-models Sep 21, 2026
7b7fb0c
Merge branch 'mass-kernels-without-gftl' into postprocessing
max-models Sep 21, 2026
16c3da6
Merge branch 'devel' into postprocessing
max-models Sep 21, 2026
17b934c
formatting
max-models Sep 23, 2026
d14b9cb
Bump scope-profiler version to 0.7.2
max-models Sep 23, 2026
9b736d0
Improve show distribution function (#390)
spossann Sep 22, 2026
8267cae
Fix B0 handling in GyroMaxwellian2D class for callable cases (#394)
bkna0327 Sep 23, 2026
5e32261
Added safe yaml representers for numpy scalars and arrays
max-models Sep 23, 2026
ce73832
remove notebook output; split some cells for better readability.
spossann Sep 23, 2026
5ab34fa
use new post-processing in feec_bcs notebook
spossann Sep 23, 2026
0698826
adapt all tutorials to new pproc with out
spossann Sep 24, 2026
c77178b
Extend the to_dict() method
max-models Sep 24, 2026
efddcb6
Added initial conditions to run_metadata
max-models Sep 24, 2026
04d8cf7
serialize the user defined functions
max-models Sep 24, 2026
0d33dd8
Added _deserialize_initial_condition
max-models Sep 24, 2026
10ab30c
Added versioning schema and roundtrip test
max-models Sep 24, 2026
48275de
Serialize perturbations
max-models Sep 24, 2026
b77170c
Extend the Simulation.from_file classmethod
max-models Sep 24, 2026
eb58301
Read the full json in the Output class
max-models Sep 24, 2026
fefb91e
Update tutorial
max-models Sep 24, 2026
eb0efca
Make out.info() print
max-models Sep 24, 2026
dfc620c
Added params_LinearMHDDriftkineticCC.py
max-models Sep 24, 2026
ad61ab3
Merge branch 'postprocessing' of github.com:struphy-hub/struphy into …
max-models Sep 24, 2026
3cbef49
cleanup
max-models Sep 24, 2026
4924d6a
Removed the PostProcessor class and integrated the functionality into…
max-models Sep 24, 2026
1f07aa0
Move time_units to process
max-models Sep 24, 2026
6ee4183
Remove the trust flag
max-models Sep 24, 2026
5c1fd41
Added spline_fields
max-models Sep 24, 2026
abc4f32
Removed Output.__getitem__
max-models Sep 24, 2026
11227e1
Updated the out.spline_fields to use a t=.. variable
max-models Sep 24, 2026
d8ad40f
Simplify the evaluate method
max-models Sep 24, 2026
c98a591
snapshots in time
max-models Sep 24, 2026
e608ad5
Enable specify etas
max-models Sep 24, 2026
11f7e21
Support ranges and lists
max-models Sep 24, 2026
23b0e16
enforce etas on logical unit cube
max-models Sep 24, 2026
f2dd7a9
Added representation arg
max-models Sep 24, 2026
3a4f6e9
Update tutorial
max-models Sep 24, 2026
460d0be
Cache the spline coeffients for each called t
max-models Sep 24, 2026
7e7b050
added manifest.py
max-models Sep 24, 2026
1d17c21
Output.evaluate() now infers the source representation from the saved…
max-models Sep 24, 2026
9a01866
updat tutorial
max-models Sep 24, 2026
374074b
Added more tests
max-models Sep 24, 2026
66f8bfc
Moved plotting scripts to a separate postprocessing_external folder
max-models Sep 24, 2026
986d958
Removed the legacy postprocessing code
max-models Sep 24, 2026
09ce9fa
Update tutorials
max-models Sep 24, 2026
d09fb7e
Update docs
max-models Sep 24, 2026
7f5e080
update claude skill
max-models Sep 24, 2026
dcb7476
Merge branch 'devel' into postprocessing
max-models Sep 24, 2026
cc8cba9
Set default etato 0.5 in evaluate
max-models Sep 24, 2026
450c671
Include mapped physical X, Y, and Z coordinates
max-models Sep 24, 2026
a1ecdb7
Removed numpy return, added scalars evaluation, pic/sph binning defaults
max-models Sep 24, 2026
24ad50c
Temporary: add the postprocesssing scripts in postprocessing_external/
max-models Sep 24, 2026
d97fe41
formatting
max-models Sep 24, 2026
e1cc5cd
Removed legacy plots
max-models Sep 25, 2026
11b41ac
Add plot of spectrum = out.dispersion(...); spectrum.power.plot(...)
max-models Sep 25, 2026
42ccfea
Moved the power spectrum logic to Output
max-models Sep 25, 2026
f1cf9ef
Removed legacy plotting scripts
max-models Sep 25, 2026
d4e1d2b
Added quickstart.py
max-models Sep 25, 2026
c991514
Deleted postprocessing_external
max-models Sep 25, 2026
239e3d6
adapt quickstart
spossann Sep 25, 2026
73c030e
Fix the to_dict() and from dict for the marker params
max-models Sep 25, 2026
a379a07
adapt particel tracing notebook
spossann Sep 25, 2026
847eb6f
Removed Output.dispersion() and Output.plot()
max-models Sep 25, 2026
bf8b5c0
formatting
max-models Sep 25, 2026
5c714d7
remove quickstart.py
max-models Sep 25, 2026
4927248
improve out.info() to show keys only
spossann Sep 25, 2026
cb68f99
Fixed serialization, include derived or legacy keys not accepted by t…
max-models Sep 25, 2026
920721c
changed the orbits logic for Output: dataset insted of dataarray
spossann Sep 25, 2026
524478c
Merge branch 'postprocessing' of github.com:struphy-hub/struphy into …
max-models Sep 25, 2026
1c4ec1c
formatting
max-models Sep 25, 2026
88523ac
updated examples to the new API
max-models Sep 25, 2026
863a15d
More bugfixes for serialization
max-models Sep 25, 2026
bbd3846
Formatting
max-models Sep 25, 2026
af88d17
Optionally plot equil
max-models Sep 25, 2026
d092511
fix tutorial
max-models Sep 25, 2026
c08d304
Set default options in two_fluid_quasi_neutral_full.py
max-models Sep 25, 2026
b437e24
Fix imports
max-models Sep 25, 2026
f2701b2
Build dispersion relation in the tutorials
max-models Sep 25, 2026
69c5ff7
Fix to_dict()
max-models Sep 25, 2026
bd1e916
Better explanation on why two sims are not equal
max-models Sep 25, 2026
c2ad7df
Fix test_output.py
max-models Sep 25, 2026
57e7d60
Update postprocessing tutorial
max-models Sep 25, 2026
4a0be82
Fix verification tests
max-models Sep 25, 2026
e40a4d1
Update tutorials
max-models Sep 25, 2026
0adc539
formatting
max-models Sep 25, 2026
3a520c6
Fix test_verif_ViscousEulerSPH.py
max-models Sep 25, 2026
50fedde
update docs
max-models Sep 25, 2026
c5cddb5
Update tests
max-models Sep 25, 2026
e80ed19
datacontainer bugfixes
max-models Sep 25, 2026
be1bd3c
Update struphy output cli
max-models Sep 25, 2026
6369139
Add the missing struphy output CLI module and DataContainer tests
max-models Sep 25, 2026
7f979f1
Bugfix for frozen test
max-models Sep 25, 2026
d00b30f
Commented out nbsphinx_kernel_name
max-models Sep 26, 2026
b55437d
Removed catalog from api docs
max-models Sep 26, 2026
e1a13a4
Removed out_folders from quickstart
max-models Sep 26, 2026
248ea1d
Removed pproc() from output-api.md, added commends on MPI postprocessing
max-models Sep 26, 2026
1e3916d
Pass parallel: bool = False to evaluate()
max-models Sep 26, 2026
d670e54
Removed params_LinearMHDDriftkineticCC.py
max-models Sep 26, 2026
afd714c
Use new api in pproc_weibel_instability.py
max-models Sep 26, 2026
4c6b174
Use new API in test_verif_IncompressibleNavierStokesSPH.py
max-models Sep 26, 2026
cc91b75
Updated remaining examples to new API
max-models Sep 26, 2026
7f133d6
Update tutorials/tutorial_dam_break_sph.ipynb
max-models Sep 26, 2026
03440f7
Port tutorials and docs to new API
max-models Sep 26, 2026
3f4ee51
Updated doc/sections/userguide.rst
max-models Sep 26, 2026
6e603f1
Use t=0 in examples/VlasovAmpereOneSpecies/bump_on/pproc_bump_on.py
max-models Sep 26, 2026
baad8e4
bugfix: Assert compared raw diff instead of relative
max-models Sep 26, 2026
3f474b9
Fixed ZeroDivisionError at pproc_weibel_instability.py, directions h…
max-models Sep 26, 2026
fcb2b55
Tutorial bugfix
max-models Sep 26, 2026
46a195a
Merge branch 'devel' into postprocessing
max-models Sep 26, 2026
29457ca
Bugfix with new api in tutorial_poisson.ipynb
max-models Sep 26, 2026
22e97ee
Renamed e1,e2,e3 to eta1,eta2,eta3
max-models Sep 26, 2026
cc1643f
Mention struphy-plots integration
max-models Sep 27, 2026
1963d56
Merge branch 'devel' into struphy-plots-integration
max-models Sep 27, 2026
eadc733
Updated docstrings
max-models Sep 27, 2026
f0860a3
Enable evaluate() without pproc() under mpirun
max-models Sep 27, 2026
5c2c536
evaluate() now accepts species/dataset/variable names
max-models Sep 27, 2026
8ba9fcb
Enable parallel processing in evaluate()
max-models Sep 27, 2026
223ddc7
Merge branch 'evaluate-under-mpirun' into struphy-plots-integration
max-models Sep 27, 2026
63904b8
Merge branch 'devel' into evaluate-under-mpirun
max-models Sep 27, 2026
4f4a033
Merge branch 'evaluate-under-mpirun' into struphy-plots-integration
max-models Sep 27, 2026
a0563b7
Bugfix for output testing with mpi
max-models Sep 27, 2026
cc919d0
Rename struphy-plots to plasma-plots
max-models Sep 28, 2026
caddd22
Merge branch 'evaluate-under-mpirun' into struphy-plots-integration
max-models Sep 28, 2026
e70afb7
Merge branch 'devel' into struphy-plots-integration
max-models Sep 29, 2026
48e5151
Added plasma-plots to struphy-pproc
max-models Sep 29, 2026
dce862b
Removed plots optional dep
max-models Sep 29, 2026
f0eaffb
Merge branch 'devel' into struphy-plots-integration
max-models Sep 30, 2026
139b8b9
Merge branch 'devel' into struphy-plots-integration
max-models Sep 30, 2026
f41f918
Merge branch 'devel' into struphy-plots-integration
max-models Sep 30, 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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ This will create `params_Maxwell.py` in your current working directory (cwd). Yo

The default output is in `sim_1/` in your cwd. You can change the output path via the class `EnvironmentOptions` in the parameter file.

For plots and diagnostics of the output, install [plasma-plots](https://struphy-hub.github.io/plasma-plots) with `pip install "struphy[pproc]"`; `Output` loads it automatically, adding `out.plot`, `out.analysis` and `.plasma.plot` on every product.

Parallel simulations are run for example with

pip install -U mpi4py
Expand Down
2 changes: 2 additions & 0 deletions README.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ python params_Maxwell.py

The default output is in `sim_1/` in your cwd. You can change the output path via the class `EnvironmentOptions` in the parameter file.

For plots and diagnostics of the output, install [plasma-plots](https://struphy-hub.github.io/plasma-plots) with `pip install "struphy[pproc]"`; `Output` loads it automatically, adding `out.plot`, `out.analysis` and `.plasma.plot` on every product.

Parallel simulations are run for example with

```
Expand Down
30 changes: 28 additions & 2 deletions doc/markdown/output-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ distribution = out.evaluate("kinetic_ions/f")
delta_f = out.evaluate("kinetic_ions/f", dataset="e1_v1_density/delta_f")
```

To make a figure, select the dimensions to show and call xarray's native `.plot()` methods (see
[Plot data](#plot-data)).
To make a figure, select the dimensions to show and call xarray's native `.plot()` methods, or
use the plots and diagnostics of the plasma-plots package (see [Plot data](#plot-data)).

## Analyze and report data

Expand Down Expand Up @@ -219,6 +219,32 @@ out.evaluate("diagnostics/rho_xyz", t=-1, eta3=0.5, method="nearest").plot(x="et
xarray squeezes size-one dimensions before plotting, so an array that is 2-D on a grid with one
cell in some direction plots as a line. Select until the array has the dimensions the plot needs.

### Plots and diagnostics with plasma-plots

For plots and diagnostics made for Struphy output, install the separate package
[plasma-plots](https://struphy-hub.github.io/plasma-plots):

```bash
pip install plasma-plots # or: pip install "struphy[pproc]"
```

When it is installed, every `Output` loads it: no import is needed. It adds `out.plot` and
`out.analysis` for a whole run, and `.plasma.plot`, `.plasma.analysis` and `.plasma.data` on
every product:

```python
out.plot.energies() # the energy budget of the run
phi = out.evaluate("em_fields/phi")
phi.plasma.plot.slice(coords="physical", plane="XY", t=-1, eta3=0)
phi.plasma.plot.animation(x="eta1", y="eta2", eta3=0) # over time
phi.plasma.analysis.mode_spectrum() # poloidal and toroidal mode numbers
out.kinetic_ions.orbits.plasma.plot.poloidal() # guiding-center orbits
```

`import plasma_plots; help(plasma_plots)` gives an overview of what the package does, and `help()` on any method,
e.g. `help(phi.plasma.plot.slice)`, its parameters. Its guides and full reference are at
<https://struphy-hub.github.io/plasma-plots>.

## MPI post-processing

`Output` always uses `MPI.COMM_WORLD`; no communicator is passed to its constructor.
Expand Down
5 changes: 4 additions & 1 deletion doc/sections/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,10 @@ For periodic boundary conditions we will stabilize via ``options``.
sim = Simulation(model=model, domain=domain, grid=grid)

6. Run the simulation. ``sim.run()`` returns an :class:`~struphy.Output` object,
the entry point for all post-processing.
the entry point for all post-processing. For plots and diagnostics made for Struphy
output (``out.plot``, ``out.analysis`` and ``.plasma.plot`` on every product), install
the separate package `plasma-plots <https://struphy-hub.github.io/plasma-plots>`_ with
``pip install "struphy[pproc]"``; ``Output`` loads it automatically.

.. code-block:: python

Expand Down
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,12 @@ phys = [
"gvec>=1.1.0, <=1.5.0",
"desc-opt<=0.17.1",
]
pproc = [
"plasma-plots>=0.1.0",
]
dev = [
"struphy[mpi]",
"struphy[pproc]",
"notebook",
"autopep8",
"isort",
Expand All @@ -83,6 +87,7 @@ dev = [
]
doc = [
"struphy[phys]",
"struphy[pproc]",
"jupyter",
"nbconvert",
"ipykernel",
Expand All @@ -106,6 +111,7 @@ likwid = [
]
all = [
"struphy[phys]",
"struphy[pproc]",
"struphy[dev]",
"struphy[mpi]",
"struphy[doc]",
Expand Down
57 changes: 56 additions & 1 deletion src/struphy/post_processing/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,32 @@ def mpi_comm_world():
return MPI.COMM_WORLD


PLOTS_HINT = (
"plots and diagnostics of Struphy output come from the plasma-plots package: "
"pip install plasma-plots (or struphy[pproc]); see https://struphy-hub.github.io/plasma-plots"
)
_plots = {"loaded": False}


def load_plasma_plots() -> bool:
"""Load plasma-plots if it is installed, and tell whether it is.

Importing ``plasma_plots`` registers ``out.plot``, ``out.analysis`` and the ``.plasma``
accessor on every product. :class:`Output` calls this when it is created, so none of that
needs an explicit ``import plasma_plots``. Struphy does not depend on the package.
"""
if not _plots["loaded"]:
try:
import plasma_plots # noqa: F401 (registers the accessors)
except ImportError:
return False
except Exception as error: # a broken install must not break reading output
logger.warning("plasma-plots is installed but could not be imported: %s", error)
return False
_plots["loaded"] = True
return True


class ProductMapping(Mapping[str, xr.DataArray]):
"""A discoverable mapping whose products are loaded on first access."""

Expand Down Expand Up @@ -206,13 +232,28 @@ class Output:
reconstructed lazily from saved metadata. No simulation object is created or retained.
* Every array carries the run in ``attrs["run"]`` (:attr:`label`) and ``attrs["run_name"]``.

**Plots and diagnostics** of the output live in the separate package
`plasma-plots <https://struphy-hub.github.io/plasma-plots>`_ (``pip install plasma-plots``,
or ``pip install "struphy[pproc]"``). When it is installed, creating an ``Output`` loads it,
which adds:

* ``out.plot`` and ``out.analysis``: whole-run plots and diagnostics, e.g.
``out.plot.energies()``, ``out.analysis.time_fft("em_fields/phi")``;
* ``.plasma.plot``, ``.plasma.analysis`` and ``.plasma.data`` on every product, e.g.
``out.evaluate("em_fields/phi").plasma.plot.slice(x="eta1", y="eta2", t=-1)``, or
``orbits.plasma.plot.poloidal()`` for an orbits Dataset.

``import plasma_plots; help(plasma_plots)`` gives an overview of the package, and ``help()``
on any accessor method (e.g. ``help(phi.plasma.plot.slice)``) its parameters.

Parameters
----------
path_out:
The simulation output folder, ``sim.env.path_out``.
"""

def __init__(self, path_out):
load_plasma_plots() # out.plot, out.analysis and .plasma on every product, if installed
self.path_out = Path(path_out).resolve()
self._time_units = "normalized"
self.comm = mpi_comm_world()
Expand Down Expand Up @@ -317,6 +358,8 @@ def evaluate(
products, a ``"species/variable"`` name selects the first matching binned
product, then density/KDE product, then orbits. Pass ``dataset=`` to select a
particular discovered product; use ``out.info("species/variable")`` to list them.
The full key of a particle product, ``"species/dataset/variable"`` (as :meth:`keys`
lists it, e.g. ``"kinetic_ions/e1_v1_density/f"``), selects that product directly.

Supplying an ``eta`` evaluates a raw FEEC spline field directly on that logical
grid. Each eta can be a scalar, a list, a one-dimensional array, or a ``range``;
Expand All @@ -337,8 +380,16 @@ def evaluate(
raise TypeError("physical is no longer supported; use eta1, eta2, eta3 and representation")
if "as_numpy" in selectors:
raise TypeError("evaluate() always returns xarray; call .to_numpy() on its result when needed")
if name.count("/") == 2:
# the full key of a particle product: "species/dataset/variable"
if dataset is not None:
raise ValueError("dataset= cannot be combined with a 'species/dataset/variable' name")
species, group, variable = name.split("/")
name, dataset = f"{species}/{variable}", f"{group}/{variable}"
if name != "scalars" and name.count("/") != 1:
raise ValueError("evaluate() names must use the 'species/variable' form, or be 'scalars'")
raise ValueError(
"evaluate() names must use the 'species/variable' or 'species/dataset/variable' form, or be 'scalars'"
)

eta = (eta1, eta2, eta3)
has_eta = any(value is not None for value in eta)
Expand Down Expand Up @@ -2056,6 +2107,10 @@ def __getattr__(self, name: str) -> ProductNamespace:
attribute.func(self)
if isinstance(attribute, property):
attribute.fget(self) # the property raised AttributeError itself; show its own error
if name in ("plot", "analysis"):
if load_plasma_plots() and name in type(self).__dict__:
return getattr(self, name)
raise AttributeError(f"Output has no {name!r} without plasma-plots; {PLOTS_HINT}")
# the raw output names the species, so an unknown name never starts post-processing
if name not in self._raw_species():
raise AttributeError(f"{name!r}; available species: {tuple(sorted(self._raw_species()))}")
Expand Down
41 changes: 41 additions & 0 deletions src/struphy/post_processing/tests/test_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,35 @@ def save_raw_field(run, *names):
file["feec/em_fields"].create_dataset(name, data=np.empty(0))


# Environment prefixes through which MPI launchers tell a process which rank of a job it is.
MPI_LAUNCHER_ENV_PREFIXES = (
"OMPI_",
"OPAL_",
"PMIX_",
"PMI_",
"PRTE_",
"MV2_",
"HYDRA_",
"I_MPI_",
"MPI_LOCALRANKID",
"ALPS_",
"PALS_",
)


@pytest.fixture
def outside_mpi_job(monkeypatch):
"""Let child processes start as independent programs, not as ranks of this test's MPI job.

Importing struphy initializes MPI. A child that inherits the launcher variables of an
``mpirun`` rank initializes as that same rank, which hangs or corrupts the parent job.
"""
for name in list(os.environ):
if name.startswith(MPI_LAUNCHER_ENV_PREFIXES):
monkeypatch.delenv(name)
monkeypatch.setenv("STRUPHY_MPI", "0")


@pytest.fixture
def run(tmp_path):
return Output(write_tree(str(tmp_path)))
Expand Down Expand Up @@ -347,6 +376,18 @@ def test_evaluate_scalars_and_particle_defaults(run):
selected = run.evaluate("kinetic_ions/f", dataset="e1_v1_density/delta_f")
assert selected.name == "delta_f"

# the full key selects the same product, with selections as keywords
by_key = run.evaluate("kinetic_ions/e1_v1_density/delta_f")
xr.testing.assert_identical(by_key, selected)
first = float(by_key.eta1[0])
xr.testing.assert_identical(
run.evaluate("kinetic_ions/e1_v1_density/delta_f", eta1=first), selected.sel(eta1=first)
)
with pytest.raises(ValueError, match="dataset="):
run.evaluate("kinetic_ions/e1_v1_density/f", dataset="e1_v1_density/f")
with pytest.raises(KeyError):
run.evaluate("kinetic_ions/no_such_density/f")

orbits = run.evaluate("kinetic_ions/orbits")
assert isinstance(orbits, xr.Dataset) and "weight" in orbits.data_vars

Expand Down
Loading