def run_pyace_fit(self, bbasisconfig: BBasisConfiguration, dataframe: pd.DataFrame, loss_spec: LossFunctionSpecification, fit_config: Dict) -> BBasisConfiguration: parallel_mode = self.backend_config.get(BACKEND_PARALLEL_MODE_KW) or "serial" batch_size = len(dataframe) log.info("Loss function specification: " + str(loss_spec)) display_step = self.backend_config.get('display_step', 20) self.fitter = PyACEFit(basis=bbasisconfig, loss_spec=loss_spec, executors_kw_args=dict(parallel_mode=parallel_mode, batch_size=batch_size, n_workers=self.backend_config.get(BACKEND_NWORKERS_KW, None) ), seed=42, display_step=display_step) maxiter = fit_config.get(FIT_NITER_KW, 100) fit_options = fit_config.get(FIT_OPTIONS_KW, {}) options = {"maxiter": maxiter, "disp": True} options.update(fit_options) self.fitter.fit(structures_dataframe=dataframe, method=fit_config[FIT_OPTIMIZER_KW], options=options, callback=self._callback) # TODO: options=self.fit_config[FIT_OPTIMIZER_OPTIONS_KW] self.res_opt = self.fitter.res_opt new_bbasisconf = self.fitter.bbasis_opt.to_BBasisConfiguration() bbasisconfig.set_all_coeffs(new_bbasisconf.get_all_coeffs()) return bbasisconfig
def construct_bbasisconfiguration( potential_config: Dict) -> BBasisConfiguration: assert len(potential_config[POTENTIAL_NRADMAX_KW]) == potential_config[POTENTIAL_RANKMAX_KW], \ "Length of the {} do not match {}".format(POTENTIAL_NRADMAX_KW, POTENTIAL_RANKMAX_KW) assert len(potential_config[POTENTIAL_LMAX_KW]) == potential_config[POTENTIAL_RANKMAX_KW], \ "Length of the {} do not match {}".format(POTENTIAL_LMAX_KW, POTENTIAL_RANKMAX_KW) block = BBasisFunctionsSpecificationBlock() block.block_name = potential_config[POTENTIAL_ELEMENT_KW] # "Al" block.elements_vec = [potential_config[POTENTIAL_ELEMENT_KW]] # ["Al"] if potential_config[POTENTIAL_RANKMAX_KW] == 1: block.nradmaxi = 0 else: block.nradmaxi = np.max(potential_config[POTENTIAL_NRADMAX_KW][1:]) block.lmaxi = np.max(potential_config[POTENTIAL_LMAX_KW]) block.npoti = potential_config[POTENTIAL_NPOT_KW] # "FinnisSinclair" block.fs_parameters = potential_config[ POTENTIAL_FS_PARAMETERS_KW] # [1, 1, 1, 0.5] block.rcutij = potential_config[POTENTIAL_RCUT_KW] block.dcutij = potential_config[POTENTIAL_DCUT_KW] # 0.01 block.NameOfCutoffFunctionij = potential_config[ POTENTIAL_CUTOFF_FUNCTION_KW] # "cos" block.nradbaseij = potential_config[POTENTIAL_NRADMAX_KW][0] block.radbase = potential_config[POTENTIAL_RADBASE_KW] # "ChebExpCos" block.radparameters = potential_config[ POTENTIAL_RADPARAMETERS_KW] # ex lmbda if POTENTIAL_RADCOEFFICIENTS_KW not in potential_config: # crad shape [nelements][nelements][nradial][lmax + 1][nradbase] -> Kronecker delta (nrad=nbase) lmaxp1 = block.lmaxi + 1 nmax = block.nradmaxi kmax = block.nradbaseij crad = np.zeros((nmax, lmaxp1, kmax)) for n in range(nmax): crad[n, :, n] = 1.0 else: crad = potential_config[POTENTIAL_RADCOEFFICIENTS_KW] block.radcoefficients = crad block.funcspecs = prepare_bbasisfuncspecifications(potential_config) if "core-repulsion" in potential_config: block.core_rep_parameters = potential_config["core-repulsion"] if "rho_core_cut" in potential_config: block.rho_cut = potential_config["rho_core_cut"] if "drho_core_cut" in potential_config: block.drho_cut = potential_config["drho_core_cut"] basis_configuration = BBasisConfiguration() basis_configuration.deltaSplineBins = potential_config[ POTENTIAL_DELTASPLINEBINS_KW] # 0.001 basis_configuration.funcspecs_blocks = [block] if POTENTIAL_METADATA_KW in potential_config: for k, v in potential_config[POTENTIAL_METADATA_KW].items(): basis_configuration.metadata[k] = v return basis_configuration
def save_interim_potential(basis_config: BBasisConfiguration, coeffs=None, potential_filename="interim_potential.yaml", verbose=True): if coeffs is not None: basis_config = basis_config.copy() safely_update_bbasisconfiguration_coefficients(coeffs, basis_config) basis_config.metadata["intermediate_time"] = str(datetime.now()) basis_config.save(potential_filename) if verbose: log.info('Intermediate potential saved in {}'.format(potential_filename))
def run_tensorpot_fit(self, bbasisconfig: BBasisConfiguration, dataframe: pd.DataFrame, loss_spec: LossFunctionSpecification, fit_config: Dict) -> BBasisConfiguration: from tensorpotential.potentials.ace import ACE from tensorpotential.tensorpot import TensorPotential from tensorpotential.fit import FitTensorPotential from tensorpotential.utils.utilities import batching_data from tensorpotential.constants import (LOSS_TYPE, LOSS_FORCE_FACTOR, LOSS_ENERGY_FACTOR, L1_REG, L2_REG, AUX_LOSS_FACTOR) batch_size = self.backend_config.get(BACKEND_BATCH_SIZE_KW, 10) log.info("Loss function specification: " + str(loss_spec)) log.info("Batch size: {}".format(batch_size)) batches = batching_data(dataframe, batch_size=batch_size) # max_bytes = self.adjust_batch_size(dataframe, bbasisconfig, ini_batch_size=batch_size) n_batches = len(batches) if loss_spec.w1_coeffs != 1.0 or loss_spec.w2_coeffs != 1.0: log.warning("WARNING! 'w1_coeffs'={} and 'w2_coeffs'={} in loss function will be ignored". format(loss_spec.w1_coeffs, loss_spec.w2_coeffs)) loss_force_factor = loss_spec.kappa if (np.array([loss_spec.w0_rad, loss_spec.w1_rad, loss_spec.w2_rad]) != 0).any(): ace_potential = ACE(bbasisconfig, compute_smoothness=True) tensorpotential = TensorPotential(ace_potential,loss_specs={ LOSS_TYPE: 'per-atom', LOSS_FORCE_FACTOR: loss_force_factor, LOSS_ENERGY_FACTOR: (1-loss_force_factor), L1_REG: np.float64(loss_spec.L1_coeffs) / n_batches, L2_REG: np.float64(loss_spec.L2_coeffs) / n_batches, AUX_LOSS_FACTOR: [np.float64(loss_spec.w0_rad) / n_batches, np.float64(loss_spec.w1_rad) / n_batches, np.float64(loss_spec.w2_rad) / n_batches]}) else: ace_potential = ACE(bbasisconfig, compute_smoothness=False) tensorpotential = TensorPotential(ace_potential, loss_specs={ LOSS_TYPE: 'per-atom', LOSS_FORCE_FACTOR: loss_force_factor, LOSS_ENERGY_FACTOR: (1 - loss_force_factor), L1_REG: np.float64(loss_spec.L1_coeffs) / n_batches, L2_REG: np.float64(loss_spec.L2_coeffs) / n_batches}) display_step = self.backend_config.get('display_step', 20) self.fitter = FitTensorPotential(tensorpotential, display_step=display_step) fit_options = fit_config.get(FIT_OPTIONS_KW, None) self.fitter.fit(dataframe, niter=fit_config[FIT_NITER_KW], optimizer=fit_config[FIT_OPTIMIZER_KW], batch_size=batch_size, jacobian_factor=None, callback=self._callback, options=fit_options) self.res_opt = self.fitter.res_opt coeffs = self.fitter.get_fitted_coefficients() bbasisconfig.set_all_coeffs(coeffs) return bbasisconfig
def callback_hook(self, coeffs, basis_config: BBasisConfiguration, current_fit_cycle: int, current_ladder_step: int): # TODO add a list of callbacks basis_config = basis_config.copy() safely_update_bbasisconfiguration_coefficients(coeffs, basis_config) for callback in self.callbacks: callback( basis_config=basis_config, current_fit_iteration=self.current_fit_iteration, current_fit_cycle=current_fit_cycle, current_ladder_step=current_ladder_step, ) self.current_fit_iteration += 1
def __init__(self, potential_config: Union[Dict, str, BBasisConfiguration, ACEBBasisSet], fit_config: Dict, data_config: Union[Dict, pd.DataFrame], backend_config: Dict, cutoff=None, seed=None, callbacks=None ): self.seed = seed if self.seed is not None: log.info("Set numpy random seed to {}".format(self.seed)) np.random.seed(self.seed) self.callbacks = [save_interim_potential_callback] if callbacks is not None: if isinstance(callbacks, (list, tuple)): for c in callbacks: if isinstance(c, Callable): self.callbacks.append(c) log.info("{} callback added".format(c)) elif isinstance(c, str): log.info("") c = active_import(c) self.callbacks.append(c) log.info("{} callback added".format(c)) else: raise ValueError( "'callbacks' should be list/tuple of importable function name or function with signature: callback" + "(coeffs, bbasisconfig: BBasisConfiguration, current_fit_cycle: int, current_ladder_step: int). " + "But got: {}".format(callbacks) ) self.current_fit_iteration = 0 self.ladder_scheme = False self.ladder_type = 'body_order' self.initial_bbasisconfig = None if isinstance(potential_config, dict): self.target_bbasisconfig = construct_bbasisconfiguration(potential_config) log.info("Target potential shape constructed from dictionary, it contains {} functions".format( self.target_bbasisconfig.total_number_of_functions)) if POTENTIAL_INITIAL_POTENTIAL_KW in potential_config: start_potential = potential_config[POTENTIAL_INITIAL_POTENTIAL_KW] if isinstance(start_potential, str): self.initial_bbasisconfig = BBasisConfiguration(start_potential) elif isinstance(start_potential, BBasisConfiguration): self.initial_bbasisconfig = start_potential else: raise ValueError("potential_config[`{}`] is neither str nor BBasisConfiguration".format( POTENTIAL_INITIAL_POTENTIAL_KW)) self.ladder_scheme = True log.info("Initial potential provided: {}, it contains {} functions".format(start_potential, self.initial_bbasisconfig.total_number_of_functions)) log.info("Ladder-scheme fitting is ON") elif FIT_LADDER_STEP_KW in fit_config: self.ladder_scheme = True self.initial_bbasisconfig = self.target_bbasisconfig.copy() for block in self.initial_bbasisconfig.funcspecs_blocks: block.lmaxi = 0 block.nradmaxi = 0 block.nradbaseij = 0 block.radcoefficients = [[[]]] block.funcspecs = [] log.info("Ladder-scheme fitting is ON") log.info("Initial potential is NOT provided, starting from empty potential") elif isinstance(potential_config, str): self.target_bbasisconfig = BBasisConfiguration(potential_config) log.info("Target potential loaded from file '{}'".format(potential_config)) elif isinstance(potential_config, BBasisConfiguration): self.target_bbasisconfig = potential_config log.info("Target potential provided as `BBasisConfiguration` object") elif isinstance(potential_config, ACEBBasisSet): self.target_bbasisconfig = potential_config.to_BBasisConfiguration() log.info("Target potential provided as `ACEBBasisSet` object") else: raise ValueError( ("Non-supported type: {}. Only dictionary (configuration), " + "str (YAML file name) or BBasisConfiguration are supported").format( type(potential_config))) # TODO: hardcoded if cutoff is None: self.cutoff = self.target_bbasisconfig.funcspecs_blocks[0].rcutij else: self.cutoff = cutoff if self.ladder_scheme: if FIT_LADDER_TYPE_KW in fit_config: self.ladder_type = str(fit_config[FIT_LADDER_TYPE_KW]) log.info("Ladder_type: {} is selected".format(self.ladder_type)) self.fit_config = fit_config if FIT_OPTIMIZER_KW not in self.fit_config: self.fit_config[FIT_OPTIMIZER_KW] = "BFGS" log.warning("'{}' is not provided, switch to default value: {}".format(FIT_OPTIMIZER_KW, self.fit_config[FIT_OPTIMIZER_KW])) if FIT_NITER_KW not in self.fit_config: self.fit_config[FIT_NITER_KW] = 100 log.warning( "'{}' is not provided, switch to default value: {}".format(FIT_NITER_KW, self.fit_config[FIT_NITER_KW])) if FIT_OPTIONS_KW in self.fit_config: log.info( "optimizer options are provided: '{}'".format(self.fit_config[FIT_OPTIONS_KW])) self.data_config = data_config self.weighting_policy_spec = self.fit_config.get(FIT_WEIGHTING_KW) self.fit_backend = FitBackendAdapter(backend_config) self.evaluator_name = self.fit_backend.evaluator_name set_general_metadata(self.target_bbasisconfig) if isinstance(self.data_config, (dict, str)): self.fitting_data = get_fitting_dataset(evaluator_name=self.evaluator_name, data_config=self.data_config, weighting_policy_spec=self.weighting_policy_spec, cutoff=self.cutoff ) elif isinstance(self.data_config, pd.DataFrame): self.fitting_data = self.data_config else: raise ValueError("'data-config' should be dictionary or pd.DataFrame") self.save_fitting_data_info() self.loss_spec = LossFunctionSpecification(**self.fit_config.get(FIT_LOSS_KW, {}))
class GeneralACEFit: """ Main fitting wrapper class :param potential_config: specification of the potential - configuration dictionary - YAML with BBasisConfiguration potential configuration - BBasisConfiguration - ACEBBasisSet :param fit_config: specification of fitting (loss function, number of iterations, weighting policy, ...) :param data_config: training data specification :param backend_config: specification of potential evaluation and fitting backend (pyace / tensorpot) - dict ['evaluator'] """ def __init__(self, potential_config: Union[Dict, str, BBasisConfiguration, ACEBBasisSet], fit_config: Dict, data_config: Union[Dict, pd.DataFrame], backend_config: Dict, cutoff=None, seed=None, callbacks=None ): self.seed = seed if self.seed is not None: log.info("Set numpy random seed to {}".format(self.seed)) np.random.seed(self.seed) self.callbacks = [save_interim_potential_callback] if callbacks is not None: if isinstance(callbacks, (list, tuple)): for c in callbacks: if isinstance(c, Callable): self.callbacks.append(c) log.info("{} callback added".format(c)) elif isinstance(c, str): log.info("") c = active_import(c) self.callbacks.append(c) log.info("{} callback added".format(c)) else: raise ValueError( "'callbacks' should be list/tuple of importable function name or function with signature: callback" + "(coeffs, bbasisconfig: BBasisConfiguration, current_fit_cycle: int, current_ladder_step: int). " + "But got: {}".format(callbacks) ) self.current_fit_iteration = 0 self.ladder_scheme = False self.ladder_type = 'body_order' self.initial_bbasisconfig = None if isinstance(potential_config, dict): self.target_bbasisconfig = construct_bbasisconfiguration(potential_config) log.info("Target potential shape constructed from dictionary, it contains {} functions".format( self.target_bbasisconfig.total_number_of_functions)) if POTENTIAL_INITIAL_POTENTIAL_KW in potential_config: start_potential = potential_config[POTENTIAL_INITIAL_POTENTIAL_KW] if isinstance(start_potential, str): self.initial_bbasisconfig = BBasisConfiguration(start_potential) elif isinstance(start_potential, BBasisConfiguration): self.initial_bbasisconfig = start_potential else: raise ValueError("potential_config[`{}`] is neither str nor BBasisConfiguration".format( POTENTIAL_INITIAL_POTENTIAL_KW)) self.ladder_scheme = True log.info("Initial potential provided: {}, it contains {} functions".format(start_potential, self.initial_bbasisconfig.total_number_of_functions)) log.info("Ladder-scheme fitting is ON") elif FIT_LADDER_STEP_KW in fit_config: self.ladder_scheme = True self.initial_bbasisconfig = self.target_bbasisconfig.copy() for block in self.initial_bbasisconfig.funcspecs_blocks: block.lmaxi = 0 block.nradmaxi = 0 block.nradbaseij = 0 block.radcoefficients = [[[]]] block.funcspecs = [] log.info("Ladder-scheme fitting is ON") log.info("Initial potential is NOT provided, starting from empty potential") elif isinstance(potential_config, str): self.target_bbasisconfig = BBasisConfiguration(potential_config) log.info("Target potential loaded from file '{}'".format(potential_config)) elif isinstance(potential_config, BBasisConfiguration): self.target_bbasisconfig = potential_config log.info("Target potential provided as `BBasisConfiguration` object") elif isinstance(potential_config, ACEBBasisSet): self.target_bbasisconfig = potential_config.to_BBasisConfiguration() log.info("Target potential provided as `ACEBBasisSet` object") else: raise ValueError( ("Non-supported type: {}. Only dictionary (configuration), " + "str (YAML file name) or BBasisConfiguration are supported").format( type(potential_config))) # TODO: hardcoded if cutoff is None: self.cutoff = self.target_bbasisconfig.funcspecs_blocks[0].rcutij else: self.cutoff = cutoff if self.ladder_scheme: if FIT_LADDER_TYPE_KW in fit_config: self.ladder_type = str(fit_config[FIT_LADDER_TYPE_KW]) log.info("Ladder_type: {} is selected".format(self.ladder_type)) self.fit_config = fit_config if FIT_OPTIMIZER_KW not in self.fit_config: self.fit_config[FIT_OPTIMIZER_KW] = "BFGS" log.warning("'{}' is not provided, switch to default value: {}".format(FIT_OPTIMIZER_KW, self.fit_config[FIT_OPTIMIZER_KW])) if FIT_NITER_KW not in self.fit_config: self.fit_config[FIT_NITER_KW] = 100 log.warning( "'{}' is not provided, switch to default value: {}".format(FIT_NITER_KW, self.fit_config[FIT_NITER_KW])) if FIT_OPTIONS_KW in self.fit_config: log.info( "optimizer options are provided: '{}'".format(self.fit_config[FIT_OPTIONS_KW])) self.data_config = data_config self.weighting_policy_spec = self.fit_config.get(FIT_WEIGHTING_KW) self.fit_backend = FitBackendAdapter(backend_config) self.evaluator_name = self.fit_backend.evaluator_name set_general_metadata(self.target_bbasisconfig) if isinstance(self.data_config, (dict, str)): self.fitting_data = get_fitting_dataset(evaluator_name=self.evaluator_name, data_config=self.data_config, weighting_policy_spec=self.weighting_policy_spec, cutoff=self.cutoff ) elif isinstance(self.data_config, pd.DataFrame): self.fitting_data = self.data_config else: raise ValueError("'data-config' should be dictionary or pd.DataFrame") self.save_fitting_data_info() self.loss_spec = LossFunctionSpecification(**self.fit_config.get(FIT_LOSS_KW, {})) def save_fitting_data_info(self): # columns to save: w_energy, w_forces, NUMBER_OF_ATOMS, PROTOTYPE_NAME, prop_id,structure_id, gen_id, if any columns_to_save = ["PROTOTYPE_NAME", "NUMBER_OF_ATOMS", "prop_id", "structure_id", "gen_id", "pbc"] + \ [ENERGY_CORRECTED_COL, EWEIGHTS_COL, FWEIGHTS_COL] fitting_data_columns = self.fitting_data.columns columns_to_save = [col for col in columns_to_save if col in fitting_data_columns] self.fitting_data[columns_to_save].to_csv(FITTING_DATA_INFO_FILENAME, index=None, sep=",") log.info("Fitting dataset info saved into {}".format(FITTING_DATA_INFO_FILENAME)) def fit(self) -> BBasisConfiguration: gc.collect() self.target_bbasisconfig.save(INITIAL_POTENTIAL_YAML) log.info("Fitting dataset size: {} structures / {} atoms".format(len(self.fitting_data), self.fitting_data["NUMBER_OF_ATOMS"].sum())) if not self.ladder_scheme: # normal "non-ladder" fit log.info("'Single-shot' fitting") self.target_bbasisconfig = self.cycle_fitting(self.target_bbasisconfig, current_ladder_step=0) else: # ladder scheme log.info("'Ladder-scheme' fitting") self.target_bbasisconfig = self.ladder_fitting(self.initial_bbasisconfig, self.target_bbasisconfig) log.info("Fitting done") return self.target_bbasisconfig def ladder_fitting(self, initial_config, target_config): total_number_of_funcs = target_config.total_number_of_functions ladder_step_param = self.fit_config.get(FIT_LADDER_STEP_KW, 0.1) current_bbasisconfig = initial_config.copy() current_ladder_step = 0 while True: prev_func_num = current_bbasisconfig.total_number_of_functions log.info("Current basis set size: {} B-functions".format(prev_func_num)) ladder_step = get_actual_ladder_step(ladder_step_param, prev_func_num, total_number_of_funcs) log.info("Ladder step size: {}".format(ladder_step)) current_bbasisconfig = extend_basis(current_bbasisconfig, target_config, self.ladder_type, ladder_step) new_func_num = current_bbasisconfig.total_number_of_functions log.info("Extended basis set size: {} B-functions".format(new_func_num)) if prev_func_num == new_func_num: log.info("No new function added after basis extension. Stopping") break current_bbasisconfig = self.cycle_fitting(current_bbasisconfig, current_ladder_step=current_ladder_step) if "_fit_cycles" in current_bbasisconfig.metadata: del current_bbasisconfig.metadata["_fit_cycles"] log.debug("Update metadata: {}".format(current_bbasisconfig.metadata)) self.fit_backend.fitter.metrics.print_extended_metrics(self.fit_backend.fitter.iter_num, float(self.fit_backend.fitter.last_loss), self.fit_backend.fitter.get_reg_components(), self.fit_backend.fitter.get_reg_weights(), title='LADDER STEP', nfuncs=new_func_num) # save ladder potential ladder_final_potential_filename = "interim_potential_ladder_step_{}.yaml".format(current_ladder_step) save_interim_potential(current_bbasisconfig, current_bbasisconfig.get_all_coeffs(), potential_filename=ladder_final_potential_filename) current_ladder_step += 1 return current_bbasisconfig def cycle_fitting(self, bbasisconfig: BBasisConfiguration, current_ladder_step: int = 0) -> BBasisConfiguration: current_bbasisconfig = bbasisconfig.copy() log.info('Cycle fitting loop') fit_cycles = int(self.fit_config.get(FIT_FIT_CYCLES_KW, 1)) noise_rel_sigma = float(self.fit_config.get(FIT_NOISE_REL_SIGMA, 0)) noise_abs_sigma = float(self.fit_config.get(FIT_NOISE_ABS_SIGMA, 0)) if "_" + FIT_FIT_CYCLES_KW in current_bbasisconfig.metadata: finished_fit_cycles = int(current_bbasisconfig.metadata["_" + FIT_FIT_CYCLES_KW]) else: finished_fit_cycles = 0 if finished_fit_cycles >= fit_cycles: log.warning( ("Number of finished fit cycles ({}) >= number of expected fit cycles ({}). " + "Use another potential or remove `{}` from potential metadata") .format(finished_fit_cycles, fit_cycles, "_" + FIT_FIT_CYCLES_KW)) return current_bbasisconfig fitting_attempts_list = [] while finished_fit_cycles < fit_cycles: current_fit_cycle = finished_fit_cycles + 1 log.info("Number of fit attempts: {}/{}".format(current_fit_cycle, fit_cycles)) num_of_functions = current_bbasisconfig.total_number_of_functions num_of_parameters = len(current_bbasisconfig.get_all_coeffs()) log.info("Total number of functions: {} / number of parameters: {}".format(num_of_functions, num_of_parameters)) log.info("Running fit backend") self.current_fit_iteration = 0 current_bbasisconfig = self.fit_backend.fit( current_bbasisconfig, dataframe=self.fitting_data, loss_spec=self.loss_spec, fit_config=self.fit_config, callback=partial(self.callback_hook, basis_config=bbasisconfig, current_fit_cycle=current_fit_cycle, current_ladder_step=current_ladder_step) ) log.info("Fitting cycle finished, final statistic:") self.fit_backend.print_detailed_metrics(prefix='Last iteration:') finished_fit_cycles = current_fit_cycle current_bbasisconfig.metadata["_" + FIT_FIT_CYCLES_KW] = str(finished_fit_cycles) current_bbasisconfig.metadata["_" + FIT_LOSS_KW] = str(self.fit_backend.res_opt.fun) log.debug("Update current_bbasisconfig.metadata = {}".format(current_bbasisconfig.metadata)) fitting_attempts_list.append((np.sum(self.fit_backend.res_opt.fun), current_bbasisconfig.copy())) # select current_bbasisconfig as a best among all previous best_ind = np.argmin([v[0] for v in fitting_attempts_list]) log.info( "Select best fit #{} among all available ({})".format(best_ind + 1, len(fitting_attempts_list))) current_bbasisconfig = fitting_attempts_list[best_ind][1].copy() if finished_fit_cycles < fit_cycles and (noise_rel_sigma > 0) or (noise_abs_sigma > 0): all_coeffs = current_bbasisconfig.get_all_coeffs() noisy_all_coeffs = all_coeffs if noise_rel_sigma > 0: log.info( "Applying Gaussian noise with relative sigma/mean = {:>1.4e} to all optimizable coefficients".format( noise_rel_sigma)) noisy_all_coeffs = apply_noise(all_coeffs, noise_rel_sigma, relative=True) elif noise_abs_sigma > 0: log.info( "Applying Gaussian noise with sigma = {:>1.4e} to all optimizable coefficients".format( noise_abs_sigma)) noisy_all_coeffs = apply_noise(all_coeffs, noise_abs_sigma, relative=False) current_bbasisconfig.set_all_coeffs(noisy_all_coeffs) # chose the best fit attempt among fitting_attempts_list best_fitting_attempts_ind = np.argmin([v[0] for v in fitting_attempts_list]) log.info("Best fitting attempt is #{}".format(best_fitting_attempts_ind + 1)) current_bbasisconfig = fitting_attempts_list[best_fitting_attempts_ind][1] save_interim_potential(current_bbasisconfig) return current_bbasisconfig def save_optimized_potential(self, potential_filename: str = "output_potential.yaml"): if "_" + FIT_FIT_CYCLES_KW in self.target_bbasisconfig.metadata: del self.target_bbasisconfig.metadata["_" + FIT_FIT_CYCLES_KW] log.debug("Update metadata: {}".format(self.target_bbasisconfig.metadata)) self.target_bbasisconfig.save(potential_filename) log.info("Final potential is saved to {}".format(potential_filename)) def callback_hook(self, coeffs, basis_config: BBasisConfiguration, current_fit_cycle: int, current_ladder_step: int): # TODO add a list of callbacks basis_config = basis_config.copy() safely_update_bbasisconfiguration_coefficients(coeffs, basis_config) for callback in self.callbacks: callback( basis_config=basis_config, current_fit_iteration=self.current_fit_iteration, current_fit_cycle=current_fit_cycle, current_ladder_step=current_ladder_step, ) self.current_fit_iteration += 1
def safely_update_bbasisconfiguration_coefficients(coeffs: np.array, config: BBasisConfiguration = None) -> None: current_coeffs = config.get_all_coeffs() for i, c in enumerate(coeffs): current_coeffs[i] = c config.set_all_coeffs(current_coeffs)
def set_general_metadata(bbasisconfig: BBasisConfiguration) -> None: bbasisconfig.metadata[METADATA_STARTTIME_KW] = str(datetime.now()) if get_username() is not None: bbasisconfig.metadata[METADATA_USER_KW] = str(get_username())
def cycle_fitting(self, bbasisconfig: BBasisConfiguration, current_ladder_step: int = 0) -> BBasisConfiguration: current_bbasisconfig = bbasisconfig.copy() log.info('Cycle fitting loop') fit_cycles = int(self.fit_config.get(FIT_FIT_CYCLES_KW, 1)) noise_rel_sigma = float(self.fit_config.get(FIT_NOISE_REL_SIGMA, 0)) noise_abs_sigma = float(self.fit_config.get(FIT_NOISE_ABS_SIGMA, 0)) if "_" + FIT_FIT_CYCLES_KW in current_bbasisconfig.metadata: finished_fit_cycles = int(current_bbasisconfig.metadata["_" + FIT_FIT_CYCLES_KW]) else: finished_fit_cycles = 0 if finished_fit_cycles >= fit_cycles: log.warning( ("Number of finished fit cycles ({}) >= number of expected fit cycles ({}). " + "Use another potential or remove `{}` from potential metadata") .format(finished_fit_cycles, fit_cycles, "_" + FIT_FIT_CYCLES_KW)) return current_bbasisconfig fitting_attempts_list = [] while finished_fit_cycles < fit_cycles: current_fit_cycle = finished_fit_cycles + 1 log.info("Number of fit attempts: {}/{}".format(current_fit_cycle, fit_cycles)) num_of_functions = current_bbasisconfig.total_number_of_functions num_of_parameters = len(current_bbasisconfig.get_all_coeffs()) log.info("Total number of functions: {} / number of parameters: {}".format(num_of_functions, num_of_parameters)) log.info("Running fit backend") self.current_fit_iteration = 0 current_bbasisconfig = self.fit_backend.fit( current_bbasisconfig, dataframe=self.fitting_data, loss_spec=self.loss_spec, fit_config=self.fit_config, callback=partial(self.callback_hook, basis_config=bbasisconfig, current_fit_cycle=current_fit_cycle, current_ladder_step=current_ladder_step) ) log.info("Fitting cycle finished, final statistic:") self.fit_backend.print_detailed_metrics(prefix='Last iteration:') finished_fit_cycles = current_fit_cycle current_bbasisconfig.metadata["_" + FIT_FIT_CYCLES_KW] = str(finished_fit_cycles) current_bbasisconfig.metadata["_" + FIT_LOSS_KW] = str(self.fit_backend.res_opt.fun) log.debug("Update current_bbasisconfig.metadata = {}".format(current_bbasisconfig.metadata)) fitting_attempts_list.append((np.sum(self.fit_backend.res_opt.fun), current_bbasisconfig.copy())) # select current_bbasisconfig as a best among all previous best_ind = np.argmin([v[0] for v in fitting_attempts_list]) log.info( "Select best fit #{} among all available ({})".format(best_ind + 1, len(fitting_attempts_list))) current_bbasisconfig = fitting_attempts_list[best_ind][1].copy() if finished_fit_cycles < fit_cycles and (noise_rel_sigma > 0) or (noise_abs_sigma > 0): all_coeffs = current_bbasisconfig.get_all_coeffs() noisy_all_coeffs = all_coeffs if noise_rel_sigma > 0: log.info( "Applying Gaussian noise with relative sigma/mean = {:>1.4e} to all optimizable coefficients".format( noise_rel_sigma)) noisy_all_coeffs = apply_noise(all_coeffs, noise_rel_sigma, relative=True) elif noise_abs_sigma > 0: log.info( "Applying Gaussian noise with sigma = {:>1.4e} to all optimizable coefficients".format( noise_abs_sigma)) noisy_all_coeffs = apply_noise(all_coeffs, noise_abs_sigma, relative=False) current_bbasisconfig.set_all_coeffs(noisy_all_coeffs) # chose the best fit attempt among fitting_attempts_list best_fitting_attempts_ind = np.argmin([v[0] for v in fitting_attempts_list]) log.info("Best fitting attempt is #{}".format(best_fitting_attempts_ind + 1)) current_bbasisconfig = fitting_attempts_list[best_fitting_attempts_ind][1] save_interim_potential(current_bbasisconfig) return current_bbasisconfig
block.NameOfCutoffFunctionij = "cos" block.nradbaseij = 1 block.radbase = "ChebExpCos" block.radparameters = [3.0] block.radcoefficients = [1] block.funcspecs = [ BBasisFunctionSpecification(["Al", "Al"], ns=[1], ls=[0], LS=[], coeffs=[1.]), # BBasisFunctionSpecification(["Al", "Al", "Al"], ns=[1, 1], ls=[0, 0], LS=[], coeffs=[2]) ] basisConfiguration = BBasisConfiguration() basisConfiguration.deltaSplineBins = 0.001 basisConfiguration.funcspecs_blocks = [block] a = bulk('Al', 'fcc', a=4, cubic=True) a.pbc = False print(a) calc = PyACECalculator(basis_set=basisConfiguration) a.set_calculator(calc) e1 = (a.get_potential_energy()) f1 = a.get_forces() print(e1) print(f1) calc2 = PyACECalculator(basis_set=ACEBBasisSet(basisConfiguration)) a2 = bulk('Al', 'fcc', a=4, cubic=True)
def extend_basis(initial_basis: BBasisConfiguration, final_basis: BBasisConfiguration, ladder_type: str, func_step: int = None) -> BBasisConfiguration: if initial_basis.total_number_of_functions == final_basis.total_number_of_functions: return initial_basis.copy() # grow basis by func_step initial_basis_funcs_list = BasisFuncsList(initial_basis) final_basis_funcs = [] for block in final_basis.funcspecs_blocks: final_basis_funcs += block.funcspecs final_basis_funcs = sort_funcspecs_list(final_basis_funcs, ladder_type) new_func_list = [] existing_func_list = [] skipped_functions = 0 for new_func in final_basis_funcs: if initial_basis_funcs_list.escribed_area_contains(new_func): existing_func = initial_basis_funcs_list.find_existing(new_func) if existing_func is not None: existing_func_list.append( existing_func) # copy with existing coefficients elif initial_basis_funcs_list.at_ns_area_border(new_func): ## ASSUME CORNER CASE NOT A HOLE!!! if initial_basis_funcs_list.is_max_func(new_func): new_func_list.append(new_func) elif ladder_type != 'body_order': new_func_list.append(new_func) else: skipped_functions += 1 # skip, as non corner case # For other types of ladder growth the possibility of having hole is not considered elif ladder_type != 'body_order': new_func_list.append(new_func) else: skipped_functions += 1 # skip, because it is hole else: # add new, green zone new_func_list.append(new_func) log.info("Skipped functions number: {}".format(skipped_functions)) # new_func_list = sort_funcspecs_list(new_func_list, 'std_ranking') if func_step is not None and len(new_func_list) > func_step: new_func_list = new_func_list[:func_step] new_func_list = sort_funcspecs_list(new_func_list, 'body_order') new_basis_config = initial_basis.copy() # TODO: currentlu only single func spec block is assumed new_basis_config.funcspecs_blocks[0].funcspecs = sort_funcspecs_list( existing_func_list + new_func_list, 'body_order') # update nradmax, lmax, nradabse new_nradmax = 0 new_nradbase = 0 new_lmax = 0 new_rankmax = 0 for func in new_basis_config.funcspecs_blocks[0].funcspecs: rank = len(func.ns) new_rankmax = max(rank, new_rankmax) if rank == 1: new_nradbase = max(max(func.ns), new_nradbase) else: new_nradmax = max(max(func.ns), new_nradmax) new_lmax = max(max(func.ls), new_lmax) new_basis_config.funcspecs_blocks[0].lmaxi = new_lmax new_basis_config.funcspecs_blocks[0].nradmaxi = new_nradmax new_basis_config.funcspecs_blocks[0].nradbaseij = new_nradbase # update crad old_crad = np.array(new_basis_config.funcspecs_blocks[0].radcoefficients) new_crad = np.zeros((new_nradmax, new_lmax + 1, new_nradbase)) for n in range(min(new_nradmax, new_nradbase)): new_crad[n, :, n] = 1. # print("old_crad.shape = ", old_crad.shape) # print("new_crad.shape = ", new_crad.shape) if old_crad.shape != (0, ): common_shape = [ min(s1, s2) for s1, s2 in zip(old_crad.shape, new_crad.shape) ] new_crad[:common_shape[0], :common_shape[1], : common_shape[2]] = old_crad[:common_shape[0], : common_shape[1], :common_shape[2]] new_basis_config.funcspecs_blocks[0].radcoefficients = new_crad # core-repulsion translating from final_basis new_basis_config.funcspecs_blocks[ 0].core_rep_parameters = final_basis.funcspecs_blocks[ 0].core_rep_parameters new_basis_config.funcspecs_blocks[ 0].rho_cut = final_basis.funcspecs_blocks[0].rho_cut new_basis_config.funcspecs_blocks[ 0].drho_cut = final_basis.funcspecs_blocks[0].drho_cut return new_basis_config