Beispiel #1
0
    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])
Beispiel #2
0
    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])