class SimulationLoopHagedorn(SimulationLoop):
    r"""
    This class acts as the main simulation loop. It owns a propagator that
    propagates a set of initial values during a time evolution. All values are
    read from the ``Parameters.py`` file.
    """

    def __init__(self, parameters):
        r"""
        Create a new simulation loop instance.
        """
        # Keep a reference to the simulation parameters
        self.parameters = parameters

        #: The time propagator instance driving the simulation.
        self.propagator = None

        #: A ``IOManager`` instance for saving simulation results.
        self.IOManager = None

        #: The number of time steps we will perform.
        self.nsteps = parameters["nsteps"]

        # Set up serializing of simulation data
        self.IOManager = IOManager()
        self.IOManager.create_file(self.parameters)
        self.IOManager.create_block()

    def prepare_simulation(self):
        r"""
        Set up a Hagedorn propagator for the simulation loop. Set the
        potential and initial values according to the configuration.

        :raise ValueError: For invalid or missing input data.
        """
        potential = PF().create_potential(self.parameters)
        N = potential.get_number_components()

        # Check for enough initial values
        if self.parameters["leading_component"] > N:
            raise ValueError("Leading component index out of range.")

        if len(self.parameters["parameters"]) < N:
            raise ValueError("Too few initial states given. Parameters are missing.")

        if len(self.parameters["coefficients"]) < N:
            raise ValueError("Too few initial states given. Coefficients are missing.")

        # Create a suitable wave packet
        packet = HagedornWavepacket(self.parameters)

        # See if we have a list of parameter tuples or just a single 5-tuple
        # This is for compatibility with the inhomogeneous case.
        try:
            # We have a list of parameter tuples, take the one given by the leading component
            len(self.parameters["parameters"][0])
            parameters = self.parameters["parameters"][self.parameters["leading_component"]]
        except TypeError:
            # We have just a single 5-tuple of parameters, take it.
            parameters = self.parameters["parameters"]

        # Set the Hagedorn parameters
        packet.set_parameters(parameters)
        packet.set_quadrature(None)

        # Set the initial values
        for component, data in enumerate(self.parameters["coefficients"]):
            for index, value in data:
                packet.set_coefficient(component, index, value)

        # Project the initial values to the canonical basis
        packet.project_to_canonical(potential)

        # Finally create and initialize the propagator instace
        self.propagator = HagedornPropagator(potential, packet, self.parameters["leading_component"], self.parameters)

        # Which data do we want to save
        tm = self.parameters.get_timemanager()
        slots = tm.compute_number_saves()

        self.IOManager.add_grid(self.parameters, blockid="global")
        self.IOManager.add_wavepacket(self.parameters, timeslots=slots)

        # Write some initial values to disk
        nodes = self.parameters["f"] * sp.pi * sp.arange(-1, 1, 2.0 / self.parameters["ngn"], dtype=np.complexfloating)
        self.IOManager.save_grid(nodes, blockid="global")
        self.IOManager.save_wavepacket_parameters(self.propagator.get_wavepackets().get_parameters(), timestep=0)
        self.IOManager.save_wavepacket_coefficients(self.propagator.get_wavepackets().get_coefficients(), timestep=0)

    def run_simulation(self):
        r"""
        Run the simulation loop for a number of time steps. The number of steps is calculated in the ``initialize`` function.
        """
        tm = self.parameters.get_timemanager()

        # Run the simulation for a given number of timesteps
        for i in xrange(1, self.nsteps + 1):
            print(" doing timestep " + str(i))

            self.propagator.propagate()

            # Save some simulation data
            if tm.must_save(i):
                self.IOManager.save_wavepacket_parameters(
                    self.propagator.get_wavepackets().get_parameters(), timestep=i
                )
                self.IOManager.save_wavepacket_coefficients(
                    self.propagator.get_wavepackets().get_coefficients(), timestep=i
                )

    def end_simulation(self):
        r"""
        Do the necessary cleanup after a simulation. For example request the
        IOManager to write the data and close the output files.
        """
        self.IOManager.finalize()
class SimulationLoopFourier(SimulationLoop):
    """This class acts as the main simulation loop. It owns a propagator that
    propagates a set of initial values during a time evolution.
    """

    def __init__(self, parameters):
        """Create a new simulation loop instance for a simulation
        using the Fourier propagation method.

        :param parameters: The simulation parameters.
        :type parameters: A :py:class:`ParameterProvider` instance.
        """
        # Keep a reference to the simulation parameters
        self.parameters = parameters

        # The time propagator instance driving the simulation.
        self.propagator = None

        # An `IOManager` instance for saving simulation results.
        self.IOManager = None

        # Which data do we want to save
        self._tm = self.parameters.get_timemanager()

        # Set up serialization of simulation data
        self.IOManager = IOManager()
        self.IOManager.create_file()
        self.IOManager.create_block()

        # Save the simulation parameters
        self.IOManager.add_parameters()
        self.IOManager.save_parameters(parameters)


    def prepare_simulation(self):
        r"""Set up a Fourier propagator for the simulation loop. Set the
        potential and initial values according to the configuration.

        :raise: :py:class:`ValueError` For invalid or missing input data.
        """
        # The potential instance
        potential = BlockFactory().create_potential(self.parameters)

        # Compute the position space grid points
        grid = BlockFactory().create_grid(self.parameters)

        # Construct initial values
        I = Initializer(self.parameters)
        initialvalues = I.initialize_for_fourier(grid)

        # Transform the initial values to the canonical basis
        BT = BasisTransformationWF(potential)
        BT.set_grid(grid)
        BT.transform_to_canonical(initialvalues)

        # Finally create and initialize the propagator instance
        self.propagator = FourierPropagator(potential, initialvalues, self.parameters)

        # Write some initial values to disk
        slots = self._tm.compute_number_saves()

        self.IOManager.add_grid(self.parameters, blockid="global")
        self.IOManager.add_fourieroperators(self.parameters)
        self.IOManager.add_wavefunction(self.parameters, timeslots=slots)

        self.IOManager.save_grid(grid.get_nodes(flat=True), blockid="global")
        self.IOManager.save_fourieroperators(self.propagator.get_operators())
        self.IOManager.save_wavefunction(initialvalues.get_values(), timestep=0)


    def run_simulation(self):
        r"""Run the simulation loop for a number of time steps.
        """
        # The number of time steps we will perform.
        nsteps = self._tm.compute_number_timesteps()

        # Run the prepropagate step
        self.propagator.pre_propagate()
        # Note: We do not save any data here

        # Run the simulation for a given number of timesteps
        for i in xrange(1, nsteps+1):
            print(" doing timestep "+str(i))

            self.propagator.propagate()

            # Save some simulation data
            if self._tm.must_save(i):
                # Run the postpropagate step
                self.propagator.post_propagate()
                self.IOManager.save_wavefunction(self.propagator.get_wavefunction().get_values(), timestep=i)
                # Run the prepropagate step
                self.propagator.pre_propagate()

        # Run the postpropagate step
        self.propagator.post_propagate()
        # Note: We do not save any data here


    def end_simulation(self):
        """Do the necessary cleanup after a simulation. For example request the
        :py:class:`IOManager` to write the data and close the output files.
        """
        self.IOManager.finalize()
class SimulationLoopHagedornInhomogeneous(SimulationLoop):
    r"""This class acts as the main simulation loop. It owns a propagator that
    propagates a set of initial values during a time evolution.
    """

    def __init__(self, parameters):
        r"""Create a new simulation loop instance for a simulation
        using the semiclassical Hagedorn wavepacket based propagation
        method.

        :param parameters: The simulation parameters.
        :type parameters: A :py:class:`ParameterProvider` instance.
        """
        # Keep a reference to the simulation parameters
        self.parameters = parameters

        # The time propagator instance driving the simulation.
        self.propagator = None

        # A `IOManager` instance for saving simulation results.
        self.IOManager = None

        # The time manager
        self._tm = TimeManager(self.parameters)

        # Set up serialization of simulation data
        self.IOManager = IOManager()
        self.IOManager.create_file()

        # Save the simulation parameters
        self.IOManager.add_parameters()
        self.IOManager.save_parameters(parameters)


    def prepare_simulation(self):
        r"""Set up a Hagedorn propagator for the simulation loop. Set the
        potential and initial values according to the configuration.

        :raise: :py:class:`ValueError` For invalid or missing input data.
        """
        # The potential instance
        potential = BlockFactory().create_potential(self.parameters)

        # Project the initial values to the canonical basis
        BT = BasisTransformationHAWP(potential)

        # Finally create and initialize the propagator instance
        # TODO: Attach the "leading_component to the hawp as codata
        self.propagator = HagedornPropagatorInhomogeneous(self.parameters, potential)

        # Create suitable wavepackets
        for packet_descr in self.parameters["initvals"]:
            packet = BlockFactory().create_wavepacket(packet_descr)
            # Transform to canonical basis
            BT.set_matrix_builder(packet.get_innerproduct())
            BT.transform_to_canonical(packet)
            # And hand over
            self.propagator.add_wavepacket((packet,))

        # Add storage for each packet
        npackets = len(self.parameters["initvals"])
        slots = self._tm.compute_number_saves()
        key = ("q","p","Q","P","S","adQ")

        for i in xrange(npackets):
            bid = self.IOManager.create_block()
            self.IOManager.add_inhomogwavepacket(self.parameters, timeslots=slots, blockid=bid, key=key)

        # Write some initial values to disk
        for packet in self.propagator.get_wavepackets():
            self.IOManager.save_inhomogwavepacket_description(packet.get_description())
            # Pi
            self.IOManager.save_inhomogwavepacket_parameters(packet.get_parameters(key=key), timestep=0, key=key)
            # Basis shapes
            for shape in packet.get_basis_shapes():
                self.IOManager.save_inhomogwavepacket_basisshapes(shape)
            # Coefficients
            self.IOManager.save_inhomogwavepacket_coefficients(packet.get_coefficients(), packet.get_basis_shapes(), timestep=0)


    def run_simulation(self):
        r"""Run the simulation loop for a number of time steps.
        """
        # The number of time steps we will perform.
        nsteps = self._tm.compute_number_timesteps()

        # Which parameter data to save.
        key = ("q","p","Q","P","S","adQ")

        # Run the prepropagate step
        self.propagator.pre_propagate()
        # Note: We do not save any data here

        # Run the simulation for a given number of timesteps
        for i in xrange(1, nsteps+1):
            print(" doing timestep "+str(i))

            self.propagator.propagate()

            # Save some simulation data
            if self._tm.must_save(i):
                # Run the postpropagate step
                self.propagator.post_propagate()

                # TODO: Generalize for arbitrary number of wavepackets
                packets = self.propagator.get_wavepackets()
                assert len(packets) == 1

                for packet in packets:
                    # Pi
                    self.IOManager.save_inhomogwavepacket_parameters(packet.get_parameters(key=key), timestep=i, key=key)
                    # Basis shapes (in case they changed!)
                    for shape in packet.get_basis_shapes():
                        self.IOManager.save_inhomogwavepacket_basisshapes(shape)
                    # Coefficients
                    self.IOManager.save_inhomogwavepacket_coefficients(packet.get_coefficients(), packet.get_basis_shapes(), timestep=i)

                # Run the prepropagate step
                self.propagator.pre_propagate()

        # Run the postpropagate step
        self.propagator.post_propagate()
        # Note: We do not save any data here


    def end_simulation(self):
        r"""Do the necessary cleanup after a simulation. For example request the
        :py:class:`IOManager` to write the data and close the output files.
        """
        self.IOManager.finalize()
Exemple #4
0
class SimulationLoopHagedornInhomogeneous(SimulationLoop):
    r"""This class acts as the main simulation loop. It owns a propagator that
    propagates a set of initial values during a time evolution.
    """
    def __init__(self, parameters):
        r"""Create a new simulation loop instance for a simulation
        using the semiclassical Hagedorn wavepacket based propagation
        method.

        :param parameters: The simulation parameters.
        :type parameters: A :py:class:`ParameterProvider` instance.
        """
        # Keep a reference to the simulation parameters
        self.parameters = parameters

        # The time propagator instance driving the simulation.
        self.propagator = None

        # A `IOManager` instance for saving simulation results.
        self.IOManager = None

        # The time manager
        self._tm = TimeManager(self.parameters)

        # Set up serialization of simulation data
        self.IOManager = IOManager()
        self.IOManager.create_file(self.parameters)

    def prepare_simulation(self):
        r"""Set up a Hagedorn propagator for the simulation loop. Set the
        potential and initial values according to the configuration.

        :raise ValueError: For invalid or missing input data.
        """
        # The potential instance
        potential = BlockFactory().create_potential(self.parameters)

        # Project the initial values to the canonical basis
        BT = BasisTransformationHAWP(potential)

        # Finally create and initialize the propagator instace
        # TODO: Attach the "leading_component to the hawp as codata
        self.propagator = HagedornPropagatorInhomogeneous(
            self.parameters, potential)

        # Create suitable wavepackets
        for packet_descr in self.parameters["initvals"]:
            packet = BlockFactory().create_wavepacket(packet_descr)
            # Transform to canonical basis
            BT.set_matrix_builder(packet.get_quadrature())
            BT.transform_to_canonical(packet)
            # And hand over
            self.propagator.add_wavepacket((packet, ))

        # Add storage for each packet
        npackets = len(self.parameters["initvals"])
        slots = self._tm.compute_number_saves()

        for i in xrange(npackets):
            bid = self.IOManager.create_block()
            self.IOManager.add_inhomogwavepacket(self.parameters,
                                                 timeslots=slots,
                                                 blockid=bid)

        # Write some initial values to disk
        for packet in self.propagator.get_wavepackets():
            self.IOManager.save_inhomogwavepacket_description(
                packet.get_description())
            # Pi
            self.IOManager.save_inhomogwavepacket_parameters(
                packet.get_parameters(), timestep=0)
            # Basis shapes
            for shape in packet.get_basis_shape():
                self.IOManager.save_inhomogwavepacket_basisshapes(shape)
            # Coefficients
            self.IOManager.save_inhomogwavepacket_coefficients(
                packet.get_coefficients(),
                packet.get_basis_shape(),
                timestep=0)

    def run_simulation(self):
        r"""Run the simulation loop for a number of time steps.
        """
        # The number of time steps we will perform.
        nsteps = self._tm.compute_number_timesteps()

        # Run the simulation for a given number of timesteps
        for i in xrange(1, nsteps + 1):
            print(" doing timestep " + str(i))

            self.propagator.propagate()

            # Save some simulation data
            if self._tm.must_save(i):
                # TODO: Generalize for arbitrary number of wavepackets
                packets = self.propagator.get_wavepackets()
                assert len(packets) == 1

                for packet in packets:
                    # Pi
                    self.IOManager.save_inhomogwavepacket_parameters(
                        packet.get_parameters(), timestep=i)
                    # Basis shapes (in case they changed!)
                    for shape in packet.get_basis_shape():
                        self.IOManager.save_inhomogwavepacket_basisshapes(
                            shape)
                    # Coefficients
                    self.IOManager.save_inhomogwavepacket_coefficients(
                        packet.get_coefficients(),
                        packet.get_basis_shape(),
                        timestep=i)

    def end_simulation(self):
        r"""Do the necessary cleanup after a simulation. For example request the
        :py:class:`IOManager` to write the data and close the output files.
        """
        self.IOManager.finalize()
Exemple #5
0
class SimulationLoopFourier(SimulationLoop):
    """This class acts as the main simulation loop. It owns a propagator that
    propagates a set of initial values during a time evolution.
    """
    def __init__(self, parameters):
        """Create a new simulation loop instance for a simulation
        using the Fourier propagation method.

        :param parameters: The simulation parameters.
        :type parameters: A :py:class:`ParameterProvider` instance.
        """
        # Keep a reference to the simulation parameters
        self.parameters = parameters

        # The time propagator instance driving the simulation.
        self.propagator = None

        # An `IOManager` instance for saving simulation results.
        self.IOManager = None

        # Which data do we want to save
        self._tm = self.parameters.get_timemanager()

        # Set up serialization of simulation data
        self.IOManager = IOManager()
        self.IOManager.create_file(self.parameters)
        self.IOManager.create_block()

    def prepare_simulation(self):
        r"""Set up a Fourier propagator for the simulation loop. Set the
        potential and initial values according to the configuration.

        :raise ValueError: For invalid or missing input data.
        """
        # The potential instance
        potential = BlockFactory().create_potential(self.parameters)

        # Compute the position space grid points
        grid = BlockFactory().create_grid(self.parameters)

        # Construct initial values
        I = Initializer(self.parameters)
        initialvalues = I.initialize_for_fourier(grid)

        # Transform the initial values to the canonical basis
        BT = BasisTransformationWF(potential)
        BT.set_grid(grid)
        BT.transform_to_canonical(initialvalues)

        # Finally create and initialize the propagator instace
        self.propagator = FourierPropagator(potential, initialvalues,
                                            self.parameters)

        # Write some initial values to disk
        slots = self._tm.compute_number_saves()

        self.IOManager.add_grid(self.parameters, blockid="global")
        self.IOManager.add_fourieroperators(self.parameters)
        self.IOManager.add_wavefunction(self.parameters, timeslots=slots)

        self.IOManager.save_grid(grid.get_nodes(flat=False), blockid="global")
        self.IOManager.save_fourieroperators(self.propagator.get_operators())
        self.IOManager.save_wavefunction(initialvalues.get_values(),
                                         timestep=0)

    def run_simulation(self):
        r"""Run the simulation loop for a number of time steps.
        """
        # The number of time steps we will perform.
        nsteps = self._tm.compute_number_timesteps()

        # Run the simulation for a given number of timesteps
        for i in xrange(1, nsteps + 1):
            print(" doing timestep " + str(i))

            self.propagator.propagate()

            # Save some simulation data
            if self._tm.must_save(i):
                self.IOManager.save_wavefunction(
                    self.propagator.get_wavefunction().get_values(),
                    timestep=i)

    def end_simulation(self):
        """Do the necessary cleanup after a simulation. For example request the
        :py:class:`IOManager` to write the data and close the output files.
        """
        self.IOManager.finalize()
class SimulationLoopFourier(SimulationLoop):
    r"""
    This class acts as the main simulation loop. It owns a propagator that
    propagates a set of initial values during a time evolution. All values are
    read from the ``Parameters.py`` file.
    """

    def __init__(self, parameters):
        r"""
        Create a new simulation loop instance.
        """
        # Keep a reference to the simulation parameters
        self.parameters = parameters

        #: The time propagator instance driving the simulation.
        self.propagator = None

        #: A ``IOManager`` instance for saving simulation results.
        self.IOManager = None

        #: The number of time steps we will perform.
        self.nsteps = parameters["nsteps"]

        # Set up serializing of simulation data
        self.IOManager = IOManager()
        self.IOManager.create_file(self.parameters)
        self.IOManager.create_block()


    def prepare_simulation(self):
        r"""
        Set up a Fourier propagator for the simulation loop. Set the
        potential and initial values according to the configuration.

        :raise ValueError: For invalid or missing input data.
        """
        # Compute the position space grid points
        nodes = self.parameters["f"] * sp.pi * sp.arange(-1, 1, 2.0/self.parameters["ngn"], dtype=np.complexfloating)

        # The potential instance
        potential = PF().create_potential(self.parameters)

        # Check for enough initial values
        if not self.parameters.has_key("initial_values"):
            if len(self.parameters["parameters"]) < potential.get_number_components():
                raise ValueError("Too few initial states given. Parameters are missing.")

            if len(self.parameters["coefficients"]) < potential.get_number_components():
                raise ValueError("Too few initial states given. Coefficients are missing.")

        # Calculate the initial values sampled from a hagedorn wave packet
        d = dict([("ncomponents", 1), ("basis_size", self.parameters["basis_size"]), ("eps", self.parameters["eps"])])

        # Initial values given in the "fourier" specific format
        if self.parameters.has_key("initial_values"):
            initialvalues = [ np.zeros(nodes.shape, dtype=np.complexfloating) for i in xrange(self.parameters["ncomponents"]) ]

            for level, params, coeffs in self.parameters["initial_values"]:
                hwp = HagedornWavepacket(d)
                hwp.set_parameters(params)

                for index, value in coeffs:
                    hwp.set_coefficient(0, index, value)

                iv = hwp.evaluate_at(nodes, component=0, prefactor=True)

                initialvalues[level] = initialvalues[level] + iv

        # Initial value read in compatibility mode to the packet algorithms
        else:
            # See if we have a list of parameter tuples or just a single 5-tuple
            # This is for compatibility with the inhomogeneous case.
            try:
                # We have a list of parameter tuples this is ok for the loop below
                len(self.parameters["parameters"][0])
                parameters = self.parameters["parameters"]
            except TypeError:
                # We have just a single 5-tuple of parameters, we need to replicate for looping
                parameters = [ self.parameters["parameters"] for i in xrange(self.parameters["ncomponents"]) ]

            initialvalues = []

            for level, item in enumerate(parameters):
                hwp = HagedornWavepacket(d)
                hwp.set_parameters(item)

                # Set the coefficients of the basis functions
                for index, value in self.parameters["coefficients"][level]:
                    hwp.set_coefficient(0, index, value)

                iv = hwp.evaluate_at(nodes, component=0, prefactor=True)

                initialvalues.append(iv)

        # Project the initial values to the canonical basis
        initialvalues = potential.project_to_canonical(nodes, initialvalues)

        # Store the initial values in a WaveFunction object
        IV = WaveFunction(self.parameters)
        IV.set_grid(nodes)
        IV.set_values(initialvalues)

        # Finally create and initialize the propagator instace
        self.propagator = FourierPropagator(potential, IV, self.parameters)

        # Which data do we want to save
        tm = self.parameters.get_timemanager()
        slots = tm.compute_number_saves()

        print(tm)

        self.IOManager.add_grid(self.parameters, blockid="global")
        self.IOManager.add_fourieroperators(self.parameters)
        self.IOManager.add_wavefunction(self.parameters, timeslots=slots)

        # Write some initial values to disk
        self.IOManager.save_grid(nodes, blockid="global")
        self.IOManager.save_fourieroperators(self.propagator.get_operators())
        self.IOManager.save_wavefunction(IV.get_values(), timestep=0)


    def run_simulation(self):
        r"""
        Run the simulation loop for a number of time steps. The number of steps is calculated in the ``initialize`` function.
        """
        tm = self.parameters.get_timemanager()

        # Run the simulation for a given number of timesteps
        for i in xrange(1, self.nsteps+1):
            print(" doing timestep "+str(i))

            self.propagator.propagate()

            # Save some simulation data
            if tm.must_save(i):
                self.IOManager.save_wavefunction(self.propagator.get_wavefunction().get_values(), timestep=i)


    def end_simulation(self):
        r"""
        Do the necessary cleanup after a simulation. For example request the
        IOManager to write the data and close the output files.
        """
        self.IOManager.finalize()
class SimulationLoopSpawnAdiabatic(SimulationLoop):
    r"""
    This class acts as the main simulation loop. It owns a propagator that
    propagates a set of initial values during a time evolution. All values are
    read from the ``Parameters.py`` file.
    """

    def __init__(self, parameters):
        r"""
        Create a new simulation loop instance.
        """
        # Keep a reference to the simulation parameters
        self.parameters = parameters

        self.tm = TimeManager(parameters)

        #: The time propagator instance driving the simulation.
        self.propagator = None

        #: A ``IOManager`` instance for saving simulation results.
        self.iom = IOManager()
        self.iom.create_file(parameters)
        self.gid = self.iom.create_group()


    def prepare_simulation(self):
        r"""
        Set up a Spawning propagator for the simulation loop. Set the
        potential and initial values according to the configuration.

        :raise ValueError: For invalid or missing input data.
        """
        potential = PotentialFactory().create_potential(self.parameters)
        N = potential.get_number_components()

        # Check for enough initial values
        if self.parameters["leading_component"] > N:
            raise ValueError("Leading component index out of range.")

        if len(self.parameters["parameters"]) < N:
            raise ValueError("Too few initial states given. Parameters are missing.")

        if len(self.parameters["coefficients"]) < N:
            raise ValueError("Too few initial states given. Coefficients are missing.")

        # Create a suitable wave packet
        packet = HagedornWavepacket(self.parameters)
        packet.set_parameters(self.parameters["parameters"][self.parameters["leading_component"]])
        packet.set_quadrature(None)

        # Set the initial values
        for component, data in enumerate(self.parameters["coefficients"]):
            for index, value in data:
                packet.set_coefficient(component, index, value)

        # Project the initial values to the canonical basis
        packet.project_to_canonical(potential)

        # Finally create and initialize the propagator instace
        inner = HagedornPropagator(potential, packet, self.parameters["leading_component"], self.parameters)
        self.propagator = SpawnAdiabaticPropagator(inner, potential, packet, self.parameters["leading_component"], self.parameters)

        # Write some initial values to disk
        slots = self.tm.compute_number_saves()
        for packet in self.propagator.get_wavepackets():
            bid = self.iom.create_block(groupid=self.gid)
            self.iom.add_wavepacket(self.parameters, timeslots=slots, blockid=bid)
            self.iom.save_wavepacket_coefficients(packet.get_coefficients(), blockid=bid, timestep=0)
            self.iom.save_wavepacket_parameters(packet.get_parameters(), blockid=bid, timestep=0)


    def run_simulation(self):
        r"""
        Run the simulation loop for a number of time steps. The number of steps is calculated in the ``initialize`` function.
        """
        tm = self.tm

        # Run the simulation for a given number of timesteps
        for i in xrange(1, tm.get_nsteps()+1):
            print(" doing timestep "+str(i))

            self.propagator.propagate(tm.compute_time(i))

            # Save some simulation data
            if tm.must_save(i):
                # Check if we need more data blocks for newly spawned packets
                while self.iom.get_number_blocks(groupid=self.gid) < self.propagator.get_number_packets():
                    bid = self.iom.create_block(groupid=self.gid)
                    self.iom.add_wavepacket(self.parameters, blockid=bid)

                for index, packet in enumerate(self.propagator.get_wavepackets()):
                    self.iom.save_wavepacket_coefficients(packet.get_coefficients(), timestep=i, blockid=index)
                    self.iom.save_wavepacket_parameters(packet.get_parameters(), timestep=i, blockid=index)


    def end_simulation(self):
        r"""
        Do the necessary cleanup after a simulation. For example request the
        IOManager to write the data and close the output files.
        """
        self.iom.finalize()