def fit_force_and_energy(self, confs, forces, glob_confs, energies, ncores=1): """ Fit the GP to a set of training energies using a 2- and 3-body single species force-force, energy-energy, and energy-forces kernel functions. The 2-body Gaussian process is first fitted, then the 3-body GP is fitted to the difference between the training energies (and forces) and the 2-body predictions of energies (and forces) on the training configurations. Args: confs (list): List of M x 5 arrays containing coordinates and atomic numbers of atoms within a cutoff from the central one forces (array) : Array containing the vector forces on the central atoms of the training configurations glob_confs (list of lists): List of configurations arranged so that grouped configurations belong to the same snapshot energies (array) : Array containing the total energy of each snapshot ncores (int): number of CPUs to use for the gram matrix evaluation """ hypotetical_model_name = "models/MODEL_ker_TwoBodySingleSpecies_ntr_%i.json" %(len(energies)+len(forces)) try: model_2b = models.TwoBodySingleSpeciesModel.from_json(hypotetical_model_name) self.rep_sig = model_2b.rep_sig self.gp_2b = model_2b.gp if self.rep_sig: self.rep_energies = utility.get_repulsive_energies( glob_confs, self.rep_sig) energies -= self.rep_energies self.rep_forces = utility.get_repulsive_forces(confs, self.rep_sig) forces -= self.rep_forces print("Loaded 2-body model to bootstart training") except: if self.rep_sig: self.rep_sig = utility.find_repulstion_sigma(confs) self.rep_energies = utility.get_repulsive_energies( glob_confs, self.rep_sig) energies -= self.rep_energies self.rep_forces = utility.get_repulsive_forces(confs, self.rep_sig) forces -= self.rep_forces self.gp_2b.fit_force_and_energy( confs, forces, glob_confs, energies, ncores=ncores) two_body_forces = self.gp_2b.predict(confs, ncores=ncores) two_body_energies = self.gp_2b.predict_energy( glob_confs, ncores=ncores) self.gp_3b.fit_force_and_energy( confs, forces - two_body_forces, glob_confs, energies - two_body_energies, ncores=ncores)
def fit_force_and_energy(self, confs, forces, glob_confs, energies, ncores=1): """ Fit the GP to a set of training forces and energies using 2-body single species force-force, energy-force and energy-energy kernels Args: confs (list): List of M x 5 arrays containing coordinates and atomic numbers of atoms within a cutoff from the central one forces (array) : Array containing the vector forces on the central atoms of the training configurations glob_confs (list of lists): List of configurations arranged so that grouped configurations belong to the same snapshot energies (array) : Array containing the total energy of each snapshot ncores (int): number of CPUs to use for the gram matrix evaluation """ if self.rep_sig: self.rep_sig = utility.find_repulstion_sigma(confs) self.rep_energies = utility.get_repulsive_energies( glob_confs, self.rep_sig) energies -= self.rep_energies self.rep_forces = utility.get_repulsive_forces(confs, self.rep_sig) forces -= self.rep_forces self.gp.fit_force_and_energy(confs, forces, glob_confs, energies, ncores=ncores)
def build_grid(self, start, num, ncores=1): """ Build the mapped 2-body potential. Calculates the energy predicted by the GP for two atoms at distances that range from start to r_cut, for a total of num points. These energies are stored and a 1D spline interpolation is created, which can be used to predict the energy and, through its analytic derivative, the force associated to any couple of atoms. The total force or local energy can then be calculated for any atom by summing the pairwise contributions of every other atom within a cutoff distance r_cut. The prediction is done by the ``calculator`` module which is built to work within the ase python package. Args: start (float): smallest interatomic distance for which the energy is predicted by the GP and stored inn the 2-body mapped potential num (int): number of points to use in the grid of the mapped potential """ self.grid_start = start self.grid_num = num dists = np.linspace(start, self.r_cut, num) confs = np.zeros((num, 1, 5)) confs[:, 0, 0] = dists confs[:, 0, 3], confs[:, 0, 4] = self.element, self.element confs = list(confs) grid_data = self.gp.predict_energy(confs, ncores=ncores, mapping=True) if self.rep_sig: grid_data += utility.get_repulsive_energies(confs, self.rep_sig, mapping=True) self.grid = interpolation.Spline1D(dists, grid_data)
def predict_energy(self, glob_confs, return_std=False, ncores=1): """ Predict the local energies of the central atoms of confs using the 2- and 3-body GPs. The total force is the sum of the two predictions. Args: glob_confs (list of lists): List of configurations arranged so that grouped configurations belong to the same snapshot return_std (bool): if True, returns the standard deviation associated to predictions according to the GP framework Returns: energies (array) : Array containing the total energy of each snapshot energies_errors (array): errors associated to the energies predictions, returned only if return_std is True """ if return_std: if self.rep_sig: rep_energies = utility.get_repulsive_energies( glob_confs, self.rep_sig) force_2b, std_2b = self.gp_2b.predict_energy( glob_confs, return_std, ncores=ncores) energy_2b += rep_energies else: energy_2b, std_2b = self.gp_2b.predict_energy( glob_confs, return_std, ncores=ncores) energy_3b, std_3b = self.gp_2b.predict_energy( glob_confs, return_std, ncores=ncores) return energy_2b + energy_3b, std_2b + std_3b else: if self.rep_sig: rep_energies = utility.get_repulsive_energies( glob_confs, self.rep_sig) return self.gp_2b.predict_energy(glob_confs, return_std, ncores=ncores) + rep_energies +\ self.gp_3b.predict_energy( glob_confs, return_std, ncores=ncores) else: return self.gp_2b.predict_energy(glob_confs, return_std, ncores=ncores) + \ self.gp_3b.predict_energy( glob_confs, return_std, ncores=ncores)
def fit_energy(self, glob_confs, energies, ncores=1): """ Fit the GP to a set of training energies using a 2- and 3-body single species energy-energy kernel functions. The 2-body Gaussian process is first fitted, then the 3-body GP is fitted to the difference between the training energies and the 2-body predictions of energies on the training configurations. Args: glob_confs (list of lists): List of configurations arranged so that grouped configurations belong to the same snapshot energies (array) : Array containing the total energy of each snapshot ncores (int): number of CPUs to use for the gram matrix evaluation """ hypotetical_model_name = "models/MODEL_ker_TwoBodyManySpecies_ntr_%i.json" %(len(energies)) try: model_2b = models.TwoBodyManySpeciesModel.from_json(hypotetical_model_name) self.rep_sig = model_2b.rep_sig self.gp_2b = model_2b.gp if self.rep_sig: self.rep_energies = utility.get_repulsive_energies( glob_confs, self.rep_sig) energies -= self.rep_energies print("Loaded 2-body model to bootstart training") except: if self.rep_sig: self.rep_sig = utility.find_repulstion_sigma(glob_confs) self.rep_energies = utility.get_repulsive_energies( glob_confs, self.rep_sig) energies -= self.rep_energies self.gp_2b.fit_energy(glob_confs, energies, ncores=1) ntr = len(glob_confs) two_body_energies = self.gp_2b.predict_energy( glob_confs, ncores=ncores) self.gp_3b.fit_energy(glob_confs, energies - two_body_energies, ncores=ncores)
def fit_energy(self, glob_confs, energies, ncores=1): """ Fit the GP to a set of training energies using a 2-body single species energy-energy kernel Args: glob_confs (list of lists): List of configurations arranged so that grouped configurations belong to the same snapshot energies (array) : Array containing the total energy of each snapshot ncores (int): number of CPUs to use for the gram matrix evaluation """ if self.rep_sig: self.rep_sig = utility.find_repulstion_sigma(glob_confs) self.rep_energies = utility.get_repulsive_energies( glob_confs, self.rep_sig) energies -= self.rep_energies self.gp.fit_energy(glob_confs, energies, ncores=ncores)
def build_grid(self, start, num_2b, num_3b, ncores=1): """Function used to create the three different 2-body energy grids for atoms of elements 0-0, 0-1, and 1-1, and the four different 3-body energy grids for atoms of elements 0-0-0, 0-0-1, 0-1-1, and 1-1-1. The function calls the ``build_grid_3b`` function for each of the 3-body grids to build. Args: start (float): smallest interatomic distance for which the energy is predicted by the GP and stored inn the 3-body mapped potential num (int): number of points to use in the grid of the 2-body mapped potentials num_3b (int): number of points to use to generate the list of distances used to generate the triplets of atoms for the 3-body mapped potentials ncores (int): number of CPUs to use to calculate the energy predictions """ self.grid_start = start self.grid_num_2b = num_2b self.grid_num_3b = num_2b perm_list_2b = list(combinations_with_replacement(self.elements, 2)) perm_list_3b = list(combinations_with_replacement(self.elements, 3)) dists_2b = np.linspace(start, self.r_cut, num_2b) confs_2b = np.zeros((num_2b, 1, 5)) confs_2b[:, 0, 0] = dists_2b for pair in perm_list_2b: # in this for loop, predicting then save for each individual one confs_2b[:, 0, 3], confs_2b[:, 0, 4] = pair[0], pair[1] mapped_energies = self.gp_2b.predict_energy( list(confs_2b), ncores=ncores, mapping=True) if self.rep_sig: mapped_energies += utility.get_repulsive_energies( confs_2b, self.rep_sig, mapping=True) self.grid_2b[pair] = interpolation.Spline1D(dists_2b, mapped_energies) dists_3b = np.linspace(start, self.r_cut, num_3b) for trip in perm_list_3b: self.grid_3b[trip] = self.build_grid_3b( dists_3b, trip[0], trip[1], trip[2], ncores = ncores)
def predict_energy(self, glob_confs, return_std=False, ncores=1): """ Predict the global energies of the central atoms of confs using a GP Args: glob_confs (list of lists): List of configurations arranged so that grouped configurations belong to the same snapshot return_std (bool): if True, returns the standard deviation associated to predictions according to the GP framework Returns: energies (array) : Array containing the total energy of each snapshot energies_errors (array): errors associated to the energies predictions, returned only if return_std is True """ if self.rep_sig: rep_energies = utility.get_repulsive_energies( glob_confs, self.rep_sig) return self.gp.predict_energy( glob_confs, return_std, ncores=ncores) + rep_energies else: return self.gp.predict_energy(glob_confs, return_std, ncores=ncores)
def build_grid(self, start, num_2b, num_3b, ncores=1): """ Build the mapped 2- and 3-body potentials. Calculates the energy predicted by the GP for two and three atoms at all possible combination of num distances ranging from start to r_cut. The energy for the 3-body mapped grid is calculated only for ``valid`` triplets of atoms, i.e. sets of three distances which form a triangle (this is checked via the triangle inequality). The grid building exploits all the permutation invariances to reduce the number of energy calculations needed to fill the grid. The computed 2-body energies are stored in an array of values, and a 1D spline interpolation is created. The computed 3-body energies are stored in a 3D cube of values, and a 3D spline interpolation is created. The total force or local energy can then be calculated for any atom by summing the pairwise and triplet contributions of every valid couple and triplet of atoms of which one is always the central one. The prediction is done by the ``calculator`` module, which is built to work within the ase python package. Args: start (float): smallest interatomic distance for which the energy is predicted by the GP and stored inn the 3-body mapped potential num_2b (int):number of points to use in the grid of the 2-body mapped potential num_3b (int): number of points to use to generate the list of distances used to generate the triplets of atoms for the 2-body mapped potential ncores (int): number of CPUs to use to calculate the energy predictions """ dists_2b = np.linspace(start, self.r_cut, num_2b) confs = np.zeros((num_2b, 1, 5)) confs[:, 0, 0] = dists_2b confs[:, 0, 3], confs[:, 0, 4] = self.element, self.element grid_data = self.gp_2b.predict_energy( confs, ncores=ncores, mapping=True) if self.rep_sig: grid_data += utility.get_repulsive_energies( confs, self.rep_sig, mapping=True) grid_2b = interpolation.Spline1D(dists_2b, grid_data) # Mapping 3 body part dists_3b = np.linspace(start, self.r_cut, num_3b) inds, r_ij_x, r_ki_x, r_ki_y = self.generate_triplets(dists_3b) confs = np.zeros((len(r_ij_x), 2, 5)) confs[:, 0, 0] = r_ij_x # Element on the x axis confs[:, 1, 0] = r_ki_x # Reshape into confs shape: this is x2 confs[:, 1, 1] = r_ki_y # Reshape into confs shape: this is y2 # Permutations of elements confs[:, :, 3] = self.element # Central element is always element 1 # Element on the x axis is always element 2 confs[:, 0, 4] = self.element # Element on the xy plane is always element 3 confs[:, 1, 4] = self.element grid_3b = np.zeros((num_3b, num_3b, num_3b)) grid_3b[inds] = self.gp_3b.predict_energy( confs, ncores=ncores, mapping=True).flatten() for ind_i in range(num_3b): for ind_j in range(ind_i + 1): for ind_k in range(ind_j + 1): grid_3b[ind_i, ind_k, ind_j] = grid_3b[ind_i, ind_j, ind_k] grid_3b[ind_j, ind_i, ind_k] = grid_3b[ind_i, ind_j, ind_k] grid_3b[ind_j, ind_k, ind_i] = grid_3b[ind_i, ind_j, ind_k] grid_3b[ind_k, ind_i, ind_j] = grid_3b[ind_i, ind_j, ind_k] grid_3b[ind_k, ind_j, ind_i] = grid_3b[ind_i, ind_j, ind_k] grid_3b = interpolation.Spline3D(dists_3b, dists_3b, dists_3b, grid_3b) self.grid_2b = grid_2b self.grid_3b = grid_3b self.grid_num_2b = num_2b self.grid_num_3b = num_3b self.grid_start = start