Source code for unified_planning.io.pddl_reader

# Copyright 2021 AIPlan4EU project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import unified_planning as up
import unified_planning.model
import unified_planning.model.htn as htn
import pyparsing # type: ignore
import typing
from mimetypes import types_map
from unified_planning.environment import Environment, get_env
from collections import OrderedDict
from fractions import Fraction
from typing import Dict, Union, Callable, List, cast
from pyparsing import Word, alphanums, alphas, ZeroOrMore, OneOrMore, Keyword
from pyparsing import Optional, Suppress, nestedExpr, Group, restOfLine
if pyparsing.__version__ < '3.0.0':
    from pyparsing import oneOf as one_of
    from pyparsing import ParseResults
else:
    from pyparsing.results import ParseResults # type: ignore
    from pyparsing import one_of


class PDDLGrammar:
    def __init__(self):
        name = Word(alphas, alphanums+'_'+'-')
        variable = Suppress('?') + name

        require_def = Suppress('(') + ':requirements' + \
            OneOrMore(one_of(':strips :typing :negative-preconditions :disjunctive-preconditions :equality :existential-preconditions :universal-preconditions :quantified-preconditions :conditional-effects :fluents :numeric-fluents :adl :durative-actions :duration-inequalities :timed-initial-literals :action-costs :hierarchy')) \
            + Suppress(')')

        types_def = Suppress('(') + ':types' + \
            OneOrMore(Group(Group(OneOrMore(name)) + \
                            Optional(Suppress('-') + name))).setResultsName('types') \
                            + Suppress(')')

        constants_def = Suppress('(') + ':constants' + \
            OneOrMore(Group(Group(OneOrMore(name)) + \
                            Optional(Suppress('-') + name))).setResultsName('constants') \
                            + Suppress(')')

        predicate = Suppress('(') + \
            Group(name + Group(ZeroOrMore(Group(Group(OneOrMore(variable)) + \
                                                Optional(Suppress('-') + name))))) \
                                                + Suppress(')')

        predicates_def = Suppress('(') + ':predicates' + \
            Group(OneOrMore(predicate)).setResultsName('predicates') \
            + Suppress(')')

        functions_def = Suppress('(') + ':functions' + \
            Group(OneOrMore(predicate)).setResultsName('functions') \
            + Suppress(')')

        parameters = ZeroOrMore(Group(Group(OneOrMore(variable)) \
                                     + Optional(Suppress('-') + name))).setResultsName('params')
        action_def = Group(Suppress('(') + ':action' + name.setResultsName('name') \
                           + ':parameters' + Suppress('(') + parameters + Suppress(')') \
                           + Optional(':precondition' + nestedExpr().setResultsName('pre')) \
                           + Optional(':effect' + nestedExpr().setResultsName('eff')) \
                           + Suppress(')'))

        dur_action_def = Group(Suppress('(') + ':durative-action' + name.setResultsName('name') \
                               + ':parameters' + Suppress('(') + parameters + Suppress(')') \
                               + ':duration' + nestedExpr().setResultsName('duration') \
                               + ':condition' + nestedExpr().setResultsName('cond') \
                               + ':effect' + nestedExpr().setResultsName('eff') \
                               + Suppress(')'))

        task_def = Group(Suppress('(') + ':task' + name.setResultsName('name')
                         + ':parameters' + Suppress('(') + parameters + Suppress(')')
                         + Suppress(')'))

        method_def = Group(Suppress('(') + ':method' + name.setResultsName('name') \
                           + ':parameters' + Suppress('(') + parameters + Suppress(')') \
                           + ':task' + nestedExpr().setResultsName('task') \
                           + Optional(':ordered-subtasks' + nestedExpr().setResultsName('ordered-subtasks')) \
                           + Optional(':subtasks' + nestedExpr().setResultsName('subtasks')) \
                           + Suppress(')'))

        domain = Suppress('(') + 'define' \
            + Suppress('(') + 'domain' + name.setResultsName('name') + Suppress(')') \
            + Optional(require_def).setResultsName("features") + Optional(types_def) + Optional(constants_def) \
            + Optional(predicates_def) + Optional(functions_def) \
            + Group(ZeroOrMore(task_def)).setResultsName('tasks') \
            + Group(ZeroOrMore(method_def)).setResultsName('methods') \
            + Group(ZeroOrMore(action_def | dur_action_def)).setResultsName('actions') \
            + Suppress(')')

        objects = OneOrMore(Group(Group(OneOrMore(name))
                                  + Optional(Suppress('-') + name))).setResultsName('objects')

        htn_def = Group(Suppress('(') + ':htn'
                        + Optional(':tasks' + nestedExpr().setResultsName('tasks'))
                        + Optional(':ordering' + nestedExpr().setResultsName('ordering'))
                        + Optional(':constraints' + nestedExpr().setResultsName('constraints'))
                        + Suppress(')'))

        metric = (Keyword('minimize') | Keyword('maximize')).setResultsName('optimization') \
            + (name | nestedExpr()).setResultsName('metric')

        problem = (Suppress('(') + 'define'
                   + Suppress('(') + 'problem' + name.setResultsName('name') + Suppress(')')
                   + Suppress('(') + ':domain' + name + Suppress(')') + Optional(require_def)
                   + Optional(Suppress('(') + ':objects' + objects + Suppress(')'))
                   + Optional(htn_def.setResultsName('htn'))
                   + Suppress('(') + ':init' + ZeroOrMore(nestedExpr()).setResultsName('init') + Suppress(')')
                   + Optional(Suppress('(') + ':goal' + nestedExpr().setResultsName('goal') + Suppress(')'))
                   + Optional(Suppress('(') + ':metric' + metric + Suppress(')'))
                   + Suppress(')'))

        domain.ignore(';' + restOfLine)
        problem.ignore(';' + restOfLine)

        self._domain = domain
        self._problem = problem
        self._parameters = parameters

    @property
    def domain(self):
        return self._domain

    @property
    def problem(self):
        return self._problem

    @property
    def parameters(self):
        return self._parameters


[docs]class PDDLReader: """ Parse a PDDL problem and generate a unified_planning problem. """ def __init__(self, env: Environment = None): self._env = get_env(env) self._em = self._env.expression_manager self._tm = self._env.type_manager self._operators: Dict[str, Callable] = { 'and' : self._em.And, 'or' : self._em.Or, 'not' : self._em.Not, 'imply' : self._em.Implies, '>=' : self._em.GE, '<=' : self._em.LE, '>' : self._em.GT, '<' : self._em.LT, '=' : self._em.Equals, '+' : self._em.Plus, '-' : self._em.Minus, '/' : self._em.Div, '*' : self._em.Times } grammar = PDDLGrammar() self._pp_domain = grammar.domain self._pp_problem = grammar.problem self._pp_parameters = grammar.parameters self._fve = up.walkers.FreeVarsExtractor() self._totalcost: typing.Optional[up.model.FNode] = None def _parse_exp(self, problem: up.model.Problem, act: typing.Optional[Union[up.model.Action, htn.Method]], types_map: Dict[str, up.model.Type], var: Dict[str, up.model.Variable], exp: Union[ParseResults, str]) -> up.model.FNode: stack = [(var, exp, False)] solved: List[up.model.FNode] = [] while len(stack) > 0: var, exp, status = stack.pop() if status: if exp[0] == '-' and len(exp) == 2: # unary minus solved.append(self._em.Times(-1, solved.pop())) elif exp[0] in self._operators: # n-ary operators op: Callable = self._operators[exp[0]] solved.append(op(*[solved.pop() for _ in exp[1:]])) elif exp[0] in ['exists', 'forall']: # quantifier operators q_op: Callable = self._em.Exists if exp[0] == 'exists' else self._em.Forall solved.append(q_op(solved.pop(), *var.values())) elif problem.has_fluent(exp[0]): # fluent reference f = problem.fluent(exp[0]) args = [solved.pop() for _ in exp[1:]] solved.append(self._em.FluentExp(f, tuple(args))) else: raise up.exceptions.UPUnreachableCodeError else: if isinstance(exp, ParseResults): if len(exp) == 0: # empty precodition solved.append(self._em.TRUE()) elif exp[0] == '-' and len(exp) == 2: # unary minus stack.append((var, exp, True)) stack.append((var, exp[1], False)) elif exp[0] in self._operators: # n-ary operators stack.append((var, exp, True)) for e in exp[1:]: stack.append((var, e, False)) elif exp[0] in ['exists', 'forall']: # quantifier operators vars_string = ' '.join(exp[1]) vars_res = self._pp_parameters.parseString(vars_string) vars = {} for g in vars_res['params']: t = types_map[g[1] if len(g) > 1 else 'object'] for o in g[0]: vars[o] = up.model.Variable(o, t) stack.append((vars, exp, True)) stack.append((vars, exp[2], False)) elif problem.has_fluent(exp[0]): # fluent reference stack.append((var, exp, True)) for e in exp[1:]: stack.append((var, e, False)) elif len(exp) == 1: # expand an element inside brackets stack.append((var, exp[0], False)) else: raise SyntaxError(f'Not able to handle: {exp}') elif isinstance(exp, str): if exp[0] == '?' and exp[1:] in var: # variable in a quantifier expression solved.append(self._em.VariableExp(var[exp[1:]])) elif exp[0] == '?': # action parameter assert act is not None solved.append(self._em.ParameterExp(act.parameter(exp[1:]))) elif problem.has_fluent(exp): # fluent solved.append(self._em.FluentExp(problem.fluent(exp))) elif problem.has_object(exp): # object solved.append(self._em.ObjectExp(problem.object(exp))) else: # number n = Fraction(exp) if n.denominator == 1: solved.append(self._em.Int(n.numerator)) else: solved.append(self._em.Real(n)) else: raise SyntaxError(f'Not able to handle: {exp}') assert len(solved) == 1 #sanity check return solved.pop() def _add_effect(self, problem: up.model.Problem, act: Union[up.model.InstantaneousAction, up.model.DurativeAction], types_map: Dict[str, up.model.Type], exp: Union[ParseResults, str], cond: Union[up.model.FNode, bool] = True, timing: typing.Optional[up.model.Timing] = None): to_add = [(exp, cond)] while to_add: exp, cond = to_add.pop(0) if len(exp) == 0: continue # ignore the case where the effect list is empty, e.g., `:effect ()` op = exp[0] if op == 'and': exp = exp[1:] for e in exp: to_add.append((e, cond)) elif op == 'when': cond = self._parse_exp(problem, act, types_map, {}, exp[1]) to_add.append((exp[2], cond)) elif op == 'not': exp = exp[1] eff = (self._parse_exp(problem, act, types_map, {}, exp), self._em.FALSE(), cond) act.add_effect(*eff if timing is None else (timing, *eff)) # type: ignore elif op == 'assign': eff = (self._parse_exp(problem, act, types_map, {}, exp[1]), self._parse_exp(problem, act, types_map, {}, exp[2]), cond) act.add_effect(*eff if timing is None else (timing, *eff)) # type: ignore elif op == 'increase': eff = (self._parse_exp(problem, act, types_map, {}, exp[1]), self._parse_exp(problem, act, types_map, {}, exp[2]), cond) act.add_increase_effect(*eff if timing is None else (timing, *eff)) # type: ignore elif op == 'decrease': eff = (self._parse_exp(problem, act, types_map, {}, exp[1]), self._parse_exp(problem, act, types_map, {}, exp[2]), cond) act.add_decrease_effect(*eff if timing is None else (timing, *eff)) # type: ignore else: eff = (self._parse_exp(problem, act, types_map, {}, exp), self._em.TRUE(), cond) act.add_effect(*eff if timing is None else (timing, *eff)) # type: ignore def _add_condition(self, problem: up.model.Problem, act: up.model.DurativeAction, exp: Union[ParseResults, str], types_map: Dict[str, up.model.Type], vars: typing.Optional[Dict[str, up.model.Variable]] = None): to_add = [(exp, vars)] while to_add: exp, vars = to_add.pop(0) op = exp[0] if op == 'and': for e in exp[1:]: to_add.append((e, vars)) elif op == 'forall': vars_string = ' '.join(exp[1]) vars_res = self._pp_parameters.parseString(vars_string) if vars is None: vars = {} for g in vars_res['params']: t = types_map[g[1] if len(g) > 1 else 'object'] for o in g[0]: vars[o] = up.model.Variable(o, t) to_add.append((exp[2], vars)) elif len(exp) == 3 and op == 'at' and exp[1] == 'start': cond = self._parse_exp(problem, act, types_map, {} if vars is None else vars, exp[2]) if vars is not None: cond = self._em.Forall(cond, *vars.values()) act.add_condition(up.model.StartTiming(), cond) elif len(exp) == 3 and op == 'at' and exp[1] == 'end': cond = self._parse_exp(problem, act, types_map, {} if vars is None else vars, exp[2]) if vars is not None: cond = self._em.Forall(cond, *vars.values()) act.add_condition(up.model.EndTiming(), cond) elif len(exp) == 3 and op == 'over' and exp[1] == 'all': t_all = up.model.OpenTimeInterval(up.model.StartTiming(), up.model.EndTiming()) cond = self._parse_exp(problem, act, types_map, {} if vars is None else vars, exp[2]) if vars is not None: cond = self._em.Forall(cond, *vars.values()) act.add_condition(t_all, cond) else: raise SyntaxError(f'Not able to handle: {exp}') def _add_timed_effects(self, problem: up.model.Problem, act: up.model.DurativeAction, types_map: Dict[str, up.model.Type], eff: ParseResults): to_add = [eff] while to_add: eff = to_add.pop(0) op = eff[0] if op == 'and': for e in eff[1:]: to_add.append(e) elif len(eff) == 3 and op == 'at' and eff[1] == 'start': self._add_effect(problem, act, types_map, eff[2], timing=up.model.StartTiming()) elif len(eff) == 3 and op == 'at' and eff[1] == 'end': self._add_effect(problem, act, types_map, eff[2], timing=up.model.EndTiming()) else: raise SyntaxError(f'Not able to handle: {eff}') def _parse_subtask(self, e, method: typing.Optional[htn.Method], problem: htn.HierarchicalProblem, types_map: Dict[str, up.model.Type]) -> typing.Optional[htn.Subtask]: """Returns the Subtask corresponding to the given expression e or None if the expression cannot be interpreted as a subtask.""" if len(e) == 0: return None task_name = e[0] task: Union[htn.Task, up.model.Action] if problem.has_task(task_name): task = problem.get_task(task_name) elif problem.has_action(task_name): task = problem.action(task_name) else: return None assert isinstance(task, htn.Task) or isinstance(task, up.model.Action) parameters = [self._parse_exp(problem, method, types_map, {}, param) for param in e[1:]] return htn.Subtask(task, *parameters) def _parse_subtasks(self, e, method: typing.Optional[htn.Method], problem: htn.HierarchicalProblem, types_map: Dict[str, up.model.Type],) -> List[htn.Subtask]: """Returns the list of subtasks of the expression""" single_task = self._parse_subtask(e, method, problem, types_map) if single_task is not None: return [single_task] elif len(e) == 0: return [] elif e[0] == 'and': return [subtask for e2 in e[1:] for subtask in self._parse_subtasks(e2, method, problem, types_map)] else: raise SyntaxError(f"Could not parse the subtasks list: {e}") def _check_if_object_type_is_needed(self, domain_res) -> bool: for p in domain_res.get('predicates', []): for g in p[1]: if len(g) <= 1 or g[1] == 'object': return True for p in domain_res.get('functions', []): for g in p[1]: if len(g) <= 1 or g[1] == 'object': return True for g in domain_res.get('constants', []): if len(g) <= 1 or g[1] == 'object': return True for a in domain_res.get('actions', []): for g in a.get('params', []): if len(g) <= 1 or g[1] == 'object': return True return False def _durative_action_has_cost(self, dur_act: up.model.DurativeAction): if self._totalcost in self._fve.get(dur_act.duration.lower) or \ self._totalcost in self._fve.get(dur_act.duration.upper): return False for _, cl in dur_act.conditions.items(): for c in cl: if self._totalcost in self._fve.get(c): return False for _, el in dur_act.effects.items(): for e in el: if self._totalcost in self._fve.get(e.fluent) or self._totalcost in self._fve.get(e.value) \ or self._totalcost in self._fve.get(e.condition): return False return True def _instantaneous_action_has_cost(self, act: up.model.InstantaneousAction): for c in act.preconditions: if self._totalcost in self._fve.get(c): return False for e in act.effects: if self._totalcost in self._fve.get(e.value) or self._totalcost in self._fve.get(e.condition): return False if e.fluent == self._totalcost: if not e.is_increase() or \ not e.condition.is_true() or \ not (e.value.is_int_constant() or \ e.value.is_real_constant()): return False return True def _problem_has_actions_cost(self, problem: up.model.Problem): if self._totalcost is None or not problem.initial_value(self._totalcost).constant_value() == 0: return False for _, el in problem.timed_effects.items(): for e in el: if self._totalcost in self._fve.get(e.fluent) or self._totalcost in self._fve.get(e.value) \ or self._totalcost in self._fve.get(e.condition): return False for c in problem.goals: if self._totalcost in self._fve.get(c): return False return True
[docs] def parse_problem(self, domain_filename: str, problem_filename: typing.Optional[str] = None) -> 'up.model.Problem': domain_res = self._pp_domain.parseFile(domain_filename) problem: up.model.Problem if ":hierarchy" in set(domain_res.get('features', [])): problem = htn.HierarchicalProblem(domain_res['name'], self._env, initial_defaults={self._tm.BoolType(): self._em.FALSE()}) else: problem = up.model.Problem(domain_res['name'], self._env, initial_defaults={self._tm.BoolType(): self._em.FALSE()}) types_map: Dict[str, 'up.model.Type'] = {} object_type_needed: bool = self._check_if_object_type_is_needed(domain_res) for types_list in domain_res.get('types', []): # types_list is a List of 1 or 2 elements, where the first one # is a List of types, and the second one can be their father, # if they have one. father: typing.Optional['up.model.Type'] = None if len(types_list) == 2: # the types have a father if types_list[1] != 'object': #the father is not object father = types_map[types_list[1]] elif object_type_needed: # the father is object, and object is needed object_type = types_map.get('object', None) if object_type is None: # the type object is not defined father = self._env.type_manager.UserType('object', None) types_map['object'] = father else: father = object_type else: assert len(types_list) == 1, "Malformed list of types, I was expecting either 1 or 2 elements" # sanity check for type_name in types_list[0]: types_map[type_name] = self._env.type_manager.UserType(type_name, father) if object_type_needed and 'object' not in types_map: # The object type is needed, but has not been defined types_map['object'] = self._env.type_manager.UserType('object', None) # We manually define it. has_actions_cost = False for p in domain_res.get('predicates', []): n = p[0] params = OrderedDict() for g in p[1]: param_type = types_map[g[1] if len(g) > 1 else 'object'] for param_name in g[0]: params[param_name] = param_type f = up.model.Fluent(n, self._tm.BoolType(), params, self._env) problem.add_fluent(f) for p in domain_res.get('functions', []): n = p[0] params = OrderedDict() for g in p[1]: param_type = types_map[g[1] if len(g) > 1 else 'object'] for param_name in g[0]: params[param_name] = param_type f = up.model.Fluent(n, self._tm.RealType(), params, self._env) if n == 'total-cost': has_actions_cost = True self._totalcost = cast(up.model.FNode, self._em.FluentExp(f)) problem.add_fluent(f) for g in domain_res.get('constants', []): t = types_map[g[1] if len(g) > 1 else 'object'] for o in g[0]: problem.add_object(up.model.Object(o, t)) for task in domain_res.get('tasks', []): assert isinstance(problem, htn.HierarchicalProblem) name = task['name'] task_params = OrderedDict() for g in task.get('params', []): t = types_map[g[1] if len(g) > 1 else 'object'] for p in g[0]: task_params[p] = t task = htn.Task(name, task_params) problem.add_task(task) for a in domain_res.get('actions', []): n = a['name'] a_params = OrderedDict() for g in a.get('params', []): t = types_map[g[1] if len(g) > 1 else 'object'] for p in g[0]: a_params[p] = t if 'duration' in a: dur_act = up.model.DurativeAction(n, a_params, self._env) dur = a['duration'][0] if dur[0] == '=': dur_act.set_fixed_duration(self._parse_exp(problem, dur_act, types_map, {}, dur[2])) elif dur[0] == 'and': upper = None lower = None for j in range(1, len(dur)): v = Fraction(dur[j][2]) if dur[j][0] == '>=': if lower is None or v > lower: lower = v elif dur[j][0] == '<=': if upper is None or v < upper: upper = v else: raise SyntaxError(f'Not able to handle duration constraint of action {n}') if lower is None or upper is None: raise SyntaxError(f'Not able to handle duration constraint of action {n}') d = up.model.ClosedDurationInterval(self._em.Real(lower), self._em.Real(upper)) dur_act.set_duration_constraint(d) else: raise SyntaxError(f'Not able to handle duration constraint of action {n}') cond = a['cond'][0] self._add_condition(problem, dur_act, cond, types_map) eff = a['eff'][0] self._add_timed_effects(problem, dur_act, types_map, eff) problem.add_action(dur_act) has_actions_cost = has_actions_cost and self._durative_action_has_cost(dur_act) else: act = up.model.InstantaneousAction(n, a_params, self._env) if 'pre' in a: act.add_precondition(self._parse_exp(problem, act, types_map, {}, a['pre'][0])) if 'eff' in a: self._add_effect(problem, act, types_map, a['eff'][0]) problem.add_action(act) has_actions_cost = has_actions_cost and self._instantaneous_action_has_cost(act) for m in domain_res.get('methods', []): assert isinstance(problem, htn.HierarchicalProblem) name = m['name'] method_params = OrderedDict() for g in m.get('params', []): t = types_map[g[1] if len(g) > 1 else 'object'] for p in g[0]: method_params[p] = t method = htn.Method(name, method_params) achieved_task = m['task'][0] # a list of the form ["go", "?robot", "?target"] for pname in achieved_task[1:]: if pname[0] != '?': raise SyntaxError(f"All arguments of the task should be parameters: {achieved_task}") achieved_task_params = [method.parameter(pname[1:]) for pname in achieved_task[1:]] method.set_task(problem.get_task(achieved_task[0]), *achieved_task_params) for ord_subs in m.get('ordered-subtasks', []): ord_subs = self._parse_subtasks(ord_subs, method, problem, types_map) for s in ord_subs: method.add_subtask(s) method.set_ordered(*ord_subs) for subs in m.get('subtasks', []): subs = self._parse_subtasks(subs, method, problem, types_map) for s in subs: method.add_subtask(s) problem.add_method(method) if problem_filename is not None: problem_res = self._pp_problem.parseFile(problem_filename) problem.name = problem_res['name'] for g in problem_res.get('objects', []): t = types_map[g[1] if len(g) > 1 else 'object'] for o in g[0]: problem.add_object(up.model.Object(o, t)) tasknet = problem_res.get('htn', None) if tasknet is not None: assert isinstance(problem, htn.HierarchicalProblem) tasks = self._parse_subtasks(tasknet['tasks'][0], None, problem, types_map) for task in tasks: problem.task_network.add_subtask(task) if len(tasknet['ordering'][0]) != 0: raise SyntaxError("Ordering not supported in the initial task network") if len(tasknet['constraints'][0]) != 0: raise SyntaxError("Constraints not supported in the initial task network") for i in problem_res.get('init', []): if i[0] == '=': problem.set_initial_value(self._parse_exp(problem, None, types_map, {}, i[1]), self._parse_exp(problem, None, types_map, {}, i[2])) elif len(i) == 3 and i[0] == 'at' and i[1].replace('.','',1).isdigit(): ti = up.model.StartTiming(Fraction(i[1])) va = self._parse_exp(problem, None, types_map, {}, i[2]) if va.is_fluent_exp(): problem.add_timed_effect(ti, va, self._em.TRUE()) elif va.is_not(): problem.add_timed_effect(ti, va.arg(0), self._em.FALSE()) elif va.is_equals(): problem.add_timed_effect(ti, va.arg(0), va.arg(1)) else: raise SyntaxError(f'Not able to handle this TIL {i}') else: problem.set_initial_value(self._parse_exp(problem, None, types_map, {}, i), self._em.TRUE()) if 'goal' in problem_res: problem.add_goal(self._parse_exp(problem, None, types_map, {}, problem_res['goal'][0])) elif not isinstance(problem, htn.HierarchicalProblem): raise SyntaxError("Missing goal section in problem file.") has_actions_cost = has_actions_cost and self._problem_has_actions_cost(problem) optimization = problem_res.get('optimization', None) metric = problem_res.get('metric', None) if metric is not None: if optimization == 'minimize' and len(metric) == 1 and metric[0] == 'total-time': problem.add_quality_metric(up.model.metrics.MinimizeMakespan()) else: metric_exp = self._parse_exp(problem, None, types_map, {}, metric) if has_actions_cost and optimization == 'minimize' and metric_exp == self._totalcost: costs = {} problem._fluents.remove(self._totalcost.fluent()) problem._initial_value.pop(self._totalcost) use_plan_length = all(False for _ in problem.durative_actions) for a in problem.instantaneous_actions: cost = None for e in a.effects: if e.fluent == self._totalcost: cost = e break if cost is not None: costs[a] = cost.value a._effects.remove(cost) if cost.value != 1: use_plan_length = False else: use_plan_length = False if use_plan_length: problem.add_quality_metric(up.model.metrics.MinimizeSequentialPlanLength()) else: problem.add_quality_metric(up.model.metrics.MinimizeActionCosts(costs, self._em.Int(0))) else: if optimization == 'minimize': problem.add_quality_metric(up.model.metrics.MinimizeExpressionOnFinalState(metric_exp)) elif optimization == 'maximize': problem.add_quality_metric(up.model.metrics.MaximizeExpressionOnFinalState(metric_exp)) return problem