Source code for tools

"""
Module `tools.py` provides functions to write files for the Fortran backend
and run serial simulations from the command line.

last revision 091911-MLW
/ instead of &end as end of namelist file for consistency with nagfor
"""
import subprocess
from math import ceil, floor
from copy import deepcopy
#import os.path
from os import remove, makedirs, symlink
import shutil
import zipfile
import hashlib
import numpy as np
#from .mpo import MPO
#from .ops import OperatorList
#from .obsterms import Observables, check_restore_timeevo
#from .state import State, GenerateqInitialState, nint, GenerateParityState
#from .dynamics import WriteDynamics, CheckOrDefault, QuenchList
#from .dynamics import QuantumCircuits

# Import * is ok, convergence defines all necessary classes in __all__
#from .convergence import *

from sys import version_info
if(version_info[0] < 3):
    myFileNotFoundError = IOError
else:
    myFileNotFoundError = FileNotFoundError


[docs]def write_makefile(settings): """ Write out the main file for compiling the fortran library. Return boolean if MPI is used. **Arguments** settings : dict Key-value pairs for the settings of the compiler. Necessary keys are PY, FC, OFLAGS, OPENMP, BLAS_FLAG, LAPACK_FLAG, ARPACK_FLAG, HDF5_FLAG, LIBFLAGS. """ imps = (len(settings['ARPACK_FLAG']) > 0) hdf5 = (len(settings['HDF5_FLAG']) > 0) opmp = (len(settings['OPENMP']) > 0) mpi = (settings['FC'] == 'mpif90') dbg = (('-O0' in settings['OFLAGS']) or ('-g' in settings['OFLAGS'])) macacc = (('-framework accelerate' in settings['LIBFLAGS']) or ('-framework accelerate' in settings['LAPACK_FLAG']) or ('-framework accelerate' in settings['BLAS_FLAG'])) # Strings for generating command line options simps = 'T' if(imps) else 'F' sdbg = 'T' if (dbg) else 'F' sacc = 'T' if (macacc) else 'F' sh5 = 'T' if(hdf5) else 'F' modulelist = ['BasicOps', 'IOOps', 'LinearAlgebra', 'ExpokitOps', 'Tensors', 'qTensors', 'ContractionOps', 'DecompositionOps', 'MPOOps', 'MPSOps', 'MPDOOps', 'LPTNOps', 'LanczosOps', 'Convergence', 'ObsOps', 'VariationalOps', 'iMPSOps', 'KrylovOps', 'TDVPOps', 'TEBDOps', 'ZaletelOps', 'TimeEvolutionOps', 'PyInterface'] if(not imps): modulelist.remove('iMPSOps') #if(not hdf5): modulelist.remove('HDF5Ops') mstr = '' mstr += '# Makefile for MPSFortLib\n' mstr += '# -----------------------\n\n' mstr += 'export PRJHOME=$(CURDIR)/\n' mstr += 'export MODDIR=$(PRJHOME)Mods/\n' mstr += 'export DIRECTORIES=$(MODDIR)\n' mstr += 'export INCLUDES=$(addprefix -I, $(DIRECTORIES))\n' mstr += 'export PY = ' + settings['PY'] + '\n' if(hdf5): mstr += 'export FC = ' + settings['HDF5_FLAG'] + '\n' else: mstr += 'export FC = ' + settings['FC'] + '\n' mstr += 'export INCFLAGS = ' + settings['INCFLAGS'] + '\n' mstr += 'export OFLAGS = ' + settings['OFLAGS'] + '\n' tmp = settings['OPENMP'] + ' ' tmp += settings['ARPACK_FLAG'] + ' ' tmp += settings['LAPACK_FLAG'] + ' ' tmp += settings['BLAS_FLAG'] + ' ' #tmp += settings['HDF5_FLAG'] + ' ' # HDF5 is not used via flags tmp += settings['LIBFLAGS'] + '\n\n' tmp = ' '.join(tmp.split()) mstr += 'export LIBFLAGS = ' + tmp + '\n\n' mstr += '# List objects\n' mstr += '# ------------\n\n' mstr += 'export OBJS =' for elem in modulelist: mstr += ' $(MODDIR)' + elem + '.o' mstr += '\n\n' mstr += 'export EXES =' for elem in modulelist: mstr += ' $(MODDIR)' + elem + '_tests' mstr += '\n\n' mstr += 'all : moddir_obj MPSMain tests\n\n' mstr += '# Object compilation rules\n' mstr += '# ------------------------\n\n' mstr += 'moddir_obj :\n' mstr += '\t@echo $(FC)\n' mstr += '\tcd $(MODDIR); $(PY) TemplateGenerator.py $(OBJS)' \ + ' --mpi=$(FC) --dbg=' + sdbg + ' --imps=' + simps \ + ' --macacc=' + sacc + ' --hdf5=' + sh5 + ' \n' mstr += '\tcd $(MODDIR); make all\n\n' mstr += 'tests : moddir_obj\n' mstr += '\t@echo "Build tests"\n' mstr += '\t@echo ""\n' mstr += '\t@echo ""\n' mstr += '\tcd $(MODDIR); make tests\n\n' mstr += '%Main.o : %Main.f90\n' if settings['INCFLAGS'] != '': mstr += '\t$(FC) $(OFLAGS) -c $< $(INCLUDES) $(INCFLAGS)\n\n' else: mstr += '\t$(FC) $(OFLAGS) -c $< $(INCLUDES)\n\n' mstr += '%_Tests.o : %_Tests.f90\n' if settings['INCFLAGS'] != '': mstr += '\t$(FC) $(OFLAGS) -c $< $(INCLUDES) $(INCFLAGS)\n\n' else: mstr += '\t$(FC) $(OFLAGS) -c $< $(INCLUDES)\n\n' mstr += '# Primary rules\n' mstr += '# -------------\n\n' mstr += '%Main : moddir_obj %Main.o\n' if settings['INCFLAGS'] != '': mstr += '\t$(FC) $(OFLAGS) -o Execute_$@ $@.o $(OBJS) $(LIBFLAGS) $(INCLUDES) $(INCFLAGS)\n\n' else: mstr += '\t$(FC) $(OFLAGS) -o Execute_$@ $@.o $(OBJS) $(LIBFLAGS) $(INCLUDES)\n\n' mstr += '%_Tests : moddir_obj %_Tests.o\n' if settings['INCFLAGS'] != '': mstr += '\t$(FC) $(OFLAGS) -o Execute_$@ $@.o $(OBJS) $(LIBFLAGS) $(INCLUDES) $(INCFLAGS)\n\n' else: mstr += '\t$(FC) $(OFLAGS) -o Execute_$@ $@.o $(OBJS) $(LIBFLAGS) $(INCLUDES)\n\n' mstr += '\tmv Execute_$@ ../tests_MPSFortLib/.\n\n' mstr += 'cleanSub : \n' mstr += '\tcd $(MODDIR); make clean\n\n' mstr += 'clean: #removes all old *.o and *.mod files\n' mstr += '\tmake cleanSub ;\n' mstr += '\trm -f *.o; rm -f Execute*;\n\n' fh = open('MPSFortLib/makefile', 'w+') fh.write(mstr) fh.close() return mpi
[docs]def BuildMPSFortLib(Parallel=False, tests=False, track=True): """ Make the fortran libraries. The return vector contains the status of make command for serial, parallel, and tests in this order if all are compiled. **Arguments** Parallel : boolean, optional sets flag for openMPI compilation. default to ``False`` (serial code without MPI) tests : boolean, optional Set handling if tests for fortran modules are compiled (True) or not (False). Default to ``True``. track : boolean, optional Flag if line numbers are decoded in file `make.log` (True) or if original output goes to standard out (False). Default to ``True``. """ if(track): val = execute(['make', 'MPSMain', '>', 'make.log', '2>&1'], cwd='MPSFortLib') else: val = execute(['make', 'MPSMain'], cwd='MPSFortLib') retvec = [val] if(Parallel): if(track): val = execute(['make', 'MPSParallelMain', '>>', 'make.log', '2>&1'], cwd='MPSFortLib') else: val = execute(['make', 'MPSParallelMain'], cwd='MPSFortLib') retvec.append(val) if(tests): if(track): val = execute(['make', 'tests', '>>', 'make.log', '2>&1'], cwd='MPSFortLib') else: val = execute(['make', 'tests'], cwd='MPSFortLib') retvec.append(val) if(track): execute(['python', 'deref.py', 'make.log'], cwd='MPSFortLib') execute(['head', '-n', '40', 'make.log'], cwd='MPSFortLib') execute(['mv', 'MPSFortLib/make.log', '.']) return retvec
[docs]def CleanMPSFortLib(): """ Clean the fortran libraries. """ return execute(['make', 'clean'], cwd='MPSFortLib')
[docs]def InstallMPSFortLib(INSTALLATION_DIR='/usr/local/bin', Parallel=False): """ Install the Fortran libraries, that is copying the build executabels to the installation directory. **Arguments** INSTALLATION_DIR : str, optional path of the installation directory. default to `/usr/local/bin/` Parallel : boolean, optional flag for openMPI installation. If ``True``, MPI-executable is copied as well to the installation directory default to False """ stat = execute(['cp', 'MPSFortLib/Execute_MPSMain', INSTALLATION_DIR + '/' + 'Execute_MPSMain']) if(Parallel): stat_par = execute(['cp', 'MPSFortLib/Execute_MPSParallelMain', INSTALLATION_DIR + '/' + 'Execute_MPSParallelMain']) stat = 0 if((stat == 0) and (stat_par == 0)) else 1 return stat
[docs]def InstallMPSCLIUtility(INSTALLATION_DIR='/usr/local/bin'): stat = execute(['cp', '-r', 'MPSCLI/*', INSTALLATION_DIR]) return stat
[docs]def execute(command, cwd=None): """ Execute the given command on the command line. **Arguments** command : list of strings List with all parts of the command. Parts seperate by whitespaces form a individual entry in the list. """ tmp = subprocess.list2cmdline(command) print(tmp) return subprocess.call(tmp, shell=True, cwd=cwd)
[docs]def submit_job(script_name, comp_info): """ Submits a job to the cluster. **Arguments** script_name : name of the pbs/slurm script to be submitted. comp_info : dictionary dictionary with cluster settings, of interest is the key ``queueing`` to decide whether `pbs` or `slurm` is used. """ if(comp_info['queueing'] == 'pbs'): cmdline = ['qsub', script_name] elif(comp_info['queueing'] == 'slurm'): cmdline = ['sbatch', script_name] else: raise ValueError('Unkown value for queueing system.') if(os.path.isfile(script_name)): execute(cmdline) else: raise ValueError('Script to be submitted does not exist.') return
[docs]def runMPS(infileList, RunDir=None, Debug=False, customAppl=None): """ Run the MPS application with the given list of input files. **Arguments** infileList : list list of of the main files passed to fortran executable as command line argument. RunDir : str, optional Specifies path of the fortran-executable (local or global installation). This is available for default-simulation and custom application. (@Developers: Debugging with valgrind always works with a local installation.) `None` : if executable available within local folder, use local installation, otherwise assume global installation, default '' (empty string) : for global installation './' : local installation PATH+'/' : path to executable ending on slash `/`. Additional steps may be necessary when using this. Debug : boolean, optional Developers only: false : run usual simulation, default true : run debugging with valgrind using local installation If the folder MPSFortLib is visible, then line numbers from f90 modules are de-references to the template line numbers. (visible = actual MPSFortLib folder in the present working directory, a symbolic link to MPSFortLib, ...) customAppl : str, optional define custom executable. Global and local installation work as before. Custom applications cannot run with valgrind. default to `None` (running default executable) """ if customAppl is not None: resList=[] if(RunDir is None): if(os.path.isfile('./' + customAppl)): # Local installtion appname = './' + customAppl else: # Global installation appname = customAppl else: # User-defined path appname = RunDir + customAppl for infile in infileList: cmdline = [appname] cmdline += [infile[0], infile[1]] ret_val=execute(cmdline) if ret_val!=0: raise MPSFortLibError(ret_val) resList.append(ret_val) else: if Debug: resList=[] counter=0 for infile in infileList: cmdline = ['valgrind', '--tool=memcheck', '--leak-check=yes', '--track-origins=yes', '--show-reachable=yes', './Execute_MPSMain', infile[0], infile[1], '>', 'memcheck_' + str(counter) + '.log', '2>&1'] ret_val = execute(cmdline) try: execute(['python', 'deref.py', '../memcheck_' + str(counter) + '.log'], cwd='MPSFortLib') except OSError: # MPSFortLib is not necessary visible, so do not throw an # error (python 2.x) print('Line numbers in F90 not de-referenced.') except FileNotFoundError: # python 3.x print('Line numbers in F90 not de-referenced.') if(ret_val != 0): raise MPSFortLibError(ret_val) resList.append(ret_val) counter = counter + 1 else: resList=[] if RunDir != None: # Local installation appname = RunDir + 'Execute_MPSMain' else: # Global installation appname = 'Execute_MPSMain' for infile in infileList: cmdline = [appname] cmdline += [infile[0], infile[1]] ret_val=execute(cmdline) if ret_val!=0: raise MPSFortLibError(ret_val) resList.append(ret_val) return resList
[docs]def WriteHparams(fileStub,Parameters,IntHparams): """ Write out the hamiltonian Parameters to a Fortran-readable file. **Arguments** fileStub : string basis for the filename extended with `HamiParams.dat` Parameters : dictionary keys correspond to values of `IntHparams[i]` for integers `i` values are floats for translational invariance models or lists/np.ndarrays for translational variance models. IntHparams : dictionary key `num` defines the number of parameters. Keys (integer) from 0 to `num` - 1 can be set if the change (no warranty that this is right) **Details** The part written by one call of ``WriteHparams`` looks like 1) number of parameters (integer) 2) number of sites (1=translational invariance) 3) floats for coupling """ Hpfilename=fileStub+'HamiParams.dat' Hpfile=open(Hpfilename,'w') #Write number of Hamiltonian parameters Hpfile.write('%16i'%(IntHparams['num'])+'\n') for i in range(IntHparams['num']): if IntHparams[i] in Parameters.keys(): #Write out site-valued term: if(hasattr(Parameters[IntHparams[i]], '__len__')): tstr='' for j in Parameters[IntHparams[i]]: tstr+='%30.15E'%(j) Hpfile.write('%16i'%(len(Parameters[IntHparams[i]]))+'\n') Hpfile.write(tstr+'\n') #Write out uniform term else: Hpfile.write('%16i'%(1)+'\n') Hpfile.write('%30.15E'%(Parameters[IntHparams[i]])+'\n') Hpfile.close() return Hpfilename
[docs]def WriteMainFile(p, cmplx_op, hashlist, MainHashList, nqns=0, npns=0, Operators=None, PostProcess=False): """ Subprogram to write main files from a parameter dictionary p for a MPS simulation. Intended for internal use only. **Arguments** p : dictionary p contains the parameter settings for the simulation specified through various keys. cmplx_op : boolean True : at least one operator is complex valued False : all operators are real hashlist : list of strings contains a list of hashes to be merged to one single hash identifying the variational/static search of MPS (containing the information about Hamiltonian, the couplings, convergence settings etc ...) MainHashList : list of strings List contains the hashes of previous main files to check nqns : integer, optional default to 0 npns : integer, optional default to 0 Operators : instance of OperatorList, optional default to None PostProcess : logical, optional unused, default to False **Details** The file `...qnumbers.dat` is written during this function. """ writedir=p['Write_Directory'] outdir=p['Output_Directory'] # Write out quantum numbers # ------------------------- if(nqns + npns > 0): # Check length of the lists if(len(p['Abelian_quantum_numbers']) != nqns): raise Exception("Number of Abelian quantum numbers passed in " + "does not match the number of generators!") if(len(p['Discrete_quantum_numbers']) != npns): raise Exception("Number of Discrete quantum numbers passed in " + "does not match the number of generators!") # Check that the shifted and scaled quantum numbers given correspond # to integers. This, in essence, states that they are valid quantum # numbers for the given system size. qstr = '' qnums = [] pnums = [] for ii in range(nqns): qnum = ((p['Abelian_quantum_numbers'][ii] - p['L'] * Operators['q_shifts'][ii]) / Operators['q_scales'][ii]) if(abs(float(nint(qnum)) - float(qnum)) > 0.00000001): raise Exception("The shifted and scaled quantum number " + str(qnum) + " computed from (" + str(p['Abelian_quantum_numbers'][ii]) + "-" + str(p['L'] * Operators['q_shifts'][ii]) + ")/" + str(Operators['q_scales'][ii]) + " " + "\nis not an integer and so does not " + "represent a valid quantum number!\n" + "If you are certain this is an error file " + "a bug report!") qnum = int(qnum) qnums.append(qnum) qstr += '%16i'%(qnum) for ii in range(npns): jj = nqns + ii qnum = ((p['Discrete_quantum_numbers'][ii] - p['L'] * Operators['q_shifts'][jj]) / Operators['q_scales'][jj]) if(ceil(qnum) != floor(qnum)): raise Exception("The shifted and scaled quantum number " + str(qnum) + " computed from (" + str(p['Abelian_quantum_numbers'][ii]) + "-" + str(p['L'] * Operators['q_shifts'][jj]) + ")/" + str(Operators['q_scales'][jj]) + " " + "\nis not an integer and so does not " + "represent a valid quantum number!\n" + "If you are certain this is an error file " + "a bug report!") qnum = int(qnum) pnums.append(qnum) qstr += '%16i'%(qnum) qfilename = writedir + p['job_ID'] + p['unique_ID'] + 'qnumbers.dat' qfile = open(qfilename,'w') qfile.write(qstr + '\n') qfile.close() if(not PostProcess and ('QT' in p)): copy_files_qt(qfilename, p['QT']) p['int_files'].append(qfilename) if(not 'State' in p): # generate initial state guess appropriate for iMPS with the # desired fixed quantum numbers p['int_files'].append(GenerateqInitialState(writedir + p['job_ID'] + p['unique_ID'], qnums, pnums, p['L'], Operators)) if(not PostProcess and ('QT' in p)): copy_files_qt(p['int_files'][-1], p['QT']) else: p['int_files'].append(GenerateParityState(writedir + p['job_ID'] + p['unique_ID'], p['L'], p['N0'], p['N1'])) # Write main file # --------------- # Generate the single hash of all the parameters [1st always on # parameters, then on Abelian operators if available, finally on # Hamiltonian. hstr = generate_full_hash(hashlist) # Check if the initial state is calculated in a simulation before timeevo_initial = (len(p['timeevo_mps_initial']) == 0) \ and (len(p['timeevo_mpdo_initial']) == 0) if((hstr in MainHashList) and timeevo_initial): wait4state = True else: wait4state = False MainHashList.append(hstr) MainFileName=writedir+p['job_ID']+p['unique_ID']+'Main.nml' FinishedFileName=outdir+p['job_ID']+p['unique_ID']+'Finished.dat' if p['strict'] is True: if os.path.isfile(MainFileName): raise Exception("Duplicate Main file already exists! Delete it or turn off strict to overwrite!") if os.path.isfile(FinishedFileName): raise Exception("Duplicate final output file already exists! Move it or turn off strict to overwrite!") MainFile=open(MainFileName,'w') MainFile.write('&SystemSettings\n') MainFile.write('job_ID="'+p['job_ID']+'", ') MainFile.write('unique_ID="'+p['unique_ID']+'", ') MainFile.write('writedir="'+writedir+'", ') MainFile.write('outdir="'+outdir+'", ') MainFile.write('nqns='+str(nqns)+', ') MainFile.write('npns='+str(npns)+', ') MainFile.write('ll=%16i'%(p['L'])+', ') MainFile.write('ne=%16i'%(p['n_excited_states'])+', ') MainFile.write('verbose=%16i'%(p['verbose'])+', ') if p['MPS']: MainFile.write('DoMPS=.True., ') else: MainFile.write('DoMPS=.False., ') if p['eMPS']: MainFile.write('DoeMPS=.True., ') else: MainFile.write('DoeMPS=.False., ') if p['Dynamics']: MainFile.write('DoDynamics=.True., ') else: MainFile.write('DoDynamics=.False., ') if(p['MPDO+']): MainFile.write('mpdoplus=.True., ') else: MainFile.write('mpdoplus=.False., ') if p['simtype']=='Infinite': MainFile.write('simtype="I", ') elif p['simtype']=='Finite': MainFile.write('simtype="F", ') else: MainFile.write('simtype="T", ') if(cmplx_op): MainFile.write('cmplx_op=.True., ') else: MainFile.write('cmplx_op=.False., ') MainFile.write('paramhash="' + hstr + '", ') if(wait4state): MainFile.write('wait4state=.True., ') else: MainFile.write('wait4state=.False., ') if(hasattr(p['statics_initial'], '__call__')): MainFile.write('statics_initial="' + p['statics_initial']() + '", ') else: MainFile.write('statics_initial="' + p['statics_initial'] + '", ') MainFile.write('timeevo_mps_initial="' + p['timeevo_mps_initial'] + '", ') MainFile.write('timeevo_mpdo_initial="' + p['timeevo_mpdo_initial'] + '", ') p['timeevo_restore'] = check_restore_timeevo(p) if(p['timeevo_restore']): MainFile.write('timeevo_restore=.True.,') else: MainFile.write('timeevo_restore=.False.,') if(p['logfile']): MainFile.write('logfile=.True.,') else: MainFile.write('logfile=.False.,') if(p['hdf5']): MainFile.write('use_h5=.True.') else: MainFile.write('use_h5=.False.') MainFile.write('/\n\n') MainFile.close() if('QT' in p): copy_files_qt(MainFileName, p['QT']) return MainFileName, hstr
[docs]def generate_full_hash(hashlist): """ Generate the full hash for a simulation based on a list of hashes. **Arguments** hashlist : list of strings contains a list of hashes to be merged to one single hash identifying the variational/static search of MPS (containing the information about Hamiltonian, the couplings, convergence settings etc ...) """ hstr = "" for hashelem in hashlist: hstr += hashelem hh = hashlib.sha512() hh.update(str.encode(hstr)) hstr = hh.hexdigest()[:50] return hstr
[docs]def WriteFiles(Parameters, Operators, HamiltonianMPO, PostProcess=False, EDSim=False): """ Write out files for use by the Fortran routines. **Arguments** Parameters: single dictionary or list of dictionaries Arguments in the dictionary are `job_ID` (mandatory), `unique_ID`, `Write_Directory` (optional), `Output_Directory` (optional) `L` (system size?, mandatory), `verbose`, `MPSObservables`, `MPSConvergenceParameters` [list incomplete] Operators : dictionary dictionary containing the matrices for the operators. The keys are the names of the operators as string. HamiltonianMPO : instance of `MPO` or list of instances of `MPO` containing the Hamiltonian. If list, it has to have the same length as the `Parameters`-list. If `Parameters` is a list, `HamiltonianMPO` can still be a single instance of `MPO`. PostProcess : boolean function distinguishes between writing files for the fortran simulation (`PostProcess=False`) and running the postprocessing of an existing data set (`PostProcessing=True`). EDSim : boolean Specify that the setup is for an ED simulation. This suppresses writing temporary files similar to PostProcess=True overwriting the PostProcessing flag. **Details** The following keys exist in Parameters: +---------------------------+----------------------------------------+-----+ | Key (strings) | Description | opt | +===========================+========================================+=====+ | L | Integer value specifying the length | N | | | of the lattice for finite systems and | | | | the length of the unit cell for | | | | infinite systems | | +---------------------------+----------------------------------------+-----+ | job_ID | String specifying a simulation. For | N | | | batched simulations, ``'job_ID'`` | | | | specifies a group of simulations | | | | which are distinguished by their | | | | ``'unique_ID'``. | | +---------------------------+----------------------------------------+-----+ | simtype | Takes values of ``'Finite'`` or | N | | | ``'Infinite'`` to toggle between | | | | finite and infinite-size ground state | | | | search, see Sec. :ref:`sec_imps`. | | | | For finite temperature evolutions via | | | | imaginary time evolution choose | | | | ``'FiniteImag'``. | | | | Defaults to ``'Finite'``. | | +---------------------------+----------------------------------------+-----+ | unique_ID | String which distinguishes a | Y | | | simulation from all others with the | | | | same ``'job_ID'``. Defaults to the | | | | empty string. | | +---------------------------+----------------------------------------+-----+ | Write_Directory | Directory where files for internal | Y | | | OSMPS use are written. Defaults to | | | | current directory. | | +---------------------------+----------------------------------------+-----+ | Output_Directory | Directory where output files are | Y | | | written. Defaults to current | | | | directory. | | +---------------------------+----------------------------------------+-----+ | verbose | Integer specifying the level of code | Y | | | output, with ``0`` being the least | | | | amount of output and ``2`` the most. | | | | Defaults to ``0``. | | +---------------------------+----------------------------------------+-----+ | logfile | True writes seperate logfile, false | Y | | | information goes to standard out. | | | | Default to False. | | +---------------------------+----------------------------------------+-----+ | hdf5 | Settings for the file type of the | Y | | | observables. If false, use human | | | | readable text files. If true, use | | | | HDF5 files, which implies that the | Y | | | F90 source was compiled with HDF5. | | | | Right now, the implementation is for | | | | serial simulations / single thread | | | | and was not tested for any scenario | | | | with multiple simulations at a time, | | | | e.g., submitting two serial | | | | simulations to a cluster. MPI is | | | | disabled internally in the MPSPyLib. | | | | Only use false for iMPS. | | | | Default to False. | | +---------------------------+----------------------------------------+-----+ | strict | Defines the overwrite policy for main | Y | | | and output files. When True files are | | | | not overwritten but an error raised. | | | | Default to False. | | +---------------------------+----------------------------------------+-----+ | MPSObservables | An object of the | Y | | | :py:class:`Obs.Observables` class | | | | specifying ground state measurements, | | | | see Sec. :ref:`sec_observables`. | | | | Defaults to no measurements. | | +---------------------------+----------------------------------------+-----+ | MPSConvergenceParameters | An object of the :py:class:`tools.MPS\ | Y | | | ConvergenceParameters` class | | | | specifying the convergence criteria | | | | imposed on variational ground state | | | | search, see Sec. :ref:`sec_vmps`. | | | | Defaults to the default values of | | | | :py:class:`tools.MPSConvergence\ | | | | Parameters` (see class description) | | +---------------------------+----------------------------------------+-----+ | n_excited_states | Integer number of excited states to | Y | | | be found using eMPS, see Sec. | | | | :ref:`sec_emps`. Defaults to 0. | | +---------------------------+----------------------------------------+-----+ | eMPSConvergenceParameters | An object of the :py:class:`tools.MPS\ | Y | | | ConvergenceParameters` class | | | | specifying the convergence criteria | | | | imposed on variational excited state | | | | search, see Sec. :ref:`sec_emps`. | | | | Defaults to the default values of | | | | :py:class:`tools.MPSConvergence\ | | | | Parameters` | | | | (see class description) | | +---------------------------+----------------------------------------+-----+ | eMPSObservables | An object of the | Y | | | :py:class:`Obs.Observables` class | | | | specifying excited state measurements, | | | | see Sec. :ref:`sec_observables`. | | | | Defaults to no measurements. | | +---------------------------+----------------------------------------+-----+ | Quenches | An object of the | Y | | | :py:class:`Dynamics.QuenchList` class | | | | specifying a dynamical process, see | | | | Sec. :ref:`sec_dynamics`. | | | | Defaults to ``None``. | | +---------------------------+----------------------------------------+-----+ | DynamicsObservables | An object of the | Y | | | :py:class:`Obs.Observables` class | | | | class specifying measurements of the | | | | time-evolved state, see Sec. | | | | :ref:`sec_observables`. Defaults to | | | | no measurements. | | +---------------------------+----------------------------------------+-----+ | timeevo_restore | True : try to continue interrupted | Y | | | time evolution; False : start time | | | | evolution at t=0. | | | | Default to False. | | +---------------------------+----------------------------------------+-----+ | timeevo_mps_initial | Can specicy an input state from file | Y | | | instead of ground state. | | | | Default empty string (Ground state). | | +---------------------------+----------------------------------------+-----+ | statics_initial | Define a initial guess for the ground | Y | | | state search. If not specified, guess | | | | is generated automatically. | | +---------------------------+----------------------------------------+-----+ | timeevo_mpdo_initial | Initial guess finite-T evolution with | Y | | | symmetries or initial state for time | | | | evolution. | | +---------------------------+----------------------------------------+-----+ | Abelian_generators | A list of the keys of generators of | Y | | | Abelian symmetries to be used. See | | | | Sec. :ref:`sec_Abelian` for more on | | | | the use of Abelian symmetries. | | | | Defaults to no symmetries. | | +---------------------------+----------------------------------------+-----+ | Abelian_quantum_numbers | A list of the quantum numbers | Y | | | corresponding to the generators in | | | | ``'Abelian_generators'``. See Sec. | | | | :ref:`sec_Abelian` for more on the | | | | use of Abelian symmetries. Defaults | | | | to no quantum numbers. | | +---------------------------+----------------------------------------+-----+ | Discrete_generators | not enables yet. Raises exception | | +---------------------------+----------------------------------------+-----+ | MPDO+ | Logical, used to switch to MPDO | Y | | | algorithms in future. | | | | Default to False. | | +---------------------------+----------------------------------------+-----+ | QT | Sampling over realizations of the | Y | | | same simulation, e.g. in future used | | | | for quantum trajectories. | | | | Default: 1 simulation (not present) | | +---------------------------+----------------------------------------+-----+ The following keys are set internally: +---------------+----------------------------------------------------------+ | Key (strings) | Description | +===============+==========================================================+ | Dynamics | True if key `Quenches` available, else default is False | +---------------+----------------------------------------------------------+ | MPS | if `MPSConvergenceParameters` given, `MPS` must be True. | | | Default value is True except for the dynamics with a | | | given initial state defined through the key ``state``. | +---------------+----------------------------------------------------------+ | eMPS | if `n_excited_states` equal to 0, `MPS=True` and | | | `eMPS=False` | | | If > 0, then `MPS=True` and `eMPS=True`. If | | | `n_excited_states` not given, default to False. | +---------------+----------------------------------------------------------+ | int_files | empty list set in this function, changed during the | | | `WriteMainFile`. | +---------------+----------------------------------------------------------+ | hash | The hash of the static simulations. | +---------------+----------------------------------------------------------+ """ # Overwrite PostProcess if EDSim if(EDSim): PostProcess = True # Checks on whole lists # --------------------- if not isinstance(Parameters,list): Parameters=[Parameters] if isinstance(HamiltonianMPO,list): if len(HamiltonianMPO)!=len(Parameters): raise Exception("Length of HamiltonianMPO list does not match " + "length of Parameters list in WriteFiles!") if(isinstance(Operators, list)): # Indexing operators using unique integer keys is done afterwards raise Exception("Operators should not be a list in WriteFiles!") elif(isinstance(Operators, dict)): # Convert dictionary to `OperatorList` in order to use class methods Operators = OperatorList(Operators) ids=[] hashlist1 = [] # Check and initialize defaults for each simulation # ------------------------------------------------- ii = 0 for p in Parameters: # Checking on coherent input # -------------------------- # Check on mandatory parameters if(not 'L' in p): raise Exception("Missing L from Parameters!") if(not 'job_ID' in p): raise Exception("Please name your job (add a key job_ID to the " + "Parameters dictionary)!") if(not 'simtype' in p): raise Exception("Please provide simtype for Parameters.") # Checks around simtype if(p['simtype'] == 'Infinite'): if('MPSConvergenceParameters' in p): if(not isinstance(p['MPSConvergenceParameters'], iMPSConvParam)): raise Exception("simtype=Infinite, but not iMPSConvParam!") else: DefaultMPSConvParam = iMPSConvParam() if('n_excited_states' in p): if(p['n_excited_states'] != 0): raise Exception("Number of excited states not 0 for " + "Infinite.") if('eMPSConvergenceParameters' in p): raise Exception("Specified eMPSConvergenceParameters for " + "Infinite.") if('eMPSObservables' in p): raise Exception("Specified eMPSObservables for Infinite.") if('Quenches' in p): raise Exception("Specified Quenches for Infinite.") if('hdf5' in p): if(p['hdf5']): raise Exception('Cannot use HDF5 for iMPS.') elif(p['simtype'] == 'Finite'): if('MPSConvergenceParameters' in p): if(not isinstance(p['MPSConvergenceParameters'], (MPSConvParam, FiniteTConvParam))): raise Exception("simtype=Finite, but ConvParam not " + "recognized.") else: DefaultMPSConvParam = MPSConvParam() elif(p['simtype'] == 'FiniteImag'): if('MPSConvergenceParameters' in p): if((not isinstance(p['MPSConvergenceParameters'], FiniteTConvParam)) and (not isinstance(p['MPSConvergenceParameters'], ImagConvParam))): raise Exception("simtype=FiniteImag, but not " + "FiniteTConvParam or ImagConvParam.") else: DefaultMPSConvParam = FiniteTConvParam() if('n_excited_states' in p): if(p['n_excited_states'] != 0): raise Exception("Number of excited states not 0 for " + "FiniteImag.") if('eMPSConvergenceParameters' in p): raise Exception("Specified eMPSConvergenceParameters for " + "FiniteImag.") if('eMPSObservables' in p): raise Exception("Specified eMPSObservables for FiniteImag.") else: raise Exception("Unknown simtype!") if(('DynamicsObservables' in p) and (not 'Quenches' in p)): raise Exception("Specified dynamic observables, but no Quenches.") # Check directories and create if necessary CheckOrDefault(p,'Write_Directory','') CheckOrDefault(p,'Output_Directory','') if(not os.path.exists(p['Write_Directory'])): makedirs(p['Write_Directory']) if(not os.path.exists(p['Output_Directory'])): makedirs(p['Output_Directory']) # Take care of jobnames CheckOrDefault(p,'unique_ID','') ids.append(p['job_ID']+p['unique_ID']) #determine which algorithms are being performed #dynamics is determined by the presence of Quenches if 'Quenches' in p: p['Dynamics']=True #If an initial state is specified with dynamics, no MPS, no eMPS if 'State' in p: CheckOrDefault(p,'MPS',False) CheckOrDefault(p,'eMPS',False) else: p['Dynamics']=False if 'MPSConvergenceParameters' in p: CheckOrDefault(p,'MPS',True) if not p['MPS']: raise Exception("Inputs specify both the use and non-use of variaitonal state search!") if 'n_excited_states' in p: CheckOrDefault(p,'MPS',True) if p['n_excited_states']>0: CheckOrDefault(p,'eMPS',True) else: CheckOrDefault(p,'eMPS',False) else: CheckOrDefault(p,'eMPS',False) CheckOrDefault(p,'MPS',True) CheckOrDefault(p,'eMPS',False) #Use default MPS convergence parameters if not specified if p['MPS'] and 'MPSConvergenceParameters' not in p: p['MPSConvergenceParameters'] = DefaultMPSConvParam #Use empty MPS observables if not specified if p['MPS'] and 'MPSObservables' not in p: MPSObs=Observables(Operators) p['MPSObservables']=MPSObs #Use default eMPS convergence parameters if not specified if p['eMPS'] and 'eMPSConvergenceParameters' not in p: p['eMPSConvergenceParameters'] = MPSConvParam() #Use empty eMPS observables if not specified if p['eMPS'] and 'eMPSObservables' not in p: MPSObs=Observables(Operators) p['eMPSObservables']=MPSObs #Use empty dynamics observables if not specified if p['Dynamics'] and 'DynamicsObservables' not in p: MPSObs=Observables(Operators) p['DynamicsObservables']=MPSObs CheckOrDefault(p,'strict',False) CheckOrDefault(p,'n_excited_states',0) CheckOrDefault(p,'verbose',0) CheckOrDefault(p, 'logfile', False) CheckOrDefault(p, 'hdf5', False) CheckOrDefault(p,'Dynamics',False) if(p['Dynamics']): if(isinstance(p['Quenches'], QuantumCircuits)): pass # Could check for Lindblad operators as well elif(isinstance(p['Quenches'].data[0]['ConvergenceParameters'], MpdoplusConvParam)): p['MPDO+'] = True CheckOrDefault(p, 'MPDO+', False) CheckOrDefault(p, 'statics_initial', '') CheckOrDefault(p, 'timeevo_mps_initial', '') CheckOrDefault(p, 'timeevo_mpdo_initial', '') CheckOrDefault(p, 'timeevo_restore', False) # Checks and flags for operators and symmetries p['has_complex_op'] = Operators.has_complex CheckOrDefault(p, 'Abelian_generators', []) CheckOrDefault(p, 'Discrete_generators', []) CheckOrDefault(p, 'Abelian_quantum_numbers', []) if(not isinstance(p['Abelian_quantum_numbers'], list)): p['Abelian_quantum_numbers'] = [p['Abelian_quantum_numbers']] CheckOrDefault(p, 'Discrete_quantum_numbers', []) if(not isinstance(p['Discrete_quantum_numbers'], list)): p['Discrete_quantum_numbers'] = [p['Discrete_quantum_numbers']] # Generate the hash for the actual parameters # ------------------------------------------- # # For convenience handling: hash as well Observables, eMPS Observables # and excited states. They change not the ground state, but that way we # can be sure to reuse the measurement outcomes. hstr = "" hstr += p['job_ID'] hstr += str(p['L']) hstr += p['Write_Directory'] hstr += p['Output_Directory'] # hstr += p['unique_ID'] - never add this because it is unique!!! # p['Quenches'] - not of importance for ground/excited state # p['Dynamics'] - not of importance for ground/excited state # p['State'] !!! WHAT IS THIS !!! if('MPSConvergenceParameters' in p): hstr += p['MPSConvergenceParameters'].get_hash() # p['MPS'] hstr += str(p['n_excited_states']) # p['eMPS'] if("MPSObservables" in p): hstr += p['MPSObservables'].get_hash() if(p['eMPS']): hstr += p['eMPSConvergenceParameters'].get_hash() if(p['eMPS']): hstr += p['eMPSObservables'].get_hash() # p['DynamicsObservables'] - not of importance for ground/excited state hstr += str(p['strict']) hstr += str(p['simtype']) # p['verbose'] - not of importance for states # p['int_files'] !!! WHAT IS THIS !!! # p['Discrete_generators'] - not enabled, would be done later # p['Abelian_generators'] - done later in the second loop hstr += str(p['Abelian_quantum_numbers']) hstr += str(p['Discrete_quantum_numbers']) hstr += str(p['MPDO+']) if(hasattr(HamiltonianMPO, '__len__')): hp_list = HamiltonianMPO[ii].get_param_set() else: hp_list = HamiltonianMPO.get_param_set() for elem in sorted(hp_list): if(elem == 'one'): hstr += str(1.0) else: hstr += str(p[elem]) hh = hashlib.sha512() hh.update(str.encode(hstr)) hashlist1.append([hh.hexdigest()]) # increment ii += 1 # Check if job names are unique if len(set(ids))<len(ids): raise Exception("Make sure all job_IDs in Parameters are unique!" + " You can add a unique_ID if needs be.") # Get a list of all unique write directories. This ensures that each one # gets its own Ops etc. MainFileList=[] MainHashList = [] # Write out operators and get indexed operators # --------------------------------------------- counter=0 hacount = -1 for p in Parameters: hacount += 1 writedir=p['Write_Directory'] p['int_files']=[] Operators.add_abelian(p['Abelian_generators']) Operators.add_discrete(p['Discrete_generators']) Operators.check_symmetry(HamiltonianMPO, counter) IntOperators, Opsfilename = Operators.write(writedir + p['job_ID'] + p['unique_ID'], PostProcess) if((not PostProcess) and ('QT' in p)): copy_files_qt(Opsfilename, p['QT']) hashlist1[hacount].append(Operators.get_hash()) nqns, npns = Operators.get_nqns_npns() #if 'State' in p: # raise Exception("Initial state use not yet supported!") # if 'n_excited_states' in p: # raise Exception("Cannot specify both n_excited_states and " + # "the initial state in parameters!") # if qOperators is None: # Statefilename = p['State'].writeState(writedir + p['job_ID'] + # p['unique_ID']) # p['int_files'].append(Statefilename) # else: # Statefilename = p['State'].writeqState(writedir + p['job_ID'] + # p['unique_ID'], # qOperators) # p['int_files'].append(Statefilename) if isinstance(HamiltonianMPO,list): H=HamiltonianMPO[counter] counter+=1 else: H=HamiltonianMPO hashlist1[hacount].append(H.get_hash()) if not PostProcess: #Get indexed Hamiltonian parameters H.IndexHParams() #Check that all parameters actually exist within parameter file for i in range((len(H.IntHparam) - 1) // 2): if((not H.IntHparam[i] in p) and (H.IntHparam[i] != 'one')): raise Exception("Hamiltonian parameter " + str(H.IntHparam[i]) + " used in H not " + "found in input parameters!") #Write out hamiltonian template Hamifilename=writedir+p['job_ID']+p['unique_ID']+'Hamirules.dat' Hamifile=open(Hamifilename,'w') H.write(Hamifile,p,IntOperators, H.IntHparam) Hamifile.close() if('QT' in p): copy_files_qt(Hamifilename, p['QT']) #Write out hamiltonian parameters HPfilename=WriteHparams(writedir+p['job_ID']+p['unique_ID'],p,H.IntHparam) if('QT' in p): copy_files_qt(HPfilename, p['QT']) if 'MPSObservables' in p: ftmp = p['MPSObservables'].write(writedir + p['job_ID'] + p['unique_ID'] + '0', p, IntOperators, H.IntHparam, PostProcess) if(not PostProcess and ('QT' in p)): copy_files_qt(ftmp, p['QT']) if 'eMPSObservables' in p: ftmp = p['eMPSObservables'].write(writedir + p['job_ID'] + p['unique_ID'] + '1', p, IntOperators, H.IntHparam, PostProcess) if(not PostProcess and ('QT' in p)): copy_files_qt(ftmp, p['QT']) if not PostProcess: #MPS convergence if p['MPS']: ftmp = writedir + p['job_ID'] + p['unique_ID'] + 'MPS.dat' p['MPSConvergenceParameters'].write(ftmp) if('QT' in p): copy_files_qt(ftmp, p['QT']) #eMPS convergence if p['eMPS']: ftmp = writedir + p['job_ID'] + p['unique_ID'] + 'eMPS.dat' p['eMPSConvergenceParameters'].write(ftmp) if('QT' in p): copy_files_qt(ftmp, p['QT']) cmplx_op = Operators.has_complex MainFileName, GsHash = WriteMainFile(p, cmplx_op, hashlist1[hacount], MainHashList, nqns, npns, Operators) qtid = 0 if('QT' not in p) else p['QT'] if(qtid == 0): MainFileList.append((MainFileName, str(qtid))) else: #raise NotImplementedError('QT flag not yet enabled.') # What about WAIT4STATE flag for ii in range(qtid): MainFileList.append((MainFileName, str(ii + 1))) append_hash2map(p, GsHash) p['hash'] = GsHash else: p['hash'] = generate_full_hash(hashlist1[hacount]) if p['Dynamics']: WriteDynamics(H, p, copy_files_qt, PostProcess) if not PostProcess: ftmp = writedir + p['job_ID'] + p['unique_ID'] + 'Dynamics' p['DynamicsObservables'].write(ftmp, p, IntOperators, H.IntHparam) if('QT' in p): copy_files_qt(ftmp + 'Obs.dat', p['QT']) return MainFileList
[docs]def CleanFiles(Parameters): """ Delete all files generated for the Fortran backend. **Arguments** Parameters : list or single string tba """ if not isinstance(Parameters,list): Parameters=[Parameters] uniquefiles=[] for p in Parameters: if 'int_files' in p: uniquefiles=[i for i in set(uniquefiles+p['int_files'])] for file in uniquefiles: remove(file) return
[docs]def append_hash2map(param, Hash): """ Append a new information to the mapping of the hashed groundstates / observables. **Arguments** param : Dictionary the dictionary with all the parameters for the simulation Hash : str hash for the parameters in `param` """ # Create filename and complete id flnm = param['Output_Directory'] + param['job_ID'] + "_static_mapping.dat" cid = param['Output_Directory'] + param['job_ID'] + param['unique_ID'] # Check if the current ID already exists in the file try: fh = open(flnm, 'r') for line in fh: print(line) this_cid, this_hash = line.split() if(this_cid == cid): if(this_hash == Hash): # This is ok, id and parameters are the same fh.close() return raise ValueError("Cannot rerun simulation with different params!") fh.close() except myFileNotFoundError: # File does not exist yet pass # Append the data to the file fh = open(flnm, 'a') fh.write(cid + " " + Hash + "\n") fh.close() return
[docs]def copy_files_qt(filename, QT): """ Makes copies of a file for quantum trajectory appending the corresponding index before the file appendix. **Arguments** filename : str file to be copied. QT : int make QT copies with indices 1 to QT (including QT). """ ext = '.' + filename.split('.')[-1] for ii in range(QT): repl = '_qt%09d'%(ii + 1) + ext shutil.copyfile(filename, filename.replace(ext, repl)) remove(filename) return
[docs]class MPSFortLibError(Exception): """ Excpetion raised if the fortran library exits with an error. **Arguments __init__** ret_val : int return code of the fortran library. """ def __init__(self, ret_val): self.val = ret_val return
[docs] def __str__(self): """ String representing error message. """ return "MPSFortLib quit with fatal return code " + str(self.val) + "!"