def __init__(self, system, model, fn_traj=None): ''' **Arguments** system An instance of the System class and contains all the system information model An instance of the Model class and contains all the info to define the total PES and its electrostatic contribution. **Optional Arguments** fn_traj A file name to store the perturbation trajectories to or to read the trajectories from if the file exists. The trajectories are stored/read after Pickling. ''' self.system = system self.model = model self.pert_theory = RelaxedGeoPertTheory(system, model) self.cost = HessianFCCost(system, model) self.fn_traj = fn_traj #SHLL 1508 #-- new attributes for storing potential types self.stretch_pot_kind = system.stretch_pot_kind self.bend_pot_kind = system.bend_pot_kind
class Program(object): ''' The central class to manage the entire program. ''' def __init__(self, system, model, fn_traj=None): ''' **Arguments** system An instance of the System class and contains all the system information model An instance of the Model class and contains all the info to define the total PES and its electrostatic contribution. **Optional Arguments** fn_traj A file name to store the perturbation trajectories to or to read the trajectories from if the file exists. The trajectories are stored/read after Pickling. ''' self.system = system self.model = model self.pert_theory = RelaxedGeoPertTheory(system, model) self.cost = HessianFCCost(system, model) self.fn_traj = fn_traj #SHLL 1508 #-- new attributes for storing potential types self.stretch_pot_kind = system.stretch_pot_kind self.bend_pot_kind = system.bend_pot_kind #SHLL end def generate_trajectories(self, skip_dihedrals=True, verbose=True): ''' Generate a perturbation trajectory for all ics (dihedrals can be excluded and store the coordinates in a dictionary. **Optional Arguments** skip_dihedrals If set to True, the dihedral ff parameters will not be calculated. ''' maxlength = max([len(icname) for icname in self.model.val.pot.terms.keys()]) + 2 #Check if a filename with trajectories is given. If the file exists, #read it and return the trajectories if self.fn_traj is not None: if os.path.isfile(self.fn_traj): with open(self.fn_traj,'r') as f: trajectories = cPickle.load(f) return trajectories #Generate trajectories from scratch trajectories = {} all_ics = [] for icname in sorted(self.model.val.pot.terms.keys()): if skip_dihedrals and icname.startswith('dihed'): continue ics = self.system.ics[icname] for ic in ics: all_ics.append(ic) if verbose: print ' %s will generate %i trajectories' %( icname+' '*(maxlength-len(icname)), len(ics) ) results = paracontext.map(self.pert_theory.generate, all_ics) for i in xrange(len(all_ics)): trajectories[all_ics[i].name] = results[i] #Check if we need to write the generated trajectories to a file if self.fn_traj is not None: with open(self.fn_traj,'w') as f: cPickle.dump(trajectories,f) return trajectories #SHLL 1508 #-- extra parameters not needed anymore # def estimate_from_pt(self, trajectories, skip_dihedrals=True, verbose=True, stretch_pot_kind='harmonic', bend_pot_kind='harmonic'): #original def estimate_from_pt(self, trajectories, skip_dihedrals=True, verbose=True): #SHLL end ''' Second Step of force field development: calculate harmonic force field parameters for every internal coordinate separately from perturbation trajectories. **Arguments** trajectories A dictionairy containing numpy arrays representing perturbation trajectories for each icname. **Optional Arguments** skip_dihedrals If set to True, the dihedral ff parameters will not be calculated. ''' ff = FFTable() #SHLL 1606 #-- transfer potential info from program to fftable ff.stretch_pot_kind = self.stretch_pot_kind ff.bend_pot_kind = self.bend_pot_kind #SHLL end maxlength = max([len(icname) for icname in self.model.val.pot.terms.keys()]) + 2 for icname in sorted(self.model.val.pot.terms.keys()): ics = self.system.ics[icname] if skip_dihedrals and icname.startswith('dihed'): continue ks = DataArray(unit=ics[0].kunit) # print 'kunit:',ics[0].kunit #GBdebug q0s = DataArray(unit=ics[0].qunit) for ic in ics: #SHLL 1508 #-- estimate spf or harmcos potential params if icname.startswith('bond') and self.stretch_pot_kind.lower()=='spf': k, q0 = self.pert_theory.estimate(ic,trajectories[ic.name], pot_kind=self.stretch_pot_kind) elif icname.startswith('angle') and self.bend_pot_kind.lower()=='harmcos': k, q0 = self.pert_theory.estimate(ic,trajectories[ic.name], pot_kind=self.bend_pot_kind) else: k, q0 = self.pert_theory.estimate(ic,trajectories[ic.name], pot_kind='harmonic') #original # k, q0 = self.pert_theory.estimate(ic,trajectories[ic.name]) #SHLL end ks.append(k) q0s.append(q0) ff.add(icname, ks, q0s) descr = icname + ' '*(maxlength-len(icname)) if verbose: print ' %s K = %s q0 = %s' % ( descr, ks.string(), q0s.string() ) self.model.val.update_fftable(ff) return ff def refine_cost(self, verbose=True): ''' Second step of force field development: refine the force constants using a Hessian least squares cost function. ''' fcs = self.cost.estimate() self.model.val.update_fcs(fcs) fftab = self.model.val.get_fftable() #SHLL 1606 #-- transfer potential info from program to fftable fftab.stretch_pot_kind = self.stretch_pot_kind fftab.bend_pot_kind = self.bend_pot_kind #SHLL end if verbose: fftab.print_screen() return fftab def run(self): ''' Run all steps of the QuickFF methodology to derive a covalent force field. This method returns an instance of the class :class:`quickff.fftable.FFTable`, which contains all force field parameters. ''' print header print sysinfo() print '~'*120+'\n' print 'System information:\n' self.system.print_atom_info() print '\nModel information:\n' self.model.print_info() print '\nDetermine dihedral potentials\n' self.model.val.determine_dihedral_potentials(self.system) print '\nDetermine the coordinates of the perturbation trajectories\n' self.trajectories = self.generate_trajectories() print '\nEstimating all pars for bonds, bends and opdists\n' fftab = self.estimate_from_pt(self.trajectories) print '\nRefining force constants using a Hessian LSQ cost\n' fftab = self.refine_cost() print '\n'+'~'*120+'\n' print 'Time: ' + datetime.datetime.now().isoformat().replace('T', ' ') + '\n' print footer return fftab def plot_pt(self, icname, start=None, end=None, steps=51, verbose=True): ''' Generate the perturbation trajectories and plot the energy contributions along the trajectory for all ics with a name compatible with icname. **Arguments** icname A string describing for which ics the perturbation trajectories should be generated. ''' #Logging if verbose: print header print sysinfo() print '~'*120+'\n' print 'System information:\n' self.system.print_atom_info() print '\nModel information:\n' self.model.print_info() print '\nDetermine the coordinates of the perturbation trajectories\n' #Reading/generating trajectories trajectories = {} if self.fn_traj is not None: if os.path.isfile(self.fn_traj): with open(self.fn_traj,'r') as f: trajectories = cPickle.load(f) for i, ic in enumerate(self.system.ics[icname]): if ic.name in trajectories.keys(): #already read if verbose: print ' %s Read %2i/%i from %s' %( icname, i+1, len(self.system.ics[icname]), self.fn_traj ) else: #generating if verbose: sys.stdout.write(' %s Generating %2i/%i' %( icname, i+1, len(self.system.ics[icname]) )) sys.stdout.flush() try: trajectories[ic.name] = self.pert_theory.generate(ic, start=start, end=end, steps=51) print '' except KeyboardInterrupt: if verbose: sys.stdout.write(' INTERRUPTED\n') sys.stdout.flush() #Writing trajectories if self.fn_traj is not None: with open(self.fn_traj,'w') as f: cPickle.dump(trajectories, f) #Plotting/writing output for ic in self.system.ics[icname]: if ic.name in trajectories.keys(): name = ic.name.replace('/', '-') self.pert_theory.plot(ic, trajectories[ic.name], 'energies-'+name+'.pdf') self.pert_theory.write(trajectories[ic.name], 'trajectory-'+name+'.xyz') #Logging if verbose: print '' print '\n'+'~'*120+'\n' print 'Time: ' + datetime.datetime.now().isoformat().replace('T', ' ') + '\n' print footer
def do_hc_estimatefc(self, tasks, logger_level=3): ''' Refine force constants using Hessian Cost function. **Arguments** tasks A list of strings identifying which terms should have their force constant estimated from the hessian cost function. Using such a flag, one can distinguish between for example force constant refinement (flag=HC_FC_DIAG) of the diagonal terms and force constant estimation of the cross terms (flag=HC_FC_CROSS). If the string 'all' is present in tasks, all fc's will be estimated. **Optional Arguments** logger_level print level at which the resulting parameters should be dumped to the logger. By default, the parameters will only be dumped at the highest log level. ''' with log.section('HCEST', 2, timer='HC Estimate FC'): self.reset_system() log.dump( 'Estimating force constants from Hessian cost for tasks %s' % ' '.join(tasks)) ffrefs = self.kwargs.get('ffrefs', []) term_indices = [] for index in xrange(self.valence.vlist.nv): term = self.valence.terms[index] flagged = False for flag in tasks: if flag in term.tasks: flagged = True break if flagged: #first check if all rest values and multiplicities have been defined if term.kind == 0: self.valence.check_params(term, ['rv']) if term.kind == 1: self.valence.check_params(term, ['a0', 'a1', 'a2', 'a3']) if term.kind == 3: self.valence.check_params(term, ['rv0', 'rv1']) if term.kind == 4: self.valence.check_params(term, ['rv', 'm']) if term.is_master(): term_indices.append(index) else: #first check if all pars have been defined if term.kind == 0: self.valence.check_params(term, ['fc', 'rv']) if term.kind == 1: self.valence.check_params(term, ['a0', 'a1', 'a2', 'a3']) if term.kind == 3: self.valence.check_params(term, ['fc', 'rv0', 'rv1']) if term.kind == 4: self.valence.check_params(term, ['fc', 'rv', 'm']) cost = HessianFCCost(self.system, self.ai, self.valence, term_indices, ffrefs=ffrefs) fcs = cost.estimate() for index, fc in zip(term_indices, fcs): master = self.valence.terms[index] assert master.is_master() self.valence.set_params(index, fc=fc) for islave in master.slaves: self.valence.set_params(islave, fc=fc) self.valence.dump_logger(print_level=logger_level)
def do_hc_estimatefc(self, tasks, logger_level=3, do_svd=False, svd_rcond=0.0, do_mass_weighting=True): ''' Refine force constants using Hessian Cost function. **Arguments** tasks A list of strings identifying which terms should have their force constant estimated from the hessian cost function. Using such a flag, one can distinguish between for example force constant refinement (flag=HC_FC_DIAG) of the diagonal terms and force constant estimation of the cross terms (flag=HC_FC_CROSS). If the string 'all' is present in tasks, all fc's will be estimated. **Optional Arguments** logger_level print level at which the resulting parameters should be dumped to the logger. By default, the parameters will only be dumped at the highest log level. do_svd whether or not to do an SVD decomposition before solving the set of equations and explicitly throw out the degrees of freedom that correspond to the lowest singular values. do_mass_weighting whether or not to apply mass weighing to the ab initio hessian and the force field contributions before doing the fitting. ''' with log.section('HCEST', 2, timer='HC Estimate FC'): self.reset_system() log.dump( 'Estimating force constants from Hessian cost for tasks %s' % ' '.join(tasks)) term_indices = [] for index in range(self.valence.vlist.nv): term = self.valence.terms[index] flagged = False for flag in tasks: if flag in term.tasks: flagged = True break if flagged: #first check if all rest values and multiplicities have been defined if term.kind == 0: self.valence.check_params(term, ['rv']) if term.kind == 1: self.valence.check_params(term, ['a0', 'a1', 'a2', 'a3']) if term.kind == 3: self.valence.check_params(term, ['rv0', 'rv1']) if term.kind == 4: self.valence.check_params(term, ['rv', 'm']) if term.is_master(): term_indices.append(index) else: #first check if all pars have been defined if term.kind == 0: self.valence.check_params(term, ['fc', 'rv']) if term.kind == 1: self.valence.check_params(term, ['a0', 'a1', 'a2', 'a3']) if term.kind == 3: self.valence.check_params(term, ['fc', 'rv0', 'rv1']) if term.kind == 4: self.valence.check_params(term, ['fc', 'rv', 'm']) if len(term_indices) == 0: log.dump( 'No terms (with task in %s) found to estimate FC from HC' % (str(tasks))) return # Try to estimate force constants; if the remove_dysfunctional_cross # keyword is True, a loop is performed which checks whether there # are cross terms for which corresponding diagonal terms have zero # force constants. If this is the case, those cross terms are removed # from the fit and we try again until such cases do no longer occur max_iter = 100 niter = 0 while niter < max_iter: cost = HessianFCCost(self.system, self.ai, self.valence, term_indices, ffrefs=self.ffrefs, do_mass_weighting=do_mass_weighting) fcs = cost.estimate(do_svd=do_svd, svd_rcond=svd_rcond) # No need to continue, if cross terms with corresponding diagonal # terms with negative force constants are allowed if self.settings.remove_dysfunctional_cross is False: break to_remove = [] for index, fc in zip(term_indices, fcs): term = self.valence.terms[index] if term.basename.startswith('Cross'): # Find force constants of corresponding diagonal terms diag_fcs = np.zeros((2)) for idiag in range(2): diag_index = term.diag_term_indexes[idiag] if diag_index in term_indices: fc_diag = fcs[term_indices.index(diag_index)] else: fc_diag = self.valence.get_params(diag_index, only='fc') diag_fcs[idiag] = fc_diag # If a force constant from any corresponding diagonal term is negative, # we remove the cross term for the next iteration if np.any(diag_fcs <= 0.0): to_remove.append(index) self.valence.set_params(index, fc=0.0) log.dump( 'WARNING! Dysfunctional cross term %s detected, removing from the hessian fit.' % term.basename) if len(to_remove) == 0: break else: for index in to_remove: term_indices.remove(index) niter += 1 assert niter < max_iter, "Could not remove all dysfunctional cross terms in %d iterations, something is seriously wrong" % max_iter for index, fc in zip(term_indices, fcs): master = self.valence.terms[index] assert master.is_master() self.valence.set_params(index, fc=fc) for islave in master.slaves: self.valence.set_params(islave, fc=fc) self.valence.dump_logger(print_level=logger_level)
def do_hc_estimatefc(self, tasks, logger_level=3, do_svd=False, svd_rcond=0.0, do_mass_weighting=True): ''' Refine force constants using Hessian Cost function. **Arguments** tasks A list of strings identifying which terms should have their force constant estimated from the hessian cost function. Using such a flag, one can distinguish between for example force constant refinement (flag=HC_FC_DIAG) of the diagonal terms and force constant estimation of the cross terms (flag=HC_FC_CROSS). If the string 'all' is present in tasks, all fc's will be estimated. **Optional Arguments** logger_level print level at which the resulting parameters should be dumped to the logger. By default, the parameters will only be dumped at the highest log level. do_svd whether or not to do an SVD decomposition before solving the set of equations and explicitly throw out the degrees of freedom that correspond to the lowest singular values. do_mass_weighting whether or not to apply mass weighing to the ab initio hessian and the force field contributions before doing the fitting. ''' with log.section('HCEST', 2, timer='HC Estimate FC'): self.reset_system() log.dump('Estimating force constants from Hessian cost for tasks %s' %' '.join(tasks)) term_indices = [] for index in range(self.valence.vlist.nv): term = self.valence.terms[index] flagged = False for flag in tasks: if flag in term.tasks: flagged = True break if flagged: #first check if all rest values and multiplicities have been defined if term.kind==0: self.valence.check_params(term, ['rv']) if term.kind==1: self.valence.check_params(term, ['a0', 'a1', 'a2', 'a3']) if term.kind==3: self.valence.check_params(term, ['rv0','rv1']) if term.kind==4: self.valence.check_params(term, ['rv', 'm']) if term.is_master(): term_indices.append(index) else: #first check if all pars have been defined if term.kind==0: self.valence.check_params(term, ['fc', 'rv']) if term.kind==1: self.valence.check_params(term, ['a0', 'a1', 'a2', 'a3']) if term.kind==3: self.valence.check_params(term, ['fc', 'rv0','rv1']) if term.kind==4: self.valence.check_params(term, ['fc', 'rv', 'm']) if len(term_indices)==0: log.dump('No terms (with task in %s) found to estimate FC from HC' %(str(tasks))) return # Try to estimate force constants; if the remove_dysfunctional_cross # keyword is True, a loop is performed which checks whether there # are cross terms for which corresponding diagonal terms have zero # force constants. If this is the case, those cross terms are removed # from the fit and we try again until such cases do no longer occur max_iter = 100 niter = 0 while niter<max_iter: cost = HessianFCCost(self.system, self.ai, self.valence, term_indices, ffrefs=self.ffrefs, do_mass_weighting=do_mass_weighting) fcs = cost.estimate(do_svd=do_svd, svd_rcond=svd_rcond) # No need to continue, if cross terms with corresponding diagonal # terms with negative force constants are allowed if self.settings.remove_dysfunctional_cross is False: break to_remove = [] for index, fc in zip(term_indices, fcs): term = self.valence.terms[index] if term.basename.startswith('Cross'): # Find force constants of corresponding diagonal terms diag_fcs = np.zeros((2)) for idiag in range(2): diag_index = term.diag_term_indexes[idiag] if diag_index in term_indices: fc_diag = fcs[term_indices.index(diag_index)] else: fc_diag = self.valence.get_params(diag_index, only='fc') diag_fcs[idiag] = fc_diag # If a force constant from any corresponding diagonal term is negative, # we remove the cross term for the next iteration if np.any(diag_fcs<=0.0): to_remove.append(index) self.valence.set_params(index, fc=0.0) log.dump('WARNING! Dysfunctional cross term %s detected, removing from the hessian fit.'%term.basename) if len(to_remove)==0: break else: for index in to_remove: term_indices.remove(index) niter += 1 assert niter<max_iter, "Could not remove all dysfunctional cross terms in %d iterations, something is seriously wrong"%max_iter for index, fc in zip(term_indices, fcs): master = self.valence.terms[index] assert master.is_master() self.valence.set_params(index, fc=fc) for islave in master.slaves: self.valence.set_params(islave, fc=fc) self.valence.dump_logger(print_level=logger_level)
def do_hc_estimatefc(self, tasks, logger_level=3, do_svd=False, do_mass_weighting=True): ''' Refine force constants using Hessian Cost function. **Arguments** tasks A list of strings identifying which terms should have their force constant estimated from the hessian cost function. Using such a flag, one can distinguish between for example force constant refinement (flag=HC_FC_DIAG) of the diagonal terms and force constant estimation of the cross terms (flag=HC_FC_CROSS). If the string 'all' is present in tasks, all fc's will be estimated. **Optional Arguments** logger_level print level at which the resulting parameters should be dumped to the logger. By default, the parameters will only be dumped at the highest log level. do_svd whether or not to do an SVD decomposition before solving the set of equations and explicitly throw out the degrees of freedom that correspond to the lowest singular values. do_mass_weighting whether or not to apply mass weighing to the ab initio hessian and the force field contributions before doing the fitting. ''' with log.section('HCEST', 2, timer='HC Estimate FC'): self.reset_system() log.dump( 'Estimating force constants from Hessian cost for tasks %s' % ' '.join(tasks)) term_indices = [] for index in range(self.valence.vlist.nv): term = self.valence.terms[index] flagged = False for flag in tasks: if flag in term.tasks: flagged = True break if flagged: #first check if all rest values and multiplicities have been defined if term.kind == 0: self.valence.check_params(term, ['rv']) if term.kind == 1: self.valence.check_params(term, ['a0', 'a1', 'a2', 'a3']) if term.kind == 3: self.valence.check_params(term, ['rv0', 'rv1']) if term.kind == 4: self.valence.check_params(term, ['rv', 'm']) if term.is_master(): term_indices.append(index) else: #first check if all pars have been defined if term.kind == 0: self.valence.check_params(term, ['fc', 'rv']) if term.kind == 1: self.valence.check_params(term, ['a0', 'a1', 'a2', 'a3']) if term.kind == 3: self.valence.check_params(term, ['fc', 'rv0', 'rv1']) if term.kind == 4: self.valence.check_params(term, ['fc', 'rv', 'm']) if len(term_indices) == 0: log.dump( 'No terms (with task in %s) found to estimate FC from HC' % (str(tasks))) return cost = HessianFCCost(self.system, self.ai, self.valence, term_indices, ffrefs=self.ffrefs, do_mass_weighting=do_mass_weighting) fcs = cost.estimate(do_svd=do_svd) for index, fc in zip(term_indices, fcs): master = self.valence.terms[index] assert master.is_master() self.valence.set_params(index, fc=fc) for islave in master.slaves: self.valence.set_params(islave, fc=fc) self.valence.dump_logger(print_level=logger_level)