def estimate(self, ic, trajectory, pot_kind='harmonic'): #original # def estimate(self, ic, trajectory): #SHLL end ''' Method to estimate the FF parameters for the given ic from the given perturbation trajectory by fitting a harmonic potential to the covalent energy along the trajectory. **Arguments** ic an instance of the class :class:`quickff.ic.IC` defining for which ic the FF parameters will be estimated trajectory a (F,N,3) numpy array defining the perturbation trajectory associated to the given ic. It contains F frames of (N,3)-dimensional geometry arrays. ''' evaluators = [eval_ic(ic), eval_energy('ai'), eval_energy('ei'), eval_energy('vdw')] qs, tot, ei, vdw = self.analyze(trajectory, evaluators) #SHLL 1508 #-- fitting params for spf or harmcos potentials pars = fitpar(qs, tot-ei-vdw, rcond=1e-6, pot_kind=pot_kind) if pot_kind.lower()=='spf': #0.5*k*(1-q0/q)**2+c = 0.5*k+c -k*q0/q +k/2*q0**2/q**2 = c0+c1/q+c2/q**2 #k=-c1/q0=c1**2/2/c2, q0=-2*c2/c1 #return pars[1]**2/2/pars[2]/1.889716165**2, -2*pars[2]/pars[1] return 2*pars[0], pars[1] if pot_kind.lower()=='harmcos': return 2*pars[0], pars[1] else: return 2*pars[0], -pars[1]/(2*pars[0])
def add_plot(xs, ys, prefix, kwargs): pars = fitpar(xs, ys, rcond=1e-6) k = 2*pars[0] if k==0: q0 = np.nan else: q0 = -pars[1]/k label = '%s (K=%.0f q0=%.3f)' %(prefix, k/parse_unit(self.kunit), q0/parse_unit(self.qunit)) kwargs['label'] = label ax.plot(xs/parse_unit(self.qunit), ys/parse_unit(eunit), **kwargs)
def estimate(self, trajectory, ai, ffrefs=[], do_valence=False): ''' Method to estimate the FF parameters for the relevant ic from the given perturbation trajectory by fitting a harmonic potential to the covalent energy along the trajectory. **Arguments** trajectory a Trajectory instance representing the perturbation trajectory ai an instance of the Reference representing the ab initio input **Optional Arguments** ffrefs a list of Reference instances representing possible a priori determined contributions to the force field (such as eg. electrostatics and van der Waals) do_valence If set to True, the current valence force field (stored in self.valence) will be used to compute the valence contribution ''' with log.section('PTEST', 3, timer='PT Estimate'): term = trajectory.term index = term.index basename = term.basename if 'active' in trajectory.__dict__.keys( ) and not trajectory.active: log.dump('Trajectory of %s was deactivated: skipping' % (basename)) return qs = trajectory.values.copy() AIs = np.zeros(len(trajectory.coords)) FFs = np.zeros(len(trajectory.coords)) RESs = np.zeros(len(trajectory.coords)) for istep, pos in enumerate(trajectory.coords): AIs[istep] = ai.energy(pos) for ref in ffrefs: FFs[istep] += ref.energy(pos) if do_valence: fc = self.valence.get_params(index, only='fc') rv = self.valence.get_params(index, only='rv') self.valence.set_params(index, fc=0.0) self.valence.set_params(index, rv0=0.0) for istep, pos in enumerate(trajectory.coords): RESs[istep] += self.valence.calc_energy( pos) #- 0.5*fc*(qs[istep]-rv)**2 self.valence.set_params(index, fc=fc) self.valence.set_params(index, rv0=rv) pars = fitpar(qs, AIs - FFs - RESs - min(AIs - FFs - RESs), rcond=-1) if pars[0] != 0.0: trajectory.fc = 2.0 * pars[0] trajectory.rv = -pars[1] / (2.0 * pars[0]) else: trajectory.fc = 0.0 trajectory.rv = qs[len(qs) / 2] log.dump( 'force constant of %s is zero: rest value set to middle value' % basename) #no negative rest values for all ics except dihedrals and bendcos if term.ics[0].kind not in [1, 3, 4, 11]: if trajectory.rv < 0: trajectory.rv = 0.0 log.dump('rest value of %s was negative: set to zero' % basename)
def estimate(self, trajectory, ai, ffrefs=[], do_valence=False, energy_noise=None, Nerrorsteps=100): ''' Method to estimate the FF parameters for the relevant ic from the given perturbation trajectory by fitting a harmonic potential to the covalent energy along the trajectory. **Arguments** trajectory a Trajectory instance representing the perturbation trajectory ai an instance of the Reference representing the ab initio input **Optional Arguments** ffrefs a list of Reference instances representing possible a priori determined contributions to the force field (such as eg. electrostatics and van der Waals) do_valence If set to True, the current valence force field (stored in self.valence) will be used to compute the valence contribution energy_noise If set to a float, the parabolic fitting will be repeated Nerrorsteps times including normal noise on top of the reference value. The mean of the noise is 0, while the std equals the number given by energy_noise. The resulting fits give a distribution of force constants and rest values instead of single value, the std is used to identify bad estimates, the mean is used for the actual FF parametrs. If set to nan, the parabolic fit is performed only once without any noise. ''' with log.section('PTEST', 3, timer='PT Estimate'): term = trajectory.term index = term.index basename = term.basename if 'active' in list(trajectory.__dict__.keys()) and not trajectory.active: log.dump('Trajectory of %s was deactivated: skipping' %(basename)) return qs = trajectory.values.copy() AIs = np.zeros(len(trajectory.coords)) FFs = np.zeros(len(trajectory.coords)) RESs = np.zeros(len(trajectory.coords)) for istep, pos in enumerate(trajectory.coords): AIs[istep] = ai.energy(pos) for ref in ffrefs: FFs[istep] += ref.energy(pos) if do_valence: fc = self.valence.get_params(index, only='fc') rv = self.valence.get_params(index, only='rv') self.valence.set_params(index, fc=0.0) self.valence.set_params(index, rv0=0.0) for istep, pos in enumerate(trajectory.coords): RESs[istep] += self.valence.calc_energy(pos) #- 0.5*fc*(qs[istep]-rv)**2 self.valence.set_params(index, fc=fc) self.valence.set_params(index, rv0=rv) pars = fitpar(qs, AIs-FFs-RESs-min(AIs-FFs-RESs), rcond=-1) if energy_noise is None: if pars[0]>0.0: #!=0.0: trajectory.fc = 2.0*pars[0] trajectory.rv = -pars[1]/(2.0*pars[0]) else: trajectory.fc = 0.0 trajectory.rv = qs[len(qs)//2] log.dump('force constant of %s is not positive: force constant set to zero and rest value set to ab initio equilibrium' %basename) else: with log.section('PTEST', 4, timer='PT Estimate'): log.dump('Performing noise analysis for trajectory of %s' %basename) As = [pars[0]] Bs = [pars[1]] for i in range(Nerrorsteps): pars = fitpar(qs, AIs-FFs-RESs-min(AIs-FFs-RESs)+np.random.normal(0.0, energy_noise, size=AIs.shape), rcond=-1) As.append(pars[0]) Bs.append(pars[1]) if 0.0 in As: log.dump(' force constant of zero detected, removing the relevant runs from analysis') Bs = np.array([b for a,b in zip(As,Bs) if a!=0.0]) As = np.array([a for a in As if a!=0.0]) ks = As*2.0 q0s = -Bs/(2.0*As) kunit = trajectory.term.units[0] qunit = trajectory.term.units[1] log.dump(' k = %8.3f +- %6.3f (noisefree: %8.3f) %s' %(ks.mean()/parse_unit(kunit), ks.std()/parse_unit(kunit), ks[0]/parse_unit(kunit), kunit)) log.dump(' q0 = %8.3f +- %6.3f (noisefree: %8.3f) %s' %(q0s.mean()/parse_unit(qunit), q0s.std()/parse_unit(qunit), q0s[0]/parse_unit(qunit), qunit)) if q0s.std()/q0s.mean()>0.01: with log.section('PTEST', 3, timer='PT Estimate'): fc, rv = self.valence.get_params(trajectory.term.index) if rv is None: log.dump('Noise on rest value of %s to high, using ab initio rest value' %basename) pars = fitpar(qs, AIs-FFs-RESs-min(AIs-FFs-RESs)+np.random.normal(0.0, energy_noise, size=AIs.shape), rcond=-1) if pars[0]!=0.0: trajectory.fc = 2.0*pars[0] trajectory.rv = -pars[1]/(2.0*pars[0]) else: trajectory.fc = 0.0 trajectory.rv = qs[len(qs)//2] log.dump('AI force constant of %s is zero: rest value set to middle value' %basename) else: log.dump('Noise on rest value of %s to high, using previous value' %basename) trajectory.fc = fc trajectory.rv = rv else: trajectory.fc = ks.mean() trajectory.rv = q0s.mean() #no negative rest values for all ics except dihedrals and bendcos if term.ics[0].kind not in [1,3,4,11]: if trajectory.rv<0: trajectory.rv = 0.0 log.dump('rest value of %s was negative: set to zero' %basename)
def estimate(self, trajectory, ai, ffrefs=[], do_valence=False, energy_noise=None, Nerrorsteps=100): ''' Method to estimate the FF parameters for the relevant ic from the given perturbation trajectory by fitting a harmonic potential to the covalent energy along the trajectory. **Arguments** trajectory a Trajectory instance representing the perturbation trajectory ai an instance of the Reference representing the ab initio input **Optional Arguments** ffrefs a list of Reference instances representing possible a priori determined contributions to the force field (such as eg. electrostatics and van der Waals) do_valence If set to True, the current valence force field (stored in self.valence) will be used to compute the valence contribution energy_noise If set to a float, the parabolic fitting will be repeated Nerrorsteps times including normal noise on top of the reference value. The mean of the noise is 0, while the std equals the number given by energy_noise. The resulting fits give a distribution of force constants and rest values instead of single value, the std is used to identify bad estimates, the mean is used for the actual FF parametrs. If set to nan, the parabolic fit is performed only once without any noise. ''' with log.section('PTEST', 3, timer='PT Estimate'): term = trajectory.term index = term.index basename = term.basename if 'active' in list(trajectory.__dict__.keys()) and not trajectory.active: log.dump('Trajectory of %s was deactivated: skipping' %(basename)) return qs = trajectory.values.copy() AIs = np.zeros(len(trajectory.coords)) FFs = np.zeros(len(trajectory.coords)) RESs = np.zeros(len(trajectory.coords)) for istep, pos in enumerate(trajectory.coords): AIs[istep] = ai.energy(pos) for ref in ffrefs: FFs[istep] += ref.energy(pos) if do_valence: fc = self.valence.get_params(index, only='fc') rv = self.valence.get_params(index, only='rv') self.valence.set_params(index, fc=0.0) self.valence.set_params(index, rv0=0.0) for istep, pos in enumerate(trajectory.coords): RESs[istep] += self.valence.calc_energy(pos) #- 0.5*fc*(qs[istep]-rv)**2 self.valence.set_params(index, fc=fc) self.valence.set_params(index, rv0=rv) pars = fitpar(qs, AIs-FFs-RESs-min(AIs-FFs-RESs), rcond=-1) if energy_noise is None: if pars[0]!=0.0: trajectory.fc = 2.0*pars[0] trajectory.rv = -pars[1]/(2.0*pars[0]) else: trajectory.fc = 0.0 trajectory.rv = qs[len(qs)//2] log.dump('force constant of %s is zero: rest value set to middle value' %basename) else: with log.section('PTEST', 4, timer='PT Estimate'): log.dump('Performing noise analysis for trajectory of %s' %basename) As = [pars[0]] Bs = [pars[1]] for i in range(Nerrorsteps): pars = fitpar(qs, AIs-FFs-RESs-min(AIs-FFs-RESs)+np.random.normal(0.0, energy_noise, size=AIs.shape), rcond=-1) As.append(pars[0]) Bs.append(pars[1]) if 0.0 in As: log.dump(' force constant of zero detected, removing the relevant runs from analysis') Bs = np.array([b for a,b in zip(As,Bs) if a!=0.0]) As = np.array([a for a in As if a!=0.0]) ks = As*2.0 q0s = -Bs/(2.0*As) kunit = trajectory.term.units[0] qunit = trajectory.term.units[1] log.dump(' k = %8.3f +- %6.3f (noisefree: %8.3f) %s' %(ks.mean()/parse_unit(kunit), ks.std()/parse_unit(kunit), ks[0]/parse_unit(kunit), kunit)) log.dump(' q0 = %8.3f +- %6.3f (noisefree: %8.3f) %s' %(q0s.mean()/parse_unit(qunit), q0s.std()/parse_unit(qunit), q0s[0]/parse_unit(qunit), qunit)) if q0s.std()/q0s.mean()>0.01: with log.section('PTEST', 3, timer='PT Estimate'): fc, rv = self.valence.get_params(trajectory.term.index) if rv is None: log.dump('Noise on rest value of %s to high, using ab initio rest value' %basename) pars = fitpar(qs, AIs-FFs-RESs-min(AIs-FFs-RESs)+np.random.normal(0.0, energy_noise, size=AIs.shape), rcond=-1) if pars[0]!=0.0: trajectory.fc = 2.0*pars[0] trajectory.rv = -pars[1]/(2.0*pars[0]) else: trajectory.fc = 0.0 trajectory.rv = qs[len(qs)//2] log.dump('AI force constant of %s is zero: rest value set to middle value' %basename) else: log.dump('Noise on rest value of %s to high, using previous value' %basename) trajectory.fc = fc trajectory.rv = rv else: trajectory.fc = ks.mean() trajectory.rv = q0s.mean() #no negative rest values for all ics except dihedrals and bendcos if term.ics[0].kind not in [1,3,4,11]: if trajectory.rv<0: trajectory.rv = 0.0 log.dump('rest value of %s was negative: set to zero' %basename)
def plot(self, ic, trajectory, filename, eunit='kjmol'): ''' Method to plot the energy contributions along a perturbation trajectory associated to a given ic. **Arguments** ic an instance of the class :class:`quickff.ic.IC` defining for which ic the plot will be made. trajectory a (F,N,3) numpy array defining the perturbation trajectory associated to the given ic. It contains F frames of (N,3)-dimensional geometry arrays. filename a string defining the name of the figure **Optional Arguments** eunit a string describing the conversion of the unit of energy. More info regarding possible strings can be found in the `MolMod documentation <http://molmod.github.io/molmod/reference/const.html#module-molmod.units>`_. ''' import matplotlib.pyplot as pp evaluators = [eval_ic(ic), eval_energy('ai'), eval_energy('ei'), eval_energy('vdw')] qs, tot, ei, vdw = self.analyze(trajectory, evaluators) label = {} for (name, obs) in zip(['ai', 'ei', 'vdw', 'cov'], [tot, ei, vdw, tot-ei-vdw]): pars = fitpar(qs, obs, rcond=1e-6) k = 2*pars[0] if k != 0.0: q0 = -0.5*pars[1]/pars[0] label[name] = '(K=%.0f q0=%.3f)' %( k/parse_unit(ic.kunit), q0/parse_unit(ic.qunit) ) else: label[name] = '(K=0.0 q0=None)' if name=='cov': cov = (pars[0]*qs**2+pars[1]*qs+pars[2]) fig, ax = pp.subplots() ax.plot( qs/parse_unit(ic.qunit), tot/parse_unit(eunit), 'k--', linewidth=4, label='AI total %s' %label['ai'] ) ax.plot( qs/parse_unit(ic.qunit), ei/parse_unit(eunit), 'b--', linewidth=2, label='FF elec %s' %label['ei'] ) ax.plot( qs/parse_unit(ic.qunit), vdw/parse_unit(eunit), 'g--', linewidth=2, label='FF vdW %s' %label['vdw'] ) ax.plot( qs/parse_unit(ic.qunit), cov/parse_unit(eunit), 'r-', linewidth=2, label='FF cov %s' %label['cov'] ) ax.set_title(ic.name) ax.set_xlabel('%s [%s]' % (ic.name.split('/')[0], ic.qunit), fontsize=16) ax.set_ylabel('Energy [%s]' %eunit, fontsize=16) ax.grid() ax.legend(loc='best', fontsize=16) fig.set_size_inches([8, 8]) fig.savefig(filename)