예제 #1
0
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
예제 #2
0
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
예제 #3
0
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
예제 #4
0
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
예제 #5
0
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