Skip to content
Draft
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
defbeb5
OPT.BASE.FIX: missing `self` on `_initialization`
vellosok75 Oct 1, 2025
fd099f4
OPT.LEASTSQUARES.WIP: initial version of least-squares optimizer
vellosok75 Oct 1, 2025
061954d
OPTICSANLY.NOECO.WIP: initial version of NOECO class
vellosok75 Oct 1, 2025
83eb616
OPT.LEASTSQUARES.BUG: avoid division by zero
vellosok75 Oct 2, 2025
3833d6a
OPT.LEASTSQUARES.BUG: var used before assignment
vellosok75 Oct 2, 2025
368c1e8
OPT.LEASTSQUARES.ENH: include error sigma as weights
vellosok75 Oct 2, 2025
64aae9c
Apply suggestions from code review
vellosok75 Oct 2, 2025
367a6aa
OPT.LEASTSQUARES.ENH: print_ function automatically handling verbose …
vellosok75 Oct 2, 2025
3d398a6
OPT.LEASTSQUARES.ENH: avoid unnecessary recalculations w/ jacobian
vellosok75 Oct 2, 2025
c90ef7b
OPTICSANLY.NOECO.STY: remove underscore from methods names
vellosok75 Oct 2, 2025
699c81c
NOECO.ENH: raveling of OEORM, new properties & inclusion of gains tra…
vellosok75 Oct 8, 2025
342c807
OPT.LEASTSQUARES.ENH: add `rcond` param for singval selection
vellosok75 Oct 16, 2025
ac6adcc
OPT.LEASTSQUARES.ENH: require position for calculating jacobian
vellosok75 Oct 16, 2025
e3697bc
OPTICSANLY.NOECO.ENH: major refactor (BPMs & corrs. gains, jacobians,…
vellosok75 Oct 16, 2025
9c20a89
Merge branch 'dev/fix/adapt-loco-to-pyaccel' into sexts-calibration
vellosok75 Oct 17, 2025
2eaa15e
OPTANLY.NOECO.FIX: 4D tracking for tune and chrom correction
vellosok75 Oct 23, 2025
776ab07
OPTANLY.ENH: support OEORM block selection
vellosok75 Oct 23, 2025
29dc354
OPTANLY.NOECO.ENH: get initial optimization position from params met…
vellosok75 Oct 23, 2025
6312e59
OPTANLY.NOECO.FEAT: allows choosing whether or not to fit sextupoles
vellosok75 Oct 23, 2025
19134e7
OPTANL.WIP: @vellosok75 and @fernandohds564 partial revistion of code.
fernandohds564 Oct 23, 2025
73d07bb
OPT.LEASTSQUARES.ENH: make `calc_residual` an explicit function of `…
vellosok75 Oct 27, 2025
674f014
OPTANLY.NOECO.ENH: improvements in functions default args & `calc_res…
vellosok75 Oct 27, 2025
8b575bc
OPTANL.WIP: @vellosok75 and @fernandohds564 partial revistion of code.
fernandohds564 Oct 27, 2025
768e680
OPTANLY.NOECO: fix corr. gains jacobian & restore temporary logic for…
vellosok75 Oct 27, 2025
11f01a1
OPTANL.WIP: @vellosok75 and @fernandohds564 partial revistion of code.
fernandohds564 Oct 27, 2025
e65a005
OPTANLY.NOECO.ENH: OEORM blocks selection
vellosok75 Oct 30, 2025
efd7fb1
OPTANLY.NOECO.FEAT: new plotting features
vellosok75 Oct 30, 2025
ad7fd1c
OPTANLY.NOECO: include chromaticity fitting
vellosok75 Nov 11, 2025
8b0a40c
OPT.ANLY.ENH: include dipersion column weight & fix errorbar setting
vellosok75 Nov 19, 2025
3e598ca
Merge branch 'master' into sexts-calibration
vellosok75 Jan 12, 2026
93d6600
OPT.LEASTSQUARES.ENH: include tentative steps to LM loop
vellosok75 Jan 15, 2026
02dacde
OPT.LSTSQR.ENH: use squared error instead of MSQE as chi2
vellosok75 Mar 3, 2026
0e5d267
OPT.BASE.ENH: float type formatting in __str__ method
vellosok75 Mar 5, 2026
d34c8af
OPTANLY.NOECO.ENH: include default limits to initialization method
vellosok75 Mar 5, 2026
7dcace7
OPT.LSTSQ.DOC: add `__str__` method
vellosok75 Mar 5, 2026
4544858
OPT.BASE.ENH: force right alignment for strings in __str__ method
vellosok75 Mar 5, 2026
d0762d2
OPTANLY.NOECO.DOC: add `__str__` method
vellosok75 Mar 5, 2026
ebb2358
WIP (OPT.LEASTSQRS): include Tikhonovo/Ridge regularization
vellosok75 Mar 6, 2026
a1a1f4a
BUG (OPT.LEASTSQRS): avoid division by zero when LM damping param is …
vellosok75 Mar 10, 2026
284d2d7
ENH (LSTSQRS): handle tikhonov constants size
vellosok75 Mar 13, 2026
1ab61af
ENH (OPT.LSTSQRS): tikhonov regulariszation in parameter space
vellosok75 Mar 31, 2026
5329183
Merge branch 'master' into sexts-calibration
vellosok75 Apr 1, 2026
20e8e46
ENH (LSTSQRS) : add suport for additional regularizations
vellosok75 Apr 7, 2026
6795fb4
ENH: (LSTSQRS): support the removal of last n singular values
vellosok75 May 7, 2026
ab9a928
ENH (OPTANLY.NOECO): control whether to adjust or not chroms and tune…
vellosok75 May 7, 2026
02e5bcb
last changes before IPAC
vellosok75 Jun 15, 2026
c3263f4
Merge branch 'master' into sexts-calibration
vellosok75 Jul 28, 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
253 changes: 253 additions & 0 deletions apsuite/optics_analysis/noeco.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
"""."""

import matplotlib.pyplot as _mplt
import numpy as _np
import pyaccel as _pa
from pyaccel.optics.miscellaneous import (
get_chromaticities,
get_mcf,
get_rf_frequency
)
from pymodels import si as _si

from apsuite.optics_analysis import ChromCorr, TuneCorr
from apsuite.optimization.least_squares import (
LeastSquaresOptimize,
LeastSquaresParams
)
from apsuite.orbcorr import OrbRespmat


class NOECOParams(LeastSquaresParams):
"""."""

SEXT_FAMS = (
'SDA0',
'SDB0',
'SDP0',
'SFA0',
'SFB0',
'SFP0',
'SDA1',
'SDB1',
'SDP1',
'SDA2',
'SDB2',
'SDP2',
'SDA3',
'SDB3',
'SDP3',
'SFA1',
'SFB1',
'SFP1',
'SFA2',
'SFB2',
'SFP2',
)
SEXT_FAMS_ACHROM = SEXT_FAMS[:6]
SEXT_FAMS_CHROM = SEXT_FAMS[6:]

def __init__(self):
"""."""
super().__init__()
self.denergy_oeorm_calc = 1e-2 # 1 %
self.tunex = 49.16
self.tuney = 14.22
self.chromx = 3.11
self.chromy = 2.5
self.sextfams2fit = self.SEXT_FAMS_CHROM


class NOECOFit(LeastSquaresOptimize):
"""."""

def __init__(
self,
merit_figure_goal=None,
jacobian=None,
use_thread=True,
isonline=False,
):
"""."""
params = NOECOParams()
super().__init__(
params, merit_figure_goal, jacobian, use_thread, isonline
)
self._model = None
self.famdata = None

self._orbmat = None
self._tunecorr = None
self._chromcorr = None

@property
def model(self):
"""."""
return self._model

@model.setter
def model(self, model):
"""."""
if not isinstance(model, _pa.accelerator.Accelerator):
raise TypeError('model must be pyaccel.accelerator.Accelerator')
self._model = model
self.famdata = _si.get_family_data(self._model)
self.create_corr_objects()

def create_model(self):
"""."""
self.model = _si.create_accelerator()

def create_corr_objects(self):
"""."""
if self.model is None:
raise RuntimeError(
'self.model is None. Call create_model() or set model first'
)

if self._orbmat is None:
self._orbmat = OrbRespmat(
model=self.model, acc='SI', use6dtrack=True
)
if self._tunecorr is None:
self._tunecorr = TuneCorr(model=self.model, acc='SI')
if self._chromcorr is None:
self._chromcorr = ChromCorr(model=self.model, acc='SI')

def adjust_model(self, tunes=None, chroms=None):
"""."""
if tunes is None:
tunes = self.params.tunex, self.params.tuney
if chroms is None:
chroms = self.params.chromx, self.params.chromy

self._tunecorr.correct_parameters(tunes, model=self.model)
self._chromcorr.correct_parameters(chroms, model=self.model)

def _set_energy_offset(self, energy_offset, model=None):
"""."""
if model is None:
model = self.model
freq0 = get_rf_frequency(model)
alpha = get_mcf(model)
df = -alpha * freq0 * energy_offset
self._set_delta_rf_frequency(df, model)

def _set_delta_rf_frequency(self, delta_frequency, model=None):
if model is None:
model = self.model
freq0 = get_rf_frequency(model)
print(f'Changing RF freq. by {delta_frequency:.4e}')
self._set_rf_frequency(freq0 + delta_frequency, model)

def _set_rf_frequency(self, frequency, model=None):
"""."""
if model is None:
model = self.model
cav_idx = _si.get_family_data(model)['SRFCav']['index'][0][0]
print(f'Setting RF freq. to {frequency:.8e}')
model[cav_idx].frequency = frequency

def get_strengths_(self):
"""."""
strengths = list()
for fam in self.params.sextfams2fit:
idc = _pa.lattice.flatten(self.famdata[fam]['index'])[0]
strengths.append(self.model[idc].SL)
return _np.array(strengths)

def set_strengths_(self, strengths):
"""."""
for stren, fam in zip(strengths, self.params.sextfams2fit):
idcs = _pa.lattice.flatten(self.famdata[fam]['index'])
_pa.lattice.set_attribute(self.model, 'SL', idcs, stren)

def get_oeorm(
self,
model=None,
strengths=None,
idempotent=True,
delta=1e-2,
normalize_rf=True,
ravel=False,
):
"""."""
if model is None:
model = self.model

if strengths is not None:
pos0 = self.get_strengths_()
self.set_strengths_(strengths)

rf_freq0 = get_rf_frequency(self.model)
self._set_energy_offset(energy_offset=delta, model=self.model)
mat_pos = self._orbmat.get_respm(add_rfline=True)

self._set_rf_frequency(frequency=rf_freq0, model=self.model)

self._set_energy_offset(energy_offset=-delta, model=self.model)
mat_neg = self._orbmat.get_respm(add_rfline=True)

self._set_rf_frequency(frequency=rf_freq0, model=self.model)

oeorm = (mat_pos - mat_neg) / 2 / delta

if normalize_rf:
oeorm[:, -1] *= 1e6 # [um/Hz]

if ravel:
oeorm = _np.ravel(oeorm)

if strengths is not None and idempotent:
self.set_strengths_(pos0)

return oeorm

def get_chroms(self, strengths=None, idempotent=True):
"""."""
if strengths is not None:
pos0 = self.get_strengths_()
self.set_strengths_(strengths)

chroms = get_chromaticities(self.model)

if strengths is not None and idempotent:
self.set_strengths_(pos0)

return _np.array(chroms)

def calc_merit_figure(self, pos):
"""."""
oeorm = self.get_oeorm(
strengths=pos,
idempotent=True,
delta=self.params.denergy_oeorm_calc,
normalize_rf=True,
ravel=True,
)
return oeorm

def get_optimization_pos(self):
"""."""
pos = self.get_strengths_()
# other params will go here as well (gains, rolls, etc)
return pos

def plot_strengths(self, strengths=None, fig=None, ax=None, label=None):
"""."""
if fig is None or ax is None:
fig, ax = _mplt.subplots(figsize=(12, 4))
if strengths is None:
strengths = self.get_strengths_()

ax.plot(strengths, 'o-', mfc='none', label=label)
tick_label = self.params.sextfams2fit
tick_pos = list(range(len(tick_label)))
ax.set_xticks(tick_pos, labels=tick_label, rotation=45)
ax.set_ylabel(r'SL [m$^{-2}$]')
ax.set_xlabel('chromatic sextupole families')
if label:
ax.legend()
fig.tight_layout()
fig.show()
return fig, ax
4 changes: 2 additions & 2 deletions apsuite/optimization/base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Simulated Annealing Algorithm for Minimization."""
"""."""
import logging as _log

import matplotlib.pyplot as _mplt
Expand Down Expand Up @@ -283,7 +283,7 @@ def _optimize(self):
"""Implement here optimization algorithm."""
raise NotImplementedError()

def _initialization():
def _initialization(self):
"""To be called before optimization starts.

If the return value is False, optimization will not run.
Expand Down
Loading