Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 344 lines (274 sloc) 11.42 kb
5a6b1f2 @lebigot Initial commit.
authored
1 """
2 Tests of the code in uncertainties.umath.
3
4 These tests can be run through the Nose testing framework.
5
852394b @lebigot Updated copyright dates.
authored
6 (c) 2010-2015 by Eric O. LEBIGOT (EOL).
5a6b1f2 @lebigot Initial commit.
authored
7 """
8
5e9877b @lebigot Reversed the 2to3 changes incorrectly added in commit 4f8832e43bec8c0…
authored
9 from __future__ import division
5a6b1f2 @lebigot Initial commit.
authored
10
11 # Standard modules
12 import sys
13 import math
14
15 # Local modules:
16 import uncertainties
17 import uncertainties.umath as umath
9e1c339 @lebigot Removed duplicate test (see test_uncertainties.py:test_power_special_…
authored
18 from uncertainties import ufloat
5a6b1f2 @lebigot Initial commit.
authored
19 from uncertainties import __author__
20
9e1c339 @lebigot Removed duplicate test (see test_uncertainties.py:test_power_special_…
authored
21 import test_uncertainties
22
5a6b1f2 @lebigot Initial commit.
authored
23 ###############################################################################
24 # Unit tests
25
26 def test_fixed_derivatives_math_funcs():
27 """
d47a92a @lebigot Tests pass. Better tests: umath.ldexp and umath.modf are tested like
authored
28 Comparison between function derivatives and numerical derivatives.
29
30 This comparison is useful for derivatives that are analytical.
5a6b1f2 @lebigot Initial commit.
authored
31 """
32
ec1eef4 @lebigot Better variable name.
authored
33 for name in umath.many_scalars_to_scalar_funcs:
5a6b1f2 @lebigot Initial commit.
authored
34 # print "Checking %s..." % name
35 func = getattr(umath, name)
36 # Numerical derivatives of func: the nominal value of func() results
37 # is used as the underlying function:
38 numerical_derivatives = uncertainties.NumericalDerivatives(
ba95cfa @lebigot Nosetests now correctly fails on incorrect derivatives.
authored
39 lambda *args: func(*args))
33efc7a @lebigot Fixed obsolete name.
authored
40 test_uncertainties.compare_derivatives(func, numerical_derivatives)
5a6b1f2 @lebigot Initial commit.
authored
41
ec1eef4 @lebigot Better variable name.
authored
42 # Functions that are not in umath.many_scalars_to_scalar_funcs:
d47a92a @lebigot Tests pass. Better tests: umath.ldexp and umath.modf are tested like
authored
43
8fc465f @lebigot Added: umath.frexp(), along with tests of its derivatives.
authored
44 ##
d47a92a @lebigot Tests pass. Better tests: umath.ldexp and umath.modf are tested like
authored
45 # modf(): returns a tuple:
8fc465f @lebigot Added: umath.frexp(), along with tests of its derivatives.
authored
46 def frac_part_modf(x):
d47a92a @lebigot Tests pass. Better tests: umath.ldexp and umath.modf are tested like
authored
47 return umath.modf(x)[0]
8fc465f @lebigot Added: umath.frexp(), along with tests of its derivatives.
authored
48 def int_part_modf(x):
d47a92a @lebigot Tests pass. Better tests: umath.ldexp and umath.modf are tested like
authored
49 return umath.modf(x)[1]
50
33efc7a @lebigot Fixed obsolete name.
authored
51 test_uncertainties.compare_derivatives(
8fc465f @lebigot Added: umath.frexp(), along with tests of its derivatives.
authored
52 frac_part_modf,
d47a92a @lebigot Tests pass. Better tests: umath.ldexp and umath.modf are tested like
authored
53 uncertainties.NumericalDerivatives(
7b20c55 @lebigot Adapted tests for modf() and frexp() to the new way numerical
authored
54 lambda x: frac_part_modf(x)))
33efc7a @lebigot Fixed obsolete name.
authored
55 test_uncertainties.compare_derivatives(
8fc465f @lebigot Added: umath.frexp(), along with tests of its derivatives.
authored
56 int_part_modf,
d47a92a @lebigot Tests pass. Better tests: umath.ldexp and umath.modf are tested like
authored
57 uncertainties.NumericalDerivatives(
7b20c55 @lebigot Adapted tests for modf() and frexp() to the new way numerical
authored
58 lambda x: int_part_modf(x)))
d47a92a @lebigot Tests pass. Better tests: umath.ldexp and umath.modf are tested like
authored
59
8fc465f @lebigot Added: umath.frexp(), along with tests of its derivatives.
authored
60 ##
61 # frexp(): returns a tuple:
62 def mantissa_frexp(x):
63 return umath.frexp(x)[0]
64 def exponent_frexp(x):
65 return umath.frexp(x)[1]
66
33efc7a @lebigot Fixed obsolete name.
authored
67 test_uncertainties.compare_derivatives(
8fc465f @lebigot Added: umath.frexp(), along with tests of its derivatives.
authored
68 mantissa_frexp,
69 uncertainties.NumericalDerivatives(
7b20c55 @lebigot Adapted tests for modf() and frexp() to the new way numerical
authored
70 lambda x: mantissa_frexp(x)))
33efc7a @lebigot Fixed obsolete name.
authored
71 test_uncertainties.compare_derivatives(
8fc465f @lebigot Added: umath.frexp(), along with tests of its derivatives.
authored
72 exponent_frexp,
73 uncertainties.NumericalDerivatives(
7b20c55 @lebigot Adapted tests for modf() and frexp() to the new way numerical
authored
74 lambda x: exponent_frexp(x)))
d47a92a @lebigot Tests pass. Better tests: umath.ldexp and umath.modf are tested like
authored
75
5a6b1f2 @lebigot Initial commit.
authored
76 def test_compound_expression():
77 """
78 Test equality between different formulas.
79 """
80
9e1c339 @lebigot Removed duplicate test (see test_uncertainties.py:test_power_special_…
authored
81 x = ufloat(3, 0.1)
5a6b1f2 @lebigot Initial commit.
authored
82
83 # Prone to numerical errors (but not much more than floats):
84 assert umath.tan(x) == umath.sin(x)/umath.cos(x)
85
86
87 def test_numerical_example():
88 "Test specific numerical examples"
89
9e1c339 @lebigot Removed duplicate test (see test_uncertainties.py:test_power_special_…
authored
90 x = ufloat(3.14, 0.01)
5a6b1f2 @lebigot Initial commit.
authored
91 result = umath.sin(x)
92 # In order to prevent big errors such as a wrong, constant value
93 # for all analytical and numerical derivatives, which would make
94 # test_fixed_derivatives_math_funcs() succeed despite incorrect
95 # calculations:
f47daf3 @lebigot Transformed all std_dev() method calls into .std_dev property access.
authored
96 assert ("%.6f +/- %.6f" % (result.nominal_value, result.std_dev)
5a6b1f2 @lebigot Initial commit.
authored
97 == "0.001593 +/- 0.010000")
98
99 # Regular calculations should still work:
100 assert("%.11f" % umath.sin(3) == "0.14112000806")
101
102 def test_monte_carlo_comparison():
103 """
104 Full comparison to a Monte-Carlo calculation.
105
106 Both the nominal values and the covariances are compared between
107 the direct calculation performed in this module and a Monte-Carlo
108 simulation.
109 """
110
111 try:
112 import numpy
113 import numpy.random
114 except ImportError:
9a6a9f9 @lebigot Tests don't fail when NumPy is not installed.
authored
115 import warnings
116 warnings.warn("Test not performed because NumPy is not available")
117 return
5a6b1f2 @lebigot Initial commit.
authored
118
119 # Works on numpy.arrays of Variable objects (whereas umath.sin()
120 # does not):
3040bfa @lebigot Fixed spelling error in variable name.
authored
121 sin_uarray_uncert = numpy.vectorize(umath.sin, otypes=[object])
5a6b1f2 @lebigot Initial commit.
authored
122
123 # Example expression (with correlations, and multiple variables combined
124 # in a non-linear way):
125 def function(x, y):
126 """
127 Function that takes two NumPy arrays of the same size.
128 """
129 # The uncertainty due to x is about equal to the uncertainty
130 # due to y:
3040bfa @lebigot Fixed spelling error in variable name.
authored
131 return 10 * x**2 - x * sin_uarray_uncert(y**3)
5a6b1f2 @lebigot Initial commit.
authored
132
9e1c339 @lebigot Removed duplicate test (see test_uncertainties.py:test_power_special_…
authored
133 x = ufloat(0.2, 0.01)
134 y = ufloat(10, 0.001)
5a6b1f2 @lebigot Initial commit.
authored
135 function_result_this_module = function(x, y)
136 nominal_value_this_module = function_result_this_module.nominal_value
137
138 # Covariances "f*f", "f*x", "f*y":
139 covariances_this_module = numpy.array(uncertainties.covariance_matrix(
140 (x, y, function_result_this_module)))
141
142 def monte_carlo_calc(n_samples):
143 """
144 Calculate function(x, y) on n_samples samples and returns the
145 median, and the covariances between (x, y, function(x, y)).
146 """
147 # Result of a Monte-Carlo simulation:
f47daf3 @lebigot Transformed all std_dev() method calls into .std_dev property access.
authored
148 x_samples = numpy.random.normal(x.nominal_value, x.std_dev,
5a6b1f2 @lebigot Initial commit.
authored
149 n_samples)
f47daf3 @lebigot Transformed all std_dev() method calls into .std_dev property access.
authored
150 y_samples = numpy.random.normal(y.nominal_value, y.std_dev,
5a6b1f2 @lebigot Initial commit.
authored
151 n_samples)
a811299 @lebigot Temporary fix in the unit tests for NumPy 1.8.
authored
152
7a81e1c @lebigot More relevant comments about the fix (which may not be temporary, and…
authored
153 # !! astype() is a fix for median() in NumPy 1.8.0:
a811299 @lebigot Temporary fix in the unit tests for NumPy 1.8.
authored
154 function_samples = function(x_samples, y_samples).astype(float)
5a6b1f2 @lebigot Initial commit.
authored
155
156 cov_mat = numpy.cov([x_samples, y_samples], function_samples)
a811299 @lebigot Temporary fix in the unit tests for NumPy 1.8.
authored
157
5a6b1f2 @lebigot Initial commit.
authored
158 return (numpy.median(function_samples), cov_mat)
159
160 (nominal_value_samples, covariances_samples) = monte_carlo_calc(1000000)
161
162
163 ## Comparison between both results:
164
165 # The covariance matrices must be close:
166
167 # We rely on the fact that covariances_samples very rarely has
168 # null elements:
c34df89 @lebigot Added todo.
authored
169
0419c12 @lebigot Higher todo priority.
authored
170 # !!! The test could be done directly with NumPy's comparison
c34df89 @lebigot Added todo.
authored
171 # tools, no? See assert_allclose, assert_array_almost_equal_nulp
b09564b @lebigot Added todo.
authored
172 # or assert_array_max_ulp. This is relevant for all vectorized
173 # occurrences of numbers_close.
c34df89 @lebigot Added todo.
authored
174
4810f3d @lebigot Fixed obsolete name.
authored
175 assert numpy.vectorize(test_uncertainties.numbers_close)(
5a6b1f2 @lebigot Initial commit.
authored
176 covariances_this_module,
177 covariances_samples,
178 0.05).all(), (
179 "The covariance matrices do not coincide between"
180 " the Monte-Carlo simulation and the direct calculation:\n"
181 "* Monte-Carlo:\n%s\n* Direct calculation:\n%s"
182 % (covariances_samples, covariances_this_module)
183 )
184
185 # The nominal values must be close:
4810f3d @lebigot Fixed obsolete name.
authored
186 assert test_uncertainties.numbers_close(
5a6b1f2 @lebigot Initial commit.
authored
187 nominal_value_this_module,
188 nominal_value_samples,
189 # The scale of the comparison depends on the standard
190 # deviation: the nominal values can differ by a fraction of
191 # the standard deviation:
192 math.sqrt(covariances_samples[2, 2])
193 / abs(nominal_value_samples) * 0.5), (
194 "The nominal value (%f) does not coincide with that of"
195 " the Monte-Carlo simulation (%f), for a standard deviation of %f."
196 % (nominal_value_this_module,
197 nominal_value_samples,
198 math.sqrt(covariances_samples[2, 2]))
199 )
200
201
202 def test_math_module():
203 "Operations with the math module"
204
9e1c339 @lebigot Removed duplicate test (see test_uncertainties.py:test_power_special_…
authored
205 x = ufloat(-1.5, 0.1)
5a6b1f2 @lebigot Initial commit.
authored
206
207 # The exponent must not be differentiated, when calculating the
208 # following (the partial derivative with respect to the exponent
209 # is not defined):
210 assert (x**2).nominal_value == 2.25
211
212 # Regular operations are chosen to be unchanged:
213 assert isinstance(umath.sin(3), float)
214
c02f240 @lebigot Specialized code for Python 2.6+.
authored
215 # factorial() must not be "damaged" by the umath module, so as
216 # to help make it a drop-in replacement for math (even though
217 # factorial() does not work on numbers with uncertainties
218 # because it is restricted to integers, as for
219 # math.factorial()):
220 assert umath.factorial(4) == 24
221
222 # fsum is special because it does not take a fixed number of
223 # variables:
224 assert umath.fsum([x, x]).nominal_value == -3
cc41cd5 @lebigot Added tests of consistency with the math module. Simpler code.
authored
225
505751c @lebigot Added test for the new functions in umath.locally_cst_funcs.
authored
226 # Functions that give locally constant results are tested: they
227 # should give the same result as their float equivalent:
228 for name in umath.locally_cst_funcs:
229
230 try:
231 func = getattr(umath, name)
232 except AttributeError:
233 continue # Not in the math module, so not in umath either
234
235 assert func(x) == func(x.nominal_value)
236 # The type should be left untouched. For example, isnan()
237 # should always give a boolean:
238 assert type(func(x)) == type(func(x.nominal_value))
239
cc41cd5 @lebigot Added tests of consistency with the math module. Simpler code.
authored
240 # The same exceptions should be generated when numbers with uncertainties
241 # are used:
242
9f811ae @lebigot Imported version-specific Nose tests from Python 2.3 branch
authored
243 ## !! The Nose testing framework seems to catch an exception when
244 ## it is aliased: "exc = OverflowError; ... except exc:..."
245 ## surprisingly catches OverflowError. So, tests are written in a
246 ## version-specific manner (until the Nose issue is resolved).
d62d6ca @lebigot Removed tests for Python 3. They should be adapted to Python 3.
authored
247
991342b @lebigot Code specialized for Python 2.6+.
authored
248 try:
249 math.log(0)
250 except ValueError as err_math:
251 # Python 3 does not make exceptions local variables: they are
252 # restricted to their except block:
253 err_math_args = err_math.args
9341e2e @lebigot Added tests for Python 3.
authored
254 else:
991342b @lebigot Code specialized for Python 2.6+.
authored
255 raise Exception('ValueError exception expected')
9f811ae @lebigot Imported version-specific Nose tests from Python 2.3 branch
authored
256
991342b @lebigot Code specialized for Python 2.6+.
authored
257 try:
258 umath.log(0)
259 except ValueError as err_ufloat:
260 assert err_math_args == err_ufloat.args
261 else:
262 raise Exception('ValueError exception expected')
263 try:
264 umath.log(ufloat(0, 0))
265 except ValueError as err_ufloat:
266 assert err_math_args == err_ufloat.args
267 else:
268 raise Exception('ValueError exception expected')
269 try:
270 umath.log(ufloat(0, 1))
271 except ValueError as err_ufloat:
272 assert err_math_args == err_ufloat.args
273 else:
274 raise Exception('ValueError exception expected')
9208753 @lebigot Put back the general wrapping "error to NaN" so that it handles a fun…
authored
275
9f811ae @lebigot Imported version-specific Nose tests from Python 2.3 branch
authored
276
9208753 @lebigot Put back the general wrapping "error to NaN" so that it handles a fun…
authored
277 def test_hypot():
278 '''
279 Special cases where derivatives cannot be calculated:
280 '''
9e1c339 @lebigot Removed duplicate test (see test_uncertainties.py:test_power_special_…
authored
281 x = ufloat(0, 1)
282 y = ufloat(0, 2)
9208753 @lebigot Put back the general wrapping "error to NaN" so that it handles a fun…
authored
283 # Derivatives that cannot be calculated simply return NaN, with no
284 # exception being raised, normally:
bbd1486 @lebigot Fixed obsolete docstring, now accept NaN as standard deviations.
authored
285 result = umath.hypot(x, y)
286 assert test_uncertainties.isnan(result.derivatives[x])
287 assert test_uncertainties.isnan(result.derivatives[y])
ab96b3a @lebigot umath.pow() now tested like the built-in pow().
authored
288
289 def test_power_all_cases():
290 '''
291 Test special cases of umath.pow().
292 '''
293 test_uncertainties.power_all_cases(umath.pow)
294
9e1c339 @lebigot Removed duplicate test (see test_uncertainties.py:test_power_special_…
authored
295 # test_power_special_cases() is similar to
296 # test_uncertainties.py:test_power_special_cases(), but with small
297 # differences: the built-in pow() and math.pow() are slightly
298 # different:
ab96b3a @lebigot umath.pow() now tested like the built-in pow().
authored
299 def test_power_special_cases():
300 '''
301 Checks special cases of umath.pow().
302 '''
9e1c339 @lebigot Removed duplicate test (see test_uncertainties.py:test_power_special_…
authored
303
ab96b3a @lebigot umath.pow() now tested like the built-in pow().
authored
304 test_uncertainties.power_special_cases(umath.pow)
b7d98f5 @lebigot Added test for corner case of built-in pow() and math.pow().
authored
305
9e1c339 @lebigot Removed duplicate test (see test_uncertainties.py:test_power_special_…
authored
306 # We want the same behavior for numbers with uncertainties and for
307 # math.pow() at their nominal values:
308
309 positive = ufloat(0.3, 0.01)
310 negative = ufloat(-0.3, 0.01)
a7e6ee0 @lebigot More tests pass.
authored
311
b7d98f5 @lebigot Added test for corner case of built-in pow() and math.pow().
authored
312 # http://stackoverflow.com/questions/10282674/difference-between-the-built-in-pow-and-math-pow-for-floats-in-python
313
314 try:
9e1c339 @lebigot Removed duplicate test (see test_uncertainties.py:test_power_special_…
authored
315 umath.pow(ufloat(0, 0.1), negative)
657efe2 @lebigot Code specialized for Python 2.6+.
authored
316 except (ValueError, OverflowError) as err:
5e42836 @lebigot Fixed test (type(err) was instance, in Python 2.4).
authored
317 err_class = err.__class__ # For Python 3: err is destroyed after except
a7e6ee0 @lebigot More tests pass.
authored
318 else:
5e42836 @lebigot Fixed test (type(err) was instance, in Python 2.4).
authored
319 err_class = None
6ee845e @lebigot Removed unnecessary test (the exception is raised, so no result is re…
authored
320
321 err_msg = 'A proper exception should have been raised'
afe0c9d @lebigot Removed duplicate tests.
authored
322
6ee845e @lebigot Removed unnecessary test (the exception is raised, so no result is re…
authored
323 # An exception must have occurred:
e98b91f @lebigot Specialized for Python 2.6+.
authored
324 assert err_class == ValueError, err_msg
6ee845e @lebigot Removed unnecessary test (the exception is raised, so no result is re…
authored
325
9e1c339 @lebigot Removed duplicate test (see test_uncertainties.py:test_power_special_…
authored
326 try:
327 result = umath.pow(negative, positive)
328 except ValueError:
329 # The reason why it should also fail in Python 3 is that the
330 # result of Python 3 is a complex number, which uncertainties
331 # does not handle (no uncertainties on complex numbers). In
332 # Python 2, this should always fail, since Python 2 does not
333 # know how to calculate it.
334 pass
335 else:
e98b91f @lebigot Specialized for Python 2.6+.
authored
336 raise Exception('A proper exception should have been raised')
ab96b3a @lebigot umath.pow() now tested like the built-in pow().
authored
337
338 def test_power_wrt_ref():
339 '''
340 Checks special cases of the umath.pow() power operator.
341 '''
342 test_uncertainties.power_wrt_ref(umath.pow, math.pow)
343
Something went wrong with that request. Please try again.