Skip to content

Commit 8330c11

Browse files
committed
Add full fluid boundary implementation (but not working at higher ka yet)
1 parent 5cbed3f commit 8330c11

File tree

1 file changed

+91
-38
lines changed

1 file changed

+91
-38
lines changed

src/echosms/psmsmodel.py

Lines changed: 91 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
"""A class that provides the prolate spheroidal modal series scattering model."""
22

33
import numpy as np
4-
from math import factorial
54
from .scattermodelbase import ScatterModelBase
5+
import scipy.integrate as integrate
66
from .utils import pro_rad1, pro_rad2, pro_ang1, wavenumber, Neumann, as_dict
77

88

@@ -11,9 +11,8 @@ class PSMSModel(ScatterModelBase):
1111
1212
Note
1313
----
14-
The fluid filled boundary type implementation is currently only accurate
15-
for weakly scattering interiors. Support for strongly scattering
16-
(e.g., gas-filled) will come later.
14+
The fluid filled boundary type implementation is under development and is of limited use
15+
at the moment.
1716
"""
1817

1918
def __init__(self):
@@ -100,17 +99,28 @@ def calculate_ts_single(self, medium_c, medium_rho, a, b, theta, f, boundary_typ
10099
q = a/xim # semi-focal length
101100

102101
km = wavenumber(medium_c, f)
102+
kt = wavenumber(target_c, f)
103103
hm = km*q
104104

105105
if boundary_type == 'fluid filled':
106106
g = target_rho / medium_rho
107107
ht = wavenumber(target_c, f)*q
108-
# Note: we can implement simpler equations if impedances are
108+
simplified = False
109+
# Note: we can implement simpler equations if sound speeds are
109110
# similar between the medium and the target. The simplified
110111
# equations are quicker, so it is worth to do.
111-
simplified = False
112-
if (abs(1.0-target_c/medium_c) <= 0.01) and (abs(1.0-g) <= 0.01):
113-
simplified = True
112+
# But, it appears that target_c/medium_c is not the only requirement for
113+
# the simplification to work well - see section 4.1.1 in:
114+
#
115+
# Gonzalez, J. D., Lavia, E. F., Blanc, S., & Prario, I. (2016).
116+
# Acoustic scattering by prolate and oblate liquid spheroids.
117+
# Proceedings of the 22nd International Congress on Acoustics.
118+
# Acoustics for the 21st Century, Buenos Aires.
119+
#
120+
# So, for the moment the simplification is turned off.
121+
122+
# if (1.0-target_c/medium_c) <= 0.01:
123+
# simplified = True
114124

115125
# Phi, the port/starboard angle is fixed for this code
116126
phi_inc = np.pi # incident direction
@@ -119,63 +129,106 @@ def calculate_ts_single(self, medium_c, medium_rho, a, b, theta, f, boundary_typ
119129
theta_inc = np.deg2rad(theta) # incident direction
120130
theta_sca = np.pi - theta_inc # scattered direction
121131

122-
# Approximate limits on the summations
123-
m_max = int(np.ceil(2*km*b))
124-
n_max = int(m_max + np.ceil(hm/2))
132+
# Approximate limits on the summations. These come from Furusawa (1988), but other
133+
# implementations of this code use more complex functions to calculate the maximum orders
134+
# of spheroidal wave functions to calculate. It is also feasible to work to a defined
135+
# convergence level. This is a potenital future improvement.
136+
m_max = int(np.ceil(2*km*b))+1
137+
n_max = int(m_max + np.ceil(hm/2))+3
125138

126139
f_sca = 0.0
127140
for m in range(m_max+1):
128141
epsilon_m = Neumann(m)
129142
cos_term = np.cos(m*(phi_sca - phi_inc))
143+
144+
if boundary_type == 'fluid filled' and not simplified:
145+
Am = PSMSModel._fluidfilled(m, n_max, hm, ht, xim, g, theta_inc)
146+
130147
for n in range(m, n_max+1):
131-
Smn_inc, _ = pro_ang1(m, n, hm, np.cos(theta_inc))
132-
Smn_sca, _ = pro_ang1(m, n, hm, np.cos(theta_sca))
133-
# The Meixner-Schäfke normalisation scheme for the angular function of the first
134-
# kind. Refer to eqn 21.7.11 in Abramowitz, M., and Stegun, I. A. (1964).
135-
# Handbook of Mathematical Functions with Formulas, Graphs, and Mathematical Tables
136-
# (Dover, New York), 10th ed.
137-
N_mn = 2/(2*n+1) * factorial(n+m) / factorial(n-m)
148+
# Use the normalisation offered by spheroidalwavefunctions.pro_ang1() because
149+
# it removes the need to calculate a normalisation factor (N_mn in Furusawa, 1998).
150+
# This is because the pro_ang1() norm is unity.
151+
Smn_inc, _ = pro_ang1(m, n, hm, np.cos(theta_inc), norm=True)
152+
Smn_sca, _ = pro_ang1(m, n, hm, np.cos(theta_sca), norm=True)
153+
154+
# FYI, the code used to use the Meixner-Schäfke normalisation scheme for the
155+
# angular function of the first kind (eqn 21.7.11 in Abramowitz, M., and Stegun,
156+
# I. A. (1964). Handbook of Mathematical Functions with Formulas, Graphs, and
157+
# Mathematical Tables # (Dover, New York), 10th ed.
158+
# N_mn = 2/(2*n+1) * factorial(n+m) / factorial(n-m)
138159

139160
R1m, dR1m = pro_rad1(m, n, hm, xim)
140161
R2m, dR2m = pro_rad2(m, n, hm, xim)
141162

142163
match boundary_type:
143164
case 'fluid filled':
144165
if simplified:
145-
Amn = PSMSModel._fluidfilled_approx(m, n, hm, ht, xim, g)
166+
E1, E3 = PSMSModel._fluidfilled_Emn(m, n, n, hm, ht, xim, g)
167+
Amn = -E1/E3
146168
else:
147-
Amn = PSMSModel._fluidfilled_exact(m, n, hm, ht, xim, g, theta_inc)
169+
Amn = Am[n-m]
148170
case 'pressure release':
149171
Amn = -R1m/(R1m + 1j*R2m)
150172
case 'fixed rigid':
151173
Amn = -dR1m/(dR1m + 1j*dR2m)
152174

153-
f_sca += epsilon_m * (Smn_inc / N_mn) * Smn_sca * Amn * cos_term
175+
f_sca += epsilon_m * Smn_inc * Amn * Smn_sca * cos_term
154176

155-
return 20*np.log10(np.abs(-2j / km * f_sca))
177+
return 20*np.log10(np.abs(-2j / km * f_sca))[0]
156178

157179
@staticmethod
158-
def _fluidfilled_approx(m, n, hm, ht, xim, g):
159-
"""Calculate Amn for fluid filled prolate spheroids when ht is approximately equal to hm."""
160-
R1m, dR1m = pro_rad1(m, n, hm, xim)
161-
R2m, dR2m = pro_rad2(m, n, hm, xim)
180+
def _fluidfilled_Emn(m, n, ell, hm, ht, xim, g):
181+
"""Calculate Emn_i values where i = 1 and 3."""
182+
R1mn_w, dR1mn_w = pro_rad1(m, n, hm, xim)
183+
R2mn_w, dR2mn_w = pro_rad2(m, n, hm, xim)
184+
R1ml_t, dR1ml_t = pro_rad1(m, ell, ht, xim)
162185

163-
R1t, dR1t = pro_rad1(m, n, ht, xim)
164-
R3m = R1m + 1j*R2m
165-
dR3m = dR1m + 1j*dR2m
186+
R3mn_w = R1mn_w + 1j*R2mn_w
187+
dR3mn_w = dR1mn_w + 1j*dR2mn_w
166188

167-
E1 = R1m - g * R1t / dR1t * dR1m
168-
E3 = R3m - g * R1t / dR1t * dR3m
169-
return -E1/E3
189+
E1 = R1mn_w - g * R1ml_t / dR1ml_t * dR1mn_w
190+
E3 = R3mn_w - g * R1ml_t / dR1ml_t * dR3mn_w
191+
192+
return E1, E3
170193

171194
@staticmethod
172-
def _fluidfilled_exact(m, n, hm, ht, xim, g, theta_inc):
195+
def _fluidfilled(m, n_max, hm, ht, xim, g, theta_inc):
173196
"""Calculate Amn for fluid filled prolate spheroids."""
174-
# This is complicated!
197+
# Rather than implement eqn (4) in Furusawa (1988), use an alternative form that
198+
# I found easier to understand. This is eqns 5, 6, 7, and 8 in:
199+
#
200+
# Gonzalez, J. D., Lavia, E. F., Blanc, S., & Prario, I. (2016).
201+
# Acoustic scattering by prolate and oblate liquid spheroids.
202+
# Proceedings of the 22nd International Congress on Acoustics.
203+
# Acoustics for the 21st Century, Buenos Aires.
204+
205+
def alpha_int(eta):
206+
"""Eqn (8) in Gonzalez et al (2016) ready for integration with respect to eta.
207+
208+
The denominator in eqn (8) is not necessary because of the norm=True
209+
option in the pro_ang1 calls.
210+
"""
211+
return pro_ang1(m, n, hm, eta, norm=True)[0]\
212+
* pro_ang1(m, ell, ht, eta, norm=True)[0]
213+
214+
# n = m to n_max
215+
# l = m to n_max (though it could be different?)
216+
l_max = n_max
217+
Q = np.full((n_max-m+1, l_max-m+1), 0j)
218+
f = np.full((n_max-m+1, 1), 0j)
219+
220+
# TODO : this can be optimised somewhat for speed
221+
for ell in range(m, l_max+1):
222+
for n in range(m, n_max+1):
223+
alpha_nl = integrate.quad(alpha_int, -1, 1)[0]
224+
Smn_w_inc, _ = pro_ang1(m, n, hm, np.cos(theta_inc), norm=True)
225+
E1, E3 = PSMSModel._fluidfilled_Emn(m, n, ell, hm, ht, xim, g)
175226

176-
# Setup the system of simultaneous equations to solve for Amn.
227+
# By using norm=True when calculating Smn_w_inc, dividing
228+
# by a norm is not necessary, so the equations below differ from
229+
# those in Gonzalezx et al - they don't have the Nmn division.
230+
Q[ell-m, n-m] = 1j**n * alpha_nl * Smn_w_inc * -E3
231+
f[ell-m] += 1j**n * alpha_nl * Smn_w_inc * E1
177232

178233
# Solve for Amn
179-
Amn = 1.0
180-
181-
return Amn
234+
return np.linalg.lstsq(Q, f, rcond=None)[0]

0 commit comments

Comments
 (0)