Skip to content

Commit 7595a2e

Browse files
committed
Polish KRM model code and fix bug with non-swimbladdered fish example datasets
1 parent 320a555 commit 7595a2e

File tree

3 files changed

+253
-37
lines changed

3 files changed

+253
-37
lines changed

src/echosms/krmdata.py

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
@dataclass
1616
class KRMshape():
17-
"""KRM shape and property storage.
17+
"""KRM shape and property class.
1818
1919
Attributes
2020
----------
@@ -25,36 +25,58 @@ class KRMshape():
2525
w :
2626
Width of the shape [m].
2727
z_U :
28-
Distance from the fish axis to the upper surface of the shape [m].
28+
Distance from the axis to the upper surface of the shape [m].
2929
z_L :
30-
Distance from the fish axis to the lower surface of the shape [m].
30+
Distance from the axis to the lower surface of the shape [m].
3131
c :
3232
Sound speed in the shape [m/s].
3333
rho :
34-
Density in the shape [kg/m³].
34+
Density of the shape material [kg/m³].
3535
"""
3636

3737
boundary: str # 'soft' (aka swimbladder) or 'fluid' (aka body)
3838
x: np.ndarray
3939
w: np.ndarray
4040
z_U: np.ndarray
4141
z_L: np.ndarray
42-
c: float = None
43-
rho: float = None
42+
c: float
43+
rho: float
44+
45+
def volume(self) -> float:
46+
"""Volume of the shape.
47+
48+
Returns
49+
-------
50+
:
51+
The volume of the shape [m³].
52+
"""
53+
thickness = np.diff(self.x)
54+
thickness = np.append(thickness, thickness[1])
55+
return np.sum(np.pi * (self.z_U - self.z_L) * self.w * thickness)
56+
57+
def length(self) -> float:
58+
"""Length of the shape.
59+
60+
Returns
61+
-------
62+
:
63+
The length of the shape [m].
64+
"""
65+
return self.x[-1] - self.x[0]
4466

4567

4668
@dataclass
47-
class KRMfish():
48-
"""KRM fish and swimbladder model shapes.
69+
class KRMorganism():
70+
"""KRM body and swimbladder model shape.
4971
5072
Attributes
5173
----------
5274
name :
53-
A name for the fish.
75+
A name for the organism.
5476
source :
55-
A link to or description of the source of the fish data.
77+
A link to or description of the source of the organism data.
5678
shapes :
57-
The shapes that make up the fish.
79+
The shapes that make up the organism.
5880
"""
5981

6082
name: str
@@ -63,7 +85,7 @@ class KRMfish():
6385

6486

6587
class KRMdata():
66-
"""Provides example fish datasets for the KRM model."""
88+
"""Example datasets for the KRM model."""
6789

6890
def __init__(self):
6991
# Load in the NOAA KRM shapes data
@@ -74,15 +96,17 @@ def __init__(self):
7496
except tomllib.TOMLDecodeError as e:
7597
raise SyntaxError(f'Error while parsing file "{self.defs_filename.name}"') from e
7698

77-
# Put the shapes into a dict of KRMfish(). Use some default values for sound speed and
99+
# Put the shapes into a dict of KRMorganism(). Use some default values for sound speed and
78100
# density
79101
self.krm_models = {}
80102
for s in shapes['shape']:
81103
body = KRMshape('fluid', np.array(s['x_b']), np.array(s['w_b']),
82-
np.array(s['z_bU']), np.array(s['z_bL']), 1570, 1070)
104+
np.array(s['z_bU']), np.array(s['z_bL']),
105+
s['body_c'], s['body_rho'])
83106
swimbladder = KRMshape('soft', np.array(s['x_sb']), np.array(s['w_sb']),
84-
np.array(s['z_sbU']), np.array(s['z_sbL']), 345, 1.24)
85-
self.krm_models[s['name']] = KRMfish(s['name'], s['source'], [body, swimbladder])
107+
np.array(s['z_sbU']), np.array(s['z_sbL']),
108+
s['swimbladder_c'], s['swimbladder_rho'])
109+
self.krm_models[s['name']] = KRMorganism(s['name'], s['source'], [body, swimbladder])
86110

87111
def names(self):
88112
"""Available KRM model names."""
@@ -95,12 +119,12 @@ def as_dict(self) -> dict:
95119
-------
96120
:
97121
All the KRM model shapes. The dataset name is the dict key and the value is an instance
98-
of `KRMfish`.
122+
of `KRMorganism`.
99123
100124
"""
101125
return self.krm_models
102126

103-
def model(self, name: str) -> KRMfish:
127+
def model(self, name: str) -> KRMorganism:
104128
"""KRM model shape with requested name.
105129
106130
Parameters
@@ -111,7 +135,7 @@ def model(self, name: str) -> KRMfish:
111135
Returns
112136
-------
113137
:
114-
An instance of `KRMfish` or None if there is no model with `name`.
138+
An instance of `KRMorganism` or None if there is no model with `name`.
115139
116140
"""
117141
try:
@@ -121,7 +145,7 @@ def model(self, name: str) -> KRMfish:
121145

122146
@staticmethod
123147
def ts(name: str) -> np.ndarray:
124-
"""KRM model ts with requested name.
148+
"""KRM model ts from model `name`.
125149
126150
Parameters
127151
----------
@@ -131,7 +155,8 @@ def ts(name: str) -> np.ndarray:
131155
Returns
132156
-------
133157
:
134-
The TS (re 1 m²) for some default model parameters [dB] or None of no TS data exist.
158+
The TS (re 1 m²) for some default model parameters [dB] or None if no TS data
159+
are available.
135160
136161
"""
137162
# Sometimes there will be TS results for the model (available for testing of the

src/echosms/krmmodel.py

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
"""A class that provides the Kirchhoff ray mode scattering model."""
22

33
from math import log10, pi, sqrt, cos, sin, radians
4+
from cmath import exp
45
import numpy as np
6+
from scipy.special import j0, y0, jvp, yvp
57
from .utils import wavenumber, as_dict
68
from .scattermodelbase import ScatterModelBase
7-
from .krmdata import KRMfish, KRMshape
9+
from .krmdata import KRMshape
810

911

1012
def _u(x, z, theta):
@@ -54,7 +56,11 @@ def validate_parameters(self, params):
5456
def calculate_ts_single(self, medium_c, medium_rho, theta, f, bodies,
5557
validate_parameters=True, **kwargs) -> float:
5658
"""
57-
Calculate the scatter using the kirchhoff ray mode model for one set of parameters.
59+
Calculate the scatter using the Kirchhoff ray mode model for one set of parameters.
60+
61+
Warning
62+
--------
63+
The low _ka_ part of this model has not yet been verified to give correct results.
5864
5965
Parameters
6066
----------
@@ -68,11 +74,11 @@ def calculate_ts_single(self, medium_c, medium_rho, theta, f, bodies,
6874
conventions/#coordinate-systems) [°].
6975
f : float
7076
Frequency to calculate the scattering at [Hz].
71-
bodies: KRMfish
77+
bodies: KRMorganism
7278
The body shapes that make up the model. Currently, `bodies` should contain only two
7379
shapes, one of which should have a boundary of `fluid` (aka, the fish body) and the
74-
other a boundary of `soft` (aka, the swimbladder). KRMfish.shapes[0] should be the
75-
fish body and KRMfish.shapes[1] the swimbladder.
80+
other a boundary of `soft` (aka, the swimbladder). KRMorganism.shapes[0] should be the
81+
fish body and KRMorganism.shapes[1] the swimbladder.
7682
validate_parameters : bool
7783
Whether to validate the model parameters.
7884
@@ -83,7 +89,7 @@ def calculate_ts_single(self, medium_c, medium_rho, theta, f, bodies,
8389
8490
Notes
8591
-----
86-
The class implements the code in Clay & Horne (1994) and when ka < 0.15 as per Clay (1992).
92+
The class implements the code in Clay & Horne (1994) and when ka < 0.15 uses Clay (1992).
8793
8894
References
8995
----------
@@ -126,14 +132,15 @@ def calculate_ts_single(self, medium_c, medium_rho, theta, f, bodies,
126132
R_bc = (gp*hp-1) / (gp*hp+1) # Eqn (9)
127133

128134
# Equivalent radius of swimbladder (as per Part A of paper)
129-
a_e = sqrt(self._volume(swimbladder)
130-
/ (pi * (np.max(swimbladder.x) - np.min(swimbladder.x))))
131-
a_e = 10
135+
a_e = sqrt(swimbladder.volume() / (pi * swimbladder.length()))
132136

133137
# Choose which modelling approach to use
134138
if k*a_e < 0.15:
135139
# Do the mode solution for the swimbladder (and ignore the body?)
136-
mode_sl = self._mode_solution(swimbladder)
140+
# TODO: need to check if it should be target or medium for the numerators
141+
g = target_rho / swimbladder_rho
142+
h = target_c / target_c
143+
mode_sl = self._mode_solution(swimbladder, g, h, k, a_e, swimbladder.length(), theta)
137144
return 20*log10(abs(mode_sl))
138145

139146
# Do the Kirchhoff-ray approximation for the swimbladder and body
@@ -142,13 +149,53 @@ def calculate_ts_single(self, medium_c, medium_rho, theta, f, bodies,
142149

143150
return 20*log10(abs(soft_sl + fluid_sl))
144151

145-
def _volume(self, shape):
146-
"""Volume of the object."""
147-
return 1.0
152+
def _mode_solution(self, swimbladder: KRMshape, g: float, h: float,
153+
k: float, a: float, L_e: float, theta: float) -> float:
154+
"""Backscatter from a soft swimbladder at low ka.
155+
156+
Parameters
157+
----------
158+
swimbladder :
159+
The shape.
160+
g :
161+
Ratio of medium density over swimbladder density.
162+
h :
163+
Ratio of medium sound speed over swimbladder sound speed.
164+
k :
165+
The wavenumber in the medium surrounding the swimbladder.
166+
a :
167+
Equivalent radius of swimbladder [m].
168+
L_e :
169+
Equivalent length of swimbladder [m].
170+
theta :
171+
Pitch angle to calculate the scattering at, as per the echoSMs
172+
[coordinate system](https://ices-tools-dev.github.io/echoSMs/
173+
conventions/#coordinate-systems) [°].
174+
175+
Returns
176+
-------
177+
:
178+
The scattering length [m].
179+
"""
180+
# Note: equation references in this function are to Clay (1992)
181+
if h == 0.0:
182+
raise ValueError('Ratio of sound speeds (h) cannot be zero for low ka solution.')
183+
184+
# Chi is approximately this. More accurate equations are in Appendix B of Clay (1992)
185+
chi = -pi/4 # Eqn (B10) and paragraph below that equation
186+
187+
ka = k*a
188+
kca = ka/h
189+
190+
C_0 = (jvp(0, kca)*y0(ka) - g*h*yvp(0, ka)*j0(kca))\
191+
/ (jvp(0, kca)*j0(ka) - g*h*jvp(0, ka)*j0(kca)) # Eqn (A1) with m=0
192+
b_0 = -1 / (1+1j*C_0) # Also Eqn (A1)
193+
194+
delta = k*L_e*cos(theta) # Eqn (4)
195+
196+
S_M = (exp(1j*(chi - pi/4)) * L_e)/pi * sin(delta)/delta * b_0 # Eqn (15)
148197

149-
def _mode_solution(self, swimbladder):
150-
"""Backscatter from a soft swimbladder at low ka."""
151-
return -1.
198+
return S_M
152199

153200
def _soft_KA(self, swimbladder, k, k_b, R_bc, TwbTbw, theta):
154201
"""Backscatter from a soft object using the Kirchhoff approximation."""

0 commit comments

Comments
 (0)