diff --git a/CMakeLists.txt b/CMakeLists.txt
index 079e6716474f67a882806d0fbc8c8afc986340a5..3328aa26f30b02a53386b076a96108ec398712de 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -27,6 +27,7 @@ add_subdirectory(cppptest)
 foreach(AE108_LIBRARY elements cpppetsc assembly solve cmdline)
     add_subdirectory(${AE108_LIBRARY})
 endforeach()
+add_subdirectory(examples)
 
 include(CMakePackageConfigHelpers)
 include(GNUInstallDirs)
diff --git a/examples/Basic.cc b/examples/Basic.cc
new file mode 100644
index 0000000000000000000000000000000000000000..01399e669d82725307b64d2a76282e272ffebe47
--- /dev/null
+++ b/examples/Basic.cc
@@ -0,0 +1,214 @@
+// © 2020 ETH Zurich, Mechanics and Materials Lab
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "ae108/assembly/Assembler.h"
+#include "ae108/cpppetsc/Mesh.h"
+#include "ae108/cpppetsc/ParallelComputePolicy.h"
+#include "ae108/cpppetsc/Vector.h"
+#include "ae108/elements/CoreElement.h"
+#include "ae108/elements/embedding/IsoparametricEmbedding.h"
+#include "ae108/elements/integrator/IsoparametricIntegrator.h"
+#include "ae108/elements/materialmodels/Hookean.h"
+#include "ae108/elements/quadrature/Quadrature.h"
+#include "ae108/elements/shape/Quad4.h"
+#include "ae108/solve/NonlinearSolver.h"
+#include <array>
+
+using namespace ae108;
+
+using Policy = cpppetsc::ParallelComputePolicy;
+using Mesh = cpppetsc::Mesh<Policy>;
+using Vector = cpppetsc::Vector<Policy>;
+using BoundaryCondition = cpppetsc::MeshBoundaryCondition<Mesh>;
+
+namespace embedding = ae108::elements::embedding;
+namespace integrator = ae108::elements::integrator;
+namespace materialmodels = ae108::elements::materialmodels;
+namespace quadrature = ae108::elements::quadrature;
+namespace shape = ae108::elements::shape;
+
+// In this example we will simulate two elements, A and B.
+// Each of these elements has four vertices.
+
+//  3 ------- 2 ------- 5
+//  |         |         |
+//  |    A    |    B    |
+//  0---------1---------4
+
+// Let's specify the parameters of this mesh.
+
+constexpr auto number_of_vertices_per_element = Mesh::size_type{4};
+constexpr auto number_of_elements = Mesh::size_type{2};
+constexpr auto number_of_vertices = Mesh::size_type{6};
+constexpr auto dimension = Mesh::size_type{2};
+constexpr auto dof_per_vertex = Mesh::size_type{2};
+
+// The connectivity specifies the vertex indices per Element.
+
+using Connectivity =
+    std::array<std::array<Mesh::size_type, number_of_vertices_per_element>,
+               number_of_elements>;
+constexpr auto connectivity = Connectivity{{
+    {{0, 1, 2, 3}}, // vertices of element A
+    {{1, 4, 5, 2}}, // vertices of element B
+}};
+
+// Each of the vertices is located at the following coordinates in physical
+// space.
+
+using VertexPositions =
+    std::array<std::array<Mesh::value_type, dimension>, number_of_vertices>;
+constexpr auto vertex_positions = VertexPositions{{
+    {{0., 0.}},
+    {{1., 0.}},
+    {{1., 1.}},
+    {{0., 1.}},
+    {{2., 0.}},
+    {{2., 1.}},
+}};
+
+// Let's specify how the elements behave when deformed.
+
+// First, we select a Hookean (linear elastic) material model in 2D.
+
+using MaterialModel = materialmodels::Hookean<dimension>;
+
+// We'll choose the Quad4 shape functions (4 shape functions) that are defined
+// in 2D reference space.
+
+using Shape = shape::Quad4;
+
+// Since also our physical space is 2D we may use the isoparametric embedding.
+
+using Embedding = embedding::IsoparametricEmbedding<Shape>;
+
+// Let's choose a simple quadrature rule in reference space.
+
+using Quadrature = quadrature::Quadrature<quadrature::QuadratureType::Cube,
+                                          dimension, 1 /* order */>;
+
+// We use an "Integrator" to integrate in physical space. Of course, it depends
+// on the selected quadrature rule and the embedding. In the case of the
+// isoparametric embedding, this embedding is specified by the "Shape".
+
+using Integrator = integrator::IsoparametricIntegrator<Shape, Quadrature>;
+
+// Now we are ready to select our element type.
+
+using Element = elements::CoreElement<MaterialModel, Integrator>;
+
+// We will assemble e.g. energy using a collection of elements. This is done by
+// the assembler. (The list DefaultFeaturePlugins contain the features (e.g.
+// energy) that most elements support.)
+
+using Assembler =
+    assembly::Assembler<Element, assembly::DefaultFeaturePlugins, Policy>;
+
+// Our goals is to minimize the energy. This is done by the solver.
+
+using Solver = solve::NonlinearSolver<Assembler>;
+
+int main(int argc, char **argv) {
+  // PETSc must be initialized before using it.
+  Policy::handleError(PetscInitialize(&argc, &argv, NULL, NULL));
+
+  // We use a scope around our computation to make sure everything is cleaned up
+  // before we call PetscFinalize.
+  {
+    const auto mesh = Mesh::fromConnectivity(
+        dimension, connectivity, number_of_vertices, dof_per_vertex);
+    auto assembler = Assembler();
+
+    const auto model = MaterialModel(1.0, 0.);
+
+    // Depending on whether we use MPI, our mesh may be distributed and not all
+    // elements are present on this computational node.
+
+    // Let's add those elements that are "local" to the assembler.
+
+    for (const auto &element : mesh.localElements()) {
+      assembler.emplaceElement(
+          element, model,
+          Integrator(Embedding(Embedding::Collection<Embedding::PhysicalPoint>{{
+              vertex_positions.at(connectivity.at(element.index()).at(0)),
+              vertex_positions.at(connectivity.at(element.index()).at(1)),
+              vertex_positions.at(connectivity.at(element.index()).at(2)),
+              vertex_positions.at(connectivity.at(element.index()).at(3)),
+          }})));
+    }
+
+    // We need to create a solver. We do not use the time, so we can set it to
+    // zero.
+
+    const auto solver = Solver(&mesh);
+    const auto time = Element::Time{0.};
+
+    // Before we can produce meaningful results, we need to specify boundary
+    // conditions. Let's fix the nodes at x=0 and pull on the nodes at x=2.
+
+    std::vector<BoundaryCondition> boundary_conditions;
+    for (const auto &vertex : mesh.localVertices()) {
+      switch (vertex.index()) {
+      case 0:
+      case 3: {
+        // The displacement in x direction is zero.
+        boundary_conditions.push_back({vertex, 0, 0.});
+        // The displacement in y direction is zero.
+        boundary_conditions.push_back({vertex, 1, 0.});
+        break;
+      }
+      case 4:
+      case 5: {
+        // The displacement in x direction is .5.
+        boundary_conditions.push_back({vertex, 0, .5});
+        // The displacement in y direction is 0.
+        boundary_conditions.push_back({vertex, 1, 0.});
+        break;
+      }
+      }
+    }
+
+    // We are ready to minimize the energy.
+
+    const auto result = solver.computeSolution(
+        boundary_conditions, Vector::fromGlobalMesh(mesh), time, &assembler);
+
+    // As a final step, we print the result.
+    // We could use: result.unwrap().print();
+
+    // However, the output of this command changes when running the application
+    // in MPI-parallel mode. This happens because the vector "result" is
+    // distributed between the ranks in this case.
+
+    // So we gather all the results locally in the canonical data format first:
+    // [ vertex-0-dof-0, vertex-0-dof-1, vertex-1-dof-0, vertex-1-dof-1, ...].
+
+    const auto global_result =
+        Vector::fromDistributedInCanonicalOrder(result, mesh, dof_per_vertex);
+
+    // Then we print this global vector only on the primary rank.
+    // We expect the degrees of freedom in x direction to be between 0 and .5,
+    // and the degrees of freedom in y direction to be 0:
+    // Vertex 0: (0.0, 0.0)
+    // Vertex 1: (.25, 0.0)
+    // Vertex 2: (.25, 0.0)
+    // Vertex 3: (0.0, 0.0)
+    // Vertex 4: (0.5, 0.0)
+    // Vertex 5: (0.5, 0.0)
+
+    if (Policy::isPrimaryRank())
+      global_result.unwrap().print();
+  }
+  Policy::handleError(PetscFinalize());
+}
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..94d6106504373ba16e7a1798c5a1c618a4b7be12
--- /dev/null
+++ b/examples/CMakeLists.txt
@@ -0,0 +1,40 @@
+# © 2020 ETH Zurich, Mechanics and Materials Lab
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+cmake_minimum_required(VERSION 3.12 FATAL_ERROR)
+
+project(ae108-examples LANGUAGES C CXX)
+
+if (NOT TARGET ae108::elements)
+    find_package(ae108 REQUIRED)
+endif()
+
+add_executable(${PROJECT_NAME}-Basic Basic.cc)
+target_link_libraries(${PROJECT_NAME}-Basic
+                      PRIVATE ae108::solve
+                      PRIVATE ae108::assembly
+                      PRIVATE ae108::elements
+)
+
+add_executable(${PROJECT_NAME}-MeshGeneration MeshGeneration.cc)
+target_link_libraries(${PROJECT_NAME}-MeshGeneration
+                      PRIVATE ae108::solve
+                      PRIVATE ae108::assembly
+                      PRIVATE ae108::elements
+)
+
+add_executable(${PROJECT_NAME}-Output Output.cc)
+target_link_libraries(${PROJECT_NAME}-Output
+                      PRIVATE ae108::cpppetsc
+)
diff --git a/examples/MeshGeneration.cc b/examples/MeshGeneration.cc
new file mode 100644
index 0000000000000000000000000000000000000000..0280241fad494b803c1811aad161f2fabf4d6c5e
--- /dev/null
+++ b/examples/MeshGeneration.cc
@@ -0,0 +1,101 @@
+// © 2020 ETH Zurich, Mechanics and Materials Lab
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "ae108/cpppetsc/Mesh.h"
+#include "ae108/cpppetsc/ParallelComputePolicy.h"
+#include "ae108/elements/mesh/Mesh.h"
+#include "ae108/elements/mesh/generate_triangle_mesh.h"
+#include "ae108/elements/tensor/Tensor.h"
+#include <array>
+
+using namespace ae108;
+
+using Policy = cpppetsc::ParallelComputePolicy;
+using Mesh = cpppetsc::Mesh<Policy>;
+
+// In this example we will mesh a rectangle with triangles and distribute the
+// result with PETSc.
+
+// Let's specify the parameters of this mesh.
+
+constexpr auto dimension = Mesh::size_type{2};
+constexpr auto dof_per_vertex = Mesh::size_type{2};
+
+// We choose the rectangle to have x-length=3 and y-length=2.
+
+constexpr auto rectangle_size = elements::tensor::Tensor<double, 2>{{3., 2.}};
+
+// Also, we want to generate 2 * 4 triangles in x-direction, and 2 * 1 triangles
+// in y-direction.
+
+constexpr auto mesh_granularity =
+    elements::tensor::Tensor<std::size_t, 2>{{4, 1}};
+
+int main(int argc, char **argv) {
+  // PETSc must be initialized before using it.
+  Policy::handleError(PetscInitialize(&argc, &argv, NULL, NULL));
+
+  // We use a scope around our computation to make sure everything is cleaned up
+  // before we call PetscFinalize.
+  {
+    // First we generate the mesh locally. This mesh will look the same on every
+    // rank.
+
+    const auto generated_mesh = elements::mesh::generate_triangle_mesh(
+        rectangle_size, mesh_granularity);
+
+    // The number of positions in the mesh provides the number of vertices here.
+
+    const auto number_of_vertices = generated_mesh.number_of_positions();
+
+    // Let's create the distributed PETSc-mesh using the generated connectivity
+    // and print it to the console. We expect 2 * 4 2-cells (triangles) and
+    // 2 * (4 + 1) 0-cells (vertices). In parallel, some vertices are
+    // shared between ranks and we therefore expect a sum of at least 10
+    // 0-cells.
+
+    const auto mesh =
+        Mesh::fromConnectivity(dimension, generated_mesh.connectivity(),
+                               number_of_vertices, dof_per_vertex);
+    mesh.print();
+
+    // Before we continue, we wait until all ranks are done printing.
+
+    Policy::handleError(PetscBarrier(nullptr));
+
+    // The PETSc mesh does not contain vertex positions, only the generated mesh
+    // does. Therefore, use the generated mesh to access vertex positions using
+    // an index. To test that this works, we print the position of the second
+    // vertex (index 1) on every rank that shares it. We expect this vertex to
+    // be at (0., 2.).
+
+    for (const auto &vertex : mesh.localVertices()) {
+      if (vertex.index() != 1) {
+        continue;
+      }
+
+      // We have found the vertex with index 1 locally.
+
+      const auto position = generated_mesh.position_of_vertex(1);
+      printf("The second vertex is at (%f, %f).\n", position.at(0),
+             position.at(1));
+    }
+
+    // If you choose enough processes (in my case 4 works), you will see that
+    // the vertex is shared by two ranks because more than one rank prints the
+    // message.
+  }
+
+  Policy::handleError(PetscFinalize());
+}
diff --git a/examples/Output.cc b/examples/Output.cc
new file mode 100644
index 0000000000000000000000000000000000000000..d39f14e4f8375cccab24fce195524117f4f53c39
--- /dev/null
+++ b/examples/Output.cc
@@ -0,0 +1,128 @@
+// © 2020 ETH Zurich, Mechanics and Materials Lab
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "ae108/cpppetsc/Mesh.h"
+#include "ae108/cpppetsc/ParallelComputePolicy.h"
+#include "ae108/cpppetsc/Vector.h"
+#include "ae108/cpppetsc/Viewer.h"
+#include "ae108/cpppetsc/createVectorFromSource.h"
+#include "ae108/cpppetsc/setName.h"
+#include "ae108/cpppetsc/writeToViewer.h"
+#include <algorithm>
+#include <array>
+
+using namespace ae108;
+
+using Policy = cpppetsc::ParallelComputePolicy;
+using Mesh = cpppetsc::Mesh<Policy>;
+using Vector = cpppetsc::Vector<Policy>;
+using Viewer = cpppetsc::Viewer<Policy>;
+
+// In this example we will save data for two elements, A and B, to a HDF5 file.
+// Each of these elements has four vertices.
+
+//  3 ------- 2 ------- 5
+//  |         |         |
+//  |    A    |    B    |
+//  0---------1---------4
+
+// Let's specify the parameters of this mesh.
+
+constexpr auto number_of_vertices_per_element = Mesh::size_type{4};
+constexpr auto number_of_elements = Mesh::size_type{2};
+constexpr auto number_of_vertices = Mesh::size_type{6};
+constexpr auto topological_dimension = Mesh::TopologicalDimension{2};
+constexpr auto coordinate_dimension = Mesh::CoordinateDimension{3};
+constexpr auto dof_per_vertex = Mesh::size_type{1};
+constexpr auto dof_per_element = Mesh::size_type{0};
+
+// The connectivity specifies the vertex indices per Element.
+
+using Connectivity =
+    std::array<std::array<Mesh::size_type, number_of_vertices_per_element>,
+               number_of_elements>;
+constexpr auto connectivity = Connectivity{{
+    {{0, 1, 2, 3}}, // vertices of element A
+    {{1, 4, 5, 2}}, // vertices of element B
+}};
+
+// Each of the vertices is located at the following coordinates in physical
+// space.
+
+using VertexPositions =
+    std::array<std::array<Mesh::value_type, coordinate_dimension>,
+               number_of_vertices>;
+constexpr auto vertex_positions = VertexPositions{{
+    {{0., 0., 0.}},
+    {{1., 0., 0.}},
+    {{1., 1., 0.}},
+    {{0., 1., 0.}},
+    {{2., 0., 0.}},
+    {{2., 1., 0.}},
+}};
+
+int main(int argc, char **argv) {
+  // PETSc must be initialized before using it.
+  Policy::handleError(PetscInitialize(&argc, &argv, NULL, NULL));
+
+  // We use a scope around our computation to make sure everything is cleaned up
+  // before we call PetscFinalize.
+  {
+    // First we create a mesh.
+    const auto mesh = Mesh::fromConnectivity(
+        topological_dimension, coordinate_dimension, connectivity,
+        number_of_vertices, dof_per_vertex, dof_per_element);
+
+    // Then we create a vector of coordinates from `vertex_positions`.
+    using DataSource = std::function<void(Mesh::size_type, Mesh::value_type *)>;
+    auto coordinates = cpppetsc::createVectorFromSource(
+        mesh, coordinate_dimension,
+        DataSource(
+            [&](const Mesh::size_type index, Mesh::value_type *const out) {
+              const auto &position = vertex_positions.at(index);
+              std::copy(position.begin(), position.end(), out);
+            }));
+    cpppetsc::setName("coordinates", &coordinates);
+
+    // Let's create a global vector and fill it with the vertex indices.
+    constexpr auto data_per_vertex = coordinate_dimension + 2;
+    auto data = cpppetsc::createVectorFromSource(
+        mesh, data_per_vertex,
+        DataSource(
+            [&](const Mesh::size_type index, Mesh::value_type *const out) {
+              std::fill_n(out, data_per_vertex,
+                          static_cast<Mesh::value_type>(index));
+            }));
+    cpppetsc::setName("vertex_index_data", &data);
+
+    // Finally we write the results to "output.ae108".
+    // We start with writing the mesh.
+    auto viewer = Viewer::fromHdf5FilePath("output.ae108");
+    cpppetsc::writeToViewer(mesh, coordinates, &viewer);
+
+    // Then we write two vector fields of different dimension.
+    cpppetsc::writeToViewer(data, &viewer);        // dimension 5
+    cpppetsc::writeToViewer(coordinates, &viewer); // dimension 3
+
+    // Note that Paraview supports both "point arrays" and "cell arrays"
+    cpppetsc::writeToViewer(
+        Mesh::vector_type::fromGlobalMesh(mesh.cloneWithDofs(1, 0)), &viewer);
+    cpppetsc::writeToViewer(
+        Mesh::vector_type::fromGlobalMesh(mesh.cloneWithDofs(0, 1)), &viewer);
+
+    fprintf(stderr,
+            "The data has been written to the file \"output.ae108\".\n");
+  }
+  Policy::handleError(PetscFinalize());
+}
\ No newline at end of file