Source code for bonafide.features.multiwfn_fukui

"""Fukui indices calculated with ``Multiwfn``."""

import os
import shutil
from typing import Any, Dict, List, Optional, Tuple, Union

from bonafide.features.multiwfn_partial_charge import (
    Multiwfn3DAtomPartialChargeBecke,
    Multiwfn3DAtomPartialChargeChelpg,
    Multiwfn3DAtomPartialChargeCm5,
    Multiwfn3DAtomPartialChargeCm5Scaled1point2,
    Multiwfn3DAtomPartialChargeCorrectedHirshfeld,
    Multiwfn3DAtomPartialChargeHirshfeld,
    Multiwfn3DAtomPartialChargeLowdin,
    Multiwfn3DAtomPartialChargeMerzKollmann,
    Multiwfn3DAtomPartialChargeMulliken,
    Multiwfn3DAtomPartialChargeMullikenBickelhaupt,
    Multiwfn3DAtomPartialChargeMullikenRosSchuit,
    Multiwfn3DAtomPartialChargeMullikenStoutPolitzer,
    Multiwfn3DAtomPartialChargeRespChelpgOneStage,
    Multiwfn3DAtomPartialChargeRespChelpgTwoStage,
    Multiwfn3DAtomPartialChargeRespMerzKollmannOneStage,
    Multiwfn3DAtomPartialChargeRespMerzKollmannTwoStage,
    Multiwfn3DAtomPartialChargeVdd,
)
from bonafide.utils.base_featurizer import BaseFeaturizer
from bonafide.utils.constants import PROGRAM_ENVIRONMENT_VARIABLES
from bonafide.utils.driver import multiwfn_driver


[docs] class _Multiwfn3DAtomCdftCondensedOrbitalWeightedFukui(BaseFeaturizer): """Parent feature factory for the 3D atom Multiwfn condensed orbital-weighted Fukui index features. For details, please refer to the Multiwfn manual (http://sobereva.com/multiwfn/, last accessed on 12.09.2025). """ ow_delta: float def __init__(self) -> None: self.extraction_mode = "multi" super().__init__()
[docs] def _run_multiwfn(self) -> None: """Run Multiwfn. Returns ------- None """ # Select C-DFT analysis multiwfn_commands: List[Union[str, int, float]] multiwfn_commands = [22] # Set delta parameter for orbital weighting multiwfn_commands.extend([4, self.ow_delta]) # Run analysis multiwfn_commands.append(6) # Exit program multiwfn_commands.extend([0, "q"]) # Set up environment variables environment_variables = { var: getattr(self, var, None) for var in PROGRAM_ENVIRONMENT_VARIABLES["multiwfn"] } # Run Multiwfn multiwfn_driver( cmds=multiwfn_commands, input_file_path=str(self.electronic_struc_n), output_file_name=f"{self.__class__.__name__}__{self.conformer_name}", environment_variables=environment_variables, namespace=self.conformer_name[::-1].split("__", 1)[-1][::-1], )
[docs] def _read_output_file(self) -> None: """Read the output file from Multiwfn and write the results to the ``results`` dictionary. Returns ------- None """ # Check if the output file exists _opath = f"{self.__class__.__name__}__{self.conformer_name}.out" if os.path.isfile(_opath) is False: self._err = ( f"Multiwfn output file '{_opath}' not found; probably the calculation " "did not run. Check your input" ) return # Open output file with open(_opath, "r") as f: multiwfn_output = f.readlines() # Find relevant position in the file start_idx = None for line_idx, line in enumerate(multiwfn_output): if all( [ "Atom index" in line, "OW f+" in line, "OW f-" in line, "OW f0" in line, "OW DD" in line, ] ): start_idx = line_idx + 1 # Check if start_idx was found if start_idx is None: self._err = ( f"output file generated through '{self.__class__.__name__}' does not " "contain the requested data; probably the calculation failed. Check the " "output file" ) return # Save values to results dictionary for line in multiwfn_output[start_idx:]: if line.strip() == "": break atom_idx = int(line.split("(")[0]) splitted = line.split() ow_f_plus = float(splitted[-4]) ow_f_minus = float(splitted[-3]) ow_f_zero = float(splitted[-2]) ow_f_dual = float(splitted[-1]) self.results[atom_idx - 1] = { "multiwfn3D-atom-cdft_condensed_orbital_weighted_fukui_plus": ow_f_plus, "multiwfn3D-atom-cdft_condensed_orbital_weighted_fukui_minus": ow_f_minus, "multiwfn3D-atom-cdft_condensed_orbital_weighted_fukui_zero": ow_f_zero, "multiwfn3D-atom-cdft_condensed_orbital_weighted_fukui_dual": ow_f_dual, }
[docs] class Multiwfn3DAtomCdftCondensedOrbitalWeightedFukuiDual( _Multiwfn3DAtomCdftCondensedOrbitalWeightedFukui ): """Feature factory for the 3D atom feature "cdft_condensed_orbital_weighted_fukui_dual", calculated with multiwfn. The index of this feature is 204 (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_condensed_orbital_weighted_fukui_dual`` feature.""" # Feature is not defined for open-shell molecules if self.multiplicity != 1: self.results[0] = {self.feature_name: "_inaccessible"} return self._run_multiwfn() self._read_output_file()
[docs] class Multiwfn3DAtomCdftCondensedOrbitalWeightedFukuiMinus( _Multiwfn3DAtomCdftCondensedOrbitalWeightedFukui ): """Feature factory for the 3D atom feature "cdft_condensed_orbital_weighted_fukui_minus", calculated with multiwfn. The index of this feature is 205 (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_condensed_orbital_weighted_fukui_minus`` feature.""" # Feature is not defined for open-shell molecules if self.multiplicity != 1: self.results[0] = {self.feature_name: "_inaccessible"} return self._run_multiwfn() self._read_output_file()
[docs] class Multiwfn3DAtomCdftCondensedOrbitalWeightedFukuiPlus( _Multiwfn3DAtomCdftCondensedOrbitalWeightedFukui ): """Feature factory for the 3D atom feature "cdft_condensed_orbital_weighted_fukui_plus", calculated with multiwfn. The index of this feature is 206 (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_condensed_orbital_weighted_fukui_plus`` feature.""" # Feature is not defined for open-shell molecules if self.multiplicity != 1: self.results[0] = {self.feature_name: "_inaccessible"} return self._run_multiwfn() self._read_output_file()
[docs] class Multiwfn3DAtomCdftCondensedOrbitalWeightedFukuiZero( _Multiwfn3DAtomCdftCondensedOrbitalWeightedFukui ): """Feature factory for the 3D atom feature "cdft_condensed_orbital_weighted_fukui_zero", calculated with multiwfn. The index of this feature is 207 (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_condensed_orbital_weighted_fukui_zero`` feature.""" # Feature is not defined for open-shell molecules if self.multiplicity != 1: self.results[0] = {self.feature_name: "_inaccessible"} return self._run_multiwfn() self._read_output_file()
[docs] class _Multiwfn3DAtomCdftCondensedFukui(BaseFeaturizer): """Parent feature factory for the 3D atom Multiwfn Fukui index features. For details, please refer to the Multiwfn manual (http://sobereva.com/multiwfn/, last accessed on 12.09.2025). """ ensemble_dimensionality: str feature_cache_n_minus1: List[Dict[str, Dict[int, Optional[Union[str, bool, int, float]]]]] feature_cache_n_plus1: List[Dict[str, Dict[int, Optional[Union[str, bool, int, float]]]]] feature_dimensionality: int iterable_option: str multiplicity: int NUM_THREADS: Optional[int] OMP_STACKSIZE: Optional[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 ) -> None: """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 ------- None """ if el_struc_data is None: self._err = ( 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" )
[docs] def _run_calculation( self, **kwargs: Any ) -> Tuple[Optional[Union[int, float, bool, str]], Optional[str]]: """Run Multiwfn to calculate the atomic partial charges required to calculate the Fukui indices. This is done by initializing a "child" feature calculation pipeline as for all other features. After this is completed, the Fukui indices are calculated. Parameters ---------- **kwargs : Any Additional keyword arguments passed to the ``calculate()`` method of the feature factory for calculating the partial charges. These parameters are set as attributes of the "child" feature factory instance. Returns ------- Tuple[Optional[Union[int, float, bool, str]], Optional[str]] The calculated partial charge and the error message from the "child" feature factory call. The error message is ``None`` if the "child" feature factory did not encounter an error. """ # Check if the "child" output files folder already exists if os.path.isdir("_temp_out_files_child_dir"): shutil.rmtree("_temp_out_files_child_dir") os.mkdir("_temp_out_files_child_dir") # Mapping dictionary to use the correct feature factory of the selected charge scheme. charge_scheme_mapping = { "becke": (Multiwfn3DAtomPartialChargeBecke, "multiwfn3D-atom-partial_charge_becke"), "chelpg": (Multiwfn3DAtomPartialChargeChelpg, "multiwfn3D-atom-partial_charge_chelpg"), "cm5": (Multiwfn3DAtomPartialChargeCm5, "multiwfn3D-atom-partial_charge_cm5"), "scaled_cm5": ( Multiwfn3DAtomPartialChargeCm5Scaled1point2, "multiwfn3D-atom-partial_charge_cm5_scaled_1point2", ), "corrected_hirshfeld": ( Multiwfn3DAtomPartialChargeCorrectedHirshfeld, "multiwfn3D-atom-partial_charge_corrected_hirshfeld", ), "hirshfeld": ( Multiwfn3DAtomPartialChargeHirshfeld, "multiwfn3D-atom-partial_charge_hirshfeld", ), "lowdin": (Multiwfn3DAtomPartialChargeLowdin, "multiwfn3D-atom-partial_charge_lowdin"), "merz_kollmann": ( Multiwfn3DAtomPartialChargeMerzKollmann, "multiwfn3D-atom-partial_charge_merz_kollmann", ), "mulliken": ( Multiwfn3DAtomPartialChargeMulliken, "multiwfn3D-atom-partial_charge_mulliken", ), "mulliken_bickelhaupt": ( Multiwfn3DAtomPartialChargeMullikenBickelhaupt, "multiwfn3D-atom-partial_charge_mulliken_bickelhaupt", ), "mulliken_ros_schuit": ( Multiwfn3DAtomPartialChargeMullikenRosSchuit, "multiwfn3D-atom-partial_charge_mulliken_ros_schuit", ), "mulliken_stout_politzer": ( Multiwfn3DAtomPartialChargeMullikenStoutPolitzer, "multiwfn3D-atom-partial_charge_mulliken_stout_politzer", ), "resp_chelpg_one_stage": ( Multiwfn3DAtomPartialChargeRespChelpgOneStage, "multiwfn3D-atom-partial_charge_resp_chelpg_one_stage", ), "resp_chelpg_two_stage": ( Multiwfn3DAtomPartialChargeRespChelpgTwoStage, "multiwfn3D-atom-partial_charge_resp_chelpg_two_stage", ), "resp_merz_kollmann_one_stage": ( Multiwfn3DAtomPartialChargeRespMerzKollmannOneStage, "multiwfn3D-atom-partial_charge_resp_merz_kollmann_one_stage", ), "resp_merz_kollmann_two_stage": ( Multiwfn3DAtomPartialChargeRespMerzKollmannTwoStage, "multiwfn3D-atom-partial_charge_resp_kollmann_two_stage", ), "vdd": (Multiwfn3DAtomPartialChargeVdd, "multiwfn3D-atom-partial_charge_vdd"), } # Calculate the atomic charges required to get the Fukui indices feature_factory, feature_name = charge_scheme_mapping[kwargs["charge_scheme"]] new_params = {name: value for name, value in kwargs.items()} new_params["feature_name"] = feature_name calc_feature = feature_factory() feature_value, error_message = calc_feature(**new_params) # Handle the output files if kwargs["_keep_output_files"] is True: self._save_output_files2() shutil.rmtree("_temp_out_files_child_dir") # Return the feature value and the error message of the "child" feature calculation # process, which is the calculation of the partial atomic charges return feature_value, error_message
[docs] def _save_output_files2(self) -> None: """Save the generated output files of the "child" feature factory. Returns ------- None """ for item in os.listdir("_temp_out_files_child_dir"): source = os.path.join("_temp_out_files_child_dir", item) dest = os.path.join(os.path.dirname(os.getcwd()), item) if os.path.isfile(source): shutil.copy2(source, dest)
[docs] class Multiwfn3DAtomCdftCondensedFukuiDual(_Multiwfn3DAtomCdftCondensedFukui): """Feature factory for the 3D atom feature "cdft_condensed_fukui_dual", calculated with multiwfn. The index of this feature is 200 (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_condensed_fukui_dual`` feature.""" # Check if both (radical anion and cation) electronic structure data is available self._check_electronic_structure_data( el_struc_data=self.electronic_struc_n_plus1, data_name="radical anion (actual molecule plus one electron)", ) if self._err is not None: return self._check_electronic_structure_data( el_struc_data=self.electronic_struc_n_minus1, data_name="radical cation (actual molecule minus one electron)", ) if self._err is not None: return # Modify feature_name to also include the name of the charge scheme self.feature_name = f"{self.feature_name}__{self.iterable_option}" # Get charges of the actual molecule feature_value_n, error_message_n = self._run_calculation( OMP_STACKSIZE=self.OMP_STACKSIZE, NUM_THREADS=self.NUM_THREADS, charge_scheme=self.iterable_option, feature_cache=self.feature_cache, feature_dimensionality=self.feature_dimensionality, ensemble_dimensionality=self.ensemble_dimensionality, conformer_idx=self.conformer_idx, conformer_name=self.conformer_name, electronic_struc_n=self.electronic_struc_n, feature_type=self.feature_type, mol=self.mol, multiplicity=self.multiplicity, atom_bond_idx=self.atom_bond_idx, _keep_output_files=self._keep_output_files, ) if error_message_n is not None: self._err = error_message_n return # Calculate multiplicity of radical anion if self.multiplicity == 1: multiplicity = 2 else: multiplicity = self.multiplicity - 1 # Get charges of the radical anion (n+1 state) feature_value_n_plus1, error_message_n_plus1 = self._run_calculation( OMP_STACKSIZE=self.OMP_STACKSIZE, NUM_THREADS=self.NUM_THREADS, charge_scheme=self.iterable_option, feature_cache=self.feature_cache_n_plus1, feature_dimensionality=self.feature_dimensionality, ensemble_dimensionality=self.ensemble_dimensionality, conformer_idx=self.conformer_idx, conformer_name=f"{self.conformer_name}__n+1", electronic_struc_n=self.electronic_struc_n_plus1, # pass electronic structure data for n+1 state to Multiwfn feature_type=self.feature_type, mol=self.mol, multiplicity=multiplicity, atom_bond_idx=self.atom_bond_idx, _keep_output_files=self._keep_output_files, ) if error_message_n_plus1 is not None: self._err = error_message_n_plus1 return # Calculate multiplicity of radical cation if self.multiplicity == 1: multiplicity = 2 else: multiplicity = self.multiplicity - 1 # Get charges of the radical cation (n-1 state) feature_value_n_minus1, error_message_n_minus1 = self._run_calculation( OMP_STACKSIZE=self.OMP_STACKSIZE, NUM_THREADS=self.NUM_THREADS, charge_scheme=self.iterable_option, feature_cache=self.feature_cache_n_minus1, feature_dimensionality=self.feature_dimensionality, ensemble_dimensionality=self.ensemble_dimensionality, conformer_idx=self.conformer_idx, conformer_name=f"{self.conformer_name}__n-1", electronic_struc_n=self.electronic_struc_n_minus1, # pass electronic structure data for n-1 state to Multiwfn feature_type=self.feature_type, mol=self.mol, multiplicity=multiplicity, atom_bond_idx=self.atom_bond_idx, _keep_output_files=self._keep_output_files, ) if error_message_n_minus1 is not None: self._err = error_message_n_minus1 return # Calculate desired value and save it to the results dictionary assert isinstance(feature_value_n_minus1, (int, float)) # for type checker assert isinstance(feature_value_n_plus1, (int, float)) # for type checker assert isinstance(feature_value_n, (int, float)) # for type checker fukui_dual = round( number=2 * feature_value_n - feature_value_n_plus1 - feature_value_n_minus1, ndigits=6 ) self.results[self.atom_bond_idx] = {self.feature_name: fukui_dual}
[docs] class Multiwfn3DAtomCdftCondensedFukuiMinus(_Multiwfn3DAtomCdftCondensedFukui): """Feature factory for the 3D atom feature "cdft_condensed_fukui_minus", calculated with multiwfn. The index of this feature is 201 (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_condensed_fukui_minus`` feature.""" # Check if all electronic structure data is available self._check_electronic_structure_data( el_struc_data=self.electronic_struc_n_minus1, data_name="actual molecule minus one electron", ) if self._err is not None: return # Modify feature_name to also include the name of the charge scheme self.feature_name = f"{self.feature_name}__{self.iterable_option}" # Get charges of the actual molecule feature_value_n, error_message_n = self._run_calculation( OMP_STACKSIZE=self.OMP_STACKSIZE, NUM_THREADS=self.NUM_THREADS, charge_scheme=self.iterable_option, feature_cache=self.feature_cache, feature_dimensionality=self.feature_dimensionality, ensemble_dimensionality=self.ensemble_dimensionality, conformer_idx=self.conformer_idx, conformer_name=self.conformer_name, electronic_struc_n=self.electronic_struc_n, feature_type=self.feature_type, mol=self.mol, multiplicity=self.multiplicity, atom_bond_idx=self.atom_bond_idx, _keep_output_files=self._keep_output_files, ) if error_message_n is not None: self._err = error_message_n return # Calculate multiplicity of radical cation if self.multiplicity == 1: multiplicity = 2 else: multiplicity = self.multiplicity - 1 # Get charges of the radical cation (n-1 state) feature_value_n_minus1, error_message_n_minus1 = self._run_calculation( OMP_STACKSIZE=self.OMP_STACKSIZE, NUM_THREADS=self.NUM_THREADS, charge_scheme=self.iterable_option, feature_cache=self.feature_cache_n_minus1, feature_dimensionality=self.feature_dimensionality, ensemble_dimensionality=self.ensemble_dimensionality, conformer_idx=self.conformer_idx, conformer_name=f"{self.conformer_name}__n-1", electronic_struc_n=self.electronic_struc_n_minus1, # pass electronic structure data for n-1 state to Multiwfn feature_type=self.feature_type, mol=self.mol, multiplicity=multiplicity, atom_bond_idx=self.atom_bond_idx, _keep_output_files=self._keep_output_files, ) if error_message_n_minus1 is not None: self._err = error_message_n_minus1 return # Calculate desired value and save it to the results dictionary assert isinstance(feature_value_n_minus1, (int, float)) # for type checker assert isinstance(feature_value_n, (int, float)) # for type checker fukui_minus = round(number=feature_value_n_minus1 - feature_value_n, ndigits=6) self.results[self.atom_bond_idx] = {self.feature_name: fukui_minus}
[docs] class Multiwfn3DAtomCdftCondensedFukuiPlus(_Multiwfn3DAtomCdftCondensedFukui): """Feature factory for the 3D atom feature "cdft_condensed_fukui_plus", calculated with multiwfn. The index of this feature is 202 (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_condensed_fukui_plus`` feature.""" # Check if the second electronic structure data is available self._check_electronic_structure_data( el_struc_data=self.electronic_struc_n_plus1, data_name="actual molecule plus one electron", ) if self._err is not None: return # Modify feature_name to also include the name of the charge scheme self.feature_name = f"{self.feature_name}__{self.iterable_option}" # Get charges of the actual molecule feature_value_n, error_message_n = self._run_calculation( OMP_STACKSIZE=self.OMP_STACKSIZE, NUM_THREADS=self.NUM_THREADS, charge_scheme=self.iterable_option, feature_cache=self.feature_cache, feature_dimensionality=self.feature_dimensionality, ensemble_dimensionality=self.ensemble_dimensionality, conformer_idx=self.conformer_idx, conformer_name=self.conformer_name, electronic_struc_n=self.electronic_struc_n, feature_type=self.feature_type, mol=self.mol, multiplicity=self.multiplicity, atom_bond_idx=self.atom_bond_idx, _keep_output_files=self._keep_output_files, ) if error_message_n is not None: self._err = error_message_n return # Calculate multiplicity of radical anion if self.multiplicity == 1: multiplicity = 2 else: multiplicity = self.multiplicity - 1 # Get charges of the radical anion (n+1 state) feature_value_n_plus1, error_message_n_plus1 = self._run_calculation( OMP_STACKSIZE=self.OMP_STACKSIZE, NUM_THREADS=self.NUM_THREADS, charge_scheme=self.iterable_option, feature_cache=self.feature_cache_n_plus1, feature_dimensionality=self.feature_dimensionality, ensemble_dimensionality=self.ensemble_dimensionality, conformer_idx=self.conformer_idx, conformer_name=f"{self.conformer_name}__n+1", electronic_struc_n=self.electronic_struc_n_plus1, # pass electronic structure data for n+1 state to Multiwfn feature_type=self.feature_type, mol=self.mol, multiplicity=multiplicity, atom_bond_idx=self.atom_bond_idx, _keep_output_files=self._keep_output_files, ) if error_message_n_plus1 is not None: self._err = error_message_n_plus1 return # Calculate desired value and save it to the results dictionary assert isinstance(feature_value_n, (int, float)) # for type checker assert isinstance(feature_value_n_plus1, (int, float)) # for type checker fukui_plus = round(number=feature_value_n - feature_value_n_plus1, ndigits=6) self.results[self.atom_bond_idx] = {self.feature_name: fukui_plus}
[docs] class Multiwfn3DAtomCdftCondensedFukuiZero(_Multiwfn3DAtomCdftCondensedFukui): """Feature factory for the 3D atom feature "cdft_condensed_fukui_zero", calculated with multiwfn. The index of this feature is 203 (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_condensed_fukui_zero`` feature.""" # Check if both (radical anion and cation) electronic structure data is available self._check_electronic_structure_data( el_struc_data=self.electronic_struc_n_plus1, data_name="actual molecule plus one electron", ) if self._err is not None: return self._check_electronic_structure_data( el_struc_data=self.electronic_struc_n_minus1, data_name="actual molecule minus one electron", ) if self._err is not None: return # Modify feature_name to also include the name of the charge scheme self.feature_name = f"{self.feature_name}__{self.iterable_option}" # Calculate multiplicity of radical anion if self.multiplicity == 1: multiplicity = 2 else: multiplicity = self.multiplicity - 1 # Get charges of the radical anion (n+1 state) feature_value_n_plus1, error_message_n_plus1 = self._run_calculation( OMP_STACKSIZE=self.OMP_STACKSIZE, NUM_THREADS=self.NUM_THREADS, charge_scheme=self.iterable_option, feature_cache=self.feature_cache_n_plus1, feature_dimensionality=self.feature_dimensionality, ensemble_dimensionality=self.ensemble_dimensionality, conformer_idx=self.conformer_idx, conformer_name=f"{self.conformer_name}__n+1", electronic_struc_n=self.electronic_struc_n_plus1, # pass electronic structure data for n+1 state to Multiwfn feature_type=self.feature_type, mol=self.mol, multiplicity=multiplicity, atom_bond_idx=self.atom_bond_idx, _keep_output_files=self._keep_output_files, ) if error_message_n_plus1 is not None: self._err = error_message_n_plus1 return # Calculate multiplicity of radical cation if self.multiplicity == 1: multiplicity = 2 else: multiplicity = self.multiplicity - 1 # Get charges of the radical cation (n-1 state) feature_value_n_minus1, error_message_n_minus1 = self._run_calculation( OMP_STACKSIZE=self.OMP_STACKSIZE, NUM_THREADS=self.NUM_THREADS, charge_scheme=self.iterable_option, feature_cache=self.feature_cache_n_minus1, feature_dimensionality=self.feature_dimensionality, ensemble_dimensionality=self.ensemble_dimensionality, conformer_idx=self.conformer_idx, conformer_name=f"{self.conformer_name}__n-1", electronic_struc_n=self.electronic_struc_n_minus1, # pass electronic structure data for n-1 state to Multiwfn feature_type=self.feature_type, mol=self.mol, multiplicity=multiplicity, atom_bond_idx=self.atom_bond_idx, _keep_output_files=self._keep_output_files, ) if error_message_n_minus1 is not None: self._err = error_message_n_minus1 return # Calculate desired value and save it to the results dictionary assert isinstance(feature_value_n_minus1, (int, float)) # for type checker assert isinstance(feature_value_n_plus1, (int, float)) # for type checker fukui_zero = round(number=(feature_value_n_minus1 - feature_value_n_plus1) / 2, ndigits=6) self.results[self.atom_bond_idx] = {self.feature_name: fukui_zero}