Skip to content

SerializedMagnets: get/set are not consitent and design/live disagree #461

Description

@GamelinAl

Serialized magnets (N magnets on one power supply: pyaml.magnet.serialized_magnet + LinearSerializedMagnetModel) do not follow one consistent convention. Depending on the accessor and the mode, a value means one magnet, the sum over the family, or the family total split by magnet length. As a result:

  • x.set(x.get()) changes the machine for several accessors, including writing N × the current to the power supply in live
  • the same call gives different results in design and live, often by a factor N.

This was found during a control-room test at SOLEIL. The S9 family (16 sextupoles, one PS) read family.hardware.get() == -3512 A while the PS showed −219 A.

This issue assume that the #458 issue is fixed by #460

Proposed behavior

We need to agree on what the different accessors should do for serialized magnets.

Serialized magnets have a single degree of freedom, the PS current I. Magnet i has strength k_i = f_i(I), where f_i is sub-model i.

Accessor get() set(x)
family.hardware I write I = x
family.strength total integrated strength S = Σ f_i(I) solve Σ f_i(I) = x for one I, write it
magnet[i].hardware I write I = x (all magnets follow)
magnet[i].strength k_i = f_i(I) write I = f_i⁻¹(x); the others follow as f_j(I). No length weighting.

@simoneliuzzo @gubaidulinvadim @TeresiaOlsson @kparasch @JeanLucPons @gupichon-soleil @Amoutardier
I need your help to see if we agree on this before this can be corrected.

Once we agree I'll make sub issues so we can start to fix this.

Activity

  1. added theissue type on Oct 1, 2026
  2. kparasch commented on Oct 1, 2026

    @kparasch
    Member

    I agree except on the family.strength. I don't see any use-case for the total integrated strength.

    Typically, all magnets that are serialized are the same type of magnets so they share the I = f(x) relation. There it would make sense for family.strength to return the strength that is equal to all magnets.

    For the funky case where the magnets don't have the same length, controlling by strength sounds awkward to me.

    Perhaps a compromise could be to have family.strength equal to average strength S = Σ f_i(I) / N ?
    Then at least in the typical case, family.strength falls back to the strength that is equal to all magnets.
    The same follows for its set logic, solve Σ f_i(I) / N = x for one I, write it

  3. TeresiaOlsson commented on Oct 1, 2026

    @TeresiaOlsson
    Member

    I don't think it is guaranteed that I = f(x) is the same for shared magnets? I think there are labs which measure all magnets and have a separate excitation curve per magnet despite that they are powered in series.

    I like the idea to have family.strength return the average strength. Then if you want to know the strength of the individual magnet you need to do magnet.strength instead.

  4. kparasch commented on Oct 1, 2026

    @kparasch
    Member

    I don't think it is guaranteed that I = f(x) is the same for shared magnets? I think there are labs which measure all magnets and have a separate excitation curve per magnet despite that they are powered in series.

    You are right, then I = f(x) is still approximately correct. I would still think that S = Σ f_i(I) / N is sensible in that case.

  5. TeresiaOlsson commented on Oct 1, 2026

    @TeresiaOlsson
    Member

    I don't think it is guaranteed that I = f(x) is the same for shared magnets? I think there are labs which measure all magnets and have a separate excitation curve per magnet despite that they are powered in series.

    You are right, then I = f(x) is still approximately correct. I would still think that S = Σ f_i(I) / N is sensible in that case.

    I agree. Then you can use get and set on the family level to set the average and if you care about the individual strengths you can still read them on the magnet level. And if you at some point in the future buy more power supplies and start to split up families the values will not change much. You just gain the freedom to being able to control the individual strengths and not just read them.

    Returning the total integrated strength I don't really understand. That doesn't sound to me like a useful parameter?

  6. GamelinAl commented on Oct 1, 2026

    @GamelinAl
    MemberAuthor

    I agree with you both, it make sense to go this way. The table is now:

    Accessor get() set(x)
    family.hardware I write I = x
    family.strength average strength S = Σ f_i(I) / N solve Σ f_i(I) / N = x for one I, write it
    magnet[i].hardware I write I = x (all magnets follow)
    magnet[i].strength k_i = f_i(I) write I = f_i⁻¹(x); the others follow as f_j(I). No length weighting.

    From this definition, do we agree that tuning tools should use the family, not the individual magnets?
    Should we provide an accessor to the total integrated strength? It would avoid having to do family.strength * len(magnets_inside_fam) and would make clear that family.strength is actually the average strength.

  7. kparasch commented on Oct 1, 2026

    @kparasch
    Member

    From this definition, do we agree that tuning tools should use the family, not the individual magnets?

    I think this is important and I agree but it is not a simple matter.

    From the point of view of configuration, the most straightforward way would be to use arrays. I am not fully up to date with how arrays work. Maybe it is already possible, but I believe it may be ok if an array can contain both individually-powered devices and a serialized family. (Here I mean that set/get of strength/hardware can be done on the array.)

    For example, imagine that to control the tune, you are using 8 different power converters attached to 8 quadrupole families. It should be possible to have a configuration that is not more complex than this one:

    - type: pyaml.tuning_tools.tune
      name: DEFAULT_TUNE_CORRECTION
      quad_array_name: QForTune
      betatron_tune_name: BETATRON_TUNE
      response_matrix: ${path:trm.json}
    
  8. TeresiaOlsson commented on Oct 1, 2026

    @TeresiaOlsson
    Member

    From this definition, do we agree that tuning tools should use the family, not the individual magnets?

    I think this is important and I agree but it is not a simple matter.

    From the point of view of configuration, the most straightforward way would be to use arrays. I am not fully up to date with how arrays work. Maybe it is already possible, but I believe it may be ok if an array can contain both individually-powered devices and a serialized family. (Here I mean that set/get of strength/hardware can be done on the array.)

    For example, imagine that to control the tune, you are using 8 different power converters attached to 8 quadrupole families. It should be possible to have a configuration that is not more complex than this one:

    - type: pyaml.tuning_tools.tune
      name: DEFAULT_TUNE_CORRECTION
      quad_array_name: QForTune
      betatron_tune_name: BETATRON_TUNE
      response_matrix: ${path:trm.json}
    

    I thought the specification was that arrays and elements should provide the same interface? So if the tuning tool makes get and set it doesn't matter if it's on an array or an element? It doesn't really make sense to me that you need to make arrays that just contain a single element if all your magnets are individually powered.

  9. kparasch commented on Oct 1, 2026

    @kparasch
    Member

    From this definition, do we agree that tuning tools should use the family, not the individual magnets?

    I think this is important and I agree but it is not a simple matter.
    From the point of view of configuration, the most straightforward way would be to use arrays. I am not fully up to date with how arrays work. Maybe it is already possible, but I believe it may be ok if an array can contain both individually-powered devices and a serialized family. (Here I mean that set/get of strength/hardware can be done on the array.)
    For example, imagine that to control the tune, you are using 8 different power converters attached to 8 quadrupole families. It should be possible to have a configuration that is not more complex than this one:

    - type: pyaml.tuning_tools.tune
      name: DEFAULT_TUNE_CORRECTION
      quad_array_name: QForTune
      betatron_tune_name: BETATRON_TUNE
      response_matrix: ${path:trm.json}
    

    I thought the specification was that arrays and elements should provide the same interface? So if the tuning tool makes get and set it doesn't matter if it's on an array or an element? It doesn't really make sense to me that you need to make arrays that just contain a single element if all your magnets are individually powered.

    If arrays and elements can provide the same interface, then great.

  10. GamelinAl commented on Oct 1, 2026

    @GamelinAl
    MemberAuthor

    Sorry my question was too imprecise. I'll try to make it better and form a proposal.

    Arrays mixing individual magnets and a serialized family

    • pyaml.arrays.magnet can mix individually powered magnets with the individual magnets of a series (N entries for one PS) which are treated as the virtual magnets of CFM magnets.
    • As of today, a pyaml.arrays.magnet cannot hold the serialized magnet family itself.
    • pyaml.arrays.serialized_magnet can hold serialized_magnet families only.
    • The tune tools only accept a magnet array. A serialized-magnet array fails.

    So today the only way to put a serialized family in a tuning tool is through its virtual magnets (i.e. individual component).

    Proposal:

    • allow to have a serialized magnet family in a pyaml.arrays.magnet
    • forbid to have a pyaml.arrays.magnet with both a serialized magnet family and its own individual magnets in the same array.
    • allow the tunning tools to take a pyaml.arrays.serialized_magnet as input

    Tune correction example

    Imagine the following measurement, using the same lattice but ran with different configuration of the quadrupoles:
    Measure the response matrix with tune_response_matrix, then call tune.add([0.01, 0.005]).

    Setup Achieved ΔQ (target [0.01, 0.005])
    8 individually powered quads (reference) ✔
    Series as 4 virtual magnets + 4 individually powered quads ✘
    A single virtual magnet + 4 individually powered quads ✔

    The second case will not work with what we proposed so far:

    • Stepping one virtual magnet moves the whole series, so each of the N columns is the full family response.
    • The pseudo-inverse treats the N columns as independent knobs and gives each 1/N of the family correction.
    • Since there is only one PS, the series moves by only that 1/N.
    • Averaging the requested strengths gives the same result, because the N requests are identical.

    Proposal:
    Tuning tools need one knob per power supply, as in MML, where chromaticity and response matrices work on families:

    • Input arrays to tuning tool can be a mix of individual magnets and serialized family.
    • If the an array that holds both individual magnets and member of serialized families is given to a tuning tool, that array is translated to a new array which contain the individual magnets and the serialized families.
  11. TeresiaOlsson commented on Oct 2, 2026

    @TeresiaOlsson
    Member

    Hmm... I think none of those options are really good but rather a review is needed of how the serialized magnets have been implemented and see if it can be simplified or done in some other way. Maybe by looking at how magnets are linked together in the twin. Last time I tested them it was not possible to configure and use them in the way we need for BESSY II (or at least I couldn't understand how). But after we decided to use our own ophyd-async devices instead of the common pyAML devices I have not looked at it further.

    I have still not understood why it is needed to have separate classes instead of just defining an individual magnet and if it's serialized link it to a power supply object which is the same for several magnets. For me the serialized magnet at the moment is a bit of a weird hybrid of an individual magnet and an array and if feels like it should be possible to make a simplified implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingenhancementNew feature or request

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions