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

1""" 

2The instance of the Luna Tomato Logistics beginner problem. 

3 

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 

21 

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""" 

38 

39from typing import Any, Callable, Final, Generator, cast 

40 

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 

46 

47from moptipyapps.spoc.spoc_4.challenge_1.beginner.data import ( 

48 open_resource_stream, 

49) 

50 

51 

52class Instance(np.ndarray, Component): 

53 """The instances of the Luna Tomato Logistics beginner problem.""" 

54 

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] 

63 

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)) 

73 

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) 

93 

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}.") 

102 

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) 

111 

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) 

115 

116 result: Instance = super().__new__(Instance, use_shape, dt) 

117 

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.") 

123 

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 

134 

135 def __str__(self) -> str: 

136 """ 

137 Get the name of this instance. 

138 

139 :returns: the name of this instance. 

140 """ 

141 return self.name 

142 

143 def log_parameters_to(self, logger: KeyValueLogSection) -> None: 

144 """ 

145 Log all parameters of this component as key-value pairs. 

146 

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))) 

152 

153 @staticmethod 

154 def list_resources() -> tuple[str, str]: 

155 """ 

156 Get all the beginner problem instances. 

157 

158 :return: the problem instance names 

159 

160 >>> Instance.list_resources() 

161 ('matching-i', 'matching-ii') 

162 

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") 

169 

170 @staticmethod 

171 def list_instances() -> Generator[Callable[[], Any], None, None]: 

172 """ 

173 Get an iterable of all instances. 

174 

175 :return: the iterable 

176 

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")