From d90c541cef90fbee2c77f997c874fd4e8b95aa9f Mon Sep 17 00:00:00 2001 From: Michael Tiemann <72577720+MichaelTiemannOSC@users.noreply.github.com> Date: Wed, 5 Jul 2023 19:44:35 -0400 Subject: [PATCH 01/10] Update core.py Add __hash__ so it works with Pandas, Pint, and Pint-Pandas Signed-off-by: Michael Tiemann <72577720+MichaelTiemannOSC@users.noreply.github.com> --- uncertainties/core.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/uncertainties/core.py b/uncertainties/core.py index 8ed7d433..a3b224ef 100644 --- a/uncertainties/core.py +++ b/uncertainties/core.py @@ -1849,6 +1849,10 @@ def std_dev(self): # Abbreviation (for formulas, etc.): s = std_dev + def __hash__(self): + # Placeholder until we figure out how to really make these hashable + return id(self) + def __repr__(self): # Not putting spaces around "+/-" helps with arrays of # Variable, as each value with an uncertainty is a From 5d429fe50332f23d5f237e83f614c9c80159d969 Mon Sep 17 00:00:00 2001 From: Michael Tiemann <72577720+MichaelTiemannOSC@users.noreply.github.com> Date: Fri, 7 Jul 2023 08:58:41 -0400 Subject: [PATCH 02/10] Implement hash invariant Tweak hash function so that when x==y, hash(x)==hash(y) Test case: ``` import uncertainties from uncertainties import ufloat u = ufloat(1.23, 2.34) v = ufloat(1.23, 2.34) print(f"u{u} == v{v}: {u==v}") print(f"hash(u){hash(u)} == hash(v){hash(v)}: {hash(u)==hash(v)}") print(f"u{u} == (u+u)/2{(u+u)/2}: {u==(u+u)/2}") print(f"hash(u){hash(u)} == hash((u+u)/2){hash((u+u)/2)}: {hash(u)==hash((u+u)/2)}") ``` Signed-off-by: Michael Tiemann <72577720+MichaelTiemannOSC@users.noreply.github.com> --- uncertainties/core.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/uncertainties/core.py b/uncertainties/core.py index a3b224ef..cbd9ae97 100644 --- a/uncertainties/core.py +++ b/uncertainties/core.py @@ -1850,8 +1850,13 @@ def std_dev(self): s = std_dev def __hash__(self): - # Placeholder until we figure out how to really make these hashable - return id(self) + if not self._linear_part.expanded(): + self.format('') + combo = tuple(iter(self._linear_part.linear_combo.items())) + if len(combo) > 1 or combo[0][1] != 1.0: + return hash(combo) + # The unique value that comes from a unique variable (which it also hashes to) + return id(combo[0][0]) def __repr__(self): # Not putting spaces around "+/-" helps with arrays of @@ -2825,7 +2830,16 @@ def __hash__(self): # variables, so they never compare equal; therefore, their # id() are allowed to differ # (http://docs.python.org/reference/datamodel.html#object.__hash__): - return id(self) + + # Also, since the _linear_part of a variable is based on self, we can use + # that as a hash (uniqueness of self), which allows us to also + # preserve the invariance that x == y implies hash(x) == hash(y) + if hasattr(self, '_linear_part'): + if self in iter(self._linear_part.linear_combo.keys()): + return id(tuple(iter(self._linear_part.linear_combo.keys()))[0]) + return hash(self._linear_part) + else: + return id(self) def __copy__(self): """ From 40154ce56582cdcacb2693b9fda530246d4359b1 Mon Sep 17 00:00:00 2001 From: Michael Tiemann <72577720+MichaelTiemannOSC@users.noreply.github.com> Date: Fri, 7 Jul 2023 22:30:09 -0400 Subject: [PATCH 03/10] Fix pickling (broken by last commit) Ensure that we have a linear_combo attribute before trying to look up its keys. Also re-indent so our conditional test is not too long, and add test case to test_uncertainties. Now test_uncertainties passes all 31 tests original tests plus the new one, and test_umath passes all 9 tests. Signed-off-by: Michael Tiemann <72577720+MichaelTiemannOSC@users.noreply.github.com> --- uncertainties/core.py | 11 +++++++---- uncertainties/test_uncertainties.py | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/uncertainties/core.py b/uncertainties/core.py index cbd9ae97..e26fef5d 100644 --- a/uncertainties/core.py +++ b/uncertainties/core.py @@ -2835,11 +2835,14 @@ def __hash__(self): # that as a hash (uniqueness of self), which allows us to also # preserve the invariance that x == y implies hash(x) == hash(y) if hasattr(self, '_linear_part'): - if self in iter(self._linear_part.linear_combo.keys()): - return id(tuple(iter(self._linear_part.linear_combo.keys()))[0]) - return hash(self._linear_part) + if ( + hasattr(self._linear_part, 'linear_combo') + and self in iter(self._linear_part.linear_combo.keys()) + ): + return id(tuple(iter(self._linear_part.linear_combo.keys()))[0]) + return hash(self._linear_part) else: - return id(self) + return id(self) def __copy__(self): """ diff --git a/uncertainties/test_uncertainties.py b/uncertainties/test_uncertainties.py index c5ed4ccc..01ba1fdf 100644 --- a/uncertainties/test_uncertainties.py +++ b/uncertainties/test_uncertainties.py @@ -2404,3 +2404,21 @@ def test_correlated_values_correlation_mat(): assert arrays_close( numpy.array(cov_mat), numpy.array(uncert_core.covariance_matrix([x2, y2, z2]))) + + def test_hash(): + ''' + Tests the invariance that if x==y, then hash(x)==hash(y) + ''' + + x = ufloat(1.23, 2.34) + y = ufloat(1.23, 2.34) + # nominal values and std_dev terms are equal, but... + assert x.n==y.n and x.s==y.s + # ...x and y are independent variables, therefore not equal as uncertain numbers + assert x != y + assert hash(x) != hash(y) + + # the equation (2x+x)/3 is equal to the variable x, so... + assert ((2*x+x)/3)==x + # ...hash of the equation and the variable should be equal + assert hash((2*x+x)/3)==hash(x) From 634db477c84e3ef28bb69885f66966a83d4bbcce Mon Sep 17 00:00:00 2001 From: Michael Tiemann <72577720+MichaelTiemannOSC@users.noreply.github.com> Date: Sat, 8 Jul 2023 15:52:09 -0400 Subject: [PATCH 04/10] Improve efficiency of AffineScalarFunc hash Call `expand` directly if self._linear_part is not yet expanded. Signed-off-by: Michael Tiemann <72577720+MichaelTiemannOSC@users.noreply.github.com> --- uncertainties/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uncertainties/core.py b/uncertainties/core.py index e26fef5d..113f3e3a 100644 --- a/uncertainties/core.py +++ b/uncertainties/core.py @@ -1851,7 +1851,7 @@ def std_dev(self): def __hash__(self): if not self._linear_part.expanded(): - self.format('') + self._linear_part.expand() combo = tuple(iter(self._linear_part.linear_combo.items())) if len(combo) > 1 or combo[0][1] != 1.0: return hash(combo) From 7cca18c49117f358e945672675d6fa1c61a2c6df Mon Sep 17 00:00:00 2001 From: Michael Tiemann <72577720+MichaelTiemannOSC@users.noreply.github.com> Date: Tue, 11 Jul 2023 12:37:35 -0400 Subject: [PATCH 05/10] Update appveyor.yml Attempt to work around old version of nose that doesn't work with later versions of Python. Will it work? Signed-off-by: Michael Tiemann <72577720+MichaelTiemannOSC@users.noreply.github.com> --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index c9adde86..d3c3f3f4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -47,7 +47,7 @@ install: - conda init cmd.exe - conda info -a # Create a conda virtual environement - - "conda create -n uncty-env numpy nose python=%PYTHON_VERSION%" + - "conda create -n uncty-env numpy nose=1.3.7=py_1006 python=%PYTHON_VERSION%" - activate uncty-env From af3447ccce6cf0523423b81263428afddf52b4b2 Mon Sep 17 00:00:00 2001 From: Michael Tiemann <72577720+MichaelTiemannOSC@users.noreply.github.com> Date: Tue, 11 Jul 2023 12:45:20 -0400 Subject: [PATCH 06/10] Revert "Update appveyor.yml" This reverts commit 7cca18c49117f358e945672675d6fa1c61a2c6df. --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index d3c3f3f4..c9adde86 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -47,7 +47,7 @@ install: - conda init cmd.exe - conda info -a # Create a conda virtual environement - - "conda create -n uncty-env numpy nose=1.3.7=py_1006 python=%PYTHON_VERSION%" + - "conda create -n uncty-env numpy nose python=%PYTHON_VERSION%" - activate uncty-env From f3cb61500b8ddcc957a1dcb24c7610c9d43af39f Mon Sep 17 00:00:00 2001 From: Michael Tiemann <72577720+MichaelTiemannOSC@users.noreply.github.com> Date: Tue, 11 Jul 2023 12:59:31 -0400 Subject: [PATCH 07/10] Replace nose with pytest Attempt replacing `nose` (which has not been regularly supported for years) with `pytest` for better CI/CD experience. Signed-off-by: Michael Tiemann <72577720+MichaelTiemannOSC@users.noreply.github.com> --- INSTALL.txt | 8 +------- appveyor.yml | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/INSTALL.txt b/INSTALL.txt index 1aa398cc..bf3c045e 100644 --- a/INSTALL.txt +++ b/INSTALL.txt @@ -19,10 +19,4 @@ or, if additional access rights are needed (Unix): testing framework. This can be achieved for instance with a command like - nosetests -sv uncertainties/ - -or simply - - nosetests uncertainties/ - -(for a less verbose output). + pytest uncertainties/ diff --git a/appveyor.yml b/appveyor.yml index c9adde86..5833e5aa 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -47,7 +47,7 @@ install: - conda init cmd.exe - conda info -a # Create a conda virtual environement - - "conda create -n uncty-env numpy nose python=%PYTHON_VERSION%" + - "conda create -n uncty-env numpy pytest python=%PYTHON_VERSION%" - activate uncty-env diff --git a/setup.py b/setup.py index 1fdce805..f2d7e5bb 100755 --- a/setup.py +++ b/setup.py @@ -339,7 +339,7 @@ install_requires=['future'], - tests_require=['nose', 'numpy'], + tests_require=['pytest', 'numpy'], # Optional dependencies install using: # `easy_install uncertainties[optional]` From 5e40c49496a56e04d0a9d28faa7647c2ad4395af Mon Sep 17 00:00:00 2001 From: Michael Tiemann <72577720+MichaelTiemannOSC@users.noreply.github.com> Date: Tue, 11 Jul 2023 13:08:56 -0400 Subject: [PATCH 08/10] Revert "Replace nose with pytest" This reverts commit f3cb61500b8ddcc957a1dcb24c7610c9d43af39f. --- INSTALL.txt | 8 +++++++- appveyor.yml | 2 +- setup.py | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/INSTALL.txt b/INSTALL.txt index bf3c045e..1aa398cc 100644 --- a/INSTALL.txt +++ b/INSTALL.txt @@ -19,4 +19,10 @@ or, if additional access rights are needed (Unix): testing framework. This can be achieved for instance with a command like - pytest uncertainties/ + nosetests -sv uncertainties/ + +or simply + + nosetests uncertainties/ + +(for a less verbose output). diff --git a/appveyor.yml b/appveyor.yml index 5833e5aa..c9adde86 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -47,7 +47,7 @@ install: - conda init cmd.exe - conda info -a # Create a conda virtual environement - - "conda create -n uncty-env numpy pytest python=%PYTHON_VERSION%" + - "conda create -n uncty-env numpy nose python=%PYTHON_VERSION%" - activate uncty-env diff --git a/setup.py b/setup.py index f2d7e5bb..1fdce805 100755 --- a/setup.py +++ b/setup.py @@ -339,7 +339,7 @@ install_requires=['future'], - tests_require=['pytest', 'numpy'], + tests_require=['nose', 'numpy'], # Optional dependencies install using: # `easy_install uncertainties[optional]` From cd3b7e0fa414c0db485e9f54faf7954982adfdca Mon Sep 17 00:00:00 2001 From: Michael Tiemann <72577720+MichaelTiemannOSC@users.noreply.github.com> Date: Tue, 11 Jul 2023 13:12:42 -0400 Subject: [PATCH 09/10] Update appveyor.yml Try running nosetests directly, rather than via setuptools, which trips up nose-1.3.7 for python >= 3.6. See https://github.com/nose-devs/nose/issues/873 Signed-off-by: Michael Tiemann <72577720+MichaelTiemannOSC@users.noreply.github.com> --- appveyor.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index c9adde86..d53334aa 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -56,4 +56,5 @@ test_script: - "cd C:\\projects\\uncertainties" - activate uncty-env # Activate the virtual environment - python setup.py egg_info - - python setup.py nosetests -sv + # Try to work around nose-1.3.7 not working with modern setuptools (> Python 3.5) + - nosetests -sv uncertainties/ From a2d4bb1a980805a2b3872ce97fabae7356083294 Mon Sep 17 00:00:00 2001 From: Michael Tiemann <72577720+MichaelTiemannOSC@users.noreply.github.com> Date: Tue, 11 Jul 2023 13:34:56 -0400 Subject: [PATCH 10/10] Update appveyor.yml Install `future` to satisfy `builtins` for Python 2.7. Signed-off-by: Michael Tiemann <72577720+MichaelTiemannOSC@users.noreply.github.com> --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index d53334aa..c7a697fa 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -47,7 +47,7 @@ install: - conda init cmd.exe - conda info -a # Create a conda virtual environement - - "conda create -n uncty-env numpy nose python=%PYTHON_VERSION%" + - "conda create -n uncty-env future numpy nose python=%PYTHON_VERSION%" - activate uncty-env