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.
Files changed (76) 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/equations/equation.py +257 -0
  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/expressions/nodes.py +546 -0
  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/equation.py +0 -216
  63. qnty/expression.py +0 -492
  64. qnty/unit_types/base.py +0 -47
  65. qnty/units.py +0 -8113
  66. qnty/variable.py +0 -263
  67. qnty/variable_types/base.py +0 -58
  68. qnty/variable_types/expression_variable.py +0 -68
  69. qnty/variable_types/typed_variable.py +0 -87
  70. qnty/variables.py +0 -2298
  71. qnty/variables.pyi +0 -6148
  72. qnty-0.0.7.dist-info/METADATA +0 -355
  73. qnty-0.0.7.dist-info/RECORD +0 -19
  74. /qnty/{unit_types → codegen}/__init__.py +0 -0
  75. /qnty/{variable_types → codegen/generators}/__init__.py +0 -0
  76. {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
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
37
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
38
+ [![Development Status](https://img.shields.io/badge/status-beta-orange.svg)](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})"