Line | Branch | Exec | Source |
---|---|---|---|
1 | // -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- | ||
2 | // vi: set et ts=4 sw=4 sts=4: | ||
3 | // | ||
4 | // SPDX-FileCopyrightInfo: Copyright © DuMux Project contributors, see AUTHORS.md in root folder | ||
5 | // SPDX-License-Identifier: GPL-3.0-or-later | ||
6 | // | ||
7 | // ## The main program (`main.cc`) | ||
8 | // This file contains the main program flow. In this example, we use a single-phase | ||
9 | // Pore-Network-Model to evaluate the upscaled Darcy permeability of a given network. | ||
10 | // [[content]] | ||
11 | // ### Includes | ||
12 | // [[details]] includes | ||
13 | // [[codeblock]] | ||
14 | #include <config.h> | ||
15 | |||
16 | #include <iostream> | ||
17 | |||
18 | #include <algorithm> | ||
19 | |||
20 | #include <dune/common/float_cmp.hh> // for floating point comparison | ||
21 | #include <dune/common/exceptions.hh> | ||
22 | |||
23 | #include <dumux/common/properties.hh> // for GetPropType | ||
24 | #include <dumux/common/parameters.hh> // for getParam | ||
25 | #include <dumux/common/initialize.hh> | ||
26 | |||
27 | #include <dumux/linear/istlsolvers.hh> | ||
28 | #include <dumux/linear/linearsolvertraits.hh> | ||
29 | #include <dumux/linear/linearalgebratraits.hh> | ||
30 | #include <dumux/linear/pdesolver.hh> | ||
31 | #include <dumux/nonlinear/newtonsolver.hh> | ||
32 | #include <dumux/assembly/fvassembler.hh> | ||
33 | |||
34 | #include <dumux/io/vtkoutputmodule.hh> | ||
35 | #include <dumux/porenetwork/common/pnmvtkoutputmodule.hh> | ||
36 | |||
37 | #include <dumux/io/grid/gridmanager_yasp.hh> | ||
38 | #include <dumux/io/grid/porenetwork/gridmanager.hh> // for pore-network grid | ||
39 | #include <dumux/porenetwork/common/boundaryflux.hh> // for getting the total mass flux leaving the network | ||
40 | |||
41 | #include "upscalinghelper.hh" | ||
42 | #include "properties.hh" | ||
43 | // [[/codeblock]] | ||
44 | // [[/details]] | ||
45 | // | ||
46 | // ### The driver function | ||
47 | // | ||
48 | // The template argument `TypeTag` determines if we run the example assuming | ||
49 | // a creeping flow regime or not. Which regime is selected is set with the parameter | ||
50 | // `Problem.AssumeCreepingFlow` in the input file. | ||
51 | namespace Dumux { | ||
52 | |||
53 | template<class TypeTag> | ||
54 | 4 | void runExample() | |
55 | { | ||
56 | // ### Create the grid and the grid geometry | ||
57 | // [[codeblock]] | ||
58 | // The grid manager can be used to create a grid from the input file | ||
59 | using GridManager = PoreNetwork::GridManager<3>; | ||
60 | 4 | GridManager gridManager; | |
61 |
5/12✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 2 times.
✓ Branch 12 taken 2 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
|
8 | gridManager.init(); |
62 | |||
63 | // We compute on the leaf grid view. | ||
64 |
2/4✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
|
4 | const auto& leafGridView = gridManager.grid().leafGridView(); |
65 | |||
66 | // instantiate the grid geometry | ||
67 |
1/4✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
8 | auto gridData = gridManager.getGridData(); |
68 | using GridGeometry = GetPropType<TypeTag, Properties::GridGeometry>; | ||
69 |
3/8✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
|
12 | auto gridGeometry = std::make_shared<GridGeometry>(leafGridView, *gridData); |
70 | // [[/codeblock]] | ||
71 | |||
72 | // ### Initialize the problem and grid variables | ||
73 | // [[codeblock]] | ||
74 | using Problem = GetPropType<TypeTag, Properties::Problem>; | ||
75 |
2/6✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
|
8 | auto problem = std::make_shared<Problem>(gridGeometry); |
76 | |||
77 | // instantiate and initialize the discrete and exact solution vectors | ||
78 | using SolutionVector = GetPropType<TypeTag, Properties::SolutionVector>; | ||
79 |
4/10✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 2 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
|
16 | SolutionVector x(gridGeometry->numDofs()); // zero-initializes |
80 | |||
81 | // instantiate and initialize the grid variables | ||
82 | using GridVariables = GetPropType<TypeTag, Properties::GridVariables>; | ||
83 |
2/6✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
|
8 | auto gridVariables = std::make_shared<GridVariables>(problem, gridGeometry); |
84 |
2/4✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
|
8 | gridVariables->init(x); |
85 | // [[/codeblock]] | ||
86 | |||
87 | // ### Instantiate the solver | ||
88 | // We use the `NewtonSolver` class, which is instantiated on the basis | ||
89 | // of an assembler and a linear solver. When the `solve()` function of the | ||
90 | // Newton solver instance is called, it uses the assembler and | ||
91 | // linear solver to assemble and solve the linear systems in each Newton step. | ||
92 | // [[codeblock]] | ||
93 | using Assembler = FVAssembler<TypeTag, DiffMethod::numeric>; | ||
94 |
2/6✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
|
8 | auto assembler = std::make_shared<Assembler>(problem, gridGeometry, gridVariables); |
95 | |||
96 | using LinearSolver = Dumux::UMFPackIstlSolver<SeqLinearSolverTraits, LinearAlgebraTraitsFromAssembler<Assembler>>; | ||
97 |
2/6✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
|
8 | auto linearSolver = std::make_shared<LinearSolver>(); |
98 | |||
99 | using NewtonSolver = NewtonSolver<Assembler, LinearSolver>; | ||
100 |
12/28✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 2 times.
✗ Branch 14 not taken.
✓ Branch 18 taken 2 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 2 times.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✓ Branch 23 taken 2 times.
✗ Branch 24 not taken.
✓ Branch 25 taken 2 times.
✗ Branch 26 not taken.
✓ Branch 27 taken 2 times.
✓ Branch 29 taken 2 times.
✗ Branch 30 not taken.
✓ Branch 32 taken 2 times.
✗ Branch 33 not taken.
✗ Branch 34 not taken.
✗ Branch 35 not taken.
✗ Branch 36 not taken.
✗ Branch 37 not taken.
|
20 | NewtonSolver nonLinearSolver(assembler, linearSolver); |
101 | // [[/codeblock]] | ||
102 | |||
103 | // ### Initialize VTK output | ||
104 | using VtkOutputFields = GetPropType<TypeTag, Properties::IOFields>; | ||
105 | using VtkWriter = PoreNetwork::VtkOutputModule<GridVariables, GetPropType<TypeTag, Properties::FluxVariables>, SolutionVector>; | ||
106 |
7/14✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 2 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 2 times.
✗ Branch 17 not taken.
✓ Branch 19 taken 2 times.
✗ Branch 20 not taken.
|
16 | VtkWriter vtkWriter(*gridVariables, x, problem->name()); |
107 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
4 | VtkOutputFields::initOutputModule(vtkWriter); |
108 | // specify the field type explicitly since it may not be possible | ||
109 | // to deduce this from the vector size in a pore network | ||
110 |
7/16✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 2 times.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✓ Branch 16 taken 2 times.
✓ Branch 18 taken 2 times.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
|
8 | vtkWriter.addField(gridGeometry->poreVolume(), "poreVolume", Vtk::FieldType::vertex); |
111 |
7/16✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 2 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 2 times.
✗ Branch 16 not taken.
✓ Branch 18 taken 2 times.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
|
12 | vtkWriter.addField(gridGeometry->throatShapeFactor(), "throatShapeFactor", Vtk::FieldType::element); |
112 |
7/16✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 2 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 2 times.
✗ Branch 16 not taken.
✓ Branch 18 taken 2 times.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
|
12 | vtkWriter.addField(gridGeometry->throatCrossSectionalArea(), "throatCrossSectionalArea", Vtk::FieldType::element); |
113 | |||
114 | // ### Prepare the upscaling procedure. | ||
115 | // Set up a helper class to determine the total mass flux leaving the network | ||
116 |
4/8✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
|
20 | const auto boundaryFlux = PoreNetwork::BoundaryFlux(*gridVariables, assembler->localResidual(), x); |
117 | |||
118 | // ### The actual upscaling procedure | ||
119 | // #### Instantiate the upscaling helper | ||
120 | // [[codeblock]] | ||
121 | using Scalar = GetPropType<TypeTag, Properties::Scalar>; | ||
122 | using UpscalingHelper = UpscalingHelper<Scalar>; | ||
123 | 4 | UpscalingHelper upscalingHelper; | |
124 | // [[/codeblock]] | ||
125 | |||
126 | // Set the side lengths used for applying the pressure gradient and calculating the REV outflow area. | ||
127 | // One can either specify these values manually (usually more accurate) or let the UpscalingHelper struct | ||
128 | // determine it automatically based on the network's bounding box. | ||
129 | // [[codeblock]] | ||
130 | 2 | const auto sideLengths = [&]() | |
131 | { | ||
132 | using GlobalPosition = typename GridGeometry::GlobalCoordinate; | ||
133 | 10 | if (hasParam("Problem.SideLength")) | |
134 | ✗ | return getParam<GlobalPosition>("Problem.SideLength"); | |
135 | else | ||
136 | 4 | return upscalingHelper.getSideLengths(*gridGeometry); | |
137 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
4 | }(); |
138 | |||
139 | // pass the side lengths to the problem | ||
140 |
2/4✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
|
8 | problem->setSideLengths(sideLengths); |
141 | // [[/codeblock]] | ||
142 | |||
143 | // Get the maximum and minimum pressure gradient and the population of sample points specified in the input file | ||
144 | // [[codeblock]] | ||
145 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
4 | const Scalar minPressureGradient = getParam<Scalar>("Problem.MinimumPressureGradient", 1e1); |
146 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
4 | const Scalar maxPressureGradient = getParam<Scalar>("Problem.MaximumPressureGradient", 1e10); |
147 | |||
148 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
4 | if (!(minPressureGradient < maxPressureGradient)) |
149 | ✗ | DUNE_THROW(Dune::InvalidStateException, "Maximum pressure gradient must be greater than minimum pressure gradient"); | |
150 | |||
151 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
4 | const int numberOfSamples = getParam<int>("Problem.NumberOfPressureGradients", 1); |
152 | // [[/codeblock]] | ||
153 | |||
154 | // Iterate over all directions specified before, apply several pressure gradients, calculate the mass flux | ||
155 | // and finally determine the the upscaled properties. | ||
156 | // [[codeblock]] | ||
157 |
5/10✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 1 times.
✗ Branch 13 not taken.
|
18 | const auto directions = getParam<std::vector<std::size_t>>("Problem.Directions", std::vector<std::size_t>{0, 1, 2}); |
158 |
3/8✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
|
4 | upscalingHelper.setDirections(directions); |
159 |
4/4✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
|
16 | for (int dimIdx : directions) |
160 | { | ||
161 | // set the direction in which the pressure gradient will be applied | ||
162 | 8 | problem->setDirection(dimIdx); | |
163 | |||
164 |
2/2✓ Branch 0 taken 20 times.
✓ Branch 1 taken 2 times.
|
44 | for (int i = 0; i < numberOfSamples; i++) |
165 | { | ||
166 | // reset the solution | ||
167 | 40 | x = 0; | |
168 | |||
169 | // set the pressure gradient to be applied | ||
170 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 18 times.
|
40 | Scalar pressureGradient = maxPressureGradient * std::exp(i + 1 - numberOfSamples); |
171 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 18 times.
|
40 | if (i == 0) |
172 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
4 | pressureGradient = std::min(minPressureGradient, pressureGradient); |
173 | |||
174 |
2/4✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 20 times.
✗ Branch 5 not taken.
|
80 | problem->setPressureGradient(pressureGradient); |
175 | |||
176 | // solve problem | ||
177 |
1/2✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
|
40 | nonLinearSolver.solve(x); |
178 | |||
179 | // set the sample points | ||
180 |
7/16✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 20 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 20 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 20 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 20 times.
✗ Branch 14 not taken.
✓ Branch 17 taken 20 times.
✗ Branch 18 not taken.
✓ Branch 20 taken 20 times.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
|
200 | const Scalar totalFluidMassFlux = boundaryFlux.getFlux(std::vector<int>{ problem->outletPoreLabel() })[0]; |
181 |
2/4✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 20 times.
✗ Branch 5 not taken.
|
80 | upscalingHelper.setDataPoints(*problem, totalFluidMassFlux); |
182 | } | ||
183 | |||
184 | // write a vtu file for the given direction for the last sample | ||
185 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
4 | vtkWriter.write(dimIdx); |
186 | } | ||
187 | |||
188 | // calculate and report the upscaled properties | ||
189 | 4 | constexpr bool isCreepingFlow = std::is_same_v<TypeTag, Properties::TTag::PNMUpscalingCreepingFlow>; | |
190 |
2/4✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
|
8 | upscalingHelper.calculateUpscaledProperties(*problem, isCreepingFlow); |
191 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
4 | upscalingHelper.report(isCreepingFlow); |
192 | |||
193 | // compare the Darcy permeability with reference data if provided in input file and report in case of inconsistency | ||
194 |
5/12✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 2 times.
✗ Branch 10 not taken.
✗ Branch 13 not taken.
✓ Branch 14 taken 2 times.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
|
4 | static const auto referenceData = getParam<std::vector<Scalar>>("Problem.ReferencePermeability", std::vector<Scalar>{}); |
195 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
8 | if (!referenceData.empty()) |
196 |
3/8✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
|
8 | upscalingHelper.compareWithReference(referenceData); |
197 | |||
198 | // plot the results just for non-creeping flow | ||
199 | // creeping flow would just result in a straight line (permeability is independent of the pressure gradient) | ||
200 |
2/6✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
|
4 | bool plotConductivity = getParam<bool>("Output.PlotConductivity", true); |
201 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
2 | if (!isCreepingFlow && plotConductivity) |
202 | ✗ | upscalingHelper.plot(); | |
203 | 4 | }; | |
204 | |||
205 | } // end namespace Dumux | ||
206 | // [[/codeblock]] | ||
207 | |||
208 | // ### The main function | ||
209 | // [[details]] main | ||
210 | // [[codeblock]] | ||
211 | 2 | int main(int argc, char** argv) | |
212 | { | ||
213 | 2 | using namespace Dumux; | |
214 | |||
215 | // Initialize MPI+X environment | ||
216 | 2 | Dumux::initialize(argc, argv); | |
217 | |||
218 | // We parse the command line arguments. | ||
219 |
9/24✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 2 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 2 times.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✓ Branch 18 taken 2 times.
✓ Branch 19 taken 2 times.
✗ Branch 20 not taken.
✓ Branch 21 taken 2 times.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
|
8 | Parameters::init(argc, argv); |
220 | |||
221 | // Convenience alias for the type tag of the problem. | ||
222 | 2 | using CreepingFlowTypeTag = Properties::TTag::PNMUpscalingCreepingFlow; | |
223 | 2 | using NonCreepingFlowTypeTag = Properties::TTag::PNMUpscalingNonCreepingFlow; | |
224 | // // [[/codeblock]] | ||
225 | |||
226 | // user decides whether creeping flow or non-creeping flow should be run | ||
227 |
2/2✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
|
2 | if (getParam<bool>("Problem.AssumeCreepingFlow", false)) |
228 | 1 | runExample<CreepingFlowTypeTag>(); | |
229 | else | ||
230 | 1 | runExample<NonCreepingFlowTypeTag>(); | |
231 | |||
232 | // program end, return with 0 exit code (success) | ||
233 | 2 | return 0; | |
234 | } | ||
235 | // [[/codeblock]] | ||
236 | // [[/details]] | ||
237 | // [[/content]] | ||
238 |