Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mandd/multipleknapsack models #17

Merged
merged 21 commits into from
Feb 16, 2021
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
from LOGOS.src import CapitalInvestmentModel
from LOGOS.src import BatteryReplacementCashFlowModel
from LOGOS.src import IncrementalNPV
from LOGOS.src.knapsack import BaseKnapsackModel
from LOGOS.src.knapsack import MultipleKnapsackModel
from LOGOS.src.knapsack import BaseKnapsackModel
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You do not need to import BaseKnapsackModel since this model can not be directly used by RAVEN.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point fixed

67 changes: 58 additions & 9 deletions doc/user_manual/include/Knapsack.tex
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ \section{Knapsack Models}
find the optimal solution.


\subsection{BaseKnapsackModel}
\subsection{BaseKnapsack Model}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BaseKnapsackModel --> SimpleKnapsackModel? If so, please update this section.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch, fixed all section

\label{subsec:BaseKnapsackModel}
This model considers the classical Knapsack Model characterized by a set of elements
that can be chosen (or not).
The goal is to maximize the sum of the chosen element values provided that the sum of
element cost values satisfy capacity constraint (specified in the \xmlNode{capacity} node).
element cost values satisfy capacity constraint (specified in the variable defined
in the \xmlNode{capacity} node).

The ID of the variables that represent cost, value, and choice of each element are
indicated in the \xmlNode{map} node.
Expand All @@ -30,21 +31,69 @@ \subsection{BaseKnapsackModel}
is not satisfied, then the \xmlNode{choiceValue} variable is penalized by multiplying the
project value by -\xmlNode{penaltyFactor}.

Example LOGOS input XML for DRO:
Example LOGOS input XML for BaseKnapsack Model:
\begin{lstlisting}[style=XML]
<Models>
<ExternalModel name="knapsack" subType="LOGOS.BaseKnapsackModel">
<variables>element1Status,element2Status,element3Status,
element1Val ,element2Val ,element3Val,
element1Cost ,element2Cost ,element3Cost,
validity ,totalValue </variables>
<capacity>10</capacity>
<variables>element1Status,element2Status,element3Status,element4Status,element5Status,
element1Val ,element2Val ,element3Val ,element4Val ,element5Val,
element1Cost ,element2Cost ,element3Cost ,element4Cost,element5Cost,
validity,totalValue,capacityID</variables>
<capacity>capacityID</capacity>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, capacity is required input/variable from RAVEN perspective, and you do not allow user to directly provide this value. Right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is correct, the goal is to have knapsack parameters as raven generated variables

<penaltyFactor>1.</penaltyFactor>
<outcome>validity</outcome>
<choiceValue>totalValue</choiceValue>
<map value='element1Val' cost='element1Cost' >element1Status</map>
<map value='element2Val' cost='element2Cost' >element2Status</map>
<map value='element3Val' cost='element3Cost' >element3Status</map>
<map value='element4Val' cost='element4Cost' >element4Status</map>
<map value='element5Val' cost='element5Cost' >element5Status</map>
</ExternalModel>
\end{lstlisting}


\subsection{MultipleKnapsack Model}
\label{subsec:MultipleKnapsackModel}
This model considers the Multiple Knapsack Model characterized by a set of elements
that can be chosen (or not) over a set of multiple knapsacks.
The goal is to maximize the sum of the chosen element values provided that the sum of
element cost values satisfy capacity constraints of each knapsack.

The capacity of each knapsack is defined in the \xmlNode{knapsack} node.

The ID of the variables that represent cost, value, and choice of each element are
indicated in the \xmlNode{map} node.
The model generates two variables:
\begin{itemize}
\item the validity of the chosen solution (specified in the \xmlNode{outcome} node): either
valid (i.e., 0), or invalid (i.e., 1) if the capacity constraint is not satisfied,
\item totalValue (specified in the \xmlNode{choiceValue} node): sum of the values of the
chosen elements
\end{itemize}

When calculating the \xmlNode{choiceValue} variable, if the capacity constraints
are not satisfied, then the \xmlNode{choiceValue} variable is penalized by multiplying the
project value by -\xmlNode{penaltyFactor}.

Example LOGOS input XML for MultipleKnapsack Model:
\begin{lstlisting}[style=XML]
<Models>
<ExternalModel name="knapsack" subType="LOGOS.MultipleKnapsackModel">
<variables>e1Status,e2Status,e3Status,e4Status,e5Status,
e1Val ,e2Val ,e3Val ,e4Val ,e5Val,
e1Cost ,e2Cost ,e3Cost ,e4Cost ,e5Cost,
validity,totalValue,
K1_cap,K2_cap,K3_cap</variables>
<knapsack ID='1'>K1_cap</knapsack>
<knapsack ID='2'>K2_cap</knapsack>
<knapsack ID='3'>K3_cap</knapsack>
<penaltyFactor>1.</penaltyFactor>
<outcome>validity</outcome>
<choiceValue>totalValue</choiceValue>
<map value='e1Val' cost='e1Cost' >e1Status</map>
<map value='e2Val' cost='e2Cost' >e2Status</map>
<map value='e3Val' cost='e3Cost' >e3Status</map>
<map value='e4Val' cost='e4Cost' >e4Status</map>
<map value='e5Val' cost='e5Cost' >e5Status</map>
</ExternalModel>
</Models>
\end{lstlisting}
2 changes: 1 addition & 1 deletion src/CapitalInvestments/PyomoModels/MultipleKnapsack.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def addVariables(self, model):
"""
model = KnapsackBase.addVariables(self, model)
def boundsExpression(model, i, j):
""" set the bounds for soluion variable x using lowerBounds and upperBounds"""
""" set the bounds for solution variable x using lowerBounds and upperBounds"""
return (self.lowerBounds[i], self.upperBounds[i])
model.x = pyomo.Var(model.investments, model.capitals, domain=pyomo.NonNegativeIntegers, bounds=boundsExpression)
return model
Expand Down
54 changes: 12 additions & 42 deletions src/knapsack/BaseKnapsackModel.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
"""

#External Modules---------------------------------------------------------------
import numpy as np
import math
import copy

#External Modules End-----------------------------------------------------------

#Internal Modules---------------------------------------------------------------
Expand All @@ -20,8 +18,9 @@

class BaseKnapsackModel(ExternalModelPluginBase):
"""
This class is designed to create the BaseKnapsack model
This class is designed to create the base class for the knapsack models
"""

@classmethod
def getInputSpecs(cls):
"""
Expand All @@ -32,19 +31,16 @@ def getInputSpecs(cls):
inputSpecs = InputData.parameterInputFactory('ExternalModel')
inputSpecs.addParam('name', param_type=InputTypes.StringType, required=True)
inputSpecs.addParam('subType', param_type=InputTypes.StringType, required=True)
inputSpecs.addSub(InputData.parameterInputFactory('capacity', contentType=InputTypes.FloatType))

inputSpecs.addSub(InputData.parameterInputFactory('penaltyFactor', contentType=InputTypes.FloatType))
inputSpecs.addSub(InputData.parameterInputFactory('outcome', contentType=InputTypes.StringType))
inputSpecs.addSub(InputData.parameterInputFactory('choiceValue', contentType=InputTypes.StringType))
inputSpecs.addSub(InputData.parameterInputFactory('variables', contentType=InputTypes.StringListType))
map = InputData.parameterInputFactory('map', contentType=InputTypes.StringType)
map.addParam('value', param_type=InputTypes.StringType, required=True)
map.addParam('cost', param_type=InputTypes.StringType, required=True)
inputSpecs.addSub(map)
alias = InputData.parameterInputFactory('alias', contentType=InputTypes.StringType)
alias.addParam('variable', param_type=InputTypes.StringType, required=True)
alias.addParam('type', param_type=InputTypes.StringType, required=True)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to keep alias, this can be useful. The alias is handled directly by Model.py inside RAVEN without using InputData. If you want to keep this capability, we need to keep these three lines to let knapsack external model aware of it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed because I thought I merely did a copy and paste; restored

inputSpecs.addSub(alias)

mapping = InputData.parameterInputFactory('map', contentType=InputTypes.StringType)
mapping.addParam('value', param_type=InputTypes.StringType, required=True)
mapping.addParam('cost', param_type=InputTypes.StringType, required=True)
inputSpecs.addSub(mapping)

return inputSpecs

Expand All @@ -56,7 +52,6 @@ def __init__(self):
"""
ExternalModelPluginBase.__init__(self)

self.capacity = None # capacity value of the knapsack
self.penaltyFactor = 1.0 # penalty factor that is used when the capacity constraint is not satisfied
self.outcome = None # ID of the variable which indicates if the chosen elements satisfy the capacity constraint
self.choiceValue = None # ID of the variable which indicates the sum of the values of the chosen project elements
Expand All @@ -69,14 +64,13 @@ def _readMoreXML(self, container, xmlNode):
@ Out, None
"""
container.mapping = {}

specs = self.getInputSpecs()()
specs.parseNode(xmlNode)
for node in specs.subparts:
name = node.getName()
val = node.value
if name == 'capacity':
self.capacity = val
elif name == 'penaltyFactor':
if name == 'penaltyFactor':
self.penaltyFactor = val
elif name == 'outcome':
self.outcome = val
Expand Down Expand Up @@ -107,28 +101,4 @@ def run(self, container, inputDict):
@ In, container, object, self-like object where all the variables can be stored
@ In, inputDict, dict, dictionary of inputs from RAVEN
"""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can make the run class as abstractmethod

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

totalValue = 0.0

for key in container.mapping:
if key in inputDict.keys() and inputDict[key] in [0.0,1.0]:
if inputDict[key] == 1.0:
testValue = self.capacity - inputDict[container.mapping[key][1]]
if testValue >= 0:
self.capacity = self.capacity - inputDict[container.mapping[key][1]]
totalValue = totalValue + inputDict[container.mapping[key][0]]
else:
self.capacity = self.capacity - inputDict[container.mapping[key][1]]
totalValue = totalValue - inputDict[container.mapping[key][0]] * self.penaltyFactor
elif inputDict[key] == 0.0:
pass
else:
raise IOError("BaseKnapsackModel: variable " + str(key) + " does not have a 0/1 value.")
else:
raise IOError("BaseKnapsackModel: variable " + str(key) + " is not found in the set of input variables.")

if self.capacity>=0:
container.__dict__[self.outcome] = 0.
else:
container.__dict__[self.outcome] = 1.

container.__dict__[self.choiceValue] = totalValue
pass
155 changes: 155 additions & 0 deletions src/knapsack/MultipleKnapsackModel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Copyright 2020, Battelle Energy Alliance, LLC
# ALL RIGHTS RESERVED
"""
Created on February 7, 2021

@author: mandd
"""

#External Modules---------------------------------------------------------------
import numpy as np
import math
import copy
#External Modules End-----------------------------------------------------------

#Internal Modules---------------------------------------------------------------
from PluginsBaseClasses.ExternalModelPluginBase import ExternalModelPluginBase
from utils import InputData, InputTypes
#Internal Modules End-----------------------------------------------------------


class MultipleKnapsackModel(ExternalModelPluginBase):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you inherit directly from BaseKnapsackModel?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a subset of elements that are in common between these two classes.
We could design a baseClass for all knapsack models.
I am open to suggestions.

"""
This class is designed to create the MultipleKnapsack model
"""

@classmethod
def getInputSpecs(cls):
"""
Collects input specifications for this class.
@ In, None
@ Out, inputSpecs, InputData, input specifications
"""
inputSpecs = InputData.parameterInputFactory('ExternalModel')
inputSpecs.addParam('name' , param_type=InputTypes.StringType, required=True)
inputSpecs.addParam('subType', param_type=InputTypes.StringType, required=True)

inputSpecs.addSub(InputData.parameterInputFactory('penaltyFactor', contentType=InputTypes.FloatType))
inputSpecs.addSub(InputData.parameterInputFactory('outcome' , contentType=InputTypes.StringType))
inputSpecs.addSub(InputData.parameterInputFactory('choiceValue' , contentType=InputTypes.StringType))

knapsack = InputData.parameterInputFactory('knapsack', contentType=InputTypes.StringType)
knapsack.addParam('ID', param_type=InputTypes.StringType, required=True)
inputSpecs.addSub(knapsack)

mapping = InputData.parameterInputFactory('map', contentType=InputTypes.StringType)
mapping.addParam('value', param_type=InputTypes.StringType, required=True)
mapping.addParam('cost', param_type=InputTypes.StringType, required=True)
inputSpecs.addSub(mapping)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you inherit directly from BaseKnapsackModel, most of these lines can be removed

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, having a base class would simplify the structure of getInputSpecs and _readMoreXML methods.


inputSpecs.addSub(InputData.parameterInputFactory('variables', contentType=InputTypes.StringListType))
map = InputData.parameterInputFactory('map', contentType=InputTypes.StringType)
map.addParam('value', param_type=InputTypes.StringType, required=True)
map.addParam('cost', param_type=InputTypes.StringType, required=True)
inputSpecs.addSub(map)

return inputSpecs

def __init__(self):
"""
Constructor
@ In, None
@ Out, None
"""
ExternalModelPluginBase.__init__(self)

self.penaltyFactor = 1.0 # penalty factor that is used when the capacity constraint is not satisfied
self.outcome = None # ID of the variable which indicates if the chosen elements satisfy the capacity constraint
self.choiceValue = None # ID of the variable which indicates the sum of the values of the chosen project elements

def _readMoreXML(self, container, xmlNode):
"""
Method to read the portion of the XML that belongs to the MultipleKnapsack model
@ In, container, object, self-like object where all the variables can be stored
@ In, xmlNode, xml.etree.ElementTree.Element, XML node that needs to be read
@ Out, None
"""
container.mapping = {}
self.knapsackSet = {}

specs = self.getInputSpecs()()
specs.parseNode(xmlNode)

for node in specs.subparts:
name = node.getName()
val = node.value
if name == 'penaltyFactor':
self.penaltyFactor = val
elif name == 'outcome':
self.outcome = val
elif name == 'choiceValue':
self.choiceValue = val
elif name == 'knapsack':
self.knapsackSet[node.parameterValues['ID']] = val
elif name == 'map':
container.mapping[val] = [node.parameterValues['value'],node.parameterValues['cost']]
elif name == 'variables':
variables = val
else:
raise IOError("MultipleKnapsackModel: xml node " + str(name) + " is not allowed")


def initialize(self, container, runInfoDict, inputFiles):
"""
Method to initialize the MultipleKnapsack model
@ In, container, object, self-like object where all the variables can be stored
@ In, runInfoDict, dict, dictionary containing all the RunInfo parameters (XML node <RunInfo>)
@ In, inputFiles, list, list of input files (if any)
@ Out, None
"""
pass


def run(self, container, inputDict):
"""
This method calculates the sum of the chosen element values and check if the capacity constraints
for all knapsacks are satisfied
@ In, container, object, self-like object where all the variables can be stored
@ In, inputDict, dict, dictionary of inputs from RAVEN
"""
totalValue = 0.0
knapsackSetValues={}

# knapsackSetValues is a dictionary in the form {knapsackID: knapsackValue}
for knapsack in self.knapsackSet.keys():
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check if self.knapsackSet[knapsack] in inputDict first?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check has been added

if self.knapsackSet[knapsack] in inputDict.keys():
knapsackSetValues[knapsack] = inputDict[self.knapsackSet[knapsack]][0]
else:
raise IOError("MultipleKnapsackModel: variable " + str(self.knapsackSet[knapsack]) + " has not been found in the input dataObject.")

# List of allowed knapsack IDs
elementAllowedValues = list(map(float, self.knapsackSet.keys()))
# Add 0.0 which implies that the element has not been assigned to any knapsack
elementAllowedValues.append(0.0)

numberUnsatConstraints = 0.0

for key in container.mapping:
if key in inputDict.keys() and inputDict[key] in elementAllowedValues:
if inputDict[key] > 0.0:
knapsackChosen = str(int(inputDict[key][0]))
knapsackSetValues[knapsackChosen] = knapsackSetValues[knapsackChosen] - inputDict[container.mapping[key][1]][0]
if knapsackSetValues[knapsackChosen] >= 0:
totalValue = totalValue + inputDict[container.mapping[key][0]]
else:
totalValue = totalValue - inputDict[container.mapping[key][0]] * self.penaltyFactor
numberUnsatConstraints = numberUnsatConstraints + 1.
else:
raise IOError("MultipleKnapsackModel: variable " + str(key) + " is either not found in the set of input variables or its values is not allowed.")

if numberUnsatConstraints > 0.0 :
container.__dict__[self.outcome] = 1.
else:
container.__dict__[self.outcome] = 0.

container.__dict__[self.choiceValue] = totalValue
Loading