Theory Reference: Bags

Finite Bags

cvc5 supports the theory of finite bags using the following sorts, constants, functions and predicates.

For the C++ API examples in the table below, we assume that we have created a cvc5::Solver solver object.

SMTLIB language

C++ API

Logic String

(set-logic ALL)

solver.setLogic("ALL");

Sort

(Bag <Sort>)

solver.mkBagSort(cvc5::Sort elementSort);

Constants

(declare-const X (Bag String))

Sort s = solver.mkBagSort(solver.getStringSort());

Term X = solver.mkConst(s, "X");

Union disjoint

(bag.union_disjoint X Y)

Term Y = solver.mkConst(s, "Y");

Term t = solver.mkTerm(Kind::BAG_UNION_DISJOINT, {X, Y});

Union max

(bag.union_max X Y)

Term Y = solver.mkConst(s, "Y");

Term t = solver.mkTerm(Kind::BAG_UNION_MAX, {X, Y});

Intersection min

(bag.inter_min X Y)

Term t = solver.mkTerm(Kind::BAG_INTER_MIN, {X, Y});

Difference subtract

(bag.difference_subtract X Y)

Term t = solver.mkTerm(Kind::BAG_DIFFERENCE_SUBTRACT, {X, Y});

Duplicate elimination

(bag.setof X)

Term t = solver.mkTerm(Kind::BAG_SETOF, {X});

Membership

(bag.member x X)

Term x = solver.mkConst(solver.getStringSort(), "x");

Term t = solver.mkTerm(Kind::BAG_MEMBER, {x, X});

Subbag

(bag.subbag X Y)

Term t = solver.mkTerm(Kind::BAG_SUBBAG, {X, Y});

Emptybag

(as bag.empty (Bag Int)) | Term t = solver.mkEmptyBag(s);

Make bag

(bag "a" 3)

Term t = solver.mkTerm(Kind::BAG_MAKE,

{solver.mkString("a"), solver.mkInteger(1)});

Semantics

A bag (or a multiset) \(m\) can be defined as a function from the domain of its elements to the set of natural numbers (i.e., \(m : D \rightarrow \mathbb{N}\) ), where \(m(e)\) represents the multiplicity of element \(e\) in the bag \(m\) .

The semantics of supported bag operators is given in the table below.

Bag operator

cvc5 operator

Semantics

union disjoint \(m_1 \uplus m_2\)

bag.union_disjoint

\(\forall e. \; (m_1 \uplus m_2)(e) = m_1(e) + m_2 (e)\)

union max \(m_1 \cup m_2\)

bag.union_max

\(\forall e. \; (m_1 \cup m_2)(e) = max(m_1(e), m_2 (e))\)

intersection \(m_1 \cap m_2\)

bag.inter_min

\(\forall e. \; (m_1 \cap m_2)(e) = min(m_1(e), m_2 (e))\)

difference subtract \(m_1 \setminus m_2\)

bag.difference_subtract

\(\forall e. \; (m_1 \setminus m_2)(e) = max(m_1(e) - m_2 (e), 0)\)

difference remove \(m_1 \setminus\setminus m_2\)

bag.difference_remove

\(\forall e. \; (m_1 \setminus\setminus m_2)(e) = ite(m_2(e) = 0, m_1(e), 0)\)

setof \(\delta(m)\)

bag.setof

\(\forall e. \; (\delta(m))(e) = ite(1 \leq m(e), 1, 0)\)

subbag \(m_1 \subseteq m_2\)

bag.subbag

\(\forall e. \; m_1(e) \leq m_2(e)\)

equality \(m_1 = m_2\)

=

\(\forall e. \; m_1(e) = m_2(e)\)

membership \(e \in m\)

bag.member

\(m(e) \geq 1\)

Below is a more extensive example on how to use finite bags:

examples/api/cpp/bags.cpp

  1/******************************************************************************
  2 * This file is part of the cvc5 project.
  3 *
  4 * Copyright (c) 2009-2026 by the authors listed in the file AUTHORS
  5 * in the top-level source directory and their institutional affiliations.
  6 * All rights reserved.  See the file COPYING in the top-level source
  7 * directory for licensing information.
  8 * ****************************************************************************
  9 *
 10 * A simple demonstration of reasoning about bags.
 11 */
 12
 13#include <cvc5/cvc5.h>
 14
 15#include <iostream>
 16
 17using namespace std;
 18using namespace cvc5;
 19
 20int main()
 21{
 22  TermManager tm;
 23  Solver slv(tm);
 24  slv.setLogic("ALL");
 25  // Produce models
 26  slv.setOption("produce-models", "true");
 27  slv.setOption("incremental", "true");
 28
 29  Sort bag = tm.mkBagSort(tm.getStringSort());
 30  Term A = tm.mkConst(bag, "A");
 31  Term B = tm.mkConst(bag, "B");
 32  Term C = tm.mkConst(bag, "C");
 33  Term x = tm.mkConst(tm.getStringSort(), "x");
 34
 35  Term intersectionAC = tm.mkTerm(Kind::BAG_INTER_MIN, {A, C});
 36  Term intersectionBC = tm.mkTerm(Kind::BAG_INTER_MIN, {B, C});
 37
 38  // union disjoint does not distribute over intersection
 39  {
 40    Term unionDisjointAB = tm.mkTerm(Kind::BAG_UNION_DISJOINT, {A, B});
 41    Term lhs = tm.mkTerm(Kind::BAG_INTER_MIN, {unionDisjointAB, C});
 42    Term rhs =
 43        tm.mkTerm(Kind::BAG_UNION_DISJOINT, {intersectionAC, intersectionBC});
 44    Term guess = tm.mkTerm(Kind::EQUAL, {lhs, rhs});
 45    cout << "cvc5 reports: " << guess.notTerm() << " is "
 46         << slv.checkSatAssuming(guess.notTerm()) << "." << endl;
 47
 48    cout << A << ": " << slv.getValue(A) << endl;
 49    cout << B << ": " << slv.getValue(B) << endl;
 50    cout << C << ": " << slv.getValue(C) << endl;
 51    cout << lhs << ": " << slv.getValue(lhs) << endl;
 52    cout << rhs << ": " << slv.getValue(rhs) << endl;
 53  }
 54
 55  // union max distributes over intersection
 56  {
 57    Term unionMaxAB = tm.mkTerm(Kind::BAG_UNION_MAX, {A, B});
 58    Term lhs = tm.mkTerm(Kind::BAG_INTER_MIN, {unionMaxAB, C});
 59    Term rhs = tm.mkTerm(Kind::BAG_UNION_MAX, {intersectionAC, intersectionBC});
 60    Term theorem = tm.mkTerm(Kind::EQUAL, {lhs, rhs});
 61    cout << "cvc5 reports: " << theorem.notTerm() << " is "
 62         << slv.checkSatAssuming(theorem.notTerm()) << "." << endl;
 63  }
 64
 65  // Verify emptbag is a subbag of any bag
 66  {
 67    Term emptybag = tm.mkEmptyBag(bag);
 68    Term theorem = tm.mkTerm(Kind::BAG_SUBBAG, {emptybag, A});
 69
 70    cout << "cvc5 reports: " << theorem.notTerm() << " is "
 71         << slv.checkSatAssuming(theorem.notTerm()) << "." << endl;
 72  }
 73
 74  // find an element with multiplicity 4 in the disjoint union of
 75  // ; {|"a", "a", "b", "b", "b"|} and {|"b", "c", "c"|}
 76
 77  {
 78    Term one = tm.mkInteger(1);
 79    Term two = tm.mkInteger(2);
 80    Term three = tm.mkInteger(3);
 81    Term four = tm.mkInteger(4);
 82    Term a = tm.mkString("a");
 83    Term b = tm.mkString("b");
 84    Term c = tm.mkString("c");
 85
 86    Term bag_a_2 = tm.mkTerm(Kind::BAG_MAKE, {a, two});
 87    Term bag_b_3 = tm.mkTerm(Kind::BAG_MAKE, {b, three});
 88    Term bag_b_1 = tm.mkTerm(Kind::BAG_MAKE, {b, one});
 89    Term bag_c_2 = tm.mkTerm(Kind::BAG_MAKE, {c, two});
 90    Term bag_a_2_b_3 = tm.mkTerm(Kind::BAG_UNION_DISJOINT, {bag_a_2, bag_b_3});
 91    Term bag_b_1_c_2 = tm.mkTerm(Kind::BAG_UNION_DISJOINT, {bag_b_1, bag_c_2});
 92    Term union_disjoint =
 93        tm.mkTerm(Kind::BAG_UNION_DISJOINT, {bag_a_2_b_3, bag_b_1_c_2});
 94
 95    Term count_x = tm.mkTerm(Kind::BAG_COUNT, {x, union_disjoint});
 96    Term e = tm.mkTerm(Kind::EQUAL, {four, count_x});
 97    Result result = slv.checkSatAssuming(e);
 98
 99    cout << "cvc5 reports: " << e << " is " << result << "." << endl;
100    if (result.isSat())
101    {
102      cout << x << ": " << slv.getValue(x) << endl;
103    }
104  }
105}