1
1
"""A class that provides the prolate spheroidal modal series scattering model."""
2
2
3
3
import numpy as np
4
- from math import factorial
5
4
from .scattermodelbase import ScatterModelBase
5
+ import scipy .integrate as integrate
6
6
from .utils import pro_rad1 , pro_rad2 , pro_ang1 , wavenumber , Neumann , as_dict
7
7
8
8
@@ -11,9 +11,8 @@ class PSMSModel(ScatterModelBase):
11
11
12
12
Note
13
13
----
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.
17
16
"""
18
17
19
18
def __init__ (self ):
@@ -100,17 +99,28 @@ def calculate_ts_single(self, medium_c, medium_rho, a, b, theta, f, boundary_typ
100
99
q = a / xim # semi-focal length
101
100
102
101
km = wavenumber (medium_c , f )
102
+ kt = wavenumber (target_c , f )
103
103
hm = km * q
104
104
105
105
if boundary_type == 'fluid filled' :
106
106
g = target_rho / medium_rho
107
107
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
109
110
# similar between the medium and the target. The simplified
110
111
# 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
114
124
115
125
# Phi, the port/starboard angle is fixed for this code
116
126
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
119
129
theta_inc = np .deg2rad (theta ) # incident direction
120
130
theta_sca = np .pi - theta_inc # scattered direction
121
131
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
125
138
126
139
f_sca = 0.0
127
140
for m in range (m_max + 1 ):
128
141
epsilon_m = Neumann (m )
129
142
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
+
130
147
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)
138
159
139
160
R1m , dR1m = pro_rad1 (m , n , hm , xim )
140
161
R2m , dR2m = pro_rad2 (m , n , hm , xim )
141
162
142
163
match boundary_type :
143
164
case 'fluid filled' :
144
165
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
146
168
else :
147
- Amn = PSMSModel . _fluidfilled_exact ( m , n , hm , ht , xim , g , theta_inc )
169
+ Amn = Am [ n - m ]
148
170
case 'pressure release' :
149
171
Amn = - R1m / (R1m + 1j * R2m )
150
172
case 'fixed rigid' :
151
173
Amn = - dR1m / (dR1m + 1j * dR2m )
152
174
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
154
176
155
- return 20 * np .log10 (np .abs (- 2j / km * f_sca ))
177
+ return 20 * np .log10 (np .abs (- 2j / km * f_sca ))[ 0 ]
156
178
157
179
@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 )
162
185
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
166
188
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
170
193
171
194
@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 ):
173
196
"""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 )
175
226
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
177
232
178
233
# Solve for Amn
179
- Amn = 1.0
180
-
181
- return Amn
234
+ return np .linalg .lstsq (Q , f , rcond = None )[0 ]
0 commit comments