Skip to content

Commit 81a50ba

Browse files
committed
speedup
1 parent 2f5ffc4 commit 81a50ba

1 file changed

Lines changed: 163 additions & 20 deletions

File tree

miepython/field.py

Lines changed: 163 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717

1818
import numpy as np
1919
import miepython as mie
20+
from scipy.special import spherical_jn, factorial2
21+
from miepython.bessel import spherical_h1, d_riccati_bessel_h1
2022
from miepython.util import spherical_vector_to_cartesian
21-
from miepython.vsh import M_even_array, M_odd_array, N_even_array, N_odd_array
2223

2324
__all__ = (
2425
"e_near",
@@ -31,6 +32,71 @@
3132
)
3233

3334

35+
def _sum_two_scaled_terms(scale, coeff1, values1, scale1, coeff2, values2, scale2):
36+
"""Return ``sum(scale * (scale1*coeff1*values1 + scale2*coeff2*values2))``."""
37+
return np.sum(scale * (scale1 * coeff1 * values1 + scale2 * coeff2 * values2))
38+
39+
40+
def _vsh_components_base(n_terms, lambda0, d_sphere, m_index, r, theta):
41+
"""Compute shared VSH base components for one spatial point.
42+
43+
Args:
44+
n_terms (int): Number of multipole terms.
45+
lambda0 (float): Vacuum wavelength.
46+
d_sphere (float): Sphere diameter.
47+
m_index (complex): Refractive index at the evaluation point.
48+
r (float): Radial coordinate.
49+
theta (float): Polar angle in radians.
50+
51+
Returns:
52+
tuple[ndarray, ndarray, ndarray, ndarray, ndarray]:
53+
``(M_theta_base, M_phi_base, N_r_base, N_theta_base, N_phi_base)``
54+
for multipole orders ``1..n_terms``.
55+
"""
56+
mu = float(np.cos(theta))
57+
if mu >= 1.0:
58+
mu = 0.999999
59+
elif mu <= -1.0:
60+
mu = -0.999999
61+
62+
pi = np.empty(n_terms)
63+
tau = np.empty(n_terms)
64+
mie._pi_tau(mu, pi, tau)
65+
66+
n_int = np.arange(1, n_terms + 1, dtype=np.int64)
67+
n_arr = n_int.astype(np.float64)
68+
rho = 2 * np.pi * m_index * r / lambda0
69+
kr = 2 * np.pi * r / lambda0
70+
inside = r < d_sphere / 2
71+
72+
if inside:
73+
jn = spherical_jn(n_int, rho)
74+
m_factor = jn
75+
76+
if np.abs(rho) < 0.01:
77+
rho_pow = rho ** np.arange(0, n_terms)
78+
denom = factorial2(2 * n_int + 1)
79+
n_factor1 = rho_pow / denom
80+
n_factor2 = (n_arr + 1.0) * rho_pow / denom
81+
else:
82+
d_vals = mie._D_calc(np.complex128(m_index), float(kr), n_terms + 1)[:n_terms]
83+
n_factor1 = jn / rho
84+
n_factor2 = jn * d_vals
85+
else:
86+
h1 = spherical_h1(n_int, rho)
87+
m_factor = h1
88+
n_factor1 = h1 / rho
89+
n_factor2 = d_riccati_bessel_h1(n_int, rho) / rho
90+
91+
sin_theta = np.sin(theta)
92+
M_theta_base = pi * m_factor
93+
M_phi_base = tau * m_factor
94+
N_r_base = n_arr * (n_arr + 1.0) * sin_theta * pi * n_factor1
95+
N_theta_base = tau * n_factor2
96+
N_phi_base = pi * n_factor2
97+
return M_theta_base, M_phi_base, N_r_base, N_theta_base, N_phi_base
98+
99+
34100
def e_far(lambda0, d_sphere, m_sphere, n_env, r, theta, phi):
35101
"""Evaluate the scattered electric far field.
36102
@@ -246,17 +312,26 @@ def _e_near_abcd(abcd, lambda0, d_sphere, m_sphere, n_env, r, theta, phi, includ
246312
# internally; use conjugated sphere index so internal fields are consistent.
247313
m_index = np.conjugate(m_sphere) if inside else n_env
248314

249-
M_rad, M_the, M_phi = M_odd_array(N, lambda0, d_sphere, m_index, r, theta, phi)
250-
N_rad, N_the, N_phi = N_even_array(N, lambda0, d_sphere, m_index, r, theta, phi)
315+
M_the_base, M_phi_base, N_r_base, N_the_base, N_phi_base = _vsh_components_base(
316+
N, lambda0, d_sphere, m_index, r, theta
317+
)
318+
cos_phi = np.cos(phi)
319+
sin_phi = np.sin(phi)
320+
M_rad = np.zeros(N, dtype=np.complex128)
321+
M_the = cos_phi * M_the_base
322+
M_phi = -sin_phi * M_phi_base
323+
N_rad = cos_phi * N_r_base
324+
N_the = cos_phi * N_the_base
325+
N_phi = -sin_phi * N_phi_base
251326

252327
if inside:
253-
E_rad = np.sum(scale * (c * M_rad - 1j * d * N_rad))
254-
E_the = np.sum(scale * (c * M_the - 1j * d * N_the))
255-
E_phi = np.sum(scale * (c * M_phi - 1j * d * N_phi))
328+
E_rad = _sum_two_scaled_terms(scale, c, M_rad, 1.0 + 0.0j, d, N_rad, -1.0j)
329+
E_the = _sum_two_scaled_terms(scale, c, M_the, 1.0 + 0.0j, d, N_the, -1.0j)
330+
E_phi = _sum_two_scaled_terms(scale, c, M_phi, 1.0 + 0.0j, d, N_phi, -1.0j)
256331
else:
257-
E_rad = np.sum(scale * (1j * a * N_rad - b * M_rad))
258-
E_the = np.sum(scale * (1j * a * N_the - b * M_the))
259-
E_phi = np.sum(scale * (1j * a * N_phi - b * M_phi))
332+
E_rad = _sum_two_scaled_terms(scale, a, N_rad, 1.0j, b, M_rad, -1.0 + 0.0j)
333+
E_the = _sum_two_scaled_terms(scale, a, N_the, 1.0j, b, M_the, -1.0 + 0.0j)
334+
E_phi = _sum_two_scaled_terms(scale, a, N_phi, 1.0j, b, M_phi, -1.0 + 0.0j)
260335

261336
if include_incident:
262337
Ei_rad, Ei_the, Ei_phi = _incident_e_spherical(lambda0, n_env, r, theta, phi)
@@ -293,18 +368,27 @@ def _h_near_abcd(abcd, lambda0, d_sphere, m_sphere, n_env, r, theta, phi, includ
293368
inside = r < d_sphere / 2
294369
m_index = np.conjugate(m_sphere) if inside else n_env
295370

296-
M_rad, M_the, M_phi = M_even_array(N, lambda0, d_sphere, m_index, r, theta, phi)
297-
N_rad, N_the, N_phi = N_odd_array(N, lambda0, d_sphere, m_index, r, theta, phi)
371+
M_the_base, M_phi_base, N_r_base, N_the_base, N_phi_base = _vsh_components_base(
372+
N, lambda0, d_sphere, m_index, r, theta
373+
)
374+
cos_phi = np.cos(phi)
375+
sin_phi = np.sin(phi)
376+
M_rad = np.zeros(N, dtype=np.complex128)
377+
M_the = -sin_phi * M_the_base
378+
M_phi = -cos_phi * M_phi_base
379+
N_rad = sin_phi * N_r_base
380+
N_the = sin_phi * N_the_base
381+
N_phi = cos_phi * N_phi_base
298382

299383
if inside:
300384
m_rel = np.conjugate(m_sphere / n_env)
301-
H_rad = m_rel * np.sum(scale * (-d * M_rad - 1j * c * N_rad))
302-
H_the = m_rel * np.sum(scale * (-d * M_the - 1j * c * N_the))
303-
H_phi = m_rel * np.sum(scale * (-d * M_phi - 1j * c * N_phi))
385+
H_rad = m_rel * _sum_two_scaled_terms(scale, d, M_rad, -1.0 + 0.0j, c, N_rad, -1.0j)
386+
H_the = m_rel * _sum_two_scaled_terms(scale, d, M_the, -1.0 + 0.0j, c, N_the, -1.0j)
387+
H_phi = m_rel * _sum_two_scaled_terms(scale, d, M_phi, -1.0 + 0.0j, c, N_phi, -1.0j)
304388
else:
305-
H_rad = np.sum(scale * (1j * b * N_rad + a * M_rad))
306-
H_the = np.sum(scale * (1j * b * N_the + a * M_the))
307-
H_phi = np.sum(scale * (1j * b * N_phi + a * M_phi))
389+
H_rad = _sum_two_scaled_terms(scale, b, N_rad, 1.0j, a, M_rad, 1.0 + 0.0j)
390+
H_the = _sum_two_scaled_terms(scale, b, N_the, 1.0j, a, M_the, 1.0 + 0.0j)
391+
H_phi = _sum_two_scaled_terms(scale, b, N_phi, 1.0j, a, M_phi, 1.0 + 0.0j)
308392

309393
if include_incident:
310394
Hi_rad, Hi_the, Hi_phi = _incident_h_spherical(lambda0, n_env, r, theta, phi)
@@ -315,6 +399,66 @@ def _h_near_abcd(abcd, lambda0, d_sphere, m_sphere, n_env, r, theta, phi, includ
315399
return np.array([H_rad, H_the, -H_phi])
316400

317401

402+
def _eh_near_abcd(abcd, lambda0, d_sphere, m_sphere, n_env, r, theta, phi, include_incident):
403+
"""Evaluate electric and magnetic near fields with shared VSH work."""
404+
a, b, c, d = abcd
405+
406+
N = len(a)
407+
nn = np.arange(1, N + 1)
408+
scale = 1j**nn * (2 * nn + 1) / ((nn + 1) * nn)
409+
410+
inside = r < d_sphere / 2
411+
m_index = np.conjugate(m_sphere) if inside else n_env
412+
M_the_base, M_phi_base, N_r_base, N_the_base, N_phi_base = _vsh_components_base(
413+
N, lambda0, d_sphere, m_index, r, theta
414+
)
415+
cos_phi = np.cos(phi)
416+
sin_phi = np.sin(phi)
417+
418+
M_odd_rad = np.zeros(N, dtype=np.complex128)
419+
M_odd_the = cos_phi * M_the_base
420+
M_odd_phi = -sin_phi * M_phi_base
421+
N_even_rad = cos_phi * N_r_base
422+
N_even_the = cos_phi * N_the_base
423+
N_even_phi = -sin_phi * N_phi_base
424+
425+
M_even_rad = np.zeros(N, dtype=np.complex128)
426+
M_even_the = -sin_phi * M_the_base
427+
M_even_phi = -cos_phi * M_phi_base
428+
N_odd_rad = sin_phi * N_r_base
429+
N_odd_the = sin_phi * N_the_base
430+
N_odd_phi = cos_phi * N_phi_base
431+
432+
if inside:
433+
e_rad = _sum_two_scaled_terms(scale, c, M_odd_rad, 1.0 + 0.0j, d, N_even_rad, -1.0j)
434+
e_the = _sum_two_scaled_terms(scale, c, M_odd_the, 1.0 + 0.0j, d, N_even_the, -1.0j)
435+
e_phi = _sum_two_scaled_terms(scale, c, M_odd_phi, 1.0 + 0.0j, d, N_even_phi, -1.0j)
436+
437+
m_rel = np.conjugate(m_sphere / n_env)
438+
h_rad = m_rel * _sum_two_scaled_terms(scale, d, M_even_rad, -1.0 + 0.0j, c, N_odd_rad, -1.0j)
439+
h_the = m_rel * _sum_two_scaled_terms(scale, d, M_even_the, -1.0 + 0.0j, c, N_odd_the, -1.0j)
440+
h_phi = m_rel * _sum_two_scaled_terms(scale, d, M_even_phi, -1.0 + 0.0j, c, N_odd_phi, -1.0j)
441+
else:
442+
e_rad = _sum_two_scaled_terms(scale, a, N_even_rad, 1.0j, b, M_odd_rad, -1.0 + 0.0j)
443+
e_the = _sum_two_scaled_terms(scale, a, N_even_the, 1.0j, b, M_odd_the, -1.0 + 0.0j)
444+
e_phi = _sum_two_scaled_terms(scale, a, N_even_phi, 1.0j, b, M_odd_phi, -1.0 + 0.0j)
445+
h_rad = _sum_two_scaled_terms(scale, b, N_odd_rad, 1.0j, a, M_even_rad, 1.0 + 0.0j)
446+
h_the = _sum_two_scaled_terms(scale, b, N_odd_the, 1.0j, a, M_even_the, 1.0 + 0.0j)
447+
h_phi = _sum_two_scaled_terms(scale, b, N_odd_phi, 1.0j, a, M_even_phi, 1.0 + 0.0j)
448+
449+
if include_incident:
450+
e_i = _incident_e_spherical(lambda0, n_env, r, theta, phi)
451+
h_i = _incident_h_spherical(lambda0, n_env, r, theta, phi)
452+
e_rad += e_i[0]
453+
e_the += e_i[1]
454+
e_phi += e_i[2]
455+
h_rad += h_i[0]
456+
h_the += h_i[1]
457+
h_phi += h_i[2]
458+
459+
return np.array([e_rad, e_the, -e_phi]), np.array([h_rad, h_the, -h_phi])
460+
461+
318462
def e_near(lambda0, d_sphere, m_sphere, n_env, r, theta, phi, include_incident=True, n_pole=0, abcd=None):
319463
"""Calculate the electric field in and around a sphere.
320464
@@ -407,9 +551,8 @@ def eh_near(
407551
if abcd is None:
408552
abcd = _coefficients_abcd(lambda0, d_sphere, m_sphere, n_env, n_pole)
409553

410-
evaluator = lambda rr, tt, pp: ( # noqa: E731
411-
_e_near_abcd(abcd, lambda0, d_sphere, m_sphere, n_env, rr, tt, pp, include_incident),
412-
_h_near_abcd(abcd, lambda0, d_sphere, m_sphere, n_env, rr, tt, pp, include_incident),
554+
evaluator = lambda rr, tt, pp: _eh_near_abcd( # noqa: E731
555+
abcd, lambda0, d_sphere, m_sphere, n_env, rr, tt, pp, include_incident
413556
)
414557
return _vectorized_field_pair_eval(evaluator, r, theta, phi)
415558

0 commit comments

Comments
 (0)