From 85180f3ee4ccbf1d3ded899d43b0777a1fe53b85 Mon Sep 17 00:00:00 2001 From: David Grote Date: Tue, 9 Sep 2025 13:53:53 -0700 Subject: [PATCH 1/7] Add example of basic python input file --- Examples/Tests/plasma_lens/CMakeLists.txt | 10 +++ .../plasma_lens/inputs_test_3d_plasma_lens.py | 77 +++++++++++++++++++ .../test_3d_plasma_lens_python.json | 21 +++++ 3 files changed, 108 insertions(+) create mode 100644 Examples/Tests/plasma_lens/inputs_test_3d_plasma_lens.py create mode 100644 Regression/Checksum/benchmarks_json/test_3d_plasma_lens_python.json diff --git a/Examples/Tests/plasma_lens/CMakeLists.txt b/Examples/Tests/plasma_lens/CMakeLists.txt index f6d6ea6daeb..379876e1103 100644 --- a/Examples/Tests/plasma_lens/CMakeLists.txt +++ b/Examples/Tests/plasma_lens/CMakeLists.txt @@ -11,6 +11,16 @@ add_warpx_test( OFF # dependency ) +add_warpx_test( + test_3d_plasma_lens_python # name + 3 # dims + 2 # nprocs + inputs_test_3d_plasma_lens.py # inputs + "analysis.py diags/diag1000084" # analysis + "analysis_default_regression.py --path diags/diag1000084" # checksum + OFF # dependency +) + add_warpx_test( test_3d_plasma_lens_boosted # name 3 # dims diff --git a/Examples/Tests/plasma_lens/inputs_test_3d_plasma_lens.py b/Examples/Tests/plasma_lens/inputs_test_3d_plasma_lens.py new file mode 100644 index 00000000000..caadaaca789 --- /dev/null +++ b/Examples/Tests/plasma_lens/inputs_test_3d_plasma_lens.py @@ -0,0 +1,77 @@ +from pywarpx import ( + Bucket, + Diagnostics, + Particles, + algo, + amr, + boundary, + diagnostics, + geometry, + particles, + picmi, + warpx, +) + +clight = picmi.constants.c +m_e = picmi.constants.m_e +q_e = picmi.constants.q_e + +# Maximum number of time steps +max_step = 84 + +# number of grid points +amr.n_cell = 16, 16, 16 + +amr.max_level = 0 + +# Geometry +geometry.dims = "3" +geometry.prob_lo = -1.0, -1.0, 0.0 # physical domain +geometry.prob_hi = 1.0, 1.0, 2.0 + +boundary.field_lo = "pec", "pec", "pec" +boundary.field_hi = "pec", "pec", "pec" +boundary.particle_lo = "absorbing", "absorbing", "absorbing" +boundary.particle_hi = "absorbing", "absorbing", "absorbing" + +# Algorithms +algo.particle_shape = 1 +warpx.cfl = 0.7 + +vel_z = 0.5 * clight + +# particles +particles.species_names = ("electrons",) + +electrons = Particles.newspecies("electrons") +electrons.charge = -q_e +electrons.mass = m_e +electrons.injection_style = "MultipleParticles" +electrons.multiple_particles_pos_x = 0.05, 0.0 +electrons.multiple_particles_pos_y = 0.0, 0.04 +electrons.multiple_particles_pos_z = 0.05, 0.05 +electrons.multiple_particles_ux = 0.0, 0.0 +electrons.multiple_particles_uy = 0.0, 0.0 +electrons.multiple_particles_uz = vel_z / clight, vel_z / clight +electrons.multiple_particles_weight = 1.0, 1.0 + +particles.E_ext_particle_init_style = "repeated_plasma_lens" +particles.B_ext_particle_init_style = "repeated_plasma_lens" +particles.repeated_plasma_lens_period = 0.5 +particles.repeated_plasma_lens_starts = 0.1, 0.11, 0.12, 0.13 +particles.repeated_plasma_lens_lengths = 0.1, 0.11, 0.12, 0.13 +particles.repeated_plasma_lens_strengths_E = 600000.0, 800000.0, 600000.0, 200000.0 +particles.repeated_plasma_lens_strengths_B = 0.0, 0.0, 0.0, 0.0 + +# Diagnostics +diag1 = Diagnostics.Diagnostic("diag1", _species_dict={}) +diagnostics._diagnostics_dict["diag1"] = diag1 +diag1._electrons = Bucket.Bucket("diag1.electrons") +diag1._species_dict["electrons"] = diag1._electrons + +diag1.intervals = 84 +diag1.diag_type = "Full" +diag1._electrons.variables = "x", "y", "z", "ux", "uy", "uz" + +warpx.init() +warpx.evolve(max_step) diff --git a/Regression/Checksum/benchmarks_json/test_3d_plasma_lens_python.json b/Regression/Checksum/benchmarks_json/test_3d_plasma_lens_python.json new file mode 100644 index 00000000000..6559909fc24 --- /dev/null +++ b/Regression/Checksum/benchmarks_json/test_3d_plasma_lens_python.json @@ -0,0 +1,21 @@ +{ + "lev=0": { + "Bx": 3.742282884162779e-14, + "By": 3.7336535598484914e-14, + "Bz": 3.159003718717131e-16, + "Ex": 4.413173821920794e-06, + "Ey": 4.4408071078056295e-06, + "Ez": 8.994610614847082e-06, + "jx": 2.294712258053488e-10, + "jy": 1.8314117729146456e-10, + "jz": 2.1990787829488006e-08 + }, + "electrons": { + "particle_momentum_x": 7.424668342068345e-24, + "particle_momentum_y": 5.939638944348394e-24, + "particle_momentum_z": 2.730924534454989e-22, + "particle_position_x": 0.036083894319082634, + "particle_position_y": 0.028872102206249608, + "particle_position_z": 3.8947999633253403 + } +} \ No newline at end of file From 32a3ab7085977b087dc7d1bf68e32dd7e0e17dd9 Mon Sep 17 00:00:00 2001 From: David Grote Date: Tue, 9 Sep 2025 14:08:02 -0700 Subject: [PATCH 2/7] Fix input file name --- Examples/Tests/plasma_lens/CMakeLists.txt | 2 +- ...t_3d_plasma_lens.py => inputs_test_3d_plasma_lens_python.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename Examples/Tests/plasma_lens/{inputs_test_3d_plasma_lens.py => inputs_test_3d_plasma_lens_python.py} (100%) diff --git a/Examples/Tests/plasma_lens/CMakeLists.txt b/Examples/Tests/plasma_lens/CMakeLists.txt index 379876e1103..4d7d65d4af6 100644 --- a/Examples/Tests/plasma_lens/CMakeLists.txt +++ b/Examples/Tests/plasma_lens/CMakeLists.txt @@ -15,7 +15,7 @@ add_warpx_test( test_3d_plasma_lens_python # name 3 # dims 2 # nprocs - inputs_test_3d_plasma_lens.py # inputs + inputs_test_3d_plasma_lens_python.py # inputs "analysis.py diags/diag1000084" # analysis "analysis_default_regression.py --path diags/diag1000084" # checksum OFF # dependency diff --git a/Examples/Tests/plasma_lens/inputs_test_3d_plasma_lens.py b/Examples/Tests/plasma_lens/inputs_test_3d_plasma_lens_python.py similarity index 100% rename from Examples/Tests/plasma_lens/inputs_test_3d_plasma_lens.py rename to Examples/Tests/plasma_lens/inputs_test_3d_plasma_lens_python.py From 1bb1fd47a03337371ace2bfa18d5988c59675dc1 Mon Sep 17 00:00:00 2001 From: David Grote Date: Tue, 16 Sep 2025 15:17:53 -0700 Subject: [PATCH 3/7] Cleanup the interface some --- .../inputs_test_3d_plasma_lens_python.py | 12 +++-------- Python/pywarpx/Bucket.py | 4 ++++ Python/pywarpx/Diagnostics.py | 20 +++++++++++++++++++ Python/pywarpx/Particles.py | 10 ++++++++-- Python/pywarpx/__init__.py | 2 +- 5 files changed, 36 insertions(+), 12 deletions(-) diff --git a/Examples/Tests/plasma_lens/inputs_test_3d_plasma_lens_python.py b/Examples/Tests/plasma_lens/inputs_test_3d_plasma_lens_python.py index caadaaca789..ea774f7a47a 100644 --- a/Examples/Tests/plasma_lens/inputs_test_3d_plasma_lens_python.py +++ b/Examples/Tests/plasma_lens/inputs_test_3d_plasma_lens_python.py @@ -1,11 +1,9 @@ from pywarpx import ( - Bucket, Diagnostics, Particles, algo, amr, boundary, - diagnostics, geometry, particles, picmi, @@ -43,7 +41,7 @@ # particles particles.species_names = ("electrons",) -electrons = Particles.newspecies("electrons") +electrons = Particles.new_species("electrons") electrons.charge = -q_e electrons.mass = m_e electrons.injection_style = "MultipleParticles" @@ -64,14 +62,10 @@ particles.repeated_plasma_lens_strengths_B = 0.0, 0.0, 0.0, 0.0 # Diagnostics -diag1 = Diagnostics.Diagnostic("diag1", _species_dict={}) -diagnostics._diagnostics_dict["diag1"] = diag1 -diag1._electrons = Bucket.Bucket("diag1.electrons") -diag1._species_dict["electrons"] = diag1._electrons - +diag1 = Diagnostics.new_diagnostic("diag1") diag1.intervals = 84 diag1.diag_type = "Full" -diag1._electrons.variables = "x", "y", "z", "ux", "uy", "uz" +diag1.electrons.variables = "x", "y", "z", "ux", "uy", "uz" warpx.init() warpx.evolve(max_step) diff --git a/Python/pywarpx/Bucket.py b/Python/pywarpx/Bucket.py index 9945b661cac..dba06da6b36 100644 --- a/Python/pywarpx/Bucket.py +++ b/Python/pywarpx/Bucket.py @@ -79,6 +79,10 @@ def attrlist(self): rhs = " ".join(map(lambda s: f"{s}", value)) elif isinstance(value, bool): rhs = 1 if value else 0 + elif isinstance(value, Bucket): + subresult = value.attrlist() + result.extend(subresult) + continue else: rhs = value attrstring = f"{self.instancename}.{attr} = {rhs}" diff --git a/Python/pywarpx/Diagnostics.py b/Python/pywarpx/Diagnostics.py index 731d1d31d01..862b975902d 100644 --- a/Python/pywarpx/Diagnostics.py +++ b/Python/pywarpx/Diagnostics.py @@ -5,11 +5,18 @@ # License: BSD-3-Clause-LBNL from .Bucket import Bucket +from .Particles import valid_species diagnostics = Bucket("diagnostics", _diagnostics_dict={}) reduced_diagnostics = Bucket("warpx", _diagnostics_dict={}) +def new_diagnostic(name): + diag = Diagnostic(name, _species_dict={}) + diagnostics._diagnostics_dict[name] = diag + return diag + + class Diagnostic(Bucket): """ This is the same as a Bucket, but checks that any attributes are always given the same value. @@ -27,6 +34,19 @@ def add_new_attr_with_check(self, name, value): ) self.argvattrs[name] = value + def __getattr__(self, name): + try: + return Bucket.__getattr__(self, name) + except AttributeError: + # Create a new attibute if the name is a valid species name + if not valid_species(name): + raise AttributeError( + "Only valid species names can be added as new attributes" + ) + new = Bucket(f"{self.instancename}.{name}") + self.argvattrs[name] = new + return new + def __setattr__(self, name, value): self.add_new_attr_with_check(name, value) diff --git a/Python/pywarpx/Particles.py b/Python/pywarpx/Particles.py index 9c87bb7083e..a614f3b5d50 100644 --- a/Python/pywarpx/Particles.py +++ b/Python/pywarpx/Particles.py @@ -8,10 +8,16 @@ particles = Bucket("particles", species_names=[], rigid_injected_species=[]) particles_list = [] -particle_dict = {} -def newspecies(name): +def new_species(name): result = Bucket(name) particles_list.append(result) return result + + +def valid_species(name): + for sp in particles_list: + if sp.instancename == name: + return True + return False diff --git a/Python/pywarpx/__init__.py b/Python/pywarpx/__init__.py index 7c276e40aa4..8376a9beb28 100644 --- a/Python/pywarpx/__init__.py +++ b/Python/pywarpx/__init__.py @@ -37,7 +37,7 @@ from .Interpolation import interpolation # noqa from .Lasers import lasers # noqa from .LoadThirdParty import load_cupy # noqa -from .Particles import newspecies, particles # noqa +from .Particles import new_species, particles # noqa from .PSATD import psatd # noqa from .WarpX import warpx # noqa From 99b67768fa1e09a9e107d2b6d91d582d7975414e Mon Sep 17 00:00:00 2001 From: David Grote Date: Tue, 16 Sep 2025 15:20:39 -0700 Subject: [PATCH 4/7] geometry.dims is now always converted to a str --- Python/pywarpx/_libwarpx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/pywarpx/_libwarpx.py b/Python/pywarpx/_libwarpx.py index 98c10f7a8c4..5bb6b1727ee 100755 --- a/Python/pywarpx/_libwarpx.py +++ b/Python/pywarpx/_libwarpx.py @@ -75,7 +75,7 @@ def load_library(self): # --- The geometry must be setup before the lib warpx shared object can be loaded. try: _prob_lo = geometry.prob_lo - _dims = geometry.dims + _dims = str(geometry.dims) except AttributeError: raise Exception( "The shared object could not be loaded. The geometry must be setup before the WarpX pybind11 module can be accessesd. The geometry determines which version of the shared object to load." From a97f042bb7e213fb275df21840ba7b38fe371025 Mon Sep 17 00:00:00 2001 From: David Grote Date: Tue, 16 Sep 2025 15:21:18 -0700 Subject: [PATCH 5/7] Use integer for geometry.dims in example --- Examples/Tests/plasma_lens/inputs_test_3d_plasma_lens_python.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/Tests/plasma_lens/inputs_test_3d_plasma_lens_python.py b/Examples/Tests/plasma_lens/inputs_test_3d_plasma_lens_python.py index ea774f7a47a..9e41e46d0de 100644 --- a/Examples/Tests/plasma_lens/inputs_test_3d_plasma_lens_python.py +++ b/Examples/Tests/plasma_lens/inputs_test_3d_plasma_lens_python.py @@ -23,7 +23,7 @@ amr.max_level = 0 # Geometry -geometry.dims = "3" +geometry.dims = 3 geometry.prob_lo = -1.0, -1.0, 0.0 # physical domain geometry.prob_hi = 1.0, 1.0, 2.0 From c10600a1b64f866f4526badbdaa1dfb0e5d09d9a Mon Sep 17 00:00:00 2001 From: David Grote Date: Tue, 16 Sep 2025 15:54:32 -0700 Subject: [PATCH 6/7] Add new Particles class that allows injection source attributes --- Python/pywarpx/Particles.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Python/pywarpx/Particles.py b/Python/pywarpx/Particles.py index a614f3b5d50..b25b96d9cb4 100644 --- a/Python/pywarpx/Particles.py +++ b/Python/pywarpx/Particles.py @@ -11,7 +11,7 @@ def new_species(name): - result = Bucket(name) + result = Species(name) particles_list.append(result) return result @@ -21,3 +21,18 @@ def valid_species(name): if sp.instancename == name: return True return False + + +class Species(Bucket): + def __getattr__(self, name): + try: + return Bucket.__getattr__(self, name) + except AttributeError: + # Create a new attibute if the name is a valid injection source name + if name not in self.injection_sources: + raise AttributeError( + "Only valid injection source names can be added as new attributes" + ) + new = Bucket(f"{self.instancename}.{name}") + self.argvattrs[name] = new + return new From ba48854d9e94b316657ff2edb854cd19f9477f22 Mon Sep 17 00:00:00 2001 From: David Grote Date: Tue, 16 Sep 2025 15:55:49 -0700 Subject: [PATCH 7/7] Remove obsolete code from WarpX.py --- Python/pywarpx/WarpX.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/Python/pywarpx/WarpX.py b/Python/pywarpx/WarpX.py index e506d295843..92026ab24f5 100644 --- a/Python/pywarpx/WarpX.py +++ b/Python/pywarpx/WarpX.py @@ -8,7 +8,6 @@ import re import sys -from . import Particles from ._libwarpx import libwarpx from .Algo import algo from .Amr import amr @@ -52,21 +51,6 @@ def create_argv_list(self, **kw): argv += psatd.attrlist() argv += eb2.attrlist() - # --- Search through species_names and add any predefined particle objects in the list. - particles_list_names = [p.instancename for p in particles_list] - for pstring in particles.species_names: - if pstring in particles_list_names: - # --- The species is already included in particles_list - continue - elif hasattr(Particles, pstring): - # --- Add the predefined species to particles_list - particles_list.append(getattr(Particles, pstring)) - particles_list_names.append(pstring) - else: - raise Exception( - "Species %s listed in species_names not defined" % pstring - ) - argv += particles.attrlist() for particle in particles_list: argv += particle.attrlist()