Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rotation of dipoles #781

Open
TeresiaOlsson opened this issue Jun 17, 2024 · 18 comments
Open

Rotation of dipoles #781

TeresiaOlsson opened this issue Jun 17, 2024 · 18 comments
Labels
Python For python AT code

Comments

@TeresiaOlsson
Copy link

Hi,

We have done measurements of the orbit distortion and dispersion when realigning a dipole magnet and I wanted to compare to what our model in pyAT predicts. I used the rotate_elem function (including the changes that have been done since the last release), but the results made no sense. Then I discovered this very nice paper explaining that the reason for it might be because applying rotations to a dipole also changes the reference system.

https://accelconf.web.cern.ch/ipac2017/papers/wepik061.pdf

Is there already a atsettiltdipole function in pyAT as in matlab AT or it has to be implemented? If I need to implement it, is there any reason to not add it directly to the rotate_elem function instead of having a separate function for dipoles?

@TeresiaOlsson TeresiaOlsson added the Python For python AT code label Jun 17, 2024
@elafmusa
Copy link

Dear Teresia,
If I understood your comment correctly, using at.rotate_elem(lattice[i], tilt=a, pitch=b, yaw=c, relative=relative) for dipoles does not lead to proper physics results. is it correct ?
I have been using this function to insert a tilt on dipoles (with pitch and yaw set to 0) together with tilt errors on other magnets, and i have noticed that there is almost no any impact of the dipole tilt on orbit distortion.

Best Regards,
Elaf

@TeresiaOlsson
Copy link
Author

Hi Elaf,

Yes, that is what I think. Like you, I also got a tiny impact in the simulation but in our measurement where we went into the tunnel and physically realigned the magnet we could see a bigger impact. We did a quick calculation of how we expected the magnetic fields to change due to the realignment and that seemed to agree well with what we measured.

If I understood the paper from @simoneliuzzo correctly, the reason is because the dipoles also are setting the reference orbit. So when we change the rotation using the method in rotate_elem we are also changing the reference. So for the dipoles we can only get correct results if we change PolynomA and PolynomB. And that functionality has so far only been implemented in the matlab version of AT as far as I have understood. But maybe it exists in pySC? Do you know?

I think Elegant does something similar. In Elegant the element CSBEND is normally used for the dipoles and it has both a parameter TILT and ETILT. For all other magnets you need to set TILT but for the dipoles you need to set ETILT. The Elegant manual says "When adding errors, care should be taken to choose the right parameters. The FSE and ETILT parameters are used for assigning errors to the strength and alignment relative to the ideal values given by ANGLE and TILT. One can also assign errors to ANGLE and TILT, but this has a different meaning: in this case, one is assigning errors to the survey itself. The reference beam path changes, so there is no orbit/trajectory error. The most common thing is to assign errors to FSE and ETILT. "

So I think it's the equivalent of setting the ETILT parameter in Elegant that we are currently missing in pyAT. I have on my to-do list to add it and benchmark it against Elegant, but so far I have not had time to do it :/

@oscarxblanco
Copy link
Contributor

Dear @TeresiaOlsson ,

I have checked the function rotate_elem in pyat and it does not change the reference system.

rotate_elem adds R1,R2 rotations and T1,T2 translations to the element in order to change the particle coordinates when crossing the magnet. T1,R1 are applied at the entrance and R2,T2 at the exit of the element during tracking.

I suspect the problem comes from the calculation of R1,R2,T1,T2, which depend on the bending magnet length, angle and point where the rotation is applied. This is still not solved in pyat, but, for short bendings, small angles and rotations around the longitudinal midpoint of the bending magnet the result should still be ok.

When the bending magnet is long, the angle is not small or you need to rotate around the entrance face instead of the center there is a Matlab routine created by Thorsten Hellert that I have checked in several cases and it has given me good results. The function is called SCgetTransformation and you can find it here https://github.com/ThorstenHellert/SC.

Here is a short example of how to use it

dx = 1e-3;
dy = 0;
dz = 0; % opposite sign to the sixth particle coordinate
atilt = 0.0;
apitch = 0.0;
ayaw = 0.0;
ax =  apitch;
ay =  ayaw;
az =  atilt;
magTheta = 2*pi/4;
magLength = 0.8;
%[T1,T2,R1,R2] = SCgetTransformation(dx,dy,dz,ax,ay,az,magTheta,magLength,'refPoint','entrance');
[T1,T2,R1,R2] = SCgetTransformation(dx,dy,dz,ax,ay,az,magTheta,magLength,'refPoint','center');

Then you will need to put those T1,T2,R1,R2 fields in your element.

@TeresiaOlsson
Copy link
Author

Okay, thank you. I have long bending magnets (the main dipoles) so that could explain it.

Have you also at some point tested the atsettiltdipole function in Matlab AT? I'm wondering now if the two ways of implementing it will give the same result. If so, it feels like the best option for the implementation in pyAT is to stick with changing the T1, T2, R1, R2 but make it work for long dipoles too.

It feels a bit dangerous to me to have a separate implementation for dipoles which changes other fields like atsettiltdipole does because then not all elements are treated the same. That's for example one issue with the Elegant implementation because it's super easy to set the TILT parameter for the dipoles too and then have no idea that your results will be wrong.

@simoneliuzzo
Copy link
Contributor

Dear @TeresiaOlsson @elafmusa and @oscarxblanco ,

I forgot I wrote that paper!
Indeed I did get into this issue a long time ago and since results had to be produced sooner than immediately, I ended up writing [atsettiltdipole](https://github.com/atcollab/at/blob/master/atmat/pubtools/LatticeTuningFunctions/errors/atsettiltdipole.m), definitely not the best solution.
I try for some time to get it into matlab AT (it is in a long time stale branch).
I think today SC is the new reference and if there is already a solution there for this issue, than that would be great!

@lmalina
Copy link

lmalina commented Jul 18, 2024

Dear @TeresiaOlsson,

pySC implements the same physics as SC (mentioned by @oscarxblanco ):

https://github.com/lmalina/pySC/blob/master/pySC/utils/sc_tools.py#L115

The function update_transformation already takes an element as an input and returns the element with updated T1, T2 , R1 and R2.

Cheers,
Lukas

@TeresiaOlsson
Copy link
Author

This is great! I will test that with our measurement results.

@simoneliuzzo
Copy link
Contributor

Dear @TeresiaOlsson, @oscarxblanco, @lmalina and @elafmusa,

it is not clear to me how T1, T2, R1, R2 can actually do anything for a dipole. To my understanding for AT a dipole does "nothing". It is a drift space. Mainly the bending angle is used to compute the survey/layout and radiation (not sure) that's all. atsettilt does set R1/R2 but with zero effect, correctly, the modified parameter is the reference survey, so no closed orbit distortion visible. If you ask atgeometry3 there you should notice (hopefully, but not sure) a non closed ring.
T1 and T2 in a dipole are also expected to do nothing, field is constant in a dipole (ignoring fringe fields).
@oscarxblanco and @lmalina, SC and pySC functions deal with the specific case of a dipole? you can get orbit and dispersion distortion from a rotated dipole by tuning T1/T2, R1 and R2? It seems rather unexpected to me.

@oscarxblanco
Copy link
Contributor

Dear Simone,
the element atsbend has a parameter input for a quadrupole component

>> help atsbend
 atsbend Creates a sector bending magnet element with class 'Bend'
...
   4. K				Focusing strength, defaults to 0
...

therefore the rotations and transformations have an impact on the optics. Also, a tilt angle would produce some vertical dispersion. When the quadrupole component is zero or when tilt is very small the effect is also very small, or as you say 'nothing'.

Apart from that, the atsettilt and atsettiltdipole have the same effect on the optics because the are written for small angles. The first one defines R1,R2, while the second defines RotAboutS which changes the PolinomA and PolinomB.

You could also compare it with R1,R2,T1,T2 for a small angle applied on the center of the bending+quad but calculated with SCgetTransformation. Here below is an example of the dispersion calculated with atlinopt. SC gives a slightly different result because it is not a small angle approximation.

Dispersion after 1mrad tilt with AT matlab atsettiltdipole=
   0.132626483969494   0.000006304142726   0.002774017880665   0.000453756253992
Dispersion after 1mrad tilt with AT matlab atsettilt=
   0.132626483969494   0.000006304142726   0.002774017880665   0.000453756253992
Dispersion after 1mrad tilt with SCgetTransformation =
   0.132628753046846   0.000006541400071   0.002721455119124   0.000442790069038

Here is the code I used in case any would like to check :

%% 2024jul18 oblanco at ALBA, test dipole tilt in AT matlab
clear;
%%
setpathalba;
%%
global THERING;
NEWRING = THERING;

%%
theangle = 1e-3;
theix = 1;
%%
thebendsidx = find(atgetcells(THERING, 'FamName', 'BEND'));
%%
NEWRING{thebendsidx(theix)}
%%
NEWRING2 = atsettiltdipole(NEWRING,thebendsidx(theix),theangle);
%%
[bp0,rp0] = atlinopt6(THERING);
%%
[bp1,rp1] = atlinopt6(NEWRING);
%%
[bp2,rp2] = atlinopt6(NEWRING2);
%%
rp1.Dispersion',rp2.Dispersion'
%%
NEWRING3 = atsettilt(NEWRING,thebendsidx(theix),theangle);
%%
[bp3,rp3] = atlinopt6(NEWRING2);
%%
rp2.Dispersion',rp3.Dispersion'
%%
dx = 0; dy = 0; dz = 0;
ax = 0; ay = 0; az = theangle;
magTheta = NEWRING{thebendsidx(theix)}.BendingAngle;
magLength = NEWRING{thebendsidx(theix)}.Length;
[T1,T2,R1,R2] = SCgetTransformation(dx,dy,dz,ax,ay,az,magTheta,magLength,'refPoint','center');
%%
R1
NEWRING3{thebendsidx(theix)}.R1
%%
NEWRING4 = NEWRING;
NEWRING4{thebendsidx(theix)}.T1 = T1;
NEWRING4{thebendsidx(theix)}.R1 = R1;
NEWRING4{thebendsidx(theix)}.T2 = T2;
NEWRING4{thebendsidx(theix)}.R2 = R2;
%%
[bp4,rp4] = atlinopt6(NEWRING4);
%% print the dispersion
rp1.Dispersion', rp2.Dispersion', rp3.Dispersion',rp4.Dispersion'

atgeometry3 reads R1,R1 and ignores T1,T2. For this example atsettiltdipole does not change the survey, while, atsettilt and SCgetTransformation do change the survey leaving it not closed. Note that T1 and T2 have no effect.
image

Here is the code :

%% 2024jul18 oblanco at ALBA, test atgeometry3 in AT matlab
geo0 = atgeometry3(THERING);
geo1 = atgeometry3(NEWRING);
geo2 = atgeometry3(NEWRING2);
geo3 = atgeometry3(NEWRING3);
geo4 = atgeometry3(NEWRING4);
%%
figure;hold on;
%plot(cat(1,geo0.z),'DisplayName','THERING');
plot(cat(1,geo1.z),'DisplayName','RING','LineWidth',4);
plot(cat(1,geo2.z),'DisplayName','RING atsettiltdipole','LineWidth',2);
plot(cat(1,geo3.z),'DisplayName','RING atsettilt','LineWidth',4);
plot(cat(1,geo4.z),'DisplayName','RING SCgetTransformation','LineWidth',2);
xlabel('index');
ylabel('z [m]');
legend;
grid;

@simoneliuzzo
Copy link
Contributor

simoneliuzzo commented Jul 19, 2024

Dear @oscarxblanco,

I do not see why the K of a dipole is relevant. hopefully, if there is a K the alignment errors set wit T1T2 R1R2 will give you misaligned quadrupole effects. However here I think we want to see the errors introduced by dipoles only. So I assume dipole K=0.

It is strange that the dispersion is not changing with atsettiltdipole. I took your test and modified it as below (clean path and new at clone for the purpose):

close all
clear

restoredefaultpath
addpath(genpath('./at/atmat'))
addpath(genpath('./at/atintegrators'))
% atmexall

load('/Users/liuzzo/beamdyn/EBSlattices/AT/S28F_PA.mat')
THERING = atSetRingProperties(LOW_EMIT_RING_INJ);
NEWRING = THERING;
N = length(THERING);
%%
theangle = 1e-3;
theix = 2;
%%
thebendsidx = find(atgetcells(THERING, 'Class', 'Bend'));
%%
disp('Initial dipole')
THERING{thebendsidx(theix)}
%%
NEWRING = atsettiltdipole(THERING,thebendsidx(theix),theangle);
disp('atsettiltdipole')
NEWRING{thebendsidx(theix)}
%%
NEWRING2 = atsettilt(THERING,thebendsidx(theix),theangle);
disp('atsettilt')
NEWRING2{thebendsidx(theix)}
%%
[bp0,rp0] = atlinopt6(THERING,1:N);
%%
[bp1,rp1] = atlinopt6(NEWRING,1:N);
%%
[bp2,rp2] = atlinopt6(NEWRING2,1:N);
%%
format long
disp('dispersion no tilt')
rp0(1).Dispersion'
disp('dispersion atsettiltdipole')
rp1(1).Dispersion'
disp('dispersion atsettilt')
rp2(1).Dispersion'

%% dispersion
eta0 = arrayfun(@(a)a.Dispersion(1),rp0);
eta1 = arrayfun(@(a)a.Dispersion(1),rp1);
eta2 = arrayfun(@(a)a.Dispersion(1),rp2);
figure;
plot(eta1-eta0,'DisplayName','atsettiltdipole'); hold on;
plot(eta2-eta0,'DisplayName','atsettilt')
ylabel('hor dispersion deviation');
legend;

hcod0 = arrayfun(@(a)a.ClosedOrbit(1),rp0);
hcod1 = arrayfun(@(a)a.ClosedOrbit(1),rp1);
hcod2 = arrayfun(@(a)a.ClosedOrbit(1),rp2);
figure;
plot(hcod1-hcod0,'DisplayName','atsettiltdipole'); hold on;
plot(hcod2-hcod0,'DisplayName','atsettilt')
ylabel('horizontal closed orbit');
legend;

vcod0 = arrayfun(@(a)a.ClosedOrbit(3),rp0);
vcod1 = arrayfun(@(a)a.ClosedOrbit(3),rp1);
vcod2 = arrayfun(@(a)a.ClosedOrbit(3),rp2);
figure;
plot(vcod1-vcod0,'DisplayName','atsettiltdipole'); hold on;
plot(vcod2-vcod0,'DisplayName','atsettilt')
legend;
ylabel('vertical closed orbit');


%%
figure('Name','no tilt');
atplot(THERING,@plClosedOrbit)
ax = gca;
ax.YLim=[-1e-4 1e-4];
ax.Parent.Children(2).YLim = [-1e-3 1e-3];
figure('Name','atsettiltdipole');
atplot(NEWRING,@plClosedOrbit)
ax = gca;
ax.YLim=[-1e-4 1e-4];
ax.Parent.Children(2).YLim = [-1e-3 1e-3];
figure('Name','atsettilt');
atplot(NEWRING2,@plClosedOrbit)
ax = gca;
ax.YLim=[-1e-4 1e-4];
ax.Parent.Children(2).YLim = [-1e-3 1e-3];

The figures are:

dispersion no tilt
-0.002831998090349 0.000000011992750 -0.000000000000000 -0.000000000000000
dispersion atsettiltdipole
-0.002830864167670 0.000000155329621 0.000281229975126 0.000062113839769
dispersion atsettilt
-0.002832012185536 0.000000012138122 0.000019801344318 -0.000001954703119

deviation of dispersion and cod:
Screenshot 2024-07-19 at 08 27 37

(same, with plClosedOrbit)
Screenshot 2024-07-19 at 08 22 47

I have an issue with the atgeometry3 output (all nan for my lattice...). atsettiltdipole has been explicitly written to avoid changes in survey and to see closed orbit and dispersion modulation instead. It should not drive any change in survey. So your figure is unexpected for me.

@TeresiaOlsson
Copy link
Author

I'm a bit confused now and need to do more thinking but I think for the specific user case I'm interested in, I want what atsettiltdipole is doing and not what SCgetTransformation is doing. This is the user case:

  1. You measure the orbit and vertical dispersion for your existing machine (with whatever misalignments it has).
  2. You go inside the tunnel and physically realign a dipole.
  3. You measure the orbit and vertical dispersion again.
  4. Then you want to compare the change in orbit and vertical dispersion that the model predicts for the realignment you did to the change you measured.

To compare that, I guess I need the reference orbit to remain the same? So I don't want the survey to change?

@oscarxblanco
Copy link
Contributor

Dear @simoneliuzzo, I just saw a typo in my script.
Wrt to dispersion, as you say, I confirm that I get different dispersion with atsettiltdipole and atsettilt.

Dispersion
   0.132710740981170   0.000013268899828  -0.000000000000000                   0
Dispersion atsettiltdipole
   0.132583873848275   0.000007105408571   0.002759006675413   0.000453692275293
Dispersion atsettilt
   0.132710163182893   0.000013370824182   0.000945652628255  -0.000045650207821
Dispersion SCgetTransfomation
   0.132591362966804   0.000007372277885   0.002712896010740   0.000442537246892

My typo was in this line:

...
[bp3,rp3] = atlinopt6(NEWRING2); % I should have written NEWRING3
...

@oscarxblanco
Copy link
Contributor

oscarxblanco commented Jul 19, 2024

I do not see why the K of a dipole is relevant. hopefully, if there is a K the alignment errors set wit T1T2 R1R2 will give you misaligned quadrupole effects. However here I think we want to see the errors introduced by dipoles only. So I assume dipole K=0.

@simoneliuzzo , wrt to K=0, I agree.
But in many lattices that K is not zero. @TeresiaOlsson, do you have bends with K not zero? or sextupoles or higher components ?

The nominal trajectory inside a bend is an arc. If you tilt the magnet (rotation around s) centered on the mid point, you will create offsets on the entry and end faces, i.e. a rotation on a curved element requires R1,R2 and also T1, and T2.

SCgetTransformation calculates R1,R2,T1,T2 to include the effect of the curvature.
atsettilt calculates R1,R2 to small angles approximation and ignores T1,T2. Ignoring T1 and T2 is ok if the magnet is not too long, or curvature is zero.
atsettiltdipole includes an attribute called RotAboutS that modifies the first and second component of Polynoms A and B, therefore, including the kick produced by the offset induced by the tilt, and the change on the gradient induced by the tilt. I believe that the difference I see wrt to SCgetTransfomation is again due to small angles approximations in atsettiltdipole.

@oscarxblanco
Copy link
Contributor

I have an issue with the atgeometry3 output (all nan for my lattice...). atsettiltdipole has been explicitly written to avoid changes in survey and to see closed orbit and dispersion modulation instead. It should not drive any change in survey. So your figure is unexpected for me.

Dear @simoneliuzzo ,
I think it is just a confusion due to default colors. Violet over yellow looks like red. Here is the same plot where I change the stile of the violet line.

As you said atsettiltdipoles has no effect on the output of atgeometry3.

image

...
plot(cat(1,geo4.z),'DisplayName','RING SCgetTransformation','LineWidth',2,'LineStyle','-.','Color','k');
...

@TeresiaOlsson
Copy link
Author

@oscarxblanco In my case I have no higher components in the dipoles, but for combined function magnets I can see that this is an issue.

So for your example it looks like you get the same effect on the dispersion with SCgetTransfomation as with atsettiltdipole. That's very nice. Did you also compare the orbit distortion?

I could have tested it myself, but unfortunately I currently don't have access to our matlab license server because of work our IT department is doing to restore our networks after the cyberattack last year. So temporarily I can't use matlab...

@oscarxblanco
Copy link
Contributor

I'm a bit confused now and need to do more thinking but I think for the specific user case I'm interested in, I want what atsettiltdipole is doing and not what SCgetTransformation is doing. This is the user case:

1. You measure the orbit and vertical dispersion for your existing machine (with whatever misalignments it has).

2. You go inside the tunnel and physically realign a dipole.

3. You measure the orbit and vertical dispersion again.

4. Then you want to compare the change in orbit and vertical dispersion that the model predicts for the realignment you did to the change you measured.

To compare that, I guess I need the reference orbit to remain the same? So I don't want the survey to change?

Dear @TeresiaOlsson , here is a recipe based on the ALBA tests I had to do recently. Please, have a look a check what applies to your case.

In order to compare the effect of the magnet offset you will need:

  • the same golden orbit before and after the magnet intervention. Or at least knowledge of the changes in between (BBA or BPM changes for example).
  • a machine as close as possible to your model. For that you will need to do an orbit correction, and an optics correction before the magnet realignment.
  • remove part of the orbit correction in the machine and in the model. You will be able to verify that your model works as expected if the measured orbit matches the orbit you simulate, and estimate the precision of it.

The reason is that you want the displacement to be with respect to some stable configuration very close to what you can simulate.

Now you intervene the machine physically realigning some elements...

  • The induced orbit would have at least similar phase and amplitude with respect to what you obtain on the model.
  • You could correct the orbit again in the machine. The correctors change (after intervention minus before the intervention) should match the correction calculated in your model for that displacement.
  • You correct the orbit and measure dispersion in the machine. Compare with the change in dispersion in the model before and after the displacement.

P.S.
The survey is irrelevant for beam dynamics, but, it might be useful if you need to interact with alignment.
If you need to keep the same survey from atgeometry3, use atsettiltdipole.
If you need the effect on beam dynamics and see the changes on the survey, use SCgetTransformation.
If the magnet is long or the angle is large, results are better with SCgetTransformation.
If the magnet has zero curvature, use atsettiltdipole, or SCgetTransformation, or atsettilt. They all do the same.

@oscarxblanco
Copy link
Contributor

@oscarxblanco In my case I have no higher components in the dipoles, but for combined function magnets I can see that this is an issue.

So for your example it looks like you get the same effect on the dispersion with SCgetTransfomation as with atsettiltdipole. That's very nice. Did you also compare the orbit distortion?

I could have tested it myself, but unfortunately I currently don't have access to our matlab license server because of work our IT department is doing to restore our networks after the cyberattack last year. So temporarily I can't use matlab...

Dear @TeresiaOlsson,
here is the orbit :
image
And here is the code I added

%%
NN = length(THERING);
o1 = findorbit6(NEWRING,1:NN);
o2 = findorbit6(NEWRING2,1:NN);
o3 = findorbit6(NEWRING3,1:NN);
o4 = findorbit6(NEWRING4,1:NN);
figure;yscale=1e3;
subplot(2,1,1);hold on;
plot(yscale*o1(1,:),'LineWidth',8,'DisplayName','RING');
plot(yscale*o2(1,:),'DisplayName','RING atsettiltdipole','LineWidth',6);
plot(yscale*o3(1,:),'DisplayName','RING atsettilt','LineWidth',4);
plot(yscale*o4(1,:),'DisplayName','RING SCgetTransformation','LineWidth',2,'LineStyle','-.','Color','k');
legend;grid;
xlabel('index');
ylabel('x [mm]');
subplot(2,1,2);hold on;
plot(yscale*o1(3,:),'LineWidth',8,'DisplayName','RING');
plot(yscale*o2(3,:),'DisplayName','RING atsettiltdipole','LineWidth',6);
plot(yscale*o3(3,:),'DisplayName','RING atsettilt','LineWidth',4);
plot(yscale*o4(3,:),'DisplayName','RING SCgetTransformation','LineWidth',2,'LineStyle','-.','Color','k');
xlabel('index');
ylabel('y [mm]');
grid;

@TeresiaOlsson
Copy link
Author

Thank you so much @oscarxblanco. That is really great! The tests you did at ALBA is also what we tried for BESSY II. The only difference I think is that we don't have any misalignments in our model yet so we can only look at difference orbits.

I will try this as soon as I get my matlab license back and then see if I can get the same with pyAT and pySC.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Python For python AT code
Projects
None yet
Development

No branches or pull requests

5 participants