Skip to content

Commit

Permalink
Add solver params (#21)
Browse files Browse the repository at this point in the history
- Change the API of:
   update_static_flow_pressure(graph, boundary_flow, params)
- PETSc solver parameters can be set in the params.yaml file. 
- Removed unit test on pressure because values may change up to a constant
- Set real values for speed in compute_static_flow_pressure
  • Loading branch information
nico-canta authored Jan 24, 2024
1 parent 1586fc1 commit 9eb1604
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 90 deletions.
41 changes: 28 additions & 13 deletions astrovascpy/bloodflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
from astrovascpy.utils import mpi_mem
from astrovascpy.utils import mpi_timer

from .typing import VasculatureParams

# PETSc is compiled with complex number support
# -> many warnings from/to PETSc to/from NumPy/SciPy
warnings.filterwarnings(action="ignore", category=np.ComplexWarning)
Expand Down Expand Up @@ -81,23 +83,31 @@ def compute_static_laplacian(graph, blood_viscosity, with_hematocrit=True):
def update_static_flow_pressure(
graph: Graph,
input_flow: npt.NDArray[np.float64],
blood_viscosity: float,
base_pressure: float,
params: VasculatureParams,
with_hematocrit: bool = True,
):
"""Compute the time-independent pressure and flow.
Args:
graph (utils.Graph): graph containing point vasculature skeleton.
input_flow(numpy.array): input flow for each graph node.
blood_viscosity (float): plasma viscosity in g.µm^-1.s^-1
base_pressure (float): minimum pressure in the output edges
params (dict): general parameters for vasculature.
with_hematocrit (bool): consider hematrocrit for resistance model
Concerns: This function is part of the public API. Any change of signature
or functional behavior may be done thoroughly.
"""

if graph is not None:
if not isinstance(graph, Graph):
raise BloodFlowError("'graph' parameter must be an instance of Graph")
for param in VasculatureParams.__annotations__:
if param not in params:
raise BloodFlowError(f"Missing parameter '{param}'")
blood_viscosity = params["blood_viscosity"]
base_pressure = params["p_base"]

if graph is not None:
entry_flow = input_flow[input_flow > 0]
exit_flow = input_flow[input_flow < 0]
Expand All @@ -124,7 +134,7 @@ def update_static_flow_pressure(
else:
laplacian_cc = None

solution = _solve_linear(laplacian_cc if graph else None, input_flow)
solution = _solve_linear(laplacian_cc if graph else None, input_flow, params)

if graph is not None:
pressure = np.zeros(shape=graph.n_nodes)
Expand Down Expand Up @@ -551,9 +561,6 @@ def simulate_ou_process(
- np.ndarray: (nb_iteration, n_edges) radius values at each time-step for each edge.
"""

BLOOD_VISCOSITY = params["blood_viscosity"]
P_BASE = params["p_base"]

nb_iteration = round(simulation_time / time_step)
# nb_iteration_noise = number of time_steps before relaxation starts:
nb_iteration_noise = round(relaxation_start / time_step)
Expand Down Expand Up @@ -601,7 +608,7 @@ def simulate_ou_process(
# Compute nodes of degree 1 where blood flows out
boundary_flow = boundary_flows_A_based(graph, entry_nodes, input_flows)

update_static_flow_pressure(graph, boundary_flow, BLOOD_VISCOSITY, P_BASE)
update_static_flow_pressure(graph, boundary_flow, params)

if graph is not None:
flows[time_it] = graph.edge_properties["flow"]
Expand Down Expand Up @@ -632,12 +639,13 @@ def construct_static_incidence_matrix(graph):
return sparse.csc_matrix((data, (row, col)), shape=(graph.n_edges, graph.n_nodes))


def _solve_linear(laplacian, input_flow):
def _solve_linear(laplacian, input_flow, params=None):
"""Solve sparse linear problem on the largest connected component only.
Args:
laplacian (scipy.sparse.csc_matrix): laplacian matrix associated to the graph.
input_flow(scipy.sparse.lil_matrix): input flow for each graph node.
params (dict): general parameters for vasculature.
Returns:
scipy.sparse.csc_matrix: frequency dependent laplacian matrix
Expand All @@ -654,6 +662,13 @@ def _solve_linear(laplacian, input_flow):
"""
)

if params is None:
params = {}

SOLVER = params.get("solver", "lgmres") # second argument is default
MAX_IT = params.get("max_it", 1e3)
R_TOL = params.get("r_tol", 1e-12)

if MPI_RANK == 0:
if sparse.issparse(input_flow):
input_flow = input_flow.toarray()
Expand Down Expand Up @@ -715,7 +730,7 @@ def _solve_linear(laplacian, input_flow):

opts = PETSc.Options()
# solver
opts["ksp_type"] = "lgmres"
opts["ksp_type"] = SOLVER
opts["ksp_gmres_restart"] = 100
# preconditioner
opts["pc_type"] = "gamg"
Expand All @@ -728,9 +743,9 @@ def _solve_linear(laplacian, input_flow):

petsc_solver = PETSc.KSP().create(PETSc.COMM_WORLD)
petsc_solver.setOperators(laplacian_petsc)
petsc_solver.rtol = 1e-12
petsc_solver.rtol = R_TOL
# petsc_solver.atol = 1e-9
petsc_solver.max_it = 1e3
petsc_solver.max_it = MAX_IT
petsc_solver.setFromOptions()

PETSc.Sys.Print("-> PETSc Solver [bloodflow.py] : Start")
Expand Down
2 changes: 2 additions & 0 deletions astrovascpy/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ class VasculatureParams(typing.TypedDict):
max_nb_inputs: int
depth_ratio: float
vasc_axis: VasculatureAxis
blood_viscosity: float
p_base: float
21 changes: 11 additions & 10 deletions examples/compute_static_flow_pressure.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
from astrovascpy.utils import create_entry_largest_nodes
from astrovascpy.utils import mpi_mem
from astrovascpy.utils import mpi_timer
from astrovascpy.vtk_io import vtk_writer

petsc4py.init(sys.argv)

Expand Down Expand Up @@ -83,21 +82,21 @@

PETSc.Sys.Print("compute input flow")

with mpi_timer.region("compute input flow"), mpi_mem.region("compute input flow"):
input_flows = len(entry_nodes) * [1.0] if graph is not None else None
with mpi_timer.region("compute boundary flows"), mpi_mem.region("compute boundary flows"):
if graph is not None:
entry_speed = 35000 # speed um/s
radii_at_entry_nodes = graph.diameters[entry_nodes] / 2
input_flows = entry_speed * np.pi * radii_at_entry_nodes**2
else:
input_flows = None
boundary_flow = bloodflow.boundary_flows_A_based(graph, entry_nodes, input_flows)

PETSc.Sys.Print("end of input flow")
PETSc.Sys.Print("end of input flow \n")

PETSc.Sys.Print("compute static flow")

with mpi_timer.region("compute static flow"), mpi_mem.region("compute static flow"):
bloodflow.update_static_flow_pressure(
graph,
boundary_flow,
blood_viscosity=params["blood_viscosity"],
base_pressure=params["p_base"],
)
bloodflow.update_static_flow_pressure(graph, boundary_flow, params)

PETSc.Sys.Print("end of static flow pressure")

Expand Down Expand Up @@ -264,6 +263,8 @@
print("end of sonata reporting")

if save_vtk:
from astrovascpy.vtk_io import vtk_writer

vtk_path = Path(params["output_folder"]) / "vtk_files"
if not vtk_path.exists():
Path.mkdir(vtk_path)
Expand Down
5 changes: 5 additions & 0 deletions examples/data/params.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,8 @@ t_2_max_capill: 2.7 # time (in seconds) to reach r_max from 0
max_r_artery: 1.23 # max radius change factor
mean_r_artery: 1.11 # mean radius change factor
t_2_max_artery: 3.3 # time (in seconds) to reach r_max from 0

# PETSc Linear solver
solver: 'lgmres'
max_it: 1000
r_tol: 1.0e-12
3 changes: 2 additions & 1 deletion examples/simulate_OU_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
from astrovascpy.utils import create_input_speed
from astrovascpy.utils import mpi_mem
from astrovascpy.utils import mpi_timer
from astrovascpy.vtk_io import vtk_writer

petsc4py.init(sys.argv)

Expand Down Expand Up @@ -142,6 +141,8 @@
print("end of sonata reporting", flush=True)

if save_vtk:
from astrovascpy.vtk_io import vtk_writer

vtk_path = Path(params["output_folder"]) / "vtk_files"
if not vtk_path.exists():
Path.mkdir(vtk_path)
Expand Down
63 changes: 24 additions & 39 deletions tests/test_bloodflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,22 @@ def edge_properties():
)


@pytest.fixture
def params():
return {
"blood_viscosity": 0.1,
"p_base": 1.33e-3,
"max_nb_inputs": 3,
"depth_ratio": 0.05,
"vasc_axis": 1,
"threshold_r": 3,
"max_r_capill": 1.38,
"t_2_max_capill": 2.7,
"max_r_artery": 1.23,
"t_2_max_artery": 3.3,
}


def test_compute_edge_resistances():
radii = np.array([1, 1.25])
resistances = tested.compute_edge_resistances(radii, blood_viscosity=1.2e-6)
Expand Down Expand Up @@ -185,7 +201,7 @@ def test_get_closest_edges(point_properties, edge_properties, caplog):
tested.get_closest_edges(args, graph)


def test_simulate_vasodilation_ou_process(point_properties, edge_properties):
def test_simulate_vasodilation_ou_process(point_properties, edge_properties, params):
graph = utils.Graph(point_properties, edge_properties)
dt = 0.01
nb_iteration = 100
Expand All @@ -196,16 +212,6 @@ def test_simulate_vasodilation_ou_process(point_properties, edge_properties):
endfoot_id = 1
endfeet_length = 1.0

params = {
"blood_viscosity": 1.2e-6,
"p_base": 1.33e-3,
"threshold_r": 3,
"max_r_capill": 1.38,
"t_2_max_capill": 2.7,
"max_r_artery": 1.23,
"t_2_max_artery": 3.3,
}

tested.set_endfoot_id(graph, endfoot_id, section_id, segment_id, endfeet_length)
radius_origin = tested.get_radius_at_endfoot(graph, endfoot_id)[:, 0]
radii = tested.simulate_vasodilation_ou_process(
Expand All @@ -221,7 +227,7 @@ def test_simulate_vasodilation_ou_process(point_properties, edge_properties):
assert proba_rad < 0.05


def test_simulate_ou_process(point_properties, edge_properties):
def test_simulate_ou_process(point_properties, edge_properties, params):
graph = utils.Graph(point_properties, edge_properties)

dt = 0.01
Expand All @@ -236,15 +242,6 @@ def test_simulate_ou_process(point_properties, edge_properties):
endfeet_length = 1.0
entry_speed = [1] * nb_iteration

params = {
"blood_viscosity": 1.2e-6,
"p_base": 1.33e-3,
"threshold_r": 3,
"max_r_capill": 1.38,
"t_2_max_capill": 2.7,
"max_r_artery": 1.23,
"t_2_max_artery": 3.3,
}
tested.set_endfoot_id(graph, endfoot_id, section_id, segment_id, endfeet_length)
flows, pressures, radiii = tested.simulate_ou_process(
graph, entry_nodes, simulation_time, relaxation_start, time_step, entry_speed, params
Expand Down Expand Up @@ -385,7 +382,7 @@ def test_compute_static_laplacian():
)


def test_update_static_flow_pressure():
def test_update_static_flow_pressure(params):
point_properties = pd.DataFrame(
{
"x": [0, 0, 0, 0, 0, 0, 0, 0],
Expand All @@ -410,24 +407,16 @@ def test_update_static_flow_pressure():
entry_nodes = [0]
input_flow = len(entry_nodes) * [1.0]
boundary_flow = tested.boundary_flows_A_based(graph, entry_nodes, input_flow)
tested.update_static_flow_pressure(
graph, boundary_flow, blood_viscosity=1.2e-6, base_pressure=1.33e-3, with_hematocrit=True
)
tested.update_static_flow_pressure(graph, boundary_flow, params, with_hematocrit=True)
npt.assert_allclose(
graph.edge_properties["flow"],
np.array([1.0, 1.0, 1.0, 0.137931, 0.862069, 0]),
rtol=5e-7,
atol=5e-7,
)
npt.assert_allclose(
graph.node_properties["pressure"],
np.array([0.00133, 0.00133, 0.00133, 0.00133, 0.00133, 0.00133, 0.00133, 0.00133]),
rtol=5e-7,
atol=5e-7,
)


def test_total_flow_conservation_in_graph():
def test_total_flow_conservation_in_graph(params):
"""Check the total conservation of the flow in a connected graph.
Here we compute the boundary flows using the area based method.
"""
Expand Down Expand Up @@ -459,9 +448,7 @@ def test_total_flow_conservation_in_graph():
atol=1e-2,
)

tested.update_static_flow_pressure(
graph, boundary_flows, blood_viscosity=1.2e-6, base_pressure=1.33e-3, with_hematocrit=True
)
tested.update_static_flow_pressure(graph, boundary_flows, params, with_hematocrit=True)
flow = graph.edge_properties["flow"].values

tot_flow = np.sum(incidence @ flow)
Expand Down Expand Up @@ -496,7 +483,7 @@ def test_total_flow_conservation_in_graph():
)


def test_conservation_flow():
def test_conservation_flow(params):
point_properties = pd.DataFrame(
{
"x": [0, 0, 0, 0],
Expand Down Expand Up @@ -524,9 +511,7 @@ def test_conservation_flow():

boundary_flow = tested.boundary_flows_A_based(graph, entry_nodes, input_flow)

tested.update_static_flow_pressure(
graph, boundary_flow, blood_viscosity=1.2e-6, base_pressure=1.33e-3
)
tested.update_static_flow_pressure(graph, boundary_flow, params)
flow = graph.edge_properties["flow"]
npt.assert_allclose(
flow[0],
Expand Down
Loading

0 comments on commit 9eb1604

Please sign in to comment.