qnty 0.0.7__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/equations/equation.py +257 -0
- 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/expressions/nodes.py +546 -0
- 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/equation.py +0 -216
- qnty/expression.py +0 -492
- qnty/unit_types/base.py +0 -47
- qnty/units.py +0 -8113
- qnty/variable.py +0 -263
- qnty/variable_types/base.py +0 -58
- qnty/variable_types/expression_variable.py +0 -68
- qnty/variable_types/typed_variable.py +0 -87
- qnty/variables.py +0 -2298
- qnty/variables.pyi +0 -6148
- qnty-0.0.7.dist-info/METADATA +0 -355
- qnty-0.0.7.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.7.dist-info → qnty-0.0.9.dist-info}/WHEEL +0 -0
@@ -0,0 +1,199 @@
|
|
1
|
+
Metadata-Version: 2.3
|
2
|
+
Name: qnty
|
3
|
+
Version: 0.0.9
|
4
|
+
Summary: High-performance unit system library for Python with dimensional safety and fast unit conversions
|
5
|
+
License: Apache-2.0
|
6
|
+
Keywords: units,dimensional analysis,engineering,physics,quantities,measurements
|
7
|
+
Author: tn3wman
|
8
|
+
Requires-Python: >=3.11, <3.14
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
10
|
+
Classifier: Intended Audience :: Developers
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
12
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
17
|
+
Classifier: Topic :: Scientific/Engineering
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
20
|
+
Provides-Extra: benchmark
|
21
|
+
Provides-Extra: dev
|
22
|
+
Requires-Dist: Pint (>=0.24.4) ; extra == "benchmark"
|
23
|
+
Requires-Dist: numpy (>=2.3.2)
|
24
|
+
Requires-Dist: pytest (>=8.4.1) ; extra == "dev"
|
25
|
+
Requires-Dist: ruff (>=0.1.0) ; extra == "dev"
|
26
|
+
Project-URL: Bug Tracker, https://github.com/tn3wman/qnty/issues
|
27
|
+
Project-URL: Documentation, https://github.com/tn3wman/qnty#readme
|
28
|
+
Project-URL: Homepage, https://github.com/tn3wman/qnty
|
29
|
+
Project-URL: Repository, https://github.com/tn3wman/qnty
|
30
|
+
Description-Content-Type: text/markdown
|
31
|
+
|
32
|
+
# Qnty
|
33
|
+
|
34
|
+
**High-performance unit system library for Python with dimensional safety and fast unit conversions for engineering calculations.**
|
35
|
+
|
36
|
+
[](https://www.python.org/downloads/)
|
37
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
38
|
+
[](https://pypi.org/project/qnty/)
|
39
|
+
|
40
|
+
## ⚠️ Important Disclaimer
|
41
|
+
|
42
|
+
**🚧 Work in Progress**: Qnty is currently in active development and has not been thoroughly vetted for production engineering calculations. While we strive for accuracy, this library should not be used for critical engineering applications without independent verification.
|
43
|
+
|
44
|
+
**📐 Accuracy Notice**: The authors are not responsible or liable for incorrect results, calculation errors, or any consequences arising from the use of this library. Always validate calculations independently using established engineering tools and practices.
|
45
|
+
|
46
|
+
*Use Qnty to help prevent unit errors, but always verify critical calculations through multiple methods.*
|
47
|
+
|
48
|
+
---
|
49
|
+
|
50
|
+
## ✨ Key Features
|
51
|
+
|
52
|
+
- **🚀 Ultra-Fast Performance**: Prime number encoding and pre-computed conversion tables
|
53
|
+
- **🛡️ Type Safety**: Compile-time dimensional analysis prevents unit errors
|
54
|
+
- **⚡ Zero-Cost Abstractions**: Optimized operations with `__slots__` and caching
|
55
|
+
- **🔗 Fluent API**: Intuitive method chaining for readable code
|
56
|
+
- **🧮 Engineering-Focused**: Built for real-world engineering calculations
|
57
|
+
- **🧬 Mathematical System**: Built-in equation solving and expression trees
|
58
|
+
- **📊 Comprehensive Testing**: 187 tests with performance benchmarks
|
59
|
+
|
60
|
+
## 🚀 Quick Start
|
61
|
+
|
62
|
+
### Installation
|
63
|
+
|
64
|
+
```bash
|
65
|
+
pip install qnty
|
66
|
+
```
|
67
|
+
|
68
|
+
### Basic Usage
|
69
|
+
|
70
|
+
```python
|
71
|
+
from qnty import Length, Pressure, Area
|
72
|
+
|
73
|
+
# Create quantities with dimensional safety
|
74
|
+
width = Length(3, "meter", "Width")
|
75
|
+
height = Length(2, "meter", "Height")
|
76
|
+
|
77
|
+
# Solve mathematical expressions
|
78
|
+
area = Area("area", is_known=False)
|
79
|
+
area.solve_from(width * height)
|
80
|
+
print(f"Area: {area}") # Area: 6.0 m²
|
81
|
+
```
|
82
|
+
|
83
|
+
### Engineering Example
|
84
|
+
|
85
|
+
```python
|
86
|
+
from qnty import Problem, Length, Pressure
|
87
|
+
|
88
|
+
class PipeThickness(Problem):
|
89
|
+
"""Calculate pipe wall thickness"""
|
90
|
+
|
91
|
+
# Known parameters
|
92
|
+
pressure = Pressure(150, "pound_force_per_square_inch", "Internal Pressure")
|
93
|
+
diameter = Length(6, "inch", "Pipe Diameter")
|
94
|
+
allowable_stress = Pressure(20000, "pound_force_per_square_inch", "Allowable Stress")
|
95
|
+
|
96
|
+
# Unknown to solve for
|
97
|
+
thickness = Length("thickness", is_known=False)
|
98
|
+
|
99
|
+
# Engineering equation: t = (P × D) / (2 × S)
|
100
|
+
equation = thickness.equals((pressure * diameter) / (2 * allowable_stress))
|
101
|
+
|
102
|
+
# Solve the problem
|
103
|
+
problem = PipeThickness()
|
104
|
+
problem.solve()
|
105
|
+
print(f"Required thickness: {problem.thickness}")
|
106
|
+
```
|
107
|
+
|
108
|
+
### Mathematical Operations
|
109
|
+
|
110
|
+
```python
|
111
|
+
from qnty import Length, sqrt, Area
|
112
|
+
|
113
|
+
# Dimensional analysis with mathematical functions
|
114
|
+
area = Area(25, "square_meter", "Square Area")
|
115
|
+
side = Length("side", is_known=False)
|
116
|
+
side.solve_from(sqrt(area)) # Returns Length, not Area!
|
117
|
+
print(f"Side length: {side}") # Side length: 5.0 m
|
118
|
+
```
|
119
|
+
|
120
|
+
## 📚 Documentation
|
121
|
+
|
122
|
+
- **[📖 Tutorial](docs/TUTORIAL.md)** - Step-by-step learning guide
|
123
|
+
- **[📋 API Reference](docs/API_REFERENCE.md)** - Complete API documentation
|
124
|
+
- **[🏗️ Examples](examples/)** - Real-world engineering examples
|
125
|
+
- **[📁 Full Documentation](docs/)** - Complete documentation index
|
126
|
+
|
127
|
+
## 🚀 Performance
|
128
|
+
|
129
|
+
Qnty significantly outperforms other unit libraries with **18.9x average speedup** over Pint:
|
130
|
+
|
131
|
+
| Operation | Qnty | Pint | **Speedup** |
|
132
|
+
|-----------|------|------|-------------|
|
133
|
+
| Mixed Unit Addition | 0.76 μs | 17.52 μs | **23.1x** |
|
134
|
+
| Complex ASME Equation | 4.07 μs | 106.17 μs | **26.1x** 🚀 |
|
135
|
+
| Type-Safe Variables | 0.98 μs | 9.65 μs | **9.8x** |
|
136
|
+
| **AVERAGE** | **1.89 μs** | **35.83 μs** | **18.9x** 🏆 |
|
137
|
+
|
138
|
+
*Run `pytest tests/test_benchmark.py -v -s` to verify on your system.*
|
139
|
+
|
140
|
+
## 🧮 100+ Engineering Quantities
|
141
|
+
|
142
|
+
Qnty provides comprehensive coverage of engineering domains:
|
143
|
+
|
144
|
+
```python
|
145
|
+
from qnty import (
|
146
|
+
# Mechanical
|
147
|
+
Length, Area, Volume, Mass, Force, Pressure, Temperature,
|
148
|
+
# Electrical
|
149
|
+
ElectricPotential, ElectricCurrentIntensity, ElectricResistance,
|
150
|
+
# Thermal
|
151
|
+
ThermalConductivity, HeatTransferCoefficient,
|
152
|
+
# Fluid Dynamics
|
153
|
+
ViscosityDynamic, MassFlowRate, VolumetricFlowRate,
|
154
|
+
# And 80+ more...
|
155
|
+
)
|
156
|
+
```
|
157
|
+
|
158
|
+
## 🔧 Development
|
159
|
+
|
160
|
+
```bash
|
161
|
+
# Install dependencies
|
162
|
+
pip install -r requirements.txt
|
163
|
+
|
164
|
+
# Run tests
|
165
|
+
pytest
|
166
|
+
|
167
|
+
# Run specific test
|
168
|
+
pytest tests/test_dimension.py -v
|
169
|
+
|
170
|
+
# Run benchmarks
|
171
|
+
python tests/test_benchmark.py
|
172
|
+
|
173
|
+
# Lint code
|
174
|
+
ruff check src/ tests/
|
175
|
+
ruff format src/ tests/
|
176
|
+
```
|
177
|
+
|
178
|
+
## 📄 License
|
179
|
+
|
180
|
+
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
|
181
|
+
|
182
|
+
## 🤝 Contributing
|
183
|
+
|
184
|
+
We welcome contributions! Please see [CLAUDE.md](CLAUDE.md) for development guidelines and:
|
185
|
+
|
186
|
+
1. Fork the repository
|
187
|
+
2. Create a feature branch
|
188
|
+
3. Add tests for new functionality
|
189
|
+
4. Ensure all tests pass: `pytest`
|
190
|
+
5. Submit a pull request
|
191
|
+
|
192
|
+
---
|
193
|
+
|
194
|
+
**Ready to supercharge your engineering calculations?** 🚀
|
195
|
+
|
196
|
+
- Start with the **[Tutorial](docs/TUTORIAL.md)**
|
197
|
+
- Browse the **[API Reference](docs/API_REFERENCE.md)**
|
198
|
+
- Try the **[Examples](examples/)**
|
199
|
+
|
@@ -0,0 +1,63 @@
|
|
1
|
+
qnty/__init__.py,sha256=nixvrkyF4sIwtLCPVIfADSEKkQoKlFZs_7TbpuBFWXE,5563
|
2
|
+
qnty/_backup/problem_original.py,sha256=M58iUTiWBO1sS3rM3aQSlpIwxbSRiW2LICYK-Lnoz90,56698
|
3
|
+
qnty/_backup/quantity.py,sha256=oD-TNDkxK1Tfa6S_sxNnDWyv1_SuW0TrNKz01e45fxg,1509
|
4
|
+
qnty/codegen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
+
qnty/codegen/cli.py,sha256=C66IQ-je69UP0llVy7kgSrfUYM3PHuantcefYvFBDHw,4092
|
6
|
+
qnty/codegen/generators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
|
+
qnty/codegen/generators/data/unit_data.json,sha256=KbJHRv5U2bF_uS_Az9G-uFSjwvxCcvmn1ZCxR5H0uGI,253446
|
8
|
+
qnty/codegen/generators/data_processor.py,sha256=3Cibqkegi48KuaD9I9i1QinP2h7Fi76EOJ2hwmZq6ug,13741
|
9
|
+
qnty/codegen/generators/dimensions_gen.py,sha256=03Ryq8CPIU3gMSOZNnOjc2JMR3Vi-lgVmBYFnOliL8w,16379
|
10
|
+
qnty/codegen/generators/doc_generator.py,sha256=zmhDWwxODII5d-_FlKskhMTHHBmS8NRZOzOyPzPMyTs,6184
|
11
|
+
qnty/codegen/generators/out/dimension_mapping.json,sha256=_YRPCzMvT9RW_YhURbuHH8gKhYtdu8gaYSqJ-bZxSAE,23864
|
12
|
+
qnty/codegen/generators/out/dimension_metadata.json,sha256=-2uXcB8grYMZew9A6mDSajrJ9yCoegrTx23g70QTK_Y,2929
|
13
|
+
qnty/codegen/generators/out/units_metadata.json,sha256=5MMDsT6Y99tthus8YT5Xu6iaG4cn99sP-JomKYZ084k,5890
|
14
|
+
qnty/codegen/generators/quantities_gen.py,sha256=9Nf_3PUpTf0A1yLe8L4CodrpsD4Oz7DTXX3E-sssj3I,6271
|
15
|
+
qnty/codegen/generators/setters_gen.py,sha256=u76OwdH7Sl6XsIDGZn6ID8Xp0VEptBXO1QJ4h6sne1c,7145
|
16
|
+
qnty/codegen/generators/stubs_gen.py,sha256=F6ZEk48W9C8nRKP6_pKoGPO3gnDtGfvn-42XgrhA4uk,6659
|
17
|
+
qnty/codegen/generators/units_gen.py,sha256=iESIFLCVNWi_4Ftss2_s2QTGTOTutInAeltLQdMjrI8,10592
|
18
|
+
qnty/codegen/generators/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
|
+
qnty/equations/__init__.py,sha256=ngficq_uwosz7O2BaqDH-G6dqp1burjfkcwE4MnGyIo,108
|
20
|
+
qnty/equations/equation.py,sha256=dv527D-fE_0jLmK6BJI5SNCjIgzql1JpBcxZkej06-A,10848
|
21
|
+
qnty/equations/system.py,sha256=mgslAh_YgEU1V_y48Yy1_eHIFO37vDsSG4Gb3zeuPxc,5115
|
22
|
+
qnty/expressions/__init__.py,sha256=qo5I-Z3O1hP9Kk1Jgf5r7x4tAzMLS-a96rY5cqRNVKc,917
|
23
|
+
qnty/expressions/cache.py,sha256=ahJ-AyxJJQyGAVQWSEcQAng1TdQ2qysBPiujk9hhink,3413
|
24
|
+
qnty/expressions/functions.py,sha256=X7Cfchmc4qY3JE3NnacI5DomYE0jPbAAQHOsrxG3vqk,3618
|
25
|
+
qnty/expressions/nodes.py,sha256=bTmpJ36sjnlxgKb2npJfh56K1arN-FOxoiI30Gx31-g,23394
|
26
|
+
qnty/generated/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
27
|
+
qnty/generated/dimensions.py,sha256=WHVr28Ou2DB2QuZCDcxLY2AcauqdE4QGrNFNUJ2EDXY,20661
|
28
|
+
qnty/generated/quantities.py,sha256=1FC1P_hIcXotUgDiJXy28P9OCq2jdWEmO5aVZJepNpY,245345
|
29
|
+
qnty/generated/quantities.pyi,sha256=URYr8K2JVeXlwbSnUvOLoLYS9beVhGrL33A7Y1hs1Hs,151386
|
30
|
+
qnty/generated/setters.py,sha256=Go62i_TlfcBztI8rI7h2FMWGmtoVPGc3JTx6ZmJMrWI,446272
|
31
|
+
qnty/generated/units.py,sha256=WXkSL_G4hKzbTiGmN3jreWCuUyutyGb50tRwfT0Xje4,285245
|
32
|
+
qnty/problem/__init__.py,sha256=9bML5bjM-uPAHet8n98_OwQLivEs2mzaHjZhENbA7sE,3140
|
33
|
+
qnty/problem/base.py,sha256=jepN3vmMyh9eXptJrAQfPICHbDPLQsD2g9k4EY7IqrA,5680
|
34
|
+
qnty/problem/composition.py,sha256=DUdJ7oMl1d4ETqgR4rDflqfBOdE2ykr-f6qONOErfZQ,13894
|
35
|
+
qnty/problem/composition_mixin.py,sha256=hH9bEZyWc5vz5LfLb7GM2K6WDP7SUgHWUu_Vx4BtEM0,17603
|
36
|
+
qnty/problem/equations.py,sha256=QefhpWBQt9OD0vUeYRZs54iv7kQ9xkO0XBtYBtOjfPE,19334
|
37
|
+
qnty/problem/metaclass.py,sha256=00T4fg3ACeTo7w1njRgZyOGP2jZm2P2_yxO0rZlnwhE,11944
|
38
|
+
qnty/problem/reconstruction.py,sha256=hE7L5Eh2OH3nR_3PFKsSiNRHDSbgSumrhXxPRESmJXI,40042
|
39
|
+
qnty/problem/solving.py,sha256=DJ_Dj63vn2ykdFUp77QNxEGOWmPrAzpLe7A_mnCkMzs,6657
|
40
|
+
qnty/problem/validation.py,sha256=1Kbebn4JvYO0qw2VOhfeyzzkuSmnTpOANpvVmsXcNuU,2157
|
41
|
+
qnty/problem/variables.py,sha256=Fmhc-GmW-tHMQk1YVtHgOQlfvRJxH1le94Nc0HmeyBY,9911
|
42
|
+
qnty/quantities/__init__.py,sha256=C8Z032FGpMVt9-PvtIQLNZkxmXjbHUm2OHoytebUL1g,107
|
43
|
+
qnty/quantities/expression_quantity.py,sha256=khJ5izSaj497-rd6Ci1p8XyBidwAoXFlqpBiOqVbi3A,13278
|
44
|
+
qnty/quantities/quantity.py,sha256=PK1vGMZv55fy87ysvakVipO0lWK_XySKZCobJfKMjTQ,18855
|
45
|
+
qnty/quantities/typed_quantity.py,sha256=-PL_6qhJvOLdGXoxJjCHiCx3ZB5Oao9l-e2zrON3cug,9681
|
46
|
+
qnty/solving/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
47
|
+
qnty/solving/manager.py,sha256=FfG3DOrwAKeR0vHll46RtkLB9ZuWN43mthYHnr5AW98,3594
|
48
|
+
qnty/solving/order.py,sha256=Z7I_7QonCaGC47DthC2O0m77On34GcaD7nn2oLIFmqs,14411
|
49
|
+
qnty/solving/solvers/__init__.py,sha256=CBI2PGLhnQ3AVsfJPfBB7b0ff_BlPCRamGUIHjEMStM,501
|
50
|
+
qnty/solving/solvers/base.py,sha256=KYACgawfo8GoRaXEduZRQjJeo948QNFU76N2VrCwcA4,3041
|
51
|
+
qnty/solving/solvers/iterative.py,sha256=5SxIGMdP1HkAKsT6cXZR7Lxv3LIgM7dXY3PsTn0w_d4,8139
|
52
|
+
qnty/solving/solvers/simultaneous.py,sha256=pqYUwh067z26kP49Nxq8HcCxsxB0dirPaVHMWG4QMTE,23225
|
53
|
+
qnty/units/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
54
|
+
qnty/units/prefixes.py,sha256=U-dGj5ZdseErwFIzvz_RC3fID0D-DazE6ozD6vn4DSs,7442
|
55
|
+
qnty/units/registry.py,sha256=rnKnEaG8rZg56ejK1nGl9xpe9hm1WmWAtD8tJ0A0MvA,7524
|
56
|
+
qnty/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
57
|
+
qnty/utils/logging.py,sha256=QSYgZg6w1n9L-lIj5c3Ufee84E8VvRZOUgoW84vTUbA,1159
|
58
|
+
qnty/validation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
59
|
+
qnty/validation/registry.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
60
|
+
qnty/validation/rules.py,sha256=4X3pR6bl9YA5skT2vRmJFB78FR588Rz_cSTTJXD0BDI,6285
|
61
|
+
qnty-0.0.9.dist-info/METADATA,sha256=AKPCK06zvVod4ss5nmYHmCgActFnehcUGRF8b7cIfLo,6761
|
62
|
+
qnty-0.0.9.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
63
|
+
qnty-0.0.9.dist-info/RECORD,,
|
qnty/dimension.py
DELETED
@@ -1,186 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Dimension System
|
3
|
-
================
|
4
|
-
|
5
|
-
Compile-time dimensional analysis using type system for ultra-fast operations.
|
6
|
-
"""
|
7
|
-
|
8
|
-
from dataclasses import dataclass
|
9
|
-
from enum import IntEnum
|
10
|
-
from typing import final
|
11
|
-
|
12
|
-
|
13
|
-
class BaseDimension(IntEnum):
|
14
|
-
"""Base dimensions as prime numbers for efficient bit operations."""
|
15
|
-
LENGTH = 2
|
16
|
-
MASS = 3
|
17
|
-
TIME = 5
|
18
|
-
CURRENT = 7
|
19
|
-
TEMPERATURE = 11
|
20
|
-
AMOUNT = 13
|
21
|
-
LUMINOSITY = 17
|
22
|
-
DIMENSIONLESS = 1 # Must be 1 to act as multiplicative identity
|
23
|
-
|
24
|
-
|
25
|
-
@final
|
26
|
-
@dataclass(frozen=True)
|
27
|
-
class DimensionSignature:
|
28
|
-
"""Immutable dimension signature for zero-cost dimensional analysis."""
|
29
|
-
|
30
|
-
# Store as bit pattern for ultra-fast comparison
|
31
|
-
_signature: int | float = 1
|
32
|
-
|
33
|
-
@classmethod
|
34
|
-
def create(cls, length=0, mass=0, time=0, current=0, temp=0, amount=0, luminosity=0):
|
35
|
-
"""Create dimension from exponents."""
|
36
|
-
signature = 1
|
37
|
-
if length != 0:
|
38
|
-
signature *= BaseDimension.LENGTH ** length
|
39
|
-
if mass != 0:
|
40
|
-
signature *= BaseDimension.MASS ** mass
|
41
|
-
if time != 0:
|
42
|
-
signature *= BaseDimension.TIME ** time
|
43
|
-
if current != 0:
|
44
|
-
signature *= BaseDimension.CURRENT ** current
|
45
|
-
if temp != 0:
|
46
|
-
signature *= BaseDimension.TEMPERATURE ** temp
|
47
|
-
if amount != 0:
|
48
|
-
signature *= BaseDimension.AMOUNT ** amount
|
49
|
-
if luminosity != 0:
|
50
|
-
signature *= BaseDimension.LUMINOSITY ** luminosity
|
51
|
-
|
52
|
-
return cls(signature)
|
53
|
-
|
54
|
-
def __mul__(self, other):
|
55
|
-
return DimensionSignature(self._signature * other._signature)
|
56
|
-
|
57
|
-
def __truediv__(self, other):
|
58
|
-
return DimensionSignature(self._signature / other._signature)
|
59
|
-
|
60
|
-
def __pow__(self, power):
|
61
|
-
return DimensionSignature(self._signature ** power)
|
62
|
-
|
63
|
-
def is_compatible(self, other):
|
64
|
-
"""Ultra-fast dimensional compatibility check."""
|
65
|
-
return self._signature == other._signature
|
66
|
-
|
67
|
-
def __eq__(self, other):
|
68
|
-
"""Fast equality check for dimensions."""
|
69
|
-
return isinstance(other, DimensionSignature) and self._signature == other._signature
|
70
|
-
|
71
|
-
def __hash__(self):
|
72
|
-
"""Enable dimensions as dictionary keys."""
|
73
|
-
return hash(self._signature)
|
74
|
-
|
75
|
-
|
76
|
-
# Pre-defined dimension constants (alphabetically ordered)
|
77
|
-
ABSORBED_DOSE = DimensionSignature.create(length=2, time=-2) # L^2 T^-2
|
78
|
-
ACCELERATION = DimensionSignature.create(length=1, time=-2) # L T^-2
|
79
|
-
ACTIVATION_ENERGY = DimensionSignature.create(amount=-1, length=2, time=-2) # N^-1 L^2 T^-2
|
80
|
-
AMOUNT = DimensionSignature.create(amount=1) # N
|
81
|
-
AMOUNT_OF_SUBSTANCE = DimensionSignature.create(amount=1) # N
|
82
|
-
ANGLE_PLANE = DimensionSignature(BaseDimension.DIMENSIONLESS) # Dimensionless
|
83
|
-
ANGLE_SOLID = DimensionSignature(BaseDimension.DIMENSIONLESS) # Dimensionless
|
84
|
-
ANGULAR_ACCELERATION = DimensionSignature.create(time=-2) # T^-2
|
85
|
-
ANGULAR_MOMENTUM = DimensionSignature.create(length=2, mass=1, time=-1) # L^2 M T^-1
|
86
|
-
AREA = DimensionSignature.create(length=2) # L^2
|
87
|
-
AREA_PER_UNIT_VOLUME = DimensionSignature.create(length=-1) # L^-1
|
88
|
-
ATOMIC_WEIGHT = DimensionSignature.create(amount=-1, mass=1) # N^-1 M
|
89
|
-
CONCENTRATION = DimensionSignature.create(length=-3, mass=1) # L^-3 M
|
90
|
-
CURRENT = DimensionSignature.create(current=1) # A
|
91
|
-
DIMENSIONLESS = DimensionSignature(BaseDimension.DIMENSIONLESS) # Dimensionless
|
92
|
-
DYNAMIC_FLUIDITY = DimensionSignature.create(length=1, mass=-1, time=1) # L M^-1 T
|
93
|
-
ELECTRICAL_CONDUCTANCE = DimensionSignature.create(current=2, length=-2, mass=-1, time=3) # A^2 L^-2 M^-1 T^3
|
94
|
-
ELECTRICAL_PERMITTIVITY = DimensionSignature.create(current=2, length=-3, mass=-1, time=4) # A^2 L^-3 M^-1 T^4
|
95
|
-
ELECTRICAL_RESISTIVITY = DimensionSignature.create(current=-2, length=3, mass=1, time=-3) # A^-2 L^3 M T^-3
|
96
|
-
ELECTRIC_CAPACITANCE = DimensionSignature.create(current=2, length=-2, mass=-1, time=4) # A^2 L^-2 M^-1 T^4
|
97
|
-
ELECTRIC_CHARGE = DimensionSignature.create(amount=-1, current=1, time=1) # N^-1 A T
|
98
|
-
ELECTRIC_CURRENT_INTENSITY = DimensionSignature.create(current=1) # A
|
99
|
-
ELECTRIC_DIPOLE_MOMENT = DimensionSignature.create(current=1, length=1, time=1) # A L T
|
100
|
-
ELECTRIC_FIELD_STRENGTH = DimensionSignature.create(current=-1, length=1, mass=1, time=-3) # A^-1 L M T^-3
|
101
|
-
ELECTRIC_INDUCTANCE = DimensionSignature.create(current=-2, length=2, mass=1, time=-2) # A^-2 L^2 M T^-2
|
102
|
-
ELECTRIC_POTENTIAL = DimensionSignature.create(current=-1, length=2, mass=1, time=-3) # A^-1 L^2 M T^-3
|
103
|
-
ELECTRIC_RESISTANCE = DimensionSignature.create(current=-2, length=2, mass=1, time=-3) # A^-2 L^2 M T^-3
|
104
|
-
ENERGY_FLUX = DimensionSignature.create(mass=1, time=-3) # M T^-3
|
105
|
-
ENERGY_HEAT_WORK = DimensionSignature.create(length=2, mass=1, time=-2) # L^2 M T^-2
|
106
|
-
ENERGY_PER_UNIT_AREA = DimensionSignature.create(mass=1, time=-2) # M T^-2
|
107
|
-
FORCE = DimensionSignature.create(length=1, mass=1, time=-2) # L M T^-2
|
108
|
-
FORCE_BODY = DimensionSignature.create(length=-2, mass=1, time=-2) # L^-2 M T^-2
|
109
|
-
FORCE_PER_UNIT_MASS = DimensionSignature.create(length=1, time=-2) # L T^-2
|
110
|
-
FREQUENCY_VOLTAGE_RATIO = DimensionSignature.create(current=1, length=-2, mass=-1, time=3) # A L^-2 M^-1 T^3
|
111
|
-
FUEL_CONSUMPTION = DimensionSignature.create(length=-2) # L^-2
|
112
|
-
HEAT_OF_COMBUSTION = DimensionSignature.create(length=2, time=-2) # L^2 T^-2
|
113
|
-
HEAT_OF_FUSION = DimensionSignature.create(length=2, time=-2) # L^2 T^-2
|
114
|
-
HEAT_OF_VAPORIZATION = DimensionSignature.create(length=2, time=-2) # L^2 T^-2
|
115
|
-
HEAT_TRANSFER_COEFFICIENT = DimensionSignature.create(mass=1, temp=-1, time=-3) # M Θ^-1 T^-3
|
116
|
-
ILLUMINANCE = DimensionSignature.create(length=-2, luminosity=1) # L^-2 J
|
117
|
-
KINETIC_ENERGY_OF_TURBULENCE = DimensionSignature.create(length=2, time=-2) # L^2 T^-2
|
118
|
-
LENGTH = DimensionSignature.create(length=1) # L
|
119
|
-
LINEAR_MASS_DENSITY = DimensionSignature.create(length=-1, mass=1) # L^-1 M
|
120
|
-
LINEAR_MOMENTUM = DimensionSignature.create(length=1, mass=1, time=-1) # L M T^-1
|
121
|
-
LUMINANCE_SELF = DimensionSignature.create(length=-2, luminosity=1) # L^-2 J
|
122
|
-
LUMINOSITY = DimensionSignature.create(luminosity=1) # J
|
123
|
-
LUMINOUS_FLUX = DimensionSignature.create(luminosity=1) # J
|
124
|
-
LUMINOUS_INTENSITY = DimensionSignature.create(luminosity=1) # J
|
125
|
-
MAGNETIC_FIELD = DimensionSignature.create(current=1, length=-1) # A L^-1
|
126
|
-
MAGNETIC_FLUX = DimensionSignature.create(current=-1, length=2, mass=1, time=-2) # A^-1 L^2 M T^-2
|
127
|
-
MAGNETIC_INDUCTION_FIELD_STRENGTH = DimensionSignature.create(current=-1, mass=1, time=-2) # A^-1 M T^-2
|
128
|
-
MAGNETIC_MOMENT = DimensionSignature.create(current=1, length=2) # A L^2
|
129
|
-
MAGNETIC_PERMEABILITY = DimensionSignature.create(current=-2, length=2, mass=1, time=-2) # A^-2 L^2 M T^-2
|
130
|
-
MAGNETOMOTIVE_FORCE = DimensionSignature.create(current=1) # A
|
131
|
-
MASS = DimensionSignature.create(mass=1) # M
|
132
|
-
MASS_DENSITY = DimensionSignature.create(length=-3, mass=1) # L^-3 M
|
133
|
-
MASS_FLOW_RATE = DimensionSignature.create(mass=1, time=-1) # M T^-1
|
134
|
-
MASS_FLUX = DimensionSignature.create(length=-2, mass=1, time=-1) # L^-2 M T^-1
|
135
|
-
MASS_FRACTION_OF_I = DimensionSignature(BaseDimension.DIMENSIONLESS) # Dimensionless
|
136
|
-
MASS_TRANSFER_COEFFICIENT = DimensionSignature.create(length=-2, mass=1, time=-1) # L^-2 M T^-1
|
137
|
-
MOLALITY_OF_SOLUTE_I = DimensionSignature.create(amount=1, mass=-1) # N M^-1
|
138
|
-
MOLARITY_OF_I = DimensionSignature.create(amount=1, length=-3) # N L^-3
|
139
|
-
MOLAR_CONCENTRATION_BY_MASS = DimensionSignature.create(amount=1) # N
|
140
|
-
MOLAR_FLOW_RATE = DimensionSignature.create(amount=1, time=-1) # N T^-1
|
141
|
-
MOLAR_FLUX = DimensionSignature.create(amount=1, length=-2, time=-1) # N L^-2 T^-1
|
142
|
-
MOLAR_HEAT_CAPACITY = DimensionSignature.create(amount=-1, length=2, temp=-1, time=-2) # N^-1 L^2 Θ^-1 T^-2
|
143
|
-
MOLE_FRACTION_OF_I = DimensionSignature(BaseDimension.DIMENSIONLESS) # Dimensionless
|
144
|
-
MOMENTUM_FLOW_RATE = DimensionSignature.create(length=1, mass=1, time=-2) # L M T^-2
|
145
|
-
MOMENTUM_FLUX = DimensionSignature.create(length=-1, mass=1, time=-2) # L^-1 M T^-2
|
146
|
-
MOMENT_OF_INERTIA = DimensionSignature.create(length=2, mass=1) # L^2 M
|
147
|
-
NORMALITY_OF_SOLUTION = DimensionSignature.create(amount=1, length=-3) # N L^-3
|
148
|
-
PARTICLE_DENSITY = DimensionSignature.create(length=-3) # L^-3
|
149
|
-
PERCENT = DimensionSignature(BaseDimension.DIMENSIONLESS) # Dimensionless
|
150
|
-
PERMEABILITY = DimensionSignature.create(length=2) # L^2
|
151
|
-
PHOTON_EMISSION_RATE = DimensionSignature.create(length=-2, time=-1) # L^-2 T^-1
|
152
|
-
POWER_PER_UNIT_MASS = DimensionSignature.create(length=2, time=-3) # L^2 T^-3
|
153
|
-
POWER_PER_UNIT_VOLUME = DimensionSignature.create(length=-1, mass=1, time=-3) # L^-1 M T^-3
|
154
|
-
POWER_THERMAL_DUTY = DimensionSignature.create(length=2, mass=1, time=-3) # L^2 M T^-3
|
155
|
-
PRESSURE = DimensionSignature.create(length=-1, mass=1, time=-2) # L^-1 M T^-2
|
156
|
-
RADIATION_DOSE_EQUIVALENT = DimensionSignature.create(length=2, time=-2) # L^2 T^-2
|
157
|
-
RADIATION_EXPOSURE = DimensionSignature.create(current=1, mass=-1, time=1) # A M^-1 T
|
158
|
-
RADIOACTIVITY = DimensionSignature.create(time=-1) # T^-1
|
159
|
-
SECOND_MOMENT_OF_AREA = DimensionSignature.create(length=4) # L^4
|
160
|
-
SECOND_RADIATION_CONSTANT_PLANCK = DimensionSignature.create(length=1, temp=1) # L Θ
|
161
|
-
SPECIFIC_ENTHALPY = DimensionSignature.create(length=2, time=-2) # L^2 T^-2
|
162
|
-
SPECIFIC_GRAVITY = DimensionSignature(BaseDimension.DIMENSIONLESS) # Dimensionless
|
163
|
-
SPECIFIC_HEAT_CAPACITY_CONSTANT_PRESSURE = DimensionSignature.create(length=2, mass=1, temp=-1, time=-2) # L^2 M Θ^-1 T^-2
|
164
|
-
SPECIFIC_LENGTH = DimensionSignature.create(length=1, mass=-1) # L M^-1
|
165
|
-
SPECIFIC_SURFACE = DimensionSignature.create(length=2, mass=-1) # L^2 M^-1
|
166
|
-
SPECIFIC_VOLUME = DimensionSignature.create(length=3, mass=-1) # L^3 M^-1
|
167
|
-
STRESS = DimensionSignature.create(length=-1, mass=1, time=-2) # L^-1 M T^-2
|
168
|
-
SURFACE_MASS_DENSITY = DimensionSignature.create(length=-2, mass=1) # L^-2 M
|
169
|
-
SURFACE_TENSION = DimensionSignature.create(mass=1, time=-2) # M T^-2
|
170
|
-
TEMPERATURE = DimensionSignature.create(temp=1) # Θ
|
171
|
-
THERMAL_CONDUCTIVITY = DimensionSignature.create(length=1, mass=1, temp=1, time=-3) # L M Θ T^-3
|
172
|
-
TIME = DimensionSignature.create(time=1) # T
|
173
|
-
TORQUE = DimensionSignature.create(length=2, mass=1, time=-2) # L^2 M T^-2
|
174
|
-
TURBULENCE_ENERGY_DISSIPATION_RATE = DimensionSignature.create(length=2, time=-3) # L^2 T^-3
|
175
|
-
VELOCITY_ANGULAR = DimensionSignature.create(time=-1) # T^-1
|
176
|
-
VELOCITY_LINEAR = DimensionSignature.create(length=1, time=-1) # L T^-1
|
177
|
-
VISCOSITY_DYNAMIC = DimensionSignature.create(length=-1, mass=1, time=-1) # L^-1 M T^-1
|
178
|
-
VISCOSITY_KINEMATIC = DimensionSignature.create(length=2, time=-1) # L^2 T^-1
|
179
|
-
VOLUME = DimensionSignature.create(length=3) # L^3
|
180
|
-
VOLUMETRIC_CALORIFIC_HEATING_VALUE = DimensionSignature.create(length=-1, mass=1, time=-2) # L^-1 M T^-2
|
181
|
-
VOLUMETRIC_COEFFICIENT_OF_EXPANSION = DimensionSignature.create(length=-3, mass=1, temp=-1) # L^-3 M Θ^-1
|
182
|
-
VOLUMETRIC_FLOW_RATE = DimensionSignature.create(length=3, time=-1) # L^3 T^-1
|
183
|
-
VOLUMETRIC_FLUX = DimensionSignature.create(length=1, time=-1) # L T^-1
|
184
|
-
VOLUMETRIC_MASS_FLOW_RATE = DimensionSignature.create(length=-3, mass=1, time=-1) # L^-3 M T^-1
|
185
|
-
VOLUME_FRACTION_OF_I = DimensionSignature(BaseDimension.DIMENSIONLESS) # Dimensionless
|
186
|
-
WAVENUMBER = DimensionSignature.create(length=-1) # L^-1
|
qnty/equation.py
DELETED
@@ -1,216 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Equation System
|
3
|
-
===============
|
4
|
-
|
5
|
-
Mathematical equations for qnty variables with solving capabilities.
|
6
|
-
"""
|
7
|
-
|
8
|
-
from __future__ import annotations
|
9
|
-
|
10
|
-
from typing import cast
|
11
|
-
|
12
|
-
from .expression import Expression, VariableReference
|
13
|
-
from .variable import TypeSafeVariable
|
14
|
-
|
15
|
-
|
16
|
-
class Equation:
|
17
|
-
"""Represents a mathematical equation with left-hand side equal to right-hand side."""
|
18
|
-
|
19
|
-
def __init__(self, name: str, lhs: TypeSafeVariable | Expression, rhs: Expression):
|
20
|
-
self.name = name
|
21
|
-
|
22
|
-
# Convert Variable to VariableReference if needed
|
23
|
-
# Use duck typing to avoid circular import
|
24
|
-
if hasattr(lhs, 'name') and hasattr(lhs, 'quantity') and hasattr(lhs, 'is_known'):
|
25
|
-
# It's a TypeSafeVariable-like object
|
26
|
-
self.lhs = VariableReference(cast('TypeSafeVariable', lhs))
|
27
|
-
else:
|
28
|
-
# It's already an Expression
|
29
|
-
self.lhs = cast(Expression, lhs)
|
30
|
-
|
31
|
-
self.rhs = rhs
|
32
|
-
self.variables = self.get_all_variables()
|
33
|
-
|
34
|
-
def get_all_variables(self) -> set[str]:
|
35
|
-
"""Get all variable names used in this equation."""
|
36
|
-
# Both lhs and rhs should be Expressions after __init__ conversion
|
37
|
-
lhs_vars = self.lhs.get_variables()
|
38
|
-
rhs_vars = self.rhs.get_variables()
|
39
|
-
return lhs_vars | rhs_vars
|
40
|
-
|
41
|
-
def get_unknown_variables(self, known_vars: set[str]) -> set[str]:
|
42
|
-
"""Get variables that are unknown (not in known_vars set)."""
|
43
|
-
return self.variables - known_vars
|
44
|
-
|
45
|
-
def get_known_variables(self, known_vars: set[str]) -> set[str]:
|
46
|
-
"""Get variables that are known (in known_vars set)."""
|
47
|
-
return self.variables & known_vars
|
48
|
-
|
49
|
-
def can_solve_for(self, target_var: str, known_vars: set[str]) -> bool:
|
50
|
-
"""Check if this equation can solve for target_var given known_vars."""
|
51
|
-
if target_var not in self.variables:
|
52
|
-
return False
|
53
|
-
# Direct assignment case: lhs is the variable
|
54
|
-
if isinstance(self.lhs, VariableReference) and self.lhs.name == target_var:
|
55
|
-
rhs_vars = self.rhs.get_variables()
|
56
|
-
return rhs_vars.issubset(known_vars)
|
57
|
-
unknown_vars = self.get_unknown_variables(known_vars)
|
58
|
-
# Can solve if target_var is the only unknown
|
59
|
-
return unknown_vars == {target_var}
|
60
|
-
|
61
|
-
def solve_for(self, target_var: str, variable_values: dict[str, TypeSafeVariable]) -> TypeSafeVariable:
|
62
|
-
"""
|
63
|
-
Solve the equation for target_var.
|
64
|
-
Returns the target variable with updated quantity.
|
65
|
-
"""
|
66
|
-
if target_var not in self.variables:
|
67
|
-
raise ValueError(f"Variable '{target_var}' not found in equation")
|
68
|
-
|
69
|
-
# Handle direct assignment: target = expression
|
70
|
-
if isinstance(self.lhs, VariableReference) and self.lhs.name == target_var:
|
71
|
-
# Direct assignment: target_var = rhs
|
72
|
-
result_qty = self.rhs.evaluate(variable_values)
|
73
|
-
|
74
|
-
# Update existing variable object to preserve references
|
75
|
-
var_obj = variable_values.get(target_var)
|
76
|
-
if var_obj is not None:
|
77
|
-
# Convert result to the target variable's original unit if it had one
|
78
|
-
if var_obj.quantity is not None and var_obj.quantity.unit is not None:
|
79
|
-
# Convert to the target variable's defined unit
|
80
|
-
try:
|
81
|
-
result_qty = result_qty.to(var_obj.quantity.unit)
|
82
|
-
except Exception:
|
83
|
-
# If conversion fails, keep the calculated unit
|
84
|
-
pass
|
85
|
-
|
86
|
-
var_obj.quantity = result_qty
|
87
|
-
var_obj.is_known = True
|
88
|
-
return var_obj
|
89
|
-
|
90
|
-
# Create new variable if not found - this shouldn't happen in normal usage
|
91
|
-
raise ValueError(f"Variable '{target_var}' not found in variable_values")
|
92
|
-
|
93
|
-
# For more complex equations, we would need algebraic manipulation
|
94
|
-
# Currently focusing on direct assignment which covers most engineering cases
|
95
|
-
raise NotImplementedError(f"Cannot solve for {target_var} in equation {self}. "
|
96
|
-
f"Only direct assignment equations (var = expression) are supported.")
|
97
|
-
|
98
|
-
def check_residual(self, variable_values: dict[str, TypeSafeVariable], tolerance: float = 1e-10) -> bool:
|
99
|
-
"""
|
100
|
-
Check if equation is satisfied by evaluating residual (LHS - RHS).
|
101
|
-
Returns True if |residual| < tolerance, accounting for units.
|
102
|
-
"""
|
103
|
-
try:
|
104
|
-
# Both lhs and rhs should be Expressions after __init__ conversion
|
105
|
-
lhs_value = self.lhs.evaluate(variable_values)
|
106
|
-
rhs_value = self.rhs.evaluate(variable_values)
|
107
|
-
|
108
|
-
# Check dimensional compatibility
|
109
|
-
if lhs_value._dimension_sig != rhs_value._dimension_sig:
|
110
|
-
return False
|
111
|
-
|
112
|
-
# Convert to same units for comparison
|
113
|
-
rhs_converted = rhs_value.to(lhs_value.unit)
|
114
|
-
residual = abs(lhs_value.value - rhs_converted.value)
|
115
|
-
|
116
|
-
return residual < tolerance
|
117
|
-
except Exception:
|
118
|
-
return False
|
119
|
-
|
120
|
-
def __str__(self) -> str:
|
121
|
-
return f"{self.lhs} = {self.rhs}"
|
122
|
-
|
123
|
-
def __repr__(self) -> str:
|
124
|
-
return f"Equation(name='{self.name}', lhs={self.lhs!r}, rhs={self.rhs!r})"
|
125
|
-
|
126
|
-
|
127
|
-
class EquationSystem:
|
128
|
-
"""System of equations that can be solved together."""
|
129
|
-
|
130
|
-
def __init__(self, equations: list[Equation] | None = None):
|
131
|
-
self.equations = equations or []
|
132
|
-
self.variables = {} # Dict[str, TypeSafeVariable]
|
133
|
-
|
134
|
-
def add_equation(self, equation: Equation):
|
135
|
-
"""Add an equation to the system."""
|
136
|
-
self.equations.append(equation)
|
137
|
-
|
138
|
-
def add_variable(self, variable: TypeSafeVariable):
|
139
|
-
"""Add a variable to the system."""
|
140
|
-
self.variables[variable.name] = variable
|
141
|
-
|
142
|
-
def get_known_variables(self) -> set[str]:
|
143
|
-
"""Get names of all known variables."""
|
144
|
-
return {name for name, var in self.variables.items() if var.is_known and var.quantity is not None}
|
145
|
-
|
146
|
-
def get_unknown_variables(self) -> set[str]:
|
147
|
-
"""Get names of all unknown variables."""
|
148
|
-
return {name for name, var in self.variables.items() if not var.is_known or var.quantity is None}
|
149
|
-
|
150
|
-
def can_solve_any(self) -> bool:
|
151
|
-
"""Check if any equation can be solved with current known variables."""
|
152
|
-
known_vars = self.get_known_variables()
|
153
|
-
unknown_vars = self.get_unknown_variables()
|
154
|
-
|
155
|
-
for equation in self.equations:
|
156
|
-
for unknown_var in unknown_vars:
|
157
|
-
if equation.can_solve_for(unknown_var, known_vars):
|
158
|
-
return True
|
159
|
-
return False
|
160
|
-
|
161
|
-
def solve_step(self) -> bool:
|
162
|
-
"""Solve one step - find and solve one equation. Returns True if progress made."""
|
163
|
-
known_vars = self.get_known_variables()
|
164
|
-
unknown_vars = self.get_unknown_variables()
|
165
|
-
|
166
|
-
# Find an equation that can be solved
|
167
|
-
for equation in self.equations:
|
168
|
-
for unknown_var in unknown_vars:
|
169
|
-
if equation.can_solve_for(unknown_var, known_vars):
|
170
|
-
# Solve for this variable
|
171
|
-
equation.solve_for(unknown_var, self.variables)
|
172
|
-
return True # Progress made
|
173
|
-
|
174
|
-
return False # No progress possible
|
175
|
-
|
176
|
-
def solve(self, max_iterations: int = 100) -> bool:
|
177
|
-
"""Solve the system iteratively. Returns True if fully solved."""
|
178
|
-
for _ in range(max_iterations):
|
179
|
-
if not self.can_solve_any():
|
180
|
-
break
|
181
|
-
if not self.solve_step():
|
182
|
-
break
|
183
|
-
|
184
|
-
# Check if all variables are known
|
185
|
-
unknown_vars = self.get_unknown_variables()
|
186
|
-
return len(unknown_vars) == 0
|
187
|
-
|
188
|
-
def get_solving_order(self) -> list[str]:
|
189
|
-
"""Get the order in which variables can be solved."""
|
190
|
-
order = []
|
191
|
-
temp_system = EquationSystem(self.equations.copy())
|
192
|
-
temp_system.variables = self.variables.copy()
|
193
|
-
|
194
|
-
while temp_system.can_solve_any():
|
195
|
-
known_vars = temp_system.get_known_variables()
|
196
|
-
unknown_vars = temp_system.get_unknown_variables()
|
197
|
-
|
198
|
-
# Find next solvable variable
|
199
|
-
for equation in temp_system.equations:
|
200
|
-
for unknown_var in unknown_vars:
|
201
|
-
if equation.can_solve_for(unknown_var, known_vars):
|
202
|
-
order.append(unknown_var)
|
203
|
-
# Mark as known for next iteration
|
204
|
-
temp_system.variables[unknown_var].is_known = True
|
205
|
-
break
|
206
|
-
else:
|
207
|
-
continue
|
208
|
-
break
|
209
|
-
|
210
|
-
return order
|
211
|
-
|
212
|
-
def __str__(self) -> str:
|
213
|
-
return f"EquationSystem({len(self.equations)} equations, {len(self.variables)} variables)"
|
214
|
-
|
215
|
-
def __repr__(self) -> str:
|
216
|
-
return f"EquationSystem(equations={self.equations!r})"
|