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,moment1x0,xp0,y0,yp0,phi0,dEk0x0_env,xp0_env,y0_env,yp0_env,phi0_env,dEk0_envx0_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