qnty 0.0.8__py3-none-any.whl → 0.0.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. qnty/__init__.py +140 -58
  2. qnty/_backup/problem_original.py +1251 -0
  3. qnty/_backup/quantity.py +63 -0
  4. qnty/codegen/cli.py +125 -0
  5. qnty/codegen/generators/data/unit_data.json +8807 -0
  6. qnty/codegen/generators/data_processor.py +345 -0
  7. qnty/codegen/generators/dimensions_gen.py +434 -0
  8. qnty/codegen/generators/doc_generator.py +141 -0
  9. qnty/codegen/generators/out/dimension_mapping.json +974 -0
  10. qnty/codegen/generators/out/dimension_metadata.json +123 -0
  11. qnty/codegen/generators/out/units_metadata.json +223 -0
  12. qnty/codegen/generators/quantities_gen.py +159 -0
  13. qnty/codegen/generators/setters_gen.py +178 -0
  14. qnty/codegen/generators/stubs_gen.py +167 -0
  15. qnty/codegen/generators/units_gen.py +295 -0
  16. qnty/codegen/generators/utils/__init__.py +0 -0
  17. qnty/equations/__init__.py +4 -0
  18. qnty/{equation.py → equations/equation.py} +78 -118
  19. qnty/equations/system.py +127 -0
  20. qnty/expressions/__init__.py +61 -0
  21. qnty/expressions/cache.py +94 -0
  22. qnty/expressions/functions.py +96 -0
  23. qnty/{expression.py → expressions/nodes.py} +209 -216
  24. qnty/generated/__init__.py +0 -0
  25. qnty/generated/dimensions.py +514 -0
  26. qnty/generated/quantities.py +6003 -0
  27. qnty/generated/quantities.pyi +4192 -0
  28. qnty/generated/setters.py +12210 -0
  29. qnty/generated/units.py +9798 -0
  30. qnty/problem/__init__.py +91 -0
  31. qnty/problem/base.py +142 -0
  32. qnty/problem/composition.py +385 -0
  33. qnty/problem/composition_mixin.py +382 -0
  34. qnty/problem/equations.py +413 -0
  35. qnty/problem/metaclass.py +302 -0
  36. qnty/problem/reconstruction.py +1016 -0
  37. qnty/problem/solving.py +180 -0
  38. qnty/problem/validation.py +64 -0
  39. qnty/problem/variables.py +239 -0
  40. qnty/quantities/__init__.py +6 -0
  41. qnty/quantities/expression_quantity.py +314 -0
  42. qnty/quantities/quantity.py +428 -0
  43. qnty/quantities/typed_quantity.py +215 -0
  44. qnty/solving/__init__.py +0 -0
  45. qnty/solving/manager.py +90 -0
  46. qnty/solving/order.py +355 -0
  47. qnty/solving/solvers/__init__.py +20 -0
  48. qnty/solving/solvers/base.py +92 -0
  49. qnty/solving/solvers/iterative.py +185 -0
  50. qnty/solving/solvers/simultaneous.py +547 -0
  51. qnty/units/__init__.py +0 -0
  52. qnty/{prefixes.py → units/prefixes.py} +54 -33
  53. qnty/{unit.py → units/registry.py} +73 -32
  54. qnty/utils/__init__.py +0 -0
  55. qnty/utils/logging.py +40 -0
  56. qnty/validation/__init__.py +0 -0
  57. qnty/validation/registry.py +0 -0
  58. qnty/validation/rules.py +167 -0
  59. qnty-0.0.9.dist-info/METADATA +199 -0
  60. qnty-0.0.9.dist-info/RECORD +63 -0
  61. qnty/dimension.py +0 -186
  62. qnty/unit_types/base.py +0 -47
  63. qnty/units.py +0 -8113
  64. qnty/variable.py +0 -300
  65. qnty/variable_types/base.py +0 -58
  66. qnty/variable_types/expression_variable.py +0 -106
  67. qnty/variable_types/typed_variable.py +0 -87
  68. qnty/variables.py +0 -2298
  69. qnty/variables.pyi +0 -6148
  70. qnty-0.0.8.dist-info/METADATA +0 -355
  71. qnty-0.0.8.dist-info/RECORD +0 -19
  72. /qnty/{unit_types → codegen}/__init__.py +0 -0
  73. /qnty/{variable_types → codegen/generators}/__init__.py +0 -0
  74. {qnty-0.0.8.dist-info → qnty-0.0.9.dist-info}/WHEEL +0 -0
@@ -0,0 +1,167 @@
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()
@@ -0,0 +1,295 @@
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()
File without changes
@@ -0,0 +1,4 @@
1
+ from .equation import Equation
2
+ from .system import EquationSystem
3
+
4
+ __all__ = ['Equation', 'EquationSystem']