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 to madx.parser.parse_file.

  • raw: Returns the raw file content as a string.

  • tfs: Converts from TFS format to pandas.DataFrame.

  • trackone: Converts from special TRACKONE-TFS format to pandas.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.