Search code examples
pythonmathematical-optimizationnonlinear-optimizationmystic

Mystic lib differential evolution optimiztion algorithm settings


I have tried to optimize smooth objective (cost function) with tough non-linear constraints using diffevo algorithm. And I cant understand how to define such parameters as: constraints, bounds and init vectors.

My obj - just a sum of budget:

def minimize_me_reach(budgetA, *args):
    # start = time.time()
    # budget = args[0]
    # # trp_total = args[0]
    # freq = args[1]
    # DF_GROUP = args[2]
    # MEMBER = args[3]
    # DF_GROUP['bud_part'] = 0
    # DF_GROUP.loc[DF_GROUP['Prime'] == 1, 'bud_part'] = budgetA
    # DF_GROUP.loc[DF_GROUP['Prime'] == 2, 'bud_part'] = budgetA
    # DF_GROUP['budget_total'] = DF_GROUP['bud_part'] * budget
    # execution of above program is :",(end - start), "sec")
    return np.nan_to_num(budgetA).sum()

My constraint - 1 for now. Planing to add some more constraints

   def constr_reach(x_all):
        #budget = x_all[-1]
        x = x_all[:-1]
        print(DF_GROUP.info())
        DF_GROUP['bud_part'] = 0
        print(MEMBER.info())
        DF_GROUP.loc[DF_GROUP['Prime'] == 1, 'bud_part'] = x
        DF_GROUP.loc[DF_GROUP['Prime'] == 2, 'bud_part'] = x
        DF_GROUP['budget_total'] = DF_GROUP['bud_part']
        DF_GROUP['trp'] = DF_GROUP['budget_total'] / DF_GROUP['tcpp'] * DF_GROUP['trp_mult']
        DF_GROUP['Q'] = pd.DataFrame([DF_GROUP['trp'] / DF_GROUP['Tvr_Origin'], DF_GROUP['COUNT'] - 1]).min()
        test1 = tv_planet.calc_reach(MEMBER, DF_GROUP)
        sOPT = test1.loc[freq - 1, '%']
        return sOPT

I tried to optimize (minimize) it like this

stepmon = VerboseMonitor(50)

    solver = DifferentialEvolutionSolver2(dim=53,NP=len(init_bud))
    solver.SetConstraints(constr_reach)
    #solver.SetInitialPoints(x0=x0, radius=0.1)
    solver.SetGenerationMonitor(stepmon)
    solver.enable_signal_handler()
    solver.Solve(minimize_me_reach, termination=VTR(0.01), strategy=Best1Exp, \
                    CrossProbability=1.0, ScalingFactor=0.9, \
                    disp=True, ExtraArgs=(reach, freq, DF_GROUP, MEMBER, days))
    result = solver.Solution()
    iterations = len(stepmon)
    cost = stepmon.y[-1]
    print("Generation %d has best Chi-Squared: %f" % (iterations, cost))
    print(result)

And this

   result = diffev(minimize_me_reach, x0=x0, npop=len(init_bud), args=(reach, freq, DF_GROUP, MEMBER, days),
                    bounds=my_bounds, ftol=0.05, gtol=100,
                    maxiter=100, maxfun=None, cross=0.9, scale=0.8, full_output=False,
                    disp=1, retall=0, callback=None, strategy=Best1Exp, constraints=constr_reach)

But I have always encountered errors relative to constraint parameter. For instance.

  File "d:\tvplan\tv-planner-backend\TV_PLANER2\top_to_bottom.py", line 1179, in optimizeReach_DE
    solver.Solve(minimize_me_reach, termination=VTR(0.01), strategy=Best1Exp, \
  File "C:\Users\Mi\AppData\Local\Programs\Python\Python310\lib\site-packages\mystic\differential_evolution.py", line 661, in Solve
    super(DifferentialEvolutionSolver2, self).Solve(cost, termination,\
  File "C:\Users\Mi\AppData\Local\Programs\Python\Python310\lib\site-packages\mystic\abstract_solver.py", line 1180, in Solve
    self._Solve(cost, ExtraArgs, **settings)
  File "C:\Users\Mi\AppData\Local\Programs\Python\Python310\lib\site-packages\mystic\abstract_solver.py", line 1123, in _Solve
    stop = self.Step(**settings) #XXX: remove need to pass settings?
  File "C:\Users\Mi\AppData\Local\Programs\Python\Python310\lib\site-packages\mystic\abstract_solver.py", line 1096, in Step
    self._Step(**kwds) #FIXME: not all kwds are given in __doc__
  File "C:\Users\Mi\AppData\Local\Programs\Python\Python310\lib\site-packages\mystic\differential_evolution.py", line 552, in _Step
    self.trialSolution[candidate][:] = constraints(self.trialSolution[candidate])
TypeError: can only assign an iterable

I also tried to optimize it with set of SciPy algorithms. But had always failed. Could someone provide me example with optimization smooth function wit non-linear constraints on Mystic diffevo algorithms? Or any advise how to define my constraint to reach the solution?


Solution

  • I'm the author of mystic. First, it seems that DF_GROUP is not defined inside of constr_reach, so it may fail with a NameError. Regardless, your constraints function seems to not meet the interface requirements.

    We define an objective as y = f(x), where x is a list and y is a scalar. You can do multi-objective optimization, but let's keep it simple. Soft constraints (i.e. a penalty) are defined as y' = p(x) where x is a list, and y' is a scalar penalty that is representative of the amount that the soft constraint is violated. Hard constraints (i.e. operators / transforms / constraints) are defined as x = c(x)wherexandx'` are a list representing the inputs.

    An objective that is both hard and soft constrained (internally to mystic) looks like this:

    y' = f(c(x)) + p(x)

    Thus, c is applied to transform the inputs (critically, so it produces a list of inputs where the constraint has been applied), and penalty p is then added to the cost of the objective to penalize where the soft constraints are violated.

    There are maybe 30 examples of applying nonlinear and other complex constraints here: https://github.com/uqfoundation/mystic/tree/master/examples2

    There are several examples with even more complex constraints examples (i.e. moment constraints) here: https://github.com/uqfoundation/mystic/tree/master/examples3

    mystic provides several pre-built constraints functions that you can use to help you construct your own constraint function, if you like. They are, for the most part, in: https://github.com/uqfoundation/mystic/blob/master/mystic/constraints.py and a few more are in: https://github.com/uqfoundation/mystic/blob/master/mystic/tools.py

    A simple, but instructive, example, from the mystic.constraints doc is:

      >>> @integers()
      ... def identity(x):
      ...     return x
      ...
      >>> identity([0.123, 1.789, 4.000])
      [0, 2, 4]
    
      >>> @integers(ints=float, index=(0,3,4))
      ... def squared(x):
      ...     return [i**2 for i in x]
      ...
      >>> squared([0.12, 0.12, 4.01, 4.01, 8, 8])
      [0.0, 0.0144, 16.080099999999998, 16.0, 64.0, 64.0]