From a6d76f4c41d0295ccf590a23322b42e52b9f14dc Mon Sep 17 00:00:00 2001 From: Hendrik Buschmeier <hbuschme@uni-bielefeld.de> Date: Sat, 5 Apr 2014 00:05:46 +0200 Subject: [PATCH] Refactoring: Moved individual network modules to networks.py. --- primo/core/BayesNet.py | 138 ---------- primo/core/BayesianDecisionNetwork.py | 105 -------- primo/core/DynamicBayesNet.py | 67 ----- primo/core/TwoTBN.py | 81 ------ primo/core/__init__.py | 5 - primo/networks.py | 374 ++++++++++++++++++++++++++ 6 files changed, 374 insertions(+), 396 deletions(-) delete mode 100644 primo/core/BayesNet.py delete mode 100644 primo/core/BayesianDecisionNetwork.py delete mode 100644 primo/core/DynamicBayesNet.py delete mode 100644 primo/core/TwoTBN.py delete mode 100644 primo/core/__init__.py create mode 100644 primo/networks.py diff --git a/primo/core/BayesNet.py b/primo/core/BayesNet.py deleted file mode 100644 index 82b6f2f..0000000 --- a/primo/core/BayesNet.py +++ /dev/null @@ -1,138 +0,0 @@ -import networkx as nx -from primo.core import Node - - - -class BayesNet(object): - - def __init__(self): - self.graph = nx.DiGraph() - self.node_lookup = {} - - def add_node(self, node): - if isinstance(node, Node): - if node.name in self.node_lookup.keys(): - raise Exception("Node name already exists in Bayesnet: "+node.name) - self.node_lookup[node.name]=node - self.graph.add_node(node) - else: - raise Exception("Can only add 'Node' and its subclasses as nodes into the BayesNet") - - def add_edge(self, node_from, node_to): - if node_from in self.graph.nodes() and node_to in self.graph.nodes(): - self.graph.add_edge(node_from, node_to) - node_to.announce_parent(node_from) - else: - raise Exception("Tried to add an Edge between two Nodes of which at least one was not contained in the Bayesnet") - - def remove_node(self, node): - if node.name not in self.node_lookup.keys(): - raise Exception("Node " + node.name + "does not exists") - else : - try: - self.graph.remove_node(node) - except nx.exception.NetworkXError: - raise Exception("Tried to remove a node which does not exist.") - del self.node_lookup[node.name] - - def remove_edge(self, node_from, node_to): - try: - self.graph.remove_edge(node_from, node_to) - except nx.exception.NetworkXError: - raise Exception("Tried to remove an edge which does not exist in the BayesNet") - #raise Exception("Fixme: Adapt CPD of child-node") - - def get_node(self, node_name): - try: - return self.node_lookup[node_name] - except KeyError: - raise Exception("There is no node with name "+node_name+" in the BayesNet") - - def get_all_nodes(self): - return self.graph.nodes() - - def get_nodes(self, node_names=[]): - nodes = [] - if not node_names: - nodes = self.graph.nodes() - else: - for node_name in node_names: - nodes.append(self.get_node(node_name)) - return nodes - - def get_nodes_in_topological_sort(self): - return nx.topological_sort(self.graph) - - def get_parents(self, node): - if node.name not in self.node_lookup.keys(): - raise Exception("Node " + node.name + "does not exists") - else: - return self.graph.predecessors(node) - - - def get_children(self, node): - if node.name not in self.node_lookup.keys(): - raise Exception("Node " + node.name + "does not exists") - else: - return self.graph.successors(node) - - - def get_markov_blanket(self, node): - raise Exception("Called unimplemented function") - - def is_dag(self): - raise Exception("Called unimplemented function") - - def draw(self): - import matplotlib.pyplot as plt - nx.draw_circular(self.graph) - plt.show() - - def draw_graphviz(self): - import matplotlib.pyplot as plt - nx.draw_graphviz(self.graph) - plt.show() - - def is_valid(self): - '''Check if graph structure is valid. - Returns true if graph is directed and acyclic, false otherwiese''' - - if self.graph.number_of_selfloops() > 0: - return False - - for node in self.graph.nodes(): - if self.has_loop(node): - return False - - return True - - def has_loop(self, node, origin=None): - '''Check if any path from node leads back to node. - - Keyword arguments: - node -- the start node - origin -- for internal recursive loop (default: None) - - Returns true on succes, false otherwise.''' - if not origin: - origin = node - - for successor in self.graph.successors(node): - if successor == origin: - return True - else: - return self.has_loop(successor, origin) - - def clear(self): - '''Remove all nodes and edges from the graph. - This also removes the name, and all graph, node and edge attributes.''' - self.graph.clear() - self.node_lookup.clear() - - def number_of_nodes(self): - '''Return the number of nodes in the graph.''' - return len(self) - - def __len__(self): - '''Return the number of nodes in the graph.''' - return len(self.graph) \ No newline at end of file diff --git a/primo/core/BayesianDecisionNetwork.py b/primo/core/BayesianDecisionNetwork.py deleted file mode 100644 index 3b190c4..0000000 --- a/primo/core/BayesianDecisionNetwork.py +++ /dev/null @@ -1,105 +0,0 @@ -# -*- coding: utf-8 -*- - -import networkx as nx - -from primo.core import BayesNet -from primo.core import Node -from primo.decision import DecisionNode -from primo.decision import UtilityNode -from primo.reasoning import DiscreteNode - -class BayesianDecisionNetwork(BayesNet): - - def __init__(self): - super(BayesianDecisionNetwork, self).__init__() - self.partialOrdering = [] - self.random_nodes = [] - self.decision_nodes = [] - self.utility_nodes = [] - - def is_valid(self): - '''Check if graph structure is valid. - Returns true if graph is directed, acyclic and if there is a path that connects every decision node(consistency check), - false otherwise''' - - if self.graph.number_of_selfloops() > 0: - return False - - for node in self.graph.nodes(): - if self.has_loop(node): - return False - - decisionNodeList = [] - for node in self.get_all_nodes(): - if isinstance(node, DecisionNode): - decisionNodeList.append(node) - - return all([nx.has_path(self.graph, x, y) == True for x in decisionNodeList for y in decisionNodeList]) - - def add_node(self, node): - if isinstance(node, Node): - if node.name in self.node_lookup.keys(): - raise Exception("Node name already exists in Bayesnet: "+node.name) - if isinstance(node, DiscreteNode): - self.random_nodes.append(node) - elif isinstance(node, UtilityNode): - self.utility_nodes.append(node) - elif isinstance(node, DecisionNode): - self.decision_nodes.append(node) - else: - raise Exception("Tried to add a node which the Bayesian Decision Network can not work with") - self.node_lookup[node.name]=node - self.graph.add_node(node) - else: - raise Exception("Can only add 'Node' and its subclasses as nodes into the BayesNet") - - def get_all_nodes(self): - '''Returns all RandomNodes''' - return self.random_nodes - - def get_all_decision_nodes(self): - return self.decision_nodes - - def get_all_utility_nodes(self): - return self.utility_nodes - - def add_edge(self, node_from, node_to): - """ - Adds an edge between two nodes. It is impossible to create en edge between two decision nodes and between two - utility nodes. - - keyword arguments: - node_from -- Node from where the edge shall begin - node_to -- Node where the edge shall end - """ - if isinstance(node_from, DecisionNode) and isinstance(node_to, DecisionNode): - raise Exception("Tried to add an edge from a DecisionNode to a DecisionNode") - if isinstance(node_from, UtilityNode) and isinstance(node_to, UtilityNode): - raise Exception("Tried to add an edge from a UtilityNode to a UtilityNode") - if node_from in self.graph.nodes() and node_to in self.graph.nodes(): - self.graph.add_edge(node_from, node_to) - node_to.announce_parent(node_from) - else: - raise Exception("Tried to add an Edge between two Nodes of which at least one was not contained in the Bayesnet") - - def reset_Decisions(self): - """ - Resets all decisions in the Bayesian Decision Network - """ - for node in self.decision_nodes: - node.set_state(None) - - def get_partialOrdering(self): - """ - Getter for the partial ordere - """ - return self.partialOrdering - - def set_partialOrdering(self, partialOrder): - """ - Sets the partial ordering for this Bayesian Decision Network - - partialOrder -- ordered list of RandomNodes and Decision Nodes - example: [decisionNode1, [randomNode1,randomNode2], decisionNode2, [randomNode3]] - """ - self.partialOrdering = partialOrder \ No newline at end of file diff --git a/primo/core/DynamicBayesNet.py b/primo/core/DynamicBayesNet.py deleted file mode 100644 index b196b87..0000000 --- a/primo/core/DynamicBayesNet.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- coding: utf-8 -*- - -from primo.core import BayesNet -from primo.core import TwoTBN - - -class DynamicBayesNet(BayesNet): - ''' This is the implementation of a dynamic Bayesian network (also called - temporal Bayesian network). - - Definition: DBN is a pair (B0, TwoTBN), where B0 is a BN over X(0), - representing the initial distribution over states, and TwoTBN is a - 2-TBN for the process. - See Koller, Friedman - "Probabilistic Graphical Models" (p. 204) - - Properties: Markov property, stationary, directed, discrete, - acyclic (within a slice) - ''' - - def __init__(self): - super(DynamicBayesNet, self).__init__() - self._B0 = BayesNet() - self._twoTBN = TwoTBN() - - @property - def B0(self): - ''' Get the Bayesian network representing the initial distribution.''' - return self._B0 - - @B0.setter - def B0(self, value): - ''' Set the Bayesian network representing the initial distribution.''' - if isinstance(value, BayesNet): - if not value.is_valid(): - raise Exception("BayesNet is not valid.") - self._B0 = value - else: - raise Exception("Can only set 'BayesNet' and its subclasses as " + - "B0 of a DBN.") - - @property - def twoTBN(self): - ''' Get the 2-time-slice Bayesian network.''' - return self._twoTBN - - @twoTBN.setter - def twoTBN(self, value): - ''' Set the 2-time-slice Bayesian network.''' - if isinstance(value, TwoTBN): - if not value.is_valid(): - raise Exception("BayesNet is not valid.") - self._twoTBN = value - else: - raise Exception("Can only set 'TwoTBN' and its subclasses as " + - "twoTBN of a DBN.") - - def is_valid(self): - '''Check if graph structure is valid. And if there is a same-named - inital node in towTBN for every node in BO. - Returns true if graph is directed and acyclic, false otherwiese''' - for node in self._B0.get_nodes(): - if not self._twoTBN.has_initial_node_by_name(node.name): - print("Node with name " + str(node.name) + - " not found in TwoTBN!") - return False; - - return super(DynamicBayesNet, self).is_valid() \ No newline at end of file diff --git a/primo/core/TwoTBN.py b/primo/core/TwoTBN.py deleted file mode 100644 index 6c5e33e..0000000 --- a/primo/core/TwoTBN.py +++ /dev/null @@ -1,81 +0,0 @@ -# -*- coding: utf-8 -*- - -from primo.core import BayesNet -from primo.reasoning.density import ProbabilityTable - - -class TwoTBN(BayesNet): - ''' This is the implementation of a 2-time-slice Bayesian network (2-TBN). - ''' - - def __init__(self, bayesnet=None): - BayesNet.__init__(self) - if bayesnet: - if not isinstance(bayesnet, BayesNet): - raise Exception("Parameter 'bayesnet' is not a instance of class BayesNet.") - self.graph = bayesnet.graph - self.node_lookup = bayesnet.node_lookup - self.__initial_nodes = [] - - def create_timeslice(self, state, initial=False): - ''' - Set all initial nodes to the value of their corresponding nodes - in state (previous time slice). - - Keyword arguments: - state -- Current state of the network (previous time slice). - initial -- Set initial to true if this will be the first time slice - and state only contains nodes of the initial distribution. - - Returns this instance with all initial nodes set to their - new value. - ''' - for (node, node_t) in self.__initial_nodes: - cpd = ProbabilityTable() - cpd.add_variable(node) - node.set_cpd(cpd) - if not initial: - node.set_probability(1., [(node, state[node_t])]) - else: - for node0 in state: - if node0.name == node.name: - node.set_probability(1., [(node, state[node0])]) - continue - return self - - - def add_node(self, node, initial=False, node_t=None): - ''' - Add a node to the TwoTBN. - - Keyword arguments: - node -- Node to be added. - initial -- If true node is marked as initial node. - node_t -- If initial is true this is the corresponding node in the time slice. - ''' - super(TwoTBN, self).add_node(node) - if initial: - self.set_initial_node(node, node_t) - - def set_initial_node(self, node_name, node_name_t): - ''' - Mark a node as initial node. - - Keyword arguments: - node_name -- Name of the initial node. - node_name_t -- Name of the corresponding node in the time slice. - ''' - node0 = self.get_node(node_name) - node1 = self.get_node(node_name_t) - self.__initial_nodes.append((node0, node1)) - - def has_initial_node_by_name(self, node_name): - ''' - Check if this instance has an inital node with name node_name. - - Returns true on success, false otherwise. - ''' - for (node, node_t) in self.__initial_nodes: - if node.name == node_name: - return True - return False \ No newline at end of file diff --git a/primo/core/__init__.py b/primo/core/__init__.py deleted file mode 100644 index c9b8166..0000000 --- a/primo/core/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from Node import Node -from BayesNet import BayesNet -from BayesianDecisionNetwork import BayesianDecisionNetwork -from TwoTBN import TwoTBN -from DynamicBayesNet import DynamicBayesNet diff --git a/primo/networks.py b/primo/networks.py new file mode 100644 index 0000000..74ee197 --- /dev/null +++ b/primo/networks.py @@ -0,0 +1,374 @@ +import networkx as nx + +import primo.nodes + +class BayesNet(object): + + def __init__(self): + self.graph = nx.DiGraph() + self.node_lookup = {} + + def add_node(self, node): + if isinstance(node, primo.nodes.Node): + if node.name in self.node_lookup.keys(): + raise Exception("Node name already exists in Bayesnet: "+node.name) + self.node_lookup[node.name]=node + self.graph.add_node(node) + else: + raise Exception("Can only add 'Node' and its subclasses as nodes into the BayesNet") + + def add_edge(self, node_from, node_to): + if node_from in self.graph.nodes() and node_to in self.graph.nodes(): + self.graph.add_edge(node_from, node_to) + node_to.announce_parent(node_from) + else: + raise Exception("Tried to add an Edge between two Nodes of which at least one was not contained in the Bayesnet") + + def remove_node(self, node): + if node.name not in self.node_lookup.keys(): + raise Exception("Node " + node.name + "does not exists") + else : + try: + self.graph.remove_node(node) + except nx.exception.NetworkXError: + raise Exception("Tried to remove a node which does not exist.") + del self.node_lookup[node.name] + + def remove_edge(self, node_from, node_to): + try: + self.graph.remove_edge(node_from, node_to) + except nx.exception.NetworkXError: + raise Exception("Tried to remove an edge which does not exist in the BayesNet") + #raise Exception("Fixme: Adapt CPD of child-node") + + def get_node(self, node_name): + try: + return self.node_lookup[node_name] + except KeyError: + raise Exception("There is no node with name "+node_name+" in the BayesNet") + + def get_all_nodes(self): + return self.graph.nodes() + + def get_nodes(self, node_names=[]): + nodes = [] + if not node_names: + nodes = self.graph.nodes() + else: + for node_name in node_names: + nodes.append(self.get_node(node_name)) + return nodes + + def get_nodes_in_topological_sort(self): + return nx.topological_sort(self.graph) + + def get_parents(self, node): + if node.name not in self.node_lookup.keys(): + raise Exception("Node " + node.name + "does not exists") + else: + return self.graph.predecessors(node) + + + def get_children(self, node): + if node.name not in self.node_lookup.keys(): + raise Exception("Node " + node.name + "does not exists") + else: + return self.graph.successors(node) + + + def get_markov_blanket(self, node): + raise Exception("Called unimplemented function") + + def is_dag(self): + raise Exception("Called unimplemented function") + + def draw(self): + import matplotlib.pyplot as plt + nx.draw_circular(self.graph) + plt.show() + + def draw_graphviz(self): + import matplotlib.pyplot as plt + nx.draw_graphviz(self.graph) + plt.show() + + def is_valid(self): + '''Check if graph structure is valid. + Returns true if graph is directed and acyclic, false otherwiese''' + + if self.graph.number_of_selfloops() > 0: + return False + + for node in self.graph.nodes(): + if self.has_loop(node): + return False + + return True + + def has_loop(self, node, origin=None): + '''Check if any path from node leads back to node. + + Keyword arguments: + node -- the start node + origin -- for internal recursive loop (default: None) + + Returns true on succes, false otherwise.''' + if not origin: + origin = node + + for successor in self.graph.successors(node): + if successor == origin: + return True + else: + return self.has_loop(successor, origin) + + def clear(self): + '''Remove all nodes and edges from the graph. + This also removes the name, and all graph, node and edge attributes.''' + self.graph.clear() + self.node_lookup.clear() + + def number_of_nodes(self): + '''Return the number of nodes in the graph.''' + return len(self) + + def __len__(self): + '''Return the number of nodes in the graph.''' + return len(self.graph) + + +class BayesianDecisionNetwork(BayesNet): + + def __init__(self): + super(BayesianDecisionNetwork, self).__init__() + self.partialOrdering = [] + self.random_nodes = [] + self.decision_nodes = [] + self.utility_nodes = [] + + def is_valid(self): + '''Check if graph structure is valid. + Returns true if graph is directed, acyclic and if there is a path that connects every decision node(consistency check), + false otherwise''' + + if self.graph.number_of_selfloops() > 0: + return False + + for node in self.graph.nodes(): + if self.has_loop(node): + return False + + decisionNodeList = [] + for node in self.get_all_nodes(): + if isinstance(node, DecisionNode): + decisionNodeList.append(node) + + return all([nx.has_path(self.graph, x, y) == True for x in decisionNodeList for y in decisionNodeList]) + + def add_node(self, node): + if isinstance(node, Node): + if node.name in self.node_lookup.keys(): + raise Exception("Node name already exists in Bayesnet: "+node.name) + if isinstance(node, DiscreteNode): + self.random_nodes.append(node) + elif isinstance(node, UtilityNode): + self.utility_nodes.append(node) + elif isinstance(node, DecisionNode): + self.decision_nodes.append(node) + else: + raise Exception("Tried to add a node which the Bayesian Decision Network can not work with") + self.node_lookup[node.name]=node + self.graph.add_node(node) + else: + raise Exception("Can only add 'Node' and its subclasses as nodes into the BayesNet") + + def get_all_nodes(self): + '''Returns all RandomNodes''' + return self.random_nodes + + def get_all_decision_nodes(self): + return self.decision_nodes + + def get_all_utility_nodes(self): + return self.utility_nodes + + def add_edge(self, node_from, node_to): + """ + Adds an edge between two nodes. It is impossible to create en edge between two decision nodes and between two + utility nodes. + + keyword arguments: + node_from -- Node from where the edge shall begin + node_to -- Node where the edge shall end + """ + if isinstance(node_from, DecisionNode) and isinstance(node_to, DecisionNode): + raise Exception("Tried to add an edge from a DecisionNode to a DecisionNode") + if isinstance(node_from, UtilityNode) and isinstance(node_to, UtilityNode): + raise Exception("Tried to add an edge from a UtilityNode to a UtilityNode") + if node_from in self.graph.nodes() and node_to in self.graph.nodes(): + self.graph.add_edge(node_from, node_to) + node_to.announce_parent(node_from) + else: + raise Exception("Tried to add an Edge between two Nodes of which at least one was not contained in the Bayesnet") + + def reset_Decisions(self): + """ + Resets all decisions in the Bayesian Decision Network + """ + for node in self.decision_nodes: + node.set_state(None) + + def get_partialOrdering(self): + """ + Getter for the partial ordere + """ + return self.partialOrdering + + def set_partialOrdering(self, partialOrder): + """ + Sets the partial ordering for this Bayesian Decision Network + + partialOrder -- ordered list of RandomNodes and Decision Nodes + example: [decisionNode1, [randomNode1,randomNode2], decisionNode2, [randomNode3]] + """ + self.partialOrdering = partialOrder + + +class DynamicBayesNet(BayesNet): + ''' This is the implementation of a dynamic Bayesian network (also called + temporal Bayesian network). + + Definition: DBN is a pair (B0, TwoTBN), where B0 is a BN over X(0), + representing the initial distribution over states, and TwoTBN is a + 2-TBN for the process. + See Koller, Friedman - "Probabilistic Graphical Models" (p. 204) + + Properties: Markov property, stationary, directed, discrete, + acyclic (within a slice) + ''' + + def __init__(self): + super(DynamicBayesNet, self).__init__() + self._B0 = BayesNet() + self._twoTBN = TwoTBN() + + @property + def B0(self): + ''' Get the Bayesian network representing the initial distribution.''' + return self._B0 + + @B0.setter + def B0(self, value): + ''' Set the Bayesian network representing the initial distribution.''' + if isinstance(value, BayesNet): + if not value.is_valid(): + raise Exception("BayesNet is not valid.") + self._B0 = value + else: + raise Exception("Can only set 'BayesNet' and its subclasses as " + + "B0 of a DBN.") + + @property + def twoTBN(self): + ''' Get the 2-time-slice Bayesian network.''' + return self._twoTBN + + @twoTBN.setter + def twoTBN(self, value): + ''' Set the 2-time-slice Bayesian network.''' + if isinstance(value, TwoTBN): + if not value.is_valid(): + raise Exception("BayesNet is not valid.") + self._twoTBN = value + else: + raise Exception("Can only set 'TwoTBN' and its subclasses as " + + "twoTBN of a DBN.") + + def is_valid(self): + '''Check if graph structure is valid. And if there is a same-named + inital node in towTBN for every node in BO. + Returns true if graph is directed and acyclic, false otherwiese''' + for node in self._B0.get_nodes(): + if not self._twoTBN.has_initial_node_by_name(node.name): + print("Node with name " + str(node.name) + + " not found in TwoTBN!") + return False; + + return super(DynamicBayesNet, self).is_valid() + + +class TwoTBN(BayesNet): + ''' This is the implementation of a 2-time-slice Bayesian network (2-TBN). + ''' + + def __init__(self, bayesnet=None): + BayesNet.__init__(self) + if bayesnet: + if not isinstance(bayesnet, BayesNet): + raise Exception("Parameter 'bayesnet' is not a instance of class BayesNet.") + self.graph = bayesnet.graph + self.node_lookup = bayesnet.node_lookup + self.__initial_nodes = [] + + def create_timeslice(self, state, initial=False): + ''' + Set all initial nodes to the value of their corresponding nodes + in state (previous time slice). + + Keyword arguments: + state -- Current state of the network (previous time slice). + initial -- Set initial to true if this will be the first time slice + and state only contains nodes of the initial distribution. + + Returns this instance with all initial nodes set to their + new value. + ''' + for (node, node_t) in self.__initial_nodes: + cpd = ProbabilityTable() + cpd.add_variable(node) + node.set_cpd(cpd) + if not initial: + node.set_probability(1., [(node, state[node_t])]) + else: + for node0 in state: + if node0.name == node.name: + node.set_probability(1., [(node, state[node0])]) + continue + return self + + + def add_node(self, node, initial=False, node_t=None): + ''' + Add a node to the TwoTBN. + + Keyword arguments: + node -- Node to be added. + initial -- If true node is marked as initial node. + node_t -- If initial is true this is the corresponding node in the time slice. + ''' + super(TwoTBN, self).add_node(node) + if initial: + self.set_initial_node(node, node_t) + + def set_initial_node(self, node_name, node_name_t): + ''' + Mark a node as initial node. + + Keyword arguments: + node_name -- Name of the initial node. + node_name_t -- Name of the corresponding node in the time slice. + ''' + node0 = self.get_node(node_name) + node1 = self.get_node(node_name_t) + self.__initial_nodes.append((node0, node1)) + + def has_initial_node_by_name(self, node_name): + ''' + Check if this instance has an inital node with name node_name. + + Returns true on success, false otherwise. + ''' + for (node, node_t) in self.__initial_nodes: + if node.name == node_name: + return True + return False -- GitLab