Skip to content

Commit f34f189

Browse files
authored
Track all refpts in one track function call in acceptance calculations (#811)
* track all refpts at once * blacked * bugfix: confusion between obspts and offset * reduce memory usage in tracking * improve printout * blacked * at a note to describe multi-refpts tracking * blacked touschek.py * remove extra quote
1 parent 6584f96 commit f34f189

File tree

3 files changed

+644
-355
lines changed

3 files changed

+644
-355
lines changed

pyat/at/acceptance/acceptance.py

Lines changed: 135 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,41 @@
11
"""Acceptance computation"""
2+
23
import numpy
34
from .boundary import GridMode
5+
46
# noinspection PyProtectedMember
57
from .boundary import boundary_search
68
from typing import Optional, Sequence
79
import multiprocessing
8-
from ..lattice import Lattice, Refpts, frequency_control, AtError
10+
from ..lattice import Lattice, Refpts, frequency_control
911

1012

11-
__all__ = ['get_acceptance', 'get_1d_acceptance', 'get_horizontal_acceptance',
12-
'get_vertical_acceptance', 'get_momentum_acceptance']
13+
__all__ = [
14+
"get_acceptance",
15+
"get_1d_acceptance",
16+
"get_horizontal_acceptance",
17+
"get_vertical_acceptance",
18+
"get_momentum_acceptance",
19+
]
1320

1421

1522
@frequency_control
1623
def get_acceptance(
17-
ring: Lattice, planes, npoints, amplitudes,
18-
nturns: Optional[int] = 1024,
19-
refpts: Optional[Refpts] = None,
20-
dp: Optional[float] = None,
21-
offset: Sequence[float] = None, bounds=None,
22-
grid_mode: Optional[GridMode] = GridMode.RADIAL,
23-
use_mp: Optional[bool] = False,
24-
verbose: Optional[bool] = True,
25-
divider: Optional[int] = 2,
26-
shift_zero: Optional[float] = 1.0e-6,
27-
start_method: Optional[str] = None,
24+
ring: Lattice,
25+
planes,
26+
npoints,
27+
amplitudes,
28+
nturns: Optional[int] = 1024,
29+
refpts: Optional[Refpts] = None,
30+
dp: Optional[float] = None,
31+
offset: Sequence[float] = None,
32+
bounds=None,
33+
grid_mode: Optional[GridMode] = GridMode.RADIAL,
34+
use_mp: Optional[bool] = False,
35+
verbose: Optional[bool] = True,
36+
divider: Optional[int] = 2,
37+
shift_zero: Optional[float] = 1.0e-6,
38+
start_method: Optional[str] = None,
2839
):
2940
# noinspection PyUnresolvedReferences
3041
r"""Computes the acceptance at ``repfts`` observation points
@@ -91,92 +102,86 @@ def get_acceptance(
91102
* When``use_mp=True`` all the available CPUs will be used.
92103
This behavior can be changed by setting
93104
``at.DConstant.patpass_poolsize`` to the desired value
105+
* When multiple ``refpts`` are provided particles are first
106+
projected to the beginning of the ring with tracking. Then,
107+
all particles are tracked up to ``nturns``. This allows to
108+
do most of the work in a single function call and allows for
109+
full parallelization.
94110
"""
95111
kwargs = {}
96112
if start_method is not None:
97-
kwargs['start_method'] = start_method
113+
kwargs["start_method"] = start_method
98114

99115
if verbose:
100116
nproc = multiprocessing.cpu_count()
101-
print('\n{0} cpu found for acceptance calculation'.format(nproc))
117+
print("\n{0} cpu found for acceptance calculation".format(nproc))
102118
if use_mp:
103119
nprocu = nproc
104-
print('Multi-process acceptance calculation selected...')
120+
print("Multi-process acceptance calculation selected...")
105121
if nproc == 1:
106-
print('Consider use_mp=False for single core computations')
122+
print("Consider use_mp=False for single core computations")
107123
else:
108124
nprocu = 1
109-
print('Single process acceptance calculation selected...')
125+
print("Single process acceptance calculation selected...")
110126
if nproc > 1:
111-
print('Consider use_mp=True for parallelized computations')
127+
print("Consider use_mp=True for parallelized computations")
112128
np = numpy.atleast_1d(npoints)
113129
na = 2
114130
if len(np) == 2:
115131
na = np[1]
116132
npp = numpy.prod(npoints)
117-
rpp = 2*numpy.ceil(numpy.log2(np[0]))*numpy.ceil(na/nprocu)
118-
mpp = npp/nprocu
133+
rpp = 2 * numpy.ceil(numpy.log2(np[0])) * numpy.ceil(na / nprocu)
134+
mpp = npp / nprocu
119135
if rpp > mpp:
120-
cond = (grid_mode is GridMode.RADIAL or
121-
grid_mode is GridMode.CARTESIAN)
136+
cond = grid_mode is GridMode.RADIAL or grid_mode is GridMode.CARTESIAN
122137
else:
123138
cond = grid_mode is GridMode.RECURSIVE
124139
if rpp > mpp and not cond:
125-
print('The estimated load for grid mode is {0}'.format(mpp))
126-
print('The estimated load for recursive mode is {0}'.format(rpp))
127-
print('{0} or {1} is recommended'.format(GridMode.RADIAL,
128-
GridMode.CARTESIAN))
140+
print("The estimated load for grid mode is {0}".format(mpp))
141+
print("The estimated load for recursive mode is {0}".format(rpp))
142+
print(
143+
"{0} or {1} is recommended".format(GridMode.RADIAL, GridMode.CARTESIAN)
144+
)
129145
elif rpp < mpp and not cond:
130-
print('The estimated load for grid mode is {0}'.format(mpp))
131-
print('The estimated load for recursive mode is {0}'.format(rpp))
132-
print('{0} is recommended'.format(GridMode.RECURSIVE))
133-
134-
boundary = []
135-
survived = []
136-
grid = []
137-
if refpts is not None:
138-
rp = ring.uint32_refpts(refpts)
139-
else:
140-
rp = numpy.atleast_1d(refpts)
141-
if offset is not None:
142-
try:
143-
offset = numpy.broadcast_to(offset, (len(rp), 6))
144-
except ValueError:
145-
msg = ('offset and refpts have incoherent '
146-
'shapes: {0}, {1}'.format(numpy.shape(offset),
147-
numpy.shape(refpts)))
148-
raise AtError(msg)
149-
else:
150-
offset=[None for _ in rp]
151-
for r, o in zip(rp, offset):
152-
b, s, g = boundary_search(ring, planes, npoints, amplitudes,
153-
nturns=nturns, obspt=r, dp=dp,
154-
offset=o, bounds=bounds,
155-
grid_mode=grid_mode, use_mp=use_mp,
156-
verbose=verbose, divider=divider,
157-
shift_zero=shift_zero, **kwargs)
158-
boundary.append(b)
159-
survived.append(s)
160-
grid.append(g)
161-
if len(rp) == 1:
162-
return boundary[0], survived[0], grid[0]
163-
else:
164-
return boundary, survived, grid
146+
print("The estimated load for grid mode is {0}".format(mpp))
147+
print("The estimated load for recursive mode is {0}".format(rpp))
148+
print("{0} is recommended".format(GridMode.RECURSIVE))
149+
150+
b, s, g = boundary_search(
151+
ring,
152+
planes,
153+
npoints,
154+
amplitudes,
155+
nturns=nturns,
156+
obspt=refpts,
157+
dp=dp,
158+
offset=offset,
159+
bounds=bounds,
160+
grid_mode=grid_mode,
161+
use_mp=use_mp,
162+
verbose=verbose,
163+
divider=divider,
164+
shift_zero=shift_zero,
165+
**kwargs,
166+
)
167+
return b, s, g
165168

166169

167170
def get_1d_acceptance(
168-
ring: Lattice, plane: str, resolution: float, amplitude: float,
169-
nturns: Optional[int] = 1024,
170-
refpts: Optional[Refpts] = None,
171-
dp: Optional[float] = None,
172-
offset: Sequence[float] = None,
173-
grid_mode: Optional[GridMode] = GridMode.RADIAL,
174-
use_mp: Optional[bool] = False,
175-
verbose: Optional[bool] = False,
176-
divider: Optional[int] = 2,
177-
shift_zero: Optional[float] = 1.0e-6,
178-
start_method: Optional[str] = None,
179-
171+
ring: Lattice,
172+
plane: str,
173+
resolution: float,
174+
amplitude: float,
175+
nturns: Optional[int] = 1024,
176+
refpts: Optional[Refpts] = None,
177+
dp: Optional[float] = None,
178+
offset: Sequence[float] = None,
179+
grid_mode: Optional[GridMode] = GridMode.RADIAL,
180+
use_mp: Optional[bool] = False,
181+
verbose: Optional[bool] = False,
182+
divider: Optional[int] = 2,
183+
shift_zero: Optional[float] = 1.0e-6,
184+
start_method: Optional[str] = None,
180185
):
181186
r"""Computes the 1D acceptance at ``refpts`` observation points
182187
@@ -231,29 +236,44 @@ def get_1d_acceptance(
231236
* When``use_mp=True`` all the available CPUs will be used.
232237
This behavior can be changed by setting
233238
``at.DConstant.patpass_poolsize`` to the desired value
239+
* When multiple ``refpts`` are provided particles are first
240+
projected to the beginning of the ring with tracking. Then,
241+
all particles are tracked up to ``nturns``. This allows to
242+
do most of the work in a single function call and allows for
243+
full parallelization.
234244
"""
235245
if not use_mp:
236246
grid_mode = GridMode.RECURSIVE
237-
assert len(numpy.atleast_1d(plane)) == 1, \
238-
'1D acceptance: single plane required'
239-
assert numpy.isscalar(resolution), '1D acceptance: scalar args required'
240-
assert numpy.isscalar(amplitude), '1D acceptance: scalar args required'
241-
npoint = numpy.ceil(amplitude/resolution)
247+
assert len(numpy.atleast_1d(plane)) == 1, "1D acceptance: single plane required"
248+
assert numpy.isscalar(resolution), "1D acceptance: scalar args required"
249+
assert numpy.isscalar(amplitude), "1D acceptance: scalar args required"
250+
npoint = numpy.ceil(amplitude / resolution)
242251
if grid_mode is not GridMode.RECURSIVE:
243-
assert npoint > 1, \
244-
'Grid has only one point: increase amplitude or reduce resolution'
245-
b, s, g = get_acceptance(ring, plane, npoint, amplitude,
246-
nturns=nturns, dp=dp, refpts=refpts,
247-
grid_mode=grid_mode, use_mp=use_mp,
248-
verbose=verbose, start_method=start_method,
249-
divider=divider, shift_zero=shift_zero,
250-
offset=offset)
252+
assert (
253+
npoint > 1
254+
), "Grid has only one point: increase amplitude or reduce resolution"
255+
b, s, g = get_acceptance(
256+
ring,
257+
plane,
258+
npoint,
259+
amplitude,
260+
nturns=nturns,
261+
dp=dp,
262+
refpts=refpts,
263+
grid_mode=grid_mode,
264+
use_mp=use_mp,
265+
verbose=verbose,
266+
start_method=start_method,
267+
divider=divider,
268+
shift_zero=shift_zero,
269+
offset=offset,
270+
)
251271
return numpy.squeeze(b), s, g
252272

253273

254-
def get_horizontal_acceptance(ring: Lattice,
255-
resolution: float, amplitude: float,
256-
*args, **kwargs):
274+
def get_horizontal_acceptance(
275+
ring: Lattice, resolution: float, amplitude: float, *args, **kwargs
276+
):
257277
r"""Computes the 1D horizontal acceptance at ``refpts`` observation points
258278
259279
See :py:func:`get_acceptance`
@@ -306,13 +326,18 @@ def get_horizontal_acceptance(ring: Lattice,
306326
* When``use_mp=True`` all the available CPUs will be used.
307327
This behavior can be changed by setting
308328
``at.DConstant.patpass_poolsize`` to the desired value
329+
* When multiple ``refpts`` are provided particles are first
330+
projected to the beginning of the ring with tracking. Then,
331+
all particles are tracked up to ``nturns``. This allows to
332+
do most of the work in a single function call and allows for
333+
full parallelization.
309334
"""
310-
return get_1d_acceptance(ring, 'x', resolution, amplitude, *args, **kwargs)
335+
return get_1d_acceptance(ring, "x", resolution, amplitude, *args, **kwargs)
311336

312337

313-
def get_vertical_acceptance(ring: Lattice,
314-
resolution: float, amplitude: float,
315-
*args, **kwargs):
338+
def get_vertical_acceptance(
339+
ring: Lattice, resolution: float, amplitude: float, *args, **kwargs
340+
):
316341
r"""Computes the 1D vertical acceptance at refpts observation points
317342
318343
See :py:func:`get_acceptance`
@@ -365,13 +390,18 @@ def get_vertical_acceptance(ring: Lattice,
365390
* When``use_mp=True`` all the available CPUs will be used.
366391
This behavior can be changed by setting
367392
``at.DConstant.patpass_poolsize`` to the desired value
393+
* When multiple ``refpts`` are provided particles are first
394+
projected to the beginning of the ring with tracking. Then,
395+
all particles are tracked up to ``nturns``. This allows to
396+
do most of the work in a single function call and allows for
397+
full parallelization.
368398
"""
369-
return get_1d_acceptance(ring, 'y', resolution, amplitude, *args, **kwargs)
399+
return get_1d_acceptance(ring, "y", resolution, amplitude, *args, **kwargs)
370400

371401

372-
def get_momentum_acceptance(ring: Lattice,
373-
resolution: float, amplitude: float,
374-
*args, **kwargs):
402+
def get_momentum_acceptance(
403+
ring: Lattice, resolution: float, amplitude: float, *args, **kwargs
404+
):
375405
r"""Computes the 1D momentum acceptance at refpts observation points
376406
377407
See :py:func:`get_acceptance`
@@ -424,9 +454,13 @@ def get_momentum_acceptance(ring: Lattice,
424454
* When``use_mp=True`` all the available CPUs will be used.
425455
This behavior can be changed by setting
426456
``at.DConstant.patpass_poolsize`` to the desired value
457+
* When multiple ``refpts`` are provided particles are first
458+
projected to the beginning of the ring with tracking. Then,
459+
all particles are tracked up to ``nturns``. This allows to
460+
do most of the work in a single function call and allows for
461+
full parallelization.
427462
"""
428-
return get_1d_acceptance(ring, 'dp', resolution, amplitude,
429-
*args, **kwargs)
463+
return get_1d_acceptance(ring, "dp", resolution, amplitude, *args, **kwargs)
430464

431465

432466
Lattice.get_acceptance = get_acceptance

0 commit comments

Comments
 (0)