def test_control(self): ''' Test observer together with a control object. ''' ctl = Control() ctl.register_observer(self.obs) ctl.add('base', [], '''\ b. {c}. a :- b, not c. #minimize{7@10,a:a; 5@10,c:c}. #project a. #project b. #external a. ''') ctl.ground([('base', [])]) a, b, c = (Function(s) for s in ('a', 'b', 'c')) la, lb, lc = (ctl.symbolic_atoms[sym].literal for sym in (a, b, c)) self.prg.sort() self.assertEqual(self.prg, Program( output_atoms={la: a, lc: c}, shows=[], facts=[Fact(symbol=b)], rules=[Rule(choice=False, head=[lb], body=[]), Rule(choice=False, head=[la], body=[-lc]), Rule(choice=True, head=[lc], body=[])], minimizes=[Minimize(priority=10, literals=[(lc, 5), (la, 7)])], externals=[External(atom=la, value=TruthValue.False_)], projects=[Project(atom=lb), Project(atom=la)]).sort())
def test_theory(self): ''' Test observer via grounding. ''' ctl = Control() obs = TestObserverTheory(self) ctl.register_observer(obs) ctl.add( 'base', [], '''\ #theory test { t { }; &a/0 : t, head }. {a; b}. #show t : a, b. &a { (1,a): a,b }. ''') ctl.ground([('base', [])]) self.assertIn('output_term', obs.called) self.assertIn('theory_term_number', obs.called) self.assertIn('theory_term_string', obs.called) self.assertIn('theory_term_compound', obs.called) self.assertIn('theory_element', obs.called) self.assertIn('theory_atom', obs.called) ctl.solve()
class Solver: def __init__(self): self.k = 0 self.prg = Control() self.prg.load("client.lp") self.prg.ground([("pigeon", []), ("sleep", [Number(self.k)])]) self.prg.assign_external(Function("sleep", [Number(self.k)]), True) self.ret = None self.models = [] def on_model(self, model): self.models.append(str(model)) def start(self, on_finish): if self.ret is not None and not self.ret.unknown(): self.k = self.k + 1 self.prg.ground([("sleep", [self.k])]) self.prg.release_external(Function("sleep", [Number(self.k - 1)])) self.prg.assign_external(Function("sleep", [Number(self.k)]), True) self.future = self.prg.solve(on_model=self.on_model, on_finish=on_finish, async_=True) def stop(self): self.future.cancel() def finish(self): ret = self.future.get() return ret def set_more_pigeon(self, more): self.prg.assign_external(Function("p"), more)
def symbolic_atoms_to_facts_test1(q,facts_only): prgstr="""xq(1). xq("a"). 1 { xp(1);xp(2) }2.""" ctrl=Control() add_program_string(ctrl,prgstr) ctrl.ground([("base",[])]) fb=symbolic_atoms_to_facts(ctrl.symbolic_atoms,[XP,XQ,XQ2], facts_only=facts_only) q.put(fb)
def get_ground_universe(program: Program) -> Set[Symbol]: prg = program_to_string(program) ctl = Control() ctl.add("base", [], prg) ctl.ground([("base", [])]) ground_universe = set( [ground_atom.symbol for ground_atom in ctl.symbolic_atoms]) log(f"Ground universe: {ground_universe}") return ground_universe
def test_ground(self): ''' Test grounding with context and parameters. ''' ctx = Context() ctl = Control() ctl.add('part', ['c'], 'p(@cb_num(c)).') ctl.ground([('part', [Number(1)])], ctx) symbols = [atom.symbol for atom in ctl.symbolic_atoms] self.assertEqual(sorted(symbols), [Function('p', [Number(0)]), Function('p', [Number(2)])])
def test_simple_stats(self): ''' Test simple statistics. ''' ctl = Control(['-t', '2', '--stats=2']) ctl.add('base', [], '1 { a; b }.') ctl.ground([('base', [])]) ctl.solve() stats = ctl.statistics self.assertGreaterEqual(stats['problem']['lp']['atoms'], 2) self.assertGreaterEqual(stats['solving']['solvers']['choices'], 1)
def test_lower(self): ''' Test lower bounds reported during optimization. ''' ctl = Control(['--opt-str=usc,oll,0', '--stats=2']) ctl.add('base', [], '1 { p(X); q(X) } 1 :- X=1..3. #minimize { 1,p,X: p(X); 1,q,X: q(X) }.') ctl.ground([('base', [])]) lower = [] self.assertTrue(ctl.solve(on_unsat=lower.append).satisfiable) self.assertEqual(lower, [[1], [2], [3]]) self.assertEqual(ctl.statistics['summary']['lower'], [3.0])
def test_control_add_facts(self): class F(Predicate): anum = IntegerField f1 = F(1) ; f2 = F(2) ctrl = Control() control_add_facts(ctrl,[f1,f2]) ctrl.ground([("base",[])]) model = None with ctrl.solve(yield_=True) as sh: for m in sh: model=str(m) self.assertEqual(model, "{} {}".format(f1,f2))
def test_solve(self): cc = Control() cc.add('base', [], ''' a(X) :- not b(X), d(X). b(X) :- not a(X), d(X).''') cc.add('base', [], "d(1;2;3).") cc.ground([("base",[])]) out = {} def onmodel(m): out['out'] = str(m) cc.solve(on_model = onmodel) self.assertEqual('d(1) d(2) d(3) b(1) b(2) b(3)', out['out'])
def __init__(self): self.size = 1 self.blocked = set() self.barriers = set() self.targets = set() self.pos = dict() self.robots = [{}] self.moves = [] self.current_target = None self.solution = None ctl = Control() ctl.load("board.lp") ctl.ground([("base", [])]) ctl.solve(on_model=self.__on_model)
def test_user_stats(self): ''' Test user statistics. ''' def on_statistics(step, accu): step['test'] = {'a': 0, 'b': [1, 2], 'c': {'d': 3}} accu['test'] = step['test'] step['test'] = { 'a': lambda a: a + 1, 'e': lambda a: 4 if a is None else 0, 'b': [-1, 2, 3] } self.assertEqual(len(step['test']), 4) self.assertEqual(len(step['test']['b']), 3) self.assertEqual(len(step['test']['c']), 1) self.assertIn('a', step['test']) self.assertEqual(sorted(step['test']), ['a', 'b', 'c', 'e']) self.assertEqual(sorted(step['test'].keys()), ['a', 'b', 'c', 'e']) self.assertEqual(sorted(step['test']['c'].items()), [('d', 3.0)]) self.assertEqual(sorted(step['test']['c'].values()), [3.0]) step['test']['b'][1] = 99 self.assertEqual(step['test']['b'][1], 99) step['test']['b'].extend([3, 4]) step['test']['b'] += [3, 4] ctl = Control(['-t', '2', '--stats=2']) ctl.add('base', [], '1 { a; b }.') ctl.ground([('base', [])]) ctl.solve(on_statistics=on_statistics) stats = ctl.statistics self.assertEqual( stats['user_step']['test'], { 'a': 1.0, 'b': [-1.0, 99.0, 3.0, 3.0, 4.0, 3.0, 4.0], 'c': { 'd': 3.0 }, 'e': 4.0 }) self.assertEqual(stats['user_accu']['test'], { 'a': 0, 'b': [1, 2], 'c': { 'd': 3 } })
def test_error_handling(self): ''' Test basic error handling during solving. ''' ctl = Control() ctl.add('base', [], '1 {a; b} 1.') ctl.ground([('base', [])]) self.assertRaises(ZeroDivisionError, lambda: ctl.solve(on_model=lambda m: 1 / 0)) with ctl.solve(on_model=lambda m: 1 / 0, yield_=True) as handle: self.assertRaises(ZeroDivisionError, lambda: [_ for _ in handle]) # Note: currently clasp does not store and re-raise the exception in # asynchronous mode, so we get a runtime error instead with ctl.solve(on_model=lambda m: 1 / 0, async_=True) as handle: self.assertRaises(RuntimeError, handle.get)
class TestSymbol(TestCase): ''' Tests basic solving and related functions. ''' def setUp(self): self.mcb = _MCB() self.ctl = Control(['0']) def tearDown(self): self.mcb = None self.ctl = None def test_propagator_control(self): ''' Test PropagateControl. ''' self.ctl.add("base", [], "{a}.") self.ctl.ground([("base", [])]) self.ctl.register_propagator(TestPropagatorControl(self)) _check_sat(self, cast(SolveResult, self.ctl.solve(on_model=self.mcb.on_model, yield_=False, async_=False))) self.assertEqual(self.mcb.models, _p(['a'])) def test_propagator_init(self): ''' Test PropagateInit and Assignment. ''' self.ctl.add("base", [], "{a; b; c}.") self.ctl.ground([("base", [])]) self.ctl.register_propagator(TestPropagatorInit(self)) _check_sat(self, cast(SolveResult, self.ctl.solve(on_model=self.mcb.on_model, yield_=False, async_=False))) self.assertEqual(self.mcb.models[-1:], _p(['a', 'b', 'c'])) def test_propagator(self): ''' Test adding literals while solving. ''' self.ctl.add("base", [], "") self.ctl.ground([("base", [])]) self.ctl.register_propagator(TestPropagator(self)) _check_sat(self, cast(SolveResult, self.ctl.solve(on_model=self.mcb.on_model, yield_=False, async_=False))) self.assertEqual(self.mcb.models, _p([], [])) def test_heurisitc(self): ''' Test decide. ''' self.ctl = Control(['1']) self.ctl.add("base", [], "{a;b}.") self.ctl.ground([("base", [])]) self.ctl.register_propagator(TestHeuristic(self)) self.ctl.solve(on_model=self.mcb.on_model) self.assertEqual(self.mcb.models, _p(['a']))
def solve_instance(self): prg = Control() prg.load("puzzle15.lp") prg.load(self.new_filename) ret, parts, step = SolveResult.unsatisfiable, [], 1 parts.append(("base", [])) while ret == SolveResult.unsatisfiable: parts.append(("step", [step])) parts.append(("check", [step])) prg.ground(parts) prg.release_external(Function("query", [step - 1])) prg.assign_external(Function("query", [step]), True) #f = lambda m: stdout.write(str(m)+'\n') print("step:" + str(step) + " Solving...") ret, parts, step = prg.solve(on_model=self.on_model), [], step + 1 if ret.__repr__() == 'UNSAT': ret = SolveResult.unsatisfiable else: ret = SolveResult.satisfiable
def test_theory_with_guard(self): ''' Test observer via grounding. ''' ctl = Control() obs = TestObserverTheoryWithGuard(self) ctl.register_observer(obs) ctl.add( 'base', [], '''\ #theory test { t { }; &a/0 : t, {=}, t, head }. &a { } = a. ''') ctl.ground([('base', [])]) self.assertIn('theory_term_string: a', obs.called) self.assertIn('theory_term_string: =', obs.called) self.assertIn('theory_atom_with_guard', obs.called) ctl.solve()
def main(): # Create a Control object that will unify models against the appropriate # predicates. Then load the asp file that encodes the problem domain. ctrl = Control(unifier=[Driver, Item, Assignment]) ctrl.load(ASP_PROGRAM) # Dynamically generate the instance data drivers = [Driver(name=n) for n in ["dave", "morri", "michael"]] items = [Item(name="item{}".format(i)) for i in range(1, 6)] instance = FactBase(drivers + items) # Add the instance data and ground the ASP program ctrl.add_facts(instance) ctrl.ground([("base", [])]) # Generate a solution - use a call back that saves the solution solution = None def on_model(model): nonlocal solution solution = model.facts(atoms=True) ctrl.solve(on_model=on_model) if not solution: raise ValueError("No solution found") # Do something with the solution - create a query so we can print out the # assignments for each driver. # query=solution.select(Assignment).where(lambda x,o: x.driver == o) query=solution.query(Driver,Assignment)\ .join(Driver.name == Assignment.driver)\ .group_by(Driver.name).order_by(Assignment.time).select(Assignment) for d, aiter in query.all(): assignments = list(aiter) if not assignments: print("Driver {} is not working today".format(d)) else: print("Driver {} must deliver: ".format(d)) for a in assignments: print("\t Item {} at time {}".format(a.item, a.time))
def sols(instance, compare, comp): files = [ os.path.join("encodings", "encoding.lp"), os.path.join("encodings", "types.lp"), instance ] thy = ClingconTheory() ctl = Control(['0']) thy.register(ctl) with ProgramBuilder(ctl) as bld: parse_files(files, lambda ast: thy.rewrite_ast(ast, bld.add)) ctl.ground([('base', [])]) thy.prepare(ctl) models = [] with ctl.solve(yield_=True, on_model=thy.on_model) as hnd: for mdl in hnd: model = [str(a) for a in mdl.symbols(shown=True)] model += [ "{}={}".format(str(key).strip('"'), val) for key, val in thy.assignment(mdl.thread_id) ] models.append(set(model)) bool_herbrand = set( [i for sl in compare for i in sl if i.startswith("var(")]) compare = [set(c) for c in compare] assert comp( len(models), len(compare) ), f"{models}\n vs {compare}\ncomputed {len(models)} expected {len(compare)}" for c in compare: # for all in herbrand which are not in compare -> ensure that they are not in model found = False for m in models: if c.issubset(m): if ((bool_herbrand - c) & m) == set(): found = True break assert found == True, f"{c} not in {models}"
def test_monkey(self): from clingo import Control from clorm import Predicate, IntegerField, StringField, FactBase, SymbolPredicateUnifier spu = SymbolPredicateUnifier() @spu.register class Afact(Predicate): num1 = IntegerField() str1 = StringField() @spu.register class Bfact(Predicate): num1 = IntegerField() str1 = StringField() af1 = Afact(1, "aaa") af2 = Afact(2, "bbb") af3 = Afact(3, "aaa") bf1 = Bfact(1, "eee") bf2 = Bfact(2, "fff") bf2 = Bfact(3, "ggg") fb2 = None def on_model(model): nonlocal fb2 fb2 = model.facts(spu, atoms=True) fb1 = FactBase([af1, af2, af3, bf1, bf2]) ctrl = Control() ctrl.add_facts(fb1) ctrl.ground([("base", [])]) ctrl.solve(on_model=on_model) s_a_all = fb2.select(Afact) self.assertEqual(set([a for a in s_a_all.get()]), set([af1, af2, af3]))
def test_sum_of_salaries(): context = Context() @context.valasp() class Income: company: str # String amount: int # Integer def __post_init__(self): if not (self.amount > 0): raise ValueError("amount must be positive") self.__class__.amount_sum += self.amount if self.__class__.amount_sum > Integer.max(): raise OverflowError( f"sum of amount may exceed {Integer.max()}") @classmethod def before_grounding_init_amount_sum(cls): cls.amount_sum = 0 @classmethod def after_grounding_check_amount_sum(cls): if cls.amount_sum < 10000: raise ValueError(f"sum of amount cannot reach 10000") if cls.amount_sum == 3000000000: raise OverflowError(f"catch this!") control = Control() control.add( "base", [], 'income("Acme ASP",1500000000). income("Yoyodyne YAML",1500000000).') control.add("valasp", [], context.valasp_validators()) context.valasp_run_class_methods('before_grounding') with pytest.raises(RuntimeError): control.ground([("base", []), ("valasp", [])], context=context) with pytest.raises(OverflowError): context.valasp_run_class_methods('after_grounding')
def valasp_run(self, control: clingo.Control, on_validation_done: Callable = None, on_model: Callable = None, aux_program: List[str] = None, with_validators: bool = True, with_solve: bool = True) -> None: """Run grounder on the given controller, possibly performing validation and searching for a model. :param control: a controller :param on_validation_done: a function invoked after grounding, if no validation error is reported :param on_model: a callback function to process a model :param aux_program: more ASP code to add to the program :param with_validators: if True, validator constraints are added, and ``before_grounding*`` and ``after_grounding*`` class methods are called :param with_solve: if True, a model is searched """ if with_validators: control.add("valasp", [], self.valasp_validators()) self.valasp_run_class_methods('before_grounding') if aux_program: control.add("aux_program", [], '\n'.join(aux_program)) control.ground([("base", []), ("valasp", []), ("aux_program", [])], context=self) if with_validators: self.valasp_run_class_methods('after_grounding') if on_validation_done: on_validation_done() if with_solve: # noinspection PyUnresolvedReferences control.solve(on_model=on_model)
from clingo import Control from clingo.ast import parse_string, ProgramBuilder from clingodl import ClingoDLTheory prg = '&diff { x } >= 1. &diff { y } >= 3.' thy = ClingoDLTheory() ctl = Control(['0']) thy.register(ctl) with ProgramBuilder(ctl) as bld: parse_string(prg, lambda ast: thy.rewrite_ast(ast, bld.add)) ctl.ground([('base', [])]) thy.prepare(ctl) with ctl.solve(yield_=True, on_model=thy.on_model) as hnd: for mdl in hnd: print([f'{key}={val}' for key, val in thy.assignment(mdl.thread_id)])
class TestAtoms(TestCase): ''' Tests for theory and symbolic atoms. ''' def setUp(self): self.ctl = Control() def test_symbolic_atom(self): ''' Test symbolic atom. ''' self.ctl.add('base', [], 'p(1). {p(2)}. #external p(3).') self.ctl.ground([('base', [])]) atoms = self.ctl.symbolic_atoms p1 = atoms[Function('p', [Number(1)])] self.assertIsNotNone(p1) self.assertTrue(p1.is_fact) self.assertFalse(p1.is_external) self.assertTrue(p1.literal >= 1) self.assertEqual(p1.symbol, Function('p', [Number(1)])) self.assertTrue(p1.match('p', 1, True)) self.assertFalse(p1.match('p', 2, True)) p2 = atoms[Function('p', [Number(2)])] self.assertIsNotNone(p2) self.assertFalse(p2.is_fact) self.assertFalse(p2.is_external) self.assertTrue(p2.literal >= 2) self.assertEqual(p2.symbol, Function('p', [Number(2)])) self.assertTrue(p2.match('p', 1, True)) self.assertFalse(p2.match('p', 2, True)) p3 = atoms[Function('p', [Number(3)])] self.assertIsNotNone(p3) self.assertFalse(p3.is_fact) self.assertTrue(p3.is_external) self.assertTrue(p3.literal >= 2) self.assertEqual(p3.symbol, Function('p', [Number(3)])) self.assertTrue(p3.match('p', 1, True)) self.assertFalse(p3.match('p', 2, True)) p4 = atoms[Function('p', [Number(4)])] self.assertIsNone(p4) def test_symbolic_atoms(self): ''' Test symbolic atoms. ''' self.ctl.add( 'base', [], 'p(1). {p(2)}. #external p(3). q(1). -p(1). {q(2)}. #external q(3).' ) self.ctl.ground([('base', [])]) atoms = self.ctl.symbolic_atoms self.assertEqual(sorted(atoms.signatures), [('p', 1, False), ('p', 1, True), ('q', 1, True)]) ps = list(atoms.by_signature('p', 1, True)) self.assertEqual(len(ps), 3) for p in ps: self.assertEqual(p.symbol.name, 'p') self.assertTrue(p.symbol.positive) self.assertEqual(len(p.symbol.arguments), 1) nps = list(atoms.by_signature('p', 1, False)) self.assertEqual(len(nps), 1) for p in nps: self.assertEqual(p.symbol.name, 'p') self.assertTrue(p.symbol.negative) self.assertEqual(p.symbol.arguments, [Number(1)]) self.assertEqual(len(atoms), 7) self.assertEqual(len(list(atoms)), 7) self.assertIn(Function('p', [Number(1)], True), atoms) self.assertIn(Function('p', [Number(1)], False), atoms) self.assertIn(Function('q', [Number(2)], True), atoms) self.assertNotIn(Function('q', [Number(2)], False), atoms) def test_theory_term(self): ''' Test theory term. ''' self.ctl.add('base', [], THEORY) self.ctl.add('base', [], '&a { 1,a,f(a),{1},(1,),[1] }.') self.ctl.ground([('base', [])]) terms = next(self.ctl.theory_atoms).elements[0].terms self.assertEqual([str(term) for term in terms], ['1', 'a', 'f(a)', '{1}', '(1,)', '[1]']) num, sym, fun, set_, tup, lst = terms self.assertEqual(num.type, TheoryTermType.Number) self.assertEqual(num.number, 1) self.assertEqual(sym.type, TheoryTermType.Symbol) self.assertEqual(sym.name, 'a') self.assertEqual(fun.type, TheoryTermType.Function) self.assertEqual(fun.name, 'f') self.assertEqual(fun.arguments, [sym]) self.assertEqual(set_.type, TheoryTermType.Set) self.assertEqual(set_.arguments, [num]) self.assertEqual(tup.type, TheoryTermType.Tuple) self.assertEqual(tup.arguments, [num]) self.assertEqual(lst.type, TheoryTermType.List) self.assertEqual(lst.arguments, [num]) self.assertNotEqual(hash(num), hash(sym)) self.assertEqual(hash(num), hash(lst.arguments[0])) self.assertNotEqual(num < sym, sym < num) self.assertRegex(repr(num), 'TheoryTerm(.*)') def test_theory_element(self): ''' Test theory element. ''' self.ctl.add('base', [], THEORY) self.ctl.add('base', [], '{a; b}.') self.ctl.add('base', [], '&a { 1; 2,3: a,b }.') self.ctl.ground([('base', [])]) atom = next(self.ctl.theory_atoms) elements = sorted(atom.elements, key=lambda elem: len(elem.terms)) self.assertEqual([str(elem) for elem in elements], ['1', '2,3: a,b']) a, b = elements self.assertEqual(len(a.condition), 0) self.assertEqual(len(b.condition), 2) self.assertTrue(all(lit >= 1 for lit in b.condition)) self.assertGreaterEqual(b.condition_id, 1) self.assertEqual(a, a) self.assertNotEqual(a, b) self.assertNotEqual(hash(a), hash(b)) self.assertNotEqual(a < b, b < a) self.assertRegex(repr(a), 'TheoryElement(.*)') def test_theory_atom(self): ''' Test theory atom. ''' self.ctl.add('base', [], THEORY) self.ctl.add('base', [], '&a {}.') self.ctl.add('base', [], '&b {} = 1.') self.ctl.ground([('base', [])]) atoms = sorted(list(self.ctl.theory_atoms), key=lambda atom: atom.term.name) self.assertEqual([str(atom) for atom in atoms], ['&a{}', '&b{}=1']) a, b = atoms self.assertTrue(a.literal >= 1) self.assertIsNone(a.guard) self.assertIsNotNone(b.guard) self.assertEqual(b.guard[0], "=") self.assertEqual(str(b.guard[1]), "1") self.assertEqual(len(a.elements), 0) self.assertEqual(a, a) self.assertNotEqual(a, b) self.assertNotEqual(hash(a), hash(b)) self.assertNotEqual(a < b, b < a) self.assertRegex(repr(a), 'TheoryAtom(.*)')
def main(self, ctl: Control, files: Sequence[str]): ''' Parse LP^MLN program and convert to ASP with weak constraints. ''' observer = Observer() ctl.register_observer(observer) ctl.add("base", [], THEORY) ctl.add("base", [], self.evidence_file) if self.two_solve_calls: ctl.add("base", [], '#external ext_helper.') # TODO: Make sure the ext_helper atom is not contained in the program. if not files: files = ["-"] self._convert(ctl, files) ctl.ground([("base", [])]) if self.query != []: self._ground_queries(ctl.symbolic_atoms) bound_hr = 2**63 - 1 if self.two_solve_calls: # First solve call # Soft rules are deactivated # TODO: Suppress output of first solve call, add flag # TODO: Activate this per flag ctl.assign_external(Function("ext_helper"), False) with ctl.solve(yield_=True) as h: for m in h: bound_hr = m.cost[0] # TODO: Don't show ext_helper # ctl.release_external(Function("ext_helper")) ctl.assign_external(Function("ext_helper"), True) if self.display_all_probs: ctl.configuration.solve.opt_mode = f'enum, {bound_hr}, {(2**63)-1}' ctl.configuration.solve.models = 0 model_costs = [] with ctl.solve(yield_=True) as handle: for model in handle: if self.display_all_probs or self.query != []: model_costs.append(model.cost) if self.query != []: self._check_model_for_query(model) if model_costs != [] and (self.display_all_probs or self.query != []): if 0 not in observer.priorities: # TODO: Should this be error or warning? print( 'No soft weights in program. Cannot calculate probabilites' ) # TODO: What about case where there are other priorities than 0/1? # elif not self.two_solve_calls and any( # x > 1 for x in observer.priorities): # print(observer.priorities) # print('testasd') else: probs = ProbabilityModule( model_costs, observer.priorities, [self.translate_hard_rules, self.two_solve_calls]) if self.display_all_probs: probs.print_probs() if self.query != []: probs.get_query_probability(self.query)
class VizloControl(Control): def add_to_painter(self, model: Union[Model, PythonModel, Collection[clingo.Symbol]]): """ will register model with the internal painter. On all consecutive calls to paint(), this model will be painted. :param model: the model to add to the painter. :return: """ self.painter.append(PythonModel(model)) def __init__(self, arguments: List[str] = [], logger=None, message_limit: int = 20, print_entire_models=False, atom_draw_maximum=15): self.control = Control(arguments, logger, message_limit) self.painter: List[PythonModel] = list() self.program: ASTProgram = list() self.raw_program: str = "" self.transformer = JustTheRulesTransformer() self._print_changes_only = not print_entire_models self._atom_draw_maximum = atom_draw_maximum def _set_print_only_changes(self, value: bool) -> None: self._print_changes_only = value def ground(self, parts: List[Tuple[str, List[Symbol]]], context: Any = None) -> None: self.control.ground(parts, context) def solve(self, assumptions: List[Union[Tuple[Symbol, bool], int]] = [], on_model=None, on_statistics=None, on_finish=None, yield_: bool = False, async_: bool = False) -> Union[SolveHandle, SolveResult]: return self.control.solve(assumptions, on_model, on_statistics, on_finish, yield_, async_) def load(self, path): prg = "" with open(path) as f: for line in f: prg += line self.program += prg self.control.load(path) def add(self, name: str, parameters: List[str], program: str) -> None: self.raw_program += program self.control.add(name, parameters, program) def find_nodes_corresponding_to_stable_models(self, g, stable_models): correspoding_nodes = set() for model in stable_models: for node in g.nodes(): log(f"{node} {type(node.model)} == {model} {type(model)} -> {set(node.model) == model}" ) if set(node.model) == model and len( g.edges(node)) == 0: # This is a leaf log(f"{node} <-> {model}") correspoding_nodes.add(node) break return correspoding_nodes def prune_graph_leading_to_models(self, graph: nx.DiGraph, models_as_nodes): before = len(graph) relevant_nodes = set() for model in models_as_nodes: for relevant_node in nx.all_simple_paths(graph, INITIAL_EMPTY_SET, model): relevant_nodes.update(relevant_node) all_nodes = set(graph.nodes()) irrelevant_nodes = all_nodes - relevant_nodes graph.remove_nodes_from(irrelevant_nodes) after = len(graph) log(f"Removed {before - after} of {before} nodes ({(before - after) / before})" ) def _make_graph(self, _sort=True): """ Ties together transformation and solving. Transforms the already added program parts and creates a solving tree. :param _sort: Whether the program should be sorted automatically. Setting this to false will likely result into wrong results! :return: :raises ValueError: """ if not len(self.raw_program): raise ValueError("Can't paint an empty program.") else: t = JustTheRulesTransformer() program = t.transform(self.raw_program, _sort) if len(self.painter): universe = get_ground_universe(program) global_assumptions = make_global_assumptions( universe, self.painter) solve_runner = SolveRunner(program, t.rule2signatures) g = solve_runner.make_graph(global_assumptions) else: solve_runner = SolveRunner(program, symbols_in_heads_map=t.rule2signatures) g = solve_runner.make_graph() return g def paint(self, atom_draw_maximum: int = 20, show_entire_model: bool = False, sort_program: bool = True, **kwargs): """ Will create a graph visualization of the solving process. If models have been added using add_to_painter, only the solving paths that lead to these models will be drawn. :param atom_draw_maximum: int The maximum amount of atoms that will be printed for each partial model. (default=20) :param show_entire_model: bool If false, only the atoms that have been added at a solving step will be printed (up to atom_draw_maximum). If true, all atoms will always be printed (up to atom_draw_maximum). (default=False) :param sort_program: If true, the rules of a program will be sorted and grouped by their dependencies. Each set of rules will contain all rules in which each atom in its heads is contained in a head. :param kwargs: kwargs will be forwarded to the visualisation module. See graph.draw() :return: """ if type(atom_draw_maximum) != int: raise ValueError( f"Argument atom_draw_maximum should be an integer (received {atom_draw_maximum})." ) g = self._make_graph(sort_program) display = NetworkxDisplay(g, atom_draw_maximum, not show_entire_model) img = display.draw(**kwargs) return img def _add_and_ground(self, prg): """Short cut for complex add and ground calls, should only be used for debugging purposes.""" self.add("base", [], prg) self.ground([("base", [])]) ################## # Just pass-through stuff ################## @property def configuration(self) -> Configuration: return self.control.configuration @property def is_conflicting(self) -> bool: return self.control.is_conflicting @property def statistics(self) -> dict: return self.control.statistics @property def symbolic_atoms(self) -> SymbolicAtoms: return self.control.symbolic_atoms @property def theory_atoms(self) -> TheoryAtomIter: return self.control.theory_atoms @property def use_enumeration_assumption(self) -> bool: return self.control.use_enumeration_assumption def assign_external(self, external: Union[Symbol, int], truth: Optional[bool], **kwargs) -> None: self.control.assign_external(external, truth, **kwargs) def backend(self) -> Backend: return self.control.backend() def builder(self) -> ProgramBuilder: return self.control.builder() def cleanup(self) -> None: self.control.cleanup() def get_const(self, name: str) -> Optional[Symbol]: return self.control.get_const(name) def interrupt(self): self.control.interrupt() def register_observer(self, observer, replace=False): self.register_observer(observer, replace) def release_external(self, symbol: Union[Symbol, int]) -> None: self.control.release_external(symbol)
class TestSolving(TestCase): ''' Tests basic solving and related functions. ''' def setUp(self): self.mcb = _MCB() self.mit = _MCB() self.ctl = Control(['0']) def tearDown(self): self.mcb = None self.mit = None self.ctl = None def test_solve_result_str(self): ''' Test string representation of solve results. ''' ret = self.ctl.solve() self.assertEqual(str(ret), 'SAT') self.assertRegex(repr(ret), 'SolveResult(.*)') def test_model_str(self): ''' Test string representation of models. ''' self.ctl.add('base', [], 'a.') self.ctl.ground([('base', [])]) with self.ctl.solve(yield_=True) as hnd: for mdl in hnd: self.assertEqual(str(mdl), "a") self.assertRegex(repr(mdl), "Model(.*)") def test_solve_cb(self): ''' Test solving using callback. ''' self.ctl.add("base", [], "1 {a; b} 1. c.") self.ctl.ground([("base", [])]) _check_sat(self, cast(SolveResult, self.ctl.solve(on_model=self.mcb.on_model, yield_=False, async_=False))) self.assertEqual(self.mcb.models, _p(['a', 'c'], ['b', 'c'])) self.assertEqual(self.mcb.last[0], ModelType.StableModel) def test_solve_async(self): ''' Test asynchonous solving. ''' self.ctl.add("base", [], "1 {a; b} 1. c.") self.ctl.ground([("base", [])]) with cast(SolveHandle, self.ctl.solve(on_model=self.mcb.on_model, yield_=False, async_=True)) as hnd: _check_sat(self, hnd.get()) self.assertEqual(self.mcb.models, _p(['a', 'c'], ['b', 'c'])) def test_solve_yield(self): ''' Test solving yielding models. ''' self.ctl.add("base", [], "1 {a; b} 1. c.") self.ctl.ground([("base", [])]) with cast(SolveHandle, self.ctl.solve(on_model=self.mcb.on_model, yield_=True, async_=False)) as hnd: for m in hnd: self.mit.on_model(m) _check_sat(self, hnd.get()) self.assertEqual(self.mcb.models, _p(['a', 'c'], ['b', 'c'])) self.assertEqual(self.mit.models, _p(['a', 'c'], ['b', 'c'])) def test_solve_async_yield(self): ''' Test solving yielding models asynchronously. ''' self.ctl.add("base", [], "1 {a; b} 1. c.") self.ctl.ground([("base", [])]) with self.ctl.solve(on_model=self.mcb.on_model, yield_=True, async_=True) as hnd: while True: hnd.resume() _ = hnd.wait() m = hnd.model() if m is None: break self.mit.on_model(m) _check_sat(self, hnd.get()) self.assertEqual(self.mcb.models, _p(['a', 'c'], ['b', 'c'])) self.assertEqual(self.mit.models, _p(['a', 'c'], ['b', 'c'])) def test_solve_interrupt(self): ''' Test interrupting solving. ''' self.ctl.add("base", [], "1 { p(P,H): H=1..99 } 1 :- P=1..100.\n1 { p(P,H): P=1..100 } 1 :- H=1..99.") self.ctl.ground([("base", [])]) with self.ctl.solve(async_=True) as hnd: hnd.resume() hnd.cancel() ret = hnd.get() self.assertTrue(ret.interrupted) with self.ctl.solve(async_=True) as hnd: hnd.resume() self.ctl.interrupt() ret = hnd.get() self.assertTrue(ret.interrupted) def test_solve_core(self): ''' Test core retrieval. ''' self.ctl.add("base", [], "3 { p(1..10) } 3.") self.ctl.ground([("base", [])]) ass = [] for atom in self.ctl.symbolic_atoms.by_signature("p", 1): ass.append(-atom.literal) ret = cast(SolveResult, self.ctl.solve(on_core=self.mcb.on_core, assumptions=ass)) self.assertTrue(ret.unsatisfiable) self.assertTrue(len(self.mcb.core) > 7) def test_enum(self): ''' Test core retrieval. ''' self.ctl = Control(['0', '-e', 'cautious']) self.ctl.add("base", [], "1 {a; b} 1. c.") self.ctl.ground([("base", [])]) self.ctl.solve(on_model=self.mcb.on_model) self.assertEqual(self.mcb.last[0], ModelType.CautiousConsequences) self.assertEqual([self.mcb.last[1]], _p(['c'])) self.ctl = Control(['0', '-e', 'brave']) self.ctl.add("base", [], "1 {a; b} 1. c.") self.ctl.ground([("base", [])]) self.ctl.solve(on_model=self.mcb.on_model) self.assertEqual(self.mcb.last[0], ModelType.BraveConsequences) self.assertEqual([self.mcb.last[1]], _p(['a', 'b', 'c'])) def test_model(self): ''' Test functions of model. ''' def on_model(m: Model): self.assertTrue(m.contains(Function('a'))) self.assertTrue(m.is_true(cast(SymbolicAtom, m.context.symbolic_atoms[Function('a')]).literal)) self.assertFalse(m.is_true(1000)) self.assertEqual(m.thread_id, 0) self.assertEqual(m.number, 1) self.assertFalse(m.optimality_proven) self.assertEqual(m.cost, [3]) m.extend([Function('e')]) self.assertSequenceEqual(m.symbols(theory=True), [Function('e')]) self.ctl.add("base", [], "a. b. c. #minimize { 1,a:a; 1,b:b; 1,c:c }.") self.ctl.ground([("base", [])]) self.ctl.solve(on_model=on_model) def test_control_clause(self): ''' Test adding clauses while solving. ''' self.ctl.add("base", [], "1 {a; b; c} 1.") self.ctl.ground([("base", [])]) with cast(SolveHandle, self.ctl.solve(on_model=self.mcb.on_model, yield_=True, async_=False)) as hnd: for m in hnd: clause = [] if m.contains(Function('a')): clause.append((Function('b'), False)) else: clause.append((Function('a'), False)) m.context.add_clause(clause) _check_sat(self, hnd.get()) self.assertEqual(len(self.mcb.models), 2) def test_control_nogood(self): ''' Test adding nogoods while solving. ''' self.ctl.add("base", [], "1 {a; b; c} 1.") self.ctl.ground([("base", [])]) with cast(SolveHandle, self.ctl.solve(on_model=self.mcb.on_model, yield_=True, async_=False)) as hnd: for m in hnd: clause = [] if m.contains(Function('a')): clause.append((Function('b'), True)) else: clause.append((Function('a'), True)) m.context.add_nogood(clause) _check_sat(self, hnd.get()) self.assertEqual(len(self.mcb.models), 2)
class Solver: def __init__(self, horizon=0): self.__horizon = horizon self.__prg = Control(['-t4']) self.__future = None self.__solution = None self.__assign = [] self.__prg.load("board.lp") self.__prg.load("robots.lp") parts = [ ("base", []) , ("check", [Number(0)]) , ("state", [Number(0)]) ] for t in range(1, self.__horizon+1): parts.extend([ ("trans", [Number(t)]) , ("check", [Number(t)]) , ("state", [Number(t)]) ]) self.__prg.ground(parts) self.__prg.assign_external(Function("horizon", [Number(self.__horizon)]), True) def __next(self): assert(self.__horizon < 30) self.__prg.assign_external(Function("horizon", [Number(self.__horizon)]), False) self.__horizon += 1 self.__prg.ground([ ("trans", [Number(self.__horizon)]) , ("check", [Number(self.__horizon)]) , ("state", [Number(self.__horizon)]) ]) self.__prg.assign_external(Function("horizon", [Number(self.__horizon)]), True) def start(self, board): self.__assign = [] for robot, (x, y) in board.pos.items(): self.__assign.append(Function("pos", [Function(robot), Number(x+1), Number(y+1), Number(0)])) self.__assign.append(Function("target", [ Function(board.current_target[0]) , Number(board.current_target[2] + 1) , Number(board.current_target[3] + 1) ])) for x in self.__assign: self.__prg.assign_external(x, True) self.__solution = None self.__future = self.__prg.solve(on_model=self.__on_model, async_=True) def busy(self): if self.__future is None: return False if self.__future.wait(0): if self.__solution is None: self.__next() self.__future = self.__prg.solve(on_model=self.__on_model, async_=True) return True else: self.__future = None return False return True def stop(self): if self.__future is not None: self.__future.cancel() self.__future.wait() self.__future = None self.get() def get(self): solution = self.__solution self.__solution = None for x in self.__assign: self.__prg.assign_external(x, False) self.__assign = [] return solution def __on_model(self, m): self.__solution = [] for atom in m.symbols(atoms=True): if atom.name == "move" and len(atom.arguments) == 4: c, x, y, t = [(n.number if n.type == SymbolType.Number else str(n)) for n in atom.arguments] self.__solution.append((c, x, y, t)) self.__solution.sort(key=lambda x: x[3]) p = None i = 0 for x in self.__solution: if p is not None and \ p[0] == x[0] and \ p[1] == x[1] and \ p[2] == x[2]: break p = x i += 1 del self.__solution[i:]
class SolveThread(Thread): STATE_SOLVE = 1 STATE_IDLE = 2 STATE_EXIT = 3 def __init__(self, connection): Thread.__init__(self) self.k = 0 self.prg = Control() self.prg.load("client.lp") self.prg.ground([("pigeon", []), ("sleep", [Number(self.k)])]) self.prg.assign_external(Function("sleep", [Number(self.k)]), True) self.state = SolveThread.STATE_IDLE self.input = Connection() self.output = connection def on_model(self, model): self.output.send("answer: " + str(model)), def on_finish(self, ret): self.output.send("finish: " + str(ret) + (" (INTERRUPTED)" if ret.interrupted else "")) def handle_message(self, msg): if msg == "interrupt": self.state = SolveThread.STATE_IDLE elif msg == "exit": self.state = SolveThread.STATE_EXIT elif msg == "less_pigeon_please": self.prg.assign_external(Function("p"), False) self.state = SolveThread.STATE_IDLE elif msg == "more_pigeon_please": self.prg.assign_external(Function("p"), True) self.state = SolveThread.STATE_IDLE elif msg == "solve": self.state = SolveThread.STATE_SOLVE else: raise (RuntimeError("unexpected message: " + msg)) def run(self): while True: if self.state == SolveThread.STATE_SOLVE: f = self.prg.solve(on_model=self.on_model, on_finish=self.on_finish, async_=True) msg = self.input.receive() if self.state == SolveThread.STATE_SOLVE: f.cancel() ret = f.get() else: ret = None self.handle_message(msg) if self.state == SolveThread.STATE_EXIT: return elif ret is not None and not ret.unknown: self.k = self.k + 1 self.prg.ground([("sleep", [Number(self.k)])]) self.prg.release_external( Function("sleep", [Number(self.k - 1)])) self.prg.assign_external(Function("sleep", [Number(self.k)]), True)
class App: def __init__(self, args): self.control = Control() self.args = args self.horizon = 0 self.objects = 0 self.end = None def show(self, model): if not self.args.quiet: print("Model: {}".format(model)) def ground(self, kind): count = self.objects + self.horizon + 1 parts = [("expand", [Number(count)])] if self.args.scratch and count > 1: self.control = Control() for source in self.args.file: self.control.load(source) for i in range(0, self.objects): parts.append(("object", [Number(i + 1, count)])) for i in range(0, self.horizon): parts.append(("horizon", [Number(i + 1, count)])) if self.args.scratch or count == 1: for option in self.args.option: setattr(self.control.configuration, option[0], option[1]) parts.append(("base", [])) if kind: self.objects += 1 parts.append(("object", [Number(self.objects), Number(count)])) else: self.horizon += 1 parts.append(("horizon", [Number(self.horizon), Number(count)])) if self.args.verbose: print("") print("Objects: {}".format(Number(self.objects))) print("Horizon: {}".format(Number(self.horizon))) self.control.ground(parts) if self.args.verbose: print("Solving: {}".format(count)) def run(self): for source in self.args.file: self.control.load(source) if self.args.maxobj is None: self.end = self.control.get_const("n").number else: self.end = self.args.maxobj while self.objects < self.end: self.ground(True) while True: ret = self.control.solve(on_model=self.show) if self.args.stats: args = { "sort_keys": True, "indent": 0, "separators": (',', ': ') } stats = {} for x in [ "step", "enumerated", "time_cpu", "time_solve", "time_sat", "time_unsat", "time_total" ]: stats[x] = self.control.statistics[x] for x in ["lp", "ctx", "solvers"]: for y in self.control.statistics[x]: stats[y] = self.control.statistics[x][y] print(json.dumps(stats, *args)) if ret.satisfiable: break self.ground(False)