{ "cells": [ { "cell_type": "markdown", "id": "21c7ff5c", "metadata": {}, "source": [ "## Tutorial 6d - Dichroic Mirror Optimization for Polarization Separation\n", "\n", "We optimize a thin-film stack to create a dichroic mirror that separates s and p polarizations near 600 nm.\n", "\n", "This version demonstrates the operand-centric architecture in ThinFilmOptimizer with a single API entry point:\n", "- standard spectral operands with add_operand(...)\n", "- a custom user-defined operand registered with register_operand(...) and used through add_operand(...)" ] }, { "cell_type": "code", "execution_count": 1, "id": "4d7f5c3a", "metadata": { "execution": { "iopub.execute_input": "2026-03-24T11:09:37.851891Z", "iopub.status.busy": "2026-03-24T11:09:37.851422Z", "iopub.status.idle": "2026-03-24T11:09:41.055855Z", "shell.execute_reply": "2026-03-24T11:09:41.055054Z" } }, "outputs": [], "source": [ "from optiland.thin_film.optimization import ThinFilmOptimizer\n", "import optiland.backend as be\n", "from optiland.thin_film import ThinFilmStack, SpectralAnalyzer\n", "from optiland.materials import Material, IdealMaterial\n", "\n", "SiO2 = Material(\"SiO2\", reference=\"Gao\")\n", "TiO2 = Material(\"TiO2\", reference=\"Zhukovsky\")\n", "BK7 = Material(\"N-BK7\", reference=\"SCHOTT\")\n", "air = IdealMaterial(n=1.0)\n", "\n", "dichroic_stack = ThinFilmStack(\n", " incident_material=air, \n", " substrate_material=BK7, \n", " reference_wl_um=0.6,\n", " reference_AOI_deg=45.0,\n", ")\n", "for i in range(10): # 10 pairs = 20 layers\n", " dichroic_stack.add_layer_qwot(material=TiO2, qwot_thickness=1.0, name=f\"$TiO_2$\")\n", " dichroic_stack.add_layer_qwot(material=SiO2, qwot_thickness=1.0, name=f\"$SiO_2$\")" ] }, { "cell_type": "code", "execution_count": 2, "id": "be178910", "metadata": { "execution": { "iopub.execute_input": "2026-03-24T11:09:41.057870Z", "iopub.status.busy": "2026-03-24T11:09:41.057694Z", "iopub.status.idle": "2026-03-24T11:09:41.069316Z", "shell.execute_reply": "2026-03-24T11:09:41.068879Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "ThinFilm Optimizer Information\n", "==================================================\n", "+--------------+---------+\n", "| Property | Count |\n", "+==============+=========+\n", "| Stack layers | 20 |\n", "+--------------+---------+\n", "| Variables | 20 |\n", "+--------------+---------+\n", "| Targets | 1 |\n", "+--------------+---------+\n", "\n", "Variables:\n", "+------+---------+------------------+------------+------------+\n", "| ID | Layer | Thickness (nm) | Min (nm) | Max (nm) |\n", "+======+=========+==================+============+============+\n", "| 0 | 0 | 88.2 | 30 | 300 |\n", "+------+---------+------------------+------------+------------+\n", "| 1 | 1 | 143.6 | 30 | 300 |\n", "+------+---------+------------------+------------+------------+\n", "| 2 | 2 | 88.2 | 30 | 300 |\n", "+------+---------+------------------+------------+------------+\n", "| 3 | 3 | 143.6 | 30 | 300 |\n", "+------+---------+------------------+------------+------------+\n", "| 4 | 4 | 88.2 | 30 | 300 |\n", "+------+---------+------------------+------------+------------+\n", "| 5 | 5 | 143.6 | 30 | 300 |\n", "+------+---------+------------------+------------+------------+\n", "| 6 | 6 | 88.2 | 30 | 300 |\n", "+------+---------+------------------+------------+------------+\n", "| 7 | 7 | 143.6 | 30 | 300 |\n", "+------+---------+------------------+------------+------------+\n", "| 8 | 8 | 88.2 | 30 | 300 |\n", "+------+---------+------------------+------------+------------+\n", "| 9 | 9 | 143.6 | 30 | 300 |\n", "+------+---------+------------------+------------+------------+\n", "| 10 | 10 | 88.2 | 30 | 300 |\n", "+------+---------+------------------+------------+------------+\n", "| 11 | 11 | 143.6 | 30 | 300 |\n", "+------+---------+------------------+------------+------------+\n", "| 12 | 12 | 88.2 | 30 | 300 |\n", "+------+---------+------------------+------------+------------+\n", "| 13 | 13 | 143.6 | 30 | 300 |\n", "+------+---------+------------------+------------+------------+\n", "| 14 | 14 | 88.2 | 30 | 300 |\n", "+------+---------+------------------+------------+------------+\n", "| 15 | 15 | 143.6 | 30 | 300 |\n", "+------+---------+------------------+------------+------------+\n", "| 16 | 16 | 88.2 | 30 | 300 |\n", "+------+---------+------------------+------------+------------+\n", "| 17 | 17 | 143.6 | 30 | 300 |\n", "+------+---------+------------------+------------+------------+\n", "| 18 | 18 | 88.2 | 30 | 300 |\n", "+------+---------+------------------+------------+------------+\n", "| 19 | 19 | 143.6 | 30 | 300 |\n", "+------+---------+------------------+------------+------------+\n", "\n", "Targets:\n", "+------+--------------------------+--------+---------+--------------+-------+----------+-------+\n", "| ID | Prop | Type | Value | Wavelength | AOI | Weight | Pol |\n", "+======+==========================+========+=========+==============+=======+==========+=======+\n", "| 0 | Rs-Rp @ 595-605nm, 45deg | custom | | - | - | 1 | - |\n", "+------+--------------------------+--------+---------+--------------+-------+----------+-------+\n", "\n", "Initial RSS: 3.058333e-01\n" ] } ], "source": [ "optimizer = ThinFilmOptimizer(dichroic_stack)\n", "\n", "# Add all thicknesses as optimization variables\n", "for i in range(len(dichroic_stack.layers)):\n", " optimizer.add_variable(\n", " layer_index=i,\n", " min_nm=30,\n", " max_nm=300\n", " )\n", "\n", "# we want to maximize the polarization contrast (Rs-Rp) at 600 nm and 45° AOI, so we \n", "# minimize the negative contrast, averaged over a wavelength range to make it more \n", "# robust to fabrication variations. The contrast is normalized to be between 0 and 1,\n", "# where 1 corresponds to Rs=1 and Rp=0\n", "wl_nm = be.linspace(595, 605, 11)\n", "\n", "# Register and add a custom operand through add_operand\n", "def polarization_contrast(stack: ThinFilmStack, wavelength_nm: be.ndarray, aoi_deg:float):\n", " \"\"\"Calculate the polarization contrast (Rs-Rp) averaged over a wavelength range.\n", " The contrast is normalized to be between 0 and 1, where 1 corresponds to Rs=1 and Rp=0.\"\"\"\n", " rs = stack.reflectance_nm_deg(wavelength_nm, aoi_deg, \"s\")\n", " rp = stack.reflectance_nm_deg(wavelength_nm, aoi_deg, \"p\")\n", " return (1 + be.mean(rs - rp))/2\n", "\n", "ThinFilmOptimizer.register_operand(\n", " \"polarization_contrast\", polarization_contrast, overwrite=True\n", ")\n", "\n", "optimizer.add_operand(\n", " property=\"polarization_contrast\",\n", " min_val=0.99,\n", " input_data={\"wavelength_nm\": wl_nm, \"aoi_deg\": 45.0},\n", " label=\"Rs-Rp @ 595-605nm, 45deg\",\n", ")\n", "\n", "# Display optimization information\n", "optimizer.info()\n", "contrast_initial=optimizer.rss()\n", "print(f\"Initial RSS: {contrast_initial:.6e}\")" ] }, { "cell_type": "code", "execution_count": 3, "id": "c5715cbf", "metadata": { "execution": { "iopub.execute_input": "2026-03-24T11:09:41.071169Z", "iopub.status.busy": "2026-03-24T11:09:41.070971Z", "iopub.status.idle": "2026-03-24T11:09:43.330047Z", "shell.execute_reply": "2026-03-24T11:09:43.329563Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Merit: 0.093534002618582 -> 0.000329856133348 in 46 iterations\n", "Improvement: 0.093204146485235\n" ] } ], "source": [ "# Launch optimization\n", "result = optimizer.optimize(\n", " method=\"L-BFGS-B\",\n", " max_iterations=1000,\n", ")\n", "\n", "print(f\"Merit: {result['initial_merit']:.15f} -> {result['final_merit']:.15f} in {result['iterations']} iterations\")\n", "print(f\"Improvement: {result['improvement']:.15f}\")" ] }, { "cell_type": "code", "execution_count": 4, "id": "8d67491c", "metadata": { "execution": { "iopub.execute_input": "2026-03-24T11:09:43.332218Z", "iopub.status.busy": "2026-03-24T11:09:43.331934Z", "iopub.status.idle": "2026-03-24T11:09:43.341462Z", "shell.execute_reply": "2026-03-24T11:09:43.341023Z" } }, "outputs": [ { "data": { "text/html": [ "
| \n", " | Variable | \n", "Initial | \n", "Final | \n", "Change | \n", "Unit | \n", "
|---|---|---|---|---|---|
| 0 | \n", "Layer 0 thickness | \n", "88.2 | \n", "71.4 | \n", "-16.8 (-19.1%) | \n", "nm | \n", "
| 1 | \n", "Layer 1 thickness | \n", "143.6 | \n", "145.8 | \n", "+2.2 (+1.5%) | \n", "nm | \n", "
| 2 | \n", "Layer 2 thickness | \n", "88.2 | \n", "78.3 | \n", "-9.9 (-11.2%) | \n", "nm | \n", "
| 3 | \n", "Layer 3 thickness | \n", "143.6 | \n", "153.6 | \n", "+10.0 (+7.0%) | \n", "nm | \n", "
| 4 | \n", "Layer 4 thickness | \n", "88.2 | \n", "199.6 | \n", "+111.4 (+126.3%) | \n", "nm | \n", "
| 5 | \n", "Layer 5 thickness | \n", "143.6 | \n", "146.2 | \n", "+2.6 (+1.8%) | \n", "nm | \n", "
| 6 | \n", "Layer 6 thickness | \n", "88.2 | \n", "74.2 | \n", "-14.1 (-16.0%) | \n", "nm | \n", "
| 7 | \n", "Layer 7 thickness | \n", "143.6 | \n", "135.6 | \n", "-8.0 (-5.6%) | \n", "nm | \n", "
| 8 | \n", "Layer 8 thickness | \n", "88.2 | \n", "72.0 | \n", "-16.2 (-18.4%) | \n", "nm | \n", "
| 9 | \n", "Layer 9 thickness | \n", "143.6 | \n", "132.5 | \n", "-11.1 (-7.7%) | \n", "nm | \n", "
| 10 | \n", "Layer 10 thickness | \n", "88.2 | \n", "70.8 | \n", "-17.4 (-19.8%) | \n", "nm | \n", "
| 11 | \n", "Layer 11 thickness | \n", "143.6 | \n", "132.5 | \n", "-11.1 (-7.7%) | \n", "nm | \n", "
| 12 | \n", "Layer 12 thickness | \n", "88.2 | \n", "67.3 | \n", "-20.9 (-23.7%) | \n", "nm | \n", "
| 13 | \n", "Layer 13 thickness | \n", "143.6 | \n", "161.4 | \n", "+17.8 (+12.4%) | \n", "nm | \n", "
| 14 | \n", "Layer 14 thickness | \n", "88.2 | \n", "196.0 | \n", "+107.7 (+122.1%) | \n", "nm | \n", "
| 15 | \n", "Layer 15 thickness | \n", "143.6 | \n", "161.7 | \n", "+18.1 (+12.6%) | \n", "nm | \n", "
| 16 | \n", "Layer 16 thickness | \n", "88.2 | \n", "77.6 | \n", "-10.7 (-12.1%) | \n", "nm | \n", "
| 17 | \n", "Layer 17 thickness | \n", "143.6 | \n", "137.2 | \n", "-6.4 (-4.5%) | \n", "nm | \n", "
| 18 | \n", "Layer 18 thickness | \n", "88.2 | \n", "71.3 | \n", "-16.9 (-19.1%) | \n", "nm | \n", "
| 19 | \n", "Layer 19 thickness | \n", "143.6 | \n", "140.0 | \n", "-3.6 (-2.5%) | \n", "nm | \n", "