qnty 0.0.9__py3-none-any.whl → 0.1.0__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 +539 -384
  18. qnty/expressions/types.py +70 -0
  19. qnty/problems/__init__.py +145 -0
  20. qnty/problems/composition.py +1031 -0
  21. qnty/problems/problem.py +695 -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} +754 -432
  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.0.dist-info}/METADATA +1 -1
  54. qnty-0.1.0.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.0.dist-info}/WHEEL +0 -0
qnty/problem/solving.py DELETED
@@ -1,180 +0,0 @@
1
- """
2
- High-level solve orchestration for Problem class.
3
-
4
- This module contains the main solving logic, dependency graph building,
5
- solution verification, and system analysis methods.
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
- # Constants
17
- MAX_ITERATIONS_DEFAULT = 100
18
- TOLERANCE_DEFAULT = 1e-10
19
-
20
-
21
- # Custom Exceptions
22
- class SolverError(RuntimeError):
23
- """Raised when the solving process fails."""
24
- pass
25
-
26
-
27
- class SolvingMixin:
28
- """Mixin class providing solving orchestration functionality."""
29
-
30
- # These attributes/methods will be provided by other mixins in the final Problem class
31
- name: str
32
- logger: Any
33
- equations: list[Equation]
34
- solver_manager: Any
35
-
36
- def get_known_symbols(self) -> set[str]:
37
- """Will be provided by VariablesMixin."""
38
- ...
39
-
40
- def get_known_variables(self) -> dict[str, Variable]:
41
- """Will be provided by VariablesMixin."""
42
- ...
43
-
44
- def get_unknown_variables(self) -> dict[str, Variable]:
45
- """Will be provided by VariablesMixin."""
46
- ...
47
-
48
- def _sync_variables_to_instance_attributes(self) -> None:
49
- """Will be provided by VariablesMixin."""
50
- ...
51
-
52
- def solve(self, max_iterations: int = MAX_ITERATIONS_DEFAULT, tolerance: float = TOLERANCE_DEFAULT) -> dict[str, Any]:
53
- """
54
- Solve the engineering problem by finding values for all unknown variables.
55
-
56
- This method orchestrates the complete solving process:
57
- 1. Builds dependency graph from equations
58
- 2. Determines optimal solving order using topological sorting
59
- 3. Solves equations iteratively using symbolic/numerical methods
60
- 4. Verifies solution against all equations
61
- 5. Updates variable states and synchronizes instance attributes
62
-
63
- Args:
64
- max_iterations: Maximum number of solving iterations (default: 100)
65
- tolerance: Numerical tolerance for convergence (default: 1e-10)
66
-
67
- Returns:
68
- dict mapping variable symbols to solved Variable objects
69
-
70
- Raises:
71
- SolverError: If solving fails or times out
72
-
73
- Example:
74
- >>> problem = MyEngineeringProblem()
75
- >>> solution = problem.solve()
76
- >>> print(f"Force = {solution['F'].quantity}")
77
- """
78
- self.logger.info(f"Solving problem: {self.name}")
79
-
80
- try:
81
- # Clear previous solution
82
- self.solution = {}
83
- self.is_solved = False
84
- self.solving_history = []
85
-
86
- # Build dependency graph
87
- self._build_dependency_graph()
88
-
89
- # Use solver manager to solve the system
90
- solve_result = self.solver_manager.solve(
91
- self.equations,
92
- self.variables,
93
- self.dependency_graph,
94
- max_iterations,
95
- tolerance
96
- )
97
-
98
- if solve_result.success:
99
- # Update variables with the result
100
- self.variables = solve_result.variables
101
- self.solving_history.extend(solve_result.steps)
102
-
103
- # Sync solved values back to instance attributes
104
- self._sync_variables_to_instance_attributes()
105
-
106
- # Verify solution
107
- self.solution = self.variables
108
- verification_passed = self.verify_solution()
109
-
110
- # Mark as solved based on solver result and verification
111
- if verification_passed:
112
- self.is_solved = True
113
- self.logger.info("Solution verified successfully")
114
- return self.solution
115
- else:
116
- self.logger.warning("Solution verification failed")
117
- return self.solution
118
- else:
119
- raise SolverError(f"Solving failed: {solve_result.message}")
120
-
121
- except SolverError:
122
- raise
123
- except Exception as e:
124
- self.logger.error(f"Solving failed: {e}")
125
- raise SolverError(f"Unexpected error during solving: {e}") from e
126
-
127
- def _build_dependency_graph(self):
128
- """Build the dependency graph for solving order determination."""
129
- # Reset the dependency graph
130
- from qnty.solving.order import Order
131
- self.dependency_graph = Order()
132
-
133
- # Get known variables
134
- known_vars = self.get_known_symbols()
135
-
136
- # Add dependencies from equations
137
- for equation in self.equations:
138
- self.dependency_graph.add_equation(equation, known_vars)
139
-
140
- def verify_solution(self, tolerance: float = 1e-10) -> bool:
141
- """Verify that all equations are satisfied."""
142
- if not self.equations:
143
- return True
144
-
145
- try:
146
- for equation in self.equations:
147
- if not equation.check_residual(self.variables, tolerance):
148
- self.logger.debug(f"Equation verification failed: {equation}")
149
- return False
150
- return True
151
- except Exception as e:
152
- self.logger.debug(f"Solution verification error: {e}")
153
- return False
154
-
155
- def analyze_system(self) -> dict[str, Any]:
156
- """Analyze the equation system for solvability, cycles, etc."""
157
- try:
158
- self._build_dependency_graph()
159
- known_vars = self.get_known_symbols()
160
- analysis = self.dependency_graph.analyze_system(known_vars)
161
-
162
- # Add some additional info
163
- analysis['total_equations'] = len(self.equations)
164
- analysis['is_determined'] = len(self.get_unknown_variables()) <= len(self.equations)
165
-
166
- return analysis
167
- except Exception as e:
168
- self.logger.debug(f"Dependency analysis failed: {e}")
169
- # Return basic analysis on failure
170
- return {
171
- 'total_variables': len(self.variables),
172
- 'known_variables': len(self.get_known_variables()),
173
- 'unknown_variables': len(self.get_unknown_variables()),
174
- 'total_equations': len(self.equations),
175
- 'is_determined': len(self.get_unknown_variables()) <= len(self.equations),
176
- 'has_cycles': False,
177
- 'solving_order': [],
178
- 'can_solve_completely': False,
179
- 'unsolvable_variables': []
180
- }
@@ -1,64 +0,0 @@
1
- """
2
- Problem-validation integration for Problem class.
3
-
4
- This module contains validation check management and execution
5
- integrated with the Problem system.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- from collections.abc import Callable
11
- from typing import TYPE_CHECKING, Any
12
-
13
- if TYPE_CHECKING:
14
- pass
15
-
16
-
17
- class ValidationMixin:
18
- """Mixin class providing validation functionality."""
19
-
20
- # These attributes will be provided by other mixins in the final Problem class
21
- logger: Any
22
- warnings: list[dict[str, Any]]
23
- validation_checks: list[Callable]
24
-
25
- def add_validation_check(self, check_function: Callable) -> None:
26
- """Add a validation check function."""
27
- self.validation_checks.append(check_function)
28
-
29
- def validate(self) -> list[dict[str, Any]]:
30
- """Run all validation checks and return any warnings."""
31
- validation_warnings = []
32
-
33
- for check in self.validation_checks:
34
- try:
35
- result = check(self)
36
- if result:
37
- validation_warnings.append(result)
38
- except Exception as e:
39
- self.logger.debug(f"Validation check failed: {e}")
40
-
41
- return validation_warnings
42
-
43
- def get_warnings(self) -> list[dict[str, Any]]:
44
- """Get all warnings from the problem."""
45
- warnings = self.warnings.copy()
46
- warnings.extend(self.validate())
47
- return warnings
48
-
49
- def _recreate_validation_checks(self):
50
- """Collect and integrate validation checks from class-level Check objects."""
51
- # Clear existing checks
52
- self.validation_checks = []
53
-
54
- # Collect Check objects from metaclass
55
- class_checks = getattr(self.__class__, '_class_checks', {})
56
-
57
- for check in class_checks.values():
58
- # Create a validation function from the Check object
59
- def make_check_function(check_obj):
60
- def check_function(problem_instance):
61
- return check_obj.evaluate(problem_instance.variables)
62
- return check_function
63
-
64
- self.validation_checks.append(make_check_function(check))
qnty/problem/variables.py DELETED
@@ -1,239 +0,0 @@
1
- """
2
- Variable lifecycle management for Problem class.
3
-
4
- This module contains all variable-related operations including adding,
5
- getting, managing known/unknown state, and variable caching.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- from typing import TYPE_CHECKING, Any
11
-
12
- if TYPE_CHECKING:
13
- from qnty.quantities import Quantity as Qty
14
- from qnty.quantities import TypeSafeVariable as Variable
15
-
16
- from qnty.generated.units import DimensionlessUnits
17
- from qnty.quantities import Quantity as Qty
18
- from qnty.quantities import TypeSafeVariable as Variable
19
-
20
-
21
- # Custom Exceptions
22
- class VariableNotFoundError(KeyError):
23
- """Raised when trying to access a variable that doesn't exist."""
24
- pass
25
-
26
-
27
- class VariablesMixin:
28
- """Mixin class providing variable management functionality."""
29
-
30
- # These attributes/methods will be provided by other mixins in the final Problem class
31
- variables: dict[str, Variable]
32
- name: str
33
- logger: Any
34
- is_solved: bool
35
- sub_problems: dict[str, Any]
36
- dependency_graph: Any
37
- _known_variables_cache: dict[str, Variable] | None
38
- _unknown_variables_cache: dict[str, Variable] | None
39
- _cache_dirty: bool
40
-
41
- def _invalidate_caches(self) -> None:
42
- """Will be provided by ProblemBase."""
43
- ...
44
-
45
- def _update_variable_caches(self) -> None:
46
- """Will be provided by ProblemBase."""
47
- ...
48
-
49
- def add_variable(self, variable: Variable) -> None:
50
- """
51
- Add a variable to the problem.
52
-
53
- The variable will be available for use in equations and can be accessed
54
- via both dictionary notation (problem['symbol']) and attribute notation
55
- (problem.symbol).
56
-
57
- Args:
58
- variable: Variable object to add to the problem
59
-
60
- Note:
61
- If a variable with the same symbol already exists, it will be replaced
62
- and a warning will be logged.
63
- """
64
- if variable.symbol in self.variables:
65
- self.logger.warning(f"Variable {variable.symbol} already exists. Replacing.")
66
-
67
- if variable.symbol is not None:
68
- self.variables[variable.symbol] = variable
69
- # Set parent problem reference for dependency invalidation
70
- try:
71
- variable._parent_problem = self
72
- except (AttributeError, TypeError):
73
- # _parent_problem might not be settable
74
- pass
75
- # Also set as instance attribute for dot notation access
76
- if variable.symbol is not None:
77
- setattr(self, variable.symbol, variable)
78
- self.is_solved = False
79
- self._invalidate_caches()
80
-
81
- def add_variables(self, *variables: Variable) -> None:
82
- """Add multiple variables to the problem."""
83
- for var in variables:
84
- self.add_variable(var)
85
-
86
- def get_variable(self, symbol: str) -> Variable:
87
- """Get a variable by its symbol."""
88
- if symbol not in self.variables:
89
- raise VariableNotFoundError(f"Variable '{symbol}' not found in problem '{self.name}'.")
90
- return self.variables[symbol]
91
-
92
- def get_known_variables(self) -> dict[str, Variable]:
93
- """Get all known variables."""
94
- if self._cache_dirty or self._known_variables_cache is None:
95
- self._update_variable_caches()
96
- return self._known_variables_cache.copy() if self._known_variables_cache else {}
97
-
98
- def get_unknown_variables(self) -> dict[str, Variable]:
99
- """Get all unknown variables."""
100
- if self._cache_dirty or self._unknown_variables_cache is None:
101
- self._update_variable_caches()
102
- return self._unknown_variables_cache.copy() if self._unknown_variables_cache else {}
103
-
104
- def get_known_symbols(self) -> set[str]:
105
- """Get symbols of all known variables."""
106
- return {symbol for symbol, var in self.variables.items() if var.is_known}
107
-
108
- def get_unknown_symbols(self) -> set[str]:
109
- """Get symbols of all unknown variables."""
110
- return {symbol for symbol, var in self.variables.items() if not var.is_known}
111
-
112
- def get_known_variable_symbols(self) -> set[str]:
113
- """Alias for get_known_symbols for compatibility."""
114
- return self.get_known_symbols()
115
-
116
- def get_unknown_variable_symbols(self) -> set[str]:
117
- """Alias for get_unknown_symbols for compatibility."""
118
- return self.get_unknown_symbols()
119
-
120
- # Properties for compatibility
121
- @property
122
- def known_variables(self) -> dict[str, Variable]:
123
- """Get all variables marked as known."""
124
- return self.get_known_variables()
125
-
126
- @property
127
- def unknown_variables(self) -> dict[str, Variable]:
128
- """Get all variables marked as unknown."""
129
- return self.get_unknown_variables()
130
-
131
- def mark_unknown(self, *symbols: str):
132
- """Mark variables as unknown (to be solved for)."""
133
- for symbol in symbols:
134
- if symbol in self.variables:
135
- self.variables[symbol].mark_unknown()
136
- else:
137
- raise VariableNotFoundError(f"Variable '{symbol}' not found in problem '{self.name}'")
138
- self.is_solved = False
139
- self._invalidate_caches()
140
- return self
141
-
142
- def mark_known(self, **symbol_values: Qty):
143
- """Mark variables as known and set their values."""
144
- for symbol, quantity in symbol_values.items():
145
- if symbol in self.variables:
146
- self.variables[symbol].mark_known(quantity)
147
- else:
148
- raise VariableNotFoundError(f"Variable '{symbol}' not found in problem '{self.name}'")
149
- self.is_solved = False
150
- self._invalidate_caches()
151
- return self
152
-
153
- def invalidate_dependents(self, changed_variable_symbol: str) -> None:
154
- """
155
- Mark all variables that depend on the changed variable as unknown.
156
- This ensures they get recalculated when the problem is re-solved.
157
-
158
- Args:
159
- changed_variable_symbol: Symbol of the variable whose value changed
160
- """
161
- if not hasattr(self, 'dependency_graph') or not self.dependency_graph:
162
- # If dependency graph hasn't been built yet, we can't invalidate
163
- return
164
-
165
- # Get all variables that depend on the changed variable
166
- dependent_vars = self.dependency_graph.graph.get(changed_variable_symbol, [])
167
-
168
- # Mark each dependent variable as unknown
169
- for dependent_symbol in dependent_vars:
170
- if dependent_symbol in self.variables:
171
- var = self.variables[dependent_symbol]
172
- # Only mark as unknown if it was previously solved (known)
173
- if var.is_known:
174
- var.mark_unknown()
175
- # Recursively invalidate variables that depend on this one
176
- self.invalidate_dependents(dependent_symbol)
177
-
178
- # Mark problem as needing re-solving
179
- self.is_solved = False
180
- self._invalidate_caches()
181
-
182
- def _create_placeholder_variable(self, symbol: str) -> None:
183
- """Create a placeholder variable for a missing symbol."""
184
-
185
- placeholder_var = Variable(
186
- name=f"Auto-created: {symbol}",
187
- expected_dimension=DimensionlessUnits.dimensionless.dimension,
188
- is_known=False
189
- )
190
- placeholder_var.symbol = symbol
191
- placeholder_var.quantity = Qty(0.0, DimensionlessUnits.dimensionless)
192
- self.add_variable(placeholder_var)
193
- self.logger.debug(f"Auto-created placeholder variable: {symbol}")
194
-
195
- def _clone_variable(self, variable: Variable) -> Variable:
196
- """Create a copy of a variable to avoid shared state without corrupting global units."""
197
- # Create a new variable of the same exact type to preserve .equals() method
198
- # This ensures domain-specific variables (Length, Pressure, etc.) keep their type
199
- variable_type = type(variable)
200
-
201
- # Use __new__ to avoid constructor parameter issues
202
- cloned = variable_type.__new__(variable_type)
203
-
204
- # Initialize manually with the same attributes as the original
205
- cloned.name = variable.name
206
- cloned.symbol = variable.symbol
207
- cloned.expected_dimension = variable.expected_dimension
208
- cloned.quantity = variable.quantity # Keep reference to same quantity - units must not be copied
209
- cloned.is_known = variable.is_known
210
-
211
- # Ensure the cloned variable has fresh validation checks
212
- if hasattr(variable, 'validation_checks'):
213
- try:
214
- cloned.validation_checks = []
215
- except (AttributeError, TypeError):
216
- # validation_checks might be read-only or not settable
217
- pass
218
- return cloned
219
-
220
- def _sync_variables_to_instance_attributes(self):
221
- """
222
- Sync variable objects to instance attributes after solving.
223
- This ensures that self.P refers to the same Variable object that's in self.variables.
224
- Variables maintain their original dimensional types (e.g., AreaVariable, PressureVariable).
225
- """
226
- for var_symbol, var in self.variables.items():
227
- # Update instance attribute if it exists
228
- if hasattr(self, var_symbol):
229
- # Variables preserve their dimensional types during solving
230
- setattr(self, var_symbol, var)
231
-
232
- # Also update sub-problem namespace objects
233
- for namespace, sub_problem in self.sub_problems.items():
234
- if hasattr(self, namespace):
235
- namespace_obj = getattr(self, namespace)
236
- for var_symbol in sub_problem.variables:
237
- namespaced_symbol = f"{namespace}_{var_symbol}"
238
- if namespaced_symbol in self.variables and hasattr(namespace_obj, var_symbol):
239
- setattr(namespace_obj, var_symbol, self.variables[namespaced_symbol])