make timeout a parameter when solving seeds

This commit is contained in:
Maximilian Keßler 2024-10-10 13:40:31 +02:00
parent e5efe517ee
commit 397dd9d641
2 changed files with 66 additions and 45 deletions

View file

@ -98,9 +98,8 @@ def subcommand_download(
logger.info("Successfully exported games for all variants") logger.info("Successfully exported games for all variants")
def subcommand_solve(var_id): def subcommand_solve(var_id, timeout: int):
instance_finder.solve_unknown_seeds(var_id, '') instance_finder.solve_unknown_seeds(var_id, timeout)
def subcommand_gen_config(): def subcommand_gen_config():
@ -164,7 +163,8 @@ def add_config_gen_subparser(subparsers):
def add_solve_subparser(subparsers): def add_solve_subparser(subparsers):
parser = subparsers.add_parser('solve', help='Seed solving') parser = subparsers.add_parser('solve', help='Seed solving')
parser.add_argument('--var_id', type=int, help='Variant id to solve instances from.', default=0) parser.add_argument('var_id', type=int, help='Variant id to solve instances from.', default=0)
parser.add_argument('--timeout', '-t', type=int, help='Timeout [s] for individual seeds.', default=150)
def add_decompress_subparser(subparsers): def add_decompress_subparser(subparsers):
parser = subparsers.add_parser('decompress', help='Decompress a hanab.live JSON-encoded replay link') parser = subparsers.add_parser('decompress', help='Decompress a hanab.live JSON-encoded replay link')

View file

@ -9,6 +9,7 @@ import time
import hanabi.hanab_game import hanabi.hanab_game
from hanabi import logger from hanabi import logger
from hanabi.hanab_game import GameState
from hanabi.solvers.sat import solve_sat from hanabi.solvers.sat import solve_sat
from hanabi import database from hanabi import database
from hanabi.live import download_data from hanabi.live import download_data
@ -17,7 +18,7 @@ from hanabi import hanab_game
from hanabi.solvers import greedy_solver from hanabi.solvers import greedy_solver
from hanabi.solvers import deck_analyzer from hanabi.solvers import deck_analyzer
from hanabi.live import variants from hanabi.live import variants
from hanabi.database.games_db_interface import load_deck from hanabi.database.games_db_interface import load_deck, load_instance
MAX_PROCESSES = 3 MAX_PROCESSES = 3
@ -91,12 +92,12 @@ def get_decks_for_all_seeds():
mutex = threading.Lock() mutex = threading.Lock()
def solve_instance(instance: hanab_game.HanabiInstance): def solve_instance(instance: hanab_game.HanabiInstance):
# first, sanity check on running out of pace # first, sanity check on running out of pace
result = deck_analyzer.analyze(instance) result = deck_analyzer.analyze(instance)
# print(result)
if len(result) != 0: if len(result) != 0:
logger.info("found infeasible deck by foreward analysis") logger.verbose("found infeasible deck by preliminary analysis")
return False, None, None return False, None, None
for num_remaining_cards in [0, 20]: for num_remaining_cards in [0, 20]:
# logger.info("trying with {} remaining cards".format(num_remaining_cards)) # logger.info("trying with {} remaining cards".format(num_remaining_cards))
@ -130,54 +131,65 @@ def solve_instance(instance: hanab_game.HanabiInstance):
return a, b, instance.draw_pile_size return a, b, instance.draw_pile_size
@pebble.concurrent.process(timeout=150)
def solve_seed_with_timeout(seed, num_players, deck, var_name: Optional[str] = None): def solve_seed(seed, num_players, deck, var_name: str, timeout: Optional[int] = 150):
try: try:
logger.verbose("Starting to solve seed {}".format(seed)) @pebble.concurrent.process(timeout=timeout)
t0 = time.perf_counter() def solve_seed_with_timeout(seed, num_players, deck, var_name: Optional[str] = None):
solvable, solution, num_remaining_cards = solve_instance(hanab_game.HanabiInstance(deck, num_players)) try:
t1 = time.perf_counter() logger.verbose("Starting to solve seed {}".format(seed))
logger.verbose("Solved instance {} in {} seconds: {}".format(seed, round(t1 - t0, 2), solvable)) t0 = time.perf_counter()
solvable, solution, num_remaining_cards = solve_instance(hanab_game.HanabiInstance(deck, num_players))
t1 = time.perf_counter()
logger.verbose("Solved instance {} in {} seconds: {}".format(seed, round(t1 - t0, 2), solvable))
mutex.acquire() mutex.acquire()
if solvable is not None: if solvable is not None:
database.cur.execute("UPDATE seeds SET feasible = (%s) WHERE seed = (%s)", (solvable, seed)) time_ms = round((t1 - t0) * 1000)
database.cur.execute("UPDATE seeds SET (feasible, solve_time_ms) = (%s, %s) WHERE seed = (%s)",
(solvable, time_ms, seed))
database.conn.commit()
mutex.release()
if solvable:
logger.verbose("Success with {} cards left in draw by greedy solver on seed {}: {}\n".format(
num_remaining_cards, seed, compress.link(solution))
)
elif not solvable:
logger.debug("seed {} was not solvable".format(seed))
logger.debug('{}-player, seed {:10}, {}\n'.format(num_players, seed, var_name))
elif solvable is None:
logger.verbose("seed {} skipped".format(seed))
else:
raise Exception("Programming Error")
except Exception as e:
print("exception in subprocess:")
traceback.print_exc()
f = solve_seed_with_timeout(seed, num_players, deck, var_name)
try:
return f.result()
except TimeoutError:
logger.verbose("Solving on seed {} timed out".format(seed))
mutex.acquire()
database.cur.execute("UPDATE seeds SET solve_time_ms = %s WHERE seed = (%s)", (1000 * timeout, seed))
database.conn.commit() database.conn.commit()
mutex.release() mutex.release()
return
if solvable == True:
logger.verbose("Success with {} cards left in draw by greedy solver on seed {}: {}\n".format(
num_remaining_cards, seed, compress.link(solution))
)
elif solvable == False:
logger.debug("seed {} was not solvable".format(seed))
logger.debug('{}-player, seed {:10}, {}\n'.format(num_players, seed, var_name))
elif solvable is None:
logger.verbose("seed {} skipped".format(seed))
else:
raise Exception("Programming Error")
except Exception as e: except Exception as e:
print("exception in subprocess:") print("exception in subprocess:")
traceback.print_exc() traceback.print_exc()
def solve_seed(seed, num_players, deck, var_name: Optional[str] = None): def solve_unknown_seeds(variant_id, timeout: Optional[int] = 150):
f = solve_seed_with_timeout(seed, num_players, deck, var_name) variant_name = variants.variant_name(variant_id)
try:
return f.result()
except TimeoutError:
logger.verbose("Solving on seed {} timed out".format(seed))
return
def solve_unknown_seeds(variant_id, variant_name: Optional[str] = None):
database.cur.execute( database.cur.execute(
"SELECT seeds.seed, num_players, array_agg(suit_index order by deck_index asc), array_agg(rank order by deck_index asc) " "SELECT seeds.seed, num_players, array_agg(suit_index order by deck_index asc), array_agg(rank order by deck_index asc) "
"FROM seeds " "FROM seeds "
"INNER JOIN decks ON seeds.seed = decks.seed " "INNER JOIN decks ON seeds.seed = decks.seed "
"WHERE variant_id = (%s) AND feasible IS NULL AND num_players = 2" "WHERE variant_id = (%s) AND num_players = 2 AND class = 1 AND feasible is null "
"GROUP BY seeds.seed ", "GROUP BY seeds.seed order by num",
(variant_id,) (variant_id,)
) )
res = database.cur.fetchall() res = database.cur.fetchall()
@ -189,8 +201,17 @@ def solve_unknown_seeds(variant_id, variant_name: Optional[str] = None):
deck.append(hanabi.hanab_game.DeckCard(suit, rank)) deck.append(hanabi.hanab_game.DeckCard(suit, rank))
data.append((seed, num_players, deck)) data.append((seed, num_players, deck))
"""
with alive_progress.alive_bar(len(res), title='Seed solving on {}'.format(variant_name)) as bar:
for d in data:
solve_seed(d[0], d[1], d[2], variant_name, timeout)
bar()
return
"""
with concurrent.futures.ProcessPoolExecutor(max_workers=MAX_PROCESSES) as executor: with concurrent.futures.ProcessPoolExecutor(max_workers=MAX_PROCESSES) as executor:
fs = [executor.submit(solve_seed, d[0], d[1], d[2], variant_name) for d in data] fs = [executor.submit(solve_seed, d[0], d[1], d[2], variant_name, timeout) for d in data]
with alive_progress.alive_bar(len(res), title='Seed solving on {}'.format(variant_name)) as bar: with alive_progress.alive_bar(len(res), title='Seed solving on {}'.format(variant_name)) as bar:
for f in concurrent.futures.as_completed(fs): for f in concurrent.futures.as_completed(fs):
bar() bar()