quantia 0.1.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.
- quantia-0.1.0/LICENSE +21 -0
- quantia-0.1.0/PKG-INFO +276 -0
- quantia-0.1.0/README.md +239 -0
- quantia-0.1.0/pyproject.toml +27 -0
- quantia-0.1.0/quantia/__init__.py +42 -0
- quantia-0.1.0/quantia/_array.py +173 -0
- quantia-0.1.0/quantia/_compound.py +416 -0
- quantia-0.1.0/quantia/_config.py +67 -0
- quantia-0.1.0/quantia/_exceptions.py +24 -0
- quantia-0.1.0/quantia/_io.py +53 -0
- quantia-0.1.0/quantia/_registry.py +80 -0
- quantia-0.1.0/quantia/_scalar.py +145 -0
- quantia-0.1.0/quantia/math.py +337 -0
- quantia-0.1.0/quantia/prob/__init__.py +3 -0
- quantia-0.1.0/quantia/prob/_array.py +184 -0
- quantia-0.1.0/quantia/prob/_config.py +1 -0
- quantia-0.1.0/quantia/prob/_copula.py +123 -0
- quantia-0.1.0/quantia/prob/_distributions.py +58 -0
- quantia-0.1.0/quantia/prob/_scalar.py +253 -0
- quantia-0.1.0/quantia/profiling/benchmark.py +331 -0
- quantia-0.1.0/quantia/units/__init__.py +9 -0
- quantia-0.1.0/quantia/units/common.py +14 -0
- quantia-0.1.0/quantia/units/data.py +11 -0
- quantia-0.1.0/quantia/units/imperial.py +24 -0
- quantia-0.1.0/quantia/units/petroleum.py +39 -0
- quantia-0.1.0/quantia/units/si.py +70 -0
- quantia-0.1.0/quantia.egg-info/PKG-INFO +276 -0
- quantia-0.1.0/quantia.egg-info/SOURCES.txt +33 -0
- quantia-0.1.0/quantia.egg-info/dependency_links.txt +1 -0
- quantia-0.1.0/quantia.egg-info/top_level.txt +5 -0
- quantia-0.1.0/setup.cfg +4 -0
- quantia-0.1.0/tests/test_algebra.py +46 -0
- quantia-0.1.0/tests/test_config.py +40 -0
- quantia-0.1.0/tests/test_core.py +127 -0
- quantia-0.1.0/tests/test_serialization.py +118 -0
quantia-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Juan Ignacio Sivori
|
|
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.
|
quantia-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: quantia
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unit-aware arithmetic with Monte Carlo uncertainty propagation
|
|
5
|
+
Author: Juan Ignacio Sivori
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Juan Ignacio Sivori
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/JuaniSivo/quantia
|
|
29
|
+
Keywords: units,dimensional analysis,monte carlo,uncertainty
|
|
30
|
+
Classifier: Programming Language :: Python :: 3
|
|
31
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
32
|
+
Classifier: Operating System :: OS Independent
|
|
33
|
+
Requires-Python: >=3.11
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
License-File: LICENSE
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# quantia
|
|
39
|
+
|
|
40
|
+
A pure-Python library for **unit-aware arithmetic** with first-class support for **Monte Carlo uncertainty propagation**.
|
|
41
|
+
|
|
42
|
+
No numpy required. No dependencies beyond the standard library.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install quantia
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## The Four Types
|
|
55
|
+
|
|
56
|
+
| Type | Use when |
|
|
57
|
+
|------|----------|
|
|
58
|
+
| `UnitFloat` | exact scalar with a unit |
|
|
59
|
+
| `UnitArray` | exact vector with a unit |
|
|
60
|
+
| `ProbUnitFloat` | uncertain scalar (Monte Carlo samples) |
|
|
61
|
+
| `ProbUnitArray` | uncertain vector of the same quantity |
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Quick Start
|
|
66
|
+
|
|
67
|
+
### Exact scalars
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import quantia as qu
|
|
71
|
+
|
|
72
|
+
d = qu.Q(100.0, "m")
|
|
73
|
+
t = qu.Q(9.81, "s")
|
|
74
|
+
v = d / t # UnitFloat(10.19…, 'm/s')
|
|
75
|
+
|
|
76
|
+
v.to("km/h") # unit conversion
|
|
77
|
+
v.to_si() # always works
|
|
78
|
+
|
|
79
|
+
qu.Q(100.0, "°C").to("K") # UnitFloat(373.15, 'K')
|
|
80
|
+
qu.Q(100.0, "°C").to("°F") # UnitFloat(212.0, '°F')
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Exact arrays
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
heights = qu.QA([1.75, 1.80, 1.65], "m")
|
|
87
|
+
|
|
88
|
+
heights.mean() # UnitFloat(1.733…, 'm')
|
|
89
|
+
heights.sum()
|
|
90
|
+
heights.to("cm")
|
|
91
|
+
|
|
92
|
+
# Boolean mask filtering
|
|
93
|
+
tall = heights[heights > qu.Q(1.78, "m")] # UnitArray([1.80], 'm')
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Uncertainty propagation
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
with qu.config(n_samples=2000, seed=42):
|
|
100
|
+
efficiency = qu.ProbUnitFloat.uniform(0.88, 0.95, "1")
|
|
101
|
+
power_in = qu.ProbUnitFloat.normal(500.0, 10.0, "W")
|
|
102
|
+
|
|
103
|
+
power_out = efficiency * power_in
|
|
104
|
+
|
|
105
|
+
power_out.mean() # UnitFloat(~457 W)
|
|
106
|
+
power_out.std()
|
|
107
|
+
power_out.interval(0.95) # (lo, hi) 95% CI as UnitFloat tuple
|
|
108
|
+
power_out.percentile(10)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Correlated inputs
|
|
112
|
+
|
|
113
|
+
When inputs are not independent, use a Gaussian copula:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
src = qu.CorrelatedSource(n_vars=2, rho=0.8)
|
|
117
|
+
|
|
118
|
+
x = src.draw(0, "normal", "m", mean=10, std=1)
|
|
119
|
+
y = src.draw(1, "uniform", "s", low=1, high=3)
|
|
120
|
+
|
|
121
|
+
speed = x / y # ProbUnitFloat, correlated samples
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Or pass a full correlation matrix:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
src = qu.CorrelatedSource(corr_matrix=[
|
|
128
|
+
[1.0, 0.7, 0.4],
|
|
129
|
+
[0.7, 1.0, 0.3],
|
|
130
|
+
[0.4, 0.3, 1.0],
|
|
131
|
+
])
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Unit Expressions
|
|
137
|
+
|
|
138
|
+
quantia parses unit strings with a full tokenizer:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
qu.Q(9.81, "m/s^2")
|
|
142
|
+
qu.Q(1.0, "kg·m/s^2") # · or * for multiplication
|
|
143
|
+
qu.Q(4.0, "m^(1/2)") # rational exponents
|
|
144
|
+
qu.Q(1.0, "kg/m^2/s") # chained division
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Built-in unit domains
|
|
148
|
+
|
|
149
|
+
| Domain | Examples |
|
|
150
|
+
|--------|---------|
|
|
151
|
+
| SI base | `m`, `kg`, `s`, `K`, `A`, `mol` |
|
|
152
|
+
| SI derived | `N`, `J`, `W`, `Pa`, `V`, `Ω`, `Hz` |
|
|
153
|
+
| Temperature | `°C`, `°F`, `K` |
|
|
154
|
+
| Imperial | `ft`, `lb`, `psi`, `BTU`, `hp`, `mph` |
|
|
155
|
+
| Petroleum | `bbl`, `Mbbl`, `psi_g`, `scf`, `Mscf`, `°API` |
|
|
156
|
+
| Data | `B`, `KB`, `MB`, `GB`, `TB`, `bit` |
|
|
157
|
+
|
|
158
|
+
### Semantic / tagged units
|
|
159
|
+
|
|
160
|
+
Dimensionally equal but semantically distinct units that won't cancel:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from quantia import register_tagged
|
|
164
|
+
|
|
165
|
+
register_tagged("Sm3_res", "m3", "reservoir")
|
|
166
|
+
register_tagged("Sm3_st", "m3", "stock_tank")
|
|
167
|
+
|
|
168
|
+
Rs = qu.Q(150.0, "Sm3_res") / qu.Q(1.0, "Sm3_st")
|
|
169
|
+
# UnitFloat(150.0, 'Sm3_res/Sm3_st') — does not reduce to 1
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Math Functions
|
|
175
|
+
|
|
176
|
+
`quantia.math` is a drop-in replacement for the stdlib `math` module that dispatches transparently on all four types:
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
import quantia.math as mmath
|
|
180
|
+
|
|
181
|
+
mmath.log10(x) # float, UnitFloat, ProbUnitFloat, UnitArray, ProbUnitArray
|
|
182
|
+
mmath.exp(x)
|
|
183
|
+
mmath.sqrt(x) # preserves units: sqrt(m^2) → m
|
|
184
|
+
mmath.sin(x) # requires angle unit on UnitFloat; raises DimensionError otherwise
|
|
185
|
+
mmath.cos(x)
|
|
186
|
+
mmath.atan2(y, x)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Configuration
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
with qu.config(n_samples=5000, seed=42):
|
|
195
|
+
x = qu.ProbUnitFloat.normal(10.0, 1.0, "m")
|
|
196
|
+
y = qu.ProbUnitFloat.uniform(0.0, 1.0, "s")
|
|
197
|
+
# config restored on exit; contexts nest cleanly
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Serialization
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
# Save and load any quantia object
|
|
206
|
+
qu.save(power_out, "result.json")
|
|
207
|
+
result = qu.load("result.json")
|
|
208
|
+
|
|
209
|
+
# Dict round-trip
|
|
210
|
+
d = power_out.to_dict()
|
|
211
|
+
pf2 = qu.from_dict(d)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### CSV export
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
# UnitArray — single column of values
|
|
218
|
+
heights.to_csv("heights.csv")
|
|
219
|
+
|
|
220
|
+
# ProbUnitArray — mean, std, CI bounds per element
|
|
221
|
+
layer_thicknesses = qu.QPA([t1, t2, t3]) # must be same unit
|
|
222
|
+
layer_thicknesses.to_csv("stats.csv", confidence=0.90)
|
|
223
|
+
layer_thicknesses.samples_to_csv("raw.csv")
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Error Handling
|
|
229
|
+
|
|
230
|
+
quantia raises named exceptions from `quantia._exceptions`:
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
from quantia import IncompatibleUnitsError, DimensionError, UnknownUnitError
|
|
234
|
+
|
|
235
|
+
qu.Q(1.0, "m") + qu.Q(1.0, "s") # → IncompatibleUnitsError
|
|
236
|
+
mmath.sin(qu.Q(1.0, "m")) # → DimensionError
|
|
237
|
+
qu.Q(1.0, "furlongs") # → UnknownUnitError
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Running Tests
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
pip install pytest
|
|
246
|
+
pytest tests/ -v
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
56 tests covering scalar arithmetic, array operations, probabilistic math,
|
|
250
|
+
unit algebra invariants, serialization, and the config system.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Benchmarks
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
python -m quantia.profiling.benchmark
|
|
258
|
+
python -m quantia.profiling.benchmark --profile # cProfile detail at n=10k
|
|
259
|
+
python -m quantia.profiling.benchmark --n 10000 # single sample size
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Typical throughput on a modern laptop (stdlib backend, no numpy):
|
|
263
|
+
|
|
264
|
+
| Operation | n=10k |
|
|
265
|
+
|-----------|-------|
|
|
266
|
+
| Scalar arithmetic chain | ~1ms |
|
|
267
|
+
| `ProbUnitFloat.normal` construction | ~4ms |
|
|
268
|
+
| `mean()` | ~0.1ms |
|
|
269
|
+
| `interval(0.95)` | ~0ms (cached sort) |
|
|
270
|
+
| Gaussian copula k=3 | ~33ms |
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## License
|
|
275
|
+
|
|
276
|
+
MIT — see [LICENSE](LICENSE).
|
quantia-0.1.0/README.md
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# quantia
|
|
2
|
+
|
|
3
|
+
A pure-Python library for **unit-aware arithmetic** with first-class support for **Monte Carlo uncertainty propagation**.
|
|
4
|
+
|
|
5
|
+
No numpy required. No dependencies beyond the standard library.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install quantia
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## The Four Types
|
|
18
|
+
|
|
19
|
+
| Type | Use when |
|
|
20
|
+
|------|----------|
|
|
21
|
+
| `UnitFloat` | exact scalar with a unit |
|
|
22
|
+
| `UnitArray` | exact vector with a unit |
|
|
23
|
+
| `ProbUnitFloat` | uncertain scalar (Monte Carlo samples) |
|
|
24
|
+
| `ProbUnitArray` | uncertain vector of the same quantity |
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
### Exact scalars
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import quantia as qu
|
|
34
|
+
|
|
35
|
+
d = qu.Q(100.0, "m")
|
|
36
|
+
t = qu.Q(9.81, "s")
|
|
37
|
+
v = d / t # UnitFloat(10.19…, 'm/s')
|
|
38
|
+
|
|
39
|
+
v.to("km/h") # unit conversion
|
|
40
|
+
v.to_si() # always works
|
|
41
|
+
|
|
42
|
+
qu.Q(100.0, "°C").to("K") # UnitFloat(373.15, 'K')
|
|
43
|
+
qu.Q(100.0, "°C").to("°F") # UnitFloat(212.0, '°F')
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Exact arrays
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
heights = qu.QA([1.75, 1.80, 1.65], "m")
|
|
50
|
+
|
|
51
|
+
heights.mean() # UnitFloat(1.733…, 'm')
|
|
52
|
+
heights.sum()
|
|
53
|
+
heights.to("cm")
|
|
54
|
+
|
|
55
|
+
# Boolean mask filtering
|
|
56
|
+
tall = heights[heights > qu.Q(1.78, "m")] # UnitArray([1.80], 'm')
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Uncertainty propagation
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
with qu.config(n_samples=2000, seed=42):
|
|
63
|
+
efficiency = qu.ProbUnitFloat.uniform(0.88, 0.95, "1")
|
|
64
|
+
power_in = qu.ProbUnitFloat.normal(500.0, 10.0, "W")
|
|
65
|
+
|
|
66
|
+
power_out = efficiency * power_in
|
|
67
|
+
|
|
68
|
+
power_out.mean() # UnitFloat(~457 W)
|
|
69
|
+
power_out.std()
|
|
70
|
+
power_out.interval(0.95) # (lo, hi) 95% CI as UnitFloat tuple
|
|
71
|
+
power_out.percentile(10)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Correlated inputs
|
|
75
|
+
|
|
76
|
+
When inputs are not independent, use a Gaussian copula:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
src = qu.CorrelatedSource(n_vars=2, rho=0.8)
|
|
80
|
+
|
|
81
|
+
x = src.draw(0, "normal", "m", mean=10, std=1)
|
|
82
|
+
y = src.draw(1, "uniform", "s", low=1, high=3)
|
|
83
|
+
|
|
84
|
+
speed = x / y # ProbUnitFloat, correlated samples
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Or pass a full correlation matrix:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
src = qu.CorrelatedSource(corr_matrix=[
|
|
91
|
+
[1.0, 0.7, 0.4],
|
|
92
|
+
[0.7, 1.0, 0.3],
|
|
93
|
+
[0.4, 0.3, 1.0],
|
|
94
|
+
])
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Unit Expressions
|
|
100
|
+
|
|
101
|
+
quantia parses unit strings with a full tokenizer:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
qu.Q(9.81, "m/s^2")
|
|
105
|
+
qu.Q(1.0, "kg·m/s^2") # · or * for multiplication
|
|
106
|
+
qu.Q(4.0, "m^(1/2)") # rational exponents
|
|
107
|
+
qu.Q(1.0, "kg/m^2/s") # chained division
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Built-in unit domains
|
|
111
|
+
|
|
112
|
+
| Domain | Examples |
|
|
113
|
+
|--------|---------|
|
|
114
|
+
| SI base | `m`, `kg`, `s`, `K`, `A`, `mol` |
|
|
115
|
+
| SI derived | `N`, `J`, `W`, `Pa`, `V`, `Ω`, `Hz` |
|
|
116
|
+
| Temperature | `°C`, `°F`, `K` |
|
|
117
|
+
| Imperial | `ft`, `lb`, `psi`, `BTU`, `hp`, `mph` |
|
|
118
|
+
| Petroleum | `bbl`, `Mbbl`, `psi_g`, `scf`, `Mscf`, `°API` |
|
|
119
|
+
| Data | `B`, `KB`, `MB`, `GB`, `TB`, `bit` |
|
|
120
|
+
|
|
121
|
+
### Semantic / tagged units
|
|
122
|
+
|
|
123
|
+
Dimensionally equal but semantically distinct units that won't cancel:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from quantia import register_tagged
|
|
127
|
+
|
|
128
|
+
register_tagged("Sm3_res", "m3", "reservoir")
|
|
129
|
+
register_tagged("Sm3_st", "m3", "stock_tank")
|
|
130
|
+
|
|
131
|
+
Rs = qu.Q(150.0, "Sm3_res") / qu.Q(1.0, "Sm3_st")
|
|
132
|
+
# UnitFloat(150.0, 'Sm3_res/Sm3_st') — does not reduce to 1
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Math Functions
|
|
138
|
+
|
|
139
|
+
`quantia.math` is a drop-in replacement for the stdlib `math` module that dispatches transparently on all four types:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
import quantia.math as mmath
|
|
143
|
+
|
|
144
|
+
mmath.log10(x) # float, UnitFloat, ProbUnitFloat, UnitArray, ProbUnitArray
|
|
145
|
+
mmath.exp(x)
|
|
146
|
+
mmath.sqrt(x) # preserves units: sqrt(m^2) → m
|
|
147
|
+
mmath.sin(x) # requires angle unit on UnitFloat; raises DimensionError otherwise
|
|
148
|
+
mmath.cos(x)
|
|
149
|
+
mmath.atan2(y, x)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Configuration
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
with qu.config(n_samples=5000, seed=42):
|
|
158
|
+
x = qu.ProbUnitFloat.normal(10.0, 1.0, "m")
|
|
159
|
+
y = qu.ProbUnitFloat.uniform(0.0, 1.0, "s")
|
|
160
|
+
# config restored on exit; contexts nest cleanly
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Serialization
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
# Save and load any quantia object
|
|
169
|
+
qu.save(power_out, "result.json")
|
|
170
|
+
result = qu.load("result.json")
|
|
171
|
+
|
|
172
|
+
# Dict round-trip
|
|
173
|
+
d = power_out.to_dict()
|
|
174
|
+
pf2 = qu.from_dict(d)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### CSV export
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
# UnitArray — single column of values
|
|
181
|
+
heights.to_csv("heights.csv")
|
|
182
|
+
|
|
183
|
+
# ProbUnitArray — mean, std, CI bounds per element
|
|
184
|
+
layer_thicknesses = qu.QPA([t1, t2, t3]) # must be same unit
|
|
185
|
+
layer_thicknesses.to_csv("stats.csv", confidence=0.90)
|
|
186
|
+
layer_thicknesses.samples_to_csv("raw.csv")
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Error Handling
|
|
192
|
+
|
|
193
|
+
quantia raises named exceptions from `quantia._exceptions`:
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from quantia import IncompatibleUnitsError, DimensionError, UnknownUnitError
|
|
197
|
+
|
|
198
|
+
qu.Q(1.0, "m") + qu.Q(1.0, "s") # → IncompatibleUnitsError
|
|
199
|
+
mmath.sin(qu.Q(1.0, "m")) # → DimensionError
|
|
200
|
+
qu.Q(1.0, "furlongs") # → UnknownUnitError
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Running Tests
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
pip install pytest
|
|
209
|
+
pytest tests/ -v
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
56 tests covering scalar arithmetic, array operations, probabilistic math,
|
|
213
|
+
unit algebra invariants, serialization, and the config system.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Benchmarks
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
python -m quantia.profiling.benchmark
|
|
221
|
+
python -m quantia.profiling.benchmark --profile # cProfile detail at n=10k
|
|
222
|
+
python -m quantia.profiling.benchmark --n 10000 # single sample size
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Typical throughput on a modern laptop (stdlib backend, no numpy):
|
|
226
|
+
|
|
227
|
+
| Operation | n=10k |
|
|
228
|
+
|-----------|-------|
|
|
229
|
+
| Scalar arithmetic chain | ~1ms |
|
|
230
|
+
| `ProbUnitFloat.normal` construction | ~4ms |
|
|
231
|
+
| `mean()` | ~0.1ms |
|
|
232
|
+
| `interval(0.95)` | ~0ms (cached sort) |
|
|
233
|
+
| Gaussian copula k=3 | ~33ms |
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## License
|
|
238
|
+
|
|
239
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "quantia"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
requires-python = ">=3.11"
|
|
9
|
+
description = "Unit-aware arithmetic with Monte Carlo uncertainty propagation"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
authors = [{ name = "Juan Ignacio Sivori" }]
|
|
13
|
+
keywords = ["units", "dimensional analysis", "monte carlo", "uncertainty"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://github.com/JuaniSivo/quantia" # TODO
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.packages.find]
|
|
24
|
+
where = ["."]
|
|
25
|
+
|
|
26
|
+
[tool.pytest.ini_options]
|
|
27
|
+
pythonpath = ["."]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import quantia.units # noqa: F401 — populates registry + aliases
|
|
2
|
+
|
|
3
|
+
from quantia._scalar import UnitFloat
|
|
4
|
+
from quantia._array import UnitArray
|
|
5
|
+
from quantia.prob._scalar import ProbUnitFloat
|
|
6
|
+
from quantia.prob._array import ProbUnitArray
|
|
7
|
+
from quantia.prob._copula import CorrelatedSource
|
|
8
|
+
from quantia._exceptions import (
|
|
9
|
+
UnknownUnitError, IncompatibleUnitsError, DimensionError, UnitParseError
|
|
10
|
+
)
|
|
11
|
+
from quantia._compound import parse_unit, register_alias, register_tagged
|
|
12
|
+
from quantia._registry import register, get_unit, registered_symbols
|
|
13
|
+
from quantia._io import save, load, from_dict
|
|
14
|
+
from quantia._config import config
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def Q(value: float, unit) -> UnitFloat:
|
|
18
|
+
"""Exact scalar: Q(9.81, 'm/s^2')"""
|
|
19
|
+
return UnitFloat(value, unit)
|
|
20
|
+
|
|
21
|
+
def QA(values, unit) -> UnitArray:
|
|
22
|
+
"""Exact array: QA([1.0, 2.0, 3.0], 'km')"""
|
|
23
|
+
return UnitArray(values, unit)
|
|
24
|
+
|
|
25
|
+
def QP(low: float, high: float, unit, n: int = None) -> ProbUnitFloat:
|
|
26
|
+
"""Probabilistic scalar, uniform: QP(0.92, 0.96, '1')"""
|
|
27
|
+
return ProbUnitFloat.uniform(low, high, unit, n)
|
|
28
|
+
|
|
29
|
+
def QPA(elements) -> ProbUnitArray:
|
|
30
|
+
"""Probabilistic array: QPA([p1, p2, p3])"""
|
|
31
|
+
return ProbUnitArray(elements)
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"Q", "QA", "QP", "QPA",
|
|
35
|
+
"UnitFloat", "UnitArray", "ProbUnitFloat", "ProbUnitArray",
|
|
36
|
+
"CorrelatedSource",
|
|
37
|
+
"parse_unit", "register_alias", "register_tagged",
|
|
38
|
+
"register", "get_unit", "registered_symbols",
|
|
39
|
+
"UnknownUnitError", "IncompatibleUnitsError", "DimensionError", "UnitParseError",
|
|
40
|
+
"save", "load", "from_dict",
|
|
41
|
+
"config",
|
|
42
|
+
]
|