1 """
2 Core functions used by unumpy and some of its submodules.
3
4 (c) 2010-2013 by Eric O. LEBIGOT (EOL).
5 """
6
7
8
9
10
11
12 from __future__ import division
13
14
15 import sys
16
17
18 import numpy
19 from numpy.core import numeric
20
21
22 from cc.ivs.sigproc.lmfit import uncertainties
23 from cc.ivs.sigproc.lmfit.uncertainties import umath
24
25 from cc.ivs.sigproc.lmfit.uncertainties import __author__
26
27 __all__ = [
28
29 'uarray', 'umatrix',
30
31
32 'nominal_values', 'std_devs',
33
34
35 'matrix'
36 ]
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56 to_nominal_values = numpy.vectorize(
57 uncertainties.nominal_value,
58 otypes=[float],
59 doc=("Applies uncertainties.nominal_value to the elements of"
60 " a NumPy (or unumpy) array (this includes matrices)."))
61
62 to_std_devs = numpy.vectorize(
63 uncertainties.std_dev,
64 otypes=[float],
65 doc=("Returns the standard deviation of the numbers with uncertainties"
66 " contained in a NumPy array, or zero for other objects."))
69 """
70 If arr in a unumpy.matrix, it is converted to a numpy.matrix.
71 Otherwise, it is returned unchanged.
72 """
73
74 return arr.view(numpy.matrix) if isinstance(arr, matrix) else arr
75
77 """
78 Returns the nominal values of the numbers in NumPy array arr.
79
80 Elements that are not uncertainties.AffineScalarFunc are passed
81 through untouched (because a numpy.array can contain numbers with
82 uncertainties and pure floats simultaneously).
83
84 If arr is of type unumpy.matrix, the returned array is a
85 numpy.matrix, because the resulting matrix does not contain
86 numbers with uncertainties.
87 """
88
89 return unumpy_to_numpy_matrix(to_nominal_values(arr))
90
92 """
93 Returns the standard deviations of the numbers in NumPy array arr.
94
95 Elements that are not uncertainties.AffineScalarFunc are given a
96 zero uncertainty ((because a numpy.array can contain numbers with
97 uncertainties and pure floats simultaneously)..
98
99 If arr is of type unumpy.matrix, the returned array is a
100 numpy.matrix, because the resulting matrix does not contain
101 numbers with uncertainties.
102 """
103
104 return unumpy_to_numpy_matrix(to_std_devs(arr))
105
109 """
110 Returns the derivative of u along var, if u is an
111 uncertainties.AffineScalarFunc instance, and if var is one of the
112 variables on which it depends. Otherwise, return 0.
113 """
114 if isinstance(u, uncertainties.AffineScalarFunc):
115 try:
116 return u.derivatives[var]
117 except KeyError:
118 return 0.
119 else:
120 return 0.
121
123 """
124 Returns a version of the function func() that works even when
125 func() is given a NumPy array that contains numbers with
126 uncertainties.
127
128 func() is supposed to return a NumPy array.
129
130 This wrapper is similar to uncertainties.wrap(), except that it
131 handles an array argument instead of float arguments.
132
133 func -- version that takes and returns a single NumPy array.
134 """
135
136 @uncertainties.set_doc("""\
137 Version of %s(...) that works even when its first argument is a NumPy
138 array that contains numbers with uncertainties.
139
140 Warning: elements of the first argument array that are not
141 AffineScalarFunc objects must not depend on uncertainties.Variable
142 objects in any way. Otherwise, the dependence of the result in
143 uncertainties.Variable objects will be incorrect.
144
145 Original documentation:
146 %s""" % (func.__name__, func.__doc__))
147 def wrapped_func(arr, *args):
148
149 arr_nominal_value = nominal_values(arr)
150 func_nominal_value = func(arr_nominal_value, *args)
151
152
153
154
155
156 variables = set()
157 for element in arr.flat:
158
159 if isinstance(element, uncertainties.AffineScalarFunc):
160 variables |= set(element.derivatives.iterkeys())
161
162
163
164 if not variables:
165 return func_nominal_value
166
167
168
169
170
171
172 derivatives = numpy.vectorize(lambda _: {})(func_nominal_value)
173 for var in variables:
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197 shift_var = max(var._std_dev/1e5, 1e-8*abs(var._nominal_value))
198
199
200
201
202
203
204 if not shift_var:
205 shift_var = 1e-8
206
207
208 shift_arr = array_derivative(arr, var)*shift_var
209
210
211 shifted_arr_values = arr_nominal_value + shift_arr
212 func_shifted = func(shifted_arr_values, *args)
213 numerical_deriv = (func_shifted-func_nominal_value)/shift_var
214
215
216
217 for (derivative_dict, derivative_value) in (
218 zip(derivatives.flat, numerical_deriv.flat)):
219
220 if derivative_value:
221 derivative_dict[var] = derivative_value
222
223
224 return numpy.vectorize(uncertainties.AffineScalarFunc)(
225 func_nominal_value, derivatives)
226
227
228
229
230 wrapped_func.__name__ = func.__name__
231
232 return wrapped_func
233
234
235
236
237
238
239
240
241
242 _uarray = numpy.vectorize(lambda v, s: uncertainties.Variable(v, s),
243 otypes=[object])
244
245 -def uarray((values, std_devs)):
246 """
247 Returns a NumPy array of numbers with uncertainties
248 initialized with the given nominal values and standard
249 deviations.
250
251 values, std_devs -- valid arguments for numpy.array, with
252 identical shapes (list of numbers, list of lists, numpy.ndarray,
253 etc.).
254 """
255
256 return _uarray(values, std_devs)
257
261 """
262 Returns the derivative of the given array with respect to the
263 given variable.
264
265 The returned derivative is a Numpy ndarray of the same shape as
266 array_like, that contains floats.
267
268 array_like -- array-like object (list, etc.) that contains
269 scalars or numbers with uncertainties.
270
271 var -- Variable object.
272 """
273 return numpy.vectorize(lambda u: derivative(u, var),
274
275
276
277
278 otypes=[float])(array_like)
279
281 """
282 Returns a function that can be applied to array-like objects that
283 contain numbers with uncertainties (lists, lists of lists, Numpy
284 arrays, etc.).
285
286 func_with_derivatives -- defines a function that takes array-like
287 objects containing scalars and returns an array. Both the value
288 and the derivatives of this function with respect to multiple
289 scalar parameters are calculated by func_with_derivatives().
290
291 func_with_derivatives(arr, input_type, derivatives, *args) returns
292 an iterator. The first element is the value of the function at
293 point 'arr' (with the correct type). The following elements are
294 arrays that represent the derivative of the function for each
295 derivative array from the iterator 'derivatives'.
296
297 func_with_derivatives takes the following arguments:
298
299 arr -- Numpy ndarray of scalars where the function must be
300 evaluated.
301
302 input_type -- type of the input array-like object. This type is
303 used for determining the type that the function should return.
304
305 derivatives -- iterator that returns the derivatives of the
306 argument of the function with respect to multiple scalar
307 variables. func_with_derivatives() returns the derivatives of
308 the defined function with respect to these variables.
309
310 args -- additional arguments that define the result (example:
311 for the pseudo-inverse numpy.linalg.pinv: numerical cutoff).
312
313 Examples of func_with_derivatives: inv_with_derivatives().
314 """
315
316 def wrapped_func(array_like, *args):
317 """
318 array_like -- array-like object that contains numbers with
319 uncertainties (list, Numpy ndarray or matrix, etc.).
320
321 args -- additional arguments that are passed directly to
322 func_with_derivatives.
323 """
324
325
326
327 array_version = numpy.asarray(array_like)
328
329
330 variables = set()
331 for element in array_version.flat:
332
333 if isinstance(element, uncertainties.AffineScalarFunc):
334 variables |= set(element.derivatives.iterkeys())
335
336 array_nominal = nominal_values(array_version)
337
338
339
340 func_and_derivs = func_with_derivatives(
341 array_nominal,
342 type(array_like),
343 (array_derivative(array_version, var) for var in variables),
344 *args)
345
346 func_nominal_value = func_and_derivs.next()
347
348 if not variables:
349 return func_nominal_value
350
351
352
353
354
355
356 derivatives = numpy.array(
357 [{} for _ in xrange(func_nominal_value.size)], dtype=object)
358 derivatives.resize(func_nominal_value.shape)
359
360
361
362
363
364
365
366
367 for (var, deriv_wrt_var) in zip(variables, func_and_derivs):
368
369
370
371 for (derivative_dict, derivative_value) in zip(
372 derivatives.flat, deriv_wrt_var.flat):
373 if derivative_value:
374 derivative_dict[var] = derivative_value
375
376
377
378 result = numpy.vectorize(uncertainties.AffineScalarFunc)(
379 func_nominal_value, derivatives)
380
381
382
383 if isinstance(result, numpy.matrix):
384 result = result.view(matrix)
385
386 return result
387
388 return wrapped_func
389
393 """
394 Defines the matrix inverse and its derivatives.
395
396 See the definition of func_with_deriv_to_uncert_func() for its
397 detailed semantics.
398 """
399
400 inverse = numpy.linalg.inv(arr)
401
402
403
404 if issubclass(input_type, numpy.matrix):
405 inverse = inverse.view(numpy.matrix)
406 yield inverse
407
408
409 inverse_mat = numpy.asmatrix(inverse)
410
411
412 for derivative in derivatives:
413 derivative_mat = numpy.asmatrix(derivative)
414 yield -inverse_mat * derivative_mat * inverse_mat
415
416 _inv = func_with_deriv_to_uncert_func(inv_with_derivatives)
417 _inv.__doc__ = """\
418 Version of numpy.linalg.inv that works with array-like objects
419 that contain numbers with uncertainties.
420
421 The result is a unumpy.matrix if numpy.linalg.pinv would return a
422 matrix for the array of nominal values.
423
424 Analytical formulas are used.
425
426 Original documentation:
427 %s
428 """ % numpy.linalg.inv.__doc__
433 """
434 Defines the matrix pseudo-inverse and its derivatives.
435
436 Works with real or complex matrices.
437
438 See the definition of func_with_deriv_to_uncert_func() for its
439 detailed semantics.
440 """
441
442 inverse = numpy.linalg.pinv(arr, rcond)
443
444
445
446 if issubclass(input_type, numpy.matrix):
447 inverse = inverse.view(numpy.matrix)
448 yield inverse
449
450
451 inverse_mat = numpy.asmatrix(inverse)
452
453
454
455
456
457
458
459
460
461
462
463 PA = arr*inverse_mat
464 AP = inverse_mat*arr
465 factor21 = inverse_mat*inverse_mat.H
466 factor22 = numpy.eye(arr.shape[0])-PA
467 factor31 = numpy.eye(arr.shape[1])-AP
468 factor32 = inverse_mat.H*inverse_mat
469
470
471 for derivative in derivatives:
472 derivative_mat = numpy.asmatrix(derivative)
473 term1 = -inverse_mat*derivative_mat*inverse_mat
474 derivative_mat_H = derivative_mat.H
475 term2 = factor21*derivative_mat_H*factor22
476 term3 = factor31*derivative_mat_H*factor32
477 yield term1+term2+term3
478
479
480 try:
481
482 _pinv_default = numpy.linalg.pinv.__defaults__[0]
483 except AttributeError:
484 _pinv_default = 1e-15
485
486 _pinv_with_uncert = func_with_deriv_to_uncert_func(pinv_with_derivatives)
487
488 @uncertainties.set_doc("""
489 Version of numpy.linalg.pinv that works with array-like objects
490 that contain numbers with uncertainties.
491
492 The result is a unumpy.matrix if numpy.linalg.pinv would return a
493 matrix for the array of nominal values.
494
495 Analytical formulas are used.
496
497 Original documentation:
498 %s
499 """ % numpy.linalg.pinv.__doc__)
502
503
504
505 -class matrix(numpy.matrix):
506
507
508 """
509 Class equivalent to numpy.matrix, but that behaves better when the
510 matrix contains numbers with uncertainties.
511 """
512
514
515
516
517
518
519 if numeric.isscalar(other):
520 return numeric.dot(self, other)
521 else:
522 return numeric.dot(other, self)
523
524
525
527 "Matrix inverse of pseudo-inverse"
528
529
530
531
532 M, N = self.shape
533 if M == N:
534 func = _inv
535 else:
536 func = _pinv
537 return func(self)
538
539
540
541
542 I = property(getI, numpy.matrix.I.fset, numpy.matrix.I.fdel,
543 numpy.matrix.I.__doc__)
544
545 @property
547 """
548 Nominal value of all the elements of the matrix.
549 """
550 return nominal_values(self)
551
552 std_devs = std_devs
553
555 """
556 Constructs a matrix that contains numbers with uncertainties.
557
558 The input data is the same as for uarray(...): a tuple with the
559 nominal values, and the standard deviations.
560
561 The returned matrix can be inverted, thanks to the fact that it is
562 a unumpy.matrix object instead of a numpy.matrix one.
563 """
564
565 return uarray(*args).view(matrix)
566
570 """
571 Defines vectorized versions of functions from uncertainties.umath.
572
573 Some functions have their name translated, so as to follow NumPy's
574 convention (example: math.acos -> numpy.arccos).
575 """
576
577 this_module = sys.modules[__name__]
578
579
580 func_name_translations = dict(
581 (f_name, 'arc'+f_name[1:])
582 for f_name in ['acos', 'acosh', 'asin', 'atan', 'atan2', 'atanh'])
583
584 new_func_names = [func_name_translations.get(function_name, function_name)
585 for function_name in umath.many_scalars_to_scalar_funcs]
586
587 for (function_name, unumpy_name) in zip(
588 umath.many_scalars_to_scalar_funcs, new_func_names):
589
590
591
592
593
594 func = getattr(umath, function_name)
595 setattr(
596 this_module, unumpy_name,
597 numpy.vectorize(func,
598
599
600
601
602
603 otypes=[object],
604 doc="""\
605 Vectorized version of umath.%s.
606
607 Original documentation:
608 %s""" % (function_name, func.__doc__)))
609
610 __all__.append(unumpy_name)
611
612 define_vectorized_funcs()
613