qnty 0.0.8__py3-none-any.whl → 0.1.0__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.
- qnty/__init__.py +140 -59
- qnty/constants/__init__.py +10 -0
- qnty/constants/numerical.py +18 -0
- qnty/constants/solvers.py +6 -0
- qnty/constants/tests.py +6 -0
- qnty/dimensions/__init__.py +23 -0
- qnty/dimensions/base.py +97 -0
- qnty/dimensions/field_dims.py +126 -0
- qnty/dimensions/field_dims.pyi +128 -0
- qnty/dimensions/signature.py +111 -0
- qnty/equations/__init__.py +4 -0
- qnty/equations/equation.py +220 -0
- qnty/equations/system.py +130 -0
- qnty/expressions/__init__.py +40 -0
- qnty/expressions/formatter.py +188 -0
- qnty/expressions/functions.py +74 -0
- qnty/expressions/nodes.py +701 -0
- qnty/expressions/types.py +70 -0
- qnty/extensions/plotting/__init__.py +0 -0
- qnty/extensions/reporting/__init__.py +0 -0
- qnty/problems/__init__.py +145 -0
- qnty/problems/composition.py +1031 -0
- qnty/problems/problem.py +695 -0
- qnty/problems/rules.py +145 -0
- qnty/problems/solving.py +1216 -0
- qnty/problems/validation.py +127 -0
- qnty/quantities/__init__.py +29 -0
- qnty/quantities/base_qnty.py +677 -0
- qnty/quantities/field_converters.py +24004 -0
- qnty/quantities/field_qnty.py +1012 -0
- qnty/quantities/field_setter.py +12320 -0
- qnty/quantities/field_vars.py +6325 -0
- qnty/quantities/field_vars.pyi +4191 -0
- qnty/solving/__init__.py +0 -0
- qnty/solving/manager.py +96 -0
- qnty/solving/order.py +403 -0
- qnty/solving/solvers/__init__.py +13 -0
- qnty/solving/solvers/base.py +82 -0
- qnty/solving/solvers/iterative.py +165 -0
- qnty/solving/solvers/simultaneous.py +475 -0
- qnty/units/__init__.py +1 -0
- qnty/units/field_units.py +10507 -0
- qnty/units/field_units.pyi +2461 -0
- qnty/units/prefixes.py +203 -0
- qnty/{unit.py → units/registry.py} +89 -61
- qnty/utils/__init__.py +16 -0
- qnty/utils/caching/__init__.py +23 -0
- qnty/utils/caching/manager.py +401 -0
- qnty/utils/error_handling/__init__.py +66 -0
- qnty/utils/error_handling/context.py +39 -0
- qnty/utils/error_handling/exceptions.py +96 -0
- qnty/utils/error_handling/handlers.py +171 -0
- qnty/utils/logging.py +40 -0
- qnty/utils/protocols.py +164 -0
- qnty/utils/scope_discovery.py +420 -0
- qnty-0.1.0.dist-info/METADATA +199 -0
- qnty-0.1.0.dist-info/RECORD +60 -0
- qnty/dimension.py +0 -186
- qnty/equation.py +0 -297
- qnty/expression.py +0 -553
- qnty/prefixes.py +0 -229
- qnty/unit_types/base.py +0 -47
- qnty/units.py +0 -8113
- qnty/variable.py +0 -300
- qnty/variable_types/base.py +0 -58
- qnty/variable_types/expression_variable.py +0 -106
- qnty/variable_types/typed_variable.py +0 -87
- qnty/variables.py +0 -2298
- qnty/variables.pyi +0 -6148
- qnty-0.0.8.dist-info/METADATA +0 -355
- qnty-0.0.8.dist-info/RECORD +0 -19
- /qnty/{unit_types → extensions}/__init__.py +0 -0
- /qnty/{variable_types → extensions/integration}/__init__.py +0 -0
- {qnty-0.0.8.dist-info → qnty-0.1.0.dist-info}/WHEEL +0 -0
qnty/expression.py
DELETED
@@ -1,553 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Expression System
|
3
|
-
=================
|
4
|
-
|
5
|
-
Mathematical expressions for building equation trees with qnty variables.
|
6
|
-
"""
|
7
|
-
|
8
|
-
import math
|
9
|
-
from abc import ABC, abstractmethod
|
10
|
-
from typing import Union, cast
|
11
|
-
|
12
|
-
from .units import DimensionlessUnits
|
13
|
-
|
14
|
-
# if TYPE_CHECKING:
|
15
|
-
from .variable import FastQuantity, TypeSafeVariable
|
16
|
-
|
17
|
-
|
18
|
-
def wrap_operand(operand: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
19
|
-
"""
|
20
|
-
Wrap non-Expression operands in appropriate Expression subclasses.
|
21
|
-
|
22
|
-
This function handles type conversion without circular imports by using
|
23
|
-
duck typing and delayed imports where necessary.
|
24
|
-
"""
|
25
|
-
# Type guard for Expression types
|
26
|
-
if hasattr(operand, 'evaluate') and hasattr(operand, 'get_variables'):
|
27
|
-
# Already an Expression
|
28
|
-
return cast('Expression', operand)
|
29
|
-
elif hasattr(operand, 'name') and hasattr(operand, 'quantity') and hasattr(operand, 'is_known'):
|
30
|
-
# TypeSafeVariable-like object
|
31
|
-
return VariableReference(cast('TypeSafeVariable', operand))
|
32
|
-
elif hasattr(operand, 'value') and hasattr(operand, 'unit') and hasattr(operand, '_dimension_sig'):
|
33
|
-
# FastQuantity-like object
|
34
|
-
return Constant(cast('FastQuantity', operand))
|
35
|
-
elif isinstance(operand, int | float):
|
36
|
-
# Numeric value - create dimensionless quantity
|
37
|
-
|
38
|
-
return Constant(FastQuantity(float(operand), DimensionlessUnits.dimensionless))
|
39
|
-
else:
|
40
|
-
raise TypeError(f"Cannot convert {type(operand)} to Expression")
|
41
|
-
|
42
|
-
|
43
|
-
class Expression(ABC):
|
44
|
-
"""Abstract base class for mathematical expressions."""
|
45
|
-
|
46
|
-
@abstractmethod
|
47
|
-
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> 'FastQuantity':
|
48
|
-
"""Evaluate the expression given variable values."""
|
49
|
-
pass
|
50
|
-
|
51
|
-
@abstractmethod
|
52
|
-
def get_variables(self) -> set[str]:
|
53
|
-
"""Get all variable symbols used in this expression."""
|
54
|
-
pass
|
55
|
-
|
56
|
-
@abstractmethod
|
57
|
-
def simplify(self) -> 'Expression':
|
58
|
-
"""Simplify the expression."""
|
59
|
-
pass
|
60
|
-
|
61
|
-
@abstractmethod
|
62
|
-
def __str__(self) -> str:
|
63
|
-
pass
|
64
|
-
|
65
|
-
def _discover_variables_from_scope(self) -> dict[str, 'TypeSafeVariable']:
|
66
|
-
"""Automatically discover variables from the calling scope."""
|
67
|
-
import inspect
|
68
|
-
|
69
|
-
# Get the frame that called this method (skip through __str__ calls)
|
70
|
-
frame = inspect.currentframe()
|
71
|
-
try:
|
72
|
-
# Skip frames until we find one outside the expression system
|
73
|
-
while frame and (
|
74
|
-
frame.f_code.co_filename.endswith('expression.py') or
|
75
|
-
frame.f_code.co_name in ['__str__', '__repr__']
|
76
|
-
):
|
77
|
-
frame = frame.f_back
|
78
|
-
|
79
|
-
if not frame:
|
80
|
-
return {}
|
81
|
-
|
82
|
-
# Combine local and global variables
|
83
|
-
all_vars = {**frame.f_globals, **frame.f_locals}
|
84
|
-
|
85
|
-
# Find TypeSafeVariable objects that match our required variables
|
86
|
-
required_vars = self.get_variables()
|
87
|
-
discovered = {}
|
88
|
-
|
89
|
-
for var_name in required_vars:
|
90
|
-
for name, obj in all_vars.items():
|
91
|
-
# Check if this is a TypeSafeVariable with matching symbol/name
|
92
|
-
if hasattr(obj, 'symbol') and obj.symbol == var_name:
|
93
|
-
discovered[var_name] = obj
|
94
|
-
break
|
95
|
-
elif hasattr(obj, 'name') and obj.name == var_name:
|
96
|
-
discovered[var_name] = obj
|
97
|
-
break
|
98
|
-
# Check if this is an Expression that can be evaluated to get the variable
|
99
|
-
elif hasattr(obj, 'get_variables') and name == var_name:
|
100
|
-
# This is an expression named after our variable - try to evaluate it
|
101
|
-
try:
|
102
|
-
if hasattr(obj, '_can_auto_evaluate'):
|
103
|
-
can_eval, expr_vars = obj._can_auto_evaluate()
|
104
|
-
if can_eval:
|
105
|
-
result = obj.evaluate(expr_vars)
|
106
|
-
# Create a temporary variable to hold the result
|
107
|
-
from .variables import Length # Import here to avoid circular import
|
108
|
-
temp_var = Length(result.value, result.unit.symbol, f"temp_{var_name}")
|
109
|
-
temp_var.symbol = var_name
|
110
|
-
discovered[var_name] = temp_var
|
111
|
-
break
|
112
|
-
except Exception:
|
113
|
-
pass
|
114
|
-
|
115
|
-
return discovered
|
116
|
-
|
117
|
-
finally:
|
118
|
-
del frame
|
119
|
-
|
120
|
-
def _can_auto_evaluate(self) -> tuple[bool, dict[str, 'TypeSafeVariable']]:
|
121
|
-
"""Check if expression can be auto-evaluated from scope."""
|
122
|
-
try:
|
123
|
-
discovered = self._discover_variables_from_scope()
|
124
|
-
required_vars = self.get_variables()
|
125
|
-
|
126
|
-
# Check if all required variables are available and have values
|
127
|
-
for var_name in required_vars:
|
128
|
-
if var_name not in discovered:
|
129
|
-
return False, {}
|
130
|
-
var = discovered[var_name]
|
131
|
-
if not hasattr(var, 'quantity') or var.quantity is None:
|
132
|
-
return False, {}
|
133
|
-
|
134
|
-
return True, discovered
|
135
|
-
|
136
|
-
except Exception:
|
137
|
-
return False, {}
|
138
|
-
|
139
|
-
def __add__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
140
|
-
return BinaryOperation('+', self, wrap_operand(other))
|
141
|
-
|
142
|
-
def __radd__(self, other: Union['TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
143
|
-
return BinaryOperation('+', wrap_operand(other), self)
|
144
|
-
|
145
|
-
def __sub__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
146
|
-
return BinaryOperation('-', self, wrap_operand(other))
|
147
|
-
|
148
|
-
def __rsub__(self, other: Union['TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
149
|
-
return BinaryOperation('-', wrap_operand(other), self)
|
150
|
-
|
151
|
-
def __mul__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
152
|
-
return BinaryOperation('*', self, wrap_operand(other))
|
153
|
-
|
154
|
-
def __rmul__(self, other: Union['TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
155
|
-
return BinaryOperation('*', wrap_operand(other), self)
|
156
|
-
|
157
|
-
def __truediv__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
158
|
-
return BinaryOperation('/', self, wrap_operand(other))
|
159
|
-
|
160
|
-
def __rtruediv__(self, other: Union['TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
161
|
-
return BinaryOperation('/', wrap_operand(other), self)
|
162
|
-
|
163
|
-
def __pow__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
164
|
-
return BinaryOperation('**', self, wrap_operand(other))
|
165
|
-
|
166
|
-
def __rpow__(self, other: Union['TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
167
|
-
return BinaryOperation('**', wrap_operand(other), self)
|
168
|
-
|
169
|
-
# Comparison operators for conditional expressions
|
170
|
-
def __lt__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'BinaryOperation':
|
171
|
-
return BinaryOperation('<', self, self._wrap_operand(other))
|
172
|
-
|
173
|
-
def __le__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'BinaryOperation':
|
174
|
-
return BinaryOperation('<=', self, self._wrap_operand(other))
|
175
|
-
|
176
|
-
def __gt__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'BinaryOperation':
|
177
|
-
return BinaryOperation('>', self, self._wrap_operand(other))
|
178
|
-
|
179
|
-
def __ge__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'BinaryOperation':
|
180
|
-
return BinaryOperation('>=', self, self._wrap_operand(other))
|
181
|
-
|
182
|
-
@staticmethod
|
183
|
-
def _wrap_operand(operand: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
184
|
-
"""Wrap non-Expression operands in appropriate Expression subclasses."""
|
185
|
-
return wrap_operand(operand)
|
186
|
-
|
187
|
-
|
188
|
-
class VariableReference(Expression):
|
189
|
-
"""Reference to a variable in an expression with performance optimizations."""
|
190
|
-
|
191
|
-
def __init__(self, variable: 'TypeSafeVariable'):
|
192
|
-
self.variable = variable
|
193
|
-
# Cache the name resolution to avoid repeated lookups
|
194
|
-
self._cached_name = None
|
195
|
-
self._last_symbol = None
|
196
|
-
|
197
|
-
@property
|
198
|
-
def name(self) -> str:
|
199
|
-
"""Get variable name with caching for performance."""
|
200
|
-
current_symbol = self.variable.symbol
|
201
|
-
if self._cached_name is None or self._last_symbol != current_symbol:
|
202
|
-
# Use symbol for optinova compatibility, fall back to name if symbol not set
|
203
|
-
self._cached_name = current_symbol if current_symbol else self.variable.name
|
204
|
-
self._last_symbol = current_symbol
|
205
|
-
return self._cached_name
|
206
|
-
|
207
|
-
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> 'FastQuantity':
|
208
|
-
try:
|
209
|
-
if self.name in variable_values:
|
210
|
-
var = variable_values[self.name]
|
211
|
-
if var.quantity is not None:
|
212
|
-
return var.quantity
|
213
|
-
elif self.variable.quantity is not None:
|
214
|
-
return self.variable.quantity
|
215
|
-
|
216
|
-
# If we reach here, no valid quantity was found
|
217
|
-
available_vars = list(variable_values.keys()) if variable_values else []
|
218
|
-
raise ValueError(
|
219
|
-
f"Cannot evaluate variable '{self.name}' without value. "
|
220
|
-
f"Available variables: {available_vars}"
|
221
|
-
)
|
222
|
-
except Exception as e:
|
223
|
-
if isinstance(e, ValueError):
|
224
|
-
raise
|
225
|
-
raise ValueError(f"Error evaluating variable '{self.name}': {e}") from e
|
226
|
-
|
227
|
-
def get_variables(self) -> set[str]:
|
228
|
-
return {self.name}
|
229
|
-
|
230
|
-
def simplify(self) -> 'Expression':
|
231
|
-
return self
|
232
|
-
|
233
|
-
def __str__(self) -> str:
|
234
|
-
return self.name
|
235
|
-
|
236
|
-
|
237
|
-
class Constant(Expression):
|
238
|
-
"""Constant value in an expression."""
|
239
|
-
|
240
|
-
def __init__(self, value: 'FastQuantity'):
|
241
|
-
self.value = value
|
242
|
-
|
243
|
-
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> 'FastQuantity':
|
244
|
-
del variable_values # Suppress unused variable warning
|
245
|
-
return self.value
|
246
|
-
|
247
|
-
def get_variables(self) -> set[str]:
|
248
|
-
return set()
|
249
|
-
|
250
|
-
def simplify(self) -> 'Expression':
|
251
|
-
return self
|
252
|
-
|
253
|
-
def __str__(self) -> str:
|
254
|
-
return str(self.value.value)
|
255
|
-
|
256
|
-
|
257
|
-
class BinaryOperation(Expression):
|
258
|
-
"""Binary operation between two expressions."""
|
259
|
-
|
260
|
-
def __init__(self, operator: str, left: Expression, right: Expression):
|
261
|
-
self.operator = operator
|
262
|
-
self.left = left
|
263
|
-
self.right = right
|
264
|
-
|
265
|
-
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> 'FastQuantity':
|
266
|
-
try:
|
267
|
-
left_val = self.left.evaluate(variable_values)
|
268
|
-
right_val = self.right.evaluate(variable_values)
|
269
|
-
|
270
|
-
if self.operator == '+':
|
271
|
-
return left_val + right_val
|
272
|
-
elif self.operator == '-':
|
273
|
-
return left_val - right_val
|
274
|
-
elif self.operator == '*':
|
275
|
-
return left_val * right_val
|
276
|
-
elif self.operator == '/':
|
277
|
-
# Check for division by zero
|
278
|
-
if abs(right_val.value) < 1e-15:
|
279
|
-
raise ValueError(f"Division by zero in expression: {self}")
|
280
|
-
return left_val / right_val
|
281
|
-
elif self.operator == '**':
|
282
|
-
# For power, right side should be dimensionless
|
283
|
-
if isinstance(right_val.value, int | float):
|
284
|
-
if right_val.value < 0 and left_val.value < 0:
|
285
|
-
raise ValueError(f"Negative base with negative exponent: {left_val.value}^{right_val.value}")
|
286
|
-
result_value = left_val.value ** right_val.value
|
287
|
-
# For power operations, we need to handle units carefully
|
288
|
-
# This is a simplified implementation
|
289
|
-
return FastQuantity(result_value, left_val.unit)
|
290
|
-
else:
|
291
|
-
raise ValueError("Exponent must be dimensionless number")
|
292
|
-
elif self.operator in ['<', '<=', '>', '>=', '==', '!=']:
|
293
|
-
# Comparison operations - return dimensionless 1.0 or 0.0
|
294
|
-
# Convert to same units for comparison if possible
|
295
|
-
try:
|
296
|
-
if left_val._dimension_sig == right_val._dimension_sig and left_val.unit != right_val.unit:
|
297
|
-
right_val = right_val.to(left_val.unit)
|
298
|
-
except (ValueError, TypeError, AttributeError):
|
299
|
-
pass
|
300
|
-
|
301
|
-
result = False # Initialize result
|
302
|
-
if self.operator == '<':
|
303
|
-
result = left_val.value < right_val.value
|
304
|
-
elif self.operator == '<=':
|
305
|
-
result = left_val.value <= right_val.value
|
306
|
-
elif self.operator == '>':
|
307
|
-
result = left_val.value > right_val.value
|
308
|
-
elif self.operator == '>=':
|
309
|
-
result = left_val.value >= right_val.value
|
310
|
-
elif self.operator == '==':
|
311
|
-
result = abs(left_val.value - right_val.value) < 1e-10
|
312
|
-
elif self.operator == '!=':
|
313
|
-
result = abs(left_val.value - right_val.value) >= 1e-10
|
314
|
-
|
315
|
-
return FastQuantity(1.0 if result else 0.0, DimensionlessUnits.dimensionless)
|
316
|
-
else:
|
317
|
-
raise ValueError(f"Unknown operator: {self.operator}")
|
318
|
-
except Exception as e:
|
319
|
-
if isinstance(e, ValueError):
|
320
|
-
raise
|
321
|
-
raise ValueError(f"Error evaluating binary operation '{self}': {e}") from e
|
322
|
-
|
323
|
-
def get_variables(self) -> set[str]:
|
324
|
-
return self.left.get_variables() | self.right.get_variables()
|
325
|
-
|
326
|
-
def simplify(self) -> Expression:
|
327
|
-
left_simplified = self.left.simplify()
|
328
|
-
right_simplified = self.right.simplify()
|
329
|
-
|
330
|
-
# Basic simplification rules
|
331
|
-
if isinstance(left_simplified, Constant) and isinstance(right_simplified, Constant):
|
332
|
-
# Evaluate constant expressions
|
333
|
-
dummy_vars = {}
|
334
|
-
try:
|
335
|
-
result = BinaryOperation(self.operator, left_simplified, right_simplified).evaluate(dummy_vars)
|
336
|
-
return Constant(result)
|
337
|
-
except (ValueError, TypeError, ArithmeticError):
|
338
|
-
pass
|
339
|
-
|
340
|
-
return BinaryOperation(self.operator, left_simplified, right_simplified)
|
341
|
-
|
342
|
-
def __str__(self) -> str:
|
343
|
-
# Try to auto-evaluate if all variables are available
|
344
|
-
can_eval, variables = self._can_auto_evaluate()
|
345
|
-
if can_eval:
|
346
|
-
try:
|
347
|
-
result = self.evaluate(variables)
|
348
|
-
return str(result)
|
349
|
-
except Exception:
|
350
|
-
pass # Fall back to symbolic representation
|
351
|
-
|
352
|
-
# Handle operator precedence for cleaner string representation
|
353
|
-
precedence = {'+': 1, '-': 1, '*': 2, '/': 2, '**': 3, '<': 0, '<=': 0, '>': 0, '>=': 0, '==': 0, '!=': 0}
|
354
|
-
left_str = str(self.left)
|
355
|
-
right_str = str(self.right)
|
356
|
-
|
357
|
-
# Add parentheses for left side when precedence is strictly lower
|
358
|
-
if isinstance(self.left, BinaryOperation) and precedence.get(self.left.operator, 0) < precedence.get(self.operator, 0):
|
359
|
-
left_str = f"({left_str})"
|
360
|
-
|
361
|
-
# CRITICAL FIX: For right side, add parentheses when:
|
362
|
-
# 1. Precedence is strictly lower, OR
|
363
|
-
# 2. Precedence is equal AND operation is left-associative (-, /)
|
364
|
-
if isinstance(self.right, BinaryOperation):
|
365
|
-
right_prec = precedence.get(self.right.operator, 0)
|
366
|
-
curr_prec = precedence.get(self.operator, 0)
|
367
|
-
|
368
|
-
# Need parentheses if:
|
369
|
-
# - Right has lower precedence, OR
|
370
|
-
# - Same precedence and current operator is left-associative (- or /)
|
371
|
-
if (right_prec < curr_prec or
|
372
|
-
(right_prec == curr_prec and self.operator in ['-', '/'])):
|
373
|
-
right_str = f"({right_str})"
|
374
|
-
|
375
|
-
return f"{left_str} {self.operator} {right_str}"
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
class UnaryFunction(Expression):
|
380
|
-
"""Unary mathematical function expression."""
|
381
|
-
|
382
|
-
def __init__(self, function_name: str, operand: Expression):
|
383
|
-
self.function_name = function_name
|
384
|
-
self.operand = operand
|
385
|
-
|
386
|
-
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> 'FastQuantity':
|
387
|
-
|
388
|
-
operand_val = self.operand.evaluate(variable_values)
|
389
|
-
|
390
|
-
if self.function_name == 'sin':
|
391
|
-
# Assume input is in radians, result is dimensionless
|
392
|
-
result_value = math.sin(operand_val.value)
|
393
|
-
return FastQuantity(result_value, DimensionlessUnits.dimensionless)
|
394
|
-
elif self.function_name == 'cos':
|
395
|
-
result_value = math.cos(operand_val.value)
|
396
|
-
return FastQuantity(result_value, DimensionlessUnits.dimensionless)
|
397
|
-
elif self.function_name == 'tan':
|
398
|
-
result_value = math.tan(operand_val.value)
|
399
|
-
return FastQuantity(result_value, DimensionlessUnits.dimensionless)
|
400
|
-
elif self.function_name == 'sqrt':
|
401
|
-
# For sqrt, we need to handle units carefully
|
402
|
-
result_value = math.sqrt(operand_val.value)
|
403
|
-
# This is simplified - proper unit handling would need dimensional analysis
|
404
|
-
return FastQuantity(result_value, operand_val.unit)
|
405
|
-
elif self.function_name == 'abs':
|
406
|
-
return FastQuantity(abs(operand_val.value), operand_val.unit)
|
407
|
-
elif self.function_name == 'ln':
|
408
|
-
# Natural log - input should be dimensionless
|
409
|
-
result_value = math.log(operand_val.value)
|
410
|
-
return FastQuantity(result_value, DimensionlessUnits.dimensionless)
|
411
|
-
elif self.function_name == 'log10':
|
412
|
-
result_value = math.log10(operand_val.value)
|
413
|
-
return FastQuantity(result_value, DimensionlessUnits.dimensionless)
|
414
|
-
elif self.function_name == 'exp':
|
415
|
-
# Exponential - input should be dimensionless
|
416
|
-
result_value = math.exp(operand_val.value)
|
417
|
-
return FastQuantity(result_value, DimensionlessUnits.dimensionless)
|
418
|
-
else:
|
419
|
-
raise ValueError(f"Unknown function: {self.function_name}")
|
420
|
-
|
421
|
-
def get_variables(self) -> set[str]:
|
422
|
-
return self.operand.get_variables()
|
423
|
-
|
424
|
-
def simplify(self) -> Expression:
|
425
|
-
simplified_operand = self.operand.simplify()
|
426
|
-
if isinstance(simplified_operand, Constant):
|
427
|
-
# Evaluate constant functions at compile time
|
428
|
-
try:
|
429
|
-
dummy_vars = {}
|
430
|
-
result = UnaryFunction(self.function_name, simplified_operand).evaluate(dummy_vars)
|
431
|
-
return Constant(result)
|
432
|
-
except (ValueError, TypeError, ArithmeticError):
|
433
|
-
pass
|
434
|
-
return UnaryFunction(self.function_name, simplified_operand)
|
435
|
-
|
436
|
-
def __str__(self) -> str:
|
437
|
-
return f"{self.function_name}({self.operand})"
|
438
|
-
|
439
|
-
|
440
|
-
class ConditionalExpression(Expression):
|
441
|
-
"""Conditional expression: if condition then true_expr else false_expr."""
|
442
|
-
|
443
|
-
def __init__(self, condition: Expression, true_expr: Expression, false_expr: Expression):
|
444
|
-
self.condition = condition
|
445
|
-
self.true_expr = true_expr
|
446
|
-
self.false_expr = false_expr
|
447
|
-
|
448
|
-
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> 'FastQuantity':
|
449
|
-
condition_val = self.condition.evaluate(variable_values)
|
450
|
-
# Consider non-zero as True
|
451
|
-
if abs(condition_val.value) > 1e-10:
|
452
|
-
return self.true_expr.evaluate(variable_values)
|
453
|
-
else:
|
454
|
-
return self.false_expr.evaluate(variable_values)
|
455
|
-
|
456
|
-
def get_variables(self) -> set[str]:
|
457
|
-
return (self.condition.get_variables() |
|
458
|
-
self.true_expr.get_variables() |
|
459
|
-
self.false_expr.get_variables())
|
460
|
-
|
461
|
-
def simplify(self) -> Expression:
|
462
|
-
simplified_condition = self.condition.simplify()
|
463
|
-
simplified_true = self.true_expr.simplify()
|
464
|
-
simplified_false = self.false_expr.simplify()
|
465
|
-
|
466
|
-
# If condition is constant, choose the appropriate branch
|
467
|
-
if isinstance(simplified_condition, Constant):
|
468
|
-
try:
|
469
|
-
dummy_vars = {}
|
470
|
-
condition_val = simplified_condition.evaluate(dummy_vars)
|
471
|
-
if abs(condition_val.value) > 1e-10:
|
472
|
-
return simplified_true
|
473
|
-
else:
|
474
|
-
return simplified_false
|
475
|
-
except (ValueError, TypeError, ArithmeticError):
|
476
|
-
pass
|
477
|
-
|
478
|
-
return ConditionalExpression(simplified_condition, simplified_true, simplified_false)
|
479
|
-
|
480
|
-
def __str__(self) -> str:
|
481
|
-
return f"if({self.condition}, {self.true_expr}, {self.false_expr})"
|
482
|
-
|
483
|
-
|
484
|
-
# Convenience functions for mathematical operations
|
485
|
-
def sin(expr: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float]) -> UnaryFunction:
|
486
|
-
"""Sine function."""
|
487
|
-
return UnaryFunction('sin', Expression._wrap_operand(expr))
|
488
|
-
|
489
|
-
def cos(expr: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float]) -> UnaryFunction:
|
490
|
-
"""Cosine function."""
|
491
|
-
return UnaryFunction('cos', Expression._wrap_operand(expr))
|
492
|
-
|
493
|
-
def tan(expr: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float]) -> UnaryFunction:
|
494
|
-
"""Tangent function."""
|
495
|
-
return UnaryFunction('tan', Expression._wrap_operand(expr))
|
496
|
-
|
497
|
-
def sqrt(expr: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float]) -> UnaryFunction:
|
498
|
-
"""Square root function."""
|
499
|
-
return UnaryFunction('sqrt', Expression._wrap_operand(expr))
|
500
|
-
|
501
|
-
def abs_expr(expr: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float]) -> UnaryFunction:
|
502
|
-
"""Absolute value function."""
|
503
|
-
return UnaryFunction('abs', Expression._wrap_operand(expr))
|
504
|
-
|
505
|
-
def ln(expr: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float]) -> UnaryFunction:
|
506
|
-
"""Natural logarithm function."""
|
507
|
-
return UnaryFunction('ln', Expression._wrap_operand(expr))
|
508
|
-
|
509
|
-
def log10(expr: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float]) -> UnaryFunction:
|
510
|
-
"""Base-10 logarithm function."""
|
511
|
-
return UnaryFunction('log10', Expression._wrap_operand(expr))
|
512
|
-
|
513
|
-
def exp(expr: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float]) -> UnaryFunction:
|
514
|
-
"""Exponential function."""
|
515
|
-
return UnaryFunction('exp', Expression._wrap_operand(expr))
|
516
|
-
|
517
|
-
def cond_expr(condition: Union[Expression, 'BinaryOperation'],
|
518
|
-
true_expr: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float],
|
519
|
-
false_expr: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float]) -> ConditionalExpression:
|
520
|
-
"""Conditional expression: if condition then true_expr else false_expr."""
|
521
|
-
return ConditionalExpression(
|
522
|
-
condition if isinstance(condition, Expression) else condition,
|
523
|
-
Expression._wrap_operand(true_expr),
|
524
|
-
Expression._wrap_operand(false_expr)
|
525
|
-
)
|
526
|
-
|
527
|
-
def min_expr(*expressions: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float]) -> Expression:
|
528
|
-
"""Minimum of multiple expressions."""
|
529
|
-
if len(expressions) < 2:
|
530
|
-
raise ValueError("min_expr requires at least 2 arguments")
|
531
|
-
|
532
|
-
wrapped_expressions = [Expression._wrap_operand(expr) for expr in expressions]
|
533
|
-
result = wrapped_expressions[0]
|
534
|
-
|
535
|
-
for expr in wrapped_expressions[1:]:
|
536
|
-
# min(a, b) = if(a < b, a, b)
|
537
|
-
result = cond_expr(result < expr, result, expr)
|
538
|
-
|
539
|
-
return result
|
540
|
-
|
541
|
-
def max_expr(*expressions: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float]) -> Expression:
|
542
|
-
"""Maximum of multiple expressions."""
|
543
|
-
if len(expressions) < 2:
|
544
|
-
raise ValueError("max_expr requires at least 2 arguments")
|
545
|
-
|
546
|
-
wrapped_expressions = [Expression._wrap_operand(expr) for expr in expressions]
|
547
|
-
result = wrapped_expressions[0]
|
548
|
-
|
549
|
-
for expr in wrapped_expressions[1:]:
|
550
|
-
# max(a, b) = if(a > b, a, b)
|
551
|
-
result = cond_expr(result > expr, result, expr)
|
552
|
-
|
553
|
-
return result
|