Example-19: Layout
[1]:
# In this example graphical representation of (planar) layout if illustrated
# Layout data can be generated to draw layout in 1D, 2D and 3D along with other curves (given on sliced lattice)
# To avoid explicit dependencies on graphical libraries (matplotlib and plotly), results are returned as dictionaries
[2]:
# Import
import matplotlib
from matplotlib import pyplot as plt
from matplotlib.patches import Rectangle
matplotlib.rcParams['text.usetex'] = True
from plotly import graph_objects
import torch
from twiss import twiss
from twiss import propagate
from twiss import wolski_to_cs
from model.library.drift import Drift
from model.library.quadrupole import Quadrupole
from model.library.sextupole import Sextupole
from model.library.dipole import Dipole
from model.library.bpm import BPM
from model.library.line import Line
from model.command.layout import Layout
[3]:
# Define simple FODO based lattice
QF = Quadrupole('QF', 0.5, +0.20)
QD = Quadrupole('QD', 0.5, -0.19)
SF = Sextupole('SF', 0.25)
SD = Sextupole('SD', 0.25)
DR = Drift('DR', 0.25)
BM = Dipole('BM', 3.50, torch.pi/4.0)
BA = BPM('BA', direction='inverse')
BB = BPM('BB', direction='forward')
FODO = Line('FODO',
[BA, QF, DR, SF, DR, BM, DR, SD, DR, QD, QD, DR, SD, DR, BM, DR, SF, DR, QF, BB],
propagate=True,
dp=0.0,
exact=False,
output=True,
matrix = True)
RING = Line('RING',
4*[FODO],
propagate=True,
dp=0.0,
exact=False,
output=True,
matrix = True)
[4]:
# Perform lattice slicing
# Note, one step is used for zero length elements
RING.ns = 0.05
print(RING.ns)
{'BA': 1, 'QF': 10, 'DR': 5, 'SF': 5, 'BM': 70, 'SD': 5, 'QD': 10, 'BB': 1}
[5]:
# Compute transport matrices along closed orbit
state = torch.tensor([0.0, 0.0, 0.0, 0.0], dtype=torch.float64)
print(torch.func.jacrev(RING)(state))
print()
total, *ms = RING.container_matrix
for m in ms:
total = m @ total
print(total)
print()
tensor([[ -0.3382, -17.5116, 0.0000, 0.0000],
[ 0.0506, -0.3382, 0.0000, 0.0000],
[ 0.0000, 0.0000, -0.2976, -6.0422],
[ 0.0000, 0.0000, 0.1508, -0.2976]], dtype=torch.float64)
tensor([[ -0.3382, -17.5116, 0.0000, 0.0000],
[ 0.0506, -0.3382, 0.0000, 0.0000],
[ 0.0000, 0.0000, -0.2976, -6.0422],
[ 0.0000, 0.0000, 0.1508, -0.2976]], dtype=torch.float64)
[6]:
# Compute twiss parameters at slices (entrance of each slice)
# Compute twiss from one-turn matrix at starting location
(nux, nuy), _, w = twiss(total)
# Print fractional tunes
print(f'{nux.item(), nuy.item()}')
# Propagate twiss parametes
ws = [w]
*ms, _ = RING.container_matrix
for m in ms:
w = propagate(w, m)
ws.append(w)
ws = torch.stack(ws)
# Convert to CS and plot beta functions
_, bx, _, by = torch.vmap(wolski_to_cs)(ws).T
plt.figure(figsize=(20, 4))
plt.plot(bx.cpu().numpy(), color='red', label=r'$\beta_x$')
plt.plot(by.cpu().numpy(), color='blue', label=r'$\beta_y$')
plt.xlabel(r'slice \#')
plt.ylabel(r'$\beta$ [m]')
plt.tight_layout()
plt.show()
(0.6950865039372625, 0.7018998427861426)
![../_images/examples_model-18_6_1.png](../_images/examples_model-18_6_1.png)
[7]:
# Generate several trajectories on slice (turn off matrix computation)
RING.matrix = False
state = torch.tensor([+0.01, 0.0, -0.01, 0.0], dtype=torch.float64)
orbit = []
for _ in range(16):
state = RING(state)
orbit.append(RING.container_output.clone())
orbit = torch.stack(orbit)
orbit.shape
[7]:
torch.Size([16, 968, 4])
[8]:
# Set layout
layout = Layout(RING)
[9]:
# Reference orbit (lines and arcs)
# With step = None, returns globals coordinates for elements entrance faces (if the last entry is dropped)
x_entrance, y_entrance, *_ = layout.orbit(flat=False, step=None)
# Step size can be passed to generate smooth closed orbit curve
# Slicing is performed only in arc sections
x, y, *_ = layout.orbit(flat=False, step=0.01)
plt.figure(figsize=(6, 6))
plt.scatter(x_entrance.cpu().numpy(), y_entrance.cpu().numpy(), marker='o', color='black')
plt.plot(x.cpu().numpy(), y.cpu().numpy(), color='black')
plt.tight_layout()
plt.show()
# Note, rotation is anti-clockwise
![../_images/examples_model-18_9_0.png](../_images/examples_model-18_9_0.png)
[10]:
# Information about each element
# (name, kind, length, angle)
names, kinds, lengths, angles = zip(*layout.line.layout())
# Element locations in global frame can be computed using orbit method
x, y, *_ = layout.orbit(step=None, flag=False, lengths=lengths, angles=angles)
plt.figure(figsize=(6, 6))
plt.errorbar(x.cpu().numpy(), y.cpu().numpy(), fmt=' ', ms=1, marker='x', color='black')
plt.tight_layout()
plt.show()
![../_images/examples_model-18_10_0.png](../_images/examples_model-18_10_0.png)
[11]:
# In sliced lattice each element is splitted into one or more slices
# Information about each slice
# (name, kind, length, angle, point)
# Here point gives element entrance face location in global coordinate system
names, kinds, lengths, angles, points = layout.slicing_table()
x, y, *_ = points.T
plt.figure(figsize=(6, 6))
plt.errorbar(x.cpu().numpy(), y.cpu().numpy(), fmt=' ', ms=1, marker='x', color='black')
plt.tight_layout()
plt.show()
![../_images/examples_model-18_11_0.png](../_images/examples_model-18_11_0.png)
[12]:
# 1D profile (rectangles)
_, _, lengths, *_ = layout.slicing_table()
rectangles, labels = layout.profile_1d(scale=5.0,
shift=-2.5,
text=True,
delta=-4.0,
rotation=90,
exclude=['BPM'])
plt.figure(figsize=(20, 5))
plt.plot(lengths.cumsum(-1).cpu().numpy(), bx.cpu().numpy(), color='red', label=r'$\beta_x$')
plt.plot(lengths.cumsum(-1).cpu().numpy(), by.cpu().numpy(), color='blue', label=r'$\beta_y$')
plt.legend()
for rectangle in rectangles:
plt.gca().add_patch(Rectangle(**rectangle))
for label in labels:
plt.text(**label)
plt.ylim(-5.0, 25.0)
plt.tight_layout()
plt.show()
# Default colors and other parametes are defined in config attribute (layout class variable)
![../_images/examples_model-18_12_0.png](../_images/examples_model-18_12_0.png)
[13]:
# 2D profile and data transformation
# Using angles and global positions, data on slices can be transformed
# In general, data scaling should be adjusted to present transformed data in appealing way\
# Transform data
*_, angles, points = layout.slicing_table()
angles = angles.cumsum(-1)
bx_x = torch.zeros_like(bx)
bx_y = +(0.05*bx + 0.5)
bx_z = torch.zeros_like(bx)
bx_points = torch.stack([bx_x, bx_y, bx_z]).T
bx_x, bx_y, bx_z = torch.vmap(layout.transform)(bx_points, points, angles).T
by_x = torch.zeros_like(by)
by_y = +(0.05*by + 0.5)
by_z = torch.zeros_like(by)
by_points = torch.stack([by_x, by_y, by_z]).T
by_x, by_y, by_z = torch.vmap(layout.transform)(by_points, points, angles).T
# Generate reference orbit
x, y, _ = layout.orbit(flat=False, step=0.01, start=(0, 0))
# Generate layout
blocks, labels = layout.profile_2d(start=(0, 0),
delta=-0.6,
linewidth=1.5,
exclude=['Drift', 'BPM'])
# Plot
plt.figure(figsize=(6, 6))
plt.plot(x, y, color='black')
plt.plot(bx_x, bx_y, color='red', label=r'$\beta_x$')
plt.plot(by_x, by_y, color='blue', label=r'$\beta_y$')
for block in blocks:
plt.errorbar(**block)
for label in labels:
plt.text(**label)
plt.legend()
plt.xlabel(r'x [m]')
plt.ylabel(r'y [m]')
plt.tight_layout()
plt.show()
![../_images/examples_model-18_13_0.png](../_images/examples_model-18_13_0.png)
[14]:
# 3D profile and data transformation
names, kinds, lengths, angles, points = layout.slicing_table()
angles = angles.cumsum(-1)
# Transform orbits
scale = 50.0
qx, _, qy, _ = orbit.swapaxes(0, 1).swapaxes(0, -1)
q_x = scale*torch.zeros_like(qx)
q_y = scale*qx
q_z = scale*qy
q_points = torch.stack([q_x, q_y, q_z]).swapaxes(0, -1)
q_x, q_y, q_z = torch.vmap(layout.transform)(q_points, points, angles).swapaxes(0, -1)
# Select data at BPMs
q_x_bpm = []
q_y_bpm = []
q_z_bpm = []
for kind, qx, qy, qz in zip(kinds, q_x.T, q_y.T, q_z.T):
if kind == 'BPM':
q_x_bpm.append(qx)
q_y_bpm.append(qy)
q_z_bpm.append(qz)
q_x_bpm = torch.stack(q_x_bpm)
q_y_bpm = torch.stack(q_y_bpm)
q_z_bpm = torch.stack(q_z_bpm)
# Generate reference orbit
x, y, z = layout.orbit(flat=False, step=0.01, start=(0, 0))
# Generate layout (can be saved as html with write_html method)
blocks = layout.profile_3d(scale=1.75)
# Plot
figure = graph_objects.Figure(
data=[
graph_objects.Scatter3d(
x=x.numpy(),
y=y.numpy(),
z=z.numpy(),
mode='lines',
name='Orbit',
line=dict(color='black',width=2.0,dash='solid'),
opacity=0.75,
showlegend=True
),
graph_objects.Scatter3d(
x=q_x.flatten().numpy(),
y=q_y.flatten().numpy(),
z=q_z.flatten().numpy(),
mode='lines',
name='Trajectory',
line=dict(color='black',width=1.0,dash='solid'),
opacity=0.50,
showlegend=True
),
graph_objects.Scatter3d(
x=q_x_bpm.flatten().numpy(),
y=q_y_bpm.flatten().numpy(),
z=q_z_bpm.flatten().numpy(),
mode='markers',
name='Projection',
marker=dict(color='red',size=1.5),
opacity=0.50,
showlegend=True
),
*[graph_objects.Mesh3d(block) for block in blocks]
]
)
figure.update_layout(
scene=dict(
xaxis=dict(visible=False, range=[-20,20]),
yaxis=dict(visible=False, range=[-20,20]),
zaxis=dict(visible=False, range=[-5,5]),
aspectratio=dict(x=1, y=1, z=1/4),
annotations=[]
),
margin=dict(l=0, r=0, t=0, b=0),
legend=dict(orientation='v', x=0., y=1., xanchor='left', yanchor='top'),
hoverlabel=dict(font_size=12, font_family="Rockwell", font_color='white'),
legend_groupclick='toggleitem'
)
figure.show()