Tracking a drifting transmitter

References

This tutorial is adapted from Example A presented in:

A version of this example was also presented in the UComms 2020 webinar:

Problem statement

Let us consider a scenario where a drifting probe acoustically transmits its sensor data periodically to a static receiver. The initial position of the sensor is perfectly known, and so is the environment. But the path of the sensor as it drifts is not known, but we’d like to get an estimate of it from the received acoustic signal. Due to the high data rate requirements, the receiver uses an equalization technique that requires an accurate estimate of the channel impulse response. We want to generate that using a propagation model and an accurate estimate of the location of the probe.

The environment is an iso-velocity channel with a constant depth of 20 m and known seabed parameters (density ρ = 1500 kg/m³, sound speed c = 1850 m/s, and attenuation δ = 0.001). The probe uses a 1-2 kHz band for data transmission, and includes 101 pilots at 10 Hz spacing to aid with channel estimation. The transmission loss can be accurately measured at those pilot frequencies, since the transmit source level is assumed to be known, but phase information is assumed to be unavailable at each pilot.

Dataset

To illustrate the idea, we generate a 60-transmission dataset with a linearly drifting path for the transmitter. Since we have an range-independent iso-velocity environment, we can use the PekerisRayTracer (otherwise we could use the RaySolver):

using UnderwaterAcoustics
using DataFrames

function 𝒴((r, d, f, ρ, c, δ))
  env = UnderwaterEnvironment(
    bathymetry = 20.0,
    seabed = FluidBoundary(ρ, c, δ)
  )
  tx = AcousticSource(r, -d, f)
  rx = AcousticReceiver(0.0, -5.0)
  pm = PekerisRayTracer(env)
  transmission_loss(pm, tx, rx)
end

data = DataFrame([(
    range=100.0 + 0.5t,
    depth=6.0 + 0.01t,
    pilots=[𝒴([100.0 + 0.5t, 6.0 + 0.01t, f, 1500.0, 1850.0, 0.001]) for f  1000.0:10.0:2000.0]
  ) for t  0.0:1.0:59.0])

Gradient descent

In order to recover the drift path of the probe, we build a simple error model for the measured pilots. We initialize the model with the known starting location of the probe, and track the probe by minimizing the error through gradient descent.

Since our propagation model is differentiable, the gradient of the error can be automatically computed during the optimization using ForwardDiff.jl.

using ForwardDiff

# channel model for pilots
pilots(r, d) = [𝒴([r, d, f, 1500.0, 1850.0, 0.001]) for f  1000.0:10.0:2000.0]

# gradient descent optimization
function chparams(data)
  history = []
  θ = [100.0, 6.0]    # known initial location
  η = [1e-4, 1e-6]    # learning rate
  for row  eachrow(data)
    err(θ) = sum(abs2, pilots(θ[1], θ[2]) .- row.pilots)  # error model
    for i  1:100      # iterations of simple gradient descent
      θ .-=  η .* ForwardDiff.gradient(err, θ)
    end
    push!(history, (range=θ[1], depth=θ[2]))
  end
  DataFrame(history)
end

p = chparams(data)

Now that we have a path estimate, let’s check it against the ground truth:

using Plots

plot(data.range, -data.depth; linewidth=2, xlabel="Range (m)", ylabel="Depth (m)", label="Ground truth")
scatter!(p.range, -p.depth; markersize=2, label="Estimated")

We have a pretty good match!