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.
- qnty/__init__.py +140 -58
- qnty/_backup/problem_original.py +1251 -0
- qnty/_backup/quantity.py +63 -0
- qnty/codegen/cli.py +125 -0
- qnty/codegen/generators/data/unit_data.json +8807 -0
- qnty/codegen/generators/data_processor.py +345 -0
- qnty/codegen/generators/dimensions_gen.py +434 -0
- qnty/codegen/generators/doc_generator.py +141 -0
- qnty/codegen/generators/out/dimension_mapping.json +974 -0
- qnty/codegen/generators/out/dimension_metadata.json +123 -0
- qnty/codegen/generators/out/units_metadata.json +223 -0
- qnty/codegen/generators/quantities_gen.py +159 -0
- qnty/codegen/generators/setters_gen.py +178 -0
- qnty/codegen/generators/stubs_gen.py +167 -0
- qnty/codegen/generators/units_gen.py +295 -0
- qnty/codegen/generators/utils/__init__.py +0 -0
- qnty/equations/__init__.py +4 -0
- qnty/{equation.py → equations/equation.py} +78 -118
- qnty/equations/system.py +127 -0
- qnty/expressions/__init__.py +61 -0
- qnty/expressions/cache.py +94 -0
- qnty/expressions/functions.py +96 -0
- qnty/{expression.py → expressions/nodes.py} +209 -216
- qnty/generated/__init__.py +0 -0
- qnty/generated/dimensions.py +514 -0
- qnty/generated/quantities.py +6003 -0
- qnty/generated/quantities.pyi +4192 -0
- qnty/generated/setters.py +12210 -0
- qnty/generated/units.py +9798 -0
- qnty/problem/__init__.py +91 -0
- qnty/problem/base.py +142 -0
- qnty/problem/composition.py +385 -0
- qnty/problem/composition_mixin.py +382 -0
- qnty/problem/equations.py +413 -0
- qnty/problem/metaclass.py +302 -0
- qnty/problem/reconstruction.py +1016 -0
- qnty/problem/solving.py +180 -0
- qnty/problem/validation.py +64 -0
- qnty/problem/variables.py +239 -0
- qnty/quantities/__init__.py +6 -0
- qnty/quantities/expression_quantity.py +314 -0
- qnty/quantities/quantity.py +428 -0
- qnty/quantities/typed_quantity.py +215 -0
- qnty/solving/__init__.py +0 -0
- qnty/solving/manager.py +90 -0
- qnty/solving/order.py +355 -0
- qnty/solving/solvers/__init__.py +20 -0
- qnty/solving/solvers/base.py +92 -0
- qnty/solving/solvers/iterative.py +185 -0
- qnty/solving/solvers/simultaneous.py +547 -0
- qnty/units/__init__.py +0 -0
- qnty/{prefixes.py → units/prefixes.py} +54 -33
- qnty/{unit.py → units/registry.py} +73 -32
- qnty/utils/__init__.py +0 -0
- qnty/utils/logging.py +40 -0
- qnty/validation/__init__.py +0 -0
- qnty/validation/registry.py +0 -0
- qnty/validation/rules.py +167 -0
- qnty-0.0.9.dist-info/METADATA +199 -0
- qnty-0.0.9.dist-info/RECORD +63 -0
- qnty/dimension.py +0 -186
- qnty/unit_types/base.py +0 -47
- qnty/units.py +0 -8113
- qnty/variable.py +0 -300
- qnty/variable_types/base.py +0 -58
- qnty/variable_types/expression_variable.py +0 -106
- qnty/variable_types/typed_variable.py +0 -87
- qnty/variables.py +0 -2298
- qnty/variables.pyi +0 -6148
- qnty-0.0.8.dist-info/METADATA +0 -355
- qnty-0.0.8.dist-info/RECORD +0 -19
- /qnty/{unit_types → codegen}/__init__.py +0 -0
- /qnty/{variable_types → codegen/generators}/__init__.py +0 -0
- {qnty-0.0.8.dist-info → qnty-0.0.9.dist-info}/WHEEL +0 -0
@@ -0,0 +1,434 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Dimension generator for qnty library.
|
4
|
+
|
5
|
+
This script generates the dimensions.py file from unit_data.json, creating
|
6
|
+
all necessary dimension signatures programmatically without hardcoding.
|
7
|
+
"""
|
8
|
+
|
9
|
+
import json
|
10
|
+
from pathlib import Path
|
11
|
+
from typing import Any
|
12
|
+
|
13
|
+
# Configuration
|
14
|
+
BASE_DIMENSIONS = {
|
15
|
+
'LENGTH': {'prime': 2, 'params': {'length': 1}},
|
16
|
+
'MASS': {'prime': 3, 'params': {'mass': 1}},
|
17
|
+
'TIME': {'prime': 5, 'params': {'time': 1}},
|
18
|
+
'CURRENT': {'prime': 7, 'params': {'current': 1}},
|
19
|
+
'TEMPERATURE': {'prime': 11, 'params': {'temp': 1}},
|
20
|
+
'AMOUNT': {'prime': 13, 'params': {'amount': 1}},
|
21
|
+
'LUMINOSITY': {'prime': 17, 'params': {'luminosity': 1}},
|
22
|
+
'DIMENSIONLESS': {'prime': 1, 'params': {}},
|
23
|
+
}
|
24
|
+
|
25
|
+
DIMENSION_SYMBOLS = {
|
26
|
+
'length': 'L',
|
27
|
+
'mass': 'M',
|
28
|
+
'time': 'T',
|
29
|
+
'current': 'A',
|
30
|
+
'temp': 'Θ',
|
31
|
+
'amount': 'N',
|
32
|
+
'luminosity': 'J',
|
33
|
+
}
|
34
|
+
|
35
|
+
# All dimensions will be explicitly defined - no lazy loading needed
|
36
|
+
|
37
|
+
|
38
|
+
class DimensionGenerator:
|
39
|
+
"""Generator for dimensions.py file."""
|
40
|
+
|
41
|
+
def __init__(self, data_path: Path, output_path: Path, out_dir: Path):
|
42
|
+
"""Initialize with paths."""
|
43
|
+
self.data_path = data_path
|
44
|
+
self.output_path = output_path
|
45
|
+
self.out_dir = out_dir
|
46
|
+
self.out_dir.mkdir(parents=True, exist_ok=True)
|
47
|
+
|
48
|
+
# Load unit data
|
49
|
+
with open(self.data_path, encoding='utf-8') as f:
|
50
|
+
self.unit_data: dict[str, Any] = json.load(f)
|
51
|
+
|
52
|
+
# Track all discovered dimensions
|
53
|
+
self.all_dimensions: dict[str, dict[str, int]] = {}
|
54
|
+
self.common_signatures: dict[tuple[int, ...], float] = {} # For cache optimization
|
55
|
+
|
56
|
+
def calculate_signature(self, dims: dict[str, int]) -> float:
|
57
|
+
"""Calculate prime factorization signature for dimensions."""
|
58
|
+
if not dims:
|
59
|
+
return 1.0
|
60
|
+
|
61
|
+
signature = 1.0
|
62
|
+
prime_map = {
|
63
|
+
'length': 2,
|
64
|
+
'mass': 3,
|
65
|
+
'time': 5,
|
66
|
+
'current': 7,
|
67
|
+
'temp': 11,
|
68
|
+
'amount': 13,
|
69
|
+
'luminosity': 17,
|
70
|
+
}
|
71
|
+
|
72
|
+
for dim_name, power in dims.items():
|
73
|
+
if power != 0 and dim_name in prime_map:
|
74
|
+
signature *= prime_map[dim_name] ** power
|
75
|
+
|
76
|
+
return signature
|
77
|
+
|
78
|
+
def tuple_from_dims(self, dims: dict[str, int]) -> tuple[int, ...]:
|
79
|
+
"""Create ordered tuple for dimension cache key."""
|
80
|
+
return (
|
81
|
+
dims.get('length', 0),
|
82
|
+
dims.get('mass', 0),
|
83
|
+
dims.get('time', 0),
|
84
|
+
dims.get('current', 0),
|
85
|
+
dims.get('temp', 0),
|
86
|
+
dims.get('amount', 0),
|
87
|
+
dims.get('luminosity', 0),
|
88
|
+
)
|
89
|
+
|
90
|
+
def format_dimension_comment(self, dims: dict[str, int]) -> str:
|
91
|
+
"""Create readable comment for a dimension."""
|
92
|
+
if not dims:
|
93
|
+
return "Dimensionless"
|
94
|
+
|
95
|
+
parts = []
|
96
|
+
for dim, power in sorted(dims.items()):
|
97
|
+
if power != 0:
|
98
|
+
symbol = DIMENSION_SYMBOLS.get(dim, dim[0].upper())
|
99
|
+
if power == 1:
|
100
|
+
parts.append(symbol)
|
101
|
+
else:
|
102
|
+
parts.append(f"{symbol}^{power}")
|
103
|
+
|
104
|
+
return ' '.join(parts) if parts else "Dimensionless"
|
105
|
+
|
106
|
+
def extract_all_dimensions(self) -> None:
|
107
|
+
"""Extract all unique dimensions from unit data."""
|
108
|
+
# Add base dimensions
|
109
|
+
for name, config in BASE_DIMENSIONS.items():
|
110
|
+
self.all_dimensions[name] = config['params']
|
111
|
+
|
112
|
+
# Extract from unit data
|
113
|
+
for field_name, field_data in self.unit_data.items():
|
114
|
+
dims = field_data.get('dimensions', {})
|
115
|
+
name = field_name.upper()
|
116
|
+
|
117
|
+
# Skip if it's a base dimension
|
118
|
+
if name not in BASE_DIMENSIONS:
|
119
|
+
self.all_dimensions[name] = dims
|
120
|
+
|
121
|
+
# Build common signatures cache (programmatically)
|
122
|
+
for _name, dims in self.all_dimensions.items():
|
123
|
+
dim_tuple = self.tuple_from_dims(dims)
|
124
|
+
signature = self.calculate_signature(dims)
|
125
|
+
self.common_signatures[dim_tuple] = signature
|
126
|
+
|
127
|
+
def generate_common_signatures_dict(self) -> list[str]:
|
128
|
+
"""Generate the _COMMON_SIGNATURES dictionary programmatically."""
|
129
|
+
lines = []
|
130
|
+
lines.append(" # Pre-computed signature cache for common dimensions")
|
131
|
+
lines.append(" _COMMON_SIGNATURES: ClassVar[dict[tuple[int, ...], int | float]] = {")
|
132
|
+
|
133
|
+
# Sort by complexity (number of non-zero dimensions) and then alphabetically
|
134
|
+
sorted_sigs = sorted(self.common_signatures.items(),
|
135
|
+
key=lambda x: (sum(abs(v) for v in x[0]), x[0]))
|
136
|
+
|
137
|
+
# Limit to most common ones to avoid huge cache
|
138
|
+
max_cache_entries = 50
|
139
|
+
for dim_tuple, signature in sorted_sigs[:max_cache_entries]:
|
140
|
+
# Find dimensions with this signature for comment
|
141
|
+
matching_dims = [name for name, dims in self.all_dimensions.items()
|
142
|
+
if self.tuple_from_dims(dims) == dim_tuple]
|
143
|
+
|
144
|
+
if matching_dims:
|
145
|
+
# Pick the shortest/most descriptive name
|
146
|
+
best_name = min(matching_dims, key=len)
|
147
|
+
dims = self.all_dimensions[best_name]
|
148
|
+
comment = self.format_dimension_comment(dims)
|
149
|
+
|
150
|
+
# Format the signature value nicely
|
151
|
+
if signature == int(signature):
|
152
|
+
sig_str = str(int(signature))
|
153
|
+
else:
|
154
|
+
sig_str = f"{signature:.10g}" # Use general format to avoid long decimals
|
155
|
+
|
156
|
+
lines.append(f" {dim_tuple}: {sig_str}, # {comment}")
|
157
|
+
|
158
|
+
lines.append(" }")
|
159
|
+
|
160
|
+
return lines
|
161
|
+
|
162
|
+
def generate_header(self) -> list[str]:
|
163
|
+
"""Generate the file header."""
|
164
|
+
lines = [
|
165
|
+
'"""',
|
166
|
+
'Dimension System',
|
167
|
+
'================',
|
168
|
+
'',
|
169
|
+
'Compile-time dimensional analysis using type system for ultra-fast operations.',
|
170
|
+
'',
|
171
|
+
'This file is auto-generated by codegen/generators/dimensions_gen.py',
|
172
|
+
'DO NOT EDIT MANUALLY - changes will be overwritten.',
|
173
|
+
'"""',
|
174
|
+
'',
|
175
|
+
'from dataclasses import dataclass',
|
176
|
+
'from enum import IntEnum',
|
177
|
+
'from typing import ClassVar, final',
|
178
|
+
'',
|
179
|
+
'',
|
180
|
+
]
|
181
|
+
return lines
|
182
|
+
|
183
|
+
def generate_base_dimension_class(self) -> list[str]:
|
184
|
+
"""Generate the BaseDimension IntEnum."""
|
185
|
+
lines = [
|
186
|
+
'class BaseDimension(IntEnum):',
|
187
|
+
' """Base dimensions as prime numbers for efficient bit operations."""',
|
188
|
+
]
|
189
|
+
|
190
|
+
for name, config in BASE_DIMENSIONS.items():
|
191
|
+
if name == 'DIMENSIONLESS':
|
192
|
+
lines.append(f" {name} = {config['prime']} # Must be 1 to act as multiplicative identity")
|
193
|
+
else:
|
194
|
+
lines.append(f" {name} = {config['prime']}")
|
195
|
+
|
196
|
+
lines.extend(['', ''])
|
197
|
+
return lines
|
198
|
+
|
199
|
+
def generate_dimension_signature_class(self) -> list[str]:
|
200
|
+
"""Generate the DimensionSignature class."""
|
201
|
+
lines = [
|
202
|
+
'@final',
|
203
|
+
'@dataclass(frozen=True, slots=True)',
|
204
|
+
'class DimensionSignature:',
|
205
|
+
' """Immutable dimension signature for zero-cost dimensional analysis."""',
|
206
|
+
' ',
|
207
|
+
' # Store as bit pattern for ultra-fast comparison',
|
208
|
+
' _signature: int | float = 1',
|
209
|
+
' ',
|
210
|
+
]
|
211
|
+
|
212
|
+
# Add common signatures cache
|
213
|
+
lines.extend(self.generate_common_signatures_dict())
|
214
|
+
|
215
|
+
lines.extend([
|
216
|
+
' ',
|
217
|
+
' # Instance cache for interning common dimensions',
|
218
|
+
' _INSTANCE_CACHE: ClassVar[dict[int | float, "DimensionSignature"]] = {}',
|
219
|
+
' ',
|
220
|
+
' def __new__(cls, signature: int | float = 1):',
|
221
|
+
' """Optimized constructor with instance interning."""',
|
222
|
+
' if signature in cls._INSTANCE_CACHE:',
|
223
|
+
' return cls._INSTANCE_CACHE[signature]',
|
224
|
+
' ',
|
225
|
+
' instance = object.__new__(cls)',
|
226
|
+
' ',
|
227
|
+
' # Cache common signatures',
|
228
|
+
' if len(cls._INSTANCE_CACHE) < 100: # Limit cache size',
|
229
|
+
' cls._INSTANCE_CACHE[signature] = instance',
|
230
|
+
' ',
|
231
|
+
' return instance',
|
232
|
+
' ',
|
233
|
+
' @classmethod',
|
234
|
+
' def create(cls, length=0, mass=0, time=0, current=0, temp=0, amount=0, luminosity=0):',
|
235
|
+
' """Create dimension from exponents with optimized lookup."""',
|
236
|
+
' # Check cache first',
|
237
|
+
' key = (length, mass, time, current, temp, amount, luminosity)',
|
238
|
+
' if key in cls._COMMON_SIGNATURES:',
|
239
|
+
' return cls(cls._COMMON_SIGNATURES[key])',
|
240
|
+
' ',
|
241
|
+
' # Fast path for dimensionless',
|
242
|
+
' if not any([length, mass, time, current, temp, amount, luminosity]):',
|
243
|
+
' return cls(1)',
|
244
|
+
' ',
|
245
|
+
' # Compute signature',
|
246
|
+
' signature = 1',
|
247
|
+
' if length != 0:',
|
248
|
+
' signature *= BaseDimension.LENGTH ** length',
|
249
|
+
' if mass != 0:',
|
250
|
+
' signature *= BaseDimension.MASS ** mass',
|
251
|
+
' if time != 0:',
|
252
|
+
' signature *= BaseDimension.TIME ** time',
|
253
|
+
' if current != 0:',
|
254
|
+
' signature *= BaseDimension.CURRENT ** current',
|
255
|
+
' if temp != 0:',
|
256
|
+
' signature *= BaseDimension.TEMPERATURE ** temp',
|
257
|
+
' if amount != 0:',
|
258
|
+
' signature *= BaseDimension.AMOUNT ** amount',
|
259
|
+
' if luminosity != 0:',
|
260
|
+
' signature *= BaseDimension.LUMINOSITY ** luminosity',
|
261
|
+
' ',
|
262
|
+
' return cls(signature)',
|
263
|
+
' ',
|
264
|
+
' def __mul__(self, other):',
|
265
|
+
' """Multiply dimensions."""',
|
266
|
+
' return DimensionSignature(self._signature * other._signature)',
|
267
|
+
' ',
|
268
|
+
' def __truediv__(self, other):',
|
269
|
+
' """Divide dimensions."""',
|
270
|
+
' return DimensionSignature(self._signature / other._signature)',
|
271
|
+
' ',
|
272
|
+
' def __pow__(self, power):',
|
273
|
+
' """Raise dimension to a power."""',
|
274
|
+
' if power == 1:',
|
275
|
+
' return self',
|
276
|
+
' if power == 0:',
|
277
|
+
' return DimensionSignature(1)',
|
278
|
+
' return DimensionSignature(self._signature ** power)',
|
279
|
+
' ',
|
280
|
+
' def is_compatible(self, other):',
|
281
|
+
' """Check dimensional compatibility."""',
|
282
|
+
' return self._signature == other._signature',
|
283
|
+
' ',
|
284
|
+
' def __eq__(self, other):',
|
285
|
+
' """Check equality."""',
|
286
|
+
' if self is other:',
|
287
|
+
' return True',
|
288
|
+
' return isinstance(other, DimensionSignature) and self._signature == other._signature',
|
289
|
+
' ',
|
290
|
+
' def __hash__(self):',
|
291
|
+
' """Hash based on signature."""',
|
292
|
+
' return hash(self._signature)',
|
293
|
+
'',
|
294
|
+
'',
|
295
|
+
])
|
296
|
+
|
297
|
+
return lines
|
298
|
+
|
299
|
+
def generate_dimension_constants(self) -> list[str]:
|
300
|
+
"""Generate dimension constant definitions."""
|
301
|
+
lines = []
|
302
|
+
|
303
|
+
# Generate signature lookup dictionary
|
304
|
+
lines.append('# Pre-computed dimension signatures for all dimensions')
|
305
|
+
lines.append('_DIMENSION_SIGNATURES = {')
|
306
|
+
|
307
|
+
for name in sorted(self.all_dimensions.keys()):
|
308
|
+
dims = self.all_dimensions[name]
|
309
|
+
signature = self.calculate_signature(dims)
|
310
|
+
comment = self.format_dimension_comment(dims)
|
311
|
+
|
312
|
+
if signature == int(signature):
|
313
|
+
sig_str = str(int(signature))
|
314
|
+
else:
|
315
|
+
sig_str = f"{signature:.10g}"
|
316
|
+
|
317
|
+
lines.append(f' "{name}": {sig_str}, # {comment}')
|
318
|
+
|
319
|
+
lines.append('}')
|
320
|
+
lines.append('')
|
321
|
+
|
322
|
+
# Lazy loading infrastructure
|
323
|
+
lines.extend([
|
324
|
+
'# Lazy loading cache',
|
325
|
+
'_dimension_cache: dict[str, DimensionSignature] = {}',
|
326
|
+
'',
|
327
|
+
'def __getattr__(name: str) -> DimensionSignature:',
|
328
|
+
' """Lazy load dimension constants."""',
|
329
|
+
' if name in _DIMENSION_SIGNATURES:',
|
330
|
+
' if name not in _dimension_cache:',
|
331
|
+
' _dimension_cache[name] = DimensionSignature(_DIMENSION_SIGNATURES[name])',
|
332
|
+
' return _dimension_cache[name]',
|
333
|
+
' raise AttributeError(f"module {__name__!r} has no attribute {name!r}")',
|
334
|
+
'',
|
335
|
+
])
|
336
|
+
|
337
|
+
# Generate ALL dimensions found in unit data - no hardcoded mappings
|
338
|
+
lines.append('# All dimension constants generated from unit data')
|
339
|
+
|
340
|
+
# Generate constants for ALL fields in the JSON data
|
341
|
+
for field_name in sorted(self.unit_data.keys()):
|
342
|
+
field_data = self.unit_data[field_name]
|
343
|
+
dims = field_data.get('dimensions', {})
|
344
|
+
const_name = field_name.upper()
|
345
|
+
|
346
|
+
signature = self.calculate_signature(dims)
|
347
|
+
comment = self.format_dimension_comment(dims)
|
348
|
+
|
349
|
+
if signature == int(signature):
|
350
|
+
sig_str = str(int(signature))
|
351
|
+
else:
|
352
|
+
sig_str = f"{signature:.10g}"
|
353
|
+
|
354
|
+
lines.append(f'{const_name} = DimensionSignature({sig_str}) # {comment}')
|
355
|
+
|
356
|
+
lines.append('')
|
357
|
+
|
358
|
+
return lines
|
359
|
+
|
360
|
+
def generate_exports(self) -> list[str]:
|
361
|
+
"""Generate __all__ export list."""
|
362
|
+
lines = [
|
363
|
+
'# Module exports',
|
364
|
+
'__all__ = [',
|
365
|
+
' "BaseDimension",',
|
366
|
+
' "DimensionSignature",',
|
367
|
+
]
|
368
|
+
|
369
|
+
# Add all dimensions from unit data
|
370
|
+
for field_name in sorted(self.unit_data.keys()):
|
371
|
+
const_name = field_name.upper()
|
372
|
+
lines.append(f' "{const_name}",')
|
373
|
+
|
374
|
+
lines.extend([
|
375
|
+
']',
|
376
|
+
'',
|
377
|
+
])
|
378
|
+
|
379
|
+
return lines
|
380
|
+
|
381
|
+
def generate(self) -> None:
|
382
|
+
"""Generate the complete dimensions.py file."""
|
383
|
+
# Extract all dimensions from data
|
384
|
+
self.extract_all_dimensions()
|
385
|
+
|
386
|
+
# Build the file content
|
387
|
+
lines = []
|
388
|
+
lines.extend(self.generate_header())
|
389
|
+
lines.extend(self.generate_base_dimension_class())
|
390
|
+
lines.extend(self.generate_dimension_signature_class())
|
391
|
+
lines.extend(self.generate_dimension_constants())
|
392
|
+
lines.extend(self.generate_exports())
|
393
|
+
|
394
|
+
# Write the file with newline at end
|
395
|
+
content = '\n'.join(lines) + '\n'
|
396
|
+
self.output_path.write_text(content, encoding='utf-8')
|
397
|
+
print(f"Generated {self.output_path}")
|
398
|
+
|
399
|
+
# Save metadata to out directory
|
400
|
+
metadata = {
|
401
|
+
'total_dimensions': len(self.all_dimensions),
|
402
|
+
'generated_dimensions': sorted(self.unit_data.keys()),
|
403
|
+
'base_dimensions': list(BASE_DIMENSIONS.keys()),
|
404
|
+
'signatures_cached': len(self.common_signatures),
|
405
|
+
}
|
406
|
+
|
407
|
+
metadata_path = self.out_dir / 'dimension_metadata.json'
|
408
|
+
with open(metadata_path, 'w', encoding='utf-8') as f:
|
409
|
+
json.dump(metadata, f, indent=2)
|
410
|
+
print(f"Saved metadata to {metadata_path}")
|
411
|
+
|
412
|
+
|
413
|
+
def main() -> None:
|
414
|
+
"""Main entry point."""
|
415
|
+
# Set up paths
|
416
|
+
generator_dir = Path(__file__).parent
|
417
|
+
data_path = generator_dir / 'data' / 'unit_data.json'
|
418
|
+
output_path = generator_dir.parent.parent / 'generated' / 'dimensions.py'
|
419
|
+
out_dir = generator_dir / 'out'
|
420
|
+
|
421
|
+
# Create output directory if needed
|
422
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
423
|
+
|
424
|
+
# Run generator
|
425
|
+
generator = DimensionGenerator(data_path, output_path, out_dir)
|
426
|
+
generator.generate()
|
427
|
+
|
428
|
+
print("\nDimension generation complete!")
|
429
|
+
print(f" - Total dimensions: {len(generator.all_dimensions)}")
|
430
|
+
print(f" - Cached signatures: {len(generator.common_signatures)}")
|
431
|
+
|
432
|
+
|
433
|
+
if __name__ == "__main__":
|
434
|
+
main()
|
@@ -0,0 +1,141 @@
|
|
1
|
+
"""
|
2
|
+
Documentation Generation Helper
|
3
|
+
==============================
|
4
|
+
|
5
|
+
Shared functions for generating consistent documentation across
|
6
|
+
quantities.py and quantities.pyi files.
|
7
|
+
"""
|
8
|
+
|
9
|
+
try:
|
10
|
+
from .data_processor import get_unit_names_and_aliases
|
11
|
+
except ImportError:
|
12
|
+
# Handle standalone execution
|
13
|
+
from .data_processor import get_unit_names_and_aliases
|
14
|
+
|
15
|
+
|
16
|
+
def generate_class_docstring(class_name: str, display_name: str, units: list, is_dimensionless: bool = False) -> list[str]:
|
17
|
+
"""Generate comprehensive class docstring for quantity classes."""
|
18
|
+
# Get example units for documentation
|
19
|
+
example_units = []
|
20
|
+
for unit in units[:3]: # Take first 3 units as examples
|
21
|
+
primary_name, _ = get_unit_names_and_aliases(unit)
|
22
|
+
example_units.append(f'"{primary_name}"')
|
23
|
+
|
24
|
+
unit_examples = ', '.join(example_units) if example_units else '"unit"'
|
25
|
+
|
26
|
+
lines = [
|
27
|
+
' """',
|
28
|
+
f' Type-safe {display_name} quantity with expression capabilities.',
|
29
|
+
' ',
|
30
|
+
]
|
31
|
+
|
32
|
+
if is_dimensionless:
|
33
|
+
lines.extend([
|
34
|
+
' Constructor Options:',
|
35
|
+
' -------------------',
|
36
|
+
f' - {class_name}("variable_name") -> Create unknown {display_name}',
|
37
|
+
f' - {class_name}(value, "variable_name") -> Create known {display_name}',
|
38
|
+
' ',
|
39
|
+
' Examples:',
|
40
|
+
' ---------',
|
41
|
+
f' >>> unknown = {class_name}("efficiency") # Unknown {display_name}',
|
42
|
+
f' >>> known = {class_name}(0.85, "thermal_efficiency") # Known {display_name}',
|
43
|
+
])
|
44
|
+
else:
|
45
|
+
lines.extend([
|
46
|
+
' Constructor Options:',
|
47
|
+
' -------------------',
|
48
|
+
f' - {class_name}("variable_name") -> Create unknown {display_name}',
|
49
|
+
f' - {class_name}(value, "unit", "variable_name") -> Create known {display_name}',
|
50
|
+
' ',
|
51
|
+
' Examples:',
|
52
|
+
' ---------',
|
53
|
+
f' >>> unknown = {class_name}("pressure") # Unknown {display_name}',
|
54
|
+
f' >>> known = {class_name}(100, {unit_examples.split(",")[0] if unit_examples else "unit"}, "inlet_pressure") # Known {display_name}',
|
55
|
+
' ',
|
56
|
+
f' Available units: {unit_examples}',
|
57
|
+
])
|
58
|
+
|
59
|
+
lines.append(' """')
|
60
|
+
return lines
|
61
|
+
|
62
|
+
|
63
|
+
def generate_init_method(class_name: str, display_name: str, is_dimensionless: bool = False, stub_only: bool = False) -> list[str]:
|
64
|
+
"""Generate __init__ method with proper type hints."""
|
65
|
+
del class_name # Unused but kept for API compatibility
|
66
|
+
lines = []
|
67
|
+
|
68
|
+
if is_dimensionless:
|
69
|
+
lines.append(' def __init__(self, name_or_value: str | int | float, name: str | None = None, is_known: bool = True):')
|
70
|
+
if not stub_only:
|
71
|
+
lines.extend([
|
72
|
+
' """',
|
73
|
+
f' Initialize {display_name} quantity.',
|
74
|
+
' ',
|
75
|
+
' Args:',
|
76
|
+
' name_or_value: Variable name (str) if unknown, or value (int/float) if known',
|
77
|
+
' name: Variable name (required if providing value)',
|
78
|
+
' is_known: Whether the variable has a known value',
|
79
|
+
' """',
|
80
|
+
' if name is None:',
|
81
|
+
' # Single argument: name only (unknown variable)',
|
82
|
+
' super().__init__(name_or_value, is_known=is_known)',
|
83
|
+
' else:',
|
84
|
+
' # Two arguments: value and name (known variable)',
|
85
|
+
' super().__init__(name_or_value, name, is_known=is_known)',
|
86
|
+
])
|
87
|
+
else:
|
88
|
+
lines.append(' ...')
|
89
|
+
else:
|
90
|
+
lines.append(' def __init__(self, name_or_value: str | int | float, unit: str | None = None, name: str | None = None, is_known: bool = True):')
|
91
|
+
if not stub_only:
|
92
|
+
lines.extend([
|
93
|
+
' """',
|
94
|
+
f' Initialize {display_name} quantity.',
|
95
|
+
' ',
|
96
|
+
' Args:',
|
97
|
+
' name_or_value: Variable name (str) if unknown, or value (int/float) if known',
|
98
|
+
' unit: Unit string (required if providing value)',
|
99
|
+
' name: Variable name (required if providing value)',
|
100
|
+
' is_known: Whether the variable has a known value',
|
101
|
+
' """',
|
102
|
+
' if unit is None and name is None:',
|
103
|
+
' # Single argument: name only (unknown variable)',
|
104
|
+
' super().__init__(name_or_value, is_known=is_known)',
|
105
|
+
' elif unit is not None and name is not None:',
|
106
|
+
' # Three arguments: value, unit, name (known variable)',
|
107
|
+
' super().__init__(name_or_value, unit, name, is_known=is_known)',
|
108
|
+
' else:',
|
109
|
+
' raise ValueError("Must provide either just name (unknown) or value, unit, and name (known)")',
|
110
|
+
])
|
111
|
+
else:
|
112
|
+
lines.append(' ...')
|
113
|
+
|
114
|
+
return lines
|
115
|
+
|
116
|
+
|
117
|
+
def generate_set_method(setter_class_name: str, display_name: str, stub_only: bool = False) -> list[str]:
|
118
|
+
"""Generate set method with comprehensive documentation."""
|
119
|
+
lines = [
|
120
|
+
f' def set(self, value: int | float) -> ts.{setter_class_name}:',
|
121
|
+
' """',
|
122
|
+
f' Create a setter for this {display_name} quantity.',
|
123
|
+
' ',
|
124
|
+
' Args:',
|
125
|
+
' value: The numeric value to set',
|
126
|
+
' ',
|
127
|
+
' Returns:',
|
128
|
+
f' {setter_class_name}: A setter with unit properties like .meters, .inches, etc.',
|
129
|
+
' ',
|
130
|
+
' Example:',
|
131
|
+
' >>> length = Length("beam_length")',
|
132
|
+
' >>> length.set(100).millimeters # Sets to 100 mm',
|
133
|
+
' """',
|
134
|
+
]
|
135
|
+
|
136
|
+
if stub_only:
|
137
|
+
lines.append(' ...')
|
138
|
+
else:
|
139
|
+
lines.append(f' return ts.{setter_class_name}(self, value)')
|
140
|
+
|
141
|
+
return lines
|