Skip to content

Commit b7074d1

Browse files
committed
ReACTR++ implementation
1 parent 134bba2 commit b7074d1

122 files changed

Lines changed: 1181 additions & 125 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎rtac/ac_functionalities/config_gens.py‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,10 @@ class AbstractConfigGen(ABC):
2525
@abstractmethod
2626
def __init__(self, scenario: argparse.Namespace):
2727
"""Initialize configuration generation class."""
28-
...
2928

3029
@abstractmethod
3130
def generate(self) -> Configuration:
3231
"""Generates and returns configuration."""
33-
...
3432

3533

3634
class DefaultConfigGen(AbstractConfigGen):

‎rtac/ac_functionalities/logs.py‎

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,16 @@ def __init__(self, scenario: argparse.Namespace):
4343
+ str(scenario.ac).split('.')[1]
4444
if not os.path.isdir(self.log_path):
4545
os.makedirs(self.log_path)
46+
self.experimental = scenario.experimental
4647
if not scenario.resume:
47-
filelist = \
48-
[f for f in os.listdir(self.log_path) if f.endswith('.log')]
48+
if not self.experimental:
49+
filelist = \
50+
[f for f in os.listdir(self.log_path)
51+
if f.endswith('.log')]
52+
else:
53+
filelist = \
54+
[f for f in os.listdir(self.log_path)
55+
if f.endswith('.log') and 'tourn_0' not in f]
4956
for f in filelist:
5057
os.remove(os.path.join(self.log_path, f))
5158
self.objective_min = scenario.objective_min
@@ -167,7 +174,7 @@ def rtac_log(self, rtac_data: RTACData,
167174

168175
@abstractmethod
169176
def load_data(self) -> Any:
170-
...
177+
"""Loads the data of either last logged, or first tournament."""
171178

172179

173180
class RTACLogs(AbstractLogs):
@@ -226,10 +233,9 @@ def ranking_log(self, pool: dict[str: Configuration],
226233
self.scores_log.addHandler(s_fh)
227234
self.scores_log.info(str(scores))
228235

229-
def load_data(self) -> tuple[dict[str: Configuration],
230-
dict[str: tuple[int, int]],
231-
dict[str: Configuration],
232-
int]:
236+
def load_data(self, tourn_nr: int | None = None) \
237+
-> tuple[dict[str: Configuration], dict[str: tuple[int, int]],
238+
dict[str: Configuration], int]:
233239
"""Loads data necessary for resuming the algorithm configuration from
234240
last logged state of ReACTR.
235241
@@ -238,8 +244,9 @@ def load_data(self) -> tuple[dict[str: Configuration],
238244
:rtype: tuple[dict[str: Configuration], dict[str: tuple[int, int]],
239245
dict[str: Configuration], int]
240246
"""
241-
with open(f'{self.log_path}/tourn_nr.log') as f:
242-
tourn_nr = int(f.readline().strip())
247+
if tourn_nr is None:
248+
with open(f'{self.log_path}/tourn_nr.log') as f:
249+
tourn_nr = int(f.readline().strip())
243250

244251
with open(f'{self.log_path}/pool_tourn_{tourn_nr}.log', 'r') as f:
245252
pool = eval(f.readline())
@@ -251,6 +258,11 @@ def load_data(self) -> tuple[dict[str: Configuration],
251258
'r') as f:
252259
contender_ids = eval(f.readline())
253260

261+
if self.experimental:
262+
os.remove(f'{self.log_path}/pool_tourn_{tourn_nr}.log')
263+
os.remove(f'{self.log_path}/scores_tourn_{tourn_nr}.log')
264+
os.remove(f'{self.log_path}/contender_dict_tourn_{tourn_nr}.log')
265+
254266
contender_dict = {}
255267
for ci in contender_ids:
256268
contender_dict[ci] = pool[ci]

‎rtac/ac_functionalities/result_processing.py‎

Lines changed: 163 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
from abc import ABC, abstractmethod
55
from ac_functionalities.config_gens import DefaultConfigGen, RandomConfigGen
66
from ac_functionalities.ranking import trueskill
7-
from ac_functionalities.rtac_data import RTACData, Configuration
7+
from ac_functionalities.rtac_data import (
8+
RTACData,
9+
Configuration,
10+
ACMethod,
11+
InterimMeaning
12+
)
813
from ac_functionalities.logs import RTACLogs
914
from multiprocessing import Value
1015
import argparse
@@ -13,6 +18,7 @@
1318
import numpy as np
1419
import uuid
1520
import copy
21+
from scipy.stats import rankdata
1622

1723

1824
class contender(object):
@@ -42,7 +48,6 @@ def __init__(self, scenario: argparse.Namespace, logs: RTACLogs) -> None:
4248
@abstractmethod
4349
def init_data(self) -> None:
4450
"""Initialize tournament result processing data."""
45-
...
4651

4752
@abstractmethod
4853
def process_results(self, rtac_data: RTACData) -> None:
@@ -53,17 +58,14 @@ def process_results(self, rtac_data: RTACData) -> None:
5358
throughout the rtac modules.
5459
:type rtac_data: RTACData
5560
"""
56-
...
5761

5862
@abstractmethod
5963
def manage_pool(self) -> None:
6064
"""Replace contenders in pool if necessary."""
61-
...
6265

6366
@abstractmethod
6467
def select_contenders(self) -> None:
6568
"""Select contenders for next tournament/problem instance."""
66-
...
6769

6870
@abstractmethod
6971
def process_tourn(self, rtac_data: RTACData) -> Value:
@@ -74,7 +76,6 @@ def process_tourn(self, rtac_data: RTACData) -> Value:
7476
throughout the rtac modules.
7577
:type rtac_data: RTACData
7678
"""
77-
...
7879

7980
def get_contender_dict(self) -> dict[str: Configuration]:
8081
"""Returns contender_dict.
@@ -88,6 +89,7 @@ def get_contender_dict(self) -> dict[str: Configuration]:
8889

8990

9091
class ResultProcessing(AbstractResultProcessing):
92+
"""Process results of prvious tournament."""
9193

9294
def __init__(self, scenario: argparse.Namespace, logs: RTACLogs) -> None:
9395
"""Initialize tournament result processing class as necessary for
@@ -159,6 +161,32 @@ def init_data(self) -> dict[str: Configuration]:
159161

160162
return self.contender_dict
161163

164+
def get_winner(self, times: list[float], res: list[float]) \
165+
-> tuple[int, list[int]]:
166+
"""Get index of the winning configuration and ranks.
167+
168+
:param times: List of time results of previous tournament.
169+
:type times: list[float]
170+
:param res: List of objective results of previous tournament.
171+
:type res: list[float]
172+
:returns: Index of winner and ranks.
173+
:rtype: tuple[int, list[int]]
174+
"""
175+
if not self.scenario.objective_min:
176+
winner = times.index(min(times))
177+
178+
else:
179+
winner = res.index(min(res))
180+
181+
ranks = []
182+
for core in range(self.scenario.number_cores):
183+
if core == winner:
184+
ranks.append(1)
185+
else:
186+
ranks.append(2)
187+
188+
return winner, ranks
189+
162190
def process_results(self, rtac_data: RTACData) -> None:
163191
"""Perform tournament result processing necessary to replace contenders
164192
in pool and select contenders for next tournament/problem instance
@@ -179,11 +207,7 @@ def process_results(self, rtac_data: RTACData) -> None:
179207
res = list(self.rtac_data.ta_res[:])
180208
self.rtac_data.best_res = min(res)
181209

182-
if not self.scenario.objective_min:
183-
winner = times.index(min(times))
184-
185-
else:
186-
winner = res.index(min(res))
210+
winner, ranks = self.get_winner(times, res)
187211

188212
if self.scenario.verbosity in (1, 2):
189213
res = list(self.rtac_data.ta_res[:])
@@ -202,19 +226,13 @@ def process_results(self, rtac_data: RTACData) -> None:
202226

203227
# Set the results of the tournament
204228
individuals = [None] * self.scenario.number_cores
229+
contender_ids = list(self.contender_dict.keys())
205230
for core in range(self.scenario.number_cores):
206-
contender_ids = list(self.contender_dict.keys())
207-
rank = (self.scores[contender_ids[core]][0],
208-
self.scores[contender_ids[core]][1])
209-
individuals[core] = contender_ids[core]
231+
skill = (self.scores[contender_ids[core]][0],
232+
self.scores[contender_ids[core]][1])
210233
individuals[core] = contender()
211-
individuals[core].skill = rank
212-
213-
for core in range(self.scenario.number_cores):
214-
if core == winner:
215-
individuals[core].rank = 1
216-
else:
217-
individuals[core].rank = 2
234+
individuals[core].skill = skill
235+
individuals[core].rank = ranks[core]
218236

219237
if self.scenario.verbosity == 2:
220238
print('Contender', contender_ids[core], 'has the rank',
@@ -373,8 +391,130 @@ def process_tourn(self, rtac_data: RTACData) -> str:
373391
self.rtac_data.newtime = self.rtac_data.ta_res_time[0]
374392

375393
if self.scenario.verbosity in (1, 2):
394+
if self.rtac_data.winner.value == 0:
395+
winner = None
396+
else:
397+
winner = self.rtac_data.winner.value
376398
print('\n\n')
377-
print('Winner was', self.rtac_data.winner.value)
399+
print('Winner was', winner)
378400
print('\n\n')
379401

380402
return self.rtac_data.winner.value
403+
404+
405+
class ResultProcessingpp(ResultProcessing):
406+
"""Process results of prvious tournament."""
407+
408+
def duplicates(self, ranks: list[int], rank: float) -> list[int]:
409+
"""List the indices of the result in the results list.
410+
411+
:param ranks: List of objective results of previous tournament.
412+
:type ranks: list[int]
413+
:param rank: a single result.
414+
:type rank: float
415+
:returns: list of indices of this result in the results list.
416+
:rtype: list[int]
417+
"""
418+
return [i for i, x in enumerate(ranks) if x == rank]
419+
420+
def get_winner(self, times: list[float], res: list[float]) \
421+
-> tuple[int, list[int]]:
422+
"""Get index of the winning configuration including last known
423+
intermidiate outputs to break ties. Additionally output complete
424+
ranking to compute more detailed assessment with trueskill.
425+
426+
:param times: List of time results of previous tournament.
427+
:type times: list[float]
428+
:param res: List of objective results of previous tournament.
429+
:type res: list[float]
430+
:returns: Index of winner and ranks.
431+
:rtype: tuple[int, list[int]]
432+
"""
433+
if not self.scenario.objective_min:
434+
winner = times.index(min(times))
435+
436+
else:
437+
winner = res.index(min(res))
438+
439+
interim_sorted = [[self.rtac_data.interim[j][i]
440+
for j in range(self.scenario.number_cores)]
441+
for i, _ in enumerate(self.rtac_data.interim[0])]
442+
443+
interim_sorted = np.array(interim_sorted)
444+
interim_sorted = interim_sorted.astype(float)
445+
446+
for i, isort in enumerate(interim_sorted):
447+
if self.rtac_data.interim_meaning[i] is \
448+
InterimMeaning.decrease:
449+
interim_sorted[i] = rankdata(isort,
450+
method='dense',
451+
nan_policy="propagate")
452+
elif self.rtac_data.interim_meaning[i] is \
453+
InterimMeaning.increase:
454+
interim_sorted[i] = rankdata([-1 * i if i is not None
455+
else None for i in isort],
456+
method='dense',
457+
nan_policy="propagate")
458+
459+
ranks = [0 for core in range(self.scenario.number_cores)]
460+
461+
for _, isort in enumerate(interim_sorted):
462+
for r, _ in enumerate(ranks):
463+
ranks[r] += isort[r]
464+
465+
if self.scenario.objective_min:
466+
res_ranks = rankdata(res, method='dense', nan_policy="propagate")
467+
468+
duplicates = []
469+
470+
for rank in sorted(set(res_ranks)):
471+
duplicates.append(self.duplicates(res_ranks, rank))
472+
473+
for duplicate in duplicates:
474+
if len(duplicate) > 1:
475+
interim_ranks = [ranks[dup] for dup in duplicate]
476+
tie_winner = \
477+
duplicate[interim_ranks.index(min(interim_ranks))]
478+
interim_ranks = rankdata(interim_ranks, method='dense')
479+
interim_ranks -= min(interim_ranks)
480+
for d, ir in zip(duplicate, interim_ranks):
481+
for r, _ in enumerate(res_ranks):
482+
if d == r and r != winner and r != tie_winner:
483+
res_ranks[r] += interim_ranks[ir]
484+
else:
485+
res_ranks[r] += max(interim_ranks)
486+
487+
ranks = res_ranks
488+
489+
ranks = rankdata(ranks, method='dense')
490+
491+
ranks[winner] = 0
492+
493+
return winner, ranks
494+
495+
496+
def processing_factory(scenario, logs) -> AbstractResultProcessing:
497+
"""Class factory to return the initialized class with data structures
498+
appropriate to the RTAC method scenario.ac.
499+
500+
:param scenario: Namespace containing all settings for the RTAC.
501+
:type scenario: argparse.Namespace
502+
:returns: Inititialized BaseTARunner object matching the RTAC method of
503+
the scenario.
504+
:rtype: BaseTARunner
505+
"""
506+
if scenario.ac in (ACMethod.ReACTR, ACMethod.CPPL):
507+
return ResultProcessing(scenario, logs)
508+
509+
elif scenario.ac == ACMethod.ReACTRpp:
510+
return ResultProcessingpp(scenario, logs)
511+
512+
'''
513+
514+
elif scenario.ac == ACMethod.GRAYBOX:
515+
return GBTARunner(scenario)
516+
'''
517+
518+
519+
if __name__ == "__main__":
520+
pass

0 commit comments

Comments
 (0)