Basic functionality for exact unitary time propagation
Created by: femtobit
This pull request contains basic functionality for exact time propagation (Issue #14 (closed)). The code is not necessarily production ready yet. However, I think this is a good point to discuss what is there so far.
Please let me know what you think of the structure and implementation of the code and whether this goes in the right direction.
If potential issues with this PR are resolved, I would suggest adding a feature/time-evolution
branch to the main netket repo and merge it there, so we can continue to work on this before merging it into master
.
General ODE solver
The directory NetKet/UnitaryTimePropagation/TimeSteppers
contains the code used for ODE propagation (as part of the namespace netket::ode
), which is not limited to the Schroedinger equation but can in principle be used for any ODE system of the form dx/dt = F(x, t)
. States should be Eigen vector (or matrix) types. A few standard algorithms (Euler and Runge-Kutta) are provided, though I think in practice one should use Dopri54
(Dormand-Prince fifth-order Runge-Kutta scheme with adaptive step size). I am not sure how much sense it makes to include the other methods, which I mostly implemented for testing and demonstration purposes. The RK4 scheme might be useful if a fixed time step is desired for some reason.
The main time-stepping loop is implemented in https://github.com/netket/netket/blob/537de3206c3b0ea38eb8710cef3513f274e1ecb5/NetKet/UnitaryTimeEvolution/TimeStepper/abstract_time_stepper.hpp#L44-L64
My custom implementation is compared in one unit test to the Boost.Numeric.Odeint library and achieves comparable speed (though I did not test this systematically yet, so take this with a grain of salt).
If desired, additional time stepping methods can be added by subclassing AbstractTimeStepper
. In principle we could also add time steppers delegating to some external library, if it can work with Eigen matrices as states.
Factory methods for JSON
The steppers can be created from JSON parameters via a factory function provided in time_stepper.hpp
. (The same is now also true for the matrix wrappers, see matrix_wrapper.hpp
.) Personally, I prefer this approach to directly passing json
objects to the constructor for the following reasons:
- The Google C++ Style Guide recommends avoiding complex initialization logic in constructors and suggests using factory functions instead.
- Having a JSON constructor tightly couples the class to the configuration file format, making it harder to change that in the future (as many parts of the code are affected) and also making the initialization logic within the class harder to read, because it is mixed with JSON validation and processing.
- Passing the required data as arguments to the constructor reduces potential errors, as at least the types are checked at compile time (making it harder to, e.g., forget or mistype a parameter), while invalid JSON will only be detected at runtime (hopefully).
Time evolution code for NetKet systems
- Commit b694c0a5
The source file NetKet/UnitaryTimePropagation/time_evolution.hpp
contains the code for time-propagating quantum states via the Schroedinger equation using the general ODE solver. The class TimeEvolutionDriver
handles most of the setup and actual time propagation. The time-stepping algorithm and tolerances as well as the desired matrix wrapper (i.e., sparse or dense) can be specified in a JSON config.
An brief example tutorial is included: https://github.com/netket/netket/blob/537de3206c3b0ea38eb8710cef3513f274e1ecb5/Tutorials/TimeEvolution/ising1d.py#L1-L66
Parallelization
Currently, the set of initial states specified in the JSON file is distributed among the MPI processes and each one writes a separate output file for each of the initial states with the corresponding time evolution.
The ODE propagation itself is not parallelized.
Sanitizer
When the tests are built with -DNETKET_Sanitizer=ON
, they fail because of memory errors such as:
3: Direct leak of 2400 byte(s) in 1 object(s) allocated from:
3: #0 0x657c96 in __interceptor_malloc (/home/travis/build/femtobit/netket/build/Test/test-hamiltonian+0x657c96)
3: #1 0x7f56a1683e96 (<unknown module>)
3: #2 0x7f56a1683d98 (<unknown module>)
3: #3 0x7f56a02076fb (<unknown module>)
3: #4 0x7f56a85d890b in mca_base_components_open (/usr/lib/libmpi.so.1+0xf890b)
3: #5 0x7f56a8577b7b in mca_pml_base_open (/usr/lib/libmpi.so.1+0x97b7b)
3: #6 0x7f56a853a198 in ompi_mpi_init (/usr/lib/libmpi.so.1+0x5a198)
3: #7 0x7f56a855165f in MPI_Init (/usr/lib/libmpi.so.1+0x7165f)
3: #8 0x873305 in main /home/travis/build/femtobit/netket/Test/unit-tests.cc:7:3
3: #9 0x7f56a709af44 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21f44)
3: #10 0x57f64e in _start (/home/travis/build/femtobit/netket/build/Test/test-hamiltonian+0x57f64e)
See https://travis-ci.org/femtobit/netket/jobs/385823408 (or soon probably the Travis build of this pull request) for more. (Commit 3e44adb3 leads to the more detailed ASan output shown above. This makes running the tests a lot slower, unfortunately.) I am not sure why this occurs and whether something in my code is at fault. From the log shown, the errors all seem to be related to the MPI library and occur in all unit tests (most of which are not changed by this pull request). Yet, none of these show up in the builds of netket/master
, which suggests something in this branch causes them. Any idea?
In any case, thanks for taking the time to read this somewhat long description. Looking forward to your feedback on this PR.