Modeling Accelerator with Lattice File
FLAME
[1] is a new envelope tracking code developed by FRIB for the
purpose of modeling (ion) accelerator efficiently, especially cases
of multi-charge states could be right handled, now it is still under
development.
FLAME
itself has Python interface with the package name of flame
.
Another Python package so-called flame_utils
[2],
which used to be one of
the modules of phantasy
is under developing, not only to make the
flame
Python interface more friendly and clear, but also to
better support the entire high-level physics applications,
which is a part of high-level control system.
Getting started
Here is the simplest way to model an accelerator from FLAME lattice file.
1from flame import Machine
2import flame_utils
3
4# create FLAME machine
5latfile = "test.lat"
6m = Machine(open(latfile, 'rb'))
7
8# create BeamState object
9bs = flame_utils.BeamState(machine=m)
10
11# create ModelFlame object
12fm = flame_utils.ModelFlame()
13# setup machine and state
14fm.bmstate, fm.machine = bs, m
15
16# setup observers and run flame model
17obs = fm.get_index_by_type(type='bpm')['bpm']
18r,s = fm.run(monitor=obs)
19
20# get data of intereset from running results
21data = fm.collect_data(r, pos=True, x0_env=True, ref_IonEk=True)
The lattice file used here could be downloaded from
here
.
The 9th line is to create a general FLAME beam state object, which
is a super class of FLAME interal state, one can use this object as
the same API as the FLAME interal state (except show()
method).
The 12th line is to create a ModelFlame
object
(see ModelFlame
), after that, machine
and bmstate
should be assigned to make it alive. The machine
attribute is just the FLAME machine object, bmstate
could accept
both FLAME interal state or BeamState
(see BeamState
), for the possible
user-customized states, the BeamState
is recommanded to
include all the operations upon the machine states object.
The advantage of re-invent the new BeamState
is to
improve the user experience in the Python CLI environment that
support auto-completion, e.g. ipython, then all properties that
BeamState
has could be reached by double hitting <TAB>
; moreover,
additional attributes could be defined in BeamState
to make the
higher level interface more clear and clean, see details at API References
and General FLAME beam state.
The run()
method of ModelFlame
is used to simulate the model, and
collect_data()
could be used to extract the data-of-interest from the
simulation results, then other operations could be done, e.g. data plotting.
Note
Method run()
does not change BeamState
inplace,
instead one can get the updated BeamState
from the
second element of the returned tuple, see 18th line.
General FLAME beam state
FLAME beam state is the most essential object in modeling an
accelerator. The Python interface of FLAME
represents the state
as _internal.State
, could be created by allocState()
method,
however, there are only two methods (clone()
and show()
) that
are exposed explicitly, one of the reasons is that _internal.State
is
designed for not only MomentMatrix
simulation type.
In order to make it more user-friendly, BeamState
class is
created specifically for the case of MomentMatrix
simulation type,
and exposing as many attributes as possible, since
Explicit is better than implicit [3].
For a typical BeamState
object, the following attributes could
be reached at the moment:
pos
,ref_beta
,ref_bg
,ref_gamma
,ref_IonEk
,ref_IonEs
,ref_IonQ
,ref_IonW
,ref_IonZ
,ref_phis
,ref_SampleIonK
,beta
,bg
,gamma
,IonEk
,IonEs
,IonQ
,IonW
,IonZ
,phis
,SampleIonK
,moment0
,moment0_rms
,moment0_env
,moment1
x0
,xp0
,y0
,yp0
,phi0
,dEk0
x0_env
,xp0_env
,y0_env
,yp0_env
,phi0_env
,dEk0_env
x0_rms
,xp0_rms
,y0_rms
,yp0_rms
,phi0_rms
,dEk0_rms
Todo
More attributes, that could be calculated from _internal.State
could be added to BeamState
class.
Create BeamState object
Basically, there are several ways to initialize the BeamState
, slight
differences should be aware of.
Note
BeamState
needs FLAME machine information (got from machine
or latfile
keyword parameter) to do further initialization,
especially, for the case of the beam state is composed of pure zeros.
Approach 1: Initialize with pre-defined flame._internal.State
object
Fist create machine and state object by FLAME Python package:
>>> import flame_utils
>>> from flame import Machine
>>>
>>> latfile = 'test.lat'
>>> m = Machine(open(latfile, 'rb'))
>>> s = m.allocState({})
>>> m.propagate(s, 0, 1)
Then, BeamState
can be created by:
>>> bs = flame_utils.BeamState(s)
or:
>>> bs = flame_utils.BeamState()
>>> bs.state = s
Now, one can use bs
object just the same way as _internal.State
,
e.g. bs
can be passed as the first argument of m
machine object’s
propagate()
method; bs
also can duplicated by clone()
method;
and even the represetation of bs
itself is much similar as s
,i.e.
print(bs)
would gives BeamState: moment0 mean=[7](-0.0007886,1.08371e-05,0.0133734,6.67853e-06,-0.000184773,0.000309995,1)
Approach 2: Initialize with pre-defined FLAME machine object
This approach will initialize a BeamState
object with the initial
attributes’ values from the pre-defined FLAME machine object, e.g.:
>>> bs = flame_utils.BeamState(machine=m)
>>> print(bs)
BeamState: moment0 mean=[7](-0.0007886,1.08371e-05,0.0133734,6.67853e-06,-0.000184773,0.000309995,1)
Also can do this by assigning latfile
keyword parameter:
>>> bs = flame_utils.BeamState(latfile=latfile)
Approach 3: Initialize with another BeamState
object
For example:
>>> bs1 = flame_utils.BeamState(latfile=latfile)
>>> bs2 = flame_utils.BeamState(bmstate=bs1)
Note
clone()
could be used to create a copy, e.g. bs2 = bs1.clone()
.
Configure BeamState object
To confiugre BeamState
is to set new values to attributes,
which could be done through properties setter methods, e.g. the initial
kinetic energy of reference charge state can be adjusted by:
>>> bs.ref_IonEk = 100000
The same rule applies to the scalar properties, however different rule
should be applied when updating array properties, e.g. moment0
,
whose value is a numpy array, if even only one element of that array
needs to be changed, one should create a new array and assign to moment0
,
rather than updating inplace, here is the example:
>>> # before adjustment
>>> print(bs.moment0)
array([[ -7.88600000e-04],
[ 1.08371000e-05],
[ 1.33734000e-02],
[ 6.67853000e-06],
[ -1.84773000e-04],
[ 3.09995000e-04],
[ 1.00000000e+00]])
>>> # right way to change the first element of moment0
>>> m0_val = bs.moment0
>>> m0_val[0] = 0
>>> bs.moment0 = b0_val
>>> print(bs.moment0)
array([[ 0.00000000e+00],
[ 1.08371000e-05],
[ 1.33734000e-02],
[ 6.67853000e-06],
[ -1.84773000e-04],
[ 3.09995000e-04],
[ 1.00000000e+00]])
Use BeamState object
Different BeamState
represent different initial conditions for
modeling processes, here are the possible cases:
Scan initial kinetic energy
import matplotlib.pyplot as plt
import numpy as np
from flame import Machine
import flame_utils
latfile = "test.lat"
m = Machine(open(latfile, 'rb'))
ek_out = []
ek0_arr = np.linspace(1, 1000, 20)
for ek0 in ek0_arr:
bs = flame_utils.BeamState(machine=m)
bs.ref_IonEk = ek0 * 1000
fm = flame_utils.ModelFlame()
fm.bmstate, fm.machine = bs, m
obs = fm.get_index_by_type(type='bpm')['bpm']
r,s = fm.run(monitor=obs)
ek_out.append(s.ref_IonEk)
Final reference ion kinetic energy v.s. initial input values could be shown as the following figure:
Tip
To disable logging messages from flame_utils
:
from flame_utils import disable_warnings
disable_warnings()
To disable logging messages from flame
:
import logging
logging.getLogger('flame.machine').disabled = True
Footnotes