qnty 0.0.8__py3-none-any.whl → 0.0.9__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 -58
- qnty/_backup/problem_original.py +1251 -0
- qnty/_backup/quantity.py +63 -0
- qnty/codegen/cli.py +125 -0
- qnty/codegen/generators/data/unit_data.json +8807 -0
- qnty/codegen/generators/data_processor.py +345 -0
- qnty/codegen/generators/dimensions_gen.py +434 -0
- qnty/codegen/generators/doc_generator.py +141 -0
- qnty/codegen/generators/out/dimension_mapping.json +974 -0
- qnty/codegen/generators/out/dimension_metadata.json +123 -0
- qnty/codegen/generators/out/units_metadata.json +223 -0
- qnty/codegen/generators/quantities_gen.py +159 -0
- qnty/codegen/generators/setters_gen.py +178 -0
- qnty/codegen/generators/stubs_gen.py +167 -0
- qnty/codegen/generators/units_gen.py +295 -0
- qnty/codegen/generators/utils/__init__.py +0 -0
- qnty/equations/__init__.py +4 -0
- qnty/{equation.py → equations/equation.py} +78 -118
- qnty/equations/system.py +127 -0
- qnty/expressions/__init__.py +61 -0
- qnty/expressions/cache.py +94 -0
- qnty/expressions/functions.py +96 -0
- qnty/{expression.py → expressions/nodes.py} +209 -216
- qnty/generated/__init__.py +0 -0
- qnty/generated/dimensions.py +514 -0
- qnty/generated/quantities.py +6003 -0
- qnty/generated/quantities.pyi +4192 -0
- qnty/generated/setters.py +12210 -0
- qnty/generated/units.py +9798 -0
- qnty/problem/__init__.py +91 -0
- qnty/problem/base.py +142 -0
- qnty/problem/composition.py +385 -0
- qnty/problem/composition_mixin.py +382 -0
- qnty/problem/equations.py +413 -0
- qnty/problem/metaclass.py +302 -0
- qnty/problem/reconstruction.py +1016 -0
- qnty/problem/solving.py +180 -0
- qnty/problem/validation.py +64 -0
- qnty/problem/variables.py +239 -0
- qnty/quantities/__init__.py +6 -0
- qnty/quantities/expression_quantity.py +314 -0
- qnty/quantities/quantity.py +428 -0
- qnty/quantities/typed_quantity.py +215 -0
- qnty/solving/__init__.py +0 -0
- qnty/solving/manager.py +90 -0
- qnty/solving/order.py +355 -0
- qnty/solving/solvers/__init__.py +20 -0
- qnty/solving/solvers/base.py +92 -0
- qnty/solving/solvers/iterative.py +185 -0
- qnty/solving/solvers/simultaneous.py +547 -0
- qnty/units/__init__.py +0 -0
- qnty/{prefixes.py → units/prefixes.py} +54 -33
- qnty/{unit.py → units/registry.py} +73 -32
- qnty/utils/__init__.py +0 -0
- qnty/utils/logging.py +40 -0
- qnty/validation/__init__.py +0 -0
- qnty/validation/registry.py +0 -0
- qnty/validation/rules.py +167 -0
- qnty-0.0.9.dist-info/METADATA +199 -0
- qnty-0.0.9.dist-info/RECORD +63 -0
- qnty/dimension.py +0 -186
- 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 → codegen}/__init__.py +0 -0
- /qnty/{variable_types → codegen/generators}/__init__.py +0 -0
- {qnty-0.0.8.dist-info → qnty-0.0.9.dist-info}/WHEEL +0 -0
@@ -1,50 +1,32 @@
|
|
1
1
|
"""
|
2
|
-
Expression
|
3
|
-
|
2
|
+
Expression AST Nodes
|
3
|
+
===================
|
4
4
|
|
5
|
-
|
5
|
+
Core abstract syntax tree nodes for mathematical expressions.
|
6
6
|
"""
|
7
7
|
|
8
8
|
import math
|
9
9
|
from abc import ABC, abstractmethod
|
10
|
-
from typing import
|
10
|
+
from typing import TYPE_CHECKING, Union
|
11
11
|
|
12
|
-
|
12
|
+
if TYPE_CHECKING:
|
13
|
+
from ..quantities.quantity import Quantity, TypeSafeVariable
|
13
14
|
|
14
|
-
|
15
|
-
from .
|
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")
|
15
|
+
from ..generated.units import DimensionlessUnits
|
16
|
+
from ..quantities.quantity import Quantity, TypeSafeVariable
|
17
|
+
from .cache import _EXPRESSION_RESULT_CACHE, _MAX_EXPRESSION_CACHE_SIZE, wrap_operand
|
41
18
|
|
42
19
|
|
43
20
|
class Expression(ABC):
|
44
21
|
"""Abstract base class for mathematical expressions."""
|
45
22
|
|
23
|
+
# Class-level optimization settings
|
24
|
+
_scope_cache = {}
|
25
|
+
_auto_eval_enabled = False # Disabled by default for performance
|
26
|
+
_max_scope_cache_size = 100 # Limit scope cache size
|
27
|
+
|
46
28
|
@abstractmethod
|
47
|
-
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> '
|
29
|
+
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> 'Quantity':
|
48
30
|
"""Evaluate the expression given variable values."""
|
49
31
|
pass
|
50
32
|
|
@@ -63,55 +45,67 @@ class Expression(ABC):
|
|
63
45
|
pass
|
64
46
|
|
65
47
|
def _discover_variables_from_scope(self) -> dict[str, 'TypeSafeVariable']:
|
66
|
-
"""Automatically discover variables from the calling scope."""
|
48
|
+
"""Automatically discover variables from the calling scope (optimized)."""
|
49
|
+
# Skip if auto-evaluation is disabled
|
50
|
+
if not self._auto_eval_enabled:
|
51
|
+
return {}
|
52
|
+
|
53
|
+
# Check cache first with size limit
|
54
|
+
cache_key = id(self)
|
55
|
+
if cache_key in self._scope_cache:
|
56
|
+
return self._scope_cache[cache_key]
|
57
|
+
|
58
|
+
# Clean cache if it gets too large
|
59
|
+
if len(self._scope_cache) >= self._max_scope_cache_size:
|
60
|
+
self._scope_cache.clear()
|
61
|
+
|
67
62
|
import inspect
|
68
63
|
|
69
64
|
# Get the frame that called this method (skip through __str__ calls)
|
70
65
|
frame = inspect.currentframe()
|
71
66
|
try:
|
72
|
-
# Skip frames until we find one outside the expression system
|
73
|
-
|
67
|
+
# Skip frames until we find one outside the expression system (with depth limit)
|
68
|
+
depth = 0
|
69
|
+
max_depth = 6 # Reduced from unlimited for performance
|
70
|
+
while frame and depth < max_depth and (
|
74
71
|
frame.f_code.co_filename.endswith('expression.py') or
|
75
72
|
frame.f_code.co_name in ['__str__', '__repr__']
|
76
73
|
):
|
77
74
|
frame = frame.f_back
|
75
|
+
depth += 1
|
78
76
|
|
79
77
|
if not frame:
|
80
78
|
return {}
|
81
79
|
|
82
|
-
#
|
83
|
-
all_vars = {**frame.f_globals, **frame.f_locals}
|
84
|
-
|
85
|
-
# Find TypeSafeVariable objects that match our required variables
|
80
|
+
# Get required variables first to optimize search
|
86
81
|
required_vars = self.get_variables()
|
82
|
+
if not required_vars:
|
83
|
+
return {}
|
84
|
+
|
87
85
|
discovered = {}
|
88
86
|
|
87
|
+
# Search locals first (most common case)
|
88
|
+
local_vars = frame.f_locals
|
89
89
|
for var_name in required_vars:
|
90
|
-
|
91
|
-
|
92
|
-
|
90
|
+
# Direct lookup first (fastest)
|
91
|
+
if var_name in local_vars:
|
92
|
+
obj = local_vars[var_name]
|
93
|
+
if isinstance(obj, TypeSafeVariable):
|
93
94
|
discovered[var_name] = obj
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
95
|
+
continue
|
96
|
+
|
97
|
+
# Search globals only for remaining variables
|
98
|
+
if len(discovered) < len(required_vars):
|
99
|
+
global_vars = frame.f_globals
|
100
|
+
remaining_vars = required_vars - discovered.keys()
|
101
|
+
for var_name in remaining_vars:
|
102
|
+
if var_name in global_vars:
|
103
|
+
obj = global_vars[var_name]
|
104
|
+
if isinstance(obj, TypeSafeVariable):
|
105
|
+
discovered[var_name] = obj
|
114
106
|
|
107
|
+
# Cache the result
|
108
|
+
self._scope_cache[cache_key] = discovered
|
115
109
|
return discovered
|
116
110
|
|
117
111
|
finally:
|
@@ -136,57 +130,66 @@ class Expression(ABC):
|
|
136
130
|
except Exception:
|
137
131
|
return False, {}
|
138
132
|
|
139
|
-
def __add__(self, other: Union['Expression', 'TypeSafeVariable', '
|
133
|
+
def __add__(self, other: Union['Expression', 'TypeSafeVariable', 'Quantity', int, float]) -> 'Expression':
|
140
134
|
return BinaryOperation('+', self, wrap_operand(other))
|
141
135
|
|
142
|
-
def __radd__(self, other: Union['TypeSafeVariable', '
|
136
|
+
def __radd__(self, other: Union['TypeSafeVariable', 'Quantity', int, float]) -> 'Expression':
|
143
137
|
return BinaryOperation('+', wrap_operand(other), self)
|
144
138
|
|
145
|
-
def __sub__(self, other: Union['Expression', 'TypeSafeVariable', '
|
139
|
+
def __sub__(self, other: Union['Expression', 'TypeSafeVariable', 'Quantity', int, float]) -> 'Expression':
|
146
140
|
return BinaryOperation('-', self, wrap_operand(other))
|
147
141
|
|
148
|
-
def __rsub__(self, other: Union['TypeSafeVariable', '
|
142
|
+
def __rsub__(self, other: Union['TypeSafeVariable', 'Quantity', int, float]) -> 'Expression':
|
149
143
|
return BinaryOperation('-', wrap_operand(other), self)
|
150
144
|
|
151
|
-
def __mul__(self, other: Union['Expression', 'TypeSafeVariable', '
|
145
|
+
def __mul__(self, other: Union['Expression', 'TypeSafeVariable', 'Quantity', int, float]) -> 'Expression':
|
152
146
|
return BinaryOperation('*', self, wrap_operand(other))
|
153
147
|
|
154
|
-
def __rmul__(self, other: Union['TypeSafeVariable', '
|
148
|
+
def __rmul__(self, other: Union['TypeSafeVariable', 'Quantity', int, float]) -> 'Expression':
|
155
149
|
return BinaryOperation('*', wrap_operand(other), self)
|
156
150
|
|
157
|
-
def __truediv__(self, other: Union['Expression', 'TypeSafeVariable', '
|
151
|
+
def __truediv__(self, other: Union['Expression', 'TypeSafeVariable', 'Quantity', int, float]) -> 'Expression':
|
158
152
|
return BinaryOperation('/', self, wrap_operand(other))
|
159
153
|
|
160
|
-
def __rtruediv__(self, other: Union['TypeSafeVariable', '
|
154
|
+
def __rtruediv__(self, other: Union['TypeSafeVariable', 'Quantity', int, float]) -> 'Expression':
|
161
155
|
return BinaryOperation('/', wrap_operand(other), self)
|
162
156
|
|
163
|
-
def __pow__(self, other: Union['Expression', 'TypeSafeVariable', '
|
157
|
+
def __pow__(self, other: Union['Expression', 'TypeSafeVariable', 'Quantity', int, float]) -> 'Expression':
|
164
158
|
return BinaryOperation('**', self, wrap_operand(other))
|
165
159
|
|
166
|
-
def __rpow__(self, other: Union['TypeSafeVariable', '
|
160
|
+
def __rpow__(self, other: Union['TypeSafeVariable', 'Quantity', int, float]) -> 'Expression':
|
167
161
|
return BinaryOperation('**', wrap_operand(other), self)
|
168
162
|
|
169
|
-
|
170
|
-
|
171
|
-
return
|
163
|
+
def __abs__(self) -> 'Expression':
|
164
|
+
"""Absolute value of the expression."""
|
165
|
+
return UnaryFunction('abs', self)
|
166
|
+
|
167
|
+
# Comparison operators for conditional expressions (consolidated)
|
168
|
+
def _make_comparison(self, operator: str, other) -> 'BinaryOperation':
|
169
|
+
"""Helper method to create comparison operations."""
|
170
|
+
return BinaryOperation(operator, self, wrap_operand(other))
|
171
|
+
|
172
|
+
def __lt__(self, other: Union['Expression', 'TypeSafeVariable', 'Quantity', int, float]) -> 'BinaryOperation':
|
173
|
+
return self._make_comparison('<', other)
|
172
174
|
|
173
|
-
def __le__(self, other: Union['Expression', 'TypeSafeVariable', '
|
174
|
-
return
|
175
|
+
def __le__(self, other: Union['Expression', 'TypeSafeVariable', 'Quantity', int, float]) -> 'BinaryOperation':
|
176
|
+
return self._make_comparison('<=', other)
|
175
177
|
|
176
|
-
def __gt__(self, other: Union['Expression', 'TypeSafeVariable', '
|
177
|
-
return
|
178
|
+
def __gt__(self, other: Union['Expression', 'TypeSafeVariable', 'Quantity', int, float]) -> 'BinaryOperation':
|
179
|
+
return self._make_comparison('>', other)
|
178
180
|
|
179
|
-
def __ge__(self, other: Union['Expression', 'TypeSafeVariable', '
|
180
|
-
return
|
181
|
+
def __ge__(self, other: Union['Expression', 'TypeSafeVariable', 'Quantity', int, float]) -> 'BinaryOperation':
|
182
|
+
return self._make_comparison('>=', other)
|
181
183
|
|
182
184
|
@staticmethod
|
183
|
-
def _wrap_operand(operand: Union['Expression', 'TypeSafeVariable', '
|
185
|
+
def _wrap_operand(operand: Union['Expression', 'TypeSafeVariable', 'Quantity', int, float]) -> 'Expression':
|
184
186
|
"""Wrap non-Expression operands in appropriate Expression subclasses."""
|
185
187
|
return wrap_operand(operand)
|
186
188
|
|
187
189
|
|
188
190
|
class VariableReference(Expression):
|
189
191
|
"""Reference to a variable in an expression with performance optimizations."""
|
192
|
+
__slots__ = ('variable', '_cached_name', '_last_symbol')
|
190
193
|
|
191
194
|
def __init__(self, variable: 'TypeSafeVariable'):
|
192
195
|
self.variable = variable
|
@@ -204,7 +207,7 @@ class VariableReference(Expression):
|
|
204
207
|
self._last_symbol = current_symbol
|
205
208
|
return self._cached_name
|
206
209
|
|
207
|
-
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> '
|
210
|
+
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> 'Quantity':
|
208
211
|
try:
|
209
212
|
if self.name in variable_values:
|
210
213
|
var = variable_values[self.name]
|
@@ -236,11 +239,12 @@ class VariableReference(Expression):
|
|
236
239
|
|
237
240
|
class Constant(Expression):
|
238
241
|
"""Constant value in an expression."""
|
242
|
+
__slots__ = ('value',)
|
239
243
|
|
240
|
-
def __init__(self, value: '
|
244
|
+
def __init__(self, value: 'Quantity'):
|
241
245
|
self.value = value
|
242
246
|
|
243
|
-
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> '
|
247
|
+
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> 'Quantity':
|
244
248
|
del variable_values # Suppress unused variable warning
|
245
249
|
return self.value
|
246
250
|
|
@@ -256,70 +260,130 @@ class Constant(Expression):
|
|
256
260
|
|
257
261
|
class BinaryOperation(Expression):
|
258
262
|
"""Binary operation between two expressions."""
|
263
|
+
__slots__ = ('operator', 'left', 'right')
|
264
|
+
|
265
|
+
# Operator dispatch table for better performance
|
266
|
+
_ARITHMETIC_OPS = {'+', '-', '*', '/', '**'}
|
267
|
+
_COMPARISON_OPS = {'<', '<=', '>', '>=', '==', '!='}
|
259
268
|
|
260
269
|
def __init__(self, operator: str, left: Expression, right: Expression):
|
261
270
|
self.operator = operator
|
262
271
|
self.left = left
|
263
272
|
self.right = right
|
264
273
|
|
265
|
-
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> '
|
274
|
+
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> 'Quantity':
|
266
275
|
try:
|
276
|
+
# Fast path for constant expressions (both sides are constants)
|
277
|
+
if isinstance(self.left, Constant) and isinstance(self.right, Constant):
|
278
|
+
cache_key = (id(self), self.operator, id(self.left.value), id(self.right.value))
|
279
|
+
if cache_key in _EXPRESSION_RESULT_CACHE:
|
280
|
+
return _EXPRESSION_RESULT_CACHE[cache_key]
|
281
|
+
|
282
|
+
# Clean cache if it gets too large
|
283
|
+
if len(_EXPRESSION_RESULT_CACHE) >= _MAX_EXPRESSION_CACHE_SIZE:
|
284
|
+
_EXPRESSION_RESULT_CACHE.clear()
|
285
|
+
else:
|
286
|
+
cache_key = None
|
287
|
+
|
267
288
|
left_val = self.left.evaluate(variable_values)
|
268
289
|
right_val = self.right.evaluate(variable_values)
|
269
290
|
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
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)
|
291
|
+
# Fast dispatch for arithmetic operations
|
292
|
+
if self.operator in self._ARITHMETIC_OPS:
|
293
|
+
result = self._evaluate_arithmetic(left_val, right_val)
|
294
|
+
elif self.operator in self._COMPARISON_OPS:
|
295
|
+
result = self._evaluate_comparison(left_val, right_val)
|
316
296
|
else:
|
317
297
|
raise ValueError(f"Unknown operator: {self.operator}")
|
298
|
+
|
299
|
+
# Cache result for constant expressions
|
300
|
+
if cache_key is not None:
|
301
|
+
_EXPRESSION_RESULT_CACHE[cache_key] = result
|
302
|
+
|
303
|
+
return result
|
318
304
|
except Exception as e:
|
319
305
|
if isinstance(e, ValueError):
|
320
306
|
raise
|
321
307
|
raise ValueError(f"Error evaluating binary operation '{self}': {e}") from e
|
322
308
|
|
309
|
+
def _evaluate_arithmetic(self, left_val: 'Quantity', right_val: 'Quantity') -> 'Quantity':
|
310
|
+
"""Evaluate arithmetic operations with fast paths."""
|
311
|
+
# Fast path optimizations for common cases
|
312
|
+
if self.operator == '*':
|
313
|
+
# Fast path for multiplication by 1
|
314
|
+
if right_val.value == 1.0:
|
315
|
+
return left_val
|
316
|
+
elif left_val.value == 1.0:
|
317
|
+
return right_val
|
318
|
+
# Fast path for multiplication by 0
|
319
|
+
elif right_val.value == 0.0 or left_val.value == 0.0:
|
320
|
+
return Quantity(0.0, left_val.unit if right_val.value == 0.0 else right_val.unit)
|
321
|
+
return left_val * right_val
|
322
|
+
elif self.operator == '+':
|
323
|
+
# Fast path for addition with 0
|
324
|
+
if right_val.value == 0.0:
|
325
|
+
return left_val
|
326
|
+
elif left_val.value == 0.0:
|
327
|
+
return right_val
|
328
|
+
return left_val + right_val
|
329
|
+
elif self.operator == '-':
|
330
|
+
# Fast path for subtraction with 0
|
331
|
+
if right_val.value == 0.0:
|
332
|
+
return left_val
|
333
|
+
return left_val - right_val
|
334
|
+
elif self.operator == '/':
|
335
|
+
# Check for division by zero
|
336
|
+
if abs(right_val.value) < 1e-15:
|
337
|
+
raise ValueError(f"Division by zero in expression: {self}")
|
338
|
+
# Fast path for division by 1
|
339
|
+
if right_val.value == 1.0:
|
340
|
+
return left_val
|
341
|
+
return left_val / right_val
|
342
|
+
elif self.operator == '**':
|
343
|
+
# For power, right side should be dimensionless
|
344
|
+
if isinstance(right_val.value, int | float):
|
345
|
+
# Fast paths for common exponents
|
346
|
+
if right_val.value == 1.0:
|
347
|
+
return left_val
|
348
|
+
elif right_val.value == 0.0:
|
349
|
+
return Quantity(1.0, DimensionlessUnits.dimensionless)
|
350
|
+
elif right_val.value == 2.0:
|
351
|
+
return left_val * left_val # Use multiplication for squaring
|
352
|
+
|
353
|
+
if right_val.value < 0 and left_val.value < 0:
|
354
|
+
raise ValueError(f"Negative base with negative exponent: {left_val.value}^{right_val.value}")
|
355
|
+
result_value = left_val.value ** right_val.value
|
356
|
+
# For power operations, we need to handle units carefully
|
357
|
+
# This is a simplified implementation
|
358
|
+
return Quantity(result_value, left_val.unit)
|
359
|
+
else:
|
360
|
+
raise ValueError("Exponent must be dimensionless number")
|
361
|
+
else:
|
362
|
+
# Unknown operator - should not happen
|
363
|
+
raise ValueError(f"Unknown arithmetic operator: {self.operator}")
|
364
|
+
|
365
|
+
def _evaluate_comparison(self, left_val: 'Quantity', right_val: 'Quantity') -> 'Quantity':
|
366
|
+
"""Evaluate comparison operations."""
|
367
|
+
# Convert to same units for comparison if possible
|
368
|
+
try:
|
369
|
+
if left_val._dimension_sig == right_val._dimension_sig and left_val.unit != right_val.unit:
|
370
|
+
right_val = right_val.to(left_val.unit)
|
371
|
+
except (ValueError, TypeError, AttributeError):
|
372
|
+
pass
|
373
|
+
|
374
|
+
# Use dispatch dictionary for comparisons
|
375
|
+
ops = {
|
376
|
+
'<': lambda left, right: left < right,
|
377
|
+
'<=': lambda left, right: left <= right,
|
378
|
+
'>': lambda left, right: left > right,
|
379
|
+
'>=': lambda left, right: left >= right,
|
380
|
+
'==': lambda left, right: abs(left - right) < 1e-10,
|
381
|
+
'!=': lambda left, right: abs(left - right) >= 1e-10
|
382
|
+
}
|
383
|
+
|
384
|
+
result = ops[self.operator](left_val.value, right_val.value)
|
385
|
+
return Quantity(1.0 if result else 0.0, DimensionlessUnits.dimensionless)
|
386
|
+
|
323
387
|
def get_variables(self) -> set[str]:
|
324
388
|
return self.left.get_variables() | self.right.get_variables()
|
325
389
|
|
@@ -375,46 +439,46 @@ class BinaryOperation(Expression):
|
|
375
439
|
return f"{left_str} {self.operator} {right_str}"
|
376
440
|
|
377
441
|
|
378
|
-
|
379
442
|
class UnaryFunction(Expression):
|
380
443
|
"""Unary mathematical function expression."""
|
444
|
+
__slots__ = ('function_name', 'operand')
|
381
445
|
|
382
446
|
def __init__(self, function_name: str, operand: Expression):
|
383
447
|
self.function_name = function_name
|
384
448
|
self.operand = operand
|
385
449
|
|
386
|
-
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> '
|
450
|
+
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> 'Quantity':
|
387
451
|
|
388
452
|
operand_val = self.operand.evaluate(variable_values)
|
389
453
|
|
390
454
|
if self.function_name == 'sin':
|
391
455
|
# Assume input is in radians, result is dimensionless
|
392
456
|
result_value = math.sin(operand_val.value)
|
393
|
-
return
|
457
|
+
return Quantity(result_value, DimensionlessUnits.dimensionless)
|
394
458
|
elif self.function_name == 'cos':
|
395
459
|
result_value = math.cos(operand_val.value)
|
396
|
-
return
|
460
|
+
return Quantity(result_value, DimensionlessUnits.dimensionless)
|
397
461
|
elif self.function_name == 'tan':
|
398
462
|
result_value = math.tan(operand_val.value)
|
399
|
-
return
|
463
|
+
return Quantity(result_value, DimensionlessUnits.dimensionless)
|
400
464
|
elif self.function_name == 'sqrt':
|
401
465
|
# For sqrt, we need to handle units carefully
|
402
466
|
result_value = math.sqrt(operand_val.value)
|
403
467
|
# This is simplified - proper unit handling would need dimensional analysis
|
404
|
-
return
|
468
|
+
return Quantity(result_value, operand_val.unit)
|
405
469
|
elif self.function_name == 'abs':
|
406
|
-
return
|
470
|
+
return Quantity(abs(operand_val.value), operand_val.unit)
|
407
471
|
elif self.function_name == 'ln':
|
408
472
|
# Natural log - input should be dimensionless
|
409
473
|
result_value = math.log(operand_val.value)
|
410
|
-
return
|
474
|
+
return Quantity(result_value, DimensionlessUnits.dimensionless)
|
411
475
|
elif self.function_name == 'log10':
|
412
476
|
result_value = math.log10(operand_val.value)
|
413
|
-
return
|
477
|
+
return Quantity(result_value, DimensionlessUnits.dimensionless)
|
414
478
|
elif self.function_name == 'exp':
|
415
479
|
# Exponential - input should be dimensionless
|
416
480
|
result_value = math.exp(operand_val.value)
|
417
|
-
return
|
481
|
+
return Quantity(result_value, DimensionlessUnits.dimensionless)
|
418
482
|
else:
|
419
483
|
raise ValueError(f"Unknown function: {self.function_name}")
|
420
484
|
|
@@ -439,13 +503,14 @@ class UnaryFunction(Expression):
|
|
439
503
|
|
440
504
|
class ConditionalExpression(Expression):
|
441
505
|
"""Conditional expression: if condition then true_expr else false_expr."""
|
506
|
+
__slots__ = ('condition', 'true_expr', 'false_expr')
|
442
507
|
|
443
508
|
def __init__(self, condition: Expression, true_expr: Expression, false_expr: Expression):
|
444
509
|
self.condition = condition
|
445
510
|
self.true_expr = true_expr
|
446
511
|
self.false_expr = false_expr
|
447
512
|
|
448
|
-
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> '
|
513
|
+
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> 'Quantity':
|
449
514
|
condition_val = self.condition.evaluate(variable_values)
|
450
515
|
# Consider non-zero as True
|
451
516
|
if abs(condition_val.value) > 1e-10:
|
@@ -479,75 +544,3 @@ class ConditionalExpression(Expression):
|
|
479
544
|
|
480
545
|
def __str__(self) -> str:
|
481
546
|
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
|
File without changes
|