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

VariableMultipole compatibility between matlab AT and pyat #839

Open
wants to merge 134 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
134 commits
Select commit Hold shift + click to select a range
17219e7
only create attributes if self is not an element
oscarxblanco Oct 3, 2024
84d3bf8
import numpy as np
oscarxblanco Oct 3, 2024
ce697be
isort
oscarxblanco Oct 3, 2024
7c3949d
black
oscarxblanco Oct 3, 2024
51db2ce
do flake8 easy fix; many are left
oscarxblanco Oct 3, 2024
f6dae67
fixing when saved in .mat
oscarxblanco Oct 4, 2024
6ced948
fix FamName when reading from matlab
oscarxblanco Oct 4, 2024
7a12dc4
fix read element from matlab
oscarxblanco Oct 4, 2024
05c0e82
fix max order in matlab
oscarxblanco Oct 4, 2024
aa71888
fixing flake8
oscarxblanco Oct 4, 2024
032c9da
fixing flake8
oscarxblanco Oct 4, 2024
e19c8f3
add check isempty
oscarxblanco Oct 4, 2024
ad441ba
matlab index starts in 1, obviously
oscarxblanco Oct 4, 2024
9e62915
fix help matlab
oscarxblanco Oct 4, 2024
59aeee3
fix doc string
oscarxblanco Oct 4, 2024
58d703b
fix help
oscarxblanco Oct 8, 2024
5115496
testing function derivatives
oscarxblanco Nov 18, 2024
d899d08
testing function derivatives
oscarxblanco Nov 18, 2024
93f6bb5
adding function derivatives wrt to tau
oscarxblanco Nov 19, 2024
97854d8
add Func derivatives wrt tau
oscarxblanco Nov 19, 2024
7c405e2
improving doc of SINE mode
oscarxblanco Dec 2, 2024
b531d4e
removing mode from vararging
oscarxblanco Dec 2, 2024
3ffa2f7
remove mode from varargsin
oscarxblanco Dec 2, 2024
79351f2
add docstring SINE mode
oscarxblanco Dec 2, 2024
07fc985
add docstring SINE mode
oscarxblanco Dec 2, 2024
aedf3ab
import VariableMultipole from variable_elements
oscarxblanco Dec 2, 2024
7c4361a
mode needs to be defined
oscarxblanco Dec 2, 2024
0f55260
import variable_elements; and defines pass method mappiing
oscarxblanco Dec 2, 2024
fb889ae
test
oscarxblanco Dec 3, 2024
c089301
separate amplitude and other parameters
oscarxblanco Dec 4, 2024
9d1412c
set parameters only for A or B
oscarxblanco Dec 5, 2024
aa1d905
fixing bugs
oscarxblanco Dec 12, 2024
5630fa5
add apertures and misalignments
oscarxblanco Dec 12, 2024
59adb54
add R1, R2, T1, T2, RApertures, EApertures
oscarxblanco Dec 12, 2024
6d88ad6
adds aperture, rotation and translations
oscarxblanco Dec 13, 2024
9d80e37
add mode
oscarxblanco Dec 13, 2024
67fb8dc
updating element
oscarxblanco Dec 13, 2024
c7197dd
fixing datatype
oscarxblanco Dec 13, 2024
4168268
black
oscarxblanco Dec 13, 2024
a272b79
improve docstring
oscarxblanco Dec 13, 2024
be0f0a4
Merge branch 'master' into fix_bug_pyat_variablemultipole
oscarxblanco Dec 13, 2024
a1045d7
modify comments
oscarxblanco Dec 16, 2024
a87c6c7
add mean and std on random function
oscarxblanco Dec 16, 2024
39c37d6
set mean and std on random whitenoise
oscarxblanco Dec 16, 2024
2d07122
remove MaxOrder from user input
oscarxblanco Dec 16, 2024
d11fb99
set white noise
oscarxblanco Dec 16, 2024
e0ee739
remove periodic from definition
oscarxblanco Dec 16, 2024
9227f95
testting random generator
oscarxblanco Dec 16, 2024
6a219b4
debugging mean and std
oscarxblanco Dec 16, 2024
d05627a
debugging seed of random number generator
oscarxblanco Dec 16, 2024
a4c66f1
add seed initial state
oscarxblanco Dec 17, 2024
287bb37
add seed initial state
oscarxblanco Dec 17, 2024
731b52b
add seed initial state
oscarxblanco Dec 17, 2024
74555bd
include comments
oscarxblanco Dec 17, 2024
9a804f2
set Periodic True default
oscarxblanco Dec 17, 2024
680efdb
fixing bug in conversions
oscarxblanco Dec 17, 2024
e1308ea
using Param->thread_rng
oscarxblanco Dec 18, 2024
ae9b0e1
Merge branch 'master' into fix_bug_pyat_variablemultipole
oscarxblanco Dec 18, 2024
a524815
removing seed; add comments; add Sinlimit
oscarxblanco Dec 19, 2024
f84c550
sinlimit > or =
oscarxblanco Dec 19, 2024
c0de957
remove seed; add sinlimit
oscarxblanco Dec 19, 2024
4f13031
add Sinlimit; remove seed
oscarxblanco Dec 19, 2024
1cd90d8
use pcg32_global in AT matlab
oscarxblanco Dec 19, 2024
13f1cad
black
oscarxblanco Dec 19, 2024
94d5837
get frequency
oscarxblanco Dec 19, 2024
68ff29e
fix bug in arbitrary function
oscarxblanco Dec 19, 2024
0a6e2f5
adding ACMode
oscarxblanco Dec 19, 2024
ed6be2c
import all from VariableMultipole
oscarxblanco Dec 19, 2024
8f1e7f3
improve help
oscarxblanco Dec 19, 2024
93ba719
improve DocSgtring; black
oscarxblanco Dec 19, 2024
40bf18b
fixing flake8
oscarxblanco Dec 19, 2024
aa00a4f
flake8
oscarxblanco Dec 19, 2024
5f704f7
branchless if
oscarxblanco Dec 20, 2024
cfb48be
removing mean and std value in white noise
oscarxblanco Jan 7, 2025
8da79fb
removing mean and std from white noise
oscarxblanco Jan 7, 2025
5aa0b0e
removing mean and std from white noise
oscarxblanco Jan 7, 2025
c631ae8
switch to AtWarning when amplitude is used instead of Amplitude
oscarxblanco Jan 7, 2025
7f5f574
set default mode
oscarxblanco Jan 7, 2025
3716abf
removing any; using Any
oscarxblanco Jan 7, 2025
5fecb70
setdefault Phase, SinLimit
oscarxblanco Jan 7, 2025
d0cb8f0
remove set amplitude
oscarxblanco Jan 7, 2025
acfb812
moving functions inside __init__
oscarxblanco Jan 7, 2025
1fbfcfb
black
oscarxblanco Jan 7, 2025
b47392d
isort
oscarxblanco Jan 7, 2025
956d32e
change Sinlimit to Sinabove
oscarxblanco Jan 7, 2025
8b64d11
changes Sinlimit to Sinabove
oscarxblanco Jan 7, 2025
e10b71c
changes Sinlimit to Sinabove; fixes docstring
oscarxblanco Jan 7, 2025
eabf92e
improve docstring
oscarxblanco Jan 7, 2025
19e0560
improve docstring
oscarxblanco Jan 7, 2025
2b637d3
Changes Sinabove to SinAabove and SinBabove
oscarxblanco Jan 7, 2025
8a6a266
changes Sinabove to SinAabove and SinBabove
oscarxblanco Jan 7, 2025
d4a2e66
changes Sinabove by SinAabove and SinBabove
oscarxblanco Jan 7, 2025
a1ecaa0
fix docstring
oscarxblanco Jan 7, 2025
6573a21
fixing matlab help
oscarxblanco Jan 7, 2025
7544283
Adding default pass method to docstring
oscarxblanco Jan 7, 2025
8605f7b
removing self from methods inside __init__
oscarxblanco Jan 7, 2025
cd27c42
fixing help message
oscarxblanco Jan 8, 2025
4b1812a
fixing help message
oscarxblanco Jan 8, 2025
01b2a66
Merge branch 'master' into fix_bug_pyat_variablemultipole
oscarxblanco Jan 8, 2025
02ee269
changes type hint to Sequence
oscarxblanco Jan 15, 2025
02a7bc0
fixing flake8 FNE008
oscarxblanco Jan 15, 2025
3642a67
set Periodic default False
oscarxblanco Jan 15, 2025
8c0ea73
Periodic set default false
oscarxblanco Jan 15, 2025
6de4a39
Merge branch 'master' into fix_bug_pyat_variablemultipole
oscarxblanco Jan 15, 2025
3d54be8
removing Float from type hint Sequence due to python3.7 error
oscarxblanco Jan 15, 2025
8cbcd97
change to VariableThinMultipole
oscarxblanco Jan 15, 2025
0054f6e
atvariablethinmultiple.m
oscarxblanco Jan 15, 2025
bf9b7ef
change to atvariablethinmultipole
oscarxblanco Jan 15, 2025
4084069
rename VariableMultipole to VariableThinMultipole
oscarxblanco Jan 16, 2025
02dad6f
rename VariableMultipole to VariableThinMultipole
oscarxblanco Jan 16, 2025
e25a0f1
reset PolynomB and PolynomA to zeros after tracking; set Periodic to …
oscarxblanco Jan 16, 2025
f5a341b
fix bug in help message
oscarxblanco Jan 16, 2025
df259e9
fixing help message
oscarxblanco Jan 17, 2025
f08edc0
fix help
oscarxblanco Jan 17, 2025
368ad62
fix help; work on the polynom inspection
oscarxblanco Jan 17, 2025
b97d73d
add polynom inspection
oscarxblanco Jan 23, 2025
8181d19
black
oscarxblanco Jan 23, 2025
07c1290
help docstring
oscarxblanco Jan 23, 2025
f059f00
Merge branch 'master' into fix_bug_pyat_variablemultipole
oscarxblanco Jan 28, 2025
d03522b
fix phase and Sinabove parameters
oscarxblanco Jan 28, 2025
3774656
adds atmat/atutils/atinspectvariablethinmultipole.m
oscarxblanco Jan 28, 2025
500b0a9
fixing T0
oscarxblanco Jan 28, 2025
d0fcb9b
fix custom element
oscarxblanco Jan 28, 2025
0c94dda
add default timeoffset
oscarxblanco Jan 28, 2025
01f09d9
fix help message
oscarxblanco Jan 28, 2025
9024245
add help message
oscarxblanco Jan 28, 2025
ec7da17
add to help message
oscarxblanco Jan 28, 2025
bcd928b
add T1,T2,R1,R2 comment in help message
oscarxblanco Jan 28, 2025
5c9d75e
add to help
oscarxblanco Jan 28, 2025
cddca24
add to help
oscarxblanco Jan 28, 2025
ef840e5
fixing flake8
oscarxblanco Jan 28, 2025
9b2280e
fixing flake8
oscarxblanco Jan 28, 2025
2f7549d
fixing flake8
oscarxblanco Jan 28, 2025
49e70ad
remove debug
oscarxblanco Jan 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions atmat/lattice/element_creation/atvariablemultipole.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@
% FREQUENCYB Frequency of SINE excitation for PolynomB
% PHASEA Phase of SINE excitation for PolynomA
% PHASEB Phase of SINE excitation for PolynomB
% MAXORDER Order of the multipole for a scalar amplitude
% MAXORDER Order of the multipole for a scalar amplitude
% SEED Input seed for the random number generator
% FUNCA ARBITRARY excitation turn-by-turn kick list for PolynomA
% FUNCB ARBITRARY excitation turn-by-turn kick list for PolynomB
% PERIODIC If true (default) the user input kick list is repeated
% RAMPS Vector (t0, t1, t2, t3) in turn number to define the ramping of the excitation
% * t<t0: excitation amlpitude is zero
% * t0<t<t1: exciation amplitude is linearly ramped up
% * t1<t<t2: exciation amplitude is constant
% * t2<t<t3: exciation amplitude is linearly ramped down
% * t3<t: exciation amplitude is zero
% * t0<t<t1: excitation amplitude is linearly ramped up
% * t1<t<t2: excitation amplitude is constant
% * t2<t<t3: excitation amplitude is linearly ramped down
% * t3<t: excitation amplitude is zero
%
% OUTPUTS
% 1. ELEM - Structure with the AT element
Expand Down Expand Up @@ -108,15 +108,21 @@ function setsine(rsrc, ab)
function rsrc = setmaxorder(rsrc)
if isfield(rsrc,'AmplitudeA')
mxa=find(abs(rsrc.AmplitudeA)>0,1,'last');
if isempty(mxa)
mxa=1;
end
else
mxa=0;
end
if isfield(rsrc,'AmplitudeB')
mxb=find(abs(rsrc.AmplitudeB)>0,1,'last');
if isempty(mxb)
mxb=1;
end
else
mxb=0;
end
mxab=max([mxa,mxb,rsrc.MaxOrder-1]);
mxab=max([mxa,mxb,rsrc.MaxOrder+1]);
rsrc.MaxOrder=mxab-1;
if isfield(rsrc,'AmplitudeA')
rsrc.AmplitudeA(mxa+1:mxab)=0;
Expand Down
206 changes: 128 additions & 78 deletions pyat/at/lattice/variable_elements.py
Original file line number Diff line number Diff line change
@@ -1,64 +1,85 @@
import numpy
from __future__ import annotations

from datetime import datetime
from enum import IntEnum

import numpy as np

from .elements import Element, _array
from .utils import AtError
from typing import Optional, Union
from enum import IntEnum
from datetime import datetime


class ACMode(IntEnum):
"""Class to define the excitation types"""
"""Class to define the excitation types."""

SINE = 0
WHITENOISE = 1
ARBITRARY = 2


class VariableMultipole(Element):
"""Class to generate an AT variable thin multipole element
"""
"""Class to generate an AT variable thin multipole element."""

_BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES
_conversions = dict(Element._conversions,
Mode=int,
AmplitudeA=_array, AmplitudeB=_array,
FrequencyA=float, FrequencyB=float,
PhaseA=float, PhaseB=float,
Seed=int, NSamplesA=int, NSamplesB=int,
FuncA=_array, FuncB=_array, Ramps=_array,
Periodic=bool)

def __init__(self, family_name, AmplitudeA=None, AmplitudeB=None,
mode=ACMode.SINE, **kwargs):
_conversions = dict(
Element._conversions,
Mode=int,
AmplitudeA=_array,
AmplitudeB=_array,
FrequencyA=float,
FrequencyB=float,
PhaseA=float,
PhaseB=float,
Seed=int,
NSamplesA=int,
NSamplesB=int,
FuncA=_array,
FuncB=_array,
Ramps=_array,
Periodic=bool,
)

def __init__(self, family_name: str, **kwargs: dict[str, any]):
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 specify the type of kwargs this is standard python

Could you please restore the defaults for AmplitudeA/B and the mode? I think it is better if they appear in the API doc, in fact I would be tempted to add more of these keyword arguments rather than removing them

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 @swhite2401 , if I remove the annotation I get the following flake8 warning. This is the reason why I included, should I ignore it ? I may be using flake8 with a wrong configuration or I may use another tool ?

/variable_elements.py:42:44: ANN003 Missing type annotation for **kwargs

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Could you please restore the defaults for AmplitudeA/B and the mode? I think it is better if they appear in the API doc, in fact I would be tempted to add more of these keyword arguments rather than removing them

Dear @swhite2401 , the reason why I remove it is because of an incompatibility between AT matlab and this implementation of the element in pyat.
If I save a lattice in matlab and try to read it in pyat I get the error here below, I'll try to explain the reason in detail, but, in few words: one cannot have args and kwargs with the same name

When reading an element from matlab it is initially put in a dictionary elem_dict, and then the _BUILD_ATTRIBUTES are stripped to pass them to the class instantiation as elem_args. See the lines below

at/pyat/at/load/utils.py

Lines 368 to 369 in 268994f

elem_args = [elem_dict.pop(attr, None) for attr in cls._BUILD_ATTRIBUTES]
element = cls(*(arg for arg in elem_args if arg is not None), **elem_dict)

I could not put the AmplutudeA, AmplitudeB and Mode in the element BUILD_ATTRIBUTES because they are not garanteed to exist in matlab, see the following line

function elem=atvariablemultipole(fname,varargin)

therefore, the call to cls with args in necessarily incomplete and the AmplitudeA, AmplitudeB and Mode are passed in a dictionary together with args that have the same name. Python does not handle this mix well, it removes the elements from the kwargs and leaves unset the args.

There are ways to workaround this situation when args and kwargs have the same name, but they require changing the way the class is instantiated in utils.py. I decided not to do it because that will change how all elements are instantiated.

A second possibility was to rename the element args in pyat, for example amplitudeA instead of AmplitudeA i.e. lower case in the first letter to make one small change. However, this would break compatibility.

I decided to keep the element without any requirement as in AT matlab, which forced me to do checks on the input parameters before assigning them inside __init__. This wouldn't break compatibility with previous versions of pyat, and AT matlab files could be read because the element also has no requirements except for the FamName.

If you prefer, I could do as you asked. Keep the args and break backwards compatibility. Or force the user to provide AmplitudeA, AmplitudeB and Mode in matlab, which will make possible to include them in BUILD_ATTRIBUTES that will be removed in the input dictionary and set before the class is instantiated.

Or maybe there is another solution ?

>>> ring = at.load_mat('test1.mat')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/oblanco/Documents/public/progs/pyenv/pyenvat/lib/python3.11/site-packages/at/load/matfile.py", line 202, in load_mat
    return Lattice(
           ^^^^^^^^
  File "/home/oblanco/Documents/public/progs/pyenv/pyenvat/lib/python3.11/site-packages/at/lattice/lattice_object.py", line 191, in __init__
    super(Lattice, self).__init__(elems)
  File "/home/oblanco/Documents/public/progs/pyenv/pyenvat/lib/python3.11/site-packages/at/lattice/lattice_object.py", line 1478, in params_filter
    for elem in elem_filter(params, *args):
  File "/home/oblanco/Documents/public/progs/pyenv/pyenvat/lib/python3.11/site-packages/at/load/matfile.py", line 140, in ringparam_filter
    for elem in elem_iterator(params, *args):
  File "/home/oblanco/Documents/public/progs/pyenv/pyenvat/lib/python3.11/site-packages/at/load/matfile.py", line 105, in _matfile_generator
    yield Element.from_dict(kwargs, index=index, check=check, quiet=quiet)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/oblanco/Documents/public/progs/pyenv/pyenvat/lib/python3.11/site-packages/at/load/utils.py", line 365, in element_from_dict
    element = cls(*(arg for arg in elem_args if arg is not None), **elem_dict)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/oblanco/Documents/public/progs/pyenv/pyenvat/lib/python3.11/site-packages/at/lattice/variable_elements.py", line 111, in __init__
    raise AtError("Please provide at least one amplitude for A or B")
at.lattice.utils.AtError: Please provide at least one amplitude for A or B

Copy link
Contributor

Choose a reason for hiding this comment

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

A second possibility was to rename the element args in pyat, for example amplitudeA instead of AmplitudeA i.e. lower case in the first letter to make one small change. However, this would break compatibility.

Hi @oscarxblanco , thanks for the detailed answers! My personnal opinion is that this was poorly implemented to start with (which is my fault in fact, sorry...) and that in such situation we should be allowed to break backward compatibility.
In all other pyAT element, args seem to have in fact different name that kwargs and we should probably stick to that convention to keep things simple. Similarly we could modify MATLAB to preserve compatibility with the new pyAT version.

This feature is relatively recent and it could well be that @ZeusMarti is the only user... we have used it at ESRF but I don't think we have saved lattice files including it. Would it be a big effort on your side to switch to a new signature?

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 @swhite2401 , I asked @ZeusMarti and he was not able to use it effectively because he intends to have a custom function where the time of the particle is also considered, i.e. not only a kick every turn for all particles.
I have started modifying the C code today to make some test, is this feature something you would accept for this element ? Or should I keep the custom function option as it is ?

o

Copy link
Contributor

Choose a reason for hiding this comment

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

I have started modifying the C code today to make some test, is this feature something you would accept for this element ?

Sure! In fact I developed this for @ZeusMarti initially, so any improvement is perfectly fine.
You can already do a kick every n turns by using the table by the way, just fill zeros where you do not want any kick.

Do you want help for this?

# noinspection PyUnresolvedReferences
r"""
Create VariableMultipole.

Parameters:
family_name(str): Element name

Keyword Arguments:
AmplitudeA(list,float): Amplitude of the excitation for PolynomA. Default None
AmplitudeB(list,float): Amplitude of the excitation for PolynomB. Default None
AmplitudeA(list,float): Amplitude of the excitation for PolynomA.
Default None
AmplitudeB(list,float): Amplitude of the excitation for PolynomB.
Default None
mode(ACMode): defines the evaluation grid. Default ACMode.SINE

* :py:attr:`.ACMode.SINE`: sine function
* :py:attr:`.ACMode.WHITENOISE`: gaussian white noise
* :py:attr:`.GridMode.ARBITRARY`: user defined turn-by-turn kick list
* :py:attr:`.ACMode.ARBITRARY`: user defined turn-by-turn kick list
FrequencyA(float): Frequency of the sine excitation for PolynomA
FrequencyB(float): Frequency of the sine excitation for PolynomB
PhaseA(float): Phase of the sine excitation for PolynomA. Default 0
PhaseB(float): Phase of the sine excitation for PolynomB. Default 0
MaxOrder(int): Order of the multipole for scalar amplitude. Default 0
It is overwritten with the length of Amplitude(A,B)
Seed(int): Seed of the random number generator for white
noise excitation. Default datetime.now()
FuncA(list): User defined tbt kick list for PolynomA
FuncB(list): User defined tbt kick list for PolynomB
Periodic(bool): If True (default) the user defined kick is repeated
Ramps(list): Vector (t0, t1, t2, t3) in turn number to define the ramping
of the excitation
Ramps(list): Vector (t0, t1, t2, t3) in turn number to define
the ramping of the excitation

* ``t<t0``: excitation amplitude is zero
* ``t0<t<t1``: exciation amplitude is linearly ramped up
* ``t1<t<t2``: exciation amplitude is constant
* ``t2<t<t3``: exciation amplitude is linearly ramped down
* ``t3<t``: exciation amplitude is zero
* ``t0<t<t1``: excitation amplitude is linearly ramped up
* ``t1<t<t2``: excitation amplitude is constant
* ``t2<t<t3``: excitation amplitude is linearly ramped down
* ``t3<t``: excitation amplitude is zero
Raises:
AtError if none of AmplitudeA or AmplitudeB is passed.
AtError if ramp is not vector of length 4 when using Ramps
AtError when Frequency is not defined if using Mode ``ACMode.SINE``
AtError when Funct is not defined if using Mode ``ACMode.ARBITRARY``


Examples:

Expand All @@ -68,74 +89,103 @@ def __init__(self, family_name, AmplitudeA=None, AmplitudeB=None,

.. note::

* For all excitation modes all least one amplitude has to be provided.
* If no parameters are given it will be initialized as IdentityPass.
* For all excitation modes at least one amplitudes (A or B) has
to be provided.
The default excitation is ``ACMode.SINE``
* For ``mode=ACMode.SINE`` the ``Frequency(A,B)`` corresponding to the
``Amplitude(A,B)`` has to be provided
``Amplitude(A,B)`` has to be provided.
* For ``mode=ACMode.ARBITRARY`` the ``Func(A,B)`` corresponding to the
``Amplitude(A,B)`` has to be provided
``Amplitude(A,B)`` has to be provided.
"""
kwargs.setdefault('PassMethod', 'VariableThinMPolePass')
self.MaxOrder = kwargs.pop('MaxOrder', 0)
self.Periodic = kwargs.pop('Periodic', True)
self.Mode = int(mode)
if AmplitudeA is None and AmplitudeB is None:
raise AtError('Please provide at least one amplitude for A or B')
AmplitudeB = self._set_params(AmplitudeB, mode, 'B', **kwargs)
AmplitudeA = self._set_params(AmplitudeA, mode, 'A', **kwargs)
self._setmaxorder(AmplitudeA, AmplitudeB)
if mode == ACMode.WHITENOISE:
self.Seed = kwargs.pop('Seed', datetime.now().timestamp())
self.PolynomA = numpy.zeros(self.MaxOrder+1)
self.PolynomB = numpy.zeros(self.MaxOrder+1)
ramps = kwargs.pop('Ramps', None)
if ramps is not None:
assert len(ramps)==4, \
'Ramps has to be a vector with 4 elements'
self.Ramps = ramps
super(VariableMultipole, self).__init__(family_name, **kwargs)

def _setmaxorder(self, ampa, ampb):
if len(kwargs) > 0:
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was needed in order to separate the case when the element is called inside pyat, therefore the object already exists and there are no kwargs, from the case when the element is instantiated using a dictionary (the dictionary comming from reading a .mat, or from user settings).

Sometimes the object is read, for example when printing in a terminal, the element is passed again (I guess this happens because it inherits __str__ from other element) and no modifications in the object are needed, I just need to pass it to super and find its way to __str__.

self.FamName = family_name
Copy link
Contributor

Choose a reason for hiding this comment

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

family_name = kwargs.pop('FamName', family_name)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I explained before, but, when the element comes from AT matlab, a dictionary is created but all BUILD_ATTRIBUTES are stripped. 'FamName' is a a BUILD_ATTRIBUTE of the class element and therefore it is not in kwargs. One has to take it from args.

if "AmplitudeA" not in kwargs and "AmplitudeB" not in kwargs:
Copy link
Contributor

Choose a reason for hiding this comment

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

AmplitudeA/B présence in kwargs also needs to be checked

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry @swhite2401 , I don't understand. What you say is implemented in line 103 above.
You mean individually ? I thought this element should accept to have at least one of the two.

raise AtError("Please provide at least one amplitude for A or B")
# start setting up Amplitudes and modes
# fist modes are called differently
modepyatinput = kwargs.pop("mode", ACMode.SINE)
Copy link
Contributor

Choose a reason for hiding this comment

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

You could replace all these lines by:

Self.Mode = kwargs.pop('Mode', int(mode))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I cannot.
The issue here is that the argument is called 'mode' in pyat and 'Mode' in MATLAB.
I need to check in advance which is correct.

modefromdict = kwargs.pop("Mode", None)
if modefromdict is not None:
mode = modefromdict # matlab class
else:
mode = modepyatinput
self.Mode = int(mode)
# MaxOrder is later overwritten by lenth of amplitudes
self.MaxOrder = kwargs.get("MaxOrder", 0)
amplitudea = None
amplitudeb = None
if "AmplitudeA" in kwargs:
Copy link
Contributor

Choose a reason for hiding this comment

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

This lol ne and the ones below can be removed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What do you mean by lol ?

amplitudea = kwargs.pop("AmplitudeA", None)
amplitudea = self._set_params(amplitudea, mode, "A", **kwargs)
if "AmplitudeB" in kwargs:
amplitudeb = kwargs.pop("AmplitudeB", None)
amplitudeb = self._set_params(amplitudeb, mode, "B", **kwargs)
if amplitudea is not None:
self.AmplitudeA = amplitudea
if amplitudeb is not None:
self.AmplitudeB = amplitudeb
# end setting up Amplitudes and modes
kwargs.setdefault("PassMethod", "VariableThinMPolePass")
# this overwrites MaxOrder
self._setmaxorder(amplitudea, amplitudeb)
if mode == ACMode.WHITENOISE and "Seed" not in kwargs:
kwargs.update({"Seed": datetime.now().timestamp()})
self.Periodic = kwargs.pop("Periodic", True)
self.PolynomA = kwargs.pop("PolynomA", np.zeros(self.MaxOrder + 1))
self.PolynomB = kwargs.pop("PolynomB", np.zeros(self.MaxOrder + 1))
# check ramps
ramps = kwargs.pop("Ramps", None)
if ramps is not None:
if len(ramps) != 4:
raise AtError("Ramps has to be a vector with 4 elements")
self.Ramps = ramps
# fill in super class
super().__init__(family_name, **kwargs)

def _setmaxorder(self, ampa: np.ndarray, ampb: np.ndarray):
Copy link
Contributor

Choose a reason for hiding this comment

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

In what cases did this end up to be none? When ampa/b was filled with only zeros?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have to repeat the test but suppose you have the current implementation available in the master. You will have this behaviour with zeros.

>>> numpy.max(numpy.nonzero([0,0,0,0]))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/oblanco/Documents/public/progs/pyenv/pyenvat/lib/python3.11/site-packages/numpy/core/fromnumeric.py", line 2810, in max
    return _wrapreduction(a, np.maximum, 'max', axis, None, out,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/oblanco/Documents/public/progs/pyenv/pyenvat/lib/python3.11/site-packages/numpy/core/fromnumeric.py", line 88, in _wrapreduction
    return ufunc.reduce(obj, axis, dtype, out, **passkwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: zero-size array to reduction operation maximum which has no identity

I changed to mxa = np.max(np.append(np.nonzero(ampa), 0)) which is the maximum between the nonzero in ampa and zero, and I get sure ampa exists before doing it.

mxa, mxb = 0, 0
if ampa is not None:
mxa = numpy.max(numpy.nonzero(ampa))
mxa = np.max(np.append(np.nonzero(ampa), 0))
if ampb is not None:
mxb = numpy.max(numpy.nonzero(ampb))
mxb = np.max(np.append(np.nonzero(ampb), 0))
self.MaxOrder = max(mxa, mxb)
if ampa is not None:
delta = self.MaxOrder - len(ampa)
if delta > 0:
ampa = numpy.pad(ampa, (0, delta))
setattr(self, 'AmplitudeA', ampa)
ampa = np.pad(ampa, (0, delta))
self.AmplitudeA = ampa
if ampb is not None:
delta = self.MaxOrder + 1 - len(ampb)
if delta > 0:
ampb = numpy.pad(ampb, (0, delta))
setattr(self, 'AmplitudeB', ampb)
ampb = np.pad(ampb, (0, delta))
self.AmplitudeB = ampb

def _set_params(self, amplitude, mode, ab, **kwargs):
def _set_params(
self, amplitude: int or str, mode, a_b: str, **kwargs: dict[str, any]
Copy link
Contributor

Choose a reason for hiding this comment

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

No need to define the type of kwargs

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is flake8 who suggested. Should I ignore it ? Do you work with some custom flake8 settings for pyat development ?

):
if amplitude is not None:
if numpy.isscalar(amplitude):
amp = numpy.zeros(self.MaxOrder)
amplitude = numpy.append(amp, amplitude)
if np.isscalar(amplitude):
amp = np.zeros(self.MaxOrder)
amplitude = np.append(amp, amplitude)
if mode == ACMode.SINE:
self._set_sine(ab, **kwargs)
self._set_sine(a_b, **kwargs)
if mode == ACMode.ARBITRARY:
self._set_arb(ab, **kwargs)
self._set_arb(a_b, **kwargs)
return amplitude

def _set_sine(self, ab, **kwargs):
frequency = kwargs.pop('Frequency' + ab, None)
phase = kwargs.pop('Phase' + ab, 0)
assert frequency is not None, \
'Please provide a value for Frequency' + ab
setattr(self, 'Frequency' + ab, frequency)
setattr(self, 'Phase' + ab, phase)
def _set_sine(self, a_b: str, **kwargs: dict[str, any]):
frequency = kwargs.pop("Frequency" + a_b, None)
phase = kwargs.pop("Phase" + a_b, 0)
if frequency is None:
raise AtError("Please provide a value for Frequency" + a_b)
setattr(self, "Frequency" + a_b, frequency)
setattr(self, "Phase" + a_b, phase)

def _set_arb(self, ab, **kwargs):
func = kwargs.pop('Func' + ab, None)
def _set_arb(self, a_b: str, **kwargs: dict[str, any]):
func = kwargs.pop("Func" + a_b, None)
nsamp = len(func)
assert func is not None, \
'Please provide a value for Func' + ab
setattr(self, 'Func' + ab, func)
setattr(self, 'NSamples' + ab, nsamp)
if func is None:
raise AtError("Please provide a value for Func" + a_b)
setattr(self, "Func" + a_b, func)
setattr(self, "NSamples" + a_b, nsamp)