//
// Copyright (c) 2006-present Benjamin Kaufmann
//
// This file is part of Clasp. See https://potassco.org/clasp/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
#include "lpcompare.h"
#include <clasp/program_builder.h>

#include <clasp/enumerator.h>
#include <clasp/logic_program.h>
#include <clasp/minimize_constraint.h>
#include <clasp/solver.h>
#include <clasp/unfounded_check.h>

#include <potassco/aspif.h>
#include <potassco/smodels.h>

#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>
#include <catch2/matchers/catch_matchers_range_equals.hpp>

using namespace std;

namespace Clasp::Test {
using namespace Clasp::Asp;
struct ClauseObserver : DecisionHeuristic {
    Literal doSelect(Solver&) override { return {}; }
    void    updateVar(const Solver&, Var_t, uint32_t) override {}
    void    newConstraint(const Solver&, LitView lits, ConstraintType) override {
        Clause c;
        for (auto lit : lits) { c.push_back(lit); }
        ranges::sort(c);
        clauses.push_back(c);
    }
    using Clause  = std::vector<Literal>;
    using Clauses = std::vector<Clause>;
    Clauses clauses;
};
void writeAspif(LogicProgram& prg, std::stringstream& str) {
    Potassco::AspifOutput out(str);
    prg.accept(out, true);
}
void writeSmodels(LogicProgram& prg, std::stringstream& str) {
    Potassco::SmodelsOutput out(str, true, prg.falseAtom());
    prg.accept(out, true);
}

TEST_CASE("Logic program types", "[asp]") {
    SECTION("testInvalidNodeId") {
        REQUIRE_THROWS_AS(PrgNode(PrgNode::no_node + 1, PrgNode::atom), std::overflow_error);
    }
    SECTION("testMergeValue") {
        PrgAtom lhs(0), rhs(1);
        Val_t   ok[15]  = {value_free,      value_free,      value_free,  value_free,      value_true,
                           value_true,      value_free,      value_false, value_false,     value_free,
                           value_weak_true, value_weak_true, value_true,  value_weak_true, value_true};
        Val_t   fail[4] = {value_true, value_false, value_weak_true, value_false};
        for (uint32_t x : irange(2u)) {
            for (uint32_t i = 0; i != 15; i += 3) {
                lhs.clearLiteral(true);
                rhs.clearLiteral(true);
                REQUIRE(lhs.assignValue(ok[i + x]));
                REQUIRE(rhs.assignValue(ok[i + (not x)]));
                REQUIRE(mergeValue(&lhs, &rhs));
                REQUIRE((lhs.value() == ok[i + 2] && rhs.value() == ok[i + 2]));
            }
        }
        for (uint32_t x : irange(2u)) {
            for (uint32_t i = 0; i != 4; i += 2) {
                lhs.clearLiteral(true);
                rhs.clearLiteral(true);
                REQUIRE(lhs.assignValue(fail[i + x]));
                REQUIRE(rhs.assignValue(fail[i + (not x)]));
                REQUIRE_FALSE(mergeValue(&lhs, &rhs));
            }
        }
    }
}

static auto getTheoryOutput(OutputTable::Theory& t, ValueView values,
                            Model::Type mt = Model::sat) -> std::vector<std::string> {
    std::vector<std::string> res;
    Model                    m;
    m.num    = 1;
    m.values = values;
    m.type   = mt;
    for (const char* x = t.first(m); x; x = t.next()) { res.emplace_back(x); }
    return res;
}

TEST_CASE("Logic program", "[asp]") {
    Var_t         a = 1, b = 2, c = 3, d = 4, f = 6;
    SharedContext ctx;
    LogicProgram  lp;
    SECTION("testIgnoreRules") {
        lpAdd(lp.start(ctx), "a :- a. b :- a.\n");
        REQUIRE(1u == lp.stats.rules[0].sum());
    }
    SECTION("testDuplicateRule") {
        lpAdd(lp.start(ctx, LogicProgram::AspOptions().iterations(1)), "{b}.\n"
                                                                       "a :- b.\n"
                                                                       "a :- b.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(lp.getLiteral(a) == lp.getLiteral(b));
    }

    SECTION("testNotAChoice") {
        lpAdd(lp.start(ctx), "{b}.\n"
                             "{a} :- not b."
                             "a :-not b.");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        ctx.master()->assume(~lp.getLiteral(b)) && ctx.master()->propagate();
        // if b is false, a must be true because a :- not b. is stronger than {a} :- not b.
        REQUIRE(ctx.master()->isTrue(lp.getLiteral(a)));
    }

    SECTION("testNotAChoiceMerge") {
        lpAdd(lp.start(ctx), "{b}.\n"
                             "{a} :- b.\n"
                             "a :- b, not c.");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(lp.getLiteral(a) == lp.getLiteral(b));
    }

    SECTION("testMergeToSelfblocker") {
        lpAdd(lp.start(ctx), "a :- not b.\n"
                             "b :- a.\n");
        REQUIRE_FALSE((lp.endProgram() && ctx.endInit()));
    }

    SECTION("testMergeToSelfblocker2") {
        lpAdd(lp.start(ctx), "a :- not d.\n"
                             "a :- not c.\n"
                             "b :- not c.\n"
                             "c :- a.");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(ctx.master()->isTrue(lp.getLiteral(a)));
        REQUIRE(ctx.numVars() == 0);
    }
    SECTION("testMergeToSelfblocker3") {
        lpAdd(lp.start(ctx), "b :- a.\n"
                             "{c}.\n"
                             "d :- 2 {a, b}.\n"
                             "e :- 2 {a}.\n"
                             "a :- not d, not e.\n");
        REQUIRE_FALSE(lp.endProgram());
        REQUIRE(lp.frozen());
    }

    SECTION("testDerivedTAUT") {
        lpAdd(lp.start(ctx), "{x1;x2}.\n"
                             "x3 :- not x2.\n"
                             "x4 :- x3.\n"
                             "x3 :- x1, x4.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(ctx.numVars() == 2);
    }

    SECTION("testOneLoop") {
        lpAdd(lp.start(ctx), "a :- not b.\n"
                             "b :- not a.\n"
                             "#output a : a.");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(1u == ctx.numVars());
        REQUIRE(0u == ctx.numConstraints());
        REQUIRE(lp.getLiteral(a) == ~lp.getLiteral(b));
        REQUIRE(lp.getOutputState(a) == Asp::LogicProgram::out_shown);
        REQUIRE(lp.getOutputState(b) == Asp::LogicProgram::out_none);
        REQUIRE(lp.getOutputState(b, MapLit::refined) == Asp::LogicProgram::out_none);
    }

    SECTION("testZeroLoop") {
        lpAdd(lp.start(ctx), "a :- b.\n"
                             "b :- a.\n"
                             "a :- not c.\n"
                             "c :- not a.\n"
                             "#output a : a.");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(1u == ctx.numVars());
        REQUIRE(0u == ctx.numConstraints());
        REQUIRE(lp.getLiteral(a) == lp.getLiteral(b));
        REQUIRE(lp.getOutputState(a) == Asp::LogicProgram::out_shown);
        REQUIRE(lp.getOutputState(b) == Asp::LogicProgram::out_none);
        REQUIRE(lp.getOutputState(b, MapLit::refined) == Asp::LogicProgram::out_shown);
    }

    SECTION("testEqSuccs") {
        lpAdd(lp.start(ctx), "{a;b}.\n"
                             "c :- a, b.\n"
                             "d :- a, b.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(3u == ctx.numVars());
        REQUIRE(3u == ctx.numConstraints());
        REQUIRE(lp.getLiteral(c) == lp.getLiteral(d));
    }

    SECTION("testEqCompute") {
        lpAdd(lp.start(ctx), "{c}.\n"
                             "a :- not c.\n"
                             "a :- b.\n"
                             "b :- a.\n"
                             "  :- not b.");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(ctx.master()->isTrue(lp.getLiteral(a)));
        REQUIRE(lp.getLiteral(a) == lp.getLiteral(b));
        REQUIRE(lp.isFact(a));
        REQUIRE(lp.isFact(b));
        REQUIRE(lp.inProgram(a));
    }

    SECTION("testFactsAreAsserted") {
        lpAdd(lp.start(ctx), "a :- not b.\n"
                             "c.");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(ctx.master()->isTrue(lp.getLiteral(a)));
        REQUIRE(ctx.master()->isTrue(lp.getLiteral(c)));
    }
    SECTION("testSelfblockersAreAsserted") {
        lpAdd(lp.start(ctx), "x1 :- not x1.\n"
                             "x2 :- not x1.\n");
        REQUIRE_FALSE((lp.endProgram() && ctx.endInit()));
    }
    SECTION("testConstraintsAreAsserted") {
        lpAdd(lp.start(ctx), "a :- not b.\n"
                             "b :- not a.\n"
                             "  :- a.");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(a)));
    }

    SECTION("testConflictingCompute") {
        lpAdd(lp.start(ctx), "  :- a.\n"
                             "  :- not a.");
        REQUIRE_FALSE((lp.endProgram() && ctx.endInit()));
        REQUIRE_FALSE(ctx.ok());
    }
    SECTION("testForceUnsuppAtomFails") {
        lpAdd(lp.start(ctx), "a :- not b. :- not b.");
        REQUIRE_FALSE((lp.endProgram() && ctx.endInit()));
    }

    SECTION("testTrivialConflictsAreDetected") {
        lpAdd(lp.start(ctx), "a :- not a. :- not a.");
        REQUIRE_FALSE((lp.endProgram() && ctx.endInit()));
    }
    SECTION("testBuildEmpty") {
        lp.start(ctx);
        lp.endProgram();
        REQUIRE(0u == ctx.numVars());
        REQUIRE(0u == ctx.numConstraints());
    }
    SECTION("testAddOneFact") {
        lpAdd(lp.start(ctx), "a.");
        REQUIRE(lp.endProgram());
        REQUIRE(0u == ctx.numVars());
        // a fact does not introduce a constraint, it is just a top-level assignment
        REQUIRE(0u == ctx.numConstraints());
        REQUIRE(lp.isFact(a));
        REQUIRE_FALSE(lp.inProgram(a));
        REQUIRE(lp.inProgram(lp.getRootId(a)));
    }

    SECTION("testTwoFactsOnlyOneVar") {
        lpAdd(lp.start(ctx), "a. b.");
        REQUIRE(lp.endProgram());
        REQUIRE(0u == ctx.numVars());
        REQUIRE(0u == ctx.numConstraints());
        REQUIRE(lp.isFact(a));
        REQUIRE(lp.isFact(b));
    }

    SECTION("testDontAddDuplicateSumBodies") {
        lpAdd(lp.start(ctx), "{a; b; c}.\n"
                             "d :- 2 {a=1, b=2, not c=1}.\n"
                             "e :- 2 {a=1, b=2, not c=1}.\n");
        REQUIRE(lp.endProgram());
        REQUIRE(lp.numBodies() == 2);
    }

    SECTION("testDontAddDuplicateSumBodies2") {
        lpAdd(lp.start(ctx), "{a; b; c; d; e; f; g}.\n"
                             "{h;i} :- 7 {a=2, b=2, not c=2, not d=2, e=2, f=2, g=2}.\n"
                             "{h;i} :- 7 {a=2, b=2, not c=2, not d=2, e=2, f=2, g=2}.\n");
        REQUIRE(lp.endProgram());
        REQUIRE(lp.numBodies() == 2);
    }

    SECTION("testAddLargeSumBody") {
        VarVec            atoms;
        Potassco::WLitVec lits;
        lp.start(ctx);
        for (auto i : irange(1u, 21u)) { atoms.push_back(i); }
        lp.addRule(HeadType::choice, atoms, {});

        atoms.assign(1, 20);
        for (auto i : irange(1, 20)) {
            Potassco::WeightLit wl = {i & 1 ? Potassco::lit(i) : Potassco::neg(i), i};
            lits.push_back(wl);
        }
        lp.addRule(HeadType::disjunctive, atoms, 10, lits);
        lits.clear();
        for (int32_t i = 20; --i;) {
            Potassco::WeightLit wl = {i & 1 ? Potassco::lit(i) : Potassco::neg(i), 20 - i};
            lits.push_back(wl);
        }
        lp.addRule(HeadType::disjunctive, atoms, 10, lits);
        REQUIRE(lp.endProgram());
        REQUIRE(lp.numBodies() == 3u);
    }
    SECTION("testDontAddDuplicateSimpBodies") {
        lpAdd(lp.start(ctx), "{x1;x2;x3;x4}.\n"
                             "x1 :- x2, x3, x4.\n"
                             "x1 :- 8 {x3=2, x2=3, x4=4}.");
        REQUIRE(lp.endProgram());
        REQUIRE(lp.numBodies() == 2);
    }

    SECTION("testDontAddUnsupportedExtNoEq") {
        lpAdd(lp.start(ctx, LogicProgram::AspOptions().noEq()), "a :- not d.\n"
                                                                "c :- a, d.\n"
                                                                "b :- 2 {a, c, not d}. % -> a\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(c)));
        REQUIRE(ctx.master()->isTrue(lp.getLiteral(a)));
        REQUIRE(ctx.master()->isTrue(lp.getLiteral(b)));
    }

    SECTION("testAssertSelfblockers") {
        lpAdd(lp.start(ctx), "a :- b, not c.\n"
                             "c :- b, not c.\n"
                             "b.");
        // Program is unsat because b must be true and false at the same time.
        REQUIRE_FALSE((lp.endProgram() && ctx.endInit()));
    }

    SECTION("testRegressionR1") {
        lpAdd(lp.start(ctx), "b. b :- not c.");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(lp.getLiteral(b) == lit_true);
        REQUIRE(lp.getLiteral(c) == lit_false);
        REQUIRE(ctx.numVars() == 0);
    }

    SECTION("testRegressionR2") {
        lpAdd(lp.start(ctx), "b :- not c.\n"
                             "b :- not d.\n"
                             "c :- not b.\n"
                             "d :- not b.\n"
                             "b.");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(lp.getLiteral(b) == lit_true);
        REQUIRE(lp.getLiteral(c) == lit_false);
        REQUIRE(lp.getLiteral(d) == lit_false);
        REQUIRE(ctx.numVars() == 0);
    }
    SECTION("testFuzzBug") {
        lpAdd(lp.start(ctx), "x1 :- not x1, not x2, not x3, not x2, not x1, not x4.");
        REQUIRE_FALSE((lp.endProgram() && ctx.endInit()));
    }
    SECTION("testBackpropNoEqBug") {
        lpAdd(lp.start(ctx, LogicProgram::AspOptions().noEq().backpropagate()), "{x2;x3;x4} :- x5.\n"
                                                                                "x1 :- x7, x5.\n"
                                                                                "x5.\n"
                                                                                "x7 :- x2, x3.\n"
                                                                                "x7 :- x6.\n"
                                                                                "x6 :- x8.\n"
                                                                                "x8 :- x4.\n"
                                                                                ":- x1.");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(6)));
    }

    SECTION("testBug") {
        lpAdd(lp.start(ctx), "x1 :- x2.\n"
                             "x2 :- x3, x1.\n"
                             "x3 :- x4.\n"
                             "x4 :-not x3.\n");
        REQUIRE_FALSE((lp.endProgram() && ctx.endInit()));
    }

    SECTION("testSatBodyBug") {
        lpAdd(lp.start(ctx), "{c;b;a}.\n"
                             "a.\n"
                             "b :- 1 {a, c}.\n"
                             "d :- b, c.\n");
        REQUIRE(lp.getAtom(c)->deps().size() <= 2u);
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(lp.getAtom(c)->deps().size() == 1u);
        REQUIRE(lp.getAtom(b)->deps().empty());
        REQUIRE(value_free == ctx.master()->value(lp.getLiteral(c).var()));
    }
    SECTION("testDepBodyBug") {
        lpAdd(lp.start(ctx), "{d;e;f}.\n"
                             "a :- d, e.\n"
                             "b :- a."
                             "c :- b, f, a.\n");
        REQUIRE(lp.endProgram());
        REQUIRE(lp.getAtom(a)->deps().size() == 2u);
    }

    SECTION("testAddUnknownAtomToMinimize") {
        lpAdd(lp.start(ctx), "#minimize{a}.");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(lp.hasMinimize());
    }

    SECTION("testWriteWeakTrue") {
        lpAdd(lp.start(ctx), "{c}.\n"
                             "a :- not b, c.\n"
                             "b :- not a.\n"
                             "  :- not a.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        a = lp.falseAtom();
        std::stringstream exp;
        exp << "1 " << a << " 1 1 3\n";
        REQUIRE(findSmodels(exp, lp));
    }

    SECTION("testSimplifyBodyEqBug") {
        lpAdd(lp.start(ctx), "{d;e;f}.\n"
                             "a :- d,f.\n"
                             "b :- d,f.\n"
                             "c :- a, e, b.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        PrgAtom* na = lp.getAtom(a);
        PrgAtom* nb = lp.getAtom(b);
        REQUIRE((nb->eq() && nb->id() == a));
        REQUIRE(na->deps().size() == 1u);
    }

    SECTION("testNoEqSameLitBug") {
        lpAdd(lp.start(ctx, LogicProgram::AspOptions().noEq()), "{x4;x5}.\n"
                                                                "x1 :- x4,x5.\n"
                                                                "x2 :- x4,x5.\n"
                                                                "x3 :- x1, x2.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(ctx.numVars() == 5);
    }

    SECTION("testAssertEqSelfblocker") {
        lpAdd(lp.start(ctx), "a :- not b, not c.\n"
                             "a :- not d, not e.\n"
                             "b :- not c.\n"
                             "c :- not b.\n"
                             "d :- not e.\n"
                             "e :- not d.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(2u == ctx.numVars());
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(a)));
    }

    SECTION("testAddClauses") {
        ClauseObserver* o;
        ctx.master()->setHeuristic(o = new ClauseObserver());
        lpAdd(lp.start(ctx), "a :- not b.\n"
                             "a :- b, c.\n"
                             "b :- not a.\n"
                             "c :- not b.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(3u == ctx.numVars());
        REQUIRE(0u == ctx.master()->numAssignedVars());

        REQUIRE(8u == ctx.numConstraints());

        Var_t bodyNotB = 1;
        Var_t bodyBC   = 3;
        REQUIRE(VarType::body == ctx.varInfo(3).type());
        auto litA = lp.getLiteral(a);
        REQUIRE(lp.getLiteral(b) == ~lp.getLiteral(a));

        // a - HeadClauses
        ClauseObserver::Clause ac;
        ac.push_back(~litA);
        ac.push_back(posLit(bodyNotB));
        ac.push_back(posLit(bodyBC));
        ranges::sort(ac);
        REQUIRE(contains(o->clauses, ac));

        // bodyNotB - Body clauses
        ClauseObserver::Clause cl;
        cl.push_back(negLit(bodyNotB));
        cl.push_back(~lp.getLiteral(b));
        ranges::sort(cl);
        REQUIRE(contains(o->clauses, cl));
        cl.clear();
        cl.push_back(posLit(bodyNotB));
        cl.push_back(lp.getLiteral(b));
        ranges::sort(cl);
        REQUIRE(contains(o->clauses, cl));
        cl.clear();
        cl.push_back(negLit(bodyNotB));
        cl.push_back(litA);
        ranges::sort(cl);
        REQUIRE(contains(o->clauses, cl));

        // bodyBC - Body clauses
        cl.clear();
        cl.push_back(negLit(bodyBC));
        cl.push_back(lp.getLiteral(b));
        ranges::sort(cl);
        REQUIRE(contains(o->clauses, cl));
        cl.clear();
        cl.push_back(negLit(bodyBC));
        cl.push_back(lp.getLiteral(c));
        ranges::sort(cl);
        REQUIRE(contains(o->clauses, cl));
        cl.clear();
        cl.push_back(posLit(bodyBC));
        cl.push_back(~lp.getLiteral(b));
        cl.push_back(~lp.getLiteral(c));
        ranges::sort(cl);
        REQUIRE(contains(o->clauses, cl));
        cl.clear();
        cl.push_back(negLit(bodyBC));
        cl.push_back(litA);
        ranges::sort(cl);
        REQUIRE(contains(o->clauses, cl));
    }

    SECTION("testAddEmptyMinimizeConstraint") {
        lpAdd(lp.start(ctx), "#minimize{}.");
        REQUIRE(lp.endProgram());
        REQUIRE(ctx.hasMinimize());
    }

    SECTION("testAddFactMinimizeConstraint") {
        lpAdd(lp.start(ctx), "#minimize{a}. a.");
        REQUIRE(lp.endProgram());
        REQUIRE(ctx.hasMinimize());
        std::stringstream str;
        writeAspif(lp, str);
        REQUIRE(str.str().find("2 0 1 1") != std::string::npos);
    }

    SECTION("testNonTight") {
        lpAdd(lp.start(ctx), "b :- c.\n"
                             "c :- b.\n"
                             "b :- not a.\n"
                             "c :- not a.\n"
                             "a :- not b.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(lp.stats.sccs != 0);
    }

    SECTION("testIgnoreCondFactsInLoops") {
        lpAdd(lp.start(ctx), "{a}.\n"
                             "b :- a.\n"
                             "a :- b.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(lp.stats.sccs == 0);
    }

    SECTION("testCrEqBug") {
        lpAdd(lp.start(ctx), "a.\n"
                             "{b}.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(1u == ctx.numVars());
        REQUIRE(value_free == ctx.master()->value(lp.getLiteral(b).var()));
    }

    SECTION("testEqOverComp") {
        lpAdd(lp.start(ctx), "a :- not b.\n"
                             "a :- b.\n"
                             "b :- not c.\n"
                             "c :- not a.\n");
        REQUIRE(lp.endProgram());
        REQUIRE(lp.getLiteral(a) == lp.getLiteral(b));
        REQUIRE((ctx.master()->numFreeVars() == 0 && ctx.master()->isTrue(lp.getLiteral(a))));
    }

    SECTION("testEqOverBodiesOfDiffType") {
        lpAdd(lp.start(ctx), "{a;b}.\n"
                             "d :- 2{a, b, c}.\n"
                             "c :- d.\n"
                             "  :- b.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(3u >= ctx.numVars());
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(b)));
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(c)));
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(d)));
    }

    SECTION("testNoBodyUnification") {
        Var_t g = 7;
        lpAdd(lp.start(ctx), "{a;b;c}.\n"
                             "d :- a, g.\n"
                             "d :- b.\n"
                             "e :- not d.\n"
                             "f :- not e.\n"
                             "g :- d.\n"
                             "g :- c.\n");
        REQUIRE((lp.endProgram() && ctx.sccGraph.get()));
        ctx.master()->addPost(new DefaultUnfoundedCheck(*ctx.sccGraph));
        REQUIRE(ctx.endInit());
        ctx.master()->assume(~lp.getLiteral(b));
        ctx.master()->propagate();
        ctx.master()->assume(~lp.getLiteral(c));
        ctx.master()->propagate();
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(g)));
    }

    SECTION("testNoEqAtomReplacement") {
        lpAdd(lp.start(ctx), "{a;b}.\n"
                             "c :- a.\n"
                             "c :- b.\n"
                             "d :- not c.\n"
                             "e :- not d.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        ctx.master()->assume(~lp.getLiteral(a));
        ctx.master()->propagate();
        ctx.master()->assume(~lp.getLiteral(b));
        ctx.master()->propagate();
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(c)));
    }

    SECTION("testAllBodiesSameLit") {
        lpAdd(lp.start(ctx), "{a}.\n"
                             "b :- not a.\n"
                             "c :- not b.\n"
                             "d :- b.\n"
                             "d :- not c.\n"
                             "b :- d.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(lp.getLiteral(b) == lp.getLiteral(d));
        REQUIRE(lp.getLiteral(a) != lp.getLiteral(c));
        ctx.master()->assume(lp.getLiteral(a)) && ctx.master()->propagate();
        REQUIRE(ctx.master()->value(lp.getLiteral(c).var()) == value_free);
        ctx.master()->assume(~lp.getLiteral(c)) && ctx.master()->propagate();
        REQUIRE((ctx.master()->numFreeVars() == 0 && ctx.master()->isTrue(lp.getLiteral(b))));
    }

    SECTION("testAllBodiesSameLit2") {
        lpAdd(lp.start(ctx), "{a;e}.\n"
                             "b :- not a.\n"
                             "c :- not b.\n"
                             "d :- b.\n"
                             "d :- not c.\n"
                             "b :- d, e.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(lp.getLiteral(b) == lp.getLiteral(d));
        REQUIRE(lp.getLiteral(a) != lp.getLiteral(c));
        ctx.master()->assume(lp.getLiteral(a)) && ctx.master()->propagate();
        REQUIRE(ctx.master()->value(lp.getLiteral(c).var()) == value_free);
        ctx.master()->assume(~lp.getLiteral(c)) && ctx.master()->propagate();
        REQUIRE((ctx.master()->numFreeVars() == 0 && ctx.master()->isTrue(lp.getLiteral(b))));
        REQUIRE(ctx.numVars() == 4);
    }

    SECTION("testCompLit") {
        lpAdd(lp.start(ctx), "{d}.\n"
                             "a :- not c.\n"
                             "c :- not a.\n"
                             "b :- a, c.\n"
                             "e :- a, d, not c. % a == ~c -> {a,c} = F -> {a, not c} = {a, d}\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(3u == ctx.numVars());
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(b)));
    }

    SECTION("testFunnySelfblockerOverEqByTwo") {
        lpAdd(lp.start(ctx), "{a,b,c}.\n"
                             "d :- a, b.\n"
                             "e :- a, b.\n"
                             "f :- b, c.\n"
                             "g :- b, c.\n"
                             "f :- d, not f.\n"
                             "h :- e, not g.\n"
                             "i :- a, h.\n"
                             "% -> d == e, f == g -> {e, not g} == {d, not f} -> F"
                             "% -> h == i are both unsupported!");
        Var_t h = 8, i = 9;
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(h)));
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(i)));
    }

    SECTION("testSimplifyToTrue") {
        lpAdd(lp.start(ctx), "a.\n"
                             "b :-  1 {c, a}.");
        REQUIRE(lp.endProgram());
        REQUIRE(lp.numBodies() <= 1);
    }

    SECTION("testSimplifyToCardBug") {
        lpAdd(lp.start(ctx), "a. b.\n"
                             "c :- 168 {not a = 576, not b=381}.\n"
                             "  :- c.\n");
        REQUIRE(lp.endProgram());
        REQUIRE(ctx.master()->numFreeVars() == 0);
    }

    SECTION("testSimplifyCompBug") {
        Var_t h = 8;
        lpAdd(lp.start(ctx, LogicProgram::AspOptions().iterations(1)), "a :- not b.\n"
                                                                       "a :- b.\n"
                                                                       "b :- not c.\n"
                                                                       "c :- not a.\n"
                                                                       "d. e. g. {f}.\n"
                                                                       "h :- d, e, b, f, g.\n");
        REQUIRE(lp.endProgram());
        PrgAtom* x    = lp.getAtom(h);
        PrgBody* body = lp.getBody(x->support().node());
        REQUIRE((body->size() == 2 && not body->goal(0).sign() && body->goal(1).sign()));
        REQUIRE(body->goal(0).var() == f);
        REQUIRE(body->goal(1).var() == c);
    }

    SECTION("testBPWeight") {
        lpAdd(lp.start(ctx), "{a, b, c, d}.\n"
                             "e :-  2 {a=1, b=2, not c=2, d=1}.\n"
                             "  :- e.");
        REQUIRE(lp.endProgram());
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(b)));
        REQUIRE(ctx.master()->isTrue(lp.getLiteral(c)));
    }

    SECTION("testExtLitsAreFrozen") {
        Var_t e = 5, g = 7, h = 8, i = 9;
        lpAdd(lp.start(ctx), "{a, b, c, d, e, f, g}.\n"
                             " h :- 2 {b, c, d}.\n"
                             "i :- 2 {c=1, d=2, e=1}.\n"
                             "#minimize {f, g}.\n");
        REQUIRE(lp.endProgram());
        REQUIRE_FALSE(ctx.varInfo(lp.getLiteral(a).var()).frozen());
        REQUIRE(ctx.varInfo(lp.getLiteral(b).var()).frozen());
        REQUIRE(ctx.varInfo(lp.getLiteral(c).var()).frozen());
        REQUIRE(ctx.varInfo(lp.getLiteral(d).var()).frozen());
        REQUIRE(ctx.varInfo(lp.getLiteral(e).var()).frozen());
        REQUIRE(ctx.varInfo(lp.getLiteral(h).var()).frozen());
        REQUIRE(ctx.varInfo(lp.getLiteral(i).var()).frozen());

        // minimize lits only frozen if constraint is actually used
        REQUIRE_FALSE(ctx.varInfo(lp.getLiteral(f).var()).frozen());
        REQUIRE_FALSE(ctx.varInfo(lp.getLiteral(g).var()).frozen());
        ctx.minimize();
        REQUIRE(ctx.varInfo(lp.getLiteral(f).var()).frozen());
        REQUIRE(ctx.varInfo(lp.getLiteral(g).var()).frozen());
    }

    SECTION("testExternalsAreFrozen") {
        lpAdd(lp.start(ctx), "#external a.\n");
        REQUIRE(lp.endProgram());
        REQUIRE(lp.getLiteral(a).var() == 1);
        REQUIRE(ctx.varInfo(lp.getLiteral(a).var()).frozen());
    }

    SECTION("testComputeTrueBug") {
        lpAdd(lp.start(ctx), "a :- not b.\n"
                             "b :- a.\n"
                             "a :- c.\n"
                             "c :- a.\n"
                             "  :- not a.\n");
        REQUIRE_FALSE((lp.endProgram() && ctx.endInit()));
    }

    SECTION("testBackprop") {
        lpAdd(lp.start(ctx, LogicProgram::AspOptions().backpropagate()), "{e;f;g}.\n"
                                                                         "d :- e, a.\n"
                                                                         "a :- f, d.\n"
                                                                         "b :- e, g.\n"
                                                                         "c :- f, g.\n"
                                                                         "h :- e, not d.\n"
                                                                         "h :- f, not b.\n"
                                                                         "h :- g, not c.\n"
                                                                         "  :- h.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(ctx.numVars() == 0);
    }
    SECTION("testBackpropTrueCon") {
        Var_t x2 = 2, x3 = 3;
        lpAdd(lp.start(ctx, LogicProgram::AspOptions().backpropagate()), "x7.\n"
                                                                         "{x2;x3}.\n"
                                                                         "x4 :- not x6.\n"
                                                                         "   :- not x5.\n"
                                                                         "x5 :- 1 {not x2, not x3}.");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        ctx.master()->assume(lp.getLiteral(x2)) && ctx.master()->propagate();
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(x3)));
        ctx.master()->undoUntil(0);
        ctx.master()->assume(lp.getLiteral(x3)) && ctx.master()->propagate();
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(x2)));
    }
    SECTION("testBuildUnsat") {
        lpAdd(lp.start(ctx), "x1. :- x1.");
        REQUIRE_FALSE(lp.endProgram());
        REQUIRE(lp.frozen());
        std::stringstream exp("1 2 0 0\n0\n0\nB+\n0\nB-\n2\n0\n1\n");
        REQUIRE(compareSmodels(exp, lp));
    }

    SECTION("testDontAddOnePredsThatAreNotHeads") {
        lpAdd(lp.start(ctx), "x1 :-not x2, not x3.\n"
                             "x3.\n"
                             "#output a : x1.\n"
                             "#output b : x2.\n"
                             "#output c : x3.");
        REQUIRE(lp.endProgram());
        REQUIRE(0u == ctx.numVars());
        std::stringstream exp("1 3 0 0 \n0\n3 c\n0\nB+\n0\nB-\n0\n1\n");
        REQUIRE(compareSmodels(exp, lp));
    }

    SECTION("testDontAddDuplicateBodies") {
        lpAdd(lp.start(ctx), "a :- b, not c.\n"
                             "d :- b, not c.\n"
                             "b. c.");
        lp.addAtomOutput(a, "a").addAtomOutput(b, "b").addAtomOutput(c, "c");
        REQUIRE(lp.endProgram());
        REQUIRE(0u == ctx.numVars());
        std::stringstream exp("1 2 0 0 \n1 3 0 0 \n0\n2 b\n3 c\n0\nB+\n0\nB-\n0\n1\n");
        REQUIRE(compareSmodels(exp, lp));
    }
    SECTION("testDontAddUnsupported") {
        lpAdd(lp.start(ctx), "x1 :- x3, x2.\n"
                             "x3 :- not x4.\n"
                             "x2 :- x1.");
        REQUIRE(lp.endProgram());
        std::stringstream exp("1 3 0 0 \n0");
        REQUIRE(compareSmodels(exp, lp));
    }
    SECTION("testDontAddUnsupportedNoEq") {
        lpAdd(lp.start(ctx, LogicProgram::AspOptions().noEq()), "x1 :- x3, x2.\n"
                                                                "x3 :- not x4.\n"
                                                                "x2 :- x1.");
        lp.addAtomOutput(a, "a").addAtomOutput(b, "b").addAtomOutput(c, "c");
        REQUIRE(lp.endProgram());
        REQUIRE(ctx.numVars() <= 2u);
        std::stringstream exp("1 3 0 0 \n0\n3 c\n0\nB+\n0\nB-\n0\n1\n");
        REQUIRE(compareSmodels(exp, lp));
    }
    SECTION("testAddCardConstraint") {
        lpAdd(lp.start(ctx), "a :- 1 { not b, c, d }.\n"
                             "{b;c}.");
        lp.addAtomOutput(a, "a").addAtomOutput(b, "b").addAtomOutput(c, "c");

        REQUIRE(lp.endProgram());
        REQUIRE(3u == ctx.numVars());

        std::stringstream exp("2 1 2 1 1 2 3 \n3 2 2 3 0 0 \n0\n1 a\n2 b\n3 c\n0\nB+\n0\nB-\n0\n1\n");
        REQUIRE(compareSmodels(exp, lp));
    }

    SECTION("testAddWeightConstraint") {
        lpAdd(lp.start(ctx), "a :- 2 {not b=1, c=2, d=2}.\n"
                             "{b;c}.");
        lp.addAtomOutput(a, "a").addAtomOutput(b, "b").addAtomOutput(c, "c").addAtomOutput(d, "d");

        REQUIRE(lp.endProgram());
        REQUIRE(3u == ctx.numVars());
        std::stringstream exp("5 1 2 2 1 2 3 1 2 \n3 2 2 3 0 0 \n0\n1 a\n2 b\n3 c\n0\nB+\n0\nB-\n0\n1\n");
        REQUIRE(compareSmodels(exp, lp));
    }
    SECTION("testAddMinimizeConstraint") {
        lpAdd(lp.start(ctx), "a :- not b.\n"
                             "b :- not a.\n"
                             "#minimize{a}@0.\n"
                             "#minimize{b}@1.\n"
                             "#output a : a. #output b : b.");
        REQUIRE(lp.endProgram());
        std::stringstream exp;
        exp << "6 0 1 0 1 1 \n"
            << "6 0 1 0 2 1 \n"
            << "1 1 1 1 2 \n"
            << "1 2 1 1 1 \n"
            << "0\n1 a\n2 b\n0\nB+\n0\nB-\n0\n1\n";
        REQUIRE(compareSmodels(exp, lp));
    }

    SECTION("testWriteMinimizeEq") {
        lpAdd(lp.start(ctx), "{a}.\n"
                             "b :- a.\n"
                             "#minimize{b}@0.\n");
        REQUIRE(lp.endProgram());
        REQUIRE(lp.inProgram(1));
        REQUIRE_FALSE(lp.inProgram(2));
        std::stringstream exp("6 0 1 0 2 1");
        REQUIRE(findSmodels(exp, lp));
    }
    SECTION("testEqOverChoiceRule") {
        lpAdd(lp.start(ctx), "{a}.\n"
                             "b :- a.\n");
        REQUIRE(lp.endProgram());
        REQUIRE(1u == ctx.numVars());
        std::stringstream exp;
        exp << "3 1 1 0 0 \n"
            << "1 2 1 0 1 \n"
            << "0\n0\nB+\n0\nB-\n0\n1\n";
        REQUIRE(compareSmodels(exp, lp));
    }
    SECTION("testRemoveKnownAtomFromWeightRule") {
        lpAdd(lp.start(ctx), "{c, d}.\n"
                             "a."
                             "e :- 5 {a = 2, not b = 2, c = 1, d = 1}.\n");
        REQUIRE(lp.endProgram());
        std::stringstream exp;
        exp << "1 1 0 0 \n"       // a.
            << "3 2 3 4 0 0 \n"   // {c, d}.
            << "2 5 2 0 1 3 4 \n" // e :- 1 { c, d }.
            << "0\n";
        REQUIRE(compareSmodels(exp, lp));
    }

    SECTION("testMergeEquivalentAtomsInConstraintRule") {
        lpAdd(lp.start(ctx), "{a, c}.\n"
                             "a :- b.\n"
                             "b :- a.\n"
                             "d :-  2 {a, b, c}.\n");
        REQUIRE(lp.endProgram());
        std::stringstream exp("5 4 2 2 0 1 3 2 1");
        // d :-  2 [a=2, c].
        REQUIRE(findSmodels(exp, lp));
    }

    SECTION("testMergeEquivalentAtomsInWeightRule") {
        lpAdd(lp.start(ctx), "{a, c, e}.\n"
                             "a :- b.\n"
                             "b :- a.\n"
                             "d :-  3 {a = 1, c = 4, b = 2, e=1}.\n");
        REQUIRE(lp.endProgram());
        // x :-  3 [a=3, c=3,d=1].
        std::stringstream exp("5 4 3 3 0 1 3 5 3 3 1");
        REQUIRE(findSmodels(exp, lp));
    }

    SECTION("testBothLitsInConstraintRule") {
        lpAdd(lp.start(ctx), "{a}.\n"
                             "b :- a.\n"
                             "c :- b.\n"
                             "d :-  1 {a, b, not b, not c}.\n");
        REQUIRE(lp.endProgram());
        // d :-  1 [a=2, not a=2].
        std::stringstream exp("5 4 1 2 1 1 1 2 2");
        REQUIRE(findSmodels(exp, lp));
    }

    SECTION("testBothLitsInWeightRule") {
        lpAdd(lp.start(ctx), "{a, e}.\n"
                             "b :- a.\n"
                             "c :- b.\n"
                             "d :-  3 {a=3, not b=1, not c=3, e=2}.\n");
        REQUIRE(lp.endProgram());
        // d :-  3 [a=3, not a=4, e=2].
        std::stringstream exp("5 4 3 3 1 1 1 5 4 3 2");
        REQUIRE(findSmodels(exp, lp));
    }

    SECTION("testWeightlessAtomsInWeightRule") {
        lpAdd(lp.start(ctx), "{a, b, c}.\n"
                             "d :-  1 {a=1, b=1, c=0}.\n");
        REQUIRE(lp.endProgram());
        // d :-  1 {a, b}.
        std::stringstream exp("2 4 2 0 1 1 2");
        REQUIRE(findSmodels(exp, lp));
    }

    SECTION("testSimplifyToNormal") {
        lpAdd(lp.start(ctx), "{a}.\n"
                             "b :-  2 {a=2,not c=1}.\n");
        REQUIRE(lp.endProgram());
        // b :-  a.
        std::stringstream exp("1 2 1 0 1");
        REQUIRE(findSmodels(exp, lp));
    }
    SECTION("writeIntegrityConstraint") {
        lpAdd(lp.start(ctx), "{a;b;c}."
                             "a :- c, not b.\n"
                             "b :- c, not b.\n");
        REQUIRE(lp.endProgram());

        // falseAtom :- c, not b.
        // compute {not falseAtom}.
        std::stringstream exp("1 4 2 1 2 3\nB-\n4");
        REQUIRE(findSmodels(exp, lp));
    }
    SECTION("testBackpropWrite") {
        lpAdd(lp.start(ctx, LogicProgram::AspOptions().backpropagate()), "a|b.\n"
                                                                         "c :- a.\n"
                                                                         "a :- c.\n"
                                                                         "d :- not c.\n"
                                                                         "e :- a,c.\n"
                                                                         "  :- e.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(ctx.numVars() == 0);
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(a)));
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(c)));
        REQUIRE(ctx.master()->isTrue(lp.getLiteral(b)));
        REQUIRE(ctx.master()->isTrue(lp.getLiteral(d)));

        std::stringstream exp("1 2 0 0\n1 4 0 0");
        REQUIRE(findSmodels(exp, lp));
        REQUIRE_FALSE(lp.isDefined(a));
        REQUIRE_FALSE(lp.isDefined(c));
    }

    SECTION("testStatisticsObject") {
        auto stats = StatisticObject::map(&lp.stats);
        for (uint32_t i : irange(stats)) {
            auto k = stats.key(i);
            auto x = stats.at(k);
            REQUIRE(x.value() == 0.0);
        }
        lpAdd(lp.start(ctx), "a :- not b.\n"
                             "b :- not a.\n");
        lp.endProgram();
        REQUIRE(stats.at("atoms").value() == (double) lp.stats.atoms);
        REQUIRE(stats.at("rules").value() == (double) lp.stats.rules[0].sum());

        REQUIRE(stats.at("atoms").size() == 0u);
        REQUIRE_THROWS_AS(stats.at("atoms").key(0), std::logic_error);
        REQUIRE_THROWS_AS(stats[0], std::logic_error);
    }
    SECTION("testFactIsDefined") {
        lp.start(ctx);
        lpAdd(lp, "a.");
        lp.endProgram();
        REQUIRE(lp.isDefined(a));
    }
    SECTION("testBogusAtomIsNotDefined") {
        lp.start(ctx);
        lp.endProgram();
        REQUIRE_FALSE(lp.isDefined(0xDEAD));
        REQUIRE_FALSE(lp.isExternal(0xDEAD));
        REQUIRE(lit_false == lp.getLiteral(0xDEAD));
    }
    SECTION("testMakeAtomicCondition") {
        lpAdd(lp.start(ctx), "{a;b}.");
        Potassco::Lit_t cond[2] = {Potassco::lit(a), Potassco::neg(b)};
        Id_t            c1      = lp.newCondition({cond, 1u});
        Id_t            c2      = lp.newCondition({cond + 1, 1u});
        REQUIRE(lp.endProgram());
        REQUIRE(lp.getLiteral(c1) == lp.getLiteral(a));
        REQUIRE(lp.getLiteral(c2) == ~lp.getLiteral(b));
        REQUIRE(lp.supportsSmodels());
    }

    SECTION("testMakeComplexCondition") {
        lpAdd(lp.start(ctx), "{a;b}.");
        Potassco::Lit_t cond[2] = {Potassco::lit(a), Potassco::neg(b)};
        Id_t            c1      = lp.newCondition({});
        Id_t            c2      = lp.newCondition(cond);
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(lp.getLiteral(c1) == lit_true);
        auto  c2Lit = lp.getLiteral(c2);
        auto& s     = *ctx.master();
        REQUIRE(value_free == s.value(c2Lit.var()));
        REQUIRE((s.assume(c2Lit) && s.propagate()));
        REQUIRE(s.isTrue(lp.getLiteral(a)));
        REQUIRE(s.isFalse(lp.getLiteral(b)));
    }
    SECTION("testMakeFalseCondition") {
        lp.start(ctx);
        a                       = lp.newAtom();
        Potassco::Lit_t cond[2] = {Potassco::lit(a), Potassco::neg(a)};
        Id_t            c1      = lp.newCondition(cond);
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(lp.getLiteral(c1) == lit_false);
        POTASSCO_WARNING_PUSH()
        POTASSCO_WARNING_IGNORE_GNU("-Wsign-conversion")
        REQUIRE_THROWS_AS(solverLiteral(lp, c1), std::logic_error);
        POTASSCO_WARNING_POP()
    }
    SECTION("testMakeInvalidCondition") {
        lp.start(ctx);
        a                       = lp.newAtom();
        b                       = lp.newAtom();
        Potassco::Lit_t cond[2] = {Potassco::lit(a), Potassco::neg(b)};
        Id_t            c1      = lp.newCondition(cond);
        auto            cAsLit  = static_cast<Potassco::Lit_t>(c1);
        REQUIRE_THROWS_AS(lp.newCondition({&cAsLit, 1u}), std::overflow_error);
    }
    SECTION("testExtractCondition") {
        lpAdd(lp.start(ctx), "{a;b}.");
        Potassco::Lit_t cond[2] = {Potassco::lit(a), Potassco::lit(b)};
        Id_t            c1      = lp.newCondition(cond);
        REQUIRE(lp.endProgram());

        Potassco::LitVec ext;
        lp.extractCondition(c1, ext);
        REQUIRE((ext.size() == 2 && std::equal(ext.begin(), ext.end(), cond)));

        lp.dispose();
        REQUIRE_THROWS_AS(lp.extractCondition(c1, ext), std::logic_error);
    }
    SECTION("testGetConditionAfterDispose") {
        lpAdd(lp.start(ctx), "{a;b}.");
        Potassco::Lit_t cond[2] = {Potassco::lit(a), Potassco::lit(b)};
        Id_t            c1      = lp.newCondition(cond);
        REQUIRE(lp.endProgram());

        REQUIRE_FALSE(isSentinel(lp.getLiteral(c1)));
        lp.dispose();
        REQUIRE_THROWS_AS(lp.getLiteral(c1), std::logic_error);
    }

    SECTION("testTheoryAtomsAreFrozen") {
        lpAdd(lp.start(ctx), "b :- a.");
        Potassco::TheoryData& t = lp.theoryData();
        t.addTerm(0, "Theory");
        t.addAtom(a, 0, {});
        lp.endProgram();
        REQUIRE(lp.getLiteral(a) != lit_false);
        REQUIRE(lp.isExternal(a));
        REQUIRE(lp.getLiteral(b) != lit_false);
        ctx.endInit();
        REQUIRE(ctx.varInfo(lp.getLiteral(a).var()).frozen());
    }

    SECTION("testAcceptIgnoresAuxChoicesFromTheoryAtoms") {
        lp.start(ctx);
        a                       = lp.newAtom();
        Potassco::TheoryData& t = lp.theoryData();
        t.addTerm(0, "Theory");
        t.addAtom(a, 0, {});
        lp.endProgram();
        REQUIRE(lp.getLiteral(a) != lit_false);
        std::stringstream str;
        writeAspif(lp, str);
        for (std::string x; std::getline(str, x);) {
            REQUIRE_FALSE(x.starts_with("1 1"));
            REQUIRE_FALSE(x.starts_with("5 1"));
        }
        ctx.endInit();
        REQUIRE(ctx.varInfo(lp.getLiteral(a).var()).frozen());
    }
    SECTION("testFalseHeadTheoryAtomsAreRemoved") {
        lpAdd(lp.start(ctx), "a :- b.");
        Potassco::TheoryData& t = lp.theoryData();
        t.addTerm(0, "Theory");
        t.addAtom(a, 0, {});
        REQUIRE(t.numAtoms() == 1);
        REQUIRE(t.numTerms() == 1);
        REQUIRE(t.numElems() == 0);

        lp.endProgram();
        REQUIRE(lp.getLiteral(a) == lit_false);
        REQUIRE(lp.getLiteral(b) == lit_false);
        REQUIRE(t.numAtoms() == 0);
        REQUIRE(t.numTerms() == 1);
        REQUIRE(t.numElems() == 0);
        REQUIRE_FALSE(t.empty());
    }

    SECTION("testTheoryHeadEvenIfRuleIsDropped") {
        lpAdd(lp.start(ctx), ":- b.\n"
                             "a :- b, c.\n");
        Potassco::TheoryData& t = lp.theoryData();
        t.addTerm(0, "ta");
        t.addTerm(1, "tc");
        t.addAtom(a, 0, {});
        t.addAtom(c, 1, {});
        REQUIRE(t.numAtoms() == 2);
        lp.endProgram();
        REQUIRE(lp.getLiteral(a) == lit_false);
        REQUIRE(lp.getLiteral(b) == lit_false);
        REQUIRE(lp.getLiteral(c) != lit_false);
        REQUIRE(t.numAtoms() == 1);
    }

    SECTION("testTheoryHeadEvenIfRuleIsDroppedLater") {
        lpAdd(lp.start(ctx), "{c}.\n"
                             "b :- c.\n"
                             ":- c.\n");
        Potassco::TheoryData& t = lp.theoryData();
        t.addTerm(0, "x");
        t.addTerm(1, "c");
        Potassco::Id_t term = 1;
        t.addElement(0, {&term, 1u}, 0);
        term = 0;
        t.addAtom(b, 0, {&term, 1u});
        REQUIRE(t.numAtoms() == 1);
        lp.endProgram();
        REQUIRE(lp.getLiteral(a) == lit_false);
        REQUIRE(lp.getLiteral(b) == lit_false);
        REQUIRE(lp.getLiteral(c) == lit_false);
        REQUIRE(t.numAtoms() == 0);
    }

    SECTION("testTheoryHeadEvenIfRuleIsSkipped") {
        lpAdd(lp.start(ctx), "{a}.\n"
                             "b :- a, not a.\n");
        Potassco::TheoryData& t = lp.theoryData();
        t.addTerm(0, "ta");
        t.addAtom(b, 0, {});
        REQUIRE(t.numAtoms() == 1);
        lp.endProgram();
        REQUIRE(lp.getLiteral(a) != lit_false);
        REQUIRE(lp.getLiteral(b) == lit_false);
        REQUIRE(t.numAtoms() == 0);
    }

    SECTION("testFalseBodyTheoryAtomsAreKept") {
        lp.start(ctx);
        a       = lp.newAtom();
        auto& t = lp.theoryData();
        t.addTerm(0, "Theory");
        t.addAtom(a, 0, {});
        lpAdd(lp, ":- a.");
        lp.endProgram();
        REQUIRE(lp.getLiteral(a) == lit_false);
        REQUIRE(t.numAtoms() == 1);
        std::stringstream str;
        writeAspif(lp, str);
        REQUIRE(str.str().find("1 0 0 0 1 1") != std::string::npos);
    }
    SECTION("testGeneralOutputNotSupportedInSmodels") {
        lp.start(ctx);
        SECTION("add fact") {
            lp.addAtomOutput(0u, "Hallo"sv);
            REQUIRE_FALSE(lp.supportsSmodels());
        }
        SECTION("add neg") {
            lp.addLiteralOutput(-1, "Hallo"sv);
            REQUIRE_FALSE(lp.supportsSmodels());
        }
        SECTION("add term") {
            lp.addShowTerm(lp.newShowTerm("Hallo"sv), {});
            REQUIRE_FALSE(lp.supportsSmodels());
        }
    }
    SECTION("testDisposeBug") {
        lp.start(ctx);
        lp.theoryData().addTerm(0, 99);
        lp.start(ctx);
        REQUIRE_FALSE(lp.theoryData().hasTerm(0));
    }
    SECTION("testAccept") {
        REQUIRE_FALSE(lp.ok());
        REQUIRE_FALSE(lp.hasConflict());
        lp.start(ctx);
        REQUIRE(lp.ok());
        REQUIRE_FALSE(lp.hasConflict());
        lp.addExternal(b, Potassco::TruthValue::false_);
        lpAdd(lp, "b :- a.\nc :- not d.\n#minimize{b}@0.\n{e}.f :- c.\ng.\nh :- g.");
        REQUIRE(lp.supportsSmodels());
        lp.addProject(Potassco::toSpan(a));
        lp.addAtomOutput(a, "a");
        lp.addAtomOutput(c, "c");
        auto e = Potassco::lit(5);
        lp.addAssumption(Potassco::toSpan(e));
        lp.addDomHeuristic(Potassco::atom(e), DomModType::init, 10, 1, {});
        lp.addAcycEdge(1u, 2u, {});
        lp.addAcycEdge(2u, 1u, {});
        REQUIRE_FALSE(lp.supportsSmodels());
        std::stringstream str;
        SECTION("beforeEnd") {
            writeAspif(lp, str);
            REQUIRE(str.str() == "asp 2 0 0\n"
                                 "5 2 2\n"          // #external b.
                                 "1 0 1 7 0 0\n"    // g.
                                 "1 0 1 8 0 0\n"    // h.
                                 "1 0 1 2 0 1 1\n"  // b :- a.
                                 "1 0 1 3 0 1 -4\n" // c :- not d.
                                 "1 1 1 5 0 0\n"    // {e}.
                                 "1 0 1 6 0 1 3\n"  // f :- c.
                                 "2 0 1 2 1\n"      // #minimize@0{b}.
                                 "4 0 1 1 a\n"      // show a : a.
                                 "4 0 3 1 c\n"      // show c : c.
                                 "3 1 1\n"          // project {a}.
                                 "6 1 5\n"          // assume {e}.
                                 "7 3 5 10 1 0\n"   // #heu e, init,5,10
                                 "8 1 2 0\n"        // #edge 1,2
                                 "8 2 1 0\n"        // #edge 2,1
                                 "0\n");
        }
        SECTION("afterEnd") {
            lp.endProgram();
            writeAspif(lp, str);
            REQUIRE(str.str() == "asp 2 0 0\n"
                                 "1 0 1 6 0 0\n"  // f.
                                 "1 0 1 7 0 0\n"  // g.
                                 "1 0 1 8 0 0\n"  // h.
                                 "1 0 1 3 0 0\n"  // c.
                                 "1 1 1 5 0 0\n"  // {e}.
                                 "2 0 0\n"        // #minimize@0{}.
                                 "4 0 3 1 c\n"    // show c : c.
                                 "3 1 1\n"        // project {a}.
                                 "6 1 5\n"        // assume {e}.
                                 "7 3 5 10 1 0\n" // #heu e, init,5,10
                                 "8 1 2 0\n"      // #edge 1,2
                                 "8 2 1 0\n"      // #edge 2,1
                                 "0\n");
        }
        SECTION("afterDispose") {
            SECTION("beforeEnd") {
                lp.dispose();
                REQUIRE(lp.ok());
                writeAspif(lp, str);
                REQUIRE(str.str() == "asp 2 0 0\n"
                                     "1 0 1 7 0 0\n" // g.
                                     "1 0 1 8 0 0\n" // h.
                                     "6 1 5\n"       // assume {e}.
                                     "0\n");
            }
            SECTION("afterEnd") {
                lp.endProgram();
                lp.dispose();
                REQUIRE(lp.ok());
                writeAspif(lp, str);
                REQUIRE(str.str() == "asp 2 0 0\n"
                                     "1 0 1 6 0 0\n" // f.
                                     "1 0 1 7 0 0\n" // g.
                                     "1 0 1 8 0 0\n" // h.
                                     "6 1 5\n"       // assume {e}.
                                     "0\n");
            }
            SECTION("full") {
                lp.start(*lp.ctx(), lp.options());
                REQUIRE(lp.ok());
                writeAspif(lp, str);
                REQUIRE(str.str() == "asp 2 0 0\n0\n");
                lp.endProgram();
            }
        }
    }
    SECTION("testTermOutput") {
        lp.start(ctx);
        lpAdd(lp, "{a,b,c}. d.");
        REQUIRE(lp.supportsSmodels());

        lp.addShowTerm(lp.newShowTerm("fact"), {});
        lp.addShowTerm(lp.newShowTerm("one"), Potassco::LitVec{1});
        lp.addShowTerm(lp.newShowTerm("two"), Potassco::LitVec{1, 4});
        lp.addShowTerm(lp.newShowTerm("drop"), Potassco::LitVec{1, 2, -4, 3});
        auto multi = lp.newShowTerm("multi");
        lp.addShowTerm(multi, Potassco::LitVec{1, 3});
        lp.addShowTerm(multi, Potassco::LitVec{2, -4});
        lp.addShowTerm(multi, Potassco::LitVec{-2, 3});
        auto late = lp.newShowTerm("late");
        lp.addShowTerm(late, Potassco::LitVec{3});
        lp.addShowTerm(late, Potassco::LitSpan{});
        lp.addShowTerm(late, Potassco::LitVec{2, 4});
        REQUIRE_FALSE(lp.supportsSmodels());
        REQUIRE(lp.getShowTerm(multi).name() == "multi");
        REQUIRE_FALSE(lp.getShowTerm(multi).conditions().empty());
        std::stringstream str;
        std::ranges::for_each(lp.getShowTerm(multi).conditions(), [&](SpanView<Id_t> condition) {
            str << "{";
            for (const auto* sep = ""; auto x : condition) { str << std::exchange(sep, ";") << Potassco::lit(x); }
            str << "}";
        });
        REQUIRE(str.str() == "{1;3}{2;-4}{-2;3}");
        str.str("");
        writeAspif(lp, str);
        REQUIRE(str.str() == "asp 2 0 0\n"
                             "1 0 1 4 0 0\n"
                             "1 1 3 1 2 3 0 0\n"
                             "4 1 0 4 fact\n"
                             "4 2 0 0\n"
                             "4 1 1 3 one\n"
                             "4 2 1 1 1\n"
                             "4 1 2 3 two\n"
                             "4 2 2 2 1 4\n"
                             "4 1 3 4 drop\n"
                             "4 2 3 4 1 2 -4 3\n"
                             "4 1 4 5 multi\n"
                             "4 2 4 2 1 3\n"
                             "4 2 4 2 2 -4\n"
                             "4 2 4 2 -2 3\n"
                             "4 1 5 4 late\n"
                             "4 2 5 1 3\n"
                             "4 2 5 0\n"
                             "4 2 5 2 2 4\n"
                             "0\n");
        REQUIRE(ctx.output.theory_range().size() == 1u);
        auto&    termOutput = *ctx.output.theory_range()[0];
        ValueVec values(ctx.numVars() + 1, value_free);
        values[0] = value_true;
        auto res  = getTheoryOutput(termOutput, values);
        REQUIRE(res.size() == 2);
        REQUIRE(contains(res, "late"s));
        REQUIRE(contains(res, "fact"s));

        str.str("");
        lp.endProgram();
        writeAspif(lp, str);
        REQUIRE(str.str() == "asp 2 0 0\n"
                             "1 0 1 4 0 0\n"
                             "1 1 3 1 2 3 0 0\n"
                             "4 1 0 4 fact\n"
                             "4 2 0 0\n"
                             "4 1 1 3 one\n"
                             "4 2 1 1 1\n"
                             "4 1 2 3 two\n"
                             "4 2 2 1 1\n"
                             "4 1 4 5 multi\n"
                             "4 2 4 2 1 3\n"
                             "4 2 4 2 -2 3\n"
                             "4 1 5 4 late\n"
                             "4 2 5 0\n"
                             "0\n");
        REQUIRE(ctx.output.numPreds() == 0);
        for (uint32_t i = 0; i != 2; ++i) {
            CAPTURE(i);
            values.assign(ctx.numVars() + 1, value_free);
            values[0] = value_true;
            res       = getTheoryOutput(termOutput, values);
            REQUIRE(res.size() == 2);
            REQUIRE(contains(res, "late"s));
            REQUIRE(contains(res, "fact"s));

            values[1] = value_true;
            values[3] = value_true;
            res       = getTheoryOutput(termOutput, values);
            REQUIRE(res.size() == 5);
            REQUIRE(contains(res, "late"s));
            REQUIRE(contains(res, "fact"s));
            REQUIRE(contains(res, "one"s));
            REQUIRE(contains(res, "two"s));
            REQUIRE(contains(res, "multi"s));

            values[1] = value_false;
            values[2] = value_false;
            values[3] = value_true;
            res       = getTheoryOutput(termOutput, values);
            REQUIRE(res.size() == 3);
            REQUIRE(contains(res, "multi"s));
            lp.dispose();
        }
        Model m;
        m.num    = 1;
        m.values = values;
        REQUIRE(lp.isShowTermTrue(m, multi));
        values[2] = value_true;
        REQUIRE_FALSE(lp.isShowTermTrue(m, multi));
        REQUIRE_FALSE(lp.isShowTermTrue(m, 0xDEADu));
    }

    SECTION("termOutputIsNotTiedToProgram") {
        lp.start(ctx);
        lpAdd(lp, "{a}.");
        lp.addShowTerm(lp.newShowTerm("fact"), {});
        lp.addShowTerm(lp.newShowTerm("one"), Potassco::LitVec{1});
        lp.endProgram();
        REQUIRE(ctx.output.theory_range().size() == 1u);
        auto&    termOutput = *ctx.output.theory_range()[0];
        ValueVec values(ctx.numVars() + 1, value_free);
        values[0]                      = value_true;
        values[lp.getLiteral(1).var()] = value_true;

        auto res = getTheoryOutput(termOutput, values);
        REQUIRE(res.size() == 2);
        res = getTheoryOutput(termOutput, values);
        REQUIRE(res.size() == 2);
        REQUIRE(contains(res, "one"s));
        REQUIRE(contains(res, "fact"s));

        SECTION("restartSameCtx") {
            lp.start(ctx);
            REQUIRE(ctx.output.theory_range().empty());
        }
        SECTION("restartNewCtx") {
            SharedContext newCtx;
            lp.start(newCtx);
            REQUIRE(newCtx.output.theory_range().empty());
            REQUIRE_FALSE(ctx.output.theory_range().empty());
            REQUIRE(&termOutput == ctx.output.theory_range()[0].get());
            res = getTheoryOutput(termOutput, values);
            REQUIRE(contains(res, "one"s));
            REQUIRE(contains(res, "fact"s));
            REQUIRE(res.size() == 2);
        }
        SECTION("destroyProgram") {
            std::destroy_at(&lp);
            std::construct_at(&lp);
            REQUIRE_FALSE(ctx.output.theory_range().empty());
            REQUIRE(&termOutput == ctx.output.theory_range()[0].get());
            res = getTheoryOutput(termOutput, values);
            REQUIRE(contains(res, "one"s));
            REQUIRE(contains(res, "fact"s));
            REQUIRE(res.size() == 2);
        }
    }

    SECTION("testTheoryOutput") {
        struct Extra : OutputTable::Theory {
            ~Extra() override {
                if (destroyed) {
                    *destroyed = true;
                }
            }
            const char* first(const Model&) override {
                idx = UINT32_MAX;
                return next();
            }
            const char*              next() override { return ++idx < size32(data) ? data[idx].c_str() : nullptr; }
            std::vector<std::string> data;
            uint32_t                 idx{0};
            bool*                    destroyed = {nullptr};
        };
        bool  destroyed = false;
        auto  output    = std::make_unique<OutputTable>();
        Model m;
        SECTION("borrow") {
            Extra myTheory;
            myTheory.data.emplace_back("foo");
            myTheory.data.emplace_back("bar");
            REQUIRE(output->theory_range().empty());
            output->add(myTheory);
            REQUIRE(output->theory_range().size() == 1u);
            std::string res;
            for (const auto& t : output->theory_range()) {
                for (const auto* n = t->first(m); n; n = t->next()) { res += n; }
            }
            REQUIRE(res == "foobar");
            SECTION("remove") {
                output->remove(myTheory);
                REQUIRE(output->theory_range().empty());
            }
            SECTION("destroy") {
                myTheory.destroyed = &destroyed;
                output.reset();
                REQUIRE_FALSE(destroyed);
            }
        }
        SECTION("own") {
            auto theirTheory       = std::make_unique<Extra>();
            theirTheory->destroyed = &destroyed;
            theirTheory->data.emplace_back("foo");
            theirTheory->data.emplace_back("bar");
            REQUIRE(output->theory_range().empty());
            output->add(std::move(theirTheory));
            REQUIRE(output->theory_range().size() == 1u);
            std::string res;
            for (const auto& t : output->theory_range()) {
                for (const auto* n = t->first(m); n; n = t->next()) { res += n; }
            }
            REQUIRE(res == "foobar");
            output.reset();
            REQUIRE(destroyed);
        }
    }
    SECTION("testPredicateSorting") {
        using P = std::pair<LogicProgram::AtomSorting, std::string_view>;
        auto x  = GENERATE(P(LogicProgram::sort_auto, "fact b(1) a(10) x(20) a(10,200) a(1,2,3) a(2) b(0,1)"),
                           P(LogicProgram::sort_no, "b(1) a(10) a(1,2,3) x(20) b(0,1) a(10,200) a(2) fact"),
                           P(LogicProgram::sort_number, "fact b(1) a(10) x(20) a(10,200) a(1,2,3) a(2) b(0,1)"),
                           P(LogicProgram::sort_name, "a(1,2,3) a(10) a(10,200) a(2) b(0,1) b(1) fact x(20)"),
                           P(LogicProgram::sort_natural, "a(1,2,3) a(2) a(10) a(10,200) b(0,1) b(1) fact x(20)"),
                           P(LogicProgram::sort_arity, "fact a(10) a(2) b(1) x(20) a(10,200) b(0,1) a(1,2,3)"),
                           P(LogicProgram::sort_arity_natual, "fact a(2) a(10) b(1) x(20) a(10,200) b(0,1) a(1,2,3)"));

        CAPTURE(x.first);
        lpAdd(lp.start(ctx, LogicProgram::AspOptions{}.sort(x.first)), "{x1;x2;x3;x4;x5;x6;x7}. x8.\n");
        lp.addAtomOutput(1, "b(1)");
        lp.addAtomOutput(2, "a(10)");
        lp.addAtomOutput(5, "a(1,2,3)");
        lp.addAtomOutput(3, "x(20)");
        lp.addAtomOutput(7, "b(0,1)");
        lp.addAtomOutput(4, "a(10,200)");
        lp.addAtomOutput(6, "a(2)");
        lp.addAtomOutput(8, "fact");
        lp.endProgram();
        for (const auto& p : ctx.output.pred_range()) {
            if (x.second.starts_with(' ')) {
                x.second.remove_prefix(1);
            }
            CAPTURE(p.name.view());
            CAPTURE(x.second);
            REQUIRE(x.second.starts_with(p.name.view()));
            x.second.remove_prefix(p.name.size());
        }
        CAPTURE(x.second);
        CHECK(x.second.empty());
    }
    SECTION("testPredicateSortingIsStable") {
        SECTION("same number") {
            lpAdd(lp.start(ctx, LogicProgram::AspOptions{}.sort(LogicProgram::sort_number)), "x1.\n");
            std::vector<std::string> expected = {"a(1)", "b(1)", "f(1)", "f(2)", "f(3)"};
            lp.addAtomOutput(1, "f(1)");
            lp.addAtomOutput(1, "f(2)");
            lp.addAtomOutput(1, "f(3)");
            lp.addAtomOutput(1, "a(1)");
            lp.addAtomOutput(1, "b(1)");
            lp.endProgram();
            REQUIRE(ctx.output.numPreds() == expected.size());
            for (auto it = expected.begin(); const auto& p : ctx.output.pred_range()) {
                REQUIRE(p.name.view() == *it++);
                REQUIRE(p.user == 1u);
            }
        }
        SECTION("same name") {
            lpAdd(lp.start(ctx, LogicProgram::AspOptions{}.sort(LogicProgram::sort_name)), "{x1;x2;x3;x4;x5}.\n");
            lp.addAtomOutput(1, "f(1)");
            lp.addAtomOutput(3, "f(1)");
            lp.addAtomOutput(2, "f(1)");
            lp.addAtomOutput(5, "f(1)");
            lp.addAtomOutput(4, "f(1)");
            lp.endProgram();
            REQUIRE(ctx.output.numPreds() == 5);
            for (auto n = 1u; const auto& p : ctx.output.pred_range()) {
                REQUIRE(p.user == n++);
                REQUIRE(p.name.view() == "f(1)"sv);
            }
        }
    }
    SECTION("testTermSorting") {
        using V              = std::vector<std::string>;
        using P              = std::pair<LogicProgram::AtomSorting, V>;
        constexpr auto multi = "multi((1,3),(2,-4),(-2,3))";
        constexpr auto late  = "late(3,(),(2,4))";
        auto           x     = GENERATE(P(LogicProgram::sort_auto, V{multi, "fact()", "one(1)", "two(1,4)", late}),
                                        P(LogicProgram::sort_no, V{multi, "fact()", "one(1)", "two(1,4)", late}),
                                        P(LogicProgram::sort_number, V{multi, "fact()", "one(1)", "two(1,4)", late}),
                                        P(LogicProgram::sort_name, V{"fact()", late, multi, "one(1)", "two(1,4)"}),
                                        P(LogicProgram::sort_natural, V{"fact()", late, multi, "one(1)", "two(1,4)"}),
                                        P(LogicProgram::sort_arity, V{"fact()", "one(1)", "two(1,4)", late, multi}),
                                        P(LogicProgram::sort_arity_natual, V{"fact()", "one(1)", "two(1,4)", late, multi}));

        lpAdd(lp.start(ctx, LogicProgram::AspOptions{}.sort(x.first)), "{a,b,c}. d.");
        auto id = lp.newShowTerm(multi);
        lp.addShowTerm(id, Potassco::LitVec{1, 3});
        lp.addShowTerm(id, Potassco::LitVec{2, -4});
        lp.addShowTerm(id, Potassco::LitVec{-2, 3});
        lp.addShowTerm(lp.newShowTerm("fact()"), {});
        lp.addShowTerm(lp.newShowTerm("one(1)"), Potassco::LitVec{1});
        lp.addShowTerm(lp.newShowTerm("two(1,4)"), Potassco::LitVec{1, 4});
        id = lp.newShowTerm(late);
        lp.addShowTerm(id, Potassco::LitVec{3});
        lp.addShowTerm(id, Potassco::LitSpan{});
        lp.addShowTerm(id, Potassco::LitVec{2, 4});
        lp.endProgram();
        auto&    termOutput = *ctx.output.theory_range()[0];
        ValueVec values(ctx.numVars() + 1, value_free);
        values[0] = value_true;
        values[1] = value_true;
        values[3] = value_true;
        auto res  = getTheoryOutput(termOutput, values);
        CAPTURE(x.first);
        REQUIRE_THAT(x.second, Catch::Matchers::RangeEquals(res));
    }
    SECTION("testTermSelection") {
        lpAdd(lp.start(ctx), "{a;b}.");
        lp.addShowTerm(lp.newShowTerm("t"), Potassco::LitVec{Potassco::lit(b), Potassco::neg(a)});
        lp.endProgram();
        auto&    termOutput = *ctx.output.theory_range()[0];
        ValueVec values(ctx.numVars() + 1, value_free);
        values[0]      = value_true;
        auto modelType = Model::sat;
        auto getShow   = [&]() { return getTheoryOutput(termOutput, values, modelType); };
        auto detached  = GENERATE(false, true);
        CAPTURE(detached);
        if (detached) {
            std::destroy_at(&lp);
            std::construct_at(std::launder(&lp));
        }
        SECTION("sat") {
            auto sat = 0;
            auto cnt = 0;
            for (auto vals = std::array{value_free, value_free};;) {
                values[a] = vals[a - 1];
                values[b] = vals[b - 1];
                ++cnt;
                CAPTURE(values[a]);
                CAPTURE(values[b]);
                auto expectSelect = values[b] == value_true && values[a] == value_false;
                CAPTURE(expectSelect);
                auto res = getShow();
                if (expectSelect) {
                    REQUIRE(res.size() == 1);
                    REQUIRE(contains(res, "t"s));
                    ++sat;
                }
                else {
                    REQUIRE(res.empty());
                }
                if (++vals[1] > value_false) {
                    vals[1] = value_free;
                    if (++vals[0] > value_false) {
                        break;
                    }
                }
            }
            REQUIRE(sat == 1);
            REQUIRE(cnt == 9);
        }
        SECTION("brave") {
            auto add = [&](Var_t v, bool est = false) { values[v] = not est ? value_true : Model::estMask(posLit(v)); };
            modelType = Model::brave;
            SECTION("empty") {
                auto res = getShow();
                REQUIRE(res.empty());
            }
            SECTION("b, est a") {
                add(b);
                add(a, true);
                auto res = getShow();
                REQUIRE(res.size() == 1);
                REQUIRE(contains(res, "t"s));
            }
            SECTION("a, est b") {
                add(a);
                add(b, true);
                auto res = getShow();
                REQUIRE(res.empty());
            }
            SECTION("a, b") {
                add(a);
                add(b);
                auto res = getShow();
                REQUIRE(res.empty());
            }
            SECTION("est a, est b") {
                add(a, true);
                add(b, true);
                auto res = getShow();
                REQUIRE(res.empty());
            }
        }
        SECTION("cautious") {
            values[a]   = Model::estMask(posLit(a));
            values[b]   = Model::estMask(posLit(b));
            auto remove = [&](Var_t v) { values[v] = value_free; };
            auto add    = [&](Var_t v) { values[v] |= value_true; };
            modelType   = Model::cautious;
            SECTION("est a, est b") {
                auto res = getShow();
                REQUIRE(res.empty());
            }
            SECTION("b, est a") {
                add(b);
                auto res = getShow();
                REQUIRE(res.size() == 1);
                REQUIRE(contains(res, "t"s));
            }
            SECTION("b only") {
                add(b);
                remove(a);
                auto res = getShow();
                REQUIRE(res.size() == 1);
                REQUIRE(contains(res, "t"s));
            }
            SECTION("a, est b") {
                add(a);
                auto res = getShow();
                REQUIRE(res.empty());
            }
            SECTION("a, b") {
                add(a);
                add(b);
                auto res = getShow();
                REQUIRE(res.empty());
            }
        }
    }
}

TEST_CASE("Incremental logic program", "[asp]") {
    SharedContext ctx;
    LogicProgram  lp;
    Var_t         a = 1, b = 2, c = 3, d = 4, e = 5, f = 6;
    SECTION("testSimple") {
        lp.start(ctx);
        lp.updateProgram();
        lpAdd(lp, "a :- not b. b :- not a.");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(ctx.numVars() == 1);
        REQUIRE(lp.getLiteral(a) == ~lp.getLiteral(b));
        lp.updateProgram();
        lpAdd(lp, "c :- a, not d.\n"
                  "d :- b, not c.\n"
                  "e :- d.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(ctx.numVars() == 3);
        REQUIRE(lp.getLiteral(a) == ~lp.getLiteral(b));
        REQUIRE(lp.getLiteral(e) == lp.getLiteral(d));
    }
    SECTION("testDistinctFacts") {
        lp.start(ctx);
        lp.enableDistinctTrue();
        lp.updateProgram();
        lpAdd(lp, "a.\n"
                  "b :- not c.\n"
                  "c.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(ctx.numVars() == 0);
        REQUIRE(lp.getLiteral(a) == lit_true);
        REQUIRE(lp.getLiteral(b) == lit_false);
        REQUIRE(lp.getLiteral(b, MapLit::refined) == lit_false);
        REQUIRE(lp.getLiteral(c) == lit_true);
        lp.updateProgram();
        Var_t g = f + 1;
        lpAdd(lp, "g :- not f.\n"
                  "d.\n"
                  "e :- f.\n"
                  "f.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(ctx.numVars() == 1);
        REQUIRE(lp.getLiteral(d) == lit_true);
        REQUIRE(lp.getLiteral(e) == lit_true);
        REQUIRE(lp.getLiteral(f) == lit_true);
        REQUIRE(lp.getLiteral(g) == lit_false);
        REQUIRE(lp.getLiteral(d, MapLit::refined) == posLit(1));
        REQUIRE(lp.getLiteral(e, MapLit::refined) == posLit(1));
        REQUIRE(lp.getLiteral(f, MapLit::refined) == posLit(1));
        REQUIRE(lp.getLiteral(g, MapLit::refined) == negLit(1));
    }
    SECTION("testDistinctFactsSimple") {
        lp.start(ctx, LogicProgram::AspOptions().noEq());
        lp.enableDistinctTrue();
        lp.updateProgram();
        lpAdd(lp, "a.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(ctx.numVars() == 0);
        REQUIRE(lp.getLiteral(a) == lit_true);
        lp.updateProgram();
        lpAdd(lp, "b :- d.\n"
                  "c :- e.\n"
                  "d. e.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(ctx.numVars() == 1);
        REQUIRE(lp.getLiteral(b) == lit_true);
        REQUIRE(lp.getLiteral(c) == lit_true);
        REQUIRE(lp.getLiteral(d) == lit_true);
        REQUIRE(lp.getLiteral(b, MapLit::refined) == posLit(1));
        REQUIRE(lp.getLiteral(c, MapLit::refined) == posLit(1));
        REQUIRE(lp.getLiteral(d, MapLit::refined) == posLit(1));
        lp.updateProgram();
        lpAdd(lp, "f.");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(ctx.numVars() == 2);
        REQUIRE(lp.getLiteral(a, MapLit::refined) == posLit(0));
        REQUIRE(lp.getLiteral(b, MapLit::refined) == posLit(1));
        REQUIRE(lp.getLiteral(f, MapLit::refined) == posLit(2));
    }
    SECTION("testFreeze") {
        lp.start(ctx, LogicProgram::AspOptions().noEq());
        lp.updateProgram();
        lpAdd(lp, "{d}.\n"
                  "a :- not c.\n"
                  "b :- a, d.\n"
                  "#external c.");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(lp.getLiteral(c) != lit_false);
        auto& solver = *ctx.master();
        solver.assume(lp.getLiteral(c));
        solver.propagate();
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(b)));
        solver.undoUntil(0);

        lp.updateProgram();
        lpAdd(lp, "{e}.\n"
                  "c :- e.\n"
                  "#external c. [release]");

        REQUIRE((lp.endProgram() && ctx.endInit()));
        solver.assume(lp.getLiteral(e));
        solver.propagate();
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(b)));
        solver.undoUntil(0);
        solver.assume(~lp.getLiteral(e));
        solver.propagate();
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(c)));
        solver.assume(lp.getLiteral(d));
        solver.propagate();
        REQUIRE(ctx.master()->isTrue(lp.getLiteral(b)));
    }
    SECTION("testFreezeValue") {
        lp.start(ctx, LogicProgram::AspOptions().noEq());
        lp.updateProgram();
        lp.freeze(a).freeze(b, value_true).freeze(c, value_false);

        REQUIRE((lp.endProgram() && ctx.endInit()));
        LitVec assume;
        lp.getAssumptions(assume);
        auto& solver = *ctx.master();
        solver.pushRoot(assume);
        REQUIRE(solver.isFalse(lp.getLiteral(a)));
        REQUIRE(solver.isTrue(lp.getLiteral(b)));
        REQUIRE(solver.isFalse(lp.getLiteral(c)));
        solver.popRootLevel(solver.decisionLevel());

        lp.updateProgram();
        lp.unfreeze(a).freeze(c, value_true).freeze(b, value_true).freeze(b, value_false);
        REQUIRE((lp.endProgram() && ctx.endInit()));
        assume.clear();
        lp.getAssumptions(assume);
        REQUIRE((assume.size() == 2 && solver.isFalse(lp.getLiteral(a))));
        solver.pushRoot(assume);
        REQUIRE(solver.isFalse(lp.getLiteral(b)));
        REQUIRE(solver.isTrue(lp.getLiteral(c)));
    }

    SECTION("testFreezeOpen") {
        lp.start(ctx, LogicProgram::AspOptions().noEq());
        // I1:
        // freeze(a, value_free)
        lp.updateProgram();
        lp.freeze(a, value_free);
        REQUIRE((lp.endProgram() && ctx.endInit()));
        LitVec assume;
        lp.getAssumptions(assume);
        auto& solver = *ctx.master();
        REQUIRE(assume.empty());
        REQUIRE(solver.value(lp.getLiteral(a).var()) == value_free);

        // I2:
        lp.updateProgram();
        REQUIRE((lp.endProgram() && ctx.endInit()));
        assume.clear();
        lp.getAssumptions(assume);
        REQUIRE(assume.empty());
        REQUIRE(solver.value(lp.getLiteral(a).var()) == value_free);

        // I3:
        lp.updateProgram();
        lp.freeze(a, value_true);
        REQUIRE((lp.endProgram() && ctx.endInit()));
        assume.clear();
        lp.getAssumptions(assume);
        REQUIRE((assume.size() == 1 && assume[0] == lp.getLiteral(a)));
    }
    SECTION("testKeepFrozen") {
        lp.start(ctx);
        // I0:
        lp.updateProgram();
        lp.freeze(a);

        REQUIRE(lp.endProgram());
        auto* x    = lp.getAtom(a);
        auto  xLit = x->literal();
        // I1:
        lp.updateProgram();
        REQUIRE(lp.endProgram());
        REQUIRE(x->literal() == xLit);
        REQUIRE(x->frozen());
    }
    SECTION("testFreezeCompute") {
        lp.start(ctx);
        lp.updateProgram();
        lpAdd(lp, "#external a. #external b. #external c. #external d.\n"
                  ":- not a.\n"
                  ":- not b.\n"
                  ":- c.\n"
                  ":- d.\n");
        REQUIRE(lp.endProgram());
        PrgAtom* x = lp.getAtom(a);
        PrgAtom* y = lp.getAtom(b);
        REQUIRE((x->frozen() && y->frozen()));
        REQUIRE(x->literal() != y->literal());
        PrgAtom* z  = lp.getAtom(c);
        PrgAtom* z2 = lp.getAtom(d);
        REQUIRE((z->frozen() && z2->frozen()));
        REQUIRE((z->literal() == z2->literal()));
        // I1:
        lp.updateProgram();
        lpAdd(lp, ":- a.");
        REQUIRE_FALSE(lp.endProgram());
        REQUIRE(lp.frozen());
    }
    SECTION("testUnfreezeUnsupp") {
        lp.start(ctx, LogicProgram::AspOptions().noEq());
        lp.updateProgram();
        lpAdd(lp, "a :- not b. #external b.");
        REQUIRE(lp.endProgram());
        DefaultUnfoundedCheck* ufsCheck = nullptr;
        if (ctx.sccGraph.get()) {
            ctx.master()->addPost(ufsCheck = new DefaultUnfoundedCheck(*ctx.sccGraph));
        }
        ctx.endInit();
        lp.updateProgram();
        lpAdd(lp, "c :- b.\n"
                  "b :- c.\n"
                  "#external b. [release]");
        REQUIRE(lp.endProgram());
        if (ctx.sccGraph.get() && not ufsCheck) {
            ctx.master()->addPost(ufsCheck = new DefaultUnfoundedCheck(*ctx.sccGraph));
        }
        ctx.endInit();
        REQUIRE(ctx.master()->isTrue(lp.getLiteral(a)));
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(b)));
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(c)));
    }

    SECTION("testUnfreezeCompute") {
        lp.start(ctx);
        lp.updateProgram();
        lpAdd(lp, "{d}.\n"
                  "a :- b, c.\n"
                  "b :- d.\n"
                  "#external c.");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(3u == ctx.numConstraints());

        lp.updateProgram();
        lpAdd(lp, "#external c.[release]\n"
                  ":- c.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(3u >= ctx.numConstraints());
        REQUIRE(ctx.master()->numFreeVars() == 1);
    }

    SECTION("testCompute") {
        lp.start(ctx, LogicProgram::AspOptions());
        lp.updateProgram();
        Var_t x2 = 2, x3 = 3, x4 = 4;
        lpAdd(lp, "{x2;x3}.\n"
                  "x1 :- x2, x3.\n"
                  "   :- x1.\n");

        REQUIRE((lp.endProgram() && ctx.endInit()));
        lp.updateProgram();
        lpAdd(lp, "{x4}. :- x2, x4.");

        REQUIRE((lp.endProgram() && ctx.endInit()));
        ctx.master()->assume(lp.getLiteral(x2));
        ctx.master()->propagate();
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(x3)));
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(x4)));
    }
    SECTION("testComputeBackprop") {
        lp.start(ctx, LogicProgram::AspOptions().backpropagate());
        lp.updateProgram();
        lpAdd(lp, "a :- not b.\n"
                  "b :- not a.\n"
                  "  :- not a.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        // I2:
        lp.updateProgram();
        lpAdd(lp, "c :- b. d :- not b.");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE_FALSE(lp.getAtom(c)->hasVar());
        REQUIRE(lit_true == lp.getLiteral(d));
    }

    SECTION("testBackpropStep") {
        lp.start(ctx);
        // I1:
        lp.updateProgram();
        lpAdd(lp, "{a}.");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        // I2:
        lp.updateProgram();
        lpAdd(lp, "{c}.    b :- a, c.    :- not b.");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(ctx.master()->isTrue(lp.getLiteral(a)));
    }

    SECTION("testEq") {
        lp.start(ctx);
        lp.updateProgram();
        lpAdd(lp, "{c;d}.\n"
                  "a :- c.\n"
                  "a :- d.\n"
                  "b :- a.\n");

        // b == a
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(lp.isDefined(a));
        REQUIRE_FALSE(lp.isDefined(b));

        std::stringstream exp("1 2 1 0 1");
        REQUIRE(findSmodels(exp, lp));
        lp.updateProgram();
        lpAdd(lp, "e :- a, b.\n"
                  "#output e : e.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        exp.str("1 5 1 0 1\n5 e");
        REQUIRE(findSmodels(exp, lp));
    }

    SECTION("testSimplifyRuleEq") {
        lp.start(ctx);
        lp.updateProgram();
        lpAdd(lp, "{x3}.\n"
                  "x1 :- x3.\n"
                  "x2 :- x3.\n");

        // x1 == x2
        REQUIRE((lp.endProgram() && ctx.endInit()));
        uint32_t x1 = lp.getAtom(1)->id();
        uint32_t x2 = lp.getAtom(2)->id();
        REQUIRE(x1 == x2);

        lp.updateProgram();

        lpAdd(lp, "{x4}.\n"
                  "x5 :- 2{x1, x2, x4}.");
        REQUIRE(lp.endProgram());
        REQUIRE(lp.getAtom(5)->numSupports() == 1);
        PrgBody* body = lp.getBody(lp.getAtom(5)->support().node());
        REQUIRE(body->type() == Potassco::BodyType::sum);
        REQUIRE(body->bound() == 2);
        REQUIRE(body->size() == 2);

        uint32_t eqIdx = body->goal(0).var() == 4;
        REQUIRE(body->weight(eqIdx) == 2);
        REQUIRE(body->weight(1 - eqIdx) == 1);
    }

    SECTION("testEqUnfreeze") {
        lp.start(ctx);
        lp.updateProgram();
        lp.freeze(a);
        lp.endProgram();
        lp.updateProgram();
        lpAdd(lp, "{d}.\n"
                  "a :- c.\n"
                  "b :- d, not a.\n"
                  "#external a. [release]");

        lp.endProgram();
        REQUIRE((ctx.numVars() == 2 && ctx.master()->numFreeVars() == 1));
        REQUIRE(lp.getLiteral(b) == lp.getLiteral(d));
    }

    SECTION("testEqComplement") {
        lp.start(ctx);
        lp.updateProgram();
        lpAdd(lp, "a :- not b.  b :- not a.");

        REQUIRE((lp.endProgram() && ctx.endInit()));
        PrgAtom* na = lp.getAtom(a);
        PrgAtom* nb = lp.getAtom(b);
        REQUIRE(nb->literal() == ~na->literal());
        // I1:
        lp.updateProgram();
        lpAdd(lp, "c :- a, b.");
        REQUIRE(lp.endProgram());
        PrgAtom* nc = lp.getAtom(c);
        REQUIRE(nc->hasVar() == false);
    }

    SECTION("testEqUpdateAssigned") {
        lp.start(ctx);
        // I0:
        lp.updateProgram();
        lp.freeze(a);
        REQUIRE((lp.endProgram() && ctx.endInit()));

        // I1:
        lp.updateProgram();
        lpAdd(lp, "a :- b.  b :- a.");

        PrgAtom* x = lp.getAtom(a);
        x->setValue(value_weak_true);
        lp.endProgram();
        // only weak-true so still relevant in bodies!
        REQUIRE(x->deps()[0].sign() == false);
    }

    SECTION("testEqResetState") {
        lp.start(ctx);
        // I0:
        lp.updateProgram();
        lpAdd(lp, "{a;b}.");
        REQUIRE((lp.endProgram() && ctx.endInit()));

        // I1:
        lp.updateProgram();
        lpAdd(lp, "c :- f.\n"
                  "d :- g.\n"
                  "c :- d, a.\n"
                  "d :- c, b.\n"
                  "f :- e, not h.\n"
                  "g :- e, not i.\n"
                  "{e}.");
        lp.endProgram();
        REQUIRE(lp.getAtom(c)->scc() == 0);
        REQUIRE(lp.getAtom(d)->scc() == 0);
    }

    SECTION("testUnfreezeUnsuppEq") {
        lp.start(ctx, LogicProgram::AspOptions().noScc());
        // I0:
        lp.updateProgram();
        lp.freeze(a);

        REQUIRE((lp.endProgram() && ctx.endInit()));
        // I1:
        lp.updateProgram();
        lpAdd(lp, "a :- b.\n"
                  "b :- a, c.\n"
                  "{c}.\n");
        lp.endProgram();
        PrgAtom* x = lp.getAtom(a);
        PrgAtom* y = lp.getAtom(b);
        REQUIRE(ctx.master()->isFalse(x->literal()));
        REQUIRE(ctx.master()->isFalse(y->literal()));
    }

    SECTION("testUnfreezeEq") {
        lp.start(ctx);
        // I0:
        lp.updateProgram();
        lp.freeze(a);

        REQUIRE(lp.endProgram());
        // I1:
        lp.updateProgram();
        lpAdd(lp, "{c}.\n"
                  "b :- c.\n"
                  "a :- b.\n");
        PrgAtom* x = lp.getAtom(a);
        lp.endProgram();
        // body {b} is eq to body {c}
        REQUIRE(ctx.master()->value(x->var()) == value_free);
        REQUIRE((x->numSupports() == 1 && x->support().node() == 1));
    }

    SECTION("testStats") {
        LpStats incStats;
        lp.start(ctx, LogicProgram::AspOptions().noEq());
        // I1:
        lp.updateProgram();
        lpAdd(lp, "a :- not b.\n"
                  "b :- not a.\n"
                  "#external c.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        incStats = lp.stats;
        REQUIRE(3u == incStats.atoms);
        REQUIRE(2u == incStats.bodies[0].sum());
        REQUIRE(2u == incStats.rules[0].sum());
        REQUIRE(0u == incStats.ufsNodes);
        REQUIRE(0u == incStats.sccs);
        REQUIRE(2u == incStats.eqs());

        // I2:
        lp.updateProgram();
        lpAdd(lp, "{c, f}.\n"
                  "d :- not c.\n"
                  "d :- e, f.\n"
                  "e :- d, f.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        incStats.accu(lp.stats);
        REQUIRE(6u == incStats.atoms);
        REQUIRE(5u == incStats.bodies[0].sum());
        REQUIRE(6u == incStats.rules[0].sum());
        REQUIRE(1u == incStats.sccs);
        REQUIRE(4u == incStats.eqs());
        // I3:
        lp.updateProgram();
        lpAdd(lp, "g :- d, not i.\n"
                  "i :- not g.\n"
                  "g :- h.\n"
                  "h :- g, not f.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        incStats.accu(lp.stats);
        REQUIRE(9u == incStats.atoms);
        REQUIRE(9u == incStats.bodies[0].sum());
        REQUIRE(10u == incStats.rules[0].sum());
        REQUIRE(2u == incStats.sccs);
        REQUIRE(7u == incStats.eqs());
    }

    SECTION("testTransform") {
        lp.start(ctx, LogicProgram::AspOptions().noEq());
        lp.setExtendedRuleMode(LogicProgram::mode_transform);
        // I1:
        lp.updateProgram();
        lpAdd(lp, "{a}. % -> a :- not a'. a' :- not a.");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(ctx.sccGraph.get() == 0);
        // I2:
        lp.updateProgram();
        lpAdd(lp, "% a' must not take up id!\n"
                  "b :- a.\n"
                  "b :- c.\n"
                  "c :- b.\n");
        REQUIRE((lp.endProgram() && ctx.endInit()));
        REQUIRE(ctx.sccGraph.get() != 0);
    }

    SECTION("testBackpropCompute") {
        lp.start(ctx);
        // I0:
        lp.updateProgram();
        lpAdd(lp, "a :- b.\n"
                  "  :- not a.\n"
                  "#external b.");
        REQUIRE(lp.endProgram());
        REQUIRE(lp.getRootId(a) == 2);
        PrgAtom* x = lp.getAtom(b);
        REQUIRE(x->value() == value_weak_true);
        // I1:
        lp.updateProgram();
        lpAdd(lp, "b :- c.  c :- b.");

        // UNSAT: no support for b,c
        REQUIRE_FALSE(lp.endProgram());
        REQUIRE(lp.frozen());
    }

    SECTION("testBackpropSolver") {
        lp.start(ctx);
        // I0:
        lp.updateProgram();
        lpAdd(lp, "{a}.\n"
                  ":- not a.\n"
                  ":- not b.\n"
                  "#external b.");
        REQUIRE(lp.endProgram());
        PrgAtom* na = lp.getAtom(a);
        PrgAtom* nb = lp.getAtom(b);
        REQUIRE(na->value() == value_true);
        REQUIRE(nb->value() == value_weak_true);
        // I1:
        lp.updateProgram();
        REQUIRE(lp.endProgram());
        REQUIRE(na->value() == value_true);
        REQUIRE(nb->value() == value_weak_true);
    }

    SECTION("testUpdateUnsatCtx") {
        lp.start(ctx);
        lp.updateProgram();
        lpAdd(lp, "{x1}.\n");
        REQUIRE(lp.endProgram());
        REQUIRE(lp.frozen());
        REQUIRE(lp.getAtom(1)->value() == value_free);

        ctx.addUnary(posLit(1));
        REQUIRE(ctx.endInit());
        ctx.addUnary(negLit(1));
        REQUIRE_FALSE(ctx.ok());

        REQUIRE_FALSE(ctx.unfreeze());
        REQUIRE_FALSE(ctx.frozen());

        REQUIRE_FALSE(lp.updateProgram());
        REQUIRE(lp.frozen());
    }

    SECTION("testPropagateLitConflict") {
        lp.start(ctx);
        lp.updateProgram();
        lpAdd(lp, "#external x1.\n.");
        REQUIRE(lp.endProgram());
        REQUIRE(lp.frozen());
        ctx.addUnary(negLit(1));
        REQUIRE(lp.update());
        REQUIRE_FALSE(lp.frozen());
        lpAdd(lp, "x1 :- not x2.");
        REQUIRE_FALSE(lp.endProgram());
        REQUIRE(lp.frozen());
    }

    SECTION("testFreezeUnfreeze") {
        lp.start(ctx);
        // I0:
        lp.updateProgram();
        lpAdd(lp, "{a}."
                  "#external b.\n"
                  "#external b. [release]");
        REQUIRE(lp.endProgram());
        REQUIRE(lit_false == lp.getLiteral(b));
        // I1:
        lp.updateProgram();
        lpAdd(lp, "#external c.\n"
                  "c :- b.");
        REQUIRE(lp.endProgram());
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(b)));
    }
    SECTION("testSymbolUpdate") {
        lp.start(ctx);
        // I0:
        lp.updateProgram();
        lpAdd(lp, "{a}.");
        lp.addAtomOutput(a, "a");
        REQUIRE(lp.endProgram());
        REQUIRE(lp.getOutputState(a) == LogicProgram::out_shown);
        uint32_t os = ctx.output.size();
        // I1:
        lp.updateProgram();
        lpAdd(lp, "{b;c}.");
        lp.addAtomOutput(b, "b");

        REQUIRE(lp.endProgram());
        REQUIRE_FALSE(isSentinel(lp.getLiteral(c)));
        REQUIRE(os + 1 == ctx.output.size());
        REQUIRE(lp.getOutputState(a) == LogicProgram::out_shown);
        REQUIRE(lp.getOutputState(b) == LogicProgram::out_shown);
        REQUIRE(lp.getOutputState(c) == LogicProgram::out_none);
    }
    SECTION("testFreezeDefined") {
        lp.start(ctx);
        // I0:
        lp.updateProgram();
        lpAdd(lp, "{a}.");
        REQUIRE(lp.endProgram());
        // I1:
        lp.updateProgram();
        lpAdd(lp, "#external a.");
        REQUIRE(lp.endProgram());
        REQUIRE_FALSE(lp.getAtom(a)->frozen());
    }
    SECTION("testUnfreezeDefined") {
        lp.start(ctx);
        // I0:
        lp.updateProgram();
        lpAdd(lp, "{a}.");
        REQUIRE(lp.endProgram());
        // I1:
        lp.updateProgram();
        lpAdd(lp, "#external a. [release]");
        REQUIRE(lp.endProgram());
        REQUIRE_FALSE(ctx.master()->isFalse(lp.getLiteral(a)));
    }
    SECTION("testUnfreezeAsFact") {
        lp.start(ctx);
        // I0:
        lp.updateProgram();
        lpAdd(lp, "#external a.");
        REQUIRE(lp.endProgram());
        auto litA = lp.getLiteral(a);
        // I1:
        lp.updateProgram();
        lpAdd(lp, "a.");
        REQUIRE(true == lp.endProgram());
        REQUIRE(litA == lp.getLiteral(a));
        REQUIRE_FALSE(lp.isExternal(a));
        REQUIRE(true == lp.isDefined(a));
        REQUIRE(ctx.master()->isTrue(litA));
    }
    SECTION("testImplicitUnfreeze") {
        lp.start(ctx);
        // I0:
        lp.updateProgram();
        lpAdd(lp, "#external a.");
        REQUIRE(lp.endProgram());
        REQUIRE(lp.getAtom(a)->frozen());
        REQUIRE_FALSE(ctx.master()->isFalse(lp.getLiteral(a)));
        // I1:
        lp.updateProgram();
        lpAdd(lp, "{a}.");
        REQUIRE(lp.endProgram());
        REQUIRE_FALSE(lp.getAtom(a)->frozen());
    }
    SECTION("testRedefine") {
        lp.start(ctx);
        // I0:
        lp.updateProgram();
        lpAdd(lp, "{a}.");
        REQUIRE(lp.endProgram());
        // I1:
        lp.updateProgram();
        REQUIRE_THROWS_AS(lpAdd(lp, "{a}."), std::logic_error);
    }
    SECTION("testGetAssumptions") {
        lp.start(ctx);
        // I0:
        lp.updateProgram();
        lpAdd(lp, "#external a. #external b.");
        REQUIRE(lp.endProgram());
        LitVec assume;
        lp.getAssumptions(assume);
        REQUIRE((assume.size() == 2 && assume[0] == ~lp.getLiteral(a) && assume[1] == ~lp.getLiteral(b)));
    }

    SECTION("testSimplifyCard") {
        lp.start(ctx);
        // I0:
        lp.updateProgram();
        lpAdd(lp, "a.");
        REQUIRE(lp.endProgram());
        lp.updateProgram();
        lpAdd(lp, "b :- 1 {c, a}.\n"
                  "d :- 1 {c, b}.\n"
                  "c :- a, b.");
        REQUIRE(lp.endProgram());
    }

    SECTION("testSimplifyMinimize") {
        lp.start(ctx);
        // I0:
        lp.updateProgram();
        lpAdd(lp, "a. #minimize{a}.");
        REQUIRE(lp.endProgram());
        REQUIRE(ctx.minimize()->adjust(0) == 1);
        lp.removeMinimize();

        lp.updateProgram();
        lpAdd(lp, "#minimize{a}.");
        REQUIRE(lp.endProgram());
        REQUIRE(ctx.hasMinimize());
        REQUIRE(ctx.minimize()->adjust(0) == 1);
        REQUIRE(lp.endProgram());
    }

    SECTION("testEqOverNeg") {
        lp.start(ctx);
        // I0: {a}.
        lp.updateProgram();
        lpAdd(lp, "{a}.");
        REQUIRE(lp.endProgram());
        // I1:
        lp.updateProgram();
        lpAdd(lp, "b :- not a.\n"
                  "c :- b.\n"
                  "c :- f.\n"
                  "d :- c.\n"
                  "e :- d.\n"
                  "g :- c.\n"
                  "h :- not f, c.\n"
                  "  :- not e.\n"
                  "  :- not h.\n");
        REQUIRE(lp.endProgram());
        REQUIRE((ctx.numVars() == 1 && ctx.master()->isFalse(lp.getLiteral(a))));
    }
    SECTION("testKeepExternalValue") {
        lp.start(ctx);
        // I0:
        lp.updateProgram();
        lpAdd(lp, "b.\n"
                  "a :- c.\n"
                  "  :- a.\n"
                  "#external c. [true]");

        REQUIRE((lp.endProgram() && ctx.endInit()));
        LitVec assume;
        lp.getAssumptions(assume);
        REQUIRE_FALSE(ctx.master()->pushRoot(assume));

        REQUIRE(lp.update());
        REQUIRE((lp.endProgram() && ctx.endInit()));
        assume.clear();
        lp.getAssumptions(assume);
        REQUIRE_FALSE(ctx.master()->pushRoot(assume));

        REQUIRE(lp.update());
        lpAdd(lp, "c.");
        REQUIRE_FALSE(lp.endProgram());
        REQUIRE(lp.frozen());
    }

    SECTION("testWriteMinimize") {
        lp.start(ctx);
        lp.updateProgram();
        lpAdd(lp, "{a}. #minimize{a}.");
        lp.endProgram();
        std::stringstream exp("6 0 1 0 1 1");
        REQUIRE(findSmodels(exp, lp));
        lp.updateProgram();
        lpAdd(lp, "{b}. #minimize{b}.");

        lp.endProgram();
        exp.str("6 0 2 0 1 2 1 1");
        REQUIRE(findSmodels(exp, lp));
        lp.updateProgram();
        lpAdd(lp, "{c}.");
        lp.endProgram();
        exp.clear();
        exp.seekg(0, ios::beg);
        REQUIRE_FALSE(findSmodels(exp, lp));
    }
    SECTION("testWriteExternal") {
        lp.start(ctx);
        lp.updateProgram();
        lpAdd(lp, "#external a.");
        lp.endProgram();
        std::stringstream str;
        writeAspif(lp, str);
        bool foundA = false, foundB = false;
        for (std::string x; std::getline(str, x);) {
            if (x.starts_with("5 1 2")) {
                foundA = true;
            }
            if (x.starts_with("5 2 2")) {
                foundB = true;
            }
        }
        REQUIRE((foundA && not foundB));
        lp.updateProgram();
        lpAdd(lp, "#external b.");
        lp.endProgram();
        foundA = foundB = false;
        str.clear();
        writeAspif(lp, str);
        for (std::string x; std::getline(str, x);) {
            if (x.starts_with("5 1 2")) {
                foundA = true;
            }
            if (x.starts_with("5 2 2")) {
                foundB = true;
            }
        }
        REQUIRE((not foundA && foundB));
    }

    SECTION("testWriteExternalBug") {
        lp.start(ctx);
        lp.updateProgram();
        lpAdd(lp, "#external a."
                  "#external b."
                  "#external c."
                  "#external d."
                  "b.");
        lp.endProgram();
        std::stringstream str;
        writeAspif(lp, str);
        int foundA = 0, foundB = 0, foundC = 0, foundD = 0;
        for (std::string x; std::getline(str, x);) {
            if (x.starts_with("5 1 2")) {
                ++foundA;
            }
            if (x.starts_with("5 2 2")) {
                ++foundB;
            }
            if (x.starts_with("5 3 2")) {
                ++foundC;
            }
            if (x.starts_with("5 4 2")) {
                ++foundD;
            }
        }
        REQUIRE(foundA == 1);
        REQUIRE(foundB == 0);
        REQUIRE(foundC == 1);
        REQUIRE(foundD == 1);
    }

    SECTION("testWriteUnfreeze") {
        lp.start(ctx);
        lp.updateProgram();
        lpAdd(lp, "#external a.");
        lp.endProgram();
        lp.updateProgram();
        lpAdd(lp, "#external a. [release]");
        lp.endProgram();
        REQUIRE_FALSE(lp.isExternal(a));
        std::stringstream str;
        writeAspif(lp, str);
        bool found = false;
        for (std::string x; std::getline(str, x);) {
            if (x.starts_with("5 1 3")) {
                found = true;
                break;
            }
        }
        REQUIRE(found);
    }
    SECTION("testSetInputAtoms") {
        lp.start(ctx);
        lp.updateProgram();
        lpAdd(lp, "{a;b;c}.");
        REQUIRE(lp.numAtoms() == 3);
        lp.setMaxInputAtom(lp.numAtoms());
        d = lp.newAtom();
        e = lp.newAtom();
        REQUIRE((d == 4 && e == 5));
        lp.endProgram();
        REQUIRE((lp.numAtoms() == 5 && lp.startAuxAtom() == 4 && lp.stats.auxAtoms == 2));
        lp.updateProgram();
        INFO("aux atoms are removed on update");
        REQUIRE(lp.numAtoms() == 3);
        REQUIRE_FALSE(lp.validAtom(d));
        REQUIRE_FALSE(lp.validAtom(e));
        REQUIRE(d == lp.newAtom());
    }

    SECTION("testFreezeIsExternal") {
        lp.start(ctx);
        lp.updateProgram();
        lpAdd(lp, "{b}. #external a.");
        REQUIRE(true == lp.isExternal(a));
        REQUIRE_FALSE(lp.isExternal(b));
        lp.endProgram();
        REQUIRE(true == lp.isExternal(a));
        REQUIRE_FALSE(lp.isExternal(b));
    }
    SECTION("testUnfreezeIsNotExternal") {
        lp.start(ctx);
        lp.updateProgram();
        lpAdd(lp, "#external a.\n"
                  "#external b. [release]\n"
                  "#external a. [release]\n"
                  "#external b.\n");
        REQUIRE_FALSE(lp.isExternal(a));
        REQUIRE_FALSE(lp.isExternal(b));
        lp.endProgram();
        REQUIRE_FALSE(lp.isExternal(a));
        REQUIRE_FALSE(lp.isExternal(b));
    }
    SECTION("testFreezeStaysExternal") {
        lp.start(ctx);
        lp.updateProgram();
        lpAdd(lp, "#external a.");
        REQUIRE(lp.isExternal(a));
        lp.endProgram();
        REQUIRE(lp.isExternal(a));
        lp.updateProgram();
        REQUIRE(lp.isExternal(a));
        lp.endProgram();
        REQUIRE(lp.isExternal(a));
    }
    SECTION("testDefinedIsNotExternal") {
        lp.start(ctx);
        lp.updateProgram();
        lpAdd(lp, "#external a.");
        lp.endProgram();
        lp.updateProgram();
        lpAdd(lp, "a :- b.");
        REQUIRE_FALSE(lp.isExternal(a));
        lp.endProgram();
        REQUIRE_FALSE(lp.isExternal(a));
    }
    SECTION("testFactIsNotExternal") {
        lp.start(ctx);
        lp.updateProgram();
        lpAdd(lp, "a. #external a.");
        lp.endProgram();
        REQUIRE_FALSE(lp.isExternal(a));
    }

    SECTION("testAssumptionsAreVolatile") {
        lp.start(ctx);
        lp.updateProgram();
        lpAdd(lp, "{a}. #assume{a}.");
        REQUIRE(lp.endProgram());
        LitVec assume;
        lp.getAssumptions(assume);
        REQUIRE((assume.size() == 1 && assume[0] == lp.getLiteral(a)));
        lp.updateProgram();
        lpAdd(lp, "#assume{not a}.");
        REQUIRE(lp.endProgram());
        assume.clear();
        lp.getAssumptions(assume);
        REQUIRE((assume.size() == 1 && assume[0] == ~lp.getLiteral(a)));
    }

    SECTION("testAssumptionsAreFrozen") {
        lpAdd(lp.start(ctx), "{a}. #assume{a}.");
        REQUIRE(lp.endProgram());
        REQUIRE(lp.getLiteral(a).var() == 1);
        REQUIRE(ctx.varInfo(lp.getLiteral(a).var()).frozen());
    }

    SECTION("testAssumptionsCanBeRemoved") {
        lp.start(ctx);
        lpAdd(lp, "{a}. #assume{a}.");
        LitVec assume;
        SECTION("sanity") {
            REQUIRE(lp.endProgram());
            lp.getAssumptions(assume);
            REQUIRE_FALSE(assume.empty());
        }
        SECTION("beforeEnd") {
            lp.removeAssumption();
            REQUIRE(lp.endProgram());
            lp.getAssumptions(assume);
            REQUIRE(assume.empty());
        }
        SECTION("afterEnd") {
            REQUIRE(lp.endProgram());
            lp.removeAssumption();
            lp.getAssumptions(assume);
            REQUIRE(assume.empty());
        }
    }

    SECTION("testProjectionIsExplicitAndCumulative") {
        lp.start(ctx);
        lp.updateProgram();
        REQUIRE(ctx.output.projectMode() == ProjectMode::output);
        lpAdd(lp, "{a;b}. #project {a}.");
        REQUIRE(lp.endProgram());
        REQUIRE(lp.getOutputState(1) == LogicProgram::out_projected);
        REQUIRE(lp.getOutputState(2) == LogicProgram::out_none);

        REQUIRE(ctx.output.projectMode() == ProjectMode::project);
        auto pr = ctx.output.proj_range();
        REQUIRE(contains(pr, lp.getLiteral(a)));
        REQUIRE_FALSE(contains(pr, lp.getLiteral(b)));

        lp.updateProgram();
        lpAdd(lp, "{c;d}. #project{d}. #output d : d.");
        REQUIRE(lp.endProgram());
        REQUIRE(lp.getOutputState(1) == LogicProgram::out_projected);
        REQUIRE(lp.getOutputState(2) == LogicProgram::out_none);
        REQUIRE(lp.getOutputState(3) == LogicProgram::out_none);
        REQUIRE(lp.getOutputState(4) == LogicProgram::out_all);

        REQUIRE(ctx.output.projectMode() == ProjectMode::project);
        pr = ctx.output.proj_range();
        REQUIRE(contains(pr, lp.getLiteral(a)));
        REQUIRE_FALSE(contains(pr, lp.getLiteral(b)));
        REQUIRE_FALSE(contains(pr, lp.getLiteral(c)));
        REQUIRE(contains(pr, lp.getLiteral(d)));

        lp.updateProgram();
        lp.removeProject();
        REQUIRE_FALSE(ctx.output.hasProject());
        REQUIRE(ctx.output.projectMode() == ProjectMode::output);
        REQUIRE(lp.getOutputState(1) == LogicProgram::out_none);
        REQUIRE(lp.getOutputState(2) == LogicProgram::out_none);
        REQUIRE(lp.getOutputState(3) == LogicProgram::out_none);
        REQUIRE(lp.getOutputState(4) == LogicProgram::out_shown);
        Var_t project[2] = {1, 4};
        lp.addProject(project);
        REQUIRE(lp.endProgram());
        REQUIRE(ctx.output.projectMode() == ProjectMode::project);
        REQUIRE(lp.getOutputState(1) == LogicProgram::out_projected);
        REQUIRE(lp.getOutputState(2) == LogicProgram::out_none);
        REQUIRE(lp.getOutputState(3) == LogicProgram::out_none);
        REQUIRE(lp.getOutputState(4) == LogicProgram::out_all);
        pr = ctx.output.proj_range();
        REQUIRE(contains(pr, lp.getLiteral(a)));
        REQUIRE_FALSE(contains(pr, lp.getLiteral(b)));
        REQUIRE_FALSE(contains(pr, lp.getLiteral(c)));
        REQUIRE(contains(pr, lp.getLiteral(d)));
    }
    SECTION("testOutputState") {
        lp.start(ctx);
        lpAdd(lp, "{x1;x100;x108;x93;x97;x223}."
                  "#output a : x1.\n"
                  "#output b : x100.\n"
                  "#output c : x108.\n"
                  "#output d : x93.\n"
                  "#project{x97, x223, x93}.");
        REQUIRE(lp.endProgram());
        CHECK(lp.getOutputState(1) == Asp::LogicProgram::out_shown);
        CHECK(lp.getOutputState(99) == Asp::LogicProgram::out_none);
        CHECK(lp.getOutputState(100) == Asp::LogicProgram::out_shown);
        CHECK(lp.getOutputState(101) == Asp::LogicProgram::out_none);
        CHECK(lp.getOutputState(108) == Asp::LogicProgram::out_shown);
        CHECK(lp.getOutputState(93) == Asp::LogicProgram::out_all);
        CHECK(lp.getOutputState(97) == Asp::LogicProgram::out_projected);
        CHECK(lp.getOutputState(222) == Asp::LogicProgram::out_none);
        CHECK(lp.getOutputState(223) == Asp::LogicProgram::out_projected);
        CHECK(lp.getOutputState(224) == Asp::LogicProgram::out_none);
        lp.updateProgram();
        lp.addAtomOutput(222, "foo");
        lp.addAtomOutput(223, "bar");
        REQUIRE(lp.endProgram());
        CHECK(lp.getOutputState(222) == Asp::LogicProgram::out_shown);
        CHECK(lp.getOutputState(223) == Asp::LogicProgram::out_all);

        lp.removeProject();
        CHECK(lp.getOutputState(93) == Asp::LogicProgram::out_shown);
        CHECK(lp.getOutputState(97) == Asp::LogicProgram::out_none);
        CHECK(lp.getOutputState(223) == Asp::LogicProgram::out_shown);
    }
    SECTION("testTheoryAtomsAreFrozenIncremental") {
        lp.start(ctx).update();
        lpAdd(lp, "b :- a.");
        Potassco::TheoryData& t = lp.theoryData();
        t.addTerm(0, "Theory");
        t.addAtom(a, 0, {});
        lp.endProgram();
        REQUIRE(lp.getLiteral(a) != lit_false);
        REQUIRE(lp.getLiteral(b) != lit_false);
        std::stringstream str;
        writeAspif(lp, str);
        for (std::string x; std::getline(str, x);) { REQUIRE(x.find("5 1") != 0); }
        lp.update();
        lpAdd(lp, "{c}."
                  "a :- c.");
        lp.endProgram();
        ctx.endInit();
        REQUIRE(ctx.master()->value(lp.getLiteral(a).var()) == value_free);
        ctx.addUnary(~lp.getLiteral(c));
        ctx.master()->propagate();
        REQUIRE(ctx.master()->isFalse(lp.getLiteral(a)));
    }
    SECTION("testFactTheoryAtomsAreNotExternal") {
        lp.start(ctx).updateProgram();
        lpAdd(lp, "a.");
        Potassco::TheoryData& t = lp.theoryData();
        t.addAtom(a, 0, {});
        lp.endProgram();
        REQUIRE(lp.getLiteral(a) == lit_true);
        REQUIRE(lp.isDefined(a));
        REQUIRE(lp.isFact(a));
        REQUIRE_FALSE(lp.isExternal(a));
        REQUIRE(lp.getRootAtom(a)->numSupports() == 0);
        lp.updateProgram();
        lpAdd(lp, "b.");
        t.addAtom(b, 0, {});
        lp.endProgram();
        REQUIRE(lp.getLiteral(b) == lit_true);
        REQUIRE(lp.isDefined(b));
        REQUIRE(lp.isFact(b));
        REQUIRE(lp.getRootAtom(b)->numSupports() == 0);
    }
    SECTION("testTheoryAtomsAreAdded") {
        lp.start(ctx).updateProgram();
        lpAdd(lp, "{a;b}.");
        Potassco::TheoryData& t = lp.theoryData();
        t.addAtom(c, 0, {});
        lp.endProgram();
        REQUIRE(lp.getLiteral(c).var() != 0);
        REQUIRE(lp.isExternal(c));
        lp.updateProgram();
        lpAdd(lp, "c.");
        lp.endProgram();
        REQUIRE(lp.isFact(c));
        REQUIRE_FALSE(lp.isExternal(c));
        REQUIRE(ctx.master()->isTrue(lp.getLiteral(c)));
        LitVec vec;
        lp.getAssumptions(vec);
        REQUIRE(vec.empty());
    }

    SECTION("testCompEqBug") {
        lp.start(ctx);
        lp.updateProgram();
        lpAdd(lp, "{a;b}.\n"
                  "c :- not a.\n"
                  "d :- not b.\n"
                  "e :- a, b.");
        REQUIRE(lp.endProgram());
        lp.updateProgram();
        lpAdd(lp, "f :- c,d.\n"
                  "  :- not f.");
        CHECK(lp.endProgram());
        CHECK(lp.getAtom(e)->numSupports() == 1);
        CHECK(lp.getLiteral(f) == lit_true);
    }

    SECTION("testTermOutput") {
        lp.start(ctx);
        lpAdd(lp, "{a,b,c}. d.");
        Id_t fact, one;
        lp.addShowTerm(fact = lp.newShowTerm("fact"), {});
        lp.addShowTerm(one = lp.newShowTerm("one"), Potassco::LitVec{1});
        lp.addShowTerm(lp.newShowTerm("two"), Potassco::LitVec{1, 4});
        lp.addShowTerm(lp.newShowTerm("drop"), Potassco::LitVec{1, 2, -4, 3});
        auto multi = lp.newShowTerm("multi");
        lp.addShowTerm(multi, Potassco::LitVec{1, 3});
        lp.addShowTerm(multi, Potassco::LitVec{2, -4});
        lp.addShowTerm(multi, Potassco::LitVec{-2, 3});
        auto late = lp.newShowTerm("late");
        lp.addShowTerm(late, Potassco::LitVec{3});
        lp.addShowTerm(late, Potassco::LitSpan{});
        lp.addShowTerm(late, Potassco::LitVec{2, 4});
        lp.endProgram();
        lp.updateProgram();
        lpAdd(lp, "{e,f}.");
        lp.addShowTerm(fact, Potassco::LitVec{1, 5});
        lp.addShowTerm(one, Potassco::LitVec{6, -7});
        lp.addShowTerm(late, Potassco::LitVec{2, 4});
        lp.addShowTerm(multi, Potassco::LitVec{-5, -6});

        std::stringstream str;
        writeAspif(lp, str);
        REQUIRE(str.str() == "1 1 2 5 6 0 0\n"
                             "4 2 1 2 6 -7\n"
                             "4 2 4 2 -5 -6\n"
                             "0\n");

        lp.endProgram();
        str.str("");
        writeAspif(lp, str);
        REQUIRE(str.str() == "1 1 2 5 6 0 0\n"
                             "4 2 1 1 6\n"
                             "4 2 4 2 -5 -6\n"
                             "0\n");

        ValueVec values(ctx.numVars() + 1, value_free);
        values[0]                      = value_true;
        values[lp.getLiteral(2).var()] = value_true;
        values[lp.getLiteral(5).var()] = value_false;
        values[lp.getLiteral(6).var()] = value_false;
        auto res                       = getTheoryOutput(*ctx.output.theory_range().front(), values);
        REQUIRE(res.size() == 3);
        REQUIRE(contains(res, "late"s));
        REQUIRE(contains(res, "fact"s));
        REQUIRE(contains(res, "multi"s));

        lp.updateProgram();
        lp.addShowTerm(multi, Potassco::LitVec{});
        lp.endProgram();
        str.str("");
        writeAspif(lp, str);
        REQUIRE(str.str() == "4 2 4 0\n"
                             "0\n");
    }
}

TEST_CASE("Sat builder", "[sat]") {
    SharedContext ctx;
    SatBuilder    builder;
    builder.startProgram(ctx);
    SECTION("testPrepare") {
        builder.prepareProblem(10);
        builder.endProgram();
        REQUIRE(ctx.numVars() == 10);
    }
    SECTION("testNoClauses") {
        builder.prepareProblem(2);
        builder.endProgram();
        REQUIRE(ctx.master()->numFreeVars() == 0);
    }
    SECTION("testAddClause") {
        builder.prepareProblem(2);
        LitVec clause;
        clause.push_back(posLit(1));
        clause.push_back(posLit(2));
        builder.addClause(clause);
        builder.endProgram();
        REQUIRE(ctx.numConstraints() == 1);
        REQUIRE_FALSE(ctx.hasMinimize());
    }
    SECTION("testAddSoftClause") {
        builder.prepareProblem(3);
        LitVec clause;
        clause.push_back(posLit(1));
        builder.addClause(clause, 1);
        clause.clear();
        clause.push_back(negLit(1));
        clause.push_back(posLit(2));
        clause.push_back(negLit(3));
        builder.addClause(clause, 2);
        builder.endProgram();
        REQUIRE(ctx.numConstraints() == 1);
        REQUIRE(ctx.minimize()->numRules() == 1);
        REQUIRE(ctx.numVars() > 3);
    }
    SECTION("testAddEmptySoftClause") {
        builder.prepareProblem(3);
        LitVec clause;
        clause.push_back(posLit(1));
        // force 1
        builder.addClause(clause);

        clause.clear();
        clause.push_back(negLit(1));
        builder.addClause(clause, 1);
        REQUIRE(builder.endProgram());
        REQUIRE(ctx.minimize()->numRules() == 1);
        REQUIRE(ctx.minimize()->adjust(0) == 1);
    }
    SECTION("testAddConflicting") {
        builder.prepareProblem(3);
        LitVec clause;
        clause.push_back(posLit(1));
        REQUIRE(builder.addClause(clause));
        clause.clear();
        clause.push_back(negLit(1));
        REQUIRE(builder.addClause(clause) == false);
        clause.clear();
        clause.push_back(posLit(2));
        clause.push_back(posLit(3));
        REQUIRE(builder.addClause(clause) == false);
        REQUIRE(builder.endProgram() == false);
        REQUIRE(ctx.ok() == false);
    }
    SECTION("testAssumptions") {
        builder.prepareProblem(16);
        LitVec         clause;
        constexpr auto a = [](Var_t v) { return posLit(v); };
        constexpr auto x = [](Var_t v) { return posLit(v + 4); };
        constexpr auto y = [](Var_t v) { return posLit(v + 8); };
        constexpr auto z = [](Var_t v) { return posLit(v + 12); };
        for (auto v : irange(1u, 5u)) {
            clause.clear();
            clause.push_back(~a(1));
            clause.push_back(x(v));
            clause.push_back(z(v));
            builder.addClause(clause);

            clause.clear();
            clause.push_back(~y(v));
            clause.push_back(~x(v));
            builder.addClause(clause);

            clause.clear();
            clause.push_back(x(v));
            clause.push_back(~z(v));
            builder.addClause(clause);
            builder.addAssumption(a(v));
        }
        builder.endProgram();
        LitVec assume;
        builder.getAssumptions(assume);
        REQUIRE(ctx.numConstraints() == 12);
        REQUIRE(ctx.numVars() == 16);

        auto& s = *ctx.master();
        REQUIRE(s.pushRoot(assume));
        REQUIRE(s.rootLevel() == 4);
        REQUIRE(s.numFreeVars() == 12);
        for (auto v : irange(1u, 5u)) {
            REQUIRE(s.value(y(v).var()) == value_free);
            REQUIRE(s.value(z(v).var()) == value_free);
            s.assume(y(v));
            REQUIRE_FALSE(s.propagate());
            REQUIRE(s.resolveConflict());
            REQUIRE(s.propagate());
            REQUIRE(s.rootLevel() == 4);
            REQUIRE(s.isTrue(x(v)));
            REQUIRE(s.isFalse(y(v)));
            REQUIRE(s.isTrue(a(1)));
        }
        REQUIRE(s.numFreeVars() == 4u);
        for (auto v : irange(1u, 5u)) {
            REQUIRE(s.value(z(v).var()) == value_free);
            REQUIRE((s.assume(z(v)) && s.propagate()));
        }
    }
}

TEST_CASE("Pb builder", "[pb]") {
    SharedContext ctx;
    PBBuilder     builder;
    builder.startProgram(ctx);
    SECTION("testPrepare") {
        builder.prepareProblem(10, 0, 0);
        builder.endProgram();
        REQUIRE(ctx.numVars() == 10);
    }
    SECTION("testNegativeWeight") {
        builder.prepareProblem(5, 0, 0);
        WeightLitVec con;
        con.push_back(WeightLiteral{posLit(1), 2});
        con.push_back(WeightLiteral{posLit(2), -2});
        con.push_back(WeightLiteral{posLit(3), -1});
        con.push_back(WeightLiteral{posLit(4), 1});
        con.push_back(WeightLiteral{posLit(5), 1});
        builder.addConstraint(con, 2);
        builder.endProgram();
        REQUIRE(ctx.numConstraints() == 1);
        REQUIRE(ctx.master()->numAssignedVars() == 0);
        ctx.master()->assume(negLit(1)) && ctx.master()->propagate();
        REQUIRE(ctx.master()->numFreeVars() == 0);
    }
    SECTION("testProduct") {
        builder.prepareProblem(3, 1, 1);
        LitVec p1(3), p2;
        p1[0]  = posLit(1);
        p1[1]  = posLit(2);
        p1[2]  = posLit(3);
        p2     = p1;
        auto x = builder.addProduct(p1);
        auto y = builder.addProduct(p2);
        REQUIRE((x.var() == 4 && x == y));
    }
    SECTION("testProductTrue") {
        builder.prepareProblem(3, 1, 1);
        LitVec p1(3);
        p1[0] = posLit(1);
        p1[1] = posLit(2);
        p1[2] = posLit(3);
        ctx.master()->force(posLit(1), nullptr);
        ctx.master()->force(posLit(2), nullptr);
        ctx.master()->force(posLit(3), nullptr);
        auto x = builder.addProduct(p1);
        REQUIRE(x == lit_true);
    }
    SECTION("testProductFalse") {
        builder.prepareProblem(3, 1, 1);
        LitVec p1(3);
        p1[0] = posLit(1);
        p1[1] = posLit(2);
        p1[2] = posLit(3);
        ctx.master()->force(negLit(2), nullptr);
        auto x = builder.addProduct(p1);
        REQUIRE(x == lit_false);
    }
    SECTION("testInconsistent") {
        builder.prepareProblem(1, 0, 0);
        WeightLitVec con;
        con.push_back(WeightLiteral{posLit(1), 1});
        REQUIRE(builder.addConstraint(con, 1));
        con.assign(1, WeightLiteral{posLit(1), 1});
        REQUIRE_FALSE(builder.addConstraint(con, 0, true));
        REQUIRE_FALSE(builder.endProgram());
    }
    SECTION("testInconsistentW") {
        builder.prepareProblem(2, 0, 0);
        WeightLitVec con;
        con.push_back(WeightLiteral{posLit(1), 1});
        REQUIRE(builder.addConstraint(con, 1));
        con.assign(1, WeightLiteral{posLit(1), 3});
        con.push_back(WeightLiteral{posLit(2), 2});
        REQUIRE_FALSE(builder.addConstraint(con, 2, true));
        REQUIRE_FALSE(builder.endProgram());
    }
    SECTION("testCoefficientReductionOnEq") {
        builder.prepareProblem(4, 0, 0);
        WeightLitVec con;
        con.push_back(WeightLiteral{posLit(1), 3});
        con.push_back(WeightLiteral{posLit(2), 2});
        con.push_back(WeightLiteral{posLit(3), 1});
        con.push_back(WeightLiteral{posLit(4), 1});
        REQUIRE(builder.addConstraint(con, 2, true));
        REQUIRE(builder.endProgram());
        REQUIRE(ctx.master()->isFalse(posLit(1)));
    }
}
} // namespace Clasp::Test
