Experiment Photodiode Angular Weights#

This example demonstrates how to model a large lens with NA=1.2 oriented at phi=45 degree and split by a mirror plane. Only one side of the lens is kept by building explicit angular_weights arrays for a sequential PhotodiodeSet experiment.

At the moment, experiment-side angular weights are supported only through PhotodiodeSet.build_sequential(...).

NA=1.2 lens at phi=45 degree split by a mirror, Full NA=1.2 lens, Mirror keeps one side, Coupling after mirror split
import numpy as np
import matplotlib.pyplot as plt

from PyMieSim.units import ureg
from PyMieSim.polarization import PolarizationState
from PyMieSim.single.source import Gaussian as SingleGaussian
from PyMieSim.single.scatterer import Sphere as SingleSphere
from PyMieSim.single.detector import Photodiode as SinglePhotodiode

from PyMieSim.experiment import Setup
from PyMieSim.experiment.detector_set import PhotodiodeSet
from PyMieSim.experiment.scatterer_set import SphereSet
from PyMieSim.experiment.source_set import GaussianSet
from PyMieSim.experiment.polarization_set import PolarizationSet


sampling = 1400
detector_numerical_aperture = 1.2
detector_phi_offset = 45 * ureg.degree
detector_gamma_offset = 0 * ureg.degree
detector_medium = 1.5


def normalize(vector: np.ndarray) -> np.ndarray:
    return vector / np.linalg.norm(vector)

# Build one reference single detector so the mesh ordering can be used to create
# explicit experiment-side angular weight vectors.
single_source = SingleGaussian(
    wavelength=1000 * ureg.nanometer,
    polarization=PolarizationState(angle=0 * ureg.degree),
    optical_power=1 * ureg.watt,
    numerical_aperture=0.2,
)

single_scatterer = SingleSphere(
    diameter=1000 * ureg.nanometer,
    material=1.5 + 0.5j,
    medium=1.0,
)

reference_detector = SinglePhotodiode(
    numerical_aperture=detector_numerical_aperture,
    phi_offset=detector_phi_offset,
    gamma_offset=detector_gamma_offset,
    sampling=sampling,
    medium=detector_medium,
)

single_scatterer.init(single_source)
reference_detector.initialize_mesh(single_scatterer)

x_coordinates = np.asarray(reference_detector.mesh.cartesian.x.magnitude)
y_coordinates = np.asarray(reference_detector.mesh.cartesian.y.magnitude)
z_coordinates = np.asarray(reference_detector.mesh.cartesian.z.magnitude)
mesh_coordinates = np.column_stack([x_coordinates, y_coordinates, z_coordinates])

# Build a detector-local basis. The mirror plane contains the collection axis
# and the local horizontal direction, so the sign of the local vertical
# projection selects which side of the split lens is kept.
collection_axis = normalize(mesh_coordinates.mean(axis=0))

reference_up = np.array([0.0, 0.0, 1.0])
if np.abs(np.dot(collection_axis, reference_up)) > 0.95:
    reference_up = np.array([0.0, 1.0, 0.0])

local_horizontal = normalize(np.cross(reference_up, collection_axis))
local_vertical = normalize(np.cross(collection_axis, local_horizontal))
local_vertical_coordinates = mesh_coordinates @ local_vertical

full_weights = np.ones(sampling, dtype=np.complex128)
mirror_kept_side = np.zeros(sampling, dtype=np.complex128)
mirror_kept_side[local_vertical_coordinates >= 0.0] = 1.0
blocked_weights = np.zeros(sampling, dtype=np.complex128)

source_set = GaussianSet.build_sequential(
    target_size=3,
    wavelength=[1000, 1000, 1000] * ureg.nanometer,
    polarization=PolarizationSet(angles=[0, 0, 0] * ureg.degree),
    optical_power=[1, 1, 1] * ureg.watt,
    numerical_aperture=[0.2, 0.2, 0.2],
)

scatterer_set = SphereSet.build_sequential(
    target_size=3,
    diameter=[1000, 1000, 1000] * ureg.nanometer,
    material=[1.5 + 0.5j, 1.5 + 0.5j, 1.5 + 0.5j],
    medium=[1.0, 1.0, 1.0],
)

detector_set = PhotodiodeSet.build_sequential(
    target_size=3,
    numerical_aperture=[detector_numerical_aperture] * 3,
    phi_offset=[45, 45, 45] * ureg.degree,
    gamma_offset=[0, 0, 0] * ureg.degree,
    sampling=[sampling, sampling, sampling],
    polarization_filter=[0, 0, 0] * ureg.degree,
    medium=[detector_medium, detector_medium, detector_medium],
    angular_weights=[
        full_weights,
        mirror_kept_side,
        blocked_weights,
    ],
)

experiment = Setup(
    scatterer_set=scatterer_set,
    source_set=source_set,
    detector_set=detector_set,
)

coupling = experiment.get_sequential("coupling")

figure = plt.figure(figsize=(14, 4.5))

axis_full = figure.add_subplot(1, 3, 1, projection="3d")
axis_full.scatter(x_coordinates, y_coordinates, z_coordinates, s=10, c="#4c72b0")
axis_full.set(title="Full NA=1.2 lens", xlabel="x", ylabel="y", zlabel="z")

axis_half = figure.add_subplot(1, 3, 2, projection="3d")
active_half = np.abs(mirror_kept_side) > 0.0
axis_half.scatter(
    x_coordinates[active_half],
    y_coordinates[active_half],
    z_coordinates[active_half],
    s=10,
    c="#4c72b0",
)
axis_half.scatter(
    x_coordinates[~active_half],
    y_coordinates[~active_half],
    z_coordinates[~active_half],
    s=10,
    c="#d9d9d9",
)
axis_half.set(title="Mirror keeps one side", xlabel="x", ylabel="y", zlabel="z")

for axis in (axis_full, axis_half):
    axis.set_xlim(-1.0, 1.0)
    axis.set_ylim(-1.0, 1.0)
    axis.set_zlim(-1.0, 1.0)
    axis.view_init(elev=22, azim=38)

axis_bar = figure.add_subplot(1, 3, 3)
axis_bar.bar(
    ["full", "kept side", "blocked"],
    coupling * 1e6,
    color=["#4c72b0", "#dd8452", "#9a9a9a"],
)
axis_bar.set(
    title="Coupling after mirror split",
    ylabel="Coupling [$\\mu$W]",
)

figure.suptitle("NA=1.2 lens at phi=45 degree split by a mirror")
figure.tight_layout()

if plt.get_backend().lower() != "agg":
    plt.show()

Total running time of the script: (0 minutes 0.442 seconds)

Gallery generated by Sphinx-Gallery