Exemple #1
0
 def test_backend(self):
     '''
     Test backend via observer.
     '''
     ctl = Control()
     obs = TestObserverBackend(self)
     ctl.register_observer(obs)
     with ctl.backend() as backend:
         self.assertIn('init_program', obs.called)
         self.assertIn('begin_step', obs.called)
         backend.add_atom()
         backend.add_atom(Function('a'))
         backend.add_rule([1], [2, 3], True)
         self.assertIn('rule', obs.called)
         backend.add_weight_rule([2], 1, [(2, 3), (4, 5)])
         self.assertIn('weight_rule', obs.called)
         backend.add_minimize(0, [(2, 3), (4, 5)])
         self.assertIn('minimize', obs.called)
         backend.add_project([2, 4])
         self.assertIn('project', obs.called)
         backend.add_heuristic(2, HeuristicType.Level, 5, 7, [1, 3])
         self.assertIn('heuristic', obs.called)
         backend.add_assume([2, 3])
         self.assertIn('assume', obs.called)
         backend.add_acyc_edge(1, 2, [3, 4])
         self.assertIn('acyc_edge', obs.called)
         backend.add_external(3, TruthValue.Release)
         self.assertIn('external', obs.called)
     self.assertIn('output_atom', obs.called)
     ctl.solve()
     self.assertIn('end_step', obs.called)
Exemple #2
0
    def _check(self, prg, prg10, prg_str):
        '''
        Check various ways to remap a program.

        1. No remapping.
        2. Identity remapping via Backend and Control.
        3. Remapping via Backend and Control.
        4. Remapping via remap function without Backend and Control.
        5. Remap a program using the Remapping class.
        '''
        self.assertEqual(self.prg, prg)
        self.assertEqual(str(self.prg), prg_str)

        r_prg = _remap(self.prg)
        self.assertEqual(self.prg, r_prg)
        self.assertEqual(str(r_prg), prg_str)

        r_prg10 = _remap(self.prg, _plus10)
        self.assertEqual(r_prg10, prg10)
        self.assertEqual(str(r_prg10), prg_str)

        ra_prg10 = self.prg.copy().remap(_plus10)
        self.assertEqual(ra_prg10, prg10)
        self.assertEqual(str(ra_prg10), prg_str)

        # note that the backend below is just used as an atom generator
        ctl = Control()
        with ctl.backend() as b:
            for _ in range(10):
                b.add_atom()
            rm_prg = prg.copy().remap(Remapping(b, self.prg.output_atoms, self.prg.facts))
        self.assertEqual(str(rm_prg), prg_str)
Exemple #3
0
def _remap(prg: Program, mapping=None):
    '''
    Add the given program to a backend passing it through an observer and then
    return the observer program.

    The resulting program is initialized with the symbols from the orginial
    program.
    '''

    ctl, chk = Control(), Program()
    # note that output atoms are not passed to the backend
    if mapping is None:
        chk.output_atoms = prg.output_atoms
        chk.shows = prg.shows
    else:
        chk.output_atoms = {mapping(lit): sym for lit, sym in prg.output_atoms.items()}
        chk.shows = [cast(Show, remap(x, mapping)) for x in prg.shows]
    chk.facts = prg.facts

    ctl.register_observer(ProgramObserver(chk))

    with ctl.backend() as b:
        prg.add_to_backend(b, mapping)

    return chk
Exemple #4
0
    def test_aux_lit(self):
        '''
        Test printing of auxiliary literals.
        '''
        out, out10 = self._add_atoms('a', 'b', 'c')
        self.obs.rule(False, [4], [1])
        self.assertEqual(self.prg, Program(
            output_atoms=out,
            rules=[Rule(choice=False, head=[4], body=[1])]))
        self.assertEqual(str(self.prg), '__x4 :- a.')

        prg10 = _remap(self.prg, _plus10)
        self.assertEqual(prg10, Program(
            output_atoms=out10,
            rules=[Rule(choice=False, head=[14], body=[11])]))
        self.assertEqual(str(prg10), '__x14 :- a.')

        ctl = Control()
        with ctl.backend() as b:
            b.add_atom()
            rm_prg = self.prg.copy().remap(Remapping(b, self.prg.output_atoms, self.prg.facts))
        self.assertEqual(str(rm_prg), '__x5 :- a.')
Exemple #5
0
class TestSymbolicBackend(TestCase):
    '''
    Tests for the ymbolic symbolic_backend.
    '''
    def setUp(self):
        self.prg = Program()
        self.obs = ProgramObserver(self.prg)
        self.ctl = Control(message_limit=0)
        self.ctl.register_observer(self.obs)

    def test_add_acyc_edge(self):
        '''
        Test edge statement.
        '''
        a = Function("a", [Function("c1")])
        b = Function("b", [Function("c2")])
        c = Function("c", [Function("c3")])
        with SymbolicBackend(self.ctl.backend()) as symbolic_backend:
            symbolic_backend.add_acyc_edge(1, 3, [a], [b, c])
        self.assertEqual(str(self.prg),
                         "#edge (1,3): a(c1), not b(c2), not c(c3).")

    def test_add_assume(self):
        '''
        Test assumptions.
        '''
        a = Function("a", [Function("c1")])
        b = Function("b", [Function("c2")])
        c = Function("c", [Function("c3")])
        with SymbolicBackend(self.ctl.backend()) as symbolic_backend:
            symbolic_backend.add_assume([a, b, c])
        self.assertEqual(str(self.prg), "% assumptions: a(c1), b(c2), c(c3)")

    def test_add_external(self):
        '''
        Test external statement.
        '''
        a = Function("a", [Function("c1")])
        with SymbolicBackend(self.ctl.backend()) as symbolic_backend:
            symbolic_backend.add_external(a, TruthValue.True_)
        self.assertEqual(str(self.prg), "#external a(c1). [True]")

    def test_add_heuristic(self):
        '''
        Test heuristic statement.
        '''
        a = Function("a", [Function("c1")])
        b = Function("b", [Function("c2")])
        c = Function("c", [Function("c3")])
        with SymbolicBackend(self.ctl.backend()) as symbolic_backend:
            symbolic_backend.add_heuristic(a, HeuristicType.Level, 2, 3, [b],
                                           [c])
        self.assertEqual(str(self.prg),
                         "#heuristic a(c1): b(c2), not c(c3). [2@3, Level]")

    def test_add_minimize(self):
        '''
        Test minimize statement.
        '''
        a = Function("a", [Function("c1")])
        b = Function("b", [Function("c2")])
        c = Function("c", [Function("c3")])
        with SymbolicBackend(self.ctl.backend()) as symbolic_backend:
            symbolic_backend.add_minimize(1, [(a, 3), (b, 5)], [(c, 7)])
        self.assertEqual(
            str(self.prg),
            "#minimize{3@1,0: a(c1); 5@1,1: b(c2); 7@1,2: not c(c3)}.")

    def test_add_project(self):
        '''
        Test project statements.
        '''
        a = Function("a", [Function("c1")])
        b = Function("b", [Function("c2")])
        c = Function("c", [Function("c3")])
        with SymbolicBackend(self.ctl.backend()) as symbolic_backend:
            symbolic_backend.add_project([a, b, c])
        self.assertEqual(str(self.prg),
                         "#project a(c1).\n#project b(c2).\n#project c(c3).")

    def test_add_empty_project(self):
        '''
        Test project statements.
        '''
        with SymbolicBackend(self.ctl.backend()) as symbolic_backend:
            symbolic_backend.add_project([])
        self.assertEqual(str(self.prg), "#project x: #false.")

    def test_add_rule(self):
        '''
        Test simple rules.
        '''
        a = Function("a", [Function("c1")])
        b = Function("b", [Function("c2")])
        c = Function("c", [Function("c3")])
        with SymbolicBackend(self.ctl.backend()) as symbolic_backend:
            symbolic_backend.add_rule([a], [b], [c])
        self.assertEqual(str(self.prg), "a(c1) :- b(c2), not c(c3).")

    def test_add_choice_rule(self):
        '''
        Test choice rules.
        '''
        a = Function("a", [Function("c1")])
        b = Function("b", [Function("c2")])
        c = Function("c", [Function("c3")])
        with SymbolicBackend(self.ctl.backend()) as symbolic_backend:
            symbolic_backend.add_rule([a], [b], [c], choice=True)
        self.assertEqual(str(self.prg), "{a(c1)} :- b(c2), not c(c3).")

    def test_add_weight_rule(self):
        '''
        Test weight rules.
        '''
        a = Function("a", [Function("c1")])
        b = Function("b", [Function("c2")])
        c = Function("c", [Function("c3")])
        with SymbolicBackend(self.ctl.backend()) as symbolic_backend:
            symbolic_backend.add_weight_rule([a], 3, [(b, 5)], [(c, 7)])
        self.assertEqual(str(self.prg),
                         "a(c1) :- 3{5,0: b(c2), 7,1: not c(c3)}.")

    def test_add_weight_choice_rule(self):
        '''
        Test weight rules that are also choice rules.
        '''
        a = Function("a", [Function("c1")])
        b = Function("b", [Function("c2")])
        c = Function("c", [Function("c3")])
        with SymbolicBackend(self.ctl.backend()) as symbolic_backend:
            symbolic_backend.add_weight_rule([a],
                                             3, [(b, 5)], [(c, 7)],
                                             choice=True)
        self.assertEqual(str(self.prg),
                         "{a(c1)} :- 3{5,0: b(c2), 7,1: not c(c3)}.")
Exemple #6
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)