-
Notifications
You must be signed in to change notification settings - Fork 0
Python code for the numerical simulation of disordered quantum systems
License
martinit18/and-python
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
# README # and-python project # Version 4.1 # Author: Dominique Delande # February, 21, 2023 WARNING: In this version, the following features are new or not yet implemented: * Lyapounov. Not implemented at all for multidimensional systems. C interface in 1d is obsolete. * Fast C versions of the most CPU intensive routines, using the ctypes interface are ony partially implemented in dimension 3 and larger. 1d and 2d version are working well and are as fast as a pure C code. * The spectral function calculation is completely new. It now uses the Kernel Polynomial Method (KPM) with Chebyshev polynomials, rather than the old Time->Energy Fourier transform of the evolution operator. * There is a new optiun to choose between reproducible and irreproducible randomness. In the first case, the same RNG is used between runs, so that the results should be identical. * There are inconsistencies on which file is used for reading parameters. See section II.9 I. Physics In this section, I discuss physics, leaving numerical methods and implementation details for section II. I.1. What is it? The and-python is a project written mainly in Python for numerical simulations of disordered quantum systems, oriented towards transport properties such as Anderson localization. It provides different methods for solving the Schroedinger equation - either time-independent or time-dependent - or the time-dependent Gross-Pitaevskii equation. This allows to compute the Green function, and to deduce from it different quantities such as eigenergies/eigenstates, density of states, spectral function, total transmission across a sample, giving access to the localization length for the non-interacting (Schroedinger) system. It is aimed at computing quantities averaged over disorder realizations, although it can also provide the statistical distributions of some quantities. The present version works in arbitrary dimension. I.2. How does it work? The Schroedinger or Gross-Pitaevskii is discretized on a regular grid in configuration space. The kinetic energy (Laplace operator) is discretized with a 3-point (in 1D) approximation, altough higher degree approximations could be easily added. The resulting discretized Hamiltonian is a sparse matrix (tridiagonal in 1D), which can be efficiently manipulated. For the Gross-Pitaevskii, the additional nonlinear term is purely diagonal. The choice of units is hbar=1, with the mass usually m=1, but can be adjusted with the parameter "one_over_mass". I.3. Exact diagonalization The simplest calculation is to diagonalize the discretized Hamliltonian to obtain the energy levels and eigenfunctions, and to compute e.g. the Inverse Participation Ratio or to study multifractality. There are tow methods : 'lapack' for full diagonalization (scales as the cube of the matrix dimension) with all eigenpairs and 'sparse' which computes few eigenvalues near a targeted energy. In the present version, only one eigenvalue is computed, but this is very easily changed in diag.py. This is only for non-interacting systems. I.4. Localization length (Lyapounov exponent) ** Currently does not work for multidimensional systems ** The transfer matrix method makes it possible to solve the Schroedinger equation at a given energy E (not necessarily an energy level) assuming some boundary condition on the left side and propagating to the right side. The propagation equation is in 1D a simple recurrence relation between \psi_{i-1}, \psi_i and \psi{i+1}, so that the propagation is trivial. In dimension d, one has to propagate a structure of dimension d-1 representing the wavefunction in an hyperplane perpendicular to the direction of propagation. The numerical method is intrisincally unstable, so that it typically picks an exponentially growing solution, with a positive Lyapounov exponent. In 1D, the only Lyapounov exponent is the inverse of the localization length. For a certain system size (much longer than the localization length), the statistical distribution of the Lyapounov exponent is close to a Gaussian distribution whose width decays as the inverse square root of the system size. Thus, the longer the system, the better. The same uncertainty is obtained by using many small systems or a single long one, all what matters is the sum of all lengths. As the number of Flops is proportional to the system size, the optimum is to use a system size significantly longer than the localization length, but not much longer, for a better use of the caches. This is only for non-interacting systems. I.5. Solving the time-dependent Schroedinger or Gross-Pitaevskii equation One starts with an arbitrary initial state, which can be anything: a Gaussian wavepacket, a plane wave, a state localized on a single site, or whatever the user builds. There are two methods to solve the evolution equation: the slow "ODE" method and the fast "Chebyshev" method. I.5.1. ODE method The discretized Schroedinger or Gross-Pitaevskii equation is a set of coupled differential equations between the various \psi_i(t), which can be solved using a standard Python package for ODEs. The advantage is that it works for arbitrary interaction strengths in the GPE. The drawback is that it is slow. Especially for the Schroedinger equation, the equations are linear, but this is not used by the algorithm. I.5.2. Chebyshev method In the non-interacting case, the evolution operator over time dt is exp(-i*H*dt) so that: \psi(dt) = \exp{-iHdt}\psi(t=0) The evolution operator is never explicitly built, but it can formally be written as an infinite series of Chebyshev polynomials of H. The coefficients of theis series decay with the order, so that the series can be efficiently propagated. Thanks to the recurrence relations between Chebyshed polynomials, it is possible to build recursively \psi(dt). If the series is truncated at order N, the calculation involves N matrix vector products of the type H|\phi>. Because of the sparse structure of H, this can be computed efficiently. The number of terms in the series is roughly range(H)*dt, where range(H)=E_max_E_min is the spectral range of the Hamiltonian. This is valid when N is large, for smaller range(H)*dt, N has to be taken slightly larger. As the CPU time needed scales like N, the cost of propagating over a long time (by chaining many elementary Chebyshev propagations) is almost independent of the time step, provided range(H)*dt is >> 1. A basic rule of thumb is not to take time steps making N smaller than 10. The interacting case is slightly more complicated, as the non-linear term is time dependent and thus cannot be included in the Chebyshev expansion. The program uses a split-step algorithm by interleaving elementary propagation operators \exp{-iHdt} and \exp{-ig|\psi|^2dt}. Propagation with \exp{-iHdt} uses the previously described Chebyshev expansion while the non-linear propagation \exp{-i g|\psi|^2dt} is diagonal in configuration space. Of course, this is a good approximation only if g is sufficiently small. Hence, the larger g, the smaller the time step dt to be taken. This may be a drawback because the number N of terms in the Chebyshev expansion may become small, implying a loss of efficiency. The program records the maximum value of g|\psi|^2dt reached during the whole propagation (the so-called "maximum nonlinear phase"), and prints it. A rule of thumb for systems where the disorder is stronger than interaction is that it should be smaller than 0.1. Sometimes, it has been observed that smaller values, say 0.02-0.05, are needed for good convergence. When the interaction is larger than the disorder, the density |\psi|^2 is more uniform and larger values of the nonlinear phase can be used. In any case, it is always good to check the conservation of the total energy, setting "dispersion_energy" to True in the parameter file. I.6. Spectral function NOTE: The spectral function calculation is completely new for version 4.0. Previously it was using temporal propagation followed by a Time->Energy Fourier transform. The new version uses direct calculation with the Kernel Polynomial Method (KPM) and Chebyshev polynomials. It is very significantly faster, less noisy, meaning that less disorder realizations are needed. The spectral function is defined as: A_\psi(E) = \overline{<\psi|\delta(H-E)|\psi>} where the overline denotes averaging over disorder realizations. In the KPM method, the function \delta(H-E) is computed as a sum over Chebyshev polynomials of the Hamiltonian H acting on the initial state |\psi>. The method is more detailed in the documentation. For the standard spectral function, \psi is a plane wave, but the program allows any initial state. Especially, if \psi is localized on a single site (option "point" for the "initial_state" variable in the [Wavefunction] section of the parameter file, the spectral function is the local density of states, which, after configuration averaging, gives the average density of states per unit volume. A better option, leading to less noisy density of states is to use: intiial_state = random which chooses uncorrelated complex Gaussian variables on each site and also provides the density of states. One has to specify the requested energy resolution and the total energy range. It is IMPORTANT that the energy range covers the full energy spectrum. Otherwise, disaster is guaranteed at the edges of the energy range which spoils everything. This is in contrast with the versions before 4.0, where the spectral function was computed by Fourier transform and the full spectral function was folded in the requested energy range. The spectral function can be computed in the interacting case, where the Hamitonian used in \delta(H-E) is now H = H-0 + disorder + multiplicative_factor_for_interaction*g*|\psi|^2 where the "multiplicative_factor_for_interaction" (default"0) is defined in the [Spectral] section of the parametre file. multiplicative_factor_for_interaction = 0.5 ensures that the average energy (preserved during the nonlinear evolution) is given by \int{dE E A_\psi(E)} and constant during the temporal evolution of \psi. II. Implementation II.1. Python modules The software is almost entirely written in Python, using mostly the standard modules numpy and scipy. However, few pieces have also pure C implementations, for the inner heavy computational routines. These may speed up the calculation by roughly one order of magnitude. Note that there is always a Python implementation available, which is 100% compatible. See section II.12 for details. The list of modules used is: os time math numpy getpass copy sys configparser argparse timeit scipy.integrate scipy.sparse scipy.special In addition, there are 6 optional modules: mkl, mkl_random, mkl_fft, numba, ctypes and numpy.ctypeslib. The mkl module is only used to set the number of OpenMP threads, it is probably useless. If the mkl_random module is present, the MKL random number generator is used. If not, the program uses numpy.random. If the mkl_fft module is present, most FFTs (the ones which use a lot of CPU time) are performed using the MKL FFT routines. If not present, the program uses numpy.fft, which is a bit slower. The ctypes and numpy.ctypeslib modules (if available) make it possible to use a C version of some numerically intensive routines. It is usually about 10 times faster than the Python code. The numba module is used to speed up a bit the pure Python code. All the modules are available using anaconda. The last module "anderson" contains all the specific code of this software. II.2. The "anderson" module This module contains all the basic code for the calculations. It contains: __init.py__ for the basic structures and methods diag.py for exact diagonalization routines io.py for input/output routines lyapounov.py for calculation of the Lyapounov exponent propagation.py for temporal propagation* II.3. Classes The code defines the following classes: Timing in __init__.py which is used for monitoring time spent in the various calculations Potential in __init__.py which defines a disordered potential Hamitonian in __init__.py, daughter of Potential, which defines a full Hamitonian (note that the Potential class is almost useless and may disappear) Wavefunction in __init__.py which defines a wavefunction, discretized in configuration space (but also has a momentum space component) Diagonalization in diag.py which defines the diagonalization to be used Lyapounov in lyapounov.py which defines where to compute the Lyapounov exponent Temporal_propagation in prop.py which defines the parameters of the temporal propagation Measurement in prop.py which defines the various measurements performed during the temporal propagation Spectral_function in prop.py which defines the parameters for the calculation of the spectral function or the density of states In a future release, we should document all methods of these classes. At the moment, there are some routines which should be turned to methods in a class. Cleaning up these things should be done... II.4. Diagonalization Everything is in the diag.py file. Very primitive calculation, computes basically the IPR of a single state using the compute_IPR routine. The diagonalization routine is either numpy.linalg.eigh (Lapack diagonalization) or scipy.sparse.linalg (Sparse diagonalization), which internally uses Arpack. Of course, sparse diagonalization is much faster for large matrices. It can easily go to matrices of size 1.e7 in 1d, the main limitation being memory allocation. In 2d, it works quite well. Preliminary tests in 3d show that it does not behave too well. It should be good to explore the specialized module primme https://github.com/primme/primme, not available in conda, but easy to get using pip. Another interesting possibility would be to interface with Jadamilu. II.5. Lyapounov Everything is in the lyapounov.py file. The compute_lyapounov method applied on a Lyapounov object creates a specific disorder and computes the lyapounov exponent on a regular grid of energies. It has a specific C implementation, much much faster, see section II.12. II.6. Temporal propagation Everything is in the propagation.py file. The main routine is gpe_evolution, which evolves an initial state in a disordered potential and measures some interesting quantities. If the Chebyshev method is used, there is a C implementation available, about 10 times faster, see section II.12. II.7. Spectral function Everything is in the propagation.py file, although it could probably be moved to a separate module. II.8. Basic examples There are basic examples in the files: diag/compute_IPR.py lyapounov/compute_lyapounov_vs_energy.py prop/compute_prop.py spectral_function/compute_spectral_function.py All these examples are supposed to run in few seconds. The structure is the same for each example: 1. Read parameters of the calculation in the parameter file (see section II.9) 2. Prepare the Hamiltonian structure 3. Prepare the calculation to be performed, including numpy arrays for the results and the "header strings" for output (see section II.10) 4. Loop over various disorder configurations 5. Gather the results averaged over disorder configurations in a single structure 6. Output results in various files (see section II.10). 7. Print a summary of CPU time and number of Flops. These examples can be used as templates for your own calculations. There should be comments in these files, but there are only few at the moment. How to launch these scripts is explained in sections II.9 (serial version) and II.12 (MPI version). II.9. Input parameters In the examples, the parameters are read from a "parameter file". The present stage is a bit inconsistent. For temporal propagation, it is required to give as argument the name of the parameter file, i.e. something like: python compute_prop.py my_parameter_file For other python scripts, the parameters are expected to be in the "params.dat" and the script is launched with e.g. python compute_IPR.py In a future version, every script will require explicitly the name of the parameter file. The parameter file is parsed using the Python configparser module. Of course, it is always possible to define the various parameters using other methods, e.g. direct assignement at the beginning of the Python script or parsing a XML file (not implemented yet). Using the configparser module, the input file (typically params.dat) is divided in several sections, each section refering to a family of parameters. For example, the parameter file params.dat for the spectral_function example is: ##################################################################### [System] dimension = 2 # System size in natural units size_1 = 20. size_2 = 20. size_3 = 5. size_4 = 1. # Spatial discretization delta_1 = 0.5 delta_2 = 0.5 delta_3 = 0.25 delta_4 = 0.25 # either periodic or open boundary_condition_1 = periodic boundary_condition_2 = periodic boundary_condition_3 = periodic boundary_condition_4 = periodic use_mkl_random = True use_mkl_fft = True [Disorder] # Various disorder types can be used # type = anderson # type = anderson_gaussian # type = regensburg type = konstanz # type = singapore # type = speckle # Correlation length of disorder sigma = 1.0 # Disorder strength V0 = 1.0 [Nonlinearity] # g is the nonlinear interaction g = 0.0 [Wavefunction] # Either plane_wave or gaussian_wave_packet initial_state = plane_wave #initial_state = gaussian_wave_packet # make sure that k_0_over_2_pi_i*size_i is an integer k_0_over_2_pi_1 = 0.0 k_0_over_2_pi_2 = 0.0 # Size of Gaussian wavepacket sigma_0_1 = 10.0 sigma_0_2 = 10.0 [Propagation] # Propagation method can be either 'ode' or 'che' (for Chebyshev) method = che data_layout = real #data_layout = complex # Total duration of the propagation t_max = 25.0 # Elementary time step delta_t = 1.0 [Averaging] n_config = 10000 [Measurement] delta_t_measurement = 1.0 #first_measurement = 0 density = True density_momentum = True dispersion_position = True dispersion_position2 = True #dispersion_momentum = True #dispersion_energy = True dispersion_variance = True #wavefunction = True #wavefunction_momentum = True #autocorrelation = True [Spectral] range = 20.0 resolution = 0.01 ##################################################################### The comments should be self-explanatory. Each example uses only the sections it needs, the other ones, if present, are ignored. The three obligatory sections are: [System] which defines the geometric properties of the system [Disorder] which defines the properties of the Hamiltonian [Averaging] which defines on how many disorder configurations are averaged WARNING: When MPI is used (see section II.12), the parameter n_config refers to the TOTAL number of configurations. This is different from the an{1,2,3}d_propyy.c programs, where it was the number of configurations per MPI process. Other sections are as follows: [Nonlinearity] to define g when needed (propagation and spectral function) [Wavefunction] when an initial state is needed (propagation and spectral function) [Propagation] for temporal propagation (propagation) [Spectral] for spectral function (spectral function) [Measurement] for temporal propagation (propagation) [Diagonalization] for diagonalization (diagonalization) [Lyapounov] for computation of the Lyapounov exponent (lyapounov) In each section, there are parameters, each defined on a single line: name_of_parameter = value_of_parameter For some parameters, there are default values. For examples, all measurements are turned off by default. If a parameter with no default value is missing, the program stops with an error message. Be careful to correctly write the name of a parameter, it is a source of common errors. II.10. Output Routines which conveniently outputs the results of the calculations are available in io.py. The general idea is that each output file contains all the information needed to reproduce it, that is the values of all input parameters, before the results themselves. In the header of each output file, there are also informations on the script used to produce the results, the machine on which it has been ran, how long the run took, and whether several MPI processes were used (see section II.12). At the moment, only ASCII human-readable files are used. In a next release, it is planned to add HDF5 outputs. The output files are produced by the numpy.savetxt routine. It consists in a header (using the "header" parameter of numpy.savetxt) followed by a series of lines, each line containing at least two numerical values. For a 1D temporal propagation, it can be for example, time in column 1 and <x> in column 2. Normally, the header_string contains all the information on the content of each column. The header is conveniently created using the output_string routine (in io.py) called with the relevant parameters. It automatically adjusts to produce a header from the transmitted parameters. The output_density routine (in io.py) outputs data with one, two or three columns, for the different types of data: 'density', 'density_momentum', 'spectral_function', 'IPR', 'histogram_IPR', 'lyapounov', 'histogram_lyapounov', with the third column used for the standard deviation, if relevant. One can also print a complex function ('autocorrelation') or a complex wavefunction in configuration ('wavefunction') of momentum ('wavefunction_momentum') space. The more specialized output_dispersion routine is for output of the various results of a temporal propagation, such as <x(t)>, <p(t)>, <E_total(t)>..., optionally with the standard deviation. II.11. C routines The most numerically intensive routines have been also written in C. They should be strictly equivalent to the corresponding Python routines. In the first versions, the CFFI interface between Python and C was used. Starting from version 3.0, the ctypes interface is used. In a near future, it may be that the pybind11 interface between Python and C++ will be used. II.11.1 Ctypes interface The basic idea is to write a pure traditional C code implementing the calculation-intensive routines. For the temporal propagation using the Chebyshev method, there is for exemple in "anderson/ctypes/chebyshev.c" a C routine defined by: double chebyshev_real(const int dimension, const int * restrict tab_dim, const int max_order, const int * restrict tab_boundary_condition, double * restrict wfc, double * restrict psi, double * restrict psi_old, const double * restrict disorder, const double * restrict tab_coef, const double * tab_tunneling, const double two_over_delta_e, const double two_e0_over_delta_e, const double g_times_delta_t, const double e0_times_delta_t); The code is compiled using "make" with the Makefile in the "anderson/ctypes" directory. The GNU, clang, Intel and Intel Oneapi compilers may be used, just by setting the COMPILER environment variable (see in the Makefile). It seems that the Intel compiler gives the fastest code, it is thus recommended to use it if available. Note that, on recent distributions like Ubuntu 22.04, the old Intel-21 compiler "icc" does not work any longer. You may use the "icc" compiler from Intel Oneapi, setting the variable COMPILER to "intel". As this "icc" compiler is no longer developed, it will probably fail in a near future. WARNING: The code is generated in a routine typically name chebyshev.so Depending on the options in the Makefile, it may run only on the specific machine where it has been compiled. If you encounter some error like "Illegal instruction", you have te recompile the C code. If using the Intel compiler on Linux, the code should be portable and optimized for all Intel/AMD architectures. On the python side, nothing has to be modified in the main script. Only the code inside the "anderson/propagation.py" file is modified to use C routines if available. For exemple, in the "gpe_evolution" routine, there are the following lines: if not H.spin_one_half and self.want_ctypes: try: import ctypes import numpy.ctypeslib as ctl self.has_specific_full_chebyshev_routine = True self.chebyshev_ctypes_lib=ctypes.CDLL(anderson.__path__[0]+"/ctypes/chebyshev.so") if self.data_layout=='real': self.chebyshev_ctypes_lib.chebyshev_real.argtypes = [ctypes.c_int, ctl.ndpointer(np.intc), ctypes.c_int, ctl.ndpointer(np.intc),\ ctl.ndpointer(np.float64), ctl.ndpointer(np.float64), ctl.ndpointer(np.float64), ctl.ndpointer(np.float64), ctl.ndpointer(np.float64), ctl.ndpointer(np.float64),\ ctypes.c_double, ctypes.c_double, ctypes.c_double, ctypes.c_double] self.chebyshev_ctypes_lib.chebyshev_real.restype = ctypes.c_double if not (hasattr(self.chebyshev_ctypes_lib,'chebyshev_real') and hasattr(self.chebyshev_ctypes_lib,'elementary_clenshaw_step_real_'+str(H.dimension)+'d')): self.has_specific_full_chebyshev_routine = False self.chebyshev_ctypes_lib = None if H.seed == 0 : print("\nWarning, chebyshev C library found, but without routine for real data layout and dimension "+str(H.dimension)+", this uses the slow Python version\n") if self.data_layout=='complex': self.chebyshev_ctypes_lib.chebyshev_complex.argtypes = [ctypes.c_int, ctl.ndpointer(np.intc), ctypes.c_int, ctl.ndpointer(np.intc),\ ctl.ndpointer(np.complex128), ctl.ndpointer(np.complex128), ctl.ndpointer(np.complex128), ctl.ndpointer(np.float64), ctl.ndpointer(np.float64), ctl.ndpointer(np.float64),\ ctypes.c_double, ctypes.c_double, ctypes.c_double, ctypes.c_double] self.chebyshev_ctypes_lib.chebyshev_complex.restype = ctypes.c_double if not (hasattr(self.chebyshev_ctypes_lib,'chebyshev_real') and hasattr(self.chebyshev_ctypes_lib,'elementary_clenshaw_step_complex_'+str(H.dimension)+'d')): self.has_specific_full_chebyshev_routine = False self.chebyshev_ctypes_lib = None if H.seed == 0 : print("\nWarning, chebyshev C library found, but without routine for complex data layout and dimension "+str(H.dimension)+", this uses the slow Python version\n") except: self.has_specific_full_chebyshev_routine = False self.chebyshev_ctypes_lib = None if H.seed == 0 : print("\nWarning, no ctypes module, no numpy.ctypeslib module or no chebyshev C library found, this uses the slow Python version!\n") self.use_ctypes = self.has_specific_full_chebyshev_routine and self.want_ctypes which test if the proper routines exist in the "anderson/ctypes/chebyshev.so" library and, if succesful, defines the types of the arguments and return value of the useful routines. This uses the standard ctypes and numpy.ctypeslib modules. Then, in the chebyshev_step routine, the C code is called if it is available with: nonlinear_phase = chebyshev_ctypes_lib.chebyshev_real(H.dimension, np.asarray(H.tab_dim,dtype=np.intc), max_order, H.array_boundary_condition,\ local_wfc, psi, psi_old, H.disorder.ravel(), propagation.tab_coef, np.asarray(H.tab_tunneling),\ H.two_over_delta_e, H.two_e0_over_delta_e, H.interaction*propagation.delta_t, H.medium_energy*propagation.delta_t) so that the conversion between Python and C variables and pointers is properly done. If the C code is not available, an equivalent Python code is used, but it is MUCH slower. At the moment, there are only the two routines chebyshev_real and chebyshev_complex in chebyshev.c (the two routines correspond to the choice of the data_layout). These routines internally manage both the 1d and 2d cases. The 3d case is only supported for the "complex" data_layout. In a near future, it could be that the "real" data layout will be completely remved, as it is not siginificantly faster. One routine could be developped for the calculation of the total energy as it is a bit costly. Computing <x>, <x^2>, <p>, <E_nonlinear> is significantly less expensive, thus a C version is not so important. II.11.2 Old CFFI interface (no longer supported) The routines use the CFFI interface. Basically, it defines a proper C-Python interface and a C source code embedded in a Python wrapper. For example, the lyapounov_build.py file contains at the beginning: from cffi import FFI ffibuilder = FFI() ffibuilder.cdef("double core_lyapounov_cffi(const int dim_x, const int loop_step, const double * disorder, const double energy, const double inv_tunneling);") which defines a routine double core_lyapounov_cffi which can be called from Python. The following of lyapounov_build.py defines the embedded C code: ffibuilder.set_source("_lyapounov", r""" #include <stdlib.h> #include <stdio.h> #include <string.h> ... double core_lyapounov_cffi(const int dim_x, const int loop_step, const double * disorder, const double energy, const double inv_tunneling) { ... calculation... return(gamma); } """) if __name__ == "__main__": ffibuilder.compile(verbose=True) The code is compiled with the Makefile in the "anderson" directory. The GNU, clang and Intel compilers may be used, just by setting the COMPILER environment variable (see in the Makefile). It seems that the Intel compiler gives the fastest code, it is thus recommended to use it if available. WARNING: The code is generated in a routine typicalled named: _lyapounov.cpython-37m-x86_64-linux-gnu.so It is compiled for the specific architecture of the host. It is thus not portable and must be recompiled on each machine. The compilation also produces intermediate _lyapounov.c and _lyapounov.o files, which can be discarded. On the Python side, the compute_lyapounov method in the Lyapounov class contains: try: from anderson._lyapounov import ffi,lib use_cffi = True # print('Using CFFI version') except ImportError: use_cffi = False print("\n Warning, this uses the slow Python version, you should build the C version!\n") which tries to load the CFFI version if available. When the routine is needed, the code looks like: if use_cffi: tab_gamma[i_energy]=lib.core_lyapounov_cffi(dim_x, loop_step,ffi.cast('double *',ffi.from_buffer(H.disorder)), self.tab_energy[i_energy], inv_tunneling) else: tab_gamma[i_energy]=core_lyapounov(dim_x, loop_step, H.disorder, self.tab_energy[i_energy], inv_tunneling) which uses the CFFI version if available, the Python version in the core_lyapounov Python routine otherwise. II.12. Parallelism using MPI The code can be parallelized using MPI. The parallelization is only the trivial one over disorder realizations (no use of the Python multiprocessing module). The reason is that it is both easier to program and slightly more efficient to use MPI. This however requires the mpi4py module, which is not always available (not by default on anaconda). When used on the LKB machines or on the ponyo cluster, there is a specific environment for the OpenMPI implementation of MPI, which can be activated using: conda activate openmpi The code must contain near the beginning something like: mpi_version, comm, nprocs, rank, mpi_string = anderson.determine_if_launched_by_mpi() which checks whether the python script is called inside a MPI process. If this is the case, mpi_version is True, the MPI communicator is comm, the number of MPI processes is nprocs, the rank of the current process is rank, and mpi_string contains some minimal MPI information If not run inside MPI, mpi_version=False, comm=None, nprocs=1, rank=0 and mpi_string contains proper information The MPI version is launched using e.g.: mpiexec -n 8 python compute_prop.py my_parameter_file If no mpi4py module is found, the program stops with an error message. If MPI is activated, all I/O is done on process 0. Input data (parsed from the input file) are broadcasted to other processes. Then, each process builds its own objects and works with the defined methods, like without MPI. After all configurations have been treated, the data are gathered/reduced on process 0, which performs the remaining calculation and the output. Altogether, it is not much change compared to the sequential code. WARNING: When MPI is used, the parameter n_config refers to the TOTAL number of configurations. This is different from the anxd_propxx.c programs, where it was the number of configurations per MPI process.
About
Python code for the numerical simulation of disordered quantum systems
Resources
License
Stars
Watchers
Forks
Releases
No releases published
Packages 0
No packages published