Getting Started¶
Contributing¶
Contributing new features, bug fixes¶
Any contribution is welcome, we are trying to follow a gitflow workflow, so the project developers can create branches named features/<name of my feature> or bugfixes/<name of the fix> directly in the main akantu repository. External fellows can Fork the project. In both cases the modifications have to be submitted in the form of a Merge Request.
Building Akantu
¶
Dependencies¶
In order to compile Akantu
any compiler supporting fully C++14 should work.
In addition some libraries are required:
CMake (>= 3.5.1)
Boost (pre-processor and Spirit)
zlib
Eigen3 (if not present the build system will try to download it)
For the python interface:
Python (>=3 is recommended)
pybind11 (if not present the build system will try to download it)
To run parallel simulations:
MPI
Scotch
To use the static or implicit dynamic solvers at least one of the following libraries is needed:
MUMPS (since this is usually compiled in static you also need MUMPS dependencies)
PETSc
To compile the tests and examples:
Gmsh
google-test (if not present the build system will try to download it)
On .deb
based Linux systems¶
> sudo apt install cmake libboost-dev zlib1g-dev gmsh libeigen3-dev
# For parallel
> sudo apt install mpi-default-dev libmumps-dev libscotch-dev
# For sequential
> sudo apt install libmumps-seq-dev
Using conda
¶
This works only for sequential computation since mumps from conda-forge is compiled without MPI support:
> conda create -n akantu
> conda activate akantu
> conda install boost cmake
> conda install -c conda-forge mumps
Using homebrew
¶
> brew install gcc
> brew install boost@1.76
> brew tap brewsci/num
> brew install brewsci-mumps --without-brewsci-parmetis
If it does not work you can edit url to http://graal.ens-lyon.fr/MUMPS/MUMPS_5.3.5.tar.gz using the command:
> brew edit brewsci/num
Configuring and compilation¶
Akantu is a CMake project, so to configure it, you can follow the usual way:
> cd akantu > mkdir build > cd build > ccmake .. [ Set the options that you need ] > make > make install
On Mac OS X with homebrew
¶
You will need to specify the compiler explicitly:
.. code-block:: bash
> CC=gcc-12 CXX=g++-12 FC=gfortran-12 cmake ..
Considering that homebrew` is installed in ``/opt/homebrew
Define the location of the Scotch
library path:
> cmake .. -DSCOTCH_LIBRARY="/opt/homebrew/lib/libscotch.dylib;/opt/homebrew/lib/libscotcherr.dylib;/opt/homebrew/lib/libscotcherrexit.dylib"
Specify path to all MUMPS
libraries:
> cmake .. -DMUMPS_DIR=/opt/homebrew/opt/brewsci-mumps
In case the above does not work, specify the MUMPS
path manually using (e.g.):
> cmake .. -DMUMPS_LIBRARY_COMMON=/opt/homebrew/opt/brewsci-mumps/lib/libmumps_common.dylib
If compilation does not work change the path of the failing libraries to brew downloads in /opt/homebrew/.
Using the python interface¶
You can install Akantu
using pip, this will install a pre-compiled version, this works only on Linux machines for now:
> pip install akantu
You can then import the package in a python script as:
import akantu
The python API is similar to the C++ one, see Reference . If you encouter any problem with the python interface, you are welcome to do a merge request or post an issue on GitLab .
Tutorials with the python interface¶
To help getting started, several tutorials using the python interface
are available as notebooks with pre-installed version of Akantu
on Renku.
The tutorials are currently available:
Writing a main
function¶
Akantu
first needs to be initialized. The memory management included in the
core library handles the correct allocation and de-allocation of vectors,
structures and/or objects. Moreover, in parallel computations, the
initialization procedure performs the communication setup. This is achieved by
the function initialize
that is used as
follows:
#include "aka_common.hh"
#include "..."
using namespace akantu;
int main(int argc, char *argv[]) {
initialize("input_file.dat", argc, argv);
// your code ...
}
The initialize
function takes the text inpute
file and the program parameters which can be parsed by Akantu
in due form
(see sect:parser). Obviously it is necessary to include all files needed in
main. In this manual all provided code implies the usage of akantu
as
namespace.
Compiling your simulation¶
The easiest way to compile your simulation is to create a cmake
project by
putting all your code in some directory of your choosing. Then, make sure that
you have cmake
installed and create a CMakeLists.txt
file. An example of
a minimal CMakeLists.txt
file would look like this:
project(my_simu)
cmake_minimum_required(VERSION 3.0.0)
find_package(Akantu REQUIRED)
add_akantu_simulation(my_simu my_simu.cc)
Then create a directory called build
and inside it execute cmake
-DAkantu_DIR=<path_to_akantu> -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
. If you
installed Akantu
in a standard directory such as /usr/local
(using
make install
), you can omit the -DAkantu_DIR=<path_to_akantu>
option.
Other why path_to_akantu
is either the folder where you built Akantu
if
you did not do a make install
, or if you installed Akantu
in
CMAKE_INSTALL_PREFIX
it is <CMAKE_INSTALL_PREFIX>/share/cmake/Akantu
.
Once cmake
managed to configure and generate a makefile
you can just do
make
Creating and Loading a Mesh¶
In its current state, Akantu
supports three types of meshes: Gmsh, Abaqus and
Diana. Once a akantu::Mesh
object is created with a given spatial
dimension, it can be filled by reading a mesh input file. The method
read
of the class Mesh
infers the mesh type from the file extension. If a non-standard
file extension is used, the mesh type has to be specified.
Int spatial_dimension = 2;
Mesh mesh(spatial_dimension);
// Reading Gmsh files
mesh.read("my_gmsh_mesh.msh");
mesh.read("my_gmsh_mesh", _miot_gmsh);
The Gmsh reader adds the geometrical and physical tags as mesh data. The
physical values are stored as a Int
data called
tag_0
, if a string name is provided it is stored as a std::string
data
named physical_names
. The geometrical tag is stored as a Int
data named tag_1
.
Using Arrays¶
Data in Akantu
can be stored in data containers implemented by the
akantu::Array
class. In its most basic usage, the Array
class implemented in akantu is similar to the std::vector
class of the Standard Template Library (STL) for C++. A simple Array
containing a sequence of nb_element
values (of a given
type) can be generated with:
Array<type> example_array(nb_element);
where type
usually is Real
, Int
, UInt
or bool
. Each value is
associated to an index, so that data can be accessed by typing:
auto & val = example_array(index);
Arrays
can also contain tuples of values for each index. In that case, the
number of components per tuple must be specified at the Array
creation. For example, if we want to create an
Array
to store the coordinates (sequences of three
values) of ten nodes, the appropriate code is the following:
UInt nb_nodes = 10;
Int spatial_dimension = 3;
Array<Real> position(nb_nodes, spatial_dimension);
In this case the \(x\) position of the eighth node number will be given
by position(7, 0)
(in C++, numbering starts at 0 and not 1). If
the number of components for the sequences is not specified, the
default value of 1 is used. Here is a list of some basic operations
that can be performed on Array
:
resize(size)
change the size of theArray
.
clear
reset the size of theArray
to zero. (warning this changed in > v4.0)
copy(const Array & other)
copy anotherArray
into the current one. The twoArrays
should have the same number of components.
push_back(tuple)
append a tuple with the correct number of components at the end of theArray
.
erase(i)
erase the value at the i-th position.
find(value)
searchvalue
in the currentArray
. Return position index of the first occurence or -1 if not found.
storage()
Return the address of the allocated memory of theArray
.
Array iterators¶
It is very common in Akantu
to loop over arrays to perform a specific treatment.
This ranges from geometric calculation on nodal quantities to tensor algebra (in
constitutive laws for example). The Array
object
has the possibility to request iterators in order to make the writing of loops
easier and enhance readability. For instance, a loop over the nodal coordinates
can be performed like:
// accessing the nodal coordinates Array
// with spatial_dimension components
const auto & nodes = mesh.getNodes();
for (const auto & coords : make_view(nodes, spatial_dimension)) {
// do what you need ....
}
In that example, each coords
is a Vector<Real>
containing geometrical array of size spatial_dimension
and the iteration is
conveniently performed by the Array
iterator.
The Array
object is intensively used to store
second order tensor values. In that case, it should be specified that the
returned object type is a matrix when constructing the iterator. This is done
when calling the make_view
. For instance,
assuming that we have a Array
storing stresses, we
can loop over the stored tensors by:
for (const auto & stress :
make_view(stresses, spatial_dimension, spatial_dimension)) {
// stress is of type `const Matrix<Real>&`
}
In that last example, the Matrix<Real>
objects are
spatial_dimension
\(\times\) spatial_dimension
matrices. The light
objects Matrix<T>
and
Vector<T>
can be used and combined to do most
common linear algebra. If the number of component is 1, it is possible to use
make_view
to this effect.
In general, a mesh consists of several kinds of elements. Consequently, the
amount of data to be stored can differ for each element type. The
straightforward example is the connectivity array, namely the sequences of nodes
belonging to each element (linear triangular elements have fewer nodes than,
say, rectangular quadratic elements etc.). A particular data structure called
ElementTypeMapArray<T>
is provided
to easily manage this kind of data. It consists of a group of Arrays
, each
associated to an element type. The following code can retrieve the
ElementTypeMapArray<UInt>
which
stores the connectivity arrays for a mesh:
const ElementTypeMapArray<UInt> & connectivities =
mesh.getConnectivities();
Then, the specific array associated to a given element type can be obtained by:
const Array<UInt> & connectivity_triangle =
connectivities(_triangle_3);
where the first order 3-node triangular element was used in the presented piece of code.
Vector & Matrix¶
The Array<T>
iterators as presented in the previous
section can be shaped as Vector<T>
or
Matrix<T>
. This objects represent 1st and 2nd order
tensors. As such they come with some functionalities that we will present a bit
more into detail in this here.
Accessors:
v(i)
gives thei
-th component of the vectorv
v[i]
gives thei
-th component of the vectorv
v.size()
gives the number of component
Level 1: (results are scalars)
v.norm()
returns the geometrical norm (\(L_2\))v.norm<N>()
returns the \(L_N\) norm defined as \(\left(\sum_i |v(i)|^N\right)^{1/N}\). N can take any positive integer value. There are also some particular values for the most commonly used norms,L_1
for the Manhattan norm,L_2
for the geometrical norm andL_inf
for the norm infinity.v.dot(x)
return the dot product ofv
andx
v.distance(x)
return the geometrical norm of \(v - x\)
Level 2: (results are vectors)
v += s
,v -= s
,v *= s
,v /= s
those are element-wise operators that sum, substract, multiply or divide all the component ofv
by the scalars
v += x
,v -= x
sums or substracts the vectorx
to/fromv
v.mul(A, x, alpha)
stores the result of \(\alpha \boldsymbol{A} \vec{x}\) inv
, \(\alpha\) is equal to 1 by defaultv.solve(A, b)
stores the result of the resolution of the system \(\boldsymbol{A} \vec{x} = \vec{b}\) inv
v.crossProduct(v1, v2)
computes the cross product ofv1
andv2
and stores the result inv
Accessors:
A(i, j)
gives the component \(A_{ij}\) of the matrixA
A(i)
gives the \(i^{th}\) column of the matrix as aVector
A[k]
gives the \(k^{th}\) component of the matrix, matrices are stored in a column major way, which means that to access \(A_{ij}\), \(k = i + j M\)A.rows()
gives the number of rows ofA
(\(M\))A.cols()
gives the number of columns ofA
(\(N\))A.size()
gives the number of component in the matrix (\(M \times N\))
Level 1: (results are scalars)
A.norm()
is equivalent toA.norm<L_2>()
A.norm<N>()
returns the \(L_N\) norm defined as \(\left(\sum_i\sum_j |A(i,j)|^N\right)^{1/N}\). N can take any positive integer value. There are also some particular values for the most commonly used norms,L_1
for the Manhattan norm,L_2
for the geometrical norm andL_inf
for the norm infinity.A.trace()
return the trace ofA
A.det()
return the determinant ofA
A.doubleDot(B)
return the double dot product ofA
andB
, \(\mat{A}:\mat{B}\)
Level 3: (results are matrices)
A.eye(s)
,Matrix<T>::eye(s)
fills/creates a matrix with the \(s\mat{I}\) with \(\mat{I}\) the identity matrixA.inverse(B)
stores \(\mat{B}^{-1}\) inA
A.transpose()
returns \(\mat{A}^{t}\)A.outerProduct(v1, v2)
stores \(\vec{v_1} \vec{v_2}^{t}\) inA
C.mul<t_A, t_B>(A, B, alpha)
: stores the result of the product ofA
and code{B} time the scalaralpha
inC
.t_A
andt_B
are boolean defining ifA
andB
should be transposed or not.t_A
t_B
result
false
false
\(\mat{C} = \alpha \mat{A} \mat{B}\)
false
true
\(\mat{C} = \alpha \mat{A} \mat{B}^t\)
true
false
\(\mat{C} = \alpha \mat{A}^t \mat{B}\)
true
true
\(\mat{C} = \alpha \mat{A}^t \mat{B}^t\)
A.eigs(d, V)
this method computes the eigenvalues and eigenvectors ofA
and store the results ind
andV
such that \(d(i) = \lambda_i\) and \(V(i) = \vec{v_i}\) with \(\mat{A}\vec{v_i} = \lambda_i\vec{v_i}\) and \(\lambda_1 > ... > \lambda_i > ... > \lambda_N\)
Mesh¶
Manipulating group of nodes and/or elements¶
Akantu
provides the possibility to manipulate subgroups of elements and
nodes. Any ElementGroup
and/or
NodeGroup
must be managed by a
GroupManager
. Such a manager has the role to
associate group objects to names. This is a useful feature, in particular for
the application of the boundary conditions, as will be demonstrated in section
Setting Boundary Conditions. To most general group manager is the Mesh
class which inherits from GroupManager
.
For instance, the following code shows how to request an element group to a mesh:
// request creation of a group of nodes
NodeGroup & my_node_group = mesh.createNodeGroup("my_node_group");
// request creation of a group of elements
ElementGroup & my_element_group = mesh.createElementGroup("my_element_group");
/* fill and use the groups */
A group of nodes is stored in NodeGroup
objects. They are quite simple objects which store the indexes of the selected
nodes in a Array<UInt>
. Nodes are selected by
adding them when calling add
. For instance
you can select nodes having a positive \(X\) coordinate with the following
code:
const auto & nodes = mesh.getNodes();
auto & group = mesh.createNodeGroup("XpositiveNode");
for (auto && data : enumerate(make_view(nodes, spatial_dimension))){
auto node = std::get<0>(data);
const auto & position = std::get<1>(data);
if (position(0) > 0) group.add(node);
}
A group of elements is stored in ElementGroup
objects. Since a group can contain elements of various
types the ElementGroup
object stores indexes
in a ElementTypeMapArray<UInt>
object. Then elements can be added to the group by calling add
.
For instance, selecting the elements for which the barycenter of the nodes has a positive \(X\) coordinate can be made with:
auto & group = mesh.createElementGroup("XpositiveElement");
Vector<Real> barycenter(spatial_dimension);
for_each_element(mesh, [&](auto && element) {
mesh.getBarycenter(element, barycenter);
if (barycenter(_x) > 0.) { group.add(element); }
});