# Ray Tracing for Tilted Flat Mirrors

This notebook goes through the formulation of the ray-tracing equations used at the LCLS Skywalker project to automate alignment of the *hard x-ray offset mirrors* (HOMS) to points on two imagers downstream of the mirrors. Below is a schematic of the mirror systems in the *front end enclosure* (FEE), including the HOMS as well as the *soft x-ray offset mirrors* (SOMS). 



Of these mirrors only M1H and M2H will be used in the derivation but in principle should be applicable to any flat two mirror system including the SOMS. In addition to the mirrors, equations for the beam position at various imagers will be derived as well and are located right before M1H, between M1H and M2H and two after M2H. The coordinate system used in this notebook will be the same as shown in the diagram, with the $z$-axis going along the beamline, the $x$-axis running in the horizontal direction parallel to the ground and the $y$ axis running vertically off the ground. For all the calulations made, the small angle approximation ($tan(x) \approx x$) was used as the range of the mirrors is only several microradians from the nominal position of 0.0014 radians. One final assumption made is that the imager normals run parallel to $z$-axis

To the first order, the beam, mirrors, and imagers can be represented as lines in space where the point of reflection/contact would just be the intersection point of these lines. Knowing this, one can go through the beamline, component by component and calculate the expected $x$ position, $z$ position and angle of the beam at the point of contact or any point in between. To actually do the derivation, the python package [sympy](http://www.sympy.org/en/index.html) was used. It is a package to perform symbolic mathematics within the python langauge.

We begin by first importing sympy and enabling nice printing.


```python
import sympy as sp
sp.init_printing() 
```

Start by initializing the symbols pertaining to the beamline components.


```python
dp1hx, dp1hz      = sp.symbols("D^x_P1H D^z_P1H")      # X and Z position at the center of P1H
dm1hx, dm1hz      = sp.symbols("D^x_M1H D^z_M1H")      # X and Z position at the center of M1H
dp2hx, dp2hz      = sp.symbols("D^x_P2H D^z_P2H")      # X and Z position at the center of P2H
dm2hx, dm2hz      = sp.symbols("D^x_M2H D^z_M2H")      # X and Z position at the center of M2H
dp3hx, dp3hz      = sp.symbols("D^x_P3H D^z_P3H")      # X and Z position at the center of P3H
ddg3x, ddg3z      = sp.symbols("D^x_DG3 D^z_DG3")      # X and Z position at the center of DG3
a1, a2            = sp.symbols("alpha_M1H alpha_M2H")  # M1H and M2H angle wrt z-axis
```

And also the beam at all the various components. Most of the symbolic definitions in this next section aren't necessary because they will end up just being expressions using the variables above. They are defined only to make it explicit to the reader what these expressions will represent.


```python
x0, xp0           = sp.symbols("x_0 x^'_0")            # X and angle of the beam at the source
xpm1h, xpm2h      = sp.symbols("x^'_M1H x^'_M2H")      # Reflection angle after M1H and M2H
xp1h, zp1h, xpp1h = sp.symbols("x_P1H z_P2H x^'_P1H")  # X, Z and angle of the beam at P1H
xm1h, zm1h, xpm1h = sp.symbols("x_M1H z_M1H x^'_M1H")  # X, Z and angle of the beam at M1H
xp2h, zp2h, xpp2h = sp.symbols("x_P2H z_P2H x^'_P2H")  # X, Z and angle of the beam at P2H
xm2h, zm2h, xpm2h = sp.symbols("x_M2H z_M2H x^'_M2H")  # X, Z and angle of the beam at M2H
xp3h, zp3h, xpp3h = sp.symbols("x_P3H z_P3H x^'_P3H")  # X, Z and angle of the beam at P3H
xdg3, zdg3, xpdg3 = sp.symbols("x_DG3 z_DG3 x^'_DG3")  # X, Z and angle of the beam at DG3
```

**NOTE:** Sympy arranges symbols differently depending on the variable names and can result in different forms of the same equation.

## P1H

This imager isn't necessarily part of the alignment but is calculated anyway to show the equations representing the beam before hitting M1H.


```python
# Standard line equation
xp1h = x0 + dp1hx * xp0

# Position in z is just the position of the imager
zp1h = dp1hz

# Angle is the same as the initial angle of the beam
xpp1h = xp0
```

And then from here we can print the equations we get:


```python
# X Position of the beam at P1H
sp.relational.Eq(sp.symbols("x_P1H"), xp1h)
```


```python
# Z Position of the beam at P1H
sp.relational.Eq(sp.symbols("z_P1H"), zp1h)
```


```python
# Angle of the beam at P1H
sp.relational.Eq(sp.symbols("x^'_P1H"), xpp1h)
```

## M1H

To solve for M1H, we can characterize the surface of the mirror as being a line with a slope $\alpha_{M1H}$, and then find the intersection between the beam and this line. Starting with point slope form, move the terms to one side and then solve using sympy.


```python
# Declare generic x and z variables to solve for
x, z = sp.symbols("x z")

# Using point slope eq for a line to get line of M1H (point is dm1hz, dm1hx)
m1h_ln = a1 * (z - dm1hz) + dm1hx - x

# Solve for x and grab the only element in the returned solution list
m1h_ln_x = sp.solve(m1h_ln, x)[0]

# Use a standard line equation to characterize the beam
beam_ln = x0 + z * xp0

# Setting them equal to each other and move to one side then solve for z
zm1h = sp.solve(m1h_ln_x - beam_ln, z)[0]

# Plugging this z into the eq for the beam line to get x point of intersection then simplifying
xm1h = sp.simplify(x0 + zm1h * xp0)

# Define the new angle of reflection
xpm1h = 2*a1 - xp0
```

And then to view the resulting equations:


```python
# X Position of the beam at the point of reflection on M1H
sp.relational.Eq(sp.symbols("x_M1H"), xm1h)
```


```python
# Z Position of the beam at the point of reflection on M1H
sp.relational.Eq(sp.symbols("z_M1H"), zm1h)
```


```python
# New angle after reflection off M1H
sp.relational.Eq(sp.symbols("x^'_M1H"), xpm1h)
```

## P2H

The same method that was applied to get P1H is re-applied here but using the $x$, $z$ and angle $x'_{M1H}$ of the beam at the point of contact at M1H.


```python
# X position follows the line eq but using x position at m1h and new angle
xp2h_eq = xm1h + xpm1h * (dp2hz-zm1h)

# Simplify
xp2h = sp.simplify(xp2h_eq)

# Z position is just the defined z position of the imager
zp2h = dp2hz

# Angle of the beam isn't changing from M1H
xpp2h = xpm1h
```

And then viewing the results:


```python
# X Position of the beam as it crosses P2H
sp.relational.Eq(sp.symbols("x_P2H"), xp2h)
```


```python
# Z Position of the beam is just the z position of the imager
sp.relational.Eq(sp.symbols("z_P2H"), zp2h)
```


```python
# Angle of the beam is just the reflected angle off M1H
sp.relational.Eq(sp.symbols("x^'_P2H"), xpp2h)
```

## M2H

For the second mirror we re-apply the same method used for the first but this time we need to solve for the line equation of the beam in addition to the mirror. The resulting line equations are then set to be equal to find the point of intersection.


```python
# Declare generic x and z variables to solve for
x, z = sp.symbols("x z")

# Using point slope eq for a line to get line of M2H (point is dm2hz, m2hdx)
m2h_ln = a2 * (z - dm2hz) + dm2hx - x

# Solve for x and grab the only element in the solution
m2h_ln_x = sp.solve(m2h_ln, x)[0]

# Get the line eq for the beam using beam parameters from m1h in point slope
beam_ln =  xpm1h*(z - zm1h) + xm1h - x

# Solve for x and grab the only element in the returned solution list
beam_ln_x = sp.solve(beam_ln, x)[0]

# Set them equal to each other and move to one side then solve for z. Grab only resulting solution
zm2h = sp.solve(m2h_ln_x - beam_ln_x, z)[0]

# Substitute z intersection point into the eq for the beam line to get x point of intersection
m2h_sub = beam_ln.subs(z, zm2h)

# Solve for x and grab the only element
xm2h = sp.solve(m2h_sub, x)[0]

# Calculate the new reflection angle
xpm2h = 2*a2 - xpm1h
```

Let's see the resulting equations:


```python
# X Position of the beam at the point of reflection on M2H
sp.relational.Eq(sp.symbols("x_M2H"), xm2h)
```


```python
# Z Position of the beam at the point of reflection on M2H
sp.relational.Eq(sp.symbols("z_M2H"), zm2h)
```


```python
# New angle after reflection off M1H
sp.relational.Eq(sp.symbols("x^'_M2H"), xpm2h)
```

## P3H

Finding the equations for the beam after M2H is the same as for P2H just using the beam positions at M2H rather than M1H. From there the equation is just a difference of how far away you are in $z$.


```python
# X position follows the line eq but using x position at m2h and new angle
xp3h_eq = xm2h + xpm2h*(dp3hz-zm2h)

# Simplify
xp3h = sp.simplify(xp3h_eq)

# Z position is just the defined z position of the imager
zp3h = dp3hz

# Angle doesn't change
xpp3h = xpm2h
```

Printing the resulting equations we get:


```python
# X Position of the beam at P3H
sp.relational.Eq(sp.symbols("x_P3H"), xp3h)
```


```python
# Z Position of the beam is just the z position of the imager
sp.relational.Eq(sp.symbols("z_P3H"), zp3h)
```


```python
# Angle of the beam is just the reflected angle off M2H
sp.relational.Eq(sp.symbols("x^'_P3H"), xpp3h)
```

## DG3

The method and resulting equations for DG3 are identical to P3H just substituted with the positions of DG3.


```python
# X position follows the line eq but using x position at m2h and new angle
xdg3_eq = xm2h + xpm2h*(ddg3z-zm2h)

# Simplify
xdg3 = sp.simplify(xdg3_eq)

# Z position is just the defined z position of the imager
zdg3 = ddg3z

# Angle doesn't change
xpdg3 = xpm2h
```

And the final resulting equations:


```python
# X Position of the beam at DG3
sp.relational.Eq(sp.symbols("x_DG3"), xdg3)
```


```python
# Z Position of the beam is just the z position of the imager
sp.relational.Eq(sp.symbols("z_DG3"), zdg3)
```


```python
# Angle of the beam is just the reflected angle off M2H
sp.relational.Eq(sp.symbols("x^'_DG3"), xpdg3)
```

# Walking the Beam

Being able to fully characterize the beam at all points on the beamline, we can use them to apply the iterative beam walking algorithm (shown [here](http://laser.physics.sunysb.edu/~simone/mini-project/) and [here](https://virtualetters.blogspot.com/2012/07/walking-beam.html)) by solving the equations above for the mirror angles at the respective imagers. The algorithm calls for aligning the angle of the first mirror to minimize the error on the first imager and then align the angle of the second mirror to minimize the error on the second imager. Repeating this sequence should converge to mirror angles that hit (or minimize the error) the desired two points.

So to do this is relatively straight forward. We solve $x_{P3H}$ for $\alpha_{M1H}$ and then solve $x_{DG3}$ for $\alpha_{M2H}$.


```python
# Declare generic x positions representing the positions we want to hit on the imagers
# These are not to be confused with the equations for xp3h and xdg3 we got earlier
p3hx, dg3x = sp.symbols("x_P3H x_DG3")

# Move everything to one side and solve p3h x position for alpha 1
p3h_alpha = sp.solve(xp3h - p3hx, a1)[0]

# Move everything to one side and solve dg3 x position for alpha 2
dg3_alpha = sp.solve(xdg3 - dg3x, a2)[0]
```

Printing the resulting equations we get:


```python
sp.relational.Eq(sp.symbols("alpha^P3H_M1H"), p3h_alpha)
```


```python
sp.relational.Eq(sp.symbols("alpha^DG3_M2H"), dg3_alpha)
```

# Solving Analytically

Using the equations obtained for alpha in the previous section, we can take them one step furthur and analytically solve for the angles that hit both targets. To do this, just substitute one equation into the other, solve for that alpha and then use the result to solve for the other.


```python
# Substitute the second equation into alpha_2 in the first
alpha_1_eq = p3h_alpha.subs(a2, dg3_alpha)

# Move alpha_1 over to the right and re-solve for alpha_1 with the substitutions
alpha_1 = sp.solve(alpha_1_eq - a1, a1)[0]
```

And then printing the new equation:


```python
sp.relational.Eq(sp.symbols("alpha_M1H"), alpha_1)
```

Then using the value for $\alpha_{M1H}$ we get there, just plug it into the same equation we had for $\alpha_{M2H}$.


```python
sp.relational.Eq(sp.symbols("alpha_M2H"), dg3_alpha)
```

763b6240577926bbc6e3e3d3d2475e797a12c340
ipynb
Jupyter Notebook
notebooks/1. Ray Tracing for Titled Flat Mirrors.ipynb
ZLLentz/pswalker
9642489e6add37906998f09cc88290bc95686ac9
BSD-3-Clause-LBNL
notebooks/1. Ray Tracing for Titled Flat Mirrors.ipynb
ZLLentz/pswalker
9642489e6add37906998f09cc88290bc95686ac9
BSD-3-Clause-LBNL
2018-04-20T21:26:02.000Z
2021-01-11T19:31:08.000Z
notebooks/1. Ray Tracing for Titled Flat Mirrors.ipynb
ZLLentz/pswalker
9642489e6add37906998f09cc88290bc95686ac9
BSD-3-Clause-LBNL
2018-06-26T00:09:27.000Z
2018-11-29T19:40:04.000Z
Qwen/Qwen-72B
1. YES
2. YES