diff --git a/src/api/js/src/high-level/high-level.ts b/src/api/js/src/high-level/high-level.ts index f1dd85261..8a2bc9fe7 100644 --- a/src/api/js/src/high-level/high-level.ts +++ b/src/api/js/src/high-level/high-level.ts @@ -45,6 +45,7 @@ import { Z3_goal_prec, Z3_param_descrs, Z3_simplifier, + Z3_rcf_num, } from '../low-level'; import { AnyAst, @@ -93,6 +94,8 @@ import { Quantifier, BodyT, RatNum, + RCFNum, + RCFNumCreation, Seq, SeqSort, Simplifier, @@ -526,6 +529,12 @@ export function createApi(Z3: Z3Core): Z3HighLevel { return isSort(obj) && obj.kind() === Z3_sort_kind.Z3_REAL_SORT; } + function isRCFNum(obj: unknown): obj is RCFNum { + const r = obj instanceof RCFNumImpl; + r && _assertContext(obj); + return r; + } + function isBitVecSort(obj: unknown): obj is BitVecSort { const r = obj instanceof BitVecSortImpl; r && _assertContext(obj); @@ -825,6 +834,29 @@ export function createApi(Z3: Z3Core): Z3HighLevel { return new RatNumImpl(Z3.mk_numeral(contextPtr, value.toString(), Real.sort().ptr)); }, }; + + const RCFNum = Object.assign( + (value: string | number) => new RCFNumImpl(value), + { + pi: () => new RCFNumImpl(check(Z3.rcf_mk_pi(contextPtr))), + + e: () => new RCFNumImpl(check(Z3.rcf_mk_e(contextPtr))), + + infinitesimal: () => new RCFNumImpl(check(Z3.rcf_mk_infinitesimal(contextPtr))), + + roots: (coefficients: RCFNum[]) => { + assert(coefficients.length > 0, 'Polynomial coefficients cannot be empty'); + const coeffPtrs = coefficients.map(c => (c as RCFNumImpl).ptr); + const { rv: numRoots, roots: rootPtrs } = Z3.rcf_mk_roots(contextPtr, coeffPtrs); + const result: RCFNum[] = []; + for (let i = 0; i < numRoots; i++) { + result.push(new RCFNumImpl(rootPtrs[i])); + } + return result; + }, + } + ) as RCFNumCreation; + const BitVec = { sort(bits: Bits): BitVecSort { assert(Number.isSafeInteger(bits), 'number of bits must be an integer'); @@ -3371,6 +3403,112 @@ export function createApi(Z3: Z3Core): Z3HighLevel { } } + class RCFNumImpl implements RCFNum { + declare readonly __typename: RCFNum['__typename']; + readonly ctx: Context; + readonly ptr: Z3_rcf_num; + + constructor(value: string | number); + constructor(ptr: Z3_rcf_num); + constructor(valueOrPtr: string | number | Z3_rcf_num) { + this.ctx = ctx; + if (typeof valueOrPtr === 'string') { + this.ptr = check(Z3.rcf_mk_rational(contextPtr, valueOrPtr)); + } else if (typeof valueOrPtr === 'number') { + this.ptr = check(Z3.rcf_mk_small_int(contextPtr, valueOrPtr)); + } else { + this.ptr = valueOrPtr; + } + cleanup.register(this, () => Z3.rcf_del(contextPtr, this.ptr), this); + } + + add(other: RCFNum): RCFNum { + _assertContext(other); + return new RCFNumImpl(check(Z3.rcf_add(contextPtr, this.ptr, (other as RCFNumImpl).ptr))); + } + + sub(other: RCFNum): RCFNum { + _assertContext(other); + return new RCFNumImpl(check(Z3.rcf_sub(contextPtr, this.ptr, (other as RCFNumImpl).ptr))); + } + + mul(other: RCFNum): RCFNum { + _assertContext(other); + return new RCFNumImpl(check(Z3.rcf_mul(contextPtr, this.ptr, (other as RCFNumImpl).ptr))); + } + + div(other: RCFNum): RCFNum { + _assertContext(other); + return new RCFNumImpl(check(Z3.rcf_div(contextPtr, this.ptr, (other as RCFNumImpl).ptr))); + } + + neg(): RCFNum { + return new RCFNumImpl(check(Z3.rcf_neg(contextPtr, this.ptr))); + } + + inv(): RCFNum { + return new RCFNumImpl(check(Z3.rcf_inv(contextPtr, this.ptr))); + } + + power(k: number): RCFNum { + return new RCFNumImpl(check(Z3.rcf_power(contextPtr, this.ptr, k))); + } + + lt(other: RCFNum): boolean { + _assertContext(other); + return check(Z3.rcf_lt(contextPtr, this.ptr, (other as RCFNumImpl).ptr)); + } + + gt(other: RCFNum): boolean { + _assertContext(other); + return check(Z3.rcf_gt(contextPtr, this.ptr, (other as RCFNumImpl).ptr)); + } + + le(other: RCFNum): boolean { + _assertContext(other); + return check(Z3.rcf_le(contextPtr, this.ptr, (other as RCFNumImpl).ptr)); + } + + ge(other: RCFNum): boolean { + _assertContext(other); + return check(Z3.rcf_ge(contextPtr, this.ptr, (other as RCFNumImpl).ptr)); + } + + eq(other: RCFNum): boolean { + _assertContext(other); + return check(Z3.rcf_eq(contextPtr, this.ptr, (other as RCFNumImpl).ptr)); + } + + neq(other: RCFNum): boolean { + _assertContext(other); + return check(Z3.rcf_neq(contextPtr, this.ptr, (other as RCFNumImpl).ptr)); + } + + isRational(): boolean { + return check(Z3.rcf_is_rational(contextPtr, this.ptr)); + } + + isAlgebraic(): boolean { + return check(Z3.rcf_is_algebraic(contextPtr, this.ptr)); + } + + isInfinitesimal(): boolean { + return check(Z3.rcf_is_infinitesimal(contextPtr, this.ptr)); + } + + isTranscendental(): boolean { + return check(Z3.rcf_is_transcendental(contextPtr, this.ptr)); + } + + toString(compact: boolean = false): string { + return check(Z3.rcf_num_to_string(contextPtr, this.ptr, compact, false)); + } + + toDecimal(precision: number): string { + return check(Z3.rcf_num_to_decimal_string(contextPtr, this.ptr, precision)); + } + } + class BitVecSortImpl extends SortImpl implements BitVecSort { declare readonly __typename: BitVecSort['__typename']; @@ -4520,6 +4658,7 @@ export function createApi(Z3: Z3Core): Z3HighLevel { isReal, isRealVal, isRealSort, + isRCFNum, isBitVecSort, isBitVec, isBitVecVal, // TODO fix ordering @@ -4553,6 +4692,7 @@ export function createApi(Z3: Z3Core): Z3HighLevel { Bool, Int, Real, + RCFNum, BitVec, Float, FloatRM, diff --git a/src/api/js/src/high-level/types.ts b/src/api/js/src/high-level/types.ts index 32d08b6ae..6c3c0aae2 100644 --- a/src/api/js/src/high-level/types.ts +++ b/src/api/js/src/high-level/types.ts @@ -278,6 +278,9 @@ export interface Context { /** @category Functions */ isRealSort(obj: unknown): boolean; + /** @category Functions */ + isRCFNum(obj: unknown): obj is RCFNum; + /** @category Functions */ isBitVecSort(obj: unknown): obj is BitVecSort; @@ -443,6 +446,8 @@ export interface Context { /** @category Expressions */ readonly Real: RealCreation; /** @category Expressions */ + readonly RCFNum: RCFNumCreation; + /** @category Expressions */ readonly BitVec: BitVecCreation; /** @category Expressions */ readonly Float: FPCreation; @@ -1985,6 +1990,212 @@ export interface RatNum extends Arith { asString(): string; } +/** + * A Real Closed Field (RCF) numeral. + * + * RCF numerals can represent: + * - Rational numbers + * - Algebraic numbers (roots of polynomials) + * - Transcendental extensions (e.g., pi, e) + * - Infinitesimal extensions + * + * ```typescript + * const { RCFNum } = Context('main'); + * + * // Create pi + * const pi = RCFNum.pi(); + * console.log(pi.toDecimal(10)); // "3.1415926536" + * + * // Create a rational + * const half = new RCFNum('1/2'); + * + * // Arithmetic + * const sum = pi.add(half); + * + * // Check properties + * console.log(pi.isTranscendental()); // true + * console.log(half.isRational()); // true + * ``` + * @category Arithmetic + */ +export interface RCFNum { + /** @hidden */ + readonly __typename: 'RCFNum'; + + /** @hidden */ + readonly ctx: Context; + + /** + * Add two RCF numerals. + * @param other - The RCF numeral to add + * @returns this + other + */ + add(other: RCFNum): RCFNum; + + /** + * Subtract two RCF numerals. + * @param other - The RCF numeral to subtract + * @returns this - other + */ + sub(other: RCFNum): RCFNum; + + /** + * Multiply two RCF numerals. + * @param other - The RCF numeral to multiply + * @returns this * other + */ + mul(other: RCFNum): RCFNum; + + /** + * Divide two RCF numerals. + * @param other - The RCF numeral to divide by + * @returns this / other + */ + div(other: RCFNum): RCFNum; + + /** + * Negate this RCF numeral. + * @returns -this + */ + neg(): RCFNum; + + /** + * Compute the multiplicative inverse. + * @returns 1/this + */ + inv(): RCFNum; + + /** + * Raise this RCF numeral to a power. + * @param k - The exponent + * @returns this^k + */ + power(k: number): RCFNum; + + /** + * Check if this RCF numeral is less than another. + * @param other - The RCF numeral to compare with + * @returns true if this < other + */ + lt(other: RCFNum): boolean; + + /** + * Check if this RCF numeral is greater than another. + * @param other - The RCF numeral to compare with + * @returns true if this > other + */ + gt(other: RCFNum): boolean; + + /** + * Check if this RCF numeral is less than or equal to another. + * @param other - The RCF numeral to compare with + * @returns true if this <= other + */ + le(other: RCFNum): boolean; + + /** + * Check if this RCF numeral is greater than or equal to another. + * @param other - The RCF numeral to compare with + * @returns true if this >= other + */ + ge(other: RCFNum): boolean; + + /** + * Check if this RCF numeral is equal to another. + * @param other - The RCF numeral to compare with + * @returns true if this == other + */ + eq(other: RCFNum): boolean; + + /** + * Check if this RCF numeral is not equal to another. + * @param other - The RCF numeral to compare with + * @returns true if this != other + */ + neq(other: RCFNum): boolean; + + /** + * Check if this RCF numeral is a rational number. + * @returns true if this is rational + */ + isRational(): boolean; + + /** + * Check if this RCF numeral is an algebraic number. + * @returns true if this is algebraic + */ + isAlgebraic(): boolean; + + /** + * Check if this RCF numeral is an infinitesimal. + * @returns true if this is infinitesimal + */ + isInfinitesimal(): boolean; + + /** + * Check if this RCF numeral is a transcendental number. + * @returns true if this is transcendental + */ + isTranscendental(): boolean; + + /** + * Convert this RCF numeral to a string. + * @param compact - If true, use compact representation + * @returns String representation + */ + toString(compact?: boolean): string; + + /** + * Convert this RCF numeral to a decimal string. + * @param precision - Number of decimal places + * @returns Decimal string representation + */ + toDecimal(precision: number): string; +} + +/** + * Creation interface for RCF numerals + * @category Arithmetic + */ +export interface RCFNumCreation { + /** + * Create an RCF numeral from a rational string. + * @param value - String representation of a rational number (e.g., "3/2", "0.5", "42") + */ + (value: string): RCFNum; + + /** + * Create an RCF numeral from a small integer. + * @param value - Integer value + */ + (value: number): RCFNum; + + /** + * Create an RCF numeral representing pi. + */ + pi(): RCFNum; + + /** + * Create an RCF numeral representing e (Euler's constant). + */ + e(): RCFNum; + + /** + * Create an RCF numeral representing an infinitesimal. + */ + infinitesimal(): RCFNum; + + /** + * Find roots of a polynomial. + * + * The polynomial is a[n-1]*x^(n-1) + ... + a[1]*x + a[0]. + * + * @param coefficients - Polynomial coefficients (constant term first) + * @returns Array of RCF numerals representing the roots + */ + roots(coefficients: RCFNum[]): RCFNum[]; +} + /** * A Sort representing Bit Vector numbers of specified {@link BitVecSort.size size} *