import pathlib
import numpy as np
import scipy.optimize
import astropy.units as u
import named_arrays as na
import optika
__all__ = [
"coating_design",
"coating_witness_measured",
"coating_witness_fit",
]
[docs]
def coating_design() -> optika.materials.MultilayerMirror:
"""
The as-designed coating for the FURST feed optics, Acton Optics
broadband VUV coating #1200.
Since we presumably don't know the formula of this proprietary
coating, this function uses the formula in :cite:t:`Quijada2012`.
Examples
--------
Plot the efficiency of the coating across the VUV range
.. jupyter-execute::
import numpy as np
import matplotlib.pyplot as plt
import astropy.units as u
import named_arrays as na
import optika
import furst
# Define an array of wavelengths with which to sample the efficiency
wavelength = na.geomspace(120, 600, axis="wavelength", num=1001) * u.nm
# Define the incident rays from the wavelength array
angle = na.linspace(0, 75, axis="angle", num=5) * u.deg
rays = optika.rays.RayVectorArray(
wavelength=wavelength,
direction=na.Cartesian3dVectorArray(
x=np.sin(angle),
y=0,
z=np.cos(angle),
),
)
# Initialize the FURST feed optic coating model
coating = furst.feed_optics.materials.coating_design()
# Compute the reflectivity of the feed optics
reflectivity = coating.efficiency(
rays=rays,
normal=na.Cartesian3dVectorArray(0, 0, -1),
)
# Plot the reflectivity of the feed optics vs wavelength
fig, ax = plt.subplots(constrained_layout=True)
na.plt.plot(
wavelength,
reflectivity,
ax=ax,
axis="wavelength",
label=angle,
);
ax.set_xlabel(f"wavelength ({wavelength.unit:latex_inline})");
ax.set_ylabel("reflectivity");
ax.legend(title="incidence angle");
"""
return optika.materials.MultilayerMirror(
layers=[
optika.materials.Layer(
chemical="MgF2",
thickness=25 * u.nm,
interface=optika.materials.profiles.ErfInterfaceProfile(1 * u.nm),
kwargs_plot=dict(
color="tab:blue",
alpha=0.3,
),
),
optika.materials.Layer(
chemical="Al",
thickness=60 * u.nm,
interface=optika.materials.profiles.ErfInterfaceProfile(1 * u.nm),
kwargs_plot=dict(
color="tab:blue",
alpha=0.5,
),
),
],
substrate=optika.materials.Layer(
chemical="SiO2",
thickness=3 * u.mm,
interface=optika.materials.profiles.ErfInterfaceProfile(1 * u.nm),
kwargs_plot=dict(
color="gray",
alpha=0.5,
),
),
)
[docs]
def coating_witness_measured() -> optika.materials.MeasuredMirror:
"""
A reflectivity measurement of the witness samples to the
feed optics.
This function assumes that the angle of incidence is 75 degrees from the normal.
This is just a guess and will have to be updated with better information.
Examples
--------
Load the witness sample measurement and plot it as a function
of wavelength against the modeled reflectivity.
.. jupyter-execute::
import numpy as np
import matplotlib.pyplot as plt
import astropy.visualization
import named_arrays as na
import optika
import furst
# Load the coating model
coating_model = furst.feed_optics.materials.coating_design()
# Load the model and the witness sample measurements
coating_measurement = furst.feed_optics.materials.coating_witness_measured()
measurement = coating_measurement.efficiency_measured
# Isolate the wavelengths of the measurement
wavelength = measurement.inputs.wavelength
# Isolate the incidence angle of the measurement
angle = measurement.inputs.direction
# Calculate the reflectivity of the model for the same
# wavelengths as the measurements
reflectivity_model = coating_model.efficiency(
rays=optika.rays.RayVectorArray(
wavelength=wavelength,
direction=na.Cartesian3dVectorArray(
x=np.sin(angle),
y=0,
z=np.cos(angle),
),
),
normal=na.Cartesian3dVectorArray(0, 0, -1),
)
# Plot the measurement as a function of wavelength
with astropy.visualization.quantity_support():
fig, ax = plt.subplots(constrained_layout=True)
na.plt.plot(
wavelength,
reflectivity_model,
axis="wavelength",
ax=ax,
label="model",
)
na.plt.plot(
measurement.inputs.wavelength,
measurement.outputs,
ax=ax,
label="measurement",
)
ax.set_xlabel(f"wavelength ({measurement.inputs.wavelength.unit:latex_inline})");
ax.set_ylabel("reflectivity");
ax.legend();
"""
wavelength, reflectivity = np.loadtxt(
fname=pathlib.Path(__file__).parent / "_data/witness-2023-May-24.txt",
skiprows=1,
unpack=True,
)
wavelength = na.ScalarArray(wavelength << u.nm, axes="wavelength")
reflectivity = na.ScalarArray(reflectivity << u.percent, axes="wavelength")
result = optika.materials.MeasuredMirror(
efficiency_measured=na.FunctionArray(
inputs=na.SpectralDirectionalVectorArray(
wavelength=wavelength,
direction=75 * u.deg,
),
outputs=reflectivity.to(u.dimensionless_unscaled),
),
substrate=optika.materials.Layer(
chemical="SiO2",
),
)
return result
[docs]
def coating_witness_fit() -> optika.materials.MultilayerMirror:
"""
A coating fitted to the :func:`coating_witness_measured` measurement.
Examples
--------
Plot the fitted vs. measured reflectivity of the feed optic witness sample.
.. jupyter-execute::
import numpy as np
import matplotlib.pyplot as plt
import named_arrays as na
import optika
from furst import feed_optics
# Load the measured reflectivity of the witness sample
multilayer_measured = feed_optics.materials.coating_witness_measured()
measurement = multilayer_measured.efficiency_measured
# Isolate the angle of incidence of the measurement
angle_incidence = measurement.inputs.direction
# Fit a MgF2+Al coating to the measured reflectivity
coating = feed_optics.materials.coating_witness_fit()
# Define the rays incident on the coating that will be used to
# compute the reflectivity
rays = optika.rays.RayVectorArray(
wavelength=measurement.inputs.wavelength,
direction=na.Cartesian3dVectorArray(
x=np.sin(angle_incidence),
y=0,
z=np.cos(angle_incidence),
),
)
# Compute the reflectivity of the fitted multilayer stack
reflectivity_fit = coating.efficiency(
rays=rays,
normal=na.Cartesian3dVectorArray(0, 0, -1),
)
# Plot the fitted vs. measured reflectivity
fig, ax = plt.subplots(constrained_layout=True)
na.plt.scatter(
measurement.inputs.wavelength,
measurement.outputs,
ax=ax,
label="measured"
);
na.plt.plot(
rays.wavelength,
reflectivity_fit,
ax=ax,
label="fitted",
color="tab:orange",
);
ax.set_xlabel(f"wavelength ({rays.wavelength.unit:latex_inline})")
ax.set_ylabel("reflectivity")
ax.legend();
# Print the fitted coating
coating
"""
design = coating_design()
measurement = coating_witness_measured()
unit = u.nm
reflectivity = measurement.efficiency_measured.outputs
angle_incidence = measurement.efficiency_measured.inputs.direction
rays = optika.rays.RayVectorArray(
wavelength=measurement.efficiency_measured.inputs.wavelength,
direction=na.Cartesian3dVectorArray(
x=np.sin(angle_incidence),
y=0,
z=np.cos(angle_incidence),
),
)
normal = na.Cartesian3dVectorArray(0, 0, -1)
def _coating(
thickness_MgF2: float,
thickness_Al: float,
width_interface: float,
):
result = coating_design()
result.layers[0].thickness = thickness_MgF2 * unit
result.layers[1].thickness = thickness_Al * unit
result.layers[0].interface.width = width_interface * unit
result.layers[1].interface.width = width_interface * unit
result.substrate.interface.width = width_interface * unit
return result
def _func(x: np.ndarray):
multilayer = _coating(*x)
reflectivity_fit = multilayer.efficiency(
rays=rays,
normal=normal,
)
result = np.sqrt(np.mean(np.square(reflectivity_fit - reflectivity)))
return result.ndarray.value
fit = scipy.optimize.minimize(
fun=_func,
x0=[
design.layers[0].thickness.to_value(unit),
design.layers[1].thickness.to_value(unit),
design.substrate.interface.width.to_value(unit),
],
bounds=[
(0, None),
(0, None),
(0, None),
],
)
return _coating(*fit.x)