Sunday, March 3, 2013

#20 - JMOO

MOO (Multi-Objective Optimization) is a field of Software Engineering that involves the solving of problems that have decisions which affect the objective goals.  Formally, the mathematical expression of the problem is as follows.

A Multi-Objective Problem (MOP) consists of a set of n decisions and k objectives.  The decisions all have upper and lower bounds as well as constraints that the decisions must all fall within.  The objectives to a problem are similar to decisions, but we do not know their bounds.  Instead, we just know our goals, which are to maximize or minimize each as specified by each objective's optimization direction.  The objective functions provide the evaluation model of mapping decisions to objective scores.  However sometimes, the problem doesn't have a simple set of functions for this; instead it may rather be a process or a set of steps that cannot be easily modeled.  Either way, the objective functions provide a way to map decisions to objectives.

JMOO is our modeling language (Joe's Multi-Objective Optimization), implemented in Python.  The framework is extremely simple and provides an easy way of devising your own MOPs.  Below we show code for implementing the Srinivas MOP.


class srinivas(jmoo_problem):
  def __init__(prob):
    prob.name = "Srinivas"
    prob.decisions = [jmoo_decision("x" + str(i+1), -20, 20) for i in range(2)]
    prob.objectives = [jmoo_objective("f1", True), jmoo_objective("f2", True)]
  def evaluate(prob,input = None):
    if input:
        for i,decision in enumerate(prob.decisions):
            decision.value = input[i]
    x1,x2 = prob.decisions[0].value, prob.decisions[1].value
    prob.objectives[0].value = (x1-2)**2 + (x2 - 1)**2 + 2  
    prob.objectives[1].value = 9*x1 - (x2 - 1)**2
    return [objective.value for objective in prob.objectives]
  def evalConstraints(prob):
    x1,x2 = prob.decisions[0].value, prob.decisions[1].value
    if (x1**2 +   x2**2 > 225): return True
    if (x1    - 3*x2    > -10): return True
    return False


Each MOP is implemented as a subclass to the jmoo_problem base class.  The __init__ method provides information about the MOP's objectives (names, optimization direction) and decisions (name, lower bound, upper bound), using list comprehensions to put them in lists.  The use of decisions and objectives work with the jmoo_decision and jmoo_objective classes, which both act as relatively simple record data types.  The base class implements a hidden generateInput method which will generate a set of inputs compliant to each decision's bounds and the constraints (using the evalConstraints method).  If the problem has no constraints, then the evalConstraints method simply returns False.  The evaluate method on the other hand, will evaluate the decisions and set the objective values.  If you simply call the evaluate problem with no parameters, it will evaluate the decision's current values, but you must be sure to call generateInput before hand.  You can however, supply your own decision values as input if using generateInput doesn't suit your needs (due to generating randomly).

To simplify the process of adding your own MOP, use the following as a scaffolding.  To see how simple it is, just observe the bolded italic lines as being the only lines you need to adjust.


class my_new_mop(jmoo_problem):
  def __init__(prob):
    prob.name = "What to call my new MOP?"
    LOWS = [1, 2, 3, 4, 5]
    UPS = [10, 20, 30, 40, 50]
    OD = optimalDirections = [True, True, False]
    n = numDecisions = 5
    k = numObjectives = 3
    prob.decisions=[jmoo_decision("x"+str(i+1),LOWS[i],UPS[i]) for i in range(n)]
    prob.objectives=[jmoo_objective("f"+str(i+1), OD[i]) for i in range(k)]
  def evaluate(prob,input = None):
    if input:
        for i,decision in enumerate(prob.decisions):
            decision.value = input[i]
    X = [prob.decisions[i].value for i in range(len(prob.decisions))]
    prob.objectives[0].value = 1 # TODO
    prob.objectives[2].value = 2 # TODO
    prob.objectives[3].value = 3 # TODO
    return [objective.value for objective in prob.objectives]
  def evalConstraints(prob):
    X = [prob.decisions[i].value for i in range(len(prob.decisions))]
    #TODO: if (expression involving the X falls out of bounds): return True
    #TODO: if (expression involving the X falls out of bounds): return True
return False


No comments:

Post a Comment