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,185 @@
1
+ from typing import Any
2
+
3
+ from qnty.equations.equation import Equation
4
+ from qnty.expressions import VariableReference
5
+ from qnty.quantities import TypeSafeVariable as Variable
6
+ from qnty.solving.order import Order
7
+
8
+ from .base import BaseSolver, SolveResult
9
+
10
+
11
+ class IterativeSolver(BaseSolver):
12
+ """
13
+ Iterative solver that follows dependency order like solving engineering problems by hand.
14
+
15
+ This solver works by:
16
+ 1. Using dependency graph to determine the correct solving order
17
+ 2. Solving variables one by one in dependency order (just like manual solving)
18
+ 3. Preserving units throughout with Pint integration
19
+ 4. Verifying each solution with residual checking
20
+ 5. Repeating until all unknowns are solved
21
+
22
+ This approach mirrors how engineers solve problems by hand: solve what you can
23
+ with what you know, then use those results to solve the next level of dependencies.
24
+ """
25
+
26
+ def can_handle(self, equations: list[Equation], unknowns: set[str],
27
+ dependency_graph: Order | None = None,
28
+ analysis: dict[str, Any] | None = None) -> bool:
29
+ """
30
+ Can handle any system that doesn't have cycles and has at least one unknown.
31
+ """
32
+ if not unknowns:
33
+ return False
34
+
35
+ # The IterativeSolver can now handle cycles through iterative convergence
36
+ # if analysis and analysis.get('has_cycles', False):
37
+ # return False
38
+
39
+ # If we have a dependency graph, we can try to solve
40
+ if dependency_graph:
41
+ return True
42
+
43
+ # As a fallback, we can try to handle any system
44
+ return len(unknowns) > 0
45
+
46
+ def solve(self, equations: list[Equation], variables: dict[str, Variable],
47
+ dependency_graph: Order | None = None,
48
+ max_iterations: int = 100, tolerance: float = 1e-10) -> SolveResult:
49
+ """
50
+ Solve the system iteratively using dependency graph.
51
+ """
52
+ self.steps = []
53
+
54
+ if not dependency_graph:
55
+ return SolveResult(
56
+ variables=variables,
57
+ steps=self.steps,
58
+ success=False,
59
+ message="Dependency graph required for iterative solving",
60
+ method="IterativeSolver"
61
+ )
62
+
63
+ # Make a copy of variables to work with
64
+ working_vars = dict(variables.items())
65
+ known_vars = self._get_known_variables(working_vars)
66
+
67
+ if self.logger:
68
+ self.logger.debug(f"Starting iterative solve with {len(known_vars)} known variables")
69
+
70
+ # Iterative solving
71
+ iteration = -1 # Initialize to handle case where loop doesn't run
72
+ for iteration in range(max_iterations):
73
+ iteration_start = len(known_vars)
74
+
75
+ # Get variables that can be solved in this iteration
76
+ solvable = dependency_graph.get_solvable_variables(known_vars)
77
+
78
+ # Fallback: attempt direct equations for remaining unknowns
79
+ if not solvable:
80
+ remaining_unknowns = [v for v in self._get_unknown_variables(working_vars)
81
+ if v not in known_vars]
82
+ for var_symbol in remaining_unknowns:
83
+ for eq in equations:
84
+ if eq.can_solve_for(var_symbol, known_vars):
85
+ solvable.append(var_symbol)
86
+ break
87
+
88
+ if not solvable:
89
+ # Try to break conditional cycles
90
+ remaining_unknowns = [v for v in self._get_unknown_variables(working_vars)
91
+ if v not in known_vars]
92
+ if remaining_unknowns:
93
+ # Look for conditional equations that can be evaluated
94
+ for var_symbol in remaining_unknowns:
95
+ for eq in equations:
96
+ # Check if LHS is a VariableReference with matching name
97
+ if (isinstance(eq.lhs, VariableReference) and
98
+ eq.lhs.name == var_symbol and
99
+ 'ConditionalExpression' in str(type(eq.rhs))):
100
+ # This is a conditional equation - try to solve it
101
+ try:
102
+ solved_var = eq.solve_for(var_symbol, working_vars)
103
+ working_vars[var_symbol] = solved_var
104
+ known_vars.add(var_symbol)
105
+ solvable = [var_symbol] # Mark as solved this iteration
106
+ if self.logger:
107
+ self.logger.debug(f"Solved conditional cycle: {var_symbol} = {solved_var.quantity}")
108
+ break
109
+ except Exception:
110
+ continue
111
+ if solvable:
112
+ break
113
+
114
+ if not solvable:
115
+ break # No more variables can be solved
116
+
117
+ if self.logger:
118
+ self.logger.debug(f"Iteration {iteration + 1} solvable: {solvable}")
119
+
120
+ # Solve for each solvable variable
121
+ for var_symbol in solvable:
122
+ equation = dependency_graph.get_equation_for_variable(var_symbol, known_vars)
123
+ if equation is None:
124
+ # Try any equation that can solve it
125
+ for eq in equations:
126
+ if eq.can_solve_for(var_symbol, known_vars):
127
+ equation = eq
128
+ break
129
+
130
+ if equation is None:
131
+ continue
132
+
133
+ try:
134
+ solved_var = equation.solve_for(var_symbol, working_vars)
135
+ working_vars[var_symbol] = solved_var
136
+ known_vars.add(var_symbol)
137
+
138
+ # Verify solution by checking residual (like checking work by hand)
139
+ if equation.check_residual(working_vars, tolerance):
140
+ if self.logger:
141
+ self.logger.debug(f"Solution verified for {var_symbol}")
142
+ else:
143
+ if self.logger:
144
+ self.logger.warning(f"Residual check failed for {var_symbol}")
145
+
146
+ self._log_step(
147
+ iteration + 1,
148
+ var_symbol,
149
+ str(equation),
150
+ str(solved_var.quantity),
151
+ 'iterative'
152
+ )
153
+
154
+ except Exception as e:
155
+ if self.logger:
156
+ self.logger.error(f"Failed to solve for {var_symbol}: {e}")
157
+ return SolveResult(
158
+ variables=working_vars,
159
+ steps=self.steps,
160
+ success=False,
161
+ message=f"Failed to solve for {var_symbol}: {e}",
162
+ method="IterativeSolver",
163
+ iterations=iteration + 1
164
+ )
165
+
166
+ # Check for progress
167
+ if len(known_vars) == iteration_start:
168
+ if self.logger:
169
+ self.logger.warning("No progress made, stopping early")
170
+ break
171
+
172
+ # Check if we solved all unknowns
173
+ remaining_unknowns = self._get_unknown_variables(working_vars)
174
+ success = len(remaining_unknowns) == 0
175
+
176
+ message = "All variables solved" if success else f"Could not solve: {remaining_unknowns}"
177
+
178
+ return SolveResult(
179
+ variables=working_vars,
180
+ steps=self.steps,
181
+ success=success,
182
+ message=message,
183
+ method="IterativeSolver",
184
+ iterations=iteration + 1
185
+ )