"""Near- and far-field electromagnetic fields for a homogeneous sphere.
This module evaluates complex phasor electric and magnetic fields for a sphere
embedded in a uniform medium.
Main entry points
-----------------
Use these for most workflows:
- ``e_near(...)``: electric field in spherical components ``[E_r, E_theta, E_phi]``.
- ``h_near(...)``: magnetic field in spherical components ``[H_r, H_theta, H_phi]``.
- ``eh_near(...)``: both electric and magnetic fields in one call.
- ``e_near_cartesian(...)`` / ``h_near_cartesian(...)`` / ``eh_near_cartesian(...)``:
same near-field calculations in Cartesian components ``[Fx, Fy, Fz]``.
- ``e_far(...)``: scattered far-field electric components.
Quick examples
--------------
Evaluate the near electric field on a ring of points:
>>> import numpy as np
>>> import miepython.field as fields
>>> theta = np.linspace(0.0, np.pi, 181)
>>> phi = np.zeros_like(theta)
>>> r = np.full_like(theta, 1.5) # same length units as lambda0 and d_sphere
>>> E = fields.e_near(
... lambda0=1.0,
... d_sphere=1.0,
... m_sphere=1.5 + 0.0j,
... n_env=1.0,
... r=r,
... theta=theta,
... phi=phi,
... )
>>> E.shape
(3, 181)
Evaluate both fields in Cartesian coordinates on a 2D x-z slice:
>>> u = np.linspace(-1.5, 1.5, 101)
>>> X, Z = np.meshgrid(u, u, indexing="xy")
>>> E_xyz, H_xyz = fields.eh_near_cartesian(
... lambda0=1.0,
... d_sphere=1.0,
... m_sphere=1.5 + 0.0j,
... n_env=1.0,
... x=X,
... y=np.zeros_like(X),
... z=Z,
... )
>>> E_xyz.shape, H_xyz.shape
((3, 101, 101), (3, 101, 101))
Reuse precomputed Mie coefficients for repeated field evaluations:
>>> import miepython as mie
>>> m_rel = (1.5 + 0.0j) / 1.0
>>> x_size = np.pi * 1.0 * 1.0 / 1.0
>>> a, b, c, d = mie.coefficients(m_rel, x_size, internal=True)
>>> abcd = np.array([a, b, c, d])
>>> E2, H2 = fields.eh_near(
... 1.0, 1.0, 1.5 + 0.0j, 1.0, r, theta, phi, abcd=abcd
... )
Conventions
-----------
- Phasor time dependence: ``exp(-i * omega * t)``.
- Incident plane wave: propagation along ``+z``, ``E`` along ``+x``, ``H`` along ``+y``,
incident amplitude ``E0 = 1``.
- Spherical coordinates: ``theta`` from ``+z`` and ``phi`` from ``+x`` toward ``+y``.
- ``lambda0`` is vacuum wavelength.
- ``n_env`` is the surrounding-medium index and ``m_sphere`` is the sphere index;
relative index is ``m_rel = m_sphere / n_env``.
- Size parameter is ``x = pi * d_sphere * n_env / lambda0``.
- Near field definition:
outside the sphere, returned field is incident plus scattered;
inside the sphere, returned field is the internal field.
- Non-magnetic materials are assumed (relative permeability ``mu_r = 1``).
"""
import numpy as np
from scipy.special import factorial2, spherical_jn
from ._backend import D_calc, pi_tau
from .bessel import d_riccati_bessel_h1, spherical_h1
from .core import S1_S2, coefficients
from .util import spherical_vector_to_cartesian
__all__ = (
"e_near",
"h_near",
"eh_near",
"e_near_cartesian",
"h_near_cartesian",
"eh_near_cartesian",
"e_far",
)
def _sum_two_scaled_terms(scale, coeff1, values1, scale1, coeff2, values2, scale2):
"""Return ``sum(scale * (scale1*coeff1*values1 + scale2*coeff2*values2))``."""
return np.sum(scale * (scale1 * coeff1 * values1 + scale2 * coeff2 * values2))
def _vsh_components_base(n_terms, lambda0, d_sphere, m_index, r, theta):
"""Compute shared VSH base components for one spatial point.
Args:
n_terms (int): Number of multipole terms.
lambda0 (float): Vacuum wavelength.
d_sphere (float): Sphere diameter.
m_index (complex): Refractive index at the evaluation point.
r (float): Radial coordinate.
theta (float): Polar angle in radians.
Returns:
tuple[ndarray, ndarray, ndarray, ndarray, ndarray]:
``(M_theta_base, M_phi_base, N_r_base, N_theta_base, N_phi_base)``
for multipole orders ``1..n_terms``.
"""
mu = float(np.cos(theta))
if mu >= 1.0:
mu = 0.999999
elif mu <= -1.0:
mu = -0.999999
pi = np.empty(n_terms)
tau = np.empty(n_terms)
pi_tau(mu, pi, tau)
n_int = np.arange(1, n_terms + 1, dtype=np.int64)
n_arr = n_int.astype(np.float64)
rho = 2 * np.pi * m_index * r / lambda0
kr = 2 * np.pi * r / lambda0
inside = r < d_sphere / 2
if inside:
jn = spherical_jn(n_int, rho)
m_factor = jn
if np.abs(rho) < 0.01:
rho_pow = rho ** np.arange(0, n_terms)
denom = factorial2(2 * n_int + 1)
n_factor1 = rho_pow / denom
n_factor2 = (n_arr + 1.0) * rho_pow / denom
else:
d_vals = D_calc(np.complex128(m_index), float(kr), n_terms + 1)[:n_terms]
n_factor1 = jn / rho
n_factor2 = jn * d_vals
else:
h1 = spherical_h1(n_int, rho)
m_factor = h1
n_factor1 = h1 / rho
n_factor2 = d_riccati_bessel_h1(n_int, rho) / rho
sin_theta = np.sin(theta)
M_theta_base = pi * m_factor
M_phi_base = tau * m_factor
N_r_base = n_arr * (n_arr + 1.0) * sin_theta * pi * n_factor1
N_theta_base = tau * n_factor2
N_phi_base = pi * n_factor2
return M_theta_base, M_phi_base, N_r_base, N_theta_base, N_phi_base
[docs]
def e_far(lambda0, d_sphere, m_sphere, n_env, r, theta, phi):
"""Evaluate the scattered electric far field.
Args:
lambda0 (float): Vacuum wavelength.
d_sphere (float): Sphere diameter.
m_sphere (complex): Sphere refractive index.
n_env (float): Refractive index of the surrounding medium.
r (float or ndarray): Radial observation distance.
theta (float or ndarray): Polar angle in radians.
phi (float or ndarray): Azimuth angle in radians.
Returns:
ndarray: Complex spherical components ``[E_r, E_theta, E_phi]`` with
shape ``(3, ...)`` following broadcasted input shape.
"""
x = np.pi * d_sphere * n_env / lambda0
m_rel = m_sphere / n_env
jkr = 1j * 2 * np.pi * n_env * r / lambda0
amp = np.exp(jkr) / (-jkr)
S1, S2 = S1_S2(m_rel, x, np.cos(theta), norm="wiscombe")
E_r = np.zeros_like(S1, dtype=complex)
E_theta = S2 * amp * np.cos(phi)
E_phi = S1 * amp * np.sin(phi)
E_theta = np.conjugate(-E_theta)
E_phi = np.conjugate(-E_phi)
return np.array([E_r, E_theta, E_phi])
def _incident_e_spherical(lambda0, n_env, r, theta, phi, amplitude=1.0):
"""Return incident electric plane-wave components in spherical coordinates.
Args:
lambda0 (float): Vacuum wavelength.
n_env (float): Refractive index of the surrounding medium.
r (float): Radial coordinate.
theta (float): Polar angle in radians.
phi (float): Azimuth angle in radians.
amplitude (complex): Incident electric field amplitude.
Returns:
ndarray: Complex spherical vector ``[E_r, E_theta, E_phi]``.
"""
k = 2 * np.pi * n_env / lambda0
phase = np.exp(1j * k * r * np.cos(theta))
amp = amplitude * phase
e_r = amp * np.sin(theta) * np.cos(phi)
e_theta = amp * np.cos(theta) * np.cos(phi)
e_phi = -amp * np.sin(phi)
return np.array([e_r, e_theta, e_phi])
def _incident_h_spherical(lambda0, n_env, r, theta, phi, amplitude=1.0):
"""Return incident magnetic plane-wave components in spherical coordinates.
Args:
lambda0 (float): Vacuum wavelength.
n_env (float): Refractive index of the surrounding medium.
r (float): Radial coordinate.
theta (float): Polar angle in radians.
phi (float): Azimuth angle in radians.
amplitude (complex): Incident magnetic field amplitude in normalized units.
Returns:
ndarray: Complex spherical vector ``[H_r, H_theta, H_phi]``.
"""
k = 2 * np.pi * n_env / lambda0
phase = np.exp(1j * k * r * np.cos(theta))
amp = amplitude * phase
h_r = amp * np.sin(theta) * np.sin(phi)
h_theta = amp * np.cos(theta) * np.sin(phi)
h_phi = amp * np.cos(phi)
return np.array([h_r, h_theta, h_phi])
def _coefficients_abcd(lambda0, d_sphere, m_sphere, n_env, n_pole):
"""Compute Mie coefficients with consistent medium scaling.
Args:
lambda0 (float): Vacuum wavelength.
d_sphere (float): Sphere diameter.
m_sphere (complex): Sphere refractive index.
n_env (float): Refractive index of the surrounding medium.
n_pole (int): Requested multipole order. ``0`` means automatic truncation.
Returns:
ndarray: Coefficients packed as ``[a, b, c, d]``.
"""
x = np.pi * d_sphere * n_env / lambda0
m_rel = m_sphere / n_env
a, b, c, d = coefficients(m_rel, x, n_pole=n_pole, internal=True)
return np.array([a, b, c, d])
def _vectorized_field_eval(evaluator, r, theta, phi):
"""Evaluate a spherical-field callback on scalar or broadcasted inputs.
Args:
evaluator (callable): Callable that accepts scalar ``(r, theta, phi)``
and returns a complex 3-vector.
r (float or ndarray): Radial coordinate(s).
theta (float or ndarray): Polar angle(s) in radians.
phi (float or ndarray): Azimuth angle(s) in radians.
Returns:
ndarray: Complex array with shape ``(3, ...)``.
"""
rr, tt, pp = np.broadcast_arrays(np.asarray(r), np.asarray(theta), np.asarray(phi))
if rr.ndim == 0:
return evaluator(float(rr), float(tt), float(pp))
out = np.empty((3,) + rr.shape, dtype=complex)
for idx in np.ndindex(rr.shape):
out[(slice(None),) + idx] = evaluator(float(rr[idx]), float(tt[idx]), float(pp[idx]))
return out
def _vectorized_field_pair_eval(evaluator, r, theta, phi):
"""Evaluate an ``(E, H)`` callback on scalar or broadcasted inputs.
Args:
evaluator (callable): Callable that accepts scalar ``(r, theta, phi)``
and returns a tuple ``(E, H)``, each a complex 3-vector.
r (float or ndarray): Radial coordinate(s).
theta (float or ndarray): Polar angle(s) in radians.
phi (float or ndarray): Azimuth angle(s) in radians.
Returns:
tuple[ndarray, ndarray]: Tuple ``(E, H)`` with each array shaped
``(3, ...)``.
"""
rr, tt, pp = np.broadcast_arrays(np.asarray(r), np.asarray(theta), np.asarray(phi))
if rr.ndim == 0:
return evaluator(float(rr), float(tt), float(pp))
e_out = np.empty((3,) + rr.shape, dtype=complex)
h_out = np.empty((3,) + rr.shape, dtype=complex)
for idx in np.ndindex(rr.shape):
e_val, h_val = evaluator(float(rr[idx]), float(tt[idx]), float(pp[idx]))
e_out[(slice(None),) + idx] = e_val
h_out[(slice(None),) + idx] = h_val
return e_out, h_out
def _cartesian_to_spherical_safe(x, y, z):
"""Convert Cartesian coordinates to spherical coordinates safely.
Args:
x (float or ndarray): Cartesian x coordinate(s).
y (float or ndarray): Cartesian y coordinate(s).
z (float or ndarray): Cartesian z coordinate(s).
Returns:
tuple[ndarray, ndarray, ndarray]: ``(r, theta, phi)`` arrays after
broadcasting the inputs.
"""
xx, yy, zz = np.broadcast_arrays(np.asarray(x), np.asarray(y), np.asarray(z))
rr = np.sqrt(xx**2 + yy**2 + zz**2)
with np.errstate(divide="ignore", invalid="ignore"):
cos_theta = np.where(rr == 0, 1.0, np.clip(zz / rr, -1.0, 1.0))
theta = np.arccos(cos_theta)
phi = np.arctan2(yy, xx)
return rr, theta, phi
def _spherical_components_to_cartesian(field_sph, r, theta, phi):
"""Convert spherical vector components to Cartesian components.
Args:
field_sph (ndarray): Spherical components ``[F_r, F_theta, F_phi]``.
r (float or ndarray): Radial coordinate(s).
theta (float or ndarray): Polar angle(s) in radians.
phi (float or ndarray): Azimuth angle(s) in radians.
Returns:
ndarray: Cartesian components ``[F_x, F_y, F_z]``.
"""
fx, fy, fz = spherical_vector_to_cartesian(field_sph[0], field_sph[1], field_sph[2], r, theta, phi)
return np.array([fx, fy, fz])
def _e_near_abcd(abcd, lambda0, d_sphere, m_sphere, n_env, r, theta, phi, include_incident):
"""Evaluate near-field electric components with precomputed coefficients.
Args:
abcd (ndarray): Mie coefficients ``[a, b, c, d]``.
lambda0 (float): Vacuum wavelength.
d_sphere (float): Sphere diameter.
m_sphere (complex): Sphere refractive index.
n_env (float): Refractive index of the surrounding medium.
r (float): Radial coordinate.
theta (float): Polar angle in radians.
phi (float): Azimuth angle in radians.
include_incident (bool): Include incident field outside the sphere.
Returns:
ndarray: Spherical electric components ``[E_r, E_theta, E_phi]``.
"""
a, b, c, d = abcd
N = len(a)
nn = np.arange(1, N + 1)
scale = 1j**nn * (2 * nn + 1) / ((nn + 1) * nn)
inside = r < d_sphere / 2
# miepython coefficients follow the n-ik convention and are conjugated
# internally; use conjugated sphere index so internal fields are consistent.
m_index = np.conjugate(m_sphere) if inside else n_env
M_the_base, M_phi_base, N_r_base, N_the_base, N_phi_base = _vsh_components_base(
N, lambda0, d_sphere, m_index, r, theta
)
cos_phi = np.cos(phi)
sin_phi = np.sin(phi)
M_rad = np.zeros(N, dtype=np.complex128)
M_the = cos_phi * M_the_base
M_phi = -sin_phi * M_phi_base
N_rad = cos_phi * N_r_base
N_the = cos_phi * N_the_base
N_phi = -sin_phi * N_phi_base
if inside:
E_rad = _sum_two_scaled_terms(scale, c, M_rad, 1.0 + 0.0j, d, N_rad, -1.0j)
E_the = _sum_two_scaled_terms(scale, c, M_the, 1.0 + 0.0j, d, N_the, -1.0j)
E_phi = _sum_two_scaled_terms(scale, c, M_phi, 1.0 + 0.0j, d, N_phi, -1.0j)
else:
E_rad = _sum_two_scaled_terms(scale, a, N_rad, 1.0j, b, M_rad, -1.0 + 0.0j)
E_the = _sum_two_scaled_terms(scale, a, N_the, 1.0j, b, M_the, -1.0 + 0.0j)
E_phi = _sum_two_scaled_terms(scale, a, N_phi, 1.0j, b, M_phi, -1.0 + 0.0j)
if include_incident:
Ei_rad, Ei_the, Ei_phi = _incident_e_spherical(lambda0, n_env, r, theta, phi)
E_rad += Ei_rad
E_the += Ei_the
E_phi += Ei_phi
return np.array([E_rad, E_the, E_phi])
def _h_near_abcd(abcd, lambda0, d_sphere, m_sphere, n_env, r, theta, phi, include_incident):
"""Evaluate near-field magnetic components with precomputed coefficients.
Args:
abcd (ndarray): Mie coefficients ``[a, b, c, d]``.
lambda0 (float): Vacuum wavelength.
d_sphere (float): Sphere diameter.
m_sphere (complex): Sphere refractive index.
n_env (float): Refractive index of the surrounding medium.
r (float): Radial coordinate.
theta (float): Polar angle in radians.
phi (float): Azimuth angle in radians.
include_incident (bool): Include incident field outside the sphere.
Returns:
ndarray: Spherical magnetic components ``[H_r, H_theta, H_phi]``.
"""
a, b, c, d = abcd
N = len(a)
nn = np.arange(1, N + 1)
scale = 1j**nn * (2 * nn + 1) / ((nn + 1) * nn)
inside = r < d_sphere / 2
m_index = np.conjugate(m_sphere) if inside else n_env
M_the_base, M_phi_base, N_r_base, N_the_base, N_phi_base = _vsh_components_base(
N, lambda0, d_sphere, m_index, r, theta
)
cos_phi = np.cos(phi)
sin_phi = np.sin(phi)
M_rad = np.zeros(N, dtype=np.complex128)
M_the = -sin_phi * M_the_base
M_phi = -cos_phi * M_phi_base
N_rad = sin_phi * N_r_base
N_the = sin_phi * N_the_base
N_phi = cos_phi * N_phi_base
if inside:
m_rel = np.conjugate(m_sphere / n_env)
H_rad = m_rel * _sum_two_scaled_terms(scale, d, M_rad, -1.0 + 0.0j, c, N_rad, -1.0j)
H_the = m_rel * _sum_two_scaled_terms(scale, d, M_the, -1.0 + 0.0j, c, N_the, -1.0j)
H_phi = m_rel * _sum_two_scaled_terms(scale, d, M_phi, -1.0 + 0.0j, c, N_phi, -1.0j)
else:
H_rad = _sum_two_scaled_terms(scale, b, N_rad, 1.0j, a, M_rad, 1.0 + 0.0j)
H_the = _sum_two_scaled_terms(scale, b, N_the, 1.0j, a, M_the, 1.0 + 0.0j)
H_phi = _sum_two_scaled_terms(scale, b, N_phi, 1.0j, a, M_phi, 1.0 + 0.0j)
if include_incident:
Hi_rad, Hi_the, Hi_phi = _incident_h_spherical(lambda0, n_env, r, theta, phi)
H_rad += Hi_rad
H_the += Hi_the
H_phi += Hi_phi
return np.array([H_rad, H_the, H_phi])
def _eh_near_abcd(abcd, lambda0, d_sphere, m_sphere, n_env, r, theta, phi, include_incident):
"""Evaluate electric and magnetic near fields with shared VSH work."""
a, b, c, d = abcd
N = len(a)
nn = np.arange(1, N + 1)
scale = 1j**nn * (2 * nn + 1) / ((nn + 1) * nn)
inside = r < d_sphere / 2
m_index = np.conjugate(m_sphere) if inside else n_env
M_the_base, M_phi_base, N_r_base, N_the_base, N_phi_base = _vsh_components_base(
N, lambda0, d_sphere, m_index, r, theta
)
cos_phi = np.cos(phi)
sin_phi = np.sin(phi)
M_odd_rad = np.zeros(N, dtype=np.complex128)
M_odd_the = cos_phi * M_the_base
M_odd_phi = -sin_phi * M_phi_base
N_even_rad = cos_phi * N_r_base
N_even_the = cos_phi * N_the_base
N_even_phi = -sin_phi * N_phi_base
M_even_rad = np.zeros(N, dtype=np.complex128)
M_even_the = -sin_phi * M_the_base
M_even_phi = -cos_phi * M_phi_base
N_odd_rad = sin_phi * N_r_base
N_odd_the = sin_phi * N_the_base
N_odd_phi = cos_phi * N_phi_base
if inside:
e_rad = _sum_two_scaled_terms(scale, c, M_odd_rad, 1.0 + 0.0j, d, N_even_rad, -1.0j)
e_the = _sum_two_scaled_terms(scale, c, M_odd_the, 1.0 + 0.0j, d, N_even_the, -1.0j)
e_phi = _sum_two_scaled_terms(scale, c, M_odd_phi, 1.0 + 0.0j, d, N_even_phi, -1.0j)
m_rel = np.conjugate(m_sphere / n_env)
h_rad = m_rel * _sum_two_scaled_terms(scale, d, M_even_rad, -1.0 + 0.0j, c, N_odd_rad, -1.0j)
h_the = m_rel * _sum_two_scaled_terms(scale, d, M_even_the, -1.0 + 0.0j, c, N_odd_the, -1.0j)
h_phi = m_rel * _sum_two_scaled_terms(scale, d, M_even_phi, -1.0 + 0.0j, c, N_odd_phi, -1.0j)
else:
e_rad = _sum_two_scaled_terms(scale, a, N_even_rad, 1.0j, b, M_odd_rad, -1.0 + 0.0j)
e_the = _sum_two_scaled_terms(scale, a, N_even_the, 1.0j, b, M_odd_the, -1.0 + 0.0j)
e_phi = _sum_two_scaled_terms(scale, a, N_even_phi, 1.0j, b, M_odd_phi, -1.0 + 0.0j)
h_rad = _sum_two_scaled_terms(scale, b, N_odd_rad, 1.0j, a, M_even_rad, 1.0 + 0.0j)
h_the = _sum_two_scaled_terms(scale, b, N_odd_the, 1.0j, a, M_even_the, 1.0 + 0.0j)
h_phi = _sum_two_scaled_terms(scale, b, N_odd_phi, 1.0j, a, M_even_phi, 1.0 + 0.0j)
if include_incident:
e_i = _incident_e_spherical(lambda0, n_env, r, theta, phi)
h_i = _incident_h_spherical(lambda0, n_env, r, theta, phi)
e_rad += e_i[0]
e_the += e_i[1]
e_phi += e_i[2]
h_rad += h_i[0]
h_the += h_i[1]
h_phi += h_i[2]
return np.array([e_rad, e_the, e_phi]), np.array([h_rad, h_the, h_phi])
[docs]
def e_near(lambda0, d_sphere, m_sphere, n_env, r, theta, phi, include_incident=True, n_pole=0, abcd=None):
"""Calculate the electric field in and around a sphere.
Args:
lambda0 (float): Vacuum wavelength.
d_sphere (float): Sphere diameter.
m_sphere (complex): Sphere refractive index.
n_env (float): Refractive index of the surrounding medium.
r (float or ndarray): Radial coordinate(s).
theta (float or ndarray): Polar angle(s) in radians.
phi (float or ndarray): Azimuth angle(s) in radians.
include_incident (bool): Include incident field for points outside sphere.
n_pole (int): Requested multipole order. ``0`` means automatic truncation.
abcd (ndarray or None): Optional precomputed coefficients ``[a, b, c, d]``.
If provided, ``n_pole`` is ignored.
Returns:
ndarray: Spherical electric components ``[E_r, E_theta, E_phi]`` with
shape ``(3, ...)``.
"""
if abcd is None:
abcd = _coefficients_abcd(lambda0, d_sphere, m_sphere, n_env, n_pole)
def evaluator(rr, tt, pp):
return _e_near_abcd(abcd, lambda0, d_sphere, m_sphere, n_env, rr, tt, pp, include_incident)
return _vectorized_field_eval(evaluator, r, theta, phi)
[docs]
def h_near(lambda0, d_sphere, m_sphere, n_env, r, theta, phi, include_incident=True, n_pole=0, abcd=None):
"""Calculate the magnetic field in and around a sphere.
Args:
lambda0 (float): Vacuum wavelength.
d_sphere (float): Sphere diameter.
m_sphere (complex): Sphere refractive index.
n_env (float): Refractive index of the surrounding medium.
r (float or ndarray): Radial coordinate(s).
theta (float or ndarray): Polar angle(s) in radians.
phi (float or ndarray): Azimuth angle(s) in radians.
include_incident (bool): Include incident field for points outside sphere.
n_pole (int): Requested multipole order. ``0`` means automatic truncation.
abcd (ndarray or None): Optional precomputed coefficients ``[a, b, c, d]``.
If provided, ``n_pole`` is ignored.
Returns:
ndarray: Spherical magnetic components ``[H_r, H_theta, H_phi]`` with
shape ``(3, ...)``.
"""
if abcd is None:
abcd = _coefficients_abcd(lambda0, d_sphere, m_sphere, n_env, n_pole)
def evaluator(rr, tt, pp):
return _h_near_abcd(abcd, lambda0, d_sphere, m_sphere, n_env, rr, tt, pp, include_incident)
return _vectorized_field_eval(evaluator, r, theta, phi)
[docs]
def eh_near(
lambda0,
d_sphere,
m_sphere,
n_env,
r,
theta,
phi,
include_incident=True,
n_pole=0,
abcd=None,
):
"""Calculate electric and magnetic fields in and around a sphere.
Args:
lambda0 (float): Vacuum wavelength.
d_sphere (float): Sphere diameter.
m_sphere (complex): Sphere refractive index.
n_env (float): Refractive index of the surrounding medium.
r (float or ndarray): Radial coordinate(s).
theta (float or ndarray): Polar angle(s) in radians.
phi (float or ndarray): Azimuth angle(s) in radians.
include_incident (bool): Include incident field for points outside sphere.
n_pole (int): Requested multipole order. ``0`` means automatic truncation.
abcd (ndarray or None): Optional precomputed coefficients ``[a, b, c, d]``.
If provided, ``n_pole`` is ignored.
Returns:
tuple[ndarray, ndarray]: Tuple ``(E, H)`` in spherical components,
each with shape ``(3, ...)``.
"""
if abcd is None:
abcd = _coefficients_abcd(lambda0, d_sphere, m_sphere, n_env, n_pole)
def evaluator(rr, tt, pp):
return _eh_near_abcd(abcd, lambda0, d_sphere, m_sphere, n_env, rr, tt, pp, include_incident)
return _vectorized_field_pair_eval(evaluator, r, theta, phi)
[docs]
def e_near_cartesian(
lambda0,
d_sphere,
m_sphere,
n_env,
x,
y,
z,
include_incident=True,
n_pole=0,
abcd=None,
):
"""Calculate electric near field in Cartesian coordinates.
Args:
lambda0 (float): Vacuum wavelength.
d_sphere (float): Sphere diameter.
m_sphere (complex): Sphere refractive index.
n_env (float): Refractive index of the surrounding medium.
x (float or ndarray): Cartesian x coordinate(s).
y (float or ndarray): Cartesian y coordinate(s).
z (float or ndarray): Cartesian z coordinate(s).
include_incident (bool): Include incident field for points outside sphere.
n_pole (int): Requested multipole order. ``0`` means automatic truncation.
abcd (ndarray or None): Optional precomputed coefficients ``[a, b, c, d]``.
Returns:
ndarray: Cartesian electric components ``[E_x, E_y, E_z]``.
"""
r, theta, phi = _cartesian_to_spherical_safe(x, y, z)
e_sph = e_near(lambda0, d_sphere, m_sphere, n_env, r, theta, phi, include_incident, n_pole, abcd)
return _spherical_components_to_cartesian(e_sph, r, theta, phi)
[docs]
def h_near_cartesian(
lambda0,
d_sphere,
m_sphere,
n_env,
x,
y,
z,
include_incident=True,
n_pole=0,
abcd=None,
):
"""Calculate magnetic near field in Cartesian coordinates.
Args:
lambda0 (float): Vacuum wavelength.
d_sphere (float): Sphere diameter.
m_sphere (complex): Sphere refractive index.
n_env (float): Refractive index of the surrounding medium.
x (float or ndarray): Cartesian x coordinate(s).
y (float or ndarray): Cartesian y coordinate(s).
z (float or ndarray): Cartesian z coordinate(s).
include_incident (bool): Include incident field for points outside sphere.
n_pole (int): Requested multipole order. ``0`` means automatic truncation.
abcd (ndarray or None): Optional precomputed coefficients ``[a, b, c, d]``.
Returns:
ndarray: Cartesian magnetic components ``[H_x, H_y, H_z]``.
"""
r, theta, phi = _cartesian_to_spherical_safe(x, y, z)
h_sph = h_near(lambda0, d_sphere, m_sphere, n_env, r, theta, phi, include_incident, n_pole, abcd)
return _spherical_components_to_cartesian(h_sph, r, theta, phi)
[docs]
def eh_near_cartesian(
lambda0,
d_sphere,
m_sphere,
n_env,
x,
y,
z,
include_incident=True,
n_pole=0,
abcd=None,
):
"""Calculate electric and magnetic near fields in Cartesian coordinates.
Args:
lambda0 (float): Vacuum wavelength.
d_sphere (float): Sphere diameter.
m_sphere (complex): Sphere refractive index.
n_env (float): Refractive index of the surrounding medium.
x (float or ndarray): Cartesian x coordinate(s).
y (float or ndarray): Cartesian y coordinate(s).
z (float or ndarray): Cartesian z coordinate(s).
include_incident (bool): Include incident field for points outside sphere.
n_pole (int): Requested multipole order. ``0`` means automatic truncation.
abcd (ndarray or None): Optional precomputed coefficients ``[a, b, c, d]``.
Returns:
tuple[ndarray, ndarray]: Tuple ``(E_xyz, H_xyz)`` where each array is
``[x, y, z]`` components.
"""
r, theta, phi = _cartesian_to_spherical_safe(x, y, z)
e_sph, h_sph = eh_near(lambda0, d_sphere, m_sphere, n_env, r, theta, phi, include_incident, n_pole, abcd)
e_xyz = _spherical_components_to_cartesian(e_sph, r, theta, phi)
h_xyz = _spherical_components_to_cartesian(h_sph, r, theta, phi)
return e_xyz, h_xyz