Example-41: Tune (Tune and chormaticity correction)
[1]:
# Import
from random import random
from pprint import pprint
import torch
from torch import Tensor
from pathlib import Path
from model.library.line import Line
from model.command.external import load_sdds
from model.command.external import load_lattice
from model.command.build import build
from model.command.tune import tune
from model.command.tune import chromaticity
[2]:
# Load ELEGANT twiss
path = Path('ic.twiss')
parameters, columns = load_sdds(path)
nu_qx:Tensor = torch.tensor(parameters['nux'] % 1, dtype=torch.float64)
nu_qy:Tensor = torch.tensor(parameters['nuy'] % 1, dtype=torch.float64)
psi_qx:Tensor = torch.tensor(parameters['dnux/dp'], dtype=torch.float64)
psi_qy:Tensor = torch.tensor(parameters['dnuy/dp'], dtype=torch.float64)
[3]:
# Build and setup lattice
# Note, sextupoles are turned off and dipoles are linear
# Load ELEGANT table
path = Path('ic.lte')
data = load_lattice(path)
# Build ELEGANT table
ring:Line = build('RING', 'ELEGANT', data)
ring.flatten()
# Merge drifts
ring.merge()
# Turn off sextupoles and set linear dipoles
for element in ring:
if element.__class__.__name__ == 'Sextupole':
element.ms = 0.0
if element.__class__.__name__ == 'Dipole':
element.linear = True
# Set number of elements of different kinds
nb = ring.describe['BPM']
nq = ring.describe['Quadrupole']
ns = ring.describe['Sextupole']
[4]:
# Compute tunes (fractional part)
guess = torch.tensor(4*[0.0], dtype=torch.float64)
nuqx, nuqy = tune(ring, [], alignment=False, matched=True, guess=guess, limit=8, epsilon=1.0E-9)
# Compare with elegant
print(torch.allclose(nu_qx, nuqx))
print(torch.allclose(nu_qy, nuqy))
True
True
[5]:
# Compute tunes derivative with respect to deviations in quadrupole settings
kn = torch.zeros(nq, dtype=torch.float64)
pprint(torch.func.jacrev(lambda kn: tune(ring, [kn], ('kn', ['Quadrupole'], None, None), matched=True, limit=1, epsilon=None))(kn))
print()
tensor([[ 0.0187, 0.0217, 0.0461, 0.0601, 0.0484, 0.0297, 0.0299, 0.0488,
0.0597, 0.0457, 0.0214, 0.0192, 0.0669, 0.0667, 0.0191, 0.0216,
0.0461, 0.0597, 0.0483, 0.0296, 0.0297, 0.0485, 0.0595, 0.0459,
0.0209, 0.0196, 0.0683, 0.0647],
[-0.0668, -0.0283, -0.0136, -0.0078, -0.0141, -0.0205, -0.0202, -0.0137,
-0.0079, -0.0137, -0.0282, -0.0661, -0.0250, -0.0251, -0.0663, -0.0282,
-0.0137, -0.0079, -0.0138, -0.0203, -0.0205, -0.0140, -0.0078, -0.0137,
-0.0288, -0.0659, -0.0250, -0.0258]], dtype=torch.float64)
[6]:
# Compute chromaticities derivatives
ms = torch.zeros(ns, dtype=torch.float64)
pprint(torch.func.jacrev(lambda ms: chromaticity(ring, [ms], ('ms', ['Sextupole'], None, None), matched=True, limit=1, epsilon=None))(ms))
print()
tensor([[ 0.0051, 0.0144, 0.0143, 0.0050, 0.0049, 0.0142, 0.0143, 0.0051,
0.0050, 0.0143, 0.0142, 0.0050, 0.0049, 0.0141, 0.0142, 0.0050],
[-0.0047, -0.0037, -0.0038, -0.0049, -0.0049, -0.0038, -0.0038, -0.0049,
-0.0049, -0.0038, -0.0038, -0.0049, -0.0049, -0.0038, -0.0037, -0.0048]],
dtype=torch.float64)
[7]:
# Tune correction
# [nux, nuy] = M [..., kn_i, ..., ks_i, ...]
kn = torch.zeros(nq, dtype=torch.float64)
response = torch.func.jacrev(lambda kn: tune(ring, [kn], ('kn', ['Quadrupole'], None, None), matched=True, limit=1, epsilon=None))(kn)
print(response.shape)
print()
# Set target tunes
nuqx_target = torch.tensor(0.75, dtype=torch.float64)
nuqy_target = torch.tensor(0.85, dtype=torch.float64)
# Perform correction (model to experiment)
lr = 1.0
kn = torch.zeros(nq, dtype=torch.float64)
for _ in range(4):
nuqx, nuqy = tune(ring, [kn], ('kn', ['Quadrupole'], None, None), matched=True, limit=1, epsilon=None)
dkn = (- lr*torch.linalg.lstsq(response, torch.stack([nuqx, nuqy]) - torch.stack([nuqx_target, nuqy_target]), driver='gels').solution)
kn += dkn
print((torch.stack([nuqx, nuqy]) - torch.stack([nuqx_target, nuqy_target])).norm())
print()
print(tune(ring, [kn], ('kn', ['Quadrupole'], None, None), matched=True, limit=1, epsilon=None))
print()
torch.Size([2, 28])
tensor(0.0471, dtype=torch.float64)
tensor(0.0007, dtype=torch.float64)
tensor(1.9547e-05, dtype=torch.float64)
tensor(5.4013e-07, dtype=torch.float64)
tensor([0.7500, 0.8500], dtype=torch.float64)
[8]:
# Chromaticity correction
# Note, one iteration is sufficient for chromaticity correction
# [psix, psiy] = M [..., ms_i, ..., ms_i, ...]
ms = torch.zeros(ns, dtype=torch.float64)
response = torch.func.jacrev(lambda ms: chromaticity(ring, [ms], ('ms', ['Sextupole'], None, None), matched=True, limit=1, epsilon=None))(ms)
print(response.shape)
print()
# Set target tunes
psiqx_target = torch.tensor(1.0, dtype=torch.float64)
psiqy_target = torch.tensor(1.0, dtype=torch.float64)
# Perform correction (model to experiment)
lr = 1.0
ks = torch.zeros(ns, dtype=torch.float64)
for _ in range(1):
psiqx, psiqy = chromaticity(ring, [ms], ('ms', ['Sextupole'], None, None), matched=True, limit=1, epsilon=None)
dms = (- lr*torch.linalg.lstsq(response, torch.stack([psiqx, psiqy]) - torch.stack([psiqx_target, psiqy_target]), driver='gels').solution)
ms += dms
print((torch.stack([psiqx, psiqy]) - torch.stack([psiqx_target, psiqy_target])).norm())
print()
print(chromaticity(ring, [ms], ('ms', ['Sextupole'], None, None), matched=True, limit=1, epsilon=None))
print()
torch.Size([2, 16])
tensor(10.1673, dtype=torch.float64)
tensor([1.0000, 1.0000], dtype=torch.float64)