From aab68cf31bf5e3c980ce3b5bc278b275ef8e4103 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sun, 31 Mar 2024 17:38:00 +0100 Subject: [PATCH 01/16] remove 1to2 and depreciations --- uncertainties/1to2.py | 19 -- uncertainties/core.py | 64 +----- uncertainties/lib1to2/__init__.py | 0 uncertainties/lib1to2/fixes/__init__.py | 0 uncertainties/lib1to2/fixes/fix_std_dev.py | 38 ---- uncertainties/lib1to2/fixes/fix_std_devs.py | 22 -- .../lib1to2/fixes/fix_uarray_umatrix.py | 80 ------- uncertainties/lib1to2/fixes/fix_ufloat.py | 105 --------- uncertainties/lib1to2/test_1to2.py | 207 ------------------ uncertainties/test_uncertainties.py | 66 ------ uncertainties/unumpy/core.py | 31 +-- uncertainties/unumpy/test_unumpy.py | 21 +- 12 files changed, 7 insertions(+), 646 deletions(-) delete mode 100755 uncertainties/1to2.py delete mode 100644 uncertainties/lib1to2/__init__.py delete mode 100644 uncertainties/lib1to2/fixes/__init__.py delete mode 100644 uncertainties/lib1to2/fixes/fix_std_dev.py delete mode 100644 uncertainties/lib1to2/fixes/fix_std_devs.py delete mode 100644 uncertainties/lib1to2/fixes/fix_uarray_umatrix.py delete mode 100644 uncertainties/lib1to2/fixes/fix_ufloat.py delete mode 100644 uncertainties/lib1to2/test_1to2.py diff --git a/uncertainties/1to2.py b/uncertainties/1to2.py deleted file mode 100755 index 66db72ef..00000000 --- a/uncertainties/1to2.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python - -''' -Fixes code like the 2to3 Python utility, but with fixers from the local fixes -directory. - -(c) 2013 by Eric O. LEBIGOT (EOL). -''' - -# Code inspired by the 2to3 Python code. - -import sys - -if sys.version_info < (2, 6): - sys.exit("Please run this program with Python 2.6+.") - -import lib2to3.main - -sys.exit(lib2to3.main.main('uncertainties.lib1to2.fixes')) diff --git a/uncertainties/core.py b/uncertainties/core.py index 8ed7d433..7d6f47ee 100644 --- a/uncertainties/core.py +++ b/uncertainties/core.py @@ -116,7 +116,6 @@ def deprecation(message): % message, stacklevel=3) ############################################################################### - ## Definitions that depend on the availability of NumPy: @@ -934,21 +933,6 @@ def PDG_precision(std_dev): # used instead of the % formatting operator, if available: robust_format = format -class CallableStdDev(float): - ''' - Class for standard deviation results, which used to be - callable. Provided for compatibility with old code. Issues an - obsolescence warning upon call. - ''' - - # This class is a float. It must be set to the standard deviation - # upon construction. - - def __call__ (self): - deprecation('the std_dev attribute should not be called' - ' anymore: use .std_dev instead of .std_dev().') - return self - # Exponent letter: the keys are the possible main_fmt_type values of # format_num(): EXP_LETTERS = {'f': 'e', 'F': 'E'} @@ -1843,7 +1827,7 @@ def std_dev(self): #std_dev value (in fact, many intermediate AffineScalarFunc do #not need to have their std_dev calculated: only the final #AffineScalarFunc returned to the user does). - return CallableStdDev(sqrt(sum( + return float(sqrt(sum( delta**2 for delta in self.error_components().values()))) # Abbreviation (for formulas, etc.): @@ -2798,13 +2782,7 @@ def std_dev(self, std_dev): if std_dev < 0 and not isinfinite(std_dev): raise NegativeStdDev("The standard deviation cannot be negative") - self._std_dev = CallableStdDev(std_dev) - - # Support for legacy method: - def set_std_dev(self, value): # Obsolete - deprecation('instead of set_std_dev(), please use' - ' .std_dev = ...') - self.std_dev = value + self._std_dev = float(std_dev) # The following method is overridden so that we can represent the tag: def __repr__(self): @@ -3263,21 +3241,9 @@ def ufloat(nominal_value, std_dev=None, tag=None): """ Return a new random variable (Variable object). - The only non-obsolete use is: - - ufloat(nominal_value, std_dev), - ufloat(nominal_value, std_dev, tag=...). - Other input parameters are temporarily supported: - - - ufloat((nominal_value, std_dev)), - - ufloat((nominal_value, std_dev), tag), - - ufloat(str_representation), - - ufloat(str_representation, tag). - - Valid string representations str_representation are listed in - the documentation for ufloat_fromstr(). - nominal_value -- nominal value of the random variable. It is more meaningful to use a value close to the central value or to the mean. This value is propagated by mathematical operations as if it @@ -3292,28 +3258,4 @@ def ufloat(nominal_value, std_dev=None, tag=None): error_components() method). """ - try: - # Standard case: - return Variable(nominal_value, std_dev, tag=tag) - # Exception types raised by, respectively: tuple or string that - # can be converted through float() (case of a number with no - # uncertainty), and string that cannot be converted through - # float(): - except (TypeError, ValueError): - - if tag is not None: - tag_arg = tag # tag keyword used: - else: - tag_arg = std_dev # 2 positional arguments form - - try: - final_ufloat = ufloat_obsolete(nominal_value, tag_arg) - except: # The input is incorrect, not obsolete - raise - else: - # Obsolete, two-argument call: - deprecation( - 'either use ufloat(nominal_value, std_dev),' - ' ufloat(nominal_value, std_dev, tag), or the' - ' ufloat_fromstr() function, for string representations.') - return final_ufloat + return Variable(nominal_value, std_dev, tag=tag) \ No newline at end of file diff --git a/uncertainties/lib1to2/__init__.py b/uncertainties/lib1to2/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/uncertainties/lib1to2/fixes/__init__.py b/uncertainties/lib1to2/fixes/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/uncertainties/lib1to2/fixes/fix_std_dev.py b/uncertainties/lib1to2/fixes/fix_std_dev.py deleted file mode 100644 index fd15912b..00000000 --- a/uncertainties/lib1to2/fixes/fix_std_dev.py +++ /dev/null @@ -1,38 +0,0 @@ -''' -Fixer for lib2to3. - -Transforms .std_dev() calls into .std_dev attribute access. - -(c) 2013 by Eric O. LEBIGOT. -''' - -from lib2to3.fixer_base import BaseFix -from lib2to3.fixer_util import Name, Assign - -class FixStdDev(BaseFix): - - PATTERN = """ - power< any* trailer< '.' 'std_dev' > trailer< '(' ')' > > - | - power< any* trailer< '.' 'set_std_dev' > trailer< '(' set_arg=any ')' > > - """ - - def transform(self, node, results): - - if 'set_arg' in results: # Case of .set_std_dev() - - # set_std_dev => std_dev - attribute = node.children[-2] # .set_std_dev - attribute.children[1].replace(Name('std_dev')) - - # Call "(arg)": removed - node.children[-1].remove() - - # Replacement by an assignment: - node.replace(Assign(node.clone(), results['set_arg'].clone())) - - else: - # '.std_dev' is followed by a call with no argument: the call - # is removed: - node.children[-1].remove() - diff --git a/uncertainties/lib1to2/fixes/fix_std_devs.py b/uncertainties/lib1to2/fixes/fix_std_devs.py deleted file mode 100644 index 31afaf7e..00000000 --- a/uncertainties/lib1to2/fixes/fix_std_devs.py +++ /dev/null @@ -1,22 +0,0 @@ -''' -Fixer for lib2to3. - -Transform .std_devs() calls into .std_devs attribute access. - -(c) 2016 by Eric O. LEBIGOT. -''' - -from lib2to3.fixer_base import BaseFix -from lib2to3.fixer_util import Name, Assign - -class FixStdDevs(BaseFix): - - PATTERN = """ - power< any* trailer< '.' 'std_devs' > trailer< '(' ')' > > - """ - - def transform(self, node, results): - - # '.std_dev' is followed by a call with no argument: the call - # is removed: - node.children[-1].remove() diff --git a/uncertainties/lib1to2/fixes/fix_uarray_umatrix.py b/uncertainties/lib1to2/fixes/fix_uarray_umatrix.py deleted file mode 100644 index a47284ad..00000000 --- a/uncertainties/lib1to2/fixes/fix_uarray_umatrix.py +++ /dev/null @@ -1,80 +0,0 @@ -''' -Fixer for lib2to3. - -Transforms uarray(tuple) into uarray(nominal_values, std_devs) and -uarray(single_arg) into uarray(*single_arg). - -(c) 2013 by Eric O. LEBIGOT (EOL). -''' - -from lib2to3.fixer_base import BaseFix -from lib2to3.fixer_util import String, ArgList, Comma, syms - -############################################################################### -# lib2to3 grammar parts. - -#! Warning: indentation is meaningful! - -# (tuple): -tuple_call = """ - trailer< '(' - atom< '(' testlist_gexp< arg0=any ',' arg1=any > ')' > - ')' >""" - - -############################################################################### - -class FixUarrayUmatrix(BaseFix): - - # Non dotted access, then dotted access. - # Tuple call, then single-argument call - PATTERN = """ - power< 'uarray' {tuple_call} any* > - | - power< object=NAME trailer< '.' 'uarray' > {tuple_call} any* > - | - power< 'uarray' trailer< '(' args=any ')' > any* > - | - power< object=NAME trailer< '.' 'uarray' > - trailer< '(' args=any ')' > - any* > - """.format(tuple_call=tuple_call) - - # Same pattern, for umatrix(): - PATTERN = '{}|{}'.format(PATTERN, PATTERN.replace('uarray', 'umatrix')) - - def transform(self, node, results): - - if 'object' in results: # If dotted access: unc.uarray() - args = node.children[2] - else: - args = node.children[1] - - if 'args' in results: # Non-tuple argument - - # A star will be inserted in from of the single argument: - - # ! The following keeps spaces in front of the argument, - # if any (but this is safer than adding forcefully a star - # in front of the value of the argument: the argument can - # be a name (where it works), but also anything else, - # including a lib2to3.pytree.Node that has no value.) This - # is OK, as the syntax f(* (2, 1)) is valid. - - args_node = results['args'] - - # We must make sure that there is a single argument: - if args_node.type == syms.arglist: - return # Nothing modified - - # Single argument (in position 1): - new_args = [String('*'), args.children[1].clone()] - - else: # Tuple argument - - # New arguments: - new_args = [results['arg0'].clone(), - Comma(), results['arg1'].clone()] - - # Argument list update: - args.replace(ArgList(new_args)) diff --git a/uncertainties/lib1to2/fixes/fix_ufloat.py b/uncertainties/lib1to2/fixes/fix_ufloat.py deleted file mode 100644 index a6e92f7f..00000000 --- a/uncertainties/lib1to2/fixes/fix_ufloat.py +++ /dev/null @@ -1,105 +0,0 @@ -''' -Fixer for lib2to3. - -Transforms ufloat(tuple,...) and ufloat(string,...) into -ufloat(nominal_value, std_dev,...) and ufloat_fromstr - -(c) 2013 by Eric O. LEBIGOT. -''' - -from lib2to3.fixer_base import BaseFix -from lib2to3.fixer_util import ArgList, Call, Comma, Name, syms - -############################################################################### -# lib2to3 grammar parts. - -#! Warning: indentation is meaningful! - -# (tuple): -tuple_call = """ - trailer< '(' - atom< '(' testlist_gexp< arg0=any ',' arg1=any > ')' > - ')' >""" - -# (tuple, any): -tuple_any_call = """ - trailer< '(' - arglist< - atom< '(' testlist_gexp< arg0=any ',' arg1=any > ')' > - ',' tag=any - > - ')' >""" - - -############################################################################### - -class FixUfloat(BaseFix): - - # Non dotted access, then dotted access. - # Tuple call, then string call. - # No-tag call, then tag call. - PATTERN = """ - power< 'ufloat' {tuple_call} any* > - | - power< 'ufloat' {tuple_any_call} any* > - | - power< 'ufloat' trailer< '(' string=STRING ')' > any* > - | - power< 'ufloat' trailer< '(' - arglist< - string=STRING - ',' tag=any - > - ')' > any* > - | - power< object=NAME trailer< '.' 'ufloat' > {tuple_call} any* > - | - power< object=NAME trailer< '.' 'ufloat' > {tuple_any_call} any* > - | - power< object=NAME trailer< '.' 'ufloat' > - trailer< '(' string=STRING ')' > - any* > - | - power< object=NAME trailer< '.' 'ufloat' > - trailer< '(' arglist< string=STRING ',' tag=any > ')' > - any* > - """.format(tuple_call=tuple_call, - tuple_any_call=tuple_any_call) - - - def transform(self, node, results): - - # Handling of the first argument: - - if 'string' in results: # String as first argument - - new_func_name = 'ufloat_fromstr' - - # New arguments: - new_args=[results['string'].clone()] - - else: # Tuple as first argument - - new_func_name = 'ufloat' - - # New arguments: - new_args = [results['arg0'].clone(), - Comma(), results['arg1'].clone()] - - # Handling of the second argument (call with a tag): - if 'tag' in results: - new_args.extend([Comma(), results['tag'].clone()]) - - if 'object' in results: # If dotted access: unc.ufloat() - func_name = node.children[1].children[1] - args = node.children[2] - else: - func_name = node.children[0] - args = node.children[1] - - # Function name update: - func_name.value = new_func_name - #! func_name.changed() # Necessary when only .value is changed - - # Argument list update: - args.replace(ArgList(new_args)) diff --git a/uncertainties/lib1to2/test_1to2.py b/uncertainties/lib1to2/test_1to2.py deleted file mode 100644 index f23f8cc4..00000000 --- a/uncertainties/lib1to2/test_1to2.py +++ /dev/null @@ -1,207 +0,0 @@ -#!/usr/bin/env python - -''' -Unit tests for the uncertainties.lib1to2 code update package. - -Meant to be run through nosetests. - -(c) 2013-2020 by Eric O. LEBIGOT (EOL). -''' - -# Code inspired by: -# -# - lib2to3.tests.test_fixers.py - -from builtins import str -import sys -import os - -# !! Would it be possible to use an import hook so as to stop the -# import if the Python version is not high enough, instead of having -# like here a whole indented block? - - -if sys.version_info < (2, 7) or "TRAVIS" in os.environ or "APPVEYOR" in os.environ: - - # This package uses lib2to3, which requires Python 2.6+. - - # lib2to3.tests.support is missing from 2.7.3 Travis Python packages. - - # !! Nosetests for Python 2.6 also fails (it looks like it tries - # to run tests via lib2to3/tests/test_refactor.py): - - pass - -else: - - import os - try: - # lib2to3 test support seems to have moved to a new place in 2013: - import test.test_lib2to3.support as support - except ImportError: - # Pre-~2013 path for lib2to3 test support - import lib2to3.tests.support as support - - # The lib1to2.fixes package given to lib2to3 is the *local* package - # (not to another installed module). This is important for the - # __import__() used via support.get_refactorer(). - sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) - - def check_refactor(refactorer, source, expected): - """ - Raises an AssertionError if the given - lib2to3.refactor.RefactoringTool does not refactor 'source' into - 'expected'. - - source, expected -- strings (typically with Python code). - """ - - # !! str() is from future's builtins and is only needed for Python 2, - # where it is mostly equivalent to unicode(): - new = str( - refactorer.refactor_string(support.reformat(source), '')) - - assert support.reformat(expected) == new, ( - "Refactoring failed: '{}' => '{}' instead of '{}'".format( - source, new.strip(), expected)) - - # print 'Checked:', source, '=>', expected - - def check_all(fixer, tests): - ''' - Takes a fixer name (module from fixes) and a mapping that maps - code using the obsolete syntax into updated code, and checks - whether the code is correctly updated. - ''' - - refactorer = support.get_refactorer( - fixer_pkg='lib1to2', fixers=[fixer]) - - for (input_str, out_str) in tests.items(): - check_refactor(refactorer, input_str, out_str) - - def test_fix_std_dev(): - 'Tests the transformation of std_dev() into std_dev.' - - - tests = { - 'x.std_dev()': 'x.std_dev', - 'y.std_dev(); unc.std_dev(z)': 'y.std_dev; unc.std_dev(z)', - 'uncertainties.std_dev(x)': 'uncertainties.std_dev(x)', - 'std_dev(x)': 'std_dev(x)', - 'obj.x.std_dev()': 'obj.x.std_dev', - - """ - long_name.std_dev( - # No argument! - )""": - """ - long_name.std_dev""", - - # set_std_dev => .std_dev: - 'x.set_std_dev(3)': 'x.std_dev = 3', - 'y = set_std_dev(3)': 'y = set_std_dev(3)', # None - 'func = x.set_std_dev': 'func = x.set_std_dev', - 'obj.x.set_std_dev(sin(y))': 'obj.x.std_dev = sin(y)' - } - - check_all('std_dev', tests) - - def test_ufloat(): - ''' - Test of the transformation of ufloat(tuple,...) and - ufloat(string,...) into ufloat(nominal_value, std_dev, tag=...). - ''' - - tests = { - # Tuples: - 'ufloat((3, 0.14))': 'ufloat(3, 0.14)', - 'ufloat((3, 0.14), "pi")': 'ufloat(3, 0.14, "pi")', - "ufloat((3, 0.14), 'pi')": "ufloat(3, 0.14, 'pi')", - "x = ufloat((3, 0.14), tag='pi')": "x = ufloat(3, 0.14, tag='pi')", - - # Simple expressions that can be transformed: - 'ufloat((n, s), tag="var")': 'ufloat(n, s, tag="var")', - - # Simple expressions that cannot be transformed automatically: - 'ufloat(str_repr, tag="var")': 'ufloat(str_repr, tag="var")', - 'ufloat(*tuple_repr, tag="var")': 'ufloat(*tuple_repr, tag="var")', - 'ufloat(*t[0, 0])': 'ufloat(*t[0, 0])', - - # Strings: - 'ufloat("-1.23(3.4)")': 'ufloat_fromstr("-1.23(3.4)")', - "ufloat('-1.23(3.4)')": "ufloat_fromstr('-1.23(3.4)')", - 'ufloat("-1.23(3.4)", "var")': - 'ufloat_fromstr("-1.23(3.4)", "var")', - 'ufloat("-1.23(3.4)", tag="var")': - 'ufloat_fromstr("-1.23(3.4)", tag="var")' - - } - - # Automatic addition of a dotted access: - tests.update(dict( - # !! Dictionary comprehension usable with Python 2.7+ - (orig.replace('ufloat', 'unc.ufloat'), - new.replace('ufloat', 'unc.ufloat')) - for (orig, new) in tests.items())) - - # Test for space consistency: - tests[' t = u.ufloat("3")'] = ' t = u.ufloat_fromstr("3")' - - # Exponentiation test: - tests.update(dict( - # !! Dictionary comprehension usable with Python 2.7+ - (orig+'**2', new+'**2') - for (orig, new) in tests.items())) - - # Exponent test: - tests['2**ufloat("3")'] = '2**ufloat_fromstr("3")' - - # Opposite test: - tests['-ufloat("3")'] = '-ufloat_fromstr("3")' - - check_all('ufloat', tests) - - def test_uarray_umatrix(): - ''' - Test of the transformation of uarray(tuple,...) into - uarray(nominal_values, std_devs). Also performs the same tests - on umatrix(). - ''' - - tests = { - 'uarray((arange(3), std_devs))': 'uarray(arange(3), std_devs)', - 'uarray(tuple_arg)': 'uarray(*tuple_arg)', - # Unmodified, correct code: - 'uarray(values, std_devs)': 'uarray(values, std_devs)', - # Spaces tests: - 'uarray( ( arange(3), std_devs ) ) ': - 'uarray( arange(3), std_devs) ', - 'uarray( tuple_arg )': 'uarray(* tuple_arg)' - - } - - # Automatic addition of a dotted access: - tests.update(dict( - # !! Dictionary comprehension usable with Python 2.7+ - (orig.replace('uarray', 'un.uarray'), - new.replace('uarray', 'un.uarray')) - for (orig, new) in tests.items())) - - # Exponentiation test: - tests.update(dict( - # !! Dictionary comprehension usable with Python 2.7+ - (orig+'**2', new+'**2') - for (orig, new) in tests.items())) - - # Test for space consistency: - tests[' t = u.uarray(args)'] = ' t = u.uarray(*args)' - - # Same tests, but for umatrix: - tests.update(dict( - (orig.replace('uarray', 'umatrix'), - new.replace('uarray', 'umatrix')) - for (orig, new) in tests.items())) - - check_all('uarray_umatrix', tests) - diff --git a/uncertainties/test_uncertainties.py b/uncertainties/test_uncertainties.py index c5ed4ccc..f37d0bbe 100644 --- a/uncertainties/test_uncertainties.py +++ b/uncertainties/test_uncertainties.py @@ -250,39 +250,6 @@ def test_value_construction(): assert x.std_dev == 0.14 assert x.tag == 'pi' - ## Comparison with the obsolete tuple form: - - # The following tuple is stored in a variable instead of being - # repeated in the calls below, so that the automatic code update - # does not replace ufloat((3, 0.14)) by ufloat(3, 14): the goal - # here is to make sure that the obsolete form gives the same - # result as the new form. - - representation = (3, 0.14) # Obsolete representation - - x = ufloat(3, 0.14) - x2 = ufloat(representation) # Obsolete - assert x.nominal_value == x2.nominal_value - assert x.std_dev == x2.std_dev - assert x.tag is None - assert x2.tag is None - - # With tag as positional argument: - x = ufloat(3, 0.14, "pi") - x2 = ufloat(representation, "pi") # Obsolete - assert x.nominal_value == x2.nominal_value - assert x.std_dev == x2.std_dev - assert x.tag == 'pi' - assert x2.tag == 'pi' - - # With tag keyword: - x = ufloat(3, 0.14, tag="pi") - x2 = ufloat(representation, tag="pi") # Obsolete - assert x.nominal_value == x2.nominal_value - assert x.std_dev == x2.std_dev - assert x.tag == 'pi' - assert x2.tag == 'pi' - # Negative standard deviations should be caught in a nice way # (with the right exception): try: @@ -290,12 +257,6 @@ def test_value_construction(): except uncert_core.NegativeStdDev: pass - try: - # Obsolete form: - x = ufloat((3, -0.1)) - except uncert_core.NegativeStdDev: - pass - ## Incorrect forms should not raise any deprecation warning, but ## raise an exception: @@ -389,25 +350,6 @@ def test_ufloat_fromstr(): assert numbers_close(num.std_dev, values[1]) assert num.tag == 'test variable' - ## Obsolete forms - - num = ufloat(representation) # Obsolete - assert numbers_close(num.nominal_value, values[0]) - assert numbers_close(num.std_dev, values[1]) - assert num.tag is None - - # Call with a tag list argument: - num = ufloat(representation, 'test variable') # Obsolete - assert numbers_close(num.nominal_value, values[0]) - assert numbers_close(num.std_dev, values[1]) - assert num.tag == 'test variable' - - # Call with a tag keyword argument: - num = ufloat(representation, tag='test variable') # Obsolete - assert numbers_close(num.nominal_value, values[0]) - assert numbers_close(num.std_dev, values[1]) - assert num.tag == 'test variable' - ############################################################################### # Test of correctness of the fixed (usually analytical) derivatives: @@ -742,14 +684,6 @@ def test_logic(): assert bool(z) == True assert bool(t) == True # Only infinitseimal neighborhood are used -def test_obsolete(): - 'Tests some obsolete creation of number with uncertainties' - x = ufloat(3, 0.1) - # Obsolete function, protected against automatic modification: - x.set_std_dev.__call__(0.2) # Obsolete - - x_std_dev = x.std_dev - assert x_std_dev() == 0.2 # Obsolete call def test_basic_access_to_data(): "Access to data from Variable and AffineScalarFunc objects." diff --git a/uncertainties/unumpy/core.py b/uncertainties/unumpy/core.py index 3507db4d..54addf06 100644 --- a/uncertainties/unumpy/core.py +++ b/uncertainties/unumpy/core.py @@ -25,7 +25,6 @@ # Local modules: import uncertainties.umath_core as umath_core import uncertainties.core as uncert_core -from uncertainties.core import deprecation __all__ = [ # Factory functions: @@ -285,8 +284,7 @@ def uarray(nominal_values, std_devs=None): """ if std_devs is None: # Obsolete, single tuple argument call - deprecation('uarray() should now be called with two arguments.') - (nominal_values, std_devs) = nominal_values + raise TypeError('uarray() should be called with two arguments.') return (numpy.vectorize( # ! Looking up uncert_core.Variable beforehand through @@ -573,28 +571,6 @@ def pinv(array_like, rcond=pinv_default): ########## Matrix class -class CallableStdDevs(numpy.matrix): - ''' - Class for standard deviation results, which used to be - callable. Provided for compatibility with old code. Issues an - obsolescence warning upon call. - - New objects must be created by passing an existing - ''' - - def __new__(cls, matrix): - # The following prevents a copy of the original matrix, which - # could be expensive, and is unnecessary (the CallableStdDevs - # is just a wrapping around the original matrix, which can be - # modified): - matrix.__class__ = cls - return matrix - - def __call__ (self): - deprecation('the std_devs attribute should not be called' - ' anymore: use .std_devs instead of .std_devs().') - return self - class matrix(numpy.matrix): # The name of this class is the same as NumPy's, which is why it # does not follow PEP 8. @@ -639,7 +615,7 @@ def nominal_values(self): # the first ones to have such methods? @property def std_devs(self): - return CallableStdDevs(std_devs(self)) + return numpy.matrix(std_devs(self)) def umatrix(nominal_values, std_devs=None): """ @@ -653,8 +629,7 @@ def umatrix(nominal_values, std_devs=None): """ if std_devs is None: # Obsolete, single tuple argument call - deprecation('umatrix() should now be called with two arguments.') - (nominal_values, std_devs) = nominal_values + raise TypeError('umatrix() should be called with two arguments.') return uarray(nominal_values, std_devs).view(matrix) diff --git a/uncertainties/unumpy/test_unumpy.py b/uncertainties/unumpy/test_unumpy.py index 5923dfd3..5e2147a5 100644 --- a/uncertainties/unumpy/test_unumpy.py +++ b/uncertainties/unumpy/test_unumpy.py @@ -309,23 +309,4 @@ def test_array_comparisons(): # 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]) - -def test_obsolete(): - 'Test of obsolete functions' - - # The new and old calls should give the same results: - - # The unusual syntax is here to protect against automatic code - # update: - arr_obs = unumpy.uarray.__call__(([1, 2], [1, 4])) # Obsolete call - arr = unumpy.uarray([1, 2], [1, 4]) - assert arrays_close(arr_obs, arr) - - # The new and old calls should give the same results: - - # The unusual syntax is here to protect against automatic code - # update: - mat_obs = unumpy.umatrix.__call__(([1, 2], [1, 4])) # Obsolete call - mat = unumpy.umatrix([1, 2], [1, 4]) - assert arrays_close(mat_obs, mat) + assert numpy.all((mat == [mat[0,0], 4]) == [True, False]) \ No newline at end of file From da08ae937b1129a783d3a6d4d211912d553e3f45 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 1 Apr 2024 01:24:58 +0100 Subject: [PATCH 02/16] move tests to tests folder --- uncertainties/{unumpy => tests}/test_ulinalg.py | 2 +- uncertainties/{ => tests}/test_umath.py | 2 +- uncertainties/{ => tests}/test_uncertainties.py | 0 uncertainties/{unumpy => tests}/test_unumpy.py | 5 +++-- 4 files changed, 5 insertions(+), 4 deletions(-) rename uncertainties/{unumpy => tests}/test_ulinalg.py (97%) rename uncertainties/{ => tests}/test_umath.py (99%) rename uncertainties/{ => tests}/test_uncertainties.py (100%) rename uncertainties/{unumpy => tests}/test_unumpy.py (98%) diff --git a/uncertainties/unumpy/test_ulinalg.py b/uncertainties/tests/test_ulinalg.py similarity index 97% rename from uncertainties/unumpy/test_ulinalg.py rename to uncertainties/tests/test_ulinalg.py index b72a0e11..2fe9dac8 100644 --- a/uncertainties/unumpy/test_ulinalg.py +++ b/uncertainties/tests/test_ulinalg.py @@ -18,7 +18,7 @@ sys.exit() # There is no reason to test the interface to NumPy from uncertainties import unumpy, ufloat -from uncertainties.unumpy.test_unumpy import arrays_close +from uncertainties.tests.test_unumpy import arrays_close def test_list_inverse(): "Test of the inversion of a square matrix" diff --git a/uncertainties/test_umath.py b/uncertainties/tests/test_umath.py similarity index 99% rename from uncertainties/test_umath.py rename to uncertainties/tests/test_umath.py index 687c2028..0e63ed9f 100644 --- a/uncertainties/test_umath.py +++ b/uncertainties/tests/test_umath.py @@ -18,7 +18,7 @@ import uncertainties.core as uncert_core import uncertainties.umath_core as umath_core -from . import test_uncertainties +from uncertainties.tests import test_uncertainties ############################################################################### # Unit tests diff --git a/uncertainties/test_uncertainties.py b/uncertainties/tests/test_uncertainties.py similarity index 100% rename from uncertainties/test_uncertainties.py rename to uncertainties/tests/test_uncertainties.py diff --git a/uncertainties/unumpy/test_unumpy.py b/uncertainties/tests/test_unumpy.py similarity index 98% rename from uncertainties/unumpy/test_unumpy.py rename to uncertainties/tests/test_unumpy.py index 5e2147a5..1f240271 100644 --- a/uncertainties/unumpy/test_unumpy.py +++ b/uncertainties/tests/test_unumpy.py @@ -18,9 +18,10 @@ # Local modules: import uncertainties import uncertainties.core as uncert_core -from uncertainties import ufloat, unumpy, test_uncertainties +from uncertainties import ufloat, unumpy +from uncertainties.tests import test_uncertainties from uncertainties.unumpy import core -from uncertainties.test_uncertainties import numbers_close, arrays_close +from uncertainties.tests.test_uncertainties import numbers_close, arrays_close def test_numpy(): From 45b60573878324909fbaff2ee5fd717e51714cac Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 1 Apr 2024 18:35:36 +0100 Subject: [PATCH 03/16] move tests folder --- {uncertainties/tests => tests}/test_ulinalg.py | 0 {uncertainties/tests => tests}/test_umath.py | 0 {uncertainties/tests => tests}/test_uncertainties.py | 0 {uncertainties/tests => tests}/test_unumpy.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {uncertainties/tests => tests}/test_ulinalg.py (100%) rename {uncertainties/tests => tests}/test_umath.py (100%) rename {uncertainties/tests => tests}/test_uncertainties.py (100%) rename {uncertainties/tests => tests}/test_unumpy.py (100%) diff --git a/uncertainties/tests/test_ulinalg.py b/tests/test_ulinalg.py similarity index 100% rename from uncertainties/tests/test_ulinalg.py rename to tests/test_ulinalg.py diff --git a/uncertainties/tests/test_umath.py b/tests/test_umath.py similarity index 100% rename from uncertainties/tests/test_umath.py rename to tests/test_umath.py diff --git a/uncertainties/tests/test_uncertainties.py b/tests/test_uncertainties.py similarity index 100% rename from uncertainties/tests/test_uncertainties.py rename to tests/test_uncertainties.py diff --git a/uncertainties/tests/test_unumpy.py b/tests/test_unumpy.py similarity index 100% rename from uncertainties/tests/test_unumpy.py rename to tests/test_unumpy.py From 9cb808562e060764ac5753802db7dbcb36bd719e Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 1 Apr 2024 18:59:44 +0100 Subject: [PATCH 04/16] move things around to get tests running --- tests/test_ulinalg.py | 10 +- tests/test_umath.py | 43 +++--- tests/test_uncertainties.py | 252 +----------------------------------- tests/test_unumpy.py | 15 +-- uncertainties/testing.py | 237 +++++++++++++++++++++++++++++++++ 5 files changed, 256 insertions(+), 301 deletions(-) create mode 100644 uncertainties/testing.py diff --git a/tests/test_ulinalg.py b/tests/test_ulinalg.py index 2fe9dac8..32604206 100644 --- a/tests/test_ulinalg.py +++ b/tests/test_ulinalg.py @@ -1,11 +1,3 @@ -""" -Tests for uncertainties.unumpy.ulinalg. - -These tests can be run through the Nose testing framework. - -(c) 2010-2016 by Eric O. LEBIGOT (EOL) . -""" - # Some tests are already performed in test_unumpy (unumpy contains a # matrix inversion, for instance). They are not repeated here. @@ -18,7 +10,7 @@ sys.exit() # There is no reason to test the interface to NumPy from uncertainties import unumpy, ufloat -from uncertainties.tests.test_unumpy import arrays_close +from uncertainties.testing import arrays_close def test_list_inverse(): "Test of the inversion of a square matrix" diff --git a/tests/test_umath.py b/tests/test_umath.py index 0e63ed9f..27a3857f 100644 --- a/tests/test_umath.py +++ b/tests/test_umath.py @@ -1,25 +1,13 @@ -""" -Tests of the code in uncertainties.umath. - -These tests can be run through the Nose testing framework. - -(c) 2010-2016 by Eric O. LEBIGOT (EOL). -""" - -from __future__ import division -from __future__ import absolute_import - -# Standard modules import sys import math +from math import isnan, isinf -# Local modules: from uncertainties import ufloat import uncertainties.core as uncert_core import uncertainties.umath_core as umath_core -from uncertainties.tests import test_uncertainties - +from uncertainties.testing import compare_derivatives, numbers_close +from test_uncertainties import power_special_cases, power_all_cases, power_wrt_ref ############################################################################### # Unit tests @@ -31,13 +19,12 @@ def test_fixed_derivatives_math_funcs(): """ for name in umath_core.many_scalars_to_scalar_funcs: - # print "Checking %s..." % name func = getattr(umath_core, name) # Numerical derivatives of func: the nominal value of func() results # is used as the underlying function: numerical_derivatives = uncert_core.NumericalDerivatives( lambda *args: func(*args)) - test_uncertainties.compare_derivatives(func, numerical_derivatives) + compare_derivatives(func, numerical_derivatives) # Functions that are not in umath_core.many_scalars_to_scalar_funcs: @@ -48,11 +35,11 @@ def frac_part_modf(x): def int_part_modf(x): return umath_core.modf(x)[1] - test_uncertainties.compare_derivatives( + compare_derivatives( frac_part_modf, uncert_core.NumericalDerivatives( lambda x: frac_part_modf(x))) - test_uncertainties.compare_derivatives( + compare_derivatives( int_part_modf, uncert_core.NumericalDerivatives( lambda x: int_part_modf(x))) @@ -64,11 +51,11 @@ def mantissa_frexp(x): def exponent_frexp(x): return umath_core.frexp(x)[1] - test_uncertainties.compare_derivatives( + compare_derivatives( mantissa_frexp, uncert_core.NumericalDerivatives( lambda x: mantissa_frexp(x))) - test_uncertainties.compare_derivatives( + compare_derivatives( exponent_frexp, uncert_core.NumericalDerivatives( lambda x: exponent_frexp(x))) @@ -172,7 +159,7 @@ def monte_carlo_calc(n_samples): # or assert_array_max_ulp. This is relevant for all vectorized # occurrences of numbers_close. - assert numpy.vectorize(test_uncertainties.numbers_close)( + assert numpy.vectorize(numbers_close)( covariances_this_module, covariances_samples, 0.06).all(), ( @@ -183,7 +170,7 @@ def monte_carlo_calc(n_samples): ) # The nominal values must be close: - assert test_uncertainties.numbers_close( + assert numbers_close( nominal_value_this_module, nominal_value_samples, # The scale of the comparison depends on the standard @@ -279,14 +266,14 @@ def test_hypot(): # Derivatives that cannot be calculated simply return NaN, with no # exception being raised, normally: result = umath_core.hypot(x, y) - assert test_uncertainties.isnan(result.derivatives[x]) - assert test_uncertainties.isnan(result.derivatives[y]) + assert isnan(result.derivatives[x]) + assert isnan(result.derivatives[y]) def test_power_all_cases(): ''' Test special cases of umath_core.pow(). ''' - test_uncertainties.power_all_cases(umath_core.pow) + power_all_cases(umath_core.pow) # test_power_special_cases() is similar to # test_uncertainties.py:test_power_special_cases(), but with small @@ -297,7 +284,7 @@ def test_power_special_cases(): Checks special cases of umath_core.pow(). ''' - test_uncertainties.power_special_cases(umath_core.pow) + power_special_cases(umath_core.pow) # We want the same behavior for numbers with uncertainties and for # math.pow() at their nominal values. @@ -341,4 +328,4 @@ def test_power_wrt_ref(): ''' Checks special cases of the umath_core.pow() power operator. ''' - test_uncertainties.power_wrt_ref(umath_core.pow, math.pow) + power_wrt_ref(umath_core.pow, math.pow) \ No newline at end of file diff --git a/tests/test_uncertainties.py b/tests/test_uncertainties.py index f37d0bbe..7cd65f3f 100644 --- a/tests/test_uncertainties.py +++ b/tests/test_uncertainties.py @@ -1,230 +1,19 @@ -# coding=utf-8 -""" -Tests of the code in uncertainties/__init__.py. - -These tests can be run through the Nose testing framework. - -(c) 2010-2016 by Eric O. LEBIGOT (EOL). -""" - -from __future__ import division -from __future__ import print_function - - -# Standard modules -from builtins import str -from builtins import zip -from builtins import map -from builtins import range import copy -import weakref import math -from math import isnan, isinf -import random import sys - -# 3rd-party modules -# import nose.tools - -# Local modules +from math import isnan, isinf import uncertainties.core as uncert_core from uncertainties.core import ufloat, AffineScalarFunc, ufloat_fromstr from uncertainties import umath +from uncertainties.testing import numbers_close, ufloats_close, compare_derivatives, arrays_close # The following information is useful for making sure that the right # version of Python is running the tests (for instance with the Travis # Continuous Integration system): print("Testing with Python", sys.version) -############################################################################### - -# Utilities for unit testing - -def numbers_close(x, y, tolerance=1e-6): - """ - Returns True if the given floats are close enough. - - The given tolerance is the relative difference allowed, or the absolute - difference, if one of the numbers is 0. - - NaN is allowed: it is considered close to itself. - """ - - # !!! Python 3.5+ has math.isclose(): maybe it could be used here. - - # Instead of using a try and ZeroDivisionError, we do a test, - # NaN could appear silently: - - if x != 0 and y != 0: - if isinf(x): - return isinf(y) - elif isnan(x): - return isnan(y) - else: - # Symmetric form of the test: - return 2*abs(x-y)/(abs(x)+abs(y)) < tolerance - - else: # Either x or y is zero - return abs(x or y) < tolerance - -def ufloats_close(x, y, tolerance=1e-6): - ''' - Tests if two numbers with uncertainties are close, as random - variables: this is stronger than testing whether their nominal - value and standard deviation are close. - - The tolerance is applied to both the nominal value and the - standard deviation of the difference between the numbers. - ''' - - diff = x-y - return (numbers_close(diff.nominal_value, 0, tolerance) - and numbers_close(diff.std_dev, 0, tolerance)) - -class DerivativesDiffer(Exception): - pass - - -def compare_derivatives(func, numerical_derivatives, - num_args_list=None): - """ - Checks the derivatives of a function 'func' (as returned by the - wrap() wrapper), by comparing them to the - 'numerical_derivatives' functions. - - Raises a DerivativesDiffer exception in case of problem. - - These functions all take the number of arguments listed in - num_args_list. If num_args is None, it is automatically obtained. - - Tests are done on random arguments. - """ - - try: - funcname = func.name - except AttributeError: - funcname = func.__name__ - - # print "Testing", func.__name__ - - if not num_args_list: - - # Detecting automatically the correct number of arguments is not - # always easy (because not all values are allowed, etc.): - - num_args_table = { - 'atanh': [1], - 'log': [1, 2] # Both numbers of arguments are tested - } - if funcname in num_args_table: - num_args_list = num_args_table[funcname] - else: - - num_args_list = [] - - # We loop until we find reasonable function arguments: - # We get the number of arguments by trial and error: - for num_args in range(10): - try: - #! Giving integer arguments is good for preventing - # certain functions from failing even though num_args - # is their correct number of arguments - # (e.g. math.ldexp(x, i), where i must be an integer) - func(*(1,)*num_args) - except TypeError: - pass # Not the right number of arguments - else: # No error - # num_args is a good number of arguments for func: - num_args_list.append(num_args) - - if not num_args_list: - raise Exception("Can't find a reasonable number of arguments" - " for function '%s'." % funcname) - - for num_args in num_args_list: - - # Argument numbers that will have a random integer value: - integer_arg_nums = set() - - if funcname == 'ldexp': - # The second argument must be an integer: - integer_arg_nums.add(1) - - while True: - try: - - # We include negative numbers, for more thorough tests: - args = [] - for arg_num in range(num_args): - if arg_num in integer_arg_nums: - args.append(random.choice(range(-10, 10))) - else: - args.append( - uncert_core.Variable(random.random()*4-2, 0)) - - # 'args', but as scalar values: - args_scalar = [uncert_core.nominal_value(v) - for v in args] - - func_approx = func(*args) - - # Some functions yield simple Python constants, after - # wrapping in wrap(): no test has to be performed. - # Some functions also yield tuples... - if isinstance(func_approx, AffineScalarFunc): - - # We compare all derivatives: - for (arg_num, (arg, numerical_deriv)) in ( - enumerate(zip(args, numerical_derivatives))): - - # Some arguments might not be differentiable: - if isinstance(arg, int): - continue - - fixed_deriv_value = func_approx.derivatives[arg] - - num_deriv_value = numerical_deriv(*args_scalar) - - # This message is useful: the user can see that - # tests are really performed (instead of not being - # performed, silently): - print("Testing derivative #%d of %s at %s" % ( - arg_num, funcname, args_scalar)) - - if not numbers_close(fixed_deriv_value, - num_deriv_value, 1e-4): - - # It is possible that the result is NaN: - if not isnan(func_approx): - raise DerivativesDiffer( - "Derivative #%d of function '%s' may be" - " wrong: at args = %s," - " value obtained = %.16f," - " while numerical approximation = %.16f." - % (arg_num, funcname, args, - fixed_deriv_value, num_deriv_value)) - - except ValueError as err: # Arguments out of range, or of wrong type - # Factorial(real) lands here: - if str(err).startswith('factorial'): - integer_arg_nums = set([0]) - continue # We try with different arguments - # Some arguments might have to be integers, for instance: - except TypeError as err: - if len(integer_arg_nums) == num_args: - raise Exception("Incorrect testing procedure: unable to " - "find correct argument values for %s: %s" - % (funcname, err)) - - # Another argument might be forced to be an integer: - integer_arg_nums.add(random.choice(range(num_args))) - else: - # We have found reasonable arguments, and the test passed: - break - -############################################################################### def test_value_construction(): ''' @@ -2097,43 +1886,6 @@ def test_custom_pretty_print_and_latex(): pass else: - def arrays_close(m1, m2, precision=1e-4): - """ - Returns True iff m1 and m2 are almost equal, where elements - can be either floats or AffineScalarFunc objects. - - Two independent AffineScalarFunc objects are deemed equal if - both their nominal value and uncertainty are equal (up to the - given precision). - - m1, m2 -- NumPy arrays. - - precision -- precision passed through to - uncertainties.test_uncertainties.numbers_close(). - """ - - # ! numpy.allclose() is similar to this function, but does not - # work on arrays that contain numbers with uncertainties, because - # of the isinf() function. - - for (elmt1, elmt2) in zip(m1.flat, m2.flat): - - # For a simpler comparison, both elements are - # converted to AffineScalarFunc objects: - elmt1 = uncert_core.to_affine_scalar(elmt1) - elmt2 = uncert_core.to_affine_scalar(elmt2) - - if not numbers_close(elmt1.nominal_value, - elmt2.nominal_value, precision): - return False - - if not numbers_close(elmt1.std_dev, - elmt2.std_dev, precision): - return False - - return True - - def test_numpy_comparison(): "Comparison with a NumPy array." diff --git a/tests/test_unumpy.py b/tests/test_unumpy.py index 1f240271..d487cdc1 100644 --- a/tests/test_unumpy.py +++ b/tests/test_unumpy.py @@ -1,27 +1,14 @@ -""" -Tests of the code in uncertainties/unumpy/__init__.py. - -These tests can be run through the Nose testing framework. - -(c) 2010-2016 by Eric O. LEBIGOT (EOL). -""" - -from __future__ import division - -# 3rd-party modules: try: import numpy except ImportError: import sys sys.exit() # There is no reason to test the interface to NumPy -# Local modules: import uncertainties import uncertainties.core as uncert_core from uncertainties import ufloat, unumpy -from uncertainties.tests import test_uncertainties from uncertainties.unumpy import core -from uncertainties.tests.test_uncertainties import numbers_close, arrays_close +from uncertainties.testing import numbers_close, arrays_close def test_numpy(): diff --git a/uncertainties/testing.py b/uncertainties/testing.py new file mode 100644 index 00000000..66c9236d --- /dev/null +++ b/uncertainties/testing.py @@ -0,0 +1,237 @@ +import weakref + +import random +from math import isnan, isinf +from uncertainties.core import ufloat, AffineScalarFunc, ufloat_fromstr + +import uncertainties.core as uncert_core + +############################################################################### + +# Utilities for unit testing + +def numbers_close(x, y, tolerance=1e-6): + """ + Returns True if the given floats are close enough. + + The given tolerance is the relative difference allowed, or the absolute + difference, if one of the numbers is 0. + + NaN is allowed: it is considered close to itself. + """ + + # !!! Python 3.5+ has math.isclose(): maybe it could be used here. + + # Instead of using a try and ZeroDivisionError, we do a test, + # NaN could appear silently: + + if x != 0 and y != 0: + if isinf(x): + return isinf(y) + elif isnan(x): + return isnan(y) + else: + # Symmetric form of the test: + return 2*abs(x-y)/(abs(x)+abs(y)) < tolerance + + else: # Either x or y is zero + return abs(x or y) < tolerance + +def ufloats_close(x, y, tolerance=1e-6): + ''' + Tests if two numbers with uncertainties are close, as random + variables: this is stronger than testing whether their nominal + value and standard deviation are close. + + The tolerance is applied to both the nominal value and the + standard deviation of the difference between the numbers. + ''' + + diff = x-y + return (numbers_close(diff.nominal_value, 0, tolerance) + and numbers_close(diff.std_dev, 0, tolerance)) + +class DerivativesDiffer(Exception): + pass + + +def compare_derivatives(func, numerical_derivatives, + num_args_list=None): + """ + Checks the derivatives of a function 'func' (as returned by the + wrap() wrapper), by comparing them to the + 'numerical_derivatives' functions. + + Raises a DerivativesDiffer exception in case of problem. + + These functions all take the number of arguments listed in + num_args_list. If num_args is None, it is automatically obtained. + + Tests are done on random arguments. + """ + + try: + funcname = func.name + except AttributeError: + funcname = func.__name__ + + # print "Testing", func.__name__ + + if not num_args_list: + + # Detecting automatically the correct number of arguments is not + # always easy (because not all values are allowed, etc.): + + num_args_table = { + 'atanh': [1], + 'log': [1, 2] # Both numbers of arguments are tested + } + if funcname in num_args_table: + num_args_list = num_args_table[funcname] + else: + + num_args_list = [] + + # We loop until we find reasonable function arguments: + # We get the number of arguments by trial and error: + for num_args in range(10): + try: + #! Giving integer arguments is good for preventing + # certain functions from failing even though num_args + # is their correct number of arguments + # (e.g. math.ldexp(x, i), where i must be an integer) + func(*(1,)*num_args) + except TypeError: + pass # Not the right number of arguments + else: # No error + # num_args is a good number of arguments for func: + num_args_list.append(num_args) + + if not num_args_list: + raise Exception("Can't find a reasonable number of arguments" + " for function '%s'." % funcname) + + for num_args in num_args_list: + + # Argument numbers that will have a random integer value: + integer_arg_nums = set() + + if funcname == 'ldexp': + # The second argument must be an integer: + integer_arg_nums.add(1) + + while True: + try: + + # We include negative numbers, for more thorough tests: + args = [] + for arg_num in range(num_args): + if arg_num in integer_arg_nums: + args.append(random.choice(range(-10, 10))) + else: + args.append( + uncert_core.Variable(random.random()*4-2, 0)) + + # 'args', but as scalar values: + args_scalar = [uncert_core.nominal_value(v) + for v in args] + + func_approx = func(*args) + + # Some functions yield simple Python constants, after + # wrapping in wrap(): no test has to be performed. + # Some functions also yield tuples... + if isinstance(func_approx, AffineScalarFunc): + + # We compare all derivatives: + for (arg_num, (arg, numerical_deriv)) in ( + enumerate(zip(args, numerical_derivatives))): + + # Some arguments might not be differentiable: + if isinstance(arg, int): + continue + + fixed_deriv_value = func_approx.derivatives[arg] + + num_deriv_value = numerical_deriv(*args_scalar) + + # This message is useful: the user can see that + # tests are really performed (instead of not being + # performed, silently): + print("Testing derivative #%d of %s at %s" % ( + arg_num, funcname, args_scalar)) + + if not numbers_close(fixed_deriv_value, + num_deriv_value, 1e-4): + + # It is possible that the result is NaN: + if not isnan(func_approx): + raise DerivativesDiffer( + "Derivative #%d of function '%s' may be" + " wrong: at args = %s," + " value obtained = %.16f," + " while numerical approximation = %.16f." + % (arg_num, funcname, args, + fixed_deriv_value, num_deriv_value)) + + except ValueError as err: # Arguments out of range, or of wrong type + # Factorial(real) lands here: + if str(err).startswith('factorial'): + integer_arg_nums = set([0]) + continue # We try with different arguments + # Some arguments might have to be integers, for instance: + except TypeError as err: + if len(integer_arg_nums) == num_args: + raise Exception("Incorrect testing procedure: unable to " + "find correct argument values for %s: %s" + % (funcname, err)) + + # Another argument might be forced to be an integer: + integer_arg_nums.add(random.choice(range(num_args))) + else: + # We have found reasonable arguments, and the test passed: + break + +############################################################################### + +try: + import numpy +except ImportError: + pass +else: + + def arrays_close(m1, m2, precision=1e-4): + """ + Returns True iff m1 and m2 are almost equal, where elements + can be either floats or AffineScalarFunc objects. + + Two independent AffineScalarFunc objects are deemed equal if + both their nominal value and uncertainty are equal (up to the + given precision). + + m1, m2 -- NumPy arrays. + + precision -- precision passed through to + uncertainties.test_uncertainties.numbers_close(). + """ + + # ! numpy.allclose() is similar to this function, but does not + # work on arrays that contain numbers with uncertainties, because + # of the isinf() function. + + for (elmt1, elmt2) in zip(m1.flat, m2.flat): + + # For a simpler comparison, both elements are + # converted to AffineScalarFunc objects: + elmt1 = uncert_core.to_affine_scalar(elmt1) + elmt2 = uncert_core.to_affine_scalar(elmt2) + + if not numbers_close(elmt1.nominal_value, + elmt2.nominal_value, precision): + return False + + if not numbers_close(elmt1.std_dev, + elmt2.std_dev, precision): + return False + + return True From 88364a77f7f123f5d2c41391a7f076742560643c Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 1 Apr 2024 22:46:20 +0100 Subject: [PATCH 05/16] remove refences to nose --- INSTALL.txt | 11 ++--------- tests/test_uncertainties.py | 2 -- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/INSTALL.txt b/INSTALL.txt index 1aa398cc..3408197f 100644 --- a/INSTALL.txt +++ b/INSTALL.txt @@ -15,14 +15,7 @@ or, if additional access rights are needed (Unix): sudo python setup.py install -* The tests programs (test_*.py) are meant to be run through the Nose -testing framework. This can be achieved for instance with a command +* The tests programs (test_*.py) are meant to be run through pytest. This can be achieved for instance with a command like - nosetests -sv uncertainties/ - -or simply - - nosetests uncertainties/ - -(for a less verbose output). + pytest ./tests \ No newline at end of file diff --git a/tests/test_uncertainties.py b/tests/test_uncertainties.py index 7cd65f3f..7778df04 100644 --- a/tests/test_uncertainties.py +++ b/tests/test_uncertainties.py @@ -173,8 +173,6 @@ def check_op(op, num_args): for op in uncert_core.modified_ops_with_reflection: check_op(op, 2) -# Additional, more complex checks, for use with the nose unit testing -# framework. def test_copy(): "Standard copy module integration" From 8f904f3b1dfd70a0d1a91dd2a034c504672d8d68 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 1 Apr 2024 23:49:49 +0100 Subject: [PATCH 06/16] add tests to pyproject.toml --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 6b8043cb..ebb08596 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,3 +57,6 @@ Changelog = "https://github.com/lmfit/uncertainties/blob/master/CHANGES.rst" [project.optional-dependencies] optional = ["numpy"] + +[tool.pytest.ini_options] +testpaths = ["tests"] From 97cbdf489ee7da11e526acd9bef117f17ee65297 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 1 Apr 2024 23:55:48 +0100 Subject: [PATCH 07/16] move power tests to helpers --- tests/helpers.py | 152 +++++++++++++++++++++++++++++++++++ tests/test_umath.py | 2 +- tests/test_uncertainties.py | 155 +----------------------------------- 3 files changed, 154 insertions(+), 155 deletions(-) create mode 100644 tests/helpers.py diff --git a/tests/helpers.py b/tests/helpers.py new file mode 100644 index 00000000..13c55f79 --- /dev/null +++ b/tests/helpers.py @@ -0,0 +1,152 @@ +from uncertainties.core import ufloat +from math import isnan + +def power_all_cases(op): + ''' + Checks all cases for the value and derivatives of power-like + operator op (op is typically the built-in pow(), or math.pow()). + + Checks only the details of special results like 0, 1 or NaN). + + Different cases for the value of x**p and its derivatives are + tested by dividing the (x, p) plane with: + + - x < 0, x = 0, x > 0 + - p integer or not, p < 0, p = 0, p > 0 + + (not all combinations are distinct: for instance x > 0 gives + identical formulas for all p). + ''' + + zero = ufloat(0, 0.1) + zero2 = ufloat(0, 0.1) + one = ufloat(1, 0.1) + positive = ufloat(0.3, 0.01) + positive2 = ufloat(0.3, 0.01) + negative = ufloat(-0.3, 0.01) + integer = ufloat(-3, 0) + non_int_larger_than_one = ufloat(3.1, 0.01) + positive_smaller_than_one = ufloat(0.3, 0.01) + + ## negative**integer + + result = op(negative, integer) + assert not isnan(result.derivatives[negative]) + assert isnan(result.derivatives[integer]) + + # Limit cases: + result = op(negative, one) + assert result.derivatives[negative] == 1 + assert isnan(result.derivatives[one]) + + result = op(negative, zero) + assert result.derivatives[negative] == 0 + assert isnan(result.derivatives[zero]) + + ## negative**non-integer + + ## zero**... + + result = op(zero, non_int_larger_than_one) + assert isnan(result.derivatives[zero]) + assert result.derivatives[non_int_larger_than_one] == 0 + + # Special cases: + result = op(zero, one) + assert result.derivatives[zero] == 1 + assert result.derivatives[one] == 0 + + result = op(zero, 2*one) + assert result.derivatives[zero] == 0 + assert result.derivatives[one] == 0 + + result = op(zero, positive_smaller_than_one) + assert isnan(result.derivatives[zero]) + assert result.derivatives[positive_smaller_than_one] == 0 + + result = op(zero, zero2) + assert result.derivatives[zero] == 0 + assert isnan(result.derivatives[zero2]) + + ## positive**...: this is a quite regular case where the value and + ## the derivatives are all defined. + + result = op(positive, positive2) + assert not isnan(result.derivatives[positive]) + assert not isnan(result.derivatives[positive2]) + + result = op(positive, zero) + assert result.derivatives[positive] == 0 + assert not isnan(result.derivatives[zero]) + + result = op(positive, negative) + assert not isnan(result.derivatives[positive]) + assert not isnan(result.derivatives[negative]) + + +def power_special_cases(op): + ''' + Checks special cases of the uncertainty power operator op (where + op is typically the built-in pow or uncertainties.umath.pow). + + The values x = 0, x = 1 and x = NaN are special, as are null, + integral and NaN values of p. + ''' + + zero = ufloat(0, 0) + one = ufloat(1, 0) + p = ufloat(0.3, 0.01) + + assert op(0, p) == 0 + assert op(zero, p) == 0 + + # The outcome of 1**nan and nan**0 was undefined before Python + # 2.6 (http://docs.python.org/library/math.html#math.pow): + assert op(float('nan'), zero) == 1.0 + assert op(one, float('nan')) == 1.0 + + # …**0 == 1.0: + assert op(p, 0) == 1.0 + assert op(zero, 0) == 1.0 + assert op((-p), 0) == 1.0 + # …**zero: + assert op((-10.3), zero) == 1.0 + assert op(0, zero) == 1.0 + assert op(0.3, zero) == 1.0 + assert op((-p), zero) == 1.0 + assert op(zero, zero) == 1.0 + assert op(p, zero) == 1.0 + + # one**… == 1.0 + assert op(one, -3) == 1.0 + assert op(one, -3.1) == 1.0 + assert op(one, 0) == 1.0 + assert op(one, 3) == 1.0 + assert op(one, 3.1) == 1.0 + + # … with two numbers with uncertainties: + assert op(one, (-p)) == 1.0 + assert op(one, zero) == 1.0 + assert op(one, p) == 1.0 + # 1**… == 1.0: + assert op(1., (-p)) == 1.0 + assert op(1., zero) == 1.0 + assert op(1., p) == 1.0 + +def power_wrt_ref(op, ref_op): + ''' + Checks special cases of the uncertainty power operator op (where + op is typically the built-in pow or uncertainties.umath.pow), by + comparing its results to the reference power operator ref_op + (which is typically the built-in pow or math.pow). + ''' + + # Negative numbers with uncertainty can be exponentiated to an + # integral power: + assert op(ufloat(-1.1, 0.1), -9).nominal_value == ref_op(-1.1, -9) + + # Case of numbers with no uncertainty: should give the same result + # as numbers with uncertainties: + assert op(ufloat(-1, 0), 9) == ref_op(-1, 9) + assert op(ufloat(-1.1, 0), 9) == ref_op(-1.1, 9) + diff --git a/tests/test_umath.py b/tests/test_umath.py index 27a3857f..52aa4269 100644 --- a/tests/test_umath.py +++ b/tests/test_umath.py @@ -7,7 +7,7 @@ import uncertainties.umath_core as umath_core from uncertainties.testing import compare_derivatives, numbers_close -from test_uncertainties import power_special_cases, power_all_cases, power_wrt_ref +from helpers import power_special_cases, power_all_cases, power_wrt_ref ############################################################################### # Unit tests diff --git a/tests/test_uncertainties.py b/tests/test_uncertainties.py index 7778df04..ae739656 100644 --- a/tests/test_uncertainties.py +++ b/tests/test_uncertainties.py @@ -8,11 +8,7 @@ from uncertainties.core import ufloat, AffineScalarFunc, ufloat_fromstr from uncertainties import umath from uncertainties.testing import numbers_close, ufloats_close, compare_derivatives, arrays_close - -# The following information is useful for making sure that the right -# version of Python is running the tests (for instance with the Travis -# Continuous Integration system): -print("Testing with Python", sys.version) +from helpers import power_special_cases, power_all_cases, power_wrt_ref def test_value_construction(): @@ -1024,88 +1020,6 @@ def test_power_all_cases(): power_all_cases(pow) -def power_all_cases(op): - ''' - Checks all cases for the value and derivatives of power-like - operator op (op is typically the built-in pow(), or math.pow()). - - Checks only the details of special results like 0, 1 or NaN). - - Different cases for the value of x**p and its derivatives are - tested by dividing the (x, p) plane with: - - - x < 0, x = 0, x > 0 - - p integer or not, p < 0, p = 0, p > 0 - - (not all combinations are distinct: for instance x > 0 gives - identical formulas for all p). - ''' - - zero = ufloat(0, 0.1) - zero2 = ufloat(0, 0.1) - one = ufloat(1, 0.1) - positive = ufloat(0.3, 0.01) - positive2 = ufloat(0.3, 0.01) - negative = ufloat(-0.3, 0.01) - integer = ufloat(-3, 0) - non_int_larger_than_one = ufloat(3.1, 0.01) - positive_smaller_than_one = ufloat(0.3, 0.01) - - ## negative**integer - - result = op(negative, integer) - assert not isnan(result.derivatives[negative]) - assert isnan(result.derivatives[integer]) - - # Limit cases: - result = op(negative, one) - assert result.derivatives[negative] == 1 - assert isnan(result.derivatives[one]) - - result = op(negative, zero) - assert result.derivatives[negative] == 0 - assert isnan(result.derivatives[zero]) - - ## negative**non-integer - - ## zero**... - - result = op(zero, non_int_larger_than_one) - assert isnan(result.derivatives[zero]) - assert result.derivatives[non_int_larger_than_one] == 0 - - # Special cases: - result = op(zero, one) - assert result.derivatives[zero] == 1 - assert result.derivatives[one] == 0 - - result = op(zero, 2*one) - assert result.derivatives[zero] == 0 - assert result.derivatives[one] == 0 - - result = op(zero, positive_smaller_than_one) - assert isnan(result.derivatives[zero]) - assert result.derivatives[positive_smaller_than_one] == 0 - - result = op(zero, zero2) - assert result.derivatives[zero] == 0 - assert isnan(result.derivatives[zero2]) - - ## positive**...: this is a quite regular case where the value and - ## the derivatives are all defined. - - result = op(positive, positive2) - assert not isnan(result.derivatives[positive]) - assert not isnan(result.derivatives[positive2]) - - result = op(positive, zero) - assert result.derivatives[positive] == 0 - assert not isnan(result.derivatives[zero]) - - result = op(positive, negative) - assert not isnan(result.derivatives[positive]) - assert not isnan(result.derivatives[negative]) - ############################################################################### @@ -1149,79 +1063,12 @@ def test_power_special_cases(): else: raise Exception('A proper exception should have been raised') -def power_special_cases(op): - ''' - Checks special cases of the uncertainty power operator op (where - op is typically the built-in pow or uncertainties.umath.pow). - - The values x = 0, x = 1 and x = NaN are special, as are null, - integral and NaN values of p. - ''' - - zero = ufloat(0, 0) - one = ufloat(1, 0) - p = ufloat(0.3, 0.01) - - assert op(0, p) == 0 - assert op(zero, p) == 0 - - # The outcome of 1**nan and nan**0 was undefined before Python - # 2.6 (http://docs.python.org/library/math.html#math.pow): - assert op(float('nan'), zero) == 1.0 - assert op(one, float('nan')) == 1.0 - - # …**0 == 1.0: - assert op(p, 0) == 1.0 - assert op(zero, 0) == 1.0 - assert op((-p), 0) == 1.0 - # …**zero: - assert op((-10.3), zero) == 1.0 - assert op(0, zero) == 1.0 - assert op(0.3, zero) == 1.0 - assert op((-p), zero) == 1.0 - assert op(zero, zero) == 1.0 - assert op(p, zero) == 1.0 - - # one**… == 1.0 - assert op(one, -3) == 1.0 - assert op(one, -3.1) == 1.0 - assert op(one, 0) == 1.0 - assert op(one, 3) == 1.0 - assert op(one, 3.1) == 1.0 - - # … with two numbers with uncertainties: - assert op(one, (-p)) == 1.0 - assert op(one, zero) == 1.0 - assert op(one, p) == 1.0 - # 1**… == 1.0: - assert op(1., (-p)) == 1.0 - assert op(1., zero) == 1.0 - assert op(1., p) == 1.0 - - def test_power_wrt_ref(): ''' Checks special cases of the built-in pow() power operator. ''' power_wrt_ref(pow, pow) -def power_wrt_ref(op, ref_op): - ''' - Checks special cases of the uncertainty power operator op (where - op is typically the built-in pow or uncertainties.umath.pow), by - comparing its results to the reference power operator ref_op - (which is typically the built-in pow or math.pow). - ''' - - # Negative numbers with uncertainty can be exponentiated to an - # integral power: - assert op(ufloat(-1.1, 0.1), -9).nominal_value == ref_op(-1.1, -9) - - # Case of numbers with no uncertainty: should give the same result - # as numbers with uncertainties: - assert op(ufloat(-1, 0), 9) == ref_op(-1, 9) - assert op(ufloat(-1.1, 0), 9) == ref_op(-1.1, 9) - ############################################################################### From 842126871df8381c4cc6c32ec099b3870bb732f4 Mon Sep 17 00:00:00 2001 From: andrewgsavage Date: Tue, 2 Apr 2024 22:59:51 +0900 Subject: [PATCH 08/16] remove 1to2 and deprecations (#214) This commit removes: - the 1to2 code and documentation, - the compatability code that handled obsolete function calls, - the deprecations in these calls, - the tests that tested obsolete behaviour --- doc/index.rst | 43 ---- uncertainties/1to2.py | 19 -- uncertainties/core.py | 82 +------ uncertainties/lib1to2/__init__.py | 0 uncertainties/lib1to2/fixes/__init__.py | 0 uncertainties/lib1to2/fixes/fix_std_dev.py | 38 ---- uncertainties/lib1to2/fixes/fix_std_devs.py | 22 -- .../lib1to2/fixes/fix_uarray_umatrix.py | 80 ------- uncertainties/lib1to2/fixes/fix_ufloat.py | 105 --------- uncertainties/lib1to2/test_1to2.py | 207 ------------------ uncertainties/test_uncertainties.py | 66 ------ uncertainties/unumpy/core.py | 31 +-- uncertainties/unumpy/test_unumpy.py | 21 +- 13 files changed, 7 insertions(+), 707 deletions(-) delete mode 100755 uncertainties/1to2.py delete mode 100644 uncertainties/lib1to2/__init__.py delete mode 100644 uncertainties/lib1to2/fixes/__init__.py delete mode 100644 uncertainties/lib1to2/fixes/fix_std_dev.py delete mode 100644 uncertainties/lib1to2/fixes/fix_std_devs.py delete mode 100644 uncertainties/lib1to2/fixes/fix_uarray_umatrix.py delete mode 100644 uncertainties/lib1to2/fixes/fix_ufloat.py delete mode 100644 uncertainties/lib1to2/test_1to2.py diff --git a/doc/index.rst b/doc/index.rst index f6945510..104fe5cf 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -247,49 +247,6 @@ total). :mod:`uncertainties` is thus a **lightweight, portable package** with abundant documentation and tests. -Migration from version 1 to version 2 -===================================== - -Some **incompatible changes** were introduced in version 2 of -:mod:`uncertainties` (see the `version history`_). While the version 2 -line will support the version 1 syntax for some time, it is -recommended to **update existing programs** as soon as possible. This -can be made easier through the provided **automatic updater**. - -The automatic updater works like Python's `2to3 -`_ updater. It can be run -(in a Unix or DOS shell) with: - -.. code-block:: sh - - python -m uncertainties.1to2 - -For example, updating a single Python program can be done with - -.. code-block:: sh - - python -m uncertainties.1to2 -w example.py - -All the Python programs contained under a directory ``Programs`` -(including in nested sub-directories) can be automatically updated -with - -.. code-block:: sh - - python -m uncertainties.1to2 -w Programs - -Backups are automatically created, unless the ``-n`` option is given. - -Some **manual adjustments** might be necessary after running the -updater (incorrectly modified lines, untouched obsolete syntax). - -While the updater creates backup copies by default, it is generally -useful to **first create a backup** of the modified directory, or -alternatively to use some `version control -`_ -system. Reviewing the modifications with a `file comparison tool -`_ might also be useful. - What others say =============== diff --git a/uncertainties/1to2.py b/uncertainties/1to2.py deleted file mode 100755 index 66db72ef..00000000 --- a/uncertainties/1to2.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python - -''' -Fixes code like the 2to3 Python utility, but with fixers from the local fixes -directory. - -(c) 2013 by Eric O. LEBIGOT (EOL). -''' - -# Code inspired by the 2to3 Python code. - -import sys - -if sys.version_info < (2, 6): - sys.exit("Please run this program with Python 2.6+.") - -import lib2to3.main - -sys.exit(lib2to3.main.main('uncertainties.lib1to2.fixes')) diff --git a/uncertainties/core.py b/uncertainties/core.py index 8ed7d433..a5870dfe 100644 --- a/uncertainties/core.py +++ b/uncertainties/core.py @@ -98,25 +98,6 @@ def set_doc_string(func): CONSTANT_TYPES = FLOAT_LIKE_TYPES+(complex,) ############################################################################### - -# Utility for issuing deprecation warnings - -def deprecation(message): - ''' - Warn the user with the given message, by issuing a - DeprecationWarning. - ''' - - # stacklevel = 3 points to the original user call (not to the - # function from this module that called deprecation()). - # DeprecationWarning is ignored by default: not used. - - warnings.warn('Obsolete: %s Code can be automatically updated with' - ' python -m uncertainties.1to2 -w ProgramDirectory.' - % message, stacklevel=3) - -############################################################################### - ## Definitions that depend on the availability of NumPy: @@ -934,21 +915,6 @@ def PDG_precision(std_dev): # used instead of the % formatting operator, if available: robust_format = format -class CallableStdDev(float): - ''' - Class for standard deviation results, which used to be - callable. Provided for compatibility with old code. Issues an - obsolescence warning upon call. - ''' - - # This class is a float. It must be set to the standard deviation - # upon construction. - - def __call__ (self): - deprecation('the std_dev attribute should not be called' - ' anymore: use .std_dev instead of .std_dev().') - return self - # Exponent letter: the keys are the possible main_fmt_type values of # format_num(): EXP_LETTERS = {'f': 'e', 'F': 'E'} @@ -1843,7 +1809,7 @@ def std_dev(self): #std_dev value (in fact, many intermediate AffineScalarFunc do #not need to have their std_dev calculated: only the final #AffineScalarFunc returned to the user does). - return CallableStdDev(sqrt(sum( + return float(sqrt(sum( delta**2 for delta in self.error_components().values()))) # Abbreviation (for formulas, etc.): @@ -2798,13 +2764,7 @@ def std_dev(self, std_dev): if std_dev < 0 and not isinfinite(std_dev): raise NegativeStdDev("The standard deviation cannot be negative") - self._std_dev = CallableStdDev(std_dev) - - # Support for legacy method: - def set_std_dev(self, value): # Obsolete - deprecation('instead of set_std_dev(), please use' - ' .std_dev = ...') - self.std_dev = value + self._std_dev = float(std_dev) # The following method is overridden so that we can represent the tag: def __repr__(self): @@ -3263,21 +3223,9 @@ def ufloat(nominal_value, std_dev=None, tag=None): """ Return a new random variable (Variable object). - The only non-obsolete use is: - - ufloat(nominal_value, std_dev), - ufloat(nominal_value, std_dev, tag=...). - Other input parameters are temporarily supported: - - - ufloat((nominal_value, std_dev)), - - ufloat((nominal_value, std_dev), tag), - - ufloat(str_representation), - - ufloat(str_representation, tag). - - Valid string representations str_representation are listed in - the documentation for ufloat_fromstr(). - nominal_value -- nominal value of the random variable. It is more meaningful to use a value close to the central value or to the mean. This value is propagated by mathematical operations as if it @@ -3292,28 +3240,4 @@ def ufloat(nominal_value, std_dev=None, tag=None): error_components() method). """ - try: - # Standard case: - return Variable(nominal_value, std_dev, tag=tag) - # Exception types raised by, respectively: tuple or string that - # can be converted through float() (case of a number with no - # uncertainty), and string that cannot be converted through - # float(): - except (TypeError, ValueError): - - if tag is not None: - tag_arg = tag # tag keyword used: - else: - tag_arg = std_dev # 2 positional arguments form - - try: - final_ufloat = ufloat_obsolete(nominal_value, tag_arg) - except: # The input is incorrect, not obsolete - raise - else: - # Obsolete, two-argument call: - deprecation( - 'either use ufloat(nominal_value, std_dev),' - ' ufloat(nominal_value, std_dev, tag), or the' - ' ufloat_fromstr() function, for string representations.') - return final_ufloat + return Variable(nominal_value, std_dev, tag=tag) \ No newline at end of file diff --git a/uncertainties/lib1to2/__init__.py b/uncertainties/lib1to2/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/uncertainties/lib1to2/fixes/__init__.py b/uncertainties/lib1to2/fixes/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/uncertainties/lib1to2/fixes/fix_std_dev.py b/uncertainties/lib1to2/fixes/fix_std_dev.py deleted file mode 100644 index fd15912b..00000000 --- a/uncertainties/lib1to2/fixes/fix_std_dev.py +++ /dev/null @@ -1,38 +0,0 @@ -''' -Fixer for lib2to3. - -Transforms .std_dev() calls into .std_dev attribute access. - -(c) 2013 by Eric O. LEBIGOT. -''' - -from lib2to3.fixer_base import BaseFix -from lib2to3.fixer_util import Name, Assign - -class FixStdDev(BaseFix): - - PATTERN = """ - power< any* trailer< '.' 'std_dev' > trailer< '(' ')' > > - | - power< any* trailer< '.' 'set_std_dev' > trailer< '(' set_arg=any ')' > > - """ - - def transform(self, node, results): - - if 'set_arg' in results: # Case of .set_std_dev() - - # set_std_dev => std_dev - attribute = node.children[-2] # .set_std_dev - attribute.children[1].replace(Name('std_dev')) - - # Call "(arg)": removed - node.children[-1].remove() - - # Replacement by an assignment: - node.replace(Assign(node.clone(), results['set_arg'].clone())) - - else: - # '.std_dev' is followed by a call with no argument: the call - # is removed: - node.children[-1].remove() - diff --git a/uncertainties/lib1to2/fixes/fix_std_devs.py b/uncertainties/lib1to2/fixes/fix_std_devs.py deleted file mode 100644 index 31afaf7e..00000000 --- a/uncertainties/lib1to2/fixes/fix_std_devs.py +++ /dev/null @@ -1,22 +0,0 @@ -''' -Fixer for lib2to3. - -Transform .std_devs() calls into .std_devs attribute access. - -(c) 2016 by Eric O. LEBIGOT. -''' - -from lib2to3.fixer_base import BaseFix -from lib2to3.fixer_util import Name, Assign - -class FixStdDevs(BaseFix): - - PATTERN = """ - power< any* trailer< '.' 'std_devs' > trailer< '(' ')' > > - """ - - def transform(self, node, results): - - # '.std_dev' is followed by a call with no argument: the call - # is removed: - node.children[-1].remove() diff --git a/uncertainties/lib1to2/fixes/fix_uarray_umatrix.py b/uncertainties/lib1to2/fixes/fix_uarray_umatrix.py deleted file mode 100644 index a47284ad..00000000 --- a/uncertainties/lib1to2/fixes/fix_uarray_umatrix.py +++ /dev/null @@ -1,80 +0,0 @@ -''' -Fixer for lib2to3. - -Transforms uarray(tuple) into uarray(nominal_values, std_devs) and -uarray(single_arg) into uarray(*single_arg). - -(c) 2013 by Eric O. LEBIGOT (EOL). -''' - -from lib2to3.fixer_base import BaseFix -from lib2to3.fixer_util import String, ArgList, Comma, syms - -############################################################################### -# lib2to3 grammar parts. - -#! Warning: indentation is meaningful! - -# (tuple): -tuple_call = """ - trailer< '(' - atom< '(' testlist_gexp< arg0=any ',' arg1=any > ')' > - ')' >""" - - -############################################################################### - -class FixUarrayUmatrix(BaseFix): - - # Non dotted access, then dotted access. - # Tuple call, then single-argument call - PATTERN = """ - power< 'uarray' {tuple_call} any* > - | - power< object=NAME trailer< '.' 'uarray' > {tuple_call} any* > - | - power< 'uarray' trailer< '(' args=any ')' > any* > - | - power< object=NAME trailer< '.' 'uarray' > - trailer< '(' args=any ')' > - any* > - """.format(tuple_call=tuple_call) - - # Same pattern, for umatrix(): - PATTERN = '{}|{}'.format(PATTERN, PATTERN.replace('uarray', 'umatrix')) - - def transform(self, node, results): - - if 'object' in results: # If dotted access: unc.uarray() - args = node.children[2] - else: - args = node.children[1] - - if 'args' in results: # Non-tuple argument - - # A star will be inserted in from of the single argument: - - # ! The following keeps spaces in front of the argument, - # if any (but this is safer than adding forcefully a star - # in front of the value of the argument: the argument can - # be a name (where it works), but also anything else, - # including a lib2to3.pytree.Node that has no value.) This - # is OK, as the syntax f(* (2, 1)) is valid. - - args_node = results['args'] - - # We must make sure that there is a single argument: - if args_node.type == syms.arglist: - return # Nothing modified - - # Single argument (in position 1): - new_args = [String('*'), args.children[1].clone()] - - else: # Tuple argument - - # New arguments: - new_args = [results['arg0'].clone(), - Comma(), results['arg1'].clone()] - - # Argument list update: - args.replace(ArgList(new_args)) diff --git a/uncertainties/lib1to2/fixes/fix_ufloat.py b/uncertainties/lib1to2/fixes/fix_ufloat.py deleted file mode 100644 index a6e92f7f..00000000 --- a/uncertainties/lib1to2/fixes/fix_ufloat.py +++ /dev/null @@ -1,105 +0,0 @@ -''' -Fixer for lib2to3. - -Transforms ufloat(tuple,...) and ufloat(string,...) into -ufloat(nominal_value, std_dev,...) and ufloat_fromstr - -(c) 2013 by Eric O. LEBIGOT. -''' - -from lib2to3.fixer_base import BaseFix -from lib2to3.fixer_util import ArgList, Call, Comma, Name, syms - -############################################################################### -# lib2to3 grammar parts. - -#! Warning: indentation is meaningful! - -# (tuple): -tuple_call = """ - trailer< '(' - atom< '(' testlist_gexp< arg0=any ',' arg1=any > ')' > - ')' >""" - -# (tuple, any): -tuple_any_call = """ - trailer< '(' - arglist< - atom< '(' testlist_gexp< arg0=any ',' arg1=any > ')' > - ',' tag=any - > - ')' >""" - - -############################################################################### - -class FixUfloat(BaseFix): - - # Non dotted access, then dotted access. - # Tuple call, then string call. - # No-tag call, then tag call. - PATTERN = """ - power< 'ufloat' {tuple_call} any* > - | - power< 'ufloat' {tuple_any_call} any* > - | - power< 'ufloat' trailer< '(' string=STRING ')' > any* > - | - power< 'ufloat' trailer< '(' - arglist< - string=STRING - ',' tag=any - > - ')' > any* > - | - power< object=NAME trailer< '.' 'ufloat' > {tuple_call} any* > - | - power< object=NAME trailer< '.' 'ufloat' > {tuple_any_call} any* > - | - power< object=NAME trailer< '.' 'ufloat' > - trailer< '(' string=STRING ')' > - any* > - | - power< object=NAME trailer< '.' 'ufloat' > - trailer< '(' arglist< string=STRING ',' tag=any > ')' > - any* > - """.format(tuple_call=tuple_call, - tuple_any_call=tuple_any_call) - - - def transform(self, node, results): - - # Handling of the first argument: - - if 'string' in results: # String as first argument - - new_func_name = 'ufloat_fromstr' - - # New arguments: - new_args=[results['string'].clone()] - - else: # Tuple as first argument - - new_func_name = 'ufloat' - - # New arguments: - new_args = [results['arg0'].clone(), - Comma(), results['arg1'].clone()] - - # Handling of the second argument (call with a tag): - if 'tag' in results: - new_args.extend([Comma(), results['tag'].clone()]) - - if 'object' in results: # If dotted access: unc.ufloat() - func_name = node.children[1].children[1] - args = node.children[2] - else: - func_name = node.children[0] - args = node.children[1] - - # Function name update: - func_name.value = new_func_name - #! func_name.changed() # Necessary when only .value is changed - - # Argument list update: - args.replace(ArgList(new_args)) diff --git a/uncertainties/lib1to2/test_1to2.py b/uncertainties/lib1to2/test_1to2.py deleted file mode 100644 index f23f8cc4..00000000 --- a/uncertainties/lib1to2/test_1to2.py +++ /dev/null @@ -1,207 +0,0 @@ -#!/usr/bin/env python - -''' -Unit tests for the uncertainties.lib1to2 code update package. - -Meant to be run through nosetests. - -(c) 2013-2020 by Eric O. LEBIGOT (EOL). -''' - -# Code inspired by: -# -# - lib2to3.tests.test_fixers.py - -from builtins import str -import sys -import os - -# !! Would it be possible to use an import hook so as to stop the -# import if the Python version is not high enough, instead of having -# like here a whole indented block? - - -if sys.version_info < (2, 7) or "TRAVIS" in os.environ or "APPVEYOR" in os.environ: - - # This package uses lib2to3, which requires Python 2.6+. - - # lib2to3.tests.support is missing from 2.7.3 Travis Python packages. - - # !! Nosetests for Python 2.6 also fails (it looks like it tries - # to run tests via lib2to3/tests/test_refactor.py): - - pass - -else: - - import os - try: - # lib2to3 test support seems to have moved to a new place in 2013: - import test.test_lib2to3.support as support - except ImportError: - # Pre-~2013 path for lib2to3 test support - import lib2to3.tests.support as support - - # The lib1to2.fixes package given to lib2to3 is the *local* package - # (not to another installed module). This is important for the - # __import__() used via support.get_refactorer(). - sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) - - def check_refactor(refactorer, source, expected): - """ - Raises an AssertionError if the given - lib2to3.refactor.RefactoringTool does not refactor 'source' into - 'expected'. - - source, expected -- strings (typically with Python code). - """ - - # !! str() is from future's builtins and is only needed for Python 2, - # where it is mostly equivalent to unicode(): - new = str( - refactorer.refactor_string(support.reformat(source), '')) - - assert support.reformat(expected) == new, ( - "Refactoring failed: '{}' => '{}' instead of '{}'".format( - source, new.strip(), expected)) - - # print 'Checked:', source, '=>', expected - - def check_all(fixer, tests): - ''' - Takes a fixer name (module from fixes) and a mapping that maps - code using the obsolete syntax into updated code, and checks - whether the code is correctly updated. - ''' - - refactorer = support.get_refactorer( - fixer_pkg='lib1to2', fixers=[fixer]) - - for (input_str, out_str) in tests.items(): - check_refactor(refactorer, input_str, out_str) - - def test_fix_std_dev(): - 'Tests the transformation of std_dev() into std_dev.' - - - tests = { - 'x.std_dev()': 'x.std_dev', - 'y.std_dev(); unc.std_dev(z)': 'y.std_dev; unc.std_dev(z)', - 'uncertainties.std_dev(x)': 'uncertainties.std_dev(x)', - 'std_dev(x)': 'std_dev(x)', - 'obj.x.std_dev()': 'obj.x.std_dev', - - """ - long_name.std_dev( - # No argument! - )""": - """ - long_name.std_dev""", - - # set_std_dev => .std_dev: - 'x.set_std_dev(3)': 'x.std_dev = 3', - 'y = set_std_dev(3)': 'y = set_std_dev(3)', # None - 'func = x.set_std_dev': 'func = x.set_std_dev', - 'obj.x.set_std_dev(sin(y))': 'obj.x.std_dev = sin(y)' - } - - check_all('std_dev', tests) - - def test_ufloat(): - ''' - Test of the transformation of ufloat(tuple,...) and - ufloat(string,...) into ufloat(nominal_value, std_dev, tag=...). - ''' - - tests = { - # Tuples: - 'ufloat((3, 0.14))': 'ufloat(3, 0.14)', - 'ufloat((3, 0.14), "pi")': 'ufloat(3, 0.14, "pi")', - "ufloat((3, 0.14), 'pi')": "ufloat(3, 0.14, 'pi')", - "x = ufloat((3, 0.14), tag='pi')": "x = ufloat(3, 0.14, tag='pi')", - - # Simple expressions that can be transformed: - 'ufloat((n, s), tag="var")': 'ufloat(n, s, tag="var")', - - # Simple expressions that cannot be transformed automatically: - 'ufloat(str_repr, tag="var")': 'ufloat(str_repr, tag="var")', - 'ufloat(*tuple_repr, tag="var")': 'ufloat(*tuple_repr, tag="var")', - 'ufloat(*t[0, 0])': 'ufloat(*t[0, 0])', - - # Strings: - 'ufloat("-1.23(3.4)")': 'ufloat_fromstr("-1.23(3.4)")', - "ufloat('-1.23(3.4)')": "ufloat_fromstr('-1.23(3.4)')", - 'ufloat("-1.23(3.4)", "var")': - 'ufloat_fromstr("-1.23(3.4)", "var")', - 'ufloat("-1.23(3.4)", tag="var")': - 'ufloat_fromstr("-1.23(3.4)", tag="var")' - - } - - # Automatic addition of a dotted access: - tests.update(dict( - # !! Dictionary comprehension usable with Python 2.7+ - (orig.replace('ufloat', 'unc.ufloat'), - new.replace('ufloat', 'unc.ufloat')) - for (orig, new) in tests.items())) - - # Test for space consistency: - tests[' t = u.ufloat("3")'] = ' t = u.ufloat_fromstr("3")' - - # Exponentiation test: - tests.update(dict( - # !! Dictionary comprehension usable with Python 2.7+ - (orig+'**2', new+'**2') - for (orig, new) in tests.items())) - - # Exponent test: - tests['2**ufloat("3")'] = '2**ufloat_fromstr("3")' - - # Opposite test: - tests['-ufloat("3")'] = '-ufloat_fromstr("3")' - - check_all('ufloat', tests) - - def test_uarray_umatrix(): - ''' - Test of the transformation of uarray(tuple,...) into - uarray(nominal_values, std_devs). Also performs the same tests - on umatrix(). - ''' - - tests = { - 'uarray((arange(3), std_devs))': 'uarray(arange(3), std_devs)', - 'uarray(tuple_arg)': 'uarray(*tuple_arg)', - # Unmodified, correct code: - 'uarray(values, std_devs)': 'uarray(values, std_devs)', - # Spaces tests: - 'uarray( ( arange(3), std_devs ) ) ': - 'uarray( arange(3), std_devs) ', - 'uarray( tuple_arg )': 'uarray(* tuple_arg)' - - } - - # Automatic addition of a dotted access: - tests.update(dict( - # !! Dictionary comprehension usable with Python 2.7+ - (orig.replace('uarray', 'un.uarray'), - new.replace('uarray', 'un.uarray')) - for (orig, new) in tests.items())) - - # Exponentiation test: - tests.update(dict( - # !! Dictionary comprehension usable with Python 2.7+ - (orig+'**2', new+'**2') - for (orig, new) in tests.items())) - - # Test for space consistency: - tests[' t = u.uarray(args)'] = ' t = u.uarray(*args)' - - # Same tests, but for umatrix: - tests.update(dict( - (orig.replace('uarray', 'umatrix'), - new.replace('uarray', 'umatrix')) - for (orig, new) in tests.items())) - - check_all('uarray_umatrix', tests) - diff --git a/uncertainties/test_uncertainties.py b/uncertainties/test_uncertainties.py index c5ed4ccc..f37d0bbe 100644 --- a/uncertainties/test_uncertainties.py +++ b/uncertainties/test_uncertainties.py @@ -250,39 +250,6 @@ def test_value_construction(): assert x.std_dev == 0.14 assert x.tag == 'pi' - ## Comparison with the obsolete tuple form: - - # The following tuple is stored in a variable instead of being - # repeated in the calls below, so that the automatic code update - # does not replace ufloat((3, 0.14)) by ufloat(3, 14): the goal - # here is to make sure that the obsolete form gives the same - # result as the new form. - - representation = (3, 0.14) # Obsolete representation - - x = ufloat(3, 0.14) - x2 = ufloat(representation) # Obsolete - assert x.nominal_value == x2.nominal_value - assert x.std_dev == x2.std_dev - assert x.tag is None - assert x2.tag is None - - # With tag as positional argument: - x = ufloat(3, 0.14, "pi") - x2 = ufloat(representation, "pi") # Obsolete - assert x.nominal_value == x2.nominal_value - assert x.std_dev == x2.std_dev - assert x.tag == 'pi' - assert x2.tag == 'pi' - - # With tag keyword: - x = ufloat(3, 0.14, tag="pi") - x2 = ufloat(representation, tag="pi") # Obsolete - assert x.nominal_value == x2.nominal_value - assert x.std_dev == x2.std_dev - assert x.tag == 'pi' - assert x2.tag == 'pi' - # Negative standard deviations should be caught in a nice way # (with the right exception): try: @@ -290,12 +257,6 @@ def test_value_construction(): except uncert_core.NegativeStdDev: pass - try: - # Obsolete form: - x = ufloat((3, -0.1)) - except uncert_core.NegativeStdDev: - pass - ## Incorrect forms should not raise any deprecation warning, but ## raise an exception: @@ -389,25 +350,6 @@ def test_ufloat_fromstr(): assert numbers_close(num.std_dev, values[1]) assert num.tag == 'test variable' - ## Obsolete forms - - num = ufloat(representation) # Obsolete - assert numbers_close(num.nominal_value, values[0]) - assert numbers_close(num.std_dev, values[1]) - assert num.tag is None - - # Call with a tag list argument: - num = ufloat(representation, 'test variable') # Obsolete - assert numbers_close(num.nominal_value, values[0]) - assert numbers_close(num.std_dev, values[1]) - assert num.tag == 'test variable' - - # Call with a tag keyword argument: - num = ufloat(representation, tag='test variable') # Obsolete - assert numbers_close(num.nominal_value, values[0]) - assert numbers_close(num.std_dev, values[1]) - assert num.tag == 'test variable' - ############################################################################### # Test of correctness of the fixed (usually analytical) derivatives: @@ -742,14 +684,6 @@ def test_logic(): assert bool(z) == True assert bool(t) == True # Only infinitseimal neighborhood are used -def test_obsolete(): - 'Tests some obsolete creation of number with uncertainties' - x = ufloat(3, 0.1) - # Obsolete function, protected against automatic modification: - x.set_std_dev.__call__(0.2) # Obsolete - - x_std_dev = x.std_dev - assert x_std_dev() == 0.2 # Obsolete call def test_basic_access_to_data(): "Access to data from Variable and AffineScalarFunc objects." diff --git a/uncertainties/unumpy/core.py b/uncertainties/unumpy/core.py index 3507db4d..54addf06 100644 --- a/uncertainties/unumpy/core.py +++ b/uncertainties/unumpy/core.py @@ -25,7 +25,6 @@ # Local modules: import uncertainties.umath_core as umath_core import uncertainties.core as uncert_core -from uncertainties.core import deprecation __all__ = [ # Factory functions: @@ -285,8 +284,7 @@ def uarray(nominal_values, std_devs=None): """ if std_devs is None: # Obsolete, single tuple argument call - deprecation('uarray() should now be called with two arguments.') - (nominal_values, std_devs) = nominal_values + raise TypeError('uarray() should be called with two arguments.') return (numpy.vectorize( # ! Looking up uncert_core.Variable beforehand through @@ -573,28 +571,6 @@ def pinv(array_like, rcond=pinv_default): ########## Matrix class -class CallableStdDevs(numpy.matrix): - ''' - Class for standard deviation results, which used to be - callable. Provided for compatibility with old code. Issues an - obsolescence warning upon call. - - New objects must be created by passing an existing - ''' - - def __new__(cls, matrix): - # The following prevents a copy of the original matrix, which - # could be expensive, and is unnecessary (the CallableStdDevs - # is just a wrapping around the original matrix, which can be - # modified): - matrix.__class__ = cls - return matrix - - def __call__ (self): - deprecation('the std_devs attribute should not be called' - ' anymore: use .std_devs instead of .std_devs().') - return self - class matrix(numpy.matrix): # The name of this class is the same as NumPy's, which is why it # does not follow PEP 8. @@ -639,7 +615,7 @@ def nominal_values(self): # the first ones to have such methods? @property def std_devs(self): - return CallableStdDevs(std_devs(self)) + return numpy.matrix(std_devs(self)) def umatrix(nominal_values, std_devs=None): """ @@ -653,8 +629,7 @@ def umatrix(nominal_values, std_devs=None): """ if std_devs is None: # Obsolete, single tuple argument call - deprecation('umatrix() should now be called with two arguments.') - (nominal_values, std_devs) = nominal_values + raise TypeError('umatrix() should be called with two arguments.') return uarray(nominal_values, std_devs).view(matrix) diff --git a/uncertainties/unumpy/test_unumpy.py b/uncertainties/unumpy/test_unumpy.py index 5923dfd3..5e2147a5 100644 --- a/uncertainties/unumpy/test_unumpy.py +++ b/uncertainties/unumpy/test_unumpy.py @@ -309,23 +309,4 @@ def test_array_comparisons(): # 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]) - -def test_obsolete(): - 'Test of obsolete functions' - - # The new and old calls should give the same results: - - # The unusual syntax is here to protect against automatic code - # update: - arr_obs = unumpy.uarray.__call__(([1, 2], [1, 4])) # Obsolete call - arr = unumpy.uarray([1, 2], [1, 4]) - assert arrays_close(arr_obs, arr) - - # The new and old calls should give the same results: - - # The unusual syntax is here to protect against automatic code - # update: - mat_obs = unumpy.umatrix.__call__(([1, 2], [1, 4])) # Obsolete call - mat = unumpy.umatrix([1, 2], [1, 4]) - assert arrays_close(mat_obs, mat) + assert numpy.all((mat == [mat[0,0], 4]) == [True, False]) \ No newline at end of file From f69d5fcac3692c05bca9515381eaadf8ebe025c2 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 2 Apr 2024 20:33:59 +0100 Subject: [PATCH 09/16] importlib --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index ebb08596..2a14efc5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,3 +60,6 @@ optional = ["numpy"] [tool.pytest.ini_options] testpaths = ["tests"] +addopts = [ + "--import-mode=importlib", +] From 57e2a20b52de934f4a2e52aa8fa9b0f2ad7261d0 Mon Sep 17 00:00:00 2001 From: Will Riley Date: Tue, 2 Apr 2024 21:36:46 +0200 Subject: [PATCH 10/16] Removed unused imported modules from tests. (#210) Removed unused imports in tests. Co-authored-by: andrewgsavage --- uncertainties/test_umath.py | 1 - uncertainties/test_uncertainties.py | 1 - 2 files changed, 2 deletions(-) diff --git a/uncertainties/test_umath.py b/uncertainties/test_umath.py index 687c2028..c8555e7c 100644 --- a/uncertainties/test_umath.py +++ b/uncertainties/test_umath.py @@ -10,7 +10,6 @@ from __future__ import absolute_import # Standard modules -import sys import math # Local modules: diff --git a/uncertainties/test_uncertainties.py b/uncertainties/test_uncertainties.py index f37d0bbe..79c1db90 100644 --- a/uncertainties/test_uncertainties.py +++ b/uncertainties/test_uncertainties.py @@ -18,7 +18,6 @@ from builtins import map from builtins import range import copy -import weakref import math from math import isnan, isinf import random From a58a7e7caee4c8ae088e3ba667959f8a849909f8 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 2 Apr 2024 20:59:26 +0100 Subject: [PATCH 11/16] importlib --- tests/{helpers.py => helpers/__init__.py} | 0 tests/test_uncertainties.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename tests/{helpers.py => helpers/__init__.py} (100%) diff --git a/tests/helpers.py b/tests/helpers/__init__.py similarity index 100% rename from tests/helpers.py rename to tests/helpers/__init__.py diff --git a/tests/test_uncertainties.py b/tests/test_uncertainties.py index ae739656..f1285eb4 100644 --- a/tests/test_uncertainties.py +++ b/tests/test_uncertainties.py @@ -8,7 +8,7 @@ from uncertainties.core import ufloat, AffineScalarFunc, ufloat_fromstr from uncertainties import umath from uncertainties.testing import numbers_close, ufloats_close, compare_derivatives, arrays_close -from helpers import power_special_cases, power_all_cases, power_wrt_ref +from tests.helpers import power_special_cases, power_all_cases, power_wrt_ref def test_value_construction(): From 7a92f057b7e923e07214c114c744c648f322f991 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 2 Apr 2024 21:05:14 +0100 Subject: [PATCH 12/16] add tests.helpers to path --- pyproject.toml | 1 + tests/helpers.py | 152 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 tests/helpers.py diff --git a/pyproject.toml b/pyproject.toml index 2a14efc5..db961655 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,7 @@ Changelog = "https://github.com/lmfit/uncertainties/blob/master/CHANGES.rst" optional = ["numpy"] [tool.pytest.ini_options] +pythonpath = ["tests"] testpaths = ["tests"] addopts = [ "--import-mode=importlib", diff --git a/tests/helpers.py b/tests/helpers.py new file mode 100644 index 00000000..13c55f79 --- /dev/null +++ b/tests/helpers.py @@ -0,0 +1,152 @@ +from uncertainties.core import ufloat +from math import isnan + +def power_all_cases(op): + ''' + Checks all cases for the value and derivatives of power-like + operator op (op is typically the built-in pow(), or math.pow()). + + Checks only the details of special results like 0, 1 or NaN). + + Different cases for the value of x**p and its derivatives are + tested by dividing the (x, p) plane with: + + - x < 0, x = 0, x > 0 + - p integer or not, p < 0, p = 0, p > 0 + + (not all combinations are distinct: for instance x > 0 gives + identical formulas for all p). + ''' + + zero = ufloat(0, 0.1) + zero2 = ufloat(0, 0.1) + one = ufloat(1, 0.1) + positive = ufloat(0.3, 0.01) + positive2 = ufloat(0.3, 0.01) + negative = ufloat(-0.3, 0.01) + integer = ufloat(-3, 0) + non_int_larger_than_one = ufloat(3.1, 0.01) + positive_smaller_than_one = ufloat(0.3, 0.01) + + ## negative**integer + + result = op(negative, integer) + assert not isnan(result.derivatives[negative]) + assert isnan(result.derivatives[integer]) + + # Limit cases: + result = op(negative, one) + assert result.derivatives[negative] == 1 + assert isnan(result.derivatives[one]) + + result = op(negative, zero) + assert result.derivatives[negative] == 0 + assert isnan(result.derivatives[zero]) + + ## negative**non-integer + + ## zero**... + + result = op(zero, non_int_larger_than_one) + assert isnan(result.derivatives[zero]) + assert result.derivatives[non_int_larger_than_one] == 0 + + # Special cases: + result = op(zero, one) + assert result.derivatives[zero] == 1 + assert result.derivatives[one] == 0 + + result = op(zero, 2*one) + assert result.derivatives[zero] == 0 + assert result.derivatives[one] == 0 + + result = op(zero, positive_smaller_than_one) + assert isnan(result.derivatives[zero]) + assert result.derivatives[positive_smaller_than_one] == 0 + + result = op(zero, zero2) + assert result.derivatives[zero] == 0 + assert isnan(result.derivatives[zero2]) + + ## positive**...: this is a quite regular case where the value and + ## the derivatives are all defined. + + result = op(positive, positive2) + assert not isnan(result.derivatives[positive]) + assert not isnan(result.derivatives[positive2]) + + result = op(positive, zero) + assert result.derivatives[positive] == 0 + assert not isnan(result.derivatives[zero]) + + result = op(positive, negative) + assert not isnan(result.derivatives[positive]) + assert not isnan(result.derivatives[negative]) + + +def power_special_cases(op): + ''' + Checks special cases of the uncertainty power operator op (where + op is typically the built-in pow or uncertainties.umath.pow). + + The values x = 0, x = 1 and x = NaN are special, as are null, + integral and NaN values of p. + ''' + + zero = ufloat(0, 0) + one = ufloat(1, 0) + p = ufloat(0.3, 0.01) + + assert op(0, p) == 0 + assert op(zero, p) == 0 + + # The outcome of 1**nan and nan**0 was undefined before Python + # 2.6 (http://docs.python.org/library/math.html#math.pow): + assert op(float('nan'), zero) == 1.0 + assert op(one, float('nan')) == 1.0 + + # …**0 == 1.0: + assert op(p, 0) == 1.0 + assert op(zero, 0) == 1.0 + assert op((-p), 0) == 1.0 + # …**zero: + assert op((-10.3), zero) == 1.0 + assert op(0, zero) == 1.0 + assert op(0.3, zero) == 1.0 + assert op((-p), zero) == 1.0 + assert op(zero, zero) == 1.0 + assert op(p, zero) == 1.0 + + # one**… == 1.0 + assert op(one, -3) == 1.0 + assert op(one, -3.1) == 1.0 + assert op(one, 0) == 1.0 + assert op(one, 3) == 1.0 + assert op(one, 3.1) == 1.0 + + # … with two numbers with uncertainties: + assert op(one, (-p)) == 1.0 + assert op(one, zero) == 1.0 + assert op(one, p) == 1.0 + # 1**… == 1.0: + assert op(1., (-p)) == 1.0 + assert op(1., zero) == 1.0 + assert op(1., p) == 1.0 + +def power_wrt_ref(op, ref_op): + ''' + Checks special cases of the uncertainty power operator op (where + op is typically the built-in pow or uncertainties.umath.pow), by + comparing its results to the reference power operator ref_op + (which is typically the built-in pow or math.pow). + ''' + + # Negative numbers with uncertainty can be exponentiated to an + # integral power: + assert op(ufloat(-1.1, 0.1), -9).nominal_value == ref_op(-1.1, -9) + + # Case of numbers with no uncertainty: should give the same result + # as numbers with uncertainties: + assert op(ufloat(-1, 0), 9) == ref_op(-1, 9) + assert op(ufloat(-1.1, 0), 9) == ref_op(-1.1, 9) + From 4b7ab869c37634b293afbd4afb1772df2916d883 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 2 Apr 2024 21:05:42 +0100 Subject: [PATCH 13/16] add tests.helpers to path --- tests/helpers.py | 152 ----------------------------------------------- 1 file changed, 152 deletions(-) delete mode 100644 tests/helpers.py diff --git a/tests/helpers.py b/tests/helpers.py deleted file mode 100644 index 13c55f79..00000000 --- a/tests/helpers.py +++ /dev/null @@ -1,152 +0,0 @@ -from uncertainties.core import ufloat -from math import isnan - -def power_all_cases(op): - ''' - Checks all cases for the value and derivatives of power-like - operator op (op is typically the built-in pow(), or math.pow()). - - Checks only the details of special results like 0, 1 or NaN). - - Different cases for the value of x**p and its derivatives are - tested by dividing the (x, p) plane with: - - - x < 0, x = 0, x > 0 - - p integer or not, p < 0, p = 0, p > 0 - - (not all combinations are distinct: for instance x > 0 gives - identical formulas for all p). - ''' - - zero = ufloat(0, 0.1) - zero2 = ufloat(0, 0.1) - one = ufloat(1, 0.1) - positive = ufloat(0.3, 0.01) - positive2 = ufloat(0.3, 0.01) - negative = ufloat(-0.3, 0.01) - integer = ufloat(-3, 0) - non_int_larger_than_one = ufloat(3.1, 0.01) - positive_smaller_than_one = ufloat(0.3, 0.01) - - ## negative**integer - - result = op(negative, integer) - assert not isnan(result.derivatives[negative]) - assert isnan(result.derivatives[integer]) - - # Limit cases: - result = op(negative, one) - assert result.derivatives[negative] == 1 - assert isnan(result.derivatives[one]) - - result = op(negative, zero) - assert result.derivatives[negative] == 0 - assert isnan(result.derivatives[zero]) - - ## negative**non-integer - - ## zero**... - - result = op(zero, non_int_larger_than_one) - assert isnan(result.derivatives[zero]) - assert result.derivatives[non_int_larger_than_one] == 0 - - # Special cases: - result = op(zero, one) - assert result.derivatives[zero] == 1 - assert result.derivatives[one] == 0 - - result = op(zero, 2*one) - assert result.derivatives[zero] == 0 - assert result.derivatives[one] == 0 - - result = op(zero, positive_smaller_than_one) - assert isnan(result.derivatives[zero]) - assert result.derivatives[positive_smaller_than_one] == 0 - - result = op(zero, zero2) - assert result.derivatives[zero] == 0 - assert isnan(result.derivatives[zero2]) - - ## positive**...: this is a quite regular case where the value and - ## the derivatives are all defined. - - result = op(positive, positive2) - assert not isnan(result.derivatives[positive]) - assert not isnan(result.derivatives[positive2]) - - result = op(positive, zero) - assert result.derivatives[positive] == 0 - assert not isnan(result.derivatives[zero]) - - result = op(positive, negative) - assert not isnan(result.derivatives[positive]) - assert not isnan(result.derivatives[negative]) - - -def power_special_cases(op): - ''' - Checks special cases of the uncertainty power operator op (where - op is typically the built-in pow or uncertainties.umath.pow). - - The values x = 0, x = 1 and x = NaN are special, as are null, - integral and NaN values of p. - ''' - - zero = ufloat(0, 0) - one = ufloat(1, 0) - p = ufloat(0.3, 0.01) - - assert op(0, p) == 0 - assert op(zero, p) == 0 - - # The outcome of 1**nan and nan**0 was undefined before Python - # 2.6 (http://docs.python.org/library/math.html#math.pow): - assert op(float('nan'), zero) == 1.0 - assert op(one, float('nan')) == 1.0 - - # …**0 == 1.0: - assert op(p, 0) == 1.0 - assert op(zero, 0) == 1.0 - assert op((-p), 0) == 1.0 - # …**zero: - assert op((-10.3), zero) == 1.0 - assert op(0, zero) == 1.0 - assert op(0.3, zero) == 1.0 - assert op((-p), zero) == 1.0 - assert op(zero, zero) == 1.0 - assert op(p, zero) == 1.0 - - # one**… == 1.0 - assert op(one, -3) == 1.0 - assert op(one, -3.1) == 1.0 - assert op(one, 0) == 1.0 - assert op(one, 3) == 1.0 - assert op(one, 3.1) == 1.0 - - # … with two numbers with uncertainties: - assert op(one, (-p)) == 1.0 - assert op(one, zero) == 1.0 - assert op(one, p) == 1.0 - # 1**… == 1.0: - assert op(1., (-p)) == 1.0 - assert op(1., zero) == 1.0 - assert op(1., p) == 1.0 - -def power_wrt_ref(op, ref_op): - ''' - Checks special cases of the uncertainty power operator op (where - op is typically the built-in pow or uncertainties.umath.pow), by - comparing its results to the reference power operator ref_op - (which is typically the built-in pow or math.pow). - ''' - - # Negative numbers with uncertainty can be exponentiated to an - # integral power: - assert op(ufloat(-1.1, 0.1), -9).nominal_value == ref_op(-1.1, -9) - - # Case of numbers with no uncertainty: should give the same result - # as numbers with uncertainties: - assert op(ufloat(-1, 0), 9) == ref_op(-1, 9) - assert op(ufloat(-1.1, 0), 9) == ref_op(-1.1, 9) - From 04070e7d70afad60201d3988015545994ee14276 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 2 Apr 2024 21:06:54 +0100 Subject: [PATCH 14/16] revert importlib --- pyproject.toml | 6 +----- tests/{helpers/__init__.py => helpers.py} | 0 2 files changed, 1 insertion(+), 5 deletions(-) rename tests/{helpers/__init__.py => helpers.py} (100%) diff --git a/pyproject.toml b/pyproject.toml index db961655..5798fcaa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,8 +59,4 @@ Changelog = "https://github.com/lmfit/uncertainties/blob/master/CHANGES.rst" optional = ["numpy"] [tool.pytest.ini_options] -pythonpath = ["tests"] -testpaths = ["tests"] -addopts = [ - "--import-mode=importlib", -] +testpaths = ["tests"] \ No newline at end of file diff --git a/tests/helpers/__init__.py b/tests/helpers.py similarity index 100% rename from tests/helpers/__init__.py rename to tests/helpers.py From a6fe054e2ee1f1528598f10e487c60c2f0fd63fe Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 2 Apr 2024 21:07:18 +0100 Subject: [PATCH 15/16] revert importlib --- tests/test_uncertainties.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_uncertainties.py b/tests/test_uncertainties.py index f1285eb4..ae739656 100644 --- a/tests/test_uncertainties.py +++ b/tests/test_uncertainties.py @@ -8,7 +8,7 @@ from uncertainties.core import ufloat, AffineScalarFunc, ufloat_fromstr from uncertainties import umath from uncertainties.testing import numbers_close, ufloats_close, compare_derivatives, arrays_close -from tests.helpers import power_special_cases, power_all_cases, power_wrt_ref +from helpers import power_special_cases, power_all_cases, power_wrt_ref def test_value_construction(): From 925f74a1c14bf85343594a949f2ca44fb3c29980 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 2 Apr 2024 21:38:08 +0100 Subject: [PATCH 16/16] empty umat --- tests/test_umath.py | 331 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 331 insertions(+) diff --git a/tests/test_umath.py b/tests/test_umath.py index e69de29b..52aa4269 100644 --- a/tests/test_umath.py +++ b/tests/test_umath.py @@ -0,0 +1,331 @@ +import sys +import math +from math import isnan, isinf + +from uncertainties import ufloat +import uncertainties.core as uncert_core +import uncertainties.umath_core as umath_core + +from uncertainties.testing import compare_derivatives, numbers_close +from helpers import power_special_cases, power_all_cases, power_wrt_ref +############################################################################### +# 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_core.many_scalars_to_scalar_funcs: + func = getattr(umath_core, name) + # Numerical derivatives of func: the nominal value of func() results + # is used as the underlying function: + numerical_derivatives = uncert_core.NumericalDerivatives( + lambda *args: func(*args)) + compare_derivatives(func, numerical_derivatives) + + # Functions that are not in umath_core.many_scalars_to_scalar_funcs: + + ## + # modf(): returns a tuple: + def frac_part_modf(x): + return umath_core.modf(x)[0] + def int_part_modf(x): + return umath_core.modf(x)[1] + + compare_derivatives( + frac_part_modf, + uncert_core.NumericalDerivatives( + lambda x: frac_part_modf(x))) + compare_derivatives( + int_part_modf, + uncert_core.NumericalDerivatives( + lambda x: int_part_modf(x))) + + ## + # frexp(): returns a tuple: + def mantissa_frexp(x): + return umath_core.frexp(x)[0] + def exponent_frexp(x): + return umath_core.frexp(x)[1] + + compare_derivatives( + mantissa_frexp, + uncert_core.NumericalDerivatives( + lambda x: mantissa_frexp(x))) + compare_derivatives( + exponent_frexp, + uncert_core.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_core.tan(x) == umath_core.sin(x)/umath_core.cos(x) + + +def test_numerical_example(): + "Test specific numerical examples" + + x = ufloat(3.14, 0.01) + result = umath_core.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_core.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_core.sin() + # does not): + sin_uarray_uncert = numpy.vectorize(umath_core.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(uncert_core.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: + + # !!! The test could be done directly with NumPy's comparison + # tools, no? See assert_allclose, assert_array_almost_equal_nulp + # or assert_array_max_ulp. This is relevant for all vectorized + # occurrences of numbers_close. + + assert numpy.vectorize(numbers_close)( + covariances_this_module, + covariances_samples, + 0.06).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 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_core.sin(3), float) + + # factorial() must not be "damaged" by the umath_core 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_core.factorial(4) == 24 + + # fsum is special because it does not take a fixed number of + # variables: + assert umath_core.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_core.locally_cst_funcs: + + try: + func = getattr(umath_core, name) + except AttributeError: + continue # Not in the math module, so not in umath_core 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 type of the expected exception is first determined, because + # it varies between versions of Python (OverflowError in Python + # 2.6+, ValueError in Python 2.5,...): + try: + math.log(0) + except Exception as err_math: + # Python 3 does not make exceptions local variables: they are + # restricted to their except block: + err_math_args = err_math.args + exception_class = err_math.__class__ + + try: + umath_core.log(0) + except exception_class as err_ufloat: + assert err_math_args == err_ufloat.args + else: + raise Exception('%s exception expected' % exception_class.__name__) + try: + umath_core.log(ufloat(0, 0)) + except exception_class as err_ufloat: + assert err_math_args == err_ufloat.args + else: + raise Exception('%s exception expected' % exception_class.__name__) + try: + umath_core.log(ufloat(0, 1)) + except exception_class as err_ufloat: + assert err_math_args == err_ufloat.args + else: + raise Exception('%s exception expected' % exception_class.__name__) + +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_core.hypot(x, y) + assert isnan(result.derivatives[x]) + assert isnan(result.derivatives[y]) + +def test_power_all_cases(): + ''' + Test special cases of umath_core.pow(). + ''' + power_all_cases(umath_core.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_core.pow(). + ''' + + power_special_cases(umath_core.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) + + # The type of the expected exception is first determined, because + # it varies between versions of Python (OverflowError in Python + # 2.6+, ValueError in Python 2.5,...): + try: + math.pow(0, negative.nominal_value) + except Exception as err_math: + # Python 3 does not make exceptions local variables: they are + # restricted to their except block: + err_math_args = err_math.args + exception_class = err_math.__class__ + + # http://stackoverflow.com/questions/10282674/difference-between-the-built-in-pow-and-math-pow-for-floats-in-python + + try: + umath_core.pow(ufloat(0, 0.1), negative) + except exception_class as err: # "as err", for Python 2.6+ + pass + else: + raise Exception('%s exception expected' % exception_class.__name__) + + try: + result = umath_core.pow(negative, positive) + except exception_class: # Assumed: same exception as for pow(0, negative) + # 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: + raise Exception('%s exception expected' % exception_class.__name__) + +def test_power_wrt_ref(): + ''' + Checks special cases of the umath_core.pow() power operator. + ''' + power_wrt_ref(umath_core.pow, math.pow) \ No newline at end of file