1717
1818import numpy as np
1919import miepython as mie
20+ from scipy .special import spherical_jn , factorial2
21+ from miepython .bessel import spherical_h1 , d_riccati_bessel_h1
2022from 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" ,
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+
34100def 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+
318462def 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