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

OutStreams as RAVEN entity #1329

Merged
merged 18 commits into from
Oct 3, 2020
Merged
Show file tree
Hide file tree
Changes from 14 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
4 changes: 2 additions & 2 deletions framework/BaseClasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def readXML(self,xmlNode,messageHandler,variableGroups={},globalAttributes=None)
self.name = xmlNode.attrib['name']
else:
self.raiseAnError(IOError,'not found name for a '+self.__class__.__name__)
self.type = xmlNode.tag
self.type = xmlNode.tag
if globalAttributes is not None:
self.globalAttributes = globalAttributes
if 'verbosity' in xmlNode.attrib.keys() or 'verbosity' in self.globalAttributes:
Expand Down Expand Up @@ -114,7 +114,7 @@ def replaceVariableGroups(node):
self.raiseADebug('------Reading Completed for:')
self.printMe()

def handleInput(self,paramInput,messageHandler,variableGroups={},globalAttributes=None):
def handleInput(self, paramInput, messageHandler, variableGroups={}, globalAttributes=None):
"""
provide a basic reading capability from the xml input file for what is common to all types in the simulation than calls _handleInput
that needs to be overloaded and used as API. Each type supported by the simulation should have: name (xml attribute), type (xml tag),
Expand Down
12 changes: 6 additions & 6 deletions framework/DataObjects/DataSet.py
Original file line number Diff line number Diff line change
Expand Up @@ -690,7 +690,7 @@ def sliceByIndex(self,index):
# NOTE: The slice may include NaN if a variable does not have a value along a different index for this snapshot along "index"
return slices

def write(self,fileName,style='netCDF',**kwargs):
def write(self, fileName, style='netCDF', **kwargs):
"""
Writes this dataset to disk based on the format.
@ In, fileName, str, path and name of file to write
Expand All @@ -703,12 +703,12 @@ def write(self,fileName,style='netCDF',**kwargs):
if style.lower() == 'netcdf':
self._toNetCDF(fileName,**kwargs)
elif style.lower() == 'csv':
if len(self)==0: #TODO what if it's just metadata?
if len(self) == 0:
self.raiseAWarning('Nothing to write to CSV! Checking metadata ...')
else:
#first write the CSV
firstIndex = kwargs.get('firstIndex',0)
self._toCSV(fileName,start=firstIndex,**kwargs)
self._toCSV(fileName, start=firstIndex, **kwargs)
# then the metaxml
if len(self._meta):
self._toCSVXML(fileName,**kwargs)
Expand Down Expand Up @@ -1681,14 +1681,14 @@ def _getRealizationFromDataByValue(self, match, noMatch, tol=1e-15, unpackXArray
return len(self),None
return idx,self._getRealizationFromDataByIndex(idx,unpackXArray)

def _getRequestedElements(self,options):
def _getRequestedElements(self, options):
"""
Obtains a list of the elements to be written, based on defaults and options[what]
@ In, options, dict, general list of options for writing output files
@ Out, keep, list(str), list of variables that will be written to file
"""
if 'what' in options.keys():
elements = options['what'].split(',')
elements = options['what']
keep = []
for entry in elements:
small = entry.strip().lower()
Expand Down Expand Up @@ -1867,7 +1867,7 @@ def _setScalingFactors(self,var=None):
except Exception:
self.raiseADebug('Had an issue with setting scaling factors for variable "{}". No big deal.'.format(name))

def _toCSV(self,fileName,start=0,**kwargs):
def _toCSV(self, fileName, start=0, **kwargs):
"""
Writes this data object to CSV file (except the general metadata, see _toCSVXML)
@ In, fileName, str, path/name to write file
Expand Down
35 changes: 14 additions & 21 deletions framework/OutStreams/Factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,22 @@

################################################################################
from utils import utils
from .OutStreamManager import OutStreamManager
from .OutStreamPlot import OutStreamPlot
from .OutStreamPrint import OutStreamPrint
## [ Add new class here ]
################################################################################
## Alternatively, to fully automate this file:
# from OutStreamManagers import *
################################################################################

"""
Interface Dictionary (factory) (private)
"""
# This machinery will automatically populate the "knownTypes" given the
# imports defined above.
__base = 'OutStreamManager'
__interFaceDict = {}
from .OutStreamBase import OutStreamBase

from .FilePrint import FilePrint
from .GeneralPlot import GeneralPlot
# from .DataMining import DataMining
# from .VDCComparison import VDCComparison
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

These are placeholders for the future; they can be removed if desired. Same with later on in this file.


for classObj in utils.getAllSubclasses(eval(__base)):
## As long as these subclasses follow the pattern of starting with OutStream
## this will appropriately key them to a more user-friendly name without the
## need for them to redudantly prepend "X" as "OutStreamX"
key = classObj.__name__.replace('OutStream','')
__interFaceDict[key] = classObj
# Interface Dictionary (factory) (private)
__base = 'OutStreamBase'
__interFaceDict = {
'Print': FilePrint,
'Plot': GeneralPlot,
# 'DataMining': DataMining,
# 'VDCComparison': VDCComparison,
Copy link
Collaborator

Choose a reason for hiding this comment

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

In my opinion the specializations of the Plots/Prints should be under Plot/Print (similarly to External Model/type or Code, CodeInterfaces)

Copy link
Collaborator

Choose a reason for hiding this comment

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

placeholder for discussion

Copy link
Collaborator Author

@PaulTalbot-INL PaulTalbot-INL Oct 2, 2020

Choose a reason for hiding this comment

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

I'm noticing immediately that there would be a big benefit for a base class for Plot, although I think the Entity approach is cleaner. Maybe in the future:

<OutStreams>
    <Print>
        <MyCustomPrint>
          <... options ...>
        </MyCustomPrint>
    </Print>
</OutStreams>

although there's a couple things I don't love about this:

  • It forces an OutStream to be only a Print or a Plot (I can imagine cases where both make sense)
  • It is regressing towards the approach in the listing class=OutStream, type=Print, subType=MyPrint instead of class=OutStream type=MyPrint
  • We have to modify all existing RAVEN inputs if we add a subspot under for , or some fancy checking to see if we're dealing with a special type or the generic type.

The other two options I see are the way I hinted at here

<OutStreams>
    <MyCustomPrint>
        <... options ...>
    </MyCustomPrint>
</OutStreams>

or using the subType approach from the ROMs, that I think we don't like much.

Definitely a good discussion to have when we start designing the first new entries.

}

def knownTypes():
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,14 @@

@author: alfoa
"""
#for future compatibility with Python 3-----------------------------------------
from __future__ import division, print_function, unicode_literals, absolute_import
#End compatibility block for Python 3-------------------------------------------
#External Modules---------------------------------------------------------------
import os
#External Modules End-----------------------------------------------------------

#Internal Modules---------------------------------------------------------------
from utils import InputData, InputTypes
import DataObjects
from .OutStreamManager import OutStreamManager
from .OutStreamBase import OutStreamBase
from ClassProperty import ClassProperty
#Internal Modules End-----------------------------------------------------------

class OutStreamPrint(OutStreamManager):
class FilePrint(OutStreamBase):
alfoa marked this conversation as resolved.
Show resolved Hide resolved
"""
Class for managing the printing of files as an output stream.
"""
Expand All @@ -46,26 +40,83 @@ def availableOutStreamTypes(cls):
"""
A class level constant that tells developers what outstreams are
available from this class
@ In, cls, the OutStreamPrint class of which this object will be a type
@ In, cls, the OutStreams class of which this object will be a type
"""
return cls._availableOutStreamTypes

@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 = OutStreamBase.getInputSpecification()

types = InputTypes.makeEnumType('FilePrintTypes', 'FilePrintTypes', cls._availableOutStreamTypes)
spec.addSub(InputData.parameterInputFactory('type', contentType=types))
spec.addSub(InputData.parameterInputFactory('source', contentType=InputTypes.StringListType))
spec.addSub(InputData.parameterInputFactory('what', contentType=InputTypes.StringListType))
spec.addSub(InputData.parameterInputFactory('filename', contentType=InputTypes.StringType))
spec.addSub(InputData.parameterInputFactory('clusterLabel', contentType=InputTypes.StringType))

# these are in user manual or code, but don't appear to be used/documented ...
# spec.addSub(InputData.parameterInputFactory('target', contentType=InputTypes.StringListType))
# spec.addSub(InputData.parameterInputFactory('directory',
# contentType=InputTypes.StringListType))
return spec

def __init__(self):
"""
Constructor
@ In, None
@ Out, None
"""
OutStreamManager.__init__(self)
OutStreamBase.__init__(self)
self.type = 'OutStreamFilePrint'
self.printTag = 'OUTSTREAM PRINT'
OutStreamManager.__init__(self)
self.sourceName = []
self.sourceData = None
self.what = None
# dictionary of what indices have already been printed, so we don't duplicate writing efforts
self.indexPrinted = {} # keys are filenames, which should be reset at the end of every step
self.subDirectory = None # subdirectory where to store the outputs

def _handleInput(self, spec):
"""
Loads the input specs for this object.
@ In, spec, InputData.ParameterInput, input specifications
@ Out, None
"""
typ = spec.findFirst('type')
if typ is None:
self.raiseAnError(IOError, f'OutStream.Print "{self.name}" is missing the "type" node!')
self.options['type'] = typ.value

source = spec.findFirst('source')
if source is None:
self.raiseAnError(IOError, f'OutStream.Print "{self.name}" is missing the "source" node!')
self.sourceName = source.value

# FIXME this is a terrible name
what = spec.findFirst('what')
if what is not None:
self.what = what.value # [x.lower() for x in what.value]

fname = spec.findFirst('filename')
if fname is not None:
self.filename = fname.value

cluster = spec.findFirst('clusterLabel')
if cluster is not None:
self.options['clusterLabel'] = cluster.value

# checks
if self.options['type'] == 'csv' and self.what is not None:
for target in [x.lower() for x in self.what]:
if not target.startswith(('input', 'output', 'metadata')):
self.raiseAnError(IOError, f'<what> requests must start with "input", "output", or "metadata"! See OutStream.Print "{self.name}"')

def localGetInitParams(self):
"""
This method is called from the base function. It retrieves the initial
Expand All @@ -78,7 +129,7 @@ def localGetInitParams(self):
for index in range(len(self.sourceName)):
paramDict['Source Name ' + str(index) + ' :'] = self.sourceName[index]
if self.what:
for index, var in enumerate(self.what.split(',')):
for index, var in enumerate(self.what):
paramDict['Variable Name ' + str(index) + ' :'] = var
return paramDict

Expand All @@ -91,35 +142,7 @@ def initialize(self, inDict):
@ Out, None
"""
# the linking to the source is performed in the base class initialize method
OutStreamManager.initialize(self, inDict)

def localReadXML(self, xmlNode):
"""
This Function is called from the base class, It reads the parameters that
belong to a plot block (outputs by filling data structure (self))
@ In, xmlNode, xml.etree.ElementTree.Element, Xml element node
@ Out, None
"""
self.type = 'OutStreamPrint'
for subnode in xmlNode:
if subnode.tag not in ['type','source','what','filename','target','clusterLabel','directory']:
self.raiseAnError(IOError, ' Print Outstream object ' + str(self.name) + ' contains the following unknown node: ' + str(subnode.tag))
if subnode.tag == 'source':
self.sourceName = subnode.text.split(',')
elif subnode.tag == 'filename':
self.filename = subnode.text
else:
self.options[subnode.tag] = subnode.text
if 'type' not in self.options.keys():
self.raiseAnError(IOError, 'type tag not present in Print block called ' + self.name)
if self.options['type'] not in self.availableOutStreamTypes:
self.raiseAnError(TypeError, 'Print type ' + self.options['type'] + ' not available yet. ')
if 'what' in self.options.keys():
self.what = self.options['what']
if self.options['type'] == 'csv':
for elm in self.what.lower().split(","):
if not elm.startswith("input") and not elm.startswith("output") and not elm.startswith("metadata"):
self.raiseAnError(IOError, 'Not recognized request in "what" node <'+elm.strip()+'>. The request must begin with one of "input", "output" or "metadata" or it could be "all" for ROMs!')
OutStreamBase.initialize(self, inDict)

def addOutput(self):
"""
Expand Down Expand Up @@ -156,12 +179,12 @@ def addOutput(self):
self.raiseAWarning('Label clustering currently only works for PointSet data objects! Skipping for',self.sourceData[index].name)
else:
dictOptions['clusterLabel'] = self.options['clusterLabel']
try:
rlzIndex = self.sourceData[index].write(filename,style='CSV',**dictOptions)
except AttributeError:
self.raiseAnError(NotImplementedError, 'No implementation for source type', self.sourceData[index].type, 'and output type "'+str(self.options['type'].strip())+'"!')
finally:
self.indexPrinted[filename] = rlzIndex
#try:
rlzIndex = self.sourceData[index].write(filename,style='CSV',**dictOptions)
#except AttributeError:
# self.raiseAnError(NotImplementedError, 'No implementation for source type', self.sourceData[index].type, 'and output type "'+str(self.options['type'].strip())+'"!')
# finally:
# self.indexPrinted[filename] = rlzIndex
elif self.options['type'] == 'xml':
try:
self.sourceData[index].printXML(dictOptions)
Expand Down
Loading