benvial/gyptis

View on GitHub
examples/scattering/plot_silver_core_shell.py

Summary

Maintainability
A
1 hr
Test Coverage
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Author: Benjamin Vial
# This file is part of gyptis
# Version: 1.0.2
# License: MIT
# See the documentation at gyptis.gitlab.io
"""
Core shell nanorod
==================

Scattering by a dielectric cylinder coated with silver.
"""


# sphinx_gallery_thumbnail_number = 2

import matplotlib.pyplot as plt
import numpy as np

import gyptis as gy
import gyptis.utils.data_download as dd
from gyptis import c, pi
from gyptis.utils import adaptive_sampler

##############################################################################
# Reference results are taken from :cite:p:`Jandieri2015`.
# We first define a function for the Drude Lorentz model of silver permittivity.


def epsilon_silver(omega):
    eps_inf = 3.91
    omega_D = 13420e12
    gamma_D = 84e12
    Omega_L = 6870e12
    Gamma_L = 12340e12
    delta_eps = 0.76
    epsilon = (
        eps_inf
        - omega_D**2 / (omega * (omega + 1j * gamma_D))
        - delta_eps
        * Omega_L**2
        / ((omega**2 - Omega_L**2) + 1j * Gamma_L * omega)
    )
    return np.conj(epsilon)


wavelength_min = 250
wavelength_max = 800
wl = np.linspace(wavelength_min, wavelength_max, 100)
omega = 2 * pi * c / (wl * 1e-9)
epsAg = epsilon_silver(omega)
fig, ax = plt.subplots(1, 2)
ax[0].plot(wl, epsAg.real, c="#7b6eaf")
ax[1].plot(wl, epsAg.imag, c="#c63c71")
ax[0].set_xlabel("wavelength (nm)")
ax[1].set_xlabel("wavelength (nm)")
ax[0].set_ylabel(r"Re $\varepsilon$")
ax[1].set_ylabel(r"Im $\varepsilon$")
plt.suptitle("silver permittivity")
plt.tight_layout()

##############################################################################
# Now we create the geometry and mesh

pmesh = 20
degree = 2
wavelength = 452
eps_core = 2


def create_geometry(wavelength, pml_width):
    R1 = 60
    R2 = 30
    Rcalc = 2 * R1
    lmin = wavelength / pmesh
    omega = 2 * pi * c / (wavelength * 1e-9)
    epsAg = epsilon_silver(omega)

    nAg = abs(epsAg.real) ** 0.5
    ncore = abs(eps_core.real) ** 0.5

    lbox = Rcalc * 2 * 1.1
    geom = gy.BoxPML(
        dim=2,
        box_size=(lbox, lbox),
        pml_width=(pml_width, pml_width),
        Rcalc=Rcalc,
    )
    box = geom.box
    shell = geom.add_circle(0, 0, 0, R1)
    out = geom.fragment(shell, box)
    box = out[1:3]
    shell = out[0]
    core = geom.add_circle(0, 0, 0, R2)
    core, shell = geom.fragment(core, shell)
    geom.add_physical(box, "box")
    geom.add_physical(core, "core")
    geom.add_physical(shell, "shell")
    [geom.set_size(pml, lmin * 1) for pml in geom.pmls]
    geom.set_size("box", lmin)
    geom.set_size("core", 0.5 * lmin / ncore)
    geom.set_size("shell", 0.5 * lmin / nAg)
    geom.build()
    return geom


geom = create_geometry(wavelength, pml_width=wavelength)


##############################################################################
# Define the incident plane wave and materials

pw = gy.PlaneWave(
    wavelength=wavelength, angle=0, dim=2, domain=geom.mesh, degree=degree
)
omega = 2 * pi * c / (wavelength * 1e-9)
epsilon = dict(box=1, core=eps_core, shell=epsilon_silver(omega))


##############################################################################
# Scattering problem

s = gy.Scattering(
    geom,
    epsilon,
    source=pw,
    degree=degree,
    polarization="TE",
)
s.solve()
s.plot_field()
geom_lines = geom.plot_subdomains()
plt.xlabel(r"$x$ (nm)")
plt.ylabel(r"$y$ (nm)")
plt.title(r"Re $H_z$")
plt.tight_layout()

##############################################################################
# Compute cross sections and check energy conservation (optical theorem)

cs = s.get_cross_sections()
print("cross sections")
print("--------------")
for k, v in cs.items():
    print(f"{k} {v:.3f}nm")
print("energy balance", (cs["scattering"] + cs["absorption"]) / cs["extinction"])
assert np.allclose(cs["extinction"], cs["scattering"] + cs["absorption"], rtol=1e-12)


##############################################################################
# Compute spectra using adaptive sampling. The function must return a scalar
# (which will be monitored by the sampler) as its first output.


@adaptive_sampler(max_bend=10, max_z_rel=0.001, max_df=0.02)
def cs_vs_wl(wavelength):
    geom = create_geometry(wavelength, pml_width=wavelength)
    pw = gy.PlaneWave(wavelength=wavelength, angle=0, dim=2, domain=geom.mesh, degree=2)
    omega = 2 * pi * c / (wavelength * 1e-9)
    epsilon = dict(box=1, core=2, shell=epsilon_silver(omega))
    s = gy.Scattering(
        geom,
        epsilon,
        source=pw,
        degree=2,
        polarization="TE",
    )
    s.solve()
    cs = s.get_cross_sections()
    return cs["absorption"], cs


wl = np.linspace(wavelength_min, wavelength_max, 50)
wla, out = cs_vs_wl(wl)
cs = [_[1] for _ in out]
scs = np.array([d["scattering"] for d in cs])
acs = np.array([d["absorption"] for d in cs])
ecs = np.array([d["extinction"] for d in cs])
balance = (scs + acs) / ecs

##############################################################################
# Plot energy balance


plt.figure()
plt.plot(
    wla,
    balance,
    c="#df6482",
)
plt.ylabel("energy balance")
plt.xlabel("wavelength (nm)")
plt.xlim(wavelength_min, wavelength_max)
plt.tight_layout()
plt.show()

##############################################################################
# Plot and comparison with benchmark

scs_file = dd.download_example_data(
    data_file_name="scs_r2_30nm.csv",
    example_dir="scattering",
)

acs_file = dd.download_example_data(
    data_file_name="acs_r2_30nm.csv",
    example_dir="scattering",
)

benchmark_scs = np.loadtxt(scs_file, delimiter=",")
benchmark_acs = np.loadtxt(acs_file, delimiter=",")


plt.figure()
plt.plot(
    benchmark_scs[:, 0],
    benchmark_scs[:, 1],
    "-",
    alpha=0.5,
    lw=2,
    c="#df6482",
    label="scattering reference",
)
plt.plot(wla, scs, c="#df6482", label="scattering gyptis")
plt.plot(
    benchmark_acs[:, 0],
    benchmark_acs[:, 1],
    "-",
    alpha=0.5,
    lw=2,
    c="#6e8cd0",
    label="absorption reference",
)
plt.plot(wla, acs, c="#6e8cd0", label="absorption gyptis")
plt.xlabel("wavelength (nm)")
plt.ylabel("cross sections (nm)")
plt.legend()
plt.xlim(wavelength_min, wavelength_max)
plt.ylim(0)
plt.tight_layout()