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/knapsack models #8

Merged
merged 8 commits into from
Feb 4, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
from LOGOS.src import CapitalInvestmentModel
from LOGOS.src import BatteryReplacementCashFlowModel
from LOGOS.src import IncrementalNPV
from LOGOS.src.knapsack import BaseKnapsackModel
50 changes: 50 additions & 0 deletions doc/user_manual/include/Knapsack.tex
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
\section{Knapsack Models}
\label{sec:KnapsackModels}

LOGOS contains a set of Knapsack models that can be used coupled with RAVEN when
the desired optimization problem requires the use of specific models to generate
knapsack required parameters.
More specifically, all these models would be contained in a RAVEN EnsembleModel
and RAVEN optimization methods (e.g., Genetic Algorithms) would be employed to
find the optimal solution.


\subsection{BaseKnapsackModel}
\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).

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 \xmlNode{capacity} constraint
is not satisfied, then the \xmlNode{choiceValue} variable is penalized by multiplying the
project value by -\xmlNode{penaltyFactor}.

Example LOGOS input XML for DRO:
\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>
<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>
</ExternalModel>
</Models>
\end{lstlisting}
1 change: 1 addition & 0 deletions doc/user_manual/user_manual.tex
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@
\input{include/CVaR.tex}
\input{include/PluginForRavenCode.tex}
\input{include/CashflowAndNPVModels.tex}
\input{include/knapsack.tex}

\section*{Document Version Information}
This document has been compiled using the following version of the plugin Git repository:
Expand Down
105 changes: 105 additions & 0 deletions src/knapsack/BaseKnapsackModel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Copyright 2020, Battelle Energy Alliance, LLC
# ALL RIGHTS RESERVED
"""
Copy link
Collaborator

Choose a reason for hiding this comment

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

update the copyright statement, this one is not consistent with the version that we are using.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed

Created on February 2, 2021

@author: mandd
"""

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

#Internal Modules---------------------------------------------------------------
from PluginsBaseClasses.ExternalModelPluginBase import ExternalModelPluginBase
#Internal Modules End-----------------------------------------------------------


class BaseKnapsackModel(ExternalModelPluginBase):
"""
This class is designed to create the BaseKnapsack model
"""
def __init__(self):
"""
Constructor
@ In, None
@ Out, None
"""
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

def _readMoreXML(self, container, xmlNode):
"""
Method to read the portion of the XML that belongs to the BaseKnapsack 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 = {}

for child in xmlNode:
if child.tag == 'capacity':
self.capacity = float(child.text.strip())
elif child.tag == 'penaltyFactor':
self.penaltyFactor = float(child.text.strip())
elif child.tag == 'outcome':
self.outcome = child.text.strip()
elif child.tag == 'choiceValue':
self.choiceValue = child.text.strip()
elif child.tag == 'map':
container.mapping[child.text.strip()] = [child.get('value'),child.get('cost')]
elif child.tag == 'variables':
variables = [str(var.strip()) for var in child.text.split(",")]
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is the External Model for RAVEN, I think we'd better to use InputData and InputTypes to process the xml input.

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 think I may need some help here

Copy link
Collaborator

Choose a reason for hiding this comment

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

I have checked other External Models inside LOGOS, they are not using the InputData yet. If you want to use InputData for LOGOS, I suggest we open an issue, and adapt the InputData in another PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Issue #9 has been opened.
We should have a chat on how to fix it.

else:
raise IOError("BaseKnapsackModel: xml node " + str (child.tag) + " is not allowed")


def initialize(self, container, runInfoDict, inputFiles):
"""
Method to initialize the BaseKnapsack 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 constraint
is 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

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
11 changes: 11 additions & 0 deletions tests/gold/BaseKnapsack/PrintPS.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
element1Status,element2Status,element3Status,element4Status,element5Status,element1Val,element2Val,element3Val,element4Val,element5Val,element1Cost,element2Cost,element3Cost,element4Cost,element5Cost,validity,totalValue
1.0,0.0,1.0,1.0,1.0,1,2,3,4,5,1,1,1,1,1,0.0,13.0
1.0,1.0,1.0,0.0,0.0,1,2,3,4,5,1,1,1,1,1,0.0,6.0
0.0,1.0,1.0,1.0,1.0,1,2,3,4,5,1,1,1,1,1,1.0,4.0
1.0,0.0,1.0,1.0,1.0,1,2,3,4,5,1,1,1,1,1,1.0,-13.0
0.0,0.0,1.0,1.0,1.0,1,2,3,4,5,1,1,1,1,1,1.0,-12.0
1.0,1.0,0.0,1.0,1.0,1,2,3,4,5,1,1,1,1,1,1.0,-12.0
1.0,0.0,0.0,0.0,0.0,1,2,3,4,5,1,1,1,1,1,1.0,-1.0
1.0,1.0,1.0,1.0,1.0,1,2,3,4,5,1,1,1,1,1,1.0,-15.0
0.0,0.0,1.0,1.0,0.0,1,2,3,4,5,1,1,1,1,1,1.0,-7.0
1.0,1.0,0.0,0.0,1.0,1,2,3,4,5,1,1,1,1,1,1.0,-8.0
110 changes: 110 additions & 0 deletions tests/test_BaseKnapsackModel.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<Simulation verbosity="debug">
<TestInfo>
<name>framework.BaseKnapsack</name>
<author>mandd</author>
<created>2021-02-02</created>
<classesTested>Models.ExternalModel.LOGOS.BaseKnapsackModel</classesTested>
<description>
This test is aimed to test the BaseKnapsackModel class
</description>
</TestInfo>

<RunInfo>
<WorkingDir>BaseKnapsack</WorkingDir>
<Sequence>Run</Sequence>
<batchSize>1</batchSize>
</RunInfo>

<Models>
<ExternalModel name="knapsack" subType="LOGOS.BaseKnapsackModel">
<variables>element1Status,element2Status,element3Status,element4Status,element5Status,
element1Val,element2Val,element3Val,element4Val,element5Val,
element1Cost,element2Cost,element3Cost,element4Cost,element5Cost,
validity,totalValue</variables>
<capacity>10</capacity>
<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>
</Models>

<Distributions>
<Bernoulli name="bernDistrib">
<p>0.5</p>
</Bernoulli>
</Distributions>

<Samplers>
<MonteCarlo name="MC_external">
<samplerInit>
<limit>10</limit>
</samplerInit>
<variable name="element1Status">
<distribution>bernDistrib</distribution>
</variable>
<variable name="element2Status">
<distribution>bernDistrib</distribution>
</variable>
<variable name="element3Status">
<distribution>bernDistrib</distribution>
</variable>
<variable name="element4Status">
<distribution>bernDistrib</distribution>
</variable>
<variable name="element5Status">
<distribution>bernDistrib</distribution>
</variable>

<constant name="element1Val">1</constant>
<constant name="element2Val">2</constant>
<constant name="element3Val">3</constant>
<constant name="element4Val">4</constant>
<constant name="element5Val">5</constant>

<constant name="element1Cost">1</constant>
<constant name="element2Cost">1</constant>
<constant name="element3Cost">1</constant>
<constant name="element4Cost">1</constant>
<constant name="element5Cost">1</constant>
</MonteCarlo>
</Samplers>

<Steps>
<MultiRun name="Run">
<Input class="DataObjects" type="PointSet" >dummyPS</Input>
<Model class="Models" type="ExternalModel" >knapsack</Model>
<Sampler class="Samplers" type="MonteCarlo" >MC_external</Sampler>
<Output class="DataObjects" type="PointSet" >dataPS</Output>
<Output class="OutStreams" type="Print" >PrintPS</Output>
</MultiRun>
</Steps>

<OutStreams>
<Print name="PrintPS">
<type>csv</type>
<source>dataPS</source>
<what>input,output</what>
</Print>
</OutStreams>

<DataObjects>
<PointSet name="dummyPS">
<Input>element1Status,element2Status,element3Status,element4Status,element5Status,
element1Val,element2Val,element3Val,element4Val,element5Val,
element1Cost,element2Cost,element3Cost,element4Cost,element5Cost</Input>
<Output>OutputPlaceHolder</Output>
</PointSet>
<PointSet name="dataPS">
<Input>element1Status,element2Status,element3Status,element4Status,element5Status,
element1Val,element2Val,element3Val,element4Val,element5Val,
element1Cost,element2Cost,element3Cost,element4Cost,element5Cost</Input>
<Output>validity,totalValue</Output>
</PointSet>
</DataObjects>

</Simulation>
6 changes: 6 additions & 0 deletions tests/tests
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,10 @@
UnorderedCsv = 'IncrementalNPV_options/dumpCostOutput.csv'
[../]

[./BaseKnapsack]
type = 'RavenFramework'
input = 'test_BaseKnapsackModel.xml'
UnorderedCsv = 'BaseKnapsack/PrintPS.csv'
[../]

[]