metrolopy 0.6.5__py3-none-any.whl → 1.0.0__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.
- metrolopy/__init__.py +5 -4
- metrolopy/budget.py +61 -50
- metrolopy/builtin_constants.py +903 -0
- metrolopy/constant.py +108 -104
- metrolopy/constcom.py +84 -83
- metrolopy/distributions.py +120 -39
- metrolopy/exceptions.py +7 -9
- metrolopy/fit.py +3 -3
- metrolopy/functions.py +4 -4
- metrolopy/gummy.py +554 -514
- metrolopy/indexed.py +69 -20
- metrolopy/logunit.py +1 -1
- metrolopy/mean.py +8 -9
- metrolopy/miscunits.py +21 -6
- metrolopy/nummy.py +208 -158
- metrolopy/offsetunit.py +2 -3
- metrolopy/prefixedunit.py +24 -23
- metrolopy/relunits.py +1 -2
- metrolopy/siunits.py +7 -5
- metrolopy/tests/__init__.py +6 -6
- metrolopy/tests/test_create.py +7 -6
- metrolopy/tests/test_gummy.py +5 -43
- metrolopy/tests/test_misc.py +1 -0
- metrolopy/tests/test_operations.py +3 -2
- metrolopy/tests/test_ubreakdown.py +3 -2
- metrolopy/ummy.py +889 -897
- metrolopy/unit.py +287 -182
- metrolopy/unitparser.py +40 -42
- metrolopy/unitutils.py +183 -159
- metrolopy/usunits.py +14 -13
- metrolopy/version.py +1 -1
- {metrolopy-0.6.5.dist-info → metrolopy-1.0.0.dist-info}/METADATA +20 -2
- metrolopy-1.0.0.dist-info/RECORD +45 -0
- metrolopy-0.6.5.dist-info/RECORD +0 -44
- {metrolopy-0.6.5.dist-info → metrolopy-1.0.0.dist-info}/WHEEL +0 -0
- {metrolopy-0.6.5.dist-info → metrolopy-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {metrolopy-0.6.5.dist-info → metrolopy-1.0.0.dist-info}/top_level.txt +0 -0
- {metrolopy-0.6.5.dist-info → metrolopy-1.0.0.dist-info}/zip-safe +0 -0
metrolopy/distributions.py
CHANGED
|
@@ -29,6 +29,7 @@ from .exceptions import NoSimulatedDataError
|
|
|
29
29
|
#from .unit import Unit
|
|
30
30
|
import numpy as np
|
|
31
31
|
|
|
32
|
+
|
|
32
33
|
class Distribution:
|
|
33
34
|
"""
|
|
34
35
|
Abstract base class for distributions used for Monte-Carlo uncertainty
|
|
@@ -75,7 +76,7 @@ class Distribution:
|
|
|
75
76
|
_random_state = None
|
|
76
77
|
simdata = None
|
|
77
78
|
|
|
78
|
-
|
|
79
|
+
utype = None
|
|
79
80
|
|
|
80
81
|
_called = []
|
|
81
82
|
_mean = None
|
|
@@ -128,6 +129,15 @@ class Distribution:
|
|
|
128
129
|
if not any([isinstance(v,Distribution) for v in d]):
|
|
129
130
|
return f(*d)
|
|
130
131
|
return Convolution(f,*d)
|
|
132
|
+
|
|
133
|
+
@staticmethod
|
|
134
|
+
def clear_all():
|
|
135
|
+
"""
|
|
136
|
+
Clears the Monte-Carlo data from all existing Distribution instances.
|
|
137
|
+
"""
|
|
138
|
+
for d in Distribution._called:
|
|
139
|
+
d.clear()
|
|
140
|
+
Distribution._called = []
|
|
131
141
|
|
|
132
142
|
@staticmethod
|
|
133
143
|
def simulate(distributions,n=100000,ufrom=None):
|
|
@@ -150,18 +160,23 @@ class Distribution:
|
|
|
150
160
|
by distributions in this list will be allowed to vary. All other
|
|
151
161
|
variables will be held fixed at the distribution x value.
|
|
152
162
|
"""
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
163
|
+
Distribution.clear_all()
|
|
164
|
+
|
|
165
|
+
if ufrom is not None and any(isinstance(i,Convolution) for i in ufrom):
|
|
166
|
+
raise TypeError('Distributions in ufrom must be independent')
|
|
167
|
+
|
|
157
168
|
for d in distributions:
|
|
158
169
|
if isinstance(d,Distribution):
|
|
159
170
|
d._simulate(int(n),ufrom)
|
|
160
171
|
|
|
161
|
-
for d in Distribution._called:
|
|
162
|
-
if d not in distributions:
|
|
163
|
-
d.clear()
|
|
164
|
-
Distribution._called.remove(d)
|
|
172
|
+
#for d in Distribution._called:
|
|
173
|
+
#if d not in distributions:
|
|
174
|
+
#d.clear()
|
|
175
|
+
#Distribution._called.remove(d)
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def isindependent(self):
|
|
179
|
+
return True
|
|
165
180
|
|
|
166
181
|
@property
|
|
167
182
|
def mean(self):
|
|
@@ -301,7 +316,7 @@ class Distribution:
|
|
|
301
316
|
self._cisym = (lower, upper)
|
|
302
317
|
return (lower, upper)
|
|
303
318
|
|
|
304
|
-
def hist(self,hold=False,xlabel=
|
|
319
|
+
def hist(self,hold=False,xlabel='$ \\mathrm{value} $',ylabel='$ \\mathrm{probability\\:density} $',
|
|
305
320
|
title=None,**kwds):
|
|
306
321
|
"""
|
|
307
322
|
Generates a histogram from the simulated data.
|
|
@@ -388,9 +403,11 @@ class Distribution:
|
|
|
388
403
|
self._cov = {}
|
|
389
404
|
if d in self._cov:
|
|
390
405
|
return self._cov[d]
|
|
391
|
-
if
|
|
406
|
+
if isinstance(d,Distribution):
|
|
407
|
+
dt = d.simdata
|
|
408
|
+
if self.simdata is None or dt is None:
|
|
392
409
|
raise NoSimulatedDataError('simulated data does not exist for both distributions')
|
|
393
|
-
cov = np.cov([self.simdata,
|
|
410
|
+
cov = np.cov([self.simdata,dt])[1][0]
|
|
394
411
|
self._cov[d] = cov
|
|
395
412
|
return cov
|
|
396
413
|
|
|
@@ -405,7 +422,8 @@ class Distribution:
|
|
|
405
422
|
if no simulated data is available from a call to
|
|
406
423
|
`Distribution.simulate` for any of `d1`, `d2`, ...
|
|
407
424
|
"""
|
|
408
|
-
|
|
425
|
+
d = [i.simdata if hasattr(i,'simdata') else i for i in d]
|
|
426
|
+
if any([(v is None) for v in d]):
|
|
409
427
|
raise NoSimulatedDataError('simulated data does not exist for all distributions')
|
|
410
428
|
return np.cov([v.simdata for v in d])
|
|
411
429
|
|
|
@@ -480,9 +498,10 @@ class Distribution:
|
|
|
480
498
|
plt.show()
|
|
481
499
|
|
|
482
500
|
def _simulate(self,n,ufrom):
|
|
483
|
-
if ufrom is not None and self not in ufrom:
|
|
501
|
+
if ufrom is not None and self not in ufrom and self.utype not in ufrom:
|
|
484
502
|
self.simdata = np.full(n,self.x())
|
|
485
503
|
return
|
|
504
|
+
|
|
486
505
|
Distribution._called.append(self)
|
|
487
506
|
self.simdata = self.random(n)
|
|
488
507
|
|
|
@@ -563,9 +582,27 @@ class Distribution:
|
|
|
563
582
|
raise NotImplementedError()
|
|
564
583
|
|
|
565
584
|
|
|
566
|
-
|
|
567
|
-
|
|
585
|
+
def _callarg(a,n,ufrom):
|
|
586
|
+
if isinstance(a,Convolution):
|
|
587
|
+
return a.func(*(_callarg(i,n,ufrom) for i in a.args))
|
|
588
|
+
|
|
589
|
+
if isinstance(a,Distribution):
|
|
590
|
+
if n is None and a.simdata is None:
|
|
591
|
+
raise NoSimulatedDataError('no simulated data is available')
|
|
592
|
+
|
|
593
|
+
if ufrom is not None and a not in ufrom and a.utype not in ufrom:
|
|
594
|
+
if n is None:
|
|
595
|
+
n = len(a.simdata)
|
|
596
|
+
return np.full(n,a.x())
|
|
597
|
+
|
|
598
|
+
if a.simdata is None:
|
|
599
|
+
a._simulate(n,None)
|
|
600
|
+
return a.simdata
|
|
568
601
|
|
|
602
|
+
return a
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
class Convolution(Distribution):
|
|
569
606
|
def __init__(self,func,*args):
|
|
570
607
|
"""
|
|
571
608
|
Represents the distribution resulting from applying func to d1, d2, ...
|
|
@@ -580,9 +617,17 @@ class Convolution(Distribution):
|
|
|
580
617
|
*args: `Distribution` or `float`
|
|
581
618
|
arguments for `func`
|
|
582
619
|
"""
|
|
583
|
-
self.func = func
|
|
584
620
|
self.args = args
|
|
585
621
|
|
|
622
|
+
if isinstance(func,np.ufunc):
|
|
623
|
+
self.func = func
|
|
624
|
+
else:
|
|
625
|
+
self.func = np.frompyfunc(func,len(self.args),1)
|
|
626
|
+
|
|
627
|
+
@property
|
|
628
|
+
def isindependent(self):
|
|
629
|
+
return False
|
|
630
|
+
|
|
586
631
|
def random(self,n=None):
|
|
587
632
|
raise NotImplementedError('use the simulate static method to generate data from a Convolution')
|
|
588
633
|
|
|
@@ -591,31 +636,12 @@ class Convolution(Distribution):
|
|
|
591
636
|
return self.func(*args)
|
|
592
637
|
|
|
593
638
|
def _random(self,n,ufrom):
|
|
594
|
-
for
|
|
595
|
-
if isinstance(a,Distribution) and (a not in Distribution._called):
|
|
596
|
-
a._simulate(n,ufrom)
|
|
597
|
-
v = [a.simdata if isinstance(a,Distribution) else a for a in self.args]
|
|
639
|
+
ret = self.func(*(_callarg(i,n,ufrom) for i in self.args))
|
|
598
640
|
|
|
599
|
-
if not isinstance(self.func,np.ufunc):
|
|
600
|
-
# If func is not a numpy ufunc, see if it broadcasts like one
|
|
601
|
-
try:
|
|
602
|
-
ntst = max(n,self.args+5)
|
|
603
|
-
vtst = [a.simdata[:ntst] if isinstance(a,Distribution) else a for a in self.args]
|
|
604
|
-
tst = self.func(*vtst)
|
|
605
|
-
if len(tst) != ntst:
|
|
606
|
-
raise TypeError()
|
|
607
|
-
|
|
608
|
-
func = self.func
|
|
609
|
-
except:
|
|
610
|
-
# if it doesn't broadcast, make it broadcast with frompyfunc
|
|
611
|
-
func = np.frompyfunc(self.func,len(v),1)
|
|
612
|
-
else:
|
|
613
|
-
func = self.func
|
|
614
|
-
|
|
615
|
-
ret = func(*v)
|
|
616
|
-
|
|
617
641
|
if ret.dtype != np.float64:
|
|
618
642
|
ret = np.array(ret,dtype=np.float64)
|
|
643
|
+
if n is not None and ret.shape == ():
|
|
644
|
+
ret = np.full(n,ret)
|
|
619
645
|
|
|
620
646
|
return ret
|
|
621
647
|
|
|
@@ -623,6 +649,42 @@ class Convolution(Distribution):
|
|
|
623
649
|
Distribution._called.append(self)
|
|
624
650
|
self.simdata = self._random(n,ufrom)
|
|
625
651
|
|
|
652
|
+
def datafrom(self,ufrom,save=True):
|
|
653
|
+
"""
|
|
654
|
+
Recomputes the convolution with only Distributions in `ufrom` allowed to
|
|
655
|
+
vary. `sim` or `simulate` must be called to generate
|
|
656
|
+
Monte-Carlo data before calling this method.
|
|
657
|
+
|
|
658
|
+
Parameters
|
|
659
|
+
----------
|
|
660
|
+
ufrom: list containing `Distribution` (not `Convolution`) or `str`
|
|
661
|
+
all independent Distributions not in the list or having a utype
|
|
662
|
+
not in the list are held fixed at their `.x()` value
|
|
663
|
+
|
|
664
|
+
save: If `save` is `True` the recomputed data is stored in the `simdata`
|
|
665
|
+
attribute and `None` is returned. If `save` is `False` then the
|
|
666
|
+
recomputed data is returned and the `simdata` attribute is not
|
|
667
|
+
overwritten.
|
|
668
|
+
|
|
669
|
+
Returns
|
|
670
|
+
-------
|
|
671
|
+
'numpy.array' if `save` is `False`, otherwise returns `None`
|
|
672
|
+
|
|
673
|
+
Raises
|
|
674
|
+
------
|
|
675
|
+
`NoSimulatedDataError`:
|
|
676
|
+
if no simulated data is available from a call to
|
|
677
|
+
`Distribution.simulate`.
|
|
678
|
+
"""
|
|
679
|
+
if any(isinstance(i,Convolution) for i in ufrom):
|
|
680
|
+
raise TypeError('Distributions in ufrom must be independent')
|
|
681
|
+
|
|
682
|
+
ret = self._random(None,ufrom)
|
|
683
|
+
if save:
|
|
684
|
+
self.simdata = ret
|
|
685
|
+
else:
|
|
686
|
+
return ret
|
|
687
|
+
|
|
626
688
|
|
|
627
689
|
class MultivariateDistribution:
|
|
628
690
|
def __init__(self,nd):
|
|
@@ -1301,4 +1363,23 @@ class WeibullDist(Distribution):
|
|
|
1301
1363
|
def u(self):
|
|
1302
1364
|
return self.scale*np.sqrt(self._gamma(1+2/self.shape) - self._gamma(1+1/self.shape)**2)
|
|
1303
1365
|
|
|
1366
|
+
class AveragedFrom(Distribution):
|
|
1367
|
+
def __init__(self,distribution,nsamples):
|
|
1368
|
+
"""
|
|
1369
|
+
Each sample from is the average of `nsamples` drawn from `distribution`.
|
|
1370
|
+
"""
|
|
1371
|
+
self._dist = distribution
|
|
1372
|
+
self.dof = nsamples - 1
|
|
1304
1373
|
|
|
1374
|
+
def random(self,n=None):
|
|
1375
|
+
ret = self._dist.random(n)
|
|
1376
|
+
for i in range(self.dof):
|
|
1377
|
+
ret += self._dist.random(n)
|
|
1378
|
+
return ret/self.dof
|
|
1379
|
+
|
|
1380
|
+
def x(self):
|
|
1381
|
+
return self._dist.x()
|
|
1382
|
+
|
|
1383
|
+
def u(self):
|
|
1384
|
+
return self._dist.u()/np.sqrt(self.dof + 1)
|
|
1385
|
+
|
metrolopy/exceptions.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# module exceptions
|
|
4
4
|
|
|
5
|
-
# Copyright (C)
|
|
5
|
+
# Copyright (C) 2025 National Research Council Canada
|
|
6
6
|
# Author: Harold Parks
|
|
7
7
|
|
|
8
8
|
# This file is part of MetroloPy.
|
|
@@ -20,16 +20,17 @@
|
|
|
20
20
|
# You should have received a copy of the GNU General Public License along with
|
|
21
21
|
# MetroloPy. If not, see <http://www.gnu.org/licenses/>.
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
|
|
24
|
+
class UnitError(ValueError):
|
|
24
25
|
"""
|
|
25
|
-
|
|
26
|
-
have units that are incompatible for that operation.
|
|
26
|
+
Base class for Unit exceptions.
|
|
27
27
|
"""
|
|
28
28
|
pass
|
|
29
29
|
|
|
30
|
-
class UnitError
|
|
30
|
+
class IncompatibleUnitsError(UnitError):
|
|
31
31
|
"""
|
|
32
|
-
|
|
32
|
+
This exception is raised when an operation or conversion is attempted with
|
|
33
|
+
Quantity instances that have units that are incompatible for that operation.
|
|
33
34
|
"""
|
|
34
35
|
pass
|
|
35
36
|
|
|
@@ -45,9 +46,6 @@ class UnitNotFoundError(UnitLibError):
|
|
|
45
46
|
class CircularUnitConversionError(UnitError):
|
|
46
47
|
pass
|
|
47
48
|
|
|
48
|
-
class NoUnitConversionFoundError(UnitError):
|
|
49
|
-
pass
|
|
50
|
-
|
|
51
49
|
class UnitLibNotFoundError(UnitLibError):
|
|
52
50
|
pass
|
|
53
51
|
|
metrolopy/fit.py
CHANGED
|
@@ -929,7 +929,7 @@ class Fit(_Fit,PrettyPrinter):
|
|
|
929
929
|
if self.p is None:
|
|
930
930
|
if cov is not None and self.count > self.nparam:
|
|
931
931
|
if self.sigma is None or not self.sigma_is_known:
|
|
932
|
-
dof = self.dof
|
|
932
|
+
dof = self.dof/self.nparam
|
|
933
933
|
if dof < 1:
|
|
934
934
|
dof = 1
|
|
935
935
|
else:
|
|
@@ -1153,7 +1153,7 @@ class Fit(_Fit,PrettyPrinter):
|
|
|
1153
1153
|
|
|
1154
1154
|
try:
|
|
1155
1155
|
if self.sigma is None or not self.sigma_is_known:
|
|
1156
|
-
dof = self.dof
|
|
1156
|
+
dof = self.dof/self.nparam
|
|
1157
1157
|
else:
|
|
1158
1158
|
dof = float('inf')
|
|
1159
1159
|
self.p = gummy.create(self.pf,unit=self.punits,dof=dof,
|
|
@@ -1741,7 +1741,7 @@ class PolyFit(Fit):
|
|
|
1741
1741
|
|
|
1742
1742
|
if p is None:
|
|
1743
1743
|
if self.uy is None or not self.sigma_is_known:
|
|
1744
|
-
dof = self.dof
|
|
1744
|
+
dof = self.dof/self.nparam
|
|
1745
1745
|
else:
|
|
1746
1746
|
dof = float('inf')
|
|
1747
1747
|
try:
|
metrolopy/functions.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# module functions
|
|
4
4
|
|
|
5
|
-
# Copyright (C)
|
|
5
|
+
# Copyright (C) 2025 National Research Council Canada
|
|
6
6
|
# Author: Harold Parks
|
|
7
7
|
|
|
8
8
|
# This file is part of MetroloPy.
|
|
@@ -39,11 +39,11 @@ def _callg(f,*args):
|
|
|
39
39
|
for a in args:
|
|
40
40
|
if isinstance(a,jummy):
|
|
41
41
|
return a.__array_ufunc__(f,'__call__',*args)
|
|
42
|
-
|
|
42
|
+
elif isinstance(a,gummy):
|
|
43
43
|
g = a
|
|
44
|
-
|
|
44
|
+
elif isinstance(a,ummy):
|
|
45
45
|
u = a
|
|
46
|
-
|
|
46
|
+
elif isinstance(a,Complex):
|
|
47
47
|
c = True
|
|
48
48
|
if g is not None:
|
|
49
49
|
if c:
|