Skip to content

Commit fabbfef

Browse files
committed
add warnings and docs about angular mesh
1 parent b76949f commit fabbfef

File tree

5 files changed

+229
-8
lines changed

5 files changed

+229
-8
lines changed

tests/raycing/misc/bessy.dc0.gz

38.3 KB
Binary file not shown.
+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# -*- coding: utf-8 -*-
2+
"""Select one of the 3 functions at the end of main()"""
3+
__author__ = "Konstantin Klementiev, Roman Chernikov"
4+
__date__ = "21 May 2020"
5+
import numpy as np
6+
import matplotlib.pyplot as plt
7+
import time
8+
9+
# path to xrt:
10+
import os, sys; sys.path.append(os.path.join('..', '..')) # analysis:ignore
11+
import xrt.backends.raycing.sources as rs
12+
13+
14+
def intensity_in_transverse_plane(energy, theta, psi, I0):
15+
plt.imshow(I0[len(energy)//2, :, :],
16+
extent=[theta[0]*1e6, theta[-1]*1e6, psi[0]*1e6, psi[-1]*1e6])
17+
18+
19+
def main(case, icalc, plot):
20+
if case == 1:
21+
angleMaxX = 30e-6 # rad
22+
angleMaxZ = 30e-6 # rad
23+
energy = np.linspace(3800, 4150, 351)
24+
theta = np.linspace(-1, 1, 51) * angleMaxX
25+
psi = np.linspace(-1, 1, 51) * angleMaxZ
26+
kwargsCommon = dict(
27+
eE=3.0, eI=0.5, eEpsilonX=0.263, eEpsilonZ=0.008,
28+
period=18.5, n=108, K=0.52,
29+
xPrimeMax=theta[-1]*1e3, zPrimeMax=psi[-1]*1e3)
30+
kwargsXRT = dict(
31+
betaX=9.539, betaZ=1.982,
32+
xPrimeMaxAutoReduce=False, zPrimeMaxAutoReduce=False,
33+
# targetOpenCL='CPU',
34+
distE='BW')
35+
elif case == 2:
36+
angleMaxX = 8.7e-6 # rad
37+
angleMaxZ = 8.7e-6 # rad
38+
energy = np.linspace(7250, 7450, 201)
39+
theta = np.linspace(-1, 1, 801) * angleMaxX * 32
40+
psi = np.linspace(-1, 1, 101) * angleMaxZ * 4
41+
kwargsCommon = dict(
42+
eE=1.72, eI=0.3, eEpsilonX=11.371, eEpsilonZ=0.1,
43+
period=17, n=82, K=1.071)
44+
kwargsXRT = dict(
45+
betaX=1.65, betaZ=1,
46+
xPrimeMax=theta[-1]*1e3, zPrimeMax=psi[-1]*1e3,
47+
xPrimeMaxAutoReduce=False, zPrimeMaxAutoReduce=False,
48+
distE='BW')
49+
elif case == 3:
50+
angleMaxX = 8.7e-6 # rad
51+
angleMaxZ = 8.7e-6 # rad
52+
energy = np.linspace(500, 10000, 1901)
53+
theta = np.linspace(-1, 1, 801) * angleMaxX * 32
54+
psi = np.linspace(-1, 1, 101) * angleMaxZ * 4
55+
kwargsCommon = dict(
56+
eE=1.72, eI=0.3, eEpsilonX=11.371, eEpsilonZ=0.1,
57+
period=17, n=82, K=1.071)
58+
kwargsXRT = dict(
59+
betaX=1.65, betaZ=1,
60+
xPrimeMax=theta[-1]*1e3, zPrimeMax=psi[-1]*1e3,
61+
xPrimeMaxAutoReduce=False, zPrimeMaxAutoReduce=False,
62+
distE='BW')
63+
64+
kwargsURGENT = dict(
65+
eSigmaX=(kwargsCommon['eEpsilonX']*kwargsXRT['betaX']*1e3)**0.5,
66+
eSigmaZ=(kwargsCommon['eEpsilonZ']*kwargsXRT['betaZ']*1e3)**0.5,
67+
eMin=energy[0], eMax=energy[-1],
68+
eN=len(energy)-1,
69+
icalc=icalc,
70+
xPrimeMax=angleMaxX*1e3, zPrimeMax=angleMaxZ*1e3,
71+
nx=12, nz=12)
72+
if kwargsURGENT['icalc'] == 3:
73+
kwargsCommon['eEpsilonX'] = 0.
74+
kwargsCommon['eEpsilonZ'] = 0.
75+
76+
fig = plt.figure(figsize=(10, 6))
77+
ax = fig.add_subplot(111)
78+
ax.set_xlabel(u'energy (keV)')
79+
apX = angleMaxX * 2 * 1e6
80+
apZ = angleMaxZ * 2 * 1e6
81+
ax.set_ylabel(u'flux through {0:.1f}×{1:.1f} µrad² (ph/s/0.1%bw)'.format(
82+
apX, apZ))
83+
axplot = ax.semilogy if 'log-y' in plot else ax.plot
84+
85+
# xrt Undulator
86+
kwargs = kwargsCommon.copy()
87+
kwargs.update(kwargsXRT)
88+
dtheta = theta[1] - theta[0]
89+
dpsi = psi[1] - psi[0]
90+
source = rs.Undulator(**kwargs)
91+
I0x, l1, l2, l3 = source.intensities_on_mesh(energy, theta, psi)
92+
whereTheta = np.argwhere(abs(theta) <= angleMaxX)
93+
wherePsi = np.argwhere(abs(psi) <= angleMaxZ)
94+
fluxX = I0x[:,
95+
slice(whereTheta.min(), whereTheta.max()+1),
96+
slice(wherePsi.min(), wherePsi.max()+1)]\
97+
.sum(axis=(1, 2)) * dtheta * dpsi
98+
axplot(energy*1e-3, fluxX, label='xrt')
99+
# end xrt Undulator
100+
101+
if case == 3:
102+
eS, fS = np.loadtxt("misc/bessy.dc0.gz", skiprows=2, usecols=(0, 1),
103+
unpack=True)
104+
axplot(eS*1e-3, fS, label='Spectra')
105+
106+
# UrgentUndulator
107+
import xrt.backends.raycing as raycing
108+
beamLine = raycing.BeamLine(azimuth=0, height=0)
109+
kwargs = kwargsCommon.copy()
110+
kwargs.update(kwargsURGENT)
111+
sourceU = rs.UndulatorUrgent(beamLine, **kwargs)
112+
I0u, l1, l2, l3 = sourceU.intensities_on_mesh()
113+
# add the other 3 quadrants, except the x=0 and z=0 lines:
114+
I0u = np.concatenate((I0u[:, :0:-1, :], I0u), axis=1)
115+
I0u = np.concatenate((I0u[:, :, :0:-1], I0u), axis=2)
116+
fluxU = I0u.sum(axis=(1, 2)) * sourceU.dx * sourceU.dz
117+
urgentEnergy = sourceU.Es
118+
axplot(urgentEnergy*1e-3, fluxU, label='Urgent')
119+
# end UrgentUndulator
120+
121+
ax.set_ylim(1e9, 4e13)
122+
ax.legend()
123+
fig.savefig(u'flux_case{0}_xrt_UrgentICALC{1}.png'.format(case, icalc))
124+
125+
# fig = plt.figure()
126+
# intensity_in_transverse_plane(energy, theta, psi, I0x)
127+
# fig = plt.figure()
128+
# intensity_in_transverse_plane(energy, theta, psi, I0u)
129+
130+
131+
if __name__ == '__main__':
132+
t0 = time.time()
133+
# ICALC=1 non-zero emittance, finite N
134+
# ICALC=2 non-zero emittance, infinite N
135+
# ICALC=3 zero emittance, finite N
136+
137+
# main(case=1, icalc=3, plot='lin-y')
138+
# main(case=1, icalc=2, plot='lin-y')
139+
# main(case=1, icalc=1, plot='lin-y')
140+
141+
# main(case=2, icalc=3, plot='lin-y')
142+
# main(case=2, icalc=2, plot='lin-y')
143+
# main(case=2, icalc=1, plot='lin-y')
144+
145+
# main(case=3, icalc=3, plot='log-y')
146+
# main(case=3, icalc=2, plot='log-y')
147+
main(case=3, icalc=1, plot='log-y')
148+
149+
print("Done in {0} s".format(time.time()-t0))
150+
plt.show()

xrt/backends/raycing/sources.py

+29-1
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,9 @@
237237
:members: __init__
238238
239239
.. autoclass:: Undulator()
240-
:members: __init__, tuning_curves, get_SIGMA, get_SIGMAP, real_photon_source_sizes
240+
:members: __init__, tuning_curves, get_SIGMA, get_SIGMAP,
241+
real_photon_source_sizes, multi_electron_stack,
242+
intensities_on_mesh, power_vs_K, tuning_curves
241243
.. autoclass:: Wiggler()
242244
:members: __init__
243245
.. autoclass:: BendingMagnet()
@@ -248,6 +250,32 @@
248250
Comparison of synchrotron source codes
249251
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
250252
253+
.. _mesh-methods:
254+
255+
Using xrt synchrotron sources on a mesh
256+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
257+
258+
The main modus operandi of xrt synchrotron sources is to provide Monte Carlo
259+
rays or wave samples. For comparing our sources with other codes – all of them
260+
are fully deterministic, being defined on certain meshes – we also supply mesh
261+
methods such as `intensities_on_mesh`, `power_vs_K` and `tuning_curves`. Note
262+
that we do not provide any internal mesh optimization, as these mesh functions
263+
are not our core objectives. Instead, the user themself should care about the
264+
proper mesh limits and step sizes. In particular, the angular meshes must be
265+
wider than the electron beam divergences in order to convolve the field
266+
distribution with the electron distribution of non-zero emittance. The above
267+
mentioned mesh methods will print a warning (new in version 1.3.4) if the
268+
requested meshes are too narrow.
269+
270+
If you want to calculate flux through a narrow aperture, you first calculate
271+
`intensities_on_mesh` on wide enough angular meshes and then slice the
272+
intensity down to the needed aperture size. An example of such calculations is
273+
given in `tests/raycing/test_undulator_on_mesh.py` which produces the following
274+
plot (for a BESSY undulator, zero energy spread, as Urgent cannot take account
275+
of it):
276+
277+
.. imagezoom:: _images/flux_case3_xrt_UrgentICALC1.png
278+
251279
Undulator spectrum across a harmonic
252280
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
253281

xrt/backends/raycing/sources_legacy.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,8 @@ class UndulatorUrgent. *mode* = 4 is by far most preferable.
253253
self.zPrimeMax = zPrimeMax
254254
self.nx = nx
255255
self.nz = nz
256+
self.dx = xPrimeMax / (nx+0.5)
257+
self.dz = zPrimeMax / (nz+0.5)
256258
self.path = path
257259
self.mode = mode
258260
self.icalc = icalc
@@ -330,7 +332,7 @@ def make_input(self, x, z, E):
330332
x, z, self.xPrimeMax, self.zPrimeMax, self.nx, self.nz)
331333
elif self.mode == 4:
332334
infile += '0. {0} {1} {2} {3} {4} {5}\n'.format(
333-
x, z, self.xPrimeMax/self.nx, self.zPrimeMax/self.nz, 11, 11)
335+
x, z, self.dx, self.dz, 15, 15)
334336
# ICALC=1 non-zero emittance, finite N
335337
# ICALC=2 non-zero emittance, infinite N
336338
# ICALC=3 zero emittance, finite N
@@ -359,8 +361,8 @@ def msg_E(self, iE):
359361
(str(len(self.Es))).zfill(self.Epads))
360362

361363
def run(self, forceRecalculate=False, iniFileForEachDirectory=False):
362-
self.xs = np.linspace(0, self.xPrimeMax, self.nx+1)
363-
self.zs = np.linspace(0, self.zPrimeMax, self.nz+1)
364+
self.xs = np.linspace(0, self.xPrimeMax-self.dx*0.5, self.nx+1)
365+
self.zs = np.linspace(0, self.zPrimeMax-self.dz*0.5, self.nz+1)
364366
self.Es = np.linspace(self.eMin, self.eMax, self.eN+1)
365367
self.energies = self.Es
366368
cwd = os.getcwd()
@@ -512,8 +514,7 @@ def make_spline_arrays(self, skiprows, cols1, cols2):
512514
if res is not None:
513515
self.Es, It, l1t, l2t, l3t = res
514516
if self.mode == 4:
515-
It /= self.xPrimeMax / (self.nx+0.5) *\
516-
self.zPrimeMax / (self.nz+0.5)
517+
It /= self.dx * self.dz
517518
else:
518519
pass
519520
if I is None:
@@ -796,7 +797,7 @@ def make_input(self, x, z, E, isBM=False):
796797
0, 0, xxx, 2*self.zPrimeMax, self.nx, self.nz)
797798
elif self.mode == 2:
798799
infile += '0. {0} {1} {2} {3} {4} {5}\n'.format(
799-
x, z, xxx, self.zPrimeMax/self.nz, self.nx, self.nz)
800+
x, z, xxx, self.zPrimeMax/(self.nz+0.5), self.nx, self.nz)
800801
infile += '{0}\n'.format(self.mode)
801802
return infile
802803

xrt/backends/raycing/sources_synchr.py

+43-1
Original file line numberDiff line numberDiff line change
@@ -1016,12 +1016,14 @@ def __init__(self, bl=None, name='und', center=(0, 0, 0),
10161016
else:
10171017
self.customFieldData = None
10181018

1019+
self.xPrimeMaxAutoReduce = xPrimeMaxAutoReduce
10191020
if xPrimeMaxAutoReduce:
10201021
xPrimeMaxTmp = self.Ky / self.gamma
10211022
if self.xPrimeMax > xPrimeMaxTmp:
10221023
print("Reducing xPrimeMax from {0} down to {1} mrad".format(
10231024
self.xPrimeMax * 1e3, xPrimeMaxTmp * 1e3))
10241025
self.xPrimeMax = xPrimeMaxTmp
1026+
self.zPrimeMaxAutoReduce = zPrimeMaxAutoReduce
10251027
if zPrimeMaxAutoReduce:
10261028
K0 = self.Kx if self.Kx > 0 else 1.
10271029
zPrimeMaxTmp = K0 / self.gamma
@@ -1240,7 +1242,7 @@ def power_vs_K(self, energy, theta, psi, harmonics, Ks):
12401242
"""Calculates *power curve* -- total power in W for all *harmomonics*
12411243
at given K values (*Ks*). The power is calculated through the aperture
12421244
defined by *theta* and *psi* opening angles within the *energy* range.
1243-
1245+
12441246
The result of this numerical integration depends on the used angular
12451247
and energy meshes; you should check convergence. Internally, electron
12461248
beam energy spread is also sampled by adding another dimension to the
@@ -1339,6 +1341,20 @@ def multi_electron_stack(self, energy='auto', theta='auto', psi='auto',
13391341

13401342
def intensities_on_mesh(self, energy='auto', theta='auto', psi='auto',
13411343
harmonic=None):
1344+
"""Returns the Stokes parameters in the shape (energy, theta, psi,
1345+
[harmonic]), with *theta* being the horizontal mesh angles and *psi*
1346+
the vertical mesh angles. Each one of the input parameters is a 1D
1347+
array of an individually selectable length.
1348+
1349+
.. note::
1350+
We do not provide any internal mesh optimization, as mesh functions
1351+
are not our core objectives. In particular, the angular meshes must
1352+
be wider than the electron beam divergences in order to convolve the
1353+
field distribution with the electron distribution. A warning will be
1354+
printed (new in version 1.3.4) if the requested meshes are too
1355+
narrow.
1356+
1357+
"""
13421358
if isinstance(energy, str): # i.e. if 'auto'
13431359
energy = np.mgrid[self.E_min:self.E_max + 0.5*self.dE:self.dE]
13441360

@@ -1408,6 +1424,32 @@ def intensities_on_mesh(self, energy='auto', theta='auto', psi='auto',
14081424
from scipy.ndimage.filters import gaussian_filter
14091425
Sx = self.dxprime / (theta[1] - theta[0])
14101426
Sz = self.dzprime / (psi[1] - psi[0])
1427+
# print(self.dxprime, theta[-1] - theta[0], Sx, len(theta))
1428+
# print(self.dzprime, psi[-1] - psi[0], Sz, len(psi))
1429+
if Sx > len(theta)//4: # ±2σ
1430+
print("************* Warning ***********************")
1431+
print("Your theta mesh is too narrow!")
1432+
print("It must be wider than the electron beam width")
1433+
print("*********************************************")
1434+
if self.xPrimeMax < theta.max():
1435+
print("************* Warning ****************************")
1436+
print("Your xPrimeMax is too small!")
1437+
print("It must be bigger than theta.max()")
1438+
if self.xPrimeMaxAutoReduce:
1439+
print("You probably need to set xPrimeMaxAutoReduce=False")
1440+
print("**************************************************")
1441+
if Sz > len(psi)//4: # ±2σ
1442+
print("************* Warning ************************")
1443+
print("Your psi mesh is too narrow!")
1444+
print("It must be wider than the electron beam height")
1445+
print("**********************************************")
1446+
if self.zPrimeMax < psi.max():
1447+
print("************* Warning ****************************")
1448+
print("Your zPrimeMax is too small!")
1449+
print("It must be bigger than psi.max()")
1450+
if self.zPrimeMaxAutoReduce:
1451+
print("You probably need to set zPrimeMaxAutoReduce=False")
1452+
print("**************************************************")
14111453
for ie, ee in enumerate(energy):
14121454
if harmonic is None:
14131455
s0[ie, :, :] = gaussian_filter(s0[ie, :, :], [Sx, Sz])

0 commit comments

Comments
 (0)