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

Synthetic History Cloud Plot #1649

Merged
merged 14 commits into from
Sep 3, 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
2 changes: 2 additions & 0 deletions framework/OutStreams/PlotInterfaces/Factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@
from .SamplePlot import SamplePlot
from .GeneralPlot import GeneralPlot
from .OptPath import OptPath
from .SyntheticCloud import SyntheticCloud

factory = EntityFactory('Plot')
factory.registerType('GeneralPlot', GeneralPlot)
factory.registerType('SamplePlot', SamplePlot)
factory.registerType('OptPath', OptPath)
factory.registerType('SyntheticCloud', SyntheticCloud)
16 changes: 16 additions & 0 deletions framework/OutStreams/PlotInterfaces/PlotInterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from abc import abstractmethod
from OutStreams import OutStreamInterface, OutStreamEntity
from utils.utils import displayAvailable

class PlotInterface(OutStreamInterface):
"""
Expand Down Expand Up @@ -72,6 +73,21 @@ def run(self):
@ Out, None
"""

def endInstructions(self, instructionString):
"""
Finalize plotter. Called if "pauseAtEnd" is in the Step attributes.
@ In, instructionString, string, instructions to execute
@ Out, None
"""
if instructionString == 'interactive' and displayAvailable():
import matplotlib.pyplot as plt
for i in plt.get_fignums():
fig = plt.figure(i)
try:
fig.ginput(n=-1, timeout=0, show_clicks=False)
except Exception as e:
self.raiseAWarning('There was an error with figure.ginput. Continuing anyway ...')

##################
# Utility
def findSource(self, name, stepEntities):
Expand Down
157 changes: 157 additions & 0 deletions framework/OutStreams/PlotInterfaces/SyntheticCloud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# 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 September 2, 2021

@author: talbpaul
Definition for a common plotting need with synthetic histories versus training data.
"""

import matplotlib.pyplot as plt

from .PlotInterface import PlotInterface
from utils import InputData, InputTypes

class SyntheticCloud(PlotInterface):
"""
Plots the training data along with a cloud of sampled data for synthetic histories.
"""
@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 the input of cls.
"""
spec = super().getInputSpecification()
spec.addSub(InputData.parameterInputFactory('training', contentType=InputTypes.StringType,
descr=r"""The name of the RAVEN DataObject from which the training (or original) data should
be taken for this plotter.
This should be the data used to train the surrogate."""))
spec.addSub(InputData.parameterInputFactory('samples', contentType=InputTypes.StringType,
descr=r"""The name of the RAVEN DataObject from which the sampled synthetic histories should
be taken for this plotter."""))
spec.addSub(InputData.parameterInputFactory('macroParam', contentType=InputTypes.StringType,
descr=r"""Name of the macro variable (e.g. Year)."""))
spec.addSub(InputData.parameterInputFactory('microParam', contentType=InputTypes.StringType,
descr=r"""Name of the micro variable or pivot parameter (e.g. Time)."""))
spec.addSub(InputData.parameterInputFactory('variables', contentType=InputTypes.StringListType,
descr=r"""Name of the signal variables to plot."""))
return spec

def __init__(self):
"""
Init of Base class
@ In, None
@ Out, None
"""
super().__init__()
self.printTag = 'OptPath Plot'
self.training = None # DataObject with training data
self.trainingName = None # name of training D.O.
self.samples = None # DataObject with sample data
self.samplesName = None # name of samples D.O.
self.macroName = None # name of macro parameter (e.g. Year)
self.microName = None # name of micro parameter (e.g. Time)
self.variables = None # variable names to plot
self.clusterName = '_ROM_Cluster' # TODO magic name

def handleInput(self, spec):
"""
Loads the input specs for this object.
@ In, spec, InputData.ParameterInput, input specifications
@ Out, None
"""
super().handleInput(spec)
self.trainingName = spec.findFirst('training').value
self.samplesName = spec.findFirst('samples').value
self.macroName = spec.findFirst('macroParam').value
self.microName = spec.findFirst('microParam').value
self.variables = spec.findFirst('variables').value
# checker; this should be superceded by "required" in input params
if self.trainingName is None:
self.raiseAnError(IOError, "Missing <training> node!")
if self.samplesName is None:
self.raiseAnError(IOError, "Missing <samples> node!")


def initialize(self, stepEntities):
"""
Function to initialize the OutStream. It basically looks for the "data"
object and links it to the system.
@ In, stepEntities, dict, contains all the Objects are going to be used in the
current step. The sources are searched into this.
@ Out, None
"""
train = self.findSource(self.trainingName, stepEntities)
if train is None:
self.raiseAnError(IOError, f'No input named "{self.trainingName}" was found in the Step for Plotter "{self.name}"!')
if train.isEmpty:
self.raiseAnError(IOError, f'Data object "{self.trainingName}" is empty!')
self.training = train
sample = self.findSource(self.samplesName, stepEntities)
if sample is None:
self.raiseAnError(IOError, f'No input named "{self.samplesName}" was found in the Step for Plotter "{self.name}"!')
if sample.isEmpty:
self.raiseAnError(IOError, f'Data object "{self.samplesName}" is empty!')
if self.clusterName in sample.getVars():
self.raiseAnError(IOError, f'Data object "{self.samplesName}" uses clusters! For this plotting, please take full samples.')
self.samples = sample

def run(self):
"""
Main run method.
@ In, None
@ Out, None
"""
tTag = self.training.sampleTag
sTag = self.samples.sampleTag
training = self.training.asDataset()
samples = self.samples.asDataset()
alpha = max(0.05, .5/len(samples))
varNames = self.variables
numVars = len(varNames)
# use the len of macro, cluster from samples to build plots
macro = samples[self.macroName]
figCounter = 0
for m, mac in enumerate(macro):
figCounter += 1
fig, axes = plt.subplots(numVars, 1, sharex=True)
if numVars == 1:
axes = [axes]
axes[-1].set_xlabel(self.microName)

mSamples = samples.drop_sel({self.macroName: mac})
mTraining = None
if self.macroName in training:
if int(mac) in training[self.macroName]:
if self.macroName in training.dims:
mTraining = training.drop_sel({self.macroName: mac})
else:
mTraining = training.where(training[self.macroName]==mac, drop=True).squeeze()
for v, var in enumerate(varNames):
ax = axes[v]
# plot cloud of sample data
for s in mSamples[sTag].values:
samp = mSamples[{sTag: s}]
ax.plot(samp[self.microName].values, samp[var].values, 'b-.', alpha=alpha)
ax.set_title(f'{var}, {self.macroName} {int(mac)}')
ax.set_ylabel(var)
if mTraining is not None:
ax.plot(mTraining[self.microName].values, mTraining[var].values, 'k-.')

filename = f'{self.name}_{m}.png'
plt.savefig(filename)
self.raiseAMessage(f'Wrote "{filename}".')

1 change: 1 addition & 0 deletions framework/OutStreams/PlotInterfaces/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@
from .SamplePlot import SamplePlot
from .GeneralPlot import GeneralPlot as Plot
from .OptPath import OptPath
from .SyntheticCloud import SyntheticCloud

from .Factory import factory
85 changes: 85 additions & 0 deletions tests/framework/ROM/TimeSeries/ARMA/cloud_plot.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<Simulation verbosity="debug">
<TestInfo>
<name>framework/ROM/TimeSeries/ARMA.CloudPlot</name>
<author>talbpaul</author>
<created>2020-09-02</created>
<classesTested>OutStreams.SyntheticCloud</classesTested>
<description>
Tests the custom synthetic history cloud plot.
</description>
</TestInfo>

<RunInfo>
<WorkingDir>Interpolated</WorkingDir>
<Sequence>load_inp, load_pk, sample, plot</Sequence>
</RunInfo>

<Steps>
<IOStep name="load_inp">
<Input class="Files" type="">input</Input>
<Output class="DataObjects" type="HistorySet">input</Output>
</IOStep>
<IOStep name="load_pk">
<Input class="Files" type="">pk</Input>
<Output class="Models" type="ROM">arma</Output>
</IOStep>
<MultiRun name="sample">
<Input class="DataObjects" type="PointSet">placeholder</Input>
<Model class="Models" type="ROM">arma</Model>
<Sampler class="Samplers" type="MonteCarlo">mc</Sampler>
<Output class="DataObjects" type="DataSet">synthetic</Output>
</MultiRun>
<IOStep name="plot">
<Input class="DataObjects" type="HistorySet">input</Input>
<Input class="DataObjects" type="DataSet">synthetic</Input>
<Output class="OutStreams" type="Plot">cloud</Output>
</IOStep>
</Steps>

<Files>
<Input name="input">head.csv</Input>
<Input name="pk">interpolated.pk</Input>
</Files>

<DataObjects>
<PointSet name="placeholder"/>
<HistorySet name="input">
<Input>scaling, Year</Input>
<Output>Signal, Time</Output>
<options>
<pivotParameter>Time</pivotParameter>
</options>
</HistorySet>
<DataSet name="synthetic">
<Input>scaling</Input>
<Output>Signal</Output>
<Index var="Time">Signal</Index>
<Index var="Year">Signal</Index>
</DataSet>
</DataObjects>

<Models>
<ROM name="arma" subType="pickledROM">
<clusterEvalMode>full</clusterEvalMode>
</ROM>
</Models>

<OutStreams>
<Plot name="cloud" subType="SyntheticCloud">
<training>input</training>
<samples>synthetic</samples>
<macroParam>Year</macroParam>
<microParam>Time</microParam>
<variables>Signal</variables>
</Plot>
</OutStreams>

<Samplers>
<MonteCarlo name="mc">
<samplerInit>
<limit>3</limit>
</samplerInit>
<constant name="scaling">1.0</constant>
</MonteCarlo>
</Samplers>
</Simulation>
14 changes: 12 additions & 2 deletions tests/framework/ROM/TimeSeries/ARMA/tests
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# As we can observe, we at most can rely on 2 digits of similarity even on the first point.
# A good alternative for non-correlated ARMAs is to run statistics on the results (mean, variance) and check those instead.

[./Basic]
[./Basic]
type = 'RavenFramework'
input = 'basic.xml'
output = 'Basic/romMeta.csv'
Expand Down Expand Up @@ -77,7 +77,7 @@
[../]
[../]

[./SingleFourier]
[./SingleFourier]
type = 'RavenFramework'
input = 'single_fourier.xml'
csv = 'SingleFourier/synthetic_0.csv'
Expand Down Expand Up @@ -278,4 +278,14 @@
csv = 'InterpolatedMaxCycles/synthetic.csv InterpolatedMaxCycles/resynthetic.csv'
skip = 'inconsistent see 1351'
[../]

[./CloudPlot]
type = 'RavenFramework'
input = 'cloud_plot.xml'
prereq = Interpolated
[./plots]
type = Exists
output = 'Interpolated/cloud_0.png Interpolated/cloud_1.png Interpolated/cloud_2.png Interpolated/cloud_3.png Interpolated/cloud_4.png Interpolated/cloud_5.png Interpolated/cloud_6.png Interpolated/cloud_7.png Interpolated/cloud_8.png Interpolated/cloud_9.png'
[../]
[../]
[]