示例#1
0
    def test_solve_with_assumptions_complex(self):
        class F(Predicate):
            num1 = IntegerField()

        class G(Predicate):
            num1 = IntegerField()

        prgstr = """ 1 { g(N) : f(N) } 2."""
        f1 = F(1)
        f2 = F(2)
        f3 = F(3)
        g1 = G(1)
        g2 = G(2)
        g3 = G(3)
        ctrl = cclingo.Control(['-n 0'], unifier=[G])
        add_program_string(ctrl, prgstr)
        ctrl.add_facts([f1, f2, f3])
        ctrl.ground([("base", [])])

        num_models = 0

        def on_model(m):
            nonlocal num_models
            fb = m.facts(atoms=True)
            self.assertTrue(len(fb) <= 2)
            self.assertTrue(len(fb) >= 1)
            num_models += 1

        num_models = 0
        if oclingo.__version__ >= "5.5.0":
            ctrl.solve(on_model=on_model, assumptions=[])
        else:
            ctrl.solve(on_model=on_model, assumptions=None)
        self.assertEqual(num_models, 6)
        num_models = 0
        ctrl.solve(on_model=on_model)
        self.assertEqual(num_models, 6)

        num_models = 0
        ctrl.solve(on_model=on_model, assumptions=[(g1, True)])
        self.assertEqual(num_models, 3)

        # Mixing raw symbol and predicate in a set
        num_models = 0
        ctrl.solve(on_model=on_model, assumptions=[(set([g1.raw, g2]), True)])
        self.assertEqual(num_models, 1)
        num_models = 0
        ctrl.solve(on_model=on_model, assumptions=[(FactBase([g1, g2]), True)])
        self.assertEqual(num_models, 1)
        num_models = 0
        ctrl.solve(on_model=on_model,
                   assumptions=[(FactBase([g1]), True), (set([g2]), False)])
        self.assertEqual(num_models, 2)

        num_models = 0
        ctrl.solve(on_model=on_model,
                   assumptions=[(FactBase([g1]), True), (set([g2]), False)])
        self.assertEqual(num_models, 2)
示例#2
0
    def test_solve_with_on_finish(self):
        spu = SymbolPredicateUnifier()

        @spu.register
        class F(Predicate):
            num1 = IntegerField()

        f1 = F(1)
        f2 = F(2)
        f3 = F(3)
        infb = FactBase([f1, f2, f2])
        ctrl = cclingo.Control(['-n 0'])
        ctrl.add_facts(infb)
        ctrl.ground([("base", [])])

        called = False

        def on_model(m):
            nonlocal called
            outfb = m.facts(spu, atoms=True)
            self.assertEqual(infb, outfb)
            self.assertFalse(called)
            called = True

        def on_finish(sr):
            self.assertTrue(sr.satisfiable)
            self.assertFalse(sr.unsatisfiable)

        sr = ctrl.solve(on_model=on_model, on_finish=on_finish)
        self.assertTrue(sr.satisfiable)
        self.assertFalse(sr.unsatisfiable)
示例#3
0
    def test_solve_with_on_statistics(self):
        class F(Predicate):
            num1 = IntegerField()

        f1 = F(1)
        f2 = F(2)
        f3 = F(3)
        infb = FactBase([f1, f2, f2])
        ctrl = cclingo.Control(['-n 0'])
        ctrl.add_facts(infb)
        ctrl.ground([("base", [])])

        def on_model(model):
            nonlocal om_called
            om_called = True

        def on_statistics(stp, acc):
            nonlocal os_called
            self.assertEqual(type(stp), oclingo.StatisticsMap)
            os_called = True

        # Calling using positional arguments
        om_called = False
        os_called = False
        sr = ctrl.solve([], on_model, on_statistics)
        self.assertTrue(om_called)
        self.assertTrue(os_called)

        # Calling using keyword arguments
        om_called = False
        os_called = False
        sr = ctrl.solve(on_statistics=on_statistics)
        self.assertFalse(om_called)
        self.assertTrue(os_called)
示例#4
0
def run_fact_querying(num):
    global g_facts

    def go():
        global g_facts
        g_facts = create_facts(num)

    pr = Profiler("Timing for fact creation and querying")
    msg1 = "Intstantiating {} new fact instances".format(num)
    pr(msg1, go)
    fb1 = pr("Adding facts to non-indexed FactBase", lambda: FactBase(g_facts))
    fb2 = pr("Adding facts to indexed FactBase",
             lambda: FactBase(g_facts, indexes=[Q.anum, Q.ap.atuple[0]]))
    c1 = pr("Conjunctive query non-indexed FactBase", lambda: query_conj(fb1))
    c2 = pr("Conjunctive Query indexed FactBase", lambda: query_conj(fb2))
    c1 = pr("Disjunctive query non-indexed FactBase", lambda: query_disj(fb1))
    c2 = pr("Disjunctive Query indexed FactBase", lambda: query_disj(fb2))
    return pr
示例#5
0
    def test_control_access_others(self):
        class F(Predicate):
            num1 = IntegerField()

        f1 = F(1)
        f2 = F(2)
        f3 = F(3)
        infb = FactBase([f1, f2])
        ctrl = cclingo.Control(['-n 0'])
        ctrl.add_facts(infb)
        ctrl.ground([("base", [])])
        self.assertTrue(ctrl.symbolic_atoms[f1.raw])
示例#6
0
    def test_solve_with_assumptions_simple(self):
        spu = SymbolPredicateUnifier()

        @spu.register
        class F(Predicate):
            num1 = IntegerField()

        @spu.register
        class G(Predicate):
            num1 = IntegerField()

        prgstr = """{ g(N) : f(N) } = 1."""
        f1 = F(1)
        f2 = F(2)
        f3 = F(3)
        g1 = G(1)
        g2 = G(2)
        g3 = G(3)
        ctrl = cclingo.Control(['-n 0'])
        with ctrl.builder() as b:
            oclingo.parse_program(prgstr, lambda stm: b.add(stm))
        ctrl.add_facts([f1, f2, f3])
        ctrl.ground([("base", [])])

        num_models = 0

        def on_modelT(m):
            nonlocal num_models
            fb = m.facts(spu, atoms=True)
            self.assertTrue(g1 in fb)
            num_models += 1

        def on_modelF(m):
            nonlocal num_models
            fb = m.facts(spu, atoms=True)
            self.assertTrue(g1 not in fb)
            self.assertFalse(g1 in fb)
            num_models += 1

        num_models = 0
        ctrl.solve(on_model=on_modelT, assumptions=[(g1, True)])
        self.assertEqual(num_models, 1)
        num_models = 0
        ctrl.solve(on_model=on_modelT, assumptions=[(g1.raw, True)])
        self.assertEqual(num_models, 1)
        num_models = 0
        ctrl.solve(on_model=on_modelF, assumptions=[(g1, False)])
        self.assertEqual(num_models, 2)
        fb2 = FactBase([g1])
        num_models = 0
        ctrl.solve(on_model=on_modelT, assumptions=[(fb2, True)])
        self.assertEqual(num_models, 1)
示例#7
0
    def test_clingo_to_clorm_model_integration(self):
        spu = SymbolPredicateUnifier()

        @spu.register
        class Afact(Predicate):
            num1 = IntegerField()

        af1 = Afact(1)
        af2 = Afact(2)

        fb1 = FactBase()
        fb1.add([af1, af2])

        def on_model(model):
            cmodel = cclingo.Model(model=model)
            self.assertTrue(cmodel.contains(af1))
            self.assertTrue(model.contains(af1.raw))

        # Use the orignal clingo.Control object so that we can test the model wrapper
        ctrl = cclingo.Control()
        ctrl.add_facts(fb1)
        octrl = ctrl.control_
        octrl.ground([("base", [])])
        octrl.solve(on_model=on_model)
示例#8
0
def compare_query_times():
    print("=========================================================")
    print(("Comparing query times for different combinations of fact base vs "
           "search through raw clingo symbols\n"))

    # Build a list of complex facts and corresponding symbols
    (facts, facts_t) = generate_list(create_complex_fact)
    symbols = [f.raw for f in facts]

    # Test time to import into a factbase with indexing
    with Timer() as fb_import_timer:
        fb = FactBase(indexes=P.meta.indexes, facts=facts)
    print("Importing {} facts to factbase: {}".format(len(fb),
                                                      fb_import_timer))

    # Query
    q = 10
    # Test the time needed to do the search on the raw symbols
    with Timer() as fb_raw_search:
        r = []
        for s in symbols:
            if s.arguments[0].arguments[1].arguments[1].arguments[1].arguments[
                    0].number <= q:
                r.append(s)
    print("Raw Symbol search: {} => {}".format(len(r), fb_raw_search))

    # Now time the FactBase query results
    q_a_posn_c = fb.query(P).where(P[0][1][1][1][0] <= ph1_)
    q_a_name_c = fb.query(P).where(P.i.arg2.arg2.arg2.a <= ph1_)
    q_b_name_c = fb.query(P).where(P.i.arg2.arg2.arg2.b <= ph1_)
    q_a_posn_l = fb.query(P).where(lambda f, a: f[0][1][1][1][0] <= a)
    q_a_name_l = fb.query(P).where(lambda f, a: f.i.arg2.arg2.arg2.a <= a)

    with Timer() as t1:
        r = list(q_a_posn_c.bind(q).all())
    print("Clorm syntax positional arguments : {} => {}".format(len(r), t1))

    with Timer() as t2:
        r = list(q_a_name_c.bind(q).all())
    print("Clorm syntax named arguments: {} => {}".format(len(r), t2))

    with Timer() as t3:
        r = list(q_b_name_c.bind(q).all())
    print("Clorm syntax, indexed, named arguments: {} => {}".format(
        len(r), t3))

    with Timer() as t4:
        r = list(q_a_posn_l.bind(a=q).all())
    print("Lambda positional arguments: {} => {}".format(len(r), t4))

    with Timer() as t5:
        r = list(q_a_posn_c.bind(q).all())
    print("Lambda named arguments: {} => {}".format(len(r), t5))
    # Access
    print("--------------------------------------------------------\n")
示例#9
0
    def setUp(self):

        class Fun(ComplexTerm):
            aint = IntegerField()
            astr = StringField()

        class Tup(ComplexTerm):
            aint = IntegerField()
            astr = StringField()
            class Meta: is_tuple = True

        class Afact(Predicate):
            aint = IntegerField()
            afun = Fun.Field()

        class Bfact(Predicate):
            astr = StringField()
            atup = Tup.Field()

        class Cfact(ComplexTerm):
            aint = IntegerField()
            astr = StringField()

        afact1 = Afact(aint=10, afun=Fun(aint=1, astr="a"))
        afact2 = Afact(aint=20, afun=Fun(aint=2, astr="b"))
        bfact1 = Bfact(astr="aa", atup=Tup(aint=1, astr="a"))
        bfact2 = Bfact(astr="bb", atup=Tup(aint=2, astr="b"))
        self.allf = [ afact1, bfact1, afact2, bfact2 ]

        self.Fun = Fun
        self.Tup = Tup
        self.Afact = Afact
        self.Bfact = Bfact
        self.Cfact = Cfact

        self.n1 = clingo.Number(60)
        self.s1 = clingo.String("aaaa")
        self.f1 = clingo.Function("",[self.n1, self.s1])
        self.p1 = Cfact(aint=60, astr="aaaa")
        self.fb1 = FactBase(facts=[self.p1])

        self.str_n1 = '{"clingo.SymbolType": "Number", "number": 60}'
        self.str_s1 = '{"clingo.SymbolType": "String", "string": "aaaa"}'
        self.str_f1 = '{"clingo.SymbolType": "Function", "name": "cfact", ' \
            + '"arguments": [' + self.str_n1 + ', ' + self.str_s1 + ']' + ', "positive": true}'
        self.str_p1 = '{"clorm.Predicate": "Cfact", "raw": ' + self.str_f1 + '}'
        self.str_fb1 = '{"clorm.FactBase": [], "facts": ['+ self.str_p1 + ']}'
示例#10
0
    def test_factbase_coder(self):
        pc = cjson.FactBaseCoder()
        allf = self.allf
        fb1 = self.fb1
        str_fb1 = self.str_fb1
        Afact = pc.register(self.Afact)
        Bfact = pc.register(self.Bfact)
        Cfact = pc.register(self.Cfact)

        json_fb1 = pc.dumps(fb1)
        self.assertEqual(json_fb1, str_fb1)

        fb_in = FactBase(facts=allf, indexes=[Afact.aint, Bfact.astr])
        json_str = pc.dumps(fb_in, indent=4, sort_keys=True)
        fb_out = pc.loads(json_str)
        self.assertEqual(fb_in.indexes, fb_out.indexes)
        self.assertEqual(set(fb_in), set(fb_out))
        self.assertEqual(fb_in, fb_out)
示例#11
0
def run(indexed):
    msg1 = "Intstantiating new FactBase with {} facts".format(len(g_plist1))
    msg2 = "Adding {} facts to new empty FactBase".format(len(g_plist2))
    if indexed:
        pr = Profiler("Profiling creating and querying an indexed FactBase")
        fb1 = pr(msg1, lambda n: FactBase(n, indexes=[P.a]), g_plist1)
        fb2 = FactBase(indexes=[P.a])
        pr(msg2, lambda n: fb2.add(n), g_plist2)
    else:
        pr = Profiler("Profiling creating and querying a non-indexed FactBase")
        fb1 = pr(msg1, lambda n: FactBase(n), g_plist1)
        fb2 = FactBase()
        pr(msg2, lambda n: fb2.add(n), g_plist2)

    tmp1 = pr("Querying fact base to find one element", query_fb, fb1, 10000)
    pr("Union operation", fb1.union, fb2)
    pr("Intersection operation", fb1.intersection, fb2)
    pr("Difference operation", fb1.difference, fb2)
    pr("Symmetric difference operation", fb1.symmetric_difference, fb2)

    return pr
示例#12
0
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))
示例#13
0
    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]))
示例#14
0
    def test_expand_assumptions(self):
        class F(Predicate):
            num1 = IntegerField()

        class G(Predicate):
            num1 = IntegerField()

        f1 = F(1)
        f2 = F(2)
        g1 = G(1)

        r = set(_expand_assumptions([(f1, True), (g1, False)]))
        self.assertEqual(r, set([(f1.raw, True), (g1.raw, False)]))

        r = set(
            _expand_assumptions([(FactBase([f1, f2]), True),
                                 (set([g1]), False)]))
        self.assertEqual(
            r, set([(f1.raw, True), (f2.raw, True), (g1.raw, False)]))

        with self.assertRaises(TypeError) as ctx:
            _expand_assumptions([g1])
        with self.assertRaises(TypeError) as ctx:
            _expand_assumptions(g1)
示例#15
0
    def test_control_model_integration(self):
        spu = SymbolPredicateUnifier()

        @spu.register
        class Afact(Predicate):
            num1 = IntegerField()
            num2 = IntegerField()
            str1 = StringField()

        @spu.register
        class Bfact(Predicate):
            num1 = IntegerField()
            str1 = StringField()

        af1 = Afact(1, 10, "bbb")
        af2 = Afact(2, 20, "aaa")
        af3 = Afact(3, 20, "aaa")
        bf1 = Bfact(1, "aaa")
        bf2 = Bfact(2, "bbb")

        fb1 = FactBase()
        fb1.add([af1, af2, af3, bf1, bf2])

        fb2 = None

        def on_model(model):
            nonlocal fb2
            self.assertTrue(model.contains(af1))
            self.assertTrue(model.contains(af1.raw))
            self.assertTrue(model.model_.contains(af1.raw))
            fb2 = model.facts(spu, atoms=True)

            # Check that the known attributes behave the same as the real model
            self.assertEqual(model.cost, model.model_.cost)
            self.assertEqual(model.number, model.model_.number)
            self.assertEqual(model.optimality_proven,
                             model.model_.optimality_proven)
            self.assertEqual(model.thread_id, model.model_.thread_id)
            self.assertEqual(model.type, model.model_.type)

            # Note: the SolveControl object returned is created dynamically on
            # each call so will be different for both calls. So test that the
            # symbolic_atoms property is the same.
            sas1 = set(model.context.symbolic_atoms)
            sas2 = set(model.model_.context.symbolic_atoms)
            self.assertEqual(len(sas1), len(sas2))

            # Test that clorm.clingo.Model produces the correct string
            self.assertEqual(str(model), str(model.model_))

            if oclingo.__version__ < "5.5.0":
                self.assertEqual(repr(model), repr(model.model_))

        # Use the orignal clingo.Control object so that we can test the wrapper call
        ctrlX_ = oclingo.Control()
        ctrl = cclingo.Control(control_=ctrlX_)

        ctrl.add_facts(fb1)
        ctrl.ground([("base", [])])
        ctrl.solve(on_model=on_model)

        # Check that the known control attributes behave the same as the real control
        cfg1 = ctrl.configuration
        cfg2 = ctrl.control_.configuration

        self.assertEqual(len(cfg1), len(cfg2))
        self.assertEqual(set(cfg1.keys), set(cfg2.keys))
        sas1 = set(ctrl.symbolic_atoms)
        sas2 = set(ctrl.control_.symbolic_atoms)
        self.assertEqual(len(sas1), len(sas2))
        self.assertEqual(ctrl.is_conflicting, ctrl.control_.is_conflicting)
        stat1 = ctrl.statistics
        stat2 = ctrl.control_.statistics
        self.assertEqual(len(stat1), len(stat2))
        tas1 = ctrl.theory_atoms
        tas2 = ctrl.control_.theory_atoms
        self.assertEqual(len(list(tas1)), len(list(tas2)))

        # _control_add_facts works with both a list of facts (either
        # clorm.Predicate or clingo.Symbol instances) and a FactBase
        ctrl2 = Control()
        ctrl2.add_facts([af1, af2, af3.raw, bf1.raw, bf2])

        safact1 = fb2.query(Afact).where(Afact.num1 == ph1_)
        safact2 = fb2.query(Afact).where(Afact.num1 < ph1_)
        self.assertEqual(safact1.bind(1).singleton(), af1)
        self.assertEqual(safact1.bind(2).singleton(), af2)
        self.assertEqual(safact1.bind(3).singleton(), af3)
        self.assertEqual(set(list(safact2.bind(1).all())), set([]))
        self.assertEqual(set(list(safact2.bind(2).all())), set([af1]))
        self.assertEqual(set(list(safact2.bind(3).all())), set([af1, af2]))
        self.assertEqual(
            fb2.query(Bfact).where(Bfact.str1 == "aaa").singleton(), bf1)
        self.assertEqual(
            fb2.query(Bfact).where(Bfact.str1 == "bbb").singleton(), bf2)
示例#16
0
def make_indexed_fbs(p_list1, p_list2):
    fb1 = FactBase(p_list1, indexes=[P.a])
    fb2 = FactBase(indexes=[P.a])
    fb2.add(p_list2)
    return fb2, fb2
示例#17
0
def make_fbs(p_list1, p_list2):
    fb1 = FactBase(p_list1)
    fb2 = FactBase()
    fb2.add(p_list2)
    return fb2, fb2
示例#18
0
    def test_solve_returning_solvehandle(self):
        spu = SymbolPredicateUnifier()

        @spu.register
        class F(Predicate):
            num1 = IntegerField()

        f1 = F(1)
        f2 = F(2)
        f3 = F(3)
        infb = FactBase([f1, f2, f3])
        ctrl = cclingo.Control(['-n 0'])
        ctrl.add_facts(infb)
        ctrl.ground([("base", [])])

        done = False

        def on_model(m):
            nonlocal done
            outfb = m.facts(spu, atoms=True)
            self.assertEqual(infb, outfb)
            self.assertFalse(done)
            done = True

        if oclingo.__version__ > '5.3.1':
            asynckw = "async_"
        else:
            asynckw = "async"

        # Test the async mode
        kwargs = {"on_model": on_model, asynckw: True}
        done = False
        sh = ctrl.solve(**kwargs)
        self.assertTrue(isinstance(sh, cclingo.SolveHandle))
        sh.get()
        self.assertTrue(done)

        # Test the yield mode
        kwargs = {"on_model": on_model, "yield_": True}
        done = False
        sh = ctrl.solve(**kwargs)
        self.assertTrue(isinstance(sh, cclingo.SolveHandle))
        count = 0
        for m in sh:
            count += 1
            outfb = m.facts(spu, atoms=True)
            self.assertEqual(infb, outfb)
        self.assertEqual(count, 1)
        self.assertTrue(done)

        # Test both async and yield mode
        kwargs = {"on_model": on_model, asynckw: True, "yield_": True}
        done = False
        sh = ctrl.solve(**kwargs)
        self.assertTrue(isinstance(sh, cclingo.SolveHandle))
        count = 0
        for m in sh:
            count += 1
            outfb = m.facts(spu, atoms=True)
            self.assertEqual(infb, outfb)
        self.assertEqual(count, 1)
        self.assertTrue(done)
示例#19
0
def make_fb(facts, indexed):
    if indexed:
        return FactBase(facts, indexes=[Customer.cid, Sale.cid])
    else:
        return FactBase(facts)
示例#20
0
    def test_assign_and_release_external(self):
        class F(Predicate):
            num1 = IntegerField()

        class G(Predicate):
            num1 = IntegerField()

        prgstr = """
#external f(1..3).
g(N) :- f(N)."""

        f1 = F(1)
        f2 = F(2)
        f3 = F(3)
        g1 = G(1)
        g2 = G(2)
        g3 = G(3)
        ctrl = cclingo.Control(unifier=[F, G])
        add_program_string(ctrl, prgstr)
        ctrl.ground([("base", [])])

        # Assign external for a factbase
        ctrl.assign_external(FactBase([f1, f2, f3]), True)
        with ctrl.solve(yield_=True) as sh:
            m = list(sh)[0]
            fb = m.facts(atoms=True)
            self.assertEqual(fb, FactBase([f1, f2, f3, g1, g2, g3]))

        # Assign external for a single clorm fact
        ctrl.assign_external(f1, False)
        with ctrl.solve(yield_=True) as sh:
            m = list(sh)[0]
            fb = m.facts(atoms=True)
            self.assertEqual(fb, FactBase([f2, f3, g2, g3]))

        # Assign external for a single clingo symbol
        ctrl.assign_external(f2.raw, False)
        with ctrl.solve(yield_=True) as sh:
            m = list(sh)[0]
            fb = m.facts(atoms=True)
            self.assertEqual(fb, FactBase([f3, g3]))

        # Back to all true so we can test release_external
        # Assign external for a factbase
        ctrl.assign_external(FactBase([f1, f2, f3]), True)
        with ctrl.solve(yield_=True) as sh:
            m = list(sh)[0]
            fb = m.facts(atoms=True)
            self.assertEqual(fb, FactBase([f1, f2, f3, g1, g2, g3]))

        # Release external for a FactBase
        ctrl.release_external(FactBase([f1]))
        with ctrl.solve(yield_=True) as sh:
            m = list(sh)[0]
            fb = m.facts(atoms=True)
            self.assertEqual(fb, FactBase([f2, f3, g2, g3]))

        # Release external for a single clorm fact
        ctrl.release_external(f2)
        with ctrl.solve(yield_=True) as sh:
            m = list(sh)[0]
            fb = m.facts(atoms=True)
            self.assertEqual(fb, FactBase([f3, g3]))

        # Release external for a single clingo symbol
        ctrl.release_external(f3.raw)
        with ctrl.solve(yield_=True) as sh:
            m = list(sh)[0]
            fb = m.facts(atoms=True)
            self.assertEqual(fb, FactBase())