From 1455f9c18b242c3ea8451f34224e4284dcbcaddf Mon Sep 17 00:00:00 2001 From: Lee Carver Date: Wed, 9 Aug 2023 09:59:26 +0200 Subject: [PATCH 01/19] New PassMethod for simple quantum diffusion. Added element to elements.py. Added function in fastring to generate ring --- atintegrators/SimpleQuantDiffPass.c | 149 ++++++++++++++++++++++++++++ pyat/at/lattice/elements.py | 51 ++++++++-- pyat/at/physics/fastring.py | 147 ++++++++++++++++++++++++++- 3 files changed, 336 insertions(+), 11 deletions(-) create mode 100644 atintegrators/SimpleQuantDiffPass.c diff --git a/atintegrators/SimpleQuantDiffPass.c b/atintegrators/SimpleQuantDiffPass.c new file mode 100644 index 000000000..d2ea6f2c3 --- /dev/null +++ b/atintegrators/SimpleQuantDiffPass.c @@ -0,0 +1,149 @@ + +#include "atelem.c" +#include "atrandom.c" + +struct elem +{ + double emit_x; + double emit_y; + double sigma_dp; + double tau_x; + double tau_y; + double tau_z; + double beta_x; + double beta_y; + double sigma_xp; + double sigma_yp; +}; + +void SimpleQuantDiffPass(double *r_in, + double sigma_xp, double sigma_yp, double sigma_dp, + double tau_x, double tau_y, double tau_z, + pcg32_random_t* rng, int num_particles) + +{ + double *r6; + int c, i; + + #pragma omp parallel for if (num_particles > OMP_PARTICLE_THRESHOLD*10) default(shared) shared(r_in,num_particles) private(c,r6) + for (c = 0; c0.0) { + r6[1] += 2*sigma_xp*sqrt(1/tau_x)*randnorm[0]; + } + if(tau_y>0.0) { + r6[3] += 2*sigma_yp*sqrt(1/tau_y)*randnorm[1]; + } + if(tau_z>0.0) { + r6[4] += 2*sigma_dp*sqrt(1/tau_z)*randnorm[2]; + } + } + } +} + +#if defined(MATLAB_MEX_FILE) || defined(PYAT) +ExportMode struct elem *trackFunction(const atElem *ElemData,struct elem *Elem, + double *r_in, int num_particles, struct parameters *Param) +{ +/* if (ElemData) {*/ + if (!Elem) { + double emit_x, emit_y, sigma_dp, tau_x, tau_y, tau_z, beta_x, beta_y; + emit_x=atGetDouble(ElemData,"emit_x"); check_error(); + emit_y=atGetDouble(ElemData,"emit_y"); check_error(); + sigma_dp=atGetDouble(ElemData,"sigma_dp"); check_error(); + tau_x=atGetDouble(ElemData,"tau_x"); check_error(); + tau_y=atGetDouble(ElemData,"tau_y"); check_error(); + tau_z=atGetDouble(ElemData,"tau_z"); check_error(); + beta_x=atGetDouble(ElemData,"beta_x"); check_error(); + beta_y=atGetDouble(ElemData,"beta_y"); check_error(); + + Elem = (struct elem*)atMalloc(sizeof(struct elem)); + Elem->emit_x=emit_x; + Elem->emit_y=emit_y; + Elem->sigma_dp=sigma_dp; + Elem->tau_x=tau_x; + Elem->tau_y=tau_y; + Elem->tau_z=tau_z; + Elem->beta_x=beta_x; + Elem->beta_y=beta_y; + Elem->sigma_xp=sqrt(emit_x/beta_x); + Elem->sigma_yp=sqrt(emit_y/beta_y); + + } + SimpleQuantDiffPass(r_in, Elem->sigma_xp, Elem->sigma_yp, Elem->sigma_dp, Elem->tau_x, Elem->tau_y, Elem->tau_z, Param->thread_rng, num_particles); +/* } + else { + atFree(Elem->T1); + atFree(Elem->T2); + atFree(Elem->R1); + atFree(Elem->R2); + atFree(Elem->EApertures); + atFree(Elem->RApertures); + }*/ + return Elem; +} + +MODULE_DEF(SimpleQuantDiffPass) /* Dummy module initialisation */ + +#endif /*defined(MATLAB_MEX_FILE) || defined(PYAT)*/ + +#if defined(MATLAB_MEX_FILE) +void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) +{ + if (nrhs == 2) { + double *r_in; + const mxArray *ElemData = prhs[0]; + int num_particles = mxGetN(prhs[1]); + double emit_x, emit_y, sigma_dp, tau_x, tau_y, tau_z, beta_x, beta_y; + + emit_x=atGetDouble(ElemData,"emit_x"); check_error(); + emit_y=atGetDouble(ElemData,"emit_y"); check_error(); + sigma_dp=atGetDouble(ElemData,"sigma_dp"); check_error(); + tau_x=atGetDouble(ElemData,"tau_x"); check_error(); + tau_y=atGetDouble(ElemData,"tau_y"); check_error(); + tau_z=atGetDouble(ElemData,"tau_z"); check_error(); + beta_x=atGetDouble(ElemData,"beta_x"); check_error(); + beta_y=atGetDouble(ElemData,"beta_y"); check_error(); + + if (mxGetM(prhs[1]) != 6) mexErrMsgIdAndTxt("AT:WrongArg","Second argument must be a 6 x N matrix"); + /* ALLOCATE memory for the output array of the same size as the input */ + plhs[0] = mxDuplicateArray(prhs[1]); + r_in = mxGetDoubles(plhs[0]); + SimpleQuantDiffPass(r_in, sigma_xp, sigma_yp, sigma_dp, tau_x, tau_y, tau_z, beta_x, beta_y, &pcg32_global, num_particles); + } + else if (nrhs == 0) { + /* list of required fields */ + plhs[0] = mxCreateCellMatrix(8,1); + mxSetCell(plhs[0],0,mxCreateString("emit_x")); + mxSetCell(plhs[0],1,mxCreateString("emit_y")); + mxSetCell(plhs[0],2,mxCreateString("sigma_dp")); + mxSetCell(plhs[0],3,mxCreateString("tau_x")); + mxSetCell(plhs[0],4,mxCreateString("tau_y")); + mxSetCell(plhs[0],5,mxCreateString("tau_z")); + mxSetCell(plhs[0],6,mxCreateString("beta_x")); + mxSetCell(plhs[0],7,mxCreateString("beta_y")); + + + + /* + if (nlhs>1) { + plhs[1] = mxCreateCellMatrix(6,1); + mxSetCell(plhs[1],0,mxCreateString("T1")); + mxSetCell(plhs[1],1,mxCreateString("T2")); + mxSetCell(plhs[1],2,mxCreateString("R1")); + mxSetCell(plhs[1],3,mxCreateString("R2")); + mxSetCell(plhs[1],4,mxCreateString("RApertures")); + mxSetCell(plhs[1],5,mxCreateString("EApertures")); + }*/ + } + else { + mexErrMsgIdAndTxt("AT:WrongArg","Needs 0 or 2 arguments"); + } +} +#endif /*defined(MATLAB_MEX_FILE)*/ diff --git a/pyat/at/lattice/elements.py b/pyat/at/lattice/elements.py index 2ed8ef446..f993da1e2 100644 --- a/pyat/at/lattice/elements.py +++ b/pyat/at/lattice/elements.py @@ -203,8 +203,8 @@ class Radiative(_Radiative): class Collective(_DictLongtMotion): """Mixin class for elements representing collective effects - Derived classes will automatically set the :py:attr:`~Element.is_collective` - property when the element is active. + Derived classes will automatically set the + :py:attr:`~Element.is_collective` property when the element is active. The class must have a ``default_pass`` class attribute, a dictionary such that: @@ -228,7 +228,7 @@ class Collective(_DictLongtMotion): def _get_collective(self): # noinspection PyUnresolvedReferences return self.PassMethod != self.default_pass[False] - + @abc.abstractmethod def clear_history(self): pass @@ -328,7 +328,7 @@ def swapattr(element, attro, attri): val = getattr(element, attri) delattr(element, attri) return attro, val - if copy: + if copy: el = self.copy() else: el = self @@ -480,14 +480,14 @@ def __init__(self, family_name: str, **kwargs): def set_buffers(self, nturns, nbunch): self._stds = numpy.zeros((6, nbunch, nturns), order='F') self._means = numpy.zeros((6, nbunch, nturns), order='F') - + @property def stds(self): return self._stds - + @property def means(self): - return self._means + return self._means class Aperture(Element): @@ -972,6 +972,43 @@ def __init__(self, family_name: str, m66=None, **kwargs): super(M66, self).__init__(family_name, M66=m66, **kwargs) +class SimpleQuantDiff(Element): + """Linear tracking element for simplified quantum diffusion""" + _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES + ['emit_x', + 'emit_y', + 'sigma_dp', + 'tau_x', + 'tau_y', + 'tau_z', + 'beta_x', + 'beta_y'] + + def __init__(self, family_name: str, emit_x: float, emit_y: float, + sigma_dp: float, tau_x: float, tau_y: float, tau_z: float, + beta_x: float, beta_y: float, **kwargs): + """ + Args: + family_name: Name of the element + emit_x: Horizontal equilibrium emittance [m.rad] + emit_y: Vertical equilibrium emittance [m.rad] + sigma_dp: Equilibrium energy spread + tau_x: Horizontal damping time [turns] + tau_y: Vertical damping time [turns] + tau_z: Longitudinal damping time [turns] + beta_x: Horizontal beta function at element [m] + beta_y: Vertical beta function at element [m] + + Default PassMethod: ``SimpleQuantDiffDamp`` + """ + kwargs.setdefault('PassMethod', 'SimpleQuantDiffPass') + super(SimpleQuantDiff, self).__init__(family_name, + emit_x=emit_x, emit_y=emit_y, + sigma_dp=sigma_dp, tau_x=tau_x, + tau_y=tau_y, tau_z=tau_z, + beta_x=beta_x, beta_y=beta_y, + **kwargs) + + class Corrector(LongElement): """Corrector element""" _BUILD_ATTRIBUTES = LongElement._BUILD_ATTRIBUTES + ['KickAngle'] diff --git a/pyat/at/physics/fastring.py b/pyat/at/physics/fastring.py index 7e5c9af42..4173965de 100644 --- a/pyat/at/physics/fastring.py +++ b/pyat/at/physics/fastring.py @@ -4,13 +4,14 @@ from functools import reduce import numpy from typing import Tuple -from at.lattice import RFCavity, Marker, Lattice, get_cells, checkname -from at.lattice import get_elements +from at.lattice import RFCavity, Element, Marker, Lattice, get_cells, checkname +from at.lattice import get_elements, M66, EnergyLoss, SimpleQuantDiff from at.physics import gen_m66_elem, gen_detuning_elem, gen_quantdiff_elem +from at.constants import clight, e_mass import copy -__all__ = ['fast_ring'] +__all__ = ['fast_ring', 'gen_linear_ring'] def _rearrange(ring: Lattice, split_inds=[]): @@ -63,7 +64,7 @@ def _fring(ring, split_inds=[], detuning_elem=None): try: qd_elem = gen_quantdiff_elem(merged_ring) fastring.append(qd_elem) - except ValueError: # No synchrotron radiation => no diffusion element + except ValueError: # No synchrotron radiation => no diffusion element pass fastring = Lattice(fastring, **vars(ring)) return fastring @@ -100,3 +101,141 @@ def fast_ring(ring: Lattice, split_inds=[]) -> Tuple[Lattice, Lattice]: split_inds=split_inds, detuning_elem=detuning_elem) return fastringnorad, fastringrad + + +def gen_simple_ring(ring_dictionary): + """Generates a "simple ring" based on a given dictionary + of global parameters + + A simple ring consists of: + + * an RF cavity, + * a 6x6 linear transfer map, + * a detuning and chromaticity element, + * an energy loss element + * a simplified quantum diffusion element + + Parameters: + ring_dictionary: Lattice description + The ring_dictionary must contain the following parameters, + * energy [eV] + * circumference [m] + * harmonic_number + * alpha_x, alpha_y + * beta_x, beta_y [m] + * Qx, Qy - full or fractional tunes + * alpha (momentum compaction factor) + * U0 - energy loss [eV] (positive number) + * Vrf - RF Voltage set point [V] + * Qpx, Qpy - linear chromaticities + * A1, A2, A3 - amplitude detuning coefficients + * emit_x, emit_y, sigma_dp - equilibrium values [m.rad, m.rad, -] + + Returns: + ring (Lattice): Simple ring + """ + + # parse everything first + + circumference = ring_dictionary['circumference'] + harmonic_number = ring_dictionary['harmonic_number'] + + energy = ring_dictionary['energy'] + + alpha_x = ring_dictionary['alpha_x'] + alpha_y = ring_dictionary['alpha_y'] + + beta_x = ring_dictionary['beta_x'] + beta_y = ring_dictionary['beta_y'] + + Qx = ring_dictionary['Qx'] + Qy = ring_dictionary['Qy'] + + alpha = ring_dictionary['alpha'] + U0 = ring_dictionary['U0'] + Vrf = ring_dictionary['Vrf'] + + Qpx = ring_dictionary['Qpx'] + Qpy = ring_dictionary['Qpy'] + + A1 = ring_dictionary['A1'] + A2 = ring_dictionary['A2'] + A3 = ring_dictionary['A3'] + + emit_x = ring_dictionary['emit_x'] + emit_y = ring_dictionary['emit_y'] + sigma_dp = ring_dictionary['sigma_dp'] + + # compute rf frequency + frf = harmonic_number * clight / circumference + + # compute slip factor + gamma = energy / e_mass + eta = alpha-1/gamma**2 + + # assuming fixed damping partitions, compute + # analytical damping rate (positive value) + damping_partitions = numpy.array([1, 1, 2]) # x, y, z + tau = 2 * energy / U0 / damping_partitions + + # compute the synchronous phase and the TimeLag + phi_s = numpy.arcsin(U0/Vrf) + TimeLag = clight * phi_s / (2 * numpy.pi * frf) + # generate rf cavity element + rfcav = RFCavity('RFC', 0, Vrf, frf, harmonic_number, energy, + TimeLag=TimeLag) + + # Now we will use the optics parameters to compute the uncoupled M66 matrix + def sincos(x): + return numpy.sin(x), numpy.cos(x) + + s_dphi_x, c_dphi_x = sincos(2*numpy.pi*Qx) + s_dphi_y, c_dphi_y = sincos(2*numpy.pi*Qy) + + M00 = c_dphi_x + alpha_x * s_dphi_x + M01 = beta_x * s_dphi_x + M10 = -(1. + alpha_x**2) / beta_x * s_dphi_x + M11 = c_dphi_x - alpha_x * s_dphi_x + + M22 = c_dphi_y + alpha_y * s_dphi_y + M23 = beta_y * s_dphi_y + M32 = -(1. + alpha_y**2) / beta_y * s_dphi_y + M33 = c_dphi_y - alpha_y * s_dphi_y + + M44 = 1. + M45 = 0. + M54 = eta*circumference + M55 = 1 + + Mat66 = numpy.array([[M00, M01, 0., 0., 0., 0.], + [M10, M11, 0., 0., 0., 0.], + [0., 0., M22, M23, 0., 0.], + [0., 0., M32, M33, 0., 0.], + [0., 0., 0., 0., M44, M45], + [0., 0., 0., 0., M54, M55]], order='F') + + # generate the linear tracking element, we set a length + # which is needed to give the lattice object the correct length + # (although it is not used) + lin_elem = M66('Linear', m66=Mat66, Length=circumference) + + # Generate the simple quantum diffusion element + quantdiff = SimpleQuantDiff('SQD', emit_x, emit_y, sigma_dp, + tau[0], tau[1], tau[2], + beta_x, beta_y) + + # Generate the energy loss element + energyloss = EnergyLoss('eloss', U0, PassMethod='EnergyLossRadPass') + + # Generate the detuning element + nonlin_elem = Element('NonLinear', PassMethod='DeltaQPass', + Betax=beta_x, Betay=beta_y, + Alphax=alpha_x, Alphay=alpha_y, + Qpx=Qpx, Qpy=Qpy, + A1=A1, A2=A2, A3=A3) + + # Assemble all elements into the lattice object + ring = Lattice([rfcav, lin_elem, nonlin_elem, energyloss, quantdiff], + energy=energy) + + return ring From ac8132d1ce49eb61dc011113c0cc1d2a99c17ad0 Mon Sep 17 00:00:00 2001 From: Lee Carver Date: Wed, 9 Aug 2023 10:00:57 +0200 Subject: [PATCH 02/19] Wrong function name in __all__ --- pyat/at/physics/fastring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyat/at/physics/fastring.py b/pyat/at/physics/fastring.py index 4173965de..7d8d7f7f2 100644 --- a/pyat/at/physics/fastring.py +++ b/pyat/at/physics/fastring.py @@ -11,7 +11,7 @@ import copy -__all__ = ['fast_ring', 'gen_linear_ring'] +__all__ = ['fast_ring', 'gen_simple_ring'] def _rearrange(ring: Lattice, split_inds=[]): From ac6dd11a279382b0aac06f79bd37bf1e792953ef Mon Sep 17 00:00:00 2001 From: Lee Carver Date: Wed, 9 Aug 2023 10:08:44 +0200 Subject: [PATCH 03/19] fix matlab test --- atintegrators/SimpleQuantDiffPass.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/atintegrators/SimpleQuantDiffPass.c b/atintegrators/SimpleQuantDiffPass.c index d2ea6f2c3..422aa5a0f 100644 --- a/atintegrators/SimpleQuantDiffPass.c +++ b/atintegrators/SimpleQuantDiffPass.c @@ -100,7 +100,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) double *r_in; const mxArray *ElemData = prhs[0]; int num_particles = mxGetN(prhs[1]); - double emit_x, emit_y, sigma_dp, tau_x, tau_y, tau_z, beta_x, beta_y; + double emit_x, emit_y, sigma_dp, tau_x, tau_y, tau_z, beta_x, beta_y, sigma_xp, sigma_yp; emit_x=atGetDouble(ElemData,"emit_x"); check_error(); emit_y=atGetDouble(ElemData,"emit_y"); check_error(); @@ -110,7 +110,8 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) tau_z=atGetDouble(ElemData,"tau_z"); check_error(); beta_x=atGetDouble(ElemData,"beta_x"); check_error(); beta_y=atGetDouble(ElemData,"beta_y"); check_error(); - + sigma_xp=sqrt(emit_x/beta_x); + sigma_yp=sqrt(emit_y/beta_y); if (mxGetM(prhs[1]) != 6) mexErrMsgIdAndTxt("AT:WrongArg","Second argument must be a 6 x N matrix"); /* ALLOCATE memory for the output array of the same size as the input */ plhs[0] = mxDuplicateArray(prhs[1]); From d8b9a892a5f76900cb6d72dffd90c562a9b67c73 Mon Sep 17 00:00:00 2001 From: Lee Carver Date: Wed, 9 Aug 2023 10:15:43 +0200 Subject: [PATCH 04/19] matlab test --- atintegrators/SimpleQuantDiffPass.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/atintegrators/SimpleQuantDiffPass.c b/atintegrators/SimpleQuantDiffPass.c index 422aa5a0f..38238567a 100644 --- a/atintegrators/SimpleQuantDiffPass.c +++ b/atintegrators/SimpleQuantDiffPass.c @@ -100,7 +100,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) double *r_in; const mxArray *ElemData = prhs[0]; int num_particles = mxGetN(prhs[1]); - double emit_x, emit_y, sigma_dp, tau_x, tau_y, tau_z, beta_x, beta_y, sigma_xp, sigma_yp; + double emit_x, emit_y, sigma_dp, tau_x, tau_y, tau_z, beta_x, beta_y; emit_x=atGetDouble(ElemData,"emit_x"); check_error(); emit_y=atGetDouble(ElemData,"emit_y"); check_error(); @@ -110,13 +110,11 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) tau_z=atGetDouble(ElemData,"tau_z"); check_error(); beta_x=atGetDouble(ElemData,"beta_x"); check_error(); beta_y=atGetDouble(ElemData,"beta_y"); check_error(); - sigma_xp=sqrt(emit_x/beta_x); - sigma_yp=sqrt(emit_y/beta_y); if (mxGetM(prhs[1]) != 6) mexErrMsgIdAndTxt("AT:WrongArg","Second argument must be a 6 x N matrix"); /* ALLOCATE memory for the output array of the same size as the input */ plhs[0] = mxDuplicateArray(prhs[1]); r_in = mxGetDoubles(plhs[0]); - SimpleQuantDiffPass(r_in, sigma_xp, sigma_yp, sigma_dp, tau_x, tau_y, tau_z, beta_x, beta_y, &pcg32_global, num_particles); + SimpleQuantDiffPass(r_in, 1, 1, sigma_dp, tau_x, tau_y, tau_z, beta_x, beta_y, &pcg32_global, num_particles); } else if (nrhs == 0) { /* list of required fields */ From 2ebd2ca7d7a7daab4966366bbc76d561aa17edf8 Mon Sep 17 00:00:00 2001 From: Lee Carver Date: Wed, 9 Aug 2023 10:21:35 +0200 Subject: [PATCH 05/19] matlab tests --- atintegrators/SimpleQuantDiffPass.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/atintegrators/SimpleQuantDiffPass.c b/atintegrators/SimpleQuantDiffPass.c index 38238567a..d4da69056 100644 --- a/atintegrators/SimpleQuantDiffPass.c +++ b/atintegrators/SimpleQuantDiffPass.c @@ -100,7 +100,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) double *r_in; const mxArray *ElemData = prhs[0]; int num_particles = mxGetN(prhs[1]); - double emit_x, emit_y, sigma_dp, tau_x, tau_y, tau_z, beta_x, beta_y; + double emit_x, emit_y, sigma_dp, tau_x, tau_y, tau_z, beta_x, beta_y, sigma_xp, sigma_yp; emit_x=atGetDouble(ElemData,"emit_x"); check_error(); emit_y=atGetDouble(ElemData,"emit_y"); check_error(); @@ -110,11 +110,13 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) tau_z=atGetDouble(ElemData,"tau_z"); check_error(); beta_x=atGetDouble(ElemData,"beta_x"); check_error(); beta_y=atGetDouble(ElemData,"beta_y"); check_error(); + sigma_xp=sqrt(emit_x/beta_x); + sigma_yp=sqrt(emit_y/beta_y); if (mxGetM(prhs[1]) != 6) mexErrMsgIdAndTxt("AT:WrongArg","Second argument must be a 6 x N matrix"); /* ALLOCATE memory for the output array of the same size as the input */ plhs[0] = mxDuplicateArray(prhs[1]); r_in = mxGetDoubles(plhs[0]); - SimpleQuantDiffPass(r_in, 1, 1, sigma_dp, tau_x, tau_y, tau_z, beta_x, beta_y, &pcg32_global, num_particles); + SimpleQuantDiffPass(r_in, sigma_xp, sigma_yp, sigma_dp, tau_x, tau_y, tau_z, &pcg32_global, num_particles); } else if (nrhs == 0) { /* list of required fields */ From 8821e97de9a7e4ffbede5cadb13dc0201acb1e58 Mon Sep 17 00:00:00 2001 From: Lee Carver Date: Wed, 23 Aug 2023 17:29:00 +0200 Subject: [PATCH 06/19] Add radiation damping and U0 to SimpleQuantDiffPass. --- atintegrators/SimpleQuantDiffPass.c | 43 +++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/atintegrators/SimpleQuantDiffPass.c b/atintegrators/SimpleQuantDiffPass.c index d4da69056..a457aef96 100644 --- a/atintegrators/SimpleQuantDiffPass.c +++ b/atintegrators/SimpleQuantDiffPass.c @@ -14,11 +14,13 @@ struct elem double beta_y; double sigma_xp; double sigma_yp; + double U0; + double EnergyLossFactor; }; void SimpleQuantDiffPass(double *r_in, double sigma_xp, double sigma_yp, double sigma_dp, - double tau_x, double tau_y, double tau_z, + double tau_x, double tau_y, double tau_z, double EnergyLossFactor, pcg32_random_t* rng, int num_particles) { @@ -34,15 +36,28 @@ void SimpleQuantDiffPass(double *r_in, } if(!atIsNaN(r6[0])) { - if(tau_x>0.0) { + if(tau_x!=0.0) { + r6[1] -= 2*r6[1]/tau_x; + } + if(sigma_xp!=0.0) { r6[1] += 2*sigma_xp*sqrt(1/tau_x)*randnorm[0]; } - if(tau_y>0.0) { + if(sigma_yp!=0.0) { r6[3] += 2*sigma_yp*sqrt(1/tau_y)*randnorm[1]; } - if(tau_z>0.0) { + if(tau_y!=0.0) { + r6[3] -= 2*r6[3]/tau_y; + } + if(sigma_dp!=0.0) { r6[4] += 2*sigma_dp*sqrt(1/tau_z)*randnorm[2]; } + if(tau_z!=0.0) { + r6[4] -= 2*r6[4]/tau_z; + } + + if(EnergyLossFactor>0.0) { + r6[4] -= EnergyLossFactor; + } } } } @@ -53,7 +68,7 @@ ExportMode struct elem *trackFunction(const atElem *ElemData,struct elem *Elem, { /* if (ElemData) {*/ if (!Elem) { - double emit_x, emit_y, sigma_dp, tau_x, tau_y, tau_z, beta_x, beta_y; + double emit_x, emit_y, sigma_dp, tau_x, tau_y, tau_z, beta_x, beta_y, U0, EnergyLossFactor; emit_x=atGetDouble(ElemData,"emit_x"); check_error(); emit_y=atGetDouble(ElemData,"emit_y"); check_error(); sigma_dp=atGetDouble(ElemData,"sigma_dp"); check_error(); @@ -62,7 +77,8 @@ ExportMode struct elem *trackFunction(const atElem *ElemData,struct elem *Elem, tau_z=atGetDouble(ElemData,"tau_z"); check_error(); beta_x=atGetDouble(ElemData,"beta_x"); check_error(); beta_y=atGetDouble(ElemData,"beta_y"); check_error(); - + U0=atGetDouble(ElemData,"U0"); check_error(); + Elem = (struct elem*)atMalloc(sizeof(struct elem)); Elem->emit_x=emit_x; Elem->emit_y=emit_y; @@ -74,9 +90,10 @@ ExportMode struct elem *trackFunction(const atElem *ElemData,struct elem *Elem, Elem->beta_y=beta_y; Elem->sigma_xp=sqrt(emit_x/beta_x); Elem->sigma_yp=sqrt(emit_y/beta_y); - + Elem->U0=U0; + Elem->EnergyLossFactor=U0/Param->energy; } - SimpleQuantDiffPass(r_in, Elem->sigma_xp, Elem->sigma_yp, Elem->sigma_dp, Elem->tau_x, Elem->tau_y, Elem->tau_z, Param->thread_rng, num_particles); + SimpleQuantDiffPass(r_in, Elem->sigma_xp, Elem->sigma_yp, Elem->sigma_dp, Elem->tau_x, Elem->tau_y, Elem->tau_z, Elem->EnergyLossFactor, Param->thread_rng, num_particles); /* } else { atFree(Elem->T1); @@ -100,7 +117,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) double *r_in; const mxArray *ElemData = prhs[0]; int num_particles = mxGetN(prhs[1]); - double emit_x, emit_y, sigma_dp, tau_x, tau_y, tau_z, beta_x, beta_y, sigma_xp, sigma_yp; + double emit_x, emit_y, sigma_dp, tau_x, tau_y, tau_z, beta_x, beta_y, sigma_xp, sigma_yp, U0, EnergyLossFactor; emit_x=atGetDouble(ElemData,"emit_x"); check_error(); emit_y=atGetDouble(ElemData,"emit_y"); check_error(); @@ -110,17 +127,19 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) tau_z=atGetDouble(ElemData,"tau_z"); check_error(); beta_x=atGetDouble(ElemData,"beta_x"); check_error(); beta_y=atGetDouble(ElemData,"beta_y"); check_error(); + U0=atGetDouble(ElemData,"U0"); check_error(); sigma_xp=sqrt(emit_x/beta_x); sigma_yp=sqrt(emit_y/beta_y); + EnergyLossFactor=U0/6e9; if (mxGetM(prhs[1]) != 6) mexErrMsgIdAndTxt("AT:WrongArg","Second argument must be a 6 x N matrix"); /* ALLOCATE memory for the output array of the same size as the input */ plhs[0] = mxDuplicateArray(prhs[1]); r_in = mxGetDoubles(plhs[0]); - SimpleQuantDiffPass(r_in, sigma_xp, sigma_yp, sigma_dp, tau_x, tau_y, tau_z, &pcg32_global, num_particles); + SimpleQuantDiffPass(r_in, sigma_xp, sigma_yp, sigma_dp, tau_x, tau_y, tau_z, EnergyLossFactor, &pcg32_global, num_particles); } else if (nrhs == 0) { /* list of required fields */ - plhs[0] = mxCreateCellMatrix(8,1); + plhs[0] = mxCreateCellMatrix(9,1); mxSetCell(plhs[0],0,mxCreateString("emit_x")); mxSetCell(plhs[0],1,mxCreateString("emit_y")); mxSetCell(plhs[0],2,mxCreateString("sigma_dp")); @@ -129,7 +148,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) mxSetCell(plhs[0],5,mxCreateString("tau_z")); mxSetCell(plhs[0],6,mxCreateString("beta_x")); mxSetCell(plhs[0],7,mxCreateString("beta_y")); - + mxSetCell(plhs[0],8,mxCreateString("U0")); /* From 72746a3354c28730882744b4f8f4b7d68f4db3c2 Mon Sep 17 00:00:00 2001 From: Lee Carver Date: Wed, 23 Aug 2023 17:29:30 +0200 Subject: [PATCH 07/19] Update arguments for SimpleQuantDiff Element --- pyat/at/lattice/elements.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pyat/at/lattice/elements.py b/pyat/at/lattice/elements.py index f993da1e2..6e9aa70de 100644 --- a/pyat/at/lattice/elements.py +++ b/pyat/at/lattice/elements.py @@ -981,11 +981,12 @@ class SimpleQuantDiff(Element): 'tau_y', 'tau_z', 'beta_x', - 'beta_y'] + 'beta_y', + 'U0'] def __init__(self, family_name: str, emit_x: float, emit_y: float, sigma_dp: float, tau_x: float, tau_y: float, tau_z: float, - beta_x: float, beta_y: float, **kwargs): + beta_x: float, beta_y: float, U0: float, **kwargs): """ Args: family_name: Name of the element @@ -997,8 +998,9 @@ def __init__(self, family_name: str, emit_x: float, emit_y: float, tau_z: Longitudinal damping time [turns] beta_x: Horizontal beta function at element [m] beta_y: Vertical beta function at element [m] - - Default PassMethod: ``SimpleQuantDiffDamp`` + U0: Energy Loss [eV] + + Default PassMethod: ``SimpleQuantDiffPass`` """ kwargs.setdefault('PassMethod', 'SimpleQuantDiffPass') super(SimpleQuantDiff, self).__init__(family_name, @@ -1006,7 +1008,7 @@ def __init__(self, family_name: str, emit_x: float, emit_y: float, sigma_dp=sigma_dp, tau_x=tau_x, tau_y=tau_y, tau_z=tau_z, beta_x=beta_x, beta_y=beta_y, - **kwargs) + U0=U0, **kwargs) class Corrector(LongElement): From 6f1ff883032017a6e7e79b936f1cef0dc7a4610d Mon Sep 17 00:00:00 2001 From: Lee Carver Date: Wed, 23 Aug 2023 17:30:02 +0200 Subject: [PATCH 08/19] Remove energyloss element. Remove analytical tau. --- pyat/at/physics/fastring.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/pyat/at/physics/fastring.py b/pyat/at/physics/fastring.py index 7d8d7f7f2..2233094f7 100644 --- a/pyat/at/physics/fastring.py +++ b/pyat/at/physics/fastring.py @@ -166,6 +166,8 @@ def gen_simple_ring(ring_dictionary): emit_y = ring_dictionary['emit_y'] sigma_dp = ring_dictionary['sigma_dp'] + tau = ring_dictionary['tau'] + # compute rf frequency frf = harmonic_number * clight / circumference @@ -173,11 +175,6 @@ def gen_simple_ring(ring_dictionary): gamma = energy / e_mass eta = alpha-1/gamma**2 - # assuming fixed damping partitions, compute - # analytical damping rate (positive value) - damping_partitions = numpy.array([1, 1, 2]) # x, y, z - tau = 2 * energy / U0 / damping_partitions - # compute the synchronous phase and the TimeLag phi_s = numpy.arcsin(U0/Vrf) TimeLag = clight * phi_s / (2 * numpy.pi * frf) @@ -222,10 +219,7 @@ def sincos(x): # Generate the simple quantum diffusion element quantdiff = SimpleQuantDiff('SQD', emit_x, emit_y, sigma_dp, tau[0], tau[1], tau[2], - beta_x, beta_y) - - # Generate the energy loss element - energyloss = EnergyLoss('eloss', U0, PassMethod='EnergyLossRadPass') + beta_x, beta_y, U0) # Generate the detuning element nonlin_elem = Element('NonLinear', PassMethod='DeltaQPass', @@ -235,7 +229,7 @@ def sincos(x): A1=A1, A2=A2, A3=A3) # Assemble all elements into the lattice object - ring = Lattice([rfcav, lin_elem, nonlin_elem, energyloss, quantdiff], + ring = Lattice([rfcav, lin_elem, nonlin_elem, quantdiff], energy=energy) return ring From 8b09580aa84290ceee4b7b8007c19cb54d073696 Mon Sep 17 00:00:00 2001 From: Lee Carver Date: Fri, 25 Aug 2023 09:46:07 +0200 Subject: [PATCH 09/19] Add rad on/off switching. Modify input types. Set defaults --- pyat/at/lattice/elements.py | 71 +++++++++++++++--------- pyat/at/lattice/lattice_object.py | 2 + pyat/at/physics/fastring.py | 89 ++++++++++++------------------- 3 files changed, 83 insertions(+), 79 deletions(-) diff --git a/pyat/at/lattice/elements.py b/pyat/at/lattice/elements.py index 6e9aa70de..0903e2a90 100644 --- a/pyat/at/lattice/elements.py +++ b/pyat/at/lattice/elements.py @@ -972,43 +972,65 @@ def __init__(self, family_name: str, m66=None, **kwargs): super(M66, self).__init__(family_name, M66=m66, **kwargs) -class SimpleQuantDiff(Element): +class SimpleQuantDiff(_DictLongtMotion, Element): """Linear tracking element for simplified quantum diffusion""" - _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES + ['emit_x', - 'emit_y', - 'sigma_dp', - 'tau_x', - 'tau_y', - 'tau_z', - 'beta_x', - 'beta_y', - 'U0'] - - def __init__(self, family_name: str, emit_x: float, emit_y: float, - sigma_dp: float, tau_x: float, tau_y: float, tau_z: float, - beta_x: float, beta_y: float, U0: float, **kwargs): + _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES + default_pass = {False: 'IdentityPass', True: 'SimpleQuantDiffPass'} + + def __init__(self, family_name: str, beta_x: Optional[float]=1.0, + beta_y: Optional[float]=1.0, emit_x: Optional[float]=0.0, + emit_y: Optional[float]=0.0, sigma_dp: Optional[float]=0.0, + tau_x: Optional[float]=0.0, tau_y: Optional[float]=0.0, + tau_z: Optional[float]=0.0, U0: Optional[float]=0.0, + **kwargs): """ Args: family_name: Name of the element + + Optional Args: + beta_x: Horizontal beta function at element [m] + beta_y: Vertical beta function at element [m] emit_x: Horizontal equilibrium emittance [m.rad] emit_y: Vertical equilibrium emittance [m.rad] sigma_dp: Equilibrium energy spread tau_x: Horizontal damping time [turns] tau_y: Vertical damping time [turns] tau_z: Longitudinal damping time [turns] - beta_x: Horizontal beta function at element [m] - beta_y: Vertical beta function at element [m] U0: Energy Loss [eV] - Default PassMethod: ``SimpleQuantDiffPass`` + Default PassMethod: ``IdentityPass`` """ - kwargs.setdefault('PassMethod', 'SimpleQuantDiffPass') - super(SimpleQuantDiff, self).__init__(family_name, - emit_x=emit_x, emit_y=emit_y, - sigma_dp=sigma_dp, tau_x=tau_x, - tau_y=tau_y, tau_z=tau_z, - beta_x=beta_x, beta_y=beta_y, - U0=U0, **kwargs) + kwargs.setdefault('PassMethod', self.default_pass[False]) + + assert tau_x>=0.0, 'tau_x must be greater than or equal to 0' + self.tau_x = tau_x + + assert tau_y>=0.0, 'tau_y must be greater than or equal to 0' + self.tau_y = tau_y + + assert tau_z>=0.0, 'tau_z must be greater than or equal to 0' + self.tau_z = tau_z + + assert emit_x>=0.0, 'emit_x must be greater than or equal to 0' + self.emit_x = emit_x + if emit_x>0.0: + assert tau_x>0.0, 'if emit_x is given, tau_x must be non zero' + + assert emit_y>=0.0, 'emit_x must be greater than or equal to 0' + self.emit_y = emit_y + if emit_y>0.0: + assert tau_y>0.0, 'if emit_y is given, tau_y must be non zero' + + assert sigma_dp>=0.0, 'sigma_dp must be greater than or equal to 0' + self.sigma_dp = sigma_dp + if sigma_dp>0.0: + assert tau_z>0.0, 'if sigma_dp is given, tau_z must be non zero' + + self.U0 = U0 + self.beta_x = beta_x + self.beta_y = beta_y + super(SimpleQuantDiff, self).__init__(family_name, **kwargs) + class Corrector(LongElement): @@ -1124,7 +1146,6 @@ def __init__(self, family_name: str, energy_loss: float, **kwargs): kwargs.setdefault('PassMethod', self.default_pass[False]) super().__init__(family_name, EnergyLoss=energy_loss, **kwargs) - Radiative.register(EnergyLoss) diff --git a/pyat/at/lattice/lattice_object.py b/pyat/at/lattice/lattice_object.py index e9eaa417f..e122f4069 100644 --- a/pyat/at/lattice/lattice_object.py +++ b/pyat/at/lattice/lattice_object.py @@ -50,6 +50,7 @@ ('collective_pass', elt.Collective, 'auto'), ('diffusion_pass', elt.QuantumDiffusion, 'auto'), ('energyloss_pass', elt.EnergyLoss, 'auto'), + ('simplequantdiff_pass', elt.SimpleQuantDiff, 'auto') ), True: ( ('cavity_pass', elt.RFCavity, 'auto'), @@ -62,6 +63,7 @@ ('collective_pass', elt.Collective, 'auto'), ('diffusion_pass', elt.QuantumDiffusion, 'auto'), ('energyloss_pass', elt.EnergyLoss, 'auto'), + ('simplequantdiff_pass', elt.SimpleQuantDiff, 'auto') ) } diff --git a/pyat/at/physics/fastring.py b/pyat/at/physics/fastring.py index 2233094f7..a86d01496 100644 --- a/pyat/at/physics/fastring.py +++ b/pyat/at/physics/fastring.py @@ -3,15 +3,15 @@ """ from functools import reduce import numpy -from typing import Tuple +from typing import Tuple, Optional from at.lattice import RFCavity, Element, Marker, Lattice, get_cells, checkname -from at.lattice import get_elements, M66, EnergyLoss, SimpleQuantDiff +from at.lattice import get_elements, M66, SimpleQuantDiff from at.physics import gen_m66_elem, gen_detuning_elem, gen_quantdiff_elem from at.constants import clight, e_mass import copy -__all__ = ['fast_ring', 'gen_simple_ring'] +__all__ = ['fast_ring', 'simple_ring'] def _rearrange(ring: Lattice, split_inds=[]): @@ -103,7 +103,17 @@ def fast_ring(ring: Lattice, split_inds=[]) -> Tuple[Lattice, Lattice]: return fastringnorad, fastringrad -def gen_simple_ring(ring_dictionary): +def simple_ring(energy: float, circumference: float, harmonic_number: float, + Qx: float, Qy: float, Vrf: float, alpha: float, + beta_x: Optional[float]=1.0, beta_y: Optional[float]=1.0, + alpha_x: Optional[float]=0.0, alpha_y: Optional[float]=0.0, + Qpx: Optional[float]=0.0, Qpy: Optional[float]=0.0, + A1: Optional[float]=0.0, A2: Optional[float]=0.0, + A3: Optional[float]=0.0, emit_x: Optional[float]=0.0, + emit_y: Optional[float]=0.0, sigma_dp: Optional[float]=0.0, + tau_x: Optional[float]=0.0, tau_y: Optional[float]=0.0, + tau_z: Optional[float]=0.0, U0: Optional[float]=0.0 + ): """Generates a "simple ring" based on a given dictionary of global parameters @@ -116,58 +126,28 @@ def gen_simple_ring(ring_dictionary): * a simplified quantum diffusion element Parameters: - ring_dictionary: Lattice description - The ring_dictionary must contain the following parameters, - * energy [eV] - * circumference [m] - * harmonic_number - * alpha_x, alpha_y - * beta_x, beta_y [m] - * Qx, Qy - full or fractional tunes - * alpha (momentum compaction factor) - * U0 - energy loss [eV] (positive number) - * Vrf - RF Voltage set point [V] - * Qpx, Qpy - linear chromaticities - * A1, A2, A3 - amplitude detuning coefficients - * emit_x, emit_y, sigma_dp - equilibrium values [m.rad, m.rad, -] + * energy [eV] + * circumference [m] + * harmonic_number + * Qx - horizontal tune + * Qy - vertical tune + * Vrf - RF Voltage set point [V] + * alpha (momentum compaction factor) + + Optional Arguments: + * beta_x, beta_y [m] + * alpha_x, alpha_y + * Qpx, Qpy - linear chromaticities + * A1, A2, A3 - amplitude detuning coefficients + * emit_x, emit_y, sigma_dp - equilibrium values [m.rad, m.rad, -] + * tau_x, tau_y, tau_z - radiation damping times [turns] + * U0 - energy loss [eV] (positive number) Returns: ring (Lattice): Simple ring """ - # parse everything first - - circumference = ring_dictionary['circumference'] - harmonic_number = ring_dictionary['harmonic_number'] - - energy = ring_dictionary['energy'] - - alpha_x = ring_dictionary['alpha_x'] - alpha_y = ring_dictionary['alpha_y'] - - beta_x = ring_dictionary['beta_x'] - beta_y = ring_dictionary['beta_y'] - - Qx = ring_dictionary['Qx'] - Qy = ring_dictionary['Qy'] - - alpha = ring_dictionary['alpha'] - U0 = ring_dictionary['U0'] - Vrf = ring_dictionary['Vrf'] - - Qpx = ring_dictionary['Qpx'] - Qpy = ring_dictionary['Qpy'] - - A1 = ring_dictionary['A1'] - A2 = ring_dictionary['A2'] - A3 = ring_dictionary['A3'] - - emit_x = ring_dictionary['emit_x'] - emit_y = ring_dictionary['emit_y'] - sigma_dp = ring_dictionary['sigma_dp'] - - tau = ring_dictionary['tau'] - + # compute rf frequency frf = harmonic_number * clight / circumference @@ -217,9 +197,10 @@ def sincos(x): lin_elem = M66('Linear', m66=Mat66, Length=circumference) # Generate the simple quantum diffusion element - quantdiff = SimpleQuantDiff('SQD', emit_x, emit_y, sigma_dp, - tau[0], tau[1], tau[2], - beta_x, beta_y, U0) + quantdiff = SimpleQuantDiff('SQD', beta_x=beta_x, beta_y=beta_y, + emit_x=emit_x, emit_y=emit_y, + sigma_dp=sigma_dp, tau_x=tau_x, + tau_y=tau_y, tau_z=tau_z, U0=U0) # Generate the detuning element nonlin_elem = Element('NonLinear', PassMethod='DeltaQPass', From 00339313802ce21835b172ea0db20cdd48850b40 Mon Sep 17 00:00:00 2001 From: Lee Carver Date: Fri, 25 Aug 2023 09:49:24 +0200 Subject: [PATCH 10/19] pep --- pyat/at/physics/fastring.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pyat/at/physics/fastring.py b/pyat/at/physics/fastring.py index a86d01496..1f4780731 100644 --- a/pyat/at/physics/fastring.py +++ b/pyat/at/physics/fastring.py @@ -122,10 +122,10 @@ def simple_ring(energy: float, circumference: float, harmonic_number: float, * an RF cavity, * a 6x6 linear transfer map, * a detuning and chromaticity element, - * an energy loss element * a simplified quantum diffusion element + which contains equilibrium emittance and radiation damping - Parameters: + Positional Arguments: * energy [eV] * circumference [m] * harmonic_number @@ -134,8 +134,8 @@ def simple_ring(energy: float, circumference: float, harmonic_number: float, * Vrf - RF Voltage set point [V] * alpha (momentum compaction factor) - Optional Arguments: - * beta_x, beta_y [m] + Optional Arguments: + * beta_x, beta_y [m] * alpha_x, alpha_y * Qpx, Qpy - linear chromaticities * A1, A2, A3 - amplitude detuning coefficients @@ -147,7 +147,6 @@ def simple_ring(energy: float, circumference: float, harmonic_number: float, ring (Lattice): Simple ring """ - # compute rf frequency frf = harmonic_number * clight / circumference @@ -197,7 +196,7 @@ def sincos(x): lin_elem = M66('Linear', m66=Mat66, Length=circumference) # Generate the simple quantum diffusion element - quantdiff = SimpleQuantDiff('SQD', beta_x=beta_x, beta_y=beta_y, + quantdiff = SimpleQuantDiff('SQD', beta_x=beta_x, beta_y=beta_y, emit_x=emit_x, emit_y=emit_y, sigma_dp=sigma_dp, tau_x=tau_x, tau_y=tau_y, tau_z=tau_z, U0=U0) From 3f9281fe82cb6bdf0594ed51037147f9f625e166 Mon Sep 17 00:00:00 2001 From: Lee Carver Date: Fri, 25 Aug 2023 10:21:28 +0200 Subject: [PATCH 11/19] change default passmethod of simplequantdiff --- pyat/at/lattice/elements.py | 2 +- pyat/at/physics/fastring.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyat/at/lattice/elements.py b/pyat/at/lattice/elements.py index 0903e2a90..66aa918a1 100644 --- a/pyat/at/lattice/elements.py +++ b/pyat/at/lattice/elements.py @@ -1000,7 +1000,7 @@ def __init__(self, family_name: str, beta_x: Optional[float]=1.0, Default PassMethod: ``IdentityPass`` """ - kwargs.setdefault('PassMethod', self.default_pass[False]) + kwargs.setdefault('PassMethod', self.default_pass[True]) assert tau_x>=0.0, 'tau_x must be greater than or equal to 0' self.tau_x = tau_x diff --git a/pyat/at/physics/fastring.py b/pyat/at/physics/fastring.py index 1f4780731..79ad46ea3 100644 --- a/pyat/at/physics/fastring.py +++ b/pyat/at/physics/fastring.py @@ -152,7 +152,7 @@ def simple_ring(energy: float, circumference: float, harmonic_number: float, # compute slip factor gamma = energy / e_mass - eta = alpha-1/gamma**2 + eta = alpha - 1/gamma**2 # compute the synchronous phase and the TimeLag phi_s = numpy.arcsin(U0/Vrf) From c51a4e4ff6ce5be08605959d74cd0024dcc1e637 Mon Sep 17 00:00:00 2001 From: Lee Carver Date: Fri, 25 Aug 2023 10:36:08 +0200 Subject: [PATCH 12/19] Add periodicity=1 to avoid at warning about no bends --- pyat/at/physics/fastring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyat/at/physics/fastring.py b/pyat/at/physics/fastring.py index 79ad46ea3..7fc64e6a2 100644 --- a/pyat/at/physics/fastring.py +++ b/pyat/at/physics/fastring.py @@ -210,6 +210,6 @@ def sincos(x): # Assemble all elements into the lattice object ring = Lattice([rfcav, lin_elem, nonlin_elem, quantdiff], - energy=energy) + energy=energy, periodicity=1) return ring From 6da14e3ba6cded349c98afb638e37032ddf3cef5 Mon Sep 17 00:00:00 2001 From: Lee Carver Date: Fri, 25 Aug 2023 10:37:54 +0200 Subject: [PATCH 13/19] Change type hint for harmonic_number to int instead of float --- pyat/at/physics/fastring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyat/at/physics/fastring.py b/pyat/at/physics/fastring.py index 7fc64e6a2..66493fdf3 100644 --- a/pyat/at/physics/fastring.py +++ b/pyat/at/physics/fastring.py @@ -103,7 +103,7 @@ def fast_ring(ring: Lattice, split_inds=[]) -> Tuple[Lattice, Lattice]: return fastringnorad, fastringrad -def simple_ring(energy: float, circumference: float, harmonic_number: float, +def simple_ring(energy: float, circumference: float, harmonic_number: int, Qx: float, Qy: float, Vrf: float, alpha: float, beta_x: Optional[float]=1.0, beta_y: Optional[float]=1.0, alpha_x: Optional[float]=0.0, alpha_y: Optional[float]=0.0, From 3f17e46c712c08d0906f347feef7fac68951a05e Mon Sep 17 00:00:00 2001 From: Lee Carver Date: Fri, 25 Aug 2023 10:41:52 +0200 Subject: [PATCH 14/19] expanded help --- pyat/at/physics/fastring.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyat/at/physics/fastring.py b/pyat/at/physics/fastring.py index 66493fdf3..ad6b73dd6 100644 --- a/pyat/at/physics/fastring.py +++ b/pyat/at/physics/fastring.py @@ -143,6 +143,11 @@ def simple_ring(energy: float, circumference: float, harmonic_number: int, * tau_x, tau_y, tau_z - radiation damping times [turns] * U0 - energy loss [eV] (positive number) + If the given emit_x,emit_y or sigma_dp is 0, then no equlibrium emittance is + applied in this plane. + If the given tau is 0, then no radiation damping is applied for this plane. + + Returns: ring (Lattice): Simple ring """ From d40dfd315473c76de6c47656ed9feba625980c43 Mon Sep 17 00:00:00 2001 From: Lee Carver Date: Fri, 25 Aug 2023 10:43:21 +0200 Subject: [PATCH 15/19] wrong text --- pyat/at/lattice/elements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyat/at/lattice/elements.py b/pyat/at/lattice/elements.py index 66aa918a1..076d534bf 100644 --- a/pyat/at/lattice/elements.py +++ b/pyat/at/lattice/elements.py @@ -998,7 +998,7 @@ def __init__(self, family_name: str, beta_x: Optional[float]=1.0, tau_z: Longitudinal damping time [turns] U0: Energy Loss [eV] - Default PassMethod: ``IdentityPass`` + Default PassMethod: ``SimpleQuantDiffPass`` """ kwargs.setdefault('PassMethod', self.default_pass[True]) From cb3479dbeda32b7b659cd018d032cafcb77d2c84 Mon Sep 17 00:00:00 2001 From: Lee Carver Date: Fri, 25 Aug 2023 16:15:46 +0200 Subject: [PATCH 16/19] Modified text --- pyat/at/lattice/elements.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyat/at/lattice/elements.py b/pyat/at/lattice/elements.py index 076d534bf..e17825909 100644 --- a/pyat/at/lattice/elements.py +++ b/pyat/at/lattice/elements.py @@ -973,7 +973,10 @@ def __init__(self, family_name: str, m66=None, **kwargs): class SimpleQuantDiff(_DictLongtMotion, Element): - """Linear tracking element for simplified quantum diffusion""" + """ + Linear tracking element for a simplified quantum diffusion, + radiation damping and energy loss + """ _BUILD_ATTRIBUTES = Element._BUILD_ATTRIBUTES default_pass = {False: 'IdentityPass', True: 'SimpleQuantDiffPass'} From f212be992df54d92815de8aba55298dc9d1cff51 Mon Sep 17 00:00:00 2001 From: Lee Carver Date: Fri, 25 Aug 2023 16:16:00 +0200 Subject: [PATCH 17/19] Added multiple cavity inputs --- pyat/at/physics/fastring.py | 46 +++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/pyat/at/physics/fastring.py b/pyat/at/physics/fastring.py index ad6b73dd6..6a77967bc 100644 --- a/pyat/at/physics/fastring.py +++ b/pyat/at/physics/fastring.py @@ -112,7 +112,8 @@ def simple_ring(energy: float, circumference: float, harmonic_number: int, A3: Optional[float]=0.0, emit_x: Optional[float]=0.0, emit_y: Optional[float]=0.0, sigma_dp: Optional[float]=0.0, tau_x: Optional[float]=0.0, tau_y: Optional[float]=0.0, - tau_z: Optional[float]=0.0, U0: Optional[float]=0.0 + tau_z: Optional[float]=0.0, U0: Optional[float]=0.0, + SetTimeLag: Optional[bool]=False ): """Generates a "simple ring" based on a given dictionary of global parameters @@ -128,10 +129,10 @@ def simple_ring(energy: float, circumference: float, harmonic_number: int, Positional Arguments: * energy [eV] * circumference [m] - * harmonic_number + * harmonic_number - can be scalar or 1d array * Qx - horizontal tune * Qy - vertical tune - * Vrf - RF Voltage set point [V] + * Vrf - RF Voltage set point [V] - can be scalar or 1d array * alpha (momentum compaction factor) Optional Arguments: @@ -142,9 +143,10 @@ def simple_ring(energy: float, circumference: float, harmonic_number: int, * emit_x, emit_y, sigma_dp - equilibrium values [m.rad, m.rad, -] * tau_x, tau_y, tau_z - radiation damping times [turns] * U0 - energy loss [eV] (positive number) + * SetTimeLag - Boolean to determine if TimeLag of cavity should be set - If the given emit_x,emit_y or sigma_dp is 0, then no equlibrium emittance is - applied in this plane. + If the given emit_x,emit_y or sigma_dp is 0, then no equlibrium emittance + is applied in this plane. If the given tau is 0, then no radiation damping is applied for this plane. @@ -152,19 +154,33 @@ def simple_ring(energy: float, circumference: float, harmonic_number: int, ring (Lattice): Simple ring """ - # compute rf frequency - frf = harmonic_number * clight / circumference - # compute slip factor gamma = energy / e_mass eta = alpha - 1/gamma**2 - # compute the synchronous phase and the TimeLag - phi_s = numpy.arcsin(U0/Vrf) - TimeLag = clight * phi_s / (2 * numpy.pi * frf) - # generate rf cavity element - rfcav = RFCavity('RFC', 0, Vrf, frf, harmonic_number, energy, - TimeLag=TimeLag) + harmonic_number = numpy.atleast_1d(harmonic_number) + Vrf = numpy.atleast_1d(Vrf) + + assert len(harmonic_number) == len(Vrf), ( + "harmonic_number input must match length of Vrf input" + ) + + # compute rf frequency + frf = harmonic_number * clight / circumference + + all_cavities = [] + for icav in numpy.arange(len(harmonic_number)): + if icav == 0 and SetTimeLag: + # compute the synchronous phase and the TimeLag + phi_s = numpy.arcsin(U0/Vrf) + TimeLag = clight * phi_s / (2 * numpy.pi * frf) + else: + TimeLag = 0 + + # generate rf cavity element + rfcav = RFCavity('RFC', 0, Vrf[icav], frf[icav], harmonic_number[icav], + energy, TimeLag=TimeLag) + all_cavities.append(rfcav) # Now we will use the optics parameters to compute the uncoupled M66 matrix def sincos(x): @@ -214,7 +230,7 @@ def sincos(x): A1=A1, A2=A2, A3=A3) # Assemble all elements into the lattice object - ring = Lattice([rfcav, lin_elem, nonlin_elem, quantdiff], + ring = Lattice(all_cavities + [lin_elem, nonlin_elem, quantdiff], energy=energy, periodicity=1) return ring From 7b630cd3a2b57eaa8d3e41ae48ac67b923d6ea61 Mon Sep 17 00:00:00 2001 From: Lee Carver Date: Fri, 25 Aug 2023 16:18:30 +0200 Subject: [PATCH 18/19] Remved sincos --- pyat/at/physics/fastring.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyat/at/physics/fastring.py b/pyat/at/physics/fastring.py index 6a77967bc..d867039a4 100644 --- a/pyat/at/physics/fastring.py +++ b/pyat/at/physics/fastring.py @@ -183,11 +183,11 @@ def simple_ring(energy: float, circumference: float, harmonic_number: int, all_cavities.append(rfcav) # Now we will use the optics parameters to compute the uncoupled M66 matrix - def sincos(x): - return numpy.sin(x), numpy.cos(x) - s_dphi_x, c_dphi_x = sincos(2*numpy.pi*Qx) - s_dphi_y, c_dphi_y = sincos(2*numpy.pi*Qy) + s_dphi_x = numpy.sin(2*numpy.pi*Qx) + c_dphi_x = numpy.cos(2*numpy.pi*Qx) + s_dphi_y = numpy.sin(2*numpy.pi*Qy) + c_dphi_y = numpy.cos(2*numpy.pi*Qy) M00 = c_dphi_x + alpha_x * s_dphi_x M01 = beta_x * s_dphi_x From bf759f99ed210196ad576a6dddc1b4f66830292d Mon Sep 17 00:00:00 2001 From: Lee Carver Date: Sun, 27 Aug 2023 16:29:53 +0200 Subject: [PATCH 19/19] Add test. Modified help of simple_ring. Added broadcasting and optional TimeLag for cavity settings --- pyat/at/physics/fastring.py | 59 ++++++++++++++++++++++--------------- pyat/test/test_physics.py | 10 ++++++- 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/pyat/at/physics/fastring.py b/pyat/at/physics/fastring.py index d867039a4..72859fe16 100644 --- a/pyat/at/physics/fastring.py +++ b/pyat/at/physics/fastring.py @@ -5,7 +5,7 @@ import numpy from typing import Tuple, Optional from at.lattice import RFCavity, Element, Marker, Lattice, get_cells, checkname -from at.lattice import get_elements, M66, SimpleQuantDiff +from at.lattice import get_elements, M66, SimpleQuantDiff, AtError from at.physics import gen_m66_elem, gen_detuning_elem, gen_quantdiff_elem from at.constants import clight, e_mass import copy @@ -113,7 +113,7 @@ def simple_ring(energy: float, circumference: float, harmonic_number: int, emit_y: Optional[float]=0.0, sigma_dp: Optional[float]=0.0, tau_x: Optional[float]=0.0, tau_y: Optional[float]=0.0, tau_z: Optional[float]=0.0, U0: Optional[float]=0.0, - SetTimeLag: Optional[bool]=False + TimeLag: Optional[bool]=False ): """Generates a "simple ring" based on a given dictionary of global parameters @@ -129,21 +129,38 @@ def simple_ring(energy: float, circumference: float, harmonic_number: int, Positional Arguments: * energy [eV] * circumference [m] - * harmonic_number - can be scalar or 1d array + * harmonic_number - can be scalar or sequence of scalars. The RF + frequency is derived from this and the ring circumference * Qx - horizontal tune * Qy - vertical tune - * Vrf - RF Voltage set point [V] - can be scalar or 1d array + * Vrf - RF Voltage set point [V] - can be scalar or sequence of scalars * alpha (momentum compaction factor) Optional Arguments: - * beta_x, beta_y [m] - * alpha_x, alpha_y - * Qpx, Qpy - linear chromaticities - * A1, A2, A3 - amplitude detuning coefficients - * emit_x, emit_y, sigma_dp - equilibrium values [m.rad, m.rad, -] - * tau_x, tau_y, tau_z - radiation damping times [turns] - * U0 - energy loss [eV] (positive number) - * SetTimeLag - Boolean to determine if TimeLag of cavity should be set + * beta_x: horizontal beta function [m], Default=1 + * beta_y: vertical beta function [m], Default=1 + * alpha_x: horizontal alpha function, Default=0 + * alpha_y: vertical alpha function, Default=0 + * Qpx: horizontal linear chromaticity, Default=0 + * Qpy: vertical linear chromaticity, Default=0 + * A1: horizontal amplitude detuning coefficient, Default=0 + * A2: cross term for amplitude detuning coefficient, Default=0 + * A3: vertical amplitude detuning coefficient, Default=0 + * emit_x: horizontal equilibrium emittance [m.rad], Default=0 + ignored if emit_x=0 + * emit_y: vertical equilibrium emittance [m.rad], Default=0 + ignored if emit_y=0 + * sigma_dp: equilibrium momentum spread, Default=0 + ignored if sigma_dp=0 + * tau_x: horizontal radiation damping time [turns], Default=0 + ignored if tau_x=0 + * tau_y: vertical radiation damping time [turns], Default=0 + ignored if tau_y=0 + * tau_z: longitudinal radiation damping time [turns], Default=0 + ignored if tau_z=0 + * U0: - energy loss [eV] (positive number), Default=0 + * TimeLag: Set the timelag of the cavities, Default=0. Can be scalar + or sequence of scalars (as with harmonic_number and Vrf). If the given emit_x,emit_y or sigma_dp is 0, then no equlibrium emittance is applied in this plane. @@ -160,26 +177,22 @@ def simple_ring(energy: float, circumference: float, harmonic_number: int, harmonic_number = numpy.atleast_1d(harmonic_number) Vrf = numpy.atleast_1d(Vrf) + try: + TimeLag = numpy.broadcast_to(TimeLag, Vrf.shape) + except ValueError: + raise AtError('TimeLag needs to be broadcastable to Vrf (same shape)') - assert len(harmonic_number) == len(Vrf), ( - "harmonic_number input must match length of Vrf input" - ) + if (len(harmonic_number) != len(Vrf)): + raise AtError('harmonic_number input must match length of Vrf input') # compute rf frequency frf = harmonic_number * clight / circumference all_cavities = [] for icav in numpy.arange(len(harmonic_number)): - if icav == 0 and SetTimeLag: - # compute the synchronous phase and the TimeLag - phi_s = numpy.arcsin(U0/Vrf) - TimeLag = clight * phi_s / (2 * numpy.pi * frf) - else: - TimeLag = 0 - # generate rf cavity element rfcav = RFCavity('RFC', 0, Vrf[icav], frf[icav], harmonic_number[icav], - energy, TimeLag=TimeLag) + energy, TimeLag=TimeLag[icav]) all_cavities.append(rfcav) # Now we will use the optics parameters to compute the uncoupled M66 matrix diff --git a/pyat/test/test_physics.py b/pyat/test/test_physics.py index 27059f353..28d66c858 100644 --- a/pyat/test/test_physics.py +++ b/pyat/test/test_physics.py @@ -1,6 +1,7 @@ import at import numpy from numpy.testing import assert_allclose as assert_close +from numpy.testing import assert_equal import pytest from at import AtWarning, physics from at import lattice_track @@ -319,7 +320,14 @@ def test_quantdiff(hmba_lattice): 0.00000000e+00, 3.71123417e-14, 5.61789810e-17]], rtol=1e-5, atol=1e-20) - +def test_simple_ring(): + ring = physics.simple_ring(6e9, 844, 992, 0.1, 0.2, 6e6, 8.5e-5) + assert_equal(len(ring), 4) + assert_equal(ring[-1].PassMethod, 'SimpleQuantDiffPass') + ring.disable_6d() + assert_equal(ring[-1].PassMethod, 'IdentityPass') + assert_close(ring.get_tune(), [0.1,0.2], atol=1e-10) + @pytest.mark.parametrize('refpts', ([121], [0, 40, 121])) def test_ohmi_envelope(hmba_lattice, refpts): hmba_lattice = hmba_lattice.radiation_on(quadrupole_pass=None, copy=True)