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 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 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 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 align_clusters(c1: Cluster, c2: Cluster) -> None: """Employs the Kabsch algorithm to align two clusters. c1 and c2 will be modified in-place See: Kabsch, Wolfgang, (1976) "A solution of the best rotation to relate two sets of vectors", Acta Crystallographica 32:922 Args: c1, (Cluster): required c2, (Cluster): required Returns: None """ # Transform both clusters CoM to the origin. c1.center() c2.center() coords_mols_labels_c1 = c1.get_particle_positions() coords_c1 = coords_mols_labels_c1[0] coords_mols_labels_c2 = c2.get_particle_positions() coords_c2 = coords_mols_labels_c2[0] # Calculate covarience matrix A = np.dot(coords_c2.transpose(), coords_c1) # Single value decomp u, s, v = np.linalg.svd(A) if np.linalg.det(u) * np.linalg.det(v) + 1.0 < 1e-8: s[-1] = -s[-1] u[:, -1] = -u[:, -1] rot_mat = np.dot(u, v).transpose() new_coordinates = [] for c in coords_c2: new_coordinates.append(np.dot(c, rot_mat)) new_coordinates = np.array(new_coordinates) c2.set_particle_positions( (new_coordinates, coords_mols_labels_c2[1], coords_mols_labels_c2[2]))
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_simple_case(self): c1 = Cluster(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(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"]) ]) align_clusters(c1, c2) for a, b in zip(c1.get_particle_positions()[0].flatten().tolist(), c2.get_particle_positions()[0].flatten().tolist()): self.assertAlmostEqual(a, b)
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_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 minimize(self, cluster: Cluster) -> Cluster: self.step_size = self.initial_step step = 0 _ = cluster.get_particle_positions() # n_coords = len(_[1]) self.coords = _[0] np.random.shuffle(self.coords) last_energy = self.pot.get_energy(cluster) converged = False while step <= self.max_steps and not converged: converged = True step += 1 for idx in range(len(self.coords)): self._take_step(self.coords[idx]) cluster.set_particle_positions((self.coords, _[1], _[2])) new_energy = self.pot.get_energy(cluster) # print(new_energy, last_energy) dE = new_energy - last_energy last_energy = new_energy self.update() if abs(dE) >= self.convergence_gradient: converged = False # print(f"Exiting in {step} steps") # for i in self.coords: # print("Cl " + " ".join([str(a) for a in i])) cluster.minimum = True return cluster
def minimize(self, cluster: Cluster, *args, **kwargs) -> Cluster: name = self.get_directory() raw_coordinates = cluster.get_particle_positions() coordinates = self.format_XYZ(raw_coordinates[0], raw_coordinates[2]) tag_dict = {"<XYZ>": coordinates, "<NAME>": name} run_dir = self.work_dir + name input_file_name = run_dir + "/input.nw" output_file_name = run_dir + "/output.out" self.insert_to_template(template=self.minimize_template, out_file=input_file_name, target_dict=tag_dict) commands = [ self.run_string, f"{input_file_name}", f"> {output_file_name}" ] commands = ' '.join(commands) self.log.info(f"Starting NWChem with commands: {commands}") nwchem_process = subprocess.Popen(commands, cwd=run_dir, shell=True) exit_code = nwchem_process.wait() if exit_code == 134: try: raise DFTExitedUnexpectedlyError( f"NWChem exited unexpectedly with exitcode: {exit_code}\n") except DFTExitedUnexpectedlyError as error: self.log.exception(error) raise if exit_code != 0: try: raise DFTExitedUnexpectedlyError( f"NWChem exited unexpectedly with exitcode: {exit_code}\n") except DFTExitedUnexpectedlyError as error: self.log.exception(error) raise else: self.log.info( f"NWChem exited successfully. Exit code: {exit_code}") output_parser = NWChemOutputParser(output_file_name) output_parser.parse() final_structure = output_parser.final_structure try: assert all(raw_coordinates[2]) == all(final_structure[0]) except AssertionError as error: self.log.exception( f"Atoms out of order! {raw_coordinates[2]} != {final_structure[0]}\n{error}" ) cluster.set_particle_positions( (final_structure[1], raw_coordinates[1], final_structure[0])) return cluster
def run(self, cluster: Cluster, n_steps) -> dict: # setup quench here, so we know how many atoms we are dealing with... self.quench = SGD(len(cluster.get_particle_positions()[0])) super().run(cluster, n_steps) return self.results_dict
def run_DeMonNano( self, cluster: Cluster, dir_name: str, optimize: bool = False): # TODO move minimize and energy to here """Common interface to DeMonNano""" if dir_name is not None: dir_name = os.path.abspath(dir_name) else: dir_name = os.path.abspath(self.get_directory()) inp_fname = dir_name + "/deMon.inp" out_fname = dir_name + "/deMon.out" shutil.copyfile("SCC-SLAKO", dir_name + "/SCC-SLAKO") shutil.copyfile("SLAKO", dir_name + "/SLAKO") coords, molecule_ids, atom_labels = cluster.get_particle_positions() Natoms = len(molecule_ids) xyz_formatted_coordinates = self.format_XYZ(coords, atom_labels) with work_dir(): os.chdir(dir_name) # Change into the scratch dir. tag_dict = {"<XYZ>": xyz_formatted_coordinates} if optimize: template = self.minimize_template else: template = self.energy_template self.insert_to_template(template=self.work_dir + template, out_file=inp_fname, target_dict=tag_dict) with open("error_file", "w") as ef: # self.run_string should just be the location of the deMonNano executable dftb_process = subprocess.Popen([self.run_string], cwd=dir_name, shell=True, stderr=ef, stdout=ef) exit_code = dftb_process.wait() # check exit code if exit_code != 0: try: raise DFTBError( f"DFTB+ exited unexpectedly with exitcode: {exit_code}\n" ) except DFTBError as error: self.log.exception(error) raise else: self.log.debug( f"DFTB+ exited successfully. Exit code: {exit_code}") if optimize: # noinspection PyTypeChecker parser = DeMonNanoParser(out_fname, natoms=Natoms, logger=self.log) result_dict = parser.parse_DeMonNano_output() new_coords = result_dict["coordinates"] cluster.set_particle_positions( (new_coords, molecule_ids, atom_labels)) else: # noinspection PyTypeChecker parser = DeMonNanoParser(out_fname, natoms=Natoms, geometry_opt=False, logger=self.log) result_dict = parser.parse_DeMonNano_output() energy = result_dict["energy"] cluster.cost = energy # os.chdir("..") # Come back out of the scratch dir. return cluster