def anneal_minimize(
        initial: T,
        cost_func: Callable[[T], float],
        move_func: Callable[[T], T],
        random_sample: Callable[[], float],
        temp_initial: float = 1.0e-2,
        temp_final: float = 1e-6,
        cooling_factor: float = 0.99,
        repeat: int = 100,
        trace_func: Callable[
            [T, float, float, float, bool], None] = None) -> T:
    """Minimize solution using Simulated Annealing meta-heuristic.

    Args:
        initial: Initial solution of type T to the problem.
        cost_func: Callable which takes current solution of type T, evaluates it
            and returns float with the cost estimate. The better solution is,
            the lower resulting value should be; negative values are allowed.
        move_func: Callable which takes current solution of type T and returns a
            new solution candidate of type T which is random iteration over
            input solution. The input solution, which is argument to this
            callback should not be mutated.
        random_sample: Callable which gives uniformly sampled random value from
            the [0, 1) interval on each call.
        temp_initial: Optional initial temperature for simulated annealing
            optimization. Scale of this value is cost_func-dependent.
        temp_final: Optional final temperature for simulated annealing
            optimization, where search should be stopped. Scale of this value is
            cost_func-dependent.
        cooling_factor: Optional factor to be applied to the current temperature
            and give the new temperature, this must be strictly greater than 0
            and strictly lower than 1.
        repeat: Optional number of iterations to perform at each given
            temperature.
        trace_func: Optional callback for tracing simulated annealing progress.
            This is going to be called at each algorithm step for the arguments:
            solution candidate (T), current temperature (float), candidate cost
            (float), probability of accepting candidate (float), and acceptance
            decision (boolean).

    Returns:
        The best solution found.

    Raises:
        ValueError: When supplied arguments are invalid.
    """

    if not 0.0 < cooling_factor < 1.0:
        raise ValueError("Cooling factor must be within (0, 1) range")

    temp = temp_initial
    sol = initial
    sol_cost = cost_func(initial)
    best = sol
    best_cost = sol_cost

    if trace_func:
        trace_func(sol, temp, sol_cost, 1.0, True)

    while temp > temp_final:
        for _ in range(0, repeat):
            # Find a new solution candidate and evaluate its cost.
            cand = move_func(sol)
            cand_cost = cost_func(cand)

            # Store the best solution, regardless if it is accepted or not.
            if best_cost > cand_cost:
                best = cand
                best_cost = cand_cost

            accepted, probability = _accept(random_sample(),
                                            cand_cost - sol_cost,
                                            temp)
            if accepted:
                sol = cand
                sol_cost = cand_cost

            if trace_func:
                trace_func(cand, temp, cand_cost, probability, accepted)

        temp *= cooling_factor

    return best
Minimize solution using Simulated Annealing meta-heuristic.

    Args:
        initial: Initial solution of type T to the problem.
        cost_func: Callable which takes current solution of type T, evaluates it
            and returns float with the cost estimate. The better solution is,
            the lower resulting value should be; negative values are allowed.
        move_func: Callable which takes current solution of type T and returns a
            new solution candidate of type T which is random iteration over
            input solution. The input solution, which is argument to this
            callback should not be mutated.
        random_sample: Callable which gives uniformly sampled random value from
            the [0, 1) interval on each call.
        temp_initial: Optional initial temperature for simulated annealing
            optimization. Scale of this value is cost_func-dependent.
        temp_final: Optional final temperature for simulated annealing
            optimization, where search should be stopped. Scale of this value is
            cost_func-dependent.
        cooling_factor: Optional factor to be applied to the current temperature
            and give the new temperature, this must be strictly greater than 0
            and strictly lower than 1.
        repeat: Optional number of iterations to perform at each given
            temperature.
        trace_func: Optional callback for tracing simulated annealing progress.
            This is going to be called at each algorithm step for the arguments:
            solution candidate (T), current temperature (float), candidate cost
            (float), probability of accepting candidate (float), and acceptance
            decision (boolean).

    Returns:
        The best solution found.

    Raises:
        ValueError: When supplied arguments are invalid.