Note
Go to the end to download the full example code.
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(...).

import numpy as np
import matplotlib.pyplot as plt
from PyMieSim import (
Experiment,
Gaussian as SingleGaussian,
GaussianSet,
Photodiode as SinglePhotodiode,
PhotodiodeSet,
PolarizationSet,
PolarizationState,
Sphere as SingleSphere,
SphereSet,
ureg,
)
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 = Experiment(
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.534 seconds)