#!/usr/bin/env python3
# -*- coding: utf-8 -*-
################################################################################
#
# solid_dmft - A versatile python wrapper to perform DFT+DMFT calculations
# utilizing the TRIQS software library
#
# Copyright (C) 2018-2020, ETH Zurich
# Copyright (C) 2021, The Simons Foundation
# authors: A. Hampel, M. Merkel, and S. Beck
#
# solid_dmft 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.
#
# solid_dmft 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
# solid_dmft (in the file COPYING.txt in this directory). If not, see
# <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Contains the handling of the VASP process. It can start VASP, reactivate it,
check if the lock file is there and finally kill VASP. Needed for CSC calculations.
This functionality is contained in the simpler public functions.
"""
import os
import signal
import time
import numpy as np
import triqs.utility.mpi as mpi
from h5 import HDFArchive
from solid_dmft.dft_managers import mpi_helpers
def _fork_and_start_vasp(mpi_exe, arguments, env_vars):
"""
Forks a process from the master process that then calls mpi to start vasp.
The child process running VASP never leaves this function whereas the main
process returns the child's process id and continues. We use explicitly
os.fork as os.execve here because subprocess does not return, and as VASP
needs to keep running throughout the whole DFT+DMFT calculation that is
not a viable solution here.
Parameters
----------
mpi_exe: string, mpi command
arguments: list of string, arguments to start mpi with
env_vars: dict of string, environment variables containing PATH
Returns
-------
int: id of the VASP child process
"""
# fork process
vasp_process_id = os.fork()
if vasp_process_id == 0:
# close file descriptors, if rank0 had open files
for fd in range(3, 256):
try:
os.close(fd)
except OSError:
pass
print('\n Starting VASP now\n')
os.execve(mpi_exe, arguments, env_vars)
print('\n VASP exec failed\n')
os._exit(127)
return vasp_process_id
def _is_lock_file_present():
"""
Checks if the lock file 'vasp.lock' is there, i.e. if VASP is still working.
"""
res_bool = False
if mpi.is_master_node():
res_bool = os.path.isfile('./vasp.lock')
res_bool = mpi.bcast(res_bool)
return res_bool
[docs]
def remove_legacy_projections_suppressed():
""" Removes legacy file vasp.suppress_projs if present. """
if mpi.is_master_node():
if os.path.isfile('./vasp.suppress_projs'):
print(' solid_dmft: Removing legacy file vasp.suppress_projs', flush=True)
os.remove('./vasp.suppress_projs')
mpi.barrier()
[docs]
def run_initial_scf(number_cores, vasp_command, cluster_name):
"""
Starts the VASP child process. Takes care of initializing a clean
environment for the child process. This is needed so that VASP does not
get confused with all the standard slurm environment variables. Returns when
VASP has completed its initial scf cycle.
Parameters
----------
number_cores: int, the number of cores that vasp runs on
vasp_command: string, the command to start vasp
cluster_name: string, name of the cluster so that settings can be tailored to it
"""
# Removes STOPCAR
if mpi.is_master_node() and os.path.isfile('STOPCAR'):
os.remove('STOPCAR')
mpi.barrier()
# get MPI env
vasp_process_id = 0
hostfile = mpi_helpers.create_hostfile(number_cores, cluster_name)
if mpi.is_master_node():
# clean environment
env_vars = {}
for var_name in ['PATH', 'LD_LIBRARY_PATH', 'SHELL', 'PWD', 'HOME',
'OMP_NUM_THREADS', 'OMPI_MCA_btl_vader_single_copy_mechanism']:
var = os.getenv(var_name)
if var:
env_vars[var_name] = var
# assuming that mpirun points to the correct mpi env
mpi_exe = mpi_helpers.find_path_to_mpi_command(env_vars, 'mpirun')
arguments = mpi_helpers.get_mpi_arguments(cluster_name, mpi_exe, number_cores, vasp_command, hostfile)
vasp_process_id = _fork_and_start_vasp(mpi_exe, arguments, env_vars)
mpi_helpers.poll_barrier(mpi.MPI.COMM_WORLD)
vasp_process_id = mpi.bcast(vasp_process_id)
# Waits for VASP to start
while not _is_lock_file_present():
time.sleep(1)
mpi.barrier()
# Waits for VASP to finish
while _is_lock_file_present():
time.sleep(1)
mpi.barrier()
return vasp_process_id
[docs]
def run_charge_update():
"""
Performs one step of the charge update with VASP by creating the vasp.lock
file and then waiting until it gets delete by VASP when it has finished.
"""
if mpi.is_master_node():
open('./vasp.lock', 'a').close()
mpi.barrier()
# Waits for VASP to finish
while _is_lock_file_present():
time.sleep(1)
mpi.barrier()
[docs]
def read_dft_energy():
"""
Reads DFT energy from the last line of Vasp's vasptriqs.h5 or from OSZICAR.
"""
if os.path.isfile('vasptriqs.h5'):
with HDFArchive('vasptriqs.h5', 'r') as h5:
dft_energy = h5['triqs/etotal']
else:
with open('OSZICAR', 'r') as file:
nextline = file.readline()
while nextline.strip():
line = nextline
nextline = file.readline()
dft_energy = float(line.split()[2])
return dft_energy
[docs]
def read_irred_kpoints(kpts):
""" Reads the indices of the irreducible k-points from the OUTCAR. """
def read_outcar(file):
has_started_reading = False
for line in file:
if 'IBZKPT_HF' in line:
has_started_reading = True
continue
if not has_started_reading:
continue
if 't-inv' in line:
yield line
continue
if '-'*10 in line:
break
irred_indices = None
if mpi.is_master_node():
with open('OUTCAR', 'r') as file:
outcar_data_raw = np.loadtxt(read_outcar(file), usecols=[0, 1, 2, 4])
outcar_kpoints = outcar_data_raw[:, :3]
outcar_indices = (outcar_data_raw[:, 3]-.5).astype(int)
assert np.allclose(outcar_kpoints, kpts)
symmetry_mapping = np.full(outcar_kpoints.shape[0], -1, dtype=int)
for i, (kpt_outcar, outcar_index) in enumerate(zip(outcar_kpoints, outcar_indices)):
for j, kpt in enumerate(kpts):
if np.allclose(kpt_outcar, kpt):
# Symmetry-irreducible k points
if i == outcar_index:
symmetry_mapping[j] = outcar_index
# Symmetry-reducible
else:
symmetry_mapping[j] = outcar_index
break
# Asserts that loop left through break, i.e. a pair was found
assert np.allclose(kpt_outcar, kpt)
irreds, irred_indices = np.unique(symmetry_mapping, return_index=True)
assert np.all(np.diff(irreds) == 1)
assert np.all(symmetry_mapping >= 0)
return mpi.bcast(irred_indices)
[docs]
def kill(vasp_process_id):
""" Kills the VASP process. """
# TODO: if we kill the process in the next step, does it make a difference if we write the STOPCAR
with open('STOPCAR', 'wt') as f_stop:
f_stop.write('LABORT = .TRUE.\n')
os.kill(vasp_process_id, signal.SIGTERM)
mpi.MPI.COMM_WORLD.Abort(1)