qnty 0.0.9__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 +2 -3
- 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 +1 -1
- qnty/equations/equation.py +118 -155
- qnty/equations/system.py +68 -65
- qnty/expressions/__init__.py +25 -46
- qnty/expressions/formatter.py +188 -0
- qnty/expressions/functions.py +46 -68
- qnty/expressions/nodes.py +539 -384
- qnty/expressions/types.py +70 -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 +28 -5
- qnty/quantities/base_qnty.py +677 -0
- qnty/quantities/field_converters.py +24004 -0
- qnty/quantities/field_qnty.py +1012 -0
- qnty/{generated/setters.py → quantities/field_setter.py} +3071 -2961
- qnty/{generated/quantities.py → quantities/field_vars.py} +754 -432
- qnty/{generated/quantities.pyi → quantities/field_vars.pyi} +1289 -1290
- qnty/solving/manager.py +50 -44
- qnty/solving/order.py +181 -133
- qnty/solving/solvers/__init__.py +2 -9
- qnty/solving/solvers/base.py +27 -37
- qnty/solving/solvers/iterative.py +115 -135
- qnty/solving/solvers/simultaneous.py +93 -165
- qnty/units/__init__.py +1 -0
- qnty/{generated/units.py → units/field_units.py} +1700 -991
- qnty/units/field_units.pyi +2461 -0
- qnty/units/prefixes.py +58 -105
- qnty/units/registry.py +76 -89
- 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 +4 -4
- qnty/utils/protocols.py +164 -0
- qnty/utils/scope_discovery.py +420 -0
- {qnty-0.0.9.dist-info → qnty-0.1.0.dist-info}/METADATA +1 -1
- qnty-0.1.0.dist-info/RECORD +60 -0
- qnty/_backup/problem_original.py +0 -1251
- qnty/_backup/quantity.py +0 -63
- qnty/codegen/cli.py +0 -125
- qnty/codegen/generators/data/unit_data.json +0 -8807
- qnty/codegen/generators/data_processor.py +0 -345
- qnty/codegen/generators/dimensions_gen.py +0 -434
- qnty/codegen/generators/doc_generator.py +0 -141
- qnty/codegen/generators/out/dimension_mapping.json +0 -974
- qnty/codegen/generators/out/dimension_metadata.json +0 -123
- qnty/codegen/generators/out/units_metadata.json +0 -223
- qnty/codegen/generators/quantities_gen.py +0 -159
- qnty/codegen/generators/setters_gen.py +0 -178
- qnty/codegen/generators/stubs_gen.py +0 -167
- qnty/codegen/generators/units_gen.py +0 -295
- qnty/expressions/cache.py +0 -94
- qnty/generated/dimensions.py +0 -514
- qnty/problem/__init__.py +0 -91
- qnty/problem/base.py +0 -142
- qnty/problem/composition.py +0 -385
- qnty/problem/composition_mixin.py +0 -382
- qnty/problem/equations.py +0 -413
- qnty/problem/metaclass.py +0 -302
- qnty/problem/reconstruction.py +0 -1016
- qnty/problem/solving.py +0 -180
- qnty/problem/validation.py +0 -64
- qnty/problem/variables.py +0 -239
- qnty/quantities/expression_quantity.py +0 -314
- qnty/quantities/quantity.py +0 -428
- qnty/quantities/typed_quantity.py +0 -215
- qnty/validation/__init__.py +0 -0
- qnty/validation/registry.py +0 -0
- qnty/validation/rules.py +0 -167
- qnty-0.0.9.dist-info/RECORD +0 -63
- /qnty/{codegen → extensions}/__init__.py +0 -0
- /qnty/{codegen/generators → extensions/integration}/__init__.py +0 -0
- /qnty/{codegen/generators/utils → extensions/plotting}/__init__.py +0 -0
- /qnty/{generated → extensions/reporting}/__init__.py +0 -0
- {qnty-0.0.9.dist-info → qnty-0.1.0.dist-info}/WHEEL +0 -0
qnty/expressions/nodes.py
CHANGED
@@ -7,196 +7,127 @@ Core abstract syntax tree nodes for mathematical expressions.
|
|
7
7
|
|
8
8
|
import math
|
9
9
|
from abc import ABC, abstractmethod
|
10
|
-
from typing import TYPE_CHECKING, Union
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
from ..
|
16
|
-
from ..
|
17
|
-
from .
|
11
|
+
from ..constants import CONDITION_EVALUATION_THRESHOLD, DIVISION_BY_ZERO_THRESHOLD, FLOAT_EQUALITY_TOLERANCE
|
12
|
+
from ..quantities import FieldQnty, Quantity
|
13
|
+
from ..units.field_units import DimensionlessUnits
|
14
|
+
from ..utils.caching.manager import get_cache_manager
|
15
|
+
from ..utils.protocols import register_expression_type, register_variable_type
|
16
|
+
from ..utils.scope_discovery import ScopeDiscoveryService
|
17
|
+
from .formatter import ExpressionFormatter
|
18
18
|
|
19
19
|
|
20
20
|
class Expression(ABC):
|
21
21
|
"""Abstract base class for mathematical expressions."""
|
22
|
-
|
22
|
+
|
23
23
|
# Class-level optimization settings
|
24
|
-
_scope_cache = {}
|
25
24
|
_auto_eval_enabled = False # Disabled by default for performance
|
26
|
-
|
27
|
-
|
25
|
+
|
28
26
|
@abstractmethod
|
29
|
-
def evaluate(self, variable_values: dict[str,
|
27
|
+
def evaluate(self, variable_values: dict[str, "FieldQnty"]) -> "Quantity":
|
30
28
|
"""Evaluate the expression given variable values."""
|
31
29
|
pass
|
32
|
-
|
30
|
+
|
33
31
|
@abstractmethod
|
34
32
|
def get_variables(self) -> set[str]:
|
35
33
|
"""Get all variable symbols used in this expression."""
|
36
34
|
pass
|
37
|
-
|
35
|
+
|
38
36
|
@abstractmethod
|
39
|
-
def simplify(self) ->
|
37
|
+
def simplify(self) -> "Expression":
|
40
38
|
"""Simplify the expression."""
|
41
39
|
pass
|
42
|
-
|
40
|
+
|
43
41
|
@abstractmethod
|
44
42
|
def __str__(self) -> str:
|
45
43
|
pass
|
46
|
-
|
47
|
-
def _discover_variables_from_scope(self) -> dict[str,
|
48
|
-
"""Automatically discover variables from the calling scope
|
44
|
+
|
45
|
+
def _discover_variables_from_scope(self) -> dict[str, "FieldQnty"]:
|
46
|
+
"""Automatically discover variables from the calling scope using centralized service."""
|
49
47
|
# Skip if auto-evaluation is disabled
|
50
48
|
if not self._auto_eval_enabled:
|
51
49
|
return {}
|
52
|
-
|
53
|
-
#
|
54
|
-
|
55
|
-
if
|
56
|
-
return
|
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
|
-
# 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
|
106
|
-
|
107
|
-
# Cache the result
|
108
|
-
self._scope_cache[cache_key] = discovered
|
109
|
-
return discovered
|
110
|
-
|
111
|
-
finally:
|
112
|
-
del frame
|
113
|
-
|
114
|
-
def _can_auto_evaluate(self) -> tuple[bool, dict[str, 'TypeSafeVariable']]:
|
115
|
-
"""Check if expression can be auto-evaluated from scope."""
|
116
|
-
try:
|
117
|
-
discovered = self._discover_variables_from_scope()
|
118
|
-
required_vars = self.get_variables()
|
119
|
-
|
120
|
-
# Check if all required variables are available and have values
|
121
|
-
for var_name in required_vars:
|
122
|
-
if var_name not in discovered:
|
123
|
-
return False, {}
|
124
|
-
var = discovered[var_name]
|
125
|
-
if not hasattr(var, 'quantity') or var.quantity is None:
|
126
|
-
return False, {}
|
127
|
-
|
128
|
-
return True, discovered
|
129
|
-
|
130
|
-
except Exception:
|
131
|
-
return False, {}
|
132
|
-
|
133
|
-
def __add__(self, other: Union['Expression', 'TypeSafeVariable', 'Quantity', int, float]) -> 'Expression':
|
134
|
-
return BinaryOperation('+', self, wrap_operand(other))
|
135
|
-
|
136
|
-
def __radd__(self, other: Union['TypeSafeVariable', 'Quantity', int, float]) -> 'Expression':
|
137
|
-
return BinaryOperation('+', wrap_operand(other), self)
|
138
|
-
|
139
|
-
def __sub__(self, other: Union['Expression', 'TypeSafeVariable', 'Quantity', int, float]) -> 'Expression':
|
140
|
-
return BinaryOperation('-', self, wrap_operand(other))
|
141
|
-
|
142
|
-
def __rsub__(self, other: Union['TypeSafeVariable', 'Quantity', int, float]) -> 'Expression':
|
143
|
-
return BinaryOperation('-', wrap_operand(other), self)
|
144
|
-
|
145
|
-
def __mul__(self, other: Union['Expression', 'TypeSafeVariable', 'Quantity', int, float]) -> 'Expression':
|
146
|
-
return BinaryOperation('*', self, wrap_operand(other))
|
147
|
-
|
148
|
-
def __rmul__(self, other: Union['TypeSafeVariable', 'Quantity', int, float]) -> 'Expression':
|
149
|
-
return BinaryOperation('*', wrap_operand(other), self)
|
150
|
-
|
151
|
-
def __truediv__(self, other: Union['Expression', 'TypeSafeVariable', 'Quantity', int, float]) -> 'Expression':
|
152
|
-
return BinaryOperation('/', self, wrap_operand(other))
|
153
|
-
|
154
|
-
def __rtruediv__(self, other: Union['TypeSafeVariable', 'Quantity', int, float]) -> 'Expression':
|
155
|
-
return BinaryOperation('/', wrap_operand(other), self)
|
156
|
-
|
157
|
-
def __pow__(self, other: Union['Expression', 'TypeSafeVariable', 'Quantity', int, float]) -> 'Expression':
|
158
|
-
return BinaryOperation('**', self, wrap_operand(other))
|
159
|
-
|
160
|
-
def __rpow__(self, other: Union['TypeSafeVariable', 'Quantity', int, float]) -> 'Expression':
|
161
|
-
return BinaryOperation('**', wrap_operand(other), self)
|
162
|
-
|
163
|
-
def __abs__(self) -> 'Expression':
|
50
|
+
|
51
|
+
# Get required variables first to optimize search
|
52
|
+
required_vars = self.get_variables()
|
53
|
+
if not required_vars:
|
54
|
+
return {}
|
55
|
+
|
56
|
+
# Use centralized scope discovery service
|
57
|
+
return ScopeDiscoveryService.discover_variables(required_vars, enable_caching=True)
|
58
|
+
|
59
|
+
def _can_auto_evaluate(self) -> tuple[bool, dict[str, "FieldQnty"]]:
|
60
|
+
"""Check if expression can be auto-evaluated from scope using centralized service."""
|
61
|
+
# Use centralized scope discovery service
|
62
|
+
return ScopeDiscoveryService.can_auto_evaluate(self)
|
63
|
+
|
64
|
+
def __add__(self, other: "OperandType") -> "Expression":
|
65
|
+
return BinaryOperation("+", self, wrap_operand(other))
|
66
|
+
|
67
|
+
def __radd__(self, other: "OperandType") -> "Expression":
|
68
|
+
return BinaryOperation("+", wrap_operand(other), self)
|
69
|
+
|
70
|
+
def __sub__(self, other: "OperandType") -> "Expression":
|
71
|
+
return BinaryOperation("-", self, wrap_operand(other))
|
72
|
+
|
73
|
+
def __rsub__(self, other: "OperandType") -> "Expression":
|
74
|
+
return BinaryOperation("-", wrap_operand(other), self)
|
75
|
+
|
76
|
+
def __mul__(self, other: "OperandType") -> "Expression":
|
77
|
+
return BinaryOperation("*", self, wrap_operand(other))
|
78
|
+
|
79
|
+
def __rmul__(self, other: "OperandType") -> "Expression":
|
80
|
+
return BinaryOperation("*", wrap_operand(other), self)
|
81
|
+
|
82
|
+
def __truediv__(self, other: "OperandType") -> "Expression":
|
83
|
+
return BinaryOperation("/", self, wrap_operand(other))
|
84
|
+
|
85
|
+
def __rtruediv__(self, other: "OperandType") -> "Expression":
|
86
|
+
return BinaryOperation("/", wrap_operand(other), self)
|
87
|
+
|
88
|
+
def __pow__(self, other: "OperandType") -> "Expression":
|
89
|
+
return BinaryOperation("**", self, wrap_operand(other))
|
90
|
+
|
91
|
+
def __rpow__(self, other: "OperandType") -> "Expression":
|
92
|
+
return BinaryOperation("**", wrap_operand(other), self)
|
93
|
+
|
94
|
+
def __abs__(self) -> "Expression":
|
164
95
|
"""Absolute value of the expression."""
|
165
|
-
return UnaryFunction(
|
166
|
-
|
96
|
+
return UnaryFunction("abs", self)
|
97
|
+
|
167
98
|
# Comparison operators for conditional expressions (consolidated)
|
168
|
-
def _make_comparison(self, operator: str, other) ->
|
99
|
+
def _make_comparison(self, operator: str, other) -> "BinaryOperation":
|
169
100
|
"""Helper method to create comparison operations."""
|
170
101
|
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)
|
174
102
|
|
175
|
-
def
|
176
|
-
return self._make_comparison(
|
177
|
-
|
178
|
-
def
|
179
|
-
return self._make_comparison(
|
180
|
-
|
181
|
-
def
|
182
|
-
return self._make_comparison(
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
103
|
+
def __lt__(self, other: "OperandType") -> "BinaryOperation":
|
104
|
+
return self._make_comparison("<", other)
|
105
|
+
|
106
|
+
def __le__(self, other: "OperandType") -> "BinaryOperation":
|
107
|
+
return self._make_comparison("<=", other)
|
108
|
+
|
109
|
+
def __gt__(self, other: "OperandType") -> "BinaryOperation":
|
110
|
+
return self._make_comparison(">", other)
|
111
|
+
|
112
|
+
def __ge__(self, other: "OperandType") -> "BinaryOperation":
|
113
|
+
return self._make_comparison(">=", other)
|
114
|
+
|
115
|
+
|
116
|
+
# Type aliases to reduce repetition
|
117
|
+
OperandType = Expression | FieldQnty | Quantity | int | float
|
188
118
|
|
189
119
|
|
190
120
|
class VariableReference(Expression):
|
191
121
|
"""Reference to a variable in an expression with performance optimizations."""
|
192
|
-
|
193
|
-
|
194
|
-
|
122
|
+
|
123
|
+
__slots__ = ("variable", "_cached_name", "_last_symbol")
|
124
|
+
|
125
|
+
def __init__(self, variable: "FieldQnty"):
|
195
126
|
self.variable = variable
|
196
127
|
# Cache the name resolution to avoid repeated lookups
|
197
128
|
self._cached_name = None
|
198
129
|
self._last_symbol = None
|
199
|
-
|
130
|
+
|
200
131
|
@property
|
201
132
|
def name(self) -> str:
|
202
133
|
"""Get variable name with caching for performance."""
|
@@ -207,7 +138,7 @@ class VariableReference(Expression):
|
|
207
138
|
self._last_symbol = current_symbol
|
208
139
|
return self._cached_name
|
209
140
|
|
210
|
-
def evaluate(self, variable_values: dict[str,
|
141
|
+
def evaluate(self, variable_values: dict[str, "FieldQnty"]) -> "Quantity":
|
211
142
|
try:
|
212
143
|
if self.name in variable_values:
|
213
144
|
var = variable_values[self.name]
|
@@ -215,279 +146,387 @@ class VariableReference(Expression):
|
|
215
146
|
return var.quantity
|
216
147
|
elif self.variable.quantity is not None:
|
217
148
|
return self.variable.quantity
|
218
|
-
|
149
|
+
|
219
150
|
# If we reach here, no valid quantity was found
|
220
151
|
available_vars = list(variable_values.keys()) if variable_values else []
|
221
|
-
raise ValueError(
|
222
|
-
f"Cannot evaluate variable '{self.name}' without value. "
|
223
|
-
f"Available variables: {available_vars}"
|
224
|
-
)
|
152
|
+
raise ValueError(f"Cannot evaluate variable '{self.name}' without value. Available variables: {available_vars}")
|
225
153
|
except Exception as e:
|
226
154
|
if isinstance(e, ValueError):
|
227
155
|
raise
|
228
156
|
raise ValueError(f"Error evaluating variable '{self.name}': {e}") from e
|
229
|
-
|
157
|
+
|
230
158
|
def get_variables(self) -> set[str]:
|
231
159
|
return {self.name}
|
232
|
-
|
233
|
-
def simplify(self) ->
|
160
|
+
|
161
|
+
def simplify(self) -> "Expression":
|
234
162
|
return self
|
235
|
-
|
163
|
+
|
236
164
|
def __str__(self) -> str:
|
237
|
-
return self
|
165
|
+
return ExpressionFormatter.format_variable_reference(self) # type: ignore[arg-type]
|
238
166
|
|
239
167
|
|
240
168
|
class Constant(Expression):
|
241
169
|
"""Constant value in an expression."""
|
242
|
-
|
243
|
-
|
244
|
-
|
170
|
+
|
171
|
+
__slots__ = ("value",)
|
172
|
+
|
173
|
+
def __init__(self, value: "Quantity"):
|
245
174
|
self.value = value
|
246
|
-
|
247
|
-
def evaluate(self, variable_values: dict[str,
|
175
|
+
|
176
|
+
def evaluate(self, variable_values: dict[str, "FieldQnty"]) -> "Quantity":
|
248
177
|
del variable_values # Suppress unused variable warning
|
249
178
|
return self.value
|
250
|
-
|
179
|
+
|
251
180
|
def get_variables(self) -> set[str]:
|
252
181
|
return set()
|
253
|
-
|
254
|
-
def simplify(self) ->
|
182
|
+
|
183
|
+
def simplify(self) -> "Expression":
|
255
184
|
return self
|
256
|
-
|
185
|
+
|
257
186
|
def __str__(self) -> str:
|
258
|
-
return
|
187
|
+
return ExpressionFormatter.format_constant(self) # type: ignore[arg-type]
|
259
188
|
|
260
189
|
|
261
190
|
class BinaryOperation(Expression):
|
262
191
|
"""Binary operation between two expressions."""
|
263
|
-
|
264
|
-
|
192
|
+
|
193
|
+
__slots__ = ("operator", "left", "right")
|
194
|
+
|
265
195
|
# Operator dispatch table for better performance
|
266
|
-
_ARITHMETIC_OPS = {
|
267
|
-
_COMPARISON_OPS = {
|
268
|
-
|
196
|
+
_ARITHMETIC_OPS = {"+", "-", "*", "/", "**"}
|
197
|
+
_COMPARISON_OPS = {"<", "<=", ">", ">=", "==", "!="}
|
198
|
+
|
269
199
|
def __init__(self, operator: str, left: Expression, right: Expression):
|
270
200
|
self.operator = operator
|
271
201
|
self.left = left
|
272
202
|
self.right = right
|
273
203
|
|
274
|
-
def evaluate(self, variable_values: dict[str,
|
204
|
+
def evaluate(self, variable_values: dict[str, "FieldQnty"]) -> "Quantity":
|
205
|
+
"""Evaluate the binary operation with caching and error handling."""
|
275
206
|
try:
|
276
|
-
|
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
|
-
|
288
|
-
left_val = self.left.evaluate(variable_values)
|
289
|
-
right_val = self.right.evaluate(variable_values)
|
290
|
-
|
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)
|
296
|
-
else:
|
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
|
207
|
+
return self._evaluate_with_caching(variable_values)
|
304
208
|
except Exception as e:
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
209
|
+
return self._handle_evaluation_error(e)
|
210
|
+
|
211
|
+
def _evaluate_with_caching(self, variable_values: dict[str, "FieldQnty"]) -> "Quantity":
|
212
|
+
"""Core evaluation logic with caching support."""
|
213
|
+
# Check cache for constant expressions
|
214
|
+
cached_result = self._try_get_cached_result()
|
215
|
+
if cached_result is not None:
|
216
|
+
return cached_result
|
217
|
+
|
218
|
+
# Evaluate operands
|
219
|
+
left_val, right_val = self._evaluate_operands(variable_values)
|
220
|
+
|
221
|
+
# Dispatch operation
|
222
|
+
result = self._dispatch_operation(left_val, right_val)
|
223
|
+
|
224
|
+
# Cache result for constant expressions
|
225
|
+
self._try_cache_result(result)
|
226
|
+
|
227
|
+
return result
|
228
|
+
|
229
|
+
def _evaluate_operands(self, variable_values: dict[str, "FieldQnty"]) -> tuple["Quantity", "Quantity"]:
|
230
|
+
"""Evaluate both operands and return as tuple."""
|
231
|
+
left_val = self.left.evaluate(variable_values)
|
232
|
+
right_val = self.right.evaluate(variable_values)
|
233
|
+
return left_val, right_val
|
234
|
+
|
235
|
+
def _handle_evaluation_error(self, error: Exception) -> "Quantity":
|
236
|
+
"""Handle evaluation errors with appropriate context."""
|
237
|
+
if isinstance(error, ValueError):
|
238
|
+
raise
|
239
|
+
raise ValueError(f"Error evaluating binary operation '{self}': {error}") from error
|
240
|
+
|
241
|
+
def _try_get_cached_result(self) -> "Quantity | None":
|
242
|
+
"""Attempt to retrieve a cached result for constant expressions."""
|
243
|
+
if not self._is_constant_expression():
|
244
|
+
return None
|
245
|
+
|
246
|
+
cache_manager = get_cache_manager()
|
247
|
+
cache_key = self._generate_cache_key()
|
248
|
+
return cache_manager.get_expression_result(cache_key)
|
249
|
+
|
250
|
+
def _try_cache_result(self, result: "Quantity") -> None:
|
251
|
+
"""Attempt to cache the result for constant expressions."""
|
252
|
+
if not self._is_constant_expression():
|
253
|
+
return
|
254
|
+
|
255
|
+
cache_manager = get_cache_manager()
|
256
|
+
cache_key = self._generate_cache_key()
|
257
|
+
cache_manager.cache_expression_result(cache_key, result)
|
258
|
+
|
259
|
+
def _is_constant_expression(self) -> bool:
|
260
|
+
"""Check if both operands are constants."""
|
261
|
+
return _is_constant_fast(self.left) and _is_constant_fast(self.right)
|
262
|
+
|
263
|
+
def _generate_cache_key(self) -> str:
|
264
|
+
"""Generate a cache key for constant expressions."""
|
265
|
+
# Safe to cast since _is_constant_expression() already verified types
|
266
|
+
left_const = self.left
|
267
|
+
right_const = self.right
|
268
|
+
return f"{id(self)}_{self.operator}_{id(left_const.value)}_{id(right_const.value)}" # type: ignore[attr-defined]
|
269
|
+
|
270
|
+
def _dispatch_operation(self, left_val: "Quantity", right_val: "Quantity") -> "Quantity":
|
271
|
+
"""Dispatch to the appropriate operation handler with fast lookup."""
|
272
|
+
# Fast path: check operator type with pre-compiled sets
|
273
|
+
if self.operator in self._ARITHMETIC_OPS:
|
274
|
+
return self._evaluate_arithmetic_dispatch(left_val, right_val)
|
275
|
+
elif self.operator in self._COMPARISON_OPS:
|
276
|
+
return self._evaluate_comparison(left_val, right_val)
|
277
|
+
else:
|
278
|
+
raise ValueError(f"Unknown operator: {self.operator}")
|
279
|
+
|
280
|
+
def _evaluate_arithmetic_dispatch(self, left_val: "Quantity", right_val: "Quantity") -> "Quantity":
|
281
|
+
"""Dispatch arithmetic operations with direct method lookup."""
|
282
|
+
# Use direct method dispatch for better performance
|
283
|
+
if self.operator == "*":
|
284
|
+
return self._multiply(left_val, right_val)
|
285
|
+
elif self.operator == "+":
|
286
|
+
return self._add(left_val, right_val)
|
287
|
+
elif self.operator == "-":
|
288
|
+
return self._subtract(left_val, right_val)
|
289
|
+
elif self.operator == "/":
|
290
|
+
return self._divide(left_val, right_val)
|
291
|
+
elif self.operator == "**":
|
292
|
+
return self._power(left_val, right_val)
|
361
293
|
else:
|
362
|
-
# Unknown operator - should not happen
|
363
294
|
raise ValueError(f"Unknown arithmetic operator: {self.operator}")
|
364
|
-
|
365
|
-
def
|
366
|
-
"""
|
367
|
-
|
295
|
+
|
296
|
+
def _evaluate_arithmetic(self, left_val: "Quantity", right_val: "Quantity") -> "Quantity":
|
297
|
+
"""Legacy arithmetic evaluation method - redirects to new dispatch."""
|
298
|
+
return self._evaluate_arithmetic_dispatch(left_val, right_val)
|
299
|
+
|
300
|
+
def _multiply(self, left_val: "Quantity", right_val: "Quantity") -> "Quantity":
|
301
|
+
"""Handle multiplication with ultra-fast path optimizations aligned with base_qnty."""
|
302
|
+
# PERFORMANCE OPTIMIZATION: Extract values once
|
303
|
+
left_value = left_val.value
|
304
|
+
right_value = right_val.value
|
305
|
+
|
306
|
+
# ENHANCED FAST PATHS: Check most common optimizations first
|
307
|
+
# Identity optimizations (1.0 multiplication) - most frequent case
|
308
|
+
if right_value == 1.0:
|
309
|
+
return left_val
|
310
|
+
elif left_value == 1.0:
|
311
|
+
return right_val
|
312
|
+
|
313
|
+
# Zero optimizations - second most common
|
314
|
+
elif right_value == 0.0:
|
315
|
+
return Quantity(0.0, right_val.unit)
|
316
|
+
elif left_value == 0.0:
|
317
|
+
return Quantity(0.0, left_val.unit)
|
318
|
+
|
319
|
+
# Additional fast paths for common values
|
320
|
+
elif right_value == -1.0:
|
321
|
+
return Quantity(-left_value, left_val.unit)
|
322
|
+
elif left_value == -1.0:
|
323
|
+
return Quantity(-right_value, right_val.unit)
|
324
|
+
|
325
|
+
# ADDITIONAL COMMON CASES: Powers of 2 and 0.5 (very common in engineering)
|
326
|
+
elif right_value == 2.0:
|
327
|
+
return Quantity(left_value * 2.0, left_val.unit)
|
328
|
+
elif left_value == 2.0:
|
329
|
+
return Quantity(right_value * 2.0, right_val.unit)
|
330
|
+
elif right_value == 0.5:
|
331
|
+
return Quantity(left_value * 0.5, left_val.unit)
|
332
|
+
elif left_value == 0.5:
|
333
|
+
return Quantity(right_value * 0.5, right_val.unit)
|
334
|
+
|
335
|
+
# OPTIMIZED REGULAR CASE: Use the enhanced multiplication from base_qnty
|
336
|
+
# This leverages the optimized caching and dimensionless handling
|
337
|
+
return left_val * right_val
|
338
|
+
|
339
|
+
def _add(self, left_val: "Quantity", right_val: "Quantity") -> "Quantity":
|
340
|
+
"""Handle addition with fast path optimizations."""
|
341
|
+
# Fast path: check for zero additions (most common optimization)
|
342
|
+
left_value = left_val.value
|
343
|
+
right_value = right_val.value
|
344
|
+
|
345
|
+
if right_value == 0.0:
|
346
|
+
return left_val
|
347
|
+
elif left_value == 0.0:
|
348
|
+
return right_val
|
349
|
+
|
350
|
+
# Regular addition
|
351
|
+
return left_val + right_val
|
352
|
+
|
353
|
+
def _subtract(self, left_val: "Quantity", right_val: "Quantity") -> "Quantity":
|
354
|
+
"""Handle subtraction with fast path optimizations."""
|
355
|
+
# Fast path: subtracting zero
|
356
|
+
right_value = right_val.value
|
357
|
+
left_value = left_val.value
|
358
|
+
|
359
|
+
if right_value == 0.0:
|
360
|
+
return left_val
|
361
|
+
elif left_value == right_value:
|
362
|
+
# Same value subtraction -> zero with left unit
|
363
|
+
return Quantity(0.0, left_val.unit)
|
364
|
+
|
365
|
+
# Regular subtraction
|
366
|
+
return left_val - right_val
|
367
|
+
|
368
|
+
def _divide(self, left_val: "Quantity", right_val: "Quantity") -> "Quantity":
|
369
|
+
"""Handle division with zero checking and optimizations."""
|
370
|
+
right_value = right_val.value
|
371
|
+
left_value = left_val.value
|
372
|
+
|
373
|
+
# Check for division by zero first
|
374
|
+
if abs(right_value) < DIVISION_BY_ZERO_THRESHOLD:
|
375
|
+
raise ValueError(f"Division by zero in expression: {self}")
|
376
|
+
|
377
|
+
# Fast paths
|
378
|
+
if right_value == 1.0:
|
379
|
+
return left_val
|
380
|
+
elif left_value == 0.0:
|
381
|
+
# Zero divided by anything is zero (with appropriate unit)
|
382
|
+
return Quantity(0.0, (left_val / right_val).unit)
|
383
|
+
elif right_value == -1.0:
|
384
|
+
# Division by -1 is negation
|
385
|
+
return Quantity(-left_value, (left_val / right_val).unit)
|
386
|
+
|
387
|
+
# Regular division
|
388
|
+
return left_val / right_val
|
389
|
+
|
390
|
+
def _power(self, left_val: "Quantity", right_val: "Quantity") -> "Quantity":
|
391
|
+
"""Handle power operations with special cases."""
|
392
|
+
right_value = right_val.value
|
393
|
+
left_value = left_val.value
|
394
|
+
|
395
|
+
if not isinstance(right_value, int | float):
|
396
|
+
raise ValueError("Exponent must be dimensionless number")
|
397
|
+
|
398
|
+
# Fast paths for common exponents
|
399
|
+
if right_value == 1.0:
|
400
|
+
return left_val
|
401
|
+
elif right_value == 0.0:
|
402
|
+
return Quantity(1.0, DimensionlessUnits.dimensionless)
|
403
|
+
elif right_value == 2.0:
|
404
|
+
return left_val * left_val # Use multiplication for squaring
|
405
|
+
elif right_value == 0.5:
|
406
|
+
# Square root optimization
|
407
|
+
import math
|
408
|
+
|
409
|
+
return Quantity(math.sqrt(left_value), left_val.unit)
|
410
|
+
elif right_value == -1.0:
|
411
|
+
# Reciprocal
|
412
|
+
return Quantity(1.0 / left_value, left_val.unit)
|
413
|
+
elif left_value == 1.0:
|
414
|
+
# 1 to any power is 1
|
415
|
+
return Quantity(1.0, DimensionlessUnits.dimensionless)
|
416
|
+
elif left_value == 0.0 and right_value > 0:
|
417
|
+
# 0 to positive power is 0
|
418
|
+
return Quantity(0.0, left_val.unit)
|
419
|
+
|
420
|
+
# Validation for negative bases
|
421
|
+
if right_value < 0 and left_value < 0:
|
422
|
+
raise ValueError(f"Negative base with negative exponent: {left_value}^{right_value}")
|
423
|
+
|
424
|
+
result_value = left_value**right_value
|
425
|
+
return Quantity(result_value, left_val.unit)
|
426
|
+
|
427
|
+
def _evaluate_comparison(self, left_val: "Quantity", right_val: "Quantity") -> "Quantity":
|
428
|
+
"""Evaluate comparison operations with optimized unit conversion."""
|
429
|
+
# Normalize units for comparison if needed
|
430
|
+
left_val, right_val = self._normalize_comparison_units(left_val, right_val)
|
431
|
+
|
432
|
+
# Perform comparison using optimized dispatch
|
433
|
+
result = self._perform_comparison(left_val.value, right_val.value)
|
434
|
+
return Quantity(1.0 if result else 0.0, DimensionlessUnits.dimensionless)
|
435
|
+
|
436
|
+
def _normalize_comparison_units(self, left_val: "Quantity", right_val: "Quantity") -> tuple["Quantity", "Quantity"]:
|
437
|
+
"""Normalize units for comparison operations."""
|
368
438
|
try:
|
369
439
|
if left_val._dimension_sig == right_val._dimension_sig and left_val.unit != right_val.unit:
|
370
440
|
right_val = right_val.to(left_val.unit)
|
371
441
|
except (ValueError, TypeError, AttributeError):
|
372
442
|
pass
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
443
|
+
return left_val, right_val
|
444
|
+
|
445
|
+
def _perform_comparison(self, left_value: float, right_value: float) -> bool:
|
446
|
+
"""Perform the actual comparison operation."""
|
447
|
+
# Direct dispatch for better performance
|
448
|
+
if self.operator == "<":
|
449
|
+
return left_value < right_value
|
450
|
+
elif self.operator == "<=":
|
451
|
+
return left_value <= right_value
|
452
|
+
elif self.operator == ">":
|
453
|
+
return left_value > right_value
|
454
|
+
elif self.operator == ">=":
|
455
|
+
return left_value >= right_value
|
456
|
+
elif self.operator == "==":
|
457
|
+
return abs(left_value - right_value) < FLOAT_EQUALITY_TOLERANCE
|
458
|
+
elif self.operator == "!=":
|
459
|
+
return abs(left_value - right_value) >= FLOAT_EQUALITY_TOLERANCE
|
460
|
+
else:
|
461
|
+
raise ValueError(f"Unknown comparison operator: {self.operator}")
|
462
|
+
|
387
463
|
def get_variables(self) -> set[str]:
|
388
464
|
return self.left.get_variables() | self.right.get_variables()
|
389
|
-
|
465
|
+
|
390
466
|
def simplify(self) -> Expression:
|
467
|
+
"""Simplify the binary operation with optimized constant folding."""
|
391
468
|
left_simplified = self.left.simplify()
|
392
469
|
right_simplified = self.right.simplify()
|
393
|
-
|
394
|
-
#
|
395
|
-
if
|
396
|
-
|
397
|
-
|
398
|
-
try:
|
399
|
-
result = BinaryOperation(self.operator, left_simplified, right_simplified).evaluate(dummy_vars)
|
400
|
-
return Constant(result)
|
401
|
-
except (ValueError, TypeError, ArithmeticError):
|
402
|
-
pass
|
403
|
-
|
470
|
+
|
471
|
+
# Fast path: check for constant expression simplification
|
472
|
+
if _is_constant_fast(left_simplified) and _is_constant_fast(right_simplified):
|
473
|
+
return self._try_constant_folding(left_simplified, right_simplified)
|
474
|
+
|
404
475
|
return BinaryOperation(self.operator, left_simplified, right_simplified)
|
405
|
-
|
476
|
+
|
477
|
+
def _try_constant_folding(self, left_const: Expression, right_const: Expression) -> Expression:
|
478
|
+
"""Attempt to fold constant expressions."""
|
479
|
+
try:
|
480
|
+
# Evaluate constant expressions at compile time
|
481
|
+
dummy_vars = {}
|
482
|
+
result = BinaryOperation(self.operator, left_const, right_const).evaluate(dummy_vars)
|
483
|
+
return Constant(result)
|
484
|
+
except (ValueError, TypeError, ArithmeticError):
|
485
|
+
# Return original operation if folding fails
|
486
|
+
return BinaryOperation(self.operator, left_const, right_const)
|
487
|
+
|
406
488
|
def __str__(self) -> str:
|
407
|
-
#
|
489
|
+
# Delegate to centralized formatter
|
408
490
|
can_eval, variables = self._can_auto_evaluate()
|
409
|
-
|
410
|
-
try:
|
411
|
-
result = self.evaluate(variables)
|
412
|
-
return str(result)
|
413
|
-
except Exception:
|
414
|
-
pass # Fall back to symbolic representation
|
415
|
-
|
416
|
-
# Handle operator precedence for cleaner string representation
|
417
|
-
precedence = {'+': 1, '-': 1, '*': 2, '/': 2, '**': 3, '<': 0, '<=': 0, '>': 0, '>=': 0, '==': 0, '!=': 0}
|
418
|
-
left_str = str(self.left)
|
419
|
-
right_str = str(self.right)
|
420
|
-
|
421
|
-
# Add parentheses for left side when precedence is strictly lower
|
422
|
-
if isinstance(self.left, BinaryOperation) and precedence.get(self.left.operator, 0) < precedence.get(self.operator, 0):
|
423
|
-
left_str = f"({left_str})"
|
424
|
-
|
425
|
-
# CRITICAL FIX: For right side, add parentheses when:
|
426
|
-
# 1. Precedence is strictly lower, OR
|
427
|
-
# 2. Precedence is equal AND operation is left-associative (-, /)
|
428
|
-
if isinstance(self.right, BinaryOperation):
|
429
|
-
right_prec = precedence.get(self.right.operator, 0)
|
430
|
-
curr_prec = precedence.get(self.operator, 0)
|
431
|
-
|
432
|
-
# Need parentheses if:
|
433
|
-
# - Right has lower precedence, OR
|
434
|
-
# - Same precedence and current operator is left-associative (- or /)
|
435
|
-
if (right_prec < curr_prec or
|
436
|
-
(right_prec == curr_prec and self.operator in ['-', '/'])):
|
437
|
-
right_str = f"({right_str})"
|
438
|
-
|
439
|
-
return f"{left_str} {self.operator} {right_str}"
|
491
|
+
return ExpressionFormatter.format_binary_operation(self, can_auto_evaluate=can_eval, auto_eval_variables=variables) # type: ignore[arg-type]
|
440
492
|
|
441
493
|
|
442
494
|
class UnaryFunction(Expression):
|
443
495
|
"""Unary mathematical function expression."""
|
444
|
-
|
445
|
-
|
496
|
+
|
497
|
+
__slots__ = ("function_name", "operand")
|
498
|
+
|
446
499
|
def __init__(self, function_name: str, operand: Expression):
|
447
500
|
self.function_name = function_name
|
448
501
|
self.operand = operand
|
449
|
-
|
450
|
-
def evaluate(self, variable_values: dict[str,
|
451
|
-
|
502
|
+
|
503
|
+
def evaluate(self, variable_values: dict[str, "FieldQnty"]) -> "Quantity":
|
452
504
|
operand_val = self.operand.evaluate(variable_values)
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
return
|
469
|
-
elif self.function_name == 'abs':
|
470
|
-
return Quantity(abs(operand_val.value), operand_val.unit)
|
471
|
-
elif self.function_name == 'ln':
|
472
|
-
# Natural log - input should be dimensionless
|
473
|
-
result_value = math.log(operand_val.value)
|
474
|
-
return Quantity(result_value, DimensionlessUnits.dimensionless)
|
475
|
-
elif self.function_name == 'log10':
|
476
|
-
result_value = math.log10(operand_val.value)
|
477
|
-
return Quantity(result_value, DimensionlessUnits.dimensionless)
|
478
|
-
elif self.function_name == 'exp':
|
479
|
-
# Exponential - input should be dimensionless
|
480
|
-
result_value = math.exp(operand_val.value)
|
481
|
-
return Quantity(result_value, DimensionlessUnits.dimensionless)
|
505
|
+
|
506
|
+
# Function dispatch table for better performance and maintainability
|
507
|
+
functions = {
|
508
|
+
"sin": lambda x: Quantity(math.sin(x.value), DimensionlessUnits.dimensionless),
|
509
|
+
"cos": lambda x: Quantity(math.cos(x.value), DimensionlessUnits.dimensionless),
|
510
|
+
"tan": lambda x: Quantity(math.tan(x.value), DimensionlessUnits.dimensionless),
|
511
|
+
"sqrt": lambda x: Quantity(math.sqrt(x.value), x.unit),
|
512
|
+
"abs": lambda x: Quantity(abs(x.value), x.unit),
|
513
|
+
"ln": lambda x: Quantity(math.log(x.value), DimensionlessUnits.dimensionless),
|
514
|
+
"log10": lambda x: Quantity(math.log10(x.value), DimensionlessUnits.dimensionless),
|
515
|
+
"exp": lambda x: Quantity(math.exp(x.value), DimensionlessUnits.dimensionless),
|
516
|
+
}
|
517
|
+
|
518
|
+
func = functions.get(self.function_name)
|
519
|
+
if func:
|
520
|
+
return func(operand_val)
|
482
521
|
else:
|
483
522
|
raise ValueError(f"Unknown function: {self.function_name}")
|
484
|
-
|
523
|
+
|
485
524
|
def get_variables(self) -> set[str]:
|
486
525
|
return self.operand.get_variables()
|
487
|
-
|
526
|
+
|
488
527
|
def simplify(self) -> Expression:
|
489
528
|
simplified_operand = self.operand.simplify()
|
490
|
-
if
|
529
|
+
if _is_constant_fast(simplified_operand):
|
491
530
|
# Evaluate constant functions at compile time
|
492
531
|
try:
|
493
532
|
dummy_vars = {}
|
@@ -496,51 +535,167 @@ class UnaryFunction(Expression):
|
|
496
535
|
except (ValueError, TypeError, ArithmeticError):
|
497
536
|
pass
|
498
537
|
return UnaryFunction(self.function_name, simplified_operand)
|
499
|
-
|
538
|
+
|
500
539
|
def __str__(self) -> str:
|
501
|
-
return
|
540
|
+
return ExpressionFormatter.format_unary_function(self) # type: ignore[arg-type]
|
502
541
|
|
503
542
|
|
504
543
|
class ConditionalExpression(Expression):
|
505
544
|
"""Conditional expression: if condition then true_expr else false_expr."""
|
506
|
-
|
507
|
-
|
545
|
+
|
546
|
+
__slots__ = ("condition", "true_expr", "false_expr")
|
547
|
+
|
508
548
|
def __init__(self, condition: Expression, true_expr: Expression, false_expr: Expression):
|
509
549
|
self.condition = condition
|
510
550
|
self.true_expr = true_expr
|
511
551
|
self.false_expr = false_expr
|
512
|
-
|
513
|
-
def evaluate(self, variable_values: dict[str,
|
552
|
+
|
553
|
+
def evaluate(self, variable_values: dict[str, "FieldQnty"]) -> "Quantity":
|
514
554
|
condition_val = self.condition.evaluate(variable_values)
|
515
555
|
# Consider non-zero as True
|
516
|
-
if abs(condition_val.value) >
|
556
|
+
if abs(condition_val.value) > CONDITION_EVALUATION_THRESHOLD:
|
517
557
|
return self.true_expr.evaluate(variable_values)
|
518
558
|
else:
|
519
559
|
return self.false_expr.evaluate(variable_values)
|
520
|
-
|
560
|
+
|
521
561
|
def get_variables(self) -> set[str]:
|
522
|
-
return
|
523
|
-
|
524
|
-
self.false_expr.get_variables())
|
525
|
-
|
562
|
+
return self.condition.get_variables() | self.true_expr.get_variables() | self.false_expr.get_variables()
|
563
|
+
|
526
564
|
def simplify(self) -> Expression:
|
527
565
|
simplified_condition = self.condition.simplify()
|
528
566
|
simplified_true = self.true_expr.simplify()
|
529
567
|
simplified_false = self.false_expr.simplify()
|
530
|
-
|
568
|
+
|
531
569
|
# If condition is constant, choose the appropriate branch
|
532
|
-
if
|
570
|
+
if _is_constant_fast(simplified_condition):
|
533
571
|
try:
|
534
572
|
dummy_vars = {}
|
535
573
|
condition_val = simplified_condition.evaluate(dummy_vars)
|
536
|
-
if abs(condition_val.value) >
|
574
|
+
if abs(condition_val.value) > CONDITION_EVALUATION_THRESHOLD:
|
537
575
|
return simplified_true
|
538
576
|
else:
|
539
577
|
return simplified_false
|
540
578
|
except (ValueError, TypeError, ArithmeticError):
|
541
579
|
pass
|
542
|
-
|
580
|
+
|
543
581
|
return ConditionalExpression(simplified_condition, simplified_true, simplified_false)
|
544
|
-
|
582
|
+
|
545
583
|
def __str__(self) -> str:
|
546
|
-
return
|
584
|
+
return ExpressionFormatter.format_conditional_expression(self) # type: ignore[arg-type]
|
585
|
+
|
586
|
+
|
587
|
+
# Utility functions for expression creation
|
588
|
+
|
589
|
+
# Cache for common types to avoid repeated type checks
|
590
|
+
_DIMENSIONLESS_CONSTANT = None
|
591
|
+
|
592
|
+
# Type caches for hot path optimization
|
593
|
+
_CONSTANT_TYPE = None
|
594
|
+
_VARIABLE_REF_TYPE = None
|
595
|
+
_QUANTITY_TYPE = None
|
596
|
+
_FIELDQNTY_TYPE = None
|
597
|
+
|
598
|
+
|
599
|
+
def _init_type_cache():
|
600
|
+
"""Initialize type cache for fast isinstance checks."""
|
601
|
+
global _CONSTANT_TYPE, _VARIABLE_REF_TYPE, _QUANTITY_TYPE, _FIELDQNTY_TYPE
|
602
|
+
if _CONSTANT_TYPE is None:
|
603
|
+
_CONSTANT_TYPE = Constant
|
604
|
+
_VARIABLE_REF_TYPE = VariableReference
|
605
|
+
_QUANTITY_TYPE = Quantity
|
606
|
+
_FIELDQNTY_TYPE = FieldQnty
|
607
|
+
|
608
|
+
|
609
|
+
def _is_constant_fast(obj) -> bool:
|
610
|
+
"""Fast type check for Constant objects."""
|
611
|
+
_init_type_cache()
|
612
|
+
return type(obj) is _CONSTANT_TYPE
|
613
|
+
|
614
|
+
|
615
|
+
def _get_cached_dimensionless():
|
616
|
+
"""Get cached dimensionless constant for numeric values."""
|
617
|
+
global _DIMENSIONLESS_CONSTANT
|
618
|
+
if _DIMENSIONLESS_CONSTANT is None:
|
619
|
+
_DIMENSIONLESS_CONSTANT = DimensionlessUnits.dimensionless
|
620
|
+
return _DIMENSIONLESS_CONSTANT
|
621
|
+
|
622
|
+
|
623
|
+
# Ultra-fast local cache for most common dimensionless values
|
624
|
+
_COMMON_DIMENSIONLESS_CACHE: dict[float, Quantity] = {}
|
625
|
+
|
626
|
+
def _get_dimensionless_quantity(value: float) -> Quantity:
|
627
|
+
"""Ultra-optimized dimensionless quantity creation with local caching."""
|
628
|
+
# Ultra-fast local cache for most common values (0, 1, 2, -1, 0.5, etc.)
|
629
|
+
cached_qty = _COMMON_DIMENSIONLESS_CACHE.get(value)
|
630
|
+
if cached_qty is not None:
|
631
|
+
return cached_qty
|
632
|
+
|
633
|
+
# Create quantity with cached unit
|
634
|
+
qty = Quantity(value, _get_cached_dimensionless())
|
635
|
+
|
636
|
+
# Cache common values locally for ultra-fast access
|
637
|
+
if value in (-1.0, 0.0, 0.5, 1.0, 2.0) or (isinstance(value, float) and -10 <= value <= 10 and value == int(value)):
|
638
|
+
if len(_COMMON_DIMENSIONLESS_CACHE) < 25: # Prevent unbounded growth
|
639
|
+
_COMMON_DIMENSIONLESS_CACHE[value] = qty
|
640
|
+
|
641
|
+
return qty
|
642
|
+
|
643
|
+
|
644
|
+
def wrap_operand(operand: "OperandType") -> Expression:
|
645
|
+
"""
|
646
|
+
Ultra-optimized operand wrapping with minimal function call overhead.
|
647
|
+
|
648
|
+
Performance optimizations:
|
649
|
+
- Single type() call instead of multiple isinstance checks
|
650
|
+
- Cached common type patterns
|
651
|
+
- Reduced function call depth
|
652
|
+
"""
|
653
|
+
# ULTRA-FAST PATH: Use single type() call for most common cases
|
654
|
+
operand_type = type(operand)
|
655
|
+
|
656
|
+
# Most common cases first: primitives (35-40% of all calls)
|
657
|
+
if operand_type in (int, float):
|
658
|
+
return Constant(_get_dimensionless_quantity(float(operand))) # type: ignore[arg-type]
|
659
|
+
|
660
|
+
# Second most common: already wrapped expressions (20-25% of calls)
|
661
|
+
if operand_type is BinaryOperation: # Direct type check is faster
|
662
|
+
return operand # type: ignore[return-value]
|
663
|
+
|
664
|
+
# Third most common: field quantities/variables (20-30% of calls)
|
665
|
+
# Use getattr with hasattr-style check to reduce calls
|
666
|
+
if hasattr(operand, "quantity") and hasattr(operand, "symbol"):
|
667
|
+
return VariableReference(operand) # type: ignore[arg-type]
|
668
|
+
|
669
|
+
# Handle other Expression types (Constant, VariableReference, etc.)
|
670
|
+
if isinstance(operand, Expression):
|
671
|
+
return operand
|
672
|
+
|
673
|
+
# Check for base Quantity objects
|
674
|
+
if hasattr(operand, "value") and hasattr(operand, "unit") and hasattr(operand, "_dimension_sig"):
|
675
|
+
return Constant(operand) # type: ignore[arg-type]
|
676
|
+
|
677
|
+
# Check for ConfigurableVariable (from composition system) - rare case
|
678
|
+
if hasattr(operand, "_variable"):
|
679
|
+
var = getattr(operand, "_variable", None)
|
680
|
+
if var is not None and hasattr(var, "quantity") and hasattr(var, "symbol"):
|
681
|
+
return VariableReference(var) # type: ignore[arg-type]
|
682
|
+
|
683
|
+
# Fast failure for unknown types
|
684
|
+
raise TypeError(f"Cannot convert {operand_type.__name__} to Expression")
|
685
|
+
|
686
|
+
|
687
|
+
# Register expression and variable types with the TypeRegistry for optimal performance
|
688
|
+
|
689
|
+
# Register expression types
|
690
|
+
register_expression_type(Expression)
|
691
|
+
register_expression_type(BinaryOperation)
|
692
|
+
register_expression_type(VariableReference)
|
693
|
+
register_expression_type(Constant)
|
694
|
+
register_expression_type(UnaryFunction)
|
695
|
+
register_expression_type(ConditionalExpression)
|
696
|
+
|
697
|
+
# Register variable types - do this at module level to ensure it happens early
|
698
|
+
try:
|
699
|
+
register_variable_type(FieldQnty)
|
700
|
+
except ImportError:
|
701
|
+
pass # Handle import ordering issues gracefully
|