Skip to content

Commit 7855f33

Browse files
committed
refactor(pm): change representation of modes and refine modal model
1 parent 75c24c1 commit 7855f33

File tree

10 files changed

+942
-40
lines changed

10 files changed

+942
-40
lines changed

docs/tmp/pm_basic.md

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
# Propagation modeling toolkit
2+
3+
## Overview
4+
5+
The _underwater acoustic propagation modeling & simulation toolkit_ provides a framework for modeling and simulating underwater acoustic environments with multiple sources and receivers. The toolkit provides a pluggable interface that allows different propagation models to be used with the same scene description. While `UnderwaterAcoustics.jl` provides several propagation model implementations that can be used out-of-the-box, the interface is designed to allow third party propagation models to be easily plugged in to the toolkit as well.
6+
7+
Available models:
8+
9+
| Model | Description | Language | Strengths | Limitations |
10+
|-------|-------------|----------|-----------|-------------|
11+
| [PekerisRayModel](@ref) | Analytical ray model for Pekeris waveguides | Julia | Fast, differentiable, multi-threaded | Isovelocity, range independent |
12+
| [RaySolver](https://github.com/org-arl/AcousticRayTracers.jl) | Ray/Gaussian beam model | Julia | Differentiable, multi-threaded | Tell us and we'll fix them! |
13+
| [Bellhop](https://github.com/org-arl/AcousticsToolbox.jl) | Interface to [OALIB Bellhop model](http://oalib.hlsresearch.com/AcousticsToolbox/) | FORTRAN | Well established benchmark model | Does not support automatic differentiation |
14+
| [Kraken](https://github.com/org-arl/AcousticsToolbox.jl) | Interface to [OALIB Kraken model](http://oalib.hlsresearch.com/AcousticsToolbox/) | FORTRAN | Well established benchmark model | Does not support automatic differentiation |
15+
| [RayBasis](https://github.com/org-arl/DataDrivenAcoustics.jl) | Ray-basis neural network models | Julia | Data-driven models | Requires training data |
16+
| [GPR](https://github.com/org-arl/DataDrivenAcoustics.jl) | Gaussian process regression models | Julia | Data-driven model | Requires training data |
17+
18+
## Quickstart guide
19+
20+
Let's get started:
21+
```julia-repl
22+
julia> using UnderwaterAcoustics
23+
```
24+
25+
### Define an environment
26+
27+
First, let's setup an environment description.
28+
29+
```julia-repl
30+
julia> env = UnderwaterEnvironment()
31+
BasicUnderwaterEnvironment:
32+
altimetry = FlatSurface()
33+
bathymetry = ConstantDepth{Float64}(20.0)
34+
ssp = IsoSSP{Float64}(1539.0866009307247)
35+
salinity = 35.0
36+
seasurface = SurfaceLoss{Float64}(2.6)
37+
seabed = RayleighReflectionCoef{Float64,Float64,Float64}(1.169, 0.9999, 0.01261)
38+
noise = RedGaussianNoise{Float64}(1.0e6)
39+
```
40+
41+
Environments are immutable, so you have to customize them during construction. For example:
42+
```julia-repl
43+
julia> env = UnderwaterEnvironment(
44+
seasurface = Vacuum,
45+
seabed = SandyClay,
46+
ssp = SampledSSP(0.0:20.0:40.0, [1500.0, 1490.0, 1520.0], :smooth),
47+
bathymetry = ConstantDepth(40.0)
48+
)
49+
BasicUnderwaterEnvironment:
50+
altimetry = FlatSurface()
51+
bathymetry = ConstantDepth{Float64}(40.0)
52+
ssp = SampledSSP{Float64,Float64,linear}(3 points)
53+
salinity = 35.0
54+
seasurface = ReflectionCoef{Float64}(-1.0)
55+
seabed = RayleighReflectionCoef{Float64,Float64,Float64}(1.147, 0.9849, 0.00242)
56+
noise = RedGaussianNoise{Float64}(1.0e6)
57+
```
58+
59+
If you have `Plots.jl` installed, you can use plot recipes to plot the environment or the soundspeed profile. For example:
60+
```julia-repl
61+
julia> using Plots
62+
julia> plot(ssp(env))
63+
```
64+
![](images/ssp1.png)
65+
66+
### Selecting a model
67+
68+
Once you have an environment, you need to select a propagation model. To get a list of all available models:
69+
```julia-repl
70+
julia> models()
71+
3-element Array{Any,1}:
72+
PekerisRayModel
73+
RaySolver
74+
Bellhop
75+
```
76+
77+
NOTE: `Bellhop` will only be available if you have a working copy of OALIB `bellhop.exe` available on your PATH.
78+
79+
Once you have an environment, you can select a model that can work with that environment:
80+
```julia-repl
81+
julia> models(env)
82+
2-element Array{Any,1}:
83+
RaySolver
84+
Bellhop
85+
```
86+
87+
In this case, we got a shorter list back because the `PekerisRayModel` can't deal with non-isovelocity SSP. We can confirm this by creating an iso-velocity environment:
88+
```julia-repl
89+
julia> env = UnderwaterEnvironment()
90+
BasicUnderwaterEnvironment:
91+
altimetry = FlatSurface()
92+
bathymetry = ConstantDepth{Float64}(20.0)
93+
ssp = IsoSSP{Float64}(1539.0866009307247)
94+
salinity = 35.0
95+
seasurface = SurfaceLoss{Float64}(2.6)
96+
seabed = RayleighReflectionCoef{Float64,Float64,Float64}(1.169, 0.9999, 0.01261)
97+
noise = RedGaussianNoise{Float64}(1.0e6)
98+
99+
julia> models(env)
100+
2-element Array{Any,1}:
101+
PekerisRayModel
102+
RaySolver
103+
```
104+
105+
This time you see that `Bellhop` wasn't included, as it assumes a `Vacuum` surface by default and we have a `SeaState1` surface as our default.
106+
107+
Let's pick a 7-ray Pakeris ray model for now:
108+
```julia-repl
109+
julia> pm = PekerisRayModel(env, 7)
110+
PekerisRayModel with BasicUnderwaterEnvironment:
111+
altimetry = FlatSurface()
112+
bathymetry = ConstantDepth{Float64}(20.0)
113+
ssp = IsoSSP{Float64}(1539.0866009307247)
114+
salinity = 35.0
115+
seasurface = SurfaceLoss{Float64}(2.6)
116+
seabed = RayleighReflectionCoef{Float64,Float64,Float64}(1.169, 0.9999, 0.01261)
117+
noise = RedGaussianNoise{Float64}(1.0e6)
118+
```
119+
120+
If you wanted the ray solver instead, you'd do `pm = RaySolver(env)`, or for a Bellhop model, you'd do `pm = Bellhop(env)`. Both models can take additional keyword parameters that can customize the solver.
121+
122+
### Defining sources and receivers
123+
124+
Now, we need a source and a receiver:
125+
```julia-repl
126+
julia> tx = AcousticSource(0.0, -5.0, 1000.0);
127+
julia> rx = AcousticReceiver(100.0, -10.0);
128+
```
129+
130+
This defines an omnidirectional 1 kHz transmitter `tx` at a depth of 5 m at the origin, and an omnidirectional receiver at a range of 100 m and a depth of 10 m.
131+
132+
NOTE: All coordinates are specified in meters as (x, y, z) for 3D or (x, z) for 2D. The coordinate system has `x` and `y` axis in the horizontal plane, and `z` axis pointing upwards, with the nominal water surface being at 0 m. This means that all `z` coordinates in water are negative.
133+
134+
### Ray tracing
135+
136+
Now that we have an environment, a propation model, a transmitter and a receiver, we can modeling. First, we ask for all eigenrays between the transmitter and receiver:
137+
```julia-repl
138+
julia> r = eigenrays(pm, tx, rx)
139+
7-element Array{UnderwaterAcoustics.RayArrival{Float64,Float64},1}:
140+
∠ -2.9° 0↑ 0↓ ∠ 2.9° | 65.05 ms | -40.0 dB ϕ -0.0° ⤷
141+
∠ 8.5° 1↑ 0↓ ∠ 8.5° | 65.70 ms | -40.1 dB ϕ-180.0° ⤷
142+
∠-14.0° 0↑ 1↓ ∠-14.0° | 66.97 ms | -59.0 dB ϕ 60.5° ⤷
143+
∠ 19.3° 1↑ 1↓ ∠-19.3° | 68.84 ms | -61.3 dB ϕ-141.7° ⤷
144+
∠-24.2° 1↑ 1↓ ∠ 24.2° | 71.25 ms | -62.3 dB ϕ-153.8° ⤷
145+
∠ 28.8° 2↑ 1↓ ∠ 28.8° | 74.15 ms | -63.0 dB ϕ 19.4° ⤷
146+
∠-33.0° 1↑ 2↓ ∠-33.0° | 77.49 ms | -85.4 dB ϕ-149.4° ⤷
147+
```
148+
149+
For each eigenray, this shows us the launch angle, number of surface bounces, number of bottom bounces, arrival angle, travel time, transmission loss along that ray, and phase change. The last "``" symbol indicates that the complete ray path is also available. We can plot the ray paths:
150+
```julia-repl
151+
julia> plot(env; sources=[tx], receivers=[rx], rays=r)
152+
```
153+
![](images/eigenrays1.png)
154+
155+
Th red star is the transmitter and the blue circle is the receiver. The stronger eigenrays are shown in blue, while the weaker ones are shown in red.
156+
157+
We might sometimes want to see all rays from the transmitter at certain angular spacing (-45°:5°:45°) and a given range (100 m):
158+
```julia-repl
159+
julia> r = rays(pm, tx, -45°:5°:45°, 100.0)
160+
19-element Array{UnderwaterAcoustics.RayArrival{Float64,Float64},1}:
161+
∠-45.0° 2↑ 3↓ ∠-45.0° | 91.89 ms | -109.6 dB ϕ 27.5° ⤷
162+
∠-40.0° 2↑ 2↓ ∠ 40.0° | 84.82 ms | -86.8 dB ϕ 22.1° ⤷
163+
∠-35.0° 1↑ 2↓ ∠-35.0° | 79.32 ms | -85.9 dB ϕ-152.3° ⤷
164+
∠-30.0° 1↑ 2↓ ∠-30.0° | 75.03 ms | -85.2 dB ϕ-143.9° ⤷
165+
∠-25.0° 1↑ 1↓ ∠ 25.0° | 71.69 ms | -62.8 dB ϕ-155.2° ⤷
166+
∠-20.0° 1↑ 1↓ ∠ 20.0° | 69.14 ms | -61.9 dB ϕ-143.9° ⤷
167+
∠-15.0° 0↑ 1↓ ∠-15.0° | 67.27 ms | -59.6 dB ϕ 55.5° ⤷
168+
169+
∠ 15.0° 1↑ 1↓ ∠-15.0° | 67.27 ms | -60.0 dB ϕ-124.5° ⤷
170+
∠ 20.0° 1↑ 1↓ ∠-20.0° | 69.14 ms | -61.9 dB ϕ-143.9° ⤷
171+
∠ 25.0° 2↑ 1↓ ∠ 25.0° | 71.69 ms | -63.1 dB ϕ 24.8° ⤷
172+
∠ 30.0° 2↑ 1↓ ∠ 30.0° | 75.03 ms | -63.6 dB ϕ 18.1° ⤷
173+
∠ 35.0° 2↑ 2↓ ∠-35.0° | 79.32 ms | -86.2 dB ϕ 27.7° ⤷
174+
∠ 40.0° 2↑ 2↓ ∠-40.0° | 84.82 ms | -86.8 dB ϕ 22.1° ⤷
175+
∠ 45.0° 3↑ 2↓ ∠ 45.0° | 91.89 ms | -87.7 dB ϕ-161.7° ⤷
176+
177+
julia> plot(env; sources=[tx], rays=r)
178+
```
179+
![](images/rays1.png)
180+
181+
### Arrivals & transmission loss
182+
183+
Often, we are interested in the arrival structure or transmission loss at a receiver. Getting the arrivals is quite similar to getting eigenrays, but the ray paths are not stored:
184+
```julia-repl
185+
julia> a = arrivals(pm, tx, rx)
186+
7-element Array{UnderwaterAcoustics.RayArrival{Float64,Missing},1}:
187+
∠ -2.9° 0↑ 0↓ ∠ 2.9° | 65.05 ms | -40.0 dB ϕ -0.0°
188+
∠ 8.5° 1↑ 0↓ ∠ 8.5° | 65.70 ms | -40.1 dB ϕ-180.0°
189+
∠-14.0° 0↑ 1↓ ∠-14.0° | 66.97 ms | -59.0 dB ϕ 60.5°
190+
∠ 19.3° 1↑ 1↓ ∠-19.3° | 68.84 ms | -61.3 dB ϕ-141.7°
191+
∠-24.2° 1↑ 1↓ ∠ 24.2° | 71.25 ms | -62.3 dB ϕ-153.8°
192+
∠ 28.8° 2↑ 1↓ ∠ 28.8° | 74.15 ms | -63.0 dB ϕ 19.4°
193+
∠-33.0° 1↑ 2↓ ∠-33.0° | 77.49 ms | -85.4 dB ϕ-149.4°
194+
```
195+
196+
If we prefer, we can plot these arrivals as an impulse response (sampled at 44.1 kSa/s, in this case):
197+
```julia-repl
198+
julia> plot(abs.(impulseresponse(a, 44100; reltime=true)); xlabel="Sample #", legend=false)
199+
```
200+
![](images/ir1.png)
201+
202+
The `reltime=true` option generates an impulse response with time relative to the first arrival (default is relative to transmission time).
203+
204+
If we want, we can also get the complex transfer coefficient or the transmission loss in dB:
205+
```julia-repl
206+
julia> transfercoef(pm, tx, rx)
207+
0.013183979186458052 - 0.012267750240848727im
208+
209+
julia> transmissionloss(pm, tx, rx)
210+
34.89032959932541
211+
```
212+
213+
You can also pass in arrays of sources and receivers, if you want many transmission losses to be computed simultanously. Some models are able to compute transmission loss on a Cartesion grid very efficiently. This is useful to plot transmission loss as a function of space.
214+
215+
To define a 1000×200 Cartesion grid with 0.1 m spacing:
216+
```julia-repl
217+
julia> rx = AcousticReceiverGrid2D(1.0, 0.1, 1000, -20.0, 0.1, 200)
218+
1000×200 AcousticReceiverGrid2D{Float64}:
219+
BasicAcousticReceiver((1.0, 0.0, -20.0)) … BasicAcousticReceiver((1.0, 0.0, -0.1))
220+
BasicAcousticReceiver((1.1, 0.0, -20.0)) BasicAcousticReceiver((1.1, 0.0, -0.1))
221+
BasicAcousticReceiver((1.2, 0.0, -20.0)) BasicAcousticReceiver((1.2, 0.0, -0.1))
222+
⋮ ⋱
223+
BasicAcousticReceiver((100.7, 0.0, -20.0)) BasicAcousticReceiver((100.7, 0.0, -0.1))
224+
BasicAcousticReceiver((100.8, 0.0, -20.0)) BasicAcousticReceiver((100.8, 0.0, -0.1))
225+
BasicAcousticReceiver((100.9, 0.0, -20.0)) BasicAcousticReceiver((100.9, 0.0, -0.1))
226+
```
227+
228+
We can then compute the transmission loss over the grid:
229+
```julia-repl
230+
julia> x = transmissionloss(pm, tx, rx)
231+
1000×200 Array{Float64,2}:
232+
19.0129 19.12 19.5288 … 9.02602 8.23644 8.86055 11.1436 16.4536
233+
19.017 19.1239 19.5324 9.02506 8.26487 8.90641 11.2 16.5155
234+
19.0217 19.1284 19.5366 … 9.02392 8.29514 8.95536 11.2602 16.5817
235+
19.0271 19.1336 19.5415 9.0226 8.32706 9.00713 11.3239 16.6519
236+
⋮ ⋮ ⋱ ⋮ ⋮
237+
35.5238 35.4909 35.4954 56.1556 57.7909 59.9858 63.1631 68.5643
238+
35.5742 35.5448 35.5526 … 56.5185 58.1488 60.3365 63.5039 68.8852
239+
35.6261 35.6 35.611 56.8971 58.522 60.7023 63.8594 69.2206
240+
35.6793 35.6565 35.6704 57.2926 58.9118 61.0841 64.2306 69.5712
241+
242+
julia> plot(env; receivers=rx, transmissionloss=x)
243+
```
244+
![](images/txloss1.png)
245+
246+
### Acoustic simulations
247+
248+
Apart from propagation modeling, we can also setup a simulation with various sources and receviers.
249+
250+
We demonstrate this by setting up a scenario with two pingers (1 kHz, 10 ms pulse with 1 Hz PRR; 2 kHz, 20 ms pulse with 2 Hz PRR) with a source level of 170 dB re µPa @ 1m, at two locations, and deploying two omnidirectional receviers to record them:
251+
```julia-repl
252+
julia> using DSP: db2amp
253+
julia> tx = [
254+
Pinger(0.0, 0.0, -5.0, 1000.0; interval=1.0, duration=10e-3, sourcelevel=db2amp(170)),
255+
Pinger(0.0, 100.0, -5.0, 2000.0; interval=0.5, duration=20e-3, sourcelevel=db2amp(170))
256+
]
257+
258+
julia> rx = [
259+
AcousticReceiver(100.0, 0.0, -10.0);
260+
AcousticReceiver(50.0, 20.0, -5.0)
261+
];
262+
```
263+
264+
To carry out the simulation, we can for a 2-second long recording (at 8 kSa/s) at the receivers:
265+
```julia-repl
266+
julia> s = record(pm, tx, rx, 2.0, 8000.0)
267+
SampledSignal @ 8000.0 Hz, 16000×2 Array{Complex{Float64},2}:
268+
127308.0+884666.0im 1.15927e6-548579.0im
269+
-263820.0+1.16962e6im 1.1377e6+541803.0im
270+
-80980.6+1.16562e6im 657226.0+738712.0im
271+
272+
-447370.0+910253.0im 163952.0-436691.0im
273+
-431239.0+903852.0im 100509.0-118066.0im
274+
-391797.0+582705.0im 49383.0-679981.0im
275+
```
276+
277+
The signals are returned as complex analytic signals, but can be easily converted to real signals, if desired:
278+
```julia-repl
279+
julia> s = real(s)
280+
SampledSignal @ 8000.0 Hz, 16000×2 Array{Float64,2}:
281+
-672702.0 318731.0
282+
-825049.0 377382.0
283+
-984626.0 214490.0
284+
285+
66193.3 -497239.0
286+
-144031.0 -321312.0
287+
-260200.0 -235680.0
288+
```
289+
290+
To visualize the recording, we plot a spectrogram of the signal at the first receiver with the `SignalAnalysis.jl` package:
291+
```julia-repl
292+
julia> using SignalAnalysis
293+
julia> specgram(s[:,1])
294+
```
295+
![](images/spec1.png)
296+
297+
We can clearly see the two pingers, as well as the ambient noise generated with the noise model defined in the environment description.

0 commit comments

Comments
 (0)