Skip to content

Commit

Permalink
Constants from DataObject (#853)
Browse files Browse the repository at this point in the history
* impement, test, doc

* models

* comments addressed
  • Loading branch information
PaulTalbot-INL authored and alfoa committed Nov 16, 2018
1 parent e56324c commit 82ce63a
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 27 deletions.
52 changes: 49 additions & 3 deletions doc/user_manual/sampler.tex
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,45 @@ \section{Samplers}
optional parameter} the user is able to input variables that need to be
kept constant.
For doing this, as many \xmlNode{constant} nodes as needed can be
input, where the body of the node contains the constant value that is
going to be injected as an additional variable. The constant has the following attributes:
input.
%
There are options for setting a constant. To simply set the constant's value,
the body of the node contains the constant value, and the \xmlNode{constant}
node has the following attributes:
\begin{itemize}
\item \xmlAttr{name}, \xmlDesc{required string attribute}, user-defined name
of this constant.
\item \shapeConstantDescription
\end{itemize}

Alternatively, the constant value can
be read from a DataObject that has been identified as a \xmlNode{ConstantSource} for
this Sampler. In this case, the body of the \xmlNode{constant} node is the name of the
variable that needs to be read from the \xmlNode{ConstantSource}, and the \xmlNode{constant}
node has the following additional attributes in addition to the those above:
\begin{itemize}
\item \xmlAttr{source}, \xmlDesc{required string attribute}, the name of the DataObject
containing the value to be used for this constant. This must be the name of one of
the \xmlNode{ConstantSource} DataObjects.
\item \xmlAttr{index}, \xmlDesc{optional integer attribute}, the index of the realization
in the source DataObject that contains the value to use for the constant. \default{the last entry}
\end{itemize}

By way of example, consider the following Sampler definition. The constant will be named
\xmlString{C} in the Sampler, and its value is taken from the DataObject \xmlString{MyConstant},
which is identified in the \xmlNode{ConstantSource} node. To find the value of the constant in
\xmlString{MyConstant}, the Sampler will look at the realization with index \xmlString{3} for the
value of variable \xmlString{A} to use as the constant value.
\begin{lstlisting}[style=XML]
<Samplers>
<WhatEverSampler name='whatever'>
<ConstantSource class='DataObjects' type='PointSet'>MyConstants</ConstantSource>
<constant name='C' source='MyConstants' index='3'>A</constant>
</WhatEverSampler>
</Samplers>
\end{lstlisting}


}

\newcommand{\distributionDescription}
Expand Down Expand Up @@ -268,7 +300,7 @@ \section{Samplers}
to control this sampler's convergence criterion.
}

\newcommand{\assemblerDescription}[1] %%NOTE this only applies to the adaptive sampler. Why is it a newcommand? %%
\newcommand{\assemblerDescription}[1]
{
\textbf{Assembler Objects} These objects are either required or optional
depending on the functionality of the #1 Sampler.
Expand Down Expand Up @@ -323,6 +355,15 @@ \section{Samplers}
\default{1e-15}
\end{itemize}
}
\newcommand{\constantSourceDescription}[1]
{
\begin{itemize}
\item \xmlNode{ConstantSource}, \xmlDesc{string, optional field}, the
body of this XML node must contain the name of an appropriate \textbf{DataObject} defined in the
\xmlNode{DataObjects} block (see Section~\ref{sec:DataObjects}). It is used as a
source from which constants can take values.
\end{itemize}
}

\newcommand{\variablesTransformationDescription}[1]
{
Expand Down Expand Up @@ -615,6 +656,7 @@ \subsubsection{Monte Carlo}
\variablesTransformationDescription{MonteCarlo}
\assemblerDescription{MonteCarlo}
\restartDescription{MonteCarlo}
\constantSourceDescription{MonteCarlo}

Example:
\begin{lstlisting}[style=XML]
Expand Down Expand Up @@ -705,6 +747,7 @@ \subsubsection{Grid}

\assemblerDescription{Grid}
\restartDescription{Grid}
\constantSourceDescription{Grid}

Example:
\begin{lstlisting}[style=XML,morekeywords={construction,steps,lowerBound,upperBound}]
Expand Down Expand Up @@ -790,6 +833,7 @@ \subsubsection{Sparse Grid Collocation}
\assemblerDescription{SparseGridCollocation}
\ROMDescription{SparseGridCollocation}
\restartDescription{SparseGridCollocation}
\constantSourceDescription{SparseGridCollocation}

\footnotesize
\begin{lstlisting}[style=XML]
Expand Down Expand Up @@ -933,6 +977,7 @@ \subsubsection{Sobol}
\assemblerDescription{Sobol}
\ROMDescription{Sobol}
\restartDescription{Sobol}
\constantSourceDescription{Sobol}

\footnotesize
\begin{lstlisting}[style=XML]
Expand Down Expand Up @@ -1017,6 +1062,7 @@ \subsubsection{Stratified}

\assemblerDescription{Stratified}
\restartDescription{Stratified}
\constantSourceDescription{Stratified}

Example:
\begin{lstlisting}[style=XML,morekeywords={construction,steps,lowerBound,upperBound}]
Expand Down
69 changes: 45 additions & 24 deletions framework/Samplers/Sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ class cls.
constantInput = InputData.parameterInputFactory("constant", contentType=InputData.InterpretedListType)
constantInput.addParam("name", InputData.StringType, True)
constantInput.addParam("shape", InputData.IntegerListType, required=False)
constantInput.addParam("source", InputData.StringType, required=False)
constantInput.addParam("index", InputData.IntegerType, required=False)

inputSpecification.addSub(constantInput)

Expand All @@ -101,6 +103,11 @@ class cls.
restartInput.addParam("class", InputData.StringType)
inputSpecification.addSub(restartInput)

sourceInput = InputData.parameterInputFactory("ConstantSource", contentType=InputData.StringType)
sourceInput.addParam("type", InputData.StringType)
sourceInput.addParam("class", InputData.StringType)
inputSpecification.addSub(sourceInput)

return inputSpecification

def __init__(self):
Expand Down Expand Up @@ -132,10 +139,14 @@ def __init__(self):
self.reseedAtEachIteration = False # Logical flag. True if every newer evaluation is performed after a new reseeding
self.FIXME = False # FIXME flag
self.printTag = self.type # prefix for all prints (sampler type)

self.restartData = None # presampled points to restart from
self.restartTolerance = 1e-15 # strictness with which to find matches in the restart data
self.restartIsCompatible = None # flags restart as compatible with the sampling scheme (used to speed up checking)

self.constantSourceData = None # dictionary of data objects from which constants can take values
self.constantSources = {} # storage for the way to obtain constant information

self._endJobRunnable = sys.maxsize # max number of inputs creatable by the sampler right after a job ends (e.g., infinite for MC, 1 for Adaptive, etc)

######
Expand All @@ -144,6 +155,7 @@ def __init__(self):
self.NDSamplingParams = {} # this dictionary contains a dictionary for each ND distribution (key). This latter dictionary contains the initialization parameters of the ND inverseCDF ('initialGridDisc' and 'tolerance')
######
self.addAssemblerObject('Restart' ,'-n',True)
self.addAssemblerObject('ConstantSource' ,'-n',True)

#used for PCA analysis
self.variablesTransformationDict = {} # for each variable 'modelName', the following informations are included: {'modelName': {latentVariables:[latentVar1, latentVar2, ...], manifestVariables:[manifestVar1,manifestVar2,...]}}
Expand Down Expand Up @@ -388,23 +400,33 @@ def _readInConstant(self,inp):
@ Out, name, string, name of constant
@ Out, value, float or np.array,
"""
# constantSources
value = inp.value
name = inp.parameterValues['name']
shape = inp.parameterValues.get('shape',None)
# if single entry, remove array structure; if multiple entries, cast them as numpy array
if len(value) == 1:
value = value[0]
source = inp.parameterValues.get('source',None)
# if constant's value is provided directly by value ...
if source is None:
# if single entry, remove array structure; if multiple entries, cast them as numpy array
if len(value) == 1:
value = value[0]
else:
value = np.asarray(value)
# if specific shape requested, then reshape it
if shape is not None:
try:
value = value.reshape(shape)
except ValueError:
self.raiseAnError(IOError,
('Requested shape "{}" ({} entries) for constant "{}"' +\
' is not consistent with the provided values ({} entries)!')
.format(shape,np.prod(shape),name,len(value)))
# else if constant's value is provided from a DataObject ...
else:
value = np.asarray(value)
# if specific shape requested, then reshape it
if shape is not None:
try:
value = value.reshape(shape)
except ValueError:
self.raiseAnError(IOError,
('Requested shape "{}" ({} entries) for constant "{}"' +\
' is not consistent with the provided values ({} entries)!')
.format(shape,np.prod(shape),name,len(value)))
self.constantSources[name] = {'shape' : shape,
'source' : source,
'index' : inp.parameterValues.get('index',-1),
'sourceVar': value[0]} # generally, constants are a list, but in this case just take the only entry
return name, value

def getInitParams(self):
Expand Down Expand Up @@ -459,17 +481,16 @@ def initialize(self,externalSeeding=None,solutionExport=None):
else:
self.raiseAMessage('No restart for '+self.printTag)

#load restart data into existing points
# TODO do not copy data! Read directly from restart.
#if self.restartData is not None:
# if len(self.restartData) > 0:
# inps = self.restartData.getInpParametersValues()
# outs = self.restartData.getOutParametersValues()
# #FIXME there is no guarantee ordering is accurate between restart data and sampler
# inputs = list(v for v in inps.values())
# existingInps = zip(*inputs)
# outVals = zip(*list(v for v in outs.values()))
# self.existing = dict(zip(existingInps,outVals))
if 'ConstantSource' in self.assemblerDict.keys():
# find all the sources requested in the sampler, map data objects to their requested names
self.constantSourceData = dict((a[2],a[3]) for a in self.assemblerDict['ConstantSource'])
for var,data in self.constantSources.items():
source = self.constantSourceData[data['source']]
rlz = source.realization(index=data['index'])
if data['sourceVar'] not in rlz:
self.raiseAnError(IOError,'Requested variable "{}" from DataObject "{}" to set constant "{}",'.format(data['sourceVar'], source.name, var) +\
' but "{}" is not a variable in "{}"!'.format(data['sourceVar'], source.name))
self.constants[var] = rlz[data['sourceVar']]

#specializing the self.localInitialize() to account for adaptive sampling
if solutionExport != None:
Expand Down
24 changes: 24 additions & 0 deletions tests/framework/Samplers/DataObjectConstants/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# 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.

import numpy as np

def run(self,Input):
"""
Runs the model.
@ In, self, object, RAVEN's API storage object
@ In, Input, dict, RAVEN's dictionary storage object
@ Out, None
"""
self.z = self.x*self.C + self.D
25 changes: 25 additions & 0 deletions tests/framework/Samplers/DataObjectConstants/premodel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# 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.

import numpy as np

def run(self,Input):
"""
Runs the model.
@ In, self, object, RAVEN's API storage object
@ In, Input, dict, RAVEN's dictionary storage object
@ Out, None
"""
self.A = self.a * 10
self.B = self.b * 10
109 changes: 109 additions & 0 deletions tests/framework/Samplers/dataobject_constants.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<Simulation verbosity="debug">
<TestInfo>
<name>framework/Samplers.DataObjectConstants</name>
<author>talbpaul</author>
<created>2018-11-14</created>
<classesTested>Sampler</classesTested>
<description>
Tests using a DataObject external to a Step as a source of constants for a Sampler.
The models are very simple, so to regold, assure the correct values pass through for
the constants and final result is obtained according to the models.
</description>
</TestInfo>

<RunInfo>
<WorkingDir>DataObjectConstants</WorkingDir>
<Sequence>presample, sample, print</Sequence>
</RunInfo>

<Steps>
<MultiRun name="presample">
<Input class="DataObjects" type="PointSet">pre_placeholder</Input>
<Model class="Models" type="ExternalModel">premodel</Model>
<Sampler class="Samplers" type="Grid">pregrid</Sampler>
<Output class="DataObjects" type="PointSet">presamples</Output>
</MultiRun>
<MultiRun name="sample">
<Input class="DataObjects" type="PointSet">placeholder</Input>
<Model class="Models" type="ExternalModel">model</Model>
<Sampler class="Samplers" type="Grid">grid</Sampler>
<Output class="DataObjects" type="PointSet">samples</Output>
</MultiRun>
<IOStep name="print">
<Input class="DataObjects" type="PointSet">presamples</Input>
<Input class="DataObjects" type="PointSet">samples</Input>
<Output class="OutStreams" type="Print">presamples</Output>
<Output class="OutStreams" type="Print">samples</Output>
</IOStep>
</Steps>

<DataObjects>
<PointSet name="pre_placeholder">
<Input>a,b</Input>
</PointSet>
<PointSet name="presamples">
<Input>a,b</Input>
<Output>A,B</Output>
</PointSet>
<PointSet name="placeholder">
<Input>x</Input>
</PointSet>
<PointSet name="samples">
<Input>x,C,D</Input>
<Output>z</Output>
</PointSet>
</DataObjects>

<Models>
<ExternalModel name="premodel" subType="" ModuleToLoad="premodel.py">
<variables>a,b,A,B</variables>
</ExternalModel>
<ExternalModel name="model" subType="" ModuleToLoad="model.py">
<variables>x,z,C,D</variables>
</ExternalModel>
</Models>

<Distributions>
<Normal name="dist">
<mean>0</mean>
<sigma>0.1</sigma>
</Normal>
</Distributions>

<Samplers>
<Grid name="pregrid">
<variable name="a">
<distribution>dist</distribution>
<grid construction="equal" type="CDF" steps="1">0.1 0.9</grid>
</variable>
<variable name="b">
<distribution>dist</distribution>
<grid construction="equal" type="CDF" steps="1">0.1 0.9</grid>
</variable>
</Grid>
<Grid name="grid">
<ConstantSource class="DataObjects" type="PointSet">presamples</ConstantSource>
<ConstantSource class="DataObjects" type="PointSet">presamples</ConstantSource>
<variable name="x">
<distribution>dist</distribution>
<grid construction="equal" type="CDF" steps="2">0.1 0.9</grid>
</variable>
<constant name="C" source="presamples" index="0">A</constant>
<constant name="D" source="presamples" index="1">B</constant>
</Grid>
</Samplers>

<OutStreams>
<Print name="presamples">
<type>csv</type>
<source>presamples</source>
<what>input,output</what>
</Print>
<Print name="samples">
<type>csv</type>
<source>samples</source>
<what>input,output</what>
</Print>
</OutStreams>

</Simulation>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
a,b,A,B
-0.128155156554,-0.128155156554,-1.28155156554,-1.28155156554
-0.128155156554,0.128155156554,-1.28155156554,1.28155156554
0.128155156554,-0.128155156554,1.28155156554,-1.28155156554
0.128155156554,0.128155156554,1.28155156554,1.28155156554
Loading

0 comments on commit 82ce63a

Please sign in to comment.