"""Condensed C-DFT features from ``Multiwfn``."""
from typing import Optional, Tuple, Union
from bonafide.features.multiwfn_fukui import (
Multiwfn3DAtomCdftCondensedFukuiDual,
Multiwfn3DAtomCdftCondensedFukuiMinus,
Multiwfn3DAtomCdftCondensedFukuiPlus,
Multiwfn3DAtomCdftCondensedFukuiZero,
)
from bonafide.utils.base_featurizer import BaseFeaturizer
from bonafide.utils.cdft_redox_mixin import CdftLocalRedoxMixin
from bonafide.utils.constants import PROGRAM_ENVIRONMENT_VARIABLES
from bonafide.utils.global_properties import (
calculate_global_cdft_descriptors_fmo,
get_fmo_energies_multiwfn,
)
[docs]
class _Multiwfn3DAtomCdftLocal(BaseFeaturizer, CdftLocalRedoxMixin):
"""Parent feature factory for the 3D atom C-DFT Multiwfn features.
For details, please refer to the Multiwfn manual (http://sobereva.com/multiwfn/, last accessed
on 12.09.2025).
"""
iterable_option: str
def __init__(self) -> None:
self.extraction_mode = "single"
super().__init__()
[docs]
def _check_electronic_structure_data(
self, el_struc_data: Optional[str], data_name: str
) -> Optional[str]:
"""Check if the required electronic structure data is available.
Parameters
----------
el_struc_data : Optional[str]
The electronic structure data to check. It is either ``None`` (not available) or the
path to the electronic structure data file.
data_name : str
The identification string of the electronic structure data to check (used for logging).
Returns
-------
Optional[str]
The error message if the required electronic structure data is not available, ``None``
if the required electronic structure data is available.
"""
_errmsg = None
if el_struc_data is None:
_errmsg = (
f"for requesting data from '{self.__class__.__name__}', electronic structure "
f"data for the {data_name} is required but is not available. Attach precomputed "
"electronic structure data or calculate it from scratch"
)
return _errmsg
[docs]
def _calculate_global_descriptors_fmo(self) -> Optional[str]:
"""Calculate molecule-level descriptors from the electronic structure data, that is from
the frontier molecular orbital energies (HOMO and LUMO).
The included descriptors are:
* HOMO energy
* LUMO energy
* HOMO-LUMO gap
* Chemical potential
* Hardness
* Softness
* Electrophilicity
* Nucleophilicity
Returns
-------
Optional[str]
The error messages of the subroutines for calculating the global descriptors. ``None``
if everything worked as expected or if the data is already in the global feature cache.
"""
# Check if the global descriptors have already been calculated
if "multiwfn3D-global-homo_energy" in self.global_feature_cache[self.conformer_idx]:
return None
# Get relevant environment variables
environment_variables = {
var: getattr(self, var, None) for var in PROGRAM_ENVIRONMENT_VARIABLES["multiwfn"]
}
# Get the frontier molecular orbital energies
assert self.electronic_struc_n is not None # for type checker
assert self.multiplicity is not None # for type checker
homo_energy, lumo_energy, error_message = get_fmo_energies_multiwfn(
input_file_path=self.electronic_struc_n,
output_file_name=f"Multiwfn3DFmoEnergies__{self.conformer_name}",
multiplicity=self.multiplicity,
environment_variables=environment_variables,
namespace=self.conformer_name[::-1].split("__", 1)[-1][::-1],
)
if error_message is not None:
return error_message
# Get the C-DFT descriptors
assert homo_energy is not None # for type checker
assert lumo_energy is not None # for type checker
(
error_message,
homo_lumo_gap,
chem_potential,
hardness,
softness,
electrophilicity,
nucleophilicity,
) = calculate_global_cdft_descriptors_fmo(homo_energy=homo_energy, lumo_energy=lumo_energy)
if error_message is not None:
return error_message
# Write the data to the global feature cache
self.global_feature_cache[self.conformer_idx]["multiwfn3D-global-homo_energy"] = homo_energy
self.global_feature_cache[self.conformer_idx]["multiwfn3D-global-lumo_energy"] = lumo_energy
self.global_feature_cache[self.conformer_idx]["multiwfn3D-global-homo_lumo_gap"] = (
homo_lumo_gap
)
self.global_feature_cache[self.conformer_idx]["multiwfn3D-global-chem_potential_fmo"] = (
chem_potential
)
self.global_feature_cache[self.conformer_idx]["multiwfn3D-global-hardness_fmo"] = hardness
self.global_feature_cache[self.conformer_idx]["multiwfn3D-global-softness_fmo"] = softness
self.global_feature_cache[self.conformer_idx]["multiwfn3D-global-electrophilicity_fmo"] = (
electrophilicity
)
self.global_feature_cache[self.conformer_idx]["multiwfn3D-global-nucleophilicity_fmo"] = (
nucleophilicity
)
return None
[docs]
def _wrap_fukui_dual(self) -> Tuple[Optional[Union[int, float, bool, str]], Optional[str]]:
"""Calculate the condensed dual descriptor with Multiwfn.
Returns
-------
Tuple[Optional[Union[int, float, bool, str]], Optional[str]]
The condensed dual descriptor and an error message, which is ``None`` if everything
worked as expected.
"""
fukui_dual = None
error_message = None
# Check if all electronic structure data is available (both redox states needed)
error_message = self._check_electronic_structure_data(
el_struc_data=self.electronic_struc_n_plus1,
data_name="actual molecule plus one electron",
)
if error_message is not None:
return fukui_dual, error_message
error_message = self._check_electronic_structure_data(
el_struc_data=self.electronic_struc_n_minus1,
data_name="actual molecule minus one electron",
)
if error_message is not None:
return fukui_dual, error_message
# Define actual name of the final feature including the iterable option
actual_feature_name = f"{self.feature_name}__{self.iterable_option}"
# Initialize the calculation of the dual descriptor. In case it was already calculated
# in a previous feature calculation, it is automatically fetched from the cache.
calc = Multiwfn3DAtomCdftCondensedFukuiDual()
# Temporarily set the feature name to calculate the Fukui minus value
params = self.__dict__
params["feature_name"] = "multiwfn3D-atom-cdft_condensed_fukui_dual"
# Get the Fukui minus value
fukui_dual, error_message = calc(**params)
# Reset the feature name back to the actual feature name
self.feature_name = actual_feature_name
return fukui_dual, error_message
[docs]
def _wrap_fukui_minus(self) -> Tuple[Optional[Union[int, float, bool, str]], Optional[str]]:
"""Calculate the condensed Fukui minus coefficient with Multiwfn.
Returns
-------
Tuple[Optional[Union[int, float, bool, str]], Optional[str]]
The condensed Fukui minus coefficient and an error message, which is ``None`` if
everything worked as expected.
"""
fukui_minus = None
error_message = None
# Check if all electronic structure data is available
error_message = self._check_electronic_structure_data(
el_struc_data=self.electronic_struc_n_minus1,
data_name="actual molecule minus one electron",
)
if error_message is not None:
return fukui_minus, error_message
# Define actual name of the final feature including the iterable option
actual_feature_name = f"{self.feature_name}__{self.iterable_option}"
# Initialize the calculation of the Fukui minus value. In case it was already calculated
# in a previous feature calculation, it is automatically fetched from the cache.
calc = Multiwfn3DAtomCdftCondensedFukuiMinus()
# Temporarily set the feature name to calculate the Fukui minus value
params = self.__dict__
params["feature_name"] = "multiwfn3D-atom-cdft_condensed_fukui_minus"
# Get the Fukui minus value
fukui_minus, error_message = calc(**params)
# Reset the feature name back to the actual feature name
self.feature_name = actual_feature_name
return fukui_minus, error_message
[docs]
def _wrap_fukui_plus(self) -> Tuple[Optional[Union[int, float, bool, str]], Optional[str]]:
"""Calculate the condensed Fukui plus coefficient with Multiwfn.
Returns
-------
Tuple[Optional[Union[int, float, bool, str]], Optional[str]]
The condensed Fukui plus coefficient and an error message, which is ``None`` if
everything worked as expected.
"""
fukui_plus = None
error_message = None
# Check if all electronic structure data is available
error_message = self._check_electronic_structure_data(
el_struc_data=self.electronic_struc_n_plus1,
data_name="actual molecule plus one electron",
)
if error_message is not None:
return fukui_plus, error_message
# Define actual name of the final feature including the iterable option
actual_feature_name = f"{self.feature_name}__{self.iterable_option}"
# Initialize the calculation of the Fukui plus value. In case it was already calculated
# in a previous feature calculation, it is automatically fetched from the cache.
calc = Multiwfn3DAtomCdftCondensedFukuiPlus()
# Temporarily set the feature name to calculate the Fukui plus value
params = self.__dict__
params["feature_name"] = "multiwfn3D-atom-cdft_condensed_fukui_plus"
# Get the Fukui plus value
fukui_plus, error_message = calc(**params)
# Reset the feature name back to the actual feature name
self.feature_name = actual_feature_name
return fukui_plus, error_message
[docs]
def _wrap_fukui_zero(self) -> Tuple[Optional[Union[int, float, bool, str]], Optional[str]]:
"""Calculate the condensed Fukui zero coefficient with Multiwfn.
Returns
-------
Tuple[Optional[Union[int, float, bool, str]], Optional[str]]
The condensed Fukui zero coefficient and an error message, which is ``None`` if
everything worked as expected.
"""
fukui_zero = None
error_message = None
# Check if all electronic structure data is available (both redox states needed)
error_message = self._check_electronic_structure_data(
el_struc_data=self.electronic_struc_n_plus1,
data_name="actual molecule plus one electron",
)
if error_message is not None:
return fukui_zero, error_message
error_message = self._check_electronic_structure_data(
el_struc_data=self.electronic_struc_n_minus1,
data_name="actual molecule minus one electron",
)
if error_message is not None:
return fukui_zero, error_message
# Define actual name of the final feature including the iterable option
actual_feature_name = f"{self.feature_name}__{self.iterable_option}"
# Initialize the calculation of the Fukui zero value. In case it was already calculated
# in a previous feature calculation, it is automatically fetched from the cache.
calc = Multiwfn3DAtomCdftCondensedFukuiZero()
# Temporarily set the feature name to calculate the Fukui zero value
params = self.__dict__
params["feature_name"] = "multiwfn3D-atom-cdft_condensed_fukui_zero"
# Get the Fukui zero value
fukui_zero, error_message = calc(**params)
# Reset the feature name back to the actual feature name
self.feature_name = actual_feature_name
return fukui_zero, error_message
[docs]
class Multiwfn3DAtomCdftLocalElectrophilicityFmo(_Multiwfn3DAtomCdftLocal):
"""Feature factory for the 3D atom feature "cdft_local_electrophilicity_fmo", calculated
with multiwfn.
The index of this feature is 208 (see the ``list_atom_features()`` and
``list_bond_features()`` method). The corresponding configuration settings can be found
under "multiwfn.cdft" in the _feature_config.toml file.
"""
def __init__(self) -> None:
super().__init__()
[docs]
def calculate(self) -> None:
"""Calculate the ``multiwfn3D-atom-cdft_local_electrophilicity_fmo`` feature."""
# Calculate the Fukui plus value which is required for the calculation of this feature
fukui_plus, error_message = self._wrap_fukui_plus()
if error_message is not None:
self._err = error_message
return
# Calculate global descriptors based on frontier molecular orbitals and write them to the
# global feature cache
error_message = self._calculate_global_descriptors_fmo()
if error_message is not None:
self._err = error_message
return
# Calculate the desired value and write it to the results dictionary
global_electrophilicity = self.global_feature_cache[self.conformer_idx][
"multiwfn3D-global-electrophilicity_fmo"
]
assert isinstance(global_electrophilicity, (int, float)) # for type checker
assert isinstance(fukui_plus, (int, float)) # for type checker
local_electrophilicity = round(number=global_electrophilicity * fukui_plus, ndigits=6)
self.results[self.atom_bond_idx][self.feature_name] = local_electrophilicity
[docs]
class Multiwfn3DAtomCdftLocalElectrophilicityRedox(_Multiwfn3DAtomCdftLocal):
"""Feature factory for the 3D atom feature "cdft_local_electrophilicity_redox", calculated
with multiwfn.
The index of this feature is 209 (see the ``list_atom_features()`` and
``list_bond_features()`` method). The corresponding configuration settings can be found
under "multiwfn.cdft" in the _feature_config.toml file.
"""
def __init__(self) -> None:
super().__init__()
[docs]
def calculate(self) -> None:
"""Calculate the ``multiwfn3D-atom-cdft_local_electrophilicity_redox`` feature."""
# Check if energies of all three redox states are available
error_message = self._check_energy_data()
if error_message is not None:
self._err = error_message
return
# Calculate the Fukui plus value which is required for the calculation of this feature
fukui_plus, error_message = self._wrap_fukui_plus()
if error_message is not None:
self._err = error_message
return
# Calculate global descriptors based on ionization potential and electron affinity and
# write them to the global feature cache
error_message = self._calculate_global_descriptors_redox()
if error_message is not None:
self._err = error_message
return
# Calculate the desired value and write it to the results dictionary
global_electrophilicity = self.global_feature_cache[self.conformer_idx][
"global-electrophilicity_redox"
]
assert isinstance(global_electrophilicity, (int, float)) # for type checker
assert isinstance(fukui_plus, (int, float)) # for type checker
local_electrophilicity = round(number=global_electrophilicity * fukui_plus, ndigits=6)
self.results[self.atom_bond_idx][self.feature_name] = local_electrophilicity
[docs]
class Multiwfn3DAtomCdftLocalHardnessMinusFmo(_Multiwfn3DAtomCdftLocal):
"""Feature factory for the 3D atom feature "cdft_local_hardness_minus_fmo", calculated with
multiwfn.
The index of this feature is 210 (see the ``list_atom_features()`` and
``list_bond_features()`` method). The corresponding configuration settings can be found
under "multiwfn.cdft" in the _feature_config.toml file.
"""
def __init__(self) -> None:
super().__init__()
[docs]
def calculate(self) -> None:
"""Calculate the ``multiwfn3D-atom-cdft_local_hardness_minus_fmo`` feature."""
# Calculate the Fukui minus value which is required for the calculation of this feature
fukui_minus, error_message = self._wrap_fukui_minus()
if error_message is not None:
self._err = error_message
return
# Calculate global descriptors based on frontier molecular orbitals and write them to the
# global feature cache
error_message = self._calculate_global_descriptors_fmo()
if error_message is not None:
self._err = error_message
return
# Calculate the desired value and write it to the results dictionary
global_hardness = self.global_feature_cache[self.conformer_idx][
"multiwfn3D-global-hardness_fmo"
]
assert isinstance(global_hardness, (int, float)) # for type checker
assert isinstance(fukui_minus, (int, float)) # for type checker
local_hardness_minus = round(number=global_hardness * fukui_minus, ndigits=6)
self.results[self.atom_bond_idx][self.feature_name] = local_hardness_minus
[docs]
class Multiwfn3DAtomCdftLocalHardnessMinusRedox(_Multiwfn3DAtomCdftLocal):
"""Feature factory for the 3D atom feature "cdft_local_hardness_minus_redox", calculated
with multiwfn.
The index of this feature is 211 (see the ``list_atom_features()`` and
``list_bond_features()`` method). The corresponding configuration settings can be found
under "multiwfn.cdft" in the _feature_config.toml file.
"""
def __init__(self) -> None:
super().__init__()
[docs]
def calculate(self) -> None:
"""Calculate the ``multiwfn3D-atom-cdft_local_hardness_minus_redox`` feature."""
# Check if energies of all three redox states are available
error_message = self._check_energy_data()
if error_message is not None:
self._err = error_message
return
# Calculate the Fukui minus value which is required for the calculation of this feature
fukui_minus, error_message = self._wrap_fukui_minus()
if error_message is not None:
self._err = error_message
return
# Calculate global descriptors based on ionization potential and electron affinity and
# write them to the global feature cache
error_message = self._calculate_global_descriptors_redox()
if error_message is not None:
self._err = error_message
return
# Calculate the desired value and write it to the results dictionary
global_hardness = self.global_feature_cache[self.conformer_idx]["global-hardness_redox"]
assert isinstance(global_hardness, (int, float)) # for type checker
assert isinstance(fukui_minus, (int, float)) # for type checker
local_hardness_minus = round(number=global_hardness * fukui_minus, ndigits=6)
self.results[self.atom_bond_idx][self.feature_name] = local_hardness_minus
[docs]
class Multiwfn3DAtomCdftLocalHardnessPlusFmo(_Multiwfn3DAtomCdftLocal):
"""Feature factory for the 3D atom feature "cdft_local_hardness_plus_fmo", calculated with
multiwfn.
The index of this feature is 212 (see the ``list_atom_features()`` and
``list_bond_features()`` method). The corresponding configuration settings can be found
under "multiwfn.cdft" in the _feature_config.toml file.
"""
def __init__(self) -> None:
super().__init__()
[docs]
def calculate(self) -> None:
"""Calculate the ``multiwfn3D-atom-cdft_local_hardness_plus_fmo`` feature."""
# Calculate the Fukui plus value which is required for the calculation of this feature
fukui_plus, error_message = self._wrap_fukui_plus()
if error_message is not None:
self._err = error_message
return
# Calculate global descriptors based on frontier molecular orbitals and write them to the
# global feature cache
error_message = self._calculate_global_descriptors_fmo()
if error_message is not None:
self._err = error_message
return
# Calculate the desired value and write it to the results dictionary
global_hardness = self.global_feature_cache[self.conformer_idx][
"multiwfn3D-global-hardness_fmo"
]
assert isinstance(global_hardness, (int, float)) # for type checker
assert isinstance(fukui_plus, (int, float)) # for type checker
local_hardness_plus = round(number=global_hardness * fukui_plus, ndigits=6)
self.results[self.atom_bond_idx][self.feature_name] = local_hardness_plus
[docs]
class Multiwfn3DAtomCdftLocalHardnessPlusRedox(_Multiwfn3DAtomCdftLocal):
"""Feature factory for the 3D atom feature "cdft_local_hardness_plus_redox", calculated with
multiwfn.
The index of this feature is 213 (see the ``list_atom_features()`` and
``list_bond_features()`` method). The corresponding configuration settings can be found
under "multiwfn.cdft" in the _feature_config.toml file.
"""
def __init__(self) -> None:
super().__init__()
[docs]
def calculate(self) -> None:
"""Calculate the ``multiwfn3D-atom-cdft_local_hardness_plus_redox`` feature."""
# Check if energies of all three redox states are available
error_message = self._check_energy_data()
if error_message is not None:
self._err = error_message
return
# Calculate the Fukui plus value which is required for the calculation of this feature
fukui_plus, error_message = self._wrap_fukui_plus()
if error_message is not None:
self._err = error_message
return
# Calculate global descriptors based on ionization potential and electron affinity and
# write them to the global feature cache
error_message = self._calculate_global_descriptors_redox()
if error_message is not None:
self._err = error_message
return
# Calculate the desired value and write it to the results dictionary
global_hardness = self.global_feature_cache[self.conformer_idx]["global-hardness_redox"]
assert isinstance(global_hardness, (int, float)) # for type checker
assert isinstance(fukui_plus, (int, float)) # for type checker
local_hardness_plus = round(number=global_hardness * fukui_plus, ndigits=6)
self.results[self.atom_bond_idx][self.feature_name] = local_hardness_plus
[docs]
class Multiwfn3DAtomCdftLocalHardnessZeroFmo(_Multiwfn3DAtomCdftLocal):
"""Feature factory for the 3D atom feature "cdft_local_hardness_zero_fmo", calculated with
multiwfn.
The index of this feature is 214 (see the ``list_atom_features()`` and
``list_bond_features()`` method). The corresponding configuration settings can be found
under "multiwfn.cdft" in the _feature_config.toml file.
"""
def __init__(self) -> None:
super().__init__()
[docs]
def calculate(self) -> None:
"""Calculate the ``multiwfn3D-atom-cdft_local_hardness_zero_fmo`` feature."""
# Calculate the Fukui zero value which is required for the calculation of this feature
fukui_zero, error_message = self._wrap_fukui_zero()
if error_message is not None:
self._err = error_message
return
# Calculate global descriptors based on frontier molecular orbitals and write them to the
# global feature cache
error_message = self._calculate_global_descriptors_fmo()
if error_message is not None:
self._err = error_message
return
# Calculate the desired value and write it to the results dictionary
global_hardness = self.global_feature_cache[self.conformer_idx][
"multiwfn3D-global-hardness_fmo"
]
assert isinstance(global_hardness, (int, float)) # for type checker
assert isinstance(fukui_zero, (int, float)) # for type checker
local_hardness_zero = round(number=global_hardness * fukui_zero, ndigits=6)
self.results[self.atom_bond_idx][self.feature_name] = local_hardness_zero
[docs]
class Multiwfn3DAtomCdftLocalHardnessZeroRedox(_Multiwfn3DAtomCdftLocal):
"""Feature factory for the 3D atom feature "cdft_local_hardness_zero_redox", calculated with
multiwfn.
The index of this feature is 215 (see the ``list_atom_features()`` and
``list_bond_features()`` method). The corresponding configuration settings can be found
under "multiwfn.cdft" in the _feature_config.toml file.
"""
def __init__(self) -> None:
super().__init__()
[docs]
def calculate(self) -> None:
"""Calculate the ``multiwfn3D-atom-cdft_local_hardness_zero_redox`` feature."""
# Check if energies of all three redox states are available
error_message = self._check_energy_data()
if error_message is not None:
self._err = error_message
return
# Calculate the Fukui zero value which is required for the calculation of this feature
fukui_zero, error_message = self._wrap_fukui_zero()
if error_message is not None:
self._err = error_message
return
# Calculate global descriptors based on ionization potential and electron affinity and
# write them to the global feature cache
error_message = self._calculate_global_descriptors_redox()
if error_message is not None:
self._err = error_message
return
# Calculate the desired value and write it to the results dictionary
global_hardness = self.global_feature_cache[self.conformer_idx]["global-hardness_redox"]
assert isinstance(global_hardness, (int, float)) # for type checker
assert isinstance(fukui_zero, (int, float)) # for type checker
local_hardness_zero = round(number=global_hardness * fukui_zero, ndigits=6)
self.results[self.atom_bond_idx][self.feature_name] = local_hardness_zero
[docs]
class Multiwfn3DAtomCdftLocalHyperhardnessFmo(_Multiwfn3DAtomCdftLocal):
"""Feature factory for the 3D atom feature "cdft_local_hyperhardness_fmo", calculated with
multiwfn.
The index of this feature is 216 (see the ``list_atom_features()`` and
``list_bond_features()`` method). The corresponding configuration settings can be found
under "multiwfn.cdft" in the _feature_config.toml file.
"""
def __init__(self) -> None:
super().__init__()
[docs]
def calculate(self) -> None:
"""Calculate the ``multiwfn3D-atom-cdft_local_hyperhardness_fmo`` feature."""
# Calculate the dual descriptor which is required for the calculation of this feature
fukui_dual, error_message = self._wrap_fukui_dual()
if error_message is not None:
self._err = error_message
return
# Calculate global descriptors based on frontier molecular orbitals and write them to the
# global feature cache
error_message = self._calculate_global_descriptors_fmo()
if error_message is not None:
self._err = error_message
return
# Calculate the desired value and write it to the results dictionary
global_hardness = self.global_feature_cache[self.conformer_idx][
"multiwfn3D-global-hardness_fmo"
]
assert isinstance(global_hardness, (int, float)) # for type checker
assert isinstance(fukui_dual, (int, float)) # for type checker
local_hyperhardness = round(number=global_hardness**2 * fukui_dual, ndigits=6)
self.results[self.atom_bond_idx][self.feature_name] = local_hyperhardness
[docs]
class Multiwfn3DAtomCdftLocalHyperhardnessRedox(_Multiwfn3DAtomCdftLocal):
"""Feature factory for the 3D atom feature "cdft_local_hyperhardness_redox", calculated with
multiwfn.
The index of this feature is 217 (see the ``list_atom_features()`` and
``list_bond_features()`` method). The corresponding configuration settings can be found
under "multiwfn.cdft" in the _feature_config.toml file.
"""
def __init__(self) -> None:
super().__init__()
[docs]
def calculate(self) -> None:
"""Calculate the ``multiwfn3D-atom-cdft_local_hyperhardness_redox`` feature."""
# Check if energies of all three redox states are available
error_message = self._check_energy_data()
if error_message is not None:
self._err = error_message
return
# Calculate the dual descriptor which is required for the calculation of this feature
fukui_dual, error_message = self._wrap_fukui_dual()
if error_message is not None:
self._err = error_message
return
# Calculate global descriptors based on ionization potential and electron affinity and
# write them to the global feature cache
error_message = self._calculate_global_descriptors_redox()
if error_message is not None:
self._err = error_message
return
# Calculate the desired value and write it to the results dictionary
global_hardness = self.global_feature_cache[self.conformer_idx]["global-hardness_redox"]
assert isinstance(global_hardness, (int, float)) # for type checker
assert isinstance(fukui_dual, (int, float)) # for type checker
local_hyperhardness = round(number=global_hardness**2 * fukui_dual, ndigits=6)
self.results[self.atom_bond_idx][self.feature_name] = local_hyperhardness
[docs]
class Multiwfn3DAtomCdftLocalHypersoftnessFmo(_Multiwfn3DAtomCdftLocal):
"""Feature factory for the 3D atom feature "cdft_local_hypersoftness_fmo", calculated with
multiwfn.
The index of this feature is 218 (see the ``list_atom_features()`` and
``list_bond_features()`` method). The corresponding configuration settings can be found
under "multiwfn.cdft" in the _feature_config.toml file.
"""
def __init__(self) -> None:
super().__init__()
[docs]
def calculate(self) -> None:
"""Calculate the ``multiwfn3D-atom-cdft_local_hypersoftness_fmo`` feature."""
# Calculate the dual descriptor which is required for the calculation of this feature
fukui_dual, error_message = self._wrap_fukui_dual()
if error_message is not None:
self._err = error_message
return
# Calculate global descriptors based on frontier molecular orbitals and write them to the
# global feature cache
error_message = self._calculate_global_descriptors_fmo()
if error_message is not None:
self._err = error_message
return
# Calculate the desired value and write it to the results dictionary
global_softness = self.global_feature_cache[self.conformer_idx][
"multiwfn3D-global-softness_fmo"
]
assert isinstance(global_softness, (int, float)) # for type checker
assert isinstance(fukui_dual, (int, float)) # for type checker
local_hypersoftness = round(number=global_softness**2 * fukui_dual, ndigits=6)
self.results[self.atom_bond_idx][self.feature_name] = local_hypersoftness
[docs]
class Multiwfn3DAtomCdftLocalHypersoftnessRedox(_Multiwfn3DAtomCdftLocal):
"""Feature factory for the 3D atom feature "cdft_local_hypersoftness_redox", calculated with
multiwfn.
The index of this feature is 219 (see the ``list_atom_features()`` and
``list_bond_features()`` method). The corresponding configuration settings can be found
under "multiwfn.cdft" in the _feature_config.toml file.
"""
def __init__(self) -> None:
super().__init__()
[docs]
def calculate(self) -> None:
"""Calculate the ``multiwfn3D-atom-cdft_local_hypersoftness_redox`` feature."""
# Check if energies of all three redox states are available
error_message = self._check_energy_data()
if error_message is not None:
self._err = error_message
return
# Calculate the dual descriptor which is required for the calculation of this feature
fukui_dual, error_message = self._wrap_fukui_dual()
if error_message is not None:
self._err = error_message
return
# Calculate global descriptors based on ionization potential and electron affinity and
# write them to the global feature cache
error_message = self._calculate_global_descriptors_redox()
if error_message is not None:
self._err = error_message
return
# Calculate the desired value and write it to the results dictionary
global_softness = self.global_feature_cache[self.conformer_idx]["global-softness_redox"]
assert isinstance(global_softness, (int, float)) # for type checker
assert isinstance(fukui_dual, (int, float)) # for type checker
local_hypersoftness = round(number=global_softness**2 * fukui_dual, ndigits=6)
self.results[self.atom_bond_idx][self.feature_name] = local_hypersoftness
[docs]
class Multiwfn3DAtomCdftLocalNucleophilicityFmo(_Multiwfn3DAtomCdftLocal):
"""Feature factory for the 3D atom feature "cdft_local_nucleophilicity_fmo", calculated with
multiwfn.
The index of this feature is 220 (see the ``list_atom_features()`` and
``list_bond_features()`` method). The corresponding configuration settings can be found
under "multiwfn.cdft" in the _feature_config.toml file.
"""
def __init__(self) -> None:
super().__init__()
[docs]
def calculate(self) -> None:
"""Calculate the ``multiwfn3D-atom-cdft_local_nucleophilicity_fmo`` feature."""
# Calculate the Fukui minus value which is required for the calculation of this feature
fukui_minus, error_message = self._wrap_fukui_minus()
if error_message is not None:
self._err = error_message
return
# Calculate global descriptors based on frontier molecular orbitals and write them to the
# global feature cache
error_message = self._calculate_global_descriptors_fmo()
if error_message is not None:
self._err = error_message
return
# Calculate the desired value and write it to the results dictionary
global_nucleophilicity = self.global_feature_cache[self.conformer_idx][
"multiwfn3D-global-nucleophilicity_fmo"
]
assert isinstance(global_nucleophilicity, (int, float)) # for type checker
assert isinstance(fukui_minus, (int, float)) # for type checker
local_nucleophilicity = round(number=global_nucleophilicity * fukui_minus, ndigits=6)
self.results[self.atom_bond_idx][self.feature_name] = local_nucleophilicity
[docs]
class Multiwfn3DAtomCdftLocalNucleophilicityRedox(_Multiwfn3DAtomCdftLocal):
"""Feature factory for the 3D atom feature "cdft_local_nucleophilicity_redox", calculated
with multiwfn.
The index of this feature is 221 (see the ``list_atom_features()`` and
``list_bond_features()`` method). The corresponding configuration settings can be found
under "multiwfn.cdft" in the _feature_config.toml file.
"""
def __init__(self) -> None:
super().__init__()
[docs]
def calculate(self) -> None:
"""Calculate the ``multiwfn3D-atom-cdft_local_nucleophilicity_redox`` feature."""
# Check if energies of all three redox states are available
error_message = self._check_energy_data()
if error_message is not None:
self._err = error_message
return
# Calculate the Fukui minus value which is required for the calculation of this feature
fukui_minus, error_message = self._wrap_fukui_minus()
if error_message is not None:
self._err = error_message
return
# Calculate global descriptors based on ionization potential and electron affinity and
# write them to the global feature cache
error_message = self._calculate_global_descriptors_redox()
if error_message is not None:
self._err = error_message
return
# Calculate the desired value and write it to the results dictionary
global_nucleophilicity = self.global_feature_cache[self.conformer_idx][
"global-nucleophilicity_redox"
]
assert isinstance(global_nucleophilicity, (int, float)) # for type checker
assert isinstance(fukui_minus, (int, float)) # for type checker
local_nucleophilicity = round(number=global_nucleophilicity * fukui_minus, ndigits=6)
self.results[self.atom_bond_idx][self.feature_name] = local_nucleophilicity
[docs]
class Multiwfn3DAtomCdftLocalRelativeElectrophilicity(_Multiwfn3DAtomCdftLocal):
"""Feature factory for the 3D atom feature "cdft_local_relative_electrophilicity",
calculated with multiwfn.
The index of this feature is 222 (see the ``list_atom_features()`` and
``list_bond_features()`` method). The corresponding configuration settings can be found
under "multiwfn.cdft" in the _feature_config.toml file.
"""
def __init__(self) -> None:
super().__init__()
[docs]
def calculate(self) -> None:
"""Calculate the ``multiwfn3D-atom-cdft_local_relative_electrophilicity`` feature."""
# Calculate the Fukui minus value which is required for the calculation of this feature
fukui_minus, error_message = self._wrap_fukui_minus()
if error_message is not None:
self._err = error_message
return
# Calculate the Fukui plus value which is required for the calculation of this feature
fukui_plus, error_message = self._wrap_fukui_plus()
if error_message is not None:
self._err = error_message
return
# Calculate the desired value and write it to the results dictionary
assert isinstance(fukui_minus, (int, float)) # for type checker
assert isinstance(fukui_plus, (int, float)) # for type checker
try:
relative_electrophilicity = round(number=fukui_plus / fukui_minus, ndigits=6)
except ZeroDivisionError:
self._err = (
"cannot be calculated because the Fukui minus coefficient (denominator) is zero"
)
return
self.results[self.atom_bond_idx][self.feature_name] = relative_electrophilicity
[docs]
class Multiwfn3DAtomCdftLocalRelativeNucleophilicity(_Multiwfn3DAtomCdftLocal):
"""Feature factory for the 3D atom feature "cdft_local_relative_nucleophilicity", calculated
with multiwfn.
The index of this feature is 223 (see the ``list_atom_features()`` and
``list_bond_features()`` method). The corresponding configuration settings can be found
under "multiwfn.cdft" in the _feature_config.toml file.
"""
def __init__(self) -> None:
super().__init__()
[docs]
def calculate(self) -> None:
"""Calculate the ``multiwfn3D-atom-cdft_local_relative_nucleophilicity`` feature."""
# Calculate the Fukui minus value which is required for the calculation of this feature
fukui_minus, error_message = self._wrap_fukui_minus()
if error_message is not None:
self._err = error_message
return
# Calculate the Fukui plus value which is required for the calculation of this feature
fukui_plus, error_message = self._wrap_fukui_plus()
if error_message is not None:
self._err = error_message
return
# Calculate the desired value and write it to the results dictionary
assert isinstance(fukui_minus, (int, float)) # for type checker
assert isinstance(fukui_plus, (int, float)) # for type checker
try:
relative_nucleophilicity = round(number=fukui_minus / fukui_plus, ndigits=6)
except ZeroDivisionError:
self._err = (
"cannot be calculated because the Fukui plus coefficient (denominator) is zero"
)
return
self.results[self.atom_bond_idx][self.feature_name] = relative_nucleophilicity
[docs]
class Multiwfn3DAtomCdftLocalSoftnessMinusFmo(_Multiwfn3DAtomCdftLocal):
"""Feature factory for the 3D atom feature "cdft_local_softness_minus_fmo", calculated with
multiwfn.
The index of this feature is 224 (see the ``list_atom_features()`` and
``list_bond_features()`` method). The corresponding configuration settings can be found
under "multiwfn.cdft" in the _feature_config.toml file.
"""
def __init__(self) -> None:
super().__init__()
[docs]
def calculate(self) -> None:
"""Calculate the ``multiwfn3D-atom-cdft_local_softness_minus_fmo`` feature."""
# Calculate the Fukui minus value which is required for the calculation of this feature
fukui_minus, error_message = self._wrap_fukui_minus()
if error_message is not None:
self._err = error_message
return
# Calculate global descriptors based on frontier molecular orbitals and write them to the
# global feature cache
error_message = self._calculate_global_descriptors_fmo()
if error_message is not None:
self._err = error_message
return
# Calculate the desired value and write it to the results dictionary
global_softness = self.global_feature_cache[self.conformer_idx][
"multiwfn3D-global-softness_fmo"
]
assert isinstance(global_softness, (int, float)) # for type checker
assert isinstance(fukui_minus, (int, float)) # for type checker
local_softness_minus = round(number=global_softness * fukui_minus, ndigits=6)
self.results[self.atom_bond_idx][self.feature_name] = local_softness_minus
[docs]
class Multiwfn3DAtomCdftLocalSoftnessMinusRedox(_Multiwfn3DAtomCdftLocal):
"""Feature factory for the 3D atom feature "cdft_local_softness_minus_redox", calculated
with multiwfn.
The index of this feature is 225 (see the ``list_atom_features()`` and
``list_bond_features()`` method). The corresponding configuration settings can be found
under "multiwfn.cdft" in the _feature_config.toml file.
"""
def __init__(self) -> None:
super().__init__()
[docs]
def calculate(self) -> None:
"""Calculate the ``multiwfn3D-atom-cdft_local_softness_minus_redox`` feature."""
# Check if energies of all three redox states are available
error_message = self._check_energy_data()
if error_message is not None:
self._err = error_message
return
# Calculate the Fukui minus value which is required for the calculation of this feature
fukui_minus, error_message = self._wrap_fukui_minus()
if error_message is not None:
self._err = error_message
return
# Calculate global descriptors based on ionization potential and electron affinity and
# write them to the global feature cache
error_message = self._calculate_global_descriptors_redox()
if error_message is not None:
self._err = error_message
return
# Calculate the desired value and write it to the results dictionary
global_softness = self.global_feature_cache[self.conformer_idx]["global-softness_redox"]
assert isinstance(global_softness, (int, float)) # for type checker
assert isinstance(fukui_minus, (int, float)) # for type checker
local_softness_minus = round(number=global_softness * fukui_minus, ndigits=6)
self.results[self.atom_bond_idx][self.feature_name] = local_softness_minus
[docs]
class Multiwfn3DAtomCdftLocalSoftnessPlusFmo(_Multiwfn3DAtomCdftLocal):
"""Feature factory for the 3D atom feature "cdft_local_softness_plus_fmo", calculated with
multiwfn.
The index of this feature is 226 (see the ``list_atom_features()`` and
``list_bond_features()`` method). The corresponding configuration settings can be found
under "multiwfn.cdft" in the _feature_config.toml file.
"""
def __init__(self) -> None:
super().__init__()
[docs]
def calculate(self) -> None:
"""Calculate the ``multiwfn3D-atom-cdft_local_softness_plus_fmo`` feature."""
# Calculate the Fukui plus value which is required for the calculation of this feature
fukui_plus, error_message = self._wrap_fukui_plus()
if error_message is not None:
self._err = error_message
return
# Calculate global descriptors based on frontier molecular orbitals and write them to the
# global feature cache
error_message = self._calculate_global_descriptors_fmo()
if error_message is not None:
self._err = error_message
return
# Calculate the desired value and write it to the results dictionary
global_softness = self.global_feature_cache[self.conformer_idx][
"multiwfn3D-global-softness_fmo"
]
assert isinstance(global_softness, (int, float)) # for type checker
assert isinstance(fukui_plus, (int, float)) # for type checker
local_softness_plus = round(number=global_softness * fukui_plus, ndigits=6)
self.results[self.atom_bond_idx][self.feature_name] = local_softness_plus
[docs]
class Multiwfn3DAtomCdftLocalSoftnessPlusRedox(_Multiwfn3DAtomCdftLocal):
"""Feature factory for the 3D atom feature "cdft_local_softness_plus_redox", calculated with
multiwfn.
The index of this feature is 227 (see the ``list_atom_features()`` and
``list_bond_features()`` method). The corresponding configuration settings can be found
under "multiwfn.cdft" in the _feature_config.toml file.
"""
def __init__(self) -> None:
super().__init__()
[docs]
def calculate(self) -> None:
"""Calculate the ``multiwfn3D-atom-cdft_local_softness_plus_redox`` feature."""
# Check if energies of all three redox states are available
error_message = self._check_energy_data()
if error_message is not None:
self._err = error_message
return
# Calculate the Fukui plus value which is required for the calculation of this feature
fukui_plus, error_message = self._wrap_fukui_plus()
if error_message is not None:
self._err = error_message
return
# Calculate global descriptors based on ionization potential and electron affinity and
# write them to the global feature cache
error_message = self._calculate_global_descriptors_redox()
if error_message is not None:
self._err = error_message
return
# Calculate the desired value and write it to the results dictionary
global_softness = self.global_feature_cache[self.conformer_idx]["global-softness_redox"]
assert isinstance(global_softness, (int, float)) # for type checker
assert isinstance(fukui_plus, (int, float)) # for type checker
local_softness_plus = round(number=global_softness * fukui_plus, ndigits=6)
self.results[self.atom_bond_idx][self.feature_name] = local_softness_plus
[docs]
class Multiwfn3DAtomCdftLocalSoftnessZeroFmo(_Multiwfn3DAtomCdftLocal):
"""Feature factory for the 3D atom feature "cdft_local_softness_zero_fmo", calculated with
multiwfn.
The index of this feature is 228 (see the ``list_atom_features()`` and
``list_bond_features()`` method). The corresponding configuration settings can be found
under "multiwfn.cdft" in the _feature_config.toml file.
"""
def __init__(self) -> None:
super().__init__()
[docs]
def calculate(self) -> None:
"""Calculate the ``multiwfn3D-atom-cdft_local_softness_zero_fmo`` feature."""
# Calculate the Fukui zero value which is required for the calculation of this feature
fukui_zero, error_message = self._wrap_fukui_zero()
if error_message is not None:
self._err = error_message
return
# Calculate global descriptors based on frontier molecular orbitals and write them to the
# global feature cache
error_message = self._calculate_global_descriptors_fmo()
if error_message is not None:
self._err = error_message
return
# Calculate the desired value and write it to the results dictionary
global_softness = self.global_feature_cache[self.conformer_idx][
"multiwfn3D-global-softness_fmo"
]
assert isinstance(global_softness, (int, float)) # for type checker
assert isinstance(fukui_zero, (int, float)) # for type checker
local_softness_zero = round(number=global_softness * fukui_zero, ndigits=6)
self.results[self.atom_bond_idx][self.feature_name] = local_softness_zero
[docs]
class Multiwfn3DAtomCdftLocalSoftnessZeroRedox(_Multiwfn3DAtomCdftLocal):
"""Feature factory for the 3D atom feature "cdft_local_softness_zero_redox", calculated with
multiwfn.
The index of this feature is 229 (see the ``list_atom_features()`` and
``list_bond_features()`` method). The corresponding configuration settings can be found
under "multiwfn.cdft" in the _feature_config.toml file.
"""
def __init__(self) -> None:
super().__init__()
[docs]
def calculate(self) -> None:
"""Calculate the ``multiwfn3D-atom-cdft_local_softness_zero_redox`` feature."""
# Check if energies of all three redox states are available
error_message = self._check_energy_data()
if error_message is not None:
self._err = error_message
return
# Calculate the Fukui zero value which is required for the calculation of this feature
fukui_zero, error_message = self._wrap_fukui_zero()
if error_message is not None:
self._err = error_message
return
# Calculate global descriptors based on ionization potential and electron affinity and
# write them to the global feature cache
error_message = self._calculate_global_descriptors_redox()
if error_message is not None:
self._err = error_message
return
# Calculate the desired value and write it to the results dictionary
global_softness = self.global_feature_cache[self.conformer_idx]["global-softness_redox"]
assert isinstance(global_softness, (int, float)) # for type checker
assert isinstance(fukui_zero, (int, float)) # for type checker
local_softness_zero = round(number=global_softness * fukui_zero, ndigits=6)
self.results[self.atom_bond_idx][self.feature_name] = local_softness_zero