def minimize(self, cluster: Cluster, *args, **kwargs) -> Cluster: """ Method to locally minimise a cluster of Lennard-Jones particles. Uses the L-BFGS-B method implemented within scipy.minimize. Args: cluster: Cluster instance, required, cluster instance to be minimized kwargs: Dict containing any other keyword arguments to be passed to the scipy optimizer Returns: result_dict{'coordinates': optimised structure coordinates, 'energy': final energy of the cluster, 'success': True if successfully minimised} """ coords, ids, labels = cluster.get_particle_positions() coordinates = coords result = minimize(fun=self.get_energy, x0=coordinates.flatten(), method='L-BFGS-B', jac=self.get_jacobian, *args, **kwargs) if not result.success: print("Optimization failed") cluster.set_particle_positions( (result.x.reshape(coordinates.shape), ids, labels)) cluster.cost = result.fun return cluster
def setUpClass(cls) -> None: """Sets up Deaven Ho mating for testing""" cls.log = logging.getLogger(__name__) cls.mate = DeavenHoCrossover(max_attempts=100) cls.seed = 1 cls.c1 = Cluster(cost=-3.5, molecules=[Molecule(np.array([[0., 0, 0], [0, 4, 0]]), ["H", "H"]), Molecule(np.array([[0., 0, 1], [0, 3, 0]]), ["H", "H"]), Molecule(np.array([[0., 0, 2], [0, 2, 0]]), ["Cr", "H"]), Molecule(np.array([[0., 0, 3], [0, 1, 0]]), ["H", "H"]), Molecule(np.array([[0., 0, 4], [0, 0, 0]]), ["H", "H"])]) cls.c2 = Cluster(cost=-3.5, molecules=[Molecule(np.array([[4., 0, 0], [0, 0, 4]]), ["H", "H"]), Molecule(np.array([[3., 0, 1], [1, 0, 2]]), ["H", "H"]), Molecule(np.array([[2., 0, 2], [2, 0, 2]]), ["H", "Cr"]), Molecule(np.array([[1., 0, 3], [3, 0, 1]]), ["H", "H"]), Molecule(np.array([[0., 2, 4], [4, 0, 1]]), ["H", "H"])]) cls.c3 = Cluster(cost=-3.5, molecules=[Molecule(np.array([[0., 1, 0], [5, 4, 3]]), ["H", "H"]), Molecule(np.array([[1., 2, 1], [4, 3, 4]]), ["H", "H"]), Molecule(np.array([[2., 3, 2], [3, 2, 5]]), ["Cr", "H"]), Molecule(np.array([[3., 4, 3], [2, 1, 6]]), ["H", "H"]), Molecule(np.array([[4., 5, 4], [1, 0, 7]]), ["H", "H"])])
def minimize(self, cluster: Cluster, *args, **kwargs) -> Cluster: """ Method to locally minimise a cluster of Lennard-Jones particles. Uses the L-BFGS-B method implemented within scipy.minimize. Attributes: coordinates: np.array(shape=(number_of_particles, 3), dtype=float) array of coordinates kwargs: Dict containing any other keyword arguments to be passed to the scipy optimizer molecules: list(int), optional, Returns ------- result_dict{'coordinates': optimised structure coordinates, 'energy': final energy of the cluster, 'success': True if successfully minimised} """ positions = list(cluster.get_particle_positions()) coordinates = positions[0].flatten() # args = {"sigma": self.sigma, "epsilon": self.epsilon4, "base_exp": 6} result = scipy.optimize.minimize( fun=self.get_energy, x0=coordinates, # , args=args, method='L-BFGS-B', jac=self.get_jacobian) positions = (result.x.reshape( (self.n_atoms, 3)), positions[1], positions[2]) cluster.set_particle_positions(positions) cluster.cost = result.fun return cluster
def get_energy(self, cluster: Cluster) -> float: """ Method to return the single-point energy of a system Parameters ---------- Returns ------- float(energy): Energy of the system at the given coordinates Args: cluster: Cluster instance, required, the for which to calculate energy *args: list, optional, postitional arguments **kwargs: Other keyword arguments needed on a per-implementation basis (i.e. atom labels) """ if isinstance(cluster, Cluster): coordinates, _, _ = cluster.get_particle_positions() elif isinstance(cluster, np.ndarray): natoms = int(len(cluster) / 3) coordinates = cluster.reshape((natoms, 3)) else: raise AttributeError("coordinate format is not accepted") rs = get_all_magnitudes(coordinates) coulombic = self.coulombic_E_component(rs) LJ_component = self.LJ_E_component(rs) return coulombic + LJ_component
def test_simple_geometric_characterizer_similar_cluster_succeed(self) -> None: compare = SimpleGeometricCharacterizer() c1 = Cluster(cost=0.0, molecules=[Molecule(coordinates=np.array([[1.0, 0.0, 0.0]]), particle_names=["LJ"]), Molecule(coordinates=np.array([[0.0, 1.0, 0.0]]), particle_names=["LJ"]), Molecule(coordinates=np.array([[0.0, 0.0, -1.0]]), particle_names=["LJ"])]) c2 = Cluster(cost=0.0, molecules=[Molecule(coordinates=np.array([[-1.0, 2e-1, 1e-1]]), particle_names=["LJ"]), Molecule(coordinates=np.array([[5e-2, -1.0, 1e-1]]), particle_names=["LJ"]), Molecule(coordinates=np.array([[1e-2, 0.0, 1.1]]), particle_names=["LJ"])]) self.assertTrue(compare(c1, c2))
def test_simple_geometric_characterizer_nearly_same_cluster_fail(self) -> None: compare = SimpleGeometricCharacterizer() c1 = Cluster(cost=0.0, molecules=[Molecule(coordinates=np.array([[1.0, 0.0, 0.0]]), particle_names=["LJ"]), Molecule(coordinates=np.array([[0.0, 1.0, 0.0]]), particle_names=["LJ"]), Molecule(coordinates=np.array([[0.0, 0.0, -1.0]]), particle_names=["LJ"])]) c2 = Cluster(cost=0.0, molecules=[Molecule(coordinates=np.array([[-1.0, 0.1, -0.1]]), particle_names=["LJ"]), Molecule(coordinates=np.array([[0.0, -1.0, 0.0]]), particle_names=["LJ"]), Molecule(coordinates=np.array([[0.1, -0.1, 1.1]]), particle_names=["LJ"])]) self.assertFalse(compare(c1, c2))
def test_RST_mutate_good(self) -> None: c1 = Cluster(cost=9.0, molecules=[Molecule(coordinates=np.random.uniform(low=10, size=(2, 3)), particle_names=["B", "Be"]), Molecule(coordinates=np.random.uniform(low=10, size=(3, 3)), particle_names=["Be", "B", "Be"])]) mutation = RandomSingleTranslation() new_cluster = mutation.mutate(copy.deepcopy(c1)) self.assertIsInstance(new_cluster, Cluster) self.assertFalse(np.allclose(new_cluster.get_molecular_positions()[0], c1.get_molecular_positions()[0]))
def test_parse_structures_list_clusters(self) -> None: c1 = Cluster(cost=0.0, molecules=[Molecule(coordinates=np.zeros(shape=(2, 3)), particle_names=["H", "He"]), Molecule(coordinates=np.zeros(shape=(3, 3)), particle_names=["H", "H", "He"])]) c2 = Cluster(cost=9.0, molecules=[Molecule(coordinates=np.ones(shape=(2, 3)), particle_names=["B", "Be"]), Molecule(coordinates=np.ones(shape=(3, 3)), particle_names=["Be", "B", "Be"])]) cluster_list = [c1, c2] output = self.writer._parse_structures(cluster_list) self.assertListEqual(output[0][1][0].tolist(), np.zeros(shape=(5, 3)).tolist()) # Check coords are correct self.assertListEqual(output[1], [0, 9])
def test_lj_energy_r_min(self) -> None: # Then check we get v~-1 @ r~r_{min} (r_min = 2**(1./exp_attractive)) c1 = Cluster(cost=0.0, molecules=[Molecule(coordinates=np.array([[0.0, 0.0, 0.0]]), particle_names=["LJ"]), Molecule(coordinates=np.array([[2**(1./6.0), 0.0, 0.0]]), particle_names=["LJ"])]) c2 = Cluster(cost=0.0, molecules=[Molecule(coordinates=np.array([[0.0, 0.0, 0.0]]), particle_names=["LJ"]), Molecule(coordinates=np.array([[2 ** (1. / 20.), 0.0, 0.0]]), particle_names=["LJ"])]) self.assertAlmostEqual(-1.0, self.LJ_6_12.get_energy(c1)) self.assertAlmostEqual(-1.0, self.LJ_15_30.get_energy(c2))
def test_get_energy_GLJ_zero(self): pot = OPLS_potential(q=[0, 0], eps=[1, 1], sigma=[1, 1]) # GLJ energy at r_ij=1.0 == 0.0 eV clus1 = Cluster(cost=0.0, molecules=[Molecule(coordinates=np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 1.0]]), particle_names=["LJ", "LJ"])]) self.assertEqual(pot.get_energy(clus1), 0.0) # At large r, en ~~ 0.0 clus2 = Cluster(cost=0.0, molecules=[Molecule(coordinates=np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 1e99]]), particle_names=["LJ", "LJ"])]) self.assertEqual(pot.get_energy(clus2), 0)
def test__get_coord_labels(self) -> None: c1 = Cluster(cost=0.0, molecules=[Molecule(coordinates=np.zeros(shape=(2, 3)), particle_names=["H", "He"]), Molecule(coordinates=np.zeros(shape=(3, 3)), particle_names=["H", "H", "He"])]) c2 = Cluster(cost=0.0, molecules=[Molecule(coordinates=np.ones(shape=(2, 3)), particle_names=["B", "Be"]), Molecule(coordinates=np.ones(shape=(3, 3)), particle_names=["Be", "B", "Be"])]) cluster_list = [c1, c2] coord_labels = self.writer._get_coord_labels(cluster_list) self.assertListEqual(list(coord_labels[0][0][0]), [0, 0, 0]) self.assertTrue("Be" in coord_labels[1][1])
def test_shake_mutate_good(self) -> None: c1 = Cluster(cost=9.0, molecules=[Molecule(coordinates=np.random.uniform(low=10, size=(2, 3)), particle_names=["B", "Be"]), Molecule(coordinates=np.random.uniform(low=10, size=(3, 3)), particle_names=["Be", "B", "Be"])]) mutation = Shake() mutated_c1 = mutation.mutate(copy.deepcopy(c1)) diff = c1.get_particle_positions()[0] - mutated_c1.get_particle_positions()[0] self.assertEqual(magnitude(diff[0]), 0.42059300827254525) self.assertEqual(magnitude(diff[-1]), 0.4186786088973787)
def test_minimise(self): if self.executable is None: self.skipTest("Executable not found. Minimise test skipped.") testdir = "test_dftb" if os.path.exists(testdir): shutil.rmtree(testdir) os.mkdir(testdir) clus1 = Cluster(molecules=[ Molecule(particle_names=["H", "H", "O"], coordinates=np.array([[0.0, 0.0, 1.0], [0.0, 0.0, -1.0], [0.0, 0.0, 0.0]])), Molecule(particle_names=["H", "H", "O"], coordinates=np.array([[2.0, 0.0, 1.0], [2.0, 0.0, -1.0], [2.0, 0.0, 0.0]])) ]) pot = DeMonDFTBPotential(minimize_template=self.test_data_path + "/dftb_in.hsd", energy_template="NONE", work_dir=".", run_string=self.executable) clus2 = pot.minimize(clus1, dir_name=testdir) coords, mol_ids, atom_names = clus2.get_particle_positions() self.assertListEqual(atom_names, ["H", "H", "O", "H", "H", "O"]) if os.path.exists(testdir): shutil.rmtree(testdir)
def setUpClass(cls) -> None: """Sets up an instance of random cluster and a logger""" cls.log = logging.getLogger(__name__) cls.mutate = RandomCluster(log=cls.log) # , box_length=10.0) cls.c1 = Cluster(cost=0.0, molecules=[Molecule(coordinates=np.array([[1., 1, 1], [0, 0, 0]]), particle_names=["He", "H"]), Molecule(coordinates=np.array([[1., -1, -1], [0, 0, 0], [-1, -1, -1]]), particle_names=["H", "H", "He"])]) cls.c2 = Cluster(cost=9.0, molecules=[Molecule(coordinates=np.ones(shape=(2, 3)), particle_names=["B", "Be"]), Molecule(coordinates=np.zeros(shape=(3, 3)), particle_names=["Be", "B", "Be"])])
def test_parse_structures_single_cluster(self) -> None: c1 = Cluster(cost=0.0, molecules=[Molecule(coordinates=np.zeros(shape=(2, 3)), particle_names=["H", "He"]), Molecule(coordinates=np.zeros(shape=(3, 3)), particle_names=["H", "H", "He"])]) output = self.writer._parse_structures(c1) self.assertListEqual(output[0][0][0].tolist(), np.zeros(shape=(5, 3)).tolist()) # Check coords are correct
def test_SGD_runs(self): writer = XYZWriter() pot = NewLJ(5) c1 = Cluster(molecules=[ Molecule(coordinates=np.array( [[0.566474720473, 0.05189774298450, 0.03347914367068]]), particle_names=["Cl"]), Molecule(coordinates=np.array( [[-0.010999390189086, 1.01397142828, -1.00418537828]]), particle_names=["Cl"]), Molecule(coordinates=np.array( [[-0.555475330284, 0.0341308287337, 0.0623354819510]]), particle_names=["Cl"]), Molecule(coordinates=np.array( [[-0.39523644062, 2.8659668824697, 0.3990951103299]]), particle_names=["Cl"]), Molecule(coordinates=np.array( [[-0.39523644062, 0.8659668824697, 0.3990951103299]]), particle_names=["Cl"]) ], ) sgd = SGD(potential=pot) c_ret = sgd(c1) writer.write(c_ret, "test_data/out.xyz") self.assertIsInstance(c_ret, Cluster) self.assertAlmostEqual(pot.get_energy(c_ret), -9.10385, places=3)
def test_lj_energy_r1(self) -> None: c1 = Cluster(cost=0.0, molecules=[Molecule(coordinates=np.array([[0.0, 0.0, 0.0]]), particle_names=["LJ"]), Molecule(coordinates=np.array([[1.0, 0.0, 0.0]]), particle_names=["LJ"])]) # First we check that we get V=0 at r=1 self.assertEqual(0.0, self.LJ_6_12.get_energy(c1)) self.assertEqual(0.0, self.LJ_15_30.get_energy(c1))
def test_get_energy_GLJ_minimum(self): # GLJ energy at r_ij~1.112 ~~ -1.0 eV pot = OPLS_potential(q=[0, 0], eps=[1, 1], sigma=[1, 1]) clus = Cluster(cost=0.0, molecules=[Molecule(coordinates=np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 2**(1/6)]]), particle_names=["LJ", "LJ"])]) self.assertEqual(pot.get_energy(clus), -1)
def test_get_energy_coulombic_unlike_charges(self): pot = OPLS_potential(q=[1, -1], eps=[0, 0], sigma=[1, 1]) clus = Cluster(cost=0.0, molecules=[Molecule(coordinates=np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 0.1]]), particle_names=["LJ", "LJ"])]) self.assertLess(pot.get_energy(clus), 0)
def test_get_energy_GLJ_close_contact(self): # At small r, en >> 0.0 pot = OPLS_potential(q=[0, 0], eps=[1, 1], sigma=[1, 1]) clus = Cluster(cost=0.0, molecules=[Molecule(coordinates=np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 0.1]]), particle_names=["LJ", "LJ"])]) self.assertGreater(pot.get_energy(clus), 1e3)
def test_ljc_get_jacobian_1(self) -> None: fast_potential = LJcPotential(2) c1 = Cluster(cost=0.0, molecules=[ Molecule(coordinates=np.array([[0.0, 0.0, 0.0]]), particle_names=["LJ"]), Molecule(coordinates=np.array( [[2**(1 / 6.), 0.0, 0.0]]), particle_names=["LJ"]) ]) test_jac = fast_potential.get_jacobian( c1.get_particle_positions()[0].flatten()) # print(ref_jac, test_jac) self.assertEqual( np.array([-12.0, 0.0, 0.0, 12.0, 0.0, 0.0]).all(), test_jac.all())
def check_cluster(self, cluster: Cluster) -> bool: """Simple distance check to ensure that there is no particle overlap""" all_r_sq = (cluster.get_particle_positions()[0]**2).sum(-1) if min(all_r_sq) <= self.cutoff: return False else: return True
def test_simple_energy_characterizer(self) -> None: """Tests the simplest characterizer, based simply on energy""" c1 = Cluster(cost=-100, molecules=[]) c2 = Cluster(cost=-99.9, molecules=[]) c3 = Cluster(cost=-99.9+1e-6, molecules=[]) c4 = Cluster(cost=-99.9+2e-6, molecules=[]) # noinspection PyArgumentEqualDefault simple_characterizer = SimpleEnergeticCharacterizer(accuracy=1e-6) # default accuracy=1e-6 shown for clarity # c1 == c1 self.assertTrue(simple_characterizer(c1, c1)) # c2 is very close in energy to c3 self.assertTrue(simple_characterizer(c2, c3)) # c1 and c2 are distant in energy self.assertFalse(simple_characterizer(c1, c2)) # c2 and c4 are above the threshold apart from each other self.assertFalse(simple_characterizer(c2, c4))
def test_minimize_LJ38(self) -> None: lj38_coordinates = np.loadtxt(bmpga.__path__[0]+"/tests/test_data/LJ38.xyz") c1 = Cluster(cost=0.0, molecules=[Molecule(coordinates=np.array([coord]), particle_names=["LJ"]) for coord in lj38_coordinates]) min_lj38 = self.LJ_6_12.minimize(c1) self.assertAlmostEqual(-173.928427, min_lj38['energy'], places=6)
def test_minimise_dimer(self) -> None: c1 = Cluster(cost=0.0, molecules=[Molecule(coordinates=np.array([[0.0, 0.0, 0.0]]), particle_names=["LJ"]), Molecule(coordinates=np.array([[2**(1./6.0), 0.0, 0.0]]), particle_names=["LJ"])]) min1 = self.LJ_6_12.minimize(c1) self.assertTrue(min1['success']) self.assertAlmostEqual((2**(1./6.)), magnitude(min1['coordinates'][0], min1['coordinates'][1]))
def compare(self, cluster1: Cluster, cluster2: Cluster, *args, **kwargs) -> bool: """Simple geometry comparison. Aligns clusters, then Args: cluster1, (Cluster): required cluster2, (Cluster): required Returns: bool, True if the clusters are the same to within self.accuracy, else False """ align_clusters(cluster1, cluster2) g_tensor_c1 = gyration_tensor(cluster1.get_particle_positions()[0]) g_tensor_c2 = gyration_tensor(cluster2.get_particle_positions()[0]) g_tensor_c1 -= g_tensor_c2 return np.linalg.norm(g_tensor_c1) <= self.accuracy
def test_minimize_full(self): pot = OPLS_potential(q=[0.5, -0.5], eps=[1., 1.], sigma=[1., 1.]) clus = Cluster(cost=0.0, molecules=[Molecule(coordinates=np.array([[0.1, 0.1, 0.], [0.2, 0.2, 1.122]]), particle_names=["LJ", "LJ"])]) res = pot.minimize(clus) print(res) self.assertTrue(res['success']) self.assertLess(res["energy"], -1.0)
def test_minimize_3LJ(self) -> None: c1 = Cluster(cost=0.0, molecules=[Molecule(coordinates=np.array([[0.0, 0.0, 0.0]]), particle_names=["LJ"]), Molecule(coordinates=np.array([[1.0, 0.0, 0.0]]), particle_names=["LJ"]), Molecule(coordinates=np.array([[0.5, 0.5, 0.0]]), particle_names=["LJ"])]) min2 = self.LJ_6_12.minimize(c1) self.assertAlmostEqual(-3, min2['energy'])
def test_DH_crossover_same_labels(self) -> None: c1 = Cluster(cost=-3.5, molecules=[Molecule(np.array([[0., 0, 0], [0, 4, 0]]), ["C", "H"]), Molecule(np.array([[0., 0, 1], [0, 3, 0]]), ["C", "H"]), Molecule(np.array([[0., 0, 2], [0, 2, 0]]), ["C", "H"]), Molecule(np.array([[0., 0, 3], [0, 1, 0]]), ["C", "H"]), Molecule(np.array([[0., 0, 4], [0, 0, 0]]), ["C", "H"])]) c2 = Cluster(cost=-3.5, molecules=[Molecule(np.array([[4., 0, 0], [0, 0, 4]]), ["H", "C"]), Molecule(np.array([[3., 0, 1], [1, 0, 3]]), ["H", "C"]), Molecule(np.array([[2., 0, 2], [2, 0, 1]]), ["H", "C"]), Molecule(np.array([[1., 0, 3], [3, 0, 1]]), ["H", "C"]), Molecule(np.array([[0., 2, 4], [4, 0, 0]]), ["H", "C"])]) child = self.mate([c1, c2], n_crossover=1) self.log.debug(child.molecules) self.log.debug(child.get_molecular_positions()) self.log.debug(child.get_particle_positions())
def get_energy(self, cluster: Cluster, *args, **kwargs) -> Cluster: """Provides the interface between the client and the minimise method of the Args: cluster: Cluster object, required, the Cluster to be minimised Returns: """ self.log.debug("Getting energy for cluster: {}".format(cluster)) # noinspection PyUnresolvedReferences result = self.potential.get_energy(cluster) cluster.cost = result return cluster