|
| 1 | +import numpy as np |
| 2 | +import at |
| 3 | +from pySC.core.beam import _real_bpm_reading |
| 4 | +from pySC.lattice_properties.response_model import SCgetModelDispersion |
| 5 | +from pySC.core.constants import NUM_TO_AB |
| 6 | +from pySC.utils import sc_tools |
| 7 | +import copy |
| 8 | + |
| 9 | +LOGGER = logging_tools.get_logger(__name__) |
| 10 | + |
| 11 | +def phase_advance_correction(ring, bpm_indices, elements_indices, dkick, cut, Px=None, Py=None, Etax=None): |
| 12 | + """ |
| 13 | + Perform phase advance and horizontal dispersion correction on the given ring. |
| 14 | + |
| 15 | + Parameters: |
| 16 | + dkick: Change in quadrupole strength for response matrix calculation. |
| 17 | + cut: number of kept singular values |
| 18 | + Px, Py, Etax: response matrices for horizontal and vertical phase advances and dispersion. |
| 19 | + If not provided, they will be calculated. |
| 20 | + |
| 21 | + Returns: |
| 22 | + corrected ring |
| 23 | + """ |
| 24 | + # Initial Twiss parameters |
| 25 | + _, _, twiss_err0 = at.get_optics(ring, bpm_indices) |
| 26 | + elemdata0, beamdata, elemdata = at.get_optics(ring, bpm_indices) |
| 27 | + mux0 = elemdata.mu[:, 0] / (2 * np.pi) |
| 28 | + muy0 = elemdata.mu[:, 1] / (2 * np.pi) |
| 29 | + Eta_x0 = elemdata.dispersion[:, 0] |
| 30 | + |
| 31 | + # Calculate Response Matrix if not provided |
| 32 | + if Px is None or Py is None or Etax is None: |
| 33 | + Px, Py, Etax = calculate_rm(dkick, ring, elements_indices, bpm_indices, mux0, muy0, Eta_x0) |
| 34 | + |
| 35 | + response_matrix = np.hstack((Px, Py, Etax)) |
| 36 | + |
| 37 | + elemdata0, beamdata, elemdata = at.get_optics(ring, bpm_indices) |
| 38 | + mux = elemdata.mu[:, 0] / (2 * np.pi) |
| 39 | + muy = elemdata.mu[:, 1] / (2 * np.pi) |
| 40 | + Eta_xx = elemdata.dispersion[:, 0] |
| 41 | + |
| 42 | + measurement = np.concatenate((mux - mux0, muy - muy0, Eta_xx - Eta_x0), axis=0) |
| 43 | + |
| 44 | + s = np.linalg.svd(response_matrix.T, compute_uv=False) |
| 45 | + system_solution = np.linalg.pinv(response_matrix.T, rcond=s[cut - 1] / s[0]) @ -measurement |
| 46 | + ring = apply_correction(ring, system_solution, elements_indices) |
| 47 | + |
| 48 | + return ring |
| 49 | + |
| 50 | + |
| 51 | +def calculate_rm(dkick, ring, elements_indices, bpm_indices, mux0, muy0, Eta_x0): |
| 52 | + """ |
| 53 | + Returns: |
| 54 | + Px, Py, Etax: Response matrices for horizontal and vertical phase advances and dispersion. |
| 55 | + """ |
| 56 | + px =[] |
| 57 | + py =[] |
| 58 | + etax = [] |
| 59 | + |
| 60 | + for index in elements_indices: |
| 61 | + original_setting = ring[index].PolynomB[1] |
| 62 | + |
| 63 | + ring[index].PolynomB[1] += dkick |
| 64 | + _, _, elemdata = at.get_optics(ring, bpm_indices) |
| 65 | + |
| 66 | + mux = elemdata.mu[:, 0] / (2 * np.pi) |
| 67 | + muy = elemdata.mu[:, 1] / (2 * np.pi) |
| 68 | + Eta_x = elemdata.dispersion[:, 0] |
| 69 | + |
| 70 | + px.append((mux - mux0) / dkick) |
| 71 | + py.append((muy - muy0) / dkick) |
| 72 | + etax.append((Eta_x - Eta_x0) / dkick) |
| 73 | + |
| 74 | + ring[index].PolynomB[1] = original_setting |
| 75 | + |
| 76 | + Px = np.squeeze(np.array(px)) |
| 77 | + Py = np.squeeze(np.array(py)) |
| 78 | + Etax = np.squeeze(np.array(etax)) |
| 79 | + |
| 80 | + return Px, Py, Etax |
| 81 | + |
| 82 | + |
| 83 | +def apply_correction(ring, corrections, elements_indices): |
| 84 | + for i, index in enumerate(elements_indices): |
| 85 | + ring[index].PolynomB[1] += corrections[i] |
| 86 | + return ring |
| 87 | + |
| 88 | +def SCgetModelPhaseAdvanceRM(SC, BPMords, ELEMords, dkick=1e-5, skewness=False, order=1, useIdealRing=True): |
| 89 | + """ |
| 90 | + (to be rewritten) |
| 91 | + Returns: |
| 92 | + Px, Py, Etax: Response matrices for horizontal and vertical phase advances and dispersion. |
| 93 | + """ |
| 94 | + LOGGER.info('Calculating model phase advance response matrix') |
| 95 | + ring = SC.IDEALRING.deepcopy() if useIdealRing else SCgetModelRING(SC) |
| 96 | + |
| 97 | + nBPM = len(BPMords) |
| 98 | + nELEM = len(ELEMords) |
| 99 | + RM = np.full((3 * nBPM, nELEM), np.nan) # (Px+Py+Etax, Elem) |
| 100 | + |
| 101 | + _, _, elemdata0 = at.get_optics(ring, BPMords) |
| 102 | + mux0 = elemdata0.mu[:, 0] / (2 * np.pi) |
| 103 | + muy0 = elemdata0.mu[:, 1] / (2 * np.pi) |
| 104 | + Eta_x0 = elemdata0.dispersion[:, 0] |
| 105 | + Ta = np.hstack((mux0, muy0, Eta_x0)) |
| 106 | + |
| 107 | + for i, ELEMord in enumerate(ELEMords): |
| 108 | + |
| 109 | + PolynomNominal = getattr(ring[ELEMord], f"Polynom{NUM_TO_AB[int(skewness)]}") |
| 110 | + changed_polynom = copy.deepcopy(PolynomNominal[:]) |
| 111 | + changed_polynom[order] += dkick |
| 112 | + setattr(ring[ELEMord], f"Polynom{NUM_TO_AB[int(skewness)]}", changed_polynom[:]) |
| 113 | + |
| 114 | + _, _, elemdata = at.get_optics(ring, BPMords) |
| 115 | + mux = elemdata.mu[:, 0] / (2 * np.pi) |
| 116 | + muy = elemdata.mu[:, 1] / (2 * np.pi) |
| 117 | + Eta_x = elemdata.dispersion[:, 0] |
| 118 | + TdB = np.hstack((mux, muy, Eta_x)) |
| 119 | + |
| 120 | + setattr(ring[ELEMord], f"Polynom{NUM_TO_AB[int(skewness)]}", PolynomNominal[:]) |
| 121 | + dTdB = (TdB - Ta) / dkick |
| 122 | + RM[:, i] = dTdB |
| 123 | + |
| 124 | + return RM |
| 125 | + |
| 126 | +def phase_advance_correction2(SC, BPMords, ELEMords, dkick=1e-5, nturns=64, skewness=False, |
| 127 | + order=1, dipole_compensation=True, alpha=1e-3, RM=None): |
| 128 | + """ |
| 129 | + (To be rewritten) |
| 130 | + Perform phase advance and horizontal dispersion correction on the given ring. |
| 131 | +
|
| 132 | + Parameters: |
| 133 | + dkick: Change in quadrupole strength for response matrix calculation. |
| 134 | + cut: number of kept singular values |
| 135 | + Px, Py, Etax: response matrices for horizontal and vertical phase advances and dispersion. |
| 136 | + If not provided, they will be calculated. |
| 137 | +
|
| 138 | + Returns: |
| 139 | + corrected ring |
| 140 | + """ |
| 141 | + # Ideal Twiss parameters and tunes |
| 142 | + elemdata0, beamdata, elemdata = at.get_optics(SC.IDEALRING, BPMords) |
| 143 | + mux0 = elemdata.mu[:, 0] / (2 * np.pi) |
| 144 | + muy0 = elemdata.mu[:, 1] / (2 * np.pi) |
| 145 | + Eta_x0 = elemdata.dispersion[:, 0] |
| 146 | + Qx0 = beamdata.tune[0] |
| 147 | + Qy0 = beamdata.tune[1] |
| 148 | + |
| 149 | + # Calculate Response Matrix if not provided |
| 150 | + if RM is None: |
| 151 | + RM = SCgetModelPhaseAdvanceRM(SC, BPMords, ELEMords, dkick=dkick, skewness=skewness, order=order, useIdealRing=True) |
| 152 | + |
| 153 | + #track two particles, one with amplitude in x and one in y |
| 154 | + Z0=np.asfortranarray(np.vstack(([dkick, 0, 0, 0, 0, 0], |
| 155 | + [0, 0, dkick, 0, 0, 0])).T) |
| 156 | + Z=at.lattice_pass(SC.RING, Z0, nturns, refpts=SC.ORD.BPM) |
| 157 | + bpm_readings = Z[[0, 2], [0, 1], :, :] |
| 158 | + real_bpm_readings = _real_bpm_reading(SC, bpm_readings[:, np.newaxis, :, :])[0] # advanced indexing to match _real_bpm_reading input |
| 159 | + |
| 160 | + # get tunes and phases using NAFF algorithm |
| 161 | + tune_x, amp_x, phase_x = at.physics.harmonic_analysis.get_main_harmonic(real_bpm_readings[0], fmin=0.5, fmax=0.95) |
| 162 | + tune_y, amp_y, phase_y = at.physics.harmonic_analysis.get_main_harmonic(real_bpm_readings[1], fmin=0.05, fmax=0.5) |
| 163 | + mux = np.unwrap(phase_x) / (2 * np.pi) |
| 164 | + mux *= np.sign(mux) # sign correction needed to fit at.get_optics() phase convention |
| 165 | + muy = np.unwrap(phase_y) / (2 * np.pi) |
| 166 | + muy *= np.sign(muy) # sign correction needed to fit at.get_optics() phase convention |
| 167 | + |
| 168 | + # dispersion calculation |
| 169 | + # I advise to use useIdealRing=True here, otherwise the dispersion is quite noisy |
| 170 | + Eta_x = SCgetModelDispersion(SC, SC.ORD.BPM, CAVords=SC.ORD.RF, useIdealRing=True, rfStep=2e1)[:SC.ORD.BPM.size] |
| 171 | + Eta_x *= -SC.RING.get_rf_frequency() * SC.RING.disable_6d(copy=True).get_mcf() # need proper normalization |
| 172 | + # SCgetModelDispersion does not return the dispersion in [m] |
| 173 | + |
| 174 | + Qx = np.mean(tune_x, keepdims=True) |
| 175 | + Qy = np.mean(tune_y, keepdims=True) |
| 176 | + |
| 177 | + # measurement contains all the observables to minimize |
| 178 | + measurement = np.concatenate((mux - mux0, muy - muy0, Eta_x - Eta_x0, Qx - Qx0, Qy - Qy0), axis=0) |
| 179 | + # weights can be changed to emphasize some observables |
| 180 | + weights = np.hstack((np.ones(mux.size), np.ones(muy.size), np.ones(Eta_x.size), np.ones(1), np.ones(1))) |
| 181 | + |
| 182 | + inverse_RM = sc_tools.pinv(RM, alpha=alpha, plot=False) |
| 183 | + system_solution = -np.dot(inverse_RM, measurement*weights) |
| 184 | + |
| 185 | + return SC, system_solution |
0 commit comments