Add multi-kickmap store to InsertionDeviceKickMap - #1124
Conversation
Add an interface for storing and selecting between multiple named
kickmaps on a single InsertionDeviceKickMap element. This supports
workflows where the same ID element must represent different operating
modes (e.g. different gap settings or energies) without recreating the
element.
New public API
--------------
- add_kickmap(key, nslice, fname, norm_energy)
Reads a kickmap from a file or dict and stores it under a string
key without activating it.
- use_kickmap(key)
Activates a stored kickmap: swaps xtable, ytable, xkick, ykick,
xkick1, ykick1, Nslice and Length to the data under that key.
Raises KeyError if the key is not found.
- active_kickmap (property)
Returns the key of the currently active kickmap, or None.
- list_kickmaps()
Returns the list of stored keys, excluding internal metadata.
The store is held in a plain _kickmap_store dict attribute and is
invisible to AT serialisation. PassMethod (IdTablePass / DriftPass)
is intentionally untouched by use_kickmap.
Also fix docstring: element supports first *and* second order kick maps.
Tests
-----
Add pyat/test/test_idtable_kickmap.py with 19 tests covering:
- store lifecycle (empty store, add, list, active key)
- field swap correctness and exact round-trip restore
- KeyError on missing or empty store
- internal _active sentinel not exposed to users
- add_kickmap does not activate
- PassMethod preserved across use_kickmap calls (both IdTablePass and DriftPass)
- set_DriftPass / set_IdTablePass still work after swapping
- kick arrays unchanged after DriftPass → IdTablePass round-trip
- full cycle: swap → Drift → IdTable → swap
|
@gubaidulinvadim , great ! At first glance it looks nice. It seems to provide the possibility to have different ID models. This is much more than what I understood you requested in #1105 (comment) about swapping tables. |
|
Also, why is the |
I've noticed this yesterday when testing. I need to fix this. The first kickmap (when object is created) is not registered at the moment. It's a bug. |
|
I think that listing the IDs by name leads to several issues. Are you sure this is OK ? |
What do you propose instead? The kickmap filename is also not always unique (and not always intelligible for beam dynamics people). And the "name"/key are up to the user to define however they like it. At SOLEIL, we would typically have names with different polarisations or different gap value. But I'm open to suggestions on how to improve this idea. P.S I can fix any bugs you find tomorrow. |
Here is my suggestion
Also, I noticed that FamName is never updated, is this behaviour on purpose ? I think it is convenient, but, it comes with the risk of not knowing which is the active table in when working in matlab. There might be other solutions, maybe someone else could advise (@lfarv , @swhite2401 ) ?
Ok, no problem. |
|
I think the duplicate name is not possible currently because the name and kickmap parameters are stored in a Python dictionary. When you try to create a duplicate name, it overwrites the existing kickmap. |
Please, do the changes you consider adequate. |
…can be correctly saved and loaded (but in JSON only)
Now on object initiation, a 'default' kickmap is created and registered in kickmap list. The new behaviour sets it as active. So, you can see which kickmap is active and that the list of kickmaps only has a default one.
If you add twice the same key, for example 'LH' and 'LH' the old one will be overwritten. The kickmaps are stored in a simple Python dictionary. The keys of a dictionary are unique.
I do not like an index approach. If someone defines four kickmaps without a name and they are assigned an index, then there's no way for the user to know what each index means (gap value? phase value? polarisation?). But we can implement it if everyone agrees that this is necessary. For Matlab, I do not know anything because I do not intend to ever use it :) Do you think the FamName should always change to reflect that the kickmap is different? I'd imagine changing FamName dynamically is undesirable because it should serve as an ID for the element. I was imagining that an interface similar to mine here can be implemented in Matlab.
I'm working on this and loking for a solution for Matlab formats. There's an issue with saving and loading lattice in .mat and .m formats. The kickmap dictionary is not saved or loaded properly and the information is lost for all kickmaps but the currently active one (it gets loaded as "default"). No issue with JSON format, which seems to be more powerful. |
|
Dear @gubaidulinvadim , typically names are optional in AT. Empty names are allowed for many other elements. It may be that in your case all kickmaps have a non-empty and unique strings to be used as names, but, it would be unusual in AT to force it. There might be another alternative, e.g. if name is not given assign the filename as the kickmap name. This is also a bit risky because you will have to deal with spaces and special characters. |
InsertionDeviceKickMap object in all formats (.m, .mat, .json). Tested in Python that one can save a lattice and load it without loosing kickmap information.
Right now the name is always assigned as 'default' Yes, in the end this is maybe a better solution. I've managed to fix the saving and loading in .m and .mat formats (which have limitations not present in JSON). There's a hack with 100+ lines of code that saves everything as arrays and reconstructs things... I'm not very happy with it. I will try to see if indexing it instead can help. Compare commits e7d4353 (.m, .mat and .json is working) and b4ebb16 (.json is working; .m and .mat are not working). @swhite2401 @lfarv maybe some of you would know a better solution? Or maybe there's a solution on the side of saving and loading the AT lattice. I'm not really familiar with MATLAB formats or their limitations. |
|
@gubaidulinvadim I am just picking up on this. |
Yes, the branch can be tested as is. Either of the last two commits can be tested.
The main issue is saving in .m and .mat files. The new interface can store several kickmaps in an Python dictionary for a single object There's also a question if such an interface needs to be in Matlab or if Python-only changes are accepted. |
Python only changes are acceptable, we already have those for many things. This is true as long as you do not modify passmethod (C) which can be called by either interface, but looking at your PR this is not the case. |
|
For the other problem, I first need to understand your code.... |
The original fix in b4ebb16 is much easier to understand :) |
|
Ok, could you send me a kick map file for testing? |
I've attached the files. The kickmaps do not correspond to anything in SOLEIL II project :) |
|
@gubaidulinvadim, @oscarxblanco , I am going to post questions / comments / suggestions along the way as I test this, here the first few (not necessarily from this PR but since this is the first time I use it here they are): -If the idtable device becomes a list it should be possible to instantiate an empty element and then add tables or give a list of files (similar to a list or an array)
But maybe I also misunderstood some of these things.... more to come |
|
@swhite2401 , you are right about these observations. Most of them are the consequence of me not having much experience with AT when I created this element about 5 years ago, so, I added what I thought could be useful.
I don't think that having a list of tables inside one element is of any advantage wrt creating an element per table and changing the element. However, this is @gubaidulinvadim 's idea and I think he develop it to have a simplified way of simulating several ID types and gaps, making of this element something closer to a library of IDs. On the other hand, it could be a step to consider tracking when ID gaps are constantly changing.
Right, it may be done in this PR ? otherwise I can fix it after this one is merged.
from_user was supposed to provide a way to inspect a table without creating the element. Sometimes the tables are a long to display and the info is difficult to plot. This was a way to read the file and get the data without creating the element. But, I agree that the documentation is not helpful. I could re organize it if not done in this PR.
I thought they might be useful instead of manipulating the pass method names directly, but, this was just my lack of experience with AT. It was difficult to memorize and write many times the correct pass method per element with and without radiation, active or identity or drift, so, I decided to create something that sets the pass method name instead of having to type it every time. |
|
Most of your questions are for the original implementation of @oscarxblanco. I don't mind making the changes in this PR to avoid handling the conflict between changes.
Yes, we can do that and instantiate an ID without a kickmap that will have DriftPass PassMethod.
@oscarxblanco feel free to change the doc, I can also do it in this PR.
@oscarxblanco feel free to change the doc, I can also do it in this PR. from_user wasn't private originally. I was also thinking that it's only ever used in the init. We can make it public if necessary. My idea was that add_kickmap() kind of replaces from_user.
This is from Oscar, but I find it useful. For IDs, one needs to set it to drift to study the impact of an ID and having a method for it is handy. get_Passmethod is maybe not so necessary.
The idea of storing several tables is to have all necessary information in the lattice. And I find it a bit annoying having to create N different objects and swap them in a lattice by hand. But this is maybe my lack of experience with pyAT. |
|
@gubaidulinvadim , I will wait until this PR is closed to make any change that is left pending. |
|
Ok thanks for the clarifications! I am also biased by my usage.... I realize that it is not so easy for first time user to disable an element. It would replace the passmethod by I haven't started looking at your problem though... |
I think a standard enable/disable is good. |
|
Hi both, So I had a quick and the problem seems to come from private attributes, starting with "_", You may simply remove the underscore to make it work, otherwise, you can use |
Ok I will work on that |
I'll try it out and see if this is better than the current solution. |
You can also use |




Adds an interface for storing and selecting between multiple named kickmaps on a single InsertionDeviceKickMap element. This supports workflows where the same ID element must represent different operating modes (e.g. different gap settings or modes (LV/LH/LT/Circular/Elliptical) without recreating the element.
New public API
The store is held in a plain _kickmap_store dict attribute and is invisible to AT serialisation. PassMethod (IdTablePass / DriftPass) is intentionally untouched by use_kickmap.
This allows to do something like:
Also fix the docstring: the element supports first and second-order kick maps.
Tests
Add pyat/test/test_idtable_kickmap.py with 19 tests covering:
Fixes #1105. @oscarxblanco, please have a look and tell me if you like the idea. I want a workflow where I just store the information in the lattice and can swap between different "modes" of the same ID.
Some tests are not very smart (changing the energy of an ID is not very physical, unless you have a ramped "storage" ring)
I would also note that readthedocs documentation of pyAT does not display some of the InsertionDeviceKickMap docstrings correctly.