Source code for psiresp.resp

from typing import Optional
import warnings

import numpy as np
from pydantic import Field

from psiresp import base, charge
from psiresp.constraint import (ESPSurfaceConstraintMatrix,
                                SparseGlobalConstraintMatrix)


class BaseRespOptions(base.Model):
    """Base RESP options"""
    resp_b: float = Field(default=0.1,
                          description="Tightness of hyperbolic penalty at its minimum")
    restrained_fit: bool = Field(default=True,
                                 description="Perform a restrained fit")
    exclude_hydrogens: bool = Field(
        default=True,
        description="if True, exclude hydrogens from restraint",
    )
    convergence_tolerance: float = Field(
        default=1e-6,
        description="threshold for convergence",
    )
    max_iter: int = Field(
        default=500,
        description="max number of iterations to solve constraint matrices",
    )


[docs]class RespOptions(BaseRespOptions): resp_a1: float = Field( default=0.0005, description=("scale factor of asymptote limits of hyperbola, " "in the stage 1 fit"), ) resp_a2: float = Field( default=0.001, description=("scale factor of asymptote limits of hyperbola, " "in the stage 2 fit"), ) stage_2: bool = True @property def _base_kwargs(self): return {k: getattr(self, k) for k in BaseRespOptions.__fields__}
[docs]class RespCharges(BaseRespOptions): """Self-contained class to solve RESP charges with charge constraints""" resp_a: float = Field(default=0.0005, description="scale factor of asymptote limits of hyperbola") _restrained_charges: Optional[np.ndarray] = None _unrestrained_charges: Optional[np.ndarray] = None _matrix: Optional[SparseGlobalConstraintMatrix] = None charge_constraints: charge.MoleculeChargeConstraints surface_constraints: ESPSurfaceConstraintMatrix def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._matrix = SparseGlobalConstraintMatrix.from_constraints( surface_constraints=self.surface_constraints, charge_constraints=self.charge_constraints, exclude_hydrogens=self.exclude_hydrogens, )
[docs] def solve(self): self._matrix._solve() self._unrestrained_charges = self._matrix._charges.flatten() if not self.restrained_fit or not self.resp_a: return n_iter = 0 b2 = self.resp_b ** 2 while (self._matrix.charge_difference > self.convergence_tolerance and n_iter < self.max_iter): self._matrix._iter_solve(self.resp_a, self.resp_b, b2) n_iter += 1 self._matrix._iter_solve(self.resp_a, self.resp_b, b2) if self._matrix.charge_difference > self.convergence_tolerance: warnings.warn("Charge fitting did not converge to " f"convergence_tolerance={self.convergence_tolerance} " f"with max_iter={self.max_iter}") self._restrained_charges = self._matrix._charges.flatten()
@property def restrained_charges(self): if self._restrained_charges is None: return None return self.charge_constraints._index_array(self._restrained_charges) @property def unrestrained_charges(self): if self._unrestrained_charges is None: return None return self.charge_constraints._index_array(self._unrestrained_charges) @property def _charges(self): if self._restrained_charges is not None: return self._restrained_charges return self._unrestrained_charges @property def charges(self): return self.charge_constraints._index_array(self._charges)