This tutorial shows how to use jInv to parallelize evaluations of your forward operator. We will see three modes for coputing the forward problems
ForwardProbType
gives a single forward problem that is evaluated by one worker. ForwardProbType
each containing a subset of sources and storing them in an Array the forward problems are solved in parallel and the assignment of problems to workers is created dynamically.ForwardProbType
per worker and dividing the sources among them yields an array of RemoteChannel
objects. The assignment is created prior to running the problem and then kept fixed. We refer to this as static scheduling. All of the above strategies give the same results and can be used with the same syntax getData(model,pFor)
thanks to Julia's multiple dispatch. The optimal choice depends on the characteristics of the problem at hand as well as on the available computational resources.
Please refer to [1] for a detailed description of the parallelization strategies in jInv. The fast marching method used to solve the Eikonal equation is presented in [2]. The test data used here is generated using jInv and based on the 3D SEG/EAGE model of a salt reservoir described in [3].
[1] Ruthotto L, Treister E, Haber E: jInv - a flexible Julia package for PDE parameter estimation, arXiv:1606.0739 [cs.MS] 2016
[2] Treister E, Haber E: A fast marching algorithm for the factored eikonal equation, Journal of Computational Physics, 324(1), p. 210–225, 2016
[3] F. Aminzadeh, B. Jean, and T. Kunz. 3-D salt and overthrust models. Society of Exploration Geophysicists, 1997.
Before we start, we should add workers to the session. Here, we add local workers, however, the workers added could also be on remote computers (see also Julia Documentation)
In [1]:
addprocs(8)
Out[1]:
In [2]:
using MAT
using EikonalInv
using jInv.Mesh
using jInv.ForwardShare
using jInv.Utils
using Test
In [3]:
matfile = matread("exEikonal.mat")
Receivers = matfile["Receivers"]# sparse matrix of discrete receivers
Sources = matfile["Sources"] # sparse matrix of discrete sources
slowref = matfile["slowref"] # reference slowness, serves as starting guess;
domain = matfile["domain"]
n = matfile["n"]
# set up mesh
h = (domain[2:2:end]-domain[1:2:end])./n
domainNodal = copy(domain); domainNodal[1:2:end] += h./2; domainNodal[2:2:end] -= h./2
Mfwd = getRegularMesh(domain,n-1);
The simplest way to evaluate the forward operator and simulate data is by generating a single EikonalInvParam
containing all sources. This way, there is no parallelization except the one included in the method for solving the forward problem, which in the case of fast marching means almost no parallelization.
In [5]:
pFor = getEikonalInvParam(Mfwd,Sources,Receivers,false,false);
@time dobs,pFor = getData(slowref,pFor);
The second, more advanced option is to divide the sources into different instances of EikonalInvParam
and use dynamic scheduling. Note that simulating the data can be done independently for different sources. Thus, when computing the data, the problems are spawned out dynamically to the next available worker. Thereby latency times are limited, however, all data required to solve the problem (including meshes, differential operators, etc) need to be sent to the remote worker.
In [7]:
pForp = Array{EikonalInvParam}(size(Sources,2))
for k=1:size(Sources,2)
pForp[k] = getEikonalInvParam(Mfwd,SparseMatrixCSC(Sources[:,k]),Receivers,false,false)
end
@time begin
# solve forward problems in parallel
dobspFuture,pForp = getData(slowref,pForp);
# fetch results from workers
dobsp = zeros(size(Receivers,2),size(Sources,2))
for k=1:size(Sources,2)
dobsp[:,k] = fetch(dobspFuture[k])
end
end
# make sure that results corresponds to serial computation
@test norm(dobsp-dobs)/norm(dobs) < 1e-14
Out[7]:
A third option is to divide the sources among the available workers before calling the forward problem and then keeping the assignmnent fixed. We refer to this as static scheduling. Here, the EikonalInvParam
is actually initialized on the remote worker and does not need to be sent back and forth. This way communication is kept at a minimum, but latency times might occur if some problems take longer to solve than others.
In [9]:
pForr = Array{RemoteChannel}(nworkers())
# create forward problems on remote workers
SourcesSubInd = Array{Any}(nworkers())
for k=1:nworkers()
SourcesSubInd[k] = collect(k:nworkers():size(Sources,2))
pForr[k] = initRemoteChannel(getEikonalInvParam, workers()[k], Mfwd,Sources[:,SourcesSubInd[k]],Receivers,false,false)
end
@time begin
# solve forward problems in parallel
dobsrFuture,pForr = getData(slowref,pForr);
# fetch results
dobsr = zeros(size(Receivers,2),size(Sources,2))
for k=1:nworkers()
dobsr[:,SourcesSubInd[k]] = fetch(dobsrFuture[k])
end
end
# make sure that results corresponds to serial computation
@test norm(dobsr-dobs)/norm(dobs) < 1e-14
Out[9]:
In [ ]: