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)
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)
def stringListToStdVectorString(string_list): """ Converts a list of strings to a std::vector<std::string> object. :param string_list: The list of strings. :returns: A corresponding std::vector<std::string> object. """ # Get the size. size = len(string_list) # Setup the c++ object. cpp_list = Backend.StdVectorString() # Copy values over. for s in string_list: cpp_list.push_back(s) # Done. return cpp_list
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
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
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)
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 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)