Пример #1
0
def numpy2DArrayToStdVectorStdVectorDouble(array):
    """
    Convert a 2D numpy array to a std::vector<std::vector<double> > representation.

    :param array: The array to convert.

    :returns: A corresponding std::vector<std::vector<double> > object.
    """
    # Get the shape of the array.
    shape = numpy.shape(array)
    nI = shape[0]
    nJ = shape[1]

    # Setup the c++ object.
    cpp_2D_vector = Backend.StdVectorStdVectorDouble(nI)

    # Copy the values over.
    for i in range(nI):
        cpp_vector = Backend.StdVectorDouble(nJ)
        for j in range(nJ):
            cpp_vector[j] = array[i][j]
        cpp_2D_vector[i] = cpp_vector

    # Done.
    return cpp_2D_vector
 def _map(self):
     """
     Query for the lattice map.
     """
     # Generate the lattice map if not done allready.
     if self.__lattice_map is None:
         self.__lattice_map = Backend.LatticeMap(
             len(self.__unit_cell.basis()),
             Backend.StdVectorInt(self.__repetitions),
             Backend.StdVectorBool(self.__periodic))
     # Return the lattice map.
     return self.__lattice_map
Пример #3
0
    def setup(self, step, time, configuration):
        """
        Recieves the setup call from the before the MC loop.

        :param step: The step number of the simulation.
        :type step: int

        :param time: The time of the simulation.
        :type time: float

        :param configuration: The configuration of the simulation.
        :type configuration: KMCConfiguration
        """
        # Make sure the track type is one of the possible types.
        if not self.__track_type in configuration.possibleTypes():
            raise Error("The track type of the MSD calculator is not one of the valid types of the configuration.")

        # Get the cell vectors out.
        abc_to_xyz = numpy.array(configuration.lattice().unitCell().cellVectors()).transpose()
        abc_to_xyz_cpp = numpy2DArrayToStdVectorCoordinate(abc_to_xyz)

        # Setup the backend.
        self.__backend = Backend.OnTheFlyMSD(configuration._backend(),
                                             self.__history_steps,
                                             self.__n_bins,
                                             self.__t_max,
                                             time,
                                             self.__track_type,
                                             abc_to_xyz_cpp,
                                             self.__blocksize)
Пример #4
0
    def backendRateCallbackBuckets(self,
                                   cpp_coords,
                                   coords_len,
                                   occupations,
                                   update,
                                   types_map,
                                   rate_constant,
                                   process_number,
                                   global_x,
                                   global_y,
                                   global_z):
        """
        Function called from C++ to get the rate. It function recieves
        the data from C++ and parse it to a Python friendly format to send it
        forward to the custom rate function.
        """
        # PERFORMME: move operations to C++.

        # Determine the occupations after the move.
        occupations_after = Backend.StdVectorTypeBucket()

        for i in range(len(update)):
            occupations_after.push_back(occupations[i].add(update[i]))

        # Call and return the custom rate.
        global_coordinate = (global_x, global_y, global_z)
        return self.rate(numpy.array(cpp_coords).reshape(coords_len,3),
                         stdVectorTypeBucketToPython(occupations, types_map),
                         stdVectorTypeBucketToPython(occupations_after, types_map),
                         rate_constant,
                         process_number,
                         global_coordinate)
Пример #5
0
    def _backend(self):
        """
        Query function for the c++ backend object.
        """
        if self.__backend is None:
            # Construct the c++ backend object.
            cpp_types = stringListToStdVectorString(self.__types)
            cpp_coords = numpy2DArrayToStdVectorStdVectorDouble(
                self.__lattice.sites())
            cpp_possible_types = Backend.StdMapStringInt(self.__possible_types)

            # Send in the coordinates and types to construct the backend configuration.
            self.__backend = Backend.Configuration(cpp_coords, cpp_types,
                                                   cpp_possible_types)

        # Return the backend.
        return self.__backend
Пример #6
0
    def testStdVectorBucketTypeToPython(self):
        """ Test the buckets format conversion routine from C++ to Python. """
        empty_py_vector = stdVectorTypeBucketToPython(
            Backend.StdVectorTypeBucket(), Backend.StdVectorString())

        self.assertEqual(empty_py_vector, [])

        # Add a vector with content.
        cpp_map = Backend.StdVectorString(3)
        cpp_map[0] = "*"
        cpp_map[1] = "A"
        cpp_map[2] = "B"

        cpp_vector = Backend.StdVectorTypeBucket(4, Backend.TypeBucket(3))

        # [(1,"A")]
        cpp_vector[0][0] = 0
        cpp_vector[0][1] = 1
        cpp_vector[0][2] = 0

        # []
        cpp_vector[1][0] = 0
        cpp_vector[1][1] = 0
        cpp_vector[1][2] = 0

        # [(3,"A"), (1, "B")]
        cpp_vector[2][0] = 0
        cpp_vector[2][1] = 3
        cpp_vector[2][2] = 1

        # [(4,"A"), (5, "B")]
        cpp_vector[3][0] = 1
        cpp_vector[3][1] = 4
        cpp_vector[3][2] = 5

        # Translate to Python.
        py_vector = stdVectorTypeBucketToPython(cpp_vector, cpp_map)

        ref_py_vector = [[(1, "A")], [], [(3, "A"), (1, "B")],
                         [(4, "A"), (5, "B")]]

        # Check.
        self.assertEqual(py_vector, ref_py_vector)
Пример #7
0
def stringListToStdVectorStdVectorString(string_list):
    """
    Converts a list of strings to a std::vector<std::vector<std::string> >
    object with one elemnt in each of the inner vectors.

    :param string_list: The list of strings.

    :returns: A corresponding std::vector<std::vector<std::string> > object.
    """
    # Get the size.
    size = len(string_list)
    # Setup the c++ object.
    cpp_list = Backend.StdVectorStdVectorString()

    # Copy values over.
    for s in string_list:
        cpp_list.push_back(Backend.StdVectorString(1, s))
    # Done.
    return cpp_list
Пример #8
0
    def testStdVectorCoordinateToNumpy2DArray(self):
        """ Test the conversion from a std::vector<Coordinate> to a numpy array. """
        # Get a reference.
        ref_array = numpy.random.rand(6).reshape((2, 3))

        # Setup the data to convert.
        cpp_vector = Backend.StdVectorCoordinate()
        cpp_vector.push_back(
            Backend.Coordinate(ref_array[0, 0], ref_array[0, 1], ref_array[0,
                                                                           2]))
        cpp_vector.push_back(
            Backend.Coordinate(ref_array[1, 0], ref_array[1, 1], ref_array[1,
                                                                           2]))

        # Convert.
        py_array = stdVectorCoordinateToNumpy2DArray(cpp_vector)

        # Check.
        diff = numpy.linalg.norm(ref_array - py_array)
        self.assertAlmostEqual(diff, 0.0, 10)
Пример #9
0
def numpy2DArrayToStdVectorCoordinate(array):
    """
    Convert a Nx3 2D numpy array to a std::vector<Coordinate> representation.

    :param array: The array to convert.

    :returns: A corresponding std::vector<Coordinate> object.
    """
    # Get the shape of the array.
    shape = numpy.shape(array)
    nI = shape[0]

    # Setup the c++ object.
    cpp_vector = Backend.StdVectorCoordinate()

    # Copy the values over.
    for i in range(nI):
        cpp_vector.push_back(
            Backend.Coordinate(array[i][0], array[i][1], array[i][2]))
    # Done.
    return cpp_vector
Пример #10
0
    def testCppPythonInheritanceDirectors(self):
        """ Test that we can extend a C++ object in Python and use the extensions from C++ """
        # This returns "I am a C++ object".
        cpp_class = SimpleDummyBaseClass()
        self.assertEqual(cpp_class.whoAmI(), "I am a C++ object")

        # This is a dummy prototype.
        class DerrivedClass(SimpleDummyBaseClass):
            def __init__(self):
                # Call the base class constructor.
                SimpleDummyBaseClass.__init__(self)
            def whoAmI(self):
                return "I am extended in Python"

        # Now we get another answer from calling whoAmI()
        py_class = DerrivedClass()
        self.assertEqual(py_class.whoAmI(), "I am extended in Python")

        # Calling from C++ gives the same result.
        self.assertEqual(Backend.callWhoAmI(cpp_class), "I am a C++ object")

        self.assertEqual(Backend.callWhoAmI(py_class), "I am extended in Python")
Пример #11
0
    def _backend(self, possible_types):
        """
        Query for the local configuration backend object.

        :param possible_types: A dict with the global mapping of type strings
                               to integers.

        :returns: The interactions object in C++
        """
        if self.__backend is None:
            # Construct the c++ backend object.
            cpp_types  = Backend.StdVectorString(self.__types)
            cpp_coords = numpy2DArrayToStdVectorStdVectorDouble(self.__coordinates)
            cpp_possible_types = Backend.StdMapStringInt(possible_types)

            # Send in the coordinates and types to construct the backend configuration.
            self.__backend = Backend.Configuration(cpp_coords,
                                                   cpp_types,
                                                   cpp_possible_types)

        # Return the backend.
        return self.__backend
Пример #12
0
    def _backend(self):
        """
        Function for generating the C++ backend reperesentation of this object.

        :returns: The C++ LatticeModel based on the parameters given to this class on construction.
        """
        if self.__backend is None:
            # Setup the C++ objects we need.
            cpp_config = self.__configuration._backend()
            cpp_lattice_map = self.__configuration._latticeMap()
            cpp_interactions = self.__interactions._backend(
                self.__configuration.possibleTypes(), cpp_lattice_map.nBasis(),
                self.__configuration)
            # Construct a timer.
            self.__cpp_timer = Backend.SimulationTimer()

            # Construct the backend object.
            self.__backend = Backend.LatticeModel(cpp_config, self.__cpp_timer,
                                                  cpp_lattice_map,
                                                  cpp_interactions)
        # Return.
        return self.__backend
Пример #13
0
def bucketListToStdVectorStdVectorString(bucket_list):
    """
    Converts a list of the format [[(n,"A"), ...], ...]
    to a std::vector< std::vector<std::string> > representation.

    :param bucket_list: The list to convert.

    :returns: A corresponding std::vector<std::vector<std::string> > object.
    """
    cpp_list = Backend.StdVectorStdVectorString()

    # For each site.
    for ss in bucket_list:
        # For all types at this site.
        site_list = Backend.StdVectorString()
        for s in ss:
            # Add the number of types.
            for i in range(s[0]):
                site_list.push_back(s[1])
        cpp_list.push_back(site_list)

    # Done.
    return cpp_list
Пример #14
0
    def _update(self):
        """
        Query for the update info.

        :returns: The update stored on the class.
        """
        if self.__cpp_update is None:

            # Create the cpp update vector of maps.
            self.__cpp_update = Backend.StdVectorStdMapStringInt()

            # Take each entry in the update list.
            for update_entry in self.__update:

                # Convert to C++
                cpp_map = Backend.StdMapStringInt()
                for e in update_entry:
                    cpp_map[e[1]] = e[0]

                # Add.
                self.__cpp_update.push_back(cpp_map)

        return self.__cpp_update
Пример #15
0
    def testCoordinate(self):
        """ Test the coordinate wrapping. """
        # Test the indexing function access.
        coord = Backend.Coordinate(1.0,2.0,3.0)
        self.assertAlmostEqual(float(coord[0]), 1.0, 10)
        self.assertAlmostEqual(coord[1], 2.0, 10)
        self.assertAlmostEqual(coord[2], 3.0, 10)

        # Test indexing function modification.
        coord[0] = 12.33;
        self.assertAlmostEqual(coord[0], 12.33, 10)
        coord[1] = 1.33;
        self.assertAlmostEqual(coord[1], 1.33, 10)
        coord[2] = 34.33;
        self.assertAlmostEqual(coord[2], 34.33, 10)
Пример #16
0
    def testStdVectorStringToStringList(self):
        """ Test the conversion of a std::vector<std::string> to a string list. """
        # Take a list of strings.
        cpp_list = Backend.StdVectorString()
        ref_list = ["AAA", "BB", "AbC", "def"]
        for s in ref_list:
            cpp_list.push_back(s)

        # Convert.
        py_repr = stdVectorStringToStringList(cpp_list)

        # Check the type.
        self.assertTrue(isinstance(py_repr, list))

        # Check the result.
        self.assertEqual(py_repr, ref_list)
Пример #17
0
    def _backend(self, possible_types, n_basis, configuration):
        """
        Query for the interactions backend object.

        :param possible_types: A dict with the global mapping of type strings
                               to integers.

        :param n_basis: The size of the configuration basis is.
        :type n_basis: int

        :param configuration: The configuration of the systm, to be passed
                              on to any attached custom rate calculator.

        :returns: The interactions object in C++
        """
        if self.__backend is None:

            # Check the possible_types against the types in the processes.
            for process_number, process in enumerate(self.__processes):
                all_elements = process.allPresentTypes()
                if (not all([(e in possible_types) for e in all_elements])):
                    raise Error(
                        "Process %i contains elements not present in the list of possible types of the configuration."
                        % (process_number))

            # Setup the correct type of backend process objects
            # depending on the presence of a rate calculator.

            if self.__rate_calculator_class is not None:

                # Instantiate the rate calculator.
                if self.__builtin_custom == False:
                    rate_calculator = self.__rate_calculator_class(
                        configuration)
                else:
                    rate_calculator = self.__rate_calculator_class(
                        configuration._backend())

                if self.__builtin_custom == False:
                    if not isinstance(rate_calculator,
                                      KMCRateCalculatorPlugin):
                        msg = """
The 'rate_calculator' given to the KMCInteractions class must
inherit from the KMCRateCalculatorPlugin. """
                        raise Error(msg)
                    elif rate_calculator.__class__ == KMCRateCalculatorPlugin(
                            configuration).__class__:
                        msg = """
The 'rate_calculator' given to the KMCInteractions class must
inherit from the KMCRateCalculatorPlugin class. It may not be
the KMCRateCalculatorPlugin class itself. """
                        raise Error(msg)
                # Tests passed. Save the instantiated rate calculator on the class.
                self.__rate_calculator = rate_calculator

                # Generate the process vector.
                cpp_processes = Backend.StdVectorCustomRateProcess()
            else:
                # Generate the process vector.
                cpp_processes = Backend.StdVectorProcess()

            # For each interaction.
            for process_number, process in enumerate(self.__processes):

                # Get the corresponding C++ objects.
                cpp_config1 = process.localConfigurations()[0]._backend(
                    possible_types)

                if len(process.localConfigurations()) == 2:
                    cpp_config2 = process.localConfigurations()[1]._backend(
                        possible_types)
                else:
                    # Take a copy of the first configuration and set the update from the
                    # process.
                    cpp_config2 = cpp_config1
                    cpp_config2.setUpdateInfo(process._update())

                rate_constant = process.rateConstant()

                basis_list = range(n_basis)
                if process.basisSites() is not None:
                    # Make sure this basis list does not contain positions
                    # that are not in the configuration.
                    basis_list = []
                    for b in process.basisSites():
                        if b < n_basis:
                            basis_list.append(b)

                # And construct the C++ entry.
                cpp_basis = Backend.StdVectorInt(basis_list)

                # Setup the move vectors representation in C++.
                move_origins = [int(v[0]) for v in process.moveVectors()]
                cpp_move_origins = Backend.StdVectorInt(move_origins)
                cpp_move_vectors = Backend.StdVectorCoordinate()
                for v in process.moveVectors():
                    cpp_move_vectors.push_back(
                        Backend.Coordinate(v[1][0], v[1][1], v[1][2]))

                # Construct and store the C++ process.
                if self.__rate_calculator is not None:
                    # Set the cutoff correctly.
                    cutoff = self.__rate_calculator.cutoff()
                    if cutoff is None:
                        cutoff = 1.0

                    # Get the cache_rate flag.
                    cache_rate = (self.__rate_calculator.cacheRates() and \
                                      not process_number in self.__rate_calculator.excludeFromCaching())

                    cpp_processes.push_back(
                        Backend.CustomRateProcess(cpp_config1, cpp_config2,
                                                  rate_constant, cpp_basis,
                                                  cutoff, cpp_move_origins,
                                                  cpp_move_vectors,
                                                  process_number, cache_rate))
                else:
                    cpp_processes.push_back(
                        Backend.Process(cpp_config1, cpp_config2,
                                        rate_constant, cpp_basis,
                                        cpp_move_origins, cpp_move_vectors,
                                        process_number))

            # Construct the C++ interactions object.
            if self.__rate_calculator is not None:
                self.__backend = Backend.Interactions(
                    cpp_processes, self.__implicit_wildcards,
                    self.__rate_calculator)
            else:
                self.__backend = Backend.Interactions(
                    cpp_processes, self.__implicit_wildcards)

        # Return the stored backend.
        return self.__backend
Пример #18
0
    def testUsage(self):
        """ Test that the KMCRateCalculatorPlugin can be used in a simulation. """
        # To get the random numbers and process numbers returned.
        ref_randoms = []
        ref_process_numbers = []
        ref_coordinates = []
        # Define a derrived class.
        class RateCalc(KMCRateCalculatorPlugin):
            # Overload the initialize function.
            def initialize(self):
                # Save something on the class here.
                self._times_called = 0
            # Overload the rate function.
            def rate(self, coords, types_befpre, types_after, rate_constant, process_number, coordinate):
                # Do some simple counting and return the random number.
                self._times_called += 1
                rnd = numpy.random.uniform(0.0,1.0)
                ref_randoms.append(rnd)
                ref_process_numbers.append(process_number)
                ref_coordinates.append(coordinate)
                return rnd
            # Overload the additive rate function.
            def useAdditiveRate(self):
                return False

        # Construct.
        calculator = RateCalc("DummyConfig")

        # Send it to C++ to get the rate out, call it 4 times.
        cpp_coords = Backend.StdVectorCoordinate()
        cpp_coords.push_back(Backend.Coordinate(1.0,2.9,3.4))
        cpp_types1 = Backend.StdVectorString()
        cpp_types1.push_back("A")
        cpp_types2 = Backend.StdVectorString()
        cpp_types2.push_back("B")
        rate_constant = 3.1415927
        ret_randoms = []
        process_numbers = [21, 12, 10, 2]
        global_xyz = numpy.array([[0.2,0.4,0.5],
                                  [1.1,1.3,1.4],
                                  [3.4,4.3,3.3],
                                  [4.2,3.2,1.9]])


        ret_randoms.append(Backend.getRate(calculator,
                                           cpp_coords,
                                           cpp_types1,
                                           cpp_types2,
                                           rate_constant,
                                           process_numbers[0],
                                           global_xyz[0,0],
                                           global_xyz[0,1],
                                           global_xyz[0,2]))
        ret_randoms.append(Backend.getRate(calculator,
                                           cpp_coords,
                                           cpp_types1,
                                           cpp_types2,
                                           rate_constant,
                                           process_numbers[1],
                                           global_xyz[1,0],
                                           global_xyz[1,1],
                                           global_xyz[1,2]))
        ret_randoms.append(Backend.getRate(calculator,
                                           cpp_coords,
                                           cpp_types1,
                                           cpp_types2,
                                           rate_constant,
                                           process_numbers[2],
                                           global_xyz[2,0],
                                           global_xyz[2,1],
                                           global_xyz[2,2]))
        ret_randoms.append(Backend.getRate(calculator,
                                           cpp_coords,
                                           cpp_types1,
                                           cpp_types2,
                                           rate_constant,
                                           process_numbers[3],
                                           global_xyz[3,0],
                                           global_xyz[3,1],
                                           global_xyz[3,2]))

        # Check that it was called 4 times.
        self.assertEqual( calculator._times_called, 4 )

        # Check the values.
        self.assertAlmostEqual( ret_randoms, ref_randoms, 12 )

        # Check the reference process numbers.
        self.assertEqual( ref_process_numbers[0], 21 )
        self.assertEqual( ref_process_numbers[1], 12 )
        self.assertEqual( ref_process_numbers[2], 10 )
        self.assertEqual( ref_process_numbers[3], 2  )

        # Check the reference coordinate.
        self.assertAlmostEqual( numpy.linalg.norm( global_xyz - numpy.array(ref_coordinates)), 0.0, 10 )
    def _backend(self, possible_types, n_basis):
        """
        Query for the interactions backend object.

        :param possible_types: A dict with the global mapping of type strings
                               to integers.

        :param n_basis: The size of the configuration basis is.
        :type n_basis: int

        :returns: The interactions object in C++
        """
        if self.__backend is None:

            # Check the possible_types against the types in the processes.
            for process_number, process in enumerate(self.__processes):
                all_elements = list(
                    set(process.elementsBefore() + process.elementsAfter()))
                if (not all([(e in possible_types) for e in all_elements])):
                    raise Error(
                        "Process %i contains elements not present in the list of possible types of the configuration."
                        % (process_number))

            # Setup the correct type of backend process objects
            # depending on the presence of a rate calculator.

            if self.__rate_calculator is not None:
                cpp_processes = Backend.StdVectorCustomRateProcess()
            else:
                cpp_processes = Backend.StdVectorProcess()

            # For each interaction.
            for process_number, process in enumerate(self.__processes):

                # Get the corresponding C++ objects.
                cpp_config1 = process.localConfigurations()[0]._backend(
                    possible_types)
                cpp_config2 = process.localConfigurations()[1]._backend(
                    possible_types)
                rate_constant = process.rateConstant()

                basis_list = range(n_basis)
                if process.basisSites() is not None:
                    # Make sure this basis list does not contain positions
                    # that are not in the configuration.
                    basis_list = []
                    for b in process.basisSites():
                        if b < n_basis:
                            basis_list.append(b)

                # And construct the C++ entry.
                cpp_basis = Backend.StdVectorInt(basis_list)

                # Setup the move vectors representation in C++.
                move_origins = [int(v[0]) for v in process.moveVectors()]
                cpp_move_origins = Backend.StdVectorInt(move_origins)
                cpp_move_vectors = Backend.StdVectorCoordinate()
                for v in process.moveVectors():
                    cpp_move_vectors.push_back(
                        Backend.Coordinate(v[1][0], v[1][1], v[1][2]))

                # Construct and store the C++ process.
                if self.__rate_calculator is not None:
                    # Set the cutoff correctly.
                    cutoff = self.__rate_calculator.cutoff()
                    if cutoff is None:
                        cutoff = 1.0

                    cpp_processes.push_back(
                        Backend.CustomRateProcess(cpp_config1, cpp_config2,
                                                  rate_constant, cpp_basis,
                                                  cutoff, cpp_move_origins,
                                                  cpp_move_vectors,
                                                  process_number))
                else:
                    cpp_processes.push_back(
                        Backend.Process(cpp_config1, cpp_config2,
                                        rate_constant, cpp_basis,
                                        cpp_move_origins, cpp_move_vectors,
                                        process_number))

            # Construct the C++ interactions object.
            if self.__rate_calculator is not None:
                self.__backend = Backend.Interactions(
                    cpp_processes, self.__implicit_wildcards,
                    self.__rate_calculator)
            else:
                self.__backend = Backend.Interactions(
                    cpp_processes, self.__implicit_wildcards)

        # Return the stored backend.
        return self.__backend
Пример #20
0
    def testBackendWithCustomRates(self):
        """ Test that we can construct the backend object with custom rates. """
        # A first process.
        coords = [[1.0, 2.0, 3.4], [1.1, 1.2, 1.3]]
        types0 = ["A", "B"]
        types1 = ["B", "A"]
        rate_0_1 = 3.5
        process_0 = KMCProcess(coords,
                               types0,
                               types1,
                               basis_sites=[0],
                               rate_constant=rate_0_1)

        # A second process.
        types0 = ["A", "C"]
        types1 = ["C", "A"]
        rate_0_1 = 1.5
        process_1 = KMCProcess(coords,
                               types0,
                               types1,
                               basis_sites=[0],
                               rate_constant=rate_0_1)

        processes = [process_0, process_1]

        # Set the custom rates class to use.
        custom_rates_class = CustomRateCalculator

        # Set the rate function on the custom rates calculator for testing.
        ref_rnd = numpy.random.uniform(0.0, 1.0)

        def testRateFunction(obj, coords, types_before, types_after,
                             rate_const, process_number, global_coordinate):
            return ref_rnd

        # Store the original.
        custom_rate_function = custom_rates_class.rate
        try:
            custom_rates_class.rate = testRateFunction

            # Construct the interactions object.
            kmc_interactions = KMCInteractions(processes=processes,
                                               implicit_wildcards=False)

            # Setup a dict with the possible types.
            possible_types = {
                "A": 13,
                "D": 2,
                "B": 3,
                "J": 4,
                "C": 5,
            }

            # Use a dummy argument for the configuration.
            config = "DummyConfig"

            # Test that it fails if the rate calculator is of wrong class.
            kmc_interactions.setRateCalculator(rate_calculator=Error)
            self.assertRaises(
                Error,
                lambda: kmc_interactions._backend(possible_types, 2, config))

            # Test that it fails if the rate calculator is the base class.
            kmc_interactions.setRateCalculator(
                rate_calculator=KMCRateCalculatorPlugin)
            self.assertRaises(
                Error,
                lambda: kmc_interactions._backend(possible_types, 2, config))

            # But this should work.
            kmc_interactions.setRateCalculator(
                rate_calculator=custom_rates_class)

            # Construct the backend.
            cpp_interactions = kmc_interactions._backend(
                possible_types, 2, config)

            # Test that the configuration on the custom rate class is the one given.
            self.assertTrue(
                kmc_interactions._KMCInteractions__rate_calculator.
                configuration == config)  #  <-  Check by reference.

            # Get the rate calculator reference out of the C++ object and
            # check that a call from C++ calls the Python extension.
            cpp_coords = Backend.StdVectorDouble()
            cpp_types1 = Backend.StdVectorString()
            cpp_types2 = Backend.StdVectorString()
            rate_constant = 543.2211
            process_number = 33
            coordinate = (1.2, 3.4, 5.6)

            self.assertAlmostEqual(
                cpp_interactions.rateCalculator().backendRateCallback(
                    cpp_coords,
                    cpp_coords.size() / 3, cpp_types1, cpp_types2,
                    rate_constant, process_number, coordinate[0],
                    coordinate[1], coordinate[2]), ref_rnd, 12)
            self.assertAlmostEqual(
                kmc_interactions._KMCInteractions__rate_calculator.
                backendRateCallback(cpp_coords,
                                    cpp_coords.size() / 3, cpp_types1,
                                    cpp_types2, rate_constant, process_number,
                                    coordinate[0], coordinate[1],
                                    coordinate[2]), ref_rnd, 12)

        finally:
            # Reset the class.
            custom_rates_class.rate = custom_rate_function

        # Construct a C++ RateCalculator object directly and check that this object
        # returns the rate given to it.
        cpp_rate_calculator = Backend.RateCalculator()
        self.assertAlmostEqual(
            cpp_rate_calculator.backendRateCallback(
                cpp_coords,
                cpp_coords.size() / 3, cpp_types1, cpp_types2, rate_constant,
                process_number, coordinate[0], coordinate[1], coordinate[2]),
            rate_constant, 12)
Пример #21
0
    def testUsage(self):
        """ Test that the KMCRateCalculatorPlugin can be used in a simulation. """
        # To get the random numbers returned.
        ref_randoms = []

        # Define a derrived class.
        class RateCalc(KMCRateCalculatorPlugin):
            # Overload the initialize function.
            def initialize(self):
                # Save something on the class here.
                self._times_called = 0
            # Overload the rate function.
            def rate(self, coords, types_befpre, types_after, rate_constant):
                # Do some simple counting and return the random number.
                self._times_called += 1
                rnd = numpy.random.uniform(0.0,1.0)
                ref_randoms.append(rnd)
                return rnd
            # Overload the additive rate function.
            def useAdditiveRate(self):
                return False

        # Construct.
        calculator = RateCalc()

        # Send it to C++ to get the rate out, call it 4 times.
        cpp_coords = Backend.StdVectorCoordinate()
        cpp_coords.push_back(Backend.Coordinate(1.0,2.9,3.4))
        cpp_types1 = Backend.StdVectorString()
        cpp_types1.push_back("A")
        cpp_types2 = Backend.StdVectorString()
        cpp_types2.push_back("B")
        rate_constant = 3.1415927

        ret_randoms = []
        ret_randoms.append(Backend.getRate(calculator,
                                           cpp_coords,
                                           cpp_types1,
                                           cpp_types2,
                                           rate_constant))
        ret_randoms.append(Backend.getRate(calculator,
                                           cpp_coords,
                                           cpp_types1,
                                           cpp_types2,
                                           rate_constant))
        ret_randoms.append(Backend.getRate(calculator,
                                           cpp_coords,
                                           cpp_types1,
                                           cpp_types2,
                                           rate_constant))
        ret_randoms.append(Backend.getRate(calculator,
                                           cpp_coords,
                                           cpp_types1,
                                           cpp_types2,
                                           rate_constant))

        # Check that it was called 4 times.
        self.assertEqual( calculator._times_called, 4 )

        # Check the values.
        self.assertAlmostEqual( ret_randoms, ref_randoms, 12 )
Пример #22
0
    def run(self,
            control_parameters=None,
            trajectory_filename=None,
            trajectory_type=None,
            analysis=None,
            breakers=None):
        """
        Run the KMC lattice model simulation with specified parameters.

        :param control_paramters:   An instance of KMCControlParameters specifying
                                    number of steps to run etc.

        :param trajectory_filename: The filename of the trajectory. If not given
                                    no trajectory will be saved.

        :param trajectory_type:     The type of trajectory to use. Either 'lattice' or 'xyz'.
                                    The 'lattice' format shows the types at the latice points.
                                    The 'xyz' format gives type and coordinate for each particle.
                                    The default type is 'lattice'.
        :param analysis:            A list of instantiated analysis objects that should be used for on-the-fly analysis.

        :param breakers:            A list of instantiated breaker objects to break the Monte Carlo loop with a custom criterion.
        """
        # Check the input.
        if not isinstance(control_parameters, KMCControlParameters):
            msg = """
The 'control_parameters' input to the KMCLatticeModel run funtion
must be an instance of type KMCControlParameters."""
            raise Error(msg)

        # Check the trajectory filename.
        use_trajectory = True
        if trajectory_filename is None:
            use_trajectory = False
            msg = """ KMCLib: WARNING: No trajectory filename given -> no trajectory will be saved."""
            prettyPrint(msg)

        elif not isinstance(trajectory_filename, str):
            msg = """
The 'trajectory_filename' input to the KMCLattice model run function
must be given as string."""
            raise Error(msg)

        # Check the analysis type.
        if trajectory_type is None:
            trajectory_type = 'lattice'

        if not isinstance(trajectory_type, str):
            raise Error("The 'trajectory_type' input must given as a string.")

        # Check the analysis.
        if analysis is None:
            analysis = []
        else:
            msg = "Each element in the 'analyis' list must be an instance of KMCAnalysisPlugin."
            analysis = checkSequenceOf(analysis, KMCAnalysisPlugin, msg)

        # Check the breakers.
        if breakers is None:
            breakers = []
        else:
            msg = "Each element in the 'breakers' list must be an instance of KMCBreakerPlugin."
            breakers = checkSequenceOf(breakers, KMCBreakerPlugin, msg)

        # Set and seed the backend random number generator.
        if not Backend.setRngType(control_parameters.rngType()):
            raise Error(
                "DEVICE random number generator is not supported by your system, or the std::random_device in the standard C++ library you use is implemented using a pseudo random number generator (entropy=0)."
            )

        Backend.seedRandom(control_parameters.timeSeed(),
                           control_parameters.seed())

        # Construct the C++ lattice model.
        prettyPrint(" KMCLib: setting up the backend C++ object.")

        cpp_model = self._backend()

        # Print the initial matching information if above the verbosity threshold.
        if self.__verbosity_level > 9:
            self.__printMatchInfo(cpp_model)

        # Check that we have at least one available process to  run the KMC simulation.
        if cpp_model.interactions().totalAvailableSites() == 0:
            raise Error(
                "No available processes. None of the processes defined as input match any position in the configuration. Change the initial configuration or processes to run KMC."
            )

        # Setup a trajectory object.
        last_time = self.__cpp_timer.simulationTime()
        if use_trajectory:
            if trajectory_type == 'lattice':
                trajectory = LatticeTrajectory(
                    trajectory_filename=trajectory_filename,
                    configuration=self.__configuration)
            elif trajectory_type == 'xyz':
                trajectory = XYZTrajectory(
                    trajectory_filename=trajectory_filename,
                    configuration=self.__configuration)
            else:
                raise Error(
                    "The 'trajectory_type' input must be either 'lattice' or 'xyz'."
                )

            # Add the first step.
            trajectory.append(
                simulation_time=self.__cpp_timer.simulationTime(),
                step=0,
                configuration=self.__configuration)

        # Setup the analysis objects.
        for ap in analysis:
            step = 0
            ap.setup(step, self.__cpp_timer.simulationTime(),
                     self.__configuration)

        # Get the needed parameters.
        n_steps = control_parameters.numberOfSteps()
        n_dump = control_parameters.dumpInterval()
        n_analyse = control_parameters.analysisInterval()
        dump_time = control_parameters.dumpTimeInterval()

        prettyPrint(" KMCLib: Runing for %i steps, starting from time: %f\n" %
                    (n_steps, self.__cpp_timer.simulationTime()))

        # Run the KMC simulation.
        try:
            # Loop over the steps.
            step = 0
            time_step = 0
            while (step < n_steps):
                step += 1

                # Check if it is possible to take a step.
                nP = cpp_model.interactions().totalAvailableSites()
                if nP == 0:
                    raise Error("No more available processes.")

                # Take a step.
                time_before = self.__cpp_timer.simulationTime()
                cpp_model.propagateTime()
                time_after = self.__cpp_timer.simulationTime()

                # Check if it is time to dump the previous step to the equidistant trajectory.
                if ((dump_time is not None)
                        and ((time_after - last_time) >= dump_time)):
                    time_step += 1
                    sample_time = time_step * dump_time
                    prettyPrint(" KMCLib: %14i steps executed. time: %20.10e" %
                                (step - 1, sample_time))
                    last_time = sample_time

                    # Perform IO using the trajectory object.
                    if use_trajectory:
                        trajectory.append(simulation_time=time_before,
                                          step=step - 1,
                                          configuration=self.__configuration)

                # Update the model.
                cpp_model.singleStep()

                # Get the current simulation time.
                now = self.__cpp_timer.simulationTime()

                # Check if it is time to write a trajectory dump.
                if ((dump_time is None) and ((step) % n_dump == 0)):
                    last_time = now
                    prettyPrint(" KMCLib: %i steps executed. time: %20.10e " %
                                (step, now))

                    # Perform IO using the trajectory object.
                    if use_trajectory:
                        trajectory.append(simulation_time=now,
                                          step=step,
                                          configuration=self.__configuration)

                if ((step) % n_analyse == 0):
                    # Run all other python analysis.
                    for ap in analysis:
                        ap.registerStep(step, now, self.__configuration)

                # Check all custom break criteria.
                break_the_loop = False
                for b in breakers:

                    # If it is time to evaluate this breaker.
                    if ((step) % b.interval() == 0):
                        break_the_loop = b.evaluate(step, now,
                                                    self.__configuration)
                        # Break the inner loop.
                        if break_the_loop:
                            break

                # Break the main Monte-Carlo loop.
                if break_the_loop:
                    break

        finally:

            # Flush the trajectory buffers when done.
            if use_trajectory:
                trajectory.flush()

            # Perform the analysis post processing.
            for ap in analysis:
                ap.finalize()
Пример #23
0
    def testBackendWithCustomRates(self):
        """ Test that we can construct the backend object with custom rates. """
        # A first process.
        coords = [[1.0, 2.0, 3.4], [1.1, 1.2, 1.3]]
        types0 = ["A", "B"]
        types1 = ["B", "A"]
        rate_0_1 = 3.5
        process_0 = KMCProcess(coords,
                               types0,
                               types1,
                               basis_sites=[0],
                               rate_constant=rate_0_1)

        # A second process.
        types0 = ["A", "C"]
        types1 = ["C", "A"]
        rate_0_1 = 1.5
        process_1 = KMCProcess(coords,
                               types0,
                               types1,
                               basis_sites=[0],
                               rate_constant=rate_0_1)

        processes = [process_0, process_1]

        # Set the custom rates class to use.
        custom_rates_class = CustomRateCalculator

        # Construct the interactions object.
        kmc_interactions = KMCInteractions(processes=processes,
                                           implicit_wildcards=False)
        kmc_interactions.setRateCalculator(rate_calculator=custom_rates_class)

        # Set the rate function on the custom rates calculator for testing.
        ref_rnd = numpy.random.uniform(0.0, 1.0)

        def testRateFunction(coords, types_before, types_after, rate_const,
                             process_number, global_coordinate):
            return ref_rnd

        kmc_interactions._KMCInteractions__rate_calculator.rate = testRateFunction

        # Setup a dict with the possible types.
        possible_types = {
            "A": 13,
            "D": 2,
            "B": 3,
            "J": 4,
            "C": 5,
        }

        # Construct the backend.
        cpp_interactions = kmc_interactions._backend(possible_types, 2)

        # Get the rate calculator reference out of the C++ object and
        # check that a call from C++ calls the Python extension.
        cpp_coords = Backend.StdVectorDouble()
        cpp_types1 = Backend.StdVectorString()
        cpp_types2 = Backend.StdVectorString()
        rate_constant = 543.2211
        process_number = 33
        coordinate = (1.2, 3.4, 5.6)

        self.assertAlmostEqual(
            cpp_interactions.rateCalculator().backendRateCallback(
                cpp_coords,
                cpp_coords.size() / 3, cpp_types1, cpp_types2, rate_constant,
                process_number, coordinate[0], coordinate[1], coordinate[2]),
            ref_rnd, 12)
        self.assertAlmostEqual(
            kmc_interactions._KMCInteractions__rate_calculator.
            backendRateCallback(cpp_coords,
                                cpp_coords.size() / 3, cpp_types1, cpp_types2,
                                rate_constant, process_number, coordinate[0],
                                coordinate[1], coordinate[2]), ref_rnd, 12)

        # Construct a C++ RateCalculator object directly and check that this object
        # returns the rate given to it.
        cpp_rate_calculator = Backend.RateCalculator()
        self.assertAlmostEqual(
            cpp_rate_calculator.backendRateCallback(
                cpp_coords,
                cpp_coords.size() / 3, cpp_types1, cpp_types2, rate_constant,
                process_number, coordinate[0], coordinate[1], coordinate[2]),
            rate_constant, 12)
Пример #24
0
    def run(self,
            control_parameters=None,
            trajectory_filename=None,
            trajectory_type=None,
            analysis=None):
        """
        Run the KMC lattice model simulation with specified parameters.

        :param control_paramters:   An instance of KMCControlParameters specifying
                                    number of steps to run etc.

        :param trajectory_filename: The filename of the trajectory. If not given
                                    no trajectory will be saved.

        :param trajectory_type:     The type of trajectory to use. Either 'lattice' or 'xyz'.
                                    The 'lattice' format shows the types at the latice points.
                                    The 'xyz' format gives type and coordinate for each particle.
                                    The default type is 'lattice'.
        :param analysis:            A list of instantiated analysis objects that should be used for on-the-fly analysis.
        """
        # Check the input.
        if not isinstance(control_parameters, KMCControlParameters):
            msg ="""
The 'control_parameters' input to the KMCLatticeModel run funtion
must be an instance of type KMCControlParameters."""
            raise Error(msg)

        # Check the trajectory filename.
        use_trajectory = True
        if trajectory_filename is None:
            use_trajectory = False
            msg =""" KMCLib: WARNING: No trajectory filename given -> no trajectory will be saved."""
            prettyPrint(msg)

        elif not isinstance(trajectory_filename, str):
            msg = """
The 'trajectory_filename' input to the KMCLattice model run function
must be given as string."""
            raise Error(msg)

        # Check the analysis type.
        if trajectory_type is None:
            trajectory_type = 'lattice'

        if not isinstance(trajectory_type, str):
            raise Error("The 'trajectory_type' input must given as a string.")

        # Check the analysis.
        if analysis is None:
            analysis = []
        else:
            msg = "Each element in the 'analyis' list must be an instance of KMCAnalysisPlugin."
            analysis = checkSequenceOf(analysis, KMCAnalysisPlugin, msg)

        # Seed the backend random number generator.
        Backend.seedRandom(control_parameters.timeSeed(),
                           control_parameters.seed())

        # Construct the C++ lattice model.
        prettyPrint(" KMCLib: setting up the backend C++ object.")

        cpp_model = self._backend()

        # Print the initial matching information if above the verbosity threshold.
        if self.__verbosity_level > 9:
            self.__printMatchInfo(cpp_model)

        # Check that we have at least one available process to  run the KMC simulation.
        if cpp_model.interactions().totalAvailableSites() == 0:
            raise Error("No available processes. None of the processes defined as input match any position in the configuration. Change the initial configuration or processes to run KMC.")
#####
##### Custom addiation to use_trajectory -- added CFG.
#####
        # Setup a trajectory object.
        if use_trajectory:
            if trajectory_type == 'lattice':
                trajectory = LatticeTrajectory(trajectory_filename=trajectory_filename,
                                               configuration=self.__configuration)
            elif trajectory_type == 'xyz':
                trajectory = XYZTrajectory(trajectory_filename=trajectory_filename,
                                           configuration=self.__configuration)
            elif trajectory_type == 'cfg':
                trajectory = CFGTrajectory(trajectory_filename=trajectory_filename,
                                           configuration=self.__configuration)                                               
            else:
                raise Error("The 'trajectory_type' input must be either 'lattice' or 'xyz'.")

            # Add the first step.
            trajectory.append(simulation_time  = self.__cpp_timer.simulationTime(),
                              step             = 0,
                              configuration    = self.__configuration)
            
        # Setup the analysis objects.
        for ap in analysis:
            step = 0
            ap.setup(step,
                     self.__cpp_timer.simulationTime(),
                     self.__configuration);

        # Get the needed parameters.
        n_steps   = control_parameters.numberOfSteps()
        n_dump    = control_parameters.dumpInterval()
        n_analyse = control_parameters.analysisInterval()
        prettyPrint(" KMCLib: Runing for %i steps, starting from time: %f\n"%(n_steps, self.__cpp_timer.simulationTime()))

        # Run the KMC simulation.
        try:
            # Loop over the steps.
            step = 0
            while(step < n_steps):
                step += 1
#            for s in range(n_steps):
#                step = s+1

                # Check if it is possible to take a step.
                nP = cpp_model.interactions().totalAvailableSites()
                if nP == 0:
                    raise Error("No more available processes.")

                # Take a step.
                cpp_model.singleStep()

                if ((step)%n_dump == 0):
                    prettyPrint(" KMCLib: %i steps executed. time: %20.10e "%(step, self.__cpp_timer.simulationTime()))

                    # Perform IO using the trajectory object.
                    if use_trajectory:
                        trajectory.append(simulation_time  = self.__cpp_timer.simulationTime(),
                                          step             = step,
                                          configuration    = self.__configuration)

                if ((step)%n_analyse == 0):
                    # Run all other python analysis.
                    for ap in analysis:
                        ap.registerStep(step,
                                        self.__cpp_timer.simulationTime(),
                                        self.__configuration);

        finally:

            # Flush the trajectory buffers when done.
            if use_trajectory:
                trajectory.flush()

            # Perform the analysis post processing.
            for ap in analysis:
                ap.finalize();
Пример #25
0
 def atomIDCoordinates(self):
     return (Backend.Coordinate(c0_ref, c1_ref, c2_ref), )
Пример #26
0
    def run(self,
            control_parameters=None,
            trajectory_filename=None,
            trajectory_type=None,
            analysis=None,
            breakers=None):
        """
        Run the KMC lattice model simulation with specified parameters.

        :param control_paramters:   An instance of KMCControlParameters specifying
                                    number of steps to run etc.

        :param trajectory_filename: The filename of the trajectory. If not given
                                    no trajectory will be saved.

        :param trajectory_type:     The type of trajectory to use. Either 'lattice' or 'xyz'.
                                    The 'lattice' format shows the types at the latice points.
                                    The 'xyz' format gives type and coordinate for each particle.
                                    The default type is 'lattice'.
        :param analysis:            A list of instantiated analysis objects that should be used for on-the-fly analysis.

        :param breakers:            A list of instantiated breaker objects to break the Monte Carlo loop with a custom criterion.
        """
        # Check the input.
        if not isinstance(control_parameters, KMCControlParameters):
            msg ="""
The 'control_parameters' input to the KMCLatticeModel run funtion
must be an instance of type KMCControlParameters."""
            raise Error(msg)

        # Check the trajectory filename.
        use_trajectory = True
        if trajectory_filename is None:
            use_trajectory = False
            msg =""" KMCLib: WARNING: No trajectory filename given -> no trajectory will be saved."""
            prettyPrint(msg)

        elif not isinstance(trajectory_filename, str):
            msg = """
The 'trajectory_filename' input to the KMCLattice model run function
must be given as string."""
            raise Error(msg)

        # Check the analysis type.
        if trajectory_type is None:
            trajectory_type = 'lattice'

        if not isinstance(trajectory_type, str):
            raise Error("The 'trajectory_type' input must given as a string.")

        # Check the analysis.
        if analysis is None:
            analysis = []
        else:
            msg = "Each element in the 'analyis' list must be an instance of KMCAnalysisPlugin."
            analysis = checkSequenceOf(analysis, KMCAnalysisPlugin, msg)

        # Check the breakers.
        if breakers is None:
            breakers = []
        else:
            msg = "Each element in the 'breakers' list must be an instance of KMCBreakerPlugin."
            breakers = checkSequenceOf(breakers, KMCBreakerPlugin, msg)

        # Set and seed the backend random number generator.
        if not Backend.setRngType(control_parameters.rngType()):
            raise Error("DEVICE random number generator is not supported by your system, or the std::random_device in the standard C++ library you use is implemented using a pseudo random number generator (entropy=0).")

        Backend.seedRandom(control_parameters.timeSeed(),
                           control_parameters.seed())

        # Construct the C++ lattice model.
        prettyPrint(" KMCLib: setting up the backend C++ object.")

        cpp_model = self._backend()

        # Print the initial matching information if above the verbosity threshold.
        if self.__verbosity_level > 9:
            self.__printMatchInfo(cpp_model)

        # Check that we have at least one available process to  run the KMC simulation.
        if cpp_model.interactions().totalAvailableSites() == 0:
            raise Error("No available processes. None of the processes defined as input match any position in the configuration. Change the initial configuration or processes to run KMC.")

        # Setup a trajectory object.
        last_time = self.__cpp_timer.simulationTime()
        if use_trajectory:
            if trajectory_type == 'lattice':
                trajectory = LatticeTrajectory(trajectory_filename=trajectory_filename,
                                               configuration=self.__configuration)
            elif trajectory_type == 'xyz':
                trajectory = XYZTrajectory(trajectory_filename=trajectory_filename,
                                           configuration=self.__configuration)
            else:
                raise Error("The 'trajectory_type' input must be either 'lattice' or 'xyz'.")

            # Add the first step.
            trajectory.append(simulation_time  = self.__cpp_timer.simulationTime(),
                              step             = 0,
                              configuration    = self.__configuration)

        # Setup the analysis objects.
        for ap in analysis:
            step = 0
            ap.setup(step,
                     self.__cpp_timer.simulationTime(),
                     self.__configuration);

        # Get the needed parameters.
        n_steps   = control_parameters.numberOfSteps()
        n_dump    = control_parameters.dumpInterval()
        n_analyse = control_parameters.analysisInterval()
        dump_time = control_parameters.dumpTimeInterval()

        prettyPrint(" KMCLib: Runing for %i steps, starting from time: %f\n"%(n_steps, self.__cpp_timer.simulationTime()))

        # Run the KMC simulation.
        try:
            # Loop over the steps.
            step = 0
            time_step = 0
            while(step < n_steps):
                step += 1

                # Check if it is possible to take a step.
                nP = cpp_model.interactions().totalAvailableSites()
                if nP == 0:
                    raise Error("No more available processes.")

                # Take a step.
                time_before = self.__cpp_timer.simulationTime()
                cpp_model.propagateTime()
                time_after  = self.__cpp_timer.simulationTime()

                # Check if it is time to dump the previous step to the equidistant trajectory.
                if ((dump_time is not None) and ((time_after-last_time) >= dump_time)):
                    time_step += 1
                    sample_time = time_step * dump_time
                    prettyPrint(" KMCLib: %14i steps executed. time: %20.10e"%(step-1, sample_time))
                    last_time = sample_time

                    # Perform IO using the trajectory object.
                    if use_trajectory:
                        trajectory.append(simulation_time  = time_before,
                                          step             = step-1,
                                          configuration    = self.__configuration)

                # Update the model.
                cpp_model.singleStep()

                # Get the current simulation time.
                now = self.__cpp_timer.simulationTime()

                # Check if it is time to write a trajectory dump.
                if ((dump_time is None) and ((step)%n_dump == 0)):
                    last_time = now
                    prettyPrint(" KMCLib: %i steps executed. time: %20.10e "%(step, now))

                    # Perform IO using the trajectory object.
                    if use_trajectory:
                        trajectory.append(simulation_time  = now,
                                          step             = step,
                                          configuration    = self.__configuration)

                if ((step)%n_analyse == 0):
                    # Run all other python analysis.
                    for ap in analysis:
                        ap.registerStep(step,
                                        now,
                                        self.__configuration)

                # Check all custom break criteria.
                break_the_loop = False
                for b in breakers:

                    # If it is time to evaluate this breaker.
                    if ((step)%b.interval() == 0):
                        break_the_loop = b.evaluate(step,
                                                    now,
                                                    self.__configuration)
                        # Break the inner loop.
                        if break_the_loop:
                            break

                # Break the main Monte-Carlo loop.
                if break_the_loop:
                    break

        finally:

            # Flush the trajectory buffers when done.
            if use_trajectory:
                trajectory.flush()

            # Perform the analysis post processing.
            for ap in analysis:
                ap.finalize();
    def testUsage(self):
        """ Test that the KMCRateCalculatorPlugin can be used in a simulation. """
        # To get the random numbers and process numbers returned.
        ref_randoms = []
        ref_process_numbers = []
        ref_coordinates = []

        # Define a derrived class.
        class RateCalc(KMCRateCalculatorPlugin):
            # Overload the initialize function.
            def initialize(self):
                # Save something on the class here.
                self._times_called = 0

            # Overload the rate function.
            def rate(self, coords, types_befpre, types_after, rate_constant,
                     process_number, coordinate):
                # Do some simple counting and return the random number.
                self._times_called += 1
                rnd = numpy.random.uniform(0.0, 1.0)
                ref_randoms.append(rnd)
                ref_process_numbers.append(process_number)
                ref_coordinates.append(coordinate)
                return rnd

            # Overload the additive rate function.
            def useAdditiveRate(self):
                return False

        # Construct.
        calculator = RateCalc("DummyConfig")

        # Send it to C++ to get the rate out, call it 4 times.
        cpp_coords = Backend.StdVectorCoordinate()
        cpp_coords.push_back(Backend.Coordinate(1.0, 2.9, 3.4))
        cpp_types1 = Backend.StdVectorString()
        cpp_types1.push_back("A")
        cpp_types2 = Backend.StdVectorString()
        cpp_types2.push_back("B")
        rate_constant = 3.1415927
        ret_randoms = []
        process_numbers = [21, 12, 10, 2]
        global_xyz = numpy.array([[0.2, 0.4, 0.5], [1.1, 1.3, 1.4],
                                  [3.4, 4.3, 3.3], [4.2, 3.2, 1.9]])

        ret_randoms.append(
            Backend.getRate(calculator, cpp_coords, cpp_types1, cpp_types2,
                            rate_constant, process_numbers[0],
                            global_xyz[0, 0], global_xyz[0, 1], global_xyz[0,
                                                                           2]))
        ret_randoms.append(
            Backend.getRate(calculator, cpp_coords, cpp_types1, cpp_types2,
                            rate_constant, process_numbers[1],
                            global_xyz[1, 0], global_xyz[1, 1], global_xyz[1,
                                                                           2]))
        ret_randoms.append(
            Backend.getRate(calculator, cpp_coords, cpp_types1, cpp_types2,
                            rate_constant, process_numbers[2],
                            global_xyz[2, 0], global_xyz[2, 1], global_xyz[2,
                                                                           2]))
        ret_randoms.append(
            Backend.getRate(calculator, cpp_coords, cpp_types1, cpp_types2,
                            rate_constant, process_numbers[3],
                            global_xyz[3, 0], global_xyz[3, 1], global_xyz[3,
                                                                           2]))

        # Check that it was called 4 times.
        self.assertEqual(calculator._times_called, 4)

        # Check the values.
        self.assertAlmostEqual(ret_randoms, ref_randoms, 12)

        # Check the reference process numbers.
        self.assertEqual(ref_process_numbers[0], 21)
        self.assertEqual(ref_process_numbers[1], 12)
        self.assertEqual(ref_process_numbers[2], 10)
        self.assertEqual(ref_process_numbers[3], 2)

        # Check the reference coordinate.
        self.assertAlmostEqual(
            numpy.linalg.norm(global_xyz - numpy.array(ref_coordinates)), 0.0,
            10)