Source code for bonafide.features.multiwfn_hilbert_space

"""Hilbert space features from ``Multiwfn``."""

import os
from typing import List, Union

from bonafide.utils.base_featurizer import BaseFeaturizer
from bonafide.utils.constants import PROGRAM_ENVIRONMENT_VARIABLES
from bonafide.utils.driver import multiwfn_driver


[docs] class _Multiwfn3DHilbertSpace(BaseFeaturizer): """Parent feature factory for the 3D atom and bond Multiwfn Hilbert space features. For details, please refer to the Multiwfn manual (http://sobereva.com/multiwfn/, last accessed on 12.09.2025). """ def __init__(self) -> None: self.extraction_mode = "multi" super().__init__()
[docs] def _run_multiwfn(self, feature_type: str) -> None: """Run Multiwfn. Parameters ---------- feature_type : str The type of the feature to calculate, either "atom" or "bond". Returns ------- None """ # Select Hilbert space analysis multiwfn_commands: List[Union[str, int, float]] multiwfn_commands = [200, 2] # Run analysis and exit program if feature_type == "atom": multiwfn_commands.extend([1, -1]) multiwfn_commands.extend([0, 0, 0, "q"]) output_file_name = f"Multiwfn3DAtomHilbertSpace__{self.conformer_name}" if feature_type == "bond": multiwfn_commands.extend([2, "b"]) multiwfn_commands.extend(["q", 0, 0, "q"]) output_file_name = f"Multiwfn3DBondHilbertSpace__{self.conformer_name}" # 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=output_file_name, environment_variables=environment_variables, namespace=self.conformer_name[::-1].split("__", 1)[-1][::-1], )
[docs] def _read_output_file_atom(self) -> None: """Read the output file from Multiwfn and write the results to the ``results`` dictionary. This method is used to process the atom features. Returns ------- None """ # Check if the output file exists _opath = f"Multiwfn3DAtomHilbertSpace__{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() # Extract data and write it to the results dictionary for line_idx, line in enumerate(multiwfn_output): if line.startswith(" Result of atom"): atom_idx = int(line.split("atom")[-1].split("(")[0]) pop_number = float(multiwfn_output[line_idx + 1].split(":")[-1]) _x_, _y_, _z_, dipole_norm_ = multiwfn_output[line_idx + 3].split("=")[1:] _x = float(_x_.split()[0]) _y = float(_y_.split()[0]) _z = float(_z_.split()[0]) dipole_norm = float(dipole_norm_) dipole = ",".join([str(_x), str(_y), str(_z)]) _x_, _y_, _z_, contrib_nuc_norm_ = multiwfn_output[line_idx + 5].split("=")[1:] _x = float(_x_.split()[0]) _y = float(_y_.split()[0]) _z = float(_z_.split()[0]) contrib_nuc_norm = float(contrib_nuc_norm_) contrib_nuc_dipole = ",".join([str(_x), str(_y), str(_z)]) _x_, _y_, _z_, contrib_el_norm_ = multiwfn_output[line_idx + 7].split("=")[1:] _x = float(_x_.split()[0]) _y = float(_y_.split()[0]) _z = float(_z_.split()[0]) contrib_el_norm = float(contrib_el_norm_) contrib_el_dipole = ",".join([str(_x), str(_y), str(_z)]) _x_, _y_, _z_, contrib_norm_ = multiwfn_output[line_idx + 9].split("=")[1:] _x = float(_x_.split()[0]) _y = float(_y_.split()[0]) _z = float(_z_.split()[0]) contrib_norm = float(contrib_norm_) contrib_dipole = ",".join([str(_x), str(_y), str(_z)]) self.results[atom_idx - 1] = { "multiwfn3D-atom-hilbert_space_local_population_number": pop_number, "multiwfn3D-atom-hilbert_space_dipole_moment": dipole, "multiwfn3D-atom-hilbert_space_dipole_moment_norm": dipole_norm, "multiwfn3D-atom-hilbert_space_contribution_to_system_dipole_moment_nuclear_charge": contrib_nuc_dipole, "multiwfn3D-atom-hilbert_space_contribution_to_system_dipole_moment_nuclear_charge_norm": contrib_nuc_norm, "multiwfn3D-atom-hilbert_space_contribution_to_system_dipole_moment_electrons": contrib_el_dipole, "multiwfn3D-atom-hilbert_space_contribution_to_system_dipole_moment_electrons_norm": contrib_el_norm, "multiwfn3D-atom-hilbert_space_contribution_to_system_dipole_moment": contrib_dipole, "multiwfn3D-atom-hilbert_space_contribution_to_system_dipole_moment_norm": contrib_norm, }
[docs] def _read_output_file_bond(self) -> None: """Read the output file from Multiwfn and write the results to the ``results`` dictionary. This method is used to process the bond features. Returns ------- None """ # Check if the output file exists _opath = f"Multiwfn3DBondHilbertSpace__{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() # Extract data and write it to the results dictionary for line_idx, line in enumerate(multiwfn_output): if all(["Result between atom" in line, "and atom" in line, "distance:" in line]): atom_idx_1_, atom_idx_2_ = line.split("atom")[1:] atom_idx_1 = int(atom_idx_1_.split("(")[0]) - 1 atom_idx_2 = int(atom_idx_2_.split("(")[0]) - 1 pop_number = float(multiwfn_output[line_idx + 1].split(":")[-1]) _x_, _y_, _z_, dipole_norm_ = multiwfn_output[line_idx + 3].split("=")[1:] _x = float(_x_.split()[0]) _y = float(_y_.split()[0]) _z = float(_z_.split()[0]) dipole_norm = float(dipole_norm_) dipole = ",".join([str(_x), str(_y), str(_z)]) _x_, _y_, _z_, contrib_norm_ = multiwfn_output[line_idx + 5].split("=")[1:] _x = float(_x_.split()[0]) _y = float(_y_.split()[0]) _z = float(_z_.split()[0]) contrib_norm = float(contrib_norm_) contrib_dipole = ",".join([str(_x), str(_y), str(_z)]) # Find the bond and write the data to the results dictionary for bond in self.mol.GetBonds(): bond_idx = bond.GetIdx() if any( [ ( bond.GetBeginAtomIdx() == atom_idx_1 and bond.GetEndAtomIdx() == atom_idx_2 ), ( bond.GetBeginAtomIdx() == atom_idx_2 and bond.GetEndAtomIdx() == atom_idx_1 ), ] ): self.results[bond_idx] = { "multiwfn3D-bond-hilbert_space_overlap_population": pop_number, "multiwfn3D-bond-hilbert_space_dipole_moment": dipole, "multiwfn3D-bond-hilbert_space_dipole_moment_norm": dipole_norm, "multiwfn3D-bond-hilbert_space_contribution_to_system_dipole_moment": contrib_dipole, "multiwfn3D-bond-hilbert_space_contribution_to_system_dipole_moment_norm": contrib_norm, } break
[docs] class Multiwfn3DAtomHilbertSpaceContributionToSystemDipoleMoment(_Multiwfn3DHilbertSpace): """Feature factory for the 3D atom feature "hilbert_space_contribution_to_system_dipole_moment", calculated with multiwfn. The index of this feature is 255 (see the ``list_atom_features()`` and ``list_bond_features()`` method). The corresponding configuration settings can be found under "multiwfn.misc" in the _feature_config.toml file. """ def __init__(self) -> None: super().__init__()
[docs] def calculate(self) -> None: """Calculate the ``multiwfn3D-atom-hilbert_space_contribution_to_system_dipole_moment`` feature.""" self._run_multiwfn(feature_type="atom") self._read_output_file_atom()
[docs] class Multiwfn3DAtomHilbertSpaceContributionToSystemDipoleMomentElectrons(_Multiwfn3DHilbertSpace): """Feature factory for the 3D atom feature "hilbert_space_contribution_to_system_dipole_moment_electrons", calculated with multiwfn. The index of this feature is 256 (see the ``list_atom_features()`` and ``list_bond_features()`` method). The corresponding configuration settings can be found under "multiwfn.misc" in the _feature_config.toml file. """ def __init__(self) -> None: super().__init__()
[docs] def calculate(self) -> None: """Calculate the ``multiwfn3D-atom-hilbert_space_contribution_to_system_dipole_moment_electrons`` feature.""" self._run_multiwfn(feature_type="atom") self._read_output_file_atom()
[docs] class Multiwfn3DAtomHilbertSpaceContributionToSystemDipoleMomentElectronsNorm( _Multiwfn3DHilbertSpace ): """Feature factory for the 3D atom feature "hilbert_space_contribution_to_system_dipole_moment_electrons_norm", calculated with multiwfn. The index of this feature is 257 (see the ``list_atom_features()`` and ``list_bond_features()`` method). The corresponding configuration settings can be found under "multiwfn.misc" in the _feature_config.toml file. """ def __init__(self) -> None: super().__init__()
[docs] def calculate(self) -> None: """Calculate the ``multiwfn3D-atom-hilbert_space_contribution_to_system_dipole_moment_electrons_norm`` feature.""" self._run_multiwfn(feature_type="atom") self._read_output_file_atom()
[docs] class Multiwfn3DAtomHilbertSpaceContributionToSystemDipoleMomentNorm(_Multiwfn3DHilbertSpace): """Feature factory for the 3D atom feature "hilbert_space_contribution_to_system_dipole_moment_norm", calculated with multiwfn. The index of this feature is 258 (see the ``list_atom_features()`` and ``list_bond_features()`` method). The corresponding configuration settings can be found under "multiwfn.misc" in the _feature_config.toml file. """ def __init__(self) -> None: super().__init__()
[docs] def calculate(self) -> None: """Calculate the ``multiwfn3D-atom-hilbert_space_contribution_to_system_dipole_moment_norm`` feature.""" self._run_multiwfn(feature_type="atom") self._read_output_file_atom()
[docs] class Multiwfn3DAtomHilbertSpaceContributionToSystemDipoleMomentNuclearCharge( _Multiwfn3DHilbertSpace ): """Feature factory for the 3D atom feature "hilbert_space_contribution_to_system_dipole_moment_nuclear_charge", calculated with multiwfn. The index of this feature is 259 (see the ``list_atom_features()`` and ``list_bond_features()`` method). The corresponding configuration settings can be found under "multiwfn.misc" in the _feature_config.toml file. """ def __init__(self) -> None: super().__init__()
[docs] def calculate(self) -> None: """Calculate the ``multiwfn3D-atom-hilbert_space_contribution_to_system_dipole_moment_nuclear_charge`` feature.""" self._run_multiwfn(feature_type="atom") self._read_output_file_atom()
[docs] class Multiwfn3DAtomHilbertSpaceContributionToSystemDipoleMomentNuclearChargeNorm( _Multiwfn3DHilbertSpace ): """Feature factory for the 3D atom feature "hilbert_space_contribution_to_system_dipole_moment_nuclear_charge_norm", calculated with multiwfn. The index of this feature is 260 (see the ``list_atom_features()`` and ``list_bond_features()`` method). The corresponding configuration settings can be found under "multiwfn.misc" in the _feature_config.toml file. """ def __init__(self) -> None: super().__init__()
[docs] def calculate(self) -> None: """Calculate the ``multiwfn3D-atom-hilbert_space_contribution_to_system_dipole_moment_nuclear_charge_norm`` feature.""" self._run_multiwfn(feature_type="atom") self._read_output_file_atom()
[docs] class Multiwfn3DAtomHilbertSpaceDipoleMoment(_Multiwfn3DHilbertSpace): """Feature factory for the 3D atom feature "hilbert_space_dipole_moment", calculated with multiwfn. The index of this feature is 261 (see the ``list_atom_features()`` and ``list_bond_features()`` method). The corresponding configuration settings can be found under "multiwfn.misc" in the _feature_config.toml file. """ def __init__(self) -> None: super().__init__()
[docs] def calculate(self) -> None: """Calculate the ``multiwfn3D-atom-hilbert_space_dipole_moment`` feature.""" self._run_multiwfn(feature_type="atom") self._read_output_file_atom()
[docs] class Multiwfn3DAtomHilbertSpaceDipoleMomentNorm(_Multiwfn3DHilbertSpace): """Feature factory for the 3D atom feature "hilbert_space_dipole_moment_norm", calculated with multiwfn. The index of this feature is 262 (see the ``list_atom_features()`` and ``list_bond_features()`` method). The corresponding configuration settings can be found under "multiwfn.misc" in the _feature_config.toml file. """ def __init__(self) -> None: super().__init__()
[docs] def calculate(self) -> None: """Calculate the ``multiwfn3D-atom-hilbert_space_dipole_moment_norm`` feature.""" self._run_multiwfn(feature_type="atom") self._read_output_file_atom()
[docs] class Multiwfn3DAtomHilbertSpaceLocalPopulationNumber(_Multiwfn3DHilbertSpace): """Feature factory for the 3D atom feature "hilbert_space_local_population_number", calculated with multiwfn. The index of this feature is 263 (see the ``list_atom_features()`` and ``list_bond_features()`` method). The corresponding configuration settings can be found under "multiwfn.misc" in the _feature_config.toml file. """ def __init__(self) -> None: super().__init__()
[docs] def calculate(self) -> None: """Calculate the ``multiwfn3D-atom-hilbert_space_local_population_number`` feature.""" self._run_multiwfn(feature_type="atom") self._read_output_file_atom()
[docs] class Multiwfn3DBondHilbertSpaceContributionToSystemDipoleMoment(_Multiwfn3DHilbertSpace): """Feature factory for the 3D bond feature "hilbert_space_contribution_to_system_dipole_moment", calculated with multiwfn. The index of this feature is 440 (see the ``list_atom_features()`` and ``list_bond_features()`` method). The corresponding configuration settings can be found under "multiwfn.misc" in the _feature_config.toml file. """ def __init__(self) -> None: super().__init__()
[docs] def calculate(self) -> None: """Calculate the ``multiwfn3D-bond-hilbert_space_contribution_to_system_dipole_moment`` feature.""" self._run_multiwfn(feature_type="bond") self._read_output_file_bond()
[docs] class Multiwfn3DBondHilbertSpaceContributionToSystemDipoleMomentNorm(_Multiwfn3DHilbertSpace): """Feature factory for the 3D bond feature "hilbert_space_contribution_to_system_dipole_moment_norm", calculated with multiwfn. The index of this feature is 441 (see the ``list_atom_features()`` and ``list_bond_features()`` method). The corresponding configuration settings can be found under "multiwfn.misc" in the _feature_config.toml file. """ def __init__(self) -> None: super().__init__()
[docs] def calculate(self) -> None: """Calculate the ``multiwfn3D-bond-hilbert_space_contribution_to_system_dipole_moment_norm`` feature.""" self._run_multiwfn(feature_type="bond") self._read_output_file_bond()
[docs] class Multiwfn3DBondHilbertSpaceDipoleMoment(_Multiwfn3DHilbertSpace): """Feature factory for the 3D bond feature "hilbert_space_dipole_moment", calculated with multiwfn. The index of this feature is 442 (see the ``list_atom_features()`` and ``list_bond_features()`` method). The corresponding configuration settings can be found under "multiwfn.misc" in the _feature_config.toml file. """ def __init__(self) -> None: super().__init__()
[docs] def calculate(self) -> None: """Calculate the ``multiwfn3D-bond-hilbert_space_dipole_moment`` feature.""" self._run_multiwfn(feature_type="bond") self._read_output_file_bond()
[docs] class Multiwfn3DBondHilbertSpaceDipoleMomentNorm(_Multiwfn3DHilbertSpace): """Feature factory for the 3D bond feature "hilbert_space_dipole_moment_norm", calculated with multiwfn. The index of this feature is 443 (see the ``list_atom_features()`` and ``list_bond_features()`` method). The corresponding configuration settings can be found under "multiwfn.misc" in the _feature_config.toml file. """ def __init__(self) -> None: super().__init__()
[docs] def calculate(self) -> None: """Calculate the ``multiwfn3D-bond-hilbert_space_dipole_moment_norm`` feature.""" self._run_multiwfn(feature_type="bond") self._read_output_file_bond()
[docs] class Multiwfn3DBondHilbertSpaceOverlapPopulation(_Multiwfn3DHilbertSpace): """Feature factory for the 3D bond feature "hilbert_space_overlap_population", calculated with multiwfn. The index of this feature is 444 (see the ``list_atom_features()`` and ``list_bond_features()`` method). The corresponding configuration settings can be found under "multiwfn.misc" in the _feature_config.toml file. """ def __init__(self) -> None: super().__init__()
[docs] def calculate(self) -> None: """Calculate the ``multiwfn3D-bond-hilbert_space_overlap_population`` feature.""" self._run_multiwfn(feature_type="bond") self._read_output_file_bond()