-
Notifications
You must be signed in to change notification settings - Fork 0
/
minmax.py
189 lines (147 loc) · 6.65 KB
/
minmax.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
import random
from sympy import symbols, simplify, pretty, N
from terminaltables import DoubleTable as FancyTable
flatten = lambda lst: [item for sublist in lst for item in sublist]
def transpose(array_2d):
transposed = []
rows = len(array_2d)
cols = len(array_2d[0])
for j in range(cols):
row = []
for i in range(rows):
row.append(array_2d[i][j])
transposed.append(row)
return transposed
class MinMaxWorkspace:
def __init__(self):
self._symbols = {}
self._equation = None
self._min_equ = None
self._max_equ = None
@property
def _has_equations(self):
return (self.equation is not None) and (self.min_equation is not None) and (self.max_equation is not None)
@property
def _present_symbols(self):
return [symbol for symbol in self._symbols.values() if self.equation.has(symbol['var'])]
@property
def _present_variables(self):
return [symbol for symbol in self._present_symbols if symbol['type'] == 'variable']
@property
def _present_constants(self):
return [symbol for symbol in self._present_symbols if symbol['type'] == 'constant']
@property
def equation(self):
return self._equation
@property
def min_equation(self):
return self._min_equ
@property
def max_equation(self):
return self._max_equ
def add_var(self, name, has_inverse_trig=False):
name = name.upper()
var, dvar = symbols(f'{name} d{name}')
self._symbols[name] = {
'type': 'variable',
'var': var, 'dvar': dvar,
'val': None, 'dval': None,
'test_val': random.uniform(0, 100) if not has_inverse_trig else random.uniform(-.5, .5),
'test_dval': random.uniform(0, 1) if not has_inverse_trig else random.uniform(-.5, .5)
}
return var
def add_const(self, name, value):
name = name.upper()
var = symbols(name)
self._symbols[name] = {
'type': 'constant',
'var': var, 'val': value
}
return var
def save_equation(self, expr, simplify_equ=True):
self._equation = simplify(expr) if simplify_equ else expr
self._compute_minmax_equations()
def _compute_minmax_equations(self):
possible_exprs = [self.equation]
max_predicted_vars = len(self._present_variables) * 2 + len(self._present_constants)
# recursively process all variables by creating a flattened tree
# i.e. for each unprocessed variable in an expression, make two duplicates with +/- dvar and add to possible expressions
for xexpr in possible_exprs:
for var in self._present_variables:
if not xexpr.has(var['var'] + var['dvar']) and not xexpr.has(var['var'] - var['dvar']):
possible_exprs.append(xexpr.subs(var['var'], var['var'] + var['dvar']))
possible_exprs.append(xexpr.subs(var['var'], var['var'] - var['dvar']))
# filter any expressions with unprocessed variables
possible_exprs = [xexpr for xexpr in possible_exprs if len(xexpr.free_symbols) == max_predicted_vars]
possible_exprs = list(set(possible_exprs))
# figure out which equations yield the lowest and highest values with uncertainties from the original value
possible_values = []
for xexpr in possible_exprs:
for var in self._present_variables:
xexpr = xexpr.subs(var['var'], var['test_val'])
xexpr = xexpr.subs(var['dvar'], var['test_dval'])
for var in self._present_constants:
xexpr = xexpr.subs(var['var'], var['val'])
possible_values.append(xexpr)
vmin = min(possible_values)
imin = possible_values.index(vmin)
vmax = max(possible_values)
imax = possible_values.index(vmax)
min_expr = possible_exprs[imin]
max_expr = possible_exprs[imax]
self._min_equ = min_expr
self._max_equ = max_expr
def display_minmax_equations(self):
if not self._has_equations:
raise Exception('Equation has not been saved!')
data = [
['Equation', 'Min equation', 'Max Equation'],
[pretty(self.equation), pretty(self.min_equation), pretty(self.max_equation)]
]
print(FancyTable(data).table)
def calc_uncertainties(self, var_map, dec_places=6):
variables = list(var_map.keys())
values = list(var_map.values())
# ensure that any singular values in variable map are 1 element arrays
for i in range(len(values)):
if type(values[i]) != list:
values[i] = [values[i]]
# only look at variables whose number of values was greater than 1
num_vars = len(variables)
num_values = list(set(len(value) for value in values if len(value) > 1))
if len(num_values) > 1:
raise Exception('Dimensions of specified values do not match')
# for all 1 element arrays, expand to the max size of the largest value array
for i in range(len(values)):
if len(values[i]) == 1:
values[i] = list(values[i]) * num_values[0]
# we go through the data row by row
value_group = transpose(values)
# prep the data table for displaying
data_table = [[*variables, 'value', 'min', 'max', 'final']]
for value_row in value_group:
equ = self.equation
min_equ = self.min_equation
max_equ = self.max_equation
# substitute input variables and respective uncertainties with given values
for i in range(num_vars):
var = variables[i]
val = value_row[i]
equ = equ.subs(var, val)
min_equ = min_equ.subs(var, val)
max_equ = max_equ.subs(var, val)
# substitute constant values
for symbol in self._present_constants:
equ = equ.subs(symbol['var'], symbol['val'])
min_equ = min_equ.subs(symbol['var'], symbol['val'])
max_equ = max_equ.subs(symbol['var'], symbol['val'])
# process any known mathematical constants
val = N(equ)
min_val = N(min_equ)
max_val = N(max_equ)
final_uncertainty = (max_val - min_val) / 2
# add variable values to data table
uncertainty_row = [round(min_val, dec_places), round(max_val, dec_places), round(final_uncertainty, dec_places)]
data_row = [*value_row, round(val, dec_places), *uncertainty_row]
data_table += [data_row]
print(FancyTable(data_table).table)