def bind(self, ensemble, motion, beads=None, forces=None): """ Initializes the normal modes object and binds to beads and ensemble. Do all the work down here as we need a full-formed necklace and ensemble to know how this should be done. Args: beads: A beads object to be bound. ensemble: An ensemble object to be bound. """ self.ensemble = ensemble self.motion = motion if beads is None: self.beads = motion.beads else: self.beads = beads self.forces = forces self.nbeads = beads.nbeads self.natoms = beads.natoms dself = dd(self) # stores a reference to the bound beads and ensemble objects self.ensemble = ensemble dpipe(dd(motion).dt, dself.dt) # sets up what's necessary to perform nm transformation. if self.nbeads == 1: # classical trajectory! don't waste time doing anything! self.transform = nmtransform.nm_noop(nbeads=self.nbeads) elif self.transform_method == "fft": self.transform = nmtransform.nm_fft(nbeads=self.nbeads, natoms=self.natoms, open_paths=self.open_paths) elif self.transform_method == "matrix": self.transform = nmtransform.nm_trans(nbeads=self.nbeads, open_paths=self.open_paths) # creates arrays to store normal modes representation of the path. # must do a lot of piping to create "ex post" a synchronization between the beads and the nm sync_q = synchronizer() sync_p = synchronizer() dself.qnm = depend_array( name="qnm", value=np.zeros((self.nbeads, 3 * self.natoms), float), func={"q": (lambda: self.transform.b2nm(dstrip(self.beads.q)))}, synchro=sync_q) dself.pnm = depend_array( name="pnm", value=np.zeros((self.nbeads, 3 * self.natoms), float), func={"p": (lambda: self.transform.b2nm(dstrip(self.beads.p)))}, synchro=sync_p) # must overwrite the functions dd(self.beads).q._func = { "qnm": (lambda: self.transform.nm2b(dstrip(self.qnm))) } dd(self.beads).p._func = { "pnm": (lambda: self.transform.nm2b(dstrip(self.pnm))) } dd(self.beads).q.add_synchro(sync_q) dd(self.beads).p.add_synchro(sync_p) # also within the "atomic" interface to beads for b in range(self.nbeads): dd(self.beads._blist[b]).q._func = { "qnm": (lambda: self.transform.nm2b(dstrip(self.qnm))) } dd(self.beads._blist[b]).p._func = { "pnm": (lambda: self.transform.nm2b(dstrip(self.pnm))) } dd(self.beads._blist[b]).q.add_synchro(sync_q) dd(self.beads._blist[b]).p.add_synchro(sync_p) # finally, we mark the beads as those containing the set positions dd(self.beads).q.update_man() dd(self.beads).p.update_man() # forces can be converted in nm representation, but here it makes no sense to set up a sync mechanism, # as they always get computed in the bead rep if not self.forces is None: dself.fnm = depend_array( name="fnm", value=np.zeros((self.nbeads, 3 * self.natoms), float), func=(lambda: self.transform.b2nm(dstrip(self.forces.f))), dependencies=[dd(self.forces).f]) else: # have a fall-back plan when we don't want to initialize a force mechanism, e.g. for ring-polymer initialization dself.fnm = depend_array( name="fnm", value=np.zeros((self.nbeads, 3 * self.natoms), float), func=(lambda: depraise( ValueError( "Cannot access NM forces when initializing the NM object without providing a force reference!" ))), dependencies=[]) # create path-frequencies related properties dself.omegan = depend_value(name='omegan', func=self.get_omegan, dependencies=[dd(self.ensemble).temp]) dself.omegan2 = depend_value(name='omegan2', func=self.get_omegan2, dependencies=[dself.omegan]) dself.omegak = depend_array(name='omegak', value=np.zeros(self.beads.nbeads, float), func=self.get_omegak, dependencies=[dself.omegan]) dself.omegak2 = depend_array(name='omegak2', value=np.zeros(self.beads.nbeads, float), func=(lambda: self.omegak**2), dependencies=[dself.omegak]) # Add o_omegak to calculate the freq in the case of open path dself.o_omegak = depend_array(name='o_omegak', value=np.zeros(self.beads.nbeads, float), func=self.get_o_omegak, dependencies=[dself.omegan]) # sets up "dynamical" masses -- mass-scalings to give the correct RPMD/CMD dynamics dself.nm_factor = depend_array( name="nm_factor", value=np.zeros(self.nbeads, float), func=self.get_nmm, dependencies=[dself.nm_freqs, dself.mode]) # add o_nm_factor for the dynamical mass in the case of open paths dself.o_nm_factor = depend_array( name="nmm", value=np.zeros(self.nbeads, float), func=self.get_o_nmm, dependencies=[dself.nm_freqs, dself.mode]) dself.dynm3 = depend_array( name="dynm3", value=np.zeros((self.nbeads, 3 * self.natoms), float), func=self.get_dynm3, dependencies=[dself.nm_factor, dd(self.beads).m3]) dself.dynomegak = depend_array( name="dynomegak", value=np.zeros(self.nbeads, float), func=self.get_dynwk, dependencies=[dself.nm_factor, dself.omegak]) dself.dt = depend_value(name="dt", value=1.0) dpipe(dd(self.motion).dt, dself.dt) dself.prop_pq = depend_array( name='prop_pq', value=np.zeros((self.beads.nbeads, 2, 2)), func=self.get_prop_pq, dependencies=[dself.omegak, dself.nm_factor, dself.dt]) dself.o_prop_pq = depend_array( name='o_prop_pq', value=np.zeros((self.beads.nbeads, 2, 2)), func=self.get_o_prop_pq, dependencies=[dself.o_omegak, dself.o_nm_factor, dself.dt]) # if the mass matrix is not the RPMD one, the MD kinetic energy can't be # obtained in the bead representation because the masses are all mixed up dself.kins = depend_array( name="kins", value=np.zeros(self.nbeads, float), func=self.get_kins, dependencies=[dself.pnm, dd(self.beads).sm3, dself.nm_factor]) dself.kin = depend_value(name="kin", func=self.get_kin, dependencies=[dself.kins]) dself.kstress = depend_array( name="kstress", value=np.zeros((3, 3), float), func=self.get_kstress, dependencies=[dself.pnm, dd(self.beads).sm3, dself.nm_factor]) # spring energy, calculated in normal modes dself.vspring = depend_value(name="vspring", value=0.0, func=self.get_vspring, dependencies=[ dself.qnm, dself.omegak, dself.o_omegak, dd(self.beads).m3 ]) # spring forces on normal modes dself.fspringnm = depend_array( name="fspringnm", value=np.zeros((self.nbeads, 3 * self.natoms), float), func=self.get_fspringnm, dependencies=[dself.qnm, dself.omegak, dd(self.beads).m3]) # spring forces on beads, transformed from normal modes dself.fspring = depend_array( name="fs", value=np.zeros((self.nbeads, 3 * self.natoms), float), func=(lambda: self.transform.nm2b(dstrip(self.fspringnm))), dependencies=[dself.fspringnm])
def bind(self, ensemble, motion, beads=None, forces=None): """ Initializes the normal modes object and binds to beads and ensemble. Do all the work down here as we need a full-formed necklace and ensemble to know how this should be done. Args: beads: A beads object to be bound. ensemble: An ensemble object to be bound. """ self.ensemble = ensemble self.motion = motion if beads is None: self.beads = motion.beads else: self.beads = beads self.forces = forces self.nbeads = beads.nbeads self.natoms = beads.natoms dself = dd(self) # stores a reference to the bound beads and ensemble objects self.ensemble = ensemble dpipe(dd(motion).dt, dself.dt) # sets up what's necessary to perform nm transformation. if self.nbeads == 1: # classical trajectory! don't waste time doing anything! self.transform = nmtransform.nm_noop(nbeads = self.nbeads) elif self.transform_method == "fft": self.transform = nmtransform.nm_fft(nbeads=self.nbeads, natoms=self.natoms, open_paths=self.open_paths) elif self.transform_method == "matrix": self.transform = nmtransform.nm_trans(nbeads=self.nbeads, open_paths=self.open_paths) # creates arrays to store normal modes representation of the path. # must do a lot of piping to create "ex post" a synchronization between the beads and the nm sync_q = synchronizer() sync_p = synchronizer() dself.qnm = depend_array(name="qnm", value=np.zeros((self.nbeads, 3 * self.natoms), float), func={"q": (lambda: self.transform.b2nm(dstrip(self.beads.q)))}, synchro=sync_q) dself.pnm = depend_array(name="pnm", value=np.zeros((self.nbeads, 3 * self.natoms), float), func={"p": (lambda: self.transform.b2nm(dstrip(self.beads.p)))}, synchro=sync_p) # must overwrite the functions dd(self.beads).q._func = {"qnm": (lambda: self.transform.nm2b(dstrip(self.qnm)))} dd(self.beads).p._func = {"pnm": (lambda: self.transform.nm2b(dstrip(self.pnm)))} dd(self.beads).q.add_synchro(sync_q) dd(self.beads).p.add_synchro(sync_p) # also within the "atomic" interface to beads for b in range(self.nbeads): dd(self.beads._blist[b]).q._func = {"qnm": (lambda: self.transform.nm2b(dstrip(self.qnm)))} dd(self.beads._blist[b]).p._func = {"pnm": (lambda: self.transform.nm2b(dstrip(self.pnm)))} dd(self.beads._blist[b]).q.add_synchro(sync_q) dd(self.beads._blist[b]).p.add_synchro(sync_p) # finally, we mark the beads as those containing the set positions dd(self.beads).q.update_man() dd(self.beads).p.update_man() # forces can be converted in nm representation, but here it makes no sense to set up a sync mechanism, # as they always get computed in the bead rep if not self.forces is None: dself.fnm = depend_array(name="fnm", value=np.zeros((self.nbeads, 3 * self.natoms), float), func=(lambda: self.transform.b2nm(dstrip(self.forces.f))), dependencies=[dd(self.forces).f]) else: # have a fall-back plan when we don't want to initialize a force mechanism, e.g. for ring-polymer initialization dself.fnm = depend_array(name="fnm", value=np.zeros((self.nbeads, 3 * self.natoms), float), func=(lambda: depraise(ValueError("Cannot access NM forces when initializing the NM object without providing a force reference!"))), dependencies=[]) # create path-frequencies related properties dself.omegan = depend_value(name='omegan', func=self.get_omegan, dependencies=[dd(self.ensemble).temp]) dself.omegan2 = depend_value(name='omegan2', func=self.get_omegan2, dependencies=[dself.omegan]) dself.omegak = depend_array(name='omegak', value=np.zeros(self.beads.nbeads, float), func=self.get_omegak, dependencies=[dself.omegan]) dself.omegak2 = depend_array(name='omegak2', value=np.zeros(self.beads.nbeads, float), func=(lambda: self.omegak**2), dependencies=[dself.omegak]) # Add o_omegak to calculate the freq in the case of open path dself.o_omegak = depend_array(name='o_omegak', value=np.zeros(self.beads.nbeads, float), func=self.get_o_omegak, dependencies=[dself.omegan]) # sets up "dynamical" masses -- mass-scalings to give the correct RPMD/CMD dynamics dself.nm_factor = depend_array(name="nm_factor", value=np.zeros(self.nbeads, float), func=self.get_nmm, dependencies=[dself.nm_freqs, dself.mode]) # add o_nm_factor for the dynamical mass in the case of open paths dself.o_nm_factor = depend_array(name="nmm", value=np.zeros(self.nbeads, float), func=self.get_o_nmm, dependencies=[dself.nm_freqs, dself.mode]) dself.dynm3 = depend_array(name="dynm3", value=np.zeros((self.nbeads, 3 * self.natoms), float), func=self.get_dynm3, dependencies=[dself.nm_factor, dd(self.beads).m3]) dself.dynomegak = depend_array(name="dynomegak", value=np.zeros(self.nbeads, float), func=self.get_dynwk, dependencies=[dself.nm_factor, dself.omegak]) dself.dt = depend_value(name="dt", value=1.0) dpipe(dd(self.motion).dt, dself.dt) dself.prop_pq = depend_array(name='prop_pq', value=np.zeros((self.beads.nbeads, 2, 2)), func=self.get_prop_pq, dependencies=[dself.omegak, dself.nm_factor, dself.dt]) dself.o_prop_pq = depend_array(name='o_prop_pq', value=np.zeros((self.beads.nbeads, 2, 2)), func=self.get_o_prop_pq, dependencies=[dself.o_omegak, dself.o_nm_factor, dself.dt]) # if the mass matrix is not the RPMD one, the MD kinetic energy can't be # obtained in the bead representation because the masses are all mixed up dself.kins = depend_array(name="kins", value=np.zeros(self.nbeads, float), func=self.get_kins, dependencies=[dself.pnm, dd(self.beads).sm3, dself.nm_factor]) dself.kin = depend_value(name="kin", func=self.get_kin, dependencies=[dself.kins]) dself.kstress = depend_array(name="kstress", value=np.zeros((3, 3), float), func=self.get_kstress, dependencies=[dself.pnm, dd(self.beads).sm3, dself.nm_factor]) # spring energy, calculated in normal modes dself.vspring = depend_value(name="vspring", value=0.0, func=self.get_vspring, dependencies=[dself.qnm, dself.omegak, dself.o_omegak, dd(self.beads).m3]) # spring forces on normal modes dself.fspringnm = depend_array(name="fspringnm", value=np.zeros((self.nbeads, 3 * self.natoms), float), func=self.get_fspringnm, dependencies=[dself.qnm, dself.omegak, dd(self.beads).m3]) # spring forces on beads, transformed from normal modes dself.fspring = depend_array(name="fs", value=np.zeros((self.nbeads, 3 * self.natoms), float), func=(lambda: self.transform.nm2b(dstrip(self.fspringnm))), dependencies=[dself.fspringnm])