1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
|
try:
import numpy
except ImportError:
import sys
sys.exit() # There is no reason to test the interface to NumPy
import uncertainties
import uncertainties.core as uncert_core
from uncertainties import ufloat, unumpy
from uncertainties.unumpy import core
from helpers import numbers_close, uarrays_close
def test_numpy():
"""
Interaction with NumPy, including matrix inversion,
correlated_values, and calculation of the mean.
"""
arr = numpy.arange(3)
num = ufloat(3.14, 0.01)
# NumPy arrays can be multiplied by Variable objects,
# whatever the order of the operands:
prod1 = arr * num
prod2 = num * arr
# Additional check:
assert (prod1 == prod2).all()
# Operations with arrays work (they are first handled by NumPy,
# then by this module):
prod1 * prod2 # This should be calculable
assert not (prod1 - prod2).any() # All elements must be 0
# Comparisons work too:
# Usual behavior:
assert len(arr[arr > 1.5]) == 1
# Comparisons with Variable objects:
assert len(arr[arr > ufloat(1.5, 0.1)]) == 1
assert len(prod1[prod1 < prod1 * prod2]) == 2
# The following can be calculated (special NumPy abs() function):
numpy.abs(arr + ufloat(-1, 0.1))
# The following does not completely work, because NumPy does not
# implement numpy.exp on an array of general objects, apparently:
assert numpy.exp(arr).all() # All elements > 0
# Equivalent with an array of AffineScalarFunc objects:
try:
numpy.exp(arr + ufloat(0, 0))
except (AttributeError, TypeError):
# In numpy<1.17, an AttributeError is raised in this situation. This was
# considered a bug however, and in numpy 1.17 it was changed to a
# TypeError (see PR #12700 in numpy repository)
pass
else:
raise Exception("numpy.exp unexpectedly worked")
# Calculation of the mean, global and with a specific axis:
arr_floats = numpy.random.random((10, 3, 5))
arr = unumpy.uarray(arr_floats, arr_floats / 100)
assert arr.mean(axis=0).shape == (3, 5)
assert arr.mean(axis=1).shape == (10, 5)
arr.mean() # Global mean
def test_matrix():
"Matrices of numbers with uncertainties"
# Matrix inversion:
# Matrix with a mix of Variable objects and regular
# Python numbers:
m = unumpy.matrix([[ufloat(10, 1), -3.1], [0, ufloat(3, 0)]])
m_nominal_values = unumpy.nominal_values(m)
# Test of the nominal_value attribute:
assert numpy.all(m_nominal_values == m.nominal_values)
assert type(m[0, 0]) == uncert_core.Variable
# Test of scalar multiplication, both sides:
3 * m
m * 3
def derivatives_close(x, y):
"""
Returns True iff the AffineScalarFunc objects x and y have
derivatives that are close to each other (they must depend
on the same variables).
"""
# x and y must depend on the same variables:
if set(x.derivatives) != set(y.derivatives):
return False # Not the same variables
return all(
numbers_close(x.derivatives[var], y.derivatives[var]) for var in x.derivatives
)
def test_inverse():
"Tests of the matrix inverse"
m = unumpy.matrix([[ufloat(10, 1), -3.1], [0, ufloat(3, 0)]])
m_nominal_values = unumpy.nominal_values(m)
# "Regular" inverse matrix, when uncertainties are not taken
# into account:
m_no_uncert_inv = m_nominal_values.I
# The matrix inversion should not yield numbers with uncertainties:
assert m_no_uncert_inv.dtype == numpy.dtype(float)
# Inverse with uncertainties:
m_inv_uncert = m.I # AffineScalarFunc elements
# The inverse contains uncertainties: it must support custom
# operations on matrices with uncertainties:
assert isinstance(m_inv_uncert, unumpy.matrix)
assert type(m_inv_uncert[0, 0]) == uncert_core.AffineScalarFunc
# Checks of the numerical values: the diagonal elements of the
# inverse should be the inverses of the diagonal elements of
# m (because we started with a triangular matrix):
assert numbers_close(
1 / m_nominal_values[0, 0], m_inv_uncert[0, 0].nominal_value
), "Wrong value"
assert numbers_close(
1 / m_nominal_values[1, 1], m_inv_uncert[1, 1].nominal_value
), "Wrong value"
####################
# Checks of the covariances between elements:
x = ufloat(10, 1)
m = unumpy.matrix([[x, x], [0, 3 + 2 * x]])
m_inverse = m.I
# Check of the properties of the inverse:
m_double_inverse = m_inverse.I
# The initial matrix should be recovered, including its
# derivatives, which define covariances:
assert numbers_close(m_double_inverse[0, 0].nominal_value, m[0, 0].nominal_value)
assert numbers_close(m_double_inverse[0, 0].std_dev, m[0, 0].std_dev)
assert uarrays_close(m_double_inverse, m)
# Partial test:
assert derivatives_close(m_double_inverse[0, 0], m[0, 0])
assert derivatives_close(m_double_inverse[1, 1], m[1, 1])
####################
# Tests of covariances during the inversion:
# There are correlations if both the next two derivatives are
# not zero:
assert m_inverse[0, 0].derivatives[x]
assert m_inverse[0, 1].derivatives[x]
# Correlations between m and m_inverse should create a perfect
# inversion:
assert uarrays_close(m * m_inverse, numpy.eye(m.shape[0]))
def test_wrap_array_func():
"""
Test of numpy.wrap_array_func(), with optional arguments and
keyword arguments.
"""
# Function that works with numbers with uncertainties in mat (if
# mat is an uncertainties.unumpy.matrix):
def f_unc(mat, *args, **kwargs):
return mat.I + args[0] * kwargs["factor"]
# Test with optional arguments and keyword arguments:
def f(mat, *args, **kwargs):
# This function is wrapped: it should only be called with pure
# numbers:
assert not any(isinstance(v, uncert_core.UFloat) for v in mat.flat)
return f_unc(mat, *args, **kwargs)
# Wrapped function:
f_wrapped = core.wrap_array_func(f)
##########
# Full rank rectangular matrix:
m = unumpy.matrix([[ufloat(10, 1), -3.1], [0, ufloat(3, 0)], [1, -3.1]])
# Numerical and package (analytical) pseudo-inverses: they must be
# the same:
m_f_wrapped = f_wrapped(m, 2, factor=10)
m_f_unc = f_unc(m, 2, factor=10)
assert uarrays_close(m_f_wrapped, m_f_unc)
def test_pseudo_inverse():
"Tests of the pseudo-inverse"
# Numerical version of the pseudo-inverse:
pinv_num = core.wrap_array_func(numpy.linalg.pinv)
##########
# Full rank rectangular matrix:
m = unumpy.matrix([[ufloat(10, 1), -3.1], [0, ufloat(3, 0)], [1, -3.1]])
# Numerical and package (analytical) pseudo-inverses: they must be
# the same:
rcond = 1e-8 # Test of the second argument to pinv()
m_pinv_num = pinv_num(m, rcond)
m_pinv_package = core.pinv(m, rcond)
assert uarrays_close(m_pinv_num, m_pinv_package)
##########
# Example with a non-full rank rectangular matrix:
vector = [ufloat(10, 1), -3.1, 11]
m = unumpy.matrix([vector, vector])
m_pinv_num = pinv_num(m, rcond)
m_pinv_package = core.pinv(m, rcond)
assert uarrays_close(m_pinv_num, m_pinv_package)
##########
# Example with a non-full-rank square matrix:
m = unumpy.matrix([[ufloat(10, 1), 0], [3, 0]])
m_pinv_num = pinv_num(m, rcond)
m_pinv_package = core.pinv(m, rcond)
assert uarrays_close(m_pinv_num, m_pinv_package)
def test_broadcast_funcs():
"""
Test of mathematical functions that work with NumPy arrays of
numbers with uncertainties.
"""
x = ufloat(0.2, 0.1)
arr = numpy.array([x, 2 * x])
assert unumpy.cos(arr)[1] == uncertainties.umath.cos(arr[1])
# Some functions do not bear the same name in the math module and
# in NumPy (acos instead of arccos, etc.):
assert unumpy.arccos(arr)[1] == uncertainties.umath.acos(arr[1])
# The acos() function should not exist in unumpy because the function
# should have been renamed to arccos(). Starting with numpy 2 numpy.acos()
# is an alias to numpy.arccos(). If similar aliases are added to unumpy,
# the following tests can be removed.
assert not hasattr(unumpy, "acos")
# Test of the __all__ variable:
assert "acos" not in unumpy.__all__
def test_array_and_matrix_creation():
"Test of custom array creation"
arr = unumpy.uarray([1, 2], [0.1, 0.2])
assert arr[1].nominal_value == 2
assert arr[1].std_dev == 0.2
# Same thing for matrices:
mat = unumpy.umatrix([1, 2], [0.1, 0.2])
assert mat[0, 1].nominal_value == 2
assert mat[0, 1].std_dev == 0.2
def test_component_extraction():
"Extracting the nominal values and standard deviations from an array"
arr = unumpy.uarray([1, 2], [0.1, 0.2])
assert numpy.all(unumpy.nominal_values(arr) == [1, 2])
assert numpy.all(unumpy.std_devs(arr) == [0.1, 0.2])
# unumpy matrices, in addition, should have nominal_values that
# are simply numpy matrices (not unumpy ones, because they have no
# uncertainties):
mat = unumpy.matrix(arr)
assert numpy.all(unumpy.nominal_values(mat) == [1, 2])
assert numpy.all(unumpy.std_devs(mat) == [0.1, 0.2])
assert type(unumpy.nominal_values(mat)) == numpy.matrix
def test_array_comparisons():
"Test of array and matrix comparisons"
arr = unumpy.uarray([1, 2], [1, 4])
assert numpy.all((arr == [arr[0], 4]) == [True, False])
# For matrices, 1D arrays are converted to 2D arrays:
mat = unumpy.umatrix([1, 2], [1, 4])
assert numpy.all((mat == [mat[0, 0], 4]) == [True, False])
|