qnty 0.0.6__tar.gz → 0.0.8__tar.gz
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-0.0.6 → qnty-0.0.8}/PKG-INFO +1 -1
- {qnty-0.0.6 → qnty-0.0.8}/pyproject.toml +1 -1
- {qnty-0.0.6 → qnty-0.0.8}/src/qnty/dimension.py +2 -2
- {qnty-0.0.6 → qnty-0.0.8}/src/qnty/equation.py +81 -0
- {qnty-0.0.6 → qnty-0.0.8}/src/qnty/expression.py +119 -58
- {qnty-0.0.6 → qnty-0.0.8}/src/qnty/unit.py +2 -2
- {qnty-0.0.6 → qnty-0.0.8}/src/qnty/units.py +8 -8
- {qnty-0.0.6 → qnty-0.0.8}/src/qnty/variable.py +39 -2
- {qnty-0.0.6 → qnty-0.0.8}/src/qnty/variable_types/expression_variable.py +38 -0
- {qnty-0.0.6 → qnty-0.0.8}/src/qnty/variables.py +8 -7
- {qnty-0.0.6 → qnty-0.0.8}/README.md +0 -0
- {qnty-0.0.6 → qnty-0.0.8}/src/qnty/__init__.py +0 -0
- {qnty-0.0.6 → qnty-0.0.8}/src/qnty/prefixes.py +0 -0
- {qnty-0.0.6 → qnty-0.0.8}/src/qnty/unit_types/__init__.py +0 -0
- {qnty-0.0.6 → qnty-0.0.8}/src/qnty/unit_types/base.py +0 -0
- {qnty-0.0.6 → qnty-0.0.8}/src/qnty/variable_types/__init__.py +0 -0
- {qnty-0.0.6 → qnty-0.0.8}/src/qnty/variable_types/base.py +0 -0
- {qnty-0.0.6 → qnty-0.0.8}/src/qnty/variable_types/typed_variable.py +0 -0
- {qnty-0.0.6 → qnty-0.0.8}/src/qnty/variables.pyi +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: qnty
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.8
|
4
4
|
Summary: High-performance unit system library for Python with dimensional safety and fast unit conversions
|
5
5
|
License: Apache-2.0
|
6
6
|
Keywords: units,dimensional analysis,engineering,physics,quantities,measurements
|
@@ -28,7 +28,7 @@ class DimensionSignature:
|
|
28
28
|
"""Immutable dimension signature for zero-cost dimensional analysis."""
|
29
29
|
|
30
30
|
# Store as bit pattern for ultra-fast comparison
|
31
|
-
_signature: int = 1
|
31
|
+
_signature: int | float = 1
|
32
32
|
|
33
33
|
@classmethod
|
34
34
|
def create(cls, length=0, mass=0, time=0, current=0, temp=0, amount=0, luminosity=0):
|
@@ -55,7 +55,7 @@ class DimensionSignature:
|
|
55
55
|
return DimensionSignature(self._signature * other._signature)
|
56
56
|
|
57
57
|
def __truediv__(self, other):
|
58
|
-
return DimensionSignature(self._signature
|
58
|
+
return DimensionSignature(self._signature / other._signature)
|
59
59
|
|
60
60
|
def __pow__(self, power):
|
61
61
|
return DimensionSignature(self._signature ** power)
|
@@ -117,7 +117,88 @@ class Equation:
|
|
117
117
|
except Exception:
|
118
118
|
return False
|
119
119
|
|
120
|
+
def _discover_variables_from_scope(self) -> dict[str, TypeSafeVariable]:
|
121
|
+
"""Automatically discover variables from the calling scope."""
|
122
|
+
import inspect
|
123
|
+
|
124
|
+
# Get the frame that called this method (skip through __str__ calls)
|
125
|
+
frame = inspect.currentframe()
|
126
|
+
try:
|
127
|
+
# Skip frames until we find one outside the equation system
|
128
|
+
while frame and (
|
129
|
+
frame.f_code.co_filename.endswith(('equation.py', 'expression.py')) or
|
130
|
+
frame.f_code.co_name in ['__str__', '__repr__']
|
131
|
+
):
|
132
|
+
frame = frame.f_back
|
133
|
+
|
134
|
+
if not frame:
|
135
|
+
return {}
|
136
|
+
|
137
|
+
# Combine local and global variables
|
138
|
+
all_vars = {**frame.f_globals, **frame.f_locals}
|
139
|
+
|
140
|
+
# Find TypeSafeVariable objects that match our required variables
|
141
|
+
required_vars = self.variables
|
142
|
+
discovered = {}
|
143
|
+
|
144
|
+
for var_name in required_vars:
|
145
|
+
for name, obj in all_vars.items():
|
146
|
+
if hasattr(obj, 'symbol') and obj.symbol == var_name:
|
147
|
+
discovered[var_name] = obj
|
148
|
+
break
|
149
|
+
elif hasattr(obj, 'name') and obj.name == var_name:
|
150
|
+
discovered[var_name] = obj
|
151
|
+
break
|
152
|
+
|
153
|
+
return discovered
|
154
|
+
|
155
|
+
finally:
|
156
|
+
del frame
|
157
|
+
|
158
|
+
def _can_auto_solve(self) -> tuple[bool, str, dict[str, TypeSafeVariable]]:
|
159
|
+
"""Check if equation can be auto-solved from scope."""
|
160
|
+
try:
|
161
|
+
discovered = self._discover_variables_from_scope()
|
162
|
+
|
163
|
+
# Check if this is a simple assignment equation (one unknown)
|
164
|
+
unknowns = []
|
165
|
+
knowns = []
|
166
|
+
|
167
|
+
for var_name in self.variables:
|
168
|
+
if var_name in discovered:
|
169
|
+
var = discovered[var_name]
|
170
|
+
if hasattr(var, 'is_known') and not var.is_known:
|
171
|
+
unknowns.append(var_name)
|
172
|
+
elif hasattr(var, 'quantity') and var.quantity is not None:
|
173
|
+
knowns.append(var_name)
|
174
|
+
else:
|
175
|
+
unknowns.append(var_name) # Assume unknown if no quantity
|
176
|
+
else:
|
177
|
+
return False, "", {} # Missing variable
|
178
|
+
|
179
|
+
# Can only auto-solve if there's exactly one unknown
|
180
|
+
if len(unknowns) == 1:
|
181
|
+
return True, unknowns[0], discovered
|
182
|
+
|
183
|
+
return False, "", {}
|
184
|
+
|
185
|
+
except Exception:
|
186
|
+
return False, "", {}
|
187
|
+
|
188
|
+
def _try_auto_solve(self) -> bool:
|
189
|
+
"""Try to automatically solve the equation if possible."""
|
190
|
+
try:
|
191
|
+
can_solve, target_var, variables = self._can_auto_solve()
|
192
|
+
if can_solve:
|
193
|
+
self.solve_for(target_var, variables)
|
194
|
+
return True
|
195
|
+
return False
|
196
|
+
except Exception:
|
197
|
+
return False
|
198
|
+
|
120
199
|
def __str__(self) -> str:
|
200
|
+
# Try to auto-solve if possible before displaying
|
201
|
+
self._try_auto_solve()
|
121
202
|
return f"{self.lhs} = {self.rhs}"
|
122
203
|
|
123
204
|
def __repr__(self) -> str:
|
@@ -62,6 +62,80 @@ class Expression(ABC):
|
|
62
62
|
def __str__(self) -> str:
|
63
63
|
pass
|
64
64
|
|
65
|
+
def _discover_variables_from_scope(self) -> dict[str, 'TypeSafeVariable']:
|
66
|
+
"""Automatically discover variables from the calling scope."""
|
67
|
+
import inspect
|
68
|
+
|
69
|
+
# Get the frame that called this method (skip through __str__ calls)
|
70
|
+
frame = inspect.currentframe()
|
71
|
+
try:
|
72
|
+
# Skip frames until we find one outside the expression system
|
73
|
+
while frame and (
|
74
|
+
frame.f_code.co_filename.endswith('expression.py') or
|
75
|
+
frame.f_code.co_name in ['__str__', '__repr__']
|
76
|
+
):
|
77
|
+
frame = frame.f_back
|
78
|
+
|
79
|
+
if not frame:
|
80
|
+
return {}
|
81
|
+
|
82
|
+
# Combine local and global variables
|
83
|
+
all_vars = {**frame.f_globals, **frame.f_locals}
|
84
|
+
|
85
|
+
# Find TypeSafeVariable objects that match our required variables
|
86
|
+
required_vars = self.get_variables()
|
87
|
+
discovered = {}
|
88
|
+
|
89
|
+
for var_name in required_vars:
|
90
|
+
for name, obj in all_vars.items():
|
91
|
+
# Check if this is a TypeSafeVariable with matching symbol/name
|
92
|
+
if hasattr(obj, 'symbol') and obj.symbol == var_name:
|
93
|
+
discovered[var_name] = obj
|
94
|
+
break
|
95
|
+
elif hasattr(obj, 'name') and obj.name == var_name:
|
96
|
+
discovered[var_name] = obj
|
97
|
+
break
|
98
|
+
# Check if this is an Expression that can be evaluated to get the variable
|
99
|
+
elif hasattr(obj, 'get_variables') and name == var_name:
|
100
|
+
# This is an expression named after our variable - try to evaluate it
|
101
|
+
try:
|
102
|
+
if hasattr(obj, '_can_auto_evaluate'):
|
103
|
+
can_eval, expr_vars = obj._can_auto_evaluate()
|
104
|
+
if can_eval:
|
105
|
+
result = obj.evaluate(expr_vars)
|
106
|
+
# Create a temporary variable to hold the result
|
107
|
+
from .variables import Length # Import here to avoid circular import
|
108
|
+
temp_var = Length(result.value, result.unit.symbol, f"temp_{var_name}")
|
109
|
+
temp_var.symbol = var_name
|
110
|
+
discovered[var_name] = temp_var
|
111
|
+
break
|
112
|
+
except Exception:
|
113
|
+
pass
|
114
|
+
|
115
|
+
return discovered
|
116
|
+
|
117
|
+
finally:
|
118
|
+
del frame
|
119
|
+
|
120
|
+
def _can_auto_evaluate(self) -> tuple[bool, dict[str, 'TypeSafeVariable']]:
|
121
|
+
"""Check if expression can be auto-evaluated from scope."""
|
122
|
+
try:
|
123
|
+
discovered = self._discover_variables_from_scope()
|
124
|
+
required_vars = self.get_variables()
|
125
|
+
|
126
|
+
# Check if all required variables are available and have values
|
127
|
+
for var_name in required_vars:
|
128
|
+
if var_name not in discovered:
|
129
|
+
return False, {}
|
130
|
+
var = discovered[var_name]
|
131
|
+
if not hasattr(var, 'quantity') or var.quantity is None:
|
132
|
+
return False, {}
|
133
|
+
|
134
|
+
return True, discovered
|
135
|
+
|
136
|
+
except Exception:
|
137
|
+
return False, {}
|
138
|
+
|
65
139
|
def __add__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
66
140
|
return BinaryOperation('+', self, wrap_operand(other))
|
67
141
|
|
@@ -93,17 +167,17 @@ class Expression(ABC):
|
|
93
167
|
return BinaryOperation('**', wrap_operand(other), self)
|
94
168
|
|
95
169
|
# Comparison operators for conditional expressions
|
96
|
-
def __lt__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> '
|
97
|
-
return
|
170
|
+
def __lt__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'BinaryOperation':
|
171
|
+
return BinaryOperation('<', self, self._wrap_operand(other))
|
98
172
|
|
99
|
-
def __le__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> '
|
100
|
-
return
|
173
|
+
def __le__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'BinaryOperation':
|
174
|
+
return BinaryOperation('<=', self, self._wrap_operand(other))
|
101
175
|
|
102
|
-
def __gt__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> '
|
103
|
-
return
|
176
|
+
def __gt__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'BinaryOperation':
|
177
|
+
return BinaryOperation('>', self, self._wrap_operand(other))
|
104
178
|
|
105
|
-
def __ge__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> '
|
106
|
-
return
|
179
|
+
def __ge__(self, other: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'BinaryOperation':
|
180
|
+
return BinaryOperation('>=', self, self._wrap_operand(other))
|
107
181
|
|
108
182
|
@staticmethod
|
109
183
|
def _wrap_operand(operand: Union['Expression', 'TypeSafeVariable', 'FastQuantity', int, float]) -> 'Expression':
|
@@ -167,6 +241,7 @@ class Constant(Expression):
|
|
167
241
|
self.value = value
|
168
242
|
|
169
243
|
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> 'FastQuantity':
|
244
|
+
del variable_values # Suppress unused variable warning
|
170
245
|
return self.value
|
171
246
|
|
172
247
|
def get_variables(self) -> set[str]:
|
@@ -214,6 +289,30 @@ class BinaryOperation(Expression):
|
|
214
289
|
return FastQuantity(result_value, left_val.unit)
|
215
290
|
else:
|
216
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)
|
217
316
|
else:
|
218
317
|
raise ValueError(f"Unknown operator: {self.operator}")
|
219
318
|
except Exception as e:
|
@@ -241,8 +340,17 @@ class BinaryOperation(Expression):
|
|
241
340
|
return BinaryOperation(self.operator, left_simplified, right_simplified)
|
242
341
|
|
243
342
|
def __str__(self) -> str:
|
343
|
+
# Try to auto-evaluate if all variables are available
|
344
|
+
can_eval, variables = self._can_auto_evaluate()
|
345
|
+
if can_eval:
|
346
|
+
try:
|
347
|
+
result = self.evaluate(variables)
|
348
|
+
return str(result)
|
349
|
+
except Exception:
|
350
|
+
pass # Fall back to symbolic representation
|
351
|
+
|
244
352
|
# Handle operator precedence for cleaner string representation
|
245
|
-
precedence = {'+': 1, '-': 1, '*': 2, '/': 2, '**': 3}
|
353
|
+
precedence = {'+': 1, '-': 1, '*': 2, '/': 2, '**': 3, '<': 0, '<=': 0, '>': 0, '>=': 0, '==': 0, '!=': 0}
|
246
354
|
left_str = str(self.left)
|
247
355
|
right_str = str(self.right)
|
248
356
|
|
@@ -260,60 +368,13 @@ class BinaryOperation(Expression):
|
|
260
368
|
# Need parentheses if:
|
261
369
|
# - Right has lower precedence, OR
|
262
370
|
# - Same precedence and current operator is left-associative (- or /)
|
263
|
-
if (right_prec < curr_prec or
|
371
|
+
if (right_prec < curr_prec or
|
264
372
|
(right_prec == curr_prec and self.operator in ['-', '/'])):
|
265
373
|
right_str = f"({right_str})"
|
266
374
|
|
267
375
|
return f"{left_str} {self.operator} {right_str}"
|
268
376
|
|
269
377
|
|
270
|
-
class ComparisonExpression(Expression):
|
271
|
-
"""Comparison expression for conditional logic."""
|
272
|
-
|
273
|
-
def __init__(self, operator: str, left: Expression, right: Expression):
|
274
|
-
self.operator = operator
|
275
|
-
self.left = left
|
276
|
-
self.right = right
|
277
|
-
|
278
|
-
def evaluate(self, variable_values: dict[str, 'TypeSafeVariable']) -> 'FastQuantity':
|
279
|
-
"""Evaluate comparison and return dimensionless result (1.0 for True, 0.0 for False)."""
|
280
|
-
|
281
|
-
left_val = self.left.evaluate(variable_values)
|
282
|
-
right_val = self.right.evaluate(variable_values)
|
283
|
-
|
284
|
-
# Convert to same units for comparison if possible
|
285
|
-
try:
|
286
|
-
if left_val._dimension_sig == right_val._dimension_sig and left_val.unit != right_val.unit:
|
287
|
-
right_val = right_val.to(left_val.unit)
|
288
|
-
except (ValueError, TypeError, AttributeError):
|
289
|
-
pass
|
290
|
-
|
291
|
-
if self.operator == '<':
|
292
|
-
result = left_val.value < right_val.value
|
293
|
-
elif self.operator == '<=':
|
294
|
-
result = left_val.value <= right_val.value
|
295
|
-
elif self.operator == '>':
|
296
|
-
result = left_val.value > right_val.value
|
297
|
-
elif self.operator == '>=':
|
298
|
-
result = left_val.value >= right_val.value
|
299
|
-
elif self.operator == '==':
|
300
|
-
result = abs(left_val.value - right_val.value) < 1e-10
|
301
|
-
elif self.operator == '!=':
|
302
|
-
result = abs(left_val.value - right_val.value) >= 1e-10
|
303
|
-
else:
|
304
|
-
raise ValueError(f"Unknown comparison operator: {self.operator}")
|
305
|
-
|
306
|
-
return FastQuantity(1.0 if result else 0.0, DimensionlessUnits.dimensionless)
|
307
|
-
|
308
|
-
def get_variables(self) -> set[str]:
|
309
|
-
return self.left.get_variables() | self.right.get_variables()
|
310
|
-
|
311
|
-
def simplify(self) -> Expression:
|
312
|
-
return ComparisonExpression(self.operator, self.left.simplify(), self.right.simplify())
|
313
|
-
|
314
|
-
def __str__(self) -> str:
|
315
|
-
return f"({self.left} {self.operator} {self.right})"
|
316
|
-
|
317
378
|
|
318
379
|
class UnaryFunction(Expression):
|
319
380
|
"""Unary mathematical function expression."""
|
@@ -453,7 +514,7 @@ def exp(expr: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float])
|
|
453
514
|
"""Exponential function."""
|
454
515
|
return UnaryFunction('exp', Expression._wrap_operand(expr))
|
455
516
|
|
456
|
-
def cond_expr(condition: Union[Expression, '
|
517
|
+
def cond_expr(condition: Union[Expression, 'BinaryOperation'],
|
457
518
|
true_expr: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float],
|
458
519
|
false_expr: Union[Expression, 'TypeSafeVariable', 'FastQuantity', int, float]) -> ConditionalExpression:
|
459
520
|
"""Conditional expression: if condition then true_expr else false_expr."""
|
@@ -64,8 +64,8 @@ class HighPerformanceRegistry:
|
|
64
64
|
def __init__(self):
|
65
65
|
self.units: dict[str, UnitDefinition] = {}
|
66
66
|
self.conversion_table: dict[tuple[str, str], float] = {} # (from_unit, to_unit) -> factor
|
67
|
-
self.dimensional_groups: dict[int, list[UnitDefinition]] = {}
|
68
|
-
self._dimension_cache: dict[int, UnitConstant] = {} # Cache for common dimension mappings
|
67
|
+
self.dimensional_groups: dict[int | float, list[UnitDefinition]] = {}
|
68
|
+
self._dimension_cache: dict[int | float, UnitConstant] = {} # Cache for common dimension mappings
|
69
69
|
self._finalized = False
|
70
70
|
self.base_units: dict[str, UnitDefinition] = {} # Track base units for prefix generation
|
71
71
|
self.prefixable_units: set[str] = set() # Track which units can have prefixes
|
@@ -22,6 +22,9 @@ from .dimension import (
|
|
22
22
|
CONCENTRATION,
|
23
23
|
DIMENSIONLESS,
|
24
24
|
DYNAMIC_FLUIDITY,
|
25
|
+
ELECTRICAL_CONDUCTANCE,
|
26
|
+
ELECTRICAL_PERMITTIVITY,
|
27
|
+
ELECTRICAL_RESISTIVITY,
|
25
28
|
ELECTRIC_CAPACITANCE,
|
26
29
|
ELECTRIC_CHARGE,
|
27
30
|
ELECTRIC_CURRENT_INTENSITY,
|
@@ -30,9 +33,6 @@ from .dimension import (
|
|
30
33
|
ELECTRIC_INDUCTANCE,
|
31
34
|
ELECTRIC_POTENTIAL,
|
32
35
|
ELECTRIC_RESISTANCE,
|
33
|
-
ELECTRICAL_CONDUCTANCE,
|
34
|
-
ELECTRICAL_PERMITTIVITY,
|
35
|
-
ELECTRICAL_RESISTIVITY,
|
36
36
|
ENERGY_FLUX,
|
37
37
|
ENERGY_HEAT_WORK,
|
38
38
|
ENERGY_PER_UNIT_AREA,
|
@@ -65,14 +65,14 @@ from .dimension import (
|
|
65
65
|
MASS_FLUX,
|
66
66
|
MASS_TRANSFER_COEFFICIENT,
|
67
67
|
MOLALITY_OF_SOLUTE_I,
|
68
|
+
MOLARITY_OF_I,
|
68
69
|
MOLAR_CONCENTRATION_BY_MASS,
|
69
70
|
MOLAR_FLOW_RATE,
|
70
71
|
MOLAR_FLUX,
|
71
72
|
MOLAR_HEAT_CAPACITY,
|
72
|
-
MOLARITY_OF_I,
|
73
|
-
MOMENT_OF_INERTIA,
|
74
73
|
MOMENTUM_FLOW_RATE,
|
75
74
|
MOMENTUM_FLUX,
|
75
|
+
MOMENT_OF_INERTIA,
|
76
76
|
NORMALITY_OF_SOLUTION,
|
77
77
|
PARTICLE_DENSITY,
|
78
78
|
PERMEABILITY,
|
@@ -109,7 +109,7 @@ from .dimension import (
|
|
109
109
|
VOLUMETRIC_FLOW_RATE,
|
110
110
|
VOLUMETRIC_FLUX,
|
111
111
|
VOLUMETRIC_MASS_FLOW_RATE,
|
112
|
-
WAVENUMBER
|
112
|
+
WAVENUMBER
|
113
113
|
)
|
114
114
|
|
115
115
|
# Comprehensive unit definitions organized by dimensional signature
|
@@ -7896,8 +7896,8 @@ UNIT_DEFINITIONS = {
|
|
7896
7896
|
|
7897
7897
|
def create_unit_class(class_name: str, dimension_data: dict) -> type:
|
7898
7898
|
"""Dynamically create a unit class with all unit constants as attributes."""
|
7899
|
-
from .prefixes import get_prefix_by_name
|
7900
7899
|
from .unit import UnitConstant, UnitDefinition
|
7900
|
+
from .prefixes import get_prefix_by_name
|
7901
7901
|
|
7902
7902
|
# Create a new class dynamically
|
7903
7903
|
unit_class = type(class_name, (), {})
|
@@ -7950,8 +7950,8 @@ def create_unit_class(class_name: str, dimension_data: dict) -> type:
|
|
7950
7950
|
|
7951
7951
|
def register_all_units(registry):
|
7952
7952
|
"""Register all unit definitions to the given registry with prefix support."""
|
7953
|
-
from .prefixes import PREFIXABLE_UNITS
|
7954
7953
|
from .unit import UnitDefinition
|
7954
|
+
from .prefixes import get_prefix_by_name, StandardPrefixes, PREFIXABLE_UNITS
|
7955
7955
|
|
7956
7956
|
# First pass: register base units with prefixes where applicable
|
7957
7957
|
for dimension_data in UNIT_DEFINITIONS.values():
|
@@ -115,7 +115,7 @@ class FastQuantity:
|
|
115
115
|
return FastQuantity(self.value / other, self.unit)
|
116
116
|
|
117
117
|
# Fast dimensional analysis using cached signatures
|
118
|
-
result_dimension_sig = self._dimension_sig
|
118
|
+
result_dimension_sig = self._dimension_sig / other._dimension_sig
|
119
119
|
|
120
120
|
# Use cached SI factors for conversion
|
121
121
|
self_si_value = self.value * self._si_factor
|
@@ -128,7 +128,7 @@ class FastQuantity:
|
|
128
128
|
|
129
129
|
return FastQuantity(result_value, result_unit)
|
130
130
|
|
131
|
-
def _find_result_unit_fast(self, result_dimension_sig: int,
|
131
|
+
def _find_result_unit_fast(self, result_dimension_sig: int | float,
|
132
132
|
left_qty: FastQuantity, right_qty: FastQuantity) -> UnitConstant:
|
133
133
|
"""Ultra-fast unit finding using cached dimension signatures."""
|
134
134
|
|
@@ -257,6 +257,43 @@ class TypeSafeVariable(Generic[DimensionType]):
|
|
257
257
|
self.is_known = True
|
258
258
|
return self
|
259
259
|
|
260
|
+
def update(self, value=None, unit=None, quantity=None, is_known=None):
|
261
|
+
"""Update variable properties flexibly."""
|
262
|
+
if quantity is not None:
|
263
|
+
self.quantity = quantity
|
264
|
+
elif value is not None:
|
265
|
+
# Create setter and call the appropriate unit property
|
266
|
+
setter = self.set(value)
|
267
|
+
if unit is not None:
|
268
|
+
# Try to find the unit property on the setter
|
269
|
+
if hasattr(setter, unit):
|
270
|
+
getattr(setter, unit)
|
271
|
+
elif hasattr(setter, unit + 's'): # Handle singular/plural
|
272
|
+
getattr(setter, unit + 's')
|
273
|
+
elif unit.endswith('s') and hasattr(setter, unit[:-1]): # Handle plural to singular
|
274
|
+
getattr(setter, unit[:-1])
|
275
|
+
else:
|
276
|
+
raise ValueError(f"Unit '{unit}' not found for {self.__class__.__name__}")
|
277
|
+
else:
|
278
|
+
# If no unit specified, we can't automatically choose a unit
|
279
|
+
# The caller should specify either a unit or a quantity
|
280
|
+
raise ValueError("Must specify either 'unit' with 'value' or provide 'quantity' directly")
|
281
|
+
if is_known is not None:
|
282
|
+
self.is_known = is_known
|
283
|
+
return self # For method chaining
|
284
|
+
|
285
|
+
def mark_known(self, quantity=None):
|
286
|
+
"""Mark variable as known, optionally updating its value."""
|
287
|
+
self.is_known = True
|
288
|
+
if quantity is not None:
|
289
|
+
self.quantity = quantity
|
290
|
+
return self # For method chaining
|
291
|
+
|
292
|
+
def mark_unknown(self):
|
293
|
+
"""Mark variable as unknown."""
|
294
|
+
self.is_known = False
|
295
|
+
return self # For method chaining
|
296
|
+
|
260
297
|
def __str__(self):
|
261
298
|
return f"{self.name}: {self.quantity}" if self.quantity else f"{self.name}: unset"
|
262
299
|
|
@@ -66,3 +66,41 @@ class ExpressionVariable(TypeSafeVariable):
|
|
66
66
|
def __rpow__(self, other: FastQuantity | int | float) -> Expression:
|
67
67
|
"""Reverse power for this variable."""
|
68
68
|
return wrap_operand(other) ** wrap_operand(self)
|
69
|
+
|
70
|
+
# Comparison methods
|
71
|
+
def lt(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
|
72
|
+
"""Less than comparison (<)."""
|
73
|
+
from ..expression import BinaryOperation
|
74
|
+
return BinaryOperation('<', wrap_operand(self), wrap_operand(other))
|
75
|
+
|
76
|
+
def leq(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
|
77
|
+
"""Less than or equal comparison (<=)."""
|
78
|
+
from ..expression import BinaryOperation
|
79
|
+
return BinaryOperation('<=', wrap_operand(self), wrap_operand(other))
|
80
|
+
|
81
|
+
def geq(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
|
82
|
+
"""Greater than or equal comparison (>=)."""
|
83
|
+
from ..expression import BinaryOperation
|
84
|
+
return BinaryOperation('>=', wrap_operand(self), wrap_operand(other))
|
85
|
+
|
86
|
+
def gt(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
|
87
|
+
"""Greater than comparison (>)."""
|
88
|
+
from ..expression import BinaryOperation
|
89
|
+
return BinaryOperation('>', wrap_operand(self), wrap_operand(other))
|
90
|
+
|
91
|
+
# Python comparison operators
|
92
|
+
def __lt__(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
|
93
|
+
"""Less than comparison (<) operator."""
|
94
|
+
return self.lt(other)
|
95
|
+
|
96
|
+
def __le__(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
|
97
|
+
"""Less than or equal comparison (<=) operator."""
|
98
|
+
return self.leq(other)
|
99
|
+
|
100
|
+
def __gt__(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
|
101
|
+
"""Greater than comparison (>) operator."""
|
102
|
+
return self.gt(other)
|
103
|
+
|
104
|
+
def __ge__(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
|
105
|
+
"""Greater than or equal comparison (>=) operator."""
|
106
|
+
return self.geq(other)
|
@@ -9,7 +9,6 @@ Auto-generated from unit_data.json and dimension_mapping.json.
|
|
9
9
|
|
10
10
|
from typing import Any
|
11
11
|
|
12
|
-
from . import units
|
13
12
|
from .dimension import (
|
14
13
|
ABSORBED_DOSE,
|
15
14
|
ACCELERATION,
|
@@ -25,6 +24,9 @@ from .dimension import (
|
|
25
24
|
CONCENTRATION,
|
26
25
|
DIMENSIONLESS,
|
27
26
|
DYNAMIC_FLUIDITY,
|
27
|
+
ELECTRICAL_CONDUCTANCE,
|
28
|
+
ELECTRICAL_PERMITTIVITY,
|
29
|
+
ELECTRICAL_RESISTIVITY,
|
28
30
|
ELECTRIC_CAPACITANCE,
|
29
31
|
ELECTRIC_CHARGE,
|
30
32
|
ELECTRIC_CURRENT_INTENSITY,
|
@@ -33,9 +35,6 @@ from .dimension import (
|
|
33
35
|
ELECTRIC_INDUCTANCE,
|
34
36
|
ELECTRIC_POTENTIAL,
|
35
37
|
ELECTRIC_RESISTANCE,
|
36
|
-
ELECTRICAL_CONDUCTANCE,
|
37
|
-
ELECTRICAL_PERMITTIVITY,
|
38
|
-
ELECTRICAL_RESISTIVITY,
|
39
38
|
ENERGY_FLUX,
|
40
39
|
ENERGY_HEAT_WORK,
|
41
40
|
ENERGY_PER_UNIT_AREA,
|
@@ -69,15 +68,15 @@ from .dimension import (
|
|
69
68
|
MASS_FRACTION_OF_I,
|
70
69
|
MASS_TRANSFER_COEFFICIENT,
|
71
70
|
MOLALITY_OF_SOLUTE_I,
|
71
|
+
MOLARITY_OF_I,
|
72
72
|
MOLAR_CONCENTRATION_BY_MASS,
|
73
73
|
MOLAR_FLOW_RATE,
|
74
74
|
MOLAR_FLUX,
|
75
75
|
MOLAR_HEAT_CAPACITY,
|
76
|
-
MOLARITY_OF_I,
|
77
76
|
MOLE_FRACTION_OF_I,
|
78
|
-
MOMENT_OF_INERTIA,
|
79
77
|
MOMENTUM_FLOW_RATE,
|
80
78
|
MOMENTUM_FLUX,
|
79
|
+
MOMENT_OF_INERTIA,
|
81
80
|
NORMALITY_OF_SOLUTION,
|
82
81
|
PARTICLE_DENSITY,
|
83
82
|
PERCENT,
|
@@ -111,14 +110,16 @@ from .dimension import (
|
|
111
110
|
VISCOSITY_DYNAMIC,
|
112
111
|
VISCOSITY_KINEMATIC,
|
113
112
|
VOLUME,
|
114
|
-
VOLUME_FRACTION_OF_I,
|
115
113
|
VOLUMETRIC_CALORIFIC_HEATING_VALUE,
|
116
114
|
VOLUMETRIC_COEFFICIENT_OF_EXPANSION,
|
117
115
|
VOLUMETRIC_FLOW_RATE,
|
118
116
|
VOLUMETRIC_FLUX,
|
119
117
|
VOLUMETRIC_MASS_FLOW_RATE,
|
118
|
+
VOLUME_FRACTION_OF_I,
|
120
119
|
WAVENUMBER,
|
121
120
|
)
|
121
|
+
from . import units
|
122
|
+
from .unit import UnitConstant, UnitDefinition
|
122
123
|
from .variable import FastQuantity, TypeSafeSetter
|
123
124
|
from .variable_types.typed_variable import TypedVariable
|
124
125
|
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|