diff --git a/appveyor.yml b/appveyor.yml index c9adde86..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 @@ -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/ diff --git a/uncertainties/core.py b/uncertainties/core.py index 8ed7d433..113f3e3a 100644 --- a/uncertainties/core.py +++ b/uncertainties/core.py @@ -1849,6 +1849,15 @@ def std_dev(self): # Abbreviation (for formulas, etc.): s = std_dev + def __hash__(self): + if not self._linear_part.expanded(): + 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) + # 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 # Variable, as each value with an uncertainty is a @@ -2821,7 +2830,19 @@ 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 ( + 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) 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)