#
from __future__ import annotations
from datetime import datetime
__author__= "Sabyasachi Tiwari"
__copyright__= "Copyright 2024, EPWpy project"
__version__= "1.0"
__maintainer__= "Sabyasachi Tiwari"
__maintainer_email__= "sabyasachi.tiwari@austin.utexas.edu"
__status__= "Production"
__date__= "May 03, 2024"
import numpy as np
import argparse
import pickle
from EPWpy import Logo as Logo
from EPWpy.default import code_loc as cd_loc
from EPWpy.default import default as df
from EPWpy.EPWpy_run import PyRun
from EPWpy.EPWpy_prepare import PyPrepare
from EPWpy.EPWpy_analysis import PyAnalysis
from EPWpy.BGW.EPWpy_BGW import PyBGW
from EPWpy.QE.EPWpy_QE import PyQE
from EPWpy.default.set_default import *
from EPWpy.structure.lattice import *
from EPWpy.error_handling.error_handler import *
from EPWpy.flow.transport import FlowManager
#from EPWpy.flow.transport import *
import os
import EPWpy.utilities
[docs]
class EPWpy(SetDefaultVals, FlowManager, PyBGW, PyQE, PyAnalysis, JobStatus, Lattice):
"""
The central interface class of the **EPWpy** framework.
EPWpy provides a unified Python interface for *ab initio* electronic-structure
and many-body codes, including **Quantum ESPRESSO (QE)**, **EPW**, and
**BerkeleyGW (BGW)**. It automates input preparation, execution, and output
parsing, enabling scalable workflows for electron–phonon and many-body
physics simulations.
This class can be initialized directly to set up and run complete workflows,
or used as a base for higher-level automated pipelines.
---------------------------------------------------------------------------
Collaborating Institutions
---------------------------------------------------------------------------
- **The University of Texas at Austin** (Prof. Feliciano Giustino)
- **Binghamton University** (Prof. Roxana Margine)
- **University of Michigan** (Prof. Emmanouil Kioupakis)
- **Université Catholique de Louvain** (Prof. Samuel Poncé)
---------------------------------------------------------------------------
Output Structure
---------------------------------------------------------------------------
All simulation results and parameters are stored internally in dictionaries.
These can be accessed via the respective attributes of QE, PH, EPW, or BGW
modules. Example:
>>> self.pw_atomic_species["atomic_species"]
Common dictionary attributes include:
- ``pw_system``: system inputs and outputs
- ``pw_control``: control inputs and outputs
- ``pw_electrons``: electron-related inputs and outputs
- ``pw_ions``: ion dynamics inputs/outputs (enabled if iondynamics=True)
- ``pw_cell``: cell dynamics inputs/outputs (enabled if celldynamics=True)
- ``pw_atomic_positions``: atomic positions
- ``pw_atomic_species``: atomic species, pseudopotentials, and masses
- ``pw_cell_parameters``: lattice vectors
- ``pw_bands``: band-structure inputs and outputs
- ``ph_params``: PHonon code parameters
- ``epw_params``: EPW parameters
- ``wannier_params``: Wannierization parameters
- ``pw2wann_params``: pw2wannier90 parameters
- ``BGW_*``: various BerkeleyGW stages (init, epsilon, sigma, kernel, etc.)
- ``zg_params``, ``q2r_params``, ``eps_inputpp``: miscellaneous utilities
---------------------------------------------------------------------------
Inherited Classes
---------------------------------------------------------------------------
- ``PyBGW``: Interface to the BerkeleyGW package
- ``PyQE``: Interface to Quantum ESPRESSO
- ``PyAnalysis``: Property extraction and analysis utilities
- ``JobStatus``: Status tracking for running jobs
- ``Lattice``: Lattice construction and pseudopotential management
- ``SetDefaultVals``: Handles default initialization values
---------------------------------------------------------------------------
Composed Classes
---------------------------------------------------------------------------
- ``PyRun``: Executes individual simulations (PW, PH, EPW, BGW)
- ``PyPrepare``: Prepares inputs and manages calculation setup
---------------------------------------------------------------------------
Parameters
---------------------------------------------------------------------------
type_input : dict, optional
Dictionary containing QE/EPW input variables for initialization.
system : str, default="si"
Folder name or system identifier for the current calculation.
code : str, optional
Path to the executable code (QE, EPW, or BGW). If ``None``, the code path
is determined from the global ``cd_loc.code_set`` variable.
env : str, default="mpirun"
Environment launcher for parallel execution. Typical values include:
``"mpirun"``, ``"srun"``, or ``"ibrun"``.
---------------------------------------------------------------------------
Attributes
---------------------------------------------------------------------------
Run : PyRun
Manages execution of QE, PH, EPW, or BGW binaries.
Prepare : PyPrepare
Handles creation of input files and directory setup.
verbosity : int
Level of console output verbosity.
state : dict
Internal EPWpy state dictionary containing runtime parameters.
writer : object
Writer utility for generating formatted input files.
transfer_file : list
Files to be transferred across stages.
dont_do : list
Steps to skip in automated flows.
run_serial : bool
Whether to enforce serial execution mode.
dolink : bool
Whether to create symbolic links between run directories.
---------------------------------------------------------------------------
Example
---------------------------------------------------------------------------
>>> from epwpy import EPWpy
>>> epw = EPWpy(system="graphene", env="ibrun", code="/path/to/pw.x")
>>> epw.Run # Access the run manager
<PyRun object>
>>> epw.Prepare # Prepare input files
<PyPrepare object>
"""
def __init__(self, type_input: dict = None, system: str = 'si',
code: str = None, env: str = 'mpirun',
QE_ver: float = 7.4, EPW_ver: float = 5.9):
"""
Initialize an EPWpy workflow object.
Parameters
----------
type_input : dict, optional
Input parameters for initialization.
system : str, default='si'
Name of the system or folder for this calculation.
code : str, optional
Path to the executable code; inferred from global settings if None.
env : str, default='mpirun'
Parallel environment command (e.g., mpirun, ibrun, srun).
QE_ver : str, default '7.4'
Quantum ESPRESSO version.
EPW_ver : str, default '5.9'
EPW version.
Returns
-------
None
"""
self.transfer_file = []
self.dont_do = []
self.epwpy_logo()
self.system = system
self.code = code
self.run_serial = True
self.dolink = False
self.QE_ver = QE_ver
self.EPW_ver = EPW_ver
if self.code is None:
if cd_loc.code_set is None:
print('No code chosen')
else:
self.code = cd_loc.code_set
self.QE_ver = float(cd_loc.QE_version)
self.EPW_ver = float(cd_loc.EPW_version)
self.env = env
os.system(f'mkdir -p {self.system}')
self.default_values()
self.set_values(type_input)
self.Run = PyRun(1, self.env, self.code)
self.Prepare = PyPrepare(self.prefix, self.pseudo, files=[])
self.verbosity = 1
self.state = {'epwpy_params': None}
self.writer = self.create_writer(type_input)
self.writer = self.get_default_writer()
self.Prepare.writer = self.writer
self.Run.writer = self.writer
[docs]
def new_system_reset(self, type_input = None, system='si', code=None, env='mpirun'):
"""
The EPWpy is a utility that wraps EPW
"""
#
self.reset()
self._hard_refresh()
#
del self.Run
del self.Prepare
self.transfer_file=[]
self.dont_do = []
self.system=system
self.code=code
self.code = code
self.run_serial = True
self.dolink = False
if self.code is None:
if code_set is None:
print('No code chosen')
else:
self.code = code_set
self.env=env
os.system('mkdir -p '+self.system)
self.default_values()
self.set_values(type_input)
self.Run = PyRun(1,self.env,self.code)
self.Prepare = PyPrepare(self.prefix,self.pseudo, files=[])
self.verbosity = 1
self.state = {'epwpy_params':None}
self.writer = self.create_writer(type_input)
self.writer = self.get_default_writer()
self.Prepare.writer = self.writer
self.Run.writer = self.writer
[docs]
def epwpy_logo(self):
"""
Print EPWpy logo
"""
Logo.plot_logo()
[docs]
def save_EPWpy(self, filename=None):
if filename is None:
filename = f"{self.system}.pkl"
with open(filename, "wb") as f:
pickle.dump(self, f)
print(f"EPWpy object saved (pickle) as {filename}")
[docs]
@classmethod
def load_EPWpy(cls, filename):
with open(filename, "rb") as f:
obj = pickle.load(f)
print(f"EPWpy object loaded (pickle) from {filename}")
return obj
[docs]
def prepare(self, procs=1, type_run='scf', name=None,
infile=None, transfer_file=[]):
"""
Prepare the environment, input files, and working directories
for a specific EPWpy calculation stage.
This method configures a new calculation (e.g., SCF, NSCF, PH, EPW, BGW)
by creating appropriate directories, linking pseudopotentials, assigning
input/output file names, and storing the state for subsequent execution.
Parameters
----------
procs : int, optional
Number of processors to allocate for the run.
Default is ``1``.
type_run : str, optional
Type of calculation to prepare. Common options include:
- ``"scf"`` : self-consistent field run
- ``"nscf"`` : non-self-consistent run
- ``"ph"`` : phonon calculation
- ``"epw"`` : electron–phonon coupling run
- ``"gw"`` : BerkeleyGW run
Default is ``"scf"``.
name : str, optional
Custom name for the run folder. If not provided, defaults to
``type_run``.
infile : str, optional
Name of the input file to be prepared. Defaults to ``"{type_run}.in"``.
transfer_file : list of str, optional
List of additional files to copy or link into the run directory.
These may include previously generated wavefunctions, dynamical
matrices, or intermediate outputs.
Returns
-------
None
The method updates the internal EPWpy state and sets up a
:class:`PyPrepare` object (`self.prep`) to handle input generation.
Notes
-----
- The method automatically initializes a :class:`PyPrepare` instance and
assigns it to ``self.prep``.
- Internal state is serialized to JSON using :meth:`_save_json`.
- Working directories and filenames are configured via helper methods:
:meth:`set_folds`, :meth:`set_files`, :meth:`set_work`, and :meth:`set_home`.
- Files listed in ``transfer_file`` are added to ``self.transfer_file`` for
future use in multi-stage workflows.
Example
-------
>>> epw = EPWpy(system="graphene", code="/path/to/pw.x")
>>> epw.prepare(type_run="nscf", procs=8)
>>> print(epw.prep.writer)
<InputWriter object for nscf run>
"""
if(len(transfer_file) != 0):
for file in transfer_file:
self.transfer_file.append(file)
if infile is None:
infile = f'{type_run}.in'
if name is None:
name = type_run
self.prep=PyPrepare(self.prefix,self.pseudo,self.transfer_file)
self.prep.dolink = self.dolink
self.set_folds(name,type_run,self.prep)
self.set_files(name,type_run,self.prep)
#if (self.write_script):
self.prep.writer = self.writer
self.set_work()
self._save_json('epwpy_save',self.state)#self.pw_system)
self.set_home()
if (type_run == 'scf'):
self.set_work()
self.prep.prepare_scf(name, infile)
self.set_home()
elif (type_run == 'custom'):
self.set_work()
self.prep.prepare_custom(name, infile)
self.set_home()
elif (type_run == 'nscf'):
self.set_work()
self.prep.prepare_nscf(name, infile)
self.set_home()
elif (type_run == 'bs'):
self.set_work()
self.prep.prepare_bs(name, infile)
self.set_home()
elif (type_run == 'ph'):
self.set_work()
self.prep.prepare_ph(name, infile)
self.set_home()
elif (type_run == 'pp'):
self.set_work()
self.prep.prepare_pp(name, infile)
self.set_home()
elif (type_run == 'epw1'):
self.set_work()
if (name == 'epw1'):
name = 'epw'
self.epw_fold = name
self.prep.prepare_epw1(name, infile)
self.set_home()
elif (type_run == 'epw2'):
self.set_work()
if (name == 'epw2'):
name = 'epw'
self.prep.prepare_epw2(name, infile)
self.set_home()
elif (type_run == 'epw3'):
self.set_work()
if (name == 'epw3'):
name = 'epw'
self.prep.prepare_epw3(name, infile)
self.set_home()
elif (type_run == 'q2r'):
self.set_work()
if (name == 'q2r'):
name = self.ph_fold
self.prep.prepare_q2r(name, infile)
self.set_home()
elif (type_run == 'dvscf_q2r'):
self.set_work()
if (name == 'dvscf_q2r'):
name = self.ph_fold
self.prep.prepare_dvscf_q2r(name, infile)
self.set_home()
elif (type_run == 'postahc'):
self.set_work()
if (name == 'postahc'):
name = self.ph_fold
self.prep.prepare_postahc(name, infile)
self.set_home()
elif (type_run == 'zg'):
self.set_work()
self.prep.prepare_zg(name, infile)
self.set_home()
elif (type_run == 'wannier'):
self.set_work()
self.prep.prepare_wannier(name, infile)
self.set_home()
elif (type_run == 'eps'):
self.set_work()
self.prep.prepare_eps()
self.set_home()
elif (type_run == 'bands'):
self.set_work()
self.prep.prepare_bands()
self.set_home()
elif (type_run == 'nscf2supercond'):
self.set_work()
self.prep.prepare_nscf2supercond()
self.set_home()
elif (type_run == 'matdyn'):
self.set_work()
self.prep.prepare_matdyn(name = self.ph_fold)
self.set_home()
elif (type_run == 'dynmat'):
self.set_work()
self.prep.prepare_dynmat(name = self.ph_fold)
self.set_home()
elif (type_run == 'phdos'):
self.set_work()
self.prep.prepare_phdos(name = self.ph_fold)
self.set_home()
elif (type_run == 'nscf_tetra'):
self.set_work()
self.prep.prepare_nscf_tetra()
self.set_home()
elif (type_run == 'dos'):
if (name == type_run):
name = self.nscf_fold
self.set_work()
self.prep.prepare_dos(name, infile)
self.set_home()
elif (type_run == 'pdos'):
if (name == type_run):
name = self.nscf_fold
self.set_work()
self.prep.prepare_pdos(name, infile)
self.set_home()
elif (type_run == 'fbw'):
self.set_work()
self.prep.prepare_fbw()
self.set_home()
elif (type_run == 'fbw_mu'):
self.set_work()
self.prep.prepare_fbw_mu()
self.set_home()
elif (type_run == 'nesting'):
self.set_work()
self.prep.prepare_nesting()
self.set_home()
elif (type_run == 'phselfen'):
self.set_work()
self.prep.prepare_phselfen()
self.set_home()
elif (type_run == 'epw_outerbands'):
self.set_work()
self.prep.prepare_outerbands()
self.set_home()
elif (type_run == ''):
self.set_home()
self.prep.prepare_script()
[docs]
def run(self, procs, type_run=None, infile=None, name=None,
parallelization=None, flow_parallelization=None, flow_procs=None,
flavor='cplx', util=None):
"""
Execute a specific EPWpy workflow stage such as QE, PH, EPW, or BGW.
This method serves as the unified **run interface** for all EPWpy-supported
codes, launching the appropriate binary (via :class:`PyRun`) with user-defined
parallel settings, environment, and input files. It can be invoked after
:meth:`prepare` or independently once inputs are in place.
Parameters
----------
procs : int
Number of processors to use for the execution.
type_run : str, optional
Type of calculation to execute. Common values include:
- ``"scf"`` : self-consistent DFT calculation (pw.x)
- ``"nscf"`` : non-self-consistent DFT calculation
- ``"ph"`` : phonon calculation (ph.x)
- ``"epw"`` : electron–phonon coupling calculation (epw.x)
- ``"bgw"`` : BerkeleyGW execution
Default is ``"scf"``.
infile : str, optional
Input file name to be used. Defaults to ``"{type_run}.in"`` if not provided.
name : str, optional
Label or directory name for this run. Defaults to ``type_run``.
parallelization : dict, optional
Dictionary specifying explicit parallelization flags (e.g.
``{"npool": 4, "ndiag": 2}``).
flow_parallelization : dict, optional
Parallelization scheme for distributed or multi-step workflows.
flow_procs : int, optional
Total number of processors assigned across a workflow stage.
flavor : str, optional
Computational flavor of the code to be executed. Default is ``"cplx"``
(complex version). Other possible values include ``"real"`` or code-specific
tags for EPW or BGW variants.
util : str, optional
Utility or executable to be run manually (e.g. ``"bands.x"``,
``"dos.x"``, ``"epsilon.x"``). Overrides ``type_run`` if specified.
Returns
-------
None
The method triggers execution of the selected binary and monitors
job completion status.
Notes
-----
- Automatically invokes :class:`PyRun` with the current environment
(``mpirun``, ``ibrun``, ``srun``).
- Can be used in conjunction with :meth:`prepare` to first set up and
then execute runs in an automated workflow.
- Parallelization flags are translated into command-line arguments for
the target code.
- Job progress and results are tracked through :class:`JobStatus`.
Example
-------
>>> epw = EPWpy(system="graphene", env="ibrun", code="/path/to/pw.x")
>>> epw.prepare(type_run="scf")
>>> epw.run(procs=8, type_run="scf")
To run an EPW calculation after SCF:
>>> epw.run(procs=16, type_run="epw", infile="epw.in")
"""
if type_run is None:
type_run = 'none'
print('No executable chosen')
if flow_parallelization is None:
flow_parallelization = []
if flow_procs is None:
flow_procs = {}
self.run_QE = PyRun(procs, self.env, self.code)
self.run_QE.serial = self.run_serial
self.procs = procs
self.run_QE.verbosity = self.verbosity
self.run_QE.proc_set = None
if parallelization is not None:
if (self.verbosity > 1):
print('parallelization chosen: ', parallelization)
self.run_QE.proc_set = parallelization
if util is not None:
print(f'Chosen utility {util}')
self.run_QE.writer = self.writer
if (self.verbosity > 2):
print(f"EPWpy.py def runner(): self.writer.script_params: {self.writer.script_params}")
if infile is None:
infile = type_run
if name is None:
try:
folder = df.def_folds[type_run]
except KeyError:
folder = None
if (folder == 'ph'):
folder = self.ph_fold
if (folder == 'scf'):
folder = self.scf_fold
if (folder == 'nscf'):
folder = self.nscf_fold
if (folder == 'epw'):
folder = self.epw_fold
if (type_run=='scf'):
self.set_work()
self.run_QE.run_scf(name=infile, folder=self.scf_fold)
self.set_home()
elif (type_run == 'custom'):
self.set_work()
self.run_QE.run_custom(name=infile, folder=name, util=util)
self.set_home()
elif (type_run == 'nscf'):
self.set_work()
self.run_QE.run_nscf(name=infile, folder=self.nscf_fold)
self.set_home()
elif (type_run == 'bs'):
self.set_work()
self.run_QE.run_bs(name=infile, folder=self.bs_fold)
self.set_home()
elif (type_run == 'ph'):
self.set_work()
self.run_QE.run_ph(name=infile, folder=self.ph_fold)
self.set_home()
elif (type_run == 'pp'):
self.set_work()
self.run_QE.run_pp(name=infile, folder=folder)
self.set_home()
elif (type_run == 'epw1'):
self.set_work()
self.run_QE.run_epw1(name=infile, folder=self.epw_fold)
self.set_home()
elif (type_run == 'epw2'):
self.set_work()
self.run_QE.run_epw2(name=infile, folder=self.epw_fold)
self.set_home()
elif (type_run == 'epw3'):
self.set_work()
self.run_QE.run_epw3(name=infile, folder=self.epw_fold)
self.set_home()
elif (type_run == 'q2r'):
self.set_work()
self.run_QE.run_q2r(name=infile, folder=self.ph_fold)
self.set_home()
elif (type_run == 'dvscf_q2r'):
self.set_work()
self.run_QE.run_dvscf_q2r(name=infile, folder=folder)
self.set_home()
elif (type_run == 'postahc'):
self.set_work()
self.run_QE.run_postahc(name=infile, folder=folder)
self.set_home()
elif (type_run == 'zg'):
self.set_work()
self.run_QE.run_zg(name=infile)
self.set_home()
elif (type_run == 'eps'):
self.set_work()
self.run_QE.run_eps(name=infile, folder=folder)
self.set_home()
elif (type_run == 'nscf_tetra'):
self.set_work()
self.run_QE.run_nscf_tetra(folder=folder)
self.set_home()
elif (type_run == 'bands'):
self.set_work()
self.run_QE.run_bands(name=infile, folder=folder)
self.set_home()
elif (type_run == 'nscf2supercond'):
self.set_work()
self.run_QE.run_nscf2supercond(name=infile, folder=folder)
self.set_home()
elif (type_run == 'matdyn'):
self.set_work()
self.run_QE.run_matdyn(name=infile, folder=folder)
self.set_home()
elif (type_run == 'dynmat'):
self.set_work()
self.run_QE.run_dynmat(name=infile, folder=folder)
self.set_home()
elif (type_run == 'phdos'):
self.set_work()
self.run_QE.run_phdos(name=infile, folder=folder)
self.set_home()
elif (type_run == 'dos'):
if name is None:
folder = self.nscf_fold
self.set_work()
self.run_QE.run_dos(name=infile, folder=folder)
self.set_home()
elif (type_run == 'pdos'):
if name is None:
folder = self.nscf_fold
self.set_work()
self.run_QE.run_pdos(name=infile, folder=folder)
self.set_home()
elif (type_run == 'fbw'):
self.set_work()
self.run_QE.run_fbw(name=infile, folder=folder)
self.set_home()
elif (type_run == 'fbw_mu'):
self.set_work()
self.run_QE.run_fbw_mu(name=infile, folder=folder)
self.set_home()
elif (type_run == 'nesting'):
self.set_work()
self.run_QE.run_nesting(name=infile, folder=folder)
self.set_home()
elif (type_run == 'phselfen'):
self.set_work()
self.run_QE.run_phselfen(name=infile, folder=folder)
self.set_home()
elif (type_run == 'epw_outerbands'):
self.set_work()
self.run_QE.run_epw_outerbands(name=infile, folder=folder)
self.set_home()
elif (type_run == 'wannier'):
self.set_work()
self.run_QE.run_wannier(name=self.prefix, folder=folder)
self.set_home()
elif (type_run == 'GW'):
self.set_work()
self.run_QE.run_nscf(folder='./GW/wfn', name='wfn')
self.run_QE.run_pw2bgw(folder='./', name=self.prefix)
self.set_home()
self.set_work()
self.run_QE.run_nscf(folder='./GW/wfnq', name='wfnq')
self.run_QE.run_pw2bgw(folder='./', name=self.prefix)
self.set_home()
self.set_work()
self.run_QE.run_nscf(folder='./GW/wfnfi', name='wfnfi')
self.run_QE.run_pw2bgw(folder='./', name=self.prefix)
self.set_home()
self.set_work()
self.run_QE.run_nscf(folder='./GW/wfnfiq', name='wfnfiq')
self.run_QE.run_pw2bgw(folder='./', name=self.prefix)
self.set_home()
elif (type_run == 'epsilon'):
self.set_work()
self.run_QE.run_epsilon(folder='./GW/epsilon', name='epsilon', flavor=flavor)
self.set_home()
elif (type_run == 'sigma'):
self.set_work()
self.run_QE.run_sigma(folder='./GW/sigma', name='sigma', flavor=flavor)
self.set_home()
elif (type_run == 'sigma2wan'):
self.set_work()
self.run_QE.run_sig2wan(folder='./GW/sigma', name='sig2wan')
self.set_home()
elif (type_run == 'kernel'):
self.set_work()
self.run_QE.run_kernel(folder='./GW/kernel', name='kernel', flavor=flavor)
self.set_home()
elif (type_run == 'absorption'):
self.set_work()
self.run_QE.run_absorption(folder='./GW/absorption', name='absorption', flavor=flavor)
self.set_home()
elif (type_run == 'transport'):
#self.fm = flow_manager(self)
self.flow_parallelization = flow_parallelization
if (len(self.flow_parallelization) == 0):
self.flow_parallelization=[None, None, None]
self.flow_procs = {}
for key in ['scf','nscf','ph','epw1','epw2']:
if (key in flow_procs.keys()):
self.flow_procs[key] = int(flow_procs[key])
else:
self.flow_procs[key] = self.procs
self.set_work()
self.transport_flow()
self.set_home()
elif (type_run == 'optics'):
#self.fm = flow_manager(self)
self.flow_parallelization = flow_parallelization
if (len(self.flow_parallelization) == 0):
self.flow_parallelization=[None, None, None]
self.flow_procs = {}
for key in ['scf','nscf','ph','epw1','epw2']:
if (key in flow_procs.keys()):
self.flow_procs[key] = int(flow_procs[key])
else:
self.flow_procs[key] = self.procs
self.set_work()
self.optics_flow()
self.set_home()