18 Minimal set of methods to build an OPAL simulation and run it.
19 This takes about 0.1 s to run on my average desktop PC.
25 import pyopal.objects.track_run
26 import pyopal.objects.beam
27 import pyopal.objects.distribution
28 import pyopal.objects.line
29 import pyopal.elements.ring_definition
30 import pyopal.elements.local_cartesian_offset
31 import pyopal.objects.field_solver
32 import pyopal.objects.track
33 import pyopal.objects.option
36 """Class to run a minimal OPAL setup.
38 Individual methods make_foo handle set up of each OPAL "global" object:
46 Explicitly, the OPAL routines are only called in make_foo and run_one so
47 that there is no OPAL things initialised and run_one_fork can be safely
50 There are three hooks for user to overload and do stuff:
51 - make_element_iterable: add extra elements to the line
52 - preprocess: add some stuff to do just before tracking starts
53 - postprocess: add some stuff to do just after tracking ends
54 user can do postprocessing after calling run_one, but run_one_fork isolates
55 memory allocation into a forked process so parent process does not have
56 access to memory for e.g. checking details of the field maps, etc.
59 """Initialise to empty data"""
84 """Make an empty fieldsolver
86 The fieldsolver has the job of solving the space charge problem on
87 successive time steps. In this example, the FieldSolver is switched off
88 (i.e. set to type = "NONE").
90 self.
field_solver = pyopal.objects.field_solver.FieldSolver()
91 self.field_solver.set_opal_name(
"DefaultFieldSolver")
92 self.field_solver.type =
"NONE"
93 self.field_solver.mesh_size_x = 5
94 self.field_solver.mesh_size_y = 5
95 self.field_solver.mesh_size_t = 5
96 self.field_solver.parallelize_x =
False
97 self.field_solver.parallelize_y =
False
98 self.field_solver.parallelize_t =
False
99 self.field_solver.boundary_x =
"open"
100 self.field_solver.boundary_y =
"open"
101 self.field_solver.boundary_t =
"open"
102 self.field_solver.bounding_box_increase = 2
103 self.field_solver.register()
106 """Make a distribution
108 The distribution is the initial beam distribution of the PyOPAL
109 simulation. In this example, a distribution loaded from a tempfile is
110 called, where the tempfile is written to disk dynamically at runtime.
116 self.
distribution = pyopal.objects.distribution.Distribution()
117 self.distribution.set_opal_name(
"DefaultDistribution")
118 self.distribution.type =
"FROMFILE"
120 self.distribution.register()
126 The beam holds the global/default beam distribution information, such as
127 the mass of particles in the beam, number of particles in the beam and
130 beam = pyopal.objects.beam.Beam()
131 beam.set_opal_name(
"DefaultBeam")
132 beam.mass = self.
mass
136 beam.number_of_slices = 10
137 beam.number_of_particles = int(self.distribution_str.split()[0])
138 beam.momentum_tolerance = 0
144 """Returns a drift of length 0
146 OPAL requires at least one element in each beam line. For this simplest
147 example a drift of length 0 is used.
149 drift = pyopal.elements.local_cartesian_offset.LocalCartesianOffset()
150 drift.set_opal_name(
"DefaultDrift")
151 drift.end_position_x=0.0
152 drift.end_position_y=0.0
153 drift.end_normal_x=0.0
154 drift.end_normal_y=1.0
158 """Make a RingDefinition object.
160 The RingDefinition holds default parameters for a ring initial
161 conditions, in particular the initial cylindrical coordinates for the
162 first element placement and beam, and the minimum and maximum radius
163 allowed before particles are considered lost. The ring can be appended
164 to self.line and used with OPAL cyclotron mode.
166 self.
ring = pyopal.elements.ring_definition.RingDefinition()
167 self.ring.set_opal_name(
"DefaultRing")
168 self.ring.lattice_initial_r = self.
r0
169 self.ring.beam_initial_r = self.
r0
170 self.ring.minimum_r = self.
r0/2
171 self.ring.maximum_r = self.
r0*100
172 self.ring.is_closed =
False
175 """Options enable setting of global control variables.
177 No options are set by default. For a full list of variables see the
180 self.
option = pyopal.objects.option.Option()
181 self.option.info = self.
verbose > 1
182 self.option.warn = self.
verbose > 0
183 self.option.execute()
186 """Make a Line object.
188 The Line holds a sequence of beam elements.
190 self.
line = pyopal.objects.line.Line()
191 self.line.set_opal_name(
"DefaultLine")
193 self.line.append(self.
ring)
199 for element
in an_element_iter:
200 self.line.append(element)
204 """Make a track object.
206 The track object handles the interface between tracking and the beam
209 track = pyopal.objects.track.Track()
210 track.line =
"DefaultLine"
211 track.beam =
"DefaultBeam"
220 The TrackRun handles the interface between the Track, distribution,
221 field solver and calls the actual tracking routines.
223 run = pyopal.objects.track_run.TrackRun()
224 run.method =
"CYCLOTRON-T"
225 run.keep_alive =
True
226 run.beam_name =
"DefaultBeam"
227 run.distribution = [
"DefaultDistribution"]
228 run.field_solver =
"DefaultFieldSolver"
233 Return an iterable (e.g. list) of elements to append to the line
235 By default, returns an empty list. User can overload this method.
240 """Perform any preprocessing steps just before the trackrun is executed
242 This method can be overloaded with user required steps.
247 """Perform any postprocessing steps after the tracking is executed
249 This method can be overloaded with user required steps.
254 """Set up and run a simulation"""
257 os.chdir(self.tmp_dir)
259 self.make_distribution()
260 self.make_field_solver()
265 self.make_track_run()
267 self.track_run.set_run_name(self.run_name)
269 self.track_run.execute()
275 print(
"Finished running in directory", os.getcwd())
280 Set up and run a simulation in a fork of the current process. The
282 This method is memory safe - resources are only created in the
283 forked process, which is destroyed when the process concludes. The
284 downside is that resources (e.g. lattice objects, etc) are destroyed
285 when the process concludes. If something is needed, overload the
286 preprocess and postprocess routines to do anything just before or just
289 Returns the return code of the forked process, given by self.exit_code
290 from the forked MinimalRunner. This can be used as a simple flag for
291 comms from the child to the parent process (e.g. for testing purposes).
292 For example, postprocess(self) can be overloaded to set an exit code.
294 Tested in linux, I don't know about OSX. Unlikely to work in any
304 retvalue = os.waitpid(a_pid, 0)[1]
307 distribution_str =
"""1
308 0.0 0.0 0.0 0.0 0.0 0.0
311 ring_error =
"Failed to append ring to the line. Try running make_ring()"+\
312 " before calling make_line()."
316 runner.execute_fork()
318 if __name__ ==
"__main__":
def make_element_iterable