```{python}
#| code-fold: true
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
"""
## TODO: write code in own words, and remove the unnecessary parameters
## Define grating geometry
d_grating_spacing = 1.0
slope_grating = 0.5
y_intercept_grating = 2.0
length_grating = 8.0
height_grating = 0.5
## Define line along the bottom of the grating
x_coordinates_along_grating = np.array([-length_grating/2, length_grating/2]) ## Center of grating is 0, endpoints are ends of the bottom of the grating
y_coordinates_along_grating_bottom = slope_grating * x_coordinates_along_grating + y_intercept_grating
"""
## Generated from AI
# ---------- Parameters ----------
d = 1.0
grazing_angle_deg = 15.0
grazing_angle_rad = np.deg2rad(grazing_angle_deg)
m = 0.5
b = 2.0
grating_length = 8.0
grating_height = 0.5
ray_length = 4.0
# ---------- Geometry ----------
x_vals = np.array([-grating_length/2, grating_length/2])
y_vals = m * x_vals + b
p_left, p_right = np.array([x_vals[0], y_vals[0]]), np.array([x_vals[1], y_vals[1]])
t_vec = p_right - p_left
t_hat = t_vec / np.linalg.norm(t_vec)
n_hat = np.array([t_hat[1], -t_hat[0]])
if not (n_hat[0] > 0 and n_hat[1] < 0):
n_hat = -n_hat
n_hat /= np.linalg.norm(n_hat)
p_left_top, p_right_top = p_left - grating_height * n_hat, p_right - grating_height * n_hat
grating_poly = np.array([p_left, p_right, p_right_top, p_left_top])
midpoint = 0.5 * (p_left + p_right)
p1, p2 = midpoint - 0.5 * d * t_hat, midpoint + 0.5 * d * t_hat
inc_dir = np.cos(grazing_angle_rad) * t_hat - np.sin(grazing_angle_rad) * n_hat
inc_dir /= np.linalg.norm(inc_dir)
if (inc_dir[0] < 0) or (inc_dir[1] < 0):
inc_dir = -inc_dir
dif_dir = np.array([1.0, 0.0])
# ---------- Arcs ----------
def angle_between(a, b):
diff = (b - a + np.pi) % (2*np.pi) - np.pi
return diff
def draw_arc_cw(ax, center, ang_from, ang_to, radius=0.9, npts=120, **kwargs):
delta = angle_between(ang_from, ang_to)
if delta >= 0: # force clockwise
delta = delta - 2*np.pi
thetas = np.linspace(ang_from, ang_from + delta, npts)
xs = center[0] + radius * np.cos(thetas)
ys = center[1] + radius * np.sin(thetas)
ax.plot(xs, ys, **kwargs)
def draw_arc_short(ax, center, ang_from, ang_to, radius=0.6, npts=80, **kwargs):
delta = angle_between(ang_from, ang_to)
thetas = np.linspace(ang_from, ang_from + delta, npts)
xs = center[0] + radius * np.cos(thetas)
ys = center[1] + radius * np.sin(thetas)
ax.plot(xs, ys, **kwargs)
ang_n = np.arctan2(n_hat[1], n_hat[0])
ang_inc_in = np.arctan2((-inc_dir)[1], (-inc_dir)[0]) # incoming direction
ang_dif = np.arctan2(dif_dir[1], dif_dir[0])
def right_triangle_third_vertex(p1, p2, ray_dir):
ray_dir /= np.linalg.norm(ray_dir)
perp = np.array([-ray_dir[1], ray_dir[0]])
A = np.column_stack([ray_dir, -perp])
rhs = (p1 - p2)
s, t = np.linalg.solve(A, rhs)
Q = p2 + s * ray_dir
return Q
q_inc = right_triangle_third_vertex(p1, p2, inc_dir)
# --- FIXED --- Corrected function for path difference triangle vertex
def calculate_path_difference_vertex(p_start, p_end, ray_dir):
ray_dir_norm = ray_dir / np.linalg.norm(ray_dir)
p_grating_line_vec = p_end - p_start
# The path difference leg is perpendicular to the ray direction.
# We find the projection of the grating segment onto the ray direction to find
# the vector that forms the adjacent side of the right triangle.
adj_vec = np.dot(p_grating_line_vec, ray_dir_norm) * ray_dir_norm
# The third vertex is found by adding the adjacent vector to the start point
# of the grating segment.
return p_start + adj_vec
q_dif = calculate_path_difference_vertex(p1, p2, dif_dir)
# --- END FIX ---
# ---------- Zooming (tighter) ----------
inc_starts = [p1 - ray_length * inc_dir, p2 - ray_length * inc_dir]
dif_ends = [p1 + ray_length * dif_dir, p2 + ray_length * dif_dir]
all_x = np.hstack([grating_poly[:,0], np.array(inc_starts)[:,0], np.array(dif_ends)[:,0], [q_inc[0], q_dif[0]]])
all_y = np.hstack([grating_poly[:,1], np.array(inc_starts)[:,1], np.array(dif_ends)[:,1], [q_inc[1], q_dif[1]]])
pad_x = 0.3 * np.max([0.5, (all_x.max() - all_x.min())])
pad_y = 0.3 * np.max([0.5, (all_y.max() - all_y.min())])
xlims, ylims = (all_x.min() - pad_x, all_x.max() + pad_x), (all_y.min() - pad_y, all_y.max() + pad_y)
# ---------- Plot ----------
fig, ax = plt.subplots(figsize=(20,20)) # bigger figure
ax.set_aspect('equal')
ax.axis('off')
#ax.set_xlim(*xlims)
#ax.set_ylim(*ylims)
## Grating
ax.add_patch(patches.Polygon(grating_poly, closed=True, color=(0.9, 0.9, 0.9, 1)))
ax.plot(x_vals, y_vals, '--', color=(0.5, 0.5, 0.5, 1), lw=3)
ax.text(0.5*(p1[0] + p2[0]), 0.75*(p1[1] + p2[1]), "Grating", color=(0, 0, 0, 1), fontsize=14, ha='center', va='center')
## Groove distance
ax.plot([p1[0], p2[0]], [p1[1], p2[1]], ls='solid', color=(0, 0, 0, 1), lw=3)
ax.text(0.5*(p1[0] + p2[0]), 0.55*(p1[1] + p2[1]), "d", color=(0, 0, 0, 1), fontsize=14, ha='center', va='center')
## Grating normal
norm_len = 4.0
for p in (p1, p2):
ax.plot([p[0], p[0] + norm_len * n_hat[0]],
[p[1], p[1] + norm_len * n_hat[1]],
ls='--', color='gray', lw=1.2)
#ax.arrow(p1[0], p1[1], 0.9*n_hat[0], 0.9*n_hat[1], head_width=0.09, head_length=0.12, fc='k', ec='k', lw=1.2)
ax.text(p1[0] + 2.0*n_hat[0], p1[1] + 2.0*n_hat[1], 'Grating normal', fontsize=10, ha='left', va='bottom')
## Light rays
for p in (p1, p2):
start = p - ray_length * inc_dir
ax.arrow(start[0], start[1], p[0]-start[0], p[1]-start[1], head_width=0.16, head_length=0.28, fc='b', ec=(0, 0, 1, 1), lw=2)
ax.arrow(p[0], p[1], ray_length * dif_dir[0], ray_length * dif_dir[1], head_width=0.16, head_length=0.28, fc='r', ec='r', lw=2)
## Label angles
draw_arc_cw(ax, p1, ang_n, ang_inc_in, radius=0.95, color='b', lw=2) ## α arc
mid_alpha = (ang_n + angle_between(ang_n, ang_inc_in)/2.0)
ax.text(p1[0] + 1.05*np.cos(mid_alpha), p1[1] + 1.05*np.sin(mid_alpha), r"$\alpha$", color='b', fontsize=14, ha='center', va='center')
draw_arc_short(ax, p1, ang_n, ang_dif, radius=0.6, color='r', lw=2) ## β arc
mid_beta = ang_n + angle_between(ang_n, ang_dif)/2.0
ax.text(p1[0] + 0.75*np.cos(mid_beta), p1[1] + 0.75*np.sin(mid_beta), r"$\beta$", color='r', fontsize=14, ha='center', va='center')
plt.show()
```