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,125 @@
1
+ import unitpy
2
+ import unitpy.definitions
3
+ import unitpy.definitions.entry
4
+
5
+ print(unitpy.U("kg/m"))
6
+
7
+
8
+ print(unitpy.U("(((kg)/m))/s"))
9
+
10
+ units1 = unitpy.U("(((kg)/m))/s")
11
+ units2 = unitpy.U("(((g)/m))/s")
12
+ unitsRatio = units1/units2
13
+ print(unitsRatio)
14
+
15
+ print(units2)
16
+ units1multiplied =1*unitpy.U("(((kg)/m))/s")
17
+ print("line 14")
18
+ ratioWithUnits = units1multiplied.to("(((g)/m))/s")
19
+ print(ratioWithUnits)
20
+ print(str(ratioWithUnits).split(' '))
21
+
22
+ units1 = unitpy.Q("1 (((kg)/m))/s")
23
+ units2 = unitpy.Q("1 (((g)/m))/s")
24
+ print("line 22", units2.base_unit)
25
+ unitsRatio = units1/units2
26
+ print("line 23")
27
+ print(unitsRatio)
28
+
29
+
30
+
31
+ print(units2)
32
+ units1multiplied =1*unitpy.U("(((kg)/m))*(s**-1)")
33
+ print("line 14")
34
+ ratioWithUnits = units1multiplied.to("(((g)/m))/s")
35
+ print(ratioWithUnits)
36
+ print(str(ratioWithUnits).split(' '))
37
+ print(units1multiplied.base_unit)
38
+
39
+ units5 = unitpy.Q("1 (((g)/m))/s")
40
+ print(units5.base_unit)
41
+
42
+
43
+ def convert_inverse_units(expression, depth=100):
44
+ import re
45
+ # Patterns to match valid reciprocals while ignoring multiplied units
46
+ patterns = [r"1/\((1/.*?)\)", r"1/([a-zA-Z]+)"]
47
+
48
+ for _ in range(depth):
49
+ new_expression = expression
50
+ for pattern in patterns:
51
+ new_expression = re.sub(pattern, r"(\1)**(-1)", new_expression)
52
+
53
+ # Stop early if no more changes are made
54
+ if new_expression == expression:
55
+ break
56
+ expression = new_expression
57
+ return expression
58
+
59
+
60
+ expression_original = "1/(1/bar)"
61
+ expression_altered = convert_inverse_units(expression_original)
62
+ units6 = unitpy.Q('1*'+expression_altered)
63
+ print(units6.unit)
64
+
65
+ from unitpy import U, Unit
66
+ import unitpy
67
+ newunit = unitpy.Unit("meter")
68
+ from unitpy.definitions.entry import Entry
69
+ # new_entry = Entry("frog", "frog", "frog", 1.0)
70
+ # unitpy.ledger.add_unit(new_entry)
71
+ def add_custom_unit_to_unitpy(unit_string):
72
+ import unitpy
73
+ from unitpy.definitions.entry import Entry
74
+ #need to put an entry into "bases" because the BaseSet class will pull from that dictionary.
75
+ unitpy.definitions.unit_base.bases[unit_string] = unitpy.definitions.unit_base.BaseUnit(label=unit_string, abbr=unit_string,dimension=unitpy.definitions.dimensions.dimensions["amount_of_substance"])
76
+ #Then need to make a BaseSet object to put in. Confusingly, we *do not* put a BaseUnit object into the base_unit argument, below.
77
+ #We use "mole" to avoid conflicting with any other existing units.
78
+ base_unit =unitpy.definitions.unit_base.BaseSet(mole = 1)
79
+ #base_unit = unitpy.definitions.unit_base.BaseUnit(label=unit_string, abbr=unit_string,dimension=unitpy.definitions.dimensions.dimensions["amount_of_substance"])
80
+ new_entry = Entry(label = unit_string, abbr = unit_string, base_unit = base_unit, multiplier= 1)
81
+ #only add the entry if it is missing. A duplicate entry would cause crashing later.
82
+ if not unitpy.ledger.get_entry(new_entry):
83
+ unitpy.ledger.add_unit(new_entry) #implied return is here. No return needed.
84
+
85
+ add_custom_unit_to_unitpy("frog")
86
+ #TODO: now know one way how to add custom units to unitpy.
87
+ #Cannot put "<>" inside unitpy, but could filter those out, and then put them back. Would need to make a list of unique entries with <> because there could be more than one.
88
+
89
+ another_test = "1/bar/(1/bar)*bar*frog"
90
+ another_test = convert_inverse_units(another_test)
91
+ print("line 65", another_test)
92
+ units7 = unitpy.U(another_test)
93
+ print("line 66", units7)
94
+ print("line 86", 1*units7)
95
+ print(unitpy.ledger.get_entry("frog"))
96
+ units_string_1 = 'm*frog'
97
+ print("line 87", 1*unitpy.U(units_string_1))
98
+
99
+
100
+
101
+
102
+ print("line 94")
103
+
104
+ units_string_2 = 'm*frog'
105
+
106
+ units_string_1_multiplied = 1*unitpy.U(units_string_1 )
107
+ units_string_1_multiplied.to(units_string_2)
108
+
109
+
110
+ print(units2)
111
+ units1multiplied =1*unitpy.U("(((kg)/m))/s")
112
+ print("line 85")
113
+ string2 = "(((g)/m))*1/s"
114
+ string2 = convert_inverse_units(string2)
115
+ print(string2)
116
+ ratioWithUnits = units1multiplied.to(string2)
117
+ print(ratioWithUnits)
118
+ print(str(ratioWithUnits).split(' '))
119
+
120
+
121
+ #micrometer symbol, "μ" will result in error, also typing out "microm" will result in error, but "micrometer" works
122
+ units1 = unitpy.U("(((kg)/mm))/s")
123
+ #units2 = unitpy.U("(((g)/μm))/s")
124
+ #units2 = unitpy.U("(((g)/microm))/s")
125
+ units2 = unitpy.U("(((g)/micrometer))/s")
@@ -0,0 +1,28 @@
1
+ import unitpy
2
+ import re
3
+ from unitpy.definitions.entry import Entry
4
+
5
+ def add_custom_unit(unit_string):
6
+ # Need to put an entry into "bases" because the BaseSet class will pull from that dictionary.
7
+ unitpy.definitions.unit_base.bases[unit_string] = unitpy.definitions.unit_base.BaseUnit(
8
+ label=unit_string, abbr=unit_string, dimension=unitpy.definitions.dimensions.dimensions["amount_of_substance"]
9
+ )
10
+
11
+ # Then need to make a BaseSet object to put in. Confusingly, we *do not* put a BaseUnit object into the base_unit argument, below.
12
+ # We use "mole" to avoid conflicting with any other existing units.
13
+ base_unit = unitpy.definitions.unit_base.BaseSet(mole=1)
14
+
15
+ new_entry = Entry(label=unit_string, abbr=unit_string, base_unit=base_unit, multiplier=1)
16
+
17
+ # Only add the entry if it is missing. A duplicate entry would cause crashing later.
18
+ if 'frog' not in unitpy.ledger._lookup:
19
+ unitpy.ledger.add_unit(new_entry) # Implied return is here. No return needed.
20
+
21
+ add_custom_unit("frog")
22
+ add_custom_unit("frog")
23
+
24
+ units_string_1 = 'm*frog'
25
+ units_string_2 = 'm*frog'
26
+ units_string_1_multiplied = 1*unitpy.U(units_string_1 )
27
+ print("line 25", type(units_string_1_multiplied))
28
+ units_string_1_multiplied.to(units_string_2)
JSONGrapher/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  #Try to do things a bit like https://github.com/python/cpython/blob/master/Lib/collections/__init__.py
2
2
  #Except instead of putting things here directly in __init__, we'll import them so they are accessible by importing the module.
3
- from JSONGrapherRC.JSONRecordCreator import *
3
+ from JSONGrapher.JSONRecordCreator import *
@@ -0,0 +1,109 @@
1
+ import os
2
+ import tkinter as tk
3
+ from tkinter import filedialog
4
+ from tkinterdnd2 import DND_FILES, TkinterDnD
5
+
6
+
7
+ #The below class creates a window for dragging and dropping or browsing and selecting files
8
+ #And each time one or more file is added, the full file list and most recently added files will be passed to
9
+ #The function supplied by the user (function_for_after_file_addition)
10
+ #with the two variables passed being all_selected_file_paths, newly_added_file_paths
11
+ #This class **cannot** be initiated directly, it should initiated using the
12
+ #companion function create_and_launch
13
+ class DragDropApp:
14
+ def __init__(self, root, app_name = '', function_for_after_file_addition = None):
15
+ self.root = root
16
+ self.root.title(app_name)
17
+ self.function_for_after_file_addition = function_for_after_file_addition
18
+
19
+ # Enable native drag-and-drop capability
20
+ self.root.drop_target_register(DND_FILES)
21
+ self.root.dnd_bind("<<Drop>>", self.drop_files)
22
+
23
+ # Create a drop zone
24
+ self.drop_frame = tk.Label(root, text="Drag and drop files here \n\n Click End When Finished", bg="lightgray", width=50, height=10)
25
+ self.drop_frame.pack(pady=10)
26
+
27
+ # Create a listbox to display selected files
28
+ self.file_listbox = tk.Listbox(root, width=60, height=10)
29
+ self.file_listbox.pack(pady=10)
30
+
31
+ # Buttons for manual selection and finalizing selection
32
+ self.select_button = tk.Button(root, text="Select Files By Browsing", command=self.open_file_dialog)
33
+ self.select_button.pack(pady=5)
34
+
35
+ # Create a frame for the middle buttons
36
+ button_frame_middle = tk.Frame(root)
37
+ button_frame_middle.pack(pady=5)
38
+
39
+ self.clear_button = tk.Button(button_frame_middle, text="Clear Files List", command=self.clear_file_list) # New "Clear" button
40
+ self.clear_button.pack(side = tk.LEFT, pady=5)
41
+
42
+ # "Download Output" button
43
+ self.download_button = tk.Button(button_frame_middle, text="Download Output", command=self.download_output)
44
+ self.download_button.pack(side = tk.RIGHT, pady=5)
45
+
46
+ self.done_button = tk.Button(root, text="End", command=self.finish_selection)
47
+ self.done_button.pack(pady=5)
48
+
49
+ # Store selected file paths
50
+ self.all_selected_file_paths = []
51
+
52
+ def clear_file_list(self):
53
+ """Clears the listbox and resets selected files."""
54
+ self.file_listbox.delete(0, tk.END) # Clear listbox
55
+ self.all_selected_file_paths = [] # Reset file list
56
+ self.function_for_after_file_addition(all_selected_file_paths=[], newly_added_file_paths=[])
57
+ print("File list cleared!") # Optional debug message
58
+
59
+ def open_file_dialog(self):
60
+ """Opens a file dialog to manually select files."""
61
+ newly_added_file_paths = self.root.tk.splitlist(tk.filedialog.askopenfilenames(title="Select files"))
62
+ if newly_added_file_paths:
63
+ self.all_selected_file_paths.extend(newly_added_file_paths)
64
+ self.update_file_list(newly_added_file_paths)
65
+
66
+ def drop_files(self, event):
67
+ """Handles dropped files into the window."""
68
+ newly_added_file_paths = self.root.tk.splitlist(event.data)
69
+ if newly_added_file_paths:
70
+ self.all_selected_file_paths.extend(newly_added_file_paths)
71
+ self.update_file_list(newly_added_file_paths)
72
+
73
+ def update_file_list(self, newly_added_file_paths):
74
+ """Updates the listbox with selected filenames."""
75
+ self.file_listbox.delete(0, tk.END) # Clear listbox
76
+ for filename_and_path in self.all_selected_file_paths:
77
+ self.file_listbox.insert(tk.END, os.path.basename(filename_and_path)) # Show filenames only
78
+ # If there is a function_for_after_file_addition, pass full list and newly added files into function_for_after_file_addition
79
+ if self.function_for_after_file_addition is not None:
80
+ output = self.function_for_after_file_addition(self.all_selected_file_paths, newly_added_file_paths)
81
+ self.output_for_download = output[0] #store the first part of the output for download.
82
+
83
+ def download_output(self):
84
+ """Allows user to choose where to save the output."""
85
+ if hasattr(self, "output_for_download"):
86
+ file_path = filedialog.asksaveasfilename(filetypes=[("*.*", "*.txt")], title="Save Output As")
87
+ if file_path: # If a valid path is chosen
88
+ with open(file_path, "w") as file:
89
+ file.write(str(self.output_for_download))
90
+ print(f"Output saved as '{file_path}'!")
91
+ else:
92
+ print("File save operation canceled.")
93
+ else:
94
+ print("No output available to download.")
95
+
96
+
97
+ def finish_selection(self):
98
+ """Closes the window and returns selected files."""
99
+ self.root.quit() # Close the window
100
+
101
+ # This function is a companion function to
102
+ # The class DragDropApp for creating a file selection and function call app
103
+ # The function_for_after_file_addition should return a list where the first item is something that can be downloaded.
104
+ def create_and_launch(app_name = '', function_for_after_file_addition=None):
105
+ """Starts the GUI and returns selected files."""
106
+ root = TkinterDnD.Tk()
107
+ app = DragDropApp(root, app_name=app_name, function_for_after_file_addition=function_for_after_file_addition)
108
+ root.mainloop() # Runs the Tkinter event loop
109
+ return app.all_selected_file_paths # Returns selected files after the window closes
@@ -0,0 +1,374 @@
1
+ import re
2
+ import json
3
+
4
+ try:
5
+ from json_equationer.equation_evaluator import evaluate_equation_dict
6
+ except ImportError:
7
+ try:
8
+ from .equation_evaluator import evaluate_equation_dict
9
+ except ImportError:
10
+ from equation_evaluator import evaluate_equation_dict
11
+
12
+
13
+ class Equation:
14
+ """
15
+ A class to manage mathematical equations with units and to evaluate them.
16
+ Provides utilities for evaluating, formatting, exporting, and printing.
17
+
18
+ Initialization:
19
+ - Normally, should be initialized as a blank dict object like example_Arrhenius = Equation().
20
+ - Defaults to an empty equation with predefined structure.
21
+ - Accepts an optional dictionary (`initial_dict`) to prepopulate the equation dictionary.
22
+
23
+ Example structure:
24
+ ```
25
+ custom_dict = {
26
+ 'equation_string': "k = A * (e ** (-Ea / (R * T)))",
27
+ 'x_variable': "T (K)",
28
+ 'y_variable': "k (s**-1)",
29
+ 'constants': {"Ea": "30000 J/mol", "R": "8.314 J/(mol*K)", "A": "1*10**13 (s**-1)", "e": "2.71828"},
30
+ 'num_of_points': 10,
31
+ 'x_range_default': [200, 500],
32
+ 'x_range_limits': [None, 600],
33
+ 'points_spacing': "Linear"
34
+ 'graphical_dimensionality': 2
35
+ }
36
+
37
+ #The reason we use 'graphical_dimensionality' rather than 'dimensionality' is that mathematicians define the dimensionality in terms of independent variables.
38
+ #But here, we are usually expecting users who are concerned with 2D or 3D graphing.
39
+
40
+ equation_instance = Equation(initial_dict=custom_dict)
41
+ ```
42
+ """
43
+
44
+ def __init__(self, initial_dict=None):
45
+ """Initialize an empty equation dictionary."""
46
+ if initial_dict==None:
47
+ initial_dict = {}
48
+ self.equation_dict = {
49
+ 'equation_string': '',
50
+ 'x_variable': '',
51
+ 'y_variable': '',
52
+ 'constants': {},
53
+ 'num_of_points': None, # Expected: Integer, defines the minimum number of points to be calculated for the range.
54
+ 'x_range_default': [0, 1], # Default to [0,1] instead of an empty list.
55
+ 'x_range_limits': [None, None], # Allows None for either limit.
56
+ 'x_points_specified': [],
57
+ 'points_spacing': '',
58
+ 'reverse_scaling': False,
59
+ }
60
+
61
+ # If a dictionary is provided, update the default values
62
+ if len(initial_dict)>0:
63
+ if isinstance(initial_dict, dict):
64
+ self.equation_dict.update(initial_dict)
65
+ else:
66
+ raise TypeError("initial_dict must be a dictionary.")
67
+
68
+ def validate_unit(self, value):
69
+ """Ensure that the value is either a pure number or contains a unit."""
70
+ unit_pattern = re.compile(r"^\d+(\.\d+)?(.*)?$")
71
+ if not unit_pattern.match(value):
72
+ raise ValueError(f"Invalid format: '{value}'. Expected a numeric value, optionally followed by a unit.")
73
+
74
+ def add_constants(self, constants):
75
+ """Add constants to the equation dictionary, supporting both single and multiple additions."""
76
+ if isinstance(constants, dict): # Single constant case
77
+ for name, value in constants.items():
78
+ self.validate_unit(value)
79
+ self.equation_dict['constants'][name] = value
80
+ elif isinstance(constants, list): # Multiple constants case
81
+ for constant_dict in constants:
82
+ if isinstance(constant_dict, dict):
83
+ for name, value in constant_dict.items():
84
+ self.validate_unit(value)
85
+ self.equation_dict['constants'][name] = value
86
+ else:
87
+ raise ValueError("Each item in the list must be a dictionary containing a constant name-value pair.")
88
+ else:
89
+ raise TypeError("Expected a dictionary for one constant or a list of dictionaries for multiple constants.")
90
+
91
+ def set_x_variable(self, x_variable):
92
+ """
93
+ Set the x-variable in the equation dictionary.
94
+ Expected format: A descriptive string including the variable name and its unit.
95
+ Example: "T (K)" for temperature in Kelvin.
96
+ """
97
+ self.equation_dict["x_variable"] = x_variable
98
+
99
+ def set_y_variable(self, y_variable):
100
+ """
101
+ Set the y-variable in the equation dictionary.
102
+ Expected format: A descriptive string including the variable name and its unit.
103
+ Example: "k (s**-1)" for a rate constant with inverse seconds as the unit.
104
+ """
105
+ self.equation_dict["y_variable"] = y_variable
106
+
107
+ def set_z_variable(self, z_variable):
108
+ """
109
+ Set the z-variable in the equation dictionary.
110
+ Expected format: A descriptive string including the variable name and its unit.
111
+ Example: "E (J)" for energy with joules as the unit.
112
+ """
113
+ self.equation_dict["z_variable"] = z_variable
114
+
115
+ def set_x_range_default(self, x_range):
116
+ """
117
+ Set the default x range.
118
+ Expected format: A list of two numeric values representing the range boundaries.
119
+ Example: set_x_range([200, 500]) for temperatures between 200K and 500K.
120
+ """
121
+ if not (isinstance(x_range, list) and len(x_range) == 2 and all(isinstance(i, (int, float)) for i in x_range)):
122
+ raise ValueError("x_range must be a list of two numeric values.")
123
+ self.equation_dict['x_range_default'] = x_range
124
+
125
+ def set_x_range_limits(self, x_limits):
126
+ """
127
+ Set the hard limits for x values.
128
+ Expected format: A list of two values (numeric or None) defining absolute boundaries.
129
+ Example: set_x_range_limits([100, 600]) to prevent x values outside this range.
130
+ Example: set_x_range_limits([None, 500]) allows an open lower limit.
131
+ """
132
+ if not (isinstance(x_limits, list) and len(x_limits) == 2):
133
+ raise ValueError("x_limits must be a list of two elements (numeric or None).")
134
+ if not all(isinstance(i, (int, float)) or i is None for i in x_limits):
135
+ raise ValueError("Elements in x_limits must be numeric or None.")
136
+ self.equation_dict['x_range_limits'] = x_limits
137
+
138
+ def set_y_range_default(self, y_range):
139
+ """
140
+ Set the default y range.
141
+ Expected format: A list of two numeric values representing the range boundaries.
142
+ Example: set_y_range([0, 100]) for a percentage scale.
143
+ """
144
+ if not (isinstance(y_range, list) and len(y_range) == 2 and all(isinstance(i, (int, float)) for i in y_range)):
145
+ raise ValueError("y_range must be a list of two numeric values.")
146
+ self.equation_dict['y_range_default'] = y_range
147
+
148
+ def set_y_range_limits(self, y_limits):
149
+ """
150
+ Set the hard limits for y values.
151
+ Expected format: A list of two values (numeric or None) defining absolute boundaries.
152
+ Example: set_y_range_limits([None, 50]) allows an open lower limit but restricts the upper limit.
153
+ """
154
+ if not (isinstance(y_limits, list) and len(y_limits) == 2):
155
+ raise ValueError("y_limits must be a list of two elements (numeric or None).")
156
+ if not all(isinstance(i, (int, float)) or i is None for i in y_limits):
157
+ raise ValueError("Elements in y_limits must be numeric or None.")
158
+ self.equation_dict['y_range_limits'] = y_limits
159
+
160
+ def set_z_range_default(self, z_range):
161
+ """
162
+ Set the default z range.
163
+ Expected format: A list of two numeric values representing the range boundaries.
164
+ Example: set_z_range([0, 5000]) for energy values in Joules.
165
+ """
166
+ if not (isinstance(z_range, list) and len(z_range) == 2 and all(isinstance(i, (int, float)) for i in z_range)):
167
+ raise ValueError("z_range must be a list of two numeric values.")
168
+ self.equation_dict['z_range_default'] = z_range
169
+
170
+ def set_z_range_limits(self, z_limits):
171
+ """
172
+ Set the hard limits for z values.
173
+ Expected format: A list of two values (numeric or None) defining absolute boundaries.
174
+ Example: set_z_range_limits([100, None]) allows an open upper limit but restricts the lower boundary.
175
+ """
176
+ if not (isinstance(z_limits, list) and len(z_limits) == 2):
177
+ raise ValueError("z_limits must be a list of two elements (numeric or None).")
178
+ if not all(isinstance(i, (int, float)) or i is None for i in z_limits):
179
+ raise ValueError("Elements in z_limits must be numeric or None.")
180
+ self.equation_dict['z_range_limits'] = z_limits
181
+
182
+ def get_z_matrix(self, x_points=None, y_points=None, z_points=None, return_as_list=False):
183
+ """
184
+ Constructs a Z matrix mapping unique (x, y) values to corresponding z values.
185
+
186
+ Parameters:
187
+ - x_points (list): List of x coordinates.
188
+ - y_points (list): List of y coordinates.
189
+ - z_points (list): List of z values.
190
+ - return_as_list (bool, optional): Whether to return the matrix as a list. Defaults to False (returns NumPy array).
191
+
192
+ Returns:
193
+ - z_matrix (2D list or numpy array): Matrix of z values.
194
+ - unique_x (list): Sorted unique x values.
195
+ - unique_y (list): Sorted unique y values.
196
+ """
197
+ if x_points == None:
198
+ x_points = self.equation_dict['x_points']
199
+ if y_points == None:
200
+ y_points = self.equation_dict['y_points']
201
+ if z_points == None:
202
+ z_points = self.equation_dict['z_points']
203
+
204
+ import numpy as np
205
+ # Get unique x and y values
206
+ unique_x = sorted(set(x_points))
207
+ unique_y = sorted(set(y_points))
208
+
209
+ # Create an empty matrix filled with NaNs
210
+ z_matrix = np.full((len(unique_x), len(unique_y)), np.nan)
211
+
212
+ # Map z values to corresponding x, y indices
213
+ for x, y, z in zip(x_points, y_points, z_points):
214
+ x_idx = unique_x.index(x)
215
+ y_idx = unique_y.index(y)
216
+ z_matrix[x_idx, y_idx] = z
217
+
218
+ # Convert to a list if requested
219
+ if return_as_list:
220
+ z_matrix = z_matrix.tolist()
221
+
222
+ return z_matrix
223
+
224
+
225
+
226
+
227
+
228
+ def set_num_of_points(self, num_points):
229
+ """
230
+ Set the number of calculation points.
231
+ Expected format: Integer, specifies the number of discrete points for calculations.
232
+ Example: set_num_of_points(10) for ten data points.
233
+ """
234
+ if not isinstance(num_points, int) or num_points <= 0:
235
+ raise ValueError("Number of points must be a positive integer.")
236
+ self.equation_dict["num_of_points"] = num_points
237
+
238
+ def set_equation(self, equation_string):
239
+ """Modify the equation string."""
240
+ self.equation_dict['equation_string'] = equation_string
241
+
242
+ def get_equation_dict(self):
243
+ """Return the complete equation dictionary."""
244
+ return self.equation_dict
245
+
246
+ def evaluate_equation(self, remove_equation_fields= False, verbose=False):
247
+ evaluated_dict = evaluate_equation_dict(self.equation_dict, verbose=verbose) #this function is from the evaluator module
248
+ if "graphical_dimensionality" in evaluated_dict:
249
+ graphical_dimensionality = evaluated_dict["graphical_dimensionality"]
250
+ else:
251
+ graphical_dimensionality = 2
252
+ self.equation_dict["x_units"] = evaluated_dict["x_units"]
253
+ self.equation_dict["y_units"] = evaluated_dict["y_units"]
254
+ self.equation_dict["x_points"] = evaluated_dict["x_points"]
255
+ self.equation_dict["y_points"] = evaluated_dict["y_points"]
256
+ if graphical_dimensionality == 3:
257
+ self.equation_dict["z_points"] = evaluated_dict["z_points"]
258
+ if remove_equation_fields == True:
259
+ #we'll just make a fresh dictionary for simplicity, in this case.
260
+ equation_dict = {}
261
+ equation_dict["x_units"] = self.equation_dict["x_units"]
262
+ equation_dict["y_units"] = self.equation_dict["y_units"]
263
+ equation_dict["x_points"] = self.equation_dict["x_points"]
264
+ equation_dict["y_points"] = self.equation_dict["y_points"]
265
+ if graphical_dimensionality == 3:
266
+ equation_dict["z_units"] = self.equation_dict["z_units"]
267
+ equation_dict["z_points"] = self.equation_dict["z_points"]
268
+ print("line 223", equation_dict["z_points"])
269
+ self.equation_dict = equation_dict
270
+ return self.equation_dict
271
+
272
+ def print_equation_dict(self, pretty_print=True, evaluate_equation = True, remove_equation_fields = False):
273
+ equation_dict = self.equation_dict #populate a variable internal to this function.
274
+ #if evaluate_equation is true, we'll try to simulate any series that need it, then clean the simulate fields out if requested.
275
+ if evaluate_equation == True:
276
+ evaluated_dict = self.evaluate_equation(remove_equation_fields = remove_equation_fields) #For this function, we don't want to remove equation fields from the object, just the export.
277
+ equation_dict = evaluated_dict
278
+ if remove_equation_fields == True:
279
+ equation_dict = {}
280
+ equation_dict["x_units"] = self.equation_dict["x_units"]
281
+ equation_dict["y_units"] = self.equation_dict["y_units"]
282
+ equation_dict["x_points"] = self.equation_dict["x_points"]
283
+ equation_dict["y_points"] = self.equation_dict["y_points"]
284
+ if pretty_print == False:
285
+ print(equation_dict)
286
+ if pretty_print == True:
287
+ equation_json_string = json.dumps(equation_dict, indent=4)
288
+ print(equation_json_string)
289
+
290
+ def export_to_json_file(self, filename, evaluate_equation = True, remove_equation_fields= False):
291
+ """
292
+ writes the json to a file
293
+ returns the json as a dictionary.
294
+ update_and_validate function will clean for plotly. One can alternatively only validate.
295
+ optionally simulates all series that have a simulate field (does so by default)
296
+ optionally removes simulate filed from all series that have a simulate field (does not do so by default)
297
+ optionally removes hints before export and return.
298
+ """
299
+ equation_dict = self.equation_dict #populate a variable internal to this function.
300
+ #if evaluate_equation is true, we'll try to simulate any series that need it, then clean the simulate fields out if requested.
301
+ if evaluate_equation == True:
302
+ evaluated_dict = self.evaluate_equation(remove_equation_fields = remove_equation_fields) #For this function, we don't want to remove equation fields from the object, just the export.
303
+ equation_dict = evaluated_dict
304
+ if remove_equation_fields == True:
305
+ equation_dict = {}
306
+ equation_dict["x_units"] = self.equation_dict["x_units"]
307
+ equation_dict["y_units"] = self.equation_dict["y_units"]
308
+ equation_dict["x_points"] = self.equation_dict["x_points"]
309
+ equation_dict["y_points"] = self.equation_dict["y_points"]
310
+ # filepath: Optional, filename with path to save the JSON file.
311
+ if len(filename) > 0: #this means we will be writing to file.
312
+ # Check if the filename has an extension and append `.json` if not
313
+ if '.json' not in filename.lower():
314
+ filename += ".json"
315
+ #Write to file using UTF-8 encoding.
316
+ with open(filename, 'w', encoding='utf-8') as f:
317
+ json.dump(equation_dict, f, indent=4)
318
+ return equation_dict
319
+
320
+
321
+
322
+ if __name__ == "__main__":
323
+ # Create an instance of Equation
324
+ example_Arrhenius = Equation()
325
+ example_Arrhenius.set_equation("k = A * (e ** (-Ea / (R * T)))")
326
+ example_Arrhenius.set_x_variable("T (K)") # Temperature in Kelvin
327
+ example_Arrhenius.set_y_variable("k (s**-1)") # Rate constant in inverse seconds
328
+
329
+ # Add a constants one at a time, or through a list.
330
+ example_Arrhenius.add_constants({"Ea": "30000 J/mol"})
331
+ example_Arrhenius.add_constants([
332
+ {"R": "8.314 J/(mol*K)"},
333
+ {"A": "1*10**13 (s**-1)"},
334
+ {"e": "2.71828"} # No unit required
335
+ ])
336
+
337
+ # Optinally, set minimum number of points and limits for calculations.
338
+ example_Arrhenius.set_num_of_points(10)
339
+ example_Arrhenius.set_x_range_default([200, 500])
340
+ example_Arrhenius.set_x_range_limits([None, 600])
341
+
342
+ # Define additional properties.
343
+ example_Arrhenius.equation_dict["points_spacing"] = "Linear"
344
+
345
+ # Retrieve and display the equation dictionary
346
+ example_equation_dict = example_Arrhenius.get_equation_dict()
347
+ print(example_equation_dict)
348
+
349
+ example_Arrhenius.evaluate_equation()
350
+ example_Arrhenius.print_equation_dict()
351
+
352
+
353
+ #Now for a 3D example.
354
+ example_Arrhenius_3D_dict = {
355
+ 'equation_string': 'k = A*(e**((-Ea)/(R*T)))',
356
+ 'graphical_dimensionality' : 3,
357
+ 'x_variable': 'T (K)',
358
+ 'y_variable': 'Ea (J)*(mol^(-1))',
359
+ 'z_variable': 'k (s**(-1))',
360
+ 'constants': {'R': '8.314 (J)*(mol^(-1))*(K^(-1))' , 'A': '1*10^13 (s^-1)', 'e': '2.71828'},
361
+ 'num_of_points': 10,
362
+ 'x_range_default': [200, 500],
363
+ 'x_range_limits' : [],
364
+ 'y_range_default': [30000, 50000],
365
+ 'y_range_limits' : [],
366
+ 'x_points_specified' : [],
367
+ 'points_spacing': 'Linear',
368
+ 'reverse_scaling' : False
369
+ }
370
+
371
+ example_Arrhenius_3D_equation = Equation(initial_dict=example_Arrhenius_3D_dict)
372
+ evaluated_output = example_Arrhenius_3D_equation.evaluate_equation()
373
+ #print(evaluated_output)
374
+ #print(example_Arrhenius_3D_equation.get_z_matrix(return_as_list=True))