From e4273b17360e87cc0298c5cde548aab7285de9b6 Mon Sep 17 00:00:00 2001 From: mafshari64 Date: Mon, 28 Apr 2025 11:48:37 +0200 Subject: [PATCH 01/15] adding openpmd.py and charge_density.py --- .../picongpu/picmi/diagnostics/__init__.py | 2 + .../picongpu/picmi/diagnostics/openpmd.py | 154 ++++++++++++++++++ .../diagnostics/openpmd_sources/__init__.py | 32 ++++ .../openpmd_sources/bound_electron_density.py | 74 +++++++++ .../openpmd_sources/charge_density.py | 73 +++++++++ .../diagnostics/openpmd_sources/source.py | 86 ++++++++++ .../picongpu/pypicongpu/output/openpmd.py | 138 ++++++++++++++++ .../output/openpmd_source/__init__.py | 29 ++++ .../output/openpmd_source/charge_density.py | 37 +++++ 9 files changed, 625 insertions(+) create mode 100644 lib/python/picongpu/picmi/diagnostics/openpmd.py create mode 100644 lib/python/picongpu/picmi/diagnostics/openpmd_sources/__init__.py create mode 100644 lib/python/picongpu/picmi/diagnostics/openpmd_sources/bound_electron_density.py create mode 100644 lib/python/picongpu/picmi/diagnostics/openpmd_sources/charge_density.py create mode 100644 lib/python/picongpu/picmi/diagnostics/openpmd_sources/source.py create mode 100644 lib/python/picongpu/pypicongpu/output/openpmd.py create mode 100644 lib/python/picongpu/pypicongpu/output/openpmd_source/__init__.py create mode 100644 lib/python/picongpu/pypicongpu/output/openpmd_source/charge_density.py diff --git a/lib/python/picongpu/picmi/diagnostics/__init__.py b/lib/python/picongpu/picmi/diagnostics/__init__.py index e54881b1a7..91584d1334 100644 --- a/lib/python/picongpu/picmi/diagnostics/__init__.py +++ b/lib/python/picongpu/picmi/diagnostics/__init__.py @@ -12,6 +12,7 @@ from .png import Png from .timestepspec import TimeStepSpec from .checkpoint import Checkpoint +from .openpmd import OpenPMD __all__ = [ "Auto", @@ -21,4 +22,5 @@ "Png", "TimeStepSpec", "Checkpoint", + "OpenPMD", ] diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd.py b/lib/python/picongpu/picmi/diagnostics/openpmd.py new file mode 100644 index 0000000000..11850ccaf2 --- /dev/null +++ b/lib/python/picongpu/picmi/diagnostics/openpmd.py @@ -0,0 +1,154 @@ +""" +This file is part of PIConGPU. +Copyright 2021-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from ...pypicongpu.output.openpmd import OpenPMD as PyPIConGPUOpenPMD +from .timestepspec import TimeStepSpec +from .openpmd_sources.source import Source + +import typeguard +from typing import Optional, Dict, Union + + +@typeguard.typechecked +class OpenPMD: + """ + Specifies the parameters for the openPMD plugin in PIConGPU simulations. + + This plugin outputs simulation data (fields and particles) to disk using the openPMD API, + with configurable periods, data sources, and backend settings. + + Attention: **period is mandatory.** + + Parameters + ---------- + period: TimeStepSpec + Specify on which time steps to output data. + Unit: steps (simulation time steps). Required. + + source: Source, optional + Data sources and filters to dump (e.g., Source([ChargeDensity(filter="filterX"), "species_all"])). + Default: Source(["species_all", "fields_all"]). + + range: str, optional + Contiguous range of cells per dimension to dump (e.g., ":,:,:"). + Format: comma-separated ranges like "begin:end,begin:end,begin:end". + Default: ":,:,:" + + file: str, optional + Relative or absolute file prefix for openPMD output files. + If relative, files are stored under simOutput. + + ext: str, optional + File extension controlling the openPMD backend. + Options: "bp" (ADIOS2, default), "h5" (HDF5), "sst" (ADIOS2/SST) for data streaming. + + infix: str, optional + Filename infix for iteration layout (e.g., "_%06T"). + Set to "NULL" for group-based layout. Mandatory "NULL" if ext="sst" for data streaming. + Default: "_%06T". + + json: Union[str, Dict], optional + Backend-specific parameters for writing, as a JSON string, dictionary, or filename + (filename must be prepended with "@"). Default: empty dictionary. + + json_restart: Union[str, Dict], optional + Backend-specific parameters for restarting, as a JSON string, dictionary, or filename + (filename must be prepended with "@"). Default: empty dictionary. + + data_preparation_strategy: str, optional + Strategy for particle data preparation. + Options: "doubleBuffer" (alias "adios", default), "mappedMemory" (alias "hdf5"). + + toml: str, optional + Path to a TOML file for openPMD configuration. + + particle_io_chunk_size: int, optional + Size of particle data chunks for writing (in MiB). + Reduces host memory footprint for bp5 backend. + + file_access: str, optional + File access mode for writing. + Options: "create" (default), "append" (for checkpoint-restart workflows). + """ + + def check(self): + """ + Validate the provided parameters. + """ + if self.period is None: + raise ValueError("period is mandatory") + if self.ext is not None and self.ext not in ["bp", "h5", "sst"]: + raise ValueError("ext must be one of 'bp', 'h5', 'sst'") + if self.data_preparation_strategy is not None and self.data_preparation_strategy not in [ + "doubleBuffer", + "adios", + "mappedMemory", + "hdf5", + ]: + raise ValueError("data_preparation_strategy must be one of 'doubleBuffer', 'adios', 'mappedMemory', 'hdf5'") + if self.file_access is not None and self.file_access not in ["create", "append"]: + raise ValueError("file_access must be one of 'create', 'append'") + if self.particle_io_chunk_size is not None and self.particle_io_chunk_size < 1: + raise ValueError("particle_io_chunk_size (in MiB) must be positive") + if self.ext == "sst" and self.infix is not None and self.infix != "NULL": + raise ValueError("infix must be 'NULL' when ext is 'sst'") + if not isinstance(self.source, Source): + raise ValueError("source must be a Source object") + + def __init__( + self, + period: TimeStepSpec, + source: Optional[Source] = None, + range: Optional[str] = None, + file: Optional[str] = None, + ext: Optional[str] = None, + infix: Optional[str] = None, + json: Optional[Union[str, Dict]] = None, + json_restart: Optional[Union[str, Dict]] = None, + data_preparation_strategy: Optional[str] = None, + toml: Optional[str] = None, + particle_io_chunk_size: Optional[int] = None, + file_access: Optional[str] = None, + ): + self.period = period + self.source = source if source is not None else Source(["species_all", "fields_all"]) + self.range = range + self.file = file + self.ext = ext + self.infix = infix + self.json = json if json is not None else {} + self.json_restart = json_restart if json_restart is not None else {} + self.data_preparation_strategy = data_preparation_strategy + self.toml = toml + self.particle_io_chunk_size = particle_io_chunk_size + self.file_access = file_access + + self.check() + + def get_as_pypicongpu( + self, + pypicongpu_by_picmi_species: Dict, + time_step_size: float, + num_steps: int, + ) -> PyPIConGPUOpenPMD: + self.check() + + pypicongpu_openpmd = PyPIConGPUOpenPMD() + pypicongpu_openpmd.period = self.period.get_as_pypicongpu(time_step_size, num_steps) + pypicongpu_openpmd.source = self.source.get_as_pypicongpu() + pypicongpu_openpmd.range = self.range + pypicongpu_openpmd.file = self.file + pypicongpu_openpmd.ext = self.ext + pypicongpu_openpmd.infix = self.infix + pypicongpu_openpmd.json = self.json + pypicongpu_openpmd.json_restart = self.json_restart + pypicongpu_openpmd.data_preparation_strategy = self.data_preparation_strategy + pypicongpu_openpmd.toml = self.toml + pypicongpu_openpmd.particle_io_chunk_size = self.particle_io_chunk_size + pypicongpu_openpmd.file_access = self.file_access + + return pypicongpu_openpmd diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/__init__.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/__init__.py new file mode 100644 index 0000000000..9a0318db1a --- /dev/null +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/__init__.py @@ -0,0 +1,32 @@ +from .bound_electron_density import BoundElectronDensity +from .charge_density import ChargeDensity +from .counter import Counter +from .density import Density +from .energy import Energy +from .energy_density import EnergyDensity +from .energy_density_cutoff import EnergyDensityCutoff +from .larmor_power import LarmorPower +from .macro_counter import MacroCounter +from .mid_current_density_component import MidCurrentDensityComponent +from .momentum import Momentum +from .momentum_density import MomentumDensity +from .weighted_velocity import WeightedVelocity + +from .source import Source + +__all__ = [ + "BoundElectronDensity", + "ChargeDensity", + "Counter", + "Density", + "Energy", + "EnergyDensity", + "EnergyDensityCutoff", + "LarmorPower", + "MacroCounter", + "MidCurrentDensityComponent", + "Momentum", + "MomentumDensity", + "WeightedVelocity", + "Source", +] diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/bound_electron_density.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/bound_electron_density.py new file mode 100644 index 0000000000..da81e2ecec --- /dev/null +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/bound_electron_density.py @@ -0,0 +1,74 @@ +""" +This file is part of PIConGPU. +Copyright 2021-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +import typeguard +from typing import Optional + + +@typeguard.typechecked +class BoundElectronDensity: + """ + Represents the BoundElectronDensity data source for the openPMD plugin in PIConGPU. + + This class defines the bound electron density field, derived from particle species at runtime, + which can be output using the openPMD plugin. This is typically used for partially ionized ions. + An optional filter can be applied to select which particles contribute to the bound electron density. + + Parameters + ---------- + filter: str, optional + Name of a deterministic filter to apply to particles, as defined in particleFilters.param + (see picongpu/include/picongpu/param/particleFilters.param). + The default filter is "all" (selects all valid particles). Additional filters, such as + "relativeGlobalDomainPosition" (selects particles in a global domain range), can be defined + in your local particleFilters.param file. If None, no filter is applied. Valid filters must be + deterministic and are listed in the PIConGPU command-line help for --openPMD.source. + """ + + def __init__(self, filter: Optional[str] = None): + self.filter = filter + self.check() + + def check(self): + """ + Validate the provided filter. + """ + if self.filter is not None and not isinstance(self.filter, str): + raise ValueError( + f"Filter must be a string or None, got {type(self.filter)}. " + "Valid filter names are defined in particleFilters.param " + "(see picongpu/include/picongpu/param/particleFilters.param). " + "The default filter is 'all' (selects all valid particles). Additional filters, such as " + "'relativeGlobalDomainPosition' (selects particles in a global domain range), can be defined " + "in your local particleFilters.param file. Valid filters are listed in the PIConGPU " + "command-line help for --openPMD.source." + ) + + def get_source_string(self) -> str: + """ + Return the source string for use in --openPMD.source. + + Returns + ------- + str + The dataset name with optional filter (e.g., "bound_electron_density" or + "bound_electron_density:filterX"). + """ + if self.filter: + return f"bound_electron_density:{self.filter}" + return "bound_electron_density" + + def get_as_pypicongpu(self) -> str: + """ + Return the source string for PyPIConGPU integration. + + Returns + ------- + str + The dataset name with optional filter. + """ + return self.get_source_string() diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/charge_density.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/charge_density.py new file mode 100644 index 0000000000..62b9bbcaaa --- /dev/null +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/charge_density.py @@ -0,0 +1,73 @@ +""" +This file is part of PIConGPU. +Copyright 2021-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +import typeguard +from typing import Optional + + +@typeguard.typechecked +class ChargeDensity: + """ + Represents the ChargeDensity data source for the openPMD plugin in PIConGPU. + + This class defines the charge density field, derived from particle species at runtime, + which can be output using the openPMD plugin. An optional filter can be applied to select + which particles contribute to the charge density. + + Parameters + ---------- + filter: str, optional + Name of a deterministic filter to apply to particles, as defined in particleFilters.param + (see picongpu/include/picongpu/param/particleFilters.param). + The default filter is "all" (selects all valid particles). Additional filters, such as + "relativeGlobalDomainPosition" (selects particles in a global domain range), can be defined + in your local particleFilters.param file. If None, no filter is applied. Valid filters must be + deterministic and are listed in the PIConGPU command-line help for --openPMD.source. + """ + + def __init__(self, filter: Optional[str] = None): + self.filter = filter + self.check() + + def check(self): + """ + Validate the provided filter. + """ + if self.filter is not None and not isinstance(self.filter, str): + raise ValueError( + f"Filter must be a string or None, got {type(self.filter)}. " + "Valid filter names are defined in particleFilters.param " + "(see picongpu/include/picongpu/param/particleFilters.param). " + "The default filter is 'all' (selects all valid particles). Additional filters, such as " + "'relativeGlobalDomainPosition' (selects particles in a global domain range), can be defined " + "in your local particleFilters.param file. Valid filters are listed in the PIConGPU " + "command-line help for --openPMD.source." + ) + + def get_source_string(self) -> str: + """ + Return the source string for use in --openPMD.source. + + Returns + ------- + str + The dataset name with optional filter (e.g., "charge_density" or "charge_density:filterX"). + """ + if self.filter: + return f"charge_density:{self.filter}" + return "charge_density" + + def get_as_pypicongpu(self) -> str: + """ + Return the source string for PyPIConGPU integration. + + Returns + ------- + str + The dataset name with optional filter. + """ + return self.get_source_string() diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/source.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/source.py new file mode 100644 index 0000000000..2820527896 --- /dev/null +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/source.py @@ -0,0 +1,86 @@ +""" +This file is part of PIConGPU. +Copyright 2021-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .bound_electron_density import BoundElectronDensity +from .charge_density import ChargeDensity +from .counter import Counter +from .density import Density +from .energy import Energy +from .energy_density import EnergyDensity +from .energy_density_cutoff import EnergyDensityCutoff +from .larmor_power import LarmorPower +from .macro_counter import MacroCounter +from .mid_current_density_component import MidCurrentDensityComponent +from .momentum import Momentum +from .momentum_density import MomentumDensity +from .weighted_velocity import WeightedVelocity + +import typeguard +from typing import List, Union + + +@typeguard.typechecked +class Source: + """ + Consolidates data sources for the openPMD plugin in PIConGPU. + + This class aggregates individual data sources (e.g., ChargeDensity) or predefined + keywords (species_all, fields_all) to define the --openPMD.source parameter: + https://picongpu.readthedocs.io/en/latest/usage/plugins/openPMD.html + + Parameters + ---------- + sources: List[Union[str, ]] + List of data sources, either as strings (e.g., "species_all", "fields_all") or + as data source objects (e.g., ChargeDensity, Density). + """ + + # List of valid data source classes + VALID_SOURCE_CLASSES = ( + BoundElectronDensity, + ChargeDensity, + Counter, + Density, + Energy, + EnergyDensity, + EnergyDensityCutoff, + LarmorPower, + MacroCounter, + MidCurrentDensityComponent, + Momentum, + MomentumDensity, + WeightedVelocity, + ) + + def __init__(self, sources: List[Union[str, *VALID_SOURCE_CLASSES]]): + self.sources = sources + self.check() + + def check(self): + """ + Validate the provided sources. + """ + valid_strings = {"species_all", "fields_all"} + for src in self.sources: + if isinstance(src, str): + if src not in valid_strings: + raise ValueError(f"Invalid source string: '{src}'. Must be one of {valid_strings}.") + elif not isinstance(src, self.VALID_SOURCE_CLASSES): + raise ValueError( + f"Invalid source type: {type(src)}. Must be str or one of {self.VALID_SOURCE_CLASSES}." + ) + + def get_as_pypicongpu(self) -> List[str]: + """ + Convert sources to a list of strings for PyPIConGPU integration. + + Returns + ------- + List[str] + List of source strings (e.g., ["species_all", "charge_density:filterX"]). + """ + return [src if isinstance(src, str) else src.get_as_pypicongpu() for src in self.sources] diff --git a/lib/python/picongpu/pypicongpu/output/openpmd.py b/lib/python/picongpu/pypicongpu/output/openpmd.py new file mode 100644 index 0000000000..ae2f36621c --- /dev/null +++ b/lib/python/picongpu/pypicongpu/output/openpmd.py @@ -0,0 +1,138 @@ +# This file is part of PIConGPU. +# Copyright 2021-2025 PIConGPU contributors +# Authors: Masoud Afshari +# License: GPLv3+ + +from .timestepspec import TimeStepSpec +from .plugin import Plugin +from .. import util +from .openpmd_source import ( + ChargeDensity, + BoundElectronDensity, + Counter, + Density, + Energy, + EnergyDensity, + EnergyDensityCutoff, + LarmorPower, + MacroCounter, + MidCurrentDensityComponent, + Momentum, + MomentumDensity, + WeightedVelocity, +) +import typeguard +import typing + + +@typeguard.typechecked +class Source: + sources = util.build_typesafe_property( + typing.List[ + typing.Union[ + str, + ChargeDensity, + BoundElectronDensity, + Counter, + Density, + Energy, + EnergyDensity, + EnergyDensityCutoff, + LarmorPower, + MacroCounter, + MidCurrentDensityComponent, + Momentum, + MomentumDensity, + WeightedVelocity, + ] + ] + ) + + def __init__( + self, + sources: typing.List[ + typing.Union[ + str, + ChargeDensity, + BoundElectronDensity, + Counter, + Density, + Energy, + EnergyDensity, + EnergyDensityCutoff, + LarmorPower, + MacroCounter, + MidCurrentDensityComponent, + Momentum, + MomentumDensity, + WeightedVelocity, + ] + ], + ): + self.sources = sources + + def _get_serialized(self) -> typing.List[typing.Any]: + return [s._get_serialized() if not isinstance(s, str) else s for s in self.sources] + + +@typeguard.typechecked +class OpenPMD(Plugin): + period = util.build_typesafe_property(TimeStepSpec) + source = util.build_typesafe_property(typing.Optional[Source]) + range = util.build_typesafe_property(typing.Optional[str]) + file = util.build_typesafe_property(typing.Optional[str]) + ext = util.build_typesafe_property(typing.Optional[str]) + infix = util.build_typesafe_property(typing.Optional[str]) + json = util.build_typesafe_property(typing.Optional[typing.Union[str, typing.Dict]]) + json_restart = util.build_typesafe_property(typing.Optional[typing.Union[str, typing.Dict]]) + data_preparation_strategy = util.build_typesafe_property(typing.Optional[str]) + toml = util.build_typesafe_property(typing.Optional[str]) + particle_io_chunk_size = util.build_typesafe_property(typing.Optional[int]) + file_access = util.build_typesafe_property(typing.Optional[str]) + + _name = "openPMD" + + def __init__( + self, + period: TimeStepSpec, + source: typing.Optional[Source] = None, + range: typing.Optional[str] = None, + file: typing.Optional[str] = None, + ext: typing.Optional[str] = None, + infix: typing.Optional[str] = None, + json: typing.Optional[typing.Union[str, typing.Dict]] = None, + json_restart: typing.Optional[typing.Union[str, typing.Dict]] = None, + data_preparation_strategy: typing.Optional[str] = None, + toml: typing.Optional[str] = None, + particle_io_chunk_size: typing.Optional[int] = None, + file_access: typing.Optional[str] = None, + ): + self.period = period + self.source = source or Source(["species_all", "fields_all"]) + self.range = range or ":,:,:" + self.file = file + self.ext = ext or "bp" + self.infix = infix or "_%06T" + self.json = json or {} + self.json_restart = json_restart or {} + self.data_preparation_strategy = data_preparation_strategy or "doubleBuffer" + self.toml = toml + self.particle_io_chunk_size = particle_io_chunk_size + self.file_access = file_access or "create" + + def _get_serialized(self) -> typing.Dict: + result = { + "period": self.period._get_serialized(), + "source": self.source._get_serialized(), + "range": self.range, + "file": self.file, + "ext": self.ext, + "infix": self.infix, + "json": self.json, + "json_restart": self.json_restart, + "data_preparation_strategy": self.data_preparation_strategy, + "toml": self.toml, + "particle_io_chunk_size": self.particle_io_chunk_size, + "file_access": self.file_access, + } + return {k: v for k, v in result.items() if v is not None} diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_source/__init__.py b/lib/python/picongpu/pypicongpu/output/openpmd_source/__init__.py new file mode 100644 index 0000000000..b32b469e93 --- /dev/null +++ b/lib/python/picongpu/pypicongpu/output/openpmd_source/__init__.py @@ -0,0 +1,29 @@ +from .charge_density import ChargeDensity +from .bound_electron_density import BoundElectronDensity +from .counter import Counter +from .density import Density +from .energy import Energy +from .energy_density import EnergyDensity +from .energy_density_cutoff import EnergyDensityCutoff +from .larmor_power import LarmorPower +from .macro_counter import MacroCounter +from .mid_current_density_component import MidCurrentDensityComponent +from .momentum import Momentum +from .momentum_density import MomentumDensity +from .weighted_velocity import WeightedVelocity + +__all__ = [ + "ChargeDensity", + "BoundElectronDensity", + "Counter", + "Density", + "Energy", + "EnergyDensity", + "EnergyDensityCutoff", + "LarmorPower", + "MacroCounter", + "MidCurrentDensityComponent", + "Momentum", + "MomentumDensity", + "WeightedVelocity", +] diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_source/charge_density.py b/lib/python/picongpu/pypicongpu/output/openpmd_source/charge_density.py new file mode 100644 index 0000000000..fe4e6b13a8 --- /dev/null +++ b/lib/python/picongpu/pypicongpu/output/openpmd_source/charge_density.py @@ -0,0 +1,37 @@ +""" +This file is part of PIConGPU. +Copyright 2021-2024 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .. import util +import typeguard +import typing + + +@typeguard.typechecked +class ChargeDensity: + filter = util.build_typesafe_property(typing.Optional[str]) + + def __init__(self, filter: typing.Optional[str] = None): + self.filter = filter + self.check() + + def check(self): + if self.filter is not None and not isinstance(self.filter, str): + raise ValueError( + f"Filter must be a string or None, got {type(self.filter)}. " + "Valid filter names are defined in particleFilters.param " + "(see picongpu/include/picongpu/param/particleFilters.param). " + "The default filter is 'all' (selects all valid particles). Additional filters, such as " + "'relativeGlobalDomainPosition' (selects particles in a global domain range), can be defined " + "in your local particleFilters.param file. Valid filters are listed in the PIConGPU " + "command-line help for --openPMD.source." + ) + + def _get_serialized(self) -> typing.Dict: + return { + "dataset": "charge_density", + "filter": self.filter, + } From 79478af589c3402c6c2d899fe48a5c002e958824 Mon Sep 17 00:00:00 2001 From: mafshari64 Date: Mon, 28 Apr 2025 17:11:19 +0200 Subject: [PATCH 02/15] common interface for all the current sources of the openPMD plugin --- .../picongpu/picmi/diagnostics/openpmd.py | 108 +++++++++--------- .../diagnostics/openpmd_sources/__init__.py | 4 +- .../openpmd_sources/bound_electron_density.py | 69 +++++------ .../openpmd_sources/charge_density.py | 66 +++++------ .../diagnostics/openpmd_sources/source.py | 86 -------------- .../openpmd_sources/source_base.py | 59 ++++++++++ 6 files changed, 166 insertions(+), 226 deletions(-) delete mode 100644 lib/python/picongpu/picmi/diagnostics/openpmd_sources/source.py create mode 100644 lib/python/picongpu/picmi/diagnostics/openpmd_sources/source_base.py diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd.py b/lib/python/picongpu/picmi/diagnostics/openpmd.py index 11850ccaf2..ca39f71fb2 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd.py @@ -6,73 +6,80 @@ """ from ...pypicongpu.output.openpmd import OpenPMD as PyPIConGPUOpenPMD +from ...pypicongpu.output import Source as PyPIConGPUSource from .timestepspec import TimeStepSpec -from .openpmd_sources.source import Source - +from .openpmd_sources.source_base import SourceBase import typeguard -from typing import Optional, Dict, Union +from typing import Optional, Dict, Union, List, Literal @typeguard.typechecked class OpenPMD: """ - Specifies the parameters for the openPMD plugin in PIConGPU simulations. + Specifies parameters for openPMD diagnostic output in particle-in-cell simulations. - This plugin outputs simulation data (fields and particles) to disk using the openPMD API, - with configurable periods, data sources, and backend settings. + This diagnostic outputs simulation data (fields and particles) to disk using the openPMD + standard, with configurable periods, data sources, and backend settings. Attention: **period is mandatory.** Parameters ---------- period: TimeStepSpec - Specify on which time steps to output data. - Unit: steps (simulation time steps). Required. + Specifies the time steps for data output. + Unit: simulation time steps. Required. - source: Source, optional - Data sources and filters to dump (e.g., Source([ChargeDensity(filter="filterX"), "species_all"])). - Default: Source(["species_all", "fields_all"]). + source: List[SourceBase], optional + List of data source objects to dump (e.g., [ChargeDensity(filter="all")]). + Default: None (no sources specified). range: str, optional Contiguous range of cells per dimension to dump (e.g., ":,:,:"). Format: comma-separated ranges like "begin:end,begin:end,begin:end". - Default: ":,:,:" + Default: None (all cells). file: str, optional Relative or absolute file prefix for openPMD output files. - If relative, files are stored under simOutput. + Default: None (backend-dependent default). - ext: str, optional + ext: Literal["bp", "h5", "sst"], optional File extension controlling the openPMD backend. - Options: "bp" (ADIOS2, default), "h5" (HDF5), "sst" (ADIOS2/SST) for data streaming. + Options: "bp" (ADIOS2), "h5" (HDF5), "sst" (ADIOS2/SST for streaming). + Default: None (backend-dependent). infix: str, optional Filename infix for iteration layout (e.g., "_%06T"). - Set to "NULL" for group-based layout. Mandatory "NULL" if ext="sst" for data streaming. - Default: "_%06T". + Use "NULL" for group-based layout. Required as "NULL" if ext="sst". + Default: None (backend-dependent). json: Union[str, Dict], optional Backend-specific parameters for writing, as a JSON string, dictionary, or filename - (filename must be prepended with "@"). Default: empty dictionary. + (filename must be prepended with "@"). + Default: None (empty dictionary). json_restart: Union[str, Dict], optional Backend-specific parameters for restarting, as a JSON string, dictionary, or filename - (filename must be prepended with "@"). Default: empty dictionary. + (filename must be prepended with "@"). + Default: None (empty dictionary). - data_preparation_strategy: str, optional + data_preparation_strategy: Literal["doubleBuffer", "adios", "mappedMemory", "hdf5"], optional Strategy for particle data preparation. - Options: "doubleBuffer" (alias "adios", default), "mappedMemory" (alias "hdf5"). + Options: "doubleBuffer" or "adios" (ADIOS2-based), "mappedMemory" or "hdf5" (HDF5-based). + Default: None (backend-dependent). toml: str, optional Path to a TOML file for openPMD configuration. + Default: None. particle_io_chunk_size: int, optional Size of particle data chunks for writing (in MiB). - Reduces host memory footprint for bp5 backend. + Reduces host memory footprint for certain backends. + Default: None (backend-dependent). - file_access: str, optional + file_access: Literal["create", "append"], optional File access mode for writing. - Options: "create" (default), "append" (for checkpoint-restart workflows). + Options: "create" (new files), "append" (for checkpoint-restart workflows). + Default: None (backend-dependent). """ def check(self): @@ -81,41 +88,30 @@ def check(self): """ if self.period is None: raise ValueError("period is mandatory") - if self.ext is not None and self.ext not in ["bp", "h5", "sst"]: - raise ValueError("ext must be one of 'bp', 'h5', 'sst'") - if self.data_preparation_strategy is not None and self.data_preparation_strategy not in [ - "doubleBuffer", - "adios", - "mappedMemory", - "hdf5", - ]: - raise ValueError("data_preparation_strategy must be one of 'doubleBuffer', 'adios', 'mappedMemory', 'hdf5'") - if self.file_access is not None and self.file_access not in ["create", "append"]: - raise ValueError("file_access must be one of 'create', 'append'") if self.particle_io_chunk_size is not None and self.particle_io_chunk_size < 1: raise ValueError("particle_io_chunk_size (in MiB) must be positive") if self.ext == "sst" and self.infix is not None and self.infix != "NULL": raise ValueError("infix must be 'NULL' when ext is 'sst'") - if not isinstance(self.source, Source): - raise ValueError("source must be a Source object") + if self.source is not None and not all(isinstance(s, SourceBase) for s in self.source): + raise ValueError("source must be a list of SourceBase objects") def __init__( self, period: TimeStepSpec, - source: Optional[Source] = None, + source: Optional[List[SourceBase]] = None, range: Optional[str] = None, file: Optional[str] = None, - ext: Optional[str] = None, + ext: Optional[Literal["bp", "h5", "sst"]] = None, infix: Optional[str] = None, json: Optional[Union[str, Dict]] = None, json_restart: Optional[Union[str, Dict]] = None, - data_preparation_strategy: Optional[str] = None, + data_preparation_strategy: Optional[Literal["doubleBuffer", "adios", "mappedMemory", "hdf5"]] = None, toml: Optional[str] = None, particle_io_chunk_size: Optional[int] = None, - file_access: Optional[str] = None, + file_access: Optional[Literal["create", "append"]] = None, ): self.period = period - self.source = source if source is not None else Source(["species_all", "fields_all"]) + self.source = source self.range = range self.file = file self.ext = ext @@ -137,18 +133,18 @@ def get_as_pypicongpu( ) -> PyPIConGPUOpenPMD: self.check() - pypicongpu_openpmd = PyPIConGPUOpenPMD() - pypicongpu_openpmd.period = self.period.get_as_pypicongpu(time_step_size, num_steps) - pypicongpu_openpmd.source = self.source.get_as_pypicongpu() - pypicongpu_openpmd.range = self.range - pypicongpu_openpmd.file = self.file - pypicongpu_openpmd.ext = self.ext - pypicongpu_openpmd.infix = self.infix - pypicongpu_openpmd.json = self.json - pypicongpu_openpmd.json_restart = self.json_restart - pypicongpu_openpmd.data_preparation_strategy = self.data_preparation_strategy - pypicongpu_openpmd.toml = self.toml - pypicongpu_openpmd.particle_io_chunk_size = self.particle_io_chunk_size - pypicongpu_openpmd.file_access = self.file_access - + pypicongpu_openpmd = PyPIConGPUOpenPMD( + period=self.period.get_as_pypicongpu(time_step_size, num_steps), + source=PyPIConGPUSource([s.get_as_pypicongpu() for s in self.source]) if self.source is not None else None, + range=self.range, + file=self.file, + ext=self.ext, + infix=self.infix, + json=self.json, + json_restart=self.json_restart, + data_preparation_strategy=self.data_preparation_strategy, + toml=self.toml, + particle_io_chunk_size=self.particle_io_chunk_size, + file_access=self.file_access, + ) return pypicongpu_openpmd diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/__init__.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/__init__.py index 9a0318db1a..a0a4160a8a 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/__init__.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/__init__.py @@ -12,7 +12,7 @@ from .momentum_density import MomentumDensity from .weighted_velocity import WeightedVelocity -from .source import Source +from .source_base import SourceBase __all__ = [ "BoundElectronDensity", @@ -28,5 +28,5 @@ "Momentum", "MomentumDensity", "WeightedVelocity", - "Source", + "SourceBase", ] diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/bound_electron_density.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/bound_electron_density.py index da81e2ecec..83b21f283d 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/bound_electron_density.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/bound_electron_density.py @@ -5,70 +5,55 @@ License: GPLv3+ """ +from .... import util +from .source_base import SourceBase +from ...pypicongpu.output.openpmd_source import BoundElectronDensity as PyPIConGPUBoundElectronDensity import typeguard -from typing import Optional +import typing @typeguard.typechecked -class BoundElectronDensity: +class BoundElectronDensity(SourceBase): """ - Represents the BoundElectronDensity data source for the openPMD plugin in PIConGPU. + Represents the bound electron density data source for openPMD output in particle-in-cell simulations. This class defines the bound electron density field, derived from particle species at runtime, - which can be output using the openPMD plugin. This is typically used for partially ionized ions. - An optional filter can be applied to select which particles contribute to the bound electron density. + which can be output using the openPMD standard. An optional filter can be applied to select + which particles contribute to the bound electron density. Parameters ---------- filter: str, optional - Name of a deterministic filter to apply to particles, as defined in particleFilters.param - (see picongpu/include/picongpu/param/particleFilters.param). - The default filter is "all" (selects all valid particles). Additional filters, such as - "relativeGlobalDomainPosition" (selects particles in a global domain range), can be defined - in your local particleFilters.param file. If None, no filter is applied. Valid filters must be - deterministic and are listed in the PIConGPU command-line help for --openPMD.source. + Name of a filter to select particles contributing to the bound electron density. + Default: None (all valid particles). """ - def __init__(self, filter: Optional[str] = None): + filter = util.build_typesafe_property(typing.Optional[str]) + + def __init__(self, filter: typing.Optional[str] = None): self.filter = filter self.check() - def check(self): - """ - Validate the provided filter. + def check(self) -> None: """ - if self.filter is not None and not isinstance(self.filter, str): - raise ValueError( - f"Filter must be a string or None, got {type(self.filter)}. " - "Valid filter names are defined in particleFilters.param " - "(see picongpu/include/picongpu/param/particleFilters.param). " - "The default filter is 'all' (selects all valid particles). Additional filters, such as " - "'relativeGlobalDomainPosition' (selects particles in a global domain range), can be defined " - "in your local particleFilters.param file. Valid filters are listed in the PIConGPU " - "command-line help for --openPMD.source." - ) + Validate the filter parameter. - def get_source_string(self) -> str: + Raises + ------ + ValueError + If the filter is not a string or None. """ - Return the source string for use in --openPMD.source. - - Returns - ------- - str - The dataset name with optional filter (e.g., "bound_electron_density" or - "bound_electron_density:filterX"). - """ - if self.filter: - return f"bound_electron_density:{self.filter}" - return "bound_electron_density" + if self.filter is not None and not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string or None, got {type(self.filter)}") - def get_as_pypicongpu(self) -> str: + def get_as_pypicongpu(self) -> PyPIConGPUBoundElectronDensity: """ - Return the source string for PyPIConGPU integration. + Convert this BoundElectronDensity to a PyPIConGPU BoundElectronDensity object. Returns ------- - str - The dataset name with optional filter. + PyPIConGPUBoundElectronDensity + A PyPIConGPU BoundElectronDensity instance with the same filter. """ - return self.get_source_string() + self.check() + return PyPIConGPUBoundElectronDensity(filter=self.filter) diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/charge_density.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/charge_density.py index 62b9bbcaaa..109beb303d 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/charge_density.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/charge_density.py @@ -5,69 +5,55 @@ License: GPLv3+ """ +from .... import util +from .source_base import SourceBase +from ...pypicongpu.output.openpmd_source import ChargeDensity as PyPIConGPUChargeDensity import typeguard -from typing import Optional +import typing @typeguard.typechecked -class ChargeDensity: +class ChargeDensity(SourceBase): """ - Represents the ChargeDensity data source for the openPMD plugin in PIConGPU. + Represents the charge density data source for openPMD output in particle-in-cell simulations. This class defines the charge density field, derived from particle species at runtime, - which can be output using the openPMD plugin. An optional filter can be applied to select + which can be output using the openPMD standard. An optional filter can be applied to select which particles contribute to the charge density. Parameters ---------- filter: str, optional - Name of a deterministic filter to apply to particles, as defined in particleFilters.param - (see picongpu/include/picongpu/param/particleFilters.param). - The default filter is "all" (selects all valid particles). Additional filters, such as - "relativeGlobalDomainPosition" (selects particles in a global domain range), can be defined - in your local particleFilters.param file. If None, no filter is applied. Valid filters must be - deterministic and are listed in the PIConGPU command-line help for --openPMD.source. + Name of a filter to select particles contributing to the charge density. + Default: None (all valid particles). """ - def __init__(self, filter: Optional[str] = None): + filter = util.build_typesafe_property(typing.Optional[str]) + + def __init__(self, filter: typing.Optional[str] = None): self.filter = filter self.check() - def check(self): - """ - Validate the provided filter. + def check(self) -> None: """ - if self.filter is not None and not isinstance(self.filter, str): - raise ValueError( - f"Filter must be a string or None, got {type(self.filter)}. " - "Valid filter names are defined in particleFilters.param " - "(see picongpu/include/picongpu/param/particleFilters.param). " - "The default filter is 'all' (selects all valid particles). Additional filters, such as " - "'relativeGlobalDomainPosition' (selects particles in a global domain range), can be defined " - "in your local particleFilters.param file. Valid filters are listed in the PIConGPU " - "command-line help for --openPMD.source." - ) + Validate the filter parameter. - def get_source_string(self) -> str: + Raises + ------ + ValueError + If the filter is not a string or None. """ - Return the source string for use in --openPMD.source. - - Returns - ------- - str - The dataset name with optional filter (e.g., "charge_density" or "charge_density:filterX"). - """ - if self.filter: - return f"charge_density:{self.filter}" - return "charge_density" + if self.filter is not None and not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string or None, got {type(self.filter)}") - def get_as_pypicongpu(self) -> str: + def get_as_pypicongpu(self) -> PyPIConGPUChargeDensity: """ - Return the source string for PyPIConGPU integration. + Convert this ChargeDensity to a PyPIConGPU ChargeDensity object. Returns ------- - str - The dataset name with optional filter. + PyPIConGPUChargeDensity + A PyPIConGPU ChargeDensity instance with the same filter. """ - return self.get_source_string() + self.check() + return PyPIConGPUChargeDensity(filter=self.filter) diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/source.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/source.py deleted file mode 100644 index 2820527896..0000000000 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/source.py +++ /dev/null @@ -1,86 +0,0 @@ -""" -This file is part of PIConGPU. -Copyright 2021-2025 PIConGPU contributors -Authors: Masoud Afshari -License: GPLv3+ -""" - -from .bound_electron_density import BoundElectronDensity -from .charge_density import ChargeDensity -from .counter import Counter -from .density import Density -from .energy import Energy -from .energy_density import EnergyDensity -from .energy_density_cutoff import EnergyDensityCutoff -from .larmor_power import LarmorPower -from .macro_counter import MacroCounter -from .mid_current_density_component import MidCurrentDensityComponent -from .momentum import Momentum -from .momentum_density import MomentumDensity -from .weighted_velocity import WeightedVelocity - -import typeguard -from typing import List, Union - - -@typeguard.typechecked -class Source: - """ - Consolidates data sources for the openPMD plugin in PIConGPU. - - This class aggregates individual data sources (e.g., ChargeDensity) or predefined - keywords (species_all, fields_all) to define the --openPMD.source parameter: - https://picongpu.readthedocs.io/en/latest/usage/plugins/openPMD.html - - Parameters - ---------- - sources: List[Union[str, ]] - List of data sources, either as strings (e.g., "species_all", "fields_all") or - as data source objects (e.g., ChargeDensity, Density). - """ - - # List of valid data source classes - VALID_SOURCE_CLASSES = ( - BoundElectronDensity, - ChargeDensity, - Counter, - Density, - Energy, - EnergyDensity, - EnergyDensityCutoff, - LarmorPower, - MacroCounter, - MidCurrentDensityComponent, - Momentum, - MomentumDensity, - WeightedVelocity, - ) - - def __init__(self, sources: List[Union[str, *VALID_SOURCE_CLASSES]]): - self.sources = sources - self.check() - - def check(self): - """ - Validate the provided sources. - """ - valid_strings = {"species_all", "fields_all"} - for src in self.sources: - if isinstance(src, str): - if src not in valid_strings: - raise ValueError(f"Invalid source string: '{src}'. Must be one of {valid_strings}.") - elif not isinstance(src, self.VALID_SOURCE_CLASSES): - raise ValueError( - f"Invalid source type: {type(src)}. Must be str or one of {self.VALID_SOURCE_CLASSES}." - ) - - def get_as_pypicongpu(self) -> List[str]: - """ - Convert sources to a list of strings for PyPIConGPU integration. - - Returns - ------- - List[str] - List of source strings (e.g., ["species_all", "charge_density:filterX"]). - """ - return [src if isinstance(src, str) else src.get_as_pypicongpu() for src in self.sources] diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/source_base.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/source_base.py new file mode 100644 index 0000000000..029e0508a5 --- /dev/null +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/source_base.py @@ -0,0 +1,59 @@ +""" +This file is part of PIConGPU. +Copyright 2021-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from abc import ABCMeta, abstractmethod +import typeguard +import typing + + +@typeguard.typechecked +class SourceBase(metaclass=ABCMeta): + """ + Abstract base class for openPMD data sources in particle-in-cell simulations. + + Defines the interface for data sources output via the openPMD standard, + such as charge density or other derived attributes. + + Subclasses must implement the filter property, check method, and get_as_pypicongpu method. + """ + + @property + @abstractmethod + def filter(self) -> typing.Optional[str]: + """ + Name of a filter to select particles contributing to the data source. + + Returns + ------- + str or None + The filter name, or None if no filter is applied. + """ + pass + + @abstractmethod + def check(self) -> None: + """ + Validate the data source parameters. + + Raises + ------ + ValueError + If the parameters (e.g., filter) are invalid. + """ + pass + + @abstractmethod + def get_as_pypicongpu(self) -> typing.Any: + """ + Convert this data source to a PyPIConGPU equivalent. + + Returns + ------- + Any + A PyPIConGPU data source object (e.g., pypicongpu.output.openpmd_source.ChargeDensity). + """ + pass From 62d4c4371baf7ff4397a4b13f8e1d63db1c0e5d7 Mon Sep 17 00:00:00 2001 From: mafshari64 Date: Tue, 29 Apr 2025 17:13:14 +0200 Subject: [PATCH 03/15] adding species to openpmd sources --- .../picongpu/picmi/diagnostics/openpmd.py | 21 +++--- .../picmi/diagnostics/openpmd_sources/auto.py | 59 ++++++++++++++++ .../openpmd_sources/bound_electron_density.py | 69 ++++++++++++++----- .../openpmd_sources/charge_density.py | 69 ++++++++++++++----- .../pypicongpu/output/openpmd_source/auto.py | 40 +++++++++++ .../openpmd_source/bound_electron_density.py | 34 +++++++++ .../output/openpmd_source/charge_density.py | 31 ++++----- 7 files changed, 258 insertions(+), 65 deletions(-) create mode 100644 lib/python/picongpu/picmi/diagnostics/openpmd_sources/auto.py create mode 100644 lib/python/picongpu/pypicongpu/output/openpmd_source/auto.py create mode 100644 lib/python/picongpu/pypicongpu/output/openpmd_source/bound_electron_density.py diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd.py b/lib/python/picongpu/picmi/diagnostics/openpmd.py index ca39f71fb2..c805d86884 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd.py @@ -36,31 +36,32 @@ class OpenPMD: range: str, optional Contiguous range of cells per dimension to dump (e.g., ":,:,:"). Format: comma-separated ranges like "begin:end,begin:end,begin:end". - Default: None (all cells). + Default: ":,::,:" (all cells). file: str, optional Relative or absolute file prefix for openPMD output files. + If relative, files are stored under the simulation output directory. Default: None (backend-dependent default). ext: Literal["bp", "h5", "sst"], optional File extension controlling the openPMD backend. Options: "bp" (ADIOS2), "h5" (HDF5), "sst" (ADIOS2/SST for streaming). - Default: None (backend-dependent). + Default: "bp" (ADIOS2 backend). infix: str, optional Filename infix for iteration layout (e.g., "_%06T"). Use "NULL" for group-based layout. Required as "NULL" if ext="sst". - Default: None (backend-dependent). + Default: "NULL" (group-based layout). json: Union[str, Dict], optional Backend-specific parameters for writing, as a JSON string, dictionary, or filename (filename must be prepended with "@"). - Default: None (empty dictionary). + Default: {} (empty dictionary). json_restart: Union[str, Dict], optional Backend-specific parameters for restarting, as a JSON string, dictionary, or filename (filename must be prepended with "@"). - Default: None (empty dictionary). + Default: {} (empty dictionary). data_preparation_strategy: Literal["doubleBuffer", "adios", "mappedMemory", "hdf5"], optional Strategy for particle data preparation. @@ -79,7 +80,7 @@ class OpenPMD: file_access: Literal["create", "append"], optional File access mode for writing. Options: "create" (new files), "append" (for checkpoint-restart workflows). - Default: None (backend-dependent). + Default: "create" (new files). """ def check(self): @@ -99,16 +100,16 @@ def __init__( self, period: TimeStepSpec, source: Optional[List[SourceBase]] = None, - range: Optional[str] = None, + range: Optional[str] = ":,:,:", file: Optional[str] = None, - ext: Optional[Literal["bp", "h5", "sst"]] = None, - infix: Optional[str] = None, + ext: Optional[Literal["bp", "h5", "sst"]] = "bp", + infix: Optional[str] = "NULL", json: Optional[Union[str, Dict]] = None, json_restart: Optional[Union[str, Dict]] = None, data_preparation_strategy: Optional[Literal["doubleBuffer", "adios", "mappedMemory", "hdf5"]] = None, toml: Optional[str] = None, particle_io_chunk_size: Optional[int] = None, - file_access: Optional[Literal["create", "append"]] = None, + file_access: Optional[Literal["create", "append"]] = "create", ): self.period = period self.source = source diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/auto.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/auto.py new file mode 100644 index 0000000000..8cbed08e36 --- /dev/null +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/auto.py @@ -0,0 +1,59 @@ +""" +This file is part of PIConGPU. +Copyright 2021-2024 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .... import util +from .source_base import SourceBase +from ...pypicongpu.output.openpmd_source import Auto as PyPIConGPUAuto +import typeguard +import typing + + +@typeguard.typechecked +class Auto(SourceBase): + """ + Represents a default data source for openPMD output in particle-in-cell simulations. + + This class specifies a backend-specific default source for dumping simulation data + using the openPMD standard. For example, in some backends, it may dump all particle + species and fields. + + Parameters + ---------- + filter: str, optional + Name of a filter to select data contributing to the source. + Default: None (backend-dependent). + """ + + filter = util.build_typesafe_property(typing.Optional[str]) + + def __init__(self, filter: typing.Optional[str] = None): + self.filter = filter + self.check() + + def check(self) -> None: + """ + Validate the filter parameter. + + Raises + ------ + ValueError + If the filter is not a string or None. + """ + if self.filter is not None and not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string or None, got {type(self.filter)}") + + def get_as_pypicongpu(self) -> PyPIConGPUAuto: + """ + Convert this Auto source to a PyPIConGPU Auto source. + + Returns + ------- + PyPIConGPUAuto + A PyPIConGPU Auto instance with the same filter. + """ + self.check() + return PyPIConGPUAuto(filter=self.filter) diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/bound_electron_density.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/bound_electron_density.py index 83b21f283d..bf7a0be81a 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/bound_electron_density.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/bound_electron_density.py @@ -1,13 +1,13 @@ """ This file is part of PIConGPU. -Copyright 2021-2025 PIConGPU contributors +Copyright 2021-2024 PIConGPU contributors Authors: Masoud Afshari License: GPLv3+ """ -from .... import util from .source_base import SourceBase from ...pypicongpu.output.openpmd_source import BoundElectronDensity as PyPIConGPUBoundElectronDensity +from ...species import Species as PICMISpecies import typeguard import typing @@ -15,45 +15,76 @@ @typeguard.typechecked class BoundElectronDensity(SourceBase): """ - Represents the bound electron density data source for openPMD output in particle-in-cell simulations. + Represents a bound electron density data source for openPMD output in particle-in-cell simulations. - This class defines the bound electron density field, derived from particle species at runtime, - which can be output using the openPMD standard. An optional filter can be applied to select - which particles contribute to the bound electron density. + This source calculates the density of bound electrons from a specified particle species, optionally + filtered by a selection criterion. Parameters ---------- + species: PICMISpecies + Particle species contributing to the bound electron density (e.g., ions). + filter: str, optional - Name of a filter to select particles contributing to the bound electron density. - Default: None (all valid particles). + Name of a filter to select particles contributing to the source. + Default: "all" (includes all particles of the specified species). """ - filter = util.build_typesafe_property(typing.Optional[str]) - - def __init__(self, filter: typing.Optional[str] = None): + def __init__(self, species: PICMISpecies, filter: str = "all"): + self.species = species self.filter = filter self.check() def check(self) -> None: """ - Validate the filter parameter. + Validate the parameters. Raises ------ ValueError - If the filter is not a string or None. + If filter is not a string or species is not a PICMISpecies. """ - if self.filter is not None and not isinstance(self.filter, str): - raise ValueError(f"Filter must be a string or None, got {type(self.filter)}") + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, PICMISpecies): + raise ValueError(f"Species must be a PICMISpecies, got {type(self.species)}") - def get_as_pypicongpu(self) -> PyPIConGPUBoundElectronDensity: + def get_as_pypicongpu( + self, + dict_species_picmi_to_pypicongpu: dict[PICMISpecies, typing.Any], + time_step_size: float, + num_steps: int, + ) -> PyPIConGPUBoundElectronDensity: """ - Convert this BoundElectronDensity to a PyPIConGPU BoundElectronDensity object. + Convert this BoundElectronDensity source to a PyPIConGPU BoundElectronDensity source. + + Parameters + ---------- + dict_species_picmi_to_pypicongpu: dict[PICMISpecies, Any] + Mapping of PICMI species to PyPIConGPU species. + time_step_size: float + Size of a simulation time step (unused). + num_steps: int + Total number of simulation steps (unused). Returns ------- PyPIConGPUBoundElectronDensity - A PyPIConGPU BoundElectronDensity instance with the same filter. + A PyPIConGPU BoundElectronDensity instance with the same filter and species. + + Raises + ------ + ValueError + If the species is not known to the simulation or not mapped to a PyPIConGPUSpecies. """ self.check() - return PyPIConGPUBoundElectronDensity(filter=self.filter) + + if self.species not in dict_species_picmi_to_pypicongpu.keys(): + raise ValueError(f"Species {self.species} is not known to Simulation") + + pypicongpu_species = dict_species_picmi_to_pypicongpu.get(self.species) + + if pypicongpu_species is None: + raise ValueError(f"Species {self.species} is not mapped to a PyPIConGPUSpecies.") + + return PyPIConGPUBoundElectronDensity(filter=self.filter, species=pypicongpu_species) diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/charge_density.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/charge_density.py index 109beb303d..db18b34fcd 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/charge_density.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/charge_density.py @@ -1,13 +1,13 @@ """ This file is part of PIConGPU. -Copyright 2021-2025 PIConGPU contributors +Copyright 2021-2024 PIConGPU contributors Authors: Masoud Afshari License: GPLv3+ """ -from .... import util from .source_base import SourceBase from ...pypicongpu.output.openpmd_source import ChargeDensity as PyPIConGPUChargeDensity +from ...species import Species as PICMISpecies import typeguard import typing @@ -15,45 +15,76 @@ @typeguard.typechecked class ChargeDensity(SourceBase): """ - Represents the charge density data source for openPMD output in particle-in-cell simulations. + Represents a charge density data source for openPMD output in particle-in-cell simulations. - This class defines the charge density field, derived from particle species at runtime, - which can be output using the openPMD standard. An optional filter can be applied to select - which particles contribute to the charge density. + This source calculates the charge density from a specified particle species, optionally + filtered by a selection criterion. Parameters ---------- + species: PICMISpecies + Particle species contributing to the charge density (e.g., electrons, protons). + filter: str, optional - Name of a filter to select particles contributing to the charge density. - Default: None (all valid particles). + Name of a filter to select particles contributing to the source. + Default: "all" (includes all particles of the specified species). """ - filter = util.build_typesafe_property(typing.Optional[str]) - - def __init__(self, filter: typing.Optional[str] = None): + def __init__(self, species: PICMISpecies, filter: str = "all"): + self.species = species self.filter = filter self.check() def check(self) -> None: """ - Validate the filter parameter. + Validate the parameters. Raises ------ ValueError - If the filter is not a string or None. + If filter is not a string or species is not a PICMISpecies. """ - if self.filter is not None and not isinstance(self.filter, str): - raise ValueError(f"Filter must be a string or None, got {type(self.filter)}") + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, PICMISpecies): + raise ValueError(f"Species must be a PICMISpecies, got {type(self.species)}") - def get_as_pypicongpu(self) -> PyPIConGPUChargeDensity: + def get_as_pypicongpu( + self, + dict_species_picmi_to_pypicongpu: dict[PICMISpecies, typing.Any], + time_step_size: float, + num_steps: int, + ) -> PyPIConGPUChargeDensity: """ - Convert this ChargeDensity to a PyPIConGPU ChargeDensity object. + Convert this ChargeDensity source to a PyPIConGPU ChargeDensity source. + + Parameters + ---------- + dict_species_picmi_to_pypicongpu: dict[PICMISpecies, Any] + Mapping of PICMI species to PyPIConGPU species. + time_step_size: float + Size of a simulation time step (unused). + num_steps: int + Total number of simulation steps (unused). Returns ------- PyPIConGPUChargeDensity - A PyPIConGPU ChargeDensity instance with the same filter. + A PyPIConGPU ChargeDensity instance with the same filter and species. + + Raises + ------ + ValueError + If the species is not known to the simulation or not mapped to a PyPIConGPUSpecies. """ self.check() - return PyPIConGPUChargeDensity(filter=self.filter) + + if self.species not in dict_species_picmi_to_pypicongpu.keys(): + raise ValueError(f"Species {self.species} is not known to Simulation") + + pypicongpu_species = dict_species_picmi_to_pypicongpu.get(self.species) + + if pypicongpu_species is None: + raise ValueError(f"Species {self.species} is not mapped to a PyPIConGPUSpecies.") + + return PyPIConGPUChargeDensity(filter=self.filter, species=pypicongpu_species) diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_source/auto.py b/lib/python/picongpu/pypicongpu/output/openpmd_source/auto.py new file mode 100644 index 0000000000..2257f1cea1 --- /dev/null +++ b/lib/python/picongpu/pypicongpu/output/openpmd_source/auto.py @@ -0,0 +1,40 @@ +""" +This file is part of PIConGPU. +Copyright 2021-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +import typeguard +import typing + + +@typeguard.typechecked +class Auto: + filter = property(lambda self: self._filter) + + def __init__(self, filter: typing.Optional[str] = None): + self._filter = filter + self.check() + + def check(self) -> None: + if self._filter is not None and not isinstance(self._filter, str): + raise ValueError(f"Filter must be a string or None, got {type(self._filter)}") + + def _get_serialized(self) -> typing.Dict: + return { + "source": [ + {"dataset": "species_all", "filter": self._filter}, + {"dataset": "fields_all", "filter": self._filter}, + ], + "range": ":,:,:", + "file": "simOutput", + "ext": "bp", + "infix": "NULL", + "json": {}, + "json_restart": {}, + "data_preparation_strategy": "doubleBuffer", + "toml": None, + "particle_io_chunk_size": None, + "file_access": "create", + } diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_source/bound_electron_density.py b/lib/python/picongpu/pypicongpu/output/openpmd_source/bound_electron_density.py new file mode 100644 index 0000000000..ef9d05adea --- /dev/null +++ b/lib/python/picongpu/pypicongpu/output/openpmd_source/bound_electron_density.py @@ -0,0 +1,34 @@ +""" +This file is part of PIConGPU. +Copyright 2021-2024 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from ...species import Species as PyPIConGPUSpecies +import typeguard +import typing + + +@typeguard.typechecked +class BoundElectronDensity: + filter = property(lambda self: self._filter) + species = property(lambda self: self._species) + + def __init__(self, filter: str, species: typing.List[PyPIConGPUSpecies]): + self._filter = filter + self._species = species + self.check() + + def check(self) -> None: + if not isinstance(self._filter, str): + raise ValueError(f"Filter must be a string, got {type(self._filter)}") + if not self._species or not all(isinstance(s, PyPIConGPUSpecies) for s in self._species): + raise ValueError("Species must be a non-empty list of PyPIConGPUSpecies") + + def _get_serialized(self) -> typing.Dict: + return { + "dataset": "boundElectronDensity", + "filter": self._filter, + "species": [s.get_rendering_context() for s in self._species], + } diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_source/charge_density.py b/lib/python/picongpu/pypicongpu/output/openpmd_source/charge_density.py index fe4e6b13a8..2b804f6236 100644 --- a/lib/python/picongpu/pypicongpu/output/openpmd_source/charge_density.py +++ b/lib/python/picongpu/pypicongpu/output/openpmd_source/charge_density.py @@ -5,33 +5,30 @@ License: GPLv3+ """ -from .. import util +from ...species import Species as PyPIConGPUSpecies import typeguard import typing @typeguard.typechecked class ChargeDensity: - filter = util.build_typesafe_property(typing.Optional[str]) + filter = property(lambda self: self._filter) + species = property(lambda self: self._species) - def __init__(self, filter: typing.Optional[str] = None): - self.filter = filter + def __init__(self, filter: str, species: typing.List[PyPIConGPUSpecies]): + self._filter = filter + self._species = species self.check() - def check(self): - if self.filter is not None and not isinstance(self.filter, str): - raise ValueError( - f"Filter must be a string or None, got {type(self.filter)}. " - "Valid filter names are defined in particleFilters.param " - "(see picongpu/include/picongpu/param/particleFilters.param). " - "The default filter is 'all' (selects all valid particles). Additional filters, such as " - "'relativeGlobalDomainPosition' (selects particles in a global domain range), can be defined " - "in your local particleFilters.param file. Valid filters are listed in the PIConGPU " - "command-line help for --openPMD.source." - ) + def check(self) -> None: + if not isinstance(self._filter, str): + raise ValueError(f"Filter must be a string, got {type(self._filter)}") + if not self._species or not all(isinstance(s, PyPIConGPUSpecies) for s in self._species): + raise ValueError("Species must be a non-empty list of PyPIConGPUSpecies") def _get_serialized(self) -> typing.Dict: return { - "dataset": "charge_density", - "filter": self.filter, + "dataset": "chargeDensity", + "filter": self._filter, + "species": [s.get_rendering_context() for s in self._species], } From abcc8acade9e8aa3bdf532321eb90d54fd90eecd Mon Sep 17 00:00:00 2001 From: mafshari64 Date: Tue, 6 May 2025 16:34:04 +0200 Subject: [PATCH 04/15] adding other openpmd picongpu sources --- .../picongpu/picmi/diagnostics/openpmd.py | 89 +++++-------------- .../diagnostics/openpmd_sources/__init__.py | 5 +- .../picmi/diagnostics/openpmd_sources/auto.py | 27 ++---- .../openpmd_sources/bound_electron_density.py | 45 +++------- .../openpmd_sources/charge_density.py | 43 ++------- .../diagnostics/openpmd_sources/counter.py | 69 ++++++++++++++ .../diagnostics/openpmd_sources/density.py | 68 ++++++++++++++ .../openpmd_sources/derived_attributes.py | 45 ++++++++++ .../diagnostics/openpmd_sources/energy.py | 66 ++++++++++++++ .../openpmd_sources/energy_density.py | 66 ++++++++++++++ .../openpmd_sources/energy_density_cutoff.py | 77 ++++++++++++++++ .../openpmd_sources/larmor_power.py | 67 ++++++++++++++ .../openpmd_sources/macro_counter.py | 65 ++++++++++++++ .../mid_current_density_component.py | 77 ++++++++++++++++ .../diagnostics/openpmd_sources/momentum.py | 76 ++++++++++++++++ .../openpmd_sources/momentum_density.py | 76 ++++++++++++++++ .../openpmd_sources/weighted_velocity.py | 77 ++++++++++++++++ 17 files changed, 884 insertions(+), 154 deletions(-) create mode 100644 lib/python/picongpu/picmi/diagnostics/openpmd_sources/counter.py create mode 100644 lib/python/picongpu/picmi/diagnostics/openpmd_sources/density.py create mode 100644 lib/python/picongpu/picmi/diagnostics/openpmd_sources/derived_attributes.py create mode 100644 lib/python/picongpu/picmi/diagnostics/openpmd_sources/energy.py create mode 100644 lib/python/picongpu/picmi/diagnostics/openpmd_sources/energy_density.py create mode 100644 lib/python/picongpu/picmi/diagnostics/openpmd_sources/energy_density_cutoff.py create mode 100644 lib/python/picongpu/picmi/diagnostics/openpmd_sources/larmor_power.py create mode 100644 lib/python/picongpu/picmi/diagnostics/openpmd_sources/macro_counter.py create mode 100644 lib/python/picongpu/picmi/diagnostics/openpmd_sources/mid_current_density_component.py create mode 100644 lib/python/picongpu/picmi/diagnostics/openpmd_sources/momentum.py create mode 100644 lib/python/picongpu/picmi/diagnostics/openpmd_sources/momentum_density.py create mode 100644 lib/python/picongpu/picmi/diagnostics/openpmd_sources/weighted_velocity.py diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd.py b/lib/python/picongpu/picmi/diagnostics/openpmd.py index c805d86884..238dd42268 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd.py @@ -1,6 +1,6 @@ """ This file is part of PIConGPU. -Copyright 2021-2025 PIConGPU contributors +Copyright 2025-2025 PIConGPU contributors Authors: Masoud Afshari License: GPLv3+ """ @@ -8,7 +8,9 @@ from ...pypicongpu.output.openpmd import OpenPMD as PyPIConGPUOpenPMD from ...pypicongpu.output import Source as PyPIConGPUSource from .timestepspec import TimeStepSpec + from .openpmd_sources.source_base import SourceBase + import typeguard from typing import Optional, Dict, Union, List, Literal @@ -16,71 +18,26 @@ @typeguard.typechecked class OpenPMD: """ - Specifies parameters for openPMD diagnostic output in particle-in-cell simulations. - - This diagnostic outputs simulation data (fields and particles) to disk using the openPMD - standard, with configurable periods, data sources, and backend settings. - - Attention: **period is mandatory.** - - Parameters - ---------- - period: TimeStepSpec - Specifies the time steps for data output. - Unit: simulation time steps. Required. - - source: List[SourceBase], optional - List of data source objects to dump (e.g., [ChargeDensity(filter="all")]). - Default: None (no sources specified). - - range: str, optional - Contiguous range of cells per dimension to dump (e.g., ":,:,:"). - Format: comma-separated ranges like "begin:end,begin:end,begin:end". - Default: ":,::,:" (all cells). - - file: str, optional - Relative or absolute file prefix for openPMD output files. - If relative, files are stored under the simulation output directory. - Default: None (backend-dependent default). - - ext: Literal["bp", "h5", "sst"], optional - File extension controlling the openPMD backend. - Options: "bp" (ADIOS2), "h5" (HDF5), "sst" (ADIOS2/SST for streaming). - Default: "bp" (ADIOS2 backend). - - infix: str, optional - Filename infix for iteration layout (e.g., "_%06T"). - Use "NULL" for group-based layout. Required as "NULL" if ext="sst". - Default: "NULL" (group-based layout). - - json: Union[str, Dict], optional - Backend-specific parameters for writing, as a JSON string, dictionary, or filename - (filename must be prepended with "@"). - Default: {} (empty dictionary). - - json_restart: Union[str, Dict], optional - Backend-specific parameters for restarting, as a JSON string, dictionary, or filename - (filename must be prepended with "@"). - Default: {} (empty dictionary). - - data_preparation_strategy: Literal["doubleBuffer", "adios", "mappedMemory", "hdf5"], optional - Strategy for particle data preparation. - Options: "doubleBuffer" or "adios" (ADIOS2-based), "mappedMemory" or "hdf5" (HDF5-based). - Default: None (backend-dependent). - - toml: str, optional - Path to a TOML file for openPMD configuration. - Default: None. - - particle_io_chunk_size: int, optional - Size of particle data chunks for writing (in MiB). - Reduces host memory footprint for certain backends. - Default: None (backend-dependent). - - file_access: Literal["create", "append"], optional - File access mode for writing. - Options: "create" (new files), "append" (for checkpoint-restart workflows). - Default: "create" (new files). + openPMD diagnostic output + + This diagnostic writes simulation data (base fields, derived fields and/or particles) to disk using the openPMD + standard, with configurable periods, data sources and backend settings. + + @param period specification of the time steps for data output, outputs will always be written at the end of a PIC time step. + @param source list of data source objects to include in the dump (e.g., [ChargeDensity(filter="all")]), + Setting this to None will cause an empty dump + @param range contiguous range of cells to dump the base- and derived field for + specification as comma-separated "begin:end" range for each dimension, i.e. "begin:end,begin:end,begin:end" + Notes: Values will be clipped to the simulation box. Begin and/or end may be omitted to indicate the limit of the simulation box in the dimension. The Default value ":,::,:" indicates that fields should be dumped for all cells. + @param file relative or absolute file path prefix for openPMD output files. Relative paths are interpreted as relative to the simulation output directory, the default value None indicates the PIC code's default. + @param ext file extension controlling the openPMD backend, options are "bp" (default backend ADIOS2), "bp4" (bp4 backend ADIOS2), "bp5" (bp5 backend ADIOS2), "h5" (HDF5), "sst" (ADIOS2/SST for streaming). + @param infix filename infix for the iteration layout (e.g., "_%06T"), use "NULL" for the group-based layout, ext="sst" requires infix="NULL". + @param json openPMD backend configuration as a JSON string, dictionary, or filename (filename must be prepended with "@"). + @param json_restart backend-specific parameters for restarting, as a JSON string, dictionary, or filename (filenames must be prepended with "@"). + @param data_preparation_strategy strategy for particle data preparation, options: "doubleBuffer" or "adios" (ADIOS2-based), "mappedMemory" or "hdf5" (HDF5-based), the default value None indicates the PIC code default + @param toml path to a TOML file for openPMD configuration. Replaces the JSON or keyword configuration. + @param particle_io_chunk_size size of particle data chunks used in writing (in MiB), reduces host memory footprint for certain backends, default "None" indicates the PIC code default. + @param file_access file access mode for writing, options: "create" (new files), "append" (for checkpoint-restart workflows). """ def check(self): diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/__init__.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/__init__.py index a0a4160a8a..8bad5040c4 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/__init__.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/__init__.py @@ -1,7 +1,9 @@ +from .auto import Auto from .bound_electron_density import BoundElectronDensity from .charge_density import ChargeDensity from .counter import Counter from .density import Density +from .derived_attributes import DerivedAttributes from .energy import Energy from .energy_density import EnergyDensity from .energy_density_cutoff import EnergyDensityCutoff @@ -11,14 +13,15 @@ from .momentum import Momentum from .momentum_density import MomentumDensity from .weighted_velocity import WeightedVelocity - from .source_base import SourceBase __all__ = [ + "Auto", "BoundElectronDensity", "ChargeDensity", "Counter", "Density", + "DerivedAttributes", "Energy", "EnergyDensity", "EnergyDensityCutoff", diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/auto.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/auto.py index 8cbed08e36..61597dd9af 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/auto.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/auto.py @@ -1,6 +1,6 @@ """ This file is part of PIConGPU. -Copyright 2021-2024 PIConGPU contributors +Copyright 2025-2025 PIConGPU contributors Authors: Masoud Afshari License: GPLv3+ """ @@ -15,17 +15,14 @@ @typeguard.typechecked class Auto(SourceBase): """ - Represents a default data source for openPMD output in particle-in-cell simulations. + Default data source for openPMD output - This class specifies a backend-specific default source for dumping simulation data - using the openPMD standard. For example, in some backends, it may dump all particle - species and fields. + This class provides a convenient way to dump default simulation data (e.g., all + particle species and fields) using the openPMD standard, with defaults determined + by the PIC code in particle-in-cell simulations. - Parameters - ---------- - filter: str, optional - Name of a filter to select data contributing to the source. - Default: None (backend-dependent). + @param filter Name of a filter to select data contributing to the source. + Default: None (PIC code-dependent). """ filter = util.build_typesafe_property(typing.Optional[str]) @@ -38,10 +35,7 @@ def check(self) -> None: """ Validate the filter parameter. - Raises - ------ - ValueError - If the filter is not a string or None. + @throw ValueError If the filter is not a string or None. """ if self.filter is not None and not isinstance(self.filter, str): raise ValueError(f"Filter must be a string or None, got {type(self.filter)}") @@ -50,10 +44,7 @@ def get_as_pypicongpu(self) -> PyPIConGPUAuto: """ Convert this Auto source to a PyPIConGPU Auto source. - Returns - ------- - PyPIConGPUAuto - A PyPIConGPU Auto instance with the same filter. + @return A PyPIConGPU Auto instance with the same filter. """ self.check() return PyPIConGPUAuto(filter=self.filter) diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/bound_electron_density.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/bound_electron_density.py index bf7a0be81a..51354b58d0 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/bound_electron_density.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/bound_electron_density.py @@ -1,6 +1,6 @@ """ This file is part of PIConGPU. -Copyright 2021-2024 PIConGPU contributors +Copyright 2025-2025 PIConGPU contributors Authors: Masoud Afshari License: GPLv3+ """ @@ -15,18 +15,13 @@ @typeguard.typechecked class BoundElectronDensity(SourceBase): """ - Represents a bound electron density data source for openPMD output in particle-in-cell simulations. + Bound electron density data source for openPMD output - This source calculates the density of bound electrons from a specified particle species, optionally - filtered by a selection criterion. + This source calculates the density of bound electrons from a specified particle species, + optionally filtered by a selection criterion, for particle-in-cell simulations. - Parameters - ---------- - species: PICMISpecies - Particle species contributing to the bound electron density (e.g., ions). - - filter: str, optional - Name of a filter to select particles contributing to the source. + @param species Particle species contributing to the bound electron density (e.g., ions). + @param filter Name of a filter to select particles contributing to the source. Default: "all" (includes all particles of the specified species). """ @@ -39,10 +34,7 @@ def check(self) -> None: """ Validate the parameters. - Raises - ------ - ValueError - If filter is not a string or species is not a PICMISpecies. + @throw ValueError If filter is not a string or species is not a PICMISpecies. """ if not isinstance(self.filter, str): raise ValueError(f"Filter must be a string, got {type(self.filter)}") @@ -52,30 +44,13 @@ def check(self) -> None: def get_as_pypicongpu( self, dict_species_picmi_to_pypicongpu: dict[PICMISpecies, typing.Any], - time_step_size: float, - num_steps: int, ) -> PyPIConGPUBoundElectronDensity: """ Convert this BoundElectronDensity source to a PyPIConGPU BoundElectronDensity source. - Parameters - ---------- - dict_species_picmi_to_pypicongpu: dict[PICMISpecies, Any] - Mapping of PICMI species to PyPIConGPU species. - time_step_size: float - Size of a simulation time step (unused). - num_steps: int - Total number of simulation steps (unused). - - Returns - ------- - PyPIConGPUBoundElectronDensity - A PyPIConGPU BoundElectronDensity instance with the same filter and species. - - Raises - ------ - ValueError - If the species is not known to the simulation or not mapped to a PyPIConGPUSpecies. + @param dict_species_picmi_to_pypicongpu Mapping of PICMI species to PyPIConGPU species. + @return A PyPIConGPU BoundElectronDensity instance with the same filter and species. + @throw ValueError If the species is not known to the simulation or not mapped to a PyPIConGPUSpecies. """ self.check() diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/charge_density.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/charge_density.py index db18b34fcd..a985e9e158 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/charge_density.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/charge_density.py @@ -1,6 +1,6 @@ """ This file is part of PIConGPU. -Copyright 2021-2024 PIConGPU contributors +Copyright 2025-2025 PIConGPU contributors Authors: Masoud Afshari License: GPLv3+ """ @@ -15,18 +15,13 @@ @typeguard.typechecked class ChargeDensity(SourceBase): """ - Represents a charge density data source for openPMD output in particle-in-cell simulations. + Charge density data source for openPMD output This source calculates the charge density from a specified particle species, optionally - filtered by a selection criterion. + filtered by a selection criterion, for particle-in-cell simulations. - Parameters - ---------- - species: PICMISpecies - Particle species contributing to the charge density (e.g., electrons, protons). - - filter: str, optional - Name of a filter to select particles contributing to the source. + @param species Particle species contributing to the charge density (e.g., electrons, protons). + @param filter Name of a filter to select particles contributing to the source. Default: "all" (includes all particles of the specified species). """ @@ -39,10 +34,7 @@ def check(self) -> None: """ Validate the parameters. - Raises - ------ - ValueError - If filter is not a string or species is not a PICMISpecies. + @throw ValueError If filter is not a string or species is not a PICMISpecies. """ if not isinstance(self.filter, str): raise ValueError(f"Filter must be a string, got {type(self.filter)}") @@ -52,30 +44,13 @@ def check(self) -> None: def get_as_pypicongpu( self, dict_species_picmi_to_pypicongpu: dict[PICMISpecies, typing.Any], - time_step_size: float, - num_steps: int, ) -> PyPIConGPUChargeDensity: """ Convert this ChargeDensity source to a PyPIConGPU ChargeDensity source. - Parameters - ---------- - dict_species_picmi_to_pypicongpu: dict[PICMISpecies, Any] - Mapping of PICMI species to PyPIConGPU species. - time_step_size: float - Size of a simulation time step (unused). - num_steps: int - Total number of simulation steps (unused). - - Returns - ------- - PyPIConGPUChargeDensity - A PyPIConGPU ChargeDensity instance with the same filter and species. - - Raises - ------ - ValueError - If the species is not known to the simulation or not mapped to a PyPIConGPUSpecies. + @param dict_species_picmi_to_pypicongpu Mapping of PICMI species to PyPIConGPU species. + @return A PyPIConGPU ChargeDensity instance with the same filter and species. + @throw ValueError If the species is not known to the simulation or not mapped to a PyPIConGPUSpecies. """ self.check() diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/counter.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/counter.py new file mode 100644 index 0000000000..ff515db6a2 --- /dev/null +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/counter.py @@ -0,0 +1,69 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .source_base import SourceBase +from ...pypicongpu.output.openpmd_source import Counter as PyPIConGPUCounter +from ...species import Species as PICMISpecies +import typeguard +import typing + + +@typeguard.typechecked +class Counter(SourceBase): + """ + Particle counter data source for openPMD output + + This source derives a scalar field representing the number of real particles per cell + for a specified species, optionally filtered by a selection criterion, in particle-in-cell + simulations. The particle count is based on the species' weighting attribute and assigned + directly to the cell containing each particle. Intended primarily for debugging due to + its non-physical deposition shape, which differs from standard charge or momentum-conserving + assignments. + + @param species Particle species to count (e.g., electrons, ions). Must have a weighting attribute. + @param filter Name of a filter to select particles contributing to the source. + Default: "all" (includes all particles of the specified species). + """ + + def __init__(self, species: PICMISpecies, filter: str = "all"): + self.species = species + self.filter = filter + self.check() + + def check(self) -> None: + """ + Validate the parameters. + + @throw ValueError If filter is not a string or species is not a PICMISpecies. + """ + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, PICMISpecies): + raise ValueError(f"Species must be a PICMISpecies, got {type(self.species)}") + + def get_as_pypicongpu( + self, + dict_species_picmi_to_pypicongpu: dict[PICMISpecies, typing.Any], + ) -> PyPIConGPUCounter: + """ + Convert this Counter source to a PyPIConGPU Counter source. + + @param dict_species_picmi_to_pypicongpu Mapping of PICMI species to PyPIConGPU species. + @return A PyPIConGPU Counter instance with the same filter and species. + @throw ValueError If the species is not known to the simulation or not mapped to a PyPIConGPUSpecies. + """ + self.check() + + if self.species not in dict_species_picmi_to_pypicongpu.keys(): + raise ValueError(f"Species {self.species} is not known to Simulation") + + pypicongpu_species = dict_species_picmi_to_pypicongpu.get(self.species) + + if pypicongpu_species is None: + raise ValueError(f"Species {self.species} is not mapped to a PyPIConGPUSpecies.") + + return PyPIConGPUCounter(filter=self.filter, species=pypicongpu_species) diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/density.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/density.py new file mode 100644 index 0000000000..0651541016 --- /dev/null +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/density.py @@ -0,0 +1,68 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .source_base import SourceBase +from ...pypicongpu.output.openpmd_source import Density as PyPIConGPUDensity +from ...species import Species as PICMISpecies +import typeguard +import typing + + +@typeguard.typechecked +class Density(SourceBase): + """ + Particle density data source for openPMD output + + This source derives a scalar field representing the number density (in m^-3) of a specified + particle species, optionally filtered by a selection criterion, in particle-in-cell simulations. + The density is calculated based on the species' weighting and position attributes and mapped to + cells according to the PIC code's spatial shape assignment. + + @param species Particle species to calculate density for (e.g., electrons, ions). + Must have weighting and position attributes. + @param filter Name of a filter to select particles contributing to the source. + Default: "all" (includes all particles of the specified species). + """ + + def __init__(self, species: PICMISpecies, filter: str = "all"): + self.species = species + self.filter = filter + self.check() + + def check(self) -> None: + """ + Validate the parameters. + + @throw ValueError If filter is not a string or species is not a PICMISpecies. + """ + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, PICMISpecies): + raise ValueError(f"Species must be a PICMISpecies, got {type(self.species)}") + + def get_as_pypicongpu( + self, + dict_species_picmi_to_pypicongpu: dict[PICMISpecies, typing.Any], + ) -> PyPIConGPUDensity: + """ + Convert this Density source to a PyPIConGPU Density source. + + @param dict_species_picmi_to_pypicongpu Mapping of PICMI species to PyPIConGPU species. + @return A PyPIConGPU Density instance with the same filter and species. + @throw ValueError If the species is not known to the simulation or not mapped to a PyPIConGPUSpecies. + """ + self.check() + + if self.species not in dict_species_picmi_to_pypicongpu.keys(): + raise ValueError(f"Species {self.species} is not known to Simulation") + + pypicongpu_species = dict_species_picmi_to_pypicongpu.get(self.species) + + if pypicongpu_species is None: + raise ValueError(f"Species {self.species} is not mapped to a PyPIConGPUSpecies.") + + return PyPIConGPUDensity(filter=self.filter, species=pypicongpu_species) diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/derived_attributes.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/derived_attributes.py new file mode 100644 index 0000000000..4b02400f29 --- /dev/null +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/derived_attributes.py @@ -0,0 +1,45 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .source_base import SourceBase +from ...pypicongpu.output.openpmd_source import DerivedAttributes as PyPIConGPUDerivedAttributes +import typeguard +import typing + + +@typeguard.typechecked +class DerivedAttributes(SourceBase): + """ + Aggregated derived attributes data source for openPMD output + + Enables all particle-to-grid derived attributes (e.g., density, charge) for openPMD output + in particle-in-cell simulations, with defaults determined by the PIC code. + + @param filter Name of a filter to select data. Default: None (PIC code-dependent). + """ + + def __init__(self, filter: typing.Optional[str] = None): + self.filter = filter + self.check() + + def check(self) -> None: + """ + Validate the filter parameter. + + @throw ValueError If filter is not a string or None. + """ + if self.filter is not None and not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string or None, got {type(self.filter)}") + + def get_as_pypicongpu(self) -> PyPIConGPUDerivedAttributes: + """ + Convert to a PyPIConGPU DerivedAttributes source. + + @return A PyPIConGPU DerivedAttributes instance with the same filter. + """ + self.check() + return PyPIConGPUDerivedAttributes(filter=self.filter) diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/energy.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/energy.py new file mode 100644 index 0000000000..915669c5df --- /dev/null +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/energy.py @@ -0,0 +1,66 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .source_base import SourceBase +from ...pypicongpu.output.openpmd_source import Energy as PyPIConGPEnergy +from ...species import Species as PICMISpecies +import typeguard +import typing + + +@typeguard.typechecked +class Energy(SourceBase): + """ + Kinetic energy data source for openPMD output + + Derives a scalar field of summed kinetic energy (in Joules) for a specified particle species, + optionally filtered, in particle-in-cell simulations. Uses weighting, momentum, and mass attributes, + mapped to cells by the PIC code's spatial shape. + + @param species Particle species to calculate energy for (e.g., electrons, ions). + Must have weighting, momentum, and mass attributes. + @param filter Name of a filter to select particles. Default: "all". + """ + + def __init__(self, species: PICMISpecies, filter: str = "all"): + self.species = species + self.filter = filter + self.check() + + def check(self) -> None: + """ + Validate the parameters. + + @throw ValueError If filter is not a string or species is not a PICMISpecies. + """ + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, PICMISpecies): + raise ValueError(f"Species must be a PICMISpecies, got {type(self.species)}") + + def get_as_pypicongpu( + self, + dict_species_picmi_to_pypicongpu: dict[PICMISpecies, typing.Any], + ) -> PyPIConGPEnergy: + """ + Convert to a PyPIConGPU Energy source. + + @param dict_species_picmi_to_pypicongpu Mapping of PICMI to PyPIConGPU species. + @return A PyPIConGPU Energy instance with the same filter and species. + @throw ValueError If species is unknown or unmapped to a PyPIConGPUSpecies. + """ + self.check() + + if self.species not in dict_species_picmi_to_pypicongpu.keys(): + raise ValueError(f"Species {self.species} is not known to Simulation") + + pypicongpu_species = dict_species_picmi_to_pypicongpu.get(self.species) + + if pypicongpu_species is None: + raise ValueError(f"Species {self.species} is not mapped to a PyPIConGPUSpecies.") + + return PyPIConGPEnergy(filter=self.filter, species=pypicongpu_species) diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/energy_density.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/energy_density.py new file mode 100644 index 0000000000..9c7a1f5468 --- /dev/null +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/energy_density.py @@ -0,0 +1,66 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .source_base import SourceBase +from ...pypicongpu.output.openpmd_source import EnergyDensity as PyPIConGPUEnergyDensity +from ...species import Species as PICMISpecies +import typeguard +import typing + + +@typeguard.typechecked +class EnergyDensity(SourceBase): + """ + Kinetic energy density data source for openPMD output + + Derives a scalar field of kinetic energy density (in J/m^3) for a specified particle species, + optionally filtered, in particle-in-cell simulations. Uses weighting, momentum, and mass attributes, + mapped to cells by the PIC code's spatial shape. + + @param species Particle species to calculate energy density for (e.g., electrons, ions). + Must have weighting, momentum, and mass attributes. + @param filter Name of a filter to select particles. Default: "all". + """ + + def __init__(self, species: PICMISpecies, filter: str = "all"): + self.species = species + self.filter = filter + self.check() + + def check(self) -> None: + """ + Validate the parameters. + + @throw ValueError If filter is not a string or species is not a PICMISpecies. + """ + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, PICMISpecies): + raise ValueError(f"Species must be a PICMISpecies, got {type(self.species)}") + + def get_as_pypicongpu( + self, + dict_species_picmi_to_pypicongpu: dict[PICMISpecies, typing.Any], + ) -> PyPIConGPUEnergyDensity: + """ + Convert to a PyPIConGPU EnergyDensity source. + + @param dict_species_picmi_to_pypicongpu Mapping of PICMI to PyPIConGPU species. + @return A PyPIConGPU EnergyDensity instance with the same filter and species. + @throw ValueError If species is unknown or unmapped to a PyPIConGPUSpecies. + """ + self.check() + + if self.species not in dict_species_picmi_to_pypicongpu.keys(): + raise ValueError(f"Species {self.species} is not known to Simulation") + + pypicongpu_species = dict_species_picmi_to_pypicongpu.get(self.species) + + if pypicongpu_species is None: + raise ValueError(f"Species {self.species} is not mapped to a PyPIConGPUSpecies.") + + return PyPIConGPUEnergyDensity(filter=self.filter, species=pypicongpu_species) diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/energy_density_cutoff.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/energy_density_cutoff.py new file mode 100644 index 0000000000..4bb47ac2c7 --- /dev/null +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/energy_density_cutoff.py @@ -0,0 +1,77 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .source_base import SourceBase +from ...pypicongpu.output.openpmd_source import EnergyDensityCutoff as PyPIConGPUEnergyDensityCutoff +from ...species import Species as PICMISpecies +import typeguard +import typing + + +@typeguard.typechecked +class EnergyDensityCutoff(SourceBase): + """ + Kinetic energy density data source with cutoff for openPMD output + + Derives a scalar field of kinetic energy density (in J/m^3) for a specified particle species, + optionally filtered, including only particles with kinetic energy below a user-defined cutoff, + in particle-in-cell simulations. Uses weighting, momentum, and mass attributes, mapped to cells + by the PIC code's spatial shape. + + @param species Particle species to calculate energy density for (e.g., electrons, ions). + Must have weighting, momentum, and mass attributes. + @param filter Name of a filter to select particles. Default: "all". + @param cutoff_max_energy Maximum kinetic energy cutoff (in Joules). + """ + + def __init__(self, species: PICMISpecies, filter: str = "all", cutoff_max_energy: float = None): + self.species = species + self.filter = filter + self.cutoff_max_energy = cutoff_max_energy + self.check() + + def check(self) -> None: + """ + Validate the parameters. + + @throw ValueError If filter is not a string, species is not a PICMISpecies, or cutoff_max_energy is invalid. + """ + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, PICMISpecies): + raise ValueError(f"Species must be a PICMISpecies, got {type(self.species)}") + if self.cutoff_max_energy is not None and not isinstance(self.cutoff_max_energy, (int, float)): + raise ValueError(f"cutoff_max_energy must be a number or None, got {type(self.cutoff_max_energy)}") + if self.cutoff_max_energy is not None and self.cutoff_max_energy <= 0: + raise ValueError(f"cutoff_max_energy must be positive, got {self.cutoff_max_energy}") + + def get_as_pypicongpu( + self, + dict_species_picmi_to_pypicongpu: dict[PICMISpecies, typing.Any], + ) -> PyPIConGPUEnergyDensityCutoff: + """ + Convert to a PyPIConGPU EnergyDensityCutoff source. + + @param dict_species_picmi_to_pypicongpu Mapping of PICMI to PyPIConGPU species. + @return A PyPIConGPU EnergyDensityCutoff instance with the same filter, species, and cutoff. + @throw ValueError If species is unknown or unmapped to a PyPIConGPUSpecies. + """ + self.check() + + if self.species not in dict_species_picmi_to_pypicongpu.keys(): + raise ValueError(f"Species {self.species} is not known to Simulation") + + pypicongpu_species = dict_species_picmi_to_pypicongpu.get(self.species) + + if pypicongpu_species is None: + raise ValueError(f"Species {self.species} is not mapped to a PyPIConGPUSpecies.") + + return PyPIConGPUEnergyDensityCutoff( + filter=self.filter, + species=pypicongpu_species, + cutoff_max_energy=self.cutoff_max_energy, + ) diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/larmor_power.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/larmor_power.py new file mode 100644 index 0000000000..412800b7cc --- /dev/null +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/larmor_power.py @@ -0,0 +1,67 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .source_base import SourceBase +from ...pypicongpu.output.openpmd_source import LarmorPower as PyPIConGPULarmorPower +from ...species import Species as PICMISpecies +import typeguard +import typing + + +@typeguard.typechecked +class LarmorPower(SourceBase): + """ + Radiated Larmor power data source for openPMD output + + Derives a scalar field of radiated power (in Joules) for a specified particle species, + optionally filtered, using the Larmor formula in particle-in-cell simulations. Uses + weighting, position, momentum, momentumPrev1, mass, and charge attributes, mapped to + cells by the PIC code's spatial shape. + + @param species Particle species to calculate Larmor power for (e.g., electrons, ions). + Must have weighting, position, momentum, momentumPrev1, mass, and charge attributes. + @param filter Name of a filter to select particles. Default: "all". + """ + + def __init__(self, species: PICMISpecies, filter: str = "all"): + self.species = species + self.filter = filter + self.check() + + def check(self) -> None: + """ + Validate the parameters. + + @throw ValueError If filter is not a string or species is not a PICMISpecies. + """ + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, PICMISpecies): + raise ValueError(f"Species must be a PICMISpecies, got {type(self.species)}") + + def get_as_pypicongpu( + self, + dict_species_picmi_to_pypicongpu: dict[PICMISpecies, typing.Any], + ) -> PyPIConGPULarmorPower: + """ + Convert to a PyPIConGPU LarmorPower source. + + @param dict_species_picmi_to_pypicongpu Mapping of PICMI to PyPIConGPU species. + @return A PyPIConGPU LarmorPower instance with the same filter and species. + @throw ValueError If species is unknown or unmapped to a PyPIConGPUSpecies. + """ + self.check() + + if self.species not in dict_species_picmi_to_pypicongpu.keys(): + raise ValueError(f"Species {self.species} is not known to Simulation") + + pypicongpu_species = dict_species_picmi_to_pypicongpu.get(self.species) + + if pypicongpu_species is None: + raise ValueError(f"Species {self.species} is not mapped to a PyPIConGPUSpecies.") + + return PyPIConGPULarmorPower(filter=self.filter, species=pypicongpu_species) diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/macro_counter.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/macro_counter.py new file mode 100644 index 0000000000..7fcd849a6d --- /dev/null +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/macro_counter.py @@ -0,0 +1,65 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .source_base import SourceBase +from ...pypicongpu.output.openpmd_source import MacroCounter as PyPIConGPUMacroCounter +from ...species import Species as PICMISpecies +import typeguard +import typing + + +@typeguard.typechecked +class MacroCounter(SourceBase): + """ + Macro-particle counter data source for openPMD output + + Derives a scalar field counting macro-particles per cell for a specified particle species, + optionally filtered, in particle-in-cell simulations. Assigns each macro-particle directly + to its cell via floor operation. Intended for debugging (e.g., validating particle memory). + + @param species Particle species to count (e.g., electrons, ions). + @param filter Name of a filter to select particles. Default: "all". + """ + + def __init__(self, species: PICMISpecies, filter: str = "all"): + self.species = species + self.filter = filter + self.check() + + def check(self) -> None: + """ + Validate the parameters. + + @throw ValueError If filter is not a string or species is not a PICMISpecies. + """ + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, PICMISpecies): + raise ValueError(f"Species must be a PICMISpecies, got {type(self.species)}") + + def get_as_pypicongpu( + self, + dict_species_picmi_to_pypicongpu: dict[PICMISpecies, typing.Any], + ) -> PyPIConGPUMacroCounter: + """ + Convert to a PyPIConGPU MacroCounter source. + + @param dict_species_picmi_to_pypicongpu Mapping of PICMI to PyPIConGPU species. + @return A PyPIConGPU MacroCounter instance with the same filter and species. + @throw ValueError If species is unknown or unmapped to a PyPIConGPUSpecies. + """ + self.check() + + if self.species not in dict_species_picmi_to_pypicongpu.keys(): + raise ValueError(f"Species {self.species} is not known to Simulation") + + pypicongpu_species = dict_species_picmi_to_pypicongpu.get(self.species) + + if pypicongpu_species is None: + raise ValueError(f"Species {self.species} is not mapped to a PyPIConGPUSpecies.") + + return PyPIConGPUMacroCounter(filter=self.filter, species=pypicongpu_species) diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/mid_current_density_component.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/mid_current_density_component.py new file mode 100644 index 0000000000..40d0ab0c48 --- /dev/null +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/mid_current_density_component.py @@ -0,0 +1,77 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .source_base import SourceBase +from ...pypicongpu.output.openpmd_source import MidCurrentDensityComponent as PyPIConGPUMidCurrentDensityComponent +from ...species import Species as PICMISpecies +import typeguard +import typing + + +@typeguard.typechecked +class MidCurrentDensityComponent(SourceBase): + """ + Current density component data source for openPMD output + + Derives a scalar field of current density (in A/m^2) in a specified direction (x=0, y=1, z=2) + for a specified particle species, optionally filtered, in particle-in-cell simulations. Uses + weighting, position, momentum, mass, and charge attributes, mapped to cells by the PIC code's + spatial shape. Intended for debugging (e.g., validating current solvers). + + @param species Particle species to calculate current density for (e.g., electrons, ions). + Must have weighting, position, momentum, mass, and charge attributes. + @param filter Name of a filter to select particles. Default: "all". + @param direction Direction of current density (0=x, 1=y, 2=z). + """ + + def __init__(self, species: PICMISpecies, filter: str = "all", direction: int = 0): + self.species = species + self.filter = filter + self.direction = direction + self.check() + + def check(self) -> None: + """ + Validate the parameters. + + @throw ValueError If filter is not a string, species is not a PICMISpecies, or direction is invalid. + """ + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, PICMISpecies): + raise ValueError(f"Species must be a PICMISpecies, got {type(self.species)}") + if not isinstance(self.direction, int): + raise ValueError(f"Direction must be an integer, got {type(self.direction)}") + if self.direction not in [0, 1, 2]: + raise ValueError(f"Direction must be 0 (x), 1 (y), or 2 (z), got {self.direction}") + + def get_as_pypicongpu( + self, + dict_species_picmi_to_pypicongpu: dict[PICMISpecies, typing.Any], + ) -> PyPIConGPUMidCurrentDensityComponent: + """ + Convert to a PyPIConGPU MidCurrentDensityComponent source. + + @param dict_species_picmi_to_pypicongpu Mapping of PICMI to PyPIConGPU species. + @return A PyPIConGPU MidCurrentDensityComponent instance with the same filter, species, and direction. + @throw ValueError If species is unknown or unmapped to a PyPIConGPUSpecies. + """ + self.check() + + if self.species not in dict_species_picmi_to_pypicongpu.keys(): + raise ValueError(f"Species {self.species} is not known to Simulation") + + pypicongpu_species = dict_species_picmi_to_pypicongpu.get(self.species) + + if pypicongpu_species is None: + raise ValueError(f"Species {self.species} is not mapped to a PyPIConGPUSpecies.") + + return PyPIConGPUMidCurrentDensityComponent( + filter=self.filter, + species=pypicongpu_species, + direction=self.direction, + ) diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/momentum.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/momentum.py new file mode 100644 index 0000000000..63383d89eb --- /dev/null +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/momentum.py @@ -0,0 +1,76 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .source_base import SourceBase +from ...pypicongpu.output.openpmd_source import Momentum as PyPIConGPUMomentum +from ...species import Species as PICMISpecies +import typeguard +import typing + + +@typeguard.typechecked +class Momentum(SourceBase): + """ + Momentum component data source for openPMD output + + Derives a scalar field of a specified momentum component (in kg·m/s, direction x=0, y=1, z=2) + for a specified particle species, optionally filtered, in particle-in-cell simulations. Uses + position and momentum attributes, mapped to cells by the PIC code's spatial shape. + + @param species Particle species to calculate momentum for (e.g., electrons, ions). + Must have position and momentum attributes. + @param filter Name of a filter to select particles. Default: "all". + @param direction Momentum component direction (0=x, 1=y, 2=z). + """ + + def __init__(self, species: PICMISpecies, filter: str = "all", direction: int = 0): + self.species = species + self.filter = filter + self.direction = direction + self.check() + + def check(self) -> None: + """ + Validate the parameters. + + @throw ValueError If filter is not a string, species is not a PICMISpecies, or direction is invalid. + """ + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, PICMISpecies): + raise ValueError(f"Species must be a PICMISpecies, got {type(self.species)}") + if not isinstance(self.direction, int): + raise ValueError(f"Direction must be an integer, got {type(self.direction)}") + if self.direction not in [0, 1, 2]: + raise ValueError(f"Direction must be 0 (x), 1 (y), or 2 (z), got {self.direction}") + + def get_as_pypicongpu( + self, + dict_species_picmi_to_pypicongpu: dict[PICMISpecies, typing.Any], + ) -> PyPIConGPUMomentum: + """ + Convert to a PyPIConGPU Momentum source. + + @param dict_species_picmi_to_pypicongpu Mapping of PICMI to PyPIConGPU species. + @return A PyPIConGPU Momentum instance with the same filter, species, and direction. + @throw ValueError If species is unknown or unmapped to a PyPIConGPUSpecies. + """ + self.check() + + if self.species not in dict_species_picmi_to_pypicongpu.keys(): + raise ValueError(f"Species {self.species} is not known to Simulation") + + pypicongpu_species = dict_species_picmi_to_pypicongpu.get(self.species) + + if pypicongpu_species is None: + raise ValueError(f"Species {self.species} is not mapped to a PyPIConGPUSpecies.") + + return PyPIConGPUMomentum( + filter=self.filter, + species=pypicongpu_species, + direction=self.direction, + ) diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/momentum_density.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/momentum_density.py new file mode 100644 index 0000000000..8f64adca5b --- /dev/null +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/momentum_density.py @@ -0,0 +1,76 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .source_base import SourceBase +from ...pypicongpu.output.openpmd_source import MomentumDensity as PyPIConGPUMomentumDensity +from ...species import Species as PICMISpecies +import typeguard +import typing + + +@typeguard.typechecked +class MomentumDensity(SourceBase): + """ + Momentum density component data source for openPMD output + + Derives a scalar field of momentum density (in kg·m/s/m^3, direction x=0, y=1, z=2) + for a specified particle species, optionally filtered, in particle-in-cell simulations. Uses + position and momentum attributes, mapped to cells by the PIC code's spatial shape. + + @param species Particle species to calculate momentum density for (e.g., electrons, ions). + Must have position and momentum attributes. + @param filter Name of a filter to select particles. Default: "all". + @param direction Momentum density component direction (0=x, 1=y, 2=z). + """ + + def __init__(self, species: PICMISpecies, filter: str = "all", direction: int = 0): + self.species = species + self.filter = filter + self.direction = direction + self.check() + + def check(self) -> None: + """ + Validate the parameters. + + @throw ValueError If filter is not a string, species is not a PICMISpecies, or direction is invalid. + """ + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, PICMISpecies): + raise ValueError(f"Species must be a PICMISpecies, got {type(self.species)}") + if not isinstance(self.direction, int): + raise ValueError(f"Direction must be an integer, got {type(self.direction)}") + if self.direction not in [0, 1, 2]: + raise ValueError(f"Direction must be 0 (x), 1 (y), or 2 (z), got {self.direction}") + + def get_as_pypicongpu( + self, + dict_species_picmi_to_pypicongpu: dict[PICMISpecies, typing.Any], + ) -> PyPIConGPUMomentumDensity: + """ + Convert to a PyPIConGPU MomentumDensity source. + + @param dict_species_picmi_to_pypicongpu Mapping of PICMI to PyPIConGPU species. + @return A PyPIConGPU MomentumDensity instance with the same filter, species, and direction. + @throw ValueError If species is unknown or unmapped to a PyPIConGPUSpecies. + """ + self.check() + + if self.species not in dict_species_picmi_to_pypicongpu.keys(): + raise ValueError(f"Species {self.species} is not known to Simulation") + + pypicongpu_species = dict_species_picmi_to_pypicongpu.get(self.species) + + if pypicongpu_species is None: + raise ValueError(f"Species {self.species} is not mapped to a PyPIConGPUSpecies.") + + return PyPIConGPUMomentumDensity( + filter=self.filter, + species=pypicongpu_species, + direction=self.direction, + ) diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/weighted_velocity.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/weighted_velocity.py new file mode 100644 index 0000000000..4016e20edb --- /dev/null +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/weighted_velocity.py @@ -0,0 +1,77 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .source_base import SourceBase +from ...pypicongpu.output.openpmd_source import WeightedVelocity as PyPIConGPUWeightedVelocity +from ...species import Species as PICMISpecies +import typeguard +import typing + + +@typeguard.typechecked +class WeightedVelocity(SourceBase): + """ + Weighted velocity component data source for openPMD output + + Derives a scalar field of a weighted velocity component (in m/s, direction x=0, y=1, z=2) + for a specified particle species, optionally filtered, in particle-in-cell simulations. Uses + position, momentum, weighting, and mass attributes, mapped to cells by the PIC code's spatial + shape. Use with AveragedAttribute to calculate average velocity. + + @param species Particle species to calculate weighted velocity for (e.g., electrons, ions). + Must have position, momentum, weighting, and mass attributes. + @param filter Name of a filter to select particles. Default: "all". + @param direction Velocity component direction (0=x, 1=y, 2=z). + """ + + def __init__(self, species: PICMISpecies, filter: str = "all", direction: int = 0): + self.species = species + self.filter = filter + self.direction = direction + self.check() + + def check(self) -> None: + """ + Validate the parameters. + + @throw ValueError If filter is not a string, species is not a PICMISpecies, or direction is invalid. + """ + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, PICMISpecies): + raise ValueError(f"Species must be a PICMISpecies, got {type(self.species)}") + if not isinstance(self.direction, int): + raise ValueError(f"Direction must be an integer, got {type(self.direction)}") + if self.direction not in [0, 1, 2]: + raise ValueError(f"Direction must be 0 (x), 1 (y), or 2 (z), got {self.direction}") + + def get_as_pypicongpu( + self, + dict_species_picmi_to_pypicongpu: dict[PICMISpecies, typing.Any], + ) -> PyPIConGPUWeightedVelocity: + """ + Convert to a PyPIConGPU WeightedVelocity source. + + @param dict_species_picmi_to_pypicongpu Mapping of PICMI to PyPIConGPU species. + @return A PyPIConGPU WeightedVelocity instance with the same filter, species, and direction. + @throw ValueError If species is unknown or unmapped to a PyPIConGPUSpecies. + """ + self.check() + + if self.species not in dict_species_picmi_to_pypicongpu.keys(): + raise ValueError(f"Species {self.species} is not known to Simulation") + + pypicongpu_species = dict_species_picmi_to_pypicongpu.get(self.species) + + if pypicongpu_species is None: + raise ValueError(f"Species {self.species} is not mapped to a PyPIConGPUSpecies.") + + return PyPIConGPUWeightedVelocity( + filter=self.filter, + species=pypicongpu_species, + direction=self.direction, + ) From 251713915a2bb3aaf4a0da7ee32f599282b38fef Mon Sep 17 00:00:00 2001 From: mafshari64 Date: Wed, 7 May 2025 16:58:59 +0200 Subject: [PATCH 05/15] providing openpmd sources for pypicongpu --- .../openpmd_sources/source_base.py | 2 +- .../picongpu/pypicongpu/output/openpmd.py | 167 +++++++----------- .../pypicongpu/output/openpmd_source/auto.py | 40 ----- .../openpmd_source/bound_electron_density.py | 34 ---- .../output/openpmd_source/charge_density.py | 34 ---- .../__init__.py | 10 +- .../pypicongpu/output/openpmd_sources/auto.py | 30 ++++ .../openpmd_sources/bound_electron_density.py | 38 ++++ .../output/openpmd_sources/charge_density.py | 38 ++++ .../output/openpmd_sources/counter.py | 38 ++++ .../output/openpmd_sources/density.py | 38 ++++ .../openpmd_sources/derived_attributes.py | 30 ++++ .../output/openpmd_sources/energy.py | 38 ++++ .../output/openpmd_sources/energy_density.py | 38 ++++ .../openpmd_sources/energy_density_cutoff.py | 45 +++++ .../output/openpmd_sources/larmor_power.py | 38 ++++ .../output/openpmd_sources/macro_counter.py | 38 ++++ .../mid_current_density_component.py | 44 +++++ .../output/openpmd_sources/momentum.py | 44 +++++ .../openpmd_sources/momentum_density.py | 44 +++++ .../output/openpmd_sources/source_base.py | 29 +++ .../openpmd_sources/weighted_velocity.py | 44 +++++ 22 files changed, 682 insertions(+), 219 deletions(-) delete mode 100644 lib/python/picongpu/pypicongpu/output/openpmd_source/auto.py delete mode 100644 lib/python/picongpu/pypicongpu/output/openpmd_source/bound_electron_density.py delete mode 100644 lib/python/picongpu/pypicongpu/output/openpmd_source/charge_density.py rename lib/python/picongpu/pypicongpu/output/{openpmd_source => openpmd_sources}/__init__.py (83%) create mode 100644 lib/python/picongpu/pypicongpu/output/openpmd_sources/auto.py create mode 100644 lib/python/picongpu/pypicongpu/output/openpmd_sources/bound_electron_density.py create mode 100644 lib/python/picongpu/pypicongpu/output/openpmd_sources/charge_density.py create mode 100644 lib/python/picongpu/pypicongpu/output/openpmd_sources/counter.py create mode 100644 lib/python/picongpu/pypicongpu/output/openpmd_sources/density.py create mode 100644 lib/python/picongpu/pypicongpu/output/openpmd_sources/derived_attributes.py create mode 100644 lib/python/picongpu/pypicongpu/output/openpmd_sources/energy.py create mode 100644 lib/python/picongpu/pypicongpu/output/openpmd_sources/energy_density.py create mode 100644 lib/python/picongpu/pypicongpu/output/openpmd_sources/energy_density_cutoff.py create mode 100644 lib/python/picongpu/pypicongpu/output/openpmd_sources/larmor_power.py create mode 100644 lib/python/picongpu/pypicongpu/output/openpmd_sources/macro_counter.py create mode 100644 lib/python/picongpu/pypicongpu/output/openpmd_sources/mid_current_density_component.py create mode 100644 lib/python/picongpu/pypicongpu/output/openpmd_sources/momentum.py create mode 100644 lib/python/picongpu/pypicongpu/output/openpmd_sources/momentum_density.py create mode 100644 lib/python/picongpu/pypicongpu/output/openpmd_sources/source_base.py create mode 100644 lib/python/picongpu/pypicongpu/output/openpmd_sources/weighted_velocity.py diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/source_base.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/source_base.py index 029e0508a5..c0e05046fe 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/source_base.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/source_base.py @@ -1,6 +1,6 @@ """ This file is part of PIConGPU. -Copyright 2021-2025 PIConGPU contributors +Copyright 2025-2025 PIConGPU contributors Authors: Masoud Afshari License: GPLv3+ """ diff --git a/lib/python/picongpu/pypicongpu/output/openpmd.py b/lib/python/picongpu/pypicongpu/output/openpmd.py index ae2f36621c..375c470acd 100644 --- a/lib/python/picongpu/pypicongpu/output/openpmd.py +++ b/lib/python/picongpu/pypicongpu/output/openpmd.py @@ -1,129 +1,81 @@ -# This file is part of PIConGPU. -# Copyright 2021-2025 PIConGPU contributors -# Authors: Masoud Afshari -# License: GPLv3+ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" +from . import util from .timestepspec import TimeStepSpec -from .plugin import Plugin -from .. import util -from .openpmd_source import ( - ChargeDensity, - BoundElectronDensity, - Counter, - Density, - Energy, - EnergyDensity, - EnergyDensityCutoff, - LarmorPower, - MacroCounter, - MidCurrentDensityComponent, - Momentum, - MomentumDensity, - WeightedVelocity, -) +from .openpmd_sources.source_base import SourceBase import typeguard import typing +from typing import Optional, List, Literal, Dict, Union @typeguard.typechecked -class Source: - sources = util.build_typesafe_property( - typing.List[ - typing.Union[ - str, - ChargeDensity, - BoundElectronDensity, - Counter, - Density, - Energy, - EnergyDensity, - EnergyDensityCutoff, - LarmorPower, - MacroCounter, - MidCurrentDensityComponent, - Momentum, - MomentumDensity, - WeightedVelocity, - ] - ] - ) - - def __init__( - self, - sources: typing.List[ - typing.Union[ - str, - ChargeDensity, - BoundElectronDensity, - Counter, - Density, - Energy, - EnergyDensity, - EnergyDensityCutoff, - LarmorPower, - MacroCounter, - MidCurrentDensityComponent, - Momentum, - MomentumDensity, - WeightedVelocity, - ] - ], - ): - self.sources = sources - - def _get_serialized(self) -> typing.List[typing.Any]: - return [s._get_serialized() if not isinstance(s, str) else s for s in self.sources] - - -@typeguard.typechecked -class OpenPMD(Plugin): +class OpenPMD: period = util.build_typesafe_property(TimeStepSpec) - source = util.build_typesafe_property(typing.Optional[Source]) - range = util.build_typesafe_property(typing.Optional[str]) - file = util.build_typesafe_property(typing.Optional[str]) - ext = util.build_typesafe_property(typing.Optional[str]) - infix = util.build_typesafe_property(typing.Optional[str]) - json = util.build_typesafe_property(typing.Optional[typing.Union[str, typing.Dict]]) - json_restart = util.build_typesafe_property(typing.Optional[typing.Union[str, typing.Dict]]) - data_preparation_strategy = util.build_typesafe_property(typing.Optional[str]) - toml = util.build_typesafe_property(typing.Optional[str]) - particle_io_chunk_size = util.build_typesafe_property(typing.Optional[int]) - file_access = util.build_typesafe_property(typing.Optional[str]) - - _name = "openPMD" + source = util.build_typesafe_property(Optional[List[SourceBase]]) + range = util.build_typesafe_property(Optional[str]) + file = util.build_typesafe_property(Optional[str]) + ext = util.build_typesafe_property(Optional[Literal["bp", "h5", "sst"]]) + infix = util.build_typesafe_property(Optional[str]) + json = util.build_typesafe_property(Union[str, Dict, None]) + json_restart = util.build_typesafe_property(Union[str, Dict, None]) + data_preparation_strategy = util.build_typesafe_property( + Optional[Literal["doubleBuffer", "adios", "mappedMemory", "hdf5"]] + ) + toml = util.build_typesafe_property(Optional[str]) + particle_io_chunk_size = util.build_typesafe_property(Optional[int]) + file_access = util.build_typesafe_property(Optional[Literal["create", "append"]]) def __init__( self, period: TimeStepSpec, - source: typing.Optional[Source] = None, - range: typing.Optional[str] = None, - file: typing.Optional[str] = None, - ext: typing.Optional[str] = None, - infix: typing.Optional[str] = None, - json: typing.Optional[typing.Union[str, typing.Dict]] = None, - json_restart: typing.Optional[typing.Union[str, typing.Dict]] = None, - data_preparation_strategy: typing.Optional[str] = None, - toml: typing.Optional[str] = None, - particle_io_chunk_size: typing.Optional[int] = None, - file_access: typing.Optional[str] = None, + source: Optional[List[SourceBase]] = None, + range: Optional[str] = ":,:,:", + file: Optional[str] = None, + ext: Optional[Literal["bp", "h5", "sst"]] = "bp", + infix: Optional[str] = "NULL", + json: Optional[Union[str, Dict]] = None, + json_restart: Optional[Union[str, Dict]] = None, + data_preparation_strategy: Optional[Literal["doubleBuffer", "adios", "mappedMemory", "hdf5"]] = None, + toml: Optional[str] = None, + particle_io_chunk_size: Optional[int] = None, + file_access: Optional[Literal["create", "append"]] = "create", ): self.period = period - self.source = source or Source(["species_all", "fields_all"]) - self.range = range or ":,:,:" + self.source = source + self.range = range self.file = file - self.ext = ext or "bp" - self.infix = infix or "_%06T" - self.json = json or {} - self.json_restart = json_restart or {} - self.data_preparation_strategy = data_preparation_strategy or "doubleBuffer" + self.ext = ext + self.infix = infix + self.json = json if json is not None else {} + self.json_restart = json_restart if json_restart is not None else {} + self.data_preparation_strategy = data_preparation_strategy self.toml = toml self.particle_io_chunk_size = particle_io_chunk_size - self.file_access = file_access or "create" + self.file_access = file_access + self.check() + + def check(self) -> None: + # Validate parameters + if self.period is None: + raise ValueError("period is mandatory") + if self.particle_io_chunk_size is not None and self.particle_io_chunk_size < 1: + raise ValueError("particle_io_chunk_size (in MiB) must be positive") + if self.ext == "sst" and self.infix is not None and self.infix != "NULL": + raise ValueError("infix must be 'NULL' when ext is 'sst'") + if self.source is not None and not all(isinstance(s, SourceBase) for s in self.source): + raise ValueError("source must be a list of SourceBase objects") def _get_serialized(self) -> typing.Dict: - result = { - "period": self.period._get_serialized(), - "source": self.source._get_serialized(), + # Return serialized representation + self.check() + return { + "period": self.period.get_rendering_context(), + "source": [s._get_serialized() for s in self.source] if self.source is not None else None, "range": self.range, "file": self.file, "ext": self.ext, @@ -135,4 +87,3 @@ def _get_serialized(self) -> typing.Dict: "particle_io_chunk_size": self.particle_io_chunk_size, "file_access": self.file_access, } - return {k: v for k, v in result.items() if v is not None} diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_source/auto.py b/lib/python/picongpu/pypicongpu/output/openpmd_source/auto.py deleted file mode 100644 index 2257f1cea1..0000000000 --- a/lib/python/picongpu/pypicongpu/output/openpmd_source/auto.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -This file is part of PIConGPU. -Copyright 2021-2025 PIConGPU contributors -Authors: Masoud Afshari -License: GPLv3+ -""" - -import typeguard -import typing - - -@typeguard.typechecked -class Auto: - filter = property(lambda self: self._filter) - - def __init__(self, filter: typing.Optional[str] = None): - self._filter = filter - self.check() - - def check(self) -> None: - if self._filter is not None and not isinstance(self._filter, str): - raise ValueError(f"Filter must be a string or None, got {type(self._filter)}") - - def _get_serialized(self) -> typing.Dict: - return { - "source": [ - {"dataset": "species_all", "filter": self._filter}, - {"dataset": "fields_all", "filter": self._filter}, - ], - "range": ":,:,:", - "file": "simOutput", - "ext": "bp", - "infix": "NULL", - "json": {}, - "json_restart": {}, - "data_preparation_strategy": "doubleBuffer", - "toml": None, - "particle_io_chunk_size": None, - "file_access": "create", - } diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_source/bound_electron_density.py b/lib/python/picongpu/pypicongpu/output/openpmd_source/bound_electron_density.py deleted file mode 100644 index ef9d05adea..0000000000 --- a/lib/python/picongpu/pypicongpu/output/openpmd_source/bound_electron_density.py +++ /dev/null @@ -1,34 +0,0 @@ -""" -This file is part of PIConGPU. -Copyright 2021-2024 PIConGPU contributors -Authors: Masoud Afshari -License: GPLv3+ -""" - -from ...species import Species as PyPIConGPUSpecies -import typeguard -import typing - - -@typeguard.typechecked -class BoundElectronDensity: - filter = property(lambda self: self._filter) - species = property(lambda self: self._species) - - def __init__(self, filter: str, species: typing.List[PyPIConGPUSpecies]): - self._filter = filter - self._species = species - self.check() - - def check(self) -> None: - if not isinstance(self._filter, str): - raise ValueError(f"Filter must be a string, got {type(self._filter)}") - if not self._species or not all(isinstance(s, PyPIConGPUSpecies) for s in self._species): - raise ValueError("Species must be a non-empty list of PyPIConGPUSpecies") - - def _get_serialized(self) -> typing.Dict: - return { - "dataset": "boundElectronDensity", - "filter": self._filter, - "species": [s.get_rendering_context() for s in self._species], - } diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_source/charge_density.py b/lib/python/picongpu/pypicongpu/output/openpmd_source/charge_density.py deleted file mode 100644 index 2b804f6236..0000000000 --- a/lib/python/picongpu/pypicongpu/output/openpmd_source/charge_density.py +++ /dev/null @@ -1,34 +0,0 @@ -""" -This file is part of PIConGPU. -Copyright 2021-2024 PIConGPU contributors -Authors: Masoud Afshari -License: GPLv3+ -""" - -from ...species import Species as PyPIConGPUSpecies -import typeguard -import typing - - -@typeguard.typechecked -class ChargeDensity: - filter = property(lambda self: self._filter) - species = property(lambda self: self._species) - - def __init__(self, filter: str, species: typing.List[PyPIConGPUSpecies]): - self._filter = filter - self._species = species - self.check() - - def check(self) -> None: - if not isinstance(self._filter, str): - raise ValueError(f"Filter must be a string, got {type(self._filter)}") - if not self._species or not all(isinstance(s, PyPIConGPUSpecies) for s in self._species): - raise ValueError("Species must be a non-empty list of PyPIConGPUSpecies") - - def _get_serialized(self) -> typing.Dict: - return { - "dataset": "chargeDensity", - "filter": self._filter, - "species": [s.get_rendering_context() for s in self._species], - } diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_source/__init__.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/__init__.py similarity index 83% rename from lib/python/picongpu/pypicongpu/output/openpmd_source/__init__.py rename to lib/python/picongpu/pypicongpu/output/openpmd_sources/__init__.py index b32b469e93..8bad5040c4 100644 --- a/lib/python/picongpu/pypicongpu/output/openpmd_source/__init__.py +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/__init__.py @@ -1,7 +1,9 @@ -from .charge_density import ChargeDensity +from .auto import Auto from .bound_electron_density import BoundElectronDensity +from .charge_density import ChargeDensity from .counter import Counter from .density import Density +from .derived_attributes import DerivedAttributes from .energy import Energy from .energy_density import EnergyDensity from .energy_density_cutoff import EnergyDensityCutoff @@ -11,12 +13,15 @@ from .momentum import Momentum from .momentum_density import MomentumDensity from .weighted_velocity import WeightedVelocity +from .source_base import SourceBase __all__ = [ - "ChargeDensity", + "Auto", "BoundElectronDensity", + "ChargeDensity", "Counter", "Density", + "DerivedAttributes", "Energy", "EnergyDensity", "EnergyDensityCutoff", @@ -26,4 +31,5 @@ "Momentum", "MomentumDensity", "WeightedVelocity", + "SourceBase", ] diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/auto.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/auto.py new file mode 100644 index 0000000000..77e5fc1fd7 --- /dev/null +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/auto.py @@ -0,0 +1,30 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .... import util +from .source_base import SourceBase +import typeguard +import typing + + +@typeguard.typechecked +class Auto(SourceBase): + filter = util.build_typesafe_property(typing.Optional[str]) + + def __init__(self, filter: typing.Optional[str] = None): + self.filter = filter + self.check() + + def check(self) -> None: + # Validate filter parameter + if self.filter is not None and not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string or None, got {type(self.filter)}") + + def _get_serialized(self) -> typing.Dict: + # Return serialized representation + self.check() + return {"filter": self.filter} diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/bound_electron_density.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/bound_electron_density.py new file mode 100644 index 0000000000..18105d83f4 --- /dev/null +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/bound_electron_density.py @@ -0,0 +1,38 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .... import util +from ....species import Species +from .source_base import SourceBase +import typeguard +import typing + + +@typeguard.typechecked +class BoundElectronDensity(SourceBase): + species = util.build_typesafe_property(Species) + filter = util.build_typesafe_property(str) + + def __init__(self, species: Species, filter: str = "all"): + self.species = species + self.filter = filter + self.check() + + def check(self) -> None: + # Validate parameters + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, Species): + raise ValueError(f"Species must be a Species, got {type(self.species)}") + + def _get_serialized(self) -> typing.Dict: + # Return serialized representation + self.check() + return { + "species": self.species.get_rendering_context(), + "filter": self.filter, + } diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/charge_density.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/charge_density.py new file mode 100644 index 0000000000..df95402500 --- /dev/null +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/charge_density.py @@ -0,0 +1,38 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .... import util +from ....species import Species +from .source_base import SourceBase +import typeguard +import typing + + +@typeguard.typechecked +class ChargeDensity(SourceBase): + species = util.build_typesafe_property(Species) + filter = util.build_typesafe_property(str) + + def __init__(self, species: Species, filter: str = "all"): + self.species = species + self.filter = filter + self.check() + + def check(self) -> None: + # Validate parameters + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, Species): + raise ValueError(f"Species must be a Species, got {type(self.species)}") + + def _get_serialized(self) -> typing.Dict: + # Return serialized representation + self.check() + return { + "species": self.species.get_rendering_context(), + "filter": self.filter, + } diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/counter.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/counter.py new file mode 100644 index 0000000000..f52d9cec8a --- /dev/null +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/counter.py @@ -0,0 +1,38 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .... import util +from ....species import Species +from .source_base import SourceBase +import typeguard +import typing + + +@typeguard.typechecked +class Counter(SourceBase): + species = util.build_typesafe_property(Species) + filter = util.build_typesafe_property(str) + + def __init__(self, species: Species, filter: str = "all"): + self.species = species + self.filter = filter + self.check() + + def check(self) -> None: + # Validate parameters + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, Species): + raise ValueError(f"Species must be a Species, got {type(self.species)}") + + def _get_serialized(self) -> typing.Dict: + # Return serialized representation + self.check() + return { + "species": self.species.get_rendering_context(), + "filter": self.filter, + } diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/density.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/density.py new file mode 100644 index 0000000000..515feba243 --- /dev/null +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/density.py @@ -0,0 +1,38 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .... import util +from ....species import Species +from .source_base import SourceBase +import typeguard +import typing + + +@typeguard.typechecked +class Density(SourceBase): + species = util.build_typesafe_property(Species) + filter = util.build_typesafe_property(str) + + def __init__(self, species: Species, filter: str = "all"): + self.species = species + self.filter = filter + self.check() + + def check(self) -> None: + # Validate parameters + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, Species): + raise ValueError(f"Species must be a Species, got {type(self.species)}") + + def _get_serialized(self) -> typing.Dict: + # Return serialized representation + self.check() + return { + "species": self.species.get_rendering_context(), + "filter": self.filter, + } diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/derived_attributes.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/derived_attributes.py new file mode 100644 index 0000000000..812c269bb8 --- /dev/null +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/derived_attributes.py @@ -0,0 +1,30 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .... import util +from .source_base import SourceBase +import typeguard +import typing + + +@typeguard.typechecked +class DerivedAttributes(SourceBase): + filter = util.build_typesafe_property(typing.Optional[str]) + + def __init__(self, filter: typing.Optional[str] = None): + self.filter = filter + self.check() + + def check(self) -> None: + # Validate filter parameter + if self.filter is not None and not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string or None, got {type(self.filter)}") + + def _get_serialized(self) -> typing.Dict: + # Return serialized representation + self.check() + return {"filter": self.filter} diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/energy.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/energy.py new file mode 100644 index 0000000000..1b5dcd99c0 --- /dev/null +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/energy.py @@ -0,0 +1,38 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .... import util +from ....species import Species +from .source_base import SourceBase +import typeguard +import typing + + +@typeguard.typechecked +class Energy(SourceBase): + species = util.build_typesafe_property(Species) + filter = util.build_typesafe_property(str) + + def __init__(self, species: Species, filter: str = "all"): + self.species = species + self.filter = filter + self.check() + + def check(self) -> None: + # Validate parameters + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, Species): + raise ValueError(f"Species must be a Species, got {type(self.species)}") + + def _get_serialized(self) -> typing.Dict: + # Return serialized representation + self.check() + return { + "species": self.species.get_rendering_context(), + "filter": self.filter, + } diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/energy_density.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/energy_density.py new file mode 100644 index 0000000000..4f331d5f2a --- /dev/null +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/energy_density.py @@ -0,0 +1,38 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .... import util +from ....species import Species +from .source_base import SourceBase +import typeguard +import typing + + +@typeguard.typechecked +class EnergyDensity(SourceBase): + species = util.build_typesafe_property(Species) + filter = util.build_typesafe_property(str) + + def __init__(self, species: Species, filter: str = "all"): + self.species = species + self.filter = filter + self.check() + + def check(self) -> None: + # Validate parameters + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, Species): + raise ValueError(f"Species must be a Species, got {type(self.species)}") + + def _get_serialized(self) -> typing.Dict: + # Return serialized representation + self.check() + return { + "species": self.species.get_rendering_context(), + "filter": self.filter, + } diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/energy_density_cutoff.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/energy_density_cutoff.py new file mode 100644 index 0000000000..83528cbe44 --- /dev/null +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/energy_density_cutoff.py @@ -0,0 +1,45 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .... import util +from ....species import Species +from .source_base import SourceBase +import typeguard +import typing + + +@typeguard.typechecked +class EnergyDensityCutoff(SourceBase): + species = util.build_typesafe_property(Species) + filter = util.build_typesafe_property(str) + cutoff_max_energy = util.build_typesafe_property(typing.Optional[float]) + + def __init__(self, species: Species, filter: str = "all", cutoff_max_energy: typing.Optional[float] = None): + self.species = species + self.filter = filter + self.cutoff_max_energy = cutoff_max_energy + self.check() + + def check(self) -> None: + # Validate parameters + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, Species): + raise ValueError(f"Species must be a Species, got {type(self.species)}") + if self.cutoff_max_energy is not None and not isinstance(self.cutoff_max_energy, (int, float)): + raise ValueError(f"cutoff_max_energy must be a number or None, got {type(self.cutoff_max_energy)}") + if self.cutoff_max_energy is not None and self.cutoff_max_energy <= 0: + raise ValueError(f"cutoff_max_energy must be positive, got {self.cutoff_max_energy}") + + def _get_serialized(self) -> typing.Dict: + # Return serialized representation + self.check() + return { + "species": self.species.get_rendering_context(), + "filter": self.filter, + "cutoff_max_energy": self.cutoff_max_energy, + } diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/larmor_power.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/larmor_power.py new file mode 100644 index 0000000000..01d8dfe07a --- /dev/null +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/larmor_power.py @@ -0,0 +1,38 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .... import util +from ....species import Species +from .source_base import SourceBase +import typeguard +import typing + + +@typeguard.typechecked +class LarmorPower(SourceBase): + species = util.build_typesafe_property(Species) + filter = util.build_typesafe_property(str) + + def __init__(self, species: Species, filter: str = "all"): + self.species = species + self.filter = filter + self.check() + + def check(self) -> None: + # Validate parameters + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, Species): + raise ValueError(f"Species must be a Species, got {type(self.species)}") + + def _get_serialized(self) -> typing.Dict: + # Return serialized representation + self.check() + return { + "species": self.species.get_rendering_context(), + "filter": self.filter, + } diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/macro_counter.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/macro_counter.py new file mode 100644 index 0000000000..9071f24fdd --- /dev/null +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/macro_counter.py @@ -0,0 +1,38 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .... import util +from ....species import Species +from .source_base import SourceBase +import typeguard +import typing + + +@typeguard.typechecked +class MacroCounter(SourceBase): + species = util.build_typesafe_property(Species) + filter = util.build_typesafe_property(str) + + def __init__(self, species: Species, filter: str = "all"): + self.species = species + self.filter = filter + self.check() + + def check(self) -> None: + # Validate parameters + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, Species): + raise ValueError(f"Species must be a Species, got {type(self.species)}") + + def _get_serialized(self) -> typing.Dict: + # Return serialized representation + self.check() + return { + "species": self.species.get_rendering_context(), + "filter": self.filter, + } diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/mid_current_density_component.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/mid_current_density_component.py new file mode 100644 index 0000000000..39bfabc1aa --- /dev/null +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/mid_current_density_component.py @@ -0,0 +1,44 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .... import util +from ....species import Species +from .source_base import SourceBase +import typeguard +import typing +from typing import Literal + + +@typeguard.typechecked +class MidCurrentDensityComponent(SourceBase): + species = util.build_typesafe_property(Species) + filter = util.build_typesafe_property(str) + direction = util.build_typesafe_property(Literal["x", "y", "z"]) + + def __init__(self, species: Species, filter: str = "all", direction: Literal["x", "y", "z"] = "x"): + self.species = species + self.filter = filter + self.direction = direction + self.check() + + def check(self) -> None: + # Validate parameters + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, Species): + raise ValueError(f"Species must be a Species, got {type(self.species)}") + if self.direction not in ["x", "y", "z"]: + raise ValueError(f"Direction must be 'x', 'y', or 'z', got {self.direction}") + + def _get_serialized(self) -> typing.Dict: + # Return serialized representation + self.check() + return { + "species": self.species.get_rendering_context(), + "filter": self.filter, + "direction": self.direction, + } diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/momentum.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/momentum.py new file mode 100644 index 0000000000..3fd64aba12 --- /dev/null +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/momentum.py @@ -0,0 +1,44 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .... import util +from ....species import Species +from .source_base import SourceBase +import typeguard +import typing +from typing import Literal + + +@typeguard.typechecked +class Momentum(SourceBase): + species = util.build_typesafe_property(Species) + filter = util.build_typesafe_property(str) + direction = util.build_typesafe_property(Literal["x", "y", "z"]) + + def __init__(self, species: Species, filter: str = "all", direction: Literal["x", "y", "z"] = "x"): + self.species = species + self.filter = filter + self.direction = direction + self.check() + + def check(self) -> None: + # Validate parameters + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, Species): + raise ValueError(f"Species must be a Species, got {type(self.species)}") + if self.direction not in ["x", "y", "z"]: + raise ValueError(f"Direction must be 'x', 'y', or 'z', got {self.direction}") + + def _get_serialized(self) -> typing.Dict: + # Return serialized representation + self.check() + return { + "species": self.species.get_rendering_context(), + "filter": self.filter, + "direction": self.direction, + } diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/momentum_density.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/momentum_density.py new file mode 100644 index 0000000000..b57dc0ccb1 --- /dev/null +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/momentum_density.py @@ -0,0 +1,44 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .... import util +from ....species import Species +from .source_base import SourceBase +import typeguard +import typing +from typing import Literal + + +@typeguard.typechecked +class MomentumDensity(SourceBase): + species = util.build_typesafe_property(Species) + filter = util.build_typesafe_property(str) + direction = util.build_typesafe_property(Literal["x", "y", "z"]) + + def __init__(self, species: Species, filter: str = "all", direction: Literal["x", "y", "z"] = "x"): + self.species = species + self.filter = filter + self.direction = direction + self.check() + + def check(self) -> None: + # Validate parameters + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, Species): + raise ValueError(f"Species must be a Species, got {type(self.species)}") + if self.direction not in ["x", "y", "z"]: + raise ValueError(f"Direction must be 'x', 'y', or 'z', got {self.direction}") + + def _get_serialized(self) -> typing.Dict: + # Return serialized representation + self.check() + return { + "species": self.species.get_rendering_context(), + "filter": self.filter, + "direction": self.direction, + } diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/source_base.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/source_base.py new file mode 100644 index 0000000000..8a7df61288 --- /dev/null +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/source_base.py @@ -0,0 +1,29 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from abc import ABCMeta, abstractmethod +import typeguard +import typing + + +@typeguard.typechecked +class SourceBase(metaclass=ABCMeta): + @property + @abstractmethod + def filter(self) -> typing.Optional[str]: + # Filter name for particle selection, None if no filter + pass + + @abstractmethod + def check(self) -> None: + # Validate data source parameters + pass + + @abstractmethod + def _get_serialized(self) -> typing.Dict: + # Return serialized representation for rendering + pass diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/weighted_velocity.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/weighted_velocity.py new file mode 100644 index 0000000000..23ead8c1c6 --- /dev/null +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/weighted_velocity.py @@ -0,0 +1,44 @@ +""" +This file is part of PIConGPU. +Copyright 2025-2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from .... import util +from ....species import Species +from .source_base import SourceBase +import typeguard +import typing +from typing import Literal + + +@typeguard.typechecked +class WeightedVelocity(SourceBase): + species = util.build_typesafe_property(Species) + filter = util.build_typesafe_property(str) + direction = util.build_typesafe_property(Literal["x", "y", "z"]) + + def __init__(self, species: Species, filter: str = "all", direction: Literal["x", "y", "z"] = "x"): + self.species = species + self.filter = filter + self.direction = direction + self.check() + + def check(self) -> None: + # Validate parameters + if not isinstance(self.filter, str): + raise ValueError(f"Filter must be a string, got {type(self.filter)}") + if not isinstance(self.species, Species): + raise ValueError(f"Species must be a Species, got {type(self.species)}") + if self.direction not in ["x", "y", "z"]: + raise ValueError(f"Direction must be 'x', 'y', or 'z', got {self.direction}") + + def _get_serialized(self) -> typing.Dict: + # Return serialized representation + self.check() + return { + "species": self.species.get_rendering_context(), + "filter": self.filter, + "direction": self.direction, + } From 584e3a1121e0234af5dc7a36749fc372d07a1730 Mon Sep 17 00:00:00 2001 From: mafshari64 Date: Thu, 8 May 2025 18:08:18 +0200 Subject: [PATCH 06/15] adding schema for openpmd plugin --- .../picongpu/picmi/diagnostics/__init__.py | 9 +- lib/python/picongpu/pypicongpu/__init__.py | 13 +- .../picongpu/pypicongpu/output/__init__.py | 4 + .../schema/output/openpmd.OpenPMD.json | 134 ++++++++++++++++++ .../output/openpmd_sources/auto.Auto.json | 15 ++ ...electron_density.BoundElectronDensity.json | 21 +++ .../charge_density.ChargeDensity.json | 21 +++ .../openpmd_sources/counter.Counter.json | 21 +++ .../openpmd_sources/density.Density.json | 21 +++ .../derived_attributes.DerivedAttributes.json | 15 ++ .../output/openpmd_sources/energy.Energy.json | 21 +++ .../energy_density.EnergyDensity.json | 21 +++ ...gy_density_cutoff.EnergyDensityCutoff.json | 29 ++++ .../larmor_power.LarmorPower.json | 21 +++ .../macro_counter.MacroCounter.json | 21 +++ ..._component.MidCurrentDensityComponent.json | 32 +++++ .../openpmd_sources/momentum.Momentum.json | 32 +++++ .../momentum_density.MomentumDensity.json | 32 +++++ .../source_base.SourceBase.json | 62 ++++++++ .../weighted_velocity.WeightedVelocity.json | 32 +++++ .../schema/output/plugin.Plugin.json | 9 +- 21 files changed, 569 insertions(+), 17 deletions(-) create mode 100644 share/picongpu/pypicongpu/schema/output/openpmd.OpenPMD.json create mode 100644 share/picongpu/pypicongpu/schema/output/openpmd_sources/auto.Auto.json create mode 100644 share/picongpu/pypicongpu/schema/output/openpmd_sources/bound_electron_density.BoundElectronDensity.json create mode 100644 share/picongpu/pypicongpu/schema/output/openpmd_sources/charge_density.ChargeDensity.json create mode 100644 share/picongpu/pypicongpu/schema/output/openpmd_sources/counter.Counter.json create mode 100644 share/picongpu/pypicongpu/schema/output/openpmd_sources/density.Density.json create mode 100644 share/picongpu/pypicongpu/schema/output/openpmd_sources/derived_attributes.DerivedAttributes.json create mode 100644 share/picongpu/pypicongpu/schema/output/openpmd_sources/energy.Energy.json create mode 100644 share/picongpu/pypicongpu/schema/output/openpmd_sources/energy_density.EnergyDensity.json create mode 100644 share/picongpu/pypicongpu/schema/output/openpmd_sources/energy_density_cutoff.EnergyDensityCutoff.json create mode 100644 share/picongpu/pypicongpu/schema/output/openpmd_sources/larmor_power.LarmorPower.json create mode 100644 share/picongpu/pypicongpu/schema/output/openpmd_sources/macro_counter.MacroCounter.json create mode 100644 share/picongpu/pypicongpu/schema/output/openpmd_sources/mid_current_density_component.MidCurrentDensityComponent.json create mode 100644 share/picongpu/pypicongpu/schema/output/openpmd_sources/momentum.Momentum.json create mode 100644 share/picongpu/pypicongpu/schema/output/openpmd_sources/momentum_density.MomentumDensity.json create mode 100644 share/picongpu/pypicongpu/schema/output/openpmd_sources/source_base.SourceBase.json create mode 100644 share/picongpu/pypicongpu/schema/output/openpmd_sources/weighted_velocity.WeightedVelocity.json diff --git a/lib/python/picongpu/picmi/diagnostics/__init__.py b/lib/python/picongpu/picmi/diagnostics/__init__.py index 91584d1334..8b6689c75c 100644 --- a/lib/python/picongpu/picmi/diagnostics/__init__.py +++ b/lib/python/picongpu/picmi/diagnostics/__init__.py @@ -1,10 +1,3 @@ -""" -This file is part of PIConGPU. -Copyright 2024 PIConGPU contributors -Authors: Julian Lenz, Masoud Afshari -License: GPLv3+ -""" - from .auto import Auto from .phase_space import PhaseSpace from .energy_histogram import EnergyHistogram @@ -13,6 +6,7 @@ from .timestepspec import TimeStepSpec from .checkpoint import Checkpoint from .openpmd import OpenPMD +from .openpmd_sources.source_base import SourceBase __all__ = [ "Auto", @@ -23,4 +17,5 @@ "TimeStepSpec", "Checkpoint", "OpenPMD", + "SourceBase", ] diff --git a/lib/python/picongpu/pypicongpu/__init__.py b/lib/python/picongpu/pypicongpu/__init__.py index 237dfb0293..dc7dcd7c71 100644 --- a/lib/python/picongpu/pypicongpu/__init__.py +++ b/lib/python/picongpu/pypicongpu/__init__.py @@ -1,7 +1,3 @@ -""" -internal representation of params to generate PIConGPU input files -""" - from .simulation import Simulation from .runner import Runner from .output.phase_space import PhaseSpace @@ -9,6 +5,8 @@ from .output.macro_particle_count import MacroParticleCount from .output.png import Png from .output.checkpoint import Checkpoint +from .output.openpmd import OpenPMD +from .output.openpmd_sources.source_base import SourceBase from . import laser from . import grid @@ -35,9 +33,6 @@ "MacroParticleCount", "Png", "Checkpoint", + "OpenPMD", + "SourceBase", ] - -# note: put down here b/c linter complains if imports are not at top -import sys - -assert sys.version_info.major > 3 or sys.version_info.minor >= 9, "Python 3.9 is required for PIConGPU" diff --git a/lib/python/picongpu/pypicongpu/output/__init__.py b/lib/python/picongpu/pypicongpu/output/__init__.py index 9636a64119..8b6689c75c 100644 --- a/lib/python/picongpu/pypicongpu/output/__init__.py +++ b/lib/python/picongpu/pypicongpu/output/__init__.py @@ -5,6 +5,8 @@ from .png import Png from .timestepspec import TimeStepSpec from .checkpoint import Checkpoint +from .openpmd import OpenPMD +from .openpmd_sources.source_base import SourceBase __all__ = [ "Auto", @@ -14,4 +16,6 @@ "Png", "TimeStepSpec", "Checkpoint", + "OpenPMD", + "SourceBase", ] diff --git a/share/picongpu/pypicongpu/schema/output/openpmd.OpenPMD.json b/share/picongpu/pypicongpu/schema/output/openpmd.OpenPMD.json new file mode 100644 index 0000000000..6825c342d7 --- /dev/null +++ b/share/picongpu/pypicongpu/schema/output/openpmd.OpenPMD.json @@ -0,0 +1,134 @@ +{ + "$id": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd.OpenPMD", + "type": "object", + "description": "openPMD output configuration for PIConGPU, writing simulation data to disk using the openPMD standard.", + "unevaluatedProperties": false, + "required": [ + "period" + ], + "properties": { + "period": { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.timestepspec.TimeStepSpec", + "description": "Specification of time steps for data output." + }, + "source": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.source_base.SourceBase" + }, + "description": "List of data source objects to include in the dump (e.g., ChargeDensity, Auto)." + }, + "range": { + "type": [ + "string", + "null" + ], + "description": "Contiguous range of cells to dump fields, in 'begin:end,begin:end,begin:end' format." + }, + "file": { + "type": [ + "string", + "null" + ], + "description": "Relative or absolute file path prefix for openPMD output files." + }, + "ext": { + "type": [ + "string", + "null" + ], + "enum": [ + "bp", + "h5", + "sst", + null + ], + "description": "File extension controlling the openPMD backend (bp=ADIOS2, h5=HDF5, sst=ADIOS2/SST)." + }, + "infix": { + "type": [ + "string", + "null" + ], + "description": "Filename infix for iteration layout (e.g., '_%06T'), 'NULL' for group-based layout." + }, + "json": { + "type": [ + "string", + "object", + "null" + ], + "description": "openPMD backend configuration as JSON string or dictionary." + }, + "json_restart": { + "type": [ + "string", + "object", + "null" + ], + "description": "Backend-specific parameters for restarting, as JSON string or dictionary." + }, + "data_preparation_strategy": { + "type": [ + "string", + "null" + ], + "enum": [ + "doubleBuffer", + "adios", + "mappedMemory", + "hdf5", + null + ], + "description": "Strategy for particle data preparation (e.g., doubleBuffer, adios)." + }, + "toml": { + "type": [ + "string", + "null" + ], + "description": "Path to a TOML file for openPMD configuration." + }, + "particle_io_chunk_size": { + "type": [ + "integer", + "null" + ], + "minimum": 1, + "description": "Size of particle data chunks used in writing (in MiB)." + }, + "file_access": { + "type": [ + "string", + "null" + ], + "enum": [ + "create", + "append", + null + ], + "description": "File access mode for writing (create=new files, append=checkpoint-restart)." + } + }, + "allOf": [ + { + "if": { + "properties": { + "ext": { + "const": "sst" + } + } + }, + "then": { + "properties": { + "infix": { + "const": "NULL" + } + } + } + } + ] +} diff --git a/share/picongpu/pypicongpu/schema/output/openpmd_sources/auto.Auto.json b/share/picongpu/pypicongpu/schema/output/openpmd_sources/auto.Auto.json new file mode 100644 index 0000000000..a22d9db0f9 --- /dev/null +++ b/share/picongpu/pypicongpu/schema/output/openpmd_sources/auto.Auto.json @@ -0,0 +1,15 @@ +{ + "$id": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.auto.Auto", + "type": "object", + "description": "Auto data source for openPMD output in PIConGPU, enabling automatic derived attributes with an optional filter.", + "unevaluatedProperties": false, + "properties": { + "filter": { + "type": [ + "string", + "null" + ], + "description": "Name of a filter to select particles contributing to the data source." + } + } +} diff --git a/share/picongpu/pypicongpu/schema/output/openpmd_sources/bound_electron_density.BoundElectronDensity.json b/share/picongpu/pypicongpu/schema/output/openpmd_sources/bound_electron_density.BoundElectronDensity.json new file mode 100644 index 0000000000..16c29b7d8b --- /dev/null +++ b/share/picongpu/pypicongpu/schema/output/openpmd_sources/bound_electron_density.BoundElectronDensity.json @@ -0,0 +1,21 @@ +{ + "$id": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.bound_electron_density.BoundElectronDensity", + "type": "object", + "description": "Bound electron density data source for openPMD output in PIConGPU, calculating density for bound electrons of a specified species.", + "unevaluatedProperties": false, + "required": [ + "species", + "filter" + ], + "properties": { + "species": { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.species.species.Species", + "description": "Particle species to calculate bound electron density for." + }, + "filter": { + "type": "string", + "description": "Name of a filter to select particles contributing to the data source.", + "default": "all" + } + } +} diff --git a/share/picongpu/pypicongpu/schema/output/openpmd_sources/charge_density.ChargeDensity.json b/share/picongpu/pypicongpu/schema/output/openpmd_sources/charge_density.ChargeDensity.json new file mode 100644 index 0000000000..3b4eb1ff44 --- /dev/null +++ b/share/picongpu/pypicongpu/schema/output/openpmd_sources/charge_density.ChargeDensity.json @@ -0,0 +1,21 @@ +{ + "$id": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.charge_density.ChargeDensity", + "type": "object", + "description": "Charge density data source for openPMD output in PIConGPU, calculating charge density for a specified species.", + "unevaluatedProperties": false, + "required": [ + "species", + "filter" + ], + "properties": { + "species": { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.species.species.Species", + "description": "Particle species to calculate charge density for." + }, + "filter": { + "type": "string", + "description": "Name of a filter to select particles contributing to the data source.", + "default": "all" + } + } +} diff --git a/share/picongpu/pypicongpu/schema/output/openpmd_sources/counter.Counter.json b/share/picongpu/pypicongpu/schema/output/openpmd_sources/counter.Counter.json new file mode 100644 index 0000000000..d25e572767 --- /dev/null +++ b/share/picongpu/pypicongpu/schema/output/openpmd_sources/counter.Counter.json @@ -0,0 +1,21 @@ +{ + "$id": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.counter.Counter", + "type": "object", + "description": "Counter data source for openPMD output in PIConGPU, counting particles of a specified species.", + "unevaluatedProperties": false, + "required": [ + "species", + "filter" + ], + "properties": { + "species": { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.species.species.Species", + "description": "Particle species to count." + }, + "filter": { + "type": "string", + "description": "Name of a filter to select particles contributing to the data source.", + "default": "all" + } + } +} diff --git a/share/picongpu/pypicongpu/schema/output/openpmd_sources/density.Density.json b/share/picongpu/pypicongpu/schema/output/openpmd_sources/density.Density.json new file mode 100644 index 0000000000..f4ac000d70 --- /dev/null +++ b/share/picongpu/pypicongpu/schema/output/openpmd_sources/density.Density.json @@ -0,0 +1,21 @@ +{ + "$id": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.density.Density", + "type": "object", + "description": "Density data source for openPMD output in PIConGPU, calculating particle density for a specified species.", + "unevaluatedProperties": false, + "required": [ + "species", + "filter" + ], + "properties": { + "species": { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.species.species.Species", + "description": "Particle species to calculate density for." + }, + "filter": { + "type": "string", + "description": "Name of a filter to select particles contributing to the data source.", + "default": "all" + } + } +} diff --git a/share/picongpu/pypicongpu/schema/output/openpmd_sources/derived_attributes.DerivedAttributes.json b/share/picongpu/pypicongpu/schema/output/openpmd_sources/derived_attributes.DerivedAttributes.json new file mode 100644 index 0000000000..096f83fcc0 --- /dev/null +++ b/share/picongpu/pypicongpu/schema/output/openpmd_sources/derived_attributes.DerivedAttributes.json @@ -0,0 +1,15 @@ +{ + "$id": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.derived_attributes.DerivedAttributes", + "type": "object", + "description": "Derived attributes data source for openPMD output in PIConGPU, enabling custom derived attributes with an optional filter.", + "unevaluatedProperties": false, + "properties": { + "filter": { + "type": [ + "string", + "null" + ], + "description": "Name of a filter to select particles contributing to the data source." + } + } +} diff --git a/share/picongpu/pypicongpu/schema/output/openpmd_sources/energy.Energy.json b/share/picongpu/pypicongpu/schema/output/openpmd_sources/energy.Energy.json new file mode 100644 index 0000000000..40effc992a --- /dev/null +++ b/share/picongpu/pypicongpu/schema/output/openpmd_sources/energy.Energy.json @@ -0,0 +1,21 @@ +{ + "$id": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.energy.Energy", + "type": "object", + "description": "Energy data source for openPMD output in PIConGPU, calculating particle energy for a specified species.", + "unevaluatedProperties": false, + "required": [ + "species", + "filter" + ], + "properties": { + "species": { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.species.species.Species", + "description": "Particle species to calculate energy for." + }, + "filter": { + "type": "string", + "description": "Name of a filter to select particles contributing to the data source.", + "default": "all" + } + } +} diff --git a/share/picongpu/pypicongpu/schema/output/openpmd_sources/energy_density.EnergyDensity.json b/share/picongpu/pypicongpu/schema/output/openpmd_sources/energy_density.EnergyDensity.json new file mode 100644 index 0000000000..1786a3e80f --- /dev/null +++ b/share/picongpu/pypicongpu/schema/output/openpmd_sources/energy_density.EnergyDensity.json @@ -0,0 +1,21 @@ +{ + "$id": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.energy_density.EnergyDensity", + "type": "object", + "description": "Energy density data source for openPMD output in PIConGPU, calculating energy density for a specified species.", + "unevaluatedProperties": false, + "required": [ + "species", + "filter" + ], + "properties": { + "species": { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.species.species.Species", + "description": "Particle species to calculate energy density for." + }, + "filter": { + "type": "string", + "description": "Name of a filter to select particles contributing to the data source.", + "default": "all" + } + } +} diff --git a/share/picongpu/pypicongpu/schema/output/openpmd_sources/energy_density_cutoff.EnergyDensityCutoff.json b/share/picongpu/pypicongpu/schema/output/openpmd_sources/energy_density_cutoff.EnergyDensityCutoff.json new file mode 100644 index 0000000000..e146915e6c --- /dev/null +++ b/share/picongpu/pypicongpu/schema/output/openpmd_sources/energy_density_cutoff.EnergyDensityCutoff.json @@ -0,0 +1,29 @@ +{ + "$id": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.energy_density_cutoff.EnergyDensityCutoff", + "type": "object", + "description": "Energy density cutoff data source for openPMD output in PIConGPU, calculating energy density for a specified species with an optional maximum energy cutoff.", + "unevaluatedProperties": false, + "required": [ + "species", + "filter" + ], + "properties": { + "species": { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.species.species.Species", + "description": "Particle species to calculate energy density for." + }, + "filter": { + "type": "string", + "description": "Name of a filter to select particles contributing to the data source.", + "default": "all" + }, + "cutoff_max_energy": { + "type": [ + "number", + "null" + ], + "exclusiveMinimum": 0, + "description": "Maximum energy cutoff for particles contributing to the energy density." + } + } +} diff --git a/share/picongpu/pypicongpu/schema/output/openpmd_sources/larmor_power.LarmorPower.json b/share/picongpu/pypicongpu/schema/output/openpmd_sources/larmor_power.LarmorPower.json new file mode 100644 index 0000000000..114387edc5 --- /dev/null +++ b/share/picongpu/pypicongpu/schema/output/openpmd_sources/larmor_power.LarmorPower.json @@ -0,0 +1,21 @@ +{ + "$id": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.larmor_power.LarmorPower", + "type": "object", + "description": "Larmor power data source for openPMD output in PIConGPU, calculating Larmor radiation power for a specified species.", + "unevaluatedProperties": false, + "required": [ + "species", + "filter" + ], + "properties": { + "species": { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.species.species.Species", + "description": "Particle species to calculate Larmor power for." + }, + "filter": { + "type": "string", + "description": "Name of a filter to select particles contributing to the data source.", + "default": "all" + } + } +} diff --git a/share/picongpu/pypicongpu/schema/output/openpmd_sources/macro_counter.MacroCounter.json b/share/picongpu/pypicongpu/schema/output/openpmd_sources/macro_counter.MacroCounter.json new file mode 100644 index 0000000000..23d6456681 --- /dev/null +++ b/share/picongpu/pypicongpu/schema/output/openpmd_sources/macro_counter.MacroCounter.json @@ -0,0 +1,21 @@ +{ + "$id": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.macro_counter.MacroCounter", + "type": "object", + "description": "Macro counter data source for openPMD output in PIConGPU, counting macro-particles of a specified species.", + "unevaluatedProperties": false, + "required": [ + "species", + "filter" + ], + "properties": { + "species": { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.species.species.Species", + "description": "Particle species to count macro-particles for." + }, + "filter": { + "type": "string", + "description": "Name of a filter to select particles contributing to the data source.", + "default": "all" + } + } +} diff --git a/share/picongpu/pypicongpu/schema/output/openpmd_sources/mid_current_density_component.MidCurrentDensityComponent.json b/share/picongpu/pypicongpu/schema/output/openpmd_sources/mid_current_density_component.MidCurrentDensityComponent.json new file mode 100644 index 0000000000..423a839f9e --- /dev/null +++ b/share/picongpu/pypicongpu/schema/output/openpmd_sources/mid_current_density_component.MidCurrentDensityComponent.json @@ -0,0 +1,32 @@ +{ + "$id": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.mid_current_density_component.MidCurrentDensityComponent", + "type": "object", + "description": "Mid-current density component data source for openPMD output in PIConGPU, calculating current density along a specified direction for a species.", + "unevaluatedProperties": false, + "required": [ + "species", + "filter", + "direction" + ], + "properties": { + "species": { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.species.species.Species", + "description": "Particle species to calculate current density for." + }, + "filter": { + "type": "string", + "description": "Name of a filter to select particles contributing to the data source.", + "default": "all" + }, + "direction": { + "type": "string", + "enum": [ + "x", + "y", + "z" + ], + "description": "Direction (x, y, or z) for the current density component.", + "default": "x" + } + } +} diff --git a/share/picongpu/pypicongpu/schema/output/openpmd_sources/momentum.Momentum.json b/share/picongpu/pypicongpu/schema/output/openpmd_sources/momentum.Momentum.json new file mode 100644 index 0000000000..e422981f0d --- /dev/null +++ b/share/picongpu/pypicongpu/schema/output/openpmd_sources/momentum.Momentum.json @@ -0,0 +1,32 @@ +{ + "$id": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.momentum.Momentum", + "type": "object", + "description": "Momentum data source for openPMD output in PIConGPU, calculating momentum along a specified direction for a species.", + "unevaluatedProperties": false, + "required": [ + "species", + "filter", + "direction" + ], + "properties": { + "species": { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.species.species.Species", + "description": "Particle species to calculate momentum for." + }, + "filter": { + "type": "string", + "description": "Name of a filter to select particles contributing to the data source.", + "default": "all" + }, + "direction": { + "type": "string", + "enum": [ + "x", + "y", + "z" + ], + "description": "Direction (x, y, or z) for the momentum component.", + "default": "x" + } + } +} diff --git a/share/picongpu/pypicongpu/schema/output/openpmd_sources/momentum_density.MomentumDensity.json b/share/picongpu/pypicongpu/schema/output/openpmd_sources/momentum_density.MomentumDensity.json new file mode 100644 index 0000000000..924cdfb024 --- /dev/null +++ b/share/picongpu/pypicongpu/schema/output/openpmd_sources/momentum_density.MomentumDensity.json @@ -0,0 +1,32 @@ +{ + "$id": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.momentum_density.MomentumDensity", + "type": "object", + "description": "Momentum density data source for openPMD output in PIConGPU, calculating momentum density along a specified direction for a species.", + "unevaluatedProperties": false, + "required": [ + "species", + "filter", + "direction" + ], + "properties": { + "species": { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.species.species.Species", + "description": "Particle species to calculate momentum density for." + }, + "filter": { + "type": "string", + "description": "Name of a filter to select particles contributing to the data source.", + "default": "all" + }, + "direction": { + "type": "string", + "enum": [ + "x", + "y", + "z" + ], + "description": "Direction (x, y, or z) for the momentum density component.", + "default": "x" + } + } +} diff --git a/share/picongpu/pypicongpu/schema/output/openpmd_sources/source_base.SourceBase.json b/share/picongpu/pypicongpu/schema/output/openpmd_sources/source_base.SourceBase.json new file mode 100644 index 0000000000..5caa7fccb3 --- /dev/null +++ b/share/picongpu/pypicongpu/schema/output/openpmd_sources/source_base.SourceBase.json @@ -0,0 +1,62 @@ +{ + "$id": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.source_base.SourceBase", + "type": "object", + "description": "Abstract base class for openPMD data sources in PIConGPU, defining common properties for derived attributes.", + "unevaluatedProperties": false, + "properties": { + "filter": { + "type": [ + "string", + "null" + ], + "description": "Name of a filter to select particles contributing to the data source." + } + }, + "anyOf": [ + { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.auto.Auto" + }, + { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.bound_electron_density.BoundElectronDensity" + }, + { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.charge_density.ChargeDensity" + }, + { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.counter.Counter" + }, + { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.density.Density" + }, + { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.derived_attributes.DerivedAttributes" + }, + { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.energy.Energy" + }, + { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.energy_density.EnergyDensity" + }, + { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.energy_density_cutoff.EnergyDensityCutoff" + }, + { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.larmor_power.LarmorPower" + }, + { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.macro_counter.MacroCounter" + }, + { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.mid_current_density_component.MidCurrentDensityComponent" + }, + { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.momentum.Momentum" + }, + { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.momentum_density.MomentumDensity" + }, + { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.weighted_velocity.WeightedVelocity" + } + ] +} diff --git a/share/picongpu/pypicongpu/schema/output/openpmd_sources/weighted_velocity.WeightedVelocity.json b/share/picongpu/pypicongpu/schema/output/openpmd_sources/weighted_velocity.WeightedVelocity.json new file mode 100644 index 0000000000..924dfe9606 --- /dev/null +++ b/share/picongpu/pypicongpu/schema/output/openpmd_sources/weighted_velocity.WeightedVelocity.json @@ -0,0 +1,32 @@ +{ + "$id": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd_sources.weighted_velocity.WeightedVelocity", + "type": "object", + "description": "Weighted velocity data source for openPMD output in PIConGPU, calculating weighted velocity along a specified direction for a species.", + "unevaluatedProperties": false, + "required": [ + "species", + "filter", + "direction" + ], + "properties": { + "species": { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.species.species.Species", + "description": "Particle species to calculate weighted velocity for." + }, + "filter": { + "type": "string", + "description": "Name of a filter to select particles contributing to the data source.", + "default": "all" + }, + "direction": { + "type": "string", + "enum": [ + "x", + "y", + "z" + ], + "description": "Direction (x, y, or z) for the weighted velocity component.", + "default": "x" + } + } +} diff --git a/share/picongpu/pypicongpu/schema/output/plugin.Plugin.json b/share/picongpu/pypicongpu/schema/output/plugin.Plugin.json index eada288928..5f622ab970 100644 --- a/share/picongpu/pypicongpu/schema/output/plugin.Plugin.json +++ b/share/picongpu/pypicongpu/schema/output/plugin.Plugin.json @@ -17,7 +17,8 @@ "energyhistogram", "macroparticlecount", "png", - "checkpoint" + "checkpoint", + "openpmd" ], "unevaluatedProperties": false, "properties": { @@ -38,6 +39,9 @@ }, "checkpoint": { "type": "boolean" + }, + "openpmd": { + "type": "boolean" } } }, @@ -61,6 +65,9 @@ }, { "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.checkpoint.Checkpoint" + }, + { + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.openpmd.OpenPMD" } ] } From 4e5b3f3f9c254cd1104ac886a8848b1f62d329be Mon Sep 17 00:00:00 2001 From: mafshari64 Date: Fri, 9 May 2025 18:16:18 +0200 Subject: [PATCH 07/15] updating openpmd files --- .../picongpu/picmi/diagnostics/openpmd.py | 10 +++---- .../picmi/diagnostics/openpmd_sources/auto.py | 5 ++-- .../openpmd_sources/bound_electron_density.py | 2 +- .../openpmd_sources/charge_density.py | 2 +- .../diagnostics/openpmd_sources/counter.py | 2 +- .../diagnostics/openpmd_sources/density.py | 2 +- .../openpmd_sources/derived_attributes.py | 2 +- .../diagnostics/openpmd_sources/energy.py | 2 +- .../openpmd_sources/energy_density.py | 2 +- .../openpmd_sources/energy_density_cutoff.py | 2 +- .../openpmd_sources/larmor_power.py | 2 +- .../openpmd_sources/macro_counter.py | 2 +- .../mid_current_density_component.py | 2 +- .../diagnostics/openpmd_sources/momentum.py | 2 +- .../openpmd_sources/momentum_density.py | 2 +- .../openpmd_sources/weighted_velocity.py | 2 +- .../picongpu/pypicongpu/output/openpmd.py | 20 ++++++++----- .../pypicongpu/output/openpmd_sources/auto.py | 2 +- .../openpmd_sources/bound_electron_density.py | 4 +-- .../output/openpmd_sources/charge_density.py | 4 +-- .../output/openpmd_sources/counter.py | 4 +-- .../output/openpmd_sources/density.py | 4 +-- .../openpmd_sources/derived_attributes.py | 2 +- .../output/openpmd_sources/energy.py | 4 +-- .../output/openpmd_sources/energy_density.py | 4 +-- .../openpmd_sources/energy_density_cutoff.py | 4 +-- .../output/openpmd_sources/larmor_power.py | 4 +-- .../output/openpmd_sources/macro_counter.py | 4 +-- .../mid_current_density_component.py | 4 +-- .../output/openpmd_sources/momentum.py | 4 +-- .../openpmd_sources/momentum_density.py | 4 +-- .../openpmd_sources/weighted_velocity.py | 4 +-- .../pypicongpu/rendering/renderedobject.py | 29 ++++++++++++++++++- .../schema/output/openpmd.OpenPMD.json | 2 +- .../schema/output/plugin.Plugin.json | 4 +-- .../template/etc/picongpu/N.cfg.mustache | 18 ++++++++++++ 36 files changed, 110 insertions(+), 62 deletions(-) diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd.py b/lib/python/picongpu/picmi/diagnostics/openpmd.py index 238dd42268..8ffaa3273f 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd.py @@ -6,7 +6,7 @@ """ from ...pypicongpu.output.openpmd import OpenPMD as PyPIConGPUOpenPMD -from ...pypicongpu.output import Source as PyPIConGPUSource +from ...pypicongpu.output.openpmd_sources.source_base import SourceBase as PyPIConGPUSource from .timestepspec import TimeStepSpec from .openpmd_sources.source_base import SourceBase @@ -37,7 +37,7 @@ class OpenPMD: @param data_preparation_strategy strategy for particle data preparation, options: "doubleBuffer" or "adios" (ADIOS2-based), "mappedMemory" or "hdf5" (HDF5-based), the default value None indicates the PIC code default @param toml path to a TOML file for openPMD configuration. Replaces the JSON or keyword configuration. @param particle_io_chunk_size size of particle data chunks used in writing (in MiB), reduces host memory footprint for certain backends, default "None" indicates the PIC code default. - @param file_access file access mode for writing, options: "create" (new files), "append" (for checkpoint-restart workflows). + @param file_writing file writing mode for writing, options: "create" (new files), "append" (for checkpoint-restart workflows). """ def check(self): @@ -66,7 +66,7 @@ def __init__( data_preparation_strategy: Optional[Literal["doubleBuffer", "adios", "mappedMemory", "hdf5"]] = None, toml: Optional[str] = None, particle_io_chunk_size: Optional[int] = None, - file_access: Optional[Literal["create", "append"]] = "create", + file_writing: Optional[Literal["create", "append"]] = "create", ): self.period = period self.source = source @@ -79,7 +79,7 @@ def __init__( self.data_preparation_strategy = data_preparation_strategy self.toml = toml self.particle_io_chunk_size = particle_io_chunk_size - self.file_access = file_access + self.file_writing = file_writing self.check() @@ -103,6 +103,6 @@ def get_as_pypicongpu( data_preparation_strategy=self.data_preparation_strategy, toml=self.toml, particle_io_chunk_size=self.particle_io_chunk_size, - file_access=self.file_access, + file_writing=self.file_writing, ) return pypicongpu_openpmd diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/auto.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/auto.py index 61597dd9af..53646e90a4 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/auto.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/auto.py @@ -5,9 +5,8 @@ License: GPLv3+ """ -from .... import util from .source_base import SourceBase -from ...pypicongpu.output.openpmd_source import Auto as PyPIConGPUAuto +from ....pypicongpu.output.openpmd_sources import Auto as PyPIConGPUAuto import typeguard import typing @@ -25,7 +24,7 @@ class Auto(SourceBase): Default: None (PIC code-dependent). """ - filter = util.build_typesafe_property(typing.Optional[str]) + # filter = util.build_typesafe_property(typing.Optional[str]) def __init__(self, filter: typing.Optional[str] = None): self.filter = filter diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/bound_electron_density.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/bound_electron_density.py index 51354b58d0..c1dc710718 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/bound_electron_density.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/bound_electron_density.py @@ -6,7 +6,7 @@ """ from .source_base import SourceBase -from ...pypicongpu.output.openpmd_source import BoundElectronDensity as PyPIConGPUBoundElectronDensity +from ....pypicongpu.output.openpmd_sources import BoundElectronDensity as PyPIConGPUBoundElectronDensity from ...species import Species as PICMISpecies import typeguard import typing diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/charge_density.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/charge_density.py index a985e9e158..3fac9e7b93 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/charge_density.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/charge_density.py @@ -6,7 +6,7 @@ """ from .source_base import SourceBase -from ...pypicongpu.output.openpmd_source import ChargeDensity as PyPIConGPUChargeDensity +from ....pypicongpu.output.openpmd_sources import ChargeDensity as PyPIConGPUChargeDensity from ...species import Species as PICMISpecies import typeguard import typing diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/counter.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/counter.py index ff515db6a2..40d2f362fb 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/counter.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/counter.py @@ -6,7 +6,7 @@ """ from .source_base import SourceBase -from ...pypicongpu.output.openpmd_source import Counter as PyPIConGPUCounter +from ....pypicongpu.output.openpmd_sources import Counter as PyPIConGPUCounter from ...species import Species as PICMISpecies import typeguard import typing diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/density.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/density.py index 0651541016..239467c62e 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/density.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/density.py @@ -6,7 +6,7 @@ """ from .source_base import SourceBase -from ...pypicongpu.output.openpmd_source import Density as PyPIConGPUDensity +from ....pypicongpu.output.openpmd_sources import Density as PyPIConGPUDensity from ...species import Species as PICMISpecies import typeguard import typing diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/derived_attributes.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/derived_attributes.py index 4b02400f29..7f50bb6916 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/derived_attributes.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/derived_attributes.py @@ -6,7 +6,7 @@ """ from .source_base import SourceBase -from ...pypicongpu.output.openpmd_source import DerivedAttributes as PyPIConGPUDerivedAttributes +from ....pypicongpu.output.openpmd_sources import DerivedAttributes as PyPIConGPUDerivedAttributes import typeguard import typing diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/energy.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/energy.py index 915669c5df..73de600332 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/energy.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/energy.py @@ -6,7 +6,7 @@ """ from .source_base import SourceBase -from ...pypicongpu.output.openpmd_source import Energy as PyPIConGPEnergy +from ....pypicongpu.output.openpmd_sources import Energy as PyPIConGPEnergy from ...species import Species as PICMISpecies import typeguard import typing diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/energy_density.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/energy_density.py index 9c7a1f5468..19ee976e4a 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/energy_density.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/energy_density.py @@ -6,7 +6,7 @@ """ from .source_base import SourceBase -from ...pypicongpu.output.openpmd_source import EnergyDensity as PyPIConGPUEnergyDensity +from ....pypicongpu.output.openpmd_sources import EnergyDensity as PyPIConGPUEnergyDensity from ...species import Species as PICMISpecies import typeguard import typing diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/energy_density_cutoff.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/energy_density_cutoff.py index 4bb47ac2c7..659d9e3c58 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/energy_density_cutoff.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/energy_density_cutoff.py @@ -6,7 +6,7 @@ """ from .source_base import SourceBase -from ...pypicongpu.output.openpmd_source import EnergyDensityCutoff as PyPIConGPUEnergyDensityCutoff +from ....pypicongpu.output.openpmd_sources import EnergyDensityCutoff as PyPIConGPUEnergyDensityCutoff from ...species import Species as PICMISpecies import typeguard import typing diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/larmor_power.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/larmor_power.py index 412800b7cc..3db7bb4628 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/larmor_power.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/larmor_power.py @@ -6,7 +6,7 @@ """ from .source_base import SourceBase -from ...pypicongpu.output.openpmd_source import LarmorPower as PyPIConGPULarmorPower +from ....pypicongpu.output.openpmd_sources import LarmorPower as PyPIConGPULarmorPower from ...species import Species as PICMISpecies import typeguard import typing diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/macro_counter.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/macro_counter.py index 7fcd849a6d..f9e3fb0c59 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/macro_counter.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/macro_counter.py @@ -6,7 +6,7 @@ """ from .source_base import SourceBase -from ...pypicongpu.output.openpmd_source import MacroCounter as PyPIConGPUMacroCounter +from ....pypicongpu.output.openpmd_sources import MacroCounter as PyPIConGPUMacroCounter from ...species import Species as PICMISpecies import typeguard import typing diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/mid_current_density_component.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/mid_current_density_component.py index 40d0ab0c48..1c2fa7dd41 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/mid_current_density_component.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/mid_current_density_component.py @@ -6,7 +6,7 @@ """ from .source_base import SourceBase -from ...pypicongpu.output.openpmd_source import MidCurrentDensityComponent as PyPIConGPUMidCurrentDensityComponent +from ....pypicongpu.output.openpmd_sources import MidCurrentDensityComponent as PyPIConGPUMidCurrentDensityComponent from ...species import Species as PICMISpecies import typeguard import typing diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/momentum.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/momentum.py index 63383d89eb..2240cab6f1 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/momentum.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/momentum.py @@ -6,7 +6,7 @@ """ from .source_base import SourceBase -from ...pypicongpu.output.openpmd_source import Momentum as PyPIConGPUMomentum +from ....pypicongpu.output.openpmd_sources import Momentum as PyPIConGPUMomentum from ...species import Species as PICMISpecies import typeguard import typing diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/momentum_density.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/momentum_density.py index 8f64adca5b..0abfbca0f1 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/momentum_density.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/momentum_density.py @@ -6,7 +6,7 @@ """ from .source_base import SourceBase -from ...pypicongpu.output.openpmd_source import MomentumDensity as PyPIConGPUMomentumDensity +from ....pypicongpu.output.openpmd_sources import MomentumDensity as PyPIConGPUMomentumDensity from ...species import Species as PICMISpecies import typeguard import typing diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/weighted_velocity.py b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/weighted_velocity.py index 4016e20edb..80356d33f0 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd_sources/weighted_velocity.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd_sources/weighted_velocity.py @@ -6,7 +6,7 @@ """ from .source_base import SourceBase -from ...pypicongpu.output.openpmd_source import WeightedVelocity as PyPIConGPUWeightedVelocity +from ....pypicongpu.output.openpmd_sources import WeightedVelocity as PyPIConGPUWeightedVelocity from ...species import Species as PICMISpecies import typeguard import typing diff --git a/lib/python/picongpu/pypicongpu/output/openpmd.py b/lib/python/picongpu/pypicongpu/output/openpmd.py index 375c470acd..a11b05ddba 100644 --- a/lib/python/picongpu/pypicongpu/output/openpmd.py +++ b/lib/python/picongpu/pypicongpu/output/openpmd.py @@ -5,16 +5,18 @@ License: GPLv3+ """ -from . import util +from .. import util from .timestepspec import TimeStepSpec from .openpmd_sources.source_base import SourceBase +from .plugin import Plugin + import typeguard import typing from typing import Optional, List, Literal, Dict, Union @typeguard.typechecked -class OpenPMD: +class OpenPMD(Plugin): period = util.build_typesafe_property(TimeStepSpec) source = util.build_typesafe_property(Optional[List[SourceBase]]) range = util.build_typesafe_property(Optional[str]) @@ -28,7 +30,9 @@ class OpenPMD: ) toml = util.build_typesafe_property(Optional[str]) particle_io_chunk_size = util.build_typesafe_property(Optional[int]) - file_access = util.build_typesafe_property(Optional[Literal["create", "append"]]) + file_writing = util.build_typesafe_property(Optional[Literal["create", "append"]]) + + _name = "openpmd" def __init__( self, @@ -43,7 +47,7 @@ def __init__( data_preparation_strategy: Optional[Literal["doubleBuffer", "adios", "mappedMemory", "hdf5"]] = None, toml: Optional[str] = None, particle_io_chunk_size: Optional[int] = None, - file_access: Optional[Literal["create", "append"]] = "create", + file_writing: Optional[Literal["create", "append"]] = "create", ): self.period = period self.source = source @@ -51,12 +55,12 @@ def __init__( self.file = file self.ext = ext self.infix = infix - self.json = json if json is not None else {} - self.json_restart = json_restart if json_restart is not None else {} + self.json = None if json is None or json == {} else json + self.json_restart = None if json_restart is None or json_restart == {} else json_restart self.data_preparation_strategy = data_preparation_strategy self.toml = toml self.particle_io_chunk_size = particle_io_chunk_size - self.file_access = file_access + self.file_writing = file_writing self.check() def check(self) -> None: @@ -85,5 +89,5 @@ def _get_serialized(self) -> typing.Dict: "data_preparation_strategy": self.data_preparation_strategy, "toml": self.toml, "particle_io_chunk_size": self.particle_io_chunk_size, - "file_access": self.file_access, + "file_writing": self.file_writing, } diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/auto.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/auto.py index 77e5fc1fd7..ff395456be 100644 --- a/lib/python/picongpu/pypicongpu/output/openpmd_sources/auto.py +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/auto.py @@ -5,7 +5,7 @@ License: GPLv3+ """ -from .... import util +from ... import util from .source_base import SourceBase import typeguard import typing diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/bound_electron_density.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/bound_electron_density.py index 18105d83f4..37afd7773e 100644 --- a/lib/python/picongpu/pypicongpu/output/openpmd_sources/bound_electron_density.py +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/bound_electron_density.py @@ -5,8 +5,8 @@ License: GPLv3+ """ -from .... import util -from ....species import Species +from ... import util +from ...species import Species from .source_base import SourceBase import typeguard import typing diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/charge_density.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/charge_density.py index df95402500..9052d18386 100644 --- a/lib/python/picongpu/pypicongpu/output/openpmd_sources/charge_density.py +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/charge_density.py @@ -5,8 +5,8 @@ License: GPLv3+ """ -from .... import util -from ....species import Species +from ... import util +from ...species import Species from .source_base import SourceBase import typeguard import typing diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/counter.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/counter.py index f52d9cec8a..310f8a3d9b 100644 --- a/lib/python/picongpu/pypicongpu/output/openpmd_sources/counter.py +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/counter.py @@ -5,8 +5,8 @@ License: GPLv3+ """ -from .... import util -from ....species import Species +from ... import util +from ...species import Species from .source_base import SourceBase import typeguard import typing diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/density.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/density.py index 515feba243..e47b798ed8 100644 --- a/lib/python/picongpu/pypicongpu/output/openpmd_sources/density.py +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/density.py @@ -5,8 +5,8 @@ License: GPLv3+ """ -from .... import util -from ....species import Species +from ... import util +from ...species import Species from .source_base import SourceBase import typeguard import typing diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/derived_attributes.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/derived_attributes.py index 812c269bb8..11aef5a83e 100644 --- a/lib/python/picongpu/pypicongpu/output/openpmd_sources/derived_attributes.py +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/derived_attributes.py @@ -5,7 +5,7 @@ License: GPLv3+ """ -from .... import util +from ... import util from .source_base import SourceBase import typeguard import typing diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/energy.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/energy.py index 1b5dcd99c0..7de4644aaa 100644 --- a/lib/python/picongpu/pypicongpu/output/openpmd_sources/energy.py +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/energy.py @@ -5,8 +5,8 @@ License: GPLv3+ """ -from .... import util -from ....species import Species +from ... import util +from ...species import Species from .source_base import SourceBase import typeguard import typing diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/energy_density.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/energy_density.py index 4f331d5f2a..85360ea8af 100644 --- a/lib/python/picongpu/pypicongpu/output/openpmd_sources/energy_density.py +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/energy_density.py @@ -5,8 +5,8 @@ License: GPLv3+ """ -from .... import util -from ....species import Species +from ... import util +from ...species import Species from .source_base import SourceBase import typeguard import typing diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/energy_density_cutoff.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/energy_density_cutoff.py index 83528cbe44..2e74b0965b 100644 --- a/lib/python/picongpu/pypicongpu/output/openpmd_sources/energy_density_cutoff.py +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/energy_density_cutoff.py @@ -5,8 +5,8 @@ License: GPLv3+ """ -from .... import util -from ....species import Species +from ... import util +from ...species import Species from .source_base import SourceBase import typeguard import typing diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/larmor_power.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/larmor_power.py index 01d8dfe07a..268ef57092 100644 --- a/lib/python/picongpu/pypicongpu/output/openpmd_sources/larmor_power.py +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/larmor_power.py @@ -5,8 +5,8 @@ License: GPLv3+ """ -from .... import util -from ....species import Species +from ... import util +from ...species import Species from .source_base import SourceBase import typeguard import typing diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/macro_counter.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/macro_counter.py index 9071f24fdd..a71a013428 100644 --- a/lib/python/picongpu/pypicongpu/output/openpmd_sources/macro_counter.py +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/macro_counter.py @@ -5,8 +5,8 @@ License: GPLv3+ """ -from .... import util -from ....species import Species +from ... import util +from ...species import Species from .source_base import SourceBase import typeguard import typing diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/mid_current_density_component.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/mid_current_density_component.py index 39bfabc1aa..bdfa97e4a9 100644 --- a/lib/python/picongpu/pypicongpu/output/openpmd_sources/mid_current_density_component.py +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/mid_current_density_component.py @@ -5,8 +5,8 @@ License: GPLv3+ """ -from .... import util -from ....species import Species +from ... import util +from ...species import Species from .source_base import SourceBase import typeguard import typing diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/momentum.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/momentum.py index 3fd64aba12..b48ddc72f2 100644 --- a/lib/python/picongpu/pypicongpu/output/openpmd_sources/momentum.py +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/momentum.py @@ -5,8 +5,8 @@ License: GPLv3+ """ -from .... import util -from ....species import Species +from ... import util +from ...species import Species from .source_base import SourceBase import typeguard import typing diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/momentum_density.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/momentum_density.py index b57dc0ccb1..526354dfeb 100644 --- a/lib/python/picongpu/pypicongpu/output/openpmd_sources/momentum_density.py +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/momentum_density.py @@ -5,8 +5,8 @@ License: GPLv3+ """ -from .... import util -from ....species import Species +from ... import util +from ...species import Species from .source_base import SourceBase import typeguard import typing diff --git a/lib/python/picongpu/pypicongpu/output/openpmd_sources/weighted_velocity.py b/lib/python/picongpu/pypicongpu/output/openpmd_sources/weighted_velocity.py index 23ead8c1c6..4bb15583fb 100644 --- a/lib/python/picongpu/pypicongpu/output/openpmd_sources/weighted_velocity.py +++ b/lib/python/picongpu/pypicongpu/output/openpmd_sources/weighted_velocity.py @@ -5,8 +5,8 @@ License: GPLv3+ """ -from .... import util -from ....species import Species +from ... import util +from ...species import Species from .source_base import SourceBase import typeguard import typing diff --git a/lib/python/picongpu/pypicongpu/rendering/renderedobject.py b/lib/python/picongpu/pypicongpu/rendering/renderedobject.py index 89006eb9a0..a2802185bc 100644 --- a/lib/python/picongpu/pypicongpu/rendering/renderedobject.py +++ b/lib/python/picongpu/pypicongpu/rendering/renderedobject.py @@ -274,6 +274,11 @@ def __init_subclass__(cls): if SelfRegisteringRenderedObject in cls.__bases__: cls._names = [] cls._registered_class = cls + elif hasattr(cls, "_name") and cls._name != cls._dummy_name: + # Register _name in parent's _names + parent = cls.__bases__[0] # Assume first base is Plugin or similar + if hasattr(parent, "_names") and cls._name not in parent._names: + parent._names.append(cls._name) super().__init_subclass__() def get_rendering_context(self): @@ -314,10 +319,32 @@ def get_rendering_context(self): # TODO: The schema for different registered classes are likely to be identical # upto the fact that they refer to different allowed content. # We might want to unify this in the future. + + # Debug logging + logging.debug(f"Rendering context for {self.__class__.__name__}, _name: {self._name}") + logging.debug(f"Plugin._names: {self._names}") + + # Ensure _names includes all expected plugin names + if not hasattr(self, "_names") or not self._names: + self._names = [ + "auto", + "phasespace", + "energyhistogram", + "macroparticlecount", + "png", + "checkpoint", + "openpmd", + ] + logging.debug(f"Fallback _names applied: {self._names}") + + type_id = {name: name == self._name for name in self._names} + logging.debug(f"Generated typeID: {type_id}") + return RenderedObject.check_context_for_type( self._registered_class, { - "typeID": {name: name == self._name for name in self._names}, + # "typeID": {name: name == self._name for name in self._names}, + "typeID": type_id, "data": RenderedObject.check_context_for_type(self.__class__, super().get_rendering_context()), }, ) diff --git a/share/picongpu/pypicongpu/schema/output/openpmd.OpenPMD.json b/share/picongpu/pypicongpu/schema/output/openpmd.OpenPMD.json index 6825c342d7..89315acf91 100644 --- a/share/picongpu/pypicongpu/schema/output/openpmd.OpenPMD.json +++ b/share/picongpu/pypicongpu/schema/output/openpmd.OpenPMD.json @@ -100,7 +100,7 @@ "minimum": 1, "description": "Size of particle data chunks used in writing (in MiB)." }, - "file_access": { + "file_writing": { "type": [ "string", "null" diff --git a/share/picongpu/pypicongpu/schema/output/plugin.Plugin.json b/share/picongpu/pypicongpu/schema/output/plugin.Plugin.json index 5f622ab970..8fdafab27b 100644 --- a/share/picongpu/pypicongpu/schema/output/plugin.Plugin.json +++ b/share/picongpu/pypicongpu/schema/output/plugin.Plugin.json @@ -49,10 +49,10 @@ "description": "configuration data of plugin", "anyOf": [ { - "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.phase_space.PhaseSpace" + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.auto.Auto" }, { - "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.auto.Auto" + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.phase_space.PhaseSpace" }, { "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.output.energy_histogram.EnergyHistogram" diff --git a/share/picongpu/pypicongpu/template/etc/picongpu/N.cfg.mustache b/share/picongpu/pypicongpu/template/etc/picongpu/N.cfg.mustache index d0c57cffd5..f8d06e8af3 100644 --- a/share/picongpu/pypicongpu/template/etc/picongpu/N.cfg.mustache +++ b/share/picongpu/pypicongpu/template/etc/picongpu/N.cfg.mustache @@ -192,6 +192,24 @@ pypicongpu_output_with_newlines=" {{/openPMD}} {{/typeID.checkpoint}} +{{#typeID.openpmd}} +--openPMD.period {{#period.specs}}{{{start}}}:{{{stop}}}:{{{step}}}{{^_last}},{{/_last}}{{/period.specs}} +{{#source}} +{{#filter}}--openPMD.source.{{{species.name}}}.filter {{{filter}}}{{/filter}} +{{#direction}}--openPMD.source.{{{species.name}}}.direction {{{direction}}}{{/direction}} +{{#cutoff_max_energy}}--openPMD.source.{{{species.name}}}.cutoff_max_energy {{{cutoff_max_energy}}}{{/cutoff_max_energy}} +{{/source}} +{{#range}}--openPMD.range {{{range}}}{{/range}} +{{#file}}--openPMD.file {{{file}}}{{/file}} +{{#ext}}--openPMD.ext {{{ext}}}{{/ext}} +{{#infix}}--openPMD.infix {{{infix}}}{{/infix}} +{{#json}}--openPMD.json {{{json}}}{{/json}} +{{#json_restart}}--checkpoint.openPMD.jsonRestart {{{json_restart}}}{{/json_restart}} +{{#data_preparation_strategy}}--openPMD.dataPreparationStrategy {{{data_preparation_strategy}}}{{/data_preparation_strategy}} +{{#toml}}--openPMD.toml {{{toml}}}{{/toml}} +{{#particle_io_chunk_size}}--openPMD.particleIOChunkSize {{{particle_io_chunk_size}}}{{/particle_io_chunk_size}} +{{#file_writing}}--openPMD.writeAccess {{{file_writing}}}{{/file_writing}} +{{/typeID.openpmd}} {{/data}} {{/output}} From 0cba2e5889406eb8f661ecab41fd87fe9a0f9e4e Mon Sep 17 00:00:00 2001 From: mafshari64 Date: Mon, 12 May 2025 16:50:57 +0200 Subject: [PATCH 08/15] finalizing openpmd plugin --- .../pypicongpu/rendering/renderedobject.py | 28 +------------------ .../examples/laser_wakefield/main.py | 14 +++++----- 2 files changed, 8 insertions(+), 34 deletions(-) diff --git a/lib/python/picongpu/pypicongpu/rendering/renderedobject.py b/lib/python/picongpu/pypicongpu/rendering/renderedobject.py index a2802185bc..409219f1b1 100644 --- a/lib/python/picongpu/pypicongpu/rendering/renderedobject.py +++ b/lib/python/picongpu/pypicongpu/rendering/renderedobject.py @@ -274,11 +274,6 @@ def __init_subclass__(cls): if SelfRegisteringRenderedObject in cls.__bases__: cls._names = [] cls._registered_class = cls - elif hasattr(cls, "_name") and cls._name != cls._dummy_name: - # Register _name in parent's _names - parent = cls.__bases__[0] # Assume first base is Plugin or similar - if hasattr(parent, "_names") and cls._name not in parent._names: - parent._names.append(cls._name) super().__init_subclass__() def get_rendering_context(self): @@ -320,31 +315,10 @@ def get_rendering_context(self): # upto the fact that they refer to different allowed content. # We might want to unify this in the future. - # Debug logging - logging.debug(f"Rendering context for {self.__class__.__name__}, _name: {self._name}") - logging.debug(f"Plugin._names: {self._names}") - - # Ensure _names includes all expected plugin names - if not hasattr(self, "_names") or not self._names: - self._names = [ - "auto", - "phasespace", - "energyhistogram", - "macroparticlecount", - "png", - "checkpoint", - "openpmd", - ] - logging.debug(f"Fallback _names applied: {self._names}") - - type_id = {name: name == self._name for name in self._names} - logging.debug(f"Generated typeID: {type_id}") - return RenderedObject.check_context_for_type( self._registered_class, { - # "typeID": {name: name == self._name for name in self._names}, - "typeID": type_id, + "typeID": {name: name == self._name for name in self._names}, "data": RenderedObject.check_context_for_type(self.__class__, super().get_rendering_context()), }, ) diff --git a/share/picongpu/pypicongpu/examples/laser_wakefield/main.py b/share/picongpu/pypicongpu/examples/laser_wakefield/main.py index 4047ea0232..c472efec77 100644 --- a/share/picongpu/pypicongpu/examples/laser_wakefield/main.py +++ b/share/picongpu/pypicongpu/examples/laser_wakefield/main.py @@ -192,6 +192,11 @@ picmi.diagnostics.Checkpoint( period=picmi.diagnostics.TimeStepSpec[::100], ), + picmi.diagnostics.OpenPMD( + period=picmi.diagnostics.TimeStepSpec[::100], + file="simOutput", + ext="bp", + ), ] sim.add_laser(laser, None) @@ -206,13 +211,8 @@ min_weight_input.addToCustomInput({"minimum_weight": 10.0}, "minimum_weight") sim.picongpu_add_custom_user_input(min_weight_input) - output_configuration = pypicongpu.customuserinput.CustomUserInput() - - output_configuration.addToCustomInput( - {"openPMD_period": 100, "openPMD_file": "simData", "openPMD_extension": "bp"}, "openPMD plugin configuration" - ) - - sim.picongpu_add_custom_user_input(output_configuration) + # output_configuration = pypicongpu.customuserinput.CustomUserInput() + # sim.picongpu_add_custom_user_input(output_configuration) if __name__ == "__main__": sim.write_input_file(OUTPUT_DIRECTORY_PATH) From 4f7a2759d4c04e270b53ec6359471e1bfdb4618e Mon Sep 17 00:00:00 2001 From: mafshari64 Date: Thu, 31 Jul 2025 14:10:08 +0200 Subject: [PATCH 09/15] adding rangespec class to openpmd --- .../picongpu/picmi/diagnostics/__init__.py | 9 + .../picongpu/picmi/diagnostics/openpmd.py | 24 +-- .../picongpu/picmi/diagnostics/rangespec.py | 157 ++++++++++++++++++ .../picongpu/pypicongpu/output/__init__.py | 2 + .../picongpu/pypicongpu/output/openpmd.py | 25 +-- .../picongpu/pypicongpu/output/rangespec.py | 63 +++++++ 6 files changed, 258 insertions(+), 22 deletions(-) create mode 100644 lib/python/picongpu/picmi/diagnostics/rangespec.py create mode 100644 lib/python/picongpu/pypicongpu/output/rangespec.py diff --git a/lib/python/picongpu/picmi/diagnostics/__init__.py b/lib/python/picongpu/picmi/diagnostics/__init__.py index 8b6689c75c..891dbf7ca9 100644 --- a/lib/python/picongpu/picmi/diagnostics/__init__.py +++ b/lib/python/picongpu/picmi/diagnostics/__init__.py @@ -1,9 +1,17 @@ +""" +This file is part of PIConGPU. +Copyright 2024 PIConGPU contributors +Authors: Julian Lenz, Masoud Afshari +License: GPLv3+ +""" + from .auto import Auto from .phase_space import PhaseSpace from .energy_histogram import EnergyHistogram from .macro_particle_count import MacroParticleCount from .png import Png from .timestepspec import TimeStepSpec +from .rangespec import RangeSpec from .checkpoint import Checkpoint from .openpmd import OpenPMD from .openpmd_sources.source_base import SourceBase @@ -15,6 +23,7 @@ "MacroParticleCount", "Png", "TimeStepSpec", + "RangeSpec", "Checkpoint", "OpenPMD", "SourceBase", diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd.py b/lib/python/picongpu/picmi/diagnostics/openpmd.py index 8ffaa3273f..2c64117217 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd.py @@ -8,11 +8,11 @@ from ...pypicongpu.output.openpmd import OpenPMD as PyPIConGPUOpenPMD from ...pypicongpu.output.openpmd_sources.source_base import SourceBase as PyPIConGPUSource from .timestepspec import TimeStepSpec - +from .rangespec import RangeSpec from .openpmd_sources.source_base import SourceBase import typeguard -from typing import Optional, Dict, Union, List, Literal +from typing import Optional, Dict, Union, List, Literal, Tuple @typeguard.typechecked @@ -26,11 +26,14 @@ class OpenPMD: @param period specification of the time steps for data output, outputs will always be written at the end of a PIC time step. @param source list of data source objects to include in the dump (e.g., [ChargeDensity(filter="all")]), Setting this to None will cause an empty dump - @param range contiguous range of cells to dump the base- and derived field for - specification as comma-separated "begin:end" range for each dimension, i.e. "begin:end,begin:end,begin:end" - Notes: Values will be clipped to the simulation box. Begin and/or end may be omitted to indicate the limit of the simulation box in the dimension. The Default value ":,::,:" indicates that fields should be dumped for all cells. + @param range contiguous range of cells to dump the base- and derived field for, specified as a RangeSpec object + or a string in the format "brange clegin:end" (1D), "begin:end,begin:end" (2D), or "begin:end,begin:end,begin:end" (3D). + Example: "0:10,5:15,2:8" specifies cells 0 to 10 (x), 5 to 15 (y), 2 to 8 (z). + Notes: Values are clipped to the simulation box. Begin and/or end may be omitted (":") to indicate the full extent + of the dimension. Negative indices are supported (e.g., "-5:-1" for last 5 cells). The default ":,:,:," (3D), + ":,:" (2D), or ":" (1D) includes all cells in the simulation box. @param file relative or absolute file path prefix for openPMD output files. Relative paths are interpreted as relative to the simulation output directory, the default value None indicates the PIC code's default. - @param ext file extension controlling the openPMD backend, options are "bp" (default backend ADIOS2), "bp4" (bp4 backend ADIOS2), "bp5" (bp5 backend ADIOS2), "h5" (HDF5), "sst" (ADIOS2/SST for streaming). + @param ext file extension controlling the openPMD backend, options are "bp" (default backend ADIOS2), "h5" (HDF5), "sst" (ADIOS2/SST for streaming). @param infix filename infix for the iteration layout (e.g., "_%06T"), use "NULL" for the group-based layout, ext="sst" requires infix="NULL". @param json openPMD backend configuration as a JSON string, dictionary, or filename (filename must be prepended with "@"). @param json_restart backend-specific parameters for restarting, as a JSON string, dictionary, or filename (filenames must be prepended with "@"). @@ -44,8 +47,6 @@ def check(self): """ Validate the provided parameters. """ - if self.period is None: - raise ValueError("period is mandatory") if self.particle_io_chunk_size is not None and self.particle_io_chunk_size < 1: raise ValueError("particle_io_chunk_size (in MiB) must be positive") if self.ext == "sst" and self.infix is not None and self.infix != "NULL": @@ -57,7 +58,7 @@ def __init__( self, period: TimeStepSpec, source: Optional[List[SourceBase]] = None, - range: Optional[str] = ":,:,:", + range: Optional[Union[str, RangeSpec]] = ":,:,:", file: Optional[str] = None, ext: Optional[Literal["bp", "h5", "sst"]] = "bp", infix: Optional[str] = "NULL", @@ -70,7 +71,7 @@ def __init__( ): self.period = period self.source = source - self.range = range + self.range = RangeSpec(range) if isinstance(range, str) else range self.file = file self.ext = ext self.infix = infix @@ -88,13 +89,14 @@ def get_as_pypicongpu( pypicongpu_by_picmi_species: Dict, time_step_size: float, num_steps: int, + simulation_box: Tuple[int, ...] = (0, 0, 0), # Default for compatibility ) -> PyPIConGPUOpenPMD: self.check() pypicongpu_openpmd = PyPIConGPUOpenPMD( period=self.period.get_as_pypicongpu(time_step_size, num_steps), source=PyPIConGPUSource([s.get_as_pypicongpu() for s in self.source]) if self.source is not None else None, - range=self.range, + range=self.range.get_as_pypicongpu(simulation_box), file=self.file, ext=self.ext, infix=self.infix, diff --git a/lib/python/picongpu/picmi/diagnostics/rangespec.py b/lib/python/picongpu/picmi/diagnostics/rangespec.py new file mode 100644 index 0000000000..a9fe4cac27 --- /dev/null +++ b/lib/python/picongpu/picmi/diagnostics/rangespec.py @@ -0,0 +1,157 @@ +""" +This file is part of PIConGPU. +Copyright 2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from typing import Tuple +from ...pypicongpu.output import RangeSpec as PyPIConGPURangeSpec +import re + + +class RangeSpec: + """ + A class to specify a contiguous range of cells for simulation output in 1D, 2D, or 3D. + + This class parses a string in the format "begin:end" (1D), "begin:end,begin:end" (2D), + or "begin:end,begin:end,begin:end" (3D) to define inclusive cell ranges for the simulation + dimensions. For example: + - 1D: RangeSpec("0:10") specifies cells 0 to 10 (x). + - 2D: RangeSpec("0:10,5:15") specifies cells 0 to 10 (x) and 5 to 15 (y). + - 3D: RangeSpec("0:10,5:15,2:8") specifies cells 0 to 10 (x), 5 to 15 (y), 2 to 8 (z). + The default ":", ":,:" or ":,:,:," includes all cells in the simulation box for 1D, 2D or 3D. + Values are clipped to the simulation box boundaries, and omitted bounds (":") indicate the + full extent of the dimension. Negative indices are supported, counting from the end of the + simulation box. + + Example usage: + # 1D: rs = RangeSpec("0:10") # x: cells 0 to 10 + # rs = RangeSpec("-5:-1") # x: last 5 cells (15 to 19 for 20-cell box) + # 2D: rs = RangeSpec("0:10,5:15") # x: 0 to 10, y: 5 to 15 + # rs = RangeSpec("-5:-1,0:15") # x: last 5 cells (15 to 19 for 20-cell box), y: 0 to 15 + # 3D: rs = RangeSpec("0:10,5:15,:") # x: 0 to 10, y: 5 to 15, z: full range + # rs = RangeSpec("-5:-1,-10:-2,2:8") # x: last 5 cells (15 to 19 for 20-cell box), y: last 9 to 2 cells (20 to 28 for 30-cell box), z: 2 to 8 + """ + + def __init__(self, range_str: str): + """ + Initialize a RangeSpec from a string. + + :param range_str: A string specifying cell ranges for 1 to 3 dimensions, e.g., "0:10" + (1D), "0:10,5:15" (2D), or "0:10,5:15,2:8" (3D). + :raises ValueError: If the string format is invalid or contains non-integer bounds. + """ + self.range_str = range_str + self.slices = self._parse_range(range_str) + self._validate() + + def _parse_range(self, range_str: str) -> Tuple[slice, ...]: + """ + Parse the range string into a tuple of slice objects for each dimension. + + :param range_str: Input string (e.g., "0:10,5:15,2:8"). + :return: Tuple of 1 to 3 slice objects. + :raises ValueError: If the string format is invalid or has more than 3 dimensions. + """ + # Split the string into dimension parts + parts = range_str.split(",") + if len(parts) > 3: + raise ValueError(f"Range must specify at most three dimensions, got {len(parts)}: {range_str}") + + slices = [] + for i, part in enumerate(parts): + part = part.strip() + if part == ":": + slices.append(slice(None, None, 1)) + continue + + # Match "begin:end" or single ":" using regex + match = re.match(r"^([-]?\d+)?(:([-]?\d+)?)?$", part) + if not match: + raise ValueError(f"Invalid range format for dimension {i+1}: {part}. Expected 'begin:end' or ':'") + + start, _, end = match.groups() + start = int(start) if start is not None else None + end = int(end) if end is not None else None + + # Step is always 1 for contiguous ranges + slices.append(slice(start, end, 1)) + + return tuple(slices) + + def _validate(self): + """ + Validate the parsed slices. + + :raises ValueError: If slices have invalid step values. + """ + for i, s in enumerate(self.slices): + if s.step is not None and s.step != 1: + raise ValueError( + f"Step size must be 1 in dimension {i+1} since RangeSpec only supports contiguous ranges. Got {s.step}." + ) + + def _interpret_nones(self, spec: slice, dim_size: int) -> slice: + """ + Replace None in slice bounds with simulation box limits (0 for start, dim_size-1 for stop). + + :param spec: Input slice. + :param dim_size: Size of the simulation box in the dimension. + :return: Slice with explicit bounds. + """ + return slice( + 0 if spec.start is None else spec.start, + dim_size - 1 if spec.stop is None else spec.stop, + 1, + ) + + def _interpret_negatives(self, spec: slice, dim_size: int) -> slice: + """ + Convert negative indices to positive, clipping to simulation box. + + :param spec: Input slice. + :param dim_size: Size of the simulation box in the dimension. + :return: Slice with non-negative bounds, clipped to [0, dim_size-1]. + """ + if dim_size <= 0: + raise ValueError(f"Dimension size must be positive. Got {dim_size}.") + + start = spec.start if spec.start is not None else 0 + stop = spec.stop if spec.stop is not None else dim_size - 1 + + # Convert negative indices + start = dim_size + start if start < 0 else start + stop = dim_size + stop if stop < 0 else stop + + # Clip to simulation box + start = max(0, min(start, dim_size - 1)) + stop = max(0, min(stop, dim_size - 1)) + + # Ensure start <= stop for a valid range + if start > stop: + start, stop = stop, start + + return slice(start, stop, 1) + + def get_as_pypicongpu(self, simulation_box: Tuple[int, ...]) -> PyPIConGPURangeSpec: + """ + Convert to a PyPIConGPURangeSpec object, applying simulation box clipping. + + :param simulation_box: Tuple of dimension sizes (1 to 3 dimensions). + :return: PyPIConGPURangeSpec object with clipped, non-negative slices. + :raises ValueError: If the number of ranges does not match the simulation box dimensions. + """ + if len(self.slices) != len(simulation_box): + raise ValueError( + f"Number of range specifications ({len(self.slices)}) must match " + f"simulation box dimensions ({len(simulation_box)})." + ) + + # Process each dimension + processed_slices = [ + self._interpret_negatives(self._interpret_nones(s, dim_size), dim_size) + for s, dim_size in zip(self.slices, simulation_box) + ] + + return PyPIConGPURangeSpec(processed_slices) diff --git a/lib/python/picongpu/pypicongpu/output/__init__.py b/lib/python/picongpu/pypicongpu/output/__init__.py index 8b6689c75c..585025d311 100644 --- a/lib/python/picongpu/pypicongpu/output/__init__.py +++ b/lib/python/picongpu/pypicongpu/output/__init__.py @@ -4,6 +4,7 @@ from .macro_particle_count import MacroParticleCount from .png import Png from .timestepspec import TimeStepSpec +from .rangespec import RangeSpec from .checkpoint import Checkpoint from .openpmd import OpenPMD from .openpmd_sources.source_base import SourceBase @@ -15,6 +16,7 @@ "MacroParticleCount", "Png", "TimeStepSpec", + "RangeSpec", "Checkpoint", "OpenPMD", "SourceBase", diff --git a/lib/python/picongpu/pypicongpu/output/openpmd.py b/lib/python/picongpu/pypicongpu/output/openpmd.py index a11b05ddba..f082516981 100644 --- a/lib/python/picongpu/pypicongpu/output/openpmd.py +++ b/lib/python/picongpu/pypicongpu/output/openpmd.py @@ -7,8 +7,9 @@ from .. import util from .timestepspec import TimeStepSpec -from .openpmd_sources.source_base import SourceBase +from .rangespec import RangeSpec from .plugin import Plugin +from ..openpmd_sources.source_base import SourceBase import typeguard import typing @@ -19,9 +20,9 @@ class OpenPMD(Plugin): period = util.build_typesafe_property(TimeStepSpec) source = util.build_typesafe_property(Optional[List[SourceBase]]) - range = util.build_typesafe_property(Optional[str]) + range = util.build_typesafe_property(Optional[RangeSpec]) file = util.build_typesafe_property(Optional[str]) - ext = util.build_typesafe_property(Optional[Literal["bp", "h5", "sst"]]) + ext = util.build_typesafe_property(Optional[Literal["bp", "bp4", "bp5", "h5", "sst"]]) infix = util.build_typesafe_property(Optional[str]) json = util.build_typesafe_property(Union[str, Dict, None]) json_restart = util.build_typesafe_property(Union[str, Dict, None]) @@ -38,9 +39,9 @@ def __init__( self, period: TimeStepSpec, source: Optional[List[SourceBase]] = None, - range: Optional[str] = ":,:,:", + range: Optional[RangeSpec] = None, file: Optional[str] = None, - ext: Optional[Literal["bp", "h5", "sst"]] = "bp", + ext: Optional[Literal["bp", "bp4", "bp5", "h5", "sst"]] = "bp", infix: Optional[str] = "NULL", json: Optional[Union[str, Dict]] = None, json_restart: Optional[Union[str, Dict]] = None, @@ -64,9 +65,9 @@ def __init__( self.check() def check(self) -> None: - # Validate parameters - if self.period is None: - raise ValueError("period is mandatory") + """ + Validate the provided parameters. + """ if self.particle_io_chunk_size is not None and self.particle_io_chunk_size < 1: raise ValueError("particle_io_chunk_size (in MiB) must be positive") if self.ext == "sst" and self.infix is not None and self.infix != "NULL": @@ -75,12 +76,14 @@ def check(self) -> None: raise ValueError("source must be a list of SourceBase objects") def _get_serialized(self) -> typing.Dict: - # Return serialized representation + """ + Serialize the OpenPMD object to a JSON-compatible dictionary. + """ self.check() return { - "period": self.period.get_rendering_context(), + "period": self.period._get_serialized(), "source": [s._get_serialized() for s in self.source] if self.source is not None else None, - "range": self.range, + "range": self.range._get_serialized() if self.range is not None else None, "file": self.file, "ext": self.ext, "infix": self.infix, diff --git a/lib/python/picongpu/pypicongpu/output/rangespec.py b/lib/python/picongpu/pypicongpu/output/rangespec.py new file mode 100644 index 0000000000..388f70eba5 --- /dev/null +++ b/lib/python/picongpu/pypicongpu/output/rangespec.py @@ -0,0 +1,63 @@ +""" +This file is part of PIConGPU. +Copyright 2025 PIConGPU contributors +Authors: Masoud Afshari +License: GPLv3+ +""" + +from ..rendering.renderedobject import RenderedObject +from ..util import build_typesafe_property + +import typeguard + + +def _serialize(spec): + """ + Serialize a slice object to a JSON-compatible dictionary. + + :param spec: A slice object representing a range for one dimension. + :return: Dictionary with start, stop, and step keys. + :raises ValueError: If the input is not a slice. + """ + if isinstance(spec, slice): + return { + "start": spec.start if spec.start is not None else 0, + "stop": spec.stop if spec.stop is not None else -1, + "step": spec.step if spec.step is not None else 1, + } + raise ValueError(f"Unknown serialization for {spec=} as a range specifier.") + + +@typeguard.typechecked +class RangeSpec(RenderedObject): + """ + A class to specify a contiguous range of cells for simulation output in 1D, 2D, or 3D. + + This class stores a list of slices representing inclusive cell ranges for each dimension. + It is used as the output of PICMI RangeSpec.get_as_pypicongpu, with negative indices and + clipping already handled. The slices must have a step size of 1 (contiguous ranges). + + """ + + specs = build_typesafe_property(list[slice]) + + def __init__(self, specs: list[slice]): + """ + Initialize a RangeSpec with a list of slices. + + :param specs: List of 1 to 3 slice objects, one per dimension. + :raises ValueError: If specs is empty, has more than 3 slices, or contains slices with step != 1. + """ + if not specs: + raise ValueError("RangeSpec must have at least one slice.") + if len(specs) > 3: + raise ValueError(f"RangeSpec must have at most 3 slices, got {len(specs)}.") + for i, spec in enumerate(specs): + if spec.step is not None and spec.step != 1: + raise ValueError( + f"Step size must be 1 in dimension {i+1} since RangeSpec only supports contiguous ranges. Got {spec.step}." + ) + self.specs = specs + + def _get_serialized(self): + return {"specs": list(map(_serialize, self.specs))} From b44bf968ae8372e30fc70eca6d5d56afb293218b Mon Sep 17 00:00:00 2001 From: mafshari64 Date: Thu, 31 Jul 2025 14:55:21 +0200 Subject: [PATCH 10/15] editing pypicongpu/output/openpmd.py --- lib/python/picongpu/pypicongpu/output/openpmd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/python/picongpu/pypicongpu/output/openpmd.py b/lib/python/picongpu/pypicongpu/output/openpmd.py index f082516981..511310f110 100644 --- a/lib/python/picongpu/pypicongpu/output/openpmd.py +++ b/lib/python/picongpu/pypicongpu/output/openpmd.py @@ -9,7 +9,7 @@ from .timestepspec import TimeStepSpec from .rangespec import RangeSpec from .plugin import Plugin -from ..openpmd_sources.source_base import SourceBase +from .openpmd_sources.source_base import SourceBase import typeguard import typing From f2da5e469b0ca551959e37637259df5ed03db64a Mon Sep 17 00:00:00 2001 From: mafshari64 Date: Fri, 1 Aug 2025 10:04:19 +0200 Subject: [PATCH 11/15] removing Default simulation_box to fix CI LWFA --- lib/python/picongpu/picmi/diagnostics/openpmd.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd.py b/lib/python/picongpu/picmi/diagnostics/openpmd.py index 2c64117217..6c2510f408 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd.py @@ -27,13 +27,13 @@ class OpenPMD: @param source list of data source objects to include in the dump (e.g., [ChargeDensity(filter="all")]), Setting this to None will cause an empty dump @param range contiguous range of cells to dump the base- and derived field for, specified as a RangeSpec object - or a string in the format "brange clegin:end" (1D), "begin:end,begin:end" (2D), or "begin:end,begin:end,begin:end" (3D). + or a string in the format "begin:end" (1D), "begin:end,begin:end" (2D), or "begin:end,begin:end,begin:end" (3D). Example: "0:10,5:15,2:8" specifies cells 0 to 10 (x), 5 to 15 (y), 2 to 8 (z). Notes: Values are clipped to the simulation box. Begin and/or end may be omitted (":") to indicate the full extent of the dimension. Negative indices are supported (e.g., "-5:-1" for last 5 cells). The default ":,:,:," (3D), ":,:" (2D), or ":" (1D) includes all cells in the simulation box. @param file relative or absolute file path prefix for openPMD output files. Relative paths are interpreted as relative to the simulation output directory, the default value None indicates the PIC code's default. - @param ext file extension controlling the openPMD backend, options are "bp" (default backend ADIOS2), "h5" (HDF5), "sst" (ADIOS2/SST for streaming). + @param ext file extension controlling the openPMD backend, options are "bp" (default backend ADIOS2), "bp4" (bp4 backend ADIOS2), "bp5" (bp5 backend ADIOS2), "h5" (HDF5), "sst" (ADIOS2/SST for streaming). @param infix filename infix for the iteration layout (e.g., "_%06T"), use "NULL" for the group-based layout, ext="sst" requires infix="NULL". @param json openPMD backend configuration as a JSON string, dictionary, or filename (filename must be prepended with "@"). @param json_restart backend-specific parameters for restarting, as a JSON string, dictionary, or filename (filenames must be prepended with "@"). @@ -60,7 +60,7 @@ def __init__( source: Optional[List[SourceBase]] = None, range: Optional[Union[str, RangeSpec]] = ":,:,:", file: Optional[str] = None, - ext: Optional[Literal["bp", "h5", "sst"]] = "bp", + ext: Optional[Literal["bp", "bp4", "bp5", "h5", "sst"]] = "bp", infix: Optional[str] = "NULL", json: Optional[Union[str, Dict]] = None, json_restart: Optional[Union[str, Dict]] = None, @@ -89,10 +89,9 @@ def get_as_pypicongpu( pypicongpu_by_picmi_species: Dict, time_step_size: float, num_steps: int, - simulation_box: Tuple[int, ...] = (0, 0, 0), # Default for compatibility + simulation_box: Tuple[int, ...], ) -> PyPIConGPUOpenPMD: self.check() - pypicongpu_openpmd = PyPIConGPUOpenPMD( period=self.period.get_as_pypicongpu(time_step_size, num_steps), source=PyPIConGPUSource([s.get_as_pypicongpu() for s in self.source]) if self.source is not None else None, From e91482583ee7ecf1381180783cee78cfd34be1c2 Mon Sep 17 00:00:00 2001 From: mafshari64 Date: Fri, 1 Aug 2025 15:48:43 +0200 Subject: [PATCH 12/15] add simulation_box to s.plugin in picmi.simulation.py --- lib/python/picongpu/picmi/diagnostics/openpmd.py | 4 ++-- lib/python/picongpu/picmi/simulation.py | 8 +++++++- lib/python/picongpu/pypicongpu/output/openpmd.py | 4 ++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/python/picongpu/picmi/diagnostics/openpmd.py b/lib/python/picongpu/picmi/diagnostics/openpmd.py index 6c2510f408..b6b9255062 100644 --- a/lib/python/picongpu/picmi/diagnostics/openpmd.py +++ b/lib/python/picongpu/picmi/diagnostics/openpmd.py @@ -33,7 +33,7 @@ class OpenPMD: of the dimension. Negative indices are supported (e.g., "-5:-1" for last 5 cells). The default ":,:,:," (3D), ":,:" (2D), or ":" (1D) includes all cells in the simulation box. @param file relative or absolute file path prefix for openPMD output files. Relative paths are interpreted as relative to the simulation output directory, the default value None indicates the PIC code's default. - @param ext file extension controlling the openPMD backend, options are "bp" (default backend ADIOS2), "bp4" (bp4 backend ADIOS2), "bp5" (bp5 backend ADIOS2), "h5" (HDF5), "sst" (ADIOS2/SST for streaming). + @param ext file extension controlling the openPMD backend, options are "bp" (default backend ADIOS2), "h5" (HDF5), "sst" (ADIOS2/SST for streaming). @param infix filename infix for the iteration layout (e.g., "_%06T"), use "NULL" for the group-based layout, ext="sst" requires infix="NULL". @param json openPMD backend configuration as a JSON string, dictionary, or filename (filename must be prepended with "@"). @param json_restart backend-specific parameters for restarting, as a JSON string, dictionary, or filename (filenames must be prepended with "@"). @@ -60,7 +60,7 @@ def __init__( source: Optional[List[SourceBase]] = None, range: Optional[Union[str, RangeSpec]] = ":,:,:", file: Optional[str] = None, - ext: Optional[Literal["bp", "bp4", "bp5", "h5", "sst"]] = "bp", + ext: Optional[Literal["bp", "h5", "sst"]] = "bp", infix: Optional[str] = "NULL", json: Optional[Union[str, Dict]] = None, json_restart: Optional[Union[str, Dict]] = None, diff --git a/lib/python/picongpu/picmi/simulation.py b/lib/python/picongpu/picmi/simulation.py index 39413d06aa..704e89dc26 100644 --- a/lib/python/picongpu/picmi/simulation.py +++ b/lib/python/picongpu/picmi/simulation.py @@ -1,7 +1,7 @@ """ This file is part of PIConGPU. Copyright 2021-2025 PIConGPU contributors -Authors: Hannes Troepgen, Brian Edward Marre, Julian Lenz +Authors: Hannes Troepgen, Brian Edward Marre, Julian Lenz, Masoud Afshari License: GPLv3+ """ @@ -480,6 +480,12 @@ def get_as_pypicongpu(self) -> pypicongpu.simulation.Simulation: s.init_manager, pypicongpu_by_picmi_species = self.__get_init_manager() + # Extract simulation_box from grid + if isinstance(self.solver.grid, Cartesian3DGrid): + simulation_box = tuple(self.solver.grid.number_of_cells) + else: + raise ValueError("Grid must be a Cartesian3DGrid with defined number_of_cells") + s.plugins = [ entry.get_as_pypicongpu(pypicongpu_by_picmi_species, self.time_step_size, s.time_steps) for entry in self.diagnostics diff --git a/lib/python/picongpu/pypicongpu/output/openpmd.py b/lib/python/picongpu/pypicongpu/output/openpmd.py index 511310f110..6071617368 100644 --- a/lib/python/picongpu/pypicongpu/output/openpmd.py +++ b/lib/python/picongpu/pypicongpu/output/openpmd.py @@ -22,7 +22,7 @@ class OpenPMD(Plugin): source = util.build_typesafe_property(Optional[List[SourceBase]]) range = util.build_typesafe_property(Optional[RangeSpec]) file = util.build_typesafe_property(Optional[str]) - ext = util.build_typesafe_property(Optional[Literal["bp", "bp4", "bp5", "h5", "sst"]]) + ext = util.build_typesafe_property(Optional[Literal["bp", "h5", "sst"]]) infix = util.build_typesafe_property(Optional[str]) json = util.build_typesafe_property(Union[str, Dict, None]) json_restart = util.build_typesafe_property(Union[str, Dict, None]) @@ -41,7 +41,7 @@ def __init__( source: Optional[List[SourceBase]] = None, range: Optional[RangeSpec] = None, file: Optional[str] = None, - ext: Optional[Literal["bp", "bp4", "bp5", "h5", "sst"]] = "bp", + ext: Optional[Literal["bp", "h5", "sst"]] = "bp", infix: Optional[str] = "NULL", json: Optional[Union[str, Dict]] = None, json_restart: Optional[Union[str, Dict]] = None, From 9f6e5a1994b3ee33040d15a6a2b4259189d0dc94 Mon Sep 17 00:00:00 2001 From: mafshari64 Date: Fri, 1 Aug 2025 17:28:36 +0200 Subject: [PATCH 13/15] Fix TypeError in PhaseSpace and Auto by adding simulation_box parameter --- lib/python/picongpu/picmi/diagnostics/auto.py | 3 ++- lib/python/picongpu/picmi/diagnostics/phase_space.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/python/picongpu/picmi/diagnostics/auto.py b/lib/python/picongpu/picmi/diagnostics/auto.py index 20f0134f64..932e5aefb0 100644 --- a/lib/python/picongpu/picmi/diagnostics/auto.py +++ b/lib/python/picongpu/picmi/diagnostics/auto.py @@ -1,7 +1,7 @@ """ This file is part of PIConGPU. Copyright 2025 PIConGPU contributors -Authors: Pawel Ordyna +Authors: Pawel Ordyna, Masoud Afshari License: GPLv3+ """ @@ -42,6 +42,7 @@ def get_as_pypicongpu( dict_species_picmi_to_pypicongpu: dict[PICMISpecies, PyPIConGPUSpecies], time_step_size, num_steps, + simulation_box=None, # Added to match OpenPMD signature, not used ) -> PyPIConGPUAuto: self.check() pypicongpu_auto = PyPIConGPUAuto() diff --git a/lib/python/picongpu/picmi/diagnostics/phase_space.py b/lib/python/picongpu/picmi/diagnostics/phase_space.py index ec3872906b..c58567c788 100644 --- a/lib/python/picongpu/picmi/diagnostics/phase_space.py +++ b/lib/python/picongpu/picmi/diagnostics/phase_space.py @@ -76,6 +76,7 @@ def get_as_pypicongpu( dict_species_picmi_to_pypicongpu: dict[PICMISpecies, PyPIConGPUSpecies], time_step_size, num_steps, + simulation_box=None, # Added to match OpenPMD signature, not used ) -> PyPIConGPUPhaseSpace: self.check() From 8f94e58a0abc9f5989b5681abf72b70f9581db26 Mon Sep 17 00:00:00 2001 From: mafshari64 Date: Mon, 4 Aug 2025 11:48:55 +0200 Subject: [PATCH 14/15] Fix TypeError for openpmd plugin --- lib/python/picongpu/picmi/diagnostics/checkpoint.py | 1 + .../picongpu/picmi/diagnostics/energy_histogram.py | 1 + .../picmi/diagnostics/macro_particle_count.py | 1 + lib/python/picongpu/picmi/diagnostics/png.py | 1 + lib/python/picongpu/pypicongpu/output/openpmd.py | 11 ++++++++++- 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/python/picongpu/picmi/diagnostics/checkpoint.py b/lib/python/picongpu/picmi/diagnostics/checkpoint.py index 160a856265..6be3a5a5e2 100644 --- a/lib/python/picongpu/picmi/diagnostics/checkpoint.py +++ b/lib/python/picongpu/picmi/diagnostics/checkpoint.py @@ -108,6 +108,7 @@ def get_as_pypicongpu( pypicongpu_by_picmi_species: Dict, time_step_size: float, num_steps: int, + simulation_box=None, # Added to match OpenPMD signature, not used ) -> PyPIConGPUCheckpoint: self.check() diff --git a/lib/python/picongpu/picmi/diagnostics/energy_histogram.py b/lib/python/picongpu/picmi/diagnostics/energy_histogram.py index 28602556f6..ae73903e7a 100644 --- a/lib/python/picongpu/picmi/diagnostics/energy_histogram.py +++ b/lib/python/picongpu/picmi/diagnostics/energy_histogram.py @@ -79,6 +79,7 @@ def get_as_pypicongpu( dict_species_picmi_to_pypicongpu: dict[PICMISpecies, PyPIConGPUSpecies], time_step_size, num_steps, + simulation_box=None, # Added to match OpenPMD signature, not used ) -> PyPIConGPUEnergyHistogram: self.check() diff --git a/lib/python/picongpu/picmi/diagnostics/macro_particle_count.py b/lib/python/picongpu/picmi/diagnostics/macro_particle_count.py index 097a4d9a6b..cf80b48e8c 100644 --- a/lib/python/picongpu/picmi/diagnostics/macro_particle_count.py +++ b/lib/python/picongpu/picmi/diagnostics/macro_particle_count.py @@ -49,6 +49,7 @@ def get_as_pypicongpu( dict_species_picmi_to_pypicongpu: dict[PICMISpecies, PyPIConGPUSpecies], time_step_size, num_steps, + simulation_box=None, # Added to match OpenPMD signature, not used ) -> PyPIConGPUMacroParticleCount: self.check() diff --git a/lib/python/picongpu/picmi/diagnostics/png.py b/lib/python/picongpu/picmi/diagnostics/png.py index f8626e0cf0..54ef2ce602 100644 --- a/lib/python/picongpu/picmi/diagnostics/png.py +++ b/lib/python/picongpu/picmi/diagnostics/png.py @@ -189,6 +189,7 @@ def get_as_pypicongpu( dict_species_picmi_to_pypicongpu: dict[PICMISpecies, PyPIConGPUSpecies], time_step_size, num_steps, + simulation_box=None, # Added to match OpenPMD signature, not used ) -> PyPIConGPUPNG: self.check() diff --git a/lib/python/picongpu/pypicongpu/output/openpmd.py b/lib/python/picongpu/pypicongpu/output/openpmd.py index 6071617368..575bc784c4 100644 --- a/lib/python/picongpu/pypicongpu/output/openpmd.py +++ b/lib/python/picongpu/pypicongpu/output/openpmd.py @@ -80,10 +80,19 @@ def _get_serialized(self) -> typing.Dict: Serialize the OpenPMD object to a JSON-compatible dictionary. """ self.check() + + # Convert RangeSpec to string format + range_context = self.range._get_serialized() if self.range is not None else None + if range_context: + specs = range_context["specs"] + range_str = ",".join(f"{spec['start']}:{spec['stop']}" for spec in specs) + else: + range_str = None + return { "period": self.period._get_serialized(), "source": [s._get_serialized() for s in self.source] if self.source is not None else None, - "range": self.range._get_serialized() if self.range is not None else None, + "range": range_str, "file": self.file, "ext": self.ext, "infix": self.infix, From b7c3c53b2a238c190682186475d784af6842dd9b Mon Sep 17 00:00:00 2001 From: mafshari64 Date: Mon, 4 Aug 2025 15:41:31 +0200 Subject: [PATCH 15/15] Fix simulation_box usage in simulation.py to resolve ruff F841 error --- lib/python/picongpu/picmi/simulation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/python/picongpu/picmi/simulation.py b/lib/python/picongpu/picmi/simulation.py index 704e89dc26..e24e19dc85 100644 --- a/lib/python/picongpu/picmi/simulation.py +++ b/lib/python/picongpu/picmi/simulation.py @@ -487,7 +487,7 @@ def get_as_pypicongpu(self) -> pypicongpu.simulation.Simulation: raise ValueError("Grid must be a Cartesian3DGrid with defined number_of_cells") s.plugins = [ - entry.get_as_pypicongpu(pypicongpu_by_picmi_species, self.time_step_size, s.time_steps) + entry.get_as_pypicongpu(pypicongpu_by_picmi_species, self.time_step_size, s.time_steps, simulation_box) for entry in self.diagnostics ]