Exemple #1
0
    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
Exemple #2
0
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
Exemple #3
0
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))
Exemple #4
0
    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
Exemple #5
0
    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
Exemple #6
0
    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, {}))
Exemple #7
0
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
Exemple #8
0
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)
Exemple #9
0
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())
Exemple #10
0
    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
Exemple #11
0
    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)
Exemple #12
0
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