qnty 0.0.8__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 (74) 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/{equation.py → equations/equation.py} +78 -118
  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/{expression.py → expressions/nodes.py} +209 -216
  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/unit_types/base.py +0 -47
  63. qnty/units.py +0 -8113
  64. qnty/variable.py +0 -300
  65. qnty/variable_types/base.py +0 -58
  66. qnty/variable_types/expression_variable.py +0 -106
  67. qnty/variable_types/typed_variable.py +0 -87
  68. qnty/variables.py +0 -2298
  69. qnty/variables.pyi +0 -6148
  70. qnty-0.0.8.dist-info/METADATA +0 -355
  71. qnty-0.0.8.dist-info/RECORD +0 -19
  72. /qnty/{unit_types → codegen}/__init__.py +0 -0
  73. /qnty/{variable_types → codegen/generators}/__init__.py +0 -0
  74. {qnty-0.0.8.dist-info → qnty-0.0.9.dist-info}/WHEEL +0 -0
@@ -0,0 +1,302 @@
1
+ """
2
+ Metaclass system for EngineeringProblem composition.
3
+
4
+ This module provides the metaclass infrastructure that enables clean
5
+ sub-problem composition syntax at the class definition level. The system
6
+ automatically detects and proxies sub-problems during class creation,
7
+ allowing for natural dotted access in equations.
8
+
9
+ Key Features:
10
+ - Automatic sub-problem detection and proxying
11
+ - Clean composition syntax with dotted access
12
+ - Proper namespace isolation and variable management
13
+ - Comprehensive error handling and validation
14
+
15
+ Example Usage:
16
+ class BranchReinforcementProblem(EngineeringProblem):
17
+ # Sub-problems are automatically detected and proxied
18
+ header = create_straight_pipe_internal()
19
+ branch = create_straight_pipe_internal()
20
+
21
+ # Configure sub-problem variables with fluent API
22
+ header.D.set(2.375).inch
23
+ branch.D.set(1.315).inch
24
+
25
+ # Equations can naturally reference sub-problem variables
26
+ d_1_eqn = d_1.equals((branch.D - 2 * (branch.T_n - branch.c)) / sin(beta))
27
+
28
+ # The metaclass handles all the complex namespace management automatically
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ from typing import Any
34
+
35
+ from ..validation.rules import Rules
36
+ from .composition import SubProblemProxy
37
+
38
+ # Constants for better maintainability
39
+ RESERVED_ATTRIBUTES: set[str] = {'name', 'description'}
40
+ PRIVATE_ATTRIBUTE_PREFIX = '_'
41
+ SUB_PROBLEM_REQUIRED_ATTRIBUTES: tuple[str, ...] = ('variables', 'equations')
42
+
43
+
44
+ # Custom exceptions for better error handling
45
+ class MetaclassError(Exception):
46
+ """Base exception for metaclass-related errors."""
47
+ pass
48
+
49
+
50
+ class SubProblemProxyError(MetaclassError):
51
+ """Raised when sub-problem proxy creation fails."""
52
+ pass
53
+
54
+
55
+ class NamespaceError(MetaclassError):
56
+ """Raised when namespace operations fail."""
57
+ pass
58
+
59
+
60
+ class ProblemMeta(type):
61
+ """
62
+ Metaclass that processes class-level sub-problems to create proper namespace proxies
63
+ BEFORE any equations are evaluated.
64
+
65
+ This metaclass enables clean composition syntax like:
66
+ class MyProblem(EngineeringProblem):
67
+ header = create_pipe_problem()
68
+ branch = create_pipe_problem()
69
+ # Equations can reference header.P, branch.T, etc.
70
+ """
71
+
72
+ # Declare the attributes that will be dynamically added to created classes
73
+ _original_sub_problems: dict[str, Any]
74
+ _proxy_configurations: dict[str, dict[str, Any]]
75
+ _class_checks: dict[str, Any]
76
+
77
+ @classmethod
78
+ def __prepare__(mcs, *args, **kwargs) -> ProxiedNamespace:
79
+ """
80
+ Called before the class body is evaluated.
81
+ Returns a custom namespace that proxies sub-problems.
82
+
83
+ Args:
84
+ *args: Positional arguments (name, bases) - unused but required by protocol
85
+ **kwargs: Additional keyword arguments - unused but required by protocol
86
+
87
+ Returns:
88
+ ProxiedNamespace that will handle sub-problem proxying
89
+ """
90
+ # Parameters are required by metaclass protocol but not used in this implementation
91
+ del args, kwargs # Explicitly acknowledge unused parameters
92
+ return ProxiedNamespace()
93
+
94
+ def __new__(mcs, name: str, bases: tuple[type, ...], namespace: ProxiedNamespace, **kwargs) -> type:
95
+ """
96
+ Create the new class with properly integrated sub-problems.
97
+
98
+ Args:
99
+ name: Name of the class being created
100
+ bases: Base classes
101
+ namespace: The ProxiedNamespace containing proxied sub-problems
102
+ **kwargs: Additional keyword arguments - unused but required by protocol
103
+
104
+ Returns:
105
+ The newly created class with metaclass attributes
106
+
107
+ Raises:
108
+ MetaclassError: If class creation fails due to metaclass issues
109
+ """
110
+ # kwargs is required by metaclass protocol but not used in this implementation
111
+ del kwargs # Explicitly acknowledge unused parameter
112
+ try:
113
+ # Validate the namespace
114
+ if not isinstance(namespace, ProxiedNamespace):
115
+ raise MetaclassError(f"Expected ProxiedNamespace, got {type(namespace)}")
116
+
117
+ # Extract the original sub-problems and proxy objects from the namespace
118
+ sub_problem_proxies = getattr(namespace, '_sub_problem_proxies', {})
119
+ proxy_objects = getattr(namespace, '_proxy_objects', {})
120
+
121
+ # Validate that proxy objects are consistent
122
+ if set(sub_problem_proxies.keys()) != set(proxy_objects.keys()):
123
+ raise MetaclassError("Inconsistent proxy state: sub-problem and proxy object keys don't match")
124
+
125
+ # Create the class normally
126
+ cls = super().__new__(mcs, name, bases, dict(namespace))
127
+
128
+ # Store the original sub-problems and proxy configurations for later integration
129
+ cls._original_sub_problems = sub_problem_proxies
130
+
131
+ # Extract configurations safely with error handling
132
+ proxy_configurations = {}
133
+ for proxy_name, proxy in proxy_objects.items():
134
+ try:
135
+ # Cache configurations to avoid recomputation
136
+ if not hasattr(proxy, '_cached_configurations'):
137
+ proxy._cached_configurations = proxy.get_configurations()
138
+ proxy_configurations[proxy_name] = proxy._cached_configurations
139
+ except Exception as e:
140
+ raise SubProblemProxyError(f"Failed to get configurations from proxy '{proxy_name}': {e}") from e
141
+
142
+ cls._proxy_configurations = proxy_configurations
143
+
144
+ # Collect Check objects from class attributes
145
+ checks = {}
146
+ for attr_name, attr_value in namespace.items():
147
+ if isinstance(attr_value, Rules):
148
+ checks[attr_name] = attr_value
149
+
150
+ cls._class_checks = checks
151
+
152
+ return cls
153
+
154
+ except Exception as e:
155
+ # Re-raise MetaclassError and SubProblemProxyError as-is
156
+ if isinstance(e, MetaclassError | SubProblemProxyError):
157
+ raise
158
+ # Wrap other exceptions
159
+ raise MetaclassError(f"Failed to create class '{name}': {e}") from e
160
+
161
+
162
+ class ProxiedNamespace(dict):
163
+ """
164
+ Custom namespace that automatically proxies sub-problems as they're added.
165
+
166
+ This namespace intercepts class attribute assignments during class creation
167
+ and automatically wraps EngineeringProblem objects in SubProblemProxy objects.
168
+ This enables clean composition syntax where sub-problems can be referenced
169
+ with dot notation in equations.
170
+
171
+ Example:
172
+ class ComposedProblem(EngineeringProblem):
173
+ header = create_pipe_problem() # Gets proxied automatically
174
+ branch = create_pipe_problem() # Gets proxied automatically
175
+ # Now equations can use header.P, branch.T, etc.
176
+ """
177
+
178
+ def __init__(self) -> None:
179
+ """Initialize the proxied namespace with empty storage."""
180
+ super().__init__()
181
+ self._sub_problem_proxies: dict[str, Any] = {}
182
+ self._proxy_objects: dict[str, SubProblemProxy] = {}
183
+
184
+ def __setitem__(self, key: str, value: Any) -> None:
185
+ """
186
+ Intercept attribute assignment and proxy sub-problems automatically.
187
+
188
+ Args:
189
+ key: The attribute name being set
190
+ value: The value being assigned
191
+
192
+ Raises:
193
+ NamespaceError: If namespace operation fails
194
+ SubProblemProxyError: If proxy creation fails
195
+ """
196
+ try:
197
+ if self._is_sub_problem(key, value):
198
+ self._create_and_store_proxy(key, value)
199
+ elif self._is_variable_with_auto_symbol(value):
200
+ self._set_variable_symbol_and_store(key, value)
201
+ else:
202
+ super().__setitem__(key, value)
203
+ except Exception as e:
204
+ if isinstance(e, NamespaceError | SubProblemProxyError):
205
+ raise
206
+ raise NamespaceError(f"Failed to set attribute '{key}': {e}") from e
207
+
208
+ def _is_sub_problem(self, key: str, value: Any) -> bool:
209
+ """
210
+ Determine if a value should be treated as a sub-problem.
211
+
212
+ Args:
213
+ key: The attribute name
214
+ value: The value being assigned
215
+
216
+ Returns:
217
+ True if this should be proxied as a sub-problem
218
+ """
219
+ # Quick checks first (fail fast)
220
+ if key.startswith(PRIVATE_ATTRIBUTE_PREFIX) or key in RESERVED_ATTRIBUTES:
221
+ return False
222
+
223
+ # Check for None or basic types that definitely aren't sub-problems
224
+ if value is None or isinstance(value, str | int | float | bool | list | dict):
225
+ return False
226
+
227
+ # Cache hasattr results to avoid repeated attribute lookups
228
+ if not hasattr(self, '_attr_cache'):
229
+ self._attr_cache = {}
230
+
231
+ # Use object id as cache key since objects are unique
232
+ cache_key = (id(value), tuple(SUB_PROBLEM_REQUIRED_ATTRIBUTES))
233
+ if cache_key not in self._attr_cache:
234
+ self._attr_cache[cache_key] = all(hasattr(value, attr) for attr in SUB_PROBLEM_REQUIRED_ATTRIBUTES)
235
+
236
+ return self._attr_cache[cache_key]
237
+
238
+ def _is_variable_with_auto_symbol(self, value: Any) -> bool:
239
+ """
240
+ Determine if a value is a Variable that needs automatic symbol assignment.
241
+
242
+ Args:
243
+ value: The value being assigned
244
+
245
+ Returns:
246
+ True if this is a Variable with symbol == "<auto>"
247
+ """
248
+ # Import Variable here to avoid circular imports
249
+ try:
250
+ from qnty.quantities.quantity import TypeSafeVariable as Variable
251
+ return isinstance(value, Variable) and value.symbol == "<auto>"
252
+ except ImportError:
253
+ return False
254
+
255
+ def _set_variable_symbol_and_store(self, key: str, value: Any) -> None:
256
+ """
257
+ Set the variable's symbol to the attribute name and store it.
258
+
259
+ Args:
260
+ key: The attribute name to use as symbol
261
+ value: The Variable object
262
+ """
263
+ try:
264
+ # Set the symbol to the attribute name
265
+ value.symbol = key
266
+ # Store the modified variable
267
+ super().__setitem__(key, value)
268
+ except Exception as e:
269
+ raise NamespaceError(f"Failed to set symbol for variable '{key}': {e}") from e
270
+
271
+ def _create_and_store_proxy(self, key: str, value: Any) -> None:
272
+ """
273
+ Create a proxy for the sub-problem and store references.
274
+
275
+ Args:
276
+ key: The attribute name for the sub-problem
277
+ value: The sub-problem object to proxy
278
+
279
+ Raises:
280
+ SubProblemProxyError: If proxy creation fails
281
+ NamespaceError: If key already exists as a sub-problem
282
+ """
283
+ # Check for conflicts
284
+ if key in self._sub_problem_proxies:
285
+ raise NamespaceError(f"Sub-problem '{key}' already exists in namespace")
286
+
287
+ try:
288
+ # Store the original sub-problem
289
+ self._sub_problem_proxies[key] = value
290
+
291
+ # Create and store the proxy
292
+ proxy = SubProblemProxy(value, key)
293
+ self._proxy_objects[key] = proxy
294
+
295
+ # Set the proxy in the namespace
296
+ super().__setitem__(key, proxy)
297
+
298
+ except Exception as e:
299
+ # Clean up partial state on failure
300
+ self._sub_problem_proxies.pop(key, None)
301
+ self._proxy_objects.pop(key, None)
302
+ raise SubProblemProxyError(f"Failed to create proxy for sub-problem '{key}': {e}") from e