metrolopy 0.6.4__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/ummy.py CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # module ummy
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.
@@ -29,27 +29,29 @@ of gummy.
29
29
  """
30
30
 
31
31
  import numpy as np
32
- import weakref
33
32
  from collections import namedtuple
34
33
  from warnings import warn
35
- from math import isnan,isinf,log,log10,pi
34
+ from math import isnan,isinf,log,pi,sqrt,copysign,floor
36
35
  from fractions import Fraction
37
- from numbers import Rational,Integral,Real,Complex
36
+ from numbers import Rational,Integral,Real,Complex,Number
38
37
  from .printing import PrettyPrinter,MetaPrettyPrinter
39
38
  from .dfunc import Dfunc
40
39
  from .exceptions import UncertiantyPrecisionWarning
41
- from .unit import Unit,one,Quantity
40
+ from .unit import Unit,one,Quantity,MFraction
41
+ from decimal import Decimal,localcontext,InvalidOperation
42
+ from abc import ABCMeta
42
43
 
43
44
  try:
44
- from mpmath import mp,mpf,rational
45
+ from mpmath import mp,mpf
45
46
  except:
46
47
  mp = mpf = None
47
48
 
49
+
48
50
  _FInfo = namedtuple('_FIinfo','rel_u precision warn_u ')
49
51
  _iinfo = _FInfo(rel_u=0,precision=0,warn_u=0)
50
52
  _finfodict = {}
51
53
  def _getfinfo(x):
52
- if isinstance(x,Rational):
54
+ if isinstance(x,Rational) or isinstance(x,Decimal):
53
55
  return (_iinfo,x)
54
56
 
55
57
  if mpf is not None and isinstance(x,mpf):
@@ -85,6 +87,11 @@ def _combu(a,b,c):
85
87
  # with 1 >= c >= -1,
86
88
  # this method avoids overflows or underflows when taking the squares
87
89
 
90
+ if b == 0:
91
+ return a
92
+ if a == 0:
93
+ return b
94
+
88
95
  if isnan(a) or isnan(b):
89
96
  try:
90
97
  return type(a)('nan')
@@ -93,39 +100,37 @@ def _combu(a,b,c):
93
100
 
94
101
  if abs(b) > abs(a):
95
102
  a,b = b,a
96
- if b == 0:
97
- return abs(a)
98
103
 
99
104
  r = b/a
100
105
  x = 1 + r**2 + 2*c*r
101
106
 
102
107
  if x < 0:
103
108
  if x < -1e6:
104
- # something horrible happened, should only get here due to a bug
109
+ # something horrible happened, a bug must have generated a value of
110
+ # for c that allows this
105
111
  raise FloatingPointError('u is sqrt of ' + str(a**2*x))
106
- # maybe possibly get here due to a floating point rounding error somewhere
112
+ # maybe possibly get here due to a floating point rounding error
107
113
  return 0
108
114
 
109
- return abs(a)*x**0.5
115
+ if hasattr(x,'sqrt'):
116
+ return abs(a)*x.sqrt()
117
+ return abs(a)*x**0.5
110
118
 
111
119
  def _isscalar(x):
120
+ if isinstance(x,str):
121
+ return True
112
122
  try:
113
123
  len(x)
114
124
  return False
115
125
  except:
116
126
  return True
117
-
118
- def _floor(x):
119
- if x < 0:
120
- return int(x) - 1
121
- return int(x)
122
127
 
123
- def _sign(x):
124
- if x < 0:
125
- return -1
126
- return 1
128
+ def sign(x):
129
+ return copysign(1,x)
127
130
 
128
131
  def _format_exp(fmt,xp):
132
+ if xp == 0:
133
+ return ''
129
134
  if fmt == 'html':
130
135
  ex = ' &times; 10<sup>' + str(xp) + '</sup>'
131
136
  elif fmt == 'latex':
@@ -137,8 +142,175 @@ def _format_exp(fmt,xp):
137
142
  ex += str(xp)
138
143
  return ex
139
144
 
145
+ def _to_decimal(x,max_digits=None):
146
+ # called from the ummy and gummy tostring methods to convert x and u values.
147
+ # The Decimal data type has some convinent methods for rounding numbers to
148
+ # show a given number of significant figures.
149
+
150
+ if mpf is not None and isinstance(x,mpf):
151
+ xd = Decimal(mp.nstr(x,n=mp.dps))
152
+ elif isinstance(x,Rational):
153
+ if max_digits is None:
154
+ max_digits = ummy.max_digits
155
+ with localcontext(prec=max_digits):
156
+ try:
157
+ xd = Decimal(x.numerator)/Decimal(x.denominator)
158
+ except:
159
+ try:
160
+ xd = Decimal(str(x.numerator))/Decimal(str(x.denominator))
161
+ except:
162
+ xd = Decimal(float(x.numerator))/Decimal(float(x.denominator))
163
+ else:
164
+ try:
165
+ xd = Decimal(x)
166
+ except:
167
+ try:
168
+ xd = Decimal(str(x))
169
+ except:
170
+ xd = Decimal(float(x))
171
+ return xd
172
+
173
+ def _decimal_str(x,fmt='unicode',rtrim=False,dplace=0,dalign=None,th_spaces=True):
174
+ # string formats a Decimal number, putting digits in groups of three
175
+ # separated by spaces if necessary
176
+
177
+ if x.is_nan():
178
+ return 'nan'
179
+ if x.is_infinite() and x > 0:
180
+ if fmt == 'html':
181
+ return '&infin;'
182
+ if fmt == 'latex':
183
+ return r'\infty'
184
+ if fmt == 'ascii':
185
+ return 'inf'
186
+ return '\u221E'
187
+ if x.is_infinite() and x < 0:
188
+ if fmt == 'html':
189
+ return '-&infin;'
190
+ if fmt == 'latex':
191
+ return r'-\infty'
192
+ if fmt == 'ascii':
193
+ return '-inf'
194
+ return '-\u221E'
195
+
196
+ s,d,e = x.as_tuple()
197
+
198
+ if dalign is not None:
199
+ dplace = len(d) + e - 1 - dalign
200
+
201
+ if rtrim:
202
+ if dplace is not None and dplace > 0:
203
+ dp = dplace
204
+ else:
205
+ dp = 0
206
+
207
+ i = len(d)
208
+ while i > dp + 1 and d[i-1] == 0:
209
+ i -= 1
210
+ if i != len(d):
211
+ d = d[:i]
212
+
213
+ if dplace is None:
214
+ th_spaces = False
215
+ else:
216
+ th_spaces &= (dplace > 3 or len(d) - dplace > 5)
217
+ if dplace < 0:
218
+ d = (0,)*(-dplace) + d
219
+ dplace = 0
220
+ elif x != 0 and dplace > len(d) - 1:
221
+ d = d + (0,)*(dplace - len(d) + 1)
222
+
223
+ if th_spaces:
224
+ if fmt == 'html':
225
+ sp = '&thinsp;'
226
+ elif fmt == 'latex':
227
+ sp = r'\,'
228
+ else:
229
+ sp = ' '
230
+
231
+ if s and x != 0:
232
+ txt = '-'
233
+ else:
234
+ txt = ''
235
+
236
+ for i in range(len(d)):
237
+ if i > 0 and dplace is not None:
238
+ if i == dplace + 1:
239
+ txt += '.'
240
+ elif th_spaces and (i - dplace - 1) % 3 == 0:
241
+ txt += sp
242
+ txt += chr(48 + d[i])
243
+
244
+ return txt
245
+
246
+ def _xtype(x):
247
+ # this is used when type conversions are needed, e.g. to convert float
248
+ # correlations or uncertainties to Decimal when dealing with ummy's
249
+ # with Decimal x values.
250
+
251
+ if isinstance(x,np.ndarray):
252
+ tp = x.dtype.type
253
+ else:
254
+ tp = type(x)
255
+ if not issubclass(tp,Number):
256
+ return float
257
+ if issubclass(tp,Integral):
258
+ return MFraction.fromnum
259
+ return tp
260
+
261
+ def _combrd(xl,du):
262
+ ret = _udict(xl[0]._ref)
263
+ for r in ret:
264
+ #ret[r] *= sign(xl[0]._u)*du[0]
265
+ ret[r] *= du[0]
266
+
267
+ for i in range(len(xl) - 1):
268
+ c = sign(xl[i+1]._u)*du[i+1]
269
+ for k,v in xl[i+1]._ref.items():
270
+ f = v*c
271
+ if k in ret:
272
+ ret[k] += f
273
+ else:
274
+ ret[k] = f
275
+
276
+ n = sum([i**2 for i in ret.values()])
277
+ if abs(n-1) > ummy.correlation_tolerance:
278
+ n = sqrt(n)
279
+ for k,v in ret.items():
280
+ ret[k] /= n
281
+
282
+ for k,v in list(ret.items()):
283
+ x = ret[k]
284
+ if x < -1 + ummy.correlation_tolerance:
285
+ return _udict(((k,-1),))
286
+ elif x > 1 - ummy.correlation_tolerance:
287
+ return _udict(((k,1),))
288
+ elif abs(ret[k]) < ummy.correlation_tolerance:
289
+ del ret[k]
290
+
291
+ return ret
292
+
293
+
294
+ # Correlations between ummy's are stored in a _udict instance.
295
+ # The keys in the _udict are _UmmyRef instances that reprensent independent
296
+ # variables and store the dof and utype of the variable. The values are the
297
+ # correlation of the ummy with those independent variables (to within a sign)
298
+ # and are also equal to the deriviative of the ummy with respect to each of
299
+ # those variables divided by the combined standard uncertainty of the ummy.
300
+ class _udict(dict):
301
+ pass
140
302
 
141
- class ummy(Dfunc,PrettyPrinter):
303
+ class _UmmyRef:
304
+ __slots__ = 'dof','utype'
305
+ def __init__(self,dof=None,utype=None,dist=None):
306
+ self.dof = dof
307
+ self.utype = utype
308
+
309
+
310
+ class MetaUmmy(MetaPrettyPrinter,ABCMeta):
311
+ pass
312
+
313
+ class ummy(Dfunc,PrettyPrinter,Number,metaclass=MetaUmmy):
142
314
  max_dof = 10000 # any larger dof will be rounded to float('inf')
143
315
  nsig = 2 # The number of digits to quote the uncertainty to
144
316
  thousand_spaces = True # If true spaces will be placed between groups of three digits.
@@ -155,7 +327,12 @@ class ummy(Dfunc,PrettyPrinter):
155
327
  # rounding errors
156
328
  rounding_u = False
157
329
 
158
- max_digits = 15
330
+ max_digits = 20
331
+
332
+ # correlations with absolute values smaller that this are rounded to
333
+ # zero and correlations samaller than -1 + correlation_tolerance or
334
+ # bigger than 1 - correlation_tolerance are rounded to -1 or 1 respectively.
335
+ correlation_tolerance = 1e-14
159
336
 
160
337
  def __init__(self,x,u=0,dof=float('inf'),utype=None):
161
338
  if isinstance(x,Quantity):
@@ -165,26 +342,25 @@ class ummy(Dfunc,PrettyPrinter):
165
342
  self._copy(x,self,formatting=False)
166
343
  return
167
344
 
168
- try:
169
- float(x)
170
- except TypeError:
171
- raise TypeError('x must be a real number')
172
-
173
- try:
174
- float(u)
175
- if not isnan(u) and u < 0:
176
- raise TypeError()
177
- except TypeError:
178
- raise TypeError('u must be a real number >= 0')
179
-
180
- if u == 0:
181
- dof = float('inf')
182
- utype = None
183
-
345
+ # MFractions convert implicitly to Decimal or mpf
184
346
  if isinstance(x,Fraction):
185
- x = MFraction(x)
347
+ x = MFraction(x)
186
348
  if isinstance(u,Fraction):
187
349
  u = MFraction(u)
350
+
351
+ # sometimes numpy.array of shape () appear and cause problems
352
+ if isinstance(x,np.ndarray):
353
+ x = x.item()
354
+ if isinstance(u,np.ndarray):
355
+ u = u.item()
356
+
357
+ try:
358
+ float(x)
359
+ hash(x)
360
+ except TypeError:
361
+ raise TypeError('x must be a real number') from None
362
+ except ValueError:
363
+ raise ValueError('x must be a real number') from None
188
364
 
189
365
  if self.rounding_u and not isinstance(x,Rational):
190
366
  finfo,x = _getfinfo(x)
@@ -193,10 +369,6 @@ class ummy(Dfunc,PrettyPrinter):
193
369
  else:
194
370
  u = _combu(u,float(abs(x)*finfo.rel_u),0)
195
371
 
196
- #uinfo = _getfinfo(u)[0]
197
- #if uc < uinfo.warn_u and u is not 0:
198
- #warn('a gummy/ummy was defined with an uncertainty too small to be accurately represented by a float\nuncertainty values may contain significant numerical errors',UncertiantyPrecisionWarning)
199
-
200
372
  self._finfo = finfo
201
373
  else:
202
374
  self._finfo = None
@@ -205,29 +377,80 @@ class ummy(Dfunc,PrettyPrinter):
205
377
  self._x = x
206
378
  self._u = u
207
379
 
208
- self._refs = 1
209
- if not isinf(u) and not isnan(u) and u > 0:
210
- if isinstance(dof,_GummyRef):
380
+ if not isinf(u) and not isnan(u) and u != 0:
381
+ if isinstance(dof,_udict):
382
+ # If the ummy has been created from a mathematical operation
383
+ # between other ummy's then correlations with the independent
384
+ # variables are stored in the _udict. The keys in the _udict
385
+ # are _UmmyRef instances that represent independent
386
+ # variables and store the dof and utype of the variable. The
387
+ # values are the correlation of the ummy with those independent
388
+ # variables (to within a sign) and are also equal to the
389
+ # deriviative of the ummy with respect to each of those variables
390
+ # divided by self._u. The actual value of the
391
+ # dof property will be computed with the W-S apporoximation.
392
+ # u may be negative indicating that it is sharing a _ref
393
+ # _udict with another ummy and has correlation = -1 with that
394
+ # ummy: e.g. if the ummy is the result of the operation 1 - x
395
+ # we don't bother to create a new _udict for the ummy, we just
396
+ # use x._ref with self._u having the opposite sign from x._u.
211
397
  self._ref = dof
212
- self._refs = utype
398
+
399
+ # _dof and _utype are set to None for now and will be computed
400
+ # if and when the dof and utype properties are called for the
401
+ # first time.
402
+ self._dof = None
403
+ self._utype = None
213
404
  else:
405
+ # The ummy represents an independent variable, that is u > 0
406
+ # and it is not (at this moment) correlated with any other
407
+ # ummy's.
408
+
214
409
  try:
215
- if isinstance(dof,Integral):
216
- dof = int(dof)
410
+ float(u)
411
+ hash(u)
412
+ if not isnan(u) and u < 0:
413
+ raise ValueError()
414
+ except TypeError:
415
+ raise TypeError('u must be a real number >= 0') from None
416
+ except ValueError:
417
+ raise ValueError('u must be a real number >= 0') from None
418
+
419
+ try:
420
+ if dof > self.max_dof:
421
+ dof = float('inf')
217
422
  else:
218
- dof = float(dof)
219
- if dof <= 0 or isnan(dof):
220
- raise TypeError()
423
+ if isinstance(dof,Integral):
424
+ dof = int(dof)
425
+ else:
426
+ dof = float(dof)
427
+ if dof <= 0 or isnan(dof):
428
+ raise ValueError()
429
+ except ValueError:
430
+ raise ValueError('dof must be a real number > 0') from None
221
431
  except TypeError:
222
- raise TypeError('dof must be a real number > 0')
223
- if dof > self.max_dof:
224
- dof = float('inf')
432
+ raise TypeError('dof must be a real number > 0') from None
433
+ if utype is not None and not isinstance(utype,str):
434
+ raise TypeError('utype must be str or None')
435
+
436
+ self._dof = dof
437
+ if utype is None:
438
+ self._utype = [None]
439
+ else:
440
+ self._utype = [utype]
225
441
 
226
- self._ref = _GummyRef(dof)
227
- if utype is not None:
228
- GummyTag.set_tag(self,utype)
442
+ # create an _UmmyRef to represent the actual independent
443
+ # variable and create a _udict with one entry defining
444
+ # the correlation with that _UmmyRef to be 1
445
+ self._ref = _udict(((_UmmyRef(dof,utype),1),))
229
446
  else:
447
+ # if u == 0, then the ummy will have no correlation with any
448
+ # independent variables, no utype and the dof property will return
449
+ # float('inf')
450
+
230
451
  self._ref = None
452
+ self._dof = None
453
+ self._utype = [None]
231
454
 
232
455
  @property
233
456
  def x(self):
@@ -235,7 +458,7 @@ class ummy(Dfunc,PrettyPrinter):
235
458
 
236
459
  @property
237
460
  def u(self):
238
- return self._u
461
+ return abs(self._u)
239
462
 
240
463
  @property
241
464
  def dof(self):
@@ -246,30 +469,71 @@ class ummy(Dfunc,PrettyPrinter):
246
469
  ummy is based on. If the ummy was created as the result of an
247
470
  operation between two or more other gummys, then the dof is the effective
248
471
  number of degrees of freedom calculated using the Welch-Satterthwaite
249
- approximation. Caution: A variation of the the Welch-Satterthwaite
250
- approximation is used that takes into account correlations, see
251
- [R. Willink, Metrologia, 44, 340 (2007)]. However correlations are
252
- not handled perfectly. So if accurate dof calculations are need, care
253
- should be taken to ensure that correlations are not generated in
254
- intermediate calculations.
472
+ approximation.
255
473
  """
474
+ if self._dof is not None:
475
+ return self._dof
476
+
256
477
  if self._ref is None:
257
- return float('inf')
258
- if self._ref.dof < 1:
259
- # Occasionally correlations can result in a _dof less than 1;
260
- # see the _get_dof method.
261
- return 1
262
- return self._ref.dof
478
+ self._dof = float('inf')
479
+ else:
480
+ # if _dof has not been set yet, calculate it using the
481
+ # Welch-Satterthwaite approximation and store the result in
482
+ # self._dof. self._ref is a dictionary and the keys are _UmmyRef
483
+ # instances representing the indedpendent variables and store the
484
+ # dof for those variables. The values are the correlations of
485
+ # self with those independent variables and these correlations
486
+ # are equal to the derivative of self with respect to each
487
+ # independent variable divided by self.u.
488
+
489
+ rdof = 0
490
+ for k,v in self._ref.items():
491
+ if k.dof is not None:
492
+ rdof += v**4/k.dof
493
+ if rdof > 0:
494
+ rdof = 1/rdof
495
+ if rdof > self.max_dof:
496
+ rdof = float('inf')
497
+ elif rdof < 1:
498
+ rdof = 1
499
+ self._dof = rdof
500
+ else:
501
+ self._dof = float('inf')
502
+
503
+ return self._dof
263
504
 
264
505
  @property
265
- def _dof(self):
266
- if self._ref is None:
267
- return float('inf')
268
- return self._ref.dof
269
- @_dof.setter
270
- def _dof(self,v):
271
- if self._ref is not None:
272
- self._ref.dof = v
506
+ def utype(self):
507
+ """
508
+ `str`, `None` or a list containing strings and possibly `None`
509
+
510
+ An arbitrary string value labeling the uncertainty type or or a
511
+ list of types if the gummy was constructed from independent
512
+ variables with different utypes.
513
+ """
514
+ if self._utype is None:
515
+ # if _utype has not been set yet, go through all the independent
516
+ # variables in self._ref and put all the utypes found in a set.
517
+ self._utype = []
518
+ for k in self._ref:
519
+ if k.utype not in self._utype:
520
+ self._utype.append(k.utype)
521
+
522
+ if len(self._utype) == 1:
523
+ return self._utype[0]
524
+ return self._utype
525
+
526
+ @property
527
+ def isindependent(self):
528
+ """
529
+ `bool`, read-only
530
+
531
+ Returns `True` if the ummy is an independent variable. That is the
532
+ ummy has u > 0 and was not correlated with any other ummy's when it was
533
+ created or is perfectly correlated or anti-correlated (correlation
534
+ coefficeint 1 or -1) with such an ummy.'
535
+ """
536
+ return (self._ref is not None) and len(self._ref) == 1
273
537
 
274
538
  @property
275
539
  def finfo(self):
@@ -278,16 +542,17 @@ class ummy(Dfunc,PrettyPrinter):
278
542
  return self._finfo
279
543
 
280
544
  @staticmethod
281
- def _copy(s,r,formatting=True,tofloat=False):
545
+ def _copy(s,r,formatting=True,totype=None):
282
546
  # copies attributes of s to r
283
- if tofloat:
284
- r._x = float(s._x)
285
- r._u = float(s._u)
547
+ if totype is not None:
548
+ r._x = totype(s._x)
549
+ r._u = totype(s._u)
286
550
  else:
287
551
  r._x = s._x
288
552
  r._u = s._u
289
553
  r._ref = s._ref
290
- r._refs = s._refs
554
+ r._dof = s._dof
555
+ r._utype = s._utype
291
556
  r._finfo = s._finfo
292
557
 
293
558
  if formatting:
@@ -296,24 +561,24 @@ class ummy(Dfunc,PrettyPrinter):
296
561
  if s.thousand_spaces != type(r).thousand_spaces:
297
562
  r.thousand_spaces = s.thousand_spaces
298
563
 
299
- def copy(self,formatting=True,tofloat=False):
564
+ def copy(self,formatting=True,totype=None):
300
565
  """
301
566
  Returns a copy of the ummy. If the `formatting` parameter is
302
567
  `True` the display formatting information will be copied and if
303
568
  `False` the display formatting will be set to the default for a
304
- new gummy. The default for `formatting` is `True`. If tofloat
305
- is true the x and u properties will be converted to float values
569
+ new gummy. The default for `formatting` is `True`. If totype
570
+ is defined the x and u properties will be converted to type `totype`
306
571
  before copying.
307
572
  """
308
573
  r = type(self).__new__(type(self))
309
- self._copy(self,r,formatting=formatting,tofloat=tofloat)
574
+ self._copy(self,r,formatting=formatting,totype=totype)
310
575
  return r
311
576
 
312
577
  def tofloat(self):
313
578
  """
314
- Returns a copy of the gummy with x an u converted to floats.
579
+ Returns a copy of the gummy with x an u converted to `float`.
315
580
  """
316
- return self.copy(formatting=False,tofloat=True)
581
+ return self.copy(formatting=False,totype=float)
317
582
 
318
583
  def splonk(self):
319
584
  """
@@ -327,21 +592,27 @@ class ummy(Dfunc,PrettyPrinter):
327
592
  """
328
593
  Returns the correlation coefficient between `self` and `g`.
329
594
  """
330
- if not isinstance(g,ummy):
595
+ if not isinstance(g,ummy) or self._ref is None or g._ref is None:
331
596
  return 0
332
- if self._ref is None or g._ref is None:
597
+
598
+ c = sign(self._u)*sign(g._u)*sum(g._ref[k]*v for k,v in self._ref.items() if k in g._ref)
599
+
600
+ if abs(c) < ummy.correlation_tolerance:
333
601
  return 0
334
- return (self._refs*g._refs)*self._ref.cor(g._ref)
602
+ if c < -1 + ummy.correlation_tolerance:
603
+ return -1
604
+ if c > 1 - ummy.correlation_tolerance:
605
+ return 1
606
+
607
+ return float(c)
335
608
 
336
609
  def covariance(self,g):
337
610
  """
338
611
  Returns the covariance between `self` and `g`.
339
612
  """
340
- if not isinstance(g,ummy):
341
- return 0
342
- if self._ref is None or g._ref is None:
343
- return 0
344
- return self._u*g._u*(self._refs*g._refs)*self._ref.cor(g._ref)
613
+ if isinstance(g,ummy):
614
+ return self.u*g.u*self.correlation(g)
615
+ return 0
345
616
 
346
617
  @staticmethod
347
618
  def correlation_matrix(gummys):
@@ -358,62 +629,9 @@ class ummy(Dfunc,PrettyPrinter):
358
629
  """
359
630
  return [[b.covariance(a) if isinstance(b,ummy) else 0 for b in gummys]
360
631
  for a in gummys]
361
-
362
- @staticmethod
363
- def _set_correlation_matrix(gummys, matrix):
364
- n = len(gummys)
365
- m = np.asarray(matrix)
366
-
367
- if not np.allclose(m.T,m,atol = 10*_GummyRef._cortol):
368
- raise ValueError('the correlation matrix is not symmetric')
369
- e,v = np.linalg.eigh(m)
370
- if any(e <= -_GummyRef._cortol):
371
- raise ValueError('the correlation matrix is not positive semi-definate')
372
-
373
- if m.shape != (n,n):
374
- raise ValueError('matrix must have shape len(gummys) x len(gummys)')
375
-
376
- # Check that the diagonal elements are 1 (leaving room for rounding errors).
377
- for i in range(n):
378
- if abs(m[i][i] - 1.0) > 0.01:
379
- raise ValueError('correlation matrix diagonal elements must be 1')
380
-
381
- # Set the correlations using the off diagonal elements.
382
- for i in range(0,n):
383
- for j in range ((i+1),n):
384
- if m[i][j] != 0:
385
- if gummys[i]._ref is not None and gummys[j]._ref is not None:
386
- gummys[i]._ref.set_cor(gummys[j],m[i][j])
387
- else:
388
- raise ValueError('constants cannot have a non-zero correlation')
389
-
390
- @staticmethod
391
- def _set_covariance_matrix(gummys, matrix):
392
- n = len(gummys)
393
- try:
394
- assert len(matrix) == n
395
- for r in matrix:
396
- assert len(r) == n
397
- except:
398
- raise ValueError('the covariance matrix must be a square matrix of the same length as gummys')
399
-
400
- # Set the u of each gummy using the diagonal of the matrix.
401
- for i in range(n):
402
- if matrix[i][i] == 0:
403
- gummys[i]._u = 0
404
- gummys[i]._ref = None
405
- else:
406
- if gummys[i]._ref is None:
407
- gummys[i]._ref = _GummyRef()
408
- gummys[i]._u = matrix[i][i]**0.5
409
-
410
- # Set the correlations
411
- m = [[e/(gummys[i]._u*gummys[j]._u) for j,e in enumerate(r)]
412
- for i,r in enumerate(matrix)]
413
- ummy._set_correlation_matrix(gummys,m)
414
632
 
415
633
  @classmethod
416
- def create(cls,x,u=None,dof=None,correlation_matrix=None,
634
+ def create(cls,x,u=None,dof=None,utype=None,correlation_matrix=None,
417
635
  covariance_matrix=None):
418
636
  """
419
637
  A class method that creates a list of (possibly) correlated ummys.
@@ -447,84 +665,162 @@ class ummy(Dfunc,PrettyPrinter):
447
665
  a list of ummys
448
666
  """
449
667
 
450
-
668
+ n = len(x)
451
669
  if covariance_matrix is not None:
452
670
  if correlation_matrix is not None:
453
671
  raise TypeError('correlation_matrix and covariance_matrix cannot both be specified')
672
+ try:
673
+ u = [sqrt(covariance_matrix[i][i]) for i in range(n)]
674
+ except ValueError:
675
+ raise ValueError('the diagonal elements of the covariance matrix must be positive real numbers')
676
+ except IndexError:
677
+ raise ValueError('covariance must have shape len(gummys) x len(gummys)')
678
+ correlation_matrix = [[covariance_matrix[i][j]/(u[i]*u[j]) for i in range(n)] for j in range(n)]
679
+ else:
680
+ if u is None:
681
+ u = [0]*n
682
+ elif _isscalar(u):
683
+ u = [u]*n
684
+
685
+ if correlation_matrix is not None:
686
+ if not _isscalar(dof):
687
+ if any(i != dof[0] for i in dof):
688
+ raise TypeError('dof cannot be set individually of a correlation of covariance matrix is specified')
689
+ dof = dof[0]
690
+ if not _isscalar(utype):
691
+ if any(i != utype[0] for i in utype):
692
+ raise TypeError('utype cannot be set individually of a correlation of covariance matrix is specified')
693
+ utype = utype[0]
454
694
 
455
- n = len(x)
456
- if u is None:
457
- u = [0]*n
458
- elif _isscalar(u):
459
- u = [u]*n
460
- if dof is None:
461
- dof = [float('inf')]*n
462
- elif _isscalar(dof):
463
- dof = [dof]*n
695
+ m = np.asarray(correlation_matrix)
464
696
 
465
- ret = [cls(x[i],u=u[i],dof=dof[i]) for i in range(n)]
697
+ if not np.allclose(m.T,m,atol = sqrt(ummy.correlation_tolerance)):
698
+ raise ValueError('the correlation matrix is not symmetric')
699
+
700
+ if m.shape != (n,n):
701
+ raise ValueError('the correlation matrix must have shape len(gummys) x len(gummys)')
466
702
 
467
- if correlation_matrix is not None:
468
- cls._set_correlation_matrix(ret, correlation_matrix)
703
+ for i in range(n):
704
+ if abs(m[i][i] - 1.0) > sqrt(ummy.correlation_tolerance):
705
+ raise ValueError('correlation matrix diagonal elements must be 1')
706
+
707
+ # The correlated values can be constructed from a linear combination
708
+ # of indepedent variables with coefficients equal to the matrix
709
+ # square root of the correlation matrix.
710
+ val,vec = np.linalg.eig(m)
711
+
712
+ # Round eigenvalues very close to zero to zero, allowing for small
713
+ # negative values that may appear due to the finite numerical
714
+ # precision.
715
+ for i in range(n):
716
+ if val[i] < -ummy.correlation_tolerance:
717
+ raise ValueError('the correlation matrix is not positive semi-definate')
718
+ if val[i] < ummy.correlation_tolerance:
719
+ val[i] = 0
720
+
721
+ # matrix sqaure root of m
722
+ sqrtm = vec*np.sqrt(val)@np.linalg.inv(vec)
723
+
724
+ ind = [_UmmyRef(dof,utype) for _ in range(n)]
725
+ idof = [_udict(((ind[j],float(sqrtm[i][j])) for j in range(n)))
726
+ for i in range(n)]
727
+
728
+ ret = [cls(x[i],u=u[i],dof=idof[i]) for i in range(n)]
729
+
730
+ else:
731
+ if dof is None:
732
+ dof = [float('inf')]*n
733
+ elif _isscalar(dof):
734
+ dof = [dof]*n
735
+
736
+ if utype is None or isinstance(utype,str):
737
+ utype = [utype]*n
469
738
 
470
- if covariance_matrix is not None:
471
- cls._set_covariance_matrix(ret, covariance_matrix)
739
+ ret = [cls(x[i],u=u[i],dof=dof[i],utype=utype[i]) for i in range(n)]
472
740
 
473
741
  return ret
474
742
 
475
743
  def tostring(self,fmt='unicode',nsig=None,**kwds):
476
744
  if nsig is None:
477
745
  nsig = self.nsig
746
+ if nsig > self.max_digits - 2:
747
+ nsig = self.max_digits - 2
748
+ if nsig < 1:
749
+ nsig = 1
478
750
 
479
- x = float(self._x)
480
751
  try:
481
- xabs = abs(x)
482
-
483
- if xabs != 0 and not isinf(xabs) and not isnan(xabs):
484
- xexp = _floor(log10(xabs))
485
- else:
486
- xexp = None
487
-
488
- u = float(self.u)
489
-
490
- if self._u == 0 or isnan(self._u) or isinf(self._u):
491
- if xexp is None:
492
- return str(self._x)
752
+ uexp = 0
753
+ xexp = 0
754
+ dp = 0
755
+ xd = _to_decimal(self._x,max_digits=self.max_digits)
756
+ ud = _to_decimal(self.u)
757
+ with localcontext(prec=self.max_digits):
758
+ if ud == 0 or not ud.is_finite():
759
+ if xd.is_finite():
760
+ nm = False
761
+ nd = self.max_digits
762
+ if self.finfo is not None and self.finfo.precision > 0 and self.finfo.precision <= nd:
763
+ nd = self.finfo.precision
764
+ nm = True
765
+ xd = round(xd,nd-xd.adjusted()-1)
766
+ if xd == 0:
767
+ xexp = 0
768
+ else:
769
+ xexp = xd.adjusted()
770
+
771
+ if self._x == xd or nm:
772
+ xd = xd.normalize()
493
773
  else:
494
- if (((self.sci_notation is None and (xexp > self.sci_notation_high or xexp < self.sci_notation_low))
495
- or self.sci_notation) ):
496
- xtxt = self._format_mantissa(fmt,x*10**(-xexp),
497
- self.finfo.precision)
498
- etxt = _format_exp(fmt,xexp)
499
- return xtxt + etxt
774
+ ud = round(ud,nsig-ud.adjusted()-1)
775
+ ud = round(ud,nsig-ud.adjusted()-1) # round again in case 9 round up to 10 in the last line
776
+ uexp = ud.adjusted()
777
+
778
+ if xd.is_finite():
779
+ try:
780
+ xd = xd.quantize(ud)
781
+ except InvalidOperation:
782
+ xd = round(xd,self.max_digits-xd.adjusted()-1)
783
+ xexp = xd.adjusted()
784
+
785
+ if abs(xd) < abs(ud):
786
+ dp = xd.adjusted() - uexp
787
+ xexp = uexp
788
+ else:
789
+ xexp = xd.adjusted()
500
790
  else:
501
- return self._format_mantissa(fmt,x,
502
- self.finfo.precision)
503
-
504
- uabs = abs(u)
505
- if uabs != 0:
506
- uexp = _floor(log10(uabs))
507
-
508
- if xexp is None:
509
- xp = uexp
791
+ xexp = uexp
792
+
793
+ scin = False
794
+ if self.sci_notation is None:
795
+ if xexp > self.sci_notation_high or xexp < self.sci_notation_low:
796
+ scin = True
510
797
  else:
511
- xp = max(xexp,uexp)
798
+ scin = self.sci_notation
799
+ if xexp > self.max_digits - 1:
800
+ scin = True
801
+ elif xexp < 0 and len(xd.as_tuple().digits) + self.sci_notation_low > self.max_digits - 1:
802
+ scin = True
803
+ elif ud.is_finite() and ud != 0 and xd.is_finite() and (xd.as_tuple()[2] > 0 or len(xd.as_tuple()[1]) < nsig):
804
+ scin = True
805
+
806
+ if not scin:
807
+ xexp = 0
512
808
 
513
- psn = (uexp - nsig + 1 > 0)
514
-
515
- if (((self.sci_notation is None and (xp > self.sci_notation_high or xp < self.sci_notation_low))
516
- or self.sci_notation) or psn or (xexp is not None and (uexp > xexp and xexp == 0))):
517
- xtxt = self._format_mantissa(fmt,x*10**(-xp),-uexp+xp+nsig-1)
518
- etxt = _format_exp(fmt,xp)
809
+ if xexp == 0:
810
+ txt =_decimal_str(xd,dalign=0,fmt=fmt)
519
811
  else:
520
- xtxt = self._format_mantissa(fmt,x,-uexp+nsig-1)
521
- etxt = ''
812
+ txt = _decimal_str(xd,dplace=dp,fmt=fmt)
522
813
 
523
- if nsig <= 0:
524
- return xtxt + etxt
525
-
526
- utxt = self._format_mantissa(fmt,u*10**(-uexp),nsig-1,parenth=True)
527
- return xtxt + '(' + utxt + ')' + etxt
814
+ if xd.is_finite():
815
+ if ud != 0:
816
+ txt += '(' + _decimal_str(ud,dplace=None,fmt=fmt) + ')'
817
+ txt += _format_exp(fmt,xexp)
818
+ else:
819
+ if ud != 0:
820
+ txt += '(' + _decimal_str(ud,dplace=None,fmt=fmt)
821
+ txt += _format_exp(fmt,xexp) + ')'
822
+ return txt
823
+
528
824
  except:
529
825
  try:
530
826
  return(str(self.x) + '{' + str(self.u) + '}' + '??')
@@ -533,114 +829,9 @@ class ummy(Dfunc,PrettyPrinter):
533
829
  return(str(self.x) + '{??}')
534
830
  except:
535
831
  return('??')
536
-
537
- def _format_mantissa(self,fmt,x,sig,parenth=False):
538
- try:
539
- if isnan(x):
540
- return 'nan'
541
- if isinf(x) and x > 0:
542
- if fmt == 'html':
543
- return '&infin;'
544
- if fmt == 'latex':
545
- return r'\infty'
546
- if fmt == 'ascii':
547
- return 'inf'
548
- return '\u221E'
549
- if isinf(x) and x < 0:
550
- if fmt == 'html':
551
- return '-&infin;'
552
- if fmt == 'latex':
553
- return r'-\infty'
554
- if fmt == 'ascii':
555
- return '-inf'
556
- return '-\u221E'
557
- except:
558
- pass
559
832
 
560
- ellipses = ''
561
- if isinstance(x,Rational) and sig is None:
562
- if x == int(x):
563
- s = str(int(x))
564
- else:
565
- xf = float(x)
566
- e = 15 - _floor(log10(abs(xf)))
567
- if e < 0:
568
- e = 0
569
- n = 10**e*x
570
- if int(n) != n:
571
- ellipses = '...'
572
- s = str(round(xf,e))
573
- elif sig is None:
574
- s = str(x)
575
- else:
576
- if x != 0 and self.max_digits is not None:
577
- if mpf is not None and isinstance(x,mpf):
578
- em = _floor(mp.log10(abs(x)))
579
- else:
580
- em = _floor(log10(abs(float(x))))
581
- e = self.max_digits - em
582
- if e < 0:
583
- e = 0
584
- if sig > e and sig > 0:
585
- ellipses = '...'
586
- sig = e
587
- if sig < 0:
588
- sig = 0
589
- if mpf is not None and isinstance(x,mpf):
590
- s = mp.nstr(x,n=sig+1,strip_zeros=False)
591
- else:
592
- s = round(float(x),sig)
593
- s = ('{:.' + str(sig) + 'f}').format(s)
594
-
595
- if parenth:
596
- s = s.replace('.','')
597
- s = s.lstrip('0')
598
- if len(s) == 0:
599
- s = '0'
600
- return s
601
- elif self.thousand_spaces:
602
- # Now insert spaces every three digits
603
- if fmt == 'html':
604
- sp = '&thinsp;'
605
- elif fmt == 'latex':
606
- sp = r'\,'
607
- else:
608
- sp = ' '
609
-
610
- d = s.find('.')
611
- if d != -1:
612
- i = d + 1
613
- while i < len(s) and s[i].isdigit():
614
- i += 1
615
- nr = i - d - 1
616
- else:
617
- nr = 0
618
- d = len(s)
619
-
620
- i = d - 1
621
- while i > 0 and s[i].isdigit():
622
- i -= 1
623
- nl = d - i - 1
624
-
625
- if nr <= 4 and nl <= 4:
626
- return s + ellipses
627
-
628
- s = list(s)
629
- if nr > 4:
630
- for i in range(int(nr/3)):
631
- s.insert(d + i + (i+1)*3 + 1, sp)
632
- if nl > 4:
633
- for i in range(int(nl/3)):
634
- s.insert(d - (i+1)*3, sp)
635
-
636
- s = ''.join(s)
637
- if s.endswith(sp):
638
- s = s[:-(len(sp))]
639
-
640
- return s + ellipses
641
-
642
- @classmethod
643
- def _get_dof(cls,dof1,dof2,du1,du2,c):
833
+ #@classmethod
834
+ #def _get_dof(cls,dof1,dof2,du1,du2,c):
644
835
  # Use the Welch-Satterhwaite approximation to combine dof1 and dof2.
645
836
  # See [R. Willink, Metrologia, 44, 340 (2007)] concerning the use
646
837
  # of the Welch-Satterwaite approximation with correlated values.
@@ -653,23 +844,21 @@ class ummy(Dfunc,PrettyPrinter):
653
844
  # d = (du1**2 + c*du1*du2)**2/dof1 + (du2**2 + c*du1*du2)**2/dof2
654
845
  # Willink's formula is used in the _apply method but not here.
655
846
 
656
- d = du1**4/dof1 + du2**4/dof2
657
- d += 2*c**2*(((du1 + du2)**4 - du1**4 - du2**4)/
658
- ((1 - 2*c + 2*c**2)*(dof1+dof2)))
847
+ #d = du1**4/dof1 + du2**4/dof2
848
+ #d += 2*c**2*(((du1 + du2)**4 - du1**4 - du2**4)/
849
+ #((1 - 2*c + 2*c**2)*(dof1+dof2)))
659
850
 
660
- if d == 0:
661
- return float('inf')
851
+ #if d == 0:
852
+ #return float('inf')
662
853
 
663
- r = 1/d
664
- if r > cls.max_dof:
665
- return float('inf')
854
+ #r = 1/d
855
+ #if r > cls.max_dof:
856
+ #return float('inf')
666
857
 
667
- #if r < -1e-6:
668
- #raise ValueError('dof is negative')
669
- if r < 0:
670
- r = 1
858
+ #if r < 0:
859
+ #r = 1
671
860
 
672
- return r
861
+ #return r
673
862
 
674
863
  @classmethod
675
864
  def _apply(cls,function,derivative,*args,fxdx=None):
@@ -682,57 +871,62 @@ class ummy(Dfunc,PrettyPrinter):
682
871
  if len(args) == 1:
683
872
  d = [d]
684
873
 
685
- ad = [(a,a.u*p) for a,p in zip(args,d)
686
- if isinstance(a,ummy) and a._u != 0]
874
+ xtype = _xtype(fx)
875
+
876
+ try:
877
+ ad = [(a,a._u*p) for a,p in zip(args,d)
878
+ if isinstance(a,ummy) and a._u != 0]
879
+ except TypeError:
880
+ ad = [(a,xtype(a._u)*p) for a,p in zip(args,d)
881
+ if isinstance(a,ummy) and a._u != 0]
687
882
  if len(ad) == 0:
688
883
  return cls(fx)
689
884
  args,du = list(map(list, zip(*ad)))
690
885
 
691
886
  if len(args) == 1:
692
- refs = -args[0]._refs if du[0] < 0 else args[0]._refs
693
- r = cls(fx,u=abs(du[0]),dof=args[0]._ref,utype=refs)
887
+ r = cls(fx,u=du[0],dof=args[0]._ref)
694
888
  return r
695
889
 
696
890
  maxdu = max(abs(a) for a in du)
697
891
  if maxdu == 0:
698
892
  return cls(fx)
699
- dun = [d/maxdu for d in du]
893
+ try:
894
+ dun = [d/maxdu for d in du]
895
+ except TypeError:
896
+ dun = [xtype(d)/xtype(maxdu) for d in du]
700
897
 
701
898
  u = sum(d**2 for d in dun)
702
- u += 2*sum(args[i].correlation(args[j])*dun[i]*dun[j]
703
- for i in range(len(args)) for j in range(i+1,len(args)))
899
+ try:
900
+ u += 2*sum(sum(args[i]._ref[k]*v for k,v in args[j]._ref.items()
901
+ if k in args[i]._ref)*dun[i]*dun[j]
902
+ for i in range(len(args)) for j in range(i+1,len(args)))
903
+ except TypeError:
904
+ u += 2*sum(xtype(sum(args[i]._ref[k]*v for k,v in args[j]._ref.items()
905
+ if k in args[i]._ref))*dun[i]*dun[j]
906
+ for i in range(len(args)) for j in range(i+1,len(args)))
704
907
 
705
908
  if not isnan(u):
706
909
  if u < 0:
707
910
  if u < -1e6:
708
- # something horrible happened, should only get here due to a bug
911
+ # something horrible and unexpected happened
709
912
  raise FloatingPointError('u is sqrt of ' + str(maxdu**2*u))
710
913
  u = 0
711
914
 
712
- u = u**0.5
915
+ if hasattr(u,'sqrt'):
916
+ u = u.sqrt()
917
+ else:
918
+ u = u**0.5
713
919
  u = maxdu*u
714
920
 
715
921
  if u == 0 or isnan(u):
716
922
  return cls(fx)
717
923
 
718
- du = [d/u for d in du]
719
- dof = float('inf')
720
- if any(not isinf(a._dof) for a in args):
721
- dm = sum(sum(args[i].correlation(args[j])*du[i]*du[j]
722
- for i in range(len(du)))**2/args[j]._dof
723
- for j in range(len(du)))
724
- if dm > 0:
725
- dof = 1/dm
726
-
727
- r = cls(fx,u=u,dof=dof)
728
-
729
- rl = {a._ref for a in args}
730
- for i,a in enumerate(args):
731
- s = sum(a.correlation(args[j])*du[j] for j in range(len(du)))
732
- a._ref.combl(r,float(a._refs*s),float(a._refs*du[i]),rl)
733
- if r._ref in rl:
734
- break
735
- r._check_cor()
924
+ try:
925
+ du = [float(d/u) for d in du]
926
+ except TypeError:
927
+ du = [float(xtype(d)/xtype(u)) for d in du]
928
+
929
+ r = cls(fx,u=u,dof=_combrd(args,du))
736
930
 
737
931
  return r
738
932
 
@@ -752,35 +946,80 @@ class ummy(Dfunc,PrettyPrinter):
752
946
 
753
947
  return cls._apply(function,lambda *x: d,*args,fxdx=fxx)
754
948
 
755
- def _check_cor(self):
756
- if self._ref is None:
757
- return
758
-
759
- rl = list(self._ref._cor.items())
760
- for k,v in rl:
761
- if k is not None:
762
- if abs(v) > 1.01:
763
- raise ValueError('abs(correlation) > 1')
764
- if abs(v) < _GummyRef._cortol:
765
- del k._cor[self._ref]
766
- del self._ref._cor[k]
767
- return
768
- if v > _GummyRef._cortolp:
769
- k.copyto(self,1)
770
- return
771
- if v < _GummyRef._cortoln:
772
- k.copyto(self,-1)
773
- return
774
-
775
- def _combc(self,a,b,dua,dub,c):
776
- dua = float(dua)
777
- dub = float(dub)
778
-
779
- a._ref.combl(self,c*dub*a._refs + dua*a._refs,dua*a._refs)
780
- if self._ref is not a._ref:
781
- b._ref.combl(self,c*dua*b._refs + dub*b._refs,dub*b._refs,[a._ref])
782
- if self._ref is not b._ref:
783
- self._check_cor()
949
+ def _aop(self,b,f,d1,d2):
950
+ # computes the result of a binary operation f(self,b). d1 and d2 are
951
+ # the derivatives with respect to self and b respectively.
952
+
953
+ # correlation between self and b (/sign(self._u)*sign(b._u))
954
+ if not isinstance(b,ummy) or b._ref is None or self._ref is None:
955
+ c = 0
956
+ else:
957
+ c = sum(b._ref[k]*v for k,v in self._ref.items() if k in b._ref)
958
+
959
+ x = f(self._x,b._x)
960
+ try:
961
+ dua = d1(self._x,b._x)*self._u
962
+ dub = d2(self._x,b._x)*b._u
963
+ u = _combu(dua,dub,c) # combined standard uncertainty
964
+ except TypeError:
965
+ # e.g. if self.x and b.x are Decimal we may need to convert the
966
+ # correlation c which is float to Decimal
967
+ xtype = _xtype(x)
968
+ dua = d1(self._x,b._x)*xtype(self._u)
969
+ dub = d2(self._x,b._x)*xtype(b._u)
970
+ u = _combu(dua,dub,xtype(c))
971
+
972
+ # some special cases where we don't need to bother to work out all the
973
+ # correlations with the independent variables
974
+ if u == 0:
975
+ return type(b)(x)
976
+
977
+ if self._u == 0:
978
+ return type(b)(x,u=u,dof=b._ref)
979
+
980
+ if b._u == 0:
981
+ return type(b)(x,u=u,dof=self._ref)
982
+
983
+ if self._ref is b._ref:
984
+ s = sign(dua + dub)
985
+ return type(b)(x,u=s*u,dof=self._ref)
986
+
987
+ # not a special case, so combine the correlation dictionaries of self
988
+ # and b
989
+
990
+ dua = float(dua/u)
991
+ dub = float(dub/u)
992
+
993
+ ref = _udict(((k,dua*v) for k,v in self._ref.items()))
994
+ for k,v in b._ref.items():
995
+ if k in ref:
996
+ ref[k] += v*dub
997
+ else:
998
+ ref[k] = v*dub
999
+
1000
+ # check that sum squared correlations add to 1 in case rounding errors
1001
+ # have accumulated
1002
+ n = sum([i**2 for i in ref.values()])
1003
+ if abs(n-1) > ummy.correlation_tolerance:
1004
+ n = sqrt(n)
1005
+ for k,v in ref.items():
1006
+ ref[k] /= n
1007
+
1008
+ # round correlations to 1, -1 or 0 if within correlation_tolerance of
1009
+ # these values
1010
+ for k,v in list(ref.items()):
1011
+ c = ref[k]
1012
+ if c < -1 + ummy.correlation_tolerance:
1013
+ ref = _udict(((k,1),))
1014
+ u = -u
1015
+ break
1016
+ elif c > 1 - ummy.correlation_tolerance:
1017
+ ref = _udict(((k,1),))
1018
+ break
1019
+ elif abs(c) < ummy.correlation_tolerance:
1020
+ del ref[k]
1021
+
1022
+ return type(b)(x,u=u,dof=ref)
784
1023
 
785
1024
  def __add__(self,b):
786
1025
  if isinstance(b,np.ndarray):
@@ -792,33 +1031,12 @@ class ummy(Dfunc,PrettyPrinter):
792
1031
  return immy(self) + b
793
1032
 
794
1033
  if not isinstance(b,ummy):
795
- return type(self)(self._x + b,self._u,dof=self._ref,utype=self._refs)
796
-
797
- c = self.correlation(b)
798
- x = self._x + b._x
799
- u = _combu(self._u,b._u,c)
1034
+ return type(self)(self._x + b,u=self._u,dof=self._ref)
800
1035
 
801
- if u == 0:
802
- return type(b)(x)
803
-
804
- if self._u == 0:
805
- return type(b)(x,u,dof=b._ref,utype=b._refs)
806
-
807
- if b._u == 0:
808
- return type(b)(x,u,dof=self._ref,utype=self._refs)
809
-
810
- dua = self._u/u
811
- dub = b._u/u
812
- if not isinf(self._ref.dof) or not isinf(b._ref.dof):
813
- dof = self._get_dof(self._dof,b._dof,dua,dub,c)
814
- else:
815
- dof = float('inf')
816
-
817
- r = type(b)(x,u=u,dof=dof)
818
-
819
- r._combc(self,b,dua,dub,c)
820
-
821
- return r
1036
+ return self._aop(b,
1037
+ lambda x1,x2:x1 + x2,
1038
+ lambda x1,x2:1,
1039
+ lambda x1,x2:1)
822
1040
 
823
1041
  def __radd__(self,b):
824
1042
  if isinstance(b,np.ndarray):
@@ -830,7 +1048,7 @@ class ummy(Dfunc,PrettyPrinter):
830
1048
  if isinstance(b,ummy):
831
1049
  return b.__add__(self)
832
1050
 
833
- return type(self)(self._x + b,self._u,dof=self._ref,utype=self._refs)
1051
+ return type(self)(self._x + b,u=self._u,dof=self._ref)
834
1052
 
835
1053
  def __sub__(self,b):
836
1054
  if isinstance(b,np.ndarray):
@@ -843,33 +1061,12 @@ class ummy(Dfunc,PrettyPrinter):
843
1061
  return immy(self) - b
844
1062
 
845
1063
  if not isinstance(b,ummy):
846
- return type(self)(self._x - b,self._u,dof=self._ref,utype=self._refs)
847
-
848
- c = self.correlation(b)
849
- x = self._x - b._x
850
- u = _combu(self._u,-b._u,c)
1064
+ return type(self)(self._x - b,u=self._u,dof=self._ref)
851
1065
 
852
- if u == 0:
853
- return type(b)(x)
854
-
855
- if self._u == 0:
856
- return type(b)(x,u,dof=b._ref,utype=-b._refs)
857
-
858
- if b._u == 0:
859
- return type(b)(x,u,dof=self._ref,utype=self._refs)
860
-
861
- dua = self._u/u
862
- dub = -b._u/u
863
- if not isinf(self._ref.dof) or not isinf(b._ref.dof):
864
- dof = self._get_dof(self._dof,b._dof,dua,dub,c)
865
- else:
866
- dof = float('inf')
867
-
868
- r = type(b)(x,u=u,dof=dof)
869
-
870
- r._combc(self,b,dua,dub,c)
871
-
872
- return r
1066
+ return self._aop(b,
1067
+ lambda x1,x2:x1 - x2,
1068
+ lambda x1,x2:1,
1069
+ lambda x1,x2:-1)
873
1070
 
874
1071
  def __rsub__(self,b):
875
1072
  if isinstance(b,np.ndarray):
@@ -881,7 +1078,7 @@ class ummy(Dfunc,PrettyPrinter):
881
1078
  if isinstance(b,ummy):
882
1079
  return b.__sub__(self)
883
1080
 
884
- r = type(self)(b - self._x,self._u,dof=self._ref,utype=-self._refs)
1081
+ r = type(self)(b - self._x,u=-self._u,dof=self._ref)
885
1082
  return r
886
1083
 
887
1084
  def __mul__(self,b):
@@ -895,38 +1092,20 @@ class ummy(Dfunc,PrettyPrinter):
895
1092
  return immy(self)*b
896
1093
 
897
1094
  if not isinstance(b,ummy):
898
- refs = -self._refs if b < 0 else self._refs
899
- return type(self)(self._x*b,abs(self._u*b),dof=self._ref,utype=refs)
900
-
901
- c = self.correlation(b)
902
- x = self._x*b._x
903
- dua = b._x*self._u
904
- dub = self._x*b._u
905
- u = _combu(dua,dub,c)
906
-
907
- if u == 0:
908
- return type(b)(x)
909
-
910
- if self._u == 0:
911
- refs = -b._refs if self._x < 0 else b._refs
912
- return type(b)(x,u,dof=b._ref,utype=refs)
913
-
914
- if b._u == 0:
915
- refs = -self._refs if b._x < 0 else self._refs
916
- return type(b)(x,u,dof=self._ref,utype=refs)
917
-
918
- dua = dua/u
919
- dub = dub/u
920
- if not isinf(self._ref.dof) or not isinf(b._ref.dof):
921
- dof = self._get_dof(self._dof,b._dof,dua,dub,c)
922
- else:
923
- dof = float('inf')
924
-
925
- r = type(b)(x,u=u,dof=dof)
926
-
927
- r._combc(self,b,dua,dub,c)
928
-
929
- return r
1095
+ x = self._x*b
1096
+ try:
1097
+ ub = self._u*b
1098
+ except TypeError:
1099
+ if isinstance(x,Integral):
1100
+ ub = MFraction.fromnum(self._u)*b
1101
+ else:
1102
+ ub = type(x)(self._u)*b
1103
+ return type(self)(x,u=ub,dof=self._ref)
1104
+
1105
+ return self._aop(b,
1106
+ lambda x1,x2:x1*x2,
1107
+ lambda x1,x2:x2,
1108
+ lambda x1,x2:x1)
930
1109
 
931
1110
  def __rmul__(self,b):
932
1111
  if isinstance(b,np.ndarray):
@@ -938,8 +1117,12 @@ class ummy(Dfunc,PrettyPrinter):
938
1117
  if isinstance(b,ummy):
939
1118
  return b.__mul__(self)
940
1119
 
941
- refs = -self._refs if b < 0 else self._refs
942
- return type(self)(self._x*b,abs(self._u*b),dof=self._ref,utype=refs)
1120
+ x = self._x*b
1121
+ try:
1122
+ ub = self._u*b
1123
+ except TypeError:
1124
+ ub = _xtype(x)(self._u)*b
1125
+ return type(self)(x,u=ub,dof=self._ref)
943
1126
 
944
1127
  def __truediv__(self,b):
945
1128
  if isinstance(b,np.ndarray):
@@ -958,10 +1141,12 @@ class ummy(Dfunc,PrettyPrinter):
958
1141
  x = MFraction(self._x,b)
959
1142
  else:
960
1143
  x = self._x/b
961
- refs = -self._refs if b < 0 else self._refs
962
- return type(self)(x,abs(self._u/b),dof=self._ref,utype=refs)
1144
+ try:
1145
+ ub = self._u/b
1146
+ except TypeError:
1147
+ ub = _xtype(x)(self._u)/b
1148
+ return type(self)(x,ub,dof=self._ref)
963
1149
 
964
- c = self.correlation(b)
965
1150
  if b._x == 0:
966
1151
  raise ZeroDivisionError('division by zero')
967
1152
  else:
@@ -971,34 +1156,10 @@ class ummy(Dfunc,PrettyPrinter):
971
1156
  else:
972
1157
  x = self._x/b._x
973
1158
 
974
- dua = self._u/b._x
975
- dub = -(self._x*b._u/b._x)/b._x
976
-
977
- u = _combu(dua,dub,c)
978
-
979
- if u == 0:
980
- return type(b)(x)
981
-
982
- if self._u == 0:
983
- refs = b._refs if self._x < 0 else -b._refs
984
- return type(b)(x,u,dof=b._ref,utype=refs)
985
-
986
- if b._u == 0:
987
- refs = -self._refs if b._x < 0 else self._refs
988
- return type(b)(x,u,dof=self._ref,utype=refs)
989
-
990
- dua = dua/u
991
- dub = dub/u
992
- if not isinf(self._ref.dof) or not isinf(b._ref.dof):
993
- dof = self._get_dof(self._dof,b._dof,dua,dub,c)
994
- else:
995
- dof = float('inf')
996
-
997
- r = type(b)(x,u=u,dof=dof)
998
-
999
- r._combc(self,b,dua,dub,c)
1000
-
1001
- return r
1159
+ return self._aop(b,
1160
+ lambda x1,x2:x1/x2,
1161
+ lambda x1,x2:1/x2,
1162
+ lambda x1,x2:-x1/x2**2)
1002
1163
 
1003
1164
  def __rtruediv__(self,b):
1004
1165
  if isinstance(b,np.ndarray):
@@ -1018,9 +1179,11 @@ class ummy(Dfunc,PrettyPrinter):
1018
1179
  x = MFraction(b,self._x)
1019
1180
  else:
1020
1181
  x = b/self._x
1021
- u = abs(b*self._u/self._x**2)
1022
- refs = self._refs if b < 0 else -self._refs
1023
- return type(self)(x,u,dof=self._ref,utype=refs)
1182
+ try:
1183
+ ub = -b*self._u/self._x**2
1184
+ except TypeError:
1185
+ ub = -b*_xtype(x)(self._u)/self._x**2
1186
+ return type(self)(x,ub,dof=self._ref)
1024
1187
 
1025
1188
  def __pow__(self,b):
1026
1189
  if isinstance(b,np.ndarray):
@@ -1042,57 +1205,30 @@ class ummy(Dfunc,PrettyPrinter):
1042
1205
  x = MFraction(1,self._x**-b)
1043
1206
  else:
1044
1207
  x = self._x**b
1045
- u = abs(b*self._x**(b-1)*self._u)
1046
- if self._x < 0:
1047
- sgn = int(-((-1)**b))
1048
- else:
1049
- sgn = 1
1050
- if b < 0:
1051
- sgn = -sgn
1052
- return type(self)(x,u,dof=self._ref,utype=sgn*self._refs)
1208
+ try:
1209
+ ub = b*self._x**(b-1)*self._u
1210
+ except TypeError:
1211
+ ub = b*self._x**(b-1)*_xtype(x)(self._u)
1212
+ return type(self)(x,ub,dof=self._ref)
1053
1213
 
1054
1214
  if self._x <= 0:
1055
1215
  raise ValueError('a negative or zero value cannot raised to a power which has an uncertainty')
1056
1216
 
1057
- c = self.correlation(b)
1058
1217
  if (isinstance(self._x,Integral) and
1059
1218
  isinstance(b._x,Integral) and b._x < 0):
1060
- x = MFraction(1,self._x**-b._x)
1219
+ f = lambda x1,x2:MFraction(1,x1**-x2)
1061
1220
  else:
1062
- x = self._x**b._x
1063
- dua = b._x*self._x**(b._x-1)*self._u
1064
- lgx = log(self._x)
1065
-
1066
- # don't exactly remember why I did this,
1067
- # but it causes problems if x is int.
1068
- #try:
1069
- #lgx = type(self._x)(lgx)
1070
- #except:
1071
- #pass
1072
-
1073
- dub = lgx*self._x**b._x*b._u
1074
- u = _combu(dua,dub,c)
1075
-
1076
- if u == 0:
1077
- return type(b)(x)
1078
-
1079
- if self._u == 0:
1080
- refs = -b._refs if self._x < 0 else b._refs
1081
- return type(b)(x,u,dof=b._ref,utype=refs)
1221
+ f = lambda x1,x2:x1**x2
1082
1222
 
1083
- dua = dua/u
1084
- dub = dub/u
1085
-
1086
- if not isinf(self._ref.dof) or not isinf(b._ref.dof):
1087
- dof = self._get_dof(self._dof,b._dof,dua,dub,c)
1223
+ if hasattr(self._x,'ln'):
1224
+ lgx = self._x.ln()
1088
1225
  else:
1089
- dof = float('inf')
1226
+ lgx = log(self._x)
1090
1227
 
1091
- r = type(b)(x,u=u,dof=dof)
1092
-
1093
- r._combc(self,b,dua,dub,c)
1094
-
1095
- return r
1228
+ return self._aop(b,
1229
+ f,
1230
+ lambda x1,x2:x2*x1**(x2 - 1),
1231
+ lambda x1,x2:lgx*x1**x2)
1096
1232
 
1097
1233
  def __rpow__(self,b):
1098
1234
  if isinstance(b,np.ndarray):
@@ -1118,18 +1254,16 @@ class ummy(Dfunc,PrettyPrinter):
1118
1254
  if b < 0:
1119
1255
  raise ValueError('a negative number cannot raised to a power which has an uncertainty')
1120
1256
 
1121
- lgb = log(b)
1122
-
1123
- # don't exactly remember why I did this,
1124
- # but it causes problems if x is int.
1125
- #try:
1126
- #lgb = type(b)(lgb)
1127
- #except:
1128
- #pass
1257
+ if hasattr(b,'ln'):
1258
+ lgb = b.ln()
1259
+ else:
1260
+ lgb = log(b)
1129
1261
 
1130
- u = abs(b**self._x*lgb*self._u)
1131
- refs = -self._refs if b < 0 else self._refs
1132
- return type(self)(x,u,dof=self._ref,utype=refs)
1262
+ try:
1263
+ ub = b**self._x*lgb*self._u
1264
+ except TypeError:
1265
+ ub = b**self._x*lgb*_xtype(x)(self._u)
1266
+ return type(self)(x,ub,dof=self._ref)
1133
1267
 
1134
1268
  def _nprnd(self,f):
1135
1269
  return type(self)(f(self._x))
@@ -1144,7 +1278,7 @@ class ummy(Dfunc,PrettyPrinter):
1144
1278
  if isinstance(b,Complex) and not isinstance(b,Real):
1145
1279
  return immy(self) // b
1146
1280
 
1147
- return self._truediv(b)._nprnd(_floor)
1281
+ return self.__truediv__(b)._nprnd(floor)
1148
1282
 
1149
1283
  def __rfloordiv__(self,b):
1150
1284
  if isinstance(b,np.ndarray):
@@ -1156,7 +1290,7 @@ class ummy(Dfunc,PrettyPrinter):
1156
1290
  if isinstance(b,ummy):
1157
1291
  return b.__floordiv__(self)
1158
1292
 
1159
- return self._rtruediv(b)._nprnd(_floor)
1293
+ return self.__rtruediv__(b)._nprnd(floor)
1160
1294
 
1161
1295
  def __mod__(self,b):
1162
1296
  if isinstance(b,np.ndarray):
@@ -1169,7 +1303,7 @@ class ummy(Dfunc,PrettyPrinter):
1169
1303
  return immy(self) % b
1170
1304
 
1171
1305
  ret = ummy._apply(lambda x1,x2: x1%x2,
1172
- lambda x1,x2: (1, _sign(x2)*abs(x1//x2)),self,b)
1306
+ lambda x1,x2: (1,copysign(abs(x1//x2),x2)),self,b)
1173
1307
  return type(self)(ret)
1174
1308
 
1175
1309
  def __rmod__(self,b):
@@ -1180,14 +1314,20 @@ class ummy(Dfunc,PrettyPrinter):
1180
1314
  return b.__mod__(self)
1181
1315
 
1182
1316
  ret = ummy._apply(lambda x1,x2: x1%x2,
1183
- lambda x1,x2: (1, _sign(x2)*abs(x1//x2)),b,self)
1317
+ lambda x1,x2: (1,copysign(abs(x1//x2),x2)),b,self)
1184
1318
  return type(self)(ret)
1319
+
1320
+ def __divmod__(self,b):
1321
+ return (self//b,self%b)
1322
+
1323
+ def __rdivmod__(self,b):
1324
+ return (b//self,b%self)
1185
1325
 
1186
1326
  def __neg__(self):
1187
1327
  r = self.copy(formatting=False)
1188
1328
  r._x = -self._x
1189
1329
  if self._ref is not None:
1190
- r._refs = -self._refs
1330
+ r._u = -self._u
1191
1331
  return r
1192
1332
 
1193
1333
  def __pos__(self):
@@ -1198,7 +1338,7 @@ class ummy(Dfunc,PrettyPrinter):
1198
1338
  if self._x < 0:
1199
1339
  r._x = -self._x
1200
1340
  if self._ref is not None:
1201
- r._refs = -self._refs
1341
+ r._u = -self._u
1202
1342
  return r
1203
1343
 
1204
1344
  def __eq__(self,v):
@@ -1208,7 +1348,7 @@ class ummy(Dfunc,PrettyPrinter):
1208
1348
  if isinstance(v,ummy):
1209
1349
  if self.u == 0 and v.u == 0:
1210
1350
  return self._x == v._x
1211
- if self._ref is v._ref and self._refs == v._refs:
1351
+ if self.correlation(v) == 1:
1212
1352
  return self.x == v.x and self.u == v.u
1213
1353
  return False
1214
1354
 
@@ -1216,16 +1356,77 @@ class ummy(Dfunc,PrettyPrinter):
1216
1356
  return self.x == v
1217
1357
 
1218
1358
  return False
1359
+
1360
+ def __ne__(self, v):
1361
+ return not self == v
1219
1362
 
1220
- #def __float__(self):
1221
- #return float(self.x)
1363
+ def __lt__(self, v):
1364
+ if self.u != 0:
1365
+ raise TypeError('an ummy with non-zero uncertainty cannot be ordered')
1366
+ return self.x < v
1367
+
1368
+ def __le__(self, v):
1369
+ if self.u != 0:
1370
+ raise TypeError('an ummy with non-zero uncertainty cannot be ordered')
1371
+ return self.x <= v
1222
1372
 
1223
- #def __int__(self):
1224
- #return int(self.x)
1373
+ def __gt__(self, v):
1374
+ if self.u != 0:
1375
+ raise TypeError('an ummy with non-zero uncertainty cannot be ordered')
1376
+ return self.x > v
1225
1377
 
1226
- #def __complex__(self):
1227
- #return complex(self.x)
1378
+ def __ge__(self, v):
1379
+ if self.u != 0:
1380
+ raise TypeError('an ummy with non-zero uncertainty cannot be ordered')
1381
+ return self.x >= v
1382
+
1383
+ def __float__(self):
1384
+ if self.u != 0:
1385
+ raise TypeError('an ummy with non-zero uncertainty cannot be converted to float')
1386
+ return float(self.x)
1387
+
1388
+ def __int__(self):
1389
+ if self.u != 0:
1390
+ raise TypeError('an ummy with non-zero uncertainty cannot be converted to int')
1391
+ return int(self.x)
1392
+
1393
+ def __complex__(self):
1394
+ if self.u != 0:
1395
+ raise TypeError('an ummy with non-zero uncertainty cannot be converted to complex')
1396
+ return complex(self.x)
1228
1397
 
1398
+ def __bool__(self):
1399
+ return self != 0
1400
+
1401
+ #def __trunc__(self):
1402
+ #try:
1403
+ #return self.x.__trunc__()
1404
+ #except:
1405
+ #return float(self.x).__trunc__()
1406
+
1407
+ #def __floor__(self):
1408
+ #try:
1409
+ #return self.x.__floor__()
1410
+ #except:
1411
+ #return float(self.x).__floor__()
1412
+
1413
+ #def __ceil__(self):
1414
+ #try:
1415
+ #return self.x.__ceil__()
1416
+ #except:
1417
+ #return float(self.x).__ceil__()
1418
+
1419
+ #def __round__(self,ndigits=None):
1420
+ #try:
1421
+ #ret = round(self.x,ndigits)
1422
+ #except:
1423
+ #ret = round(float(self.x),ndigits)
1424
+
1425
+ #if ndigits is None:
1426
+ #return ret
1427
+ #else:
1428
+ #return type(self)(ret)
1429
+
1229
1430
  @property
1230
1431
  def real(self):
1231
1432
  return self
@@ -1243,46 +1444,48 @@ class ummy(Dfunc,PrettyPrinter):
1243
1444
  else:
1244
1445
  return type(self)(pi)
1245
1446
 
1246
- @property
1247
- def utype(self):
1248
- """
1249
- `str` or `None`
1250
-
1251
- An arbitrary string value labeling the uncertainty type.
1252
- """
1253
- if self._ref is None or self._ref._tag is None:
1254
- return None
1255
- return self._ref._tag.name
1447
+ def __hash__(self):
1448
+ if self._ref is None:
1449
+ return hash(self.x)
1450
+ s = sign(self._u)
1451
+ return hash((tuple((k,s*v) for k,v in self._ref.items()),self.x,self.u))
1452
+
1453
+ def _ufromrefs(self,x):
1454
+ if self._ref is None:
1455
+ return dict()
1256
1456
 
1257
- @staticmethod
1258
- def _toummylist(x):
1259
- if isinstance(x,ummy):
1260
- return [x]
1261
- elif isinstance(x,GummyTag):
1262
- return x.get_values()
1263
- elif isinstance(x,str):
1264
- t = GummyTag.tags.get(x)
1265
- if t is None:
1266
- return []
1267
- x = t.get_values()
1268
- return x
1269
- elif hasattr(x,'value') and isinstance(x.value,ummy):
1270
- return [x.value]
1271
-
1272
- xl = []
1273
- for e in x:
1274
- if hasattr(e,'value') and isinstance(e.value,ummy):
1275
- xl.append(e.value)
1276
- elif isinstance(e,ummy):
1277
- xl.append(e)
1278
- elif isinstance(e,GummyTag):
1279
- xl += e.get_values()
1280
- elif isinstance(e,str):
1281
- t = GummyTag.tags.get(e)
1282
- if t is not None:
1283
- xl += t.get_values()
1284
- return xl
1457
+ if isinstance(x,ummy) or isinstance(x,str) or isinstance(x,Quantity):
1458
+ x = [x]
1285
1459
 
1460
+ x = [i.value if isinstance(i,Quantity) else i for i in x]
1461
+
1462
+ d = dict()
1463
+ for g in x:
1464
+ if isinstance(g,str):
1465
+ for k in self._ref:
1466
+ if k.utype == g:
1467
+ if k in d:
1468
+ d[k] += 1
1469
+ else:
1470
+ d[k] = 1
1471
+ elif isinstance(g,ummy):
1472
+ for k,v in g._ref.items():
1473
+ if k in d:
1474
+ d[k] += v**2
1475
+ else:
1476
+ d[k] = v**2
1477
+
1478
+ for k,v in list(d.items()):
1479
+ if k in self._ref and v > 0:
1480
+ if v > 1:
1481
+ d[k] = self._ref[k]**2
1482
+ else:
1483
+ d[k] *= self._ref[k]**2
1484
+ else:
1485
+ del d[k]
1486
+
1487
+ return d
1488
+
1286
1489
 
1287
1490
  def ufrom(self,x):
1288
1491
  """
@@ -1308,29 +1511,31 @@ class ummy(Dfunc,PrettyPrinter):
1308
1511
  >>> d.ufrom('A')
1309
1512
  0.53851648071345048
1310
1513
  """
1311
- x = ummy._toummylist(x)
1312
- x = [i for i in x if self.correlation(i) != 0]
1313
- if len(x) == 0:
1314
- return 0
1514
+
1515
+ #x = ummy._toummylist(x)
1516
+ #x = [i for i in x if self.correlation(i) != 0]
1517
+ #if len(x) == 0:
1518
+ #return 0
1315
1519
 
1316
- v = ummy.correlation_matrix(x)
1520
+ #v = ummy.correlation_matrix(x)
1317
1521
 
1318
- b = [self.correlation(z) for z in x]
1319
- s = np.linalg.lstsq(v,b,rcond=None)[0]
1320
- u = 0
1321
-
1322
- d = [i*self.u/j.u for i,j in zip(s,x)]
1323
- for i in range(len(x)):
1324
- for j in range(len(x)):
1325
- u += d[i]*d[j]*x[i].correlation(x[j])*x[i].u*x[j].u
1522
+ #b = [self.correlation(z) for z in x]
1523
+ #s = np.linalg.lstsq(v,b,rcond=None)[0]
1524
+ #u = 0
1525
+
1526
+ #d = [i*self.u/j.u for i,j in zip(s,x)]
1527
+ #for i in range(len(x)):
1528
+ #for j in range(len(x)):
1529
+ #u += d[i]*d[j]*x[i].correlation(x[j])*x[i].u*x[j].u
1326
1530
 
1327
- return u**0.5
1531
+ #return u**0.5
1532
+
1533
+ return float(self.u*np.sqrt(sum(v for v in self._ufromrefs(x).values())))
1328
1534
 
1329
1535
  def doffrom(self,x):
1330
1536
  """
1331
1537
  Gets the degrees of freedom contributed from particular ummys or
1332
- utypes if all other free variables are held fixed. Caution: any
1333
- correlations in the calculations can cause errors in dof calculations.
1538
+ utypes if all other free variables are held fixed.
1334
1539
 
1335
1540
  Parameters
1336
1541
  ----------
@@ -1351,193 +1556,43 @@ class ummy(Dfunc,PrettyPrinter):
1351
1556
  >>> d.doffrom('A')
1352
1557
  9.0932962619709627
1353
1558
  """
1354
- x = ummy._toummylist(x)
1355
- x = [i for i in x if self.correlation(i) != 0]
1356
- if len(x) == 0:
1357
- return float('inf')
1559
+ #x = ummy._toummylist(x)
1560
+ #x = [i for i in x if self.correlation(i) != 0]
1561
+ #if len(x) == 0:
1562
+ #return float('inf')
1358
1563
 
1359
- v = ummy.correlation_matrix(x)
1360
- b = [self.correlation(z) for z in x]
1361
- s = np.linalg.lstsq(v,b,rcond=None)[0]
1362
- d = [i*self.u/j.u for i,j in zip(s,x)]
1363
- usq = 0
1364
- dm = 0
1365
- for i in range(len(x)):
1366
- for j in range(len(x)):
1367
- usqi = d[i]*d[j]*x[i].correlation(x[j])*x[i].u*x[j].u
1368
- usq += usqi
1369
- dm += usqi**2/x[i].dof
1564
+ #v = ummy.correlation_matrix(x)
1565
+ #b = [self.correlation(z) for z in x]
1566
+ #s = np.linalg.lstsq(v,b,rcond=None)[0]
1567
+ #d = [i*self.u/j.u for i,j in zip(s,x)]
1568
+ #usq = 0
1569
+ #dm = 0
1570
+ #for i in range(len(x)):
1571
+ #for j in range(len(x)):
1572
+ #usqi = d[i]*d[j]*x[i].correlation(x[j])*x[i].u*x[j].u
1573
+ #usq += usqi
1574
+ #dm += usqi**2/x[i].dof
1370
1575
 
1371
- if dm == 0:
1576
+ #if dm == 0:
1577
+ # return float('inf')
1578
+ #dof = usq**2/dm
1579
+ #if dof > ummy.max_dof:
1580
+ #return float('inf')
1581
+ #if dof < 1:
1582
+ #return 1
1583
+ #return dof
1584
+
1585
+ dof = sum(v**2/k.dof for k,v in self._ufromrefs(x).items() if k.dof is not None)
1586
+ if dof > 0:
1587
+ dof = sum(v for v in self._ufromrefs(x).values())**2/dof
1588
+ else:
1372
1589
  return float('inf')
1373
- dof = usq**2/dm
1374
1590
  if dof > ummy.max_dof:
1375
1591
  return float('inf')
1376
1592
  if dof < 1:
1377
1593
  return 1
1378
- return dof
1379
-
1380
-
1381
- class _GummyRef:
1382
- # Instances of this class are hashable unlike gummys, so we can use this in
1383
- # a gummy's ._cor weak key dictionary and a GummyTag's .values weak set.
1384
-
1385
- _cortol = 1e-15 # correlations smaller than this are rounded to zero
1386
- _cortolp = 1 - 1e-15 # correlations larger than this are rounded to 1
1387
- _cortoln = -1 + 1e-15 # correlations smaller than this are rounded to -1
1388
-
1389
- def __init__(self,dof=float('inf')):
1390
- self._cor = weakref.WeakKeyDictionary()
1391
- self._tag = None
1392
- self._tag_refs = []
1393
- self._tag_deps = []
1394
- self.dof = dof
1395
-
1396
- def cor(self,ref):
1397
- if ref is self:
1398
- return 1
1399
- c = self._cor.get(ref)
1400
- if c is None:
1401
- return 0
1402
- else:
1403
- return c
1404
-
1405
- def set_cor(self,g,c):
1406
- # We allow input values slighly greater than 1 without raising an
1407
- # exception to give room for rounding errors (but values greater than
1408
- # 1 will be rounded to 1 below).
1409
-
1410
- if abs(c) > 1.01:
1411
- raise ValueError('abs(c) > 1')
1412
-
1413
- if abs(c) < _GummyRef._cortol:
1414
- if g._ref in self._cor:
1415
- del self._cor[g._ref]
1416
- del g._ref._cor[self]
1417
- return
1418
-
1419
- if c > _GummyRef._cortolp:
1420
- self.copyto(g,1)
1421
- return
1422
-
1423
- if c < _GummyRef._cortoln:
1424
- self.copyto(g,-1)
1425
- return
1426
-
1427
- # We can't let an ummy with a utype die and be collected with the garbage
1428
- # if it is correlated with a living ummy in case ufrom() or doffrom() is
1429
- # called with the utype as an argument. So we add a strong reference
1430
- # pointing to the utyped ummy to the GummyRef of all other ummys that
1431
- # are correlated with it.
1432
- if self._tag is not None:
1433
- g._ref._tag_deps.append(self.get_tag_ref())
1434
- elif len(self._tag_deps) > 0:
1435
- g._ref._tag_deps += self._tag_deps
1436
-
1437
- self._cor[g._ref] = c
1438
- g._ref._cor[self] = c
1439
-
1440
- def add_cor(self,g,c):
1441
- if c == 0:
1442
- return
1443
- v = self._cor.get(g._ref)
1444
- if v is None:
1445
- v = c
1446
- else:
1447
- v += c
1448
- self._cor[g._ref] = v
1449
- g._ref._cor[self] = v
1450
-
1451
- @staticmethod
1452
- def check_cor(r):
1453
- a = list(r._ref._cor.items())
1454
- for k,v in a:
1455
- if k is not None:
1456
- if abs(v) > 1.01:
1457
- raise ValueError('abs(correlation) > 1')
1458
- if abs(v) < _GummyRef._cortol:
1459
- del k._cor[r._ref]
1460
- del r._ref._cor[k]
1461
- return
1462
- if v > _GummyRef._cortolp:
1463
- k.copyto(r,1)
1464
- return
1465
- if v < _GummyRef._cortoln:
1466
- k.copyto(r,-1)
1467
- return
1468
-
1469
- def copyto(self,g,refs=1):
1470
- g._ref = self
1471
- g._refs = refs
1472
- if self._tag is not None:
1473
- self._tag_refs.append(weakref.ref(g))
1474
-
1475
- def combl(self,g,c1,c2,rl=None):
1476
- self.set_cor(g,c1)
1477
- if g._ref is not self:
1478
- a = list(self._cor.items())
1479
- for k,v in a:
1480
- if k is not None and k is not g._ref:
1481
- if rl is None or not any(i is k for i in rl):
1482
- k.add_cor(g,c2*v)
1483
-
1484
- def get_tag_ref(self):
1485
- try:
1486
- ret = self._tag_refs[-1]()
1487
- while ret is None:
1488
- # The GummyRef may be shared by several copys of the original
1489
- # ummy. Find one that is still alive.
1490
- self._tag_refs.pop()
1491
- ret = self._tag_refs[-1]()
1492
- return ret
1493
- except IndexError:
1494
- return None
1495
-
1496
-
1497
- class GummyTag:
1498
- # Do not create GummyTag objects directly. A GummyTag instance is created
1499
- # each time a gummy instance with a new and unique utype parameter is created.
1500
-
1501
- tags = weakref.WeakValueDictionary()
1502
-
1503
- @staticmethod
1504
- def set_tag(g,tag):
1505
- if isinstance(tag,str):
1506
- tag = tag.strip()
1507
- if tag in GummyTag.tags:
1508
- tag = GummyTag.tags[tag]
1509
- else:
1510
- tag = GummyTag(tag)
1511
-
1512
- elif not isinstance(tag,GummyTag):
1513
- raise ValueError('utype must be either None or a str')
1514
-
1515
- tag.values.add(g._ref)
1516
- g._ref._tag = tag
1517
- g._ref._tag_refs.append(weakref.ref(g))
1518
-
1519
- def __init__(self,tag_name):
1520
- GummyTag.tags[tag_name] = self
1521
-
1522
- self.values = weakref.WeakSet()
1523
-
1524
- self.name = tag_name
1525
-
1526
- def get_values(self):
1527
- vals = list(self.values)
1528
-
1529
- # Can't put this in a list comprehension since v.ref is a weakref and
1530
- # it could die in the middle of the loop.
1531
- ret = []
1532
- for v in vals:
1533
- if v is not None:
1534
- r = v.get_tag_ref()
1535
- if r is not None:
1536
- ret.append(r)
1537
-
1538
- return ret
1594
+ return float(dof)
1539
1595
 
1540
-
1541
1596
  def _der(function,*args):
1542
1597
  # computes the numerical derivative of function with respect to args.
1543
1598
  # puts zeros for any of args that are not an ummy
@@ -1576,7 +1631,7 @@ def _der(function,*args):
1576
1631
  v[i] = a
1577
1632
  d = np.zeros(n)
1578
1633
  for i,p in enumerate(args):
1579
- if isinstance(p,ummy) and p._u > 0:
1634
+ if isinstance(p,ummy) and p._u != 0:
1580
1635
  df = None
1581
1636
  s = 2*p.u
1582
1637
  x1 = np.array(v)
@@ -1607,7 +1662,7 @@ def _der(function,*args):
1607
1662
  return d
1608
1663
 
1609
1664
 
1610
- class MetaImmy(MetaPrettyPrinter):
1665
+ class MetaImmy(MetaPrettyPrinter,ABCMeta):
1611
1666
  @property
1612
1667
  def style(cls):
1613
1668
  """
@@ -1640,7 +1695,7 @@ class MetaImmy(MetaPrettyPrinter):
1640
1695
  def imag_symbol(cls,value):
1641
1696
  immy._imag_symbol = str(value)
1642
1697
 
1643
- class immy(PrettyPrinter,Dfunc,metaclass=MetaImmy):
1698
+ class immy(PrettyPrinter,Dfunc,Number,metaclass=MetaImmy):
1644
1699
 
1645
1700
  _style = 'cartesian'
1646
1701
  _imag_symbol = 'j'
@@ -1774,20 +1829,20 @@ class immy(PrettyPrinter,Dfunc,metaclass=MetaImmy):
1774
1829
  def r(self):
1775
1830
  """
1776
1831
  read-only
1777
- Returns the magnitude of the value.
1832
+ Returns the magnitude of the value (`abs(self)`).
1778
1833
  """
1779
1834
  if self._r is None:
1780
- self._r = abs(self)
1835
+ self._r = (self.real**2 + self.imag**2)**0.5
1781
1836
  return self._r
1782
1837
 
1783
1838
  @property
1784
1839
  def phi(self):
1785
1840
  """
1786
1841
  read-only
1787
- Returns the polar angle of the value.
1842
+ Returns the polar angle of the value (`self.angle()`)
1788
1843
  """
1789
1844
  if self._phi is None:
1790
- self._phi = self.angle()
1845
+ self._phi = np.arctan2(self.imag,self.real)
1791
1846
  return self._phi
1792
1847
 
1793
1848
  def conjugate(self):
@@ -1798,11 +1853,12 @@ class immy(PrettyPrinter,Dfunc,metaclass=MetaImmy):
1798
1853
 
1799
1854
  def angle(self):
1800
1855
  """
1801
- Returns the polar angle in radians.
1856
+ Returns the polar angle in radians (which is also the phi read-only
1857
+ property value).
1802
1858
  """
1803
- return np.arctan2(self.imag,self.real)
1859
+ return self.phi
1804
1860
 
1805
- def copy(self,formatting=True,tofloat=False):
1861
+ def copy(self,formatting=True,totype=None):
1806
1862
  """
1807
1863
  Returns a copy of the jummy. If the `formatting` parameter is
1808
1864
  `True` the display formatting information will be copied and if
@@ -1812,12 +1868,13 @@ class immy(PrettyPrinter,Dfunc,metaclass=MetaImmy):
1812
1868
  imaginary components will be converted to floats.
1813
1869
  """
1814
1870
  if self._ridef:
1815
- r = self.real.copy(formatting=formatting,tofloat=tofloat)
1816
- i = self.imag.copy(formatting=formatting,tofloat=tofloat)
1871
+ r = self.real.copy(formatting=formatting,totype=totype)
1872
+ i = self.imag.copy(formatting=formatting,totype=totype)
1817
1873
  return type(self)(real=r,imag=i)
1874
+
1818
1875
  else:
1819
- r = self.r.copy(formatting=formatting,tofloat=tofloat)
1820
- phi = self.phi.copy(formatting=formatting,tofloat=tofloat)
1876
+ r = self.r.copy(formatting=formatting,totype=totype)
1877
+ phi = self.phi.copy(formatting=formatting,totype=totype)
1821
1878
  return type(self)(r=r,phi=phi)
1822
1879
 
1823
1880
  def tofloat(self):
@@ -1825,7 +1882,7 @@ class immy(PrettyPrinter,Dfunc,metaclass=MetaImmy):
1825
1882
  Returns a copy of the gummy with x an u (for both the real and
1826
1883
  imaginary components) converted to floats.
1827
1884
  """
1828
- return self.copy(formatting=False,tofloat=True)
1885
+ return self.copy(formatting=False,totype=float)
1829
1886
 
1830
1887
  def splonk(self):
1831
1888
  if self.real.u == 0 and self.imag.u == 0:
@@ -2135,7 +2192,7 @@ class immy(PrettyPrinter,Dfunc,metaclass=MetaImmy):
2135
2192
  raise TypeError("can't mod immy")
2136
2193
 
2137
2194
  def __abs__(self):
2138
- return (self.real**2 + self.imag**2)**0.5
2195
+ return self.r
2139
2196
 
2140
2197
  def __neg__(self):
2141
2198
  return type(self)(real=-self.real.copy(formatting=False),
@@ -2145,85 +2202,20 @@ class immy(PrettyPrinter,Dfunc,metaclass=MetaImmy):
2145
2202
  return type(self)(real=self.real.copy(formatting=False),
2146
2203
  imag=self.imag.copy(formatting=False))
2147
2204
 
2148
- #def __complex__(self):
2149
- #return complex(self.real.x,self.imag.x)
2150
-
2151
- #def __float__(self):
2152
- #raise TypeError("can't convert immy to float")
2153
-
2154
- #def __int__(self):
2155
- #raise TypeError("can't convert immy to int")
2156
-
2157
2205
  def __eq__(self,v):
2158
2206
  return self.real == v.real and self.imag == v.imag
2159
-
2160
-
2161
- class MFraction(Fraction):
2162
- """
2163
- A fraction.Fraction sub-class that works with mpmath.mpf objects
2164
- """
2165
-
2166
- def __new__(cls, *args, **kwargs):
2167
- if len(args) == 1 and not (isinstance(args[0],str)
2168
- or isinstance(args[0],Fraction)):
2169
- return args[0]
2170
- ret = super(MFraction, cls).__new__(cls, *args, **kwargs)
2171
- if ret.denominator == 1:
2172
- return ret.numerator
2173
- return ret
2174
2207
 
2175
- def _mpmath_(self,p,r):
2176
- return rational.mpq(self.numerator,self.denominator)
2208
+ def __hash__(self):
2209
+ return hash((self.real,self.imag))
2177
2210
 
2178
- def __add__(self,v):
2179
- return MFraction(super().__add__(v))
2211
+ def __complex__(self):
2212
+ return complex(float(self.real),float(self.imag))
2180
2213
 
2181
- def __radd__(self,v):
2182
- return MFraction(super().__radd__(v))
2183
-
2184
- def __sub__(self,v):
2185
- return MFraction(super().__sub__(v))
2186
-
2187
- def __rsub__(self,v):
2188
- return MFraction(super().__rsub__(v))
2189
-
2190
- def __mul__(self,v):
2191
- return MFraction(super().__mul__(v))
2214
+ def __bool__(self):
2215
+ return self != 0
2192
2216
 
2193
- def __rmul__(self,v):
2194
- return MFraction(super().__rmul__(v))
2195
-
2196
- def __truediv__(self,v):
2197
- return MFraction(super().__truediv__(v))
2198
-
2199
- def __rtruediv__(self,v):
2200
- return MFraction(super().__rtruediv__(v))
2201
-
2202
- def __pow__(self,v):
2203
- return MFraction(super().__pow__(v))
2204
-
2205
- def __rpow__(self,v):
2206
- if isinstance(v,Fraction):
2207
- return MFraction(v).__pow__(self)
2208
- return MFraction(super().__rpow__(v))
2209
-
2210
- def __floordiv__(self,v):
2211
- return MFraction(super().__floordiv__(v))
2212
-
2213
- def __rfloordiv__(self,v):
2214
- return MFraction(super().__rfloordiv__(v))
2217
+ #def __float__(self):
2218
+ #raise TypeError("can't convert immy to float")
2215
2219
 
2216
- def __mod__(self,v):
2217
- return MFraction(super().__mod__(v))
2218
-
2219
- def __rmod__(self,v):
2220
- return MFraction(super().__rmod__(v))
2221
-
2222
- def __abs__(self):
2223
- return MFraction(super().__abs__())
2224
-
2225
- def __neg__(self):
2226
- return MFraction(super().__neg__())
2227
-
2228
- def __pos__(self):
2229
- return MFraction(super().__pos__())
2220
+ #def __int__(self):
2221
+ #raise TypeError("can't convert immy to int")