qnty 0.0.7__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 (76) 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/equations/equation.py +257 -0
  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/expressions/nodes.py +546 -0
  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/equation.py +0 -216
  63. qnty/expression.py +0 -492
  64. qnty/unit_types/base.py +0 -47
  65. qnty/units.py +0 -8113
  66. qnty/variable.py +0 -263
  67. qnty/variable_types/base.py +0 -58
  68. qnty/variable_types/expression_variable.py +0 -68
  69. qnty/variable_types/typed_variable.py +0 -87
  70. qnty/variables.py +0 -2298
  71. qnty/variables.pyi +0 -6148
  72. qnty-0.0.7.dist-info/METADATA +0 -355
  73. qnty-0.0.7.dist-info/RECORD +0 -19
  74. /qnty/{unit_types → codegen}/__init__.py +0 -0
  75. /qnty/{variable_types → codegen/generators}/__init__.py +0 -0
  76. {qnty-0.0.7.dist-info → qnty-0.0.9.dist-info}/WHEEL +0 -0
@@ -0,0 +1,314 @@
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