using JUDI, PyPlot, LinearAlgebra
Introduction to JUDI
JUDI is a framework for large-scale seismic modeling and inversion and designed to enable rapid translations of algorithms to fast and efficient code that scales to industry-size 3D problems. The focus of the package lies on seismic modeling as well as PDE-constrained optimization such as full-waveform inversion (FWI) and imaging (LS-RTM). Wave equations in JUDI are solved with Devito, a Python domain-specific language for automated finite-difference (FD) computations. JUDI’s modeling operators can also be used as layers in (convolutional) neural networks to implement physics-augmented deep learning algorithms. For this, check out JUDI’s deep learning extension via ChainRules.jl in this tutorial.
Physical problem setup
Grid
JUDI relies on a cartesian grid for modeling and inversion. We start by defining the parameters needed for a cartesian grid: - A shape - A grid spacing in each direction - An origin
= (201, 201) # Number of gridpoints nx, nz
shape = (10.0, 10.0) # #n meters here
spacing = (0.0, 0.0) # In meters as well origin
(0.0, 0.0)
Physical object
JUDI defines a few basic types to handle physical object such as the velocity model. The type PhyisicalParameter
is an abstract vector and behaves as a standard vector. A PhysicalParameter
can be constructed in various ways but always require the origin o
and grid spacing d
that cannot be infered from the array.
PhysicalParameter(v::Array{vDT}, d, o) where `v` is an n-dimensional array and n=size(v)
PhysicalParameter(n, d, o; vDT=Float32) Creates a zero PhysicalParameter
PhysicalParameter(v::Array{vDT}, A::PhysicalParameter) Creates a PhysicalParameter from the Array `v` with n, d, o from `A`
PhysicalParameter(v::Array{vDT, N}, n::Tuple, d::Tuple, o::Tuple) where `v` is a vector or nd-array that is reshaped into shape `n`
PhysicalParameter(v::vDT, n::Tuple, d::Tuple, o::Tuple) Creates a constant (single number) PhyicalParameter
Let’s make a simple three layer velocity model
# Define the velocity (in km/sec=m/ms)
= 1.5f0 * ones(Float32, shape)
vp :, 66:end] .= 2.0f0
vp[:, 134:end] .= 2.5f0
vp[# Create a physical parameter
= PhysicalParameter(vp, spacing, origin); VP
Let’s plot the velocities. Because we adopt a standad cartesian dimension ordering for generality (X, Z) in 2D and (X, Y, Z) in 3D, we plot the transpose of the velocity for proper visualization.
figure()
subplot(121)
imshow(vp', cmap="jet")
title("vp")
subplot(122)
imshow(VP', cmap="jet")
title("vp as a physical parameter")
PyObject Text(0.5, 1.0, 'vp as a physical parameter')
Because the physical parameter behaves as vector, we can easily perform standard operations on it.
norm(VP), extrema(VP), 2f0 .* VP, VP .^ 2
(411.6956f0, (1.5f0, 2.5f0), PhysicalParameter{Float32} of size (201, 201) with origin (0.0, 0.0) and spacing (10.0, 10.0), PhysicalParameter{Float32} of size (201, 201) with origin (0.0, 0.0) and spacing (10.0, 10.0))
Model
JUDI then provide a Model
structure that wraps multiple physical parameters toghether. A Model
accept currently only accept standard Array as an input (to be fixed #1)
= Model(shape, spacing, origin, 1f0./vp.^2f0) model
Model (n=(201, 201), d=(10.0f0, 10.0f0), o=(0.0f0, 0.0f0)) with parameters [:m]
Modeling
Now that we have a seismic model, we will generate a few shot records.
Acquisition Geometry
The first thing we need is an acquisiton geometry. In JUDI there is two ways to create a Geometry. - By hand, as we will show here - From a SEGY file, as we will show in a follow-up tutorial
We create a split-spread geomtry with sources at the top and receivers at the ocean bottom (top of second layer).
Note: - JUDI currently expects all three coordinates to be inputed to setup a Geometry in 2D as well. This will be fixed in a later version of JUDI.
# Sources position
= 11
nsrc = range(0f0, (shape[1] -1)*spacing[1], length=nsrc)
xsrc = 0f0 .* xsrc # this a 2D case so we set y to zero
ysrc = 12.5f0*ones(Float32, nsrc); zsrc
Now this definition creates a single Array of position, which would correspond to a single Simultenous source. Since we are interested in single source experiments here, we convert these position into an Array of Array of size nsrc
where each sub-array is a single source position
= convertToCell.([xsrc, ysrc, zsrc]); xsrc, ysrc, zsrc
# OBN position
= 101
nrec = range(0f0, (shape[1] -1)*spacing[1], length=nrec)
xrec = 0f0 # this a 2D case so we set y to zero. This can be a single number for receivers
yrec = (66*spacing[1])*ones(Float32, nrec); zrec
The last step to be able to create and acquisiton geometry is to define a recording time and sampling rate
= 4000f0 # Recording time in ms (since we have m/ms for the velocity)
record_time = 4f0; # Let's use a standard 4ms sampling rate sampling_rate
Now we can create the source and receivers geometry
= Geometry(xsrc, ysrc, zsrc; dt=sampling_rate, t=record_time)
src_geom # For the receiver geometry, we specify the number of source to tell JUDI to use the same receiver position for all sources
= Geometry(xrec, yrec, zrec, dt=sampling_rate, t=record_time, nsrc=nsrc); rec_geom
Let’s visualize the geometry onto the model
figure();
imshow(vp', cmap="jet", extent=[0, (shape[1]-1)*spacing[1], (shape[2]-1)*spacing[2], 0])
scatter(xsrc, zsrc, label=:sources)
scatter(xrec, zrec, label="OBN")
legend()
PyObject <matplotlib.legend.Legend object at 0x2bf0da200>
Source wavelet
For the source wavelet, we will use a standard Ricker wavelet at 10Hz for this tutorial.In practice this wavelet would be read from a file or estimated during inversion.
= 0.010 # Since we use ms, the frequency is in KHz
f0 = ricker_wavelet(record_time, sampling_rate, f0);
wavelet plot(wavelet)
1-element Vector{PyCall.PyObject}:
PyObject <matplotlib.lines.Line2D object at 0x2bf1896c0>
judiVector
In order to represent seismic data, JUDI provide the judiVector
type. This type wraps a geometry with the seismic data corresponding to it. Let’s cretae one for the source
= judiVector(src_geom, wavelet) q
judiVector{Float32, Matrix{Float32}} with 11 sources
Linear operator
The last step to model our seismic data os to create the linear operator representing the discretized wave equation on the Model we defined. We also need to define the linear operator corresponding to the source injection and the receiver interpolation.
= judiProjection(rec_geom) # receiver interpolation
Pr = judiProjection(src_geom) # Source interpolation
Ps = judiModeling(model) # Inverse of the disrete ewave equation. Ainv
JUDI forward{Float32} propagator (x * z * time) -> (x * z * time)
WARNING While these three operator are well defined in JUDI, judiProjection
is a no-op operator and cannot be used by itself but only in combination with a judiModeling
operator
Seismic data
Now that we have all our operators setup we can finally generate synthetic data wit ha simple mat-vec product thanks to the abstraction
= Pr * Ainv * Ps' * q d_obs
Building forward operator
Operator `forward` ran in 0.48 s
Operator `forward` ran in 0.68 s
Operator `forward` ran in 0.69 s
Operator `forward` ran in 0.68 s
Operator `forward` ran in 0.69 s
Operator `forward` ran in 0.68 s
Operator `forward` ran in 0.66 s
Operator `forward` ran in 0.68 s
Operator `forward` ran in 0.68 s
Operator `forward` ran in 0.66 s
Operator `forward` ran in 0.68 s
judiVector{Float32, Matrix{Float32}} with 11 sources
= [xrec[1], xrec[end], 1f-3*record_time, 0]
data_extent figure(figsize=(20, 5))
for i=1:5
subplot(1, 5, i)
imshow(d_obs.data[2*i], vmin=-1, vmax=1, cmap="Greys", extent=data_extent, aspect="auto")
xlabel("Receiver position (m)")
ylabel("Recording time (s)")
title("xsrc=$(1f-3xsrc[2*i][1])km")
end
tight_layout()