Source code for BioSimSpace.Parameters._utils
######################################################################
# BioSimSpace: Making biomolecular simulation a breeze!
#
# Copyright: 2017-2025
#
# Authors: Lester Hedges <lester.hedges@gmail.com>
#
# BioSimSpace is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BioSimSpace is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BioSimSpace. If not, see <http://www.gnu.org/licenses/>.
#####################################################################
"""Utility functions."""
__author__ = "Lester Hedges"
__email__ = "lester.hedges@gmail.com"
__all__ = ["formalCharge"]
import tempfile as _tempfile
from .. import _isVerbose
from .. import IO as _IO
from .. import _Utils
from ..Units.Charge import electron_charge as _electron_charge
from .._SireWrappers import Molecule as _Molecule
[docs]
def formalCharge(molecule, property_map={}):
"""
Compute the formal charge on a molecule. This function requires that
the molecule has explicit hydrogen atoms.
Parameters
----------
molecule : :class:`Molecule <BioSimSpace._SireWrappers.Molecule>`
A molecule object.
property_map : dict
A dictionary that maps system "properties" to their user defined
values. This allows the user to refer to properties with their
own naming scheme, e.g. { "charge" : "my-charge" }
Returns
-------
formal_charge : :class:`Charge <BioSimSpace.Types.Charge>`
The total formal charge on the molecule.
"""
if not isinstance(molecule, _Molecule):
raise TypeError(
"'molecule' must be of type 'BioSimSpace._SireWrappers.Molecule'"
)
if not isinstance(property_map, dict):
raise TypeError("'property_map' must be of type 'dict'")
from rdkit import Chem as _Chem
from rdkit import RDLogger as _RDLogger
# Disable RDKit warnings.
_RDLogger.DisableLog("rdApp.*")
# Create a temporary working directory.
tmp_dir = _tempfile.TemporaryDirectory()
work_dir = tmp_dir.name
# Zero the total formal charge.
formal_charge = 0
# Get the fileformat property name.
property = property_map.get("fileformat", "fileformat")
# Preferentially use the file format that the molecule was loaded from.
try:
# Get the raw list of formats.
raw_formats = molecule._sire_object.property(property).value().split(",")
# Remove all formats other than PDB and SDF.
formats = [f for f in raw_formats if f in ["PDB", "SDF"]]
if len(formats) == 0:
formats = ["PDB", "SDF"]
elif len(formats) == 1:
if formats[0] == "PDB":
formats.append("SDF")
else:
formats.append("PDB")
except:
formats = ["PDB", "SDF"]
# List of exceptions.
exceptions = []
# Try going via each format in turn.
for format in formats:
try:
with _Utils.cd(work_dir):
# Save the molecule in the given format.
_IO.saveMolecules("tmp", molecule, format)
# Load with RDKit.
if format == "SDF":
rdmol = _Chem.MolFromMolFile("tmp.sdf")
else:
rdmol = _Chem.MolFromPDBFile("tmp.pdb")
# Compute the formal charge.
formal_charge = _Chem.rdmolops.GetFormalCharge(rdmol)
return formal_charge * _electron_charge
except Exception as e:
exceptions.append(e)
# If we got this far, then we failed to compute the formal charge.
msg = "Failed to compute the formal charge on the molecule."
if _isVerbose():
for e in exceptions:
msg += "\n\n" + str(e)
raise RuntimeError(msg)
else:
raise RuntimeError(msg) from None