{ "cells": [ { "cell_type": "raw", "id": "0", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Grating Specifications\n", "======================\n", "\n", "This report collects the specifications of the ESIS-II diffraction gratings\n", "in a form that can be independently checked against the vendor's ruling\n", "prescription, in the same spirit as the primary-mirror sag table in\n", ":doc:`design`.\n", "\n", "The ESIS-II gratings are spherical, varied-line-space (VLS) gratings with a\n", "trapezoidal aperture:\n", "a skinny end which points toward the axis of symmetry of the instrument,\n", "and a fat end which points away from it.\n", "Since the flight rulings are too fine to test with visible light,\n", "each science grating is paired with a visible-light alignment grating whose\n", "ruling spacing is scaled so that a HeNe laser reproduces the flight geometry.\n", "The alignment gratings are modeled by\n", ":func:`~esis.flights.f2.optics.design_visible`,\n", "a visible-light version of the instrument which inherits everything from the\n", "EUV design except the grating ruling spacing.\n", "\n", "We start by verifying how the ruling spacing changes from the skinny end of\n", "the grating to the fat end." ] }, { "cell_type": "code", "execution_count": null, "id": "1", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import astropy.units as u\n", "import astropy.visualization\n", "import pandas\n", "import named_arrays as na\n", "import esis" ] }, { "cell_type": "raw", "id": "2", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Load a single channel of the EUV optical design,\n", "which carries the science grating model that the specifications below are\n", "derived from." ] }, { "cell_type": "code", "execution_count": null, "id": "3", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "instrument = esis.flights.f2.optics.design_single(num_distribution=0)\n", "grating = instrument.grating" ] }, { "cell_type": "raw", "id": "4", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Coordinate System\n", "-----------------\n", "\n", "All positions on the face of the grating are expressed in the following\n", "right-handed coordinate system:\n", "\n", "- The origin is the vertex of the optical surface, which lies on the axis of\n", " symmetry of the trapezoid.\n", " Note that the vertex is very nearly the center of the *clear* aperture\n", " (within 25 μm),\n", " not the center of the substrate,\n", " which is about 1.3 mm closer to the skinny end.\n", "- The :math:`y` axis lies in the plane of the face along the axis of symmetry\n", " of the trapezoid, with :math:`+y` pointing from the skinny end toward the\n", " fat end.\n", " When the grating is installed, :math:`+y` points radially away from the\n", " axis of symmetry of the instrument, and :math:`y` is the direction of\n", " dispersion.\n", "- The :math:`x` axis lies in the plane of the face, parallel to the grooves.\n", "- The :math:`z` axis is normal to the vertex of the optical surface,\n", " pointing away from the substrate.\n", "\n", "The grooves are parallel to the :math:`x` axis and the ruling spacing varies\n", "only as a function of :math:`y`.\n", "\n", "Plot the clear and mechanical apertures of the grating in this coordinate\n", "system.\n", "Note that the surface-local coordinates of the :mod:`optika` model swap the\n", "roles of :math:`x` and :math:`y` relative to the definition above\n", "(the model measures the ruling spacing along its local :math:`x` axis),\n", "so the model's axes are interchanged whenever we evaluate or plot below." ] }, { "cell_type": "code", "execution_count": null, "id": "5", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "surface = grating.surface\n", "\n", "y_skinny = -(grating.halfwidth_inner + grating.width_border_inner)\n", "y_fat = grating.halfwidth_outer + grating.width_border\n", "\n", "with astropy.visualization.quantity_support():\n", " fig, ax = plt.subplots(constrained_layout=True)\n", " ax.set_aspect(\"equal\")\n", " surface.aperture_mechanical.plot(\n", " ax=ax,\n", " components=(\"y\", \"x\"),\n", " color=\"black\",\n", " )\n", " surface.aperture.plot(\n", " ax=ax,\n", " components=(\"y\", \"x\"),\n", " color=\"tab:blue\",\n", " )\n", " ax.scatter(0 * u.mm, 0 * u.mm, color=\"black\", zorder=3)\n", " ax.text(x=0.5, y=0.8, s=\"vertex\")\n", " for y_groove in np.linspace(-6, 6, 7):\n", " ax.plot(\n", " [-3, 3] * u.mm,\n", " [y_groove, y_groove] * u.mm,\n", " color=\"tab:blue\",\n", " alpha=0.3,\n", " )\n", " ax.text(\n", " x=0,\n", " y=y_skinny.to_value(u.mm) - 1,\n", " s=\"skinny end\",\n", " ha=\"center\",\n", " va=\"top\",\n", " )\n", " ax.text(\n", " x=0,\n", " y=y_fat.to_value(u.mm) + 1,\n", " s=\"fat end\",\n", " ha=\"center\",\n", " va=\"bottom\",\n", " )\n", " ax.text(\n", " x=6,\n", " y=0,\n", " s=\"grooves\",\n", " ha=\"center\",\n", " va=\"center\",\n", " color=\"tab:blue\",\n", " )\n", " ax.set_xlim(-16, 16)\n", " ax.set_ylim(-17, 14)\n", " ax.set_xlabel(f\"$x$ ({ax.get_xlabel()})\")\n", " ax.set_ylabel(f\"$y$ ({ax.get_ylabel()})\")\n", " ax.set_title(\"view of the optical face, mechanical (black) and clear (blue)\")" ] }, { "cell_type": "raw", "id": "6", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Science Grating Ruling Prescription\n", "-----------------------------------\n", "\n", "The ruling spacing of the science grating is a polynomial in :math:`y`,\n", "\n", ".. math::\n", "\n", " d(y) = d_0 + d_1 y + d_2 y^2,\n", "\n", "defined about the vertex of the optical surface.\n", "Print the model of the rulings, which includes the values of the\n", "polynomial coefficients." ] }, { "cell_type": "code", "execution_count": null, "id": "7", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "grating.rulings.spacing" ] }, { "cell_type": "raw", "id": "8", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Since :math:`d_1` and :math:`d_2` are both negative,\n", "the ruling spacing *decreases* (and the ruling density *increases*)\n", "monotonically from the skinny end of the grating to the fat end.\n", "\n", "Evaluate the ruling spacing over the full substrate,\n", "from the skinny end to the fat end,\n", "and print it as a table which can be checked against the vendor's\n", "prescription.\n", "Note the interchange of :math:`x` and :math:`y` between the face coordinates\n", "and the surface-local coordinates of the model discussed above." ] }, { "cell_type": "code", "execution_count": null, "id": "9", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "y = na.linspace(y_skinny, y_fat, axis=\"y\", num=25)\n", "\n", "position = na.Cartesian3dVectorArray(x=y, y=0 * u.mm, z=0 * u.mm)\n", "normal = na.Cartesian3dVectorArray(0, 0, -1)\n", "\n", "spacing = grating.rulings.spacing(position, normal).length\n", "\n", "print(\n", " pandas.DataFrame(\n", " {\n", " \"y (mm)\": y.ndarray.to_value(u.mm),\n", " \"spacing (um)\": spacing.ndarray.to_value(u.um),\n", " \"density (1/mm)\": (1 / spacing).ndarray.to_value(1 / u.mm),\n", " }\n", " )\n", ")" ] }, { "cell_type": "raw", "id": "10", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Visible Alignment Gratings\n", "--------------------------\n", "\n", "The alignment gratings are designed so that a HeNe laser\n", "(:math:`\\lambda = 632.8` nm) diffracted into first order follows the same\n", "path through the instrument as the center of the EUV passband does for the\n", "science gratings.\n", "From the grating equation,\n", "\n", ".. math::\n", "\n", " \\sin \\alpha + \\sin \\beta = \\frac{m \\lambda}{d(y)},\n", "\n", "the diffracted angles match at every point on the face of the grating if\n", ":math:`\\lambda / d(y)` is preserved,\n", "so the alignment ruling spacing is the science ruling spacing scaled by the\n", "constant ratio\n", "\n", ".. math::\n", "\n", " \\frac{d_\\text{vis}(y)}{d(y)}\n", " = \\frac{\\lambda_\\text{HeNe}}{\\lambda_c},\n", "\n", "where :math:`\\lambda_c` is the center of the EUV passband.\n", "Compute this ratio." ] }, { "cell_type": "code", "execution_count": null, "id": "11", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "wavelength_center = (\n", " esis.flights.f2.wavelength_Ne_VII + esis.flights.f2.wavelength_Si_XII\n", ") / 2\n", "\n", "ratio = esis.flights.f2.wavelength_HeNe / wavelength_center\n", "ratio = ratio.to(u.dimensionless_unscaled)\n", "ratio" ] }, { "cell_type": "raw", "id": "12", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "This scaling is implemented by\n", ":func:`~esis.flights.f2.optics.design_visible`,\n", "which inherits everything else from the EUV design.\n", "Load the visible-light instrument and print the ruling spacing polynomial of\n", "its alignment gratings." ] }, { "cell_type": "code", "execution_count": null, "id": "13", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "instrument_visible = esis.flights.f2.optics.design_visible(num_distribution=0)\n", "grating_visible = instrument_visible.grating\n", "\n", "grating_visible.rulings.spacing" ] }, { "cell_type": "raw", "id": "14", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Evaluate the alignment ruling spacing over the full substrate,\n", "from the skinny end to the fat end." ] }, { "cell_type": "code", "execution_count": null, "id": "15", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "spacing_visible = grating_visible.rulings.spacing(position, normal).length" ] }, { "cell_type": "raw", "id": "16", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Plot the line spacing of the visible alignment gratings as a function of\n", ":math:`y`." ] }, { "cell_type": "code", "execution_count": null, "id": "17", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "with astropy.visualization.quantity_support():\n", " fig, ax = plt.subplots(constrained_layout=True)\n", " na.plt.plot(y, spacing_visible.to(u.um), ax=ax, color=\"tab:blue\")\n", " ax.axvline(\n", " -grating.halfwidth_inner.to_value(u.mm),\n", " color=\"gray\",\n", " linestyle=\"--\",\n", " )\n", " ax.axvline(\n", " grating.halfwidth_outer.to_value(u.mm),\n", " color=\"gray\",\n", " linestyle=\"--\",\n", " label=\"clear aperture\",\n", " )\n", " ax.text(0.01, 0.02, \"skinny end\", transform=ax.transAxes, ha=\"left\", va=\"bottom\")\n", " ax.text(0.99, 0.02, \"fat end\", transform=ax.transAxes, ha=\"right\", va=\"bottom\")\n", " ax.set_xlabel(f\"$y$ ({ax.get_xlabel()})\")\n", " ax.set_ylabel(f\"ruling spacing ({ax.get_ylabel()})\")\n", " ax.legend()" ] }, { "cell_type": "raw", "id": "18", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Plot the corresponding ruling density." ] }, { "cell_type": "code", "execution_count": null, "id": "19", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "with astropy.visualization.quantity_support():\n", " fig, ax = plt.subplots(constrained_layout=True)\n", " na.plt.plot(y, (1 / spacing_visible).to(1 / u.mm), ax=ax, color=\"tab:blue\")\n", " ax.axvline(\n", " -grating.halfwidth_inner.to_value(u.mm),\n", " color=\"gray\",\n", " linestyle=\"--\",\n", " )\n", " ax.axvline(\n", " grating.halfwidth_outer.to_value(u.mm),\n", " color=\"gray\",\n", " linestyle=\"--\",\n", " label=\"clear aperture\",\n", " )\n", " ax.text(0.01, 0.98, \"skinny end\", transform=ax.transAxes, ha=\"left\", va=\"top\")\n", " ax.text(0.99, 0.98, \"fat end\", transform=ax.transAxes, ha=\"right\", va=\"top\")\n", " ax.set_xlabel(f\"$y$ ({ax.get_xlabel()})\")\n", " ax.set_ylabel(f\"ruling density ({ax.get_ylabel()})\")\n", " ax.legend()" ] }, { "cell_type": "raw", "id": "20", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Print the line spacing and ruling density of the alignment gratings as a\n", "table which can be checked against the vendor's prescription." ] }, { "cell_type": "code", "execution_count": null, "id": "21", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "print(\n", " pandas.DataFrame(\n", " {\n", " \"y (mm)\": y.ndarray.to_value(u.mm),\n", " \"spacing (um)\": spacing_visible.ndarray.to_value(u.um),\n", " \"density (1/mm)\": (1 / spacing_visible).ndarray.to_value(1 / u.mm),\n", " }\n", " )\n", ")" ] }, { "cell_type": "raw", "id": "22", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "The line spacing of the visible alignment gratings decreases monotonically\n", "from the skinny end of the grating to the fat end,\n", "and the corresponding ruling density increases,\n", "consistent with the sign of the VLS coefficients of the science gratings.\n", "\n", "End-to-end Check\n", "----------------\n", "\n", "As an independent check of the scaling,\n", "trace a HeNe laser through the visible-light instrument and confirm that it\n", "lands on the sensors at the same place as the center of the EUV passband.\n", "\n", "Start by plotting the rays traveling through the visible-light instrument,\n", "as viewed from the side." ] }, { "cell_type": "code", "execution_count": null, "id": "23", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "instrument_visible.field.num = 3\n", "instrument_visible.pupil.num = 3\n", "\n", "with astropy.visualization.quantity_support():\n", " fig, ax = plt.subplots(constrained_layout=True)\n", " instrument_visible.system.plot(\n", " components=(\"z\", \"x\"),\n", " color=\"black\",\n", " kwargs_rays=dict(\n", " color=\"tab:red\",\n", " ),\n", " );" ] }, { "cell_type": "raw", "id": "24", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Now compute the mean position of the unvignetted HeNe rays on the sensors." ] }, { "cell_type": "code", "execution_count": null, "id": "25", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "rays_visible = instrument_visible.system.rayfunction().outputs\n", "\n", "where = rays_visible.unvignetted\n", "position_visible = (rays_visible.position.x * where).sum() / where.sum()\n", "position_visible.ndarray.to(u.mm)" ] }, { "cell_type": "raw", "id": "26", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Compare against the mean position of each EUV spectral line on the sensors\n", "of the flight instrument." ] }, { "cell_type": "code", "execution_count": null, "id": "27", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "instrument_euv = esis.flights.f2.optics.design(num_distribution=0)\n", "instrument_euv.field.num = 3\n", "instrument_euv.pupil.num = 3\n", "\n", "rays_euv = instrument_euv.system.rayfunction().outputs\n", "\n", "axis = tuple(a for a in rays_euv.position.x.shape if a != \"wavelength\")\n", "\n", "where = rays_euv.unvignetted\n", "position_euv = (rays_euv.position.x * where).sum(axis=axis) / where.sum(axis=axis)\n", "position_euv.ndarray.to(u.mm)" ] }, { "cell_type": "raw", "id": "28", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "The HeNe laser lands within a few microns of the midpoint of the two EUV\n", "spectral lines,\n", "confirming that the alignment gratings reproduce the flight geometry." ] }, { "cell_type": "code", "execution_count": null, "id": "29", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "position_difference = position_visible - position_euv.mean(axis=\"wavelength\")\n", "position_difference.ndarray.to(u.um)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.3" } }, "nbformat": 4, "nbformat_minor": 5 }