qnty 0.0.8__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 (74) hide show
  1. qnty/__init__.py +140 -59
  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 +4 -0
  12. qnty/equations/equation.py +220 -0
  13. qnty/equations/system.py +130 -0
  14. qnty/expressions/__init__.py +40 -0
  15. qnty/expressions/formatter.py +188 -0
  16. qnty/expressions/functions.py +74 -0
  17. qnty/expressions/nodes.py +701 -0
  18. qnty/expressions/types.py +70 -0
  19. qnty/extensions/plotting/__init__.py +0 -0
  20. qnty/extensions/reporting/__init__.py +0 -0
  21. qnty/problems/__init__.py +145 -0
  22. qnty/problems/composition.py +1031 -0
  23. qnty/problems/problem.py +695 -0
  24. qnty/problems/rules.py +145 -0
  25. qnty/problems/solving.py +1216 -0
  26. qnty/problems/validation.py +127 -0
  27. qnty/quantities/__init__.py +29 -0
  28. qnty/quantities/base_qnty.py +677 -0
  29. qnty/quantities/field_converters.py +24004 -0
  30. qnty/quantities/field_qnty.py +1012 -0
  31. qnty/quantities/field_setter.py +12320 -0
  32. qnty/quantities/field_vars.py +6325 -0
  33. qnty/quantities/field_vars.pyi +4191 -0
  34. qnty/solving/__init__.py +0 -0
  35. qnty/solving/manager.py +96 -0
  36. qnty/solving/order.py +403 -0
  37. qnty/solving/solvers/__init__.py +13 -0
  38. qnty/solving/solvers/base.py +82 -0
  39. qnty/solving/solvers/iterative.py +165 -0
  40. qnty/solving/solvers/simultaneous.py +475 -0
  41. qnty/units/__init__.py +1 -0
  42. qnty/units/field_units.py +10507 -0
  43. qnty/units/field_units.pyi +2461 -0
  44. qnty/units/prefixes.py +203 -0
  45. qnty/{unit.py → units/registry.py} +89 -61
  46. qnty/utils/__init__.py +16 -0
  47. qnty/utils/caching/__init__.py +23 -0
  48. qnty/utils/caching/manager.py +401 -0
  49. qnty/utils/error_handling/__init__.py +66 -0
  50. qnty/utils/error_handling/context.py +39 -0
  51. qnty/utils/error_handling/exceptions.py +96 -0
  52. qnty/utils/error_handling/handlers.py +171 -0
  53. qnty/utils/logging.py +40 -0
  54. qnty/utils/protocols.py +164 -0
  55. qnty/utils/scope_discovery.py +420 -0
  56. qnty-0.1.0.dist-info/METADATA +199 -0
  57. qnty-0.1.0.dist-info/RECORD +60 -0
  58. qnty/dimension.py +0 -186
  59. qnty/equation.py +0 -297
  60. qnty/expression.py +0 -553
  61. qnty/prefixes.py +0 -229
  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 → extensions}/__init__.py +0 -0
  73. /qnty/{variable_types → extensions/integration}/__init__.py +0 -0
  74. {qnty-0.0.8.dist-info → qnty-0.1.0.dist-info}/WHEEL +0 -0
qnty/variable.py DELETED
@@ -1,300 +0,0 @@
1
- """
2
- High-Performance Quantity and Variables
3
- ========================================
4
-
5
- FastQuantity class and type-safe variables optimized for engineering calculations
6
- with dimensional safety.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- from typing import Generic, Self, TypeVar
12
-
13
- from .dimension import AREA, DIMENSIONLESS, FORCE, LENGTH, PRESSURE, VOLUME, DimensionSignature
14
- from .dimension import ENERGY_HEAT_WORK as ENERGY
15
- from .unit import UnitConstant, UnitDefinition, registry
16
- from .units import DimensionlessUnits, LengthUnits, PressureUnits
17
-
18
- # TypeVar for generic dimensional types
19
- DimensionType = TypeVar('DimensionType', bound='FastQuantity')
20
-
21
-
22
- class TypeSafeSetter:
23
- """Basic type-safe setter that accepts compatible units."""
24
-
25
- def __init__(self, variable: TypeSafeVariable, value: float):
26
- self.variable = variable
27
- self.value = value
28
-
29
- def with_unit(self, unit: UnitConstant) -> TypeSafeVariable:
30
- """Set with type-safe unit constant."""
31
- if not self.variable.expected_dimension.is_compatible(unit.dimension):
32
- raise TypeError(f"Unit {unit.name} incompatible with expected dimension")
33
-
34
- self.variable.quantity = FastQuantity(self.value, unit)
35
- return self.variable
36
-
37
-
38
- class FastQuantity:
39
- """High-performance quantity optimized for engineering calculations."""
40
-
41
- __slots__ = ('value', 'unit', 'dimension', '_si_factor', '_dimension_sig')
42
-
43
- def __init__(self, value: float, unit: UnitConstant):
44
- self.value = float(value)
45
- self.unit = unit
46
- self.dimension = unit.dimension
47
- # Cache commonly used values to avoid lookups
48
- self._si_factor = unit.si_factor
49
- self._dimension_sig = unit.dimension._signature
50
-
51
- def __str__(self):
52
- return f"{self.value} {self.unit.symbol}"
53
-
54
- def __repr__(self):
55
- return f"FastQuantity({self.value}, {self.unit.name})"
56
-
57
- # Ultra-fast arithmetic with dimensional checking
58
- def __add__(self, other: FastQuantity) -> FastQuantity:
59
- # Fast dimension compatibility check using cached signatures
60
- if self._dimension_sig != other._dimension_sig:
61
- raise ValueError(f"Cannot add {self.unit.name} and {other.unit.name}")
62
-
63
- # Fast path for same units - no conversion needed
64
- if self.unit == other.unit:
65
- return FastQuantity(self.value + other.value, self.unit)
66
-
67
- # Convert other to self's units using cached SI factors
68
- other_value = other.value * other._si_factor / self._si_factor
69
- return FastQuantity(self.value + other_value, self.unit)
70
-
71
- def __sub__(self, other: FastQuantity) -> FastQuantity:
72
- # Fast dimension compatibility check using cached signatures
73
- if self._dimension_sig != other._dimension_sig:
74
- raise ValueError(f"Cannot subtract {other.unit.name} from {self.unit.name}")
75
-
76
- # Fast path for same units - no conversion needed
77
- if self.unit == other.unit:
78
- return FastQuantity(self.value - other.value, self.unit)
79
-
80
- # Convert other to self's units using cached SI factors
81
- other_value = other.value * other._si_factor / self._si_factor
82
- return FastQuantity(self.value - other_value, self.unit)
83
-
84
- def __mul__(self, other: FastQuantity | float | int) -> FastQuantity:
85
- if isinstance(other, int | float):
86
- return FastQuantity(self.value * other, self.unit)
87
-
88
- # Handle TypeSafeVariable objects by using their quantity
89
- from .variable_types.typed_variable import TypedVariable # Avoid circular imports
90
- if isinstance(other, TypedVariable) and other.quantity is not None:
91
- other = other.quantity
92
-
93
- # Fast dimensional analysis using cached signatures
94
- result_dimension_sig = self._dimension_sig * other._dimension_sig
95
-
96
- # Use cached SI factors for conversion
97
- self_si_value = self.value * self._si_factor
98
- other_si_value = other.value * other._si_factor
99
- result_si_value = self_si_value * other_si_value
100
-
101
- # Fast path for common dimension combinations
102
- result_unit = self._find_result_unit_fast(result_dimension_sig, self, other)
103
- result_value = result_si_value / result_unit.si_factor
104
-
105
- return FastQuantity(result_value, result_unit)
106
-
107
- def __rmul__(self, other: float | int) -> FastQuantity:
108
- """Reverse multiplication for cases like 2 * quantity."""
109
- if isinstance(other, int | float):
110
- return FastQuantity(other * self.value, self.unit)
111
- return NotImplemented
112
-
113
- def __truediv__(self, other: FastQuantity | float | int) -> FastQuantity:
114
- if isinstance(other, int | float):
115
- return FastQuantity(self.value / other, self.unit)
116
-
117
- # Fast dimensional analysis using cached signatures
118
- result_dimension_sig = self._dimension_sig / other._dimension_sig
119
-
120
- # Use cached SI factors for conversion
121
- self_si_value = self.value * self._si_factor
122
- other_si_value = other.value * other._si_factor
123
- result_si_value = self_si_value / other_si_value
124
-
125
- # Fast path for common dimension combinations
126
- result_unit = self._find_result_unit_fast(result_dimension_sig, self, other)
127
- result_value = result_si_value / result_unit.si_factor
128
-
129
- return FastQuantity(result_value, result_unit)
130
-
131
- def _find_result_unit_fast(self, result_dimension_sig: int | float,
132
- left_qty: FastQuantity, right_qty: FastQuantity) -> UnitConstant:
133
- """Ultra-fast unit finding using cached dimension signatures."""
134
-
135
- # Initialize dimension cache if empty
136
- if not registry._dimension_cache:
137
- from .dimension import ENERGY_PER_UNIT_AREA, SURFACE_TENSION
138
- registry._dimension_cache = {
139
- DIMENSIONLESS._signature: DimensionlessUnits.dimensionless,
140
- LENGTH._signature: LengthUnits.millimeter,
141
- PRESSURE._signature: PressureUnits.Pa,
142
- AREA._signature: LengthUnits.millimeter, # mm²
143
- VOLUME._signature: LengthUnits.millimeter, # mm³
144
- FORCE._signature: UnitConstant(UnitDefinition("newton", "N", FORCE, 1.0)),
145
- ENERGY._signature: UnitConstant(UnitDefinition("joule", "J", ENERGY, 1.0)),
146
- SURFACE_TENSION._signature: UnitConstant(UnitDefinition("newton_per_meter", "N/m", SURFACE_TENSION, 1.0)),
147
- ENERGY_PER_UNIT_AREA._signature: UnitConstant(UnitDefinition("joule_per_square_meter", "J/m²", ENERGY_PER_UNIT_AREA, 1.0)),
148
- }
149
-
150
- # O(1) lookup for common dimensions
151
- if result_dimension_sig in registry._dimension_cache:
152
- return registry._dimension_cache[result_dimension_sig]
153
-
154
- # For rare combined dimensions, create SI base unit with descriptive name
155
- result_dimension = DimensionSignature(result_dimension_sig)
156
-
157
- # Create descriptive name based on dimensional analysis
158
- si_name = self._create_si_unit_name(result_dimension)
159
- si_symbol = self._create_si_unit_symbol(result_dimension)
160
-
161
- temp_unit = UnitDefinition(
162
- name=si_name,
163
- symbol=si_symbol,
164
- dimension=result_dimension,
165
- si_factor=1.0
166
- )
167
- result_unit = UnitConstant(temp_unit)
168
-
169
- # Cache for future use
170
- registry._dimension_cache[result_dimension_sig] = result_unit
171
- return result_unit
172
-
173
- def _create_si_unit_name(self, dimension: DimensionSignature) -> str:
174
- """Create descriptive SI unit name based on dimensional analysis."""
175
- # For now, return a generic SI unit name. In the future, this could be enhanced
176
- # to parse the dimension signature and create descriptive names like "newton_per_meter"
177
- return f"si_derived_unit_{abs(hash(dimension._signature)) % 10000}"
178
-
179
- def _create_si_unit_symbol(self, _dimension: DimensionSignature) -> str:
180
- """Create SI unit symbol based on dimensional analysis."""
181
- # For complex units, return descriptive symbol based on common engineering units
182
- return "SI_unit"
183
-
184
- def _find_result_unit(self, result_dimension: DimensionSignature,
185
- left_qty: FastQuantity, right_qty: FastQuantity) -> UnitConstant:
186
- """Legacy method - kept for compatibility."""
187
- return self._find_result_unit_fast(result_dimension._signature, left_qty, right_qty)
188
-
189
- # Ultra-fast comparisons
190
- def __lt__(self, other: FastQuantity) -> bool:
191
- if self._dimension_sig != other._dimension_sig:
192
- raise ValueError("Cannot compare incompatible dimensions")
193
-
194
- # Fast path for same units
195
- if self.unit == other.unit:
196
- return self.value < other.value
197
-
198
- # Convert using cached SI factors
199
- other_value = other.value * other._si_factor / self._si_factor
200
- return self.value < other_value
201
-
202
- def __eq__(self, other) -> bool:
203
- if not isinstance(other, FastQuantity):
204
- return False
205
- if self._dimension_sig != other._dimension_sig:
206
- return False
207
-
208
- # Fast path for same units
209
- if self.unit == other.unit:
210
- return abs(self.value - other.value) < 1e-10
211
-
212
- # Convert using cached SI factors
213
- other_value = other.value * other._si_factor / self._si_factor
214
- return abs(self.value - other_value) < 1e-10
215
-
216
- def to(self, target_unit: UnitConstant) -> FastQuantity:
217
- """Ultra-fast unit conversion."""
218
- if self.unit == target_unit:
219
- return FastQuantity(self.value, target_unit)
220
-
221
- # Direct SI factor conversion - avoid registry lookup
222
- converted_value = self.value * self._si_factor / target_unit.si_factor
223
- return FastQuantity(converted_value, target_unit)
224
-
225
-
226
- class TypeSafeVariable(Generic[DimensionType]):
227
- """
228
- Base class for type-safe variables with dimensional checking.
229
-
230
- This is a simple data container without dependencies on expressions or equations.
231
- Mathematical operations are added by subclasses or mixins.
232
- """
233
-
234
- # Class attribute defining which setter to use - subclasses can override
235
- _setter_class = TypeSafeSetter
236
-
237
- def __init__(self, name: str, expected_dimension, is_known: bool = True):
238
- self.name = name
239
- self.symbol: str | None = None # Will be set by EngineeringProblem to attribute name
240
- self.expected_dimension = expected_dimension
241
- self.quantity: FastQuantity | None = None
242
- self.is_known = is_known
243
-
244
- def set(self, value: float):
245
- """Create a setter for this variable using the class-specific setter type."""
246
- return self._setter_class(self, value)
247
-
248
- @property
249
- def unknown(self) -> Self:
250
- """Mark this variable as unknown using fluent API."""
251
- self.is_known = False
252
- return self
253
-
254
- @property
255
- def known(self) -> Self:
256
- """Mark this variable as known using fluent API."""
257
- self.is_known = True
258
- return self
259
-
260
- def update(self, value=None, unit=None, quantity=None, is_known=None):
261
- """Update variable properties flexibly."""
262
- if quantity is not None:
263
- self.quantity = quantity
264
- elif value is not None:
265
- # Create setter and call the appropriate unit property
266
- setter = self.set(value)
267
- if unit is not None:
268
- # Try to find the unit property on the setter
269
- if hasattr(setter, unit):
270
- getattr(setter, unit)
271
- elif hasattr(setter, unit + 's'): # Handle singular/plural
272
- getattr(setter, unit + 's')
273
- elif unit.endswith('s') and hasattr(setter, unit[:-1]): # Handle plural to singular
274
- getattr(setter, unit[:-1])
275
- else:
276
- raise ValueError(f"Unit '{unit}' not found for {self.__class__.__name__}")
277
- else:
278
- # If no unit specified, we can't automatically choose a unit
279
- # The caller should specify either a unit or a quantity
280
- raise ValueError("Must specify either 'unit' with 'value' or provide 'quantity' directly")
281
- if is_known is not None:
282
- self.is_known = is_known
283
- return self # For method chaining
284
-
285
- def mark_known(self, quantity=None):
286
- """Mark variable as known, optionally updating its value."""
287
- self.is_known = True
288
- if quantity is not None:
289
- self.quantity = quantity
290
- return self # For method chaining
291
-
292
- def mark_unknown(self):
293
- """Mark variable as unknown."""
294
- self.is_known = False
295
- return self # For method chaining
296
-
297
- def __str__(self):
298
- return f"{self.name}: {self.quantity}" if self.quantity else f"{self.name}: unset"
299
-
300
-
@@ -1,58 +0,0 @@
1
- """
2
- Base Variable Module Definition
3
- ===============================
4
-
5
- Provides abstract base class for variable modules and registration functionality.
6
- """
7
-
8
- from abc import ABC, abstractmethod
9
- from typing import Any
10
-
11
-
12
- class VariableModule(ABC):
13
- """Abstract base class for variable modules."""
14
-
15
- @abstractmethod
16
- def get_variable_class(self) -> type[Any]:
17
- """Return the variable class for this module."""
18
- pass
19
-
20
- @abstractmethod
21
- def get_setter_class(self) -> type[Any]:
22
- """Return the setter class for this module."""
23
- pass
24
-
25
- @abstractmethod
26
- def get_expected_dimension(self) -> Any:
27
- """Return the expected dimension for this variable type."""
28
- pass
29
-
30
- def register_to_registry(self, variable_registry):
31
- """Register this variable module to the given registry."""
32
- variable_registry.register_module(
33
- self.get_expected_dimension(),
34
- self.get_variable_class(),
35
- self.get_setter_class()
36
- )
37
-
38
-
39
- class VariableRegistry:
40
- """Registry for variable modules."""
41
-
42
- def __init__(self):
43
- self._modules = {}
44
-
45
- def register_module(self, dimension, variable_class, setter_class):
46
- """Register a variable module."""
47
- self._modules[dimension] = {
48
- 'variable_class': variable_class,
49
- 'setter_class': setter_class
50
- }
51
-
52
- def get_variable_class(self, dimension):
53
- """Get variable class for a dimension."""
54
- return self._modules.get(dimension, {}).get('variable_class')
55
-
56
- def get_setter_class(self, dimension):
57
- """Get setter class for a dimension."""
58
- return self._modules.get(dimension, {}).get('setter_class')
@@ -1,106 +0,0 @@
1
- """
2
- Expression Variable Base Class
3
- ==============================
4
-
5
- Base class that extends TypeSafeVariable with mathematical expression
6
- and equation capabilities.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- from ..equation import Equation
12
- from ..expression import Expression, wrap_operand
13
- from ..variable import FastQuantity, TypeSafeVariable
14
-
15
-
16
- class ExpressionVariable(TypeSafeVariable):
17
- """
18
- TypeSafeVariable extended with expression and equation capabilities.
19
-
20
- This adds mathematical operations that create expressions and equations,
21
- keeping the base TypeSafeVariable free of these dependencies.
22
- """
23
-
24
- def equals(self, expression: Expression | TypeSafeVariable | FastQuantity | int | float):
25
- """Create an equation: self = expression."""
26
- # Wrap the expression in proper Expression type
27
- rhs_expr = wrap_operand(expression)
28
- return Equation(f"{self.name}_eq", self, rhs_expr)
29
-
30
- def __add__(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
31
- """Add this variable to another operand, returning an Expression."""
32
- return wrap_operand(self) + wrap_operand(other)
33
-
34
- def __radd__(self, other: FastQuantity | int | float) -> Expression:
35
- """Reverse add for this variable."""
36
- return wrap_operand(other) + wrap_operand(self)
37
-
38
- def __sub__(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
39
- """Subtract another operand from this variable, returning an Expression."""
40
- return wrap_operand(self) - wrap_operand(other)
41
-
42
- def __rsub__(self, other: FastQuantity | int | float) -> Expression:
43
- """Reverse subtract for this variable."""
44
- return wrap_operand(other) - wrap_operand(self)
45
-
46
- def __mul__(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
47
- """Multiply this variable by another operand, returning an Expression."""
48
- return wrap_operand(self) * wrap_operand(other)
49
-
50
- def __rmul__(self, other: FastQuantity | int | float) -> Expression:
51
- """Reverse multiply for this variable."""
52
- return wrap_operand(other) * wrap_operand(self)
53
-
54
- def __truediv__(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
55
- """Divide this variable by another operand, returning an Expression."""
56
- return wrap_operand(self) / wrap_operand(other)
57
-
58
- def __rtruediv__(self, other: FastQuantity | int | float) -> Expression:
59
- """Reverse divide for this variable."""
60
- return wrap_operand(other) / wrap_operand(self)
61
-
62
- def __pow__(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
63
- """Raise this variable to a power, returning an Expression."""
64
- return wrap_operand(self) ** wrap_operand(other)
65
-
66
- def __rpow__(self, other: FastQuantity | int | float) -> Expression:
67
- """Reverse power for this variable."""
68
- return wrap_operand(other) ** wrap_operand(self)
69
-
70
- # Comparison methods
71
- def lt(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
72
- """Less than comparison (<)."""
73
- from ..expression import BinaryOperation
74
- return BinaryOperation('<', wrap_operand(self), wrap_operand(other))
75
-
76
- def leq(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
77
- """Less than or equal comparison (<=)."""
78
- from ..expression import BinaryOperation
79
- return BinaryOperation('<=', wrap_operand(self), wrap_operand(other))
80
-
81
- def geq(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
82
- """Greater than or equal comparison (>=)."""
83
- from ..expression import BinaryOperation
84
- return BinaryOperation('>=', wrap_operand(self), wrap_operand(other))
85
-
86
- def gt(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
87
- """Greater than comparison (>)."""
88
- from ..expression import BinaryOperation
89
- return BinaryOperation('>', wrap_operand(self), wrap_operand(other))
90
-
91
- # Python comparison operators
92
- def __lt__(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
93
- """Less than comparison (<) operator."""
94
- return self.lt(other)
95
-
96
- def __le__(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
97
- """Less than or equal comparison (<=) operator."""
98
- return self.leq(other)
99
-
100
- def __gt__(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
101
- """Greater than comparison (>) operator."""
102
- return self.gt(other)
103
-
104
- def __ge__(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
105
- """Greater than or equal comparison (>=) operator."""
106
- return self.geq(other)
@@ -1,87 +0,0 @@
1
- """
2
- Typed Variable Base Class
3
- =========================
4
-
5
- Base class that provides common constructor logic for all typed variables,
6
- handling both the original syntax and the new value/unit/name syntax.
7
- """
8
-
9
- from ..dimension import DimensionSignature
10
- from ..variable import TypeSafeSetter
11
- from .expression_variable import ExpressionVariable
12
-
13
-
14
- class TypedVariable(ExpressionVariable):
15
- """
16
- Base class for typed variables with common constructor logic.
17
-
18
- Subclasses need to define:
19
- - _setter_class: The setter class to use
20
- - _expected_dimension: The expected dimension
21
- - _default_unit_property: The default unit property name for fallback
22
- """
23
-
24
- _setter_class: type[TypeSafeSetter] | None = None
25
- _expected_dimension: DimensionSignature | None = None
26
- _default_unit_property: str | None = None
27
-
28
- def __init__(self, *args, is_known: bool = True):
29
- """
30
- Flexible constructor supporting multiple syntaxes.
31
-
32
- Single argument: TypedVariable("name")
33
- Three arguments: TypedVariable(value, "unit", "name")
34
- Two arguments (Dimensionless only): TypedVariable(value, "name")
35
- """
36
- if self._setter_class is None or self._expected_dimension is None:
37
- raise NotImplementedError("Subclass must define _setter_class and _expected_dimension")
38
-
39
- # Handle different argument patterns
40
- if len(args) == 1:
41
- # Original syntax: Variable("name")
42
- super().__init__(args[0], self._expected_dimension, is_known=is_known)
43
-
44
- elif len(args) == 2 and self.__class__.__name__ == 'Dimensionless':
45
- # Special case for Dimensionless: (value, "name")
46
- value, name = args
47
- super().__init__(name, self._expected_dimension, is_known=is_known)
48
- setter = self._setter_class(self, value)
49
- # For DimensionlessSetter, use the dimensionless property
50
- # Type ignore since we know DimensionlessSetter has this property
51
- getattr(setter, 'dimensionless', None) # type: ignore
52
-
53
- elif len(args) == 3:
54
- # New syntax: Variable(value, "unit", "name")
55
- # But Dimensionless doesn't support this pattern
56
- if self.__class__.__name__ == 'Dimensionless':
57
- raise ValueError(f"{self.__class__.__name__} expects either 1 argument (name) or 2 arguments (value, name), got {len(args)}")
58
-
59
- value, unit, name = args
60
- super().__init__(name, self._expected_dimension, is_known=is_known)
61
-
62
- # Auto-set the value with the specified unit
63
- setter = self._setter_class(self, value)
64
-
65
- # Try to find the unit property on the setter
66
- if hasattr(setter, unit):
67
- getattr(setter, unit)
68
- elif hasattr(setter, unit + 's'): # Handle singular/plural
69
- getattr(setter, unit + 's')
70
- elif unit.endswith('s') and hasattr(setter, unit[:-1]): # Handle plural to singular
71
- getattr(setter, unit[:-1])
72
- else:
73
- # Unit not found - provide helpful error with available units
74
- unit_properties = [attr for attr in dir(setter)
75
- if not attr.startswith('_') and attr != 'value' and attr != 'variable']
76
- available_units = ', '.join(sorted(unit_properties[:10])) # Show first 10 units
77
- if len(unit_properties) > 10:
78
- available_units += f' ... and {len(unit_properties) - 10} more'
79
- raise ValueError(f"Unit '{unit}' not found for {self.__class__.__name__}. "
80
- f"Available units: {available_units}")
81
-
82
- else:
83
- # More specific error messages matching test expectations
84
- if self.__class__.__name__ == 'Dimensionless':
85
- raise ValueError(f"{self.__class__.__name__} expects either 1 argument (name) or 2 arguments (value, name), got {len(args)}")
86
- else:
87
- raise ValueError(f"{self.__class__.__name__} expects either 1 argument (name) or 3 arguments (value, unit, name), got {len(args)}")