Skip to content

Commit

Permalink
Ensemble Model Connections Improvement (#732)
Browse files Browse the repository at this point in the history
* fix the graph theory used to check the connections between different models

* add test

* resolve comments, update tests and docstring
  • Loading branch information
wangcj05 authored and alfoa committed Aug 9, 2018
1 parent 3c202ee commit 02ba132
Show file tree
Hide file tree
Showing 12 changed files with 383 additions and 32 deletions.
7 changes: 0 additions & 7 deletions framework/Models/EnsembleModel.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,13 +300,6 @@ def initialize(self,runInfo,inputs,initDict=None):
# construct the ensemble model directed graph
self.ensembleModelGraph = graphStructure.graphObject(modelsToOutputModels)
# make some checks
# FIXME: the following check is too tight, even if the models are connected, the
# code may still raise an error. I think in really, we do not need to raise an error,
# maybe a warning is enough. For example:
# a -> b -> c
# ^
# |
# e -> d -> f
if not self.ensembleModelGraph.isConnectedNet():
isolatedModels = self.ensembleModelGraph.findIsolatedVertices()
self.raiseAnError(IOError, "Some models are not connected. Possible candidates are: "+' '.join(isolatedModels))
Expand Down
4 changes: 2 additions & 2 deletions framework/OutStreams/OutStreamPrint.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ def localGetInitParams(self):
for index in range(len(self.sourceName)):
paramDict['Source Name ' + str(index) + ' :'] = self.sourceName[index]
if self.what:
for index in range(len(self.what)):
paramDict['Variable Name ' + str(index) + ' :'] = self.what[index]
for index, var in enumerate(self.what.split(',')):
paramDict['Variable Name ' + str(index) + ' :'] = var
return paramDict

def initialize(self, inDict):
Expand Down
60 changes: 37 additions & 23 deletions framework/utils/graphStructure.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#External Modules------------------------------------------------------------------------------------
import sys
import itertools
import copy
#External Modules End--------------------------------------------------------------------------------
#Internal Modules------------------------------------------------------------------------------------
from utils import utils
Expand Down Expand Up @@ -124,10 +125,12 @@ def __generateEdges(self):
def findIsolatedVertices(self):
"""
This method ispects the graph and returns a list of isolated vertices.
WARNING: the self.__extendDictForGraphTheory() is used here, and never store the outputs
of this method into the self.__graphDict.
@ In, None
@ Out, isolated, list, list of isolated nodes (verteces)
"""
graph = self.__graphDict
graph = self.__extendDictForGraphTheory()
isolated = []
for vertex in graph:
if not graph[vertex]:
Expand Down Expand Up @@ -202,52 +205,63 @@ def findAllPaths(self, startVertex, endVertex, path=[]):
paths.append(p)
return paths

def isConnected(self, verticesEncountered = None, startVertex=None):
def isConnected(self, graphDict, verticesEncountered = None, startVertex=None):
"""
Method that determines if the graph is connected (graph theory connectivity)
@ In, verticesEncountered, set, the encountered vertices (generally it is None when called from outside)
@ In, graphDict, dict, the graph dictionary
@ In, startVertex, string, the starting vertex
@ Out, isConnected, bool, is the graph fully connected?
"""
if verticesEncountered is None:
verticesEncountered = set()
gdict = self.__graphDict
vertices = list(gdict.keys())
vertices = list(graphDict.keys())
if not startVertex:
# chosse a vertex from graph as a starting point
startVertex = vertices[0]
verticesEncountered.add(startVertex)
if len(verticesEncountered) != len(vertices):
for vertex in gdict[startVertex]:
for vertex in graphDict[startVertex]:
if vertex not in verticesEncountered:
if self.isConnected(verticesEncountered, vertex):
if self.isConnected(graphDict, verticesEncountered, vertex):
return True
else:
return True
return False

def isConnectedNet(self, startVertex=None):
def isConnectedNet(self):
"""
Method that determines if the graph is a connected net (all the vertices are connect with each other through other vertices)
This method can state that we have a connected net even if, based on graph theory, the graph is not connected
@ In, startVertex, string, the starting vertex
Method that determines if the graph is a connected net (all the vertices are connect
with each other through other vertices).
WARNING: the self.__extendDictForGraphTheory() is used here, and never store the outputs
of this method into the self.__graphDict.
@ In, None
@ Out, graphNetConnected, bool, is the graph net fully connected?
"""
graphNetConnected = self.isConnected()
if graphNetConnected:
return graphNetConnected
# the graph is not connected (Graph theory)
uniquePaths = self.findAllUniquePaths()
# now check if there is at list a node that is common in all the paths
if len(uniquePaths) > 0:
graphNetConnected = True
startPath = set(uniquePaths.pop())
for otherPath in uniquePaths:
if startPath.isdisjoint(otherPath):
graphNetConnected = False
break
graphDict = self.__extendDictForGraphTheory()
graphNetConnected = self.isConnected(graphDict)
return graphNetConnected

def __extendDictForGraphTheory(self):
"""
Method to extend the graph dictionary in order to be accepted by the graph theory.
WARNING: This is method is only used to extend the __graphDict, and should be only
used for isConnectedNet method. The extended dictionary should not be stored in
__graphDict. This is because the class graphObject is supposed to work with
directed graph to determine the execution orders of the Models listed under EnsembleModel.
@ In, None
@ Out, graphDict, dict, the extended graph dictionary, used for isConnectedNet and findIsolatedVertices
"""
graphDict = copy.deepcopy(self.__graphDict)
# Extend graph dictionary generated by ensemble model to graph theory acceptable dictionary
# Basicly, if a -> b (a is connected to b), the self.__graphDict = {'a':['b'], 'b':[]}
# Since a is connected to b, from the graph theory, the dictionary should become {'a':['b'], 'b':['a']}
for inModel, outModelList in self.__graphDict.items():
for outModel in outModelList:
if inModel not in graphDict[outModel]:
graphDict[outModel].append(inModel)
return graphDict

def findAllUniquePaths(self, startVertices=None):
"""
This method finds all the unique paths in the graph
Expand Down
24 changes: 24 additions & 0 deletions tests/framework/ensembleModelTests/checkConnection/alpha.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.

def run(self, Input):
"""
A dummy model used to check the connections for the EnsembleModel
@ In, self, object, object to store members on
@ In, Input, dict, dictionary containing inputs from RAVEN
@ Out, None
"""
self.A = Input.get('A')
self.B = Input.get('B')
self.C = self.A + self.B
24 changes: 24 additions & 0 deletions tests/framework/ensembleModelTests/checkConnection/beta.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.

def run(self, Input):
"""
A dummy model used to check the connections for the EnsembleModel
@ In, self, object, object to store members on
@ In, Input, dict, dictionary containing inputs from RAVEN
@ Out, None
"""
self.C = Input.get('C')
self.D = Input.get('D')
self.E = self.C - self.D + 1.0
23 changes: 23 additions & 0 deletions tests/framework/ensembleModelTests/checkConnection/delta.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# 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.

def run(self, Input):
"""
A dummy model used to check the connections for the EnsembleModel
@ In, self, object, object to store members on
@ In, Input, dict, dictionary containing inputs from RAVEN
@ Out, None
"""
self.E = Input.get('E')
self.F = self.E + 1.0
23 changes: 23 additions & 0 deletions tests/framework/ensembleModelTests/checkConnection/epsilon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# 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.

def run(self, Input):
"""
A dummy model used to check the connections for the EnsembleModel
@ In, self, object, object to store members on
@ In, Input, dict, dictionary containing inputs from RAVEN
@ Out, None
"""
self.H = Input.get('H')
self.G = self.H + 3.0
23 changes: 23 additions & 0 deletions tests/framework/ensembleModelTests/checkConnection/gamma.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# 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.

def run(self, Input):
"""
A dummy model used to check the connections for the EnsembleModel
@ In, self, object, object to store members on
@ In, Input, dict, dictionary containing inputs from RAVEN
@ Out, None
"""
self.G = Input.get('G')
self.D = self.G + 1.0
23 changes: 23 additions & 0 deletions tests/framework/ensembleModelTests/checkConnection/rho.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# 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.

def run(self, Input):
"""
A dummy model used to check the connections for the EnsembleModel
@ In, self, object, object to store members on
@ In, Input, dict, dictionary containing inputs from RAVEN
@ Out, None
"""
self.D = Input.get('D')
self.I = self.D - 1.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
A,B,C,D,E,F,G
0.0,2.0,2.0,9.0,-6.0,-5.0,8.0
0.0,2.0,2.0,10.5,-7.5,-6.5,9.5
0.0,2.0,2.0,12.0,-9.0,-8.0,11.0
0.0,3.0,3.0,9.0,-5.0,-4.0,8.0
0.0,3.0,3.0,10.5,-6.5,-5.5,9.5
0.0,3.0,3.0,12.0,-8.0,-7.0,11.0
1.0,2.0,3.0,9.0,-5.0,-4.0,8.0
1.0,2.0,3.0,10.5,-6.5,-5.5,9.5
1.0,2.0,3.0,12.0,-8.0,-7.0,11.0
1.0,3.0,4.0,9.0,-4.0,-3.0,8.0
1.0,3.0,4.0,10.5,-5.5,-4.5,9.5
1.0,3.0,4.0,12.0,-7.0,-6.0,11.0
Loading

0 comments on commit 02ba132

Please sign in to comment.