diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 9dae34e5e18ae4d9fb7106aea800412b0ca7bc5f..bfc65f8a34e21538c41ff111b8325936f142f17e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -43,9 +43,8 @@ build-gcc-openmpi:
       - *export_GCC_compilers
       - *build
     variables:
-      COMPILER: "gcc"
+      COMPILER: "gcc/11"
       MPI: "openmpi"
-      MPICXX: "mpicxx"
     tags:
       - docker
     artifacts:
@@ -63,9 +62,8 @@ build-gcc-impi:
         export MPI_HOME=${I_MPI_ROOT}
       - *build
     variables:
-      COMPILER: "gcc"
-      MPI: "impi"
-      MPICXX: "mpigxx"
+      COMPILER: "gcc/11"
+      MPI: "impi/2021.5"
     tags:
       - docker
     artifacts:
@@ -83,9 +81,8 @@ build-intel:
         export MPI_HOME=${I_MPI_ROOT}
       - *build
     variables:
-      COMPILER: "intel"
-      MPI: "impi"
-      MPICXX: "mpiicpc"
+      COMPILER: "intel/21.5.0"
+      MPI: "impi/2021.5"
     tags:
       - docker
     artifacts:
@@ -105,9 +102,8 @@ test-gcc-impi:
     tags:
       - docker
     variables:
-      COMPILER: "gcc"
-      MPI: "impi"
-      MPICXX: "mpigxx"
+      COMPILER: "gcc/11"
+      MPI: "impi/2021.5"
     needs:
         - job: build-gcc-impi
           artifacts: true
@@ -124,7 +120,6 @@ test-gcc-impi:
 #    variables:
 #      COMPILER: "gcc"
 #      MPI: "openmpi"
-#      MPICXX: "mpicxx"
 #    needs:
 #        - job: build-gcc-openmpi
 #          artifacts: true
@@ -140,9 +135,8 @@ test-intel:
     tags:
       - docker
     variables:
-      COMPILER: "intel"
-      MPI: "impi"
-      MPICXX: "mpiicpc"
+      COMPILER: "intel/21.5.0"
+      MPI: "impi/2021.5"
     needs:
         - job: build-intel
           artifacts: true
@@ -153,7 +147,7 @@ build-doc:
       - *load_modules
       - mkdir build-doc
       - cd build-doc
-      - module load git gcc graphviz doxygen
+      - module load git gcc/11 graphviz doxygen
       - *export_GCC_compilers
       - pip install breathe
       - yum -y install gd
@@ -163,9 +157,8 @@ build-doc:
     tags:
       - docker
     variables:
-      COMPILER: "gcc"
-      MPI: "impi"
-      MPICXX: "mpigxx"
+      COMPILER: "gcc/11"
+      MPI: "impi/2021.5"
     artifacts:
         paths:
             - build-doc/
diff --git a/TurTLE/DNS.py b/TurTLE/DNS.py
index 4b26dd81590097d2f9b40a4fb677ecfbcbcaf745..d69af1ead1f0006a71c4b520a8ffa5a2df89a1f7 100644
--- a/TurTLE/DNS.py
+++ b/TurTLE/DNS.py
@@ -149,6 +149,10 @@ class DNS(_code):
         self.parameter_description['famplitude'] = 'Forcing parameter: amplitude of Kolmogorov forcing, in code units.'
         self.parameters['friction_coefficient'] = float(0.5)
         self.parameter_description['friction_coefficient'] = 'Forcing parameter: drag coefficient, in code units.'
+        self.parameters['variation_strength'] = float(0.25)
+        self.parameter_description['variation_strength'] = 'Forcing parameter: amplitude of time variation of injection rate, in code units.'
+        self.parameters['variation_time_scale'] = float(1.)
+        self.parameter_description['variation_time_scale'] = 'Forcing parameter: time scale of variation of injection rate, in code units.'
         self.parameters['energy'] = float(0.5)
         self.parameter_description['energy'] = 'Forcing parameter: if fluid is forced by enforcing a constant energy, this is the value (in code units).'
         self.parameters['injection_rate'] = float(0.4)
diff --git a/TurTLE/test/particle_set/NSVE_tracers_test.cpp b/TurTLE/test/particle_set/NSVE_tracers_test.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0f542e6221fbd518477ddc6b3d99a37bedde6fe0
--- /dev/null
+++ b/TurTLE/test/particle_set/NSVE_tracers_test.cpp
@@ -0,0 +1,292 @@
+/**********************************************************************
+*                                                                     *
+*  Copyright 2022 Max Planck Computing and Data Facility              *
+*                                                                     *
+*  This file is part of TurTLE.                                       *
+*                                                                     *
+*  TurTLE is free software: you can redistribute it and/or modify     *
+*  it under the terms of the GNU General Public License as published  *
+*  by the Free Software Foundation, either version 3 of the License,  *
+*  or (at your option) any later version.                             *
+*                                                                     *
+*  TurTLE 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 General Public License for more details.                       *
+*                                                                     *
+*  You should have received a copy of the GNU General Public License  *
+*  along with TurTLE.  If not, see <http://www.gnu.org/licenses/>     *
+*                                                                     *
+* Contact: Cristian.Lalescu@mpcdf.mpg.de                              *
+*                                                                     *
+**********************************************************************/
+
+
+
+//#include "NSVE_tracers_test.hpp"
+
+#include <string>
+#include <cmath>
+
+using std::make_pair;
+
+template <typename rnumber>
+template <int neighbours, int smoothness>
+int NSVE_tracers_test<rnumber>::create_tracers()
+{
+    TIMEZONE("NSVE_tracers_test::create_solver");
+    assert(this->pset[make_pair(0, 0)] != nullptr);
+    this->pset[make_pair(neighbours, smoothness)] = new particle_set<3, neighbours, smoothness>(
+        this->fs->cvelocity->rlayout,
+        this->dkx,
+        this->dky,
+        this->dkz);
+    *(this->pset[make_pair(neighbours, smoothness)]) = this->pset[make_pair(0, 0)];
+    this->psolver[make_pair(neighbours, smoothness)] = new particle_solver(
+        *(this->pset[make_pair(neighbours, smoothness)]),
+        0);
+    return EXIT_SUCCESS;
+}
+
+template <typename rnumber>
+int NSVE_tracers_test<rnumber>::initialize(void)
+{
+    TIMEZONE("NSVE_tracers_test::intialize");
+    this->NSVE<rnumber>::initialize();
+
+    // allocate field for acceleration
+    this->acceleration = new field<rnumber, FFTW, THREE>(
+            this->fs->cvelocity->rlayout->sizes[2],
+            this->fs->cvelocity->rlayout->sizes[1],
+            this->fs->cvelocity->rlayout->sizes[0],
+            this->fs->cvelocity->rlayout->comm,
+            this->fs->cvelocity->fftw_plan_rigor);
+
+    // allocate previous velocity
+    this->previous_velocity = new field<rnumber, FFTW, THREE>(
+            this->fs->cvelocity->rlayout->sizes[2],
+            this->fs->cvelocity->rlayout->sizes[1],
+            this->fs->cvelocity->rlayout->sizes[0],
+            this->fs->cvelocity->rlayout->comm,
+            this->fs->cvelocity->fftw_plan_rigor);
+    this->fs->compute_velocity(this->fs->cvorticity);
+    *this->previous_velocity = *this->fs->cvelocity;
+    this->previous_velocity->ift();
+
+    this->fti = new field_tinterpolator<rnumber, FFTW, THREE, LINEAR>();
+    this->trhs = new tracer_with_collision_counter_rhs<rnumber, FFTW, LINEAR>();
+
+    // set two fields for the temporal interpolation
+    this->fti->set_field(this->previous_velocity, 0);
+    this->fti->set_field(this->fs->cvelocity, 1);
+    this->trhs->setVelocity(this->fti);
+
+    // create linear interpolation particle set
+    this->pset[make_pair(0, 0)] = new particle_set<3, 0, 0>(
+            this->fs->cvelocity->rlayout,
+            this->dkx,
+            this->dky,
+            this->dkz);
+
+    // set up initial condition reader
+    particles_input_hdf5<long long int, double, 3, 3> particle_reader(
+            this->comm,
+            this->fs->get_current_fname(),
+            std::string("/tracers0/state/") + std::to_string(this->fs->iteration), // dataset name for initial input
+            std::string("/tracers0/rhs/")  + std::to_string(this->fs->iteration),  // dataset name for initial input
+            this->pset[make_pair(0, 0)]->getSpatialLowLimitZ(),
+            this->pset[make_pair(0, 0)]->getSpatialUpLimitZ());
+
+    // read initial condition with linear interpolation particle set
+    this->pset[make_pair(0, 0)]->init(particle_reader);
+
+    // create solver for linear interpolation particle set
+    this->psolver[make_pair(0, 0)] = new particle_solver(
+            *(this->pset[make_pair(0, 0)]),
+            0);
+
+    // create Lagrange interpolation particles
+    this->template create_tracers<1, 0>();
+    this->template create_tracers<2, 0>();
+    this->template create_tracers<3, 0>();
+    this->template create_tracers<1, 1>();
+    this->template create_tracers<1, 2>();
+    this->template create_tracers<1, 3>();
+    this->template create_tracers<2, 3>();
+    this->template create_tracers<1, 4>();
+    this->template create_tracers<2, 4>();
+    this->template create_tracers<3, 4>();
+
+    this->particles_output_writer_mpi = new particles_output_hdf5<
+        long long int, double, 3>(
+                MPI_COMM_WORLD,
+                "tracers0",
+                nparticles,
+                0);
+    this->particles_sample_writer_mpi = new particles_output_sampling_hdf5<
+        long long int, double, double, 3>(
+                MPI_COMM_WORLD,
+                this->pset[make_pair(0, 0)]->getTotalNumberOfParticles(),
+                (this->simname + "_particles.h5"),
+                "tracers_n0_m0",
+                "position/0");
+    return EXIT_SUCCESS;
+}
+
+template <typename rnumber>
+int NSVE_tracers_test<rnumber>::step(void)
+{
+    TIMEZONE("NSVE_tracers_test::step");
+    // compute next step Navier-Stokes
+    this->NSVE<rnumber>::step();
+    // update velocity 1
+    this->fs->compute_velocity(this->fs->cvorticity);
+    this->fs->cvelocity->ift();
+    // compute particle step using velocity 0 and velocity 1
+    for (auto it=this->psolver.begin(); it != this->psolver.end(); ++it)
+    {
+        it->second->cRK4(this->dt, *(this->trhs));
+        it->second->setIteration(this->fs->iteration);
+    }
+    // update velocity 0
+    *this->previous_velocity = *this->fs->cvelocity;
+    return EXIT_SUCCESS;
+}
+
+template <typename rnumber>
+int NSVE_tracers_test<rnumber>::write_checkpoint(void)
+{
+    TIMEZONE("NSVE_tracers_test::write_checkpoint");
+    this->NSVE<rnumber>::write_checkpoint();
+
+    for (auto it=this->psolver.begin(); it != this->psolver.end(); ++it)
+    {
+        this->particles_output_writer_mpi->open_file(this->fs->get_current_fname());
+        this->particles_output_writer_mpi->update_particle_species_name(
+                "tracers_n" +
+                std::to_string(it->first.first) +
+                "_m" +
+                std::to_string(it->first.second));
+        it->second->setIteration(this->fs->iteration);
+        it->second->template writeCheckpoint<3>(this->particles_output_writer_mpi);
+    }
+    this->particles_output_writer_mpi->close_file();
+    return EXIT_SUCCESS;
+}
+
+template <typename rnumber>
+int NSVE_tracers_test<rnumber>::finalize(void)
+{
+    TIMEZONE("NSVE_tracers_test::finalize");
+    delete this->particles_sample_writer_mpi;
+    delete this->particles_output_writer_mpi;
+    for (auto it=this->psolver.begin(); it != this->psolver.end(); ++it)
+    {
+        delete it->second;
+    }
+    for (auto it=this->pset.begin(); it != this->pset.end(); ++it)
+    {
+        delete it->second;
+    }
+    delete this->trhs;
+    delete this->fti;
+    delete this->previous_velocity;
+    delete this->acceleration;
+    this->NSVE<rnumber>::finalize();
+    return EXIT_SUCCESS;
+}
+
+/** \brief Compute fluid stats and sample fields at particle locations.
+ */
+
+template <typename rnumber>
+int NSVE_tracers_test<rnumber>::do_stats()
+{
+    TIMEZONE("NSVE_tracers_test::do_stats");
+    /// fluid stats go here
+    this->NSVE<rnumber>::do_stats();
+
+
+    /// either one of two conditions suffices to compute statistics:
+    /// 1) current iteration is a multiple of niter_part
+    /// 2) we are within niter_part_fine_duration/2 of a multiple of niter_part_fine_period
+    if (!(this->iteration % this->niter_part == 0 ||
+          ((this->iteration + this->niter_part_fine_duration/2) % this->niter_part_fine_period <=
+           this->niter_part_fine_duration)))
+        return EXIT_SUCCESS;
+
+    // ensure velocity field is computed, and is in real space
+    if (!(this->iteration % this->niter_stat == 0))
+    {
+        // we need to compute velocity field manually, because it didn't happen in NSVE::do_stats()
+        this->fs->compute_velocity(this->fs->cvorticity);
+        *this->tmp_vec_field = *(this->fs->cvelocity);
+        this->tmp_vec_field->ift();
+    }
+
+    // compute acceleration
+    this->fs->compute_Lagrangian_acceleration(this->acceleration);
+    this->acceleration->ift();
+    DEBUG_MSG("hello, finished with acceleration\n");
+
+    for (auto it=this->pset.begin(); it != this->pset.end(); ++it)
+    {
+        const int neighbours = it->first.first;
+        const int smoothness = it->first.second;
+        abstract_particle_set *ps = it->second;
+        const std::string species_name = (
+                "tracers_n" +
+                std::to_string(neighbours) +
+                "_m" +
+                std::to_string(smoothness));
+        DEBUG_MSG("hello, doing %s\n", species_name.c_str());
+
+        // sample position
+        ps->writeStateTriplet(
+                0,
+                this->particles_sample_writer_mpi,
+                species_name,
+                "position",
+                this->iteration);
+        MPI_Barrier(this->comm);
+
+        // sample velocity
+        ps->writeSample(
+                this->tmp_vec_field,
+                this->particles_sample_writer_mpi,
+                species_name,
+                "velocity",
+                this->iteration);
+        MPI_Barrier(this->comm);
+
+        // sample velocity
+        ps->writeSample(
+                this->acceleration,
+                this->particles_sample_writer_mpi,
+                species_name,
+                "acceleration",
+                this->iteration);
+        MPI_Barrier(this->comm);
+    }
+
+    return EXIT_SUCCESS;
+}
+
+template <typename rnumber>
+int NSVE_tracers_test<rnumber>::read_parameters(void)
+{
+    TIMEZONE("NSVE_tracers_test::read_parameters");
+    this->NSVE<rnumber>::read_parameters();
+    hid_t parameter_file = H5Fopen((this->simname + ".h5").c_str(), H5F_ACC_RDONLY, H5P_DEFAULT);
+    this->niter_part = hdf5_tools::read_value<int>(parameter_file, "parameters/niter_part");
+    this->niter_part_fine_period = hdf5_tools::read_value<int>(parameter_file, "parameters/niter_part_fine_period");
+    this->niter_part_fine_duration = hdf5_tools::read_value<int>(parameter_file, "parameters/niter_part_fine_duration");
+    this->nparticles = hdf5_tools::read_value<int>(parameter_file, "parameters/nparticles");
+    H5Fclose(parameter_file);
+    MPI_Barrier(this->comm);
+    return EXIT_SUCCESS;
+}
+
+template class NSVE_tracers_test<float>;
+template class NSVE_tracers_test<double>;
+
diff --git a/TurTLE/test/particle_set/NSVE_tracers_test.hpp b/TurTLE/test/particle_set/NSVE_tracers_test.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..01ed7d983ac7b26149fa5e5412678e107c6a5f85
--- /dev/null
+++ b/TurTLE/test/particle_set/NSVE_tracers_test.hpp
@@ -0,0 +1,99 @@
+/**********************************************************************
+*                                                                     *
+*  Copyright 2022 Max Planck Computing and Data Facility              *
+*                                                                     *
+*  This file is part of TurTLE.                                       *
+*                                                                     *
+*  TurTLE is free software: you can redistribute it and/or modify     *
+*  it under the terms of the GNU General Public License as published  *
+*  by the Free Software Foundation, either version 3 of the License,  *
+*  or (at your option) any later version.                             *
+*                                                                     *
+*  TurTLE 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 General Public License for more details.                       *
+*                                                                     *
+*  You should have received a copy of the GNU General Public License  *
+*  along with TurTLE.  If not, see <http://www.gnu.org/licenses/>     *
+*                                                                     *
+* Contact: Cristian.Lalescu@mpcdf.mpg.de                              *
+*                                                                     *
+**********************************************************************/
+
+
+
+#ifndef NSVE_TRACERS_TEST_HPP
+#define NSVE_TRACERS_TEST_HPP
+
+
+
+#include "base.hpp"
+#include "vorticity_equation.hpp"
+#include "full_code/NSVE.hpp"
+#include "particles/particles_system_builder.hpp"
+#include "particles/particles_output_hdf5.hpp"
+#include "particles/particles_sampling.hpp"
+#include "particles/interpolation/field_tinterpolator.hpp"
+#include "particles/interpolation/particle_set.hpp"
+#include "particles/particle_solver.hpp"
+#include "particles/rhs/tracer_with_collision_counter_rhs.hpp"
+
+#include <cstdlib>
+#include <map>
+
+/** \brief Test of tracer integration.
+ *
+ *  Child of Navier Stokes vorticity equation solver, this class calls all the
+ *  methods from `NSVE`, and in addition integrates simple Lagrangian tracers
+ *  in the resulting velocity field.
+ *
+ *  The code is meant to compare results of using different interpolation methods.
+ */
+
+template <typename rnumber>
+class NSVE_tracers_test: public NSVE<rnumber>
+{
+    public:
+
+        /* parameters that are read in read_parameters */
+        int niter_part;
+        int niter_part_fine_period;
+        int niter_part_fine_duration;
+        long long int nparticles;
+
+        /* other stuff */
+        field_tinterpolator<rnumber, FFTW, THREE, LINEAR> *fti;
+        tracer_rhs<rnumber, FFTW, LINEAR> *trhs;
+
+        std::map<std::pair<int, int>, abstract_particle_set *> pset;
+        std::map<std::pair<int, int>, particle_solver *> psolver;
+
+        field<rnumber, FFTW, THREE> *previous_velocity;
+        field<rnumber, FFTW, THREE> *acceleration;
+
+        particles_output_hdf5<long long int, double, 3> *particles_output_writer_mpi;
+        particles_output_sampling_hdf5<long long int, double, double, 3> *particles_sample_writer_mpi;
+
+        NSVE_tracers_test(
+                const MPI_Comm COMMUNICATOR,
+                const std::string &simulation_name):
+            NSVE<rnumber>(
+                    COMMUNICATOR,
+                    simulation_name){}
+        ~NSVE_tracers_test(){}
+
+        int initialize(void);
+        int step(void);
+        int finalize(void);
+
+        int read_parameters(void);
+        int write_checkpoint(void);
+        int do_stats(void);
+
+        template <int neighbours, int smoothness>
+            int create_tracers();
+};
+
+#endif//NSVE_TRACERS_TEST_HPP
+
diff --git a/TurTLE/test/test_tracer_integration.py b/TurTLE/test/test_tracer_integration.py
new file mode 100644
index 0000000000000000000000000000000000000000..d9087e0d4a52aca9217862f8fd169f21a174b868
--- /dev/null
+++ b/TurTLE/test/test_tracer_integration.py
@@ -0,0 +1,182 @@
+import os
+import sys
+import argparse
+import getpass
+
+import numpy as np
+import h5py
+import matplotlib.pyplot as plt
+
+import TurTLE
+import TurTLE._base
+import TurTLE.DNS
+from TurTLE._code import _code
+
+cpp_location = os.path.join(
+        TurTLE.data_dir, 'particle_set')
+
+
+class ADNS(TurTLE.DNS):
+    def __init__(
+            self,
+            **kwargs):
+        TurTLE.DNS.__init__(self, **kwargs)
+        self.list_of_particle_codes.append('NSVE_tracers_test')
+        self.extra_parameters['NSVE_tracers_test'] = {}
+        return None
+    def write_src(
+            self):
+        self.version_message = (
+                '/***********************************************************************\n' +
+                '* this code automatically generated by TurTLE\n' +
+                '* version {0}\n'.format(TurTLE.__version__) +
+                '***********************************************************************/\n\n\n')
+        self.include_list = [
+                '"base.hpp"',
+                '"scope_timer.hpp"',
+                '"fftw_interface.hpp"',
+                '"full_code/main_code.hpp"',
+                '"full_code/NSVEparticles.hpp"',
+                '<cmath>',
+                '<iostream>',
+                '<hdf5.h>',
+                '<string>',
+                '<cstring>',
+                '<fftw3-mpi.h>',
+                '<omp.h>',
+                '<cfenv>',
+                '<cstdlib>']
+        self.main = """
+            int main(int argc, char *argv[])
+            {{
+                bool fpe = (
+                    (getenv("BFPS_FPE_OFF") == nullptr) ||
+                    (getenv("BFPS_FPE_OFF") != std::string("TRUE")));
+                return main_code< {0} >(argc, argv, fpe);
+            }}
+            """.format(self.dns_type + '<{0}>'.format(self.C_field_dtype))
+        self.includes = '\n'.join(
+                ['#include ' + hh
+                 for hh in self.include_list])
+        self.name = 'NSVE_tracers_test'
+        self.dns_type = 'NSVE_tracers_test'
+        self.definitions += open(
+            os.path.join(
+                cpp_location, 'NSVE_tracers_test.hpp'), 'r').read()
+        self.definitions += open(
+            os.path.join(
+                cpp_location, 'NSVE_tracers_test.cpp'), 'r').read()
+        with open(self.name + '.cpp', 'w') as outfile:
+            outfile.write(self.version_message + '\n\n')
+            outfile.write(self.includes + '\n\n')
+            outfile.write(self.definitions + '\n\n')
+            outfile.write(self.main + '\n')
+        self.check_current_vorticity_exists = True
+        return None
+
+    def write_par(
+            self,
+            iter0 = 0):
+        TurTLE.DNS.write_par(self, iter0 = iter0)
+        with h5py.File(self.get_data_file_name(), 'r+') as ofile:
+            #TODO move collision stats to particle stats
+            for i in range(self.parameters['niter_out']):
+                ofile.create_dataset('statistics/collisions/'+str(i),
+                                     shape=(1,),
+                                     dtype = np.int64)
+        return None
+
+    def launch(
+            self,
+            args = [],
+            **kwargs):
+        opt = self.prepare_launch(args = args)
+        if not os.path.exists(self.get_data_file_name()):
+            self.write_par()
+            self.generate_initial_condition(opt = opt)
+            with h5py.File(self.get_particle_file_name(), 'w') as particle_file:
+                for (n, m) in [
+                        (0, 0),
+                        (1, 0), (2, 0), (3, 0),
+                        (1, 1),
+                        (1, 2),
+                        (1, 3), (2, 3),
+                        (1, 4), (2, 4), (3, 4)]:
+                    particle_file.create_group('tracers_n{0}_m{1}/position'.format(n, m))
+                    particle_file.create_group('tracers_n{0}_m{1}/velocity'.format(n, m))
+                    particle_file.create_group('tracers_n{0}_m{1}/acceleration'.format(n, m))
+            self.generate_tracer_state(integration_steps = 0,
+                                       rseed = opt.particle_rand_seed,
+                                       ncomponents = 3)
+        self.run(
+                nb_processes = opt.nb_processes,
+                nb_threads_per_process = opt.nb_threads_per_process,
+                njobs = opt.njobs,
+                hours = opt.minutes // 60,
+                minutes = opt.minutes % 60,
+                no_submit = opt.no_submit,
+                no_debug = opt.no_debug)
+        return None
+
+def main():
+    bla = ADNS()
+    if not os.path.exists('test.h5'):
+        bla.launch([
+            'NSVE_tracers_test',
+            '--niter_todo', '20',
+            '--niter_stat', '10',
+            '--niter_out', '20',
+            '--nparticles', '1000',
+            '--kMeta', '3.0',
+            ] + sys.argv[1:])
+    else:
+        bla.read_parameters()
+    df = bla.get_particle_file()
+    f = plt.figure(figsize=(16,4))
+    ax1 = f.add_subplot(141)
+    ax2 = f.add_subplot(142)
+    ax3 = f.add_subplot(143)
+    ax4 = f.add_subplot(144)
+    for species in df.keys():
+        iterations = range(bla.parameters['niter_out']//2, bla.parameters['niter_out'])
+        pos = np.array(
+                [df[species + '/position/{0}'.format(ii)][()]
+                 for ii in iterations])
+        vel = np.array(
+                [df[species + '/velocity/{0}'.format(ii)][()]
+                 for ii in iterations])
+        acc = np.array(
+                [df[species + '/acceleration/{0}'.format(ii)][()]
+                 for ii in iterations])
+        d1x = (pos[2:] - pos[:-2]) / (2*bla.parameters['dt'])
+        d2x = (pos[2:] - 2*pos[1:-1] + pos[:-2]) / (bla.parameters['dt']**2)
+        d1v = (vel[2:] - vel[:-2]) / (2*bla.parameters['dt'])
+        vv = vel[1:-1]
+        aa = acc[1:-1]
+        errv = np.abs(np.sum((vv - d1x)**2, axis = 2) / np.sum(vv**2, axis = 2))**0.5
+        erra1 = np.abs(np.sum((aa - d1v)**2, axis = 2) / np.sum(aa**2, axis = 2))**0.5
+        erra2 = np.abs(np.sum((aa - d2x)**2, axis = 2) / np.sum(aa**2, axis = 2))**0.5
+        erra3 = np.abs(np.sum((d1v - d2x)**2, axis = 2) / np.sum(aa**2, axis = 2))**0.5
+        ax1.hist(errv.flatten(),  label = species, bins = 101, histtype = 'step', density = True)
+        ax2.hist(erra1.flatten(), label = species, bins = 101, histtype = 'step', density = True)
+        ax3.hist(erra2.flatten(), label = species, bins = 101, histtype = 'step', density = True)
+        ax4.hist(erra3.flatten(), label = species, bins = 101, histtype = 'step', density = True)
+    for aa in [ax1, ax2, ax3, ax4]:
+        aa.set_xscale('log')
+        aa.set_yscale('log')
+        aa.legend(loc = 'best', fontsize = 6)
+        aa.tick_params(axis = 'both', which = 'both', labelsize = 6, pad = 0)
+    ax2.set_xlim(1e-4, 2)
+    ax3.set_xlim(1e-4, 2)
+    ax4.set_xlim(1e-4, 2)
+    ax1.set_title('vel vs d1x', fontsize = 6)
+    ax2.set_title('acc vs d1v', fontsize = 6)
+    ax3.set_title('acc vs d2x', fontsize = 6)
+    ax4.set_title('d1v vs d2x', fontsize = 6)
+    f.tight_layout()
+    f.savefig('tmp.pdf')
+    plt.close(f)
+    return None
+
+if __name__ == '__main__':
+    main()
diff --git a/cpp/full_code/NSVE.cpp b/cpp/full_code/NSVE.cpp
index 09ca7f286be9d1d17ba3bce5125187bf88fb8024..5c0d360c0cdced78c17b72b11b30d813239d75c8 100644
--- a/cpp/full_code/NSVE.cpp
+++ b/cpp/full_code/NSVE.cpp
@@ -77,10 +77,13 @@ int NSVE<rnumber>::initialize(void)
     this->fs->fmode = fmode;
     this->fs->famplitude = famplitude;
     this->fs->friction_coefficient = friction_coefficient;
+    this->fs->variation_strength = variation_strength;
+    this->fs->variation_time_scale = variation_time_scale;
     this->fs->energy = energy;
     this->fs->injection_rate = injection_rate;
     this->fs->fk0 = fk0;
     this->fs->fk1 = fk1;
+    this->fs->dt = dt;
     strncpy(this->fs->forcing_type, forcing_type, 128);
     this->fs->iteration = this->iteration;
     this->fs->checkpoint = this->checkpoint;
@@ -181,6 +184,8 @@ int NSVE<rnumber>::read_parameters(void)
     this->fmode = hdf5_tools::read_value<int>(parameter_file, "parameters/fmode");
     this->famplitude = hdf5_tools::read_value<double>(parameter_file, "parameters/famplitude");
     this->friction_coefficient = hdf5_tools::read_value<double>(parameter_file, "parameters/friction_coefficient");
+    this->variation_strength = hdf5_tools::read_value<double>(parameter_file, "parameters/variation_strength");
+    this->variation_time_scale = hdf5_tools::read_value<double>(parameter_file, "parameters/variation_time_scale");
     this->injection_rate = hdf5_tools::read_value<double>(parameter_file, "parameters/injection_rate");
     this->fk0 = hdf5_tools::read_value<double>(parameter_file, "parameters/fk0");
     this->fk1 = hdf5_tools::read_value<double>(parameter_file, "parameters/fk1");
diff --git a/cpp/full_code/NSVE.hpp b/cpp/full_code/NSVE.hpp
index 2f29f91fef5c990f1676dc547e30458c0ff97a0b..15a388dc4f8e7fdd1db8e11b3cff1806094e18d3 100644
--- a/cpp/full_code/NSVE.hpp
+++ b/cpp/full_code/NSVE.hpp
@@ -51,6 +51,8 @@ class NSVE: public direct_numerical_simulation
         double dt;
         double famplitude;
         double friction_coefficient;
+        double variation_strength;
+        double variation_time_scale;
         double fk0;
         double fk1;
         double energy;
diff --git a/cpp/full_code/joint_acc_vel_stats.cpp b/cpp/full_code/joint_acc_vel_stats.cpp
index 8fe55d2025e1cf7c0c1b91144db2aab56ba6b4bc..1daf7ed0f3d6b1cc26be4854ca0bb33b93bd0d9a 100644
--- a/cpp/full_code/joint_acc_vel_stats.cpp
+++ b/cpp/full_code/joint_acc_vel_stats.cpp
@@ -51,14 +51,10 @@ int joint_acc_vel_stats<rnumber>::initialize(void)
             (this->simname + std::string(".h5")).c_str(),
             H5F_ACC_RDONLY,
             H5P_DEFAULT);
-    hid_t dset = H5Dopen(parameter_file, "/parameters/niter_out", H5P_DEFAULT);
-    H5Dread(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, &this->niter_out);
-    H5Dclose(dset);
+    this->niter_out = hdf5_tools::read_value<int>(parameter_file, "/parameters/niter_out");
     if (H5Lexists(parameter_file, "/parameters/checkpoints_per_file", H5P_DEFAULT))
     {
-        dset = H5Dopen(parameter_file, "/parameters/checkpoints_per_file", H5P_DEFAULT);
-        H5Dread(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, &this->checkpoints_per_file);
-        H5Dclose(dset);
+        this->checkpoints_per_file = hdf5_tools::read_value<int>(parameter_file, "/parameters/checkpoints_per_file");
     }
     else
         this->checkpoints_per_file = 1;
@@ -72,12 +68,8 @@ int joint_acc_vel_stats<rnumber>::initialize(void)
     this->iteration_list = hdf5_tools::read_vector<int>(
             parameter_file,
             "/joint_acc_vel_stats/parameters/iteration_list");
-    dset = H5Dopen(parameter_file, "joint_acc_vel_stats/parameters/max_acceleration_estimate", H5P_DEFAULT);
-    H5Dread(dset, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, &this->max_acceleration_estimate);
-    H5Dclose(dset);
-    dset = H5Dopen(parameter_file, "joint_acc_vel_stats/parameters/max_velocity_estimate", H5P_DEFAULT);
-    H5Dread(dset, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, &this->max_velocity_estimate);
-    H5Dclose(dset);
+    this->max_acceleration_estimate = hdf5_tools::read_value<int>(parameter_file, "joint_acc_vel_stats/parameters/max_acceleration_estimate");
+    this->max_velocity_estimate = hdf5_tools::read_value<int>(parameter_file, "joint_acc_vel_stats/parameters/max_velocity_estimate");
     H5Fclose(parameter_file);
     // the following ensures the file is free for rank 0 to open in read/write mode
     MPI_Barrier(this->comm);
@@ -196,7 +188,7 @@ int joint_acc_vel_stats<rnumber>::work_on_current_iteration(void)
 template <typename rnumber>
 int joint_acc_vel_stats<rnumber>::finalize(void)
 {
-    DEBUG_MSG("entered joint_acc_vel_stats::finalize\n");
+    TIMEZONE("joint_acc_vel_stats::finalize");
     delete this->ve;
     delete this->kk;
     if (this->myrank == 0)
diff --git a/cpp/full_code/postprocess.cpp b/cpp/full_code/postprocess.cpp
index 0f80890c144ba6a64ffcfbb72e9b24ede476aea2..2fe20c34ef524419e6e591b6d381421f12944d3b 100644
--- a/cpp/full_code/postprocess.cpp
+++ b/cpp/full_code/postprocess.cpp
@@ -69,6 +69,8 @@ int postprocess::read_parameters()
     this->friction_coefficient = hdf5_tools::read_value<double>(parameter_file, "parameters/friction_coefficient");
     this->fk0 = hdf5_tools::read_value<double>(parameter_file, "parameters/fk0");
     this->fk1 = hdf5_tools::read_value<double>(parameter_file, "parameters/fk1");
+    this->variation_strength = hdf5_tools::read_value<double>(parameter_file, "parameters/variation_strength");
+    this->variation_time_scale = hdf5_tools::read_value<double>(parameter_file, "parameters/variation_time_scale");
     this->energy = hdf5_tools::read_value<double>(parameter_file, "parameters/energy");
     this->injection_rate = hdf5_tools::read_value<double>(parameter_file, "parameters/injection_rate");
     std::string tmp = hdf5_tools::read_string(parameter_file, "parameters/forcing_type");
@@ -83,6 +85,7 @@ template <typename rnumber,
 int postprocess::copy_parameters_into(
         vorticity_equation<rnumber, be> *dst)
 {
+    TIMEZONE("postprocess::copy_parameters_into");
     dst->nu = this->nu;
     dst->fmode = this->fmode;
     dst->famplitude = this->famplitude;
@@ -91,7 +94,9 @@ int postprocess::copy_parameters_into(
     dst->injection_rate = this->injection_rate;
     dst->energy = this->energy;
     dst->friction_coefficient = this->friction_coefficient;
-    strncpy(this->forcing_type, dst->forcing_type, 128);
+    dst->variation_strength = this->variation_strength;
+    dst->variation_time_scale = this->variation_time_scale;
+    strncpy(dst->forcing_type, this->forcing_type, 128);
     return EXIT_SUCCESS;
 }
 
diff --git a/cpp/full_code/postprocess.hpp b/cpp/full_code/postprocess.hpp
index da22c91e9f7ca187cf7b3038994c0852e255924b..4a8fab396149ccddc008d15dedbc15ce968b8a70 100644
--- a/cpp/full_code/postprocess.hpp
+++ b/cpp/full_code/postprocess.hpp
@@ -53,6 +53,8 @@ class postprocess: public code_base
         double fk1;
         double energy;
         double injection_rate;
+        double variation_strength;
+        double variation_time_scale;
         int fmode;
         char forcing_type[512];
         double nu;
diff --git a/cpp/full_code/test_particle_integration.cpp b/cpp/full_code/test_particle_integration.cpp
index a1d3cbaf957ffb6dab9070746ac70d3d22f57695..d914aa921cb15fd34b21e326a77ac0d85ffbc19e 100644
--- a/cpp/full_code/test_particle_integration.cpp
+++ b/cpp/full_code/test_particle_integration.cpp
@@ -174,7 +174,7 @@ int test_particle_integration<rnumber>::do_work()
             0.40, // default
             3*3./2); // 3/2 to account for divfree call below
                      // 3 because I want a larger amplitude
-    this->kk->force_divfree<rnumber>(this->velocity_back->get_cdata());
+    this->kk->template force_divfree<rnumber>(this->velocity_back->get_cdata());
 
     // take field to real space
     this->velocity_back->ift();
diff --git a/cpp/particles/interpolation/abstract_particle_set.hpp b/cpp/particles/interpolation/abstract_particle_set.hpp
index 95d94fa1798a806cc97fd92197e96757ba174564..af907584ddd55dedc7d8a6d5a6b59f6f01262928 100644
--- a/cpp/particles/interpolation/abstract_particle_set.hpp
+++ b/cpp/particles/interpolation/abstract_particle_set.hpp
@@ -26,14 +26,18 @@
 #ifndef ABSTRACT_PARTICLE_SET_HPP
 #define ABSTRACT_PARTICLE_SET_HPP
 
+
+
+#include "field.hpp"
+#include "particles/p2p/p2p_distr_mpi.hpp"
+#include "particles/particles_sampling.hpp"
+#include "particles/abstract_particles_input.hpp"
+
 #include <array>
 #include <cassert>
 #include <vector>
 #include <memory>
 #include <string>
-#include "field.hpp"
-#include "particles/p2p/p2p_distr_mpi.hpp"
-#include "particles/particles_sampling.hpp"
 
 
 
@@ -77,6 +81,12 @@ class abstract_particle_set
 
         virtual std::vector<hsize_t> getParticleFileLayout() = 0;
 
+        virtual particle_rnumber getSpatialLowLimitZ() const = 0;
+        virtual particle_rnumber getSpatialUpLimitZ() const = 0;
+        virtual int init(abstract_particles_input<partsize_t, particle_rnumber>& particles_input) = 0;
+        virtual int operator=(abstract_particle_set *src) = 0;
+
+
         // get p2p computer
         virtual p2p_distr_mpi<partsize_t, particle_rnumber>* getP2PComputer() = 0;
 
diff --git a/cpp/particles/interpolation/particle_set.hpp b/cpp/particles/interpolation/particle_set.hpp
index 071614ad52369d596565a50fa2e8ac34d5c22e99..79335800ee1de0cbc77666fbf459eb3295c827b4 100644
--- a/cpp/particles/interpolation/particle_set.hpp
+++ b/cpp/particles/interpolation/particle_set.hpp
@@ -709,12 +709,12 @@ class particle_set: public abstract_particle_set
             return EXIT_SUCCESS;
         }
 
-        particle_rnumber getSpatialLowLimitZ()
+        particle_rnumber getSpatialLowLimitZ() const
         {
             return this->pInterpolator.getSpatialLowLimitZ();
         }
 
-        particle_rnumber getSpatialUpLimitZ()
+        particle_rnumber getSpatialUpLimitZ() const
         {
             return this->pInterpolator.getSpatialUpLimitZ();
         }
diff --git a/cpp/vorticity_equation.cpp b/cpp/vorticity_equation.cpp
index e6c500b90277f2d586bf92ef5aa3c645f2949c3b..c32c03319d1e35796a847a30d1b9590999d3678a 100644
--- a/cpp/vorticity_equation.cpp
+++ b/cpp/vorticity_equation.cpp
@@ -159,6 +159,7 @@ vorticity_equation<rnumber, be>::vorticity_equation(
     this->friction_coefficient = 1.0;
     this->fk0  = 2.0;
     this->fk1 = 4.0;
+    this->dt = 0.1;
 }
 
 template <class rnumber,
@@ -284,7 +285,8 @@ template <class rnumber,
           field_backend be>
 void vorticity_equation<rnumber, be>::add_forcing(
         field<rnumber, be, THREE> *dst,
-        field<rnumber, be, THREE> *vort_field)
+        field<rnumber, be, THREE> *vort_field,
+        double t)
 {
     TIMEZONE("vorticity_equation::add_forcing");
     if (strcmp(this->forcing_type, "Kolmogorov") == 0)
@@ -334,7 +336,8 @@ void vorticity_equation<rnumber, be>::add_forcing(
         return;
     }
     if ((strcmp(this->forcing_type, "fixed_energy_injection_rate") == 0) ||
-        (strcmp(this->forcing_type, "fixed_energy_injection_rate_and_drag") == 0))
+        (strcmp(this->forcing_type, "fixed_energy_injection_rate_and_drag") == 0) ||
+        (strcmp(this->forcing_type, "sinusoidal_energy_injection_rate") == 0))
     {
         // first, compute energy in shell
         shared_array<double> local_energy_in_shell(1);
@@ -373,7 +376,14 @@ void vorticity_equation<rnumber, be>::add_forcing(
         // now, modify amplitudes
         if (energy_in_shell < 10*std::numeric_limits<rnumber>::epsilon())
             energy_in_shell = 1;
-        double temp_famplitude = this->injection_rate / energy_in_shell;
+        double temp_famplitude = 0;
+        if (strcmp(this->forcing_type, "sinusoidal_energy_injection_rate") == 0){
+            temp_famplitude = (this->injection_rate + this->variation_strength
+                                *sin(t/this->variation_time_scale))
+                                / energy_in_shell;
+        }else{
+            temp_famplitude = this->injection_rate / energy_in_shell;
+        }
         this->add_field_band(
                 dst, vort_field,
                 this->fk0, this->fk1,
@@ -472,7 +482,7 @@ void vorticity_equation<rnumber, be>::impose_forcing(
 template <class rnumber,
           field_backend be>
 void vorticity_equation<rnumber, be>::omega_nonlin(
-        int src)
+        int src, double t)
 {
     //DEBUG_MSG("vorticity_equation::omega_nonlin(%d)\n", src);
     assert(src >= 0 && src < 3);
@@ -529,7 +539,7 @@ void vorticity_equation<rnumber, be>::omega_nonlin(
             this->u->cval(cindex, cc, i) = tmp[cc][i];
     }
     );
-    this->add_forcing(this->u, this->v[src]);
+    this->add_forcing(this->u, this->v[src], t);
     this->kk->template force_divfree<rnumber>(this->u->get_cdata());
     this->u->symmetrize();
 }
@@ -538,10 +548,15 @@ template <class rnumber,
           field_backend be>
 void vorticity_equation<rnumber, be>::step(double dt)
 {
-    //DEBUG_MSG("vorticity_equation::step\n");
+    // This is an implementation of a memory-saving, third-order
+    // Runge-Kutta method. See also
+    // https://en.wikipedia.org/wiki/List_of_Runge%E2%80%93Kutta_methods#Explicit_methods
+    // where it is called "Third-order Strong Stability Preserving Runge-Kutta (SSPRK3)".
+    // The intermediate values of t passed to `omega_nonlin` can be read from
+    // the corresponding Butcher tableau.
     TIMEZONE("vorticity_equation::step");
     *this->v[1] = 0.0;
-    this->omega_nonlin(0);
+    this->omega_nonlin(0, this->iteration*dt);
     this->kk->CLOOP_K2(
                 [&](const ptrdiff_t cindex,
                     const ptrdiff_t xindex,
@@ -560,7 +575,7 @@ void vorticity_equation<rnumber, be>::step(double dt)
     );
     this->impose_forcing(this->v[1], this->v[0]);
 
-    this->omega_nonlin(1);
+    this->omega_nonlin(1, (this->iteration + 1)*dt);
     this->kk->CLOOP_K2(
                 [&](const ptrdiff_t cindex,
                     const ptrdiff_t xindex,
@@ -581,7 +596,7 @@ void vorticity_equation<rnumber, be>::step(double dt)
     );
     this->impose_forcing(this->v[2], this->v[0]);
 
-    this->omega_nonlin(2);
+    this->omega_nonlin(2, (this->iteration + 1./2.)*dt);
     // store old vorticity
     *this->v[1] = *this->v[0];
     this->kk->CLOOP_K2(
@@ -719,6 +734,9 @@ void vorticity_equation<rnumber, be>::compute_Lagrangian_acceleration(
     this->compute_velocity(this->cvorticity);
     acceleration->real_space_representation = false;
     *acceleration = 0.0;
+    // add curl of forcing
+    this->add_forcing(acceleration, this->cvorticity, this->iteration*this->dt);
+    // loop over all modes, inverting curl and then adding the rest of the terms
     this->kk->CLOOP_K2(
                 [&](const ptrdiff_t cindex,
                     const ptrdiff_t xindex,
@@ -727,27 +745,36 @@ void vorticity_equation<rnumber, be>::compute_Lagrangian_acceleration(
                     const double k2){
         if (k2 <= this->kk->kM2)
         {
-            ptrdiff_t tindex = 3*cindex;
-            for (int cc=0; cc<3; cc++)
-                for (int i=0; i<2; i++)
-                    acceleration->get_cdata()[tindex+cc][i] = \
-                        - this->nu*k2*this->cvelocity->get_cdata()[tindex+cc][i];
-            if (strcmp(this->forcing_type, "linear") == 0)
+            // first invert curl
+            if (k2 > 0)
             {
-                double knorm = sqrt(k2);
-                if ((this->fk0 <= knorm) &&
-                        (this->fk1 >= knorm))
-                    for (int c=0; c<3; c++)
-                        for (int i=0; i<2; i++)
-                            acceleration->get_cdata()[tindex+c][i] += \
-                                this->famplitude*this->cvelocity->get_cdata()[tindex+c][i];
+                const rnumber rax = acceleration->cval(cindex, 0, 0);
+                const rnumber cax = acceleration->cval(cindex, 0, 1);
+                const rnumber ray = acceleration->cval(cindex, 1, 0);
+                const rnumber cay = acceleration->cval(cindex, 1, 1);
+                const rnumber raz = acceleration->cval(cindex, 2, 0);
+                const rnumber caz = acceleration->cval(cindex, 2, 1);
+                acceleration->cval(cindex,0,0) = -(kk->ky[yindex]*caz - kk->kz[zindex]*cay) / k2;
+                acceleration->cval(cindex,0,1) =  (kk->ky[yindex]*raz - kk->kz[zindex]*ray) / k2;
+                acceleration->cval(cindex,1,0) = -(kk->kz[zindex]*cax - kk->kx[xindex]*caz) / k2;
+                acceleration->cval(cindex,1,1) =  (kk->kz[zindex]*rax - kk->kx[xindex]*raz) / k2;
+                acceleration->cval(cindex,2,0) = -(kk->kx[xindex]*cay - kk->ky[yindex]*cax) / k2;
+                acceleration->cval(cindex,2,1) =  (kk->kx[xindex]*ray - kk->ky[yindex]*rax) / k2;
             }
-            acceleration->get_cdata()[tindex+0][0] += this->kk->kx[xindex]*pressure->get_cdata()[cindex][1];
-            acceleration->get_cdata()[tindex+1][0] += this->kk->ky[yindex]*pressure->get_cdata()[cindex][1];
-            acceleration->get_cdata()[tindex+2][0] += this->kk->kz[zindex]*pressure->get_cdata()[cindex][1];
-            acceleration->get_cdata()[tindex+0][1] -= this->kk->kx[xindex]*pressure->get_cdata()[cindex][0];
-            acceleration->get_cdata()[tindex+1][1] -= this->kk->ky[yindex]*pressure->get_cdata()[cindex][0];
-            acceleration->get_cdata()[tindex+2][1] -= this->kk->kz[zindex]*pressure->get_cdata()[cindex][0];
+
+            // add dissipative term
+            for (int cc=0; cc<3; cc++)
+                for (int i=0; i<2; i++)
+                    acceleration->cval(cindex,cc, i) -= \
+                        this->nu*k2*this->cvelocity->cval(cindex,cc,i);
+
+            // add nonlinear term
+            acceleration->cval(cindex,0,0) += this->kk->kx[xindex]*pressure->cval(cindex,1);
+            acceleration->cval(cindex,0,1) -= this->kk->kx[xindex]*pressure->cval(cindex,0);
+            acceleration->cval(cindex,1,0) += this->kk->ky[yindex]*pressure->cval(cindex,1);
+            acceleration->cval(cindex,1,1) -= this->kk->ky[yindex]*pressure->cval(cindex,0);
+            acceleration->cval(cindex,2,0) += this->kk->kz[zindex]*pressure->cval(cindex,1);
+            acceleration->cval(cindex,2,1) -= this->kk->kz[zindex]*pressure->cval(cindex,0);
         }
         });
     if (own_pressure)
diff --git a/cpp/vorticity_equation.hpp b/cpp/vorticity_equation.hpp
index 32c6547b9781d23bf180d2b28a13d5cfdcd4e69a..90803b4b29a65951cac5afe93bfdd49cccf722da 100644
--- a/cpp/vorticity_equation.hpp
+++ b/cpp/vorticity_equation.hpp
@@ -70,12 +70,15 @@ class vorticity_equation
 
         /* physical parameters */
         double nu;
+        double dt;
         int fmode;                   // for Kolmogorov flow
         double famplitude;           // both for Kflow and band forcing
         double fk0, fk1;             // for band forcing
         double injection_rate;       // for fixed energy injection rate
         double energy;               // for fixed energy
         double friction_coefficient; // for Kolmogorov_and_drag
+        double variation_strength;   // for time-varying forcing
+        double variation_time_scale; // for time-varying forcing
         char forcing_type[128];
 
         /* constructor, destructor */
@@ -91,7 +94,7 @@ class vorticity_equation
         virtual ~vorticity_equation(void) noexcept(false);
 
         /* solver essential methods */
-        virtual void omega_nonlin(int src);
+        virtual void omega_nonlin(int src, double t = 0.);
         virtual void step(double dt);
         void impose_zero_modes(void);
 
@@ -103,7 +106,8 @@ class vorticity_equation
          *
          */
         void add_forcing(field<rnumber, be, THREE> *dst,
-                         field<rnumber, be, THREE> *src_vorticity);
+                         field<rnumber, be, THREE> *src_vorticity,
+                        double t = 0.);
 
         void add_Kolmogorov_forcing(field<rnumber, be, THREE> *dst,
                                     const int fmode,
diff --git a/documentation/index.html.in b/documentation/index.html.in
index 8614030531341f1e033c295771b5e1ccbc7793d7..e565b34a2913fbf66ddb4aa961e4047fd7da3567 100644
--- a/documentation/index.html.in
+++ b/documentation/index.html.in
@@ -17,13 +17,14 @@
     and the
     <a href="https://www.wilczek.physik.uni-bayreuth.de/en/team/index.html">
         University of Bayreuth</a>
-    in collaboration with the Application Support Group of the Max Planck
-    Computing and Data Facility.
+    in collaboration with the Application Support Group of the <a href=https://www.mpcdf.mpg.de/>Max Planck
+        Computing and Data Facility</a>.
     </p>
     <p>
     TurTLE is a framework for
     pseudo-spectral simulations of turbulence, with a highly optimized particle tracking method.
     It consists of a C++ core library, as well as a Python "wrapper" library.
+    TurTLE is open sourced (GPLv3), you may access it <a href="https://gitlab.mpcdf.mpg.de/TurTLE/turtle">here</a>.
     </p>
     <p>
     The documentation consists of two parts: