def visit_TheoryFunction(self, x): """ Theory functions are mapped to functions. """ isnum = lambda y: y.type == _ast.ASTType.Symbol and y.symbol.type == _clingo.SymbolType.Number if x.name == "-" and len(x.arguments) == 1: rhs = self(x.arguments[0]) if isnum(rhs): return _ast.Symbol(x.location, _clingo.Number(-rhs.symbol.number)) else: return _ast.UnaryOperation(x.location, _ast.UnaryOperator.Minus, rhs) elif (x.name == "+" or x.name == "-") and len(x.arguments) == 2: lhs = self(x.arguments[0]) rhs = self(x.arguments[1]) op = _ast.BinaryOperator.Plus if x.name == "+" else _ast.BinaryOperator.Minus if isnum(lhs) and isnum(rhs): lhs = lhs.symbol.number rhs = rhs.symbol.number return _ast.Symbol(x.location, _clingo.Number(lhs + rhs if x.name == "+" else lhs - rhs)) else: return _ast.BinaryOperation(x.location, op, lhs, rhs) elif x.name == "-" and len(x.arguments) == 2: return _ast.BinaryOperation(x.location, _ast.BinaryOperator.Minus, self(x.arguments[0]), self(x.arguments[1])) elif (x.name, TheoryParser.binary) in TheoryParser.table or (x.name, TheoryParser.unary) in TheoryParser.table: raise RuntimeError("operator not handled: {}".format(str_location(x.location))) else: return _ast.Function(x.location, x.name, [self(a) for a in x.arguments], False)
def transform(self, atom): loc = atom.location false = self.__false_atom(loc) atom, ranges = transform_theory_atom(atom) variables = get_variables(atom) param = time_parameter(loc) shift = _ast.Variable(loc, g_tel_shift_variable) aux = self.__aux_atom(loc, variables + [param]) saux = self.__aux_atom(loc, variables + [shift], inc=0) rules = [] if self.__false_external is None: rules.append(_tf.External(loc, false.atom, [])) rules.append(_ast.Rule(loc, atom, [aux])) if ranges: elems = [] for (lhs, rhs), heads in ranges: cond = [] diff = _ast.BinaryOperation(loc, _ast.BinaryOperator.Minus, param, shift) if lhs.ast_type != _ast.ASTType.SymbolicTerm or lhs.symbol.type != _clingo.SymbolType.Number or lhs.symbol.number > 0: cond.append(_ast.Literal(loc, _ast.Sign.NoSign, _ast.Comparison(_ast.ComparisonOperator.LessEqual, lhs, diff))) if rhs.ast_type != _ast.ASTType.SymbolicTerm or rhs.symbol.type != _clingo.SymbolType.Supremum: cond.append(_ast.Literal(loc, _ast.Sign.NoSign, _ast.Comparison(_ast.ComparisonOperator.LessEqual, diff, rhs))) elems.extend([_ast.ConditionalLiteral(loc, _ast.Literal(loc, _ast.Sign.NoSign, head), cond) for head in heads]) rules.append(_ast.Rule(loc, _ast.Disjunction(loc, elems), [saux, false])) return aux, rules
def __get_param(self, name, location): n = name.replace('\'', '') primes = len(name) - len(n) param = ast.SymbolicTerm(location, self.parameter) if primes > 0: param = ast.BinaryOperation( location, ast.BinaryOperator.Minus, param, ast.SymbolicTerm(location, Number(primes))) return n, param
def transform(inputs, callback): """ Transforms the given list of temporal programs in string form into an ASP program. Returns the future predicates whose atoms have to be set to false if referring to the future, and program parts that have to be regrounded if there are constraints referring to the future. Arguments: inputs -- The list of inputs. callback -- Callback for rewritten statements. """ loc = { 'begin': { 'line': 1, 'column': 1, 'filename': '<transform>' }, 'end': { 'line': 1, 'column': 1, 'filename': '<transform>' } } future_predicates = set() constraint_parts = {} time = _ast.Symbol(loc, _clingo.Function(_tf.g_time_parameter_name)) wrap_lit = lambda a: _ast.Literal(loc, _ast.Sign.NoSign, a) # apply transformer to program def append(s): if s is not None: callback(s) aux_rules = [] transformer = _prg.ProgramTransformer(future_predicates, constraint_parts, aux_rules) for i in inputs: _clingo.parse_program(i, lambda s: append(transformer.visit(s))) if aux_rules: callback( _ast.Program(loc, "always", [ _ast.Id(loc, _tf.g_time_parameter_name), _ast.Id(loc, _tf.g_time_parameter_name_alt) ])) for rule in aux_rules: callback(rule) # add auxiliary rules for future predicates future_sigs = [] if len(future_predicates) > 0: callback( _ast.Program(loc, "always", [ _ast.Id(loc, _tf.g_time_parameter_name), _ast.Id(loc, _tf.g_time_parameter_name_alt) ])) for name, arity, positive, shift in sorted(future_predicates): variables = [ _ast.Variable(loc, "{}{}".format(_tf.g_variable_prefix, i)) for i in range(arity) ] s = _ast.Symbol(loc, _clingo.Number(shift)) t_shifted = _ast.BinaryOperation(loc, _ast.BinaryOperator.Plus, time, s) add_sign = lambda lit: lit if positive else _ast.UnaryOperation( loc, _ast.UnaryOperator.Minus, lit) p_current = _ast.SymbolicAtom( add_sign(_ast.Function(loc, name, variables + [time], False))) f_current = _ast.SymbolicAtom( add_sign( _ast.Function(loc, _tf.g_future_prefix + name, variables + [s, time], False))) callback(_ast.Rule(loc, wrap_lit(p_current), [wrap_lit(f_current)])) future_sigs.append( (_tf.g_future_prefix + name, arity + 2, positive)) # gather rules for constraints referring to the future reground_parts = [] if len(constraint_parts) > 0: for (name, shift), rules in constraint_parts.items(): assert (shift > 0) params = [ _ast.Id(loc, _tf.g_time_parameter_name), _ast.Id(loc, _tf.g_time_parameter_name_alt) ] # parts to be regrounded part = "{}_0_{}".format(name, shift - 1) callback(_ast.Program(loc, part, params)) for p, l in rules: callback(p) reground_parts.append((name, part, range(shift))) # parts that no longer have to be regrounded last_part = "{}_{}".format(name, shift) callback(_ast.Program(loc, last_part, params)) for p, l in rules: callback(l) reground_parts.append((name, last_part, range(shift, shift + 1))) def add_part(part_name, atom_name, statement, wrap=lambda x: x): params = [ _ast.Id(loc, _tf.g_time_parameter_name), _ast.Id(loc, _tf.g_time_parameter_name_alt) ] callback(_ast.Program(loc, part_name, params)) atom = wrap( _ast.SymbolicAtom(_ast.Function(loc, atom_name, [time], False))) callback(statement(loc, atom, [])) add_part('initial', '__initial', _ast.Rule, wrap_lit) add_part('always', '__final', _tf.External) reground_parts.append(('always', 'always', range(1))) reground_parts.append(('dynamic', 'dynamic', range(1))) reground_parts.append(('initial', 'initial', range(1))) def no_program(s): if s.type != _ast.ASTType.Program: callback(s) _clingo.parse_program( _dedent('''\ #theory tel { formula_body { & : 7, unary; % prefix for keywords - : 7, unary; % classical negation + : 6, binary, left; % arithmetic + - : 6, binary, left; % arithmetic - ~ : 5, unary; % negation < : 5, unary; % previous < : 5, binary, right; % n x previous <: : 5, unary; % weak previous <: : 5, binary, right; % n x weak previous <? : 5, unary; % eventually- <* : 5, unary; % always- << : 5, unary; % initially > : 5, unary; % next > : 5, binary, right; % n x next >: : 5, unary; % weak next >: : 5, binary, right; % n x weak next >? : 5, unary; % eventually+ >* : 5, unary; % always+ >> : 5, unary; % finally >* : 4, binary, left; % release >? : 4, binary, left; % until <* : 4, binary, left; % trigger <? : 4, binary, left; % since & : 3, binary, left; % and | : 2, binary, left; % or <- : 1, binary, left; % left implication -> : 1, binary, left; % right implication <> : 1, binary, left; % equivalence ;> : 0, binary, right; % sequence next ;>: : 0, binary, right; % sequence weak next <; : 0, binary, left; % sequence previous <:; : 0, binary, left % sequence weak previous }; formula_head { & : 7, unary; % prefix for keywords - : 7, unary; % classical negation + : 6, binary, left; % arithmetic + - : 6, binary, left; % arithmetic - ~ : 5, unary; % negation > : 5, unary; % next > : 5, binary, right; % n x next >: : 5, unary; % weak next >: : 5, binary, right; % n x weak next >? : 5, unary; % eventually+ >* : 5, unary; % always+ >> : 5, unary; % finally >* : 4, binary, left; % release >? : 4, binary, left; % until & : 3, binary, left; % and | : 2, binary, left; % or ;> : 0, binary, right; % sequence next ;>: : 0, binary, right % sequence weak next }; &tel/1 : formula_body, body; &__tel_head/1 : formula_body, head }. '''), no_program) _clingo.parse_program( _dedent('''\ #theory del { formula_body { & : 7, unary; % prefix for keywords ? : 4, unary; % check * : 3, unary; % kleene star + : 2, binary, left; % choice ;; : 1, binary, left; % sequence .>? : 0, binary, right; % diamond (eventually) .>* : 0, binary, right % box (always) }; &del/1 : formula_body, body }. '''), no_program) return future_sigs, reground_parts
def __get_param(self, name, arity, location, replace_future, fail_future, fail_past, max_shift): """ Strips previous and next operators from function names and returns the updated name plus the time arguments to append. Furthermore, if the initially operator (_ prefix) is used, then the time parameter is replaced with 0. Otherwise, it is treated like a past operator. If replace_future is set this also introduces a new name for the predicate, which is recorded in the list of atoms that have to be made redefinable. In this case the name is prefixed with __future_. Such dynamic predicates are recorded in the future_predicates list. Arguments: name -- The name of the predicate (trailing primes denote previous operators). location -- Location for generated terms. replace_future -- Whether atoms referring to the future have to be replaced by a special future atom. fail_future -- Fail if the atom refers to the future. fail_past -- Fail if the atom refers to the past. max_shift -- The maximum number of steps terms look into the future. Example for body atoms: p(X) :- 'q(X) becomes p(X,t) :- q(X,t-1) Example for head atoms (replace_future=True): p''(X) :- q(X). becomes __future__p(X,2,t) :- q(X,t). and future_predicates is extended with (p,1,2) -> False """ n = name.strip("'") shift = 0 for c in name: if c == "'": shift -= 1 else: break shift += len(name) - len(n) + shift initially = False if n.startswith("_") and not n.startswith("__"): n = n[1:] if n.startswith("'") or name.startswith("'") or name.endswith("'"): raise RuntimeError( "initially operator cannot be used with primes: {}".format( _tf.str_location(location))) initially = True finally_ = False if n.endswith("_") and not n.endswith("__"): n = n[:-1] if n.endswith("'") or name.startswith("'") or name.endswith("'"): raise RuntimeError( "finally operator cannot be used with primes: {}".format( _tf.str_location(location))) finally_ = True raise RuntimeError("finally operator not yet supported: {}".format( _tf.str_location(location))) if initially and finally_: raise RuntimeError( "finally and initially operator cannot used together: {}". format(_tf.str_location(location))) params = [ _ast.Symbol(location, _clingo.Function(_tf.g_time_parameter_name)) ] if fail_future and (shift > 0 or finally_): raise RuntimeError( "future atoms not supported in this context: {}".format( _tf.str_location(location))) if fail_past and (shift < 0 or initially): raise RuntimeError( "past atoms not supported in this context: {}".format( _tf.str_location(location))) if shift > 0: if replace_future: self.__future_predicates.add( (n, arity, self.__positive, shift)) n = _tf.g_future_prefix + n params.insert(0, _ast.Symbol(location, shift)) else: max_shift[0] = max(max_shift[0], shift) if shift != 0: params[-1] = _ast.BinaryOperation(location, _ast.BinaryOperator.Plus, params[-1], _ast.Symbol(location, shift)) elif initially: params[-1] = _ast.Symbol(location, 0) return (n, params)
def visit_BinaryOperation(self, x): """ BinaryOperation(location: Location, operator: BinaryOperator, left: term, right: term) """ return [ast.BinaryOperation(x.location, x.operator, l, r) for l, r in product(self.visit(x.left), self.visit(x.right))]