qnty 0.0.1__py3-none-any.whl → 0.0.3__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/variable.py CHANGED
@@ -2,21 +2,37 @@
2
2
  High-Performance Quantity and Variables
3
3
  ========================================
4
4
 
5
- FastQuantity class and type-safe variables optimized for engineering calculations
5
+ FastQuantity class and type-safe variables optimized for engineering calculations
6
6
  with dimensional safety.
7
7
  """
8
8
 
9
- from typing import Union, Optional, TypeVar, Generic, TYPE_CHECKING
10
- from .dimension import DimensionSignature, DIMENSIONLESS, LENGTH, PRESSURE, AREA, VOLUME, FORCE, ENERGY
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
11
15
  from .unit import UnitConstant, UnitDefinition, registry
12
16
  from .units import DimensionlessUnits, LengthUnits, PressureUnits
13
17
 
14
- if TYPE_CHECKING:
15
- from .setters import TypeSafeSetter, LengthSetter, PressureSetter
18
+ # TypeVar for generic dimensional types
19
+ DimensionType = TypeVar('DimensionType', bound='FastQuantity')
16
20
 
17
21
 
18
- DimensionType = TypeVar('DimensionType', bound='FastQuantity')
19
- SetterType = TypeVar('SetterType')
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
20
36
 
21
37
 
22
38
  class FastQuantity:
@@ -39,7 +55,7 @@ class FastQuantity:
39
55
  return f"FastQuantity({self.value}, {self.unit.name})"
40
56
 
41
57
  # Ultra-fast arithmetic with dimensional checking
42
- def __add__(self, other: 'FastQuantity') -> 'FastQuantity':
58
+ def __add__(self, other: FastQuantity) -> FastQuantity:
43
59
  # Fast dimension compatibility check using cached signatures
44
60
  if self._dimension_sig != other._dimension_sig:
45
61
  raise ValueError(f"Cannot add {self.unit.name} and {other.unit.name}")
@@ -52,7 +68,7 @@ class FastQuantity:
52
68
  other_value = other.value * other._si_factor / self._si_factor
53
69
  return FastQuantity(self.value + other_value, self.unit)
54
70
 
55
- def __sub__(self, other: 'FastQuantity') -> 'FastQuantity':
71
+ def __sub__(self, other: FastQuantity) -> FastQuantity:
56
72
  # Fast dimension compatibility check using cached signatures
57
73
  if self._dimension_sig != other._dimension_sig:
58
74
  raise ValueError(f"Cannot subtract {other.unit.name} from {self.unit.name}")
@@ -65,8 +81,8 @@ class FastQuantity:
65
81
  other_value = other.value * other._si_factor / self._si_factor
66
82
  return FastQuantity(self.value - other_value, self.unit)
67
83
 
68
- def __mul__(self, other: Union['FastQuantity', float, int]) -> 'FastQuantity':
69
- if isinstance(other, (int, float)):
84
+ def __mul__(self, other: FastQuantity | float | int) -> FastQuantity:
85
+ if isinstance(other, int | float):
70
86
  return FastQuantity(self.value * other, self.unit)
71
87
 
72
88
  # Fast dimensional analysis using cached signatures
@@ -83,14 +99,14 @@ class FastQuantity:
83
99
 
84
100
  return FastQuantity(result_value, result_unit)
85
101
 
86
- def __rmul__(self, other: Union[float, int]) -> 'FastQuantity':
102
+ def __rmul__(self, other: float | int) -> FastQuantity:
87
103
  """Reverse multiplication for cases like 2 * quantity."""
88
- if isinstance(other, (int, float)):
104
+ if isinstance(other, int | float):
89
105
  return FastQuantity(other * self.value, self.unit)
90
106
  return NotImplemented
91
107
 
92
- def __truediv__(self, other: Union['FastQuantity', float, int]) -> 'FastQuantity':
93
- if isinstance(other, (int, float)):
108
+ def __truediv__(self, other: FastQuantity | float | int) -> FastQuantity:
109
+ if isinstance(other, int | float):
94
110
  return FastQuantity(self.value / other, self.unit)
95
111
 
96
112
  # Fast dimensional analysis using cached signatures
@@ -107,8 +123,8 @@ class FastQuantity:
107
123
 
108
124
  return FastQuantity(result_value, result_unit)
109
125
 
110
- def _find_result_unit_fast(self, result_dimension_sig: int,
111
- left_qty: 'FastQuantity', right_qty: 'FastQuantity') -> UnitConstant:
126
+ def _find_result_unit_fast(self, result_dimension_sig: int,
127
+ left_qty: FastQuantity, right_qty: FastQuantity) -> UnitConstant:
112
128
  """Ultra-fast unit finding using cached dimension signatures."""
113
129
 
114
130
  # Initialize dimension cache if empty
@@ -130,7 +146,7 @@ class FastQuantity:
130
146
  # For rare combined dimensions, create temporary unit
131
147
  temp_unit = UnitDefinition(
132
148
  name=f"combined_{result_dimension_sig}",
133
- symbol="combined",
149
+ symbol="combined",
134
150
  dimension=DimensionSignature(result_dimension_sig),
135
151
  si_factor=1.0
136
152
  )
@@ -140,13 +156,13 @@ class FastQuantity:
140
156
  registry._dimension_cache[result_dimension_sig] = result_unit
141
157
  return result_unit
142
158
 
143
- def _find_result_unit(self, result_dimension: DimensionSignature,
144
- left_qty: 'FastQuantity', right_qty: 'FastQuantity') -> UnitConstant:
159
+ def _find_result_unit(self, result_dimension: DimensionSignature,
160
+ left_qty: FastQuantity, right_qty: FastQuantity) -> UnitConstant:
145
161
  """Legacy method - kept for compatibility."""
146
162
  return self._find_result_unit_fast(result_dimension._signature, left_qty, right_qty)
147
163
 
148
164
  # Ultra-fast comparisons
149
- def __lt__(self, other: 'FastQuantity') -> bool:
165
+ def __lt__(self, other: FastQuantity) -> bool:
150
166
  if self._dimension_sig != other._dimension_sig:
151
167
  raise ValueError("Cannot compare incompatible dimensions")
152
168
 
@@ -172,7 +188,7 @@ class FastQuantity:
172
188
  other_value = other.value * other._si_factor / self._si_factor
173
189
  return abs(self.value - other_value) < 1e-10
174
190
 
175
- def to(self, target_unit: UnitConstant) -> 'FastQuantity':
191
+ def to(self, target_unit: UnitConstant) -> FastQuantity:
176
192
  """Ultra-fast unit conversion."""
177
193
  if self.unit == target_unit:
178
194
  return FastQuantity(self.value, target_unit)
@@ -183,16 +199,40 @@ class FastQuantity:
183
199
 
184
200
 
185
201
  class TypeSafeVariable(Generic[DimensionType]):
186
- """Type-safe variable with compile-time dimensional checking."""
202
+ """
203
+ Base class for type-safe variables with dimensional checking.
187
204
 
188
- def __init__(self, name: str, expected_dimension: DimensionSignature):
205
+ This is a simple data container without dependencies on expressions or equations.
206
+ Mathematical operations are added by subclasses or mixins.
207
+ """
208
+
209
+ # Class attribute defining which setter to use - subclasses can override
210
+ _setter_class = TypeSafeSetter
211
+
212
+ def __init__(self, name: str, expected_dimension, is_known: bool = True):
189
213
  self.name = name
214
+ self.symbol: str | None = None # Will be set by EngineeringProblem to attribute name
190
215
  self.expected_dimension = expected_dimension
191
- self.quantity: Optional[FastQuantity] = None
216
+ self.quantity: FastQuantity | None = None
217
+ self.is_known = is_known
218
+
219
+ def set(self, value: float):
220
+ """Create a setter for this variable using the class-specific setter type."""
221
+ return self._setter_class(self, value)
192
222
 
193
- def set(self, value: float) -> Union['TypeSafeSetter', 'LengthSetter', 'PressureSetter']:
194
- from .setters import TypeSafeSetter
195
- return TypeSafeSetter(self, value)
223
+ @property
224
+ def unknown(self) -> Self:
225
+ """Mark this variable as unknown using fluent API."""
226
+ self.is_known = False
227
+ return self
228
+
229
+ @property
230
+ def known(self) -> Self:
231
+ """Mark this variable as known using fluent API."""
232
+ self.is_known = True
233
+ return self
196
234
 
197
235
  def __str__(self):
198
- return f"{self.name}: {self.quantity}" if self.quantity else f"{self.name}: unset"
236
+ return f"{self.name}: {self.quantity}" if self.quantity else f"{self.name}: unset"
237
+
238
+
File without changes
@@ -0,0 +1,58 @@
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')
@@ -0,0 +1,68 @@
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)
@@ -0,0 +1,92 @@
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
+ # Handle special unit aliases
66
+ if unit == "in": # Handle Python reserved word
67
+ unit = "inchs" # Match the actual property name
68
+ elif unit == "inches": # Handle common plural form
69
+ unit = "inchs" # Match the actual property name
70
+
71
+ # Try to find the unit property on the setter
72
+ if hasattr(setter, unit):
73
+ getattr(setter, unit)
74
+ elif hasattr(setter, unit + 's'): # Handle singular/plural
75
+ getattr(setter, unit + 's')
76
+ elif self._default_unit_property and hasattr(setter, self._default_unit_property):
77
+ # Fall back to default unit
78
+ getattr(setter, self._default_unit_property)
79
+ else:
80
+ # Last resort - try to find any valid unit property
81
+ # This helps with forward compatibility
82
+ unit_properties = [attr for attr in dir(setter)
83
+ if not attr.startswith('_') and attr != 'value' and attr != 'variable']
84
+ if unit_properties:
85
+ getattr(setter, unit_properties[0])
86
+
87
+ else:
88
+ # More specific error messages matching test expectations
89
+ if self.__class__.__name__ == 'Dimensionless':
90
+ raise ValueError(f"{self.__class__.__name__} expects either 1 argument (name) or 2 arguments (value, name), got {len(args)}")
91
+ else:
92
+ raise ValueError(f"{self.__class__.__name__} expects either 1 argument (name) or 3 arguments (value, unit, name), got {len(args)}")