Source code for luna.mol.charge_model

from luna.wrappers.base import AtomWrapper

import logging
logger = logging.getLogger()


# TODO: implement this other model: https://github.com/openbabel/openbabel/blob/master/src/formats/mdlvalence.h


[docs]class ChargeModel: """Implementation of a charge model."""
[docs] def get_charge(self): raise NotImplementedError("Subclasses should implement this.")
[docs]class OpenEyeModel(ChargeModel): """Implementation of OpenEye charge model."""
[docs] def get_charge(self, atm_obj): """Get the formal charge for atom ``atom_obj``. Currently, only formal charges for the elements Hydrogen, Carbon, Nitrogen, Oxygen, Phosphorus, Sulfur, Chlorine, Fluorine, Bromine, Iodine, Magnesium, Calcium, Zinc, Lithium, Sodium, Potassium, and Boron can be recovered. Parameters ---------- atm_obj : :class:`~luna.wrappers.base.AtomWrapper` The target atom. Examples -------- >>> from luna.mol.charge_model import OpenEyeModel >>> from luna.wrappers.base import MolWrapper >>> cm = OpenEyeModel() >>> mol_obj = MolWrapper.from_smiles("[NH3+]CC([O-])=O") >>> for atm_obj in mol_obj.get_atoms(): >>> formal_charge = cm.get_charge(atm_obj) >>> print("Charge for atom #%d (%s): %d" % (atm_obj.get_idx(), ... atm_obj.get_symbol(), ... formal_charge)) Charge for atom #0 (N): 1. Charge for atom #1 (C): 0. Charge for atom #2 (C): 0. Charge for atom #3 (O): -1. Charge for atom #4 (O): 0. """ formal_charge = None atm_num = atm_obj.get_atomic_num() valence = atm_obj.get_valence() # Hydrogen if atm_num == 1: if valence != 1: formal_charge = 1 elif valence == 1: formal_charge = 0 # Carbon elif atm_num == 6: if valence == 3: has_polar_nb = False nbs = atm_obj.get_neighbors() for nb_atm_obj in nbs: nb_atm_obj = AtomWrapper(nb_atm_obj) # Polar neighbor: N, O or S if nb_atm_obj.get_atomic_num() in (7, 8, 16): has_polar_nb = True break if has_polar_nb is True: formal_charge = 1 else: formal_charge = -1 elif valence == 4: formal_charge = 0 # Nitrogen elif atm_num == 7: if valence == 2: formal_charge = -1 elif valence == 4: formal_charge = 1 elif valence == 3: formal_charge = 0 # Oxygen elif atm_num == 8: if valence == 1: formal_charge = -1 elif valence == 3: formal_charge = 1 elif valence == 2: formal_charge = 0 # Phosphorus elif atm_num == 15: if valence == 4: formal_charge = 1 # Sulfur elif atm_num == 16: if valence == 1: formal_charge = -1 elif valence == 3: formal_charge = 1 elif valence == 5: formal_charge = -1 elif valence == 4: if atm_obj.get_degree() == 4: formal_charge = 2 elif valence == 2 or valence == 6: formal_charge == 0 # Chlorine elif atm_num == 17: if valence == 0: formal_charge = -1 elif valence == 4: formal_charge = 3 elif valence == 1: formal_charge = 0 # Fluorine, Bromine, Iodine elif atm_num == 9 or atm_num == 35 or atm_num == 53: if valence == 0: formal_charge = -1 elif valence == 1: formal_charge = 0 # Magnesium, Calcium, Zinc elif atm_num == 12 or atm_num == 20 or atm_num == 30: if valence == 0: formal_charge = 2 elif valence == 2: formal_charge = 0 # Lithium, Sodium, Potassium elif atm_num == 3 or atm_num == 11 or atm_num == 19: if valence == 0: formal_charge = 1 elif valence == 1: formal_charge = 0 # Boron elif atm_num == 5: # If the valence is four, the formal charge is -1. # OBS: there is an error in OpenEye chargel model text, they said that Boron should have a # charge of +1 when the valence is 4. However, the MDL Valence Model says it should be -1. if valence == 4: formal_charge = -1 elif valence == 3: formal_charge = 0 # Iron: 26 # If the valence is zero, the formal charge is +3 if the partial charge is 3.0, and +2 otherwise. # Copper: 29 # If the valence is zero, the formal charge is +2 if the partial charge is 2.0, and +1 otherwise. # else: # For the remaining elements, if the valence of an atom is zero, its formal charge is set from its partial charge. return formal_charge