jsongrapher 1.6__py3-none-any.whl → 3.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,670 @@
1
+ from sympy import symbols, Eq, solve, sympify
2
+ from pint import UnitRegistry
3
+ import re
4
+
5
+ ureg = UnitRegistry()
6
+
7
+ def parse_equation(equation_str, variables):
8
+ """Replace variable names and standalone numbers with their magnitudes and formatted units."""
9
+ def detect_and_format_units(equation_str):
10
+ """Detect standalone numbers with units and format them correctly."""
11
+ pattern = r"(\d+(\.\d+)?)\s*([a-zA-Z]+)"
12
+ matches = re.findall(pattern, equation_str)
13
+ # Explanation of regular expression parts
14
+ # (\d+(\.\d+)?)
15
+ # \d+ → Matches one or more digits (e.g., "10", "100", "3").
16
+ # (\.\d+)? → Matches optional decimal values (e.g., "10.5", "3.14").
17
+ # This entire part captures numerical values, whether integers or decimals.
18
+ # \s*
19
+ # Matches zero or more spaces between the number and the unit.
20
+ # Ensures flexibility in formatting (e.g., "10m" vs. "10 m").
21
+ # ([a-zA-Z]+)
22
+ # Matches one or more alphabetical characters, capturing the unit symbol.
23
+ # Ensures that only valid unit names (e.g., "m", "s", "kg") are recognized.
24
+ # Example Matches
25
+ # "10 m" → ("10", "", "m")
26
+ # "3.5 kg" → ("3.5", ".5", "kg")
27
+ # "100s" → ("100", "", "s")
28
+ for match in matches:
29
+ magnitude = match[0] # Extract number
30
+ unit = match[2] # Extract unit
31
+ try:
32
+ quantity = ureg(f"{magnitude} {unit}") # Convert to Pint quantity
33
+ formatted_unit = str(quantity.units)
34
+ equation_str = equation_str.replace(f"{magnitude} {unit}", f"({quantity.magnitude} * {formatted_unit})")
35
+ except: #This comment is so that VS code pylint will not flag this line: pylint: disable=bare-except
36
+ pass # Ignore invalid unit conversions
37
+ return equation_str
38
+
39
+
40
+ # Sort variable names by length in descending order
41
+ variables_sorted_by_name = sorted(variables.items(), key=lambda x: -len(x[0]))
42
+
43
+ # Need to first replace the constants because they could be like "letter e"
44
+ # and could mess up the string after the units are added in.
45
+ # Also need to sort them by length and replace longer ones first because
46
+ # we could have "Ea" and "a", for example.
47
+ for var_name, var_value in variables_sorted_by_name: # Removed `.items()`
48
+ if not hasattr(var_value, "magnitude"): # For constants like "e" with no units
49
+ equation_str = equation_str.replace(var_name, str(var_value)) # Directly use plain values
50
+
51
+ # Replace variables with their magnitudes and units
52
+ for var_name, var_value in variables_sorted_by_name: # Removed `.items()`
53
+ if hasattr(var_value, "magnitude"): # Ensure it has a magnitude attribute
54
+ magnitude = var_value.magnitude
55
+ unit = str(var_value.units)
56
+ equation_str = equation_str.replace(var_name, f"({magnitude} * {unit})")
57
+
58
+ # Detect and format standalone numbers with units, like "10 m"
59
+ equation_str = detect_and_format_units(equation_str)
60
+ return equation_str
61
+
62
+ def solve_equation(equation_string, independent_variables_values_and_units, dependent_variable):
63
+ """
64
+ Solve for the specified dependent variable in terms of multiple independent variables.
65
+ # # Example usage
66
+ # independent_variables_values_and_units = {
67
+ # "x": "2 m / s",
68
+ # "y": "3 meter"
69
+ # }
70
+ # equation_string = "x * t + y = 10 m"
71
+ # solve_equation(equation_string, independent_variables_values_and_units, dependent_variable="t")
72
+ # It will solve for the value of t.
73
+ # What is returned is a list of solutions.
74
+ # if there is any "^" in the equation, it will be changed to **
75
+
76
+ """
77
+ # Convert string inputs into Pint quantities
78
+ variables = {name: ureg(value) for name, value in independent_variables_values_and_units.items()}
79
+ independent_variables = list(independent_variables_values_and_units.keys())
80
+ # Explicitly define symbolic variables
81
+ symbols_dict = {var: symbols(var) for var in independent_variables_values_and_units.keys()}
82
+ for var in independent_variables:
83
+ symbols_dict[var] = symbols(var)
84
+ symbols_dict[dependent_variable] = symbols(dependent_variable)
85
+
86
+ #change any "^" into "**"
87
+ equation_string = equation_string.replace("^","**")
88
+ # Split the equation into left-hand and right-hand sides
89
+ lhs, rhs = equation_string.split("=")
90
+
91
+ # Convert both sides to SymPy expressions
92
+ lhs_sympy = sympify(parse_equation(lhs.strip(), variables), locals=symbols_dict, evaluate=False)
93
+ rhs_sympy = sympify(parse_equation(rhs.strip(), variables), locals=symbols_dict, evaluate=False)
94
+
95
+ # Create the equation object
96
+ eq_sympy = Eq(lhs_sympy, rhs_sympy)
97
+ # Solve for the dependent variable
98
+ solutions = solve(eq_sympy, symbols_dict[dependent_variable])
99
+ # Extract magnitude and unit separately from SymPy expressions
100
+ separated_solutions = []
101
+ for sol in solutions:
102
+ magnitude, unit = sol.as_coeff_Mul() # Works for ANY SymPy expression
103
+ separated_solutions.append((magnitude, unit))
104
+
105
+ # Format solutions properly with a space between the magnitude and unit
106
+ formatted_solutions = [f"{mag} ({unit})" for mag, unit in separated_solutions]
107
+ #print(f"Solutions for {dependent_variable} in terms of {independent_variables}: {formatted_solutions}")
108
+ return formatted_solutions
109
+
110
+
111
+ def parse_equation_dict(equation_dict):
112
+ def extract_value_units(entry):
113
+ trimmed_entry = entry.strip() # Remove leading/trailing whitespace
114
+ split_entry = trimmed_entry.split(" ", 1) # Split on the first space
115
+ if len(split_entry) > 1:
116
+ value = split_entry[0]
117
+ units = split_entry[1] # Everything after the number
118
+ return [value, units]
119
+ else:
120
+ return [float(split_entry[0]), None] # Handle constants without units
121
+
122
+ def extract_constants(constants_dict):
123
+ return {
124
+ name: extract_value_units(value)
125
+ for name, value in constants_dict.items()
126
+ }
127
+
128
+ def extract_equation(equation_string):
129
+ variables_list = re.findall(r"([A-Za-z]+)", equation_string)
130
+ return {"equation_string": equation_string, "variables_list": variables_list}
131
+
132
+ if 'graphical_dimensionality' in equation_dict:
133
+ graphical_dimensionality = equation_dict['graphical_dimensionality']
134
+ else:
135
+ graphical_dimensionality = 2
136
+
137
+ constants_extracted_dict = extract_constants(equation_dict["constants"])
138
+ equation_extracted_dict = extract_equation(equation_dict["equation_string"])
139
+ # x_match = re.match(r"([\w\d{}$/*_°α-ωΑ-Ω]+)\s*\(([\w\d{}$/*_°α-ωΑ-Ω]*)\)", equation_dict["x_variable"])
140
+ # y_match = re.match(r"([\w\d{}$/*_°α-ωΑ-Ω]+)\s*\(([\w\d{}$/*_°α-ωΑ-Ω]*)\)", equation_dict["y_variable"])
141
+ # x_match = (x_match.group(1), x_match.group(2)) if x_match else (equation_dict["x_variable"], None)
142
+ # y_match = (y_match.group(1), y_match.group(2)) if y_match else (equation_dict["y_variable"], None)
143
+ x_match = extract_value_units(equation_dict["x_variable"])
144
+ y_match = extract_value_units(equation_dict["y_variable"])
145
+ if graphical_dimensionality == 3:
146
+ z_match = extract_value_units(equation_dict["z_variable"])
147
+
148
+ # Create dictionaries for extracted variables
149
+ x_variable_extracted_dict = {"label": x_match[0], "units": x_match[1]}
150
+ y_variable_extracted_dict = {"label": y_match[0], "units": y_match[1]}
151
+ if graphical_dimensionality == 3:
152
+ z_variable_extracted_dict = {"label": z_match[0], "units": z_match[1]}
153
+
154
+ def prepare_independent_variables(constants_extracted_dict):
155
+ independent_variables_dict = {
156
+ name: f"{value} {units}" if units else f"{value}"
157
+ for name, (value, units) in constants_extracted_dict.items()
158
+ }
159
+ return independent_variables_dict
160
+ independent_variables_dict = prepare_independent_variables(constants_extracted_dict)
161
+
162
+ if graphical_dimensionality == 2:
163
+ return independent_variables_dict, constants_extracted_dict, equation_extracted_dict, x_variable_extracted_dict, y_variable_extracted_dict
164
+ if graphical_dimensionality == 3:
165
+ return independent_variables_dict, constants_extracted_dict, equation_extracted_dict, x_variable_extracted_dict, y_variable_extracted_dict, z_variable_extracted_dict
166
+
167
+ # equation_dict = {
168
+ # 'equation_string': 'k = A*(e**((-Ea)/(R*T)))',
169
+ # 'x_variable': 'T (K)',
170
+ # 'y_variable': 'k (s**(-1))',
171
+ # 'constants': {'Ea': '30000 (J)*(mol^(-1))', 'R': '8.314 (J)*(mol^(-1))*(K^(-1))' , 'A': '1E13 (s**-1)', 'e': '2.71828'},
172
+ # 'num_of_points': 10,
173
+ # 'x_range_default': [200, 500],
174
+ # 'x_range_limits' : [],
175
+ # 'points_spacing': 'Linear'
176
+ # }
177
+
178
+ # try:
179
+ # result_extracted = parse_equation_dict(equation_dict)
180
+ # print(result_extracted)
181
+ # except ValueError as e:
182
+ # print(f"Error: {e}")
183
+
184
+
185
+ def generate_multiplicative_points(range_min, range_max, num_of_points=None, factor=None, reverse_scaling=False):
186
+ """
187
+ Generates a sequence of points using relative spacing within a normalized range.
188
+
189
+ - Spacing between points changes multiplicatively (e.g., doubling means each interval doubles).
190
+ - Returns range_min and range_max explicitly in all cases.
191
+ - Works for negative values and cases where min is negative while max is positive.
192
+ - If `reverse_scaling` is True, exponential scaling occurs from the max end instead.
193
+
194
+ Parameters:
195
+ - range_min (float): The starting value of the sequence.
196
+ - range_max (float): The maximum limit for generated values.
197
+ - num_of_points (int, optional): Desired number of points (excluding min/max).
198
+ - factor (float, optional): Multiplication factor for spacing between successive values.
199
+ - reverse_scaling (bool, optional): If True, spacing is applied in reverse direction.
200
+
201
+ Returns:
202
+ - List of generated points (standard Python floats).
203
+
204
+ Raises:
205
+ - ValueError: If neither num_of_points nor factor is provided.
206
+ """
207
+
208
+ # Define normalized bounds
209
+ relative_min = 0
210
+ relative_max = 1
211
+ total_value_range = range_max - range_min
212
+
213
+ # Case 1: num_of_points is provided (factor may be provided too)
214
+ if num_of_points is not None and num_of_points > 1:
215
+
216
+ # Case 1a: Generate points using equal spacing in relative space
217
+ equal_spacing_list = [relative_min] # Start at normalized min
218
+ equal_spacing_value = (relative_max - relative_min) / (num_of_points - 1) # Normalized step size
219
+
220
+ for step_index in range(1, num_of_points):
221
+ equal_spacing_list.append(relative_min + step_index * equal_spacing_value)
222
+
223
+ # Case 1b: Generate points using multiplication factor (if provided)
224
+ factor_spacing_list = [relative_min]
225
+ if factor is not None and factor > 0:
226
+ relative_spacing = 0.01 # Start at 1% of the range (normalized units)
227
+ current_position = relative_min
228
+
229
+ while current_position + relative_spacing < relative_max:
230
+ current_position += relative_spacing
231
+ factor_spacing_list.append(current_position)
232
+ relative_spacing *= factor # Multiply spacing by factor
233
+
234
+ # Compare list lengths explicitly and select the better approach
235
+ if len(factor_spacing_list) > len(equal_spacing_list):
236
+ normalized_points = factor_spacing_list
237
+ else:
238
+ normalized_points = equal_spacing_list
239
+
240
+ # Case 2: Only factor is provided, generate points using the multiplication factor
241
+ elif factor is not None and factor > 0:
242
+ relative_spacing = 0.01 # Start at 1% of the range
243
+ current_position = relative_min
244
+ normalized_points = [relative_min]
245
+
246
+ while current_position + relative_spacing < relative_max:
247
+ current_position += relative_spacing
248
+ normalized_points.append(current_position)
249
+ relative_spacing *= factor # Multiply spacing by factor
250
+
251
+ # Case 3: Neither num_of_points nor factor is provided, compute equal spacing dynamically
252
+ elif num_of_points is None and factor is None:
253
+ equal_spacing_value = (relative_max - relative_min) / 9 # Default to 9 intermediate points
254
+ normalized_points = [relative_min + step_index * equal_spacing_value for step_index in range(1, 9)]
255
+
256
+ # Case 4: Invalid input case—neither num_of_points nor factor is properly set
257
+ else:
258
+ raise ValueError("Either num_of_points or factor must be provided.")
259
+
260
+ # Ensure the last relative point is relative_max before scaling
261
+ if normalized_points[-1] != relative_max:
262
+ normalized_points.append(relative_max)
263
+
264
+ # Scale normalized points back to the actual range
265
+ if reverse_scaling:
266
+ scaled_points = [range_max - ((relative_max - p) * total_value_range) for p in normalized_points] # Reverse scaling adjustment
267
+ else:
268
+ scaled_points = [range_min + (p * total_value_range) for p in normalized_points]
269
+
270
+ return scaled_points
271
+
272
+ # # Example usages
273
+ # print("line 224")
274
+ # print(generate_multiplicative_points(0, 100, num_of_points=10, factor=2)) # Default exponential scaling from min end
275
+ # print(generate_multiplicative_points(0, 100, num_of_points=10, factor=2, reverse_scaling=True)) # Exponential scaling from max end
276
+ # print(generate_multiplicative_points(1, 100, num_of_points=10, factor=1.3)) # Compares num_of_points vs factor, chooses whichever makes more points
277
+ # print(generate_multiplicative_points(1, 100, num_of_points=10)) # Computes factor dynamically
278
+ # print(generate_multiplicative_points(1, 100, factor=2)) # Uses factor normally
279
+ # print(generate_multiplicative_points(1, 100)) # Uses step_factor for default 10 points with 8 intermediate values
280
+ # print("line 228")
281
+
282
+
283
+ # # Example usages with reverse scaling
284
+ # print("line 240")
285
+ # print(generate_multiplicative_points(-50, 100, num_of_points=10, factor=2)) # Case 1b: Uses spacing factor with num_of_points
286
+ # print(generate_multiplicative_points(-50, 100, num_of_points=10, factor=2, reverse_scaling=True)) # Reverse scaling version
287
+ # print(generate_multiplicative_points(-100, -10, num_of_points=5, factor=1.5)) # Case 1b: Works with negatives
288
+ # print(generate_multiplicative_points(-25, 75, num_of_points=7)) # Case 1a: Computes spacing dynamically
289
+ # print(generate_multiplicative_points(-10, 50, factor=1.3)) # Case 2: Uses factor-based spacing
290
+ # print(generate_multiplicative_points(-30, 30)) # Case 3: Uses default intermediate spacing
291
+
292
+ def generate_points_by_spacing(num_of_points=10, range_min=0, range_max=1, points_spacing="linear"):
293
+ """
294
+ Generates a sequence of points based on the specified spacing method.
295
+
296
+ Supported spacing types:
297
+ - "linear": Evenly spaced values between range_min and range_max.
298
+ - "logarithmic": Logarithmically spaced values.
299
+ - "exponential": Exponentially increasing values.
300
+ - A real number > 0: Used as a multiplication factor to generate values.
301
+
302
+ Parameters:
303
+ - num_of_points (int): The number of points to generate. Default is 10.
304
+ - range_min (float): The starting value of the sequence. Default is 1.
305
+ - range_max (float): The maximum limit for generated values. Default is 100.
306
+ - points_spacing (str or float): Defines the spacing method or multiplication factor.
307
+
308
+ Returns:
309
+ - List of generated points.
310
+
311
+ Raises:
312
+ - ValueError: If an unsupported spacing type is provided.
313
+ """
314
+ import numpy as np # Ensure numpy is imported
315
+ spacing_type = str(points_spacing).lower() if isinstance(points_spacing, str) else None
316
+ points_list = None
317
+ if num_of_points == None:
318
+ num_of_points = 10
319
+ if range_min == None:
320
+ range_min = 0
321
+ if range_max == None:
322
+ range_max = 1
323
+ if str(spacing_type).lower() == "none":
324
+ spacing_type = "linear"
325
+ if spacing_type == "":
326
+ spacing_type = "linear"
327
+ if spacing_type.lower() == "linear":
328
+ points_list = np.linspace(range_min, range_max, num_of_points).tolist()
329
+ elif spacing_type.lower() == "logarithmic":
330
+ points_list = np.logspace(np.log10(range_min), np.log10(range_max), num_of_points).tolist()
331
+ elif spacing_type.lower() == "exponential":
332
+ points_list = (range_min * np.exp(np.linspace(0, np.log(range_max/range_min), num_of_points))).tolist()
333
+ elif isinstance(points_spacing, (int, float)) and points_spacing > 0:
334
+ points_list = generate_multiplicative_points(range_min, range_max, points_spacing, num_of_points)
335
+ else:
336
+ raise ValueError(f"Unsupported spacing type: {points_spacing}")
337
+
338
+ return points_list
339
+
340
+
341
+ # # Example usage demonstrating different spacing types:
342
+ # print(generate_points_by_spacing(num_of_points=10, range_min=1, range_max=100, points_spacing="linear")) # Linear spacing
343
+ # print(generate_points_by_spacing(num_of_points=10, range_min=1, range_max=100, points_spacing="logarithmic")) # Logarithmic spacing
344
+ # print(generate_points_by_spacing(num_of_points=10, range_min=1, range_max=100, points_spacing="exponential")) # Exponential spacing
345
+ # print(generate_points_by_spacing(num_of_points=10, range_min=1, range_max=100, points_spacing=2)) # Multiplicative factor spacing
346
+
347
+
348
+ def generate_points_from_range_dict(range_dict, variable_name="x"):
349
+ """
350
+ Extracts the necessary range and parameters from range_dict and generates a sequence of points.
351
+ In practice, the range_dict can be a full equation_dict with extra fields that will not be used.
352
+
353
+ The function follows these rules:
354
+ 1. If '{variable_name}_range_limits' is provided as a list of two numbers, it is used as the range.
355
+ 2. Otherwise, '{variable_name}_range_default' is used as the range.
356
+ 3. Calls generate_points_by_spacing() to generate the appropriate sequence based on num_of_points and points_spacing.
357
+
358
+ Parameters:
359
+ - range_dict (dict): Dictionary containing equation details, including range limits, num_of_points, and spacing type.
360
+ - variable_name (str, optional): Name of the variable to determine the range settings. Defaults to 'x'.
361
+
362
+ Returns:
363
+ - List of generated points.
364
+ """
365
+ range_default_key = f"{variable_name}_range_default"
366
+ range_limits_key = f"{variable_name}_range_limits"
367
+
368
+ # Assigning range.
369
+ # Start with default values
370
+ if range_dict.get(range_default_key): # get prevents crashing if the field is not present.
371
+ range_min, range_max = range_dict[range_default_key]
372
+
373
+ # If '{variable_name}_range_limits' is provided, update values only if they narrow the range
374
+ if range_dict.get(range_limits_key):
375
+ limit_min, limit_max = range_dict[range_limits_key]
376
+ # Apply limits only if they tighten the range
377
+ if limit_min is not None and limit_min > range_min:
378
+ range_min = limit_min
379
+ if limit_max is not None and limit_max < range_max:
380
+ range_max = limit_max
381
+
382
+ # Ensure at least one valid limit exists
383
+ if range_min is None or range_max is None:
384
+ raise ValueError(f"At least one min and one max must be specified between {variable_name}_range_default and {variable_name}_range_limits.")
385
+
386
+ list_of_points = generate_points_by_spacing(
387
+ num_of_points=range_dict['num_of_points'],
388
+ range_min=range_min,
389
+ range_max=range_max,
390
+ points_spacing=range_dict['points_spacing']
391
+ )
392
+ # Generate points using the specified spacing method
393
+ return list_of_points
394
+
395
+
396
+ ## Start of Portion of code for parsing out tagged ustom units and returning them ##
397
+
398
+ def return_custom_units_markup(units_string, custom_units_list):
399
+ """puts markup around custom units with '<' and '>' """
400
+ sorted_custom_units_list = sorted(custom_units_list, key=len, reverse=True)
401
+ #the units should be sorted from longest to shortest if not already sorted that way.
402
+ for custom_unit in sorted_custom_units_list:
403
+ units_string = units_string.replace(custom_unit, '<'+custom_unit+'>')
404
+ return units_string
405
+
406
+ def extract_tagged_strings(text):
407
+ """Extracts tags surrounded by <> from a given string. Used for custom units.
408
+ returns them as a list sorted from longest to shortest"""
409
+ list_of_tags = re.findall(r'<(.*?)>', text)
410
+ set_of_tags = set(list_of_tags)
411
+ sorted_tags = sorted(set_of_tags, key=len, reverse=True)
412
+ return sorted_tags
413
+
414
+ ##End of Portion of code for parsing out tagged ustom units and returning them ##
415
+
416
+
417
+
418
+ #This function is to convert things like (1/bar) to (bar)**(-1)
419
+ #It was written by copilot and refined by further prompting of copilot by testing.
420
+ #The depth is because the function works iteratively and then stops when finished.
421
+ def convert_inverse_units(expression, depth=100):
422
+ # Patterns to match valid reciprocals while ignoring multiplied units, so (1/bar)*bar should be handled correctly.
423
+ patterns = [r"1/\((1/.*?)\)", r"1/([a-zA-Z]+)"]
424
+ for _ in range(depth):
425
+ new_expression = expression
426
+ for pattern in patterns:
427
+ new_expression = re.sub(pattern, r"(\1)**(-1)", new_expression)
428
+
429
+ # Stop early if no more changes are made
430
+ if new_expression == expression:
431
+ break
432
+ expression = new_expression
433
+ return expression
434
+
435
+ #This support function is just for code readability.
436
+ #It returnts two strings in a list, split at the first delimiter.
437
+ def split_at_first_delimiter(string, delimter=" "):
438
+ return string.split(delimter, 1)
439
+
440
+ #This function takes an equation dict (see examples) and returns the x_points, y_points, and x_units and y_units.
441
+ #If there is more than one solution (like in a circle, for example) all solutions should be returned.
442
+ #The function is slow. I have checked what happens if "vectorize" is used on the x_point loop (which is the main work)
443
+ #and the function time didn't change. So the functions it calls must be where the slow portion is.
444
+ #I have not timed the individual functions to find and diagnose the slow step(s) to make them more efficient.
445
+ #Although there is lots of conversion between different object types to support the units format flexiblity that this function has,
446
+ #I would still expect the optimzed code to be an order of magnitude faster. So it may be worth finding the slow steps.
447
+ #One possibility might be to use "re.compile()"
448
+ def evaluate_equation_dict(equation_dict, verbose=False):
449
+ #First a block of code to extract the x_points needed
450
+ # Extract each dictionary key as a local variable
451
+ equation_string = equation_dict['equation_string']
452
+ if 'graphical_dimensionality' in equation_dict:
453
+ graphical_dimensionality = equation_dict['graphical_dimensionality']
454
+ graphical_dimensionality_added = False
455
+ else: #assume graphical_dimensionality is 2 if one is not provided.
456
+ equation_dict['graphical_dimensionality'] = 2
457
+ graphical_dimensionality_added = True
458
+ graphical_dimensionality = 2
459
+ if 'verbose' in equation_dict:
460
+ verbose = equation_dict["verbose"]
461
+ # We don't need the below variables, because they are in the equation_dict.
462
+ # x_variable = equation_dict['x_variable']
463
+ # y_variable = equation_dict['y_variable']
464
+ # constants = equation_dict['constants']
465
+ # reverse_scaling = equation_dict['reverse_scaling']
466
+ x_points = generate_points_from_range_dict(range_dict = equation_dict, variable_name='x')
467
+ if graphical_dimensionality == 3: #for graphical_dimensionality of 3, the y_points are also an independent_variable to generate.
468
+ y_points = generate_points_from_range_dict(range_dict = equation_dict, variable_name='y')
469
+
470
+ #Now get the various variables etc.
471
+ if graphical_dimensionality == 2:
472
+ independent_variables_dict, constants_extracted_dict, equation_extracted_dict, x_variable_extracted_dict, y_variable_extracted_dict = parse_equation_dict(equation_dict=equation_dict)
473
+ constants_extracted_dict, equation_extracted_dict #These will not be used. The rest of this comment is to avoid a vs code pylint flag. # pylint: disable=unused-variable, disable=pointless-statement
474
+ elif graphical_dimensionality == 3:
475
+ independent_variables_dict, constants_extracted_dict, equation_extracted_dict, x_variable_extracted_dict, y_variable_extracted_dict, z_variable_extracted_dict = parse_equation_dict(equation_dict=equation_dict)
476
+ constants_extracted_dict, equation_extracted_dict #These will not be used. The rest of this comment is to avoid a vs code pylint flag. # pylint: disable=unused-variable, disable=pointless-statement
477
+ else:
478
+ raise ValueError("Error: graphical_dimensionality not received and/or not evaluatable by current code.")
479
+
480
+ #Start of block to check for any custom units and add them to the ureg if necessary.
481
+ custom_units_list = []
482
+ for constant_entry_key in independent_variables_dict.keys():
483
+ independent_variables_string = independent_variables_dict[constant_entry_key]
484
+ custom_units_extracted = extract_tagged_strings(independent_variables_string)
485
+ for custom_unit in custom_units_extracted: #this will be skipped if the list is empty.
486
+ ureg.define(f"{custom_unit} = [custom]") #use "[custom]" to create a custom unit in the pint module.
487
+ custom_units_list.extend(custom_units_extracted)
488
+
489
+ #now also check for the x_variable_extracted_dict
490
+ custom_units_extracted = extract_tagged_strings(x_variable_extracted_dict["units"])
491
+ for custom_unit in custom_units_extracted: #this will be skipped if the list is empty.
492
+ ureg.define(f"{custom_unit} = [custom]") #use "[custom]" to create a custom unit in the pint module.
493
+ custom_units_list.extend(custom_units_extracted)
494
+
495
+ #now also check for the y_variable_extracted_dict (technically not needed)
496
+ custom_units_extracted = extract_tagged_strings(y_variable_extracted_dict["units"])
497
+ for custom_unit in custom_units_extracted: #this will be skipped if the list is empty.
498
+ ureg.define(f"{custom_unit} = [custom]") #use "[custom]" to create a custom unit in the pint module.
499
+ custom_units_list.extend(custom_units_extracted)
500
+
501
+ if graphical_dimensionality == 3:
502
+ #now also check for the z_variable_extracted_dict (technically not needed)
503
+ custom_units_extracted = extract_tagged_strings(z_variable_extracted_dict["units"])
504
+ for custom_unit in custom_units_extracted: #this will be skipped if the list is empty.
505
+ ureg.define(f"{custom_unit} = [custom]") #use "[custom]" to create a custom unit in the pint module.
506
+ custom_units_list.extend(custom_units_extracted)
507
+
508
+ #now also check for the equation_string
509
+ custom_units_extracted = extract_tagged_strings(equation_string)
510
+ for custom_unit in custom_units_extracted: #this will be skipped if the list is empty.
511
+ ureg.define(f"{custom_unit} = [custom]") #use "[custom]" to create a custom unit in the pint module.
512
+ custom_units_list.extend(custom_units_extracted)
513
+
514
+ #now sort from longest to shortest, since we will have to put them back in that way later.
515
+ custom_units_list = sorted(custom_units_list, key=len, reverse=True)
516
+ #End of block to check for any custom units and add them to the ureg if necessary.
517
+
518
+ #For graphical_dimensionality of 2, The full list of independent variables includes the x_variable and the independent_variables.
519
+ independent_variables = list(independent_variables_dict.keys())#.append(x_variable_extracted_dict['label'])
520
+ independent_variables.append(x_variable_extracted_dict['label'])
521
+ if graphical_dimensionality == 3: #for graphical_dimensionality of 3, the y_variable is also an independent variable.
522
+ independent_variables.append(y_variable_extracted_dict['label'])
523
+
524
+ #Now define the dependent variable:
525
+ if graphical_dimensionality == 2:
526
+ dependent_variable = y_variable_extracted_dict["label"]
527
+ elif graphical_dimensionality == 3:
528
+ dependent_variable = z_variable_extracted_dict["label"]
529
+ else:
530
+ raise ValueError("Error: graphical_dimensionality not received and/or not evaluatable by current code.")
531
+ solved_coordinates_list = [] #These are x,y pairs or x,y,z triplets. can't just keep y_points, because there could be more than one solution.
532
+ y_units = ''#just initializing.
533
+ dependent_variable_units = '' #just initializing.
534
+
535
+ if graphical_dimensionality == 2:
536
+ input_points_list = x_points #currently a list of points [1,2,3]
537
+ #nested_x_points = [[x] for x in input_points_list] #this way could have [ [x1],[x2],...]
538
+ elif graphical_dimensionality == 3:
539
+ import itertools
540
+ input_points_list = list(itertools.product(x_points, y_points)) #[ [x1,y1], [x1,y2] ]
541
+ else:
542
+ raise ValueError("Error: graphical_dimensionality not received and/or not evaluatable by current code.")
543
+
544
+ for current_point in input_points_list:
545
+ #For each point, need to call the "solve_equation" equation (or a vectorized version of it).
546
+ #This is the form that the variables need to take
547
+ # # Example usage
548
+ # independent_variables_values_and_units = {
549
+ # "x": "2 m / s",
550
+ # "y": "3 meter"
551
+ # }
552
+ # We also need to define the independent variables and dependent variables.
553
+ if graphical_dimensionality == 2:
554
+ independent_variables_dict[x_variable_extracted_dict["label"]] = str(current_point) + " " + x_variable_extracted_dict["units"]
555
+ if graphical_dimensionality == 3:
556
+ independent_variables_dict[x_variable_extracted_dict["label"]] = str(current_point[0]) + " " + x_variable_extracted_dict["units"]
557
+ independent_variables_dict[y_variable_extracted_dict["label"]] = str(current_point[1]) + " " + y_variable_extracted_dict["units"]
558
+ #if graphical_dimensionality is 2D, dependent_variable_solutions is y_solutions.
559
+ #if graphical_dimensionality is 3D, dependent_variable_solutions is z_solutions.
560
+ if verbose: print("json_equationer > equation_evaluator > evaluate_equation_dict > current_point:", current_point)
561
+ dependent_variable_solutions = solve_equation(equation_string, independent_variables_values_and_units=independent_variables_dict, dependent_variable=dependent_variable)
562
+ if dependent_variable_solutions:
563
+ for dependent_variable_point_with_units in dependent_variable_solutions:
564
+ if graphical_dimensionality == 2:
565
+ y_point = float(dependent_variable_point_with_units.split(" ", 1)[0]) #the 1 splits only at first space.
566
+ solved_coordinates_list.append([current_point, y_point])
567
+ if dependent_variable_units == '': #only extract units the first time.
568
+ y_units = dependent_variable_point_with_units.split(" ", 1)[1] #the 1 splits only at first space.
569
+ if graphical_dimensionality == 3:
570
+ z_point = float(dependent_variable_point_with_units.split(" ", 1)[0]) #the 1 splits only at first space.
571
+ solved_coordinates_list.append([current_point[0],current_point[1], z_point])
572
+ if dependent_variable_units == '': #only extract units the first time.
573
+ z_units = dependent_variable_point_with_units.split(" ", 1)[1] #the 1 splits only at first space.
574
+
575
+ #now need to convert the x_y_pairs.
576
+ # Separating x and y points
577
+ if graphical_dimensionality == 2:
578
+ x_points, y_points = zip(*solved_coordinates_list)
579
+ elif graphical_dimensionality == 3:
580
+ x_points, y_points, z_points = zip(*solved_coordinates_list)
581
+
582
+ # Convert tuples to lists
583
+ x_points = list(x_points)
584
+ y_points = list(y_points)
585
+ if graphical_dimensionality == 3:
586
+ z_points = list(z_points)
587
+
588
+ #Some lines to ensure units are appropriate format before doing any inverse units conversions.
589
+ if graphical_dimensionality == 2:
590
+ x_units = x_variable_extracted_dict["units"]
591
+ if "(" not in x_units:
592
+ x_units = "(" + x_units + ")"
593
+ if "(" not in y_units:
594
+ y_units = "(" + y_units + ")"
595
+
596
+ if graphical_dimensionality == 3:
597
+ x_units = x_variable_extracted_dict["units"]
598
+ y_units = y_variable_extracted_dict["units"]
599
+ if "(" not in x_units:
600
+ x_units = "(" + x_units + ")"
601
+ if "(" not in y_units:
602
+ y_units = "(" + y_units + ")"
603
+ if "(" not in z_units:
604
+ z_units = "(" + z_units + ")"
605
+
606
+ y_units = convert_inverse_units(y_units)
607
+ x_units = convert_inverse_units(x_units)
608
+ if graphical_dimensionality == 3:
609
+ z_units = convert_inverse_units(z_units)
610
+
611
+ #Put back any custom units tags, only needed for dependent variable.
612
+ if graphical_dimensionality == 2:
613
+ y_units = return_custom_units_markup(y_units, custom_units_list)
614
+ if graphical_dimensionality == 3:
615
+ z_units = return_custom_units_markup(z_units, custom_units_list)
616
+
617
+ #Fill the dictionary that will be returned.
618
+ evaluated_dict = {}
619
+ evaluated_dict['graphical_dimensionality'] = graphical_dimensionality
620
+ evaluated_dict['x_units'] = x_units
621
+ evaluated_dict['y_units'] = y_units
622
+ evaluated_dict['x_points'] = x_points
623
+ evaluated_dict['y_points'] = y_points
624
+ if graphical_dimensionality == 3:
625
+ z_units = return_custom_units_markup(z_units, custom_units_list)
626
+ evaluated_dict['z_units'] = z_units
627
+ evaluated_dict['z_points'] = z_points
628
+ if graphical_dimensionality_added == True: #undo adding graphical_dimensionality if it was added by this function.
629
+ equation_dict.pop("graphical_dimensionality")
630
+ return evaluated_dict
631
+
632
+ if __name__ == "__main__":
633
+ #Here is a 2D example:
634
+ example_equation_dict = {
635
+ 'equation_string': 'k = A*(e**((-Ea)/(R*T)))',
636
+ 'x_variable': 'T (K)',
637
+ 'y_variable': 'k (s**(-1))',
638
+ 'constants': {'Ea': '30000 (J)*(mol^(-1))', 'R': '8.314 (J)*(mol^(-1))*(K^(-1))' , 'A': '1*10^13 (s^-1)', 'e': '2.71828'},
639
+ 'num_of_points': 10,
640
+ 'x_range_default': [200, 500],
641
+ 'x_range_limits' : [],
642
+ 'x_points_specified' : [],
643
+ 'points_spacing': 'Linear',
644
+ 'reverse_scaling' : False
645
+ }
646
+
647
+ example_evaluated_dict = evaluate_equation_dict(example_equation_dict)
648
+ print(example_evaluated_dict)
649
+
650
+ #Here is a 3D example.
651
+ example_equation_dict = {
652
+ 'equation_string': 'k = A*(e**((-Ea)/(R*T)))',
653
+ 'graphical_dimensionality' : 3,
654
+ 'x_variable': 'T (K)',
655
+ 'y_variable': 'Ea (J)*(mol^(-1))',
656
+ 'z_variable': 'k (s**(-1))',
657
+ 'constants': {'R': '8.314 (J)*(mol^(-1))*(K^(-1))' , 'A': '1*10^13 (s^-1)', 'e': '2.71828'},
658
+ 'num_of_points': 10,
659
+ 'x_range_default': [200, 500],
660
+ 'x_range_limits' : [],
661
+ 'y_range_default': [30000, 50000],
662
+ 'y_range_limits' : [],
663
+ 'x_points_specified' : [],
664
+ 'points_spacing': 'Linear',
665
+ 'reverse_scaling' : False
666
+ }
667
+
668
+ example_evaluated_dict = evaluate_equation_dict(example_equation_dict)
669
+ print(example_evaluated_dict)
670
+
File without changes