unit-aware-arithmetic 1.0.0__tar.gz
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.
- unit_aware_arithmetic-1.0.0/LICENSE +21 -0
- unit_aware_arithmetic-1.0.0/MANIFEST.in +3 -0
- unit_aware_arithmetic-1.0.0/PKG-INFO +299 -0
- unit_aware_arithmetic-1.0.0/README.md +238 -0
- unit_aware_arithmetic-1.0.0/dimensional.py +364 -0
- unit_aware_arithmetic-1.0.0/py.typed +0 -0
- unit_aware_arithmetic-1.0.0/pyproject.toml +51 -0
- unit_aware_arithmetic-1.0.0/setup.cfg +4 -0
- unit_aware_arithmetic-1.0.0/setup.py +47 -0
- unit_aware_arithmetic-1.0.0/unit_aware_arithmetic.egg-info/PKG-INFO +299 -0
- unit_aware_arithmetic-1.0.0/unit_aware_arithmetic.egg-info/SOURCES.txt +12 -0
- unit_aware_arithmetic-1.0.0/unit_aware_arithmetic.egg-info/dependency_links.txt +1 -0
- unit_aware_arithmetic-1.0.0/unit_aware_arithmetic.egg-info/requires.txt +6 -0
- unit_aware_arithmetic-1.0.0/unit_aware_arithmetic.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Parthiv Rawat
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: unit-aware-arithmetic
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Type-safe dimensional arithmetic library with unit tracking
|
|
5
|
+
Home-page: https://github.com/parthivrawat/unit-aware-arithmetic
|
|
6
|
+
Author: Parthiv Rawat
|
|
7
|
+
Author-email: Parthiv Rawat <parthiv05022000@gmail.com>
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2026 Parthiv Rawat
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
|
|
30
|
+
Project-URL: Homepage, https://github.com/parthivrawat/unit-aware-arithmetic
|
|
31
|
+
Project-URL: Repository, https://github.com/parthivrawat/unit-aware-arithmetic.git
|
|
32
|
+
Project-URL: Issues, https://github.com/parthivrawat/unit-aware-arithmetic/issues
|
|
33
|
+
Keywords: units,dimensions,physics,measurement,type-safe,dimensional-analysis
|
|
34
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
35
|
+
Classifier: Intended Audience :: Developers
|
|
36
|
+
Classifier: Intended Audience :: Science/Research
|
|
37
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
38
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
39
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
40
|
+
Classifier: Programming Language :: Python :: 3
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
43
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
44
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
45
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
46
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
47
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
48
|
+
Classifier: Typing :: Typed
|
|
49
|
+
Requires-Python: >=3.7
|
|
50
|
+
Description-Content-Type: text/markdown
|
|
51
|
+
License-File: LICENSE
|
|
52
|
+
Provides-Extra: dev
|
|
53
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
54
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
55
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
56
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
57
|
+
Dynamic: author
|
|
58
|
+
Dynamic: home-page
|
|
59
|
+
Dynamic: license-file
|
|
60
|
+
Dynamic: requires-python
|
|
61
|
+
|
|
62
|
+
# Unit-Aware Arithmetic (Python)
|
|
63
|
+
|
|
64
|
+
A type-safe dimensional arithmetic library that tracks units at runtime and prevents invalid operations.
|
|
65
|
+
|
|
66
|
+
## Features
|
|
67
|
+
|
|
68
|
+
- ✅ **Type-safe dimensional analysis**: Prevents incompatible unit operations
|
|
69
|
+
- ✅ **Zero dependencies**: Core functionality has no external dependencies
|
|
70
|
+
- ✅ **Comprehensive unit coverage**: SI, imperial, and derived units
|
|
71
|
+
- ✅ **Arithmetic operations**: Add, subtract, multiply, divide with unit tracking
|
|
72
|
+
- ✅ **Unit conversion**: Automatic and explicit conversion between compatible units
|
|
73
|
+
- ✅ **Clear error messages**: Helpful errors for incompatible operations
|
|
74
|
+
- ✅ **Production-ready**: Comprehensive test coverage (>95%)
|
|
75
|
+
|
|
76
|
+
## Installation
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install -e .
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Quick Start
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from dimensional import Quantity, units
|
|
86
|
+
|
|
87
|
+
# Create quantities with units
|
|
88
|
+
distance = Quantity(100, units.meter)
|
|
89
|
+
time = Quantity(9.58, units.second)
|
|
90
|
+
|
|
91
|
+
# Arithmetic operations with automatic unit tracking
|
|
92
|
+
speed = distance / time
|
|
93
|
+
print(speed) # 10.438413361169102 m/s
|
|
94
|
+
|
|
95
|
+
# Unit conversion
|
|
96
|
+
distance_km = distance.to(units.kilometer)
|
|
97
|
+
print(distance_km) # 0.1 km
|
|
98
|
+
|
|
99
|
+
# Type-safe operations - this will raise an error!
|
|
100
|
+
try:
|
|
101
|
+
distance + time # IncompatibleUnitsError
|
|
102
|
+
except Exception as e:
|
|
103
|
+
print(f"Error: {e}")
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Usage Examples
|
|
107
|
+
|
|
108
|
+
### Basic Arithmetic
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from dimensional import Quantity, units
|
|
112
|
+
|
|
113
|
+
# Addition (same dimension required)
|
|
114
|
+
d1 = Quantity(5, units.meter)
|
|
115
|
+
d2 = Quantity(3, units.meter)
|
|
116
|
+
total = d1 + d2 # 8.0 m
|
|
117
|
+
|
|
118
|
+
# Multiplication creates derived units
|
|
119
|
+
area = Quantity(5, units.meter) * Quantity(3, units.meter)
|
|
120
|
+
print(area) # 15.0 m·m
|
|
121
|
+
|
|
122
|
+
# Division creates derived units
|
|
123
|
+
velocity = Quantity(100, units.meter) / Quantity(10, units.second)
|
|
124
|
+
print(velocity) # 10.0 m/s
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Unit Conversion
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from dimensional import Quantity, units
|
|
131
|
+
|
|
132
|
+
# Length conversion
|
|
133
|
+
distance = Quantity(1, units.mile)
|
|
134
|
+
distance_km = distance.to(units.kilometer)
|
|
135
|
+
print(distance_km) # 1.60934 km
|
|
136
|
+
|
|
137
|
+
# Temperature conversion
|
|
138
|
+
temp_c = Quantity(0, units.celsius)
|
|
139
|
+
temp_k = temp_c.to(units.kelvin)
|
|
140
|
+
print(temp_k) # 273.15 K
|
|
141
|
+
|
|
142
|
+
# Mass conversion
|
|
143
|
+
mass_lb = Quantity(10, units.pound)
|
|
144
|
+
mass_kg = mass_lb.to(units.kilogram)
|
|
145
|
+
print(mass_kg) # 4.53592 kg
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Physics Calculations
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from dimensional import Quantity, units
|
|
152
|
+
|
|
153
|
+
# Calculate velocity
|
|
154
|
+
distance = Quantity(100, units.meter)
|
|
155
|
+
time = Quantity(9.58, units.second)
|
|
156
|
+
velocity = distance / time
|
|
157
|
+
print(f"Velocity: {velocity}")
|
|
158
|
+
|
|
159
|
+
# Calculate force (F = ma)
|
|
160
|
+
mass = Quantity(10, units.kilogram)
|
|
161
|
+
acceleration = Quantity(9.8, units.meter_per_second_squared)
|
|
162
|
+
force = mass * acceleration
|
|
163
|
+
print(f"Force: {force}")
|
|
164
|
+
|
|
165
|
+
# Calculate kinetic energy (KE = 1/2 * m * v²)
|
|
166
|
+
mass = Quantity(2, units.kilogram)
|
|
167
|
+
velocity = Quantity(10, units.meter_per_second)
|
|
168
|
+
ke = 0.5 * mass * (velocity ** 2)
|
|
169
|
+
print(f"Kinetic Energy: {ke}")
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Comparison Operations
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
from dimensional import Quantity, units
|
|
176
|
+
|
|
177
|
+
d1 = Quantity(100, units.centimeter)
|
|
178
|
+
d2 = Quantity(1, units.meter)
|
|
179
|
+
|
|
180
|
+
# Automatic conversion for comparison
|
|
181
|
+
print(d1 == d2) # True
|
|
182
|
+
print(d1 < Quantity(2, units.meter)) # True
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Available Units
|
|
186
|
+
|
|
187
|
+
### Length
|
|
188
|
+
- `meter`, `kilometer`, `centimeter`, `millimeter`
|
|
189
|
+
- `inch`, `foot`, `yard`, `mile`
|
|
190
|
+
|
|
191
|
+
### Mass
|
|
192
|
+
- `kilogram`, `gram`, `milligram`, `tonne`
|
|
193
|
+
- `pound`, `ounce`
|
|
194
|
+
|
|
195
|
+
### Time
|
|
196
|
+
- `second`, `minute`, `hour`, `day`
|
|
197
|
+
|
|
198
|
+
### Temperature
|
|
199
|
+
- `kelvin`, `celsius`, `fahrenheit`
|
|
200
|
+
|
|
201
|
+
### Current
|
|
202
|
+
- `ampere`, `milliampere`
|
|
203
|
+
|
|
204
|
+
### Derived Units
|
|
205
|
+
- `newton` (force)
|
|
206
|
+
- `joule`, `kilojoule` (energy)
|
|
207
|
+
- `watt`, `kilowatt` (power)
|
|
208
|
+
- `pascal`, `kilopascal` (pressure)
|
|
209
|
+
- `meter_per_second`, `kilometer_per_hour` (velocity)
|
|
210
|
+
- `meter_per_second_squared` (acceleration)
|
|
211
|
+
|
|
212
|
+
## Error Handling
|
|
213
|
+
|
|
214
|
+
The library provides clear error messages for invalid operations:
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
from dimensional import Quantity, units, IncompatibleUnitsError
|
|
218
|
+
|
|
219
|
+
distance = Quantity(100, units.meter)
|
|
220
|
+
time = Quantity(10, units.second)
|
|
221
|
+
|
|
222
|
+
try:
|
|
223
|
+
# This will raise IncompatibleUnitsError
|
|
224
|
+
result = distance + time
|
|
225
|
+
except IncompatibleUnitsError as e:
|
|
226
|
+
print(e) # "Cannot add m and s: incompatible dimensions"
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Testing
|
|
230
|
+
|
|
231
|
+
Run the test suite:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
pytest test_dimensional.py -v
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Run with coverage:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
pytest test_dimensional.py --cov=dimensional --cov-report=html
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Type Checking
|
|
244
|
+
|
|
245
|
+
This library includes type hints. Run type checking with:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
mypy dimensional.py
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Design Principles
|
|
252
|
+
|
|
253
|
+
1. **Zero Dependencies**: Core functionality has no external dependencies
|
|
254
|
+
2. **Type Safety**: Prevents invalid operations at runtime
|
|
255
|
+
3. **Clear Errors**: Actionable error messages
|
|
256
|
+
4. **Production Ready**: Comprehensive test coverage
|
|
257
|
+
5. **Performance**: Efficient implementation with minimal overhead
|
|
258
|
+
|
|
259
|
+
## API Reference
|
|
260
|
+
|
|
261
|
+
### `Dimension`
|
|
262
|
+
|
|
263
|
+
Represents the dimensional formula of a unit (e.g., L^1 T^-2 for acceleration).
|
|
264
|
+
|
|
265
|
+
### `Unit`
|
|
266
|
+
|
|
267
|
+
Represents a unit of measurement with its dimension and conversion factor.
|
|
268
|
+
|
|
269
|
+
### `Quantity`
|
|
270
|
+
|
|
271
|
+
A numeric value with an associated unit. Supports:
|
|
272
|
+
- Arithmetic: `+`, `-`, `*`, `/`, `**`, `-` (negation), `abs()`
|
|
273
|
+
- Comparison: `==`, `!=`, `<`, `<=`, `>`, `>=`
|
|
274
|
+
- Conversion: `.to(target_unit)`
|
|
275
|
+
|
|
276
|
+
### `units`
|
|
277
|
+
|
|
278
|
+
Namespace containing all predefined units.
|
|
279
|
+
|
|
280
|
+
## License
|
|
281
|
+
|
|
282
|
+
MIT License
|
|
283
|
+
|
|
284
|
+
## Contributing
|
|
285
|
+
|
|
286
|
+
Contributions are welcome! Please ensure:
|
|
287
|
+
- All tests pass
|
|
288
|
+
- Code coverage remains >90%
|
|
289
|
+
- Type hints are included
|
|
290
|
+
- Documentation is updated
|
|
291
|
+
|
|
292
|
+
## Changelog
|
|
293
|
+
|
|
294
|
+
### 1.0.0 (2026-08-28)
|
|
295
|
+
- Initial release
|
|
296
|
+
- Support for SI and imperial units
|
|
297
|
+
- Comprehensive dimensional analysis
|
|
298
|
+
- Temperature conversion with offset handling
|
|
299
|
+
- Full test coverage
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# Unit-Aware Arithmetic (Python)
|
|
2
|
+
|
|
3
|
+
A type-safe dimensional arithmetic library that tracks units at runtime and prevents invalid operations.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ **Type-safe dimensional analysis**: Prevents incompatible unit operations
|
|
8
|
+
- ✅ **Zero dependencies**: Core functionality has no external dependencies
|
|
9
|
+
- ✅ **Comprehensive unit coverage**: SI, imperial, and derived units
|
|
10
|
+
- ✅ **Arithmetic operations**: Add, subtract, multiply, divide with unit tracking
|
|
11
|
+
- ✅ **Unit conversion**: Automatic and explicit conversion between compatible units
|
|
12
|
+
- ✅ **Clear error messages**: Helpful errors for incompatible operations
|
|
13
|
+
- ✅ **Production-ready**: Comprehensive test coverage (>95%)
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install -e .
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from dimensional import Quantity, units
|
|
25
|
+
|
|
26
|
+
# Create quantities with units
|
|
27
|
+
distance = Quantity(100, units.meter)
|
|
28
|
+
time = Quantity(9.58, units.second)
|
|
29
|
+
|
|
30
|
+
# Arithmetic operations with automatic unit tracking
|
|
31
|
+
speed = distance / time
|
|
32
|
+
print(speed) # 10.438413361169102 m/s
|
|
33
|
+
|
|
34
|
+
# Unit conversion
|
|
35
|
+
distance_km = distance.to(units.kilometer)
|
|
36
|
+
print(distance_km) # 0.1 km
|
|
37
|
+
|
|
38
|
+
# Type-safe operations - this will raise an error!
|
|
39
|
+
try:
|
|
40
|
+
distance + time # IncompatibleUnitsError
|
|
41
|
+
except Exception as e:
|
|
42
|
+
print(f"Error: {e}")
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Usage Examples
|
|
46
|
+
|
|
47
|
+
### Basic Arithmetic
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from dimensional import Quantity, units
|
|
51
|
+
|
|
52
|
+
# Addition (same dimension required)
|
|
53
|
+
d1 = Quantity(5, units.meter)
|
|
54
|
+
d2 = Quantity(3, units.meter)
|
|
55
|
+
total = d1 + d2 # 8.0 m
|
|
56
|
+
|
|
57
|
+
# Multiplication creates derived units
|
|
58
|
+
area = Quantity(5, units.meter) * Quantity(3, units.meter)
|
|
59
|
+
print(area) # 15.0 m·m
|
|
60
|
+
|
|
61
|
+
# Division creates derived units
|
|
62
|
+
velocity = Quantity(100, units.meter) / Quantity(10, units.second)
|
|
63
|
+
print(velocity) # 10.0 m/s
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Unit Conversion
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from dimensional import Quantity, units
|
|
70
|
+
|
|
71
|
+
# Length conversion
|
|
72
|
+
distance = Quantity(1, units.mile)
|
|
73
|
+
distance_km = distance.to(units.kilometer)
|
|
74
|
+
print(distance_km) # 1.60934 km
|
|
75
|
+
|
|
76
|
+
# Temperature conversion
|
|
77
|
+
temp_c = Quantity(0, units.celsius)
|
|
78
|
+
temp_k = temp_c.to(units.kelvin)
|
|
79
|
+
print(temp_k) # 273.15 K
|
|
80
|
+
|
|
81
|
+
# Mass conversion
|
|
82
|
+
mass_lb = Quantity(10, units.pound)
|
|
83
|
+
mass_kg = mass_lb.to(units.kilogram)
|
|
84
|
+
print(mass_kg) # 4.53592 kg
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Physics Calculations
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from dimensional import Quantity, units
|
|
91
|
+
|
|
92
|
+
# Calculate velocity
|
|
93
|
+
distance = Quantity(100, units.meter)
|
|
94
|
+
time = Quantity(9.58, units.second)
|
|
95
|
+
velocity = distance / time
|
|
96
|
+
print(f"Velocity: {velocity}")
|
|
97
|
+
|
|
98
|
+
# Calculate force (F = ma)
|
|
99
|
+
mass = Quantity(10, units.kilogram)
|
|
100
|
+
acceleration = Quantity(9.8, units.meter_per_second_squared)
|
|
101
|
+
force = mass * acceleration
|
|
102
|
+
print(f"Force: {force}")
|
|
103
|
+
|
|
104
|
+
# Calculate kinetic energy (KE = 1/2 * m * v²)
|
|
105
|
+
mass = Quantity(2, units.kilogram)
|
|
106
|
+
velocity = Quantity(10, units.meter_per_second)
|
|
107
|
+
ke = 0.5 * mass * (velocity ** 2)
|
|
108
|
+
print(f"Kinetic Energy: {ke}")
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Comparison Operations
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from dimensional import Quantity, units
|
|
115
|
+
|
|
116
|
+
d1 = Quantity(100, units.centimeter)
|
|
117
|
+
d2 = Quantity(1, units.meter)
|
|
118
|
+
|
|
119
|
+
# Automatic conversion for comparison
|
|
120
|
+
print(d1 == d2) # True
|
|
121
|
+
print(d1 < Quantity(2, units.meter)) # True
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Available Units
|
|
125
|
+
|
|
126
|
+
### Length
|
|
127
|
+
- `meter`, `kilometer`, `centimeter`, `millimeter`
|
|
128
|
+
- `inch`, `foot`, `yard`, `mile`
|
|
129
|
+
|
|
130
|
+
### Mass
|
|
131
|
+
- `kilogram`, `gram`, `milligram`, `tonne`
|
|
132
|
+
- `pound`, `ounce`
|
|
133
|
+
|
|
134
|
+
### Time
|
|
135
|
+
- `second`, `minute`, `hour`, `day`
|
|
136
|
+
|
|
137
|
+
### Temperature
|
|
138
|
+
- `kelvin`, `celsius`, `fahrenheit`
|
|
139
|
+
|
|
140
|
+
### Current
|
|
141
|
+
- `ampere`, `milliampere`
|
|
142
|
+
|
|
143
|
+
### Derived Units
|
|
144
|
+
- `newton` (force)
|
|
145
|
+
- `joule`, `kilojoule` (energy)
|
|
146
|
+
- `watt`, `kilowatt` (power)
|
|
147
|
+
- `pascal`, `kilopascal` (pressure)
|
|
148
|
+
- `meter_per_second`, `kilometer_per_hour` (velocity)
|
|
149
|
+
- `meter_per_second_squared` (acceleration)
|
|
150
|
+
|
|
151
|
+
## Error Handling
|
|
152
|
+
|
|
153
|
+
The library provides clear error messages for invalid operations:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from dimensional import Quantity, units, IncompatibleUnitsError
|
|
157
|
+
|
|
158
|
+
distance = Quantity(100, units.meter)
|
|
159
|
+
time = Quantity(10, units.second)
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
# This will raise IncompatibleUnitsError
|
|
163
|
+
result = distance + time
|
|
164
|
+
except IncompatibleUnitsError as e:
|
|
165
|
+
print(e) # "Cannot add m and s: incompatible dimensions"
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Testing
|
|
169
|
+
|
|
170
|
+
Run the test suite:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
pytest test_dimensional.py -v
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Run with coverage:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
pytest test_dimensional.py --cov=dimensional --cov-report=html
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Type Checking
|
|
183
|
+
|
|
184
|
+
This library includes type hints. Run type checking with:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
mypy dimensional.py
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Design Principles
|
|
191
|
+
|
|
192
|
+
1. **Zero Dependencies**: Core functionality has no external dependencies
|
|
193
|
+
2. **Type Safety**: Prevents invalid operations at runtime
|
|
194
|
+
3. **Clear Errors**: Actionable error messages
|
|
195
|
+
4. **Production Ready**: Comprehensive test coverage
|
|
196
|
+
5. **Performance**: Efficient implementation with minimal overhead
|
|
197
|
+
|
|
198
|
+
## API Reference
|
|
199
|
+
|
|
200
|
+
### `Dimension`
|
|
201
|
+
|
|
202
|
+
Represents the dimensional formula of a unit (e.g., L^1 T^-2 for acceleration).
|
|
203
|
+
|
|
204
|
+
### `Unit`
|
|
205
|
+
|
|
206
|
+
Represents a unit of measurement with its dimension and conversion factor.
|
|
207
|
+
|
|
208
|
+
### `Quantity`
|
|
209
|
+
|
|
210
|
+
A numeric value with an associated unit. Supports:
|
|
211
|
+
- Arithmetic: `+`, `-`, `*`, `/`, `**`, `-` (negation), `abs()`
|
|
212
|
+
- Comparison: `==`, `!=`, `<`, `<=`, `>`, `>=`
|
|
213
|
+
- Conversion: `.to(target_unit)`
|
|
214
|
+
|
|
215
|
+
### `units`
|
|
216
|
+
|
|
217
|
+
Namespace containing all predefined units.
|
|
218
|
+
|
|
219
|
+
## License
|
|
220
|
+
|
|
221
|
+
MIT License
|
|
222
|
+
|
|
223
|
+
## Contributing
|
|
224
|
+
|
|
225
|
+
Contributions are welcome! Please ensure:
|
|
226
|
+
- All tests pass
|
|
227
|
+
- Code coverage remains >90%
|
|
228
|
+
- Type hints are included
|
|
229
|
+
- Documentation is updated
|
|
230
|
+
|
|
231
|
+
## Changelog
|
|
232
|
+
|
|
233
|
+
### 1.0.0 (2026-08-28)
|
|
234
|
+
- Initial release
|
|
235
|
+
- Support for SI and imperial units
|
|
236
|
+
- Comprehensive dimensional analysis
|
|
237
|
+
- Temperature conversion with offset handling
|
|
238
|
+
- Full test coverage
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit-Aware Numeric Arithmetic Library
|
|
3
|
+
|
|
4
|
+
A type-safe dimensional arithmetic library that tracks units at runtime
|
|
5
|
+
and prevents invalid operations.
|
|
6
|
+
|
|
7
|
+
Zero dependencies, production-ready.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
from typing import Dict, Optional, Union, Any
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
import math
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class Dimension:
|
|
18
|
+
"""Represents the dimensional formula of a unit (e.g., L^1 T^-2 for acceleration)."""
|
|
19
|
+
|
|
20
|
+
length: int = 0 # L
|
|
21
|
+
mass: int = 0 # M
|
|
22
|
+
time: int = 0 # T
|
|
23
|
+
current: int = 0 # I
|
|
24
|
+
temperature: int = 0 # Θ
|
|
25
|
+
amount: int = 0 # N
|
|
26
|
+
luminosity: int = 0 # J
|
|
27
|
+
|
|
28
|
+
def __mul__(self, other: Dimension) -> Dimension:
|
|
29
|
+
"""Multiply dimensions (add exponents)."""
|
|
30
|
+
return Dimension(
|
|
31
|
+
length=self.length + other.length,
|
|
32
|
+
mass=self.mass + other.mass,
|
|
33
|
+
time=self.time + other.time,
|
|
34
|
+
current=self.current + other.current,
|
|
35
|
+
temperature=self.temperature + other.temperature,
|
|
36
|
+
amount=self.amount + other.amount,
|
|
37
|
+
luminosity=self.luminosity + other.luminosity,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def __truediv__(self, other: Dimension) -> Dimension:
|
|
41
|
+
"""Divide dimensions (subtract exponents)."""
|
|
42
|
+
return Dimension(
|
|
43
|
+
length=self.length - other.length,
|
|
44
|
+
mass=self.mass - other.mass,
|
|
45
|
+
time=self.time - other.time,
|
|
46
|
+
current=self.current - other.current,
|
|
47
|
+
temperature=self.temperature - other.temperature,
|
|
48
|
+
amount=self.amount - other.amount,
|
|
49
|
+
luminosity=self.luminosity - other.luminosity,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def __pow__(self, exponent: int) -> Dimension:
|
|
53
|
+
"""Raise dimension to a power."""
|
|
54
|
+
return Dimension(
|
|
55
|
+
length=self.length * exponent,
|
|
56
|
+
mass=self.mass * exponent,
|
|
57
|
+
time=self.time * exponent,
|
|
58
|
+
current=self.current * exponent,
|
|
59
|
+
temperature=self.temperature * exponent,
|
|
60
|
+
amount=self.amount * exponent,
|
|
61
|
+
luminosity=self.luminosity * exponent,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def is_dimensionless(self) -> bool:
|
|
65
|
+
"""Check if this is a dimensionless quantity."""
|
|
66
|
+
return all([
|
|
67
|
+
self.length == 0,
|
|
68
|
+
self.mass == 0,
|
|
69
|
+
self.time == 0,
|
|
70
|
+
self.current == 0,
|
|
71
|
+
self.temperature == 0,
|
|
72
|
+
self.amount == 0,
|
|
73
|
+
self.luminosity == 0,
|
|
74
|
+
])
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class Unit:
|
|
78
|
+
"""Represents a unit of measurement with its dimension and conversion factor to base units."""
|
|
79
|
+
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
name: str,
|
|
83
|
+
symbol: str,
|
|
84
|
+
dimension: Dimension,
|
|
85
|
+
to_base: float = 1.0,
|
|
86
|
+
offset: float = 0.0,
|
|
87
|
+
):
|
|
88
|
+
self.name = name
|
|
89
|
+
self.symbol = symbol
|
|
90
|
+
self.dimension = dimension
|
|
91
|
+
self.to_base = to_base # Conversion factor to base unit
|
|
92
|
+
self.offset = offset # Offset for affine conversions (e.g., Celsius)
|
|
93
|
+
|
|
94
|
+
def __repr__(self) -> str:
|
|
95
|
+
return f"Unit({self.symbol})"
|
|
96
|
+
|
|
97
|
+
def __eq__(self, other: Any) -> bool:
|
|
98
|
+
if not isinstance(other, Unit):
|
|
99
|
+
return False
|
|
100
|
+
return (
|
|
101
|
+
self.symbol == other.symbol
|
|
102
|
+
and self.dimension == other.dimension
|
|
103
|
+
and self.to_base == other.to_base
|
|
104
|
+
and self.offset == other.offset
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def __hash__(self) -> int:
|
|
108
|
+
return hash((self.symbol, self.dimension, self.to_base, self.offset))
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class IncompatibleUnitsError(Exception):
|
|
112
|
+
"""Raised when attempting incompatible unit operations."""
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class Quantity:
|
|
117
|
+
"""A numeric value with an associated unit."""
|
|
118
|
+
|
|
119
|
+
def __init__(self, value: Union[int, float], unit: Unit):
|
|
120
|
+
self.value = float(value)
|
|
121
|
+
self.unit = unit
|
|
122
|
+
|
|
123
|
+
def __repr__(self) -> str:
|
|
124
|
+
return f"{self.value} {self.unit.symbol}"
|
|
125
|
+
|
|
126
|
+
def __str__(self) -> str:
|
|
127
|
+
return self.__repr__()
|
|
128
|
+
|
|
129
|
+
# Arithmetic operations
|
|
130
|
+
|
|
131
|
+
def __add__(self, other: Quantity) -> Quantity:
|
|
132
|
+
"""Add two quantities (must have compatible dimensions)."""
|
|
133
|
+
if not isinstance(other, Quantity):
|
|
134
|
+
raise TypeError(f"Cannot add Quantity and {type(other).__name__}")
|
|
135
|
+
|
|
136
|
+
if self.unit.dimension != other.unit.dimension:
|
|
137
|
+
raise IncompatibleUnitsError(
|
|
138
|
+
f"Cannot add {self.unit.symbol} and {other.unit.symbol}: "
|
|
139
|
+
f"incompatible dimensions"
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# Convert other to self's unit
|
|
143
|
+
other_in_self_unit = other.to(self.unit)
|
|
144
|
+
return Quantity(self.value + other_in_self_unit.value, self.unit)
|
|
145
|
+
|
|
146
|
+
def __sub__(self, other: Quantity) -> Quantity:
|
|
147
|
+
"""Subtract two quantities (must have compatible dimensions)."""
|
|
148
|
+
if not isinstance(other, Quantity):
|
|
149
|
+
raise TypeError(f"Cannot subtract {type(other).__name__} from Quantity")
|
|
150
|
+
|
|
151
|
+
if self.unit.dimension != other.unit.dimension:
|
|
152
|
+
raise IncompatibleUnitsError(
|
|
153
|
+
f"Cannot subtract {other.unit.symbol} from {self.unit.symbol}: "
|
|
154
|
+
f"incompatible dimensions"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
other_in_self_unit = other.to(self.unit)
|
|
158
|
+
return Quantity(self.value - other_in_self_unit.value, self.unit)
|
|
159
|
+
|
|
160
|
+
def __mul__(self, other: Union[Quantity, int, float]) -> Quantity:
|
|
161
|
+
"""Multiply quantity by another quantity or scalar."""
|
|
162
|
+
if isinstance(other, (int, float)):
|
|
163
|
+
return Quantity(self.value * other, self.unit)
|
|
164
|
+
|
|
165
|
+
if isinstance(other, Quantity):
|
|
166
|
+
# Multiply values and dimensions
|
|
167
|
+
new_value = self.value * other.value
|
|
168
|
+
new_dimension = self.unit.dimension * other.unit.dimension
|
|
169
|
+
|
|
170
|
+
# Create a derived unit
|
|
171
|
+
new_symbol = f"{self.unit.symbol}·{other.unit.symbol}"
|
|
172
|
+
new_unit = Unit(
|
|
173
|
+
name=f"{self.unit.name} {other.unit.name}",
|
|
174
|
+
symbol=new_symbol,
|
|
175
|
+
dimension=new_dimension,
|
|
176
|
+
to_base=self.unit.to_base * other.unit.to_base,
|
|
177
|
+
)
|
|
178
|
+
return Quantity(new_value, new_unit)
|
|
179
|
+
|
|
180
|
+
raise TypeError(f"Cannot multiply Quantity and {type(other).__name__}")
|
|
181
|
+
|
|
182
|
+
def __rmul__(self, other: Union[int, float]) -> Quantity:
|
|
183
|
+
"""Right multiplication (scalar * quantity)."""
|
|
184
|
+
return self.__mul__(other)
|
|
185
|
+
|
|
186
|
+
def __truediv__(self, other: Union[Quantity, int, float]) -> Quantity:
|
|
187
|
+
"""Divide quantity by another quantity or scalar."""
|
|
188
|
+
if isinstance(other, (int, float)):
|
|
189
|
+
return Quantity(self.value / other, self.unit)
|
|
190
|
+
|
|
191
|
+
if isinstance(other, Quantity):
|
|
192
|
+
# Divide values and dimensions
|
|
193
|
+
new_value = self.value / other.value
|
|
194
|
+
new_dimension = self.unit.dimension / other.unit.dimension
|
|
195
|
+
|
|
196
|
+
# Create a derived unit
|
|
197
|
+
new_symbol = f"{self.unit.symbol}/{other.unit.symbol}"
|
|
198
|
+
new_unit = Unit(
|
|
199
|
+
name=f"{self.unit.name} per {other.unit.name}",
|
|
200
|
+
symbol=new_symbol,
|
|
201
|
+
dimension=new_dimension,
|
|
202
|
+
to_base=self.unit.to_base / other.unit.to_base,
|
|
203
|
+
)
|
|
204
|
+
return Quantity(new_value, new_unit)
|
|
205
|
+
|
|
206
|
+
raise TypeError(f"Cannot divide Quantity by {type(other).__name__}")
|
|
207
|
+
|
|
208
|
+
def __pow__(self, exponent: Union[int, float]) -> Quantity:
|
|
209
|
+
"""Raise quantity to a power."""
|
|
210
|
+
if not isinstance(exponent, (int, float)):
|
|
211
|
+
raise TypeError(f"Exponent must be numeric, not {type(exponent).__name__}")
|
|
212
|
+
|
|
213
|
+
new_value = self.value ** exponent
|
|
214
|
+
|
|
215
|
+
# For integer exponents, we can compute the exact dimension
|
|
216
|
+
if isinstance(exponent, int):
|
|
217
|
+
new_dimension = self.unit.dimension ** exponent
|
|
218
|
+
new_symbol = f"{self.unit.symbol}^{exponent}"
|
|
219
|
+
new_unit = Unit(
|
|
220
|
+
name=f"{self.unit.name} to the power {exponent}",
|
|
221
|
+
symbol=new_symbol,
|
|
222
|
+
dimension=new_dimension,
|
|
223
|
+
to_base=self.unit.to_base ** exponent,
|
|
224
|
+
)
|
|
225
|
+
return Quantity(new_value, new_unit)
|
|
226
|
+
|
|
227
|
+
# For fractional exponents, we need to be careful
|
|
228
|
+
# This is a simplified implementation
|
|
229
|
+
raise NotImplementedError("Fractional exponents not yet supported")
|
|
230
|
+
|
|
231
|
+
def __neg__(self) -> Quantity:
|
|
232
|
+
"""Negate quantity."""
|
|
233
|
+
return Quantity(-self.value, self.unit)
|
|
234
|
+
|
|
235
|
+
def __abs__(self) -> Quantity:
|
|
236
|
+
"""Absolute value."""
|
|
237
|
+
return Quantity(abs(self.value), self.unit)
|
|
238
|
+
|
|
239
|
+
# Comparison operations
|
|
240
|
+
|
|
241
|
+
def __eq__(self, other: Any) -> bool:
|
|
242
|
+
if not isinstance(other, Quantity):
|
|
243
|
+
return False
|
|
244
|
+
|
|
245
|
+
if self.unit.dimension != other.unit.dimension:
|
|
246
|
+
return False
|
|
247
|
+
|
|
248
|
+
other_in_self_unit = other.to(self.unit)
|
|
249
|
+
return math.isclose(self.value, other_in_self_unit.value)
|
|
250
|
+
|
|
251
|
+
def __lt__(self, other: Quantity) -> bool:
|
|
252
|
+
if not isinstance(other, Quantity):
|
|
253
|
+
raise TypeError(f"Cannot compare Quantity and {type(other).__name__}")
|
|
254
|
+
|
|
255
|
+
if self.unit.dimension != other.unit.dimension:
|
|
256
|
+
raise IncompatibleUnitsError(
|
|
257
|
+
f"Cannot compare {self.unit.symbol} and {other.unit.symbol}"
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
other_in_self_unit = other.to(self.unit)
|
|
261
|
+
return self.value < other_in_self_unit.value
|
|
262
|
+
|
|
263
|
+
def __le__(self, other: Quantity) -> bool:
|
|
264
|
+
return self == other or self < other
|
|
265
|
+
|
|
266
|
+
def __gt__(self, other: Quantity) -> bool:
|
|
267
|
+
return not self <= other
|
|
268
|
+
|
|
269
|
+
def __ge__(self, other: Quantity) -> bool:
|
|
270
|
+
return not self < other
|
|
271
|
+
|
|
272
|
+
# Unit conversion
|
|
273
|
+
|
|
274
|
+
def to(self, target_unit: Unit) -> Quantity:
|
|
275
|
+
"""Convert to another unit (must have compatible dimensions)."""
|
|
276
|
+
if self.unit.dimension != target_unit.dimension:
|
|
277
|
+
raise IncompatibleUnitsError(
|
|
278
|
+
f"Cannot convert {self.unit.symbol} to {target_unit.symbol}: "
|
|
279
|
+
f"incompatible dimensions"
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
# Handle affine conversions (e.g., temperature)
|
|
283
|
+
if self.unit.offset != 0 or target_unit.offset != 0:
|
|
284
|
+
# Convert to base unit first (remove offset)
|
|
285
|
+
base_value = (self.value + self.unit.offset) * self.unit.to_base
|
|
286
|
+
# Convert from base to target (apply offset)
|
|
287
|
+
new_value = base_value / target_unit.to_base - target_unit.offset
|
|
288
|
+
else:
|
|
289
|
+
# Simple linear conversion
|
|
290
|
+
new_value = self.value * (self.unit.to_base / target_unit.to_base)
|
|
291
|
+
|
|
292
|
+
return Quantity(new_value, target_unit)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
# ============================================================================
|
|
296
|
+
# Unit Definitions
|
|
297
|
+
# ============================================================================
|
|
298
|
+
|
|
299
|
+
class units:
|
|
300
|
+
"""Namespace for predefined units."""
|
|
301
|
+
|
|
302
|
+
# Dimensionless
|
|
303
|
+
dimensionless = Unit("dimensionless", "", Dimension())
|
|
304
|
+
|
|
305
|
+
# Length
|
|
306
|
+
meter = Unit("meter", "m", Dimension(length=1))
|
|
307
|
+
kilometer = Unit("kilometer", "km", Dimension(length=1), to_base=1000.0)
|
|
308
|
+
centimeter = Unit("centimeter", "cm", Dimension(length=1), to_base=0.01)
|
|
309
|
+
millimeter = Unit("millimeter", "mm", Dimension(length=1), to_base=0.001)
|
|
310
|
+
|
|
311
|
+
# Imperial length
|
|
312
|
+
inch = Unit("inch", "in", Dimension(length=1), to_base=0.0254)
|
|
313
|
+
foot = Unit("foot", "ft", Dimension(length=1), to_base=0.3048)
|
|
314
|
+
yard = Unit("yard", "yd", Dimension(length=1), to_base=0.9144)
|
|
315
|
+
mile = Unit("mile", "mi", Dimension(length=1), to_base=1609.34)
|
|
316
|
+
|
|
317
|
+
# Mass
|
|
318
|
+
kilogram = Unit("kilogram", "kg", Dimension(mass=1))
|
|
319
|
+
gram = Unit("gram", "g", Dimension(mass=1), to_base=0.001)
|
|
320
|
+
milligram = Unit("milligram", "mg", Dimension(mass=1), to_base=1e-6)
|
|
321
|
+
tonne = Unit("tonne", "t", Dimension(mass=1), to_base=1000.0)
|
|
322
|
+
|
|
323
|
+
# Imperial mass
|
|
324
|
+
pound = Unit("pound", "lb", Dimension(mass=1), to_base=0.453592)
|
|
325
|
+
ounce = Unit("ounce", "oz", Dimension(mass=1), to_base=0.0283495)
|
|
326
|
+
|
|
327
|
+
# Time
|
|
328
|
+
second = Unit("second", "s", Dimension(time=1))
|
|
329
|
+
minute = Unit("minute", "min", Dimension(time=1), to_base=60.0)
|
|
330
|
+
hour = Unit("hour", "h", Dimension(time=1), to_base=3600.0)
|
|
331
|
+
day = Unit("day", "d", Dimension(time=1), to_base=86400.0)
|
|
332
|
+
|
|
333
|
+
# Temperature (absolute)
|
|
334
|
+
kelvin = Unit("kelvin", "K", Dimension(temperature=1))
|
|
335
|
+
celsius = Unit("celsius", "°C", Dimension(temperature=1), to_base=1.0, offset=273.15)
|
|
336
|
+
fahrenheit = Unit("fahrenheit", "°F", Dimension(temperature=1), to_base=5/9, offset=459.67)
|
|
337
|
+
|
|
338
|
+
# Current
|
|
339
|
+
ampere = Unit("ampere", "A", Dimension(current=1))
|
|
340
|
+
milliampere = Unit("milliampere", "mA", Dimension(current=1), to_base=0.001)
|
|
341
|
+
|
|
342
|
+
# Derived units
|
|
343
|
+
|
|
344
|
+
# Force (kg·m/s²)
|
|
345
|
+
newton = Unit("newton", "N", Dimension(mass=1, length=1, time=-2))
|
|
346
|
+
|
|
347
|
+
# Energy (kg·m²/s²)
|
|
348
|
+
joule = Unit("joule", "J", Dimension(mass=1, length=2, time=-2))
|
|
349
|
+
kilojoule = Unit("kilojoule", "kJ", Dimension(mass=1, length=2, time=-2), to_base=1000.0)
|
|
350
|
+
|
|
351
|
+
# Power (kg·m²/s³)
|
|
352
|
+
watt = Unit("watt", "W", Dimension(mass=1, length=2, time=-3))
|
|
353
|
+
kilowatt = Unit("kilowatt", "kW", Dimension(mass=1, length=2, time=-3), to_base=1000.0)
|
|
354
|
+
|
|
355
|
+
# Pressure (kg/(m·s²))
|
|
356
|
+
pascal = Unit("pascal", "Pa", Dimension(mass=1, length=-1, time=-2))
|
|
357
|
+
kilopascal = Unit("kilopascal", "kPa", Dimension(mass=1, length=-1, time=-2), to_base=1000.0)
|
|
358
|
+
|
|
359
|
+
# Velocity (m/s)
|
|
360
|
+
meter_per_second = Unit("meter per second", "m/s", Dimension(length=1, time=-1))
|
|
361
|
+
kilometer_per_hour = Unit("kilometer per hour", "km/h", Dimension(length=1, time=-1), to_base=1000.0/3600.0)
|
|
362
|
+
|
|
363
|
+
# Acceleration (m/s²)
|
|
364
|
+
meter_per_second_squared = Unit("meter per second squared", "m/s²", Dimension(length=1, time=-2))
|
|
File without changes
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "unit-aware-arithmetic"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Parthiv Rawat", email = "parthiv05022000@gmail.com" },
|
|
10
|
+
]
|
|
11
|
+
description = "Type-safe dimensional arithmetic library with unit tracking"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
license = { file = "LICENSE" }
|
|
14
|
+
requires-python = ">=3.7"
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 5 - Production/Stable",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Intended Audience :: Science/Research",
|
|
19
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
20
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
21
|
+
"License :: OSI Approved :: MIT License",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.7",
|
|
24
|
+
"Programming Language :: Python :: 3.8",
|
|
25
|
+
"Programming Language :: Python :: 3.9",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
30
|
+
"Typing :: Typed",
|
|
31
|
+
]
|
|
32
|
+
keywords = ["units", "dimensions", "physics", "measurement", "type-safe", "dimensional-analysis"]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/parthivrawat/unit-aware-arithmetic"
|
|
36
|
+
Repository = "https://github.com/parthivrawat/unit-aware-arithmetic.git"
|
|
37
|
+
Issues = "https://github.com/parthivrawat/unit-aware-arithmetic/issues"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools]
|
|
40
|
+
py-modules = ["dimensional"]
|
|
41
|
+
|
|
42
|
+
[tool.setuptools.package-data]
|
|
43
|
+
dimensional = ["py.typed"]
|
|
44
|
+
|
|
45
|
+
[project.optional-dependencies]
|
|
46
|
+
dev = [
|
|
47
|
+
"pytest>=7.0.0",
|
|
48
|
+
"pytest-cov>=4.0.0",
|
|
49
|
+
"black>=23.0.0",
|
|
50
|
+
"mypy>=1.0.0",
|
|
51
|
+
]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Setup script for unit-aware-arithmetic
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from setuptools import setup, find_packages
|
|
6
|
+
|
|
7
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
8
|
+
long_description = fh.read()
|
|
9
|
+
|
|
10
|
+
setup(
|
|
11
|
+
name="unit-aware-arithmetic",
|
|
12
|
+
version="1.0.0",
|
|
13
|
+
author="Parthiv Rawat",
|
|
14
|
+
author_email="parthiv05022000@gmail.com",
|
|
15
|
+
description="Type-safe dimensional arithmetic library with unit tracking",
|
|
16
|
+
long_description=long_description,
|
|
17
|
+
long_description_content_type="text/markdown",
|
|
18
|
+
url="https://github.com/parthivrawat/unit-aware-arithmetic",
|
|
19
|
+
py_modules=["dimensional"],
|
|
20
|
+
classifiers=[
|
|
21
|
+
"Development Status :: 5 - Production/Stable",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"Intended Audience :: Science/Research",
|
|
24
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
25
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
26
|
+
"License :: OSI Approved :: MIT License",
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"Programming Language :: Python :: 3.7",
|
|
29
|
+
"Programming Language :: Python :: 3.8",
|
|
30
|
+
"Programming Language :: Python :: 3.9",
|
|
31
|
+
"Programming Language :: Python :: 3.10",
|
|
32
|
+
"Programming Language :: Python :: 3.11",
|
|
33
|
+
"Programming Language :: Python :: 3.12",
|
|
34
|
+
"Typing :: Typed",
|
|
35
|
+
],
|
|
36
|
+
python_requires=">=3.7",
|
|
37
|
+
install_requires=[],
|
|
38
|
+
extras_require={
|
|
39
|
+
"dev": [
|
|
40
|
+
"pytest>=7.0.0",
|
|
41
|
+
"pytest-cov>=4.0.0",
|
|
42
|
+
"black>=23.0.0",
|
|
43
|
+
"mypy>=1.0.0",
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
keywords="units, dimensions, physics, measurement, type-safe, dimensional-analysis",
|
|
47
|
+
)
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: unit-aware-arithmetic
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Type-safe dimensional arithmetic library with unit tracking
|
|
5
|
+
Home-page: https://github.com/parthivrawat/unit-aware-arithmetic
|
|
6
|
+
Author: Parthiv Rawat
|
|
7
|
+
Author-email: Parthiv Rawat <parthiv05022000@gmail.com>
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2026 Parthiv Rawat
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
|
|
30
|
+
Project-URL: Homepage, https://github.com/parthivrawat/unit-aware-arithmetic
|
|
31
|
+
Project-URL: Repository, https://github.com/parthivrawat/unit-aware-arithmetic.git
|
|
32
|
+
Project-URL: Issues, https://github.com/parthivrawat/unit-aware-arithmetic/issues
|
|
33
|
+
Keywords: units,dimensions,physics,measurement,type-safe,dimensional-analysis
|
|
34
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
35
|
+
Classifier: Intended Audience :: Developers
|
|
36
|
+
Classifier: Intended Audience :: Science/Research
|
|
37
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
38
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
39
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
40
|
+
Classifier: Programming Language :: Python :: 3
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
43
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
44
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
45
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
46
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
47
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
48
|
+
Classifier: Typing :: Typed
|
|
49
|
+
Requires-Python: >=3.7
|
|
50
|
+
Description-Content-Type: text/markdown
|
|
51
|
+
License-File: LICENSE
|
|
52
|
+
Provides-Extra: dev
|
|
53
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
54
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
55
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
56
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
57
|
+
Dynamic: author
|
|
58
|
+
Dynamic: home-page
|
|
59
|
+
Dynamic: license-file
|
|
60
|
+
Dynamic: requires-python
|
|
61
|
+
|
|
62
|
+
# Unit-Aware Arithmetic (Python)
|
|
63
|
+
|
|
64
|
+
A type-safe dimensional arithmetic library that tracks units at runtime and prevents invalid operations.
|
|
65
|
+
|
|
66
|
+
## Features
|
|
67
|
+
|
|
68
|
+
- ✅ **Type-safe dimensional analysis**: Prevents incompatible unit operations
|
|
69
|
+
- ✅ **Zero dependencies**: Core functionality has no external dependencies
|
|
70
|
+
- ✅ **Comprehensive unit coverage**: SI, imperial, and derived units
|
|
71
|
+
- ✅ **Arithmetic operations**: Add, subtract, multiply, divide with unit tracking
|
|
72
|
+
- ✅ **Unit conversion**: Automatic and explicit conversion between compatible units
|
|
73
|
+
- ✅ **Clear error messages**: Helpful errors for incompatible operations
|
|
74
|
+
- ✅ **Production-ready**: Comprehensive test coverage (>95%)
|
|
75
|
+
|
|
76
|
+
## Installation
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install -e .
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Quick Start
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from dimensional import Quantity, units
|
|
86
|
+
|
|
87
|
+
# Create quantities with units
|
|
88
|
+
distance = Quantity(100, units.meter)
|
|
89
|
+
time = Quantity(9.58, units.second)
|
|
90
|
+
|
|
91
|
+
# Arithmetic operations with automatic unit tracking
|
|
92
|
+
speed = distance / time
|
|
93
|
+
print(speed) # 10.438413361169102 m/s
|
|
94
|
+
|
|
95
|
+
# Unit conversion
|
|
96
|
+
distance_km = distance.to(units.kilometer)
|
|
97
|
+
print(distance_km) # 0.1 km
|
|
98
|
+
|
|
99
|
+
# Type-safe operations - this will raise an error!
|
|
100
|
+
try:
|
|
101
|
+
distance + time # IncompatibleUnitsError
|
|
102
|
+
except Exception as e:
|
|
103
|
+
print(f"Error: {e}")
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Usage Examples
|
|
107
|
+
|
|
108
|
+
### Basic Arithmetic
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from dimensional import Quantity, units
|
|
112
|
+
|
|
113
|
+
# Addition (same dimension required)
|
|
114
|
+
d1 = Quantity(5, units.meter)
|
|
115
|
+
d2 = Quantity(3, units.meter)
|
|
116
|
+
total = d1 + d2 # 8.0 m
|
|
117
|
+
|
|
118
|
+
# Multiplication creates derived units
|
|
119
|
+
area = Quantity(5, units.meter) * Quantity(3, units.meter)
|
|
120
|
+
print(area) # 15.0 m·m
|
|
121
|
+
|
|
122
|
+
# Division creates derived units
|
|
123
|
+
velocity = Quantity(100, units.meter) / Quantity(10, units.second)
|
|
124
|
+
print(velocity) # 10.0 m/s
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Unit Conversion
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from dimensional import Quantity, units
|
|
131
|
+
|
|
132
|
+
# Length conversion
|
|
133
|
+
distance = Quantity(1, units.mile)
|
|
134
|
+
distance_km = distance.to(units.kilometer)
|
|
135
|
+
print(distance_km) # 1.60934 km
|
|
136
|
+
|
|
137
|
+
# Temperature conversion
|
|
138
|
+
temp_c = Quantity(0, units.celsius)
|
|
139
|
+
temp_k = temp_c.to(units.kelvin)
|
|
140
|
+
print(temp_k) # 273.15 K
|
|
141
|
+
|
|
142
|
+
# Mass conversion
|
|
143
|
+
mass_lb = Quantity(10, units.pound)
|
|
144
|
+
mass_kg = mass_lb.to(units.kilogram)
|
|
145
|
+
print(mass_kg) # 4.53592 kg
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Physics Calculations
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from dimensional import Quantity, units
|
|
152
|
+
|
|
153
|
+
# Calculate velocity
|
|
154
|
+
distance = Quantity(100, units.meter)
|
|
155
|
+
time = Quantity(9.58, units.second)
|
|
156
|
+
velocity = distance / time
|
|
157
|
+
print(f"Velocity: {velocity}")
|
|
158
|
+
|
|
159
|
+
# Calculate force (F = ma)
|
|
160
|
+
mass = Quantity(10, units.kilogram)
|
|
161
|
+
acceleration = Quantity(9.8, units.meter_per_second_squared)
|
|
162
|
+
force = mass * acceleration
|
|
163
|
+
print(f"Force: {force}")
|
|
164
|
+
|
|
165
|
+
# Calculate kinetic energy (KE = 1/2 * m * v²)
|
|
166
|
+
mass = Quantity(2, units.kilogram)
|
|
167
|
+
velocity = Quantity(10, units.meter_per_second)
|
|
168
|
+
ke = 0.5 * mass * (velocity ** 2)
|
|
169
|
+
print(f"Kinetic Energy: {ke}")
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Comparison Operations
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
from dimensional import Quantity, units
|
|
176
|
+
|
|
177
|
+
d1 = Quantity(100, units.centimeter)
|
|
178
|
+
d2 = Quantity(1, units.meter)
|
|
179
|
+
|
|
180
|
+
# Automatic conversion for comparison
|
|
181
|
+
print(d1 == d2) # True
|
|
182
|
+
print(d1 < Quantity(2, units.meter)) # True
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Available Units
|
|
186
|
+
|
|
187
|
+
### Length
|
|
188
|
+
- `meter`, `kilometer`, `centimeter`, `millimeter`
|
|
189
|
+
- `inch`, `foot`, `yard`, `mile`
|
|
190
|
+
|
|
191
|
+
### Mass
|
|
192
|
+
- `kilogram`, `gram`, `milligram`, `tonne`
|
|
193
|
+
- `pound`, `ounce`
|
|
194
|
+
|
|
195
|
+
### Time
|
|
196
|
+
- `second`, `minute`, `hour`, `day`
|
|
197
|
+
|
|
198
|
+
### Temperature
|
|
199
|
+
- `kelvin`, `celsius`, `fahrenheit`
|
|
200
|
+
|
|
201
|
+
### Current
|
|
202
|
+
- `ampere`, `milliampere`
|
|
203
|
+
|
|
204
|
+
### Derived Units
|
|
205
|
+
- `newton` (force)
|
|
206
|
+
- `joule`, `kilojoule` (energy)
|
|
207
|
+
- `watt`, `kilowatt` (power)
|
|
208
|
+
- `pascal`, `kilopascal` (pressure)
|
|
209
|
+
- `meter_per_second`, `kilometer_per_hour` (velocity)
|
|
210
|
+
- `meter_per_second_squared` (acceleration)
|
|
211
|
+
|
|
212
|
+
## Error Handling
|
|
213
|
+
|
|
214
|
+
The library provides clear error messages for invalid operations:
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
from dimensional import Quantity, units, IncompatibleUnitsError
|
|
218
|
+
|
|
219
|
+
distance = Quantity(100, units.meter)
|
|
220
|
+
time = Quantity(10, units.second)
|
|
221
|
+
|
|
222
|
+
try:
|
|
223
|
+
# This will raise IncompatibleUnitsError
|
|
224
|
+
result = distance + time
|
|
225
|
+
except IncompatibleUnitsError as e:
|
|
226
|
+
print(e) # "Cannot add m and s: incompatible dimensions"
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Testing
|
|
230
|
+
|
|
231
|
+
Run the test suite:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
pytest test_dimensional.py -v
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Run with coverage:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
pytest test_dimensional.py --cov=dimensional --cov-report=html
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Type Checking
|
|
244
|
+
|
|
245
|
+
This library includes type hints. Run type checking with:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
mypy dimensional.py
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Design Principles
|
|
252
|
+
|
|
253
|
+
1. **Zero Dependencies**: Core functionality has no external dependencies
|
|
254
|
+
2. **Type Safety**: Prevents invalid operations at runtime
|
|
255
|
+
3. **Clear Errors**: Actionable error messages
|
|
256
|
+
4. **Production Ready**: Comprehensive test coverage
|
|
257
|
+
5. **Performance**: Efficient implementation with minimal overhead
|
|
258
|
+
|
|
259
|
+
## API Reference
|
|
260
|
+
|
|
261
|
+
### `Dimension`
|
|
262
|
+
|
|
263
|
+
Represents the dimensional formula of a unit (e.g., L^1 T^-2 for acceleration).
|
|
264
|
+
|
|
265
|
+
### `Unit`
|
|
266
|
+
|
|
267
|
+
Represents a unit of measurement with its dimension and conversion factor.
|
|
268
|
+
|
|
269
|
+
### `Quantity`
|
|
270
|
+
|
|
271
|
+
A numeric value with an associated unit. Supports:
|
|
272
|
+
- Arithmetic: `+`, `-`, `*`, `/`, `**`, `-` (negation), `abs()`
|
|
273
|
+
- Comparison: `==`, `!=`, `<`, `<=`, `>`, `>=`
|
|
274
|
+
- Conversion: `.to(target_unit)`
|
|
275
|
+
|
|
276
|
+
### `units`
|
|
277
|
+
|
|
278
|
+
Namespace containing all predefined units.
|
|
279
|
+
|
|
280
|
+
## License
|
|
281
|
+
|
|
282
|
+
MIT License
|
|
283
|
+
|
|
284
|
+
## Contributing
|
|
285
|
+
|
|
286
|
+
Contributions are welcome! Please ensure:
|
|
287
|
+
- All tests pass
|
|
288
|
+
- Code coverage remains >90%
|
|
289
|
+
- Type hints are included
|
|
290
|
+
- Documentation is updated
|
|
291
|
+
|
|
292
|
+
## Changelog
|
|
293
|
+
|
|
294
|
+
### 1.0.0 (2026-08-28)
|
|
295
|
+
- Initial release
|
|
296
|
+
- Support for SI and imperial units
|
|
297
|
+
- Comprehensive dimensional analysis
|
|
298
|
+
- Temperature conversion with offset handling
|
|
299
|
+
- Full test coverage
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
dimensional.py
|
|
5
|
+
py.typed
|
|
6
|
+
pyproject.toml
|
|
7
|
+
setup.py
|
|
8
|
+
unit_aware_arithmetic.egg-info/PKG-INFO
|
|
9
|
+
unit_aware_arithmetic.egg-info/SOURCES.txt
|
|
10
|
+
unit_aware_arithmetic.egg-info/dependency_links.txt
|
|
11
|
+
unit_aware_arithmetic.egg-info/requires.txt
|
|
12
|
+
unit_aware_arithmetic.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
dimensional
|