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

SurfaceRZFourier cache isn't invalidated by setting rc, zs, rs, zc arrays #465

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

missing-user
Copy link
Contributor

@missing-user missing-user commented Dec 18, 2024

There are not setter properties for the arrays rc, zs, rs, zc, so recompute_bell() is not triggered by overwriting them (there is of course setter properties for individual elements tho).

For example, this means that plots, or depending objective functions aren't updated when overwriting the entire rc array, e.g. to scale a configuration by a constant factor. The following example script will plot the same torus twice, eventho the coefficients were changed (same problem with ).

from simsopt import geo
import matplotlib.pyplot as plt
s = geo.SurfaceRZFourier(3, True, 5, 8)
s.make_rotating_ellipse(1, .2, .5, .1)
s.plot()
plt.show()
scaling = 10
s.rc = s.rc * scaling # This doesn't invalidate the cache
s.zc *= scaling         # or equivalently
s.plot()
plt.show()

can now be done with

from simsopt import geo
import matplotlib.pyplot as plt
s = geo.SurfaceRZFourier(3, True, 5, 8)
s.make_rotating_ellipse(1, .2, .5, .1)
s.plot()
plt.show()
scaling = 10
s.rc_array = s.rc * scaling
s.zc_array *= scaling
s.plot()
plt.show()

My suggestion is to either

  • Add array setter properties to the fourier components of the surface (this PR)
  • Refactor SurfaceRZFourier to have private _rc and property decorators for the accessors rc, but that would also involve changing the CPP class and seems quite invasive
  • Add a short warning to the set_zs() docstring:
    """Modifyting the zs array directly is discouraged, since it doesn't trigger the recompute_bell(). """

Copy link
Contributor

@andrewgiuliani andrewgiuliani left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice idea!

what about setting splices of rc, zs which is a common use case (on my end at least). Do the set_rc, set_rs function take in ranges as well?

unit tests are needed too

@missing-user
Copy link
Contributor Author

missing-user commented Jan 9, 2025

Neither the set_rc style methods, nor my proposed methods support this properly, which I agree is a major shortcoming. Setting a range of elements is just too common to ignore.

Technically set_rc can take an explicitly constructed slice(start, end) object to address a range, but only for m since it doesn't support the + self.ntor operation. It definitely does not support the expected set_rc(start:end, 0, value) syntax.

I believe this could be implemented by overloading the __setitem__ method, but this changes the type of rc, zs and seems quite invasive.

class ObservableArray(np.ndarray):
    def __new__(cls, array, callback=None):
        obj = np.asarray(array).view(cls)
        obj._callback = callback
        return obj

    # supports slice syntax as well
    def __setitem__(self, key, value):
        super().__setitem__(key, value)
        if self._callback:
            self._callback()

https://numpy.org/doc/stable/user/basics.subclassing.html#view-casting
https://numpy.org/doc/stable/user/basics.dispatch.html#
I am unsure what the best way to handle this, but in principle such an observable array class would be useful in a lot of other places too.

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

Successfully merging this pull request may close these issues.

2 participants