MADX utilities
Several utilities for interfacing with the MADX program exist in the module madx.utils
. The following functions can be used to run MADX scripts:
run_file
- Runs a bunch of dependent MADX script files together with possibilities for further configuration (see the API docs for more details).run_script
- Runs a single script with the possibility for further configuration (see the API docs for more details).run_orm
- Compute the orbit response matrix for a given MADX sequence definition.
The resulting files are returned as pandas data frames by default but the functions can be configured to return the raw file contents instead. The stdout and stderr is returned as well.
These functions can also be imported from the dipas.madx
subpackage directly.
Running MADX scripts
For example we can run TWISS
computations on one of the example scripts:
[1]:
from importlib import resources
import os.path
from dipas.madx import run_file, run_script, run_orm
import dipas.test.sequences
with resources.path(dipas.test.sequences, 'cryring.seq') as path:
result = run_file(path, madx=os.path.expanduser('~/bin/madx'))
print(list(result))
print(result['stderr'])
['stdout', 'stderr']
result['stdout']
contains the whole echo from the script, which is pretty long, so we won’t print it here. The empty list which was passed to run_file
is a list of resulting files that should be retrieved, but since that example script just contained the sequence definition, no files were created anyway. Let’s change that and also remove the echo:
[2]:
script = resources.read_text(dipas.test.sequences, 'cryring.seq')
script = 'option, -echo;\n' + script
script += 'twiss, save, file = "twiss";'
result = run_script(script, ['twiss'], madx=os.path.expanduser('~/bin/madx'))
Here we added a TWISS
command to the script, which generates the file “twiss” which we then specified as a result in the call to run_script
. Let’s check the result
now:
[3]:
print(list(result))
print(type(result['twiss']))
print(result['twiss'].columns)
['stdout', 'stderr', 'twiss']
<class 'pandas.core.frame.DataFrame'>
Index(['NAME', 'KEYWORD', 'S', 'BETX', 'ALFX', 'MUX', 'BETY', 'ALFY', 'MUY',
'X',
...
'SIG54', 'SIG55', 'SIG56', 'SIG61', 'SIG62', 'SIG63', 'SIG64', 'SIG65',
'SIG66', 'N1'],
dtype='object', length=256)
The result['twiss']
is a pandas data frame, containing exactly the information from the generates “twiss” file.
Instead of adding the twiss
command manually we can also specify the keyword argument twiss=True
for run_script
which will take care of the necessary actions (+ add 'twiss'
as a requested result).
[4]:
with resources.path(dipas.test.sequences, 'cryring.seq') as path:
result = run_file(path, twiss=True, madx=os.path.expanduser('~/bin/madx'))
print(list(result))
print(type(result['twiss']))
['stdout', 'stderr', 'twiss']
<class 'tuple'>
Alternatively we could also provide a dict
for the twiss
keyword argument in order to specify the various arguments for the twiss
command.
Retrieving meta data from output files
If we wanted the meta information, at the beginning of the “twiss” file and prefixed by “@”, as well we can specify the resulting files that we want to retrieve as a dict instead: keys are file names and values are bools, indicating whether we want the meta information for that particular file or not:
[5]:
result = run_script(script, {'twiss': True}, madx=os.path.expanduser('~/bin/madx'))
print(type(result['twiss']))
print(type(result['twiss'][0]))
print(type(result['twiss'][1]))
<class 'tuple'>
<class 'pandas.core.frame.DataFrame'>
<class 'dict'>
There also exists a shorthand syntax for requesting meta data, namely specifying '<filename>+meta'
in the results list:
[6]:
result = run_script(script, ['twiss+meta'], madx=os.path.expanduser('~/bin/madx'))
print(type(result['twiss']))
<class 'tuple'>
[7]:
from pprint import pprint
pprint(result['twiss'][1])
{'ALFA': 0.1884508489,
'BCURRENT': 0.0,
'BETXMAX': 7.14827132,
'BETYMAX': 7.958073519,
'BV_FLAG': 1.0,
'CHARGE': 1.0,
'DATE': '05/02/21',
'DELTAP': 0.0,
'DQ1': -4.394427712,
'DQ2': -10.92147539,
'DXMAX': 5.852905623,
'DXRMS': 4.993097628,
'DYMAX': 0.0,
'DYRMS': 0.0,
'ENERGY': 1.0,
'ET': 0.001,
'EX': 1.0,
'EY': 1.0,
'GAMMA': 1.065788933,
'GAMMATR': 2.303567544,
'KBUNCH': 1.0,
'LENGTH': 54.17782237,
'MASS': 0.9382720813,
'NAME': 'TWISS',
'NPART': 0.0,
'ORBIT5': -0.0,
'ORIGIN': '5.05.02 Linux 64',
'PARTICLE': 'PROTON',
'PC': 0.3458981085,
'Q1': 2.42,
'Q2': 2.419999999,
'SEQUENCE': 'CRYRING',
'SIGE': 0.001,
'SIGT': 1.0,
'SYNCH_1': 0.0,
'SYNCH_2': 0.0,
'SYNCH_3': 0.0,
'SYNCH_4': 0.0,
'SYNCH_5': 0.0,
'TIME': '22.08.23',
'TITLE': 'no-title',
'TYPE': 'TWISS',
'XCOMAX': 0.0,
'XCORMS': 0.0,
'YCOMAX': 0.0,
'YCORMS': 0.0}
If meta information is requested, the result is a tuple containing the actual file content as a data frame and the meta data as a dict.
Requested output files will be converted according to their suffix (if present) or if they have a special file name. File names ending in "one"
are assumed to have emerged from TRACKONE
and are parsed accordingly. File names ending in .madx
or .seq
are assumed to be MADX files and parsed versions are returned (see madx.parser.parse_file
). Otherwise it is attempted to convert the file from TFS format to a pandas.DataFrame
. If this fails, then the raw file content is
returned as a string. We can also request explicitly that a file should be treated according to a given format via <filename>;tfs
. Here the file format is specified after a semicolon. The following formats are available:
madx
: Will be parsed according tomadx.parser.parse_file
.raw
: Returns the raw file content as a string.tfs
: Converts from TFS format topandas.DataFrame
.trackone
: Converts from special TRACKONE-TFS format topandas.DataFrame
.
For more details see madx.utils.convert
.
[8]:
result = run_script(script, ['twiss;raw'], madx=os.path.expanduser('~/bin/madx'))
print(type(result['twiss']), end='\n\n')
pprint(result['twiss'].splitlines()[:5])
<class 'str'>
['@ NAME %05s "TWISS"',
'@ TYPE %05s "TWISS"',
'@ SEQUENCE %07s "CRYRING"',
'@ PARTICLE %06s "PROTON"',
'@ MASS %le 0.9382720813']
Configure scripts before running
Now let’s inspect the resulting data frame, for example the orbit:
[9]:
result = run_script(script, ['twiss'], madx=os.path.expanduser('~/bin/madx'))
twiss = result['twiss']
print(twiss[['X', 'Y']].describe())
X Y
count 184.0 184.0
mean 0.0 0.0
std 0.0 0.0
min 0.0 0.0
25% 0.0 0.0
50% 0.0 0.0
75% 0.0 0.0
max 0.0 0.0
Since the lattice does not contain any zeroth order kicks the orbit is just zero. We can change that by modifying (configuring) the script while we run it. For that purpose we can use the variables
parameter. This parameter allows for replacing in variable definition of the form name :?= value;
the value
with a new_value
.
[10]:
result = run_script(script, ['twiss'], variables={'k02kh': 0.005}, madx=os.path.expanduser('~/bin/madx'))
print(result['twiss'][['X', 'Y']].describe())
X Y
count 184.000000 184.0
mean 0.002166 0.0
std 0.009515 0.0
min -0.016486 0.0
25% -0.008191 0.0
50% 0.005374 0.0
75% 0.008827 0.0
max 0.016840 0.0
Since we configured one of the horizontal kickers to have a non-zero kick strength the horizontal orbit changed but the vertical orbit remained zero (no coupling in the lattice).
Compute the Orbit Response Matrix
Using the madx.utils.run_orm
function we can compute the orbit response matrix for the given lattice, by specifying a list of kicker and monitor labels. The ORM will be computed using these kickers and measured at these monitors:
[11]:
kickers = ['yr04kh', 'yr06kh', 'yr08kh', 'yr10kh']
monitors = ['yr02dx1', 'yr03dx1', 'yr03dx4']
orm = run_orm(script, kickers=kickers, monitors=monitors, madx=os.path.expanduser('~/bin/madx'))
print(orm, end='\n\n')
print(orm.loc['X'], end='\n\n')
yr04kh yr06kh yr08kh yr10kh
X yr02dx1 1.092090 -2.951312 3.234832 -2.705921
yr03dx1 1.808234 -1.395442 0.589074 0.474549
yr03dx4 1.640022 -0.751784 -0.167347 1.159677
Y yr02dx1 0.000000 0.000000 0.000000 0.000000
yr03dx1 0.000000 0.000000 0.000000 0.000000
yr03dx4 0.000000 0.000000 0.000000 0.000000
yr04kh yr06kh yr08kh yr10kh
yr02dx1 1.092090 -2.951312 3.234832 -2.705921
yr03dx1 1.808234 -1.395442 0.589074 0.474549
yr03dx4 1.640022 -0.751784 -0.167347 1.159677
Since we chose only horizontal kickers, the vertical ORM is zero (no coupling).
Direct comparison with MADX
The dipas.aux
module provides the compare_with_madx
function which can be used to compare Twiss and ORM results directly. It returns three dataframes, df_global, df_twiss, df_orm
which hold the global lattice parameters, lattice functions and Orbit Response Matrix data for both DiPAS and MADX. Each dataframe has a multi-level row index where the first level contains the keys ['dipas', 'madx']
which can be used to access the different versions.
[12]:
import numpy as np
from dipas.aux import compare_with_madx
from dipas.build import from_file
with resources.path('dipas.test.sequences', 'cryring.seq') as f_path:
lattice = from_file(f_path)
df_global, df_twiss = compare_with_madx(lattice, orm=False, madx=os.path.expanduser('~/bin/madx'))
print(df_global.loc['dipas'] - df_global.loc['madx'], end=2*'\n')
print(np.abs(df_twiss.loc['dipas'] - df_twiss.loc['madx']).max(), end=2*'\n')
Q1 4.532117e-10
Q2 2.747775e-10
dtype: float64
x 0.000000e+00
px 0.000000e+00
y 0.000000e+00
py 0.000000e+00
bx 4.934237e-10
ax 4.981462e-10
mx 4.939862e-10
by 4.991119e-10
ay 4.896188e-10
my 4.978684e-10
dx 3.150174e-07
dpx 8.382842e-08
dy 0.000000e+00
dpy 0.000000e+00
dtype: float64
There exists also a command line utility which can be used to check whether the results agree with MADX: dipas verify /path/to/script.madx
.