def _process_algorithm(algorithm): """Identify the algorithm from the user-supplied string. Args: algorithm (str): Package and name of the algorithm. It should be of the format {pkg}_{name}. Returns: origin (str): Name of the package. algo_name (str): Name of the algorithm. """ current_dir_path = Path(__file__).resolve().parent with open(current_dir_path / "algo_dict.json") as j: algos = json.load(j) origin, algo_name = algorithm.split("_", 1) try: assert algo_name in algos[ origin], "Invalid algorithm requested: {}".format(algorithm) except (AssertionError, KeyError): proposals = propose_algorithms(algorithm, algos) raise NotImplementedError( f"{algorithm} is not a valid choice. Did you mean one of {proposals}?" ) return origin, algo_name
def _internal_minimize( criterion, criterion_kwargs, params, internal_params, constraints, algorithm, algo_options, gradient, gradient_options, general_options, database, queue, fitness_factor, ): """Create the internal criterion function and minimize it. Args: criterion (function): Python function that takes a pandas DataFrame with parameters as the first argument and returns a scalar floating point value. criterion_kwargs (dict): additional keyword arguments for criterion params (pd.DataFrame): See :ref:`params`. internal_params (DataFrame): See :ref:`params`. constraints (list): list with constraint dictionaries. See for details. algorithm (str): specifies the optimization algorithm. See :ref:`list_of_algorithms`. algo_options (dict): algorithm specific configurations for the optimization gradient (callable or None): Gradient function. gradient_options (dict): Options for the gradient function. general_options (dict): additional configurations for the optimization database (sqlalchemy.sql.schema.MetaData). The engine that connects to the database can be accessed via ``database.bind``. queue (Queue): queue to which the fitness evaluations and params DataFrames are supplied. fitness_factor (float): multiplicative factor for the fitness displayed in the dashboard. Set to -1 for maximizations to plot the fitness that is being maximized. """ logging_decorator = functools.partial( log_evaluation, database=database, tables=["params_history", "criterion_history", "comparison_plot"], ) exception_decorator = functools.partial( handle_exceptions, database=database, params=params, constraints=constraints, start_params=internal_params, general_options=general_options, ) internal_criterion = create_internal_criterion( criterion=criterion, params=params, constraints=constraints, criterion_kwargs=criterion_kwargs, logging_decorator=logging_decorator, exception_decorator=exception_decorator, queue=queue, fitness_factor=fitness_factor, ) internal_gradient = create_internal_gradient( gradient=gradient, gradient_options=gradient_options, criterion=criterion, params=params, internal_params=internal_params, constraints=constraints, criterion_kwargs=criterion_kwargs, database=database, exception_decorator=exception_decorator, fitness_factor=fitness_factor, algorithm=algorithm, general_options=general_options, ) current_dir_path = Path(__file__).resolve().parent with open(current_dir_path / "algo_dict.json") as j: algos = json.load(j) origin, algo_name = algorithm.split("_", 1) try: assert algo_name in algos[ origin], "Invalid algorithm requested: {}".format(algorithm) except (AssertionError, KeyError): proposals = propose_algorithms(algorithm, algos) raise NotImplementedError( f"{algorithm} is not a valid choice. Did you mean one of {proposals}?" ) bounds = tuple( params.query("_internal_free")[["lower", "upper"]].to_numpy().T) if database: update_scalar_field(database, "optimization_status", "running") if origin in ["nlopt", "pygmo"]: results = minimize_pygmo_np( internal_criterion, internal_params, bounds, origin, algo_name, algo_options, internal_gradient, ) elif origin == "scipy": results = minimize_scipy_np( internal_criterion, internal_params, bounds=bounds, algo_name=algo_name, algo_options=algo_options, gradient=internal_gradient, ) elif origin == "tao": crit_val = general_options["start_criterion_value"] len_criterion_value = 1 if np.isscalar(crit_val) else len(crit_val) results = minimize_pounders_np( internal_criterion, internal_params, bounds, n_errors=len_criterion_value, **algo_options, ) else: raise NotImplementedError("Invalid algorithm requested.") if database: update_scalar_field(database, "optimization_status", results["status"]) params = reparametrize_from_internal( internal=results["x"], fixed_values=params["_internal_fixed_value"].to_numpy(), pre_replacements=params["_pre_replacements"].to_numpy().astype(int), processed_constraints=constraints, post_replacements=params["_post_replacements"].to_numpy().astype(int), processed_params=params, ) return results, params
def _minimize( criterion, criterion_args, criterion_kwargs, params, internal_params, constraints, algorithm, algo_options, general_options, queue, ): """ Create the internal criterion function and minimize it. Args: criterion (function): Python function that takes a pandas Series with parameters as the first argument and returns a scalar floating point value. criterion_args (list or tuple): additional positional arguments for criterion criterion_kwargs (dict): additional keyword arguments for criterion params (pd.DataFrame): See :ref:`params`. internal_params (DataFrame): See :ref:`params`. constraints (list): list with constraint dictionaries. See for details. algorithm (str): specifies the optimization algorithm. See :ref:`list_of_algorithms`. algo_options (dict): algorithm specific configurations for the optimization general_options (dict): additional configurations for the optimization queue (Queue): queue to which originally the parameters DataFrame is supplied and to which the updated parameter Series will be supplied later. """ internal_criterion = _create_internal_criterion( criterion=criterion, params=params, internal_params=internal_params, constraints=constraints, criterion_args=criterion_args, criterion_kwargs=criterion_kwargs, queue=queue, ) with open(os.path.join(os.path.dirname(__file__), "algo_dict.json")) as j: algos = json.load(j) origin, algo_name = algorithm.split("_", 1) try: assert algo_name in algos[ origin], "Invalid algorithm requested: {}".format(algorithm) except (AssertionError, KeyError): proposals = propose_algorithms(algorithm, algos) raise NotImplementedError( f"{algorithm} is not a valid choice. Did you mean one of {proposals}?" ) if origin in ["nlopt", "pygmo"]: prob = _create_problem(internal_criterion, internal_params) algo = _create_algorithm(algo_name, algo_options, origin) pop = _create_population(prob, algo_options, internal_params) evolved = algo.evolve(pop) result = _process_results(evolved, params, internal_params, constraints, origin) elif origin == "scipy": bounds = _get_scipy_bounds(internal_params) x0 = _x_from_params(params, constraints) minimized = scipy_minimize( internal_criterion, x0, method=algo_name, bounds=bounds, options=algo_options, ) result = _process_results(minimized, params, internal_params, constraints, origin) else: raise ValueError("Invalid algorithm requested.") return result
def _single_optimize( direction, criterion, criterion_kwargs, params, algorithm, constraints, algo_options, derivative, derivative_kwargs, criterion_and_derivative, criterion_and_derivative_kwargs, numdiff_options, logging, log_options, error_handling, error_penalty, cache_size, ): """Minimize or maximize *criterion* using *algorithm* subject to *constraints*. See the docstring of ``optimize`` for an explanation of all arguments. Returns: dict: The optimization result. """ # store all arguments in a dictionary to save them in the database later problem_data = { "direction": direction, # "criterion"-criterion, "criterion_kwargs": criterion_kwargs, "algorithm": algorithm, "constraints": constraints, "algo_options": algo_options, # "derivative"-derivative, "derivative_kwargs": derivative_kwargs, # "criterion_and_derivative"-criterion_and_derivative, "criterion_and_derivative_kwargs": criterion_and_derivative_kwargs, "numdiff_options": numdiff_options, "logging": logging, "log_options": log_options, "error_handling": error_handling, "error_penalty": error_penalty, "cache_size": int(cache_size), } # partial the kwargs into corresponding functions criterion = functools.partial(criterion, **criterion_kwargs) if derivative is not None: derivative = functools.partial(derivative, **derivative_kwargs) if criterion_and_derivative is not None: criterion_and_derivative = functools.partial( criterion_and_derivative, **criterion_and_derivative_kwargs) # process params and constraints params = process_bounds(params) for col in ["value", "lower_bound", "upper_bound"]: params[col] = params[col].astype(float) _check_params(params) processed_constraints, processed_params = process_constraints( constraints, params) # name and group column are needed in the dashboard but could lead to problems # if present anywhere else params_with_name_and_group = _add_name_and_group_columns_to_params(params) problem_data["params"] = params_with_name_and_group # get internal parameters and bounds x = reparametrize_to_internal( params["value"].to_numpy(), processed_params["_internal_free"].to_numpy(), processed_constraints, ) free = processed_params.query("_internal_free") lower_bounds = free["_internal_lower"].to_numpy() upper_bounds = free["_internal_upper"].to_numpy() # process algorithm and algo_options if isinstance(algorithm, str): algo_name = algorithm else: algo_name = getattr(algorithm, "name", "your algorithm") if isinstance(algorithm, str): try: algorithm = AVAILABLE_ALGORITHMS[algorithm] except KeyError: proposed = propose_algorithms(algorithm, list(AVAILABLE_ALGORITHMS)) raise ValueError( f"Invalid algorithm: {algorithm}. Did you mean {proposed}?") algo_options = _adjust_options_to_algorithms(algo_options, lower_bounds, upper_bounds, algorithm, algo_name) # get partialed reparametrize from internal pre_replacements = processed_params["_pre_replacements"].to_numpy() post_replacements = processed_params["_post_replacements"].to_numpy() fixed_values = processed_params["_internal_fixed_value"].to_numpy() partialed_reparametrize_from_internal = functools.partial( reparametrize_from_internal, fixed_values=fixed_values, pre_replacements=pre_replacements, processed_constraints=processed_constraints, post_replacements=post_replacements, ) # get convert derivative pre_replace_jac = pre_replace_jacobian(pre_replacements=pre_replacements, dim_in=len(x)) post_replace_jac = post_replace_jacobian( post_replacements=post_replacements) convert_derivative = functools.partial( convert_external_derivative_to_internal, fixed_values=fixed_values, pre_replacements=pre_replacements, processed_constraints=processed_constraints, pre_replace_jac=pre_replace_jac, post_replace_jac=post_replace_jac, ) # do first function evaluation first_eval = { "internal_params": x, "external_params": params, "output": criterion(params), } # fill numdiff_options with defaults numdiff_options = _fill_numdiff_options_with_defaults( numdiff_options, lower_bounds, upper_bounds) # create and initialize the database if not logging: database = False else: database = _create_and_initialize_database(logging, log_options, first_eval, problem_data) # set default error penalty error_penalty = _fill_error_penalty_with_defaults(error_penalty, first_eval, direction) # create cache x_hash = hash_array(x) cache = {x_hash: {"criterion": first_eval["output"]}} # partial the internal_criterion_and_derivative_template internal_criterion_and_derivative = functools.partial( internal_criterion_and_derivative_template, direction=direction, criterion=criterion, params=params, reparametrize_from_internal=partialed_reparametrize_from_internal, convert_derivative=convert_derivative, derivative=derivative, criterion_and_derivative=criterion_and_derivative, numdiff_options=numdiff_options, database=database, database_path=logging, log_options=log_options, error_handling=error_handling, error_penalty=error_penalty, first_criterion_evaluation=first_eval, cache=cache, cache_size=cache_size, ) res = algorithm(internal_criterion_and_derivative, x, **algo_options) p = params.copy() p["value"] = partialed_reparametrize_from_internal(res["solution_x"]) res["solution_params"] = p if "solution_criterion" not in res: res["solution_criterion"] = criterion(p) # in the long run we can get some of those from the database if logging was used. optional_entries = [ "solution_derivative", "solution_hessian", "n_criterion_evaluations", "n_derivative_evaluations", "n_iterations", "success", "reached_convergence_criterion", "message", ] for entry in optional_entries: res[entry] = res.get(entry, f"Not reported by {algo_name}") if logging: _log_final_status(res, database, logging, log_options) return res
def _internal_minimize( criterion, criterion_kwargs, params, internal_params, constraints, algorithm, algo_options, general_options, queue, fitness_factor, ): """Create the internal criterion function and minimize it. Args: criterion (function): Python function that takes a pandas DataFrame with parameters as the first argument and returns a scalar floating point value. criterion_kwargs (dict): additional keyword arguments for criterion params (pd.DataFrame): See :ref:`params`. internal_params (DataFrame): See :ref:`params`. constraints (list): list with constraint dictionaries. See for details. algorithm (str): specifies the optimization algorithm. See :ref:`list_of_algorithms`. algo_options (dict): algorithm specific configurations for the optimization general_options (dict): additional configurations for the optimization queue (Queue): queue to which the fitness evaluations and params DataFrames are supplied. fitness_factor (float): multiplicative factor for the fitness displayed in the dashboard. Set to -1 for maximizations to plot the fitness that is being maximized. """ internal_criterion = create_internal_criterion( criterion=criterion, params=params, constraints=constraints, criterion_kwargs=criterion_kwargs, queue=queue, fitness_factor=fitness_factor, ) current_dir_path = Path(__file__).resolve().parent with open(current_dir_path / "algo_dict.json") as j: algos = json.load(j) origin, algo_name = algorithm.split("_", 1) try: assert algo_name in algos[ origin], "Invalid algorithm requested: {}".format(algorithm) except (AssertionError, KeyError): proposals = propose_algorithms(algorithm, algos) raise NotImplementedError( f"{algorithm} is not a valid choice. Did you mean one of {proposals}?" ) if origin == "pygmo" and algorithm != "pygmo_simulated_annealing": assert ( "popsize" in algo_options ), f"For genetic optimizers like {algo_name}, popsize is mandatory." assert ( "gen" in algo_options ), f"For genetic optimizers like {algo_name}, gen is mandatory." if origin in ["nlopt", "pygmo"]: prob = _create_problem(internal_criterion, params) algo = _create_algorithm(algo_name, algo_options, origin) pop = _create_population(prob, algo_options, internal_params) evolved = algo.evolve(pop) result = _process_results(evolved, params, internal_params, constraints, origin) elif origin == "scipy": bounds = _get_scipy_bounds(params) minimized = scipy_minimize( internal_criterion, internal_params, method=algo_name, bounds=bounds, options=algo_options, ) result = _process_results(minimized, params, internal_params, constraints, origin) elif origin == "tao": len_output = algo_options.pop("len_output", None) if len_output is None: len_output = len(criterion(params)) bounds = (params.query("_internal_free")[["lower", "upper" ]].to_numpy().T.tolist()) minimized = minimize_pounders(internal_criterion, internal_params, len_output, bounds, **algo_options) result = _process_results(minimized, params, internal_params, constraints, origin) else: raise ValueError("Invalid algorithm requested.") return result