-
Notifications
You must be signed in to change notification settings - Fork 225
add class to evalute c++ density functions from picmi meta-data output #5490
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from 2 commits
e48404d
9242eaf
9986272
811e5fb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,147 @@ | ||||||||||||||
| import numpy as np | ||||||||||||||
| import matplotlib.pyplot as plt | ||||||||||||||
| import sympy | ||||||||||||||
| import json | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| 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): | ||||||||||||||
| """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 of form condition ? 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 :] | ||||||||||||||
|
Comment on lines
+72
to
+77
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Algorithm for detecting matching parenthesis (adapted for this problem): from itertools import groupby
from sympy import Piecewise
def nesting_level(c):
if c == "?":
return 1
if c == ":":
return -1
return 0
class Counter:
counter = 0
def __add__(self, addition):
self.counter += addition
return self.counter
def parse_branchless(string):
# ... do parsing with guaranteed no ternary operators...
return string
def parse_with_keys(keys_and_expressions):
if len(keys_and_expressions) == 1:
return parse_branchless(keys_and_expressions[0][1].strip("?:() "))
condition = parse_with_keys(keys_and_expressions[:1])
# poor man's index finding because list.index doesn't support key functions
else_index = (
next(
filter(
lambda x: x[1][0] == keys_and_expressions[0][0],
enumerate(keys_and_expressions[1:]),
)
)[0]
+ 1
)
then_expression = parse_with_keys(keys_and_expressions[1:else_index])
else_expression = parse_with_keys(keys_and_expressions[else_index:])
# should be
# return Piecewise([(then_expression, condition), (else_expression, True)])
# but I didn't implement the parse function
return [(then_expression, condition), (else_expression, True)]
def parse(string):
counter = Counter()
return parse_with_keys(
list(
map(
lambda x: (x[0], "".join(x[1])),
groupby(s, lambda c: counter + nesting_level(c)),
)
)
)
if __name__ == "__main__":
# a little demonstration
s = "c>1 ? (c<3 ? 5: 6) : (c<-5 ? 42 : 2)"
print(parse(s))
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will add this tomorrow
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I might need some more time to do so |
||||||||||||||
|
|
||||||||||||||
| 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] | ||||||||||||||
|
||||||||||||||
| while s[0].isspace(): | |
| s = s[1:] | |
| while s[-1].isspace(): | |
| s = s[:-1] | |
| s = s.strip() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks - I forgot about that method :-)
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this needed only a single time? Otherwise we'd need at least a while loop here.
| if s[0] == "(" and s[-1] == ")": | |
| s = s[1:-1] | |
| while s.startswith('('): | |
| if not s.endswith(')'): | |
| raise ValueError(f"Missing closing parenthesis in {s=}.") | |
| s = s[1:-1] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like your code
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing corresponding file close.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a comment how to generate this file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was wondering if you need a call to file close at the end of main to prevent resource leaks.
https://stackoverflow.com/questions/7395542/is-explicitly-closing-files-important
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Valid point
will fix
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| file = open("pypicongpu.json") | |
| sim_dict = json.load(file) | |
| with open("pypicongpu.json") as file: | |
| sim_dict = json.load(file) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done (already suggested by @ikbuibui)
Uh oh!
There was an error while loading. Please reload this page.