From ade7321b9ed3e4e929e28a3d8a5a2a61b3f21ce3 Mon Sep 17 00:00:00 2001 From: Diego Mandelli Date: Tue, 21 Apr 2020 14:07:28 -0600 Subject: [PATCH] Mandd/pareto frontier pp (#1196) Addition of PARETO FRONTIER post processor --- doc/user_manual/postprocessor.tex | 55 +++++++- .../ParetoFrontierPostProcessor.py | 131 ++++++++++++++++++ framework/PostProcessors/__init__.py | 4 +- .../ParetoFrontier/data.csv | 29 ++++ .../gold/ParetoFrontier/PrintPareto.csv | 8 ++ .../test_paretoFrontier.xml | 60 ++++++++ .../ParetoFrontierPostProcessor/tests | 9 ++ 7 files changed, 294 insertions(+), 2 deletions(-) create mode 100644 framework/PostProcessors/ParetoFrontierPostProcessor.py create mode 100644 tests/framework/PostProcessors/ParetoFrontierPostProcessor/ParetoFrontier/data.csv create mode 100644 tests/framework/PostProcessors/ParetoFrontierPostProcessor/gold/ParetoFrontier/PrintPareto.csv create mode 100644 tests/framework/PostProcessors/ParetoFrontierPostProcessor/test_paretoFrontier.xml create mode 100644 tests/framework/PostProcessors/ParetoFrontierPostProcessor/tests diff --git a/doc/user_manual/postprocessor.tex b/doc/user_manual/postprocessor.tex index 2c54b79552..c7c337c927 100644 --- a/doc/user_manual/postprocessor.tex +++ b/doc/user_manual/postprocessor.tex @@ -27,6 +27,7 @@ \subsection{PostProcessor} \item \textbf{ValueDuration} \item \textbf{FastFourierTransform} \item \textbf{SampleSelector} + \item \textbf{MCSImporter} %\item \textbf{PrintCSV} %\item \textbf{LoadCsvIntoInternalObject} \end{itemize} @@ -1803,10 +1804,62 @@ \subsubsection{RavenOutput} \end{lstlisting} The resulting PointSet has \emph{time} as an input and \emph{first} as an output. +%%%%%%%%%%%%%% ParetoFrontier PP %%%%%%%%%%%%%%%%%%% + +\subsubsection{ParetoFrontier} +\label{ParetoFrontierPP} +The \textbf{ParetoFrontier} post-processor is designed to identify the points lying on the Pareto Frontier in a cost-value space. +This post-processor receives as input a \textbf{DataObject} (a PointSet only) which contains all data points in the cost-value space and it +returns the subset of points lying in the Pareto Frontier as a PointSet. + +It is here assumed that each data point of the input PointSet is a realization of the system under consideration for a +specific configuration to which corresponds a cost and a value. + +% +\ppType{ParetoFrontier}{ParetoFrontier} +% +\begin{itemize} + \item \xmlNode{costID},\xmlDesc{string, required parameter}, ID of the input PointSet variable that is considered the cost variable + \item \xmlNode{valueID},\xmlDesc{string, required parameter}, ID of the input PointSet variable that is considered the value variable +\end{itemize} + +The following is an example where a set of realizations (the ``candidates'' PointSet) has been generated by changing two parameters +(var1 and var2) which produced two output variables (cost and value). +The \textbf{ParetoFrontier} post-processor takes the ``candidates'' PointSet and populates a Point similar in structure +(the ``paretoPoints'' PointSet). + +\textbf{Example:} +\begin{lstlisting}[style=XML,morekeywords={anAttribute},caption=ParetoFrontier input example (no expand)., label=lst:ParetoFrontier_PP_InputExample] + + + cost + value + + + + + + candidates + paretoPP + paretoPoints + + + + + + var1,var2 + cost,value + + + var1,var2 + cost,value + + +\end{lstlisting} %%%%%%%%%%%%%% MCSImporter PP %%%%%%%%%%%%%%%%%%% -\subsubsection{MCS Importer} +\subsubsection{MCSImporter} \label{MCSimporterPP} The \textbf{MCSImporter} post-processor has been designed to import Minimal Cut Sets (MCSs) into RAVEN. This post-processor reads a csv file which contain the list of MCSs and it save this list as a DataObject diff --git a/framework/PostProcessors/ParetoFrontierPostProcessor.py b/framework/PostProcessors/ParetoFrontierPostProcessor.py new file mode 100644 index 0000000000..1809b79043 --- /dev/null +++ b/framework/PostProcessors/ParetoFrontierPostProcessor.py @@ -0,0 +1,131 @@ +# Copyright 2017 Battelle Energy Alliance, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Created on March 25, 2020 + +@author: mandd +""" + +#External Modules--------------------------------------------------------------- +import numpy as np +#External Modules End----------------------------------------------------------- + +#Internal Modules--------------------------------------------------------------- +from .PostProcessor import PostProcessor +from utils import utils +from utils import InputData, InputTypes +import Runners +#Internal Modules End----------------------------------------------------------- + +class ParetoFrontier(PostProcessor): + """ + This postprocessor selects the points that lie on the Pareto frontier + The postprocessor acts only on PointSet and return a subset of such PointSet + """ + + @classmethod + def getInputSpecification(cls): + """ + Method to get a reference to a class that specifies the input data for + class cls. + @ In, cls, the class for which we are retrieving the specification + @ Out, inputSpecification, InputData.ParameterInput, class to use for + specifying input of cls. + """ + inputSpecification = super(ParetoFrontier, cls).getInputSpecification() + inputSpecification.addSub(InputData.parameterInputFactory('costID' , contentType=InputTypes.StringType)) + inputSpecification.addSub(InputData.parameterInputFactory('valueID', contentType=InputTypes.StringType)) + return inputSpecification + + def _localReadMoreXML(self, xmlNode): + """ + Function to read the portion of the xml input that belongs to this specialized class + and initialize some stuff based on the inputs got + @ In, xmlNode, xml.etree.Element, Xml element node + @ Out, None + """ + paramInput = ParetoFrontier.getInputSpecification()() + paramInput.parseNode(xmlNode) + self._handleInput(paramInput) + + def _handleInput(self, paramInput): + """ + Function to handle the parsed paramInput for this class. + @ In, paramInput, ParameterInput, the already-parsed input. + @ Out, None + """ + costID = paramInput.findFirst('costID') + self.costID = costID.value + valueID = paramInput.findFirst('valueID') + self.valueID = valueID.value + + def inputToInternal(self, currentInp): + """ + Method to convert an input object into the internal format that is + understandable by this pp. + In this case, we only want data objects! + @ In, currentInp, list, an object that needs to be converted + @ Out, currentInp, DataObject.HistorySet, input data + """ + if len(currentInp) > 1: + self.raiseAnError(IOError, 'ParetoFrontier postprocessor {} expects one input DataObject, but received {} inputs!".' + .format(self.name,len(currentInp))) + currentInp = currentInp[0] + if currentInp.type not in ['PointSet']: + self.raiseAnError(IOError, 'ParetoFrontier postprocessor "{}" requires a DataObject input! Got "{}".' + .format(self.name, currentInput.type)) + return currentInp + + def run(self, inputIn): + """ + This method executes the postprocessor action. + @ In, inputIn, DataObject, point set that contains the data to be processed + @ Out, paretoFrontierDict, dict, dictionary containning the Pareto Frontier information + """ + inData = self.inputToInternal(inputIn) + data = inData.asDataset() + sortedData = data.sortby(self.costID) + + coordinates = np.zeros(1,dtype=int) + for index,elem in enumerate(sortedData[self.costID].values): + if (index>1) and (sortedData[self.valueID].values[index]>sortedData[self.valueID].values[coordinates[-1]]): + coordinates = np.append(coordinates,index) + + selection = sortedData.isel(RAVEN_sample_ID=coordinates).to_array().values + paretoFrontierData = np.transpose(selection) + + paretoFrontierDict = {} + for index,varID in enumerate(sortedData.data_vars): + paretoFrontierDict[varID] = paretoFrontierData[:,index] + return paretoFrontierDict + + def collectOutput(self, finishedJob, output): + """ + Function to place all of the computed data into the output object + @ In, finishedJob, JobHandler External or Internal instance, A JobHandler object that is in charge of running this post-processor + @ In, output, DataObject.DataObject, The object where we want to place our computed results + @ Out, None + """ + evaluation = finishedJob.getEvaluation() + + outputDict ={} + outputDict['data'] = evaluation[1] + + if output.type in ['PointSet']: + outputDict['dims'] = {} + for key in outputDict.keys(): + outputDict['dims'][key] = [] + output.load(outputDict['data'], style='dict', dims=outputDict['dims']) + else: + self.raiseAnError(RuntimeError, 'ParetoFrontier failed: Output type ' + str(output.type) + ' is not supported.') diff --git a/framework/PostProcessors/__init__.py b/framework/PostProcessors/__init__.py index 27aac04346..a55487a0af 100644 --- a/framework/PostProcessors/__init__.py +++ b/framework/PostProcessors/__init__.py @@ -49,6 +49,7 @@ from .DataClassifier import DataClassifier from .ComparisonStatisticsModule import ComparisonStatistics from .RealizationAverager import RealizationAverager +from .ParetoFrontierPostProcessor import ParetoFrontier from .MCSimporter import MCSImporter # from .RavenOutput import RavenOutput # deprecated for now @@ -88,5 +89,6 @@ 'DataClassifier', 'SampleSelector', 'ETImporter', - 'RealizationAverager'] + additionalModules + 'RealizationAverager', + 'ParetoFrontier'] + additionalModules # 'RavenOutput', # deprecated for now diff --git a/tests/framework/PostProcessors/ParetoFrontierPostProcessor/ParetoFrontier/data.csv b/tests/framework/PostProcessors/ParetoFrontierPostProcessor/ParetoFrontier/data.csv new file mode 100644 index 0000000000..67ac0291c4 --- /dev/null +++ b/tests/framework/PostProcessors/ParetoFrontierPostProcessor/ParetoFrontier/data.csv @@ -0,0 +1,29 @@ +var1,var2,cost,value +0.610859173,2.654360106,0.119184948,0.258905327 +1.000062354,0.378710223,0.55643265,0.566231764 +0.192283951,0.689577052,0.060905454,0.18054485 +2.540589393,1.903305493,0.687883548,0.813536866 +0.379652948,3.670016433,0.71839606,0.066388849 +0.018649501,2.419989092,0.848137196,0.132795653 +1.330707892,3.30304347,0.920517482,0.016587155 +1.124432133,1.292992123,0.569248014,0.470709174 +0.113252566,1.809448753,0.158429805,0.058030974 +0.791262635,2.240760603,0.784246453,0.37114112 +2.301074231,3.766843205,0.755648003,0.731028739 +1.586862706,2.728372649,0.540216204,0.109898232 +0.7791167,0.166729443,0.733693488,0.090933319 +2.089949241,2.85512794,0.81212754,0.897978379 +1.4476716,1.507081576,0.590949365,0.670322915 +1.086484674,3.14355567,0.433299584,0.08645684 +0.211447941,0.001989369,0.29248432,0.513449866 +2.202997279,0.434109143,0.511187573,0.330193147 +1.915360888,1.476389367,0.943463507,0.882958351 +2.061818372,0.278500517,0.006895977,0.071068495 +0.216133506,0.320616331,0.044354112,0.068657568 +2.713265808,2.358974745,0.522916259,0.126625517 +0.596297114,2.368502385,0.846362805,0.386032272 +1.655405307,0.816356271,0.93359994,0.329798081 +0.77173529,3.400705844,0.561187856,0.375795736 +0.5696295,1.055822709,0.873151817,0.160984792 +0.828752896,1.715167593,0.520305981,0.719334099 +2.719657094,0.486305191,0.133546208,0.202038808 \ No newline at end of file diff --git a/tests/framework/PostProcessors/ParetoFrontierPostProcessor/gold/ParetoFrontier/PrintPareto.csv b/tests/framework/PostProcessors/ParetoFrontierPostProcessor/gold/ParetoFrontier/PrintPareto.csv new file mode 100644 index 0000000000..395bde2643 --- /dev/null +++ b/tests/framework/PostProcessors/ParetoFrontierPostProcessor/gold/ParetoFrontier/PrintPareto.csv @@ -0,0 +1,8 @@ +var1,var2,cost,value +2.061818372,0.278500517,0.006895977,0.071068495 +0.192283951,0.689577052,0.060905454,0.18054485 +0.610859173,2.654360106,0.119184948,0.258905327 +0.211447941,0.001989369,0.29248432,0.513449866 +0.828752896,1.715167593,0.520305981,0.719334099 +2.540589393,1.903305493,0.687883548,0.813536866 +2.089949241,2.85512794,0.81212754,0.897978379 diff --git a/tests/framework/PostProcessors/ParetoFrontierPostProcessor/test_paretoFrontier.xml b/tests/framework/PostProcessors/ParetoFrontierPostProcessor/test_paretoFrontier.xml new file mode 100644 index 0000000000..0511aa0a12 --- /dev/null +++ b/tests/framework/PostProcessors/ParetoFrontierPostProcessor/test_paretoFrontier.xml @@ -0,0 +1,60 @@ + + + framework/PostProcessors/ParetoFrontierPostProcessor.ParetoFrontier + mandd + 2020-03-25 + Models.PostProcessors.ParetoFrontier + + This post-processor identifies the points lying on the Pareto Frontier in a cost-value space + + + + + data.csv + + + + ParetoFrontier + LoadPS,PP + 1 + + + + + cost + value + + + + + + data + candidates + + + candidates + paretoPP + paretoPoints + PrintPareto + + + + + + csv + paretoPoints + + + + + + var1,var2 + cost,value + + + var1,var2 + cost,value + + + + diff --git a/tests/framework/PostProcessors/ParetoFrontierPostProcessor/tests b/tests/framework/PostProcessors/ParetoFrontierPostProcessor/tests new file mode 100644 index 0000000000..f307326170 --- /dev/null +++ b/tests/framework/PostProcessors/ParetoFrontierPostProcessor/tests @@ -0,0 +1,9 @@ +[Tests] + [./ParetoFrontier] + type = 'RavenFramework' + input = 'test_paretoFrontier.xml' + csv = 'ParetoFrontier/PrintPareto.csv' + [../] +[] + +