#!/usr/bin/env python # coding: utf-8 # # Focusing Properties of a Binary-Phase Zone Plate # # It is also possible to compute a [near-to-far field transformation](https://meep.readthedocs.io/en/latest/Python_User_Interface/#near-to-far-field-spectra) in cylindrical coordinates. This is demonstrated in this example for a binary-phase [zone plate](https://en.wikipedia.org/wiki/Zone_plate) which is a rotationally-symmetric diffractive lens used to focus a normally-incident planewave to a single spot. # # Using [scalar theory](http://zoneplate.lbl.gov/theory), the radius of the $n$th zone can be computed as: # #
# $$ r_n^2 = n\lambda (f+\frac{n\lambda}{4})$$ #
# # where $n$ is the zone index (1,2,3,...,$N$), $f$ is the focal length, and $\lambda$ is the operating wavelength. The main design variable is the number of zones $N$. The design specifications of the zone plate are similar to the binary-phase grating in [Tutorial/Mode Decomposition/Diffraction Spectrum of a Binary Grating](https://meep.readthedocs.io/en/latest/Python_Tutorials/Mode_Decomposition/#diffraction-spectrum-of-a-binary-grating) with refractive index of 1.5 (glass), $\lambda$ of 0.5 μm, and height of 0.5 μm. The focusing property of the zone plate is verified by the concentration of the electric-field energy density at the focal length of 0.2 mm (which lies *outside* the cell). The planewave is incident from within a glass substrate and spans the entire length of the cell in the radial direction. The cell is surrounded on all sides by PML. A schematic of the simulation geometry for a design with 25 zones and flat-surface termination is shown below. The near-field line monitor is positioned at the edge of the PML. # # ![](https://meep.readthedocs.io/en/latest/images/zone_plate_schematic.png) # In[1]: import math import matplotlib.pyplot as plt import meep as mp import numpy as np resolution_um = 25 pml_um = 1.0 substrate_um = 2.0 padding_um = 2.0 height_um = 0.5 focal_length_um = 200 scan_length_z_um = 100 farfield_resolution_um = 10 pml_layers = [mp.PML(thickness=pml_um)] wavelength_um = 0.5 frequency = 1 / wavelength_um frequench_width = 0.2 * frequency # The number of zones in the zone plate. # Odd-numbered zones impart a π phase shift and # even-numbered zones impart no phase shift. num_zones = 25 # Specify the radius of each zone using the equation # from https://en.wikipedia.org/wiki/Zone_plate. zone_radius_um = np.zeros(num_zones) for n in range(1, num_zones + 1): zone_radius_um[n - 1] = math.sqrt( n * wavelength_um * (focal_length_um + n * wavelength_um / 4) ) size_r_um = zone_radius_um[-1] + padding_um + pml_um size_z_um = pml_um + substrate_um + height_um + padding_um + pml_um cell_size = mp.Vector3(size_r_um, 0, size_z_um) # Specify a (linearly polarized) planewave at normal incidence. sources = [ mp.Source( mp.GaussianSource(frequency, fwidth=frequench_width, is_integrated=True), component=mp.Er, center=mp.Vector3(0.5 * size_r_um, 0, -0.5 * size_z_um + pml_um), size=mp.Vector3(size_r_um), ), mp.Source( mp.GaussianSource(frequency, fwidth=frequench_width, is_integrated=True), component=mp.Ep, center=mp.Vector3(0.5 * size_r_um, 0, -0.5 * size_z_um + pml_um), size=mp.Vector3(size_r_um), amplitude=-1j, ), ] glass = mp.Medium(index=1.5) # Add the substrate. geometry = [ mp.Block( material=glass, size=mp.Vector3(size_r_um, 0, pml_um + substrate_um), center=mp.Vector3( 0.5 * size_r_um, 0, -0.5 * size_z_um + 0.5 * (pml_um + substrate_um) ), ) ] # Add the zone plates starting with the ones with largest radius. for n in range(num_zones - 1, -1, -1): geometry.append( mp.Block( material=glass if n % 2 == 0 else mp.vacuum, size=mp.Vector3(zone_radius_um[n], 0, height_um), center=mp.Vector3( 0.5 * zone_radius_um[n], 0, -0.5 * size_z_um + pml_um + substrate_um + 0.5 * height_um, ), ) ) sim = mp.Simulation( cell_size=cell_size, boundary_layers=pml_layers, resolution=resolution_um, sources=sources, geometry=geometry, dimensions=mp.CYLINDRICAL, m=-1, ) # Add the near-field monitor (must be entirely in air). n2f_monitor = sim.add_near2far( frequency, 0, 1, mp.Near2FarRegion( center=mp.Vector3(0.5 * (size_r_um - pml_um), 0, 0.5 * size_z_um - pml_um), size=mp.Vector3(size_r_um - pml_um, 0, 0), ), mp.Near2FarRegion( center=mp.Vector3( size_r_um - pml_um, 0, 0.5 * size_z_um - pml_um - 0.5 * (height_um + padding_um), ), size=mp.Vector3(0, 0, height_um + padding_um), ), ) # In[2]: fig, ax = plt.subplots() sim.plot2D(ax=ax) # In[3]: # Timestep the fields until they have sufficiently decayed away. sim.run( until_after_sources=mp.stop_when_fields_decayed( 50.0, mp.Er, mp.Vector3(0.5 * size_r_um, 0, 0), 1e-6 ) ) # In[4]: farfields_r = sim.get_farfields( n2f_monitor, farfield_resolution_um, center=mp.Vector3( 0.5 * (size_r_um - pml_um), 0, -0.5 * size_z_um + pml_um + substrate_um + height_um + focal_length_um, ), size=mp.Vector3(size_r_um - pml_um, 0, 0), ) farfields_z = sim.get_farfields( n2f_monitor, farfield_resolution_um, center=mp.Vector3( 0, 0, -0.5 * size_z_um + pml_um + substrate_um + height_um + focal_length_um ), size=mp.Vector3(0, 0, scan_length_z_um), ) # In[5]: intensity_r = ( np.absolute(farfields_r["Ex"]) ** 2 + np.absolute(farfields_r["Ey"]) ** 2 + np.absolute(farfields_r["Ez"]) ** 2 ) intensity_z = ( np.absolute(farfields_z["Ex"]) ** 2 + np.absolute(farfields_z["Ey"]) ** 2 + np.absolute(farfields_z["Ez"]) ** 2 ) # Plot the intensity data and save the result to disk. fig, ax = plt.subplots(ncols=2) ax[0].semilogy(np.linspace(0, size_r_um - pml_um, intensity_r.size), intensity_r, "bo-") ax[0].set_xlim(-2, 20) ax[0].set_xticks(np.arange(0, 25, 5)) ax[0].grid(True, axis="y", which="both", ls="-") ax[0].set_xlabel(r"$r$ coordinate (μm)") ax[0].set_ylabel(r"energy density of far fields, |E|$^2$") ax[1].semilogy( np.linspace( focal_length_um - 0.5 * scan_length_z_um, focal_length_um + 0.5 * scan_length_z_um, intensity_z.size, ), intensity_z, "bo-", ) ax[1].grid(True, axis="y", which="both", ls="-") ax[1].set_xlabel(r"$z$ coordinate (μm)") ax[1].set_ylabel(r"energy density of far fields, |E|$^2$") fig.suptitle(f"binary-phase zone plate with focal length $z$ = {focal_length_um} μm") # Note that the volume specified in `get_farfields` via `center` and `size` is in cylindrical coordinates. These points must therefore lie in the $\phi = 0$ ($rz = xz$) plane. The fields $E$ and $H$ returned by `get_farfields` can be thought of as either cylindrical ($r$,$\phi$,$z$) or Cartesian ($x$,$y$,$z$) coordinates since these are the same in the $\phi = 0$ plane (i.e., $E_r=E_x$ and $E_\phi=E_y$). Also, `get_farfields` tends to gradually *slow down* as the far-field point gets closer to the near-field monitor. This performance degradation is unavoidable and is due to the larger number of $\phi$ integration points required for accurate convergence of the integral involving the Green's function which diverges as the evaluation point approaches the source point. # # Shown below is the far-field energy-density profile around the focal length for both the *r* and *z* coordinate directions for three lens designs with $N$ of 25, 50, and 100. The focus becomes sharper with increasing $N$ due to the enhanced constructive interference of the diffracted beam. As the number of zones $N$ increases, the size of the focal spot (full width at half maximum) at $z = 200$ μm decreases as $1/\sqrt{N}$ (see eq. 17 of the [reference](http://zoneplate.lbl.gov/theory)). This means that doubling the resolution (halving the spot width) requires quadrupling the number of zones. # # ![](https://meep.readthedocs.io/en/latest/images/zone_plate_farfield.png)