diff --git a/src/api/js/src/high-level/high-level.test.ts b/src/api/js/src/high-level/high-level.test.ts index 7d3038a7b..b3ed6e82f 100644 --- a/src/api/js/src/high-level/high-level.test.ts +++ b/src/api/js/src/high-level/high-level.test.ts @@ -521,6 +521,133 @@ describe('high-level', () => { }); }); + describe('sets', () => { + it('Example 1', async () => { + const Z3 = api.Context('main'); + + const set = Z3.Set.const('set', Z3.Int.sort()); + const [a, b] = Z3.Int.consts('a b'); + + const conjecture = set.contains(a).and(set.contains(b)).implies(Z3.EmptySet(Z3.Int.sort()).neq(set)); + await prove(conjecture); + }); + + it('Example 2', async () => { + const Z3 = api.Context('main'); + + const set = Z3.Set.const('set', Z3.Int.sort()); + const [a, b] = Z3.Int.consts('a b'); + + const conjecture = set.contains(a).and(set.contains(b)).implies(Z3.Set.val([a, b], Z3.Int.sort()).subsetOf(set)); + await prove(conjecture); + }); + + it('Example 3', async () => { + const Z3 = api.Context('main'); + + const set = Z3.Set.const('set', Z3.Int.sort()); + const [a, b] = Z3.Int.consts('a b'); + + const conjecture = set.contains(a).and(set.contains(b)).and(Z3.Set.val([a, b], Z3.Int.sort()).eq(set)); + await solve(conjecture); + }); + + it('Intersection 1', async () => { + const Z3 = api.Context('main'); + + const set = Z3.Set.const('set', Z3.Int.sort()); + const [a, b] = Z3.Int.consts('a b'); + const abset = Z3.Set.val([a, b], Z3.Int.sort()); + + const conjecture = set.intersect(abset).subsetOf(abset); + await prove(conjecture); + }); + + it('Intersection 2', async () => { + const Z3 = api.Context('main'); + + const set = Z3.Set.const('set', Z3.Int.sort()); + const [a, b] = Z3.Int.consts('a b'); + const abset = Z3.Set.val([a, b], Z3.Int.sort()); + + const conjecture = set.subsetOf(set.intersect(abset)); + await solve(conjecture); + }); + + it('Union 1', async () => { + const Z3 = api.Context('main'); + + const set = Z3.Set.const('set', Z3.Int.sort()); + const [a, b] = Z3.Int.consts('a b'); + const abset = Z3.Set.val([a, b], Z3.Int.sort()); + + const conjecture = set.subsetOf(set.union(abset)); + await prove(conjecture); + }); + + it('Union 2', async () => { + const Z3 = api.Context('main'); + + const set = Z3.Set.const('set', Z3.Int.sort()); + const [a, b] = Z3.Int.consts('a b'); + const abset = Z3.Set.val([a, b], Z3.Int.sort()); + + const conjecture = set.union(abset).subsetOf(abset); + await solve(conjecture); + }); + + it('Complement 1', async () => { + const Z3 = api.Context('main'); + + const set = Z3.Set.const('set', Z3.Int.sort()); + const a = Z3.Int.const('a'); + + const conjecture = set.complement().complement().eq(set) + await prove(conjecture); + }); + it('Complement 2', async () => { + const Z3 = api.Context('main'); + + const set = Z3.Set.const('set', Z3.Int.sort()); + const a = Z3.Int.const('a'); + + const conjecture = set.contains(a).implies(Z3.Not(set.complement().contains(a))) + await prove(conjecture); + }); + + it('Difference', async () => { + const Z3 = api.Context('main'); + + const [set1, set2] = Z3.Set.consts('set1 set2', Z3.Int.sort()); + const a = Z3.Int.const('a'); + + const conjecture = set1.contains(a).implies(Z3.Not(set2.diff(set1).contains(a))) + + await prove(conjecture); + }); + + it('FullSet', async () => { + const Z3 = api.Context('main'); + + const set = Z3.Set.const('set', Z3.Int.sort()); + + const conjecture = set.complement().eq(Z3.FullSet(Z3.Int.sort()).diff(set)); + + await prove(conjecture); + }); + + it('SetDel', async () => { + const Z3 = api.Context('main'); + + const empty = Z3.Set.empty(Z3.Int.sort()); + const [a, b] = Z3.Int.consts('a b'); + + const conjecture = empty.add(a).add(b).del(a).del(b).eq(empty); + + await prove(conjecture); + }); + }); + describe('quantifiers', () => { it('Basic Universal', async () => { const Z3 = api.Context('main'); diff --git a/src/api/js/src/high-level/high-level.ts b/src/api/js/src/high-level/high-level.ts index f3bc51583..7f1859c67 100644 --- a/src/api/js/src/high-level/high-level.ts +++ b/src/api/js/src/high-level/high-level.ts @@ -86,6 +86,8 @@ import { CoercibleToArith, NonEmptySortArray, FuncEntry, + SMTSetSort, + SMTSet, } from './types'; import { allSatisfy, assert, assertExhaustive } from './utils'; @@ -795,6 +797,33 @@ export function createApi(Z3: Z3Core): Z3HighLevel { return new ArrayImpl<[DomainSort], RangeSort>(check(Z3.mk_const_array(contextPtr, domain.ptr, value.ptr))); }, }; + const Set = { + // reference: https://z3prover.github.io/api/html/namespacez3py.html#a545f894afeb24caa1b88b7f2a324ee7e + sort>(sort: ElemSort): SMTSetSort { + return Array.sort(sort, Bool.sort()); + }, + const>(name: string, sort: ElemSort) : SMTSet { + return new SetImpl( + check(Z3.mk_const(contextPtr, _toSymbol(name), Array.sort(sort, Bool.sort()).ptr)), + ); + }, + consts>(names: string | string[], sort: ElemSort) : SMTSet[] { + if (typeof names === 'string') { + names = names.split(' '); + } + return names.map(name => Set.const(name, sort)); + }, + empty>(sort: ElemSort): SMTSet { + return EmptySet(sort); + }, + val>(values: CoercibleToMap, Name>[], sort: ElemSort): SMTSet { + var result = EmptySet(sort); + for (const value of values) { + result = SetAdd(result, value); + } + return result; + } + } //////////////// // Operations // @@ -1249,6 +1278,49 @@ export function createApi(Z3: Z3Core): Z3HighLevel { >; } + function SetUnion>(...args: SMTSet[]): SMTSet { + return new SetImpl(check(Z3.mk_set_union(contextPtr, args.map(arg => arg.ast)))); + } + + function SetIntersect>(...args: SMTSet[]): SMTSet { + return new SetImpl(check(Z3.mk_set_intersect(contextPtr, args.map(arg => arg.ast)))); + } + + function SetDifference>(a: SMTSet, b: SMTSet): SMTSet { + return new SetImpl(check(Z3.mk_set_difference(contextPtr, a.ast, b.ast))); + } + + function SetHasSize>(set: SMTSet, size: bigint | number | string | IntNum): Bool { + const a = typeof size === 'object'? Int.sort().cast(size) : Int.sort().cast(size); + return new BoolImpl(check(Z3.mk_set_has_size(contextPtr, set.ast, a.ast))); + } + + function SetAdd>(set: SMTSet, elem: CoercibleToMap, Name>): SMTSet { + const arg = set.elemSort().cast(elem as any); + return new SetImpl(check(Z3.mk_set_add(contextPtr, set.ast, arg.ast))); + } + function SetDel>(set: SMTSet, elem: CoercibleToMap, Name>): SMTSet { + const arg = set.elemSort().cast(elem as any); + return new SetImpl(check(Z3.mk_set_del(contextPtr, set.ast, arg.ast))); + } + function SetComplement>(set: SMTSet): SMTSet { + return new SetImpl(check(Z3.mk_set_complement(contextPtr, set.ast))); + } + + function EmptySet>(sort: ElemSort): SMTSet { + return new SetImpl(check(Z3.mk_empty_set(contextPtr, sort.ptr))); + } + function FullSet>(sort: ElemSort): SMTSet { + return new SetImpl(check(Z3.mk_full_set(contextPtr, sort.ptr))); + } + function isMember>(elem: CoercibleToMap, Name>, set: SMTSet): Bool { + const arg = set.elemSort().cast(elem as any); + return new BoolImpl(check(Z3.mk_set_member(contextPtr, arg.ast, set.ast))); + } + function isSubset>(a: SMTSet, b: SMTSet): Bool { + return new BoolImpl(check(Z3.mk_set_subset(contextPtr, a.ast, b.ast))); + } + class AstImpl implements Ast { declare readonly __typename: Ast['__typename']; readonly ctx: Context; @@ -2536,6 +2608,41 @@ export function createApi(Z3: Z3Core): Z3HighLevel { } } + class SetImpl> extends ExprImpl>> implements SMTSet { + declare readonly __typename: 'Array'; + + elemSort(): ElemSort { + return this.sort.domain(); + } + union(...args: SMTSet[]): SMTSet { + return SetUnion(this, ...args); + } + intersect(...args: SMTSet[]): SMTSet { + return SetIntersect(this, ...args); + } + diff(b: SMTSet): SMTSet { + return SetDifference(this, b); + } + hasSize(size: string | number | bigint | IntNum): Bool { + return SetHasSize(this, size); + } + add(elem: CoercibleToMap, Name>): SMTSet { + return SetAdd(this, elem); + } + del(elem: CoercibleToMap, Name>): SMTSet { + return SetDel(this, elem); + } + complement(): SMTSet { + return SetComplement(this); + } + contains(elem: CoercibleToMap, Name>): Bool { + return isMember(elem, this); + } + subsetOf(b: SMTSet): Bool { + return isSubset(this, b); + } + } + class QuantifierImpl< QVarSorts extends NonEmptySortArray, QSort extends BoolSort | SMTArraySort, @@ -2917,6 +3024,7 @@ export function createApi(Z3: Z3Core): Z3HighLevel { Real, BitVec, Array, + Set, //////////////// // Operations // @@ -2979,6 +3087,18 @@ export function createApi(Z3: Z3Core): Z3HighLevel { // Loading // ///////////// ast_from_string, + + SetUnion, + SetIntersect, + SetDifference, + SetHasSize, + SetAdd, + SetDel, + SetComplement, + EmptySet, + FullSet, + isMember, + isSubset, }; cleanup.register(ctx, () => Z3.del_context(contextPtr)); return ctx; diff --git a/src/api/js/src/high-level/types.ts b/src/api/js/src/high-level/types.ts index ca8b30ef0..02b95951e 100644 --- a/src/api/js/src/high-level/types.ts +++ b/src/api/js/src/high-level/types.ts @@ -357,6 +357,8 @@ export interface Context { readonly BitVec: BitVecCreation; /** @category Expressions */ readonly Array: SMTArrayCreation; + /** @category Expressions */ + readonly Set: SMTSetCreation; //////////////// // Operations // @@ -611,6 +613,39 @@ export interface Context { substitute(t: Expr, ...substitutions: [Expr, Expr][]): Expr; simplify(expr: Expr): Promise>; + + /** @category Operations */ + SetUnion>(...args: SMTSet[]): SMTSet; + + /** @category Operations */ + SetIntersect>(...args: SMTSet[]): SMTSet; + + /** @category Operations */ + SetDifference>(a: SMTSet, b: SMTSet): SMTSet; + + /** @category Operations */ + SetHasSize>(set: SMTSet, size: bigint | number | string | IntNum): Bool; + + /** @category Operations */ + SetAdd>(set: SMTSet, elem: CoercibleToMap, Name>): SMTSet; + + /** @category Operations */ + SetDel>(set: SMTSet, elem: CoercibleToMap, Name>): SMTSet; + + /** @category Operations */ + SetComplement>(set: SMTSet): SMTSet; + + /** @category Operations */ + EmptySet>(sort: ElemSort): SMTSet; + + /** @category Operations */ + FullSet>(sort: ElemSort): SMTSet; + + /** @category Operations */ + isMember>(elem: CoercibleToMap, Name>, set: SMTSet): Bool; + + /** @category Operations */ + isSubset>(a: SMTSet, b: SMTSet): Bool; } export interface Ast { @@ -1568,6 +1603,54 @@ export interface SMTArray< ): SMTArray; } +/** + * Set Implemented using Arrays + * + * @typeParam ElemSort The sort of the element of the set + * @category Sets + */ +export type SMTSetSort = Sort> = SMTArraySort>; + + +/** @category Sets*/ +export interface SMTSetCreation { + sort>(elemSort: ElemSort): SMTSetSort; + + const>(name: string, elemSort: ElemSort): SMTSet; + + consts>(names: string | string[], elemSort: ElemSort): SMTSet[]; + + empty>(sort: ElemSort): SMTSet; + + val>(values: CoercibleToMap, Name>[], sort: ElemSort): SMTSet; +} + +/** + * Represents Set expression + * + * @typeParam ElemSort The sort of the element of the set + * @category Arrays + */ +export interface SMTSet = Sort> extends Expr, Z3_ast> { + readonly __typename: 'Array'; + + elemSort(): ElemSort; + + union(...args: SMTSet[]): SMTSet; + intersect(...args: SMTSet[]): SMTSet; + diff(b: SMTSet): SMTSet; + + hasSize(size: bigint | number | string | IntNum): Bool; + + add(elem: CoercibleToMap, Name>): SMTSet; + del(elem: CoercibleToMap, Name>): SMTSet; + complement(): SMTSet; + + contains(elem: CoercibleToMap, Name>): Bool; + subsetOf(b: SMTSet): Bool; + +} + /** * Defines the expression type of the body of a quantifier expression *