|
2 | 2 | "cells": [ |
3 | 3 | { |
4 | 4 | "cell_type": "code", |
5 | | - "execution_count": 6, |
| 5 | + "execution_count": 1, |
6 | 6 | "metadata": {}, |
7 | | - "outputs": [], |
| 7 | + "outputs": [ |
| 8 | + { |
| 9 | + "name": "stdout", |
| 10 | + "output_type": "stream", |
| 11 | + "text": [ |
| 12 | + "Missing HsMG for fract norm computing\n" |
| 13 | + ] |
| 14 | + } |
| 15 | + ], |
8 | 16 | "source": [ |
9 | 17 | "from fenics import *\n", |
10 | 18 | "import sys\n", |
11 | 19 | "sys.path.append('../data/')\n", |
12 | | - "sys.path.append('../graphnics/')\n", |
13 | | - "sys.path.append('../applications/')\n", |
14 | | - "sys.path.append('../../NetworkGen/')\n", |
15 | 20 | "import matplotlib.pyplot as plt\n", |
16 | 21 | "import networkx as nx\n", |
17 | 22 | "import numpy as np\n", |
|
33 | 38 | "- blood flow in vascularized tissue,\n", |
34 | 39 | "- water flow through the root network in soil,\n", |
35 | 40 | "- fluid flow through wells drilled in a reservoir.\n", |
36 | | - "- \n", |
| 41 | + "\n", |
37 | 42 | "Each of these application share a common geometry: A network of flow channels $\\Lambda$ (blood vessels, roots or wells) embedded in a surrounding porous media $\\Omega \\subset \\mathbb{R}^3$ (tissue, soil or reservoir).\n", |
38 | 43 | "\n", |
39 | 44 | "## Model equations\n", |
|
42 | 47 | "\n", |
43 | 48 | "$$\n", |
44 | 49 | "\\begin{align*}\n", |
45 | | - "- \\Delta u &= \\beta(\\hat{u}-\\bar{u})\\delta_\\Gamma \\quad &&\\text{ in } \\Omega, \\\\\n", |
| 50 | + "- \\Delta u &= \\beta(\\hat{u}-\\bar{u})\\delta_\\Lambda \\quad &&\\text{ in } \\Omega, \\\\\n", |
46 | 51 | "- \\partial_{ss} \\hat{u} &= -\\beta(\\hat{u}-\\bar{u}) \\quad &&\\text{ on } \\Lambda\n", |
47 | 52 | "\\end{align*}\n", |
48 | 53 | "$$\n", |
|
71 | 76 | }, |
72 | 77 | { |
73 | 78 | "cell_type": "code", |
74 | | - "execution_count": 7, |
| 79 | + "execution_count": 19, |
75 | 80 | "metadata": {}, |
76 | 81 | "outputs": [], |
77 | 82 | "source": [ |
|
87 | 92 | "mesh3d = UnitCubeMesh(40, 40, 40)\n", |
88 | 93 | "\n", |
89 | 94 | "c = mesh3d.coordinates()\n", |
| 95 | + "xc, yc, zc = (np.min(node_coords, axis=0)+np.max(node_coords, axis=0))*0.5\n", |
| 96 | + "xl, yl, zl = (np.max(node_coords, axis=0)-np.min(node_coords, axis=0))\n", |
90 | 97 | "\n", |
91 | | - "xmin, ymin, zmin = np.min(node_coords, axis=0)\n", |
92 | | - "xmax, ymax, zmax = np.max(node_coords, axis=0)\n", |
93 | | - "\n", |
94 | | - "c[:, 0] *= (xmax - xmin) * 1.2\n", |
95 | | - "c[:, 1] *= (ymax - ymin) * 1.2\n", |
96 | | - "c[:, 2] *= 0.5\n", |
97 | | - "c[:, 2] -= 0.25\n", |
98 | | - "c[:, 0] -= np.abs(xmin)*1.2\n", |
99 | | - "c[:, 1] -= 0.1\n", |
100 | | - "\n", |
101 | | - "File(\"plots/coupled1d3d/box.pvd\") << Function(FunctionSpace(mesh3d, \"CG\", 1))\n", |
102 | | - "File(\"plots/coupled1d3d/network.pvd\") << Function(FunctionSpace(mesh1d, \"CG\", 1))" |
| 98 | + "c[:,:]-=[xc,yc,zc]\n", |
| 99 | + "for i, length in enumerate([xl, yl, zl]):\n", |
| 100 | + " c[:,i]*=length*1.1" |
103 | 101 | ] |
104 | 102 | }, |
105 | 103 | { |
|
113 | 111 | }, |
114 | 112 | { |
115 | 113 | "cell_type": "code", |
116 | | - "execution_count": 8, |
| 114 | + "execution_count": 3, |
117 | 115 | "metadata": {}, |
118 | 116 | "outputs": [], |
119 | 117 | "source": [ |
|
173 | 171 | }, |
174 | 172 | { |
175 | 173 | "cell_type": "code", |
176 | | - "execution_count": 10, |
| 174 | + "execution_count": 4, |
177 | 175 | "metadata": {}, |
178 | 176 | "outputs": [], |
179 | 177 | "source": [ |
|
216 | 214 | }, |
217 | 215 | { |
218 | 216 | "cell_type": "code", |
219 | | - "execution_count": 11, |
| 217 | + "execution_count": 5, |
220 | 218 | "metadata": {}, |
221 | 219 | "outputs": [ |
222 | 220 | { |
223 | | - "name": "stdout", |
| 221 | + "name": "stderr", |
224 | 222 | "output_type": "stream", |
225 | 223 | "text": [ |
226 | | - "Calling FFC just-in-time (JIT) compiler, this may take some time.\n", |
227 | | - "Calling FFC just-in-time (JIT) compiler, this may take some time.\n" |
| 224 | + "Averaging over 48 cells: 48it [00:00, 1315.14it/s]\n" |
228 | 225 | ] |
229 | 226 | } |
230 | 227 | ], |
|
233 | 230 | "a = [[a00, a01], [a10, a11]]\n", |
234 | 231 | "L = [L0, L1]\n", |
235 | 232 | "\n", |
236 | | - "W_bcs = [\n", |
237 | | - " [DirichletBC(V3, u_bc_3, \"on_boundary\")],\n", |
238 | | - " [DirichletBC(V1, u_bc_1, \"on_boundary\")],\n", |
239 | | - "]\n", |
| 233 | + "W_bcs = [[DirichletBC(V3, u_bc_3, \"on_boundary\")],\n", |
| 234 | + " [DirichletBC(V1, u_bc_1, \"on_boundary\")]]\n", |
240 | 235 | "\n", |
241 | 236 | "A, b = map(ii_assemble, (a, L))\n", |
242 | 237 | "A, b = apply_bc(A, b, W_bcs)\n", |
|
258 | 253 | "\n", |
259 | 254 | "Finally, we can plot the results using paraview. If you open the files `pressure1d.pvd` and `pressure3d.pvd` located in the `plots/coupled1d3d/` folder you should see something like this:\n", |
260 | 255 | "\n", |
261 | | - "<img alt=\"alt_text\" width=\"500px\" src=\"coupled1d3d.png\" />" |
| 256 | + "<img alt=\"alt_text\" width=\"500px\" src=\"coupled1d3d.png\" />\n", |
| 257 | + "\n", |
| 258 | + "## Coupled 1d-3d flow model for carcinoma\n", |
| 259 | + "\n", |
| 260 | + "As you might have noticed from the above figure, `fenics_ii` can assemble and solve the coupled 1d-3d flow model even if the 1d mesh does not conform to the 3d mesh. Thus, using e.g. `graphnics` to make the 1d mesh, it is straightforward to extend our approach to more complicated networks. We illustrate this by using a graph constructed from the vascular network of a carcinoma in a rat." |
| 261 | + ] |
| 262 | + }, |
| 263 | + { |
| 264 | + "cell_type": "code", |
| 265 | + "execution_count": 48, |
| 266 | + "metadata": {}, |
| 267 | + "outputs": [ |
| 268 | + { |
| 269 | + "name": "stdout", |
| 270 | + "output_type": "stream", |
| 271 | + "text": [ |
| 272 | + "Downoading data from https://physiology.arizona.edu/sites/default/files/rattum98_0.txt \n", |
| 273 | + "\n", |
| 274 | + "Please visit https://physiology.arizona.edu/people/secomb/network for information on how to cite the source for this data\n", |
| 275 | + " \n" |
| 276 | + ] |
| 277 | + } |
| 278 | + ], |
| 279 | + "source": [ |
| 280 | + "# Get 1d network\n", |
| 281 | + "from data.secomb_tumours import read_network\n", |
| 282 | + "G = read_network.get_graph('https://physiology.arizona.edu/sites/default/files/rattm93b.txt')\n", |
| 283 | + "G.make_mesh(4)\n", |
| 284 | + "mesh1d = G.global_mesh\n", |
| 285 | + "\n", |
| 286 | + "# Fit box around it\n", |
| 287 | + "pos = nx.get_node_attributes(G, \"pos\")\n", |
| 288 | + "node_coords = np.asarray(list(pos.values()))\n", |
| 289 | + "\n", |
| 290 | + "mesh3d = UnitCubeMesh(10, 10, 10)\n", |
| 291 | + "\n", |
| 292 | + "c = mesh3d.coordinates()\n", |
| 293 | + "xc, yc, zc = (np.min(node_coords, axis=0)+np.max(node_coords, axis=0))*0.5\n", |
| 294 | + "xl, yl, zl = (np.max(node_coords, axis=0)-np.min(node_coords, axis=0))\n", |
| 295 | + "\n", |
| 296 | + "c[:,:]-=[xc,yc,zc]\n", |
| 297 | + "for i, length in enumerate([xl, yl, zl]):\n", |
| 298 | + " c[:,i]*=length*1.1" |
| 299 | + ] |
| 300 | + }, |
| 301 | + { |
| 302 | + "cell_type": "markdown", |
| 303 | + "metadata": {}, |
| 304 | + "source": [ |
| 305 | + "Assembling and solving the model is performed as shown earlier, with the exception that the averaging radius is taken from the vasculature radius." |
| 306 | + ] |
| 307 | + }, |
| 308 | + { |
| 309 | + "cell_type": "code", |
| 310 | + "execution_count": null, |
| 311 | + "metadata": {}, |
| 312 | + "outputs": [], |
| 313 | + "source": [ |
| 314 | + "W = [FunctionSpace(msh, \"CG\", 1) for msh in [mesh3d, mesh1d]]\n", |
| 315 | + "\n", |
| 316 | + "u3, u1 = list(map(TrialFunction, W))\n", |
| 317 | + "v3, v1 = list(map(TestFunction, W))\n", |
| 318 | + "\n", |
| 319 | + "\n", |
| 320 | + "# Averaging radius is taken from radius data\n", |
| 321 | + "class AveragingRadius(UserExpression):\n", |
| 322 | + " '''\n", |
| 323 | + " Vessel radius as a fenics expression\n", |
| 324 | + " '''\n", |
| 325 | + " def __init__(self, G, **kwargs):\n", |
| 326 | + " self.G = G\n", |
| 327 | + " super().__init__(**kwargs)\n", |
| 328 | + " def eval(self, value, x):\n", |
| 329 | + " p = Point(x[0], x[1], x[2])\n", |
| 330 | + " tree = BoundingBoxTree()\n", |
| 331 | + " tree.build(mesh1d)\n", |
| 332 | + " cell = tree.compute_first_entity_collision(p)\n", |
| 333 | + "\n", |
| 334 | + " edge_ix = self.G.mf[cell]\n", |
| 335 | + " edge = list(G.edges())[edge_ix]\n", |
| 336 | + " value[0] = self.G.edges()[edge]['radius']\n", |
| 337 | + " \n", |
| 338 | + "radius = Radius(G, degree=2)\n", |
| 339 | + "\n", |
| 340 | + "cylinder = Circle(radius=radius, degree=10)\n", |
| 341 | + "\n", |
| 342 | + "Pi_u, Pi_v = [Average(func, mesh1d, cylinder) for func in [u3, v3]]" |
262 | 343 | ] |
| 344 | + }, |
| 345 | + { |
| 346 | + "cell_type": "markdown", |
| 347 | + "metadata": {}, |
| 348 | + "source": [ |
| 349 | + "From here we assemble the form and solve:" |
| 350 | + ] |
| 351 | + }, |
| 352 | + { |
| 353 | + "cell_type": "code", |
| 354 | + "execution_count": 81, |
| 355 | + "metadata": {}, |
| 356 | + "outputs": [ |
| 357 | + { |
| 358 | + "name": "stdout", |
| 359 | + "output_type": "stream", |
| 360 | + "text": [ |
| 361 | + "WARNING: user expression has not supplied value_shape method or an element. Assuming scalar element.\n" |
| 362 | + ] |
| 363 | + } |
| 364 | + ], |
| 365 | + "source": [ |
| 366 | + "beta = Constant(0.000001)\n", |
| 367 | + "\n", |
| 368 | + "dxGamma = Measure(\"dx\", domain=mesh1d)\n", |
| 369 | + "\n", |
| 370 | + "# blocks\n", |
| 371 | + "a00 = inner(grad(u3), grad(v3)) * dx + circum* beta * inner(Pi_u, Pi_v) * dxGamma\n", |
| 372 | + "a01 = -beta * circum * inner(u1, Pi_v) * dxGamma\n", |
| 373 | + "a10 = -beta * inner(Pi_u, v1) * dxGamma\n", |
| 374 | + "a11 = inner(grad(u1), grad(v1)) * dx + beta * inner(u1, v1) * dxGamma\n", |
| 375 | + "\n", |
| 376 | + "# right-hand side\n", |
| 377 | + "L0 = inner(Constant(0), Pi_v) * dxGamma\n", |
| 378 | + "L1 = inner(Constant(0), v1) * dxGamma\n", |
| 379 | + "\n", |
| 380 | + "\n", |
| 381 | + "a = [[a00, a01], [a10, a11]]\n", |
| 382 | + "L = [L0, L1]\n", |
| 383 | + "\n", |
| 384 | + "u_bc_1 = Expression('0.1*x[1]', degree=2)\n", |
| 385 | + "\n", |
| 386 | + "W_bcs = [[DirichletBC(W[0], u_bc_3, \"on_boundary\")],\n", |
| 387 | + " [DirichletBC(W[1], u_bc_1, \"on_boundary\")]]\n", |
| 388 | + "\n", |
| 389 | + "A, b = map(ii_assemble, (a, L))\n", |
| 390 | + "A, b = apply_bc(A, b, W_bcs)\n", |
| 391 | + "A, b = map(ii_convert, (A, b))\n", |
| 392 | + "\n", |
| 393 | + "wh = ii_Function(W)\n", |
| 394 | + "solver = LUSolver(A, \"mumps\")\n", |
| 395 | + "solver.solve(wh.vector(), b)" |
| 396 | + ] |
| 397 | + }, |
| 398 | + { |
| 399 | + "cell_type": "code", |
| 400 | + "execution_count": 82, |
| 401 | + "metadata": {}, |
| 402 | + "outputs": [ |
| 403 | + { |
| 404 | + "name": "stderr", |
| 405 | + "output_type": "stream", |
| 406 | + "text": [ |
| 407 | + "Averaging over 1648 cells: 1648it [00:03, 538.39it/s]\n" |
| 408 | + ] |
| 409 | + }, |
| 410 | + { |
| 411 | + "data": { |
| 412 | + "text/plain": [ |
| 413 | + "1" |
| 414 | + ] |
| 415 | + }, |
| 416 | + "execution_count": 82, |
| 417 | + "metadata": {}, |
| 418 | + "output_type": "execute_result" |
| 419 | + } |
| 420 | + ], |
| 421 | + "source": [ |
| 422 | + "write_vtp(G, [(radius_i, 'radius'), (wh[1], 'p1d')], fname='plots/coupled1d3d/pressure1d.vtp')\n", |
| 423 | + "wh[0].rename('p3d', '0.0')\n", |
| 424 | + "File('plots/coupled1d3d/pressure3d.pvd') << wh[0]" |
| 425 | + ] |
| 426 | + }, |
| 427 | + { |
| 428 | + "cell_type": "code", |
| 429 | + "execution_count": 83, |
| 430 | + "metadata": {}, |
| 431 | + "outputs": [], |
| 432 | + "source": [] |
263 | 433 | } |
264 | 434 | ], |
265 | 435 | "metadata": { |
266 | 436 | "kernelspec": { |
267 | | - "display_name": "Python 3.6.9 64-bit", |
| 437 | + "display_name": "Python 3", |
268 | 438 | "language": "python", |
269 | 439 | "name": "python3" |
270 | 440 | }, |
|
0 commit comments