Skip to content

Commit

Permalink
linear programming in simus done
Browse files Browse the repository at this point in the history
  • Loading branch information
leliel12 committed Feb 19, 2018
1 parent ceadf2d commit 3f80ab8
Show file tree
Hide file tree
Showing 7 changed files with 293 additions and 27 deletions.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@

REQUIREMENTS = [
"numpy", "scipy", "six", "mock", "tabulate", "matplotlib", "pulp",
"json-tricks", "joblib" # this is for io
"json-tricks", "joblib", "attrs"
]


Expand Down
6 changes: 4 additions & 2 deletions skcriteria/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ def preprocess(self, data):
return Data(mtx=nmtx, criteria=data.criteria, weights=nweights,
anames=data.anames, cnames=data.cnames)

def decide(self, data, criteria=None, weights=None):
def decide(self, data, criteria=None, weights=None, **kwargs):
"""Execute the Solver over the given data.
Parameters
Expand All @@ -383,6 +383,8 @@ def decide(self, data, criteria=None, weights=None):
- If data is 2d array_like and weights are 1d array_like with `m`
elements (number of criteria); the i-nth element represent the
importance of the i-nth criteria.
kwargs : optional
keywords arguments for the solve method
Returns
-------
Expand All @@ -401,7 +403,7 @@ def decide(self, data, criteria=None, weights=None):
"provide a 'criteria' array")
data = Data(data, criteria, weights)
pdata = self.preprocess(data)
result = self.solve(pdata)
result = self.solve(pdata, **kwargs)
return self.make_result(data, *result)

@abc.abstractmethod
Expand Down
2 changes: 1 addition & 1 deletion skcriteria/madm/_dmaker.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ def make_result(self, data, kernel, rank, extra):
-------
:py:class:`skcriteria.madm.Decision`
A convenient instance conraining all the parameters.
A convenient instance containing all the parameters.
"""
decision = Decision(
Expand Down
166 changes: 166 additions & 0 deletions skcriteria/madm/simus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Copyright (c) 2016-2017, Cabral, Juan; Luczywo, Nadia
# All rights reserved.

# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:

# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.

# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.

# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.

# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.


# =============================================================================
# FUTURE
# =============================================================================

from __future__ import unicode_literals


# =============================================================================
# DOCS
# =============================================================================

__doc__ = """Simus methods"""

__all__ = [
"SIMUS"]


# =============================================================================
# IMPORTS
# =============================================================================

import operator

import numpy as np

import joblib

from ..validate import MAX, MIN
from ..utils import lp
from ..utils.doc_inherit import doc_inherit

from ._dmaker import DecisionMaker


# =============================================================================
# FUNCTIONS
# =============================================================================

def make_stage(mtx, b, senses, zindex, solver):
# retrieve the problem class
problem = lp.Minimize if senses[zindex] == MIN else lp.Maximize

# create the variables
xs = tuple(
lp.Float("x{}".format(idx), low=0) for idx in range(mtx.shape[1]))

# create the objective function
z_coef = mtx[zindex]
z = sum(c * x for c, x in zip(z_coef, xs))

# the conditions
conditions = []
for idx in range(mtx.shape[0]):
if idx == zindex:
continue
coef = mtx[idx]

left = sum(c * x for c, x in zip(coef, xs))
op = operator.le if senses[idx] == MAX else operator.ge
right = b[idx]

condition = op(left, right)
conditions.append(condition)
stage = problem(z=z, solver=solver).sa(*conditions)
return stage


def solve(stage):
return stage.solve()


def simus(nmtx, ncriteria, nweights, b=None, solver="pulp", njobs=None):

t_nmtx = nmtx.T

b = np.asarray(b)
if None in b:
mins = np.min(t_nmtx, axis=1)
maxs = np.max(t_nmtx, axis=1)

auto_b = np.where(ncriteria == MAX, maxs, mins)
b = np.where(b.astype(bool), b, auto_b)

njobs = njobs or joblib.cpu_count()
with joblib.Parallel(n_jobs=njobs) as jobs:
stages = jobs(
joblib.delayed(make_stage)(
mtx=t_nmtx, b=b, senses=ncriteria, zindex=idx, solver=solver)
for idx in range(t_nmtx.shape[0]))

results = jobs(
joblib.delayed(solve)(stage) for stage in stages)

return None, results


# =============================================================================
# OO
# =============================================================================

class SIMUS(DecisionMaker):
r"""
"""

def __init__(self, mnorm="none", wnorm="none", solver="pulp", njobs=None):
super(SIMUS, self).__init__(mnorm=mnorm, wnorm=wnorm)
self._solver = solver
self._njobs = njobs

@doc_inherit
def solve(self, ndata, b):
nmtx, ncriteria, nweights = ndata.mtx, ndata.criteria, ndata.weights
rank, points = simus(
nmtx, ncriteria, nweights,
b=b, solver=self._solver, njobs=self._njobs)
return None, rank, {"points": points}

@property
def solver(self):
return self._solver

@property
def njobs(self):
return self._njobs


# =============================================================================
# MAIN
# =============================================================================

if __name__ == "__main__":
print(__doc__)
84 changes: 84 additions & 0 deletions skcriteria/tests/madm/test_simus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Copyright (c) 2016-2017-2018, Cabral, Juan; Luczywo, Nadia
# All rights reserved.

# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:

# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.

# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.

# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.

# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.


# =============================================================================
# FUTURE
# =============================================================================

from __future__ import unicode_literals


# =============================================================================
# DOC
# =============================================================================

__doc__ = """test simple methods"""


# =============================================================================
# IMPORTS
# =============================================================================

from skcriteria import Data
from ...madm import simus

from ..tcore import SKCriteriaTestCase


# =============================================================================
# Tests
# =============================================================================

class SimusTest(SKCriteriaTestCase):
mnorm = "sum"
wnorm = "sum"

def setUp(self):
# Data From:
# Munier, N., Carignano, C., & Alberto, C.
# UN MÉTODO DE PROGRAMACIÓN MULTIOBJETIVO.
# Revista de la Escuela de Perfeccionamiento en Investigación
# Operativa, 24(39).

self.data = Data(
mtx=[[250, 120, 20, 800],
[130, 200, 40, 1000],
[350, 340, 15, 600]],
criteria=[max, max, min, max],
anames=["Proyecto 1", "Proyecto 2", "Proyecto 3"],
cnames=["Criterio 1", "Criterio 2", "Criterio 3", "Criterio 4"])
self.b = [None, 500, None, None]

def test_simus(self):
dm = simus.SIMUS()
dm.decide(self.data, b=self.b)
56 changes: 34 additions & 22 deletions skcriteria/utils/lp.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
# =============================================================================

import operator
from collections import Mapping

import pulp

Expand All @@ -55,12 +54,6 @@
# CONSTANTS
# =============================================================================

VAR_TYPE = {
int: pulp.LpInteger,
float: pulp.LpContinuous,
bool: pulp.LpBinary
}

SOLVERS = {
"pulp": pulp.solvers.PULP_CBC_CMD,

Expand Down Expand Up @@ -115,12 +108,35 @@ class Result(object):
variables = attr.ib(converter=Bunch)


class Var(pulp.LpVariable):
# =============================================================================
# VARIABLES
# =============================================================================

def __init__(self, name, low=None, up=None, type=float, *args, **kwargs):
super(Var, self).__init__(
class _Var(pulp.LpVariable):

def __init__(self, name, low=None, up=None, *args, **kwargs):
super(_Var, self).__init__(
name=name, lowBound=low, upBound=up,
cat=VAR_TYPE[type], *args, **kwargs)
cat=self.var_type, *args, **kwargs)


class Float(_Var):
var_type = pulp.LpContinuous


class Int(_Var):
var_type = pulp.LpInteger


class Bool(_Var):
var_type = pulp.LpBinary


VAR_TYPE = {
int: Int,
float: Float,
bool: Bool
}


# =============================================================================
Expand All @@ -147,15 +163,11 @@ def frommtx(cls, c, A, b, x=None, *args, **kwargs):
else:
x_n = len(x)

xkwds = []
for xi in x:
if xi is None:
xi = {"low": 0, "type": float}
elif xi in VAR_TYPE:
xi = {"low": 0, "type": xi}
xkwds.append(xi)

x_s = [Var("x{}".format(idx + 1), **xkwds[idx]) for idx in range(x_n)]
x_s = []
for idx, xi in enumerate(x):
cls = VAR_TYPE.get(type(xi), Float)
x = cls("x{}".format(idx + 1), low=0)
x_s.append(x)

# Z
z = sum([ci * xi for ci, xi in zip(c, x_s)])
Expand Down Expand Up @@ -185,8 +197,8 @@ def solve(self):
super(_LP, self).solve()
objective = pulp.value(self.objective)
variables = {v.name: v.varValue for v in self.variables()}
status=pulp.LpStatus[self.status]
model.assignVarsVals(dict.fromkeys(variables, None))
status = pulp.LpStatus[self.status]
self.assignVarsVals(dict.fromkeys(variables, None))
return Result(status_code=self.status,
status=status,
objective=objective,
Expand Down

0 comments on commit 3f80ab8

Please sign in to comment.