###############################################################################
# Top contributors (to current version):
#   Yoni Zohar, Aina Niemetz, Andrew Reynolds
#
# This file is part of the cvc5 project.
#
# Copyright (c) 2009-2025 by the authors listed in the file AUTHORS
# in the top-level source directory and their institutional affiliations.
# All rights reserved.  See the file COPYING in the top-level source
# directory for licensing information.
# #############################################################################
##

import pytest
import cvc5
from cvc5 import Kind, RoundingMode
from cvc5 import Sort, Term
from fractions import Fraction


@pytest.fixture
def tm():
    return cvc5.TermManager()
@pytest.fixture
def solver(tm):
    return cvc5.Solver(tm)


def test_eq(tm):
    uSort = tm.mkUninterpretedSort("u")
    x = tm.mkVar(uSort, "x")
    y = tm.mkVar(uSort, "y")
    assert x == x
    assert not x != x
    assert not x == y
    assert x != y


def test_get_id(tm):
    x = tm.mkVar(tm.getIntegerSort(), "x")
    x.getId()
    y = x
    assert x.getId() == y.getId()
    z = tm.mkVar(tm.getIntegerSort(), "z")
    assert x.getId() != z.getId()


def test_get_kind(tm):
    uSort = tm.mkUninterpretedSort("u")
    intSort = tm.getIntegerSort()
    boolSort = tm.getBooleanSort()
    funSort1 = tm.mkFunctionSort(uSort, intSort)
    funSort2 = tm.mkFunctionSort(intSort, boolSort)

    x = tm.mkVar(uSort, "x")
    x.getKind()
    y = tm.mkVar(uSort, "y")
    y.getKind()

    f = tm.mkVar(funSort1, "f")
    f.getKind()
    p = tm.mkVar(funSort2, "p")
    p.getKind()

    zero = tm.mkInteger(0)
    zero.getKind()

    f_x = tm.mkTerm(Kind.APPLY_UF, f, x)
    f_x.getKind()
    f_y = tm.mkTerm(Kind.APPLY_UF, f, y)
    f_y.getKind()
    sum = tm.mkTerm(Kind.ADD, f_x, f_y)
    sum.getKind()
    p_0 = tm.mkTerm(Kind.APPLY_UF, p, zero)
    p_0.getKind()
    p_f_y = tm.mkTerm(Kind.APPLY_UF, p, f_y)
    p_f_y.getKind()

    # Sequence kinds do not exist internally, test that the API properly
    # converts them back.
    seqSort = tm.mkSequenceSort(intSort)
    s = tm.mkConst(seqSort, "s")
    ss = tm.mkTerm(Kind.SEQ_CONCAT, s, s)
    assert ss.getKind() == Kind.SEQ_CONCAT


def test_get_sort(tm):
    bvSort = tm.mkBitVectorSort(8)
    intSort = tm.getIntegerSort()
    boolSort = tm.getBooleanSort()
    funSort1 = tm.mkFunctionSort(bvSort, intSort)
    funSort2 = tm.mkFunctionSort(intSort, boolSort)

    x = tm.mkVar(bvSort, "x")
    x.getSort()
    assert x.getSort() == bvSort
    y = tm.mkVar(bvSort, "y")
    y.getSort()
    assert y.getSort() == bvSort

    f = tm.mkVar(funSort1, "f")
    f.getSort()
    assert f.getSort() == funSort1
    p = tm.mkVar(funSort2, "p")
    p.getSort()
    assert p.getSort() == funSort2

    zero = tm.mkInteger(0)
    zero.getSort()
    assert zero.getSort() == intSort

    f_x = tm.mkTerm(Kind.APPLY_UF, f, x)
    f_x.getSort()
    assert f_x.getSort() == intSort
    f_y = tm.mkTerm(Kind.APPLY_UF, f, y)
    f_y.getSort()
    assert f_y.getSort() == intSort
    sum = tm.mkTerm(Kind.ADD, f_x, f_y)
    sum.getSort()
    assert sum.getSort() == intSort
    p_0 = tm.mkTerm(Kind.APPLY_UF, p, zero)
    p_0.getSort()
    assert p_0.getSort() == boolSort
    p_f_y = tm.mkTerm(Kind.APPLY_UF, p, f_y)
    p_f_y.getSort()
    assert p_f_y.getSort() == boolSort


def test_get_op(tm):
    intsort = tm.getIntegerSort()
    bvsort = tm.mkBitVectorSort(8)
    arrsort = tm.mkArraySort(bvsort, intsort)
    funsort = tm.mkFunctionSort(intsort, bvsort)

    x = tm.mkConst(intsort, "x")
    a = tm.mkConst(arrsort, "a")
    b = tm.mkConst(bvsort, "b")

    assert not x.hasOp()
    with pytest.raises(RuntimeError):
        x.getOp()

    ab = tm.mkTerm(Kind.SELECT, a, b)
    ext = tm.mkOp(Kind.BITVECTOR_EXTRACT, 4, 0)
    extb = tm.mkTerm(ext, b)

    assert ab.hasOp()
    assert not ab.getOp().isIndexed()
    # can compare directly to a Kind (will invoke constructor)
    assert extb.hasOp()
    assert extb.getOp().isIndexed()
    assert extb.getOp() == ext

    bit = tm.mkOp(Kind.BITVECTOR_BIT, 4)
    bitb = tm.mkTerm(bit, b)
    assert bitb.getKind() == Kind.BITVECTOR_BIT
    assert bitb.hasOp()
    assert bitb.getOp() == bit
    assert bitb.getOp().isIndexed()
    assert bit.getNumIndices() == 1
    assert bit[0] == tm.mkInteger(4)

    f = tm.mkConst(funsort, "f")
    fx = tm.mkTerm(Kind.APPLY_UF, f, x)

    assert not f.hasOp()
    with pytest.raises(RuntimeError):
        f.getOp()
    assert fx.hasOp()
    children = [c for c in fx]
    # testing rebuild from op and children
    assert fx == tm.mkTerm(fx.getOp(), *children)

    # Test Datatypes Ops
    sort = tm.mkParamSort("T")
    listDecl = tm.mkDatatypeDecl("paramlist", [sort])
    cons = tm.mkDatatypeConstructorDecl("cons")
    nil = tm.mkDatatypeConstructorDecl("nil")
    cons.addSelector("head", sort)
    cons.addSelectorSelf("tail")
    listDecl.addConstructor(cons)
    listDecl.addConstructor(nil)
    listSort = tm.mkDatatypeSort(listDecl)
    intListSort = listSort.instantiate([tm.getIntegerSort()])
    c = tm.mkConst(intListSort, "c")
    list1 = listSort.getDatatype()
    # list datatype constructor and selector operator terms
    consOpTerm = list1.getConstructor("cons").getTerm()
    nilOpTerm = list1.getConstructor("nil").getTerm()
    headOpTerm = list1["cons"].getSelector("head").getTerm()
    tailOpTerm = list1["cons"].getSelector("tail").getTerm()

    nilTerm = tm.mkTerm(Kind.APPLY_CONSTRUCTOR, nilOpTerm)
    consTerm = tm.mkTerm(Kind.APPLY_CONSTRUCTOR, consOpTerm,
                             tm.mkInteger(0), nilTerm)
    headTerm = tm.mkTerm(Kind.APPLY_SELECTOR, headOpTerm, consTerm)
    tailTerm = tm.mkTerm(Kind.APPLY_SELECTOR, tailOpTerm, consTerm)

    assert nilTerm.hasOp()
    assert consTerm.hasOp()
    assert headTerm.hasOp()
    assert tailTerm.hasOp()

    # Test rebuilding
    children = [c for c in headTerm]
    assert headTerm == tm.mkTerm(headTerm.getOp(), *children)


def test_has_get_symbol(tm):
    t = tm.mkBoolean(True)
    c = tm.mkConst(tm.getBooleanSort(), "|\\|")

    assert not t.hasSymbol()
    assert c.hasSymbol()

    with pytest.raises(RuntimeError):
        t.getSymbol()
    assert c.getSymbol() == "|\\|"


def test_is_null(tm):
    x = tm.mkVar(tm.mkBitVectorSort(4), "x")
    assert not x.isNull()


def test_not_term(tm):
    bvSort = tm.mkBitVectorSort(8)
    intSort = tm.getIntegerSort()
    boolSort = tm.getBooleanSort()
    funSort1 = tm.mkFunctionSort(bvSort, intSort)
    funSort2 = tm.mkFunctionSort(intSort, boolSort)

    b = tm.mkTrue()
    b.notTerm()
    x = tm.mkVar(tm.mkBitVectorSort(8), "x")
    with pytest.raises(RuntimeError):
        x.notTerm()
    f = tm.mkVar(funSort1, "f")
    with pytest.raises(RuntimeError):
        f.notTerm()
    p = tm.mkVar(funSort2, "p")
    with pytest.raises(RuntimeError):
        p.notTerm()
    zero = tm.mkInteger(0)
    with pytest.raises(RuntimeError):
        zero.notTerm()
    f_x = tm.mkTerm(Kind.APPLY_UF, f, x)
    with pytest.raises(RuntimeError):
        f_x.notTerm()
    sum = tm.mkTerm(Kind.ADD, f_x, f_x)
    with pytest.raises(RuntimeError):
        sum.notTerm()
    p_0 = tm.mkTerm(Kind.APPLY_UF, p, zero)
    p_0.notTerm()
    p_f_x = tm.mkTerm(Kind.APPLY_UF, p, f_x)
    p_f_x.notTerm()


def test_and_term(tm):
    bvSort = tm.mkBitVectorSort(8)
    intSort = tm.getIntegerSort()
    boolSort = tm.getBooleanSort()
    funSort1 = tm.mkFunctionSort(bvSort, intSort)
    funSort2 = tm.mkFunctionSort(intSort, boolSort)

    b = tm.mkTrue()
    b.andTerm(b)
    x = tm.mkVar(tm.mkBitVectorSort(8), "x")
    with pytest.raises(RuntimeError):
        x.andTerm(b)
    with pytest.raises(RuntimeError):
        x.andTerm(x)
    f = tm.mkVar(funSort1, "f")
    with pytest.raises(RuntimeError):
        f.andTerm(b)
    with pytest.raises(RuntimeError):
        f.andTerm(x)
    with pytest.raises(RuntimeError):
        f.andTerm(f)
    p = tm.mkVar(funSort2, "p")
    with pytest.raises(RuntimeError):
        p.andTerm(b)
    with pytest.raises(RuntimeError):
        p.andTerm(x)
    with pytest.raises(RuntimeError):
        p.andTerm(f)
    with pytest.raises(RuntimeError):
        p.andTerm(p)
    zero = tm.mkInteger(0)
    with pytest.raises(RuntimeError):
        zero.andTerm(b)
    with pytest.raises(RuntimeError):
        zero.andTerm(x)
    with pytest.raises(RuntimeError):
        zero.andTerm(f)
    with pytest.raises(RuntimeError):
        zero.andTerm(p)
    with pytest.raises(RuntimeError):
        zero.andTerm(zero)
    f_x = tm.mkTerm(Kind.APPLY_UF, f, x)
    with pytest.raises(RuntimeError):
        f_x.andTerm(b)
    with pytest.raises(RuntimeError):
        f_x.andTerm(x)
    with pytest.raises(RuntimeError):
        f_x.andTerm(f)
    with pytest.raises(RuntimeError):
        f_x.andTerm(p)
    with pytest.raises(RuntimeError):
        f_x.andTerm(zero)
    with pytest.raises(RuntimeError):
        f_x.andTerm(f_x)
    sum = tm.mkTerm(Kind.ADD, f_x, f_x)
    with pytest.raises(RuntimeError):
        sum.andTerm(b)
    with pytest.raises(RuntimeError):
        sum.andTerm(x)
    with pytest.raises(RuntimeError):
        sum.andTerm(f)
    with pytest.raises(RuntimeError):
        sum.andTerm(p)
    with pytest.raises(RuntimeError):
        sum.andTerm(zero)
    with pytest.raises(RuntimeError):
        sum.andTerm(f_x)
    with pytest.raises(RuntimeError):
        sum.andTerm(sum)
    p_0 = tm.mkTerm(Kind.APPLY_UF, p, zero)
    p_0.andTerm(b)
    with pytest.raises(RuntimeError):
        p_0.andTerm(x)
    with pytest.raises(RuntimeError):
        p_0.andTerm(f)
    with pytest.raises(RuntimeError):
        p_0.andTerm(p)
    with pytest.raises(RuntimeError):
        p_0.andTerm(zero)
    with pytest.raises(RuntimeError):
        p_0.andTerm(f_x)
    with pytest.raises(RuntimeError):
        p_0.andTerm(sum)
    p_0.andTerm(p_0)
    p_f_x = tm.mkTerm(Kind.APPLY_UF, p, f_x)
    p_f_x.andTerm(b)
    with pytest.raises(RuntimeError):
        p_f_x.andTerm(x)
    with pytest.raises(RuntimeError):
        p_f_x.andTerm(f)
    with pytest.raises(RuntimeError):
        p_f_x.andTerm(p)
    with pytest.raises(RuntimeError):
        p_f_x.andTerm(zero)
    with pytest.raises(RuntimeError):
        p_f_x.andTerm(f_x)
    with pytest.raises(RuntimeError):
        p_f_x.andTerm(sum)
    p_f_x.andTerm(p_0)
    p_f_x.andTerm(p_f_x)


def test_or_term(tm):
    bvSort = tm.mkBitVectorSort(8)
    intSort = tm.getIntegerSort()
    boolSort = tm.getBooleanSort()
    funSort1 = tm.mkFunctionSort(bvSort, intSort)
    funSort2 = tm.mkFunctionSort(intSort, boolSort)

    b = tm.mkTrue()
    b.orTerm(b)
    x = tm.mkVar(tm.mkBitVectorSort(8), "x")
    with pytest.raises(RuntimeError):
        x.orTerm(b)
    with pytest.raises(RuntimeError):
        x.orTerm(x)
    f = tm.mkVar(funSort1, "f")
    with pytest.raises(RuntimeError):
        f.orTerm(b)
    with pytest.raises(RuntimeError):
        f.orTerm(x)
    with pytest.raises(RuntimeError):
        f.orTerm(f)
    p = tm.mkVar(funSort2, "p")
    with pytest.raises(RuntimeError):
        p.orTerm(b)
    with pytest.raises(RuntimeError):
        p.orTerm(x)
    with pytest.raises(RuntimeError):
        p.orTerm(f)
    with pytest.raises(RuntimeError):
        p.orTerm(p)
    zero = tm.mkInteger(0)
    with pytest.raises(RuntimeError):
        zero.orTerm(b)
    with pytest.raises(RuntimeError):
        zero.orTerm(x)
    with pytest.raises(RuntimeError):
        zero.orTerm(f)
    with pytest.raises(RuntimeError):
        zero.orTerm(p)
    with pytest.raises(RuntimeError):
        zero.orTerm(zero)
    f_x = tm.mkTerm(Kind.APPLY_UF, f, x)
    with pytest.raises(RuntimeError):
        f_x.orTerm(b)
    with pytest.raises(RuntimeError):
        f_x.orTerm(x)
    with pytest.raises(RuntimeError):
        f_x.orTerm(f)
    with pytest.raises(RuntimeError):
        f_x.orTerm(p)
    with pytest.raises(RuntimeError):
        f_x.orTerm(zero)
    with pytest.raises(RuntimeError):
        f_x.orTerm(f_x)
    sum = tm.mkTerm(Kind.ADD, f_x, f_x)
    with pytest.raises(RuntimeError):
        sum.orTerm(b)
    with pytest.raises(RuntimeError):
        sum.orTerm(x)
    with pytest.raises(RuntimeError):
        sum.orTerm(f)
    with pytest.raises(RuntimeError):
        sum.orTerm(p)
    with pytest.raises(RuntimeError):
        sum.orTerm(zero)
    with pytest.raises(RuntimeError):
        sum.orTerm(f_x)
    with pytest.raises(RuntimeError):
        sum.orTerm(sum)
    p_0 = tm.mkTerm(Kind.APPLY_UF, p, zero)
    p_0.orTerm(b)
    with pytest.raises(RuntimeError):
        p_0.orTerm(x)
    with pytest.raises(RuntimeError):
        p_0.orTerm(f)
    with pytest.raises(RuntimeError):
        p_0.orTerm(p)
    with pytest.raises(RuntimeError):
        p_0.orTerm(zero)
    with pytest.raises(RuntimeError):
        p_0.orTerm(f_x)
    with pytest.raises(RuntimeError):
        p_0.orTerm(sum)
    p_0.orTerm(p_0)
    p_f_x = tm.mkTerm(Kind.APPLY_UF, p, f_x)
    p_f_x.orTerm(b)
    with pytest.raises(RuntimeError):
        p_f_x.orTerm(x)
    with pytest.raises(RuntimeError):
        p_f_x.orTerm(f)
    with pytest.raises(RuntimeError):
        p_f_x.orTerm(p)
    with pytest.raises(RuntimeError):
        p_f_x.orTerm(zero)
    with pytest.raises(RuntimeError):
        p_f_x.orTerm(f_x)
    with pytest.raises(RuntimeError):
        p_f_x.orTerm(sum)
    p_f_x.orTerm(p_0)
    p_f_x.orTerm(p_f_x)


def test_xor_term(tm):
    bvSort = tm.mkBitVectorSort(8)
    intSort = tm.getIntegerSort()
    boolSort = tm.getBooleanSort()
    funSort1 = tm.mkFunctionSort(bvSort, intSort)
    funSort2 = tm.mkFunctionSort(intSort, boolSort)

    b = tm.mkTrue()
    b.xorTerm(b)
    x = tm.mkVar(tm.mkBitVectorSort(8), "x")
    with pytest.raises(RuntimeError):
        x.xorTerm(b)
    with pytest.raises(RuntimeError):
        x.xorTerm(x)
    f = tm.mkVar(funSort1, "f")
    with pytest.raises(RuntimeError):
        f.xorTerm(b)
    with pytest.raises(RuntimeError):
        f.xorTerm(x)
    with pytest.raises(RuntimeError):
        f.xorTerm(f)
    p = tm.mkVar(funSort2, "p")
    with pytest.raises(RuntimeError):
        p.xorTerm(b)
    with pytest.raises(RuntimeError):
        p.xorTerm(x)
    with pytest.raises(RuntimeError):
        p.xorTerm(f)
    with pytest.raises(RuntimeError):
        p.xorTerm(p)
    zero = tm.mkInteger(0)
    with pytest.raises(RuntimeError):
        zero.xorTerm(b)
    with pytest.raises(RuntimeError):
        zero.xorTerm(x)
    with pytest.raises(RuntimeError):
        zero.xorTerm(f)
    with pytest.raises(RuntimeError):
        zero.xorTerm(p)
    with pytest.raises(RuntimeError):
        zero.xorTerm(zero)
    f_x = tm.mkTerm(Kind.APPLY_UF, f, x)
    with pytest.raises(RuntimeError):
        f_x.xorTerm(b)
    with pytest.raises(RuntimeError):
        f_x.xorTerm(x)
    with pytest.raises(RuntimeError):
        f_x.xorTerm(f)
    with pytest.raises(RuntimeError):
        f_x.xorTerm(p)
    with pytest.raises(RuntimeError):
        f_x.xorTerm(zero)
    with pytest.raises(RuntimeError):
        f_x.xorTerm(f_x)
    sum = tm.mkTerm(Kind.ADD, f_x, f_x)
    with pytest.raises(RuntimeError):
        sum.xorTerm(b)
    with pytest.raises(RuntimeError):
        sum.xorTerm(x)
    with pytest.raises(RuntimeError):
        sum.xorTerm(f)
    with pytest.raises(RuntimeError):
        sum.xorTerm(p)
    with pytest.raises(RuntimeError):
        sum.xorTerm(zero)
    with pytest.raises(RuntimeError):
        sum.xorTerm(f_x)
    with pytest.raises(RuntimeError):
        sum.xorTerm(sum)
    p_0 = tm.mkTerm(Kind.APPLY_UF, p, zero)
    p_0.xorTerm(b)
    with pytest.raises(RuntimeError):
        p_0.xorTerm(x)
    with pytest.raises(RuntimeError):
        p_0.xorTerm(f)
    with pytest.raises(RuntimeError):
        p_0.xorTerm(p)
    with pytest.raises(RuntimeError):
        p_0.xorTerm(zero)
    with pytest.raises(RuntimeError):
        p_0.xorTerm(f_x)
    with pytest.raises(RuntimeError):
        p_0.xorTerm(sum)
    p_0.xorTerm(p_0)
    p_f_x = tm.mkTerm(Kind.APPLY_UF, p, f_x)
    p_f_x.xorTerm(b)
    with pytest.raises(RuntimeError):
        p_f_x.xorTerm(x)
    with pytest.raises(RuntimeError):
        p_f_x.xorTerm(f)
    with pytest.raises(RuntimeError):
        p_f_x.xorTerm(p)
    with pytest.raises(RuntimeError):
        p_f_x.xorTerm(zero)
    with pytest.raises(RuntimeError):
        p_f_x.xorTerm(f_x)
    with pytest.raises(RuntimeError):
        p_f_x.xorTerm(sum)
    p_f_x.xorTerm(p_0)
    p_f_x.xorTerm(p_f_x)


def test_eq_term(tm):
    bvSort = tm.mkBitVectorSort(8)
    intSort = tm.getIntegerSort()
    boolSort = tm.getBooleanSort()
    funSort1 = tm.mkFunctionSort(bvSort, intSort)
    funSort2 = tm.mkFunctionSort(intSort, boolSort)

    b = tm.mkTrue()
    b.eqTerm(b)
    x = tm.mkVar(tm.mkBitVectorSort(8), "x")
    with pytest.raises(RuntimeError):
        x.eqTerm(b)
    x.eqTerm(x)
    f = tm.mkVar(funSort1, "f")
    with pytest.raises(RuntimeError):
        f.eqTerm(b)
    with pytest.raises(RuntimeError):
        f.eqTerm(x)
    f.eqTerm(f)
    p = tm.mkVar(funSort2, "p")
    with pytest.raises(RuntimeError):
        p.eqTerm(b)
    with pytest.raises(RuntimeError):
        p.eqTerm(x)
    with pytest.raises(RuntimeError):
        p.eqTerm(f)
    p.eqTerm(p)
    zero = tm.mkInteger(0)
    with pytest.raises(RuntimeError):
        zero.eqTerm(b)
    with pytest.raises(RuntimeError):
        zero.eqTerm(x)
    with pytest.raises(RuntimeError):
        zero.eqTerm(f)
    with pytest.raises(RuntimeError):
        zero.eqTerm(p)
    zero.eqTerm(zero)
    f_x = tm.mkTerm(Kind.APPLY_UF, f, x)
    with pytest.raises(RuntimeError):
        f_x.eqTerm(b)
    with pytest.raises(RuntimeError):
        f_x.eqTerm(x)
    with pytest.raises(RuntimeError):
        f_x.eqTerm(f)
    with pytest.raises(RuntimeError):
        f_x.eqTerm(p)
    f_x.eqTerm(zero)
    f_x.eqTerm(f_x)
    sum = tm.mkTerm(Kind.ADD, f_x, f_x)
    with pytest.raises(RuntimeError):
        sum.eqTerm(b)
    with pytest.raises(RuntimeError):
        sum.eqTerm(x)
    with pytest.raises(RuntimeError):
        sum.eqTerm(f)
    with pytest.raises(RuntimeError):
        sum.eqTerm(p)
    sum.eqTerm(zero)
    sum.eqTerm(f_x)
    sum.eqTerm(sum)
    p_0 = tm.mkTerm(Kind.APPLY_UF, p, zero)
    p_0.eqTerm(b)
    with pytest.raises(RuntimeError):
        p_0.eqTerm(x)
    with pytest.raises(RuntimeError):
        p_0.eqTerm(f)
    with pytest.raises(RuntimeError):
        p_0.eqTerm(p)
    with pytest.raises(RuntimeError):
        p_0.eqTerm(zero)
    with pytest.raises(RuntimeError):
        p_0.eqTerm(f_x)
    with pytest.raises(RuntimeError):
        p_0.eqTerm(sum)
    p_0.eqTerm(p_0)
    p_f_x = tm.mkTerm(Kind.APPLY_UF, p, f_x)
    p_f_x.eqTerm(b)
    with pytest.raises(RuntimeError):
        p_f_x.eqTerm(x)
    with pytest.raises(RuntimeError):
        p_f_x.eqTerm(f)
    with pytest.raises(RuntimeError):
        p_f_x.eqTerm(p)
    with pytest.raises(RuntimeError):
        p_f_x.eqTerm(zero)
    with pytest.raises(RuntimeError):
        p_f_x.eqTerm(f_x)
    with pytest.raises(RuntimeError):
        p_f_x.eqTerm(sum)
    p_f_x.eqTerm(p_0)
    p_f_x.eqTerm(p_f_x)


def test_imp_term(tm):
    bvSort = tm.mkBitVectorSort(8)
    intSort = tm.getIntegerSort()
    boolSort = tm.getBooleanSort()
    funSort1 = tm.mkFunctionSort(bvSort, intSort)
    funSort2 = tm.mkFunctionSort(intSort, boolSort)

    b = tm.mkTrue()
    b.impTerm(b)
    x = tm.mkVar(tm.mkBitVectorSort(8), "x")
    with pytest.raises(RuntimeError):
        x.impTerm(b)
    with pytest.raises(RuntimeError):
        x.impTerm(x)
    f = tm.mkVar(funSort1, "f")
    with pytest.raises(RuntimeError):
        f.impTerm(b)
    with pytest.raises(RuntimeError):
        f.impTerm(x)
    with pytest.raises(RuntimeError):
        f.impTerm(f)
    p = tm.mkVar(funSort2, "p")
    with pytest.raises(RuntimeError):
        p.impTerm(b)
    with pytest.raises(RuntimeError):
        p.impTerm(x)
    with pytest.raises(RuntimeError):
        p.impTerm(f)
    with pytest.raises(RuntimeError):
        p.impTerm(p)
    zero = tm.mkInteger(0)
    with pytest.raises(RuntimeError):
        zero.impTerm(b)
    with pytest.raises(RuntimeError):
        zero.impTerm(x)
    with pytest.raises(RuntimeError):
        zero.impTerm(f)
    with pytest.raises(RuntimeError):
        zero.impTerm(p)
    with pytest.raises(RuntimeError):
        zero.impTerm(zero)
    f_x = tm.mkTerm(Kind.APPLY_UF, f, x)
    with pytest.raises(RuntimeError):
        f_x.impTerm(b)
    with pytest.raises(RuntimeError):
        f_x.impTerm(x)
    with pytest.raises(RuntimeError):
        f_x.impTerm(f)
    with pytest.raises(RuntimeError):
        f_x.impTerm(p)
    with pytest.raises(RuntimeError):
        f_x.impTerm(zero)
    with pytest.raises(RuntimeError):
        f_x.impTerm(f_x)
    sum = tm.mkTerm(Kind.ADD, f_x, f_x)
    with pytest.raises(RuntimeError):
        sum.impTerm(b)
    with pytest.raises(RuntimeError):
        sum.impTerm(x)
    with pytest.raises(RuntimeError):
        sum.impTerm(f)
    with pytest.raises(RuntimeError):
        sum.impTerm(p)
    with pytest.raises(RuntimeError):
        sum.impTerm(zero)
    with pytest.raises(RuntimeError):
        sum.impTerm(f_x)
    with pytest.raises(RuntimeError):
        sum.impTerm(sum)
    p_0 = tm.mkTerm(Kind.APPLY_UF, p, zero)
    p_0.impTerm(b)
    with pytest.raises(RuntimeError):
        p_0.impTerm(x)
    with pytest.raises(RuntimeError):
        p_0.impTerm(f)
    with pytest.raises(RuntimeError):
        p_0.impTerm(p)
    with pytest.raises(RuntimeError):
        p_0.impTerm(zero)
    with pytest.raises(RuntimeError):
        p_0.impTerm(f_x)
    with pytest.raises(RuntimeError):
        p_0.impTerm(sum)
    p_0.impTerm(p_0)
    p_f_x = tm.mkTerm(Kind.APPLY_UF, p, f_x)
    p_f_x.impTerm(b)
    with pytest.raises(RuntimeError):
        p_f_x.impTerm(x)
    with pytest.raises(RuntimeError):
        p_f_x.impTerm(f)
    with pytest.raises(RuntimeError):
        p_f_x.impTerm(p)
    with pytest.raises(RuntimeError):
        p_f_x.impTerm(zero)
    with pytest.raises(RuntimeError):
        p_f_x.impTerm(f_x)
    with pytest.raises(RuntimeError):
        p_f_x.impTerm(sum)
    p_f_x.impTerm(p_0)
    p_f_x.impTerm(p_f_x)


def test_ite_term(tm):
    bvSort = tm.mkBitVectorSort(8)
    intSort = tm.getIntegerSort()
    boolSort = tm.getBooleanSort()
    funSort1 = tm.mkFunctionSort(bvSort, intSort)
    funSort2 = tm.mkFunctionSort(intSort, boolSort)

    b = tm.mkTrue()
    b.iteTerm(b, b)
    x = tm.mkVar(tm.mkBitVectorSort(8), "x")
    b.iteTerm(x, x)
    b.iteTerm(b, b)
    with pytest.raises(RuntimeError):
        b.iteTerm(x, b)
    with pytest.raises(RuntimeError):
        x.iteTerm(x, x)
    with pytest.raises(RuntimeError):
        x.iteTerm(x, b)
    f = tm.mkVar(funSort1, "f")
    with pytest.raises(RuntimeError):
        f.iteTerm(b, b)
    with pytest.raises(RuntimeError):
        x.iteTerm(b, x)
    p = tm.mkVar(funSort2, "p")
    with pytest.raises(RuntimeError):
        p.iteTerm(b, b)
    with pytest.raises(RuntimeError):
        p.iteTerm(x, b)
    zero = tm.mkInteger(0)
    with pytest.raises(RuntimeError):
        zero.iteTerm(x, x)
    with pytest.raises(RuntimeError):
        zero.iteTerm(x, b)
    f_x = tm.mkTerm(Kind.APPLY_UF, f, x)
    with pytest.raises(RuntimeError):
        f_x.iteTerm(b, b)
    with pytest.raises(RuntimeError):
        f_x.iteTerm(b, x)
    sum = tm.mkTerm(Kind.ADD, f_x, f_x)
    with pytest.raises(RuntimeError):
        sum.iteTerm(x, x)
    with pytest.raises(RuntimeError):
        sum.iteTerm(b, x)
    p_0 = tm.mkTerm(Kind.APPLY_UF, p, zero)
    p_0.iteTerm(b, b)
    p_0.iteTerm(x, x)
    with pytest.raises(RuntimeError):
        p_0.iteTerm(x, b)
    p_f_x = tm.mkTerm(Kind.APPLY_UF, p, f_x)
    p_f_x.iteTerm(b, b)
    p_f_x.iteTerm(x, x)
    with pytest.raises(RuntimeError):
        p_f_x.iteTerm(x, b)


def test_term_assignment(tm):
    t1 = tm.mkInteger(1)
    t2 = t1
    t2 = tm.mkInteger(2)
    assert t1 == tm.mkInteger(1)


def test_substitute(tm):
    x = tm.mkConst(tm.getIntegerSort(), "x")
    one = tm.mkInteger(1)
    ttrue = tm.mkTrue()
    xpx = tm.mkTerm(Kind.ADD, x, x)
    onepone = tm.mkTerm(Kind.ADD, one, one)

    assert xpx.substitute(x, one) == onepone
    assert onepone.substitute(one, x) == xpx
    # incorrect due to type
    with pytest.raises(RuntimeError):
        xpx.substitute(one, ttrue)

    # simultaneous substitution
    y = tm.mkConst(tm.getIntegerSort(), "y")
    xpy = tm.mkTerm(Kind.ADD, x, y)
    xpone = tm.mkTerm(Kind.ADD, y, one)
    es = []
    rs = []
    es.append(x)
    rs.append(y)
    es.append(y)
    rs.append(one)
    assert xpy.substitute(es, rs) == xpone

    # incorrect substitution due to arity
    rs.pop()
    with pytest.raises(RuntimeError):
        xpy.substitute(es, rs)

    # incorrect substitution due to types
    rs.append(ttrue)
    with pytest.raises(RuntimeError):
        xpy.substitute(es, rs)


def test_term_compare(tm):
    t1 = tm.mkInteger(1)
    t2 = tm.mkTerm(Kind.ADD, tm.mkInteger(2), tm.mkInteger(2))
    t3 = tm.mkTerm(Kind.ADD, tm.mkInteger(2), tm.mkInteger(2))
    assert t2 >= t3
    assert t2 <= t3
    assert (t1 > t2) != (t1 < t2)
    assert (t1 > t2 or t1 == t2) == (t1 >= t2)


def test_term_children(tm):
    # simple term 2+3
    two = tm.mkInteger(2)
    t1 = tm.mkTerm(Kind.ADD, two, tm.mkInteger(3))
    assert t1[0] == two
    assert t1.getNumChildren() == 2

    # apply term f(2)
    intSort = tm.getIntegerSort()
    fsort = tm.mkFunctionSort(intSort, intSort)
    f = tm.mkConst(fsort, "f")
    t2 = tm.mkTerm(Kind.APPLY_UF, f, two)
    # due to our higher-order view of terms, we treat f as a child of
    # Kind.APPLY_UF
    assert t2.getNumChildren() == 2
    assert t2[0] == f
    assert t2[1] == two


def test_get_const_array_base(tm):
    intsort = tm.getIntegerSort()
    arrsort = tm.mkArraySort(intsort, intsort)
    one = tm.mkInteger(1)
    constarr = tm.mkConstArray(arrsort, one)

    assert constarr.isConstArray()
    assert one == constarr.getConstArrayBase()

    with pytest.raises(RuntimeError):
        one.getConstArrayBase()

    a = tm.mkConst(arrsort, "a")
    with pytest.raises(RuntimeError):
        a.getConstArrayBase()


def test_get_uninterpreted_sort_value(tm, solver):
    solver.setOption("produce-models", "true")
    uSort = tm.mkUninterpretedSort("u")
    x = tm.mkConst(uSort, "x")
    y = tm.mkConst(uSort, "y")
    solver.assertFormula(tm.mkTerm(Kind.EQUAL, x, y))
    assert solver.checkSat().isSat()
    vx = solver.getValue(x)
    vy = solver.getValue(y)
    assert vx.isUninterpretedSortValue()
    assert vy.isUninterpretedSortValue()
    assert vx.getUninterpretedSortValue() == vy.getUninterpretedSortValue()


def test_is_rounding_mode_value(tm):
    assert not tm.mkInteger(15).isRoundingModeValue()
    assert tm.mkRoundingMode(
        RoundingMode.ROUND_NEAREST_TIES_TO_EVEN).isRoundingModeValue()
    assert not tm.mkConst(
        tm.getRoundingModeSort()).isRoundingModeValue()


def test_get_rounding_mode_value(tm):
    with pytest.raises(RuntimeError):
        tm.mkInteger(15).getRoundingModeValue()
    assert tm.mkRoundingMode(
        RoundingMode.ROUND_NEAREST_TIES_TO_EVEN).getRoundingModeValue(
        ) == RoundingMode.ROUND_NEAREST_TIES_TO_EVEN
    assert tm.mkRoundingMode(
        RoundingMode.ROUND_TOWARD_POSITIVE).getRoundingModeValue(
        ) == RoundingMode.ROUND_TOWARD_POSITIVE
    assert tm.mkRoundingMode(
        RoundingMode.ROUND_TOWARD_NEGATIVE).getRoundingModeValue(
        ) == RoundingMode.ROUND_TOWARD_NEGATIVE
    assert tm.mkRoundingMode(
        RoundingMode.ROUND_TOWARD_ZERO).getRoundingModeValue(
        ) == RoundingMode.ROUND_TOWARD_ZERO
    assert tm.mkRoundingMode(
        RoundingMode.ROUND_NEAREST_TIES_TO_AWAY).getRoundingModeValue(
        ) == RoundingMode.ROUND_NEAREST_TIES_TO_AWAY


def test_get_tuple(tm):
    t1 = tm.mkInteger(15)
    t2 = tm.mkReal(17, 25)
    t3 = tm.mkString("abc")

    tup = tm.mkTuple([t1, t2, t3])

    assert tup.isTupleValue()
    assert [t1, t2, t3] == tup.getTupleValue()


def test_get_set(tm, solver):
    s = tm.mkSetSort(tm.getIntegerSort())

    i1 = tm.mkInteger(5)
    i2 = tm.mkInteger(7)

    s1 = tm.mkEmptySet(s)
    s2 = tm.mkTerm(Kind.SET_SINGLETON, i1)
    s3 = tm.mkTerm(Kind.SET_SINGLETON, i1)
    s4 = tm.mkTerm(Kind.SET_SINGLETON, i2)
    s5 = tm.mkTerm(
            Kind.SET_UNION, s2, tm.mkTerm(Kind.SET_UNION, s3, s4))

    assert s1.isSetValue()
    assert s2.isSetValue()
    assert s3.isSetValue()
    assert s4.isSetValue()
    assert not s5.isSetValue()
    s5 = solver.simplify(s5)
    assert s5.isSetValue()

    assert set([]) == s1.getSetValue()
    assert set([i1]) == s2.getSetValue()
    assert set([i1]) == s3.getSetValue()
    assert set([i2]) == s4.getSetValue()
    assert set([i1, i2]) == s5.getSetValue()


def test_get_sequence(tm, solver):
    s = tm.mkSequenceSort(tm.getIntegerSort())

    i1 = tm.mkInteger(5)
    i2 = tm.mkInteger(7)

    s1 = tm.mkEmptySequence(s)
    s2 = tm.mkTerm(Kind.SEQ_UNIT, i1)
    s3 = tm.mkTerm(Kind.SEQ_UNIT, i1)
    s4 = tm.mkTerm(Kind.SEQ_UNIT, i2)
    s5 = tm.mkTerm(Kind.SEQ_CONCAT, s2,
                       tm.mkTerm(Kind.SEQ_CONCAT, s3, s4))

    assert s1.isSequenceValue()
    assert not s2.isSequenceValue()
    assert not s3.isSequenceValue()
    assert not s4.isSequenceValue()
    assert not s5.isSequenceValue()

    s2 = solver.simplify(s2)
    s3 = solver.simplify(s3)
    s4 = solver.simplify(s4)
    s5 = solver.simplify(s5)

    assert [] == s1.getSequenceValue()
    assert [i1] == s2.getSequenceValue()
    assert [i1] == s3.getSequenceValue()
    assert [i2] == s4.getSequenceValue()
    assert [i1, i1, i2] == s5.getSequenceValue()


def test_get_floating_point(tm):
    bvval = tm.mkBitVector(16, "0000110000000011", 2)
    fp = tm.mkFloatingPoint(5, 11, bvval)

    assert fp.isFloatingPointValue()
    assert not fp.isFloatingPointPosZero()
    assert not fp.isFloatingPointNegZero()
    assert not fp.isFloatingPointPosInf()
    assert not fp.isFloatingPointNegInf()
    assert not fp.isFloatingPointNaN()
    assert (5, 11, bvval) == fp.getFloatingPointValue()

    assert tm.mkFloatingPointPosZero(5, 11).isFloatingPointPosZero()
    assert tm.mkFloatingPointNegZero(5, 11).isFloatingPointNegZero()
    assert tm.mkFloatingPointPosInf(5, 11).isFloatingPointPosInf()
    assert tm.mkFloatingPointNegInf(5, 11).isFloatingPointNegInf()
    assert tm.mkFloatingPointNaN(5, 11).isFloatingPointNaN()


def test_is_integer(tm):
    int1 = tm.mkInteger("-18446744073709551616")
    int2 = tm.mkInteger("-18446744073709551615")
    int3 = tm.mkInteger("-4294967296")
    int4 = tm.mkInteger("-4294967295")
    int5 = tm.mkInteger("-10")
    int6 = tm.mkInteger("0")
    int7 = tm.mkInteger("10")
    int8 = tm.mkInteger("4294967295")
    int9 = tm.mkInteger("4294967296")
    int10 = tm.mkInteger("18446744073709551615")
    int11 = tm.mkInteger("18446744073709551616")
    int12 = tm.mkInteger("-0")

    with pytest.raises(RuntimeError):
        tm.mkInteger("")
    with pytest.raises(RuntimeError):
        tm.mkInteger("-")
    with pytest.raises(RuntimeError):
        tm.mkInteger("-1-")
    with pytest.raises(RuntimeError):
        tm.mkInteger("0.0")
    with pytest.raises(RuntimeError):
        tm.mkInteger("-0.1")
    with pytest.raises(RuntimeError):
        tm.mkInteger("012")
    with pytest.raises(RuntimeError):
        tm.mkInteger("0000")
    with pytest.raises(RuntimeError):
        tm.mkInteger("-01")
    with pytest.raises(RuntimeError):
        tm.mkInteger("-00")

    assert int1.isIntegerValue()
    assert int2.isIntegerValue()
    assert int3.isIntegerValue()
    assert int4.isIntegerValue()
    assert int5.isIntegerValue()
    assert int6.isIntegerValue()
    assert int7.isIntegerValue()
    assert int8.isIntegerValue()
    assert int9.isIntegerValue()
    assert int10.isIntegerValue()
    assert int11.isIntegerValue()

    assert int1.getIntegerValue() == -18446744073709551616
    assert int2.getIntegerValue() == -18446744073709551615
    assert int3.getIntegerValue() == -4294967296
    assert int4.getIntegerValue() == -4294967295
    assert int5.getIntegerValue() == -10
    assert int6.getIntegerValue() == 0
    assert int7.getIntegerValue() == 10
    assert int8.getIntegerValue() == 4294967295
    assert int9.getIntegerValue() == 4294967296
    assert int10.getIntegerValue() == 18446744073709551615
    assert int11.getIntegerValue() == 18446744073709551616

    assert int1.getRealOrIntegerValueSign() == -1
    assert int6.getRealOrIntegerValueSign() == 0
    assert int7.getRealOrIntegerValueSign() == 1


def test_get_string(tm):
    s1 = tm.mkString("abcde")
    assert s1.isStringValue()
    assert s1.getStringValue() == str("abcde")


def test_get_real(tm):
    real1 = tm.mkReal("0")
    real2 = tm.mkReal(".0")
    real3 = tm.mkReal("-17")
    real4 = tm.mkReal("-3/5")
    real5 = tm.mkReal("12.7")
    real6 = tm.mkReal("1/4294967297")
    real7 = tm.mkReal("4294967297")
    real8 = tm.mkReal("1/18446744073709551617")
    real9 = tm.mkReal("18446744073709551617")

    assert real1.isRealValue()
    assert real2.isRealValue()
    assert real3.isRealValue()
    assert real4.isRealValue()
    assert real5.isRealValue()
    assert real6.isRealValue()
    assert real7.isRealValue()
    assert real8.isRealValue()
    assert real9.isRealValue()

    assert 0 == real1.getRealValue()
    assert 0 == real2.getRealValue()
    assert -17 == real3.getRealValue()
    assert Fraction(-3, 5) == real4.getRealValue()
    assert Fraction(127, 10) == real5.getRealValue()
    assert Fraction(1, 4294967297) == real6.getRealValue()
    assert 4294967297 == real7.getRealValue()
    assert Fraction(1, 18446744073709551617) == real8.getRealValue()
    assert Fraction(18446744073709551617, 1) == real9.getRealValue()

    # Check denominator too large for float
    num = 1
    den = 2 ** 64 + 1
    real_big = tm.mkReal(num, den)
    assert real_big.isRealValue()
    assert Fraction(num, den) == real_big.getRealValue()

    # Check that we're treating floats as decimal aproximations rather than
    # IEEE-754-specified values.
    real_decimal = tm.mkReal(0.3)
    assert real_decimal.isRealValue()
    assert Fraction("0.3") == real_decimal.getRealValue()
    assert Fraction(0.3) == Fraction(5404319552844595, 18014398509481984)
    assert Fraction(0.3) != real_decimal.getRealValue()
    
    with pytest.raises(RuntimeError):
        tm.mkReal("1/0")
    with pytest.raises(RuntimeError):
        tm.mkReal("2/0000")


def test_get_boolean(tm):
    b1 = tm.mkBoolean(True)
    b2 = tm.mkBoolean(False)

    assert b1.isBooleanValue()
    assert b2.isBooleanValue()
    assert b1.getBooleanValue()
    assert not b2.getBooleanValue()


def test_get_bit_vector(tm):
    b1 = tm.mkBitVector(8, 15)
    b2 = tm.mkBitVector(8, "00001111", 2)
    b3 = tm.mkBitVector(8, "15", 10)
    b4 = tm.mkBitVector(8, "0f", 16)
    b5 = tm.mkBitVector(9, "00001111", 2);
    b6 = tm.mkBitVector(9, "15", 10);
    b7 = tm.mkBitVector(9, "0f", 16);

    assert b1.isBitVectorValue()
    assert b2.isBitVectorValue()
    assert b3.isBitVectorValue()
    assert b4.isBitVectorValue()
    assert b5.isBitVectorValue()
    assert b6.isBitVectorValue()
    assert b7.isBitVectorValue()

    assert "00001111" == b1.getBitVectorValue(2)
    assert "15" == b1.getBitVectorValue(10)
    assert "f" == b1.getBitVectorValue(16)
    assert "00001111" == b2.getBitVectorValue(2)
    assert "15" == b2.getBitVectorValue(10)
    assert "f" == b2.getBitVectorValue(16)
    assert "00001111" == b3.getBitVectorValue(2)
    assert "15" == b3.getBitVectorValue(10)
    assert "f" == b3.getBitVectorValue(16)
    assert "00001111" == b4.getBitVectorValue(2)
    assert "15" == b4.getBitVectorValue(10)
    assert "f" == b4.getBitVectorValue(16)
    assert "000001111" == b5.getBitVectorValue(2)
    assert "15" == b5.getBitVectorValue(10)
    assert "f" == b5.getBitVectorValue(16)
    assert "000001111" == b6.getBitVectorValue(2)
    assert "15" == b6.getBitVectorValue(10)
    assert "f" == b6.getBitVectorValue(16)
    assert "000001111" == b7.getBitVectorValue(2)
    assert "15" == b7.getBitVectorValue(10)
    assert "f" == b7.getBitVectorValue(16)


def test_const_array(tm):
    intsort = tm.getIntegerSort()
    arrsort = tm.mkArraySort(intsort, intsort)
    a = tm.mkConst(arrsort, "a")
    one = tm.mkInteger(1)
    two = tm.mkBitVector(2, 2)
    iconst = tm.mkConst(intsort)
    constarr = tm.mkConstArray(arrsort, one)

    with pytest.raises(RuntimeError):
        tm.mkConstArray(arrsort, two)
    with pytest.raises(RuntimeError):
        tm.mkConstArray(arrsort, iconst)

    assert constarr.getKind() == Kind.CONST_ARRAY
    assert constarr.getConstArrayBase() == one
    with pytest.raises(RuntimeError):
        a.getConstArrayBase()

    arrsort = tm.mkArraySort(tm.getRealSort(), tm.getRealSort())
    zero_array = tm.mkConstArray(arrsort, tm.mkReal(0))
    stores = tm.mkTerm(Kind.STORE, zero_array, tm.mkReal(1),
                           tm.mkReal(2))
    stores = tm.mkTerm(Kind.STORE, stores, tm.mkReal(2),
                           tm.mkReal(3))
    stores = tm.mkTerm(Kind.STORE, stores, tm.mkReal(4),
                           tm.mkReal(5))


def test_const_sequence_elements(tm):
    realsort = tm.getRealSort()
    seqsort = tm.mkSequenceSort(realsort)
    s = tm.mkEmptySequence(seqsort)

    assert s.getKind() == Kind.CONST_SEQUENCE
    # empty sequence has zero elements
    cs = s.getSequenceValue()
    assert len(cs) == 0

    # A seq.unit app is not a constant sequence (regardless of whether it is
    # applied to a constant).
    su = tm.mkTerm(Kind.SEQ_UNIT, tm.mkReal(1))
    with pytest.raises(RuntimeError):
        su.getSequenceValue()

def test_get_cardinality_constraint(tm):
    su = tm.mkUninterpretedSort("u")
    t = tm.mkCardinalityConstraint(su, 3)
    assert t.isCardinalityConstraint()
    cc = t.getCardinalityConstraint()
    assert cc[0] == su
    assert cc[1] == 3
    x = tm.mkConst(tm.getIntegerSort(), "x")
    assert not x.isCardinalityConstraint()
    with pytest.raises(RuntimeError):
      x.getCardinalityConstraint()

def test_get_real_algebraic_number(tm, solver):
    solver.setOption("produce-models", "true")
    solver.setLogic("QF_NRA")
    realsort = tm.getRealSort()
    x = tm.mkConst(realsort, "x")
    x2 = tm.mkTerm(Kind.MULT, x, x)
    two = tm.mkReal(2, 1)
    eq = tm.mkTerm(Kind.EQUAL, x2, two)
    solver.assertFormula(eq)
    # Note that check-sat should only return "sat" if libpoly is enabled.
    # Otherwise, we do not test the following functionality.
    if solver.checkSat().isSat():
        # We find a model for (x*x = 2), where x should be a real algebraic number.
        # We assert that its defining polynomial is non-null and its lower and
        # upper bounds are real.
        vx = solver.getValue(x)
        assert vx.isRealAlgebraicNumber()
        y = tm.mkVar(realsort, "y")
        poly = vx.getRealAlgebraicNumberDefiningPolynomial(y)
        assert not poly.isNull()
        lb = vx.getRealAlgebraicNumberLowerBound()
        assert lb.isRealValue()
        ub = vx.getRealAlgebraicNumberUpperBound()
        assert ub.isRealValue()
        # cannot call with non-variable
        yc = tm.mkConst(realsort, "y")
        with pytest.raises(RuntimeError):
            vx.getRealAlgebraicNumberDefiningPolynomial(yc)

def test_get_skolem(tm, solver):
    # ordinary variables are not skolems
    x = tm.mkConst(tm.getIntegerSort(), "x")
    assert not x.isSkolem()
    with pytest.raises(RuntimeError):
        x.getSkolemId()
    with pytest.raises(RuntimeError):
        x.getSkolemIndices()
    

def test_term_scoped_to_string(tm):
    intsort = tm.getIntegerSort()
    x = tm.mkConst(intsort, "x")
    assert str(x) == "x"
    tm2 = cvc5.Solver(tm)
    assert str(x) == "x"
