Skip to content

Commit 4be4067

Browse files
authored
Typescript high-level api for Sets (#7471)
1 parent a17d4e6 commit 4be4067

File tree

3 files changed

+330
-0
lines changed

3 files changed

+330
-0
lines changed

src/api/js/src/high-level/high-level.test.ts

+127
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,133 @@ describe('high-level', () => {
521521
});
522522
});
523523

524+
describe('sets', () => {
525+
it('Example 1', async () => {
526+
const Z3 = api.Context('main');
527+
528+
const set = Z3.Set.const('set', Z3.Int.sort());
529+
const [a, b] = Z3.Int.consts('a b');
530+
531+
const conjecture = set.contains(a).and(set.contains(b)).implies(Z3.EmptySet(Z3.Int.sort()).neq(set));
532+
await prove(conjecture);
533+
});
534+
535+
it('Example 2', async () => {
536+
const Z3 = api.Context('main');
537+
538+
const set = Z3.Set.const('set', Z3.Int.sort());
539+
const [a, b] = Z3.Int.consts('a b');
540+
541+
const conjecture = set.contains(a).and(set.contains(b)).implies(Z3.Set.val([a, b], Z3.Int.sort()).subsetOf(set));
542+
await prove(conjecture);
543+
});
544+
545+
it('Example 3', async () => {
546+
const Z3 = api.Context('main');
547+
548+
const set = Z3.Set.const('set', Z3.Int.sort());
549+
const [a, b] = Z3.Int.consts('a b');
550+
551+
const conjecture = set.contains(a).and(set.contains(b)).and(Z3.Set.val([a, b], Z3.Int.sort()).eq(set));
552+
await solve(conjecture);
553+
});
554+
555+
it('Intersection 1', async () => {
556+
const Z3 = api.Context('main');
557+
558+
const set = Z3.Set.const('set', Z3.Int.sort());
559+
const [a, b] = Z3.Int.consts('a b');
560+
const abset = Z3.Set.val([a, b], Z3.Int.sort());
561+
562+
const conjecture = set.intersect(abset).subsetOf(abset);
563+
await prove(conjecture);
564+
});
565+
566+
it('Intersection 2', async () => {
567+
const Z3 = api.Context('main');
568+
569+
const set = Z3.Set.const('set', Z3.Int.sort());
570+
const [a, b] = Z3.Int.consts('a b');
571+
const abset = Z3.Set.val([a, b], Z3.Int.sort());
572+
573+
const conjecture = set.subsetOf(set.intersect(abset));
574+
await solve(conjecture);
575+
});
576+
577+
it('Union 1', async () => {
578+
const Z3 = api.Context('main');
579+
580+
const set = Z3.Set.const('set', Z3.Int.sort());
581+
const [a, b] = Z3.Int.consts('a b');
582+
const abset = Z3.Set.val([a, b], Z3.Int.sort());
583+
584+
const conjecture = set.subsetOf(set.union(abset));
585+
await prove(conjecture);
586+
});
587+
588+
it('Union 2', async () => {
589+
const Z3 = api.Context('main');
590+
591+
const set = Z3.Set.const('set', Z3.Int.sort());
592+
const [a, b] = Z3.Int.consts('a b');
593+
const abset = Z3.Set.val([a, b], Z3.Int.sort());
594+
595+
const conjecture = set.union(abset).subsetOf(abset);
596+
await solve(conjecture);
597+
});
598+
599+
it('Complement 1', async () => {
600+
const Z3 = api.Context('main');
601+
602+
const set = Z3.Set.const('set', Z3.Int.sort());
603+
const a = Z3.Int.const('a');
604+
605+
const conjecture = set.complement().complement().eq(set)
606+
await prove(conjecture);
607+
});
608+
it('Complement 2', async () => {
609+
const Z3 = api.Context('main');
610+
611+
const set = Z3.Set.const('set', Z3.Int.sort());
612+
const a = Z3.Int.const('a');
613+
614+
const conjecture = set.contains(a).implies(Z3.Not(set.complement().contains(a)))
615+
await prove(conjecture);
616+
});
617+
618+
it('Difference', async () => {
619+
const Z3 = api.Context('main');
620+
621+
const [set1, set2] = Z3.Set.consts('set1 set2', Z3.Int.sort());
622+
const a = Z3.Int.const('a');
623+
624+
const conjecture = set1.contains(a).implies(Z3.Not(set2.diff(set1).contains(a)))
625+
626+
await prove(conjecture);
627+
});
628+
629+
it('FullSet', async () => {
630+
const Z3 = api.Context('main');
631+
632+
const set = Z3.Set.const('set', Z3.Int.sort());
633+
634+
const conjecture = set.complement().eq(Z3.FullSet(Z3.Int.sort()).diff(set));
635+
636+
await prove(conjecture);
637+
});
638+
639+
it('SetDel', async () => {
640+
const Z3 = api.Context('main');
641+
642+
const empty = Z3.Set.empty(Z3.Int.sort());
643+
const [a, b] = Z3.Int.consts('a b');
644+
645+
const conjecture = empty.add(a).add(b).del(a).del(b).eq(empty);
646+
647+
await prove(conjecture);
648+
});
649+
});
650+
524651
describe('quantifiers', () => {
525652
it('Basic Universal', async () => {
526653
const Z3 = api.Context('main');

src/api/js/src/high-level/high-level.ts

+120
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ import {
8686
CoercibleToArith,
8787
NonEmptySortArray,
8888
FuncEntry,
89+
SMTSetSort,
90+
SMTSet,
8991
} from './types';
9092
import { allSatisfy, assert, assertExhaustive } from './utils';
9193

@@ -795,6 +797,33 @@ export function createApi(Z3: Z3Core): Z3HighLevel {
795797
return new ArrayImpl<[DomainSort], RangeSort>(check(Z3.mk_const_array(contextPtr, domain.ptr, value.ptr)));
796798
},
797799
};
800+
const Set = {
801+
// reference: https://z3prover.github.io/api/html/namespacez3py.html#a545f894afeb24caa1b88b7f2a324ee7e
802+
sort<ElemSort extends AnySort<Name>>(sort: ElemSort): SMTSetSort<Name, ElemSort> {
803+
return Array.sort(sort, Bool.sort());
804+
},
805+
const<ElemSort extends AnySort<Name>>(name: string, sort: ElemSort) : SMTSet<Name, ElemSort> {
806+
return new SetImpl<ElemSort>(
807+
check(Z3.mk_const(contextPtr, _toSymbol(name), Array.sort(sort, Bool.sort()).ptr)),
808+
);
809+
},
810+
consts<ElemSort extends AnySort<Name>>(names: string | string[], sort: ElemSort) : SMTSet<Name, ElemSort>[] {
811+
if (typeof names === 'string') {
812+
names = names.split(' ');
813+
}
814+
return names.map(name => Set.const(name, sort));
815+
},
816+
empty<ElemSort extends AnySort<Name>>(sort: ElemSort): SMTSet<Name, ElemSort> {
817+
return EmptySet(sort);
818+
},
819+
val<ElemSort extends AnySort<Name>>(values: CoercibleToMap<SortToExprMap<ElemSort, Name>, Name>[], sort: ElemSort): SMTSet<Name, ElemSort> {
820+
var result = EmptySet(sort);
821+
for (const value of values) {
822+
result = SetAdd(result, value);
823+
}
824+
return result;
825+
}
826+
}
798827

799828
////////////////
800829
// Operations //
@@ -1249,6 +1278,49 @@ export function createApi(Z3: Z3Core): Z3HighLevel {
12491278
>;
12501279
}
12511280

1281+
function SetUnion<ElemSort extends AnySort<Name>>(...args: SMTSet<Name, ElemSort>[]): SMTSet<Name, ElemSort> {
1282+
return new SetImpl<ElemSort>(check(Z3.mk_set_union(contextPtr, args.map(arg => arg.ast))));
1283+
}
1284+
1285+
function SetIntersect<ElemSort extends AnySort<Name>>(...args: SMTSet<Name, ElemSort>[]): SMTSet<Name, ElemSort> {
1286+
return new SetImpl<ElemSort>(check(Z3.mk_set_intersect(contextPtr, args.map(arg => arg.ast))));
1287+
}
1288+
1289+
function SetDifference<ElemSort extends AnySort<Name>>(a: SMTSet<Name, ElemSort>, b: SMTSet<Name, ElemSort>): SMTSet<Name, ElemSort> {
1290+
return new SetImpl<ElemSort>(check(Z3.mk_set_difference(contextPtr, a.ast, b.ast)));
1291+
}
1292+
1293+
function SetHasSize<ElemSort extends AnySort<Name>>(set: SMTSet<Name, ElemSort>, size: bigint | number | string | IntNum<Name>): Bool<Name> {
1294+
const a = typeof size === 'object'? Int.sort().cast(size) : Int.sort().cast(size);
1295+
return new BoolImpl(check(Z3.mk_set_has_size(contextPtr, set.ast, a.ast)));
1296+
}
1297+
1298+
function SetAdd<ElemSort extends AnySort<Name>>(set: SMTSet<Name, ElemSort>, elem: CoercibleToMap<SortToExprMap<ElemSort, Name>, Name>): SMTSet<Name, ElemSort> {
1299+
const arg = set.elemSort().cast(elem as any);
1300+
return new SetImpl<ElemSort>(check(Z3.mk_set_add(contextPtr, set.ast, arg.ast)));
1301+
}
1302+
function SetDel<ElemSort extends AnySort<Name>>(set: SMTSet<Name, ElemSort>, elem: CoercibleToMap<SortToExprMap<ElemSort, Name>, Name>): SMTSet<Name, ElemSort> {
1303+
const arg = set.elemSort().cast(elem as any);
1304+
return new SetImpl<ElemSort>(check(Z3.mk_set_del(contextPtr, set.ast, arg.ast)));
1305+
}
1306+
function SetComplement<ElemSort extends AnySort<Name>>(set: SMTSet<Name, ElemSort>): SMTSet<Name, ElemSort> {
1307+
return new SetImpl<ElemSort>(check(Z3.mk_set_complement(contextPtr, set.ast)));
1308+
}
1309+
1310+
function EmptySet<ElemSort extends AnySort<Name>>(sort: ElemSort): SMTSet<Name, ElemSort> {
1311+
return new SetImpl<ElemSort>(check(Z3.mk_empty_set(contextPtr, sort.ptr)));
1312+
}
1313+
function FullSet<ElemSort extends AnySort<Name>>(sort: ElemSort): SMTSet<Name, ElemSort> {
1314+
return new SetImpl<ElemSort>(check(Z3.mk_full_set(contextPtr, sort.ptr)));
1315+
}
1316+
function isMember<ElemSort extends AnySort<Name>>(elem: CoercibleToMap<SortToExprMap<ElemSort, Name>, Name>, set: SMTSet<Name, ElemSort>): Bool<Name> {
1317+
const arg = set.elemSort().cast(elem as any);
1318+
return new BoolImpl(check(Z3.mk_set_member(contextPtr, arg.ast, set.ast)));
1319+
}
1320+
function isSubset<ElemSort extends AnySort<Name>>(a: SMTSet<Name, ElemSort>, b: SMTSet<Name, ElemSort>): Bool<Name> {
1321+
return new BoolImpl(check(Z3.mk_set_subset(contextPtr, a.ast, b.ast)));
1322+
}
1323+
12521324
class AstImpl<Ptr extends Z3_ast> implements Ast<Name, Ptr> {
12531325
declare readonly __typename: Ast['__typename'];
12541326
readonly ctx: Context<Name>;
@@ -2536,6 +2608,41 @@ export function createApi(Z3: Z3Core): Z3HighLevel {
25362608
}
25372609
}
25382610

2611+
class SetImpl<ElemSort extends Sort<Name>> extends ExprImpl<Z3_ast, ArraySortImpl<[ElemSort], BoolSort<Name>>> implements SMTSet<Name, ElemSort> {
2612+
declare readonly __typename: 'Array';
2613+
2614+
elemSort(): ElemSort {
2615+
return this.sort.domain();
2616+
}
2617+
union(...args: SMTSet<Name, ElemSort>[]): SMTSet<Name, ElemSort> {
2618+
return SetUnion(this, ...args);
2619+
}
2620+
intersect(...args: SMTSet<Name, ElemSort>[]): SMTSet<Name, ElemSort> {
2621+
return SetIntersect(this, ...args);
2622+
}
2623+
diff(b: SMTSet<Name, ElemSort>): SMTSet<Name, ElemSort> {
2624+
return SetDifference(this, b);
2625+
}
2626+
hasSize(size: string | number | bigint | IntNum<Name>): Bool<Name> {
2627+
return SetHasSize(this, size);
2628+
}
2629+
add(elem: CoercibleToMap<SortToExprMap<ElemSort, Name>, Name>): SMTSet<Name, ElemSort> {
2630+
return SetAdd(this, elem);
2631+
}
2632+
del(elem: CoercibleToMap<SortToExprMap<ElemSort, Name>, Name>): SMTSet<Name, ElemSort> {
2633+
return SetDel(this, elem);
2634+
}
2635+
complement(): SMTSet<Name, ElemSort> {
2636+
return SetComplement(this);
2637+
}
2638+
contains(elem: CoercibleToMap<SortToExprMap<ElemSort, Name>, Name>): Bool<Name> {
2639+
return isMember(elem, this);
2640+
}
2641+
subsetOf(b: SMTSet<Name, ElemSort>): Bool<Name> {
2642+
return isSubset(this, b);
2643+
}
2644+
}
2645+
25392646
class QuantifierImpl<
25402647
QVarSorts extends NonEmptySortArray<Name>,
25412648
QSort extends BoolSort<Name> | SMTArraySort<Name, QVarSorts>,
@@ -2917,6 +3024,7 @@ export function createApi(Z3: Z3Core): Z3HighLevel {
29173024
Real,
29183025
BitVec,
29193026
Array,
3027+
Set,
29203028

29213029
////////////////
29223030
// Operations //
@@ -2979,6 +3087,18 @@ export function createApi(Z3: Z3Core): Z3HighLevel {
29793087
// Loading //
29803088
/////////////
29813089
ast_from_string,
3090+
3091+
SetUnion,
3092+
SetIntersect,
3093+
SetDifference,
3094+
SetHasSize,
3095+
SetAdd,
3096+
SetDel,
3097+
SetComplement,
3098+
EmptySet,
3099+
FullSet,
3100+
isMember,
3101+
isSubset,
29823102
};
29833103
cleanup.register(ctx, () => Z3.del_context(contextPtr));
29843104
return ctx;

src/api/js/src/high-level/types.ts

+83
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,8 @@ export interface Context<Name extends string = 'main'> {
357357
readonly BitVec: BitVecCreation<Name>;
358358
/** @category Expressions */
359359
readonly Array: SMTArrayCreation<Name>;
360+
/** @category Expressions */
361+
readonly Set: SMTSetCreation<Name>;
360362

361363
////////////////
362364
// Operations //
@@ -611,6 +613,39 @@ export interface Context<Name extends string = 'main'> {
611613
substitute(t: Expr<Name>, ...substitutions: [Expr<Name>, Expr<Name>][]): Expr<Name>;
612614

613615
simplify(expr: Expr<Name>): Promise<Expr<Name>>;
616+
617+
/** @category Operations */
618+
SetUnion<ElemSort extends AnySort<Name>>(...args: SMTSet<Name, ElemSort>[]): SMTSet<Name, ElemSort>;
619+
620+
/** @category Operations */
621+
SetIntersect<ElemSort extends AnySort<Name>>(...args: SMTSet<Name, ElemSort>[]): SMTSet<Name, ElemSort>;
622+
623+
/** @category Operations */
624+
SetDifference<ElemSort extends AnySort<Name>>(a: SMTSet<Name, ElemSort>, b: SMTSet<Name, ElemSort>): SMTSet<Name, ElemSort>;
625+
626+
/** @category Operations */
627+
SetHasSize<ElemSort extends AnySort<Name>>(set: SMTSet<Name, ElemSort>, size: bigint | number | string | IntNum<Name>): Bool<Name>;
628+
629+
/** @category Operations */
630+
SetAdd<ElemSort extends AnySort<Name>>(set: SMTSet<Name, ElemSort>, elem: CoercibleToMap<SortToExprMap<ElemSort, Name>, Name>): SMTSet<Name, ElemSort>;
631+
632+
/** @category Operations */
633+
SetDel<ElemSort extends AnySort<Name>>(set: SMTSet<Name, ElemSort>, elem: CoercibleToMap<SortToExprMap<ElemSort, Name>, Name>): SMTSet<Name, ElemSort>;
634+
635+
/** @category Operations */
636+
SetComplement<ElemSort extends AnySort<Name>>(set: SMTSet<Name, ElemSort>): SMTSet<Name, ElemSort>;
637+
638+
/** @category Operations */
639+
EmptySet<ElemSort extends AnySort<Name>>(sort: ElemSort): SMTSet<Name, ElemSort>;
640+
641+
/** @category Operations */
642+
FullSet<ElemSort extends AnySort<Name>>(sort: ElemSort): SMTSet<Name, ElemSort>;
643+
644+
/** @category Operations */
645+
isMember<ElemSort extends AnySort<Name>>(elem: CoercibleToMap<SortToExprMap<ElemSort, Name>, Name>, set: SMTSet<Name, ElemSort>): Bool<Name>;
646+
647+
/** @category Operations */
648+
isSubset<ElemSort extends AnySort<Name>>(a: SMTSet<Name, ElemSort>, b: SMTSet<Name, ElemSort>): Bool<Name>;
614649
}
615650

616651
export interface Ast<Name extends string = 'main', Ptr = unknown> {
@@ -1568,6 +1603,54 @@ export interface SMTArray<
15681603
): SMTArray<Name, DomainSort, RangeSort>;
15691604
}
15701605

1606+
/**
1607+
* Set Implemented using Arrays
1608+
*
1609+
* @typeParam ElemSort The sort of the element of the set
1610+
* @category Sets
1611+
*/
1612+
export type SMTSetSort<Name extends string = 'main', ElemSort extends AnySort<Name> = Sort<Name>> = SMTArraySort<Name, [ElemSort], BoolSort<Name>>;
1613+
1614+
1615+
/** @category Sets*/
1616+
export interface SMTSetCreation<Name extends string> {
1617+
sort<ElemSort extends AnySort<Name>>(elemSort: ElemSort): SMTSetSort<Name, ElemSort>;
1618+
1619+
const<ElemSort extends AnySort<Name>>(name: string, elemSort: ElemSort): SMTSet<Name, ElemSort>;
1620+
1621+
consts<ElemSort extends AnySort<Name>>(names: string | string[], elemSort: ElemSort): SMTSet<Name, ElemSort>[];
1622+
1623+
empty<ElemSort extends AnySort<Name>>(sort: ElemSort): SMTSet<Name, ElemSort>;
1624+
1625+
val<ElemSort extends AnySort<Name>>(values: CoercibleToMap<SortToExprMap<ElemSort, Name>, Name>[], sort: ElemSort): SMTSet<Name, ElemSort>;
1626+
}
1627+
1628+
/**
1629+
* Represents Set expression
1630+
*
1631+
* @typeParam ElemSort The sort of the element of the set
1632+
* @category Arrays
1633+
*/
1634+
export interface SMTSet<Name extends string = 'main', ElemSort extends AnySort<Name> = Sort<Name>> extends Expr<Name, SMTSetSort<Name, ElemSort>, Z3_ast> {
1635+
readonly __typename: 'Array';
1636+
1637+
elemSort(): ElemSort;
1638+
1639+
union(...args: SMTSet<Name, ElemSort>[]): SMTSet<Name, ElemSort>;
1640+
intersect(...args: SMTSet<Name, ElemSort>[]): SMTSet<Name, ElemSort>;
1641+
diff(b: SMTSet<Name, ElemSort>): SMTSet<Name, ElemSort>;
1642+
1643+
hasSize(size: bigint | number | string | IntNum<Name>): Bool<Name>;
1644+
1645+
add(elem: CoercibleToMap<SortToExprMap<ElemSort, Name>, Name>): SMTSet<Name, ElemSort>;
1646+
del(elem: CoercibleToMap<SortToExprMap<ElemSort, Name>, Name>): SMTSet<Name, ElemSort>;
1647+
complement(): SMTSet<Name, ElemSort>;
1648+
1649+
contains(elem: CoercibleToMap<SortToExprMap<ElemSort, Name>, Name>): Bool<Name>;
1650+
subsetOf(b: SMTSet<Name, ElemSort>): Bool<Name>;
1651+
1652+
}
1653+
15711654
/**
15721655
* Defines the expression type of the body of a quantifier expression
15731656
*

0 commit comments

Comments
 (0)