.. include:: include.txt ====================== PyZgoubi Documentation ====================== The latest version of PyZgoubi and this document can be found at http://www.hep.manchester.ac.uk/u/sam/pyzgoubi/ Introduction ------------ http://sourceforge.net/projects/zgoubi Zgoubi is a particle tracking code maintained by François Méot. PyZgoubi is an interface to Zgoubi writing in python. It aims to make input files that are easy to read and can contain calculations, loops, and any other python program feature. A basic knowledge of http://python.org/ Python is useful to use PyZgoubi. Python is a general purpose, high level, interpreted programming language. I recommend the http://www.python.org/doc/2.5.2/tut/tut.html Official Python Tutorial. Quick Start ----------- Put the following in a text file named quickstart.py:: make_line('line') # create and OBJET2 and add 1 electron to it ob = OBJET2() ob.set(BORO=ke_to_rigidity(10e6, ELECTRON_MASS)) ob.add(Y=0, T=0.1, D=1) add(ob) add(FAISCNL(FNAME='zgoubi.fai')) # record point d1 = DRIFT(XL=50) q1 = QUADRUPO(XL=20, B_0=5, R_0=10, XPAS=(10,20,10)) d2 = DRIFT(XL=50) q2 = QUADRUPO(XL=20, B_0=-5, R_0=10, XPAS=(10,20,10)) add(d1, q1, d2, q2) add(FAISCNL(FNAME='zgoubi.fai')) # record point add(END()) print output() # file fed to zgoubi (zgoubi.dat) run() # run the line in zgoubi print res() # show the zgoubi.res file print get_all('fai') # so the data recorded with FAISCNL The file can also be found in examples/quickstart.py Run it with:: pyzgoubi quickstart.py You should see the zgoubi data file, the output from zgoubi, the res file from zgoubi, and the coordinates at the 2 FAISCNL points displayed to the terminal. For more advance use it may be better store the |Line| with a name, this allows you to work with multiple |Lines| simultaneously. The following shows the same file, but with a named |Line|:: myline = Line('line') # create and OBJET2 and add 1 electron to it ob = OBJET2() ob.set(BORO=ke_to_rigidity(10e6, ELECTRON_MASS)) ob.add(Y=0, T=0.1, D=1) myline.add(ob) myline.add(FAISCNL(FNAME='zgoubi.fai')) # record point d1 = DRIFT(XL=50) q1 = QUADRUPO(XL=20, B_0=5, R_0=10, XPAS=(10,20,10)) d2 = DRIFT(XL=50) q2 = QUADRUPO(XL=20, B_0=-5, R_0=10, XPAS=(10,20,10)) myline.add(d1, q1, d2, q2) myline.add(FAISCNL(FNAME='zgoubi.fai')) # record point myline.add(END()) print myline.output() # file fed to zgoubi (zgoubi.dat) myresults = myline.run() # run the line in zgoubi print myresults.res() # show the zgoubi.res file print myresults.get_all('fai') # so the data recorded with FAISCNL Generating zgoubi.dat files --------------------------- The simplest level of Pyzgoubi is to use it to generate a zgoubi.dat file. You can then run Zgoubi with the file to get your results. PyZgoubi works with |Lines|, which are made up of Elements. The Elements are mostly the same as those used in Zgoubi. Most Elements correspond to magnets and other beam line elements of a physical accelerator, e.g. DIPOLE, MUTIPOL and CAVITE. Some are used to define the beam e.g. OBJET2. Some are to control the execution of Zgoubi, e.g. FAISCNL, MATRIX and END. A number of these Elements can be added to a |Line|. This |Line| then has all the information needed to make a zgoubi.dat file. A |Line| can be created as follows:: emma = Line('emma simulation') emma is the name used to refer to the |Line| in the program. 'emma simulation' is the text that will be put at the start of the zgoubi.dat file. To create an Element use:: q1 = QUADRUPO('defoc', XL=20, R_0=2, B_0=2, XPAS=0.1) This can now be added to the |Line| :: emma.add(q1) These last 2 steps can be contracted:: emma.add(QUADRUPO('defoc', XL=20, R_0=2, B_0=2, XPAS=0.1)) however this means that there is no reference to the element that could be used for modifying its parameters later, e.g.:: q1.set(B_0=3) The |Line| can now be used to output a zgoubi.dat using the output() function:: print emma.output() All these instructions can be put in a text file and run using the command pyzgoubi. (pyzgoubi is an alias set up by the installer). Below is a section of emma.py from the examples:: emma = Line('emma') xpas = (20,20,20) cells = 42 angle = 360/cells d_offset = -34.048 * mm f_offset = -7.514 * mm #lengths ld = 210 * mm sd = 50 * mm fq = 58.782 * mm dq = 75.699 * mm # quad radius fr = 37 * mm dr = 53 * mm fb = -6.695 * fr * T db = 4.704 * dr * T ob = OBJET2() emma.add(ob) emma.add(ELECTRON()) emma.add(DRIFT('ld', XL=ld*cm_/2)) emma.add(CHANGREF(ALE=angle)) emma.add(CHANGREF(YCE=d_offset*cm_)) emma.add(QUADRUPO('defoc', XL=dq*cm_, R_0=dr*cm_, B_0=db*kgauss_, XPAS=xpas)) emma.add(CHANGREF(YCE=-d_offset*cm_)) emma.add(DRIFT('sd', XL=sd*cm_)) emma.add(CHANGREF(YCE=f_offset*cm_)) emma.add(QUADRUPO('foc', XL=fq*cm_, R_0=fr*cm_, B_0=fb*kgauss_, XPAS=xpas)) emma.add(CHANGREF(YCE=-f_offset*cm_)) emma.add(DRIFT('ld', XL=ld*cm_/2)) emma.add(FAISCNL(FNAME='zgoubi.fai')) emma.add(REBELOTE(K=99, NPASS=10)) emma.add(END()) rigidity = ke_to_rigidity(10e6, 0.51099892e6) ob.set(BORO=-rigidity) ob.add(Y=0, T=0, D=1) print emma.output() This can be run with the command:: pyzgoubi emma.py It will build a |Line| and print the zgoubi.dat input to the screen. The '.py' extension is not necessary, but will cause your text editor to use python syntax highlighting. Particles / Particuls """"""""""""""""""""" Zgoubi allows a particles parameters such as charge, mass and half-life to be set with the PARTICUL element. These not needed for basic tracking, as the rigidity is given in the OBJET element. PyZgoubi offers some pre-made particles:: ELECTRON PROTON MUON IMMORTAL_MUON The IMMORTAL_MUON is a muon with an infinite lifetime, for use when decay is not needed. Anti particles with opposite can me made by negating a particle. eg:: m = -MUON() my_line.add(m) #or my_line.add(-MUON()) The masses and charges are defined in zgoubi/constants.py Defining Elements ----------------- There are two ways Elements can be defined in pyzgoubi. Most Elements are simple, they have a static list of parameters. Some have some extra complexity, for example different parameters depending on options, sections repeated N times. These elements can be defined using a simple syntax, which is then converted into python code. More complex elements must be written in python. The simple elements are defined in defs/simple_elements.defs. For each element there is a number of lines of text, delimited by blank lines. Comments can be put after a '#' character. The first line gives the name of the class, this is the name you use in the input file. The second line gives the name used by Zgoubi, this must match the Zgoubi manual. Then follows a line for each line of output in the zgoubi.dat file; first the names of the parameters, then a ':', then the types. For example:: BEND BEND IL : I XL, Sk, B1 : 3E X_E, LAM_E, W_E : 3E N, C_0, C_1, C_2, C_3, C_4, C_5 : I,6E X_S, LAM_S, W_S : 3E NS, CS_0, CS_1, CS_2, CS_3, CS_4, CS_5 : I,6E XPAS: X KPOS, XCE, YCE, ALE : I,3E The types can be: - I : integer - E : real (floating point) - Ax : string with up to x characters - X : special type for XPAS. Can be integer, or group of 3 integers e.g. (10,20,10) The type can be followed by a number for several parameters of the same type. If the parameters used vary depending on the value of another option the following syntax can be used:: CAVITE CAVITE IOPT : I !IOPT==0 X, X : 2E !IOPT==1 L, h : 2E V, X : 2E !IOPT==2 L, h : 2E V, sig_s : 2E !IOPT==3 X,X : 2E V, sig_s : 2E Here the value of IOPT switches the element to output different parameters. (See the zgoubi manual's description of CAVITE for more info). Elements with a looped section can be defined as follows:: FFAG FFAG IL : I N, AT, RM: I,2E !N*{ ACN, DELTA_RM, BZ_0, K: 4E G0_E, KAPPA_E: 2E NCE, CE_0, CE_1, CE_2, CE_3, CE_4, CE_5, SHIFT_E: I,7E OMEGA_E, THETA_E, R1_E, U1_E, U2_E, R2_E: 6E G0_S, KAPPA_S: 2E NCS, CS_0, CS_1, CS_2, CS_3, CS_4, CS_5, SHIFT_S: I,7E OMEGA_S, THETA_S, R1_S, U1_S, U2_S, R2_S: 6E G0_L, KAPPA_L: 2E NCL, CL_0, CL_1, CL_2, CL_3, CL_4, CL_5, SHIFT_L: I,7E OMEGA_L, THETA_L, R1_L, U1_L, U2_L, R2_L: 6E !} KIRD, RESOL: 2I XPAS: E KPOS, RE, TE, RS, TS: I,4E Here the section between the braces is repeated. These elements are used slightly differently to simpler elements. Then non looping section is defined normally.:: triplet = FFAG('triplet', IL=0, AT=10 ... ) Then the looped part can be added:: triplet.add(ACN = 6, BZ_0 = 0.5 ...) triplet.add(ACN = 4, BZ_0 = -0.5 ...) triplet.add(ACN = 6, BZ_0 = 0.5 ...) N gets automatically set. All the looped parts can be removed using:: triplet.clear() When pyzgoubi runs it searches the defs folder for files ending in .defs. Additional files can be added to the extra_defs_files list in zgoubi_settings.py. If any of these files have been modified then they are reread and the defs.py is regenerated. The Elements that cannot be defined in this way must be put into the static_defs.py file. They must be classes that have an output() method, which generates the code needed for the zgoubi.dat file. There is also a FAKE_ELEM element. This allows you to put arbitrary text into the zgoubi.dat file. It is useful for using an Zgoubi element that pyzgoubi does not have a definition for. For example:: change_txt = """'CHANGREF' 5.0 0 10.0 """ change = FAKE_ELEM(change_txt) line.add(change) Available Elements """""""""""""""""" - BEND - CAVITE - CHANGREF - DRIFT - ELECTRON - END - FAISCEAU - FAISCNL - FAISTORE - FAKE_ELEM - FFAG - IMMORTAL_MUON - MARKER - MATRIX - MULTIPOL - MUON - OBJET1 - OBJET2 - OBJET5 - PARTICUL - PROTON - QUADRUPO - REBELOTE - TOSCA To find the full list of elements available in the current version run:: pyzgoubi help elements To find the names of the parameters available for an element use:: pyzgoubi help element_name e.g.:: pyzgoubi help MULTIPOL Use this in combination with the Zgoubi manual. Running Zgoubi -------------- Once a |Line| has been created and had the needed elements added it can be run. PyZgoubi will take care of creating a temporary directory, creating the zgoubi.dat file and running Zgoubi. This is done to prevent zgoubi from overwriting any existing files. If you wish to keep any of the output files you must use the commands to copy these to where you want them. The following example shows how to run a |Line|:: #create line emma = Line('emma') #add elements emma.add( ... ) ... #run line emma_res = emma.run() #save output emma_res.save_res("emma.res") emma_res.save_plt("emma.plt") Note that you will need to make sure your |Line| will actually create plt or fai files, otherwise you will receive a file not found error. See the Zgoubi manual for more information. The |run| function can take several options. If you want to inspect the directory where zgoubi is run, or to use zpop, then set xterm=True. If you want to change the directory that zgoubi is run in you can use the tmp_prefix option. It is best to make sure this is a local disk (i.e. not a network/remote disk). The default directory can be set in the zgoubi_settings.py file.:: emma.run(xterm=True) emma.run(tmp_prefix = '/var/tmp/sam/') emma.run(xterm=True, tmp_prefix = '/home/sam/tmp/') If you want to do analysis of the simulation you can use the |Results| object that is returned by the |run| function.:: res = emma.run() See the |Results| Object chapter for more info. Each time a |Line| is run a temporary director is created. These are normally automatically cleared up when PyZgoubi finishes (also the /tmp directory is usually emptied when a computer shuts down). However if you are making repeated calls to |run|, then you may want to manually clear away these files. This can be done with the |clean| function. Don't clean the |Results| until you have finished working with its output files.:: res = emma.run() res.clean() Results Object -------------- When you run a |Line| it creates a |Results| object, that can be used to get information about the paths of the particles.:: res = pamela.run() |get_all| and |get_track|, let you get lists of the particle coordinates. They each need to be told if they should read the plt (points within the magnetic elements) or fai (beam at FAISCNL element). |get_all| returns a list of dictionaries, containing all the coordinates and information. |get_track| returns a list of lists of just the requested coordinates.:: print res.get_all('plt') print res.get_all('fai') print res.get_track('fai', ['Y','T']) Bunch Objects ------------- New in Pyzgoubi 0.4 The |Bunch| object represents a bunch of particles. Each particle has coordinates D, Y, T, Z, P, S, tof, and X, and the whole bunch has the shared properties rigidity/kinetic energy, particle mass, particle charge. These are stored in m, rad, eV, s, and automatically converted to Zgoubi units by the associated functions. It can be used in 2 ways. Firstly with the OBJET_bunch element, which behaves similarly to the real OBJET elements. Secondly it can be used to drive Zgoubi in a more abstracted method. To use with OBJET_bunch, first create a bunch, then create an OBJET_bunch, then add it to a |Line|:: my_bunch = Bunch(nparticles=5, ke=1e6, mass=PROTON_MASS, charge=1) my_bunch.particles()[0]['Y'] = 3 ... ob = OBJET_bunch(bunch=my_bunch) my_line = Line("a line") my_line.add(ob) my_line.add( ... ) This |Line| can then be run as before. The second method is to create a |Line| that has no OBJET, PARTICUL or END elements, only beamline elements. Then the |Line.track_bunch| method can be called, which will return the tracked bunch:: my_line = Line("a line") my_line.add( QUADRUPO( ... ) ) my_line.add( DRIFT( ... ) ) ... my_bunch2 = my_line.track_bunch(my_bunch) This allows tracking a bunch around multiple lattices. Suppose that you create 3 |Lines|: injecttion_line, ring and extraction_line. You can then take a bunch through each in turn:: my_bunch = Bunch( ... ) my_bunch = injecttion_line.track_bunch(my_bunch) for n in xrange(n_laps): my_bunch = ring.track_bunch(my_bunch) my_bunch = extraction_line.track_bunch(my_bunch) Note that this method does not allow you to access the Result object. If you have a multi-CPU or multi-core CPU, then you can swap |Line.track_bunch| for the multithreaded version |Line.track_bunch_mt|. The multithreaded version also has the advantage that it can track an arbitrarily large bunch (more than Zgoubi's max particles limit). Loops ----- For making complex |Lines| it can be useful to use python features such as loops, e.g. to put 5 identical FODO cells you could use :: line = Line("example") line.add( ... ) line.add( ... ) for x in range(5): line.add(QUADRUPO( ... )) line.add(DRIFT( ... )) line.add(QUADRUPO( ... )) line.add(DRIFT( ... )) line.add( ... ) Note that the for loop block lasts as long as the code is indented. If you want to make one iteration different then you can do a test based on the x :: for x in range(5): line.add(QUADRUPO( ... )) line.add(DRIFT( ... )) line.add(QUADRUPO( ... )) line.add(DRIFT( ... )) if (x == 2): #note x counts from zero line.add(DIPOLE( ... ) Command line arguments ---------------------- There is a convenience function for using command line arguments. For example near the start of the code put:: number_of_laps = int(get_cmd_param('laps', 10)) Then when creating the |Line| use:: l.add(REBELOTE(NPASS=number_of_laps-1, KWRIT=1, K=99)) to access the variable. When you run the your simulation use the command line argument:: pyzgoubi sim.py laps=50 I the second parameter to get_cmd_param() is the default. If you don't give a value as an argument then the default is used. If you don't give a default eg:: particle_energy = float(get_cmd_param('energy')) then you will receive and error if you don't provide a value as an argument. Units ----- PyZgoubi does not do any automatic unit conversion. When you give a parameter you must give the units that Zgoubi expects. However PyZgoubi does define some values to save some effort, in the conversion. Any of the following will set x to 2:: x = 2 * m x = 200 * cm x = 2000 * mm Then to output x in a different unit:: print x * m_ , "m" print x * cm_ , "cm" print x * mm_ , "mm" So for example DRIFT expects cm, but your lattice might use meters, so do:: d_length = 0.6 * m DRIFT('d1', XL=d_length*cm_) Currently the following units are defined:: m cm mm T kgauss but more can be added on request. For conversion between degrees and radians use the python math functions:: degrees(2*pi) # gives 360 radians(180) # gives 3.1416... Settings and Options -------------------- PyZgoubi settings are stored in a file .pyzgoubi/settings.ini in your home folder. It is automatically created the first time pyzgoubi is used. It can be used to customise some PyZgoubi options. The following keys can be set:: extra_defs_files Give the path of any additional definition files you want to be considered:: tmp_dir Where temporary files should be written, this is most likely /tmp/, but in some case you may wish to use /var/tmp/ or a ramdisk /dev/shm/:: zgoubi_path The path to the zgoubi binary file. Note that this can also be set with the commandline option --zgoubi=/path/to/zgoubi. Debugging and Profiling """"""""""""""""""""""" When installing PyZgoubi it will suggest adding some aliases to your .bashrc file. These are:: pyzgoubi - run PyZgoubi normally pyzgoubii - run PyZgoubi, and start an interactive python shell when its done, or fails pyzgoubip - run PyZgoubi, and save profiling information to prof.log The interactive shell can be used to check the values of variables at the point of a crash. The profiling information can be read with the python pstats module (part of cProfile). For example to see in which functions most time was spent run:: python -c "import pstats; pstats.Stats('prof.log').sort_stats('cumulative').print_stats()" or start a python shell and run:: >>> import pstats >>> p = pstats.Stats('prof.log') >>> p.sort_stats('cumulative').print_stats() Logging levels """""""""""""" The verbosity of PyZgoubi can be adjusted. By default the log_level is set to 'warn', so only warnings and error messages are printed. One can raise the level to 'debug' which will also show debug messages. To do this for a single run use:: pyzgoubi --debug script or pyzgoubi --log_level=debug script or you can adjust the value in the settings.ini file:: log_level = debug Other levels can also be set, see http://docs.python.org/library/logging.html for more information. Upgrade Notes ------------- Although it would be nice to have perfect backwards compatibility that sometimes interferes with progress, and things have to break. There should be no breaks between a version X.Y.Z and X.Y.Z+1, but there can be between X.Y.Z and X.Y+1.0. 0.3.x to 0.4.x """""""""""""" PyZgoubi 0.4 supports the new fai/plt output formats introduced into Zgoubi in early 2010. These have a header that labels the columns. Reading the new format was taken as an opportunity to use numpy more extensively. If an older version of Zgoubi (5.1 or 5.0) is being used then the old fai/plt reading code will be used, and data will be returned as python dictionaries and arrays. If a newer version of Zgoubi is being used (SVN r251 or newer), then Results.get_all() will return a `numpy structured array `_ with the column names. Some column names will change when using the new fai/plt files. This is because older versions of PyZgoubi muddled the S and X coordinates. Also 'D' and 'D0' are now the more accurate 'D-1' and 'D0-1'. The coordinate name is now taken from the fai/plt file directly. When using the new fai/plt files labels are stored as a fixed length string, and so include any whitespace, e.g. 'foc' vs. 'foc'. To get back to the short version use strip(), e.g.:: label1 = 'foc ' label_short = label1.strip() #or labels1 = ['foc ', 'defoc '] labels2 = [x.strip() for x in labels1] The 'tol' parameter in find_closed_orbit() now is a measure of convergence rather than area. This should give better results over a wide range of scales. However, a large 'tol' is needed to give the same degree of accuracy. If you previously had tol=1e-10, then it may no longer converge, but if you change it to tol=1e-6 you will get a similar result to before. Some obsolete functions have been removed: Results.plt_to_csv(), Results.get_all_old(), Line.split_line() Some obsolete functions have been marked as deprecated, and will give warnings when used. These will likely be removed in a future version of PyZgoubi, unless you let me know that they are worth keeping. Some of these functions are old and undocumented, and I suspect no one uses them. Some have just move, like the functions for directly accessing and saving res and fai files (moved from Line to Results). If you do not care about modifying you're code to prepare for a future version then pass the argument ``-W ignore::DeprecationWarning`` to python. Tips ---- Python hints """""""""""" If there is a '#' character on a line, everything after it is treated as a comment. Python uses whitespace to delimit blocks (instead of braces '{' and '}' in C/C++). The PyZgoubi code uses tabs, so it is best to use tabs in your input files. If you get an 'IndentationError' check that you have not mixed spaces and tabs, or accidentally started a line with a space/tab. Python identifiers (variable, function, object names etc) are case sensitive, they must start with a letter and only contain letters, numbers and underscores. Zgoubi hints """""""""""" Errors ~~~~~~ sfe: formatted io not allowed:: Zgoubi, version 5.0.0. Job started on 05-Feb-09, at 15:00:29 Copying zgoubi.dat into zgoubi.res, numbering and labeling elements... acc Zgoubi Version 5.0.0. 366/ 367 REBELOTE sfe: formatted io not allowed apparent state: unit 21 named zgoubi.res lately writing sequential formatted external IO This may mean that you have tried to write output to both ascii and binary files, eg zgoubi.fai and b_zgoubi.fai