VaMpy : A Python Package to Solve 1 D Blood Flow Problems

Finite-differences methods such as the Lax-Wendroff method (LW) are commonly used to solve 1D models of blood flow. These models solve for blood flow and lumen area and are useful in disease research, such as hypertension and atherosclerosis, where flow and pressure are good indicators for the presence of disease. Despite the popularity of the LW method to solve the blood flow equations, no implementation of a LW solver for these equations has been published and made publicly available. This leads to the reimplementation of the same methods within different research groups and makes verification of results more difficult. The Vascular Modelling in Python (VaMpy) toolkit is a Python package that aims to fill this gap. It implements Richtmyer’s two-step Lax-Wendroff scheme to solve 1D model equations of blood flow in arterial trees and aims at facilitating the solution of blood flow problems for various medical applications.


Introduction
One-dimensional (1D) modelling of the cardiovascular system is useful in predicting and understanding the dynamics of blood pressure propagation [1,2,3,4,5,6].Here, arteries are regarded as 1D axisymmetric tubes that are described by flux q inside the lumen and cross-sectional area A of the vessel lumen along the vessel length.One popular finite-differences method to numerically solve the equations governing blood flow through arteries is Richtmyer's two-step Lax-Wendroff method [7,8], which has been used by a number of groups [1,4,6,9,10,11].Alternative methods of solving the blood flow equations include for example variations of the Galerkin finite-element method, which instead solve the blood flow equations for flow velocity u and cross-sectional area A [2,12].
The computational implementation of the Lax-Wendroff method is straightforward and previously mentioned references have produced results that are validated against experimental results, justifying the popularity of the method.However, no openly available implementation of the Lax-Wendroff method could be found, which results in the same work being carried out numerous times.Whilst one open-source Python package implementing a haemodynamic model exists, pyNS focusses on the implementation of a 0D pulse wave propagation model, representing arteries as electrical circuits [13], and therefore its scope and application are different from VaMpy.Solutions computed using VaMpy are exported to the commonly used CSV file format, thereby allowing for the integration of data with most other software.For example, solutions calculated using VaMpy could be used as a boundary condition for higher order models of larger arteries further upstream.
Arteries are considered to be elastic axisymmetrical tubes of initial radius r 0 (z) in a cylindrical coordinate system.The radius at rest is allowed to taper exponentially for an arterial segment if different values are given for the upstream radius R u and downstream radius R d .An example geometry for the bifurcation of the common carotid artery, which is used to validate the solution calculated by VaMpy is shown in Figure 1.Then the vessel radius for an arterial segment of length L is Blood flow through arteries is governed by the Navier-Stokes equations for conservation of mass (continuity equation) and momentum in a 1D cylindrical coordinate system ( ) , where u = (u z (r, z, t), u r (r, z, t)) denotes blood flow veloctiy, p(z, t) denotes blood pressure, which is assumed to be uniform across r and the parameters ρ and ν denote blood density and viscosity, respectively.By integration of the governing equations over cross-sectional area A(z, t) = πR(z, t) 2 the 1D conservation law can be derived.Details on the derivation of (4) can be found elsewhere [1,6].Here, the unknowns are the vessel cross-sectional area A(z, t) and flux q(z, t).Elasticity of the vessel is described by the quantity f(r 0 ) with relaxed vessel radius r 0 (z), A 0 (z) is the relaxed cross-sectional vessel area, R(z, t) the vessel radius, δ b is the boundary layer thickness and Re is the Reynold's number.Although these equations have been commonly used by various groups [1,4,5,14], no publicly accessible implementation of the solution to (4) could be found, meaning that each publication from a separate group resulted in the reimplementation of the same or very similar methods and equations.Therefore, the Vascular Modelling in Python toolkit (VaMpy) was developed and published on GitHub 1 with the documentation available on GitHub Pages. 2 Support for the use of VaMpy is mainly available via the Issue Tracker feature on GitHub, but also via contacting the authors.

Implementation and architecture
The VaMpy implementation and architecture are described in this section.VaMpy is object-oriented to allow for an intuitive understanding of its design and to facilitate the addition of new features.The base of the package is the class ArteryNetwork, which defines the arterial tree.The class contains methods that are applied on the entire network of arteries as well as boundary conditions.Each artery within the tree is defined as an object of the class Artery, which contains its own solver instance.The solver itself is implemented in the independent class LaxWendroff that implements the Lax-Wendroff method as described below.This approach allows for the integration of other solvers within the software.
The code was developed in Python 2.7 and implements Richtmyer's two-step version of the Lax-Wendroff method [7,8], which is second-order accurate in time and space.For a point in time, n, the solution at the next time step n + 1 at grid location m is given by Diem and Bressloff: VaMpy Art.17, p. 3 of 7 ( ) ( ) (5) where is the solution at position m∆z and time n∆t.The half time step values for F and S are determined by for j = m ± 1/2.An illustration of the computational procedure to determine Boundary conditions are applied at both ends of the vessel and are either an inlet, outlet or bifurcation condition.The inlet boundary condition is used at the inlet of the parent vessel only [1].It requires flux values q(0, t) to be prescribed.The inlet area is then calculated according to (5) ( ) This requires the evaluation of the term , which is estimated from ( ) where is evaluated from the function prescribing flux values at the inlet and 1/ 2 1/ 2 n q + is evaluated from (6), see also [1,4].
Algorithm 1: Iterative scheme to determine 1 n M + U using a 3WK boundary condition [4].An initial guess is made for + , from which + is calculated using ( 9) and + is calculated using the discretized mass conservation equation (12).Using + is found via the state equation (11).The algorithm stops after k max iterations or when the difference between pressure estimates is less than the small threshold value .
The outlet boundary condition is a three-element Windkessel (3WK) and its implementation follows [4].The 3WK equation is ( , )( ) , where R 1 , R 2 and C T are resistance and compliance parameters.Discretization of (9) leads to where M is the spatial position of the outlet.The 3WK boundary condition requires the evaluation of pressure in the vessel, which is related to area via the discretized State Equation [1] .
The outlet boundary condition is solved using an iterative scheme with an initial guess for . This requires a discretized version of the mass conservation equation to obtain an estimate for ) Finally, a bifurcation boundary condition applies between any vessel that is not a terminal vessel and its two daughter vessels [1,4].Relations between parent and daughter vessels lead to a system of eighteen equations for eighteen unknowns, which are solved using Newton's method according to where k indicates the current iteration, = (x 1 , x 2 , ..., x 18 ), J (x k )) is the Jacobian of the system of equations and f J (x k ) is the vector of residuals.The full system of equations required to solve the boundary conditions at bifurcations can be found elsewhere [6,15].

Algorithm 2:
Setup routine for a network of arteries.The artery network is created as a binary tree and contains 2 depth -1 arteries.At each depth level the up-and downstream radii of daughter vessels are calculated using the scaling parameters a and b.Artery objects are then created for each new daughter vessel and stored in a list.
A simulation model is set up by creating an ArteryNetwork object and solved by executing the following functions from vampy.artery_network import ArteryNetwork an = ArteryNetwork(Ru, Rd, a, b, lam, k, rho, nu, p0, depth, ntr, Re) an.mesh(dx) an.set_time(dt, T[, tc]) an.initial_conditions(q0) an.solve(q_in, out_args) A network of arteries is created using the upstream and downstream radii Ru and Rd of the parent vessel, the radius-to-length ratio lam and scaling parameters a and b using Algorithm 2. Two daughter vessels are created for a parent vessel by multiplying their upstream and downstream radii with scaling parameters a and b respectively.This process is repeated until the desired tree depth is reached and the number of arteries in the network is 2 depth -1.A second setup routine exists to create an artery network, which is Art.17, p. 5 of 7 Timing parameters are the time step size dt, time of one period T and, optionally, the number of periods tc, which defaults to one if left unspecified.Initial conditions are supplied for q(z, t) as a single value q0, while the initial condition for A(z, t) is calculated from the radii at rest.The solve function is supplied with boundary condition parameters q_in and out_args, where q_in contains the values q(0, t) and out_args contains the parameters for a 3WK model.The solver loops over the simulation time steps and creates a LaxWendroff object for each Artery object lw = LaxWendroff(theta, gamma, artery.nx)with theta = dt/artery.dx,gamma = dt/2 and number of spatial steps artery.nx.The next time step is computed according to ( 5) and ( 6) at the inner grid points.Note that the time step size needs to fulfill the Courant-Friedrichs-Lewy (CFL) condition, which in this case is where is the wave speed.The CFL condition is automatically checked by the ArteryNetwork solver and the simulation stops with an error message if the condition is not met.
The following section demonstrates the use of VaMpy for the simulation of the common carotid artery as done by [4].This publication was chosen as it provides detailed information on the geometry used and Windkessel parameter for the outlet boundary condition.A detailed walkthrough of how to write configuration and simulation files can be found on the documentation website. 3

Quality control
The VaMpy Git repository contains unit tests to ensure functions perform as expected.Additionally, the file bifurcation_example.py demonstrates VaMpy's performance by validating its results results in [4] on the common carotid artery bifurcation.Whilst unit tests demonstrate that the functionality of the software meets expecations, validation against experimental results and other researchers' results ensures that the software additionally generates sensible output data and that parameters have been chosen sensibly.Users implementing arteries using other parameters than the ones tested in the example files in VaMpy should therefore always cross-check their results against experimental or other simulation results using the same parameters to ensure that the choice of parameters is realistic.
The solution computed using VaMpy is shown in Figure 3 and matches the corresponding figures in [4].Thus this example demonstrates that VaMpy performs as expected.for the same simulation in [4] validates the implementation of the blood flow equations in VaMpy.

Figure 1 :
Figure 1: Example geometry of a bifurcation implemented in VaMpy.The example represents the common carotid artery (parent vessel) and its two daughter vessels, which are used for validation purposes of the software.Artery segments have an upstream and downstream radius, where the downstream radius has to be equal to or smaller than the upstream radius.The radius of the vessel then is 0

Figure 2 .
It illustrates that both initial conditions at n = 0 for all m and left and right boundary conditions are required to determine U.

Figure 2 :
Figure 2: Illustration of the LW method.The solution is fully known at time step n (black circles) and we are looking for the solution at grid point m at time step n + 1 (white circle).To determine the unknown solution, two intermediate solutions at half grid points m ± 1/2 and at half time step n + 1/2 are determined from grid points m -1, m and m + 1 at current time step n.The intermediate solutions are then used in conjunction with the known solution at grid point m and current time step n to calculate the unknown solution at grid point m and next time step n + 1 [4].

= 1 #= 1 #
an = ArteryNetwork(Ru, Rd, lam, k, rho, nu, p0, depth, ntr, Re)Here, Ru, Rd and lam are iterables (for example lists or Numpy arrays) of length depth containing these values for each artery.The remaining parameters required by the ArteryNetwork constructor are the elasticity parameter k, blood density rho, blood viscosity nu, diastolic pressure p0, number of output parameters ntr and Reynold's number Re.The latter method is used by the examples shown in this paper.The spatial discretisation is created by supplying the spatial step size dx, which is used internally to create Numpy arrays for all variables along the vessel.pos = 0 # identifier for the next artery arteries[pos] = Artery (Ru, Rd) # list containing artery # objects radii_u = [Ru] # list containing upstream radii of arteries radii_d = [Rd] # list containing downstream radii of # arteries for j in range (depth): # lists for storage of upstream/downstream radii # of arteries on the next depth level new_radii_u = [] new_radii_d = [] for i in range (length (radii_u)): # set daughter vessel radii using scaling # parameters a and b ra_u = radii_u[i] * a rb_u = radii_u[i] * b ra_d = radii_d[i] * a rb_d = radii_d[i] * b pos + first daughter vessel using scaling # parameter a arteries[pos] = Artery (ra_u, ra_d) pos + second daughter vessel using scaling # parameter b arteries[pos] = Artery (rb_u, rb_d) # store new radii for next iteration over j new_radii_u[i] = ra_u new_radii_u[i] = rb_u new_radii_d[i] = ra_d new_radii_d[i] = rb_d radii_u = new_radii_u radii_d = new_radii_d Diem and Bressloff: VaMpy

Figure 3 :
Figure 3: One pulse in the common carotid artery using VaMpy: a) flow rate, b) pressure.Comparison with the resultsfor the same simulation in[4] validates the implementation of the blood flow equations in VaMpy.