示例#1
0
 def gradient(self, X):
     '''
         Compute the gradient of the strain w.r.t. Cartesian coordinates of
         the system. For the ic that needs to be constrained, a Lagrange
         multiplier is included.
     '''
     #initialize return value
     grad = np.zeros((len(X),))
     #compute strain gradient
     gstrain = np.zeros(self.coords0.shape)
     self.update_pos(self.coords0 + X[:self.ndof].reshape((-1,3)))
     self.value = self.compute(gpos=gstrain)
     #compute constraint gradient
     gconstraint = np.zeros(self.coords0.shape)
     self.constraint.update_pos(self.coords0 + X[:self.ndof].reshape((-1,3)))
     self.constrain_value = self.constraint.compute(gpos=gconstraint) + 1.0
     #construct gradient
     grad[:self.ndof] = gstrain.reshape((-1,)) + X[self.ndof]*gconstraint.reshape((-1,))
     grad[self.ndof] = self.constrain_value - self.constrain_target
     #cartesian penalty, i.e. extra penalty for deviation w.r.t. cartesian equilibrium coords
     indices = np.array([[3*i,3*i+1,3*i+2] for i in range(self.ndof//3) if i not in self.cons_ic_atindexes]).ravel()
     if len(indices)>0:
         grad[indices] += X[indices]/(self.ndof*self.cart_penalty**2)
     with log.section('PTGEN', 4, timer='PT Generate'):
         log.dump('      Gradient:  rms = %.3e  max = %.3e  cnstr = %.3e' %(np.sqrt((grad[:self.ndof]**2).mean()), max(grad[:self.ndof]), grad[self.ndof]))
     return grad
示例#2
0
文件: program.py 项目: molmod/QuickFF
    def __init__(self, system, ai, settings, ffrefs=[]):
        '''
            **Arguments**

            system
                a Yaff `System` instance defining the system

            ai
                a `Reference` instance corresponding to the ab initio input data

            settings
                a `Settings` instance defining all QuickFF settings

            **Optional Arguments**

            ffrefs
                a list of `Reference` instances defining the a-priori force
                field contributions.
        '''
        with log.section('INIT', 1, timer='Initializing'):
            log.dump('Initializing program')
            self.settings = settings
            self.system = system
            self.ai = ai
            self.ffrefs = ffrefs
            self.valence = ValenceFF(system, settings)
            self.perturbation = RelaxedStrain(system, self.valence, settings)
            self.trajectories = None
            self.print_system()
示例#3
0
文件: program.py 项目: molmod/QuickFF
 def do_eq_setrv(self, tasks, logger_level=3):
     '''
         Set the rest values to their respective AI equilibrium values.
     '''
     with log.section('EQSET', 2, timer='Equil Set RV'):
         self.reset_system()
         log.dump('Setting rest values to AI equilibrium values for tasks %s' %' '.join(tasks))
         for term in self.valence.terms:
             vterm = self.valence.vlist.vtab[term.index]
             if np.array([task in term.tasks for task in tasks]).any():
                 if term.kind==3:#cross term
                     ic0 = self.valence.iclist.ictab[vterm['ic0']]
                     ic1 = self.valence.iclist.ictab[vterm['ic1']]
                     self.valence.set_params(term.index, rv0=ic0['value'], rv1=ic1['value'])
                 elif term.kind==4 and term.ics[0].kind==4:#Cosine of DihedAngle
                     ic = self.valence.iclist.ictab[vterm['ic0']]
                     m = self.valence.get_params(term.index, only='m')
                     rv = ic['value']%(360.0*deg/m)
                     with log.section('EQSET', 4, timer='Equil Set RV'):
                         log.dump('Set rest value of %s(%s) (eq=%.3f deg) to %.3f deg' %(
                             term.basename,
                             '.'.join([str(at) for at in term.get_atoms()]),
                             ic['value']/deg, rv/deg
                         ))
                     self.valence.set_params(term.index, rv0=rv)
                 else:
                     rv = self.valence.iclist.ictab[vterm['ic0']]['value']
                     self.valence.set_params(term.index, rv0=rv)
         self.valence.dump_logger(print_level=logger_level)
         self.average_pars()
示例#4
0
文件: program.py 项目: molmod/QuickFF
    def do_bendclin(self, thresshold=5*deg):
        '''
            No Harmonic bend can have a rest value equal to are large than
            180 deg - thresshold. If a master (or its slaves) has such a rest
            value, convert master and all slaves to BendCLin (which corresponds
            to a chebychev1 potential with sign=+1):

                0.5*K*[1+cos(theta)]
        '''
        for master in self.valence.iter_masters(label='BendAHarm'):
            indices = [master.index]
            for slave in master.slaves: indices.append(slave)
            found = False
            for index in indices:
                rv = self.valence.get_params(index, only='rv')
                if rv>=180.0*deg-thresshold:
                    found = True
                    break
            if found:
                log.dump('%s has rest value > 180-%.0f deg, converted to BendCheby1' %(master.basename, thresshold/deg))
                for index in indices:
                    term = self.valence.terms[index]
                    self.valence.modify_term(
                        index,
                        Chebychev1, [BendCos(*term.get_atoms())],
                        term.basename.replace('BendAHarm', 'BendCheby1'),
                        ['HC_FC_DIAG'], ['kjmol', 'au']
                    )
                    self.valence.set_params(index, fc=0.0, sign=1.0)
                    for traj in self.trajectories:
                        if traj.term.index==index:
                            traj.rv = None
                            traj.fc = None
                            traj.active = False
示例#5
0
文件: program.py 项目: molmod/QuickFF
 def do_sqoopdist_to_oopdist(self, thresshold=1e-4*angstrom):
     '''
         Transform a SqOopdist term with a rest value that has been set to
         zero, to a term Oopdist (harmonic in Oopdist instead of square of
         Oopdist) with a rest value of 0.0 A.
     '''
     for master in self.valence.iter_masters(label='SqOopdist'):
         indices = [master.index]
         for slave in master.slaves: indices.append(slave)
         found = False
         for index in indices:
             rv = self.valence.get_params(index, only='rv')
             if rv<=thresshold:
                 found = True
                 break
         if found:
             log.dump('%s has rest value <= %.0f A^2, converted to Oopdist with d0=0' %(master.basename, thresshold/angstrom))
             for index in indices:
                 term = self.valence.terms[index]
                 self.valence.modify_term(
                     index,
                     Harmonic, [OopDist(*term.get_atoms())],
                     term.basename.replace('SqOopdist', 'Oopdist'),
                     ['HC_FC_DIAG'], ['kjmol/A**2', 'A']
                 )
                 self.valence.set_params(index, fc=0.0, rv0=0.0)
示例#6
0
文件: program.py 项目: molmod/QuickFF
    def do_pt_estimate(self, do_valence=False, energy_noise=None, logger_level=3):
        '''
            Estimate force constants and rest values from the perturbation
            trajectories

            **Optional Arguments**

            do_valence
                if set to True, the current valence force field will be used to
                estimate the contribution of all other valence terms.
        '''
        with log.section('PTEST', 2, timer='PT Estimate'):
            self.reset_system()
            message = 'Estimating FF parameters from perturbation trajectories'
            if do_valence: message += ' with valence reference'
            log.dump(message)
            #compute fc and rv from trajectory
            only = self.settings.only_traj
            for traj in self.trajectories:
                if traj is None: continue
                if not (only is None or only=='PT_ALL' or only=='pt_all'):
                    if isinstance(only, str): only = [only]
                    basename = self.valence.terms[traj.term.master].basename
                    if basename not in only: continue
                self.perturbation.estimate(traj, self.ai, ffrefs=self.ffrefs, do_valence=do_valence, energy_noise=energy_noise)
            #set force field parameters to computed fc and rv
            for traj in self.trajectories:
                if traj is None: continue
                if not (only is None or only=='PT_ALL' or only=='pt_all'):
                    if isinstance(only, str): only = [only]
                    basename = self.valence.terms[traj.term.master].basename
                    if basename not in only: continue
                self.valence.set_params(traj.term.index, fc=traj.fc, rv0=traj.rv)
            #output
            self.valence.dump_logger(print_level=logger_level)
示例#7
0
文件: program.py 项目: molmod/QuickFF
 def reset_system(self):
     '''
         routine to reset the system coords to the ai equilbrium
     '''
     log.dump('Resetting system coordinates to ab initio ref')
     self.system.pos = self.ai.coords0.copy()
     self.valence.dlist.forward()
     self.valence.iclist.forward()
示例#8
0
文件: program.py 项目: molmod/QuickFF
    def do_squarebend(self, thresshold=20*deg):
        '''
            Identify bend patterns in which 4 atoms of type A surround a central
            atom of type B with A-B-A angles of 90/180 degrees. A simple
            harmonic pattern will not be adequate since a rest value of 90 and
            180 degrees is possible for the same A-B-A term. Therefore, a
            cosine term with multiplicity of 4 is used (which corresponds to a
            chebychev4 potential with sign=-1):

                  V = K/2*[1-cos(4*theta)]

            To identify the patterns, it is assumed that the rest values have
            already been estimated from the perturbation trajectories. For each
            master and slave of a BENDAHARM term, its rest value is computed and
            checked if it lies either the interval [90-thresshold,90+thresshold]
            or [180-thresshold,180]. If this is the case, the new cosine term
            is used.

            **Optional arguments**

            thresshold
                the (half) the width of the interval around 180 deg (90 degrees)
                to check if a square BA4
        '''
        for master in self.valence.iter_masters(label='BendAHarm'):
            rvs = np.zeros([len(master.slaves)+1], float)
            rvs[0] = self.valence.get_params(master.index, only='rv')
            for i, islave in enumerate(master.slaves):
                rvs[1+i] = self.valence.get_params(islave, only='rv')
            n90 = 0
            n180 = 0
            nother = 0
            for i, rv in enumerate(rvs):
                if 90*deg-thresshold<=rv and rv<=90*deg+thresshold: n90 += 1
                elif 180*deg-thresshold<=rv and rv<=180*deg+thresshold: n180 += 1
                else: nother += 1
            if n90>0 and n180>0:
                log.dump('%s has rest values around 90 deg and 180 deg, converted to BendCheby4' %master.basename)
                #modify master and slaves
                indices = [master.index]
                for slave in master.slaves: indices.append(slave)
                for index in indices:
                    term = self.valence.terms[index]
                    self.valence.modify_term(
                        index,
                        Chebychev4, [BendCos(*term.get_atoms())],
                        term.basename.replace('BendAHarm', 'BendCheby4'),
                        ['HC_FC_DIAG'], ['kjmol', 'au']
                    )
                    self.valence.set_params(index, sign=-1)
                    for traj in self.trajectories:
                        if traj.term.index==index:
                            traj.active = False
                            traj.fc = None
                            traj.rv = None
示例#9
0
文件: program.py 项目: molmod/QuickFF
 def write_trajectories(self):
     '''
         Write perturbation trajectories to XYZ files.
     '''
     only = self.settings.only_traj
     if not isinstance(only, list): only = [only]
     with log.section('XYZ', 3, timer='PT dump XYZ'):
         for trajectory in self.trajectories:
             if trajectory is None: continue
             for pattern in only:
                 if pattern=='PT_ALL' or pattern in trajectory.term.basename:
                     log.dump('Writing XYZ trajectory for %s' %trajectory.term.basename)
                     trajectory.to_xyz()
示例#10
0
文件: program.py 项目: molmod/QuickFF
 def plot_trajectories(self, do_valence=False, suffix=''):
     '''
         Plot energy contributions along perturbation trajectories and
     '''
     only = self.settings.only_traj
     if not isinstance(only, list): only = [only]
     with log.section('PLOT', 3, timer='PT plot energy'):
         valence = None
         if do_valence: valence=self.valence
         for trajectory in self.trajectories:
             if trajectory is None: continue
             for pattern in only:
                 if pattern=='PT_ALL' or pattern in trajectory.term.basename:
                     log.dump('Plotting trajectory for %s' %trajectory.term.basename)
                     trajectory.plot(self.ai, ffrefs=self.ffrefs, valence=valence, suffix=suffix)
示例#11
0
文件: program.py 项目: molmod/QuickFF
 def average_pars(self):
     '''
         Average force field parameters over master and slaves.
     '''
     log.dump('Averaging force field parameters over master and slaves')
     for master in self.valence.iter_masters():
         npars = len(self.valence.get_params(master.index))
         pars = np.zeros([len(master.slaves)+1, npars], float)
         pars[0,:] = np.array(self.valence.get_params(master.index))
         for i, islave in enumerate(master.slaves):
             pars[1+i,:] = np.array(self.valence.get_params(islave))
         if master.kind in [0,2,11,12]:#harmonic,fues,MM3Quartic,MM3Bend
             fc, rv = pars.mean(axis=0)
             self.valence.set_params(master.index, fc=fc, rv0=rv)
             for islave in master.slaves:
                 self.valence.set_params(islave, fc=fc, rv0=rv)
         elif master.kind==1:
             a0, a1, a2, a3 = pars.mean(axis=0)
             self.valence.set_params(master.index, a0=a0, a1=a1, a2=a2, a3=a3)
             for islave in master.slaves:
                 self.valence.set_params(islave, a0=a0, a1=a1, a2=a2, a3=a3)
         elif master.kind==3:#cross
             fc, rv0, rv1 = pars.mean(axis=0)
             self.valence.set_params(master.index, fc=fc, rv0=rv0, rv1=rv1)
             for islave in master.slaves:
                 self.valence.set_params(islave, fc=fc, rv0=rv0, rv1=rv1)
         elif master.kind==4:#cosine
             assert pars[:,0].std()<1e-6, 'dihedral multiplicity not unique'
             m, fc, rv = pars.mean(axis=0)
             self.valence.set_params(master.index, fc=fc, rv0=rv, m=m)
             for islave in master.slaves:
                 self.valence.set_params(islave, fc=fc, rv0=rv, m=m)
         elif master.kind in [5, 6, 7, 8, 9]:#chebychev
             assert pars.shape[1]==2
             fc = pars[:,0].mean()
             self.valence.set_params(master.index, fc=fc)
             for islave in master.slaves:
                 self.valence.set_params(islave, fc=fc)
         else:
             raise NotImplementedError
示例#12
0
文件: program.py 项目: molmod/QuickFF
 def do_pt_generate(self):
     '''
         Generate perturbation trajectories.
     '''
     with log.section('PTGEN', 2, timer='PT Generate'):
         #read if an existing file was specified through fn_traj
         fn_traj = self.settings.fn_traj
         if fn_traj is not None and os.path.isfile(fn_traj):
             self.trajectories = pickle.load(open(fn_traj, 'rb'))
             log.dump('Trajectories read from file %s' %fn_traj)
             self.update_trajectory_terms()
             newname = 'updated_'+fn_traj.split('/')[-1]
             pickle.dump(self.trajectories, open(newname, 'wb'))
             return
         #configure
         self.reset_system()
         only = self.settings.only_traj
         if only is None or only=='PT_ALL' or only=='pt_all':
             do_terms = [term for term in self.valence.terms if term.kind in [0,2,11,12]]
         else:
             if isinstance(only, str): only = [only]
             do_terms = []
             for pattern in only:
                 for term in self.valence.iter_terms(pattern):
                     if term.kind in [0,2,11,12]:
                         do_terms.append(term)
         trajectories = self.perturbation.prepare(do_terms)
         #compute
         log.dump('Constructing trajectories')
         self.trajectories = paracontext.map(self.perturbation.generate, [traj for traj in trajectories if traj.active])
         #write the trajectories to the non-existing file fn_traj
         if fn_traj is not None:
             assert not os.path.isfile(fn_traj)
             pickle.dump(self.trajectories, open(fn_traj, 'wb'))
             log.dump('Trajectories stored to file %s' %fn_traj)
示例#13
0
文件: program.py 项目: molmod/QuickFF
 def update_trajectory_terms(self):
     '''
         Routine to make ``self.valence.terms`` and the term attribute of each
         trajectory in ``self.trajectories`` consistent again. This is usefull
         if the trajectory were read from a file and the ``valenceFF`` instance
         was modified.
     '''
     log.dump('Updating terms of trajectories to current valenceFF terms')
     with log.section('PTUPD', 3):
         #update the terms in the trajectories to match the terms in
         #self.valence
         for traj in self.trajectories:
             found = False
             for term in self.valence.iter_terms():
                 if traj.term.get_atoms()==term.get_atoms():
                     if found: raise ValueError('Found two terms for trajectory %s with atom indices %s' %(traj.term.basename, str(traj.term.get_atoms())))
                     traj.term = term
                     if 'PT_ALL' not in term.tasks:
                         log.dump('PT_ALL not in tasks of %s-%i, deactivated PT' %(term.basename, term.index))
                         traj.active = False
                     found = True
             if not found:
                 log.warning('No term found for trajectory %s with atom indices %s, deactivating trajectory' %(traj.term.basename, str(traj.term.get_atoms())))
                 traj.active = False
         #check if every term with task PT_ALL has a trajectory associated
         #with it. It a trajectory is missing, generate it.
         for term in self.valence.iter_terms():
             if 'PT_ALL' not in term.tasks: continue
             found = False
             for traj in self.trajectories:
                 if term.get_atoms()==traj.term.get_atoms():
                     if found: raise ValueError('Found two trajectories for term %s with atom indices %s' %(term.basename, str(term.get_atoms())))
                     found =True
             if not found:
                 log.warning('No trajectory found for term %s with atom indices %s. Generating it now.' %(term.basename, str(term.get_atoms())))
                 trajectory = self.perturbation.prepare([term])[term.index]
                 self.perturbation.generate(trajectory)
                 self.trajectories.append(trajectory)
示例#14
0
 def print_system(self):
     '''
         dump overview of atoms (and associated parameters) in the system
     '''
     with log.section('SYS', 3, timer='Initializing'):
         log.dump('Atomic configuration of the system:')
         log.dump('')
         log.dump('  index  |  x [A]  |  y [A]  |  z [A]  | ffatype |    q    |  R [A]  ')
         log.dump('---------------------------------------------------------------------')
         for i in range(len(self.system.numbers)):
             x, y, z = self.system.pos[i,0], self.system.pos[i,1], self.system.pos[i,2]
             if self.system.charges is not None:
                 q = self.system.charges[i]
             else:
                 q = np.nan
             if self.system.radii is not None:
                 R = self.system.radii[i]
             else:
                 R = np.nan
             log.dump('  %4i   | % 7.3f | % 7.3f | % 7.3f |  %6s | % 7.3f | % 7.3f ' %(
                 i, x/angstrom, y/angstrom, z/angstrom,
                 self.system.ffatypes[self.system.ffatype_ids[i]],
                 q, R/angstrom
             ))
示例#15
0
文件: program.py 项目: molmod/QuickFF
 def print_system(self):
     '''
         dump overview of atoms (and associated parameters) in the system
     '''
     with log.section('SYS', 3, timer='Initializing'):
         log.dump('Atomic configuration of the system:')
         log.dump('')
         log.dump('  index  |  x [A]  |  y [A]  |  z [A]  | ffatype |    q    |  R [A]  ')
         log.dump('---------------------------------------------------------------------')
         for i in range(len(self.system.numbers)):
             x, y, z = self.system.pos[i,0], self.system.pos[i,1], self.system.pos[i,2]
             if self.system.charges is not None:
                 q = self.system.charges[i]
             else:
                 q = np.nan
             if self.system.radii is not None:
                 R = self.system.radii[i]
             else:
                 R = np.nan
             log.dump('  %4i   | % 7.3f | % 7.3f | % 7.3f |  %6s | % 7.3f | % 7.3f ' %(
                 i, x/angstrom, y/angstrom, z/angstrom,
                 self.system.ffatypes[self.system.ffatype_ids[i]],
                 q, R/angstrom
             ))
示例#16
0
 def dump_logger(self, print_level=1):
     if log.log_level < print_level: return
     with log.section('', print_level):
         sequence = [
             'bondharm', 'bendaharm', 'bendcharm', 'bendcos', 'torsion',
             'torsc2harm', 'dihedharm', 'oopdist', 'cross'
         ]
         log.dump('')
         for label in sequence:
             lines = []
             for term in self.iter_masters(label=label):
                 lines.append(term.to_string(self))
             for line in sorted(lines):
                 log.dump(line)
                 log.dump('')
示例#17
0
 def do_pt_generate(self):
     '''
         Generate perturbation trajectories.
     '''
     with log.section('PTGEN', 2, timer='PT Generate'):
         #read if an existing file was specified through fn_traj
         fn_traj = self.settings.fn_traj
         if fn_traj is not None and os.path.isfile(fn_traj):
             self.trajectories = pickle.load(open(fn_traj, 'rb'))
             log.dump('Trajectories read from file %s' % fn_traj)
             self.update_trajectory_terms()
             newname = 'updated_' + fn_traj.split('/')[-1]
             pickle.dump(self.trajectories, open(newname, 'wb'))
             return
         #configure
         self.reset_system()
         only = self.settings.only_traj
         if only is None or only == 'PT_ALL' or only == 'pt_all':
             do_terms = [
                 term for term in self.valence.terms
                 if term.kind in [0, 2, 11, 12]
             ]
         else:
             if isinstance(only, str): only = [only]
             do_terms = []
             for pattern in only:
                 for term in self.valence.iter_terms(pattern):
                     if term.kind in [0, 2, 11, 12]:
                         do_terms.append(term)
         trajectories = self.perturbation.prepare(do_terms)
         #compute
         log.dump('Constructing trajectories')
         self.trajectories = paracontext.map(
             self.perturbation.generate,
             [traj for traj in trajectories if traj.active])
         #write the trajectories to the non-existing file fn_traj
         if fn_traj is not None:
             assert not os.path.isfile(fn_traj)
             pickle.dump(self.trajectories, open(fn_traj, 'wb'))
             log.dump('Trajectories stored to file %s' % fn_traj)
示例#18
0
    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)
示例#19
0
    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)
示例#20
0
    def do_pt_generate(self):
        '''
            Generate perturbation trajectories.
        '''
        with log.section('PTGEN', 2, timer='PT Generate'):
            #read if an existing file was specified through fn_traj
            fn_traj = self.settings.fn_traj
            if fn_traj is not None and os.path.isfile(fn_traj):
                self.trajectories = pickle.load(open(fn_traj, 'rb'))
                log.dump('Trajectories read from file %s' % fn_traj)
                self.update_trajectory_terms()
                newname = 'updated_' + fn_traj.split('/')[-1]
                pickle.dump(self.trajectories, open(newname, 'wb'))
                return
            #configure
            self.reset_system()
            only = self.settings.only_traj
            dont_traj = self.settings.dont_traj

            if sum([only is None, dont_traj is None]) == 0:
                raise AssertionError(
                    'The settings only_traj and dont_traj cannot be specified both'
                )
            if (only is None or only == 'PT_ALL' or only == 'pt_all'
                ) and dont_traj is None:  # only=None is equivalent to PT_ALL
                do_terms = [
                    term for term in self.valence.terms
                    if term.kind in [0, 2, 11, 12]
                ]
            elif only is None and dont_traj is not None:
                kind2string = {
                    0: 'bond',
                    2: 'bend',
                    11: 'oopdist',
                    12: 'dihedral'
                }
                ffatypes = [
                    self.system.ffatypes[fid]
                    for fid in self.system.ffatype_ids
                ]

                dont_patterns = dont_traj.split(',')  # split patterns
                dont_terms = []
                for term in self.valence.terms:
                    if term.kind in [0, 2, 11, 12]:
                        types = term.basename.split('/')[1].split('.')
                        option1 = '.'.join(types)
                        option2 = '.'.join(types[::-1])
                        for dp in dont_patterns:
                            pattern = re.compile(dp, re.IGNORECASE)
                            if pattern.match(option1) or pattern.match(
                                    option2):
                                dont_terms.append(term)

                do_terms = [
                    term for term in self.valence.terms
                    if term.kind in [0, 2, 11, 12] and term not in dont_terms
                ]
                with log.section('PTNOT', 3):
                    for term in dont_terms:
                        log.dump(
                            'Taking AI equilibrium rest value instead of generating perturbation trajectory for %s'
                            % term.basename)
                        vterm = self.valence.vlist.vtab[term.index]
                        self.valence.set_params(term.index,
                                                fc=0,
                                                rv0=self.valence.iclist.ictab[
                                                    vterm['ic0']]['value'])
            else:
                if isinstance(only, str): only = [only]
                do_terms = []
                for pattern in only:
                    for term in self.valence.iter_terms(pattern):
                        if term.kind in [0, 2, 11, 12]:
                            do_terms.append(term)
            trajectories = self.perturbation.prepare(do_terms)
            #compute
            log.dump('Constructing trajectories')
            self.trajectories = paracontext.map(
                self.perturbation.generate,
                [traj for traj in trajectories if traj.active])
            #write the trajectories to the non-existing file fn_traj
            if fn_traj is not None:
                assert not os.path.isfile(fn_traj)
                pickle.dump(self.trajectories, open(fn_traj, 'wb'))
                log.dump('Trajectories stored to file %s' % fn_traj)
示例#21
0
    def generate(self, trajectory, remove_com=True):
        '''
            Method to calculate the perturbation trajectory, i.e. the trajectory
            that scans the geometry along the direction of the ic figuring in
            the term with the given index (should be a diagonal term). This
            method should be implemented in the derived classes.

            **Arguments**

            trajectory
                instance of Trajectory class representing the perturbation
                trajectory

            **Optional Arguments**

            remove_com
                if set to True, removes the center of mass translation from the
                resulting perturbation trajectories [default=True].
        '''
        #TODO: find out why system.cell is not parsed correctly when using scoop
        #force correct rvecs
        self.system0.cell.update_rvecs(self.system_rvecs)
        with log.section('PTGEN', 4, timer='PT Generate'):
            log.dump('  Generating %s(atoms=%s)' %(trajectory.term.basename, trajectory.term.get_atoms()))
            strain = Strain(self.system, trajectory.term, self.valence.terms)
            natom = self.system0.natom
            q0 = self.valence.iclist.ictab[self.valence.vlist.vtab[trajectory.term.index]['ic0']]['value']
            diag = np.array([0.1*angstrom,]*3*natom+[abs(q0-trajectory.targets[0])])
            sol = None
            for iq, target in enumerate(trajectory.targets):
                log.dump('    Frame %i (target=%.3f)' %(iq, target))
                strain.constrain_target = target
                if abs(target-q0)<1e-6:
                    sol = np.zeros([strain.ndof+1],float)
                    #call strain.gradient once to compute/store/log relevant information
                    strain.gradient(sol)
                else:
                    if sol is not None:
                        init = sol.copy()
                    else:
                        init = np.zeros([3*natom+1], float)
                    init[-1] = np.sign(q0-target)
                    sol, infodict, ier, mesg = scipy.optimize.fsolve(strain.gradient, init, xtol=self.settings.pert_traj_tol, full_output=True, diag=diag)
                    if ier!=1:
                        #fsolve did not converge, try again after adding small random noise
                        log.dump('      %s' %mesg.replace('\n', ' '))
                        log.dump('    Frame %i (target=%.3f) %s(%s) did not converge. Trying again with slightly perturbed initial conditions.' %(
                            iq, target, trajectory.term.basename, trajectory.term.get_atoms()
                        ))
                        #try one more time
                        init = sol.copy()
                        init[:3*natom] += np.random.normal(0.0, 0.01, [3*natom])*angstrom
                        sol, infodict, ier, mesg = scipy.optimize.fsolve(strain.gradient, init, xtol=self.settings.pert_traj_tol, full_output=True, diag=diag)
                        #fsolve did STILL not converge, flag this frame for deletion
                        if ier!=1:
                            log.dump('      %s' %mesg.replace('\n', ' '))
                            log.dump('    Frame %i (target=%.3f) %s(%s) STILL did not converge.' %(
                                iq, target, trajectory.term.basename, trajectory.term.get_atoms()
                            ))
                            trajectory.targets[iq] = np.nan
                            continue
                x = self.system0.pos.copy() + sol[:3*natom].reshape((-1,3))
                trajectory.values[iq] = strain.constrain_value
                log.dump('    Converged (value=%.3f, lagmult=%.3e)' %(strain.constrain_value,sol[3*natom]))
                if remove_com:
                    com = (x.T*self.system0.masses.copy()).sum(axis=1)/self.system0.masses.sum()
                    for i in range(natom):
                        x[i,:] -= com
                trajectory.coords[iq,:,:] = x
            #delete flagged frames
            targets = []
            values = []
            coords = []
            for target, value, coord in zip(trajectory.targets, trajectory.values, trajectory.coords):
                if not np.isnan(target):
                    targets.append(target)
                    values.append(value)
                    coords.append(coord)
            trajectory.targets = np.array(targets)
            trajectory.values = np.array(values)
            trajectory.coords = np.array(coords)
        return trajectory
示例#22
0
def project_negative_freqs(hessian, masses, thresshold=0.0):
    N = len(masses)
    sqrt_mass_matrix = np.diag(
        np.sqrt((np.array([masses, masses, masses]).T).ravel()))
    isqrt_mass_matrix = np.linalg.inv(sqrt_mass_matrix)
    matrix = np.dot(isqrt_mass_matrix,
                    np.dot(hessian.reshape([3 * N, 3 * N]), isqrt_mass_matrix))
    #diagonalize
    if ((matrix - matrix.T) < 1e-6 * lightspeed / centimeter).all():
        evals, evecs = np.linalg.eigh(matrix)
    else:
        evals, evecs = np.linalg.eig(matrix)
    log.dump('20 lowest frequencies [1/cm] before projection:')
    log.dump(str(evals[:4] / (lightspeed / centimeter)))
    log.dump(str(evals[4:8] / (lightspeed / centimeter)))
    log.dump(str(evals[8:12] / (lightspeed / centimeter)))
    log.dump(str(evals[12:16] / (lightspeed / centimeter)))
    log.dump(str(evals[16:20] / (lightspeed / centimeter)))
    #set negative eigenvalues to zero
    evals[evals < thresshold] = 0.0
    projected_matrix = np.dot(evecs, np.dot(np.diag(evals), evecs.T))
    projected_hessian = np.dot(sqrt_mass_matrix,
                               np.dot(projected_matrix, sqrt_mass_matrix))
    #dump freqs after projection as check
    evals, evecs = np.linalg.eigh(projected_matrix)
    log.dump('20 lowest frequencies [1/cm] after projection:')
    log.dump(str(evals[:4] / (lightspeed / centimeter)))
    log.dump(str(evals[4:8] / (lightspeed / centimeter)))
    log.dump(str(evals[8:12] / (lightspeed / centimeter)))
    log.dump(str(evals[12:16] / (lightspeed / centimeter)))
    log.dump(str(evals[16:20] / (lightspeed / centimeter)))
    return projected_hessian.reshape([N, 3, N, 3])
示例#23
0
    def generate(self, trajectory, remove_com=True):
        '''
            Method to calculate the perturbation trajectory, i.e. the trajectory
            that scans the geometry along the direction of the ic figuring in
            the term with the given index (should be a diagonal term). This
            method should be implemented in the derived classes.

            **Arguments**

            trajectory
                instance of Trajectory class representing the perturbation
                trajectory

            **Optional Arguments**

            remove_com
                if set to True, removes the center of mass translation from the
                resulting perturbation trajectories [default=True].
        '''
        index = trajectory.term.index
        with log.section('PTGEN', 3, timer='PT Generate'):
            log.dump('  Generating %s(atoms=%s)' %
                     (self.valence.terms[index].basename,
                      trajectory.term.get_atoms()))
            strain = self.strains[index]
            natom = self.system.natom
            if strain is None:
                log.warning(
                    'Strain for term %i (%s) is not initialized, skipping.' %
                    (index, self.valence.terms[index].basename))
                return
            q0 = self.valence.iclist.ictab[self.valence.vlist.vtab[index]
                                           ['ic0']]['value']
            diag = np.ones([strain.ndof + 1], float)
            diag[:strain.ndof] *= 0.1 * angstrom
            diag[strain.ndof] *= abs(q0 - trajectory.targets[0])
            sol = None
            for iq, target in enumerate(trajectory.targets):
                log.dump('    Frame %i (target=%.3f)' % (iq, target))
                strain.constrain_target = target
                if abs(target - q0) < 1e-6:
                    sol = np.zeros([strain.ndof + 1], float)
                    strain.gradient(sol)
                else:
                    if sol is not None:
                        init = sol.copy()
                    else:
                        init = np.zeros([strain.ndof + 1], float)
                    init[-1] = np.sign(q0 - target)
                    sol, infodict, ier, mesg = scipy.optimize.fsolve(
                        strain.gradient,
                        init,
                        xtol=1e-3,
                        full_output=True,
                        diag=diag)
                    if ier != 1:
                        #fsolve did not converge, flag this frame for deletion
                        log.dump('      %s' % mesg.replace('\n', ' '))
                        log.dump(
                            '    Frame %i (target=%.3f) %s(%s) did not converge. Trying again with slightly perturbed initial conditions.'
                            % (iq, target, self.valence.terms[index].basename,
                               trajectory.term.get_atoms()))
                        #try one more time
                        init = sol.copy()
                        init[:3 * natom] += np.random.normal(
                            0.0, 0.01, [3 * natom]) * angstrom
                        sol, infodict, ier, mesg = scipy.optimize.fsolve(
                            strain.gradient,
                            init,
                            xtol=1e-3,
                            full_output=True,
                            diag=diag)
                        if ier != 1:
                            log.dump('      %s' % mesg.replace('\n', ' '))
                            log.dump(
                                '    Frame %i (target=%.3f) %s(%s) STILL did not converge.'
                                % (iq, target,
                                   self.valence.terms[index].basename,
                                   trajectory.term.get_atoms()))
                            trajectory.targets[iq] = np.nan
                            continue
                x = strain.coords0 + sol[:3 * natom].reshape((-1, 3))
                trajectory.values[iq] = strain.constrain_value
                log.dump('    Converged (value=%.3f, lagmult=%.3e)' %
                         (strain.constrain_value, sol[3 * natom]))
                if remove_com:
                    com = (x.T * self.system.masses).sum(
                        axis=1) / self.system.masses.sum()
                    for i in xrange(natom):
                        x[i, :] -= com
                trajectory.coords[iq, :, :] = x
            #delete flagged frames
            targets = []
            values = []
            coords = []
            for target, value, coord in zip(trajectory.targets,
                                            trajectory.values,
                                            trajectory.coords):
                if not np.isnan(target):
                    targets.append(target)
                    values.append(value)
                    coords.append(coord)
            trajectory.targets = np.array(targets)
            trajectory.values = np.array(values)
            trajectory.coords = np.array(coords)
            return trajectory
示例#24
0
    def generate(self, trajectory, remove_com=True):
        '''
            Method to calculate the perturbation trajectory, i.e. the trajectory
            that scans the geometry along the direction of the ic figuring in
            the term with the given index (should be a diagonal term). This
            method should be implemented in the derived classes.

            **Arguments**

            trajectory
                instance of Trajectory class representing the perturbation
                trajectory

            **Optional Arguments**

            remove_com
                if set to True, removes the center of mass translation from the
                resulting perturbation trajectories [default=True].
        '''
        #TODO: find out why system.cell is not parsed correctly when using scoop
        #force correct rvecs
        self.system0.cell.update_rvecs(self.system_rvecs)
        with log.section('PTGEN', 4, timer='PT Generate'):
            log.dump('  Generating %s(atoms=%s)' %(trajectory.term.basename, trajectory.term.get_atoms()))
            strain = Strain(self.system, trajectory.term, self.valence.terms)
            natom = self.system0.natom
            q0 = self.valence.iclist.ictab[self.valence.vlist.vtab[trajectory.term.index]['ic0']]['value']
            diag = np.array([0.1*angstrom,]*3*natom+[abs(q0-trajectory.targets[0])])
            sol = None
            for iq, target in enumerate(trajectory.targets):
                log.dump('    Frame %i (target=%.3f)' %(iq, target))
                strain.constrain_target = target
                if abs(target-q0)<1e-6:
                    sol = np.zeros([strain.ndof+1],float)
                    #call strain.gradient once to compute/store/log relevant information
                    strain.gradient(sol)
                else:
                    if sol is not None:
                        init = sol.copy()
                    else:
                        init = np.zeros([3*natom+1], float)
                    init[-1] = np.sign(q0-target)
                    sol, infodict, ier, mesg = scipy.optimize.fsolve(strain.gradient, init, xtol=self.settings.pert_traj_tol, full_output=True, diag=diag)
                    if ier!=1:
                        #fsolve did not converge, try again after adding small random noise
                        log.dump('      %s' %mesg.replace('\n', ' '))
                        log.dump('    Frame %i (target=%.3f) %s(%s) did not converge. Trying again with slightly perturbed initial conditions.' %(
                            iq, target, trajectory.term.basename, trajectory.term.get_atoms()
                        ))
                        #try one more time
                        init = sol.copy()
                        init[:3*natom] += np.random.normal(0.0, 0.01, [3*natom])*angstrom
                        sol, infodict, ier, mesg = scipy.optimize.fsolve(strain.gradient, init, xtol=self.settings.pert_traj_tol, full_output=True, diag=diag)
                        #fsolve did STILL not converge, flag this frame for deletion
                        if ier!=1:
                            log.dump('      %s' %mesg.replace('\n', ' '))
                            log.dump('    Frame %i (target=%.3f) %s(%s) STILL did not converge.' %(
                                iq, target, trajectory.term.basename, trajectory.term.get_atoms()
                            ))
                            trajectory.targets[iq] = np.nan
                            continue
                x = self.system0.pos.copy() + sol[:3*natom].reshape((-1,3))
                trajectory.values[iq] = strain.constrain_value
                log.dump('    Converged (value=%.3f, lagmult=%.3e)' %(strain.constrain_value,sol[3*natom]))
                if remove_com:
                    com = (x.T*self.system0.masses.copy()).sum(axis=1)/self.system0.masses.sum()
                    for i in range(natom):
                        x[i,:] -= com
                trajectory.coords[iq,:,:] = x
            #delete flagged frames
            targets = []
            values = []
            coords = []
            for target, value, coord in zip(trajectory.targets, trajectory.values, trajectory.coords):
                if not np.isnan(target):
                    targets.append(target)
                    values.append(value)
                    coords.append(coord)
            trajectory.targets = np.array(targets)
            trajectory.values = np.array(values)
            trajectory.coords = np.array(coords)
        return trajectory
示例#25
0
 def init_oop_terms(self, thresshold_zero=5e-2 * angstrom):
     '''
         Initialize all out-of-plane terms in the system based on the oops
         attribute of the system instance. All oops are given harmonic
         potentials.
     '''
     with log.section('VAL', 3, 'Initializing'):
         #get all dihedrals
         from molmod.ic import opbend_dist, _opdist_low
         ffatypes = [
             self.system.ffatypes[fid] for fid in self.system.ffatype_ids
         ]
         opdists = {}
         for opdist in self.system.iter_oops():
             opdist, types = term_sort_atypes(ffatypes, opdist, 'opdist')
             if types in opdists.keys():
                 opdists[types].append(opdist)
             else:
                 opdists[types] = [opdist]
         #loop over all distinct opdist types
         nharm = 0
         nsq = 0
         for types, oops in opdists.iteritems():
             d0s = np.zeros(len(oops), float)
             for i, oop in enumerate(oops):
                 if self.system.cell.nvec > 0:
                     d01 = self.system.pos[oop[1]] - self.system.pos[oop[0]]
                     d02 = self.system.pos[oop[2]] - self.system.pos[oop[0]]
                     d03 = self.system.pos[oop[3]] - self.system.pos[oop[0]]
                     self.system.cell.mic(d01)
                     self.system.cell.mic(d02)
                     self.system.cell.mic(d03)
                     d0s[i] = abs(_opdist_low(d01, d02, d03, 0)[0])
                 else:
                     rs = np.array(
                         [  #mind the order, is(or was) wrongly documented in molmod
                             self.system.pos[oop[0]],
                             self.system.pos[oop[1]],
                             self.system.pos[oop[2]],
                             self.system.pos[oop[3]],
                         ])
                     d0s[i] = abs(opbend_dist(rs)[0])
             if d0s.mean() < thresshold_zero:  #TODO: check this thresshold
                 #add regular term harmonic in oopdist
                 for oop in oops:
                     term = self.add_term(Harmonic, [OopDist(*oop)], types,
                                          ['HC_FC_DIAG'],
                                          ['kjmol/A**2', 'A'])
                     self.set_params(term.index, rv0=0.0)
                     nharm += 1
             else:
                 #add term harmonic in square of oopdist
                 log.dump(
                     'Mean absolute value of OopDist %s is %.3e A, used SQOOPDIST'
                     % ('.'.join(types), d0s.mean() / angstrom))
                 for oop in oops:
                     self.add_term(Harmonic, [SqOopDist(*oop)], types,
                                   ['PT_ALL', 'HC_FC_DIAG'],
                                   ['kjmol/A**4', 'A**2'])
                     nsq += 1
     log.dump(
         'Added %i Harmonic and %i SquareHarmonic out-of-plane distance terms'
         % (nharm, nsq))
示例#26
0
文件: program.py 项目: molmod/QuickFF
    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)
示例#27
0
 def dump_log(self):
     sorted_keys = sorted(self.__dict__.keys())
     with log.section('SETT', 3):
         for key in sorted_keys:
             value = str(self.__dict__[key])
             log.dump('%s  %s' %(key+' '*(30-len(key)), value))
示例#28
0
    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)
示例#29
0
    def __init__(self, system, ai, **kwargs):
        '''
            **Arguments**

            system
                a Yaff `System` object defining the system

            ai
                a `Reference` instance corresponding to the ab initio input data

            **Keyword Arguments**

            ffrefs
                a list of `Reference` objects corresponding to a priori determined
                contributions to the force field (such as eg. electrostatics
                or van der Waals contributions)

            fn_yaff
                the name of the file to write the final parameters to in Yaff
                format. The default is `pars.txt`.

            fn_charmm22_prm
                the name of a CHARMM parameter file. If not given, the file is not written

            fn_charmm22_psf
                the name of a CHARMM topology file. If not given, the file is not written

            fn_sys
                the name of the file to write the system to. The default is
                `system.chk`.

            fn_traj
                a cPickle filename to read/write the perturbation trajectories
                from/to. If the file exists, the trajectories are read from the
                file. If the file does not exist, the trajectories are written
                to the file.

            only_traj
                specifier to determine for which terms a perturbation trajectory
                needs to be constructed. If ONLY_TRAJ is a single string, it is
                interpreted as a task (only terms that have this task in their
                tasks attribute will get a trajectory). If ONLY_TRAJ is a list
                of strings, each string is interpreted as the basename of the
                term for which a trajectory will be constructed.

            plot_traj
                if set to True, all energy contributions along each perturbation
                trajectory will be plotted using the final force field.

            xyz_traj
                if set to True, each perturbation trajectory will be written to
                an XYZ file.
        '''
        with log.section('PROG', 2, timer='Initializing'):
            log.dump('Initializing program')
            self.system = system
            self.ai = ai
            self.kwargs = kwargs
            self.valence = ValenceFF(system)
            self.perturbation = RelaxedStrain(system, self.valence)
            self.trajectories = None
示例#30
0
 def dump_log(self):
     sorted_keys = sorted(self.__dict__.keys())
     with log.section('SETT', 3):
         for key in sorted_keys:
             value = str(self.__dict__[key])
             log.dump('%s  %s' %(key+' '*(30-len(key)), value))
示例#31
0
    def init_dihedral_terms(self, thresshold=20 * deg):
        '''
            Initialize the dihedral potentials from the local topology. The
            dihedral potential will be one of the two following possibilities:

                The multiplicity m is determined from the local topology, i.e.
                the number of neighbors of the central two atoms in the dihedral

                If the equilibrium value of all instances of the torsion are
                within `thresshold` of 0 deg or per/2 with per = 180deg/m,
                the following potential will be chosen:

                    0.5*K*(1-cos(m*psi-m*psi0)) with psi0 = 0 or 360/(2*m)
        '''
        with log.section('VAL', 3, 'Initializing'):
            #get all dihedrals
            from molmod.ic import dihed_angle, _dihed_angle_low
            ffatypes = [
                self.system.ffatypes[fid] for fid in self.system.ffatype_ids
            ]
            dihedrals = {}
            for dihedral in self.system.iter_dihedrals():
                dihedral, types = term_sort_atypes(ffatypes, dihedral,
                                                   'dihedral')
                if types in dihedrals.keys():
                    dihedrals[types].append(dihedral)
                else:
                    dihedrals[types] = [dihedral]
            #loop over all distinct dihedral types
            ncos = 0
            for types, diheds in dihedrals.iteritems():
                psi0s = np.zeros(len(diheds), float)
                ms = np.zeros(len(diheds), float)
                for i, dihed in enumerate(diheds):
                    if self.system.cell.nvec > 0:
                        d10 = self.system.pos[dihed[0]] - self.system.pos[
                            dihed[1]]
                        d12 = self.system.pos[dihed[2]] - self.system.pos[
                            dihed[1]]
                        d23 = self.system.pos[dihed[3]] - self.system.pos[
                            dihed[2]]
                        self.system.cell.mic(d10)
                        self.system.cell.mic(d12)
                        self.system.cell.mic(d23)
                        psi0s[i] = _dihed_angle_low(d10, d12, d23, 0)[0]
                    else:
                        rs = np.array([self.system.pos[j] for j in dihed])
                        psi0s[i] = dihed_angle(rs)[0]
                    n1 = len(self.system.neighs1[dihed[1]])
                    n2 = len(self.system.neighs1[dihed[2]])
                    ms[i] = get_multiplicity(n1, n2)
                nan = False
                for m in ms:
                    if np.isnan(m): nan = True
                if nan or None in ms or ms.std() > 1e-3:
                    ms_string = str(ms)
                    if nan: ms_string = 'nan'
                    log.warning('missing dihedral for %s (m is %s)' %
                                ('.'.join(types), ms_string))
                    continue
                m = int(np.round(ms.mean()))
                rv = get_restvalue(psi0s, m, thresshold=thresshold, mode=1)
                if rv is not None:
                    #a regular Cosine term is used for the dihedral potential
                    for dihed in diheds:
                        term = self.add_term(Cosine, [DihedAngle(*dihed)],
                                             types, ['HC_FC_DIAG'],
                                             ['au', 'kjmol', 'deg'])
                        self.set_params(term.index, rv0=rv, m=m)
                        ncos += 1
                else:
                    #no dihedral potential could be determine, hence it is ignored
                    log.warning(
                        'missing dihedral for %s (could not determine rest value from %s)'
                        % ('.'.join(types), str(psi0s / deg)))
                    continue
        log.dump('Added %i Cosine dihedral terms' % ncos)
示例#32
0
    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)
示例#33
0
文件: qff.py 项目: tovrstra/QuickFF
def main():
    options, fns = parse()
    #define logger
    if options.silent:
        log.set_level('silent')
    else:
        if options.very_verbose:
            log.set_level('highest')
        elif options.verbose:
            log.set_level('high')
        if options.logfile is not None and isinstance(options.logfile, str):
            log.write_to_file(options.logfile)
    with log.section('QFF', 1, timer='Initializing'):
        log.dump('Initializing system')
        #read system and ab initio reference
        system = None
        energy = 0.0
        grad = None
        hess = None
        rvecs = None
        for fn in fns:
            if fn.endswith('.fchk') or fn.endswith('.xml'):
                numbers, coords, energy, grad, hess, masses, rvecs, pbc = read_abinitio(
                    fn)
                if system is None:
                    system = System(numbers,
                                    coords,
                                    rvecs=rvecs,
                                    charges=None,
                                    radii=None,
                                    masses=masses)
                else:
                    system.pos = coords.copy()
                    system.cell = Cell(rvecs)
                    system.numbers = numbers.copy()
                    if masses is not None: system.masses = masses.copy()
                    system._init_derived()
            elif fn.endswith('.chk'):
                sample = load_chk(fn)
                if 'energy' in sample.keys(): energy = sample['energy']
                if 'grad' in sample.keys(): grad = sample['grad']
                elif 'gradient' in sample.keys(): grad = sample['gradient']
                if 'hess' in sample.keys(): hess = sample['hess']
                elif 'hessian' in sample.keys(): hess = sample['hessian']
                if system is None:
                    system = System.from_file(fn)
                else:
                    if 'pos' in sample.keys(): system.pos = sample['pos']
                    elif 'coords' in sample.keys():
                        system.pos = sample['coords']
                    if 'rvecs' in sample.keys():
                        system.cell = Cell(sample['rvecs'])
                    elif 'cell' in sample.keys():
                        system.cell = Cell(sample['cell'])
                    if 'bonds' in sample.keys(): system.bonds = sample['bonds']
                    if 'ffatypes' in sample.keys():
                        system.ffatypes = sample['ffatypes']
                    if 'ffatype_ids' in sample.keys():
                        system.ffatype_ids = sample['ffatype_ids']
                    system._init_derived()
            else:
                raise NotImplementedError('File format for %s not supported' %
                                          fn)
        assert system is not None, 'No system could be defined from input'
        assert grad is not None, 'No ab initio gradient found in input'
        assert hess is not None, 'No ab initio hessian found in input'
        #complete the system information
        if system.bonds is None: system.detect_bonds()
        if system.masses is None: system.set_standard_masses()
        if system.ffatypes is None:
            if options.ffatypes in ['low', 'medium', 'high', 'highest']:
                guess_ffatypes(system, options.ffatypes)
            elif options.ffatypes is not None:
                raise NotImplementedError(
                    'Guessing atom types from %s not implemented' %
                    options.ffatypes)
            else:
                raise AssertionError('No atom types defined')
        #construct ab initio reference
        ai = SecondOrderTaylor('ai',
                               coords=system.pos.copy(),
                               energy=energy,
                               grad=grad,
                               hess=hess,
                               pbc=pbc)
        #detect a priori defined contributions to the force field
        refs = []
        if options.ei is not None:
            if rvecs is None:
                ff = ForceField.generate(system,
                                         options.ei,
                                         rcut=50 * angstrom)
            else:
                ff = ForceField.generate(system,
                                         options.ei,
                                         rcut=20 * angstrom,
                                         alpha_scale=3.2,
                                         gcut_scale=1.5,
                                         smooth_ei=True)
            refs.append(YaffForceField('EI', ff))
        if options.vdw is not None:
            ff = ForceField.generate(system, options.vdw, rcut=20 * angstrom)
            refs.append(YaffForceField('vdW', ff))
        if options.covres is not None:
            ff = ForceField.generate(system, options.covres)
            refs.append(YaffForceField('Cov res', ff))
    #define quickff program
    assert options.program_mode in allowed_programs, \
        'Given program mode %s not allowed. Choose one of %s' %(
            options.program_mode,
            ', '.join([prog for prog in allowed_programs if not prog=='BaseProgram'])
        )
    mode = program_modes[options.program_mode]
    only_traj = 'PT_ALL'
    if options.only_traj is not None: only_traj = options.only_traj.split(',')
    program = mode(system,
                   ai,
                   ffrefs=refs,
                   fn_traj=options.fn_traj,
                   only_traj=only_traj,
                   plot_traj=options.ener_traj,
                   xyz_traj=options.xyz_traj,
                   suffix=options.suffix)
    #run program
    program.run()
示例#34
0
    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)
示例#35
0
def qff(args=None):
    if args is None:
        args = qff_parse_args()
    else:
        args = qff_parse_args(args)
    #define logger
    verbosity = None
    if args.silent:
        verbosity = 'silent'
    else:
        if args.very_verbose:
            verbosity = 'highest'
        elif args.verbose:
            verbosity = 'high'
    #get settings
    kwargs = {
        'fn_traj': args.fn_traj,
        'only_traj': args.only_traj,
        'program_mode': args.program_mode,
        'plot_traj': args.plot_traj,
        'xyz_traj': args.xyz_traj,
        'suffix': args.suffix,
        'log_level': verbosity,
        'log_file': args.logfile,
        'ffatypes': args.ffatypes,
        'ei': args.ei,
        'ei_rcut': args.ei_rcut,
        'vdw': args.vdw,
        'vdw_rcut': args.vdw_rcut,
        'covres': args.covres,
    }
    settings = Settings(fn=args.config_file, **kwargs)
    with log.section('INIT', 1, timer='Initializing'):
        log.dump('Initializing system')
        #read system and ab initio reference
        system = None
        energy = 0.0
        grad = None
        hess = None
        pbc = None
        rvecs = None
        for fn in args.fn:
            if fn.endswith('.fchk') or fn.endswith('.xml'):
                numbers, coords, energy, grad, hess, masses, rvecs, pbc = read_abinitio(
                    fn)
                if system is None:
                    system = System(numbers,
                                    coords,
                                    rvecs=rvecs,
                                    charges=None,
                                    radii=None,
                                    masses=masses)
                else:
                    system.pos = coords.copy()
                    system.cell = Cell(rvecs)
                    system.numbers = numbers.copy()
                    if masses is not None: system.masses = masses.copy()
                    system._init_derived()
            elif fn.endswith('.chk'):
                sample = load_chk(fn)
                if 'energy' in list(sample.keys()): energy = sample['energy']
                if 'grad' in list(sample.keys()): grad = sample['grad']
                elif 'gradient' in list(sample.keys()):
                    grad = sample['gradient']
                if 'hess' in list(sample.keys()): hess = sample['hess']
                elif 'hessian' in list(sample.keys()): hess = sample['hessian']
                if 'rvecs' in list(sample.keys()): pbc = [1, 1, 1]
                else: pbc = [0, 0, 0]
                if system is None:
                    system = System.from_file(fn)
                else:
                    if 'pos' in list(sample.keys()): system.pos = sample['pos']
                    elif 'coords' in list(sample.keys()):
                        system.pos = sample['coords']
                    if 'rvecs' in list(sample.keys()):
                        system.cell = Cell(sample['rvecs'])
                    elif 'cell' in list(sample.keys()):
                        system.cell = Cell(sample['cell'])
                    if 'bonds' in list(sample.keys()):
                        system.bonds = sample['bonds']
                    if 'ffatypes' in list(sample.keys()):
                        system.ffatypes = sample['ffatypes']
                    if 'ffatype_ids' in list(sample.keys()):
                        system.ffatype_ids = sample['ffatype_ids']
                    system._init_derived()
            else:
                raise NotImplementedError('File format for %s not supported' %
                                          fn)
        assert system is not None, 'No system could be defined from input'
        assert grad is not None, 'No ab initio gradient found in input'
        assert hess is not None, 'No ab initio hessian found in input'
        #complete the system information
        if system.bonds is None: system.detect_bonds()
        if system.masses is None: system.set_standard_masses()
        if system.ffatypes is None:
            if settings.ffatypes is not None:
                set_ffatypes(system, settings.ffatypes)
            else:
                raise AssertionError('No atom types defined')
        if settings.do_hess_negfreq_proj:
            log.dump(
                'Projecting negative frequencies out of the mass-weighted hessian.'
            )
            with log.section('SYS', 3, 'Initializing'):
                hess = project_negative_freqs(hess, system.masses)
        #construct ab initio reference
        ai = SecondOrderTaylor('ai',
                               coords=system.pos.copy(),
                               energy=energy,
                               grad=grad,
                               hess=hess,
                               pbc=pbc)
        #detect a priori defined contributions to the force field
        refs = []
        if settings.ei is not None:
            if rvecs is None:
                if settings.ei_rcut is None:
                    rcut = 50 * angstrom
                else:
                    rcut = settings.ei_rcut
                ff = ForceField.generate(system, settings.ei, rcut=rcut)
            else:
                if settings.ei_rcut is None:
                    rcut = 20 * angstrom
                else:
                    rcut = settings.ei_rcut
                ff = ForceField.generate(system,
                                         settings.ei,
                                         rcut=rcut,
                                         alpha_scale=3.2,
                                         gcut_scale=1.5,
                                         smooth_ei=True)
            refs.append(YaffForceField('EI', ff))
        if settings.vdw is not None:
            ff = ForceField.generate(system,
                                     settings.vdw,
                                     rcut=settings.vdw_rcut)
            refs.append(YaffForceField('vdW', ff))
        if settings.covres is not None:
            ff = ForceField.generate(system, settings.covres)
            refs.append(YaffForceField('Cov res', ff))
    #define quickff program
    assert settings.program_mode in allowed_programs, \
        'Given program mode %s not allowed. Choose one of %s' %(
            settings.program_mode,
            ', '.join([prog for prog in allowed_programs if not prog=='BaseProgram'])
        )
    mode = program_modes[settings.program_mode]
    program = mode(system, ai, settings, ffrefs=refs)
    #run program
    program.run()
    return program
示例#36
0
 def __init__(self, name, ff):
     log.dump('Initializing Yaff force field reference for %s' %name)
     self.ff = ff
     Reference.__init__(self, name)
示例#37
0
    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)