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}