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.
- 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 +540 -384
- qnty/expressions/types.py +70 -0
- qnty/problems/__init__.py +145 -0
- qnty/problems/composition.py +1101 -0
- qnty/problems/problem.py +737 -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} +829 -444
- 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.1.dist-info}/METADATA +1 -1
- qnty-0.1.1.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.1.dist-info}/WHEEL +0 -0
@@ -2,11 +2,19 @@ from typing import Any
|
|
2
2
|
|
3
3
|
import numpy as np
|
4
4
|
|
5
|
-
|
6
|
-
from
|
7
|
-
|
5
|
+
try:
|
6
|
+
from scipy.linalg import solve as scipy_solve # type: ignore[import-untyped]
|
7
|
+
|
8
|
+
HAS_SCIPY = True
|
9
|
+
except ImportError:
|
10
|
+
HAS_SCIPY = False
|
11
|
+
scipy_solve = None
|
12
|
+
|
8
13
|
from qnty.solving.order import Order
|
9
14
|
|
15
|
+
from ...equations import Equation
|
16
|
+
from ...quantities import Quantity
|
17
|
+
from ...quantities.field_qnty import FieldQnty
|
10
18
|
from .base import BaseSolver, SolveResult
|
11
19
|
|
12
20
|
|
@@ -44,9 +52,7 @@ class SimultaneousEquationSolver(BaseSolver):
|
|
44
52
|
LARGE_SYSTEM_THRESHOLD = 100 # Switch to optimized algorithms for n > 100
|
45
53
|
SPARSE_THRESHOLD = 0.1 # Use sparse matrices if density < 10%
|
46
54
|
|
47
|
-
def can_handle(self, equations: list[Equation], unknowns: set[str],
|
48
|
-
dependency_graph: Order | None = None,
|
49
|
-
analysis: dict[str, Any] | None = None) -> bool:
|
55
|
+
def can_handle(self, equations: list[Equation], unknowns: set[str], dependency_graph: Order | None = None, analysis: dict[str, Any] | None = None) -> bool:
|
50
56
|
"""
|
51
57
|
Determine if this solver can handle the given system.
|
52
58
|
|
@@ -64,7 +70,7 @@ class SimultaneousEquationSolver(BaseSolver):
|
|
64
70
|
"""
|
65
71
|
# dependency_graph parameter unused but required for interface compatibility
|
66
72
|
_ = dependency_graph
|
67
|
-
|
73
|
+
|
68
74
|
system_size = len(equations)
|
69
75
|
num_unknowns = len(unknowns)
|
70
76
|
|
@@ -75,17 +81,10 @@ class SimultaneousEquationSolver(BaseSolver):
|
|
75
81
|
# Only handle systems with cycles (mutual dependencies)
|
76
82
|
if analysis is None:
|
77
83
|
return False
|
78
|
-
has_cycles = analysis.get(
|
84
|
+
has_cycles = analysis.get("has_cycles", False)
|
79
85
|
return bool(has_cycles)
|
80
86
|
|
81
|
-
def solve(
|
82
|
-
self,
|
83
|
-
equations: list[Equation],
|
84
|
-
variables: dict[str, Variable],
|
85
|
-
dependency_graph: Order | None = None,
|
86
|
-
max_iterations: int = 100,
|
87
|
-
tolerance: float = DEFAULT_TOLERANCE
|
88
|
-
) -> SolveResult:
|
87
|
+
def solve(self, equations: list[Equation], variables: dict[str, FieldQnty], dependency_graph: Order | None = None, max_iterations: int = 100, tolerance: float = DEFAULT_TOLERANCE) -> SolveResult:
|
89
88
|
"""
|
90
89
|
Solve the n×n simultaneous system using matrix operations.
|
91
90
|
|
@@ -116,49 +115,24 @@ class SimultaneousEquationSolver(BaseSolver):
|
|
116
115
|
|
117
116
|
# Step 2: Extract and solve matrix system
|
118
117
|
try:
|
119
|
-
solution_vector = self._solve_matrix_system(
|
120
|
-
equations, unknown_variable_names, working_variables
|
121
|
-
)
|
118
|
+
solution_vector = self._solve_matrix_system(equations, unknown_variable_names, working_variables)
|
122
119
|
if solution_vector is None:
|
123
|
-
return SolveResult(
|
124
|
-
variables=working_variables,
|
125
|
-
steps=self.steps,
|
126
|
-
success=False,
|
127
|
-
message="Failed to solve matrix system",
|
128
|
-
method="SimultaneousEquationSolver"
|
129
|
-
)
|
120
|
+
return SolveResult(variables=working_variables, steps=self.steps, success=False, message="Failed to solve matrix system", method="SimultaneousEquationSolver")
|
130
121
|
|
131
122
|
# Step 3: Update variables with solutions
|
132
|
-
self._apply_solution_to_variables(
|
133
|
-
unknown_variable_names, solution_vector, working_variables
|
134
|
-
)
|
123
|
+
self._apply_solution_to_variables(unknown_variable_names, solution_vector, working_variables)
|
135
124
|
|
136
125
|
# Step 4: Verify solution quality
|
137
|
-
verification_result = self._verify_solution_quality(
|
138
|
-
equations, working_variables, tolerance
|
139
|
-
)
|
126
|
+
verification_result = self._verify_solution_quality(equations, working_variables, tolerance)
|
140
127
|
|
141
128
|
return SolveResult(
|
142
|
-
variables=working_variables,
|
143
|
-
steps=self.steps,
|
144
|
-
success=verification_result.success,
|
145
|
-
message=verification_result.message,
|
146
|
-
method="SimultaneousEquationSolver",
|
147
|
-
iterations=1
|
129
|
+
variables=working_variables, steps=self.steps, success=verification_result.success, message=verification_result.message, method="SimultaneousEquationSolver", iterations=1
|
148
130
|
)
|
149
131
|
|
150
132
|
except Exception as general_error:
|
151
|
-
return SolveResult(
|
152
|
-
variables=working_variables,
|
153
|
-
steps=self.steps,
|
154
|
-
success=False,
|
155
|
-
message=f"Simultaneous solving failed: {general_error}",
|
156
|
-
method="SimultaneousEquationSolver"
|
157
|
-
)
|
133
|
+
return SolveResult(variables=working_variables, steps=self.steps, success=False, message=f"Simultaneous solving failed: {general_error}", method="SimultaneousEquationSolver")
|
158
134
|
|
159
|
-
def _validate_system(
|
160
|
-
self, equations: list[Equation], variables: dict[str, Variable]
|
161
|
-
) -> SolveResult:
|
135
|
+
def _validate_system(self, equations: list[Equation], variables: dict[str, FieldQnty]) -> SolveResult:
|
162
136
|
"""
|
163
137
|
Validate that the system meets requirements for simultaneous solving.
|
164
138
|
|
@@ -176,24 +150,15 @@ class SimultaneousEquationSolver(BaseSolver):
|
|
176
150
|
steps=self.steps,
|
177
151
|
success=False,
|
178
152
|
message=f"Simultaneous solver requires n×n system (got {num_equations} equations, {num_unknowns} unknowns)",
|
179
|
-
method="SimultaneousEquationSolver"
|
153
|
+
method="SimultaneousEquationSolver",
|
180
154
|
)
|
181
155
|
|
182
156
|
if self.logger:
|
183
|
-
self.logger.debug(
|
184
|
-
f"Attempting {num_unknowns}×{num_unknowns} simultaneous solution for {unknown_variable_names}"
|
185
|
-
)
|
157
|
+
self.logger.debug(f"Attempting {num_unknowns}×{num_unknowns} simultaneous solution for {unknown_variable_names}")
|
186
158
|
|
187
|
-
return SolveResult(
|
188
|
-
variables=variables,
|
189
|
-
steps=self.steps,
|
190
|
-
success=True,
|
191
|
-
message="System validation passed",
|
192
|
-
method="SimultaneousEquationSolver"
|
193
|
-
)
|
159
|
+
return SolveResult(variables=variables, steps=self.steps, success=True, message="System validation passed", method="SimultaneousEquationSolver")
|
194
160
|
|
195
|
-
def _solve_matrix_system(self, equations: list[Equation], unknown_variables: list[str],
|
196
|
-
working_variables: dict[str, Variable]) -> np.ndarray | None:
|
161
|
+
def _solve_matrix_system(self, equations: list[Equation], unknown_variables: list[str], working_variables: dict[str, FieldQnty]) -> np.ndarray | None:
|
197
162
|
"""
|
198
163
|
Extract coefficient matrix and solve the linear system.
|
199
164
|
|
@@ -230,8 +195,7 @@ class SimultaneousEquationSolver(BaseSolver):
|
|
230
195
|
self.logger.error(f"Linear algebra error: {linear_algebra_error}")
|
231
196
|
return None
|
232
197
|
|
233
|
-
def _solve_optimized_system(self, coefficient_matrix: np.ndarray, constant_vector: np.ndarray,
|
234
|
-
system_size: int) -> np.ndarray:
|
198
|
+
def _solve_optimized_system(self, coefficient_matrix: np.ndarray, constant_vector: np.ndarray, system_size: int) -> np.ndarray:
|
235
199
|
"""
|
236
200
|
Solve the matrix system using optimized algorithms based on system characteristics.
|
237
201
|
|
@@ -267,41 +231,19 @@ class SimultaneousEquationSolver(BaseSolver):
|
|
267
231
|
|
268
232
|
def _solve_sparse_system(self, coefficient_matrix: np.ndarray, constant_vector: np.ndarray) -> np.ndarray:
|
269
233
|
"""
|
270
|
-
Solve sparse matrix system
|
271
|
-
|
272
|
-
Note: This is a placeholder for potential scipy.sparse integration.
|
273
|
-
For now, falls back to standard NumPy solver.
|
234
|
+
Solve sparse matrix system. Currently falls back to dense solver.
|
274
235
|
"""
|
275
|
-
# Future optimization: Convert to scipy.sparse.csc_matrix and use sparse solvers
|
276
|
-
# from scipy.sparse.linalg import spsolve
|
277
|
-
# sparse_matrix = scipy.sparse.csc_matrix(coefficient_matrix)
|
278
|
-
# return spsolve(sparse_matrix, constant_vector)
|
279
|
-
|
280
|
-
# Fallback to standard solver for now
|
281
236
|
return np.linalg.solve(coefficient_matrix, constant_vector)
|
282
237
|
|
283
238
|
def _solve_large_dense_system(self, coefficient_matrix: np.ndarray, constant_vector: np.ndarray) -> np.ndarray:
|
284
239
|
"""
|
285
240
|
Solve large dense matrix system using optimized algorithms.
|
286
|
-
|
287
|
-
Uses LU decomposition with partial pivoting for better numerical stability
|
288
|
-
and potential reuse of factorization.
|
289
241
|
"""
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
from scipy.linalg import solve # type: ignore[import-untyped]
|
294
|
-
return solve(coefficient_matrix, constant_vector, assume_a='gen')
|
295
|
-
except ImportError:
|
296
|
-
# Fallback to NumPy if scipy is not available
|
297
|
-
return np.linalg.solve(coefficient_matrix, constant_vector)
|
242
|
+
if HAS_SCIPY and scipy_solve is not None:
|
243
|
+
return scipy_solve(coefficient_matrix, constant_vector, assume_a="gen")
|
244
|
+
return np.linalg.solve(coefficient_matrix, constant_vector)
|
298
245
|
|
299
|
-
def _apply_solution_to_variables(
|
300
|
-
self,
|
301
|
-
unknown_variables: list[str],
|
302
|
-
solution_vector: np.ndarray,
|
303
|
-
working_variables: dict[str, Variable]
|
304
|
-
):
|
246
|
+
def _apply_solution_to_variables(self, unknown_variables: list[str], solution_vector: np.ndarray, working_variables: dict[str, FieldQnty]):
|
305
247
|
"""
|
306
248
|
Apply solution values to variables and record solving steps.
|
307
249
|
"""
|
@@ -309,12 +251,7 @@ class SimultaneousEquationSolver(BaseSolver):
|
|
309
251
|
solution_value = float(solution_vector[i])
|
310
252
|
self._update_variable_with_solution(variable_name, solution_value, working_variables)
|
311
253
|
|
312
|
-
def _verify_solution_quality(
|
313
|
-
self,
|
314
|
-
equations: list[Equation],
|
315
|
-
working_variables: dict[str, Variable],
|
316
|
-
tolerance: float
|
317
|
-
) -> SolveResult:
|
254
|
+
def _verify_solution_quality(self, equations: list[Equation], working_variables: dict[str, FieldQnty], tolerance: float) -> SolveResult:
|
318
255
|
"""
|
319
256
|
Verify solution quality by checking equation residuals.
|
320
257
|
|
@@ -340,16 +277,9 @@ class SimultaneousEquationSolver(BaseSolver):
|
|
340
277
|
variable_solutions = {var: f"{working_variables[var].quantity}" for var in working_variables if not working_variables[var].is_known}
|
341
278
|
self.logger.debug(f"Solved {num_unknowns}×{num_unknowns} system: {variable_solutions}")
|
342
279
|
|
343
|
-
return SolveResult(
|
344
|
-
variables=working_variables,
|
345
|
-
steps=[],
|
346
|
-
success=is_successful,
|
347
|
-
message=success_message,
|
348
|
-
method="SimultaneousEquationSolver"
|
349
|
-
)
|
280
|
+
return SolveResult(variables=working_variables, steps=[], success=is_successful, message=success_message, method="SimultaneousEquationSolver")
|
350
281
|
|
351
|
-
def _extract_matrix_system(self, equations: list[Equation], unknown_variables: list[str],
|
352
|
-
variables: dict[str, Variable]) -> tuple[np.ndarray | None, np.ndarray | None]:
|
282
|
+
def _extract_matrix_system(self, equations: list[Equation], unknown_variables: list[str], variables: dict[str, FieldQnty]) -> tuple[np.ndarray | None, np.ndarray | None]:
|
353
283
|
"""
|
354
284
|
Extract coefficient matrix A and constant vector b from the system of equations.
|
355
285
|
|
@@ -379,24 +309,21 @@ class SimultaneousEquationSolver(BaseSolver):
|
|
379
309
|
|
380
310
|
# Process equations in batches for large systems to reduce memory pressure
|
381
311
|
for equation_index, equation in enumerate(equations):
|
382
|
-
coefficient_list = self._extract_linear_coefficients_vector(
|
383
|
-
equation, unknown_variables, variables
|
384
|
-
)
|
312
|
+
coefficient_list = self._extract_linear_coefficients_vector(equation, unknown_variables, variables)
|
385
313
|
if coefficient_list is None:
|
386
314
|
return None, None
|
387
315
|
|
388
316
|
# coefficient_list contains [a1, a2, ..., an, constant]
|
389
317
|
# where equation is a1*x1 + a2*x2 + ... + an*xn = constant
|
390
318
|
coefficient_matrix[equation_index, :] = coefficient_list[:-1] # Coefficients
|
391
|
-
constant_vector[equation_index] = coefficient_list[-1]
|
319
|
+
constant_vector[equation_index] = coefficient_list[-1] # Constant term
|
392
320
|
|
393
321
|
return coefficient_matrix, constant_vector
|
394
322
|
|
395
323
|
except Exception:
|
396
324
|
return None, None
|
397
325
|
|
398
|
-
def _extract_linear_coefficients_vector(self, equation: Equation, unknown_variables: list[str],
|
399
|
-
variables: dict[str, Variable]) -> list[float] | None:
|
326
|
+
def _extract_linear_coefficients_vector(self, equation: Equation, unknown_variables: list[str], variables: dict[str, FieldQnty]) -> list[float] | None:
|
400
327
|
"""
|
401
328
|
Extract linear coefficients from equation using numerical differentiation.
|
402
329
|
|
@@ -420,47 +347,19 @@ class SimultaneousEquationSolver(BaseSolver):
|
|
420
347
|
coefficients = []
|
421
348
|
|
422
349
|
# Memory optimization: Pre-allocate arrays for large systems
|
423
|
-
residual_test_cases:
|
350
|
+
residual_test_cases: list[float] = []
|
424
351
|
|
425
352
|
# Reuse test variables dictionary to reduce object creation overhead
|
426
353
|
test_vars = variables.copy()
|
427
354
|
|
428
355
|
# Test case for each unknown variable (finite difference)
|
429
356
|
for variable_index in range(num_unknowns):
|
430
|
-
|
431
|
-
for unknown_index, var_name in enumerate(unknown_variables):
|
432
|
-
test_value = 1.0 if unknown_index == variable_index else 0.0
|
433
|
-
original_var = test_vars[var_name]
|
434
|
-
if original_var.quantity is None:
|
435
|
-
raise ValueError(f"Variable {var_name} has no quantity")
|
436
|
-
test_var = Variable(
|
437
|
-
name=f"test_{var_name}",
|
438
|
-
expected_dimension=original_var.quantity.dimension,
|
439
|
-
is_known=True
|
440
|
-
)
|
441
|
-
test_var.quantity = Qty(test_value, original_var.quantity.unit)
|
442
|
-
test_var.symbol = var_name
|
443
|
-
test_vars[var_name] = test_var
|
444
|
-
|
357
|
+
self._set_test_variables(test_vars, unknown_variables, variable_index)
|
445
358
|
residual = self._calculate_equation_residual(equation, test_vars)
|
446
|
-
|
447
|
-
residual_test_cases.append(residual)
|
448
|
-
else:
|
449
|
-
residual_test_cases[variable_index] = residual
|
359
|
+
residual_test_cases.append(residual)
|
450
360
|
|
451
361
|
# Test case with all unknowns = 0 (baseline)
|
452
|
-
|
453
|
-
original_var = test_vars[var_name]
|
454
|
-
if original_var.quantity is None:
|
455
|
-
raise ValueError(f"Variable {var_name} has no quantity")
|
456
|
-
test_var = Variable(
|
457
|
-
name=f"test_{var_name}",
|
458
|
-
expected_dimension=original_var.quantity.dimension,
|
459
|
-
is_known=True
|
460
|
-
)
|
461
|
-
test_var.quantity = Qty(0.0, original_var.quantity.unit)
|
462
|
-
test_var.symbol = var_name
|
463
|
-
test_vars[var_name] = test_var
|
362
|
+
self._set_test_variables(test_vars, unknown_variables, -1) # -1 means set all to 0
|
464
363
|
baseline_residual = self._calculate_equation_residual(equation, test_vars)
|
465
364
|
|
466
365
|
# Extract coefficients: for equation sum(ai*xi) - c = 0
|
@@ -478,7 +377,7 @@ class SimultaneousEquationSolver(BaseSolver):
|
|
478
377
|
except Exception:
|
479
378
|
return None
|
480
379
|
|
481
|
-
def _calculate_equation_residual(self, equation: Equation, test_variables: dict[str,
|
380
|
+
def _calculate_equation_residual(self, equation: Equation, test_variables: dict[str, FieldQnty]) -> float:
|
482
381
|
"""
|
483
382
|
Calculate equation residual (LHS - RHS) with proper unit handling.
|
484
383
|
|
@@ -494,24 +393,15 @@ class SimultaneousEquationSolver(BaseSolver):
|
|
494
393
|
left_hand_side = equation.lhs.evaluate(test_variables)
|
495
394
|
right_hand_side = equation.rhs.evaluate(test_variables)
|
496
395
|
|
497
|
-
# Calculate residual
|
396
|
+
# Calculate residual and extract numerical value
|
498
397
|
residual = left_hand_side - right_hand_side
|
499
|
-
|
500
|
-
# Return the magnitude of the residual
|
501
|
-
if hasattr(residual, 'value'):
|
502
|
-
return residual.value
|
503
|
-
elif isinstance(residual, int | float):
|
504
|
-
return float(residual)
|
505
|
-
else:
|
506
|
-
# If it's a Qty object without .value, try to convert
|
507
|
-
return float(residual.value) if hasattr(residual, 'value') else 0.0
|
398
|
+
return self._extract_numerical_value(residual)
|
508
399
|
|
509
400
|
except Exception:
|
510
401
|
# Fallback for cases where evaluation fails
|
511
|
-
return float(
|
402
|
+
return float("inf")
|
512
403
|
|
513
|
-
def _update_variable_with_solution(self, variable_symbol: str, solution_value: float,
|
514
|
-
variables: dict[str, Variable]):
|
404
|
+
def _update_variable_with_solution(self, variable_symbol: str, solution_value: float, variables: dict[str, FieldQnty]):
|
515
405
|
"""
|
516
406
|
Update a variable with its solved value and record the solving step.
|
517
407
|
|
@@ -524,15 +414,11 @@ class SimultaneousEquationSolver(BaseSolver):
|
|
524
414
|
if original_variable.quantity is None:
|
525
415
|
raise ValueError(f"Variable {variable_symbol} has no quantity")
|
526
416
|
result_unit = original_variable.quantity.unit
|
527
|
-
solution_quantity =
|
417
|
+
solution_quantity = Quantity(solution_value, result_unit)
|
528
418
|
|
529
419
|
# Preserve the original variable name and create solved variable
|
530
420
|
original_name = original_variable.name
|
531
|
-
solved_variable =
|
532
|
-
name=original_name,
|
533
|
-
expected_dimension=solution_quantity.dimension,
|
534
|
-
is_known=True
|
535
|
-
)
|
421
|
+
solved_variable = FieldQnty(name=original_name, expected_dimension=solution_quantity.dimension, is_known=True)
|
536
422
|
solved_variable.quantity = solution_quantity
|
537
423
|
solved_variable.symbol = variable_symbol
|
538
424
|
variables[variable_symbol] = solved_variable
|
@@ -541,7 +427,49 @@ class SimultaneousEquationSolver(BaseSolver):
|
|
541
427
|
self._log_step(
|
542
428
|
1, # iteration number
|
543
429
|
variable_symbol,
|
544
|
-
|
430
|
+
"simultaneous_system",
|
545
431
|
str(solution_quantity),
|
546
|
-
|
432
|
+
"simultaneous",
|
547
433
|
)
|
434
|
+
|
435
|
+
def _set_test_variables(self, test_vars: dict[str, FieldQnty], unknown_variables: list[str], active_index: int):
|
436
|
+
"""
|
437
|
+
Set test variables for coefficient extraction.
|
438
|
+
|
439
|
+
Args:
|
440
|
+
test_vars: Dictionary of test variables to modify
|
441
|
+
unknown_variables: List of unknown variable names
|
442
|
+
active_index: Index of variable to set to 1.0, others set to 0.0. If -1, all set to 0.0
|
443
|
+
"""
|
444
|
+
for unknown_index, var_name in enumerate(unknown_variables):
|
445
|
+
test_value = 1.0 if unknown_index == active_index else 0.0
|
446
|
+
original_var = test_vars[var_name]
|
447
|
+
if original_var.quantity is None:
|
448
|
+
raise ValueError(f"Variable {var_name} has no quantity")
|
449
|
+
test_var = FieldQnty(name=f"test_{var_name}", expected_dimension=original_var.quantity.dimension, is_known=True)
|
450
|
+
test_var.quantity = Quantity(test_value, original_var.quantity.unit)
|
451
|
+
test_var.symbol = var_name
|
452
|
+
test_vars[var_name] = test_var
|
453
|
+
|
454
|
+
def _extract_numerical_value(self, value: Any) -> float:
|
455
|
+
"""
|
456
|
+
Extract numerical value from various quantity types.
|
457
|
+
|
458
|
+
Args:
|
459
|
+
value: Value that may be a Quantity, float, int, or other numeric type
|
460
|
+
|
461
|
+
Returns:
|
462
|
+
Float representation of the value
|
463
|
+
"""
|
464
|
+
# Check for Quantity type first (most common case)
|
465
|
+
if isinstance(value, Quantity):
|
466
|
+
return float(value.value)
|
467
|
+
# Handle primitive numeric types
|
468
|
+
elif isinstance(value, int | float):
|
469
|
+
return float(value)
|
470
|
+
# Handle objects with .value attribute as last resort
|
471
|
+
elif hasattr(value, "value"):
|
472
|
+
return float(value.value)
|
473
|
+
else:
|
474
|
+
# Last resort: try direct conversion
|
475
|
+
return float(value)
|
qnty/units/__init__.py
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
from .field_units import *
|