Note
Click here to download the full example code
Save simulations meta-data 1
Often phase field models are simulated using different parameters. However, managing different parameters values, simulation outputs, and other meta-data might be not trivial and can lead to mistakes and repeated simulations.
For this reason Mocafe provides tool to make simulation management easier. To see this in action, we are going to simulate the Prostate Cancer models presented in this demo for different parameters values.
Table of Contents
How to run this example on Mocafe
Make sure you have FEniCS and Mocafe installed and download the source script of this page (see above for the link). Then, simply run it using python:
python3 multiple_pc_simulations.py
However, it is recommended to exploit parallelization to save simulation time:
mpirun -n 4 python3 multiple_pc_simulations.py
Notice that the number following the -n
option is the number of MPI processes you using for parallelizing the
simulation. You can change it accordingly with your CPU.
Visualize the results of this simulation
You need to have Paraview to visualize the results. Once you have installed it,
you can easly import the .xdmf
files generated during the simulation and visualize the result.
Implementation
The simulated model is the same of the Prostate Cancer demo, so most of
the code would be just the same. Thus, we created a convenience method that will do most of the work
for us, called run_prostate_cancer_simulation
:
def run_prostate_cancer_simulation(loading_message, parameters, data_folder):
...
This method contains basically an adapted version of the code we saw in Prostate Cancer demo and thus we skip a full explanation in this demo. Still, you can see the complete implementation in the Full Code section.
Notice that run_prostate_cancer_simulation
takes just three arguments:
loading_message
: just a string containing a message to display nearby the progress barparameters
: the simulation parametersdata_folder
: the folder to store the simulation output
Managing multiple simulations
In the Prostate Cancer model original paper [LST+16] they simulated the model for two conditions:
setting parameters A = 300 [\(y^{-1}\)] and \(\chi\) = 400 [\(L \cdot g^{-1} \cdot y^{-1}\)], which lead to a rounded shape tumour;
setting parameters A = 600 [\(y^{-1}\)] and \(\chi\) = 600 [\(L \cdot g^{-1} \cdot y^{-1}\)], which lead to a ‘fingered’ shape tumour;
Now that we defined the run_prostate_cancer_simulation
is very easy to do the same in Mocafe. The first
step is to define a set of parameters (now the values of \(\chi\) and A don’t matter):
std_parameters = from_dict({
"phi0_in": 1., # adimentional
"phi0_out": 0., # adimdimentional
"sigma0_in": 0.2, # adimentional
"sigma0_out": 1., # adimentional
"dt": 0.001, # years
"lambda": 1.6E5, # (um^2) / years
"tau": 0.01, # years
"chempot_constant": 16, # adimensional
"chi": 600.0, # Liters / (gram * years)
"A": 600.0, # 1 / years
"epsilon": 5.0E6, # (um^2) / years
"delta": 1003.75, # grams / (Liters * years)
"gamma": 1000.0, # grams / (Liters * years)
"s_average": 2.75 * 365, # 961.2, # grams / (Liters * years)
"s_max": 73.,
"s_min": -73.
})
Then, we define the parameters values we want to change in lists:
chi_values = [400, 600]
A_values = [300, 600]
And we test the two conditions using a for loop:
for chi_value, A_value in zip(chi_values, A_values):
# set data folder for current simulation
data_folder = setup_data_folder(folder_path=f"{file_folder / Path('demo_out')}/multiple_pc_simulations",
auto_enumerate=True)
# set new parameters values
std_parameters.set_value("chi", chi_value)
std_parameters.set_value("A", A_value)
# run simulation measuring execution time
init_time = time.time()
run_prostate_cancer_simulation(f"simulating for chi = {chi_value}, A = {A_value}",
std_parameters,
data_folder)
execution_time = time.time() - init_time
# store simulation meta-data
save_sim_info(data_folder,
parameters=std_parameters,
execution_time=execution_time,
sim_name="Simulating 2D prostate cancer model",
sim_description="Simulating 2D PC model changing the values of parameters A and chi")
As you can see, inside the loop we do a number of operations:
We use
setup_data_folder
with the argumentauto_enumerate
=True
to automatically create multiple data folder nested inside the given folder;We change the value of the parameters of interest using
std_parameters.set_value
;At the end of the simulation, we use the method
save_sim_info
to store the simulation meta-data inside the data folder. Indeed, this method generates a file calledsim_info.html
, unique for each simulation, containing all the meta-data we asked to save. For instance, this is the file generated for the first simulation:
After the loop, the result will be stored in a tree like the following:
demo_out/multiple_pc_simulations/
├── 0000
│ ├── phi.h5
│ ├── phi.xdmf
│ ├── sigma.h5
│ ├── sigma.xdmf
│ └── sim_info.html
└── 0001
├── phi.h5
├── phi.xdmf
├── sigma.h5
├── sigma.xdmf
└── sim_info.html
As you can see, there are two nested folders inside demo_out/multiple_pc_simulations
, called 0000
(the first simulation) and 0001
(the second simulation). For each folder, the simulation output (phi.*
and sigma.*
) is stored together with the report file sim_info.html
, containing the simulation meta-data.
Result
We uploaded on Youtube the result on this simulation. You can check it out below or at this link
Full code
import numpy as np
import fenics
import time
from tqdm import tqdm
from pathlib import Path
import petsc4py
from mocafe.fenut.solvers import SNESProblem
from mocafe.fenut.fenut import get_mixed_function_space, setup_xdmf_files
from mocafe.fenut.mansimdata import setup_data_folder, save_sim_info
from mocafe.expressions import EllipseField
from mocafe.fenut.parameters import from_dict
import mocafe.litforms.prostate_cancer as pc_model
def run_prostate_cancer_simulation(loading_message, parameters, data_folder):
phi_xdmf, sigma_xdmf = setup_xdmf_files(["phi", "sigma"], data_folder)
# Mesh definition
nx = 130
ny = nx
x_max = 1000 # um
x_min = -1000 # um
y_max = x_max
y_min = x_min
mesh = fenics.RectangleMesh(fenics.Point(x_min, y_min),
fenics.Point(x_max, y_max),
nx,
ny)
# Spatial discretization
function_space = get_mixed_function_space(mesh, 2, "CG", 1)
# Initial conditions
semiax_x = 100 # um
semiax_y = 150 # um
phi0 = EllipseField(center=np.array([0., 0.]),
semiax_x=semiax_x,
semiax_y=semiax_y,
inside_value=parameters.get_value("phi0_in"),
outside_value=parameters.get_value("phi0_out"))
phi0 = fenics.interpolate(phi0, function_space.sub(0).collapse())
phi_xdmf.write(phi0, 0)
sigma0 = EllipseField(center=np.array([0., 0.]),
semiax_x=semiax_x,
semiax_y=semiax_y,
inside_value=parameters.get_value("sigma0_in"),
outside_value=parameters.get_value("sigma0_out"))
sigma0 = fenics.interpolate(sigma0, function_space.sub(0).collapse())
sigma_xdmf.write(sigma0, 0)
# Weak form definition
u = fenics.Function(function_space)
phi, sigma = fenics.split(u)
s_exp = fenics.Expression("(s_av + s_min) + ((s_max - s_min)*(random()/((double)RAND_MAX)))",
degree=2,
s_av=parameters.get_value("s_average"),
s_min=parameters.get_value("s_min"),
s_max=parameters.get_value("s_max"))
s = fenics.interpolate(s_exp, function_space.sub(0).collapse())
v1, v2 = fenics.TestFunctions(function_space)
weak_form = pc_model.prostate_cancer_form(phi, phi0, sigma, v1, parameters) + \
pc_model.prostate_cancer_nutrient_form(sigma, sigma0, phi, v2, s, parameters)
# Simulation: setup
n_steps = 1000
if rank == 0:
progress_bar = tqdm(total=n_steps, ncols=100)
progress_bar.set_description(loading_message)
else:
progress_bar = None
petsc4py.init([__name__,
"-snes_type", "newtonls",
"-ksp_type", "gmres",
"-pc_type", "gamg"])
from petsc4py import PETSc
# define solver
snes_solver = PETSc.SNES().create(comm)
snes_solver.setFromOptions()
t = 0
for current_step in range(n_steps):
# update time
t += parameters.get_value("dt")
# define problem
problem = SNESProblem(weak_form, u, [])
# set up algebraic system for SNES
b = fenics.PETScVector()
J_mat = fenics.PETScMatrix()
snes_solver.setFunction(problem.F, b.vec())
snes_solver.setJacobian(problem.J, J_mat.mat())
# solve system
snes_solver.solve(None, u.vector().vec())
# save new values to phi0 and sigma0, in order for them to be the initial condition for the next step
fenics.assign([phi0, sigma0], u)
# save current solutions to file
phi_xdmf.write(phi0, t) # write the value of phi at time t
sigma_xdmf.write(sigma0, t) # write the value of sigma at time t
# update progress bar
if rank == 0:
progress_bar.update(1)
# initial setup
fenics.set_log_level(fenics.LogLevel.ERROR)
comm = fenics.MPI.comm_world
rank = comm.Get_rank()
# get this file folder
file_folder = Path(__file__).parent.resolve()
# init standard parameters
std_parameters = from_dict({
"phi0_in": 1., # adimentional
"phi0_out": 0., # adimdimentional
"sigma0_in": 0.2, # adimentional
"sigma0_out": 1., # adimentional
"dt": 0.001, # years
"lambda": 1.6E5, # (um^2) / years
"tau": 0.01, # years
"chempot_constant": 16, # adimensional
"chi": 600.0, # Liters / (gram * years)
"A": 600.0, # 1 / years
"epsilon": 5.0E6, # (um^2) / years
"delta": 1003.75, # grams / (Liters * years)
"gamma": 1000.0, # grams / (Liters * years)
"s_average": 2.75 * 365, # 961.2, # grams / (Liters * years)
"s_max": 73.,
"s_min": -73.
})
# define parameters values to test
chi_values = [400, 600]
A_values = [300, 600]
# run multiple simulations
for chi_value, A_value in zip(chi_values, A_values):
# set data folder for current simulation
data_folder = setup_data_folder(folder_path=f"{file_folder / Path('demo_out')}/multiple_pc_simulations")
# set new parameters values
std_parameters.set_value("chi", chi_value)
std_parameters.set_value("A", A_value)
# run simulation measuring execution time
init_time = time.time()
run_prostate_cancer_simulation(f"simulating for chi = {chi_value}, A = {A_value}",
std_parameters,
data_folder)
execution_time = time.time() - init_time
# store simulation meta-data
save_sim_info(data_folder,
parameters=std_parameters,
execution_time=execution_time,
sim_name="Simulating 2D prostate cancer model",
sim_description="Simulating 2D PC model changing the values of parameters A and chi")
Total running time of the script: ( 0 minutes 0.000 seconds)