qnty 0.0.1__py3-none-any.whl → 0.0.2__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 +3 -0
- qnty/dimension.py +5 -4
- qnty/equation.py +216 -0
- qnty/expression.py +480 -0
- qnty/unit.py +8 -8
- qnty/units.py +2 -3
- qnty/variable.py +68 -29
- qnty/variables.py +217 -19
- {qnty-0.0.1.dist-info → qnty-0.0.2.dist-info}/METADATA +109 -22
- qnty-0.0.2.dist-info/RECORD +11 -0
- qnty/setters.py +0 -89
- qnty-0.0.1.dist-info/RECORD +0 -10
- {qnty-0.0.1.dist-info → qnty-0.0.2.dist-info}/WHEEL +0 -0
qnty/expression.py
ADDED
@@ -0,0 +1,480 @@
|
|
1
|
+
"""
|
2
|
+
Expression System
|
3
|
+
=================
|
4
|
+
|
5
|
+
Mathematical expressions for building equation trees with qnty variables.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import math
|
9
|
+
from abc import ABC, abstractmethod
|
10
|
+
from typing import Union, cast
|
11
|
+
|
12
|
+
from .units import DimensionlessUnits
|
13
|
+
|
14
|
+
# if TYPE_CHECKING:
|
15
|
+
from .variable import FastQuantity, TypeSafeVariable
|
16
|
+
|
17
|
+
|
18
|
+
def wrap_operand(operand: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
19
|
+
"""
|
20
|
+
Wrap non-Expression operands in appropriate Expression subclasses.
|
21
|
+
|
22
|
+
This function handles type conversion without circular imports by using
|
23
|
+
duck typing and delayed imports where necessary.
|
24
|
+
"""
|
25
|
+
# Type guard for Expression types
|
26
|
+
if hasattr(operand, 'evaluate') and hasattr(operand, 'get_variables'):
|
27
|
+
# Already an Expression
|
28
|
+
return cast('Expression', operand)
|
29
|
+
elif hasattr(operand, 'name') and hasattr(operand, 'quantity') and hasattr(operand, 'is_known'):
|
30
|
+
# TypeSafeVariable-like object
|
31
|
+
return VariableReference(cast('TypeSafeVariable', operand))
|
32
|
+
elif hasattr(operand, 'value') and hasattr(operand, 'unit') and hasattr(operand, '_dimension_sig'):
|
33
|
+
# FastQuantity-like object
|
34
|
+
return Constant(cast('FastQuantity', operand))
|
35
|
+
elif isinstance(operand, int | float):
|
36
|
+
# Numeric value - create dimensionless quantity
|
37
|
+
|
38
|
+
return Constant(FastQuantity(float(operand), DimensionlessUnits.dimensionless))
|
39
|
+
else:
|
40
|
+
raise TypeError(f"Cannot convert {type(operand)} to Expression")
|
41
|
+
|
42
|
+
|
43
|
+
class Expression(ABC):
|
44
|
+
"""Abstract base class for mathematical expressions."""
|
45
|
+
|
46
|
+
@abstractmethod
|
47
|
+
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> 'FastQuantity':
|
48
|
+
"""Evaluate the expression given variable values."""
|
49
|
+
pass
|
50
|
+
|
51
|
+
@abstractmethod
|
52
|
+
def get_variables(self) -> set[str]:
|
53
|
+
"""Get all variable symbols used in this expression."""
|
54
|
+
pass
|
55
|
+
|
56
|
+
@abstractmethod
|
57
|
+
def simplify(self) -> 'Expression':
|
58
|
+
"""Simplify the expression."""
|
59
|
+
pass
|
60
|
+
|
61
|
+
@abstractmethod
|
62
|
+
def __str__(self) -> str:
|
63
|
+
pass
|
64
|
+
|
65
|
+
def __add__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
66
|
+
return BinaryOperation('+', self, wrap_operand(other))
|
67
|
+
|
68
|
+
def __radd__(self, other: Union['TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
69
|
+
return BinaryOperation('+', wrap_operand(other), self)
|
70
|
+
|
71
|
+
def __sub__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
72
|
+
return BinaryOperation('-', self, wrap_operand(other))
|
73
|
+
|
74
|
+
def __rsub__(self, other: Union['TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
75
|
+
return BinaryOperation('-', wrap_operand(other), self)
|
76
|
+
|
77
|
+
def __mul__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
78
|
+
return BinaryOperation('*', self, wrap_operand(other))
|
79
|
+
|
80
|
+
def __rmul__(self, other: Union['TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
81
|
+
return BinaryOperation('*', wrap_operand(other), self)
|
82
|
+
|
83
|
+
def __truediv__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
84
|
+
return BinaryOperation('/', self, wrap_operand(other))
|
85
|
+
|
86
|
+
def __rtruediv__(self, other: Union['TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
87
|
+
return BinaryOperation('/', wrap_operand(other), self)
|
88
|
+
|
89
|
+
def __pow__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
90
|
+
return BinaryOperation('**', self, wrap_operand(other))
|
91
|
+
|
92
|
+
def __rpow__(self, other: Union['TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
93
|
+
return BinaryOperation('**', wrap_operand(other), self)
|
94
|
+
|
95
|
+
# Comparison operators for conditional expressions
|
96
|
+
def __lt__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'ComparisonExpression':
|
97
|
+
return ComparisonExpression('<', self, self._wrap_operand(other))
|
98
|
+
|
99
|
+
def __le__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'ComparisonExpression':
|
100
|
+
return ComparisonExpression('<=', self, self._wrap_operand(other))
|
101
|
+
|
102
|
+
def __gt__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'ComparisonExpression':
|
103
|
+
return ComparisonExpression('>', self, self._wrap_operand(other))
|
104
|
+
|
105
|
+
def __ge__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'ComparisonExpression':
|
106
|
+
return ComparisonExpression('>=', self, self._wrap_operand(other))
|
107
|
+
|
108
|
+
@staticmethod
|
109
|
+
def _wrap_operand(operand: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
110
|
+
"""Wrap non-Expression operands in appropriate Expression subclasses."""
|
111
|
+
return wrap_operand(operand)
|
112
|
+
|
113
|
+
|
114
|
+
class VariableReference(Expression):
|
115
|
+
"""Reference to a variable in an expression with performance optimizations."""
|
116
|
+
|
117
|
+
def __init__(self, variable: 'TypeSafeVariable'):
|
118
|
+
self.variable = variable
|
119
|
+
# Cache the name resolution to avoid repeated lookups
|
120
|
+
self._cached_name = None
|
121
|
+
self._last_symbol = None
|
122
|
+
|
123
|
+
@property
|
124
|
+
def name(self) -> str:
|
125
|
+
"""Get variable name with caching for performance."""
|
126
|
+
current_symbol = self.variable.symbol
|
127
|
+
if self._cached_name is None or self._last_symbol != current_symbol:
|
128
|
+
# Use symbol for optinova compatibility, fall back to name if symbol not set
|
129
|
+
self._cached_name = current_symbol if current_symbol else self.variable.name
|
130
|
+
self._last_symbol = current_symbol
|
131
|
+
return self._cached_name
|
132
|
+
|
133
|
+
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> 'FastQuantity':
|
134
|
+
try:
|
135
|
+
if self.name in variable_values:
|
136
|
+
var = variable_values[self.name]
|
137
|
+
if var.quantity is not None:
|
138
|
+
return var.quantity
|
139
|
+
elif self.variable.quantity is not None:
|
140
|
+
return self.variable.quantity
|
141
|
+
|
142
|
+
# If we reach here, no valid quantity was found
|
143
|
+
available_vars = list(variable_values.keys()) if variable_values else []
|
144
|
+
raise ValueError(
|
145
|
+
f"Cannot evaluate variable '{self.name}' without value. "
|
146
|
+
f"Available variables: {available_vars}"
|
147
|
+
)
|
148
|
+
except Exception as e:
|
149
|
+
if isinstance(e, ValueError):
|
150
|
+
raise
|
151
|
+
raise ValueError(f"Error evaluating variable '{self.name}': {e}") from e
|
152
|
+
|
153
|
+
def get_variables(self) -> set[str]:
|
154
|
+
return {self.name}
|
155
|
+
|
156
|
+
def simplify(self) -> 'Expression':
|
157
|
+
return self
|
158
|
+
|
159
|
+
def __str__(self) -> str:
|
160
|
+
return self.name
|
161
|
+
|
162
|
+
|
163
|
+
class Constant(Expression):
|
164
|
+
"""Constant value in an expression."""
|
165
|
+
|
166
|
+
def __init__(self, value: 'FastQuantity'):
|
167
|
+
self.value = value
|
168
|
+
|
169
|
+
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> 'FastQuantity':
|
170
|
+
return self.value
|
171
|
+
|
172
|
+
def get_variables(self) -> set[str]:
|
173
|
+
return set()
|
174
|
+
|
175
|
+
def simplify(self) -> 'Expression':
|
176
|
+
return self
|
177
|
+
|
178
|
+
def __str__(self) -> str:
|
179
|
+
return str(self.value.value)
|
180
|
+
|
181
|
+
|
182
|
+
class BinaryOperation(Expression):
|
183
|
+
"""Binary operation between two expressions."""
|
184
|
+
|
185
|
+
def __init__(self, operator: str, left: Expression, right: Expression):
|
186
|
+
self.operator = operator
|
187
|
+
self.left = left
|
188
|
+
self.right = right
|
189
|
+
|
190
|
+
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> 'FastQuantity':
|
191
|
+
try:
|
192
|
+
left_val = self.left.evaluate(variable_values)
|
193
|
+
right_val = self.right.evaluate(variable_values)
|
194
|
+
|
195
|
+
if self.operator == '+':
|
196
|
+
return left_val + right_val
|
197
|
+
elif self.operator == '-':
|
198
|
+
return left_val - right_val
|
199
|
+
elif self.operator == '*':
|
200
|
+
return left_val * right_val
|
201
|
+
elif self.operator == '/':
|
202
|
+
# Check for division by zero
|
203
|
+
if abs(right_val.value) < 1e-15:
|
204
|
+
raise ValueError(f"Division by zero in expression: {self}")
|
205
|
+
return left_val / right_val
|
206
|
+
elif self.operator == '**':
|
207
|
+
# For power, right side should be dimensionless
|
208
|
+
if isinstance(right_val.value, int | float):
|
209
|
+
if right_val.value < 0 and left_val.value < 0:
|
210
|
+
raise ValueError(f"Negative base with negative exponent: {left_val.value}^{right_val.value}")
|
211
|
+
result_value = left_val.value ** right_val.value
|
212
|
+
# For power operations, we need to handle units carefully
|
213
|
+
# This is a simplified implementation
|
214
|
+
return FastQuantity(result_value, left_val.unit)
|
215
|
+
else:
|
216
|
+
raise ValueError("Exponent must be dimensionless number")
|
217
|
+
else:
|
218
|
+
raise ValueError(f"Unknown operator: {self.operator}")
|
219
|
+
except Exception as e:
|
220
|
+
if isinstance(e, ValueError):
|
221
|
+
raise
|
222
|
+
raise ValueError(f"Error evaluating binary operation '{self}': {e}") from e
|
223
|
+
|
224
|
+
def get_variables(self) -> set[str]:
|
225
|
+
return self.left.get_variables() | self.right.get_variables()
|
226
|
+
|
227
|
+
def simplify(self) -> Expression:
|
228
|
+
left_simplified = self.left.simplify()
|
229
|
+
right_simplified = self.right.simplify()
|
230
|
+
|
231
|
+
# Basic simplification rules
|
232
|
+
if isinstance(left_simplified, Constant) and isinstance(right_simplified, Constant):
|
233
|
+
# Evaluate constant expressions
|
234
|
+
dummy_vars = {}
|
235
|
+
try:
|
236
|
+
result = BinaryOperation(self.operator, left_simplified, right_simplified).evaluate(dummy_vars)
|
237
|
+
return Constant(result)
|
238
|
+
except (ValueError, TypeError, ArithmeticError):
|
239
|
+
pass
|
240
|
+
|
241
|
+
return BinaryOperation(self.operator, left_simplified, right_simplified)
|
242
|
+
|
243
|
+
def __str__(self) -> str:
|
244
|
+
# Handle operator precedence for cleaner string representation
|
245
|
+
precedence = {'+': 1, '-': 1, '*': 2, '/': 2, '**': 3}
|
246
|
+
left_str = str(self.left)
|
247
|
+
right_str = str(self.right)
|
248
|
+
|
249
|
+
# Add parentheses only when needed based on precedence
|
250
|
+
if isinstance(self.left, BinaryOperation) and precedence.get(self.left.operator, 0) < precedence.get(self.operator, 0):
|
251
|
+
left_str = f"({left_str})"
|
252
|
+
if isinstance(self.right, BinaryOperation) and precedence.get(self.right.operator, 0) < precedence.get(self.operator, 0):
|
253
|
+
right_str = f"({right_str})"
|
254
|
+
|
255
|
+
return f"{left_str} {self.operator} {right_str}"
|
256
|
+
|
257
|
+
|
258
|
+
class ComparisonExpression(Expression):
|
259
|
+
"""Comparison expression for conditional logic."""
|
260
|
+
|
261
|
+
def __init__(self, operator: str, left: Expression, right: Expression):
|
262
|
+
self.operator = operator
|
263
|
+
self.left = left
|
264
|
+
self.right = right
|
265
|
+
|
266
|
+
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> 'FastQuantity':
|
267
|
+
"""Evaluate comparison and return dimensionless result (1.0 for True, 0.0 for False)."""
|
268
|
+
|
269
|
+
left_val = self.left.evaluate(variable_values)
|
270
|
+
right_val = self.right.evaluate(variable_values)
|
271
|
+
|
272
|
+
# Convert to same units for comparison if possible
|
273
|
+
try:
|
274
|
+
if left_val._dimension_sig == right_val._dimension_sig and left_val.unit != right_val.unit:
|
275
|
+
right_val = right_val.to(left_val.unit)
|
276
|
+
except (ValueError, TypeError, AttributeError):
|
277
|
+
pass
|
278
|
+
|
279
|
+
if self.operator == '<':
|
280
|
+
result = left_val.value < right_val.value
|
281
|
+
elif self.operator == '<=':
|
282
|
+
result = left_val.value <= right_val.value
|
283
|
+
elif self.operator == '>':
|
284
|
+
result = left_val.value > right_val.value
|
285
|
+
elif self.operator == '>=':
|
286
|
+
result = left_val.value >= right_val.value
|
287
|
+
elif self.operator == '==':
|
288
|
+
result = abs(left_val.value - right_val.value) < 1e-10
|
289
|
+
elif self.operator == '!=':
|
290
|
+
result = abs(left_val.value - right_val.value) >= 1e-10
|
291
|
+
else:
|
292
|
+
raise ValueError(f"Unknown comparison operator: {self.operator}")
|
293
|
+
|
294
|
+
return FastQuantity(1.0 if result else 0.0, DimensionlessUnits.dimensionless)
|
295
|
+
|
296
|
+
def get_variables(self) -> set[str]:
|
297
|
+
return self.left.get_variables() | self.right.get_variables()
|
298
|
+
|
299
|
+
def simplify(self) -> Expression:
|
300
|
+
return ComparisonExpression(self.operator, self.left.simplify(), self.right.simplify())
|
301
|
+
|
302
|
+
def __str__(self) -> str:
|
303
|
+
return f"({self.left} {self.operator} {self.right})"
|
304
|
+
|
305
|
+
|
306
|
+
class UnaryFunction(Expression):
|
307
|
+
"""Unary mathematical function expression."""
|
308
|
+
|
309
|
+
def __init__(self, function_name: str, operand: Expression):
|
310
|
+
self.function_name = function_name
|
311
|
+
self.operand = operand
|
312
|
+
|
313
|
+
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> 'FastQuantity':
|
314
|
+
|
315
|
+
operand_val = self.operand.evaluate(variable_values)
|
316
|
+
|
317
|
+
if self.function_name == 'sin':
|
318
|
+
# Assume input is in radians, result is dimensionless
|
319
|
+
result_value = math.sin(operand_val.value)
|
320
|
+
return FastQuantity(result_value, DimensionlessUnits.dimensionless)
|
321
|
+
elif self.function_name == 'cos':
|
322
|
+
result_value = math.cos(operand_val.value)
|
323
|
+
return FastQuantity(result_value, DimensionlessUnits.dimensionless)
|
324
|
+
elif self.function_name == 'tan':
|
325
|
+
result_value = math.tan(operand_val.value)
|
326
|
+
return FastQuantity(result_value, DimensionlessUnits.dimensionless)
|
327
|
+
elif self.function_name == 'sqrt':
|
328
|
+
# For sqrt, we need to handle units carefully
|
329
|
+
result_value = math.sqrt(operand_val.value)
|
330
|
+
# This is simplified - proper unit handling would need dimensional analysis
|
331
|
+
return FastQuantity(result_value, operand_val.unit)
|
332
|
+
elif self.function_name == 'abs':
|
333
|
+
return FastQuantity(abs(operand_val.value), operand_val.unit)
|
334
|
+
elif self.function_name == 'ln':
|
335
|
+
# Natural log - input should be dimensionless
|
336
|
+
result_value = math.log(operand_val.value)
|
337
|
+
return FastQuantity(result_value, DimensionlessUnits.dimensionless)
|
338
|
+
elif self.function_name == 'log10':
|
339
|
+
result_value = math.log10(operand_val.value)
|
340
|
+
return FastQuantity(result_value, DimensionlessUnits.dimensionless)
|
341
|
+
elif self.function_name == 'exp':
|
342
|
+
# Exponential - input should be dimensionless
|
343
|
+
result_value = math.exp(operand_val.value)
|
344
|
+
return FastQuantity(result_value, DimensionlessUnits.dimensionless)
|
345
|
+
else:
|
346
|
+
raise ValueError(f"Unknown function: {self.function_name}")
|
347
|
+
|
348
|
+
def get_variables(self) -> set[str]:
|
349
|
+
return self.operand.get_variables()
|
350
|
+
|
351
|
+
def simplify(self) -> Expression:
|
352
|
+
simplified_operand = self.operand.simplify()
|
353
|
+
if isinstance(simplified_operand, Constant):
|
354
|
+
# Evaluate constant functions at compile time
|
355
|
+
try:
|
356
|
+
dummy_vars = {}
|
357
|
+
result = UnaryFunction(self.function_name, simplified_operand).evaluate(dummy_vars)
|
358
|
+
return Constant(result)
|
359
|
+
except (ValueError, TypeError, ArithmeticError):
|
360
|
+
pass
|
361
|
+
return UnaryFunction(self.function_name, simplified_operand)
|
362
|
+
|
363
|
+
def __str__(self) -> str:
|
364
|
+
return f"{self.function_name}({self.operand})"
|
365
|
+
|
366
|
+
|
367
|
+
class ConditionalExpression(Expression):
|
368
|
+
"""Conditional expression: if condition then true_expr else false_expr."""
|
369
|
+
|
370
|
+
def __init__(self, condition: Expression, true_expr: Expression, false_expr: Expression):
|
371
|
+
self.condition = condition
|
372
|
+
self.true_expr = true_expr
|
373
|
+
self.false_expr = false_expr
|
374
|
+
|
375
|
+
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> 'FastQuantity':
|
376
|
+
condition_val = self.condition.evaluate(variable_values)
|
377
|
+
# Consider non-zero as True
|
378
|
+
if abs(condition_val.value) > 1e-10:
|
379
|
+
return self.true_expr.evaluate(variable_values)
|
380
|
+
else:
|
381
|
+
return self.false_expr.evaluate(variable_values)
|
382
|
+
|
383
|
+
def get_variables(self) -> set[str]:
|
384
|
+
return (self.condition.get_variables() |
|
385
|
+
self.true_expr.get_variables() |
|
386
|
+
self.false_expr.get_variables())
|
387
|
+
|
388
|
+
def simplify(self) -> Expression:
|
389
|
+
simplified_condition = self.condition.simplify()
|
390
|
+
simplified_true = self.true_expr.simplify()
|
391
|
+
simplified_false = self.false_expr.simplify()
|
392
|
+
|
393
|
+
# If condition is constant, choose the appropriate branch
|
394
|
+
if isinstance(simplified_condition, Constant):
|
395
|
+
try:
|
396
|
+
dummy_vars = {}
|
397
|
+
condition_val = simplified_condition.evaluate(dummy_vars)
|
398
|
+
if abs(condition_val.value) > 1e-10:
|
399
|
+
return simplified_true
|
400
|
+
else:
|
401
|
+
return simplified_false
|
402
|
+
except (ValueError, TypeError, ArithmeticError):
|
403
|
+
pass
|
404
|
+
|
405
|
+
return ConditionalExpression(simplified_condition, simplified_true, simplified_false)
|
406
|
+
|
407
|
+
def __str__(self) -> str:
|
408
|
+
return f"if({self.condition}, {self.true_expr}, {self.false_expr})"
|
409
|
+
|
410
|
+
|
411
|
+
# Convenience functions for mathematical operations
|
412
|
+
def sin(expr: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float]) -> UnaryFunction:
|
413
|
+
"""Sine function."""
|
414
|
+
return UnaryFunction('sin', Expression._wrap_operand(expr))
|
415
|
+
|
416
|
+
def cos(expr: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float]) -> UnaryFunction:
|
417
|
+
"""Cosine function."""
|
418
|
+
return UnaryFunction('cos', Expression._wrap_operand(expr))
|
419
|
+
|
420
|
+
def tan(expr: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float]) -> UnaryFunction:
|
421
|
+
"""Tangent function."""
|
422
|
+
return UnaryFunction('tan', Expression._wrap_operand(expr))
|
423
|
+
|
424
|
+
def sqrt(expr: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float]) -> UnaryFunction:
|
425
|
+
"""Square root function."""
|
426
|
+
return UnaryFunction('sqrt', Expression._wrap_operand(expr))
|
427
|
+
|
428
|
+
def abs_expr(expr: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float]) -> UnaryFunction:
|
429
|
+
"""Absolute value function."""
|
430
|
+
return UnaryFunction('abs', Expression._wrap_operand(expr))
|
431
|
+
|
432
|
+
def ln(expr: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float]) -> UnaryFunction:
|
433
|
+
"""Natural logarithm function."""
|
434
|
+
return UnaryFunction('ln', Expression._wrap_operand(expr))
|
435
|
+
|
436
|
+
def log10(expr: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float]) -> UnaryFunction:
|
437
|
+
"""Base-10 logarithm function."""
|
438
|
+
return UnaryFunction('log10', Expression._wrap_operand(expr))
|
439
|
+
|
440
|
+
def exp(expr: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float]) -> UnaryFunction:
|
441
|
+
"""Exponential function."""
|
442
|
+
return UnaryFunction('exp', Expression._wrap_operand(expr))
|
443
|
+
|
444
|
+
def cond_expr(condition: Union[Expression, 'ComparisonExpression'],
|
445
|
+
true_expr: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float],
|
446
|
+
false_expr: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float]) -> ConditionalExpression:
|
447
|
+
"""Conditional expression: if condition then true_expr else false_expr."""
|
448
|
+
return ConditionalExpression(
|
449
|
+
condition if isinstance(condition, Expression) else condition,
|
450
|
+
Expression._wrap_operand(true_expr),
|
451
|
+
Expression._wrap_operand(false_expr)
|
452
|
+
)
|
453
|
+
|
454
|
+
def min_expr(*expressions: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float]) -> Expression:
|
455
|
+
"""Minimum of multiple expressions."""
|
456
|
+
if len(expressions) < 2:
|
457
|
+
raise ValueError("min_expr requires at least 2 arguments")
|
458
|
+
|
459
|
+
wrapped_expressions = [Expression._wrap_operand(expr) for expr in expressions]
|
460
|
+
result = wrapped_expressions[0]
|
461
|
+
|
462
|
+
for expr in wrapped_expressions[1:]:
|
463
|
+
# min(a, b) = if(a < b, a, b)
|
464
|
+
result = cond_expr(result < expr, result, expr)
|
465
|
+
|
466
|
+
return result
|
467
|
+
|
468
|
+
def max_expr(*expressions: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float]) -> Expression:
|
469
|
+
"""Maximum of multiple expressions."""
|
470
|
+
if len(expressions) < 2:
|
471
|
+
raise ValueError("max_expr requires at least 2 arguments")
|
472
|
+
|
473
|
+
wrapped_expressions = [Expression._wrap_operand(expr) for expr in expressions]
|
474
|
+
result = wrapped_expressions[0]
|
475
|
+
|
476
|
+
for expr in wrapped_expressions[1:]:
|
477
|
+
# max(a, b) = if(a > b, a, b)
|
478
|
+
result = cond_expr(result > expr, result, expr)
|
479
|
+
|
480
|
+
return result
|
qnty/unit.py
CHANGED
@@ -5,9 +5,9 @@ Unit System
|
|
5
5
|
Unit definitions, constants and registry for the high-performance unit system.
|
6
6
|
"""
|
7
7
|
|
8
|
-
from typing import Dict, Tuple, List
|
9
8
|
from dataclasses import dataclass
|
10
|
-
|
9
|
+
|
10
|
+
from .dimension import DIMENSIONLESS, LENGTH, PRESSURE, DimensionSignature
|
11
11
|
|
12
12
|
|
13
13
|
@dataclass(frozen=True)
|
@@ -46,11 +46,11 @@ class HighPerformanceRegistry:
|
|
46
46
|
"""Ultra-fast registry with pre-computed conversion tables."""
|
47
47
|
|
48
48
|
def __init__(self):
|
49
|
-
self.units:
|
50
|
-
self.conversion_table:
|
51
|
-
self.dimensional_groups:
|
52
|
-
self._dimension_cache:
|
53
|
-
|
49
|
+
self.units: dict[str, UnitDefinition] = {}
|
50
|
+
self.conversion_table: dict[tuple[str, str], float] = {} # (from_unit, to_unit) -> factor
|
51
|
+
self.dimensional_groups: dict[int, list[UnitDefinition]] = {}
|
52
|
+
self._dimension_cache: dict[int, UnitConstant] = {} # Cache for common dimension mappings
|
53
|
+
|
54
54
|
self._initialize_units()
|
55
55
|
self._precompute_conversions()
|
56
56
|
|
@@ -110,4 +110,4 @@ class HighPerformanceRegistry:
|
|
110
110
|
|
111
111
|
|
112
112
|
# Global high-performance registry
|
113
|
-
registry = HighPerformanceRegistry()
|
113
|
+
registry = HighPerformanceRegistry()
|
qnty/units.py
CHANGED
@@ -7,7 +7,6 @@ Type-safe unit constants for common engineering units.
|
|
7
7
|
|
8
8
|
from .unit import UnitConstant, registry
|
9
9
|
|
10
|
-
|
11
10
|
# =====================================================================
|
12
11
|
# Type-Safe Unit Constants (No More Strings!)
|
13
12
|
# =====================================================================
|
@@ -31,7 +30,7 @@ class LengthUnits:
|
|
31
30
|
class PressureUnits:
|
32
31
|
"""Type-safe pressure unit constants."""
|
33
32
|
pascal = UnitConstant(registry.units["pascal"])
|
34
|
-
kilopascal = UnitConstant(registry.units["kilopascal"])
|
33
|
+
kilopascal = UnitConstant(registry.units["kilopascal"])
|
35
34
|
megapascal = UnitConstant(registry.units["megapascal"])
|
36
35
|
psi = UnitConstant(registry.units["psi"])
|
37
36
|
bar = UnitConstant(registry.units["bar"])
|
@@ -44,4 +43,4 @@ class PressureUnits:
|
|
44
43
|
|
45
44
|
class DimensionlessUnits:
|
46
45
|
"""Type-safe dimensionless unit constants."""
|
47
|
-
dimensionless = UnitConstant(registry.units["dimensionless"])
|
46
|
+
dimensionless = UnitConstant(registry.units["dimensionless"])
|