✨ Update Python version and enhance plasma loading features#27
✨ Update Python version and enhance plasma loading features#27munechika-koyo wants to merge 89 commits intomasterfrom
Conversation
Use latest python version for CI
…n using `plotly` to changelog
…nd enhance documentation clarity
…ta, and SpeciesComposition dataclasses; refactor get_ion_state and get_neutral_state functions for improved clarity and functionality.
…ion calculation in coronal equilibrium; update `__init__.py` to include the new function.
…oved structure and clarity; enhance documentation and streamline data handling.
…tion` dataclass for improved type safety and clarity; enhance parameter documentation.
…ators; enhance `load_core_plasma` function to support atomic data and improve species distribution handling.
…aise a RuntimeError if the toroidal component of the magnetic field is not found; add fallback logic to DDv3
…es; enhance `ProfileData` to include velocity data.
…eper structure retrieval; improve handling of `IDSNumericArray` and add return type checks.
…e handling; remove unused `ProfileInterporater` dataclass and improve error checks for electron properties.
…ture, and velocity interpolating functions; improve warning messages for unsupported species.
…utilize `ProfileData` and `SpeciesComposition` dataclasses; enhance profile loading logic and streamline data handling for species and velocities.
…g and streamline species distribution logic; enhance profile interpolation with `ProfileInterporater` dataclass.
Use data from dataclasses implemented before
To improve plotting functions and add functionality for splitting bundled species profiles
…nd improve density checks for split species
… Vector3DFunction2D
…asma loading and documentation
…ent passing in exception handling
… dataset filename
…profile interpolation
…prove profile loading logic
Add test for calling magnetic field function
To link aginst the latest version's cherab
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #27 +/- ##
==========================================
+ Coverage 49.50% 50.49% +0.98%
==========================================
Files 43 45 +2
Lines 2137 2646 +509
Branches 349 452 +103
==========================================
+ Hits 1058 1336 +278
- Misses 953 1133 +180
- Partials 126 177 +51 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR modernizes the IMAS plasma loading stack by introducing structured datamodels for species/profile data, adding ion-bundle splitting via a coronal-equilibrium solver, and updating tooling/docs/CI for newer Python and notebook workflows.
Changes:
- Add dataclass-based species/profile models and refactor core/edge/full plasma loaders to use them (incl. optional ion-bundle splitting).
- Add coronal-equilibrium solver utility plus dataset patching for JINTRAC sample data.
- Add Plotly-based 3D bolometer camera visualization and update docs/notebooks, CI, and Pixi environments for Python 3.14.
Reviewed changes
Copilot reviewed 40 out of 40 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/plasma/test_equilibrium.py | Update magnetic-field return-type expectation and add basic callability checks. |
| tests/plasma/test_edge.py | Consolidate edge plasma tests and add assertions for ion-bundle splitting behavior. |
| tests/plasma/test_core.py | Consolidate core plasma tests and add assertions for ion-bundle splitting behavior. |
| tests/plasma/test_blend.py | Consolidate blended plasma tests and add assertions for ion-bundle splitting behavior. |
| tests/plasma/conftest.py | Add fixtures for additional sample datasets (SOLPS/JOREK). |
| tests/conftest.py | Add autouse OpenADAS repository population hook for tests. |
| src/cherab/imas/plasma/utility.py | Introduce ProfileInterpolator + ZERO_VELOCITY and update species-warning utility for new datamodels. |
| src/cherab/imas/plasma/equilibrium.py | Adjust Raysect interpolator imports/types; update load_magnetic_field() return type hint. |
| src/cherab/imas/plasma/edge.py | Refactor edge plasma loading to new dataclasses, add ion-bundle splitting controls + atomic data injection. |
| src/cherab/imas/plasma/core.py | Refactor core plasma loading to new dataclasses, add ion-bundle splitting controls + atomic data injection. |
| src/cherab/imas/plasma/blend.py | Refactor core/edge blending pipeline to dataclass interpolators and new composition handling. |
| src/cherab/imas/observer/bolometer.py | Add Plotly-based 3D camera visualize() helper and supporting geometry extraction utilities. |
| src/cherab/imas/math/functions/vector_functions.pyx | Minor docstring grammar tweaks. |
| src/cherab/imas/math/functions/vector_functions.pyi | Minor docstring grammar tweaks. |
| src/cherab/imas/ids/wall/load3d.py | Improve docstring formatting with proper Sphinx code-block. |
| src/cherab/imas/ids/wall/load2d.py | Improve docstring typing markup consistency. |
| src/cherab/imas/ids/equilibrium/load_field.py | Add b_field_phi/b_field_tor fallback logic and deprecation warning. |
| src/cherab/imas/ids/edge_profiles/load_profiles.py | Replace dict-based profiles with dataclasses; implement optional ion-bundle splitting via coronal equilibrium. |
| src/cherab/imas/ids/core_profiles/load_profiles.py | Add GridData dataclass; replace dict-based profiles with dataclasses; implement optional ion-bundle splitting. |
| src/cherab/imas/ids/core_profiles/init.py | Export GridData in package API. |
| src/cherab/imas/ids/common/species.py | Add species/profile/composition dataclasses and replace legacy ID frozenset logic. |
| src/cherab/imas/ids/common/_model.py | Add coronal-equilibrium solver implementation. |
| src/cherab/imas/ids/common/init.py | Export solve_coronal_equilibrium. |
| src/cherab/imas/ggd/unstruct_2d_extend_mesh.py | Docstring section ordering tweak. |
| src/cherab/imas/datasets/_registry.py | Update dataset registry entries (incl. patched JINTRAC variant). |
| src/cherab/imas/datasets/_patch.py | Add JINTRAC patching utility to fix bundled ion z-range metadata. |
| src/cherab/imas/datasets/_fetchers.py | Apply JINTRAC patch automatically after fetch; tighten fetcher dependency guard. |
| pyproject.toml | Add Python 3.13/3.14 classifiers. |
| pixi.toml | Add Python 3.14 build variant, add plotly + nbstripout, restructure tasks/environments. |
| docs/source/intro.md | Update citation DOI. |
| docs/source/examples.md | Adjust examples globbing path. |
| docs/source/conf.py | Sphinx napoleon config tweak; simplify HTML title. |
| docs/notebooks/plasma/full_plasma.ipynb | Update narrative, add ADAS download guidance, adjust plotting utilities, add Zeff example. |
| docs/notebooks/plasma/emission.ipynb | Rework OpenADAS setup guidance and emission workflow; update plotting and sampling strategy. |
| docs/notebooks/plasma/edge_plasma.ipynb | Update plotting stack (ultraplot) and add ion-bundle splitting demonstration. |
| docs/notebooks/misc/fractional_abundances.ipynb | New notebook demonstrating coronal-equilibrium fractional abundances. |
| CHANGELOG.md | Add 0.4.0 release notes entry. |
| .lefthook.yaml | Update pre-commit job structure; add nbstripout; adjust CI skips. |
| .github/workflows/docs.yml | Remove Pixi version pin. |
| .github/workflows/ci.yaml | Switch matrix env from py313 to py314; remove Pixi version pin. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if z_min >= z_max: | ||
| raise ValueError("z_min cannot be greater than or equal to z_max.") |
There was a problem hiding this comment.
solve_coronal_equilibrium() currently rejects z_min == z_max (raises when z_min >= z_max). A single charge state (or a bundle of exactly one state) is a valid edge case and should return an array with one row equal to density (or at least be allowed by changing the validation to z_min > z_max).
| if z_min >= z_max: | |
| raise ValueError("z_min cannot be greater than or equal to z_max.") | |
| if z_min > z_max: | |
| raise ValueError("z_min cannot be greater than z_max.") |
| (local_x_axis, "X Axis", "rgb(256, 0, 0)"), | ||
| (local_y_axis, "Y Axis", "rgb(0, 256, 0)"), | ||
| (local_z_axis, "Z Axis", "rgb(0, 0, 256)"), |
There was a problem hiding this comment.
Plotly RGB strings should use channel values in the 0–255 range. The current axis colors use 256, which can lead to invalid color parsing; use 255 instead (or hex colors).
| (local_x_axis, "X Axis", "rgb(256, 0, 0)"), | |
| (local_y_axis, "Y Axis", "rgb(0, 256, 0)"), | |
| (local_z_axis, "Z Axis", "rgb(0, 0, 256)"), | |
| (local_x_axis, "X Axis", "rgb(255, 0, 0)"), | |
| (local_y_axis, "Y Axis", "rgb(0, 255, 0)"), | |
| (local_z_axis, "Z Axis", "rgb(0, 0, 255)"), |
| camera.parent = world | ||
|
|
||
| # === Local Axis === | ||
| local_origin = ORIGIN + sum( | ||
| [ORIGIN.vector_to(foil.slit.centre_point) for foil in camera.foil_detectors], | ||
| Vector3D(0, 0, 0), | ||
| ) / len(camera.foil_detectors) | ||
| local_z_axis: Vector3D = sum( | ||
| [foil.slit.normal_vector.normalise() for foil in camera.foil_detectors], Vector3D(0, 0, 0) | ||
| ).normalise() | ||
| local_x_axis: Vector3D = sum( | ||
| [foil.slit.basis_x.normalise() for foil in camera.foil_detectors], Vector3D(0, 0, 0) | ||
| ).normalise() | ||
|
|
||
| local_y_axis = local_z_axis.cross(local_x_axis).normalise() | ||
|
|
There was a problem hiding this comment.
visualize() mutates camera.parent to attach it to a temporary World, but restoration happens only on the normal return path. If any exception is raised during plotting/ray tracing, camera.parent will remain modified. Wrap the temporary parent assignment in a try/finally to guarantee restoration (and consider similarly scoping temporary scene objects).
| camera.parent = world | |
| # === Local Axis === | |
| local_origin = ORIGIN + sum( | |
| [ORIGIN.vector_to(foil.slit.centre_point) for foil in camera.foil_detectors], | |
| Vector3D(0, 0, 0), | |
| ) / len(camera.foil_detectors) | |
| local_z_axis: Vector3D = sum( | |
| [foil.slit.normal_vector.normalise() for foil in camera.foil_detectors], Vector3D(0, 0, 0) | |
| ).normalise() | |
| local_x_axis: Vector3D = sum( | |
| [foil.slit.basis_x.normalise() for foil in camera.foil_detectors], Vector3D(0, 0, 0) | |
| ).normalise() | |
| local_y_axis = local_z_axis.cross(local_x_axis).normalise() | |
| try: | |
| camera.parent = world | |
| # === Local Axis === | |
| local_origin = ORIGIN + sum( | |
| [ORIGIN.vector_to(foil.slit.centre_point) for foil in camera.foil_detectors], | |
| Vector3D(0, 0, 0), | |
| ) / len(camera.foil_detectors) | |
| local_z_axis: Vector3D = sum( | |
| [foil.slit.normal_vector.normalise() for foil in camera.foil_detectors], | |
| Vector3D(0, 0, 0), | |
| ).normalise() | |
| local_x_axis: Vector3D = sum( | |
| [foil.slit.basis_x.normalise() for foil in camera.foil_detectors], | |
| Vector3D(0, 0, 0), | |
| ).normalise() | |
| local_y_axis = local_z_axis.cross(local_x_axis).normalise() | |
| finally: | |
| camera.parent = prev_parent |
| @pytest.fixture(scope="session", autouse=True) | ||
| def populate_openadas_repository(): | ||
| """Fixture to populate the OpenADAS repository before running tests.""" | ||
| adas = OpenADAS(missing_rates_return_null=False) | ||
| try: | ||
| adas.ionisation_rate(neon, 1) | ||
| except Exception: | ||
| print("Populating OpenADAS repository...") | ||
| populate() |
There was a problem hiding this comment.
This autouse session fixture can trigger cherab.openadas.repository.populate() during test collection, which may download data from the network and make the test suite flaky/offline-unfriendly. Consider avoiding network access in tests (e.g., ship minimal ADAS test data, use a pre-populated cache in CI, or skip these tests when data is unavailable), and catch a narrower exception than Exception so unexpected errors aren’t silently turned into downloads.
|
|
||
| The ion bundle species are split into their constituent charge states using `.solve_coronal_equilibrium`. |
There was a problem hiding this comment.
The docstring says ion bundle species “are split into their constituent charge states”, but the implementation is best-effort: splitting depends on split_ion_bundles and available atomic data, and failures fall back to leaving bundles unsplit with a warning. Please clarify the docstring to reflect the conditional/partial behavior.
Key changes include:
Support for Python 3.14 and update CI configurations.
Addition of
plotlydependency for 3D visualization of bolometer cameras.Introduction of new data models (
SpeciesData,ProfileData,SpeciesComposition,VelocityData) for improved plasma data handling.Refactoring of core and edge profile loading functions to utilize dataclasses for better structure and clarity.
Enhancement of documentation and Sphinx configuration for improved clarity and consistency.
Fixes for regression in magnetic field loading and path handling in dataset workflows.