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
@@ -1,314 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Expression Variable Base Class
|
3
|
-
==============================
|
4
|
-
|
5
|
-
Base class that extends TypeSafeVariable with mathematical expression
|
6
|
-
and equation capabilities.
|
7
|
-
"""
|
8
|
-
|
9
|
-
from __future__ import annotations
|
10
|
-
|
11
|
-
from ..equations.equation import Equation
|
12
|
-
from ..expressions import BinaryOperation, Expression, wrap_operand
|
13
|
-
from .quantity import Quantity, TypeSafeVariable
|
14
|
-
|
15
|
-
# Type alias for cleaner method signatures
|
16
|
-
Operand = TypeSafeVariable | Quantity | int | float | Expression
|
17
|
-
ReverseOperand = Quantity | int | float
|
18
|
-
|
19
|
-
# Cache for commonly used wrapped constants to avoid repeated wrap_operand calls
|
20
|
-
_CONSTANT_CACHE = {}
|
21
|
-
|
22
|
-
def _initialize_constant_cache():
|
23
|
-
"""Initialize cache with common constants."""
|
24
|
-
common_constants = [0, 1, 2, 3, 4, 5, -1, -2, 0.5, 1.0, 2.0, 10, 100, 1000]
|
25
|
-
for value in common_constants:
|
26
|
-
_CONSTANT_CACHE[value] = wrap_operand(value)
|
27
|
-
|
28
|
-
# Initialize on module load
|
29
|
-
_initialize_constant_cache()
|
30
|
-
|
31
|
-
|
32
|
-
class ExpressionQuantity(TypeSafeVariable):
|
33
|
-
"""
|
34
|
-
TypeSafeVariable extended with expression and equation capabilities.
|
35
|
-
|
36
|
-
This adds mathematical operations that create expressions and equations,
|
37
|
-
keeping the base TypeSafeVariable free of these dependencies.
|
38
|
-
"""
|
39
|
-
|
40
|
-
def _get_self_wrapped(self) -> Expression:
|
41
|
-
"""Get wrapped self expression, cached for performance."""
|
42
|
-
if not hasattr(self, '_wrapped_self'):
|
43
|
-
self._wrapped_self = wrap_operand(self)
|
44
|
-
return self._wrapped_self
|
45
|
-
|
46
|
-
@staticmethod
|
47
|
-
def _get_cached_constant(value) -> Expression:
|
48
|
-
"""Get cached wrapped constant for common values."""
|
49
|
-
if isinstance(value, int | float) and value in _CONSTANT_CACHE:
|
50
|
-
return _CONSTANT_CACHE[value]
|
51
|
-
return wrap_operand(value)
|
52
|
-
|
53
|
-
def equals(self, expression: Operand) -> Equation:
|
54
|
-
"""Create an equation: self = expression."""
|
55
|
-
rhs_expr = wrap_operand(expression)
|
56
|
-
return Equation(f"{self.name}_eq", self, rhs_expr)
|
57
|
-
|
58
|
-
def solve_from(self, expression: Operand) -> TypeSafeVariable:
|
59
|
-
"""
|
60
|
-
Create an equation (self = expression) and immediately solve it.
|
61
|
-
|
62
|
-
This is a convenience method that combines equation creation and solving.
|
63
|
-
It automatically discovers variables from the calling scope.
|
64
|
-
|
65
|
-
Args:
|
66
|
-
expression: The expression this variable should equal
|
67
|
-
|
68
|
-
Returns:
|
69
|
-
self: The variable with updated quantity
|
70
|
-
|
71
|
-
Example:
|
72
|
-
T_bar = Length(0.147, "inches", "T_bar")
|
73
|
-
U_m = Dimensionless(0.125, "U_m")
|
74
|
-
T = Length("T", is_known=False)
|
75
|
-
T.solve_from(T_bar * (1 - U_m)) # Solves T = T_bar * (1 - U_m)
|
76
|
-
"""
|
77
|
-
import inspect
|
78
|
-
|
79
|
-
# Create the equation
|
80
|
-
equation = self.equals(expression)
|
81
|
-
|
82
|
-
# Get calling frame for variable discovery
|
83
|
-
frame = inspect.currentframe()
|
84
|
-
if frame is None:
|
85
|
-
raise ValueError("Unable to access calling scope")
|
86
|
-
|
87
|
-
try:
|
88
|
-
frame = frame.f_back # Get caller's frame
|
89
|
-
if frame is None:
|
90
|
-
raise ValueError("Unable to access calling scope")
|
91
|
-
|
92
|
-
# Discover variables from scope
|
93
|
-
variables_found = {}
|
94
|
-
|
95
|
-
# Search locals first
|
96
|
-
for obj in frame.f_locals.values():
|
97
|
-
if self._is_variable(obj):
|
98
|
-
var_name = self._get_variable_name(obj)
|
99
|
-
if var_name:
|
100
|
-
variables_found[var_name] = obj
|
101
|
-
|
102
|
-
# Search globals
|
103
|
-
for obj in frame.f_globals.values():
|
104
|
-
if self._is_variable(obj):
|
105
|
-
var_name = self._get_variable_name(obj)
|
106
|
-
if var_name:
|
107
|
-
variables_found[var_name] = obj
|
108
|
-
|
109
|
-
# Add self
|
110
|
-
self_name = self.symbol if self.symbol else self.name
|
111
|
-
variables_found[self_name] = self
|
112
|
-
|
113
|
-
# Solve the equation
|
114
|
-
equation.solve_for(self_name, variables_found)
|
115
|
-
return self
|
116
|
-
|
117
|
-
finally:
|
118
|
-
del frame
|
119
|
-
|
120
|
-
def __add__(self, other: Operand) -> Expression:
|
121
|
-
"""Add this variable to another operand, returning an Expression."""
|
122
|
-
# Fast path for simple cases
|
123
|
-
if isinstance(other, int | float):
|
124
|
-
return BinaryOperation('+', self._get_self_wrapped(), self._get_cached_constant(other))
|
125
|
-
return BinaryOperation('+', self._get_self_wrapped(), wrap_operand(other))
|
126
|
-
|
127
|
-
def __radd__(self, other: ReverseOperand) -> Expression:
|
128
|
-
"""Reverse add for this variable."""
|
129
|
-
return BinaryOperation('+', self._get_cached_constant(other), self._get_self_wrapped())
|
130
|
-
|
131
|
-
def __sub__(self, other: Operand) -> Expression:
|
132
|
-
"""Subtract another operand from this variable, returning an Expression."""
|
133
|
-
if isinstance(other, int | float):
|
134
|
-
return BinaryOperation('-', self._get_self_wrapped(), self._get_cached_constant(other))
|
135
|
-
return BinaryOperation('-', self._get_self_wrapped(), wrap_operand(other))
|
136
|
-
|
137
|
-
def __rsub__(self, other: ReverseOperand) -> Expression:
|
138
|
-
"""Reverse subtract for this variable."""
|
139
|
-
return BinaryOperation('-', self._get_cached_constant(other), self._get_self_wrapped())
|
140
|
-
|
141
|
-
def __mul__(self, other: Operand) -> Expression:
|
142
|
-
"""Multiply this variable by another operand, returning an Expression."""
|
143
|
-
if isinstance(other, int | float):
|
144
|
-
return BinaryOperation('*', self._get_self_wrapped(), self._get_cached_constant(other))
|
145
|
-
return BinaryOperation('*', self._get_self_wrapped(), wrap_operand(other))
|
146
|
-
|
147
|
-
def __rmul__(self, other: ReverseOperand) -> Expression:
|
148
|
-
"""Reverse multiply for this variable."""
|
149
|
-
return BinaryOperation('*', self._get_cached_constant(other), self._get_self_wrapped())
|
150
|
-
|
151
|
-
def __truediv__(self, other: Operand) -> Expression:
|
152
|
-
"""Divide this variable by another operand, returning an Expression."""
|
153
|
-
if isinstance(other, int | float):
|
154
|
-
return BinaryOperation('/', self._get_self_wrapped(), self._get_cached_constant(other))
|
155
|
-
return BinaryOperation('/', self._get_self_wrapped(), wrap_operand(other))
|
156
|
-
|
157
|
-
def __rtruediv__(self, other: ReverseOperand) -> Expression:
|
158
|
-
"""Reverse divide for this variable."""
|
159
|
-
return BinaryOperation('/', self._get_cached_constant(other), self._get_self_wrapped())
|
160
|
-
|
161
|
-
def __pow__(self, other: Operand) -> Expression:
|
162
|
-
"""Raise this variable to a power, returning an Expression."""
|
163
|
-
if isinstance(other, int | float):
|
164
|
-
return BinaryOperation('**', self._get_self_wrapped(), self._get_cached_constant(other))
|
165
|
-
return BinaryOperation('**', self._get_self_wrapped(), wrap_operand(other))
|
166
|
-
|
167
|
-
def __rpow__(self, other: ReverseOperand) -> Expression:
|
168
|
-
"""Reverse power for this variable."""
|
169
|
-
return BinaryOperation('**', self._get_cached_constant(other), self._get_self_wrapped())
|
170
|
-
|
171
|
-
# Comparison methods
|
172
|
-
def lt(self, other: Operand) -> Expression:
|
173
|
-
"""Less than comparison (<)."""
|
174
|
-
return BinaryOperation('<', self._get_self_wrapped(), wrap_operand(other))
|
175
|
-
|
176
|
-
def leq(self, other: Operand) -> Expression:
|
177
|
-
"""Less than or equal comparison (<=)."""
|
178
|
-
return BinaryOperation('<=', self._get_self_wrapped(), wrap_operand(other))
|
179
|
-
|
180
|
-
def geq(self, other: Operand) -> Expression:
|
181
|
-
"""Greater than or equal comparison (>=)."""
|
182
|
-
return BinaryOperation('>=', self._get_self_wrapped(), wrap_operand(other))
|
183
|
-
|
184
|
-
def gt(self, other: Operand) -> Expression:
|
185
|
-
"""Greater than comparison (>)."""
|
186
|
-
return BinaryOperation('>', self._get_self_wrapped(), wrap_operand(other))
|
187
|
-
|
188
|
-
# Python comparison operators - optimized direct implementations
|
189
|
-
def __lt__(self, other: Operand) -> Expression:
|
190
|
-
"""Less than comparison (<) operator."""
|
191
|
-
return BinaryOperation('<', self._get_self_wrapped(), wrap_operand(other))
|
192
|
-
|
193
|
-
def __le__(self, other: Operand) -> Expression:
|
194
|
-
"""Less than or equal comparison (<=) operator."""
|
195
|
-
return BinaryOperation('<=', self._get_self_wrapped(), wrap_operand(other))
|
196
|
-
|
197
|
-
def __gt__(self, other: Operand) -> Expression:
|
198
|
-
"""Greater than comparison (>) operator."""
|
199
|
-
return BinaryOperation('>', self._get_self_wrapped(), wrap_operand(other))
|
200
|
-
|
201
|
-
def __ge__(self, other: Operand) -> Expression:
|
202
|
-
"""Greater than or equal comparison (>=) operator."""
|
203
|
-
return BinaryOperation('>=', self._get_self_wrapped(), wrap_operand(other))
|
204
|
-
|
205
|
-
def solve(self) -> TypeSafeVariable:
|
206
|
-
"""
|
207
|
-
Solve for this variable by automatically discovering equations and variables from scope.
|
208
|
-
|
209
|
-
This method:
|
210
|
-
1. Searches the calling scope for equations involving this variable
|
211
|
-
2. Discovers all variables referenced in those equations
|
212
|
-
3. Attempts to solve for this variable using available values
|
213
|
-
|
214
|
-
Returns:
|
215
|
-
self: The variable with updated quantity if solved successfully
|
216
|
-
|
217
|
-
Raises:
|
218
|
-
ValueError: If no solvable equation is found or if solving fails
|
219
|
-
"""
|
220
|
-
import inspect
|
221
|
-
|
222
|
-
# Get the calling frame
|
223
|
-
frame = inspect.currentframe()
|
224
|
-
if frame is None:
|
225
|
-
raise ValueError("Unable to access calling scope")
|
226
|
-
|
227
|
-
try:
|
228
|
-
frame = frame.f_back # Get caller's frame
|
229
|
-
if frame is None:
|
230
|
-
raise ValueError("Unable to access calling scope")
|
231
|
-
|
232
|
-
# Search for equations in the calling scope
|
233
|
-
equations_found = []
|
234
|
-
variables_found = {}
|
235
|
-
|
236
|
-
# Search locals first (most common)
|
237
|
-
for obj in frame.f_locals.values():
|
238
|
-
if self._is_equation(obj) and self._equation_contains_variable(obj):
|
239
|
-
equations_found.append(obj)
|
240
|
-
elif self._is_variable(obj):
|
241
|
-
var_name = self._get_variable_name(obj)
|
242
|
-
if var_name:
|
243
|
-
variables_found[var_name] = obj
|
244
|
-
|
245
|
-
# Search globals if needed
|
246
|
-
for obj in frame.f_globals.values():
|
247
|
-
if self._is_equation(obj) and self._equation_contains_variable(obj):
|
248
|
-
equations_found.append(obj)
|
249
|
-
elif self._is_variable(obj):
|
250
|
-
var_name = self._get_variable_name(obj)
|
251
|
-
if var_name:
|
252
|
-
variables_found[var_name] = obj
|
253
|
-
|
254
|
-
# Add self to variables if not found
|
255
|
-
self_name = self.symbol if self.symbol else self.name
|
256
|
-
if self_name not in variables_found:
|
257
|
-
variables_found[self_name] = self
|
258
|
-
|
259
|
-
# Try to solve using found equations
|
260
|
-
if not equations_found:
|
261
|
-
raise ValueError(f"No equations found in scope containing variable '{self_name}'")
|
262
|
-
|
263
|
-
# Try each equation to see if it can solve for this variable
|
264
|
-
for equation in equations_found:
|
265
|
-
try:
|
266
|
-
if self._can_equation_solve_for_self(equation, variables_found):
|
267
|
-
equation.solve_for(self_name, variables_found)
|
268
|
-
return self
|
269
|
-
except Exception:
|
270
|
-
continue # Try next equation
|
271
|
-
|
272
|
-
raise ValueError(f"Unable to solve for variable '{self_name}' with available equations and values")
|
273
|
-
|
274
|
-
finally:
|
275
|
-
del frame
|
276
|
-
|
277
|
-
def _is_equation(self, obj) -> bool:
|
278
|
-
"""Check if object is an equation."""
|
279
|
-
return hasattr(obj, 'solve_for') and hasattr(obj, 'variables')
|
280
|
-
|
281
|
-
def _is_variable(self, obj) -> bool:
|
282
|
-
"""Check if object is a qnty variable."""
|
283
|
-
return (isinstance(obj, TypeSafeVariable) and
|
284
|
-
hasattr(obj, 'symbol') and hasattr(obj, 'name'))
|
285
|
-
|
286
|
-
def _equation_contains_variable(self, equation) -> bool:
|
287
|
-
"""Check if equation contains this variable."""
|
288
|
-
if not hasattr(equation, 'variables'):
|
289
|
-
return False
|
290
|
-
try:
|
291
|
-
var_name = self.symbol if self.symbol else self.name
|
292
|
-
return var_name in equation.variables
|
293
|
-
except Exception:
|
294
|
-
return False
|
295
|
-
|
296
|
-
def _get_variable_name(self, var) -> str | None:
|
297
|
-
"""Get the name/symbol to use for a variable."""
|
298
|
-
try:
|
299
|
-
return var.symbol if var.symbol else var.name
|
300
|
-
except Exception:
|
301
|
-
return None
|
302
|
-
|
303
|
-
def _can_equation_solve_for_self(self, equation, variables: dict[str, TypeSafeVariable]) -> bool:
|
304
|
-
"""Check if equation can solve for this variable."""
|
305
|
-
try:
|
306
|
-
var_name = self.symbol if self.symbol else self.name
|
307
|
-
|
308
|
-
# Get known variables (those with quantities)
|
309
|
-
known_vars = {name for name, var in variables.items()
|
310
|
-
if var is not self and var.quantity is not None}
|
311
|
-
|
312
|
-
return equation.can_solve_for(var_name, known_vars)
|
313
|
-
except Exception:
|
314
|
-
return False
|