thesis.tex 20.7 KB
Newer Older
sfritschi's avatar
sfritschi committed
1
\documentclass[10pt]{article}
sfritschi's avatar
sfritschi committed
2
3
4
5
6

\title{\textbf{Generation and Simulation of Large-Scale Flow Networks}}
\author{Severin Fritschi}
\date{}

sfritschi's avatar
sfritschi committed
7
% packages
sfritschi's avatar
sfritschi committed
8
\usepackage[backend=biber, sorting=none]{biblatex}
sfritschi's avatar
sfritschi committed
9
\usepackage[labelfont=bf]{caption} % figure captions (bold-face)
sfritschi's avatar
sfritschi committed
10
\usepackage{multicol}  % multicolumn environment
11
\usepackage{tikz} % figures
sfritschi's avatar
sfritschi committed
12
\usepackage{xcolor} % color for cell lists picture
sfritschi's avatar
sfritschi committed
13
14
\usepackage{hyperref} % web-links
\usepackage{listings} % line break in verb environment
15
16
17
18
19
20
21
\usepackage{amsmath} % math commands
% packages for algorithms
\usepackage{algorithm}
\usepackage{algpseudocode}

% argmin
\DeclareMathOperator*{\argmin}{arg\min}
sfritschi's avatar
sfritschi committed
22
23
24

% references
\addbibresource{bib/RandomGenerationFlowNetworks.bib}
sfritschi's avatar
sfritschi committed
25
\addbibresource{bib/netflow.bib}
sfritschi's avatar
sfritschi committed
26
\addbibresource{bib/petsc.bib}
sfritschi's avatar
sfritschi committed
27
\addbibresource{bib/hypre.bib}
sfritschi's avatar
sfritschi committed
28

sfritschi's avatar
sfritschi committed
29
30
\begin{document}
	\maketitle
sfritschi's avatar
sfritschi committed
31
32
	\begin{center}
		\large{Study Programme Computational Sciences and Engineering}
sfritschi's avatar
sfritschi committed
33
	
sfritschi's avatar
sfritschi committed
34
35
		\vspace{20ex}
		\large{Bachelor Thesis HS 21}
sfritschi's avatar
sfritschi committed
36
	
sfritschi's avatar
sfritschi committed
37
38
		\vspace{5ex}
		\large{Institute of Fluid Dynamics}
sfritschi's avatar
sfritschi committed
39
	
sfritschi's avatar
sfritschi committed
40
		\large{ETH Zürich}
sfritschi's avatar
sfritschi committed
41
	
sfritschi's avatar
sfritschi committed
42
		\vspace{20ex}
sfritschi's avatar
sfritschi committed
43
44
45
46
47
		\begin{tabular}{ l l }
			\large{Supervisor:} & \large{Daniel W. Meyer} \\
			\large{Professor:} & \large{Patrick Jenny}
		\end{tabular}
	\end{center}
sfritschi's avatar
sfritschi committed
48

sfritschi's avatar
sfritschi committed
49
50
	\newpage
	
sfritschi's avatar
sfritschi committed
51
	\begin{center} \Large{\textbf{Abstract}} \end{center}
sfritschi's avatar
sfritschi committed
52
	\hspace{0.5cm}To study the flow properties of large void-space geometries found in porous media such as f.ex. soil or gravel, \cite{MEYER2021103936} describes and implements routines for the generation \& simulation of flow networks in the Python library \emph{netflow} \cite{MEYER2021101592}. Based on a relatively small base network acquired via tomographic scans, the generated flow network is of intermediate size (millions of pores). To extend this procedure to even larger networks (up to 100 millions of pores), parallel computing is employed for both generation of pore-networks as well as solving the flow for said networks. In the latter, we rely on existing MPI-based parallel solvers from the PETSc \cite{petsc-web-page} toolkit. See Appendix ~\ref{appendix:install} for installation details.
sfritschi's avatar
sfritschi committed
53
54
55
56

	\vspace{5ex}
	\begin{multicols}{2}
    	\section{Introduction}
sfritschi's avatar
sfritschi committed
57
    	\hspace{0.5cm}Porous media are abundant in nature. Various types of soils harbor intricate networks that enable the flow of groundwater and subsequently the transport of important chemical compounds through the soil. To understand these natural phenomena, it is of key interest to study the flow through pore networks. However, this requires sufficiently large void-space geometries taken from porous bodies, which despite advances in scanning technologies, is infeasible. Additionally, in pursuit of simulation efficiency, the 3D images obtained from scans are converted into a simplified representation consisting of spherical pores (nodes), connected by cylindrical throats (edges). To overcome above size limitation, the paper from \cite{MEYER2021103936} outlines a procedure for generating random realizations of much larger flow networks, taking an existing base network (See Appendix ~\ref{appendix:base}), obtained from a scan of a porous medium, as input. Furthermore, this method is particularly useful for generating heterogeneous networks, characterized by an irregular pore distribution. This is done with dendrogram-based clustering of pores in the original network, followed by random rotations of said clusters. When performed repeatedly and arranged in a 3-dimensional grid of perturbed base network copies, the resulting network, carrying over pore statistics such as coordination number \& radius, preserves the irregular pore structure of the original network. The \emph{netflow} package that implements the mentioned algorithm also provides functionality for solving and analyzing the flow through such networks. The former of which is now to be parallelized in a distributed fashion, so that it may support larger networks with up to 100 millions of pores. In a further step, the network generation algorithm shall be complemented by a parallel shared memory approach, specifically for connecting the generated pores in the resulting network.
sfritschi's avatar
sfritschi committed
58
59
    
		\section{Parallel Flow Solver}
sfritschi's avatar
sfritschi committed
60
		\subsection{PETSc Interface}
sfritschi's avatar
sfritschi committed
61
		\hspace{0.5cm}Despite the availability of Python package \emph{petsc4py}, we decided to use the native implementation in C instead. This choice was motivated by the fact that the latter is better maintained, and the inclusion of an additional Python module would further complicate the dependency tree on the Python side. In order to interface the chosen C API of PETSc with the \emph{netflow} Python module, we rely on Cython to wrap the C source in Python, that is subsequently compiled with all required compilation and linking flags of PETSc. This allows us to invoke a \verb|solve_py()| function from Python delegating the relevant parameters, namely the system matrix and right-hand-side vector, to the C function \verb|solve()|. Here, the system matrix, in the compressed row storage format, is converted into PETSc's internal representation for sparse and distributed matrices called \verb|Mat|. The same applies to the r.h.s. which is used to initialize a distributed vector object in PETSc, i.e. \verb|Vec|. When the solution has been computed, it is communicated in full to the root rank via a call to \verb|MPI_Gatherv()|. It is stored in a Cython memoryview, converted back into a numpy array, and finally returned by \verb|solve_py()|.
sfritschi's avatar
sfritschi committed
62
		\subsection{Solver}
sfritschi's avatar
sfritschi committed
63
	    \hspace{0.5cm}The actual solver written in C then utilizes PETSc's collection of krylov-subspace (KSP) methods to iteratively approximate the solution of the system in parallel with the available MPI processes. To avoid data duplication, the initial assembly of PETSc objects is only done on the root rank and then communicated in parts to the corresponding ranks through PETSc's collective \verb|Assembly| routines. The iterative method chosen to solve the non-symmetric pressure system, arising from the flow network, is GMRES together with a left algebraic multi-grid preconditioner supplied via hypre \cite{hypre-web-page}.
sfritschi's avatar
sfritschi committed
64
	    \subsection{Limitations}
65
	    \hspace{0.5cm}Since the datastructure used to represent the pores in \emph{netflow} is a Python \verb|set|, the order of the pores is arbitrary. In particular, this means each MPI process sees a different ordering from eachother, which necessitates initialization of the full system matrix \& r.h.s. on a given root rank, such that the ordering is consistent for all ranks. This requires additional communication, but prevents duplication of the data associated with the matrix etc. on the remaining processes.
sfritschi's avatar
sfritschi committed
66
	    \subsection{Results}
sfritschi's avatar
sfritschi committed
67
	    \hspace{0.5cm}In order to assess the quality of the pressure-solution obtained by this solver, we study the fluxes induced by the pore pressures for a given base network comprised of 2636 pores. In particular, we look at all in- and out-going fluxes as well as their sum on a per pore basis (except for source/sink pores), obtained from the function \verb|flux_balance()|. When we complete this analysis for all available solvers and compare the results of the parallel PETSc solver with the existing single-core solvers, we see that the parallel version is in complete agreement with the rest in terms of solution quality, as depicted in Figure ~\ref{fig:balance}.
sfritschi's avatar
sfritschi committed
68
69
	
	\end{multicols}
sfritschi's avatar
sfritschi committed
70
	
sfritschi's avatar
sfritschi committed
71
72
	\begin{figure}[ht]
		\vspace{-0.5cm}
73
		\centering
74
		\includegraphics[width=0.8\textwidth]{plots/flux_PETSC.png}
75
		\caption{Pressures $p_{\mathrm{in}}$ and $p_{\mathrm{out}}$ are applied to in-pores and out-pores respectively, driving the network flow. The resulting pressure system is solved with the available solvers and the different fluxes are shown for the individual pores in the case of PETSc (using 4 processes). Since the sequential solvers produce an identical plot, they are omitted here. }
76
77
		\label{fig:balance}
	\end{figure}
78
	
sfritschi's avatar
sfritschi committed
79
	\begin{multicols}{2}
sfritschi's avatar
sfritschi committed
80
81
		\section[Parallel Network Generation]{Parallel Network\\ Generation}
		\vspace{-0.25cm}
82
83
		\hspace{0.5cm}The existing serial dendrogram-based network generation algorithm, as presented in \cite{MEYER2021101592}, is now modified. Concretely, to allow connecting all pores that populate the larger, generated network domain in parallel, we rely on a shared memory approach via the \verb|multiprocessing| Python module. To accomplish this, we first shift our attention to \emph{cell lists}, which offer a direct application of the already existing \textbf{maximal throat length} parameter $L_m$ as a suitable \textbf{cell size}.
		\subsection{Cell Lists}
sfritschi's avatar
sfritschi committed
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
		\hspace{0.5cm}To speed up neighbor search for the \emph{stationary} pores, we have opted to use the well-known cell lists data structure instead of the triangulation based approach outlined in \cite{MEYER2021103936}. This choice is supported by the useful properties of cell lists for our purpose of generating a network of similar pore-arrangement. It is also favorable over the triangulation method as the cell lists are only initialized once, and pores that have already been processed by the algorithm can be removed efficiently. Primarily however, it allows a straight-forward application of parallel computing, by distributing the work needed to find the neighbors of individual pores evenly. See Figure ~\ref{fig:cell} for a visualization.
	\end{multicols}

	\newpage
	
	\begin{figure}[ht]
		\vspace{-0.5cm}
		\centering
		\begin{tikzpicture}
			\draw[black] (-3,-3) grid (3,3);
			\draw[draw=orange, fill=orange!10!white] (-3, -3) grid (2, -2) rectangle (-3, -3);
			\draw[draw=orange, fill=orange!10!white] (2, -3) grid (3, 3) rectangle (2, -3);
			\draw[draw=orange, fill=orange!10!white] (-3, 2) grid (2, 3) rectangle (-3, 2);
			\draw[draw=orange, fill=orange!10!white] (-3, -2) grid (-2, 2) rectangle (-3, -2);
			\draw[draw=blue, fill=blue!10!white] (-2, -1) grid (1, 2) rectangle (-2, -1);
			\node[anchor=north, black] at (-2.5, -3) {$0$};
			\node[anchor=north, black] at (-1.5, -3) {$1$};
			\node[anchor=north, black] at (-0.5, -3) {$2$};
			\node[anchor=north, black] at (0.5, -3) {$3$};
			\node[anchor=north, black] at (1.5, -3) {$4$};
			\node[anchor=north, black] at (2.5, -3) {$5$};
			
			\node[anchor=east, black] at (-3, -2.5) {$0$};
			\node[anchor=east, black] at (-3, -1.5) {$1$};
			\node[anchor=east, black] at (-3, -0.5) {$2$};
			\node[anchor=east, black] at (-3, 0.5) {$3$};
			\node[anchor=east, black] at (-3, 1.5) {$4$};
			\node[anchor=east, black] at (-3, 2.5) {$5$};
			% Points
			\node[red] at (-0.5, 0.5) {\textbullet};
sfritschi's avatar
sfritschi committed
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
			\node[black] at (-1.04, 1.78) {\textbullet};
			\node[black] at (2.96, 1.78) {\textbullet};
			\node[black] at (-1.04, -2.22) {\textbullet};
			\node[black] at (2.96, -2.22) {\textbullet};
			\node[black] at (-0.10, 0.20) {\textbullet};
			\node[black] at (-0.13, -0.03) {\textbullet};
			\node[black] at (0.44, 0.23) {\textbullet};
			\node[black] at (-0.02, 1.70) {\textbullet};
			\node[black] at (-0.02, -2.30) {\textbullet};
			\node[black] at (-1.33, 1.17) {\textbullet};
			\node[black] at (2.67, 1.17) {\textbullet};
			\node[black] at (-1.33, -2.83) {\textbullet};
			\node[black] at (2.67, -2.83) {\textbullet};
			\node[black] at (0.63, -0.33) {\textbullet};
			\node[black] at (-1.05, 1.71) {\textbullet};
			\node[black] at (2.95, 1.71) {\textbullet};
			\node[black] at (-1.05, -2.29) {\textbullet};
			\node[black] at (2.95, -2.29) {\textbullet};
			\node[black] at (-1.33, 0.53) {\textbullet};
			\node[black] at (2.67, 0.53) {\textbullet};
			\node[black] at (-0.86, 0.14) {\textbullet};
			\node[black] at (-1.43, -0.91) {\textbullet};
			\node[black] at (2.57, -0.91) {\textbullet};
			\node[black] at (0.20, 1.58) {\textbullet};
			\node[black] at (0.20, -2.42) {\textbullet};
			\node[black] at (1.08, 1.53) {\textbullet};
			\node[black] at (-2.92, 1.53) {\textbullet};
			\node[black] at (1.08, -2.47) {\textbullet};
			\node[black] at (-2.92, -2.47) {\textbullet};
			\node[black] at (-0.83, -1.07) {\textbullet};
			\node[black] at (-0.83, 2.93) {\textbullet};
			\node[black] at (1.51, -0.19) {\textbullet};
			\node[black] at (-2.49, -0.19) {\textbullet};
			\node[black] at (1.25, 0.25) {\textbullet};
			\node[black] at (-2.75, 0.25) {\textbullet};
			\node[black] at (0.24, -1.89) {\textbullet};
			\node[black] at (0.24, 2.11) {\textbullet};
			\node[black] at (1.64, -1.59) {\textbullet};
			\node[black] at (-2.36, -1.59) {\textbullet};
			\node[black] at (1.64, 2.41) {\textbullet};
			\node[black] at (-2.36, 2.41) {\textbullet};
			\node[black] at (0.81, -0.14) {\textbullet};
			\node[black] at (0.99, 1.11) {\textbullet};
			\node[black] at (0.99, -2.89) {\textbullet};
			\node[black] at (1.56, 0.19) {\textbullet};
			\node[black] at (-2.44, 0.19) {\textbullet};
			\node[black] at (0.00, 0.29) {\textbullet};
			\node[black] at (-0.59, 1.98) {\textbullet};
			\node[black] at (-0.59, -2.02) {\textbullet};
			\node[black] at (0.69, 0.22) {\textbullet};
			\node[black] at (1.72, -0.51) {\textbullet};
			\node[black] at (-2.28, -0.51) {\textbullet};
			\node[black] at (1.46, 0.32) {\textbullet};
			\node[black] at (-2.54, 0.32) {\textbullet};
			\node[black] at (-1.95, -1.76) {\textbullet};
			\node[black] at (2.05, -1.76) {\textbullet};
			\node[black] at (-1.95, 2.24) {\textbullet};
			\node[black] at (2.05, 2.24) {\textbullet};
			\node[black] at (-1.17, 0.31) {\textbullet};
			\node[black] at (2.83, 0.31) {\textbullet};
			\node[black] at (0.55, -0.60) {\textbullet};
			\node[black] at (0.38, -1.83) {\textbullet};
			\node[black] at (0.38, 2.17) {\textbullet};
			\node[black] at (-0.87, 1.61) {\textbullet};
			\node[black] at (-0.87, -2.39) {\textbullet};
			\node[black] at (-0.72, -1.95) {\textbullet};
			\node[black] at (-0.72, 2.05) {\textbullet};
			\node[black] at (0.23, -0.43) {\textbullet};
			\node[black] at (-1.72, 1.11) {\textbullet};
			\node[black] at (2.28, 1.11) {\textbullet};
			\node[black] at (-1.72, -2.89) {\textbullet};
			\node[black] at (2.28, -2.89) {\textbullet};
			\node[black] at (-0.03, -0.16) {\textbullet};
			\node[black] at (1.77, 1.06) {\textbullet};
			\node[black] at (-2.23, 1.06) {\textbullet};
			\node[black] at (1.77, -2.94) {\textbullet};
			\node[black] at (-2.23, -2.94) {\textbullet};
			\node[black] at (-0.79, 0.18) {\textbullet};
			\node[black] at (-1.34, -0.35) {\textbullet};
			\node[black] at (2.66, -0.35) {\textbullet};
			\node[black] at (-1.77, -1.49) {\textbullet};
			\node[black] at (2.23, -1.49) {\textbullet};
			\node[black] at (-1.77, 2.51) {\textbullet};
			\node[black] at (2.23, 2.51) {\textbullet};
			\node[black] at (-0.13, -0.43) {\textbullet};
			\node[black] at (-0.28, 0.22) {\textbullet};
			\node[black] at (1.69, 0.74) {\textbullet};
			\node[black] at (-2.31, 0.74) {\textbullet};
			\node[black] at (0.16, -0.40) {\textbullet};
			\node[black] at (1.73, -1.17) {\textbullet};
			\node[black] at (-2.27, -1.17) {\textbullet};
			\node[black] at (1.73, 2.83) {\textbullet};
			\node[black] at (-2.27, 2.83) {\textbullet};
sfritschi's avatar
sfritschi committed
207
208
209
210
		\end{tikzpicture}
		\caption{Cell lists visualized in 2D. The neighborhood of the pore highlighted in red is marked in blue. Because the cell-size is the maximally permissible throat length $L_m$, only the pores contained within the blue region must be considered during neighbor search. Finally, the periodic buffer layers, containing copies of pores on the opposite side from the interior, are painted in orange.}
		\label{fig:cell}
	\end{figure}	
211
		
sfritschi's avatar
sfritschi committed
212
	\begin{multicols}{2}
213
214
215
216
217
		\subsection{Iterative Algorithm}
		\hspace{0.5cm}To connect the generated pores by throats, we employ an \textbf{iterative} strategy. Beforehand however, the cell list is constructed based on the extent of the full domain (including periodic buffer layer) and cell-size $L_m$. Next, all pores are placed in their respective cell computed from their position. Now, for each pore and for each of its throats, which are copied from the base network and are sought to be realized, we find an \emph{ideal} match from its \textbf{neighboring cells}. Here, ideal refers to minimal absolute difference between physical distance of the pores and original length of the current throat $L_t$. Given position of $i$-th pore $\mathbf{p}_i$ and original throat length $L_t$ we seek:
		\begin{equation}
			j^* = \argmin_{j \in \mathcal{N}(i)}\Bigr| ||\mathbf{p}_i - \mathbf{p}_j||_2 - L_t \Bigr|
		\end{equation}
sfritschi's avatar
sfritschi committed
218
219
220
		Where $\mathcal{N}(i)$ is the set of all pores in adjacent cells to pore $i$. Finding $j^*$ for different pores is \emph{embarrassingly parallel} and can therefore be computed by a large number of threads, storing their results in shared memory. Each thread works on an even chunk of the pores located \emph{inside} the domain. Subsequently, these ideal matches are connected, while avoiding \textbf{conflicts} of pores seeking either a neighbor that is already fully-connected or that was previously connected to them. Finally we repeat the two steps from above, now only considering pores that still have throats left in \emph{random} order (for improved \textbf{load balancing}), until \textbf{no more connections} can be found. Empirically, this process converges very fast and consistently, for different target sizes of the full domain, after merely 7-8 iterations. The first iteration alone leaves only $\approx 18\%$ of all possible throats left, see Table ~\ref{table:iter}. Final iterations may be performed serially if only few pores remain as to avoid costly spawning of threads without speed gain. The procedure is summarized as pseudo-code in Algorithm ~\ref{alg:connect}.
		\subsection{Results}
		\hspace{0.5cm}The parallel algorithm achieves
sfritschi's avatar
sfritschi committed
221
222
	\end{multicols}

sfritschi's avatar
sfritschi committed
223
	\begin{algorithm}[ht]
224
225
226
227
228
		\caption{Connect pores in parallel}
		\begin{algorithmic}
			\State Initialize $cellList$ using $pores$ and compute $totalThroats$
			\State $poresRemain \gets pores$
			\State $throatsLeft \gets totalThroats$
sfritschi's avatar
sfritschi committed
229
230
			\State $throatsUnrealized \gets totalThroats$
			\While{$throatsUnrealized > 0$}
231
232
233
234
235
236
			\State Spawn $nthreads$ threads
			\State Compute best matches for all pores in $poresRemain$ using threads
			\State Store result in shared memory location $poreMatchTable$
			\For{$pore$ \textbf{in} $poresRemain$}
			\For{$throat$ \textbf{in} $pore.throats$}
			\State Fetch $match$ pore from $poreMatchTable$
237
			\If{$throat$ is not already realized \textbf{and} $match$ is found}
238
239
240
241
242
243
244
245
246
247
248
249
250
			\State Realize $throat$
			\EndIf
			\EndFor
			\EndFor
			\State Compute list of pores with throats left in random order: $nextPores$
			\State $poresRemain \gets nextPores$
			\State Count throats that are still left: $nextThroats$
			\State $throatsLeft \gets nextThroats$
			\EndWhile
		\end{algorithmic}
		\label{alg:connect}
	\end{algorithm}

sfritschi's avatar
sfritschi committed
251
252
	\begin{table}[ht]
		\centering
sfritschi's avatar
sfritschi committed
253
		\caption{Sample run of parallel pore-connecting algorithm using 4 threads. The generated network is 3 times as large as the base network in all directions. The maximal feasible number of throats is 103464, of which 27 were not realized due to there being no possible candidates left for these remaining pores.}
sfritschi's avatar
sfritschi committed
254
255
256
257
		\begin{tabular}{|c|c|c|}
			\hline
			\textbf{Iteration} & \textbf{Throats left} & \textbf{Rel. percentage} \\
			\hline
sfritschi's avatar
sfritschi committed
258
259
260
261
262
263
264
265
			0 & 103464 & 100.0\% \\
			1 & 18690 & 18.1\% \\
			2 & 4329 & 4.2\% \\
			3 & 1096 & 1.1\% \\
			4 & 282 & 0.3\% \\
			5 & 73 & 0.1\% \\
			6 & 30 & 0.03\% \\
			7 & 27 & 0.026\% \\
sfritschi's avatar
sfritschi committed
266
267
268
269
270
			\hline
		\end{tabular}
		\label{table:iter}
	\end{table}

271
272
	\newpage
	
sfritschi's avatar
sfritschi committed
273
274
275
	\appendix
	\begin{center} \Large{\textbf{Appendix}} \end{center}
	\section{PETSc Installation}
sfritschi's avatar
sfritschi committed
276
	\label{appendix:install}
277
	For the purposes of this thesis, PETSc was installed in the following way. Given \textbf{existing} (open)MPI compilers located at \verb|/usr/bin/| (Linux).
sfritschi's avatar
sfritschi committed
278
279
280
281
282
283
284
285
286
287
288
289
	\begin{itemize}
		\item Clone \href{https://gitlab.com/petsc/petsc}{PETSc repository}.
		\item Run \textbf{configuration} script:\\
		\begin{tabular}{ l l }
			\verb|./configure| & \verb|--with-cc=/usr/bin/mpicc| \\
							   & \verb|--with-cxx=/usr/bin/mpicxx| \\
							   & \verb|--with-fc=0| \\
							   & \verb|--download-hypre|
		\end{tabular}
		\item Set environment flags \verb|PETSC_DIR| and \verb|PETSC_ARCH| as specified in the output of \verb|./configure| \& run \textbf{make all}.
	\end{itemize}
	
sfritschi's avatar
sfritschi committed
290
291
	\section{Base Network}
	\label{appendix:base}
sfritschi's avatar
sfritschi committed
292
293
294
295
296
297
298
299
300
301
	Throughout this thesis we rely on existing networks obtained via tomographic scans to serve as a \emph{basis} for \textbf{generation} of larger networks and \textbf{simulation} of network flow. The statistics of the base network mentioned previously are detailed in Figure ~\ref{fig:base}.
	
	\begin{figure}[ht]
		\vspace{-0.5cm}
		\centering
		\includegraphics[width=0.8\textwidth]{plots/base.png}
		\caption{Network consisting of 2636 pores and 4291 throats, inscribed within a cube extending $1.07\cdot 10^{-3}$m in each spatial direction. The pore-arrangement is obviously \emph{not uniform}, as can be seen by the clustering of pores in some regions, while others are mostly undisturbed. The \emph{porosity}, measured as the relative fraction of volume taken up by the void-space geometry, is roughly $32\%$.}
		\label{fig:base}
	\end{figure}

sfritschi's avatar
sfritschi committed
302
303
	\newpage
	
sfritschi's avatar
sfritschi committed
304
	\centering
sfritschi's avatar
sfritschi committed
305
	\printbibliography
sfritschi's avatar
sfritschi committed
306
\end{document}