Commit 1f801483 authored by Dennis Gläser's avatar Dennis Gläser
Browse files

Merge branch 'feature/improve-docu' into 'master'

[README] improve introductory section

See merge request tools/frackit!167
parents 3ffe1369 96569d79
......@@ -6,10 +6,18 @@
What is Frackit?
================
Frackit is an open-source framework for the stochastic generation of fracture networks.
The implementation is written in C++ language and extensively uses [OpenCASCADE][2],
an open-source Computer Aided Design (CAD) library. Moreover, Python bindings are available
that allow for using almost all of the functionality of Frackit from Python.
Frackit is an open-source software framework for the stochastic generation of fracture networks,
in which the fractures are represented by geometric entities of codimension one. That is,
two-dimensional geometries in three-dimensional space, or one-dimensional geometries in
two-dimensional space. Its main purpose is to provide a means of generating networks with
specific statistical characteristics to be used in conjunction with numerical models, for
instance, in investigations on the impact of fracture network topologies on the hydraulic
or mechanical properties of fractured porous media.
The code is written in C++ language, but Python bindings are available that allow users to
access all functionalities from Python after their installation. Frackit makes extensive use
of [OpenCASCADE][2], an open-source Computer Aided Design (CAD) library, which provides
great flexibility concerning the geometries that can be used.
The geometric data generated by Frackit can be exported into a number of standard CAD
file formats supported by [OpenCASCADE][2]. This allows for visualization with a
......@@ -38,7 +46,7 @@ Thus, you can use Frackit in a toolchain with [Gmsh][1] and
[DuMuX][0] to perform simulations on the generated fracture networks.
The following picture shows an exemplary result for a single-phase simulation
on the fracture network shown above, and it depicts the pressure distribution
on a fracture network generated with Frackit, and it depicts the pressure distribution
on the fractures as well as the fluid velocities in the domain:
<p align="center">
......@@ -52,11 +60,18 @@ contains the code to generating a fracture network on the above domain geometry.
General Concept
===============
Frackit does not prescribe a certain program flow, instead, users are motivated to implement
their own applications using the provided functions, classes and concepts. These comprise
functionality related to the stochastic sampling of a fracture entity, checking geometric
constraints, and fragmentation of a collection of entities to determine connectivity
information.
### Geometry Sampling
The generation of the fracture networks occurs by randomly sampling instances of the
desired fracture geometry on the basis of probability distributions, which
can be defined by the user. The implementation allows for both selecting the type of
distribution (uniform, exponential, etc.) as well as the distribution parameters.
desired fracture geometry on the basis of probability distributions for the parameters
that describe their orientation and/or spatial location. In the code this is realized
in sampler classes, into which the desired statistical properties can be injected and
from which entity candidates can then be sampled.
### Constraints Evaluation
After the generation of a new candidate for a fracture entity, a number of constraints can
......@@ -65,26 +80,25 @@ fracture network, e.g. fracture spacing, by defining a minimum distance between
Other constraints are targeted mainly at guaranteeing certain mesh properties by avoiding
very small length scales, and thus, small elements in the computational mesh. As constraints
one can define a minimum length scale of the intersections between fracture entities, a
minimum intersection angle, and a minimum distance of the intersection geometry to the boundary
minimum intersection angle, and a minimum distance of the intersection geometry to the boundaries
of the intersecting entities. If the user-defined constraints are not fulfilled, the candidate
is rejected.
can be rejected. Frackit provides a class that allows users to define and check for geometric
constraints, but one can also implement custom constraints using the provided geometric algorithms.
### Fragmentation of the network
After the desired number of fracture entities have been generated, an __EntityNetwork__
can be constructed from the raw entities. This intersects and fragments all entities, and
if desired, one can confine the network to a domain of choice.
Note that none of these steps are mandatory and there is no fixed and prescribed
program flow. Instead, users are motivated to implement their own applications
using the provided functions, classes and concepts. The modular design of the
above-mentioned building blocks enables users to fully customize each step to
their needs. Three exemplary applications are contained in this repository:
The modular design of the above-mentioned building blocks enables users to fully customize
each step to their needs. In order to provide an overview of the capabilities of Frackit
and how to use them, various exemplary applications are contained in this repository.
### Example applications
This repository provides a number of examples that may help you to get familiar with the basic concepts of Frackit.
These are not meant to represent realistic scenarios or lead to realistic network topologies, but are rather designed
such that the execution time is low and that a wide range of functionalities of Frackit are covered.
such that the execution time is low and that a wide range of functionalities of Frackit is covered.
* [Example 1][5] Generation of a simple network consisting of quadrilaterals with two main orientations.
* [Example 2][6] Generation of a network of quadrilaterals embedded in a cylindrical domain (mimicking a core sample).
......
......@@ -6,15 +6,23 @@
Example 1
=========
In this exemplary application, a network of quadrilateral fractures is generated
within the unit cube (see image above). The main file containing the source code
to this example is the file `example1.cc` which is located in this folder. Note
that this description focuses on the C++ implementation, but in `example1.py`
it is illustrated how to realize this example using the Frackit python bindings.
In this exemplary application, a network of quadrilateral fractures with two main
orientations is generated within the unit cube (see image above). The main file
containing the source code to this example is the file `example1.cc` which is
located in this folder. Note that this description focuses on the C++ implementation,
but in `example1.py` you can find how to realize this example using the Frackit
python bindings.
<b> _In this example, you will learn how to:_ </b>
* use the sampler class for quadrilaterals
* define and enforce geometric constraints between entities
* construct an __EntityNetwork__ out of the raw entities to gather connectivity information
### Quadrilateral samplers
Two main orientations are considered for the quadrilaterals, for both of which
Two main orientations are considered for the quadrilaterals, and for both
a corresponding instance of the `QuadrilateralSampler` class is created.
For example, we instantiate an instance of this class by writing:
......@@ -27,7 +35,7 @@ using UniformDistro = std::uniform_real_distribution<ctype>;
using QuadSampler = QuadrilateralSampler<worldDimension>;
Box<ctype> domain(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
QuadSampler quadSampler(makeUniformPointSampler(domain), // point sampler that samples the center points of the quadrilaterals
QuadSampler quadSampler(makeUniformPointSampler(domain), // point sampler that samples the center points of the quadrilaterals
NormalDistro(toRadians(0.0), toRadians(5.0)), // strike angle: mean value & standard deviation
NormalDistro(toRadians(0.0), toRadians(5.0)), // dip angle: mean value & standard deviation
UniformDistro(0.4, 0.8), // strike length
......@@ -38,8 +46,9 @@ The first constructor argument is a point sampler with which the center points o
the quadrilaterals are sampled. Here we use uniformly sampled points in the unit
cube, which is represented by an instance of the `Box` class, stored in the
variable `domain`. The second and third arguments define the distributions for
the strike and dip angle (for details see the [class documentation][2]), where in this case we use uniform distributions with
a mean value of 0° and a standard deviation of 5°. The fourth and last arguments
the strike and dip angles (for details see the [class documentation][2]), where in
this case, we use uniform distributions with
a mean value of 0° and a standard deviation of 5°. The last two arguments
are distributions for the sizes of the quadrilaterals in strike and dip direction.
In the example, the quadrilaterals are sampled from the two samplers `quadSampler1` and
......@@ -57,7 +66,7 @@ entities.
### Constraints definitions
In order to certain constraints to be fulfilled among the entities, we use instances
In order for certain constraints to be fulfilled among the entities, we use instances
of the `EntityNetworkConstraints` class and
configure it as desired. For example, the constraints on entities of the same
orientation are defined in this example as follows:
......@@ -76,6 +85,16 @@ constraintsOnSelf.setMinIntersectionMagnitude(0.05);
constraintsOnSelf.setMinIntersectionDistance(0.05);
```
Among entities of different orientation, we want to ensure larger intersection angles.
To this end, we simply create a copy of the constraints object, but define a larger
minimum intersection angle:
```cpp
// with respect to entities of the other set, we want to have larger intersection angles
auto constraintsOnOther = constraintsOnSelf;
constraintsOnOther.setMinIntersectingAngle(toRadians(40.0));
```
### Entity sampling
In the main loop of quadrilateral generation, we sample candidates for new
......@@ -92,16 +111,22 @@ where `entityset1` and `entitySet2` are of type `std::vector<Quadrilateral>` and
store all quadrilaterals that are accepted. The function `evaluate` of the
`EntityNetworkConstraints` class evaluates the constraints for `quad` against all
entities contained in `entitySet` and returns `true` only if no violation of
any of the defined constraints has been found. After an admissible quadrilateral
has been generated, the line
any of the defined constraints has been detected. After an admissible quadrilateral
has been generated, we add it to the current set by
```cpp
// the quadrilateral is admissible
entitySet.push_back(quad);
```
and make sure that in the next iteration we sample a candidate of the other orientation:
```cpp
// sample into the other set the next time
sampleIntoSet1 = !sampleIntoSet1;
```
at the end of the loop makes sure that a quadrilateral of the other orientation
is sampled next. In [Example 3][0] we will get to know how to use helper classes
In [Example 3][0] we will get to know how to use helper classes
that store different entity sets and automatically sample from various sampler
classes such that this can be written more easily.
......@@ -123,7 +148,7 @@ This network can then be written to disk, for example in [Gmsh][1] (.geo) file f
```cpp
GmshWriter writer(network);
writer.write("network", // filename of the .geo files (will add extension .geo automatically)
0.1); // element size to be used
0.1); // element size to be used on the network entities
```
Note that with the `EntityNetworkBuilder` class we have created a network that
......
......@@ -10,6 +10,12 @@ __Note that this description focuses on the C++ implementation given in the main
file `example2.cc`, but in `example2.py` it is illustrated how to realize this
example using the Frackit python bindings.__
<b> _In this example, you will learn how to:_ </b>
* define a cylindrical domain
* compute the part of entities that is contained inside the domain
* confine the generated entity network onto the domain
In this exemplary application, a network of quadrilaterals, following the same
distributions as the network of [example 1][0], is created. However, in this
example we want to confine the network to a cylindrical domain (see image above).
......@@ -58,8 +64,8 @@ In the first two if clauses, the constraints against other entities are checked,
subsequently, the constraints against the boundary faces of the domain cylinder are evaluated.
As you can see, the constraints against the other entities are evaluated on the basis of the
raw entities. This can potentially fail to detect small length scales that appear after confining
the entities to the domain. A possibilty to address this and make the algorithm more robust (but
less efficient) is discussed at the end of this page.
the entities to the domain. A possibilty to address this and make the algorithm more robust
(but computationally more demanding) is discussed at the end of this page.
As you can see in the above code snippet, the `Cylinder` class provides functions for obtaining th
representations of the top, bottom and lateral boundaries. The top and bottom boundaries are
......@@ -160,7 +166,7 @@ In such a case, the constraint on the minimum allowed length of intersection seg
might be fulfilled when checked on the raw entities, but might not be fulfilled on the
final geometry after confinement of all entities to the domain.
In order to overcome this, one can also evaluate the constraints on the confined entities,
thus, using the final geometry of an entity after its confinement. This could be achieved
and thus, using the final geometry of an entity after its confinement. This could be achieved
by adjusting the part of the code of this example where the constraints are evaluated in the following way:
```cpp
......@@ -185,7 +191,7 @@ Note that the variable `containedFaces` is of type `std::vector<TopoDS_Face>`,
where `TopoDS_Face` is the OpenCascade type used to represent general two-dimensional
faces. In the above code snippets we have assumed `entitySet` and `otherEntitySet`
to also be of type `std::vector<TopoDS_Face>`, and to be composed of the previously
accepted faces.
accepted faces (also confined to the domain).
An effective way to remove very small fragments or edges that were not "detected"
by the constraints, is to adjust the epsilon used for the fragmentation.
......
......@@ -10,6 +10,16 @@ __As in the previous examples, this description focuses on the C++ implementatio
given in the main file `example3.cc`, but in `example3.py` you can find how to
realize this example using the Frackit python bindings.__
<b> _In this example, you will learn how to:_ </b>
* read in a complex domain geometry from an CAD file
* get the bounding box of an arbitrary geometry
* use the `MultiGeometrySampler` and `MultiGeometryEntitySet` classes to conveniently compose a network out of multiple entity geometries
* use the `ConstraintsMatrix` class to conveniently define several constraints to be fulfilled among different entity sets
* define multiple confining/non-confining sub-domains to place the entities in
### Read in a domain geometry
In contrast to the [previous example][0], we now want to create a network within
a complex domain that cannot be represented by the internal geometry classes.
The domain has been created with [Gmsh][1] and has been saved in .brep file format.
......@@ -24,7 +34,7 @@ const auto domainShape = OCCUtilities::readShape(BREPFILE);
BREPFILE is a preprocessor variable that is substituted by cmake with the actual
path to the .brep file used for this test (see the file `CMakeLists.txt` in this folder).
The domain specified in the .brep file consists of three layers (see above image),
and we want to construct an entity network only in the center one. The variable
and here we want to construct an entity network only in the center one. The variable
`domainShape` contains the information of all layers, and we can use the utility
function
......@@ -41,6 +51,9 @@ const auto& networkDomain = solids[1];
```
Note that this requires knowledge about the ordering of the solids in the .brep file.
### Define entity samplers
As in the previous examples, we need a point sampler that samples the points to be
used as the center points of the fracture entities. In this example, we again want to
uniformly sample the points within a box, for which we use the bounding box of
......@@ -83,7 +96,7 @@ constructor arguments specify the distributions used to determine the orientatio
of the disks (for details we refer to the [class documentation][3]).
We use the `MultiGeometrySampler` class to facilitate sampling from several sampler
class with possibly varying geometry types. Arbitrary many samplers can be added to
classes with possibly varying geometry types. Arbitrary many samplers can be added to
this class, with the following syntax:
```cpp
......@@ -102,8 +115,8 @@ multiSampler.addGeometrySampler(diskSampler, diskSetId);
multiSampler.addGeometrySampler(quadSampler, quadSetId);
```
Each sampler is associated with a unique identifier and an error is thrown if it
is tried to add a sampler with an identifier that is already taken. Sampling
Each sampler is associated with a unique identifier and an error is thrown if one
tries to add a sampler with an identifier that is already taken. Sampling
occurs again by using the `()` operator, but, in this case it receives an instance
of `Id`, in which it will store the identifier of the sampler from which the
geometry has been generated. That is, the code
......@@ -113,21 +126,26 @@ Id id;
auto geom = multiSampler(id);
```
stores in `id` the identifier of the sampler from which `geom` was sampled.
The variable `geom` holds an instance of an abstract geometry class as the
return type of the `()` operator of the `MultiGeometrySampler` must be uniquely
defined. However, instances of the abstract geometry class can be cast back into
the actual geometry (in this case `Disk` or `Quad`).
stores in `id` the identifier of the sampler from which `geom` was sampled
(in this case this is either `diskSetId` or `quadSetId`). Note that, per default,
a `SequentialSamplingStrategy` is used, which means that the provided ids are used
successively in the calls to the `()` operator. However, users can implement their
own strategies and pass them to the constructor of `MultiGeometrySampler`.
In the above code snippet, the variable `geom` holds an instance of an abstract
geometry class as the return type of the `()` operator of the `MultiGeometrySampler`
must be uniquely defined. However, instances of the abstract geometry class can be
cast back into the actual geometry (in this case `Disk` or `Quad`).
In this context, another useful class is the `MultiGeometryEntitySet`. It can
store arbitrarily many sets of entities of different types. These
types have to be provided as template arguments to the class:
types have to be provided as template arguments to the class. In this example,
we use `Quadrilateral`s and `Disk`s, and therefore, we construct the class as follows:
```cpp
MultiGeometryEntitySet<Disk, Quad> entitySets;
```
Entities are added passing a unique identifier, that is, the code
Entities are again added by passing a unique identifier, that is, the code
```cpp
Disk disk = diskSampler();
......@@ -148,9 +166,11 @@ entitySets.addEntity(geom, id);
This allows the generation and storage of multiple geometry types and multiple orientations
in a compact way.
### Constraints definitions
In the previous examples, the constraints for a new entity candidate were evaluated
manually against the previously admitted entities obtained from the different samplers.
This becomes increasingly cumbersome the more directions are to be considered, especially
This becomes increasingly cumbersome the more orientations are to be considered, especially
if individual constraints are chosen between entities obtained from different samplers.
In such cases, the `EntityNetworkConstraintsMatrix` class can be used to facilitate
the constraints evaluation. It can be used in conjunction with the `MultiGeometryEntitySet`
......@@ -184,11 +204,14 @@ if (!constraintsMatrix.evaluate(entitySets, geom, id))
In this function call, `id` holds the identifier of the set for which the candidate
`geom` was sampled. Internally, the `EntityNetworkConstraintsMatrix` instance will
check `geom` against the other entity sets using all constraints that were defined for this identifier. For instance, in this example a disk-shaped geometry sampled from
check `geom` against the other entity sets using all constraints that were defined for this identifier.
For instance, in this example a disk-shaped geometry sampled from
the sampler with id `diskSetId`, will be checked against all entities of the set with
id `diskSetId` using `constraints1` and against all entities of the set with id `quadSetId`
using `constraintsOnOther`.
### Network construction
After successful generation of the desired number of entities, we again want to
construct an entity network from the raw entities and write it out in [Gmsh][1]
file format. In a first step, we create an instance of the network builder
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment