qnty 0.0.9__py3-none-any.whl → 0.1.1__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 +540 -384
  18. qnty/expressions/types.py +70 -0
  19. qnty/problems/__init__.py +145 -0
  20. qnty/problems/composition.py +1101 -0
  21. qnty/problems/problem.py +737 -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} +829 -444
  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.1.dist-info}/METADATA +1 -1
  54. qnty-0.1.1.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.1.dist-info}/WHEEL +0 -0
@@ -1,167 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Script to generate comprehensive variables.pyi type stub file.
4
-
5
- This script generates complete type hints for all 105 variable types in the
6
- consolidated variables system, providing full IDE autocomplete and type checking
7
- support for the fluent API with dimension-specific unit properties.
8
-
9
- Uses the same source of truth as the consolidated variables system.
10
- """
11
-
12
- from pathlib import Path
13
-
14
- try:
15
- from .data_processor import (
16
- augment_with_prefixed_units,
17
- calculate_statistics,
18
- convert_to_class_name,
19
- get_dimension_constant_name,
20
- load_json_data,
21
- save_text_file,
22
- setup_import_path,
23
- )
24
- from .doc_generator import generate_class_docstring, generate_init_method, generate_set_method
25
- except ImportError:
26
- from .data_processor import augment_with_prefixed_units, calculate_statistics, convert_to_class_name, get_dimension_constant_name, load_json_data, save_text_file, setup_import_path
27
- from .doc_generator import generate_class_docstring, generate_init_method, generate_set_method
28
-
29
-
30
-
31
-
32
- def generate_quantities_pyi(parsed_data: dict, dimension_mapping: dict) -> str:
33
- """Generate the variables.pyi type stub content."""
34
-
35
- lines = [
36
- '"""',
37
- 'Type stubs for quantities module - Complete Edition.',
38
- '',
39
- 'Provides complete type hints for IDE autocomplete and type checking',
40
- 'for quantity classes and their setter relationships.',
41
- f'Contains {len([f for f in parsed_data.values() if f.get("units")])} quantity types with {sum(len(f.get("units", [])) for f in parsed_data.values())} total units.',
42
- '',
43
- 'Auto-generated from unit_data.json.',
44
- '"""',
45
- '',
46
- 'from typing import Any',
47
- '',
48
- 'from ..quantities.typed_quantity import TypedQuantity',
49
- 'from . import dimensions as dim',
50
- 'from . import setters as ts',
51
- ''
52
- ]
53
-
54
- # Generate type stubs for quantity classes only (setters are in separate file)
55
- sorted_fields = sorted(parsed_data.items())
56
- fields_with_units = [(k, v) for k, v in sorted_fields if v.get('units')]
57
-
58
- lines.extend([
59
- '# ===== QUANTITY CLASSES =====',
60
- '# Type stubs for quantity classes with setter relationships',
61
- ''
62
- ])
63
-
64
- for field_name, field_data in fields_with_units:
65
- class_name = convert_to_class_name(field_name)
66
- setter_name = f"{class_name}Setter"
67
- display_name = field_data.get('field', class_name).lower()
68
- dimension_constant = get_dimension_constant_name(field_name)
69
- units = field_data.get('units', [])
70
- is_dimensionless = class_name == 'Dimensionless'
71
-
72
- # Generate quantity class stub
73
- lines.append(f'class {class_name}(TypedQuantity):')
74
- lines.extend(generate_class_docstring(class_name, display_name, units, is_dimensionless))
75
- # Class attributes
76
- lines.append(' __slots__ = ()')
77
- lines.append(f' _setter_class = ts.{setter_name}')
78
- lines.append(f' _expected_dimension = dim.{dimension_constant}')
79
- lines.append(' ')
80
-
81
- # Generate __init__ method
82
- lines.extend(generate_init_method(class_name, display_name, is_dimensionless, stub_only=True))
83
-
84
- # Generate set method
85
- lines.append(' ')
86
- lines.extend(generate_set_method(setter_name, display_name, stub_only=True))
87
- lines.append(' ')
88
- lines.append('')
89
-
90
- lines.extend([
91
- '# All quantity classes are defined above',
92
- '# Setter classes are defined in setters.pyi',
93
- ])
94
-
95
- return '\n'.join(lines) + '\n'
96
-
97
-
98
- def main():
99
- """Main execution function."""
100
- # Setup import path and import prefixes
101
- setup_import_path()
102
-
103
- # Setup paths using pathlib
104
- base_path = Path(__file__).parents[4] # Go up to qnty root
105
- data_path = Path(__file__).parent / "data"
106
- output_path = Path(__file__).parent / "out"
107
- generated_path = base_path / "src" / "qnty" / "generated"
108
-
109
- # Ensure output directories exist
110
- output_path.mkdir(exist_ok=True)
111
- generated_path.mkdir(exist_ok=True)
112
-
113
- parsed_file = data_path / "unit_data.json"
114
- dimension_file = output_path / "dimension_mapping.json"
115
- output_file = generated_path / "quantities.pyi"
116
-
117
- print("Loading parsed units data for type stub generation...")
118
-
119
- # Load data using shared processor
120
- parsed_data = load_json_data(parsed_file)
121
- dimension_mapping = load_json_data(dimension_file) if dimension_file.exists() else {}
122
-
123
- print(f"Loaded {len(parsed_data)} fields with units")
124
-
125
- # Augment data with missing prefixed units using shared processor
126
- print("\nAugmenting data with missing prefixed units...")
127
- augmented_data, generated_count = augment_with_prefixed_units(parsed_data)
128
- print(f"Generated {generated_count} missing prefixed units for type stubs")
129
-
130
- # Count fields with units
131
- fields_with_units = sum(1 for field_data in augmented_data.values() if field_data.get('units'))
132
- print(f"Found {len(augmented_data)} total fields, {fields_with_units} fields with units")
133
-
134
- # Generate type stub file
135
- print("Generating quantities.pyi...")
136
- content = generate_quantities_pyi(augmented_data, dimension_mapping)
137
-
138
- # Write output file
139
- save_text_file(content, output_file)
140
- print(f"Generated type stub file: {output_file}")
141
-
142
- # Print statistics using shared calculator
143
- stats = calculate_statistics(augmented_data)
144
- lines_count = len(content.splitlines())
145
- print("\nStatistics:")
146
- print(f" Total fields: {stats['total_fields']}")
147
- print(f" Quantity types: {fields_with_units}")
148
- print(f" Original units: {stats['original_units']}")
149
- print(f" Total units (with prefixes): {stats['total_units']}")
150
- print(f" Generated prefixed units: {stats['generated_prefixed_units']}")
151
- print(f" Generated lines: {lines_count:,}")
152
-
153
- # Show top quantity types by unit count
154
- fields_by_units = [(field_name, len(field_data.get('units', [])), field_data.get('field', ''))
155
- for field_name, field_data in augmented_data.items() if field_data.get('units')]
156
- fields_by_units.sort(key=lambda x: x[1], reverse=True)
157
-
158
- print("\nTop quantity types by unit count:")
159
- for field_name, unit_count, display_name in fields_by_units[:10]:
160
- class_name = convert_to_class_name(field_name)
161
- print(f" {class_name:<25} : {unit_count:>3} units ({display_name})")
162
-
163
- print("\nQuantity type stubs generated with full IDE support!")
164
-
165
-
166
- if __name__ == "__main__":
167
- main()
@@ -1,295 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Units generator for qnty library.
4
-
5
- This script generates the units.py file from unit_data.json, creating
6
- comprehensive unit definitions organized by dimensional groups.
7
- """
8
-
9
- import json
10
- from pathlib import Path
11
- from typing import Any
12
-
13
- try:
14
- from .data_processor import (
15
- augment_with_prefixed_units,
16
- convert_to_class_name,
17
- escape_string,
18
- get_dimension_constant_name,
19
- get_unit_names_and_aliases,
20
- is_valid_python_identifier,
21
- load_unit_data,
22
- setup_import_path,
23
- )
24
- except ImportError:
25
- from .data_processor import (
26
- augment_with_prefixed_units,
27
- convert_to_class_name,
28
- escape_string,
29
- get_dimension_constant_name,
30
- get_unit_names_and_aliases,
31
- is_valid_python_identifier,
32
- load_unit_data,
33
- setup_import_path,
34
- )
35
-
36
-
37
- class UnitsGenerator:
38
- """Generator for units.py file."""
39
-
40
- def __init__(self, data_path: Path, output_path: Path, out_dir: Path):
41
- """Initialize with paths."""
42
- self.data_path = data_path
43
- self.output_path = output_path
44
- self.out_dir = out_dir
45
- self.out_dir.mkdir(parents=True, exist_ok=True)
46
-
47
- # Setup import path and load data
48
- setup_import_path()
49
- raw_unit_data = load_unit_data(self.data_path)
50
-
51
- # Augment with prefixed units using shared processor
52
- self.unit_data, generated_count = augment_with_prefixed_units(raw_unit_data)
53
- print(f"Generated {generated_count} prefixed units")
54
-
55
- # Track generated info
56
- self.dimension_constants: set[str] = set()
57
- self.field_to_class_mapping: dict[str, str] = {}
58
-
59
-
60
- def get_class_name(self, field_name: str) -> str:
61
- """Convert field name to class name."""
62
- # Use shared utility and add 'Units' suffix
63
- class_name = convert_to_class_name(field_name) + 'Units'
64
- self.field_to_class_mapping[field_name] = class_name
65
- return class_name
66
-
67
- def get_dimension_constant_name(self, field_name: str) -> str:
68
- """Get the dimension constant name for a field."""
69
- constant_name = get_dimension_constant_name(field_name)
70
- self.dimension_constants.add(constant_name)
71
- return constant_name
72
-
73
-
74
- def generate_header(self) -> list[str]:
75
- """Generate file header with imports."""
76
- lines = [
77
- '"""',
78
- 'Comprehensive Units Module',
79
- '==========================',
80
- '',
81
- 'Auto-generated unit definitions for all engineering units.',
82
- f'Contains {sum(len(field_data["units"]) for field_data in self.unit_data.values())} units',
83
- f'across {len(self.unit_data)} fields.',
84
- '',
85
- 'This file is auto-generated by codegen/generators/units_gen.py',
86
- 'DO NOT EDIT MANUALLY - changes will be overwritten.',
87
- '"""',
88
- '',
89
- ]
90
-
91
- lines.extend([
92
- 'from ..units.registry import UnitConstant, UnitDefinition',
93
- 'from . import dimensions as dim',
94
- '',
95
- '',
96
- ])
97
-
98
- return lines
99
-
100
- def generate_unit_class(self, field_name: str, field_data: dict[str, Any]) -> list[str]:
101
- """Generate a unit class for a field."""
102
- class_name = self.get_class_name(field_name)
103
- dimension_constant = self.get_dimension_constant_name(field_name)
104
-
105
- lines = [
106
- f'class {class_name}:',
107
- f' """Unit constants for {field_data["field"]}."""',
108
- ' __slots__ = ()',
109
- '',
110
- ]
111
-
112
- # Process all units for this field
113
- units = field_data.get('units', [])
114
- if not units:
115
- lines.extend([' pass', '', ''])
116
- return lines
117
-
118
- # Generate unit constants using shared name processing
119
- for unit_data in units:
120
- primary_name, aliases = get_unit_names_and_aliases(unit_data)
121
- if not is_valid_python_identifier(primary_name):
122
- continue
123
-
124
- full_name = unit_data.get('name', '')
125
- symbol = unit_data.get('notation', '')
126
- si_factor = unit_data.get('si_conversion', 1.0)
127
-
128
- lines.extend([
129
- f' # {full_name}',
130
- f' {primary_name} = UnitConstant(UnitDefinition(',
131
- f' name="{escape_string(primary_name)}",',
132
- f' symbol="{escape_string(symbol)}",',
133
- f' dimension=dim.{dimension_constant},',
134
- f' si_factor={si_factor},',
135
- ' si_offset=0.0',
136
- ' ))',
137
- '',
138
- ])
139
-
140
- # Add aliases as class attributes using shared processing
141
- for alias in aliases:
142
- if is_valid_python_identifier(alias):
143
- lines.append(f' {alias} = {primary_name}')
144
-
145
- lines.append('')
146
- return lines
147
-
148
- def generate_registry_function(self) -> list[str]:
149
- """Generate function to register all units with registry."""
150
- lines = [
151
- 'def register_all_units(registry) -> None:',
152
- ' """Register all unit definitions with the registry."""',
153
- ' unit_classes = [',
154
- ]
155
-
156
- # Add all generated unit classes
157
- for class_name in sorted(self.field_to_class_mapping.values()):
158
- lines.append(f' {class_name},')
159
-
160
- lines.extend([
161
- ' ]',
162
- '',
163
- ' for unit_class in unit_classes:',
164
- ' for attr_name in dir(unit_class):',
165
- ' if not attr_name.startswith("_"):',
166
- ' unit_constant = getattr(unit_class, attr_name, None)',
167
- ' if unit_constant is not None and hasattr(unit_constant, "definition"):',
168
- ' unit_def = unit_constant.definition',
169
- ' if unit_def.name not in registry.units:',
170
- ' registry.register_unit(unit_def)',
171
- '',
172
- ' # Finalize registry',
173
- ' registry.finalize_registration()',
174
- '',
175
- '',
176
- ])
177
-
178
- return lines
179
-
180
- def generate_dimensionless_class(self) -> list[str]:
181
- """Generate DimensionlessUnits class for backward compatibility."""
182
- return [
183
- '# Backward compatibility class',
184
- 'class DimensionlessUnits:',
185
- ' """Dimensionless units for backward compatibility."""',
186
- ' __slots__ = ()',
187
- '',
188
- ' dimensionless = UnitConstant(UnitDefinition(',
189
- ' name="dimensionless",',
190
- ' symbol="",',
191
- ' dimension=dim.DIMENSIONLESS,',
192
- ' si_factor=1.0,',
193
- ' si_offset=0.0',
194
- ' ))',
195
- '',
196
- '',
197
- ]
198
-
199
- def generate_exports(self) -> list[str]:
200
- """Generate __all__ export list."""
201
- lines = [
202
- '# Export list',
203
- '__all__ = [',
204
- ' "register_all_units",',
205
- ' "DimensionlessUnits",',
206
- ]
207
-
208
- for class_name in sorted(self.field_to_class_mapping.values()):
209
- lines.append(f' "{class_name}",')
210
-
211
- lines.extend([
212
- ']',
213
- '',
214
- ])
215
-
216
- return lines
217
-
218
- def generate_statistics(self) -> list[str]:
219
- """Generate statistics section."""
220
- total_units = sum(len(field_data.get('units', [])) for field_data in self.unit_data.values())
221
-
222
- return [
223
- '# Statistics',
224
- f'TOTAL_UNITS = {total_units}',
225
- f'TOTAL_FIELDS = {len(self.unit_data)}',
226
- f'TOTAL_DIMENSIONS = {len(self.dimension_constants)}',
227
- ]
228
-
229
- def generate(self) -> None:
230
- """Generate the complete units.py file."""
231
- # First pass: collect all dimension constants
232
- for field_name in self.unit_data.keys():
233
- self.get_dimension_constant_name(field_name)
234
- self.get_class_name(field_name)
235
-
236
- # Build file content
237
- lines = []
238
- lines.extend(self.generate_header())
239
-
240
- # Generate unit classes for each field
241
- for field_name, field_data in sorted(self.unit_data.items()):
242
- lines.extend(self.generate_unit_class(field_name, field_data))
243
-
244
- lines.extend(self.generate_registry_function())
245
- lines.extend(self.generate_exports())
246
- lines.extend(self.generate_statistics())
247
-
248
- # Write the file
249
- content = '\n'.join(lines) + '\n'
250
- self.output_path.write_text(content, encoding='utf-8')
251
- print(f"Generated {self.output_path}")
252
-
253
- # Save metadata
254
- metadata = {
255
- 'total_units': sum(len(field_data.get('units', [])) for field_data in self.unit_data.values()),
256
- 'total_fields': len(self.unit_data),
257
- 'total_dimensions': len(self.dimension_constants),
258
- 'dimension_constants': sorted(self.dimension_constants),
259
- 'unit_classes': sorted(self.field_to_class_mapping.values()),
260
- }
261
-
262
- metadata_path = self.out_dir / 'units_metadata.json'
263
- with open(metadata_path, 'w', encoding='utf-8') as f:
264
- json.dump(metadata, f, indent=2)
265
- print(f"Saved metadata to {metadata_path}")
266
-
267
-
268
- def main() -> None:
269
- """Main entry point."""
270
- # Set up paths
271
- generator_dir = Path(__file__).parent
272
- data_path = generator_dir / 'data' / 'unit_data.json'
273
- output_path = generator_dir.parent.parent / 'generated' / 'units.py'
274
- out_dir = generator_dir / 'out'
275
-
276
- # Create output directory if needed
277
- output_path.parent.mkdir(parents=True, exist_ok=True)
278
-
279
- # Check if data file exists
280
- if not data_path.exists():
281
- print(f"Error: Data file not found at {data_path}")
282
- return
283
-
284
- # Run generator
285
- generator = UnitsGenerator(data_path, output_path, out_dir)
286
- generator.generate()
287
-
288
- print("\nUnits generation complete!")
289
- print(f" - Total units: {sum(len(field_data.get('units', [])) for field_data in generator.unit_data.values())}")
290
- print(f" - Total fields: {len(generator.unit_data)}")
291
- print(f" - Unit classes: {len(generator.field_to_class_mapping)}")
292
-
293
-
294
- if __name__ == "__main__":
295
- main()
qnty/expressions/cache.py DELETED
@@ -1,94 +0,0 @@
1
- """
2
- Expression Caching System
3
- ========================
4
-
5
- Caching infrastructure for optimized expression evaluation and type checking.
6
- """
7
-
8
- from typing import TYPE_CHECKING, Union
9
-
10
- if TYPE_CHECKING:
11
- from ..quantities.quantity import Quantity, TypeSafeVariable
12
- from .nodes import Expression
13
-
14
- # Import here to avoid circular imports - delayed imports
15
- from ..generated.units import DimensionlessUnits
16
- from ..quantities.quantity import Quantity, TypeSafeVariable
17
-
18
- # Cache for common types to avoid repeated type checks
19
- _NUMERIC_TYPES = (int, float)
20
- _DIMENSIONLESS_CONSTANT = None
21
- _CACHED_DIMENSIONLESS_QUANTITIES = {} # Cache for common numeric values
22
- _MAX_CACHE_SIZE = 50 # Limit cache size to prevent memory bloat
23
- _TYPE_CHECK_CACHE = {} # Cache for expensive isinstance checks
24
-
25
- # Expression evaluation cache for repeated operations
26
- _EXPRESSION_RESULT_CACHE = {}
27
- _MAX_EXPRESSION_CACHE_SIZE = 200
28
-
29
-
30
- def _get_cached_dimensionless():
31
- """Get cached dimensionless constant for numeric values."""
32
- global _DIMENSIONLESS_CONSTANT
33
- if _DIMENSIONLESS_CONSTANT is None:
34
- _DIMENSIONLESS_CONSTANT = DimensionlessUnits.dimensionless
35
- return _DIMENSIONLESS_CONSTANT
36
-
37
-
38
- def _get_dimensionless_quantity(value: float) -> 'Quantity':
39
- """Get cached dimensionless quantity for common numeric values."""
40
- if value in _CACHED_DIMENSIONLESS_QUANTITIES:
41
- return _CACHED_DIMENSIONLESS_QUANTITIES[value]
42
-
43
- # Cache common values with size limit
44
- if len(_CACHED_DIMENSIONLESS_QUANTITIES) < _MAX_CACHE_SIZE and -10 <= value <= 10:
45
- qty = Quantity(value, _get_cached_dimensionless())
46
- _CACHED_DIMENSIONLESS_QUANTITIES[value] = qty
47
- return qty
48
-
49
- # Don't cache uncommon values
50
- return Quantity(value, _get_cached_dimensionless())
51
-
52
-
53
- def _is_numeric_type(obj) -> bool:
54
- """Cached type check for numeric types."""
55
- obj_type = type(obj)
56
- if obj_type not in _TYPE_CHECK_CACHE:
57
- _TYPE_CHECK_CACHE[obj_type] = obj_type in _NUMERIC_TYPES
58
- return _TYPE_CHECK_CACHE[obj_type]
59
-
60
-
61
- def wrap_operand(operand: Union['Expression', 'TypeSafeVariable', 'Quantity', int, float]) -> 'Expression':
62
- """
63
- Optimized operand wrapping with cached type checks.
64
-
65
- This function uses cached type checks for maximum performance.
66
- """
67
- # Import Expression classes to avoid circular imports
68
- from .nodes import Constant, Expression, VariableReference
69
-
70
- # Fast path: check most common cases first using cached type check
71
- if _is_numeric_type(operand):
72
- # operand is guaranteed to be int or float at this point
73
- return Constant(_get_dimensionless_quantity(float(operand))) # type: ignore[arg-type]
74
-
75
- # Check if already an Expression (using isinstance for speed)
76
- if isinstance(operand, Expression):
77
- return operand
78
-
79
- # Check for FastQuantity
80
- if isinstance(operand, Quantity):
81
- return Constant(operand)
82
-
83
- # Check for TypeSafeVariable
84
- if isinstance(operand, TypeSafeVariable):
85
- return VariableReference(operand)
86
-
87
- # Check for ConfigurableVariable (from composition system)
88
- if hasattr(operand, '_variable'):
89
- var = getattr(operand, '_variable', None)
90
- if isinstance(var, TypeSafeVariable):
91
- return VariableReference(var)
92
-
93
- # No duck typing - fail fast for unknown types
94
- raise TypeError(f"Cannot convert {type(operand)} to Expression")