diff --git a/examples/python/solver_callback/CMakeLists.txt b/examples/python/solver_callback/CMakeLists.txt
new file mode 100644
index 000000000..4f7e8017e
--- /dev/null
+++ b/examples/python/solver_callback/CMakeLists.txt
@@ -0,0 +1,35 @@
+#===============================================================================
+# @file   CMakeLists.txt
+#
+# @author Nicolas Richart <nicolas.richart@epfl.ch>
+#
+# @brief  CMakeLists for solid mechanics dynamics example
+#
+#
+# @section LICENSE
+#
+# Copyright (©) 2018-2021 EPFL (Ecole Polytechnique Fédérale de Lausanne)
+# Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)
+#
+# Akantu is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option) any
+# later version.
+# 
+# Akantu is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+# 
+# You should have received a copy of the GNU Lesser General Public License along
+# with Akantu. If not, see <http://www.gnu.org/licenses/>.
+#
+#===============================================================================
+
+
+add_mesh(solver_callback_mesh bar.geo DIM 2)
+register_example(solver_callback
+  SCRIPT solver_callback.py
+  PYTHON
+  FILES_TO_COPY material.dat
+  DEPENDS solver_callback_mesh)
diff --git a/examples/python/solver_callback/bar.geo b/examples/python/solver_callback/bar.geo
new file mode 100644
index 000000000..cf7a819d3
--- /dev/null
+++ b/examples/python/solver_callback/bar.geo
@@ -0,0 +1,35 @@
+// Mesh size
+h  = 0.05;
+
+h1 = h;
+h2 = h;
+
+// Dimensions of the bar
+Lx = 10;
+Ly = 1;
+
+// ------------------------------------------
+// Geometry
+// ------------------------------------------
+
+Point(101) = { 0.0, -Ly/2, 0.0, h1};
+Point(102) = { Lx,  -Ly/2, 0.0, h2};
+
+Point(103) = { Lx,  Ly/2., 0.0,  h2};
+Point(104) = { 0.0, Ly/2., 0.0,  h1};
+
+Line(201) = {101, 102};
+Line(202) = {102, 103};
+Line(203) = {103, 104};
+Line(204) = {104, 101};
+
+Line Loop(301) = {201, 202, 203, 204};
+Plane Surface(301) = {301};
+
+Transfinite Surface "*";
+Recombine Surface "*";
+Physical Surface("Body") = {301};
+
+Physical Line("YBlocked") = {201, 203};
+Physical Line("Right") = {202};
+Physical Line("Left") = {204};
diff --git a/examples/python/solver_callback/material.dat b/examples/python/solver_callback/material.dat
new file mode 100644
index 000000000..42b9b8ae7
--- /dev/null
+++ b/examples/python/solver_callback/material.dat
@@ -0,0 +1,6 @@
+material elastic [
+	 name = fictive
+	 rho = 1.      # density
+	 E = 1.        # young modulus
+	 nu = .3       # poisson ratio
+]
diff --git a/examples/python/solver_callback/solver_callback.py b/examples/python/solver_callback/solver_callback.py
new file mode 100644
index 000000000..5ef00ca96
--- /dev/null
+++ b/examples/python/solver_callback/solver_callback.py
@@ -0,0 +1,178 @@
+#!/usr/bin/env python3
+""" solver_callback.py: solver_callback overload example"""
+
+__author__ = "Nicolas Richart"
+__credits__ = [
+    "Guillaume Anciaux <guillaume.anciaux@epfl.ch>",
+    "Nicolas Richart <nicolas.richart@epfl.ch>",
+]
+__copyright__ = "Copyright (©) 2016-2021 EPFL (Ecole Polytechnique Fédérale" \
+                " de Lausanne) Laboratory (LSMS - Laboratoire de Simulation" \
+                " en Mécanique des Solides)"
+__license__ = "LGPLv3"
+
+import numpy as np
+import akantu as aka
+
+
+class SolverCallback(aka.SolverCallback):
+    def __init__(self, model):
+        super().__init__(model.getDOFManager())
+        self.model = model
+
+        mesh = model.getMesh()
+        left = mesh.getElementGroup("Left").getNodeGroup().getNodes()
+        right = mesh.getElementGroup("Right").getNodeGroup().getNodes()
+        position = mesh.getNodes()
+
+        self.pair = []
+        for node_l in left:
+            node_l = int(node_l)
+            for node_r in right:
+                node_r = int(node_r)
+                if abs(position[node_r, 1] - position[node_l, 1]) < 1e-6:
+                    self.pair.append([node_l, node_r])
+
+        blocked_dofs = model.getBlockedDOFs()
+        self.periodic_K_modif = aka.TermsToAssemble()
+
+        matrix_type = self.model.getMatrixType("K")
+        for p in self.pair:
+            #blocked_dofs[p[1]] = True
+            self.periodic_K_modif(p[0]*2, p[0]*2, 1)
+            self.periodic_K_modif(p[0]*2, p[1]*2, -1)
+            if matrix_type == aka._unsymmetric:
+                self.periodic_K_modif(p[1]*2, p[0]*2, -1)
+                self.periodic_K_modif(p[1]*2, p[1]*2,  1)
+
+        self.first = True
+        self.k_release = -1
+
+    def getMatrixType(self, matrix_id):
+        return self.model.getMatrixType(matrix_id)
+
+    def assembleMatrix(self, matrix_id):
+        self.model.assembleMatrix(matrix_id)
+        if matrix_id == "K":
+            release = self.model.getDOFManager().getMatrix("K").getRelease()
+            if release == self.k_release:
+                return
+
+            if self.first:
+                self.model.getDOFManager().getMatrix("K").saveMatrix("K0.mtx")
+
+            self.model.getDOFManager().assemblePreassembledMatrix(
+                'displacement', 'displacement', "K", self.periodic_K_modif)
+            if self.first:
+                self.model.getDOFManager().getMatrix("K").saveMatrix("K1.mtx")
+
+            self.k_release = self.model.getDOFManager().getMatrix("K").getRelease()
+            self.first = False
+
+    def assembleLumpedMatrix(self, matrix_id):
+        self.model.assembleLumpedMatrix(matrix_id)
+
+    def assembleResidual(self):
+        displacement = self.model.getDisplacement()
+        force = np.zeros(displacement.shape)
+        for p in self.pair:
+            force[p[0], 0] += displacement[p[0], 0] - displacement[p[1], 0]
+            force[p[1], 0] += displacement[p[1], 0] - displacement[p[0], 0]
+        self.model.getDOFManager().assembleToResidual('displacement', force, -1.);
+        self.model.assembleResidual();
+
+# -----------------------------------------------------------------------------
+def main():
+    spatial_dimension = 2
+    mesh_file = 'bar.msh'
+    max_steps = 250
+    time_step = 1e-3
+
+    aka.parseInput('material.dat')
+
+    # -------------------------------------------------------------------------
+    # Initialization
+    # -------------------------------------------------------------------------
+    mesh = aka.Mesh(spatial_dimension)
+    mesh.read(mesh_file)
+
+    model = aka.SolidMechanicsModel(mesh)
+
+    model.initFull(_analysis_method=aka._implicit_dynamic)
+
+    model.setBaseName("solver_callback")
+    model.addDumpFieldVector("displacement")
+    model.addDumpFieldVector("acceleration")
+    model.addDumpFieldVector("velocity")
+    model.addDumpFieldVector("internal_force")
+    model.addDumpFieldVector("external_force")
+    model.addDumpField("strain")
+    model.addDumpField("stress")
+    model.addDumpField("blocked_dofs")
+
+    # -------------------------------------------------------------------------
+    # boundary conditions
+    # -------------------------------------------------------------------------
+    model.applyBC(aka.FixedValue(0, aka._y), "YBlocked")
+
+    # -------------------------------------------------------------------------
+    # initial conditions
+    # -------------------------------------------------------------------------
+    displacement = model.getDisplacement()
+    velocity = model.getVelocity()
+    nb_nodes = mesh.getNbNodes()
+    position = mesh.getNodes()
+
+    L = 1 # pulse_width
+    A = 0.01
+    v = np.sqrt(model.getMaterial(0).getReal('E') /
+                model.getMaterial(0).getReal('rho'))
+    k = 0.1 * 2 * np.pi * 3 / L
+    t = 0.
+    velocity[:, 0] = k * v * A * np.sin(k * ((position[:, 0] - 5.) - v * t))
+    displacement[:, 0] = A * np.cos(k * ((position[:, 0] - 5.) - v * t))
+
+    # -------------------------------------------------------------------------
+    # timestep value computation
+    # -------------------------------------------------------------------------
+    time_factor = 0.8
+    stable_time_step = model.getStableTimeStep() * time_factor
+
+    print("Stable Time Step = {0}".format(stable_time_step))
+    print("Required Time Step = {0}".format(time_step))
+
+    time_step = stable_time_step * time_factor
+
+    model.setTimeStep(time_step)
+    solver_callback = SolverCallback(model)
+
+    solver = model.getNonLinearSolver()
+    solver.set("max_iterations", 100)
+    solver.set("threshold", 1e-7)
+
+    # -------------------------------------------------------------------------
+    # loop for evolution of motion dynamics
+    # -------------------------------------------------------------------------
+    print("step,step * time_step,epot,ekin,epot + ekin")
+    for step in range(0, max_steps + 1):
+
+        model.solveStep(solver_callback)
+        #model.solveStep()
+
+        if step % 10 == 0:
+            model.dump()
+
+        epot = model.getEnergy('potential')
+        ekin = model.getEnergy('kinetic')
+
+        # output energy calculation to screen
+        print("{0},{1},{2},{3},{4}".format(step, step * time_step,
+                                           epot, ekin,
+                                           (epot + ekin)))
+
+    return
+
+
+# -----------------------------------------------------------------------------
+if __name__ == "__main__":
+    main()