Parameters#

Parameters are objects of class Param which can be used instead of numeric values as Element attributes.

Parameters are initialised with a scalar numeric value. They have an optional name, used when printtig the parameter. Optionally they can be bounded.

total_length = Param(2.5, name="total_length")
dlength = Param(1.0, name="dlength", bounds=(0.0, 5.0))
print(f"{total_length}: {total_length!r}")
print(f"{dlength}: {dlength!r}")
total_length: 2.5
dlength: 1.0

The value of a parameter can be read or modified through its value property. set() and get() methods are also available:

total_length.value = 2.4
print(f"{total_length}: {total_length!r}")
total_length: 2.4
total_length.set(2.3)
print(total_length.get())
2.3

Arithmetic combinations of parameters create new read-only parameters of class ParamBase, whose value is permanently kept up-to-date:

qlength = total_length - dlength
print(f"{qlength}: {qlength!r}")
total_length-dlength: 1.2999999999999998
dlength.value = 0.9
print(f"{qlength}: {qlength!r}")
total_length-dlength: 1.4

Parameters may be assigned to Element attributes, for instance on initialisation:

dr1 = at.Drift('DR1', dlength)
qf1 = at.Quadrupole('QF1', qlength, 0.6)
print(dr1)
print(qf1)
Drift:
       FamName: DR1
        Length: dlength
    PassMethod: DriftPass
Quadrupole:
       FamName: QF1
        Length: total_length-dlength
    PassMethod: StrMPoleSymplectic4Pass
      MaxOrder: 1
   NumIntSteps: 10
      PolynomA: [0. 0.]
      PolynomB: [0.  0.6]
             K: 0.6

The Element attributes keep their type so that all the processing of elements either in python functions or in C integrators is unchanged:

print(dr1.Length, type(dr1.Length))
0.9 <class 'float'>

Assigning parameters#

To a single element#

Parameters may be assigned to Element attributes in several ways:

At element creation:

dr2 = at.Drift('DR2', dlength)

By converting a numeric attribute into a parameter:

qd1 = at.Quadrupole('QD1', 0.5, -0.4)
ql = qd1.parameterise('Length')
vkick = qd1.parameterise("PolynomA", index=0, name="vkick")
print(f"{ql}: {ql!r}")
param3: 0.5

By normal assignment, only for scalar parameters:

qd1.Length = Param(0.2)
print(qd1)
Quadrupole:
       FamName: QD1
        Length: param5
    PassMethod: StrMPoleSymplectic4Pass
      MaxOrder: 1
   NumIntSteps: 10
      PolynomA: [vkick 0.0]
      PolynomB: [ 0.  -0.4]
             K: -0.4

With the set_parameter() method:

qstrength = Param(-0.5, name='quad_strength')
qd1.set_parameter('PolynomB', qstrength, index=1)
print(qd1)
Quadrupole:
       FamName: QD1
        Length: param5
    PassMethod: StrMPoleSymplectic4Pass
      MaxOrder: 1
   NumIntSteps: 10
      PolynomA: [vkick 0.0]
      PolynomB: [0.0 quad_strength]
             K: -0.5

To selected elements of a Lattice#

To act on several elements in a single step, Lattice methods similar to Element methods are available.

Convert numeric attributes into parameters:

The attribute of all the selected elements is replaced by a single parameter whose initial value is the average of the original values.

kf1 = ring.parameterise('QF1[AE]', 'PolynomB', index=1, name='kf1')
print(f"{kf1}: {kf1!r}")
print(ring[5])
kf1: np.float64(2.5394599781303304)
Quadrupole:
       FamName: QF1A
        Length: 0.311896
    PassMethod: StrMPoleSymplectic4Pass
FringeQuadEntrance: 1
FringeQuadExit: 1
      MaxOrder: 1
   NumIntSteps: 20
      PolynomA: [0. 0.]
      PolynomB: [0.0 kf1]
             K: 2.5394599781303304

Use the set_parameter() method:

The attribute of all the selected elements is replaced by the provided parameter,

lf1 = Param(0.311896, name='lf1')
ring.set_parameter('QF1[AE]', 'Length', lf1)
print(ring[117])
Quadrupole:
       FamName: QF1E
        Length: lf1
    PassMethod: StrMPoleSymplectic4Pass
FringeQuadEntrance: 1
FringeQuadExit: 1
      MaxOrder: 1
   NumIntSteps: 20
      PolynomA: [0. 0.]
      PolynomB: [0.0 kf1]
             K: 2.5394599781303304

Once a Parameter is assigned to an attribute, it acquires the type and the constraints of the attribute. For instance:

num_int_steps = Param(14.4, name="num_int_steps")
ring.set_parameter(at.Multipole, 'NumIntSteps', num_int_steps)
print(ring[5].NumIntSteps, type(ring[5].NumIntSteps))
14 <class 'int'>
num_int_steps.value = -5
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[17], line 1
----> 1 num_int_steps.value = -5

File ~/checkouts/readthedocs.org/user_builds/at/envs/parameters3/lib/python3.11/site-packages/at/lattice/parameters.py:70, in Param.value(self, value)
     68 @value.setter
     69 def value(self, value: Number):
---> 70     self.set(value)

File ~/checkouts/readthedocs.org/user_builds/at/envs/parameters3/lib/python3.11/site-packages/at/lattice/variables.py:360, in VariableBase.set(self, value, **setkw)
    350 r"""Set the variable value.
    351 
    352 Args:
   (...)    357       They augment the keyword arguments given in the constructor.
    358 """
    359 self.check_bounds(value)
--> 360 self._setfun(value, *self.args, **(self.kwargs | setkw))
    361 if np.isnan(self._initial):
    362     self._initial = value

File ~/checkouts/readthedocs.org/user_builds/at/envs/parameters3/lib/python3.11/site-packages/at/lattice/parameters.py:59, in Param._setfun(self, value, **_)
     58 def _setfun(self, value: Number, **_) -> None:
---> 59     self._evaluator = _Constant(self._conversion(value))

File ~/checkouts/readthedocs.org/user_builds/at/envs/parameters3/lib/python3.11/site-packages/at/lattice/elements/element_object.py:60, in Element.<lambda>(v)
     42 """Base class for AT elements."""
     44 _BUILD_ATTRIBUTES: ClassVar[list[str]] = ["FamName"]
     45 _conversions: ClassVar[dict] = {
     46     "FamName": str,
     47     "PassMethod": str,
     48     "Length": _float,
     49     "R1": _array66,
     50     "R2": _array66,
     51     "T1": lambda v: _array(v, (6,)),
     52     "T2": lambda v: _array(v, (6,)),
     53     "RApertures": lambda v: _array(v, (4,)),
     54     "EApertures": lambda v: _array(v, (2,)),
     55     "KickAngle": lambda v: _array(v, (2,)),
     56     "PolynomB": _array,
     57     "PolynomA": _array,
     58     "BendingAngle": _float,
     59     "MaxOrder": _int,
---> 60     "NumIntSteps": lambda v: _int(v, vmin=0),
     61     "Energy": _float,
     62 }
     63 _file_classname: ClassVar[str]
     65 _entrance_fields: ClassVar[list[str]] = ["T1", "R1"]

File ~/checkouts/readthedocs.org/user_builds/at/envs/parameters3/lib/python3.11/site-packages/at/lattice/elements/conversions.py:23, in _int(value, vmin, vmax)
     21 if vmin is not None and intv < vmin:
     22     msg = f"Value must be greater of equal to {vmin}"
---> 23     raise ValueError(msg)
     24 if vmax is not None and intv > vmax:
     25     msg = f"Value must be smaller of equal to {vmax}"

ValueError: Value must be greater of equal to 0

The parameter behaves as the attribute.

Retrieving parameters#

Since the values of Element attributes keep their original type, they cannot be used to access the underlying parameter. The only way to retrieve it is to use the get_parameter() method. a TypeError is raised if the attribute is not a Parameter.

ql = qf1.get_parameter('Length')
print(f"{ql}: {ql!r}")
print("ql is qlength:", ql is qlength)
total_length-dlength: 1.4
ql is qlength: True

This also works for items in array attributes:

qs = qd1.get_parameter('PolynomB', index=1)
print(f"{qs}: {qs!r}")
print("qs is qstrength:", qs is qstrength)
quad_strength: -0.5
qs is qstrength: True

Checking parametrisation#

The is_parameterised() method may be applied to:

  • a full element: it returns true is any of its attributes is a parameter,

  • an array attribute: it returns true if any of its items is a parameter,

  • a scalar attribute or an item of an array.

print(qd1.is_parameterised())
print(qd1.is_parameterised('PolynomB', index=1))
print(qd1.is_parameterised('PolynomB', index=0))
True
True
False

Removing parameters#

Removing the parameters will “freeze” the element at its current value. The unparameterise() method is defined for both Element and Lattice, and may be applied to:

  • a full element: all the parameters are replaced by their value,

  • an array attribute: the whole ParamArray is replaced by a numpy array,

  • a scalar attribute or an item of an array.

In an Element#

print(qd1)
qd1.unparameterise('Length')
print(qd1)
Quadrupole:
       FamName: QD1
        Length: param5
    PassMethod: StrMPoleSymplectic4Pass
      MaxOrder: 1
   NumIntSteps: 10
      PolynomA: [vkick 0.0]
      PolynomB: [0.0 quad_strength]
             K: -0.5
Quadrupole:
       FamName: QD1
        Length: 0.2
    PassMethod: StrMPoleSymplectic4Pass
      MaxOrder: 1
   NumIntSteps: 10
      PolynomA: [vkick 0.0]
      PolynomB: [0.0 quad_strength]
             K: -0.5
qd1.unparameterise('PolynomB', 1)
print(qd1)
Quadrupole:
       FamName: QD1
        Length: 0.2
    PassMethod: StrMPoleSymplectic4Pass
      MaxOrder: 1
   NumIntSteps: 10
      PolynomA: [vkick 0.0]
      PolynomB: [ 0.  -0.5]
             K: -0.5

In a Lattice#

print(ring[5])
ring.unparameterise('QF1[AE]', 'PolynomB', index=1)
print(ring[5])
Quadrupole:
       FamName: QF1A
        Length: lf1
    PassMethod: StrMPoleSymplectic4Pass
FringeQuadEntrance: 1
FringeQuadExit: 1
      MaxOrder: 1
   NumIntSteps: num_int_steps
      PolynomA: [0. 0.]
      PolynomB: [0.0 kf1]
             K: 2.5394599781303304
Quadrupole:
       FamName: QF1A
        Length: lf1
    PassMethod: StrMPoleSymplectic4Pass
FringeQuadEntrance: 1
FringeQuadExit: 1
      MaxOrder: 1
   NumIntSteps: num_int_steps
      PolynomA: [0. 0.]
      PolynomB: [0.         2.53945998]
             K: 2.5394599781303304
ring.unparameterise('QF1[AE]')
print(ring[117])
Quadrupole:
       FamName: QF1E
        Length: 0.311896
    PassMethod: StrMPoleSymplectic4Pass
FringeQuadEntrance: 1
FringeQuadExit: 1
      MaxOrder: 1
   NumIntSteps: 14
      PolynomA: [0. 0.]
      PolynomB: [0.         2.53945998]
             K: 2.5394599781303304

Parameter history#

Parameter values are kept in an history buffer. The properties initial_value, last_value and previous_value are also available:

dlength.value = 1.1
print(dlength.history)
print(dlength.initial_value)
print(dlength.previous_value)
[1.0, 0.9, 1.1]
1.0
0.9

After varying parameters, in matching for instance, the current status can be printed:

print(dlength.status())
        Name      Initial          Final        Variation

     dlength    1.000000e+00    1.100000e+00    1.000000e-01

Parameters may be reset to a previous history value with the reset() and set_previous() methods. The history is shortened accordingly.

dlength.set_previous()
print(dlength, dlength.history)
dlength.reset()
print(dlength, dlength.history)
dlength [1.0, 0.9]
dlength [1.0]

Copying and Saving#

Parameters cannot be copied. A shallow or deep copy of a parameter returns the parameter itself.

So in a deep copy of an Element, the parameters are preserved.

When saving a lattice with parameterised elements, as a .mat, a .m or .repr file, all parametrisation is removed, and a “frozen” state of the lattice is saved.

In pickle dumps of an Element, parameters are preserved.