codexlaue-polychromatic-spot-pattern.py
laue-polychromatic-spot-pattern.py
"""Original transmission Laue positions; Python 3.14, NumPy 2.3.5, Matplotlib 3.10.7.
One point scatterer per primitive cubic cell, a=0.400 nm. Incident direction
is lab +z; G=(2*pi/a) R(h,k,l). Elasticity gives lambda=-2*a*v_z/|v|²,
v=R(h,k,l), and s_out=z_hat+(lambda/a)*v. Only forward rays landing on
a square detector at z=50 mm are retained. Marker area is NOT intensity.
Run from _media; output is this script's basename.png in the working directory.
"""
from pathlib import Path
import itertools
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import Normalize
A = 0.400
BAND = (0.080, 0.160)
DISTANCE = 50.0
HALF_WIDTH = 75.0
def rotation_y(degrees):
t = np.deg2rad(degrees)
return np.array([[np.cos(t), 0, np.sin(t)], [0, 1, 0],
[-np.sin(t), 0, np.cos(t)]])
def reflections(degrees=0.0):
"""Return (indices, wavelength_nm, outgoing_unit_direction, position_mm)."""
rotation = rotation_y(degrees)
# |G| <= 2|k| implies |(h,k,l)| <= 2a/lambda_min, in any orientation.
bound = int(np.ceil(2 * A / BAND[0]))
result = []
for indices in itertools.product(range(-bound, bound + 1), repeat=3):
v = rotation @ np.array(indices, dtype=float)
square = float(v @ v)
if square == 0:
continue
wavelength = -2 * A * v[2] / square
if not BAND[0] - 1e-12 <= wavelength <= BAND[1] + 1e-12:
continue
outgoing = np.array([0., 0., 1.]) + wavelength / A * v
assert abs(outgoing @ outgoing - 1) < 1e-12
if outgoing[2] <= 1e-12:
continue
xy = DISTANCE * outgoing[:2] / outgoing[2]
if np.max(np.abs(xy)) <= HALF_WIDTH + 1e-10:
result.append((indices, wavelength, outgoing, xy))
return result
def draw():
plt.rcParams.update({'font.size': 11, 'axes.titlesize': 12})
fig, axes = plt.subplots(1, 2, figsize=(10.6, 5.7), layout='constrained',
facecolor='white')
norm = Normalize(*BAND)
for ax, tilt in zip(axes, (0.0, 7.0)):
rows = reflections(tilt)
xy = np.array([r[3] for r in rows])
wavelengths = np.array([r[1] for r in rows])
# Longer wavelengths first: any exact harmonic overlap remains one spot.
order = np.argsort(wavelengths)[::-1]
scatter = ax.scatter(xy[order, 0], xy[order, 1], c=wavelengths[order],
cmap='viridis', norm=norm, s=32, linewidths=0.4,
edgecolors='#243041')
ax.plot(0, 0, '+', color='#c44e2a', ms=11, mew=1.8)
ax.set(xlim=(-80, 80), ylim=(-80, 80), aspect='equal',
xlabel='Plate X (mm)', ylabel='Plate Y (mm)',
title='[001] along beam' if tilt == 0 else 'Same crystal tilted 7° about lab y')
ax.set_xticks([-75, -50, -25, 0, 25, 50, 75])
ax.set_yticks([-75, -50, -25, 0, 25, 50, 75])
ax.grid(alpha=0.16)
if tilt == 0:
ax.annotate(r'$(2,1,-1)$' + '\nλ = 0.1333 nm', (50, 25), (8, 45),
fontsize=10, arrowprops={'arrowstyle': '->', 'color': '#243041'})
bar = fig.colorbar(scatter, ax=axes, shrink=0.79, pad=0.02)
bar.set_label('Selected wavelength λ (nm)')
fig.suptitle('Calculated white-beam Laue spots — chosen primitive cubic lattice',
fontsize=14)
fig.supxlabel('a = 0.400 nm • λ = 0.080–0.160 nm • plate distance = 50 mm\n'
'Equal-size position markers; colors label wavelengths, not intensity.\n'
'+ direct beam; harmonic reflections can coincide. Not an experimental plate.',
fontsize=10)
fig.savefig(Path(__file__).stem + '.png', dpi=110, facecolor='white', transparent=False)
plt.close(fig)
print({f'tilt_{t:g}_reflections': len(reflections(t)) for t in (0.0, 7.0)})
if __name__ == '__main__':
draw()