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/base.py DELETED
@@ -1,142 +0,0 @@
1
- """
2
- Core Problem base class with state management and initialization.
3
-
4
- This module contains the foundational Problem class with core state,
5
- initialization logic, caching, and utility methods.
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 TypeSafeVariable as Variable
14
-
15
- from qnty.equations import EquationSystem
16
- from qnty.problem.reconstruction import EquationReconstructor
17
- from qnty.solving.order import Order
18
- from qnty.solving.solvers import SolverManager
19
- from qnty.utils.logging import get_logger
20
-
21
-
22
- class ProblemBase:
23
- """
24
- Base class for Problem with core state management and initialization.
25
-
26
- This class provides the foundational structure for engineering problems,
27
- including variable storage, equation management, caching, and logging.
28
- """
29
-
30
- def __init__(self, name: str | None = None, description: str = ""):
31
- # Handle subclass mode (class-level name/description) vs explicit name
32
- self.name = name or getattr(self.__class__, 'name', self.__class__.__name__)
33
- self.description = description or getattr(self.__class__, 'description', "")
34
-
35
- # Core storage
36
- self.variables: dict[str, Variable] = {}
37
- self.equations: list = [] # Will be properly typed when importing Equation
38
-
39
- # Internal systems
40
- self.equation_system = EquationSystem()
41
- self.dependency_graph = Order()
42
-
43
- # Solving state
44
- self.is_solved = False
45
- self.solution: dict[str, Variable] = {}
46
- self.solving_history: list[dict[str, Any]] = []
47
-
48
- # Performance optimization caches
49
- self._known_variables_cache: dict[str, Variable] | None = None
50
- self._unknown_variables_cache: dict[str, Variable] | None = None
51
- self._cache_dirty = True
52
-
53
- # Validation and warning system
54
- self.warnings: list[dict[str, Any]] = []
55
- self.validation_checks: list = [] # Will be properly typed when importing Callable
56
-
57
- self.logger = get_logger()
58
- self.solver_manager = SolverManager(self.logger)
59
-
60
- # Sub-problem composition support
61
- self.sub_problems: dict[str, Any] = {} # Will be properly typed as Problem
62
- self.variable_aliases: dict[str, str] = {} # Maps alias -> original variable symbol
63
-
64
- # Initialize equation reconstructor
65
- self.equation_reconstructor = EquationReconstructor(self)
66
-
67
- def _invalidate_caches(self) -> None:
68
- """Invalidate performance caches when variables change."""
69
- self._cache_dirty = True
70
-
71
- def _update_variable_caches(self) -> None:
72
- """Update the variable caches for performance."""
73
- if not self._cache_dirty:
74
- return
75
-
76
- self._known_variables_cache = {symbol: var for symbol, var in self.variables.items() if var.is_known}
77
- self._unknown_variables_cache = {symbol: var for symbol, var in self.variables.items() if not var.is_known}
78
- self._cache_dirty = False
79
-
80
- def reset_solution(self):
81
- """Reset the problem to unsolved state."""
82
- self.is_solved = False
83
- self.solution = {}
84
- self.solving_history = []
85
-
86
- # Reset unknown variables to unknown state
87
- for var in self.variables.values():
88
- if not var.is_known:
89
- var.is_known = False
90
-
91
- def copy(self):
92
- """Create a copy of this problem."""
93
- from copy import deepcopy
94
- return deepcopy(self)
95
-
96
- def __str__(self) -> str:
97
- """String representation of the problem."""
98
- status = "SOLVED" if self.is_solved else "UNSOLVED"
99
- return f"EngineeringProblem('{self.name}', vars={len(self.variables)}, eqs={len(self.equations)}, {status})"
100
-
101
- def __repr__(self) -> str:
102
- """Detailed representation of the problem."""
103
- return self.__str__()
104
-
105
- def __setattr__(self, name: str, value: Any) -> None:
106
- """Custom attribute setting to maintain variable synchronization."""
107
- # During initialization, use normal attribute setting
108
- if not hasattr(self, 'variables') or name.startswith('_'):
109
- super().__setattr__(name, value)
110
- return
111
-
112
- # Import here to avoid circular imports
113
- try:
114
- from qnty.quantities import TypeSafeVariable as Variable
115
- # If setting a variable that exists in our variables dict, update both
116
- if isinstance(value, Variable) and name in self.variables:
117
- self.variables[name] = value
118
- except ImportError:
119
- pass
120
-
121
- super().__setattr__(name, value)
122
-
123
- def __getitem__(self, key: str):
124
- """Allow dict-like access to variables."""
125
- from .variables import VariablesMixin
126
- # Type ignore: self will have VariablesMixin via multiple inheritance
127
- return VariablesMixin.get_variable(self, key) # type: ignore[arg-type]
128
-
129
- def __setitem__(self, key: str, value) -> None:
130
- """Allow dict-like assignment of variables."""
131
- # Import here to avoid circular imports
132
- try:
133
- from qnty.quantities import TypeSafeVariable as Variable
134
- if isinstance(value, Variable):
135
- # Update the symbol to match the key if they differ
136
- if value.symbol != key:
137
- value.symbol = key
138
- from .variables import VariablesMixin
139
- # Type ignore: self will have VariablesMixin via multiple inheritance
140
- VariablesMixin.add_variable(self, value) # type: ignore[arg-type]
141
- except ImportError:
142
- pass
@@ -1,385 +0,0 @@
1
- """
2
- Composition system for EngineeringProblem sub-problems.
3
-
4
- This module provides the infrastructure for composing engineering problems
5
- from reusable sub-problems with clean syntax and automatic integration.
6
- """
7
-
8
- from qnty.expressions import BinaryOperation, max_expr, min_expr, sin
9
- from qnty.generated.quantities import Dimensionless
10
- from qnty.quantities.quantity import TypeSafeVariable as Variable
11
-
12
-
13
- class DelayedEquation:
14
- """
15
- Stores an equation definition that will be evaluated later when proper context is available.
16
- """
17
- def __init__(self, lhs_symbol, rhs_factory, name=None):
18
- self.lhs_symbol = lhs_symbol
19
- self.rhs_factory = rhs_factory # Function that creates the RHS expression
20
- self.name = name or f"{lhs_symbol}_equation"
21
-
22
- def evaluate(self, context):
23
- """Evaluate the equation with the given context (namespace with variables)."""
24
- if self.lhs_symbol not in context:
25
- return None
26
-
27
- lhs_var = context[self.lhs_symbol]
28
-
29
- try:
30
- # Call the factory function with the context to create the RHS
31
- rhs_expr = self.rhs_factory(context)
32
- return lhs_var.equals(rhs_expr)
33
- except Exception:
34
- return None
35
-
36
-
37
- class SubProblemProxy:
38
- """
39
- Proxy object that represents a sub-problem and provides namespaced variable access
40
- during class definition. Returns properly namespaced variables immediately to prevent
41
- malformed expressions.
42
- """
43
- def __init__(self, sub_problem, namespace):
44
- self._sub_problem = sub_problem
45
- self._namespace = namespace
46
- self._variable_cache = {}
47
- self._variable_configurations = {} # Track configurations applied to variables
48
- # Global registry to track which expressions involve proxy variables
49
- if not hasattr(SubProblemProxy, '_expressions_with_proxies'):
50
- SubProblemProxy._expressions_with_proxies = set()
51
-
52
- def __getattr__(self, name):
53
- # Handle internal Python attributes to prevent recursion during deepcopy
54
- if name.startswith('_'):
55
- raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
56
-
57
- if name in self._variable_cache:
58
- return self._variable_cache[name]
59
-
60
- if hasattr(self._sub_problem, name):
61
- attr_value = getattr(self._sub_problem, name)
62
- if isinstance(attr_value, Variable):
63
- # Create a properly namespaced variable immediately
64
- namespaced_var = self._create_namespaced_variable(attr_value)
65
- self._variable_cache[name] = namespaced_var
66
- return namespaced_var
67
-
68
- return getattr(self._sub_problem, name)
69
-
70
- def _create_namespaced_variable(self, original_var):
71
- """Create a Variable with namespaced symbol for proper expression creation."""
72
- namespaced_symbol = f"{self._namespace}_{original_var.symbol}"
73
-
74
- # Create new Variable with namespaced symbol that tracks modifications
75
- namespaced_var = ConfigurableVariable(
76
- symbol=namespaced_symbol,
77
- name=f"{original_var.name} ({self._namespace.title()})",
78
- quantity=original_var.quantity,
79
- is_known=original_var.is_known,
80
- proxy=self,
81
- original_symbol=original_var.symbol
82
- )
83
-
84
- return namespaced_var
85
-
86
- def track_configuration(self, original_symbol, quantity, is_known):
87
- """Track a configuration change made to a variable."""
88
- self._variable_configurations[original_symbol] = {
89
- 'quantity': quantity,
90
- 'is_known': is_known
91
- }
92
-
93
- def get_configurations(self):
94
- """Get all tracked configurations."""
95
- return self._variable_configurations.copy()
96
-
97
-
98
- class ConfigurableVariable:
99
- """
100
- A Variable wrapper that can track configuration changes and report them back to its proxy.
101
- This acts as a proxy around the actual qnty Variable rather than inheriting from it.
102
- """
103
- def __init__(self, symbol, name, quantity, is_known=True, proxy=None, original_symbol=None):
104
- # Store the actual variable (we'll delegate to it)
105
- # Create a variable of the appropriate type based on the original
106
- # For now, we'll create a Dimensionless variable and update it
107
- self._variable = Dimensionless(name)
108
-
109
- # Set the properties
110
- self._variable.symbol = symbol
111
- self._variable.quantity = quantity
112
- self._variable.is_known = is_known
113
-
114
- # Store proxy information
115
- self._proxy = proxy
116
- self._original_symbol = original_symbol
117
-
118
- def __getattr__(self, name):
119
- """Delegate all other attributes to the wrapped variable."""
120
- return getattr(self._variable, name)
121
-
122
- # Delegate arithmetic operations to the wrapped variable
123
- def __add__(self, other):
124
- return self._variable.__add__(other)
125
-
126
- def __radd__(self, other):
127
- return self._variable.__radd__(other)
128
-
129
- def __sub__(self, other):
130
- return self._variable.__sub__(other)
131
-
132
- def __rsub__(self, other):
133
- return self._variable.__rsub__(other)
134
-
135
- def __mul__(self, other):
136
- return self._variable.__mul__(other)
137
-
138
- def __rmul__(self, other):
139
- return self._variable.__rmul__(other)
140
-
141
- def __truediv__(self, other):
142
- return self._variable.__truediv__(other)
143
-
144
- def __rtruediv__(self, other):
145
- return self._variable.__rtruediv__(other)
146
-
147
- def __pow__(self, other):
148
- return self._variable.__pow__(other)
149
-
150
- def __neg__(self):
151
- # Implement negation as multiplication by -1, consistent with other arithmetic operations
152
- return self._variable * (-1)
153
-
154
- # Comparison operations
155
- def __lt__(self, other):
156
- return self._variable.__lt__(other)
157
-
158
- def __le__(self, other):
159
- return self._variable.__le__(other)
160
-
161
- def __gt__(self, other):
162
- return self._variable.__gt__(other)
163
-
164
- def __ge__(self, other):
165
- return self._variable.__ge__(other)
166
-
167
- def __eq__(self, other):
168
- return self._variable.__eq__(other)
169
-
170
- def __ne__(self, other):
171
- return self._variable.__ne__(other)
172
-
173
- def __setattr__(self, name, value):
174
- """Delegate attribute setting to the wrapped variable when appropriate."""
175
- if name.startswith('_') or name in ('_variable', '_proxy', '_original_symbol'):
176
- super().__setattr__(name, value)
177
- else:
178
- setattr(self._variable, name, value)
179
-
180
- def set(self, value):
181
- """Override set method to track configuration changes."""
182
- result = self._variable.set(value)
183
- if self._proxy and self._original_symbol:
184
- # Track this configuration change
185
- self._proxy.track_configuration(self._original_symbol, self._variable.quantity, self._variable.is_known)
186
- return result
187
-
188
- def update(self, value=None, unit=None, quantity=None, is_known=None):
189
- """Override update method to track configuration changes."""
190
- result = self._variable.update(value, unit, quantity, is_known)
191
- if self._proxy and self._original_symbol:
192
- # Track this configuration change
193
- self._proxy.track_configuration(self._original_symbol, self._variable.quantity, self._variable.is_known)
194
- return result
195
-
196
- def mark_known(self, quantity=None):
197
- """Override mark_known to track configuration changes."""
198
- result = self._variable.mark_known(quantity)
199
- if self._proxy and self._original_symbol:
200
- # Track this configuration change
201
- self._proxy.track_configuration(self._original_symbol, self._variable.quantity, self._variable.is_known)
202
- return result
203
-
204
- def mark_unknown(self):
205
- """Override mark_unknown to track configuration changes."""
206
- result = self._variable.mark_unknown()
207
- if self._proxy and self._original_symbol:
208
- # Track this configuration change
209
- self._proxy.track_configuration(self._original_symbol, self._variable.quantity, self._variable.is_known)
210
- return result
211
-
212
-
213
- class DelayedVariableReference:
214
- """
215
- A placeholder for a variable that will be resolved to its namespaced version later.
216
- Supports arithmetic operations that create delayed expressions.
217
- """
218
- def __init__(self, namespace, symbol, original_var):
219
- self.namespace = namespace
220
- self.symbol = symbol
221
- self.original_var = original_var
222
- self._namespaced_symbol = f"{namespace}_{symbol}"
223
-
224
- def resolve(self, context):
225
- """Resolve to the actual namespaced variable from context."""
226
- return context.get(self._namespaced_symbol)
227
-
228
- def __add__(self, other):
229
- return DelayedExpression('+', self, other)
230
-
231
- def __radd__(self, other):
232
- return DelayedExpression('+', other, self)
233
-
234
- def __sub__(self, other):
235
- return DelayedExpression('-', self, other)
236
-
237
- def __rsub__(self, other):
238
- return DelayedExpression('-', other, self)
239
-
240
- def __mul__(self, other):
241
- return DelayedExpression('*', self, other)
242
-
243
- def __rmul__(self, other):
244
- return DelayedExpression('*', other, self)
245
-
246
- def __truediv__(self, other):
247
- return DelayedExpression('/', self, other)
248
-
249
- def __rtruediv__(self, other):
250
- return DelayedExpression('/', other, self)
251
-
252
-
253
- class DelayedExpression:
254
- """
255
- Represents an arithmetic expression that will be resolved later when context is available.
256
- Supports chaining of operations.
257
- """
258
- def __init__(self, operation, left, right):
259
- self.operation = operation
260
- self.left = left
261
- self.right = right
262
-
263
- def resolve(self, context):
264
- """Resolve this expression to actual Variable/Expression objects."""
265
- left_resolved = self._resolve_operand(self.left, context)
266
- right_resolved = self._resolve_operand(self.right, context)
267
-
268
- if left_resolved is None or right_resolved is None:
269
- return None
270
-
271
- # Create the actual expression
272
- if self.operation == '+':
273
- return left_resolved + right_resolved
274
- elif self.operation == '-':
275
- return left_resolved - right_resolved
276
- elif self.operation == '*':
277
- return left_resolved * right_resolved
278
- elif self.operation == '/':
279
- return left_resolved / right_resolved
280
- else:
281
- return BinaryOperation(self.operation, left_resolved, right_resolved)
282
-
283
- def _resolve_operand(self, operand, context):
284
- """Resolve a single operand to a Variable/Expression."""
285
- if isinstance(operand, DelayedVariableReference):
286
- return operand.resolve(context)
287
- elif isinstance(operand, DelayedExpression):
288
- return operand.resolve(context)
289
- elif hasattr(operand, 'resolve'):
290
- return operand.resolve(context)
291
- else:
292
- # It's a literal value or Variable
293
- return operand
294
-
295
- def __add__(self, other):
296
- return DelayedExpression('+', self, other)
297
-
298
- def __radd__(self, other):
299
- return DelayedExpression('+', other, self)
300
-
301
- def __sub__(self, other):
302
- return DelayedExpression('-', self, other)
303
-
304
- def __rsub__(self, other):
305
- return DelayedExpression('-', other, self)
306
-
307
- def __mul__(self, other):
308
- return DelayedExpression('*', self, other)
309
-
310
- def __rmul__(self, other):
311
- return DelayedExpression('*', other, self)
312
-
313
- def __truediv__(self, other):
314
- return DelayedExpression('/', self, other)
315
-
316
- def __rtruediv__(self, other):
317
- return DelayedExpression('/', other, self)
318
-
319
-
320
- class DelayedFunction:
321
- """
322
- Represents a function call that will be resolved later when context is available.
323
- """
324
- def __init__(self, func_name, *args):
325
- self.func_name = func_name
326
- self.args = args
327
-
328
- def resolve(self, context):
329
- """Resolve function call with given context."""
330
- # Resolve all arguments
331
- resolved_args = []
332
- for arg in self.args:
333
- if hasattr(arg, 'resolve'):
334
- resolved_arg = arg.resolve(context)
335
- if resolved_arg is None:
336
- return None
337
- resolved_args.append(resolved_arg)
338
- else:
339
- resolved_args.append(arg)
340
-
341
- # Call the appropriate function
342
- if self.func_name == 'sin':
343
- return sin(resolved_args[0])
344
- elif self.func_name == 'min_expr':
345
- return min_expr(*resolved_args)
346
- elif self.func_name == 'max_expr':
347
- return max_expr(*resolved_args)
348
- else:
349
- # Generic function call
350
- return None
351
-
352
- def __add__(self, other):
353
- return DelayedExpression('+', self, other)
354
-
355
- def __radd__(self, other):
356
- return DelayedExpression('+', other, self)
357
-
358
- def __sub__(self, other):
359
- return DelayedExpression('-', self, other)
360
-
361
- def __rsub__(self, other):
362
- return DelayedExpression('-', other, self)
363
-
364
- def __mul__(self, other):
365
- return DelayedExpression('*', self, other)
366
-
367
- def __rmul__(self, other):
368
- return DelayedExpression('*', other, self)
369
-
370
- def __truediv__(self, other):
371
- return DelayedExpression('/', self, other)
372
-
373
- def __rtruediv__(self, other):
374
- return DelayedExpression('/', other, self)
375
-
376
-
377
- # Delayed function factories
378
- def delayed_sin(expr):
379
- return DelayedFunction('sin', expr)
380
-
381
- def delayed_min_expr(*args):
382
- return DelayedFunction('min_expr', *args)
383
-
384
- def delayed_max_expr(*args):
385
- return DelayedFunction('max_expr', *args)