{ "cells": [ { "cell_type": "raw", "id": "0", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Design & Specifications\n", "=======================\n", "\n", "This report inspects the ESIS-II optical design and evaluates its performance.\n", "\n", "The ESIS-II optical design has been modified from the ESIS-I design to have higher resolution \n", "and different target wavelengths.\n", "The changes from ESIS-I are as follows:\n", "\n", "- The target wavelengths have changed to Ne VII 46.5 nm and Si XII 49.9 nm.\n", "- The primary mirror is moved back by 6 holes on the optical bench.\n", "- The gratings are moved forward by 2 holes on the optical bench.\n", "- The filter position and orientation has been adjusted to account for the shallower beam.\n", "\n", "To account for these changes, the angle, radius of curvature, and the\n", "ruling parameters of the grating need to be adjusted.\n", "This design used :func:`~scipy.optimize.differential_evolution` to\n", "compute these parameters." ] }, { "cell_type": "code", "execution_count": null, "id": "1", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "import dataclasses\n", "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 optika\n", "import esis" ] }, { "cell_type": "raw", "id": "2", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Start by loading a single channel of the optical design to investigate its performance." ] }, { "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)" ] }, { "cell_type": "raw", "id": "4", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Lower the number of field angles from the default to make the results easier to interpret." ] }, { "cell_type": "code", "execution_count": null, "id": "5", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "instrument.field.num = 5" ] }, { "cell_type": "raw", "id": "6", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Plot the rays associated with this single channel." ] }, { "cell_type": "code", "execution_count": null, "id": "7", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "with astropy.visualization.quantity_support():\n", " fig, ax = plt.subplots(\n", " figsize=(8, 4),\n", " constrained_layout=True,\n", " )\n", " instrument.system.plot(\n", " ax=ax,\n", " components=(\"z\", \"x\"),\n", " color=\"black\",\n", " kwargs_rays=dict(\n", " color=na.ScalarArray(\n", " ndarray=np.array([\"tab:blue\", \"tab:orange\"]),\n", " axes=\"wavelength\",\n", " ),\n", " label=instrument.wavelength.to_string_array(),\n", " zorder=-instrument.wavelength.value,\n", " ),\n", " )\n", " ax.set_xlabel(f\"$z$ ({ax.get_xlabel()})\")\n", " ax.set_ylabel(f\"$x$ ({ax.get_ylabel()})\")\n", "\n", " handles, labels = ax.get_legend_handles_labels()\n", " by_label = dict(zip(labels, handles))\n", " ax.legend(by_label.values(), by_label.keys())" ] }, { "cell_type": "raw", "id": "8", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Spot Diagram\n", "------------\n", "Plot the spot diagram associated with this channel to inspect the PSF of the system." ] }, { "cell_type": "code", "execution_count": null, "id": "9", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "instrument.system.spot_diagram();" ] }, { "cell_type": "raw", "id": "10", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Primary\n", "-------\n", "\n", "Sag Table\n", "^^^^^^^^^\n", "\n", "The vendor requires a sag table for the primary mirror, so we will evaluate the sag function of this surface at several points along a radial vector.\n", "We'll start by defining a position coordinate that varies only along the :math:`x` axis." ] }, { "cell_type": "code", "execution_count": null, "id": "11", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "position = na.Cartesian2dVectorArray(\n", " x=na.linspace(0, 80, axis=\"x\", num=11) * u.mm,\n", " y=0 * u.mm,\n", ")" ] }, { "cell_type": "raw", "id": "12", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Next, evaluate the negative of the sag function for each of the defined positions." ] }, { "cell_type": "code", "execution_count": null, "id": "13", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "z = -instrument.primary_mirror.sag(position)" ] }, { "cell_type": "raw", "id": "14", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Plot the sag as a function of radial position." ] }, { "cell_type": "code", "execution_count": null, "id": "15", "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(position.x, z, label=\"primary mirror\")\n", " ax.set_xlabel(f\"x ({ax.get_xlabel()})\")\n", " ax.set_ylabel(f\"sag ({ax.get_ylabel()})\")\n", " ax.legend()" ] }, { "cell_type": "raw", "id": "16", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Print the sag function as a table." ] }, { "cell_type": "code", "execution_count": null, "id": "17", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "print(pandas.DataFrame(np.stack([position.x.ndarray, z.ndarray]).T, columns=[\"x\", \"sag\"]))" ] }, { "cell_type": "raw", "id": "18", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Footprint Diagram\n", "^^^^^^^^^^^^^^^^^\n", "Now, we would like to plot a diagram showing the footprint of the beam on the primary mirror.\n", "To do this, we will define a dense array of field points that goes almost to the edge of the FOV," ] }, { "cell_type": "code", "execution_count": null, "id": "19", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "field = na.Cartesian2dVectorLinearSpace(\n", " start=-0.99,\n", " stop=+0.99,\n", " axis=na.Cartesian2dVectorArray(\"fx\", \"fy\"),\n", " num=11,\n", ")" ] }, { "cell_type": "raw", "id": "20", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "and a denser array of pupil points that also goes almost to the edge." ] }, { "cell_type": "code", "execution_count": null, "id": "21", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "pupil=na.Cartesian2dVectorLinearSpace(\n", " start=-0.99,\n", " stop=+0.99,\n", " axis=na.Cartesian2dVectorArray(\"px\", \"py\"),\n", " num=41,\n", ")" ] }, { "cell_type": "raw", "id": "22", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Now, load the model of the full ESIS-II instrument, including all channels, with a much denser grid of rays." ] }, { "cell_type": "code", "execution_count": null, "id": "23", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "instrument_full = esis.flights.f2.optics.design(\n", " grid=optika.vectors.ObjectVectorArray(\n", " wavelength=instrument.wavelength,\n", " field=field,\n", " pupil=pupil,\n", " ), \n", " num_distribution=0,\n", ")" ] }, { "cell_type": "raw", "id": "24", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "We will also rotate the instrument 22.5 degrees counter clockwise so that the borders of the primary are parallel to the vertical and horizontal." ] }, { "cell_type": "code", "execution_count": null, "id": "25", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "instrument_full.roll = 22.5 * u.deg" ] }, { "cell_type": "raw", "id": "26", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Finally, we will rotate the primary mirror 180 degrees about the :math:`y` axis so that we view it from the front." ] }, { "cell_type": "code", "execution_count": null, "id": "27", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "transformation = na.transformations.Cartesian3dRotationY(180 * u.deg)" ] }, { "cell_type": "raw", "id": "28", "metadata": { "editable": true, "raw_mimetype": "text/x-rst", "slideshow": { "slide_type": "" }, "tags": [] }, "source": [ "Now, we are ready to plot a footprint diagram of the beam on the primary mirror." ] }, { "cell_type": "code", "execution_count": null, "id": "29", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "with astropy.visualization.quantity_support():\n", " fig, ax = plt.subplots(constrained_layout=True)\n", " instrument_full.schematic_primary(\n", " ax=ax,\n", " transformation=transformation,\n", " )\n", " ax.set_aspect(\"equal\")\n", " keepout = dataclasses.replace(instrument_full.central_obscuration, halfwidth=25 * u.mm)\n", " keepout.surface.plot(\n", " ax=ax,\n", " transformation=transformation @ instrument_full.transformation,\n", " components=(\"x\", \"y\"),\n", " color=\"red\",\n", " )" ] }, { "cell_type": "code", "execution_count": null, "id": "30", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [] } ], "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 }