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.
@@ -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
- isindependent = True
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
- for d in Distribution._called:
154
- d.clear()
155
- Distribution._called = []
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=None,ylabel='$ \\mathrm{probability\\:density} $',
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 self.simdata is None or d.simdata is None:
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,d.simdata])[1][0]
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
- if any([(v.simdata is None) for v in d]):
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
- class Convolution(Distribution):
567
- isindependent = False
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 a in self.args:
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) 2019 National Research Council Canada
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
- class IncompatibleUnitsError(ValueError):
23
+
24
+ class UnitError(ValueError):
24
25
  """
25
- This exception is raised when an operation is attempted with gummys that
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(Exception):
30
+ class IncompatibleUnitsError(UnitError):
31
31
  """
32
- Base class for Unit exceptions.
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) 2019 National Research Council Canada
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
- if isinstance(a,gummy):
42
+ elif isinstance(a,gummy):
43
43
  g = a
44
- if isinstance(a,ummy):
44
+ elif isinstance(a,ummy):
45
45
  u = a
46
- if isinstance(a,Complex):
46
+ elif isinstance(a,Complex):
47
47
  c = True
48
48
  if g is not None:
49
49
  if c: