From e48404d0b4c359b6a6b3fb730827472377a06e03 Mon Sep 17 00:00:00 2001 From: Richard Pausch Date: Fri, 26 Sep 2025 17:13:59 +0200 Subject: [PATCH 1/4] add class to evalute c++ density functions from picmi meta-data output --- lib/python/picongpu/extra/utils/__init__.py | 3 +- .../extra/utils/picmi_density_reader.py | 142 ++++++++++++++++++ 2 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 lib/python/picongpu/extra/utils/picmi_density_reader.py diff --git a/lib/python/picongpu/extra/utils/__init__.py b/lib/python/picongpu/extra/utils/__init__.py index 43e18115ba..caa0a0eca7 100644 --- a/lib/python/picongpu/extra/utils/__init__.py +++ b/lib/python/picongpu/extra/utils/__init__.py @@ -2,5 +2,6 @@ from .memory_calculator import MemoryCalculator from .field_ionization import FieldIonization from . import FLYonPICRateCalculationReference +from picmi_density_reader import cpp_fct_reader -__all__ = ["FindTime", "MemoryCalculator", "FieldIonization", "FLYonPICRateCalculationReference"] +__all__ = ["FindTime", "MemoryCalculator", "FieldIonization", "FLYonPICRateCalculationReference", "cpp_fct_reader"] diff --git a/lib/python/picongpu/extra/utils/picmi_density_reader.py b/lib/python/picongpu/extra/utils/picmi_density_reader.py new file mode 100644 index 0000000000..6f677ab472 --- /dev/null +++ b/lib/python/picongpu/extra/utils/picmi_density_reader.py @@ -0,0 +1,142 @@ +import numpy as np +import matplotlib.pyplot as plt +import sympy +import json + + +class cpp_fct_reader: + """class that alllows to evalute the PIConGPU free density + from a c++ code string + """ + + def __init__(self, s, debug=False): + """constructor + s ... string from pypicongpu.json file + debug ... bool for debug output + """ + self.debug = debug + self.cpp_code = s.replace("\n", " ") + self.cpp_code = self.initial_clean(self.cpp_code) + + def evaluate(self, x, symbol="y"): + """evalute expression + x ... float: position [m] where to evaluate the density + symbol ... string: symbol to replace (default: 'y') + """ + return self.inner_evaluate(self.cpp_code, x, symbol=symbol) + 0.0 # add float for cast + + def test_for_cases(self, s): + """internal method checking for c++ cases + s ... string to check + """ + index_first_q = s.find("?") + index_first_c = s.find(":") + if index_first_q == -1 and index_first_c == -1: + return False + else: + return True + + def inner_evaluate(self, s, x, symbol): + """evalute string - this is a recursevly called internal method + s ... string code + x ... float value to evalute density at + symbol ... string symbol to replace + """ + if self.test_for_cases(s): + if self.debug: + print("CASES") + return self.eval_cases(s, x, symbol) + + else: + s_sym = sympy.parsing.sympy_parser.parse_expr(s) + res = s_sym.subs(symbol, x) + if self.debug: + print("eval:", res) + return res + + def eval_cases(self, s, x, symbol): + """handle c++ cases o form cond ? case1 ? case 2 + s ... string code + x ... float value to evalute + symbol ... symbol to replace in string s + """ + if self.debug: + print("-->", s) + s = self.clean_substring(s) + if self.debug: + print("==>", s) + index_first_q = s.find("?") + index_first_c = s.find(":") # this assumes that there are no cases in the first branch + + condition = s[0:index_first_q] + case1 = s[index_first_q + 1 : index_first_c] + case2 = s[index_first_c + 1 :] + + if self.debug: + print("if") + print(condition) + print("do") + print(case1) + print("else") + print(case2) + print("fi") + + if self.inner_evaluate(condition, x, symbol): + return self.inner_evaluate(case1, x, symbol) + else: + return self.inner_evaluate(case2, x, symbol) + + def initial_clean(self, s): + """initially clean string from c++/picongpu methods + making it readably for sympy + s ... string + """ + if self.debug: + print("clean before:", s) + s = s.replace("^", "**") + s = s.replace("pmacc::math::exp", "exp") + s = s.replace("pmacc::math::pow", "pow") + if self.debug: + print("clean after:", s) + return s + + def clean_substring(self, s): + """clean any substrings from cases from paraneties and whitespace + s ... string + """ + while s[0].isspace(): + s = s[1:] + + while s[-1].isspace(): + s = s[:-1] + + if s[0] == "(" and s[-1] == ")": + s = s[1:-1] + return s + + +if __name__ == "__main__": + # load pypicongpu.json, convert json to dict and extract equation for density + file = open("pypicongpu.json") + sim_dict = json.load(file) + density_fct_str = sim_dict["species_initmanager"]["operations"]["simple_density"][0]["profile"]["data"][ + "function_body" + ] + + # create cpp_fct_reader class for later evaluation + reader = cpp_fct_reader(density_fct_str) + + # define positions where to evaluate the density + x_array = np.linspace(0.0, 5.0e-3, 1000) + n_array = np.zeros_like(x_array) + + # evalute density + for i, x in enumerate(x_array): + n_array[i] = reader.evaluate(x) + + # plot density + plt.plot(x_array, n_array) + plt.xlabel(r"$y \, \mathrm{[m]}$") + plt.xlabel(r"$n \, \mathrm{[m^-3]}$") + plt.yscale("log") + plt.show() From 9242eafe3ca18e33b623e5f3f00c72ecfc759221 Mon Sep 17 00:00:00 2001 From: Richard Pausch Date: Wed, 1 Oct 2025 14:44:56 +0200 Subject: [PATCH 2/4] add fix for include and rename after review --- lib/python/picongpu/extra/utils/__init__.py | 4 ++-- .../picongpu/extra/utils/picmi_density_reader.py | 13 +++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/python/picongpu/extra/utils/__init__.py b/lib/python/picongpu/extra/utils/__init__.py index caa0a0eca7..ba0f60107f 100644 --- a/lib/python/picongpu/extra/utils/__init__.py +++ b/lib/python/picongpu/extra/utils/__init__.py @@ -2,6 +2,6 @@ from .memory_calculator import MemoryCalculator from .field_ionization import FieldIonization from . import FLYonPICRateCalculationReference -from picmi_density_reader import cpp_fct_reader +from .picmi_density_reader import CppFctReader -__all__ = ["FindTime", "MemoryCalculator", "FieldIonization", "FLYonPICRateCalculationReference", "cpp_fct_reader"] +__all__ = ["FindTime", "MemoryCalculator", "FieldIonization", "FLYonPICRateCalculationReference", "CppFctReader"] diff --git a/lib/python/picongpu/extra/utils/picmi_density_reader.py b/lib/python/picongpu/extra/utils/picmi_density_reader.py index 6f677ab472..53d6425ef5 100644 --- a/lib/python/picongpu/extra/utils/picmi_density_reader.py +++ b/lib/python/picongpu/extra/utils/picmi_density_reader.py @@ -4,9 +4,13 @@ import json -class cpp_fct_reader: +class CppFctReader: """class that alllows to evalute the PIConGPU free density from a c++ code string + + BE AWARE: the code assumes that: + 1.) a free formular density profile was used in PICMI + 2.) if sympy.Piecewise was used, that the c++ code first case, will not branch """ def __init__(self, s, debug=False): @@ -55,7 +59,7 @@ def inner_evaluate(self, s, x, symbol): return res def eval_cases(self, s, x, symbol): - """handle c++ cases o form cond ? case1 ? case 2 + """handle c++ cases of form condition ? case1 : case 2 s ... string code x ... float value to evalute symbol ... symbol to replace in string s @@ -117,6 +121,7 @@ def clean_substring(self, s): if __name__ == "__main__": # load pypicongpu.json, convert json to dict and extract equation for density + # a pypicongpu.json is created for every PICMI call file = open("pypicongpu.json") sim_dict = json.load(file) density_fct_str = sim_dict["species_initmanager"]["operations"]["simple_density"][0]["profile"]["data"][ @@ -124,7 +129,7 @@ def clean_substring(self, s): ] # create cpp_fct_reader class for later evaluation - reader = cpp_fct_reader(density_fct_str) + reader = CppFctReader(density_fct_str) # define positions where to evaluate the density x_array = np.linspace(0.0, 5.0e-3, 1000) @@ -137,6 +142,6 @@ def clean_substring(self, s): # plot density plt.plot(x_array, n_array) plt.xlabel(r"$y \, \mathrm{[m]}$") - plt.xlabel(r"$n \, \mathrm{[m^-3]}$") + plt.ylabel(r"$n \, \mathrm{[m^-3]}$") plt.yscale("log") plt.show() From 998627264c6b526088142d223b1b17bfa2f7f2eb Mon Sep 17 00:00:00 2001 From: Richard Pausch Date: Tue, 7 Oct 2025 18:05:36 +0200 Subject: [PATCH 3/4] fix file reading - close file Co-authored-by: Tapish Narwal Co-authored-by: Julian Lenz --- .../picongpu/extra/utils/picmi_density_reader.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/python/picongpu/extra/utils/picmi_density_reader.py b/lib/python/picongpu/extra/utils/picmi_density_reader.py index 53d6425ef5..be18a6b170 100644 --- a/lib/python/picongpu/extra/utils/picmi_density_reader.py +++ b/lib/python/picongpu/extra/utils/picmi_density_reader.py @@ -122,11 +122,11 @@ def clean_substring(self, s): if __name__ == "__main__": # load pypicongpu.json, convert json to dict and extract equation for density # a pypicongpu.json is created for every PICMI call - file = open("pypicongpu.json") - sim_dict = json.load(file) - density_fct_str = sim_dict["species_initmanager"]["operations"]["simple_density"][0]["profile"]["data"][ - "function_body" - ] + with open("pypicongpu.json") as file: + sim_dict = json.load(file) + density_fct_str = sim_dict["species_initmanager"]["operations"]["simple_density"][0]["profile"]["data"][ + "function_body" + ] # create cpp_fct_reader class for later evaluation reader = CppFctReader(density_fct_str) From 811e5fb05f45543e1cadd11d654976a68836ec40 Mon Sep 17 00:00:00 2001 From: Richard Pausch Date: Tue, 7 Oct 2025 18:11:54 +0200 Subject: [PATCH 4/4] improve parenthesis-pair remover Co-authored-by: Julian Lenz --- .../picongpu/extra/utils/picmi_density_reader.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/python/picongpu/extra/utils/picmi_density_reader.py b/lib/python/picongpu/extra/utils/picmi_density_reader.py index be18a6b170..d18995ffab 100644 --- a/lib/python/picongpu/extra/utils/picmi_density_reader.py +++ b/lib/python/picongpu/extra/utils/picmi_density_reader.py @@ -98,8 +98,7 @@ def initial_clean(self, s): if self.debug: print("clean before:", s) s = s.replace("^", "**") - s = s.replace("pmacc::math::exp", "exp") - s = s.replace("pmacc::math::pow", "pow") + s = s.replace("pmacc::math::", " ") if self.debug: print("clean after:", s) return s @@ -108,13 +107,11 @@ def clean_substring(self, s): """clean any substrings from cases from paraneties and whitespace s ... string """ - while s[0].isspace(): - s = s[1:] + s.strip() - while s[-1].isspace(): - s = s[:-1] - - if s[0] == "(" and s[-1] == ")": + while s.startswith("("): + if not s.endswith(")"): + raise ValueError(f"Missing closing parenthesis in {s=}.") s = s[1:-1] return s