diff --git a/lib/python/picongpu/picmi/requirements.txt b/lib/python/picongpu/picmi/requirements.txt index 9462d872c4c..a8e4ad85f87 100644 --- a/lib/python/picongpu/picmi/requirements.txt +++ b/lib/python/picongpu/picmi/requirements.txt @@ -4,4 +4,5 @@ sympy >= 1.9 chevron >= 0.13.1 jsonschema == 4.17.3 scipy >= 1.7.1 +pydantic >= 2.6.4 picmistandard >= 0.27.0 diff --git a/lib/python/picongpu/picmi/simulation.py b/lib/python/picongpu/picmi/simulation.py index 8fa2ebcaf25..6de667c0bb9 100644 --- a/lib/python/picongpu/picmi/simulation.py +++ b/lib/python/picongpu/picmi/simulation.py @@ -1,11 +1,11 @@ """ This file is part of the PIConGPU. -Copyright 2021-2023 PIConGPU contributors +Copyright 2021-2024 PIConGPU contributors Authors: Hannes Troepgen, Brian Edward Marre License: GPLv3+ """ -from ..pypicongpu import simulation, runner, util, species +from ..pypicongpu import simulation, runner, util, species, movingwindow from . import constants from .grid import Cartesian3DGrid from .species import Species as PicongpuPicmiSpecies @@ -99,6 +99,8 @@ def __init__( self, picongpu_template_dir: typing.Optional[typing.Union[str, pathlib.Path]] = None, picongpu_typical_ppc: typing.Optional[int] = None, + picongpu_moving_window_move_point: typing.Optional[float] = None, + picongpu_moving_window_stop_iteration: typing.Optional[int] = None, **kw, ): # delegate actual work to parent @@ -119,6 +121,9 @@ def __init__( assert template_path.is_dir(), "picongpu_template_dir must be existing dir" self.picongpu_template_dir = str(template_path) + self.moving_window_move_point = picongpu_moving_window_move_point + self.moving_window_stop_iteration = picongpu_moving_window_stop_iteration + self.picongpu_typical_ppc = picongpu_typical_ppc # store runner state @@ -441,6 +446,13 @@ def get_as_pypicongpu(self) -> simulation.Simulation: # todo: check grid compatibility s.grid = self.solver.grid.get_as_pypicongpu() + if self.moving_window_move_point is None: + s.moving_window = None + else: + s.moving_window = movingwindow.MovingWindow( + move_point=self.moving_window_move_point, stop_iteration=self.moving_window_stop_iteration + ) + # any injection method != None is not supported if len(self.laser_injection_methods) != self.laser_injection_methods.count(None): util.unsupported("laser injection method", self.laser_injection_methods, []) diff --git a/lib/python/picongpu/pypicongpu/movingwindow.py b/lib/python/picongpu/pypicongpu/movingwindow.py new file mode 100644 index 00000000000..377d081b50c --- /dev/null +++ b/lib/python/picongpu/pypicongpu/movingwindow.py @@ -0,0 +1,36 @@ +""" +This file is part of the PIConGPU. +Copyright 2024 PIConGPU contributors +Authors: Brian Edward Marre +License: GPLv3+ +""" + +import pydantic +import typing + +from .rendering import RenderedObject + + +class MovingWindow(RenderedObject, pydantic.BaseModel): + move_point: float + """ + point a light ray reaches in y from the left border until we begin sliding the simulation window with the speed of + light + + in multiples of the simulation window size + + @attention if moving window is active, one gpu in y direction is reserved for initializing new spaces, + thereby reducing the simulation window size according + """ + + stop_iteration: typing.Optional[int] + """iteration, at which to stop moving the simulation window""" + + def check(self) -> None: + if self.move_point < 0.0: + raise ValueError("window_move point must be >= 0.") + if self.stop_iteration <= 0: + raise ValueError("stop iteration must be > 0.") + + def _get_serialized(self) -> dict: + return {"move_point": self.move_point, "stop_iteration": self.stop_iteration} diff --git a/lib/python/picongpu/pypicongpu/simulation.py b/lib/python/picongpu/pypicongpu/simulation.py index fa7521d3139..a106569222d 100644 --- a/lib/python/picongpu/pypicongpu/simulation.py +++ b/lib/python/picongpu/pypicongpu/simulation.py @@ -7,6 +7,7 @@ from .grid import Grid3D from .laser import GaussianLaser +from .movingwindow import MovingWindow from .solver import Solver from . import species from . import util @@ -62,6 +63,9 @@ class Simulation(RenderedObject): @attention custom user input is global to the simulation """ + moving_window = util.build_typesafe_property(typing.Optional[MovingWindow]) + """used moving Window, set to None to disable""" + def __get_output_context(self) -> dict: """retrieve all output objects""" auto = output.Auto() @@ -129,6 +133,11 @@ def _get_serialized(self) -> dict: else: serialized["laser"] = None + if self.moving_window is not None: + serialized["moving_window"] = self.moving_window.get_rendering_context() + else: + serialized["moving_window"] = None + if self.custom_user_input is not None: serialized["customuserinput"] = self.__render_custom_user_input_list() self.__foundCustomInput(serialized) diff --git a/share/ci/pypicongpu_generator.py b/share/ci/pypicongpu_generator.py index 46fe9d390aa..dad870c589a 100644 --- a/share/ci/pypicongpu_generator.py +++ b/share/ci/pypicongpu_generator.py @@ -293,6 +293,7 @@ def print_job_yaml(test_pkg_versions: Dict[str, List[str]]): "typeguard": get_all_major_pypi_versions, "jsonschema": get_all_pypi_versions, # @todo change back, Brian Marre, 2023 "picmistandard": get_all_pypi_versions, + "pydantic": get_all_major_pypi_versions, } if __name__ == "__main__": diff --git a/share/picongpu/pypicongpu/schema/movingWindowVelocity.json b/share/picongpu/pypicongpu/schema/movingWindowVelocity.json deleted file mode 100644 index a2d55d9a748..00000000000 --- a/share/picongpu/pypicongpu/schema/movingWindowVelocity.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "$id": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.movingWindowVelocity", - "type": "object", - "description": "Describes the velocity of the moving window in the simulation.", - "unevaluatedProperties": false, - "required": ["x", "y", "z"], - "properties": { - "x": { - "type": "number", - "exclusiveMinimum" : 0 - }, - "y": { - "type": "number", - "exclusiveMinimum" : 0 - }, - "z": { - "type": "number", - "exclusiveMinimum" : 0 - } - } -} diff --git a/share/picongpu/pypicongpu/schema/movingwindow.MovingWindow.json b/share/picongpu/pypicongpu/schema/movingwindow.MovingWindow.json new file mode 100644 index 00000000000..3606ab3e06a --- /dev/null +++ b/share/picongpu/pypicongpu/schema/movingwindow.MovingWindow.json @@ -0,0 +1,24 @@ +{ + "$id": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.movingwindow.MovingWindow", + "type": "object", + "description": "moving window setting of picongpu", + "unevaluatedProperties": false, + "required": ["move_point", "stop_iteration"], + "properties": { + "move_point": { + "type": "number", + "minimum" : 0 + }, + "stop_iteration": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "integer", + "exclusiveMinimum" : 0 + } + ] + } + } +} diff --git a/share/picongpu/pypicongpu/schema/simulation.Simulation.json b/share/picongpu/pypicongpu/schema/simulation.Simulation.json index 9950414aa5b..6cea40f9fcf 100644 --- a/share/picongpu/pypicongpu/schema/simulation.Simulation.json +++ b/share/picongpu/pypicongpu/schema/simulation.Simulation.json @@ -33,13 +33,13 @@ } ] }, - "moving_window_velocity": { + "moving_window": { "anyOf": [ { "type": "null" }, { - "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.movingWindowVelocity" + "$ref": "https://registry.hzdr.de/crp/picongpu/schema/picongpu.pypicongpu.movingwindow.MovingWindow" } ] }, @@ -81,7 +81,7 @@ "solver", "grid", "laser", - "moving_window_velocity", + "moving_window", "customuserinput" ], "unevaluatedProperties": false diff --git a/share/picongpu/pypicongpu/template/etc/picongpu/N.cfg.mustache b/share/picongpu/pypicongpu/template/etc/picongpu/N.cfg.mustache index 724c5d79784..6c4e4b00b56 100644 --- a/share/picongpu/pypicongpu/template/etc/picongpu/N.cfg.mustache +++ b/share/picongpu/pypicongpu/template/etc/picongpu/N.cfg.mustache @@ -1,4 +1,5 @@ -# Copyright 2013-2023 Axel Huebl, Rene Widera, Felix Schmitt, Franz Poeschel +# Copyright 2013-2023 Axel Huebl, Rene Widera, Felix Schmitt, Franz Poeschel, +# Hannes Tropegen, Brian Marre # # This file is part of PIConGPU. # @@ -60,15 +61,13 @@ TBG_wallTime="1:00:00" TBG_steps="{{{time_steps}}}" -{{#grid.boundary_condition}} - TBG_periodic="--periodic {{{x}}} {{{y}}} {{{z}}}" -{{/grid.boundary_condition}} - - ################################# ## Section: Optional Variables ## ################################# +{{#grid.boundary_condition}} + TBG_periodic="--periodic {{{x}}} {{{y}}} {{{z}}}" +{{/grid.boundary_condition}} {{#output.auto}} # only use charge conservation if solver is yee AND using cuda backend @@ -80,6 +79,14 @@ else fi {{/output.auto}} +{{#moving_window}} + TBG_movingWindow="-m" + TBG_windowMovePoint="--windowMovePoint {{{move_point}}}" + {{#stop_iteration}} + TBG_stopWindow="--stopWindow {{{stop_iteration}}}" + {{/stop_iteration}} +{{/moving_window}} + pypicongpu_output_with_newlines=" {{#output.auto}} --fields_energy.period {{{period}}} @@ -114,23 +121,26 @@ TBG_pypicongpu_output=$(sed -z 's/\n/ /g' <<< "$pypicongpu_output_with_newlines" TBG_plugins="!TBG_pypicongpu_output" + + ################################# ## Section: Program Parameters ## ################################# TBG_deviceDist="!TBG_devices_x !TBG_devices_y !TBG_devices_z" -TBG_programParams=" -{{#moving_window_velocity}} - --moving_window_velocity_X = {{{x}}} \ - --moving_window_velocity_Y = {{{y}}} \ - --moving_window_velocity_Z = {{{z}}} \ -{{/moving_window_velocity}} - -d !TBG_deviceDist \ - -g !TBG_gridSize \ - -s !TBG_steps \ - !TBG_periodic \ - !TBG_plugins \ +TBG_programParams="-d !TBG_deviceDist \ + -g !TBG_gridSize \ + -s !TBG_steps \ + !TBG_periodic \ + !TBG_plugins \ + {{#moving_window}} + !TBG_movingWindow \ + !TBG_windowMovePoint \ + {{#stop_iteration}} + !TBG_stopWindow \ + {{/stop_iteration}} + {{/moving_window}} --versionOnce" # TOTAL number of devices diff --git a/test/python/picongpu/compiling/simulation.py b/test/python/picongpu/compiling/simulation.py index db2814b89b0..4c81945885a 100644 --- a/test/python/picongpu/compiling/simulation.py +++ b/test/python/picongpu/compiling/simulation.py @@ -8,7 +8,6 @@ from picongpu import pypicongpu, picmi import typeguard - import unittest import tempfile import os @@ -29,6 +28,17 @@ def get_grid(delta_x: float, delta_y: float, delta_z: float, n: int): class TestSimulation(unittest.TestCase): + def _set_up_sim(self, **kw): + grid = picmi.Cartesian3DGrid( + number_of_cells=[192, 2048, 12], + lower_bound=[0, 0, 0], + upper_bound=[3.40992e-5, 9.07264e-5, 2.1312e-6], + lower_boundary_conditions=["open", "open", "periodic"], + upper_boundary_conditions=["open", "open", "periodic"], + ) + solver = picmi.ElectromagneticSolver(method="Yee", grid=grid) + return picmi.Simulation(time_step_size=1.39e-16, max_steps=int(2048), solver=solver, **kw) + def test_minimal(self): """smallest possible example""" sim = pypicongpu.Simulation() @@ -48,6 +58,7 @@ def test_minimal(self): sim.grid.boundary_condition_z = pypicongpu.grid.BoundaryCondition.PERIODIC sim.laser = None sim.custom_user_input = None + sim.moving_window = None sim.solver = pypicongpu.solver.YeeSolver() sim.init_manager = pypicongpu.species.InitManager() @@ -56,6 +67,12 @@ def test_minimal(self): runner.build() runner.run() + def test_moving_window(self): + picmi_sim = self._set_up_sim(picongpu_moving_window_move_point=0.9) + runner = pypicongpu.Runner(picmi_sim) + runner.generate(printDirToConsole=True) + runner.build() + def test_custom_template_dir(self): """may pass custom template dir""" diff --git a/test/python/picongpu/quick/picmi/simulation.py b/test/python/picongpu/quick/picmi/simulation.py index fcbfb2bf56d..5c630b291e7 100644 --- a/test/python/picongpu/quick/picmi/simulation.py +++ b/test/python/picongpu/quick/picmi/simulation.py @@ -342,6 +342,24 @@ def test_operation_momentum(self): ) self.assertAlmostEqual(1.491037242289643, mom_op.drift.gamma) + def test_moving_window(self): + """test that the user may set moving window""" + grid = picmi.Cartesian3DGrid( + number_of_cells=[192, 2048, 12], + lower_bound=[0, 0, 0], + upper_bound=[3.40992e-5, 9.07264e-5, 2.1312e-6], + lower_boundary_conditions=["open", "open", "periodic"], + upper_boundary_conditions=["open", "open", "periodic"], + ) + solver = picmi.ElectromagneticSolver(method="Yee", grid=grid) + sim = picmi.Simulation( + time_step_size=1.39e-16, max_steps=int(2048), solver=solver, picongpu_moving_window_move_point=0.9 + ) + pypic = sim.get_as_pypicongpu() + + self.assertAlmostEqual(pypic.moving_window.move_point, 0.9) + self.assertEqual(pypic.moving_window.stop_iteration, None) + def test_ionization_electron_explicit(self): """electrons for ionization can be specified explicitly""" # note: the difficulty here is preserving the PICMI- -> PICMI-object diff --git a/test/python/picongpu/quick/pypicongpu/simulation.py b/test/python/picongpu/quick/pypicongpu/simulation.py index ed3ea608f85..bbde00019cc 100644 --- a/test/python/picongpu/quick/pypicongpu/simulation.py +++ b/test/python/picongpu/quick/pypicongpu/simulation.py @@ -45,6 +45,7 @@ def setUp(self): self.s.solver = solver.YeeSolver() self.s.laser = None self.s.custom_user_input = None + self.s.moving_window = None self.s.init_manager = species.InitManager() self.laser = GaussianLaser()