Skip to content

Commit f9c94b1

Browse files
committed
release-script: Merge branch 'release/v1.12.4'
2 parents 3a3d3a5 + a7ee766 commit f9c94b1

File tree

6 files changed

+351
-14
lines changed

6 files changed

+351
-14
lines changed

PyHEADTAIL/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '1.12.3'
1+
__version__ = '1.12.4'

PyHEADTAIL/cobra_functions/stats.pyx

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ cimport libc.math as cmath
1111

1212
cimport cython.boundscheck
1313
cimport cython.cdivision
14+
cimport cython.wraparound
1415

1516

1617

@@ -196,6 +197,7 @@ cpdef count_macroparticles_per_slice(int[::1] slice_index_of_particle,
196197
s_idx = slice_index_of_particle[particles_within_cuts[i]]
197198
n_macroparticles[s_idx] += 1
198199

200+
199201
@cython.boundscheck(False)
200202
@cython.cdivision(True)
201203
cpdef sort_particle_indices_by_slice(int[::1] slice_index_of_particle,
@@ -223,6 +225,7 @@ cpdef sort_particle_indices_by_slice(int[::1] slice_index_of_particle,
223225
particle_indices_by_slice[pos] = p_idx
224226
pos_ctr[s_idx] += 1
225227

228+
226229
@cython.boundscheck(False)
227230
@cython.cdivision(True)
228231
cpdef mean_per_slice(int[::1] slice_index_of_particle,
@@ -245,6 +248,7 @@ cpdef mean_per_slice(int[::1] slice_index_of_particle,
245248
if n_macroparticles[i]:
246249
mean_u[i] /= n_macroparticles[i]
247250

251+
248252
@cython.boundscheck(False)
249253
@cython.cdivision(True)
250254
cpdef std_per_slice(int[::1] slice_index_of_particle,
@@ -360,6 +364,7 @@ cpdef emittance_per_slice_old(int[::1] slice_index_of_particle,
360364

361365
epsn_u[i] = cmath.sqrt(u2[i]*up2[i] - uup[i]*uup[i])
362366

367+
363368
@cython.boundscheck(False)
364369
@cython.cdivision(True)
365370
cpdef emittance_per_slice(int[::1] slice_index_of_particle,
@@ -375,7 +380,7 @@ cpdef emittance_per_slice(int[::1] slice_index_of_particle,
375380
for each slice.
376381
"""
377382
cdef unsigned int n_slices = emittance.shape[0]
378-
# allocate arrays for covariances
383+
# allocate arrays for covariances
379384
cdef double[::1] cov_u2 = np.zeros(n_slices, dtype=np.double)
380385
cdef double[::1] cov_up2 = np.zeros(n_slices, dtype=np.double)
381386
cdef double[::1] cov_u_up = np.zeros(n_slices, dtype=np.double)
@@ -409,3 +414,84 @@ cpdef emittance_per_slice(int[::1] slice_index_of_particle,
409414
emittance[i] = cmath.sqrt(_det_beam_matrix(sigma11, sigma12, sigma22))
410415
else:
411416
emittance[i] = 0.
417+
418+
@cython.boundscheck(False)
419+
@cython.cdivision(True)
420+
@cython.wraparound(False)
421+
cpdef calc_cell_stats(bunch, double beta_z, double radial_cut,
422+
int n_rings, int n_azim_slices):
423+
424+
# Prepare arrays to store cell statistics.
425+
cdef int[:,::1] n_particles_cell = np.zeros((n_azim_slices, n_rings),
426+
dtype=np.int32)
427+
cdef double[:,::1] mean_x_cell = np.zeros((n_azim_slices, n_rings),
428+
dtype=np.double)
429+
cdef double[:,::1] mean_xp_cell = np.zeros((n_azim_slices, n_rings),
430+
dtype=np.double)
431+
cdef double[:,::1] mean_y_cell = np.zeros((n_azim_slices, n_rings),
432+
dtype=np.double)
433+
cdef double[:,::1] mean_yp_cell = np.zeros((n_azim_slices, n_rings),
434+
dtype=np.double)
435+
cdef double[:,::1] mean_z_cell = np.zeros((n_azim_slices, n_rings),
436+
dtype=np.double)
437+
cdef double[:,::1] mean_dp_cell = np.zeros((n_azim_slices, n_rings),
438+
dtype=np.double)
439+
440+
# Declare datatypes of bunch coords.
441+
cdef double[::1] x = bunch.x
442+
cdef double[::1] xp = bunch.xp
443+
cdef double[::1] y = bunch.y
444+
cdef double[::1] yp = bunch.yp
445+
cdef double[::1] z = bunch.z
446+
cdef double[::1] dp = bunch.dp
447+
cdef unsigned int n_particles = x.shape[0]
448+
449+
cdef double ring_width = radial_cut / <double>n_rings
450+
cdef double azim_width = 2.*cmath.M_PI / <double>n_azim_slices
451+
cdef double beta_z_square = beta_z*beta_z
452+
453+
cdef double z_i, dp_i, long_action
454+
cdef unsigned int p_idx
455+
cdef int ring_idx, azim_idx
456+
for p_idx in xrange(n_particles):
457+
z_i = z[p_idx]
458+
dp_i = dp[p_idx]
459+
460+
# Slice radially.
461+
long_action = cmath.sqrt(z_i*z_i + beta_z_square*dp_i*dp_i)
462+
ring_idx = <int>cmath.floor(long_action / ring_width)
463+
if ring_idx >= n_rings:
464+
continue
465+
466+
# Slice azimuthally: atan 2 returns values in the interval [-pi, pi];
467+
# hence, in order to avoid negative indices, we need to add an offset of
468+
# +pi before performing the floor division (this consequently just adds
469+
# an offset to the slices indices). The one-to-one mapping between
470+
# angles and slice indices is retained as desired. In this
471+
# interpretation, slice index 0 corresponds to -pi (i.e. starting in 3rd
472+
# quadrant) and slice n-1 correspoinds to +pi (i.e. ending in 2nd
473+
# quadrant). This needs to be taken into account when interpreting and
474+
# plotting cell monitor data - for this, use
475+
# theta = np.linspace(-np.pi, np.pi, n_azim_slices)
476+
azim_idx = <int>cmath.floor(
477+
(cmath.M_PI + cmath.atan2(beta_z*dp_i, z_i)) / azim_width)
478+
479+
n_particles_cell[azim_idx, ring_idx] += 1
480+
mean_x_cell[azim_idx, ring_idx] += x[p_idx]
481+
mean_xp_cell[azim_idx, ring_idx] += xp[p_idx]
482+
mean_y_cell[azim_idx, ring_idx] += y[p_idx]
483+
mean_yp_cell[azim_idx, ring_idx] += yp[p_idx]
484+
mean_z_cell[azim_idx, ring_idx] += z_i
485+
mean_dp_cell[azim_idx, ring_idx] += dp_i
486+
487+
for azim_idx in xrange(n_azim_slices):
488+
for ring_idx in xrange(n_rings):
489+
if n_particles_cell[azim_idx, ring_idx] > 0:
490+
mean_x_cell[azim_idx, ring_idx] /= n_particles_cell[azim_idx, ring_idx]
491+
mean_xp_cell[azim_idx, ring_idx] /= n_particles_cell[azim_idx, ring_idx]
492+
mean_y_cell[azim_idx, ring_idx] /= n_particles_cell[azim_idx, ring_idx]
493+
mean_yp_cell[azim_idx, ring_idx] /= n_particles_cell[azim_idx, ring_idx]
494+
mean_z_cell[azim_idx, ring_idx] /= n_particles_cell[azim_idx, ring_idx]
495+
mean_dp_cell[azim_idx, ring_idx] /= n_particles_cell[azim_idx, ring_idx]
496+
497+
return n_particles_cell, mean_x_cell, mean_xp_cell, mean_y_cell, mean_yp_cell, mean_z_cell, mean_dp_cell

PyHEADTAIL/gpu/thrust_code.cu

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include <thrust/gather.h>
12
#include <thrust/sort.h>
23
#include <thrust/binary_search.h>
34
#include <thrust/device_vector.h>

PyHEADTAIL/monitors/monitors.py

Lines changed: 156 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
from ..gpu import gpu_utils as gpu_utils
1818
from ..general import decorators as decorators
1919

20+
from ..cobra_functions import stats as cp
21+
22+
# from .. import cobra_functions.stats.calc_cell_stats as calc_cell_stats
23+
2024

2125
class Monitor(Printing):
2226
""" Abstract base class for monitors. A monitor can request
@@ -122,9 +126,9 @@ def _create_file_structure(self, parameters_dict):
122126
h5group.create_dataset(stats, shape=(self.n_steps,),
123127
compression='gzip', compression_opts=9)
124128
h5file.close()
125-
except:
126-
self.prints('Creation of bunch monitor file ' + self.filename +
127-
'failed. \n')
129+
except Exception as err:
130+
self.warns('Problem occurred during Bunch monitor creation.')
131+
self.warns(err.message)
128132
raise
129133

130134
@decorators.synchronize_gpu_streams_after
@@ -296,9 +300,9 @@ def _create_file_structure(self, parameters_dict):
296300
h5group_slice.create_dataset(stats, shape=(self.slicer.n_slices,
297301
self.n_steps), compression='gzip', compression_opts=9)
298302
h5file.close()
299-
except:
300-
self.prints('Creation of slice monitor file ' + self.filename +
301-
'failed. \n')
303+
except Exception as err:
304+
self.warns('Problem occurred during Slice monitor creation.')
305+
self.warns(err.message)
302306
raise
303307

304308
def _write_data_to_buffer(self, bunch):
@@ -398,7 +402,7 @@ def __init__(self, filename, stride=1, parameters_dict=None,
398402
399403
Optionally pass a list called quantities_to_store which
400404
specifies which members of the bunch will be called/stored.
401-
"""
405+
"""
402406
quantities_to_store = [ 'x', 'xp', 'y', 'yp', 'z', 'dp', 'id' ]
403407
self.quantities_to_store = kwargs.pop('quantities_to_store',
404408
quantities_to_store)
@@ -424,9 +428,9 @@ def _create_file_structure(self, parameters_dict):
424428
for key in parameters_dict:
425429
h5file.attrs[key] = parameters_dict[key]
426430
h5file.close()
427-
except:
428-
self.prints('Creation of particle monitor file ' +
429-
self.filename + 'failed. \n')
431+
except Exception as err:
432+
self.warns('Problem occurred during Particle monitor creation.')
433+
self.warns(err.message)
430434
raise
431435

432436
def _write_data_to_file(self, bunch, arrays_dict):
@@ -465,3 +469,145 @@ def _write_data_to_file(self, bunch, arrays_dict):
465469
h5group[quant][:] = quant_values[::self.stride]
466470

467471
h5file.close()
472+
473+
474+
class CellMonitor(Monitor):
475+
""" Class to store cell (z, dp) specific data (for the moment only
476+
mean_x, mean_y, mean_z, mean_dp and n_particles_in_cell) to a HDF5
477+
file. This monitor uses a buffer (shift register) to reduce the
478+
number of writing operations to file. This also helps to avoid IO
479+
errors and loss of data when writing to a file that may become
480+
temporarily unavailable (e.g. if file is located on network) during
481+
the simulation. """
482+
483+
def __init__(self, filename, n_steps, n_azimuthal_slices, n_radial_slices,
484+
radial_cut, beta_z, parameters_dict=None,
485+
write_buffer_every=512, buffer_size=4096, *args, **kwargs):
486+
""" Create an instance of a CellMonitor class. Apart from
487+
initializing the HDF5 file, a buffer self.buffer_cell is
488+
prepared to buffer the cell-specific data before writing them
489+
to file.
490+
491+
filename: Path and name of HDF5 file. Without file
492+
extension.
493+
n_steps: Number of entries to be reserved for each
494+
of the quantities in self.stats_to_store.
495+
n_azimuthal_slices: Number of pizza slices (azimuthal slicing).
496+
n_radial_slices: Number of rings (radial slicing).
497+
radial_cut: 'Radius' of the outermost ring in
498+
longitudinal phase space (using beta_z*dp)
499+
parameters_dict: Metadata for HDF5 file containing main
500+
simulation parameters.
501+
write_buffer_every: Number of steps after which buffer
502+
contents are actually written to file.
503+
buffer_size: Number of steps to be buffered. """
504+
stats_to_store = [
505+
'mean_x', 'mean_xp',
506+
'mean_y', 'mean_yp',
507+
'mean_z', 'mean_dp',
508+
'macroparticlenumber']
509+
self.stats_to_store = kwargs.pop('stats_to_store', stats_to_store)
510+
511+
self.filename = filename
512+
self.n_steps = n_steps
513+
self.i_steps = 0
514+
self.n_azimuthal_slices = n_azimuthal_slices
515+
self.n_radial_slices = n_radial_slices
516+
self.radial_cut = radial_cut
517+
self.beta_z = beta_z
518+
519+
# Prepare buffer.
520+
self.buffer_size = buffer_size
521+
self.write_buffer_every = write_buffer_every
522+
self.buffer_cell = {}
523+
524+
for stats in self.stats_to_store:
525+
self.buffer_cell[stats] = (
526+
np.zeros((self.n_azimuthal_slices, self.n_radial_slices, self.buffer_size)))
527+
self._create_file_structure(parameters_dict)
528+
529+
def dump(self, bunch):
530+
""" Evaluate the statistics for the given cells and write the
531+
data to the buffer and/or to the HDF5 file. The buffer is used
532+
to reduce the number of writing operations to file. This helps
533+
to avoid IO errors and loss of data when writing data to a file
534+
that may become temporarily unavailable (e.g. if file is on
535+
network) during the simulation. Buffer contents are written to
536+
file only every self.write_buffer_every steps. """
537+
self._write_data_to_buffer(bunch)
538+
if ((self.i_steps + 1) % self.write_buffer_every == 0 or
539+
(self.i_steps + 1) == self.n_steps):
540+
self._write_buffer_to_file()
541+
self.i_steps += 1
542+
543+
def _create_file_structure(self, parameters_dict):
544+
""" Initialize HDF5 file and create its basic structure (groups
545+
and datasets). One dataset for each of the quantities defined
546+
in self.stats_to_store is generated. If specified by
547+
the user, write the contents of the parameters_dict as metadata
548+
(attributes) to the file. Maximum file compression is
549+
activated. """
550+
try:
551+
h5file = hp.File(self.filename + '.h5', 'w')
552+
if parameters_dict:
553+
for key in parameters_dict:
554+
h5file.attrs[key] = parameters_dict[key]
555+
556+
h5file.create_group('Cells')
557+
h5group_cells = h5file['Cells']
558+
for stats in self.stats_to_store:
559+
h5group_cells.create_dataset(
560+
stats, compression='gzip', compression_opts=9,
561+
shape=(self.n_azimuthal_slices, self.n_radial_slices,
562+
self.n_steps))
563+
h5file.close()
564+
except Exception as err:
565+
self.warns('Problem occurred during Cell monitor creation.')
566+
self.warns(err.message)
567+
raise
568+
569+
def _write_data_to_buffer(self, bunch):
570+
""" Store the data in the self.buffer dictionary before writing
571+
them to file. The buffer is implemented as a shift register. The
572+
cell-specific data are computed by a cython function. """
573+
574+
from PyHEADTAIL.cobra_functions.stats import calc_cell_stats
575+
n_cl, x_cl, xp_cl, y_cl, yp_cl, z_cl, dp_cl = calc_cell_stats(
576+
bunch, self.beta_z, self.radial_cut, self.n_radial_slices, self.n_azimuthal_slices)
577+
578+
self.buffer_cell['mean_x'][:,:,0] = x_cl[:,:]
579+
self.buffer_cell['mean_xp'][:,:,0] = xp_cl[:,:]
580+
self.buffer_cell['mean_y'][:,:,0] = y_cl[:,:]
581+
self.buffer_cell['mean_yp'][:,:,0] = yp_cl[:,:]
582+
self.buffer_cell['mean_z'][:,:,0] = z_cl[:,:]
583+
self.buffer_cell['mean_dp'][:,:,0] = dp_cl[:,:]
584+
self.buffer_cell['macroparticlenumber'][:,:,0] = n_cl[:,:]
585+
586+
for stats in self.stats_to_store:
587+
self.buffer_cell[stats] = np.roll(
588+
self.buffer_cell[stats], shift=-1, axis=2)
589+
590+
def _write_buffer_to_file(self):
591+
""" Write buffer contents to the HDF5 file. The file is opened
592+
and closed each time the buffer is written to file to prevent
593+
from loss of data in case of a crash. """
594+
# Keep track of where to read from buffers and where to store
595+
# data in file.
596+
n_entries_in_buffer = min(self.i_steps+1, self.buffer_size)
597+
low_pos_in_buffer = self.buffer_size - n_entries_in_buffer
598+
low_pos_in_file = self.i_steps + 1 - n_entries_in_buffer
599+
up_pos_in_file = self.i_steps + 1
600+
601+
# Try to write data to file. If file is not available, skip this
602+
# step and repeat it again after self.write_buffer_every. As
603+
# long as self.buffer_size is not exceeded, no data are lost.
604+
try:
605+
h5file = hp.File(self.filename + '.h5', 'a')
606+
h5group_cells = h5file['Cells']
607+
608+
for stats in self.stats_to_store:
609+
h5group_cells[stats][:,:,low_pos_in_file:up_pos_in_file] = \
610+
self.buffer_cell[stats][:,:,low_pos_in_buffer:]
611+
h5file.close()
612+
except Exception as err:
613+
self.warns(err.message)

0 commit comments

Comments
 (0)