88import numexpr as ne
99import numpy as np
1010import plotly .express as px
11+
1112from fourc_webviewer .input_file_utils .io_utils import (
1213 get_variable_data_by_name_in_funct_item ,
1314)
1415
15-
1616# functional expressions / constants known by 4C, that are replaced by the numpy counterpart during evaluation
1717DEF_FUNCT = ["exp" , "sqrt" , "log" , "sin" , "cos" , "tan" , "heaviside" , "pi" ]
1818
1919
2020def get_variable_names_in_funct_expression (funct_expression : str ):
21- """Returns all variable names present in a functional expression, using regular expressions."""
21+ """Returns all variable names present in a functional expression, using
22+ regular expressions."""
2223 vars_found = re .findall (r"[A-Za-z_]+" , funct_expression )
2324 return [
2425 v for v in vars_found if v not in DEF_FUNCT and v not in ["t" , "x" , "y" , "z" ]
@@ -159,48 +160,54 @@ def funct_using_eval(x, y, z, t):
159160 Returns:
160161 parsed object using ast.literal_eval
161162 """
162- # funct_string copy
163+ # Create a safe environment
164+ safe_dict = {
165+ "x" : x ,
166+ "y" : y ,
167+ "z" : z ,
168+ "t" : t ,
169+ "sin" : np .sin ,
170+ "cos" : np .cos ,
171+ "exp" : np .exp ,
172+ "log" : np .log ,
173+ "sqrt" : np .sqrt ,
174+ "where" : np .where ,
175+ "pi" : np .pi ,
176+ # "heaviside": np.heaviside, -> no heaviside, numexpr will
177+ # not deal with this
178+ # add other safe functions as needed
179+ }
180+
163181 funct_string_copy = funct_string
164182
165- # replace variables by their functional expressions
183+ # replace variables by their
166184 for k , v in variable_funct_strings .items ():
167185 funct_string_copy = re .sub (
168186 rf"(?<![A-Za-z]){ k } (?![A-Za-z])" , v , funct_string_copy
169187 )
170188
171- # replace the defined functions in the funct_string with "<DEF_FUNCT>"
172- for i in range (len (DEF_FUNCT )):
173- funct_string_copy = funct_string_copy .replace (
174- DEF_FUNCT [i ], f"np.{ DEF_FUNCT [i ]} "
175- )
176-
177- # replace the used power sign
189+ # replace heaviside functions with where / np.where
178190 funct_string_copy = funct_string_copy .replace ("^" , "**" )
179-
180- # replace variables
181- funct_string_copy = (
182- funct_string_copy .replace ("x" , str (x ))
183- .replace ("y" , str (y ))
184- .replace ("z" , str (z ))
185- .replace ("t" , str (t ))
191+ funct_string_copy = re .sub (
192+ r"heaviside\(([^),]+)\)" , r"where(\1 >= 0, 1, 0)" , funct_string_copy
186193 )
187-
188- # for heaviside: np.heaviside takes two arguments -> second argument denotes the function value at the first argument -> we set it by default to 0
189194 funct_string_copy = re .sub (
190- r"heaviside\((.*?)\)" , r"heaviside(\1,0)" , funct_string_copy
191- ) # usage of raw strings, (.*?) is a non greedy capturing, and \1 replaces the captured value
195+ r"heaviside\(([^),]+),\s*([^)]+)\)" ,
196+ r"where(\1 > 0, 1, where(\1 == 0, \2, 0))" ,
197+ funct_string_copy ,
198+ )
192199
193- return eval (
194- funct_string_copy , {"np" : np }, {}
195- ) # this parses string in as a function
200+ # Numexpr evaluation (much safer)
201+ return ne .evaluate (funct_string_copy , local_dict = safe_dict )
196202
197203 return np .frompyfunc (funct_using_eval , 4 , 1 )
198204
199205
200206def construct_funct_string_from_variable_data (
201207 variable_name : str , funct_section_item : dict
202208):
203- """Constructs a functional string from the given data for a function variable."""
209+ """Constructs a functional string from the given data for a function
210+ variable."""
204211
205212 # retrieve variable data
206213 variable_data = get_variable_data_by_name_in_funct_item (
@@ -212,8 +219,9 @@ def construct_funct_string_from_variable_data(
212219 match variable_data ["TYPE" ]:
213220 case "linearinterpolation" :
214221 # get times and values
215- times , values = np .array (variable_data ["TIMES" ]), np .array (
216- variable_data ["VALUES" ]
222+ times , values = (
223+ np .array (variable_data ["TIMES" ]),
224+ np .array (variable_data ["VALUES" ]),
217225 )
218226
219227 # consistency check: time should start with 0.0
@@ -226,7 +234,7 @@ def construct_funct_string_from_variable_data(
226234 if time_instant_index != 0 :
227235 funct_string += "+"
228236
229- funct_string += f"({ values [time_instant_index ]} +({ values [time_instant_index + 1 ]} -{ values [time_instant_index ]} )/({ times [time_instant_index + 1 ]} -{ time_instant } )*(t-{ time_instant } ))*heaviside(t-{ time_instant } )*heaviside({ times [time_instant_index + 1 ]} -t)"
237+ funct_string += f"({ values [time_instant_index ]} +({ values [time_instant_index + 1 ]} -{ values [time_instant_index ]} )/({ times [time_instant_index + 1 ]} -{ time_instant } )*(t-{ time_instant } ))*heaviside(t-{ time_instant } )*heaviside({ times [time_instant_index + 1 ]} -t)"
230238
231239 funct_string += ")"
232240
@@ -247,7 +255,7 @@ def construct_funct_string_from_variable_data(
247255 if time_instant_index != 0 :
248256 funct_string += "+"
249257
250- funct_string += f"({ descriptions [time_instant_index ]} *heaviside(t-{ time_instant } )*heaviside({ times [time_instant_index + 1 ]} -t))"
258+ funct_string += f"({ descriptions [time_instant_index ]} *heaviside(t-{ time_instant } )*heaviside({ times [time_instant_index + 1 ]} -t))"
251259
252260 funct_string += ")"
253261
0 commit comments