diff --git a/code/python/src/local/maths.py b/code/python/src/local/maths.py index 7bfe9ad..d29c145 100644 --- a/code/python/src/local/maths.py +++ b/code/python/src/local/maths.py @@ -6,6 +6,7 @@ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ import math; +import numpy as np; import random; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -14,5 +15,6 @@ import random; __all__ = [ 'math', + 'np', 'random', ]; diff --git a/code/python/src/local/typing.py b/code/python/src/local/typing.py index 618353a..6a074a6 100644 --- a/code/python/src/local/typing.py +++ b/code/python/src/local/typing.py @@ -17,6 +17,7 @@ from typing import Tuple; from typing import Type; from typing import TypeVar; from typing import Union; +from nptyping import NDArray; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # EXPORTS @@ -34,4 +35,5 @@ __all__ = [ 'Type', 'TypeVar', 'Union', + 'NDArray', ]; diff --git a/code/python/src/main.py b/code/python/src/main.py index 6f01478..b541ccd 100644 --- a/code/python/src/main.py +++ b/code/python/src/main.py @@ -12,8 +12,10 @@ os.chdir(os.path.join(os.path.dirname(__file__), '..')); sys.path.insert(0, os.getcwd()); from src.core.log import *; +from src.local.maths import *; from src.graphs.graph import *; from src.graphs.tarjan import *; +from src.travel.naive import *; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # GLOBAL CONSTANTS/VARIABLES @@ -26,28 +28,17 @@ from src.graphs.tarjan import *; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def enter(): - ## Beispiel aus Seminarblatt 1 - nodes = [1,2,3,4,5,6,7,8]; - edges = [ - (1,2), - (1,3), - (2,4), - (2,5), - (3,5), - (3,6), - (3,8), - (4,5), - (4,7), - (5,1), - (5,8), - (6,8), - (7,8), - (8,6), - ]; - G = Graph(nodes=nodes, edges=edges); - components = tarjan_algorithm(G, debug=True); - for component in components: - log_debug(component); + ## Beispiel aus Seminarblatt 8 + tsp_naive_algorithm( + dist = np.asarray([ + [0, 7, 2, 5], + [7, 0, 5, 6], + [2, 5, 0, 5], + [2, 7, 4, 0], + ], dtype=float), + optimise=max, + verbose=True, + ); return; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/code/python/tests/core/__init__.py b/code/python/src/travel/__init__.py similarity index 100% rename from code/python/tests/core/__init__.py rename to code/python/src/travel/__init__.py diff --git a/code/python/src/travel/naive.py b/code/python/src/travel/naive.py new file mode 100644 index 0000000..2afc100 --- /dev/null +++ b/code/python/src/travel/naive.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from __future__ import annotations; +from src.local.typing import *; +from src.local.maths import *; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'tsp_naive_algorithm', +]; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# METHOD tsp_naive_algorithm +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +def tsp_naive_algorithm( + dist: NDArray[(Any, Any), float], + optimise = min, + verbose: bool = False, +) -> tuple[float, list[list[int]]]: + m, n = dist.shape[:2]; + assert m == n; + memory: Dict[tuple[int, tuple], tuple[float, list[list[int]]]] = dict(); + + def g(i: int, S: list[int]) -> tuple[float, list[list[int]]]: + # wenn g bereits für den Input definiert ist, gib diesen Wert zurück: + if (i, tuple(S)) not in memory.keys(): + if len(S) == 0: + paths = [[i]] if i == 0 else [[i, 0]]; + memory[(i, tuple(S))] = (dist[i,0], paths); + else: + values_and_paths = [ (j, *g(j, (*S[:index], *S[(index+1):]))) for index, j in enumerate(S) ]; + # berechne d(i,j) + g(j, S \ {i}) for each j in S: + values_and_paths = [ (j, dist[i,j] + value, paths) for j, value, paths in values_and_paths]; + value = optimise([value for _, value, _ in values_and_paths]); + paths = []; + for j, value_, paths_ in values_and_paths: + if value_ == value: + paths += [ [i, *path] for path in paths_ ]; + memory[(i, tuple(S))] = (value, paths); + return memory[(i, tuple(S))]; + + # berechne g(0, {1,2,...,n-1}): + optimal_wert = g(0, [i for i in range(1,n)]); + + if verbose: + display_computation(n, memory); + + return optimal_wert, []; + +def display_computation(n: int, memory: Dict[tuple[int, tuple], tuple[float, list[list[int]]]]): + keys = sorted(memory.keys(), key=lambda key: (len(key[1]), key[0], key[1])); + for k in range(0,n): + print(f'\x1b[4;1m|S| = {k}:\x1b[0m'); + for (i, S) in keys: + if len(S) != k: + continue; + value, paths = memory[(i, S)]; + print(f'g({i}, {list(S)}) = {value}'); + if len(paths) == 1: + print(f'optimal path: {" -> ".join(map(str, paths[0]))}'); + else: + print('optimal paths:'); + for path in paths: + print(f'* {" -> ".join(map(str, path))}'); + print(''); + return; diff --git a/code/python/tests/conftest.py b/code/python/tests/conftest.py index 953327c..3e40355 100644 --- a/code/python/tests/conftest.py +++ b/code/python/tests/conftest.py @@ -6,17 +6,8 @@ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ from unittest import TestCase; -from pytest import mark; from pytest import fixture; -from tests.core.log import *; - -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# CONSTANTS -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -# - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # FIXTURES # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -24,3 +15,11 @@ from tests.core.log import *; @fixture(scope='module') def test(): return TestCase(); + +@fixture(scope='module') +def debug(): + def log(*lines: str): + with open('logs/debug.log', 'a') as fp: + for line in lines: + print(line, end='\n', file=fp); + return log; diff --git a/code/python/tests/core/log.py b/code/python/tests/core/log.py deleted file mode 100644 index 9d555cc..0000000 --- a/code/python/tests/core/log.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# IMPORTS -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -# - -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# METHODS - logging -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -def log(*lines: str): - with open('logs/debug.log', 'a') as fp: - for line in lines: - print(line, end='\n', file=fp); diff --git a/code/python/tests/test_graphs/conftest.py b/code/python/tests/test_graphs/conftest.py index 83cbdfe..bf949b7 100644 --- a/code/python/tests/test_graphs/conftest.py +++ b/code/python/tests/test_graphs/conftest.py @@ -9,8 +9,6 @@ from unittest import TestCase; from pytest import mark; from pytest import fixture; -from tests.core.log import *; - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # CONSTANTS # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/code/python/tests/test_graphs/test_tarjan.py b/code/python/tests/test_graphs/test_tarjan.py index d20e6b4..e9e287c 100644 --- a/code/python/tests/test_graphs/test_tarjan.py +++ b/code/python/tests/test_graphs/test_tarjan.py @@ -5,12 +5,12 @@ # IMPORTS # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -from contextlib import nullcontext as does_not_raise -from collections import Counter; +import pytest; from pytest import mark; from pytest import fixture; from pytest import lazy_fixture; from unittest import TestCase; +from unittest.mock import patch; from src.local.typing import *; from src.graphs.graph import *; @@ -73,6 +73,13 @@ def test_tarjan(test, G, expected): components = tarjan_algorithm(G, False); assert_components_eq(test, components, expected); +@patch(f'{__name__}.tarjan_algorithm', lambda *_: []) +@mark.parametrize(('G', 'expected'), [ (lazy_fixture('graph1'), [[1], [3], [2,4]])]) +@mark.usefixtures('test') +def test_failable_tarjan(test, G, expected): + with pytest.raises(AssertionError): + test_tarjan(test, G, expected); + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # AUXILIARY METHODS # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~