Beispiel #1
0
    def _advance(self, **kwargs):

        # all the elements in the interval
        a, c, d, b = self.pop

        # the golden ratio (precomputed constant)
        R = self.R

        # if the left solution is better than the right
        if c.F[0] < d.F[0]:

            # make the right to be the new right bound and the left becomes the right
            a, b = a, d
            d = c

            # create a new left individual and evaluate
            c = Individual(X=b.X - R * (b.X - a.X))
            self.evaluator.eval(self.problem, c, algorithm=self)
            self.infills = c

        # if the right solution is better than the left
        else:

            # make the left to be the new left bound and the right becomes the left
            a, b = c, b
            c = d

            # create a new right individual and evaluate
            d = Individual(X=a.X + R * (b.X - a.X))
            self.evaluator.eval(self.problem, d, algorithm=self)
            self.infills = d

        # update the population with all the four individuals
        self.pop = Population.create(a, c, d, b)
Beispiel #2
0
        def zoom(alpha_low, alpha_high, max_iter=100):

            while True:

                _alpha = (alpha_high.get("alpha") + alpha_low.get("alpha")) / 2
                _point = Individual(X=_alpha)
                evaluator.eval(problem, _point, evaluate_values_of=["F", "CV"])

                if _point.F[
                        0] > sol_F + self.c1 * _alpha * sol_dF @ direction or _point.F[
                            0] > alpha_low.F[0]:
                    alpha_high = _point
                else:
                    evaluator.eval(problem, _point, evaluate_values_of=["dF"])
                    point_dF = _point.get("dF")[0]

                    if np.abs(point_dF
                              @ direction) <= -self.c2 * sol_dF @ direction:
                        return _point

                    if (point_dF @ direction) * (alpha_high.get("alpha") -
                                                 alpha_low.get("alpha")) >= 0:
                        alpha_high = alpha_low

                    alpha_low = _point
Beispiel #3
0
    def _initialize_infill(self):

        # x could be a vector or an individual
        if isinstance(self.point, np.ndarray):
            self.point = Individual(X=self.point)

        # make sure it is evaluated - if not yet also get the gradient
        if self.point.get("F") is None:
            self.evaluator.eval(self.problem, self.point, algorithm=self)

        self.infill = self.point
Beispiel #4
0
class LineSearch(Algorithm):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.point, self.direction = None, None

    def setup(self, problem, point=None, direction=None, **kwargs):
        super().setup(problem, **kwargs)

        msg = "Only problems with one objective and no constraints can be solved using a line search!"
        assert not problem.has_constraints() and problem.n_obj == 1, msg

        assert point is not None, "You have to define a starting point for the algorithm"
        self.point = point

        assert direction is not None, "You have to define a direction point for the algorithm"
        self.direction = direction

        return self

    def _initialize_infill(self):

        # x could be a vector or an individual
        if isinstance(self.point, np.ndarray):
            self.point = Individual(X=self.point)

        # make sure it is evaluated - if not yet also get the gradient
        if self.point.get("F") is None:
            self.evaluator.eval(self.problem, self.point, algorithm=self)

        self.infill = self.point
Beispiel #5
0
    def _initialize_infill(self):
        super()._initialize_infill()
        a, b = self.a, self.b

        # the golden ratio (precomputed constant)
        R = self.R

        # create the left and right in the interval itself
        c, d = Individual(X=b.X - R * (b.X - a.X)), Individual(X=a.X + R *
                                                               (b.X - a.X))

        # create a population with all four individuals
        pop = Population.create(a, c, d, b)

        self.pop, self.infills = pop, pop

        return pop
Beispiel #6
0
    def _pattern_move(self, _current, _direction):

        # calculate the new X and repair out of bounds if necessary
        X = _current.X + self.step_size * _direction
        set_to_bounds_if_outside_by_problem(self.problem, X)

        # create the new center individual and evaluate it
        trial = Individual(X=X)
        self.evaluator.eval(self.problem, trial, algorithm=self)

        return trial
Beispiel #7
0
    def _initialize_infill(self):
        super()._initialize_infill()
        a, b = self.a, self.b

        # set c to be directly in the middle between the two brackets
        c = Individual(X=(b.X - a.X) / 2)

        # create a population with all three individuals
        pop = Population.create(a, b, c)

        return pop
Beispiel #8
0
    def _infill(self):

        # the offspring population to finally evaluate and attach to the population
        infills = Population()

        # find the potential optimal solution in the current population
        potential_optimal = self._potential_optimal()

        # for each of those solutions execute the division move
        for current in potential_optimal:

            # find the largest dimension the solution has not been evaluated yet
            nxl, nxu = norm_bounds(current, self.problem)
            k = np.argmax(nxu - nxl)

            # the delta value to be used to get left and right - this is one sixth of the range
            xl, xu = current.get("xl"), current.get("xu")

            delta = (xu[k] - xl[k]) / 6

            # create the left individual
            left_x = np.copy(current.X)
            left_x[k] = xl[k] + delta
            left = Individual(X=left_x)

            # create the right individual
            right_x = np.copy(current.X)
            right_x[k] = xu[k] - delta
            right = Individual(X=right_x)

            # update the boundaries for all the points accordingly
            for ind in [current, left, right]:
                update_bounds(ind, xl, xu, k, delta)

            # create the offspring population, evaluate and attach to current population
            _infill = Population.create(left, right)
            _infill.set("depth", current.get("depth") + 1)

            infills = Population.merge(infills, _infill)

        return infills
Beispiel #9
0
    def _backward(self, X, inplace=False, **kwargs):
        assert isinstance(X, np.ndarray)

        # if it should be 2d we convert it to be a 1-d array
        if X.ndim == 2 and len(X) == 1:
            vals = X[0]

        assert vals.ndim == 1

        if inplace:
            self.obj.set(self.attr, vals)
            return self.obj
        else:
            return Individual(**{self.attr: vals})
Beispiel #10
0
    def _initialize_infill(self):

        # the boundaries of the problem for initialization
        xl, xu = self.problem.bounds()

        # take care of the lower bound - make sure it is an individual and make sure it is evaluated
        if self.a is None:
            assert xl is not None, "Either provide a left bound or set the xl attribute in the problem."
            self.a = Individual(X=xl)

        if self.a.F is None:
            self.evaluator.eval(self.problem, self.a, algorithm=self)

        # take care of the upper bound - make sure it is an individual and make sure it is evaluated
        if self.b is None:
            assert xl is not None, "Either provide a right bound or set the xu attribute in the problem."
            self.b = Individual(X=xu)

        if self.b.F is None:
            self.evaluator.eval(self.problem, self.b, algorithm=self)

        assert self.a.X[0] <= self.b.X[
            0], "The left bound must be smaller than the left bound!"
Beispiel #11
0
    def step(self):
        alpha, max_alpha = self.alpha, self.problem.xu[0]

        if alpha > max_alpha:
            alpha = max_alpha

        infill = Individual(X=np.array([alpha]))
        self.evaluator.eval(self.problem, infill)
        self.pop = Population.merge(self.pop, infill)[-10:]

        if is_better(self.point, infill, eps=0.0) or alpha == max_alpha:
            self.termination.force_termination = True
            return

        self.point = infill
        self.alpha *= 2
Beispiel #12
0
def step(problem, x, delta, k):
    # copy and add delta to the new point
    X = np.copy(x)

    # if the problem has bounds normalize the delta
    # if problem.has_bounds():
    #     xl, xu = problem.bounds()
    #     delta *= (xu[k] - xl[k])

    # now add to the current solution
    X[k] = X[k] + delta[k]

    # repair if out of bounds if necessary
    X = set_to_bounds_if_outside_by_problem(problem, X)

    return Individual(X=X)
Beispiel #13
0
    def _setup(self, problem, **kwargs):

        xl, xu = problem.bounds()
        X = denormalize(0.5 * np.ones(problem.n_var), xl, xu)

        x0 = Individual(X=X)
        x0.set("xl", xl)
        x0.set("xu", xu)
        x0.set("depth", 0)

        self.x0 = x0
Beispiel #14
0
    def _advance(self, **kwargs):

        # all the elements in the interval
        a, b, c = self.pop

        # if this is the case then the function is not convex (which means U shaped)
        if c.F[0] >= a.F[0] or c.F[0] >= b.F[0]:

            # choose the left side if a smaller than b, or the right side otherwise
            if a.F[0] <= b.F[0]:
                a = c
            else:
                b = c

            c = Individual(X=(b.X - a.X) / 2)
            self.evaluator.eval(self.problem, c, algorithm=self)
            self.infills = c

        else:

            d = quadr_interp(a, b, c)
            self.evaluator.eval(self.problem, d, algorithm=self)
            self.infills = d

            # swap c and d -> make sure d is always on the right of c -> a, c, d, b
            if c.X[0] > d.X[0]:
                c, d = d, c

            # if c is better than d, then d becomes the new right bound
            if c.F[0] <= d.F[0]:
                b = d

            # if d is better than c, then c becomes the new left bound and d the new right bound
            else:
                a, c = c, d

        self.pop = Population.create(a, b, c)
Beispiel #15
0
 def __new__(cls, n_individuals=0):
     obj = super(Population, cls).__new__(cls, n_individuals, dtype=cls).view(cls)
     for i in range(n_individuals):
         obj[i] = Individual()
     return obj
Beispiel #16
0
    def _infill(self):

        problem, evaluator = self.problem, self.evaluator
        evaluator.skip_already_evaluated = False

        sol, direction = self.problem.point, self.problem.direction

        # the function value and gradient of the initial solution
        sol.set("alpha", 0.0)
        sol_F, sol_dF = sol.F[0], sol.get("dF")[0]

        def zoom(alpha_low, alpha_high, max_iter=100):

            while True:

                _alpha = (alpha_high.get("alpha") + alpha_low.get("alpha")) / 2
                _point = Individual(X=_alpha)
                evaluator.eval(problem, _point, evaluate_values_of=["F", "CV"])

                if _point.F[
                        0] > sol_F + self.c1 * _alpha * sol_dF @ direction or _point.F[
                            0] > alpha_low.F[0]:
                    alpha_high = _point
                else:
                    evaluator.eval(problem, _point, evaluate_values_of=["dF"])
                    point_dF = _point.get("dF")[0]

                    if np.abs(point_dF
                              @ direction) <= -self.c2 * sol_dF @ direction:
                        return _point

                    if (point_dF @ direction) * (alpha_high.get("alpha") -
                                                 alpha_low.get("alpha")) >= 0:
                        alpha_high = alpha_low

                    alpha_low = _point

        last = sol

        alpha = 1.0
        current = Individual(X=alpha)

        for i in range(1, self.max_iter + 1):

            # evaluate the solutions
            evaluator.eval(problem, current, evaluate_values_of=["F", "CV"])

            # get the values from the solution to be used to evaluate the conditions
            F, dF, _F = last.F[0], last.get("dF")[0], current.F[0]

            # if the wolfe condition is violate we have found our upper bound
            if _F > sol_F + self.c1 * sol_dF @ direction or (i > 1
                                                             and F >= _F):
                return zoom(last, current)

            # for the other condition we need the gradient information
            evaluator.eval(problem, current, evaluate_values_of=["dF"])
            _dF = current.get("dF")[0]

            if np.abs(_dF @ direction) <= -self.c2 * sol_dF @ direction:
                return current

            if _dF @ direction >= 0:
                return zoom(current, last)

            alpha = 2 * alpha
            last = current
            current = Individual(X=alpha)

        return current
Beispiel #17
0
def wolfe_line_search(problem,
                      sol,
                      direction,
                      c1=1e-4,
                      c2=0.9,
                      max_iter=10,
                      evaluator=None):
    # initialize the evaluator to be used (this will make sure evaluations are counted)
    evaluator = evaluator if evaluator is not None else Evaluator()
    evaluator.skip_already_evaluated = False

    # the function value and gradient of the initial solution
    sol.set("alpha", 0.0)
    sol_F, sol_dF = sol.F[0], sol.get("dF")[0]

    def zoom(alpha_low, alpha_high, max_iter=100):

        while True:

            _alpha = (alpha_high.get("alpha") + alpha_low.get("alpha")) / 2
            _point = Individual(X=sol.X + _alpha * direction, alpha=_alpha)
            evaluator.eval(problem, _point, evaluate_values_of=["F", "CV"])

            if _point.F[
                    0] > sol_F + c1 * _alpha * sol_dF @ direction or _point.F[
                        0] > alpha_low.F[0]:
                alpha_high = _point
            else:
                evaluator.eval(problem, _point, evaluate_values_of=["dF"])
                point_dF = _point.get("dF")[0]

                if np.abs(point_dF @ direction) <= -c2 * sol_dF @ direction:
                    return _point

                if (point_dF @ direction) * (alpha_high.get("alpha") -
                                             alpha_low.get("alpha")) >= 0:
                    alpha_high = alpha_low

                alpha_low = _point

    last = sol

    alpha = 1.0
    current = Individual(X=sol.X + alpha * direction, alpha=alpha)

    for i in range(1, max_iter + 1):

        # evaluate the solutions
        evaluator.eval(problem, current, evaluate_values_of=["F", "CV"])

        # get the values from the solution to be used to evaluate the conditions
        F, dF, _F = last.F[0], last.get("dF")[0], current.F[0]

        # if the wolfe condition is violate we have found our upper bound
        if _F > sol_F + c1 * sol_dF @ direction or (i > 1 and F >= _F):
            return zoom(last, current)

        # for the other condition we need the gradient information
        evaluator.eval(problem, current, evaluate_values_of=["dF"])
        _dF = current.get("dF")[0]

        if np.abs(_dF @ direction) <= -c2 * sol_dF @ direction:
            return current

        if _dF @ direction >= 0:
            return zoom(current, last)

        alpha = 2 * alpha
        last = current
        current = Individual(X=sol.X + alpha * direction, alpha=alpha)

    return current
Beispiel #18
0
    def _local_advance(self, **kwargs):

        # number of variables increased by one - matches equations in the paper
        xl, xu = self.problem.bounds()
        pop, n = self.pop, self.problem.n_var - 1

        # calculate the centroid
        centroid = pop[:n + 1].get("X").mean(axis=0)

        # -------------------------------------------------------------------------------------------
        # REFLECT
        # -------------------------------------------------------------------------------------------

        # check the maximum alpha until the bounds are hit
        alphaU = max_alpha(centroid, (centroid - pop[n + 1].X), xl, xu)

        # reflect the point, consider factor if bounds are there, make sure in bounds (floating point) evaluate
        x_reflect = centroid + min(self.alpha, alphaU) * (centroid - pop[n + 1].X)
        x_reflect = set_to_bounds_if_outside_by_problem(self.problem, x_reflect)
        reflect = self.evaluator.eval(self.problem, Individual(X=x_reflect), algorithm=self)

        # whether a shrink is necessary or not - decided during this step
        shrink = False

        better_than_current_best = is_better(reflect, pop[0])
        better_than_second_worst = is_better(reflect, pop[n])
        better_than_worst = is_better(reflect, pop[n + 1])

        # if better than the current best - check for expansion
        if better_than_current_best:

            # -------------------------------------------------------------------------------------------
            # EXPAND
            # -------------------------------------------------------------------------------------------

            # the maximum expansion until the bounds are hit
            betaU = max_alpha(centroid, (x_reflect - centroid), xl, xu)

            # expand using the factor, consider bounds, make sure in case of floating point issues
            x_expand = centroid + min(self.beta, betaU) * (x_reflect - centroid)
            x_expand = set_to_bounds_if_outside_by_problem(self.problem, x_expand)

            # if the expansion is almost equal to reflection (if boundaries were hit) - no evaluation
            if np.allclose(x_expand, x_reflect, atol=1e-16):
                expand = reflect
            else:
                expand = self.evaluator.eval(self.problem, Individual(X=x_expand), algorithm=self)

            # if the expansion further improved take it - otherwise use expansion
            if is_better(expand, reflect):
                pop[n + 1] = expand
            else:
                pop[n + 1] = reflect

        # if the new point is not better than the best, but better than second worst - just keep it
        elif not better_than_current_best and better_than_second_worst:
            pop[n + 1] = reflect

        # if not worse than the worst - outside contraction
        elif not better_than_second_worst and better_than_worst:

            # -------------------------------------------------------------------------------------------
            # Outside Contraction
            # -------------------------------------------------------------------------------------------

            x_contract_outside = centroid + self.gamma * (x_reflect - centroid)
            contract_outside = self.evaluator.eval(self.problem, Individual(X=x_contract_outside), algorithm=self)

            if is_better(contract_outside, reflect):
                pop[n + 1] = contract_outside
            else:
                shrink = True

        # if the reflection was worse than the worst - inside contraction
        else:

            # -------------------------------------------------------------------------------------------
            # Inside Contraction
            # -------------------------------------------------------------------------------------------

            x_contract_inside = centroid - self.gamma * (x_reflect - centroid)
            contract_inside = self.evaluator.eval(self.problem, Individual(X=x_contract_inside), algorithm=self)

            if is_better(contract_inside, pop[n + 1]):
                pop[n + 1] = contract_inside
            else:
                shrink = True

        # -------------------------------------------------------------------------------------------
        # Shrink (only if necessary)
        # -------------------------------------------------------------------------------------------

        if shrink:
            for i in range(1, len(pop)):
                pop[i].X = pop[0].X + self.delta * (pop[i].X - pop[0].X)
            pop[1:] = self.evaluator.eval(self.problem, pop[1:], algorithm=self)

        self.pop = FitnessSurvival().do(self.problem, pop, n_survive=len(pop))
Beispiel #19
0
def test_evaluate_individual():
    evaluator = Evaluator()
    ind = evaluator.eval(problem, Individual(X=X[0]))
    np.testing.assert_allclose(F[0], ind.get("F"))
    assert evaluator.n_eval == 1
Beispiel #20
0
    def _advance(self, **kwargs):
        repair, crossover, mutation = self.repair, self.mating.crossover, self.mating.mutation

        pc_pop = self.pc_pop.copy(deep=True)
        npc_pop = self.npc_pop.copy(deep=True)

        ##############################################################
        # PC evolving
        ##############################################################

        # Normalise both poulations according to the PC individuals
        pc_pop, npc_pop = normalize_bothpop(pc_pop, npc_pop)

        PCObj = pc_pop.get("F")
        NPCObj = npc_pop.get("F")
        pc_size = PCObj.shape[0]
        npc_size = NPCObj.shape[0]

        ######################################################
        # Calculate the Euclidean distance among individuals
        ######################################################
        d = np.zeros((pc_size, pc_size))
        d = cdist(PCObj, PCObj, 'euclidean')
        d[d == 0] = np.inf

        # Determine the size of the niche
        if pc_size == 1:
            radius = 0
        else:
            radius = determine_radius(d, pc_size, self.pc_capacity)

        # calculate the radius for individual exploration
        r = pc_size / self.pc_capacity * radius

        ########################################################
        # find the promising individuals in PC for exploration
        ########################################################

        # promising_num: record how many promising individuals in PC
        promising_num = 0
        # count: record how many NPC individuals are located in each PC individual's niche
        count = np.array([])

        d2 = np.zeros((pc_size, npc_size))
        d2 = cdist(PCObj, NPCObj, 'euclidean')

        # Count of True elements in each row (each individual in PC) of 2D Numpy Array
        count = np.count_nonzero(d2 <= r, axis=1)

        # Check if the niche has no NPC individual or has only one NPC individual
        # Record the indices of promising individuals.
        # Since np.nonzero() returns a tuple of arrays, we change the type of promising_index to a numpy.ndarray.
        promising_index = np.nonzero(count <= 1)
        promising_index = np.asarray(promising_index).flatten()

        # Record total number of promising individuals in PC for exploration
        promising_num = len(promising_index)

        ########################################
        # explore these promising individuals
        ########################################

        original_size = pc_size
        off = Individual()

        if promising_num > 0:
            for i in range(promising_num):
                if original_size > 1:
                    parents = Population.new(2)

                    # The explored individual is considered as one parent
                    parents[0] = pc_pop[promising_index[i]]

                    # The remaining parent will be selected randomly from the PC population
                    rnd = np.random.permutation(pc_size)

                    for j in rnd:
                        if j != promising_index[i]:
                            parents[1] = pc_pop[j]
                            break

                    index = np.array([0, 1])
                    parents_shape = index[None, :]

                    # do recombination and create an offspring
                    off = crossover.do(self.problem, parents, parents_shape)[0]

                else:
                    off = pc_pop[0]

                # mutation
                off = Population.create(off)
                off = mutation.do(self.problem, off)

                # evaluate the offspring
                self.evaluator.eval(self.problem, off)

                # update the PC population by the offspring
                self.pc_pop = update_PCpop(self.pc_pop, off)

                # update the ideal point
                self.ideal = np.min(np.vstack([self.ideal,
                                               off.get("F")]),
                                    axis=0)

                # update at most one solution in NPC population
                self.npc_pop = update_NPCpop(self.npc_pop, off, self.ideal,
                                             self.ref_dirs, self.decomp)

        ########################################################
        # NPC evolution based on MOEA/D
        ########################################################

        # iterate for each member of the population in random order
        for i in np.random.permutation(len(self.npc_pop)):
            # get the parents using the neighborhood selection
            P = self.selection.do(self.npc_pop,
                                  1,
                                  self.mating.crossover.n_parents,
                                  k=[i])

            # perform a mating using the default operators (recombination & mutation) - if more than one offspring just pick the first
            off = self.mating.do(self.problem, self.npc_pop, 1, parents=P)[0]

            off = Population.create(off)

            # evaluate the offspring
            self.evaluator.eval(self.problem, off, algorithm=self)

            # update the PC population by the offspring
            self.pc_pop = update_PCpop(self.pc_pop, off)

            # update the ideal point
            self.ideal = np.min(np.vstack([self.ideal, off.get("F")]), axis=0)

            # now actually do the replacement of the individual is better
            self.npc_pop = self._replace(i, off)

        ########################################################
        # population maintenance operation in the PC evolution
        ########################################################

        current_pop = Population.merge(self.pc_pop, self.npc_pop)
        current_pop = Population.merge(current_pop, self.pop)

        # filter duplicate in the population
        pc_pop = self.eliminate_duplicates.do(current_pop)

        pc_size = len(pc_pop)

        if (pc_size > self.pc_capacity):

            # get the objective space values and objects
            pc_objs = pc_pop.get("F")

            fronts, rank = NonDominatedSorting().do(pc_objs, return_rank=True)
            front_0_index = fronts[0]

            # put the nondominated individuals of the NPC population into the PC population
            self.pc_pop = pc_pop[front_0_index]

            if len(self.pc_pop) > self.pc_capacity:
                self.pc_pop = maintain_PCpop(self.pc_pop, self.pc_capacity)

        self.pop = self.pc_pop.copy(deep=True)
Beispiel #21
0
def quadr_interp(a, b, c):
    return Individual(X=quadr_interp_equ(a.X, a.F, b.X, b.F, c.X, c.F))
Beispiel #22
0
    def _advance(self, **kwargs):
        problem, evaluator = self.problem, self.evaluator

        # add the box constraints defined in the problem
        bounds = None
        if self.use_bounds:

            xl, xu = self.problem.bounds()
            if self.with_bounds:
                bounds = np.column_stack([xl, xu])
            else:
                if xl is not None or xu is not None:
                    raise Exception(
                        f"Error: Boundary constraints can not be handled by {self.method}"
                    )

        # define the actual constraints if supported by the algorithm
        constr = []
        if self.use_constr:

            constr = [LinearConstraint(np.eye(self.problem.n_var), xl, xu)]

            if problem.has_constraints():

                if self.with_constr:

                    def fun_constr(x):
                        g, cv = problem.evaluate(x,
                                                 return_values_of=["G", "CV"])
                        return cv[0]

                    non_lin_constr = NonlinearConstraint(
                        fun_constr, -float("inf"), 0)

                    constr.append(non_lin_constr)

                else:
                    raise Exception(
                        f"Error: Constraint handling is not supported by {self.method}"
                    )

        # the objective function to be optimized and add gradients if available
        if self.estm_gradients:
            jac = None

            def fun_obj(x):
                f = problem.evaluate(x, return_values_of=["F"])[0]
                evaluator.n_eval += 1
                return f

        else:
            jac = True

            def fun_obj(x):
                f, df = problem.evaluate(x, return_values_of=["F", "dF"])

                if df is None:
                    raise Exception(
                        "If the gradient shall not be estimate, please set out['dF'] in _evaluate. "
                    )

                evaluator.n_eval += 1
                return f[0], df[0]

        # the arguments to be used
        kwargs = dict(args=(),
                      method=self.method,
                      bounds=bounds,
                      constraints=constr,
                      jac=jac,
                      options=self.options)

        # the starting solution found by sampling beforehand
        x0 = self.opt[0].X

        # actually run the optimization
        if not self.show_warnings:
            warnings.simplefilter("ignore")
        res = scipy_minimize(fun_obj, x0, **kwargs)

        opt = Population.create(Individual(X=res.x))
        self.evaluator.eval(self.problem, opt, algorithm=self)

        self.pop, self.off = opt, opt

        self.termination.force_termination = True

        if hasattr("res", "nit"):
            self.n_gen = res.nit + 1