(1) Overview

Introduction

One-dimensional (1D) modelling of the cardiovascular system is useful in predicting and understanding the dynamics of blood pressure propagation [, , , , , ]. 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 [, ], which has been used by a number of groups [, , , , , ]. 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 [, ].

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 [], 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 r0(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 Ru and downstream radius Rd. 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

(1)
r0(z)=Ru·exp(log(RdRu)zL).
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 r0(z)= Ruexp(log(Rd/Ru)z/L).

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

(2)
uz(r,z,t)z+1r(rur(r,z,t))t=0
(3)
uz(r, z, t)t+uz(r, z, t)uz(r, z, t)z+ ur(r, z, t)uz(r, z, t)r+1ρp(z, t)z=νrr (ruz(r, z, t)r),

where u = (uz (r, z, t), ur (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

(4)
Ut+Fz=S

with

U=(A(z, t)q(z, t)),   F=(q(z, t)q(z, t)2A(z, t)+f(r0)A0(z)A(z, t)),S=(0S1)S1=2πR(z, t)δbReq(z, t)A(z, t)+    (2A(z, t)(πf(r0)+A0(z)df(r0)dr0)         A(z, t)df(r0)dr0)dr0(z)dz

can be derived. Details on the derivation of (4) can be found elsewhere [, ]. 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(r0) with relaxed vessel radius r0(z), A0(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 [, , , ], 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 with the documentation available on GitHub Pages. 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 [, ], 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

(5)
Umn+1=UmnΔtΔz(Fm+1/2n+1/2Fm1/2n+1/2)+              Δt2(Sm+1/2n+1/2+Sm1/2n+1/2),

where Umn =U(mΔz,nΔt) is the solution at position mΔz and time nΔt. The half time step values for F and S are determined by

(6)
Ujn+1/2=Uj+1/2n+Uj1/2n2+Δt2                  (Fj+1/2nFj1/2nΔz+Sj+1/2n+Sj1/2n2)

for j = m ± 1/2. An illustration of the computational procedure to determine UMn+1 is shown in 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 

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 [].

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 []. It requires flux values q(0, t) to be prescribed. The inlet area is then calculated according to (5)

(7)
A0n+1=A0nΔtΔz(q1/2n+1/2q1/2n+1/2).

This requires the evaluation of the term q1/2n+1/2, which is estimated from

(8)
q0n+1/2=12(q1/2n+1/2+q1/2n+1/2),

where q0n+1/2 is evaluated from the function prescribing flux values at the inlet and q1/2n+1/2 is evaluated from (6), see also [, ].

Algorithm 1: Iterative scheme to determine UMn+1 using a 3WK boundary condition []. An initial guess is made for pMn+1, from which qMn+1 is calculated using (9) and AMn+1 is calculated using the discretized mass conservation equation (12). Using AMn+1 the next iteration of pMn+1 is found via the state equation (11). The algorithm stops after kmax iterations or when the difference between pressure estimates is less than the small threshold value ϵ.

pMn+1=pmn#initial guess for pMn+1k=0for kkmax:      pold=pMn      qMn+1=qMn+pMn+1pMnR1+ΔtpMnR1R2CTΔtqMn(R1+R2)R1R2CT      AMn+1=A0nΔtΔz(qMn+1qM1n+1)      pMn+1=fMn+1(1(A0)MAMn+1)      if |poldpMn+1| ​​​ϵ:             break      k=k+1

The outlet boundary condition is a three-element Windkessel (3WK) and its implementation follows []. The 3WK equation is

(9)
p(z, t)t=R1q(z, t)tp(z, t)R2CT+                        q(z, t)(R1+R2)R2CT,

where R1, R2 and CT are resistance and compliance parameters. Discretization of (9) leads to

(10)
pMn+1pMnΔt=R1qMn+1qMnΔtpMnR2CT+                           qMn(R1+R2)R2CT,

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 []

(11)
pMn+1=fMn+1(1(A0)MAMn+1).

The outlet boundary condition is solved using an iterative scheme with an initial guess for pMn+1 (see Algorithm 1). This requires a discretized version of the mass conservation equation to obtain an estimate for AMn+1

(12)
AMn+1=A0nΔtΔz(qMn+1qM1n+1).

Finally, a bifurcation boundary condition applies between any vessel that is not a terminal vessel and its two daughter vessels [, ]. 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

(13)
xk+1=xk(J(xk))1fJ(xk)  for k=0,1,2,

where k indicates the current iteration, = (x1, x2, …, x18), J (xk)) is the Jacobian of the system of equations and fJ (xk) is the vector of residuals. The full system of equations required to solve the boundary conditions at bifurcations can be found elsewhere [, ].

Algorithm 2: Setup routine for a network of arteries. The artery network is created as a binary tree and contains 2depth – 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.

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 + = 1
          # first daughter vessel using scaling
          # parameter a
          arteries[pos] = Artery (ra_u, ra_d)
          pos + = 1
          # 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

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 2depth – 1. A second setup routine exists to create an artery network, which is

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. 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

(14)
ΔtΔx|qA±c|1,

where c=A/ρ p/A 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 []. 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.

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 against results in [] 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 []. Thus this example demonstrates that VaMpy performs as expected.

Figure 3 

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

To execute the example run

python bifurcation_example.py bifurcation.cfg

To plot the data created from the example run

python plot_example.py bifurcation.cfg

The first version of VaMpy focusses on the simulation of a single bifurcation, i. e. one parent vessel with two daughter vessels. The development of the first version of VaMpy was based on the simulation of flow through the middle cerebral artery in order to evaluate lymphatic drainage through the wall of the artery [], and for this purpose a single bifurcation was regarded sufficient. Validation on larger networks of arteries with multiple levels of bifurcations has therefore not been carried out yet, but is planned for the next release cycle. Additionally, it is planned to offer a choice of alternative outlet boundary conditions, such as the structured tree [, ]. It has been demonstrated that by taking into account bifurcation pressure drops, the accuracy of reduced order models such as the system of equations (4) can improve significantly compared to higher order models []. This means that a similar accuracy of blood flow solutions could be achieved for 1D models compared to 2D or 3D models by increasing the depth of the arterial tree to be modelled.

(2) Availability

Operating system

VaMpy is compatible with any operating system that is compatible with Python 2.7 and the dependent packages.

Programming language

VaMpy was written in and for Python 2.7 and above.

Additional system requirements

There are no additional system requirements. However, the requirements for memory and processing power are dependent on the number of the grid points.

Dependencies

NumPy, SciPy, Matplotlib, ConfigParser.

Software location

Archive

Name: GitHub

Persistent identifier: https://github.com/akdiem/vampy/releases/tag/v1.0

Licence: Three-Clause BSD

Publisher: Alexandra K. Diem

Version published: v1.0

Date published: 22/03/2017

Code repository

Name: GitHub

Persistent identifier: https://github.com/akdiem/vampy

Licence: Three-Clause BSD

Date published: 26/04/2016

Language

Python 2.7

(3) Reuse potential

Modelling blood flow dynamics is a useful tool in vascular diseases research and 1D models provide good approximations. The method implemented in VaMpy is used by a variety of research groups [, , ] and therefore it is expected that the reuse potential for VaMpy is high, especially in multiscale simulations. Because the commonly accepted CSV file format is used for input and output data for VaMpy integration of results from VaMpy simulations with other third-party software packages is expected to be straightforward. For example, VaMpy could be used as a boundary condition for 3D simulations or constitute a part of multi-scale simulations.

The publication of this software additionally provides opportunities for other researchers to add functionality and because VaMpy has been validated on results published in the literature it simplifies and promotes reproducibility of results. The following features are planned for the next releases:

  • asymmetric daughter vessel geometries with separate Windkessel parameters,
  • validation of the method on bifurcation networks larger than two levels and
  • integration of models of the dynamics of the artery wall.

The current release of VaMpy was developed to implement a bifurcation at the middle cerebral artery as part of a multi-scale model of lymphatic flow through the basement membrane embedded in the artery wall, which is relevant for resolving the mechanisms behind the onset and progression of Alzheimer’s disease [, ].