Coverage for moptipyapps / ttp / game_plan.py: 93%
45 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-11 04:40 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-11 04:40 +0000
1"""
2A game plan assigns teams to games.
4A game plan is a two-dimensional matrix `G`. The rows are the time slots.
5There is one column for each time. If `G` has value `v` at row `i` and
6column `j`, then this means:
8- at the time slot `i` ...
9- the team with name `j+1` plays
10 + no team if `v == 0`,
11 + *at home* against the team `v` if `v > 0`, i.e., team `v` travels
12 to the home stadium of team `j+1`
13 + *away* against the team `-v` if `v < 0`, i.e., team `j+1` travels
14 to the home stadium of team `-v` and plays against them there
16Indices in matrices are zero-based, i.e., the lowest index for a row `i` is
17`0` and the lowest index for a column `j` is also `0`. However, team names
18are one-based, i.e., that with `1`. Therefore, we need to translate the
19zero-based column index `j` to a team name by adding `1` to it.
21This is just a numerical variant of the game plan representation given at
22<https://robinxval.ugent.be/RobinX/travelRepo.php>. Indeed, the `str(...)`
23representation of a game plan is exactly the table shown there.
25Of course, if `G[i, j] = v`, then `G[i, v - 1] = -(j + 1)` should hold if
26`v > 0`, for example. Vice versa, if `v < 0` and `G[i, j] = v`, then
27`G[i, (-v) - 1] = j + 1` should hold. Such constraints are checked by the
28:mod:`~moptipyapps.ttp.errors` objective function.
30The corresponding space implementation,
31:mod:`~moptipyapps.ttp.game_plan_space`, offers the functionality to convert
32strings to game plans as well as to instantiate them in a black-box algorithm.
33"""
35from io import StringIO
36from typing import Final
38import numpy as np
39from moptipy.api.component import Component
40from moptipy.utils.logger import CSV_SEPARATOR
41from pycommons.types import type_error
43from moptipyapps.ttp.instance import Instance
46class GamePlan(Component, np.ndarray):
47 """A game plan, i.e., a solution to the Traveling Tournament Problem."""
49 #: the TTP instance
50 instance: Instance
52 def __new__(cls, instance: Instance) -> "GamePlan":
53 """
54 Create a solution record for the Traveling Tournament Problem.
56 :param cls: the class
57 :param instance: the solution record
58 """
59 if not isinstance(instance, Instance):
60 raise type_error(instance, "instance", Instance)
62 n: Final[int] = instance.n_cities # the number of teams
63 # each team plays every other team 'rounds' times
64 n_days: Final[int] = (n - 1) * instance.rounds
65 obj: Final[GamePlan] = super().__new__(
66 cls, (n_days, n), instance.game_plan_dtype)
67 #: the TTP instance
68 obj.instance = instance
69 return obj
71 def __str__(self):
72 """
73 Convert the game plan to a compact string.
75 The first line of the output is a flattened version of this matrix
76 with the values being separated by `;`. Then we place an empty line.
78 We then put a more easy-to-read representation and follow the pattern
79 given at https://robinxval.ugent.be/RobinX/travelRepo.php, which is
80 based upon the notation by Easton et al. Here, first, a row with the
81 team names separated by spaces is generated. Then, each row contains
82 the opponents of these teams, again separated by spaces. If an
83 opponent plays at their home, this is denoted by an `@`.
84 If a team has no scheduled opponent, then this is denoted as `-`.
86 :return: the compact string
87 """
88 csv: Final[str] = CSV_SEPARATOR
89 sep: str = ""
90 teams: Final[tuple[str, ...]] = self.instance.teams
91 len(teams)
93 with StringIO() as sio:
94 for k in self.flatten():
95 sio.write(sep)
96 sio.write(str(k))
97 sep = csv
99 sio.write("\n\n")
101 sep = ""
102 for t in teams:
103 sio.write(sep)
104 sio.write(t)
105 sep = " "
107 for row in self:
108 sio.write("\n")
109 sep = ""
110 for d in row:
111 sio.write(sep)
112 if d < 0:
113 sio.write(f"@{teams[-d - 1]}")
114 elif d > 0:
115 sio.write(teams[d - 1])
116 else:
117 sio.write("-")
118 sep = " "
120 return sio.getvalue()