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.
- qnty/__init__.py +140 -58
- qnty/_backup/problem_original.py +1251 -0
- qnty/_backup/quantity.py +63 -0
- qnty/codegen/cli.py +125 -0
- qnty/codegen/generators/data/unit_data.json +8807 -0
- qnty/codegen/generators/data_processor.py +345 -0
- qnty/codegen/generators/dimensions_gen.py +434 -0
- qnty/codegen/generators/doc_generator.py +141 -0
- qnty/codegen/generators/out/dimension_mapping.json +974 -0
- qnty/codegen/generators/out/dimension_metadata.json +123 -0
- qnty/codegen/generators/out/units_metadata.json +223 -0
- qnty/codegen/generators/quantities_gen.py +159 -0
- qnty/codegen/generators/setters_gen.py +178 -0
- qnty/codegen/generators/stubs_gen.py +167 -0
- qnty/codegen/generators/units_gen.py +295 -0
- qnty/codegen/generators/utils/__init__.py +0 -0
- qnty/equations/__init__.py +4 -0
- qnty/equations/equation.py +257 -0
- qnty/equations/system.py +127 -0
- qnty/expressions/__init__.py +61 -0
- qnty/expressions/cache.py +94 -0
- qnty/expressions/functions.py +96 -0
- qnty/expressions/nodes.py +546 -0
- qnty/generated/__init__.py +0 -0
- qnty/generated/dimensions.py +514 -0
- qnty/generated/quantities.py +6003 -0
- qnty/generated/quantities.pyi +4192 -0
- qnty/generated/setters.py +12210 -0
- qnty/generated/units.py +9798 -0
- qnty/problem/__init__.py +91 -0
- qnty/problem/base.py +142 -0
- qnty/problem/composition.py +385 -0
- qnty/problem/composition_mixin.py +382 -0
- qnty/problem/equations.py +413 -0
- qnty/problem/metaclass.py +302 -0
- qnty/problem/reconstruction.py +1016 -0
- qnty/problem/solving.py +180 -0
- qnty/problem/validation.py +64 -0
- qnty/problem/variables.py +239 -0
- qnty/quantities/__init__.py +6 -0
- qnty/quantities/expression_quantity.py +314 -0
- qnty/quantities/quantity.py +428 -0
- qnty/quantities/typed_quantity.py +215 -0
- qnty/solving/__init__.py +0 -0
- qnty/solving/manager.py +90 -0
- qnty/solving/order.py +355 -0
- qnty/solving/solvers/__init__.py +20 -0
- qnty/solving/solvers/base.py +92 -0
- qnty/solving/solvers/iterative.py +185 -0
- qnty/solving/solvers/simultaneous.py +547 -0
- qnty/units/__init__.py +0 -0
- qnty/{prefixes.py → units/prefixes.py} +54 -33
- qnty/{unit.py → units/registry.py} +73 -32
- qnty/utils/__init__.py +0 -0
- qnty/utils/logging.py +40 -0
- qnty/validation/__init__.py +0 -0
- qnty/validation/registry.py +0 -0
- qnty/validation/rules.py +167 -0
- qnty-0.0.9.dist-info/METADATA +199 -0
- qnty-0.0.9.dist-info/RECORD +63 -0
- qnty/dimension.py +0 -186
- qnty/equation.py +0 -216
- qnty/expression.py +0 -492
- qnty/unit_types/base.py +0 -47
- qnty/units.py +0 -8113
- qnty/variable.py +0 -263
- qnty/variable_types/base.py +0 -58
- qnty/variable_types/expression_variable.py +0 -68
- qnty/variable_types/typed_variable.py +0 -87
- qnty/variables.py +0 -2298
- qnty/variables.pyi +0 -6148
- qnty-0.0.7.dist-info/METADATA +0 -355
- qnty-0.0.7.dist-info/RECORD +0 -19
- /qnty/{unit_types → codegen}/__init__.py +0 -0
- /qnty/{variable_types → codegen/generators}/__init__.py +0 -0
- {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
|
+
)
|