Skip to content

Commit 646092f

Browse files
committed
release-script: Merge branch 'release/v1.12.1'
2 parents 5d81968 + b6e6ef2 commit 646092f

File tree

3 files changed

+127
-14
lines changed

3 files changed

+127
-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.0'
1+
__version__ = '1.12.1'

PyHEADTAIL/spacecharge/pypic_spacecharge.py

Lines changed: 97 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -135,36 +135,76 @@ class FrozenGaussianSpaceCharge25D(FieldMapSliceWise):
135135
This frozen space charge model essentially acts equivalently to an
136136
external magnet and fails to provide self-consistent treatment of
137137
space charge related effects like quadrupolar envelope breathing
138-
etc.
138+
etc. Also the sum of all kicks does not vanish resulting in a net
139+
kick for the centroid as opposed to the SpaceChargePIC treatment.
140+
141+
FrozenGaussianSpaceCharge25D can continuously recompute and update
142+
the fields during tracking of the beam if a relative change of the
143+
beam RMS sizes is fixed via the argument sigma_rtol. If sigma_rtol
144+
is None, the stored fields are not updated and remain constant.
139145
'''
140-
def __init__(self, slicer, length, sigma_x, sigma_y, gamma,
146+
147+
'''A tuple of field maps in the beam rest frame.'''
148+
fields = None
149+
150+
def __init__(self, slicer, length, beam=None, sigma_x=None, sigma_y=None,
141151
n_mesh_sigma=[6, 6], mesh_size=[1024, 1024],
142-
*args, **kwargs):
152+
sigma_rtol=None, *args, **kwargs):
143153
'''Arguments:
144154
- slicer: determines the longitudinal discretisation for the
145155
local line charge density, with which the field is
146156
multiplied at each track call.
147157
- length: interaction length around the accelerator over
148158
which the force of the field is integrated.
159+
- beam: the Particles instance of which sigma_x, sigma_y and
160+
gamma are evaluated to compute the field map.
161+
Cannot provide sigma_x and sigma_y arguments when
162+
giving beam argument.
149163
- sigma_x, sigma_y: the horizontal and vertical RMS width of
150164
the transverse Gaussian distribution modelling the beam
151-
distribution.
152-
- gamma: the relativistic Lorentz factor of the beam.
165+
distribution. Either provide both sigma_x and sigma_y
166+
as an argument or the beam instead.
153167
154168
Optional arguments:
155169
- n_mesh_sigma: 2-list of number of beam RMS values in
156170
[x, y] to span across half the mesh width for the
157171
field interpolation.
158172
- mesh_size: 2-list of number of mesh nodes per transverse
159173
plane [x, y].
174+
- sigma_rtol: relative tolerance (float between 0 and 1)
175+
which is compared between the stored fields RMS size and
176+
the tracked beam RMS size, it determines when the fields
177+
are recomputed. For sigma_rtol == None (default value)
178+
the fields remain constant. E.g. sigma_rtol = 0.05 means
179+
if beam.sigma_x changes by more than 5%, the fields will
180+
be recomputed taking into account the new beam.sigma_x.
181+
A negative sigma_rtol will lead to continuous field
182+
updates at every track call.
160183
161184
NB: FrozenGaussianSpaceCharge25D instances should be initialised
162185
in the proper context. If the FrozenGaussianSpaceCharge25D will
163186
track on the GPU, it should be initiated within a GPU context:
164-
>>> with PyHEADTAIL.general.contextmanager.GPU(beam) as cmg:
187+
>>> with PyHEADTAIL.general.contextmanager.GPU(beam):
165188
>>> frozen_sc_node = FrozenGaussianSpaceCharge25D(...)
166189
'''
167190
wrt_beam_centroid = True
191+
self._n_mesh_sigma = n_mesh_sigma
192+
self.sigma_rtol = sigma_rtol
193+
194+
if beam is not None:
195+
if sigma_x is not None or sigma_y is not None:
196+
raise ValueError('FrozenGaussianSpaceCharge25D accepts either '
197+
'beam as an argument or both sigma_x '
198+
'and sigma_y. Do not mix these args!')
199+
sigma_x = pm.ensure_CPU(beam.sigma_x())
200+
sigma_y = pm.ensure_CPU(beam.sigma_y())
201+
else:
202+
if sigma_x is None or sigma_y is None:
203+
raise ValueError('FrozenGaussianSpaceCharge25D requires both '
204+
'sigma_x and sigma_y as arguments. '
205+
'Alternatively provide a beam whose sigma are '
206+
'evaluated.')
207+
168208
mesh = create_mesh(
169209
mesh_origin=[-n_mesh_sigma[0] * sigma_x,
170210
-n_mesh_sigma[1] * sigma_y],
@@ -184,11 +224,58 @@ def __init__(self, slicer, length, sigma_x, sigma_y, gamma,
184224
be_sc = TransverseGaussianSpaceCharge(None, None)
185225
fields_beamframe = be_sc.get_efieldn(xg, yg, 0, 0, sigma_x, sigma_y)
186226

227+
super(FrozenGaussianSpaceCharge25D, self).__init__(
228+
slicer=slicer, length=length, mesh=mesh,
229+
fields=fields_beamframe,
230+
wrt_beam_centroid=wrt_beam_centroid, *args, **kwargs)
231+
232+
@property
233+
def sigma_x(self):
234+
return -self.pypic.mesh.x0 / self._n_mesh_sigma[0]
235+
236+
@property
237+
def sigma_y(self):
238+
return -self.pypic.mesh.y0 / self._n_mesh_sigma[1]
239+
240+
@property
241+
def mesh_size(self):
242+
return self.pypic.mesh.shape_r
243+
244+
def update_field(self, beam=None, sigma_x=None, sigma_y=None):
245+
'''Update the stored Gaussian field to new RMS sizes.
246+
Take either the RMS values from the provided Particles instance
247+
beam or use sigma_x and sigma_y.
248+
249+
NB: make sure to call this method in the proper context
250+
(CPU/GPU), cf. comment in __init__ method.
251+
'''
252+
sigma_x = pm.ensure_CPU(sigma_x)
253+
sigma_y = pm.ensure_CPU(sigma_y)
254+
self.__init__(slicer=self.slicer, length=self.length, beam=beam,
255+
sigma_x=sigma_x, sigma_y=sigma_y,
256+
n_mesh_sigma=self._n_mesh_sigma,
257+
mesh_size=self.mesh_size,
258+
sigma_rtol=self.sigma_rtol)
259+
260+
def track(self, beam):
261+
# in contrast to FieldMap's length, here length becomes a float
262+
# storing the interaction length including the Lorentz transform,
263+
# so the integrated part of the accelerator x gamma**-2.
187264
# Lorentz trafo to lab frame + magnetic fields:
188265
# F = q E_beam / gamma = q (E_n_BassErsk * lambda_beam) / gamma
189266
# = q E_n_BassErsk * lambda_lab / gamma^2
190-
fields = [f * gamma**-2 for f in fields_beamframe]
267+
# this trick avoids multiplying the field arrays twice:
268+
interaction_length = self.length
269+
self.length *= beam.gamma**-2
191270

192-
super(FrozenGaussianSpaceCharge25D, self).__init__(
193-
slicer=slicer, length=length, mesh=mesh, fields=fields,
194-
wrt_beam_centroid=wrt_beam_centroid, *args, **kwargs)
271+
# update field?
272+
if self.sigma_rtol is not None:
273+
if (np.abs(pm.ensure_CPU(beam.sigma_x()) - self.sigma_x)
274+
> self.sigma_rtol * self.sigma_x):
275+
self.prints('FrozenGaussianSpaceCharge25D: '
276+
'updating field maps...')
277+
self.update_field(beam)
278+
279+
super(FrozenGaussianSpaceCharge25D, self).track(beam)
280+
281+
self.length = interaction_length

release.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,23 @@ def ensure_gothub_is_installed():
175175
'https://github.com/itchio/gothub !'
176176
)
177177

178+
def check_or_setup_github_OAuth_token():
179+
'''Check if github OAuth security token is set as an environment
180+
variable. If not ask for it and give instruction how to get it.
181+
The token is needed for gothub.
182+
'''
183+
if os.environ.get('GITHUB_TOKEN', None):
184+
print ('\n*** github OAuth security token found in $GITHUB_TOKEN.\n')
185+
return
186+
print (
187+
'\n*** No github OAuth security token found in $GITHUB_TOKEN.'
188+
' You need the token for gothub to draft the release on github!'
189+
' (Get the security token via github\'s website, cf.'
190+
'\n*** https://help.github.com/articles/creating-a-personal-access-'
191+
'token-for-the-command-line/ )\n')
192+
token = input('--> Please enter your github OAuth security token:\n')
193+
os.environ['GITHUB_TOKEN'] = token
194+
178195
def ensure_gitpulls_is_installed():
179196
'''Check whether the gem git-pulls (to get github pull requests) is
180197
installed.
@@ -195,10 +212,12 @@ def check_release_tools():
195212
drafting the github release from CLI). If not, ask whether user
196213
wants to continue and draft the release manually (if this is not
197214
the case, raise exception!).
215+
If no github OAuth security token is set, ask for it.
198216
'''
199217
try:
200218
ensure_gitpulls_is_installed()
201219
ensure_gothub_is_installed()
220+
check_or_setup_github_OAuth_token()
202221
return True
203222
except OSError:
204223
answer = ''
@@ -366,12 +385,18 @@ def finalise_release():
366385

367386
# publish github release (with message from pull request)
368387
if draft_release:
369-
assert subprocess.call(
388+
release_failed = subprocess.call(
370389
['gothub', 'release', '-u', github_user, '-r', github_repo,
371390
'-t', 'v' + new_version,
372391
'-n', '"PyHEADTAIL v{}"'.format(new_version),
373392
'-d', '"{}"'.format(message),
374393
'-c', 'master'])
394+
if release_failed:
395+
print ('*** Drafting the release via gothub failed. '
396+
'Did you provide the correct github OAuth security '
397+
'token via $GITHUB_TOKEN? '
398+
'You may draft this release manually from the '
399+
'github website.')
375400
else:
376401
print ('*** Remember to manually draft this release from the '
377402
'github website.')
@@ -407,9 +432,10 @@ def release_pip():
407432
# are we on a release branch already?
408433
if not (current_branch()[:len(release_branch_prefix)] ==
409434
release_branch_prefix):
410-
args = parser.parse_args()
411435
if current_branch() == 'master':
412436
release_pip()
413-
init_release(args.part)
437+
else:
438+
args = parser.parse_args()
439+
init_release(args.part)
414440
else:
415441
finalise_release()

0 commit comments

Comments
 (0)