.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "demo_doc/angiogenesis_2d.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note Click :ref:`here ` to download the full example code .. rst-class:: sphx-glr-example-title .. _sphx_glr_demo_doc_angiogenesis_2d.py: .. _Angiogenesis 2D Demo: Angiogenesis ============= In this demo we will reproduce the angiogenesis phase field model described by Travasso et al. in 2011 :cite:`Travasso2011a`. In this implementation, we will simulate a set of discrete cells expressing a generic angiogenic factor (e.g. VEGF), which lead to the sprouting of a 2D vascular network. In the following, we will refer to these cells as *source cells*, since are the only source of angiogenic factor in this model. .. contents:: Table of Contents :local: 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, download the parameters file for the simulation from :download:`this link<./demo_in/angiogenesis_2d/parameters.ods>` and place it inside the folder ``demo_in/angiogenesis_2d``: .. code-block:: console mkdir demo_in mkdir demo_in/angiogenesis_2d mv parameters.ods demo_in/angiogenesis_2d/ Then, simply run it using python: .. code-block:: console python3 angiogenesis_2d.py However, it is recommended to exploit parallelization to save simulation time: .. code-block:: console mpirun -n 4 python3 angiogenesis_2d.py Notice that the number following the ``-n`` option is the number of MPI processes you using for parallelize the simulation. You can change it accordingly with your CPU. .. _angiogenesis_2d_brief_introduction: 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. Brief introduction to the mathematical model -------------------------------------------- The model is composed of two main parts interacting together: a set of differential equation and a computational "agent-based" model. The first part takes into account the continuous dynamics of the angiogenic factor and of the capillaries field, while the latter is responsible for the discrete dynamics of the source cells (i.e. the cells expressing a generic angiogenic factor, such as VEGF) and of the tip cells (i.e. the cells of endothelial origin which lead the sprouting of the new vessels). A complete discussion of this model is above the purpose of this demo so, if you're interested, we suggest you to refer to the original paper :cite:`Travasso2011a`. However, in the following we provide you a short introduction for your convenience. Differential equations ^^^^^^^^^^^^^^^^^^^^^^ The mathematical part is just composed of two partial differential equations (PDEs). The first PDE is for the angiogenic factor (:math:`af`) and takes into account: a. its diffusion; b. its consumption by the endothelial capillary cells (the field c). The equation reads: .. math:: \frac{\partial af}{\partial t} = \nabla (D_{af} \cdot \nabla af) - \alpha_T \cdot c \cdot af \cdot \Theta(c) Where the first part of the right-hand term comes from the Fick's low of diffusion :cite:`enwiki:1058693490`, while the second term is a decrease driven by the c field. The second PDE describes the dynamics of the capillaries, which are represented by a field :math:`c` of extreme values -1 and +1, where high values represent a part of the domain where the capillary is present, while the low values represent the parts of the domain where the capillaries are not present. The equation reads: .. math:: \frac{\partial c}{\partial t} = M_c \nabla^2 \cdot [-c + c^3 - \epsilon \nabla^2 c] + \alpha_p(af)c\Theta(c) Again, we have two terms composing the right-hand side of the equation: the first term is a Cahn-Hillard term, which is responsible for the interface dynamics of the field; the second just represents the proliferation of endothelial cells, which is driven by the angiogenic factor :math:`af`. This dependence, however, is not linear: the proliferation rate :math:`alpha_p(af)` grows linearly with af only up to a certain value of :math:`af`, limiting the growth of endothelial cells: .. math:: \alpha_p(af) = \alpha_p \cdot af_p & \quad \textrm{if} \quad af>af_p \\ = \alpha_p \cdot af & \quad \textrm{if} \quad 0 initial_vessel_width + parameters.get_value("d")) However, the source map is not sufficient to define the initial condition we need. To do so, we need an additional Mocafe object, a ``SourcesManager``: .. GENERATED FROM PYTHON SOURCE LINES 298-300 .. code-block:: default sources_manager = af_sourcing.SourcesManager(sources_map, mesh, parameters) .. GENERATED FROM PYTHON SOURCE LINES 301-305 As the name suggests, a ``SourcesManager`` is an object responsible for the actual management of the sources in the given source map. One of the function it provides is exactly what we need, that is to apply the sources to a given FEniCS function. Thus, to define the initial condition we need, is sufficient to define a function which is zero everywhere: .. GENERATED FROM PYTHON SOURCE LINES 305-307 .. code-block:: default af_0 = fenics.interpolate(fenics.Constant(0.), function_space.sub(0).collapse()) .. GENERATED FROM PYTHON SOURCE LINES 308-310 And to call the method ``apply_sources`` on it, which will take care of modifying the value of the function in the points inside the source cells. .. GENERATED FROM PYTHON SOURCE LINES 310-312 .. code-block:: default sources_manager.apply_sources(af_0) .. GENERATED FROM PYTHON SOURCE LINES 313-314 Finally, we can save the initial conditions to the xdmf files defined above: .. GENERATED FROM PYTHON SOURCE LINES 314-317 .. code-block:: default file_af.write(af_0, 0) file_c.write(c_0, 0) .. GENERATED FROM PYTHON SOURCE LINES 318-324 Visualizing the field that we just defined with `Paraview `_, what we get is exactly what we expect: an initial vessel on the left side of the domain and a set of randomly distributed source cells: .. image:: ./images/angiogenesis_2d/angiogenesis_2d_initial_condition.png :width: 600 .. GENERATED FROM PYTHON SOURCE LINES 326-330 Weak form definition ^^^^^^^^^^^^^^^^^^^^^ After having defined the initial conditions for the system, we continue with the definition of the system itself. As usual, we define the test functions necessary for computing the solution with the finite element method: .. GENERATED FROM PYTHON SOURCE LINES 330-332 .. code-block:: default v1, v2, v3 = fenics.TestFunctions(function_space) .. GENERATED FROM PYTHON SOURCE LINES 333-334 Then, we define the three functions involved in the PDE system: :math:`c`, :math:`\mu`, and :math:`af`: .. GENERATED FROM PYTHON SOURCE LINES 334-337 .. code-block:: default u = fenics.Function(function_space) af, c, mu = fenics.split(u) .. GENERATED FROM PYTHON SOURCE LINES 338-340 Moreover, we define two additional functions: one for the gradient of the angiogenic factor and one for the tip cells. Again, remember that the latter is defined just for visualization purposes and is not necessary for the simulation. .. GENERATED FROM PYTHON SOURCE LINES 340-343 .. code-block:: default grad_af = fenics.Function(grad_af_function_space) tipcells_field = fenics.Function(function_space.sub(0).collapse()) .. GENERATED FROM PYTHON SOURCE LINES 344-347 Then, since we have already defined the initial condition for :math:`af`, we can already compute its gradient and assign it to the variable defined above. Notice that this is quite simple in FEniCS, because it just requires to call the method ``grad`` on the function and to project it in the function space: .. GENERATED FROM PYTHON SOURCE LINES 347-351 .. code-block:: default grad_af.assign( # assign to grad_af fenics.project(fenics.grad(af_0), grad_af_function_space) # the projection on the fun space of grad(af_0) ) .. GENERATED FROM PYTHON SOURCE LINES 352-355 Finally, we proceed to the definition of the weak from for the system. As in the case of the prostate cancer, one could define the weak form using the FEniCS UFL, but for your convenience we already defined it for you and we wrapped the form in two methods: one for the angiogenic factor equation: .. GENERATED FROM PYTHON SOURCE LINES 355-357 .. code-block:: default form_af = angiogenic_factor_form(af, af_0, c, v1, parameters) .. GENERATED FROM PYTHON SOURCE LINES 358-359 and one for the :math:`c` field equation: .. GENERATED FROM PYTHON SOURCE LINES 359-361 .. code-block:: default form_ang = angiogenesis_form(c, c_0, mu, mu_0, v2, v3, af, parameters) .. GENERATED FROM PYTHON SOURCE LINES 362-363 which can be composed together simply summing them, as follows: .. GENERATED FROM PYTHON SOURCE LINES 363-365 .. code-block:: default weak_form = form_af + form_ang .. GENERATED FROM PYTHON SOURCE LINES 366-374 Simulation setup ^^^^^^^^^^^^^^^^ Now that everything is set up we can proceed to the actual simulation, which will be different from the one defined for the prostate cancer model because it will require us to handle the source cells and the tip cells. Just as for the source cells we defined a ``SourceCellsManager``, for the tip cells we need to define a ``TipCellsManager``, which will take care of the job of activating, deactivating and moving the tip cells. We initialize it simply calling: .. GENERATED FROM PYTHON SOURCE LINES 374-377 .. code-block:: default tip_cell_manager = tipcells.TipCellManager(mesh, parameters) .. GENERATED FROM PYTHON SOURCE LINES 378-383 And then we will use iteratively in the time simulation for our needs. Notice that the rules for activating, deactivating and moving the tip cells are already implemented in the object class and all we need to do is passing the mesh and the simulation parameters to the constructor. Then, we can proceed similarly to any other simulation, defining the Jacobian for the weak form: .. GENERATED FROM PYTHON SOURCE LINES 383-385 .. code-block:: default jacobian = fenics.derivative(weak_form, u) .. GENERATED FROM PYTHON SOURCE LINES 386-387 And initializing the time iteration .. GENERATED FROM PYTHON SOURCE LINES 387-392 .. code-block:: default t = 0. n_steps = int(parameters.get_value("n_steps")) tqdm_file = sys.stdout if rank == 0 else None tqdm_disable = (rank != 0) .. GENERATED FROM PYTHON SOURCE LINES 393-394 Now, we can start iterating .. GENERATED FROM PYTHON SOURCE LINES 394-430 .. code-block:: default for step in tqdm(range(1, n_steps + 1), ncols=100, desc="angiogenesis_2d", file=tqdm_file, disable=tqdm_disable): # update time t += parameters.get_value("dt") # turn off near sources sources_manager.remove_sources_near_vessels(c_0) # activate tip cell tip_cell_manager.activate_tip_cell(c_0, af_0, grad_af, step) # revert tip cells tip_cell_manager.revert_tip_cells(af_0, grad_af) # move tip cells tip_cell_manager.move_tip_cells(c_0, af_0, grad_af) # get tip cells field tipcells_field.assign(tip_cell_manager.get_latest_tip_cell_function()) # update fields fenics.solve(weak_form == 0, u, J=jacobian) # assign u to the initial conditions functions fenics.assign([af_0, c_0, mu_0], u) # update source field sources_manager.apply_sources(af_0) # compute grad_T grad_af.assign(fenics.project(fenics.grad(af_0), grad_af_function_space)) # save data file_af.write(af_0, t) file_c.write(c_0, t) tipcells_xdmf.write(tipcells_field, t) .. GENERATED FROM PYTHON SOURCE LINES 431-668 Notice that additionally to the system solution a number of operations are performed at each time stem which require a bit of clarification. Let's see the code step by step then. The first thing we did just after the time update is removing the sources near the vessels, calling: .. code-block:: default sources_manager.remove_sources_near_vessels(c_0) With this single line, we are asking the sources manager to check the field ``c_0``, which represent the vessels, and to remove all the source cells the center of which is closer than the distance :math:`d`. Notice that we don't pass the distance as argument of the method because it's already contained in the parameters file we passed to the object constructor, but we could also pass it in the method through the 'd' key: ``sources_manager.remove_sources_near_vessels(c_0, d=given_value)`` The second thing we did is to handle the tip cells using the three statements: .. code-block:: default # activate tip cell tip_cell_manager.activate_tip_cell(c_0, af_0, grad_af, step) # revert tip cells tip_cell_manager.revert_tip_cells(af_0, grad_af) # move tip cells tip_cell_manager.move_tip_cells(c_0, af_0, grad_af) Which respectively activate, deactivate and move the tip cells according to the algorithm we briefly discussed in the section :ref:`Brief introduction to the mathematical model` and that is extensively explained in the original paper :cite`Travasso2011a`. Notice that, similarly to the methods before, all the default threshold values do not need to be passed in the methods because they are already defined in the parameters file. Also notice that, in case there are no active tip cells in the current time step, the second and the third statement have no effect. Then, we save the current tip cells in the above-defined tip cells field for visualizing them, using the method ``get_latest_tip_cell_function()``: .. code-block:: default tipcells_field.assign(tip_cell_manager.get_latest_tip_cell_function()) After having took care of all these things, we simply solve the PDE model and assign the computed values of the solution to the ``c_0``, ``mu_0`` and ``af_0`` fields, in order to have them as initial condition for the next step: .. code-block:: default fenics.solve(weak_form == 0, u, J=jacobian) # assign u to the initial conditions functions fenics.assign([af_0, c_0, mu_0], u) Finally, we apply the remaining sources to the new ``af_0`` function: .. code-block:: default # update source field sources_manager.apply_sources(af_0) we compute the new value for the gradient of ``af``: .. code-block:: default grad_af.assign(fenics.project(fenics.grad(af_0), grad_af_function_space)) we write everything on the ``.xdmf files``: .. code-block:: default # save data file_af.write(af_0, t) file_c.write(c_0, t) tipcells_xdmf.write(tipcells_field, t) and we update the progress bar, in order to inform the user on the progress of the simulation. .. code-block:: default if _rank == 0: pbar.update(1) Result ------ We uploaded on Youtube the result on this simulation. You can check it out below or at `this link `_ .. youtube:: xTOa6dTCWgk Visualize the result with ParaView ---------------------------------- The result of the simulation is stored in the ``.xdmf`` file generated, which are easy to load and visualize in expernal softwares as ParaView. If you don't now how to do it, you can check out the tutorial below or at `this Youtube link `_. .. youtube:: TiqoMD-eGM4 Full code ========= .. code-block:: default import fenics import mshr from tqdm import tqdm from pathlib import Path import mocafe.fenut.fenut as fu import mocafe.fenut.mansimdata as mansimd from mocafe.angie import af_sourcing, tipcells from mocafe.angie.forms import angiogenesis_form, angiogenic_factor_form import mocafe.fenut.parameters as mpar # MPI _comm = fenics.MPI.comm_world _rank = _comm.Get_rank() # only process 0 logs fenics.parameters["std_out_all_processes"] = False # set log level ERROR fenics.set_log_level(fenics.LogLevel.ERROR) # define data folder file_folder = Path(__file__).parent.resolve() data_folder = mansimd.setup_data_folder(folder_path=f"{file_folder / Path('demo_out')}/angiogenesis_2d", auto_enumerate=None) file_names = ["c", "af", "tipcells"] file_c, file_af, tipcells_xdmf = fu.setup_xdmf_files(file_names, data_folder) file_folder = Path(__file__).parent.resolve() parameters_file = file_folder / Path("demo_in/angiogenesis_2d/parameters.ods") parameters = mpar.from_ods_sheet(parameters_file, "SimParams") # Mesh definition Lx = parameters.get_value("Lx") Ly = parameters.get_value("Ly") nx = int(parameters.get_value("nx")) ny = int(parameters.get_value("ny")) mesh = fenics.RectangleMesh(fenics.Point(0., 0.), fenics.Point(Lx, Ly), nx, ny) # Spatial discretization # define function space for c and af function_space = fu.get_mixed_function_space(mesh, 3, "CG", 1) # define function space for grad_T grad_af_function_space = fenics.VectorFunctionSpace(mesh, "CG", 1) # Initial conditions initial_vessel_width = parameters.get_value("initial_vessel_width") c_0_exp = fenics.Expression("(x[0] < i_v_w) ? 1 : -1", degree=2, i_v_w=initial_vessel_width) c_0 = fenics.interpolate(c_0_exp, function_space.sub(0).collapse()) mu_0 = fenics.interpolate(fenics.Constant(0.), function_space.sub(0).collapse()) n_sources = int(parameters.get_value("n_sources")) random_sources_domain = mshr.Rectangle(fenics.Point(initial_vessel_width + parameters.get_value("d"), 0), fenics.Point(Lx, Ly)) sources_map = af_sourcing.RandomSourceMap(mesh, n_sources, parameters, where=random_sources_domain) sources_manager = af_sourcing.SourcesManager(sources_map, mesh, parameters) af_0 = fenics.interpolate(fenics.Constant(0.), function_space.sub(0).collapse()) sources_manager.apply_sources(af_0) file_af.write(af_0, 0) file_c.write(c_0, 0) # Weak form defintion v1, v2, v3 = fenics.TestFunctions(function_space) u = fenics.Function(function_space) af, c, mu = fenics.split(u) grad_af = fenics.Function(grad_af_function_space) tipcells_field = fenics.Function(function_space.sub(0).collapse()) grad_af.assign( # assign to grad_af fenics.project(fenics.grad(af_0), grad_af_function_space) # the projection on the fun space of grad(af_0) ) form_af = angiogenic_factor_form(af, af_0, c, v1, parameters) form_ang = angiogenesis_form(c, c_0, mu, mu_0, v2, v3, af, parameters) weak_form = form_af + form_ang # Solution tip_cell_manager = tipcells.TipCellManager(mesh, parameters) jacobian = fenics.derivative(weak_form, u) t = 0. n_steps = int(parameters.get_value("n_steps")) tqdm_file = sys.stdout if rank == 0 else None tqdm_disable = (rank != 0) # start iterating for step in tqdm(range(1, n_steps + 1), ncols=100, desc="angiogenesis_2d", file=tqdm_file, disable=tqdm_disable): # update time t += parameters.get_value("dt") # turn off near sources sources_manager.remove_sources_near_vessels(c_0) # activate tip cell tip_cell_manager.activate_tip_cell(c_0, af_0, grad_af, step) # revert tip cells tip_cell_manager.revert_tip_cells(af_0, grad_af) # move tip cells tip_cell_manager.move_tip_cells(c_0, af_0, grad_af) # get tip cells field tipcells_field.assign(tip_cell_manager.get_latest_tip_cell_function()) # update fields fenics.solve(weak_form == 0, u, J=jacobian) # assign u to the initial conditions functions fenics.assign([af_0, c_0, mu_0], u) # update source field sources_manager.apply_sources(af_0) # compute grad_T grad_af.assign(fenics.project(fenics.grad(af_0), grad_af_function_space)) # save data file_af.write(af_0, t) file_c.write(c_0, t) tipcells_xdmf.write(tipcells_field, t) .. rst-class:: sphx-glr-timing **Total running time of the script:** ( 0 minutes 0.000 seconds) .. _sphx_glr_download_demo_doc_angiogenesis_2d.py: .. only :: html .. container:: sphx-glr-footer :class: sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: angiogenesis_2d.py ` .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: angiogenesis_2d.ipynb ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_