From 817c6621eeca21a1557aa138ae3ee7d245ae741b Mon Sep 17 00:00:00 2001 From: Brandon Rodriguez <brodriguez8774@gmail.com> Date: Fri, 29 Nov 2019 19:46:33 -0500 Subject: [PATCH] Create weighted graph and associated components --- __init__.py | 1 + resources/__init__.py | 1 + resources/graphs/__init__.py | 2 + resources/graphs/weighted_graph/__init__.py | 13 + resources/graphs/weighted_graph/components.py | 88 ++++ resources/graphs/weighted_graph/graph.py | 276 +++++++++++++ .../graphs/weighted_graph/__init__.py | 0 tests/resources/graphs/weighted_graph/edge.py | 57 +++ .../resources/graphs/weighted_graph/graph.py | 387 ++++++++++++++++++ tests/resources/graphs/weighted_graph/node.py | 20 + 10 files changed, 845 insertions(+) create mode 100644 resources/graphs/weighted_graph/__init__.py create mode 100644 resources/graphs/weighted_graph/components.py create mode 100644 resources/graphs/weighted_graph/graph.py create mode 100644 tests/resources/graphs/weighted_graph/__init__.py create mode 100644 tests/resources/graphs/weighted_graph/edge.py create mode 100644 tests/resources/graphs/weighted_graph/graph.py create mode 100644 tests/resources/graphs/weighted_graph/node.py diff --git a/__init__.py b/__init__.py index c15db28..d5917fe 100644 --- a/__init__.py +++ b/__init__.py @@ -11,4 +11,5 @@ __all__ = [ 'BasicEdge', 'BasicNode', 'BasicGraph', 'DirectedEdge', 'DirectedNode', 'DirectedGraph', 'StateMachineEdge', 'StateMachineNode', 'StateMachineGraph', + 'WeightedGraph', 'WeightedEdge', 'WeightedNode', ] diff --git a/resources/__init__.py b/resources/__init__.py index 28e6dd0..ac9353c 100644 --- a/resources/__init__.py +++ b/resources/__init__.py @@ -11,4 +11,5 @@ __all__ = [ 'BasicEdge', 'BasicNode', 'BasicGraph', 'DirectedEdge', 'DirectedNode', 'DirectedGraph', 'StateMachineEdge', 'StateMachineNode', 'StateMachineGraph', + 'WeightedEdge', 'WeightedNode', 'WeightedGraph', ] diff --git a/resources/graphs/__init__.py b/resources/graphs/__init__.py index 882d3e6..5baf47a 100644 --- a/resources/graphs/__init__.py +++ b/resources/graphs/__init__.py @@ -6,6 +6,7 @@ from .basic_graph import * from .directed_graph import * from .state_machine import * +from .weighted_graph import * # Define imports when using the * flag on this folder. @@ -13,4 +14,5 @@ __all__ = [ 'BasicEdge', 'BasicNode', 'BasicGraph', 'DirectedEdge', 'DirectedNode', 'DirectedGraph', 'StateMachineEdge', 'StateMachineNode', 'StateMachineGraph', + 'WeightedEdge', 'WeightedNode', 'WeightedGraph', ] diff --git a/resources/graphs/weighted_graph/__init__.py b/resources/graphs/weighted_graph/__init__.py new file mode 100644 index 0000000..9f6af66 --- /dev/null +++ b/resources/graphs/weighted_graph/__init__.py @@ -0,0 +1,13 @@ +""" +"Weighted Graph" folder importing definitions. +""" + +# Import files/values we want to be available to other folders. +from .components import WeightedEdge, WeightedNode +from .graph import WeightedGraph + + +# Define imports when using the * flag on this folder. +__all__ = [ + 'WeightedEdge', 'WeightedNode', 'WeightedGraph', +] diff --git a/resources/graphs/weighted_graph/components.py b/resources/graphs/weighted_graph/components.py new file mode 100644 index 0000000..379fe56 --- /dev/null +++ b/resources/graphs/weighted_graph/components.py @@ -0,0 +1,88 @@ +""" +Date: 11-29-19 +Class: CS5310 +Assignment: Graph Library +Author: Brandon Rodriguez + + +Edge and Node classes for a "weighted graph" (Has edge weights, but no edge directions). +Essentially the "vertex" and "vertex connection" classes in a Graph data structure. + +These classes are combined into one single file to avoid circular import dependencies. +""" + +# System Imports. + +# User Class Imports. +from ..basic_graph.components import BasicEdge, BasicNode + + +#region Logging Initialization + +# Since this is a library, we specifically only modify logging if the library is being run alone. +try: + # First, try to import the variable specific to our library logging. + from resources.logging import graph_library_logger + + # It worked, so we know the project is being run stand alone, probably as a unittest. + # Proceed to configure logging. + from resources.logging import get_logger as init_logging + logger = init_logging(__name__) +except (ImportError, ModuleNotFoundError): + # Above import failed. Project is being run as a library. + # Just import existing logger and do not modify. + import logging as init_logging + logger = init_logging.getLogger('graph_library') + +#endregion Logging Initialization + + +class WeightedEdge(BasicEdge): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # Define expected class types (should all be of "Directed" type). + # This is necessary for inheritance, or else child classes will only have access to parent functions. + self._edge_type = WeightedEdge + self._node_type = WeightedNode + + self._weight = 0 + + def get_weight(self): + """ + Gets edge weight. + :return: Edge's weight value. + """ + return self._weight + + def set_weight(self, weight): + """ + Sets edge weight to given value. + :param weight: Value to set for weight. + :return: Edge weight was set for. + """ + if weight is None: + raise TypeError('Edge weight cannot be None.') + + self._weight = weight + + return self + + def reset_weight(self): + """ + Resets edge weight to 0. + :return: Edge weight was reset for. + """ + self._weight = 0 + + return self + + +class WeightedNode(BasicNode): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # Define expected class types (should all be of "Directed" type). + # This is necessary for inheritance, or else child classes will only have access to parent functions. + self._edge_type = WeightedEdge + self._node_type = WeightedNode diff --git a/resources/graphs/weighted_graph/graph.py b/resources/graphs/weighted_graph/graph.py new file mode 100644 index 0000000..96c1881 --- /dev/null +++ b/resources/graphs/weighted_graph/graph.py @@ -0,0 +1,276 @@ +""" +Date: 11-29-19 +Class: CS5310 +Assignment: Graph Library +Author: Brandon Rodriguez + + +Graph class for a "weighted graph" (Has edge weights, but no edge directions). +Parent structure containing various Nodes and Edges. +""" + +# System Imports. + +# User Class Imports. +from .components import WeightedEdge, WeightedNode +from ..basic_graph.graph import BasicGraph, BasicGraphDisplay, BasicGraphEdges, BasicGraphNodes + + +#region Logging Initialization + +# Since this is a library, we specifically only modify logging if the library is being run alone. +try: + # First, try to import the variable specific to our library logging. + from resources.logging import graph_library_logger + + # It worked, so we know the project is being run stand alone, probably as a unittest. + # Proceed to configure logging. + from resources.logging import get_logger as init_logging + logger = init_logging(__name__) +except (ImportError, ModuleNotFoundError): + # Above import failed. Project is being run as a library. + # Just import existing logger and do not modify. + import logging as init_logging + logger = init_logging.getLogger('graph_library') + +#endregion Logging Initialization + + +class WeightedGraph(BasicGraph): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # Define expected class types (should all be of "Directed" type). + # This is necessary for inheritance, or else child classes will only have access to parent functions. + self._edge_type = WeightedEdge + self._graph_type = WeightedGraph + self._node_type = WeightedNode + + # Get associated child classes. + self.display = WeightedGraphDisplay(self) + self.edges = WeightedGraphEdges(self) + self.nodes = WeightedGraphNodes(self) + + +class WeightedGraphDisplay(BasicGraphDisplay): + def __init__(self, parent, *args, **kwargs): + super().__init__(parent, *args, *kwargs) + + if not isinstance(parent, WeightedGraph): + raise TypeError('Expected parent of type WeightedGraph.') + + # Get calling parent. + self._parent = parent + + +class WeightedGraphEdges(BasicGraphEdges): + def __init__(self, parent, *args, **kwargs): + super().__init__(parent, *args, *kwargs) + + if not isinstance(parent, WeightedGraph): + raise TypeError('Expected parent of type WeightedGraph.') + + # Get calling parent. + self._parent = parent + + def get_weight(self, node_1_identifier=None, node_2_identifier=None, edge_identifier=None): + """ + Gets weight of edge in graph. + :param node_1_identifier: First connected node or name of first connected node. + :param node_2_identifier: Second connected node or name of second connected node. + :param edge_identifier: Connecting edge or name of connecting edge. + :return: Weight of found edge | None if edge doesn't exist. + """ + # Check if edge identifier and one (or both) node identifier was passed. + if edge_identifier is not None and (node_1_identifier is not None or node_2_identifier is not None): + raise AttributeError('Must pass in either edge value or node values to find edge. Got both.') + # Check if edge identifier was passed. + elif edge_identifier is not None: + connected_edge = self._parent.edges.get(edge_identifier) + + # Check that edge was found. + if connected_edge is not None: + # Edge found. Get weight. + return connected_edge.get_weight() + else: + # Edge not found. + logger.warning('Could not find edge "{0}" in list of graph edges.'.format(edge_identifier)) + return None + + # Check if node identifiers were passed. + elif node_1_identifier is not None and node_2_identifier is not None: + connected_node_1 = self._parent.nodes.get(node_1_identifier) + connected_node_2 = self._parent.nodes.get(node_2_identifier) + + # Check that both nodes were found. + if connected_node_1 is not None and connected_node_2 is not None: + # Both nodes found. Get weight. + connected_edge = connected_node_1.get_edge_by_connected_node(connected_node_2) + + if connected_edge is not None: + # Nodes are connected. Get weight. + return connected_edge.get_weight() + else: + # Nodes are not connected. + logger.warning('Nodes "{0}" and "{1}" are not connected. Cannot get edge weight.') + return None + else: + # One or both nodes not found. + if connected_node_1 is None and connected_node_2 is None: + # Failed to find both nodes. + logger.warning('Could not find nodes "{0}" and "{1}" in list of graph nodes.'.format( + node_1_identifier, + node_2_identifier, + )) + elif connected_node_1 is None: + # Failed to find connected_node_1 node. + logger.warning('Could not find node "{0}" in list of graph nodes.'.format(node_1_identifier)) + elif connected_node_2 is None: + # Failed to find connected_node_2 node. + logger.warning('Could not find node "{0}" in list of graph nodes.'.format(node_2_identifier)) + + return None + + # No args were passed. Raise error. + else: + raise AttributeError('Must pass in either edge value or node values to find edge.') + + def set_weight(self, weight, node_1_identifier=None, node_2_identifier=None, edge_identifier=None): + """ + Sets weight of edge in graph. + :param weight: Weight to set for edge. + :param node_1_identifier: First connected node or name of first connected node. + :param node_2_identifier: Second connected node or name of second connected node. + :param edge_identifier: Connecting edge or name of connecting edge. + :return: Edge weight was set for | None if edge doesn't exist. + """ + # Check that weight was passed. + if weight is None: + raise TypeError('Weight cannot be None.') + + # Check if edge identifier and one (or both) node identifier was passed. + if edge_identifier is not None and (node_1_identifier is not None or node_2_identifier is not None): + raise AttributeError('Must pass in either edge value or node values to find edge. Got both.') + # Check if edge identifier was passed. + elif edge_identifier is not None: + connected_edge = self._parent.edges.get(edge_identifier) + + # Check that edge was found. + if connected_edge is not None: + # Edge found. Set weight. + return connected_edge.set_weight(weight) + else: + # Edge not found. + logger.warning('Could not find edge "{0}" in list of graph edges.'.format(edge_identifier)) + return None + + # Check if node identifiers were passed. + elif node_1_identifier is not None and node_2_identifier is not None: + connected_node_1 = self._parent.nodes.get(node_1_identifier) + connected_node_2 = self._parent.nodes.get(node_2_identifier) + + # Check that both nodes were found. + if connected_node_1 is not None and connected_node_2 is not None: + # Both nodes found. Get edge. + connected_edge = connected_node_1.get_edge_by_connected_node(connected_node_2) + + if connected_edge is not None: + # Nodes are connected. Set weight. + return connected_edge.set_weight(weight) + else: + # Nodes are not connected. + logger.warning('Nodes "{0}" and "{1}" are not connected. Cannot set edge weight.') + return None + else: + # One or both nodes not found. + if connected_node_1 is None and connected_node_2 is None: + # Failed to find both nodes. + logger.warning('Could not find nodes "{0}" and "{1}" in list of graph nodes.'.format( + node_1_identifier, + node_2_identifier, + )) + elif connected_node_1 is None: + # Failed to find connected_node_1 node. + logger.warning('Could not find node "{0}" in list of graph nodes.'.format(node_1_identifier)) + elif connected_node_2 is None: + # Failed to find connected_node_2 node. + logger.warning('Could not find node "{0}" in list of graph nodes.'.format(node_2_identifier)) + + return None + + # No args were passed. Raise error. + else: + raise AttributeError('Must pass in either edge value or node values to find edge.') + + def reset_weight(self, node_1_identifier=None, node_2_identifier=None, edge_identifier=None): + """ + Resets weight of edge in graph to 0. + :param node_1_identifier: First connected node or name of first connected node. + :param node_2_identifier: Second connected node or name of second connected node. + :param edge_identifier: Connecting edge or name of connecting edge. + :return: Edge weight was reset for | None if edge doesn't exist. + """ + # Check if edge identifier and one (or both) node identifier was passed. + if edge_identifier is not None and (node_1_identifier is not None or node_2_identifier is not None): + raise AttributeError('Must pass in either edge value or node values to find edge. Got both.') + # Check if edge identifier was passed. + elif edge_identifier is not None: + connected_edge = self._parent.edges.get(edge_identifier) + + # Check that edge was found. + if connected_edge is not None: + # Edge found. Set weight. + return connected_edge.reset_weight() + else: + # Edge not found. + logger.warning('Could not find edge "{0}" in list of graph edges.'.format(edge_identifier)) + return None + + # Check if node identifiers were passed. + elif node_1_identifier is not None and node_2_identifier is not None: + connected_node_1 = self._parent.nodes.get(node_1_identifier) + connected_node_2 = self._parent.nodes.get(node_2_identifier) + + # Check that both nodes were found. + if connected_node_1 is not None and connected_node_2 is not None: + # Both nodes found. Get edge. + connected_edge = connected_node_1.get_edge_by_connected_node(connected_node_2) + + if connected_edge is not None: + # Nodes are connected. Reset weight. + return connected_edge.reset_weight() + else: + # Nodes are not connected. + logger.warning('Nodes "{0}" and "{1}" are not connected. Cannot set edge weight.') + return None + else: + # One or both nodes not found. + if connected_node_1 is None and connected_node_2 is None: + # Failed to find both nodes. + logger.warning('Could not find nodes "{0}" and "{1}" in list of graph nodes.'.format( + node_1_identifier, + node_2_identifier, + )) + elif connected_node_1 is None: + # Failed to find connected_node_1 node. + logger.warning('Could not find node "{0}" in list of graph nodes.'.format(node_1_identifier)) + elif connected_node_2 is None: + # Failed to find connected_node_2 node. + logger.warning('Could not find node "{0}" in list of graph nodes.'.format(node_2_identifier)) + + return None + + # No args were passed. Raise error. + else: + raise AttributeError('Must pass in either edge value or node values to find edge.') + + +class WeightedGraphNodes(BasicGraphNodes): + def __init__(self, parent, *args, **kwargs): + super().__init__(parent, *args, *kwargs) + + if not isinstance(parent, WeightedGraph): + raise TypeError('Expected parent of type WeightedGraph.') + + # Get calling parent. + self._parent = parent diff --git a/tests/resources/graphs/weighted_graph/__init__.py b/tests/resources/graphs/weighted_graph/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/resources/graphs/weighted_graph/edge.py b/tests/resources/graphs/weighted_graph/edge.py new file mode 100644 index 0000000..f9571b8 --- /dev/null +++ b/tests/resources/graphs/weighted_graph/edge.py @@ -0,0 +1,57 @@ +""" +Date: 11-29-19 +Class: CS5310 +Assignment: Graph Library +Author: Brandon Rodriguez + + +Tests for "Weighted Edge" class. +""" + +# System Imports. +import unittest + +# User Class Imports. +from resources import WeightedEdge, WeightedNode + + +class TestWeightedEdge(unittest.TestCase): + def setUp(self): + self.test_edge = WeightedEdge('Test Edge') + + def test__edge_initialization(self): + self.assertEqual(self.test_edge._weight, 0) + + def test__get_weight(self): + # Test initial value. + self.assertEqual(self.test_edge._weight, 0) + self.assertEqual(self.test_edge.get_weight(), 0) + + # Test setting to new value. + self.test_edge._weight = 5 + self.assertEqual(self.test_edge._weight, 5) + self.assertEqual(self.test_edge.get_weight(), 5) + + def test__set_weight__success(self): + # Test initial value. + self.assertEqual(self.test_edge.get_weight(), 0) + + # Test setting to new value. + self.test_edge.set_weight(5) + self.assertEqual(self.test_edge.get_weight(), 5) + + def test__set_weight__failure(self): + with self.subTest('Weight is None.'): + with self.assertRaises(TypeError): + self.test_edge.set_weight(None) + + def test__reset_weight(self): + # Test non-default value. + self.test_edge.set_weight(5) + self.assertEqual(self.test_edge.get_weight(), 5) + self.test_edge.reset_weight() + self.assertEqual(self.test_edge.get_weight(), 0) + + # Test with default value. + self.test_edge.reset_weight() + self.assertEqual(self.test_edge.get_weight(), 0) diff --git a/tests/resources/graphs/weighted_graph/graph.py b/tests/resources/graphs/weighted_graph/graph.py new file mode 100644 index 0000000..7317cb8 --- /dev/null +++ b/tests/resources/graphs/weighted_graph/graph.py @@ -0,0 +1,387 @@ +""" +Date: 11-29-19 +Class: CS5310 +Assignment: Graph Library +Author: Brandon Rodriguez + + +Tests for "Weighted Graph" class. +""" + +# System Imports. +import unittest + +# User Class Imports. +from resources import WeightedEdge, WeightedNode +from resources import WeightedGraph + + +class TestWeightedGraphEdges(unittest.TestCase): + def setUp(self): + self.test_graph = WeightedGraph() + + def test__get_weight__success(self): + # Create nodes. + node_1 = self.test_graph.nodes.create() + node_2 = self.test_graph.nodes.create() + + with self.subTest('By node classes.'): + # Create node connections. + edge_1 = self.test_graph.nodes.connect(node_1, node_2) + + # Test default state. + self.assertEqual(edge_1.get_weight(), 0) + self.assertEqual(self.test_graph.edges.get_weight(node_1_identifier=node_1, node_2_identifier=node_2), 0) + + # Test non-default state. + edge_1._weight = 5 + self.assertEqual(edge_1.get_weight(), 5) + self.assertEqual(self.test_graph.edges.get_weight(node_1_identifier=node_1, node_2_identifier=node_2), 5) + + # Remove connection for further subtests. + self.test_graph.nodes.disconnect(edge_identifier=edge_1) + + with self.subTest('By node names.'): + # Create node connections. + edge_1 = self.test_graph.nodes.connect(node_1, node_2) + + # Test default state. + self.assertEqual(edge_1.get_weight(), 0) + self.assertEqual( + self.test_graph.edges.get_weight( + node_1_identifier=node_1.get_name(), + node_2_identifier=node_2.get_name() + ), + 0, + ) + + # Test non-default state. + edge_1._weight = 5 + self.assertEqual(edge_1.get_weight(), 5) + self.assertEqual( + self.test_graph.edges.get_weight( + node_1_identifier=node_1.get_name(), + node_2_identifier=node_2.get_name(), + ), + 5, + ) + + # Remove connection for further subtests. + self.test_graph.nodes.disconnect(edge_identifier=edge_1) + + with self.subTest('By edge classes.'): + # Create node connections. + edge_1 = self.test_graph.nodes.connect(node_1, node_2) + + # Test default state. + self.assertEqual(edge_1.get_weight(), 0) + self.assertEqual(self.test_graph.edges.get_weight(edge_identifier=edge_1), 0) + + # Test non-default state. + edge_1._weight = 5 + self.assertEqual(edge_1.get_weight(), 5) + self.assertEqual(self.test_graph.edges.get_weight(edge_identifier=edge_1), 5) + + # Remove connection for further subtests. + self.test_graph.nodes.disconnect(edge_identifier=edge_1) + + with self.subTest('By edge names.'): + # Create node connections. + edge_1 = self.test_graph.nodes.connect(node_1, node_2) + + # Test default state. + self.assertEqual(edge_1.get_weight(), 0) + self.assertEqual(self.test_graph.edges.get_weight(edge_identifier=edge_1.get_name()), 0) + + # Test non-default state. + edge_1._weight = 5 + self.assertEqual(edge_1.get_weight(), 5) + self.assertEqual(self.test_graph.edges.get_weight(edge_identifier=edge_1.get_name()), 5) + + # Remove connection for further subtests. + self.test_graph.nodes.disconnect(edge_identifier=edge_1) + + def test__get_weight__failure(self): + # Create nodes. + node_1 = self.test_graph.nodes.create() + node_2 = self.test_graph.nodes.create() + node_3 = self.test_graph.nodes.create() + edge_1 = self.test_graph.nodes.connect(node_1, node_2) + + with self.subTest('All values None'): + with self.assertRaises(AttributeError): + self.test_graph.edges.get_weight() + + with self.subTest('All identifiers passed.'): + with self.assertRaises(AttributeError): + self.test_graph.edges.get_weight( + node_1_identifier=node_1, + node_2_identifier=node_2, + edge_identifier=edge_1, + ) + + with self.subTest('Node identifiers with no connection.'): + with self.assertLogs(level='WARNING'): + self.assertIsNone(self.test_graph.edges.get_weight(node_1_identifier=node_1, node_2_identifier=node_3)) + + with self.subTest('Only node identifier 1 passed.'): + with self.assertLogs(level='WARNING'): + self.assertIsNone(self.test_graph.edges.get_weight(node_1_identifier=node_1, node_2_identifier='a')) + + with self.subTest('Only node identifier 2 passed.'): + with self.assertLogs(level='WARNING'): + self.assertIsNone(self.test_graph.edges.get_weight(node_1_identifier='a', node_2_identifier=node_2)) + + with self.subTest('Edge doesn\'t exist'): + with self.assertLogs(level='WARNING'): + self.assertIsNone(self.test_graph.edges.get_weight(edge_identifier='a')) + + + def test__set_weight__success(self): + # Create nodes. + node_1 = self.test_graph.nodes.create() + node_2 = self.test_graph.nodes.create() + + with self.subTest('By node classes.'): + # Create node connections. + edge_1 = self.test_graph.nodes.connect(node_1, node_2) + + # Test initial state. + self.assertEqual(edge_1.get_weight(), 0) + + # Test setting weight. + set_edge = self.test_graph.edges.set_weight(5, node_1_identifier=node_1, node_2_identifier=node_2) + self.assertEqual(edge_1, set_edge) + self.assertEqual(edge_1.get_weight(), 5) + + # Remove connection for further subtests. + self.test_graph.nodes.disconnect(edge_identifier=edge_1) + + with self.subTest('By node names.'): + # Create node connections. + edge_1 = self.test_graph.nodes.connect(node_1, node_2) + + # Test initial state. + self.assertEqual(edge_1.get_weight(), 0) + + # Test setting weight. + set_edge = self.test_graph.edges.set_weight( + 5, + node_1_identifier=node_1.get_name(), + node_2_identifier=node_2.get_name(), + ) + self.assertEqual(edge_1, set_edge) + self.assertEqual(edge_1.get_weight(), 5) + + # Remove connection for further subtests. + self.test_graph.nodes.disconnect(edge_identifier=edge_1) + + with self.subTest('By edge classes.'): + # Create node connections. + edge_1 = self.test_graph.nodes.connect(node_1, node_2) + + # Test initial state. + self.assertEqual(edge_1.get_weight(), 0) + + # Test setting weight. + set_edge = self.test_graph.edges.set_weight(5, edge_identifier=edge_1) + self.assertEqual(edge_1, set_edge) + self.assertEqual(edge_1.get_weight(), 5) + + # Remove connection for further subtests. + self.test_graph.nodes.disconnect(edge_identifier=edge_1) + + with self.subTest('By edge names.'): + # Create node connections. + edge_1 = self.test_graph.nodes.connect(node_1, node_2) + + # Test initial state. + self.assertEqual(edge_1.get_weight(), 0) + + # Test setting weight. + set_edge = self.test_graph.edges.set_weight(5, edge_identifier=edge_1.get_name()) + self.assertEqual(edge_1, set_edge) + self.assertEqual(edge_1.get_weight(), 5) + + # Remove connection for further subtests. + self.test_graph.nodes.disconnect(edge_identifier=edge_1) + + def test__set_weight__failure(self): + # Create nodes. + node_1 = self.test_graph.nodes.create() + node_2 = self.test_graph.nodes.create() + node_3 = self.test_graph.nodes.create() + edge_1 = self.test_graph.nodes.connect(node_1, node_2) + + with self.subTest('All values None'): + with self.assertRaises(AttributeError): + self.test_graph.edges.set_weight(5) + + with self.subTest('All identifiers passed.'): + with self.assertRaises(AttributeError): + self.test_graph.edges.set_weight( + 5, + node_1_identifier=node_1, + node_2_identifier=node_2, + edge_identifier=edge_1, + ) + + with self.subTest('Node identifiers with no connection.'): + with self.assertLogs(level='WARNING'): + self.assertIsNone(self.test_graph.edges.set_weight(5, node_1_identifier=node_1, node_2_identifier=node_3)) + + with self.subTest('Only node identifier 1 passed.'): + with self.assertLogs(level='WARNING'): + self.assertIsNone(self.test_graph.edges.set_weight(5, node_1_identifier=node_1, node_2_identifier='a')) + + with self.subTest('Only node identifier 2 passed.'): + with self.assertLogs(level='WARNING'): + self.assertIsNone(self.test_graph.edges.set_weight(5, node_1_identifier='a', node_2_identifier=node_2)) + + with self.subTest('Edge doesn\'t exist'): + with self.assertLogs(level='WARNING'): + self.assertIsNone(self.test_graph.edges.set_weight(5, edge_identifier='a')) + + def test__reset_weight__success(self): + # Create nodes. + node_1 = self.test_graph.nodes.create() + node_2 = self.test_graph.nodes.create() + + with self.subTest('By node classes.'): + # Create node connections. + edge_1 = self.test_graph.nodes.connect(node_1, node_2) + + # Test initial state. + self.test_graph.edges.set_weight(5, edge_identifier=edge_1) + self.assertEqual(edge_1.get_weight(), 5) + + # Test reset. + set_edge = self.test_graph.edges.reset_weight(node_1_identifier=node_1, node_2_identifier=node_2) + self.assertEqual(edge_1, set_edge) + self.assertEqual(edge_1.get_weight(), 0) + + # Remove connection for further subtests. + self.test_graph.nodes.disconnect(edge_identifier=edge_1) + + with self.subTest('By node names.'): + # Create node connections. + edge_1 = self.test_graph.nodes.connect(node_1, node_2) + + # Test initial state. + self.test_graph.edges.set_weight(5, edge_identifier=edge_1) + self.assertEqual(edge_1.get_weight(), 5) + + # Test reset. + set_edge = self.test_graph.edges.reset_weight( + node_1_identifier=node_1.get_name(), + node_2_identifier=node_2.get_name(), + ) + self.assertEqual(edge_1, set_edge) + self.assertEqual(edge_1.get_weight(), 0) + + # Remove connection for further subtests. + self.test_graph.nodes.disconnect(edge_identifier=edge_1) + + with self.subTest('By edge classes.'): + # Create node connections. + edge_1 = self.test_graph.nodes.connect(node_1, node_2) + + # Test initial state. + self.test_graph.edges.set_weight(5, edge_identifier=edge_1) + self.assertEqual(edge_1.get_weight(), 5) + + # Test reset. + set_edge = self.test_graph.edges.reset_weight(edge_identifier=edge_1) + self.assertEqual(edge_1, set_edge) + self.assertEqual(edge_1.get_weight(), 0) + + # Remove connection for further subtests. + self.test_graph.nodes.disconnect(edge_identifier=edge_1) + + with self.subTest('By edge names.'): + # Create node connections. + edge_1 = self.test_graph.nodes.connect(node_1, node_2) + + # Test initial state. + self.test_graph.edges.set_weight(5, edge_identifier=edge_1) + self.assertEqual(edge_1.get_weight(), 5) + + # Test reset. + set_edge = self.test_graph.edges.reset_weight(edge_identifier=edge_1.get_name()) + self.assertEqual(edge_1, set_edge) + self.assertEqual(edge_1.get_weight(), 0) + + # Remove connection for further subtests. + self.test_graph.nodes.disconnect(edge_identifier=edge_1) + + def test__reset_weight__failure(self): + # Create nodes. + node_1 = self.test_graph.nodes.create() + node_2 = self.test_graph.nodes.create() + node_3 = self.test_graph.nodes.create() + edge_1 = self.test_graph.nodes.connect(node_1, node_2) + + with self.subTest('All values None'): + with self.assertRaises(AttributeError): + self.test_graph.edges.reset_weight() + + with self.subTest('All identifiers passed.'): + with self.assertRaises(AttributeError): + self.test_graph.edges.reset_weight( + node_1_identifier=node_1, + node_2_identifier=node_2, + edge_identifier=edge_1, + ) + + with self.subTest('Node identifiers with no connection.'): + with self.assertLogs(level='WARNING'): + self.assertIsNone(self.test_graph.edges.reset_weight(node_1_identifier=node_1, node_2_identifier=node_3)) + + with self.subTest('Only node identifier 1 passed.'): + with self.assertLogs(level='WARNING'): + self.assertIsNone(self.test_graph.edges.reset_weight(node_1_identifier=node_1, node_2_identifier='a')) + + with self.subTest('Only node identifier 2 passed.'): + with self.assertLogs(level='WARNING'): + self.assertIsNone(self.test_graph.edges.reset_weight(node_1_identifier='a', node_2_identifier=node_2)) + + with self.subTest('Edge doesn\'t exist'): + with self.assertLogs(level='WARNING'): + self.assertIsNone(self.test_graph.edges.reset_weight(edge_identifier='a')) + + +class TestWeightedGraphNodes(unittest.TestCase): + def setUp(self): + self.test_graph = WeightedGraph() + + def test__create(self): + # We have pretty thorough testing of the parent function. + # Thus, just test returned node is of "Weighted" type. + node_1 = self.test_graph.nodes.create() + self.assertTrue(isinstance(node_1, WeightedNode)) + + def test__remove(self): + # We have pretty thorough testing of the parent function. + # Thus, just test returned node is of "Weighted" type. + node_1 = self.test_graph.nodes.create() + node_1 = self.test_graph.nodes.remove(node_1) + self.assertTrue(isinstance(node_1, WeightedNode)) + + def test__connect(self): + # We have pretty thorough testing of the parent function. + # Thus, just test returned edge is of "Weighted" type. + node_1 = self.test_graph.nodes.create() + node_2 = self.test_graph.nodes.create() + edge_1 = self.test_graph.nodes.connect(node_1, node_2) + self.assertTrue(isinstance(edge_1, WeightedEdge)) + + def test__disconnect(self): + # We have pretty thorough testing of the parent function. + # Thus, just test returned edge is of "Weighted" type. + node_1 = self.test_graph.nodes.create() + node_2 = self.test_graph.nodes.create() + self.test_graph.nodes.connect(node_1, node_2) + edge_1 = self.test_graph.nodes.disconnect(node_1_identifier=node_1, node_2_identifier=node_2) + self.assertTrue(isinstance(edge_1, WeightedEdge)) + + diff --git a/tests/resources/graphs/weighted_graph/node.py b/tests/resources/graphs/weighted_graph/node.py new file mode 100644 index 0000000..3b66139 --- /dev/null +++ b/tests/resources/graphs/weighted_graph/node.py @@ -0,0 +1,20 @@ +""" +Date: 11-29-19 +Class: CS5310 +Assignment: Graph Library +Author: Brandon Rodriguez + + +Tests for "Weighted Node" class. +""" + +# System Imports. +import unittest + +# User Class Imports. +from resources import WeightedEdge, WeightedNode + + +class TestWeightedNode(unittest.TestCase): + def setUp(self): + self.test_node = WeightedNode('Test Node') -- GitLab