Coverage for moptipyapps / spoc / spoc_4 / challenge_1 / beginner / instance.py: 85%
74 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-03 04:37 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-03 04:37 +0000
1"""
2The instance of the Luna Tomato Logistics beginner problem.
4>>> inst1 = Instance("matching-i")
5>>> inst1.n
625000
7>>> inst1.shape
8(25000, 4)
9>>> list(map(int, inst1[0, :]))
10[3390, 4454, 3664, 267]
11>>> inst1[0, 1]
12np.int64(4454)
13>>> inst1.penalty
14125526621
15>>> inst1.lengths
16(5000, 5000, 5000)
17>>> inst1.name
18'matching-i'
19>>> inst1 is Instance("matching-i")
20True
22>>> inst2 = Instance("matching-ii")
23>>> inst2.n
2492103
25>>> inst2.shape
26(92103, 4)
27>>> list(map(int, inst2[0, :]))
28[5559, 5444, 3794, 9723]
29>>> inst2[0, 1]
30np.int64(5444)
31>>> inst2.penalty
32460191177
33>>> inst2.lengths
34(10000, 10000, 10000)
35>>> inst2.name
36'matching-ii'
37"""
39from typing import Any, Callable, Final, Generator, cast
41import numpy as np
42from moptipy.api.component import Component
43from moptipy.utils.logger import KeyValueLogSection
44from moptipy.utils.nputils import int_range_to_dtype
45from pycommons.types import check_to_int_range
47from moptipyapps.spoc.spoc_4.challenge_1.beginner.data import (
48 open_resource_stream,
49)
52class Instance(np.ndarray, Component):
53 """The instances of the Luna Tomato Logistics beginner problem."""
55 #: set the result name
56 name: str
57 #: set the penalty
58 penalty: int
59 #: the number of orbit pairs
60 n: int
61 #: the number of values per column
62 lengths: tuple[int, int, int]
64 def __new__(cls, name: str) -> "Instance": # noqa: PYI034
65 """Create the single instance."""
66 name = str.strip(name)
67 if str.__len__(name) <= 0:
68 raise ValueError("Invalid name.")
69 meth: Final = Instance.__new__
70 attr: Final[str] = f"__{name}_data"
71 if hasattr(meth, attr):
72 return cast("Instance", getattr(meth, attr))
74 data: list[list] = []
75 power: int = 0
76 with open_resource_stream(f"{name}.txt") as stream:
77 for oline in stream:
78 line = str.strip(oline)
79 if str.__len__(line) <= 0:
80 continue
81 splt: list[str] = line.split()
82 if list.__len__(splt) != 4:
83 raise ValueError(f"Invalid format: {oline!r}.")
84 cap: str = splt[3]
85 data.append([
86 check_to_int_range(splt[0], "e") - 1,
87 check_to_int_range(splt[1], "l") - 1,
88 check_to_int_range(splt[2], "d") - 1,
89 cap])
90 li: int = cap.rfind(".")
91 if li >= 0:
92 power = max(power, len(cap) - li)
94 power -= 1
95 if power != 3:
96 raise ValueError(f"Power must be 3, but is {power}.")
97 power = 10 ** power
98 for row in data:
99 row[-1] = round(float(row[-1]) * power)
100 if any(r < 0 for r in row):
101 raise ValueError(f"Invalid row: {row}.")
103 penalty: Final[int] = sum(r[3] for r in data) + 1
104 if penalty <= 1:
105 raise ValueError(
106 f"Maximum must be positive, but is {penalty - 1}.")
107 lengths: Final[tuple[int, int, int]] = (
108 max(r[0] for r in data) + 1,
109 max(r[1] for r in data) + 1,
110 max(r[2] for r in data) + 1)
112 use_shape: tuple[int, int] = (len(data), 4)
113 dt: np.dtype = int_range_to_dtype(
114 0, (3 * penalty * use_shape[0]) + penalty - 1)
116 result: Instance = super().__new__(Instance, use_shape, dt)
118 for i, row in enumerate(data):
119 for j, val in enumerate(row):
120 result[i, j] = val
121 if val != result[i, j]:
122 raise ValueError("Error during conversion.")
124 #: set the result name
125 result.name = name
126 #: set the penalty
127 result.penalty = penalty
128 #: the number of orbit pairs
129 result.n = use_shape[0]
130 #: the number of values per column
131 result.lengths = lengths
132 setattr(meth, attr, result)
133 return result
135 def __str__(self) -> str:
136 """
137 Get the name of this instance.
139 :returns: the name of this instance.
140 """
141 return self.name
143 def log_parameters_to(self, logger: KeyValueLogSection) -> None:
144 """
145 Log all parameters of this component as key-value pairs.
147 :param logger: the logger for the parameters
148 """
149 super().log_parameters_to(logger)
150 logger.key_value("penalty", self.penalty)
151 logger.key_value("lengths", ";".join(map(str, self.lengths)))
153 @staticmethod
154 def list_resources() -> tuple[str, str]:
155 """
156 Get all the beginner problem instances.
158 :return: the problem instance names
160 >>> Instance.list_resources()
161 ('matching-i', 'matching-ii')
163 >>> for ix in Instance.list_resources():
164 ... print(Instance(ix).name)
165 matching-i
166 matching-ii
167 """
168 return ("matching-i", "matching-ii")
170 @staticmethod
171 def list_instances() -> Generator[Callable[[], Any], None, None]:
172 """
173 Get an iterable of all instances.
175 :return: the iterable
177 >>> for ix in Instance.list_instances():
178 ... print(ix().name)
179 matching-i
180 matching-ii
181 """
182 yield lambda: Instance("matching-i")
183 yield lambda: Instance("matching-ii")