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

pyat ReadWrite Insertion Devices into mat files #597

Merged
merged 52 commits into from
Jul 31, 2023

Conversation

oscarxblanco
Copy link
Contributor

@oscarxblanco oscarxblanco commented Apr 30, 2023

Dear all,

this PR is a workaround to a limitation in read and write capabilities on pyat when adding an Insertion Device map, already mentioned in #558 . The existing InsertionDeviceKickMap class always needed the field map file (here called, IdFile) to be read, and that made it incompatible with the save_mat function.

Here, the class InsertionDeviceKickMap has been modified to a function, and a new class called IdTable is created compatible with the structure stored in .mat files from pyat and matlab. Due to the non-existence of an equivalent class in AT matlab, the insertion device element is saved by changing it into a multipole which exists in both AT versions.

I expect no impact on the users, as the InsertionDeviceKickMap function uses exactly the same call and parameters, and the IdTable is saved in a backwards compatible format through the multipole class (no new elements are created on the .mat output).

The output to .m files is still not compatible. The output from ATmatlab transforms the element into a drift which could change the beam dynamics results when read back. I have done the same on pyAT to keep similar results on both sides, but, I discourage to use .m file for the moment.

The following diagram shows the data flow.

Best regards,
o

sequenceDiagram
    participant IdFile
    participant pyAT
    participant .mat
    participant .m
    participant ATmatlab
    IdFile->> pyAT: InsertionDeviceKickMap (creates IdTable)
    pyAT ->> .mat: at.save_mat(IdTable to multipole)
    .mat ->>pyAT: at.load_mat
    IdFile->>ATmatlab:atidtable_dat
    ATmatlab->>.mat: save (VoidClass)
    .mat->>ATmatlab: load
    ATmatlab->>.m: atwritem(VoidClass to Drift)
    pyAT->>.m: at.save_m(IdTable to Drift)
Loading

@oscarxblanco
Copy link
Contributor Author

Dear @lfarv ,

what is your opinion on this modifications ? Would you consider them ok to review ?
Otherwise I could leave them as a draft for a longer discussion on how to pass new classes from one AT implementation to the other.

Best regards,
o

@swhite2401
Copy link
Contributor

Hello @oscarxblanco , let's wait for @lfarv to be back he may have good suggestions, but this seems very complicated to me.

I have a couple questions:
-would it be useful to add a load_file() function and make the file optional in the constructor, such that in case it is missing you can still build the element?
-it seems the kickmap is saved in the element attributes, in case they already exist (as is the case when loading the element from lattice file I suppose) why do you request the kickmap file to construct the element? In fact the passmethod does not need the file, just the kickmap arrays
-in matlab can't we just write an element creation function to avoid changing the class to multipole?
-for the .m files I think the same comments as above apply and this should work, however the full kickmap will be displayed but that is ok I believe no?

Cheers

@oscarxblanco
Copy link
Contributor Author

Dear @swhite2401

Hello @oscarxblanco , let's wait for @lfarv to be back he may have good suggestions, but this seems very complicated to me.

I have a couple questions: -would it be useful to add a load_file() function and make the file optional in the constructor, such that in case it is missing you can still build the element?

Sure it could be an option that I have not tried out yet.

-it seems the kickmap is saved in the element attributes, in case they already exist (as is the case when loading the element from lattice file I suppose) why do you request the kickmap file to construct the element?

When starting from python it should have some kind of class to be saved. And you are right, the attributes are saved but the mapping from pyat classes to AT matlab elements is still missing.

In fact the passmethod does not need the file, just the kickmap arrays -in matlab can't we just write an element creation function to avoid changing the class to multipole?

Yes, I would also prefer this solution. But, I wrote this work around to avoid changing both pyat at AT matlab at the same time. This of course is possible.

-for the .m files I think the same comments as above apply and this should work, however the full kickmap will be displayed but that is ok I believe no?

Unfortunately not yet, there is a problem when reading back the array from an m file. The memory allocation is not fortran-aligned. I believe this could be solved but I wanted to raise the point before doing more modifications.

Cheers

@swhite2401
Copy link
Contributor

My feeling is that we should go for an option that does not require external files to load a lattice file. I you want I can help with the implementation (after IPAC).

@oscarxblanco oscarxblanco added the WIP work in progress label May 5, 2023
@oscarxblanco
Copy link
Contributor Author

My feeling is that we should go for an option that does not require external files to load a lattice file. I you want I can help with the implementation (after IPAC).

Perfect ! Thank you

@oscarxblanco
Copy link
Contributor Author

Quick update:
This PR is still WIP, but, there is a small update wrt to pyat.

After conversations with @swhite2401 and @simoneliuzzo , the initial proposal to store the ID data in a multipole class is abandoned because it was too misleading.

In tag bd48038
An element with the class name idtable is created, and I already tested writing to and reading from, mat and m files on pyat.
There is still a problem with the Energy parameter that is automatically ignored when reading and therefore needs to be included manually every time the lattice is read.

So, I'm half way through. The pyat modifications work. The matlab implementation of a compatible atidtable is missing. I will solve the issue wrt to matlab, and at the end try to solve the conflict on pyat/at/load/utils.py shown in the automatic check.

o

@swhite2401
Copy link
Contributor

Thanks for the update, I have not arrived to this issue yet but I will soon be able to help if needed!

@oscarxblanco oscarxblanco removed the WIP work in progress label Jul 18, 2023
@oscarxblanco
Copy link
Contributor Author

Dear @lfarv I have implemented the modifications you suggested, and it looks much better.

I attach a zip file with the scripts I used for testing.
saveIDtest02.zip

NOTE:
This pull request addresses the limitation when saving files since #558 .
However, you mentioned additional blocks #558 (comment)

The InsertionDeviceKickMap still does not implement the additional blocks xkick1 and ykick1, and assume they are filled with zeros. I don't have an example of an insertion device that uses these two parameters.

Copy link
Contributor

@lfarv lfarv left a comment

Choose a reason for hiding this comment

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

Taking NumX and NumY out of the element attributes is the main modification, since they appear in many places…

@@ -120,37 +49,156 @@ def set_IdTablePass(self):
def get_PassMethod(self):
return getattr(self, 'PassMethod')
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the interest of these methods compared to the direct access to the attributes:

elem.PassMethod='IdTablePass'

PassMethod is a public attribute. Following this, we could add "set" methods for all attributes: set_length(), get_length(), set_FamName(), etc

It looks redundant to me…

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Dear @lfarv , at the moment of testing the Insertion Device I tipically needed to set the pass method to IdTablePass and DriftPass. I implemented these functions to profit from autocompletion therefore avoiding misspelling errors.

In any case, you are right, get_PassMethod is redudant. I have put a UserWarning about its deprecation. It might be possible to completely remove it in a following version. Or if you see it is better I would remove it now.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, I don' care, it's just unusual but it is harmless!

pyat/at/lattice/idtable_element.py Outdated Show resolved Hide resolved
Comment on lines 26 to 27
'NumX',
'NumY',
Copy link
Contributor

Choose a reason for hiding this comment

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

You don't need to store NumX and NumY as attributes: they can be derived from the shape of xkick. The pass method does not use them, it looks at xkick.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

NumX and Numy were removed

Comment on lines 109 to 110
'NumX', ...
'NumY', ...
Copy link
Contributor

@lfarv lfarv Jul 24, 2023

Choose a reason for hiding this comment

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

Not necessary: see comment above

Copy link
Contributor Author

Choose a reason for hiding this comment

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

NumX and Numy were removed

Comment on lines 65 to 66
Elem.NumX = NumX;
Elem.NumY = NumY;
Copy link
Contributor

@lfarv lfarv Jul 24, 2023

Choose a reason for hiding this comment

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

As above: you don't need to store NumX and NumY: they are not used be the pass method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

NumX and NumY were removed

Comment on lines 182 to 185
if len(args) == 3:
Nslice = args[0]
Filename_in = args[1]
Energy = args[2]
Copy link
Contributor

@lfarv lfarv Jul 24, 2023

Choose a reason for hiding this comment

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

What happens if not ?

You could instead declare from_text_file as:

def from_text_file(self, Nslice, Filename_in, Energy):

which avoids this test and ensures these variables are defined. Also kwargs is unused.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you, this comparison has been removed.

Comment on lines 266 to 273
if len(args) < 13:
# get data from text file
elemargs = self.from_text_file(*args, **kwargs)
else:
# get data from arguments
elemargs = dict(zip(_argnames, args))
elemargs.update(kwargs)
super().__init__(family_name, **elemargs)
Copy link
Contributor

@lfarv lfarv Jul 24, 2023

Choose a reason for hiding this comment

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

        if len(args) < 13:
             # get data from text file
             elemargs = self.from_text_file(*args)
         else:
             # get data from arguments
             elemargs = dict(zip(_argnames, args))
         elemargs.update(kwargs)
         super().__init__(family_name, **elemargs)

kwargs is no more declared in from_text_file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you again, I changed the first line in the code you suggested; length should be larger than 11.

@oscarxblanco oscarxblanco added the WIP work in progress label Jul 25, 2023
@oscarxblanco oscarxblanco removed the WIP work in progress label Jul 25, 2023
else:
# get data from arguments
elemargs = dict(zip(_argnames, args))
elemargs.update(kwargs)
Copy link
Contributor

Choose a reason for hiding this comment

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

My last comment: you can take the elemargs.update(kwargs) statement out of the test, so that it applies in both cases:

    if len(args) < 11:
        # get data from text file
        elemargs = self.from_text_file(*args)
    else:
        # get data from arguments
        elemargs = dict(zip(_argnames, args))
    elemargs.update(kwargs)
    super().__init__(family_name, **elemargs)

Thanks for your patience, now it's ok for me !

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No problem at all. This suggestion is implemented in the last commit.

Copy link
Contributor

@lfarv lfarv left a comment

Choose a reason for hiding this comment

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

Ok for me

@oscarxblanco
Copy link
Contributor Author

@swhite2401 do you have any suggestions ?

@swhite2401
Copy link
Contributor

Hi @oscarxblanco , sorry I am just back from vacations. No objection on my side please go ahead with the merge!

@oscarxblanco
Copy link
Contributor Author

@swhite2401 No problem

@oscarxblanco oscarxblanco merged commit 509f82b into master Jul 31, 2023
@oscarxblanco oscarxblanco deleted the readwrite_ID_in_mat branch July 31, 2023 11:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants