Welcome to desdeo-problem’s documentation

Contains tools to model and define multiobjective optimization problems to be used in the DESDEO framework.

Requirements

See pyproject.toml for Python package requirements.

Installation

To install and use this package on a *nix-based system, follow one of the following procedures.

For users

First, create a new virtual environment for the project. Then install the package using the following command:

$ pip install desdeo_problem

For developers

Download the code or clone it with the following command:

$ git clone https://github.com/industrial-optimization-group/desdeo-problem

Then, create a new virtual environment for the project and install the package in it:

$ cd desdeo-problem
$ poetry init
$ poetry install

API Documentation

desdeo_problem.problem Package

Desdeo-problem package

This package is for creating a problem for desdeo to solve. It includes modules for Variables, Objectives, Constraints, and actual Problem.

Functions

constraint_function_factory(lhs, rhs, operator)

A function that creates an evaluator.

variable_builder(names, initial_values[, ...])

Automatically build all variable objects.

Classes

ScalarConstraint(name, n_decision_vars, ...)

A simple scalar constraint that evaluates to a single scalar.

ConstraintError

Raised when an error related to the Constraint class in encountered.

ConstraintBase()

Base class for constraints.

ObjectiveBase()

The abstract base class for objectives.

ObjectiveError

Raised when an error related to the Objective class is encountered.

ObjectiveEvaluationResults(objectives[, ...])

The return object of <problem>.evaluate methods.

VectorObjectiveBase()

The abstract base class for multiple objectives which are calculated at once.

ScalarObjective(name, evaluator[, ...])

A simple objective function that returns a scalar.

_ScalarObjective(name, evaluator[, ...])

VectorObjective(name, evaluator[, ...])

An objective object that calculated one or more objective functions.

ScalarDataObjective(name, data[, evaluator, ...])

A simple Objective class for single valued objectives.

_ScalarDataObjective(name, data[, ...])

VectorDataObjective(name, data[, evaluator, ...])

A Objective class for multi/valued objectives.

ProblemError

Raised when an error related to the Problem class is encountered.

ProblemBase()

The base class for the problems.

EvaluationResults(objectives, fitness[, ...])

The return object of <problem>.evaluate methods.

ScalarMOProblem(objectives, variables, ...)

A multiobjective optimization problem.

ScalarDataProblem(decision_vectors, ...)

A problem class for case where the data is pre-computed.

MOProblem(objectives, variables[, ...])

An user defined multiobjective optimization problem.

DataProblem(data, variable_names, ...[, ...])

A class for a data based problem.

ExperimentalProblem(variable_names, ...[, ...])

A problem class for data-based problem.

VariableError

Raised when an error is encountered during the handling of the Variable objects.

VariableBuilderError

Raised when an error is encountered during the handling of the Variable objects.

Variable(name, initial_value[, lower_bound, ...])

Simple variable with a name, initial value and bounds.

DiscreteDataProblem(data, variable_names, ...)

A problem class for data-based problems with discrete values.

Class Inheritance Diagram
Inheritance diagram of desdeo_problem.problem.Constraint.ScalarConstraint, desdeo_problem.problem.Constraint.ConstraintError, desdeo_problem.problem.Constraint.ConstraintBase, desdeo_problem.problem.Objective.ObjectiveBase, desdeo_problem.problem.Objective.ObjectiveError, desdeo_problem.problem.Objective.ObjectiveEvaluationResults, desdeo_problem.problem.Objective.VectorObjectiveBase, desdeo_problem.problem.Objective.ScalarObjective, desdeo_problem.problem.Objective._ScalarObjective, desdeo_problem.problem.Objective.VectorObjective, desdeo_problem.problem.Objective.ScalarDataObjective, desdeo_problem.problem.Objective._ScalarDataObjective, desdeo_problem.problem.Objective.VectorDataObjective, desdeo_problem.problem.Problem.ProblemError, desdeo_problem.problem.Problem.ProblemBase, desdeo_problem.problem.Problem.EvaluationResults, desdeo_problem.problem.Problem.ScalarMOProblem, desdeo_problem.problem.Problem.ScalarDataProblem, desdeo_problem.problem.Problem.MOProblem, desdeo_problem.problem.Problem.DataProblem, desdeo_problem.problem.Problem.ExperimentalProblem, desdeo_problem.problem.Variable.VariableError, desdeo_problem.problem.Variable.VariableBuilderError, desdeo_problem.problem.Variable.Variable, desdeo_problem.problem.Problem.DiscreteDataProblem

desdeo_problem.testproblems Package

Functions

test_problem_builder(name[, n_of_variables, ...])

Build test problems.

dummy_problem()

An example on how to implement a problem with 3 objectives and 4 variables and no constraints.

desdeo_problem.surrogatemodels Package

Classes

ModelError

Raised when an error related to the surrogate models classes is encountered.

BaseRegressor()

GaussianProcessRegressor(**kwargs)

LipschitzianRegressor([L])

Class Inheritance Diagram
Inheritance diagram of desdeo_problem.surrogatemodels.SurrogateModels.ModelError, desdeo_problem.surrogatemodels.SurrogateModels.BaseRegressor, desdeo_problem.surrogatemodels.SurrogateModels.GaussianProcessRegressor, desdeo_problem.surrogatemodels.lipschitzian.LipschitzianRegressor

Examples

Analytical problem

Defining a problem with an explicit mathematical representation is straightforward.

As an example, consider the following multiobjective optimization problem:

\begin{equation} \begin{aligned} & \underset{\mathbf x}{\text{min}} & & x_1^2 - x_2; x_2^2 - 3x_1 \\ & \text{s.t.} & & x_1 + x_2 \leq 10 \\ & & & \mathbf{x} \; \in S, \\ \end{aligned} \end{equation}

where the feasible region is

\begin{equation} x_i \in \left[-5, 5\right] \; \forall i \;\in \left[1,2\right]. \end{equation}

Begin by importing the necessary classes:

[1]:
from desdeo_problem import Variable, ScalarObjective, ScalarConstraint, ScalarMOProblem

Define the variables:

[2]:
# Args: name, starting value, lower bound, upper bound
x1 = Variable("x_1", 0, -0.5, 0.5)
x2 = Variable("x_2", 0, -0.5, 0.5)

Define the objectives, notice the argument of the callable objective function, it is assumed to be array-like.

[3]:
# Args: name, callable
obj1 = ScalarObjective("f_1", lambda x: x[:,0]**2 - x[:,1])
obj2 = ScalarObjective("f_2", lambda x: x[:,1]**2 - 3*x[:,0])

Define the constraints. Constraint may depend on objective function as well (second argument to the lambda, notice the underscore). In that case, the objectives should not be defined inline, like above, but as their own function definitions. The constraint should be defined so, that when evaluated, it should return a positive value, if the constraint is adhered to, and a negative, if the constraint is breached.

[4]:
# Args: name, n of variables, n of objectives, callable
cons1 = ScalarConstraint("c_1", 2, 2, lambda x, _: 10 - (x[:,0] + x[:,1]))

Finally, put it all together and create the problem.

[5]:
# Args: list of objevtives, variables and constraints
problem = ScalarMOProblem([obj1, obj2]
                         ,[x1, x2]
                         ,[cons1])

Now, the problem is fully specified and can be evaluated and played around with.

[6]:
import numpy as np

print("N of objectives:", problem.n_of_objectives)
print("N of variables:", problem.n_of_variables)
print("N of constraints:", problem.n_of_constraints)

res1 = problem.evaluate(np.array([2, 4]))
res2 = problem.evaluate(np.array([6, 6]))
res3 = problem.evaluate(np.array([[6, 3], [4,3], [7,4]]))

print("Single feasible decision variables:", res1.objectives, "with constraint values", res1.constraints)
print("Single non-feasible decision variables:", res2.objectives, "with constraint values", res2.constraints)
print("Multiple decision variables:", res3.objectives, "with constraint values", res3.constraints)
N of objectives: 2
N of variables: 2
N of constraints: 1
Single feasible decision variables: [[ 0. 10.]] with constraint values [[4.]]
Single non-feasible decision variables: [[30. 18.]] with constraint values [[-2.]]
Multiple decision variables: [[33. -9.]
 [13. -3.]
 [45. -5.]] with constraint values [[ 1.]
 [ 3.]
 [-1.]]
[ ]:

The Problem class

An analytical problem is a problem where the mathematical formulation of the various objectives is known, as opposed to a data-driven problem, where one may need to train surrogate models to proceed with optimization.

The Problem class is the way to define optimization problems in the DESDEO framework. Once defined, the same Problem class instance can be used to solve optimization problems using various EAs from the desdeo-emo package, or the more traditional methods from the desdeo-mcdm package.

This notebook will help you understand how to instantiate a analytical problem object from scratch. The notebook will also go over other abstractions, namely classes for defining the decision variables, objectives, and the constraints, and will go over the functionalities provided by the abstractions.

Multiobjective Optimization Problem

Let’s say that we have the following minimization problem:

\begin{equation} \begin{aligned} & \underset{\mathbf x}{\text{min}} & & y_1, y_2, y_3\\ & & & y_1 = x_1 + x_2 + x_3 \\ & & & y_2 = x_1 * x_2 * x_3 \\ & & & y_3 = x_1 * x_2 + x_3 \\ & \text{s.t.} & & -2 \leq x_1 \leq 5 \\ & & & -1 \leq x_2 \leq 10 \\ & & & -0 \leq x_3 \leq 3 \\ & & & x_1 + x_2 + x_3 \leq 10 \\ & & & \mathbf{x} \; \in S, \\ \end{aligned} \end{equation}
Variables

Before instantiating the problem instance, we have to create object to define each of the variables, objectives, and constraints.

The variable objects can be created with the desdeo_problem.Variable.Variable class. This object stores the information related to the variable (such as, lower bound, upper bound, and an initial value). This information is used by the methods whenever required (such as when setting box constraints on searching algorithms or recombination operators) and for displaying results to the decision maker. Use this class to create variable objects, one variable at a time.

To define multiple Variable instances easily, use the desdeo_problem.Variable.variable_builder function. The function takes in all the necessary information for all the variables at once, and returns a List of Variable instances, one for each decision variable.

Use the help() function to know more about any function/class in the desdeo framework.

[1]:
from desdeo_problem import variable_builder

help(variable_builder)
Help on function variable_builder in module desdeo_problem.problem.Variable:

variable_builder(names: List[str], initial_values: Union[List[float], numpy.ndarray], lower_bounds: Union[List[float], numpy.ndarray] = None, upper_bounds: Union[List[float], numpy.ndarray] = None) -> List[desdeo_problem.problem.Variable.Variable]
    Automatically build all variable objects.

    Args:
        names (List[str]): Names of the variables
        initial_values (np.ndarray): Initial values taken by the variables.
        lower_bounds (Union[List[float], np.ndarray], optional): Lower bounds of the
            variables. If None, it defaults to negative infinity. Defaults to None.
        upper_bounds (Union[List[float], np.ndarray], optional): Upper bounds of the
            variables. If None, it defaults to positive infinity. Defaults to None.

    Raises:
        VariableError: Lengths of the input arrays are different.

    Returns:
        List[Variable]: List of variable objects

Let’s build the Variable objects

[2]:
var_names = ["a", "b", "c"]  #  Make sure that the variable names are meaningful to you.

initial_values = [1, 1, 1]
lower_bounds = [-2, -1, 0]
upper_bounds = [5, 10, 3]

variables = variable_builder(var_names, initial_values, lower_bounds, upper_bounds)
[3]:
print("Type of \"variables\": ", type(variables))
print("Length of \"variables\": ", len(variables))
print("Type of the contents of \"variables\": ", type(variables[0]))
Type of "variables":  <class 'list'>
Length of "variables":  3
Type of the contents of "variables":  <class 'desdeo_problem.problem.Variable.Variable'>
Objectives

Objectives are defined using tha various objective classes found within the module desdeo_problem.Objective. To define an objective class instance, one needs to pass the following:

  1. Objective name/s (Required): Name of the objective (or list of names, for multiple objective). This information will be used when displaying results to the user. Hence, these names must be understandable to the user.

  2. Evaluator (Required for analytical/simulation based objectives): An evaluator is a python Callable which takes in the decision variables as it’s input and returns the corresponding objective values. This python function can be used to connect to simulators outside the DESDEO framework.

  3. Lower bound (Not required): A lower bound for the objective. This information can be used to generate approximate ideal/nadir point during optimization.

  4. Upper bound (Not required): An upper bound for the objective. This information can be used to generate approximate ideal/nadir point during optimization.

  5. maximize (Not required): This is a boolean value that determines whether an objective is to be maximized or minimized. This is False by default (i.e. the objective is minimized).

The DESDEO framework has the following classification for objectives, based on the kind of evaluater to be used:

  1. “Scalar” objectives: If an evaluator/simulator evaluates only one objective, the objective is defined as a Scalar objective. Use the desdeo_problem.Objective._ScalarObjective class to handle such cases.

  2. “Vector” objectives: If an evaluator evaluates and returns more than one objective at once, the set of objectives is defined as Vector objective. Use the desdeo_problem.Objective.VectorObjective class to handle such cases.

Note:_ScalarObjective will be depreciated in the future, and all of it’s functionality will be handled by the VectorObjective class, which will be renamed to, simply, Objective.

To define a problem instance, the objectives may be defined as all Scalar objectives, all Vector objectives, or a mix of both, depending upon the case.

Let’s see how to define and use both kinds of Objective classes:

[4]:
from desdeo_problem import ScalarObjective, VectorObjective

import numpy as np

Define the evaluators for the objectives. These evaluators should be python functions that take in the decision variable values and give out the objective value/s. The arguments of these evaluators are 2-D Numpy arrays.

[5]:
def obj1_2(x):  #  This is a "simulator" that returns more than one objective at a time. Hence, use VectorObjective
    y1 = x[:, 0] + x[:, 1] + x[:, 2]
    y2 = x[:, 0] * x[:, 1] * x[:, 2]
    return (y1, y2)


def obj3(x):  #  This is a "simulator" that returns only one objective at a time. Hence, use ScalarObjective
    y3 = x[:, 0] * x[:, 1] + x[:, 2]
    return y3

Define the objectives. For this, you need the names of the objectives, and the evaluators defined above. If an evaluator returns multiple objective values, use the VectorObjective class to define those objectives. If an evaluator returns objective values for only one objective, either VectorObjective or ScalarObjective can be used.

If using VectorObjective, names should be provided in a list.

Additionaly, bounds of the objective values can also be provided.

[6]:
f1_2 = VectorObjective(["y1", "y2"], obj1_2)
f3 = ScalarObjective("y3", obj3, maximize=True)  # Note: f3 = VectorObjective(["y3"], obj3) will also work.
Constraints

Constraint may depend on the decision variable values, as well as the objective function.

The constraint should be defined so, that when evaluated, it should return a positive value, if the constraint is adhered to, and a negative, if the constraint is breached.

[7]:
from desdeo_problem import ScalarConstraint

const_func = lambda x, y: 10 - (x[:, 0] + x[:, 1] + x[:, 2])

# Args: name, number of variables, number of objectives, callable

cons1 = ScalarConstraint("c_1", 3, 3, const_func)
Creating the Problem object

Now that we have all the building blocks, we can create the problem object, using the desdeo_problem.Problem.MOProblem class.

Provide objectives, variables and constraints in lists.

[8]:
from desdeo_problem import MOProblem

prob = MOProblem(objectives=[f1_2, f3], variables=variables, constraints=[cons1])

The problem class provides abstractions such as the evaluate method. The method evaluates all the objective and constraint values for a given set of decision variables (in a numpy array), using the evaluators.

The abstraction also provides methods such as train and surrogate_evaluate for data driven problem. These will be tackled in the next notebook.

The output is a NamedTuple object. It contains the following elements:

  1. objectives: Contains the objective values

  2. fitness: Contains the fitness values. Fitness is either equal to the objective value, or equal to (-1 * objective value), depending upon whether the objective is to be minimized or maximized respectively. The optimization methods in the DESDEO framework internally use this value, rather than the values contained in output.objectives

  3. constraints: Contains constraint violation values.

  4. uncertainity: Contains the quantification of “uncertainity” of the evaluation

All of these values can be accessed in different ways, as shown below.

Note: Input as list of lists is not supported

[9]:
data = np.asarray([[1, -1, 0], [5, 5, 2]])
res= prob.evaluate(data)
[10]:
print(res)
# Note the sign reversal in the third objective and third fitness values because of maximization.
Evaluation Results Object
Objective values are:
[[ 0. 12. -1.]
 [ 0. 50. 27.]]
Constraint violation values are:
[[10.]
 [-2.]]
Fitness values are:
[[  0.  12.   1.]
 [  0.  50. -27.]]
Uncertainity values are:
[[nan nan nan]
 [nan nan nan]]

[11]:
print("The objective values for the given set of decision variables are: \n", res.objectives)
print("The constraint violation for the given set of decision variables are:\n", res.constraints)
The objective values for the given set of decision variables are:
 [[ 0. 12. -1.]
 [ 0. 50. 27.]]
The constraint violation for the given set of decision variables are:
 [[10.]
 [-2.]]
[12]:
res
[12]:
EvaluationResults(objectives=array([[ 0., 12., -1.],
       [ 0., 50., 27.]]), fitness=array([[  0.,  12.,   1.],
       [  0.,  50., -27.]]), constraints=array([[10.],
       [-2.]]), uncertainity=array([[nan, nan, nan],
       [nan, nan, nan]]))
[ ]:

How to make and use the test problems

Currently supported: * ZDT Problems- ZDT1-4, ZDT6 * DTLZ Problems- DTLZ1-7

Import the test problem builder

[1]:
from desdeo_problem.testproblems.TestProblems import test_problem_builder

Use test_problem_builder to build the necessary MOProblem instance, which can be used by methods in desdeo-emo and desdeo-mcdm to solve multiobjective optimization problems

[2]:
help(test_problem_builder)
Help on function test_problem_builder in module desdeo_problem.testproblems.TestProblems:

test_problem_builder(name: str, n_of_variables: int = None, n_of_objectives: int = None) -> desdeo_problem.problem.Problem.MOProblem
    Build test problems. Currently supported: ZDT1-4, ZDT6, and DTLZ1-7.

    Args:
        name (str): Name of the problem in all caps. For example: "ZDT1", "DTLZ4", etc.
        n_of_variables (int, optional): Number of variables. Required for DTLZ problems,
            but can be skipped for ZDT problems as they only support one variable value.
        n_of_objectives (int, optional): Required for DTLZ problems,
            but can be skipped for ZDT problems as they only support one variable value.

    Raises:
        ProblemError: When one of many issues occur while building the MOProblem
            instance.

    Returns:
        MOProblem: The test problem object

[3]:
zdt1 = test_problem_builder("ZDT1")
zdt1
[3]:
<desdeo_problem.problem.Problem.MOProblem at 0x1f33b279ac8>
[4]:
dtlz3 = test_problem_builder("DTLZ3", n_of_objectives= 3, n_of_variables=20)
dtlz3
[4]:
<desdeo_problem.problem.Problem.MOProblem at 0x1f33b279208>

How to use these instances for other purposes, such as generating data:

[5]:
import numpy as np

Generate input data as desired:

[6]:
number_of_samples = 3
zdt_data = np.random.random((number_of_samples, 30))  # 30 is the number of variables in the ZDT1 problem
print(zdt_data)
[[0.9566665  0.12642707 0.75401858 0.03765694 0.01986561 0.48660617
  0.49280935 0.85344899 0.27133411 0.93323257 0.84133493 0.20475801
  0.92905088 0.06490354 0.8570188  0.83492128 0.62833644 0.99593786
  0.81635487 0.82580931 0.56251793 0.97574662 0.47558831 0.3939823
  0.27397178 0.7496003  0.04909389 0.08239682 0.34656906 0.49915204]
 [0.74919761 0.28625722 0.67908382 0.6106337  0.6950148  0.25785019
  0.61746779 0.76319615 0.89890242 0.75963628 0.98161652 0.67291301
  0.79612155 0.52273917 0.20450823 0.7952598  0.60585745 0.4121897
  0.05809478 0.34800526 0.58840432 0.18724738 0.68237086 0.61657321
  0.3096879  0.32458604 0.16036243 0.82480997 0.17956196 0.01421743]
 [0.9957692  0.19364135 0.11009589 0.63606894 0.92162454 0.95342228
  0.88665615 0.74501953 0.09816078 0.48951933 0.76896919 0.64603171
  0.90088292 0.26154581 0.91006787 0.89883207 0.45426937 0.47012129
  0.01451065 0.40256939 0.2019439  0.45817166 0.56534801 0.18641038
  0.91780371 0.19666782 0.83473067 0.15496044 0.01478023 0.85545543]]
[7]:
dtlz_data = np.random.random((number_of_samples, 20)) # We put the number of variables earlier as 20

<MOProblem object>.evaluate(data) returns a tuple containing the objective values and constraint violations

[8]:
zdt_obj_val = zdt1.evaluate(zdt_data)
zdt_obj_val
[8]:
EvaluationResults(objectives=array([[0.9566665 , 3.42361516],
       [0.74919761, 3.55955498],
       [0.9957692 , 3.31853043]]), fitness=array([[0.9566665 , 3.42361516],
       [0.74919761, 3.55955498],
       [0.9957692 , 3.31853043]]), constraints=None, uncertainity=array([[nan, nan],
       [nan, nan],
       [nan, nan]]))

There are no constraints in the zdt or dtlz problems, hence cons_val is None

[9]:
dtlz_obj_val = dtlz3.evaluate(dtlz_data)
dtlz_obj_val
[9]:
EvaluationResults(objectives=array([[1307.49928399, 1214.1266996 ,  555.10066581],
       [ 248.94975775,  162.82774202, 2199.31133262],
       [1052.58290325, 1701.71639663,  600.67385014]]), fitness=array([[1307.49928399, 1214.1266996 ,  555.10066581],
       [ 248.94975775,  162.82774202, 2199.31133262],
       [1052.58290325, 1701.71639663,  600.67385014]]), constraints=None, uncertainity=array([[nan, nan, nan],
       [nan, nan, nan],
       [nan, nan, nan]]))
[ ]:

Data based problem

If the problem to be optimized has already been solved for a representation of its’ Pareto efficient front, it can be defined as a ScalarDataProblem.

Suppose we have a problem with 2 decision variables and 4 objectives. In this case, it is the river pollution problem as defined in https://ieeexplore.ieee.org/document/35354

The computed Pareto efficient solutions and the corresponding objective vector values have been computed in the file ‘riverpollution.dat’. There is a total of 500 entries. Begin by importing relevant classes and laoding the data.

[2]:
# NOTE: This will soon be depreciated.
from desdeo_problem import ScalarDataProblem

import numpy as np

data = np.loadtxt("./data/riverpollution.dat")

The first 2 entries of each row are the decision variables, and the last 4 the objective function values.

[3]:
xs, fs = data[:, 0:2], data[:, 2:]

The problem can now be defined:

[4]:
problem = ScalarDataProblem(xs, fs)

That’s it. Now the problem is defined and can be further utilized. Notice that there are no constraints. It is assumed that all the entries in the data file are feasible. The warning has to do with the fact, that the data is discrete, therefore the evaluations for specific decision variable values have to be approximated somehow. At the moment, the closest pair of decision variables is searched for in the data. Later on, a surrogate model for the data might be build to have a better approximation.

[5]:
print("N of objectives:", problem.n_of_objectives)
print("N of variables:", problem.n_of_variables)

print("Single decision vector evaluaion:", problem.evaluate([0.4, 0.5]))
N of objectives: 4
N of variables: 2
Single decision vector evaluaion: (array([-5.6304735 , -2.87892556, -7.06008234,  0.05013393]),)
[ ]:

Data Based Problems

The DESDEO framework provides handling of data-driven optimization problems. Some methods, such as E-NAUTILUS in desdeo-mcdm, find the most preffered solution from a provided dataset. Other methods, such as most of the EA’s from desdeo-emo, require a surrogate model to be trained for each of the objectives. The desdeo_problem provides support for both of these cases.

For data based problems, use the data specific objective/problem classes

[1]:
import pandas as pd
import numpy as np

VectorDataObjective is an objective class that can handle data, as well as multi-objective evaluators.

The GaussianProcessRegressor here is same as the one in scikit-learn with one small difference. The predict method has been replaced to return uncertainity values (in the form of standard deviation of the prediction) by default. It supports hyperparameters in the same format as the sklearn method.

[2]:
from desdeo_problem import VectorDataObjective as VDO
from desdeo_problem.surrogatemodels.SurrogateModels import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import Matern
Creating some random data

‘a’ and ‘b’ are randomly generated between 0 and 1.

f1 = a + b
f2 = a * b

For data-driven problems, make sure that the input dataset is in the pandas DataFrame format, with the column names being the same as the variable/objective names.

[3]:
data = np.random.rand(100,2)

f1 = (data[:,0]+data[:,1]).reshape(-1,1)
f2 = (data[:,0]*data[:,1]).reshape(-1,1)

data = np.hstack((data, f1, f2))

X = ['a','b']
y = ['f1','f2']
datapd = pd.DataFrame(data, columns=X+y)
datapd.head()
[3]:
a b f1 f2
0 0.730445 0.051709 0.782154 0.037771
1 0.039999 0.693915 0.733915 0.027756
2 0.024957 0.584356 0.609313 0.014584
3 0.774647 0.427847 1.202494 0.331430
4 0.500549 0.012714 0.513263 0.006364
Using VectorDataObjective class

The VectorDataObjective class takes as its input the data in a dataframe format and the objective names in a list.

[4]:
obj = VDO(data=datapd, name=y)
Training surrogate models

Pass the surrogate modelling technique and the model parameters to the train method of the objective instance.

If only one modelling technique is passed, the model_parameters should be a dict (or None) and this will be used for all the objectives.

If multiple modelling techniques are passed, models should be the list of modelling techniques, and model_parameters should be a list of dicts. The length of these lists should be the same as the number of objectives and each list element will be used to train one objective in order.

[5]:
obj.train(models=GaussianProcessRegressor, model_parameters={'kernel': Matern(nu=1.5)})
Using surrogate models to evaluate objective values

Use the obj.evaluate method to get predictions. Note that use_surrogates should be true.

[6]:
print(obj.evaluate(np.asarray([[0.5,0.3]]), use_surrogate=True))
Objective Evaluation Results Object
Objective values are:
         f1        f2
0  0.800011  0.150043
Uncertainity values are:
         f1        f2
0  0.000615  0.001536

[7]:
obj._model_trained
[7]:
{'f1': True, 'f2': True}
Creating data problem class

Creating the objective class should be bypassed for now, use DataProblem class directly with the data in a dataframe.

The DataProblem provides a train method which trains all the objectives sequentially. The input arguments for this train method is the same as that of the VectorDataObjective class.

To make sure that the evaluate method uses the surrogate models for evaluations, pass the use_surrogate=True argument.

[8]:
from desdeo_problem import DataProblem
[9]:
maximize = pd.DataFrame([[True, False]], columns=['f1','f2'])
prob = DataProblem(data=datapd, objective_names=y, variable_names=X, maximize=maximize)
[10]:
prob.train(GaussianProcessRegressor)
[11]:
print(prob.evaluate(np.asarray([[0.1,0.8], [0.5,0.3]]), use_surrogate=True))
Evaluation Results Object
Objective values are:
[[0.89999924 0.08000021]
 [0.8        0.14999895]]
Constraint violation values are:
None
Fitness values are:
[[-0.89999924  0.08000021]
 [-0.8         0.14999895]]
Uncertainity values are:
[[0.00346248 0.00346248]
 [0.00374312 0.00374312]]

Lipschitian models
[12]:
from desdeo_problem.surrogatemodels.lipschitzian import LipschitzianRegressor
[13]:
prob = DataProblem(data=datapd, objective_names=y, variable_names=X)
[14]:
prob.train(LipschitzianRegressor)
[15]:
print(prob.evaluate(np.asarray([[0.1,0.8], [0.5,0.3]]), use_surrogate=True))
Evaluation Results Object
Objective values are:
[[0.9        0.08734629]
 [0.8        0.16056562]]
Constraint violation values are:
None
Fitness values are:
[[0.9        0.08734629]
 [0.8        0.16056562]]
Uncertainity values are:
[[3.33066907e-16 1.74576926e-02]
 [6.10622664e-16 7.72293212e-02]]

[ ]:

How to use Lipschitzian modelling

[1]:
import numpy as np
import pandas as pd
from desdeo_problem.surrogatemodels.lipschitzian import LipschitzianRegressor
import matplotlib.pyplot as plt
[2]:
def y_func(x):
    return(np.sin(x) + np.sin(2*x))
[3]:
num_points = 20
x = np.linspace(0, 2 * np.pi, num_points).reshape(-1,1)
y = y_func(x)
data = pd.DataFrame(np.hstack((x,y)), columns = ['x','y'])
[4]:
model = LipschitzianRegressor()
[5]:
model.fit(X=data['x'], y = data['y'])
[6]:
model.L
[6]:
2.8392177826759726
[7]:
model.y
[7]:
array([[ 0.00000000e+00],
       [ 9.38912182e-01],
       [ 1.58361298e+00],
       [ 1.75293980e+00],
       [ 1.44534766e+00],
       [ 8.31989903e-01],
       [ 1.80049416e-01],
       [-2.60860582e-01],
       [-3.61219085e-01],
       [-1.60104879e-01],
       [ 1.60104879e-01],
       [ 3.61219085e-01],
       [ 2.60860582e-01],
       [-1.80049416e-01],
       [-8.31989903e-01],
       [-1.44534766e+00],
       [-1.75293980e+00],
       [-1.58361298e+00],
       [-9.38912182e-01],
       [-7.34788079e-16]])
[8]:
x_new = np.linspace(0, 2*np.pi, 1000).reshape(-1,1)
y_new_true = y_func(x_new)
y_mean, y_delta = model.predict(x_new)
[9]:
plt.scatter(x_new, y_new_true, marker='.')
plt.scatter(x_new, y_mean, marker='.')
plt.scatter(x_new, y_mean + y_delta, marker='.')
plt.scatter(x_new, y_mean - y_delta, marker='.')
[9]:
<matplotlib.collections.PathCollection at 0x2c4d3b5d9c8>
_images/notebooks_Lipschitzian_modelling_9_1.png
[10]:
def y_func2d(x):
    return(np.sin(x[:,0]) + np.sin(2*x[:,1]))
[11]:
x = np.random.rand(200,2)
y = y_func2d(x).reshape(-1,1)
data = pd.DataFrame(np.hstack((x,y)), columns = ['x1', 'x2','y'])
[12]:
model = LipschitzianRegressor()
model.fit(X=data[['x1', 'x2']], y = data['y'])
[13]:
x_new = np.random.rand(2000,2)
y_new_true = y_func2d(x_new).reshape(-1,1)
y_predict, y_delta = model.predict(x_new)
[14]:
line = np.linspace(y_new_true.min(),y_new_true.max(),200)
plt.scatter(y_new_true, y_predict.reshape(-1,1), marker=".")
plt.scatter(y_new_true, (y_predict+y_delta).reshape(-1,1), marker=".", alpha=0.3)
plt.scatter(y_new_true, (y_predict-y_delta).reshape(-1,1), marker=".", alpha=0.3)
plt.scatter(line,line, marker='.')
[14]:
<matplotlib.collections.PathCollection at 0x2c4d4c56c08>
_images/notebooks_Lipschitzian_modelling_14_1.png
[ ]:

Indices and tables