Exemplo n.º 1
0
def spawn_backward(child, current_time, end_time, dt):
    """Propagates the child backwards in time until the current time
    is reached."""
    nstep = int(round( np.abs((current_time-end_time) / dt) ))

    back_time = current_time
    for i in range(nstep):
        step.step_trajectory(child, back_time, dt)
        back_time = back_time + dt
        log.print_message('spawn_back', [back_time])
Exemplo n.º 2
0
def step_trajectory(traj, init_time, dt):
    """Propagates a single trajectory.

    Used to backward/forward propagate a trajectory during spawning.
    NOTE: step_wavefunction and step_trajectory could/should probably
    be integrated somehow...
    """
    current_time = init_time
    end_time = init_time + dt
    time_step = dt
    min_time_step = abs(dt / 2.**5)

    while not step_complete(current_time, end_time, time_step):
        # save the wavefunction from previous step in case step rejected
        traj0 = traj.copy()

        # propagate single trajectory
        glbl.modules['propagator'].propagate_trajectory(traj, time_step)

        # update the couplings for the trajectory
        glbl.modules['interface'].evaluate_coupling(traj)

        # update current time
        proposed_time = current_time + time_step

        # check time_step is fine, energy/amplitude conserved
        accept = check_step_trajectory(traj0, traj)

        # if everything is ok..
        if accept:
            current_time = proposed_time
        else:
            # redo time step
            # recall -- this time trying to propagate
            # to the failed step
            time_step *= 0.5

            if abs(time_step) < min_time_step:
                log.print_message('general',
                                  ['minimum time step exceeded -- STOPPING.'])
                raise ValueError('Trajectory minimum step exceeded.')

            # reset the beginning of the time step and go to beginning of loop
            traj = traj0.copy()
Exemplo n.º 3
0
def set_initial_coords(wfn):
    """Takes initial position and momentum from geometry specified in input"""
    coords = glbl.properties['init_coords']
    ndim   = coords.shape[-1]

    log.print_message('string',[' Initial coordinates taken from input file(s).\n'])

    for coord in coords:
        itraj = trajectory.Trajectory(glbl.properties['n_states'], ndim,
                                      width=glbl.properties['crd_widths'],
                                      mass=glbl.properties['crd_masses'],
                                      parent=0, kecoef=glbl.modules['integrals'].kecoef)

        # set position and momentum
        itraj.update_x(np.array(coord[0]))
        itraj.update_p(np.array(coord[1]))

        # add a single trajectory specified by geometry.dat
        wfn.add_trajectory(itraj)
Exemplo n.º 4
0
def set_initial_state(wfn):
    """Sets the initial state of the trajectories in the bundle."""

    if glbl.properties['init_brightest']:
        # initialize to the state with largest transition dipole moment
        # set all states to the ground state
        for i in range(wfn.n_traj()):
            wfn.traj[i].state = 0
            # compute transition dipoles
            evaluate.update_pes_traj(wfn.traj[i])

        # set the initial state to the one with largest t. dip.
        for i in range(wfn.n_traj()):
            if 'dipole' not in wfn.traj[i].pes.avail_data():
                raise KeyError('trajectory ' + str(i) +
                               ': Cannot set state by transition moments - ' +
                               'dipole not in pes.avail_data()')

            tr_dipole = wfn.traj[i].pes.get_data('dipole')
            tdip = np.array([
                np.linalg.norm(tr_dipole[:, 0, j])
                for j in range(1, glbl.properties['n_states'])
            ])
            log.print_message('general',
                             ['Initializing trajectory '+str(i)+
                              ' to state '+str(np.argmax(tdip)+1)+
                              ' | tr. dipople array='+np.array2string(tdip, \
                              formatter={'float_kind':lambda x: "%.4f" % x})])
            wfn.traj[i].state = np.argmax(tdip) + 1
    elif len(glbl.properties['init_state']) == wfn.n_traj():
        # use "init_state" to set the initial state
        for i in range(wfn.n_traj()):
            istate = glbl.properties['init_state'][i]
            if istate < 0:
                wfn.traj[i].state = glbl.properties['n_states'] + istate
            else:
                wfn.traj[i].state = istate
    else:
        raise ValueError('Ambiguous initial state assignment.')
Exemplo n.º 5
0
def spawn_forward(parent, child_state, initial_time, dt):
    """Propagates the parent forward (into the future) until the coupling
    decreases."""
    parent_state    = parent.state
    current_time    = initial_time
    spawn_time      = initial_time
    exit_time       = initial_time
    child_created   = False
    parent_at_spawn = None
    child_at_spawn  = None

    coup = np.zeros(3)
    log.print_message('spawn_start',
                             [parent.label, parent_state, child_state])

    while True:
        coup                = np.roll(coup,1)
        coup[0]             = abs(parent.coupling(parent_state, child_state))
        child_attempt       = parent.copy()
        child_attempt.state = child_state
        adjust_success      = utils.adjust_child(parent, child_attempt,
                                                 parent.derivative(parent_state,
                                                                   child_state))
        sij = abs(glbl.modules['integrals'].nuc_overlap(parent, child_attempt))

        # if the coupling has already peaked, either we exit with a successful
        # spawn from previous step, or we exit with a fail
        if np.all(coup[0] < coup[1:]):
            sp_str = 'no [decreasing coupling]'
            log.print_message('spawn_step',
                                     [current_time, coup[0], sij, sp_str])

            if child_created:
                log.print_message('spawn_success', [spawn_time])
                child_at_spawn.exit_time[parent_state] = current_time
            else:
                log.print_message('spawn_failure', [current_time])

            # exit, we're done trying to spawn
            exit_time = current_time
            break

        # coupling still increasing
        else:
            # try to set up the child
            if not adjust_success:
                sp_str = 'no [momentum adjust fail]'
            elif sij < glbl.properties['spawn_olap_thresh']:
                sp_str = 'no [overlap too small]'
            elif not np.all(coup[0] > coup[1:]):
                sp_str = 'no [decreasing coupling]'
            else:
                spawn_time                              = current_time
                child_created                           = True
                parent_at_spawn                         = parent.copy()
                child_at_spawn                          = child_attempt.copy()
                child_at_spawn.last_spawn[parent_state] = spawn_time
                child_at_spawn.amplitude                = 0j
                child_at_spawn.parent                   = parent.label
                child_at_spawn.label                    = parent.label
                sp_str                                  = 'yes'

            log.print_message('spawn_step',
                                     [current_time, coup[0], sij, sp_str])

            step.step_trajectory(parent, current_time, dt)
            current_time = current_time + dt

    return child_created, child_at_spawn, parent_at_spawn, spawn_time, exit_time
Exemplo n.º 6
0
def spawn(wfn, dt):
    """Propagates to the point of maximum coupling, spawns a new
    basis function, then propagates the function to the current time."""
    global coup_hist

    basis_grown   = False
    current_time  = wfn.time
    # list of added trajectories

    # we want to know the history of the coupling for each trajectory
    # in order to assess spawning criteria -- make sure coup_hist has a slot
    # for every trajectory
    if len(coup_hist) < wfn.n_traj():
        n_add = wfn.n_traj() - len(coup_hist)
        for i in range(n_add):
            coup_hist.append(np.zeros((glbl.properties['n_states'], 3)))

    # iterate over all trajectories in bundle
    for i in range(wfn.n_traj()):
        # only live trajectories can spawn
        if not wfn.traj[i].alive:
            continue

        for st in range(glbl.properties['n_states']):
            # can only spawn to different electronic states
            if wfn.traj[i].state == st:
                continue

            # compute magnitude of coupling to state j
            coup = abs(wfn.traj[i].coupling(wfn.traj[i].state, st))
            coup_hist[i][st,:] = np.roll(coup_hist[i][st,:],1)
            coup_hist[i][st,0] = coup

            # if we satisfy spawning conditions, begin spawn process
            if spawn_trajectory(wfn, i, st, coup_hist[i][st,:],
                                current_time):
                # we're going to messing with this trajectory -- mess with a copy
                parent = wfn.traj[i].copy()

                # propagate the parent forward in time until coupling maximized
                [success, child, parent_spawn, spawn_time,
                 exit_time] = spawn_forward(parent, st, current_time, dt)

                # set the spawn attempt in wfn, even if spawn failed (avoid repeated fails)
                wfn.traj[i].last_spawn[st] = spawn_time
                wfn.traj[i].exit_time[st]  = exit_time

                if success:
                    # at this point, child is at the spawn point. Propagate
                    # backwards in time until we reach the current time
                    child_spawn = child.copy()
                    # need electronic structure at current geometry -- on correct state
                    evaluate.update_pes_traj(child)
                    spawn_backward(child, spawn_time, current_time, -dt)
                    bundle_overlap = utils.overlap_with_bundle(child, wfn)
                    if not bundle_overlap:
                        basis_grown = True
                        wfn.add_trajectory(child)
                        child_spawn.label = wfn.traj[-1].label # a little hacky...
                        utils.write_spawn_log(current_time, spawn_time, exit_time,
                                                  parent_spawn, child_spawn)
                    else:
                        log.print_message('spawn_bad_step',
                                                 ['overlap with bundle too large'])

    # let caller known if the basis has been changed
    return basis_grown
Exemplo n.º 7
0
def init_interface():
    """Reads the operator file.

    Note that the order of active modes is determined by the init_coords
    read from the input file.
    """
    global ham

    # Read in operator file
    ham = VibHam()

    ham.mlbl_active  = [lbl.lower() for lbl in glbl.properties['crd_labels']]
    ham.nmode_active = len(glbl.properties['crd_labels'])

    # operator file will always be a separate file
    #ham.rdoperfile(glbl.home_path + '/' + glbl.vibronic['opfile'])
    ham.rdoperfile(glbl.vibronic['opfile'])

    # Ouput some information about the Hamiltonian
    log.print_message('string', ['*'*72])
    log.print_message('string',
                             ['* Vibronic Coupling Hamiltonian Information'])
    log.print_message('string', ['*'*72])
    log.print_message('string',
                             ['Operator file: ' + glbl.vibronic['opfile']])
    log.print_message('string',
                             ['Number of Hamiltonian terms: ' + str(ham.nterms)])
    string = 'Total no. modes: ' + str(ham.nmode_total)
    log.print_message('string', [string])

    string = 'No. active modes: ' + str(ham.nmode_active)
    log.print_message('string', [string])

    log.print_message('string', ['Active mode labels:'])
    for i in range(ham.nmode_active):
        string = str(i+1) + ' ' + ham.mlbl_active[i]
        log.print_message('string', [string])

    log.print_message('string', ['Active mode frequencies (a.u.):'])
    for i in range(ham.nmode_active):
        string = str(i+1) + ' ' + str(ham.freq[i])
        log.print_message('string', [string])
Exemplo n.º 8
0
def set_initial_coords(wfn):
    """Samples a v=0 Wigner distribution."""
    # Set the coordinate type: Cartesian or normal mode coordinates
    if glbl.methods['interface'] == 'vibronic':
        coordtype = 'normal'
        ham = glbl.modules['interface'].ham
    else:
        coordtype = 'cart'

    log.print_message('string', [' sampling from v=0 Wigner distribution.\n'])

    # if multiple geometries, just take the first one
    coords = glbl.properties['init_coords']
    ndim = coords.shape[-1]
    x_ref = coords[0, 0]
    p_ref = coords[0, 1]
    w_vec = glbl.properties['crd_widths']
    m_vec = glbl.properties['crd_masses']

    # create template trajectory basis function
    template = trajectory.Trajectory(glbl.properties['n_states'],
                                     ndim,
                                     width=w_vec,
                                     mass=m_vec,
                                     parent=0,
                                     kecoef=glbl.modules['integrals'].kecoef)
    template.update_x(x_ref)
    template.update_p(p_ref)

    # If Cartesian coordinates are being used, then set up the
    # mass-weighted Hessian and diagonalise to obtain the normal modes
    # and frequencies
    if coordtype == 'cart':
        iface_dict = glbl.sections[glbl.methods['interface']]
        hessian = iface_dict['hessian']
        invmass = np.asarray([
            1. / np.sqrt(m_vec[i]) if m_vec[i] != 0. else 0
            for i in range(len(m_vec))
        ],
                             dtype=float)
        mw_hess = invmass * hessian * invmass[:, np.newaxis]
        evals, evecs = sp_linalg.eigh(mw_hess)
        f_cutoff = 0.0001
        freq_list = []
        mode_list = []
        for i in range(len(evals)):
            if evals[i] >= 0 and np.sqrt(evals[i]) >= f_cutoff:
                freq_list.append(np.sqrt(evals[i]))
                mode_list.append(evecs[:, i].tolist())
        n_modes = len(freq_list)
        freqs = np.asarray(freq_list)
        modes = np.asarray(mode_list).transpose()
        # confirm that modes * tr(modes) = 1
        m_chk = np.dot(modes.T, modes)
        if not np.allclose(m_chk, np.eye(len(m_chk))):
            raise ValueError('Internal coordinates not orthonormal.')
        log.print_message('string', [' -- frequencies from hessian.dat --\n'])

    # If normal modes are being used, set the no. modes
    # equal to the total number of modes of the model
    # Hamiltonian and load only the active frequencies
    if coordtype == 'normal':
        n_modes = len(w_vec)
        # we multiply by 0.5 below -- multiply by 2 here (i.e. default width for
        # vibronic hamiltonans is 1/2, assuming frequency weighted coords
        freqs = 2. * w_vec
        log.print_message(
            'string', ['\n -- widths employed in coordinate sampling --\n'])

    # write out frequencies
    fstr = '\n'.join([
        '{0:.5f} au == {1:10.1f} cm^-1'.format(freqs[j],
                                               freqs[j] * constants.au2cm)
        for j in range(n_modes)
    ])
    log.print_message('string', [fstr + '\n'])

    # loop over the number of initial trajectories
    max_try = 1000
    ntraj = glbl.properties['n_init_traj']
    for i in range(ntraj):
        delta_x = np.zeros(n_modes)
        delta_p = np.zeros(n_modes)
        x_sample = np.zeros(n_modes)
        p_sample = np.zeros(n_modes)
        for j in range(n_modes):
            alpha = 0.5 * freqs[j]
            if alpha > constants.fpzero:
                sigma_x = (glbl.properties['distrib_compression'] *
                           np.sqrt(0.25 / alpha))
                sigma_p = (glbl.properties['distrib_compression'] *
                           np.sqrt(alpha))
                itry = 0
                while itry <= max_try:
                    dx = np.random.normal(0., sigma_x)
                    dp = np.random.normal(0., sigma_p)
                    itry += 1
                    if mode_overlap(
                            alpha, dx,
                            dp) > glbl.properties['init_mode_min_olap']:
                        break
                if mode_overlap(alpha, dx,
                                dp) < glbl.properties['init_mode_min_olap']:
                    raise ValueError(
                        'Cannot get mode overlap > ' +
                        str(glbl.properties['init_mode_min_olap']) +
                        ' within ' + str(max_try) + ' attempts. Exiting...')
                delta_x[j] = dx
                delta_p[j] = dp

        # If Cartesian coordinates are being used, displace along each
        # normal mode to generate the final geometry...
        if coordtype == 'cart':
            disp_x = np.dot(modes, delta_x) / np.sqrt(m_vec)
            disp_p = np.dot(modes, delta_p) * np.sqrt(m_vec)

        # ... else if mass- and frequency-scaled normal modes are
        # being used, then take the frequency-scaled normal mode
        # displacements and momenta as the inital point in phase
        # space
        elif coordtype == 'normal':
            disp_x = delta_x
            disp_p = delta_p
            #disp_x = delta_x * np.sqrt(freqs)
            #disp_p = delta_p * np.sqrt(freqs)

        x_sample = x_ref + disp_x
        p_sample = p_ref + disp_p

        # add new trajectory to the bundle
        new_traj = template.copy()
        new_traj.update_x(x_sample)
        new_traj.update_p(p_sample)

        # Add the trajectory to the bundle
        wfn.add_trajectory(new_traj)
Exemplo n.º 9
0
def step_wavefunction(dt):
    """Propagates the wave packet using a run-time selected propagator."""
    # save the wavefunction from previous step in case step rejected
    end_time = min(glbl.modules['wfn'].time + dt,
                   glbl.properties['simulation_time'])
    time_step = min(
        dt, glbl.properties['simulation_time'] - glbl.modules['wfn'].time)
    min_time_step = dt / 2.**5

    while not step_complete(glbl.modules['wfn'].time, end_time, dt):
        # save the wavefunction from previous step in case step rejected
        wfn_start = glbl.modules['wfn'].copy()

        # propagate each trajectory in the wavefunction
        time_step = min(time_step, end_time - glbl.modules['wfn'].time)

        # propagate amplitudes for 1/2 time step using x0
        glbl.modules['wfn'].update_amplitudes(0.5 * dt)

        # the propagators update the potential energy surface as need be.
        glbl.modules['propagator'].propagate_wfn(glbl.modules['wfn'],
                                                 time_step)

        # update the couplings for all the trajectories
        for i in range(glbl.modules['wfn'].n_traj()):
            glbl.modules['interface'].evaluate_coupling(
                glbl.modules['wfn'].traj[i])

        # propagate amplitudes for 1/2 time step using x1
        glbl.modules['matrices'].build(glbl.modules['wfn'],
                                       glbl.modules['integrals'])
        glbl.modules['wfn'].update_matrices(glbl.modules['matrices'])
        glbl.modules['wfn'].update_amplitudes(0.5 * dt)

        # Renormalization
        if glbl.properties['renorm'] == 1:
            glbl.modules['wfn'].renormalize()

        # check time_step is fine, energy/amplitude conserved
        accept, error_msg = check_step_wfn(wfn_start, glbl.modules['wfn'],
                                           time_step)

        # if everything is ok..
        if accept:
            # update the wavefunction time
            glbl.modules['wfn'].time += time_step
            # spawn new basis functions if necessary
            basis_grown = glbl.modules['adapt'].spawn(glbl.modules['wfn'],
                                                      time_step)
            # kill the dead trajectories
            basis_pruned = utilities.prune(glbl.modules['wfn'])

            # if a trajectory has been added, then call update_pes
            # to get the electronic structure information at the associated
            # centroids. This is necessary in order to propagate the amplitudes
            # at the start of the next time step.
            if basis_grown and glbl.modules['integrals'].require_centroids:
                evaluate.update_pes(glbl.modules['wfn'])

            # update the Hamiltonian and associated matrices
            if basis_grown or basis_pruned:
                glbl.modules['matrices'].build(glbl.modules['wfn'],
                                               glbl.modules['integrals'])
                glbl.modules['wfn'].update_matrices(glbl.modules['matrices'])
                for i in range(glbl.modules['wfn'].n_traj()):
                    glbl.modules['interface'].evaluate_coupling(
                        glbl.modules['wfn'].traj[i])

            # re-expression of the basis using the matching pursuit
            # algorithm
            #if glbl.properties['matching_pursuit'] == 1:
            #    mp.reexpress_basis(master)

            # update the running log
            log.print_message('t_step', [
                glbl.modules['wfn'].time, time_step, glbl.modules['wfn'].nalive
            ])
            #del wfn_start

        else:
            # recall -- this time trying to propagate to the failed step
            time_step *= 0.5
            log.print_message('new_step', [error_msg, time_step])

            if time_step < min_time_step:
                log.print_message('general',
                                  ['minimum time step exceeded -- STOPPING.'])
                raise ValueError('Bundle minimum step exceeded.')

            # reset the beginning of the time step and go to beginning of loop
            #del master
            glbl.modules['wfn'] = wfn_start.copy()
Exemplo n.º 10
0
def spawn(wfn, dt):
    """Spawns a basis function if the minimum overlap drops below a given
    threshold."""
    basis_grown = False
    current_time = wfn.time
    for i in range(wfn.n_traj()):
        if not wfn.traj[i].alive:
            continue

        parent = wfn.traj[i]
        for st in range(glbl.properties['n_states']):
            # don't check overlap with basis functions on same state
            if st == parent.state:
                continue

            s_array = [
                abs(glbl.modules['integrals'].nuc_overlap(parent, wfn.traj[j]))
                if wfn.traj[j].state == st and wfn.traj[j].alive else 0.
                for j in range(wfn.n_traj())
            ]
            if max(s_array,
                   key=abs) < glbl.properties['continuous_min_overlap']:
                child = parent.copy()
                child.amplitude = 0j
                child.state = st
                child.parent = parent.label

                success = utilities.adjust_child(
                    parent, child, parent.derivative(parent.state,
                                                     child.state))
                sij = glbl.modules['integrals'].nuc_overlap(parent, child)

                # try to set up the child
                if not success:
                    pass
                    #log.print_message('spawn_bad_step',
                    #                         ['cannot adjust kinetic energy of child'])
                elif abs(sij) < glbl.properties['spawn_olap_thresh']:
                    pass
                    #log.print_message('spawn_bad_step',
                    #                         ['child-parent overlap too small'])
                else:
                    child_created = True
                    spawn_time = current_time
                    parent.last_spawn[child.state] = spawn_time
                    child.last_spawn[parent.state] = spawn_time

                    bundle_overlap = utilities.overlap_with_bundle(child, wfn)
                    if not bundle_overlap:
                        basis_grown = True
                        wfn.add_trajectory(child)
                        log.print_message('spawn_success',
                                          [current_time, parent.label, st])
                        utilities.write_spawn_log(current_time, current_time,
                                                  current_time, parent,
                                                  wfn.traj[-1])
                    else:
                        err_msg = ('Traj ' + str(parent.label) +
                                   ' from state ' + str(parent.state) +
                                   ' to state ' + str(st) + ': ' +
                                   'overlap with bundle too large,' +
                                   ' s_max=' +
                                   str(glbl.properties['sij_thresh']))
                        log.print_message('spawn_bad_step', [err_msg])
    return basis_grown
Exemplo n.º 11
0
def init_wavefunction():
    """Initializes the trajectories."""
    # initialize the interface we'll be using the determine the
    # the PES. There are some details here that trajectories
    # will want to know about

    log.print_message(
        'string',
        ['\n **************\n' + ' initialization\n' + ' **************\n'])

    # creat the wave function instance
    glbl.modules['wfn'] = wavefunction.Wavefunction()

    glbl.modules['interface'] = __import__('nomad.interfaces.' +
                                           glbl.methods['interface'],
                                           fromlist=['NA'])
    glbl.modules['interface'].init_interface()

    # create glbl modules
    glbl.modules['matrices'] = matrices.Matrices()

    glbl.modules['init_conds'] = __import__('nomad.initconds.' +
                                            glbl.methods['init_conds'],
                                            fromlist=['NA'])
    glbl.modules['adapt'] = __import__('nomad.adapt.' +
                                       glbl.methods['adapt_basis'],
                                       fromlist=['a'])
    glbl.modules['propagator'] = __import__('nomad.propagators.' +
                                            glbl.methods['propagator'],
                                            fromlist=['a'])

    # WARNING: this should be done more eloquently
    # need to determine form of the coefficients on the kinetic energy
    # operator, i.e. cartesian vs. normal mode coordinate basis
    # this is messy -- should be re-worked mvoing forward...
    if glbl.methods['interface'] == 'vibronic':
        # if normal modes, read frequencies from vibronic interface
        kecoef = 0.5 * glbl.modules['interface'].ham.freq
    else:
        kecoef = 0.5 / glbl.properties['crd_masses']
    glbl.modules['integrals'] = integral.Integral(
        kecoef, glbl.methods['ansatz'], glbl.methods['integral_eval'])

    # now load the initial trajectories into the bundle
    if glbl.properties['restart']:

        # print to logfile that we're restarting simulation
        log.print_message('string', [
            ' restarting simulation from checkpoint file: ' +
            str(glbl.paths['chkpt_file']) + '\n'
        ])

        # retrieve current wave function, no arguments defaults to most recent simulation
        wfn0 = None
        ints0 = None
        if glbl.mpi['rank'] == 0:
            [glbl.modules['wfn'],
             glbl.modules['integrals']] = checkpoint.retrieve_simulation(
                 time=glbl.properties['restart_time'])
            [wfn0, ints0] = checkpoint.retrieve_simulation(time=0.)

        # only root reads checkpoint file -- then broadcasts contents to other proceses
        if glbl.mpi['parallel']:
            # synchronize tasks
            glbl.mpi['comm'].barrier()
            glbl.modules['wfn'] = glbl.mpi['comm'].bcast(glbl.modules['wfn'],
                                                         root=0)
            wfn0 = glbl.mpi['comm'].bcast(wfn0, root=0)
            if glbl.modules['integrals'].require_centroids:
                glbl.modules['integrals'].centroid_required = glbl.mpi[
                    'comm'].bcast(glbl.modules['integrals'].centroid_required,
                                  root=0)
                glbl.modules['integrals'].centroids = glbl.mpi['comm'].bcast(
                    glbl.modules['integrals'].centroids, root=0)

        # save copy of t=0 wfn for autocorrelation function
        save_initial_wavefunction(wfn0)

        # check that we have all the data we need to propagate the first step -- otherwise, we
        # need to update the potential
        update_surface = False
        for i in range(glbl.modules['wfn'].n_traj()):
            if glbl.modules['wfn'].traj[i].alive and glbl.modules['wfn'].traj[
                    i].active:
                pes_data = glbl.modules['wfn'].traj[i].pes
                if ('potential' not in pes_data.avail_data()
                        or 'derivative' not in pes_data.avail_data()):
                    update_suface = True
            if glbl.modules['integrals'].require_centroids:
                for j in range(i):
                    pes_data = glbl.modules['integrals'].centroids[i][j].pes
                    if glbl.modules['integrals'].centroid_required[i][j]:
                        if ('potential' not in pes_data.avail_data()
                                and glbl.modules['wfn'].traj[i].state
                                == glbl.modules['wfn'].traj[j].state):
                            update_surface = True
                        if ('derivative' not in pes_data.avail_data()
                                and glbl.modules['wfn'].traj[i].state !=
                                glbl.modules['wfn'].traj[j].state):
                            update_surface = True
        if update_surface:
            evaluate.update_pes(glbl.modules['wfn'])
            # update the couplings for all the trajectories
            for i in range(glbl.modules['wfn'].n_traj()):
                glbl.modules['interface'].evaluate_coupling(
                    glbl.modules['wfn'].traj[i])

        # build the necessary matrices
        glbl.modules['matrices'].build(glbl.modules['wfn'],
                                       glbl.modules['integrals'])
        glbl.modules['wfn'].update_matrices(glbl.modules['matrices'])

    else:
        # first generate the initial nuclear coordinates and momenta
        # and add the resulting trajectories to the bundle
        glbl.modules['init_conds'].set_initial_coords(glbl.modules['wfn'])

        # set the initial state of the trajectories in bundle. This may
        # require evaluation of electronic structure
        set_initial_state(glbl.modules['wfn'])

        # set the initial amplitudes of the basis functions
        set_initial_amplitudes(glbl.modules['wfn'])

        # add virtual basis functions, if desired
        if glbl.properties['virtual_basis']:
            virtual_basis(glbl.modules['wfn'])

        # update the integrals
        glbl.modules['integrals'].update(glbl.modules['wfn'])

        # once phase space position and states of the basis functions
        # are set, update the potential information
        evaluate.update_pes(glbl.modules['wfn'])

        # update the couplings for all the trajectories
        for i in range(glbl.modules['wfn'].n_traj()):
            glbl.modules['interface'].evaluate_coupling(
                glbl.modules['wfn'].traj[i])

        # compute the hamiltonian matrix...
        glbl.modules['matrices'].build(glbl.modules['wfn'],
                                       glbl.modules['integrals'])
        glbl.modules['wfn'].update_matrices(glbl.modules['matrices'])

        # so that we may appropriately renormalize to unity
        glbl.modules['wfn'].renormalize()

        # this is the bundle at time t=0.  Save in order to compute auto
        # correlation function
        save_initial_wavefunction(glbl.modules['wfn'])

    # write the wavefunction to the archive
    if glbl.mpi['rank'] == 0:
        checkpoint.archive_simulation(glbl.modules['wfn'],
                                      glbl.modules['integrals'])

    log.print_message(
        'string', ['\n ***********\n' + ' propagation\n' + ' ***********\n\n'])

    log.print_message('t_step', [
        glbl.modules['wfn'].time, glbl.properties['default_time_step'],
        glbl.modules['wfn'].nalive
    ])

    return glbl.modules['wfn'].time