Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
176 changes: 94 additions & 82 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
import pytest
import networkx as nx
import pandas as pd
from postman_problems.tests.utils import create_mock_csv_from_dataframe
import pytest

from postman_problems.tests.utils import create_mock_csv_from_dataframe

# ---------------------------------------------------------------------------------------
# Configuration for slow tests
# ---------------------------------------------------------------------------------------


def pytest_addoption(parser):
"""
Grabbed from:
https://docs.pytest.org/en/latest/example/simple.html#control-skipping-of-tests-according-to-command-line-option
"""
parser.addoption('--runslow', action='store_true', default=False, help='run slow tests')
parser.addoption("--runslow", action="store_true", default=False, help="run slow tests")


def pytest_collection_modifyitems(config, items):
if config.getoption('--runslow'):
if config.getoption("--runslow"):
# --runslow given in cli: do not skip slow tests
return
skip_slow = pytest.mark.skip(reason='need --runslow option to run')
skip_slow = pytest.mark.skip(reason="need --runslow option to run")
for item in items:
if 'slow' in item.keywords:
if "slow" in item.keywords:
item.add_marker(skip_slow)


# ---------------------------------------------------------------------------------------
# Graph objects shared between tests
# ---------------------------------------------------------------------------------------
Expand All @@ -34,60 +36,66 @@ def pytest_collection_modifyitems(config, items):
# Graph 1: Simple graph. Required edges only. CPP == RPP
# -------------------------------------------------------------------------------------

@pytest.fixture(scope='session', autouse=True)
def GRAPH_1():
return nx.MultiGraph([
('a', 'b', {'id': 1, 'distance': 5}),
('a', 'c', {'id': 2, 'distance': 20}),
('b', 'c', {'id': 3, 'distance': 10}),
('c', 'd', {'id': 4, 'distance': 3}),
('d', 'b', {'id': 5, 'distance': 2})
])


@pytest.fixture(scope='session', autouse=True)
@pytest.fixture(scope="session", autouse=True)
def GRAPH_1():
return nx.MultiGraph(
[
("a", "b", {"id": 1, "distance": 5}),
("a", "c", {"id": 2, "distance": 20}),
("b", "c", {"id": 3, "distance": 10}),
("c", "d", {"id": 4, "distance": 3}),
("d", "b", {"id": 5, "distance": 2}),
]
)


@pytest.fixture(scope="session", autouse=True)
def GRAPH_1_EDGELIST_DF_W_ID():
return pd.DataFrame({
'node1': ['a', 'a', 'b', 'c', 'd'],
'node2': ['b', 'c', 'c', 'd', 'b'],
'distance': [5, 20, 10, 3, 2],
'id': [1, 2, 3, 4, 5]
}, columns=['node1', 'node2', 'distance', 'id'])


@pytest.fixture(scope='function', autouse=True, )
return pd.DataFrame(
{
"node1": ["a", "a", "b", "c", "d"],
"node2": ["b", "c", "c", "d", "b"],
"distance": [5, 20, 10, 3, 2],
"id": [1, 2, 3, 4, 5],
},
columns=["node1", "node2", "distance", "id"],
)


@pytest.fixture(
scope="function",
autouse=True,
)
def GRAPH_1_EDGELIST_DF(GRAPH_1_EDGELIST_DF_W_ID):
return GRAPH_1_EDGELIST_DF_W_ID.drop('id', axis=1)
return GRAPH_1_EDGELIST_DF_W_ID.drop("id", axis=1)


@pytest.fixture(scope='function', autouse=True)
@pytest.fixture(scope="function", autouse=True)
def GRAPH_1_EDGELIST_CSV(GRAPH_1_EDGELIST_DF):
return create_mock_csv_from_dataframe(GRAPH_1_EDGELIST_DF)


@pytest.fixture(scope='function', autouse=True)
@pytest.fixture(scope="function", autouse=True)
def GRAPH_1_EDGELIST_W_ID_CSV(GRAPH_1_EDGELIST_DF_W_ID):
return create_mock_csv_from_dataframe(GRAPH_1_EDGELIST_DF_W_ID)


@pytest.fixture(scope='session', autouse=True)
@pytest.fixture(scope="session", autouse=True)
def GRAPH_1_NODE_ATTRIBUTES():
return pd.DataFrame({
'id': ['a', 'b', 'c', 'd'],
'attr_fruit': ['apple', 'banana', 'cherry', 'durian']
})
return pd.DataFrame({"id": ["a", "b", "c", "d"], "attr_fruit": ["apple", "banana", "cherry", "durian"]})


@pytest.fixture(scope='session', autouse=True)
@pytest.fixture(scope="session", autouse=True)
def GRAPH_1_CIRCUIT_CPP():
return [
('a', 'b', 0, {'distance': 5, 'id': 0}),
('b', 'd', 0, {'distance': 2, 'augmented': True, 'id': 4}),
('d', 'c', 0, {'distance': 3, 'augmented': True, 'id': 3}),
('c', 'b', 0, {'distance': 10, 'id': 2}),
('b', 'd', 0, {'distance': 2, 'id': 4}),
('d', 'c', 0, {'distance': 3, 'id': 3}),
('c', 'a', 0, {'distance': 20, 'id': 1})
("a", "b", 0, {"distance": 5, "id": 0}),
("b", "d", 0, {"distance": 2, "augmented": True, "id": 4}),
("d", "c", 0, {"distance": 3, "augmented": True, "id": 3}),
("c", "b", 0, {"distance": 10, "id": 2}),
("b", "d", 0, {"distance": 2, "id": 4}),
("d", "c", 0, {"distance": 3, "id": 3}),
("c", "a", 0, {"distance": 20, "id": 1}),
]


Expand All @@ -96,35 +104,37 @@ def GRAPH_1_CIRCUIT_CPP():
# -------------------------------------------------------------------------------------


@pytest.fixture(scope='session', autouse=True)
@pytest.fixture(scope="session", autouse=True)
def GRAPH_2():
return nx.MultiGraph([
('a', 'b', {'distance': 20, 'required': 1}),
('a', 'c', {'distance': 25, 'required': 1}),
('a', 'd', {'distance': 30, 'required': 1}),
('a', 'e', {'distance': 35, 'required': 1}),
('b', 'c', {'distance': 2, 'required': 0}),
('c', 'd', {'distance': 3, 'required': 0}),
('d', 'e', {'distance': 4, 'required': 0}),
('e', 'b', {'distance': 6, 'required': 0})
])


@pytest.fixture(scope='session', autouse=True)
return nx.MultiGraph(
[
("a", "b", {"distance": 20, "required": 1}),
("a", "c", {"distance": 25, "required": 1}),
("a", "d", {"distance": 30, "required": 1}),
("a", "e", {"distance": 35, "required": 1}),
("b", "c", {"distance": 2, "required": 0}),
("c", "d", {"distance": 3, "required": 0}),
("d", "e", {"distance": 4, "required": 0}),
("e", "b", {"distance": 6, "required": 0}),
]
)


@pytest.fixture(scope="session", autouse=True)
def GRAPH_2_EDGELIST_CSV(GRAPH_2):
edgelist = nx.to_pandas_edgelist(GRAPH_2, source='_node1', target='_node2')
edgelist = nx.to_pandas_edgelist(GRAPH_2, source="_node1", target="_node2")
return create_mock_csv_from_dataframe(edgelist)


@pytest.fixture(scope='session', autouse=True)
@pytest.fixture(scope="session", autouse=True)
def GRAPH_2_CIRCUIT_RPP():
return [
('a', 'c', 0, {'distance': 25, 'id': 4, 'required': 1}),
('c', 'b', 0, {'distance': 2, 'id': 6, 'augmented': True, 'required': 0}),
('b', 'a', 0, {'distance': 20, 'id': 3, 'required': 1}),
('a', 'e', 0, {'distance': 35, 'id': 5, 'required': 1}),
('e', 'd', 0, {'distance': 4, 'id': 2, 'augmented': True, 'required': 0}),
('d', 'a', 0, {'distance': 30, 'id': 0, 'required': 1})
("a", "c", 0, {"distance": 25, "id": 4, "required": 1}),
("c", "b", 0, {"distance": 2, "id": 6, "augmented": True, "required": 0}),
("b", "a", 0, {"distance": 20, "id": 3, "required": 1}),
("a", "e", 0, {"distance": 35, "id": 5, "required": 1}),
("e", "d", 0, {"distance": 4, "id": 2, "augmented": True, "required": 0}),
("d", "a", 0, {"distance": 30, "id": 0, "required": 1}),
]


Expand All @@ -133,23 +143,25 @@ def GRAPH_2_CIRCUIT_RPP():
# -------------------------------------------------------------------------------------


@pytest.fixture(scope='session', autouse=True)
@pytest.fixture(scope="session", autouse=True)
def GRAPH_3():
return nx.MultiGraph([
('a', 'b', {'distance': 20, 'required': 1}),
('a', 'c', {'distance': 25, 'required': 1}),
('a', 'd', {'distance': 30, 'required': 1}),
('a', 'e', {'distance': 35, 'required': 1}),
('b', 'c', {'distance': 2, 'required': 0}),
('c', 'd', {'distance': 3, 'required': 0}),
('d', 'e', {'distance': 4, 'required': 0}),
('e', 'b', {'distance': 6, 'required': 0}),
('b', 'f', {'distance': 7, 'required': 0}),
('f', 'g', {'distance': 8, 'required': 1})
])


@pytest.fixture(scope='session', autouse=True)
return nx.MultiGraph(
[
("a", "b", {"distance": 20, "required": 1}),
("a", "c", {"distance": 25, "required": 1}),
("a", "d", {"distance": 30, "required": 1}),
("a", "e", {"distance": 35, "required": 1}),
("b", "c", {"distance": 2, "required": 0}),
("c", "d", {"distance": 3, "required": 0}),
("d", "e", {"distance": 4, "required": 0}),
("e", "b", {"distance": 6, "required": 0}),
("b", "f", {"distance": 7, "required": 0}),
("f", "g", {"distance": 8, "required": 1}),
]
)


@pytest.fixture(scope="session", autouse=True)
def GRAPH_3_EDGELIST_CSV(GRAPH_3):
edgelist = nx.to_pandas_edgelist(GRAPH_3, source='_node1', target='_node2')
return create_mock_csv_from_dataframe(edgelist)
edgelist = nx.to_pandas_edgelist(GRAPH_3, source="_node1", target="_node2")
return create_mock_csv_from_dataframe(edgelist)
1 change: 0 additions & 1 deletion postman_problems/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import logging


logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
96 changes: 52 additions & 44 deletions postman_problems/examples/seven_bridges/cpp_seven_bridges.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
"""

import logging

import pkg_resources

from postman_problems.solver import cpp
from postman_problems.stats import calculate_postman_solution_stats

Expand All @@ -36,68 +38,74 @@ def main():
# PARAMS / DATA ---------------------------------------------------------------------

# inputs
EDGELIST = pkg_resources.resource_filename('postman_problems', 'examples/seven_bridges/edgelist_seven_bridges.csv')
START_NODE = 'D'
EDGELIST = pkg_resources.resource_filename("postman_problems", "examples/seven_bridges/edgelist_seven_bridges.csv")
START_NODE = "D"

# outputs
PNG_PATH = pkg_resources.resource_filename('postman_problems', 'examples/seven_bridges/output/png/')
CPP_VIZ_FILENAME = pkg_resources.resource_filename('postman_problems', 'examples/seven_bridges/output/cpp_graph')
CPP_BASE_VIZ_FILENAME = pkg_resources.resource_filename('postman_problems',
'examples/seven_bridges/output/base_cpp_graph')
CPP_GIF_FILENAME = pkg_resources.resource_filename('postman_problems',
'examples/seven_bridges/output/cpp_graph.gif')
PNG_PATH = pkg_resources.resource_filename("postman_problems", "examples/seven_bridges/output/png/")
CPP_VIZ_FILENAME = pkg_resources.resource_filename("postman_problems", "examples/seven_bridges/output/cpp_graph")
CPP_BASE_VIZ_FILENAME = pkg_resources.resource_filename(
"postman_problems", "examples/seven_bridges/output/base_cpp_graph"
)
CPP_GIF_FILENAME = pkg_resources.resource_filename(
"postman_problems", "examples/seven_bridges/output/cpp_graph.gif"
)

# setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# SOLVE CPP -------------------------------------------------------------------------

logger.info('Solve CPP')
logger.info("Solve CPP")
circuit, graph = cpp(edgelist_filename=EDGELIST, start_node=START_NODE)

logger.info('Print the CPP solution:')
logger.info("Print the CPP solution:")
for e in circuit:
logger.info(e)

logger.info('Solution summary stats:')
logger.info("Solution summary stats:")
for k, v in calculate_postman_solution_stats(circuit).items():
logger.info(str(k) + ' : ' + str(v))
logger.info(str(k) + " : " + str(v))

# VIZ -------------------------------------------------------------------------------

try:
from postman_problems.viz import plot_circuit_graphviz, make_circuit_images, make_circuit_video

logger.info('Creating single SVG of base graph')
plot_circuit_graphviz(circuit=circuit,
graph=graph,
filename=CPP_BASE_VIZ_FILENAME,
edge_label_attr='distance',
format='svg',
engine='circo',
graph_attr={'label': 'Base Graph: Distances', 'labelloc': 't'})

logger.info('Creating single SVG of CPP solution')
plot_circuit_graphviz(circuit=circuit,
graph=graph,
filename=CPP_VIZ_FILENAME,
format='svg',
engine='circo',
graph_attr={'label': 'Base Graph: Chinese Postman Solution', 'labelloc': 't'})

logger.info('Creating PNG files for GIF')
make_circuit_images(circuit=circuit,
graph=graph,
outfile_dir=PNG_PATH,
format='png',
engine='circo',
graph_attr={'label': 'Base Graph: Chinese Postman Solution', 'labelloc': 't'})

logger.info('Creating GIF')
video_message = make_circuit_video(infile_dir_images=PNG_PATH,
outfile_movie=CPP_GIF_FILENAME,
fps=0.5)
from postman_problems.viz import make_circuit_images, make_circuit_video, plot_circuit_graphviz

logger.info("Creating single SVG of base graph")
plot_circuit_graphviz(
circuit=circuit,
graph=graph,
filename=CPP_BASE_VIZ_FILENAME,
edge_label_attr="distance",
format="svg",
engine="circo",
graph_attr={"label": "Base Graph: Distances", "labelloc": "t"},
)

logger.info("Creating single SVG of CPP solution")
plot_circuit_graphviz(
circuit=circuit,
graph=graph,
filename=CPP_VIZ_FILENAME,
format="svg",
engine="circo",
graph_attr={"label": "Base Graph: Chinese Postman Solution", "labelloc": "t"},
)

logger.info("Creating PNG files for GIF")
make_circuit_images(
circuit=circuit,
graph=graph,
outfile_dir=PNG_PATH,
format="png",
engine="circo",
graph_attr={"label": "Base Graph: Chinese Postman Solution", "labelloc": "t"},
)

logger.info("Creating GIF")
video_message = make_circuit_video(infile_dir_images=PNG_PATH, outfile_movie=CPP_GIF_FILENAME, fps=0.5)

logger.info(video_message)
logger.info("and that's a wrap, checkout the output!")
Expand All @@ -107,5 +115,5 @@ def main():
print("Sorry, looks like you don't have all the needed visualization dependencies.")


if __name__ == '__main__':
if __name__ == "__main__":
main()
Loading