Error Studies with Parallelised Pynac

Many types of accelerator error studies are highly parallel due to the fact that each run of the simulation does not depend at all on any other runs. For example, studying the effect of a random misalignment of an accelerator component by running a large number of studies with different misalignments. This type of study can be easily performed with Pynac as follows.

The strategy here will be to use the multiProcessPynac function from Pynac.Core to build a queue of different simulations, and to execute them in parallel.

First we import all the functionality that we need from general libraries, and then make sure we're working in a clean environment (i.e., no files hanging around from previous runs).


In [1]:
import numpy as np
import os
import glob
import shutil
import random
from contextlib import contextmanager

def clean():
    for outputdir in glob.glob('./dynacProc_*'):
        if os.path.isdir(outputdir):
            shutil.rmtree(outputdir)

clean()

Then import the necessary Pynac functionality.


In [2]:
from Pynac.Core import Pynac, multiProcessPynac, makePhaseSpaceList, getNumberOfParticles
from Pynac.Plotting import PynPlt
from Pynac.Elements import Quad, CavityAnalytic

Then import some functionality for plotting results.


In [3]:
from bokeh.io import output_notebook
from bokeh.plotting import figure, show
from bokeh.models.ranges import Range1d
from bokeh.models.axes import LinearAxis
output_notebook()


Loading BokehJS ...

Create and populate the working directory

In our case, the relevant files are in the ../tests directory, and so we copy them from there.


In [4]:
newDir = 'workingDir'
if os.path.isdir(newDir):
    shutil.rmtree(newDir)
os.mkdir(newDir)
filelist = [
    'ESS_with_SC_ana.in',
    'Spoke_F2F_field.txt',
    'MBL_F2F_field.txt',
    'HBL_F2F_field.txt',
    'ESS_RFQ_out_70mA.dst',
]
for f in filelist:
    shutil.copyfile('../tests/' + f, newDir + '/' + f)
os.chdir(newDir)

A function to do the hard work

We need a function that adds random errors to some components, and then runs Pynac. The function applyErrorsAndRunPynac applies a random error to the fields of all the quadrupoles, and then random errors to the amplitude and phase of all the accelerating cavities. It then runs Pynac on the resulting lattice.


In [5]:
random.seed(19781216)

def getRandomScaling(scale = 1e-3):
    return random.gauss(0, scale)

def applyErrorsAndRunPynac():
    a = Pynac('ESS_with_SC_ana.in')
    for ind in a.getXinds('QUADRUPO'):
        quad = Quad(a.lattice[ind])
        quad.scaleField(1 + getRandomScaling(scale = 1e-2))
        a.lattice[ind] = quad.dynacRepresentation()
    for ind in a.getXinds('CAVMC'):
        cav = CavityAnalytic(a.lattice[ind])
        cav.adjustPhase(getRandomScaling(2.0))
        cav.scaleField(1 + getRandomScaling(scale = 1e-3))
        a.lattice[ind] = cav.dynacRepresentation()
    a.run()

Running in parallel

Now run a large number of iterations (400 in this case) of this function. I've chosen 8 workers on my dual-threaded, quad-core, Macbook, since this gives me the best speed improvement. Your mileage my vary -- experiment a little to find the optimal number of workers for your machine.

Note that the multiProcessPynac function takes care of creating and populating the necessary new directories in order to prevent the different parallel runs clobbering each other. It needs to know which files are necessary to do the Pynac run, and so these are provided in the filelist input variable.


In [6]:
%%time
filelist =['ESS_with_SC_ana.in', 
           'ESS_RFQ_out_70mA.dst', 
           'Spoke_F2F_field.txt', 
           'MBL_F2F_field.txt', 
           'HBL_F2F_field.txt']
print(multiProcessPynac(filelist = filelist, pynacFunc = applyErrorsAndRunPynac, numIters = 400, max_workers = 8))


Running 0
Running 1
Running 5
Running 2
Running 3
Running 6
Running 4
Running 7
Running 10
Running 9
Running 8
Running 11
Running 12
Running 13
Running 14
Running 15
Running 16
Running 17
Running 18
Running 19
Running 20
Running 21
Running 22
Running 23
Running 24
Running 25
Running 27
Running 26
Running 28
Running 29
Running 30
Running 31
Running 32
Running 33
Running 34
Running 35
Running 36
Running 37
Running 38
Running 39
Running 40
Running 41
Running 42
Running 43
Running 44
Running 45
Running 46
Running 47
Running 48
Running 49
Running 50
Running 51
Running 52
Running 53
Running 54
Running 55
Running 56
Running 57
Running 58
Running 59
Running 60
Running 61
Running 62
Running 63
Running 64
Running 65
Running 66
Running 67
Running 68
Running 70
Running 69
Running 71
Running 73
Running 72
Running 74
Running 75
Running 76
Running 77
Running 78
Running 79
Running 80
Running 81
Running 82
Running 83
Running 84
Running 85
Running 86
Running 87
Running 88
Running 89
Running 91
Running 90
Running 92
Running 93
Running 94
Running 95
Running 96
Running 97
Running 98
Running 99
Running 100
Running 101
Running 102
Running 103
Running 104
Running 105
Running 106
Running 107
Running 108
Running 109
Running 110
Running 111
Running 112
Running 113
Running 114
Running 115
Running 116
Running 117
Running 118
Running 119
Running 120
Running 121
Running 122
Running 123
Running 124
Running 126
Running 125
Running 127
Running 128
Running 130
Running 129
Running 131
Running 132
Running 133
Running 134
Running 135
Running 136
Running 137
Running 138
Running 140
Running 139
Running 141
Running 142
Running 143
Running 144
Running 145
Running 146
Running 147
Running 148
Running 149
Running 150
Running 151
Running 152
Running 153
Running 154
Running 155
Running 156
Running 157
Running 158
Running 159
Running 160
Running 161
Running 162
Running 163
Running 164
Running 165
Running 166
Running 167
Running 168
Running 169
Running 170
Running 171
Running 172
Running 173
Running 174
Running 175
Running 176
Running 177
Running 178
Running 179
Running 180
Running 181
Running 182
Running 183
Running 184
Running 185
Running 186
Running 187
Running 188
Running 189
Running 190
Running 191
Running 192
Running 193
Running 194
Running 195
Running 196
Running 197
Running 198
Running 199
Running 200
Running 201
Running 202
Running 204
Running 203
Running 205
Running 206
Running 207
Running 208
Running 209
Running 210
Running 211
Running 212
Running 213
Running 214
Running 215
Running 216
Running 217
Running 218
Running 219
Running 220
Running 221
Running 222
Running 223
Running 224
Running 225
Running 226
Running 227
Running 228
Running 229
Running 230
Running 231
Running 233
Running 232
Running 234
Running 235
Running 236
Running 237
Running 238
Running 239
Running 240
Running 241
Running 242
Running 243
Running 244
Running 245
Running 246
Running 247
Running 248
Running 249
Running 250
Running 251
Running 252
Running 253
Running 254
Running 255
Running 256
Running 257
Running 258
Running 259
Running 260
Running 261
Running 262
Running 263
Running 264
Running 265
Running 267
Running 266
Running 268
Running 269
Running 270
Running 271
Running 272
Running 273
Running 274
Running 275
Running 276
Running 277
Running 278
Running 279
Running 280
Running 281
Running 282
Running 283
Running 284
Running 285
Running 286
Running 287
Running 288
Running 289
Running 291
Running 290
Running 292
Running 293
Running 294
Running 295
Running 296
Running 297
Running 298
Running 299
Running 300
Running 301
Running 302
Running 303
Running 304
Running 305
Running 306
Running 307
Running 308
Running 309
Running 310
Running 311
Running 312
Running 313
Running 314
Running 315
Running 316
Running 317
Running 318
Running 319
Running 320
Running 321
Running 322
Running 323
Running 324
Running 325
Running 326
Running 327
Running 328
Running 329
Running 330
Running 331
Running 332
Running 333
Running 334
Running 335
Running 336
Running 337
Running 339
Running 338
Running 340
Running 341
Running 342
Running 343
Running 344
Running 345
Running 346
Running 347
Running 348
Running 349
Running 350
Running 351
Running 352
Running 353
Running 354
Running 355
Running 356
Running 357
Running 358
Running 359
Running 360
Running 361
Running 362
Running 363
Running 364
Running 365
Running 366
Running 367
Running 368
Running 369
Running 370
Running 371
Running 372
Running 373
Running 374
Running 375
Running 376
Running 377
Running 378
Running 379
Running 380
Running 381
Running 382
Running 383
Running 384
Running 385
Running 386
Running 387
Running 388
Running 389
Running 390
Running 391
Running 392
Running 393
Running 394
Running 395
Running 396
Running 397
Running 398
Running 399
No errors encountered
CPU times: user 403 ms, sys: 224 ms, total: 626 ms
Wall time: 4min 7s

Analysis

To analyse the output of the parallel run, we need to take account of the fact that each execution of the simulation will have been done in a different sub-directory. To help with this, we define a context manager that does the boring work of moving into and out of the subdirectories.


In [7]:
@contextmanager
def workInOtherDirectory(dirname):
    presentDir = os.getcwd()
    os.chdir(dirname)
    try:
        yield
    finally:
        os.chdir(presentDir)

With the help of this context manager we scan through all the directories created by the parallel Pynac run (notice the 'dynacProc_%04d' % dirnum directory signature), and extract information for plotting.


In [8]:
xemit = []
emitAtEnd = []
for dirnum in range(10000):
    try:
        with workInOtherDirectory('dynacProc_%04d' % dirnum):
            psList = makePhaseSpaceList()
            xemit.append([ps.xPhaseSpace.normEmit.val for ps in psList])
            emitAtEnd.append(xemit[-1][-1])
    except FileNotFoundError:
        break

Now plot the data that we've just extracted.

Note that the first time I wrote this part, I had the plotting inter-twined with the data extraction but that was a mistake. The problem is that any time I wanted to change the appearance of the plots, I had to run the entire data extraction code again, and on large datasets this can take a long time. It is much better to separate the work of extracting the data from the filesystem and plotting that data.


In [10]:
p = figure(plot_width=600, plot_height=300)
for emit in xemit:
    p.line(range(len(emit)), emit)
p.yaxis.axis_label = 'Normalised x emittance'
p.xaxis.axis_label = 'Emittance measurement device number'
show(p)

p = figure(plot_width=600, plot_height=300)
hist, edges = np.histogram(emitAtEnd, bins='auto')
p.quad(top=hist, bottom=0, left=edges[:-1], right=edges[1:])
p.xaxis.axis_label = 'Normalised x emittance at linac end'
p.yaxis.axis_label = 'Number of simulations'
show(p)