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.
Files changed (74) hide show
  1. qnty/__init__.py +140 -58
  2. qnty/_backup/problem_original.py +1251 -0
  3. qnty/_backup/quantity.py +63 -0
  4. qnty/codegen/cli.py +125 -0
  5. qnty/codegen/generators/data/unit_data.json +8807 -0
  6. qnty/codegen/generators/data_processor.py +345 -0
  7. qnty/codegen/generators/dimensions_gen.py +434 -0
  8. qnty/codegen/generators/doc_generator.py +141 -0
  9. qnty/codegen/generators/out/dimension_mapping.json +974 -0
  10. qnty/codegen/generators/out/dimension_metadata.json +123 -0
  11. qnty/codegen/generators/out/units_metadata.json +223 -0
  12. qnty/codegen/generators/quantities_gen.py +159 -0
  13. qnty/codegen/generators/setters_gen.py +178 -0
  14. qnty/codegen/generators/stubs_gen.py +167 -0
  15. qnty/codegen/generators/units_gen.py +295 -0
  16. qnty/codegen/generators/utils/__init__.py +0 -0
  17. qnty/equations/__init__.py +4 -0
  18. qnty/{equation.py → equations/equation.py} +78 -118
  19. qnty/equations/system.py +127 -0
  20. qnty/expressions/__init__.py +61 -0
  21. qnty/expressions/cache.py +94 -0
  22. qnty/expressions/functions.py +96 -0
  23. qnty/{expression.py → expressions/nodes.py} +209 -216
  24. qnty/generated/__init__.py +0 -0
  25. qnty/generated/dimensions.py +514 -0
  26. qnty/generated/quantities.py +6003 -0
  27. qnty/generated/quantities.pyi +4192 -0
  28. qnty/generated/setters.py +12210 -0
  29. qnty/generated/units.py +9798 -0
  30. qnty/problem/__init__.py +91 -0
  31. qnty/problem/base.py +142 -0
  32. qnty/problem/composition.py +385 -0
  33. qnty/problem/composition_mixin.py +382 -0
  34. qnty/problem/equations.py +413 -0
  35. qnty/problem/metaclass.py +302 -0
  36. qnty/problem/reconstruction.py +1016 -0
  37. qnty/problem/solving.py +180 -0
  38. qnty/problem/validation.py +64 -0
  39. qnty/problem/variables.py +239 -0
  40. qnty/quantities/__init__.py +6 -0
  41. qnty/quantities/expression_quantity.py +314 -0
  42. qnty/quantities/quantity.py +428 -0
  43. qnty/quantities/typed_quantity.py +215 -0
  44. qnty/solving/__init__.py +0 -0
  45. qnty/solving/manager.py +90 -0
  46. qnty/solving/order.py +355 -0
  47. qnty/solving/solvers/__init__.py +20 -0
  48. qnty/solving/solvers/base.py +92 -0
  49. qnty/solving/solvers/iterative.py +185 -0
  50. qnty/solving/solvers/simultaneous.py +547 -0
  51. qnty/units/__init__.py +0 -0
  52. qnty/{prefixes.py → units/prefixes.py} +54 -33
  53. qnty/{unit.py → units/registry.py} +73 -32
  54. qnty/utils/__init__.py +0 -0
  55. qnty/utils/logging.py +40 -0
  56. qnty/validation/__init__.py +0 -0
  57. qnty/validation/registry.py +0 -0
  58. qnty/validation/rules.py +167 -0
  59. qnty-0.0.9.dist-info/METADATA +199 -0
  60. qnty-0.0.9.dist-info/RECORD +63 -0
  61. qnty/dimension.py +0 -186
  62. qnty/unit_types/base.py +0 -47
  63. qnty/units.py +0 -8113
  64. qnty/variable.py +0 -300
  65. qnty/variable_types/base.py +0 -58
  66. qnty/variable_types/expression_variable.py +0 -106
  67. qnty/variable_types/typed_variable.py +0 -87
  68. qnty/variables.py +0 -2298
  69. qnty/variables.pyi +0 -6148
  70. qnty-0.0.8.dist-info/METADATA +0 -355
  71. qnty-0.0.8.dist-info/RECORD +0 -19
  72. /qnty/{unit_types → codegen}/__init__.py +0 -0
  73. /qnty/{variable_types → codegen/generators}/__init__.py +0 -0
  74. {qnty-0.0.8.dist-info → qnty-0.0.9.dist-info}/WHEEL +0 -0
@@ -1,50 +1,32 @@
1
1
  """
2
- Expression System
3
- =================
2
+ Expression AST Nodes
3
+ ===================
4
4
 
5
- Mathematical expressions for building equation trees with qnty variables.
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 Union, cast
10
+ from typing import TYPE_CHECKING, Union
11
11
 
12
- from .units import DimensionlessUnits
12
+ if TYPE_CHECKING:
13
+ from ..quantities.quantity import Quantity, TypeSafeVariable
13
14
 
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")
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']) -> 'FastQuantity':
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
- while frame and (
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
- # Combine local and global variables
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
- 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:
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
- 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
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', 'FastQuantity', int, float]) -> 'Expression':
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', 'FastQuantity', int, float]) -> 'Expression':
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', 'FastQuantity', int, float]) -> 'Expression':
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', 'FastQuantity', int, float]) -> 'Expression':
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', 'FastQuantity', int, float]) -> 'Expression':
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', 'FastQuantity', int, float]) -> 'Expression':
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', 'FastQuantity', int, float]) -> 'Expression':
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', 'FastQuantity', int, float]) -> 'Expression':
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', 'FastQuantity', int, float]) -> 'Expression':
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', 'FastQuantity', int, float]) -> 'Expression':
160
+ def __rpow__(self, other: Union['TypeSafeVariable', 'Quantity', int, float]) -> 'Expression':
167
161
  return BinaryOperation('**', wrap_operand(other), self)
168
162
 
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))
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', 'FastQuantity', int, float]) -> 'BinaryOperation':
174
- return BinaryOperation('<=', self, self._wrap_operand(other))
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', 'FastQuantity', int, float]) -> 'BinaryOperation':
177
- return BinaryOperation('>', self, self._wrap_operand(other))
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', 'FastQuantity', int, float]) -> 'BinaryOperation':
180
- return BinaryOperation('>=', self, self._wrap_operand(other))
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', 'FastQuantity', int, float]) -> 'Expression':
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']) -> 'FastQuantity':
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: 'FastQuantity'):
244
+ def __init__(self, value: 'Quantity'):
241
245
  self.value = value
242
246
 
243
- def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> 'FastQuantity':
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']) -> 'FastQuantity':
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
- 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)
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']) -> 'FastQuantity':
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 FastQuantity(result_value, DimensionlessUnits.dimensionless)
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 FastQuantity(result_value, DimensionlessUnits.dimensionless)
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 FastQuantity(result_value, DimensionlessUnits.dimensionless)
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 FastQuantity(result_value, operand_val.unit)
468
+ return Quantity(result_value, operand_val.unit)
405
469
  elif self.function_name == 'abs':
406
- return FastQuantity(abs(operand_val.value), operand_val.unit)
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 FastQuantity(result_value, DimensionlessUnits.dimensionless)
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 FastQuantity(result_value, DimensionlessUnits.dimensionless)
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 FastQuantity(result_value, DimensionlessUnits.dimensionless)
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']) -> 'FastQuantity':
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