Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
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)