Skip to content

Commit b3fa3b9

Browse files
PaulTalbot-INLalfoa
authored andcommitted
ExternalXML in RAVEN Code Interface (#594)
* moved ExternalXML reader to xmlUtils * found common replacement strategy for both ElementTree and InputTree * RrR ExternalXML compatability and test * documentation * pylint file to open fix * reviewer comment
1 parent 2235c9b commit b3fa3b9

File tree

11 files changed

+221
-104
lines changed

11 files changed

+221
-104
lines changed

doc/user_manual/existing_interfaces.tex

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ \subsection{Generic Interface}
8383
the GenericCode interface can be invoked using the \xmlNode{outputFile}
8484
node in which the output file name (CSV only) must be specified.
8585
For example, in the previous example, say instead of \texttt{-a gen.two} and \texttt{-o myOut}
86-
in the command line, the code always produce a CSV file named ``[email protected]'';
86+
in the command line, the code always produce a CSV file named ``[email protected]'';
8787

8888
Then, our example XML for the code would be
8989

@@ -134,9 +134,9 @@ \subsection{RAVEN Interface}
134134
\label{subsec:RAVENInterface}
135135
The RAVEN interface is meant to provide the possibility to execute a RAVEN input file
136136
driving a set of SLAVE RAVEN calculations. For example, if the user wants to optimize the parameters
137-
of a surrogate model (e.g. minimizing the distance between the surrogate predictions and the real data), he
137+
of a surrogate model (e.g. minimizing the distance between the surrogate predictions and the real data), he
138138
can achieve this task by setting up a RAVEN input file (master) that performs an optimization on the feature
139-
space characterized by the surrogate model parameters, whose training and validation assessment is performed in the SLAVE
139+
space characterized by the surrogate model parameters, whose training and validation assessment is performed in the SLAVE
140140
RAVEN runs.
141141
\\ There are some limitations for this interface:
142142
\begin{itemize}
@@ -150,7 +150,7 @@ \subsection{RAVEN Interface}
150150
\\ Similarly to any other code interface, the user provides paths to executables and aliases for sampled variables within the
151151
\xmlNode{Models} block. The \xmlNode{Code} block will contain attributes \xmlAttr{name} and
152152
\xmlAttr{subType}. \xmlAttr{name} identifies that particular \xmlNode{Code} model within RAVEN, and
153-
\xmlAttr{subType} specifies which code interface the model will use (In this case \xmlAttr{subType}=``RAVEN'').
153+
\xmlAttr{subType} specifies which code interface the model will use (In this case \xmlAttr{subType}=``RAVEN'').
154154
The \xmlNode{executable}
155155
block should contain the absolute or relative (with respect to the current working
156156
directory) path to the RAVEN framework script (\textbf{raven\_framework}).
@@ -167,45 +167,45 @@ \subsection{RAVEN Interface}
167167
\begin{lstlisting}[language=python]
168168
def manipulateScalarSampledVariables(sampledVariables):
169169
"""
170-
This method is aimed to manipulate scalar variables.
171-
The user can create new variables based on the
170+
This method is aimed to manipulate scalar variables.
171+
The user can create new variables based on the
172172
variables sampled by RAVEN
173-
@ In, sampledVariables, dict, dictionary of
173+
@ In, sampledVariables, dict, dictionary of
174174
sampled variables ({"var1":value1,"var2":value2})
175-
@ Out, None, the new variables should be
175+
@ Out, None, the new variables should be
176176
added in the "sampledVariables" dictionary
177177
"""
178-
newVariableValue =
179-
sampledVariables['Distributions|Uniform@name:a_dist|lowerBound']
178+
newVariableValue =
179+
sampledVariables['Distributions|Uniform@name:a_dist|lowerBound']
180180
+ 1.0
181-
sampledVariables['Distributions|Uniform@name:a_dist|upperBound'] =
181+
sampledVariables['Distributions|Uniform@name:a_dist|upperBound'] =
182182
newVariableValue
183183
return
184184
\end{lstlisting}
185185

186-
\item \textbf{\textit{convertNotScalarSampledVariables}}, a method that is aimed to convert not scalar variables (e.g. 1D arrays) into multiple scalar variables
186+
\item \textbf{\textit{convertNotScalarSampledVariables}}, a method that is aimed to convert not scalar variables (e.g. 1D arrays) into multiple scalar variables
187187
(e.g. \xmlNode{constant}(s) in a sampling strategy).
188-
This method is going to be required in case not scalar variables are detected by the interface.
188+
This method is going to be required in case not scalar variables are detected by the interface.
189189
Example:
190190
\begin{lstlisting}[language=python]
191191
def convertNotScalarSampledVariables(noScalarVariables):
192192
"""
193-
This method is aimed to convert not scalar
193+
This method is aimed to convert not scalar
194194
variables into multiple scalar variables. The user MUST
195195
create new variables based on the not Scalar Variables
196196
sampled (and passed in) by RAVEN
197-
@ In, noScalarVariables, dict, dictionary of sampled
197+
@ In, noScalarVariables, dict, dictionary of sampled
198198
variables that are not scalar ({"var1":1Darray1,"var2":1Darray2})
199-
@ Out, newVars, dict, the new variables that have
200-
been created based on the not scalar variables
199+
@ Out, newVars, dict, the new variables that have
200+
been created based on the not scalar variables
201201
contained in "noScalarVariables" dictionary
202202
"""
203203
oneDimensionalArray =
204204
noScalarVariables['temperatureHistory']
205205
newVars = {}
206206
for cnt, value in enumerate(oneDimensionalArray):
207207
newVars['Samplers|MonteCarlo@name:myMC|constant'+
208-
'@name=temperatureHistory'+str(cnt)] =
208+
'@name=temperatureHistory'+str(cnt)] =
209209
oneDimensionalArray[cnt]
210210
return newVars
211211
\end{lstlisting}
@@ -225,7 +225,7 @@ \subsection{RAVEN Interface}
225225
</Code>
226226
\end{lstlisting}
227227

228-
Like for every other interface, the syntax of the variable names is important to make the parser understand how to perturb an input file.
228+
Like for every other interface, the syntax of the variable names is important to make the parser understand how to perturb an input file.
229229
\\ For the RAVEN interface, a syntax inspired by the XPath nomenclature is used.
230230
\begin{lstlisting}[style=XML]
231231
<Samplers>
@@ -251,7 +251,7 @@ \subsection{RAVEN Interface}
251251
\begin{lstlisting}[style=XML]
252252
<Models>
253253
<ROM name="ROM1" subType="SciKitLearn">
254-
...
254+
...
255255
<C>10.0</C>
256256
...
257257
</ROM>
@@ -261,7 +261,7 @@ \subsection{RAVEN Interface}
261261
\begin{lstlisting}[style=XML]
262262
<Models>
263263
<ROM name="ROM1" subType="SciKitLearn">
264-
...
264+
...
265265
<tol>0.0001</tol>
266266
...
267267
</ROM>
@@ -275,9 +275,9 @@ \subsection{RAVEN Interface}
275275
<variable name="var1">
276276
...
277277
<grid construction="equal" type="value" steps="1">0 1</grid>
278-
...
278+
...
279279
</variable>
280-
280+
281281
...
282282
</MonteCarlo>
283283
</Samplers>
@@ -295,6 +295,12 @@ \subsection{RAVEN Interface}
295295
</Files>
296296
\end{lstlisting}
297297

298+
\subsubsection{ExternalXML and RAVEN interface}
299+
Care must be taken if the SLAVE RAVEN uses \xmlNode{ExternalXML} nodes. In this case, each file containing
300+
external XML nodes must be added in the \xmlNode{Step} as an \xmlNode{Input} class \xmlAttr{Files} to make sure it gets copied to
301+
the individual run directory. The type for these files can be anything, with the exception of type
302+
\xmlString{raven}.
303+
298304
%%%%%%%%%%%%%%%%%%%%%%%%%%%%
299305
%%%%%% RELAP5 INTERFACE %%%%%%
300306
%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -1303,20 +1309,20 @@ \subsubsection{Models}
13031309
...
13041310
</Simulation>
13051311
\end{lstlisting}
1306-
RAVEN works best with Comma-Separated Value (CSV) files. Therefore, the default
1312+
RAVEN works best with Comma-Separated Value (CSV) files. Therefore, the default
13071313
.mat output type needs to be converted to .csv output.
1308-
The Dymola interface will automatically convert the .mat output to human-readable
1314+
The Dymola interface will automatically convert the .mat output to human-readable
13091315
forms, i.e., .csv output, through its implementation of the finalizeCodeOutput function.
13101316
\\In order to speed up the reading and conversion of the .mat file, the user can specify
1311-
the list of variables (in addition to the Time variable) that need to be imported and
1312-
converted into a csv file minimizing
1313-
the IO memory usage as much as possible. Within the \xmlNode{Code} the following
1314-
XML
1317+
the list of variables (in addition to the Time variable) that need to be imported and
1318+
converted into a csv file minimizing
1319+
the IO memory usage as much as possible. Within the \xmlNode{Code} the following
1320+
XML
13151321
node (in addition ot the \xmlNode{executable} one) can be inputted:
13161322

13171323
\begin{itemize}
1318-
\item \xmlNode{outputVariablesToLoad}, \xmlDesc{space separated list, optional
1319-
parameter}, a space separated list of variables that need be exported from the .mat
1324+
\item \xmlNode{outputVariablesToLoad}, \xmlDesc{space separated list, optional
1325+
parameter}, a space separated list of variables that need be exported from the .mat
13201326
file (in addition to the Time variable). \default{all the variables in the .mat file}.
13211327
\end{itemize}
13221328
For example:

framework/CodeInterfaces/RAVEN/RAVENparser.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import numpy as np
3131
from collections import OrderedDict
3232

33+
from utils import xmlUtils
34+
3335
class RAVENparser():
3436
"""
3537
Import the RAVEN input as xml tree, provide methods to add/change entries and print it back
@@ -51,6 +53,11 @@ def __init__(self, inputFile):
5153
except IOError as e:
5254
raise IOError(self.printTag+' ERROR: Input Parsing error!\n' +str(e)+'\n')
5355
self.tree = tree.getroot()
56+
57+
# expand the ExteranlXML nodes
58+
cwd = os.path.dirname(inputFile)
59+
xmlUtils.expandExternalXML(self.tree,cwd)
60+
5461
# get the variable groups
5562
variableGroup = self.tree.find('VariableGroups')
5663
if variableGroup is not None:

framework/Driver.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,8 @@ def checkVersions():
217217
sys.exit(1)
218218

219219
# call the function to load the external xml files into the input tree
220-
simulation.XMLpreprocess(root,inputFileName=inputFile)
220+
cwd = os.path.dirname(os.path.abspath(inputFile))
221+
simulation.XMLpreprocess(root,cwd)
221222
#generate all the components of the simulation
222223
#Call the function to read and construct each single module of the simulation
223224
simulation.XMLread(root,runInfoSkip=set(["DefaultInputFile"]),xmlFilename=inputFile)

framework/Simulation.py

Lines changed: 4 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,7 @@
4747
from JobHandler import JobHandler
4848
import MessageHandler
4949
import VariableGroups
50-
from utils import utils
51-
from utils import TreeStructure
50+
from utils import utils,TreeStructure,xmlUtils
5251
from Application import __QtAvailable
5352
from Interaction import Interaction
5453
if __QtAvailable:
@@ -370,49 +369,14 @@ def __createAbsPath(self,fileIn):
370369
path = os.path.normpath(self.runInfoDict['WorkingDir'])
371370
curfile.prependPath(path) #this respects existing path from the user input, if any
372371

373-
def ExternalXMLread(self,externalXMLFile,externalXMLNode,xmlFileName=None):
374-
"""
375-
parses the external xml input file
376-
@ In, externalXMLFile, string, the filename for the external xml file that will be loaded
377-
@ In, externalXMLNode, string, decribes which node will be loaded to raven input file
378-
@ In, xmlFileName, string, optional, the raven input file name
379-
@ Out, externalElemment, xml.etree.ElementTree.Element, object that will be added to the current tree of raven input
380-
"""
381-
#TODO make one for getpot too
382-
if '~' in externalXMLFile:
383-
externalXMLFile = os.path.expanduser(externalXMLFile)
384-
if not os.path.isabs(externalXMLFile):
385-
if xmlFileName == None:
386-
self.raiseAnError(IOError,'Relative working directory requested but input xmlFileName is None.')
387-
xmlDirectory = os.path.dirname(os.path.abspath(xmlFileName))
388-
externalXMLFile = os.path.join(xmlDirectory,externalXMLFile)
389-
if os.path.exists(externalXMLFile):
390-
externalTree = TreeStructure.parse(externalXMLFile)
391-
externalElement = externalTree.getroot()
392-
if externalElement.tag != externalXMLNode:
393-
self.raiseAnError(IOError,'The required node is: ' + externalXMLNode + 'is different from the provided external xml type: ' + externalElement.tag)
394-
else:
395-
self.raiseAnError(IOError,'The external xml input file ' + externalXMLFile + ' does not exist!')
396-
return externalElement
397-
398-
def XMLpreprocess(self,node,inputFileName=None):
372+
def XMLpreprocess(self,node,cwd):
399373
"""
400374
Preprocess the input file, load external xml files into the main ET
401375
@ In, node, TreeStructure.InputNode, element of RAVEN input file
402-
@ In, inputFileName, string, optional, the raven input file name
376+
@ In, cwd, string, current working directory (for relative path searches)
403377
@ Out, None
404378
"""
405-
self.verbosity = node.attrib.get('verbosity','all').lower()
406-
for element in node.iter():
407-
for subElement in element:
408-
if subElement.tag == 'ExternalXML':
409-
self.raiseADebug('-'*2+' Loading external xml within block '+ element.tag+ ' for: {0:15}'.format(str(subElement.attrib['node']))+2*'-')
410-
nodeName = subElement.attrib['node']
411-
xmlToLoad = subElement.attrib['xmlToLoad'].strip()
412-
newElement = self.ExternalXMLread(xmlToLoad,nodeName,inputFileName)
413-
element.append(newElement)
414-
element.remove(subElement)
415-
self.XMLpreprocess(node,inputFileName)
379+
xmlUtils.expandExternalXML(node,cwd)
416380

417381
def XMLread(self,xmlNode,runInfoSkip = set(),xmlFilename=None):
418382
"""

framework/utils/TreeStructure.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,16 @@ def __getitem__(self, index):
435435
"""
436436
return self.children[index]
437437

438+
def __setitem__(self,index,value):
439+
"""
440+
Sets a specific child node.
441+
@ In, index, int, the index for the child
442+
@ In, value, Node, the child itself
443+
@ Out, None
444+
"""
445+
value = self.assureIsNode(value)
446+
self.children[index] = value
447+
438448
def __repr__(self):
439449
"""
440450
String representation.
@@ -468,7 +478,7 @@ def append(self,node):
468478
@ In, node, Node, node to append to children
469479
@ Out, None
470480
"""
471-
assert isinstance(node,InputNode)
481+
node = self.assureIsNode(node)
472482
self.children.append(node)
473483

474484
def find(self,nodeName):
@@ -524,6 +534,21 @@ def iter(self, name=None):
524534
for e in e.iter(name):
525535
yield e
526536

537+
def assureIsNode(self,node):
538+
"""
539+
Takes care of translating XML to Node on demand.
540+
@ In, node, Node or ET.Element, node to fix up
541+
@ Out, node, fixed node
542+
"""
543+
if not isinstance(node,InputNode):
544+
# if XML, convert to InputNode
545+
if isinstance(node,ET.Element):
546+
tree = ET.ElementTree(node)
547+
node = xmlToInputTree(tree).getroot()
548+
else:
549+
raise TypeError('TREE-STRUCTURE ERROR: When trying to use node "{}", unrecognized type "{}"!'.format(node,type(node)))
550+
return node
551+
527552
def printXML(self):
528553
"""
529554
Returns string representation of tree (in XML format).

framework/utils/xmlUtils.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,3 +308,43 @@ def fixXmlTag(msg):
308308
print('XML UTILS: Prepending "_" to illegal tag "'+msg+'"')
309309
msg = '_' + msg
310310
return msg
311+
312+
def expandExternalXML(root,workingDir):
313+
"""
314+
Expands "ExternalXML" nodes with the associated nodes and returns the full tree.
315+
@ In, root, xml.etree.ElementTree.Element, main node whose children might be ExternalXML nodes
316+
@ In, workingDir, string, base location from which to find additional xml files
317+
@ Out, None
318+
"""
319+
# find instances of ExteranlXML nodes to replace
320+
for i,subElement in enumerate(root):
321+
if subElement.tag == 'ExternalXML':
322+
nodeName = subElement.attrib['node']
323+
xmlToLoad = subElement.attrib['xmlToLoad'].strip()
324+
newElement = readExternalXML(xmlToLoad,nodeName,workingDir)
325+
root[i] = newElement
326+
subElement = newElement
327+
# whether expanded or not, search each subnodes for more external xml
328+
expandExternalXML(subElement,workingDir)
329+
330+
def readExternalXML(extFile,extNode,cwd):
331+
"""
332+
Loads external XML into nodes.
333+
@ In, extFile, string, filename for the external xml file
334+
@ In, extNode, string, tag of node to load
335+
@ In, cwd, string, current working directory (for relative paths)
336+
@ Out, externalElement, xml.etree.ElementTree.Element, object from file
337+
"""
338+
# expand user tilde
339+
if '~' in extFile:
340+
extFile = os.path.expanduser(extFile)
341+
# check if absolute or relative found
342+
if not os.path.isabs(extFile):
343+
extFile = os.path.join(cwd,extFile)
344+
if not os.path.exists(extFile):
345+
raise IOError('XML UTILS ERROR: External XML file not found: "{}"'.format(os.path.abspath(extFile)))
346+
# find the element to read
347+
root = ET.parse(open(extFile,'r')).getroot()
348+
if root.tag != extNode.strip():
349+
raise IOError('XML UTILS ERROR: Node "{}" is not the root node of "{}"!'.format(extNode,extFile))
350+
return root
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<DataObjects>
2+
<PointSet name="inputHolder">
3+
<Input>DeltaTimeScramToAux,DG1recoveryTime</Input>
4+
<Output>OutputPlaceHolder</Output>
5+
</PointSet>
6+
<PointSet inputTs="2" name="Pointset_from_database_for_rom_trainer">
7+
<Input>DeltaTimeScramToAux,DG1recoveryTime</Input>
8+
<Output>CladTempThreshold</Output>
9+
</PointSet>
10+
<PointSet historyName="1" inputTs="2" name="data_for_sampling_empty_at_begin">
11+
<Input>DeltaTimeScramToAux,DG1recoveryTime</Input>
12+
<Output>OutputPlaceHolder</Output>
13+
</PointSet>
14+
<PointSet historyName="1" inputTs="2" name="data_for_sampling_empty_at_begin_nd">
15+
<Input>DeltaTimeScramToAux,DG1recoveryTime</Input>
16+
<Output>OutputPlaceHolder</Output>
17+
</PointSet>
18+
<PointSet inputTs="2" name="outputMontecarloRom">
19+
<Input>DeltaTimeScramToAux,DG1recoveryTime</Input>
20+
<Output>CladTempThreshold</Output>
21+
</PointSet>
22+
<HistorySet name="outputMontecarloRomHS">
23+
<Input>DeltaTimeScramToAux,DG1recoveryTime</Input>
24+
<Output>CladTempThreshold</Output>
25+
</HistorySet>
26+
<PointSet inputTs="2" name="outputMontecarloRomND">
27+
<Input>DeltaTimeScramToAux,DG1recoveryTime</Input>
28+
<Output>CladTempThreshold</Output>
29+
</PointSet>
30+
</DataObjects>

0 commit comments

Comments
 (0)