Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strange behavior on copying #260

Open
jagerber48 opened this issue Jul 28, 2024 · 0 comments
Open

Strange behavior on copying #260

jagerber48 opened this issue Jul 28, 2024 · 0 comments

Comments

@jagerber48
Copy link
Contributor

AffineScalarFunc/UFloat has very strange behavior on copying. We discovered this in one of the issues about hash. @newville provided this example clearly showing some of the strange behavior.

>>> from uncertainties import ufloat
>>> import copy
>>> a = ufloat(1, 0.1)
>>> a == a*1.0
True
>>> a == copy.copy(a)
False
>>> a*1.0 == copy.copy(a*1.0)
True
>>> a == copy.copy(a*1.0)
True
>>> a*1.0 == copy.copy(a)
False

This behavior is very confusing to a user, but, looking at the test_copy test, it looks like some of this behavior is desired. There is similar behavior for pickling.

def test_copy():
"Standard copy module integration"
import gc
x = ufloat(3, 0.1)
assert x == x
y = copy.copy(x)
assert x != y
assert not (x == y)
assert y in y.derivatives.keys() # y must not copy the dependence on x
z = copy.deepcopy(x)
assert x != z
# Copy tests on expressions:
t = x + 2 * z
# t depends on x:
assert x in t.derivatives
# The relationship between the copy of an expression and the
# original variables should be preserved:
t_copy = copy.copy(t)
# Shallow copy: the variables on which t depends are not copied:
assert x in t_copy.derivatives
assert uncert_core.covariance_matrix([t, z]) == uncert_core.covariance_matrix(
[t_copy, z]
)
# However, the relationship between a deep copy and the original
# variables should be broken, since the deep copy created new,
# independent variables:
t_deepcopy = copy.deepcopy(t)
assert x not in t_deepcopy.derivatives
assert uncert_core.covariance_matrix([t, z]) != uncert_core.covariance_matrix(
[t_deepcopy, z]
)
# Test of implementations with weak references:
# Weak references: destroying a variable should never destroy the
# integrity of its copies (which would happen if the copy keeps a
# weak reference to the original, in its derivatives member: the
# weak reference to the original would become invalid):
del x
gc.collect()
assert y in list(y.derivatives.keys())

It seems like the targeted behavior involves copies creating new, independent AffineScalarFunc instances but somehow retaining the correlations of the original. It just seems strange.

I would suggest that copies should be equal to the originals and have the exact same correlations as the original. Copies are correlated with originals, copies are correlated with copies etc. This is realized without any extra handling on my rewrite branch which uses immutable UAtoms and UCombos to represent the uncertainty of UFloats. This allows the following diff on test_copy and changes newville's test to

>>> from uncertainties.new import ufloat
>>> import copy
>>> a = ufloat(1, 0.1)
>>> a == a*1.0
True
>>> a == copy.copy(a)
True
>>> a*1.0 == copy.copy(a*1.0)
True
>>> a == copy.copy(a*1.0)
True
>>> a*1.0 == copy.copy(a)
True

Regardless of how the fix is implemented, I think this behavior should be modified to be more intuitive.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant