Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
DimitriWeiss committed Jul 26, 2023
0 parents commit ed04302
Show file tree
Hide file tree
Showing 46 changed files with 2,397 additions and 0 deletions.
28 changes: 28 additions & 0 deletions .github/workflows/python-unittest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@


name: Python selector

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: "3.8"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Test with unittest
run: |
python -m unittest discover test
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Byte-compiled / optimized / DLL files
__pycache__/

# json files
*.json

# pyenv
.python-version

# SMAC files
smac3_output/
187 changes: 187 additions & 0 deletions AC_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
"""Generic algorithm configuration interface for unified planning."""
import unified_planning
from unified_planning.environment import get_environment
from unified_planning.shortcuts import *
from utils.pcs_transform import transform_pcs
from utils.ac_feedback import qaul_feedback, runtime_feedback
import copy

from ConfigSpace.read_and_write import pcs


class GenericACInterface():
"""Generic AC interface."""

def __init__(self):
"""Initialize generic interface."""
self.environment = get_environment()
self.available_engines = self.get_available_engines()
self.engine_param_spaces = {}
self.engine_param_types = {}

def get_available_engines(self):
"""Get planning engines installed in up."""
factory = unified_planning.engines.factory.Factory(self.environment)

return factory.engines

def read_engine_pcs(self, engines, pcs_dir):
"""Read in pcs file for engine.
parameter engines: list of str, names of engines.
parameter pcs_dir: str, path to directory with pcs files.
"""
if pcs_dir[-1] != '/':
pcs_dir = pcs_dir + '/'

for engine in engines:
with open(pcs_dir + engine + '.pcs', 'r') as f:
self.engine_param_spaces[engine] = pcs.read(f)

with open(pcs_dir + engine + '.pcs', 'r') as f:
lines = f.readlines()
self.engine_param_types[engine] = {}
for line in lines:
if '# FLAGS #' in line:
self.engine_param_types[engine]['-'+line.split(' ')[0]] = 'FLAGS'
elif '# FLAG' in line:
self.engine_param_types[engine]['-'+line.split(' ')[0]] = 'FLAG'

def transform_conf_from_ac(self, ac_tool, engine, configuration):
"""Transform configuration to up engine format.
parameter ac_tool: str, name of AC tool in use.
parameter engines: list of str, names of engines.
parameter configuration: list of str, names of engines.
return config: dict, configuration.
"""
if ac_tool == 'SMAC':
config = transform_pcs(engine, configuration)
if engine == 'lpg':
del_list = []
add_list = []
for pname, pvalue in config.items():
if pname in self.engine_param_types:
if self.engine_param_types[pname] == 'FLAGS':
del_list.append(pname)
flag_pname = pname + '=' + config[pname]
add_list.append(flag_pname)
elif self.engine_param_types[pname] == 'FLAG':
if config[pname] == '1':
config[pname] = ''
else:
del_list.append(pname)

for dl in del_list:
del config[dl]

for al in add_list:
config[al] = ''

if engine == 'fast-downward':

evals = ['eager_greedy', 'eager_wastar',
'lazy_greedy', 'lazy_wastar']
open_eval = ['epsilon_greedy', 'single']
open_evals = ['pareto', 'tiebreaking',
'type_based']
pruning = ['atom_centric_stubborn_sets']

search_option = config['fast_downward_search_config'] + '('

if 'evaluator' in config:
if config['evaluator'] in evals:
search_option += '[' + str(config['evaluator']) + '()], '
else:
search_option += str(config['evaluator']) + '(), '

if 'open' in config:
if config['open'] not in open_eval and \
config['open'] not in open_evals:
search_option += '[' + str(config['open']) + '()], '
elif config['open'] in open_eval:
search_option += '[' + str(config['open']) + '(' + str(config['open_list_evals']) + ')], '
elif config['open'] in open_evals:
search_option += '[' + str(config['open']) + '([]' + str(config['open_list_evals']) + '])], '

if config['evaluator'] == 'ehc':
search_option += 'preferred_usage=' + str(config['ehc_preferred_usage']) + ','

if 'reopen_closed' in config:
search_option += 'reopen_closed=' + str(config['reopen_closed']) + ','

if 'randomize_successors' in config:
search_option += 'randomize_successors=' + str(config['randomize_successors']) + ','

if 'pruning' in config:
if config['pruning'] in pruning:
search_option += 'pruning=' + str(config['pruning']) + '(use_sibling_shortcut=' \
+ config['atom_centric_stubborn_sets_use_sibling'] + \
',atom_selection_strategy=' + config['atom_selection_strategy'] + '(), '
else:
search_option += 'pruning=' + str(config['pruning']) + '(),'

search_option += 'cost_type=' + config['cost_type'] + ')'
search_option = search_option.replace(" ", "")

config = {'fast_downward_search_config': search_option}

return config

def transform_param_space(self, ac_tool, pcs):
"""Transform configuration to AC tool format.
parameter pcs: ConfigSpace object, parameter space.
parameter ac_tool: str, AC tool in use.
return param_space: transformed parameter space.
"""
if ac_tool == 'SMAC': # SMAC uses ConfigSpace
param_space = pcs

return param_space

def get_feedback(self, engine, fbtype, result):
"""Get feedback from planning engine after run.
parameter engine: str, name of planning engine.
parameter fbtype: str, type of feedback
parameter result: object, planning result.
"""
if fbtype == 'quality':
feedback = qaul_feedback(engine, result)
if fbtype == 'runtime':
feedback = runtime_feedback(engine, result)
if fbtype == 'gray_box':
feedback = None

return feedback

def run_engine_config(self, ac_tool, config, metric, engine, plantype, problem):
"""Execute configurated engine run.
paremer config: configuration of engine.
parameter engine: str, engine name.
parameter plantype: str, type of planning.
return feedback: result from configurated engine run.
"""
if plantype == 'OneshotPlanner':
config = self.transform_conf_from_ac(ac_tool, engine, config)
with OneshotPlanner(name=engine,
params=config) as planner:
try:
result = planner.solve(problem)
if (result.status ==
up.engines.PlanGenerationResultStatus.
SOLVED_SATISFICING):
print("Result found.\n")
else:
print("No plan found.\n")
feedback = self.get_feedback(engine, metric, result)
except:
print("No plan found.\n")
feedback = None

return feedback
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Algorithm Configuration for the AIPlan4EU Unified Planning
131 changes: 131 additions & 0 deletions configurators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
"""Functionalities for managing and calling configurators."""
from smac import Scenario
from smac import AlgorithmConfigurationFacade

from unified_planning.io import PDDLReader

from AC_interface import *

import json

class Configurator():
"""Configurator functions."""

def __init__(self):
"""Initialize generic interface."""
self.incumbent = None
self.instance_features = {}
self.train_set = {}
self.test_set = {}
self.reader = PDDLReader()
self.metric = None
self.crash_cost = 0
self.ac = None

def get_instance_features(self, instance_features=None):
self.instance_features = instance_features
print('\nSetting instance features.\n')

def set_training_instance_set(self, train_set):
self.train_set = train_set
print('\nSetting training instance set.\n')

def set_test_instance_set(self, test_set):
self.test_set = test_set
print('\nSetting testing instance set.\n')

def get_feedback_function(self, ac_tool, gaci, engine, metric, mode):
self.metric = metric
if ac_tool == 'SMAC':
def planner_feedback(config, instance, seed=0):
instance_p = f'{instance}'
domain_path = instance_p.rsplit('/', 1)[0]
out_file = instance_p.rsplit('/', 1)[1]
domain = f'{domain_path}/domain.pddl'
pddl_problem = self.reader.parse_problem(f'{domain}',
f'{instance_p}')
# print(pddl_problem)
feedback = \
gaci.run_engine_config(ac_tool,
config,
metric,
engine,
mode,
pddl_problem)
if feedback is not None:
# SMAC always minimizes
if metric == 'quality':
return -feedback
elif metric == 'runtime':
return feedback
else:
return None

print('\nSMAC feedback function is generated.\n')

return planner_feedback

def set_scenario(self, ac_tool, param_space, configuration_time=120,
n_trials=400, min_budget=1, max_budget=3, crash_cost=0,
planner_timelimit=30, n_workers=1, instances=[],
instance_features=None):
if not instances:
instances = self.train_set
self.crash_cost = crash_cost
if ac_tool == 'SMAC':
scenario = Scenario(
param_space,
walltime_limit=configuration_time, # We want to optimize for <configuration_time> seconds
n_trials=n_trials, # We want to try max <n_trials> different trials
min_budget=min_budget, # Use min <min_budget> instance
max_budget=max_budget, # Use max <max_budget> instances
deterministic=True, # Not stochastic algorithm
crash_cost=crash_cost, # Cost of algorithm crashing -> AC metric to evaluate configuration
trial_walltime_limit=planner_timelimit, # Max time for algorithm to run
use_default_config=True, # include default config
n_workers=n_workers, # Number of parallel runs
instances=instances, # List of training instances
instance_features=instance_features # Dict of instance features
)
print('\nSMAC scenario is set.\n')

self.scenario = scenario

def optimize(self, ac_tool, feedback_function=None, gray_box=False):
if ac_tool == 'SMAC':
print('\nStarting Parameter optimization\n')
ac = AlgorithmConfigurationFacade(
self.scenario,
feedback_function,
overwrite=True,
)

self.incumbent = ac.optimize()

print('\nBest Configuration found is:\n', self.incumbent)

return self.incumbent, ac

def evaluate(self, feedback_function, incumbent, instances=[]):
if not instances:
instances = self.test_set
nr_inst = len(instances)
avg_f = 0
for inst in instances:
f = feedback_function(incumbent, inst, seed=0)
if f is not None and self.metric == 'quality':
f = -f
print(f'\nFeedback on instance {inst}:\n\n', f, '\n')
if f is not None:
avg_f += f
else:
avg_f += self.crash_cost
nr_inst -= 1
avg_f = avg_f / nr_inst
print('\nAverage performance:', avg_f, '\n')

def save_config(self, path, config, gaci, ac_tool, engine):
config = gaci.transform_conf_from_ac(ac_tool, engine, config)
with open(f'{path}/incumbent_{engine}.json', 'w') as f:
json.dump(config, f)
print(f'\nSaved best configuration in {path}/incumbent_{engine}.json\n')
2 changes: 2 additions & 0 deletions engine_pcs/enhsp.pcs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
heuristic {hadd, hmax, blind} [hadd]
search_algorithm {gbfs, astar, wastar} [gbfs]
30 changes: 30 additions & 0 deletions engine_pcs/fast-downward.pcs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
fast_downward_search_config {astar, eager, eager_greedy, eager_wastar, ehc, lazy, lazy_greedy, lazy_wastar} [astar] # SearchEngine

# Options for SearchEngines
evaluator {add, blind, cea, cegar, cg, ff, goalcount, hm, hmax, lmcut, const, g, pref, cpdbs, ipdb, pdb, zopdbs} [blind]

ehc_preferred_usage {prune_by_preferred, rank_preferred_first} [prune_by_preferred]

open {alt, epsilon_greedy, pareto, single, tiebreaking, type_based} [alt]
open_list_evals {add, blind, cea, cegar, cg, ff, goalcount, hm, hmax, lmcut, const, g, pref, cpdbs, ipdb, pdb, zopdbs} [blind]

reopen_closed {false, true} [false]
randomize_successors {false, true} [false]

cost_type {normal, one, plus_one} [normal]

# Pruning Methods
pruning {atom_centric_stubborn_sets, null, stubborn_sets_ec, stubborn_sets_simple} [null]
atom_centric_stubborn_sets_use_sibling {false, true} [false]
atom_selection_strategy {fast_downward, quick_skip, static_small, dynamic_small} [quick_skip] # Example: atom_centric_stubborn_sets(use_sibling_shortcut=true, atom_selection_strategy=quick_skip, verbosity=normal)

# Conditional, <child name> | <parent name> in {<parent val1>, ..., <parent valK>}: child only active if parent takes specified values
evaluator | fast_downward_search_config in {astar, eager_greedy, eager_wastar, ehc, lazy_greedy, lazy_wastar}
ehc_preferred_usage | fast_downward_search_config in {ehc}
reopen_closed | fast_downward_search_config in {eager, eager_wastar, lazy, lazy_greedy, lazy_wastar}
randomize_successors | fast_downward_search_config in {lazy, lazy_greedy, lazy_wastar}
open | fast_downward_search_config in {eager, lazy}
open_list_evals | fast_downward_search_config in {eager, lazy}
pruning | fast_downward_search_config in {astar, eager, eager_greedy, eager_wastar}
atom_centric_stubborn_sets_use_sibling | pruning in {atom_centric_stubborn_sets}
atom_selection_strategy | pruning in {atom_centric_stubborn_sets}
2 changes: 2 additions & 0 deletions engine_pcs/fmap.pcs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
search_algorithm {0} [0]
heuristic {0, 1, 2, 3} [2]
Loading

0 comments on commit ed04302

Please sign in to comment.