"""Module to provide the Interface base class, required for each interface.
Each interface is required to inherite from this class, to be able to be
used for an autocas calculation.
"""
# -*- coding: utf-8 -*-
__copyright__ = """This code is licensed under the 3-clause BSD license.
Copyright ETH Zurich, Department of Chemistry and Applied Biosciences, Reiher Group.
See LICENSE.txt for details.
"""
import os
from enum import Enum
from typing import Any, Dict, List, Optional, Tuple, Union
import numpy as np
from scine_autocas.autocas_utils.molecule import Molecule
[docs]class Interface:
"""The base class for all Interfaces.
An Interface is an interface to a corresponding program, which
creates inputs, runs the program, reads outputs and converts these
into autoCAS compatible containers.
Attributes
----------
settings : Settings
handles settings of an interface and defines required settings
dumper : Union[Dumper, Any]
provides the ability to dump calculation
"""
[docs] class Settings:
"""The settings class for all Interfaces.
An instance of Settings controls the input parameters for the
underlying electronic structure program.
Attributes
----------
basis_set : str, default = "cc-pvdz"
basis set for a calculation
spin_multiplicity : int
the spin multiplicity of the molecule
charge : int
the total charge of the molecule
dmrg_sweeps : int
the number of dmrg sweeps
dmrg_bond_dimension : int
the dmrg bond dimension
xyz_file : str
path to the xyz file
method : str
the active space method to use
post_cas_method : str
the post cas method to use
work_dir : str
the work dir of the electronic structure program
Notes
-----
The string for method and post_cas_method are stripped from characters,
to prevent misspellings. Removed characters are:
" ", "-", "_", "/", "."
Additionally the string are cast to uppercase, to further prevent misspellings.
"""
[docs] class Methods(Enum):
"""Enum class for all methods.
This class is a base class for CasMethods and
PostCasMethods.
"""
[docs] @classmethod
def has(cls, key: str) -> str:
"""Check if method is part of class.
A method is part of a class, if it is defined in the corresponding enum.
This function also stripps the input key from some special characters
and casts it to uppercase to prevent misspellings.
Parameters
----------
key : str
method string
Returns
-------
str
if method is in enum class, return the key, else an empty string
"""
chars_to_remove = set([" ", "-", "_", "/", "."])
if key is not None:
key = key.upper()
key = "".join([c for c in key if c not in chars_to_remove])
if key not in cls.__members__:
key = ""
return key
[docs] @classmethod
def key_value(cls, key: str) -> Union[int, bool]:
"""Return the value of the method if key exists in enum.
Parameters
----------
key : str
method string
Returns
-------
Union[int, bool]
The value if method is in enum class, False otherwise
"""
key = cls.has(key)
for method in cls:
if str(method)[11:] == key:
return method.value
if str(method)[15:] == key:
return method.value
return False
[docs] class CasMethods(Methods):
"""Stores all possible CAS methods.
Even numbers correspond to methods with orbital
optimization, e.g. DMRGSCF and CASSCF and odd numbers to
methods without orbital optimization like DMRGCI and CASCI.
Numbers lower than 100 correspond to CI methods, e.g. CASCI
and CASSCF and higher numbers to DMRG methods like DMRGCI
and DMRGSCF.
"""
CASCI = 1
CASSCF = 2
DMRGCI = 101
DMRGSCF = 102
SRDFTLRDMRGCI = 1001
SRDFTLRDMRGSCF = 1002
[docs] class PostCasMethods(Methods):
"""Store all possible post-CAS methods."""
CASPT2 = 1
NEVPT2 = 2
# settings
__slots__ = (
"basis_set",
"spin_multiplicity",
"charge",
"dmrg_sweeps",
"dmrg_bond_dimension",
"xyz_file",
"method",
"post_cas_method",
"work_dir",
"n_excited_states"
)
[docs] def __init__(self, molecules: List[Molecule], settings_dict: Optional[Dict[str, Any]] = None):
"""Construct a settings object.
This is just the constructor of the base class. To use it, it needs to be called by
super().__init__(molecules=molecules, settings_dict=settings_dict)
Parameters
----------
molecule : Molecule
the molecule object provides all required information of the molecular system.
settings_dict : Dict[str, Any], optional
a dict, usually provided by the input_handler, which stores attributes and corresponding values
See Also
--------
settings_dict : InputHandler
"""
self.basis_set: str = "cc-pvdz"
"""basis set for the calculation"""
self.spin_multiplicity: int = molecules[0].spin_multiplicity
"""spin multiplicity of the system"""
self.charge: int = molecules[0].charge
"""total charge of the system"""
self.dmrg_sweeps: int = 5
"""number of sweeps in a DMRG calculation"""
self.dmrg_bond_dimension: int = 250
"""maximal bond dimension (m) in a DMRG calculation"""
self.xyz_file: str = ""
"""path to the XYZ file"""
self.method: str = "dmrg_ci"
"""defines the method used to evaluate the initial active space"""
self.post_cas_method: str = ""
"""defines post-CAS methods like CASPT2 and NEVPT2"""
self.work_dir: str = os.getcwd()
"""defines the working directory where produced data is stored"""
self.n_excited_states: int = 0
"""the number of excited states to calculate"""
if settings_dict:
self.apply_settings(settings_dict)
[docs] def apply_settings(self, settings: Dict[str, Any]):
"""Apply settings from a dict.
The dict can come from an Inputhandler instance.
Parameters
----------
settings: Dict[str, Any]
stores class attributes as string and the corresponding values
"""
for key in settings:
if hasattr(self, key):
setattr(self, key, settings[key])
# interface
[docs] def __init__(self, molecules: List[Molecule], settings_dict: Optional[Dict[str, Any]] = None):
"""Construct Interface from a molecule.
This is just the constructor of the base class. To use it, it
needs to be called by
super().__init__(molecules=molecules, settings_dict=settings_dict)
Parameters
----------
dumper: Dumper, optional
if a dumper is provided in the main class it can be used
settings: Settings
the settings of the interface.
"""
self.dumper: Optional[Any] = None
"""if a dumper is provided in the main class it can be used"""
self.settings: Interface.Settings
"""contains information on the input of the electronic structure program"""
if settings_dict:
self.settings = self.Settings(
molecules=molecules, settings_dict=settings_dict["settings"]
)
else:
self.settings = self.Settings(molecules=molecules)
[docs] def calculate(
self, cas_occupation: Optional[List[int]] = None, cas_indices: Optional[List[int]] = None
) -> Tuple[Union[float, np.ndarray], Union[np.ndarray, List[np.ndarray]],
Union[np.ndarray, List[np.ndarray]], Union[np.ndarray, List[np.ndarray]]]:
"""DMRG calculations including orbitals corresponding to cas_indices
and electrons corresponding to occupations.
Parameters
----------
cas_occupations: List[int], optional
contains the occupation for each spatial orbital. 2: doubly occupied, 1: singly occupied, 0: virtual
cas_indices: List[int], optional
contains the indices of the orbitals for the CAS calculation
Notes
-----
Either cas_occupations and cas_indices is provided and a CAS calculation is started,
or if none are provided a plain HF calculation is started.
This function must be implemented by the corresponding interface.
"""
raise NotImplementedError
[docs] def resource_estimate(self):
"""Estimate resources for the calculation."""
return NotImplementedError
[docs] def get_orbital_map(self):
"""
Getter for an orbital map in terms of orbital groups, e.g,
[
[[3, 4, 5], [3, 4, 6]],
[[6], [5]],
[[7], [7]],
...
]
This list means that the orbitals 3, 4, and 5 of the first system are mapped to the orbitals 3, 4, and 6 of
the second system. The orbital 6 of system 1 is mapped to orbital 5 of system 2, and the orbital 7 of system
1 is mapped to the orbital 7 of system 2.
Returns:
--------
The list of orbital groups / the orbital mpa.
"""
return NotImplementedError