/usr/share/pyshared/uncertainties/test_umath.py is in python-uncertainties 2.4.4-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
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 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 | """
Tests of the code in uncertainties.umath.
These tests can be run through the Nose testing framework.
(c) 2010-2013 by Eric O. LEBIGOT (EOL).
"""
from __future__ import division
# Standard modules
import sys
import math
# Local modules:
import uncertainties
import uncertainties.umath as umath
from uncertainties import ufloat
from uncertainties import __author__
import test_uncertainties
###############################################################################
# Unit tests
def test_fixed_derivatives_math_funcs():
"""
Comparison between function derivatives and numerical derivatives.
This comparison is useful for derivatives that are analytical.
"""
for name in umath.many_scalars_to_scalar_funcs:
# print "Checking %s..." % name
func = getattr(umath, name)
# Numerical derivatives of func: the nominal value of func() results
# is used as the underlying function:
numerical_derivatives = uncertainties.NumericalDerivatives(
lambda *args: func(*args))
test_uncertainties.compare_derivatives(func, numerical_derivatives)
# Functions that are not in umath.many_scalars_to_scalar_funcs:
##
# modf(): returns a tuple:
def frac_part_modf(x):
return umath.modf(x)[0]
def int_part_modf(x):
return umath.modf(x)[1]
test_uncertainties.compare_derivatives(
frac_part_modf,
uncertainties.NumericalDerivatives(
lambda x: frac_part_modf(x)))
test_uncertainties.compare_derivatives(
int_part_modf,
uncertainties.NumericalDerivatives(
lambda x: int_part_modf(x)))
##
# frexp(): returns a tuple:
def mantissa_frexp(x):
return umath.frexp(x)[0]
def exponent_frexp(x):
return umath.frexp(x)[1]
test_uncertainties.compare_derivatives(
mantissa_frexp,
uncertainties.NumericalDerivatives(
lambda x: mantissa_frexp(x)))
test_uncertainties.compare_derivatives(
exponent_frexp,
uncertainties.NumericalDerivatives(
lambda x: exponent_frexp(x)))
def test_compound_expression():
"""
Test equality between different formulas.
"""
x = ufloat(3, 0.1)
# Prone to numerical errors (but not much more than floats):
assert umath.tan(x) == umath.sin(x)/umath.cos(x)
def test_numerical_example():
"Test specific numerical examples"
x = ufloat(3.14, 0.01)
result = umath.sin(x)
# In order to prevent big errors such as a wrong, constant value
# for all analytical and numerical derivatives, which would make
# test_fixed_derivatives_math_funcs() succeed despite incorrect
# calculations:
assert ("%.6f +/- %.6f" % (result.nominal_value, result.std_dev)
== "0.001593 +/- 0.010000")
# Regular calculations should still work:
assert("%.11f" % umath.sin(3) == "0.14112000806")
def test_monte_carlo_comparison():
"""
Full comparison to a Monte-Carlo calculation.
Both the nominal values and the covariances are compared between
the direct calculation performed in this module and a Monte-Carlo
simulation.
"""
try:
import numpy
import numpy.random
except ImportError:
import warnings
warnings.warn("Test not performed because NumPy is not available")
return
# Works on numpy.arrays of Variable objects (whereas umath.sin()
# does not):
sin_uarray_uncert = numpy.vectorize(umath.sin, otypes=[object])
# Example expression (with correlations, and multiple variables combined
# in a non-linear way):
def function(x, y):
"""
Function that takes two NumPy arrays of the same size.
"""
# The uncertainty due to x is about equal to the uncertainty
# due to y:
return 10 * x**2 - x * sin_uarray_uncert(y**3)
x = ufloat(0.2, 0.01)
y = ufloat(10, 0.001)
function_result_this_module = function(x, y)
nominal_value_this_module = function_result_this_module.nominal_value
# Covariances "f*f", "f*x", "f*y":
covariances_this_module = numpy.array(uncertainties.covariance_matrix(
(x, y, function_result_this_module)))
def monte_carlo_calc(n_samples):
"""
Calculate function(x, y) on n_samples samples and returns the
median, and the covariances between (x, y, function(x, y)).
"""
# Result of a Monte-Carlo simulation:
x_samples = numpy.random.normal(x.nominal_value, x.std_dev,
n_samples)
y_samples = numpy.random.normal(y.nominal_value, y.std_dev,
n_samples)
# !! astype() is a fix for median() in NumPy 1.8.0:
function_samples = function(x_samples, y_samples).astype(float)
cov_mat = numpy.cov([x_samples, y_samples], function_samples)
return (numpy.median(function_samples), cov_mat)
(nominal_value_samples, covariances_samples) = monte_carlo_calc(1000000)
## Comparison between both results:
# The covariance matrices must be close:
# We rely on the fact that covariances_samples very rarely has
# null elements:
assert numpy.vectorize(test_uncertainties.numbers_close)(
covariances_this_module,
covariances_samples,
0.05).all(), (
"The covariance matrices do not coincide between"
" the Monte-Carlo simulation and the direct calculation:\n"
"* Monte-Carlo:\n%s\n* Direct calculation:\n%s"
% (covariances_samples, covariances_this_module)
)
# The nominal values must be close:
assert test_uncertainties.numbers_close(
nominal_value_this_module,
nominal_value_samples,
# The scale of the comparison depends on the standard
# deviation: the nominal values can differ by a fraction of
# the standard deviation:
math.sqrt(covariances_samples[2, 2])
/ abs(nominal_value_samples) * 0.5), (
"The nominal value (%f) does not coincide with that of"
" the Monte-Carlo simulation (%f), for a standard deviation of %f."
% (nominal_value_this_module,
nominal_value_samples,
math.sqrt(covariances_samples[2, 2]))
)
def test_math_module():
"Operations with the math module"
x = ufloat(-1.5, 0.1)
# The exponent must not be differentiated, when calculating the
# following (the partial derivative with respect to the exponent
# is not defined):
assert (x**2).nominal_value == 2.25
# Regular operations are chosen to be unchanged:
assert isinstance(umath.sin(3), float)
# Python >=2.6 functions:
if sys.version_info >= (2, 6):
# factorial() must not be "damaged" by the umath module, so as
# to help make it a drop-in replacement for math (even though
# factorial() does not work on numbers with uncertainties
# because it is restricted to integers, as for
# math.factorial()):
assert umath.factorial(4) == 24
# fsum is special because it does not take a fixed number of
# variables:
assert umath.fsum([x, x]).nominal_value == -3
# Functions that give locally constant results are tested: they
# should give the same result as their float equivalent:
for name in umath.locally_cst_funcs:
try:
func = getattr(umath, name)
except AttributeError:
continue # Not in the math module, so not in umath either
assert func(x) == func(x.nominal_value)
# The type should be left untouched. For example, isnan()
# should always give a boolean:
assert type(func(x)) == type(func(x.nominal_value))
# The same exceptions should be generated when numbers with uncertainties
# are used:
## !! The Nose testing framework seems to catch an exception when
## it is aliased: "exc = OverflowError; ... except exc:..."
## surprisingly catches OverflowError. So, tests are written in a
## version-specific manner (until the Nose issue is resolved).
if sys.version_info < (2, 6):
try:
math.log(0)
except OverflowError, err_math: # "as", for Python 2.6+
pass
else:
raise Exception('OverflowError exception expected')
try:
umath.log(0)
except OverflowError, err_ufloat: # "as", for Python 2.6+
assert err_math.args == err_ufloat.args
else:
raise Exception('OverflowError exception expected')
try:
umath.log(ufloat(0, 0))
except OverflowError, err_ufloat: # "as", for Python 2.6+
assert err_math.args == err_ufloat.args
else:
raise Exception('OverflowError exception expected')
try:
umath.log(ufloat(0, 1))
except OverflowError, err_ufloat: # "as", for Python 2.6+
assert err_math.args == err_ufloat.args
else:
raise Exception('OverflowError exception expected')
else:
try:
math.log(0)
except ValueError, err_math: # Python 2.6+: as err_math
# Python 3 does not make exceptions local variables: they are
# restricted to their except block:
err_math_args = err_math.args
else:
raise Exception('ValueError exception expected')
try:
umath.log(0)
except ValueError, err_ufloat: # Python 2.6+: as err_math
assert err_math_args == err_ufloat.args
else:
raise Exception('ValueError exception expected')
try:
umath.log(ufloat(0, 0))
except ValueError, err_ufloat: # Python 2.6+: as err_math
assert err_math_args == err_ufloat.args
else:
raise Exception('ValueError exception expected')
try:
umath.log(ufloat(0, 1))
except ValueError, err_ufloat: # Python 2.6+: as err_math
assert err_math_args == err_ufloat.args
else:
raise Exception('ValueError exception expected')
def test_hypot():
'''
Special cases where derivatives cannot be calculated:
'''
x = ufloat(0, 1)
y = ufloat(0, 2)
# Derivatives that cannot be calculated simply return NaN, with no
# exception being raised, normally:
result = umath.hypot(x, y)
assert test_uncertainties.isnan(result.derivatives[x])
assert test_uncertainties.isnan(result.derivatives[y])
def test_power_all_cases():
'''
Test special cases of umath.pow().
'''
test_uncertainties.power_all_cases(umath.pow)
# test_power_special_cases() is similar to
# test_uncertainties.py:test_power_special_cases(), but with small
# differences: the built-in pow() and math.pow() are slightly
# different:
def test_power_special_cases():
'''
Checks special cases of umath.pow().
'''
test_uncertainties.power_special_cases(umath.pow)
# We want the same behavior for numbers with uncertainties and for
# math.pow() at their nominal values:
positive = ufloat(0.3, 0.01)
negative = ufloat(-0.3, 0.01)
# http://stackoverflow.com/questions/10282674/difference-between-the-built-in-pow-and-math-pow-for-floats-in-python
try:
umath.pow(ufloat(0, 0.1), negative)
except (ValueError, OverflowError), err: # Python 2.6+ "as err"
err_class = err.__class__ # For Python 3: err is destroyed after except
else:
err_class = None
err_msg = 'A proper exception should have been raised'
# An exception must have occurred:
if sys.version_info >= (2, 6):
assert err_class == ValueError, err_msg
else:
assert err_class == OverflowError, err_msg
try:
result = umath.pow(negative, positive)
except ValueError:
# The reason why it should also fail in Python 3 is that the
# result of Python 3 is a complex number, which uncertainties
# does not handle (no uncertainties on complex numbers). In
# Python 2, this should always fail, since Python 2 does not
# know how to calculate it.
pass
else:
if sys.version_info >= (2, 6):
raise Exception('A proper exception should have been raised')
else:
assert test_uncertainties.isnan(result.nominal_value)
assert test_uncertainties.isnan(result.std_dev)
def test_power_wrt_ref():
'''
Checks special cases of the umath.pow() power operator.
'''
test_uncertainties.power_wrt_ref(umath.pow, math.pow)
|