def main(self, ctl: Control, files: Sequence[str]): ''' The main function implementing incremental solving. ''' if not files: files = ["-"] for file_ in files: ctl.load(file_) ctl.add("check", ["t"], "#external query(t).") conf = self._conf step = 0 ret: Optional[SolveResult] = None while ((conf.imax is None or step < conf.imax) and (ret is None or step < conf.imin or ((conf.istop == "SAT" and not ret.satisfiable) or (conf.istop == "UNSAT" and not ret.unsatisfiable) or (conf.istop == "UNKNOWN" and not ret.unknown)))): parts = [] parts.append(("check", [Number(step)])) if step > 0: ctl.release_external(Function("query", [Number(step - 1)])) parts.append(("step", [Number(step)])) else: parts.append(("base", [])) ctl.ground(parts) ctl.assign_external(Function("query", [Number(step)]), True) ret, step = cast(SolveResult, ctl.solve()), step + 1
def acyc_edge(self, node_u: int, node_v: int, condition: Sequence[int]) -> None: self._output( "edge", [Number(node_u), Number(node_v), self._lit_tuple(condition)])
def theory_atom(self, atom_id_or_zero: int, term_id: int, elements: Sequence[int]) -> None: tuple_e_id = self._tuple("theory_element_tuple", self._step_data.theory_element_tuples, elements, _lit) self._output("theory_atom", [Number(atom_id_or_zero), Number(term_id), tuple_e_id])
def heuristic(self, atom: int, type_: HeuristicType, bias: int, priority: int, condition: Sequence[int]) -> None: type_name = str(type_).replace('HeuristicType.', '').lower().rstrip('_') condition_lit = self._lit_tuple(condition) self._output("heuristic", [ Number(atom), Function(type_name), Number(bias), Number(priority), condition_lit ])
def product_and_infra_agent_connector( self, source_agent: Number, target_agent: Number ) -> Symbol: src_agent_number = source_agent.number tgt_agent_number = target_agent.number # Since clingo doesn't support Boolean return, we are returning # Number(1) for true, and Number(0) for false. if tgt_agent_number * 3 + 200 < src_agent_number: return Number(1) else: return Number(0)
def calculate_sccs(self) -> None: ''' Trigger computation of SCCs. SCCs can only be computed if the Reifier has been initialized with `calculate_sccs=True`, This function is called automatically if `reify_steps=True` has been set when initializing the Reifier. ''' for idx, scc in enumerate(self._step_data.graph.tarjan()): for atm in scc: self._output('scc', [Number(idx), Number(atm)])
def theory_atom_with_guard(self, atom_id_or_zero: int, term_id: int, elements: Sequence[int], operator_id: int, right_hand_side_id: int) -> None: tuple_id = self._tuple("theory_element_tuple", self._step_data.theory_element_tuples, elements, _lit) self._output("theory_atom", [ Number(atom_id_or_zero), Number(term_id), tuple_id, Number(operator_id), Number(right_hand_side_id) ])
def theory_term_compound(self, term_id: int, name_id_or_type: int, arguments: Sequence[int]) -> None: names = {-1: "tuple", -2: "set", -3: "list"} if name_id_or_type in names: name = "theory_sequence" value = Function(names[name_id_or_type]) else: name = "theory_function" value = Number(name_id_or_type) tuple_id = self._tuple("theory_tuple", self._step_data.theory_tuples, arguments, _theory, True) self._output(name, [Number(term_id), value, tuple_id])
def _evaluate(term: TheoryTerm) -> Symbol: ''' Evaluates the operators in a theory term in the same fashion as clingo evaluates its arithmetic functions. ''' # tuples if term.type == TheoryTermType.Tuple: return Tuple_([_evaluate(x) for x in term.arguments]) # functions and arithmetic operations if term.type == TheoryTermType.Function: # binary operations if term.name in _BOP and len(term.arguments) == 2: term_a = _evaluate(term.arguments[0]) term_b = _evaluate(term.arguments[1]) if term_a.type != SymbolType.Number or term_b.type != SymbolType.Number: raise RuntimeError("Invalid Binary Operation") if term.name in ("/", "\\") and term_b.number == 0: raise RuntimeError("Division by Zero") return Number(_BOP[term.name](term_a.number, term_b.number)) # unary operations if term.name == "-" and len(term.arguments) == 1: term_a = _evaluate(term.arguments[0]) if term_a.type == SymbolType.Number: return Number(-term_a.number) if term_a.type == SymbolType.Function and term_a.name: return Function(term_a.name, term_a.arguments, not term_a.positive) raise RuntimeError("Invalid Unary Operation") # functions return Function(term.name, [_evaluate(x) for x in term.arguments]) # constants if term.type == TheoryTermType.Symbol: return Function(term.name) # numbers if term.type == TheoryTermType.Number: return Number(term.number) raise RuntimeError("Invalid Syntax")
def main(self, ctl: Control, files: Sequence[str]): ''' Register the difference constraint propagator, and then ground and solve. ''' ctl.register_propagator(self._propagator) ctl.add("base", [], THEORY) if not files: files = ["-"] self._rewrite(ctl, files) ctl.ground([("base", [])]) if self._minimize is None: ctl.solve(on_model=self._propagator.on_model) else: ctl.add("bound", ["b", "v"], "&diff(head) { v-0 } <= b.") while cast(SolveResult, ctl.solve(on_model=self._on_model)).satisfiable: print("Found new bound: {}".format(self._bound)) if self._bound is None: break ctl.ground([ ("bound", [Number(cast(int, self._bound) - 1), self._minimize]) ]) if self._bound is not None: print("Optimum found")
def weight_rule(self, choice: bool, head: Sequence[int], lower_bound: int, body: Sequence[Tuple[int, int]]) -> None: hn = "choice" if choice else "disjunction" hd = Function(hn, [self._atom_tuple(head)]) bd = Function("sum", [self._wlit_tuple(body), Number(lower_bound)]) self._output("rule", [hd, bd]) self._add_edges(head, [lit for lit, w in body])
def _tuple(self, name: str, snmap: Dict[Sequence[U], int], elems: Sequence[U], afun: Callable[[Symbol, int, U], Sequence[Symbol]], ordered: bool = False) -> Symbol: pruned: Sequence[U] if ordered: pruned = elems ident = tuple(elems) else: seen = set() pruned = [] for elem in elems: if elem not in seen: seen.add(elem) pruned.append(elem) ident = tuple(sorted(pruned)) n = len(snmap) i = Number(snmap.setdefault(ident, n)) if n == i.number: self._output(name, [i]) for idx, atm in enumerate(pruned): self._output(name, afun(i, idx, atm)) return i
def divisors(sym: Symbol) -> Iterator[Symbol]: ''' Return all divisors of the given number. ''' num = sym.number for i in range(1, num + 1): if num % i == 0: yield Number(i)
def theory_element(self, element_id: int, terms: Sequence[int], condition: Sequence[int]) -> None: tuple_id = self._tuple("theory_tuple", self._step_data.theory_tuples, terms, _theory, True) condition_id = self._tuple("literal_tuple", self._step_data.lit_tuples, condition, _lit) self._output("theory_element", [Number(element_id), tuple_id, condition_id])
def get_assignment(self) -> List[Tuple[Node, Weight]]: ''' Get the current assignment to integer variables. ''' zero = Number(0) adjust = self._potential[zero] if zero in self._potential else 0 return [(node, adjust - self._potential[node]) for node in self._potential if node != zero]
def on_model(self, model: Model): ''' This function should be called when a model has been found to extend it with the integer variable assignments. ''' assignment = self._state(model.thread_id).get_assignment() model.extend([ Function("dl", [var, Number(value)]) for var, value in assignment ])
def agent_connector( self, source_agent: Number, target_agent: Number, source_level: Number, target_level: Number, ) -> Symbol: src_agent_number = source_agent.number tgt_agent_number = target_agent.number src_level_number = source_level.number tgt_level_number = target_level.number # Since clingo doesn't support Boolean return, we are returning # Number(1) for true, and Number(0) for false. if ( src_agent_number < tgt_agent_number + 200 and tgt_agent_number < src_agent_number and tgt_level_number < src_level_number ): return Number(1) else: return Number(0)
def _set(matches: Sequence[Tuple[str, int]], lst: List[Symbol], sym, append: bool = False, default: Symbol = Number(0)) -> bool: for match in matches: if not sym.match(*match): continue idx = len(lst) if append else sym.arguments[0].number while len(lst) <= idx: lst.append(default) lst[idx] = sym return True return False
def copy_symbol(symbol): # functions if symbol.type == SymbolType.Function: return Function(symbol.name, [copy_symbol(x) for x in symbol.arguments]) # constants if symbol.type == SymbolType.String: return Function(symbol.name) # numbers if symbol.type == SymbolType.Number: return Number(symbol.number) raise RuntimeError("Not a symbol!")
def main(self, ctl: Control, files: Sequence[str]): ''' Main function implementing branch and bound optimization. ''' if not files: files = ["-"] for file_ in files: ctl.load(file_) ctl.add("bound", ["b"], ":- #sum { V,I: _minimize(V,I) } >= b.") ctl.ground([("base", [])]) while cast(SolveResult, ctl.solve(on_model=self._on_model)).satisfiable: print("Found new bound: {}".format(self._bound)) ctl.ground([("bound", [Number(cast(int, self._bound))])]) if self._bound is not None: print("Optimum found")
def test_theory(self): ''' Test the reified theory class. ''' def get_theory(prg): symbols = reify_program(prg) thy = ReifiedTheory(symbols) return list(thy) atm1 = get_theory(THEORY + '&a { f(1+ -2): x } = z. { x }.')[0] atm2 = get_theory(THEORY + '&a { f((1,2)): x }. { x }.')[0] atm3 = get_theory(THEORY + '&a { f([1,2]): x }. { x }.')[0] atm4 = get_theory(THEORY + '&a { f({1,2}): x }. { x }.')[0] atm5 = get_theory(THEORY + '&a. { x }.')[0] self.assertEqual(str(atm1), '&a { f((1)+(-(2))): literal_tuple(1) } = z') self.assertEqual(str(atm2), '&a { f((1,2)): literal_tuple(1) }') self.assertEqual(str(atm3), '&a { f([1,2]): literal_tuple(1) }') self.assertEqual(str(atm4), '&a { f({1,2}): literal_tuple(1) }') self.assertEqual(str(atm5), '&a') self.assertEqual(evaluate(atm1.elements[0].terms[0]), Function('f', [Number(-1)])) self.assertGreaterEqual(atm1.literal, 1) dir1 = get_theory(THEORY + '&b.')[0] self.assertEqual(dir1.literal, 0) atms = get_theory(THEORY + '&a { 1 }. &a { 2 }. &a { 3 }.') self.assertEqual(len(set(atms)), 3) self.assertNotEqual(atms[0], atms[1]) self.assertNotEqual(atms[0] < atms[1], atms[0] > atms[1]) aele = get_theory(THEORY + '&a { 1; 2; 3 }.')[0] self.assertEqual(len(set(aele.elements)), 3) self.assertNotEqual(aele.elements[0], aele.elements[1]) self.assertNotEqual(aele.elements[0] < aele.elements[1], aele.elements[0] > aele.elements[1]) atup = get_theory(THEORY + '&a { 1,2,3 }.')[0] self.assertEqual(len(set(atup.elements[0].terms)), 3) self.assertNotEqual(atup.elements[0].terms[0], atup.elements[0].terms[1]) self.assertNotEqual(atup.elements[0].terms[0] < atup.elements[0].terms[1], atup.elements[0].terms[0] > atup.elements[0].terms[1])
def theory_term_number(self, term_id: int, number: int) -> None: self._output("theory_number", [Number(term_id), Number(number)])
def inc(self, x): return Number(x.number + 1)
print(' loading files:') for file_ in files: print(f' - {file_}') ctl.load(file_) print(' grounding:') for part in parts: print(f' - {part_str(part)}') ctl.ground([part]) if assign: print(' assigning externals:') for sym, truth in assign: print(f' - {sym}={truth}') ctl.assign_external(sym, truth) print(' solutions:') ctl.solve(on_model=lambda m: print(f' - {m}')) print('Exmaple 1:') run(['chemistry.lp'], [("base", [])]) print('\nExmaple 2:') run(['chemistry.lp'], [("acid", [Number(42)])]) print('\nExmaple 3:') run(['chemistry.lp', 'external.lp'], [("base", []), ("acid", [Number(42)])], [(Function("d", [Number(1), Number(42)]), True)])
def assume(self, literals: Sequence[int]) -> None: for lit in literals: self._output("assume", [Number(lit)])
def external(self, atom: int, value: TruthValue) -> None: value_name = str(value).replace('TruthValue.', '').lower().rstrip('_') self._output("external", [Number(atom), Function(value_name)])
def project(self, atoms: Sequence[int]) -> None: for atom in atoms: self._output("project", [Number(atom)])
def output_csp(self, symbol: Symbol, value: int, condition: Sequence[int]) -> None: self._output( "output_csp", [symbol, Number(value), self._lit_tuple(condition)])
def theory_term_string(self, term_id: int, name: str) -> None: self._output("theory_string", [Number(term_id), String(name)])
def on_model(self, m): state = self._state(m.thread_id) m.extend([Function("pref", [Function(name), Function(self._preferences[name].type()), Number(value)]) for name, value in state._values.items()]) if self._mode == "breadth": self._solutions_map[copy(self._dict_to_vector(state._values))] = [copy_symbol(atom) for atom in m.symbols(theory=True,shown=True)] elif self._mode == "depth": self._best_known = Solution([copy_symbol(atom) for atom in m.symbols(theory=True,shown=True)],copy(state._values))