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.
- qnty/__init__.py +2 -3
- qnty/constants/__init__.py +10 -0
- qnty/constants/numerical.py +18 -0
- qnty/constants/solvers.py +6 -0
- qnty/constants/tests.py +6 -0
- qnty/dimensions/__init__.py +23 -0
- qnty/dimensions/base.py +97 -0
- qnty/dimensions/field_dims.py +126 -0
- qnty/dimensions/field_dims.pyi +128 -0
- qnty/dimensions/signature.py +111 -0
- qnty/equations/__init__.py +1 -1
- qnty/equations/equation.py +118 -155
- qnty/equations/system.py +68 -65
- qnty/expressions/__init__.py +25 -46
- qnty/expressions/formatter.py +188 -0
- qnty/expressions/functions.py +46 -68
- qnty/expressions/nodes.py +539 -384
- qnty/expressions/types.py +70 -0
- qnty/problems/__init__.py +145 -0
- qnty/problems/composition.py +1031 -0
- qnty/problems/problem.py +695 -0
- qnty/problems/rules.py +145 -0
- qnty/problems/solving.py +1216 -0
- qnty/problems/validation.py +127 -0
- qnty/quantities/__init__.py +28 -5
- qnty/quantities/base_qnty.py +677 -0
- qnty/quantities/field_converters.py +24004 -0
- qnty/quantities/field_qnty.py +1012 -0
- qnty/{generated/setters.py → quantities/field_setter.py} +3071 -2961
- qnty/{generated/quantities.py → quantities/field_vars.py} +754 -432
- qnty/{generated/quantities.pyi → quantities/field_vars.pyi} +1289 -1290
- qnty/solving/manager.py +50 -44
- qnty/solving/order.py +181 -133
- qnty/solving/solvers/__init__.py +2 -9
- qnty/solving/solvers/base.py +27 -37
- qnty/solving/solvers/iterative.py +115 -135
- qnty/solving/solvers/simultaneous.py +93 -165
- qnty/units/__init__.py +1 -0
- qnty/{generated/units.py → units/field_units.py} +1700 -991
- qnty/units/field_units.pyi +2461 -0
- qnty/units/prefixes.py +58 -105
- qnty/units/registry.py +76 -89
- qnty/utils/__init__.py +16 -0
- qnty/utils/caching/__init__.py +23 -0
- qnty/utils/caching/manager.py +401 -0
- qnty/utils/error_handling/__init__.py +66 -0
- qnty/utils/error_handling/context.py +39 -0
- qnty/utils/error_handling/exceptions.py +96 -0
- qnty/utils/error_handling/handlers.py +171 -0
- qnty/utils/logging.py +4 -4
- qnty/utils/protocols.py +164 -0
- qnty/utils/scope_discovery.py +420 -0
- {qnty-0.0.9.dist-info → qnty-0.1.0.dist-info}/METADATA +1 -1
- qnty-0.1.0.dist-info/RECORD +60 -0
- qnty/_backup/problem_original.py +0 -1251
- qnty/_backup/quantity.py +0 -63
- qnty/codegen/cli.py +0 -125
- qnty/codegen/generators/data/unit_data.json +0 -8807
- qnty/codegen/generators/data_processor.py +0 -345
- qnty/codegen/generators/dimensions_gen.py +0 -434
- qnty/codegen/generators/doc_generator.py +0 -141
- qnty/codegen/generators/out/dimension_mapping.json +0 -974
- qnty/codegen/generators/out/dimension_metadata.json +0 -123
- qnty/codegen/generators/out/units_metadata.json +0 -223
- qnty/codegen/generators/quantities_gen.py +0 -159
- qnty/codegen/generators/setters_gen.py +0 -178
- qnty/codegen/generators/stubs_gen.py +0 -167
- qnty/codegen/generators/units_gen.py +0 -295
- qnty/expressions/cache.py +0 -94
- qnty/generated/dimensions.py +0 -514
- qnty/problem/__init__.py +0 -91
- qnty/problem/base.py +0 -142
- qnty/problem/composition.py +0 -385
- qnty/problem/composition_mixin.py +0 -382
- qnty/problem/equations.py +0 -413
- qnty/problem/metaclass.py +0 -302
- qnty/problem/reconstruction.py +0 -1016
- qnty/problem/solving.py +0 -180
- qnty/problem/validation.py +0 -64
- qnty/problem/variables.py +0 -239
- qnty/quantities/expression_quantity.py +0 -314
- qnty/quantities/quantity.py +0 -428
- qnty/quantities/typed_quantity.py +0 -215
- qnty/validation/__init__.py +0 -0
- qnty/validation/registry.py +0 -0
- qnty/validation/rules.py +0 -167
- qnty-0.0.9.dist-info/RECORD +0 -63
- /qnty/{codegen → extensions}/__init__.py +0 -0
- /qnty/{codegen/generators → extensions/integration}/__init__.py +0 -0
- /qnty/{codegen/generators/utils → extensions/plotting}/__init__.py +0 -0
- /qnty/{generated → extensions/reporting}/__init__.py +0 -0
- {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
|
qnty/problem/composition.py
DELETED
@@ -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)
|