Skip to content

Commit 9bd5875

Browse files
committed
Add test using 4 x 4 normalization matrix from eigenvectors
1 parent 957fcfa commit 9bd5875

File tree

1 file changed

+190
-0
lines changed

1 file changed

+190
-0
lines changed
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
"""Test one-turn tune estimation in uncoupled lattice using 4D normalization.
2+
3+
See comments on `test_tune.py`. This example is the same, but the tunes are
4+
estimated using the `BunchTuneAnalysis4D` class. Since there is no coupling
5+
in the lattice, the (eigen)tunes {nu1, nu2} should be the same as horizontal
6+
and vertical tunes {nux, nuy}.
7+
"""
8+
import math
9+
import os
10+
import pathlib
11+
import random
12+
from pprint import pprint
13+
14+
import numpy as np
15+
import pandas as pd
16+
import matplotlib.pyplot as plt
17+
18+
from orbit.core.bunch import Bunch
19+
from orbit.core.bunch import BunchTwissAnalysis
20+
from orbit.diagnostics import TeapotTuneAnalysisNode
21+
from orbit.lattice import AccLattice
22+
from orbit.lattice import AccNode
23+
from orbit.teapot import TEAPOT_Lattice
24+
from orbit.teapot import TEAPOT_MATRIX_Lattice
25+
from orbit.teapot import SolenoidTEAPOT
26+
from orbit.utils.consts import mass_proton
27+
28+
from utils import make_lattice
29+
30+
31+
# Setup
32+
# ------------------------------------------------------------------------------------
33+
34+
path = pathlib.Path(__file__)
35+
output_dir = os.path.join("outputs", path.stem)
36+
os.makedirs(output_dir, exist_ok=True)
37+
38+
39+
# Initialize lattice and bunch
40+
# ------------------------------------------------------------------------------------
41+
42+
lattice = make_lattice()
43+
44+
bunch = Bunch()
45+
bunch.mass(mass_proton)
46+
bunch.getSyncParticle().kinEnergy(1.000)
47+
48+
49+
# Analyze transfer matrix
50+
# ------------------------------------------------------------------------------------
51+
52+
53+
def calc_eigtune(eigval: float) -> float:
54+
return np.arccos(np.real(eigval)) / (2.0 * np.pi)
55+
56+
57+
def unit_symplectic_matrix(ndim: int) -> np.ndarray:
58+
U = np.zeros((ndim, ndim))
59+
for i in range(0, ndim, 2):
60+
U[i: i + 2, i: i + 2] = [[0.0, 1.0], [-1.0, 0.0]]
61+
return U
62+
63+
64+
def normalize_eigvec(v: np.ndarray) -> np.ndarray:
65+
U = unit_symplectic_matrix(len(v))
66+
67+
def _norm(v):
68+
return np.linalg.multi_dot([np.conj(v), U, v])
69+
70+
if _norm(v) > 0.0:
71+
v = np.conj(v)
72+
73+
v *= np.sqrt(2.0 / np.abs(_norm(v)))
74+
assert np.isclose(np.imag(_norm(v)), -2.0)
75+
assert np.isclose(np.real(_norm(v)), +0.0)
76+
return v
77+
78+
79+
def calc_norm_matrix_from_eigvecs(v1: np.ndarray, v2: np.ndarray) -> np.ndarray:
80+
V = np.zeros((4, 4))
81+
V[:, 0] = +np.real(v1)
82+
V[:, 1] = -np.imag(v1)
83+
V[:, 2] = +np.real(v2)
84+
V[:, 3] = -np.imag(v2)
85+
return np.linalg.inv(V)
86+
87+
88+
# Estimate transfer matrix
89+
matrix_lattice = TEAPOT_MATRIX_Lattice(lattice, bunch)
90+
91+
M = np.zeros((4, 4))
92+
for i in range(4):
93+
for j in range(4):
94+
M[i, j] = matrix_lattice.getOneTurnMatrix().get(i, j)
95+
96+
# Calculate eigenvalues and eigenvectors
97+
eigvals, eigvecs = np.linalg.eig(M)
98+
eigvals = eigvals[[0, 2]]
99+
eigvecs = eigvecs[:, [0, 2]]
100+
101+
v1 = normalize_eigvec(eigvecs[:, 0])
102+
v2 = normalize_eigvec(eigvecs[:, 1])
103+
104+
# Calculate tunes from transfer matrix
105+
tune_1_true = calc_eigtune(eigvals[0])
106+
tune_2_true = calc_eigtune(eigvals[1])
107+
108+
# Calculate normalization matrix from transfer matrix
109+
V_inv = calc_norm_matrix_from_eigvecs(v1, v2)
110+
V = np.linalg.inv(V_inv)
111+
112+
# Print normalization matrix
113+
print("Normalization matrix V^{-1}:")
114+
print(V_inv)
115+
116+
117+
# Add tune diagnostic node
118+
# ------------------------------------------------------------------------------------
119+
120+
tune_node = TeapotTuneAnalysisNode()
121+
tune_node.setNormMatrix(V_inv)
122+
lattice.getNodes()[0].addChildNode(tune_node, 0)
123+
124+
125+
# Generate phase space distribution
126+
# ------------------------------------------------------------------------------------
127+
128+
rng = np.random.default_rng()
129+
130+
n = 1000
131+
eps_1 = 25.0e-06 # mode 1 rms emittance
132+
eps_2 = 25.0e-06 # mode 2 rms emittance
133+
134+
# Generate particles in normalized phase space
135+
particles = np.zeros((n, 6))
136+
particles[:, (0, 1)] = rng.normal(size=(n, 2), scale=np.sqrt(eps_1))
137+
particles[:, (2, 3)] = rng.normal(size=(n, 2), scale=np.sqrt(eps_2))
138+
particles[:, 4] = rng.uniform(-25.0, 25.0, size=n)
139+
particles[:, 5] = 0.0
140+
141+
# Unnormalize transverse coordinates (match to lattice)
142+
particles[:, :4] = np.matmul(particles[:, :4], V.T)
143+
144+
# Add particles to bunch
145+
for index in range(n):
146+
bunch.addParticle(*particles[index])
147+
148+
149+
# Tracking
150+
# ------------------------------------------------------------------------------------
151+
152+
n_turns = 10
153+
for turn in range(n_turns):
154+
lattice.trackBunch(bunch)
155+
156+
twiss_calc = BunchTwissAnalysis()
157+
twiss_calc.analyzeBunch(bunch)
158+
xrms = math.sqrt(twiss_calc.getCorrelation(0, 0)) * 1000.0
159+
yrms = math.sqrt(twiss_calc.getCorrelation(2, 2)) * 1000.0
160+
print("turn={} xrms={:0.3f} yrms={:0.3f}".format(turn + 1, xrms, yrms))
161+
162+
163+
# Analysis
164+
# ------------------------------------------------------------------------------------
165+
166+
# Collect phase data from bunch
167+
phase_data = {}
168+
for i in range(bunch.getSize()):
169+
data = tune_node.getData(bunch, i)
170+
for key in data:
171+
if key in phase_data:
172+
phase_data[key].append(data[key])
173+
else:
174+
phase_data[key] = []
175+
176+
phase_data = pd.DataFrame(phase_data)
177+
print(phase_data)
178+
179+
# Check against tune from transfer matrix
180+
tune_1_pred = np.mean(phase_data["tune_x"])
181+
tune_2_pred = np.mean(phase_data["tune_y"])
182+
183+
tune_1_err = tune_1_pred - tune_1_true
184+
tune_2_err = tune_2_pred - tune_2_true
185+
186+
print("tune_1_err", tune_1_err)
187+
print("tune_2_err", tune_2_err)
188+
189+
assert np.abs(tune_1_err) < 1.00e-08
190+
assert np.abs(tune_2_err) < 1.00e-08

0 commit comments

Comments
 (0)