Skip to content

Commit

Permalink
fix(interpreted functions): various bug fixes in IF remover - work in…
Browse files Browse the repository at this point in the history
… progress - not stable results
  • Loading branch information
Samuel Gobbi committed Oct 30, 2024
1 parent 5d12d93 commit 7c8e24d
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 82 deletions.
157 changes: 106 additions & 51 deletions unified_planning/engines/compilers/interpreted_functions_remover.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ def _compile(
:raises: :exc:`~unified_planning.exceptions.UPProblemDefinitionError` when the :meth:`condition<unified_planning.model.Effect.condition>` of an
:class:`~unified_planning.model.Effect` can't be removed without changing the :class:`~unified_planning.model.Problem` semantic.
"""
print("Compiling...")
assert isinstance(problem, Problem)
env = problem.environment
em = env.expression_manager
Expand All @@ -186,34 +187,46 @@ def _compile(
new_problem.name = f"{self.name}_{problem.name}"
new_problem.clear_actions()

kNum = env.type_manager.UserType(get_fresh_name(problem, "kNum"))
kNum = env.type_manager.UserType(get_fresh_name(new_problem, "kNum"))

new_objects = {}
new_fluents = {}
if_known = {}
for ifun_exp, val in self._interpreted_functions_values:
new_objects: dict = {}
new_fluents: dict = {}
if_known: dict = {}
for ifun_exp, val in self._interpreted_functions_values.items():
# this does not add the functions we have not found with the validator yet
ifun = ifun_exp.interpreted_function()
if ifun not in if_known:
if_known[ifun] = []
if ifun not in new_fluents:
f = Fluent(get_fresh_name(f"_f_{ifun.name}"), ifun.return_type, p=kNum)
f = Fluent(
get_fresh_name(new_problem, f"_f_{ifun.name}"),
ifun.return_type,
p=kNum,
)
new_fluents[ifun] = f
new_problem.add_fluent(
f, default_initial_value=self.default(ifun.return_type)
f,
default_initial_value=self._default_value_given_type(
ifun.return_type
),
)
else:
f = new_fluents[ifun]
if tuple(ifun_exp.args) in new_objects:
o = new_objects[tuple(ifun_exp.args)]
else:
if_known[ifun].append(tuple(ifun_exp.args))
o = Object(get_fresh_name(f"_o"), kNum)
o = Object(get_fresh_name(new_problem, f"_o"), kNum)
new_objects[tuple(ifun_exp.args)] = o
new_problem.add_object(o)
new_problem.set_initial_value(f(o), val)

for a in problem.actions:
print("-------------------------------------------------------------------")
print("looping for a in problem actions")

print("now on action")
print(a)
conds = []
if_conds = []
for t, c in self.get_conditions(a):
Expand All @@ -237,35 +250,35 @@ def _compile(
upper = None

for known in itertools.product([True, False], repeat=len(if_conds)):
if not knowledge_compatible(if_conds, known, new_fluents.keys()):
print("this is not compatible with our knowledge")
print("we have to skip this case")
# is this ok?
continue
new_params = []
new_conds = []
paramcounter = 0
for (t, c, ifuns, is_duration, is_lower), k in zip(if_conds, known):
# TODO - find better solution for under this
for ifun in ifuns:
if ifun not in new_fluents.keys():
print(
"function that should be in known case has no known values"
)
k = False
# ------ find better solution for above this
subs = {}
implies = []
l1 = []
for ifun in ifuns:
if k:
paramcounter = paramcounter + 1
new_param = up.model.Parameter(
get_fresh_name(
problem, f"_p_{ifun.interpreted_function().name}"
new_problem,
f"_p_{ifun.interpreted_function().name}_"
+ str(paramcounter),
),
kNum,
)
new_params.append(new_param)
print(new_fluents) # TODO - remove debug prints
f = new_fluents[ifun.interpreted_function()]
subs[ifun] = f(new_param)
l2 = []
# TODO - check if fix is ok - added if ... in keys
if ifun.interpreted_function() in if_known.keys():

for p_known in if_known[ifun.interpreted_function()]:
pf = em.And(
[
Expand All @@ -279,12 +292,12 @@ def _compile(
(t, em.Implies(pf, em.Equals(new_param, ob)))
)
l2.append(pf)
print("l2") # TODO - remove debug prints
print(l2) # TODO - remove debug prints
else:
# in this case it means that this function has to be ocnsidered unknown
# and there are no known values to put as not in the condition
pass
if len(l2) != 0:
l1.append(em.Or(l2))
print("l1") # TODO - remove debug prints
print(l1) # TODO - remove debug prints
if k:
new_conds.append((t, em.And(l1)))
new_conds.extend(implies)
Expand All @@ -297,45 +310,73 @@ def _compile(
new_conds.append((t, c.substitute(subs)))
else:
if len(l1) != 0:
new_conds.append(em.Not(em.And(l1)))
new_conds.append((t, em.Not(em.And(l1))))
if is_duration:
if is_lower:
lower = em.Real(Fraction(1, 1))
else:
upper = em.Real(Fraction(1000000, 1))

# clone action
print("conds") # TODO - remove debug prints
print(conds) # TODO - remove debug prints
print("new_conds") # TODO - remove debug prints
print(new_conds) # TODO - remove debug prints
new_a = self.clone_action_with_extra_params(
a, new_params, conds + new_conds, (lower, upper)
)
print("case:")
for ifc, kv in zip(if_conds, known):
print(*ifc[2])
if kv:
print("known")
else:
print("unknown")
print("---compiled action:")
new_a.name = get_fresh_name(new_problem, a.name)
print(new_a)
new_problem.add_action(new_a)
new_to_old[new_a] = a
print("finished compiling!")

return CompilerResult(
new_problem, partial(custom_replace, map=new_to_old), self.name
)

def clone_action_with_extra_params(self, a, new_params, conditions, duration):
print("self") # TODO - remove debug prints
print(self) # TODO - remove debug prints
print("a") # TODO - remove debug prints
print(a) # TODO - remove debug prints
print("new_params") # TODO - remove debug prints
print(new_params) # TODO - remove debug prints
print("conditions") # TODO - remove debug prints
print(conditions) # TODO - remove debug prints
print("duration") # TODO - remove debug prints
print(duration) # TODO - remove debug prints
# TODO - implement this
# TODO - finish implementing this

updated_parameters = OrderedDict(
(param.name, param.type) for param in a.parameters
)
for n in new_params:
updated_parameters[n.name] = n.type

# following stuff has to be split between instantaneous and durative
# new_instantaneous_action = InstantaneousAction(
# action.name, new_params, action.environment
# )
# for p in action.preconditions:
# new_instantaneous_action.add_precondition(p)
# for eff in action.effects:
# new_instantaneous_action._add_effect_instance(eff.clone())
# if action.simulated_effect is not None:
# new_instantaneous_action.set_simulated_effect(action.simulated_effect)

new_action = None
if isinstance(a, up.model.DurativeAction):
raise NotImplementedError
elif isinstance(a, up.model.InstantaneousAction):
raise NotImplementedError
new_action = up.model.InstantaneousAction(
a.name, updated_parameters, a.environment
)

for eff in a.effects:
new_action._add_effect_instance(eff.clone())
if a.simulated_effect is not None:
new_action.set_simulated_effect(a.simulated_effect)

not_timed_conditions = []
for c in conditions:
not_timed_conditions.append(c[1])
new_action.clear_preconditions()
for c in not_timed_conditions:
new_action.add_precondition(c)
else:
raise NotImplementedError
return new_action
Expand All @@ -348,21 +389,25 @@ def EqualsOrIff(self, v1, v2, manager):
ret_exp = manager.Equals(v1, v2)
return ret_exp

def default(self, t):
if t.is_bool_type:
def _default_value_given_type(self, t):
if t.is_bool_type():
return False
if t.is_int_type:
# TODO check for bounds
return 0
if t.is_real_type:
# TODO check for bounds
return Fraction(0, 1)
if t.is_int_type():
if t.lower_bound is None:
return 0
else:
return t.lower_bound
if t.is_real_type():
if t.lower_bound is None:
return Fraction(0, 1)
else:
return t.lower_bound
else:
raise NotImplementedError

def get_conditions(self, a):
cond_list = []
time_list = []
cond_list: list = []
time_list: list = []
if isinstance(a, up.model.DurativeAction):
a_conditions = a.conditions.items()
for ii, cl in a_conditions:
Expand Down Expand Up @@ -424,3 +469,13 @@ def custom_replace(
)
else:
return None


def knowledge_compatible(_ic, _k, _kl):
retval = True
for (t, c, ifuns, is_duration, is_lower), k in zip(_ic, _k):
if k:
for ifun in ifuns:
if ifun.interpreted_function() not in _kl:
retval = False
return retval
33 changes: 2 additions & 31 deletions unified_planning/engines/interpreted_functions_planner.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,6 @@ def _solve(
problem_kind=problem.kind,
compilation_kind=CompilationKind.INTERPRETED_FUNCTIONS_REMOVING,
) as if_remover:
if self._use_old_compiler:
print("using old compiler algorithm, many small actions")
if_remover._use_old_algorithm = True
ifr = if_remover.compile(
problem, CompilationKind.INTERPRETED_FUNCTIONS_REMOVING
)
Expand All @@ -187,21 +184,16 @@ def _attempt_to_solve(
if self._skip_checks:
self.engine._skip_checks = True
self._times_called = self._times_called + 1
# before = round(time.time() * 1000)
print("<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>")
print("our knowledge was")
print(self.knowledge)
print("asking planner to solve the following problem:")
print(new_problem)
print("planner")
print(self.engine.name)
print("says:")
res = self.engine.solve(new_problem, heuristic, timeout, output_stream)
print(res)
# after = round(time.time() * 1000)
# actual_time = after - before
# print("this call took")
# print(actual_time)
# print("milliseconds")
# self._time_list.append(actual_time)

if timeout is not None:
timeout -= min(timeout, time.time() - start)
Expand All @@ -217,19 +209,6 @@ def _attempt_to_solve(

if self._debug_print_final_problem:
print(new_problem)
print("planner called")
print(self._times_called)
# print(len(self._time_list))
# print("times")
# print("on average each call lasted")

# tottime = 0

# for t in self._time_list:
# tottime = tottime + t

# avgtime = tottime / self._times_called
# print(avgtime)

retval = PlanGenerationResult(
status,
Expand Down Expand Up @@ -275,24 +254,16 @@ def _attempt_to_solve(

def _refine(self, problem, validation_result):
newProb = None
# print("Now let's refine!")
if validation_result.calculated_interpreted_functions is None:
print("no updates available, the problem has no solution")
elif len(validation_result.calculated_interpreted_functions) == 0:
print("no updates available, the problem has no solution")
else:
for k in validation_result.calculated_interpreted_functions:
self.add_knowledge(k, validation_result.calculated_interpreted_functions[k])
# print("we have knowledge:")
# print(self.knowledge)
with InterpretedFunctionsRemover(self.knowledge) as if_remover:
if self._use_old_compiler:
# print("refining with old compiler")
if_remover._use_old_algorithm = True
newProb = if_remover.compile(
problem,
CompilationKind.INTERPRETED_FUNCTIONS_REMOVING,
)
# print ("new, better (hopefully) problem:")
# print (newProb)
return newProb

0 comments on commit 7c8e24d

Please sign in to comment.