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