From f32eb593b14957058169203a2dc056fecbf3ee1c Mon Sep 17 00:00:00 2001
From: Bjoern Ludwig <bjoern.ludwig@ptb.de>
Date: Tue, 1 Feb 2022 20:06:56 +0100
Subject: [PATCH 01/22] refactor(CharacterCounts): change name to CharProbs

---
 src/ilp_keyboard_layout_optimization/receive_data.py | 8 ++++----
 test/test_receive_data.py                            | 6 +++---
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/src/ilp_keyboard_layout_optimization/receive_data.py b/src/ilp_keyboard_layout_optimization/receive_data.py
index 3c19218..133164c 100644
--- a/src/ilp_keyboard_layout_optimization/receive_data.py
+++ b/src/ilp_keyboard_layout_optimization/receive_data.py
@@ -1,6 +1,6 @@
 """This module contains a class representing (special) character counts"""
 
-__all__ = ["CharacterCounts"]
+__all__ = ["CharProbs"]
 
 import csv
 from math import comb
@@ -11,8 +11,8 @@ from urllib.request import urlopen
 from .types import CharSet, CharTuple
 
 
-class CharacterCounts:
-    """Instances represent all relevant (special) character counts
+class CharProbs:
+    """Instances represent all relevant (special) character probabilities
 
     Parameters
     ----------
@@ -142,7 +142,7 @@ class CharacterCounts:
 
 
 if __name__ == "__main__":
-    CharacterCounts(
+    CharProbs(
         ("a", "b", "c"),
         "http://www.ids-mannheim.de/fileadmin/kl/derewo/"
         "DeReChar-v-uni-204-a-c-2018-02-28-1.0.csv",
diff --git a/test/test_receive_data.py b/test/test_receive_data.py
index 42393c7..2e7e193 100644
--- a/test/test_receive_data.py
+++ b/test/test_receive_data.py
@@ -3,17 +3,17 @@ from urllib.request import urlopen
 
 import pytest
 
-from ilp_keyboard_layout_optimization.receive_data import CharacterCounts
+from src.ilp_keyboard_layout_optimization.receive_data import CharProbs
 
 
 @pytest.fixture
 def characters_count():
-    return CharacterCounts()
+    return CharProbs()
 
 
 @pytest.fixture()
 def characters_count_custom():
-    return CharacterCounts(
+    return CharProbs(
         ("A", "B", "C"),
         "http://www.ids-mannheim.de/fileadmin/kl/derewo/"
         "DeReChar-v-uni-204-a-c-2018-02-28-1.0.csv",
-- 
GitLab


From 88d2809727820326feedb5a540b3f86927204cf7 Mon Sep 17 00:00:00 2001
From: Bjoern Ludwig <bjoern.ludwig@ptb.de>
Date: Fri, 11 Feb 2022 18:09:28 +0100
Subject: [PATCH 02/22] build(deps): introduce Hypothesis into dev deps

---
 .gitignore          | 1 +
 dev-requirements.in | 5 +++--
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/.gitignore b/.gitignore
index 07996c0..8b88dbb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+**/.hypothesis/
 /.mypy_cache/
 /build/
 /dist/
diff --git a/dev-requirements.in b/dev-requirements.in
index 4982ff8..dcf4dd6 100644
--- a/dev-requirements.in
+++ b/dev-requirements.in
@@ -1,8 +1,9 @@
 -c requirements.txt
 
+black
 build
-twine
+hypothesis
 mypy
-black
 pylint
 pytest
+twine
-- 
GitLab


From 270728e3c366c31b6375fa76b6107308db81b100 Mon Sep 17 00:00:00 2001
From: Bjoern Ludwig <bjoern.ludwig@ptb.de>
Date: Fri, 11 Feb 2022 18:10:45 +0100
Subject: [PATCH 03/22] build(deps): recompile deps

---
 dev-requirements.txt | 37 +++++++++++++++++++++----------------
 1 file changed, 21 insertions(+), 16 deletions(-)

diff --git a/dev-requirements.txt b/dev-requirements.txt
index 4f402bb..6f203ab 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -7,8 +7,10 @@
 astroid==2.9.3
     # via pylint
 attrs==21.4.0
-    # via pytest
-black==21.12b0
+    # via
+    #   hypothesis
+    #   pytest
+black==22.1.0
     # via -r dev-requirements.in
 bleach==4.1.0
     # via readme-renderer
@@ -18,7 +20,7 @@ certifi==2021.10.8
     # via requests
 cffi==1.15.0
     # via cryptography
-charset-normalizer==2.0.10
+charset-normalizer==2.0.11
     # via requests
 click==8.0.3
     # via black
@@ -28,9 +30,11 @@ cryptography==36.0.1
     # via secretstorage
 docutils==0.18.1
     # via readme-renderer
+hypothesis==6.36.1
+    # via -r dev-requirements.in
 idna==3.3
     # via requests
-importlib-metadata==4.10.1
+importlib-metadata==4.11.0
     # via
     #   keyring
     #   twine
@@ -65,7 +69,7 @@ pep517==0.12.0
     # via build
 pkginfo==1.8.2
     # via twine
-platformdirs==2.4.1
+platformdirs==2.5.0
     # via
     #   black
     #   pylint
@@ -79,9 +83,9 @@ pygments==2.11.2
     # via readme-renderer
 pylint==2.12.2
     # via -r dev-requirements.in
-pyparsing==3.0.6
+pyparsing==3.0.7
     # via packaging
-pytest==6.2.5
+pytest==7.0.0
     # via -r dev-requirements.in
 readme-renderer==32.0
     # via twine
@@ -97,26 +101,27 @@ secretstorage==3.3.1
     # via keyring
 six==1.16.0
     # via bleach
+sortedcontainers==2.4.0
+    # via hypothesis
 toml==0.10.2
-    # via
-    #   pylint
-    #   pytest
-tomli==1.2.3
+    # via pylint
+tomli==2.0.1
     # via
     #   black
     #   build
     #   mypy
     #   pep517
+    #   pytest
 tqdm==4.62.3
     # via twine
-twine==3.7.1
+twine==3.8.0
     # via -r dev-requirements.in
 typing-extensions==4.0.1
-    # via
-    #   black
-    #   mypy
+    # via mypy
 urllib3==1.26.8
-    # via requests
+    # via
+    #   requests
+    #   twine
 webencodings==0.5.1
     # via bleach
 wrapt==1.13.3
-- 
GitLab


From ddeb9d21ecb36558132728966fae45bbacb645bb Mon Sep 17 00:00:00 2001
From: Bjoern Ludwig <bjoern.ludwig@ptb.de>
Date: Fri, 11 Feb 2022 18:13:49 +0100
Subject: [PATCH 04/22] refactor(_types): rename module to type_aliases.py

---
 src/ilp_keyboard_layout_optimization/ilp.py                   | 4 +---
 src/ilp_keyboard_layout_optimization/optimize.py              | 2 +-
 src/ilp_keyboard_layout_optimization/receive_data.py          | 2 +-
 .../{types.py => type_aliases.py}                             | 0
 4 files changed, 3 insertions(+), 5 deletions(-)
 rename src/ilp_keyboard_layout_optimization/{types.py => type_aliases.py} (100%)

diff --git a/src/ilp_keyboard_layout_optimization/ilp.py b/src/ilp_keyboard_layout_optimization/ilp.py
index 033724a..00611c0 100644
--- a/src/ilp_keyboard_layout_optimization/ilp.py
+++ b/src/ilp_keyboard_layout_optimization/ilp.py
@@ -2,9 +2,7 @@
 from itertools import chain, permutations, product
 from typing import Iterable
 
-from pyscipopt import Model, quicksum
-
-from .types import (
+from .type_aliases import (
     CharKeyPair,
     CharKeyQuadruple,
     CharTuple,
diff --git a/src/ilp_keyboard_layout_optimization/optimize.py b/src/ilp_keyboard_layout_optimization/optimize.py
index 2e7f7e4..4d3e949 100644
--- a/src/ilp_keyboard_layout_optimization/optimize.py
+++ b/src/ilp_keyboard_layout_optimization/optimize.py
@@ -13,7 +13,7 @@ We might add command line parameters at a later time. For now please edit the ma
 function at the very bottom of this file to change inputs.
 """
 from ilp_keyboard_layout_optimization.ilp import KeyboardOptimization
-from ilp_keyboard_layout_optimization.types import LinCosts, QuadCosts
+from ilp_keyboard_layout_optimization.type_aliases import LinCosts, QuadCosts
 
 
 def prepare_costs(
diff --git a/src/ilp_keyboard_layout_optimization/receive_data.py b/src/ilp_keyboard_layout_optimization/receive_data.py
index 133164c..2e41fd0 100644
--- a/src/ilp_keyboard_layout_optimization/receive_data.py
+++ b/src/ilp_keyboard_layout_optimization/receive_data.py
@@ -8,7 +8,7 @@ from os.path import abspath, basename
 from typing import Optional
 from urllib.request import urlopen
 
-from .types import CharSet, CharTuple
+from src.ilp_keyboard_layout_optimization.type_aliases import CharSet, CharTuple
 
 
 class CharProbs:
diff --git a/src/ilp_keyboard_layout_optimization/types.py b/src/ilp_keyboard_layout_optimization/type_aliases.py
similarity index 100%
rename from src/ilp_keyboard_layout_optimization/types.py
rename to src/ilp_keyboard_layout_optimization/type_aliases.py
-- 
GitLab


From d26ba8b3a97cbece40fac7e5b3b0ed0a9af9e39e Mon Sep 17 00:00:00 2001
From: Bjoern Ludwig <bjoern.ludwig@ptb.de>
Date: Fri, 11 Feb 2022 18:14:53 +0100
Subject: [PATCH 05/22] refactor(locs): rename locations to positions

---
 src/ilp_keyboard_layout_optimization/ilp.py   |  7 ++--
 .../optimize.py                               | 38 ++++++++---------
 test/test_initialization.py                   | 42 +++++++++----------
 test/test_types.py                            |  6 +--
 4 files changed, 47 insertions(+), 46 deletions(-)

diff --git a/src/ilp_keyboard_layout_optimization/ilp.py b/src/ilp_keyboard_layout_optimization/ilp.py
index 00611c0..f1f86a2 100644
--- a/src/ilp_keyboard_layout_optimization/ilp.py
+++ b/src/ilp_keyboard_layout_optimization/ilp.py
@@ -12,6 +12,7 @@ from .type_aliases import (
     QuadCosts,
     QuadVars,
 )
+from pyscipopt import Model, quicksum
 
 
 class KeyboardOptimization:
@@ -74,7 +75,7 @@ class KeyboardOptimization:
                         if char_2 != char
                     )
                     <= self.char_key_assigns[char, key],
-                    f"QuadCharacterAssignedLEQThanLocation({char},{key},{key_2})",
+                    f"QuadCharacterAssignedLEQThanPosition({char},{key},{key_2})",
                 )
                 self.model.addCons(
                     quicksum(
@@ -83,7 +84,7 @@ class KeyboardOptimization:
                         if char_2 != char
                     )
                     <= self.char_key_assigns[char, key],
-                    f"QuadCharacterAssignedLEQThanSecondLocation({char},{key_2},{key})",
+                    f"QuadCharacterAssignedLEQThanSecondPosition({char},{key_2},{key})",
                 )
             for char_2 in self.chars:
                 for key in self.keys:
@@ -119,7 +120,7 @@ class KeyboardOptimization:
         for key in self.keys:
             constr[key] = self.model.addCons(
                 quicksum(self.char_key_assigns[char, key] for char in self.chars) == 1,
-                f"AllLocationAssignedOnce({key})",
+                f"AllPositionsAssignedOnce({key})",
             )
 
         self.model.setObjective(
diff --git a/src/ilp_keyboard_layout_optimization/optimize.py b/src/ilp_keyboard_layout_optimization/optimize.py
index 4d3e949..cabf999 100644
--- a/src/ilp_keyboard_layout_optimization/optimize.py
+++ b/src/ilp_keyboard_layout_optimization/optimize.py
@@ -33,48 +33,48 @@ def prepare_costs(
     """
     _linear_costs = {}
     _quad_costs = {}
-    for (char, loc) in optimization_problem.char_key_assigns_keys:
+    for (char, pos) in optimization_problem.char_key_assigns_keys:
         if (
-            (char == "u" and loc == "left_pinky_home")
-            or (char == "n" and loc == "right_index_home")
-            or (char == "r" and loc == "right_middle_home")
-            or (char == "t" and loc == "right_ring_home")
-            or (char == "d" and loc == "right_pinky_home")
+            (char == "u" and pos == "left_pinky_home")
+            or (char == "n" and pos == "right_index_home")
+            or (char == "r" and pos == "right_middle_home")
+            or (char == "t" and pos == "right_ring_home")
+            or (char == "d" and pos == "right_pinky_home")
         ):
-            _linear_costs[char, loc] = 0.0
+            _linear_costs[char, pos] = 0.0
             continue
-        _linear_costs[char, loc] = 1.0
+        _linear_costs[char, pos] = 1.0
 
-    for (char, char_2, loc, loc_2) in optimization_problem.quad_char_key_assigns_keys:
+    for (char, char_2, pos, pos_2) in optimization_problem.quad_char_key_assigns_keys:
         if (
             (
                 char == "u"
-                and loc == "left_pinky_home"
+                and pos == "left_pinky_home"
                 and char_2 == "i"
-                and loc_2 == "left_middle_home"
+                and pos_2 == "left_middle_home"
             )
             or (
                 char == "i"
-                and loc == "left_middle_home"
+                and pos == "left_middle_home"
                 and char_2 == "a"
-                and loc_2 == "left_index_home"
+                and pos_2 == "left_index_home"
             )
             or (
                 char == "a"
-                and loc == "left_index_home"
+                and pos == "left_index_home"
                 and char_2 == "e"
-                and loc_2 == "left_ring_home"
+                and pos_2 == "left_ring_home"
             )
         ):
-            _quad_costs[char, char_2, loc, loc_2] = 0.0
+            _quad_costs[char, char_2, pos, pos_2] = 0.0
             continue
-        _quad_costs[char, char_2, loc, loc_2] = 1.0
+        _quad_costs[char, char_2, pos, pos_2] = 1.0
     return _linear_costs, _quad_costs
 
 
 if __name__ == "__main__":
     test_chars = ("a", "e", "i", "u", "n", "r", "t", "d")
-    test_locs = (
+    test_poss = (
         "left_pinky_home",
         "left_ring_home",
         "left_middle_home",
@@ -84,7 +84,7 @@ if __name__ == "__main__":
         "right_ring_home",
         "right_pinky_home",
     )
-    optimization_model = KeyboardOptimization(test_chars, test_locs)
+    optimization_model = KeyboardOptimization(test_chars, test_poss)
     linear_costs, quad_costs = prepare_costs(optimization_model)
     optimization_model.set_up_model(linear_costs, quad_costs)
     optimization_model.solve()
diff --git a/test/test_initialization.py b/test/test_initialization.py
index 1d06626..3e502a2 100644
--- a/test/test_initialization.py
+++ b/test/test_initialization.py
@@ -12,7 +12,7 @@ def three_chars() -> CharTuple:
 
 
 @pytest.fixture(scope="session")
-def three_locs() -> CharTuple:
+def three_poss() -> CharTuple:
     return "left_pinky_home", "left_ring_home", "right_index_home"
 
 
@@ -23,36 +23,36 @@ def test_keyboard_optimization_init_throw_errors(init_params):
         KeyboardOptimization(init_params)
 
 
-def test_keyboard_optimization_init(three_chars, three_locs):
-    KeyboardOptimization(three_chars, three_locs)
+def test_keyboard_optimization_init(three_chars, three_poss):
+    KeyboardOptimization(three_chars, three_poss)
 
 
-def test_keyboard_optimization_char_loc_assigns_keys(three_chars, three_locs):
+def test_keyboard_optimization_char_pos_assigns_keys(three_chars, three_poss):
     linear_keys = tuple(
-        KeyboardOptimization(three_chars, three_locs).char_key_assigns_keys
+        KeyboardOptimization(three_chars, three_poss).char_key_assigns_keys
     )
-    assert len(linear_keys) == len(three_chars) * len(three_locs)
+    assert len(linear_keys) == len(three_chars) * len(three_poss)
     for char in three_chars:
-        for loc in three_locs:
-            assert (char, loc) in linear_keys
+        for pos in three_poss:
+            assert (char, pos) in linear_keys
 
 
-def test_keyboard_optimization_quad_char_loc_assigns_keys(three_chars, three_locs):
+def test_keyboard_optimization_quad_char_pos_assigns_keys(three_chars, three_poss):
     quadratic_keys = tuple(
-        KeyboardOptimization(three_chars, three_locs).quad_char_key_assigns_keys
+        KeyboardOptimization(three_chars, three_poss).quad_char_key_assigns_keys
     )
     assert len(quadratic_keys) == len(three_chars) * (len(three_chars) - 1) * len(
-        three_locs
-    ) * (len(three_locs) - 1)
+        three_poss
+    ) * (len(three_poss) - 1)
     for (char, char_2) in permutations(three_chars, 2):
-        for (loc, loc_2) in permutations(three_locs, 2):
-            assert (char, char_2, loc, loc_2) in quadratic_keys
+        for (pos, pos_2) in permutations(three_poss, 2):
+            assert (char, char_2, pos, pos_2) in quadratic_keys
 
 
-def test_keyboard_optimization_quad_locs(three_chars, three_locs):
-    quad_locs = tuple(KeyboardOptimization(three_chars, three_locs).key_pairs)
-    assert len(quad_locs) == len(three_locs) * (len(three_locs) - 1)
-    for loc in three_locs:
-        for loc_2 in three_locs:
-            if loc != loc_2:
-                assert (loc, loc_2) in quad_locs
+def test_keyboard_optimization_quad_poss(three_chars, three_poss):
+    quad_poss = tuple(KeyboardOptimization(three_chars, three_poss).key_pairs)
+    assert len(quad_poss) == len(three_poss) * (len(three_poss) - 1)
+    for pos in three_poss:
+        for pos_2 in three_poss:
+            if pos != pos_2:
+                assert (pos, pos_2) in quad_poss
diff --git a/test/test_types.py b/test/test_types.py
index 60ba63d..85c9c20 100644
--- a/test/test_types.py
+++ b/test/test_types.py
@@ -36,7 +36,7 @@ def test_bigram():
     assert Bigram == tuple[Char, Char]
 
 
-def test_loc_pair():
+def test_pos_pair():
     assert KeyPair == tuple[Key, Key]
 
 
@@ -48,7 +48,7 @@ def test_char_tuple():
     assert CharTuple == tuple[Char, ...]
 
 
-def test_loc_tuple():
+def test_pos_tuple():
     assert KeyTuple == tuple[Key, ...]
 
 
@@ -56,7 +56,7 @@ def test_lin_vars():
     assert LinVars == dict[CharKeyPair, bool]
 
 
-def test_quad_loc_quadruple():
+def test_quad_pos_quadruple():
     assert CharKeyQuadruple == tuple[Char, Key, Char, Key]
 
 
-- 
GitLab


From 225e52e7ce2e7b7fe316fe4b641c3e7bb729f428 Mon Sep 17 00:00:00 2001
From: Bjoern Ludwig <bjoern.ludwig@ptb.de>
Date: Fri, 11 Feb 2022 18:15:26 +0100
Subject: [PATCH 06/22] wip(data_aquisition): introduce new package

---
 src/ilp_keyboard_layout_optimization/data_aquisition/__init__.py | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 src/ilp_keyboard_layout_optimization/data_aquisition/__init__.py

diff --git a/src/ilp_keyboard_layout_optimization/data_aquisition/__init__.py b/src/ilp_keyboard_layout_optimization/data_aquisition/__init__.py
new file mode 100644
index 0000000..e69de29
-- 
GitLab


From 69401c188dc9c808e82c70bf66a32cc2072da305 Mon Sep 17 00:00:00 2001
From: Bjoern Ludwig <bjoern.ludwig@ptb.de>
Date: Fri, 11 Feb 2022 18:15:48 +0100
Subject: [PATCH 07/22] wip(chars): introduce new module and class Chars

---
 .../data_aquisition/chars.py                  | 39 +++++++++++++++++++
 1 file changed, 39 insertions(+)
 create mode 100644 src/ilp_keyboard_layout_optimization/data_aquisition/chars.py

diff --git a/src/ilp_keyboard_layout_optimization/data_aquisition/chars.py b/src/ilp_keyboard_layout_optimization/data_aquisition/chars.py
new file mode 100644
index 0000000..568b50b
--- /dev/null
+++ b/src/ilp_keyboard_layout_optimization/data_aquisition/chars.py
@@ -0,0 +1,39 @@
+"""This module contains the class providing a unified interface for character sets"""
+
+__all__ = ["Chars"]
+
+from typing import Optional, Union
+
+from src.ilp_keyboard_layout_optimization.type_aliases import CharTuple
+
+
+class Chars:
+    _chars: str
+
+    def __init__(self, chars: Optional[Union[str, CharTuple]] = None):
+        if chars is None:
+            self._chars = (
+                """abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890,"""
+                r"""üöäÜÖÄß.:;-–…_[](){}^\/#!?+<>&$|~`*=%"'@"""
+            )
+        else:
+            self.chars = chars
+
+    @property
+    def chars(self) -> str:
+        return self._chars
+
+    @chars.setter
+    def chars(self, chars: Union[str, CharTuple]):
+        if isinstance(chars, str):
+            self._chars = chars
+        else:  # isinstance(chars, CharTuple):
+            self._chars = "".join(char for char in chars)
+
+    @property
+    def char_tuple(self) -> CharTuple:
+        return self._str2char_tuple(self.chars)
+
+    @staticmethod
+    def _str2char_tuple(string: str) -> CharTuple:
+        return tuple(char for char in string)
-- 
GitLab


From ff8011de886bed0bb66a4924a149db1adbea32ea Mon Sep 17 00:00:00 2001
From: Bjoern Ludwig <bjoern.ludwig@ptb.de>
Date: Fri, 11 Feb 2022 18:16:10 +0100
Subject: [PATCH 08/22] wip(chars): introduce tests for new module and class
 Chars

---
 test/test_chars.py | 65 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 65 insertions(+)
 create mode 100644 test/test_chars.py

diff --git a/test/test_chars.py b/test/test_chars.py
new file mode 100644
index 0000000..f6785cd
--- /dev/null
+++ b/test/test_chars.py
@@ -0,0 +1,65 @@
+from typing import Iterable, List
+
+from hypothesis import given, settings, strategies as hst
+
+from src.ilp_keyboard_layout_optimization.data_aquisition.chars import (
+    Chars,
+)
+from src.ilp_keyboard_layout_optimization.type_aliases import CharTuple
+
+
+def test_chars_init():
+    assert Chars()
+
+
+def test_chars_chars_type():
+    assert isinstance(Chars().chars, str)
+
+
+def test_chars_char_tuple_type():
+    assert isinstance(Chars().char_tuple, CharTuple.__origin__)
+
+
+@given(hst.tuples(hst.text(min_size=1)))
+@settings(deadline=None)
+# TODO Fix this test, it is not generating what it is supposed to
+def test_chars_input_tuple(char_tuple):
+    assert Chars(char_tuple).chars == "".join(char_tuple)
+
+
+@given(hst.text(min_size=1))
+def test_chars_input_str(char_string):
+    assert Chars(char_string).chars == char_string
+
+
+@given(hst.text(min_size=1))
+def test_chars_input_tuple_equals_input_str(char_string):
+    char_tuple = Chars._str2char_tuple(char_string)
+    assert Chars(char_string).chars == Chars(char_tuple).chars
+
+
+@given(hst.text(min_size=1))
+def test_chars_input_str(char_string):
+    assert Chars(char_string).chars == char_string
+
+
+def test_chars_default():
+    all_test_chars = list(Chars().chars)
+    basic_latin = _get_unicode_chars(range(0x0021, 0x007F))
+    latin_1_supp = _get_unicode_chars(
+        (0x00C4, 0x00D6, 0x00DC, 0x00E4, 0x00F6, 0x00FC, 0x00DF)
+    )
+    general_punc = _get_unicode_chars((0x2013, 0x2026))
+    extended_alphabet = basic_latin + latin_1_supp + general_punc
+    for char in extended_alphabet:
+        all_test_chars.remove(char)
+    assert not all_test_chars
+
+
+def _get_unicode_chars(code_point_range: Iterable) -> List[str]:
+    return [chr(char) for char in code_point_range]
+
+
+@given(hst.text(min_size=1))
+def test_chars_monograms(char_string):
+    pass
-- 
GitLab


From 2c3a0b4cf0e2f47c74dcc0af791a8c922067fdee Mon Sep 17 00:00:00 2001
From: Bjoern Ludwig <bjoern.ludwig@ptb.de>
Date: Fri, 11 Feb 2022 18:16:51 +0100
Subject: [PATCH 09/22] wip(receive_data): fix computation of normalized
 weights of bigrams

---
 src/ilp_keyboard_layout_optimization/receive_data.py | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/ilp_keyboard_layout_optimization/receive_data.py b/src/ilp_keyboard_layout_optimization/receive_data.py
index 2e41fd0..8b0d8cf 100644
--- a/src/ilp_keyboard_layout_optimization/receive_data.py
+++ b/src/ilp_keyboard_layout_optimization/receive_data.py
@@ -120,11 +120,10 @@ class CharProbs:
                     row["bigram"][0] in self.chars and row["bigram"][1] in self.chars
                 ):
                     bi_probs[row["bigram"]] = absolute_count
-        total_sum = sum(bi_probs.values())
-        assert total_sum > 0
-        normalizer = 100 / total_sum
+        absolute_sum = sum(bi_probs.values())
         for character, count in bi_probs.items():
-            bi_probs[character] = count / normalizer
+            bi_probs[character] = count / absolute_sum
+        assert round(sum(bi_probs.values()), 8) == 1
         assert len(bi_probs) == reader.line_num or len(bi_probs) <= comb(
             reader.line_num, 2
         )
-- 
GitLab


From fb813af046da97d18d513dafa797cc3fcee32690 Mon Sep 17 00:00:00 2001
From: Bjoern Ludwig <bjoern.ludwig@ptb.de>
Date: Fri, 11 Feb 2022 18:17:11 +0100
Subject: [PATCH 10/22] wip(receive_data): change default values when calling
 directly

---
 src/ilp_keyboard_layout_optimization/receive_data.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/ilp_keyboard_layout_optimization/receive_data.py b/src/ilp_keyboard_layout_optimization/receive_data.py
index 8b0d8cf..4841175 100644
--- a/src/ilp_keyboard_layout_optimization/receive_data.py
+++ b/src/ilp_keyboard_layout_optimization/receive_data.py
@@ -142,9 +142,9 @@ class CharProbs:
 
 if __name__ == "__main__":
     CharProbs(
-        ("a", "b", "c"),
+        None,
         "http://www.ids-mannheim.de/fileadmin/kl/derewo/"
         "DeReChar-v-uni-204-a-c-2018-02-28-1.0.csv",
         "http://practicalcryptography.com/media/cryptanalysis/files/"
-        "icelandic_bigrams.txt",
+        "german_bigrams.txt",
     )
-- 
GitLab


From 96ac13b91d340468d910848a14f578536ef51e80 Mon Sep 17 00:00:00 2001
From: Bjoern Ludwig <bjoern.ludwig@ptb.de>
Date: Fri, 11 Feb 2022 23:15:43 +0100
Subject: [PATCH 11/22] refactor(type_aliases): rename some type aliases, the
 module itself and include all in __all__

---
 .../data_aquisition/chars.py                  |  6 ++-
 src/ilp_keyboard_layout_optimization/ilp.py   | 15 +++++-
 .../type_aliases.py                           | 52 ++++++++++---------
 test/test_types.py                            | 32 ++++++------
 4 files changed, 61 insertions(+), 44 deletions(-)

diff --git a/src/ilp_keyboard_layout_optimization/data_aquisition/chars.py b/src/ilp_keyboard_layout_optimization/data_aquisition/chars.py
index 568b50b..0b31d3e 100644
--- a/src/ilp_keyboard_layout_optimization/data_aquisition/chars.py
+++ b/src/ilp_keyboard_layout_optimization/data_aquisition/chars.py
@@ -2,9 +2,11 @@
 
 __all__ = ["Chars"]
 
-from typing import Optional, Union
+import string
+from itertools import product
+from typing import Optional, Tuple, Union
 
-from src.ilp_keyboard_layout_optimization.type_aliases import CharTuple
+from ..type_aliases import Bigram, CharTuple
 
 
 class Chars:
diff --git a/src/ilp_keyboard_layout_optimization/ilp.py b/src/ilp_keyboard_layout_optimization/ilp.py
index f1f86a2..01fb376 100644
--- a/src/ilp_keyboard_layout_optimization/ilp.py
+++ b/src/ilp_keyboard_layout_optimization/ilp.py
@@ -14,6 +14,17 @@ from .type_aliases import (
 )
 from pyscipopt import Model, quicksum
 
+from .data_aquisition.chars import Chars
+from .type_aliases import (
+    CharPosPair,
+    CharPosQuadruple,
+    LinCosts,
+    LinVars,
+    PosTuple,
+    QuadCosts,
+    QuadVars,
+)
+
 
 class KeyboardOptimization:
     """Instances of this class represent instances of the keyboard layout QAP
@@ -179,12 +190,12 @@ class KeyboardOptimization:
         print(f"{str(solution_assignments)}")
 
     @property
-    def char_key_assigns_keys(self) -> Iterable[CharKeyPair]:
+    def char_key_assigns_keys(self) -> Iterable[CharPosPair]:
         """An iterator for the pairs of (special) characters and corresponding keys"""
         return product(self.chars, self.keys)
 
     @property
-    def quad_char_key_assigns_keys(self) -> Iterable[CharKeyQuadruple]:
+    def quad_char_key_assigns_keys(self) -> Iterable[CharPosQuadruple]:
         """An iterator for quadruples of character pairs and corresponding key pairs"""
         flattened_tuple_of_quads = chain.from_iterable(
             chain.from_iterable(
diff --git a/src/ilp_keyboard_layout_optimization/type_aliases.py b/src/ilp_keyboard_layout_optimization/type_aliases.py
index 0727d46..1eb2b5d 100644
--- a/src/ilp_keyboard_layout_optimization/type_aliases.py
+++ b/src/ilp_keyboard_layout_optimization/type_aliases.py
@@ -1,40 +1,44 @@
-"""This module contains custom types for type hints and thus more convenient coding"""
+"""This module contains type aliases for type hints and thus more convenient coding"""
 
 __all__ = [
-    "CharKeyPair",
-    "CharKeyQuadruple",
-    "CharTuple",
+    "Bigram",
+    "Char",
+    "CharPosPair",
+    "CharPosQuadruple",
     "CharSet",
-    "KeyTuple",
+    "CharTuple",
     "LinCosts",
     "LinVars",
+    "Pos",
+    "PosPair",
+    "PosTuple",
     "QuadCosts",
     "QuadVars",
 ]
 
 Char = str
 """A (special) character"""
-Key = str
-"""A key"""
-CharKeyPair = tuple[Char, Key]
-"""A pair of a (special) character and a key"""
-Bigram = tuple[Char, Char]
-"""A tuple of two (special) characters"""
-KeyPair = tuple[Key, Key]
-"""A tuple of two keys"""
+Pos = str
+"""A position"""
+CharPosPair = tuple[Char, Pos]
+"""A pair of a (special) character and a position"""
+Bigram = str
+"""A length-two string of (special) characters"""
+PosPair = tuple[Pos, Pos]
+"""A tuple of two positions"""
 CharTuple = tuple[Char, ...]
 """A tuple of several (special) characters"""
 CharSet = set[Char]
 """A set of several (special) characters"""
-KeyTuple = tuple[Key, ...]
-"""A tuple of several keys"""
-LinCosts = dict[CharKeyPair, float]
+PosTuple = tuple[Pos, ...]
+"""A tuple of several positions"""
+LinCosts = dict[CharPosPair, float]
 """A dictionary assigning costs to (special) character bigrams"""
-CharKeyQuadruple = tuple[Char, Key, Char, Key]
-"""A four-tuple: (special) character, key, another (special) character, another key"""
-QuadCosts = dict[CharKeyQuadruple, float]
-"""A dictionary assigning costs to (special) character, key quadruples"""
-LinVars = dict[CharKeyPair, bool]
-"""A dictionary of binary decisions of assigning (special) characters to keys"""
-QuadVars = dict[CharKeyQuadruple, bool]
-"""A dictionary of binary decisions of assigning two (special) characters to two keys"""
+CharPosQuadruple = tuple[Char, Char, Pos, Pos]
+"""A four-tuple: two (special) characters and their respective positions"""
+QuadCosts = dict[CharPosQuadruple, float]
+"""A dictionary assigning costs to (special) character, position quadruples"""
+LinVars = dict[CharPosPair, bool]
+"""A dictionary of binary decisions of assigning (special) characters to positions"""
+QuadVars = dict[CharPosQuadruple, bool]
+"""A dictionary of binary vars assigning two (special) characters to two positions"""
diff --git a/test/test_types.py b/test/test_types.py
index 85c9c20..a1601f6 100644
--- a/test/test_types.py
+++ b/test/test_types.py
@@ -1,13 +1,13 @@
 from ilp_keyboard_layout_optimization.costs import FreqTuple
-from ilp_keyboard_layout_optimization.types import (
+from ilp_keyboard_layout_optimization.type_aliases import (
     Bigram,
     Char,
-    CharKeyPair,
-    CharKeyQuadruple,
+    CharPosPair,
+    CharPosQuadruple,
     CharTuple,
-    Key,
-    KeyPair,
-    KeyTuple,
+    Pos,
+    PosPair,
+    PosTuple,
     LinCosts,
     LinVars,
     QuadCosts,
@@ -25,23 +25,23 @@ def test_char():
 
 
 def test_key():
-    assert Key == str
+    assert Pos == str
 
 
 def test_char_key_pair():
-    assert CharKeyPair == tuple[Char, Key]
+    assert CharPosPair == tuple[Char, Pos]
 
 
 def test_bigram():
-    assert Bigram == tuple[Char, Char]
+    assert Bigram == str
 
 
 def test_pos_pair():
-    assert KeyPair == tuple[Key, Key]
+    assert PosPair == tuple[Pos, Pos]
 
 
 def test_lin_costs():
-    assert LinCosts == dict[CharKeyPair, float]
+    assert LinCosts == dict[CharPosPair, float]
 
 
 def test_char_tuple():
@@ -49,20 +49,20 @@ def test_char_tuple():
 
 
 def test_pos_tuple():
-    assert KeyTuple == tuple[Key, ...]
+    assert PosTuple == tuple[Pos, ...]
 
 
 def test_lin_vars():
-    assert LinVars == dict[CharKeyPair, bool]
+    assert LinVars == dict[CharPosPair, bool]
 
 
 def test_quad_pos_quadruple():
-    assert CharKeyQuadruple == tuple[Char, Key, Char, Key]
+    assert CharPosQuadruple == tuple[Char, Pos, Char, Pos]
 
 
 def test_quad_costs():
-    assert QuadCosts == dict[CharKeyQuadruple, float]
+    assert QuadCosts == dict[CharPosQuadruple, float]
 
 
 def test_quad_vars():
-    assert QuadVars == dict[CharKeyQuadruple, bool]
+    assert QuadVars == dict[CharPosQuadruple, bool]
-- 
GitLab


From 9928abd41044555c15d34e70d15b0a5d4038a0e4 Mon Sep 17 00:00:00 2001
From: Bjoern Ludwig <bjoern.ludwig@ptb.de>
Date: Fri, 11 Feb 2022 23:17:13 +0100
Subject: [PATCH 12/22] wip(chars): finalize new chars and associated tests

---
 .../data_aquisition/chars.py                  | 49 +++++++--
 src/ilp_keyboard_layout_optimization/ilp.py   | 20 ++--
 .../optimize.py                               |  4 +-
 test/test_chars.py                            | 99 +++++++++++++++++--
 4 files changed, 147 insertions(+), 25 deletions(-)

diff --git a/src/ilp_keyboard_layout_optimization/data_aquisition/chars.py b/src/ilp_keyboard_layout_optimization/data_aquisition/chars.py
index 0b31d3e..7cc435b 100644
--- a/src/ilp_keyboard_layout_optimization/data_aquisition/chars.py
+++ b/src/ilp_keyboard_layout_optimization/data_aquisition/chars.py
@@ -10,13 +10,28 @@ from ..type_aliases import Bigram, CharTuple
 
 
 class Chars:
+    """A unified interface to a collection of characters and corresponding bigrams
+
+    Parameters
+    ----------
+    chars : str or CharTupel, optional
+        A string of concatenated (special) characters or a CharTupel of single
+        characters, that are supposed to be considered. Defaults to the most common
+        letters, numbers and punctuation in German texts.
+    """
+
     _chars: str
+    _monos: CharTuple
+    _bis: Tuple[Bigram]
 
     def __init__(self, chars: Optional[Union[str, CharTuple]] = None):
         if chars is None:
             self._chars = (
-                """abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890,"""
-                r"""üöäÜÖÄß.:;-–…_[](){}^\/#!?+<>&$|~`*=%"'@"""
+                string.ascii_lowercase
+                + string.ascii_uppercase
+                + string.digits
+                + string.punctuation
+                + "üöäÜÖÄß–…"
             )
         else:
             self.chars = chars
@@ -31,11 +46,33 @@ class Chars:
             self._chars = chars
         else:  # isinstance(chars, CharTuple):
             self._chars = "".join(char for char in chars)
+        try:
+            del self._monos
+        except AttributeError:
+            pass
+        try:
+            del self._bis
+        except AttributeError:
+            pass
 
     @property
-    def char_tuple(self) -> CharTuple:
-        return self._str2char_tuple(self.chars)
+    def monos(self) -> CharTuple:
+        try:
+            return self._monos
+        except AttributeError:
+            self._monos = self._str2char_tuple(self.chars)
+            return self._monos
 
     @staticmethod
-    def _str2char_tuple(string: str) -> CharTuple:
-        return tuple(char for char in string)
+    def _str2char_tuple(char_str: str) -> CharTuple:
+        return tuple(char for char in char_str)
+
+    @property
+    def bis(self):
+        try:
+            return self._bis
+        except AttributeError:
+            self._bis = tuple(
+                "".join(bigram_tuple) for bigram_tuple in product(self.chars, repeat=2)
+            )
+            return self._bis
diff --git a/src/ilp_keyboard_layout_optimization/ilp.py b/src/ilp_keyboard_layout_optimization/ilp.py
index 01fb376..db7d88b 100644
--- a/src/ilp_keyboard_layout_optimization/ilp.py
+++ b/src/ilp_keyboard_layout_optimization/ilp.py
@@ -47,8 +47,8 @@ class KeyboardOptimization:
     chars: CharTuple
     keys: KeyTuple
 
-    def __init__(self, chars: CharTuple, keys: KeyTuple):
-        assert len(chars) == len(keys)
+    def __init__(self, chars: Chars, poss: PosTuple):
+        assert len(chars.monos) == len(poss)
         self.chars = chars
         self.keys = keys
         self.char_key_assigns: LinVars = {}
@@ -73,7 +73,7 @@ class KeyboardOptimization:
         self.quad_char_key_costs = quad_char_key_costs
 
         constr = {}
-        for char in self.chars:
+        for char in self.chars.monos:
             self.model.addCons(
                 quicksum(self.char_key_assigns[char, key] for key in self.keys) == 1,
                 f"AllCharacterAssignedOnce({char})",
@@ -81,8 +81,8 @@ class KeyboardOptimization:
             for (key, key_2) in self.key_pairs:
                 self.model.addCons(
                     quicksum(
-                        self.quad_char_key_assigns[char, char_2, key, key_2]
-                        for char_2 in self.chars
+                        self.quad_char_pos_assigns[char, char_2, key, key_2]
+                        for char_2 in self.chars.monos
                         if char_2 != char
                     )
                     <= self.char_key_assigns[char, key],
@@ -90,15 +90,15 @@ class KeyboardOptimization:
                 )
                 self.model.addCons(
                     quicksum(
-                        self.quad_char_key_assigns[char_2, char, key_2, key]
-                        for char_2 in self.chars
+                        self.quad_char_pos_assigns[char_2, char, key_2, key]
+                        for char_2 in self.chars.monos
                         if char_2 != char
                     )
                     <= self.char_key_assigns[char, key],
                     f"QuadCharacterAssignedLEQThanSecondPosition({char},{key_2},{key})",
                 )
-            for char_2 in self.chars:
-                for key in self.keys:
+            for char_2 in self.chars.monos:
+                for key in self.poss:
                     if char_2 != char:
                         self.model.addCons(
                             quicksum(
@@ -199,7 +199,7 @@ class KeyboardOptimization:
         """An iterator for quadruples of character pairs and corresponding key pairs"""
         flattened_tuple_of_quads = chain.from_iterable(
             chain.from_iterable(
-                product(permutations(self.chars, 2), permutations(self.keys, 2))
+                product(permutations(self.chars.monos, 2), permutations(self.poss, 2))
             )
         )
         iter_of_quads = (iter(flattened_tuple_of_quads),) * 4
diff --git a/src/ilp_keyboard_layout_optimization/optimize.py b/src/ilp_keyboard_layout_optimization/optimize.py
index cabf999..1e10727 100644
--- a/src/ilp_keyboard_layout_optimization/optimize.py
+++ b/src/ilp_keyboard_layout_optimization/optimize.py
@@ -15,6 +15,8 @@ function at the very bottom of this file to change inputs.
 from ilp_keyboard_layout_optimization.ilp import KeyboardOptimization
 from ilp_keyboard_layout_optimization.type_aliases import LinCosts, QuadCosts
 
+from src.ilp_keyboard_layout_optimization.data_aquisition.chars import Chars
+
 
 def prepare_costs(
     optimization_problem: KeyboardOptimization,
@@ -73,7 +75,7 @@ def prepare_costs(
 
 
 if __name__ == "__main__":
-    test_chars = ("a", "e", "i", "u", "n", "r", "t", "d")
+    test_chars = Chars(("a", "e", "i", "u", "n", "r", "t", "d"))
     test_poss = (
         "left_pinky_home",
         "left_ring_home",
diff --git a/test/test_chars.py b/test/test_chars.py
index f6785cd..2108497 100644
--- a/test/test_chars.py
+++ b/test/test_chars.py
@@ -16,15 +16,14 @@ def test_chars_chars_type():
     assert isinstance(Chars().chars, str)
 
 
-def test_chars_char_tuple_type():
-    assert isinstance(Chars().char_tuple, CharTuple.__origin__)
+def test_chars_monos_type():
+    assert isinstance(Chars().monos, CharTuple.__origin__)
 
 
-@given(hst.tuples(hst.text(min_size=1)))
+@given(hst.lists(hst.characters(), min_size=1))
 @settings(deadline=None)
-# TODO Fix this test, it is not generating what it is supposed to
 def test_chars_input_tuple(char_tuple):
-    assert Chars(char_tuple).chars == "".join(char_tuple)
+    assert Chars(tuple(char_tuple)).chars == "".join(char_tuple)
 
 
 @given(hst.text(min_size=1))
@@ -52,6 +51,7 @@ def test_chars_default():
     general_punc = _get_unicode_chars((0x2013, 0x2026))
     extended_alphabet = basic_latin + latin_1_supp + general_punc
     for char in extended_alphabet:
+        assert char in all_test_chars
         all_test_chars.remove(char)
     assert not all_test_chars
 
@@ -60,6 +60,89 @@ def _get_unicode_chars(code_point_range: Iterable) -> List[str]:
     return [chr(char) for char in code_point_range]
 
 
-@given(hst.text(min_size=1))
-def test_chars_monograms(char_string):
-    pass
+def test_chars_monograms():
+    assert Chars().monos
+
+
+def test_chars_monograms_multiple_times():
+    test_chars = Chars()
+    first_time_monos = test_chars.monos
+    second_time_monos = test_chars.monos
+    assert first_time_monos == second_time_monos
+
+
+@given(hst.lists(hst.characters(), min_size=3))
+def test_chars_monograms_after_resetting(char_tuple):
+    first_test_chars = Chars(char_tuple)
+    assert first_test_chars.monos == tuple(char_tuple)
+    first_test_chars.chars = char_tuple[1:-1]
+    assert first_test_chars.monos == tuple(char_tuple[1:-1])
+
+
+def test_chars_bigrams():
+    assert Chars().bis
+
+
+def test_chars_bigrams_length():
+    for bigram in Chars().bis:
+        assert len(bigram) == 2
+
+
+def test_bigrams_default():
+    all_test_bigrams = list(Chars("1234").bis)
+    actual_bigrams = (
+        "11",
+        "12",
+        "21",
+        "13",
+        "31",
+        "14",
+        "41",
+        "22",
+        "23",
+        "32",
+        "24",
+        "42",
+        "33",
+        "34",
+        "44",
+        "43",
+    )
+    for bigram in actual_bigrams:
+        assert bigram in all_test_bigrams
+        all_test_bigrams.remove(bigram)
+    assert not all_test_bigrams
+
+
+def test_chars_bigrams_multiple_times():
+    test_chars = Chars()
+    first_time_bis = test_chars.bis
+    second_time_bis = test_chars.bis
+    assert first_time_bis == second_time_bis
+
+
+def test_chars_bigrams_after_resetting():
+    test_chars = Chars("12")
+    first_bigram_list = list(test_chars.bis)
+    first_actual_bigrams = (
+        "11",
+        "12",
+        "21",
+        "22",
+    )
+    for bigram in first_actual_bigrams:
+        assert bigram in first_bigram_list
+        first_bigram_list.remove(bigram)
+    assert not first_bigram_list
+    test_chars.chars = "23"
+    second_bigram_list = list(test_chars.bis)
+    second_actual_bigrams = (
+        "22",
+        "23",
+        "32",
+        "33",
+    )
+    for bigram in second_actual_bigrams:
+        assert bigram in second_bigram_list
+        second_bigram_list.remove(bigram)
+    assert not second_bigram_list
-- 
GitLab


From 67ba0ce181be286ed3390ffa733cd65f460e691b Mon Sep 17 00:00:00 2001
From: Bjoern Ludwig <bjoern.ludwig@ptb.de>
Date: Fri, 11 Feb 2022 23:17:45 +0100
Subject: [PATCH 13/22] refactor(ilp): switch from keys to positions

---
 src/ilp_keyboard_layout_optimization/ilp.py | 87 +++++++++------------
 1 file changed, 39 insertions(+), 48 deletions(-)

diff --git a/src/ilp_keyboard_layout_optimization/ilp.py b/src/ilp_keyboard_layout_optimization/ilp.py
index db7d88b..0c03cca 100644
--- a/src/ilp_keyboard_layout_optimization/ilp.py
+++ b/src/ilp_keyboard_layout_optimization/ilp.py
@@ -2,16 +2,6 @@
 from itertools import chain, permutations, product
 from typing import Iterable
 
-from .type_aliases import (
-    CharKeyPair,
-    CharKeyQuadruple,
-    CharTuple,
-    KeyTuple,
-    LinCosts,
-    LinVars,
-    QuadCosts,
-    QuadVars,
-)
 from pyscipopt import Model, quicksum
 
 from .data_aquisition.chars import Chars
@@ -30,52 +20,52 @@ class KeyboardOptimization:
     """Instances of this class represent instances of the keyboard layout QAP
 
     The IP variant of an optimization of character to key assignments can be modeled
-    as a so called quadratic assignment problem (QAP). The task is to assign a set of
+    as a so-called quadratic assignment problem (QAP). The task is to assign a set of
     characters to a set of keys on a keyboard. The way in which they should be
     arranged has to meet certain criteria such as for instance: characters that are
-    often typed after one another should not be assigned to keys, that are supposed
+    often typed after one another should not be assigned to positions, that are supposed
     to be pressed by the same finger.
 
     Parameters
     ----------
     chars : CharTuple
         the (special) characters to be assign
-    keys: KeyTuple
-        the keys to which we want to assign the (special) characters
+    poss: PosTuple
+        the positions to which we want to assign the (special) characters
     """
 
-    chars: CharTuple
-    keys: KeyTuple
+    chars: Chars
+    poss: PosTuple
 
     def __init__(self, chars: Chars, poss: PosTuple):
         assert len(chars.monos) == len(poss)
         self.chars = chars
-        self.keys = keys
-        self.char_key_assigns: LinVars = {}
-        self.quad_char_key_assigns: QuadVars = {}
-        self.char_key_costs: LinCosts = {}
-        self.quad_char_key_costs: QuadCosts = {}
+        self.poss = poss
+        self.char_pos_assigns: LinVars = {}
+        self.quad_char_pos_assigns: QuadVars = {}
+        self.char_pos_costs: LinCosts = {}
+        self.quad_char_pos_costs: QuadCosts = {}
         self.model: Model = Model("Keyboard Layout Optimization")
 
     def set_up_model(self, char_key_costs: LinCosts, quad_char_key_costs: QuadCosts):
         """Set up all the variables and initialize the costs for the SCIP model"""
         for (char, key) in self.char_key_assigns_keys:
-            self.char_key_assigns[char, key] = self.model.addVar(
+            self.char_pos_assigns[char, key] = self.model.addVar(
                 name=f"{key}={char}", vtype="B"
             )
         for (char, char_2, key, key_2) in self.quad_char_key_assigns_keys:
-            self.quad_char_key_assigns[char, char_2, key, key_2] = self.model.addVar(
+            self.quad_char_pos_assigns[char, char_2, key, key_2] = self.model.addVar(
                 name=f"{key}={char}_and_{key_2}={char_2}", vtype="C", lb=0, ub=1
             )
-        assert len(quad_char_key_costs) == len(self.quad_char_key_assigns)
-        assert len(char_key_costs) == len(self.char_key_assigns)
-        self.char_key_costs = char_key_costs
-        self.quad_char_key_costs = quad_char_key_costs
+        assert len(quad_char_key_costs) == len(self.quad_char_pos_assigns)
+        assert len(char_key_costs) == len(self.char_pos_assigns)
+        self.char_pos_costs = char_key_costs
+        self.quad_char_pos_costs = quad_char_key_costs
 
         constr = {}
         for char in self.chars.monos:
             self.model.addCons(
-                quicksum(self.char_key_assigns[char, key] for key in self.keys) == 1,
+                quicksum(self.char_pos_assigns[char, key] for key in self.poss) == 1,
                 f"AllCharacterAssignedOnce({char})",
             )
             for (key, key_2) in self.key_pairs:
@@ -85,7 +75,7 @@ class KeyboardOptimization:
                         for char_2 in self.chars.monos
                         if char_2 != char
                     )
-                    <= self.char_key_assigns[char, key],
+                    <= self.char_pos_assigns[char, key],
                     f"QuadCharacterAssignedLEQThanPosition({char},{key},{key_2})",
                 )
                 self.model.addCons(
@@ -94,7 +84,7 @@ class KeyboardOptimization:
                         for char_2 in self.chars.monos
                         if char_2 != char
                     )
-                    <= self.char_key_assigns[char, key],
+                    <= self.char_pos_assigns[char, key],
                     f"QuadCharacterAssignedLEQThanSecondPosition({char},{key_2},{key})",
                 )
             for char_2 in self.chars.monos:
@@ -102,35 +92,36 @@ class KeyboardOptimization:
                     if char_2 != char:
                         self.model.addCons(
                             quicksum(
-                                self.quad_char_key_assigns[char, char_2, key, key_2]
-                                for key_2 in self.keys
+                                self.quad_char_pos_assigns[char, char_2, key, key_2]
+                                for key_2 in self.poss
                                 if key_2 != key
                             )
-                            <= self.char_key_assigns[char, key],
+                            <= self.char_pos_assigns[char, key],
                             f"QuadCharacterAssignedLEQThanCharacter({char},{char_2},"
                             f"{key})",
                         )
                         self.model.addCons(
                             quicksum(
-                                self.quad_char_key_assigns[char_2, char, key_2, key]
-                                for key_2 in self.keys
+                                self.quad_char_pos_assigns[char_2, char, key_2, key]
+                                for key_2 in self.poss
                                 if key_2 != key
                             )
-                            <= self.char_key_assigns[char, key],
+                            <= self.char_pos_assigns[char, key],
                             f"QuadCharacterAssignedLEQThanSecondCharacter({char_2},"
                             f"{char},{key})",
                         )
 
         for (char, char_2, key, key_2) in self.quad_char_key_assigns_keys:
             self.model.addCons(
-                self.char_key_assigns[char, key] + self.char_key_assigns[char_2, key_2]
-                <= 1 + self.quad_char_key_assigns[char, char_2, key, key_2],
+                self.char_pos_assigns[char, key] + self.char_pos_assigns[char_2, key_2]
+                <= 1 + self.quad_char_pos_assigns[char, char_2, key, key_2],
                 f"IntegrableQuadAssign({char},{key_2},{key})",
             )
 
-        for key in self.keys:
+        for key in self.poss:
             constr[key] = self.model.addCons(
-                quicksum(self.char_key_assigns[char, key] for char in self.chars) == 1,
+                quicksum(self.char_pos_assigns[char, key] for char in self.chars.monos)
+                == 1,
                 f"AllPositionsAssignedOnce({key})",
             )
 
@@ -138,14 +129,14 @@ class KeyboardOptimization:
             quicksum(
                 costs * assigns
                 for (costs, assigns) in zip(
-                    self.char_key_costs.values(), self.char_key_assigns.values()
+                    self.char_pos_costs.values(), self.char_pos_assigns.values()
                 )
             )
             + quicksum(
                 costs * assigns
                 for (costs, assigns) in zip(
-                    self.quad_char_key_costs.values(),
-                    self.quad_char_key_assigns.values(),
+                    self.quad_char_pos_costs.values(),
+                    self.quad_char_pos_assigns.values(),
                 )
             ),
             "minimize",
@@ -169,10 +160,10 @@ class KeyboardOptimization:
         for (char, key) in self.char_key_assigns_keys:
             print(
                 f"({char}, {key}): "
-                f"{self.model.getVal(self.char_key_assigns[char, key])}, "
-                f"cost: {self.char_key_costs[char, key]}"
+                f"{self.model.getVal(self.char_pos_assigns[char, key])}, "
+                f"cost: {self.char_pos_costs[char, key]}"
             )
-            if self.model.getVal(self.char_key_assigns[char, key]) == 1:
+            if self.model.getVal(self.char_pos_assigns[char, key]) == 1:
                 solution_assignments.append((char, key))
 
         assert "('u', 'left_pinky_home')" in str(solution_assignments)
@@ -192,7 +183,7 @@ class KeyboardOptimization:
     @property
     def char_key_assigns_keys(self) -> Iterable[CharPosPair]:
         """An iterator for the pairs of (special) characters and corresponding keys"""
-        return product(self.chars, self.keys)
+        return product(self.chars.monos, self.poss)
 
     @property
     def quad_char_key_assigns_keys(self) -> Iterable[CharPosQuadruple]:
@@ -208,4 +199,4 @@ class KeyboardOptimization:
     @property
     def key_pairs(self) -> Iterable:
         """An iterator for all pairs of keys that are possible"""
-        return permutations(self.keys, 2)
+        return permutations(self.poss, 2)
-- 
GitLab


From 0cef67674447aff05abd215b856ae69067675491 Mon Sep 17 00:00:00 2001
From: Bjoern Ludwig <bjoern.ludwig@ptb.de>
Date: Sat, 12 Feb 2022 00:18:31 +0100
Subject: [PATCH 14/22] docs(README and pull_and_optimize): update information
 on how to use script

---
 README.md            | 11 ++++++-----
 pull_and_optimize.sh | 18 ++++++++++++++----
 2 files changed, 20 insertions(+), 9 deletions(-)

diff --git a/README.md b/README.md
index 98a6b66..0bc9c37 100644
--- a/README.md
+++ b/README.md
@@ -22,15 +22,16 @@ actual code can then be found in the [_src/ilp_keyboard_layout_optimization_ sub
 We included [a bash script _pull_and_optimize.sh_
 ](https://git.tu-berlin.de/blutub3d/ilp_keyboard_layout_optimization/-/blob/main/pull_and_optimize.sh)
 in our codebase to streamline a remote development workflow. We work on the code on a 
-computer, that is well equipped for that task. The committed and pushed code then 
+computer, that is well-equipped for that task. The committed and pushed code then 
 gets processed on another machine, which uses this script, to update its code base 
-and run the parameters handed over. It is designed to have the Python script name 
-for execution with a Python interpreter and PySCIPOpt as the only parameter, e.g.
+and run the parameters handed over. It is designed to be called without parameters
+to execute the _optimize_ module of the [latest version released on Test.PyPI.org
+](https://test.pypi.org/project/ilp-keyboard-layout-optimization/).
 
 ```shell
-$ ./pull_and_optimize.sh ilp_optimize.py
+$ ./pull_and_optimize.sh
 ```
 
 The execution requires the Docker image of our repository [docker_pyscipopt
-](https://github.com/BjoernLudwigPTB/docker_pyscipopt) to be built in advance but it 
+](https://github.com/BjoernLudwigPTB/docker_pyscipopt) to be built in advance, but it 
 could be easily adapted for a local installation of the SCIP Optimization Suite.
\ No newline at end of file
diff --git a/pull_and_optimize.sh b/pull_and_optimize.sh
index 2f718bb..fded983 100755
--- a/pull_and_optimize.sh
+++ b/pull_and_optimize.sh
@@ -2,9 +2,19 @@
 # This script was written to streamline a remote development workflow. We work on the
 # code on a computer, that is well equipped for that task. The committed and pushed
 # code then gets processed on another machine, which uses this script, to update its
-# code base and run the parameters handed over. It is designed to have the script
-# name for execution with a Python interpreter and PySCIPOpt as the only parameter, e.g.
-# $ ./pull_and_optimize.sh ilp_optimize.py
+# code base and run the parameters handed over. It is designed to be called without
+# parameters to execute the optimize module of the latest version released on
+# Test.PyPI.org.
+#
+# $ ./pull_and_optimize.sh
+#
+# Alternatively you could invoke any other command in the Python interpreter by
+# appending any command, normally appended to 'python <YOUR_COMMAND>' to the script.
+# i.e.
+#
+#
+# $ ./pull_and_optimize.sh -m pytest
+#
 # The execution requires the Docker image of our repository
 # https://github.com/BjoernLudwigPTB/docker_pyscipopt to be built in advance.
 SCRIPT_PATH="${BASH_SOURCE}"
@@ -20,5 +30,5 @@ cd ${SCRIPT_DIR}
 git pull
 docker build -t ilp_keyboard_layout_optimization:latest docker/
 docker run -it --rm ilp_keyboard_layout_optimization \
-  -m ilp_keyboard_layout_optimization.optimize
+  ${1:--m ilp_keyboard_layout_optimization.optimize}
 
-- 
GitLab


From f6e06934e12652967e73169bc7dedae8e920809b Mon Sep 17 00:00:00 2001
From: Bjoern Ludwig <bjoern.ludwig@ptb.de>
Date: Sat, 12 Feb 2022 00:19:39 +0100
Subject: [PATCH 15/22] build(setup): introduce extras_require: test

---
 setup.cfg | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/setup.cfg b/setup.cfg
index 482a061..f2431c4 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -41,3 +41,8 @@ python_requires = >=3.10
 
 [options.packages.find]
 where = src
+
+[options.extras_require]
+test =
+    hypothesis
+    pytest
-- 
GitLab


From 99bf72dad95e3d6e3271dd614201335976d59c12 Mon Sep 17 00:00:00 2001
From: Bjoern Ludwig <bjoern.ludwig@ptb.de>
Date: Sat, 12 Feb 2022 00:20:47 +0100
Subject: [PATCH 16/22] build(Dockerfile): introduce tester stage

---
 docker/Dockerfile | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/docker/Dockerfile b/docker/Dockerfile
index 1f18f65..e8a3730 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -1,4 +1,4 @@
-FROM pyscipopt:4.0.0
+FROM pyscipopt:4.0.0 AS optimizer
 
 USER root
 
@@ -8,3 +8,15 @@ RUN python -m pip install --upgrade \
         ilp-keyboard-layout-optimization
 
 USER user
+
+FROM optimizer as tester
+
+USER root
+
+RUN python -m pip install --upgrade \
+    pip \
+    -i https://test.pypi.org/simple/  \
+        ilp-keyboard-layout-optimization[test]
+
+USER user
+
-- 
GitLab


From 9dfe17aea29128190052f6ec605bd8f2c9399686 Mon Sep 17 00:00:00 2001
From: Bjoern Ludwig <bjoern.ludwig@ptb.de>
Date: Sat, 12 Feb 2022 00:30:22 +0100
Subject: [PATCH 17/22] chore(pull_and_optimize.sh): fail on error

---
 pull_and_optimize.sh | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/pull_and_optimize.sh b/pull_and_optimize.sh
index fded983..c4f06ac 100755
--- a/pull_and_optimize.sh
+++ b/pull_and_optimize.sh
@@ -25,10 +25,9 @@ while [ -L "${SCRIPT_PATH}" ]; do
 done
 SCRIPT_PATH="$(readlink -f "${SCRIPT_PATH}")"
 SCRIPT_DIR="$(cd -P "$(dirname -- "${SCRIPT_PATH}")" >/dev/null 2>&1 && pwd)"
-set -x
-cd ${SCRIPT_DIR}
-git pull
-docker build -t ilp_keyboard_layout_optimization:latest docker/
+cd ${SCRIPT_DIR} && \
+git pull && \
+docker build -t ilp_keyboard_layout_optimization:latest docker/ && \
 docker run -it --rm ilp_keyboard_layout_optimization \
   ${1:--m ilp_keyboard_layout_optimization.optimize}
 
-- 
GitLab


From 6ccba81e405d55e4f273c35c843de6ca2e0de2a0 Mon Sep 17 00:00:00 2001
From: Bjoern Ludwig <bjoern.ludwig@ptb.de>
Date: Sat, 12 Feb 2022 00:30:50 +0100
Subject: [PATCH 18/22] build(setup.cfg): increment alpha version to 0.0.2a8

---
 setup.cfg | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup.cfg b/setup.cfg
index f2431c4..fd24791 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
 [metadata]
 name = ilp_keyboard_layout_optimization
-version = 0.0.2a7
+version = 0.0.2a8
 description =
     The QAP variant of keyboard layout optimization, i.e. character to key assignments
 long_description = file: README.md
-- 
GitLab


From 200ca4bf531e4b849be33455e4495b715ce725f6 Mon Sep 17 00:00:00 2001
From: Bjoern Ludwig <bjoern.ludwig@ptb.de>
Date: Sat, 12 Feb 2022 00:38:36 +0100
Subject: [PATCH 19/22] build(Dockerfile): switch to PyPI for releases

---
 docker/Dockerfile | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/docker/Dockerfile b/docker/Dockerfile
index e8a3730..b70efcd 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -4,8 +4,7 @@ USER root
 
 RUN python -m pip install --upgrade \
     pip \
-    -i https://test.pypi.org/simple/  \
-        ilp-keyboard-layout-optimization
+    ilp-keyboard-layout-optimization
 
 USER user
 
@@ -14,9 +13,7 @@ FROM optimizer as tester
 USER root
 
 RUN python -m pip install --upgrade \
-    pip \
-    -i https://test.pypi.org/simple/  \
-        ilp-keyboard-layout-optimization[test]
+    ilp-keyboard-layout-optimization[test]
 
 USER user
 
-- 
GitLab


From 9688927ba6da22e286e26e19ac502fb3849278f9 Mon Sep 17 00:00:00 2001
From: Bjoern Ludwig <bjoern.ludwig@ptb.de>
Date: Sat, 12 Feb 2022 00:45:18 +0100
Subject: [PATCH 20/22] fix(optimize): correct import

---
 src/ilp_keyboard_layout_optimization/optimize.py | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/ilp_keyboard_layout_optimization/optimize.py b/src/ilp_keyboard_layout_optimization/optimize.py
index 1e10727..714ef97 100644
--- a/src/ilp_keyboard_layout_optimization/optimize.py
+++ b/src/ilp_keyboard_layout_optimization/optimize.py
@@ -12,11 +12,10 @@ $ python -m ilp_keyboard_layout_optimization.optimize
 We might add command line parameters at a later time. For now please edit the main
 function at the very bottom of this file to change inputs.
 """
+from ilp_keyboard_layout_optimization.data_aquisition.chars import Chars
 from ilp_keyboard_layout_optimization.ilp import KeyboardOptimization
 from ilp_keyboard_layout_optimization.type_aliases import LinCosts, QuadCosts
 
-from src.ilp_keyboard_layout_optimization.data_aquisition.chars import Chars
-
 
 def prepare_costs(
     optimization_problem: KeyboardOptimization,
-- 
GitLab


From 10367c8b89d041f6bbc73cb50a22175d89a2a44c Mon Sep 17 00:00:00 2001
From: Bjoern Ludwig <bjoern.ludwig@ptb.de>
Date: Sat, 12 Feb 2022 00:47:11 +0100
Subject: [PATCH 21/22] build(setup.cfg): increment alpha version to 0.0.2a9

---
 setup.cfg | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup.cfg b/setup.cfg
index fd24791..34bc04a 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
 [metadata]
 name = ilp_keyboard_layout_optimization
-version = 0.0.2a8
+version = 0.0.2a9
 description =
     The QAP variant of keyboard layout optimization, i.e. character to key assignments
 long_description = file: README.md
-- 
GitLab


From c9e7d546d19d375049ab88b66ac5a09b40fde7ed Mon Sep 17 00:00:00 2001
From: Bjoern Ludwig <bjoern.ludwig@ptb.de>
Date: Sat, 12 Feb 2022 00:49:39 +0100
Subject: [PATCH 22/22] build(Dockerfile): insert no cache

---
 pull_and_optimize.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pull_and_optimize.sh b/pull_and_optimize.sh
index c4f06ac..fb2aafe 100755
--- a/pull_and_optimize.sh
+++ b/pull_and_optimize.sh
@@ -27,7 +27,7 @@ SCRIPT_PATH="$(readlink -f "${SCRIPT_PATH}")"
 SCRIPT_DIR="$(cd -P "$(dirname -- "${SCRIPT_PATH}")" >/dev/null 2>&1 && pwd)"
 cd ${SCRIPT_DIR} && \
 git pull && \
-docker build -t ilp_keyboard_layout_optimization:latest docker/ && \
+docker build --no-cache -t ilp_keyboard_layout_optimization:latest docker/ && \
 docker run -it --rm ilp_keyboard_layout_optimization \
   ${1:--m ilp_keyboard_layout_optimization.optimize}
 
-- 
GitLab