Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
f6825b5
Add test script for tune calculation node
austin-hoover Aug 29, 2025
66c5c2c
Use correct column order for tune diagnostic
austin-hoover Aug 29, 2025
86dfa8a
Test collecting phase information from bunch
austin-hoover Aug 29, 2025
c3ce40e
Track number of turns and sum of previous particle phases
austin-hoover Aug 29, 2025
cbdfbe5
Revert "Track number of turns and sum of previous particle phases"
austin-hoover Aug 29, 2025
a1115fe
Merge branch 'PyORBIT-Collaboration:main' into tune
austin-hoover Aug 29, 2025
44411b2
Initial version of BunchTuneAnalysis4D
austin-hoover Aug 29, 2025
3eefadb
Fix typos
austin-hoover Aug 29, 2025
3d0ce30
Try adding BunchTuneAnalysis4D to orbit.core module
austin-hoover Aug 29, 2025
731a3ae
Keep BunchTuneAnalysis4D in same file as BunchTuneAnalysis
austin-hoover Sep 3, 2025
64f6d38
Update test_tune.py
austin-hoover Sep 3, 2025
75164fd
Add test_tune_4d.py
austin-hoover Sep 3, 2025
cc3b5f5
Rename test_tune_4d.py to test_tune_4d_uncoupled.py
austin-hoover Sep 3, 2025
9d9ac9c
Delete file
austin-hoover Sep 4, 2025
62439c6
Implement setMatrixElement method
austin-hoover Sep 4, 2025
0cfba27
Add example test_tune_4d_uncoupled.py
austin-hoover Sep 4, 2025
30e5e93
Change function names
austin-hoover Sep 4, 2025
adb0977
Add comments
austin-hoover Sep 4, 2025
49e207f
Add comments
austin-hoover Sep 4, 2025
0c62262
Use built-in TeapotTuneAnalysisNode
austin-hoover Sep 4, 2025
9247486
Update documentation
austin-hoover Sep 4, 2025
fb89208
Add getData method to tune analysis nodes
austin-hoover Sep 4, 2025
7eaee4d
Fix type on examples
austin-hoover Sep 5, 2025
98e8f34
Merge branch 'PyORBIT-Collaboration:main' into tune
austin-hoover Sep 11, 2025
73086e7
Add setActive method to TeapotTuneAnalysis4DNode
austin-hoover Sep 11, 2025
c483fa4
Unify BunchTuneAnalysis and BunchTuneAnalysis4D
austin-hoover Sep 12, 2025
1b00ec7
Unify BunchTuneAnalysis and BunchTuneAnalysis4D python classes
austin-hoover Sep 12, 2025
e346558
Update tune example
austin-hoover Sep 12, 2025
12cc93a
Add setActive function to TeapotTuneAnalysisNode
austin-hoover Sep 15, 2025
fb2035d
Check tunes against transfer matrix
austin-hoover Sep 19, 2025
3dddd80
Add option to start FODO lattice at quad or drift center
austin-hoover Sep 19, 2025
6ea69a4
Fix fill fraction argument in make_lattice
austin-hoover Sep 19, 2025
9965cb6
Improve make_lattice helper function
austin-hoover Sep 19, 2025
957fcfa
Remove unused imports
austin-hoover Sep 19, 2025
9bd5875
Add test using 4 x 4 normalization matrix from eigenvectors
austin-hoover Sep 19, 2025
37f22ba
Add 4D coupled tune analysis example
austin-hoover Sep 19, 2025
d1c9858
tune_node.getData(bunch) returns data for all particles
austin-hoover Sep 19, 2025
efd235e
Accidentally committed output files
austin-hoover Sep 19, 2025
2cd54f9
Update tests
austin-hoover Sep 19, 2025
722c0cb
Format
austin-hoover Sep 19, 2025
5cee774
Change tunemap labels to use 1/2 instead of x/y
austin-hoover Sep 19, 2025
b75e967
Change labeling from x/y to 1/2
austin-hoover Sep 19, 2025
3d47f39
Merge branch 'PyORBIT-Collaboration:main' into tune
austin-hoover Sep 22, 2025
30266a2
Merge branch 'PyORBIT-Collaboration:main' into tune
austin-hoover Sep 27, 2025
1278867
Merge branch 'PyORBIT-Collaboration:main' into tune
austin-hoover Jun 29, 2026
bbbde6c
Change function name to setNormMatrixFromTwiss
austin-hoover Jun 29, 2026
af233ad
Update examples
austin-hoover Jun 29, 2026
0fb2211
Merge branch 'tune' of https://github.com/austin-hoover/PyORBIT3 into…
austin-hoover Jun 29, 2026
a3080b5
Change C++ function name assignTwiss -> setNormMatrixFromTwiss
austin-hoover Jun 29, 2026
4886f93
Create orbit.diagnostics.eig module
austin-hoover Jun 29, 2026
21fa60f
Add function setNormMatrixFromTransferMatrix
austin-hoover Jun 29, 2026
7b397f3
Start working on setNormMatrixFromCov
austin-hoover Jun 29, 2026
aed846e
Update examples to include SNS ring
austin-hoover Jun 30, 2026
1eba4e6
Update test_sns_4d.py
austin-hoover Jun 30, 2026
547ee8d
Don't print
austin-hoover Jun 30, 2026
6b5e004
Address issue with setNormMatrixFromCov when eps1=eps2
austin-hoover Jun 30, 2026
91ab53e
Add check if sigma is coupled
austin-hoover Jun 30, 2026
5b0e5a9
Accidentally deleted method
austin-hoover Jun 30, 2026
e890997
Add flag to reset particle phase attributes when normalization matrix…
austin-hoover Jun 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
768 changes: 768 additions & 0 deletions examples/Diagnostics/Tunes/inputs/sns_ring.lat

Large diffs are not rendered by default.

155 changes: 155 additions & 0 deletions examples/Diagnostics/Tunes/test_sns_2d.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
"""Test one-turn tune estimation in uncoupled lattice.

This example tracks a Gaussian distribution through a FODO lattice. The tunes
are estimated from the phase space coordinates before/after tracking using the
`BunchTuneAnalysis` class.
"""
import argparse
import math
import os
import pathlib
import random
from pprint import pprint

import numpy as np
import pandas as pd

from orbit.core.bunch import Bunch
from orbit.core.bunch import BunchTwissAnalysis
from orbit.bunch_generators import TwissContainer
from orbit.bunch_generators import GaussDist2D
from orbit.diagnostics import TeapotTuneAnalysisNode
from orbit.diagnostics.matrix import TransferMatrixAnalysis
from orbit.teapot import TEAPOT_Lattice
from orbit.teapot import TEAPOT_MATRIX_Lattice
from orbit.utils.consts import mass_proton

from utils import make_lattice_sns
from utils import get_tmat


# Arguments
parser = argparse.ArgumentParser()
parser.add_argument("--norm-from", type=str, default="tmat", choices=["tmat", "cov", "twiss"])
args = parser.parse_args()

# Setup
path = pathlib.Path(__file__)
output_dir = os.path.join("outputs", path.stem)
os.makedirs(output_dir, exist_ok=True)

# Initialize lattice and bunch
lattice = make_lattice_sns()

bunch = Bunch()
bunch.mass(mass_proton)
bunch.getSyncParticle().kinEnergy(1.000)

# Calculate transfer matrix
matrix_lattice = TEAPOT_MATRIX_Lattice(lattice, bunch)
lattice_params = matrix_lattice.getRingParametersDict()
pprint(lattice_params)

# Store some parameters as variables
lattice_alpha_x = lattice_params["alpha x"]
lattice_alpha_y = lattice_params["alpha y"]
lattice_beta_x = lattice_params["beta x [m]"]
lattice_beta_y = lattice_params["beta y [m]"]
lattice_eta_x = lattice_params["dispersion x [m]"]
lattice_etap_x = lattice_params["dispersion prime x"]

# Add tune diagnostic node
tune_node = TeapotTuneAnalysisNode()

if args.norm_from == "twiss":
tune_node.setNormMatrixFromTwiss(
betax=lattice_beta_x,
alphax=lattice_alpha_x,
etax=lattice_eta_x,
etapx=lattice_etap_x,
betay=lattice_beta_y,
alphay=lattice_alpha_y,
)
elif args.norm_from == "tmat":
tmat = get_tmat(lattice, bunch)
tune_node.setNormMatrixFromTransferMatrix(tmat)
elif args.norm_from == "cov":
tmat = get_tmat(lattice, bunch)
tmat_analysis = TransferMatrixAnalysis(tmat)
cov_matrix = tmat_analysis.cov_matrix(1e-7, 1e-7)
tune_node.setNormMatrixFromCovMatrix(cov_matrix)
else:
raise ValueError("Invalid norm_from argument")

lattice.getNodes()[0].addChildNode(tune_node, 0)

# Generate particles
emittance_x = 0.1e-06
emittance_y = 0.1e-06
twiss_x = TwissContainer(lattice_alpha_x, lattice_beta_x, emittance_x)
twiss_y = TwissContainer(lattice_alpha_y, lattice_beta_y, emittance_y)
dist = GaussDist2D(twiss_x, twiss_y)

for index in range(1000):
x, xp, y, yp = dist.getCoordinates()
z = random.uniform(-25.0, 25.0)
bunch.addParticle(x, xp, y, yp, z, 0.0)

# Track particles
for turn in range(10):
lattice.trackBunch(bunch)

twiss_calc = BunchTwissAnalysis()
twiss_calc.analyzeBunch(bunch)
xrms = 1000.0 * math.sqrt(twiss_calc.getCorrelation(0, 0))
yrms = 1000.0 * math.sqrt(twiss_calc.getCorrelation(2, 2))
print("turn={} xrms={:0.3f} yrms={:0.3f}".format(turn + 1, xrms, yrms))

# Test writing to file
filename = "bunch.dat"
filename = os.path.join(output_dir, filename)
bunch.dumpBunch(filename)

# Collect phase data from bunch
phase_data = tune_node.getData(bunch)
phase_data = pd.DataFrame(phase_data)

# Read phase data from file
particles = np.loadtxt(filename, comments="%")
particles = pd.DataFrame(
particles,
columns=[ # https://github.com/PyORBIT-Collaboration/PyORBIT3/issues/78
"x",
"xp",
"y",
"yp",
"z",
"dE",
"phase_x",
"phase_y",
"tune_x",
"tune_y",
"action_x",
"action_y",
],
)
print(particles.iloc[:, 6:])

# Check against tune from transfer matrix
tune_x_true = lattice_params["fractional tune x"]
tune_y_true = lattice_params["fractional tune y"]
tune_x_calc = np.mean(phase_data["tune_1"])
tune_y_calc = np.mean(phase_data["tune_2"])

tune_x_err = tune_x_calc - tune_x_true
tune_y_err = tune_y_calc - tune_y_true

print("tune_x_true", tune_x_true)
print("tune_x_calc", tune_x_calc)
print("tune_y_true", tune_y_true)
print("tune_y_calc", tune_y_calc)
print("tune_x_err", tune_x_err)
print("tune_y_err", tune_y_err)

assert np.abs(tune_x_err) < 1.00e-06
assert np.abs(tune_y_err) < 1.00e-06
107 changes: 107 additions & 0 deletions examples/Diagnostics/Tunes/test_sns_4d.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"""Test one-turn tune estimation in coupled lattice."""

import argparse
import math
import os
import pathlib

import numpy as np
import pandas as pd

from orbit.core.bunch import Bunch
from orbit.core.bunch import BunchTwissAnalysis
from orbit.diagnostics import TeapotTuneAnalysisNode
from orbit.diagnostics.matrix import TransferMatrixAnalysis
from orbit.teapot import TEAPOT_Ring
from orbit.teapot import TEAPOT_MATRIX_Lattice
from orbit.utils.consts import mass_proton

from utils import make_lattice_sns
from utils import get_tmat


# Arguments
parser = argparse.ArgumentParser()
parser.add_argument("--coupled", type=int, default=1)
parser.add_argument("--norm-from", type=str, default="tmat", choices=["tmat", "cov"])
parser.add_argument("--eps1", type=float, default=1.0)
parser.add_argument("--eps2", type=float, default=1.01)
args = parser.parse_args()

# Setup
path = pathlib.Path(__file__)
output_dir = os.path.join("outputs", path.stem)
os.makedirs(output_dir, exist_ok=True)

# Initialize lattice and bunch
lattice = make_lattice_sns(sol=args.coupled)
lattice.initialize()

bunch = Bunch()
bunch.mass(mass_proton)
bunch.getSyncParticle().kinEnergy(1.000)

# Calculate transfer matrix
M = get_tmat(lattice, bunch)
tmat_analysis = TransferMatrixAnalysis(M)
tune_1_true = tmat_analysis.eigtunes[0]
tune_2_true = tmat_analysis.eigtunes[1]

# Calculate matched covariance matrix
eps_1 = args.eps1 * 1e-6
eps_2 = args.eps2 * 1e-6
S_matched = tmat_analysis.cov_matrix(eps_1, eps_2)

# Add tune diagnostic node
tune_node = TeapotTuneAnalysisNode()
if args.norm_from == "tmat":
tune_node.setNormMatrixFromTransferMatrix(M)
else:
tune_node.setNormMatrixFromCovMatrix(S_matched)
lattice.getNodes()[0].addChildNode(tune_node, 0)

# Generate particles
rng = np.random.default_rng(123)
particles = np.zeros((1000, 6))
particles[:, :4] = rng.multivariate_normal(
mean=np.zeros(4), cov=S_matched, size=particles.shape[0]
)
particles[:, 4] = rng.uniform(-25.0, 25.0, size=particles.shape[0])
particles[:, 5] = 0.0

for index in range(particles.shape[0]):
bunch.addParticle(*particles[index])

# Track particles
for turn in range(10):
lattice.trackBunch(bunch)

twiss_calc = BunchTwissAnalysis()
twiss_calc.analyzeBunch(bunch)
xrms = 1000.0 * math.sqrt(twiss_calc.getCorrelation(0, 0))
yrms = 1000.0 * math.sqrt(twiss_calc.getCorrelation(2, 2))
print("turn={} xrms={:0.3f} yrms={:0.3f}".format(turn + 1, xrms, yrms))

# Analysis
phase_data = tune_node.getData(bunch) # phase_data = pd.DataFrame(phase_data)

tune_1_calc = np.mean(phase_data["tune_1"])
tune_2_calc = np.mean(phase_data["tune_2"])

# Order depends on eps1/eps2. To check values against transfer matrix,
# sort tunes by magnitude.
if tune_1_calc < tune_2_calc:
(tune_1_calc, tune_2_calc) = (tune_2_calc, tune_1_calc)

tune_1_err = tune_1_calc - tune_1_true
tune_2_err = tune_2_calc - tune_2_true

print("tune_1_true", tune_1_true)
print("tune_1_calc", tune_1_calc)
print("tune_2_true", tune_2_true)
print("tune_2_calc", tune_2_calc)
print("tune_1_err", tune_1_err)
print("tune_2_err", tune_2_err)

assert np.abs(tune_1_err) < 1.00e-05
assert np.abs(tune_2_err) < 1.00e-05
99 changes: 99 additions & 0 deletions examples/Diagnostics/Tunes/test_update_norm_mat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
"""Test updating normalization matrix during tracking.

Average tunes are printed out after each turn. The normalization matrix
is updated twice during tracking. A warning message should print that
indicates the normalization matrix has changed, and that the tunes
will be inaccurate until the next tracked turn.
"""
import os
import pathlib
import random
from pprint import pprint

import numpy as np

from orbit.core.bunch import Bunch
from orbit.core.bunch import BunchTwissAnalysis
from orbit.bunch_generators import TwissContainer
from orbit.bunch_generators import GaussDist2D
from orbit.diagnostics import TeapotTuneAnalysisNode
from orbit.teapot import TEAPOT_MATRIX_Lattice
from orbit.utils.consts import mass_proton

from utils import make_lattice_sns

# Setup
path = pathlib.Path(__file__)
output_dir = os.path.join("outputs", path.stem)
os.makedirs(output_dir, exist_ok=True)

# Initialize lattice and bunch
lattice = make_lattice_sns()

bunch = Bunch()
bunch.mass(mass_proton)
bunch.getSyncParticle().kinEnergy(1.000)

# Calculate transfer matrix
matrix_lattice = TEAPOT_MATRIX_Lattice(lattice, bunch)
lattice_params = matrix_lattice.getRingParametersDict()
pprint(lattice_params)

# Store some parameters as variables
lattice_alpha_x = lattice_params["alpha x"]
lattice_alpha_y = lattice_params["alpha y"]
lattice_beta_x = lattice_params["beta x [m]"]
lattice_beta_y = lattice_params["beta y [m]"]
lattice_eta_x = lattice_params["dispersion x [m]"]
lattice_etap_x = lattice_params["dispersion prime x"]

# Add tune diagnostic node
tune_node = TeapotTuneAnalysisNode()

tune_node.setNormMatrixFromTwiss(
betax=lattice_beta_x,
alphax=lattice_alpha_x,
etax=lattice_eta_x,
etapx=lattice_etap_x,
betay=lattice_beta_y,
alphay=lattice_alpha_y,
)

lattice.getNodes()[0].addChildNode(tune_node, 0)

# Generate particles
emittance_x = 0.1e-06
emittance_y = 0.1e-06
twiss_x = TwissContainer(lattice_alpha_x, lattice_beta_x, emittance_x)
twiss_y = TwissContainer(lattice_alpha_y, lattice_beta_y, emittance_y)
dist = GaussDist2D(twiss_x, twiss_y)

for index in range(10_000):
x, xp, y, yp = dist.getCoordinates()
z = random.uniform(-25.0, 25.0)
bunch.addParticle(x, xp, y, yp, z, 0.0)

# Track particles
for turn in range(20):
lattice.trackBunch(bunch)


if turn >= 0:
phase_data = tune_node.getData(bunch)
nu_x = np.mean(phase_data["tune_1"])
nu_y = np.mean(phase_data["tune_2"])

print("turn{} nux={:0.5f} nuy={:0.5f}".format(turn, nu_x, nu_y))

if turn == 5:
tune_node.setNormMatrixFromBunch(bunch)

if turn == 10:
tune_node.setNormMatrixFromTwiss(
betax=lattice_beta_x,
alphax=lattice_alpha_x,
etax=lattice_eta_x,
etapx=lattice_etap_x,
betay=lattice_beta_y,
alphay=lattice_alpha_y,
)
Loading
Loading