示例#1
0
    def setUp(self):
        from parameterize.home import home
        from moleculekit.molecule import Molecule
        from parameterize.parameterization.detect import detectEquivalentAtoms
        from parameterize.parameterization.fftype import fftype

        molFile = os.path.join(home("test-param"), "glycol.mol2")
        mol = Molecule(molFile)
        self.equivalents = detectEquivalentAtoms(mol)
        _, self.mol = fftype(mol, method="GAFF2")
示例#2
0
    def setUp(self):
        from parameterize.home import home
        from moleculekit.molecule import Molecule

        molFile = os.path.join(home("test-param"), "H2O2.mol2")
        mol = Molecule(molFile, guessNE="bonds", guess=("angles", "dihedrals"))
        self.mol = mol
        self.esp = ESP()
        self.esp.molecule = self.mol

        # Precalculating here for the tests
        equivalents = detectEquivalentAtoms(self.esp.molecule)
        self.esp._equivalent_atom_groups = equivalents[0]
        self.esp._equivalent_group_by_atom = equivalents[2]
示例#3
0
def symmetrizeCharges(mol):
    """
    Average the charges of equivalent atoms

    Parameters
    ----------
    mol: Molecule
        Molecule to symmetrize the charges

    Return
    ------
    results: Molecule
        Copy of the molecule with the symmetrized charges

    Examples
    --------
    >>> from parameterize.home import home
    >>> from moleculekit.molecule import Molecule
    >>> molFile = os.path.join(home('test-charge'), 'H2O.mol2')
    >>> mol = Molecule(molFile)
    >>> mol.charge[:] = [0.5, -0.5, 0.0]

    >>> new_mol = symmetrizeCharges(mol)
    >>> assert new_mol is not mol
    >>> new_mol.charge
    array([ 0.5 , -0.25, -0.25], dtype=float32)
    """

    from parameterize.parameterization.detect import detectEquivalentAtoms

    if not isinstance(mol, Molecule):
        raise TypeError('"mol" has to be instance of {}'.format(Molecule))
    if mol.numFrames != 1:
        raise ValueError(
            '"mol" can have just one frame, but it has {}'.format(mol.numFrames)
        )

    mol = mol.copy()
    molCharge = np.sum(mol.charge)

    equivalent_groups, _, _ = detectEquivalentAtoms(mol)
    for atoms in equivalent_groups:
        atoms = list(atoms)
        mol.charge[atoms] = np.mean(mol.charge[atoms])

    assert np.isclose(np.sum(mol.charge), molCharge, atol=1e-6)

    return mol
示例#4
0
def inventAtomTypes(mol, fit_dihedrals):
    """
    Duplicate atom types of the dihedral, so its parameters are unique.
    """
    # TODO check symmetry

    from parameterize.parameterization.detect import detectEquivalentAtoms

    equivalents = detectEquivalentAtoms(mol)

    mol = mol.copy()

    alltypes = list(mol.atomtype)
    originaltype = {type: type for type in np.unique(mol.atomtype)}

    # Duplicate the atom types of the dihedral
    for dih in fit_dihedrals:
        for d in dih:
            oldtype = mol.atomtype[d]
            # if the type is already duplicated
            if re.match(_ATOM_TYPE_REG_EX, oldtype):
                continue
            # Create a new atom type name
            i = 0
            while ("{}x{}".format(oldtype, i)) in alltypes:
                i += 1
            newtype = "{}x{}".format(oldtype, i)
            alltypes.append(newtype)
            originaltype[newtype] = oldtype

            mol.atomtype[d] = newtype
            # Rename the atom types of the equivalent atoms
            for index in equivalents[1][d]:
                if index != d:
                    assert not re.match(_ATOM_TYPE_REG_EX, mol.atomtype[index])
                    mol.atomtype[index] = newtype

        equivalent_dihedrals = _getEquivalentDihedrals(mol, equivalents, np.array(dih))
        if len(equivalent_dihedrals) > 1:
            raise ValueError(
                "Dihedral term still not unique after duplication. Dihedral {} has {} equivalent "
                "dihedrals: {}".format(
                    dih, len(equivalent_dihedrals), equivalent_dihedrals
                )
            )

    return mol, originaltype
示例#5
0
    def run(self):
        """
        Run ESP charge fitting

        Return
        ------
        results : dict
            Dictionary with the fitted charges and fitting loss value
        """
        logger.info("Start RESP charge fitting")

        self._molecular_charge = self.qm_results[0].charge

        # Detect equivalent atoms
        equivalents = detectEquivalentAtoms(self.molecule)
        self._equivalent_atom_groups = equivalents[0]
        self._equivalent_group_by_atom = equivalents[2]

        # Set up heavy atom restrains
        self._restraint_factors = np.zeros(self.molecule.numAtoms)
        if self.restraint_factor > 0:
            for i, element in enumerate(self.molecule.element):
                if element != "H":
                    self._restraint_factors[i] = self.restraint_factor

        # Get charge bounds
        lower_bounds, upper_bounds = self._get_bounds()

        logger.info("Atom charge boundaries and restraint factor:")
        for i, name in enumerate(self.molecule.name):
            lower = lower_bounds[self._equivalent_group_by_atom[i]]
            upper = upper_bounds[self._equivalent_group_by_atom[i]]
            factor = self._restraint_factors[i]
            logger.info(
                "  {:4s}: {:7.3f} {:7.3f} {:10.6f}".format(name, lower, upper, factor)
            )

        # Set up optimizer
        opt = nlopt.opt(nlopt.LN_COBYLA, self.ngroups)
        logger.info("Optimizer: {}".format(opt.get_algorithm_name()))
        opt.set_min_objective(self._objective)
        opt.add_equality_constraint(self._constraint)
        logger.info(
            "Molecular charges constraint: {:.3f}".format(self._molecular_charge)
        )
        opt.set_lower_bounds(lower_bounds)
        opt.set_upper_bounds(upper_bounds)
        opt.set_xtol_rel(1.0e-6)
        opt.set_maxeval(1000 * self.ngroups)
        opt.set_initial_step(0.001)

        # Optimize the charges
        group_charges = opt.optimize(np.zeros(self.ngroups))
        status = opt.last_optimize_result()
        loss = self._objective(group_charges, None)
        charges = self._map_groups_to_atoms(group_charges)
        logger.info("Optimizer status: {}".format(status))
        logger.info("Final loss: {:.6f}".format(loss))

        # Compute RMSD
        self._restraint_factors[:] = 0
        msd = self._objective(group_charges, None)
        logger.info("Final RMSD: {:.6f} au".format(np.sqrt(msd)))

        logger.info("Finish RESP charge fitting")

        return {
            "charges": charges,
            "status": status,
            "loss": loss,
            "RMSD": np.sqrt(msd),
        }
示例#6
0
def _fit_charges(mol, args, qm, atom_types):

    from parameterize.charge import (
        fitGasteigerCharges,
        fitChargesWithAntechamber,
        fitESPCharges,
        symmetrizeCharges,
    )
    from parameterize.parameterization.util import (
        guessBondType,
        getFixedChargeAtomIndices,
        getDipole,
        _qm_method_name,
    )
    from parameterize.parameterization.detect import detectEquivalentAtoms

    logger.info("=== Atomic charge fitting ===")
    logger.info("Method: {}".format(args.charge_type))

    if args.charge_type == "None":

        # TODO move to argument validation
        if len(args.fix_charge) > 0:
            logger.warning("Flag --fix-charge does not have effect!")

        logger.info("Atomic charges are taken from {}".format(args.filename))

    elif args.charge_type == "Gasteiger":

        # TODO move to argument validation
        if len(args.fix_charge) > 0:
            logger.warning("Flag --fix-charge does not have effect!")

        # TODO move to _prepare_molecule
        if np.any(mol.bondtype == "un"):
            logger.info("Guessing bond types")
            mol = guessBondType(mol)

        mol = fitGasteigerCharges(mol, atom_types=atom_types)

        charge = int(round(np.sum(mol.charge)))
        if args.charge != charge:
            logger.warning(
                f"Molecular charge is {args.charge}, but Gasteiger atomic charges add up to {charge}!"
            )
            args.charge = charge

    elif args.charge_type == "AM1-BCC":

        # TODO move to argument validation
        if len(args.fix_charge) > 0:
            logger.warning("Flag --fix-charge does not have effect!")

        mol = fitChargesWithAntechamber(mol, type="bcc", molCharge=args.charge)
        mol = symmetrizeCharges(mol)

    elif args.charge_type == "ESP":

        # Detect equivalent atom groups
        logger.info("Equivalent atom groups:")
        atom_groups = [
            group for group in detectEquivalentAtoms(mol)[0] if len(group) > 1
        ]
        for atom_group in atom_groups:
            logger.info("    {}".format(", ".join(mol.name[list(atom_group)])))

        # Select the atoms with fixed charges
        fixed_atom_indices = getFixedChargeAtomIndices(mol, args.fix_charge)

        # Create an ESP directory
        espDir = os.path.join(args.outdir, "esp", _qm_method_name(qm))
        os.makedirs(espDir, exist_ok=True)

        charge = int(round(np.sum(mol.charge)))
        if args.charge != charge:
            logger.warning(
                "Molecular charge is set to {}, but atomic charges add up to {}"
                "".format(args.charge, charge))
            if len(args.fix_charge) > 0:
                raise RuntimeError(
                    "Flag --fix-charge cannot be used when atomic charges are inconsistent with passed "
                    "molecular charge {}".format(args.charge))
            mol.charge[:] = args.charge / mol.numAtoms

        # Set random number generator seed
        if args.seed:
            np.random.seed(args.seed)

        # Fit ESP charges
        mol, extra = fitESPCharges(mol, qm, espDir, fixed=fixed_atom_indices)

        # Print QM dipole
        logger.info(
            "QM dipole: {:6.3f} {:6.3f} {:6.3f}; total: {:6.3f}".format(
                *extra["qm_dipole"]))

    else:
        raise ValueError()

    # Print MM dipole
    mm_dipole = getDipole(mol)
    logger.info("MM dipole: {:6.3f} {:6.3f} {:6.3f}; total: {:6.3f}".format(
        *mm_dipole))

    # Print the new charges
    logger.info("Atomic charges:")
    for name, charge in zip(mol.name, mol.charge):
        logger.info("   {:4s}: {:6.3f}".format(name, charge))
    logger.info("Molecular charge: {:6.3f}".format(np.sum(mol.charge)))

    return mol