qnty 0.0.9__py3-none-any.whl → 0.1.1__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 (92) hide show
  1. qnty/__init__.py +2 -3
  2. qnty/constants/__init__.py +10 -0
  3. qnty/constants/numerical.py +18 -0
  4. qnty/constants/solvers.py +6 -0
  5. qnty/constants/tests.py +6 -0
  6. qnty/dimensions/__init__.py +23 -0
  7. qnty/dimensions/base.py +97 -0
  8. qnty/dimensions/field_dims.py +126 -0
  9. qnty/dimensions/field_dims.pyi +128 -0
  10. qnty/dimensions/signature.py +111 -0
  11. qnty/equations/__init__.py +1 -1
  12. qnty/equations/equation.py +118 -155
  13. qnty/equations/system.py +68 -65
  14. qnty/expressions/__init__.py +25 -46
  15. qnty/expressions/formatter.py +188 -0
  16. qnty/expressions/functions.py +46 -68
  17. qnty/expressions/nodes.py +540 -384
  18. qnty/expressions/types.py +70 -0
  19. qnty/problems/__init__.py +145 -0
  20. qnty/problems/composition.py +1101 -0
  21. qnty/problems/problem.py +737 -0
  22. qnty/problems/rules.py +145 -0
  23. qnty/problems/solving.py +1216 -0
  24. qnty/problems/validation.py +127 -0
  25. qnty/quantities/__init__.py +28 -5
  26. qnty/quantities/base_qnty.py +677 -0
  27. qnty/quantities/field_converters.py +24004 -0
  28. qnty/quantities/field_qnty.py +1012 -0
  29. qnty/{generated/setters.py → quantities/field_setter.py} +3071 -2961
  30. qnty/{generated/quantities.py → quantities/field_vars.py} +829 -444
  31. qnty/{generated/quantities.pyi → quantities/field_vars.pyi} +1289 -1290
  32. qnty/solving/manager.py +50 -44
  33. qnty/solving/order.py +181 -133
  34. qnty/solving/solvers/__init__.py +2 -9
  35. qnty/solving/solvers/base.py +27 -37
  36. qnty/solving/solvers/iterative.py +115 -135
  37. qnty/solving/solvers/simultaneous.py +93 -165
  38. qnty/units/__init__.py +1 -0
  39. qnty/{generated/units.py → units/field_units.py} +1700 -991
  40. qnty/units/field_units.pyi +2461 -0
  41. qnty/units/prefixes.py +58 -105
  42. qnty/units/registry.py +76 -89
  43. qnty/utils/__init__.py +16 -0
  44. qnty/utils/caching/__init__.py +23 -0
  45. qnty/utils/caching/manager.py +401 -0
  46. qnty/utils/error_handling/__init__.py +66 -0
  47. qnty/utils/error_handling/context.py +39 -0
  48. qnty/utils/error_handling/exceptions.py +96 -0
  49. qnty/utils/error_handling/handlers.py +171 -0
  50. qnty/utils/logging.py +4 -4
  51. qnty/utils/protocols.py +164 -0
  52. qnty/utils/scope_discovery.py +420 -0
  53. {qnty-0.0.9.dist-info → qnty-0.1.1.dist-info}/METADATA +1 -1
  54. qnty-0.1.1.dist-info/RECORD +60 -0
  55. qnty/_backup/problem_original.py +0 -1251
  56. qnty/_backup/quantity.py +0 -63
  57. qnty/codegen/cli.py +0 -125
  58. qnty/codegen/generators/data/unit_data.json +0 -8807
  59. qnty/codegen/generators/data_processor.py +0 -345
  60. qnty/codegen/generators/dimensions_gen.py +0 -434
  61. qnty/codegen/generators/doc_generator.py +0 -141
  62. qnty/codegen/generators/out/dimension_mapping.json +0 -974
  63. qnty/codegen/generators/out/dimension_metadata.json +0 -123
  64. qnty/codegen/generators/out/units_metadata.json +0 -223
  65. qnty/codegen/generators/quantities_gen.py +0 -159
  66. qnty/codegen/generators/setters_gen.py +0 -178
  67. qnty/codegen/generators/stubs_gen.py +0 -167
  68. qnty/codegen/generators/units_gen.py +0 -295
  69. qnty/expressions/cache.py +0 -94
  70. qnty/generated/dimensions.py +0 -514
  71. qnty/problem/__init__.py +0 -91
  72. qnty/problem/base.py +0 -142
  73. qnty/problem/composition.py +0 -385
  74. qnty/problem/composition_mixin.py +0 -382
  75. qnty/problem/equations.py +0 -413
  76. qnty/problem/metaclass.py +0 -302
  77. qnty/problem/reconstruction.py +0 -1016
  78. qnty/problem/solving.py +0 -180
  79. qnty/problem/validation.py +0 -64
  80. qnty/problem/variables.py +0 -239
  81. qnty/quantities/expression_quantity.py +0 -314
  82. qnty/quantities/quantity.py +0 -428
  83. qnty/quantities/typed_quantity.py +0 -215
  84. qnty/validation/__init__.py +0 -0
  85. qnty/validation/registry.py +0 -0
  86. qnty/validation/rules.py +0 -167
  87. qnty-0.0.9.dist-info/RECORD +0 -63
  88. /qnty/{codegen → extensions}/__init__.py +0 -0
  89. /qnty/{codegen/generators → extensions/integration}/__init__.py +0 -0
  90. /qnty/{codegen/generators/utils → extensions/plotting}/__init__.py +0 -0
  91. /qnty/{generated → extensions/reporting}/__init__.py +0 -0
  92. {qnty-0.0.9.dist-info → qnty-0.1.1.dist-info}/WHEEL +0 -0
qnty/problem/equations.py DELETED
@@ -1,413 +0,0 @@
1
- """
2
- Equation processing pipeline for Problem class.
3
-
4
- This module contains all equation-related operations including adding equations,
5
- processing equation validation, handling missing variables, and equation reconstruction.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- from typing import TYPE_CHECKING, Any
11
-
12
- if TYPE_CHECKING:
13
- from qnty.equations import Equation
14
- from qnty.quantities import TypeSafeVariable as Variable
15
-
16
- from qnty.equations import Equation
17
-
18
- # Constants for equation processing
19
- MATHEMATICAL_OPERATORS = ['+', '-', '*', '/', ' / ', ' * ', ' + ', ' - ']
20
- COMMON_COMPOSITE_VARIABLES = ['P', 'c', 'S', 'E', 'W', 'Y']
21
-
22
-
23
- # Custom Exceptions
24
- class EquationValidationError(ValueError):
25
- """Raised when an equation fails validation."""
26
- pass
27
-
28
-
29
- class EquationsMixin:
30
- """Mixin class providing equation management functionality."""
31
-
32
- # These attributes/methods will be provided by other mixins in the final Problem class
33
- variables: dict[str, Variable]
34
- equations: list[Equation]
35
- equation_system: Any
36
- sub_problems: dict[str, Any]
37
- logger: Any
38
- equation_reconstructor: Any
39
-
40
- def _create_placeholder_variable(self, _symbol: str) -> None:
41
- """Will be provided by VariablesMixin."""
42
- ...
43
-
44
- def add_equation(self, equation: Equation) -> None:
45
- """
46
- Add an equation to the problem.
47
-
48
- The equation will be validated to ensure all referenced variables exist.
49
- Missing variables that look like simple identifiers will be auto-created
50
- as unknown placeholders.
51
-
52
- Args:
53
- equation: Equation object to add to the problem
54
-
55
- Raises:
56
- EquationValidationError: If the equation is invalid or cannot be processed
57
-
58
- Note:
59
- Adding an equation resets the problem to unsolved state.
60
- """
61
- if equation is None:
62
- raise EquationValidationError("Cannot add None equation to problem")
63
-
64
- # Fix VariableReferences in equation to point to correct Variables
65
- equation = self._fix_variable_references(equation)
66
-
67
- # Validate that all variables in the equation exist
68
- try:
69
- equation_vars = equation.get_all_variables()
70
- except Exception as e:
71
- raise EquationValidationError(f"Failed to extract variables from equation: {e}") from e
72
-
73
- missing_vars = [var for var in equation_vars if var not in self.variables]
74
-
75
- if missing_vars:
76
- self._handle_missing_variables(missing_vars)
77
-
78
- # Check again for remaining missing variables
79
- equation_vars = equation.get_all_variables()
80
- remaining_missing = [var for var in equation_vars if var not in self.variables]
81
- if remaining_missing:
82
- self.logger.warning(f"Equation references missing variables: {remaining_missing}")
83
-
84
- self.equations.append(equation)
85
- self.equation_system.add_equation(equation)
86
- self.is_solved = False
87
-
88
- def add_equations(self, *equations: Equation):
89
- """Add multiple equations to the problem."""
90
- for eq in equations:
91
- self.add_equation(eq)
92
- return self
93
-
94
- def _handle_missing_variables(self, missing_vars: list[str]) -> None:
95
- """Handle missing variables by creating placeholders for simple symbols."""
96
- for missing_var in missing_vars:
97
- if self._is_simple_variable_symbol(missing_var):
98
- self._create_placeholder_variable(missing_var)
99
-
100
- def _is_simple_variable_symbol(self, symbol: str) -> bool:
101
- """Check if a symbol looks like a simple variable identifier."""
102
- return (symbol.isidentifier() and
103
- not any(char in symbol for char in ['(', ')', '+', '-', '*', '/', ' ']))
104
-
105
- def _process_equation(self, attr_name: str, equation: Equation) -> bool:
106
- """Process a single equation and add it to the problem if valid."""
107
- return self._process_equation_impl(attr_name, equation)
108
-
109
- def _process_equation_impl(self, attr_name: str, equation: Equation) -> bool:
110
- """
111
- Process a single equation and determine if it should be added.
112
- Returns True if the equation was successfully processed.
113
- """
114
- # First, update variable references to use symbols instead of names
115
- updated_equation = self._update_equation_variable_references(equation)
116
-
117
- # Check if this equation contains delayed expressions
118
- try:
119
- has_delayed = self.equation_reconstructor.contains_delayed_expressions(updated_equation)
120
- if has_delayed:
121
- return self._handle_delayed_equation(attr_name, updated_equation)
122
- except Exception as e:
123
- self.logger.debug(f"Error checking delayed expressions for {attr_name}: {e}")
124
-
125
- # Check if this equation has invalid self-references
126
- try:
127
- has_self_ref = self._has_invalid_self_references(updated_equation)
128
- if has_self_ref:
129
- self.logger.debug(f"Skipping invalid self-referencing equation {attr_name}: {updated_equation}")
130
- return False
131
- except Exception as e:
132
- self.logger.debug(f"Error checking self-references for {attr_name}: {e}")
133
-
134
- # Check if equation references non-existent variables
135
- try:
136
- has_missing = self._equation_has_missing_variables(updated_equation)
137
- if has_missing:
138
- return self._handle_equation_with_missing_variables(attr_name, updated_equation)
139
- except Exception as e:
140
- self.logger.debug(f"Error checking missing variables for {attr_name}: {e}")
141
-
142
- # Process valid equation
143
- self.add_equation(updated_equation)
144
- return True
145
-
146
- def _handle_delayed_equation(self, attr_name: str, equation: Equation) -> bool:
147
- """Handle equations with delayed expressions."""
148
- resolved_equation = self.equation_reconstructor.resolve_delayed_equation(equation)
149
- if resolved_equation:
150
- self.add_equation(resolved_equation)
151
- setattr(self, attr_name, resolved_equation)
152
- return True
153
- else:
154
- self.logger.debug(f"Skipping unresolvable delayed equation {attr_name}: {equation}")
155
- return False
156
-
157
- def _handle_equation_with_missing_variables(self, attr_name: str, equation: Equation) -> bool:
158
- """Handle equations that reference missing variables."""
159
- # Handle conditional equations more carefully
160
- if self._is_conditional_equation(equation):
161
- return self._handle_conditional_equation(attr_name, equation)
162
-
163
- # Only attempt reconstruction for simple mathematical expressions from composition
164
- if self.equation_reconstructor.should_attempt_reconstruction(equation):
165
- return self._attempt_equation_reconstruction(attr_name, equation)
166
- else:
167
- # Skip other problematic equations
168
- self.logger.debug(f"Skipping equation with missing variables {attr_name}: {equation}")
169
- return False
170
-
171
- def _handle_conditional_equation(self, attr_name: str, equation: Equation) -> bool:
172
- """Handle conditional equations with missing variables."""
173
- missing_vars = equation.get_all_variables() - set(self.variables.keys())
174
-
175
- # Skip conditional equations from sub-problems in composed systems
176
- if self.sub_problems and self._is_conditional_equation_from_subproblem(equation, attr_name):
177
- self.logger.debug(f"Skipping conditional equation {attr_name} from sub-problem in composed system")
178
- return False
179
-
180
- # Check for composite expressions that might be reconstructable
181
- unresolvable_vars = [var for var in missing_vars
182
- if any(op in var for op in MATHEMATICAL_OPERATORS)]
183
-
184
- if self.sub_problems and unresolvable_vars:
185
- # Before skipping, try to reconstruct conditional equations with composite expressions
186
- self.logger.debug(f"Attempting to reconstruct conditional equation {attr_name} with composite variables: {unresolvable_vars}")
187
- reconstructed_equation = self.equation_reconstructor.reconstruct_composite_expressions_generically(equation)
188
- if reconstructed_equation:
189
- self.logger.debug(f"Successfully reconstructed conditional equation {attr_name}: {reconstructed_equation}")
190
- self.add_equation(reconstructed_equation)
191
- setattr(self, attr_name, reconstructed_equation)
192
- return True
193
- else:
194
- self.logger.debug(f"Failed to reconstruct conditional equation {attr_name}, trying simple substitution")
195
- # Try simple substitution for basic arithmetic expressions
196
- reconstructed_equation = self._try_simple_substitution(equation, missing_vars)
197
- if reconstructed_equation:
198
- self.add_equation(reconstructed_equation)
199
- return True
200
- else:
201
- self.add_equation(equation)
202
- return True
203
- else:
204
- # Try to add the conditional equation even with missing simple variables
205
- self.add_equation(equation)
206
- return True
207
-
208
- def _try_simple_substitution(self, _equation: Equation, _missing_vars: set[str]) -> Equation | None:
209
- """
210
- Try simple substitution for basic arithmetic expressions in conditional equations.
211
-
212
- The real issue is that nested expressions in conditionals aren't being handled properly.
213
- For now, just return None and let the equation be added as-is.
214
- """
215
- return None
216
-
217
- def _attempt_equation_reconstruction(self, attr_name: str, equation: Equation) -> bool:
218
- """Attempt to reconstruct equations with composite expressions."""
219
- missing_vars = equation.get_all_variables() - set(self.variables.keys())
220
- self.logger.debug(f"Attempting to reconstruct equation {attr_name} with missing variables: {missing_vars}")
221
-
222
- reconstructed_equation = self.equation_reconstructor.reconstruct_composite_expressions_generically(equation)
223
- if reconstructed_equation:
224
- self.logger.debug(f"Successfully reconstructed {attr_name}: {reconstructed_equation}")
225
- self.add_equation(reconstructed_equation)
226
- setattr(self, attr_name, reconstructed_equation)
227
- return True
228
- else:
229
- self.logger.debug(f"Failed to reconstruct equation {attr_name}: {equation}")
230
- return False
231
-
232
- def _get_equation_lhs_symbol(self, equation: Equation) -> str | None:
233
- """Safely extract the symbol from equation's left-hand side."""
234
- return getattr(equation.lhs, 'symbol', None)
235
-
236
- def _is_conditional_equation(self, equation: Equation) -> bool:
237
- """Check if an equation is a conditional equation."""
238
- return 'cond(' in str(equation)
239
-
240
- def _equation_has_missing_variables(self, equation: Equation) -> bool:
241
- """Check if an equation references variables that don't exist in this problem."""
242
- try:
243
- all_vars = equation.get_all_variables()
244
- missing_vars = [var for var in all_vars if var not in self.variables]
245
- return len(missing_vars) > 0
246
- except Exception:
247
- return False
248
-
249
- def _has_invalid_self_references(self, equation: Equation) -> bool:
250
- """
251
- Check if an equation has invalid self-references.
252
- This catches malformed equations like 'c = max(c, c)' from class definition.
253
- """
254
- try:
255
- # Get LHS variable - check if it has symbol attribute
256
- lhs_symbol = self._get_equation_lhs_symbol(equation)
257
- if lhs_symbol is None:
258
- return False
259
-
260
- # Get all variables referenced in RHS
261
- rhs_vars = equation.rhs.get_variables() if hasattr(equation.rhs, 'get_variables') else set()
262
-
263
- # Check if the LHS variable appears multiple times in RHS (indicating self-reference)
264
- # This is a heuristic - a proper implementation would parse the expression tree
265
- equation_str = str(equation)
266
- if lhs_symbol in rhs_vars:
267
- # For conditional equations, self-references are often valid (as fallback values)
268
- if 'cond(' in equation_str:
269
- return False # Allow self-references in conditional equations
270
-
271
- # Count occurrences of the variable in the equation string
272
- count = equation_str.count(lhs_symbol)
273
- if count > 2: # LHS + multiple RHS occurrences
274
- return True
275
-
276
- return False
277
-
278
- except Exception:
279
- return False
280
-
281
- def _is_conditional_equation_from_subproblem(self, equation: Equation, _equation_name: str) -> bool:
282
- """
283
- Check if a conditional equation comes from a sub-problem and should be skipped in composed systems.
284
-
285
- In composed systems, if a sub-problem's conditional variable is already set to a known value,
286
- we don't want to include the conditional equation that would override that known value.
287
- """
288
- try:
289
- # Check if this is a conditional equation with a known LHS variable
290
- lhs_symbol = self._get_equation_lhs_symbol(equation)
291
- if lhs_symbol is not None:
292
-
293
- # Check if the LHS variable already exists and is known
294
- if lhs_symbol in self.variables:
295
- var = self.variables[lhs_symbol]
296
- # If the variable is already known (set explicitly in composition),
297
- # and this conditional equation references missing variables, skip it
298
- missing_vars = equation.get_all_variables() - set(self.variables.keys())
299
- if missing_vars and var.is_known:
300
- # This is a sub-problem's conditional equation for a variable that's already known
301
- # No point including it since the value is already determined
302
- return True
303
-
304
- return False
305
-
306
- except Exception:
307
- return False
308
-
309
- def _fix_variable_references(self, equation: Equation) -> Equation:
310
- """
311
- Fix VariableReferences in equation expressions to point to Variables in problem.variables.
312
-
313
- This resolves issues where expression trees contain VariableReferences pointing to
314
- proxy Variables from class creation time instead of the actual Variables in the problem.
315
- """
316
- try:
317
- # Fix the RHS expression
318
- fixed_rhs = self._fix_expression_variables(equation.rhs)
319
-
320
- # Create new equation with fixed RHS (LHS should already be correct)
321
- return Equation(equation.name, equation.lhs, fixed_rhs)
322
-
323
- except Exception as e:
324
- self.logger.debug(f"Error fixing variable references in equation {equation.name}: {e}")
325
- return equation # Return original if fixing fails
326
-
327
- def _fix_expression_variables(self, expr):
328
- """
329
- Recursively fix VariableReferences in an expression tree to point to correct Variables.
330
- """
331
- from qnty.expressions import BinaryOperation, Constant, VariableReference
332
-
333
- if isinstance(expr, VariableReference):
334
- # Check if this VariableReference points to the wrong Variable
335
- symbol = getattr(expr, 'symbol', None)
336
- if symbol and symbol in self.variables:
337
- correct_var = self.variables[symbol]
338
- if expr.variable is not correct_var:
339
- # Create new VariableReference pointing to correct Variable
340
- return VariableReference(correct_var)
341
- return expr
342
-
343
- elif isinstance(expr, BinaryOperation):
344
- # Recursively fix left and right operands
345
- fixed_left = self._fix_expression_variables(expr.left)
346
- fixed_right = self._fix_expression_variables(expr.right)
347
- return BinaryOperation(expr.operator, fixed_left, fixed_right)
348
-
349
- elif hasattr(expr, 'operand'):
350
- # Recursively fix operand
351
- fixed_operand = self._fix_expression_variables(expr.operand)
352
- return type(expr)(expr.operator, fixed_operand)
353
-
354
- elif hasattr(expr, 'function_name'):
355
- # Recursively fix left and right operands
356
- fixed_left = self._fix_expression_variables(expr.left)
357
- fixed_right = self._fix_expression_variables(expr.right)
358
- return type(expr)(expr.function_name, fixed_left, fixed_right)
359
-
360
- elif isinstance(expr, Constant):
361
- return expr
362
-
363
- else:
364
- # Unknown expression type, return as-is
365
- return expr
366
-
367
- def _update_equation_variable_references(self, equation: Equation) -> Equation:
368
- """Update VariableReference objects in equation to use symbols instead of names."""
369
- from qnty.expressions import VariableReference
370
-
371
- # Update LHS if it's a VariableReference
372
- updated_lhs = equation.lhs
373
- if isinstance(equation.lhs, VariableReference):
374
- # Find the variable by name and update to use symbol
375
- var_name = equation.lhs.variable.name
376
- matching_var = None
377
- for var in self.variables.values():
378
- if var.name == var_name:
379
- matching_var = var
380
- break
381
- if matching_var and matching_var.symbol:
382
- updated_lhs = VariableReference(matching_var)
383
-
384
- # Update RHS by recursively updating expressions
385
- updated_rhs = self._update_expression_variable_references(equation.rhs)
386
-
387
- # Create new equation with updated references
388
- return Equation(equation.name, updated_lhs, updated_rhs)
389
-
390
- def _update_expression_variable_references(self, expr):
391
- """Recursively update VariableReference objects in expression tree."""
392
- from qnty.expressions import BinaryOperation, Constant, VariableReference
393
-
394
- if isinstance(expr, VariableReference):
395
- # Find the variable by name and update to use symbol
396
- var_name = expr.variable.name
397
- matching_var = None
398
- for var in self.variables.values():
399
- if var.name == var_name:
400
- matching_var = var
401
- break
402
- if matching_var and matching_var.symbol:
403
- return VariableReference(matching_var)
404
- return expr
405
- elif isinstance(expr, BinaryOperation):
406
- updated_left = self._update_expression_variable_references(expr.left)
407
- updated_right = self._update_expression_variable_references(expr.right)
408
- return BinaryOperation(expr.operator, updated_left, updated_right)
409
- elif isinstance(expr, Constant):
410
- return expr
411
- else:
412
- # Return unknown expression types as-is
413
- return expr