godunov.jl |
|||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1D blood flow within elastic vessels is ruled by a system of hyperbolic partial derivative equations \[ \begin{cases} \dfrac{\partial A}{\partial t} + \dfrac{\partial Au}{\partial x} = 0, \\ \dfrac{\partial Au}{\partial t} + \dfrac{\partial Au^2}{\partial x} \dfrac{A}{\rho}\dfrac{\partial P}{\partial x} = - Ru, \\ P(x, t) = P_e(x, t) + K_a \left[\left(\dfrac{A}{A_0} \right)^{1/2} -1 \right], \end{cases} \] By defining \(\mathbf{U} = [A, u]^T\), the conservative form of the system can be written along with initial and boundary conditions as \[ \begin{cases} \mathbf{U}_t + \mathbf{F}(\mathbf{U})_x = \mathbf{S}(\mathbf{U}), \quad x \in \big[0, L \big], \\ \mathbf{U}(x, 0) = \mathbf{U}^{(0)}(x), \quad t>0, \\ \mathbf{U}(0, t) = \mathbf{U}_l(t), \mathbf{U}(L, t) = \mathbf{U}_r(t), \end{cases} \] where \(\mathbf{F}\) is the flux term, and \(\mathbf{S}\) is the source term. This set of equations and initial and boundary conditions is also called Initial Boundary Value Problem (IBVP). The IBVP is solved by means of the Godunov’s method 1, a conservative first order finite volume method. The method is applied on a computational domain (red) subdivided in \(M\) cells \(I_i\) (gray). |
|
||||||||||||||||||
|
|
||||||||||||||||||
Domain length is equal to vessel length \(L\), and cell length is given by \(\Delta x\) size. The total simulation time \(T\) defines domain height, hence cell height is given by \(\Delta t\). In each cell \(I_i\) three points are defined: center point \(x_i\), left extreme \(x_{i-1/2}\), and right extreme \(x_{i+1/2}\). |
|
||||||||||||||||||
|
|
||||||||||||||||||
Godunov’s method assumes a piece-wise constant distribution of \(\mathbf{U}\) at each \(t = t^n\). |
|
||||||||||||||||||
|
|
||||||||||||||||||
For a small enough \(\Delta t\), the method updates the solution as \[ \mathbf{U}_i^{n+1} = \mathbf{U}_i^{n} + \frac{\Delta t}{\Delta x} \left(\mathbf{F}_{i-1/2} - \mathbf{F}_{i+1/2} \right), \] where \(\mathbf{F}_{i\pm 1/2} = \mathbf{F}\left(\mathbf{U}_{i\pm 1/2}(0) \right)\) is the numerical flux. The argument of the numerical flux, \(\mathbf{U}_{i\pm 1/2}(0)\), is computed as the solution of a homogeneous ( the source term will be added later on) quasi-linear local Riemann problem (RP) at the interface of two cells. For \(\mathbf{U}_{i + 1/2}(0)\) we have \[ \begin{cases} \mathbf{U}_t + \mathbf{F}_\mathbf{U} \mathbf{U}_x = 0, \\ \mathbf{U}(x_i, 0) = \mathbf{U}^{(0)}(x_i) = \begin{cases} \mathbf{U}_L = \mathbf{U}(x_{i-1/2}), \quad x<0, \\ \mathbf{U}_R = \mathbf{U}(x_{i+1/2}), \quad x>0. \\ \end{cases} \end{cases} \] The solution of the RP is depicted below. |
|
||||||||||||||||||
|
|
||||||||||||||||||
The solution structure includes two families of waves associated to the two system Riemann invariants \(\lambda_1\) and \(\lambda_2\), respectively. There are three constant states \(\mathbf{U}_L\), \(\mathbf{U}^*\), and \(\mathbf{U}_R\), of which \(\mathbf{U}^*\) is unknown and refers to the solution in the star region (yellow). We need jump conditions to cross the waves and connect these states. Depending on the nature of each wave we have shock or rarefaction conditions. |
|
||||||||||||||||||
|
|
||||||||||||||||||
function
|
function Godunov(i :: Int64, v :: Vessel, dt :: Float64, b :: Blood)
|
||||||||||||||||||
|
|
||||||||||||||||||
First we compute the states \(\mathbf{U}_L\) and \(\mathbf{U}_R\) at the left hand side interface (\(i-1/2\)). When we deal with the inlet node (\(i=1\)), blue ghost cells are used to find left states. Right states are always inside the numerical domain and they do not need ghost cells. |
#i-1/2 local left
if i-1 == 0 #1st ghost cell
UlA = v.U00A
UlQ = v.U00Q
elseif i-1 == -1 #2nd ghost cell
UlA = v.U01A
UlQ = v.U01Q
else
UlA = v.A[i-1]
UlQ = v.Q[i-1]
end
#local right
UrA = v.A[i]
UrQ = v.Q[i]
|
||||||||||||||||||
Once the local states have been computed, the solution of the RP at the left interface is found by |
Al, Ql = riemannSolver(UlA, UlQ, UrA, UrQ, v)
|
||||||||||||||||||
The same process is applied to the local right interface. |
#i+1/2 local right
if i+1 == v.M+1 #1st ghost cell
UrA = v.UM1A
UrQ = v.UM1Q
elseif i+1 == v.M+2 #2nd ghost cell
UrA = v.UM2A
UrQ = v.UM2Q
else
UrA = v.A[i+1]
UrQ = v.Q[i+1]
end
#local left
UlA = v.A[i]
UlQ = v.Q[i]
Ar, Qr = riemannSolver(UlA, UlQ, UrA, UrQ, v)
|
||||||||||||||||||
When both interfaces are solved, left and right (\(\mathbf{F}_{i\mp 1/2}\)) numerical fluxes are computed by |
Fl1, Fl2 = flux(Al, Ql, v.gamma)
Fr1, Fr2 = flux(Ar, Qr, v.gamma)
|
||||||||||||||||||
Conservative variables |
v.A[i] += dt/v.dx * (Fl1-Fr1)
v.Q[i] += dt/v.dx * (Fl2-Fr2)
|
||||||||||||||||||
The source term is solved by |
v.Q[i] += source(dt, v.A[i], v.u[i], b)
|
||||||||||||||||||
Eventually, primitive variables |
v.P[i] = pressure(v.A[i], v.A0, v.beta, v.Pext)
v.u[i] = v.Q[i]/v.A[i]
v.c[i] = waveSpeed(v.A[i], v.gamma)
end
|
||||||||||||||||||
The RP solution is herein coded. The Riemann solver computes the cross-sectional area in the star region \(A^*\) starting from a guess solution (two rarefactions solution). The value of \(A^*\) comes from the solution of the nonlinear equation \[
f(A^*) = f_L(A^*) + f_R(A^*) + \Delta u = 0,
\] where \(\Delta u = u_R - u_L\), and \(f_L\) and \(f_R\) are the local fluxes computed by |
|
||||||||||||||||||
function
|
function riemannSolver(Al :: Float64, Ql :: Float64,
Ar :: Float64, Qr :: Float64,
v :: Vessel)
|
||||||||||||||||||
|
#initialise solution
ur = Qr/Ar
ul = Ql/Al
cl = waveSpeed(Al, v.gamma)
cr = waveSpeed(Ar, v.gamma)
du = ur - ul
#two rarefaction solution
cs = 0.5 * (cl + cr) - (ur - ul)*0.125
As = 4/9 * (cs^4)/(v.gamma*v.gamma)
|
||||||||||||||||||
The nonlinear equation for the RP is defined and solved by Newton’s method. |
fun(As) = fL(As, Al, cs, cl, v) + fR(As, Ar, cs, cr, v) + du
As = newton(fun, As)
|
||||||||||||||||||
Eventually, \(u^*\) is computed as \[ \frac{1}{2}\left(u_L + u_R\right) + \frac{1}{2}\left[f_R\left(A^*\right) -f_L\left(A^* \right) \right], \] |
us = 0.5*((ur+ul) + (fR(As, Ar, cs, cr, v) - fL(As, Al, cs, cl, v)))
|
||||||||||||||||||
\(Q^*\) is calculated from its definition and returned. |
Qs = As * us
|
||||||||||||||||||
|
return As, Qs
end
|
||||||||||||||||||
Local fluxes (left and right) are computed depending on the nature of the wave system located at the interface. In particular \[ f_L = \begin{cases} 4\left(c^* - c_L\right), & A^*\leq A_L \quad \text{(rarefaction)}, \\ \sqrt{\dfrac{\gamma\left(A^*-A_L\right)\left(A^{* 3/2}- A_L^{3/2}\right)}{A_L A^*}}, & A^*>A_L \quad \text{(shock)}, \end{cases} \] \[ f_R = \begin{cases} 4\left(c^* - c_R\right), & A^*\leq A_R \quad \text{(rarefaction)}, \\ \sqrt{\dfrac{\gamma\left(A^*-A_R\right)\left(A^{* 3/2}- A_R^{3/2}\right)}{A_R A^*}}, & A^*>A_R \quad \text{(shock)}, \end{cases} \] |
|
||||||||||||||||||
function
|
function fL(A, Al :: Float64, c :: Float64, cl :: Float64, v :: Vessel)
if A <= Al
return 4*(c - cl)
else
return sqrt(
(v.gamma*(A - Al)*(A^1.5 - Al^1.5)) /
(Al*A)
)
end
end
|
||||||||||||||||||
function
|
function fR(A, Ar :: Float64, c :: Float64, cr :: Float64, v :: Vessel)
if A <= Ar
return 4*(c - cr)
else
return sqrt(
(v.gamma*(A - Ar)*(A^1.5 - Ar^1.5)) /
(Ar*A)
)
end
end
|
||||||||||||||||||
The flux in conservative form reads \[
\mathbf{F}(\mathbf{U}) = \left\{ \begin{array}{c}
Au \\
Au^2 + \gamma A^{3/2}
\end{array} \right\},
\] and its numerical form is computed by |
|
||||||||||||||||||
function
|
function flux(A :: Float64, Q :: Float64, gamma :: Float64)
f1 = Q
f2 = Q*Q/A + gamma*A^1.5
return f1, f2
end
|
||||||||||||||||||
function
|
function source(dt :: Float64, A :: Float64, u :: Float64, b :: Blood)
return -b.Cf*u*dt
end
|
||||||||||||||||||
function
|
function calculateDeltaT(vessels, dt :: Array{Float64, 1})
|
||||||||||||||||||
For each vessel the \(\Delta t\) is computed as \[ \Delta t = \frac{\Delta x}{S_{max}} C_{cfl} \] where \(S_{max}\) is the maximum between the forward and the backward characteristics, and \(C_{cfl}\) is the Courant-Friedrichs-Lewy condition defined by the user. |
i = 1
for v in vessels
lambdap = zeros(Float64, v.M)
for j in 1:v.M
lambdap[j] = v.u[j] + v.c[j]
end
Smax = maximum(abs.(lambdap))
dt[i] = v.dx*v.Ccfl/Smax
i+=1
end
|
||||||||||||||||||
|
return minimum(dt)
end
|
||||||||||||||||||
function
|
function solveModel(vessels, heart :: Heart, edge_list,
blood :: Blood, dt :: Float64, current_time :: Float64)
for j in 1:size(edge_list)[1]
i = edge_list[j,1]
s = edge_list[j,2]
t = edge_list[j,3]
v = vessels[i]
lbl = v.label
if size(find(edge_list[:,3] .== s))[1] == 0
openBF.setInletBC(current_time, dt, v, heart)
end
openBF.MUSCL(v, dt, blood)
if size(find(edge_list[:,2] .== t))[1] == 0
openBF.setOutletBC(dt, v)
elseif size(find(edge_list[:,2] .== t))[1] == 2
d1_i = find(edge_list[:,2] .== t)[1]
d2_i = find(edge_list[:,2] .== t)[2]
openBF.joinVessels(blood, v, vessels[d1_i], vessels[d2_i])
elseif size(find(edge_list[:,3] .== t))[1] == 1
d_i = find(edge_list[:,2] .== t)[1]
openBF.joinVessels(blood, v, vessels[d_i])
elseif size(find(edge_list[:,3] .== t))[1] == 2
p1_i = find(edge_list[:,3] .== t)[1]
p2_i = find(edge_list[:,3] .== t)[2]
if maximum([p1_i, p2_i]) == i
p2_i = minimum([p1_i, p2_i])
d = find(edge_list[:,2] .== t)[1]
openBF.solveAnastomosis(v, vessels[p2_i], vessels[d])
end
end
end
end
|
||||||||||||||||||
References
|
|