//
// Copyright (c) 2013-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 <clasp/logic_program.h>

#include <clasp/asp_preprocessor.h>
#include <clasp/clause.h>
#include <clasp/dependency_graph.h>
#include <clasp/enumerator.h>
#include <clasp/minimize_constraint.h>
#include <clasp/parser.h>
#include <clasp/shared_context.h>
#include <clasp/solver.h>
#include <clasp/util/misc_types.h>

#include <potassco/theory_data.h>

#include <cctype>
#include <unordered_map>
#include <unordered_set>

namespace Clasp::Asp {
/////////////////////////////////////////////////////////////////////////////////////////
// Statistics
/////////////////////////////////////////////////////////////////////////////////////////
using namespace std::literals;
#define RK(x) RuleStats::x
auto RuleStats::toStr(unsigned k) -> std::string_view {
    POTASSCO_ASSERT(k <= numKeys(), "Invalid key");
    switch (k) {
        case normal   : return "Normal"sv;
        case choice   : return "Choice"sv;
        case minimize : return "Minimize"sv;
        case acyc     : return "Acyc"sv;
        case heuristic: return "Heuristic"sv;
        default       : return "None"sv;
    }
}
uint32_t RuleStats::sum() const { return std::accumulate(key, key + numKeys(), 0u); }
auto     BodyStats::toStr(unsigned t) -> std::string_view {
    POTASSCO_ASSERT(t < numKeys(), "Invalid body type!");
    switch (t) {
        default                            : return "Normal"sv;
        case to_underlying(BodyType::count): return "Count"sv;
        case to_underlying(BodyType::sum)  : return "Sum"sv;
    }
}
uint32_t BodyStats::sum() const { return std::accumulate(key, key + numKeys(), 0u); }

namespace {
double sum(const BodyStats* bs) { return bs->sum(); }
double sum(const RuleStats* rs) { return rs->sum(); }
double sumEqs(const LpStats* self) { return self->eqs(); }
} // namespace
#define LP_STATS(APPLY)                                                                                                \
    APPLY("atoms", VALUE(atoms))                                                                                       \
    APPLY("atoms_aux", VALUE(auxAtoms))                                                                                \
    APPLY("disjunctions", VALUE(disjunctions[0]))                                                                      \
    APPLY("disjunctions_non_hcf", VALUE(disjunctions[1]))                                                              \
    APPLY("bodies", SUM(bodies[0]))                                                                                    \
    APPLY("bodies_tr", SUM(bodies[1]))                                                                                 \
    APPLY("sum_bodies", VALUE(bodies[0][to_underlying(BodyType::sum)]))                                                \
    APPLY("sum_bodies_tr", VALUE(bodies[1][to_underlying(BodyType::sum)]))                                             \
    APPLY("count_bodies", VALUE(bodies[0][to_underlying(BodyType::count)]))                                            \
    APPLY("count_bodies_tr", VALUE(bodies[1][to_underlying(BodyType::count)]))                                         \
    APPLY("sccs", VALUE(sccs))                                                                                         \
    APPLY("sccs_non_hcf", VALUE(nonHcfs))                                                                              \
    APPLY("gammas", VALUE(gammas))                                                                                     \
    APPLY("ufs_nodes", VALUE(ufsNodes))                                                                                \
    APPLY("rules", SUM(rules[0]))                                                                                      \
    APPLY("rules_normal", VALUE(rules[0][RK(normal)]))                                                                 \
    APPLY("rules_choice", VALUE(rules[0][RK(choice)]))                                                                 \
    APPLY("rules_minimize", VALUE(rules[0][RK(minimize)]))                                                             \
    APPLY("rules_acyc", VALUE(rules[0][RK(acyc)]))                                                                     \
    APPLY("rules_heuristic", VALUE(rules[0][RK(heuristic)]))                                                           \
    APPLY("rules_tr", SUM(rules[1]))                                                                                   \
    APPLY("rules_tr_normal", VALUE(rules[1][RK(normal)]))                                                              \
    APPLY("rules_tr_choice", VALUE(rules[1][RK(choice)]))                                                              \
    APPLY("rules_tr_minimize", VALUE(rules[1][RK(minimize)]))                                                          \
    APPLY("rules_tr_acyc", VALUE(rules[1][RK(acyc)]))                                                                  \
    APPLY("rules_tr_heuristic", VALUE(rules[1][RK(heuristic)]))                                                        \
    APPLY("eqs", FUN(sumEqs))                                                                                          \
    APPLY("eqs_atom", VALUE(eqs_[+VarType::atom - 1]))                                                                 \
    APPLY("eqs_body", VALUE(eqs_[+VarType::body - 1]))                                                                 \
    APPLY("eqs_other", VALUE(eqs_[+VarType::hybrid - 1]))

void LpStats::accu(const LpStats& o) {
    atoms    += o.atoms;
    auxAtoms += o.auxAtoms;
    ufsNodes += o.ufsNodes;
    if (sccs == PrgNode::scc_not_set || o.sccs == PrgNode::scc_not_set) {
        sccs    = o.sccs;
        nonHcfs = o.nonHcfs;
        gammas  = o.gammas;
    }
    else {
        sccs    += o.sccs;
        nonHcfs += o.nonHcfs;
        gammas  += o.gammas;
    }
    for (auto i : irange(disjunctions)) {
        disjunctions[i] += o.disjunctions[i];
        for (auto k : irange(BodyStats::numKeys())) { bodies[i][k] += o.bodies[i][k]; }
        for (auto k : irange(RuleStats::numKeys())) { rules[i][k] += o.rules[i][k]; }
    }
    std::ranges::transform(eqs_, o.eqs_, eqs_, std::plus{});
}

static constexpr std::string_view lp_stats_s[] = {
#define KEY(x, y) x,
    LP_STATS(KEY)
#undef KEY
};
auto LpStats::size() -> uint32_t { return size32(lp_stats_s); }
auto LpStats::key(uint32_t i) -> std::string_view {
    POTASSCO_CHECK(i < size(), ERANGE);
    return lp_stats_s[i];
}
auto LpStats::at(std::string_view k) const -> StatisticObject {
#define MAP_IF(x, A)                                                                                                   \
    if (k == (x))                                                                                                      \
        return A;
#define VALUE(X) StatisticObject::value(&(X))
#define SUM(X)   StatisticObject::value<static_cast<double (*)(decltype(&this->X))>(sum)>(&this->X)
#define FUN(F)   StatisticObject::value<F>(this)
    LP_STATS(MAP_IF)
#undef VALUE
#undef FUNC
#undef MAP_IF
    POTASSCO_FAIL(ERANGE);
}
#undef LP_STATS
/////////////////////////////////////////////////////////////////////////////////////////
// class LogicProgram
/////////////////////////////////////////////////////////////////////////////////////////
constexpr uint32_t false_id = PrgNode::no_node;
constexpr uint32_t body_id  = PrgNode::no_node + 1;
constexpr bool     isAtom(Id_t uid) { return Potassco::atom(Potassco::lit(uid)) < body_id; }
constexpr bool     isBody(Id_t uid) { return Potassco::atom(Potassco::lit(uid)) >= body_id; }
constexpr Id_t     nodeId(Id_t uid) { return Potassco::atom(Potassco::lit(uid)) - (isAtom(uid) ? 0 : body_id); }
constexpr bool     signId(Id_t uid) { return Potassco::lit(uid) < 0; }

using AtomVal = std::pair<Atom_t, Potassco::TruthValue>;
constexpr uint32_t encodeExternal(Atom_t a, Potassco::TruthValue value) {
    return (a << 2) | static_cast<uint32_t>(value);
}
constexpr AtomVal decodeExternal(uint32_t x) { return {x >> 2, static_cast<Potassco::TruthValue>(x & 3u)}; }

// Adds nogoods representing this node to the solver.
template <typename NodeType>
static bool toConstraint(NodeType* node, const LogicProgram& prg, ClauseCreator& c) {
    if (node->value() != value_free && not prg.ctx()->addUnary(node->trueLit())) {
        return false;
    }
    return not node->relevant() || node->addConstraints(prg, c);
}

using IdSet    = std::unordered_set<Id_t>;
using IndexMap = std::unordered_multimap<uint32_t, uint32_t>;
struct LogicProgram::Aux {
    auto showAtoms(const SharedContext& ctx) const {
        return ctx.output.pred_range().subspan(std::min(show, ctx.output.numPreds()));
    }
    auto hasLitOutput(const SharedContext& ctx) const {
        return std::ranges::any_of(
            showAtoms(ctx), [](const OutputTable::PredType& pred) { return pred.user == 0 || signId(pred.user); });
    }
    AtomList  scc;              // atoms that are strongly connected
    DomRules  dom;              // list of domain heuristic directives
    AcycRules acyc;             // list of user-defined edges for acyclicity check
    VarVec    project;          // atoms in projection directives
    VarVec    external;         // atoms in external directives
    IdSet     skippedHeads;     // heads of rules that have been removed during parsing
    uint32_t  show{UINT32_MAX}; // position of first output predicate
};

struct LogicProgram::IndexData {
    IndexMap body; // hash -> body id
    IndexMap disj; // hash -> disjunction id
    PrgAtom* eqTrue{nullptr};
    Atom_t   fact{0}; // first fact atom
    bool     distTrue{false};
};

struct LogicProgram::ShowTerm {
    explicit ShowTerm(std::string_view str) : name(str) {}
    [[nodiscard]] auto isSat() const -> bool { return condition.size() == 1; }
    [[nodiscard]] auto isUnsat() const -> bool { return condition.empty(); }
    [[nodiscard]] auto isOpen() const -> bool { return condition.size() > 1; }
    [[nodiscard]] auto view() const -> ShowTermView { return {*this}; }
    [[nodiscard]] auto conditions(uint32_t offset = 0) const -> ShowTermView::CondView {
        return ShowTermView::CondView{*this, offset};
    }
    Potassco::ConstString name;
    PodVector_t<Id_t>     condition;
};
LogicProgram::ShowTermView::CondView::CondView(const ShowTerm& t, uint32_t off)
    : c(std::span(t.condition).subspan(off)) {}
LogicProgram::ShowTermView::ShowTermView(const ShowTerm& t) : name_(t.name), conditions_(t.conditions()) {}

auto LogicProgram::ShowTermView::CondIter::operator*() const -> value_type {
    return data_.empty() ? data_ : std::span{data_.data() + 1, data_.front()};
}
void LogicProgram::ShowTermView::CondIter::advance() {
    assert(not data_.empty());
    auto n = data_.front() + 1;
    data_  = data_.subspan(n + (n < size32(data_) && data_[n] == 0));
}

class LogicProgram::TermOutput : public OutputTable::Theory {
public:
    using TermMap = PodVector_t<ShowTerm*>;
    using IdVec   = PodVector_t<Id_t>;
    struct TermRef {
        Id_t     id         = 0;
        uint32_t pos   : 31 = 0;
        uint32_t first : 1  = 0;
    };
    using StepVec = PodVector_t<TermRef>;
    explicit TermOutput(LogicProgram& lp) : prg_(&lp), ctx_(lp.ctx()) {}
    ~TermOutput() override { std::ranges::for_each(terms_, DeleteObject()); }
    auto first(const Model& m) -> const char* override {
        model_ = &m;
        mView_ = sorted_;
        return next();
    }
    auto next() -> const char* override {
        while (not mView_.empty()) {
            auto front = mView_.front();
            mView_     = mView_.subspan(1);
            if (auto* term = isTrue(*model_, front); term && not ctx_->output.filter(term->name.view())) {
                return term->name.c_str();
            }
        }
        return nullptr;
    }
    [[nodiscard]] auto type() const -> OutputTable::Type override { return OutputTable::type_term; }
    [[nodiscard]] auto isTrue(const Model& m, Id_t termId) const -> const ShowTerm* {
        auto cons = m.consequences();
        if (auto* term = termId < size32(terms_) ? terms_[termId] : nullptr; term) {
            for (auto cube : term->conditions()) {
                if (std::ranges::all_of(cube, [&](Id_t x) {
                        auto xLit = prg_ ? prg_->getLiteral(x) : Literal::fromRep(x);
                        if (auto ok = m.isTrue(xLit); ok || not cons) {
                            return ok;
                        }
                        auto isNeg = prg_ ? signId(x) : xLit.flagged();
                        return isNeg && not m.isTrue(~xLit);
                    })) {
                    return term;
                }
            }
        }
        return nullptr;
    }
    [[nodiscard]] auto get(Id_t id) const -> ShowTermView {
        POTASSCO_CHECK_PRE(id < size32(terms_) && terms_[id] != nullptr, "term not defined");
        return terms_[id]->view();
    }
    Id_t add(Id_t id, std::string_view str) {
        POTASSCO_CHECK_PRE(id >= size32(terms_) || terms_[id] == nullptr || terms_[id]->name == str,
                           "redefinition of term %u", id);
        if (id == id_max) {
            id = size32(terms_);
        }
        if (id >= size32(terms_)) {
            terms_.resize(id + 1, nullptr);
        }
        if (terms_[id] == nullptr) {
            terms_[id] = new ShowTerm(str);
            sorted_.push_back(id);
        }
        return id;
    }
    void append(Id_t id, Potassco::LitSpan cond) {
        POTASSCO_CHECK_PRE(id < size32(terms_) && terms_[id] != nullptr, "unknown show term");
        POTASSCO_CHECK_PRE(prg_, "not attached");
        auto* term = terms_[id];
        if (term->isSat()) { // already always SAT - ignore additional condition
            return;
        }
        auto& dnf = term->condition;
        if (dnf.empty() || dnf.back() != 0) { // first time occurrence in this step
            step_.push_back({id, size32(dnf), dnf.empty()});
        }
        auto insPos = size32(dnf);
        dnf.resize(insPos + size32(cond) + 2);
        dnf[insPos++] = size32(cond);
        *std::ranges::transform(cond, dnf.data() + insPos, [](Potassco::Lit_t lit) { return Asp::id(lit); }).out = 0;
    }
    void prepare(AtomSorting sorting) {
        POTASSCO_CHECK_PRE(prg_, "not attached");
        for (auto& [termId, pos, _] : step_) {
            if (auto* term = terms_.at(termId); term->isOpen() && term->condition.back() == 0) {
                auto& dnf = term->condition;
                POTASSCO_ASSERT(pos < size32(dnf));
                auto j = dnf.begin() + pos;
                auto v = term->conditions(pos);
                for (auto it = v.begin(); it != v.end();) {
                    auto out   = j + 1;
                    bool unsat = false;
                    for (auto x : *it++) {
                        if (auto lit = prg_->getLiteral(x); not isSentinel(lit)) {
                            *out++ = x;
                        }
                        else if (lit == lit_false) {
                            unsat = true;
                            break; // drop this false condition
                        }
                    }
                    if (not unsat) {
                        if (*j = static_cast<uint32_t>(out - j) - 1; *j == 0) { // new fact - no need to continue
                            dnf.assign(1u, 0u);
                            pos = 0;
                            break;
                        }
                        j = out;
                    }
                }
                if (not term->isSat()) {
                    dnf.erase(j, dnf.end());
                    POTASSCO_ASSERT(pos <= size32(dnf));
                }
            }
        }
        auto cmp = static_cast<Potassco::AtomCompare>(-1);
        switch (sorting) {
            case sort_name        : cmp = Potassco::AtomCompare::cmp_default; break;
            case sort_natural     : cmp = Potassco::AtomCompare::cmp_natural; break;
            case sort_arity       : cmp = Potassco::AtomCompare::cmp_arity; break;
            case sort_arity_natual: cmp = Potassco::AtomCompare::cmp_arity | Potassco::AtomCompare::cmp_natural; break;
            default               : return;
        }
        std::ranges::sort(sorted_, [&](const Id_t lhs, const Id_t rhs) {
            auto r = Potassco::cmpAtom(terms_[lhs]->name.view(), terms_[rhs]->name.view(), cmp);
            return std::is_lt(r) || (std::is_eq(r) && lhs < rhs);
        });
    }
    void accept(Potassco::AbstractProgram& out, Potassco::LitVec& temp) {
        POTASSCO_CHECK_PRE(prg_, "not attached");
        for (const auto& [termId, pos, first] : step_) {
            auto* term = terms_.at(termId);
            POTASSCO_ASSERT(pos <= size32(term->condition));
            if (not term->isUnsat() && first) {
                out.outputTerm(termId, term->name.view());
            }
            for (auto cube : term->conditions(pos)) {
                temp.clear();
                for (auto x : cube) { temp.push_back(Potassco::lit(x)); }
                out.output(termId, temp);
            }
        }
    }
    void dispose() { discardVec(step_); }
    void detach() {
        if (auto lp = std::exchange(prg_, nullptr)) {
            for (auto* term : terms_) {
                for (auto it = term->condition.begin(), end = term->condition.end(); it != end;) {
                    for (auto n = *it++; n--; ++it) {
                        auto neg = signId(*it);
                        auto lit = lp->getLiteral(*it);
                        *it      = neg ? lit.flag().rep() : lit.unflag().rep();
                        POTASSCO_ASSERT(lit.flagged() == neg);
                    }
                }
            }
        }
    }

private:
    using IdView = SpanView<Id_t>;
    LogicProgram*  prg_{nullptr};
    SharedContext* ctx_{nullptr};
    const Model*   model_{nullptr};
    IdView         mView_;
    TermMap        terms_;
    IdVec          sorted_;
    StepVec        step_;
};

LogicProgram::LogicProgram()
    : index_(std::make_unique<IndexData>())
    , input_(1, UINT32_MAX)
    , statsId_(0)
    , auxData_(std::make_unique<Aux>())
    , incData_(nullptr)
    , termOutput_(nullptr) {}
LogicProgram::~LogicProgram() { reset(nullptr); }
void LogicProgram::reset(SharedContext* nc) {
    if (auto* to = std::exchange(termOutput_, nullptr)) {
        if (nc && nc->output.remove(*to)) {
            delete to;
        }
        else {
            to->detach();
        }
    }
    dispose();
    deleteAtoms(0);
    discardVec(assume_);
    discardVec(atomState_);
    discardVec(atoms_);
    discardVec(frozen_);
    discardVec(propQ_);
    nonHcfs_ = NonHcfSet();
    incData_.reset();
    input_   = AtomRange(1, UINT32_MAX);
    stats    = {};
    statsId_ = 0;
    *index_  = IndexData();
}
void LogicProgram::dispose() {
    auto disposeVec = [](auto& vec, auto destroyOp) {
        std::ranges::for_each(vec, destroyOp);
        discardVec(vec);
    };
    disposeVec(bodies_, DestroyObject());
    disposeVec(disjunctions_, DestroyObject());
    disposeVec(extended_, DeleteObject());
    disposeVec(minimize_, DeleteObject());
    discardVec(initialSupp_);
    *auxData_ = Aux();
    index_->body.clear();
    index_->disj.clear();
    theory_.reset();
    rule_.clear();
    if (termOutput_) {
        termOutput_->dispose();
    }
}
void LogicProgram::deleteAtoms(uint32_t start) {
    for (PrgAtom* atom : atoms(start)) {
        if (atom != index_->eqTrue) {
            delete atom;
        }
    }
    if (start == 0 && index_ && index_->eqTrue) {
        delete std::exchange(index_->eqTrue, nullptr);
    }
}
bool LogicProgram::doStartProgram() {
    if (not atoms_.empty() || termOutput_) {
        reset(ctx());
    }
    // atom 0 is always true
    atoms_.push_back(new PrgAtom(0));
    auto* trueAt = getTrueAtom();
    trueAt->setScc(PrgNode::scc_triv);
    trueAt->assignValue(value_true);
    trueAt->setInUpper(true);
    trueAt->setLiteral(lit_true);
    trueAt->setFact(true);
    auxData_->show = ctx()->output.numPreds();
    return true;
}
void LogicProgram::setOptions(const AspOptions& opts) {
    opts_ = opts;
    if (opts.suppMod) {
        opts_.noSCC = 1;
    }
    if (opts.suppMod && ctx() && ctx()->sccGraph.get()) {
        ctx()->warn("'supp-models' ignored for non-tight programs.");
        opts_.suppMod = 0;
        opts_.noSCC   = 0;
    }
    if (opts_.sortAtom == sort_auto) {
        opts_.sortAtom = sort_number;
    }
}
void LogicProgram::enableDistinctTrue() { index_->distTrue = true; }
auto LogicProgram::doCreateParser() -> ParserPtr { return std::make_unique<AspParser>(*this); }
bool LogicProgram::doUpdateProgram() {
    if (not incData_) {
        incData_ = std::make_unique<Incremental>();
    }
    if (not frozen()) {
        return true;
    }
    // delete bodies/disjunctions...
    dispose();
    setFrozen(false);
    assume_.clear();
    theory_.update();
    incData_->unfreeze.clear();
    incData_->first = false;
    input_.hi       = std::min(input_.hi, endAtom());
    // reset the prop queue and add supported atoms from previous steps
    // {'ai' | 'ai' in P}.
    PrgBody* support = input_.hi > 1 ? getTrueBody() : nullptr;
    propQ_.clear();
    for (auto end = startAuxAtom(); Atom_t i : irange(1u, end)) {
        if (getRootId(i) >= end) {
            // atom is equivalent to some aux atom - make i the new root
            uint32_t r = getRootId(i);
            std::swap(atoms_[i], atoms_[r]);
            atoms_[r]->setEq(i);
        }
        // remove dangling references
        PrgAtom* a = atoms_[i];
        a->clearSupports();
        a->clearDeps(PrgAtom::dep_all);
        if (a->relevant() || a->frozen()) {
            auto v = a->value();
            a->setValue(value_free);
            a->resetId(i, not a->frozen());
            if (ctx()->master()->value(a->var()) != value_free && not a->frozen()) {
                v = ctx()->master()->isTrue(a->literal()) ? value_true : value_false;
            }
            if (v != value_free) {
                assignValue(a, v, PrgEdge::noEdge());
            }
            if (not a->frozen()) {
                a->setScc(PrgNode::scc_triv);
                if (a->value() != value_false) {
                    support->addHead(a, PrgEdge::gamma_choice);
                }
            }
            else {
                a->setScc(PrgNode::scc_not_set);
            }
        }
        else {
            assert(a->removed() || a->eq());
            if (a->removed()) {
                a->resetId(i, true);
                a->setValue(value_false);
            }
        }
    }
    // Delete any introduced aux atoms.
    // This is safe because aux atoms are never part of the input program.
    // It is necessary to free their ids, i.e., the id of an aux atom
    // from step I might be needed for a program atom in step I+1.
    deleteAtoms(startAuxAtom());
    shrinkVecTo(atoms_, startAuxAtom());
    auto nAtoms = size32(atoms_);
    atomState_.resize(nAtoms);
    input_         = AtomRange(nAtoms, UINT32_MAX);
    stats          = {};
    statsId_       = 0;
    auxData_->show = ctx()->output.numPreds();
    return true;
}
bool LogicProgram::doEndProgram() {
    if (not frozen() && ctx()->ok()) {
        prepareProgram(not opts_.noSCC);
        addConstraints();
        addDomRules();
        addAcycConstraint();
    }
    return ctx()->ok();
}

void LogicProgram::addMinimize() {
    POTASSCO_ASSERT(frozen());
    for (const auto* min : minimize_) {
        auto prio = min->bound();
        auto lits = min->sumLits();
        for (const auto& [lit, weight] : lits) { addMinLit(prio, WeightLiteral{getLiteral(Asp::id(lit)), weight}); }
        // Make sure the minimize constraint is not empty
        if (lits.empty()) {
            addMinLit(prio, WeightLiteral{lit_false, 1});
        }
    }
}
static void outRule(Potassco::AbstractProgram& out, const Rule& r) {
    if (r.normal()) {
        out.rule(r.ht, r.head, r.cond);
    }
    else {
        out.rule(r.ht, r.head, r.agg.bound, r.agg.lits);
    }
}

void LogicProgram::accept(Potassco::AbstractProgram& out, bool addPreamble) {
    if (not started()) {
        return;
    }
    if (addPreamble) {
        if (not incData_ || incData_->first) {
            out.initProgram(incData_ != nullptr);
        }
        out.beginStep();
    }
    POTASSCO_SCOPE_EXIT({
        if (addPreamble) {
            out.endStep();
        }
    });
    if (not ok()) {
        out.rule(HeadType::disjunctive, {}, {});
        return;
    }
    // visit external directives
    for (auto e : auxData_->external) {
        auto [atom, value] = decodeExternal(e);
        out.external(atom, value);
    }
    // visit eq- and assigned atoms
    for (auto i : irange(startAtom(), size32(atoms_))) {
        if (auto* atom = atoms_[i]; atom->eq()) {
            auto head = Potassco::toSpan(i);
            if (auto body = Potassco::lit(getRootId(i)); isFact(Potassco::atom(body))) {
                out.rule(HeadType::disjunctive, head, {});
            }
            else if (inProgram(Potassco::atom(body))) {
                out.rule(HeadType::disjunctive, head, Potassco::toSpan(body));
            }
        }
        else if (not atom->isFact() && atom->value() != value_free) {
            auto body = Potassco::neg(i);
            if (atoms_[i]->value() != value_false) {
                out.rule(HeadType::disjunctive, {}, Potassco::toSpan(body));
            }
            else if (inProgram(i)) {
                body = Potassco::neg(body);
                out.rule(HeadType::disjunctive, {}, Potassco::toSpan(body));
            }
        }
    }
    // visit program rules
    const bool simp = frozen();
    using Potassco::Lit_t;
    VarVec choice;
    for (auto* b : bodies_) {
        rule_.clear();
        choice.clear();
        if (b->relevant() && (b->inRule() || b->value() == value_false) && b->toData(*this, rule_)) {
            if (b->value() == value_false) {
                outRule(out, rule_.rule());
                continue;
            }
            uint32_t numDis = 0;
            Atom_t   head;
            Lit_t    auxB;
            Rule     r = rule_.rule();
            for (auto h : b->heads()) {
                if (h.isGamma() || (simp && not getHead(h)->hasVar())) {
                    continue;
                }
                if (h.isAtom() && h.node() && inProgram(h.node())) {
                    if (h.isNormal()) {
                        head   = h.node();
                        r.head = Potassco::toSpan(head);
                        outRule(out, r);
                    }
                    else if (h.isChoice()) {
                        choice.push_back(h.node());
                    }
                    if (simp && getRootAtom(h.node())->var() == b->var() && not r.normal()) {
                        // replace complex body with head atom
                        auxB = Potassco::lit(h.node());
                        if (getRootAtom(h.node())->literal() != b->literal()) {
                            auxB *= -1;
                        }
                        r.bt   = BodyType::normal;
                        r.cond = Potassco::toSpan(auxB);
                    }
                }
                else if (h.isDisj()) {
                    ++numDis;
                }
            }
            if (not choice.empty()) {
                r.head = choice;
                r.ht   = HeadType::choice;
                outRule(out, r);
            }
            if (numDis) {
                for (auto h : b->heads()) {
                    if (h.isDisj()) {
                        PrgDisj* d = getDisj(h.node());
                        r.head     = d->atoms();
                        r.ht       = HeadType::disjunctive;
                        outRule(out, r);
                        if (--numDis == 0) {
                            break;
                        }
                    }
                }
            }
        }
    }
    LpWLitVec wlits;
    auto      pred = [&](Potassco::WeightLit x) {
        return x.weight != 0 && (x.weight < 0 || x.lit < 0 || inProgram(getRootId(Potassco::atom(x))));
    };
    for (const auto* min : minimize_) {
        WeightLitSpan ws = min->sumLits();
        if (auto it = std::ranges::find_if_not(ws, pred); it != ws.end()) {
            // simplify literals
            wlits.assign(ws.begin(), it);
            for (++it; it != ws.end(); ++it) {
                if (pred(*it)) {
                    wlits.push_back(*it);
                }
            }
            ws = wlits;
        }
        out.minimize(min->bound(), ws);
    }
    // visit output atoms
    for (const auto& x : auxData_->showAtoms(*ctx())) {
        if (auto a = x.user ? x.user : factAtom(); a && isAtom(a) && not signId(a)) {
            out.outputAtom(a, x.name.view());
        }
        else {
            // Literal output is not supported in AbstractProgram!
        }
    }
    // visit output terms
    Potassco::LitVec lits;
    if (termOutput_) {
        termOutput_->accept(out, lits);
    }
    // visit projection directives
    if (not auxData_->project.empty()) {
        out.project(auxData_->project.back() ? Potassco::AtomSpan(auxData_->project) : Potassco::AtomSpan{});
    }
    // visit assumptions
    if (not assume_.empty()) {
        out.assume(assume_);
    }
    // visit heuristics
    for (const auto& dom : auxData_->dom) {
        if (extractCondition(dom.cond, lits)) {
            out.heuristic(dom.atom, static_cast<DomModType>(dom.type), dom.bias, dom.prio, lits);
        }
    }
    // visit acyc edges
    for (const auto& a : auxData_->acyc) {
        if (extractCondition(a.cond, lits)) {
            out.acycEdge(static_cast<int>(a.node[0]), static_cast<int>(a.node[1]), lits);
        }
    }

    if (theory_.numAtoms()) {
        struct This final : TheoryData::Visitor {
            This(const LogicProgram& p, Potassco::AbstractProgram& o, Potassco::LitVec& c)
                : self(&p)
                , out(&o)
                , cond(&c) {}
            void visit(const TheoryData& data, Id_t termId, const Potassco::TheoryTerm& t) override {
                if (not addTermSeen(termId)) {
                    return;
                }
                data.accept(t, *this, TheoryData::visit_current);
                Potassco::print(*out, termId, t);
            }
            void visit(const TheoryData& data, Id_t elemId, const Potassco::TheoryElement& e) override {
                if (not addElemSeen(elemId)) {
                    return;
                }
                data.accept(e, *this, TheoryData::visit_current);
                cond->clear();
                if (e.condition()) {
                    self->extractCondition(e.condition(), *cond);
                }
                out->theoryElement(elemId, e.terms(), *cond);
            }
            void visit(const TheoryData& data, const Potassco::TheoryAtom& a) override {
                data.accept(a, *this, TheoryData::visit_current);
                Potassco::print(*out, a);
                const Atom_t id = a.atom();
                if (self->validAtom(id) && not self->inProgram(id) && self->getAtom(id)->fixed() == value_false) {
                    Lit_t x = Potassco::lit(id);
                    out->rule(HeadType::disjunctive, {}, Potassco::toSpan(x));
                }
            }
            bool addTermSeen(Id_t id) { return seen.add(id * 2); }
            bool addElemSeen(Id_t id) { return seen.add((id * 2) + 1); }

            const LogicProgram*        self;
            Potassco::AbstractProgram* out;
            Potassco::LitVec*          cond;
            Potassco::DynamicBitset    seen;
        } self(*this, out, lits);
        theory_.accept(self, TheoryData::visit_current);
    }
}
/////////////////////////////////////////////////////////////////////////////////////////
// Program mutating functions
/////////////////////////////////////////////////////////////////////////////////////////
#define CHECK_NOT_FROZEN() POTASSCO_CHECK_PRE(not frozen(), "Can't update frozen program!")
static const char* getAtomName(const LogicProgram& prg, Atom_t a) {
    const char* ret = prg.findName(a);
    return ret && *ret ? ret : "_";
}
#define CHECK_MODULAR(x, atomId)                                                                                       \
    POTASSCO_CHECK_PRE(x, "redefinition of atom <'%s',%u>", getAtomName(*this, (atomId)), (atomId))

Atom_t LogicProgram::newAtom() {
    CHECK_NOT_FROZEN();
    auto id = size32(atoms_);
    atoms_.push_back(new PrgAtom(id));
    return id;
}
Id_t LogicProgram::newCondition(Potassco::LitSpan cond) {
    CHECK_NOT_FROZEN();
    SRule meta;
    if (simplifyNormal(HeadType::disjunctive, {}, cond, rule_, meta)) {
        Rule r = rule_.rule();
        if (r.cond.empty()) {
            return 0;
        }
        if (r.cond.size() == 1) {
            return Asp::id(r.cond[0]);
        }
        PrgBody* b = getBodyFor(r, meta);
        b->markFrozen();
        return static_cast<Id_t>(Clasp::Asp::body_id | b->id());
    }
    return static_cast<Id_t>(Clasp::Asp::false_id);
}
auto LogicProgram::addAtomOutput(Atom_t atom, std::string_view name) -> LogicProgram& {
    return addLiteralOutput(Potassco::lit(atom), name);
}
auto LogicProgram::addLiteralOutput(Potassco::Lit_t lit, std::string_view name) -> LogicProgram& {
    CHECK_NOT_FROZEN();
    resize(Potassco::atom(lit));
    ctx()->output.add(name, lit_false, id(lit));
    return *this;
}
Id_t LogicProgram::newShowTerm(std::string_view str, Id_t id) {
    CHECK_NOT_FROZEN();
    if (not termOutput_) {
        auto termPtr = std::make_unique<TermOutput>(*this);
        termOutput_  = termPtr.get();
        ctx()->output.add(std::move(termPtr));
    }
    return termOutput_->add(id, str);
}
LogicProgram& LogicProgram::addShowTerm(Id_t id, Potassco::LitSpan cond) {
    CHECK_NOT_FROZEN();
    POTASSCO_CHECK_PRE(termOutput_, "term not defined");
    termOutput_->append(id, cond);
    return *this;
}
auto LogicProgram::getShowTerm(Id_t id) const -> ShowTermView {
    POTASSCO_CHECK_PRE(termOutput_, "term not defined");
    return termOutput_->get(id);
}
auto LogicProgram::isShowTermTrue(const Model& m, Id_t term) const -> bool {
    return termOutput_ && termOutput_->isTrue(m, term) != nullptr;
}
LogicProgram& LogicProgram::addProject(Potassco::AtomSpan atoms) {
    CHECK_NOT_FROZEN();
    VarVec& pro = auxData_->project;
    if (not atoms.empty()) {
        if (not pro.empty() && pro.back() == 0) {
            pro.pop_back();
        }
        pro.insert(pro.end(), atoms.begin(), atoms.end());
    }
    else if (pro.empty()) {
        pro.push_back(0);
    }
    return *this;
}
LogicProgram& LogicProgram::removeProject() {
    bool cleanup = not auxData_->project.empty() || ctx()->output.hasProject();
    auxData_->project.clear();
    ctx()->output.clearProject();
    if (cleanup) {
        for (auto x : irange(atoms_)) { atomState_.clear(x, AtomState::project_flag); }
    }
    return *this;
}

Potassco::TheoryData& LogicProgram::theoryData() { return theory_; }

void LogicProgram::pushFrozen(PrgAtom* atom, Val_t v) {
    if (not atom->frozen()) {
        frozen_.push_back(atom->id());
    }
    atom->markFrozen(v);
}

LogicProgram& LogicProgram::addExternal(Atom_t atomId, Potassco::TruthValue value) {
    CHECK_NOT_FROZEN();
    if (PrgAtom* a = resize(atomId); a->numSupports() == 0 && (isNewAtom(a->id()) || a->frozen())) {
        if (value == Potassco::TruthValue::release) {
            // add fake edge - will be removed once we update the set of frozen atoms
            a->addSupport(PrgEdge::noEdge());
            value = Potassco::TruthValue::free;
        }
        pushFrozen(a, static_cast<Val_t>(value));
        auxData_->external.push_back(encodeExternal(a->id(), value));
    }
    return *this;
}

LogicProgram& LogicProgram::freeze(Atom_t atomId, Val_t value) {
    POTASSCO_ASSERT(value < value_weak_true);
    return addExternal(atomId, static_cast<Potassco::TruthValue>(value));
}

LogicProgram& LogicProgram::unfreeze(Atom_t atomId) { return addExternal(atomId, Potassco::TruthValue::release); }
void          LogicProgram::setMaxInputAtom(uint32_t n) {
    CHECK_NOT_FROZEN();
    resize(n++);
    POTASSCO_CHECK_PRE(n >= startAtom(), "invalid input range");
    input_.hi = n;
}
Atom_t LogicProgram::startAuxAtom() const { return validAtom(input_.hi) ? input_.hi : size32(atoms_); }
Atom_t LogicProgram::factAtom() const { return index_->fact; }
bool   LogicProgram::supportsSmodels(const char** errorOut) const {
    const char*  ignore;
    const char*& eOut = errorOut ? *errorOut : ignore;
    if (incData_ || not theory_.empty()) {
        eOut = incData_ ? "incremental" : "theory";
        return false;
    }
    if (not auxData_->dom.empty()) {
        eOut = "heuristic";
        return false;
    }
    if (not auxData_->acyc.empty()) {
        eOut = "edge";
        return false;
    }
    if (not assume_.empty()) {
        eOut = "assumption";
        return false;
    }
    if (not auxData_->project.empty()) {
        eOut = "projection";
        return false;
    }
    if (termOutput_ || auxData_->hasLitOutput(*ctx())) {
        eOut = "general output";
        return false;
    }
    return true;
}

bool LogicProgram::isExternal(Atom_t aId) const {
    if (not aId || not validAtom(aId)) {
        return false;
    }
    PrgAtom* a = getRootAtom(aId);
    return a->frozen() && (a->numSupports() == 0 || frozen());
}
bool LogicProgram::isFact(Atom_t a) const { return validAtom(a) && (getAtom(a)->isFact() || getRootAtom(a)->isFact()); }
bool LogicProgram::isNew(Atom_t a) const { return isNewAtom(a) && validAtom(a); }
bool LogicProgram::isOld(Atom_t a) const { return a < startAtom(); }
bool LogicProgram::isDefined(Atom_t aId) const {
    if (not validAtom(aId) || getAtom(aId)->removed()) {
        return false;
    }
    PrgAtom* a = getAtom(aId);
    return isFact(aId) || (a->relevant() && a->numSupports() && not isExternal(aId));
}
bool LogicProgram::inProgram(Atom_t id) const {
    if (PrgAtom* a = validAtom(id) ? getAtom(id) : nullptr; a) {
        return a->relevant() && (a->numSupports() || a->frozen() || isOld(id));
    }
    return false;
}
LogicProgram& LogicProgram::addAssumption(Potassco::LitSpan lits) {
    assume_.insert(assume_.end(), lits.begin(), lits.end());
    return *this;
}
LogicProgram& LogicProgram::removeAssumption() {
    assume_.clear();
    return *this;
}

LogicProgram& LogicProgram::addAcycEdge(uint32_t n1, uint32_t n2, Id_t condId) {
    CHECK_NOT_FROZEN();
    if (condId != false_id) {
        AcycArc arc = {condId, {n1, n2}};
        auxData_->acyc.push_back(arc);
    }
    upStat(RK(acyc), 1);
    return *this;
}

LogicProgram& LogicProgram::addDomHeuristic(Atom_t atom, DomModType t, int bias, unsigned prio) {
    return addDomHeuristic(atom, t, bias, prio, {});
}
LogicProgram& LogicProgram::addDomHeuristic(Atom_t atom, DomModType type, int bias, unsigned prio, Id_t cond) {
    static_assert(sizeof(DomRule) == sizeof(uint32_t[3]), "Invalid DomRule size");
    CHECK_NOT_FROZEN();
    if (cond != false_id) {
        auxData_->dom.push_back(DomRule());
        DomRule& x = auxData_->dom.back();
        x.atom     = atom;
        x.type     = +type;
        x.cond     = cond;
        x.bias     = Clasp::saturate_cast<int16_t>(bias);
        x.prio     = Clasp::saturate_cast<uint16_t>(prio);
    }
    upStat(RK(heuristic), 1);
    return *this;
}
LogicProgram& LogicProgram::addRule(const Rule& rule) {
    CHECK_NOT_FROZEN();
    if (SRule meta; simplifyRule(rule, rule_, meta)) {
        Rule sRule = rule_.rule();
        upStat(sRule.ht);
        if (handleNatively(sRule)) { // and can be handled natively
            addRule(sRule, meta);
        }
        else {
            upStat(sRule.bt);
            if (sRule.head.size() <= 1 && transformNoAux(sRule)) {
                // rule transformation does not require aux atoms and can be done immediately
                int oId  = statsId_;
                statsId_ = 1;
                RuleTransform tm(*this);
                upStat(sRule.bt, -1);
                upStat(rule.ht, -1);
                tm.transform(sRule, RuleTransform::strategy_no_aux);
                statsId_ = oId;
            }
            else {
                // make sure we have all head atoms
                for (auto head : sRule.head) { resize(head); }
                extended_.push_back(new RuleBuilder(rule_));
            }
        }
    }
    if (statsId_ == 0) {
        // Assume all (new) heads are initially in the "upper" closure.
        for (auto h : rule.head) {
            if (isOld(h)) {
                continue;
            }
            if (validAtom(h)) {
                getAtom(h)->setInUpper(true);
            }
            else {
                auxData_->skippedHeads.insert(h);
            }
        }
    }
    rule_.clear();
    return *this;
}

LogicProgram& LogicProgram::addRule(Potassco::RuleBuilder& rb) {
    LogicProgramAdapter prg(*this);
    rb.end(&prg);
    return *this;
}
LogicProgram& LogicProgram::addMinimize(Weight_t prio, WeightLitSpan lits) {
    CHECK_NOT_FROZEN();
    auto it = std::ranges::lower_bound(minimize_, prio, std::less{}, [](const auto* rb) { return rb->bound(); });
    if (it == minimize_.end() || (*it)->bound() != prio) {
        upStat(RuleStats::minimize);
        it = minimize_.insert(it, new RuleBuilder());
        (*it)->startMinimize(prio);
    }
    for (const auto& lit : lits) {
        (*it)->addGoal(lit);
        // Touch all atoms in minimize statement -> these are input atoms even if they never occur in a head.
        resize(Potassco::atom(lit));
    }
    return *this;
}
LogicProgram& LogicProgram::removeMinimize() {
    std::ranges::for_each(minimize_, DeleteObject());
    discardVec(minimize_);
    ctx()->removeMinimize();
    return *this;
}
#undef CHECK_NOT_FROZEN
/////////////////////////////////////////////////////////////////////////////////////////
// Query functions
/////////////////////////////////////////////////////////////////////////////////////////
Literal LogicProgram::getLiteral(Id_t id, MapLit m) const {
    Literal out = lit_false;
    if (Id_t nId = nodeId(id); isAtom(id) && validAtom(nId)) {
        out = getRootAtom(nId)->literal();
        if (m == MapLit::refined) {
            if (isSentinel(out) && incData_ && not incData_->steps.empty()) {
                auto v = isNewAtom(id)
                             ? incData_->steps.back().second
                             : std::ranges::lower_bound(incData_->steps, Incremental::StepTrue(nId, 0))->second;
                out    = Literal(v, out.sign());
            }
            else if (auto dom = getAtom(id)->domVar(); dom != 0 && dom != out.var()) {
                out = posLit(dom);
            }
        }
    }
    else if (isBody(id)) {
        POTASSCO_CHECK_PRE(validBody(nId), "Invalid condition");
        out = getBody(getEqBody(nId))->literal();
    }
    return out ^ signId(id);
}

LogicProgram::OutputState LogicProgram::getOutputState(Atom_t atom, MapLit mode) const {
    uint32_t res = out_none;
    while (validAtom(atom)) {
        if (atomState_.isSet(atom, AtomState::shown_flag)) {
            res |= out_shown;
        }
        if (atomState_.isSet(atom, AtomState::project_flag)) {
            res |= out_projected;
        }
        auto next = mode == MapLit::raw ? atom : getRootId(atom);
        if (next == atom) {
            break;
        }
        atom = next;
        mode = MapLit::raw;
    }
    return static_cast<OutputState>(res);
}

void LogicProgram::doGetAssumptions(LitVec& out) const {
    for (auto v : frozen_) {
        if (Literal lit = getRootAtom(v)->assumption(); lit != lit_true) {
            out.push_back(lit);
        }
    }
    for (auto v : assume_) { out.push_back(getLiteral(Asp::id(v))); }
}
bool LogicProgram::translateCore(LitView solverCore, Potassco::LitVec& prgLits) const {
    uint32_t marked = 0;
    prgLits.clear();
    for (auto lit : solverCore) {
        if (not ctx()->validVar(lit.var())) {
            break;
        }
        ctx()->mark(lit);
        ++marked;
    }
    if (marked == solverCore.size()) {
        for (auto it = frozen_.begin(), end = frozen_.end(); it != end && marked; ++it) {
            PrgAtom* atom = getRootAtom(*it);
            Literal  lit  = atom->assumption();
            if (lit == lit_true || not ctx()->marked(lit)) {
                continue;
            }
            prgLits.push_back(atom->literal() == lit ? Potassco::lit(*it) : Potassco::neg(*it));
            ctx()->unmark(lit);
            --marked;
        }
        for (auto it = assume_.begin(), end = assume_.end(); it != end && marked; ++it) {
            Literal lit = getLiteral(Asp::id(*it));
            if (not ctx()->marked(lit)) {
                continue;
            }
            prgLits.push_back(*it);
            ctx()->unmark(lit);
            --marked;
        }
    }
    for (auto lit : solverCore) {
        if (ctx()->validVar(lit.var())) {
            ctx()->unmark(lit.var());
        }
    }
    return prgLits.size() == solverCore.size();
}
/////////////////////////////////////////////////////////////////////////////////////////
// Program definition - private
/////////////////////////////////////////////////////////////////////////////////////////
void LogicProgram::addRule(const Rule& r, const SRule& meta) {
    if (r.head.size() <= 1 && r.ht == HeadType::disjunctive) {
        if (r.head.empty()) {
            addIntegrity(r, meta);
            return;
        }
        if (r.normal() && r.cond.empty()) {
            addFact(r.head[0]);
            return;
        }
    }
    // only a non-false body can define atoms
    if (PrgBody* b = getBodyFor(r, meta); b->value() != value_false) {
        bool const     disjunctive = r.head.size() > 1 && r.ht == HeadType::disjunctive;
        const EdgeType t           = r.ht == HeadType::disjunctive ? PrgEdge::normal : PrgEdge::choice;
        uint32_t       headHash    = 0;
        bool           ignoreScc   = opts_.noSCC || b->size() == 0;
        for (auto h : r.head) {
            PrgAtom* a = resize(h);
            CHECK_MODULAR(isNewAtom(h) || a->frozen() || a->value() == value_false, h);
            if (not disjunctive) {
                // Note: b->heads may now contain duplicates. They are removed in PrgBody::simplifyHeads.
                b->addHead(a, t);
                if (ignoreScc) {
                    a->setScc(PrgNode::scc_triv);
                }
            }
            else {
                headHash += hashLit(posLit(h));
                atomState_.addToHead(h);
            }
        }
        if (disjunctive) {
            PrgDisj* d = getDisjFor(r.head, headHash);
            b->addHead(d, t);
        }
    }
}
void LogicProgram::addFact(Atom_t atomId) {
    PrgAtom* a = resize(atomId);
    CHECK_MODULAR(isNewAtom(atomId) || a->frozen() || a->value() == value_false, atomId);
    if (atomId != a->id() || a->isFact()) {
        return;
    }
    a->setScc(PrgNode::scc_triv);
    a->setFact(true);
    if (not index_->fact) {
        index_->fact = atomId;
    }
    if (a->frozen() || not a->deps().empty()) {
        PrgBody* tb = getTrueBody();
        tb->addHead(a, PrgEdge::normal);
        assignValue(a, value_true, PrgEdge::newEdge(*tb, PrgEdge::normal));
        return;
    }
    // Simplify and remove atom from the program
    if (not a->assignValue(value_true)) {
        setConflict();
    }
    a->setEq(0);
    a->propagateValue(*this, false);
    if (index_->eqTrue == nullptr) {
        a->clearSupports();
        a->setInUpper(false);
        a->clearLiteral(true);
        a->setFact(true);
        index_->eqTrue = std::exchange(a, nullptr);
    }
    atoms_[atomId] = index_->eqTrue;
    delete a;
}
void LogicProgram::addIntegrity(const Rule& r, const SRule& meta) {
    if (r.sum() || r.cond.size() != 1 || meta.bid != var_max) {
        PrgBody* body = getBodyFor(r, meta);
        if (not body->assignValue(value_false) || not body->propagateValue(*this, true)) {
            setConflict();
        }
    }
    else {
        PrgAtom* a = resize(Potassco::atom(r.cond[0]));
        auto     v = r.cond[0] > 0 ? value_false : value_weak_true;
        assignValue(a, v, PrgEdge::noEdge());
    }
}
bool LogicProgram::assignValue(PrgAtom* a, Val_t v, PrgEdge reason) {
    if (a->eq()) {
        a = getRootAtom(a->id());
    }
    auto old = a->value();
    if (old == value_weak_true && v != value_weak_true) {
        old = value_free;
    }
    if (not a->assignValue(v)) {
        setConflict();
        return false;
    }
    if (old == value_free) {
        propQ_.push_back(a->id());
    }
    if (v == value_true && reason.isBody() && reason.isNormal() && getBody(reason.node())->bound() == 0) {
        a->setScc(PrgNode::scc_triv);
        a->setFact(true);
    }
    return true;
}
bool LogicProgram::assignValue(PrgHead* h, Val_t v, PrgEdge reason) {
    return not h->isAtom() || assignValue(node_cast<PrgAtom>(h), v, reason);
}

bool LogicProgram::handleNatively(const Rule& r) const {
    auto mode = opts_.erMode;
    if (mode == mode_native || (r.normal() && r.ht == HeadType::disjunctive)) {
        return true;
    }
    switch (mode) {
        case mode_transform        : return false;
        case mode_transform_choice : return r.ht != HeadType::choice;
        case mode_transform_card   : return r.bt != BodyType::count;
        case mode_transform_weight : return r.normal();
        case mode_transform_dynamic: return r.normal() || transformNoAux(r) == false;
        default:
            POTASSCO_ASSERT(mode == mode_transform_integ || mode == mode_transform_scc || mode == mode_transform_nhcf,
                            "unhandled extended rule mode");
            return true;
    }
}

bool LogicProgram::transformNoAux(const Rule& r) {
    return r.ht == HeadType::disjunctive && r.sum() &&
           (r.agg.bound == 1 ||
            (r.agg.lits.size() <= 6 && choose(size32(r.agg.lits), static_cast<unsigned>(r.agg.bound)) <= 15));
}

void LogicProgram::transformExtended() {
    uint32_t      a = numAtoms();
    RuleTransform tm(*this);
    for (const auto* rb : extended_) {
        Rule r = rb->rule();
        upStat(r.ht, -1);
        upStat(r.bt, -1);
        if (r.normal() || (r.ht == HeadType::disjunctive && r.head.size() < 2)) {
            tm.transform(r);
        }
        else {
            using Potassco::Lit_t;
            Atom_t aux   = newAtom();
            Lit_t  auxB  = Potassco::lit(aux);
            Rule   rAux1 = r; // aux :- body
            rAux1.ht     = HeadType::disjunctive;
            rAux1.head   = Potassco::toSpan(aux);
            Rule rAux2   = Rule::normal(r.ht, r.head, Potassco::toSpan(auxB)); // head :- auxB
            if (handleNatively(rAux1)) {
                addRule(rAux1);
            }
            else {
                auto st = transformNoAux(rAux1) ? RuleTransform::strategy_no_aux : RuleTransform::strategy_default;
                tm.transform(rAux1, st);
            }
            if (handleNatively(rAux2)) {
                addRule(rAux2);
            }
            else {
                tm.transform(rAux2);
            }
        }
        delete rb;
    }
    extended_.clear();
    incTrAux(numAtoms() - a);
}

void LogicProgram::transformIntegrity(uint32_t nAtoms, uint32_t maxAux) {
    if (stats.bodies[1][to_underlying(BodyType::count)] == 0) {
        return;
    }
    // find all constraint rules that are integrity constraints
    BodyList integrity;
    for (auto* b : bodies_) {
        if (b->relevant() && b->type() == BodyType::count && b->value() == value_false) {
            integrity.push_back(b);
        }
    }
    if (not integrity.empty() && (integrity.size() == 1 || (ratio(nAtoms, size32(bodies_)) > 0.5 &&
                                                            ratio(integrity.size(), size32(bodies_)) < 0.01))) {
        auto          aux = size32(atoms_);
        RuleTransform tr(*this);
        RuleBuilder   temp;
        // transform integrity constraints
        for (auto* b : integrity) {
            auto est = static_cast<uint32_t>(b->bound() * (b->sumW() - b->bound()));
            if (est > maxAux) {
                // reached limit on aux atoms - stop transformation
                break;
            }
            if (b->toData(*this, temp) && temp.bodyType() != BodyType::normal) {
                maxAux -= est;
                // transform rule
                setFrozen(false);
                upStat(HeadType::disjunctive, -1);
                upStat(BodyType::count, -1);
                tr.transform(Rule::sum(HeadType::disjunctive, {}, temp.sum()));
                setFrozen(true);
                // propagate integrity condition to new rules
                propagate(true);
                b->markRemoved();
            }
            temp.clear();
        }
        // create vars for new atoms/bodies
        for (auto* a : atoms(aux)) {
            for (auto s : a->supports()) {
                PrgBody* nb = bodies_[s.node()];
                POTASSCO_ASSERT(nb->value() != value_false);
                nb->assignVar(*this);
            }
            a->assignVar(*this, a->support());
        }
        incTrAux(size32(atoms_) - aux);
    }
}

void LogicProgram::prepareExternals() {
    if (auxData_->external.empty()) {
        return;
    }
    VarVec& external = auxData_->external;
    auto    j        = external.begin();
    for (auto ext : external) {
        Atom_t         id   = getRootId(decodeExternal(ext).first);
        const PrgAtom* atom = getAtom(id);
        if (not atomState_.inHead(id) && not atom->support()) {
            auto value = atom->numSupports() == 0 ? static_cast<Potassco::TruthValue>(atom->freezeValue())
                                                  : Potassco::TruthValue::release;
            atomState_.addToHead(id);
            *j++ = encodeExternal(id, value);
        }
    }
    external.erase(j, external.end());
    atomState_.clearRule(external, [](unsigned ext) { return decodeExternal(ext).first; });
}
void LogicProgram::updateFrozenAtoms() {
    if (frozen_.empty()) {
        return;
    }
    PrgBody* support = nullptr;
    auto     j       = frozen_.begin();
    for (auto f : frozen_) {
        Id_t     id = getRootId(f);
        PrgAtom* a  = getAtom(id);
        POTASSCO_ASSERT(a->frozen());
        a->resetId(id, false);
        if (a->numSupports() == 0) {
            POTASSCO_ASSERT(a->relevant());
            POTASSCO_CHECK_PRE(id < startAuxAtom(), "frozen atom shall be an input atom");
            if (not support) {
                support = getTrueBody();
            }
            a->setScc(PrgNode::scc_triv);
            support->addHead(a, PrgEdge::gamma_choice);
            *j++ = id; // still frozen
        }
        else {
            a->clearFrozen();
            if (not a->support()) {
                // remove fake edge added in unfreeze()
                a->removeSupport(PrgEdge::noEdge());
            }
            if (isOld(id) && incData_) {
                // add to unfreeze so that we can later perform completion
                incData_->unfreeze.push_back(id);
            }
        }
    }
    frozen_.erase(j, frozen_.end());
}

void LogicProgram::prepareProgram(bool checkSccs) {
    POTASSCO_ASSERT(not frozen());
    prepareExternals();
    // Given that freezeTheory() might introduce otherwise
    // unused atoms, it must be called before we fix the
    // number of input atoms. It must also be called before resetting
    // the initial "upper" closure so that we can correctly classify
    // theory atoms.
    freezeTheory();
    // Prepare for preprocessing by resetting our "upper" closure.
    for (auto* atom : newAtoms()) { atom->setInUpper(false); }
    uint32_t nAtoms  = (input_.hi = std::min(input_.hi, endAtom()));
    stats.auxAtoms  += endAtom() - nAtoms;
    for (auto i : irange(RuleStats::numKeys())) { stats.rules[1][i] += stats.rules[0][i]; }
    for (auto i : irange(BodyStats::numKeys())) { stats.bodies[1][i] += stats.bodies[0][i]; }
    statsId_ = 1;
    transformExtended();
    updateFrozenAtoms();
    PrgAtom* suppAtom = nullptr;
    if (opts_.suppMod) {
        VarVec h;
        suppAtom = getAtom(newAtom());
        h.assign(1, suppAtom->id());
        addRule(HeadType::choice, h, {});
        auto body = Potassco::lit(suppAtom->id());
        h.clear();
        for (Atom_t v : irange(startAtom(), suppAtom->id())) {
            if (atoms_[v]->numSupports()) {
                h.push_back(v);
            }
        }
        addRule(HeadType::choice, h, Potassco::toSpan(body));
    }
    setFrozen(true);
    Preprocessor p;
    if (hasConflict() || not propagate(true) ||
        not p.preprocess(*this, opts_.iters != 0 ? Preprocessor::full_eq : Preprocessor::no_eq, opts_.iters,
                         opts_.dfOrder != 0)) {
        setConflict();
        return;
    }
    if (suppAtom && (not assignValue(suppAtom, value_false, PrgEdge::noEdge()) || not propagate(true))) {
        setConflict();
        return;
    }
    if (opts_.erMode == mode_transform_integ || opts_.erMode == mode_transform_dynamic) {
        nAtoms -= startAtom();
        transformIntegrity(nAtoms, std::min(15000u, nAtoms * 2));
    }
    addMinimize();
    uint32_t sccs = 0;
    if (checkSccs) {
        uint32_t   startScc = incData_ ? incData_->startScc : 0;
        SccChecker c(*this, auxData_->scc, startScc);
        sccs       = c.sccs();
        stats.sccs = (sccs - startScc);
        if (incData_) {
            incData_->startScc = c.sccs();
        }
        if (not disjunctions_.empty() || (opts_.erMode == mode_transform_scc && sccs)) {
            // reset node ids changed by scc checking
            for (auto i : irange(bodies_)) {
                if (auto* b = getBody(i); b->relevant()) {
                    b->resetId(i, true);
                }
            }
            for (auto i : irange(atoms_)) {
                if (auto* a = getAtom(i); a->relevant()) {
                    a->resetId(i, true);
                }
            }
        }
    }
    else {
        stats.sccs = PrgNode::scc_not_set;
    }
    finalizeDisjunctions(p, sccs);
    prepareComponents();
    prepareOutputTable();
    freezeAssumptions();
    if (incData_ && index_->distTrue) {
        for (auto end = startAuxAtom(); auto a : irange(startAtom(), end)) {
            if (isSentinel(getRootAtom(a)->literal())) {
                Incremental::StepTrue t(end - 1, 0);
                if (not incData_->steps.empty()) {
                    t.second = ctx()->addVar(VarType::atom, 0);
                }
                incData_->steps.push_back(t);
                break;
            }
        }
    }
    theory_.filter([this](const Potassco::TheoryAtom& a) {
        Atom_t id = a.atom();
        if (getLiteral(id) != lit_false && getRootAtom(id)->value() != value_false) {
            ctx()->setFrozen(getLiteral(id).var(), true);
            return false;
        }
        PrgAtom* at = getRootAtom(id);
        return not at->frozen();
    });
    stats.atoms = size32(atoms_) - startAtom();
    index_->body.clear();
    index_->disj.clear();
}
void LogicProgram::freezeTheory() {
    const IdSet& skippedHeads = auxData_->skippedHeads;
    for (const auto* a : theory_.currAtoms()) {
        if (isFact(a->atom()) || isOld(a->atom())) {
            continue;
        }
        PrgAtom* atom    = resize(a->atom());
        bool     inUpper = atom->inUpper() || skippedHeads.contains(a->atom());
        if (not atom->frozen() && atom->numSupports() == 0 && atom->relevant() && not inUpper) {
            pushFrozen(atom, value_free);
        }
    }
}

POTASSCO_WARNING_PUSH()
POTASSCO_WARNING_IGNORE_GNU("-Wnon-virtual-dtor") // Base class dtor is protected and therefore non-virtual is safe.
struct LogicProgram::DlpTr final : RuleTransform::ProgramAdapter {
    DlpTr(LogicProgram* x, EdgeType et) : self(x), type(et), scc(PrgNode::scc_triv) {}
    Atom_t newAtom() override {
        Atom_t   x = self->newAtom();
        PrgAtom* a = self->getAtom(x);
        a->setScc(scc);
        a->setSeen(true);
        atoms.push_back(x);
        if (isScc(scc)) {
            self->auxData_->scc.push_back(a);
        }
        return x;
    }
    void addRule(const Rule& r) override {
        SRule meta;
        if (not self->simplifyRule(r, rule, meta)) {
            return;
        }
        bool     gamma = type == PrgEdge::gamma;
        Rule     rs    = rule.rule();
        PrgAtom* a     = self->getAtom(rs.head[0]);
        PrgBody* body  = self->assignBodyFor(rs, meta, type, gamma);
        if (body->value() != value_false && not body->hasHead(a, PrgEdge::normal)) {
            body->addHead(a, type);
            self->stats.gammas += static_cast<uint32_t>(gamma);
        }
    }
    void assignAuxAtoms() {
        self->incTrAux(size32(atoms));
        while (not atoms.empty()) {
            PrgAtom* ax = self->getAtom(atoms.back());
            atoms.pop_back();
            if (auto s = ax->support(); s) {
                ax->setInUpper(true);
                ax->assignVar(*self, s);
            }
            else {
                POTASSCO_ASSERT(ax->numSupports() == 0);
                self->assignValue(ax, value_false, s);
            }
        }
    }
    LogicProgram* self;
    EdgeType      type;
    uint32_t      scc;
    VarVec        atoms;
    RuleBuilder   rule;
};
POTASSCO_WARNING_POP()

// replace disjunctions with gamma (shifted) and delta (component-shifted) rules
void LogicProgram::finalizeDisjunctions(Preprocessor& p, uint32_t numSccs) {
    if (disjunctions_.empty()) {
        return;
    }
    VarVec   head;
    BodyList supports;
    index_->disj.clear();
    SccMap sccMap;
    sccMap.resize(numSccs, 0);
    enum SccFlag : uint32_t { seen_scc = 1u, is_scc_non_hcf = 128u };
    // replace disjunctions with shifted rules and non-hcf-disjunctions
    DisjList disj;
    disj.swap(disjunctions_);
    setFrozen(false);
    uint32_t shifted     = 0;
    stats.nonHcfs        = size32(nonHcfs_);
    Literal          bot = lit_false;
    Potassco::LitVec rb;
    VarVec           rh;
    DlpTr            tr(this, PrgEdge::gamma);

    // detach disjunctions
    for (auto id : irange(disj)) {
        PrgDisj* d = disj[id];
        d->resetId(id, true); // id changed during scc checking
        d->disconnect(*this); // remove from atoms and bodies but keep state
    }

    // replace disjunctions with shifted rules or new component-shifted disjunction
    for (auto* d : disj) {
        Literal dx = d->inUpper() ? d->literal() : bot;
        head.clear();
        supports.clear();
        for (auto aId : d->atoms()) {
            PrgAtom* at = getAtom(aId);
            if (dx == bot) {
                continue;
            }
            if (at->eq()) {
                at = getAtom(aId = getRootId(aId));
            }
            if (at->isFact()) {
                dx = bot;
                continue;
            }
            if (at->inUpper()) {
                head.push_back(aId);
                if (at->inScc()) {
                    sccMap[at->scc()] = seen_scc;
                }
            }
        }
        for (auto edge : d->supports()) {
            if (PrgBody* b = getBody(edge.node()); b->relevant() && b->value() != value_false) {
                supports.push_back(b);
            }
        }
        d->destroy();
        // create a shortcut for supports to avoid duplications during shifting
        Literal supportLit = dx != bot ? getEqAtomLit(dx, supports, p, sccMap) : dx;
        // create shifted rules and split disjunctions into non-hcf components
        RuleTransform shifter(tr);
        for (auto h : head) {
            uint32_t scc = getAtom(h)->scc();
            if (not isScc(scc) || (sccMap[scc] & seen_scc) != 0) {
                if (isScc(scc)) {
                    sccMap[scc] &= ~seen_scc;
                }
                else {
                    scc = UINT32_MAX;
                }
                rh.assign(1, h);
                rb.clear();
                if (supportLit.var() != 0) {
                    rb.push_back(toInt(supportLit));
                }
                else if (supportLit.sign()) {
                    continue;
                }
                for (auto o : head) {
                    if (o != h) {
                        if (getAtom(o)->scc() == scc) {
                            rh.push_back(o);
                        }
                        else {
                            rb.push_back(Potassco::neg(o));
                        }
                    }
                }
                SRule meta;
                if (not simplifyRule(Rule::normal(HeadType::disjunctive, rh, rb), rule_, meta)) {
                    continue;
                }
                Rule     sr   = rule_.rule();
                PrgBody* body = assignBodyFor(sr, meta, PrgEdge::normal, true);
                if (body->value() != value_false && sr.head.size() == 1) {
                    ++shifted;
                    body->addHead(getAtom(sr.head[0]), PrgEdge::normal);
                }
                else if (body->value() != value_false && sr.head.size() > 1) {
                    PrgDisj* x = getDisjFor(sr.head, 0);
                    body->addHead(x, PrgEdge::normal);
                    x->assignVar(*this, x->support());
                    x->setInUpper(true);
                    x->setSeen(true);
                    if ((sccMap[scc] & is_scc_non_hcf) == 0) {
                        sccMap[scc] |= is_scc_non_hcf;
                        nonHcfs_.add(scc);
                    }
                    if (not options().noGamma) {
                        if (sr.cond.size() >= 4) {
                            // make body eq to a new aux atom
                            tr.scc        = body->scc(*this) == scc ? scc : PrgNode::scc_triv;
                            Atom_t eqAtom = tr.newAtom();
                            body->addHead(getAtom(eqAtom), PrgEdge::normal);
                            rb.assign(1, Potassco::lit(eqAtom));
                            sr.cond = rb;
                            tr.assignAuxAtoms();
                            tr.scc = PrgNode::scc_triv;
                        }
                        shifter.transform(sr, RuleTransform::strategy_no_aux);
                    }
                    else {
                        // only add support edge
                        for (auto a : x->atoms()) { body->addHead(getAtom(a), PrgEdge::gamma_choice); }
                    }
                }
            }
        }
    }
    POTASSCO_ASSERT(tr.atoms.empty());
    if (not disjunctions_.empty() && nonHcfs_.config == nullptr) {
        nonHcfs_.config = ctx()->configuration()->config("tester");
    }
    upStat(RK(normal), static_cast<int>(shifted));
    stats.nonHcfs = size32(nonHcfs_) - stats.nonHcfs;
    rh.clear();
    setFrozen(true);
}
// optionally transform extended rules in sccs
void LogicProgram::prepareComponents() {
    int trRec = opts_.erMode == mode_transform_scc;
    // HACK: force transformation of extended rules in non-hcf components
    // REMOVE this once minimality check supports aggregates
    if (not disjunctions_.empty() && trRec != 1) {
        trRec = 2;
    }
    if (trRec != 0) {
        DlpTr         tr(this, PrgEdge::normal);
        RuleTransform trans(tr);
        RuleBuilder   temp;
        setFrozen(false);
        EdgeVec heads;
        // find recursive aggregates
        for (auto bId : irange(numBodies())) { // NOTE: the set of bodies might change
            PrgBody* body = getBody(bId);
            if (body->type() == BodyType::normal || not body->hasVar() || body->value() == value_false) {
                continue;
            } // not aggregate or not relevant
            tr.scc = body->scc(*this);
            if (not isScc(tr.scc) || (trRec == 2 && not nonHcfs_.find(tr.scc))) {
                continue;
            } // not recursive
            // transform all rules a :- B, where scc(a) == scc(B):
            heads.clear();
            for (auto h : body->heads()) {
                POTASSCO_ASSERT(h.isAtom());
                if (getAtom(h.node())->scc() == tr.scc) {
                    heads.push_back(h);
                }
            }
            if (heads.empty()) {
                continue;
            }
            temp.clear();
            if (not body->toData(*this, temp) || temp.bodyType() == BodyType::normal) {
                body->simplify(*this, true, nullptr);
                continue;
            }
            HeadType ht = not isChoice(heads[0].type()) ? HeadType::disjunctive : HeadType::choice;
            Atom_t   h  = heads[0].node();
            if (heads.size() > 1) { // more than one head, make body eq to some new aux atom
                ht = HeadType::disjunctive;
                h  = tr.newAtom();
            }
            trans.transform(Rule::sum(ht, Potassco::toSpan(h), temp.sum()));
            temp.clearBody().addGoal(Potassco::lit(h));
            for (auto head : heads) {
                body->removeHead(getAtom(head.node()), head.type());
                if (h != head.node()) {
                    ht      = not isChoice(head.type()) ? HeadType::disjunctive : HeadType::choice;
                    h       = head.node();
                    tr.type = ht == HeadType::disjunctive ? PrgEdge::normal : PrgEdge::choice;
                    tr.addRule(Rule::normal(ht, Potassco::toSpan(h), temp.body()));
                }
            }
        }
        tr.assignAuxAtoms();
        setFrozen(true);
    }
}

void LogicProgram::prepareOutputTable() {
    auto& out      = ctx()->output;
    auto  filter   = false;
    auxData_->show = std::min(auxData_->show, out.numPreds());
    for (auto idx = auxData_->show; const auto& [name, _, atom] : auxData_->showAtoms(*ctx())) {
        auto lit = getLiteral(atom);
        filter   = filter || out.filter(name.view()) || lit == lit_false;
        out.setPredicateCondition(idx++, lit);
        if (atom < startAuxAtom()) {
            atomState_.set(atom, AtomState::shown_flag);
            ctx()->setOutput(lit.var(), true);
        }
    }
    if (filter) {
        out.filter(auxData_->show);
    }
    if (opts_.sortAtom != sort_no) {
        // sort predicates in requested order
        out.sortPredicates(
            [mode = static_cast<AtomSorting>(opts_.sortAtom)](const OutputTable::PredType& lhs,
                                                              const OutputTable::PredType& rhs) {
                using enum Potassco::AtomCompare;
                Potassco::AtomCompare cmp;
                switch (mode) {
                    default:
                        if ((isSentinel(lhs.cond) || isSentinel(rhs.cond)) && lhs.cond != rhs.cond) {
                            return lhs.cond < rhs.cond; // legacy: facts first
                        }
                        return lhs.user < rhs.user || (lhs.user == rhs.user && lhs.name < rhs.name);
                    case sort_name        : cmp = cmp_default; break;
                    case sort_natural     : cmp = cmp_natural; break;
                    case sort_arity       : cmp = cmp_arity; break;
                    case sort_arity_natual: cmp = cmp_arity | cmp_natural; break;
                }
                if (auto r = Potassco::cmpAtom(lhs.name.view(), rhs.name.view(), cmp); not std::is_eq(r)) {
                    return std::is_lt(r);
                }
                return lhs.user != rhs.user ? lhs.user < rhs.user : lhs.cond < rhs.cond;
            },
            auxData_->show);
    }
    if (termOutput_) {
        termOutput_->prepare(static_cast<AtomSorting>(opts_.sortAtom));
    }
    std::ranges::sort(auxData_->project);
    for (auto p : auxData_->project) {
        out.addProject(getLiteral(p));
        atomState_.set(p, AtomState::project_flag);
    }
}

// Make assumptions/externals exempt from sat-preprocessing
void LogicProgram::freezeAssumptions() {
    for (auto a : frozen_) { ctx()->setFrozen(getRootAtom(a)->var(), true); }
    for (auto l : assume_) { ctx()->setFrozen(getLiteral(Asp::id(l)).var(), true); }
}

// add (completion) nogoods
bool LogicProgram::addConstraints() {
    ClauseCreator gc(ctx()->master());
    if (options().iters == 0) {
        gc.addDefaultFlags(ClauseCreator::clause_force_simplify);
    }
    ctx()->startAddConstraints();
    // handle initial conflict, if any
    if (not ctx()->ok() || not ctx()->addUnary(getTrueAtom()->trueLit())) {
        return false;
    }
    if (incData_ && not incData_->steps.empty() && not ctx()->addUnary(posLit(incData_->steps.back().second))) {
        return false;
    }
    if (options().noGamma && not disjunctions_.empty()) {
        // add "rule" nogoods for disjunctions
        for (const auto* disjunction : disjunctions_) {
            gc.start().add(~disjunction->literal());
            for (auto a : disjunction->atoms()) { gc.add(getAtom(a)->literal()); }
            if (not gc.end()) {
                return false;
            }
        }
    }
    // add bodies from this step
    for (auto* body : bodies_) {
        if (not toConstraint(body, *this, gc)) {
            return false;
        }
    }
    // add atoms thawed in this step
    for (auto u : unfreeze()) {
        if (not toConstraint(getAtom(u), *this, gc)) {
            return false;
        }
    }
    // add atoms from this step
    const bool freezeAll = incData_ != nullptr;
    const auto hiAtom    = startAuxAtom();
    for (auto id = startAtom(); auto* a : atoms(id)) {
        if (not toConstraint(a, *this, gc)) {
            return false;
        }
        if (id < hiAtom && a->hasVar()) {
            if (freezeAll) {
                ctx()->setFrozen(a->var(), true);
            }
            ctx()->setInput(a->var(), true);
        }
        ++id;
    }
    if (not auxData_->scc.empty()) {
        if (not ctx()->sccGraph) {
            ctx()->sccGraph = std::make_unique<PrgDepGraph>(static_cast<PrgDepGraph::NonHcfMapType>(opts_.oldMap == 0));
        }
        uint32_t oldNodes = ctx()->sccGraph->nodes();
        ctx()->sccGraph->addSccs(*this, auxData_->scc, nonHcfs_);
        stats.ufsNodes = ctx()->sccGraph->nodes() - oldNodes;
    }
    return true;
}
void LogicProgram::addDomRules() {
    if (auxData_->dom.empty()) {
        return;
    }
    VarVec        domVec;
    EqVec         eqVec;
    DomRules&     doms = auxData_->dom;
    Solver const& s    = *ctx()->master();
    // mark any previous domain atoms so that we can decide
    // whether existing variables can be used for the atoms in doms
    if (incData_) {
        erase_if(incData_->doms, [&](Var_t v) {
            if (s.value(v) == value_free) {
                ctx()->mark(posLit(v));
                return false;
            }
            return true;
        });
        domVec.swap(incData_->doms);
    }
    auto j = doms.begin();
    for (auto& dr : doms) {
        auto cond = getLiteral(dr.cond);
        auto slit = getLiteral(dr.atom);
        if (s.isFalse(cond) || s.value(slit.var()) != value_free) {
            continue;
        }
        if (s.isTrue(cond)) {
            dr.cond = 0;
            cond    = lit_true;
        }
        *j++      = dr;
        auto svar = slit.var();
        // check if atom is the root for its var
        if (auto v = getAtom(dr.atom)->domVar(); v != svar) {
            if (v != 0) {
                assert(ctx()->marked(posLit(svar)));
                // var(it->atom) is used but we already created a new var for it->atom
                slit = posLit(svar = v);
            }
            else if (not ctx()->marked(posLit(svar))) {
                // var(it->atom) is not yet used - make it->atom its root
                ctx()->mark(posLit(svar));
                domVec.push_back(svar);
                getAtom(dr.atom)->setDomVar(svar);
            }
            else {
                // var(it->atom) is used - introduce new aux var and make it eq to lit(atom)
                Eq n = {ctx()->addVar(VarType::atom, VarInfo::flag_nant), slit};
                eqVec.push_back(n);
                svar = n.var;
                slit = posLit(svar);
                getAtom(dr.atom)->setDomVar(svar);
            }
        }
        auto x = dr;
        if (slit.sign()) {
            switch (static_cast<DomModType>(dr.type)) {
                default                : break;
                case DomModType::sign  : x.bias = static_cast<int16_t>(-std::max<int>(dr.bias, INT16_MIN + 1)); break;
                case DomModType::true_ : x.type = +DomModType::false_; break;
                case DomModType::false_: x.type = +DomModType::true_; break;
            }
        }
        ctx()->heuristic.add(svar, static_cast<DomModType>(x.type), x.bias, x.prio, cond);
    }
    if (j != doms.end()) {
        upStat(RK(heuristic), -static_cast<int>(doms.end() - j));
        doms.erase(j, doms.end());
    }
    // cleanup var flags
    for (auto v : domVec) { ctx()->unmark(v); }
    if (incData_) {
        incData_->doms.swap(domVec);
    }
    if (not eqVec.empty()) {
        ctx()->startAddConstraints();
        for (const auto& [var, lit] : eqVec) {
            // var == lit
            ctx()->addBinary(~lit, posLit(var));
            ctx()->addBinary(lit, negLit(var));
        }
    }
}

void LogicProgram::addAcycConstraint() {
    AcycRules& acyc = auxData_->acyc;
    if (acyc.empty()) {
        return;
    }
    SharedContext& ctx = *this->ctx();
    const Solver&  s   = *ctx.master();
    if (ctx.extGraph) {
        ctx.extGraph->update();
    }
    else {
        ctx.extGraph = std::make_unique<ExtDepGraph>();
    }
    auto* graph = ctx.extGraph.get();
    for (auto x : acyc) {
        if (auto lit = getLiteral(x.cond); not s.isFalse(lit)) {
            graph->addEdge(lit, x.node[0], x.node[1]);
        }
        else {
            upStat(RK(acyc), -1);
        }
    }
    if (graph->finalize(ctx) == 0) {
        ctx.extGraph = nullptr;
    }
}
#undef CHECK_MODULAR
/////////////////////////////////////////////////////////////////////////////////////////
// misc/helper functions
/////////////////////////////////////////////////////////////////////////////////////////
PrgAtom* LogicProgram::resize(Atom_t atomId) {
    POTASSCO_CHECK(atomId < body_id, EOVERFLOW, "Atom out of bounds");
    while (size32(atoms_) <= atomId) { newAtom(); }
    return getRootAtom(atomId);
}
void LogicProgram::setConflict() {
    POTASSCO_ASSERT(started());
    getTrueAtom()->setLiteral(lit_false);
}
bool LogicProgram::propagate(bool backprop) {
    POTASSCO_ASSERT(frozen());
    bool oldB      = opts_.backprop != 0;
    opts_.backprop = backprop;
    for (auto qFront = 0u; qFront < size32(propQ_);) {
        PrgAtom* a = getAtom(propQ_[qFront++]);
        if (not a->relevant()) {
            continue;
        }
        if (not a->propagateValue(*this, backprop)) {
            setConflict();
            return false;
        }
        if (a->hasVar() && a->id() < startAtom()) {
            if (auto lit = a->trueLit(); ctx()->master()->isFalse(lit) || not ctx()->addUnary(lit)) {
                setConflict();
                return false;
            }
        }
    }
    opts_.backprop = oldB;
    propQ_.clear();
    return true;
}
Val_t LogicProgram::litVal(const PrgAtom* a, bool pos) {
    if (a->value() != value_free || not a->relevant()) {
        if (bool vSign = a->value() == value_false || not a->relevant(); vSign == pos) {
            return value_false;
        }
        return a->value() != value_weak_true ? value_true : value_free;
    }
    return value_free;
}

// Simplifies the given normal rule H :- l1, ..., ln
//  - removes true and duplicate literals from the body: {T,a,b,a} -> {a, b}.
//  - checks for contradictions and false literals in the body: {'a', not 'a'} -> F
//  - checks for satisfied head and removes false atoms from head
// POST: if true, out contains the simplified normal rule.
bool LogicProgram::simplifyNormal(HeadType ht, Potassco::AtomSpan head, Potassco::LitSpan body, RuleBuilder& out,
                                  SRule& meta) {
    out.clear();
    out.startBody();
    meta    = SRule();
    bool ok = true;
    for (auto lit : body) {
        auto* a = resize(Potassco::atom(lit));
        auto  p = Literal(a->id(), lit < 0); // replace any eq atoms
        auto  v = litVal(a, not p.sign());
        if (v == value_false || atomState_.inBody(~p)) {
            ok = false;
            break;
        }
        if (v != value_true && not atomState_.inBody(p)) {
            atomState_.addToBody(p);
            out.addGoal(toInt(p));
            meta.pos  += not p.sign();
            meta.hash += hashLit(p);
        }
    }
    meta.bid = ok ? findBody(meta.hash, size32(out.body())) : var_max;
    ok       = ok && pushHead(ht, head, 0, out);
    atomState_.clearRule(out.body());
    return ok;
}

// Simplifies the given sum rule: H :- lb { l1 = w1 ... ln = wn }.
//  - removes assigned literals and updates lb accordingly
//  - removes literals li with weight wi = 0
//  - reduces weights wi > bound() to bound
//  - merges duplicate literals in sum, i.e. lb {a=w1, b=w2, a=w3} -> lb {a=w1+w3, b=w2}
//  - checks for contradiction, i.e. sum contains both p and not p and both are needed
//  - replaces sum with count if all weights are equal
//  - replaces sum with normal body if all literals must be true for the sum to be satisfied
// POST: if true out contains the simplified rule.
bool LogicProgram::simplifySum(HeadType ht, Potassco::AtomSpan head, const Potassco::Sum& body, RuleBuilder& out,
                               SRule& meta) {
    meta           = SRule();
    Weight_t bound = body.bound, maxW = 1, minW = weight_max, sumW = 0, dirty = 0;
    out.clear();
    out.startSum(bound);
    for (const auto& [lit, weight] : body.lits) {
        if (bound <= 0) {
            break;
        }
        if (weight <= 0) {
            POTASSCO_CHECK(weight == 0, EDOM, "Non-negative weight expected!");
            continue; // skip irrelevant lits
        }
        auto* a = resize(Potassco::atom(lit));
        auto  p = Literal(a->id(), lit < 0); // replace any eq atoms
        if (auto v = litVal(a, not p.sign()); v == value_true) {
            bound -= weight;
        }
        else if (v != value_false) {
            POTASSCO_CHECK((weight_max - sumW) >= weight, EOVERFLOW, "Integer overflow!");
            sumW   += weight;
            auto w  = weight;
            if (not atomState_.inBody(p)) {
                atomState_.addToBody(p);
                out.addGoal(toInt(p), weight);
                meta.pos  += not p.sign();
                meta.hash += hashLit(p);
            }
            else { // Merge duplicate lits
                auto* pos = out.findSumLit(toInt(p));
                POTASSCO_ASSERT(pos);
                w = (pos->weight += weight);
                ++dirty; // minW might have changed
            }
            if (w > maxW) {
                maxW = w;
            }
            if (w < minW) {
                minW = w;
            }
            dirty += static_cast<Weight_t>(atomState_.inBody(~p));
        }
    }
    Weight_t sumR = sumW;
    if (bound > 0 && (dirty || maxW > bound)) {
        sumR = 0, minW = weight_max;
        for (auto& [lit, weight] : out.sumLits()) {
            if (weight > bound) {
                sumW   -= (weight - bound);
                weight  = (maxW = bound);
            }
            if (weight < minW) {
                minW = weight;
            }
            if (not atomState_.inBody(~toLit(lit))) {
                sumR += weight;
            }
            else if (lit > 0) { // body contains lit and ~lit: we can achieve at most max(weight(lit), weight(~lit))
                auto cpw  = out.findSumLit(Potassco::neg(lit))->weight;
                sumR     += std::max(weight, std::min(cpw, bound));
            }
        }
    }
    out.setBound(bound);
    if (bound <= 0 || sumR < bound) {
        atomState_.clearRule(out.sumLits());
        return bound <= 0 && simplifyNormal(ht, head, {}, out, meta);
    }
    if ((sumW - minW) < bound) {
        out.weaken(BodyType::normal);
        meta.bid = findBody(meta.hash, size32(out.body()));
        bool ok  = pushHead(ht, head, 0, out);
        atomState_.clearRule(out.body());
        return ok;
    }
    if (minW == maxW) {
        out.weaken(BodyType::count, maxW != 1);
        bound = out.bound();
    }
    meta.bid = findBody(meta.hash, out.bodyType(), out.bound(), out.sumLits());
    bool ok  = pushHead(ht, head, sumW - out.bound(), out);
    atomState_.clearRule(out.sumLits());
    return ok;
}

// Pushes the given rule head to the body given in out.
// Pre: Body literals are marked and lits is != 0 if body is a sum.
bool LogicProgram::pushHead(HeadType ht, Potassco::AtomSpan head, Weight_t slack, RuleBuilder& out) {
    bool sat = false, sum = out.bodyType() == BodyType::sum;
    out.start(ht);
    for (auto h : head) {
        auto fixed = validAtom(h) ? getAtom(h)->fixed() : value_free;
        if (not atomState_.isSet(h, AtomState::rule_mask) && fixed == value_free) {
            out.addHead(h);
            atomState_.addToHead(h);
        }
        else if (not atomState_.isSet(h, AtomState::head_flag) &&
                 fixed != value_false) { // h occurs in B+ and/or B- or is true
            auto wp = static_cast<Weight_t>(atomState_.inBody(posLit(h))),
                 wn = static_cast<Weight_t>(atomState_.inBody(negLit(h)));
            if (wp && sum) {
                wp = out.findSumLit(Potassco::lit(h))->weight;
            }
            if (wn && sum) {
                wn = out.findSumLit(Potassco::neg(h))->weight;
            }
            if (fixed == value_true || wp > slack) {
                sat = true;
            }
            else if (wn <= slack) {
                out.addHead(h);
                atomState_.addToHead(h);
            }
        }
    }
    atomState_.clearRule(out.head());
    return not sat || (ht == HeadType::choice && not out.head().empty());
}

bool LogicProgram::simplifyRule(const Rule& r, Potassco::RuleBuilder& out, SRule& meta) {
    return r.normal() ? simplifyNormal(r.ht, r.head, r.cond, out, meta) : simplifySum(r.ht, r.head, r.agg, out, meta);
}
// create new atom aux representing supports, i.e.
// aux == S1 v ... v Sn
Literal LogicProgram::getEqAtomLit(Literal lit, const BodyList& supports, Preprocessor& p, const SccMap& sccMap) {
    if (supports.empty() || lit == lit_false) {
        return lit_false;
    }
    if (supports.size() == 1 && supports[0]->size() < 2 && supports.back()->literal() == lit) {
        return supports[0]->size() == 0 ? lit_true : supports[0]->goal(0);
    }
    if (p.getRootAtom(lit) != var_max && opts_.noSCC) {
        // Use the existing root atom only if scc checking is disabled.
        // Otherwise, we would have to recheck SCCs from that atom again because
        // adding a new edge could create a new or change an existing SCC.
        return posLit(p.getRootAtom(lit));
    }
    incTrAux(1);
    Atom_t   auxV = newAtom();
    PrgAtom* aux  = getAtom(auxV);
    aux->setLiteral(lit);
    aux->setSeen(true);
    if (p.getRootAtom(lit) == var_max) {
        p.setRootAtom(aux->literal(), auxV);
    }
    uint32_t scc = PrgNode::scc_triv;
    for (auto* b : supports) {
        if (b->relevant() && b->value() != value_false) {
            for (uint32_t g = 0; not isScc(scc) && g != b->size() && not b->goal(g).sign(); ++g) {
                uint32_t aScc = getAtom(b->goal(g).var())->scc();
                if (isScc(aScc) && (sccMap[aScc] & 1u)) {
                    scc = aScc;
                }
            }
            b->addHead(aux, PrgEdge::normal);
            if (b->value() != value_free && not assignValue(aux, b->value(), PrgEdge::newEdge(*b, PrgEdge::normal))) {
                break;
            }
            aux->setInUpper(true);
        }
    }
    if (not aux->inUpper()) {
        aux->setValue(value_false);
        return lit_false;
    }
    if (isScc(scc)) {
        aux->setScc(scc);
        auxData_->scc.push_back(aux);
    }
    return posLit(auxV);
}

PrgBody* LogicProgram::getBodyFor(const Rule& r, const SRule& meta, bool addDeps) {
    if (meta.bid < size32(bodies_)) {
        return getBody(meta.bid);
    }
    // no corresponding body exists, create a new object
    PrgBody* b = PrgBody::create(*this, numBodies(), r, meta.pos, addDeps);
    index_->body.emplace(meta.hash, b->id());
    bodies_.push_back(b);
    if (b->isSupported()) {
        initialSupp_.push_back(b->id());
    }
    upStat(r.bt);
    return b;
}
PrgBody* LogicProgram::getTrueBody() {
    if (uint32_t id = findBody(0, 0); validBody(id)) {
        return getBody(id);
    }
    return getBodyFor(Rule::normal(HeadType::choice, {}, {}), SRule());
}
PrgBody* LogicProgram::assignBodyFor(const Rule& r, const SRule& meta, EdgeType depEdge, bool simpStrong) {
    PrgBody* b = getBodyFor(r, meta, depEdge != PrgEdge::gamma);
    if (not b->hasVar() && not b->seen()) {
        uint32_t eqId;
        b->markDirty();
        b->simplify(*this, simpStrong, &eqId);
        if (eqId != b->id()) {
            POTASSCO_ASSERT(b->id() == size32(bodies_) - 1);
            removeBody(b, meta.hash);
            bodies_.pop_back();
            if (depEdge != PrgEdge::gamma) {
                for (auto g : b->goals()) { getAtom(g.var())->removeDep(b->id(), not g.sign()); }
            }
            b->destroy();
            b = bodies_[eqId];
        }
    }
    b->setSeen(true);
    b->assignVar(*this);
    return b;
}

bool LogicProgram::equalLits(const PrgBody& b, WeightLitSpan lits) {
    auto last = lits.begin(), e = lits.end();
    for (auto i : irange(b.size())) {
        Potassco::WeightLit wl = {toInt(b.goal(i)), b.weight(i)};
        if (auto x = wl <=> *last; std::is_eq(x)) {
            continue;
        }
        else if (std::is_lt(x)) {
            last = std::lower_bound(lits.begin(), e = last, wl);
        }
        else {
            last = std::lower_bound(last + 1, e = lits.end(), wl);
        }
        if (last == e || *last != wl) {
            return false;
        }
    }
    return true;
}

// Pre: all literals in the body are marked.
uint32_t LogicProgram::findBody(uint32_t hash, BodyType type, uint32_t size, Weight_t bound,
                                Potassco::WeightLit* sum) const {
    POTASSCO_ASSERT(type != BodyType::normal || static_cast<uint32_t>(bound) == size);
    bool sorted = false;
    for (auto [it, end] = index_->body.equal_range(hash); it != end; ++it) {
        const PrgBody& b = *getBody(it->second);
        if (not checkBody(b, type, size, bound) || not atomState_.inBody(b.goals())) {
            continue;
        }
        if (not b.hasWeights()) {
            return b.id();
        }
        if (sum) {
            if (not sorted) {
                std::sort(sum, sum + size);
                sorted = true;
            }
            if (equalLits(b, {sum, size})) {
                return b.id();
            }
        }
    }
    return var_max;
}

uint32_t LogicProgram::findEqBody(const PrgBody* b, uint32_t hash) {
    uint32_t eqId = var_max, n = 0, r = 0;
    for (auto [it, end] = index_->body.equal_range(hash); it != end && eqId == var_max; ++it) {
        const PrgBody& rhs = *getBody(it->second);
        if (not checkBody(rhs, b->type(), b->size(), b->bound())) {
            continue;
        }
        if (b->size() == 0) {
            eqId = rhs.id();
        }
        else if (b->size() == 1) {
            eqId = b->goal(0) == rhs.goal(0) && b->weight(0) == rhs.weight(0) ? rhs.id() : var_max;
        }
        else {
            if (++n == 1) {
                atomState_.addToBody(b->goals());
            }
            if (not atomState_.inBody(rhs.goals())) {
                continue;
            }
            if (not b->hasWeights()) {
                eqId = rhs.id();
            }
            else {
                if (n == 1 || r == 0) {
                    rule_.clear();
                    if (not b->toData(*this, rule_) || rule_.bodyType() != BodyType::sum) {
                        rule_.clear();
                        continue;
                    }
                    r = 1;
                    std::ranges::sort(rule_.sumLits());
                }
                if (equalLits(rhs, rule_.sumLits())) {
                    eqId = rhs.id();
                }
            }
        }
    }
    if (n) {
        rule_.clear();
        atomState_.clearBody(b->goals());
    }
    return eqId;
}

PrgDisj* LogicProgram::getDisjFor(Potassco::AtomSpan head, uint32_t headHash) {
    PrgDisj* d = nullptr;
    if (headHash) {
        for (auto [it, end] = index_->disj.equal_range(headHash); it != end; ++it) {
            PrgDisj& o = *disjunctions_[it->second];
            if (o.relevant() && o.size() == head.size() && atomState_.allMarked(o.atoms(), AtomState::head_flag)) {
                POTASSCO_ASSERT(o.id() == it->second);
                d = &o;
                break;
            }
        }
        atomState_.clearRule(head);
    }
    if (not d) {
        // no corresponding disjunction exists, create a new object
        // and link it to all atoms
        ++stats.disjunctions[statsId_];
        d = PrgDisj::create(size32(disjunctions_), head);
        disjunctions_.push_back(d);
        PrgEdge edge = PrgEdge::newEdge(*d, PrgEdge::choice);
        for (auto h : head) { getAtom(h)->addSupport(edge); }
        if (headHash) {
            index_->disj.emplace(headHash, d->id());
        }
    }
    return d;
}

// body has changed - update index
uint32_t LogicProgram::update(PrgBody* body, uint32_t oldHash, uint32_t newHash) {
    uint32_t id = removeBody(body, oldHash);
    if (body->relevant()) {
        uint32_t eqId = findEqBody(body, newHash);
        if (eqId == var_max) {
            // No equivalent body found.
            // Add a new entry to the index.
            index_->body.emplace(newHash, id);
        }
        return eqId;
    }
    return var_max;
}

// body b has changed - remove old entry from body node index
uint32_t LogicProgram::removeBody(const PrgBody* b, uint32_t oldHash) {
    uint32_t id = b->id();
    for (auto [it, end] = index_->body.equal_range(oldHash); it != end; ++it) {
        if (bodies_[it->second] == b) {
            id = it->second;
            index_->body.erase(it);
            break;
        }
    }
    return id;
}

PrgAtom* LogicProgram::mergeEqAtoms(PrgAtom* a, Id_t rootId) {
    PrgAtom* root = getAtom(rootId = getRootId(rootId));
    auto     mv   = getMergeValue(a, root);
    POTASSCO_ASSERT(not a->eq() && not root->eq() && not a->frozen());
    if (auto scc = a->scc(); a->hasScc()) {
        root->setScc(scc);
    }
    if (mv != a->value() && not assignValue(a, mv, PrgEdge::noEdge())) {
        return nullptr;
    }
    if (mv != root->value() && not assignValue(root, mv, PrgEdge::noEdge())) {
        return nullptr;
    }
    a->setEq(rootId);
    incEqs(VarType::atom);
    return root;
}

// returns whether posSize(root) <= posSize(body)
bool LogicProgram::positiveLoopSafe(const PrgBody* body, const PrgBody* root) {
    uint32_t i = 0, end = std::min(body->size(), root->size());
    while (i != end && body->goal(i).sign() == root->goal(i).sign()) { ++i; }
    return i == root->size() || root->goal(i).sign();
}

PrgBody* LogicProgram::mergeEqBodies(PrgBody* b, Id_t rootId, bool hashEq, bool atomsAssigned) {
    PrgBody* root = getBody(rootId = getEqNode(bodies_, rootId));
    bool     bp   = options().backprop != 0;
    if (b == root) {
        return root;
    }
    POTASSCO_ASSERT(not b->eq() && not root->eq() && (hashEq || b->literal() == root->literal()));
    if (not b->simplifyHeads(*this, atomsAssigned) ||
        (b->value() != root->value() &&
         (not mergeValue(b, root) || not root->propagateValue(*this, bp) || not b->propagateValue(*this, bp)))) {
        setConflict();
        return nullptr;
    }
    if (hashEq || positiveLoopSafe(b, root)) {
        b->setLiteral(root->literal());
        if (not root->mergeHeads(*this, *b, atomsAssigned, not hashEq)) {
            setConflict();
            return nullptr;
        }
        incEqs(VarType::body);
        b->setEq(rootId);
        return root;
    }
    return b;
}

const char* LogicProgram::findName(Atom_t x) const {
    for (const auto& pred : ctx()->output.pred_range()) {
        if (pred.user == x) {
            return pred.name.c_str();
        }
    }
    return "";
}
VarVec& LogicProgram::getSupportedBodies(bool sorted) {
    if (sorted) {
        std::ranges::sort(initialSupp_, [&](Id_t lhs, Id_t rhs) {
            const auto* bLhs = bodies_[lhs];
            const auto* bRhs = bodies_[rhs];
            auto        r    = bLhs->size() <=> bRhs->size();
            if (not std::is_eq(r)) {
                return std::is_lt(r);
            }
            r = bLhs->type() <=> bRhs->type();
            return std::is_lt(r) || (std::is_eq(r) && lhs < rhs);
        });
    }
    return initialSupp_;
}

Atom_t LogicProgram::falseAtom() {
    for (auto i : irange(1u, size32(atoms_))) {
        if (atoms_[i]->fixed() == value_false) {
            return i;
        }
    }
    bool s = frozen();
    setFrozen(false);
    auto aFalse = newAtom();
    assignValue(getAtom(aFalse), value_false, PrgEdge::noEdge());
    setFrozen(s);
    return aFalse;
}

bool LogicProgram::extractCondition(Id_t cId, Potassco::LitVec& cond) const {
    cond.clear();
    if (cId == false_id || (frozen() && getLiteral(cId) == lit_false)) {
        return false;
    }
    if (not cId || isAtom(cId)) {
        cond.assign(cId != 0, Potassco::lit(cId));
        return true;
    }
    Id_t bId = nodeId(cId);
    POTASSCO_ASSERT(validBody(bId), "Invalid literal");
    const PrgBody* body = getBody(getEqBody(bId));
    cond.reserve(body->size());
    for (auto g : body->goals()) { cond.push_back(toInt(g)); }
    return true;
}
Val_t isConsequence(const LogicProgram& prg, Potassco::Lit_t atomLit, const Model& model) {
    auto outState = prg.getOutputState(Potassco::atom(atomLit));
    auto expState =
        prg.ctx()->output.projectMode() == ProjectMode::project ? LogicProgram::out_projected : LogicProgram::out_shown;
    if (not Potassco::test(outState, expState)) {
        return value_false;
    }
    return model.isCons(solverLiteral(prg, atomLit));
}
#undef RT
/////////////////////////////////////////////////////////////////////////////////////////
// class LogicProgramAdapter
/////////////////////////////////////////////////////////////////////////////////////////
LogicProgramAdapter::LogicProgramAdapter(LogicProgram& prg, const Options& opts)
    : lp_(&prg)
    , opts_(opts)
    , inc_(false) {}
LogicProgramAdapter::LogicProgramAdapter(LogicProgram& prg) : LogicProgramAdapter(prg, {}) {}
void LogicProgramAdapter::initProgram(bool inc) { inc_ = inc; }
void LogicProgramAdapter::beginStep() {
    if (inc_ || lp_->frozen()) {
        lp_->updateProgram();
    }
}
void LogicProgramAdapter::endStep() {
    if (inc_ && opts_.removeMinimize && lp_->ctx()->hasMinimize()) {
        lp_->ctx()->removeMinimize();
    }
}
void LogicProgramAdapter::rule(HeadType ht, Potassco::AtomSpan head, Potassco::LitSpan body) {
    lp_->addRule(ht, head, body);
}
void LogicProgramAdapter::rule(HeadType ht, Potassco::AtomSpan head, Potassco::Weight_t bound, WeightLitSpan body) {
    lp_->addRule(ht, head, bound, body);
}
void LogicProgramAdapter::minimize(Potassco::Weight_t prio, WeightLitSpan lits) { lp_->addMinimize(prio, lits); }
void LogicProgramAdapter::project(Potassco::AtomSpan atoms) { lp_->addProject(atoms); }
void LogicProgramAdapter::outputAtom(Atom_t atom, std::string_view n) { lp_->addAtomOutput(atom, n); }
void LogicProgramAdapter::outputTerm(Id_t term, std::string_view n) { lp_->newShowTerm(n, term); }
void LogicProgramAdapter::output(Id_t term, Potassco::LitSpan cond) { lp_->addShowTerm(term, cond); }
void LogicProgramAdapter::external(Atom_t a, Potassco::TruthValue v) { lp_->addExternal(a, v); }
void LogicProgramAdapter::assume(Potassco::LitSpan lits) { lp_->addAssumption(lits); }
void LogicProgramAdapter::heuristic(Atom_t a, Potassco::DomModifier t, int bias, unsigned prio,
                                    Potassco::LitSpan cond) {
    lp_->addDomHeuristic(a, t, bias, prio, cond);
}
void LogicProgramAdapter::acycEdge(int s, int t, Potassco::LitSpan cond) {
    lp_->addAcycEdge(static_cast<uint32_t>(s), static_cast<uint32_t>(t), cond);
}
void LogicProgramAdapter::theoryTerm(Id_t termId, int number) { lp_->theoryData().addTerm(termId, number); }
void LogicProgramAdapter::theoryTerm(Id_t termId, std::string_view name) { lp_->theoryData().addTerm(termId, name); }
void LogicProgramAdapter::theoryTerm(Id_t termId, int cId, Potassco::IdSpan args) {
    if (cId >= 0) {
        lp_->theoryData().addTerm(termId, static_cast<Id_t>(cId), args);
    }
    else {
        lp_->theoryData().addTerm(termId, static_cast<Potassco::TupleType>(cId), args);
    }
}
void LogicProgramAdapter::theoryElement(Id_t elementId, Potassco::IdSpan terms, Potassco::LitSpan cond) {
    lp_->theoryData().addElement(elementId, terms, lp_->newCondition(cond));
}
void LogicProgramAdapter::theoryAtom(Id_t atomOrZero, Id_t termId, Potassco::IdSpan elements) {
    lp_->theoryData().addAtom(atomOrZero, termId, elements);
}
void LogicProgramAdapter::theoryAtom(Id_t atomOrZero, Id_t termId, Potassco::IdSpan elements, Id_t op, Id_t rhs) {
    lp_->theoryData().addAtom(atomOrZero, termId, elements, op, rhs);
}
} // namespace Clasp::Asp
