From 31895440507aef1eb16dd8d2ddd205e059e1075d Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 21 Jun 2022 21:05:51 -0700 Subject: [PATCH 001/125] next split Signed-off-by: Nikolaj Bjorner --- src/api/dotnet/UserPropagator.cs | 9 +++++++++ src/api/java/NativeStatic.txt | 1 + 2 files changed, 10 insertions(+) diff --git a/src/api/dotnet/UserPropagator.cs b/src/api/dotnet/UserPropagator.cs index bfd7887dd..0b0e5a57f 100644 --- a/src/api/dotnet/UserPropagator.cs +++ b/src/api/dotnet/UserPropagator.cs @@ -298,6 +298,15 @@ namespace Microsoft.Z3 } } + + /// + /// Set the next decision + /// + public void NextSplit(Expr e, uint idx, Z3_lbool phase) + { + Native.Z3_solver_next_split(ctx.nCtx, this.callback, e.NativeObject, idx, phase); + } + /// /// Track assignments to a term /// diff --git a/src/api/java/NativeStatic.txt b/src/api/java/NativeStatic.txt index 2cd718627..657050951 100644 --- a/src/api/java/NativeStatic.txt +++ b/src/api/java/NativeStatic.txt @@ -76,3 +76,4 @@ DLL_VIS JNIEXPORT void JNICALL Java_com_microsoft_z3_Native_setInternalErrorHand { Z3_set_error_handler((Z3_context)a0, Z3JavaErrorHandler); } + From 8234eeae40ff5f14ce3c2816e8b3a35d6fadd27d Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 22 Jun 2022 09:03:24 -0700 Subject: [PATCH 002/125] unbreak Signed-off-by: Nikolaj Bjorner --- src/api/dotnet/UserPropagator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/dotnet/UserPropagator.cs b/src/api/dotnet/UserPropagator.cs index 0b0e5a57f..273bd4da9 100644 --- a/src/api/dotnet/UserPropagator.cs +++ b/src/api/dotnet/UserPropagator.cs @@ -302,7 +302,7 @@ namespace Microsoft.Z3 /// /// Set the next decision /// - public void NextSplit(Expr e, uint idx, Z3_lbool phase) + public void NextSplit(Expr e, uint idx, int phase) { Native.Z3_solver_next_split(ctx.nCtx, this.callback, e.NativeObject, idx, phase); } From c15a000d9bcfad856da7e8220132cc2be9675e03 Mon Sep 17 00:00:00 2001 From: Kevin Gibbons Date: Wed, 22 Jun 2022 09:26:44 -0700 Subject: [PATCH 003/125] Make high-level JS API more idiomatic/type-safe (#6101) * make JS api more idiomatic * make JS api type-safe by default * use strings, not symbols, for results * add toString * add miracle sudoku example * ints should be ints * add error handling * add missing Cond to Context * fewer side-effecting getters --- .../js/examples/high-level/miracle-sudoku.ts | 206 ++ src/api/js/src/high-level/high-level.test.ts | 72 +- src/api/js/src/high-level/high-level.ts | 3192 +++++++++-------- src/api/js/src/high-level/types.ts | 131 +- src/api/js/src/high-level/utils.test.ts | 24 +- src/api/js/src/high-level/utils.ts | 26 - 6 files changed, 1934 insertions(+), 1717 deletions(-) create mode 100644 src/api/js/examples/high-level/miracle-sudoku.ts diff --git a/src/api/js/examples/high-level/miracle-sudoku.ts b/src/api/js/examples/high-level/miracle-sudoku.ts new file mode 100644 index 000000000..093d599f3 --- /dev/null +++ b/src/api/js/examples/high-level/miracle-sudoku.ts @@ -0,0 +1,206 @@ +import { init } from '../../build/node'; + +import type { Solver, Arith } from '../../build/node'; + +// solve the "miracle sudoku" +// https://www.youtube.com/watch?v=yKf9aUIxdb4 +// most of the interesting stuff is in `solve` +// the process is: +// - parse the board +// - create a Solver +// - create a Z3.Int variable for each square +// - for known cells, add a constraint which says the variable for that cell equals that value +// - add the usual uniqueness constraints +// - add the special "miracle sudoku" constraints +// - call `await solver.check()` +// - if the result is "sat", the board is solvable +// - call `solver.model()` to get a model, i.e. a concrete assignment of variables which satisfies the model +// - for each variable, call `model.evaluate(v)` to recover its value + +function parseSudoku(str: string) { + // derive a list of { row, col, val } records, one for each specified position + // from a string like + // ....1..3. + // ..9..5..8 + // 8.4..6.25 + // ......6.. + // ..8..4... + // 12..87... + // 3..9..2.. + // .65..8... + // 9........ + + let cells = []; + + let lines = str.trim().split('\n'); + if (lines.length !== 9) { + throw new Error(`expected 9 lines, got ${lines.length}`); + } + for (let row = 0; row < 9; ++row) { + let line = lines[row].trim(); + if (line.length !== 9) { + throw new Error(`expected line of length 9, got length ${line.length}`); + } + for (let col = 0; col < 9; ++col) { + let char = line[col]; + if (char === '.') { + continue; + } + if (char < '1' || char > '9') { + throw new Error(`expected digit or '.', got ${char}`); + } + cells.push({ row, col, value: char.codePointAt(0)! - 48 /* '0' */ }); + } + } + return cells; +} + +(async () => { + let { Context, em } = await init(); + + // if you use 'main' as your context name, you won't need to name it in types like Solver + // if you're creating multiple contexts, give them different names + // then the type system will prevent you from mixing them + let Z3 = Context('main'); + + function addSudokuConstraints(solver: Solver, cells: Arith[][]) { + // the usual constraints: + + // every square is between 1 and 9 + for (let row of cells) { + for (let cell of row) { + solver.add(cell.ge(1)); + solver.add(cell.le(9)); + } + } + + // values in each row are unique + for (let row of cells) { + solver.add(Z3.Distinct(...row)); + } + + // values in each column are unique + for (let col = 0; col < 9; ++col) { + solver.add(Z3.Distinct(...cells.map(row => row[col]))); + } + + // values in each 3x3 subdivision are unique + for (let suprow = 0; suprow < 3; ++suprow) { + for (let supcol = 0; supcol < 3; ++supcol) { + let square = []; + for (let row = 0; row < 3; ++row) { + for (let col = 0; col < 3; ++col) { + square.push(cells[suprow * 3 + row][supcol * 3 + col]); + } + } + solver.add(Z3.Distinct(...square)); + } + } + } + + function applyOffsets(x: number, y: number, offsets: [number, number][]) { + let out = []; + for (let offset of offsets) { + let rx = x + offset[0]; + let ry = y + offset[1]; + if (rx >= 0 && rx < 9 && ry >= 0 && ry < 8) { + out.push({ x: rx, y: ry }); + } + } + return out; + } + + function addMiracleConstraints(s: Solver, cells: Arith[][]) { + // the special "miracle sudoku" constraints + + // any two cells separated by a knight's move or a kings move cannot contain the same digit + let knightOffets: [number, number][] = [ + [1, -2], + [2, -1], + [2, 1], + [1, 2], + [-1, 2], + [-2, 1], + [-2, -1], + [-1, -2], + ]; + let kingOffsets: [number, number][] = [ + [1, 1], + [1, -1], + [-1, 1], + [-1, -1], + ]; // skipping immediately adjacent because those are covered by normal sudoku rules + let allOffets = [...knightOffets, ...kingOffsets]; + for (let row = 0; row < 9; ++row) { + for (let col = 0; col < 9; ++col) { + for (let { x, y } of applyOffsets(row, col, allOffets)) { + s.add(cells[row][col].neq(cells[x][y])); + } + } + } + + // any two orthogonally adjacent cells cannot contain consecutive digits + let orthoOffsets: [number, number][] = [ + [0, 1], + [0, -1], + [1, 0], + [-1, 0], + ]; + for (let row = 0; row < 9; ++row) { + for (let col = 0; col < 9; ++col) { + for (let { x, y } of applyOffsets(row, col, orthoOffsets)) { + s.add(cells[row][col].sub(cells[x][y]).neq(1)); + } + } + } + } + + async function solve(str: string) { + let solver = new Z3.Solver(); + let cells = Array.from({ length: 9 }, (_, col) => Array.from({ length: 9 }, (_, row) => Z3.Int.const(`c_${row}_${col}`))); + for (let { row, col, value } of parseSudoku(str)) { + solver.add(cells[row][col].eq(value)); + } + addSudokuConstraints(solver, cells); + addMiracleConstraints(solver, cells); // remove this line to solve normal sudokus + + let start = Date.now(); + console.log('starting... this may take a minute or two'); + let check = await solver.check(); + console.log(`problem was determined to be ${check} in ${Date.now() - start} ms`); + if (check === 'sat') { + let model = solver.model(); + let str = ''; + for (let row = 0; row < 9; ++row) { + for (let col = 0; col < 9; ++col) { + str += model.eval(cells[row][col]).toString() + (col === 8 ? '' : ' '); + if (col === 2 || col === 5) { + str += ' '; + } + } + str += '\n'; + if (row === 2 || row === 5) { + str += '\n'; + } + } + console.log(str); + } + } + + await solve(` +......... +......... +......... +......... +..1...... +......2.. +......... +......... +......... +`); + + em.PThread.terminateAllThreads(); +})().catch(e => { + console.error('error', e); + process.exit(1); +}); 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 9555eea31..9ec7d6eca 100644 --- a/src/api/js/src/high-level/high-level.test.ts +++ b/src/api/js/src/high-level/high-level.test.ts @@ -1,7 +1,7 @@ import assert from 'assert'; import asyncToArray from 'iter-tools/methods/async-to-array'; import { init, killThreads } from '../jest'; -import { Arith, Bool, Model, sat, unsat, Z3AssertionError, Z3HighLevel } from './types'; +import { Arith, Bool, Model, Z3AssertionError, Z3HighLevel } from './types'; /** * Generate all possible solutions from given assumptions. @@ -31,7 +31,7 @@ async function* allSolutions(...assertions: Bool[]): const solver = new assertions[0].ctx.Solver(); solver.add(...assertions); - while ((await solver.check()) === sat) { + while ((await solver.check()) === 'sat') { const model = solver.model(); const decls = model.decls(); if (decls.length === 0) { @@ -59,13 +59,13 @@ async function prove(conjecture: Bool): Promise { const solver = new conjecture.ctx.Solver(); const { Not } = solver.ctx; solver.add(Not(conjecture)); - expect(await solver.check()).toStrictEqual(unsat); + expect(await solver.check()).toStrictEqual('unsat'); } async function solve(conjecture: Bool): Promise { const solver = new conjecture.ctx.Solver(); solver.add(conjecture); - expect(await solver.check()).toStrictEqual(sat); + expect(await solver.check()).toStrictEqual('sat'); return solver.model(); } @@ -96,7 +96,7 @@ describe('high-level', () => { }); it('proves x = y implies g(x) = g(y)', async () => { - const { Solver, Int, Function, Implies, Not } = new api.Context('main'); + const { Solver, Int, Function, Implies, Not } = api.Context('main'); const solver = new Solver(); const sort = Int.sort(); @@ -106,11 +106,11 @@ describe('high-level', () => { const conjecture = Implies(x.eq(y), g.call(x).eq(g.call(y))); solver.add(Not(conjecture)); - expect(await solver.check()).toStrictEqual(unsat); + expect(await solver.check()).toStrictEqual('unsat'); }); it('disproves x = y implies g(g(x)) = g(y)', async () => { - const { Solver, Int, Function, Implies, Not } = new api.Context('main'); + const { Solver, Int, Function, Implies, Not } = api.Context('main'); const solver = new Solver(); const sort = Int.sort(); @@ -119,14 +119,14 @@ describe('high-level', () => { const g = Function.declare('g', sort, sort); const conjecture = Implies(x.eq(y), g.call(g.call(x)).eq(g.call(y))); solver.add(Not(conjecture)); - expect(await solver.check()).toStrictEqual(sat); + expect(await solver.check()).toStrictEqual('sat'); }); it('checks that Context matches', () => { - const c1 = new api.Context('context'); - const c2 = new api.Context('context'); - const c3 = new api.Context('foo'); - const c4 = new api.Context('bar'); + const c1 = api.Context('context'); + const c2 = api.Context('context'); + const c3 = api.Context('foo'); + const c4 = api.Context('bar'); // Contexts with the same name don't do type checking during compile time. // We need to check for different context dynamically @@ -144,7 +144,7 @@ describe('high-level', () => { describe('booleans', () => { it("proves De Morgan's Law", async () => { - const { Bool, Not, And, Eq, Or } = new api.Context('main'); + const { Bool, Not, And, Eq, Or } = api.Context('main'); const [x, y] = [Bool.const('x'), Bool.const('y')]; const conjecture = Eq(Not(And(x, y)), Or(Not(x), Not(y))); @@ -155,7 +155,7 @@ describe('high-level', () => { describe('ints', () => { it('finds a model', async () => { - const { Solver, Int, isIntVal } = new api.Context('main'); + const { Solver, Int, isIntVal } = api.Context('main'); const solver = new Solver(); const x = Int.const('x'); const y = Int.const('y'); @@ -163,10 +163,10 @@ describe('high-level', () => { solver.add(x.ge(1)); // x >= 1 solver.add(y.lt(x.add(3))); // y < x + 3 - expect(await solver.check()).toStrictEqual(sat); + expect(await solver.check()).toStrictEqual('sat'); const model = solver.model(); - expect(model.length).toStrictEqual(2); + expect(model.length()).toStrictEqual(2); for (const decl of model) { expect(decl.arity()).toStrictEqual(0); @@ -175,8 +175,8 @@ describe('high-level', () => { const yValueExpr = model.get(y); assert(isIntVal(xValueExpr)); assert(isIntVal(yValueExpr)); - const xValue = xValueExpr.value; - const yValue = yValueExpr.value; + const xValue = xValueExpr.value(); + const yValue = yValueExpr.value(); assert(typeof xValue === 'bigint'); assert(typeof yValue === 'bigint'); expect(xValue).toBeGreaterThanOrEqual(1n); @@ -225,7 +225,7 @@ describe('high-level', () => { 541972386 `); - const { Solver, Int, Distinct, isIntVal } = new api.Context('main'); + const { Solver, Int, Distinct, isIntVal } = api.Context('main'); const cells: Arith[][] = []; // 9x9 matrix of integer variables @@ -284,7 +284,7 @@ describe('high-level', () => { } } - expect(await solver.check()).toStrictEqual(sat); + expect(await solver.check()).toStrictEqual('sat'); const model = solver.model(); const result = []; @@ -293,7 +293,7 @@ describe('high-level', () => { for (let j = 0; j < 9; j++) { const cell = model.eval(cells[i][j]); assert(isIntVal(cell)); - const value = cell.value; + const value = cell.value(); assert(typeof value === 'bigint'); expect(value).toBeGreaterThanOrEqual(0n); expect(value).toBeLessThanOrEqual(9n); @@ -308,7 +308,7 @@ describe('high-level', () => { describe('reals', () => { it('can work with numerals', async () => { - const { Real, And } = new api.Context('main'); + const { Real, And } = api.Context('main'); const n1 = Real.val('1/2'); const n2 = Real.val('0.5'); const n3 = Real.val(0.5); @@ -322,7 +322,7 @@ describe('high-level', () => { it('can do non-linear arithmetic', async () => { api.setParam('pp.decimal', true); api.setParam('pp.decimal_precision', 20); - const { Real, Solver, isReal, isRealVal } = new api.Context('main'); + const { Real, Solver, isReal, isRealVal } = api.Context('main'); const x = Real.const('x'); const y = Real.const('y'); const z = Real.const('z'); @@ -331,7 +331,7 @@ describe('high-level', () => { solver.add(x.mul(x).add(y.mul(y)).eq(1)); // x^2 + y^2 == 1 solver.add(x.mul(x).mul(x).add(z.mul(z).mul(z)).lt('1/2')); // x^3 + z^3 < 1/2 - expect(await solver.check()).toStrictEqual(sat); + expect(await solver.check()).toStrictEqual('sat'); const model = solver.model(); expect(isRealVal(model.get(x))).toStrictEqual(true); @@ -344,7 +344,7 @@ describe('high-level', () => { describe('bitvectors', () => { it('can do simple proofs', async () => { - const { BitVec, Concat, Implies, isBitVecVal } = new api.Context('main'); + const { BitVec, Concat, Implies, isBitVecVal } = api.Context('main'); const x = BitVec.const('x', 32); @@ -354,7 +354,7 @@ describe('high-level', () => { assert(isBitVecVal(sSol) && isBitVecVal(uSol)); let v = sSol.asSignedValue(); expect(v - 10n <= 0n === v <= 10n).toStrictEqual(true); - v = uSol.value; + v = uSol.value(); expect(v - 10n <= 0n === v <= 10n).toStrictEqual(true); const y = BitVec.const('y', 32); @@ -363,7 +363,7 @@ describe('high-level', () => { }); it('finds x and y such that: x ^ y - 103 == x * y', async () => { - const { BitVec, isBitVecVal } = new api.Context('main'); + const { BitVec, isBitVecVal } = api.Context('main'); const x = BitVec.const('x', 32); const y = BitVec.const('y', 32); @@ -382,28 +382,28 @@ describe('high-level', () => { describe('Solver', () => { it('can use push and pop', async () => { - const { Solver, Int } = new api.Context('main'); + const { Solver, Int } = api.Context('main'); const solver = new Solver(); const x = Int.const('x'); solver.add(x.gt(0)); - expect(await solver.check()).toStrictEqual(sat); + expect(await solver.check()).toStrictEqual('sat'); solver.push(); solver.add(x.lt(0)); expect(solver.numScopes()).toStrictEqual(1); - expect(await solver.check()).toStrictEqual(unsat); + expect(await solver.check()).toStrictEqual('unsat'); solver.pop(); expect(solver.numScopes()).toStrictEqual(0); - expect(await solver.check()).toStrictEqual(sat); + expect(await solver.check()).toStrictEqual('sat'); }); it('can find multiple solutions', async () => { - const { Int, isIntVal } = new api.Context('main'); + const { Int, isIntVal } = api.Context('main'); const x = Int.const('x'); @@ -413,7 +413,7 @@ describe('high-level', () => { .map(solution => { const expr = solution.eval(x); assert(isIntVal(expr)); - return expr.value; + return expr.value(); }) .sort((a, b) => { assert(a !== null && b !== null && typeof a === 'bigint' && typeof b === 'bigint'); @@ -431,7 +431,7 @@ describe('high-level', () => { describe('AstVector', () => { it('can use basic methods', async () => { - const { Solver, AstVector, Int } = new api.Context('main'); + const { Solver, AstVector, Int } = api.Context('main'); const solver = new Solver(); const vector = new AstVector(); @@ -439,12 +439,12 @@ describe('high-level', () => { vector.push(Int.const(`int__${i}`)); } - const length = vector.length; + const length = vector.length(); for (let i = 0; i < length; i++) { solver.add(vector.get(i).gt(1)); } - expect(await solver.check()).toStrictEqual(sat); + expect(await solver.check()).toStrictEqual('sat'); }); }); }); diff --git a/src/api/js/src/high-level/high-level.ts b/src/api/js/src/high-level/high-level.ts index 731e47b07..c68e803f9 100644 --- a/src/api/js/src/high-level/high-level.ts +++ b/src/api/js/src/high-level/high-level.ts @@ -18,6 +18,7 @@ import { Z3_ast_vector, Z3_context, Z3_decl_kind, + Z3_error_code, Z3_func_decl, Z3_func_interp, Z3_lbool, @@ -62,17 +63,14 @@ import { Model, Probe, RatNum, - sat, Solver, Sort, SortToExprMap, Tactic, - unknown, - unsat, Z3Error, Z3HighLevel, } from './types'; -import { allSatisfy, assert, assertExhaustive, autoBind } from './utils'; +import { allSatisfy, assert, assertExhaustive } from './utils'; const FALLBACK_PRECISION = 17; @@ -150,738 +148,112 @@ export function createApi(Z3: Z3Core): Z3HighLevel { return Z3.global_param_get(name); } - function isContext(obj: unknown): obj is Context { - return obj instanceof ContextImpl; - } + function createContext(name: Name, options?: Record): Context { + const cfg = Z3.mk_config(); + if (options != null) { + Object.entries(options).forEach(([key, value]) => check(Z3.set_param_value(cfg, key, value.toString()))); + } + const contextPtr = Z3.mk_context_rc(cfg); + Z3.set_ast_print_mode(contextPtr, Z3_ast_print_mode.Z3_PRINT_SMTLIB2_COMPLIANT); + Z3.del_config(cfg); - class ContextImpl implements Context { - declare readonly __typename: Context['__typename']; - - readonly ptr: Z3_context; - readonly name: string; - - constructor(name: string, params: Record = {}) { - const cfg = Z3.mk_config(); - Object.entries(params).forEach(([key, value]) => Z3.set_param_value(cfg, key, value.toString())); - const context = Z3.mk_context_rc(cfg); - - this.ptr = context; - this.name = name; - - Z3.set_ast_print_mode(this.ptr, Z3_ast_print_mode.Z3_PRINT_SMTLIB2_COMPLIANT); - Z3.del_config(cfg); - - // We want to bind functions and operations to `this` inside Context - // So that the user can write things like this and have it work: - // ``` - // const { And, Or } = new Context('main'); - // ``` - // - // Typescript doesn't handle overloading of method fields, only - // methods. We can't use closures to bind this, so we use auto-bind library - // ``` - // class { - // // This works - // test(a: boolean): boolean; - // test(a: number): number; - // test(a: boolean | number): boolean | number { - // return 0; - // } - // - // // This fails to compile - // test2: (a: boolean) => boolean; - // test2: (a: number) => number; - // test2 = (a: boolean | number): boolean | number => { - // return 0; - // } - // } - // ``` - autoBind(this); - - cleanup.register(this, () => Z3.del_context(context)); + function _assertContext(...ctxs: (Context | { ctx: Context })[]) { + ctxs.forEach(other => assert('ctx' in other ? ctx === other.ctx : ctx === other, 'Context mismatch')); } - /////////////// - // Functions // - /////////////// - interrupt(): void { - Z3.interrupt(this.ptr); - } - - isModel(obj: unknown): obj is Model { - const r = obj instanceof ModelImpl; - r && this._assertContext(obj); - return r; - } - - isAst(obj: unknown): obj is Ast { - const r = obj instanceof AstImpl; - r && this._assertContext(obj); - return r; - } - - isSort(obj: unknown): obj is Sort { - const r = obj instanceof SortImpl; - r && this._assertContext(obj); - return r; - } - - isFuncDecl(obj: unknown): obj is FuncDecl { - const r = obj instanceof FuncDeclImpl; - r && this._assertContext(obj); - return r; - } - - isApp(obj: unknown): boolean { - if (!this.isExpr(obj)) { - return false; - } - const kind = Z3.get_ast_kind(this.ptr, obj.ast); - return kind === Z3_ast_kind.Z3_NUMERAL_AST || kind === Z3_ast_kind.Z3_APP_AST; - } - - isConst(obj: unknown): boolean { - return this.isExpr(obj) && this.isApp(obj) && obj.numArgs() === 0; - } - - isExpr(obj: unknown): obj is Expr { - const r = obj instanceof ExprImpl; - r && this._assertContext(obj); - return r; - } - - isVar(obj: unknown): boolean { - return this.isExpr(obj) && Z3.get_ast_kind(this.ptr, obj.ast) === Z3_ast_kind.Z3_VAR_AST; - } - - isAppOf(obj: unknown, kind: Z3_decl_kind): boolean { - return this.isExpr(obj) && this.isApp(obj) && obj.decl().kind() === kind; - } - - isBool(obj: unknown): obj is Bool { - const r = obj instanceof BoolImpl; - r && this._assertContext(obj); - return r; - } - - isTrue(obj: unknown): boolean { - return this.isAppOf(obj, Z3_decl_kind.Z3_OP_TRUE); - } - - isFalse(obj: unknown): boolean { - return this.isAppOf(obj, Z3_decl_kind.Z3_OP_FALSE); - } - - isAnd(obj: unknown): boolean { - return this.isAppOf(obj, Z3_decl_kind.Z3_OP_AND); - } - - isOr(obj: unknown): boolean { - return this.isAppOf(obj, Z3_decl_kind.Z3_OP_OR); - } - - isImplies(obj: unknown): boolean { - return this.isAppOf(obj, Z3_decl_kind.Z3_OP_IMPLIES); - } - - isNot(obj: unknown): boolean { - return this.isAppOf(obj, Z3_decl_kind.Z3_OP_NOT); - } - - isEq(obj: unknown): boolean { - return this.isAppOf(obj, Z3_decl_kind.Z3_OP_EQ); - } - - isDistinct(obj: unknown): boolean { - return this.isAppOf(obj, Z3_decl_kind.Z3_OP_DISTINCT); - } - - isArith(obj: unknown): obj is Arith { - const r = obj instanceof ArithImpl; - r && this._assertContext(obj); - return r; - } - - isArithSort(obj: unknown): obj is ArithSort { - const r = obj instanceof ArithSortImpl; - r && this._assertContext(obj); - return r; - } - - isInt(obj: unknown): boolean { - return this.isArith(obj) && this.isIntSort(obj.sort); - } - - isIntVal(obj: unknown): obj is IntNum { - const r = obj instanceof IntNumImpl; - r && this._assertContext(obj); - return r; - } - - isIntSort(obj: unknown): boolean { - return this.isSort(obj) && obj.kind() === Z3_sort_kind.Z3_INT_SORT; - } - - isReal(obj: unknown): boolean { - return this.isArith(obj) && this.isRealSort(obj.sort); - } - - isRealVal(obj: unknown): obj is RatNum { - const r = obj instanceof RatNumImpl; - r && this._assertContext(obj); - return r; - } - - isRealSort(obj: unknown): boolean { - return this.isSort(obj) && obj.kind() === Z3_sort_kind.Z3_REAL_SORT; - } - - isBitVecSort(obj: unknown): obj is BitVecSort { - const r = obj instanceof BitVecSortImpl; - r && this._assertContext(obj); - return r; - } - - isBitVec(obj: unknown): obj is BitVec { - const r = obj instanceof BitVecImpl; - r && this._assertContext(obj); - return r; - } - - isBitVecVal(obj: unknown): obj is BitVecNum { - const r = obj instanceof BitVecNumImpl; - r && this._assertContext(obj); - return r; - } - - isProbe(obj: unknown): obj is Probe { - const r = obj instanceof ProbeImpl; - r && this._assertContext(obj); - return r; - } - - isTactic(obj: unknown): obj is Tactic { - const r = obj instanceof TacticImpl; - r && this._assertContext(obj); - return r; - } - - isAstVector(obj: unknown): obj is AstVector { - const r = obj instanceof AstVectorImpl; - r && this._assertContext(obj); - return r; - } - - eqIdentity(a: Ast, b: Ast): boolean { - return a.eqIdentity(b); - } - - getVarIndex(obj: Expr): number { - assert(this.isVar(obj), 'Z3 bound variable expected'); - return Z3.get_index_value(this.ptr, obj.ast); - } - - from(primitive: boolean): Bool; - from(primitive: number | CoercibleRational): RatNum; - from(primitive: bigint): IntNum; - from(expr: T): T; - from(expr: CoercibleToExpr): AnyExpr; - from(value: CoercibleToExpr): AnyExpr { - if (typeof value === 'boolean') { - return this.Bool.val(value); - } else if (typeof value === 'number' || isCoercibleRational(value)) { - return this.Real.val(value); - } else if (typeof value === 'bigint') { - return this.Int.val(value); - } else if (this.isExpr(value)) { - return value; - } - assert(false); - } - - async solve(...assertions: Bool[]): Promise { - const solver = new this.Solver(); - solver.add(...assertions); - const result = await solver.check(); - if (result === sat) { - return solver.model(); - } - return result; - } - - ///////////// - // Classes // - ///////////// - readonly Solver = SolverImpl.bind(SolverImpl, this); - readonly Model = ModelImpl.bind(ModelImpl, this); - readonly Tactic = TacticImpl.bind(TacticImpl, this); - readonly AstVector = AstVectorImpl.bind(AstVectorImpl, this) as AstVectorCtor; - readonly AstMap = AstMapImpl.bind(AstMapImpl, this) as AstMapCtor; - - ///////////// - // Objects // - ///////////// - readonly Sort = { - declare: (name: string) => new SortImpl(this, Z3.mk_uninterpreted_sort(this.ptr, this._toSymbol(name))), - }; - readonly Function = { - declare: (name: string, ...signature: FuncDeclSignature) => { - const arity = signature.length - 1; - const rng = signature[arity]; - this._assertContext(rng); - const dom = []; - for (let i = 0; i < arity; i++) { - this._assertContext(signature[i]); - dom.push(signature[i].ptr); - } - return new FuncDeclImpl(this, Z3.mk_func_decl(this.ptr, this._toSymbol(name), dom, rng.ptr)); - }, - fresh: (...signature: FuncDeclSignature) => { - const arity = signature.length - 1; - const rng = signature[arity]; - this._assertContext(rng); - const dom = []; - for (let i = 0; i < arity; i++) { - this._assertContext(signature[i]); - dom.push(signature[i].ptr); - } - return new FuncDeclImpl(this, Z3.mk_fresh_func_decl(this.ptr, 'f', dom, rng.ptr)); - }, - }; - readonly RecFunc = { - declare: (name: string, ...signature: FuncDeclSignature) => { - const arity = signature.length - 1; - const rng = signature[arity]; - this._assertContext(rng); - const dom = []; - for (let i = 0; i < arity; i++) { - this._assertContext(signature[i]); - dom.push(signature[i].ptr); - } - return new FuncDeclImpl(this, Z3.mk_rec_func_decl(this.ptr, this._toSymbol(name), dom, rng.ptr)); - }, - - addDefinition: (f: FuncDecl, args: Expr[], body: Expr) => { - this._assertContext(f, ...args, body); - Z3.add_rec_def( - this.ptr, - f.ptr, - args.map(arg => arg.ast), - body.ast, - ); - }, - }; - readonly Bool = { - sort: () => new BoolSortImpl(this, Z3.mk_bool_sort(this.ptr)), - - const: (name: string) => new BoolImpl(this, Z3.mk_const(this.ptr, this._toSymbol(name), this.Bool.sort().ptr)), - consts: (names: string | string[]) => { - if (typeof names === 'string') { - names = names.split(' '); - } - return names.map(name => this.Bool.const(name)); - }, - vector: (prefix: string, count: number) => { - const result = []; - for (let i = 0; i < count; i++) { - result.push(this.Bool.const(`${prefix}__${i}`)); - } - return result; - }, - fresh: (prefix = 'b') => new BoolImpl(this, Z3.mk_fresh_const(this.ptr, prefix, this.Bool.sort().ptr)), - - val: (value: boolean) => { - if (value) { - return new BoolImpl(this, Z3.mk_true(this.ptr)); - } - return new BoolImpl(this, Z3.mk_false(this.ptr)); - }, - }; - readonly Int = { - sort: () => new ArithSortImpl(this, Z3.mk_int_sort(this.ptr)), - - const: (name: string) => new ArithImpl(this, Z3.mk_const(this.ptr, this._toSymbol(name), this.Int.sort().ptr)), - consts: (names: string | string[]) => { - if (typeof names === 'string') { - names = names.split(' '); - } - return names.map(name => this.Int.const(name)); - }, - vector: (prefix: string, count: number) => { - const result = []; - for (let i = 0; i < count; i++) { - result.push(this.Int.const(`${prefix}__${i}`)); - } - return result; - }, - fresh: (prefix = 'x') => new ArithImpl(this, Z3.mk_fresh_const(this.ptr, prefix, this.Int.sort().ptr)), - - val: (value: number | bigint | string) => { - assert(typeof value === 'bigint' || typeof value === 'string' || Number.isSafeInteger(value)); - return new IntNumImpl(this, Z3.mk_numeral(this.ptr, value.toString(), this.Int.sort().ptr)); - }, - }; - readonly Real = { - sort: () => new ArithSortImpl(this, Z3.mk_real_sort(this.ptr)), - - const: (name: string) => new ArithImpl(this, Z3.mk_const(this.ptr, this._toSymbol(name), this.Real.sort().ptr)), - consts: (names: string | string[]) => { - if (typeof names === 'string') { - names = names.split(' '); - } - return names.map(name => this.Real.const(name)); - }, - vector: (prefix: string, count: number) => { - const result = []; - for (let i = 0; i < count; i++) { - result.push(this.Real.const(`${prefix}__${i}`)); - } - return result; - }, - fresh: (prefix = 'b') => new ArithImpl(this, Z3.mk_fresh_const(this.ptr, prefix, this.Real.sort().ptr)), - - val: (value: number | bigint | string | CoercibleRational) => { - if (isCoercibleRational(value)) { - value = `${value.numerator}/${value.denominator}`; - } - return new RatNumImpl(this, Z3.mk_numeral(this.ptr, value.toString(), this.Real.sort().ptr)); - }, - }; - readonly BitVec = { - sort: (bits: number): BitVecSort => { - assert(Number.isSafeInteger(bits), 'number of bits must be an integer'); - return new BitVecSortImpl(this, Z3.mk_bv_sort(this.ptr, bits)); - }, - - const: (name: string, bits: number | BitVecSort): BitVec => - new BitVecImpl( - this, - Z3.mk_const(this.ptr, this._toSymbol(name), this.isBitVecSort(bits) ? bits.ptr : this.BitVec.sort(bits).ptr), - ), - - consts: (names: string | string[], bits: number | BitVecSort): BitVec[] => { - if (typeof names === 'string') { - names = names.split(' '); - } - return names.map(name => this.BitVec.const(name, bits)); - }, - - val: (value: bigint | number | boolean, bits: number | BitVecSort): BitVecNum => { - if (value === true) { - return this.BitVec.val(1, bits); - } else if (value === false) { - return this.BitVec.val(0, bits); - } - return new BitVecNumImpl( - this, - Z3.mk_numeral(this.ptr, value.toString(), this.isBitVecSort(bits) ? bits.ptr : this.BitVec.sort(bits).ptr), - ); - }, - }; - - //////////////// - // Operations // - //////////////// - If(condition: Probe, onTrue: Tactic, onFalse: Tactic): Tactic; - If( - condition: Bool | boolean, - onTrue: OnTrueRef, - onFalse: OnFalseRef, - ): CoercibleToExprMap; - If( - condition: Bool | Probe | boolean, - onTrue: CoercibleToExpr | Tactic, - onFalse: CoercibleToExpr | Tactic, - ): Expr | Tactic { - if (this.isProbe(condition) && this.isTactic(onTrue) && this.isTactic(onFalse)) { - return this.Cond(condition, onTrue, onFalse); - } - assert( - !this.isProbe(condition) && !this.isTactic(onTrue) && !this.isTactic(onFalse), - 'Mixed expressions and goals', - ); - if (typeof condition === 'boolean') { - condition = this.Bool.val(condition); - } - onTrue = this.from(onTrue); - onFalse = this.from(onFalse); - return this._toExpr(Z3.mk_ite(this.ptr, condition.ptr, onTrue.ast, onFalse.ast)); - } - - Distinct(...exprs: CoercibleToExpr[]): Bool { - assert(exprs.length > 0, "Can't make Distinct ouf of nothing"); - - return new BoolImpl( - this, - Z3.mk_distinct( - this.ptr, - exprs.map(expr => { - expr = this.from(expr); - this._assertContext(expr); - return expr.ast; - }), - ), - ); - } - - Const(name: string, sort: S): SortToExprMap { - this._assertContext(sort); - return this._toExpr(Z3.mk_const(this.ptr, this._toSymbol(name), sort.ptr)) as SortToExprMap; - } - - Consts(names: string | string[], sort: S): SortToExprMap[] { - this._assertContext(sort); - if (typeof names === 'string') { - names = names.split(' '); - } - return names.map(name => this.Const(name, sort)); - } - - FreshConst(sort: S, prefix: string = 'c'): SortToExprMap { - this._assertContext(sort); - return this._toExpr(Z3.mk_fresh_const(sort.ctx.ptr, prefix, sort.ptr)) as SortToExprMap; - } - - Var(idx: number, sort: S): SortToExprMap { - this._assertContext(sort); - return this._toExpr(Z3.mk_bound(sort.ctx.ptr, idx, sort.ptr)) as SortToExprMap; - } - - Implies(a: Bool | boolean, b: Bool | boolean): Bool { - a = this.from(a) as Bool; - b = this.from(b) as Bool; - this._assertContext(a, b); - return new BoolImpl(this, Z3.mk_implies(this.ptr, a.ptr, b.ptr)); - } - - Eq(a: CoercibleToExpr, b: CoercibleToExpr): Bool { - a = this.from(a); - b = this.from(b); - this._assertContext(a, b); - return a.eq(b); - } - - Xor(a: Bool | boolean, b: Bool | boolean): Bool { - a = this.from(a) as Bool; - b = this.from(b) as Bool; - this._assertContext(a, b); - return new BoolImpl(this, Z3.mk_xor(this.ptr, a.ptr, b.ptr)); - } - - Not(a: Probe): Probe; - Not(a: Bool | boolean): Bool; - Not(a: Bool | boolean | Probe): Bool | Probe { - if (typeof a === 'boolean') { - a = this.from(a); - } - this._assertContext(a); - if (this.isProbe(a)) { - return new ProbeImpl(this, Z3.probe_not(this.ptr, a.ptr)); - } - return new BoolImpl(this, Z3.mk_not(this.ptr, a.ptr)); - } - - And(): Bool; - And(vector: AstVector): Bool; - And(...args: (Bool | boolean)[]): Bool; - And(...args: Probe[]): Probe; - And(...args: (AstVector | Probe | Bool | boolean)[]): Bool | Probe { - if (args.length == 1 && args[0] instanceof this.AstVector) { - args = [...args[0].values()]; - assert(allSatisfy(args, this.isBool.bind(this)) ?? true, 'AstVector containing not bools'); - } - - const allProbes = allSatisfy(args, this.isProbe.bind(this)) ?? false; - if (allProbes) { - return this._probeNary(Z3.probe_and, args as [Probe, ...Probe[]]); - } else { - args = args.map(this.from.bind(this)) as Bool[]; - this._assertContext(...(args as Bool[])); - return new BoolImpl( - this, - Z3.mk_and( - this.ptr, - args.map(arg => (arg as Bool).ptr), - ), - ); + // call this after every nontrivial call to the underlying API + function throwIfError() { + if (Z3.get_error_code(contextPtr) !== Z3_error_code.Z3_OK) { + throw new Error(Z3.get_error_msg(ctx.ptr, Z3.get_error_code(ctx.ptr))); } } - Or(): Bool; - Or(vector: AstVector): Bool; - Or(...args: (Bool | boolean)[]): Bool; - Or(...args: Probe[]): Probe; - Or(...args: (AstVector | Probe | Bool | boolean)[]): Bool | Probe { - if (args.length == 1 && args[0] instanceof this.AstVector) { - args = [...args[0].values()]; - assert(allSatisfy(args, this.isBool.bind(this)) ?? true, 'AstVector containing not bools'); - } - - const allProbes = allSatisfy(args, this.isProbe.bind(this)) ?? false; - if (allProbes) { - return this._probeNary(Z3.probe_or, args as [Probe, ...Probe[]]); - } else { - args = args.map(this.from.bind(this)) as Bool[]; - this._assertContext(...(args as Bool[])); - return new BoolImpl( - this, - Z3.mk_or( - this.ptr, - args.map(arg => (arg as Bool).ptr), - ), - ); - } - } - - ToReal(expr: Arith | bigint): Arith { - expr = this.from(expr) as Arith; - this._assertContext(expr); - assert(this.isInt(expr), 'Int expression expected'); - return new ArithImpl(this, Z3.mk_int2real(this.ptr, expr.ast)); - } - - ToInt(expr: Arith | number | CoercibleRational | string): Arith { - if (!this.isExpr(expr)) { - expr = this.Real.val(expr); - } - this._assertContext(expr); - assert(this.isReal(expr), 'Real expression expected'); - return new ArithImpl(this, Z3.mk_real2int(this.ptr, expr.ast)); - } - - IsInt(expr: Arith | number | CoercibleRational | string): Bool { - if (!this.isExpr(expr)) { - expr = this.Real.val(expr); - } - this._assertContext(expr); - assert(this.isReal(expr), 'Real expression expected'); - return new BoolImpl(this, Z3.mk_is_int(this.ptr, expr.ast)); - } - - Sqrt(a: Arith | number | bigint | string | CoercibleRational): Arith { - if (!this.isExpr(a)) { - a = this.Real.val(a); - } - return a.pow('1/2'); - } - - Cbrt(a: Arith | number | bigint | string | CoercibleRational): Arith { - if (!this.isExpr(a)) { - a = this.Real.val(a); - } - return a.pow('1/3'); - } - - BV2Int(a: BitVec, isSigned: boolean): Arith { - this._assertContext(a); - return new ArithImpl(this, Z3.mk_bv2int(this.ptr, a.ast, isSigned)); - } - - Int2BV(a: Arith | bigint | number, bits: number): BitVec { - if (this.isArith(a)) { - assert(this.isInt(a), 'parameter must be an integer'); - } else { - assert(typeof a !== 'number' || Number.isSafeInteger(a), 'parameter must not have decimal places'); - a = this.Int.val(a); - } - return new BitVecImpl(this, Z3.mk_int2bv(this.ptr, bits, a.ast)); - } - - Concat(...bitvecs: BitVec[]): BitVec { - this._assertContext(...bitvecs); - return bitvecs.reduce((prev, curr) => new BitVecImpl(this, Z3.mk_concat(this.ptr, prev.ast, curr.ast))); - } - - Cond(probe: Probe, onTrue: Tactic, onFalse: Tactic): Tactic { - this._assertContext(probe, onTrue, onFalse); - return new this.Tactic(Z3.tactic_cond(this.ptr, probe.ptr, onTrue.ptr, onFalse.ptr)); + function check(val: T) { + throwIfError(); + return val; } ///////////// // Private // ///////////// - _assertContext(...ctxs: (Context | { ctx: Context })[]) { - ctxs.forEach(other => assert('ctx' in other ? this === other.ctx : this === other, 'Context mismatch')); - } - - _toSymbol(s: string | number) { + function _toSymbol(s: string | number) { if (typeof s === 'number') { - return Z3.mk_int_symbol(this.ptr, s); + return check(Z3.mk_int_symbol(contextPtr, s)); } else { - return Z3.mk_string_symbol(this.ptr, s); + return check(Z3.mk_string_symbol(contextPtr, s)); } } - _fromSymbol(sym: Z3_symbol) { - const kind = Z3.get_symbol_kind(this.ptr, sym); + function _fromSymbol(sym: Z3_symbol) { + const kind = check(Z3.get_symbol_kind(contextPtr, sym)); switch (kind) { case Z3_symbol_kind.Z3_INT_SYMBOL: - return Z3.get_symbol_int(this.ptr, sym); + return Z3.get_symbol_int(contextPtr, sym); case Z3_symbol_kind.Z3_STRING_SYMBOL: - return Z3.get_symbol_string(this.ptr, sym); + return Z3.get_symbol_string(contextPtr, sym); default: assertExhaustive(kind); } } - _toAst(ast: Z3_ast): AnyAst { - switch (Z3.get_ast_kind(this.ptr, ast)) { + function _toAst(ast: Z3_ast): AnyAst { + switch (check(Z3.get_ast_kind(contextPtr, ast))) { case Z3_ast_kind.Z3_SORT_AST: - return this._toSort(ast as Z3_sort); + return _toSort(ast as Z3_sort); case Z3_ast_kind.Z3_FUNC_DECL_AST: - return new FuncDeclImpl(this, ast as Z3_func_decl); + return new FuncDeclImpl(ast as Z3_func_decl); default: - return this._toExpr(ast); + return _toExpr(ast); } } - _toSort(ast: Z3_sort): AnySort { - switch (Z3.get_sort_kind(this.ptr, ast)) { + function _toSort(ast: Z3_sort): AnySort { + switch (check(Z3.get_sort_kind(contextPtr, ast))) { case Z3_sort_kind.Z3_BOOL_SORT: - return new BoolSortImpl(this, ast); + return new BoolSortImpl(ast); case Z3_sort_kind.Z3_INT_SORT: case Z3_sort_kind.Z3_REAL_SORT: - return new ArithSortImpl(this, ast); + return new ArithSortImpl(ast); case Z3_sort_kind.Z3_BV_SORT: - return new BitVecSortImpl(this, ast); + return new BitVecSortImpl(ast); default: - return new SortImpl(this, ast); + return new SortImpl(ast); } } - _toExpr(ast: Z3_ast): Bool | IntNum | RatNum | Arith | Expr { - const kind = Z3.get_ast_kind(this.ptr, ast); + function _toExpr(ast: Z3_ast): Bool | IntNum | RatNum | Arith | Expr { + const kind = check(Z3.get_ast_kind(contextPtr, ast)); if (kind === Z3_ast_kind.Z3_QUANTIFIER_AST) { assert(false); } - const sortKind = Z3.get_sort_kind(this.ptr, Z3.get_sort(this.ptr, ast)); + const sortKind = check(Z3.get_sort_kind(contextPtr, Z3.get_sort(contextPtr, ast))); switch (sortKind) { case Z3_sort_kind.Z3_BOOL_SORT: - return new BoolImpl(this, ast); + return new BoolImpl(ast); case Z3_sort_kind.Z3_INT_SORT: if (kind === Z3_ast_kind.Z3_NUMERAL_AST) { - return new IntNumImpl(this, ast); + return new IntNumImpl(ast); } - return new ArithImpl(this, ast); + return new ArithImpl(ast); case Z3_sort_kind.Z3_REAL_SORT: if (kind === Z3_ast_kind.Z3_NUMERAL_AST) { - return new RatNumImpl(this, ast); + return new RatNumImpl(ast); } - return new ArithImpl(this, ast); + return new ArithImpl(ast); case Z3_sort_kind.Z3_BV_SORT: if (kind === Z3_ast_kind.Z3_NUMERAL_AST) { - return new BitVecNumImpl(this, ast); + return new BitVecNumImpl(ast); } - return new BitVecImpl(this, ast); + return new BitVecImpl(ast); default: - return new ExprImpl(this, ast); + return new ExprImpl(ast); } } - _flattenArgs(args: (T | AstVector)[]): T[] { + function _flattenArgs = AnyAst>(args: (T | AstVector)[]): T[] { const result: T[] = []; for (const arg of args) { - if (this.isAstVector(arg)) { + if (isAstVector(arg)) { result.push(...arg.values()); } else { result.push(arg); @@ -890,984 +262,1693 @@ export function createApi(Z3: Z3Core): Z3HighLevel { return result; } - _toProbe(p: Probe | Z3_probe): Probe { - if (this.isProbe(p)) { + function _toProbe(p: Probe | Z3_probe): Probe { + if (isProbe(p)) { return p; } - return new ProbeImpl(this, p); + return new ProbeImpl(p); } - _probeNary( + function _probeNary( f: (ctx: Z3_context, left: Z3_probe, right: Z3_probe) => Z3_probe, - args: [Probe | Z3_probe, ...(Probe | Z3_probe)[]], + args: [Probe | Z3_probe, ...(Probe | Z3_probe)[]], ) { assert(args.length > 0, 'At least one argument expected'); - let r = this._toProbe(args[0]); + let r = _toProbe(args[0]); for (let i = 1; i < args.length; i++) { - r = new ProbeImpl(this, f(this.ptr, r.ptr, this._toProbe(args[i]).ptr)); + r = new ProbeImpl(check(f(contextPtr, r.ptr, _toProbe(args[i]).ptr))); } return r; } - } - class AstImpl implements Ast { - declare readonly __typename: Ast['__typename']; - - constructor(readonly ctx: ContextImpl, readonly ptr: Ptr) { - const myAst = this.ast; - - Z3.inc_ref(ctx.ptr, myAst); - cleanup.register(this, () => Z3.dec_ref(ctx.ptr, myAst)); + /////////////// + // Functions // + /////////////// + function interrupt(): void { + check(Z3.interrupt(contextPtr)); } - get ast(): Z3_ast { - return this.ptr as any as Z3_ast; + function isModel(obj: unknown): obj is Model { + const r = obj instanceof ModelImpl; + r && _assertContext(obj); + return r; } - get id() { - return Z3.get_ast_id(this.ctx.ptr, this.ast); + function isAst(obj: unknown): obj is Ast { + const r = obj instanceof AstImpl; + r && _assertContext(obj); + return r; } - eqIdentity(other: Ast) { - this.ctx._assertContext(other); - return Z3.is_eq_ast(this.ctx.ptr, this.ast, other.ast); + function isSort(obj: unknown): obj is Sort { + const r = obj instanceof SortImpl; + r && _assertContext(obj); + return r; } - neqIdentity(other: Ast) { - this.ctx._assertContext(other); - return !this.eqIdentity(other); + function isFuncDecl(obj: unknown): obj is FuncDecl { + const r = obj instanceof FuncDeclImpl; + r && _assertContext(obj); + return r; } - sexpr() { - return Z3.ast_to_string(this.ctx.ptr, this.ast); - } - - hash() { - return Z3.get_ast_hash(this.ctx.ptr, this.ast); - } - } - - class SolverImpl implements Solver { - declare readonly __typename: Solver['__typename']; - - readonly ptr: Z3_solver; - - constructor(readonly ctx: ContextImpl, ptr: Z3_solver | string = Z3.mk_solver(ctx.ptr)) { - let myPtr: Z3_solver; - if (typeof ptr === 'string') { - myPtr = Z3.mk_solver_for_logic(ctx.ptr, ctx._toSymbol(ptr)); - } else { - myPtr = ptr; + function isApp(obj: unknown): boolean { + if (!isExpr(obj)) { + return false; } - this.ptr = myPtr; - Z3.solver_inc_ref(ctx.ptr, myPtr); - cleanup.register(this, () => Z3.solver_dec_ref(ctx.ptr, myPtr)); + const kind = check(Z3.get_ast_kind(contextPtr, obj.ast)); + return kind === Z3_ast_kind.Z3_NUMERAL_AST || kind === Z3_ast_kind.Z3_APP_AST; } - push() { - Z3.solver_push(this.ctx.ptr, this.ptr); + function isConst(obj: unknown): boolean { + return isExpr(obj) && isApp(obj) && obj.numArgs() === 0; } - pop(num: number = 1) { - Z3.solver_pop(this.ctx.ptr, this.ptr, num); + + function isExpr(obj: unknown): obj is Expr { + const r = obj instanceof ExprImpl; + r && _assertContext(obj); + return r; } - numScopes() { - return Z3.solver_get_num_scopes(this.ctx.ptr, this.ptr); + + function isVar(obj: unknown): boolean { + return isExpr(obj) && check(Z3.get_ast_kind(contextPtr, obj.ast)) === Z3_ast_kind.Z3_VAR_AST; } - reset() { - Z3.solver_reset(this.ctx.ptr, this.ptr); + + function isAppOf(obj: unknown, kind: Z3_decl_kind): boolean { + return isExpr(obj) && isApp(obj) && obj.decl().kind() === kind; } - add(...exprs: (Bool | AstVector)[]) { - this.ctx._flattenArgs(exprs).forEach(expr => { - this.ctx._assertContext(expr); - Z3.solver_assert(this.ctx.ptr, this.ptr, expr.ast); - }); + + function isBool(obj: unknown): obj is Bool { + const r = obj instanceof BoolImpl; + r && _assertContext(obj); + return r; } - addAndTrack(expr: Bool, constant: Bool | string) { - if (typeof constant === 'string') { - constant = this.ctx.Bool.const(constant); + + function isTrue(obj: unknown): boolean { + return isAppOf(obj, Z3_decl_kind.Z3_OP_TRUE); + } + + function isFalse(obj: unknown): boolean { + return isAppOf(obj, Z3_decl_kind.Z3_OP_FALSE); + } + + function isAnd(obj: unknown): boolean { + return isAppOf(obj, Z3_decl_kind.Z3_OP_AND); + } + + function isOr(obj: unknown): boolean { + return isAppOf(obj, Z3_decl_kind.Z3_OP_OR); + } + + function isImplies(obj: unknown): boolean { + return isAppOf(obj, Z3_decl_kind.Z3_OP_IMPLIES); + } + + function isNot(obj: unknown): boolean { + return isAppOf(obj, Z3_decl_kind.Z3_OP_NOT); + } + + function isEq(obj: unknown): boolean { + return isAppOf(obj, Z3_decl_kind.Z3_OP_EQ); + } + + function isDistinct(obj: unknown): boolean { + return isAppOf(obj, Z3_decl_kind.Z3_OP_DISTINCT); + } + + function isArith(obj: unknown): obj is Arith { + const r = obj instanceof ArithImpl; + r && _assertContext(obj); + return r; + } + + function isArithSort(obj: unknown): obj is ArithSort { + const r = obj instanceof ArithSortImpl; + r && _assertContext(obj); + return r; + } + + function isInt(obj: unknown): boolean { + return isArith(obj) && isIntSort(obj.sort); + } + + function isIntVal(obj: unknown): obj is IntNum { + const r = obj instanceof IntNumImpl; + r && _assertContext(obj); + return r; + } + + function isIntSort(obj: unknown): boolean { + return isSort(obj) && obj.kind() === Z3_sort_kind.Z3_INT_SORT; + } + + function isReal(obj: unknown): boolean { + return isArith(obj) && isRealSort(obj.sort); + } + + function isRealVal(obj: unknown): obj is RatNum { + const r = obj instanceof RatNumImpl; + r && _assertContext(obj); + return r; + } + + function isRealSort(obj: unknown): boolean { + return isSort(obj) && obj.kind() === Z3_sort_kind.Z3_REAL_SORT; + } + + function isBitVecSort(obj: unknown): obj is BitVecSort { + const r = obj instanceof BitVecSortImpl; + r && _assertContext(obj); + return r; + } + + function isBitVec(obj: unknown): obj is BitVec { + const r = obj instanceof BitVecImpl; + r && _assertContext(obj); + return r; + } + + function isBitVecVal(obj: unknown): obj is BitVecNum { + const r = obj instanceof BitVecNumImpl; + r && _assertContext(obj); + return r; + } + + function isProbe(obj: unknown): obj is Probe { + const r = obj instanceof ProbeImpl; + r && _assertContext(obj); + return r; + } + + function isTactic(obj: unknown): obj is Tactic { + const r = obj instanceof TacticImpl; + r && _assertContext(obj); + return r; + } + + function isAstVector(obj: unknown): obj is AstVector { + const r = obj instanceof AstVectorImpl; + r && _assertContext(obj); + return r; + } + + function eqIdentity(a: Ast, b: Ast): boolean { + return a.eqIdentity(b); + } + + function getVarIndex(obj: Expr): number { + assert(isVar(obj), 'Z3 bound variable expected'); + return Z3.get_index_value(contextPtr, obj.ast); + } + + function from(primitive: boolean): Bool; + function from(primitive: number | CoercibleRational): RatNum; + function from(primitive: bigint): IntNum; + function from>(expr: T): T; + function from(expr: CoercibleToExpr): AnyExpr; + function from(value: CoercibleToExpr): AnyExpr { + if (typeof value === 'boolean') { + return Bool.val(value); + } else if (typeof value === 'number') { + if (!Number.isFinite(value)) { + throw new Error(`cannot represent infinity/NaN (got ${value})`); + } + if (Math.floor(value) === value) { + return Int.val(value); + } + return Real.val(value); + } else if (isCoercibleRational(value)) { + return Real.val(value); + } else if (typeof value === 'bigint') { + return Int.val(value); + } else if (isExpr(value)) { + return value; } - assert(this.ctx.isConst(constant), 'Provided expression that is not a constant to addAndTrack'); - Z3.solver_assert_and_track(this.ctx.ptr, this.ptr, expr.ast, constant.ast); + assert(false); } - assertions(): AstVector { - return new AstVectorImpl(this.ctx, Z3.solver_get_assertions(this.ctx.ptr, this.ptr)); + async function solve(...assertions: Bool[]): Promise | 'unsat' | 'unknown'> { + const solver = new ctx.Solver(); + solver.add(...assertions); + const result = await solver.check(); + if (result === 'sat') { + return solver.model(); + } + return result; } - async check(...exprs: (Bool | AstVector)[]): Promise { - const assumptions = this.ctx._flattenArgs(exprs).map(expr => { - this.ctx._assertContext(expr); - return expr.ast; - }); - const result = await asyncMutex.runExclusive(() => - Z3.solver_check_assumptions(this.ctx.ptr, this.ptr, assumptions), + ///////////// + // Objects // + ///////////// + const Sort = { + declare: (name: string) => new SortImpl(Z3.mk_uninterpreted_sort(contextPtr, _toSymbol(name))), + }; + const Function = { + declare: (name: string, ...signature: FuncDeclSignature) => { + const arity = signature.length - 1; + const rng = signature[arity]; + _assertContext(rng); + const dom = []; + for (let i = 0; i < arity; i++) { + _assertContext(signature[i]); + dom.push(signature[i].ptr); + } + return new FuncDeclImpl(Z3.mk_func_decl(contextPtr, _toSymbol(name), dom, rng.ptr)); + }, + fresh: (...signature: FuncDeclSignature) => { + const arity = signature.length - 1; + const rng = signature[arity]; + _assertContext(rng); + const dom = []; + for (let i = 0; i < arity; i++) { + _assertContext(signature[i]); + dom.push(signature[i].ptr); + } + return new FuncDeclImpl(Z3.mk_fresh_func_decl(contextPtr, 'f', dom, rng.ptr)); + }, + }; + const RecFunc = { + declare: (name: string, ...signature: FuncDeclSignature) => { + const arity = signature.length - 1; + const rng = signature[arity]; + _assertContext(rng); + const dom = []; + for (let i = 0; i < arity; i++) { + _assertContext(signature[i]); + dom.push(signature[i].ptr); + } + return new FuncDeclImpl(Z3.mk_rec_func_decl(contextPtr, _toSymbol(name), dom, rng.ptr)); + }, + + addDefinition: (f: FuncDecl, args: Expr[], body: Expr) => { + _assertContext(f, ...args, body); + check( + Z3.add_rec_def( + contextPtr, + f.ptr, + args.map(arg => arg.ast), + body.ast, + ), + ); + }, + }; + const Bool = { + sort: () => new BoolSortImpl(Z3.mk_bool_sort(contextPtr)), + + const: (name: string) => new BoolImpl(Z3.mk_const(contextPtr, _toSymbol(name), Bool.sort().ptr)), + consts: (names: string | string[]) => { + if (typeof names === 'string') { + names = names.split(' '); + } + return names.map(name => Bool.const(name)); + }, + vector: (prefix: string, count: number) => { + const result = []; + for (let i = 0; i < count; i++) { + result.push(Bool.const(`${prefix}__${i}`)); + } + return result; + }, + fresh: (prefix = 'b') => new BoolImpl(Z3.mk_fresh_const(contextPtr, prefix, Bool.sort().ptr)), + + val: (value: boolean) => { + if (value) { + return new BoolImpl(Z3.mk_true(contextPtr)); + } + return new BoolImpl(Z3.mk_false(contextPtr)); + }, + }; + const Int = { + sort: () => new ArithSortImpl(Z3.mk_int_sort(contextPtr)), + + const: (name: string) => new ArithImpl(Z3.mk_const(contextPtr, _toSymbol(name), Int.sort().ptr)), + consts: (names: string | string[]) => { + if (typeof names === 'string') { + names = names.split(' '); + } + return names.map(name => Int.const(name)); + }, + vector: (prefix: string, count: number) => { + const result = []; + for (let i = 0; i < count; i++) { + result.push(Int.const(`${prefix}__${i}`)); + } + return result; + }, + fresh: (prefix = 'x') => new ArithImpl(Z3.mk_fresh_const(contextPtr, prefix, Int.sort().ptr)), + + val: (value: number | bigint | string) => { + assert(typeof value === 'bigint' || typeof value === 'string' || Number.isSafeInteger(value)); + return new IntNumImpl(check(Z3.mk_numeral(contextPtr, value.toString(), Int.sort().ptr))); + }, + }; + const Real = { + sort: () => new ArithSortImpl(Z3.mk_real_sort(contextPtr)), + + const: (name: string) => new ArithImpl(check(Z3.mk_const(contextPtr, _toSymbol(name), Real.sort().ptr))), + consts: (names: string | string[]) => { + if (typeof names === 'string') { + names = names.split(' '); + } + return names.map(name => Real.const(name)); + }, + vector: (prefix: string, count: number) => { + const result = []; + for (let i = 0; i < count; i++) { + result.push(Real.const(`${prefix}__${i}`)); + } + return result; + }, + fresh: (prefix = 'b') => new ArithImpl(Z3.mk_fresh_const(contextPtr, prefix, Real.sort().ptr)), + + val: (value: number | bigint | string | CoercibleRational) => { + if (isCoercibleRational(value)) { + value = `${value.numerator}/${value.denominator}`; + } + return new RatNumImpl(Z3.mk_numeral(contextPtr, value.toString(), Real.sort().ptr)); + }, + }; + const BitVec = { + sort(bits: Bits): BitVecSort { + assert(Number.isSafeInteger(bits), 'number of bits must be an integer'); + return new BitVecSortImpl(Z3.mk_bv_sort(contextPtr, bits)); + }, + + const(name: string, bits: Bits | BitVecSort): BitVec { + return new BitVecImpl( + check(Z3.mk_const(contextPtr, _toSymbol(name), isBitVecSort(bits) ? bits.ptr : BitVec.sort(bits).ptr)), + ); + }, + + consts(names: string | string[], bits: Bits | BitVecSort): BitVec[] { + if (typeof names === 'string') { + names = names.split(' '); + } + return names.map(name => BitVec.const(name, bits)); + }, + + val( + value: bigint | number | boolean, + bits: Bits | BitVecSort, + ): BitVecNum { + if (value === true) { + return BitVec.val(1, bits); + } else if (value === false) { + return BitVec.val(0, bits); + } + return new BitVecNumImpl( + check(Z3.mk_numeral(contextPtr, value.toString(), isBitVecSort(bits) ? bits.ptr : BitVec.sort(bits).ptr)), + ); + }, + }; + + //////////////// + // Operations // + //////////////// + function If(condition: Probe, onTrue: Tactic, onFalse: Tactic): Tactic; + function If, OnFalseRef extends CoercibleToExpr>( + condition: Bool | boolean, + onTrue: OnTrueRef, + onFalse: OnFalseRef, + ): CoercibleToExprMap; + function If( + condition: Bool | Probe | boolean, + onTrue: CoercibleToExpr | Tactic, + onFalse: CoercibleToExpr | Tactic, + ): Expr | Tactic { + if (isProbe(condition) && isTactic(onTrue) && isTactic(onFalse)) { + return Cond(condition, onTrue, onFalse); + } + assert(!isProbe(condition) && !isTactic(onTrue) && !isTactic(onFalse), 'Mixed expressions and goals'); + if (typeof condition === 'boolean') { + condition = Bool.val(condition); + } + onTrue = from(onTrue); + onFalse = from(onFalse); + return _toExpr(check(Z3.mk_ite(contextPtr, condition.ptr, onTrue.ast, onFalse.ast))); + } + + function Distinct(...exprs: CoercibleToExpr[]): Bool { + assert(exprs.length > 0, "Can't make Distinct ouf of nothing"); + + return new BoolImpl( + check( + Z3.mk_distinct( + contextPtr, + exprs.map(expr => { + expr = from(expr); + _assertContext(expr); + return expr.ast; + }), + ), + ), ); - switch (result) { - case Z3_lbool.Z3_L_FALSE: - return unsat; - case Z3_lbool.Z3_L_TRUE: - return sat; - case Z3_lbool.Z3_L_UNDEF: - return unknown; - default: - assertExhaustive(result); + } + + function Const>(name: string, sort: S): SortToExprMap { + _assertContext(sort); + return _toExpr(check(Z3.mk_const(contextPtr, _toSymbol(name), sort.ptr))) as SortToExprMap; + } + + function Consts>(names: string | string[], sort: S): SortToExprMap[] { + _assertContext(sort); + if (typeof names === 'string') { + names = names.split(' '); + } + return names.map(name => Const(name, sort)); + } + + function FreshConst>(sort: S, prefix: string = 'c'): SortToExprMap { + _assertContext(sort); + return _toExpr(Z3.mk_fresh_const(sort.ctx.ptr, prefix, sort.ptr)) as SortToExprMap; + } + + function Var>(idx: number, sort: S): SortToExprMap { + _assertContext(sort); + return _toExpr(Z3.mk_bound(sort.ctx.ptr, idx, sort.ptr)) as SortToExprMap; + } + + function Implies(a: Bool | boolean, b: Bool | boolean): Bool { + a = from(a) as Bool; + b = from(b) as Bool; + _assertContext(a, b); + return new BoolImpl(check(Z3.mk_implies(contextPtr, a.ptr, b.ptr))); + } + + function Eq(a: CoercibleToExpr, b: CoercibleToExpr): Bool { + a = from(a); + b = from(b); + _assertContext(a, b); + return a.eq(b); + } + + function Xor(a: Bool | boolean, b: Bool | boolean): Bool { + a = from(a) as Bool; + b = from(b) as Bool; + _assertContext(a, b); + return new BoolImpl(check(Z3.mk_xor(contextPtr, a.ptr, b.ptr))); + } + + function Not(a: Probe): Probe; + function Not(a: Bool | boolean): Bool; + function Not(a: Bool | boolean | Probe): Bool | Probe { + if (typeof a === 'boolean') { + a = from(a); + } + _assertContext(a); + if (isProbe(a)) { + return new ProbeImpl(check(Z3.probe_not(contextPtr, a.ptr))); + } + return new BoolImpl(check(Z3.mk_not(contextPtr, a.ptr))); + } + + function And(): Bool; + function And(vector: AstVector>): Bool; + function And(...args: (Bool | boolean)[]): Bool; + function And(...args: Probe[]): Probe; + function And( + ...args: (AstVector> | Probe | Bool | boolean)[] + ): Bool | Probe { + if (args.length == 1 && args[0] instanceof ctx.AstVector) { + args = [...args[0].values()]; + assert(allSatisfy(args, isBool) ?? true, 'AstVector containing not bools'); + } + + const allProbes = allSatisfy(args, isProbe) ?? false; + if (allProbes) { + return _probeNary(Z3.probe_and, args as [Probe, ...Probe[]]); + } else { + const castArgs = args.map(from) as Bool[]; + _assertContext(...castArgs); + return new BoolImpl( + check( + Z3.mk_and( + contextPtr, + castArgs.map(arg => arg.ptr), + ), + ), + ); } } - model() { - return new this.ctx.Model(Z3.solver_get_model(this.ctx.ptr, this.ptr)); - } - } + function Or(): Bool; + function Or(vector: AstVector>): Bool; + function Or(...args: (Bool | boolean)[]): Bool; + function Or(...args: Probe[]): Probe; + function Or( + ...args: (AstVector> | Probe | Bool | boolean)[] + ): Bool | Probe { + if (args.length == 1 && args[0] instanceof ctx.AstVector) { + args = [...args[0].values()]; + assert(allSatisfy(args, isBool) ?? true, 'AstVector containing not bools'); + } - class ModelImpl implements Model { - declare readonly __typename: Model['__typename']; - - constructor(readonly ctx: ContextImpl, readonly ptr: Z3_model = Z3.mk_model(ctx.ptr)) { - Z3.model_inc_ref(ctx.ptr, ptr); - cleanup.register(this, () => Z3.model_dec_ref(ctx.ptr, ptr)); - } - - get length() { - return Z3.model_get_num_consts(this.ctx.ptr, this.ptr) + Z3.model_get_num_funcs(this.ctx.ptr, this.ptr); - } - - [Symbol.iterator](): Iterator { - return this.values(); - } - - *entries(): IterableIterator<[number, FuncDecl]> { - const length = this.length; - for (let i = 0; i < length; i++) { - yield [i, this.get(i)]; + const allProbes = allSatisfy(args, isProbe) ?? false; + if (allProbes) { + return _probeNary(Z3.probe_or, args as [Probe, ...Probe[]]); + } else { + const castArgs = args.map(from) as Bool[]; + _assertContext(...castArgs); + return new BoolImpl( + check( + Z3.mk_or( + contextPtr, + castArgs.map(arg => arg.ptr), + ), + ), + ); } } - *keys(): IterableIterator { - for (const [key] of this.entries()) { - yield key; + function ToReal(expr: Arith | bigint): Arith { + expr = from(expr) as Arith; + _assertContext(expr); + assert(isInt(expr), 'Int expression expected'); + return new ArithImpl(check(Z3.mk_int2real(contextPtr, expr.ast))); + } + + function ToInt(expr: Arith | number | CoercibleRational | string): Arith { + if (!isExpr(expr)) { + expr = Real.val(expr); + } + _assertContext(expr); + assert(isReal(expr), 'Real expression expected'); + return new ArithImpl(check(Z3.mk_real2int(contextPtr, expr.ast))); + } + + function IsInt(expr: Arith | number | CoercibleRational | string): Bool { + if (!isExpr(expr)) { + expr = Real.val(expr); + } + _assertContext(expr); + assert(isReal(expr), 'Real expression expected'); + return new BoolImpl(check(Z3.mk_is_int(contextPtr, expr.ast))); + } + + function Sqrt(a: Arith | number | bigint | string | CoercibleRational): Arith { + if (!isExpr(a)) { + a = Real.val(a); + } + return a.pow('1/2'); + } + + function Cbrt(a: Arith | number | bigint | string | CoercibleRational): Arith { + if (!isExpr(a)) { + a = Real.val(a); + } + return a.pow('1/3'); + } + + function BV2Int(a: BitVec, isSigned: boolean): Arith { + _assertContext(a); + return new ArithImpl(check(Z3.mk_bv2int(contextPtr, a.ast, isSigned))); + } + + function Int2BV(a: Arith | bigint | number, bits: Bits): BitVec { + if (isArith(a)) { + assert(isInt(a), 'parameter must be an integer'); + } else { + assert(typeof a !== 'number' || Number.isSafeInteger(a), 'parameter must not have decimal places'); + a = Int.val(a); + } + return new BitVecImpl(check(Z3.mk_int2bv(contextPtr, bits, a.ast))); + } + + function Concat(...bitvecs: BitVec[]): BitVec { + _assertContext(...bitvecs); + return bitvecs.reduce((prev, curr) => new BitVecImpl(check(Z3.mk_concat(contextPtr, prev.ast, curr.ast)))); + } + + function Cond(probe: Probe, onTrue: Tactic, onFalse: Tactic): Tactic { + _assertContext(probe, onTrue, onFalse); + return new TacticImpl(check(Z3.tactic_cond(contextPtr, probe.ptr, onTrue.ptr, onFalse.ptr))); + } + + class AstImpl implements Ast { + declare readonly __typename: Ast['__typename']; + readonly ctx: Context; + + constructor(readonly ptr: Ptr) { + this.ctx = ctx; + const myAst = this.ast; + + Z3.inc_ref(contextPtr, myAst); + cleanup.register(this, () => Z3.dec_ref(contextPtr, myAst)); + } + + get ast(): Z3_ast { + return this.ptr; + } + + id() { + return Z3.get_ast_id(contextPtr, this.ast); + } + + eqIdentity(other: Ast) { + _assertContext(other); + return check(Z3.is_eq_ast(contextPtr, this.ast, other.ast)); + } + + neqIdentity(other: Ast) { + _assertContext(other); + return !this.eqIdentity(other); + } + + sexpr() { + return Z3.ast_to_string(contextPtr, this.ast); + } + + hash() { + return Z3.get_ast_hash(contextPtr, this.ast); + } + + toString() { + return this.sexpr(); } } - *values(): IterableIterator { - for (const [, value] of this.entries()) { - yield value; + class SolverImpl implements Solver { + declare readonly __typename: Solver['__typename']; + + readonly ptr: Z3_solver; + readonly ctx: Context; + constructor(ptr: Z3_solver | string = Z3.mk_solver(contextPtr)) { + this.ctx = ctx; + let myPtr: Z3_solver; + if (typeof ptr === 'string') { + myPtr = check(Z3.mk_solver_for_logic(contextPtr, _toSymbol(ptr))); + } else { + myPtr = ptr; + } + this.ptr = myPtr; + Z3.solver_inc_ref(contextPtr, myPtr); + cleanup.register(this, () => Z3.solver_dec_ref(contextPtr, myPtr)); + } + + push() { + Z3.solver_push(contextPtr, this.ptr); + } + pop(num: number = 1) { + Z3.solver_pop(contextPtr, this.ptr, num); + } + numScopes() { + return Z3.solver_get_num_scopes(contextPtr, this.ptr); + } + reset() { + Z3.solver_reset(contextPtr, this.ptr); + } + add(...exprs: (Bool | AstVector>)[]) { + _flattenArgs(exprs).forEach(expr => { + _assertContext(expr); + check(Z3.solver_assert(contextPtr, this.ptr, expr.ast)); + }); + } + addAndTrack(expr: Bool, constant: Bool | string) { + if (typeof constant === 'string') { + constant = Bool.const(constant); + } + assert(isConst(constant), 'Provided expression that is not a constant to addAndTrack'); + check(Z3.solver_assert_and_track(contextPtr, this.ptr, expr.ast, constant.ast)); + } + + assertions(): AstVector> { + return new AstVectorImpl(check(Z3.solver_get_assertions(contextPtr, this.ptr))); + } + + async check(...exprs: (Bool | AstVector>)[]): Promise { + const assumptions = _flattenArgs(exprs).map(expr => { + _assertContext(expr); + return expr.ast; + }); + const result = await asyncMutex.runExclusive(() => + check(Z3.solver_check_assumptions(contextPtr, this.ptr, assumptions)), + ); + switch (result) { + case Z3_lbool.Z3_L_FALSE: + return 'unsat'; + case Z3_lbool.Z3_L_TRUE: + return 'sat'; + case Z3_lbool.Z3_L_UNDEF: + return 'unknown'; + default: + assertExhaustive(result); + } + } + + model() { + return new ModelImpl(check(Z3.solver_get_model(contextPtr, this.ptr))); + } + + toString() { + return check(Z3.solver_to_string(contextPtr, this.ptr)); } } - decls() { - return [...this.values()]; - } + class ModelImpl implements Model { + declare readonly __typename: Model['__typename']; + readonly ctx: Context; - sexpr() { - return Z3.model_to_string(this.ctx.ptr, this.ptr); - } - - eval(expr: Bool, modelCompletion?: boolean): Bool; - eval(expr: Arith, modelCompletion?: boolean): Arith; - eval(expr: Expr, modelCompletion: boolean = false) { - this.ctx._assertContext(expr); - const r = Z3.model_eval(this.ctx.ptr, this.ptr, expr.ast, modelCompletion); - if (r === null) { - throw new Z3Error('Failed to evaluatio expression in the model'); + constructor(readonly ptr: Z3_model = Z3.mk_model(contextPtr)) { + this.ctx = ctx; + Z3.model_inc_ref(contextPtr, ptr); + cleanup.register(this, () => Z3.model_dec_ref(contextPtr, ptr)); + } + + length() { + return Z3.model_get_num_consts(contextPtr, this.ptr) + Z3.model_get_num_funcs(contextPtr, this.ptr); + } + + [Symbol.iterator](): Iterator> { + return this.values(); + } + + *entries(): IterableIterator<[number, FuncDecl]> { + const length = this.length(); + for (let i = 0; i < length; i++) { + yield [i, this.get(i)]; + } + } + + *keys(): IterableIterator { + for (const [key] of this.entries()) { + yield key; + } + } + + *values(): IterableIterator> { + for (const [, value] of this.entries()) { + yield value; + } + } + + decls() { + return [...this.values()]; + } + + sexpr() { + return check(Z3.model_to_string(contextPtr, this.ptr)); + } + + eval(expr: Bool, modelCompletion?: boolean): Bool; + eval(expr: Arith, modelCompletion?: boolean): Arith; + eval(expr: Expr, modelCompletion: boolean = false) { + _assertContext(expr); + const r = check(Z3.model_eval(contextPtr, this.ptr, expr.ast, modelCompletion)); + if (r === null) { + throw new Z3Error('Failed to evaluatio expression in the model'); + } + return _toExpr(r); + } + + get(i: number): FuncDecl; + get(from: number, to: number): FuncDecl[]; + get(declaration: FuncDecl): FuncInterp | Expr; + get(constant: Expr): Expr; + get(sort: Sort): AstVector>; + get( + i: number | FuncDecl | Expr | Sort, + to?: number, + ): FuncDecl | FuncInterp | Expr | AstVector> | FuncDecl[] { + assert(to === undefined || typeof i === 'number'); + if (typeof i === 'number') { + const length = this.length(); + + if (i >= length) { + throw new RangeError(`expected index ${i} to be less than length ${length}`); + } + + if (to === undefined) { + const numConsts = check(Z3.model_get_num_consts(contextPtr, this.ptr)); + if (i < numConsts) { + return new FuncDeclImpl(check(Z3.model_get_const_decl(contextPtr, this.ptr, i))); + } else { + return new FuncDeclImpl(check(Z3.model_get_func_decl(contextPtr, this.ptr, i - numConsts))); + } + } + + if (to < 0) { + to += length; + } + if (to >= length) { + throw new RangeError(`expected index ${to} to be less than length ${length}`); + } + const result = []; + for (let j = i; j < to; j++) { + result.push(this.get(j)); + } + return result; + } else if (isFuncDecl(i) || (isExpr(i) && isConst(i))) { + const result = this.getInterp(i); + assert(result !== null); + return result; + } else if (isSort(i)) { + return this.getUniverse(i); + } + assert(false, 'Number, declaration or constant expected'); + } + + private getInterp(expr: FuncDecl | Expr): Expr | FuncInterp | null { + assert(isFuncDecl(expr) || isConst(expr), 'Declaration expected'); + if (isConst(expr)) { + assert(isExpr(expr)); + expr = expr.decl(); + } + assert(isFuncDecl(expr)); + if (expr.arity() === 0) { + const result = check(Z3.model_get_const_interp(contextPtr, this.ptr, expr.ptr)); + if (result === null) { + return null; + } + return _toExpr(result); + } else { + const interp = check(Z3.model_get_func_interp(contextPtr, this.ptr, expr.ptr)); + if (interp === null) { + return null; + } + return new FuncInterpImpl(interp); + } + } + + private getUniverse(sort: Sort): AstVector> { + _assertContext(sort); + return new AstVectorImpl(check(Z3.model_get_sort_universe(contextPtr, this.ptr, sort.ptr))); } - return this.ctx._toExpr(r); } - get(i: number): FuncDecl; - get(from: number, to: number): FuncDecl[]; - get(declaration: FuncDecl): FuncInterp | Expr; - get(constant: Expr): Expr; - get(sort: Sort): AstVector; - get( - i: number | FuncDecl | Expr | Sort, - to?: number, - ): FuncDecl | FuncInterp | Expr | AstVector | FuncDecl[] { - assert(to === undefined || typeof i === 'number'); - if (typeof i === 'number') { - const length = this.length; + class FuncInterpImpl implements FuncInterp { + declare readonly __typename: FuncInterp['__typename']; + readonly ctx: Context; - if (i >= length) { - throw new RangeError(); + constructor(readonly ptr: Z3_func_interp) { + this.ctx = ctx; + Z3.func_interp_inc_ref(contextPtr, ptr); + cleanup.register(this, () => Z3.func_interp_dec_ref(contextPtr, ptr)); + } + } + + class SortImpl extends AstImpl implements Sort { + declare readonly __typename: Sort['__typename']; + + get ast(): Z3_ast { + return Z3.sort_to_ast(contextPtr, this.ptr); + } + + kind() { + return Z3.get_sort_kind(contextPtr, this.ptr); + } + + subsort(other: Sort) { + _assertContext(other); + return false; + } + + cast(expr: Expr): Expr { + _assertContext(expr); + assert(expr.sort.eqIdentity(expr.sort), 'Sort mismatch'); + return expr; + } + + name() { + return _fromSymbol(Z3.get_sort_name(contextPtr, this.ptr)); + } + + eqIdentity(other: Sort) { + _assertContext(other); + return check(Z3.is_eq_sort(contextPtr, this.ptr, other.ptr)); + } + + neqIdentity(other: Sort) { + return !this.eqIdentity(other); + } + } + + class FuncDeclImpl extends AstImpl implements FuncDecl { + declare readonly __typename: FuncDecl['__typename']; + + get ast() { + return Z3.func_decl_to_ast(contextPtr, this.ptr); + } + + name() { + return _fromSymbol(Z3.get_decl_name(contextPtr, this.ptr)); + } + + arity() { + return Z3.get_arity(contextPtr, this.ptr); + } + + domain(i: number) { + assert(i < this.arity(), 'Index out of bounds'); + return _toSort(Z3.get_domain(contextPtr, this.ptr, i)); + } + + range() { + return _toSort(Z3.get_range(contextPtr, this.ptr)); + } + + kind() { + return Z3.get_decl_kind(contextPtr, this.ptr); + } + + params(): (number | string | Z3_symbol | Sort | Expr | FuncDecl)[] { + const n = Z3.get_decl_num_parameters(contextPtr, this.ptr); + const result = []; + for (let i = 0; i < n; i++) { + const kind = check(Z3.get_decl_parameter_kind(contextPtr, this.ptr, i)); + switch (kind) { + case Z3_parameter_kind.Z3_PARAMETER_INT: + result.push(check(Z3.get_decl_int_parameter(contextPtr, this.ptr, i))); + break; + case Z3_parameter_kind.Z3_PARAMETER_DOUBLE: + result.push(check(Z3.get_decl_double_parameter(contextPtr, this.ptr, i))); + break; + case Z3_parameter_kind.Z3_PARAMETER_RATIONAL: + result.push(check(Z3.get_decl_rational_parameter(contextPtr, this.ptr, i))); + break; + case Z3_parameter_kind.Z3_PARAMETER_SYMBOL: + result.push(check(Z3.get_decl_symbol_parameter(contextPtr, this.ptr, i))); + break; + case Z3_parameter_kind.Z3_PARAMETER_SORT: + result.push(new SortImpl(check(Z3.get_decl_sort_parameter(contextPtr, this.ptr, i)))); + break; + case Z3_parameter_kind.Z3_PARAMETER_AST: + result.push(new ExprImpl(check(Z3.get_decl_ast_parameter(contextPtr, this.ptr, i)))); + break; + case Z3_parameter_kind.Z3_PARAMETER_FUNC_DECL: + result.push(new FuncDeclImpl(check(Z3.get_decl_func_decl_parameter(contextPtr, this.ptr, i)))); + break; + default: + assertExhaustive(kind); + } + } + return result; + } + + call(...args: CoercibleToExpr[]) { + assert(args.length === this.arity(), `Incorrect number of arguments to ${this}`); + return _toExpr( + check(Z3.mk_app( + contextPtr, + this.ptr, + args.map((arg, i) => { + return this.domain(i).cast(arg).ast; + }), + )), + ); + } + } + + class ExprImpl = AnySort> extends AstImpl implements Expr { + declare readonly __typename: Expr['__typename']; + + get sort(): S { + return _toSort(Z3.get_sort(contextPtr, this.ast)) as S; + } + + eq(other: CoercibleToExpr): Bool { + return new BoolImpl(check(Z3.mk_eq(contextPtr, this.ast, from(other).ast))); + } + + neq(other: CoercibleToExpr): Bool { + return new BoolImpl( + check(Z3.mk_distinct( + contextPtr, + [this, other].map(expr => from(expr).ast), + )), + ); + } + + params() { + return this.decl().params(); + } + + decl(): FuncDecl { + assert(isApp(this), 'Z3 application expected'); + return new FuncDeclImpl(check(Z3.get_app_decl(contextPtr, check(Z3.to_app(contextPtr, this.ast))))); + } + + numArgs(): number { + assert(isApp(this), 'Z3 applicaiton expected'); + return check(Z3.get_app_num_args(contextPtr, check(Z3.to_app(contextPtr, this.ast)))); + } + + arg(i: number): ReturnType { + assert(isApp(this), 'Z3 applicaiton expected'); + assert(i < this.numArgs(), `Invalid argument index - expected ${i} to be less than ${this.numArgs()}`); + return _toExpr(check(Z3.get_app_arg(contextPtr, check(Z3.to_app(contextPtr, this.ast)), i))); + } + + children(): ReturnType[] { + const num_args = this.numArgs(); + if (isApp(this)) { + const result = []; + for (let i = 0; i < num_args; i++) { + result.push(this.arg(i)); + } + return result; + } + return []; + } + } + + class BoolSortImpl extends SortImpl implements BoolSort { + declare readonly __typename: BoolSort['__typename']; + + cast(other: Bool | boolean): Bool; + cast(other: CoercibleToExpr): never; + cast(other: CoercibleToExpr | Bool) { + if (typeof other === 'boolean') { + other = Bool.val(other); + } + assert(isExpr(other), 'true, false or Z3 Boolean expression expected.'); + assert(this.eqIdentity(other.sort), 'Value cannot be converted into a Z3 Boolean value'); + return other; + } + + subsort(other: Sort) { + _assertContext(other.ctx); + return other instanceof ArithSortImpl; + } + } + + class BoolImpl extends ExprImpl> implements Bool { + declare readonly __typename: Bool['__typename']; + + not(): Bool { + return Not(this); + } + and(other: Bool | boolean): Bool { + return And(this, other); + } + or(other: Bool | boolean): Bool { + return Or(this, other); + } + xor(other: Bool | boolean): Bool { + return Xor(this, other); + } + } + + class ProbeImpl implements Probe { + declare readonly __typename: Probe['__typename']; + readonly ctx: Context; + + constructor(readonly ptr: Z3_probe) { + this.ctx = ctx; + } + } + + class TacticImpl implements Tactic { + declare readonly __typename: Tactic['__typename']; + + readonly ptr: Z3_tactic; + readonly ctx: Context; + + constructor(tactic: string | Z3_tactic) { + this.ctx = ctx; + let myPtr: Z3_tactic; + if (typeof tactic === 'string') { + myPtr = check(Z3.mk_tactic(contextPtr, tactic)); + } else { + myPtr = tactic; + } + + this.ptr = myPtr; + + Z3.tactic_inc_ref(contextPtr, myPtr); + cleanup.register(this, () => Z3.tactic_dec_ref(contextPtr, myPtr)); + } + } + + class ArithSortImpl extends SortImpl implements ArithSort { + declare readonly __typename: ArithSort['__typename']; + + cast(other: bigint | number): IntNum | RatNum; + cast(other: CoercibleRational | RatNum): RatNum; + cast(other: IntNum): IntNum; + cast(other: Bool | Arith): Arith; + cast(other: CoercibleToExpr): never; + cast(other: CoercibleToExpr): Arith | RatNum | IntNum { + const sortTypeStr = isIntSort(this) ? 'IntSort' : 'RealSort'; + if (isExpr(other)) { + const otherS = other.sort; + if (isArith(other)) { + if (this.eqIdentity(otherS)) { + return other; + } else if (isIntSort(otherS) && isRealSort(this)) { + return ToReal(other); + } + assert(false, "Can't cast Real to IntSort without loss"); + } else if (isBool(other)) { + if (isIntSort(this)) { + return If(other, 1, 0); + } else { + return ToReal(If(other, 1, 0)); + } + } + assert(false, `Can't cast expression to ${sortTypeStr}`); + } else { + if (typeof other !== 'boolean') { + if (isIntSort(this)) { + assert(!isCoercibleRational(other), "Can't cast fraction to IntSort"); + return Int.val(other); + } + return Real.val(other); + } + assert(false, `Can't cast primitive to ${sortTypeStr}`); + } + } + } + + class ArithImpl extends ExprImpl> implements Arith { + declare readonly __typename: Arith['__typename']; + + add(other: Arith | number | bigint | string | CoercibleRational) { + return new ArithImpl(check(Z3.mk_add(contextPtr, [this.ast, this.sort.cast(other).ast]))); + } + + mul(other: Arith | number | bigint | string | CoercibleRational) { + return new ArithImpl(check(Z3.mk_mul(contextPtr, [this.ast, this.sort.cast(other).ast]))); + } + + sub(other: Arith | number | bigint | string | CoercibleRational) { + return new ArithImpl(check(Z3.mk_sub(contextPtr, [this.ast, this.sort.cast(other).ast]))); + } + + pow(exponent: Arith | number | bigint | string | CoercibleRational) { + return new ArithImpl(check(Z3.mk_power(contextPtr, this.ast, this.sort.cast(exponent).ast))); + } + + div(other: Arith | number | bigint | string | CoercibleRational) { + return new ArithImpl(check(Z3.mk_div(contextPtr, this.ast, this.sort.cast(other).ast))); + } + + mod(other: Arith | number | bigint | string | CoercibleRational) { + return new ArithImpl(check(Z3.mk_mod(contextPtr, this.ast, this.sort.cast(other).ast))); + } + + neg() { + return new ArithImpl(check(Z3.mk_unary_minus(contextPtr, this.ast))); + } + + le(other: Arith | number | bigint | string | CoercibleRational) { + return new BoolImpl(check(Z3.mk_le(contextPtr, this.ast, this.sort.cast(other).ast))); + } + + lt(other: Arith | number | bigint | string | CoercibleRational) { + return new BoolImpl(check(Z3.mk_lt(contextPtr, this.ast, this.sort.cast(other).ast))); + } + + gt(other: Arith | number | bigint | string | CoercibleRational) { + return new BoolImpl(check(Z3.mk_gt(contextPtr, this.ast, this.sort.cast(other).ast))); + } + + ge(other: Arith | number | bigint | string | CoercibleRational) { + return new BoolImpl(check(Z3.mk_ge(contextPtr, this.ast, this.sort.cast(other).ast))); + } + } + + class IntNumImpl extends ArithImpl implements IntNum { + declare readonly __typename: IntNum['__typename']; + + value() { + return BigInt(this.asString()); + } + + asString() { + return Z3.get_numeral_string(contextPtr, this.ast); + } + + asBinary() { + return Z3.get_numeral_binary_string(contextPtr, this.ast); + } + } + + class RatNumImpl extends ArithImpl implements RatNum { + declare readonly __typename: RatNum['__typename']; + + value() { + return { numerator: this.numerator().value(), denominator: this.denominator().value() }; + } + + numerator() { + return new IntNumImpl(Z3.get_numerator(contextPtr, this.ast)); + } + + denominator() { + return new IntNumImpl(Z3.get_denominator(contextPtr, this.ast)); + } + + asNumber() { + const { numerator, denominator } = this.value(); + const div = numerator / denominator; + return Number(div) + Number(numerator - div * denominator) / Number(denominator); + } + + asDecimal(prec: number = Number.parseInt(getParam('precision') ?? FALLBACK_PRECISION.toString())) { + return Z3.get_numeral_decimal_string(contextPtr, this.ast, prec); + } + + asString() { + return Z3.get_numeral_string(contextPtr, this.ast); + } + } + + class BitVecSortImpl extends SortImpl implements BitVecSort { + declare readonly __typename: BitVecSort['__typename']; + + size() { + return Z3.get_bv_sort_size(contextPtr, this.ptr) as Bits; + } + + subsort(other: Sort): boolean { + return isBitVecSort(other) && this.size() < other.size(); + } + + cast(other: CoercibleToBitVec): BitVec; + cast(other: CoercibleToExpr): Expr; + cast(other: CoercibleToExpr): Expr { + if (isExpr(other)) { + _assertContext(other); + return other; + } + assert(!isCoercibleRational(other), "Can't convert rational to BitVec"); + return BitVec.val(other, this.size()); + } + } + + class BitVecImpl extends ExprImpl> implements BitVec { + declare readonly __typename: BitVec['__typename']; + + size() { + return this.sort.size(); + } + + add(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvadd(contextPtr, this.ast, this.sort.cast(other).ast))); + } + mul(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvmul(contextPtr, this.ast, this.sort.cast(other).ast))); + } + sub(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvsub(contextPtr, this.ast, this.sort.cast(other).ast))); + } + sdiv(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvsdiv(contextPtr, this.ast, this.sort.cast(other).ast))); + } + udiv(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvudiv(contextPtr, this.ast, this.sort.cast(other).ast))); + } + smod(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvsmod(contextPtr, this.ast, this.sort.cast(other).ast))); + } + urem(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvurem(contextPtr, this.ast, this.sort.cast(other).ast))); + } + srem(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvsrem(contextPtr, this.ast, this.sort.cast(other).ast))); + } + neg(): BitVec { + return new BitVecImpl(check(Z3.mk_bvneg(contextPtr, this.ast))); + } + + or(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvor(contextPtr, this.ast, this.sort.cast(other).ast))); + } + and(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvand(contextPtr, this.ast, this.sort.cast(other).ast))); + } + nand(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvnand(contextPtr, this.ast, this.sort.cast(other).ast))); + } + xor(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvxor(contextPtr, this.ast, this.sort.cast(other).ast))); + } + xnor(other: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvxnor(contextPtr, this.ast, this.sort.cast(other).ast))); + } + shr(count: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvashr(contextPtr, this.ast, this.sort.cast(count).ast))); + } + lshr(count: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvlshr(contextPtr, this.ast, this.sort.cast(count).ast))); + } + shl(count: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_bvshl(contextPtr, this.ast, this.sort.cast(count).ast))); + } + rotateRight(count: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_ext_rotate_right(contextPtr, this.ast, this.sort.cast(count).ast))); + } + rotateLeft(count: CoercibleToBitVec): BitVec { + return new BitVecImpl(check(Z3.mk_ext_rotate_left(contextPtr, this.ast, this.sort.cast(count).ast))); + } + not(): BitVec { + return new BitVecImpl(check(Z3.mk_bvnot(contextPtr, this.ast))); + } + + extract(high: number, low: number): BitVec { + return new BitVecImpl(check(Z3.mk_extract(contextPtr, high, low, this.ast))); + } + signExt(count: number): BitVec { + return new BitVecImpl(check(Z3.mk_sign_ext(contextPtr, count, this.ast))); + } + zeroExt(count: number): BitVec { + return new BitVecImpl(check(Z3.mk_zero_ext(contextPtr, count, this.ast))); + } + repeat(count: number): BitVec { + return new BitVecImpl(check(Z3.mk_repeat(contextPtr, count, this.ast))); + } + + sle(other: CoercibleToBitVec): Bool { + return new BoolImpl(check(Z3.mk_bvsle(contextPtr, this.ast, this.sort.cast(other).ast))); + } + ule(other: CoercibleToBitVec): Bool { + return new BoolImpl(check(Z3.mk_bvule(contextPtr, this.ast, this.sort.cast(other).ast))); + } + slt(other: CoercibleToBitVec): Bool { + return new BoolImpl(check(Z3.mk_bvslt(contextPtr, this.ast, this.sort.cast(other).ast))); + } + ult(other: CoercibleToBitVec): Bool { + return new BoolImpl(check(Z3.mk_bvult(contextPtr, this.ast, this.sort.cast(other).ast))); + } + sge(other: CoercibleToBitVec): Bool { + return new BoolImpl(check(Z3.mk_bvsge(contextPtr, this.ast, this.sort.cast(other).ast))); + } + uge(other: CoercibleToBitVec): Bool { + return new BoolImpl(check(Z3.mk_bvuge(contextPtr, this.ast, this.sort.cast(other).ast))); + } + sgt(other: CoercibleToBitVec): Bool { + return new BoolImpl(check(Z3.mk_bvsgt(contextPtr, this.ast, this.sort.cast(other).ast))); + } + ugt(other: CoercibleToBitVec): Bool { + return new BoolImpl(check(Z3.mk_bvugt(contextPtr, this.ast, this.sort.cast(other).ast))); + } + + redAnd(): BitVec { + return new BitVecImpl(check(Z3.mk_bvredand(contextPtr, this.ast))); + } + redOr(): BitVec { + return new BitVecImpl(check(Z3.mk_bvredor(contextPtr, this.ast))); + } + + addNoOverflow(other: CoercibleToBitVec, isSigned: boolean): Bool { + return new BoolImpl(check(Z3.mk_bvadd_no_overflow(contextPtr, this.ast, this.sort.cast(other).ast, isSigned))); + } + addNoUnderflow(other: CoercibleToBitVec): Bool { + return new BoolImpl(check(Z3.mk_bvadd_no_underflow(contextPtr, this.ast, this.sort.cast(other).ast))); + } + subNoOverflow(other: CoercibleToBitVec): Bool { + return new BoolImpl(check(Z3.mk_bvsub_no_overflow(contextPtr, this.ast, this.sort.cast(other).ast))); + } + subNoUndeflow(other: CoercibleToBitVec, isSigned: boolean): Bool { + return new BoolImpl(check(Z3.mk_bvsub_no_underflow(contextPtr, this.ast, this.sort.cast(other).ast, isSigned))); + } + sdivNoOverflow(other: CoercibleToBitVec): Bool { + return new BoolImpl(check(Z3.mk_bvsdiv_no_overflow(contextPtr, this.ast, this.sort.cast(other).ast))); + } + mulNoOverflow(other: CoercibleToBitVec, isSigned: boolean): Bool { + return new BoolImpl(check(Z3.mk_bvmul_no_overflow(contextPtr, this.ast, this.sort.cast(other).ast, isSigned))); + } + mulNoUndeflow(other: CoercibleToBitVec): Bool { + return new BoolImpl(check(Z3.mk_bvmul_no_underflow(contextPtr, this.ast, this.sort.cast(other).ast))); + } + negNoOverflow(): Bool { + return new BoolImpl(check(Z3.mk_bvneg_no_overflow(contextPtr, this.ast))); + } + } + + class BitVecNumImpl extends BitVecImpl implements BitVecNum { + declare readonly __typename: BitVecNum['__typename']; + value() { + return BigInt(this.asString()); + } + + asSignedValue() { + let val = this.value(); + const size = BigInt(this.size()); + if (val >= 2n ** (size - 1n)) { + val = val - 2n ** size; + } + if (val < (-2n) ** (size - 1n)) { + val = val + 2n ** size; + } + return val; + } + asString() { + return Z3.get_numeral_string(contextPtr, this.ast); + } + asBinaryString() { + return Z3.get_numeral_binary_string(contextPtr, this.ast); + } + } + + class AstVectorImpl> { + declare readonly __typename: AstVector['__typename']; + readonly ctx: Context; + + constructor(readonly ptr: Z3_ast_vector = Z3.mk_ast_vector(contextPtr)) { + this.ctx = ctx; + Z3.ast_vector_inc_ref(contextPtr, ptr); + cleanup.register(this, () => Z3.ast_vector_dec_ref(contextPtr, ptr)); + } + + length(): number { + return Z3.ast_vector_size(contextPtr, this.ptr); + } + + [Symbol.iterator](): IterableIterator { + return this.values(); + } + + *entries(): IterableIterator<[number, Item]> { + const length = this.length(); + for (let i = 0; i < length; i++) { + yield [i, this.get(i)]; + } + } + + *keys(): IterableIterator { + for (let [key] of this.entries()) { + yield key; + } + } + + *values(): IterableIterator { + for (let [, value] of this.entries()) { + yield value; + } + } + + get(i: number): Item; + get(from: number, to: number): Item[]; + get(from: number, to?: number): Item | Item[] { + const length = this.length(); + if (from < 0) { + from += length; + } + if (from >= length) { + throw new RangeError(`expected from index ${from} to be less than length ${length}`); } if (to === undefined) { - const numConsts = Z3.model_get_num_consts(this.ctx.ptr, this.ptr); - if (i < numConsts) { - return new FuncDeclImpl(this.ctx, Z3.model_get_const_decl(this.ctx.ptr, this.ptr, i)); - } else { - return new FuncDeclImpl(this.ctx, Z3.model_get_func_decl(this.ctx.ptr, this.ptr, i - numConsts)); - } + return _toAst(check(Z3.ast_vector_get(contextPtr, this.ptr, from))) as Item; } if (to < 0) { to += length; } if (to >= length) { - throw new RangeError(); + throw new RangeError(`expected to index ${to} to be less than length ${length}`); } - const result = []; - for (let j = i; j < to; j++) { - result.push(this.get(j)); - } - return result; - } else if (this.ctx.isFuncDecl(i) || (this.ctx.isExpr(i) && this.ctx.isConst(i))) { - const result = this.getInterp(i); - assert(result !== null); - return result; - } else if (this.ctx.isSort(i)) { - return this.getUniverse(i); - } - assert(false, 'Number, declaration or constant expected'); - } - private getInterp(expr: FuncDecl | Expr): Expr | FuncInterp | null { - assert(this.ctx.isFuncDecl(expr) || this.ctx.isConst(expr), 'Declaration expected'); - if (this.ctx.isConst(expr)) { - assert(this.ctx.isExpr(expr)); - expr = expr.decl(); - } - assert(this.ctx.isFuncDecl(expr)); - if (expr.arity() === 0) { - const result = Z3.model_get_const_interp(this.ctx.ptr, this.ptr, expr.ptr); - if (result === null) { - return null; - } - return this.ctx._toExpr(result); - } else { - const interp = Z3.model_get_func_interp(this.ctx.ptr, this.ptr, expr.ptr); - if (interp === null) { - return null; - } - return new FuncInterpImpl(this.ctx, interp); - } - } - - private getUniverse(sort: Sort): AstVector { - this.ctx._assertContext(sort); - return new AstVectorImpl(this.ctx, Z3.model_get_sort_universe(this.ctx.ptr, this.ptr, sort.ptr)); - } - } - - class FuncInterpImpl implements FuncInterp { - declare readonly __typename: FuncInterp['__typename']; - - constructor(readonly ctx: Context, readonly ptr: Z3_func_interp) { - Z3.func_interp_inc_ref(ctx.ptr, ptr); - cleanup.register(this, () => Z3.func_interp_dec_ref(ctx.ptr, ptr)); - } - } - - class SortImpl extends AstImpl implements Sort { - declare readonly __typename: Sort['__typename']; - - get ast(): Z3_ast { - return Z3.sort_to_ast(this.ctx.ptr, this.ptr); - } - - kind() { - return Z3.get_sort_kind(this.ctx.ptr, this.ptr); - } - - subsort(other: Sort) { - this.ctx._assertContext(other); - return false; - } - - cast(expr: Expr): Expr { - this.ctx._assertContext(expr); - assert(expr.sort.eqIdentity(expr.sort), 'Sort mismatch'); - return expr; - } - - name() { - return this.ctx._fromSymbol(Z3.get_sort_name(this.ctx.ptr, this.ptr)); - } - - eqIdentity(other: Sort) { - this.ctx._assertContext(other); - return Z3.is_eq_sort(this.ctx.ptr, this.ptr, other.ptr); - } - - neqIdentity(other: Sort) { - return !this.eqIdentity(other); - } - } - - class FuncDeclImpl extends AstImpl implements FuncDecl { - declare readonly __typename: FuncDecl['__typename']; - - get ast() { - return Z3.func_decl_to_ast(this.ctx.ptr, this.ptr); - } - - name() { - return this.ctx._fromSymbol(Z3.get_decl_name(this.ctx.ptr, this.ptr)); - } - - arity() { - return Z3.get_arity(this.ctx.ptr, this.ptr); - } - - domain(i: number) { - assert(i < this.arity(), 'Index out of bounds'); - return this.ctx._toSort(Z3.get_domain(this.ctx.ptr, this.ptr, i)); - } - - range() { - return this.ctx._toSort(Z3.get_range(this.ctx.ptr, this.ptr)); - } - - kind() { - return Z3.get_decl_kind(this.ctx.ptr, this.ptr); - } - - params(): (number | string | Z3_symbol | Sort | Expr | FuncDecl)[] { - const n = Z3.get_decl_num_parameters(this.ctx.ptr, this.ptr); - const result = []; - for (let i = 0; i < n; i++) { - const kind = Z3.get_decl_parameter_kind(this.ctx.ptr, this.ptr, i); - switch (kind) { - case Z3_parameter_kind.Z3_PARAMETER_INT: - result.push(Z3.get_decl_int_parameter(this.ctx.ptr, this.ptr, i)); - break; - case Z3_parameter_kind.Z3_PARAMETER_DOUBLE: - result.push(Z3.get_decl_double_parameter(this.ctx.ptr, this.ptr, i)); - break; - case Z3_parameter_kind.Z3_PARAMETER_RATIONAL: - result.push(Z3.get_decl_rational_parameter(this.ctx.ptr, this.ptr, i)); - break; - case Z3_parameter_kind.Z3_PARAMETER_SYMBOL: - result.push(Z3.get_decl_symbol_parameter(this.ctx.ptr, this.ptr, i)); - break; - case Z3_parameter_kind.Z3_PARAMETER_SORT: - result.push(new SortImpl(this.ctx, Z3.get_decl_sort_parameter(this.ctx.ptr, this.ptr, i))); - break; - case Z3_parameter_kind.Z3_PARAMETER_AST: - result.push(new ExprImpl(this.ctx, Z3.get_decl_ast_parameter(this.ctx.ptr, this.ptr, i))); - break; - case Z3_parameter_kind.Z3_PARAMETER_FUNC_DECL: - result.push(new FuncDeclImpl(this.ctx, Z3.get_decl_func_decl_parameter(this.ctx.ptr, this.ptr, i))); - break; - default: - assertExhaustive(kind); - } - } - return result; - } - - call(...args: CoercibleToExpr[]) { - assert(args.length === this.arity(), `Incorrect number of arguments to ${this}`); - return this.ctx._toExpr( - Z3.mk_app( - this.ctx.ptr, - this.ptr, - args.map((arg, i) => { - return this.domain(i).cast(arg).ast; - }), - ), - ); - } - } - - class ExprImpl extends AstImpl implements Expr { - declare readonly __typename: Expr['__typename']; - - get sort(): S { - return this.ctx._toSort(Z3.get_sort(this.ctx.ptr, this.ast)) as S; - } - - eq(other: CoercibleToExpr): Bool { - return new BoolImpl(this.ctx, Z3.mk_eq(this.ctx.ptr, this.ast, this.ctx.from(other).ast)); - } - - neq(other: CoercibleToExpr): Bool { - return new BoolImpl( - this.ctx, - Z3.mk_distinct( - this.ctx.ptr, - [this, other].map(expr => this.ctx.from(expr).ast), - ), - ); - } - - params() { - return this.decl().params(); - } - - decl(): FuncDecl { - assert(this.ctx.isApp(this), 'Z3 application expected'); - return new FuncDeclImpl(this.ctx, Z3.get_app_decl(this.ctx.ptr, Z3.to_app(this.ctx.ptr, this.ast))); - } - - numArgs(): number { - assert(this.ctx.isApp(this), 'Z3 applicaiton expected'); - return Z3.get_app_num_args(this.ctx.ptr, Z3.to_app(this.ctx.ptr, this.ast)); - } - - arg(i: number): ReturnType { - assert(this.ctx.isApp(this), 'Z3 applicaiton expected'); - assert(i < this.numArgs(), 'Invalid argument index'); - return this.ctx._toExpr(Z3.get_app_arg(this.ctx.ptr, Z3.to_app(this.ctx.ptr, this.ast), i)); - } - - children(): ReturnType[] { - const num_args = this.numArgs(); - if (this.ctx.isApp(this)) { - const result = []; - for (let i = 0; i < num_args; i++) { - result.push(this.arg(i)); + const result: Item[] = []; + for (let i = from; i < to; i++) { + result.push(_toAst(check(Z3.ast_vector_get(contextPtr, this.ptr, i))) as Item); } return result; } - return []; - } - } - class BoolSortImpl extends SortImpl implements BoolSort { - declare readonly __typename: BoolSort['__typename']; - - cast(other: Bool | boolean): Bool; - cast(other: CoercibleToExpr): never; - cast(other: CoercibleToExpr | Bool) { - if (typeof other === 'boolean') { - other = this.ctx.Bool.val(other); - } - assert(this.ctx.isExpr(other), 'true, false or Z3 Boolean expression expected.'); - assert(this.eqIdentity(other.sort), 'Value cannot be converted into a Z3 Boolean value'); - return other; - } - - subsort(other: Sort) { - this.ctx._assertContext(other.ctx); - return other instanceof ArithSortImpl; - } - } - - class BoolImpl extends ExprImpl implements Bool { - declare readonly __typename: Bool['__typename']; - - not(): Bool { - return this.ctx.Not(this); - } - and(other: Bool | boolean): Bool { - return this.ctx.And(this, other); - } - or(other: Bool | boolean): Bool { - return this.ctx.Or(this, other); - } - xor(other: Bool | boolean): Bool { - return this.ctx.Xor(this, other); - } - } - - class ProbeImpl implements Probe { - declare readonly __typename: Probe['__typename']; - - constructor(readonly ctx: ContextImpl, readonly ptr: Z3_probe) {} - } - - class TacticImpl implements Tactic { - declare readonly __typename: Tactic['__typename']; - - readonly ptr: Z3_tactic; - - constructor(readonly ctx: ContextImpl, tactic: string | Z3_tactic) { - let myPtr: Z3_tactic; - if (typeof tactic === 'string') { - myPtr = Z3.mk_tactic(ctx.ptr, tactic); - } else { - myPtr = tactic; + set(i: number, v: Item): void { + _assertContext(v); + if (i >= this.length()) { + throw new RangeError(`expected index ${i} to be less than length ${this.length()}`); + } + check(Z3.ast_vector_set(contextPtr, this.ptr, i, v.ast)); } - this.ptr = myPtr; + push(v: Item): void { + _assertContext(v); + check(Z3.ast_vector_push(contextPtr, this.ptr, v.ast)); + } - Z3.tactic_inc_ref(ctx.ptr, myPtr); - cleanup.register(this, () => Z3.tactic_dec_ref(ctx.ptr, myPtr)); - } - } + resize(size: number): void { + check(Z3.ast_vector_resize(contextPtr, this.ptr, size)); + } - class ArithSortImpl extends SortImpl implements ArithSort { - declare readonly __typename: ArithSort['__typename']; - - cast(other: bigint | number): IntNum | RatNum; - cast(other: CoercibleRational | RatNum): RatNum; - cast(other: IntNum): IntNum; - cast(other: Bool | Arith): Arith; - cast(other: CoercibleToExpr): never; - cast(other: CoercibleToExpr): Arith | RatNum | IntNum { - const { If, isExpr, isArith, isBool, isIntSort, isRealSort, ToReal, Int, Real } = this.ctx; - const sortTypeStr = isIntSort(this) ? 'IntSort' : 'RealSort'; - if (isExpr(other)) { - const otherS = other.sort; - if (isArith(other)) { - if (this.eqIdentity(otherS)) { - return other; - } else if (isIntSort(otherS) && isRealSort(this)) { - return this.ctx.ToReal(other); - } - assert(false, "Can't cast Real to IntSort without loss"); - } else if (isBool(other)) { - if (isIntSort(this)) { - return If(other, 1, 0); - } else { - return ToReal(If(other, 1, 0)); + has(v: Item): boolean { + _assertContext(v); + for (const item of this.values()) { + if (item.eqIdentity(v)) { + return true; } } - assert(false, `Can't cast expression to ${sortTypeStr}`); - } else { - if (typeof other !== 'boolean') { - if (isIntSort(this)) { - assert(!isCoercibleRational(other), "Can't cast fraction to IntSort"); - return Int.val(other); - } - return Real.val(other); - } - assert(false, `Can't cast primitive to ${sortTypeStr}`); + return false; } - } - } - class ArithImpl extends ExprImpl implements Arith { - declare readonly __typename: Arith['__typename']; - - add(other: Arith | number | bigint | string | CoercibleRational) { - return new ArithImpl(this.ctx, Z3.mk_add(this.ctx.ptr, [this.ast, this.sort.cast(other).ast])); - } - - mul(other: Arith | number | bigint | string | CoercibleRational) { - return new ArithImpl(this.ctx, Z3.mk_mul(this.ctx.ptr, [this.ast, this.sort.cast(other).ast])); - } - - sub(other: Arith | number | bigint | string | CoercibleRational) { - return new ArithImpl(this.ctx, Z3.mk_sub(this.ctx.ptr, [this.ast, this.sort.cast(other).ast])); - } - - pow(exponent: Arith | number | bigint | string | CoercibleRational) { - return new ArithImpl(this.ctx, Z3.mk_power(this.ctx.ptr, this.ast, this.sort.cast(exponent).ast)); - } - - div(other: Arith | number | bigint | string | CoercibleRational) { - return new ArithImpl(this.ctx, Z3.mk_div(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - - mod(other: Arith | number | bigint | string | CoercibleRational) { - return new ArithImpl(this.ctx, Z3.mk_mod(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - - neg() { - return new ArithImpl(this.ctx, Z3.mk_unary_minus(this.ctx.ptr, this.ast)); - } - - le(other: Arith | number | bigint | string | CoercibleRational) { - return new BoolImpl(this.ctx, Z3.mk_le(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - - lt(other: Arith | number | bigint | string | CoercibleRational) { - return new BoolImpl(this.ctx, Z3.mk_lt(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - - gt(other: Arith | number | bigint | string | CoercibleRational) { - return new BoolImpl(this.ctx, Z3.mk_gt(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - - ge(other: Arith | number | bigint | string | CoercibleRational) { - return new BoolImpl(this.ctx, Z3.mk_ge(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - } - - class IntNumImpl extends ArithImpl implements IntNum { - declare readonly __typename: IntNum['__typename']; - - get value() { - return BigInt(this.asString()); - } - - asString() { - return Z3.get_numeral_string(this.ctx.ptr, this.ast); - } - - asBinary() { - return Z3.get_numeral_binary_string(this.ctx.ptr, this.ast); - } - } - - class RatNumImpl extends ArithImpl implements RatNum { - declare readonly __typename: RatNum['__typename']; - - get value() { - return { numerator: this.numerator().value, denominator: this.denominator().value }; - } - - numerator() { - return new IntNumImpl(this.ctx, Z3.get_numerator(this.ctx.ptr, this.ast)); - } - - denominator() { - return new IntNumImpl(this.ctx, Z3.get_denominator(this.ctx.ptr, this.ast)); - } - - asNumber() { - const { numerator, denominator } = this.value; - const div = numerator / denominator; - return Number(div) + Number(numerator - div * denominator) / Number(denominator); - } - - asDecimal(prec: number = Number.parseInt(getParam('precision') ?? FALLBACK_PRECISION.toString())) { - return Z3.get_numeral_decimal_string(this.ctx.ptr, this.ast, prec); - } - - asString() { - return Z3.get_numeral_string(this.ctx.ptr, this.ast); - } - } - - class BitVecSortImpl extends SortImpl implements BitVecSort { - declare readonly __typename: BitVecSort['__typename']; - - get size() { - return Z3.get_bv_sort_size(this.ctx.ptr, this.ptr); - } - - subsort(other: Sort): boolean { - return this.ctx.isBitVecSort(other) && this.size < other.size; - } - - cast(other: CoercibleToBitVec): BitVec; - cast(other: CoercibleToExpr): Expr; - cast(other: CoercibleToExpr): Expr { - if (this.ctx.isExpr(other)) { - this.ctx._assertContext(other); - return other; - } - assert(!isCoercibleRational(other), "Can't convert rational to BitVec"); - return this.ctx.BitVec.val(other, this.size); - } - } - - class BitVecImpl extends ExprImpl implements BitVec { - declare readonly __typename: BitVec['__typename']; - - get size() { - return this.sort.size; - } - - add(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvadd(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - mul(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvmul(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - sub(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvsub(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - sdiv(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvsdiv(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - udiv(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvudiv(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - smod(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvsmod(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - urem(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvurem(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - srem(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvsrem(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - neg(): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvneg(this.ctx.ptr, this.ast)); - } - - or(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvor(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - and(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvand(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - nand(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvnand(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - xor(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvxor(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - xnor(other: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvxnor(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - shr(count: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvashr(this.ctx.ptr, this.ast, this.sort.cast(count).ast)); - } - lshr(count: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvlshr(this.ctx.ptr, this.ast, this.sort.cast(count).ast)); - } - shl(count: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvshl(this.ctx.ptr, this.ast, this.sort.cast(count).ast)); - } - rotateRight(count: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_ext_rotate_right(this.ctx.ptr, this.ast, this.sort.cast(count).ast)); - } - rotateLeft(count: CoercibleToBitVec): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_ext_rotate_left(this.ctx.ptr, this.ast, this.sort.cast(count).ast)); - } - not(): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvnot(this.ctx.ptr, this.ast)); - } - - extract(high: number, low: number): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_extract(this.ctx.ptr, high, low, this.ast)); - } - signExt(count: number): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_sign_ext(this.ctx.ptr, count, this.ast)); - } - zeroExt(count: number): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_zero_ext(this.ctx.ptr, count, this.ast)); - } - repeat(count: number): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_repeat(this.ctx.ptr, count, this.ast)); - } - - sle(other: CoercibleToBitVec): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvsle(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - ule(other: CoercibleToBitVec): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvule(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - slt(other: CoercibleToBitVec): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvslt(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - ult(other: CoercibleToBitVec): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvult(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - sge(other: CoercibleToBitVec): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvsge(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - uge(other: CoercibleToBitVec): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvuge(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - sgt(other: CoercibleToBitVec): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvsgt(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - ugt(other: CoercibleToBitVec): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvugt(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - - redAnd(): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvredand(this.ctx.ptr, this.ast)); - } - redOr(): BitVec { - return new BitVecImpl(this.ctx, Z3.mk_bvredor(this.ctx.ptr, this.ast)); - } - - addNoOverflow(other: CoercibleToBitVec, isSigned: boolean): Bool { - return new BoolImpl( - this.ctx, - Z3.mk_bvadd_no_overflow(this.ctx.ptr, this.ast, this.sort.cast(other).ast, isSigned), - ); - } - addNoUnderflow(other: CoercibleToBitVec): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvadd_no_underflow(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - subNoOverflow(other: CoercibleToBitVec): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvsub_no_overflow(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - subNoUndeflow(other: CoercibleToBitVec, isSigned: boolean): Bool { - return new BoolImpl( - this.ctx, - Z3.mk_bvsub_no_underflow(this.ctx.ptr, this.ast, this.sort.cast(other).ast, isSigned), - ); - } - sdivNoOverflow(other: CoercibleToBitVec): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvsdiv_no_overflow(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - mulNoOverflow(other: CoercibleToBitVec, isSigned: boolean): Bool { - return new BoolImpl( - this.ctx, - Z3.mk_bvmul_no_overflow(this.ctx.ptr, this.ast, this.sort.cast(other).ast, isSigned), - ); - } - mulNoUndeflow(other: CoercibleToBitVec): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvmul_no_underflow(this.ctx.ptr, this.ast, this.sort.cast(other).ast)); - } - negNoOverflow(): Bool { - return new BoolImpl(this.ctx, Z3.mk_bvneg_no_overflow(this.ctx.ptr, this.ast)); - } - } - - class BitVecNumImpl extends BitVecImpl implements BitVecNum { - declare readonly __typename: BitVecNum['__typename']; - get value() { - return BigInt(this.asString()); - } - - asSignedValue() { - let val = this.value; - const size = BigInt(this.size); - if (val >= 2n ** (size - 1n)) { - val = val - 2n ** size; - } - if (val < (-2n) ** (size - 1n)) { - val = val + 2n ** size; - } - return val; - } - asString() { - return Z3.get_numeral_string(this.ctx.ptr, this.ast); - } - asBinaryString() { - return Z3.get_numeral_binary_string(this.ctx.ptr, this.ast); - } - } - - class AstVectorImpl { - declare readonly __typename: AstVector['__typename']; - - constructor(readonly ctx: ContextImpl, readonly ptr: Z3_ast_vector = Z3.mk_ast_vector(ctx.ptr)) { - Z3.ast_vector_inc_ref(ctx.ptr, ptr); - cleanup.register(this, () => Z3.ast_vector_dec_ref(ctx.ptr, ptr)); - } - - get length(): number { - return Z3.ast_vector_size(this.ctx.ptr, this.ptr); - } - - [Symbol.iterator](): IterableIterator { - return this.values(); - } - - *entries(): IterableIterator<[number, Item]> { - const length = this.length; - for (let i = 0; i < length; i++) { - yield [i, this.get(i)]; + sexpr(): string { + return check(Z3.ast_vector_to_string(contextPtr, this.ptr)); } } - *keys(): IterableIterator { - for (let [key] of this.entries()) { - yield key; - } - } + class AstMapImpl, Value extends AnyAst> implements AstMap { + declare readonly __typename: AstMap['__typename']; + readonly ctx: Context; - *values(): IterableIterator { - for (let [, value] of this.entries()) { - yield value; - } - } - - get(i: number): Item; - get(from: number, to: number): Item[]; - get(from: number, to?: number): Item | Item[] { - const length = this.length; - if (from < 0) { - from += length; - } - if (from >= length) { - throw new RangeError(); + constructor(readonly ptr: Z3_ast_map = Z3.mk_ast_map(contextPtr)) { + this.ctx = ctx; + Z3.ast_map_inc_ref(contextPtr, ptr); + cleanup.register(this, () => Z3.ast_map_dec_ref(contextPtr, ptr)); } - if (to === undefined) { - return this.ctx._toAst(Z3.ast_vector_get(this.ctx.ptr, this.ptr, from)) as Item; + [Symbol.iterator](): Iterator<[Key, Value]> { + return this.entries(); } - if (to < 0) { - to += length; - } - if (to >= length) { - throw new RangeError(); + get size(): number { + return Z3.ast_map_size(contextPtr, this.ptr); } - const result: Item[] = []; - for (let i = from; i < to; i++) { - result.push(this.ctx._toAst(Z3.ast_vector_get(this.ctx.ptr, this.ptr, i)) as Item); - } - return result; - } - - set(i: number, v: Item): void { - this.ctx._assertContext(v); - if (i >= this.length) { - throw new RangeError(); - } - Z3.ast_vector_set(this.ctx.ptr, this.ptr, i, v.ast); - } - - push(v: Item): void { - this.ctx._assertContext(v); - Z3.ast_vector_push(this.ctx.ptr, this.ptr, v.ast); - } - - resize(size: number): void { - Z3.ast_vector_resize(this.ctx.ptr, this.ptr, size); - } - - has(v: Item): boolean { - this.ctx._assertContext(v); - for (const item of this.values()) { - if (item.eqIdentity(v)) { - return true; + *entries(): IterableIterator<[Key, Value]> { + for (const key of this.keys()) { + yield [key, this.get(key)]; } } - return false; - } - sexpr(): string { - return Z3.ast_vector_to_string(this.ctx.ptr, this.ptr); - } - } + keys(): AstVector { + return new AstVectorImpl(Z3.ast_map_keys(contextPtr, this.ptr)); + } - class AstMapImpl implements AstMap { - declare readonly __typename: AstMap['__typename']; + *values(): IterableIterator { + for (const [_, value] of this.entries()) { + yield value; + } + } + get(key: Key): Value { + return _toAst(check(Z3.ast_map_find(contextPtr, this.ptr, key.ast))) as Value; + } - constructor(readonly ctx: ContextImpl, readonly ptr: Z3_ast_map = Z3.mk_ast_map(ctx.ptr)) { - Z3.ast_map_inc_ref(ctx.ptr, ptr); - cleanup.register(this, () => Z3.ast_map_dec_ref(ctx.ptr, ptr)); - } + set(key: Key, value: Value): void { + check(Z3.ast_map_insert(contextPtr, this.ptr, key.ast, value.ast)); + } - [Symbol.iterator](): Iterator<[Key, Value]> { - return this.entries(); - } + delete(key: Key): void { + check(Z3.ast_map_erase(contextPtr, this.ptr, key.ast)); + } - get size(): number { - return Z3.ast_map_size(this.ctx.ptr, this.ptr); - } + clear(): void { + check(Z3.ast_map_reset(contextPtr, this.ptr)); + } - *entries(): IterableIterator<[Key, Value]> { - for (const key of this.keys()) { - yield [key, this.get(key)]; + has(key: Key): boolean { + return check(Z3.ast_map_contains(contextPtr, this.ptr, key.ast)); + } + + sexpr(): string { + return check(Z3.ast_map_to_string(contextPtr, this.ptr)); } } - keys(): AstVector { - return new AstVectorImpl(this.ctx, Z3.ast_map_keys(this.ctx.ptr, this.ptr)); - } + const ctx: Context = { + ptr: contextPtr, + name, - *values(): IterableIterator { - for (const [_, value] of this.entries()) { - yield value; - } - } - get(key: Key): Value { - return this.ctx._toAst(Z3.ast_map_find(this.ctx.ptr, this.ptr, key.ast)) as Value; - } + ///////////// + // Classes // + ///////////// + Solver: SolverImpl, + Model: ModelImpl, + Tactic: TacticImpl, + AstVector: AstVectorImpl as AstVectorCtor, + AstMap: AstMapImpl as AstMapCtor, - set(key: Key, value: Value): void { - Z3.ast_map_insert(this.ctx.ptr, this.ptr, key.ast, value.ast); - } + /////////////// + // Functions // + /////////////// + interrupt, + isModel, + isAst, + isSort, + isFuncDecl, + isApp, + isConst, + isExpr, + isVar, + isAppOf, + isBool, + isTrue, + isFalse, + isAnd, + isOr, + isImplies, + isNot, + isEq, + isDistinct, + isArith, + isArithSort, + isInt, + isIntVal, + isIntSort, + isReal, + isRealVal, + isRealSort, + isBitVecSort, + isBitVec, + isBitVecVal, // TODO fix ordering + isProbe, + isTactic, + isAstVector, + eqIdentity, + getVarIndex, + from, + solve, - delete(key: Key): void { - Z3.ast_map_erase(this.ctx.ptr, this.ptr, key.ast); - } + ///////////// + // Objects // + ///////////// + Sort, + Function, + RecFunc, + Bool, + Int, + Real, + BitVec, - clear(): void { - Z3.ast_map_reset(this.ctx.ptr, this.ptr); - } - - has(key: Key): boolean { - return Z3.ast_map_contains(this.ctx.ptr, this.ptr, key.ast); - } - - sexpr(): string { - return Z3.ast_map_to_string(this.ctx.ptr, this.ptr); - } + //////////////// + // Operations // + //////////////// + If, + Distinct, + Const, + Consts, + FreshConst, + Var, + Implies, + Eq, + Xor, + Not, + And, + Or, + ToReal, + ToInt, + IsInt, + Sqrt, + Cbrt, + BV2Int, + Int2BV, + Concat, + Cond, + }; + cleanup.register(ctx, () => Z3.del_context(contextPtr)); + return ctx; } return { @@ -1881,8 +1962,7 @@ export function createApi(Z3: Z3Core): Z3HighLevel { getParam, setParam, resetParams, - isContext, - Context: ContextImpl as ContextCtor, + Context: createContext, }; } diff --git a/src/api/js/src/high-level/types.ts b/src/api/js/src/high-level/types.ts index a056ef686..eae81ff98 100644 --- a/src/api/js/src/high-level/types.ts +++ b/src/api/js/src/high-level/types.ts @@ -16,13 +16,13 @@ import { } from '../low-level'; /** @hidden */ -export type AnySort = +export type AnySort = | Sort | BoolSort | ArithSort | BitVecSort; /** @hidden */ -export type AnyExpr = +export type AnyExpr = | Expr | Bool | Arith @@ -31,10 +31,10 @@ export type AnyExpr = | BitVec | BitVecNum; /** @hidden */ -export type AnyAst = AnyExpr | AnySort | FuncDecl; +export type AnyAst = AnyExpr | AnySort | FuncDecl; /** @hidden */ -export type SortToExprMap, Name extends string = any> = S extends BoolSort +export type SortToExprMap, Name extends string = 'main'> = S extends BoolSort ? Bool : S extends ArithSort ? Arith @@ -45,7 +45,7 @@ export type SortToExprMap, Name extends string = any> = : never; /** @hidden */ -export type CoercibleToExprMap, Name extends string = any> = S extends bigint +export type CoercibleToExprMap, Name extends string = 'main'> = S extends bigint ? IntNum : S extends number | CoercibleRational ? RatNum @@ -76,38 +76,20 @@ export type CoercibleToExprMap, Name extends str export type CoercibleRational = { numerator: bigint | number; denominator: bigint | number }; /** @hidden */ -export type CoercibleToExpr = number | bigint | boolean | CoercibleRational | Expr; +export type CoercibleToExpr = number | bigint | boolean | CoercibleRational | Expr; export class Z3Error extends Error {} export class Z3AssertionError extends Z3Error {} -/** - * Returned by {@link Solver.check} when Z3 could find a solution - * @category Global - */ -export const sat = Symbol('Solver found a solution'); -/** - * Returned by {@link Solver.check} when Z3 couldn't find a solution - * @category Global - */ -export const unsat = Symbol("Solver didn't find a solution"); -/** - * Returned by {@link Solver.check} when Z3 couldn't reason about the assumptions - * @category Global - */ -export const unknown = Symbol("Solver couldn't reason about the assumptions"); /** @category Global */ -export type CheckSatResult = typeof sat | typeof unsat | typeof unknown; +export type CheckSatResult = 'sat' | 'unsat' | 'unknown'; /** @hidden */ export interface ContextCtor { - new (name: Name, options?: Record): Context; + (name: Name, options?: Record): Context; } -export interface Context { - /** @hidden */ - readonly __typename: 'Context'; - +export interface Context { /** @hidden */ readonly ptr: Z3_context; /** @@ -190,7 +172,7 @@ export interface Context { /** @category Functions */ isTactic(obj: unknown): obj is Tactic; /** @category Functions */ - isAstVector(obj: unknown): obj is AstVector, Name>; + isAstVector(obj: unknown): obj is AstVector>; /** * Returns whether two Asts are the same thing * @category Functions */ @@ -232,7 +214,7 @@ export interface Context { * * @see {@link Solver} * @category Functions */ - solve(...assertions: Bool[]): Promise; + solve(...assertions: Bool[]): Promise | 'unsat' | 'unknown'>; ///////////// // Classes // @@ -250,9 +232,9 @@ export interface Context { */ readonly Model: new () => Model; /** @category Classes */ - readonly AstVector: new = AnyAst>() => AstVector; + readonly AstVector: new = AnyAst>() => AstVector; /** @category Classes */ - readonly AstMap: new () => AstMap; + readonly AstMap: new = AnyAst, Value extends Ast = AnyAst>() => AstMap; /** @category Classes */ readonly Tactic: new (name: string) => Tactic; @@ -309,7 +291,7 @@ export interface Context { /** @category Operations */ And(): Bool; /** @category Operations */ - And(vector: AstVector, Name>): Bool; + And(vector: AstVector>): Bool; /** @category Operations */ And(...args: (Bool | boolean)[]): Bool; /** @category Operations */ @@ -317,7 +299,7 @@ export interface Context { /** @category Operations */ Or(): Bool; /** @category Operations */ - Or(vector: AstVector, Name>): Bool; + Or(vector: AstVector>): Bool; /** @category Operations */ Or(...args: (Bool | boolean)[]): Bool; /** @category Operations */ @@ -368,9 +350,11 @@ export interface Context { Int2BV(a: Arith | bigint | number, bits: Bits): BitVec; /** @category Operations */ Concat(...bitvecs: BitVec[]): BitVec; + /** @category Operations */ + Cond(probe: Probe, onTrue: Tactic, onFalse: Tactic): Tactic } -export interface Ast { +export interface Ast { /** @hidden */ readonly __typename: 'Ast' | Sort['__typename'] | FuncDecl['__typename'] | Expr['__typename']; @@ -380,7 +364,7 @@ export interface Ast { /** @virtual */ get ast(): Z3_ast; /** @virtual */ - get id(): number; + id(): number; eqIdentity(other: Ast): boolean; neqIdentity(other: Ast): boolean; @@ -392,7 +376,7 @@ export interface Ast { export interface SolverCtor { new (): Solver; } -export interface Solver { +export interface Solver { /** @hidden */ readonly __typename: 'Solver'; @@ -407,10 +391,10 @@ export interface Solver { pop(num?: number): void; numScopes(): number; reset(): void; - add(...exprs: (Bool | AstVector, Name>)[]): void; + add(...exprs: (Bool | AstVector>)[]): void; addAndTrack(expr: Bool, constant: Bool | string): void; - assertions(): AstVector, Name>; - check(...exprs: (Bool | AstVector, Name>)[]): Promise; + assertions(): AstVector>; + check(...exprs: (Bool | AstVector>)[]): Promise; model(): Model; } @@ -418,14 +402,14 @@ export interface Solver { export interface ModelCtor { new (): Model; } -export interface Model extends Iterable> { +export interface Model extends Iterable> { /** @hidden */ readonly __typename: 'Model'; readonly ctx: Context; readonly ptr: Z3_model; - get length(): number; + length(): number; entries(): IterableIterator<[number, FuncDecl]>; keys(): IterableIterator; @@ -436,10 +420,10 @@ export interface Model extends Iterable, modelCompletion?: boolean): Arith; eval(expr: Expr, modelCompletion?: boolean): Expr; get(i: number): FuncDecl; - get(from: number, to: number): FuncDecl[]; + get(from: number, to: number): FuncDecl[]; get(declaration: FuncDecl): FuncInterp | Expr; get(constant: Expr): Expr; - get(sort: Sort): AstVector, Name>; + get(sort: Sort): AstVector>; } /** @@ -461,7 +445,7 @@ export interface Model extends Iterable { declare(name: string): Sort; } -export interface Sort extends Ast { +export interface Sort extends Ast { /** @hidden */ readonly __typename: 'Sort' | BoolSort['__typename'] | ArithSort['__typename'] | BitVecSort['__typename']; @@ -476,7 +460,7 @@ export interface Sort extends Ast { /** * @category Functions */ -export interface FuncInterp { +export interface FuncInterp { /** @hidden */ readonly __typename: 'FuncInterp'; @@ -516,7 +500,7 @@ export interface RecFuncCreation { /** * @category Functions */ -export interface FuncDecl extends Ast { +export interface FuncDecl extends Ast { /** @hidden */ readonly __typename: 'FuncDecl'; @@ -529,7 +513,7 @@ export interface FuncDecl extends Ast[]): AnyExpr; } -export interface Expr = AnySort, Ptr = unknown> +export interface Expr = AnySort, Ptr = unknown> extends Ast { /** @hidden */ readonly __typename: 'Expr' | Bool['__typename'] | Arith['__typename'] | BitVec['__typename']; @@ -546,7 +530,7 @@ export interface Expr = AnySort< } /** @category Booleans */ -export interface BoolSort extends Sort { +export interface BoolSort extends Sort { /** @hidden */ readonly __typename: 'BoolSort'; @@ -554,7 +538,7 @@ export interface BoolSort extends Sort { cast(expr: CoercibleToExpr): never; } /** @category Booleans */ -export interface BoolCreation { +export interface BoolCreation { sort(): BoolSort; const(name: string): Bool; @@ -565,7 +549,7 @@ export interface BoolCreation { val(value: boolean): Bool; } /** @category Booleans */ -export interface Bool extends Expr, Z3_ast> { +export interface Bool extends Expr, Z3_ast> { /** @hidden */ readonly __typename: 'Bool'; @@ -579,7 +563,7 @@ export interface Bool extends Expr extends Sort { +export interface ArithSort extends Sort { /** @hidden */ readonly __typename: 'ArithSort'; @@ -615,7 +599,7 @@ export interface RealCreation { * Represents Integer or Real number expression * @category Arithmetic */ -export interface Arith extends Expr, Z3_ast> { +export interface Arith extends Expr, Z3_ast> { /** @hidden */ readonly __typename: 'Arith' | IntNum['__typename'] | RatNum['__typename']; @@ -683,11 +667,11 @@ export interface Arith extends Expr extends Arith { +export interface IntNum extends Arith { /** @hidden */ readonly __typename: 'IntNum'; - get value(): bigint; + value(): bigint; asString(): string; asBinary(): string; } @@ -707,11 +691,11 @@ export interface IntNum extends Arith { * ``` * @category Arithmetic */ -export interface RatNum extends Arith { +export interface RatNum extends Arith { /** @hidden */ readonly __typename: 'RatNum'; - get value(): { numerator: bigint; denominator: bigint }; + value(): { numerator: bigint; denominator: bigint }; numerator(): IntNum; denominator(): IntNum; asNumber(): number; @@ -725,7 +709,7 @@ export interface RatNum extends Arith { * @typeParam Bits - A number representing amount of bits for this sort * @category Bit Vectors */ -export interface BitVecSort extends Sort { +export interface BitVecSort extends Sort { /** @hidden */ readonly __typename: 'BitVecSort'; @@ -739,14 +723,14 @@ export interface BitVecSort): BitVec; cast(other: CoercibleToExpr): Expr; } /** @hidden */ -export type CoercibleToBitVec = +export type CoercibleToBitVec = | bigint | number | BitVec; @@ -769,7 +753,7 @@ export interface BitVecCreation { * Represents Bit Vector expression * @category Bit Vectors */ -export interface BitVec +export interface BitVec extends Expr, Z3_ast> { /** @hidden */ readonly __typename: 'BitVec' | BitVecNum['__typename']; @@ -790,7 +774,7 @@ export interface BitVec * // 8 * ``` */ - get size(): Bits; + size(): Bits; /** @category Arithmetic */ add(other: CoercibleToBitVec): BitVec; @@ -959,17 +943,17 @@ export interface BitVec * Represents Bit Vector constant value * @category Bit Vectors */ -export interface BitVecNum extends BitVec { +export interface BitVecNum extends BitVec { /** @hidden */ readonly __typename: 'BitVecNum'; - get value(): bigint; + value(): bigint; asSignedValue(): bigint; asString(): string; asBinaryString(): string; } -export interface Probe { +export interface Probe { /** @hidden */ readonly __typename: 'Probe'; @@ -981,7 +965,7 @@ export interface Probe { export interface TacticCtor { new (name: string): Tactic; } -export interface Tactic { +export interface Tactic { /** @hidden */ readonly __typename: 'Tactic'; @@ -991,7 +975,7 @@ export interface Tactic { /** @hidden */ export interface AstVectorCtor { - new = AnyAst>(): AstVector; + new = AnyAst>(): AstVector; } /** * Stores multiple {@link Ast} objects @@ -1009,13 +993,13 @@ export interface AstVectorCtor { * // [2, x] * ``` */ -export interface AstVector = AnyAst, Name extends string = any> extends Iterable { +export interface AstVector = AnyAst> extends Iterable { /** @hidden */ readonly __typename: 'AstVector'; readonly ctx: Context; readonly ptr: Z3_ast_vector; - get length(): number; + length(): number; entries(): IterableIterator<[number, Item]>; keys(): IterableIterator; @@ -1031,7 +1015,7 @@ export interface AstVector = AnyAst, Name extends string /** @hidden */ export interface AstMapCtor { - new (): AstMap; + new = AnyAst, Value extends Ast = AnyAst>(): AstMap; } /** * Stores a mapping between different {@link Ast} objects @@ -1054,7 +1038,7 @@ export interface AstMapCtor { * // 0 * ``` */ -export interface AstMap = AnyAst, Value extends Ast = AnyAst, Name extends string = any> +export interface AstMap = AnyAst, Value extends Ast = AnyAst> extends Iterable<[Key, Value]> { /** @hidden */ readonly __typename: 'AstMap'; @@ -1064,7 +1048,7 @@ export interface AstMap = AnyAst, Value extends Ast = AnyA get size(): number; entries(): IterableIterator<[Key, Value]>; - keys(): AstVector; + keys(): AstVector; values(): IterableIterator; get(key: Key): Value | undefined; set(key: Key, value: Value): void; @@ -1119,11 +1103,6 @@ export interface Z3HighLevel { */ getParam(name: string): string | null; - /** - * Returns whether the given object is a {@link Context} - */ - isContext(obj: unknown): obj is Context; - /** * Use this to create new contexts * @see {@link Context} diff --git a/src/api/js/src/high-level/utils.test.ts b/src/api/js/src/high-level/utils.test.ts index 945f7e9d0..791782783 100644 --- a/src/api/js/src/high-level/utils.test.ts +++ b/src/api/js/src/high-level/utils.test.ts @@ -1,5 +1,5 @@ import { Z3AssertionError } from './types'; -import { allSatisfy, assert, assertExhaustive, autoBind } from './utils'; +import { allSatisfy, assert, assertExhaustive } from './utils'; describe('allSatisfy', () => { it('returns null on empty array', () => { @@ -56,28 +56,6 @@ describe('assertExhaustive', () => { }); }); -describe('autoBind', () => { - class Binded { - readonly name = 'Richard'; - constructor(shouldBind: boolean) { - if (shouldBind === true) { - autoBind(this); - } - } - - test(): string { - return `Hello ${this.name}`; - } - } - - it('binds this', () => { - const { test: withoutBind } = new Binded(false); - const { test: withBind } = new Binded(true); - expect(() => withoutBind()).toThrowError(TypeError); - expect(withBind()).toStrictEqual('Hello Richard'); - }); -}); - describe('assert', () => { it('throws on failure', () => { expect(() => assert(false)).toThrowError(Z3AssertionError); diff --git a/src/api/js/src/high-level/utils.ts b/src/api/js/src/high-level/utils.ts index bd5013710..2f01da170 100644 --- a/src/api/js/src/high-level/utils.ts +++ b/src/api/js/src/high-level/utils.ts @@ -10,32 +10,6 @@ function getAllProperties(obj: Record) { return properties; } -// https://github.com/sindresorhus/auto-bind -// We modify it to use CommonJS instead of ESM -/* -MIT License - -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ -export function autoBind>(self: Self): Self { - for (const [obj, key] of getAllProperties(self.constructor.prototype)) { - if (key === 'constructor') { - continue; - } - const descriptor = Reflect.getOwnPropertyDescriptor(obj, key); - if (descriptor && typeof descriptor.value === 'function') { - (self[key] as any) = self[key].bind(self); - } - } - return self; -} - /** * Use to ensure that switches are checked to be exhaustive at compile time * From 352666b19fd782df6623645f46cfb52f0044aeef Mon Sep 17 00:00:00 2001 From: Kevin Gibbons Date: Wed, 22 Jun 2022 14:51:40 -0700 Subject: [PATCH 004/125] JS api: fix type for from (#6103) * JS api: fix type for from * whitespace --- src/api/js/src/high-level/high-level.ts | 3 ++- src/api/js/src/high-level/types.ts | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/api/js/src/high-level/high-level.ts b/src/api/js/src/high-level/high-level.ts index c68e803f9..489d4acdb 100644 --- a/src/api/js/src/high-level/high-level.ts +++ b/src/api/js/src/high-level/high-level.ts @@ -462,7 +462,8 @@ export function createApi(Z3: Z3Core): Z3HighLevel { } function from(primitive: boolean): Bool; - function from(primitive: number | CoercibleRational): RatNum; + function from(primitive: number): IntNum | RatNum; + function from(primitive: CoercibleRational): RatNum; function from(primitive: bigint): IntNum; function from>(expr: T): T; function from(expr: CoercibleToExpr): AnyExpr; diff --git a/src/api/js/src/high-level/types.ts b/src/api/js/src/high-level/types.ts index eae81ff98..d8bda01a1 100644 --- a/src/api/js/src/high-level/types.ts +++ b/src/api/js/src/high-level/types.ts @@ -184,9 +184,13 @@ export interface Context { * @category Functions */ from(primitive: boolean): Bool; /** - * Coerce a number or rational into a Real expression + * Coerce a number to an Int or Real expression (integral numbers become Ints) * @category Functions */ - from(primitive: number | CoercibleRational): RatNum; + from(primitive: number): IntNum | RatNum; + /** + * Coerce a rational into a Real expression + * @category Functions */ + from(primitive: CoercibleRational): RatNum; /** * Coerce a big number into a Integer expression * @category Functions */ From 56aa4261b64e7552b71f3c3399776da09fa90926 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Thu, 23 Jun 2022 07:42:53 -0700 Subject: [PATCH 005/125] fix #6082 --- src/smt/theory_special_relations.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/smt/theory_special_relations.cpp b/src/smt/theory_special_relations.cpp index b25c2bac8..40f94cdbc 100644 --- a/src/smt/theory_special_relations.cpp +++ b/src/smt/theory_special_relations.cpp @@ -36,7 +36,7 @@ namespace smt { if (!m_next) { sort* s = decl()->get_domain(0); sort* domain[2] = {s, s}; - m_next = m.mk_fresh_func_decl("next", "", 2, domain, s); + m_next = m.mk_fresh_func_decl("specrel.next", "", 2, domain, s, false); } return m_next; } From 30165ed40af81a3574da92c0abf8fdc8086f0d86 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sun, 26 Jun 2022 20:37:18 -0700 Subject: [PATCH 006/125] fix #6105 non-linear division axioms appear incomplete. Fixed for legacy arithmetic. Fix pending for new arithmetic solver. --- src/smt/theory_arith_core.h | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/smt/theory_arith_core.h b/src/smt/theory_arith_core.h index 88db34012..1610c4f53 100644 --- a/src/smt/theory_arith_core.h +++ b/src/smt/theory_arith_core.h @@ -574,7 +574,8 @@ namespace smt { lower = m_util.mk_ge(mod, zero); upper = m_util.mk_le(mod, abs_divisor); TRACE("div_axiom_bug", - tout << "eqz: " << eqz << " neq: " << eq << "\n"; + tout << "eqz: " << eqz << "\n"; + tout << "neq: " << eq << "\n"; tout << "lower: " << lower << "\n"; tout << "upper: " << upper << "\n";); @@ -583,14 +584,27 @@ namespace smt { mk_axiom(eqz, upper, !m_util.is_numeral(abs_divisor)); rational k; - if (!m_util.is_numeral(divisor)) { - // (=> (> y 0) (<= (* y (div x y)) x)) - // (=> (< y 0) ???) - expr_ref div_ge(m), div_non_pos(m); + if (m_util.is_zero(dividend) && false) { + mk_axiom(eqz, m.mk_eq(div, zero)); + mk_axiom(eqz, m.mk_eq(mod, zero)); + } + + // (or (= y 0) (<= (* y (div x y)) x)) + // (or (<= y 0) (>= (* (+ y 1) (div x y)) x)) + // (or (>= y 0) (>= (* (+ y -1) (div x y)) x)) + else if (!m_util.is_numeral(divisor)) { + expr_ref div_ge(m), div_le(m), ge(m), le(m); div_ge = m_util.mk_ge(m_util.mk_sub(dividend, m_util.mk_mul(divisor, div)), zero); - s(div_ge); - div_non_pos = m_util.mk_le(divisor, zero); - mk_axiom(div_non_pos, div_ge, false); + s(div_ge); + mk_axiom(eqz, div_ge, false); + ge = m_util.mk_ge(divisor, zero); + le = m_util.mk_le(divisor, zero); + div_le = m_util.mk_le(m_util.mk_sub(dividend, m_util.mk_mul(m_util.mk_add(divisor, one), div)), zero); + s(div_le); + mk_axiom(le, div_le, false); + div_le = m_util.mk_le(m_util.mk_sub(dividend, m_util.mk_mul(m_util.mk_sub(divisor, one), div)), zero); + s(div_le); + mk_axiom(ge, div_le, false); } if (m_params.m_arith_enum_const_mod && m_util.is_numeral(divisor, k) && From 1fcf7cf0b7789e08d11f51b447584908e8ac7de6 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 27 Jun 2022 09:02:53 -0700 Subject: [PATCH 007/125] add nl div mod axioms --- src/sat/smt/arith_axioms.cpp | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/sat/smt/arith_axioms.cpp b/src/sat/smt/arith_axioms.cpp index 7cb6a05b6..e660c8f16 100644 --- a/src/sat/smt/arith_axioms.cpp +++ b/src/sat/smt/arith_axioms.cpp @@ -128,6 +128,22 @@ namespace arith { } else { + + expr_ref mone(a.mk_int(-1), m); + expr_ref abs_q(m.mk_ite(a.mk_ge(q, zero), q, a.mk_uminus(q)), m); + literal eqz = mk_literal(m.mk_eq(q, zero)); + literal mod_ge_0 = mk_literal(a.mk_ge(mod, zero)); + literal mod_lt_q = mk_literal(a.mk_lt(a.mk_sub(mod, abs_q), mone)); + + // q = 0 or p = (p mod q) + q * (p div q) + // q = 0 or (p mod q) >= 0 + // q = 0 or (p mod q) < abs(q) + + add_clause(eqz, eq); + add_clause(eqz, mod_ge_0); + add_clause(eqz, mod_lt_q); + +#if 0 /*literal div_ge_0 = */ mk_literal(a.mk_ge(div, zero)); /*literal div_le_0 = */ mk_literal(a.mk_le(div, zero)); /*literal p_ge_0 = */ mk_literal(a.mk_ge(p, zero)); @@ -139,6 +155,8 @@ namespace arith { // q <= 0 or (p mod q) >= 0 // q <= 0 or (p mod q) < q // q >= 0 or (p mod q) < -q + + literal q_ge_0 = mk_literal(a.mk_ge(q, zero)); literal q_le_0 = mk_literal(a.mk_le(q, zero)); @@ -148,6 +166,22 @@ namespace arith { add_clause(q_le_0, mod_ge_0); add_clause(q_le_0, ~mk_literal(a.mk_ge(a.mk_sub(mod, q), zero))); add_clause(q_ge_0, ~mk_literal(a.mk_ge(a.mk_add(mod, q), zero))); +#endif + + if (a.is_zero(p)) { + add_clause(eqz, mk_literal(m.mk_eq(mod, zero))); + add_clause(eqz, mk_literal(m.mk_eq(div, zero))); + } + else if (!a.is_numeral(q)) { + // (or (= y 0) (<= (* y (div x y)) x)) + // (or (<= y 0) (>= (* (+ y 1) (div x y)) x)) + // (or (>= y 0) (>= (* (+ y -1) (div x y)) x)) + expr_ref one(a.mk_int(1), m); + add_clause(eqz, mk_literal(a.mk_le(a.mk_mul(q, div), p))); + add_clause(mk_literal(a.mk_le(q, zero)), mk_literal(a.mk_ge(a.mk_mul(a.mk_add(q, one), div), q))); + add_clause(mk_literal(a.mk_ge(q, zero)), mk_literal(a.mk_ge(a.mk_mul(a.mk_add(q, mone), div), q))); + } + } if (get_config().m_arith_enum_const_mod && k.is_pos() && k < rational(8)) { From 61f5489223bef249bee10574fa41ffa68136a47b Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 27 Jun 2022 16:53:18 -0700 Subject: [PATCH 008/125] fix #6107 --- src/opt/opt_context.cpp | 7 +-- src/sat/sat_solver.cpp | 11 ++-- src/sat/smt/pb_solver.cpp | 104 +++++++++++++++++++------------------- 3 files changed, 61 insertions(+), 61 deletions(-) diff --git a/src/opt/opt_context.cpp b/src/opt/opt_context.cpp index fcc8cdd46..e116b07c5 100644 --- a/src/opt/opt_context.cpp +++ b/src/opt/opt_context.cpp @@ -682,14 +682,11 @@ namespace opt { void context::init_solver() { setup_arith_solver(); + m_sat_solver = nullptr; m_opt_solver = alloc(opt_solver, m, m_params, *m_fm); m_opt_solver->set_logic(m_logic); m_solver = m_opt_solver.get(); - m_opt_solver->ensure_pb(); - - //if (opt_params(m_params).priority() == symbol("pareto") || - // (opt_params(m_params).priority() == symbol("lex") && m_objectives.size() > 1)) { - //} + m_opt_solver->ensure_pb(); } void context::setup_arith_solver() { diff --git a/src/sat/sat_solver.cpp b/src/sat/sat_solver.cpp index 729b3c1f4..9570da396 100644 --- a/src/sat/sat_solver.cpp +++ b/src/sat/sat_solver.cpp @@ -2029,17 +2029,18 @@ namespace sat { sort_watch_lits(); CASSERT("sat_simplify_bug", check_invariant()); - m_probing(); - CASSERT("sat_missed_prop", check_missed_propagation()); - CASSERT("sat_simplify_bug", check_invariant()); - m_asymm_branch(false); - CASSERT("sat_missed_prop", check_missed_propagation()); CASSERT("sat_simplify_bug", check_invariant()); if (m_ext) { m_ext->clauses_modifed(); m_ext->simplify(); } + + m_probing(); + CASSERT("sat_missed_prop", check_missed_propagation()); + CASSERT("sat_simplify_bug", check_invariant()); + m_asymm_branch(false); + if (m_config.m_lookahead_simplify && !m_ext) { lookahead lh(*this); lh.simplify(true); diff --git a/src/sat/smt/pb_solver.cpp b/src/sat/smt/pb_solver.cpp index f85872db6..09f1ffaca 100644 --- a/src/sat/smt/pb_solver.cpp +++ b/src/sat/smt/pb_solver.cpp @@ -34,7 +34,7 @@ namespace pb { void solver::set_conflict(constraint& c, literal lit) { m_stats.m_num_conflicts++; - TRACE("ba", display(tout, c, true); ); + TRACE("pb", display(tout, c, true); ); if (!validate_conflict(c)) { IF_VERBOSE(0, display(verbose_stream(), c, true)); UNREACHABLE(); @@ -56,7 +56,7 @@ namespace pb { default: m_stats.m_num_propagations++; m_num_propagations_since_pop++; - //TRACE("ba", tout << "#prop: " << m_stats.m_num_propagations << " - " << c.lit() << " => " << lit << "\n";); + //TRACE("pb", tout << "#prop: " << m_stats.m_num_propagations << " - " << c.lit() << " => " << lit << "\n";); SASSERT(validate_unit_propagation(c, lit)); assign(lit, sat::justification::mk_ext_justification(s().scope_lvl(), c.cindex())); break; @@ -69,7 +69,7 @@ namespace pb { void solver::simplify(constraint& p) { SASSERT(s().at_base_lvl()); if (p.lit() != sat::null_literal && value(p.lit()) == l_false) { - TRACE("ba", tout << "pb: flip sign " << p << "\n";); + TRACE("pb", tout << "pb: flip sign " << p << "\n";); IF_VERBOSE(2, verbose_stream() << "sign is flipped " << p << "\n";); return; } @@ -280,7 +280,7 @@ namespace pb { */ lbool solver::add_assign(pbc& p, literal alit) { BADLOG(display(verbose_stream() << "assign: " << alit << " watch: " << p.num_watch() << " size: " << p.size(), p, true)); - TRACE("ba", display(tout << "assign: " << alit << "\n", p, true);); + TRACE("pb", display(tout << "assign: " << alit << "\n", p, true);); SASSERT(!inconsistent()); unsigned sz = p.size(); unsigned bound = p.k(); @@ -290,6 +290,7 @@ namespace pb { SASSERT(p.lit() == sat::null_literal || value(p.lit()) == l_true); SASSERT(num_watch <= sz); SASSERT(num_watch > 0); + SASSERT(validate_watch(p, sat::null_literal)); unsigned index = 0; m_a_max = 0; m_pb_undef.reset(); @@ -311,8 +312,6 @@ namespace pb { return l_undef; } - SASSERT(validate_watch(p, sat::null_literal)); - // SASSERT(validate_watch(p, sat::null_literal)); SASSERT(index < num_watch); unsigned index1 = index + 1; @@ -349,7 +348,7 @@ namespace pb { SASSERT(validate_watch(p, sat::null_literal)); BADLOG(display(verbose_stream() << "conflict: " << alit << " watch: " << p.num_watch() << " size: " << p.size(), p, true)); SASSERT(bound <= slack); - TRACE("ba", tout << "conflict " << alit << "\n";); + TRACE("pb", tout << "conflict " << alit << "\n";); set_conflict(p, alit); return l_false; } @@ -366,13 +365,14 @@ namespace pb { p.swap(num_watch, index); + // // slack >= bound, but slack - w(l) < bound // l must be true. // if (slack < bound + m_a_max) { BADLOG(verbose_stream() << "slack " << slack << " " << bound << " " << m_a_max << "\n";); - TRACE("ba", tout << p << "\n"; for(auto j : m_pb_undef) tout << j << " "; tout << "\n";); + TRACE("pb", tout << p << "\n"; for(auto j : m_pb_undef) tout << j << " "; tout << "\n";); for (unsigned index1 : m_pb_undef) { if (index1 == num_watch) { index1 = index; @@ -389,7 +389,7 @@ namespace pb { SASSERT(validate_watch(p, alit)); // except that alit is still watched. - TRACE("ba", display(tout << "assign: " << alit << "\n", p, true);); + TRACE("pb", display(tout << "assign: " << alit << "\n", p, true);); BADLOG(verbose_stream() << "unwatch " << alit << " watch: " << p.num_watch() << " size: " << p.size() << " slack: " << p.slack() << " " << inconsistent() << "\n"); @@ -547,7 +547,7 @@ namespace pb { literal l = literal(v, c1 < 0); c1 = std::abs(c1); unsigned c = static_cast(c1); - // TRACE("ba", tout << l << " " << c << "\n";); + // TRACE("pb", tout << l << " " << c << "\n";); m_overflow |= c != c1; return wliteral(c, l); } @@ -638,7 +638,7 @@ namespace pb { m_bound = 0; literal consequent = s().m_not_l; sat::justification js = s().m_conflict; - TRACE("ba", tout << consequent << " " << js << "\n";); + TRACE("pb", tout << consequent << " " << js << "\n";); bool unique_max; m_conflict_lvl = s().get_max_lvl(consequent, js, unique_max); if (m_conflict_lvl == 0) { @@ -667,7 +667,7 @@ namespace pb { } DEBUG_CODE(TRACE("sat_verbose", display(tout, m_A););); - TRACE("ba", tout << "process consequent: " << consequent << " : "; s().display_justification(tout, js) << "\n";); + TRACE("pb", tout << "process consequent: " << consequent << " : "; s().display_justification(tout, js) << "\n";); SASSERT(offset > 0); DEBUG_CODE(justification2pb(js, consequent, offset, m_B);); @@ -741,7 +741,7 @@ namespace pb { inc_bound(offset); inc_coeff(consequent, offset); get_antecedents(consequent, p, m_lemma); - TRACE("ba", display(tout, p, true); tout << m_lemma << "\n";); + TRACE("pb", display(tout, p, true); tout << m_lemma << "\n";); if (_debug_conflict) { verbose_stream() << consequent << " "; verbose_stream() << "antecedents: " << m_lemma << "\n"; @@ -766,7 +766,7 @@ namespace pb { active2pb(m_C); VERIFY(validate_resolvent()); m_A = m_C; - TRACE("ba", display(tout << "conflict: ", m_A););); + TRACE("pb", display(tout << "conflict: ", m_A););); cut(); @@ -887,7 +887,7 @@ namespace pb { } } ineq.divide(c); - TRACE("ba", display(tout << "var: " << v << " " << c << ": ", ineq, true);); + TRACE("pb", display(tout << "var: " << v << " " << c << ": ", ineq, true);); } void solver::round_to_one(bool_var w) { @@ -905,7 +905,7 @@ namespace pb { SASSERT(validate_lemma()); divide(c); SASSERT(validate_lemma()); - TRACE("ba", active2pb(m_B); display(tout, m_B, true);); + TRACE("pb", active2pb(m_B); display(tout, m_B, true);); } void solver::divide(unsigned c) { @@ -935,14 +935,14 @@ namespace pb { } void solver::resolve_with(ineq const& ineq) { - TRACE("ba", display(tout, ineq, true);); + TRACE("pb", display(tout, ineq, true);); inc_bound(ineq.m_k); - TRACE("ba", tout << "bound: " << m_bound << "\n";); + TRACE("pb", tout << "bound: " << m_bound << "\n";); for (unsigned i = ineq.size(); i-- > 0; ) { literal l = ineq.lit(i); inc_coeff(l, static_cast(ineq.coeff(i))); - TRACE("ba", tout << "bound: " << m_bound << " lit: " << l << " coeff: " << ineq.coeff(i) << "\n";); + TRACE("pb", tout << "bound: " << m_bound << " lit: " << l << " coeff: " << ineq.coeff(i) << "\n";); } } @@ -995,11 +995,11 @@ namespace pb { consequent.neg(); process_antecedent(consequent, 1); } - TRACE("ba", tout << consequent << " " << js << "\n";); + TRACE("pb", tout << consequent << " " << js << "\n";); unsigned idx = s().m_trail.size() - 1; do { - TRACE("ba", s().display_justification(tout << "process consequent: " << consequent << " : ", js) << "\n"; + TRACE("pb", s().display_justification(tout << "process consequent: " << consequent << " : ", js) << "\n"; if (consequent != sat::null_literal) { active2pb(m_A); display(tout, m_A, true); } ); @@ -1069,7 +1069,7 @@ namespace pb { } else { SASSERT(k > c); - TRACE("ba", tout << "visited: " << l << "\n";); + TRACE("pb", tout << "visited: " << l << "\n";); k -= c; } } @@ -1118,7 +1118,7 @@ namespace pb { } } if (idx == 0) { - TRACE("ba", tout << "there is no consequent\n";); + TRACE("pb", tout << "there is no consequent\n";); goto bail_out; } --idx; @@ -1131,7 +1131,7 @@ namespace pb { js = s().m_justification[v]; } while (m_num_marks > 0 && !m_overflow); - TRACE("ba", active2pb(m_A); display(tout, m_A, true);); + TRACE("pb", active2pb(m_A); display(tout, m_A, true);); // TBD: check if this is useful if (!m_overflow && consequent != sat::null_literal) { @@ -1143,7 +1143,7 @@ namespace pb { } bail_out: - TRACE("ba", tout << "bail " << m_overflow << "\n";); + TRACE("pb", tout << "bail " << m_overflow << "\n";); if (m_overflow) { ++m_stats.m_num_overflow; m_overflow = false; @@ -1199,23 +1199,23 @@ namespace pb { } } if (slack >= 0) { - TRACE("ba", tout << "slack is non-negative\n";); + TRACE("pb", tout << "slack is non-negative\n";); IF_VERBOSE(20, verbose_stream() << "(sat.card slack: " << slack << " skipped: " << num_skipped << ")\n";); return false; } if (m_overflow) { - TRACE("ba", tout << "overflow\n";); + TRACE("pb", tout << "overflow\n";); return false; } if (m_lemma[0] == sat::null_literal) { if (m_lemma.size() == 1) { s().set_conflict(sat::justification(0)); } - TRACE("ba", tout << "no asserting literal\n";); + TRACE("pb", tout << "no asserting literal\n";); return false; } - TRACE("ba", tout << m_lemma << "\n";); + TRACE("pb", tout << m_lemma << "\n";); if (get_config().m_drat && m_solver) { s().m_drat.add(m_lemma, sat::status::th(true, get_id())); @@ -1224,7 +1224,7 @@ namespace pb { s().m_lemma.reset(); s().m_lemma.append(m_lemma); for (unsigned i = 1; i < m_lemma.size(); ++i) { - CTRACE("ba", s().is_marked(m_lemma[i].var()), tout << "marked: " << m_lemma[i] << "\n";); + CTRACE("pb", s().is_marked(m_lemma[i].var()), tout << "marked: " << m_lemma[i] << "\n";); s().mark(m_lemma[i].var()); } return true; @@ -1346,11 +1346,11 @@ namespace pb { } solver::solver(ast_manager& m, sat::sat_internalizer& si, euf::theory_id id) - : euf::th_solver(m, symbol("ba"), id), + : euf::th_solver(m, symbol("pb"), id), si(si), m_pb(m), m_lookahead(nullptr), m_constraint_id(0), m_ba(*this), m_sort(m_ba) { - TRACE("ba", tout << this << "\n";); + TRACE("pb", tout << this << "\n";); m_num_propagations_since_pop = 0; } @@ -1418,6 +1418,8 @@ namespace pb { } else if (lit == sat::null_literal) { init_watch(*c); + if (c->is_pb()) + validate_watch(c->to_pb(), sat::null_literal); } else { if (m_solver) m_solver->set_external(lit.var()); @@ -1569,7 +1571,7 @@ namespace pb { } void solver::get_antecedents(literal l, pbc const& p, literal_vector& r) { - TRACE("ba", display(tout << l << " level: " << s().scope_lvl() << " ", p, true);); + TRACE("pb", display(tout << l << " level: " << s().scope_lvl() << " ", p, true);); SASSERT(p.lit() == sat::null_literal || value(p.lit()) == l_true); if (p.lit() != sat::null_literal) { @@ -1621,7 +1623,7 @@ namespace pb { if (j < p.num_watch()) { j = p.num_watch(); } - CTRACE("ba", coeff == 0, display(tout << l << " coeff: " << coeff << "\n", p, true);); + CTRACE("pb", coeff == 0, display(tout << l << " coeff: " << coeff << "\n", p, true);); if (_debug_conflict) { IF_VERBOSE(0, verbose_stream() << "coeff " << coeff << "\n";); @@ -1672,7 +1674,7 @@ namespace pb { for (unsigned i = 0; !found && i < c.k(); ++i) { found = c[i] == l; } - CTRACE("ba",!found, s().display(tout << l << ":" << c << "\n");); + CTRACE("pb",!found, s().display(tout << l << ":" << c << "\n");); SASSERT(found);); VERIFY(c.lit() == sat::null_literal || value(c.lit()) != l_false); @@ -1712,7 +1714,7 @@ namespace pb { } void solver::remove_constraint(constraint& c, char const* reason) { - TRACE("ba", display(tout << "remove ", c, true) << " " << reason << "\n";); + TRACE("pb", display(tout << "remove ", c, true) << " " << reason << "\n";); IF_VERBOSE(21, display(verbose_stream() << "remove " << reason << " ", c, true);); c.nullify_tracking_literal(*this); clear_watch(c); @@ -1886,7 +1888,7 @@ namespace pb { } void solver::gc_half(char const* st_name) { - TRACE("ba", tout << "gc\n";); + TRACE("pb", tout << "gc\n";); unsigned sz = m_learned.size(); unsigned new_sz = sz/2; unsigned removed = 0; @@ -1933,7 +1935,7 @@ namespace pb { // literal is assigned to false. unsigned sz = c.size(); unsigned bound = c.k(); - TRACE("ba", tout << "assign: " << c.lit() << ": " << ~alit << "@" << lvl(~alit) << " " << c << "\n";); + TRACE("pb", tout << "assign: " << c.lit() << ": " << ~alit << "@" << lvl(~alit) << " " << c << "\n";); SASSERT(0 < bound && bound <= sz); if (bound == sz) { @@ -1971,7 +1973,7 @@ namespace pb { // conflict if (bound != index && value(c[bound]) == l_false) { - TRACE("ba", tout << "conflict " << c[bound] << " " << alit << "\n";); + TRACE("pb", tout << "conflict " << c[bound] << " " << alit << "\n";); if (c.lit() != sat::null_literal && value(c.lit()) == l_undef) { if (index + 1 < bound) c.swap(index, bound - 1); assign(c, ~c.lit()); @@ -1985,7 +1987,7 @@ namespace pb { c.swap(index, bound); } - // TRACE("ba", tout << "no swap " << index << " " << alit << "\n";); + // TRACE("pb", tout << "no swap " << index << " " << alit << "\n";); // there are no literals to swap with, // prepare for unit propagation by swapping the false literal into // position bound. Then literals in positions 0..bound-1 have to be @@ -2351,7 +2353,7 @@ namespace pb { } if (!all_units) { - TRACE("ba", tout << "replacing by pb: " << c << "\n";); + TRACE("pb", tout << "replacing by pb: " << c << "\n";); m_wlits.reset(); for (unsigned i = 0; i < sz; ++i) { m_wlits.push_back(wliteral(coeffs[i], c[i])); @@ -2914,13 +2916,13 @@ namespace pb { SASSERT(&c1 != &c2); if (subsumes(c1, c2, slit)) { if (slit.empty()) { - TRACE("ba", tout << "subsume cardinality\n" << c1 << "\n" << c2 << "\n";); + TRACE("pb", tout << "subsume cardinality\n" << c1 << "\n" << c2 << "\n";); remove_constraint(c2, "subsumed"); ++m_stats.m_num_pb_subsumes; set_non_learned(c1); } else { - TRACE("ba", tout << "self subsume cardinality\n";); + TRACE("pb", tout << "self subsume cardinality\n";); IF_VERBOSE(11, verbose_stream() << "self-subsume cardinality\n"; verbose_stream() << c1 << "\n"; @@ -2952,7 +2954,7 @@ namespace pb { // self-subsumption is TBD } else { - TRACE("ba", tout << "remove\n" << c1 << "\n" << c2 << "\n";); + TRACE("pb", tout << "remove\n" << c1 << "\n" << c2 << "\n";); removed_clauses.push_back(&c2); ++m_stats.m_num_clause_subsumes; set_non_learned(c1); @@ -3284,7 +3286,7 @@ namespace pb { val += wl.first; } } - CTRACE("ba", val >= 0, active2pb(m_A); display(tout, m_A, true);); + CTRACE("pb", val >= 0, active2pb(m_A); display(tout, m_A, true);); return val < 0; } @@ -3297,7 +3299,7 @@ namespace pb { if (!is_false(wl.second)) k += wl.first; } - CTRACE("ba", k > 0, display(tout, ineq, true);); + CTRACE("pb", k > 0, display(tout, ineq, true);); return k <= 0; } @@ -3356,7 +3358,7 @@ namespace pb { return nullptr; } constraint* c = add_pb_ge(sat::null_literal, m_wlits, m_bound, true); - TRACE("ba", if (c) display(tout, *c, true);); + TRACE("pb", if (c) display(tout, *c, true);); ++m_stats.m_num_lemmas; return c; } @@ -3587,7 +3589,7 @@ namespace pb { s0.assign_scoped(l2); s0.assign_scoped(l3); lbool is_sat = s0.check(); - TRACE("ba", s0.display(tout << "trying sat encoding");); + TRACE("pb", s0.display(tout << "trying sat encoding");); if (is_sat == l_false) return true; IF_VERBOSE(0, @@ -3698,11 +3700,11 @@ namespace pb { bool solver::validate_conflict(literal_vector const& lits, ineq& p) { for (literal l : lits) { if (value(l) != l_false) { - TRACE("ba", tout << "literal " << l << " is not false\n";); + TRACE("pb", tout << "literal " << l << " is not false\n";); return false; } if (!p.contains(l)) { - TRACE("ba", tout << "lemma contains literal " << l << " not in inequality\n";); + TRACE("pb", tout << "lemma contains literal " << l << " not in inequality\n";); return false; } } @@ -3713,7 +3715,7 @@ namespace pb { value += coeff; } } - CTRACE("ba", value >= p.m_k, tout << "slack: " << value << " bound " << p.m_k << "\n"; + CTRACE("pb", value >= p.m_k, tout << "slack: " << value << " bound " << p.m_k << "\n"; display(tout, p); tout << lits << "\n";); return value < p.m_k; From 4c8f6b60ce884f5132eca3c53f8db17b831adb17 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 27 Jun 2022 20:51:30 -0700 Subject: [PATCH 009/125] fix #6107 --- src/sat/smt/pb_solver.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sat/smt/pb_solver.cpp b/src/sat/smt/pb_solver.cpp index 09f1ffaca..c92efdb7d 100644 --- a/src/sat/smt/pb_solver.cpp +++ b/src/sat/smt/pb_solver.cpp @@ -596,12 +596,13 @@ namespace pb { s().reset_mark(v); --m_num_marks; } - if (idx == 0 && !_debug_conflict) { + if (idx == 0 && !_debug_conflict && m_num_marks > 0) { _debug_conflict = true; _debug_var2position.reserve(s().num_vars()); for (unsigned i = 0; i < lits.size(); ++i) { _debug_var2position[lits[i].var()] = i; } + IF_VERBOSE(0, verbose_stream() << "num marks: " << m_num_marks << "\n"); IF_VERBOSE(0, active2pb(m_A); uint64_t c = 0; @@ -617,20 +618,19 @@ namespace pb { } } m_num_marks = 0; - resolve_conflict(); + resolve_conflict(); + exit(0); } --idx; } } lbool solver::resolve_conflict() { - if (0 == m_num_propagations_since_pop) { + if (0 == m_num_propagations_since_pop) return l_undef; - } - if (s().m_config.m_pb_resolve == sat::PB_ROUNDING) { + if (s().m_config.m_pb_resolve == sat::PB_ROUNDING) return resolve_conflict_rs(); - } m_overflow = false; reset_coeffs(); From b43965bf05ac2c01c26eb728623b7b183a63084f Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 28 Jun 2022 09:42:28 -0700 Subject: [PATCH 010/125] make user propagator work with combined solver Then users don't have to specify SImpleSolver, but can use "Solver" --- src/solver/combined_solver.cpp | 42 ++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/solver/combined_solver.cpp b/src/solver/combined_solver.cpp index bfe495b6e..29a5c0a8f 100644 --- a/src/solver/combined_solver.cpp +++ b/src/solver/combined_solver.cpp @@ -343,7 +343,49 @@ public: else return m_solver2->get_labels(r); } + + void user_propagate_init( + void* ctx, + user_propagator::push_eh_t& push_eh, + user_propagator::pop_eh_t& pop_eh, + user_propagator::fresh_eh_t& fresh_eh) override { + switch_inc_mode(); + m_solver2->user_propagate_init(ctx, push_eh, pop_eh, fresh_eh); + } + + void user_propagate_register_fixed(user_propagator::fixed_eh_t& fixed_eh) override { + m_solver2->user_propagate_register_fixed(fixed_eh); + } + + void user_propagate_register_final(user_propagator::final_eh_t& final_eh) override { + m_solver2->user_propagate_register_final(final_eh); + } + + void user_propagate_register_eq(user_propagator::eq_eh_t& eq_eh) override { + m_solver2->user_propagate_register_eq(eq_eh); + } + + void user_propagate_register_diseq(user_propagator::eq_eh_t& diseq_eh) override { + m_solver2->user_propagate_register_diseq(diseq_eh); + } + + void user_propagate_register_expr(expr* e) override { + m_solver2->user_propagate_register_expr(e); + } + + void user_propagate_register_created(user_propagator::created_eh_t& r) override { + m_solver2->user_propagate_register_created(r); + } + + void user_propagate_register_decide(user_propagator::decide_eh_t& r) override { + m_solver2->user_propagate_register_decide(r); + } + + void user_propagate_clear() override { + m_solver2->user_propagate_clear(); + } + }; From 9836d5e6fc8c0ca2b11d06a66a658d324885e3bb Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 28 Jun 2022 12:46:29 -0700 Subject: [PATCH 011/125] missing public --- src/api/dotnet/UserPropagator.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/api/dotnet/UserPropagator.cs b/src/api/dotnet/UserPropagator.cs index 273bd4da9..adabe3ff5 100644 --- a/src/api/dotnet/UserPropagator.cs +++ b/src/api/dotnet/UserPropagator.cs @@ -207,16 +207,16 @@ namespace Microsoft.Z3 /// /// Declare combination of assigned expressions a conflict /// - void Conflict(params Expr[] terms) { - Propagate(terms, ctx.MkFalse()); + public void Conflict(params Expr[] terms) { + Propagate(terms, ctx.MkFalse()); } /// /// Propagate consequence /// - void Propagate(Expr[] terms, Expr conseq) { - var nTerms = Z3Object.ArrayToNative(terms); - Native.Z3_solver_propagate_consequence(ctx.nCtx, this.callback, (uint)nTerms.Length, nTerms, 0u, null, null, conseq.NativeObject); + public void Propagate(Expr[] terms, Expr conseq) { + var nTerms = Z3Object.ArrayToNative(terms); + Native.Z3_solver_propagate_consequence(ctx.nCtx, this.callback, (uint)nTerms.Length, nTerms, 0u, null, null, conseq.NativeObject); } From 820c782b5e180bb0e4f731d7f50a690d8307f992 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 28 Jun 2022 13:03:52 -0700 Subject: [PATCH 012/125] pinned semantics --- src/api/dotnet/UserPropagator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/dotnet/UserPropagator.cs b/src/api/dotnet/UserPropagator.cs index adabe3ff5..0e0ad818a 100644 --- a/src/api/dotnet/UserPropagator.cs +++ b/src/api/dotnet/UserPropagator.cs @@ -161,7 +161,7 @@ namespace Microsoft.Z3 /// public UserPropagator(Solver s) { - gch = GCHandle.Alloc(this); + gch = GCHandle.Alloc(this, GCHandleType.Pinned); solver = s; ctx = solver.Context; var cb = GCHandle.ToIntPtr(gch); From 556f0d7b5f109adbb33db0686e833941fb76f831 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 28 Jun 2022 14:09:22 -0700 Subject: [PATCH 013/125] use static list to connect managed and unmanaged objects --- src/api/dotnet/UserPropagator.cs | 50 ++++++++++++++------------------ 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/src/api/dotnet/UserPropagator.cs b/src/api/dotnet/UserPropagator.cs index 0e0ad818a..cd8fa3ba5 100644 --- a/src/api/dotnet/UserPropagator.cs +++ b/src/api/dotnet/UserPropagator.cs @@ -62,9 +62,12 @@ namespace Microsoft.Z3 /// public delegate void DecideEh(ref Expr term, ref uint idx, ref int phase); - Solver solver; + // access managed objects through a static array. + // thread safety is ignored for now. + static List propagators = new List(); + int id; + Solver solver; Context ctx; - GCHandle gch; Z3_solver_callback callback = IntPtr.Zero; FixedEh fixed_eh; Action final_eh; @@ -88,65 +91,56 @@ namespace Microsoft.Z3 static void _push(voidp ctx, Z3_solver_callback cb) { - var gch = GCHandle.FromIntPtr(ctx); - var prop = (UserPropagator)gch.Target; + var prop = propagators[ctx.ToInt32()]; prop.Callback(() => prop.Push(), cb); } static void _pop(voidp ctx, Z3_solver_callback cb, uint num_scopes) { - var gch = GCHandle.FromIntPtr(ctx); - var prop = (UserPropagator)gch.Target; + var prop = propagators[ctx.ToInt32()]; prop.Callback(() => prop.Pop(num_scopes), cb); } static voidp _fresh(voidp _ctx, Z3_context new_context) { - var gch = GCHandle.FromIntPtr(_ctx); - var prop = (UserPropagator)gch.Target; + var prop = propagators[_ctx.ToInt32()]; var ctx = new Context(new_context); - var prop1 = prop.Fresh(ctx); - return GCHandle.ToIntPtr(prop1.gch); + var prop1 = prop.Fresh(prop.ctx); + return new IntPtr(prop1.id); } static void _fixed(voidp ctx, Z3_solver_callback cb, Z3_ast _term, Z3_ast _value) { - var gch = GCHandle.FromIntPtr(ctx); - var prop = (UserPropagator)gch.Target; + var prop = propagators[ctx.ToInt32()]; using var term = Expr.Create(prop.ctx, _term); using var value = Expr.Create(prop.ctx, _value); prop.Callback(() => prop.fixed_eh(term, value), cb); } static void _final(voidp ctx, Z3_solver_callback cb) { - var gch = GCHandle.FromIntPtr(ctx); - var prop = (UserPropagator)gch.Target; + var prop = propagators[ctx.ToInt32()]; prop.Callback(() => prop.final_eh(), cb); } static void _eq(voidp ctx, Z3_solver_callback cb, Z3_ast a, Z3_ast b) { - var gch = GCHandle.FromIntPtr(ctx); - var prop = (UserPropagator)gch.Target; + var prop = propagators[ctx.ToInt32()]; using var s = Expr.Create(prop.ctx, a); using var t = Expr.Create(prop.ctx, b); prop.Callback(() => prop.eq_eh(s, t), cb); } static void _diseq(voidp ctx, Z3_solver_callback cb, Z3_ast a, Z3_ast b) { - var gch = GCHandle.FromIntPtr(ctx); - var prop = (UserPropagator)gch.Target; + var prop = propagators[ctx.ToInt32()]; using var s = Expr.Create(prop.ctx, a); using var t = Expr.Create(prop.ctx, b); prop.Callback(() => prop.diseq_eh(s, t), cb); } static void _created(voidp ctx, Z3_solver_callback cb, Z3_ast a) { - var gch = GCHandle.FromIntPtr(ctx); - var prop = (UserPropagator)gch.Target; + var prop = propagators[ctx.ToInt32()]; using var t = Expr.Create(prop.ctx, a); prop.Callback(() => prop.created_eh(t), cb); } static void _decide(voidp ctx, Z3_solver_callback cb, ref Z3_ast a, ref uint idx, ref int phase) { - var gch = GCHandle.FromIntPtr(ctx); - var prop = (UserPropagator)gch.Target; + var prop = propagators[ctx.ToInt32()]; var t = Expr.Create(prop.ctx, a); var u = t; prop.callback = cb; @@ -161,11 +155,11 @@ namespace Microsoft.Z3 /// public UserPropagator(Solver s) { - gch = GCHandle.Alloc(this, GCHandleType.Pinned); + id = propagators.Count; + propagators.Add(this); solver = s; ctx = solver.Context; - var cb = GCHandle.ToIntPtr(gch); - Native.Z3_solver_propagate_init(ctx.nCtx, solver.NativeObject, cb, _push, _pop, _fresh); + Native.Z3_solver_propagate_init(ctx.nCtx, solver.NativeObject, new IntPtr(id), _push, _pop, _fresh); } /// @@ -173,7 +167,8 @@ namespace Microsoft.Z3 /// public UserPropagator(Context _ctx) { - gch = GCHandle.Alloc(this); + id = propagators.Count; + propagators.Add(this); solver = null; ctx = _ctx; } @@ -183,8 +178,7 @@ namespace Microsoft.Z3 /// ~UserPropagator() { - if (gch != null) - gch.Free(); + propagators[id] = null; if (solver == null) ctx.Dispose(); } From 798a4ee86efcd1cd46557d98507a418bc2368bcc Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 28 Jun 2022 14:24:05 -0700 Subject: [PATCH 014/125] use IEnumerator and format --- src/api/dotnet/UserPropagator.cs | 384 +++++++++++++++++-------------- 1 file changed, 205 insertions(+), 179 deletions(-) diff --git a/src/api/dotnet/UserPropagator.cs b/src/api/dotnet/UserPropagator.cs index cd8fa3ba5..eb181b272 100644 --- a/src/api/dotnet/UserPropagator.cs +++ b/src/api/dotnet/UserPropagator.cs @@ -37,280 +37,306 @@ namespace Microsoft.Z3 /// /// Propagator context for .Net /// - public class UserPropagator + public class UserPropagator { /// /// Delegate type for fixed callback /// - public delegate void FixedEh(Expr term, Expr value); + public delegate void FixedEh(Expr term, Expr value); /// /// Delegate type for equality or disequality callback /// - public delegate void EqEh(Expr term, Expr value); + public delegate void EqEh(Expr term, Expr value); /// /// Delegate type for when a new term using a registered function symbol is created internally /// - public delegate void CreatedEh(Expr term); + public delegate void CreatedEh(Expr term); /// /// Delegate type for callback into solver's branching - /// A bit-vector or Boolean used for branching - /// If the term is a bit-vector, then an index into the bit-vector being branched on - /// Set phase to -1 (false) or 1 (true) to override solver's phase - /// - public delegate void DecideEh(ref Expr term, ref uint idx, ref int phase); + /// A bit-vector or Boolean used for branching + /// If the term is a bit-vector, then an index into the bit-vector being branched on + /// Set phase to -1 (false) or 1 (true) to override solver's phase + /// + public delegate void DecideEh(ref Expr term, ref uint idx, ref int phase); // access managed objects through a static array. - // thread safety is ignored for now. - static List propagators = new List(); - int id; - Solver solver; - Context ctx; - Z3_solver_callback callback = IntPtr.Zero; - FixedEh fixed_eh; - Action final_eh; - EqEh eq_eh; - EqEh diseq_eh; - CreatedEh created_eh; - DecideEh decide_eh; + // thread safety is ignored for now. + static List propagators = new List(); + int id; + Solver solver; + Context ctx; + Z3_solver_callback callback = IntPtr.Zero; + FixedEh fixed_eh; + Action final_eh; + EqEh eq_eh; + EqEh diseq_eh; + CreatedEh created_eh; + DecideEh decide_eh; - void Callback(Action fn, Z3_solver_callback cb) { - this.callback = cb; - try { - fn(); - } - catch { - // TBD: add debug log or exception handler - } - finally { - this.callback = IntPtr.Zero; - } - } + void Callback(Action fn, Z3_solver_callback cb) + { + this.callback = cb; + try + { + fn(); + } + catch + { + // TBD: add debug log or exception handler + } + finally + { + this.callback = IntPtr.Zero; + } + } - static void _push(voidp ctx, Z3_solver_callback cb) { - var prop = propagators[ctx.ToInt32()]; - prop.Callback(() => prop.Push(), cb); - } - - static void _pop(voidp ctx, Z3_solver_callback cb, uint num_scopes) { - var prop = propagators[ctx.ToInt32()]; - prop.Callback(() => prop.Pop(num_scopes), cb); - } - - static voidp _fresh(voidp _ctx, Z3_context new_context) { - var prop = propagators[_ctx.ToInt32()]; - var ctx = new Context(new_context); - var prop1 = prop.Fresh(prop.ctx); - return new IntPtr(prop1.id); - } + static void _push(voidp ctx, Z3_solver_callback cb) + { + var prop = propagators[ctx.ToInt32()]; + prop.Callback(() => prop.Push(), cb); + } - static void _fixed(voidp ctx, Z3_solver_callback cb, Z3_ast _term, Z3_ast _value) { - var prop = propagators[ctx.ToInt32()]; - using var term = Expr.Create(prop.ctx, _term); - using var value = Expr.Create(prop.ctx, _value); - prop.Callback(() => prop.fixed_eh(term, value), cb); - } + static void _pop(voidp ctx, Z3_solver_callback cb, uint num_scopes) + { + var prop = propagators[ctx.ToInt32()]; + prop.Callback(() => prop.Pop(num_scopes), cb); + } - static void _final(voidp ctx, Z3_solver_callback cb) { - var prop = propagators[ctx.ToInt32()]; - prop.Callback(() => prop.final_eh(), cb); - } + static voidp _fresh(voidp _ctx, Z3_context new_context) + { + var prop = propagators[_ctx.ToInt32()]; + var ctx = new Context(new_context); + var prop1 = prop.Fresh(prop.ctx); + return new IntPtr(prop1.id); + } - static void _eq(voidp ctx, Z3_solver_callback cb, Z3_ast a, Z3_ast b) { - var prop = propagators[ctx.ToInt32()]; - using var s = Expr.Create(prop.ctx, a); - using var t = Expr.Create(prop.ctx, b); - prop.Callback(() => prop.eq_eh(s, t), cb); - } + static void _fixed(voidp ctx, Z3_solver_callback cb, Z3_ast _term, Z3_ast _value) + { + var prop = propagators[ctx.ToInt32()]; + using var term = Expr.Create(prop.ctx, _term); + using var value = Expr.Create(prop.ctx, _value); + prop.Callback(() => prop.fixed_eh(term, value), cb); + } - static void _diseq(voidp ctx, Z3_solver_callback cb, Z3_ast a, Z3_ast b) { - var prop = propagators[ctx.ToInt32()]; - using var s = Expr.Create(prop.ctx, a); - using var t = Expr.Create(prop.ctx, b); - prop.Callback(() => prop.diseq_eh(s, t), cb); - } + static void _final(voidp ctx, Z3_solver_callback cb) + { + var prop = propagators[ctx.ToInt32()]; + prop.Callback(() => prop.final_eh(), cb); + } - static void _created(voidp ctx, Z3_solver_callback cb, Z3_ast a) { - var prop = propagators[ctx.ToInt32()]; - using var t = Expr.Create(prop.ctx, a); - prop.Callback(() => prop.created_eh(t), cb); - } + static void _eq(voidp ctx, Z3_solver_callback cb, Z3_ast a, Z3_ast b) + { + var prop = propagators[ctx.ToInt32()]; + using var s = Expr.Create(prop.ctx, a); + using var t = Expr.Create(prop.ctx, b); + prop.Callback(() => prop.eq_eh(s, t), cb); + } - static void _decide(voidp ctx, Z3_solver_callback cb, ref Z3_ast a, ref uint idx, ref int phase) { - var prop = propagators[ctx.ToInt32()]; - var t = Expr.Create(prop.ctx, a); - var u = t; - prop.callback = cb; - prop.decide_eh(ref t, ref idx, ref phase); - prop.callback = IntPtr.Zero; - if (u != t) - a = t.NativeObject; - } + static void _diseq(voidp ctx, Z3_solver_callback cb, Z3_ast a, Z3_ast b) + { + var prop = propagators[ctx.ToInt32()]; + using var s = Expr.Create(prop.ctx, a); + using var t = Expr.Create(prop.ctx, b); + prop.Callback(() => prop.diseq_eh(s, t), cb); + } + + static void _created(voidp ctx, Z3_solver_callback cb, Z3_ast a) + { + var prop = propagators[ctx.ToInt32()]; + using var t = Expr.Create(prop.ctx, a); + prop.Callback(() => prop.created_eh(t), cb); + } + + static void _decide(voidp ctx, Z3_solver_callback cb, ref Z3_ast a, ref uint idx, ref int phase) + { + var prop = propagators[ctx.ToInt32()]; + var t = Expr.Create(prop.ctx, a); + var u = t; + prop.callback = cb; + prop.decide_eh(ref t, ref idx, ref phase); + prop.callback = IntPtr.Zero; + if (u != t) + a = t.NativeObject; + } /// /// Propagator constructor from a solver class. /// public UserPropagator(Solver s) - { - id = propagators.Count; - propagators.Add(this); - solver = s; - ctx = solver.Context; - Native.Z3_solver_propagate_init(ctx.nCtx, solver.NativeObject, new IntPtr(id), _push, _pop, _fresh); - } + { + id = propagators.Count; + propagators.Add(this); + solver = s; + ctx = solver.Context; + Native.Z3_solver_propagate_init(ctx.nCtx, solver.NativeObject, new IntPtr(id), _push, _pop, _fresh); + } /// /// Propagator constructor from a context. It is used from inside of Fresh. /// public UserPropagator(Context _ctx) - { - id = propagators.Count; - propagators.Add(this); + { + id = propagators.Count; + propagators.Add(this); solver = null; - ctx = _ctx; - } + ctx = _ctx; + } /// /// Release provate memory. /// - ~UserPropagator() - { + ~UserPropagator() + { propagators[id] = null; if (solver == null) ctx.Dispose(); - } + } /// /// Virtual method for push. It must be overwritten by inherited class. - /// - public virtual void Push() { throw new Z3Exception("Push method should be overwritten"); } + /// + public virtual void Push() { throw new Z3Exception("Push method should be overwritten"); } /// /// Virtual method for pop. It must be overwritten by inherited class. - /// - public virtual void Pop(uint n) { throw new Z3Exception("Pop method should be overwritten"); } + /// + public virtual void Pop(uint n) { throw new Z3Exception("Pop method should be overwritten"); } /// /// Virtual method for fresh. It can be overwritten by inherited class. - /// - public virtual UserPropagator Fresh(Context ctx) { return new UserPropagator(ctx); } + /// + public virtual UserPropagator Fresh(Context ctx) { return new UserPropagator(ctx); } /// /// Declare combination of assigned expressions a conflict - /// - public void Conflict(params Expr[] terms) { - Propagate(terms, ctx.MkFalse()); + /// + public void Conflict(params Expr[] terms) + { + Propagate(terms, ctx.MkFalse()); + } + + /// + /// Declare combination of assigned expressions a conflict + /// + public void Conflict(IEnumerable terms) + { + Propagate(terms, ctx.MkFalse()); } /// /// Propagate consequence - /// - public void Propagate(Expr[] terms, Expr conseq) { - var nTerms = Z3Object.ArrayToNative(terms); + /// + public void Propagate(IEnumerable terms, Expr conseq) + { + var nTerms = Z3Object.ArrayToNative(terms.ToArray()); Native.Z3_solver_propagate_consequence(ctx.nCtx, this.callback, (uint)nTerms.Length, nTerms, 0u, null, null, conseq.NativeObject); } /// /// Set fixed callback - /// + /// public FixedEh Fixed - { - set - { - this.fixed_eh = value; - if (solver != null) - Native.Z3_solver_propagate_fixed(ctx.nCtx, solver.NativeObject, _fixed); - } - } + { + set + { + this.fixed_eh = value; + if (solver != null) + Native.Z3_solver_propagate_fixed(ctx.nCtx, solver.NativeObject, _fixed); + } + } /// /// Set final callback - /// - public Action Final - { - set - { - this.final_eh = value; - if (solver != null) - Native.Z3_solver_propagate_final(ctx.nCtx, solver.NativeObject, _final); + /// + public Action Final + { + set + { + this.final_eh = value; + if (solver != null) + Native.Z3_solver_propagate_final(ctx.nCtx, solver.NativeObject, _final); } } /// /// Set equality event callback - /// - public EqEh Eq - { - set - { - this.eq_eh = value; - if (solver != null) - Native.Z3_solver_propagate_eq(ctx.nCtx, solver.NativeObject, _eq); - } + /// + public EqEh Eq + { + set + { + this.eq_eh = value; + if (solver != null) + Native.Z3_solver_propagate_eq(ctx.nCtx, solver.NativeObject, _eq); + } } /// /// Set disequality event callback - /// - public EqEh Diseq - { - set - { - this.diseq_eh = value; - if (solver != null) - Native.Z3_solver_propagate_diseq(ctx.nCtx, solver.NativeObject, _diseq); - } + /// + public EqEh Diseq + { + set + { + this.diseq_eh = value; + if (solver != null) + Native.Z3_solver_propagate_diseq(ctx.nCtx, solver.NativeObject, _diseq); + } } /// /// Set created callback - /// - public CreatedEh Created - { - set - { - this.created_eh = value; - if (solver != null) - Native.Z3_solver_propagate_created(ctx.nCtx, solver.NativeObject, _created); - } + /// + public CreatedEh Created + { + set + { + this.created_eh = value; + if (solver != null) + Native.Z3_solver_propagate_created(ctx.nCtx, solver.NativeObject, _created); + } } /// /// Set decision callback - /// - public DecideEh Decide - { - set - { - this.decide_eh = value; - if (solver != null) - Native.Z3_solver_propagate_decide(ctx.nCtx, solver.NativeObject, _decide); - } + /// + public DecideEh Decide + { + set + { + this.decide_eh = value; + if (solver != null) + Native.Z3_solver_propagate_decide(ctx.nCtx, solver.NativeObject, _decide); + } } - + /// /// Set the next decision - /// - public void NextSplit(Expr e, uint idx, int phase) - { - Native.Z3_solver_next_split(ctx.nCtx, this.callback, e.NativeObject, idx, phase); - } + /// + public void NextSplit(Expr e, uint idx, int phase) + { + Native.Z3_solver_next_split(ctx.nCtx, this.callback, e.NativeObject, idx, phase); + } /// /// Track assignments to a term - /// - public void Register(Expr term) { - if (this.callback != IntPtr.Zero) { - Native.Z3_solver_propagate_register_cb(ctx.nCtx, callback, term.NativeObject); + /// + public void Register(Expr term) + { + if (this.callback != IntPtr.Zero) + { + Native.Z3_solver_propagate_register_cb(ctx.nCtx, callback, term.NativeObject); } - else { - Native.Z3_solver_propagate_register(ctx.nCtx, solver.NativeObject, term.NativeObject); + else + { + Native.Z3_solver_propagate_register(ctx.nCtx, solver.NativeObject, term.NativeObject); } - } + } } } From 79778767b0ec6f252941310c246a64f7b480cd0d Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 28 Jun 2022 14:25:43 -0700 Subject: [PATCH 015/125] add doc string --- src/api/dotnet/Z3Object.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/api/dotnet/Z3Object.cs b/src/api/dotnet/Z3Object.cs index d385d9d62..432885b66 100644 --- a/src/api/dotnet/Z3Object.cs +++ b/src/api/dotnet/Z3Object.cs @@ -113,7 +113,10 @@ namespace Microsoft.Z3 return s.NativeObject; } - public Context Context + /// + /// Access Context object + /// + public Context Context { get { From 12e7b4c3d622923e91e575403a0e1432bbb187dd Mon Sep 17 00:00:00 2001 From: Max Levatich Date: Tue, 28 Jun 2022 19:22:41 -0700 Subject: [PATCH 016/125] fix gc'ed callbacks in .NET propagator api (#6118) Co-authored-by: Maxwell Levatich --- src/api/dotnet/UserPropagator.cs | 38 ++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/src/api/dotnet/UserPropagator.cs b/src/api/dotnet/UserPropagator.cs index eb181b272..ce9680a7a 100644 --- a/src/api/dotnet/UserPropagator.cs +++ b/src/api/dotnet/UserPropagator.cs @@ -61,7 +61,7 @@ namespace Microsoft.Z3 /// Set phase to -1 (false) or 1 (true) to override solver's phase /// public delegate void DecideEh(ref Expr term, ref uint idx, ref int phase); - + // access managed objects through a static array. // thread safety is ignored for now. static List propagators = new List(); @@ -76,6 +76,17 @@ namespace Microsoft.Z3 CreatedEh created_eh; DecideEh decide_eh; + Native.Z3_push_eh push_eh; + Native.Z3_pop_eh pop_eh; + Native.Z3_fresh_eh fresh_eh; + + Native.Z3_fixed_eh fixed_wrapper; + Native.Z3_final_eh final_wrapper; + Native.Z3_eq_eh eq_wrapper; + Native.Z3_eq_eh diseq_wrapper; + Native.Z3_decide_eh decide_wrapper; + Native.Z3_created_eh created_wrapper; + void Callback(Action fn, Z3_solver_callback cb) { this.callback = cb; @@ -85,7 +96,7 @@ namespace Microsoft.Z3 } catch { - // TBD: add debug log or exception handler + // TBD: add debug log or exception handler } finally { @@ -172,7 +183,10 @@ namespace Microsoft.Z3 propagators.Add(this); solver = s; ctx = solver.Context; - Native.Z3_solver_propagate_init(ctx.nCtx, solver.NativeObject, new IntPtr(id), _push, _pop, _fresh); + push_eh = _push; + pop_eh = _pop; + fresh_eh = _fresh; + Native.Z3_solver_propagate_init(ctx.nCtx, solver.NativeObject, new IntPtr(id), push_eh, pop_eh, fresh_eh); } /// @@ -244,9 +258,10 @@ namespace Microsoft.Z3 { set { + this.fixed_wrapper = _fixed; this.fixed_eh = value; if (solver != null) - Native.Z3_solver_propagate_fixed(ctx.nCtx, solver.NativeObject, _fixed); + Native.Z3_solver_propagate_fixed(ctx.nCtx, solver.NativeObject, fixed_wrapper); } } @@ -257,9 +272,10 @@ namespace Microsoft.Z3 { set { + this.final_wrapper = _final; this.final_eh = value; if (solver != null) - Native.Z3_solver_propagate_final(ctx.nCtx, solver.NativeObject, _final); + Native.Z3_solver_propagate_final(ctx.nCtx, solver.NativeObject, final_wrapper); } } @@ -270,9 +286,10 @@ namespace Microsoft.Z3 { set { + this.eq_wrapper = _eq; this.eq_eh = value; if (solver != null) - Native.Z3_solver_propagate_eq(ctx.nCtx, solver.NativeObject, _eq); + Native.Z3_solver_propagate_eq(ctx.nCtx, solver.NativeObject, eq_wrapper); } } @@ -283,9 +300,10 @@ namespace Microsoft.Z3 { set { + this.diseq_wrapper = _diseq; this.diseq_eh = value; if (solver != null) - Native.Z3_solver_propagate_diseq(ctx.nCtx, solver.NativeObject, _diseq); + Native.Z3_solver_propagate_diseq(ctx.nCtx, solver.NativeObject, diseq_wrapper); } } @@ -296,9 +314,10 @@ namespace Microsoft.Z3 { set { + this.created_wrapper = _created; this.created_eh = value; if (solver != null) - Native.Z3_solver_propagate_created(ctx.nCtx, solver.NativeObject, _created); + Native.Z3_solver_propagate_created(ctx.nCtx, solver.NativeObject, created_wrapper); } } @@ -309,9 +328,10 @@ namespace Microsoft.Z3 { set { + this.decide_wrapper = _decide; this.decide_eh = value; if (solver != null) - Native.Z3_solver_propagate_decide(ctx.nCtx, solver.NativeObject, _decide); + Native.Z3_solver_propagate_decide(ctx.nCtx, solver.NativeObject, decide_wrapper); } } From fd8ee34564e9f1b188e74b1ff6d719c4f13f8d63 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 28 Jun 2022 16:10:15 -0700 Subject: [PATCH 017/125] add logging --- src/smt/theory_user_propagator.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/smt/theory_user_propagator.cpp b/src/smt/theory_user_propagator.cpp index 780023fab..911d1715e 100644 --- a/src/smt/theory_user_propagator.cpp +++ b/src/smt/theory_user_propagator.cpp @@ -20,6 +20,7 @@ Author: #include "smt/theory_bv.h" #include "smt/theory_user_propagator.h" #include "smt/smt_context.h" +#include "ast/ast_ll_pp.h" using namespace smt; @@ -49,6 +50,7 @@ void theory_user_propagator::add_expr(expr* term, bool ensure_enode) { expr_ref r(m); expr* e = term; ctx.get_rewriter()(e, r); + TRACE("user_propagate", tout << "add " << mk_bounded_pp(e, m) << "\n"); if (r != e) { r = m.mk_fresh_const("aux-expr", e->get_sort()); expr_ref eq(m.mk_eq(r, e), m); From 5afcb489e05ff4b677a8bb53fe917d138d4f1c4c Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 29 Jun 2022 08:17:39 -0700 Subject: [PATCH 018/125] adding totalizer --- src/opt/CMakeLists.txt | 1 + src/opt/totalizer.cpp | 122 ++++++++++++++++++++++++++++++++++++++++ src/opt/totalizer.h | 44 +++++++++++++++ src/test/CMakeLists.txt | 1 + src/test/main.cpp | 1 + src/test/totalizer.cpp | 25 ++++++++ 6 files changed, 194 insertions(+) create mode 100644 src/opt/totalizer.cpp create mode 100644 src/opt/totalizer.h create mode 100644 src/test/totalizer.cpp diff --git a/src/opt/CMakeLists.txt b/src/opt/CMakeLists.txt index 9c20b7d2d..21075d88c 100644 --- a/src/opt/CMakeLists.txt +++ b/src/opt/CMakeLists.txt @@ -14,6 +14,7 @@ z3_add_component(opt opt_solver.cpp pb_sls.cpp sortmax.cpp + totalizer.cpp wmax.cpp COMPONENT_DEPENDENCIES sat_solver diff --git a/src/opt/totalizer.cpp b/src/opt/totalizer.cpp new file mode 100644 index 000000000..fee66e5d7 --- /dev/null +++ b/src/opt/totalizer.cpp @@ -0,0 +1,122 @@ +/*++ +Copyright (c) 2022 Microsoft Corporation + +Module Name: + + totalizer.cpp + +Abstract: + + Incremental totalizer for at least constraints + +Author: + + Nikolaj Bjorner (nbjorner) 2022-06-27 + +--*/ + +#include "opt/totalizer.h" +#include "ast/ast_util.h" +#include "ast/ast_pp.h" +#include + +namespace opt { + + + void totalizer::ensure_bound(node* n, unsigned k) { + auto& lits = n->m_literals; + if (k > lits.size()) + return; + auto* l = n->m_left; + auto* r = n->m_right; + if (l) + ensure_bound(l, k); + if (r) + ensure_bound(r, k); + + for (unsigned i = k; i > 0 && !lits.get(i - 1); --i) { + if (l->m_literals.size() + r->m_literals.size() < i) { + lits[i - 1] = m.mk_false(); + continue; + } + + expr* c = m.mk_fresh_const("c", m.mk_bool_sort()); + lits[i - 1] = c; + + // >= 3 + // r[2] => >= 3 + // l[0] & r[1] => >= 3 + // l[1] & r[0] => >= 3 + // l[2] => >= 3 + + for (unsigned j1 = 0; j1 <= i; ++j1) { + unsigned j2 = i - j1; + if (j1 > l->m_literals.size()) + continue; + if (j2 > r->m_literals.size()) + continue; + expr_ref_vector clause(m); + if (0 < j1) { + expr* a = l->m_literals.get(j1 - 1); + clause.push_back(mk_not(m, a)); + } + if (0 < j2) { + expr* b = r->m_literals.get(j2 - 1); + clause.push_back(mk_not(m, b)); + } + if (clause.empty()) + continue; + clause.push_back(c); + m_clauses.push_back(clause); + } + } + } + + totalizer::totalizer(expr_ref_vector const& literals): + m(literals.m()), + m_literals(literals), + m_tree(nullptr) { + ptr_vector trees; + for (expr* e : literals) { + expr_ref_vector ls(m); + ls.push_back(e); + trees.push_back(alloc(node, ls)); + } + for (unsigned i = 0; i + 1 < trees.size(); i += 2) { + node* left = trees[i]; + node* right = trees[i + 1]; + expr_ref_vector ls(m); + ls.resize(left->m_literals.size() + right->m_literals.size()); + node* n = alloc(node, ls); + n->m_left = left; + n->m_right = right; + trees.push_back(n); + } + m_tree = trees.back(); + } + + totalizer::~totalizer() { + ptr_vector trees; + trees.push_back(m_tree); + while (!trees.empty()) { + node* n = trees.back(); + trees.pop_back(); + if (n->m_left) + trees.push_back(n->m_left); + if (n->m_right) + trees.push_back(n->m_right); + dealloc(n); + } + } + + expr* totalizer::at_least(unsigned k) { + if (k == 0) + return m.mk_true(); + if (m_tree->m_literals.size() < k) + return m.mk_false(); + SASSERT(1 <= k && k <= m_tree->m_literals.size()); + ensure_bound(m_tree, k); + return m_tree->m_literals.get(k - 1); + } + +} diff --git a/src/opt/totalizer.h b/src/opt/totalizer.h new file mode 100644 index 000000000..e68ac81b5 --- /dev/null +++ b/src/opt/totalizer.h @@ -0,0 +1,44 @@ +/*++ +Copyright (c) 2022 Microsoft Corporation + +Module Name: + + totalizer.h + +Abstract: + + Incremental totalizer for at least constraints + +Author: + + Nikolaj Bjorner (nbjorner) 2022-06-27 + +--*/ + +#pragma once +#include "ast/ast.h" + +namespace opt { + + class totalizer { + struct node { + node* m_left = nullptr; + node* m_right = nullptr; + expr_ref_vector m_literals; + node(expr_ref_vector& l): m_literals(l) {} + }; + + ast_manager& m; + expr_ref_vector m_literals; + node* m_tree; + vector m_clauses; + + void ensure_bound(node* n, unsigned k); + + public: + totalizer(expr_ref_vector const& literals); + ~totalizer(); + expr* at_least(unsigned k); + vector& clauses() { return m_clauses; } + }; +} diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 500cb4258..f959e9bd5 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -120,6 +120,7 @@ add_executable(test-z3 theory_pb.cpp timeout.cpp total_order.cpp + totalizer.cpp trigo.cpp udoc_relation.cpp uint_set.cpp diff --git a/src/test/main.cpp b/src/test/main.cpp index 6272c2dee..f9e4e0815 100644 --- a/src/test/main.cpp +++ b/src/test/main.cpp @@ -263,4 +263,5 @@ int main(int argc, char ** argv) { TST(solver_pool); //TST_ARGV(hs); TST(finder); + TST(totalizer); } diff --git a/src/test/totalizer.cpp b/src/test/totalizer.cpp new file mode 100644 index 000000000..13cebd7c7 --- /dev/null +++ b/src/test/totalizer.cpp @@ -0,0 +1,25 @@ +#include "opt/totalizer.h" +#include "ast/ast_pp.h" +#include "ast/reg_decl_plugins.h" +#include + +void tst_totalizer() { + std::cout << "totalizer\n"; + ast_manager m; + reg_decl_plugins(m); + expr_ref_vector lits(m); + for (unsigned i = 0; i < 5; ++i) + lits.push_back(m.mk_fresh_const("a", m.mk_bool_sort())); + opt::totalizer tot(lits); + + for (unsigned i = 0; i <= 6; ++i) { + std::cout << "at least " << i << " "; + expr* am = tot.at_least(i); + std::cout << mk_pp(am, m) << "\n"; + } + for (auto& clause : tot.clauses()) { + for (auto * l : clause) + std::cout << mk_pp(l, m) << " "; + std::cout << "\n"; + } +} From ff265235c1d01b72d0065fd5b7e75bf57d3724f4 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 29 Jun 2022 08:19:52 -0700 Subject: [PATCH 019/125] adjust trace output --- src/math/grobner/grobner.cpp | 3 +++ src/smt/theory_arith_nl.h | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/math/grobner/grobner.cpp b/src/math/grobner/grobner.cpp index a0947ea42..264c2a08c 100644 --- a/src/math/grobner/grobner.cpp +++ b/src/math/grobner/grobner.cpp @@ -116,10 +116,13 @@ void grobner::reset() { } void grobner::display_var(std::ostream & out, expr * var) const { + out << "#" << var->get_id(); +#if 0 if (is_app(var) && to_app(var)->get_num_args() > 0) out << mk_bounded_pp(var, m_manager); else out << mk_pp(var, m_manager); +#endif } void grobner::display_vars(std::ostream & out, unsigned num_vars, expr * const * vars) const { diff --git a/src/smt/theory_arith_nl.h b/src/smt/theory_arith_nl.h index 044a42e7e..436dccc6a 100644 --- a/src/smt/theory_arith_nl.h +++ b/src/smt/theory_arith_nl.h @@ -2154,13 +2154,14 @@ void theory_arith::set_gb_exhausted() { // Scan the grobner basis eqs, and look for inconsistencies. template bool theory_arith::get_gb_eqs_and_look_for_conflict(ptr_vector& eqs, grobner& gb) { - TRACE("grobner", ); eqs.reset(); gb.get_equations(eqs); - TRACE("grobner_bug", tout << "after gb\n";); + TRACE("grobner", tout << "after gb\n"; + for (grobner::equation* eq : eqs) + gb.display_equation(tout, *eq); + ); for (grobner::equation* eq : eqs) { - TRACE("grobner_bug", gb.display_equation(tout, *eq);); if (is_inconsistent(eq, gb) || is_inconsistent2(eq, gb)) { TRACE("grobner", tout << "inconsistent: "; gb.display_equation(tout, *eq);); return true; From 03287d65a49f6aa7e37e462d973f14f71403f470 Mon Sep 17 00:00:00 2001 From: Joe Hauns Date: Wed, 29 Jun 2022 19:10:33 +0100 Subject: [PATCH 020/125] fixes issue #6119 (#6120) Co-authored-by: Johannes Schoisswohl --- src/util/mpz.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/mpz.cpp b/src/util/mpz.cpp index bdad1ddfe..736b401b8 100644 --- a/src/util/mpz.cpp +++ b/src/util/mpz.cpp @@ -1842,7 +1842,7 @@ std::string mpz_manager::to_string(mpz const & a) const { template unsigned mpz_manager::hash(mpz const & a) { if (is_small(a)) - return a.m_val; + return ::abs(a.m_val); #ifndef _MP_GMP unsigned sz = size(a); if (sz == 1) From 1a9122663ccccc782d0dc8f530cf8224875d4619 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 29 Jun 2022 11:16:10 -0700 Subject: [PATCH 021/125] remove unsound axioms, fix #6115 --- src/sat/smt/arith_axioms.cpp | 5 ----- src/smt/theory_arith_core.h | 13 ++----------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/sat/smt/arith_axioms.cpp b/src/sat/smt/arith_axioms.cpp index e660c8f16..47eb4499d 100644 --- a/src/sat/smt/arith_axioms.cpp +++ b/src/sat/smt/arith_axioms.cpp @@ -174,12 +174,7 @@ namespace arith { } else if (!a.is_numeral(q)) { // (or (= y 0) (<= (* y (div x y)) x)) - // (or (<= y 0) (>= (* (+ y 1) (div x y)) x)) - // (or (>= y 0) (>= (* (+ y -1) (div x y)) x)) - expr_ref one(a.mk_int(1), m); add_clause(eqz, mk_literal(a.mk_le(a.mk_mul(q, div), p))); - add_clause(mk_literal(a.mk_le(q, zero)), mk_literal(a.mk_ge(a.mk_mul(a.mk_add(q, one), div), q))); - add_clause(mk_literal(a.mk_ge(q, zero)), mk_literal(a.mk_ge(a.mk_mul(a.mk_add(q, mone), div), q))); } diff --git a/src/smt/theory_arith_core.h b/src/smt/theory_arith_core.h index 1610c4f53..c7a92b18c 100644 --- a/src/smt/theory_arith_core.h +++ b/src/smt/theory_arith_core.h @@ -584,27 +584,18 @@ namespace smt { mk_axiom(eqz, upper, !m_util.is_numeral(abs_divisor)); rational k; - if (m_util.is_zero(dividend) && false) { + if (m_util.is_zero(dividend)) { mk_axiom(eqz, m.mk_eq(div, zero)); mk_axiom(eqz, m.mk_eq(mod, zero)); } // (or (= y 0) (<= (* y (div x y)) x)) - // (or (<= y 0) (>= (* (+ y 1) (div x y)) x)) - // (or (>= y 0) (>= (* (+ y -1) (div x y)) x)) else if (!m_util.is_numeral(divisor)) { expr_ref div_ge(m), div_le(m), ge(m), le(m); div_ge = m_util.mk_ge(m_util.mk_sub(dividend, m_util.mk_mul(divisor, div)), zero); s(div_ge); mk_axiom(eqz, div_ge, false); - ge = m_util.mk_ge(divisor, zero); - le = m_util.mk_le(divisor, zero); - div_le = m_util.mk_le(m_util.mk_sub(dividend, m_util.mk_mul(m_util.mk_add(divisor, one), div)), zero); - s(div_le); - mk_axiom(le, div_le, false); - div_le = m_util.mk_le(m_util.mk_sub(dividend, m_util.mk_mul(m_util.mk_sub(divisor, one), div)), zero); - s(div_le); - mk_axiom(ge, div_le, false); + TRACE("arith", tout << eqz << " " << div_ge << "\n"); } if (m_params.m_arith_enum_const_mod && m_util.is_numeral(divisor, k) && From 8ab8b63a4ccc897149036f34011cbe7a9ff2d193 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 29 Jun 2022 12:32:04 -0700 Subject: [PATCH 022/125] fix incorrect mod axiomatization #6116 --- src/sat/smt/arith_axioms.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sat/smt/arith_axioms.cpp b/src/sat/smt/arith_axioms.cpp index 47eb4499d..517efcfe3 100644 --- a/src/sat/smt/arith_axioms.cpp +++ b/src/sat/smt/arith_axioms.cpp @@ -133,7 +133,7 @@ namespace arith { expr_ref abs_q(m.mk_ite(a.mk_ge(q, zero), q, a.mk_uminus(q)), m); literal eqz = mk_literal(m.mk_eq(q, zero)); literal mod_ge_0 = mk_literal(a.mk_ge(mod, zero)); - literal mod_lt_q = mk_literal(a.mk_lt(a.mk_sub(mod, abs_q), mone)); + literal mod_lt_q = mk_literal(a.mk_le(a.mk_sub(mod, abs_q), mone)); // q = 0 or p = (p mod q) + q * (p div q) // q = 0 or (p mod q) >= 0 From c3d2120bddb99c5a1802db5ddcddcf24cd7f75af Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 29 Jun 2022 23:10:42 -0700 Subject: [PATCH 023/125] add totalizer version of rc2 Signed-off-by: Nikolaj Bjorner --- src/opt/maxcore.cpp | 48 +++++++++++++++++++++++++++++++++++++++++ src/opt/maxcore.h | 2 ++ src/opt/maxsmt.cpp | 2 ++ src/opt/opt_context.cpp | 2 ++ src/opt/totalizer.cpp | 15 +++++++++---- src/opt/totalizer.h | 8 ++++--- src/test/totalizer.cpp | 7 ++---- 7 files changed, 72 insertions(+), 12 deletions(-) diff --git a/src/opt/maxcore.cpp b/src/opt/maxcore.cpp index 70bf38d87..f23954506 100644 --- a/src/opt/maxcore.cpp +++ b/src/opt/maxcore.cpp @@ -13,6 +13,7 @@ Abstract: - mus-mss: based on dual refinement of bounds. - binary: binary version of maxres - rc2: implementaion of rc2 heuristic using cardinality constraints + - rc2t: implementaion of rc2 heuristic using totalizerx - rc2-binary: hybrid of rc2 and binary maxres. Perform one step of binary maxres. If there are more than 16 soft constraints create a cardinality constraint. @@ -60,6 +61,7 @@ Notes: #include "ast/ast_pp.h" #include "ast/pb_decl_plugin.h" #include "ast/ast_util.h" +#include "ast/ast_smt_pp.h" #include "model/model_smt2_pp.h" #include "solver/solver.h" #include "solver/mus.h" @@ -71,6 +73,8 @@ Notes: #include "opt/opt_cores.h" #include "opt/maxsmt.h" #include "opt/maxcore.h" +#include "opt/totalizer.h" +#include using namespace opt; @@ -81,6 +85,7 @@ public: s_primal_dual, s_primal_binary, s_rc2, + s_rc2tot, s_primal_binary_rc2 }; private: @@ -160,6 +165,9 @@ public: case s_rc2: m_trace_id = "rc2"; break; + case s_rc2tot: + m_trace_id = "rc2tot"; + break; case s_primal_binary_rc2: m_trace_id = "rc2bin"; break; @@ -366,6 +374,7 @@ public: case s_primal: case s_primal_binary: case s_rc2: + case s_rc2tot: case s_primal_binary_rc2: return mus_solver(); case s_primal_dual: @@ -558,6 +567,7 @@ public: bin_max_resolve(core, w); break; case strategy_t::s_rc2: + case strategy_t::s_rc2tot: max_resolve_rc2(core, w); break; case strategy_t::s_primal_binary_rc2: @@ -780,8 +790,38 @@ public: obj_map m_at_mostk; obj_map m_bounds; rational m_unfold_upper; + obj_map m_totalizers; + + expr* mk_atmost_tot(expr_ref_vector const& es, unsigned bound, rational const& weight) { + pb_util pb(m); + expr_ref am(pb.mk_at_most_k(es, 0), m); + totalizer* t = nullptr; + if (!m_totalizers.find(am, t)) { + m_trail.push_back(am); + t = alloc(totalizer, es); + m_totalizers.insert(am, t); + } + expr* at_least = t->at_least(bound + 1); + am = m.mk_not(at_least); + m_trail.push_back(am); + expr_ref_vector& clauses = t->clauses(); + for (auto & clause : clauses) { + add(clause); + m_defs.push_back(clause); + } + clauses.reset(); + auto& defs = t->defs(); + for (auto & [v, d] : defs) + update_model(v, d); + defs.reset(); + bound_info b(es, bound, weight); + m_bounds.insert(am, b); + return am; + } expr* mk_atmost(expr_ref_vector const& es, unsigned bound, rational const& weight) { + if (m_st == strategy_t::s_rc2tot) + return mk_atmost_tot(es, bound, weight); pb_util pb(m); expr_ref am(pb.mk_at_most_k(es, bound), m); expr* r = nullptr; @@ -1040,6 +1080,9 @@ public: m_unfold_upper = 0; m_at_mostk.reset(); m_bounds.reset(); + for (auto& [k,t] : m_totalizers) + dealloc(t); + m_totalizers.reset(); return l_true; } @@ -1109,6 +1152,11 @@ opt::maxsmt_solver_base* opt::mk_rc2( return alloc(maxcore, c, id, soft, maxcore::s_rc2); } +opt::maxsmt_solver_base* opt::mk_rc2tot( + maxsat_context& c, unsigned id, vector& soft) { + return alloc(maxcore, c, id, soft, maxcore::s_rc2tot); +} + opt::maxsmt_solver_base* opt::mk_rc2bin( maxsat_context& c, unsigned id, vector& soft) { return alloc(maxcore, c, id, soft, maxcore::s_primal_binary_rc2); diff --git a/src/opt/maxcore.h b/src/opt/maxcore.h index 2038c5e98..f64184e7f 100644 --- a/src/opt/maxcore.h +++ b/src/opt/maxcore.h @@ -23,6 +23,8 @@ namespace opt { maxsmt_solver_base* mk_rc2(maxsat_context& c, unsigned id, vector& soft); + maxsmt_solver_base* mk_rc2tot(maxsat_context& c, unsigned id, vector& soft); + maxsmt_solver_base* mk_rc2bin(maxsat_context& c, unsigned id, vector& soft); maxsmt_solver_base* mk_maxres(maxsat_context& c, unsigned id, vector& soft); diff --git a/src/opt/maxsmt.cpp b/src/opt/maxsmt.cpp index 3d0834472..a6eb17aa5 100644 --- a/src/opt/maxsmt.cpp +++ b/src/opt/maxsmt.cpp @@ -193,6 +193,8 @@ namespace opt { m_msolver = mk_maxres_binary(m_c, m_index, m_soft); else if (maxsat_engine == symbol("rc2")) m_msolver = mk_rc2(m_c, m_index, m_soft); + else if (maxsat_engine == symbol("rc2tot")) + m_msolver = mk_rc2tot(m_c, m_index, m_soft); else if (maxsat_engine == symbol("rc2bin")) m_msolver = mk_rc2bin(m_c, m_index, m_soft); else if (maxsat_engine == symbol("pd-maxres")) diff --git a/src/opt/opt_context.cpp b/src/opt/opt_context.cpp index e116b07c5..fa2903c35 100644 --- a/src/opt/opt_context.cpp +++ b/src/opt/opt_context.cpp @@ -705,6 +705,8 @@ namespace opt { if (m_maxsat_engine != symbol("maxres") && m_maxsat_engine != symbol("rc2") && + m_maxsat_engine != symbol("rc2tot") && + m_maxsat_engine != symbol("rc2bin") && m_maxsat_engine != symbol("maxres-bin") && m_maxsat_engine != symbol("maxres-bin-delay") && m_maxsat_engine != symbol("pd-maxres") && diff --git a/src/opt/totalizer.cpp b/src/opt/totalizer.cpp index fee66e5d7..1e4618b24 100644 --- a/src/opt/totalizer.cpp +++ b/src/opt/totalizer.cpp @@ -34,13 +34,15 @@ namespace opt { if (r) ensure_bound(r, k); + expr_ref c(m), def(m); + expr_ref_vector ors(m), clause(m); for (unsigned i = k; i > 0 && !lits.get(i - 1); --i) { if (l->m_literals.size() + r->m_literals.size() < i) { lits[i - 1] = m.mk_false(); continue; } - expr* c = m.mk_fresh_const("c", m.mk_bool_sort()); + c = m.mk_fresh_const("c", m.mk_bool_sort()); lits[i - 1] = c; // >= 3 @@ -49,13 +51,15 @@ namespace opt { // l[1] & r[0] => >= 3 // l[2] => >= 3 + ors.reset(); + for (unsigned j1 = 0; j1 <= i; ++j1) { unsigned j2 = i - j1; if (j1 > l->m_literals.size()) continue; if (j2 > r->m_literals.size()) continue; - expr_ref_vector clause(m); + clause.reset(); if (0 < j1) { expr* a = l->m_literals.get(j1 - 1); clause.push_back(mk_not(m, a)); @@ -66,16 +70,19 @@ namespace opt { } if (clause.empty()) continue; + ors.push_back(mk_or(clause)); clause.push_back(c); - m_clauses.push_back(clause); + m_clauses.push_back(mk_or(clause)); } + def = mk_not(m, mk_and(ors)); + m_defs.push_back(std::make_pair(c, def)); } } totalizer::totalizer(expr_ref_vector const& literals): m(literals.m()), m_literals(literals), - m_tree(nullptr) { + m_clauses(m) { ptr_vector trees; for (expr* e : literals) { expr_ref_vector ls(m); diff --git a/src/opt/totalizer.h b/src/opt/totalizer.h index e68ac81b5..eba4474d0 100644 --- a/src/opt/totalizer.h +++ b/src/opt/totalizer.h @@ -30,8 +30,9 @@ namespace opt { ast_manager& m; expr_ref_vector m_literals; - node* m_tree; - vector m_clauses; + node* m_tree = nullptr; + expr_ref_vector m_clauses; + vector> m_defs; void ensure_bound(node* n, unsigned k); @@ -39,6 +40,7 @@ namespace opt { totalizer(expr_ref_vector const& literals); ~totalizer(); expr* at_least(unsigned k); - vector& clauses() { return m_clauses; } + expr_ref_vector& clauses() { return m_clauses; } + vector>& defs() { return m_defs; } }; } diff --git a/src/test/totalizer.cpp b/src/test/totalizer.cpp index 13cebd7c7..20d8edb70 100644 --- a/src/test/totalizer.cpp +++ b/src/test/totalizer.cpp @@ -17,9 +17,6 @@ void tst_totalizer() { expr* am = tot.at_least(i); std::cout << mk_pp(am, m) << "\n"; } - for (auto& clause : tot.clauses()) { - for (auto * l : clause) - std::cout << mk_pp(l, m) << " "; - std::cout << "\n"; - } + for (auto& clause : tot.clauses()) + std::cout << clause << "\n"; } From e054f1683c4aca66dccbfb8217506ce2659f4e2d Mon Sep 17 00:00:00 2001 From: Mark Marron Date: Thu, 30 Jun 2022 15:39:28 -0700 Subject: [PATCH 024/125] fixing compiler warn (missing override) (#6125) --- src/sat/smt/recfun_solver.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sat/smt/recfun_solver.h b/src/sat/smt/recfun_solver.h index d469b7437..4e41a35a9 100644 --- a/src/sat/smt/recfun_solver.h +++ b/src/sat/smt/recfun_solver.h @@ -108,7 +108,7 @@ namespace recfun { bool is_shared(euf::theory_var v) const override { return true; } void init_search() override {} bool should_research(sat::literal_vector const& core) override; - bool is_beta_redex(euf::enode* p, euf::enode* n) const; + bool is_beta_redex(euf::enode* p, euf::enode* n) const override; void add_assumptions(sat::literal_set& assumptions) override; bool tracking_assumptions() override { return true; } }; From 959a0ba370f73f4dda41643e3df6da8f04b57e3f Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Thu, 30 Jun 2022 19:47:26 -0700 Subject: [PATCH 025/125] fix #6121 --- src/ackermannization/lackr_model_constructor.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/ackermannization/lackr_model_constructor.cpp b/src/ackermannization/lackr_model_constructor.cpp index f9450771d..76c1803f2 100644 --- a/src/ackermannization/lackr_model_constructor.cpp +++ b/src/ackermannization/lackr_model_constructor.cpp @@ -21,6 +21,7 @@ #include "ast/for_each_expr.h" #include "ast/rewriter/bv_rewriter.h" #include "ast/rewriter/bool_rewriter.h" +#include struct lackr_model_constructor::imp { public: @@ -186,7 +187,7 @@ private: return m_app2val.find(a, val); } - bool evaluate(app * const a, expr_ref& result) { + bool evaluate(app * a, expr_ref& result) { SASSERT(!is_val(a)); const unsigned num = a->get_num_args(); if (num == 0) { // handle constants @@ -232,20 +233,20 @@ private: // Check and record the value for a given term, given that all arguments are already checked. // bool mk_value(app * a) { - if (is_val(a)) return true; // skip numerals + if (is_val(a)) + return true; // skip numerals TRACE("model_constructor", tout << "mk_value(\n" << mk_ismt2_pp(a, m, 2) << ")\n";); SASSERT(!m_app2val.contains(a)); expr_ref result(m); - if (!evaluate(a, result)) return false; - SASSERT(is_val(result)); + if (!evaluate(a, result)) + return false; TRACE("model_constructor", tout << "map term(\n" << mk_ismt2_pp(a, m, 2) << "\n->" << mk_ismt2_pp(result.get(), m, 2)<< ")\n"; ); CTRACE("model_constructor", !is_val(result.get()), - tout << "eval fail\n" << mk_ismt2_pp(a, m, 2) << mk_ismt2_pp(result, m, 2) << "\n"; + tout << "eval didn't create a constant \n" << mk_ismt2_pp(a, m, 2) << " " << mk_ismt2_pp(result, m, 2) << "\n"; ); - SASSERT(is_val(result.get())); m_app2val.insert(a, result.get()); // memoize m_pinned.push_back(a); m_pinned.push_back(result); From 94a2477fa088df41723cb922435bab16e98fb26c Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Thu, 30 Jun 2022 19:49:19 -0700 Subject: [PATCH 026/125] totalizer --- src/opt/maxcore.cpp | 19 +++++++------------ src/opt/maxcore.h | 2 -- src/opt/maxsmt.cpp | 2 -- src/opt/opt_params.pyg | 1 + src/opt/totalizer.cpp | 30 ++++++++++-------------------- src/opt/totalizer.h | 8 +++++++- 6 files changed, 25 insertions(+), 37 deletions(-) diff --git a/src/opt/maxcore.cpp b/src/opt/maxcore.cpp index f23954506..20cc028aa 100644 --- a/src/opt/maxcore.cpp +++ b/src/opt/maxcore.cpp @@ -85,7 +85,6 @@ public: s_primal_dual, s_primal_binary, s_rc2, - s_rc2tot, s_primal_binary_rc2 }; private: @@ -136,6 +135,7 @@ private: bool m_enable_lns = false; // enable LNS improvements unsigned m_lns_conflicts = 1000; // number of conflicts used for LNS improvement bool m_enable_core_rotate = false; // enable core rotation + bool m_use_totalizer = true; // use totalizer instead of cardinality encoding std::string m_trace_id; typedef ptr_vector exprs; @@ -165,9 +165,6 @@ public: case s_rc2: m_trace_id = "rc2"; break; - case s_rc2tot: - m_trace_id = "rc2tot"; - break; case s_primal_binary_rc2: m_trace_id = "rc2bin"; break; @@ -177,7 +174,10 @@ public: } } - ~maxcore() override {} + ~maxcore() override { + for (auto& [k,t] : m_totalizers) + dealloc(t); + } bool is_literal(expr* l) { return @@ -374,7 +374,6 @@ public: case s_primal: case s_primal_binary: case s_rc2: - case s_rc2tot: case s_primal_binary_rc2: return mus_solver(); case s_primal_dual: @@ -567,7 +566,6 @@ public: bin_max_resolve(core, w); break; case strategy_t::s_rc2: - case strategy_t::s_rc2tot: max_resolve_rc2(core, w); break; case strategy_t::s_primal_binary_rc2: @@ -820,7 +818,7 @@ public: } expr* mk_atmost(expr_ref_vector const& es, unsigned bound, rational const& weight) { - if (m_st == strategy_t::s_rc2tot) + if (m_use_totalizer) return mk_atmost_tot(es, bound, weight); pb_util pb(m); expr_ref am(pb.mk_at_most_k(es, bound), m); @@ -1064,6 +1062,7 @@ public: m_enable_lns = p.enable_lns(); m_enable_core_rotate = p.enable_core_rotate(); m_lns_conflicts = p.lns_conflicts(); + m_use_totalizer = p.rc2_totalizer(); if (m_c.num_objectives() > 1) m_add_upper_bound_block = false; } @@ -1152,10 +1151,6 @@ opt::maxsmt_solver_base* opt::mk_rc2( return alloc(maxcore, c, id, soft, maxcore::s_rc2); } -opt::maxsmt_solver_base* opt::mk_rc2tot( - maxsat_context& c, unsigned id, vector& soft) { - return alloc(maxcore, c, id, soft, maxcore::s_rc2tot); -} opt::maxsmt_solver_base* opt::mk_rc2bin( maxsat_context& c, unsigned id, vector& soft) { diff --git a/src/opt/maxcore.h b/src/opt/maxcore.h index f64184e7f..2038c5e98 100644 --- a/src/opt/maxcore.h +++ b/src/opt/maxcore.h @@ -23,8 +23,6 @@ namespace opt { maxsmt_solver_base* mk_rc2(maxsat_context& c, unsigned id, vector& soft); - maxsmt_solver_base* mk_rc2tot(maxsat_context& c, unsigned id, vector& soft); - maxsmt_solver_base* mk_rc2bin(maxsat_context& c, unsigned id, vector& soft); maxsmt_solver_base* mk_maxres(maxsat_context& c, unsigned id, vector& soft); diff --git a/src/opt/maxsmt.cpp b/src/opt/maxsmt.cpp index a6eb17aa5..3d0834472 100644 --- a/src/opt/maxsmt.cpp +++ b/src/opt/maxsmt.cpp @@ -193,8 +193,6 @@ namespace opt { m_msolver = mk_maxres_binary(m_c, m_index, m_soft); else if (maxsat_engine == symbol("rc2")) m_msolver = mk_rc2(m_c, m_index, m_soft); - else if (maxsat_engine == symbol("rc2tot")) - m_msolver = mk_rc2tot(m_c, m_index, m_soft); else if (maxsat_engine == symbol("rc2bin")) m_msolver = mk_rc2bin(m_c, m_index, m_soft); else if (maxsat_engine == symbol("pd-maxres")) diff --git a/src/opt/opt_params.pyg b/src/opt/opt_params.pyg index 93ecddbe4..df3ab0925 100644 --- a/src/opt/opt_params.pyg +++ b/src/opt/opt_params.pyg @@ -19,6 +19,7 @@ def_module_params('opt', ('pb.compile_equality', BOOL, False, 'compile arithmetical equalities into pseudo-Boolean equality (instead of two inequalites)'), ('pp.wcnf', BOOL, False, 'print maxsat benchmark into wcnf format'), ('maxlex.enable', BOOL, True, 'enable maxlex heuristic for lexicographic MaxSAT problems'), + ('rc2.totalizer', BOOL, True, 'use totalizer for rc2 encoding'), ('maxres.hill_climb', BOOL, True, 'give preference for large weight cores'), ('maxres.add_upper_bound_block', BOOL, False, 'restict upper bound with constraint'), ('maxres.max_num_cores', UINT, 200, 'maximal number of cores per round'), diff --git a/src/opt/totalizer.cpp b/src/opt/totalizer.cpp index 1e4618b24..41d730560 100644 --- a/src/opt/totalizer.cpp +++ b/src/opt/totalizer.cpp @@ -37,7 +37,7 @@ namespace opt { expr_ref c(m), def(m); expr_ref_vector ors(m), clause(m); for (unsigned i = k; i > 0 && !lits.get(i - 1); --i) { - if (l->m_literals.size() + r->m_literals.size() < i) { + if (l->size() + r->size() < i) { lits[i - 1] = m.mk_false(); continue; } @@ -55,9 +55,9 @@ namespace opt { for (unsigned j1 = 0; j1 <= i; ++j1) { unsigned j2 = i - j1; - if (j1 > l->m_literals.size()) + if (j1 > l->size()) continue; - if (j2 > r->m_literals.size()) + if (j2 > r->size()) continue; clause.reset(); if (0 < j1) { @@ -93,37 +93,27 @@ namespace opt { node* left = trees[i]; node* right = trees[i + 1]; expr_ref_vector ls(m); - ls.resize(left->m_literals.size() + right->m_literals.size()); + ls.resize(left->size() + right->size()); node* n = alloc(node, ls); n->m_left = left; n->m_right = right; trees.push_back(n); } - m_tree = trees.back(); + m_root = trees.back(); } totalizer::~totalizer() { - ptr_vector trees; - trees.push_back(m_tree); - while (!trees.empty()) { - node* n = trees.back(); - trees.pop_back(); - if (n->m_left) - trees.push_back(n->m_left); - if (n->m_right) - trees.push_back(n->m_right); - dealloc(n); - } + dealloc(m_root); } expr* totalizer::at_least(unsigned k) { if (k == 0) return m.mk_true(); - if (m_tree->m_literals.size() < k) + if (m_root->size() < k) return m.mk_false(); - SASSERT(1 <= k && k <= m_tree->m_literals.size()); - ensure_bound(m_tree, k); - return m_tree->m_literals.get(k - 1); + SASSERT(1 <= k && k <= m_root->size()); + ensure_bound(m_root, k); + return m_root->m_literals.get(k - 1); } } diff --git a/src/opt/totalizer.h b/src/opt/totalizer.h index eba4474d0..3d26fa2e6 100644 --- a/src/opt/totalizer.h +++ b/src/opt/totalizer.h @@ -26,11 +26,17 @@ namespace opt { node* m_right = nullptr; expr_ref_vector m_literals; node(expr_ref_vector& l): m_literals(l) {} + ~node() { + dealloc(m_left); + dealloc(m_right); + } + unsigned size() const { return m_literals.size(); } + }; ast_manager& m; expr_ref_vector m_literals; - node* m_tree = nullptr; + node* m_root = nullptr; expr_ref_vector m_clauses; vector> m_defs; From ea2a84332513a7823e8e5c96f039fd048cb43dc5 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Thu, 30 Jun 2022 19:59:46 -0700 Subject: [PATCH 027/125] flat only remove option for uzers (users who are in reality fuzzers) to toggle flat option. The legacy arithmetic solver bakes in assumptions about flat format so it isn't helpful to expose this to fuzzers, I mean uzers. --- src/ast/rewriter/th_rewriter.cpp | 2 +- src/params/rewriter_params.pyg | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ast/rewriter/th_rewriter.cpp b/src/ast/rewriter/th_rewriter.cpp index 5b0df8147..c69534b08 100644 --- a/src/ast/rewriter/th_rewriter.cpp +++ b/src/ast/rewriter/th_rewriter.cpp @@ -78,7 +78,7 @@ struct th_rewriter_cfg : public default_rewriter_cfg { void updt_local_params(params_ref const & _p) { rewriter_params p(_p); - m_flat = p.flat(); + m_flat = true; m_max_memory = megabytes_to_bytes(p.max_memory()); m_max_steps = p.max_steps(); m_pull_cheap_ite = p.pull_cheap_ite(); diff --git a/src/params/rewriter_params.pyg b/src/params/rewriter_params.pyg index 18bb29e56..290f7b1da 100644 --- a/src/params/rewriter_params.pyg +++ b/src/params/rewriter_params.pyg @@ -3,7 +3,6 @@ def_module_params('rewriter', export=True, params=(max_memory_param(), max_steps_param(), - ("flat", BOOL, True, "create nary applications for and,or,+,*,bvadd,bvmul,bvand,bvor,bvxor"), ("push_ite_arith", BOOL, False, "push if-then-else over arithmetic terms."), ("push_ite_bv", BOOL, False, "push if-then-else over bit-vector terms."), ("pull_cheap_ite", BOOL, False, "pull if-then-else terms when cheap."), From 3c94083a233054f7fe8db5073a5071561252afae Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Fri, 1 Jul 2022 15:29:36 -0700 Subject: [PATCH 028/125] fix doc errors Signed-off-by: Nikolaj Bjorner --- doc/website.dox.in | 4 ++-- src/api/dotnet/Context.cs | 8 ++++---- src/api/java/Context.java | 32 ++++++++++++++++---------------- src/api/java/Model.java | 2 +- src/api/java/Quantifier.java | 6 ++++++ src/api/z3_api.h | 4 ++-- 6 files changed, 31 insertions(+), 25 deletions(-) diff --git a/doc/website.dox.in b/doc/website.dox.in index 5d084550c..de5feb685 100644 --- a/doc/website.dox.in +++ b/doc/website.dox.in @@ -8,6 +8,6 @@ This website hosts the automatically generated documentation for the Z3 APIs. - - \ref @C_API@ - - \ref @CPP_API@ @DOTNET_API@ @JAVA_API@ @PYTHON_API@ @OCAML_API@ @JS_API@ + - \ref @C_API@ + - \ref @CPP_API@ @DOTNET_API@ @JAVA_API@ @PYTHON_API@ @OCAML_API@ @JS_API@ */ diff --git a/src/api/dotnet/Context.cs b/src/api/dotnet/Context.cs index c71289d4d..764beb470 100644 --- a/src/api/dotnet/Context.cs +++ b/src/api/dotnet/Context.cs @@ -4620,16 +4620,16 @@ namespace Microsoft.Z3 /// /// /// Produces a term that represents the conversion of the floating-point term t into a - /// bit-vector term of size sz in 2's complement format (signed when signed==true). If necessary, + /// bit-vector term of size sz in 2's complement format (signed when sign==true). If necessary, /// the result will be rounded according to rounding mode rm. /// /// RoundingMode term. /// FloatingPoint term /// Size of the resulting bit-vector. - /// Indicates whether the result is a signed or unsigned bit-vector. - public BitVecExpr MkFPToBV(FPRMExpr rm, FPExpr t, uint sz, bool signed) + /// Indicates whether the result is a signed or unsigned bit-vector. + public BitVecExpr MkFPToBV(FPRMExpr rm, FPExpr t, uint sz, bool sign) { - if (signed) + if (sign) return new BitVecExpr(this, Native.Z3_mk_fpa_to_sbv(this.nCtx, rm.NativeObject, t.NativeObject, sz)); else return new BitVecExpr(this, Native.Z3_mk_fpa_to_ubv(this.nCtx, rm.NativeObject, t.NativeObject, sz)); diff --git a/src/api/java/Context.java b/src/api/java/Context.java index 7b5f8a936..4582439ec 100644 --- a/src/api/java/Context.java +++ b/src/api/java/Context.java @@ -1717,8 +1717,8 @@ public class Context implements AutoCloseable { * {@code [domain -> range]}, and {@code i} must have the sort * {@code domain}. The sort of the result is {@code range}. * - * @see #mkArraySort(Sort[], Sort) - * @see #mkStore + * @see #mkArraySort(Sort[], R) + * @see #mkStore(Expr> a, Expr i, Expr v) **/ public Expr mkSelect(Expr> a, Expr i) { @@ -1739,8 +1739,8 @@ public class Context implements AutoCloseable { * {@code [domains -> range]}, and {@code args} must have the sorts * {@code domains}. The sort of the result is {@code range}. * - * @see #mkArraySort(Sort[], Sort) - * @see #mkStore + * @see #mkArraySort(Sort[], R) + * @see #mkStore(Expr> a, Expr i, Expr v) **/ public Expr mkSelect(Expr> a, Expr[] args) { @@ -1763,8 +1763,8 @@ public class Context implements AutoCloseable { * {@code select}) on all indices except for {@code i}, where it * maps to {@code v} (and the {@code select} of {@code a} * with respect to {@code i} may be a different value). - * @see #mkArraySort(Sort[], Sort) - * @see #mkSelect + * @see #mkArraySort(Sort[], R) + * @see #mkSelect(Expr> a, Expr i) **/ public ArrayExpr mkStore(Expr> a, Expr i, Expr v) @@ -1788,8 +1788,8 @@ public class Context implements AutoCloseable { * {@code select}) on all indices except for {@code args}, where it * maps to {@code v} (and the {@code select} of {@code a} * with respect to {@code args} may be a different value). - * @see #mkArraySort(Sort[], Sort) - * @see #mkSelect + * @see #mkArraySort(Sort[], R) + * @see #mkSelect(Expr> a, Expr i) **/ public ArrayExpr mkStore(Expr> a, Expr[] args, Expr v) @@ -1806,8 +1806,8 @@ public class Context implements AutoCloseable { * Remarks: The resulting term is an array, such * that a {@code select} on an arbitrary index produces the value * {@code v}. - * @see #mkArraySort(Sort[], Sort) - * @see #mkSelect + * @see #mkArraySort(Sort[], R) + * @see #mkSelect(Expr> a, Expr i) * **/ public ArrayExpr mkConstArray(D domain, Expr v) @@ -1826,9 +1826,9 @@ public class Context implements AutoCloseable { * {@code f} must have type {@code range_1 .. range_n -> range}. * {@code v} must have sort range. The sort of the result is * {@code [domain_i -> range]}. - * @see #mkArraySort(Sort[], Sort) - * @see #mkSelect - * @see #mkStore + * @see #mkArraySort(Sort[], R) + * @see #mkSelect(Expr> a, Expr i) + * @see #mkStore(Expr> a, Expr i, Expr v) **/ @SafeVarargs @@ -2476,7 +2476,7 @@ public class Context implements AutoCloseable { * * @return A Term with value {@code num}/{@code den} * and sort Real - * @see #mkNumeral(String,Sort) + * @see #mkNumeral(String v, R ty) **/ public RatNum mkReal(int num, int den) { @@ -2612,7 +2612,7 @@ public class Context implements AutoCloseable { * 'names' of the bound variables, and {@code body} is the body * of the quantifier. Quantifiers are associated with weights indicating the * importance of using the quantifier during instantiation. - * Note that the bound variables are de-Bruijn indices created using {@link #mkBound}. + * Note that the bound variables are de-Bruijn indices created using {#mkBound}. * Z3 applies the convention that the last element in {@code names} and * {@code sorts} refers to the variable with index 0, the second to last element * of {@code names} and {@code sorts} refers to the variable @@ -2707,7 +2707,7 @@ public class Context implements AutoCloseable { * with the sorts of the bound variables, {@code names} is an array with the * 'names' of the bound variables, and {@code body} is the body of the * lambda. - * Note that the bound variables are de-Bruijn indices created using {@link #mkBound} + * Note that the bound variables are de-Bruijn indices created using {#mkBound} * Z3 applies the convention that the last element in {@code names} and * {@code sorts} refers to the variable with index 0, the second to last element * of {@code names} and {@code sorts} refers to the variable diff --git a/src/api/java/Model.java b/src/api/java/Model.java index 032745fc5..ffc4dd47f 100644 --- a/src/api/java/Model.java +++ b/src/api/java/Model.java @@ -242,7 +242,7 @@ public class Model extends Z3Object { * values. We say this finite set is the "universe" of the sort. * * @see #getNumSorts - * @see #getSortUniverse + * @see #getSortUniverse(R s) * * @throws Z3Exception **/ diff --git a/src/api/java/Quantifier.java b/src/api/java/Quantifier.java index 89ff61a3d..efeac9bb5 100644 --- a/src/api/java/Quantifier.java +++ b/src/api/java/Quantifier.java @@ -161,6 +161,12 @@ public class Quantifier extends BoolExpr /** * Create a quantified expression. * + * @param ctx Context to create the quantifier on. + * @param isForall Quantifier type. + * @param sorts Sorts of bound variables. + * @param names Names of bound variables + * @param body Body of quantifier + * @param weight Weight used to indicate priority for qunatifier instantiation * @param patterns Nullable patterns * @param noPatterns Nullable noPatterns * @param quantifierID Nullable quantifierID diff --git a/src/api/z3_api.h b/src/api/z3_api.h index 8c60a09d1..dbf8d9252 100644 --- a/src/api/z3_api.h +++ b/src/api/z3_api.h @@ -6811,7 +6811,7 @@ extern "C" { /** \brief register a callback when a new expression with a registered function is used by the solver - The registered function appears at the top level and is created using \ref Z3_propagate_solver_declare. + The registered function appears at the top level and is created using \ref Z3_solver_propagate_declare. def_API('Z3_solver_propagate_created', VOID, (_in(CONTEXT), _in(SOLVER), _fnptr(Z3_created_eh))) */ @@ -6837,7 +6837,7 @@ extern "C" { /** Create uninterpreted function declaration for the user propagator. When expressions using the function are created by the solver invoke a callback - to \ref \Z3_solver_progate_created with arguments + to \ref \Z3_solver_propagate_created with arguments 1. context and callback solve 2. declared_expr: expression using function that was used as the top-level symbol 3. declared_id: a unique identifier (unique within the current scope) to track the expression. From 4f9ef12f3419a220362f34ab65530bf14589e073 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Fri, 1 Jul 2022 18:13:28 -0700 Subject: [PATCH 029/125] create dummy tactics for single threaded mode Signed-off-by: Nikolaj Bjorner --- src/solver/parallel_tactic.cpp | 14 +++++++++++++- src/tactic/tactical.cpp | 22 ++++++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/solver/parallel_tactic.cpp b/src/solver/parallel_tactic.cpp index 2f3ba7798..01c18fa04 100644 --- a/src/solver/parallel_tactic.cpp +++ b/src/solver/parallel_tactic.cpp @@ -39,10 +39,22 @@ Notes: #include "solver/parallel_tactic.h" #include "solver/parallel_params.hpp" + +class non_parallel_tactic : public tactic { + non_parallel_tactic(solver* s, params_ref const& p) { + } + + char const* name() const override { return "parallel_tactic"; } + + void operator()(const goal_ref & g,goal_ref_buffer & result) override { + throw default_exception("parallel tactic is disabled in single threaded mode"); + } +}; + #ifdef SINGLE_THREAD tactic * mk_parallel_tactic(solver* s, params_ref const& p) { - throw default_exception("parallel tactic is disabled in single threaded mode"); + return alloc(non_parallel_tactic, s, p); } #else diff --git a/src/tactic/tactical.cpp b/src/tactic/tactical.cpp index 67a0e3062..1b5094cdc 100644 --- a/src/tactic/tactical.cpp +++ b/src/tactic/tactical.cpp @@ -424,10 +424,18 @@ tactic * or_else(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5 return or_else(10, ts); } +class no_par_tactical : public tactic { +public: + char const* name() const override { return "par"; } + void operator()(goal_ref const & in, goal_ref_buffer& result) override { + throw default_exception("par_tactical is unavailable in single threaded mode"); + } +}; + #ifdef SINGLE_THREAD tactic * par(unsigned num, tactic * const * ts) { - throw default_exception("par_tactical is unavailable in single threaded mode"); + return alloc(no_par_tactical); } #else @@ -576,11 +584,21 @@ tactic * par(tactic * t1, tactic * t2, tactic * t3, tactic * t4) { return par(4, ts); } +class no_par_and_then_tactical : public tactic { +public: + char const* name() const override { return "par_then"; } + void operator()(goal_ref const & in, goal_ref_buffer& result) override { + throw default_exception("par_and_then is not available in single threaded mode"); + } +}; + + #ifdef SINGLE_THREAD tactic * par_and_then(tactic * t1, tactic * t2) { - throw default_exception("par_and_then is not available in single threaded mode"); + return alloc(no_par_and_then_tactical); } + #else class par_and_then_tactical : public and_then_tactical { public: From 06771d1ac56fd90c55544c7fc6b0a945ba52d6d8 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Fri, 1 Jul 2022 18:31:08 -0700 Subject: [PATCH 030/125] missing virtual functions Signed-off-by: Nikolaj Bjorner --- src/solver/parallel_tactic.cpp | 3 +++ src/tactic/tactical.cpp | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/solver/parallel_tactic.cpp b/src/solver/parallel_tactic.cpp index 01c18fa04..5c8c30c5d 100644 --- a/src/solver/parallel_tactic.cpp +++ b/src/solver/parallel_tactic.cpp @@ -49,6 +49,9 @@ class non_parallel_tactic : public tactic { void operator()(const goal_ref & g,goal_ref_buffer & result) override { throw default_exception("parallel tactic is disabled in single threaded mode"); } + tactic * translate(ast_manager & m) override { return nullptr; } + void cleanup() override {} + }; #ifdef SINGLE_THREAD diff --git a/src/tactic/tactical.cpp b/src/tactic/tactical.cpp index 1b5094cdc..25f8365e3 100644 --- a/src/tactic/tactical.cpp +++ b/src/tactic/tactical.cpp @@ -429,7 +429,9 @@ public: char const* name() const override { return "par"; } void operator()(goal_ref const & in, goal_ref_buffer& result) override { throw default_exception("par_tactical is unavailable in single threaded mode"); - } + } + tactic * translate(ast_manager & m) override { return nullptr; } + void cleanup() override {} }; #ifdef SINGLE_THREAD @@ -589,7 +591,9 @@ public: char const* name() const override { return "par_then"; } void operator()(goal_ref const & in, goal_ref_buffer& result) override { throw default_exception("par_and_then is not available in single threaded mode"); - } + } + tactic * translate(ast_manager & m) override { return nullptr; } + void cleanup() override {} }; From 8c2ba3d47e2f93490c779304370b144960960cc4 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Fri, 1 Jul 2022 19:18:09 -0700 Subject: [PATCH 031/125] missing virtual functions Signed-off-by: Nikolaj Bjorner --- src/solver/parallel_tactic.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/solver/parallel_tactic.cpp b/src/solver/parallel_tactic.cpp index 5c8c30c5d..c79f52084 100644 --- a/src/solver/parallel_tactic.cpp +++ b/src/solver/parallel_tactic.cpp @@ -41,6 +41,7 @@ Notes: class non_parallel_tactic : public tactic { +public: non_parallel_tactic(solver* s, params_ref const& p) { } From 815518dc026e900392bf0d08ed859e5ec42d1e43 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Fri, 1 Jul 2022 20:27:06 -0700 Subject: [PATCH 032/125] add facility for incremental parsing #6123 Adding new API object to maintain state between calls to parser. The state is incremental: all declarations of sorts and functions are valid in the next parse. The parser produces an ast-vector of assertions that are parsed in the current calls. The following is a unit test: ``` from z3 import * pc = ParserContext() A = DeclareSort('A') pc.add_sort(A) print(pc.from_string("(declare-const x A) (declare-const y A) (assert (= x y))")) print(pc.from_string("(declare-const z A) (assert (= x z))")) print(parse_smt2_string("(declare-const x Int) (declare-const y Int) (assert (= x y))")) s = Solver() s.from_string("(declare-sort A)") s.from_string("(declare-const x A)") s.from_string("(declare-const y A)") s.from_string("(assert (= x y))") print(s.assertions()) s.from_string("(declare-const z A)") print(s.assertions()) s.from_string("(assert (= x z))") print(s.assertions()) ``` It produces results of the form ``` [x == y] [x == z] [x == y] [x == y] [x == y] [x == y, x == z] ``` Thus, the set of assertions returned by a parse call is just the set of assertions added. The solver maintains state between parser calls so that declarations made in a previous call are still available when declaring the constant 'z'. The same holds for the parser_context_from_string function: function and sort declarations either added externally or declared using SMTLIB2 command line format as strings are valid for later calls. --- src/api/api_parsers.cpp | 198 ++++++++++++++++++++++---------- src/api/api_solver.cpp | 6 +- src/api/api_solver.h | 1 + src/api/python/z3/z3.py | 19 +++ src/api/python/z3/z3types.py | 7 ++ src/api/z3_api.h | 52 +++++++++ src/cmd_context/cmd_context.cpp | 24 ++-- src/cmd_context/cmd_context.h | 1 + 8 files changed, 235 insertions(+), 73 deletions(-) diff --git a/src/api/api_parsers.cpp b/src/api/api_parsers.cpp index 1c5afb34f..f00d0a44a 100644 --- a/src/api/api_parsers.cpp +++ b/src/api/api_parsers.cpp @@ -35,6 +35,136 @@ extern "C" { // --------------- // Support for SMTLIB2 + struct Z3_parser_context_ref : public api::object { + scoped_ptr ctx; + + Z3_parser_context_ref(api::context& c): api::object(c) { + ast_manager& m = c.m(); + ctx = alloc(cmd_context, false, &(m)); + install_dl_cmds(*ctx.get()); + install_opt_cmds(*ctx.get()); + install_smt2_extra_cmds(*ctx.get()); + ctx->register_plist(); + ctx->set_ignore_check(true); + } + + ~Z3_parser_context_ref() override {} + }; + + inline Z3_parser_context_ref * to_parser_context(Z3_parser_context pc) { return reinterpret_cast(pc); } + inline Z3_parser_context of_parser_context(Z3_parser_context_ref* pc) { return reinterpret_cast(pc); } + + Z3_parser_context Z3_API Z3_mk_parser_context(Z3_context c) { + Z3_TRY; + LOG_Z3_mk_parser_context(c); + RESET_ERROR_CODE(); + Z3_parser_context_ref * pc = alloc(Z3_parser_context_ref, *mk_c(c)); + mk_c(c)->save_object(pc); + Z3_parser_context r = of_parser_context(pc); + RETURN_Z3(r); + Z3_CATCH_RETURN(nullptr); + } + + void Z3_API Z3_parser_context_inc_ref(Z3_context c, Z3_parser_context pc) { + Z3_TRY; + LOG_Z3_parser_context_inc_ref(c, pc); + RESET_ERROR_CODE(); + to_parser_context(pc)->inc_ref(); + Z3_CATCH; + } + + void Z3_API Z3_parser_context_dec_ref(Z3_context c, Z3_parser_context pc) { + Z3_TRY; + LOG_Z3_parser_context_dec_ref(c, pc); + RESET_ERROR_CODE(); + to_parser_context(pc)->dec_ref(); + Z3_CATCH; + } + + static void insert_datatype(ast_manager& m, scoped_ptr& ctx, sort* srt) { + datatype_util dt(m); + if (!dt.is_datatype(srt)) + return; + + for (func_decl * c : *dt.get_datatype_constructors(srt)) { + ctx->insert(c->get_name(), c); + func_decl * r = dt.get_constructor_recognizer(c); + ctx->insert(r->get_name(), r); + for (func_decl * a : *dt.get_constructor_accessors(c)) { + ctx->insert(a->get_name(), a); + } + } + } + + static void insert_sort(ast_manager& m, scoped_ptr& ctx, symbol const& name, sort* srt) { + if (ctx->find_psort_decl(name)) + return; + psort* ps = ctx->pm().mk_psort_cnst(srt); + ctx->insert(ctx->pm().mk_psort_user_decl(0, name, ps)); + insert_datatype(m, ctx, srt); + } + + void Z3_API Z3_parser_context_add_sort(Z3_context c, Z3_parser_context pc, Z3_sort s) { + Z3_TRY; + LOG_Z3_parser_context_add_sort(c, pc, s); + RESET_ERROR_CODE(); + auto& ctx = to_parser_context(pc)->ctx; + sort* srt = to_sort(s); + symbol name = srt->get_name(); + insert_sort(mk_c(c)->m(), ctx, name, srt); + Z3_CATCH; + } + + void Z3_API Z3_parser_context_add_decl(Z3_context c, Z3_parser_context pc, Z3_func_decl f) { + Z3_TRY; + LOG_Z3_parser_context_add_decl(c, pc, f); + RESET_ERROR_CODE(); + auto& ctx = *to_parser_context(pc)->ctx; + func_decl* fn = to_func_decl(f); + symbol name = fn->get_name(); + ctx.insert(name, fn); + Z3_CATCH; + } + + Z3_ast_vector Z3_parser_context_parse_stream(Z3_context c, scoped_ptr& ctx, bool owned, std::istream& is) { + Z3_TRY; + Z3_ast_vector_ref * v = alloc(Z3_ast_vector_ref, *mk_c(c), mk_c(c)->m()); + mk_c(c)->save_object(v); + std::stringstream errstrm; + ctx->set_regular_stream(errstrm); + try { + if (!parse_smt2_commands(*ctx, is)) { + if (owned) + ctx = nullptr; + SET_ERROR_CODE(Z3_PARSER_ERROR, errstrm.str()); + return of_ast_vector(v); + } + } + catch (z3_exception& e) { + if (owned) + ctx = nullptr; + errstrm << e.msg(); + SET_ERROR_CODE(Z3_PARSER_ERROR, errstrm.str()); + return of_ast_vector(v); + } + for (expr* e : ctx->tracked_assertions()) + v->m_ast_vector.push_back(e); + ctx->reset_tracked_assertions(); + return of_ast_vector(v); + Z3_CATCH_RETURN(nullptr); + } + + Z3_ast_vector Z3_API Z3_parser_context_from_string(Z3_context c, Z3_parser_context pc, Z3_string str) { + Z3_TRY; + LOG_Z3_parser_context_from_string(c, pc, str); + std::string s(str); + std::istringstream is(s); + auto& ctx = to_parser_context(pc)->ctx; + Z3_ast_vector r = Z3_parser_context_parse_stream(c, ctx, false, is); + RETURN_Z3(r); + Z3_CATCH_RETURN(nullptr); + } + Z3_ast_vector parse_smtlib2_stream(bool exec, Z3_context c, std::istream& is, unsigned num_sorts, Z3_symbol const _sort_names[], @@ -48,70 +178,16 @@ extern "C" { install_dl_cmds(*ctx.get()); install_opt_cmds(*ctx.get()); install_smt2_extra_cmds(*ctx.get()); - ctx->register_plist(); ctx->set_ignore_check(true); - Z3_ast_vector_ref * v = alloc(Z3_ast_vector_ref, *mk_c(c), m); - - vector sort_names; - ptr_vector sorts; - for (unsigned i = 0; i < num_sorts; ++i) { - sorts.push_back(to_sort(_sorts[i])); - sort_names.push_back(to_symbol(_sort_names[i])); - } - mk_c(c)->save_object(v); - for (unsigned i = 0; i < num_decls; ++i) { - func_decl* d = to_func_decl(decls[i]); - ctx->insert(to_symbol(decl_names[i]), d); - sort_names.push_back(d->get_range()->get_name()); - sorts.push_back(d->get_range()); - for (sort* s : *d) { - sort_names.push_back(s->get_name()); - sorts.push_back(s); - } - } - datatype_util dt(m); - for (unsigned i = 0; i < num_sorts; ++i) { - sort* srt = sorts[i]; - symbol name = sort_names[i]; - if (ctx->find_psort_decl(name)) { - continue; - } - psort* ps = ctx->pm().mk_psort_cnst(srt); - ctx->insert(ctx->pm().mk_psort_user_decl(0, name, ps)); - if (!dt.is_datatype(srt)) { - continue; - } + for (unsigned i = 0; i < num_decls; ++i) + ctx->insert(to_symbol(decl_names[i]), to_func_decl(decls[i])); - for (func_decl * c : *dt.get_datatype_constructors(srt)) { - ctx->insert(c->get_name(), c); - func_decl * r = dt.get_constructor_recognizer(c); - ctx->insert(r->get_name(), r); - for (func_decl * a : *dt.get_constructor_accessors(c)) { - ctx->insert(a->get_name(), a); - } - } - } - std::stringstream errstrm; - ctx->set_regular_stream(errstrm); - try { - if (!parse_smt2_commands(*ctx.get(), is)) { - ctx = nullptr; - SET_ERROR_CODE(Z3_PARSER_ERROR, errstrm.str()); - return of_ast_vector(v); - } - } - catch (z3_exception& e) { - errstrm << e.msg(); - ctx = nullptr; - SET_ERROR_CODE(Z3_PARSER_ERROR, errstrm.str()); - return of_ast_vector(v); - } - for (expr* e : ctx->tracked_assertions()) { - v->m_ast_vector.push_back(e); - } - return of_ast_vector(v); + for (unsigned i = 0; i < num_sorts; ++i) + insert_sort(m, ctx, to_symbol(_sort_names[i]), to_sort(_sorts[i])); + + return Z3_parser_context_parse_stream(c, ctx, true, is); Z3_CATCH_RETURN(nullptr); } @@ -180,4 +256,6 @@ extern "C" { RETURN_Z3(mk_c(c)->mk_external_string(ous.str())); Z3_CATCH_RETURN(mk_c(c)->mk_external_string(ous.str())); } + + }; diff --git a/src/api/api_solver.cpp b/src/api/api_solver.cpp index 8e5ebcf2a..53fe1166d 100644 --- a/src/api/api_solver.cpp +++ b/src/api/api_solver.cpp @@ -256,7 +256,10 @@ extern "C" { } void solver_from_stream(Z3_context c, Z3_solver s, std::istream& is) { - scoped_ptr ctx = alloc(cmd_context, false, &(mk_c(c)->m())); + auto& solver = *to_solver(s); + if (!solver.m_cmd_context) + solver.m_cmd_context = alloc(cmd_context, false, &(mk_c(c)->m())); + auto& ctx = solver.m_cmd_context; ctx->set_ignore_check(true); std::stringstream errstrm; ctx->set_regular_stream(errstrm); @@ -272,6 +275,7 @@ extern "C" { init_solver(c, s); for (expr* e : ctx->tracked_assertions()) to_solver(s)->assert_expr(e); + ctx->reset_tracked_assertions(); to_solver_ref(s)->set_model_converter(ctx->get_model_converter()); } diff --git a/src/api/api_solver.h b/src/api/api_solver.h index 5e01d5349..62be1ee60 100644 --- a/src/api/api_solver.h +++ b/src/api/api_solver.h @@ -44,6 +44,7 @@ struct Z3_solver_ref : public api::object { params_ref m_params; symbol m_logic; scoped_ptr m_pp; + scoped_ptr m_cmd_context; mutex m_mux; event_handler* m_eh; diff --git a/src/api/python/z3/z3.py b/src/api/python/z3/z3.py index 7dbef9f6e..5d728e594 100644 --- a/src/api/python/z3/z3.py +++ b/src/api/python/z3/z3.py @@ -9192,6 +9192,25 @@ def _dict2darray(decls, ctx): i = i + 1 return sz, _names, _decls +class ParserContext: + def __init__(self, ctx= None): + self.ctx = _get_ctx(ctx) + self.pctx = Z3_mk_parser_context(self.ctx.ref()) + Z3_parser_context_inc_ref(self.ctx.ref(), self.pctx) + + def __del__(self): + if self.ctx.ref() is not None and self.pctx is not None and Z3_parser_context_dec_ref is not None: + Z3_parser_context_dec_ref(self.ctx.ref(), self.pctx) + self.pctx = None + + def add_sort(self, sort): + Z3_parser_context_add_sort(self.ctx.ref(), self.pctx, sort.as_ast()) + + def add_decl(self, decl): + Z3_parser_context_add_decl(self.ctx.ref(), self.pctx, decl.as_ast()) + + def from_string(self, s): + return AstVector(Z3_parser_context_from_string(self.ctx.ref(), self.pctx, s), self.ctx) def parse_smt2_string(s, sorts={}, decls={}, ctx=None): """Parse a string in SMT 2.0 format using the given sorts and decls. diff --git a/src/api/python/z3/z3types.py b/src/api/python/z3/z3types.py index 6c93c0bee..500e3606e 100644 --- a/src/api/python/z3/z3types.py +++ b/src/api/python/z3/z3types.py @@ -216,6 +216,13 @@ class ParamDescrs(ctypes.c_void_p): def from_param(obj): return obj +class ParserContextObj(ctypes.c_void_p): + def __init__(self, pc): + self._as_parameter_ = pc + + def from_param(obj): + return obj + class FuncInterpObj(ctypes.c_void_p): def __init__(self, f): diff --git a/src/api/z3_api.h b/src/api/z3_api.h index dbf8d9252..831e03b0e 100644 --- a/src/api/z3_api.h +++ b/src/api/z3_api.h @@ -20,6 +20,7 @@ DEFINE_TYPE(Z3_constructor); DEFINE_TYPE(Z3_constructor_list); DEFINE_TYPE(Z3_params); DEFINE_TYPE(Z3_param_descrs); +DEFINE_TYPE(Z3_parser_context); DEFINE_TYPE(Z3_goal); DEFINE_TYPE(Z3_tactic); DEFINE_TYPE(Z3_probe); @@ -58,6 +59,7 @@ DEFINE_TYPE(Z3_rcf_num); - \c Z3_constructor_list: list of constructors for a (recursive) datatype. - \c Z3_params: parameter set used to configure many components such as: simplifiers, tactics, solvers, etc. - \c Z3_param_descrs: provides a collection of parameter names, their types, default values and documentation strings. Solvers, tactics, and other objects accept different collection of parameters. + - \c Z3_parser_context: context for incrementally parsing strings. Declarations can be added incrementally to the parser state. - \c Z3_model: model for the constraints asserted into the logical context. - \c Z3_func_interp: interpretation of a function in a model. - \c Z3_func_entry: representation of the value of a \c Z3_func_interp at a particular point. @@ -1413,6 +1415,7 @@ typedef enum def_Type('CONSTRUCTOR_LIST', 'Z3_constructor_list', 'ConstructorList') def_Type('SOLVER', 'Z3_solver', 'SolverObj') def_Type('SOLVER_CALLBACK', 'Z3_solver_callback', 'SolverCallbackObj') + def_Type('PARSER_CONTEXT', 'Z3_parser_context', 'ParserContextObj') def_Type('GOAL', 'Z3_goal', 'GoalObj') def_Type('TACTIC', 'Z3_tactic', 'TacticObj') def_Type('PARAMS', 'Z3_params', 'Params') @@ -5827,6 +5830,55 @@ extern "C" { Z3_string Z3_API Z3_eval_smtlib2_string(Z3_context, Z3_string str); + + /** + \brief Create a parser context. + + A parser context maintains state between calls to \c Z3_parser_context_parse_string + where the caller can pass in a set of SMTLIB2 commands. + It maintains all the declarations from previous calls together with + of sorts and function declarations (including 0-ary) that are added directly to the context. + + def_API('Z3_mk_parser_context', PARSER_CONTEXT, (_in(CONTEXT),)) + */ + Z3_parser_context Z3_API Z3_mk_parser_context(Z3_context c); + + /** + \brief Increment the reference counter of the given \c Z3_parser_context object. + + def_API('Z3_parser_context_inc_ref', VOID, (_in(CONTEXT), _in(PARSER_CONTEXT))) + */ + void Z3_API Z3_parser_context_inc_ref(Z3_context c, Z3_parser_context pc); + + /** + \brief Decrement the reference counter of the given \c Z3_parser_context object. + + def_API('Z3_parser_context_dec_ref', VOID, (_in(CONTEXT), _in(PARSER_CONTEXT))) + */ + void Z3_API Z3_parser_context_dec_ref(Z3_context c, Z3_parser_context pc); + + /** + \brief Add a sort declaration. + + def_API('Z3_parser_context_add_sort', VOID, (_in(CONTEXT), _in(PARSER_CONTEXT), _in(SORT))) + */ + void Z3_API Z3_parser_context_add_sort(Z3_context c, Z3_parser_context pc, Z3_sort s); + + /** + \brief Add a function declaration. + + def_API('Z3_parser_context_add_decl', VOID, (_in(CONTEXT), _in(PARSER_CONTEXT), _in(FUNC_DECL))) + */ + void Z3_API Z3_parser_context_add_decl(Z3_context c, Z3_parser_context pc, Z3_func_decl f); + + /** + \brief Parse a string of SMTLIB2 commands. Return assertions. + + def_API('Z3_parser_context_from_string', AST_VECTOR, (_in(CONTEXT), _in(PARSER_CONTEXT), _in(STRING))) + */ + Z3_ast_vector Z3_API Z3_parser_context_from_string(Z3_context c, Z3_parser_context pc, Z3_string s); + + /**@}*/ /** @name Error Handling */ diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp index 2174c9e0b..705396f53 100644 --- a/src/cmd_context/cmd_context.cpp +++ b/src/cmd_context/cmd_context.cpp @@ -2203,22 +2203,25 @@ expr_ref_vector cmd_context::tracked_assertions() { for (unsigned i = 0; i < assertions().size(); ++i) { expr* an = assertion_names()[i]; expr* asr = assertions()[i]; - if (an) { + if (an) result.push_back(m().mk_implies(an, asr)); - } - else { + else result.push_back(asr); - } } } else { - for (expr * e : assertions()) { + for (expr * e : assertions()) result.push_back(e); - } } return result; } +void cmd_context::reset_tracked_assertions() { + m_assertion_names.reset(); + for (expr* a : m_assertions) + m().dec_ref(a); + m_assertions.reset(); +} void cmd_context::display_assertions() { if (!m_interactive_mode) @@ -2254,9 +2257,8 @@ format_ns::format * cmd_context::pp(sort * s) const { } cmd_context::pp_env & cmd_context::get_pp_env() const { - if (m_pp_env.get() == nullptr) { + if (m_pp_env.get() == nullptr) const_cast(this)->m_pp_env = alloc(pp_env, *const_cast(this)); - } return *(m_pp_env.get()); } @@ -2314,9 +2316,8 @@ void cmd_context::display_smt2_benchmark(std::ostream & out, unsigned num, expr out << "(set-logic " << logic << ")" << std::endl; // collect uninterpreted function declarations decl_collector decls(m()); - for (unsigned i = 0; i < num; i++) { + for (unsigned i = 0; i < num; i++) decls.visit(assertions[i]); - } // TODO: display uninterpreted sort decls, and datatype decls. @@ -2342,9 +2343,8 @@ void cmd_context::slow_progress_sample() { svector labels; m_solver->get_labels(labels); regular_stream() << "(labels"; - for (symbol const& s : labels) { + for (symbol const& s : labels) regular_stream() << " " << s; - } regular_stream() << "))" << std::endl; } diff --git a/src/cmd_context/cmd_context.h b/src/cmd_context/cmd_context.h index 60a6e930b..c51809190 100644 --- a/src/cmd_context/cmd_context.h +++ b/src/cmd_context/cmd_context.h @@ -485,6 +485,7 @@ public: ptr_vector const& assertions() const { return m_assertions; } ptr_vector const& assertion_names() const { return m_assertion_names; } expr_ref_vector tracked_assertions(); + void reset_tracked_assertions(); /** \brief Hack: consume assertions if there are no scopes. From 4d23f2801c504342991a45000d0fd193f278ecd8 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Fri, 1 Jul 2022 20:35:47 -0700 Subject: [PATCH 033/125] ml pre Signed-off-by: Nikolaj Bjorner --- src/api/ml/z3native.ml.pre | 1 + src/api/ml/z3native_stubs.c.pre | 1 + 2 files changed, 2 insertions(+) diff --git a/src/api/ml/z3native.ml.pre b/src/api/ml/z3native.ml.pre index 8f76aa950..93df8ad07 100644 --- a/src/api/ml/z3native.ml.pre +++ b/src/api/ml/z3native.ml.pre @@ -21,6 +21,7 @@ and solver_callback = ptr and goal = ptr and tactic = ptr and params = ptr +and parser_context = ptr and probe = ptr and stats = ptr and ast_vector = ptr diff --git a/src/api/ml/z3native_stubs.c.pre b/src/api/ml/z3native_stubs.c.pre index 0efaa110f..e9cfa443b 100644 --- a/src/api/ml/z3native_stubs.c.pre +++ b/src/api/ml/z3native_stubs.c.pre @@ -418,6 +418,7 @@ MK_PLUS_OBJ_NO_REF(constructor_list, 16) MK_PLUS_OBJ_NO_REF(rcf_num, 16) MK_PLUS_OBJ(params, 64) MK_PLUS_OBJ(param_descrs, 64) +MK_PLUS_OBJ(parser_context, 64) MK_PLUS_OBJ(model, 64) MK_PLUS_OBJ(func_interp, 32) MK_PLUS_OBJ(func_entry, 32) From f20db3e644c07f8be2ff032d1206240ca6825469 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sat, 2 Jul 2022 09:31:36 -0700 Subject: [PATCH 034/125] allow for toggling proof and core mode until the first assertion. --- src/cmd_context/basic_cmds.cpp | 29 ++++++++++++++++++----------- src/cmd_context/cmd_context.cpp | 20 ++++---------------- src/cmd_context/cmd_context.h | 31 ++++++++++++++++--------------- 3 files changed, 38 insertions(+), 42 deletions(-) diff --git a/src/cmd_context/basic_cmds.cpp b/src/cmd_context/basic_cmds.cpp index f3bd0ed57..dc8836138 100644 --- a/src/cmd_context/basic_cmds.cpp +++ b/src/cmd_context/basic_cmds.cpp @@ -219,9 +219,8 @@ ATOMIC_CMD(get_proof_graph_cmd, "get-proof-graph", "retrieve proof and print it pr = ctx.get_check_sat_result()->get_proof(); if (pr == 0) throw cmd_exception("proof is not available"); - if (ctx.well_sorted_check_enabled() && !is_well_sorted(ctx.m(), pr)) { + if (ctx.well_sorted_check_enabled() && !is_well_sorted(ctx.m(), pr)) throw cmd_exception("proof is not well sorted"); - } context_params& params = ctx.params(); const std::string& file = params.m_dot_proof_file; @@ -235,11 +234,11 @@ static void print_core(cmd_context& ctx) { ctx.regular_stream() << "("; bool first = true; for (expr* e : core) { - if (first) - first = false; - else - ctx.regular_stream() << " "; - ctx.regular_stream() << mk_ismt2_pp(e, ctx.m()); + if (first) + first = false; + else + ctx.regular_stream() << " "; + ctx.regular_stream() << mk_ismt2_pp(e, ctx.m()); } ctx.regular_stream() << ")" << std::endl; } @@ -260,9 +259,8 @@ ATOMIC_CMD(get_unsat_assumptions_cmd, "get-unsat-assumptions", "retrieve subset return; if (!ctx.produce_unsat_assumptions()) throw cmd_exception("unsat assumptions construction is not enabled, use command (set-option :produce-unsat-assumptions true)"); - if (!ctx.has_manager() || ctx.cs_state() != cmd_context::css_unsat) { + if (!ctx.has_manager() || ctx.cs_state() != cmd_context::css_unsat) throw cmd_exception("unsat assumptions is not available"); - } print_core(ctx); }); @@ -410,6 +408,15 @@ class set_option_cmd : public set_get_option_cmd { } } + static void check_no_assertions(cmd_context & ctx, symbol const & opt_name) { + if (ctx.has_assertions()) { + std::string msg = "error setting '"; + msg += opt_name.str(); + msg += "', option value cannot be modified after assertions have been added"; + throw cmd_exception(std::move(msg)); + } + } + void set_param(cmd_context & ctx, char const * value) { try { gparams::set(m_option, value); @@ -437,11 +444,11 @@ class set_option_cmd : public set_get_option_cmd { ctx.set_interactive_mode(to_bool(value)); } else if (m_option == m_produce_proofs) { - check_not_initialized(ctx, m_produce_proofs); + check_no_assertions(ctx, m_produce_proofs); ctx.set_produce_proofs(to_bool(value)); } else if (m_option == m_produce_unsat_cores) { - check_not_initialized(ctx, m_produce_unsat_cores); + check_no_assertions(ctx, m_produce_unsat_cores); ctx.set_produce_unsat_cores(to_bool(value)); } else if (m_option == m_produce_unsat_assumptions) { diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp index 705396f53..ad150b674 100644 --- a/src/cmd_context/cmd_context.cpp +++ b/src/cmd_context/cmd_context.cpp @@ -538,22 +538,9 @@ public: cmd_context::cmd_context(bool main_ctx, ast_manager * m, symbol const & l): m_main_ctx(main_ctx), m_logic(l), - m_interactive_mode(false), - m_global_decls(false), m_print_success(m_params.m_smtlib2_compliant), - m_random_seed(0), - m_produce_unsat_cores(false), - m_produce_unsat_assumptions(false), - m_produce_assignments(false), - m_status(UNKNOWN), - m_numeral_as_real(false), - m_ignore_check(false), - m_exit_on_error(false), m_manager(m), m_own_manager(m == nullptr), - m_manager_initialized(false), - m_pmanager(nullptr), - m_sexpr_manager(nullptr), m_regular("stdout", std::cout), m_diagnostic("stderr", std::cerr) { SASSERT(m != 0 || !has_manager()); @@ -626,13 +613,14 @@ void cmd_context::set_produce_models(bool f) { void cmd_context::set_produce_unsat_cores(bool f) { // can only be set before initialization - SASSERT(!has_manager()); + SASSERT(!has_assertions()); m_params.m_unsat_core |= f; } void cmd_context::set_produce_proofs(bool f) { - // can only be set before initialization - SASSERT(!has_manager()); + SASSERT(!has_assertions()); + if (has_manager()) + m().toggle_proof_mode(f ? PGM_ENABLED : PGM_DISABLED); m_params.m_proof = f; } diff --git a/src/cmd_context/cmd_context.h b/src/cmd_context/cmd_context.h index c51809190..3dc49624b 100644 --- a/src/cmd_context/cmd_context.h +++ b/src/cmd_context/cmd_context.h @@ -194,21 +194,21 @@ public: protected: - ast_context_params m_params; + ast_context_params m_params; bool m_main_ctx; symbol m_logic; - bool m_interactive_mode; - bool m_global_decls; + bool m_interactive_mode = false; + bool m_global_decls = false; bool m_print_success; - unsigned m_random_seed; - bool m_produce_unsat_cores; - bool m_produce_unsat_assumptions; - bool m_produce_assignments; - status m_status; - bool m_numeral_as_real; - bool m_ignore_check; // used by the API to disable check-sat() commands when parsing SMT 2.0 files. - bool m_exit_on_error; - bool m_allow_duplicate_declarations { false }; + unsigned m_random_seed = 0; + bool m_produce_unsat_cores = false; + bool m_produce_unsat_assumptions = false; + bool m_produce_assignments = false; + status m_status = UNKNOWN; + bool m_numeral_as_real = false; + bool m_ignore_check = false; // used by the API to disable check-sat() commands when parsing SMT 2.0 files. + bool m_exit_on_error = false; + bool m_allow_duplicate_declarations = false; static std::ostringstream g_error_stream; @@ -216,9 +216,9 @@ protected: sref_vector m_mcs; ast_manager * m_manager; bool m_own_manager; - bool m_manager_initialized; - pdecl_manager * m_pmanager; - sexpr_manager * m_sexpr_manager; + bool m_manager_initialized = false; + pdecl_manager * m_pmanager = nullptr; + sexpr_manager * m_sexpr_manager = nullptr; check_logic m_check_logic; stream_ref m_regular; stream_ref m_diagnostic; @@ -362,6 +362,7 @@ public: bool produce_unsat_cores() const; bool well_sorted_check_enabled() const; bool validate_model_enabled() const; + bool has_assertions() const { return !m_assertions.empty(); } void set_produce_models(bool flag); void set_produce_unsat_cores(bool flag); void set_produce_proofs(bool flag); From 004139b320aaaaf9458d5a0c844583b2cd085966 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sat, 2 Jul 2022 11:37:21 -0700 Subject: [PATCH 035/125] rewrites for characters Signed-off-by: Nikolaj Bjorner --- src/ast/rewriter/char_rewriter.cpp | 36 ++++++++++++++++++++++++++++++ src/ast/rewriter/char_rewriter.h | 6 +++++ src/ast/seq_decl_plugin.cpp | 7 +++++- 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/ast/rewriter/char_rewriter.cpp b/src/ast/rewriter/char_rewriter.cpp index 14b86773a..01f605816 100644 --- a/src/ast/rewriter/char_rewriter.cpp +++ b/src/ast/rewriter/char_rewriter.cpp @@ -37,21 +37,57 @@ br_status char_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * co case OP_CHAR_CONST: break; case OP_CHAR_LE: + st = mk_char_le(args[0], args[1], result); break; case OP_CHAR_TO_INT: st = mk_char_to_int(args[0], result); break; case OP_CHAR_TO_BV: + st = mk_char_to_bv(args[0], result); break; case OP_CHAR_FROM_BV: st = mk_char_from_bv(args[0], result); break; case OP_CHAR_IS_DIGIT: + st = mk_char_is_digit(args[0], result); break; } return st; } +br_status char_rewriter::mk_char_is_digit(expr* a, expr_ref& result) { + unsigned n; + if (!m_char->is_const_char(a, n)) + return BR_FAILED; + result = m.mk_bool_val('0' <= n && n <= '9'); + return BR_DONE; +} + +br_status char_rewriter::mk_char_to_bv(expr* a, expr_ref& result) { + return BR_FAILED; +} + +br_status char_rewriter::mk_char_le(expr* a, expr* b, expr_ref& result) { + unsigned na = 0, nb = 0; + if (m_char->is_const_char(a, na)) { + if (na == 0) { + result = m.mk_true(); + return BR_DONE; + } + } + if (m_char->is_const_char(b, nb)) { + if (na != 0) { + result = m.mk_bool_val(na <= nb); + return BR_DONE; + } + if (nb == m_char->max_char()) { + result = m.mk_true(); + return BR_DONE; + } + } + return BR_FAILED; +} + br_status char_rewriter::mk_char_from_bv(expr* e, expr_ref& result) { bv_util bv(m); rational n; diff --git a/src/ast/rewriter/char_rewriter.h b/src/ast/rewriter/char_rewriter.h index f67695bd8..0ff833849 100644 --- a/src/ast/rewriter/char_rewriter.h +++ b/src/ast/rewriter/char_rewriter.h @@ -35,6 +35,12 @@ class char_rewriter { br_status mk_char_to_int(expr* e, expr_ref& result); + br_status mk_char_le(expr* a, expr* b, expr_ref& result); + + br_status mk_char_is_digit(expr* a, expr_ref& result); + + br_status mk_char_to_bv(expr* a, expr_ref& result); + public: char_rewriter(ast_manager& m); diff --git a/src/ast/seq_decl_plugin.cpp b/src/ast/seq_decl_plugin.cpp index 95b9b8579..bb9df68a2 100644 --- a/src/ast/seq_decl_plugin.cpp +++ b/src/ast/seq_decl_plugin.cpp @@ -532,7 +532,12 @@ func_decl* seq_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, p case _OP_STRING_FROM_CHAR: { if (!(num_parameters == 1 && parameters[0].is_int())) m.raise_exception("character literal expects integer parameter"); - zstring zs(parameters[0].get_int()); + int i = parameters[0].get_int(); + if (i < 0) + m.raise_exception("character literal expects a non-negative integer parameter"); + if (i > (int)m_char_plugin->max_char()) + m.raise_exception("character literal is out of bounds"); + zstring zs(i); parameter p(zs); return m.mk_const_decl(m_stringc_sym, m_string,func_decl_info(m_family_id, OP_STRING_CONST, 1, &p)); } From 54b16f049698611c00f2d838c8afec2b12e5ba30 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sat, 2 Jul 2022 13:04:09 -0700 Subject: [PATCH 036/125] Update NativeStatic.txt not so automatically generated --- src/api/java/NativeStatic.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/java/NativeStatic.txt b/src/api/java/NativeStatic.txt index 657050951..4693272d5 100644 --- a/src/api/java/NativeStatic.txt +++ b/src/api/java/NativeStatic.txt @@ -1,4 +1,4 @@ -// Automatically generated file + #include #include #include"z3.h" From c35d0d1e4901fcecff11dd136b3a1fd4ada8e5a7 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sat, 2 Jul 2022 13:05:35 -0700 Subject: [PATCH 037/125] Update update_api.py add automation! --- scripts/update_api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/update_api.py b/scripts/update_api.py index f5d935514..67d6fcb67 100755 --- a/scripts/update_api.py +++ b/scripts/update_api.py @@ -689,6 +689,7 @@ def mk_java(java_src, java_dir, package_name): java_native.write('}\n') java_wrapper = open(java_wrapperf, 'w') pkg_str = package_name.replace('.', '_') + java_wrapperf.write("// Automatically generated file\n") with open(java_src + "/NativeStatic.txt") as ins: for line in ins: java_wrapper.write(line) From d37ed4171dedba13b1dcb8c2f5a4aa87a22974cd Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sat, 2 Jul 2022 13:12:54 -0700 Subject: [PATCH 038/125] Update Expr.cs Add a Dup functionality that allows extending the life-time of expressions that are passed by the UserPropagator callbacks (or other code). --- src/api/dotnet/Expr.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/api/dotnet/Expr.cs b/src/api/dotnet/Expr.cs index f735401d8..7dabc49cd 100644 --- a/src/api/dotnet/Expr.cs +++ b/src/api/dotnet/Expr.cs @@ -168,6 +168,16 @@ namespace Microsoft.Z3 { return (Expr)base.Translate(ctx); } + + /// + /// Create a duplicate of expression. + /// This feature is to allow extending the life-time of expressions that were passed down as arguments + /// by the user propagator callbacks. By default the life-time of arguments to callbacks is within the + /// callback only. + /// + public Expr Dup() { + return Expr.Create(Context, NativeObject); + } /// /// Returns a string representation of the expression. From bb966776b8e79b213c0c785683bfcbfd1d2e24f6 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sat, 2 Jul 2022 13:15:05 -0700 Subject: [PATCH 039/125] Update UserPropagator.cs --- src/api/dotnet/UserPropagator.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/api/dotnet/UserPropagator.cs b/src/api/dotnet/UserPropagator.cs index ce9680a7a..d937b17a9 100644 --- a/src/api/dotnet/UserPropagator.cs +++ b/src/api/dotnet/UserPropagator.cs @@ -41,6 +41,9 @@ namespace Microsoft.Z3 { /// /// Delegate type for fixed callback + /// Note that the life-time of the term and value only applies within the scope of the callback. + /// That means the term and value cannot be stored in an array, dictionary or similar and accessed after the callback has returned. + /// Use the functionality Dup on expressions to create a duplicate copy that extends the lifetime. /// public delegate void FixedEh(Expr term, Expr value); From e6e0c74324e5105ce864aba071d849d53ac6e489 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sat, 2 Jul 2022 13:17:14 -0700 Subject: [PATCH 040/125] Update update_api.py fix typo --- scripts/update_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/update_api.py b/scripts/update_api.py index 67d6fcb67..43903e92c 100755 --- a/scripts/update_api.py +++ b/scripts/update_api.py @@ -689,7 +689,7 @@ def mk_java(java_src, java_dir, package_name): java_native.write('}\n') java_wrapper = open(java_wrapperf, 'w') pkg_str = package_name.replace('.', '_') - java_wrapperf.write("// Automatically generated file\n") + java_wrapper.write("// Automatically generated file\n") with open(java_src + "/NativeStatic.txt") as ins: for line in ins: java_wrapper.write(line) From 1e8f9078e3246ba47f9f79344bd9515780d4293a Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sun, 3 Jul 2022 16:57:49 -0700 Subject: [PATCH 041/125] fix unsoundness in explanation handling for nested datatypes and sequences --- src/ast/seq_decl_plugin.cpp | 16 +++++++++ src/ast/seq_decl_plugin.h | 1 + src/smt/theory_datatype.cpp | 65 ++++++++++++++++++++----------------- src/smt/theory_datatype.h | 2 +- 4 files changed, 54 insertions(+), 30 deletions(-) diff --git a/src/ast/seq_decl_plugin.cpp b/src/ast/seq_decl_plugin.cpp index bb9df68a2..bf377df43 100644 --- a/src/ast/seq_decl_plugin.cpp +++ b/src/ast/seq_decl_plugin.cpp @@ -971,6 +971,22 @@ bool seq_util::str::is_len_sub(expr const* s, expr*& l, expr*& u, rational& k) c return false; } +bool seq_util::str::is_concat_of_units(expr* s) const { + ptr_vector todo; + todo.push_back(s); + while (!todo.empty()) { + expr* e = todo.back(); + todo.pop_back(); + if (is_empty(e) || is_unit(e)) + continue; + if (is_concat(e)) + todo.append(to_app(e)->get_num_args(), to_app(e)->get_args()); + else + return false; + } + return true; +} + bool seq_util::str::is_unit_string(expr const* s, expr_ref& c) const { zstring z; expr* ch = nullptr; diff --git a/src/ast/seq_decl_plugin.h b/src/ast/seq_decl_plugin.h index 29d7b08ef..b0c3ab3c6 100644 --- a/src/ast/seq_decl_plugin.h +++ b/src/ast/seq_decl_plugin.h @@ -375,6 +375,7 @@ public: bool is_to_code(expr const* n) const { return is_app_of(n, m_fid, OP_STRING_TO_CODE); } bool is_len_sub(expr const* n, expr*& l, expr*& u, rational& k) const; + bool is_concat_of_units(expr* n) const; /* tests if s is a single character string(c) or a unit (c) diff --git a/src/smt/theory_datatype.cpp b/src/smt/theory_datatype.cpp index 27b922dba..358fda122 100644 --- a/src/smt/theory_datatype.cpp +++ b/src/smt/theory_datatype.cpp @@ -25,6 +25,7 @@ Revision History: #include "smt/theory_datatype.h" #include "smt/theory_array.h" #include "smt/smt_model_generator.h" +#include namespace smt { @@ -519,9 +520,8 @@ namespace smt { void theory_datatype::explain_is_child(enode* parent, enode* child) { enode * parentc = oc_get_cstor(parent); - if (parent != parentc) { + if (parent != parentc) m_used_eqs.push_back(enode_pair(parent, parentc)); - } // collect equalities on all children that may have been used. bool found = false; @@ -546,11 +546,13 @@ namespace smt { } sort* se = nullptr; if (m_sutil.is_seq(s, se) && m_util.is_datatype(se)) { - for (enode* aarg : get_seq_args(arg)) { + enode* sibling; + for (enode* aarg : get_seq_args(arg, sibling)) { if (aarg->get_root() == child->get_root()) { - if (aarg != child) { + if (aarg != child) m_used_eqs.push_back(enode_pair(aarg, child)); - } + if (sibling != arg) + m_used_eqs.push_back(enode_pair(arg, sibling)); found = true; } } @@ -582,9 +584,11 @@ namespace smt { TRACE("datatype", tout << "occurs_check\n"; - for (enode_pair const& p : m_used_eqs) { + for (enode_pair const& p : m_used_eqs) tout << enode_eq_pp(p, ctx); - }); + for (auto const& [a,b] : m_used_eqs) + tout << mk_pp(a->get_expr(), m) << " = " << mk_pp(b->get_expr(), m) << "\n"; + ); } // start exploring subgraph below `app` @@ -596,9 +600,9 @@ namespace smt { } v = m_find.find(v); var_data * d = m_var_data[v]; - if (!d->m_constructor) { + + if (!d->m_constructor) return false; - } enode * parent = d->m_constructor; oc_mark_on_stack(parent); auto process_arg = [&](enode* aarg) { @@ -616,9 +620,8 @@ namespace smt { }; for (enode * arg : enode::args(parent)) { - if (oc_cycle_free(arg)) { + if (oc_cycle_free(arg)) continue; - } if (oc_on_stack(arg)) { // arg was explored before app, and is still on the stack: cycle occurs_check_explain(parent, arg); @@ -632,9 +635,11 @@ namespace smt { oc_push_stack(arg); } else if (m_sutil.is_seq(s, se) && m_util.is_datatype(se)) { - for (enode* sarg : get_seq_args(arg)) - if (process_arg(sarg)) + enode* sibling; + for (enode* sarg : get_seq_args(arg, sibling)) { + if (process_arg(sarg)) return true; + } } else if (m_autil.is_array(s) && m_util.is_datatype(get_array_range(s))) { for (enode* aarg : get_array_args(arg)) @@ -645,7 +650,7 @@ namespace smt { return false; } - ptr_vector const& theory_datatype::get_seq_args(enode* n) { + ptr_vector const& theory_datatype::get_seq_args(enode* n, enode*& sibling) { m_args.reset(); m_todo.reset(); auto add_todo = [&](enode* n) { @@ -654,9 +659,14 @@ namespace smt { m_todo.push_back(n); } }; - - for (enode* sib : *n) - add_todo(sib); + + for (enode* sib : *n) { + if (m_sutil.str.is_concat_of_units(sib->get_expr())) { + add_todo(sib); + sibling = sib; + break; + } + } for (unsigned i = 0; i < m_todo.size(); ++i) { enode* n = m_todo[i]; @@ -691,7 +701,7 @@ namespace smt { a3 = cons(v3, a1) */ bool theory_datatype::occurs_check(enode * n) { - TRACE("datatype", tout << "occurs check: " << enode_pp(n, ctx) << "\n";); + TRACE("datatype_verbose", tout << "occurs check: " << enode_pp(n, ctx) << "\n";); m_stats.m_occurs_check++; bool res = false; @@ -706,7 +716,7 @@ namespace smt { if (oc_cycle_free(app)) continue; - TRACE("datatype", tout << "occurs check loop: " << enode_pp(app, ctx) << (op==ENTER?" enter":" exit")<< "\n";); + TRACE("datatype_verbose", tout << "occurs check loop: " << enode_pp(app, ctx) << (op==ENTER?" enter":" exit")<< "\n";); switch (op) { case ENTER: @@ -830,15 +840,14 @@ namespace smt { SASSERT(d->m_constructor); func_decl * c_decl = d->m_constructor->get_decl(); datatype_value_proc * result = alloc(datatype_value_proc, c_decl); - for (enode* arg : enode::args(d->m_constructor)) { + for (enode* arg : enode::args(d->m_constructor)) result->add_dependency(arg); - } TRACE("datatype", tout << pp(n, m) << "\n"; tout << "depends on\n"; - for (enode* arg : enode::args(d->m_constructor)) { + for (enode* arg : enode::args(d->m_constructor)) tout << " " << pp(arg, m) << "\n"; - }); + ); return result; } @@ -965,12 +974,11 @@ namespace smt { SASSERT(!lits.empty()); region & reg = ctx.get_region(); TRACE("datatype_conflict", tout << mk_ismt2_pp(recognizer->get_expr(), m) << "\n"; - for (literal l : lits) { + for (literal l : lits) ctx.display_detailed_literal(tout, l) << "\n"; - } - for (auto const& p : eqs) { + for (auto const& p : eqs) tout << enode_eq_pp(p, ctx); - }); + ); ctx.set_conflict(ctx.mk_justification(ext_theory_conflict_justification(get_id(), reg, lits.size(), lits.data(), eqs.size(), eqs.data()))); } else if (num_unassigned == 1) { @@ -1052,9 +1060,8 @@ namespace smt { ctx.mark_as_relevant(curr); return; } - else if (ctx.get_assignment(curr) != l_false) { + else if (ctx.get_assignment(curr) != l_false) return; - } } if (r == nullptr) return; // all recognizers are asserted to false... conflict will be detected... diff --git a/src/smt/theory_datatype.h b/src/smt/theory_datatype.h index c0e06b58d..d64cc9388 100644 --- a/src/smt/theory_datatype.h +++ b/src/smt/theory_datatype.h @@ -94,7 +94,7 @@ namespace smt { void oc_push_stack(enode * n); ptr_vector m_args, m_todo; ptr_vector const& get_array_args(enode* n); - ptr_vector const& get_seq_args(enode* n); + ptr_vector const& get_seq_args(enode* n, enode*& sibling); // class for managing state of final_check class final_check_st { From 02a92fb9e9fb2b9460d640dff7404961f0abf028 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sun, 3 Jul 2022 17:00:20 -0700 Subject: [PATCH 042/125] revert to use GCHandle for UserPropagator avoids using a global static array --- src/api/dotnet/UserPropagator.cs | 33 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/api/dotnet/UserPropagator.cs b/src/api/dotnet/UserPropagator.cs index d937b17a9..3e9344556 100644 --- a/src/api/dotnet/UserPropagator.cs +++ b/src/api/dotnet/UserPropagator.cs @@ -67,8 +67,7 @@ namespace Microsoft.Z3 // access managed objects through a static array. // thread safety is ignored for now. - static List propagators = new List(); - int id; + GCHandle gch; Solver solver; Context ctx; Z3_solver_callback callback = IntPtr.Zero; @@ -110,27 +109,27 @@ namespace Microsoft.Z3 static void _push(voidp ctx, Z3_solver_callback cb) { - var prop = propagators[ctx.ToInt32()]; + var prop = (UserPropagator)GCHandle.FromIntPtr(ctx).Target; prop.Callback(() => prop.Push(), cb); } static void _pop(voidp ctx, Z3_solver_callback cb, uint num_scopes) { - var prop = propagators[ctx.ToInt32()]; + var prop = (UserPropagator)GCHandle.FromIntPtr(ctx).Target; prop.Callback(() => prop.Pop(num_scopes), cb); } static voidp _fresh(voidp _ctx, Z3_context new_context) { - var prop = propagators[_ctx.ToInt32()]; + var prop = (UserPropagator)GCHandle.FromIntPtr(_ctx).Target; var ctx = new Context(new_context); var prop1 = prop.Fresh(prop.ctx); - return new IntPtr(prop1.id); + return GCHandle.ToIntPtr(prop1.gch); } static void _fixed(voidp ctx, Z3_solver_callback cb, Z3_ast _term, Z3_ast _value) { - var prop = propagators[ctx.ToInt32()]; + var prop = (UserPropagator)GCHandle.FromIntPtr(ctx).Target; using var term = Expr.Create(prop.ctx, _term); using var value = Expr.Create(prop.ctx, _value); prop.Callback(() => prop.fixed_eh(term, value), cb); @@ -138,13 +137,13 @@ namespace Microsoft.Z3 static void _final(voidp ctx, Z3_solver_callback cb) { - var prop = propagators[ctx.ToInt32()]; + var prop = (UserPropagator)GCHandle.FromIntPtr(ctx).Target; prop.Callback(() => prop.final_eh(), cb); } static void _eq(voidp ctx, Z3_solver_callback cb, Z3_ast a, Z3_ast b) { - var prop = propagators[ctx.ToInt32()]; + var prop = (UserPropagator)GCHandle.FromIntPtr(ctx).Target; using var s = Expr.Create(prop.ctx, a); using var t = Expr.Create(prop.ctx, b); prop.Callback(() => prop.eq_eh(s, t), cb); @@ -152,7 +151,7 @@ namespace Microsoft.Z3 static void _diseq(voidp ctx, Z3_solver_callback cb, Z3_ast a, Z3_ast b) { - var prop = propagators[ctx.ToInt32()]; + var prop = (UserPropagator)GCHandle.FromIntPtr(ctx).Target; using var s = Expr.Create(prop.ctx, a); using var t = Expr.Create(prop.ctx, b); prop.Callback(() => prop.diseq_eh(s, t), cb); @@ -160,14 +159,14 @@ namespace Microsoft.Z3 static void _created(voidp ctx, Z3_solver_callback cb, Z3_ast a) { - var prop = propagators[ctx.ToInt32()]; + var prop = (UserPropagator)GCHandle.FromIntPtr(ctx).Target; using var t = Expr.Create(prop.ctx, a); prop.Callback(() => prop.created_eh(t), cb); } static void _decide(voidp ctx, Z3_solver_callback cb, ref Z3_ast a, ref uint idx, ref int phase) { - var prop = propagators[ctx.ToInt32()]; + var prop = (UserPropagator)GCHandle.FromIntPtr(ctx).Target; var t = Expr.Create(prop.ctx, a); var u = t; prop.callback = cb; @@ -182,14 +181,13 @@ namespace Microsoft.Z3 /// public UserPropagator(Solver s) { - id = propagators.Count; - propagators.Add(this); + gch = GCHandle.Alloc(this); solver = s; ctx = solver.Context; push_eh = _push; pop_eh = _pop; fresh_eh = _fresh; - Native.Z3_solver_propagate_init(ctx.nCtx, solver.NativeObject, new IntPtr(id), push_eh, pop_eh, fresh_eh); + Native.Z3_solver_propagate_init(ctx.nCtx, solver.NativeObject, GCHandle.ToIntPtr(gch), push_eh, pop_eh, fresh_eh); } /// @@ -197,8 +195,7 @@ namespace Microsoft.Z3 /// public UserPropagator(Context _ctx) { - id = propagators.Count; - propagators.Add(this); + gch = GCHandle.Alloc(this); solver = null; ctx = _ctx; } @@ -208,7 +205,7 @@ namespace Microsoft.Z3 /// ~UserPropagator() { - propagators[id] = null; + gch.Free(); if (solver == null) ctx.Dispose(); } From d61d0f6a66b3874e247d8f8396bd86ffc0dee05e Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sun, 3 Jul 2022 17:00:29 -0700 Subject: [PATCH 043/125] prepare release notes --- RELEASE_NOTES | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/RELEASE_NOTES b/RELEASE_NOTES index e9c5c1fd7..abc6fec1d 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -1,6 +1,6 @@ RELEASE NOTES -Version 4.8.next +Version 4.9.next ================ - Planned features - sat.euf @@ -10,6 +10,20 @@ Version 4.8.next - native word level bit-vector solving. - introduction of simple induction lemmas to handle a limited repertoire of induction proofs. +Version 4.9 +=========== +- API for incremental parsing of assertions. + A description of the feature is given by example here: https://github.com/Z3Prover/z3/commit/815518dc026e900392bf0d08ed859e5ec42d1e43 + It also allows incrementality at the level of adding assertions to the + solver object. +- Fold/map for sequences: + https://microsoft.github.io/rise4fun/docs/guide/Sequences#map-and-fold + At this point these functions are only exposed over the SMTLIB2 interface (and not programmatic API) +- User Propagator: + - Add functions and callbacks for external control over branching + - A functioning dotnet API for the User Propagator + https://github.com/Z3Prover/z3/blob/master/src/api/dotnet/UserPropagator.cs + Version 4.8.17 ============== - fix breaking bug in python interface for user propagator pop @@ -17,6 +31,29 @@ Version 4.8.17 - initial support for nested algebraic datatypes with sequences - initiate map/fold operators on sequences - full integration for next releases - initiate maxdiff/mindiff on arrays - full integration for next releases + +Examples: + +``` +(declare-sort Expr) +(declare-sort Var) +(declare-datatypes ((Stmt 0)) + (((Assignment (lval Var) (rval Expr)) + (If (cond Expr) (th Stmt) (el Stmt)) + (Seq (stmts (Seq Stmt)))))) + +(declare-const s Stmt) +(declare-const t Stmt) + +(assert ((_ is Seq) t)) +(assert ((_ is Seq) s)) +(assert (= s (seq.nth (stmts t) 2))) +(assert (>= (seq.len (stmts s)) 5)) +(check-sat) +(get-model) +(assert (= s (Seq (seq.unit s)))) +(check-sat) +``` Version 4.8.16 ============== From ac8aaed1d4f409fa8b4fc60313fc5029fb18d731 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sun, 3 Jul 2022 17:47:05 -0700 Subject: [PATCH 044/125] fix #6126 --- src/ackermannization/ackr_model_converter.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/ackermannization/ackr_model_converter.cpp b/src/ackermannization/ackr_model_converter.cpp index a5b2630c7..78fe6bef0 100644 --- a/src/ackermannization/ackr_model_converter.cpp +++ b/src/ackermannization/ackr_model_converter.cpp @@ -46,11 +46,15 @@ public: void operator()(model_ref & md) override { TRACE("ackermannize", tout << (fixed_model? "fixed" : "nonfixed") << "\n";); - SASSERT(!fixed_model || md.get() == 0 || (!md->get_num_constants() && !md->get_num_functions())); - model_ref& old_model = fixed_model ? abstr_model : md; - SASSERT(old_model.get()); - model * new_model = alloc(model, m); - convert(old_model.get(), new_model); + CTRACE("ackermannize", md, tout << *md << "\n"); + CTRACE("ackermannize", fixed_model, tout << *abstr_model << "\n"); + + model* new_model = alloc(model, m); + + if (abstr_model) + convert(abstr_model.get(), new_model); + if (md) + convert(md.get(), new_model); md = new_model; } From 6ed2b444b5a679b1cb6dd0502dab227f1cf1468f Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sun, 3 Jul 2022 18:10:52 -0700 Subject: [PATCH 045/125] probably won't fix #6127 recfun decl plugin does not get copied so recursive functions are lost when cloning. Fix is risky and use case is limited to threads + recursive definitions --- src/smt/theory_recfun.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index c18e5577b..416275275 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -43,32 +43,32 @@ namespace smt { char const * theory_recfun::get_name() const { return "recfun"; } - theory* theory_recfun::mk_fresh(context* new_ctx) { + theory* theory_recfun::mk_fresh(context* new_ctx) { return alloc(theory_recfun, *new_ctx); } bool theory_recfun::internalize_atom(app * atom, bool gate_ctx) { + TRACE("recfun", tout << mk_pp(atom, m) << " " << u().has_defs() << "\n"); if (!u().has_defs()) { +// if (u().is_defined(atom)) +// throw default_exception("recursive atom definition is out of scope"); return false; } - for (expr * arg : *atom) { + for (expr * arg : *atom) ctx.internalize(arg, false); - } - if (!ctx.e_internalized(atom)) { + if (!ctx.e_internalized(atom)) ctx.mk_enode(atom, false, true, true); - } - if (!ctx.b_internalized(atom)) { - bool_var v = ctx.mk_bool_var(atom); - ctx.set_var_theory(v, get_id()); - } - if (!ctx.relevancy() && u().is_defined(atom)) { + if (!ctx.b_internalized(atom)) + ctx.set_var_theory(ctx.mk_bool_var(atom), get_id()); + if (!ctx.relevancy() && u().is_defined(atom)) push_case_expand(atom); - } return true; } bool theory_recfun::internalize_term(app * term) { if (!u().has_defs()) { +// if (u().is_defined(term)) +// throw default_exception("recursive term definition is out of scope"); return false; } for (expr* e : *term) { From 0353fc38ff7cab4eb2190db1f17bd89f179624fb Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 4 Jul 2022 12:42:11 -0700 Subject: [PATCH 046/125] fix #6127 again this time adding inheritance to the recfun plugin so it properly contains the recursive definitions from the source. --- RELEASE_NOTES | 15 ++++++++++++-- src/ast/recfun_decl_plugin.cpp | 37 +++++++++++++++++++++++++++++++++ src/ast/recfun_decl_plugin.h | 11 ++++++++++ src/sat/smt/dt_solver.cpp | 38 +++++++++++++++++++++------------- src/sat/smt/dt_solver.h | 2 +- 5 files changed, 86 insertions(+), 17 deletions(-) diff --git a/RELEASE_NOTES b/RELEASE_NOTES index abc6fec1d..dac0367d2 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -10,8 +10,8 @@ Version 4.9.next - native word level bit-vector solving. - introduction of simple induction lemmas to handle a limited repertoire of induction proofs. -Version 4.9 -=========== +Version 4.9.0 +============= - API for incremental parsing of assertions. A description of the feature is given by example here: https://github.com/Z3Prover/z3/commit/815518dc026e900392bf0d08ed859e5ec42d1e43 It also allows incrementality at the level of adding assertions to the @@ -19,10 +19,21 @@ Version 4.9 - Fold/map for sequences: https://microsoft.github.io/rise4fun/docs/guide/Sequences#map-and-fold At this point these functions are only exposed over the SMTLIB2 interface (and not programmatic API) + maxdiff/mindiff on arrays are more likely to be deprecated - User Propagator: - Add functions and callbacks for external control over branching - A functioning dotnet API for the User Propagator https://github.com/Z3Prover/z3/blob/master/src/api/dotnet/UserPropagator.cs +- Java Script API + - higher level object wrappers are available thanks to Gibbons and Tomalka +- Totalizers and RC2 + - The MaxSAT engine now allows to run RC2 with totalizer encoding. + Totalizers are on by default as preliminary tests suggest this solves already 10% more problems on + standard benchmarks. The option rc2.totalizer (which by default is true) is used to control whether to use + totalizer encoding or built-in cardinality constraints. + The default engine is still maxres, so you have to set opt.maxsat_engine=rc2 to + enable the rc2 option at this point. The engines maxres-bin and rc2bin are experimental should not be used + (they are inferior to default options). Version 4.8.17 ============== diff --git a/src/ast/recfun_decl_plugin.cpp b/src/ast/recfun_decl_plugin.cpp index ca2e1584d..93e10f094 100644 --- a/src/ast/recfun_decl_plugin.cpp +++ b/src/ast/recfun_decl_plugin.cpp @@ -67,6 +67,30 @@ namespace recfun { m_decl = m.mk_func_decl(s, arity, domain, range, info); } + def* def::copy(util& dst, ast_translation& tr) { + SASSERT(&dst.m() == &tr.to()); + sort_ref_vector domain(tr.to()); + sort_ref range(tr(m_range.get()), tr.to()); + for (auto* s : m_domain) + domain.push_back(tr(s)); + family_id fid = dst.get_family_id(); + bool is_generated = m_decl->get_parameter(0).get_int() != 0; + def* r = alloc(def, tr.to(), fid, m_name, domain.size(), domain.data(), range, is_generated); + r->m_rhs = tr(m_rhs.get()); + for (auto* v : m_vars) + r->m_vars.push_back(tr(v)); + for (auto const& c1 : m_cases) { + r->m_cases.push_back(case_def(tr.to())); + auto& c2 = r->m_cases.back(); + c2.m_pred = tr(c1.m_pred.get()); + c2.m_guards = tr(c1.m_guards); + c2.m_rhs = tr(c1.m_rhs.get()); + c2.m_def = r; + c2.m_immediate = c1.m_immediate; + } + return r; + } + bool def::contains_def(util& u, expr * e) { struct def_find_p : public i_expr_pred { util& u; @@ -415,6 +439,19 @@ namespace recfun { return promise_def(&u(), d); } + void plugin::inherit(decl_plugin* other, ast_translation& tr) { + for (auto [k, v] : static_cast(other)->m_defs) { + func_decl_ref f(tr(k), tr.to()); + if (m_defs.contains(f)) + continue; + def* d = v->copy(u(), tr); + m_defs.insert(f, d); + for (case_def & c : d->get_cases()) + m_case_defs.insert(c.get_decl(), &c); + + } + } + promise_def plugin::ensure_def(symbol const& name, unsigned n, sort *const * params, sort * range, bool is_generated) { def* d = u().decl_fun(name, n, params, range, is_generated); erase_def(d->get_decl()); diff --git a/src/ast/recfun_decl_plugin.h b/src/ast/recfun_decl_plugin.h index 70447ff67..bbc4e5810 100644 --- a/src/ast/recfun_decl_plugin.h +++ b/src/ast/recfun_decl_plugin.h @@ -21,6 +21,7 @@ Revision History: #include "ast/ast.h" #include "ast/ast_pp.h" +#include "ast/ast_translation.h" #include "util/obj_hashtable.h" namespace recfun { @@ -62,6 +63,12 @@ namespace recfun { def * m_def; // const& solver::get_seq_args(enode* n) { + ptr_vector const& solver::get_seq_args(enode* n, enode*& sibling) { m_nodes.reset(); m_todo.reset(); auto add_todo = [&](enode* n) { @@ -515,9 +515,15 @@ namespace dt { m_todo.push_back(n); } }; - - for (enode* sib : euf::enode_class(n)) - add_todo(sib); + + for (enode* sib : euf::enode_class(n)) { + if (m_sutil.str.is_concat_of_units(sib->get_expr())) { + add_todo(sib); + sibling = sib; + break; + } + } + for (unsigned i = 0; i < m_todo.size(); ++i) { enode* n = m_todo[i]; @@ -551,10 +557,10 @@ namespace dt { // collect equalities on all children that may have been used. bool found = false; - auto add = [&](enode* arg) { - if (arg->get_root() == child->get_root()) { - if (arg != child) - m_used_eqs.push_back(enode_pair(arg, child)); + auto add = [&](enode* seq_arg) { + if (seq_arg->get_root() == child->get_root()) { + if (seq_arg != child) + m_used_eqs.push_back(enode_pair(seq_arg, child)); found = true; } }; @@ -564,11 +570,14 @@ namespace dt { if (m_autil.is_array(s) && dt.is_datatype(get_array_range(s))) for (enode* aarg : get_array_args(arg)) add(aarg); - } - sort* se; - if (m_sutil.is_seq(child->get_sort(), se) && dt.is_datatype(se)) { - for (enode* aarg : get_seq_args(child)) - add(aarg); + sort* se; + if (m_sutil.is_seq(arg->get_sort(), se) && dt.is_datatype(se)) { + enode* sibling = nullptr; + for (enode* seq_arg : get_seq_args(arg, sibling)) + add(seq_arg); + if (sibling && sibling != arg) + m_used_eqs.push_back(enode_pair(arg, sibling)); + } } VERIFY(found); @@ -636,12 +645,13 @@ namespace dt { // explore `arg` (with parent) expr* earg = arg->get_expr(); sort* s = earg->get_sort(), *se; + enode* sibling; if (dt.is_datatype(s)) { m_parent.insert(arg->get_root(), parent); oc_push_stack(arg); } else if (m_sutil.is_seq(s, se) && dt.is_datatype(se)) { - for (enode* sarg : get_seq_args(arg)) + for (enode* sarg : get_seq_args(arg, sibling)) if (process_arg(sarg)) return true; } diff --git a/src/sat/smt/dt_solver.h b/src/sat/smt/dt_solver.h index e0a076a2d..4e2524f6b 100644 --- a/src/sat/smt/dt_solver.h +++ b/src/sat/smt/dt_solver.h @@ -112,7 +112,7 @@ namespace dt { void oc_push_stack(enode * n); ptr_vector m_nodes, m_todo; ptr_vector const& get_array_args(enode* n); - ptr_vector const& get_seq_args(enode* n); + ptr_vector const& get_seq_args(enode* n, enode*& sibling); void pop_core(unsigned n) override; From 71fc83c051b7a2163a46310f6ee74b972d7d8e6d Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 4 Jul 2022 12:42:39 -0700 Subject: [PATCH 047/125] Move out equality use out of the loop --- src/smt/theory_datatype.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_datatype.cpp b/src/smt/theory_datatype.cpp index 358fda122..d11172691 100644 --- a/src/smt/theory_datatype.cpp +++ b/src/smt/theory_datatype.cpp @@ -551,11 +551,12 @@ namespace smt { if (aarg->get_root() == child->get_root()) { if (aarg != child) m_used_eqs.push_back(enode_pair(aarg, child)); - if (sibling != arg) - m_used_eqs.push_back(enode_pair(arg, sibling)); found = true; } } + if (sibling && sibling != arg) + m_used_eqs.push_back(enode_pair(arg, sibling)); + } } VERIFY(found); From 605a3128d92ae6eb777749f3dec1f324b4aed434 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 4 Jul 2022 17:06:21 -0700 Subject: [PATCH 048/125] make release notes markdown Signed-off-by: Nikolaj Bjorner --- RELEASE_NOTES => RELEASE_NOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename RELEASE_NOTES => RELEASE_NOTES.md (99%) diff --git a/RELEASE_NOTES b/RELEASE_NOTES.md similarity index 99% rename from RELEASE_NOTES rename to RELEASE_NOTES.md index dac0367d2..4c8cf74c7 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES.md @@ -29,7 +29,7 @@ Version 4.9.0 - Totalizers and RC2 - The MaxSAT engine now allows to run RC2 with totalizer encoding. Totalizers are on by default as preliminary tests suggest this solves already 10% more problems on - standard benchmarks. The option rc2.totalizer (which by default is true) is used to control whether to use + standard benchmarks. The option opt.rc2.totalizer (which by default is true) is used to control whether to use totalizer encoding or built-in cardinality constraints. The default engine is still maxres, so you have to set opt.maxsat_engine=rc2 to enable the rc2 option at this point. The engines maxres-bin and rc2bin are experimental should not be used From 2990b6929999f545ec1f71d5325888672c04e744 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 4 Jul 2022 17:16:12 -0700 Subject: [PATCH 049/125] Update RELEASE_NOTES.md --- RELEASE_NOTES.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 4c8cf74c7..c512ba1de 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -12,6 +12,8 @@ Version 4.9.next Version 4.9.0 ============= +- Native M1 (Mac ARM64) binaries and pypi distribution. + - thanks to Isabel Garcia Contreras and Arie Gurfinkel for testing and fixes - API for incremental parsing of assertions. A description of the feature is given by example here: https://github.com/Z3Prover/z3/commit/815518dc026e900392bf0d08ed859e5ec42d1e43 It also allows incrementality at the level of adding assertions to the @@ -25,7 +27,7 @@ Version 4.9.0 - A functioning dotnet API for the User Propagator https://github.com/Z3Prover/z3/blob/master/src/api/dotnet/UserPropagator.cs - Java Script API - - higher level object wrappers are available thanks to Gibbons and Tomalka + - higher level object wrappers are available thanks to Kevin Gibbons and Olaf Tomalka - Totalizers and RC2 - The MaxSAT engine now allows to run RC2 with totalizer encoding. Totalizers are on by default as preliminary tests suggest this solves already 10% more problems on @@ -1012,7 +1014,7 @@ The following bugs are fixed in this release: - Non-termination problem associated with option LOOKAHEAD=true. It gets set for QF_UF in auto-configuration mode. - Thanks to Pierre-Christophe Bué. + Thanks to Pierre-Christophe Bué. - Incorrect axioms created for injective functions. Thanks to Sascha Boehme. @@ -1034,7 +1036,7 @@ Version 2.6 =========== This release fixes a few bugs. -Thanks to Marko Kääramees for reporting a bug in the strong context simplifier and +Thanks to Marko Kääramees for reporting a bug in the strong context simplifier and to Josh Berdine. This release also introduces some new preprocessing features: @@ -1066,7 +1068,7 @@ This release introduces the following features: Z3_update_param_value in the C API. This is particularly useful for turning the strong context simplifier on and off. -It also fixes bugs reported by Enric Rodríguez Carbonell, +It also fixes bugs reported by Enric Rodríguez Carbonell, Nuno Lopes, Josh Berdine, Ethan Jackson, Rob Quigley and Lucas Cordeiro. @@ -1133,7 +1135,7 @@ Version 2.1 =========== This is a bug fix release. -Many thanks to Robert Brummayer, Carine Pascal, François Remy, +Many thanks to Robert Brummayer, Carine Pascal, François Remy, Rajesh K Karmani, Roberto Lublinerman and numerous others for their feedback and bug reports. From 2cf0c8173b2a686f238cf43d8f8ee9dbf4b5ed53 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 4 Jul 2022 17:16:56 -0700 Subject: [PATCH 050/125] Update RELEASE_NOTES.md --- RELEASE_NOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index c512ba1de..19ac57ecb 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -23,7 +23,7 @@ Version 4.9.0 At this point these functions are only exposed over the SMTLIB2 interface (and not programmatic API) maxdiff/mindiff on arrays are more likely to be deprecated - User Propagator: - - Add functions and callbacks for external control over branching + - Add functions and callbacks for external control over branching thanks to Clemens Eisenhofer - A functioning dotnet API for the User Propagator https://github.com/Z3Prover/z3/blob/master/src/api/dotnet/UserPropagator.cs - Java Script API From ac822acb0f8bb6d5d1aabf10e1ca70fbd0bc622e Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 5 Jul 2022 08:09:49 -0700 Subject: [PATCH 051/125] add parameter incremental to ensure preprocessing does not interefere with adding constraints during search Signed-off-by: Nikolaj Bjorner --- src/opt/opt_context.cpp | 19 +++++++------------ src/opt/opt_context.h | 11 ++++++----- src/opt/opt_params.pyg | 1 + 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/opt/opt_context.cpp b/src/opt/opt_context.cpp index fa2903c35..8c4ec6ed9 100644 --- a/src/opt/opt_context.cpp +++ b/src/opt/opt_context.cpp @@ -137,9 +137,6 @@ namespace opt { m_model_fixed(), m_objective_refs(m), m_core(m), - m_enable_sat(false), - m_is_clausal(false), - m_pp_neat(false), m_unknown("unknown") { params_ref p; @@ -196,6 +193,8 @@ namespace opt { void context::add_hard_constraint(expr* f) { if (m_calling_on_model) { + if (!m_incremental) + throw default_exception("Set opt.incremental = true to allow adding constraints during search"); get_solver().assert_expr(f); for (auto const& [k, v] : m_maxsmts) v->reset_upper(); @@ -838,19 +837,14 @@ namespace opt { } goal_ref g(alloc(goal, m, true, !asms.empty())); - for (expr* fml : fmls) { + for (expr* fml : fmls) g->assert_expr(fml); - } - for (expr * a : asms) { + for (expr * a : asms) g->assert_expr(a, a); - } tactic_ref tac0 = and_then(mk_simplify_tactic(m, m_params), mk_propagate_values_tactic(m), - mk_solve_eqs_tactic(m), - // NB: cannot ackermannize because max/min objectives would disappear - // mk_ackermannize_bv_tactic(m, m_params), - // NB: mk_elim_uncstr_tactic(m) is not sound with soft constraints + m_incremental ? mk_skip_tactic() : mk_solve_eqs_tactic(m), mk_simplify_tactic(m)); opt_params optp(m_params); tactic_ref tac1, tac2, tac3, tac4; @@ -861,7 +855,7 @@ namespace opt { m.linearize(core, deps); has_dep |= !deps.empty(); } - if (optp.elim_01() && m_logic.is_null() && !has_dep) { + if (optp.elim_01() && m_logic.is_null() && !has_dep && !m_incremental) { tac1 = mk_dt2bv_tactic(m); tac2 = mk_lia2card_tactic(m); tac3 = mk_eq2bv_tactic(m); @@ -1568,6 +1562,7 @@ namespace opt { m_maxsat_engine = _p.maxsat_engine(); m_pp_neat = _p.pp_neat(); m_pp_wcnf = _p.pp_wcnf(); + m_incremental = _p.incremental(); } std::string context::to_string() { diff --git a/src/opt/opt_context.h b/src/opt/opt_context.h index c02689a38..49cc6adcd 100644 --- a/src/opt/opt_context.h +++ b/src/opt/opt_context.h @@ -194,11 +194,12 @@ namespace opt { func_decl_ref_vector m_objective_refs; expr_ref_vector m_core; tactic_ref m_simplify; - bool m_enable_sat { true } ; - bool m_enable_sls { false }; - bool m_is_clausal { false }; - bool m_pp_neat { true }; - bool m_pp_wcnf { false }; + bool m_enable_sat = true; + bool m_enable_sls = false; + bool m_is_clausal = false; + bool m_pp_neat = false; + bool m_pp_wcnf = false; + bool m_incremental = false; symbol m_maxsat_engine; symbol m_logic; svector m_labels; diff --git a/src/opt/opt_params.pyg b/src/opt/opt_params.pyg index df3ab0925..893b4bfd6 100644 --- a/src/opt/opt_params.pyg +++ b/src/opt/opt_params.pyg @@ -15,6 +15,7 @@ def_module_params('opt', ('enable_core_rotate', BOOL, False, 'enable core rotation to both sample cores and correction sets'), ('enable_sat', BOOL, True, 'enable the new SAT core for propositional constraints'), ('elim_01', BOOL, True, 'eliminate 01 variables'), + ('incremental', BOOL, False, 'set incremental mode. It disables pre-processing and enables adding constraints in model event handler'), ('pp.neat', BOOL, True, 'use neat (as opposed to less readable, but faster) pretty printer when displaying context'), ('pb.compile_equality', BOOL, False, 'compile arithmetical equalities into pseudo-Boolean equality (instead of two inequalites)'), ('pp.wcnf', BOOL, False, 'print maxsat benchmark into wcnf format'), From cd416ee9a96271f92c9f61db35ceca1385cff185 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 5 Jul 2022 08:20:46 -0700 Subject: [PATCH 052/125] add note about opt.incremental Signed-off-by: Nikolaj Bjorner --- RELEASE_NOTES.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 19ac57ecb..6cec47b5a 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -35,7 +35,12 @@ Version 4.9.0 totalizer encoding or built-in cardinality constraints. The default engine is still maxres, so you have to set opt.maxsat_engine=rc2 to enable the rc2 option at this point. The engines maxres-bin and rc2bin are experimental should not be used - (they are inferior to default options). + (they are inferior to default options). +- Incremental constraints during optimization set option opt.incremental = true + - The interface `Z3_optimize_register_model_eh` allows to monitor incremental results during optimization. + It is now possible to also add constraints to the optimization context during search. + You have to set the option opt.incremental=true to be able to add constraints. The option + disables some pre-processing functionality that removes variables from the constraints. Version 4.8.17 ============== From 2a5d23b301dae3162cb514eff6c887f93457d8c3 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 5 Jul 2022 11:49:52 -0700 Subject: [PATCH 053/125] rename URL Signed-off-by: Nikolaj Bjorner --- RELEASE_NOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 6cec47b5a..4b69522a8 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -19,7 +19,7 @@ Version 4.9.0 It also allows incrementality at the level of adding assertions to the solver object. - Fold/map for sequences: - https://microsoft.github.io/rise4fun/docs/guide/Sequences#map-and-fold + https://microsoft.github.io/z3guide/docs/guide/Sequences#map-and-fold At this point these functions are only exposed over the SMTLIB2 interface (and not programmatic API) maxdiff/mindiff on arrays are more likely to be deprecated - User Propagator: From 282c786f1c8a49a504ff44fb2f4158d5ef3f79f8 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 5 Jul 2022 11:51:12 -0700 Subject: [PATCH 054/125] setting version to release Signed-off-by: Nikolaj Bjorner --- CMakeLists.txt | 2 +- scripts/mk_project.py | 2 +- scripts/nightly.yaml | 4 ++-- scripts/release.yml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b0d111e6..28748e604 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.4) set(CMAKE_USER_MAKE_RULES_OVERRIDE_CXX "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cxx_compiler_flags_overrides.cmake") -project(Z3 VERSION 4.8.18.0 LANGUAGES CXX) +project(Z3 VERSION 4.9.0.0 LANGUAGES CXX) ################################################################################ # Project version diff --git a/scripts/mk_project.py b/scripts/mk_project.py index ccc855734..cb3f1df53 100644 --- a/scripts/mk_project.py +++ b/scripts/mk_project.py @@ -8,7 +8,7 @@ from mk_util import * def init_version(): - set_version(4, 8, 18, 0) + set_version(4, 9, 0, 0) # Z3 Project definition def init_project_def(): diff --git a/scripts/nightly.yaml b/scripts/nightly.yaml index e7ccb4731..b4582670c 100644 --- a/scripts/nightly.yaml +++ b/scripts/nightly.yaml @@ -1,8 +1,8 @@ variables: Major: '4' - Minor: '8' - Patch: '18' + Minor: '9' + Patch: '0' NightlyVersion: $(Major).$(Minor).$(Patch).$(Build.BuildId)-$(Build.DefinitionName) stages: diff --git a/scripts/release.yml b/scripts/release.yml index 20361e9ca..f8c742a7a 100644 --- a/scripts/release.yml +++ b/scripts/release.yml @@ -6,7 +6,7 @@ trigger: none variables: - ReleaseVersion: '4.8.18' + ReleaseVersion: '4.9.0' stages: From de41cfd2772193016566b0c4dfc2b8d4cf71852f Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 5 Jul 2022 12:23:24 -0700 Subject: [PATCH 055/125] fix #6104 add equality reasoning to bit-vector solver to instantiate int2bv(bv2int(x)) = x identity on demand. --- src/smt/theory_bv.cpp | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/smt/theory_bv.cpp b/src/smt/theory_bv.cpp index 90c8cd89e..2a70e884d 100644 --- a/src/smt/theory_bv.cpp +++ b/src/smt/theory_bv.cpp @@ -52,9 +52,8 @@ namespace smt { bits.reset(); m_bits_expr.reset(); - for (unsigned i = 0; i < bv_size; i++) { + for (unsigned i = 0; i < bv_size; i++) m_bits_expr.push_back(mk_bit2bool(owner, i)); - } ctx.internalize(m_bits_expr.data(), bv_size, true); for (unsigned i = 0; i < bv_size; i++) { @@ -601,9 +600,8 @@ namespace smt { TRACE("bv", tout << mk_bounded_pp(n, m) << "\n";); process_args(n); mk_enode(n); - if (!ctx.relevancy()) { + if (!ctx.relevancy()) assert_bv2int_axiom(n); - } } @@ -669,10 +667,11 @@ namespace smt { mk_enode(n); theory_var v = ctx.get_enode(n)->get_th_var(get_id()); mk_bits(v); + mk_var(ctx.get_enode(n->get_arg(0))); - if (!ctx.relevancy()) { + + if (!ctx.relevancy()) assert_int2bv_axiom(n); - } } void theory_bv::assert_int2bv_axiom(app* n) { @@ -1497,6 +1496,34 @@ namespace smt { unsigned sz = m_bits[v1].size(); bool changed = true; TRACE("bv", tout << "bits size: " << sz << "\n";); + if (sz == 0) { + enode* n1 = get_enode(r1); + enode* int2bv = nullptr; + for (enode* sib : *n1) { + if (m_util.is_bv2int(sib->get_expr())) { + int2bv = sib; + break; + } + } + if (!int2bv) + return; + + for (enode* p : enode::parents(n1->get_root())) { + if (m_util.is_int2bv(p->get_expr())) { + enode* int2bv_arg = int2bv->get_arg(0); + if (p->get_root() != int2bv_arg->get_root()) { + enode_pair_vector eqs; + eqs.push_back({n1, p->get_arg(0) }); + eqs.push_back({n1, int2bv}); + justification * js = ctx.mk_justification( + ext_theory_eq_propagation_justification(get_id(), ctx.get_region(), 0, nullptr, eqs.size(), eqs.data(), p, int2bv_arg)); + ctx.assign_eq(p, int2bv_arg, eq_justification(js)); + break; + } + } + } + + } do { // This outerloop is necessary to avoid missing propagation steps. // For example, let's assume that bits1 and bits2 contains the following From f82ca197d20868fa9b75c104d523b5b3c24ca05b Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 5 Jul 2022 12:38:07 -0700 Subject: [PATCH 056/125] #6104 also in the new core --- src/sat/smt/bv_internalize.cpp | 1 + src/sat/smt/bv_solver.cpp | 26 ++++++++++++++++++++++++++ src/sat/smt/sat_th.cpp | 4 ++++ src/sat/smt/sat_th.h | 1 + 4 files changed, 32 insertions(+) diff --git a/src/sat/smt/bv_internalize.cpp b/src/sat/smt/bv_internalize.cpp index 8496717eb..8fb6ff95d 100644 --- a/src/sat/smt/bv_internalize.cpp +++ b/src/sat/smt/bv_internalize.cpp @@ -434,6 +434,7 @@ namespace bv { SASSERT(bv.is_int2bv(n)); euf::enode* e = expr2enode(n); mk_bits(e->get_th_var(get_id())); + get_var(e->get_arg(0)); assert_int2bv_axiom(n); } diff --git a/src/sat/smt/bv_solver.cpp b/src/sat/smt/bv_solver.cpp index bc435d7eb..5ed7c1a27 100644 --- a/src/sat/smt/bv_solver.cpp +++ b/src/sat/smt/bv_solver.cpp @@ -208,7 +208,33 @@ namespace bv { if (is_bv(eq.v1())) { m_find.merge(eq.v1(), eq.v2()); VERIFY(eq.is_eq()); + return; } + euf::enode* n1 = var2enode(eq.v1()); + euf::enode* int2bv = nullptr; + for (euf::enode* sib : euf::enode_class(n1)) { + if (bv.is_bv2int(sib->get_expr())) { + int2bv = sib; + break; + } + } + if (!int2bv) + return; + + for (euf::enode* p : euf::enode_parents(n1->get_root())) { + if (bv.is_int2bv(p->get_expr())) { + euf::enode* int2bv_arg = int2bv->get_arg(0); + if (p->get_root() != int2bv_arg->get_root()) { + euf::enode_pair_vector eqs; + eqs.push_back({ n1, p->get_arg(0) }); + eqs.push_back({ n1, int2bv }); + ctx.propagate(p, int2bv_arg, euf::th_explain::propagate(*this, eqs, p, int2bv_arg)); + break; + } + } + } + + } void solver::new_diseq_eh(euf::th_eq const& ne) { diff --git a/src/sat/smt/sat_th.cpp b/src/sat/smt/sat_th.cpp index 6167d3b9b..7f4ff2f4d 100644 --- a/src/sat/smt/sat_th.cpp +++ b/src/sat/smt/sat_th.cpp @@ -270,6 +270,10 @@ namespace euf { return mk(th, lits.size(), lits.data(), eqs.size(), eqs.data(), sat::null_literal, x, y, pma); } + th_explain* th_explain::propagate(th_euf_solver& th, enode_pair_vector const& eqs, euf::enode* x, euf::enode* y, sat::proof_hint const* pma) { + return mk(th, 0, nullptr, eqs.size(), eqs.data(), sat::null_literal, x, y, pma); + } + th_explain* th_explain::propagate(th_euf_solver& th, sat::literal lit, euf::enode* x, euf::enode* y) { return mk(th, 1, &lit, 0, nullptr, sat::null_literal, x, y); } diff --git a/src/sat/smt/sat_th.h b/src/sat/smt/sat_th.h index f699c864b..4484a7717 100644 --- a/src/sat/smt/sat_th.h +++ b/src/sat/smt/sat_th.h @@ -241,6 +241,7 @@ namespace euf { static th_explain* conflict(th_euf_solver& th, sat::literal lit, euf::enode* x, euf::enode* y); static th_explain* conflict(th_euf_solver& th, euf::enode* x, euf::enode* y); static th_explain* propagate(th_euf_solver& th, sat::literal lit, euf::enode* x, euf::enode* y); + static th_explain* propagate(th_euf_solver& th, enode_pair_vector const& eqs, euf::enode* x, euf::enode* y, sat::proof_hint const* pma = nullptr); static th_explain* propagate(th_euf_solver& th, sat::literal_vector const& lits, enode_pair_vector const& eqs, sat::literal consequent, sat::proof_hint const* pma = nullptr); static th_explain* propagate(th_euf_solver& th, sat::literal_vector const& lits, enode_pair_vector const& eqs, euf::enode* x, euf::enode* y, sat::proof_hint const* pma = nullptr); From db09d381348a08a28bac83fa9bb285f2cb6bde5e Mon Sep 17 00:00:00 2001 From: Kevin Gibbons Date: Tue, 5 Jul 2022 12:39:43 -0700 Subject: [PATCH 057/125] bump emscripten version used to build wasm artifact (#6136) --- .github/workflows/wasm-release.yml | 2 +- .github/workflows/wasm.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wasm-release.yml b/.github/workflows/wasm-release.yml index 376a26684..255de7dc5 100644 --- a/.github/workflows/wasm-release.yml +++ b/.github/workflows/wasm-release.yml @@ -10,7 +10,7 @@ defaults: working-directory: src/api/js env: - EM_VERSION: 3.1.0 + EM_VERSION: 3.1.15 jobs: publish: diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 809fad42e..bd76c8033 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -10,7 +10,7 @@ defaults: working-directory: src/api/js env: - EM_VERSION: 3.1.0 + EM_VERSION: 3.1.15 jobs: check: From d7472f0726ee9d198a0008b8afd0b0e2e425678a Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 5 Jul 2022 12:48:21 -0700 Subject: [PATCH 058/125] fix #6124 expression pointers were changed within a function, but not pinned. So the pointers got stale. To enforce their life-time within the function body (for use in logging) pin the expressions. --- src/smt/theory_arith_core.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/smt/theory_arith_core.h b/src/smt/theory_arith_core.h index c7a92b18c..808984521 100644 --- a/src/smt/theory_arith_core.h +++ b/src/smt/theory_arith_core.h @@ -492,6 +492,7 @@ namespace smt { void theory_arith::mk_axiom(expr * ante, expr * conseq, bool simplify_conseq) { th_rewriter & s = ctx.get_rewriter(); expr_ref s_ante(m), s_conseq(m); + expr_ref p_ante(ante, m), p_conseq(conseq, m); // pinned versions expr* s_conseq_n, * s_ante_n; bool negated; From 6e53621146accfea773595e1db66d98c5b7b978e Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 5 Jul 2022 13:17:04 -0700 Subject: [PATCH 059/125] #6112 add q->get_qid() to comparison of quantifiers --- src/ast/ast.cpp | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/ast/ast.cpp b/src/ast/ast.cpp index 5c48f31d0..468ad8c81 100644 --- a/src/ast/ast.cpp +++ b/src/ast/ast.cpp @@ -471,26 +471,30 @@ bool compare_nodes(ast const * n1, ast const * n2) { return to_var(n1)->get_idx() == to_var(n2)->get_idx() && to_var(n1)->get_sort() == to_var(n2)->get_sort(); - case AST_QUANTIFIER: + case AST_QUANTIFIER: { + quantifier const* q1 = to_quantifier(n1); + quantifier const* q2 = to_quantifier(n2); return - to_quantifier(n1)->get_kind() == to_quantifier(n2)->get_kind() && - to_quantifier(n1)->get_num_decls() == to_quantifier(n2)->get_num_decls() && - compare_arrays(to_quantifier(n1)->get_decl_sorts(), - to_quantifier(n2)->get_decl_sorts(), - to_quantifier(n1)->get_num_decls()) && - compare_arrays(to_quantifier(n1)->get_decl_names(), - to_quantifier(n2)->get_decl_names(), - to_quantifier(n1)->get_num_decls()) && - to_quantifier(n1)->get_expr() == to_quantifier(n2)->get_expr() && - to_quantifier(n1)->get_weight() == to_quantifier(n2)->get_weight() && - to_quantifier(n1)->get_num_patterns() == to_quantifier(n2)->get_num_patterns() && - compare_arrays(to_quantifier(n1)->get_patterns(), - to_quantifier(n2)->get_patterns(), - to_quantifier(n1)->get_num_patterns()) && - to_quantifier(n1)->get_num_no_patterns() == to_quantifier(n2)->get_num_no_patterns() && - compare_arrays(to_quantifier(n1)->get_no_patterns(), - to_quantifier(n2)->get_no_patterns(), - to_quantifier(n1)->get_num_no_patterns()); + q1->get_kind() == q2->get_kind() && + q1->get_num_decls() == q2->get_num_decls() && + compare_arrays(q1->get_decl_sorts(), + q2->get_decl_sorts(), + q1->get_num_decls()) && + compare_arrays(q1->get_decl_names(), + q2->get_decl_names(), + q1->get_num_decls()) && + q1->get_expr() == q2->get_expr() && + q1->get_weight() == q2->get_weight() && + q1->get_num_patterns() == q2->get_num_patterns() && + q1->get_qid() == q2->get_qid() && + compare_arrays(q1->get_patterns(), + q2->get_patterns(), + q1->get_num_patterns()) && + q1->get_num_no_patterns() == q2->get_num_no_patterns() && + compare_arrays(q1->get_no_patterns(), + q2->get_no_patterns(), + q1->get_num_no_patterns()); + } default: UNREACHABLE(); } From a374e2c57584baa0e50d43a29886d0148911cf2d Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 5 Jul 2022 15:47:48 -0700 Subject: [PATCH 060/125] ignore qid if they are both numerical - come from the parser Signed-off-by: Nikolaj Bjorner --- src/ast/ast.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ast/ast.cpp b/src/ast/ast.cpp index 468ad8c81..473bd82b5 100644 --- a/src/ast/ast.cpp +++ b/src/ast/ast.cpp @@ -486,7 +486,8 @@ bool compare_nodes(ast const * n1, ast const * n2) { q1->get_expr() == q2->get_expr() && q1->get_weight() == q2->get_weight() && q1->get_num_patterns() == q2->get_num_patterns() && - q1->get_qid() == q2->get_qid() && + ((q1->get_qid().is_numerical() && q2->get_qid().is_numerical()) || + (q1->get_qid() == q2->get_qid())) && compare_arrays(q1->get_patterns(), q2->get_patterns(), q1->get_num_patterns()) && From f23dc894b408e2bc051ab824c7fcf6a8b8180aca Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 5 Jul 2022 16:51:05 -0700 Subject: [PATCH 061/125] add disabled pass to detect upper bound range constraints Signed-off-by: Nikolaj Bjorner --- src/tactic/bv/bv_size_reduction_tactic.cpp | 119 +++++++++++++-------- src/util/rational.h | 2 +- 2 files changed, 78 insertions(+), 43 deletions(-) diff --git a/src/tactic/bv/bv_size_reduction_tactic.cpp b/src/tactic/bv/bv_size_reduction_tactic.cpp index a9d15a2a6..22c1f0b9e 100644 --- a/src/tactic/bv/bv_size_reduction_tactic.cpp +++ b/src/tactic/bv/bv_size_reduction_tactic.cpp @@ -110,7 +110,19 @@ public: unsigned sz = g.size(); numeral val; unsigned bv_sz; - expr * f, * lhs, * rhs; + expr * f, * lhs, * rhs; + + auto match_bitmask = [&](expr* lhs, expr* rhs) { + unsigned lo, hi; + expr* arg; + if (m_util.is_numeral(rhs, val, bv_sz) && val.is_zero() && m_util.is_extract(lhs, lo, hi, arg) && lo > 0 && hi + 1 == m_util.get_bv_size(arg) && is_uninterp_const(arg) ) { + val = rational::power_of_two(lo - 1) -1 ; + update_unsigned_upper(to_app(arg), val); + return true; + } + return false; + }; + for (unsigned i = 0; i < sz; i++) { bool negated = false; f = g.form(i); @@ -152,22 +164,31 @@ public: else update_signed_lower(to_app(rhs), val); } } - -#if 0 +#if 0 else if (m_util.is_bv_ule(f, lhs, rhs)) { if (is_uninterp_const(lhs) && m_util.is_numeral(rhs, val, bv_sz)) { TRACE("bv_size_reduction", tout << (negated?"not ":"") << mk_ismt2_pp(f, m) << std::endl; ); // v <= k - if (negated) update_unsigned_lower(to_app(lhs), val+numeral(1)); - else update_unsigned_upper(to_app(lhs), val); + if (negated) + update_unsigned_lower(to_app(lhs), val+numeral(1)); + else + update_unsigned_upper(to_app(lhs), val); } else if (is_uninterp_const(rhs) && m_util.is_numeral(lhs, val, bv_sz)) { TRACE("bv_size_reduction", tout << (negated?"not ":"") << mk_ismt2_pp(f, m) << std::endl; ); // k <= v - if (negated) update_unsigned_upper(to_app(rhs), val-numeral(1)); - else update_unsigned_lower(to_app(rhs), val); + if (negated) + update_unsigned_upper(to_app(rhs), val-numeral(1)); + else + update_unsigned_lower(to_app(rhs), val); } } + else if (m.is_eq(f, lhs, rhs)) { + if (match_bitmask(lhs, rhs)) + continue; + if (match_bitmask(rhs, lhs)) + continue; + } #endif } } @@ -185,33 +206,48 @@ public: mc = nullptr; m_mc = nullptr; unsigned num_reduced = 0; + { tactic_report report("reduce-bv-size", g); collect_bounds(g); // create substitution expr_substitution subst(m); + + auto insert_def = [&](app* k, expr* new_def, app* new_const) { + if (!new_def) + return; + subst.insert(k, new_def); + if (m_produce_models) { + if (!m_mc) + m_mc = alloc(bv_size_reduction_mc, m, "bv_size_reduction"); + m_mc->add(k, new_def); + if (!m_fmc && new_const) + m_fmc = alloc(generic_model_converter, m, "bv_size_reduction"); + if (new_const) + m_fmc->hide(new_const); + } + num_reduced++; + }; + if (!(m_signed_lowers.empty() || m_signed_uppers.empty())) { TRACE("bv_size_reduction", - tout << "m_signed_lowers: " << std::endl; - for (obj_map::iterator it = m_signed_lowers.begin(); it != m_signed_lowers.end(); it++) - tout << mk_ismt2_pp(it->m_key, m) << " >= " << it->m_value.to_string() << std::endl; - tout << "m_signed_uppers: " << std::endl; - for (obj_map::iterator it = m_signed_uppers.begin(); it != m_signed_uppers.end(); it++) - tout << mk_ismt2_pp(it->m_key, m) << " <= " << it->m_value.to_string() << std::endl; - ); + tout << "m_signed_lowers: " << std::endl; + for (auto const& [k, v] : m_signed_lowers) + tout << mk_ismt2_pp(k, m) << " >= " << v.to_string() << std::endl; + tout << "m_signed_uppers: " << std::endl; + for (auto const& [k, v] : m_signed_uppers) + tout << mk_ismt2_pp(k, m) << " <= " << v.to_string() << std::endl; + ); - obj_map::iterator it = m_signed_lowers.begin(); - obj_map::iterator end = m_signed_lowers.end(); - for (; it != end; ++it) { - app * v = it->m_key; - unsigned bv_sz = m_util.get_bv_size(v); - numeral l = m_util.norm(it->m_value, bv_sz, true); - obj_map::obj_map_entry * entry = m_signed_uppers.find_core(v); + for (auto& [k, val] : m_signed_lowers) { + unsigned bv_sz = m_util.get_bv_size(k); + numeral l = m_util.norm(val, bv_sz, true); + obj_map::obj_map_entry * entry = m_signed_uppers.find_core(k); if (entry != nullptr) { numeral u = m_util.norm(entry->get_data().m_value, bv_sz, true); - TRACE("bv_size_reduction", tout << l << " <= " << v->get_decl()->get_name() << " <= " << u << "\n";); + TRACE("bv_size_reduction", tout << l << " <= " << k->get_decl()->get_name() << " <= " << u << "\n";); expr * new_def = nullptr; app * new_const = nullptr; if (l > u) { @@ -219,19 +255,19 @@ public: return; } else if (l == u) { - new_def = m_util.mk_numeral(l, v->get_sort()); + new_def = m_util.mk_numeral(l, k->get_sort()); } else { // l < u if (l.is_neg()) { unsigned l_nb = (-l).get_num_bits(); - unsigned v_nb = m_util.get_bv_size(v); + unsigned v_nb = m_util.get_bv_size(k); if (u.is_neg()) { // l <= v <= u <= 0 unsigned i_nb = l_nb; - TRACE("bv_size_reduction", tout << " l <= " << v->get_decl()->get_name() << " <= u <= 0 " << " --> " << i_nb << " bits\n";); + TRACE("bv_size_reduction", tout << " l <= " << k->get_decl()->get_name() << " <= u <= 0 " << " --> " << i_nb << " bits\n";); if (i_nb < v_nb) { new_const = m.mk_fresh_const(nullptr, m_util.mk_sort(i_nb)); new_def = m_util.mk_concat(m_util.mk_numeral(numeral(-1), v_nb - i_nb), new_const); @@ -241,7 +277,7 @@ public: // l <= v <= 0 <= u unsigned u_nb = u.get_num_bits(); unsigned i_nb = ((l_nb > u_nb) ? l_nb : u_nb) + 1; - TRACE("bv_size_reduction", tout << " l <= " << v->get_decl()->get_name() << " <= 0 <= u " << " --> " << i_nb << " bits\n";); + TRACE("bv_size_reduction", tout << " l <= " << k->get_decl()->get_name() << " <= 0 <= u " << " --> " << i_nb << " bits\n";); if (i_nb < v_nb) { new_const = m.mk_fresh_const(nullptr, m_util.mk_sort(i_nb)); new_def = m_util.mk_sign_extend(v_nb - i_nb, new_const); @@ -251,31 +287,30 @@ public: else { // 0 <= l <= v <= u unsigned u_nb = u.get_num_bits(); - unsigned v_nb = m_util.get_bv_size(v); - TRACE("bv_size_reduction", tout << l << " <= " << v->get_decl()->get_name() << " <= " << u << " --> " << u_nb << " bits\n";); + unsigned v_nb = m_util.get_bv_size(k); + TRACE("bv_size_reduction", tout << l << " <= " << k->get_decl()->get_name() << " <= " << u << " --> " << u_nb << " bits\n";); if (u_nb < v_nb) { new_const = m.mk_fresh_const(nullptr, m_util.mk_sort(u_nb)); new_def = m_util.mk_concat(m_util.mk_numeral(numeral(0), v_nb - u_nb), new_const); } } } - - if (new_def) { - subst.insert(v, new_def); - if (m_produce_models) { - if (!m_mc) - m_mc = alloc(bv_size_reduction_mc, m, "bv_size_reduction"); - m_mc->add(v, new_def); - if (!m_fmc && new_const) - m_fmc = alloc(generic_model_converter, m, "bv_size_reduction"); - if (new_const) - m_fmc->hide(new_const); - } - num_reduced++; - } + + insert_def(k, new_def, new_const); } } } + + for (auto const& [k, v] : m_unsigned_uppers) { + unsigned shift; + unsigned bv_sz = m_util.get_bv_size(k); + numeral u = m_util.norm(v, bv_sz, true) + 1; + if (u.is_power_of_two(shift) && shift < bv_sz) { + app* new_const = m.mk_fresh_const(nullptr, m_util.mk_sort(shift)); + expr* new_def = m_util.mk_concat(m_util.mk_numeral(numeral(0), bv_sz - shift), new_const); + insert_def(k, new_def, new_const); + } + } #if 0 if (!(m_unsigned_lowers.empty() && m_unsigned_uppers.empty())) { diff --git a/src/util/rational.h b/src/util/rational.h index dffcc52f3..f28a502ef 100644 --- a/src/util/rational.h +++ b/src/util/rational.h @@ -343,7 +343,7 @@ public: static rational power_of_two(unsigned k); - bool is_power_of_two(unsigned & shift) { + bool is_power_of_two(unsigned & shift) const { return m().is_power_of_two(m_val, shift); } From 85c3d874dc3eb4c6097d48d4ad0a53e34ada72f0 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 5 Jul 2022 16:57:41 -0700 Subject: [PATCH 062/125] neatify Signed-off-by: Nikolaj Bjorner --- src/tactic/bv/bv_size_reduction_tactic.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/tactic/bv/bv_size_reduction_tactic.cpp b/src/tactic/bv/bv_size_reduction_tactic.cpp index 22c1f0b9e..18083452b 100644 --- a/src/tactic/bv/bv_size_reduction_tactic.cpp +++ b/src/tactic/bv/bv_size_reduction_tactic.cpp @@ -115,12 +115,21 @@ public: auto match_bitmask = [&](expr* lhs, expr* rhs) { unsigned lo, hi; expr* arg; - if (m_util.is_numeral(rhs, val, bv_sz) && val.is_zero() && m_util.is_extract(lhs, lo, hi, arg) && lo > 0 && hi + 1 == m_util.get_bv_size(arg) && is_uninterp_const(arg) ) { - val = rational::power_of_two(lo - 1) -1 ; - update_unsigned_upper(to_app(arg), val); - return true; - } - return false; + if (!m_util.is_numeral(rhs, val, bv_sz)) + return false; + if (!val.is_zero()) + return false; + if (!m_util.is_extract(lhs, lo, hi, arg)) + return false; + if (lo == 0) + return false; + if (hi + 1 != m_util.get_bv_size(arg)) + return false; + if (!is_uninterp_const(arg)) + return false; + val = rational::power_of_two(lo - 1) -1 ; + update_unsigned_upper(to_app(arg), val); + return true; }; for (unsigned i = 0; i < sz; i++) { From 73f35e067cc9d1beab592c76f74f11b04b88bbfc Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 5 Jul 2022 17:13:55 -0700 Subject: [PATCH 063/125] Update release.yml for Azure Pipelines pre-release --- scripts/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/release.yml b/scripts/release.yml index f8c742a7a..ca46784f7 100644 --- a/scripts/release.yml +++ b/scripts/release.yml @@ -513,7 +513,7 @@ stages: releaseNotes: '$(ReleaseVersion) release' assets: '$(Agent.TempDirectory)/*.*' isDraft: true - isPreRelease: false + isPreRelease: true # Enable on release (after fixing Nuget key) - job: NuGetPublish @@ -538,7 +538,7 @@ stages: # Enable on release: - job: PyPIPublish - condition: eq(1,1) + condition: eq(0,1) displayName: "Publish to PyPI" pool: vmImage: "ubuntu-latest" From 3ce6663536732a739c5e879e9a09605122344037 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 5 Jul 2022 17:17:07 -0700 Subject: [PATCH 064/125] update release script Signed-off-by: Nikolaj Bjorner --- scripts/release.yml | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/scripts/release.yml b/scripts/release.yml index ca46784f7..a70af4cbd 100644 --- a/scripts/release.yml +++ b/scripts/release.yml @@ -130,22 +130,14 @@ stages: targetPath: $(Build.ArtifactStagingDirectory) - job: LinuxBuilds - strategy: - matrix: - manyLinux: - name: ManyLinux - image: "quay.io/pypa/manylinux2010_x86_64:latest" - python: "/opt/python/cp37-cp37m/bin/python" - muslLinux: - name: MuslLinux - image: "quay.io/pypa/musllinux_1_1_x86_64:latest" - python: "/opt/python/cp310-cp310/bin/python" - displayName: "$(name) build" + displayName: "Manylinux build" pool: vmImage: "ubuntu-latest" container: $(image) variables: - python: $(python) + name: ManyLinux + image: "quay.io/pypa/manylinux2010_x86_64:latest" + python: "/opt/python/cp37-cp37m/bin/python" steps: - task: PythonScript@0 displayName: Build From 594b5daa9dc5e6ae3b07459ccc578c105baf91c5 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 5 Jul 2022 17:18:04 -0700 Subject: [PATCH 065/125] remove download of mullinux Signed-off-by: Nikolaj Bjorner --- scripts/release.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scripts/release.yml b/scripts/release.yml index a70af4cbd..a3fe017e5 100644 --- a/scripts/release.yml +++ b/scripts/release.yml @@ -402,11 +402,6 @@ stages: inputs: artifact: 'ManyLinuxBuild' path: $(Agent.TempDirectory) - - task: DownloadPipelineArtifact@2 - displayName: 'Download MuslLinux Build' - inputs: - artifact: 'MuslLinuxBuild' - path: $(Agent.TempDirectory) - task: DownloadPipelineArtifact@2 displayName: 'Download Win32 Build' inputs: From 8b35b7becc4a2b58f920c7b05aa17d7185832a70 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 5 Jul 2022 17:20:21 -0700 Subject: [PATCH 066/125] bind variables Signed-off-by: Nikolaj Bjorner --- scripts/release.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/release.yml b/scripts/release.yml index a3fe017e5..85e1903b2 100644 --- a/scripts/release.yml +++ b/scripts/release.yml @@ -130,14 +130,14 @@ stages: targetPath: $(Build.ArtifactStagingDirectory) - job: LinuxBuilds - displayName: "Manylinux build" - pool: - vmImage: "ubuntu-latest" - container: $(image) + displayName: "ManyLinux build" variables: name: ManyLinux image: "quay.io/pypa/manylinux2010_x86_64:latest" python: "/opt/python/cp37-cp37m/bin/python" + pool: + vmImage: "ubuntu-latest" + container: $(image) steps: - task: PythonScript@0 displayName: Build @@ -162,7 +162,7 @@ stages: targetFolder: $(Build.ArtifactStagingDirectory) - task: PublishPipelineArtifact@0 inputs: - artifactName: '$(name)Build' + artifactName: 'ManyLinuxBuild' targetPath: $(Build.ArtifactStagingDirectory) - template: build-win-signed.yml From 593d5be20207a693754771076f09b9e6ffecacf4 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 5 Jul 2022 17:21:59 -0700 Subject: [PATCH 067/125] bind variables Signed-off-by: Nikolaj Bjorner --- scripts/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release.yml b/scripts/release.yml index 85e1903b2..f341c3e37 100644 --- a/scripts/release.yml +++ b/scripts/release.yml @@ -137,7 +137,7 @@ stages: python: "/opt/python/cp37-cp37m/bin/python" pool: vmImage: "ubuntu-latest" - container: $(image) + container: "quay.io/pypa/manylinux2010_x86_64:latest" steps: - task: PythonScript@0 displayName: Build From 4f62336fa873476a0268ad219c1ae2ec69a8e161 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 5 Jul 2022 18:23:32 -0700 Subject: [PATCH 068/125] download arm64 Signed-off-by: Nikolaj Bjorner --- scripts/release.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/release.yml b/scripts/release.yml index f341c3e37..6518312b8 100644 --- a/scripts/release.yml +++ b/scripts/release.yml @@ -397,6 +397,11 @@ stages: inputs: artifact: 'macOSBuild' path: $(Agent.TempDirectory) + - task: DownloadPipelineArtifact@2 + displayName: 'Download macOS Arm64 Build' + inputs: + artifact: 'MacOSBuildArm64' + path: $(Agent.TempDirectory)\package - task: DownloadPipelineArtifact@2 displayName: 'Download ManyLinux Build' inputs: From bda86726af13eccd1f1455c576f039f2d7fced2f Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 5 Jul 2022 20:02:27 -0700 Subject: [PATCH 069/125] macarm Signed-off-by: Nikolaj Bjorner --- scripts/release.yml | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/scripts/release.yml b/scripts/release.yml index 6518312b8..494442290 100644 --- a/scripts/release.yml +++ b/scripts/release.yml @@ -48,21 +48,14 @@ stages: pool: vmImage: "macOS-latest" steps: - - task: PythonScript@0 - displayName: Build + - script: python scripts/mk_unix_dist.py --dotnet-key=$(Build.SourcesDirectory)/resources/z3.snk --arch=arm64 --os=osx-11.0 + - script: git clone https://github.com/z3prover/z3test z3test + - script: cp dist/*.zip $(Build.ArtifactStagingDirectory)/. + - task: PublishPipelineArtifact@1 inputs: - scriptSource: 'filepath' - scriptPath: scripts/mk_unix_dist.py - arguments: --dotnet-key=$(Build.SourcesDirectory)/resources/z3.snk --nojava --arch=arm64 --os=osx-11.0 - - task: CopyFiles@2 - inputs: - sourceFolder: dist - contents: '*.zip' - targetFolder: $(Build.ArtifactStagingDirectory) - - task: PublishPipelineArtifact@0 - inputs: - artifactName: 'macOSBuildArm64' + artifactName: 'MacArm64' targetPath: $(Build.ArtifactStagingDirectory) + - job: UbuntuBuild @@ -400,7 +393,7 @@ stages: - task: DownloadPipelineArtifact@2 displayName: 'Download macOS Arm64 Build' inputs: - artifact: 'MacOSBuildArm64' + artifact: 'MacArm64' path: $(Agent.TempDirectory)\package - task: DownloadPipelineArtifact@2 displayName: 'Download ManyLinux Build' @@ -465,7 +458,7 @@ stages: - task: DownloadPipelineArtifact@2 displayName: 'Download macOSArm64 Build' inputs: - artifact: 'macOSBuildArm64' + artifact: 'MacArm64' path: $(Agent.TempDirectory) - task: DownloadPipelineArtifact@2 displayName: 'Download Win32 Build' From 580ed31afd6eb12fc183c8f81ab993a2321e52be Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 6 Jul 2022 01:08:54 -0700 Subject: [PATCH 070/125] fix types and incompleteness for feature #6104 --- src/sat/smt/bv_solver.cpp | 26 ++++++++------------------ src/smt/theory_bv.cpp | 28 ++++++++++------------------ 2 files changed, 18 insertions(+), 36 deletions(-) diff --git a/src/sat/smt/bv_solver.cpp b/src/sat/smt/bv_solver.cpp index 5ed7c1a27..2a39fae6a 100644 --- a/src/sat/smt/bv_solver.cpp +++ b/src/sat/smt/bv_solver.cpp @@ -211,30 +211,20 @@ namespace bv { return; } euf::enode* n1 = var2enode(eq.v1()); - euf::enode* int2bv = nullptr; - for (euf::enode* sib : euf::enode_class(n1)) { - if (bv.is_bv2int(sib->get_expr())) { - int2bv = sib; - break; - } - } - if (!int2bv) - return; - - for (euf::enode* p : euf::enode_parents(n1->get_root())) { - if (bv.is_int2bv(p->get_expr())) { - euf::enode* int2bv_arg = int2bv->get_arg(0); - if (p->get_root() != int2bv_arg->get_root()) { + for (euf::enode* bv2int : euf::enode_class(n1)) { + if (!bv.is_bv2int(bv2int->get_expr())) + continue; + euf::enode* bv2int_arg = bv2int->get_arg(0); + for (euf::enode* p : euf::enode_parents(n1->get_root())) { + if (bv.is_int2bv(p->get_expr()) && p->get_sort() == bv2int_arg->get_sort() && p->get_root() != bv2int_arg->get_root()) { euf::enode_pair_vector eqs; eqs.push_back({ n1, p->get_arg(0) }); - eqs.push_back({ n1, int2bv }); - ctx.propagate(p, int2bv_arg, euf::th_explain::propagate(*this, eqs, p, int2bv_arg)); + eqs.push_back({ n1, bv2int }); + ctx.propagate(p, bv2int_arg, euf::th_explain::propagate(*this, eqs, p, bv2int_arg)); break; } } } - - } void solver::new_diseq_eh(euf::th_eq const& ne) { diff --git a/src/smt/theory_bv.cpp b/src/smt/theory_bv.cpp index 2a70e884d..dba402a6e 100644 --- a/src/smt/theory_bv.cpp +++ b/src/smt/theory_bv.cpp @@ -1497,32 +1497,24 @@ namespace smt { bool changed = true; TRACE("bv", tout << "bits size: " << sz << "\n";); if (sz == 0) { + // int2bv(bv2int(x)) = x when int2bv(bv2int(x)) has same sort as x enode* n1 = get_enode(r1); - enode* int2bv = nullptr; - for (enode* sib : *n1) { - if (m_util.is_bv2int(sib->get_expr())) { - int2bv = sib; - break; - } - } - if (!int2bv) - return; - - for (enode* p : enode::parents(n1->get_root())) { - if (m_util.is_int2bv(p->get_expr())) { - enode* int2bv_arg = int2bv->get_arg(0); - if (p->get_root() != int2bv_arg->get_root()) { + for (enode* bv2int : *n1) { + if (!m_util.is_bv2int(bv2int->get_expr())) + continue; + enode* bv2int_arg = bv2int->get_arg(0); + for (enode* p : enode::parents(n1->get_root())) { + if (m_util.is_int2bv(p->get_expr()) && p->get_root() != bv2int_arg->get_root() && p->get_sort() == bv2int_arg->get_sort()) { enode_pair_vector eqs; eqs.push_back({n1, p->get_arg(0) }); - eqs.push_back({n1, int2bv}); + eqs.push_back({n1, bv2int}); justification * js = ctx.mk_justification( - ext_theory_eq_propagation_justification(get_id(), ctx.get_region(), 0, nullptr, eqs.size(), eqs.data(), p, int2bv_arg)); - ctx.assign_eq(p, int2bv_arg, eq_justification(js)); + ext_theory_eq_propagation_justification(get_id(), ctx.get_region(), 0, nullptr, eqs.size(), eqs.data(), p, bv2int_arg)); + ctx.assign_eq(p, bv2int_arg, eq_justification(js)); break; } } } - } do { // This outerloop is necessary to avoid missing propagation steps. From 7f2ebf84a2f779b631e744cb3332b0b306bdfbd6 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 6 Jul 2022 01:09:11 -0700 Subject: [PATCH 071/125] Remove package sub-directory from release script --- scripts/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/release.yml b/scripts/release.yml index 494442290..39df80751 100644 --- a/scripts/release.yml +++ b/scripts/release.yml @@ -44,7 +44,7 @@ stages: targetPath: $(Build.ArtifactStagingDirectory) - job: MacBuildArm64 - displayName: "macOS Build" + displayName: "macOS ARM64 Build" pool: vmImage: "macOS-latest" steps: @@ -394,7 +394,7 @@ stages: displayName: 'Download macOS Arm64 Build' inputs: artifact: 'MacArm64' - path: $(Agent.TempDirectory)\package + path: $(Agent.TempDirectory) - task: DownloadPipelineArtifact@2 displayName: 'Download ManyLinux Build' inputs: From f1b7ab3d3fe9094dc94c2bb10d32f48d27b32134 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 6 Jul 2022 01:53:26 -0700 Subject: [PATCH 072/125] x64 Signed-off-by: Nikolaj Bjorner --- scripts/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release.yml b/scripts/release.yml index 39df80751..c8d275a84 100644 --- a/scripts/release.yml +++ b/scripts/release.yml @@ -410,7 +410,7 @@ stages: inputs: artifact: 'WindowsBuild-x64' path: $(Agent.TempDirectory) - - script: cd $(Agent.TempDirectory); mkdir osx-bin; cd osx-bin; unzip ../*osx*.zip + - script: cd $(Agent.TempDirectory); mkdir osx-x64-bin; cd osx-x64-bin; unzip ../*x64-osx*.zip - script: cd $(Agent.TempDirectory); mkdir osx-arm64-bin; cd osx-arm64-bin; unzip ../*arm64-osx*.zip - script: cd $(Agent.TempDirectory); mkdir libc-bin; cd libc-bin; unzip ../*glibc*.zip - script: cd $(Agent.TempDirectory); mkdir musl-bin; cd musl-bin; unzip ../*-linux.zip From 15391fc9b9c4241af34667fb008b3402a38226e1 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 6 Jul 2022 07:37:51 -0700 Subject: [PATCH 073/125] remove musll from release.yml Signed-off-by: Nikolaj Bjorner --- scripts/release.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/release.yml b/scripts/release.yml index c8d275a84..4630ae4b0 100644 --- a/scripts/release.yml +++ b/scripts/release.yml @@ -413,16 +413,14 @@ stages: - script: cd $(Agent.TempDirectory); mkdir osx-x64-bin; cd osx-x64-bin; unzip ../*x64-osx*.zip - script: cd $(Agent.TempDirectory); mkdir osx-arm64-bin; cd osx-arm64-bin; unzip ../*arm64-osx*.zip - script: cd $(Agent.TempDirectory); mkdir libc-bin; cd libc-bin; unzip ../*glibc*.zip - - script: cd $(Agent.TempDirectory); mkdir musl-bin; cd musl-bin; unzip ../*-linux.zip - script: cd $(Agent.TempDirectory); mkdir win32-bin; cd win32-bin; unzip ../*x86-win*.zip - script: cd $(Agent.TempDirectory); mkdir win64-bin; cd win64-bin; unzip ../*x64-win*.zip - script: python3 -m pip install --user -U setuptools wheel - script: cd src/api/python; python3 setup.py sdist # take a look at this PREMIUM HACK I came up with to get around the fact that the azure variable syntax overloads the bash syntax for subshells - - script: cd src/api/python; echo $(Agent.TempDirectory)/osx-bin/* | xargs printf 'PACKAGE_FROM_RELEASE=%s\n' | xargs -I '{}' env '{}' python3 setup.py bdist_wheel + - script: cd src/api/python; echo $(Agent.TempDirectory)/osx-x64-bin/* | xargs printf 'PACKAGE_FROM_RELEASE=%s\n' | xargs -I '{}' env '{}' python3 setup.py bdist_wheel - script: cd src/api/python; echo $(Agent.TempDirectory)/osx-arm64-bin/* | xargs printf 'PACKAGE_FROM_RELEASE=%s\n' | xargs -I '{}' env '{}' python3 setup.py bdist_wheel - script: cd src/api/python; echo $(Agent.TempDirectory)/libc-bin/* | xargs printf 'PACKAGE_FROM_RELEASE=%s\n' | xargs -I '{}' env '{}' python3 setup.py bdist_wheel - - script: cd src/api/python; echo $(Agent.TempDirectory)/musl-bin/* | xargs printf 'PACKAGE_FROM_RELEASE=%s\n' | xargs -I '{}' env '{}' python3 setup.py bdist_wheel - script: cd src/api/python; echo $(Agent.TempDirectory)/win32-bin/* | xargs printf 'PACKAGE_FROM_RELEASE=%s\n' | xargs -I '{}' env '{}' python3 setup.py bdist_wheel - script: cd src/api/python; echo $(Agent.TempDirectory)/win64-bin/* | xargs printf 'PACKAGE_FROM_RELEASE=%s\n' | xargs -I '{}' env '{}' python3 setup.py bdist_wheel - task: PublishPipelineArtifact@0 From 2ae84f88dfa4698ecd6d356ca09486717ce80036 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 6 Jul 2022 09:10:16 -0700 Subject: [PATCH 074/125] Update release.yml for Azure Pipelines --- scripts/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release.yml b/scripts/release.yml index 4630ae4b0..1e4b4002e 100644 --- a/scripts/release.yml +++ b/scripts/release.yml @@ -521,7 +521,7 @@ stages: # Enable on release: - job: PyPIPublish - condition: eq(0,1) + condition: eq(1,1) displayName: "Publish to PyPI" pool: vmImage: "ubuntu-latest" From cc841caf08a67a1a6058c5ec7a8c286311f6d978 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 6 Jul 2022 10:15:34 -0700 Subject: [PATCH 075/125] increment minor version for dev branch Signed-off-by: Nikolaj Bjorner --- CMakeLists.txt | 2 +- scripts/mk_project.py | 2 +- scripts/nightly.yaml | 2 +- scripts/release.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 28748e604..3da472158 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.4) set(CMAKE_USER_MAKE_RULES_OVERRIDE_CXX "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cxx_compiler_flags_overrides.cmake") -project(Z3 VERSION 4.9.0.0 LANGUAGES CXX) +project(Z3 VERSION 4.9.1.0 LANGUAGES CXX) ################################################################################ # Project version diff --git a/scripts/mk_project.py b/scripts/mk_project.py index cb3f1df53..fb8894231 100644 --- a/scripts/mk_project.py +++ b/scripts/mk_project.py @@ -8,7 +8,7 @@ from mk_util import * def init_version(): - set_version(4, 9, 0, 0) + set_version(4, 9, 1, 0) # Z3 Project definition def init_project_def(): diff --git a/scripts/nightly.yaml b/scripts/nightly.yaml index b4582670c..69e8100c0 100644 --- a/scripts/nightly.yaml +++ b/scripts/nightly.yaml @@ -2,7 +2,7 @@ variables: Major: '4' Minor: '9' - Patch: '0' + Patch: '1' NightlyVersion: $(Major).$(Minor).$(Patch).$(Build.BuildId)-$(Build.DefinitionName) stages: diff --git a/scripts/release.yml b/scripts/release.yml index 1e4b4002e..71daee2c8 100644 --- a/scripts/release.yml +++ b/scripts/release.yml @@ -6,7 +6,7 @@ trigger: none variables: - ReleaseVersion: '4.9.0' + ReleaseVersion: '4.9.1' stages: From 0d4169533a7a0bd566d481eb8aec9bc19ab87a74 Mon Sep 17 00:00:00 2001 From: Kevin Gibbons Date: Wed, 6 Jul 2022 10:59:01 -0700 Subject: [PATCH 076/125] fix js distributable (#6139) --- src/api/js/package-lock.json | 2 +- src/api/js/package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api/js/package-lock.json b/src/api/js/package-lock.json index c860eda9b..d736468a9 100644 --- a/src/api/js/package-lock.json +++ b/src/api/js/package-lock.json @@ -27,7 +27,7 @@ "typescript": "^4.5.4" }, "engines": { - "node": ">=16 <18" + "node": ">=16" } }, "node_modules/@ampproject/remapping": { diff --git a/src/api/js/package.json b/src/api/js/package.json index 53fd25b2a..7249b85a2 100644 --- a/src/api/js/package.json +++ b/src/api/js/package.json @@ -12,13 +12,13 @@ "homepage": "https://github.com/Z3Prover/z3/tree/master/src/api/js", "repository": "github:Z3Prover/z3", "engines": { - "node": ">=16 <18" + "node": ">=16" }, "browser": "build/browser.js", "main": "build/node.js", "types": "build/node.d.ts", "files": [ - "build/*.{js,d.ts,wasm}" + "build/**/*.{js,d.ts,wasm}" ], "scripts": { "build:ts": "run-s -l build:ts:generate build:ts:tsc", From 6ed071b44407cf6623b8d3c0dceb2a8fb7040cee Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 6 Jul 2022 11:38:25 -0700 Subject: [PATCH 077/125] update release notes Signed-off-by: Nikolaj Bjorner --- RELEASE_NOTES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 4b69522a8..aecf10f2c 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -10,6 +10,10 @@ Version 4.9.next - native word level bit-vector solving. - introduction of simple induction lemmas to handle a limited repertoire of induction proofs. +Version 4.9.1 +============= +- Bugfix release to ensure npm package works + Version 4.9.0 ============= - Native M1 (Mac ARM64) binaries and pypi distribution. From 0c42d3b079483bb449faf36864a32e5a71093942 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 6 Jul 2022 11:41:48 -0700 Subject: [PATCH 078/125] small format update Signed-off-by: Nikolaj Bjorner --- src/smt/theory_seq.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/smt/theory_seq.cpp b/src/smt/theory_seq.cpp index e1285e749..e5e0c9cb0 100644 --- a/src/smt/theory_seq.cpp +++ b/src/smt/theory_seq.cpp @@ -3216,18 +3216,15 @@ void theory_seq::relevant_eh(app* n) { add_ubv_string(n); expr* arg = nullptr; - if (m_sk.is_tail(n, arg)) { + if (m_sk.is_tail(n, arg)) add_length_limit(arg, m_max_unfolding_depth, true); - } - if (m_util.str.is_length(n, arg) && !has_length(arg) && ctx.e_internalized(arg)) { + if (m_util.str.is_length(n, arg) && !has_length(arg) && ctx.e_internalized(arg)) add_length_to_eqc(arg); - } if (m_util.str.is_replace_all(n) || m_util.str.is_replace_re(n) || - m_util.str.is_replace_re_all(n) - ) { + m_util.str.is_replace_re_all(n)) { add_unhandled_expr(n); } } From 9d9414c1110bc65108202cdcc473c703d12b54f1 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 6 Jul 2022 14:00:40 -0700 Subject: [PATCH 079/125] inc version number --- CMakeLists.txt | 2 +- package-lock.json | 27 +++++++++++++++++++++++++++ scripts/mk_project.py | 2 +- scripts/nightly.yaml | 2 +- scripts/release.yml | 2 +- 5 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 package-lock.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 3da472158..a3d3bcfa0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.4) set(CMAKE_USER_MAKE_RULES_OVERRIDE_CXX "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cxx_compiler_flags_overrides.cmake") -project(Z3 VERSION 4.9.1.0 LANGUAGES CXX) +project(Z3 VERSION 4.9.2.0 LANGUAGES CXX) ################################################################################ # Project version diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..4c22d5b85 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,27 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "async-mutex": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz", + "integrity": "sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==", + "requires": { + "tslib": "^2.3.1" + } + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "z3-solver": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/z3-solver/-/z3-solver-4.9.0.tgz", + "integrity": "sha512-clSV0uyHsfrO84pSbHxoqvmd5HgSG4CoSJG2f8U65hBVylbV6p/0svctQWee9W2fWo0IsxHYRjxz2Z85GT0LAA==", + "requires": { + "async-mutex": "^0.3.2" + } + } + } +} diff --git a/scripts/mk_project.py b/scripts/mk_project.py index fb8894231..9f7a69b56 100644 --- a/scripts/mk_project.py +++ b/scripts/mk_project.py @@ -8,7 +8,7 @@ from mk_util import * def init_version(): - set_version(4, 9, 1, 0) + set_version(4, 9, 2, 0) # Z3 Project definition def init_project_def(): diff --git a/scripts/nightly.yaml b/scripts/nightly.yaml index 69e8100c0..fd93d4236 100644 --- a/scripts/nightly.yaml +++ b/scripts/nightly.yaml @@ -2,7 +2,7 @@ variables: Major: '4' Minor: '9' - Patch: '1' + Patch: '2' NightlyVersion: $(Major).$(Minor).$(Patch).$(Build.BuildId)-$(Build.DefinitionName) stages: diff --git a/scripts/release.yml b/scripts/release.yml index 71daee2c8..419d4bc4b 100644 --- a/scripts/release.yml +++ b/scripts/release.yml @@ -6,7 +6,7 @@ trigger: none variables: - ReleaseVersion: '4.9.1' + ReleaseVersion: '4.9.2' stages: From b68af0c1e590c5fdd8543a5deaa99f90e4407a77 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 11 Jul 2022 07:38:51 -0700 Subject: [PATCH 080/125] working on reconciling perf for arithmetic solvers this update integrates inferences to smt.arith.solver=6 related to grobner basis computation and handling of div/mod axioms to reconcile performance with smt.arith.solver=2. The default of smt.arth.nl.grobner_subs_fixed is changed to 1 to make comparison with solver=2 more direct. The selection of cluster equalities for solver=6 was reconciled with how it is done for solver=2. --- src/math/dd/dd_pdd.h | 6 +- src/math/dd/pdd_interval.h | 34 ++++- src/math/grobner/grobner.cpp | 32 ++--- src/math/grobner/grobner.h | 32 ++++- src/math/grobner/pdd_solver.cpp | 2 +- src/math/interval/dep_intervals.h | 1 - src/math/lp/horner.cpp | 4 +- src/math/lp/lar_solver.h | 8 +- src/math/lp/nla_common.cpp | 8 +- src/math/lp/nla_core.cpp | 202 ++++++++++++++++----------- src/math/lp/nla_core.h | 7 +- src/math/lp/nla_order_lemmas.cpp | 2 +- src/math/lp/nla_settings.h | 116 ++++----------- src/math/lp/nla_tangent_lemmas.cpp | 2 +- src/sat/smt/arith_internalize.cpp | 34 ++--- src/smt/params/smt_params_helper.pyg | 2 +- src/smt/theory_arith_core.h | 2 +- src/smt/theory_arith_nl.h | 63 ++++----- src/smt/theory_lra.cpp | 82 +++++++---- 19 files changed, 357 insertions(+), 282 deletions(-) diff --git a/src/math/dd/dd_pdd.h b/src/math/dd/dd_pdd.h index d061bb0f7..78c447494 100644 --- a/src/math/dd/dd_pdd.h +++ b/src/math/dd/dd_pdd.h @@ -398,13 +398,17 @@ namespace dd { inline pdd operator-(rational const& r, pdd const& b) { return b.rev_sub(r); } inline pdd operator-(int x, pdd const& b) { return rational(x) - b; } inline pdd operator-(pdd const& b, int x) { return b + (-rational(x)); } - + inline pdd operator-(pdd const& b, rational const& r) { return b + (-r); } + inline pdd& operator&=(pdd & p, pdd const& q) { p = p & q; return p; } inline pdd& operator^=(pdd & p, pdd const& q) { p = p ^ q; return p; } inline pdd& operator*=(pdd & p, pdd const& q) { p = p * q; return p; } inline pdd& operator|=(pdd & p, pdd const& q) { p = p | q; return p; } inline pdd& operator-=(pdd & p, pdd const& q) { p = p - q; return p; } inline pdd& operator+=(pdd & p, pdd const& q) { p = p + q; return p; } + inline pdd& operator+=(pdd & p, rational const& v) { p = p + v; return p; } + inline pdd& operator-=(pdd & p, rational const& v) { p = p - v; return p; } + inline pdd& operator*=(pdd & p, rational const& v) { p = p * v; return p; } std::ostream& operator<<(std::ostream& out, pdd const& b); diff --git a/src/math/dd/pdd_interval.h b/src/math/dd/pdd_interval.h index 55d2c9b21..d8e2b8db1 100644 --- a/src/math/dd/pdd_interval.h +++ b/src/math/dd/pdd_interval.h @@ -27,7 +27,33 @@ typedef dep_intervals::with_deps_t w_dep; class pdd_interval { dep_intervals& m_dep_intervals; std::function m_var2interval; - + + // retrieve intervals after distributing multiplication over addition. + template + void get_interval_distributed(pdd const& p, scoped_dep_interval& i, scoped_dep_interval& ret) { + bool deps = wd == w_dep::with_deps; + if (p.is_val()) { + if (deps) + m_dep_intervals.mul(p.val(), i, ret); + else + m_dep_intervals.mul(p.val(), i, ret); + return; + } + scoped_dep_interval hi(m()), lo(m()), t(m()), a(m()); + get_interval_distributed(p.lo(), i, lo); + m_var2interval(p.var(), deps, a); + if (deps) { + m_dep_intervals.mul(a, i, t); + get_interval_distributed(p.hi(), t, hi); + m_dep_intervals.add(hi, lo, ret); + } + else { + m_dep_intervals.mul(a, i, t); + get_interval_distributed(p.hi(), t, hi); + m_dep_intervals.add(hi, lo, ret); + } + } + public: pdd_interval(dep_intervals& d): m_dep_intervals(d) {} @@ -57,5 +83,11 @@ public: } } + template + void get_interval_distributed(pdd const& p, scoped_dep_interval& ret) { + scoped_dep_interval i(m()); + m_dep_intervals.set_interval_for_scalar(i, rational::one()); + get_interval_distributed(p, i, ret); + } }; } diff --git a/src/math/grobner/grobner.cpp b/src/math/grobner/grobner.cpp index 264c2a08c..39a327891 100644 --- a/src/math/grobner/grobner.cpp +++ b/src/math/grobner/grobner.cpp @@ -132,7 +132,7 @@ void grobner::display_vars(std::ostream & out, unsigned num_vars, expr * const * } } -void grobner::display_monomial(std::ostream & out, monomial const & m) const { +void grobner::display_monomial(std::ostream & out, monomial const & m, std::function& display_var) const { if (!m.m_coeff.is_one() || m.m_vars.empty()) { out << m.m_coeff; if (!m.m_vars.empty()) @@ -165,7 +165,7 @@ void grobner::display_monomial(std::ostream & out, monomial const & m) const { } } -void grobner::display_monomials(std::ostream & out, unsigned num_monomials, monomial * const * monomials) const { +void grobner::display_monomials(std::ostream & out, unsigned num_monomials, monomial * const * monomials, std::function& display_var) const { bool first = true; for (unsigned i = 0; i < num_monomials; i++) { monomial const * m = monomials[i]; @@ -173,26 +173,26 @@ void grobner::display_monomials(std::ostream & out, unsigned num_monomials, mono first = false; else out << " + "; - display_monomial(out, *m); + display_monomial(out, *m, display_var); } } -void grobner::display_equation(std::ostream & out, equation const & eq) const { - display_monomials(out, eq.m_monomials.size(), eq.m_monomials.data()); +void grobner::display_equation(std::ostream & out, equation const & eq, std::function& display_var) const { + display_monomials(out, eq.m_monomials.size(), eq.m_monomials.data(), display_var); out << " = 0\n"; } -void grobner::display_equations(std::ostream & out, equation_set const & v, char const * header) const { - if (!v.empty()) { - out << header << "\n"; - for (equation const* eq : v) - display_equation(out, *eq); - } +void grobner::display_equations(std::ostream & out, equation_set const & v, char const * header, std::function& display_var) const { + if (v.empty()) + return; + out << header << "\n"; + for (equation const* eq : v) + display_equation(out, *eq, display_var); } -void grobner::display(std::ostream & out) const { - display_equations(out, m_processed, "processed:"); - display_equations(out, m_to_process, "to process:"); +void grobner::display(std::ostream & out, std::function& display_var) const { + display_equations(out, m_processed, "processed:", display_var); + display_equations(out, m_to_process, "to process:", display_var); } void grobner::set_weight(expr * n, int weight) { @@ -528,7 +528,7 @@ bool grobner::is_subset(monomial const * m1, monomial const * m2, ptr_vectorm_vars[i2]); TRACE("grobner", - tout << "monomail: "; display_monomial(tout, *m1); tout << " is a subset of "; + tout << "monomial: "; display_monomial(tout, *m1); tout << " is a subset of "; display_monomial(tout, *m2); tout << "\n"; tout << "rest: "; display_vars(tout, rest.size(), rest.data()); tout << "\n";); return true; @@ -552,7 +552,7 @@ bool grobner::is_subset(monomial const * m1, monomial const * m2, ptr_vector& display_var) const; - void display_equations(std::ostream & out, equation_set const & v, char const * header) const; + + void display_monomials(std::ostream & out, unsigned num_monomials, monomial * const * monomials) const { + std::function _fn = [&](std::ostream& out, expr* v) { display_var(out, v); }; + display_monomials(out, num_monomials, monomials, _fn); + } + + + void display_equations(std::ostream & out, equation_set const & v, char const * header, std::function& display_var) const; void del_equations(unsigned old_size); @@ -281,11 +288,26 @@ public: void pop_scope(unsigned num_scopes); - void display_equation(std::ostream & out, equation const & eq) const; + void display_equation(std::ostream & out, equation const & eq) const { + std::function _fn = [&](std::ostream& out, expr* v) { display_var(out, v); }; + display_equation(out, eq, _fn); + } - void display_monomial(std::ostream & out, monomial const & m) const; + void display_monomial(std::ostream & out, monomial const & m) const { + std::function _fn = [&](std::ostream& out, expr* v) { display_var(out, v); }; + display_monomial(out, m, _fn); + } + + void display_equation(std::ostream & out, equation const & eq, std::function& display_var) const; - void display(std::ostream & out) const; + void display_monomial(std::ostream & out, monomial const & m, std::function& display_var) const; + + void display(std::ostream & out) const { + std::function _fn = [&](std::ostream& out, expr* v) { display_var(out, v); }; + display(out, _fn); + } + + void display(std::ostream & out, std::function& display_var) const; }; diff --git a/src/math/grobner/pdd_solver.cpp b/src/math/grobner/pdd_solver.cpp index 11c34e180..dd79b506b 100644 --- a/src/math/grobner/pdd_solver.cpp +++ b/src/math/grobner/pdd_solver.cpp @@ -11,9 +11,9 @@ --*/ +#include "util/uint_set.h" #include "math/grobner/pdd_solver.h" #include "math/grobner/pdd_simplifier.h" -#include "util/uint_set.h" #include diff --git a/src/math/interval/dep_intervals.h b/src/math/interval/dep_intervals.h index edc2da146..990816696 100644 --- a/src/math/interval/dep_intervals.h +++ b/src/math/interval/dep_intervals.h @@ -222,7 +222,6 @@ public: template void mul(const rational& r, const interval& a, interval& b) const { - if (r.is_zero()) return; m_imanager.mul(r.to_mpq(), a, b); if (wd == with_deps) { auto lower_dep = a.m_lower_dep; diff --git a/src/math/lp/horner.cpp b/src/math/lp/horner.cpp index 82fb89b4e..4d4ac4975 100644 --- a/src/math/lp/horner.cpp +++ b/src/math/lp/horner.cpp @@ -40,7 +40,7 @@ bool horner::row_has_monomial_to_refine(const T& row) const { template bool horner::row_is_interesting(const T& row) const { TRACE("nla_solver_details", c().print_row(row, tout);); - if (row.size() > c().m_nla_settings.horner_row_length_limit()) { + if (row.size() > c().m_nla_settings.horner_row_length_limit) { TRACE("nla_solver_details", tout << "disregard\n";); return false; } @@ -98,7 +98,7 @@ bool horner::lemmas_on_row(const T& row) { } bool horner::horner_lemmas() { - if (!c().m_nla_settings.run_horner()) { + if (!c().m_nla_settings.run_horner) { TRACE("nla_solver", tout << "not generating horner lemmas\n";); return false; } diff --git a/src/math/lp/lar_solver.h b/src/math/lp/lar_solver.h index 0c61bdcb2..f1cbd3370 100644 --- a/src/math/lp/lar_solver.h +++ b/src/math/lp/lar_solver.h @@ -275,9 +275,6 @@ class lar_solver : public column_namer { return m_column_buffer; } bool bound_is_integer_for_integer_column(unsigned j, const mpq & right_side) const; - inline unsigned get_base_column_in_row(unsigned row_index) const { - return m_mpq_lar_core_solver.m_r_solver.get_base_column_in_row(row_index); - } inline lar_core_solver & get_core_solver() { return m_mpq_lar_core_solver; } void catch_up_in_updating_int_solver(); var_index to_column(unsigned ext_j) const; @@ -357,6 +354,10 @@ public: } void set_value_for_nbasic_column(unsigned j, const impq& new_val); + inline unsigned get_base_column_in_row(unsigned row_index) const { + return m_mpq_lar_core_solver.m_r_solver.get_base_column_in_row(row_index); + } + // lp_assert(implied_bound_is_correctly_explained(ib, explanation)); } constraint_index mk_var_bound(var_index j, lconstraint_kind kind, const mpq & right_side); @@ -630,6 +631,7 @@ public: } void round_to_integer_solution(); inline const row_strip & get_row(unsigned i) const { return A_r().m_rows[i]; } + inline const row_strip & basic2row(unsigned i) const { return A_r().m_rows[row_of_basic_column(i)]; } inline const column_strip & get_column(unsigned i) const { return A_r().m_columns[i]; } bool row_is_correct(unsigned i) const; bool ax_is_correct() const; diff --git a/src/math/lp/nla_common.cpp b/src/math/lp/nla_common.cpp index 8f635f391..45898c613 100644 --- a/src/math/lp/nla_common.cpp +++ b/src/math/lp/nla_common.cpp @@ -71,11 +71,11 @@ void common::add_deps_of_fixed(lpvar j, u_dependency*& dep) { // creates a nex expression for the coeff and var, nex * common::nexvar(const rational & coeff, lpvar j, nex_creator& cn, u_dependency*& dep) { SASSERT(!coeff.is_zero()); - if (c().m_nla_settings.horner_subs_fixed() == 1 && c().var_is_fixed(j)) { + if (c().m_nla_settings.horner_subs_fixed == 1 && c().var_is_fixed(j)) { add_deps_of_fixed(j, dep); return cn.mk_scalar(coeff * c().m_lar_solver.column_lower_bound(j).x); } - if (c().m_nla_settings.horner_subs_fixed() == 2 && c().var_is_fixed_to_zero(j)) { + if (c().m_nla_settings.horner_subs_fixed == 2 && c().var_is_fixed_to_zero(j)) { add_deps_of_fixed(j, dep); return cn.mk_scalar(rational(0)); } @@ -89,10 +89,10 @@ nex * common::nexvar(const rational & coeff, lpvar j, nex_creator& cn, u_depende mf *= coeff; u_dependency * initial_dep = dep; for (lpvar k : m.vars()) { - if (c().m_nla_settings.horner_subs_fixed() && c().var_is_fixed(k)) { + if (c().m_nla_settings.horner_subs_fixed == 1 && c().var_is_fixed(k)) { add_deps_of_fixed(k, dep); mf *= c().m_lar_solver.column_lower_bound(k).x; - } else if (c().m_nla_settings.horner_subs_fixed() == 2 && + } else if (c().m_nla_settings.horner_subs_fixed == 2 && c().var_is_fixed_to_zero(k)) { dep = initial_dep; add_deps_of_fixed(k, dep); diff --git a/src/math/lp/nla_core.cpp b/src/math/lp/nla_core.cpp index d8d4930ce..e6eb8ad26 100644 --- a/src/math/lp/nla_core.cpp +++ b/src/math/lp/nla_core.cpp @@ -557,7 +557,7 @@ std::ostream & core::print_ineq(const ineq & in, std::ostream & out) const { } std::ostream & core::print_var(lpvar j, std::ostream & out) const { - if (m_emons.is_monic_var(j)) { + if (is_monic_var(j)) { print_monic(m_emons[j], out); } @@ -846,7 +846,7 @@ std::unordered_set core::collect_vars(const lemma& l) const { std::unordered_set vars; auto insert_j = [&](lpvar j) { vars.insert(j); - if (m_emons.is_monic_var(j)) { + if (is_monic_var(j)) { for (lpvar k : m_emons[j].vars()) vars.insert(k); } @@ -948,7 +948,7 @@ void core::maybe_add_a_factor(lpvar i, std::unordered_set& found_rm, vector & r) const { SASSERT(abs(val(i)) == abs(val(c))); - if (!m_emons.is_monic_var(i)) { + if (!is_monic_var(i)) { i = m_evars.find(i).var(); if (try_insert(i, found_vars)) { r.push_back(factor(i, factor_type::VAR)); @@ -1228,7 +1228,7 @@ bool core::var_breaks_correct_monic_as_factor(lpvar j, const monic& m) const { } bool core::var_breaks_correct_monic(lpvar j) const { - if (emons().is_monic_var(j) && !m_to_refine.contains(j)) { + if (is_monic_var(j) && !m_to_refine.contains(j)) { TRACE("nla_solver", tout << "j = " << j << ", m = "; print_monic(emons()[j], tout) << "\n";); return true; // changing the value of a correct monic } @@ -1333,7 +1333,7 @@ bool in_power(const svector& vs, unsigned l) { bool core::to_refine_is_correct() const { for (unsigned j = 0; j < m_lar_solver.number_of_vars(); j++) { - if (!emons().is_monic_var(j)) continue; + if (!is_monic_var(j)) continue; bool valid = check_monic(emons()[j]); if (valid == m_to_refine.contains(j)) { TRACE("nla_solver", tout << "inconstency in m_to_refine : "; @@ -1414,7 +1414,7 @@ void core::patch_monomials_on_to_refine() { void core::patch_monomials() { m_cautious_patching = true; patch_monomials_on_to_refine(); - if (m_to_refine.size() == 0 || !m_nla_settings.expensive_patching()) { + if (m_to_refine.size() == 0 || !m_nla_settings.expensive_patching) { return; } NOT_IMPLEMENTED_YET(); @@ -1530,11 +1530,11 @@ lbool core::check(vector& l_vec) { check_weighted(3, checks); unsigned num_calls = lp_settings().stats().m_nla_calls; - if (!conflict_found() && m_nla_settings.run_nra() && num_calls % 50 == 0 && num_calls > 500) + if (!conflict_found() && m_nla_settings.run_nra && num_calls % 50 == 0 && num_calls > 500) ret = bounded_nlsat(); } - if (l_vec.empty() && !done() && m_nla_settings.run_nra() && ret == l_undef) { + if (l_vec.empty() && !done() && m_nla_settings.run_nra && ret == l_undef) { ret = m_nra.check(); m_stats.m_nra_calls++; } @@ -1554,7 +1554,7 @@ lbool core::check(vector& l_vec) { } bool core::should_run_bounded_nlsat() { - if (!m_nla_settings.run_nra()) + if (!m_nla_settings.run_nra) return false; if (m_nlsat_delay > m_nlsat_fails) ++m_nlsat_fails; @@ -1619,8 +1619,13 @@ std::ostream& core::print_terms(std::ostream& out) const { } std::string core::var_str(lpvar j) const { - return is_monic_var(j)? - (product_indices_str(m_emons[j].vars()) + (check_monic(m_emons[j])? "": "_")) : (std::string("j") + lp::T_to_string(j)); + std::string result; + if (is_monic_var(j)) + result += product_indices_str(m_emons[j].vars()) + (check_monic(m_emons[j])? "": "_"); + else + result += std::string("j") + lp::T_to_string(j); + // result += ":w" + lp::T_to_string(get_var_weight(j)); + return result; } std::ostream& core::print_term( const lp::lar_term& t, std::ostream& out) const { @@ -1632,7 +1637,7 @@ std::ostream& core::print_term( const lp::lar_term& t, std::ostream& out) const void core::run_grobner() { - unsigned& quota = m_nla_settings.grobner_quota(); + unsigned& quota = m_nla_settings.grobner_quota; if (quota == 1) { return; } @@ -1645,13 +1650,14 @@ void core::run_grobner() { bool conflict = false; unsigned n = m_pdd_grobner.number_of_conflicts_to_report(); SASSERT(n > 0); - for (auto eq : m_pdd_grobner.equations()) { + for (auto eq : m_pdd_grobner.equations()) { if (check_pdd_eq(eq)) { conflict = true; if (--n == 0) break; } } + TRACE("grobner", m_pdd_grobner.display(tout)); if (conflict) { IF_VERBOSE(2, verbose_stream() << "grobner conflict\n"); return; @@ -1694,14 +1700,32 @@ void core::configure_grobner() { m_pdd_grobner.reset(); try { set_level2var_for_grobner(); - for (unsigned i : m_rows) { - add_row_to_grobner(m_lar_solver.A_r().m_rows[i]); + TRACE("grobner", + tout << "base vars: "; + for (lpvar j : active_var_set()) + if (m_lar_solver.is_base(j)) + tout << "j" << j << " "; + tout << "\n"); + for (lpvar j : active_var_set()) { + if (m_lar_solver.is_base(j)) + add_row_to_grobner(m_lar_solver.basic2row(j)); + + if (is_monic_var(j) && var_is_fixed(j)) { + u_dependency* dep = nullptr; + dd::pdd r = m_pdd_manager.mk_val(rational(1)); + for (lpvar k : emons()[j].vars()) + r *= pdd_expr(rational::one(), k, dep); + r -= val_of_fixed_var_with_deps(j, dep); + m_pdd_grobner.add(r, dep); + } } } catch (...) { IF_VERBOSE(2, verbose_stream() << "pdd throw\n"); return; } + TRACE("grobner", m_pdd_grobner.display(tout)); + #if 0 IF_VERBOSE(2, m_pdd_grobner.display(verbose_stream())); dd::pdd_eval eval(m_pdd_manager); @@ -1717,11 +1741,11 @@ void core::configure_grobner() { struct dd::solver::config cfg; cfg.m_max_steps = m_pdd_grobner.equations().size(); - cfg.m_max_simplified = m_nla_settings.grobner_max_simplified(); - cfg.m_eqs_growth = m_nla_settings.grobner_eqs_growth(); - cfg.m_expr_size_growth = m_nla_settings.grobner_expr_size_growth(); - cfg.m_expr_degree_growth = m_nla_settings.grobner_expr_degree_growth(); - cfg.m_number_of_conflicts_to_report = m_nla_settings.grobner_number_of_conflicts_to_report(); + cfg.m_max_simplified = m_nla_settings.grobner_max_simplified; + cfg.m_eqs_growth = m_nla_settings.grobner_eqs_growth; + cfg.m_expr_size_growth = m_nla_settings.grobner_expr_size_growth; + cfg.m_expr_degree_growth = m_nla_settings.grobner_expr_degree_growth; + cfg.m_number_of_conflicts_to_report = m_nla_settings.grobner_number_of_conflicts_to_report; m_pdd_grobner.set(cfg); m_pdd_grobner.adjust_cfg(); m_pdd_manager.set_max_num_nodes(10000); // or something proportional to the number of initial nodes. @@ -1762,49 +1786,66 @@ bool core::check_pdd_eq(const dd::solver::equation* e) { }; scoped_dep_interval i(di), i_wd(di); eval.get_interval(e->poly(), i); - if (!di.separated_from_zero(i)) + if (!di.separated_from_zero(i)) { + TRACE("grobner", m_pdd_grobner.display(tout << "not separated from 0 ", *e) << "\n"; + eval.get_interval_distributed(e->poly(), i); + tout << "separated from 0: " << di.separated_from_zero(i) << "\n"; + for (auto j : e->poly().free_vars()) { + scoped_dep_interval a(di); + m_intervals.set_var_interval(j, a); + m_intervals.display(tout << "j" << j << " ", a); tout << " "; + } + tout << "\n"); + return false; + } eval.get_interval(e->poly(), i_wd); std::function f = [this](const lp::explanation& e) { new_lemma lemma(*this, "pdd"); lemma &= e; }; if (di.check_interval_for_conflict_on_zero(i_wd, e->dep(), f)) { + TRACE("grobner", m_pdd_grobner.display(tout << "conflict ", *e) << "\n"); lp_settings().stats().m_grobner_conflicts++; return true; } else { + TRACE("grobner", m_pdd_grobner.display(tout << "no conflict ", *e) << "\n"); return false; } } void core::add_var_and_its_factors_to_q_and_collect_new_rows(lpvar j, svector & q) { - if (active_var_set_contains(j) || var_is_fixed(j)) return; - TRACE("grobner", tout << "j = " << j << ", " << pp(j);); - const auto& matrix = m_lar_solver.A_r(); + if (active_var_set_contains(j)) + return; insert_to_active_var_set(j); - for (auto & s : matrix.m_columns[j]) { - unsigned row = s.var(); - if (m_rows.contains(row)) continue; - if (matrix.m_rows[row].size() > m_nla_settings.grobner_row_length_limit()) { - TRACE("grobner", tout << "ignore the row " << row << " with the size " << matrix.m_rows[row].size() << "\n";); - continue; - } - m_rows.insert(row); - for (auto& rc : matrix.m_rows[row]) { - add_var_and_its_factors_to_q_and_collect_new_rows(rc.var(), q); - } + if (is_monic_var(j)) { + const monic& m = emons()[j]; + for (auto fcn : factorization_factory_imp(m, *this)) + for (const factor& fc: fcn) + q.push_back(var(fc)); } - if (!is_monic_var(j)) + if (var_is_fixed(j)) return; + const auto& matrix = m_lar_solver.A_r(); + for (auto & s : matrix.m_columns[j]) { + unsigned row = s.var(); + if (m_rows.contains(row)) + continue; + m_rows.insert(row); + unsigned k = m_lar_solver.get_base_column_in_row(row); + if (m_lar_solver.column_is_free(k) && k != j) + continue; + CTRACE("grobner", matrix.m_rows[row].size() > m_nla_settings.grobner_row_length_limit, + tout << "ignore the row " << row << " with the size " << matrix.m_rows[row].size() << "\n";); + if (matrix.m_rows[row].size() > m_nla_settings.grobner_row_length_limit) + continue; + for (auto& rc : matrix.m_rows[row]) + add_var_and_its_factors_to_q_and_collect_new_rows(rc.var(), q); + } + - const monic& m = emons()[j]; - for (auto fcn : factorization_factory_imp(m, *this)) { - for (const factor& fc: fcn) { - q.push_back(var(fc)); - } - } } const rational& core::val_of_fixed_var_with_deps(lpvar j, u_dependency*& dep) { @@ -1816,41 +1857,36 @@ const rational& core::val_of_fixed_var_with_deps(lpvar j, u_dependency*& dep) { } dd::pdd core::pdd_expr(const rational& c, lpvar j, u_dependency*& dep) { - if (m_nla_settings.grobner_subs_fixed() == 1 && var_is_fixed(j)) { - return m_pdd_manager.mk_val(c * val_of_fixed_var_with_deps(j, dep)); - } - - if (m_nla_settings.grobner_subs_fixed() == 2 && var_is_fixed_to_zero(j)) { - return m_pdd_manager.mk_val(val_of_fixed_var_with_deps(j, dep)); - } - - if (!is_monic_var(j)) - return c * m_pdd_manager.mk_var(j); - - u_dependency* zero_dep = dep; - // j is a monic var dd::pdd r = m_pdd_manager.mk_val(c); - const monic& m = emons()[j]; - for (lpvar k : m.vars()) { - if (m_nla_settings.grobner_subs_fixed() && var_is_fixed(k)) { - r *= m_pdd_manager.mk_val(val_of_fixed_var_with_deps(k, dep)); - } else if (m_nla_settings.grobner_subs_fixed() == 2 && var_is_fixed_to_zero(k)) { - r = m_pdd_manager.mk_val(val_of_fixed_var_with_deps(k, zero_dep)); + sbuffer vars; + vars.push_back(j); + u_dependency* zero_dep = dep; + while (!vars.empty()) { + j = vars.back(); + vars.pop_back(); + if (m_nla_settings.grobner_subs_fixed > 0 && var_is_fixed_to_zero(j)) { + r = m_pdd_manager.mk_val(val_of_fixed_var_with_deps(j, zero_dep)); dep = zero_dep; return r; - } else { - r *= m_pdd_manager.mk_var(k); } + if (m_nla_settings.grobner_subs_fixed == 1 && var_is_fixed(j)) + r *= val_of_fixed_var_with_deps(j, dep); + else if (!is_monic_var(j)) + r *= m_pdd_manager.mk_var(j); + else + for (lpvar k : emons()[j].vars()) + vars.push_back(k); } return r; } void core::add_row_to_grobner(const vector> & row) { u_dependency *dep = nullptr; + rational val; dd::pdd sum = m_pdd_manager.mk_val(rational(0)); - for (const auto &p : row) { - sum += pdd_expr(p.coeff(), p.var(), dep); - } + for (const auto &p : row) + sum += pdd_expr(p.coeff(), p.var(), dep); + TRACE("grobner", print_row(row, tout) << " " << sum << "\n"); m_pdd_grobner.add(sum, dep); } @@ -1858,17 +1894,21 @@ void core::add_row_to_grobner(const vector> & row) { void core::find_nl_cluster() { prepare_rows_and_active_vars(); svector q; - for (lpvar j : m_to_refine) { - TRACE("grobner", print_monic(emons()[j], tout) << "\n";); + TRACE("grobner", for (lpvar j : m_to_refine) print_monic(emons()[j], tout) << "\n";); + + for (lpvar j : m_to_refine) q.push_back(j); - } while (!q.empty()) { lpvar j = q.back(); q.pop_back(); add_var_and_its_factors_to_q_and_collect_new_rows(j, q); } - TRACE("grobner", display_matrix_of_m_rows(tout);); + TRACE("grobner", tout << "vars in cluster: "; + for (lpvar j : active_var_set()) tout << "j" << j << " "; tout << "\n"; + display_matrix_of_m_rows(tout); + /*emons().display(tout << "emons\n");*/ + ); } void core::prepare_rows_and_active_vars() { @@ -1902,18 +1942,16 @@ std::unordered_set core::get_vars_of_expr_with_opening_terms(const nex *e void core::display_matrix_of_m_rows(std::ostream & out) const { const auto& matrix = m_lar_solver.A_r(); - out << m_rows.size() << " rows" <<"\n"; + out << m_rows.size() << " rows" << "\n"; out << "the matrix\n"; - for (const auto & r : matrix.m_rows) { + for (const auto & r : matrix.m_rows) print_row(r, out) << std::endl; - } } void core::set_active_vars_weights(nex_creator& nc) { nc.set_number_of_vars(m_lar_solver.column_count()); - for (lpvar j : active_var_set()) { + for (lpvar j : active_var_set()) nc.set_var_weight(j, get_var_weight(j)); - } } void core::set_level2var_for_grobner() { @@ -1944,6 +1982,11 @@ void core::set_level2var_for_grobner() { l2v[j] = sorted_vars[j]; m_pdd_manager.reset(l2v); + + TRACE("grobner", + for (auto v : sorted_vars) + tout << "j" << v << " w:" << weighted_vars[v] << " "; + tout << "\n"); } unsigned core::get_var_weight(lpvar j) const { @@ -1954,14 +1997,14 @@ unsigned core::get_var_weight(lpvar j) const { k = 0; break; case lp::column_type::boxed: - k = 2; + k = 3; break; case lp::column_type::lower_bound: case lp::column_type::upper_bound: - k = 4; + k = 6; break; case lp::column_type::free_column: - k = 6; + k = 9; break; default: UNREACHABLE(); @@ -1969,9 +2012,8 @@ unsigned core::get_var_weight(lpvar j) const { } if (is_monic_var(j)) { k++; - if (m_to_refine.contains(j)) { + if (m_to_refine.contains(j)) k++; - } } return k; } diff --git a/src/math/lp/nla_core.h b/src/math/lp/nla_core.h index d0dc8d77d..e50075564 100644 --- a/src/math/lp/nla_core.h +++ b/src/math/lp/nla_core.h @@ -243,11 +243,11 @@ public: // returns true if the combination of the Horner's schema and Grobner Basis should be called bool need_run_horner() const { - return m_nla_settings.run_horner() && lp_settings().stats().m_nla_calls % m_nla_settings.horner_frequency() == 0; + return m_nla_settings.run_horner && lp_settings().stats().m_nla_calls % m_nla_settings.horner_frequency == 0; } bool need_run_grobner() const { - return m_nla_settings.run_grobner() && lp_settings().stats().m_nla_calls % m_nla_settings.grobner_frequency() == 0; + return m_nla_settings.run_grobner && lp_settings().stats().m_nla_calls % m_nla_settings.grobner_frequency == 0; } void incremental_linearization(bool); @@ -456,8 +456,7 @@ public: for (auto p : row) { v.push_back(std::make_pair(p.coeff(), p.var())); } - return lp::print_linear_combination_customized(v, [this](lpvar j) { return var_str(j); }, - out); + return lp::print_linear_combination_customized(v, [this](lpvar j) { return var_str(j); }, out); } void run_grobner(); void find_nl_cluster(); diff --git a/src/math/lp/nla_order_lemmas.cpp b/src/math/lp/nla_order_lemmas.cpp index a7910601f..94ddc4d9b 100644 --- a/src/math/lp/nla_order_lemmas.cpp +++ b/src/math/lp/nla_order_lemmas.cpp @@ -19,7 +19,7 @@ typedef lp::lar_term term; // a > b && c > 0 => ac > bc void order::order_lemma() { TRACE("nla_solver", ); - if (!c().m_nla_settings.run_order()) { + if (!c().m_nla_settings.run_order) { TRACE("nla_solver", tout << "not generating order lemmas\n";); return; } diff --git a/src/math/lp/nla_settings.h b/src/math/lp/nla_settings.h index 4045f4567..ec11ea5b2 100644 --- a/src/math/lp/nla_settings.h +++ b/src/math/lp/nla_settings.h @@ -9,94 +9,38 @@ Author: #pragma once namespace nla { -class nla_settings { - bool m_run_order; - bool m_run_tangents; - bool m_run_horner; - // how often to call the horner heuristic - unsigned m_horner_frequency; - unsigned m_horner_row_length_limit; - unsigned m_horner_subs_fixed; - // grobner fields - bool m_run_grobner; - unsigned m_grobner_row_length_limit; - unsigned m_grobner_subs_fixed; - unsigned m_grobner_eqs_growth; - unsigned m_grobner_tree_size_growth; - unsigned m_grobner_expr_size_growth; - unsigned m_grobner_expr_degree_growth; - unsigned m_grobner_max_simplified; - unsigned m_grobner_number_of_conflicts_to_report; - unsigned m_grobner_quota; - unsigned m_grobner_frequency; - bool m_run_nra; - // expensive patching - bool m_expensive_patching; -public: - nla_settings() : m_run_order(true), - m_run_tangents(true), - m_run_horner(true), - m_horner_frequency(4), - m_horner_row_length_limit(10), - m_horner_subs_fixed(2), - m_run_grobner(true), - m_grobner_row_length_limit(50), - m_grobner_subs_fixed(false), - m_grobner_quota(0), - m_grobner_frequency(4), - m_run_nra(false), - m_expensive_patching(false) - {} - unsigned grobner_eqs_growth() const { return m_grobner_eqs_growth;} - unsigned& grobner_eqs_growth() { return m_grobner_eqs_growth;} - bool run_order() const { return m_run_order; } - bool& run_order() { return m_run_order; } + struct nla_settings { + bool run_order = true; + bool run_tangents = true; + + // horner fields + bool run_horner = true; + unsigned horner_frequency = 4; + unsigned horner_row_length_limit = 10; + unsigned horner_subs_fixed = 2; - bool run_tangents() const { return m_run_tangents; } - bool& run_tangents() { return m_run_tangents; } + + // grobner fields + bool run_grobner = true; + unsigned grobner_row_length_limit = 50; + unsigned grobner_subs_fixed = 1; + unsigned grobner_eqs_growth = 10; + unsigned grobner_tree_size_growth = 2; + unsigned grobner_expr_size_growth = 2; + unsigned grobner_expr_degree_growth = 2; + unsigned grobner_max_simplified = 10000; + unsigned grobner_number_of_conflicts_to_report = 1; + unsigned grobner_quota = 0; + unsigned grobner_frequency = 4; - bool expensive_patching() const { return m_expensive_patching; } - bool& expensive_patching() { return m_expensive_patching; } - bool run_horner() const { return m_run_horner; } - bool& run_horner() { return m_run_horner; } - - unsigned horner_frequency() const { return m_horner_frequency; } - unsigned& horner_frequency() { return m_horner_frequency; } - unsigned horner_row_length_limit() const { return m_horner_row_length_limit; } - unsigned& horner_row_length_limit() { return m_horner_row_length_limit; } - unsigned horner_subs_fixed() const { return m_horner_subs_fixed; } - unsigned& horner_subs_fixed() { return m_horner_subs_fixed; } - - bool run_grobner() const { return m_run_grobner; } - bool& run_grobner() { return m_run_grobner; } - unsigned grobner_frequency() const { return m_grobner_frequency; } - unsigned& grobner_frequency() { return m_grobner_frequency; } - - bool run_nra() const { return m_run_nra; } - bool& run_nra() { return m_run_nra; } - - unsigned grobner_row_length_limit() const { return m_grobner_row_length_limit; } - unsigned& grobner_row_length_limit() { return m_grobner_row_length_limit; } - unsigned grobner_subs_fixed() const { return m_grobner_subs_fixed; } - unsigned& grobner_subs_fixed() { return m_grobner_subs_fixed; } - - unsigned grobner_tree_size_growth() const { return m_grobner_tree_size_growth; } - unsigned & grobner_tree_size_growth() { return m_grobner_tree_size_growth; } - - unsigned grobner_expr_size_growth() const { return m_grobner_expr_size_growth; } - unsigned & grobner_expr_size_growth() { return m_grobner_expr_size_growth; } - - unsigned grobner_expr_degree_growth() const { return m_grobner_expr_degree_growth; } - unsigned & grobner_expr_degree_growth() { return m_grobner_expr_degree_growth; } - - unsigned grobner_max_simplified() const { return m_grobner_max_simplified; } - unsigned & grobner_max_simplified() { return m_grobner_max_simplified; } - - unsigned grobner_number_of_conflicts_to_report() const { return m_grobner_number_of_conflicts_to_report; } - unsigned & grobner_number_of_conflicts_to_report() { return m_grobner_number_of_conflicts_to_report; } - - unsigned& grobner_quota() { return m_grobner_quota; } + // nra fields + bool run_nra = false; -}; + // expensive patching + bool expensive_patching = false; + + nla_settings() {} + + }; } diff --git a/src/math/lp/nla_tangent_lemmas.cpp b/src/math/lp/nla_tangent_lemmas.cpp index d01dc51ca..299d8031f 100644 --- a/src/math/lp/nla_tangent_lemmas.cpp +++ b/src/math/lp/nla_tangent_lemmas.cpp @@ -186,7 +186,7 @@ tangents::tangents(core * c) : common(c) {} void tangents::tangent_lemma() { factorization bf(nullptr); const monic* m = nullptr; - if (c().m_nla_settings.run_tangents() && c().find_bfc_to_refine(m, bf)) { + if (c().m_nla_settings.run_tangents && c().find_bfc_to_refine(m, bf)) { lpvar j = m->var(); tangent_imp tangent(point(val(bf[0]), val(bf[1])), c().val(j), *m, bf, *this); tangent(); diff --git a/src/sat/smt/arith_internalize.cpp b/src/sat/smt/arith_internalize.cpp index 732cbfc6f..09352e147 100644 --- a/src/sat/smt/arith_internalize.cpp +++ b/src/sat/smt/arith_internalize.cpp @@ -69,23 +69,23 @@ namespace arith { m_nla->push(); } smt_params_helper prms(s().params()); - m_nla->settings().run_order() = prms.arith_nl_order(); - m_nla->settings().run_tangents() = prms.arith_nl_tangents(); - m_nla->settings().run_horner() = prms.arith_nl_horner(); - m_nla->settings().horner_subs_fixed() = prms.arith_nl_horner_subs_fixed(); - m_nla->settings().horner_frequency() = prms.arith_nl_horner_frequency(); - m_nla->settings().horner_row_length_limit() = prms.arith_nl_horner_row_length_limit(); - m_nla->settings().run_grobner() = prms.arith_nl_grobner(); - m_nla->settings().run_nra() = prms.arith_nl_nra(); - m_nla->settings().grobner_subs_fixed() = prms.arith_nl_grobner_subs_fixed(); - m_nla->settings().grobner_eqs_growth() = prms.arith_nl_grobner_eqs_growth(); - m_nla->settings().grobner_expr_size_growth() = prms.arith_nl_grobner_expr_size_growth(); - m_nla->settings().grobner_expr_degree_growth() = prms.arith_nl_grobner_expr_degree_growth(); - m_nla->settings().grobner_max_simplified() = prms.arith_nl_grobner_max_simplified(); - m_nla->settings().grobner_number_of_conflicts_to_report() = prms.arith_nl_grobner_cnfl_to_report(); - m_nla->settings().grobner_quota() = prms.arith_nl_gr_q(); - m_nla->settings().grobner_frequency() = prms.arith_nl_grobner_frequency(); - m_nla->settings().expensive_patching() = false; + m_nla->settings().run_order = prms.arith_nl_order(); + m_nla->settings().run_tangents = prms.arith_nl_tangents(); + m_nla->settings().run_horner = prms.arith_nl_horner(); + m_nla->settings().horner_subs_fixed = prms.arith_nl_horner_subs_fixed(); + m_nla->settings().horner_frequency = prms.arith_nl_horner_frequency(); + m_nla->settings().horner_row_length_limit = prms.arith_nl_horner_row_length_limit(); + m_nla->settings().run_grobner = prms.arith_nl_grobner(); + m_nla->settings().run_nra = prms.arith_nl_nra(); + m_nla->settings().grobner_subs_fixed = prms.arith_nl_grobner_subs_fixed(); + m_nla->settings().grobner_eqs_growth = prms.arith_nl_grobner_eqs_growth(); + m_nla->settings().grobner_expr_size_growth = prms.arith_nl_grobner_expr_size_growth(); + m_nla->settings().grobner_expr_degree_growth = prms.arith_nl_grobner_expr_degree_growth(); + m_nla->settings().grobner_max_simplified = prms.arith_nl_grobner_max_simplified(); + m_nla->settings().grobner_number_of_conflicts_to_report = prms.arith_nl_grobner_cnfl_to_report(); + m_nla->settings().grobner_quota = prms.arith_nl_gr_q(); + m_nla->settings().grobner_frequency = prms.arith_nl_grobner_frequency(); + m_nla->settings().expensive_patching = false; } } diff --git a/src/smt/params/smt_params_helper.pyg b/src/smt/params/smt_params_helper.pyg index 33b3e458f..2cf29ffa8 100644 --- a/src/smt/params/smt_params_helper.pyg +++ b/src/smt/params/smt_params_helper.pyg @@ -71,7 +71,7 @@ def_module_params(module_name='smt', ('arith.nl.grobner_max_simplified', UINT, 10000, 'grobner\'s maximum number of simplifications'), ('arith.nl.grobner_cnfl_to_report', UINT, 1, 'grobner\'s maximum number of conflicts to report'), ('arith.nl.gr_q', UINT, 10, 'grobner\'s quota'), - ('arith.nl.grobner_subs_fixed', UINT, 2, '0 - no subs, 1 - substitute, 2 - substitute fixed zeros only'), + ('arith.nl.grobner_subs_fixed', UINT, 1, '0 - no subs, 1 - substitute, 2 - substitute fixed zeros only'), ('arith.nl.delay', UINT, 500, 'number of calls to final check before invoking bounded nlsat check'), ('arith.propagate_eqs', BOOL, True, 'propagate (cheap) equalities'), ('arith.propagation_mode', UINT, 1, '0 - no propagation, 1 - propagate existing literals, 2 - refine finite bounds'), diff --git a/src/smt/theory_arith_core.h b/src/smt/theory_arith_core.h index 808984521..1b2a7cc49 100644 --- a/src/smt/theory_arith_core.h +++ b/src/smt/theory_arith_core.h @@ -592,7 +592,7 @@ namespace smt { // (or (= y 0) (<= (* y (div x y)) x)) else if (!m_util.is_numeral(divisor)) { - expr_ref div_ge(m), div_le(m), ge(m), le(m); + expr_ref div_ge(m); div_ge = m_util.mk_ge(m_util.mk_sub(dividend, m_util.mk_mul(divisor, div)), zero); s(div_ge); mk_axiom(eqz, div_ge, false); diff --git a/src/smt/theory_arith_nl.h b/src/smt/theory_arith_nl.h index 436dccc6a..3aa34cbc7 100644 --- a/src/smt/theory_arith_nl.h +++ b/src/smt/theory_arith_nl.h @@ -80,14 +80,12 @@ void theory_arith::mark_dependents(theory_var v, svector & vars if (is_fixed(v)) return; column & c = m_columns[v]; - typename svector::iterator it = c.begin_entries(); - typename svector::iterator end = c.end_entries(); - for (; it != end; ++it) { - if (it->is_dead() || already_visited_rows.contains(it->m_row_id)) + for (auto& ce : c) { + if (ce.is_dead() || already_visited_rows.contains(ce.m_row_id)) continue; - TRACE("non_linear_bug", tout << "visiting row: " << it->m_row_id << "\n";); - already_visited_rows.insert(it->m_row_id); - row & r = m_rows[it->m_row_id]; + TRACE("non_linear_bug", tout << "visiting row: " << ce.m_row_id << "\n";); + already_visited_rows.insert(ce.m_row_id); + row & r = m_rows[ce.m_row_id]; theory_var s = r.get_base_var(); // ignore quasi base vars... actually they should not be used if the problem is non linear... if (is_quasi_base(s)) @@ -97,14 +95,10 @@ void theory_arith::mark_dependents(theory_var v, svector & vars // was eliminated by substitution. if (s != null_theory_var && is_free(s) && s != v) continue; - typename vector::const_iterator it2 = r.begin_entries(); - typename vector::const_iterator end2 = r.end_entries(); - for (; it2 != end2; ++it2) { - if (!it2->is_dead() && !is_fixed(it2->m_var)) - mark_var(it2->m_var, vars, already_found); - if (!it2->is_dead() && is_fixed(it2->m_var)) { - TRACE("non_linear", tout << "skipped fixed\n";); - } + for (auto& re : r) { + if (!re.is_dead() && !is_fixed(re.m_var)) + mark_var(re.m_var, vars, already_found); + CTRACE("non_linear", !re.is_dead() && is_fixed(re.m_var), tout << "skipped fixed\n"); } } } @@ -119,6 +113,7 @@ void theory_arith::get_non_linear_cluster(svector & vars) { return; var_set already_found; row_set already_visited_rows; + for (theory_var v : m_nl_monomials) { expr * n = var2expr(v); if (ctx.is_relevant(n)) @@ -130,9 +125,9 @@ void theory_arith::get_non_linear_cluster(svector & vars) { TRACE("non_linear", tout << "marking dependents of: v" << v << "\n";); mark_dependents(v, vars, already_found, already_visited_rows); } - TRACE("non_linear", tout << "variables in non linear cluster:\n"; - for (theory_var v : vars) tout << "v" << v << " "; - tout << "\n";); + TRACE("non_linear", tout << "variables in non linear cluster: "; + for (theory_var v : vars) tout << "v" << v << " "; tout << "\n"; + for (theory_var v : m_nl_monomials) tout << "non-linear v" << v << " " << mk_pp(var2expr(v), m) << "\n";); } @@ -1740,22 +1735,21 @@ grobner::monomial * theory_arith::mk_gb_monomial(rational const & _coeff, e */ template void theory_arith::add_row_to_gb(row const & r, grobner & gb) { - TRACE("non_linear", tout << "adding row to gb\n"; display_row(tout, r);); + TRACE("grobner", tout << "adding row to gb\n"; display_row(tout, r);); ptr_buffer monomials; v_dependency * dep = nullptr; m_tmp_var_set.reset(); - typename vector::const_iterator it = r.begin_entries(); - typename vector::const_iterator end = r.end_entries(); - for (; it != end; ++it) { - if (!it->is_dead()) { - rational coeff = it->m_coeff.to_rational(); - expr * m = var2expr(it->m_var); - TRACE("non_linear", tout << "monomial: " << mk_pp(m, get_manager()) << "\n";); - grobner::monomial * new_m = mk_gb_monomial(coeff, m, gb, dep, m_tmp_var_set); - TRACE("non_linear", tout << "new monomial:\n"; if (new_m) gb.display_monomial(tout, *new_m); else tout << "null"; tout << "\n";); - if (new_m) - monomials.push_back(new_m); - } + for (auto& re : r) { + if (re.is_dead()) + continue; + rational coeff = re.m_coeff.to_rational(); + expr * m = var2expr(re.m_var); + grobner::monomial * new_m = mk_gb_monomial(coeff, m, gb, dep, m_tmp_var_set); + if (new_m) + monomials.push_back(new_m); + TRACE("grobner", + tout << "monomial: " << mk_pp(m, get_manager()) << "\n"; + tout << "new monomial: "; if (new_m) gb.display_monomial(tout, *new_m); else tout << "null"; tout << "\n";); } gb.assert_eq_0(monomials.size(), monomials.data(), dep); } @@ -2158,8 +2152,9 @@ bool theory_arith::get_gb_eqs_and_look_for_conflict(ptr_vector _fn = [&](std::ostream& out, expr* v) { out << "v" << expr2var(v); }; for (grobner::equation* eq : eqs) - gb.display_equation(tout, *eq); + gb.display_equation(tout, *eq, _fn); ); for (grobner::equation* eq : eqs) { if (is_inconsistent(eq, gb) || is_inconsistent2(eq, gb)) { @@ -2259,7 +2254,9 @@ typename theory_arith::gb_result theory_arith::compute_grobner(svector ptr_vector eqs; do { - TRACE("non_linear_gb", tout << "before:\n"; gb.display(tout);); + TRACE("grobner", tout << "before grobner:\n"; + std::function _fn = [&](std::ostream& out, expr* v) { out << "v" << expr2var(v); }; + gb.display(tout, _fn)); compute_basis(gb, warn); update_statistics(gb); TRACE("non_linear_gb", tout << "after:\n"; gb.display(tout);); diff --git a/src/smt/theory_lra.cpp b/src/smt/theory_lra.cpp index 67d9988ed..794a61e76 100644 --- a/src/smt/theory_lra.cpp +++ b/src/smt/theory_lra.cpp @@ -276,23 +276,23 @@ class theory_lra::imp { m_nla->push(); } smt_params_helper prms(ctx().get_params()); - m_nla->settings().run_order() = prms.arith_nl_order(); - m_nla->settings().run_tangents() = prms.arith_nl_tangents(); - m_nla->settings().run_horner() = prms.arith_nl_horner(); - m_nla->settings().horner_subs_fixed() = prms.arith_nl_horner_subs_fixed(); - m_nla->settings().horner_frequency() = prms.arith_nl_horner_frequency(); - m_nla->settings().horner_row_length_limit() = prms.arith_nl_horner_row_length_limit(); - m_nla->settings().run_grobner() = prms.arith_nl_grobner(); - m_nla->settings().run_nra() = prms.arith_nl_nra(); - m_nla->settings().grobner_subs_fixed() = prms.arith_nl_grobner_subs_fixed(); - m_nla->settings().grobner_eqs_growth() = prms.arith_nl_grobner_eqs_growth(); - m_nla->settings().grobner_expr_size_growth() = prms.arith_nl_grobner_expr_size_growth(); - m_nla->settings().grobner_expr_degree_growth() = prms.arith_nl_grobner_expr_degree_growth(); - m_nla->settings().grobner_max_simplified() = prms.arith_nl_grobner_max_simplified(); - m_nla->settings().grobner_number_of_conflicts_to_report() = prms.arith_nl_grobner_cnfl_to_report(); - m_nla->settings().grobner_quota() = prms.arith_nl_gr_q(); - m_nla->settings().grobner_frequency() = prms.arith_nl_grobner_frequency(); - m_nla->settings().expensive_patching() = false; + m_nla->settings().run_order = prms.arith_nl_order(); + m_nla->settings().run_tangents = prms.arith_nl_tangents(); + m_nla->settings().run_horner = prms.arith_nl_horner(); + m_nla->settings().horner_subs_fixed = prms.arith_nl_horner_subs_fixed(); + m_nla->settings().horner_frequency = prms.arith_nl_horner_frequency(); + m_nla->settings().horner_row_length_limit = prms.arith_nl_horner_row_length_limit(); + m_nla->settings().run_grobner = prms.arith_nl_grobner(); + m_nla->settings().run_nra = prms.arith_nl_nra(); + m_nla->settings().grobner_subs_fixed = prms.arith_nl_grobner_subs_fixed(); + m_nla->settings().grobner_eqs_growth = prms.arith_nl_grobner_eqs_growth(); + m_nla->settings().grobner_expr_size_growth = prms.arith_nl_grobner_expr_size_growth(); + m_nla->settings().grobner_expr_degree_growth = prms.arith_nl_grobner_expr_degree_growth(); + m_nla->settings().grobner_max_simplified = prms.arith_nl_grobner_max_simplified(); + m_nla->settings().grobner_number_of_conflicts_to_report = prms.arith_nl_grobner_cnfl_to_report(); + m_nla->settings().grobner_quota = prms.arith_nl_gr_q(); + m_nla->settings().grobner_frequency = prms.arith_nl_grobner_frequency(); + m_nla->settings().expensive_patching = false; } } @@ -1224,9 +1224,9 @@ public: return; } expr_ref mod_r(a.mk_add(a.mk_mul(q, div), mod), m); - + ctx().get_rewriter()(mod_r); expr_ref eq_r(th.mk_eq_atom(mod_r, p), m); - ctx().internalize(eq_r, false); + ctx().internalize(eq_r, false); literal eq = ctx().get_literal(eq_r); rational k(0); @@ -1256,6 +1256,38 @@ public: } else { + expr_ref abs_q(m.mk_ite(a.mk_ge(q, zero), q, a.mk_uminus(q)), m); + expr_ref mone(a.mk_int(-1), m); + expr_ref modmq(a.mk_sub(mod, abs_q), m); + ctx().get_rewriter()(modmq); + literal eqz = mk_literal(m.mk_eq(q, zero)); + literal mod_ge_0 = mk_literal(a.mk_ge(mod, zero)); + literal mod_lt_q = mk_literal(a.mk_le(modmq, mone)); + + // q = 0 or p = (p mod q) + q * (p div q) + // q = 0 or (p mod q) >= 0 + // q = 0 or (p mod q) < abs(q) + + mk_axiom(eqz, eq); + mk_axiom(eqz, mod_ge_0); + mk_axiom(eqz, mod_lt_q); + + if (a.is_zero(p)) { + mk_axiom(eqz, mk_literal(m.mk_eq(div, zero))); + mk_axiom(eqz, mk_literal(m.mk_eq(mod, zero))); + } + // (or (= y 0) (<= (* y (div x y)) x)) + else if (!a.is_numeral(q)) { + expr_ref div_ge(m); + div_ge = a.mk_ge(a.mk_sub(p, a.mk_mul(q, div)), zero); + ctx().get_rewriter()(div_ge); + mk_axiom(eqz, mk_literal(div_ge)); + TRACE("arith", tout << eqz << " " << div_ge << "\n"); + } + + +#if 0 + /*literal div_ge_0 = */ mk_literal(a.mk_ge(div, zero)); /*literal div_le_0 = */ mk_literal(a.mk_le(div, zero)); /*literal p_ge_0 = */ mk_literal(a.mk_ge(p, zero)); @@ -1266,7 +1298,7 @@ public: // q >= 0 or (p mod q) >= 0 // q <= 0 or (p mod q) >= 0 // q <= 0 or (p mod q) < q - // q >= 0 or (p mod q) < -q + // q >= 0 or (p mod q) < -q literal q_ge_0 = mk_literal(a.mk_ge(q, zero)); literal q_le_0 = mk_literal(a.mk_le(q, zero)); literal mod_ge_0 = mk_literal(a.mk_ge(mod, zero)); @@ -1277,11 +1309,11 @@ public: mk_axiom(q_le_0, mod_ge_0); mk_axiom(q_le_0, ~mk_literal(a.mk_ge(a.mk_sub(mod, q), zero))); mk_axiom(q_ge_0, ~mk_literal(a.mk_ge(a.mk_add(mod, q), zero))); - +#endif #if 0 // seem expensive - + mk_axiom(q_le_0, ~p_ge_0, div_ge_0); mk_axiom(q_le_0, ~p_le_0, div_le_0); mk_axiom(q_ge_0, ~p_ge_0, div_le_0); @@ -1293,19 +1325,21 @@ public: mk_axiom(q_ge_0, p_le_0, ~div_ge_0); #endif +#if 0 std::function log = [&,this]() { th.log_axiom_unit(m.mk_implies(m.mk_not(m.mk_eq(q, zero)), c.bool_var2expr(eq.var()))); th.log_axiom_unit(m.mk_implies(m.mk_not(m.mk_eq(q, zero)), c.bool_var2expr(mod_ge_0.var()))); th.log_axiom_unit(m.mk_implies(a.mk_lt(q, zero), a.mk_lt(a.mk_sub(mod, q), zero))); th.log_axiom_unit(m.mk_implies(a.mk_lt(q, zero), a.mk_lt(a.mk_add(mod, q), zero))); + }; + if_trace_stream _ts(m, log); +#endif #if 0 th.log_axiom_unit(m.mk_implies(m.mk_and(a.mk_gt(q, zero), c.bool_var2expr(p_ge_0.var())), c.bool_var2expr(div_ge_0.var()))); th.log_axiom_unit(m.mk_implies(m.mk_and(a.mk_gt(q, zero), c.bool_var2expr(p_le_0.var())), c.bool_var2expr(div_le_0.var()))); th.log_axiom_unit(m.mk_implies(m.mk_and(a.mk_lt(q, zero), c.bool_var2expr(p_ge_0.var())), c.bool_var2expr(div_le_0.var()))); th.log_axiom_unit(m.mk_implies(m.mk_and(a.mk_lt(q, zero), c.bool_var2expr(p_le_0.var())), c.bool_var2expr(div_ge_0.var()))); #endif - }; - if_trace_stream _ts(m, log); } if (params().m_arith_enum_const_mod && k.is_pos() && k < rational(8)) { unsigned _k = k.get_unsigned(); From 9dd529bb126951c582aa8c832ea86d6b82754a9e Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 11 Jul 2022 08:17:38 -0700 Subject: [PATCH 081/125] missing initialization of List for cmd interpreter --- src/api/api_parsers.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/api/api_parsers.cpp b/src/api/api_parsers.cpp index f00d0a44a..6355642fc 100644 --- a/src/api/api_parsers.cpp +++ b/src/api/api_parsers.cpp @@ -231,11 +231,13 @@ extern "C" { Z3_TRY; LOG_Z3_eval_smtlib2_string(c, str); if (!mk_c(c)->cmd()) { - mk_c(c)->cmd() = alloc(cmd_context, false, &(mk_c(c)->m())); - install_dl_cmds(*mk_c(c)->cmd()); - install_opt_cmds(*mk_c(c)->cmd()); - install_smt2_extra_cmds(*mk_c(c)->cmd()); - mk_c(c)->cmd()->set_solver_factory(mk_smt_strategic_solver_factory()); + auto* ctx = alloc(cmd_context, false, &(mk_c(c)->m())); + mk_c(c)->cmd() = ctx; + install_dl_cmds(*ctx); + install_opt_cmds(*ctx); + install_smt2_extra_cmds(*ctx); + ctx->register_plist(); + ctx->set_solver_factory(mk_smt_strategic_solver_factory()); } scoped_ptr& ctx = mk_c(c)->cmd(); std::string s(str); From 1f2346073a79eb144199fe4c4c389dac16f5cba0 Mon Sep 17 00:00:00 2001 From: Clemens Eisenhofer <56730610+CEisenhofer@users.noreply.github.com> Date: Mon, 11 Jul 2022 18:24:03 +0200 Subject: [PATCH 082/125] Fixed missing assignment for binary clauses (#6148) * Added function to select the next variable to split on * Fixed typo * Small fixes * uint -> int * Fixed missing assignment for binary clauses --- src/smt/smt_internalizer.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/smt/smt_internalizer.cpp b/src/smt/smt_internalizer.cpp index 451136351..092bc0127 100644 --- a/src/smt/smt_internalizer.cpp +++ b/src/smt/smt_internalizer.cpp @@ -1429,7 +1429,10 @@ namespace smt { inc_ref(l2); m_watches[(~l1).index()].insert_literal(l2); m_watches[(~l2).index()].insert_literal(l1); - if (get_assignment(l2) == l_false) { + if (get_assignment(l1) == l_false) { + assign(l2, b_justification(~l1)); + } + else if (get_assignment(l2) == l_false) { assign(l1, b_justification(~l2)); } m_clause_proof.add(l1, l2, k, j); From 99212a2726d2d1de8ba5d404d89bd9643983fc0c Mon Sep 17 00:00:00 2001 From: Stefan Muenzel Date: Mon, 11 Jul 2022 23:25:05 +0700 Subject: [PATCH 083/125] Use int64 for ocaml api functions that require it (#6150) * Use int64 for ocaml api functions that require it Signed-off-by: Stefan Muenzel * Use elif Signed-off-by: Stefan Muenzel --- scripts/update_api.py | 12 +++++++----- src/api/ml/z3.mli | 12 ++++++------ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/scripts/update_api.py b/scripts/update_api.py index 43903e92c..8c6275c56 100755 --- a/scripts/update_api.py +++ b/scripts/update_api.py @@ -91,7 +91,7 @@ Type2Dotnet = { VOID : 'void', VOID_PTR : 'IntPtr', INT : 'int', UINT : 'uint', # Mapping to ML types -Type2ML = { VOID : 'unit', VOID_PTR : 'ptr', INT : 'int', UINT : 'int', INT64 : 'int', UINT64 : 'int', DOUBLE : 'float', +Type2ML = { VOID : 'unit', VOID_PTR : 'ptr', INT : 'int', UINT : 'int', INT64 : 'int64', UINT64 : 'int64', DOUBLE : 'float', FLOAT : 'float', STRING : 'string', STRING_PTR : 'char**', BOOL : 'bool', SYMBOL : 'z3_symbol', PRINT_MODE : 'int', ERROR_CODE : 'int', CHAR : 'char', CHAR_PTR : 'string', LBOOL : 'int' } @@ -254,8 +254,10 @@ def param2pystr(p): def param2ml(p): k = param_kind(p) if k == OUT: - if param_type(p) == INT or param_type(p) == UINT or param_type(p) == BOOL or param_type(p) == INT64 or param_type(p) == UINT64: + if param_type(p) == INT or param_type(p) == UINT or param_type(p) == BOOL: return "int" + elif param_type(p) == INT64 or param_type(p) == UINT64: + return "int64" elif param_type(p) == STRING: return "string" else: @@ -1252,9 +1254,9 @@ def ml_unwrap(t, ts, s): elif t == UINT: return '(' + ts + ') Unsigned_int_val(' + s + ')' elif t == INT64: - return '(' + ts + ') Long_val(' + s + ')' + return '(' + ts + ') Int64_val(' + s + ')' elif t == UINT64: - return '(' + ts + ') Unsigned_long_val(' + s + ')' + return '(' + ts + ') Int64_val(' + s + ')' elif t == DOUBLE: return '(' + ts + ') Double_val(' + s + ')' elif ml_has_plus_type(ts): @@ -1271,7 +1273,7 @@ def ml_set_wrap(t, d, n): elif t == INT or t == UINT or t == PRINT_MODE or t == ERROR_CODE or t == LBOOL: return d + ' = Val_int(' + n + ');' elif t == INT64 or t == UINT64: - return d + ' = Val_long(' + n + ');' + return d + ' = caml_copy_int64(' + n + ');' elif t == DOUBLE: return d + '= caml_copy_double(' + n + ');' elif t == STRING: diff --git a/src/api/ml/z3.mli b/src/api/ml/z3.mli index 74320dd72..a7c629018 100644 --- a/src/api/ml/z3.mli +++ b/src/api/ml/z3.mli @@ -927,10 +927,10 @@ end module FiniteDomain : sig (** Create a new finite domain sort. *) - val mk_sort : context -> Symbol.symbol -> int -> Sort.sort + val mk_sort : context -> Symbol.symbol -> int64 -> Sort.sort (** Create a new finite domain sort. *) - val mk_sort_s : context -> string -> int -> Sort.sort + val mk_sort_s : context -> string -> int64 -> Sort.sort (** Indicates whether the term is of an array sort. *) val is_finite_domain : Expr.expr -> bool @@ -939,7 +939,7 @@ sig val is_lt : Expr.expr -> bool (** The size of the finite domain sort. *) - val get_size : Sort.sort -> int + val get_size : Sort.sort -> int64 end @@ -2078,7 +2078,7 @@ sig val mk_numeral_i : context -> int -> Sort.sort -> Expr.expr (** Create a numeral of FloatingPoint sort from a sign bit and two integers. *) - val mk_numeral_i_u : context -> bool -> int -> int -> Sort.sort -> Expr.expr + val mk_numeral_i_u : context -> bool -> int64 -> int64 -> Sort.sort -> Expr.expr (** Create a numeral of FloatingPoint sort from a string *) val mk_numeral_s : context -> string -> Sort.sort -> Expr.expr @@ -2303,7 +2303,7 @@ sig val get_numeral_exponent_string : context -> Expr.expr -> bool -> string (** Return the exponent value of a floating-point numeral as a signed integer *) - val get_numeral_exponent_int : context -> Expr.expr -> bool -> bool * int + val get_numeral_exponent_int : context -> Expr.expr -> bool -> bool * int64 (** Return the exponent of a floating-point numeral as a bit-vector expression. Remark: NaN's do not have a bit-vector exponent, so they are invalid arguments. *) @@ -2320,7 +2320,7 @@ sig Remark: This function extracts the significand bits, without the hidden bit or normalization. Throws an exception if the significand does not fit into an int. *) - val get_numeral_significand_uint : context -> Expr.expr -> bool * int + val get_numeral_significand_uint : context -> Expr.expr -> bool * int64 (** Indicates whether a floating-point numeral is a NaN. *) val is_numeral_nan : context -> Expr.expr -> bool From 7ae1a338a7917c3dd944aa09e222b818c895c281 Mon Sep 17 00:00:00 2001 From: Anthony Romano Date: Mon, 11 Jul 2022 09:26:11 -0700 Subject: [PATCH 084/125] parallel-tactic: fix deadlocking race between shutdown and get_task (#6152) Deadlock/Race is as follows: 1. get_task() reads m_shutdown == false and enters loop body 2. shutdown() is called; sets m_shutdown = true 3. shutdown() calls m_cond.notify_all() 4. get_task() finds no task in try_get_task() 5. get_task() calls m_cond.wait(), missing the notification 6. solve() waits forever on join() Provided patch wraps (2) and (3) with the condition variable lock so that step (5) cannot miss the notification. Co-authored-by: Anthony Romano --- src/solver/parallel_tactic.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/solver/parallel_tactic.cpp b/src/solver/parallel_tactic.cpp index c79f52084..1d24ed1e6 100644 --- a/src/solver/parallel_tactic.cpp +++ b/src/solver/parallel_tactic.cpp @@ -113,9 +113,9 @@ class parallel_tactic : public tactic { void shutdown() { if (!m_shutdown) { + std::lock_guard lock(m_mutex); m_shutdown = true; m_cond.notify_all(); - std::lock_guard lock(m_mutex); for (solver_state* st : m_active) { st->m().limit().cancel(); } @@ -147,7 +147,9 @@ class parallel_tactic : public tactic { } { std::unique_lock lock(m_mutex); - m_cond.wait(lock); + if (!m_shutdown) { + m_cond.wait(lock); + } } dec_wait(); } From 8b29f401523e350caa75e3993942a654340260d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Pal=C3=A9ologue?= Date: Mon, 11 Jul 2022 18:46:23 +0200 Subject: [PATCH 085/125] Fix build on Mac (#6146) * Fix finding Python on Mac On Mac you have to specify the version. It also works well on other platforms this way. * Ignore CMake build directories from index * Fix warning about unused variable in release The variable is used in debug only, but it's legit that the compiler does not warn us for that in release. --- .gitignore | 1 + CMakeLists.txt | 10 +++++----- src/util/parray.h | 26 +++++++++++++------------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 29c49a130..3fe3a3110 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,7 @@ bld_rel/* bld_dbg_x64/* bld_rel_x64/* .vscode +*build*/** # Auto generated files. config.log config.status diff --git a/CMakeLists.txt b/CMakeLists.txt index a3d3bcfa0..cffe1a4d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -160,7 +160,7 @@ list(APPEND Z3_COMPONENT_CXX_DEFINES $<$:_EXTERNAL_RELEAS ################################################################################ # Find Python ################################################################################ -find_package(PythonInterp REQUIRED) +find_package(PythonInterp 3 REQUIRED) message(STATUS "PYTHON_EXECUTABLE: ${PYTHON_EXECUTABLE}") ################################################################################ @@ -230,7 +230,7 @@ else() message(FATAL_ERROR "Platform \"${CMAKE_SYSTEM_NAME}\" not recognised") endif() -list(APPEND Z3_COMPONENT_EXTRA_INCLUDE_DIRS +list(APPEND Z3_COMPONENT_EXTRA_INCLUDE_DIRS "${PROJECT_BINARY_DIR}/src" "${PROJECT_SOURCE_DIR}/src" ) @@ -293,8 +293,8 @@ if ((TARGET_ARCHITECTURE STREQUAL "x86_64") OR (TARGET_ARCHITECTURE STREQUAL "i6 set(SSE_FLAGS "-mfpmath=sse" "-msse" "-msse2") elseif (CMAKE_CXX_COMPILER_ID MATCHES "Intel") set(SSE_FLAGS "-mfpmath=sse" "-msse" "-msse2") - # Intel's compiler requires linking with libiomp5 - list(APPEND Z3_DEPENDENT_LIBS "iomp5") + # Intel's compiler requires linking with libiomp5 + list(APPEND Z3_DEPENDENT_LIBS "iomp5") elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") set(SSE_FLAGS "/arch:SSE2") else() @@ -624,7 +624,7 @@ install( ################################################################################ # Examples ################################################################################ -cmake_dependent_option(Z3_ENABLE_EXAMPLE_TARGETS +cmake_dependent_option(Z3_ENABLE_EXAMPLE_TARGETS "Build Z3 api examples" ON "CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR" OFF) if (Z3_ENABLE_EXAMPLE_TARGETS) diff --git a/src/util/parray.h b/src/util/parray.h index 0c3173f6c..f8f2d7e54 100644 --- a/src/util/parray.h +++ b/src/util/parray.h @@ -91,7 +91,7 @@ private: } void dec_ref(unsigned sz, value * vs) { - if (C::ref_count) + if (C::ref_count) for (unsigned i = 0; i < sz; i++) m_vmanager.dec_ref(vs[i]); } @@ -151,7 +151,7 @@ private: size_t new_capacity = curr_capacity == 0 ? 2 : (3 * curr_capacity + 1) >> 1; value * new_vs = allocate_values(new_capacity); if (curr_capacity > 0) { - for (size_t i = 0; i < curr_capacity; i++) + for (size_t i = 0; i < curr_capacity; i++) new_vs[i] = vs[i]; deallocate_values(vs); } @@ -177,7 +177,7 @@ private: inc_ref(v); vs[sz] = v; sz++; - } + } void rpush_back(cell * c, value const & v) { SASSERT(c->kind() == ROOT); @@ -269,7 +269,7 @@ public: } value_manager & manager() { return m_vmanager; } - + void mk(ref & r) { dec_ref(r.m_ref); cell * new_c = mk(ROOT); @@ -283,12 +283,12 @@ public: r.m_ref = nullptr; r.m_updt_counter = 0; } - + void copy(ref const & s, ref & t) { inc_ref(s.m_ref); dec_ref(t.m_ref); t.m_ref = s.m_ref; - t.m_updt_counter = 0; + t.m_updt_counter = 0; } unsigned size(ref const & r) const { @@ -310,7 +310,7 @@ public: } void check_size(cell* c) const { - unsigned r; + [[maybe_unused]] unsigned r; while (c) { switch (c->kind()) { case SET: @@ -333,7 +333,7 @@ public: value const & get(ref const & r, unsigned i) const { SASSERT(i < size(r)); - + unsigned trail_sz = 0; cell * c = r.m_ref; @@ -451,7 +451,7 @@ public: inc_ref(v); new_c->m_elem = v; new_c->m_next = r.m_ref; - r.m_ref = new_c; + r.m_ref = new_c; SASSERT(new_c->m_ref_count == 1); } @@ -536,7 +536,7 @@ public: r.m_updt_counter = 0; SASSERT(r.root()); } - + void reroot(ref & r) { if (r.root()) return; @@ -545,7 +545,7 @@ public: unsigned r_sz = size(r); unsigned trail_split_idx = r_sz / C::factor; unsigned i = 0; - cell * c = r.m_ref; + cell * c = r.m_ref; while (c->kind() != ROOT && i < trail_split_idx) { cs.push_back(c); c = c->next(); @@ -556,7 +556,7 @@ public: unfold(c); } DEBUG_CODE(check_size(c);); - SASSERT(c->kind() == ROOT); + SASSERT(c->kind() == ROOT); for (i = cs.size(); i-- > 0; ) { cell * p = cs[i]; SASSERT(c->m_kind == ROOT); @@ -574,7 +574,7 @@ public: case PUSH_BACK: c->m_kind = POP_BACK; if (sz == capacity(vs)) - expand(vs); + expand(vs); vs[sz] = p->m_elem; ++sz; c->m_idx = sz; From 5c54d6564b93d9d345e10303bcb03f1faf2a7c59 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 11 Jul 2022 12:09:15 -0700 Subject: [PATCH 086/125] fix #6143 --- src/sat/sat_simplifier.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/sat/sat_simplifier.cpp b/src/sat/sat_simplifier.cpp index ea936600c..409d03652 100644 --- a/src/sat/sat_simplifier.cpp +++ b/src/sat/sat_simplifier.cpp @@ -1787,6 +1787,7 @@ namespace sat { clause& c = it.curr(); if (!c.is_learned() && !c.was_removed()) { r.push_back(clause_wrapper(c)); + SASSERT(r.back().contains(l)); SASSERT(r.back().size() == c.size()); } } @@ -1808,9 +1809,13 @@ namespace sat { Return false if the result is a tautology */ bool simplifier::resolve(clause_wrapper const & c1, clause_wrapper const & c2, literal l, literal_vector & r) { - CTRACE("resolve_bug", !c1.contains(l), tout << c1 << "\n" << c2 << "\nl: " << l << "\n";); + CTRACE("resolve_bug", !c1.contains(l) || !c2.contains(~l), tout << c1 << "\n" << c2 << "\nl: " << l << "\n";); if (m_visited.size() <= 2*s.num_vars()) m_visited.resize(2*s.num_vars(), false); + if (c1.was_removed()) + return false; + if (c2.was_removed()) + return false; SASSERT(c1.contains(l)); SASSERT(c2.contains(~l)); bool res = true; @@ -1973,7 +1978,14 @@ namespace sat { } } } - TRACE("sat_simplifier", tout << "eliminate " << v << ", before: " << before_clauses << " after: " << after_clauses << "\n";); + TRACE("sat_simplifier", tout << "eliminate " << v << ", before: " << before_clauses << " after: " << after_clauses << "\n"; + tout << "pos\n"; + for (auto & c : m_pos_cls) + tout << c << "\n"; + tout << "neg\n"; + for (auto & c : m_neg_cls) + tout << c << "\n"; + ); m_elim_counter -= num_pos * num_neg + before_lits; m_elim_counter -= num_pos * num_neg + before_lits; @@ -1988,6 +2000,8 @@ namespace sat { m_elim_counter -= num_pos * num_neg + before_lits; for (auto & c1 : m_pos_cls) { + if (c1.was_removed()) + continue; for (auto & c2 : m_neg_cls) { m_new_cls.reset(); if (!resolve(c1, c2, pos_l, m_new_cls)) From f33c933241640396065087a5485aa42240f2e36b Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 11 Jul 2022 12:10:28 -0700 Subject: [PATCH 087/125] Add substitution routine to pdd For Grobner we want to preserve directions of intervals for finding sign conflicts. This means that it makes sense to have external control over linear solutions. --- src/math/dd/dd_pdd.cpp | 21 +++++++++++++++++++++ src/math/dd/dd_pdd.h | 6 ++++++ 2 files changed, 27 insertions(+) diff --git a/src/math/dd/dd_pdd.cpp b/src/math/dd/dd_pdd.cpp index 0dbf3c6f9..857665583 100644 --- a/src/math/dd/dd_pdd.cpp +++ b/src/math/dd/dd_pdd.cpp @@ -1291,6 +1291,27 @@ namespace dd { return *this; } + + /** + * \brief substitute variable v by r. + * This base line implementation is simplistic and does not use operator caching. + */ + pdd pdd::subst_pdd(unsigned v, pdd const& r) const { + if (is_val()) + return *this; + if (m.m_var2level[var()] < m.m_var2level[v]) + return *this; + pdd l = lo().subst_pdd(v, r); + pdd h = hi().subst_pdd(v, r); + if (var() == v) + return r*h + l; + else if (l == lo() && h == hi()) + return *this; + else + return m.mk_var(v)*h + l; + } + + std::ostream& operator<<(std::ostream& out, pdd const& b) { return b.display(out); } void pdd_iterator::next() { diff --git a/src/math/dd/dd_pdd.h b/src/math/dd/dd_pdd.h index 78c447494..13c6c0605 100644 --- a/src/math/dd/dd_pdd.h +++ b/src/math/dd/dd_pdd.h @@ -367,6 +367,12 @@ namespace dd { pdd subst_val(vector> const& s) const { return m.subst_val(*this, s); } pdd subst_val(unsigned v, rational const& val) const { return m.subst_val(*this, v, val); } + + /** + * \brief substitute variable v by r. + */ + pdd subst_pdd(unsigned v, pdd const& r) const; + std::ostream& display(std::ostream& out) const { return m.display(out, *this); } bool operator==(pdd const& other) const { return root == other.root; } bool operator!=(pdd const& other) const { return root != other.root; } From 316ed778e0de6dfe8c8ee7144db236632d035766 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 11 Jul 2022 16:14:26 -0700 Subject: [PATCH 088/125] Tune Grobner equations \brief convert p == 0 into a solved form v == r, such that v has bounds [lo, oo) iff r has bounds [lo', oo) v has bounds (oo,hi] iff r has bounds (oo,hi'] The solved form allows the Grobner solver identify more bounds conflicts. A bad leading term can miss bounds conflicts. For example for x + y + z == 0 where x, y : [0, oo) and z : (oo,0] we prefer to solve z == -x - y instead of x == -z - y because the solution -z - y has neither an upper, nor a lower bound. The Grobner solver is augmented with a notion of a substitution that is applied before the solver is run. --- src/math/dd/dd_pdd.cpp | 2 +- src/math/grobner/pdd_solver.cpp | 55 ++++++++++++++++++++---- src/math/grobner/pdd_solver.h | 4 ++ src/math/lp/nla_core.cpp | 75 +++++++++++++++++++++++++++++++-- src/math/lp/nla_core.h | 4 +- 5 files changed, 127 insertions(+), 13 deletions(-) diff --git a/src/math/dd/dd_pdd.cpp b/src/math/dd/dd_pdd.cpp index 857665583..455fc4039 100644 --- a/src/math/dd/dd_pdd.cpp +++ b/src/math/dd/dd_pdd.cpp @@ -1308,7 +1308,7 @@ namespace dd { else if (l == lo() && h == hi()) return *this; else - return m.mk_var(v)*h + l; + return m.mk_var(var())*h + l; } diff --git a/src/math/grobner/pdd_solver.cpp b/src/math/grobner/pdd_solver.cpp index dd79b506b..51aed555c 100644 --- a/src/math/grobner/pdd_solver.cpp +++ b/src/math/grobner/pdd_solver.cpp @@ -342,6 +342,7 @@ namespace dd { for (equation* e : m_solved) dealloc(e); for (equation* e : m_to_simplify) dealloc(e); for (equation* e : m_processed) dealloc(e); + m_subst.reset(); m_solved.reset(); m_processed.reset(); m_to_simplify.reset(); @@ -354,16 +355,39 @@ namespace dd { void solver::add(pdd const& p, u_dependency * dep) { if (p.is_zero()) return; equation * eq = alloc(equation, p, dep); - if (check_conflict(*eq)) { + if (check_conflict(*eq)) return; - } push_equation(to_simplify, eq); - if (!m_var2level.empty()) { + if (!m_var2level.empty()) m_levelp1 = std::max(m_var2level[p.var()]+1, m_levelp1); - } update_stats_max_degree_and_size(*eq); - } + } + + void solver::add_subst(unsigned v, pdd const& p, u_dependency* dep) { + SASSERT(m_processed.empty()); + SASSERT(m_solved.empty()); + + m_subst.push_back({v, p, dep}); + + for (auto* e : m_to_simplify) { + auto r = e->poly().subst_pdd(v, p); + if (r == e->poly()) + continue; + *e = m_dep_manager.mk_join(dep, e->dep()); + *e = r; + } + } + + void solver::simplify(pdd& p, u_dependency*& d) { + for (auto const& [v, q, d2] : m_subst) { + pdd r = p.subst_pdd(v, q); + if (r != p) { + p = r; + d = m_dep_manager.mk_join(d, d2); + } + } + } bool solver::canceled() { return m_limit.is_canceled(); @@ -446,9 +470,24 @@ namespace dd { } std::ostream& solver::display(std::ostream& out) const { - out << "solved\n"; for (auto e : m_solved) display(out, *e); - out << "processed\n"; for (auto e : m_processed) display(out, *e); - out << "to_simplify\n"; for (auto e : m_to_simplify) display(out, *e); + if (!m_solved.empty()) { + out << "solved\n"; for (auto e : m_solved) display(out, *e); + } + if (!m_processed.empty()) { + out << "processed\n"; for (auto e : m_processed) display(out, *e); + } + if (!m_to_simplify.empty()) { + out << "to_simplify\n"; for (auto e : m_to_simplify) display(out, *e); + } + if (!m_subst.empty()) { + out << "subst\n"; + for (auto const& [v, p, d] : m_subst) { + out << "v" << v << " := " << p; + if (m_print_dep) + m_print_dep(d, out); + out << "\n"; + } + } return display_statistics(out); } diff --git a/src/math/grobner/pdd_solver.h b/src/math/grobner/pdd_solver.h index 150a70df8..3f3c4e73e 100644 --- a/src/math/grobner/pdd_solver.h +++ b/src/math/grobner/pdd_solver.h @@ -118,6 +118,7 @@ private: equation_vector m_solved; // equations with solved variables, triangular equation_vector m_processed; equation_vector m_to_simplify; + vector> m_subst; mutable u_dependency_manager m_dep_manager; equation_vector m_all_eqs; equation* m_conflict; @@ -136,6 +137,9 @@ public: void add(pdd const& p) { add(p, nullptr); } void add(pdd const& p, u_dependency * dep); + void simplify(pdd& p, u_dependency*& dep); + void add_subst(unsigned v, pdd const& p, u_dependency* dep); + void simplify(); void saturate(); diff --git a/src/math/lp/nla_core.cpp b/src/math/lp/nla_core.cpp index e6eb8ad26..9e3e168e9 100644 --- a/src/math/lp/nla_core.cpp +++ b/src/math/lp/nla_core.cpp @@ -1716,7 +1716,7 @@ void core::configure_grobner() { for (lpvar k : emons()[j].vars()) r *= pdd_expr(rational::one(), k, dep); r -= val_of_fixed_var_with_deps(j, dep); - m_pdd_grobner.add(r, dep); + add_eq_to_grobner(r, dep); } } } @@ -1880,6 +1880,76 @@ dd::pdd core::pdd_expr(const rational& c, lpvar j, u_dependency*& dep) { return r; } +/** + \brief convert p == 0 into a solved form v == r, such that + v has bounds [lo, oo) iff r has bounds [lo', oo) + v has bounds (oo,hi] iff r has bounds (oo,hi'] + + The solved form allows the Grobner solver identify more bounds conflicts. + A bad leading term can miss bounds conflicts. + For example for x + y + z == 0 where x, y : [0, oo) and z : (oo,0] + we prefer to solve z == -x - y instead of x == -z - y + because the solution -z - y has neither an upper, nor a lower bound. + */ +bool core::is_solved(dd::pdd const& p, unsigned& v, dd::pdd& r) { + if (!p.is_linear()) + return false; + r = p; + unsigned num_lo = 0, num_hi = 0; + unsigned lo = 0, hi = 0; + rational lc, hc, c; + while (!r.is_val()) { + SASSERT(r.hi().is_val()); + v = r.var(); + rational val = r.hi().val(); + switch (m_lar_solver.get_column_type(v)) { + case lp::column_type::lower_bound: + if (val > 0) num_lo++, lo = v, lc = val; else num_hi++, hi = v, hc = val; + break; + case lp::column_type::upper_bound: + if (val < 0) num_lo++, lo = v, lc = val; else num_hi++, hi = v, hc = val; + break; + case lp::column_type::fixed: + case lp::column_type::boxed: + break; + default: + return false; + } + if (num_lo > 1 && num_hi > 1) + return false; + r = r.lo(); + } + if (num_lo == 1 && num_hi > 1) { + v = lo; + c = lc; + } + else if (num_hi == 1 && num_lo > 1) { + v = hi; + c = hc; + } + else + return false; + + r = c*m_pdd_manager.mk_var(v) - p; + if (c != 1) + r = r * (1/c); + return true; +} + +/** + \brief add an equality to grobner solver, convert it to solved form if available. +*/ +void core::add_eq_to_grobner(dd::pdd& p, u_dependency* dep) { + unsigned v; + dd::pdd q(m_pdd_manager); + m_pdd_grobner.simplify(p, dep); + if (is_solved(p, v, q)) + m_pdd_grobner.add_subst(v, q, dep); + else + m_pdd_grobner.add(p, dep); +} + + void core::add_row_to_grobner(const vector> & row) { u_dependency *dep = nullptr; rational val; @@ -1887,7 +1957,7 @@ void core::add_row_to_grobner(const vector> & row) { for (const auto &p : row) sum += pdd_expr(p.coeff(), p.var(), dep); TRACE("grobner", print_row(row, tout) << " " << sum << "\n"); - m_pdd_grobner.add(sum, dep); + add_eq_to_grobner(sum, dep); } @@ -1907,7 +1977,6 @@ void core::find_nl_cluster() { TRACE("grobner", tout << "vars in cluster: "; for (lpvar j : active_var_set()) tout << "j" << j << " "; tout << "\n"; display_matrix_of_m_rows(tout); - /*emons().display(tout << "emons\n");*/ ); } diff --git a/src/math/lp/nla_core.h b/src/math/lp/nla_core.h index e50075564..ee07c7db1 100644 --- a/src/math/lp/nla_core.h +++ b/src/math/lp/nla_core.h @@ -466,7 +466,9 @@ public: void display_matrix_of_m_rows(std::ostream & out) const; void set_active_vars_weights(nex_creator&); unsigned get_var_weight(lpvar) const; - void add_row_to_grobner(const vector> & row); + void add_row_to_grobner(const vector> & row); + bool is_solved(dd::pdd const& p, unsigned& v, dd::pdd& r); + void add_eq_to_grobner(dd::pdd& p, u_dependency* dep); bool check_pdd_eq(const dd::solver::equation*); const rational& val_of_fixed_var_with_deps(lpvar j, u_dependency*& dep); dd::pdd pdd_expr(const rational& c, lpvar j, u_dependency*&); From 2e797045fad7fd4ab9427599e5dbcebbc69f1c39 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 11 Jul 2022 20:33:12 -0700 Subject: [PATCH 089/125] remove space Signed-off-by: Nikolaj Bjorner --- doc/website.dox.in | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/website.dox.in b/doc/website.dox.in index de5feb685..5249f2ae0 100644 --- a/doc/website.dox.in +++ b/doc/website.dox.in @@ -8,6 +8,5 @@ This website hosts the automatically generated documentation for the Z3 APIs. - - \ref @C_API@ - - \ref @CPP_API@ @DOTNET_API@ @JAVA_API@ @PYTHON_API@ @OCAML_API@ @JS_API@ + - \ref @C_API@ @CPP_API@ @DOTNET_API@ @JAVA_API@ @PYTHON_API@ @OCAML_API@ @JS_API@ */ From 4dc88f0993dbe1d118b3ac3c4e553e3398c8dda4 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 11 Jul 2022 20:37:50 -0700 Subject: [PATCH 090/125] add --js to nightly and release scripts, nb @ritave Signed-off-by: Nikolaj Bjorner --- scripts/nightly.yaml | 2 +- scripts/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/nightly.yaml b/scripts/nightly.yaml index fd93d4236..f210370ab 100644 --- a/scripts/nightly.yaml +++ b/scripts/nightly.yaml @@ -75,7 +75,7 @@ stages: set -e eval `opam config env` cd doc - python mk_api_doc.py --mld --z3py-package-path=../build/python/z3 + python mk_api_doc.py --mld --js --z3py-package-path=../build/python/z3 mkdir api/html/ml ocamldoc -html -d api/html/ml -sort -hide Z3 -I $( ocamlfind query zarith ) -I ../build/api/ml ../build/api/ml/z3enums.mli ../build/api/ml/z3.mli cd .. diff --git a/scripts/release.yml b/scripts/release.yml index 419d4bc4b..8cc91e252 100644 --- a/scripts/release.yml +++ b/scripts/release.yml @@ -111,7 +111,7 @@ stages: set -e eval `opam config env` cd doc - python mk_api_doc.py --mld --z3py-package-path=../build/python/z3 + python mk_api_doc.py --mld --js --z3py-package-path=../build/python/z3 mkdir api/html/ml ocamldoc -html -d api/html/ml -sort -hide Z3 -I $( ocamlfind query zarith ) -I ../build/api/ml ../build/api/ml/z3enums.mli ../build/api/ml/z3.mli cd .. From d5779bf99c49ff264ed0ff30f397007fe16c95a4 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 11 Jul 2022 21:05:26 -0700 Subject: [PATCH 091/125] handle trivial equalities in simplify_leaf Signed-off-by: Nikolaj Bjorner --- src/math/grobner/pdd_simplifier.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/math/grobner/pdd_simplifier.cpp b/src/math/grobner/pdd_simplifier.cpp index fe6d45d30..e8b10ad80 100644 --- a/src/math/grobner/pdd_simplifier.cpp +++ b/src/math/grobner/pdd_simplifier.cpp @@ -223,6 +223,8 @@ namespace dd { for (unsigned i = 0; i < s.m_to_simplify.size(); ++i) { equation* e = s.m_to_simplify[i]; pdd p = e->poly(); + if (p.is_val()) + continue; if (!p.hi().is_val()) { continue; } From faf6c02cf83eadd94b0565b9dc9682d114a5fcb5 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 12 Jul 2022 07:46:06 -0700 Subject: [PATCH 092/125] remove --js from nightly and release doc builds as the npm run 'check-engine' fails Signed-off-by: Nikolaj Bjorner --- scripts/nightly.yaml | 2 +- scripts/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/nightly.yaml b/scripts/nightly.yaml index f210370ab..fd93d4236 100644 --- a/scripts/nightly.yaml +++ b/scripts/nightly.yaml @@ -75,7 +75,7 @@ stages: set -e eval `opam config env` cd doc - python mk_api_doc.py --mld --js --z3py-package-path=../build/python/z3 + python mk_api_doc.py --mld --z3py-package-path=../build/python/z3 mkdir api/html/ml ocamldoc -html -d api/html/ml -sort -hide Z3 -I $( ocamlfind query zarith ) -I ../build/api/ml ../build/api/ml/z3enums.mli ../build/api/ml/z3.mli cd .. diff --git a/scripts/release.yml b/scripts/release.yml index 8cc91e252..419d4bc4b 100644 --- a/scripts/release.yml +++ b/scripts/release.yml @@ -111,7 +111,7 @@ stages: set -e eval `opam config env` cd doc - python mk_api_doc.py --mld --js --z3py-package-path=../build/python/z3 + python mk_api_doc.py --mld --z3py-package-path=../build/python/z3 mkdir api/html/ml ocamldoc -html -d api/html/ml -sort -hide Z3 -I $( ocamlfind query zarith ) -I ../build/api/ml ../build/api/ml/z3enums.mli ../build/api/ml/z3.mli cd .. From 43cf0530664ba62e77e590b56133b07d463a1f6b Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 12 Jul 2022 15:43:12 -0700 Subject: [PATCH 093/125] fix #6128 --- src/smt/smt_model_checker.cpp | 2 +- src/smt/theory_array_full.cpp | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/smt/smt_model_checker.cpp b/src/smt/smt_model_checker.cpp index 5f6131f59..90ef225a5 100644 --- a/src/smt/smt_model_checker.cpp +++ b/src/smt/smt_model_checker.cpp @@ -258,7 +258,7 @@ namespace smt { bindings.set(num_decls - i - 1, sk_value); } - TRACE("model_checker", tout << q->get_qid() << " found (use_inv: " << use_inv << ") new instance: " << bindings << "\n" << defs << "\n";); + TRACE("model_checker", tout << q->get_qid() << " found (use_inv: " << use_inv << ") new instance: " << bindings << "\ndefs:\n" << defs << "\n";); if (!defs.empty()) def = mk_and(defs); max_generation = std::max(m_qm->get_generation(q), max_generation); add_instance(q, bindings, max_generation, def.get()); diff --git a/src/smt/theory_array_full.cpp b/src/smt/theory_array_full.cpp index 1a793a116..4311ef32c 100644 --- a/src/smt/theory_array_full.cpp +++ b/src/smt/theory_array_full.cpp @@ -594,10 +594,13 @@ namespace smt { if (!ctx.add_fingerprint(this, m_default_lambda_fingerprint, 1, &arr)) return false; m_stats.m_num_default_lambda_axiom++; - expr* def = mk_default(arr->get_expr()); + expr* e = arr->get_expr(); + expr* def = mk_default(e); quantifier* lam = m.is_lambda_def(arr->get_decl()); - expr_ref_vector args(m); - args.push_back(lam); + TRACE("array", tout << mk_pp(lam, m) << "\n" << mk_pp(e, m) << "\n"); + expr_ref_vector args(m); + var_subst subst(m, false); + args.push_back(subst(lam, to_app(e)->get_num_args(), to_app(e)->get_args())); for (unsigned i = 0; i < lam->get_num_decls(); ++i) args.push_back(mk_epsilon(lam->get_decl_sort(i)).first); expr_ref val(mk_select(args), m); From ca80d996176f26fc171b2a131d7dcf6bd112a38d Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 12 Jul 2022 15:49:57 -0700 Subject: [PATCH 094/125] fix #6153 --- src/smt/theory_arith_core.h | 7 +++++-- src/smt/theory_lra.cpp | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_arith_core.h b/src/smt/theory_arith_core.h index 1b2a7cc49..012ad695b 100644 --- a/src/smt/theory_arith_core.h +++ b/src/smt/theory_arith_core.h @@ -563,7 +563,7 @@ namespace smt { if (!m_util.is_zero(divisor)) { // if divisor is zero, then idiv and mod are uninterpreted functions. expr_ref div(m), mod(m), zero(m), abs_divisor(m), one(m); - expr_ref eqz(m), eq(m), lower(m), upper(m); + expr_ref eqz(m), eq(m), lower(m), upper(m), qr(m); div = m_util.mk_idiv(dividend, divisor); mod = m_util.mk_mod(dividend, divisor); zero = m_util.mk_int(0); @@ -571,7 +571,8 @@ namespace smt { abs_divisor = m_util.mk_sub(m.mk_ite(m_util.mk_lt(divisor, zero), m_util.mk_sub(zero, divisor), divisor), one); s(abs_divisor); eqz = m.mk_eq(divisor, zero); - eq = m.mk_eq(m_util.mk_add(m_util.mk_mul(divisor, div), mod), dividend); + qr = m_util.mk_add(m_util.mk_mul(divisor, div), mod); + eq = m.mk_eq(qr, dividend); lower = m_util.mk_ge(mod, zero); upper = m_util.mk_le(mod, abs_divisor); TRACE("div_axiom_bug", @@ -585,6 +586,8 @@ namespace smt { mk_axiom(eqz, upper, !m_util.is_numeral(abs_divisor)); rational k; + m_arith_eq_adapter.mk_axioms(ensure_enode(qr), ensure_enode(mod)); + if (m_util.is_zero(dividend)) { mk_axiom(eqz, m.mk_eq(div, zero)); mk_axiom(eqz, m.mk_eq(mod, zero)); diff --git a/src/smt/theory_lra.cpp b/src/smt/theory_lra.cpp index 794a61e76..75f9cb997 100644 --- a/src/smt/theory_lra.cpp +++ b/src/smt/theory_lra.cpp @@ -1271,6 +1271,7 @@ public: mk_axiom(eqz, eq); mk_axiom(eqz, mod_ge_0); mk_axiom(eqz, mod_lt_q); + m_arith_eq_adapter.mk_axioms(th.ensure_enode(mod_r), th.ensure_enode(p)); if (a.is_zero(p)) { mk_axiom(eqz, mk_literal(m.mk_eq(div, zero))); From 8900db527fdf76275c98d81901b90b023cc0e9ec Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 12 Jul 2022 20:49:54 -0700 Subject: [PATCH 095/125] add diagnostics for grobner --- src/math/lp/nla_core.cpp | 7 +++ src/math/lp/nra_solver.cpp | 108 +++++++++++++++++++++++++++++++++++-- src/math/lp/nra_solver.h | 6 +++ 3 files changed, 116 insertions(+), 5 deletions(-) diff --git a/src/math/lp/nla_core.cpp b/src/math/lp/nla_core.cpp index 9e3e168e9..470f05d13 100644 --- a/src/math/lp/nla_core.cpp +++ b/src/math/lp/nla_core.cpp @@ -1663,6 +1663,13 @@ void core::run_grobner() { return; } +#if 0 + vector eqs; + for (auto eq : m_pdd_grobner.equations()) + eqs.push_back(eq->poly()); + m_nra.check(eqs); +#endif + #if 0 bool propagated = false; for (auto eq : m_pdd_grobner.equations()) { diff --git a/src/math/lp/nra_solver.cpp b/src/math/lp/nra_solver.cpp index 31c8d2f11..56a84d1f0 100644 --- a/src/math/lp/nra_solver.cpp +++ b/src/math/lp/nra_solver.cpp @@ -65,12 +65,10 @@ struct solver::imp { } // add polynomial definitions. - for (auto const& m : m_nla_core.emons()) { + for (auto const& m : m_nla_core.emons()) add_monic_eq(m); - } - for (unsigned i : m_term_set) { + for (unsigned i : m_term_set) add_term(i); - } // TBD: add variable bounds? lbool r = l_undef; @@ -176,7 +174,103 @@ struct solver::imp { lp_assert(false); // unreachable } m_nlsat->mk_clause(1, &lit, a); - } + } + + + lbool check(vector const& eqs) { + m_zero = nullptr; + m_nlsat = alloc(nlsat::solver, m_limit, m_params, false); + m_zero = alloc(scoped_anum, am()); + m_lp2nl.reset(); + m_term_set.clear(); + for (auto const& eq : eqs) + add_eq(eq); + for (auto const& [v, w] : m_lp2nl) { + auto& ls = m_nla_core.m_lar_solver; + if (ls.column_has_lower_bound(v)) + add_lb(ls.get_lower_bound(v), w); + if (ls.column_has_upper_bound(v)) + add_ub(ls.get_upper_bound(v), w); + } + + lbool r = l_undef; + try { + r = m_nlsat->check(); + } + catch (z3_exception&) { + if (m_limit.is_canceled()) { + r = l_undef; + } + else { + throw; + } + } + + IF_VERBOSE(0, verbose_stream() << "check-nra " << r << "\n"; + m_nlsat->display(verbose_stream()); + for (auto const& [v, w] : m_lp2nl) { + auto& ls = m_nla_core.m_lar_solver; + if (ls.column_has_lower_bound(v)) + verbose_stream() << w << " >= " << ls.get_lower_bound(v) << "\n"; + if (ls.column_has_upper_bound(v)) + verbose_stream() << w << " <= " << ls.get_upper_bound(v) << "\n"; + }); + + + return r; + } + + void add_eq(dd::pdd const& eq) { + dd::pdd normeq = eq; + rational lc(1); + for (auto const& [c, m] : eq) + lc = lcm(denominator(c), lc); + if (lc != 1) + normeq *= lc; + polynomial::manager& pm = m_nlsat->pm(); + polynomial::polynomial_ref p(pdd2polynomial(normeq), pm); + bool is_even[1] = { false }; + polynomial::polynomial* ps[1] = { p }; + nlsat::literal lit = m_nlsat->mk_ineq_literal(nlsat::atom::kind::EQ, 1, ps, is_even); + m_nlsat->mk_clause(1, &lit, nullptr); + } + + void add_lb(lp::impq const& b, unsigned w) { + add_bound(b.x, w, b.y <= 0, b.y > 0 ? nlsat::atom::kind::GT : nlsat::atom::kind::LT); + } + void add_ub(lp::impq const& b, unsigned w) { + add_bound(b.x, w, b.y >= 0, b.y < 0 ? nlsat::atom::kind::LT : nlsat::atom::kind::GT); + } + + // w - bound < 0 + // w - bound > 0 + void add_bound(lp::mpq const& bound, unsigned w, bool neg, nlsat::atom::kind k) { + polynomial::manager& pm = m_nlsat->pm(); + polynomial::polynomial_ref p1(pm.mk_polynomial(w), pm); + polynomial::polynomial_ref p2(pm.mk_const(bound), pm); + polynomial::polynomial_ref p(pm.sub(p1, p2), pm); + polynomial::polynomial* ps[1] = { p }; + bool is_even[1] = { false }; + nlsat::literal lit = m_nlsat->mk_ineq_literal(k, 1, ps, is_even); + if (neg) + lit.neg(); + m_nlsat->mk_clause(1, &lit, nullptr); + } + + polynomial::polynomial* pdd2polynomial(dd::pdd const& p) { + polynomial::manager& pm = m_nlsat->pm(); + if (p.is_val()) + return pm.mk_const(p.val()); + polynomial::polynomial_ref lo(pdd2polynomial(p.lo()), pm); + polynomial::polynomial_ref hi(pdd2polynomial(p.hi()), pm); + unsigned w, v = p.var(); + if (!m_lp2nl.find(v, w)) { + w = m_nlsat->mk_var(false); + m_lp2nl.insert(v, w); + } + polynomial::polynomial_ref vp(pm.mk_polynomial(w, 1), pm); + return pm.add(lo, pm.mul(vp, hi)); + } bool is_int(lp::var_index v) { return s.var_is_int(v); @@ -265,6 +359,10 @@ lbool solver::check() { return m_imp->check(); } +lbool solver::check(vector const& eqs) { + return m_imp->check(eqs); +} + bool solver::need_check() { return m_imp->need_check(); } diff --git a/src/math/lp/nra_solver.h b/src/math/lp/nra_solver.h index 56dcd69b5..b8863e44b 100644 --- a/src/math/lp/nra_solver.h +++ b/src/math/lp/nra_solver.h @@ -9,6 +9,7 @@ #include "util/rlimit.h" #include "util/params.h" #include "nlsat/nlsat_solver.h" +#include "math/dd/dd_pdd.h" namespace lp { class lar_solver; @@ -36,6 +37,11 @@ namespace nra { */ lbool check(); + /** + \breif Check feasibility of equalities modulo bounds constraints on their variables. + */ + lbool check(vector const& eqs); + /* \brief determine whether nra check is needed. */ From 7d0c789af0f8678af445ea53b5fc6a31569ef78d Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 12 Jul 2022 20:50:28 -0700 Subject: [PATCH 096/125] propagate has-length over map/mapi --- src/smt/theory_seq.cpp | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/smt/theory_seq.cpp b/src/smt/theory_seq.cpp index e5e0c9cb0..2d05ee9aa 100644 --- a/src/smt/theory_seq.cpp +++ b/src/smt/theory_seq.cpp @@ -2783,30 +2783,29 @@ bool theory_seq::get_length(expr* e, rational& val) { todo.push_back(e1); todo.push_back(e2); } - else if (m_util.str.is_unit(c)) { + else if (m_util.str.is_unit(c)) val += rational(1); - } - else if (m_util.str.is_empty(c)) { + else if (m_util.str.is_empty(c)) continue; - } - else if (m_util.str.is_string(c, s)) { + else if (m_util.str.is_map(c, e1, e2)) + todo.push_back(e2); + else if (m_util.str.is_mapi(c, e1, e2, c)) + todo.push_back(c); + else if (m_util.str.is_string(c, s)) val += rational(s.length()); - } - else if (!has_length(c)) { - len = mk_len(c); + else + continue; + len = mk_len(c); + if (!has_length(c)) { add_axiom(mk_literal(m_autil.mk_ge(len, m_autil.mk_int(0)))); TRACE("seq", tout << "literal has no length " << mk_pp(c, m) << "\n";); return false; } - else { - len = mk_len(c); - if (m_arith_value.get_value(len, val1) && !val1.is_neg()) { - val += val1; - } - else { - TRACE("seq", tout << "length has not been internalized " << mk_pp(c, m) << "\n";); - return false; - } + else if (m_arith_value.get_value(len, val1) && !val1.is_neg()) + val += val1; + else { + TRACE("seq", tout << "length has not been internalized " << mk_pp(c, m) << "\n";); + return false; } } CTRACE("seq", !val.is_int(), tout << "length is not an integer\n";); From b81f70f6fcc7850369c3131ebc11a4ffca9da393 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 13 Jul 2022 13:05:57 -0700 Subject: [PATCH 097/125] split nla_grobner to separate file --- src/math/lp/CMakeLists.txt | 1 + src/math/lp/nla_core.cpp | 363 --------------------------------- src/math/lp/nla_core.h | 4 + src/math/lp/nla_grobner.cpp | 393 ++++++++++++++++++++++++++++++++++++ 4 files changed, 398 insertions(+), 363 deletions(-) create mode 100644 src/math/lp/nla_grobner.cpp diff --git a/src/math/lp/CMakeLists.txt b/src/math/lp/CMakeLists.txt index ad21bea37..6ec8ba12d 100644 --- a/src/math/lp/CMakeLists.txt +++ b/src/math/lp/CMakeLists.txt @@ -34,6 +34,7 @@ z3_add_component(lp nla_basics_lemmas.cpp nla_common.cpp nla_core.cpp + nla_grobner.cpp nla_intervals.cpp nla_monotone_lemmas.cpp nla_order_lemmas.cpp diff --git a/src/math/lp/nla_core.cpp b/src/math/lp/nla_core.cpp index 470f05d13..41482f3c2 100644 --- a/src/math/lp/nla_core.cpp +++ b/src/math/lp/nla_core.cpp @@ -1636,362 +1636,6 @@ std::ostream& core::print_term( const lp::lar_term& t, std::ostream& out) const } -void core::run_grobner() { - unsigned& quota = m_nla_settings.grobner_quota; - if (quota == 1) { - return; - } - clear_and_resize_active_var_set(); - find_nl_cluster(); - - lp_settings().stats().m_grobner_calls++; - configure_grobner(); - m_pdd_grobner.saturate(); - bool conflict = false; - unsigned n = m_pdd_grobner.number_of_conflicts_to_report(); - SASSERT(n > 0); - for (auto eq : m_pdd_grobner.equations()) { - if (check_pdd_eq(eq)) { - conflict = true; - if (--n == 0) - break; - } - } - TRACE("grobner", m_pdd_grobner.display(tout)); - if (conflict) { - IF_VERBOSE(2, verbose_stream() << "grobner conflict\n"); - return; - } - -#if 0 - vector eqs; - for (auto eq : m_pdd_grobner.equations()) - eqs.push_back(eq->poly()); - m_nra.check(eqs); -#endif - -#if 0 - bool propagated = false; - for (auto eq : m_pdd_grobner.equations()) { - auto const& p = eq->poly(); - if (p.is_offset()) { - lpvar v = p.var(); - if (m_lar_solver.column_has_lower_bound(v) && - m_lar_solver.column_has_upper_bound(v)) - continue; - rational fixed_val = -p.lo().val(); - lp::explanation ex; - u_dependency_manager dm; - vector lv; - dm.linearize(eq->dep(), lv); - for (unsigned ci : lv) - ex.push_back(ci); - new_lemma lemma(*this, "pdd-eq"); - lemma &= ex; - lemma |= ineq(v, llc::EQ, fixed_val); - propagated = true; - } - } - if (propagated) - return; -#endif - - if (quota > 1) - quota--; - IF_VERBOSE(2, verbose_stream() << "grobner miss, quota " << quota << "\n"); - IF_VERBOSE(4, diagnose_pdd_miss(verbose_stream())); - -} - -void core::configure_grobner() { - m_pdd_grobner.reset(); - try { - set_level2var_for_grobner(); - TRACE("grobner", - tout << "base vars: "; - for (lpvar j : active_var_set()) - if (m_lar_solver.is_base(j)) - tout << "j" << j << " "; - tout << "\n"); - for (lpvar j : active_var_set()) { - if (m_lar_solver.is_base(j)) - add_row_to_grobner(m_lar_solver.basic2row(j)); - - if (is_monic_var(j) && var_is_fixed(j)) { - u_dependency* dep = nullptr; - dd::pdd r = m_pdd_manager.mk_val(rational(1)); - for (lpvar k : emons()[j].vars()) - r *= pdd_expr(rational::one(), k, dep); - r -= val_of_fixed_var_with_deps(j, dep); - add_eq_to_grobner(r, dep); - } - } - } - catch (...) { - IF_VERBOSE(2, verbose_stream() << "pdd throw\n"); - return; - } - TRACE("grobner", m_pdd_grobner.display(tout)); - -#if 0 - IF_VERBOSE(2, m_pdd_grobner.display(verbose_stream())); - dd::pdd_eval eval(m_pdd_manager); - eval.var2val() = [&](unsigned j){ return val(j); }; - for (auto* e : m_pdd_grobner.equations()) { - dd::pdd p = e->poly(); - rational v = eval(p); - if (p.is_linear() && !eval(p).is_zero()) { - IF_VERBOSE(0, verbose_stream() << "violated linear constraint " << p << "\n"); - } - } -#endif - - struct dd::solver::config cfg; - cfg.m_max_steps = m_pdd_grobner.equations().size(); - cfg.m_max_simplified = m_nla_settings.grobner_max_simplified; - cfg.m_eqs_growth = m_nla_settings.grobner_eqs_growth; - cfg.m_expr_size_growth = m_nla_settings.grobner_expr_size_growth; - cfg.m_expr_degree_growth = m_nla_settings.grobner_expr_degree_growth; - cfg.m_number_of_conflicts_to_report = m_nla_settings.grobner_number_of_conflicts_to_report; - m_pdd_grobner.set(cfg); - m_pdd_grobner.adjust_cfg(); - m_pdd_manager.set_max_num_nodes(10000); // or something proportional to the number of initial nodes. -} - -std::ostream& core::diagnose_pdd_miss(std::ostream& out) { - - // m_pdd_grobner.display(out); - - dd::pdd_eval eval; - eval.var2val() = [&](unsigned j){ return val(j); }; - for (auto* e : m_pdd_grobner.equations()) { - dd::pdd p = e->poly(); - rational v = eval(p); - if (!v.is_zero()) { - out << p << " := " << v << "\n"; - } - } - - for (unsigned j = 0; j < m_lar_solver.number_of_vars(); ++j) { - if (m_lar_solver.column_has_lower_bound(j) || m_lar_solver.column_has_upper_bound(j)) { - out << j << ": ["; - if (m_lar_solver.column_has_lower_bound(j)) out << m_lar_solver.get_lower_bound(j); - out << ".."; - if (m_lar_solver.column_has_upper_bound(j)) out << m_lar_solver.get_upper_bound(j); - out << "]\n"; - } - } - return out; -} - -bool core::check_pdd_eq(const dd::solver::equation* e) { - auto& di = m_intervals.get_dep_intervals(); - dd::pdd_interval eval(di); - eval.var2interval() = [this](lpvar j, bool deps, scoped_dep_interval& a) { - if (deps) m_intervals.set_var_interval(j, a); - else m_intervals.set_var_interval(j, a); - }; - scoped_dep_interval i(di), i_wd(di); - eval.get_interval(e->poly(), i); - if (!di.separated_from_zero(i)) { - TRACE("grobner", m_pdd_grobner.display(tout << "not separated from 0 ", *e) << "\n"; - eval.get_interval_distributed(e->poly(), i); - tout << "separated from 0: " << di.separated_from_zero(i) << "\n"; - for (auto j : e->poly().free_vars()) { - scoped_dep_interval a(di); - m_intervals.set_var_interval(j, a); - m_intervals.display(tout << "j" << j << " ", a); tout << " "; - } - tout << "\n"); - - return false; - } - eval.get_interval(e->poly(), i_wd); - std::function f = [this](const lp::explanation& e) { - new_lemma lemma(*this, "pdd"); - lemma &= e; - }; - if (di.check_interval_for_conflict_on_zero(i_wd, e->dep(), f)) { - TRACE("grobner", m_pdd_grobner.display(tout << "conflict ", *e) << "\n"); - lp_settings().stats().m_grobner_conflicts++; - return true; - } - else { - TRACE("grobner", m_pdd_grobner.display(tout << "no conflict ", *e) << "\n"); - return false; - } -} - -void core::add_var_and_its_factors_to_q_and_collect_new_rows(lpvar j, svector & q) { - if (active_var_set_contains(j)) - return; - insert_to_active_var_set(j); - if (is_monic_var(j)) { - const monic& m = emons()[j]; - for (auto fcn : factorization_factory_imp(m, *this)) - for (const factor& fc: fcn) - q.push_back(var(fc)); - } - - if (var_is_fixed(j)) - return; - const auto& matrix = m_lar_solver.A_r(); - for (auto & s : matrix.m_columns[j]) { - unsigned row = s.var(); - if (m_rows.contains(row)) - continue; - m_rows.insert(row); - unsigned k = m_lar_solver.get_base_column_in_row(row); - if (m_lar_solver.column_is_free(k) && k != j) - continue; - CTRACE("grobner", matrix.m_rows[row].size() > m_nla_settings.grobner_row_length_limit, - tout << "ignore the row " << row << " with the size " << matrix.m_rows[row].size() << "\n";); - if (matrix.m_rows[row].size() > m_nla_settings.grobner_row_length_limit) - continue; - for (auto& rc : matrix.m_rows[row]) - add_var_and_its_factors_to_q_and_collect_new_rows(rc.var(), q); - } - - -} - -const rational& core::val_of_fixed_var_with_deps(lpvar j, u_dependency*& dep) { - unsigned lc, uc; - m_lar_solver.get_bound_constraint_witnesses_for_column(j, lc, uc); - dep = m_intervals.mk_join(dep, m_intervals.mk_leaf(lc)); - dep = m_intervals.mk_join(dep, m_intervals.mk_leaf(uc)); - return m_lar_solver.column_lower_bound(j).x; -} - -dd::pdd core::pdd_expr(const rational& c, lpvar j, u_dependency*& dep) { - dd::pdd r = m_pdd_manager.mk_val(c); - sbuffer vars; - vars.push_back(j); - u_dependency* zero_dep = dep; - while (!vars.empty()) { - j = vars.back(); - vars.pop_back(); - if (m_nla_settings.grobner_subs_fixed > 0 && var_is_fixed_to_zero(j)) { - r = m_pdd_manager.mk_val(val_of_fixed_var_with_deps(j, zero_dep)); - dep = zero_dep; - return r; - } - if (m_nla_settings.grobner_subs_fixed == 1 && var_is_fixed(j)) - r *= val_of_fixed_var_with_deps(j, dep); - else if (!is_monic_var(j)) - r *= m_pdd_manager.mk_var(j); - else - for (lpvar k : emons()[j].vars()) - vars.push_back(k); - } - return r; -} - -/** - \brief convert p == 0 into a solved form v == r, such that - v has bounds [lo, oo) iff r has bounds [lo', oo) - v has bounds (oo,hi] iff r has bounds (oo,hi'] - - The solved form allows the Grobner solver identify more bounds conflicts. - A bad leading term can miss bounds conflicts. - For example for x + y + z == 0 where x, y : [0, oo) and z : (oo,0] - we prefer to solve z == -x - y instead of x == -z - y - because the solution -z - y has neither an upper, nor a lower bound. - */ -bool core::is_solved(dd::pdd const& p, unsigned& v, dd::pdd& r) { - if (!p.is_linear()) - return false; - r = p; - unsigned num_lo = 0, num_hi = 0; - unsigned lo = 0, hi = 0; - rational lc, hc, c; - while (!r.is_val()) { - SASSERT(r.hi().is_val()); - v = r.var(); - rational val = r.hi().val(); - switch (m_lar_solver.get_column_type(v)) { - case lp::column_type::lower_bound: - if (val > 0) num_lo++, lo = v, lc = val; else num_hi++, hi = v, hc = val; - break; - case lp::column_type::upper_bound: - if (val < 0) num_lo++, lo = v, lc = val; else num_hi++, hi = v, hc = val; - break; - case lp::column_type::fixed: - case lp::column_type::boxed: - break; - default: - return false; - } - if (num_lo > 1 && num_hi > 1) - return false; - r = r.lo(); - } - if (num_lo == 1 && num_hi > 1) { - v = lo; - c = lc; - } - else if (num_hi == 1 && num_lo > 1) { - v = hi; - c = hc; - } - else - return false; - - r = c*m_pdd_manager.mk_var(v) - p; - if (c != 1) - r = r * (1/c); - return true; -} - -/** - \brief add an equality to grobner solver, convert it to solved form if available. -*/ -void core::add_eq_to_grobner(dd::pdd& p, u_dependency* dep) { - unsigned v; - dd::pdd q(m_pdd_manager); - m_pdd_grobner.simplify(p, dep); - if (is_solved(p, v, q)) - m_pdd_grobner.add_subst(v, q, dep); - else - m_pdd_grobner.add(p, dep); -} - - -void core::add_row_to_grobner(const vector> & row) { - u_dependency *dep = nullptr; - rational val; - dd::pdd sum = m_pdd_manager.mk_val(rational(0)); - for (const auto &p : row) - sum += pdd_expr(p.coeff(), p.var(), dep); - TRACE("grobner", print_row(row, tout) << " " << sum << "\n"); - add_eq_to_grobner(sum, dep); -} - - -void core::find_nl_cluster() { - prepare_rows_and_active_vars(); - svector q; - TRACE("grobner", for (lpvar j : m_to_refine) print_monic(emons()[j], tout) << "\n";); - - for (lpvar j : m_to_refine) - q.push_back(j); - - while (!q.empty()) { - lpvar j = q.back(); - q.pop_back(); - add_var_and_its_factors_to_q_and_collect_new_rows(j, q); - } - TRACE("grobner", tout << "vars in cluster: "; - for (lpvar j : active_var_set()) tout << "j" << j << " "; tout << "\n"; - display_matrix_of_m_rows(tout); - ); -} - -void core::prepare_rows_and_active_vars() { - m_rows.clear(); - m_rows.resize(m_lar_solver.row_count()); - clear_and_resize_active_var_set(); -} std::unordered_set core::get_vars_of_expr_with_opening_terms(const nex *e ) { @@ -2016,13 +1660,6 @@ std::unordered_set core::get_vars_of_expr_with_opening_terms(const nex *e return ret; } -void core::display_matrix_of_m_rows(std::ostream & out) const { - const auto& matrix = m_lar_solver.A_r(); - out << m_rows.size() << " rows" << "\n"; - out << "the matrix\n"; - for (const auto & r : matrix.m_rows) - print_row(r, out) << std::endl; -} void core::set_active_vars_weights(nex_creator& nc) { nc.set_number_of_vars(m_lar_solver.column_count()); diff --git a/src/math/lp/nla_core.h b/src/math/lp/nla_core.h index ee07c7db1..043a74655 100644 --- a/src/math/lp/nla_core.h +++ b/src/math/lp/nla_core.h @@ -458,6 +458,8 @@ public: } return lp::print_linear_combination_customized(v, [this](lpvar j) { return var_str(j); }, out); } + + void run_grobner(); void find_nl_cluster(); void prepare_rows_and_active_vars(); @@ -467,6 +469,7 @@ public: void set_active_vars_weights(nex_creator&); unsigned get_var_weight(lpvar) const; void add_row_to_grobner(const vector> & row); + void add_fixed_monic_to_grobner(unsigned j); bool is_solved(dd::pdd const& p, unsigned& v, dd::pdd& r); void add_eq_to_grobner(dd::pdd& p, u_dependency* dep); bool check_pdd_eq(const dd::solver::equation*); @@ -476,6 +479,7 @@ public: void configure_grobner(); bool influences_nl_var(lpvar) const; bool is_nl_var(lpvar) const; + bool is_used_in_monic(lpvar) const; void patch_monomials(); void patch_monomials_on_to_refine(); diff --git a/src/math/lp/nla_grobner.cpp b/src/math/lp/nla_grobner.cpp new file mode 100644 index 000000000..f674c0e01 --- /dev/null +++ b/src/math/lp/nla_grobner.cpp @@ -0,0 +1,393 @@ + /*++ +Copyright (c) 2017 Microsoft Corporation + +Module Name: + + nla_grobner.cpp + +Author: + Lev Nachmanson (levnach) + Nikolaj Bjorner (nbjorner) + +--*/ +#include "util/uint_set.h" +#include "math/lp/nla_core.h" +#include "math/lp/factorization_factory_imp.h" +#include "math/lp/nex.h" +#include "math/grobner/pdd_solver.h" +#include "math/dd/pdd_interval.h" +#include "math/dd/pdd_eval.h" + +namespace nla { + + void core::run_grobner() { + unsigned& quota = m_nla_settings.grobner_quota; + if (quota == 1) { + return; + } + clear_and_resize_active_var_set(); + find_nl_cluster(); + + lp_settings().stats().m_grobner_calls++; + configure_grobner(); + m_pdd_grobner.saturate(); + bool conflict = false; + unsigned n = m_pdd_grobner.number_of_conflicts_to_report(); + SASSERT(n > 0); + for (auto eq : m_pdd_grobner.equations()) { + if (check_pdd_eq(eq)) { + conflict = true; + if (--n == 0) + break; + } + } + TRACE("grobner", m_pdd_grobner.display(tout)); + if (conflict) { + IF_VERBOSE(2, verbose_stream() << "grobner conflict\n"); + return; + } + +#if 0 + // diagnostics: did we miss something + vector eqs; + for (auto eq : m_pdd_grobner.equations()) + eqs.push_back(eq->poly()); + m_nra.check(eqs); +#endif + +#if 0 + bool propagated = false; + for (auto eq : m_pdd_grobner.equations()) { + auto const& p = eq->poly(); + if (p.is_offset()) { + lpvar v = p.var(); + if (m_lar_solver.column_has_lower_bound(v) && + m_lar_solver.column_has_upper_bound(v)) + continue; + rational fixed_val = -p.lo().val(); + lp::explanation ex; + u_dependency_manager dm; + vector lv; + dm.linearize(eq->dep(), lv); + for (unsigned ci : lv) + ex.push_back(ci); + new_lemma lemma(*this, "pdd-eq"); + lemma &= ex; + lemma |= ineq(v, llc::EQ, fixed_val); + propagated = true; + } + } + if (propagated) + return; +#endif + + if (quota > 1) + quota--; + IF_VERBOSE(2, verbose_stream() << "grobner miss, quota " << quota << "\n"); + IF_VERBOSE(4, diagnose_pdd_miss(verbose_stream())); + + } + + void core::configure_grobner() { + m_pdd_grobner.reset(); + try { + set_level2var_for_grobner(); + TRACE("grobner", + tout << "base vars: "; + for (lpvar j : active_var_set()) + if (m_lar_solver.is_base(j)) + tout << "j" << j << " "; + tout << "\n"); + for (lpvar j : active_var_set()) { + if (m_lar_solver.is_base(j)) + add_row_to_grobner(m_lar_solver.basic2row(j)); + + if (is_monic_var(j) && var_is_fixed(j)) + add_fixed_monic_to_grobner(j); + } + } + catch (...) { + IF_VERBOSE(2, verbose_stream() << "pdd throw\n"); + return; + } + TRACE("grobner", m_pdd_grobner.display(tout)); + +#if 0 + IF_VERBOSE(2, m_pdd_grobner.display(verbose_stream())); + dd::pdd_eval eval(m_pdd_manager); + eval.var2val() = [&](unsigned j){ return val(j); }; + for (auto* e : m_pdd_grobner.equations()) { + dd::pdd p = e->poly(); + rational v = eval(p); + if (p.is_linear() && !eval(p).is_zero()) { + IF_VERBOSE(0, verbose_stream() << "violated linear constraint " << p << "\n"); + } + } +#endif + + struct dd::solver::config cfg; + cfg.m_max_steps = m_pdd_grobner.equations().size(); + cfg.m_max_simplified = m_nla_settings.grobner_max_simplified; + cfg.m_eqs_growth = m_nla_settings.grobner_eqs_growth; + cfg.m_expr_size_growth = m_nla_settings.grobner_expr_size_growth; + cfg.m_expr_degree_growth = m_nla_settings.grobner_expr_degree_growth; + cfg.m_number_of_conflicts_to_report = m_nla_settings.grobner_number_of_conflicts_to_report; + m_pdd_grobner.set(cfg); + m_pdd_grobner.adjust_cfg(); + m_pdd_manager.set_max_num_nodes(10000); // or something proportional to the number of initial nodes. + } + + std::ostream& core::diagnose_pdd_miss(std::ostream& out) { + + // m_pdd_grobner.display(out); + + dd::pdd_eval eval; + eval.var2val() = [&](unsigned j){ return val(j); }; + for (auto* e : m_pdd_grobner.equations()) { + dd::pdd p = e->poly(); + rational v = eval(p); + if (!v.is_zero()) { + out << p << " := " << v << "\n"; + } + } + + for (unsigned j = 0; j < m_lar_solver.number_of_vars(); ++j) { + if (m_lar_solver.column_has_lower_bound(j) || m_lar_solver.column_has_upper_bound(j)) { + out << j << ": ["; + if (m_lar_solver.column_has_lower_bound(j)) out << m_lar_solver.get_lower_bound(j); + out << ".."; + if (m_lar_solver.column_has_upper_bound(j)) out << m_lar_solver.get_upper_bound(j); + out << "]\n"; + } + } + return out; + } + + bool core::check_pdd_eq(const dd::solver::equation* e) { + auto& di = m_intervals.get_dep_intervals(); + dd::pdd_interval eval(di); + eval.var2interval() = [this](lpvar j, bool deps, scoped_dep_interval& a) { + if (deps) m_intervals.set_var_interval(j, a); + else m_intervals.set_var_interval(j, a); + }; + scoped_dep_interval i(di), i_wd(di); + eval.get_interval(e->poly(), i); + if (!di.separated_from_zero(i)) { + TRACE("grobner", m_pdd_grobner.display(tout << "not separated from 0 ", *e) << "\n"; + eval.get_interval_distributed(e->poly(), i); + tout << "separated from 0: " << di.separated_from_zero(i) << "\n"; + for (auto j : e->poly().free_vars()) { + scoped_dep_interval a(di); + m_intervals.set_var_interval(j, a); + m_intervals.display(tout << "j" << j << " ", a); tout << " "; + } + tout << "\n"); + + return false; + } + eval.get_interval(e->poly(), i_wd); + std::function f = [this](const lp::explanation& e) { + new_lemma lemma(*this, "pdd"); + lemma &= e; + }; + if (di.check_interval_for_conflict_on_zero(i_wd, e->dep(), f)) { + TRACE("grobner", m_pdd_grobner.display(tout << "conflict ", *e) << "\n"); + lp_settings().stats().m_grobner_conflicts++; + return true; + } + else { + TRACE("grobner", m_pdd_grobner.display(tout << "no conflict ", *e) << "\n"); + return false; + } + } + + void core::add_var_and_its_factors_to_q_and_collect_new_rows(lpvar j, svector & q) { + if (active_var_set_contains(j)) + return; + insert_to_active_var_set(j); + if (is_monic_var(j)) { + const monic& m = emons()[j]; + for (auto fcn : factorization_factory_imp(m, *this)) + for (const factor& fc: fcn) + q.push_back(var(fc)); + } + + if (var_is_fixed(j)) + return; + const auto& matrix = m_lar_solver.A_r(); + for (auto & s : matrix.m_columns[j]) { + unsigned row = s.var(); + if (m_rows.contains(row)) + continue; + m_rows.insert(row); + unsigned k = m_lar_solver.get_base_column_in_row(row); + if (m_lar_solver.column_is_free(k) && k != j) + continue; + CTRACE("grobner", matrix.m_rows[row].size() > m_nla_settings.grobner_row_length_limit, + tout << "ignore the row " << row << " with the size " << matrix.m_rows[row].size() << "\n";); + if (matrix.m_rows[row].size() > m_nla_settings.grobner_row_length_limit) + continue; + for (auto& rc : matrix.m_rows[row]) + add_var_and_its_factors_to_q_and_collect_new_rows(rc.var(), q); + } + + + } + + const rational& core::val_of_fixed_var_with_deps(lpvar j, u_dependency*& dep) { + unsigned lc, uc; + m_lar_solver.get_bound_constraint_witnesses_for_column(j, lc, uc); + dep = m_intervals.mk_join(dep, m_intervals.mk_leaf(lc)); + dep = m_intervals.mk_join(dep, m_intervals.mk_leaf(uc)); + return m_lar_solver.column_lower_bound(j).x; + } + + dd::pdd core::pdd_expr(const rational& c, lpvar j, u_dependency*& dep) { + dd::pdd r = m_pdd_manager.mk_val(c); + sbuffer vars; + vars.push_back(j); + u_dependency* zero_dep = dep; + while (!vars.empty()) { + j = vars.back(); + vars.pop_back(); + if (m_nla_settings.grobner_subs_fixed > 0 && var_is_fixed_to_zero(j)) { + r = m_pdd_manager.mk_val(val_of_fixed_var_with_deps(j, zero_dep)); + dep = zero_dep; + return r; + } + if (m_nla_settings.grobner_subs_fixed == 1 && var_is_fixed(j)) + r *= val_of_fixed_var_with_deps(j, dep); + else if (!is_monic_var(j)) + r *= m_pdd_manager.mk_var(j); + else + for (lpvar k : emons()[j].vars()) + vars.push_back(k); + } + return r; + } + + /** + \brief convert p == 0 into a solved form v == r, such that + v has bounds [lo, oo) iff r has bounds [lo', oo) + v has bounds (oo,hi] iff r has bounds (oo,hi'] + + The solved form allows the Grobner solver identify more bounds conflicts. + A bad leading term can miss bounds conflicts. + For example for x + y + z == 0 where x, y : [0, oo) and z : (oo,0] + we prefer to solve z == -x - y instead of x == -z - y + because the solution -z - y has neither an upper, nor a lower bound. + */ + bool core::is_solved(dd::pdd const& p, unsigned& v, dd::pdd& r) { + if (!p.is_linear()) + return false; + r = p; + unsigned num_lo = 0, num_hi = 0; + unsigned lo = 0, hi = 0; + rational lc, hc, c; + while (!r.is_val()) { + SASSERT(r.hi().is_val()); + v = r.var(); + rational val = r.hi().val(); + switch (m_lar_solver.get_column_type(v)) { + case lp::column_type::lower_bound: + if (val > 0) num_lo++, lo = v, lc = val; else num_hi++, hi = v, hc = val; + break; + case lp::column_type::upper_bound: + if (val < 0) num_lo++, lo = v, lc = val; else num_hi++, hi = v, hc = val; + break; + case lp::column_type::fixed: + case lp::column_type::boxed: + break; + default: + return false; + } + if (num_lo > 1 && num_hi > 1) + return false; + r = r.lo(); + } + if (num_lo == 1 && num_hi > 1) { + v = lo; + c = lc; + } + else if (num_hi == 1 && num_lo > 1) { + v = hi; + c = hc; + } + else + return false; + + r = c*m_pdd_manager.mk_var(v) - p; + if (c != 1) + r = r * (1/c); + return true; + } + + /** + \brief add an equality to grobner solver, convert it to solved form if available. + */ + void core::add_eq_to_grobner(dd::pdd& p, u_dependency* dep) { + unsigned v; + dd::pdd q(m_pdd_manager); + m_pdd_grobner.simplify(p, dep); + if (is_solved(p, v, q)) + m_pdd_grobner.add_subst(v, q, dep); + else + m_pdd_grobner.add(p, dep); + } + + void core::add_fixed_monic_to_grobner(unsigned j) { + u_dependency* dep = nullptr; + dd::pdd r = m_pdd_manager.mk_val(rational(1)); + for (lpvar k : emons()[j].vars()) + r *= pdd_expr(rational::one(), k, dep); + r -= val_of_fixed_var_with_deps(j, dep); + add_eq_to_grobner(r, dep); + } + + void core::add_row_to_grobner(const vector> & row) { + u_dependency *dep = nullptr; + rational val; + dd::pdd sum = m_pdd_manager.mk_val(rational(0)); + for (const auto &p : row) + sum += pdd_expr(p.coeff(), p.var(), dep); + TRACE("grobner", print_row(row, tout) << " " << sum << "\n"); + add_eq_to_grobner(sum, dep); + } + + + void core::find_nl_cluster() { + prepare_rows_and_active_vars(); + svector q; + TRACE("grobner", for (lpvar j : m_to_refine) print_monic(emons()[j], tout) << "\n";); + + for (lpvar j : m_to_refine) + q.push_back(j); + + while (!q.empty()) { + lpvar j = q.back(); + q.pop_back(); + add_var_and_its_factors_to_q_and_collect_new_rows(j, q); + } + TRACE("grobner", tout << "vars in cluster: "; + for (lpvar j : active_var_set()) tout << "j" << j << " "; tout << "\n"; + display_matrix_of_m_rows(tout); + ); + } + + void core::prepare_rows_and_active_vars() { + m_rows.clear(); + m_rows.resize(m_lar_solver.row_count()); + clear_and_resize_active_var_set(); + } + + + void core::display_matrix_of_m_rows(std::ostream & out) const { + const auto& matrix = m_lar_solver.A_r(); + out << m_rows.size() << " rows" << "\n"; + out << "the matrix\n"; + for (const auto & r : matrix.m_rows) + print_row(r, out) << std::endl; + } + + +} From 8e23af33d761d5268d6698efb63287ece02c0554 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 13 Jul 2022 14:20:21 -0700 Subject: [PATCH 098/125] fix build Signed-off-by: Nikolaj Bjorner --- src/smt/theory_seq.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/smt/theory_seq.cpp b/src/smt/theory_seq.cpp index 2d05ee9aa..00b607fbc 100644 --- a/src/smt/theory_seq.cpp +++ b/src/smt/theory_seq.cpp @@ -2793,19 +2793,19 @@ bool theory_seq::get_length(expr* e, rational& val) { todo.push_back(c); else if (m_util.str.is_string(c, s)) val += rational(s.length()); - else - continue; - len = mk_len(c); - if (!has_length(c)) { - add_axiom(mk_literal(m_autil.mk_ge(len, m_autil.mk_int(0)))); - TRACE("seq", tout << "literal has no length " << mk_pp(c, m) << "\n";); - return false; - } - else if (m_arith_value.get_value(len, val1) && !val1.is_neg()) - val += val1; - else { - TRACE("seq", tout << "length has not been internalized " << mk_pp(c, m) << "\n";); - return false; + else { + len = mk_len(c); + if (!has_length(c)) { + add_axiom(mk_literal(m_autil.mk_ge(len, m_autil.mk_int(0)))); + TRACE("seq", tout << "literal has no length " << mk_pp(c, m) << "\n";); + return false; + } + else if (m_arith_value.get_value(len, val1) && !val1.is_neg()) + val += val1; + else { + TRACE("seq", tout << "length has not been internalized " << mk_pp(c, m) << "\n";); + return false; + } } } CTRACE("seq", !val.is_int(), tout << "length is not an integer\n";); From a3eb9da1913217b0dc8da4a7480c948cde9976d4 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 13 Jul 2022 14:33:42 -0700 Subject: [PATCH 099/125] fix #6158 --- src/cmd_context/cmd_context.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp index ad150b674..e1d61d82c 100644 --- a/src/cmd_context/cmd_context.cpp +++ b/src/cmd_context/cmd_context.cpp @@ -594,6 +594,7 @@ void cmd_context::global_params_updated() { m_params.updt_params(); if (m_params.m_smtlib2_compliant) m_print_success = true; + set_produce_proofs(m_params.m_proof); if (m_solver) { params_ref p; if (!m_params.m_auto_config) From 1378e713ba9cadfb1cc36fa5d114e2f038c4516a Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 13 Jul 2022 14:37:04 -0700 Subject: [PATCH 100/125] fix #6157 --- src/smt/theory_bv.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_bv.cpp b/src/smt/theory_bv.cpp index dba402a6e..196f49281 100644 --- a/src/smt/theory_bv.cpp +++ b/src/smt/theory_bv.cpp @@ -667,8 +667,9 @@ namespace smt { mk_enode(n); theory_var v = ctx.get_enode(n)->get_th_var(get_id()); mk_bits(v); - mk_var(ctx.get_enode(n->get_arg(0))); - + enode* k = ctx.get_enode(n->get_arg(0)); + if (!is_attached_to_var(k)) + mk_var(k); if (!ctx.relevancy()) assert_int2bv_axiom(n); From dec87fe4d970d0b59480fe85773bbaf177566137 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 13 Jul 2022 16:19:12 -0700 Subject: [PATCH 101/125] fix issue with set-logic for eval_smtlib2_string --- src/cmd_context/cmd_context.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp index e1d61d82c..2f6d11601 100644 --- a/src/cmd_context/cmd_context.cpp +++ b/src/cmd_context/cmd_context.cpp @@ -824,15 +824,16 @@ bool cmd_context::set_logic(symbol const & s) { TRACE("cmd_context", tout << s << "\n";); if (has_logic()) throw cmd_exception("the logic has already been set"); - if (has_manager() && m_main_ctx) + if (has_assertions() && m_main_ctx) throw cmd_exception("logic must be set before initialization"); - if (!smt_logics::supported_logic(s)) { + if (!smt_logics::supported_logic(s)) return false; - } + m_logic = s; - if (smt_logics::logic_has_reals_only(s)) { + if (m_solver) + mk_solver(); + if (smt_logics::logic_has_reals_only(s)) m_numeral_as_real = true; - } return true; } From b253db2c0a11afef809645149e8e2b7fe7baf2e1 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 13 Jul 2022 16:20:03 -0700 Subject: [PATCH 102/125] redundant parenthesis --- src/smt/smt_model_checker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/smt/smt_model_checker.cpp b/src/smt/smt_model_checker.cpp index 90ef225a5..18a290d0a 100644 --- a/src/smt/smt_model_checker.cpp +++ b/src/smt/smt_model_checker.cpp @@ -453,7 +453,7 @@ namespace smt { TRACE("model_checker", tout << "model checker result: " << (num_failures == 0) << "\n";); m_max_cexs += m_params.m_mbqi_max_cexs; - if (num_failures == 0 && (!m_context->validate_model())) { + if (num_failures == 0 && !m_context->validate_model()) { num_failures = 1; // this time force expanding recursive function definitions // that are not forced true in the current model. From 894fb836e2c4cf47f23cc9e5ca809ddc547d1595 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 13 Jul 2022 17:26:56 -0700 Subject: [PATCH 103/125] fix build break (debug assertion) and isolate gomory functionality Signed-off-by: Nikolaj Bjorner --- src/cmd_context/cmd_context.cpp | 2 +- src/math/lp/nla_core.cpp | 56 ++------- src/math/lp/nla_core.h | 46 +++----- src/math/lp/nla_grobner.cpp | 194 +++++++++++++++++++++----------- src/math/lp/nla_grobner.h | 49 ++++++++ 5 files changed, 208 insertions(+), 139 deletions(-) create mode 100644 src/math/lp/nla_grobner.h diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp index 2f6d11601..baaf483cd 100644 --- a/src/cmd_context/cmd_context.cpp +++ b/src/cmd_context/cmd_context.cpp @@ -619,7 +619,7 @@ void cmd_context::set_produce_unsat_cores(bool f) { } void cmd_context::set_produce_proofs(bool f) { - SASSERT(!has_assertions()); + SASSERT(!has_assertions() || m_params.m_proof == f); if (has_manager()) m().toggle_proof_mode(f ? PGM_ENABLED : PGM_DISABLED); m_params.m_proof = f; diff --git a/src/math/lp/nla_core.cpp b/src/math/lp/nla_core.cpp index 41482f3c2..7fb34b5a3 100644 --- a/src/math/lp/nla_core.cpp +++ b/src/math/lp/nla_core.cpp @@ -31,8 +31,7 @@ core::core(lp::lar_solver& s, reslimit & lim) : m_intervals(this, lim), m_monomial_bounds(this), m_horner(this), - m_pdd_manager(s.number_of_vars()), - m_pdd_grobner(lim, m_pdd_manager), + m_grobner(this), m_emons(m_evars), m_reslim(lim), m_use_nra_model(false), @@ -1506,7 +1505,7 @@ lbool core::check(vector& l_vec) { m_horner.horner_lemmas(); if (l_vec.empty() && !done() && need_run_grobner()) - run_grobner(); + m_grobner(); if (l_vec.empty() && !done()) m_basics.basic_lemma(true); @@ -1661,51 +1660,15 @@ std::unordered_set core::get_vars_of_expr_with_opening_terms(const nex *e } -void core::set_active_vars_weights(nex_creator& nc) { - nc.set_number_of_vars(m_lar_solver.column_count()); - for (lpvar j : active_var_set()) - nc.set_var_weight(j, get_var_weight(j)); +bool core::is_nl_var(lpvar j) const { + return is_monic_var(j) || m_emons.is_used_in_monic(j); } -void core::set_level2var_for_grobner() { - unsigned n = m_lar_solver.column_count(); - unsigned_vector sorted_vars(n), weighted_vars(n); - for (unsigned j = 0; j < n; j++) { - sorted_vars[j] = j; - weighted_vars[j] = get_var_weight(j); - } -#if 1 - // potential update to weights - for (unsigned j = 0; j < n; j++) { - if (is_monic_var(j) && m_to_refine.contains(j)) { - for (lpvar k : m_emons[j].vars()) { - weighted_vars[k] += 6; - } - } - } -#endif - - std::sort(sorted_vars.begin(), sorted_vars.end(), [&](unsigned a, unsigned b) { - unsigned wa = weighted_vars[a]; - unsigned wb = weighted_vars[b]; - return wa < wb || (wa == wb && a < b); }); - - unsigned_vector l2v(n); - for (unsigned j = 0; j < n; j++) - l2v[j] = sorted_vars[j]; - - m_pdd_manager.reset(l2v); - - TRACE("grobner", - for (auto v : sorted_vars) - tout << "j" << v << " w:" << weighted_vars[v] << " "; - tout << "\n"); -} unsigned core::get_var_weight(lpvar j) const { unsigned k; switch (m_lar_solver.get_column_type(j)) { - + case lp::column_type::fixed: k = 0; break; @@ -1725,14 +1688,17 @@ unsigned core::get_var_weight(lpvar j) const { } if (is_monic_var(j)) { k++; - if (m_to_refine.contains(j)) + if (m_to_refine.contains(j)) k++; } return k; } -bool core::is_nl_var(lpvar j) const { - return is_monic_var(j) || m_emons.is_used_in_monic(j); + +void core::set_active_vars_weights(nex_creator& nc) { + nc.set_number_of_vars(m_lar_solver.column_count()); + for (lpvar j : active_var_set()) + nc.set_var_weight(j, get_var_weight(j)); } bool core::influences_nl_var(lpvar j) const { diff --git a/src/math/lp/nla_core.h b/src/math/lp/nla_core.h index 043a74655..5c44f53a6 100644 --- a/src/math/lp/nla_core.h +++ b/src/math/lp/nla_core.h @@ -18,13 +18,13 @@ #include "math/lp/nla_basics_lemmas.h" #include "math/lp/nla_order_lemmas.h" #include "math/lp/nla_monotone_lemmas.h" +#include "math/lp/nla_grobner.h" #include "math/lp/emonics.h" #include "math/lp/nla_settings.h" #include "math/lp/nex.h" #include "math/lp/horner.h" #include "math/lp/monomial_bounds.h" #include "math/lp/nla_intervals.h" -#include "math/grobner/pdd_solver.h" #include "nlsat/nlsat_solver.h" @@ -139,6 +139,9 @@ struct pp_factorization { }; class core { + friend class new_lemma; + friend class grobner; + struct stats { unsigned m_nla_explanations; unsigned m_nla_lemmas; @@ -148,11 +151,11 @@ class core { memset(this, 0, sizeof(*this)); } }; - stats m_stats; - friend class new_lemma; - unsigned m_nlsat_delay { 50 }; - unsigned m_nlsat_fails { 0 }; + stats m_stats; + unsigned m_nlsat_delay = 50; + unsigned m_nlsat_fails = 0; + bool should_run_bounded_nlsat(); lbool bounded_nlsat(); public: @@ -168,13 +171,12 @@ public: monomial_bounds m_monomial_bounds; horner m_horner; nla_settings m_nla_settings; - dd::pdd_manager m_pdd_manager; - dd::solver m_pdd_grobner; + grobner m_grobner; private: emonics m_emons; svector m_add_buffer; mutable lp::u_set m_active_var_set; - lp::u_set m_rows; + reslimit m_nra_lim; public: reslimit& m_reslim; @@ -205,6 +207,8 @@ public: m_active_var_set.resize(m_lar_solver.number_of_vars()); } + unsigned get_var_weight(lpvar) const; + reslimit& reslim() { return m_reslim; } emonics& emons() { return m_emons; } const emonics& emons() const { return m_emons; } @@ -249,6 +253,9 @@ public: bool need_run_grobner() const { return m_nla_settings.run_grobner && lp_settings().stats().m_nla_calls % m_nla_settings.grobner_frequency == 0; } + + void set_active_vars_weights(nex_creator&); + std::unordered_set get_vars_of_expr_with_opening_terms(const nex* e); void incremental_linearization(bool); @@ -450,33 +457,16 @@ public: lpvar map_to_root(lpvar) const; std::ostream& print_terms(std::ostream&) const; std::ostream& print_term(const lp::lar_term&, std::ostream&) const; + template - std::ostream& print_row(const T & row , std::ostream& out) const { + std::ostream& print_row(const T& row, std::ostream& out) const { vector> v; for (auto p : row) { v.push_back(std::make_pair(p.coeff(), p.var())); } - return lp::print_linear_combination_customized(v, [this](lpvar j) { return var_str(j); }, out); + return lp::print_linear_combination_customized(v, [this](lpvar j) { return var_str(j); }, out); } - - void run_grobner(); - void find_nl_cluster(); - void prepare_rows_and_active_vars(); - void add_var_and_its_factors_to_q_and_collect_new_rows(lpvar j, svector& q); - std::unordered_set get_vars_of_expr_with_opening_terms(const nex* e); - void display_matrix_of_m_rows(std::ostream & out) const; - void set_active_vars_weights(nex_creator&); - unsigned get_var_weight(lpvar) const; - void add_row_to_grobner(const vector> & row); - void add_fixed_monic_to_grobner(unsigned j); - bool is_solved(dd::pdd const& p, unsigned& v, dd::pdd& r); - void add_eq_to_grobner(dd::pdd& p, u_dependency* dep); - bool check_pdd_eq(const dd::solver::equation*); - const rational& val_of_fixed_var_with_deps(lpvar j, u_dependency*& dep); - dd::pdd pdd_expr(const rational& c, lpvar j, u_dependency*&); - void set_level2var_for_grobner(); - void configure_grobner(); bool influences_nl_var(lpvar) const; bool is_nl_var(lpvar) const; diff --git a/src/math/lp/nla_grobner.cpp b/src/math/lp/nla_grobner.cpp index f674c0e01..c5d1e7c2d 100644 --- a/src/math/lp/nla_grobner.cpp +++ b/src/math/lp/nla_grobner.cpp @@ -20,16 +20,28 @@ Author: namespace nla { - void core::run_grobner() { - unsigned& quota = m_nla_settings.grobner_quota; + grobner::grobner(core* c): + common(c), + m_pdd_manager(m_core.m_lar_solver.number_of_vars()), + m_pdd_grobner(m_core.m_reslim, m_pdd_manager), + m_lar_solver(m_core.m_lar_solver) + + {} + + lp::lp_settings& grobner::lp_settings() { + return c().lp_settings(); + } + + void grobner::operator()() { + unsigned& quota = c().m_nla_settings.grobner_quota; if (quota == 1) { return; } - clear_and_resize_active_var_set(); + c().clear_and_resize_active_var_set(); find_nl_cluster(); - lp_settings().stats().m_grobner_calls++; - configure_grobner(); + c().lp_settings().stats().m_grobner_calls++; + configure(); m_pdd_grobner.saturate(); bool conflict = false; unsigned n = m_pdd_grobner.number_of_conflicts_to_report(); @@ -47,6 +59,20 @@ namespace nla { return; } + if (propagate_bounds()) + return; + + if (propagate_eqs()) + return; + + if (quota > 1) + quota--; + + + IF_VERBOSE(2, verbose_stream() << "grobner miss, quota " << quota << "\n"); + IF_VERBOSE(4, diagnose_pdd_miss(verbose_stream())); + + #if 0 // diagnostics: did we miss something vector eqs; @@ -81,29 +107,33 @@ namespace nla { return; #endif - if (quota > 1) - quota--; - IF_VERBOSE(2, verbose_stream() << "grobner miss, quota " << quota << "\n"); - IF_VERBOSE(4, diagnose_pdd_miss(verbose_stream())); } - void core::configure_grobner() { + bool grobner::propagate_bounds() { + return false; + } + + bool grobner::propagate_eqs() { + return false; + } + + void grobner::configure() { m_pdd_grobner.reset(); try { - set_level2var_for_grobner(); + set_level2var(); TRACE("grobner", tout << "base vars: "; - for (lpvar j : active_var_set()) - if (m_lar_solver.is_base(j)) + for (lpvar j : c().active_var_set()) + if (c().m_lar_solver.is_base(j)) tout << "j" << j << " "; tout << "\n"); - for (lpvar j : active_var_set()) { - if (m_lar_solver.is_base(j)) - add_row_to_grobner(m_lar_solver.basic2row(j)); + for (lpvar j : c().active_var_set()) { + if (c().m_lar_solver.is_base(j)) + add_row(c().m_lar_solver.basic2row(j)); - if (is_monic_var(j) && var_is_fixed(j)) - add_fixed_monic_to_grobner(j); + if (c().is_monic_var(j) && c().var_is_fixed(j)) + add_fixed_monic(j); } } catch (...) { @@ -127,17 +157,17 @@ namespace nla { struct dd::solver::config cfg; cfg.m_max_steps = m_pdd_grobner.equations().size(); - cfg.m_max_simplified = m_nla_settings.grobner_max_simplified; - cfg.m_eqs_growth = m_nla_settings.grobner_eqs_growth; - cfg.m_expr_size_growth = m_nla_settings.grobner_expr_size_growth; - cfg.m_expr_degree_growth = m_nla_settings.grobner_expr_degree_growth; - cfg.m_number_of_conflicts_to_report = m_nla_settings.grobner_number_of_conflicts_to_report; + cfg.m_max_simplified = c().m_nla_settings.grobner_max_simplified; + cfg.m_eqs_growth = c().m_nla_settings.grobner_eqs_growth; + cfg.m_expr_size_growth = c().m_nla_settings.grobner_expr_size_growth; + cfg.m_expr_degree_growth = c().m_nla_settings.grobner_expr_degree_growth; + cfg.m_number_of_conflicts_to_report = c().m_nla_settings.grobner_number_of_conflicts_to_report; m_pdd_grobner.set(cfg); m_pdd_grobner.adjust_cfg(); m_pdd_manager.set_max_num_nodes(10000); // or something proportional to the number of initial nodes. } - std::ostream& core::diagnose_pdd_miss(std::ostream& out) { + std::ostream& grobner::diagnose_pdd_miss(std::ostream& out) { // m_pdd_grobner.display(out); @@ -163,12 +193,12 @@ namespace nla { return out; } - bool core::check_pdd_eq(const dd::solver::equation* e) { - auto& di = m_intervals.get_dep_intervals(); + bool grobner::check_pdd_eq(const dd::solver::equation* e) { + auto& di = c().m_intervals.get_dep_intervals(); dd::pdd_interval eval(di); eval.var2interval() = [this](lpvar j, bool deps, scoped_dep_interval& a) { - if (deps) m_intervals.set_var_interval(j, a); - else m_intervals.set_var_interval(j, a); + if (deps) c().m_intervals.set_var_interval(j, a); + else c().m_intervals.set_var_interval(j, a); }; scoped_dep_interval i(di), i_wd(di); eval.get_interval(e->poly(), i); @@ -178,8 +208,8 @@ namespace nla { tout << "separated from 0: " << di.separated_from_zero(i) << "\n"; for (auto j : e->poly().free_vars()) { scoped_dep_interval a(di); - m_intervals.set_var_interval(j, a); - m_intervals.display(tout << "j" << j << " ", a); tout << " "; + c().m_intervals.set_var_interval(j, a); + c().m_intervals.display(tout << "j" << j << " ", a); tout << " "; } tout << "\n"); @@ -187,7 +217,7 @@ namespace nla { } eval.get_interval(e->poly(), i_wd); std::function f = [this](const lp::explanation& e) { - new_lemma lemma(*this, "pdd"); + new_lemma lemma(m_core, "pdd"); lemma &= e; }; if (di.check_interval_for_conflict_on_zero(i_wd, e->dep(), f)) { @@ -201,18 +231,18 @@ namespace nla { } } - void core::add_var_and_its_factors_to_q_and_collect_new_rows(lpvar j, svector & q) { - if (active_var_set_contains(j)) + void grobner::add_var_and_its_factors_to_q_and_collect_new_rows(lpvar j, svector & q) { + if (c().active_var_set_contains(j)) return; - insert_to_active_var_set(j); - if (is_monic_var(j)) { - const monic& m = emons()[j]; - for (auto fcn : factorization_factory_imp(m, *this)) + c().insert_to_active_var_set(j); + if (c().is_monic_var(j)) { + const monic& m = c().emons()[j]; + for (auto fcn : factorization_factory_imp(m, m_core)) for (const factor& fc: fcn) q.push_back(var(fc)); } - if (var_is_fixed(j)) + if (c().var_is_fixed(j)) return; const auto& matrix = m_lar_solver.A_r(); for (auto & s : matrix.m_columns[j]) { @@ -223,9 +253,9 @@ namespace nla { unsigned k = m_lar_solver.get_base_column_in_row(row); if (m_lar_solver.column_is_free(k) && k != j) continue; - CTRACE("grobner", matrix.m_rows[row].size() > m_nla_settings.grobner_row_length_limit, + CTRACE("grobner", matrix.m_rows[row].size() > c().m_nla_settings.grobner_row_length_limit, tout << "ignore the row " << row << " with the size " << matrix.m_rows[row].size() << "\n";); - if (matrix.m_rows[row].size() > m_nla_settings.grobner_row_length_limit) + if (matrix.m_rows[row].size() > c().m_nla_settings.grobner_row_length_limit) continue; for (auto& rc : matrix.m_rows[row]) add_var_and_its_factors_to_q_and_collect_new_rows(rc.var(), q); @@ -234,33 +264,33 @@ namespace nla { } - const rational& core::val_of_fixed_var_with_deps(lpvar j, u_dependency*& dep) { + const rational& grobner::val_of_fixed_var_with_deps(lpvar j, u_dependency*& dep) { unsigned lc, uc; m_lar_solver.get_bound_constraint_witnesses_for_column(j, lc, uc); - dep = m_intervals.mk_join(dep, m_intervals.mk_leaf(lc)); - dep = m_intervals.mk_join(dep, m_intervals.mk_leaf(uc)); + dep = c().m_intervals.mk_join(dep, c().m_intervals.mk_leaf(lc)); + dep = c().m_intervals.mk_join(dep, c().m_intervals.mk_leaf(uc)); return m_lar_solver.column_lower_bound(j).x; } - dd::pdd core::pdd_expr(const rational& c, lpvar j, u_dependency*& dep) { - dd::pdd r = m_pdd_manager.mk_val(c); + dd::pdd grobner::pdd_expr(const rational& coeff, lpvar j, u_dependency*& dep) { + dd::pdd r = m_pdd_manager.mk_val(coeff); sbuffer vars; vars.push_back(j); u_dependency* zero_dep = dep; while (!vars.empty()) { j = vars.back(); vars.pop_back(); - if (m_nla_settings.grobner_subs_fixed > 0 && var_is_fixed_to_zero(j)) { + if (c().m_nla_settings.grobner_subs_fixed > 0 && c().var_is_fixed_to_zero(j)) { r = m_pdd_manager.mk_val(val_of_fixed_var_with_deps(j, zero_dep)); dep = zero_dep; return r; } - if (m_nla_settings.grobner_subs_fixed == 1 && var_is_fixed(j)) + if (c().m_nla_settings.grobner_subs_fixed == 1 && c().var_is_fixed(j)) r *= val_of_fixed_var_with_deps(j, dep); - else if (!is_monic_var(j)) + else if (!c().is_monic_var(j)) r *= m_pdd_manager.mk_var(j); else - for (lpvar k : emons()[j].vars()) + for (lpvar k : c().emons()[j].vars()) vars.push_back(k); } return r; @@ -277,7 +307,7 @@ namespace nla { we prefer to solve z == -x - y instead of x == -z - y because the solution -z - y has neither an upper, nor a lower bound. */ - bool core::is_solved(dd::pdd const& p, unsigned& v, dd::pdd& r) { + bool grobner::is_solved(dd::pdd const& p, unsigned& v, dd::pdd& r) { if (!p.is_linear()) return false; r = p; @@ -325,7 +355,7 @@ namespace nla { /** \brief add an equality to grobner solver, convert it to solved form if available. */ - void core::add_eq_to_grobner(dd::pdd& p, u_dependency* dep) { + void grobner::add_eq(dd::pdd& p, u_dependency* dep) { unsigned v; dd::pdd q(m_pdd_manager); m_pdd_grobner.simplify(p, dep); @@ -335,32 +365,32 @@ namespace nla { m_pdd_grobner.add(p, dep); } - void core::add_fixed_monic_to_grobner(unsigned j) { + void grobner::add_fixed_monic(unsigned j) { u_dependency* dep = nullptr; dd::pdd r = m_pdd_manager.mk_val(rational(1)); - for (lpvar k : emons()[j].vars()) + for (lpvar k : c().emons()[j].vars()) r *= pdd_expr(rational::one(), k, dep); r -= val_of_fixed_var_with_deps(j, dep); - add_eq_to_grobner(r, dep); + add_eq(r, dep); } - void core::add_row_to_grobner(const vector> & row) { + void grobner::add_row(const vector> & row) { u_dependency *dep = nullptr; rational val; dd::pdd sum = m_pdd_manager.mk_val(rational(0)); for (const auto &p : row) sum += pdd_expr(p.coeff(), p.var(), dep); - TRACE("grobner", print_row(row, tout) << " " << sum << "\n"); - add_eq_to_grobner(sum, dep); + TRACE("grobner", c().print_row(row, tout) << " " << sum << "\n"); + add_eq(sum, dep); } - void core::find_nl_cluster() { + void grobner::find_nl_cluster() { prepare_rows_and_active_vars(); svector q; - TRACE("grobner", for (lpvar j : m_to_refine) print_monic(emons()[j], tout) << "\n";); + TRACE("grobner", for (lpvar j : c().m_to_refine) print_monic(c().emons()[j], tout) << "\n";); - for (lpvar j : m_to_refine) + for (lpvar j : c().m_to_refine) q.push_back(j); while (!q.empty()) { @@ -369,25 +399,59 @@ namespace nla { add_var_and_its_factors_to_q_and_collect_new_rows(j, q); } TRACE("grobner", tout << "vars in cluster: "; - for (lpvar j : active_var_set()) tout << "j" << j << " "; tout << "\n"; + for (lpvar j : c().active_var_set()) tout << "j" << j << " "; tout << "\n"; display_matrix_of_m_rows(tout); ); } - void core::prepare_rows_and_active_vars() { + void grobner::prepare_rows_and_active_vars() { m_rows.clear(); m_rows.resize(m_lar_solver.row_count()); - clear_and_resize_active_var_set(); + c().clear_and_resize_active_var_set(); } - void core::display_matrix_of_m_rows(std::ostream & out) const { + void grobner::display_matrix_of_m_rows(std::ostream & out) const { const auto& matrix = m_lar_solver.A_r(); out << m_rows.size() << " rows" << "\n"; out << "the matrix\n"; for (const auto & r : matrix.m_rows) - print_row(r, out) << std::endl; + c().print_row(r, out) << std::endl; } + void grobner::set_level2var() { + unsigned n = m_lar_solver.column_count(); + unsigned_vector sorted_vars(n), weighted_vars(n); + for (unsigned j = 0; j < n; j++) { + sorted_vars[j] = j; + weighted_vars[j] = c().get_var_weight(j); + } +#if 1 + // potential update to weights + for (unsigned j = 0; j < n; j++) { + if (c().is_monic_var(j) && c().m_to_refine.contains(j)) { + for (lpvar k : c().m_emons[j].vars()) { + weighted_vars[k] += 6; + } + } + } +#endif + + std::sort(sorted_vars.begin(), sorted_vars.end(), [&](unsigned a, unsigned b) { + unsigned wa = weighted_vars[a]; + unsigned wb = weighted_vars[b]; + return wa < wb || (wa == wb && a < b); }); + + unsigned_vector l2v(n); + for (unsigned j = 0; j < n; j++) + l2v[j] = sorted_vars[j]; + + m_pdd_manager.reset(l2v); + + TRACE("grobner", + for (auto v : sorted_vars) + tout << "j" << v << " w:" << weighted_vars[v] << " "; + tout << "\n"); + } } diff --git a/src/math/lp/nla_grobner.h b/src/math/lp/nla_grobner.h new file mode 100644 index 000000000..a9b284a74 --- /dev/null +++ b/src/math/lp/nla_grobner.h @@ -0,0 +1,49 @@ +/*++ + Copyright (c) 2017 Microsoft Corporation + + Author: + Nikolaj Bjorner (nbjorner) + Lev Nachmanson (levnach) + + --*/ +#pragma once + +#include "math/lp/nla_common.h" +#include "math/lp/nla_intervals.h" +#include "math/lp/nex.h" +#include "math/lp/cross_nested.h" +#include "math/lp/u_set.h" +#include "math/grobner/pdd_solver.h" + +namespace nla { + class core; + + class grobner : common { + dd::pdd_manager m_pdd_manager; + dd::solver m_pdd_grobner; + lp::lar_solver& m_lar_solver; + lp::u_set m_rows; + + bool propagate_bounds(); + bool propagate_eqs(); + lp::lp_settings& lp_settings(); + void find_nl_cluster(); + void prepare_rows_and_active_vars(); + void add_var_and_its_factors_to_q_and_collect_new_rows(lpvar j, svector& q); + void display_matrix_of_m_rows(std::ostream& out) const; + void add_row(const vector>& row); + void add_fixed_monic(unsigned j); + bool is_solved(dd::pdd const& p, unsigned& v, dd::pdd& r); + void add_eq(dd::pdd& p, u_dependency* dep); + bool check_pdd_eq(const dd::solver::equation*); + const rational& val_of_fixed_var_with_deps(lpvar j, u_dependency*& dep); + dd::pdd pdd_expr(const rational& c, lpvar j, u_dependency*&); + void set_level2var(); + void configure(); + std::ostream& diagnose_pdd_miss(std::ostream& out); + + public: + grobner(core *core); + void operator()(); + }; +} From 981c82c8147d0b15d7791ea12d9b63979e365b66 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 13 Jul 2022 18:11:18 -0700 Subject: [PATCH 104/125] fix initialization order --- src/math/lp/nla_core.cpp | 3451 ++++++++++++++++++----------------- src/math/lp/nla_core.h | 34 +- src/math/lp/nla_grobner.cpp | 112 +- src/math/lp/nla_grobner.h | 12 +- 4 files changed, 1812 insertions(+), 1797 deletions(-) diff --git a/src/math/lp/nla_core.cpp b/src/math/lp/nla_core.cpp index 7fb34b5a3..7c9ac73f7 100644 --- a/src/math/lp/nla_core.cpp +++ b/src/math/lp/nla_core.cpp @@ -1,1725 +1,1726 @@ - /*++ -Copyright (c) 2017 Microsoft Corporation - -Module Name: - - nla_core.cpp - -Author: - Lev Nachmanson (levnach) - Nikolaj Bjorner (nbjorner) - ---*/ -#include "util/uint_set.h" -#include "math/lp/nla_core.h" -#include "math/lp/factorization_factory_imp.h" -#include "math/lp/nex.h" -#include "math/grobner/pdd_solver.h" -#include "math/dd/pdd_interval.h" -#include "math/dd/pdd_eval.h" -namespace nla { - -typedef lp::lar_term term; - -core::core(lp::lar_solver& s, reslimit & lim) : - m_evars(), - m_lar_solver(s), - m_tangents(this), - m_basics(this), - m_order(this), - m_monotone(this), - m_intervals(this, lim), - m_monomial_bounds(this), - m_horner(this), - m_grobner(this), - m_emons(m_evars), - m_reslim(lim), - m_use_nra_model(false), - m_nra(s, m_nra_lim, *this) -{ - m_nlsat_delay = lp_settings().nlsat_delay(); -} - -bool core::compare_holds(const rational& ls, llc cmp, const rational& rs) const { - switch(cmp) { - case llc::LE: return ls <= rs; - case llc::LT: return ls < rs; - case llc::GE: return ls >= rs; - case llc::GT: return ls > rs; - case llc::EQ: return ls == rs; - case llc::NE: return ls != rs; - default: SASSERT(false); - }; - - return false; -} - -rational core::value(const lp::lar_term& r) const { - rational ret(0); - for (lp::lar_term::ival t : r) { - ret += t.coeff() * val(t.column()); - } - return ret; -} - -lp::lar_term core::subs_terms_to_columns(const lp::lar_term& t) const { - lp::lar_term r; - for (lp::lar_term::ival p : t) { - lpvar j = p.column(); - if (lp::tv::is_term(j)) - j = m_lar_solver.map_term_index_to_column_index(j); - r.add_monomial(p.coeff(), j); - } - return r; -} - -bool core::ineq_holds(const ineq& n) const { - return compare_holds(value(n.term()), n.cmp(), n.rs()); -} - -bool core::lemma_holds(const lemma& l) const { - for(const ineq &i : l.ineqs()) { - if (ineq_holds(i)) - return true; - } - return false; -} - -lpvar core::map_to_root(lpvar j) const { - return m_evars.find(j).var(); -} - -svector core::sorted_rvars(const factor& f) const { - if (f.is_var()) { - svector r; r.push_back(map_to_root(f.var())); - return r; - } - return m_emons[f.var()].rvars(); -} - -// the value of the factor is equal to the value of the variable multiplied -// by the canonize_sign -bool core::canonize_sign(const factor& f) const { - return f.sign() ^ (f.is_var()? canonize_sign(f.var()) : canonize_sign(m_emons[f.var()])); -} - -bool core::canonize_sign(lpvar j) const { - return m_evars.find(j).sign(); -} - -bool core::canonize_sign_is_correct(const monic& m) const { - bool r = false; - for (lpvar j : m.vars()) { - r ^= canonize_sign(j); - } - return r == m.rsign(); -} - -bool core::canonize_sign(const monic& m) const { - SASSERT(canonize_sign_is_correct(m)); - return m.rsign(); -} - -bool core::canonize_sign(const factorization& f) const { - bool r = false; - for (const factor & a : f) { - r ^= canonize_sign(a); - } - return r; -} - -void core::add_monic(lpvar v, unsigned sz, lpvar const* vs) { - m_add_buffer.resize(sz); - for (unsigned i = 0; i < sz; i++) { - lpvar j = vs[i]; - if (lp::tv::is_term(j)) - j = m_lar_solver.map_term_index_to_column_index(j); - m_add_buffer[i] = j; - } - m_emons.add(v, m_add_buffer); -} - -void core::push() { - TRACE("nla_solver_verbose", tout << "\n";); - m_emons.push(); -} - - -void core::pop(unsigned n) { - TRACE("nla_solver_verbose", tout << "n = " << n << "\n";); - m_emons.pop(n); - SASSERT(elists_are_consistent(false)); -} - -rational core::product_value(const monic& m) const { - rational r(1); - for (auto j : m.vars()) { - r *= m_lar_solver.get_column_value(j).x; - } - return r; -} - -// return true iff the monic value is equal to the product of the values of the factors -bool core::check_monic(const monic& m) const { - SASSERT((!m_lar_solver.column_is_int(m.var())) || m_lar_solver.get_column_value(m.var()).is_int()); - bool ret = product_value(m) == m_lar_solver.get_column_value(m.var()).x; - CTRACE("nla_solver_check_monic", !ret, print_monic(m, tout) << '\n';); - return ret; -} - - -template -std::ostream& core::print_product(const T & m, std::ostream& out) const { - bool first = true; - for (lpvar v : m) { - if (!first) out << "*"; else first = false; - if (lp_settings().print_external_var_name()) - out << "(" << m_lar_solver.get_variable_name(v) << "=" << val(v) << ")"; - else - out << "(j" << v << " = " << val(v) << ")"; - - } - return out; -} -template -std::string core::product_indices_str(const T & m) const { - std::stringstream out; - bool first = true; - for (lpvar v : m) { - if (!first) - out << "*"; - else - first = false; - out << "j" << v;; - } - return out.str(); -} - -std::ostream & core::print_factor(const factor& f, std::ostream& out) const { - if (f.sign()) - out << "- "; - if (f.is_var()) { - out << "VAR, " << pp(f.var()); - } else { - out << "MON, v" << m_emons[f.var()] << " = "; - print_product(m_emons[f.var()].rvars(), out); - } - out << "\n"; - return out; -} - -std::ostream & core::print_factor_with_vars(const factor& f, std::ostream& out) const { - if (f.is_var()) { - out << pp(f.var()); - } - else { - out << " MON = " << pp_mon_with_vars(*this, m_emons[f.var()]); - } - return out; -} - -std::ostream& core::print_monic(const monic& m, std::ostream& out) const { - if (lp_settings().print_external_var_name()) - out << "([" << m.var() << "] = " << m_lar_solver.get_variable_name(m.var()) << " = " << val(m.var()) << " = "; - else - out << "(j" << m.var() << " = " << val(m.var()) << " = "; - print_product(m.vars(), out) << ")\n"; - return out; -} - - -std::ostream& core::print_bfc(const factorization& m, std::ostream& out) const { - SASSERT(m.size() == 2); - out << "( x = " << pp(m[0]) << "* y = " << pp(m[1]) << ")"; - return out; -} - -std::ostream& core::print_monic_with_vars(lpvar v, std::ostream& out) const { - return print_monic_with_vars(m_emons[v], out); -} -template -std::ostream& core::print_product_with_vars(const T& m, std::ostream& out) const { - print_product(m, out) << "\n"; - for (unsigned k = 0; k < m.size(); k++) { - print_var(m[k], out); - } - return out; -} - -std::ostream& core::print_monic_with_vars(const monic& m, std::ostream& out) const { - out << "[" << pp(m.var()) << "]\n"; - out << "vars:"; print_product_with_vars(m.vars(), out) << "\n"; - if (m.vars() == m.rvars()) - out << "same rvars, and m.rsign = " << m.rsign() << " of course\n"; - else { - out << "rvars:"; print_product_with_vars(m.rvars(), out) << "\n"; - out << "rsign:" << m.rsign() << "\n"; - } - return out; -} - -std::ostream& core::print_explanation(const lp::explanation& exp, std::ostream& out) const { - out << "expl: "; - unsigned i = 0; - for (auto p : exp) { - out << "(" << p.ci() << ")"; - m_lar_solver.constraints().display(out, [this](lpvar j) { return var_str(j);}, p.ci()); - if (++i < exp.size()) - out << " "; - } - return out; -} - -bool core::explain_upper_bound(const lp::lar_term& t, const rational& rs, lp::explanation& e) const { - rational b(0); // the bound - for (lp::lar_term::ival p : t) { - rational pb; - if (explain_coeff_upper_bound(p, pb, e)) { - b += pb; - } else { - e.clear(); - return false; - } - } - if (b > rs ) { - e.clear(); - return false; - } - return true; -} -bool core::explain_lower_bound(const lp::lar_term& t, const rational& rs, lp::explanation& e) const { - rational b(0); // the bound - for (lp::lar_term::ival p : t) { - rational pb; - if (explain_coeff_lower_bound(p, pb, e)) { - b += pb; - } else { - e.clear(); - return false; - } - } - if (b < rs ) { - e.clear(); - return false; - } - return true; -} - -bool core::explain_coeff_lower_bound(const lp::lar_term::ival& p, rational& bound, lp::explanation& e) const { - const rational& a = p.coeff(); - SASSERT(!a.is_zero()); - unsigned c; // the index for the lower or the upper bound - if (a.is_pos()) { - unsigned c = m_lar_solver.get_column_lower_bound_witness(p.column()); - if (c + 1 == 0) - return false; - bound = a * m_lar_solver.get_lower_bound(p.column()).x; - e.push_back(c); - return true; - } - // a.is_neg() - c = m_lar_solver.get_column_upper_bound_witness(p.column()); - if (c + 1 == 0) - return false; - bound = a * m_lar_solver.get_upper_bound(p.column()).x; - e.push_back(c); - return true; -} - -bool core::explain_coeff_upper_bound(const lp::lar_term::ival& p, rational& bound, lp::explanation& e) const { - const rational& a = p.coeff(); - lpvar j = p.column(); - SASSERT(!a.is_zero()); - unsigned c; // the index for the lower or the upper bound - if (a.is_neg()) { - unsigned c = m_lar_solver.get_column_lower_bound_witness(j); - if (c + 1 == 0) - return false; - bound = a * m_lar_solver.get_lower_bound(j).x; - e.push_back(c); - return true; - } - // a.is_pos() - c = m_lar_solver.get_column_upper_bound_witness(j); - if (c + 1 == 0) - return false; - bound = a * m_lar_solver.get_upper_bound(j).x; - e.push_back(c); - return true; -} - -// return true iff the negation of the ineq can be derived from the constraints -bool core::explain_ineq(new_lemma& lemma, const lp::lar_term& t, llc cmp, const rational& rs) { - // check that we have something like 0 < 0, which is always false and can be safely - // removed from the lemma - - if (t.is_empty() && rs.is_zero() && - (cmp == llc::LT || cmp == llc::GT || cmp == llc::NE)) return true; - lp::explanation exp; - bool r; - switch (negate(cmp)) { - case llc::LE: - r = explain_upper_bound(t, rs, exp); - break; - case llc::LT: - r = explain_upper_bound(t, rs - rational(1), exp); - break; - case llc::GE: - r = explain_lower_bound(t, rs, exp); - break; - case llc::GT: - r = explain_lower_bound(t, rs + rational(1), exp); - break; - - case llc::EQ: - r = (explain_lower_bound(t, rs, exp) && explain_upper_bound(t, rs, exp)) || - (rs.is_zero() && explain_by_equiv(t, exp)); - break; - case llc::NE: - // TBD - NB: does this work for Reals? - r = explain_lower_bound(t, rs + rational(1), exp) || explain_upper_bound(t, rs - rational(1), exp); - break; - default: - UNREACHABLE(); - return false; - } - if (r) { - lemma &= exp; - return true; - } - - return false; -} - -/** - * \brief - if t is an octagon term -+x -+ y try to explain why the term always is - equal zero -*/ -bool core::explain_by_equiv(const lp::lar_term& t, lp::explanation& e) const { - lpvar i,j; - bool sign; - if (!is_octagon_term(t, sign, i, j)) - return false; - if (m_evars.find(signed_var(i, false)) != m_evars.find(signed_var(j, sign))) - return false; - - m_evars.explain(signed_var(i, false), signed_var(j, sign), e); - TRACE("nla_solver", tout << "explained :"; m_lar_solver.print_term_as_indices(t, tout);); - return true; -} - -void core::mk_ineq_no_expl_check(new_lemma& lemma, lp::lar_term& t, llc cmp, const rational& rs) { - TRACE("nla_solver_details", m_lar_solver.print_term_as_indices(t, tout << "t = ");); - lemma |= ineq(cmp, t, rs); - CTRACE("nla_solver", ineq_holds(ineq(cmp, t, rs)), print_ineq(ineq(cmp, t, rs), tout) << "\n";); - SASSERT(!ineq_holds(ineq(cmp, t, rs))); -} - -llc apply_minus(llc cmp) { - switch(cmp) { - case llc::LE: return llc::GE; - case llc::LT: return llc::GT; - case llc::GE: return llc::LE; - case llc::GT: return llc::LT; - default: break; - } - return cmp; -} - -// the monics should be equal by modulo sign but this is not so in the model -void core::fill_explanation_and_lemma_sign(new_lemma& lemma, const monic& a, const monic & b, rational const& sign) { - SASSERT(sign == 1 || sign == -1); - lemma &= a; - lemma &= b; - TRACE("nla_solver", tout << "used constraints: " << lemma;); - SASSERT(lemma.num_ineqs() == 0); - lemma |= ineq(term(rational(1), a.var(), -sign, b.var()), llc::EQ, 0); -} - -// Replaces each variable index by the root in the tree and flips the sign if the var comes with a minus. -// Also sorts the result. -// -svector core::reduce_monic_to_rooted(const svector & vars, rational & sign) const { - svector ret; - bool s = false; - for (lpvar v : vars) { - auto root = m_evars.find(v); - s ^= root.sign(); - TRACE("nla_solver_eq", - tout << pp(v) << " mapped to " << pp(root.var()) << "\n";); - ret.push_back(root.var()); - } - sign = rational(s? -1: 1); - std::sort(ret.begin(), ret.end()); - return ret; -} - - -// Replaces definition m_v = v1* .. * vn by -// m_v = coeff * w1 * ... * wn, where w1, .., wn are canonical -// representatives, which are the roots of the equivalence tree, under current equations. -// -monic_coeff core::canonize_monic(monic const& m) const { - rational sign = rational(1); - svector vars = reduce_monic_to_rooted(m.vars(), sign); - return monic_coeff(vars, sign); -} - -int core::vars_sign(const svector& v) { - int sign = 1; - for (lpvar j : v) { - sign *= nla::rat_sign(val(j)); - if (sign == 0) - return 0; - } - return sign; -} - -bool core::has_upper_bound(lpvar j) const { - return m_lar_solver.column_has_upper_bound(j); -} - -bool core::has_lower_bound(lpvar j) const { - return m_lar_solver.column_has_lower_bound(j); -} -const rational& core::get_upper_bound(unsigned j) const { - return m_lar_solver.get_upper_bound(j).x; -} - -const rational& core::get_lower_bound(unsigned j) const { - return m_lar_solver.get_lower_bound(j).x; -} - -bool core::zero_is_an_inner_point_of_bounds(lpvar j) const { - if (has_upper_bound(j) && get_upper_bound(j) <= rational(0)) - return false; - if (has_lower_bound(j) && get_lower_bound(j) >= rational(0)) - return false; - return true; -} - -int core::rat_sign(const monic& m) const { - int sign = 1; - for (lpvar j : m.vars()) { - auto v = val(j); - if (v.is_neg()) { - sign = - sign; - continue; - } - if (v.is_pos()) { - continue; - } - sign = 0; - break; - } - return sign; -} - -// Returns true if the monic sign is incorrect -bool core::sign_contradiction(const monic& m) const { - return nla::rat_sign(var_val(m)) != rat_sign(m); -} - -/* - unsigned_vector eq_vars(lpvar j) const { - TRACE("nla_solver_eq", tout << "j = " << pp(j) << "eqs = "; - for(auto jj : m_evars.eq_vars(j)) tout << pp(jj) << " "; - }); - return m_evars.eq_vars(j); - } -*/ - -bool core::var_is_fixed_to_zero(lpvar j) const { - return - m_lar_solver.column_is_fixed(j) && - m_lar_solver.get_lower_bound(j) == lp::zero_of_type(); -} -bool core::var_is_fixed_to_val(lpvar j, const rational& v) const { - return - m_lar_solver.column_is_fixed(j) && - m_lar_solver.get_lower_bound(j) == lp::impq(v); -} - -bool core::var_is_fixed(lpvar j) const { - return m_lar_solver.column_is_fixed(j); -} - -bool core::var_is_free(lpvar j) const { - return m_lar_solver.column_is_free(j); -} - -std::ostream & core::print_ineq(const ineq & in, std::ostream & out) const { - m_lar_solver.print_term_as_indices(in.term(), out); - out << " " << lconstraint_kind_string(in.cmp()) << " " << in.rs(); - return out; -} - -std::ostream & core::print_var(lpvar j, std::ostream & out) const { - if (is_monic_var(j)) { - print_monic(m_emons[j], out); - } - - m_lar_solver.print_column_info(j, out); - signed_var jr = m_evars.find(j); - out << "root="; - if (jr.sign()) { - out << "-"; - } - - out << m_lar_solver.get_variable_name(jr.var()) << "\n"; - return out; -} - -std::ostream & core::print_monics(std::ostream & out) const { - for (auto &m : m_emons) { - print_monic_with_vars(m, out); - } - return out; -} - -std::ostream & core::print_ineqs(const lemma& l, std::ostream & out) const { - std::unordered_set vars; - out << "ineqs: "; - if (l.ineqs().size() == 0) { - out << "conflict\n"; - } else { - for (unsigned i = 0; i < l.ineqs().size(); i++) { - auto & in = l.ineqs()[i]; - print_ineq(in, out); - if (i + 1 < l.ineqs().size()) out << " or "; - for (lp::lar_term::ival p: in.term()) - vars.insert(p.column()); - } - out << std::endl; - for (lpvar j : vars) { - print_var(j, out); - } - out << "\n"; - } - return out; -} - -std::ostream & core::print_factorization(const factorization& f, std::ostream& out) const { - if (f.is_mon()){ - out << "is_mon " << pp_mon(*this, f.mon()); - } - else { - for (unsigned k = 0; k < f.size(); k++ ) { - out << "(" << pp(f[k]) << ")"; - if (k < f.size() - 1) - out << "*"; - } - } - return out; -} - -bool core::find_canonical_monic_of_vars(const svector& vars, lpvar & i) const { - monic const* sv = m_emons.find_canonical(vars); - return sv && (i = sv->var(), true); -} - -bool core::is_canonical_monic(lpvar j) const { - return m_emons.is_canonical_monic(j); -} - - -void core::trace_print_monic_and_factorization(const monic& rm, const factorization& f, std::ostream& out) const { - out << "rooted vars: "; - print_product(rm.rvars(), out) << "\n"; - out << "mon: " << pp_mon(*this, rm.var()) << "\n"; - out << "value: " << var_val(rm) << "\n"; - print_factorization(f, out << "fact: ") << "\n"; -} - - -bool core::var_has_positive_lower_bound(lpvar j) const { - return m_lar_solver.column_has_lower_bound(j) && m_lar_solver.get_lower_bound(j) > lp::zero_of_type(); -} - -bool core::var_has_negative_upper_bound(lpvar j) const { - return m_lar_solver.column_has_upper_bound(j) && m_lar_solver.get_upper_bound(j) < lp::zero_of_type(); -} - -bool core::var_is_separated_from_zero(lpvar j) const { - return - var_has_negative_upper_bound(j) || - var_has_positive_lower_bound(j); -} - - -bool core::vars_are_equiv(lpvar a, lpvar b) const { - SASSERT(abs(val(a)) == abs(val(b))); - return m_evars.vars_are_equiv(a, b); -} - -bool core::has_zero_factor(const factorization& factorization) const { - for (factor f : factorization) { - if (val(f).is_zero()) - return true; - } - return false; -} - - -template -bool core::mon_has_zero(const T& product) const { - for (lpvar j: product) { - if (val(j).is_zero()) - return true; - } - return false; -} - -template bool core::mon_has_zero(const unsigned_vector& product) const; - - -lp::lp_settings& core::lp_settings() { - return m_lar_solver.settings(); -} -const lp::lp_settings& core::lp_settings() const { - return m_lar_solver.settings(); -} - -unsigned core::random() { return lp_settings().random_next(); } - - -// we look for octagon constraints here, with a left part +-x +- y -void core::collect_equivs() { - const lp::lar_solver& s = m_lar_solver; - - for (unsigned i = 0; i < s.terms().size(); i++) { - if (!s.term_is_used_as_row(i)) - continue; - lpvar j = s.external_to_local(lp::tv::mask_term(i)); - if (var_is_fixed_to_zero(j)) { - TRACE("nla_solver_mons", s.print_term_as_indices(*s.terms()[i], tout << "term = ") << "\n";); - add_equivalence_maybe(s.terms()[i], s.get_column_upper_bound_witness(j), s.get_column_lower_bound_witness(j)); - } - } - m_emons.ensure_canonized(); -} - - -// returns true iff the term is in a form +-x-+y. -// the sign is true iff the term is x+y, -x-y. -bool core::is_octagon_term(const lp::lar_term& t, bool & sign, lpvar& i, lpvar &j) const { - if (t.size() != 2) - return false; - bool seen_minus = false; - bool seen_plus = false; - i = null_lpvar; - for(lp::lar_term::ival p : t) { - const auto & c = p.coeff(); - if (c == 1) { - seen_plus = true; - } else if (c == - 1) { - seen_minus = true; - } else { - return false; - } - if (i == null_lpvar) - i = p.column(); - else - j = p.column(); - } - SASSERT(j != null_lpvar); - sign = (seen_minus && seen_plus)? false : true; - return true; -} - -void core::add_equivalence_maybe(const lp::lar_term *t, lpci c0, lpci c1) { - bool sign; - lpvar i, j; - if (!is_octagon_term(*t, sign, i, j)) - return; - if (sign) - m_evars.merge_minus(i, j, eq_justification({c0, c1})); - else - m_evars.merge_plus(i, j, eq_justification({c0, c1})); -} - -// x is equivalent to y if x = +- y -void core::init_vars_equivalence() { - collect_equivs(); - // SASSERT(tables_are_ok()); -} - -bool core::vars_table_is_ok() const { - // return m_var_eqs.is_ok(); - return true; -} - -bool core::rm_table_is_ok() const { - // return m_emons.is_ok(); - return true; -} - -bool core::tables_are_ok() const { - return vars_table_is_ok() && rm_table_is_ok(); -} - -bool core::var_is_a_root(lpvar j) const { return m_evars.is_root(j); } - -template -bool core::vars_are_roots(const T& v) const { - for (lpvar j: v) { - if (!var_is_a_root(j)) - return false; - } - return true; -} - - - -template -void core::trace_print_rms(const T& p, std::ostream& out) { - out << "p = {\n"; - for (auto j : p) { - out << "j = " << j << ", rm = " << m_emons[j] << "\n"; - } - out << "}"; -} - -void core::print_monic_stats(const monic& m, std::ostream& out) { - if (m.size() == 2) return; - monic_coeff mc = canonize_monic(m); - for(unsigned i = 0; i < mc.vars().size(); i++){ - if (abs(val(mc.vars()[i])) == rational(1)) { - auto vv = mc.vars(); - vv.erase(vv.begin()+i); - monic const* sv = m_emons.find_canonical(vv); - if (!sv) { - out << "nf length" << vv.size() << "\n"; ; - } - } - } -} - -void core::print_stats(std::ostream& out) { -} - - -void core::clear() { - m_lemma_vec->clear(); -} - -void core::init_search() { - TRACE("nla_solver_mons", tout << "init\n";); - SASSERT(m_emons.invariant()); - clear(); - init_vars_equivalence(); - SASSERT(m_emons.invariant()); - SASSERT(elists_are_consistent(false)); -} - -void core::insert_to_refine(lpvar j) { - TRACE("lar_solver", tout << "j=" << j << '\n';); - m_to_refine.insert(j); -} - -void core::erase_from_to_refine(lpvar j) { - TRACE("lar_solver", tout << "j=" << j << '\n';); - m_to_refine.erase(j); -} - - -void core::init_to_refine() { - TRACE("nla_solver_details", tout << "emons:" << pp_emons(*this, m_emons);); - m_to_refine.clear(); - m_to_refine.resize(m_lar_solver.number_of_vars()); - unsigned r = random(), sz = m_emons.number_of_monics(); - for (unsigned k = 0; k < sz; k++) { - auto const & m = *(m_emons.begin() + (k + r)% sz); - if (!check_monic(m)) - insert_to_refine(m.var()); - } - - TRACE("nla_solver", - tout << m_to_refine.size() << " mons to refine:\n"; - for (lpvar v : m_to_refine) tout << pp_mon(*this, m_emons[v]) << ":error = " << - (val(v) - mul_val(m_emons[v])).get_double() << "\n";); -} - -std::unordered_set core::collect_vars(const lemma& l) const { - std::unordered_set vars; - auto insert_j = [&](lpvar j) { - vars.insert(j); - if (is_monic_var(j)) { - for (lpvar k : m_emons[j].vars()) - vars.insert(k); - } - }; - - for (const auto& i : l.ineqs()) { - for (lp::lar_term::ival p : i.term()) { - insert_j(p.column()); - } - } - for (auto p : l.expl()) { - const auto& c = m_lar_solver.constraints()[p.ci()]; - for (const auto& r : c.coeffs()) { - insert_j(r.second); - } - } - return vars; -} - -// divides bc by c, so bc = b*c -bool core::divide(const monic& bc, const factor& c, factor & b) const { - svector c_rvars = sorted_rvars(c); - TRACE("nla_solver_div", tout << "c_rvars = "; print_product(c_rvars, tout); tout << "\nbc_rvars = "; print_product(bc.rvars(), tout);); - if (!lp::is_proper_factor(c_rvars, bc.rvars())) - return false; - - auto b_rvars = lp::vector_div(bc.rvars(), c_rvars); - TRACE("nla_solver_div", tout << "b_rvars = "; print_product(b_rvars, tout);); - SASSERT(b_rvars.size() > 0); - if (b_rvars.size() == 1) { - b = factor(b_rvars[0], factor_type::VAR); - } else { - monic const* sv = m_emons.find_canonical(b_rvars); - if (sv == nullptr) { - TRACE("nla_solver_div", tout << "not in rooted";); - return false; - } - b = factor(sv->var(), factor_type::MON); - } - SASSERT(!b.sign()); - // We have bc = canonize_sign(bc)*bc.rvars() = canonize_sign(b)*b.rvars()*canonize_sign(c)*c.rvars(). - // Dividing by bc.rvars() we get canonize_sign(bc) = canonize_sign(b)*canonize_sign(c) - // Currently, canonize_sign(b) is 1, we might need to adjust it - b.sign() = canonize_sign(b) ^ canonize_sign(c) ^ canonize_sign(bc); - TRACE("nla_solver", tout << "success div:" << pp(b) << "\n";); - return true; -} - - -void core::negate_factor_equality(new_lemma& lemma, const factor& c, - const factor& d) { - if (c == d) - return; - lpvar i = var(c); - lpvar j = var(d); - auto iv = val(i), jv = val(j); - SASSERT(abs(iv) == abs(jv)); - lemma |= ineq(term(i, rational(iv == jv ? -1 : 1), j), llc::NE, 0); -} - -void core::negate_factor_relation(new_lemma& lemma, const rational& a_sign, const factor& a, const rational& b_sign, const factor& b) { - rational a_fs = sign_to_rat(canonize_sign(a)); - rational b_fs = sign_to_rat(canonize_sign(b)); - llc cmp = a_sign*val(a) < b_sign*val(b)? llc::GE : llc::LE; - lemma |= ineq(term(a_fs*a_sign, var(a), - b_fs*b_sign, var(b)), cmp, 0); -} - -std::ostream& core::print_lemma(const lemma& l, std::ostream& out) const { - static int n = 0; - out << "lemma:" << ++n << " "; - print_ineqs(l, out); - print_explanation(l.expl(), out); - for (lpvar j : collect_vars(l)) { - print_var(j, out); - } - return out; -} - - -void core::trace_print_ol(const monic& ac, - const factor& a, - const factor& c, - const monic& bc, - const factor& b, - std::ostream& out) { - out << "ac = " << pp_mon(*this, ac) << "\n"; - out << "bc = " << pp_mon(*this, bc) << "\n"; - out << "a = "; - print_factor_with_vars(a, out); - out << ", \nb = "; - print_factor_with_vars(b, out); - out << "\nc = "; - print_factor_with_vars(c, out); -} - -void core::maybe_add_a_factor(lpvar i, - const factor& c, - std::unordered_set& found_vars, - std::unordered_set& found_rm, - vector & r) const { - SASSERT(abs(val(i)) == abs(val(c))); - if (!is_monic_var(i)) { - i = m_evars.find(i).var(); - if (try_insert(i, found_vars)) { - r.push_back(factor(i, factor_type::VAR)); - } - } else { - if (try_insert(i, found_rm)) { - r.push_back(factor(i, factor_type::MON)); - TRACE("nla_solver", tout << "inserting factor = "; print_factor_with_vars(factor(i, factor_type::MON), tout); ); - } - } -} - - -// Returns rooted monics by arity -std::unordered_map core::get_rm_by_arity() { - std::unordered_map m; - for (auto const& mon : m_emons) { - unsigned arity = mon.vars().size(); - auto it = m.find(arity); - if (it == m.end()) { - it = m.insert(it, std::make_pair(arity, unsigned_vector())); - } - it->second.push_back(mon.var()); - } - return m; -} - -bool core::rm_check(const monic& rm) const { - return check_monic(m_emons[rm.var()]); -} - - -bool core::find_bfc_to_refine_on_monic(const monic& m, factorization & bf) { - for (auto f : factorization_factory_imp(m, *this)) { - if (f.size() == 2) { - auto a = f[0]; - auto b = f[1]; - if (var_val(m) != val(a) * val(b)) { - bf = f; - TRACE("nla_solver", tout << "found bf"; - tout << ":m:" << pp_mon_with_vars(*this, m) << "\n"; - tout << "bf:"; print_bfc(bf, tout);); - - return true; - } - } - } - return false; -} - -// finds a monic to refine with its binary factorization -bool core::find_bfc_to_refine(const monic* & m, factorization & bf){ - m = nullptr; - unsigned r = random(), sz = m_to_refine.size(); - for (unsigned k = 0; k < sz; k++) { - lpvar i = m_to_refine[(k + r) % sz]; - m = &m_emons[i]; - SASSERT (!check_monic(*m)); - if (has_real(m)) - continue; - if (m->size() == 2) { - bf.set_mon(m); - bf.push_back(factor(m->vars()[0], factor_type::VAR)); - bf.push_back(factor(m->vars()[1], factor_type::VAR)); - return true; - } - - if (find_bfc_to_refine_on_monic(*m, bf)) { - TRACE("nla_solver", - tout << "bf = "; print_factorization(bf, tout); - tout << "\nval(*m) = " << var_val(*m) << ", should be = (val(bf[0])=" << val(bf[0]) << ")*(val(bf[1]) = " << val(bf[1]) << ") = " << val(bf[0])*val(bf[1]) << "\n";); - return true; - } - } - return false; -} - -rational core::val(const factorization& f) const { - rational r(1); - for (const factor &p : f) { - r *= val(p); - } - return r; -} - -new_lemma::new_lemma(core& c, char const* name):name(name), c(c) { - c.m_lemma_vec->push_back(lemma()); -} - -new_lemma& new_lemma::operator|=(ineq const& ineq) { - if (!c.explain_ineq(*this, ineq.term(), ineq.cmp(), ineq.rs())) { - CTRACE("nla_solver", c.ineq_holds(ineq), c.print_ineq(ineq, tout) << "\n";); - SASSERT(!c.ineq_holds(ineq)); - current().push_back(ineq); - } - return *this; -} - - -new_lemma::~new_lemma() { - static int i = 0; - (void)i; - (void)name; - // code for checking lemma can be added here - TRACE("nla_solver", tout << name << " " << (++i) << "\n" << *this; ); -} - -lemma& new_lemma::current() const { - return c.m_lemma_vec->back(); -} - -new_lemma& new_lemma::operator&=(lp::explanation const& e) { - expl().add_expl(e); - return *this; -} - -new_lemma& new_lemma::operator&=(const monic& m) { - for (lpvar j : m.vars()) - *this &= j; - return *this; -} - -new_lemma& new_lemma::operator&=(const factor& f) { - if (f.type() == factor_type::VAR) - *this &= f.var(); - else - *this &= c.m_emons[f.var()]; - return *this; -} - -new_lemma& new_lemma::operator&=(const factorization& f) { - if (f.is_mon()) - return *this; - for (const auto& fc : f) { - *this &= fc; - } - return *this; -} - -new_lemma& new_lemma::operator&=(lpvar j) { - c.m_evars.explain(j, expl()); - return *this; -} - -new_lemma& new_lemma::explain_fixed(lpvar j) { - SASSERT(c.var_is_fixed(j)); - explain_existing_lower_bound(j); - explain_existing_upper_bound(j); - return *this; -} - -new_lemma& new_lemma::explain_equiv(lpvar a, lpvar b) { - SASSERT(abs(c.val(a)) == abs(c.val(b))); - if (c.vars_are_equiv(a, b)) { - *this &= a; - *this &= b; - } else { - explain_fixed(a); - explain_fixed(b); - } - return *this; -} - -new_lemma& new_lemma::explain_var_separated_from_zero(lpvar j) { - SASSERT(c.var_is_separated_from_zero(j)); - if (c.m_lar_solver.column_has_upper_bound(j) && - (c.m_lar_solver.get_upper_bound(j)< lp::zero_of_type())) - explain_existing_upper_bound(j); - else - explain_existing_lower_bound(j); - return *this; -} - -new_lemma& new_lemma::explain_existing_lower_bound(lpvar j) { - SASSERT(c.has_lower_bound(j)); - lp::explanation ex; - ex.push_back(c.m_lar_solver.get_column_lower_bound_witness(j)); - *this &= ex; - TRACE("nla_solver", tout << j << ": " << *this << "\n";); - return *this; -} - -new_lemma& new_lemma::explain_existing_upper_bound(lpvar j) { - SASSERT(c.has_upper_bound(j)); - lp::explanation ex; - ex.push_back(c.m_lar_solver.get_column_upper_bound_witness(j)); - *this &= ex; - return *this; -} - -std::ostream& new_lemma::display(std::ostream & out) const { - auto const& lemma = current(); - - for (auto p : lemma.expl()) { - out << "(" << p.ci() << ") "; - c.m_lar_solver.constraints().display(out, [this](lpvar j) { return c.var_str(j);}, p.ci()); - } - out << " ==> "; - if (lemma.ineqs().empty()) { - out << "false"; - } - else { - bool first = true; - for (auto & in : lemma.ineqs()) { - if (first) first = false; else out << " or "; - c.print_ineq(in, out); - } - } - out << "\n"; - for (lpvar j : c.collect_vars(lemma)) { - c.print_var(j, out); - } - return out; -} - -void core::negate_relation(new_lemma& lemma, unsigned j, const rational& a) { - SASSERT(val(j) != a); - lemma |= ineq(j, val(j) < a ? llc::GE : llc::LE, a); -} - -bool core::conflict_found() const { - for (const auto & l : * m_lemma_vec) { - if (l.is_conflict()) - return true; - } - return false; -} - -bool core::done() const { - return m_lemma_vec->size() >= 10 || - conflict_found() || - lp_settings().get_cancel_flag(); -} - -bool core::elist_is_consistent(const std::unordered_set & list) const { - bool first = true; - bool p; - for (lpvar j : list) { - if (first) { - p = check_monic(m_emons[j]); - first = false; - } else - if (check_monic(m_emons[j]) != p) - return false; - } - return true; -} - -bool core::elists_are_consistent(bool check_in_model) const { - std::unordered_map, hash_svector> lists; - if (!m_emons.elists_are_consistent(lists)) - return false; - - if (!check_in_model) - return true; - for (const auto & p : lists) { - if (! elist_is_consistent(p.second)) - return false; - } - return true; -} - -bool core::var_breaks_correct_monic_as_factor(lpvar j, const monic& m) const { - if (!val(var(m)).is_zero()) - return true; - - if (!val(j).is_zero()) // j was not zero: the new value does not matter - m must have another zero factor - return false; - // do we have another zero in m? - for (lpvar k : m) { - if (k != j && val(k).is_zero()) { - return false; // not breaking - } - } - // j was the only zero in m - return true; -} - -bool core::var_breaks_correct_monic(lpvar j) const { - if (is_monic_var(j) && !m_to_refine.contains(j)) { - TRACE("nla_solver", tout << "j = " << j << ", m = "; print_monic(emons()[j], tout) << "\n";); - return true; // changing the value of a correct monic - } - - for (const monic & m : emons().get_use_list(j)) { - if (m_to_refine.contains(m.var())) - continue; - if (var_breaks_correct_monic_as_factor(j, m)) - return true; - } - - return false; -} - -void core::update_to_refine_of_var(lpvar j) { - for (const monic & m : emons().get_use_list(j)) { - if (var_val(m) == mul_val(m)) - erase_from_to_refine(var(m)); - else - insert_to_refine(var(m)); - } - if (is_monic_var(j)) { - const monic& m = emons()[j]; - if (var_val(m) == mul_val(m)) - erase_from_to_refine(j); - else - insert_to_refine(j); - } -} - -bool core::var_is_big(lpvar j) const { - return !var_is_int(j) && val(j).is_big(); -} - -bool core::has_big_num(const monic& m) const { - if (var_is_big(var(m))) - return true; - for (lpvar j : m.vars()) - if (var_is_big(j)) - return true; - return false; -} - -bool core::has_real(const factorization& f) const { - for (const factor& fc: f) { - lpvar j = var(fc); - if (!var_is_int(j)) - return true; - } - return false; -} - -bool core::has_real(const monic& m) const { - for (lpvar j : m.vars()) - if (!var_is_int(j)) - return true; - return false; -} - -// returns true if the patching is blocking -bool core::is_patch_blocked(lpvar u, const lp::impq& ival) const { - TRACE("nla_solver", tout << "u = " << u << '\n';); - if (m_cautious_patching && - (!m_lar_solver.inside_bounds(u, ival) || (var_is_int(u) && ival.is_int() == false))) { - TRACE("nla_solver", tout << "u = " << u << " blocked, for feas or integr\n";); - return true; // block - } - - if (u == m_patched_var) { - TRACE("nla_solver", tout << "u == m_patched_var, no block\n";); - - return false; // do not block - } - // we can change only one variable in variables of m_patched_var - if (m_patched_monic->contains_var(u) || u == var(*m_patched_monic)) { - TRACE("nla_solver", tout << "u = " << u << " blocked as contained\n";); - return true; // block - } - - if (var_breaks_correct_monic(u)) { - TRACE("nla_solver", tout << "u = " << u << " blocked as used in a correct monomial\n";); - return true; - } - - TRACE("nla_solver", tout << "u = " << u << ", m_patched_m = "; print_monic(*m_patched_monic, tout) << - ", not blocked\n";); - - return false; -} - -// it tries to patch m_patched_var -bool core::try_to_patch(const rational& v) { - auto is_blocked = [this](lpvar u, const lp::impq& iv) { return is_patch_blocked(u, iv); }; - auto change_report = [this](lpvar u) { update_to_refine_of_var(u); }; - return m_lar_solver.try_to_patch(m_patched_var, v, is_blocked, change_report); -} - -bool in_power(const svector& vs, unsigned l) { - unsigned k = vs[l]; - return (l != 0 && vs[l - 1] == k) || (l + 1 < vs.size() && k == vs[l + 1]); -} - -bool core::to_refine_is_correct() const { - for (unsigned j = 0; j < m_lar_solver.number_of_vars(); j++) { - if (!is_monic_var(j)) continue; - bool valid = check_monic(emons()[j]); - if (valid == m_to_refine.contains(j)) { - TRACE("nla_solver", tout << "inconstency in m_to_refine : "; - print_monic(emons()[j], tout) << "\n"; - if (valid) tout << "should NOT be in to_refine\n"; - else tout << "should be in to_refine\n";); - return false; - } - } - return true; -} - -void core::patch_monomial(lpvar j) { - m_patched_monic =& (emons()[j]); - m_patched_var = j; - TRACE("nla_solver", tout << "m = "; print_monic(*m_patched_monic, tout) << "\n";); - rational v = mul_val(*m_patched_monic); - if (val(j) == v) { - erase_from_to_refine(j); - return; - } - if (!var_breaks_correct_monic(j) && try_to_patch(v)) { - SASSERT(to_refine_is_correct()); - return; - } - - // We could not patch j, now we try patching the factor variables. - TRACE("nla_solver", tout << " trying squares\n";); - // handle perfect squares - if ((*m_patched_monic).vars().size() == 2 && (*m_patched_monic).vars()[0] == (*m_patched_monic).vars()[1]) { - rational root; - if (v.is_perfect_square(root)) { - m_patched_var = (*m_patched_monic).vars()[0]; - if (!var_breaks_correct_monic(m_patched_var) && (try_to_patch(root) || try_to_patch(-root))) { - TRACE("nla_solver", tout << "patched square\n";); - return; - } - } - TRACE("nla_solver", tout << " cannot patch\n";); - return; - } - - // We have v != abc, but we need to have v = abc. - // If we patch b then b should be equal to v/ac = v/(abc/b) = b(v/abc) - if (!v.is_zero()) { - rational r = val(j) / v; - SASSERT((*m_patched_monic).is_sorted()); - TRACE("nla_solver", tout << "r = " << r << ", v = " << v << "\n";); - for (unsigned l = 0; l < (*m_patched_monic).size(); l++) { - m_patched_var = (*m_patched_monic).vars()[l]; - if (!in_power((*m_patched_monic).vars(), l) && - !var_breaks_correct_monic(m_patched_var) && - try_to_patch(r * val(m_patched_var))) { // r * val(k) gives the right value of k - TRACE("nla_solver", tout << "patched " << m_patched_var << "\n";); - SASSERT(mul_val((*m_patched_monic)) == val(j)); - erase_from_to_refine(j); - break; - } - } - } -} - -void core::patch_monomials_on_to_refine() { - auto to_refine = m_to_refine.index(); - // the rest of the function might change m_to_refine, so have to copy - unsigned sz = to_refine.size(); - - unsigned start = random(); - for (unsigned i = 0; i < sz; i++) { - patch_monomial(to_refine[(start + i) % sz]); - if (m_to_refine.size() == 0) - break; - } - TRACE("nla_solver", tout << "sz = " << sz << ", m_to_refine = " << m_to_refine.size() << - (sz > m_to_refine.size()? " less" : "same" ) << "\n";); -} - -void core::patch_monomials() { - m_cautious_patching = true; - patch_monomials_on_to_refine(); - if (m_to_refine.size() == 0 || !m_nla_settings.expensive_patching) { - return; - } - NOT_IMPLEMENTED_YET(); - m_cautious_patching = false; - patch_monomials_on_to_refine(); - m_lar_solver.push(); - save_tableau(); - constrain_nl_in_tableau(); - if (solve_tableau() && integrality_holds()) { - m_lar_solver.pop(1); - } else { - m_lar_solver.pop(); - restore_tableau(); - m_lar_solver.clear_inf_set(); - } - SASSERT(m_lar_solver.ax_is_correct()); -} - -void core::constrain_nl_in_tableau() { - NOT_IMPLEMENTED_YET(); -} - -bool core::solve_tableau() { - NOT_IMPLEMENTED_YET(); - return false; -} - -void core::restore_tableau() { - NOT_IMPLEMENTED_YET(); -} - -void core::save_tableau() { - NOT_IMPLEMENTED_YET(); -} - -bool core::integrality_holds() { - NOT_IMPLEMENTED_YET(); - return false; -} - -/** - * Cycle through different end-game solvers weighted by probability. - */ -void core::check_weighted(unsigned sz, std::pair>* checks) { - unsigned bound = 0; - for (unsigned i = 0; i < sz; ++i) - bound += checks[i].first; - uint_set seen; - while (bound > 0 && !done() && m_lemma_vec->empty()) { - unsigned n = random() % bound; - for (unsigned i = 0; i < sz; ++i) { - if (seen.contains(i)) - continue; - if (n < checks[i].first) { - seen.insert(i); - checks[i].second(); - bound -= checks[i].first; - break; - } - n -= checks[i].first; - } - } -} - - -lbool core::check(vector& l_vec) { - lp_settings().stats().m_nla_calls++; - TRACE("nla_solver", tout << "calls = " << lp_settings().stats().m_nla_calls << "\n";); - m_lar_solver.get_rid_of_inf_eps(); - m_lemma_vec = &l_vec; - if (!(m_lar_solver.get_status() == lp::lp_status::OPTIMAL || - m_lar_solver.get_status() == lp::lp_status::FEASIBLE)) { - TRACE("nla_solver", tout << "unknown because of the m_lar_solver.m_status = " << m_lar_solver.get_status() << "\n";); - return l_undef; - } - - init_to_refine(); - patch_monomials(); - set_use_nra_model(false); - if (m_to_refine.empty()) { return l_true; } - init_search(); - - lbool ret = l_undef; - - if (l_vec.empty() && !done()) - m_monomial_bounds(); - - if (l_vec.empty() && !done() && need_run_horner()) - m_horner.horner_lemmas(); - - if (l_vec.empty() && !done() && need_run_grobner()) - m_grobner(); - - if (l_vec.empty() && !done()) - m_basics.basic_lemma(true); - - if (l_vec.empty() && !done()) - m_basics.basic_lemma(false); - - if (!conflict_found() && !done() && should_run_bounded_nlsat()) - ret = bounded_nlsat(); - - - if (l_vec.empty() && !done() && ret == l_undef) { - std::function check1 = [&]() { m_order.order_lemma(); }; - std::function check2 = [&]() { m_monotone.monotonicity_lemma(); }; - std::function check3 = [&]() { m_tangents.tangent_lemma(); }; - - std::pair> checks[] = - { { 6, check1 }, - { 2, check2 }, - { 1, check3 }}; - check_weighted(3, checks); - - unsigned num_calls = lp_settings().stats().m_nla_calls; - if (!conflict_found() && m_nla_settings.run_nra && num_calls % 50 == 0 && num_calls > 500) - ret = bounded_nlsat(); - } - - if (l_vec.empty() && !done() && m_nla_settings.run_nra && ret == l_undef) { - ret = m_nra.check(); - m_stats.m_nra_calls++; - } - - if (ret == l_undef && !l_vec.empty() && m_reslim.inc()) - ret = l_false; - - m_stats.m_nla_lemmas += l_vec.size(); - for (const auto& l : l_vec) - m_stats.m_nla_explanations += static_cast(l.expl().size()); - - - TRACE("nla_solver", tout << "ret = " << ret << ", lemmas count = " << l_vec.size() << "\n";); - IF_VERBOSE(2, if(ret == l_undef) {verbose_stream() << "Monomials\n"; print_monics(verbose_stream());}); - CTRACE("nla_solver", ret == l_undef, tout << "Monomials\n"; print_monics(tout);); - return ret; -} - -bool core::should_run_bounded_nlsat() { - if (!m_nla_settings.run_nra) - return false; - if (m_nlsat_delay > m_nlsat_fails) - ++m_nlsat_fails; - return m_nlsat_delay <= m_nlsat_fails; -} - -lbool core::bounded_nlsat() { - params_ref p; - lbool ret; - p.set_uint("max_conflicts", 100); - m_nra.updt_params(p); - { - scoped_limits sl(m_reslim); - sl.push_child(&m_nra_lim); - scoped_rlimit sr(m_nra_lim, 100000); - ret = m_nra.check(); - } - p.set_uint("max_conflicts", UINT_MAX); - m_nra.updt_params(p); - m_stats.m_nra_calls++; - if (ret == l_undef) - ++m_nlsat_delay; - else { - m_nlsat_fails = 0; - m_nlsat_delay /= 2; - } - if (ret == l_true) { - m_lemma_vec->reset(); - } - return ret; -} - -bool core::no_lemmas_hold() const { - for (auto & l : * m_lemma_vec) { - if (lemma_holds(l)) { - TRACE("nla_solver", print_lemma(l, tout);); - return false; - } - } - return true; -} - -lbool core::test_check(vector& l) { - m_lar_solver.set_status(lp::lp_status::OPTIMAL); - return check(l); -} - -std::ostream& core::print_terms(std::ostream& out) const { - for (unsigned i = 0; i< m_lar_solver.terms().size(); i++) { - unsigned ext = lp::tv::mask_term(i); - if (!m_lar_solver.var_is_registered(ext)) { - out << "term is not registered\n"; - continue; - } - - const lp::lar_term & t = *m_lar_solver.terms()[i]; - out << "term:"; print_term(t, out) << std::endl; - lpvar j = m_lar_solver.external_to_local(ext); - print_var(j, out); - } - return out; -} - -std::string core::var_str(lpvar j) const { - std::string result; - if (is_monic_var(j)) - result += product_indices_str(m_emons[j].vars()) + (check_monic(m_emons[j])? "": "_"); - else - result += std::string("j") + lp::T_to_string(j); - // result += ":w" + lp::T_to_string(get_var_weight(j)); - return result; -} - -std::ostream& core::print_term( const lp::lar_term& t, std::ostream& out) const { - return lp::print_linear_combination_customized( - t.coeffs_as_vector(), - [this](lpvar j) { return var_str(j); }, - out); -} - - - - -std::unordered_set core::get_vars_of_expr_with_opening_terms(const nex *e ) { - auto ret = get_vars_of_expr(e); - auto & ls = m_lar_solver; - svector added; - for (auto j : ret) { - added.push_back(j); - } - for (unsigned i = 0; i < added.size(); ++i) { - lpvar j = added[i]; - if (ls.column_corresponds_to_term(j)) { - const auto& t = m_lar_solver.get_term(lp::tv::raw(ls.local_to_external(j))); - for (auto p : t) { - if (ret.find(p.column()) == ret.end()) { - added.push_back(p.column()); - ret.insert(p.column()); - } - } - } - } - return ret; -} - - -bool core::is_nl_var(lpvar j) const { - return is_monic_var(j) || m_emons.is_used_in_monic(j); -} - - -unsigned core::get_var_weight(lpvar j) const { - unsigned k; - switch (m_lar_solver.get_column_type(j)) { - - case lp::column_type::fixed: - k = 0; - break; - case lp::column_type::boxed: - k = 3; - break; - case lp::column_type::lower_bound: - case lp::column_type::upper_bound: - k = 6; - break; - case lp::column_type::free_column: - k = 9; - break; - default: - UNREACHABLE(); - break; - } - if (is_monic_var(j)) { - k++; - if (m_to_refine.contains(j)) - k++; - } - return k; -} - - -void core::set_active_vars_weights(nex_creator& nc) { - nc.set_number_of_vars(m_lar_solver.column_count()); - for (lpvar j : active_var_set()) - nc.set_var_weight(j, get_var_weight(j)); -} - -bool core::influences_nl_var(lpvar j) const { - if (lp::tv::is_term(j)) - j = lp::tv::unmask_term(j); - if (is_nl_var(j)) - return true; - for (const auto & c : m_lar_solver.A_r().m_columns[j]) { - lpvar basic_in_row = m_lar_solver.r_basis()[c.var()]; - if (is_nl_var(basic_in_row)) - return true; - } - return false; -} - -void core::collect_statistics(::statistics & st) { - st.update("arith-nla-explanations", m_stats.m_nla_explanations); - st.update("arith-nla-lemmas", m_stats.m_nla_lemmas); - st.update("arith-nra-calls", m_stats.m_nra_calls); -} - - -} // end of nla - + /*++ +Copyright (c) 2017 Microsoft Corporation + +Module Name: + + nla_core.cpp + +Author: + Lev Nachmanson (levnach) + Nikolaj Bjorner (nbjorner) + +--*/ +#include "util/uint_set.h" +#include "math/lp/nla_core.h" +#include "math/lp/factorization_factory_imp.h" +#include "math/lp/nex.h" +#include "math/grobner/pdd_solver.h" +#include "math/dd/pdd_interval.h" +#include "math/dd/pdd_eval.h" +namespace nla { + +typedef lp::lar_term term; + +core::core(lp::lar_solver& s, reslimit & lim) : + m_evars(), + m_lar_solver(s), + m_reslim(lim), + m_tangents(this), + m_basics(this), + m_order(this), + m_monotone(this), + m_intervals(this, lim), + m_monomial_bounds(this), + m_horner(this), + m_grobner(this), + m_emons(m_evars), + m_use_nra_model(false), + m_nra(s, m_nra_lim, *this) +{ + m_nlsat_delay = lp_settings().nlsat_delay(); +} + +bool core::compare_holds(const rational& ls, llc cmp, const rational& rs) const { + switch(cmp) { + case llc::LE: return ls <= rs; + case llc::LT: return ls < rs; + case llc::GE: return ls >= rs; + case llc::GT: return ls > rs; + case llc::EQ: return ls == rs; + case llc::NE: return ls != rs; + default: SASSERT(false); + }; + + return false; +} + +rational core::value(const lp::lar_term& r) const { + rational ret(0); + for (lp::lar_term::ival t : r) { + ret += t.coeff() * val(t.column()); + } + return ret; +} + +lp::lar_term core::subs_terms_to_columns(const lp::lar_term& t) const { + lp::lar_term r; + for (lp::lar_term::ival p : t) { + lpvar j = p.column(); + if (lp::tv::is_term(j)) + j = m_lar_solver.map_term_index_to_column_index(j); + r.add_monomial(p.coeff(), j); + } + return r; +} + +bool core::ineq_holds(const ineq& n) const { + return compare_holds(value(n.term()), n.cmp(), n.rs()); +} + +bool core::lemma_holds(const lemma& l) const { + for(const ineq &i : l.ineqs()) { + if (ineq_holds(i)) + return true; + } + return false; +} + +lpvar core::map_to_root(lpvar j) const { + return m_evars.find(j).var(); +} + +svector core::sorted_rvars(const factor& f) const { + if (f.is_var()) { + svector r; r.push_back(map_to_root(f.var())); + return r; + } + return m_emons[f.var()].rvars(); +} + +// the value of the factor is equal to the value of the variable multiplied +// by the canonize_sign +bool core::canonize_sign(const factor& f) const { + return f.sign() ^ (f.is_var()? canonize_sign(f.var()) : canonize_sign(m_emons[f.var()])); +} + +bool core::canonize_sign(lpvar j) const { + return m_evars.find(j).sign(); +} + +bool core::canonize_sign_is_correct(const monic& m) const { + bool r = false; + for (lpvar j : m.vars()) { + r ^= canonize_sign(j); + } + return r == m.rsign(); +} + +bool core::canonize_sign(const monic& m) const { + SASSERT(canonize_sign_is_correct(m)); + return m.rsign(); +} + +bool core::canonize_sign(const factorization& f) const { + bool r = false; + for (const factor & a : f) { + r ^= canonize_sign(a); + } + return r; +} + +void core::add_monic(lpvar v, unsigned sz, lpvar const* vs) { + m_add_buffer.resize(sz); + for (unsigned i = 0; i < sz; i++) { + lpvar j = vs[i]; + if (lp::tv::is_term(j)) + j = m_lar_solver.map_term_index_to_column_index(j); + m_add_buffer[i] = j; + } + m_emons.add(v, m_add_buffer); +} + +void core::push() { + TRACE("nla_solver_verbose", tout << "\n";); + m_emons.push(); +} + + +void core::pop(unsigned n) { + TRACE("nla_solver_verbose", tout << "n = " << n << "\n";); + m_emons.pop(n); + SASSERT(elists_are_consistent(false)); +} + +rational core::product_value(const monic& m) const { + rational r(1); + for (auto j : m.vars()) { + r *= m_lar_solver.get_column_value(j).x; + } + return r; +} + +// return true iff the monic value is equal to the product of the values of the factors +bool core::check_monic(const monic& m) const { + SASSERT((!m_lar_solver.column_is_int(m.var())) || m_lar_solver.get_column_value(m.var()).is_int()); + bool ret = product_value(m) == m_lar_solver.get_column_value(m.var()).x; + CTRACE("nla_solver_check_monic", !ret, print_monic(m, tout) << '\n';); + return ret; +} + + +template +std::ostream& core::print_product(const T & m, std::ostream& out) const { + bool first = true; + for (lpvar v : m) { + if (!first) out << "*"; else first = false; + if (lp_settings().print_external_var_name()) + out << "(" << m_lar_solver.get_variable_name(v) << "=" << val(v) << ")"; + else + out << "(j" << v << " = " << val(v) << ")"; + + } + return out; +} +template +std::string core::product_indices_str(const T & m) const { + std::stringstream out; + bool first = true; + for (lpvar v : m) { + if (!first) + out << "*"; + else + first = false; + out << "j" << v;; + } + return out.str(); +} + +std::ostream & core::print_factor(const factor& f, std::ostream& out) const { + if (f.sign()) + out << "- "; + if (f.is_var()) { + out << "VAR, " << pp(f.var()); + } else { + out << "MON, v" << m_emons[f.var()] << " = "; + print_product(m_emons[f.var()].rvars(), out); + } + out << "\n"; + return out; +} + +std::ostream & core::print_factor_with_vars(const factor& f, std::ostream& out) const { + if (f.is_var()) { + out << pp(f.var()); + } + else { + out << " MON = " << pp_mon_with_vars(*this, m_emons[f.var()]); + } + return out; +} + +std::ostream& core::print_monic(const monic& m, std::ostream& out) const { + if (lp_settings().print_external_var_name()) + out << "([" << m.var() << "] = " << m_lar_solver.get_variable_name(m.var()) << " = " << val(m.var()) << " = "; + else + out << "(j" << m.var() << " = " << val(m.var()) << " = "; + print_product(m.vars(), out) << ")\n"; + return out; +} + + +std::ostream& core::print_bfc(const factorization& m, std::ostream& out) const { + SASSERT(m.size() == 2); + out << "( x = " << pp(m[0]) << "* y = " << pp(m[1]) << ")"; + return out; +} + +std::ostream& core::print_monic_with_vars(lpvar v, std::ostream& out) const { + return print_monic_with_vars(m_emons[v], out); +} +template +std::ostream& core::print_product_with_vars(const T& m, std::ostream& out) const { + print_product(m, out) << "\n"; + for (unsigned k = 0; k < m.size(); k++) { + print_var(m[k], out); + } + return out; +} + +std::ostream& core::print_monic_with_vars(const monic& m, std::ostream& out) const { + out << "[" << pp(m.var()) << "]\n"; + out << "vars:"; print_product_with_vars(m.vars(), out) << "\n"; + if (m.vars() == m.rvars()) + out << "same rvars, and m.rsign = " << m.rsign() << " of course\n"; + else { + out << "rvars:"; print_product_with_vars(m.rvars(), out) << "\n"; + out << "rsign:" << m.rsign() << "\n"; + } + return out; +} + +std::ostream& core::print_explanation(const lp::explanation& exp, std::ostream& out) const { + out << "expl: "; + unsigned i = 0; + for (auto p : exp) { + out << "(" << p.ci() << ")"; + m_lar_solver.constraints().display(out, [this](lpvar j) { return var_str(j);}, p.ci()); + if (++i < exp.size()) + out << " "; + } + return out; +} + +bool core::explain_upper_bound(const lp::lar_term& t, const rational& rs, lp::explanation& e) const { + rational b(0); // the bound + for (lp::lar_term::ival p : t) { + rational pb; + if (explain_coeff_upper_bound(p, pb, e)) { + b += pb; + } else { + e.clear(); + return false; + } + } + if (b > rs ) { + e.clear(); + return false; + } + return true; +} +bool core::explain_lower_bound(const lp::lar_term& t, const rational& rs, lp::explanation& e) const { + rational b(0); // the bound + for (lp::lar_term::ival p : t) { + rational pb; + if (explain_coeff_lower_bound(p, pb, e)) { + b += pb; + } else { + e.clear(); + return false; + } + } + if (b < rs ) { + e.clear(); + return false; + } + return true; +} + +bool core::explain_coeff_lower_bound(const lp::lar_term::ival& p, rational& bound, lp::explanation& e) const { + const rational& a = p.coeff(); + SASSERT(!a.is_zero()); + unsigned c; // the index for the lower or the upper bound + if (a.is_pos()) { + unsigned c = m_lar_solver.get_column_lower_bound_witness(p.column()); + if (c + 1 == 0) + return false; + bound = a * m_lar_solver.get_lower_bound(p.column()).x; + e.push_back(c); + return true; + } + // a.is_neg() + c = m_lar_solver.get_column_upper_bound_witness(p.column()); + if (c + 1 == 0) + return false; + bound = a * m_lar_solver.get_upper_bound(p.column()).x; + e.push_back(c); + return true; +} + +bool core::explain_coeff_upper_bound(const lp::lar_term::ival& p, rational& bound, lp::explanation& e) const { + const rational& a = p.coeff(); + lpvar j = p.column(); + SASSERT(!a.is_zero()); + unsigned c; // the index for the lower or the upper bound + if (a.is_neg()) { + unsigned c = m_lar_solver.get_column_lower_bound_witness(j); + if (c + 1 == 0) + return false; + bound = a * m_lar_solver.get_lower_bound(j).x; + e.push_back(c); + return true; + } + // a.is_pos() + c = m_lar_solver.get_column_upper_bound_witness(j); + if (c + 1 == 0) + return false; + bound = a * m_lar_solver.get_upper_bound(j).x; + e.push_back(c); + return true; +} + +// return true iff the negation of the ineq can be derived from the constraints +bool core::explain_ineq(new_lemma& lemma, const lp::lar_term& t, llc cmp, const rational& rs) { + // check that we have something like 0 < 0, which is always false and can be safely + // removed from the lemma + + if (t.is_empty() && rs.is_zero() && + (cmp == llc::LT || cmp == llc::GT || cmp == llc::NE)) return true; + lp::explanation exp; + bool r; + switch (negate(cmp)) { + case llc::LE: + r = explain_upper_bound(t, rs, exp); + break; + case llc::LT: + r = explain_upper_bound(t, rs - rational(1), exp); + break; + case llc::GE: + r = explain_lower_bound(t, rs, exp); + break; + case llc::GT: + r = explain_lower_bound(t, rs + rational(1), exp); + break; + + case llc::EQ: + r = (explain_lower_bound(t, rs, exp) && explain_upper_bound(t, rs, exp)) || + (rs.is_zero() && explain_by_equiv(t, exp)); + break; + case llc::NE: + // TBD - NB: does this work for Reals? + r = explain_lower_bound(t, rs + rational(1), exp) || explain_upper_bound(t, rs - rational(1), exp); + break; + default: + UNREACHABLE(); + return false; + } + if (r) { + lemma &= exp; + return true; + } + + return false; +} + +/** + * \brief + if t is an octagon term -+x -+ y try to explain why the term always is + equal zero +*/ +bool core::explain_by_equiv(const lp::lar_term& t, lp::explanation& e) const { + lpvar i,j; + bool sign; + if (!is_octagon_term(t, sign, i, j)) + return false; + if (m_evars.find(signed_var(i, false)) != m_evars.find(signed_var(j, sign))) + return false; + + m_evars.explain(signed_var(i, false), signed_var(j, sign), e); + TRACE("nla_solver", tout << "explained :"; m_lar_solver.print_term_as_indices(t, tout);); + return true; +} + +void core::mk_ineq_no_expl_check(new_lemma& lemma, lp::lar_term& t, llc cmp, const rational& rs) { + TRACE("nla_solver_details", m_lar_solver.print_term_as_indices(t, tout << "t = ");); + lemma |= ineq(cmp, t, rs); + CTRACE("nla_solver", ineq_holds(ineq(cmp, t, rs)), print_ineq(ineq(cmp, t, rs), tout) << "\n";); + SASSERT(!ineq_holds(ineq(cmp, t, rs))); +} + +llc apply_minus(llc cmp) { + switch(cmp) { + case llc::LE: return llc::GE; + case llc::LT: return llc::GT; + case llc::GE: return llc::LE; + case llc::GT: return llc::LT; + default: break; + } + return cmp; +} + +// the monics should be equal by modulo sign but this is not so in the model +void core::fill_explanation_and_lemma_sign(new_lemma& lemma, const monic& a, const monic & b, rational const& sign) { + SASSERT(sign == 1 || sign == -1); + lemma &= a; + lemma &= b; + TRACE("nla_solver", tout << "used constraints: " << lemma;); + SASSERT(lemma.num_ineqs() == 0); + lemma |= ineq(term(rational(1), a.var(), -sign, b.var()), llc::EQ, 0); +} + +// Replaces each variable index by the root in the tree and flips the sign if the var comes with a minus. +// Also sorts the result. +// +svector core::reduce_monic_to_rooted(const svector & vars, rational & sign) const { + svector ret; + bool s = false; + for (lpvar v : vars) { + auto root = m_evars.find(v); + s ^= root.sign(); + TRACE("nla_solver_eq", + tout << pp(v) << " mapped to " << pp(root.var()) << "\n";); + ret.push_back(root.var()); + } + sign = rational(s? -1: 1); + std::sort(ret.begin(), ret.end()); + return ret; +} + + +// Replaces definition m_v = v1* .. * vn by +// m_v = coeff * w1 * ... * wn, where w1, .., wn are canonical +// representatives, which are the roots of the equivalence tree, under current equations. +// +monic_coeff core::canonize_monic(monic const& m) const { + rational sign = rational(1); + svector vars = reduce_monic_to_rooted(m.vars(), sign); + return monic_coeff(vars, sign); +} + +int core::vars_sign(const svector& v) { + int sign = 1; + for (lpvar j : v) { + sign *= nla::rat_sign(val(j)); + if (sign == 0) + return 0; + } + return sign; +} + +bool core::has_upper_bound(lpvar j) const { + return m_lar_solver.column_has_upper_bound(j); +} + +bool core::has_lower_bound(lpvar j) const { + return m_lar_solver.column_has_lower_bound(j); +} +const rational& core::get_upper_bound(unsigned j) const { + return m_lar_solver.get_upper_bound(j).x; +} + +const rational& core::get_lower_bound(unsigned j) const { + return m_lar_solver.get_lower_bound(j).x; +} + +bool core::zero_is_an_inner_point_of_bounds(lpvar j) const { + if (has_upper_bound(j) && get_upper_bound(j) <= rational(0)) + return false; + if (has_lower_bound(j) && get_lower_bound(j) >= rational(0)) + return false; + return true; +} + +int core::rat_sign(const monic& m) const { + int sign = 1; + for (lpvar j : m.vars()) { + auto v = val(j); + if (v.is_neg()) { + sign = - sign; + continue; + } + if (v.is_pos()) { + continue; + } + sign = 0; + break; + } + return sign; +} + +// Returns true if the monic sign is incorrect +bool core::sign_contradiction(const monic& m) const { + return nla::rat_sign(var_val(m)) != rat_sign(m); +} + +/* + unsigned_vector eq_vars(lpvar j) const { + TRACE("nla_solver_eq", tout << "j = " << pp(j) << "eqs = "; + for(auto jj : m_evars.eq_vars(j)) tout << pp(jj) << " "; + }); + return m_evars.eq_vars(j); + } +*/ + +bool core::var_is_fixed_to_zero(lpvar j) const { + return + m_lar_solver.column_is_fixed(j) && + m_lar_solver.get_lower_bound(j) == lp::zero_of_type(); +} +bool core::var_is_fixed_to_val(lpvar j, const rational& v) const { + return + m_lar_solver.column_is_fixed(j) && + m_lar_solver.get_lower_bound(j) == lp::impq(v); +} + +bool core::var_is_fixed(lpvar j) const { + return m_lar_solver.column_is_fixed(j); +} + +bool core::var_is_free(lpvar j) const { + return m_lar_solver.column_is_free(j); +} + + +std::ostream & core::print_ineq(const ineq & in, std::ostream & out) const { + m_lar_solver.print_term_as_indices(in.term(), out); + out << " " << lconstraint_kind_string(in.cmp()) << " " << in.rs(); + return out; +} + +std::ostream & core::print_var(lpvar j, std::ostream & out) const { + if (is_monic_var(j)) { + print_monic(m_emons[j], out); + } + + m_lar_solver.print_column_info(j, out); + signed_var jr = m_evars.find(j); + out << "root="; + if (jr.sign()) { + out << "-"; + } + + out << m_lar_solver.get_variable_name(jr.var()) << "\n"; + return out; +} + +std::ostream & core::print_monics(std::ostream & out) const { + for (auto &m : m_emons) { + print_monic_with_vars(m, out); + } + return out; +} + +std::ostream & core::print_ineqs(const lemma& l, std::ostream & out) const { + std::unordered_set vars; + out << "ineqs: "; + if (l.ineqs().size() == 0) { + out << "conflict\n"; + } else { + for (unsigned i = 0; i < l.ineqs().size(); i++) { + auto & in = l.ineqs()[i]; + print_ineq(in, out); + if (i + 1 < l.ineqs().size()) out << " or "; + for (lp::lar_term::ival p: in.term()) + vars.insert(p.column()); + } + out << std::endl; + for (lpvar j : vars) { + print_var(j, out); + } + out << "\n"; + } + return out; +} + +std::ostream & core::print_factorization(const factorization& f, std::ostream& out) const { + if (f.is_mon()){ + out << "is_mon " << pp_mon(*this, f.mon()); + } + else { + for (unsigned k = 0; k < f.size(); k++ ) { + out << "(" << pp(f[k]) << ")"; + if (k < f.size() - 1) + out << "*"; + } + } + return out; +} + +bool core::find_canonical_monic_of_vars(const svector& vars, lpvar & i) const { + monic const* sv = m_emons.find_canonical(vars); + return sv && (i = sv->var(), true); +} + +bool core::is_canonical_monic(lpvar j) const { + return m_emons.is_canonical_monic(j); +} + + +void core::trace_print_monic_and_factorization(const monic& rm, const factorization& f, std::ostream& out) const { + out << "rooted vars: "; + print_product(rm.rvars(), out) << "\n"; + out << "mon: " << pp_mon(*this, rm.var()) << "\n"; + out << "value: " << var_val(rm) << "\n"; + print_factorization(f, out << "fact: ") << "\n"; +} + + +bool core::var_has_positive_lower_bound(lpvar j) const { + return m_lar_solver.column_has_lower_bound(j) && m_lar_solver.get_lower_bound(j) > lp::zero_of_type(); +} + +bool core::var_has_negative_upper_bound(lpvar j) const { + return m_lar_solver.column_has_upper_bound(j) && m_lar_solver.get_upper_bound(j) < lp::zero_of_type(); +} + +bool core::var_is_separated_from_zero(lpvar j) const { + return + var_has_negative_upper_bound(j) || + var_has_positive_lower_bound(j); +} + + +bool core::vars_are_equiv(lpvar a, lpvar b) const { + SASSERT(abs(val(a)) == abs(val(b))); + return m_evars.vars_are_equiv(a, b); +} + +bool core::has_zero_factor(const factorization& factorization) const { + for (factor f : factorization) { + if (val(f).is_zero()) + return true; + } + return false; +} + + +template +bool core::mon_has_zero(const T& product) const { + for (lpvar j: product) { + if (val(j).is_zero()) + return true; + } + return false; +} + +template bool core::mon_has_zero(const unsigned_vector& product) const; + + +lp::lp_settings& core::lp_settings() { + return m_lar_solver.settings(); +} +const lp::lp_settings& core::lp_settings() const { + return m_lar_solver.settings(); +} + +unsigned core::random() { return lp_settings().random_next(); } + + +// we look for octagon constraints here, with a left part +-x +- y +void core::collect_equivs() { + const lp::lar_solver& s = m_lar_solver; + + for (unsigned i = 0; i < s.terms().size(); i++) { + if (!s.term_is_used_as_row(i)) + continue; + lpvar j = s.external_to_local(lp::tv::mask_term(i)); + if (var_is_fixed_to_zero(j)) { + TRACE("nla_solver_mons", s.print_term_as_indices(*s.terms()[i], tout << "term = ") << "\n";); + add_equivalence_maybe(s.terms()[i], s.get_column_upper_bound_witness(j), s.get_column_lower_bound_witness(j)); + } + } + m_emons.ensure_canonized(); +} + + +// returns true iff the term is in a form +-x-+y. +// the sign is true iff the term is x+y, -x-y. +bool core::is_octagon_term(const lp::lar_term& t, bool & sign, lpvar& i, lpvar &j) const { + if (t.size() != 2) + return false; + bool seen_minus = false; + bool seen_plus = false; + i = null_lpvar; + for(lp::lar_term::ival p : t) { + const auto & c = p.coeff(); + if (c == 1) { + seen_plus = true; + } else if (c == - 1) { + seen_minus = true; + } else { + return false; + } + if (i == null_lpvar) + i = p.column(); + else + j = p.column(); + } + SASSERT(j != null_lpvar); + sign = (seen_minus && seen_plus)? false : true; + return true; +} + +void core::add_equivalence_maybe(const lp::lar_term *t, lpci c0, lpci c1) { + bool sign; + lpvar i, j; + if (!is_octagon_term(*t, sign, i, j)) + return; + if (sign) + m_evars.merge_minus(i, j, eq_justification({c0, c1})); + else + m_evars.merge_plus(i, j, eq_justification({c0, c1})); +} + +// x is equivalent to y if x = +- y +void core::init_vars_equivalence() { + collect_equivs(); + // SASSERT(tables_are_ok()); +} + +bool core::vars_table_is_ok() const { + // return m_var_eqs.is_ok(); + return true; +} + +bool core::rm_table_is_ok() const { + // return m_emons.is_ok(); + return true; +} + +bool core::tables_are_ok() const { + return vars_table_is_ok() && rm_table_is_ok(); +} + +bool core::var_is_a_root(lpvar j) const { return m_evars.is_root(j); } + +template +bool core::vars_are_roots(const T& v) const { + for (lpvar j: v) { + if (!var_is_a_root(j)) + return false; + } + return true; +} + + + +template +void core::trace_print_rms(const T& p, std::ostream& out) { + out << "p = {\n"; + for (auto j : p) { + out << "j = " << j << ", rm = " << m_emons[j] << "\n"; + } + out << "}"; +} + +void core::print_monic_stats(const monic& m, std::ostream& out) { + if (m.size() == 2) return; + monic_coeff mc = canonize_monic(m); + for(unsigned i = 0; i < mc.vars().size(); i++){ + if (abs(val(mc.vars()[i])) == rational(1)) { + auto vv = mc.vars(); + vv.erase(vv.begin()+i); + monic const* sv = m_emons.find_canonical(vv); + if (!sv) { + out << "nf length" << vv.size() << "\n"; ; + } + } + } +} + +void core::print_stats(std::ostream& out) { +} + + +void core::clear() { + m_lemma_vec->clear(); +} + +void core::init_search() { + TRACE("nla_solver_mons", tout << "init\n";); + SASSERT(m_emons.invariant()); + clear(); + init_vars_equivalence(); + SASSERT(m_emons.invariant()); + SASSERT(elists_are_consistent(false)); +} + +void core::insert_to_refine(lpvar j) { + TRACE("lar_solver", tout << "j=" << j << '\n';); + m_to_refine.insert(j); +} + +void core::erase_from_to_refine(lpvar j) { + TRACE("lar_solver", tout << "j=" << j << '\n';); + m_to_refine.erase(j); +} + + +void core::init_to_refine() { + TRACE("nla_solver_details", tout << "emons:" << pp_emons(*this, m_emons);); + m_to_refine.clear(); + m_to_refine.resize(m_lar_solver.number_of_vars()); + unsigned r = random(), sz = m_emons.number_of_monics(); + for (unsigned k = 0; k < sz; k++) { + auto const & m = *(m_emons.begin() + (k + r)% sz); + if (!check_monic(m)) + insert_to_refine(m.var()); + } + + TRACE("nla_solver", + tout << m_to_refine.size() << " mons to refine:\n"; + for (lpvar v : m_to_refine) tout << pp_mon(*this, m_emons[v]) << ":error = " << + (val(v) - mul_val(m_emons[v])).get_double() << "\n";); +} + +std::unordered_set core::collect_vars(const lemma& l) const { + std::unordered_set vars; + auto insert_j = [&](lpvar j) { + vars.insert(j); + if (is_monic_var(j)) { + for (lpvar k : m_emons[j].vars()) + vars.insert(k); + } + }; + + for (const auto& i : l.ineqs()) { + for (lp::lar_term::ival p : i.term()) { + insert_j(p.column()); + } + } + for (auto p : l.expl()) { + const auto& c = m_lar_solver.constraints()[p.ci()]; + for (const auto& r : c.coeffs()) { + insert_j(r.second); + } + } + return vars; +} + +// divides bc by c, so bc = b*c +bool core::divide(const monic& bc, const factor& c, factor & b) const { + svector c_rvars = sorted_rvars(c); + TRACE("nla_solver_div", tout << "c_rvars = "; print_product(c_rvars, tout); tout << "\nbc_rvars = "; print_product(bc.rvars(), tout);); + if (!lp::is_proper_factor(c_rvars, bc.rvars())) + return false; + + auto b_rvars = lp::vector_div(bc.rvars(), c_rvars); + TRACE("nla_solver_div", tout << "b_rvars = "; print_product(b_rvars, tout);); + SASSERT(b_rvars.size() > 0); + if (b_rvars.size() == 1) { + b = factor(b_rvars[0], factor_type::VAR); + } else { + monic const* sv = m_emons.find_canonical(b_rvars); + if (sv == nullptr) { + TRACE("nla_solver_div", tout << "not in rooted";); + return false; + } + b = factor(sv->var(), factor_type::MON); + } + SASSERT(!b.sign()); + // We have bc = canonize_sign(bc)*bc.rvars() = canonize_sign(b)*b.rvars()*canonize_sign(c)*c.rvars(). + // Dividing by bc.rvars() we get canonize_sign(bc) = canonize_sign(b)*canonize_sign(c) + // Currently, canonize_sign(b) is 1, we might need to adjust it + b.sign() = canonize_sign(b) ^ canonize_sign(c) ^ canonize_sign(bc); + TRACE("nla_solver", tout << "success div:" << pp(b) << "\n";); + return true; +} + + +void core::negate_factor_equality(new_lemma& lemma, const factor& c, + const factor& d) { + if (c == d) + return; + lpvar i = var(c); + lpvar j = var(d); + auto iv = val(i), jv = val(j); + SASSERT(abs(iv) == abs(jv)); + lemma |= ineq(term(i, rational(iv == jv ? -1 : 1), j), llc::NE, 0); +} + +void core::negate_factor_relation(new_lemma& lemma, const rational& a_sign, const factor& a, const rational& b_sign, const factor& b) { + rational a_fs = sign_to_rat(canonize_sign(a)); + rational b_fs = sign_to_rat(canonize_sign(b)); + llc cmp = a_sign*val(a) < b_sign*val(b)? llc::GE : llc::LE; + lemma |= ineq(term(a_fs*a_sign, var(a), - b_fs*b_sign, var(b)), cmp, 0); +} + +std::ostream& core::print_lemma(const lemma& l, std::ostream& out) const { + static int n = 0; + out << "lemma:" << ++n << " "; + print_ineqs(l, out); + print_explanation(l.expl(), out); + for (lpvar j : collect_vars(l)) { + print_var(j, out); + } + return out; +} + + +void core::trace_print_ol(const monic& ac, + const factor& a, + const factor& c, + const monic& bc, + const factor& b, + std::ostream& out) { + out << "ac = " << pp_mon(*this, ac) << "\n"; + out << "bc = " << pp_mon(*this, bc) << "\n"; + out << "a = "; + print_factor_with_vars(a, out); + out << ", \nb = "; + print_factor_with_vars(b, out); + out << "\nc = "; + print_factor_with_vars(c, out); +} + +void core::maybe_add_a_factor(lpvar i, + const factor& c, + std::unordered_set& found_vars, + std::unordered_set& found_rm, + vector & r) const { + SASSERT(abs(val(i)) == abs(val(c))); + if (!is_monic_var(i)) { + i = m_evars.find(i).var(); + if (try_insert(i, found_vars)) { + r.push_back(factor(i, factor_type::VAR)); + } + } else { + if (try_insert(i, found_rm)) { + r.push_back(factor(i, factor_type::MON)); + TRACE("nla_solver", tout << "inserting factor = "; print_factor_with_vars(factor(i, factor_type::MON), tout); ); + } + } +} + + +// Returns rooted monics by arity +std::unordered_map core::get_rm_by_arity() { + std::unordered_map m; + for (auto const& mon : m_emons) { + unsigned arity = mon.vars().size(); + auto it = m.find(arity); + if (it == m.end()) { + it = m.insert(it, std::make_pair(arity, unsigned_vector())); + } + it->second.push_back(mon.var()); + } + return m; +} + +bool core::rm_check(const monic& rm) const { + return check_monic(m_emons[rm.var()]); +} + + +bool core::find_bfc_to_refine_on_monic(const monic& m, factorization & bf) { + for (auto f : factorization_factory_imp(m, *this)) { + if (f.size() == 2) { + auto a = f[0]; + auto b = f[1]; + if (var_val(m) != val(a) * val(b)) { + bf = f; + TRACE("nla_solver", tout << "found bf"; + tout << ":m:" << pp_mon_with_vars(*this, m) << "\n"; + tout << "bf:"; print_bfc(bf, tout);); + + return true; + } + } + } + return false; +} + +// finds a monic to refine with its binary factorization +bool core::find_bfc_to_refine(const monic* & m, factorization & bf){ + m = nullptr; + unsigned r = random(), sz = m_to_refine.size(); + for (unsigned k = 0; k < sz; k++) { + lpvar i = m_to_refine[(k + r) % sz]; + m = &m_emons[i]; + SASSERT (!check_monic(*m)); + if (has_real(m)) + continue; + if (m->size() == 2) { + bf.set_mon(m); + bf.push_back(factor(m->vars()[0], factor_type::VAR)); + bf.push_back(factor(m->vars()[1], factor_type::VAR)); + return true; + } + + if (find_bfc_to_refine_on_monic(*m, bf)) { + TRACE("nla_solver", + tout << "bf = "; print_factorization(bf, tout); + tout << "\nval(*m) = " << var_val(*m) << ", should be = (val(bf[0])=" << val(bf[0]) << ")*(val(bf[1]) = " << val(bf[1]) << ") = " << val(bf[0])*val(bf[1]) << "\n";); + return true; + } + } + return false; +} + +rational core::val(const factorization& f) const { + rational r(1); + for (const factor &p : f) { + r *= val(p); + } + return r; +} + +new_lemma::new_lemma(core& c, char const* name):name(name), c(c) { + c.m_lemma_vec->push_back(lemma()); +} + +new_lemma& new_lemma::operator|=(ineq const& ineq) { + if (!c.explain_ineq(*this, ineq.term(), ineq.cmp(), ineq.rs())) { + CTRACE("nla_solver", c.ineq_holds(ineq), c.print_ineq(ineq, tout) << "\n";); + SASSERT(!c.ineq_holds(ineq)); + current().push_back(ineq); + } + return *this; +} + + +new_lemma::~new_lemma() { + static int i = 0; + (void)i; + (void)name; + // code for checking lemma can be added here + TRACE("nla_solver", tout << name << " " << (++i) << "\n" << *this; ); +} + +lemma& new_lemma::current() const { + return c.m_lemma_vec->back(); +} + +new_lemma& new_lemma::operator&=(lp::explanation const& e) { + expl().add_expl(e); + return *this; +} + +new_lemma& new_lemma::operator&=(const monic& m) { + for (lpvar j : m.vars()) + *this &= j; + return *this; +} + +new_lemma& new_lemma::operator&=(const factor& f) { + if (f.type() == factor_type::VAR) + *this &= f.var(); + else + *this &= c.m_emons[f.var()]; + return *this; +} + +new_lemma& new_lemma::operator&=(const factorization& f) { + if (f.is_mon()) + return *this; + for (const auto& fc : f) { + *this &= fc; + } + return *this; +} + +new_lemma& new_lemma::operator&=(lpvar j) { + c.m_evars.explain(j, expl()); + return *this; +} + +new_lemma& new_lemma::explain_fixed(lpvar j) { + SASSERT(c.var_is_fixed(j)); + explain_existing_lower_bound(j); + explain_existing_upper_bound(j); + return *this; +} + +new_lemma& new_lemma::explain_equiv(lpvar a, lpvar b) { + SASSERT(abs(c.val(a)) == abs(c.val(b))); + if (c.vars_are_equiv(a, b)) { + *this &= a; + *this &= b; + } else { + explain_fixed(a); + explain_fixed(b); + } + return *this; +} + +new_lemma& new_lemma::explain_var_separated_from_zero(lpvar j) { + SASSERT(c.var_is_separated_from_zero(j)); + if (c.m_lar_solver.column_has_upper_bound(j) && + (c.m_lar_solver.get_upper_bound(j)< lp::zero_of_type())) + explain_existing_upper_bound(j); + else + explain_existing_lower_bound(j); + return *this; +} + +new_lemma& new_lemma::explain_existing_lower_bound(lpvar j) { + SASSERT(c.has_lower_bound(j)); + lp::explanation ex; + ex.push_back(c.m_lar_solver.get_column_lower_bound_witness(j)); + *this &= ex; + TRACE("nla_solver", tout << j << ": " << *this << "\n";); + return *this; +} + +new_lemma& new_lemma::explain_existing_upper_bound(lpvar j) { + SASSERT(c.has_upper_bound(j)); + lp::explanation ex; + ex.push_back(c.m_lar_solver.get_column_upper_bound_witness(j)); + *this &= ex; + return *this; +} + +std::ostream& new_lemma::display(std::ostream & out) const { + auto const& lemma = current(); + + for (auto p : lemma.expl()) { + out << "(" << p.ci() << ") "; + c.m_lar_solver.constraints().display(out, [this](lpvar j) { return c.var_str(j);}, p.ci()); + } + out << " ==> "; + if (lemma.ineqs().empty()) { + out << "false"; + } + else { + bool first = true; + for (auto & in : lemma.ineqs()) { + if (first) first = false; else out << " or "; + c.print_ineq(in, out); + } + } + out << "\n"; + for (lpvar j : c.collect_vars(lemma)) { + c.print_var(j, out); + } + return out; +} + +void core::negate_relation(new_lemma& lemma, unsigned j, const rational& a) { + SASSERT(val(j) != a); + lemma |= ineq(j, val(j) < a ? llc::GE : llc::LE, a); +} + +bool core::conflict_found() const { + for (const auto & l : * m_lemma_vec) { + if (l.is_conflict()) + return true; + } + return false; +} + +bool core::done() const { + return m_lemma_vec->size() >= 10 || + conflict_found() || + lp_settings().get_cancel_flag(); +} + +bool core::elist_is_consistent(const std::unordered_set & list) const { + bool first = true; + bool p; + for (lpvar j : list) { + if (first) { + p = check_monic(m_emons[j]); + first = false; + } else + if (check_monic(m_emons[j]) != p) + return false; + } + return true; +} + +bool core::elists_are_consistent(bool check_in_model) const { + std::unordered_map, hash_svector> lists; + if (!m_emons.elists_are_consistent(lists)) + return false; + + if (!check_in_model) + return true; + for (const auto & p : lists) { + if (! elist_is_consistent(p.second)) + return false; + } + return true; +} + +bool core::var_breaks_correct_monic_as_factor(lpvar j, const monic& m) const { + if (!val(var(m)).is_zero()) + return true; + + if (!val(j).is_zero()) // j was not zero: the new value does not matter - m must have another zero factor + return false; + // do we have another zero in m? + for (lpvar k : m) { + if (k != j && val(k).is_zero()) { + return false; // not breaking + } + } + // j was the only zero in m + return true; +} + +bool core::var_breaks_correct_monic(lpvar j) const { + if (is_monic_var(j) && !m_to_refine.contains(j)) { + TRACE("nla_solver", tout << "j = " << j << ", m = "; print_monic(emons()[j], tout) << "\n";); + return true; // changing the value of a correct monic + } + + for (const monic & m : emons().get_use_list(j)) { + if (m_to_refine.contains(m.var())) + continue; + if (var_breaks_correct_monic_as_factor(j, m)) + return true; + } + + return false; +} + +void core::update_to_refine_of_var(lpvar j) { + for (const monic & m : emons().get_use_list(j)) { + if (var_val(m) == mul_val(m)) + erase_from_to_refine(var(m)); + else + insert_to_refine(var(m)); + } + if (is_monic_var(j)) { + const monic& m = emons()[j]; + if (var_val(m) == mul_val(m)) + erase_from_to_refine(j); + else + insert_to_refine(j); + } +} + +bool core::var_is_big(lpvar j) const { + return !var_is_int(j) && val(j).is_big(); +} + +bool core::has_big_num(const monic& m) const { + if (var_is_big(var(m))) + return true; + for (lpvar j : m.vars()) + if (var_is_big(j)) + return true; + return false; +} + +bool core::has_real(const factorization& f) const { + for (const factor& fc: f) { + lpvar j = var(fc); + if (!var_is_int(j)) + return true; + } + return false; +} + +bool core::has_real(const monic& m) const { + for (lpvar j : m.vars()) + if (!var_is_int(j)) + return true; + return false; +} + +// returns true if the patching is blocking +bool core::is_patch_blocked(lpvar u, const lp::impq& ival) const { + TRACE("nla_solver", tout << "u = " << u << '\n';); + if (m_cautious_patching && + (!m_lar_solver.inside_bounds(u, ival) || (var_is_int(u) && ival.is_int() == false))) { + TRACE("nla_solver", tout << "u = " << u << " blocked, for feas or integr\n";); + return true; // block + } + + if (u == m_patched_var) { + TRACE("nla_solver", tout << "u == m_patched_var, no block\n";); + + return false; // do not block + } + // we can change only one variable in variables of m_patched_var + if (m_patched_monic->contains_var(u) || u == var(*m_patched_monic)) { + TRACE("nla_solver", tout << "u = " << u << " blocked as contained\n";); + return true; // block + } + + if (var_breaks_correct_monic(u)) { + TRACE("nla_solver", tout << "u = " << u << " blocked as used in a correct monomial\n";); + return true; + } + + TRACE("nla_solver", tout << "u = " << u << ", m_patched_m = "; print_monic(*m_patched_monic, tout) << + ", not blocked\n";); + + return false; +} + +// it tries to patch m_patched_var +bool core::try_to_patch(const rational& v) { + auto is_blocked = [this](lpvar u, const lp::impq& iv) { return is_patch_blocked(u, iv); }; + auto change_report = [this](lpvar u) { update_to_refine_of_var(u); }; + return m_lar_solver.try_to_patch(m_patched_var, v, is_blocked, change_report); +} + +bool in_power(const svector& vs, unsigned l) { + unsigned k = vs[l]; + return (l != 0 && vs[l - 1] == k) || (l + 1 < vs.size() && k == vs[l + 1]); +} + +bool core::to_refine_is_correct() const { + for (unsigned j = 0; j < m_lar_solver.number_of_vars(); j++) { + if (!is_monic_var(j)) continue; + bool valid = check_monic(emons()[j]); + if (valid == m_to_refine.contains(j)) { + TRACE("nla_solver", tout << "inconstency in m_to_refine : "; + print_monic(emons()[j], tout) << "\n"; + if (valid) tout << "should NOT be in to_refine\n"; + else tout << "should be in to_refine\n";); + return false; + } + } + return true; +} + +void core::patch_monomial(lpvar j) { + m_patched_monic =& (emons()[j]); + m_patched_var = j; + TRACE("nla_solver", tout << "m = "; print_monic(*m_patched_monic, tout) << "\n";); + rational v = mul_val(*m_patched_monic); + if (val(j) == v) { + erase_from_to_refine(j); + return; + } + if (!var_breaks_correct_monic(j) && try_to_patch(v)) { + SASSERT(to_refine_is_correct()); + return; + } + + // We could not patch j, now we try patching the factor variables. + TRACE("nla_solver", tout << " trying squares\n";); + // handle perfect squares + if ((*m_patched_monic).vars().size() == 2 && (*m_patched_monic).vars()[0] == (*m_patched_monic).vars()[1]) { + rational root; + if (v.is_perfect_square(root)) { + m_patched_var = (*m_patched_monic).vars()[0]; + if (!var_breaks_correct_monic(m_patched_var) && (try_to_patch(root) || try_to_patch(-root))) { + TRACE("nla_solver", tout << "patched square\n";); + return; + } + } + TRACE("nla_solver", tout << " cannot patch\n";); + return; + } + + // We have v != abc, but we need to have v = abc. + // If we patch b then b should be equal to v/ac = v/(abc/b) = b(v/abc) + if (!v.is_zero()) { + rational r = val(j) / v; + SASSERT((*m_patched_monic).is_sorted()); + TRACE("nla_solver", tout << "r = " << r << ", v = " << v << "\n";); + for (unsigned l = 0; l < (*m_patched_monic).size(); l++) { + m_patched_var = (*m_patched_monic).vars()[l]; + if (!in_power((*m_patched_monic).vars(), l) && + !var_breaks_correct_monic(m_patched_var) && + try_to_patch(r * val(m_patched_var))) { // r * val(k) gives the right value of k + TRACE("nla_solver", tout << "patched " << m_patched_var << "\n";); + SASSERT(mul_val((*m_patched_monic)) == val(j)); + erase_from_to_refine(j); + break; + } + } + } +} + +void core::patch_monomials_on_to_refine() { + auto to_refine = m_to_refine.index(); + // the rest of the function might change m_to_refine, so have to copy + unsigned sz = to_refine.size(); + + unsigned start = random(); + for (unsigned i = 0; i < sz; i++) { + patch_monomial(to_refine[(start + i) % sz]); + if (m_to_refine.size() == 0) + break; + } + TRACE("nla_solver", tout << "sz = " << sz << ", m_to_refine = " << m_to_refine.size() << + (sz > m_to_refine.size()? " less" : "same" ) << "\n";); +} + +void core::patch_monomials() { + m_cautious_patching = true; + patch_monomials_on_to_refine(); + if (m_to_refine.size() == 0 || !m_nla_settings.expensive_patching) { + return; + } + NOT_IMPLEMENTED_YET(); + m_cautious_patching = false; + patch_monomials_on_to_refine(); + m_lar_solver.push(); + save_tableau(); + constrain_nl_in_tableau(); + if (solve_tableau() && integrality_holds()) { + m_lar_solver.pop(1); + } else { + m_lar_solver.pop(); + restore_tableau(); + m_lar_solver.clear_inf_set(); + } + SASSERT(m_lar_solver.ax_is_correct()); +} + +void core::constrain_nl_in_tableau() { + NOT_IMPLEMENTED_YET(); +} + +bool core::solve_tableau() { + NOT_IMPLEMENTED_YET(); + return false; +} + +void core::restore_tableau() { + NOT_IMPLEMENTED_YET(); +} + +void core::save_tableau() { + NOT_IMPLEMENTED_YET(); +} + +bool core::integrality_holds() { + NOT_IMPLEMENTED_YET(); + return false; +} + +/** + * Cycle through different end-game solvers weighted by probability. + */ +void core::check_weighted(unsigned sz, std::pair>* checks) { + unsigned bound = 0; + for (unsigned i = 0; i < sz; ++i) + bound += checks[i].first; + uint_set seen; + while (bound > 0 && !done() && m_lemma_vec->empty()) { + unsigned n = random() % bound; + for (unsigned i = 0; i < sz; ++i) { + if (seen.contains(i)) + continue; + if (n < checks[i].first) { + seen.insert(i); + checks[i].second(); + bound -= checks[i].first; + break; + } + n -= checks[i].first; + } + } +} + + +lbool core::check(vector& l_vec) { + lp_settings().stats().m_nla_calls++; + TRACE("nla_solver", tout << "calls = " << lp_settings().stats().m_nla_calls << "\n";); + m_lar_solver.get_rid_of_inf_eps(); + m_lemma_vec = &l_vec; + if (!(m_lar_solver.get_status() == lp::lp_status::OPTIMAL || + m_lar_solver.get_status() == lp::lp_status::FEASIBLE)) { + TRACE("nla_solver", tout << "unknown because of the m_lar_solver.m_status = " << m_lar_solver.get_status() << "\n";); + return l_undef; + } + + init_to_refine(); + patch_monomials(); + set_use_nra_model(false); + if (m_to_refine.empty()) { return l_true; } + init_search(); + + lbool ret = l_undef; + + if (l_vec.empty() && !done()) + m_monomial_bounds(); + + if (l_vec.empty() && !done() && need_run_horner()) + m_horner.horner_lemmas(); + + if (l_vec.empty() && !done() && need_run_grobner()) + m_grobner(); + + if (l_vec.empty() && !done()) + m_basics.basic_lemma(true); + + if (l_vec.empty() && !done()) + m_basics.basic_lemma(false); + + if (!conflict_found() && !done() && should_run_bounded_nlsat()) + ret = bounded_nlsat(); + + + if (l_vec.empty() && !done() && ret == l_undef) { + std::function check1 = [&]() { m_order.order_lemma(); }; + std::function check2 = [&]() { m_monotone.monotonicity_lemma(); }; + std::function check3 = [&]() { m_tangents.tangent_lemma(); }; + + std::pair> checks[] = + { { 6, check1 }, + { 2, check2 }, + { 1, check3 }}; + check_weighted(3, checks); + + unsigned num_calls = lp_settings().stats().m_nla_calls; + if (!conflict_found() && m_nla_settings.run_nra && num_calls % 50 == 0 && num_calls > 500) + ret = bounded_nlsat(); + } + + if (l_vec.empty() && !done() && m_nla_settings.run_nra && ret == l_undef) { + ret = m_nra.check(); + m_stats.m_nra_calls++; + } + + if (ret == l_undef && !l_vec.empty() && m_reslim.inc()) + ret = l_false; + + m_stats.m_nla_lemmas += l_vec.size(); + for (const auto& l : l_vec) + m_stats.m_nla_explanations += static_cast(l.expl().size()); + + + TRACE("nla_solver", tout << "ret = " << ret << ", lemmas count = " << l_vec.size() << "\n";); + IF_VERBOSE(2, if(ret == l_undef) {verbose_stream() << "Monomials\n"; print_monics(verbose_stream());}); + CTRACE("nla_solver", ret == l_undef, tout << "Monomials\n"; print_monics(tout);); + return ret; +} + +bool core::should_run_bounded_nlsat() { + if (!m_nla_settings.run_nra) + return false; + if (m_nlsat_delay > m_nlsat_fails) + ++m_nlsat_fails; + return m_nlsat_delay <= m_nlsat_fails; +} + +lbool core::bounded_nlsat() { + params_ref p; + lbool ret; + p.set_uint("max_conflicts", 100); + m_nra.updt_params(p); + { + scoped_limits sl(m_reslim); + sl.push_child(&m_nra_lim); + scoped_rlimit sr(m_nra_lim, 100000); + ret = m_nra.check(); + } + p.set_uint("max_conflicts", UINT_MAX); + m_nra.updt_params(p); + m_stats.m_nra_calls++; + if (ret == l_undef) + ++m_nlsat_delay; + else { + m_nlsat_fails = 0; + m_nlsat_delay /= 2; + } + if (ret == l_true) { + m_lemma_vec->reset(); + } + return ret; +} + +bool core::no_lemmas_hold() const { + for (auto & l : * m_lemma_vec) { + if (lemma_holds(l)) { + TRACE("nla_solver", print_lemma(l, tout);); + return false; + } + } + return true; +} + +lbool core::test_check(vector& l) { + m_lar_solver.set_status(lp::lp_status::OPTIMAL); + return check(l); +} + +std::ostream& core::print_terms(std::ostream& out) const { + for (unsigned i = 0; i< m_lar_solver.terms().size(); i++) { + unsigned ext = lp::tv::mask_term(i); + if (!m_lar_solver.var_is_registered(ext)) { + out << "term is not registered\n"; + continue; + } + + const lp::lar_term & t = *m_lar_solver.terms()[i]; + out << "term:"; print_term(t, out) << std::endl; + lpvar j = m_lar_solver.external_to_local(ext); + print_var(j, out); + } + return out; +} + +std::string core::var_str(lpvar j) const { + std::string result; + if (is_monic_var(j)) + result += product_indices_str(m_emons[j].vars()) + (check_monic(m_emons[j])? "": "_"); + else + result += std::string("j") + lp::T_to_string(j); + // result += ":w" + lp::T_to_string(get_var_weight(j)); + return result; +} + +std::ostream& core::print_term( const lp::lar_term& t, std::ostream& out) const { + return lp::print_linear_combination_customized( + t.coeffs_as_vector(), + [this](lpvar j) { return var_str(j); }, + out); +} + + + + +std::unordered_set core::get_vars_of_expr_with_opening_terms(const nex *e ) { + auto ret = get_vars_of_expr(e); + auto & ls = m_lar_solver; + svector added; + for (auto j : ret) { + added.push_back(j); + } + for (unsigned i = 0; i < added.size(); ++i) { + lpvar j = added[i]; + if (ls.column_corresponds_to_term(j)) { + const auto& t = m_lar_solver.get_term(lp::tv::raw(ls.local_to_external(j))); + for (auto p : t) { + if (ret.find(p.column()) == ret.end()) { + added.push_back(p.column()); + ret.insert(p.column()); + } + } + } + } + return ret; +} + + +bool core::is_nl_var(lpvar j) const { + return is_monic_var(j) || m_emons.is_used_in_monic(j); +} + + +unsigned core::get_var_weight(lpvar j) const { + unsigned k; + switch (m_lar_solver.get_column_type(j)) { + + case lp::column_type::fixed: + k = 0; + break; + case lp::column_type::boxed: + k = 3; + break; + case lp::column_type::lower_bound: + case lp::column_type::upper_bound: + k = 6; + break; + case lp::column_type::free_column: + k = 9; + break; + default: + UNREACHABLE(); + break; + } + if (is_monic_var(j)) { + k++; + if (m_to_refine.contains(j)) + k++; + } + return k; +} + + +void core::set_active_vars_weights(nex_creator& nc) { + nc.set_number_of_vars(m_lar_solver.column_count()); + for (lpvar j : active_var_set()) + nc.set_var_weight(j, get_var_weight(j)); +} + +bool core::influences_nl_var(lpvar j) const { + if (lp::tv::is_term(j)) + j = lp::tv::unmask_term(j); + if (is_nl_var(j)) + return true; + for (const auto & c : m_lar_solver.A_r().m_columns[j]) { + lpvar basic_in_row = m_lar_solver.r_basis()[c.var()]; + if (is_nl_var(basic_in_row)) + return true; + } + return false; +} + +void core::collect_statistics(::statistics & st) { + st.update("arith-nla-explanations", m_stats.m_nla_explanations); + st.update("arith-nla-lemmas", m_stats.m_nla_lemmas); + st.update("arith-nra-calls", m_stats.m_nra_calls); +} + + +} // end of nla + diff --git a/src/math/lp/nla_core.h b/src/math/lp/nla_core.h index 5c44f53a6..0f1990933 100644 --- a/src/math/lp/nla_core.h +++ b/src/math/lp/nla_core.h @@ -27,6 +27,9 @@ #include "math/lp/nla_intervals.h" #include "nlsat/nlsat_solver.h" +namespace nra { + class solver; +} namespace nla { @@ -139,8 +142,19 @@ struct pp_factorization { }; class core { + friend struct common; friend class new_lemma; friend class grobner; + friend class order; + friend struct basics; + friend struct tangents; + friend class monotone; + friend struct nla_settings; + friend class intervals; + friend class horner; + friend class solver; + friend class monomial_bounds; + friend class nra::solver; struct stats { unsigned m_nla_explanations; @@ -158,9 +172,11 @@ class core { bool should_run_bounded_nlsat(); lbool bounded_nlsat(); -public: + var_eqs m_evars; + lp::lar_solver& m_lar_solver; + reslimit& m_reslim; vector * m_lemma_vec; lp::u_set m_to_refine; tangents m_tangents; @@ -169,23 +185,21 @@ public: monotone m_monotone; intervals m_intervals; monomial_bounds m_monomial_bounds; + nla_settings m_nla_settings; + horner m_horner; - nla_settings m_nla_settings; grobner m_grobner; -private: emonics m_emons; svector m_add_buffer; mutable lp::u_set m_active_var_set; reslimit m_nra_lim; -public: - reslimit& m_reslim; - bool m_use_nra_model; + + bool m_use_nra_model = false; nra::solver m_nra; -private: - bool m_cautious_patching; - lpvar m_patched_var; - monic const* m_patched_monic; + bool m_cautious_patching = true; + lpvar m_patched_var = 0; + monic const* m_patched_monic = nullptr; void check_weighted(unsigned sz, std::pair>* checks); diff --git a/src/math/lp/nla_grobner.cpp b/src/math/lp/nla_grobner.cpp index c5d1e7c2d..0857db249 100644 --- a/src/math/lp/nla_grobner.cpp +++ b/src/math/lp/nla_grobner.cpp @@ -23,7 +23,7 @@ namespace nla { grobner::grobner(core* c): common(c), m_pdd_manager(m_core.m_lar_solver.number_of_vars()), - m_pdd_grobner(m_core.m_reslim, m_pdd_manager), + m_solver(m_core.m_reslim, m_pdd_manager), m_lar_solver(m_core.m_lar_solver) {} @@ -34,30 +34,16 @@ namespace nla { void grobner::operator()() { unsigned& quota = c().m_nla_settings.grobner_quota; - if (quota == 1) { + if (quota == 1) return; - } - c().clear_and_resize_active_var_set(); - find_nl_cluster(); - c().lp_settings().stats().m_grobner_calls++; + lp_settings().stats().m_grobner_calls++; + find_nl_cluster(); configure(); - m_pdd_grobner.saturate(); - bool conflict = false; - unsigned n = m_pdd_grobner.number_of_conflicts_to_report(); - SASSERT(n > 0); - for (auto eq : m_pdd_grobner.equations()) { - if (check_pdd_eq(eq)) { - conflict = true; - if (--n == 0) - break; - } - } - TRACE("grobner", m_pdd_grobner.display(tout)); - if (conflict) { - IF_VERBOSE(2, verbose_stream() << "grobner conflict\n"); + m_solver.saturate(); + + if (find_conflict()) return; - } if (propagate_bounds()) return; @@ -68,22 +54,33 @@ namespace nla { if (quota > 1) quota--; - IF_VERBOSE(2, verbose_stream() << "grobner miss, quota " << quota << "\n"); IF_VERBOSE(4, diagnose_pdd_miss(verbose_stream())); + } + bool grobner::find_conflict() { + unsigned conflicts = 0; + for (auto eq : m_solver.equations()) { + if (check_pdd_eq(eq) && ++conflicts >= m_solver.number_of_conflicts_to_report()) + break; + } + TRACE("grobner", m_solver.display(tout)); + IF_VERBOSE(2, if (conflicts > 0) verbose_stream() << "grobner conflict\n"); -#if 0 - // diagnostics: did we miss something - vector eqs; - for (auto eq : m_pdd_grobner.equations()) - eqs.push_back(eq->poly()); - m_nra.check(eqs); -#endif - + return conflicts > 0; + } + + bool grobner::propagate_bounds() { + for (auto eq : m_solver.equations()) { + + } + return false; + } + + bool grobner::propagate_eqs() { #if 0 bool propagated = false; - for (auto eq : m_pdd_grobner.equations()) { + for (auto eq : m_solver.equations()) { auto const& p = eq->poly(); if (p.is_offset()) { lpvar v = p.var(); @@ -103,34 +100,25 @@ namespace nla { propagated = true; } } - if (propagated) + if (propagated) return; #endif - - - } - - bool grobner::propagate_bounds() { - return false; - } - - bool grobner::propagate_eqs() { return false; } void grobner::configure() { - m_pdd_grobner.reset(); + m_solver.reset(); try { set_level2var(); TRACE("grobner", tout << "base vars: "; for (lpvar j : c().active_var_set()) - if (c().m_lar_solver.is_base(j)) + if (m_lar_solver.is_base(j)) tout << "j" << j << " "; tout << "\n"); for (lpvar j : c().active_var_set()) { - if (c().m_lar_solver.is_base(j)) - add_row(c().m_lar_solver.basic2row(j)); + if (m_lar_solver.is_base(j)) + add_row(m_lar_solver.basic2row(j)); if (c().is_monic_var(j) && c().var_is_fixed(j)) add_fixed_monic(j); @@ -140,7 +128,7 @@ namespace nla { IF_VERBOSE(2, verbose_stream() << "pdd throw\n"); return; } - TRACE("grobner", m_pdd_grobner.display(tout)); + TRACE("grobner", m_solver.display(tout)); #if 0 IF_VERBOSE(2, m_pdd_grobner.display(verbose_stream())); @@ -156,14 +144,14 @@ namespace nla { #endif struct dd::solver::config cfg; - cfg.m_max_steps = m_pdd_grobner.equations().size(); + cfg.m_max_steps = m_solver.equations().size(); cfg.m_max_simplified = c().m_nla_settings.grobner_max_simplified; cfg.m_eqs_growth = c().m_nla_settings.grobner_eqs_growth; cfg.m_expr_size_growth = c().m_nla_settings.grobner_expr_size_growth; cfg.m_expr_degree_growth = c().m_nla_settings.grobner_expr_degree_growth; cfg.m_number_of_conflicts_to_report = c().m_nla_settings.grobner_number_of_conflicts_to_report; - m_pdd_grobner.set(cfg); - m_pdd_grobner.adjust_cfg(); + m_solver.set(cfg); + m_solver.adjust_cfg(); m_pdd_manager.set_max_num_nodes(10000); // or something proportional to the number of initial nodes. } @@ -173,7 +161,7 @@ namespace nla { dd::pdd_eval eval; eval.var2val() = [&](unsigned j){ return val(j); }; - for (auto* e : m_pdd_grobner.equations()) { + for (auto* e : m_solver.equations()) { dd::pdd p = e->poly(); rational v = eval(p); if (!v.is_zero()) { @@ -190,6 +178,14 @@ namespace nla { out << "]\n"; } } + +#if 0 + // diagnostics: did we miss something + vector eqs; + for (auto eq : m_solver.equations()) + eqs.push_back(eq->poly()); + m_nra.check(eqs); +#endif return out; } @@ -203,7 +199,7 @@ namespace nla { scoped_dep_interval i(di), i_wd(di); eval.get_interval(e->poly(), i); if (!di.separated_from_zero(i)) { - TRACE("grobner", m_pdd_grobner.display(tout << "not separated from 0 ", *e) << "\n"; + TRACE("grobner", m_solver.display(tout << "not separated from 0 ", *e) << "\n"; eval.get_interval_distributed(e->poly(), i); tout << "separated from 0: " << di.separated_from_zero(i) << "\n"; for (auto j : e->poly().free_vars()) { @@ -221,12 +217,12 @@ namespace nla { lemma &= e; }; if (di.check_interval_for_conflict_on_zero(i_wd, e->dep(), f)) { - TRACE("grobner", m_pdd_grobner.display(tout << "conflict ", *e) << "\n"); + TRACE("grobner", m_solver.display(tout << "conflict ", *e) << "\n"); lp_settings().stats().m_grobner_conflicts++; return true; } else { - TRACE("grobner", m_pdd_grobner.display(tout << "no conflict ", *e) << "\n"); + TRACE("grobner", m_solver.display(tout << "no conflict ", *e) << "\n"); return false; } } @@ -260,8 +256,6 @@ namespace nla { for (auto& rc : matrix.m_rows[row]) add_var_and_its_factors_to_q_and_collect_new_rows(rc.var(), q); } - - } const rational& grobner::val_of_fixed_var_with_deps(lpvar j, u_dependency*& dep) { @@ -358,11 +352,11 @@ namespace nla { void grobner::add_eq(dd::pdd& p, u_dependency* dep) { unsigned v; dd::pdd q(m_pdd_manager); - m_pdd_grobner.simplify(p, dep); + m_solver.simplify(p, dep); if (is_solved(p, v, q)) - m_pdd_grobner.add_subst(v, q, dep); + m_solver.add_subst(v, q, dep); else - m_pdd_grobner.add(p, dep); + m_solver.add(p, dep); } void grobner::add_fixed_monic(unsigned j) { @@ -385,7 +379,7 @@ namespace nla { } - void grobner::find_nl_cluster() { + void grobner::find_nl_cluster() { prepare_rows_and_active_vars(); svector q; TRACE("grobner", for (lpvar j : c().m_to_refine) print_monic(c().emons()[j], tout) << "\n";); diff --git a/src/math/lp/nla_grobner.h b/src/math/lp/nla_grobner.h index a9b284a74..716554a4e 100644 --- a/src/math/lp/nla_grobner.h +++ b/src/math/lp/nla_grobner.h @@ -20,17 +20,21 @@ namespace nla { class grobner : common { dd::pdd_manager m_pdd_manager; - dd::solver m_pdd_grobner; + dd::solver m_solver; lp::lar_solver& m_lar_solver; lp::u_set m_rows; + lp::lp_settings& lp_settings(); + + // solving + bool find_conflict(); bool propagate_bounds(); bool propagate_eqs(); - lp::lp_settings& lp_settings(); + void find_nl_cluster(); void prepare_rows_and_active_vars(); void add_var_and_its_factors_to_q_and_collect_new_rows(lpvar j, svector& q); - void display_matrix_of_m_rows(std::ostream& out) const; + void add_row(const vector>& row); void add_fixed_monic(unsigned j); bool is_solved(dd::pdd const& p, unsigned& v, dd::pdd& r); @@ -40,6 +44,8 @@ namespace nla { dd::pdd pdd_expr(const rational& c, lpvar j, u_dependency*&); void set_level2var(); void configure(); + + void display_matrix_of_m_rows(std::ostream& out) const; std::ostream& diagnose_pdd_miss(std::ostream& out); public: From 4a192850f2ece0a1a714420820566419456a2335 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Thu, 14 Jul 2022 11:06:43 -0700 Subject: [PATCH 105/125] add var_factors Add routine to partially factor polynomials. It factors out variables. --- src/math/dd/dd_pdd.cpp | 72 ++++++++++++++++++++++++++++++++++++++++++ src/math/dd/dd_pdd.h | 5 +++ src/test/pdd.cpp | 57 +++++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+) diff --git a/src/math/dd/dd_pdd.cpp b/src/math/dd/dd_pdd.cpp index 455fc4039..8604af3fb 100644 --- a/src/math/dd/dd_pdd.cpp +++ b/src/math/dd/dd_pdd.cpp @@ -1311,6 +1311,78 @@ namespace dd { return m.mk_var(var())*h + l; } + std::pair pdd::var_factors() { + if (is_val()) + return { unsigned_vector(), *this }; + unsigned v = var(); + if (lo().is_val()) { + if (!lo().is_zero()) + return { unsigned_vector(), *this }; + auto [vars, p] = hi().var_factors(); + vars.push_back(v); + return {vars, p}; + } + auto [lo_vars, q] = lo().var_factors(); + if (lo_vars.empty()) + return { unsigned_vector(), *this }; + + unsigned_vector lo_and_hi; + auto merge = [&](unsigned_vector& lo_vars, unsigned_vector& hi_vars) { + unsigned ir = 0, jr = 0; + for (unsigned i = 0, j = 0; i < lo_vars.size() || j < hi_vars.size(); ) { + if (i == lo_vars.size()) { + hi_vars[jr++] = hi_vars[j++]; + continue; + } + if (j == hi_vars.size()) { + lo_vars[ir++] = lo_vars[i++]; + continue; + } + if (lo_vars[i] == hi_vars[j]) { + lo_and_hi.push_back(lo_vars[i]); + ++i; + ++j; + continue; + } + unsigned lvl_lo = m.m_var2level[lo_vars[i]]; + unsigned lvl_hi = m.m_var2level[hi_vars[j]]; + if (lvl_lo > lvl_hi) { + hi_vars[jr++] = hi_vars[j++]; + continue; + } + else { + lo_vars[ir++] = lo_vars[i++]; + continue; + } + } + lo_vars.shrink(ir); + hi_vars.shrink(jr); + }; + + auto mul = [&](unsigned_vector const& vars, pdd p) { + for (auto v : vars) + p *= m.mk_var(v); + return p; + }; + + auto [hi_vars, p] = hi().var_factors(); + if (lo_vars.back() == v) { + lo_vars.pop_back(); + merge(lo_vars, hi_vars); + lo_and_hi.push_back(v); + return { lo_and_hi, mul(lo_vars, q) + mul(hi_vars, p) }; + } + if (hi_vars.empty()) + return { unsigned_vector(), *this }; + + merge(lo_vars, hi_vars); + hi_vars.push_back(v); + if (lo_and_hi.empty()) + return { unsigned_vector(), *this }; + else + return { lo_and_hi, mul(lo_vars, q) + mul(hi_vars, p) }; + } + std::ostream& operator<<(std::ostream& out, pdd const& b) { return b.display(out); } diff --git a/src/math/dd/dd_pdd.h b/src/math/dd/dd_pdd.h index 13c6c0605..45d646c51 100644 --- a/src/math/dd/dd_pdd.h +++ b/src/math/dd/dd_pdd.h @@ -364,6 +364,11 @@ namespace dd { bool different_leading_term(pdd const& other) const { return m.different_leading_term(*this, other); } void factor(unsigned v, unsigned degree, pdd& lc, pdd& rest) { m.factor(*this, v, degree, lc, rest); } + /** + * \brief factor out variables + */ + std::pair var_factors(); + pdd subst_val(vector> const& s) const { return m.subst_val(*this, s); } pdd subst_val(unsigned v, rational const& val) const { return m.subst_val(*this, v, val); } diff --git a/src/test/pdd.cpp b/src/test/pdd.cpp index b0cbb657c..9139e499f 100644 --- a/src/test/pdd.cpp +++ b/src/test/pdd.cpp @@ -323,6 +323,62 @@ public : SASSERT(!(2*a*b + 3*b + 2).is_non_zero()); } + static void factors() { + pdd_manager m(3); + pdd v0 = m.mk_var(0); + pdd v1 = m.mk_var(1); + pdd v2 = m.mk_var(2); + pdd v3 = m.mk_var(3); + pdd v4 = m.mk_var(4); + pdd c1 = v0 * v1 * v2 + v2 * v0 + v1 + 1; + { + auto [vars, p] = c1.var_factors(); + VERIFY(p == c1 && vars.empty()); + } + { + auto q = c1 * v4; + auto [vars, p] = q.var_factors(); + std::cout << p << " " << vars << "\n"; + VERIFY(p == c1 && vars.size() == 1 && vars[0] == 4); + } + for (unsigned i = 0; i < 5; ++i) { + auto v = m.mk_var(i); + auto q = c1 * v; + std::cout << i << ": " << q << "\n"; + auto [vars, p] = q.var_factors(); + std::cout << p << " " << vars << "\n"; + VERIFY(p == c1 && vars.size() == 1 && vars[0] == i); + } + for (unsigned i = 0; i < 5; ++i) { + for (unsigned j = 0; j < 5; ++j) { + auto vi = m.mk_var(i); + auto vj = m.mk_var(j); + auto q = c1 * vi * vj; + auto [vars, p] = q.var_factors(); + std::cout << p << " " << vars << "\n"; + VERIFY(p == c1 && vars.size() == 2); + VERIFY(vars[0] == i || vars[1] == i); + VERIFY(vars[0] == j || vars[1] == j); + } + } + for (unsigned i = 0; i < 5; ++i) { + for (unsigned j = i; j < 5; ++j) { + for (unsigned k = j; k < 5; ++k) { + auto vi = m.mk_var(i); + auto vj = m.mk_var(j); + auto vk = m.mk_var(k); + auto q = c1 * vi * vj * vk; + auto [vars, p] = q.var_factors(); + std::cout << p << " " << vars << "\n"; + VERIFY(p == c1 && vars.size() == 3); + VERIFY(vars[0] == i || vars[1] == i || vars[2] == i); + VERIFY(vars[0] == j || vars[1] == j || vars[2] == j); + VERIFY(vars[0] == k || vars[1] == k || vars[2] == k); + } + } + } + } + }; } @@ -337,4 +393,5 @@ void tst_pdd() { dd::test::order(); dd::test::order_lm(); dd::test::mod4_operations(); + dd::test::factors(); } From 2f5fef92b7210c5465c3ebb8cebc2158ca835e1d Mon Sep 17 00:00:00 2001 From: Stefan Muenzel Date: Fri, 15 Jul 2022 01:11:56 +0700 Subject: [PATCH 106/125] Cache param descrs when modifying solver params (#6156) --- src/api/api_solver.cpp | 8 +++++--- src/api/api_solver.h | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/api/api_solver.cpp b/src/api/api_solver.cpp index 53fe1166d..bf8a52d51 100644 --- a/src/api/api_solver.cpp +++ b/src/api/api_solver.cpp @@ -391,9 +391,11 @@ extern "C" { bool new_model = params.get_bool("model", true); if (old_model != new_model) to_solver_ref(s)->set_produce_models(new_model); - param_descrs r; - to_solver_ref(s)->collect_param_descrs(r); - context_params::collect_solver_param_descrs(r); + param_descrs& r = to_solver(s)->m_param_descrs; + if(r.size () == 0) { + to_solver_ref(s)->collect_param_descrs(r); + context_params::collect_solver_param_descrs(r); + } params.validate(r); to_solver_ref(s)->updt_params(params); } diff --git a/src/api/api_solver.h b/src/api/api_solver.h index 62be1ee60..5214d274c 100644 --- a/src/api/api_solver.h +++ b/src/api/api_solver.h @@ -42,6 +42,7 @@ struct Z3_solver_ref : public api::object { scoped_ptr m_solver_factory; ref m_solver; params_ref m_params; + param_descrs m_param_descrs; symbol m_logic; scoped_ptr m_pp; scoped_ptr m_cmd_context; From af80bd18ce10daad8c2ae6715629b657a10ca9ae Mon Sep 17 00:00:00 2001 From: Andrea Lattuada Date: Thu, 14 Jul 2022 13:43:57 -0700 Subject: [PATCH 107/125] Flush the trace stream before displaying sat results (#6162) --- src/cmd_context/cmd_context.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp index baaf483cd..32dd6aee5 100644 --- a/src/cmd_context/cmd_context.cpp +++ b/src/cmd_context/cmd_context.cpp @@ -1827,6 +1827,10 @@ void cmd_context::add_declared_functions(model& mdl) { } void cmd_context::display_sat_result(lbool r) { + if (has_manager() && m().has_trace_stream()) { + m().trace_stream().flush(); + } + switch (r) { case l_true: regular_stream() << "sat" << std::endl; From 7c177584f31e9b2f0aa90d64338deb8b6c755e1a Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Thu, 14 Jul 2022 15:45:00 -0700 Subject: [PATCH 108/125] add propagators to grobner --- src/math/grobner/pdd_solver.cpp | 46 +++++--- src/math/grobner/pdd_solver.h | 1 + src/math/lp/nla_grobner.cpp | 183 +++++++++++++++++++++++--------- src/math/lp/nla_grobner.h | 27 +++-- 4 files changed, 184 insertions(+), 73 deletions(-) diff --git a/src/math/grobner/pdd_solver.cpp b/src/math/grobner/pdd_solver.cpp index 51aed555c..d9171aabd 100644 --- a/src/math/grobner/pdd_solver.cpp +++ b/src/math/grobner/pdd_solver.cpp @@ -169,7 +169,7 @@ namespace dd { /* Use the given equation to simplify equations in set */ - void solver::simplify_using(equation_vector& set, equation const& eq) { + void solver::simplify_using(equation_vector& set, std::function& simplifier) { struct scoped_update { equation_vector& set; unsigned i, j, sz; @@ -191,7 +191,7 @@ namespace dd { equation& target = *set[sr.i]; bool changed_leading_term = false; bool simplified = true; - simplified = !done() && try_simplify_using(target, eq, changed_leading_term); + simplified = !done() && simplifier(target, changed_leading_term); if (simplified && is_trivial(target)) { retire(&target); @@ -210,6 +210,13 @@ namespace dd { sr.nextj(); } } + } + + void solver::simplify_using(equation_vector& set, equation const& eq) { + std::function simplifier = [&](equation& target, bool& changed_leading_term) { + return try_simplify_using(target, eq, changed_leading_term); + }; + simplify_using(set, simplifier); } /* @@ -353,7 +360,8 @@ namespace dd { } void solver::add(pdd const& p, u_dependency * dep) { - if (p.is_zero()) return; + if (p.is_zero()) + return; equation * eq = alloc(equation, p, dep); if (check_conflict(*eq)) return; @@ -365,18 +373,30 @@ namespace dd { } void solver::add_subst(unsigned v, pdd const& p, u_dependency* dep) { - SASSERT(m_processed.empty()); - SASSERT(m_solved.empty()); - m_subst.push_back({v, p, dep}); + if (!m_var2level.empty()) + m_levelp1 = std::max(m_var2level[v]+1, std::max(m_var2level[p.var()]+1, m_levelp1)); - for (auto* e : m_to_simplify) { - auto r = e->poly().subst_pdd(v, p); - if (r == e->poly()) - continue; - *e = m_dep_manager.mk_join(dep, e->dep()); - *e = r; - } + std::function simplifier = [&](equation& dst, bool& changed_leading_term) { + auto r = dst.poly().subst_pdd(v, p); + if (r == dst.poly()) + return false; + if (is_too_complex(r)) { + m_too_complex = true; + return false; + } + changed_leading_term = m.different_leading_term(r, dst.poly()); + dst = r; + dst = m_dep_manager.mk_join(dst.dep(), dep); + update_stats_max_degree_and_size(dst); + return true; + }; + if (!done()) + simplify_using(m_processed, simplifier); + if (!done()) + simplify_using(m_to_simplify, simplifier); + if (!done()) + simplify_using(m_solved, simplifier); } void solver::simplify(pdd& p, u_dependency*& d) { diff --git a/src/math/grobner/pdd_solver.h b/src/math/grobner/pdd_solver.h index 3f3c4e73e..0069eadf3 100644 --- a/src/math/grobner/pdd_solver.h +++ b/src/math/grobner/pdd_solver.h @@ -164,6 +164,7 @@ private: void simplify_using(equation& eq, equation_vector const& eqs); void simplify_using(equation_vector& set, equation const& eq); void simplify_using(equation & dst, equation const& src, bool& changed_leading_term); + void simplify_using(equation_vector& set, std::function& simplifier); bool try_simplify_using(equation& target, equation const& source, bool& changed_leading_term); bool is_trivial(equation const& eq) const { return eq.poly().is_zero(); } diff --git a/src/math/lp/nla_grobner.cpp b/src/math/lp/nla_grobner.cpp index 0857db249..c6540c26d 100644 --- a/src/math/lp/nla_grobner.cpp +++ b/src/math/lp/nla_grobner.cpp @@ -42,28 +42,43 @@ namespace nla { configure(); m_solver.saturate(); - if (find_conflict()) + if (is_conflicting()) return; +#if 0 if (propagate_bounds()) return; if (propagate_eqs()) return; + if (propagate_factorization()) + return; +#endif if (quota > 1) quota--; IF_VERBOSE(2, verbose_stream() << "grobner miss, quota " << quota << "\n"); IF_VERBOSE(4, diagnose_pdd_miss(verbose_stream())); + +#if 0 + // diagnostics: did we miss something + vector eqs; + for (auto eq : m_solver.equations()) + eqs.push_back(eq->poly()); + c().m_nra.check(eqs); +#endif } - bool grobner::find_conflict() { + bool grobner::is_conflicting() { unsigned conflicts = 0; - for (auto eq : m_solver.equations()) { - if (check_pdd_eq(eq) && ++conflicts >= m_solver.number_of_conflicts_to_report()) + for (auto eq : m_solver.equations()) + if (is_conflicting(*eq) && ++conflicts >= m_solver.number_of_conflicts_to_report()) break; - } + + if (conflicts > 0) + lp_settings().stats().m_grobner_conflicts++; + TRACE("grobner", m_solver.display(tout)); IF_VERBOSE(2, if (conflicts > 0) verbose_stream() << "grobner conflict\n"); @@ -71,39 +86,100 @@ namespace nla { } bool grobner::propagate_bounds() { - for (auto eq : m_solver.equations()) { + unsigned bounds = 0; + for (auto eq : m_solver.equations()) + if (propagate_bounds(*eq) && ++bounds >= m_solver.number_of_conflicts_to_report()) + return true; + return bounds > 0; + } + bool grobner::propagate_eqs() { + unsigned fixed = 0; + for (auto eq : m_solver.equations()) + if (propagate_fixed(*eq) && ++fixed >= m_solver.number_of_conflicts_to_report()) + return true; + return fixed > 0; + } + + bool grobner::propagate_factorization() { + unsigned changed = 0; + for (auto eq : m_solver.equations()) + if (propagate_factorization(*eq) && ++changed >= m_solver.number_of_conflicts_to_report()) + return true; + return changed > 0; + } + + /** + \brief detect equalities + - k*x = 0, that is x = 0 + - ax + b = 0 + */ + typedef lp::lar_term term; + bool grobner::propagate_fixed(const dd::solver::equation& eq) { + dd::pdd const& p = eq.poly(); + //IF_VERBOSE(0, verbose_stream() << p << "\n"); + if (p.is_unary()) { + unsigned v = p.var(); + if (c().var_is_fixed(v)) + return false; + new_lemma lemma(c(), "pdd-eq"); + add_dependencies(lemma, eq); + lemma |= ineq(v, llc::EQ, rational::zero()); + return true; + } + if (p.is_offset()) { + unsigned v = p.var(); + if (c().var_is_fixed(v)) + return false; + rational a = p.hi().val(); + rational b = -p.lo().val(); + rational d = lcm(denominator(a), denominator(b)); + a *= d; + b *= d; + new_lemma lemma(c(), "pdd-eq"); + add_dependencies(lemma, eq); + lemma |= ineq(term(a, v), llc::EQ, b); + return true; + } + + return false; + } + + /** + \brief detect simple factors + x*q = 0 => x = 0 or q = 0 + */ + + bool grobner::propagate_factorization(const dd::solver::equation& eq) { + dd::pdd const& p = eq.poly(); + if (!p.is_val() && p.lo().is_zero() && !p.hi().is_val() && p.hi().is_linear()) { + //IF_VERBOSE(0, verbose_stream() << "factored " << p << "\n"); + unsigned v = p.var(); + auto q = p.hi(); + new_lemma lemma(c(), "pdd-factored"); + add_dependencies(lemma, eq); + term t; + while (!q.is_val()) { + t.add_monomial(q.hi().val(), q.var()); + q = q.lo(); + } + lemma |= ineq(v, llc::EQ, rational::zero()); + lemma |= ineq(t, llc::EQ, -q.val()); + //lemma.display(verbose_stream()); + return true; } return false; } - bool grobner::propagate_eqs() { -#if 0 - bool propagated = false; - for (auto eq : m_solver.equations()) { - auto const& p = eq->poly(); - if (p.is_offset()) { - lpvar v = p.var(); - if (m_lar_solver.column_has_lower_bound(v) && - m_lar_solver.column_has_upper_bound(v)) - continue; - rational fixed_val = -p.lo().val(); - lp::explanation ex; - u_dependency_manager dm; - vector lv; - dm.linearize(eq->dep(), lv); - for (unsigned ci : lv) - ex.push_back(ci); - new_lemma lemma(*this, "pdd-eq"); - lemma &= ex; - lemma |= ineq(v, llc::EQ, fixed_val); - propagated = true; - } - } - if (propagated) - return; -#endif - return false; + + void grobner::add_dependencies(new_lemma& lemma, const dd::solver::equation& eq) { + lp::explanation ex; + u_dependency_manager dm; + vector lv; + dm.linearize(eq.dep(), lv); + for (unsigned ci : lv) + ex.push_back(ci); + lemma &= ex; } void grobner::configure() { @@ -178,18 +254,10 @@ namespace nla { out << "]\n"; } } - -#if 0 - // diagnostics: did we miss something - vector eqs; - for (auto eq : m_solver.equations()) - eqs.push_back(eq->poly()); - m_nra.check(eqs); -#endif return out; } - bool grobner::check_pdd_eq(const dd::solver::equation* e) { + bool grobner::is_conflicting(const dd::solver::equation& e) { auto& di = c().m_intervals.get_dep_intervals(); dd::pdd_interval eval(di); eval.var2interval() = [this](lpvar j, bool deps, scoped_dep_interval& a) { @@ -197,12 +265,12 @@ namespace nla { else c().m_intervals.set_var_interval(j, a); }; scoped_dep_interval i(di), i_wd(di); - eval.get_interval(e->poly(), i); + eval.get_interval(e.poly(), i); if (!di.separated_from_zero(i)) { - TRACE("grobner", m_solver.display(tout << "not separated from 0 ", *e) << "\n"; - eval.get_interval_distributed(e->poly(), i); + TRACE("grobner", m_solver.display(tout << "not separated from 0 ", e) << "\n"; + eval.get_interval_distributed(e.poly(), i); tout << "separated from 0: " << di.separated_from_zero(i) << "\n"; - for (auto j : e->poly().free_vars()) { + for (auto j : e.poly().free_vars()) { scoped_dep_interval a(di); c().m_intervals.set_var_interval(j, a); c().m_intervals.display(tout << "j" << j << " ", a); tout << " "; @@ -211,22 +279,35 @@ namespace nla { return false; } - eval.get_interval(e->poly(), i_wd); + eval.get_interval(e.poly(), i_wd); std::function f = [this](const lp::explanation& e) { new_lemma lemma(m_core, "pdd"); lemma &= e; }; - if (di.check_interval_for_conflict_on_zero(i_wd, e->dep(), f)) { - TRACE("grobner", m_solver.display(tout << "conflict ", *e) << "\n"); - lp_settings().stats().m_grobner_conflicts++; + if (di.check_interval_for_conflict_on_zero(i_wd, e.dep(), f)) { + TRACE("grobner", m_solver.display(tout << "conflict ", e) << "\n"); return true; } else { - TRACE("grobner", m_solver.display(tout << "no conflict ", *e) << "\n"); + TRACE("grobner", m_solver.display(tout << "no conflict ", e) << "\n"); return false; } } + bool grobner::propagate_bounds(const dd::solver::equation& e) { + return false; + // TODO + auto& di = c().m_intervals.get_dep_intervals(); + dd::pdd_interval eval(di); + eval.var2interval() = [this](lpvar j, bool deps, scoped_dep_interval& a) { + if (deps) c().m_intervals.set_var_interval(j, a); + else c().m_intervals.set_var_interval(j, a); + }; + scoped_dep_interval i(di), i_wd(di); + eval.get_interval(e.poly(), i); + return false; + } + void grobner::add_var_and_its_factors_to_q_and_collect_new_rows(lpvar j, svector & q) { if (c().active_var_set_contains(j)) return; diff --git a/src/math/lp/nla_grobner.h b/src/math/lp/nla_grobner.h index 716554a4e..902ad3a46 100644 --- a/src/math/lp/nla_grobner.h +++ b/src/math/lp/nla_grobner.h @@ -27,23 +27,32 @@ namespace nla { lp::lp_settings& lp_settings(); // solving - bool find_conflict(); + bool is_conflicting(); + bool is_conflicting(const dd::solver::equation& eq); + bool propagate_bounds(); + bool propagate_bounds(const dd::solver::equation& eq); + bool propagate_eqs(); - + bool propagate_fixed(const dd::solver::equation& eq); + + bool propagate_factorization(); + bool propagate_factorization(const dd::solver::equation& eq); + + void add_dependencies(new_lemma& lemma, const dd::solver::equation& eq); + + // setup + void configure(); + void set_level2var(); void find_nl_cluster(); void prepare_rows_and_active_vars(); - void add_var_and_its_factors_to_q_and_collect_new_rows(lpvar j, svector& q); - + void add_var_and_its_factors_to_q_and_collect_new_rows(lpvar j, svector& q); void add_row(const vector>& row); void add_fixed_monic(unsigned j); bool is_solved(dd::pdd const& p, unsigned& v, dd::pdd& r); - void add_eq(dd::pdd& p, u_dependency* dep); - bool check_pdd_eq(const dd::solver::equation*); + void add_eq(dd::pdd& p, u_dependency* dep); const rational& val_of_fixed_var_with_deps(lpvar j, u_dependency*& dep); - dd::pdd pdd_expr(const rational& c, lpvar j, u_dependency*&); - void set_level2var(); - void configure(); + dd::pdd pdd_expr(const rational& c, lpvar j, u_dependency*& dep); void display_matrix_of_m_rows(std::ostream& out) const; std::ostream& diagnose_pdd_miss(std::ostream& out); From b29cdca936e7dc7efebc17f5430ce74f342ff839 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Thu, 14 Jul 2022 21:24:27 -0700 Subject: [PATCH 109/125] integrate factorization to Grobner --- src/math/dd/dd_pdd.cpp | 2 +- src/math/dd/dd_pdd.h | 2 +- src/math/lp/nla_core.cpp | 26 +++++++++++++------- src/math/lp/nla_grobner.cpp | 47 ++++++++++++++++++------------------- 4 files changed, 42 insertions(+), 35 deletions(-) diff --git a/src/math/dd/dd_pdd.cpp b/src/math/dd/dd_pdd.cpp index 8604af3fb..3c50076da 100644 --- a/src/math/dd/dd_pdd.cpp +++ b/src/math/dd/dd_pdd.cpp @@ -1311,7 +1311,7 @@ namespace dd { return m.mk_var(var())*h + l; } - std::pair pdd::var_factors() { + std::pair pdd::var_factors() const { if (is_val()) return { unsigned_vector(), *this }; unsigned v = var(); diff --git a/src/math/dd/dd_pdd.h b/src/math/dd/dd_pdd.h index 45d646c51..9cd454698 100644 --- a/src/math/dd/dd_pdd.h +++ b/src/math/dd/dd_pdd.h @@ -367,7 +367,7 @@ namespace dd { /** * \brief factor out variables */ - std::pair var_factors(); + std::pair var_factors() const; pdd subst_val(vector> const& s) const { return m.subst_val(*this, s); } pdd subst_val(unsigned v, rational const& val) const { return m.subst_val(*this, v, val); } diff --git a/src/math/lp/nla_core.cpp b/src/math/lp/nla_core.cpp index 7c9ac73f7..efd510824 100644 --- a/src/math/lp/nla_core.cpp +++ b/src/math/lp/nla_core.cpp @@ -35,7 +35,7 @@ core::core(lp::lar_solver& s, reslimit & lim) : m_grobner(this), m_emons(m_evars), m_use_nra_model(false), - m_nra(s, m_nra_lim, *this) + m_nra(s, m_nra_lim, *this) { m_nlsat_delay = lp_settings().nlsat_delay(); } @@ -56,9 +56,8 @@ bool core::compare_holds(const rational& ls, llc cmp, const rational& rs) const rational core::value(const lp::lar_term& r) const { rational ret(0); - for (lp::lar_term::ival t : r) { + for (lp::lar_term::ival t : r) ret += t.coeff() * val(t.column()); - } return ret; } @@ -78,10 +77,9 @@ bool core::ineq_holds(const ineq& n) const { } bool core::lemma_holds(const lemma& l) const { - for(const ineq &i : l.ineqs()) { + for (const ineq &i : l.ineqs()) if (ineq_holds(i)) return true; - } return false; } @@ -1498,14 +1496,17 @@ lbool core::check(vector& l_vec) { init_search(); lbool ret = l_undef; + bool run_grobner = need_run_grobner(); + bool run_horner = need_run_horner(); + bool run_bounded_nlsat = should_run_bounded_nlsat(); if (l_vec.empty() && !done()) m_monomial_bounds(); - if (l_vec.empty() && !done() && need_run_horner()) + if (l_vec.empty() && !done() && run_horner) m_horner.horner_lemmas(); - if (l_vec.empty() && !done() && need_run_grobner()) + if (l_vec.empty() && !done() && run_grobner) m_grobner(); if (l_vec.empty() && !done()) @@ -1514,9 +1515,16 @@ lbool core::check(vector& l_vec) { if (l_vec.empty() && !done()) m_basics.basic_lemma(false); - if (!conflict_found() && !done() && should_run_bounded_nlsat()) +#if 0 + if (l_vec.empty() && !done() && !run_horner) + m_horner.horner_lemmas(); + + if (l_vec.empty() && !done() && !run_grobner) + m_grobner(); +#endif + + if (!conflict_found() && !done() && run_bounded_nlsat) ret = bounded_nlsat(); - if (l_vec.empty() && !done() && ret == l_undef) { std::function check1 = [&]() { m_order.order_lemma(); }; diff --git a/src/math/lp/nla_grobner.cpp b/src/math/lp/nla_grobner.cpp index c6540c26d..b7226f27d 100644 --- a/src/math/lp/nla_grobner.cpp +++ b/src/math/lp/nla_grobner.cpp @@ -45,7 +45,6 @@ namespace nla { if (is_conflicting()) return; -#if 0 if (propagate_bounds()) return; @@ -54,7 +53,7 @@ namespace nla { if (propagate_factorization()) return; -#endif + if (quota > 1) quota--; @@ -86,19 +85,19 @@ namespace nla { } bool grobner::propagate_bounds() { - unsigned bounds = 0; + unsigned changed = 0; for (auto eq : m_solver.equations()) - if (propagate_bounds(*eq) && ++bounds >= m_solver.number_of_conflicts_to_report()) + if (propagate_bounds(*eq) && ++changed >= m_solver.number_of_conflicts_to_report()) return true; - return bounds > 0; + return changed > 0; } bool grobner::propagate_eqs() { - unsigned fixed = 0; + unsigned changed = 0; for (auto eq : m_solver.equations()) - if (propagate_fixed(*eq) && ++fixed >= m_solver.number_of_conflicts_to_report()) + if (propagate_fixed(*eq) && ++changed >= m_solver.number_of_conflicts_to_report()) return true; - return fixed > 0; + return changed > 0; } bool grobner::propagate_factorization() { @@ -152,23 +151,23 @@ namespace nla { bool grobner::propagate_factorization(const dd::solver::equation& eq) { dd::pdd const& p = eq.poly(); - if (!p.is_val() && p.lo().is_zero() && !p.hi().is_val() && p.hi().is_linear()) { - //IF_VERBOSE(0, verbose_stream() << "factored " << p << "\n"); - unsigned v = p.var(); - auto q = p.hi(); - new_lemma lemma(c(), "pdd-factored"); - add_dependencies(lemma, eq); - term t; - while (!q.is_val()) { - t.add_monomial(q.hi().val(), q.var()); - q = q.lo(); - } - lemma |= ineq(v, llc::EQ, rational::zero()); - lemma |= ineq(t, llc::EQ, -q.val()); - //lemma.display(verbose_stream()); - return true; + auto [vars, q] = p.var_factors(); + if (vars.empty() || !q.is_linear()) + return false; + + // IF_VERBOSE(0, verbose_stream() << "factored " << q << " : " << vars << "\n"); + new_lemma lemma(c(), "pdd-factored"); + add_dependencies(lemma, eq); + term t; + while (!q.is_val()) { + t.add_monomial(q.hi().val(), q.var()); + q = q.lo(); } - return false; + for (auto v : vars) + lemma |= ineq(v, llc::EQ, rational::zero()); + lemma |= ineq(t, llc::EQ, -q.val()); + //lemma.display(verbose_stream()); + return true; } From 6688c1d62a111d35291ab11bd0101d6a911961ab Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Fri, 15 Jul 2022 03:53:15 -0700 Subject: [PATCH 110/125] prepare for #6160 The idea is to set _concurrent_dec_ref from the API (function not yet provided externally, but you can experiment with it by setting the default of m_concurrent_dec_ref to true). It then provides concurrency support for dec_ref operations. --- src/api/api_context.cpp | 54 ++++++++++++++++++++++++++++++++++++----- src/api/api_context.h | 8 +++++- src/api/api_util.h | 3 ++- 3 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/api/api_context.cpp b/src/api/api_context.cpp index 3ba279dca..1951d16ea 100644 --- a/src/api/api_context.cpp +++ b/src/api/api_context.cpp @@ -35,11 +35,12 @@ namespace api { object::object(context& c): m_ref_count(0), m_context(c) { this->m_id = m_context.add_object(this); } - void object::inc_ref() { m_ref_count++; } + void object::inc_ref() { ++m_ref_count; } - void object::dec_ref() { SASSERT(m_ref_count > 0); m_ref_count--; if (m_ref_count == 0) m_context.del_object(this); } + void object::dec_ref() { SASSERT(m_ref_count > 0); if (--m_ref_count == 0) m_context.del_object(this); } unsigned context::add_object(api::object* o) { + flush_objects(); unsigned id = m_allocated_objects.size(); if (!m_free_object_ids.empty()) { id = m_free_object_ids.back(); @@ -50,9 +51,48 @@ namespace api { } void context::del_object(api::object* o) { - m_free_object_ids.push_back(o->id()); - m_allocated_objects.remove(o->id()); - dealloc(o); + if (m_concurrent_dec_ref) { + lock_guard lock(m_mux); + m_objects_to_flush.push_back(o); + } + else { + m_free_object_ids.push_back(o->id()); + m_allocated_objects.remove(o->id()); + dealloc(o); + } + } + + void context::dec_ref(ast* a) { + if (m_concurrent_dec_ref) { + lock_guard lock(m_mux); + m_asts_to_flush.push_back(a); + } + else + m().dec_ref(a); + } + + void context::flush_objects() { + if (!m_concurrent_dec_ref) + return; + { + lock_guard lock(m_mux); + if (m_asts_to_flush.empty() && m_objects_to_flush.empty()) + return; + m_asts_to_flush2.append(m_asts_to_flush); + m_asts_to_flush.reset(); + m_objects_to_flush2.append(m_objects_to_flush); + m_objects_to_flush.reset(); + } + for (ast* a : m_asts_to_flush2) + m().dec_ref(a); + for (auto* o : m_objects_to_flush2) { + m_free_object_ids.push_back(o->id()); + m_allocated_objects.remove(o->id()); + dealloc(o); + } + m_objects_to_flush2.reset(); + m_asts_to_flush2.reset(); + } static void default_error_handler(Z3_context ctx, Z3_error_code c) { @@ -106,6 +146,7 @@ namespace api { context::~context() { m_last_obj = nullptr; + flush_objects(); for (auto& kv : m_allocated_objects) { api::object* val = kv.m_value; DEBUG_CODE(warning_msg("Uncollected memory: %d: %s", kv.m_key, typeid(*val).name());); @@ -365,6 +406,7 @@ extern "C" { Z3_TRY; LOG_Z3_inc_ref(c, a); RESET_ERROR_CODE(); + mk_c(c)->flush_objects(); mk_c(c)->m().inc_ref(to_ast(a)); Z3_CATCH; } @@ -379,7 +421,7 @@ extern "C" { return; } if (a) { - mk_c(c)->m().dec_ref(to_ast(a)); + mk_c(c)->dec_ref(to_ast(a)); } Z3_CATCH; } diff --git a/src/api/api_context.h b/src/api/api_context.h index 41c797163..23b5d6f70 100644 --- a/src/api/api_context.h +++ b/src/api/api_context.h @@ -91,8 +91,11 @@ namespace api { smt_params m_fparams; // ------------------------------- - ast_ref_vector m_ast_trail; + bool m_concurrent_dec_ref = false; + ptr_vector m_asts_to_flush, m_asts_to_flush2; + ptr_vector m_objects_to_flush, m_objects_to_flush2; + ast_ref_vector m_ast_trail; ref m_last_obj; //!< reference to the last API object returned by the APIs u_map m_allocated_objects; // !< table containing current set of allocated API objects unsigned_vector m_free_object_ids; // !< free list of identifiers available for allocated objects. @@ -170,8 +173,11 @@ namespace api { void set_error_code(Z3_error_code err, std::string &&opt_msg); void set_error_handler(Z3_error_handler h) { m_error_handler = h; } + void set_concurrent_dec_ref() { m_concurrent_dec_ref = true; } unsigned add_object(api::object* o); void del_object(api::object* o); + void dec_ref(ast* a); + void flush_objects(); Z3_ast_print_mode get_print_mode() const { return m_print_mode; } void set_print_mode(Z3_ast_print_mode m) { m_print_mode = m; } diff --git a/src/api/api_util.h b/src/api/api_util.h index 80fea1ac3..474d3410e 100644 --- a/src/api/api_util.h +++ b/src/api/api_util.h @@ -20,6 +20,7 @@ Revision History: #include "util/params.h" #include "util/lbool.h" #include "ast/ast.h" +#include #define Z3_TRY try { #define Z3_CATCH_CORE(CODE) } catch (z3_exception & ex) { mk_c(c)->handle_exception(ex); CODE } @@ -34,7 +35,7 @@ namespace api { // Generic wrapper for ref-count objects exposed by the API class object { - unsigned m_ref_count; + atomic m_ref_count; unsigned m_id; context& m_context; public: From 2696775088d709e456a263fd4a92852932e12ffc Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Fri, 15 Jul 2022 04:03:25 -0700 Subject: [PATCH 111/125] remove stale assertion with support for substitutions we allow the simplifier to change the state of equations. --- src/math/grobner/pdd_solver.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/math/grobner/pdd_solver.cpp b/src/math/grobner/pdd_solver.cpp index d9171aabd..aefe5d6dc 100644 --- a/src/math/grobner/pdd_solver.cpp +++ b/src/math/grobner/pdd_solver.cpp @@ -200,7 +200,6 @@ namespace dd { // pushed to solved } else if (simplified && changed_leading_term) { - SASSERT(target.state() == processed); push_equation(to_simplify, target); if (!m_var2level.empty()) { m_levelp1 = std::max(m_var2level[target.poly().var()]+1, m_levelp1); From b743e210f8e2be03150032b5be07540818fa91d3 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Fri, 15 Jul 2022 08:43:46 -0700 Subject: [PATCH 112/125] give java dynamic lib a chance for extra flags for #5848 --- scripts/mk_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/mk_util.py b/scripts/mk_util.py index 13c01567d..54242dfe6 100644 --- a/scripts/mk_util.py +++ b/scripts/mk_util.py @@ -1831,7 +1831,7 @@ class JavaDLLComponent(Component): out.write('\t$(SLINK) $(SLINK_OUT_FLAG)libz3java$(SO_EXT) $(SLINK_FLAGS) %s$(OBJ_EXT) libz3$(LIB_EXT)\n' % os.path.join('api', 'java', 'Native')) else: - out.write('\t$(SLINK) $(SLINK_OUT_FLAG)libz3java$(SO_EXT) $(SLINK_FLAGS) %s$(OBJ_EXT) libz3$(SO_EXT)\n' % + out.write('\t$(SLINK) $(SLINK_OUT_FLAG)libz3java$(SO_EXT) $(SLINK_FLAGS) $(SLINK_EXTRA_FLAGS) %s$(OBJ_EXT) libz3$(SO_EXT)\n' % os.path.join('api', 'java', 'Native')) out.write('%s.jar: libz3java$(SO_EXT) ' % self.package_name) deps = '' From 4ecb61aeaa29984eb23cd62e9491bd3fa447b0f3 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Thu, 14 Jul 2022 11:12:12 -0700 Subject: [PATCH 113/125] neatify Signed-off-by: Nikolaj Bjorner --- src/math/dd/dd_pdd.cpp | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/src/math/dd/dd_pdd.cpp b/src/math/dd/dd_pdd.cpp index 3c50076da..b8946ebf6 100644 --- a/src/math/dd/dd_pdd.cpp +++ b/src/math/dd/dd_pdd.cpp @@ -1330,30 +1330,19 @@ namespace dd { auto merge = [&](unsigned_vector& lo_vars, unsigned_vector& hi_vars) { unsigned ir = 0, jr = 0; for (unsigned i = 0, j = 0; i < lo_vars.size() || j < hi_vars.size(); ) { - if (i == lo_vars.size()) { + if (i == lo_vars.size()) hi_vars[jr++] = hi_vars[j++]; - continue; - } - if (j == hi_vars.size()) { + else if (j == hi_vars.size()) lo_vars[ir++] = lo_vars[i++]; - continue; - } - if (lo_vars[i] == hi_vars[j]) { + else if (lo_vars[i] == hi_vars[j]) { lo_and_hi.push_back(lo_vars[i]); ++i; ++j; - continue; } - unsigned lvl_lo = m.m_var2level[lo_vars[i]]; - unsigned lvl_hi = m.m_var2level[hi_vars[j]]; - if (lvl_lo > lvl_hi) { + else if (m.m_var2level[lo_vars[i]] > m.m_var2level[hi_vars[j]]) hi_vars[jr++] = hi_vars[j++]; - continue; - } - else { + else lo_vars[ir++] = lo_vars[i++]; - continue; - } } lo_vars.shrink(ir); hi_vars.shrink(jr); From 6c5747a80e6853ad54449dbb188d277a499b0798 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Fri, 15 Jul 2022 10:03:31 -0700 Subject: [PATCH 114/125] guard against lemmas that are already true Signed-off-by: Nikolaj Bjorner --- src/math/lp/nla_grobner.cpp | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/math/lp/nla_grobner.cpp b/src/math/lp/nla_grobner.cpp index b7226f27d..51604fb47 100644 --- a/src/math/lp/nla_grobner.cpp +++ b/src/math/lp/nla_grobner.cpp @@ -121,9 +121,12 @@ namespace nla { unsigned v = p.var(); if (c().var_is_fixed(v)) return false; + ineq new_eq(v, llc::EQ, rational::zero()); + if (c().ineq_holds(new_eq)) + return false; new_lemma lemma(c(), "pdd-eq"); add_dependencies(lemma, eq); - lemma |= ineq(v, llc::EQ, rational::zero()); + lemma |= new_eq; return true; } if (p.is_offset()) { @@ -135,9 +138,12 @@ namespace nla { rational d = lcm(denominator(a), denominator(b)); a *= d; b *= d; + ineq new_eq(term(a, v), llc::EQ, b); + if (c().ineq_holds(new_eq)) + return false; new_lemma lemma(c(), "pdd-eq"); add_dependencies(lemma, eq); - lemma |= ineq(term(a, v), llc::EQ, b); + lemma |= new_eq; return true; } @@ -156,16 +162,24 @@ namespace nla { return false; // IF_VERBOSE(0, verbose_stream() << "factored " << q << " : " << vars << "\n"); - new_lemma lemma(c(), "pdd-factored"); - add_dependencies(lemma, eq); + term t; while (!q.is_val()) { t.add_monomial(q.hi().val(), q.var()); q = q.lo(); } - for (auto v : vars) - lemma |= ineq(v, llc::EQ, rational::zero()); - lemma |= ineq(t, llc::EQ, -q.val()); + vector ineqs; + for (auto v : vars) + ineqs.push_back(ineq(v, llc::EQ, rational::zero())); + ineqs.push_back(ineq(t, llc::EQ, -q.val())); + for (auto const& i : ineqs) + if (c().ineq_holds(i)) + return false; + + new_lemma lemma(c(), "pdd-factored"); + add_dependencies(lemma, eq); + for (auto const& i : ineqs) + lemma |= i; //lemma.display(verbose_stream()); return true; } From aefd336c186ce35111c77bbea8009292c6982d7e Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sat, 16 Jul 2022 16:49:39 -0700 Subject: [PATCH 115/125] set OCaml default behaivor to enable concurrent dec ref #6160 Add Z3_enable_concurrent_dec_ref to the API. It is enables behavior of dec_ref functions that are exposed over the API to work with concurrent GC. The API calls to dec_ref are queued and processed in the main thread where context operations take place (in a way that is assumed thread safe as context operations are only allowed to be serialized on one thread at a time). --- src/api/api_context.cpp | 8 ++++++++ src/api/api_context.h | 4 ++-- src/api/ml/z3.ml | 1 + src/api/z3_api.h | 10 ++++++++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/api/api_context.cpp b/src/api/api_context.cpp index 1951d16ea..896d0ec85 100644 --- a/src/api/api_context.cpp +++ b/src/api/api_context.cpp @@ -397,6 +397,14 @@ extern "C" { Z3_CATCH; } + void Z3_API Z3_enable_concurrent_dec_ref(Z3_context c) { + Z3_TRY; + LOG_Z3_enable_concurrent_dec_ref(c); + mk_c(c)->enableset_concurrent_dec_ref(); + Z3_CATCH; + } + + void Z3_API Z3_toggle_warning_messages(bool enabled) { LOG_Z3_toggle_warning_messages(enabled); enable_warning_messages(enabled != 0); diff --git a/src/api/api_context.h b/src/api/api_context.h index 23b5d6f70..cf8c6ac87 100644 --- a/src/api/api_context.h +++ b/src/api/api_context.h @@ -172,8 +172,8 @@ namespace api { void set_error_code(Z3_error_code err, char const* opt_msg); void set_error_code(Z3_error_code err, std::string &&opt_msg); void set_error_handler(Z3_error_handler h) { m_error_handler = h; } - - void set_concurrent_dec_ref() { m_concurrent_dec_ref = true; } + + void enable_concurrent_dec_ref() { m_concurrent_dec_ref = true; } unsigned add_object(api::object* o); void del_object(api::object* o); void dec_ref(ast* a); diff --git a/src/api/ml/z3.ml b/src/api/ml/z3.ml index 36753a4f9..2fa4acc65 100644 --- a/src/api/ml/z3.ml +++ b/src/api/ml/z3.ml @@ -59,6 +59,7 @@ let mk_context (settings:(string * string) list) = Z3native.del_config cfg; Z3native.set_ast_print_mode res (Z3enums.int_of_ast_print_mode PRINT_SMTLIB2_COMPLIANT); Z3native.set_internal_error_handler res; + Z3native.enable_concurrent_dec_ref res; res module Symbol = diff --git a/src/api/z3_api.h b/src/api/z3_api.h index 831e03b0e..2820205b9 100644 --- a/src/api/z3_api.h +++ b/src/api/z3_api.h @@ -1705,6 +1705,16 @@ extern "C" { void Z3_API Z3_interrupt(Z3_context c); + /** + \brief use concurrency control for dec-ref. + Reference counting decrements are allowed in separate threads from the context. + If this setting is not invoked, reference counting decrements are not going to be thread safe. + + def_API('Z3_enable_concurrent_dec_ref', VOID, (_in(CONTEXT),)) + */ + void Z3_API Z3_enable_concurrent_dec_ref(Z3_context c); + + /**@}*/ /** @name Parameters */ From eb2ee34dfef58ad3564c0e0d36395957f641e21b Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sat, 16 Jul 2022 16:58:57 -0700 Subject: [PATCH 116/125] fix typo Signed-off-by: Nikolaj Bjorner --- src/api/api_context.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/api/api_context.cpp b/src/api/api_context.cpp index 896d0ec85..aa96998b3 100644 --- a/src/api/api_context.cpp +++ b/src/api/api_context.cpp @@ -400,10 +400,9 @@ extern "C" { void Z3_API Z3_enable_concurrent_dec_ref(Z3_context c) { Z3_TRY; LOG_Z3_enable_concurrent_dec_ref(c); - mk_c(c)->enableset_concurrent_dec_ref(); + mk_c(c)->enable_concurrent_dec_ref(); Z3_CATCH; - } - + } void Z3_API Z3_toggle_warning_messages(bool enabled) { LOG_Z3_toggle_warning_messages(enabled); From 6e5ced0080671da5f3a8661eb7085e7f08575e81 Mon Sep 17 00:00:00 2001 From: Nuno Lopes Date: Sun, 17 Jul 2022 11:44:35 +0100 Subject: [PATCH 117/125] optimizations to api ctx ref counting --- src/api/api_context.cpp | 18 +++++++++++------- src/api/api_context.h | 14 ++++++++++++-- src/api/api_util.h | 4 +--- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/api/api_context.cpp b/src/api/api_context.cpp index aa96998b3..a16341955 100644 --- a/src/api/api_context.cpp +++ b/src/api/api_context.cpp @@ -51,11 +51,14 @@ namespace api { } void context::del_object(api::object* o) { +#ifndef SINGLE_THREAD if (m_concurrent_dec_ref) { lock_guard lock(m_mux); m_objects_to_flush.push_back(o); } - else { + else +#endif + { m_free_object_ids.push_back(o->id()); m_allocated_objects.remove(o->id()); dealloc(o); @@ -63,25 +66,26 @@ namespace api { } void context::dec_ref(ast* a) { +#ifndef SINGLE_THREAD if (m_concurrent_dec_ref) { lock_guard lock(m_mux); m_asts_to_flush.push_back(a); } - else + else +#endif m().dec_ref(a); } void context::flush_objects() { +#ifndef SINGLE_THREAD if (!m_concurrent_dec_ref) return; { lock_guard lock(m_mux); if (m_asts_to_flush.empty() && m_objects_to_flush.empty()) return; - m_asts_to_flush2.append(m_asts_to_flush); - m_asts_to_flush.reset(); - m_objects_to_flush2.append(m_objects_to_flush); - m_objects_to_flush.reset(); + m_asts_to_flush2.swap(m_asts_to_flush); + m_objects_to_flush2.swap(m_objects_to_flush); } for (ast* a : m_asts_to_flush2) m().dec_ref(a); @@ -92,7 +96,7 @@ namespace api { } m_objects_to_flush2.reset(); m_asts_to_flush2.reset(); - +#endif } static void default_error_handler(Z3_context ctx, Z3_error_code c) { diff --git a/src/api/api_context.h b/src/api/api_context.h index cf8c6ac87..182958b1e 100644 --- a/src/api/api_context.h +++ b/src/api/api_context.h @@ -75,6 +75,9 @@ namespace api { struct add_plugins { add_plugins(ast_manager & m); }; ast_context_params m_params; bool m_user_ref_count; //!< if true, the user is responsible for managing reference counters. +#ifndef SINGLE_THREAD + bool m_concurrent_dec_ref = false; +#endif scoped_ptr m_manager; scoped_ptr m_cmd; add_plugins m_plugins; @@ -91,9 +94,10 @@ namespace api { smt_params m_fparams; // ------------------------------- - bool m_concurrent_dec_ref = false; +#ifndef SINGLE_THREAD ptr_vector m_asts_to_flush, m_asts_to_flush2; ptr_vector m_objects_to_flush, m_objects_to_flush2; +#endif ast_ref_vector m_ast_trail; ref m_last_obj; //!< reference to the last API object returned by the APIs @@ -173,7 +177,13 @@ namespace api { void set_error_code(Z3_error_code err, std::string &&opt_msg); void set_error_handler(Z3_error_handler h) { m_error_handler = h; } - void enable_concurrent_dec_ref() { m_concurrent_dec_ref = true; } + void enable_concurrent_dec_ref() { +#ifdef SINGLE_THREAD + set_error_code(Z3_EXCEPTION, "Can't use concurrent features with a single-thread build"); +#else + m_concurrent_dec_ref = true; +#endif + } unsigned add_object(api::object* o); void del_object(api::object* o); void dec_ref(ast* a); diff --git a/src/api/api_util.h b/src/api/api_util.h index 474d3410e..80f4f5ec7 100644 --- a/src/api/api_util.h +++ b/src/api/api_util.h @@ -19,8 +19,8 @@ Revision History: #include "util/params.h" #include "util/lbool.h" +#include "util/mutex.h" #include "ast/ast.h" -#include #define Z3_TRY try { #define Z3_CATCH_CORE(CODE) } catch (z3_exception & ex) { mk_c(c)->handle_exception(ex); CODE } @@ -88,7 +88,6 @@ inline lbool to_lbool(Z3_lbool b) { return static_cast(b); } struct Z3_params_ref : public api::object { params_ref m_params; Z3_params_ref(api::context& c): api::object(c) {} - ~Z3_params_ref() override {} }; inline Z3_params_ref * to_params(Z3_params p) { return reinterpret_cast(p); } @@ -98,7 +97,6 @@ inline params_ref& to_param_ref(Z3_params p) { return p == nullptr ? const_cast< struct Z3_param_descrs_ref : public api::object { param_descrs m_descrs; Z3_param_descrs_ref(api::context& c): api::object(c) {} - ~Z3_param_descrs_ref() override {} }; inline Z3_param_descrs_ref * to_param_descrs(Z3_param_descrs p) { return reinterpret_cast(p); } From 95c3dd9224b538b940dbb9d91f0ae7d42f3f70d5 Mon Sep 17 00:00:00 2001 From: Clemens Eisenhofer <56730610+CEisenhofer@users.noreply.github.com> Date: Sun, 17 Jul 2022 19:07:52 +0200 Subject: [PATCH 118/125] Added missing decide-callback for tactics (#6166) * Added function to select the next variable to split on * Fixed typo * Small fixes * uint -> int * Fixed missing assignment for binary clauses * Added missing decide-callback for tactics --- src/smt/tactic/smt_tactic_core.cpp | 5 +++++ src/solver/tactic2solver.cpp | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/smt/tactic/smt_tactic_core.cpp b/src/smt/tactic/smt_tactic_core.cpp index 9c5fc1c8e..c45165d79 100644 --- a/src/smt/tactic/smt_tactic_core.cpp +++ b/src/smt/tactic/smt_tactic_core.cpp @@ -348,6 +348,7 @@ public: m_eq_eh = nullptr; m_diseq_eh = nullptr; m_created_eh = nullptr; + m_decide_eh = nullptr; } void user_propagate_init( @@ -385,6 +386,10 @@ public: void user_propagate_register_created(user_propagator::created_eh_t& created_eh) override { m_created_eh = created_eh; } + + void user_propagate_register_decide(user_propagator::decide_eh_t& decide_eh) override { + m_decide_eh = decide_eh; + } }; static tactic * mk_seq_smt_tactic(ast_manager& m, params_ref const & p) { diff --git a/src/solver/tactic2solver.cpp b/src/solver/tactic2solver.cpp index fe89d6533..09ede0c35 100644 --- a/src/solver/tactic2solver.cpp +++ b/src/solver/tactic2solver.cpp @@ -116,8 +116,8 @@ public: m_tactic->user_propagate_register_created(created_eh); } - void user_propagate_register_decide(user_propagator::decide_eh_t& created_eh) override { - m_tactic->user_propagate_register_decide(created_eh); + void user_propagate_register_decide(user_propagator::decide_eh_t& decide_eh) override { + m_tactic->user_propagate_register_decide(decide_eh); } void user_propagate_clear() override { From b5a89eb4abd2ca62a37958ba5f394cfe47b0a1da Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sun, 17 Jul 2022 11:03:43 -0700 Subject: [PATCH 119/125] add missing generation of z3.z3 for pydoc and add some explanations to logging function declaration --- doc/mk_api_doc.py | 1 + src/api/ml/z3.mli | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/doc/mk_api_doc.py b/doc/mk_api_doc.py index 7ca47b89a..670634e07 100644 --- a/doc/mk_api_doc.py +++ b/doc/mk_api_doc.py @@ -335,6 +335,7 @@ try: for modulename in ( 'z3', + 'z3.z3', 'z3.z3consts', 'z3.z3core', 'z3.z3num', diff --git a/src/api/ml/z3.mli b/src/api/ml/z3.mli index a7c629018..b7fa27b5e 100644 --- a/src/api/ml/z3.mli +++ b/src/api/ml/z3.mli @@ -49,6 +49,13 @@ type context val mk_context : (string * string) list -> context (** Interaction logging for Z3 + Interaction logs are used to record calls into the API into a text file. + The text file can be replayed using z3. It has to be the same version of z3 + to ensure that low level codes emitted in a log are compatible with the + version of z3 replaying the log. The file suffix ".log" is understood + by z3 as the format of the file being an interaction log. You can use the + optional comman-line parameter "-log" to force z3 to treat an input file + as an interaction log. Note that this is a global, static log and if multiple Context objects are created, it logs the interaction with all of them. *) module Log : From 527914db05d80da4d0a070485ddd87720f514c66 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sun, 17 Jul 2022 11:49:28 -0700 Subject: [PATCH 120/125] update documentation to use latest conventions Signed-off-by: Nikolaj Bjorner --- src/api/js/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/js/README.md b/src/api/js/README.md index 27ff5bdfe..42c29518e 100644 --- a/src/api/js/README.md +++ b/src/api/js/README.md @@ -9,7 +9,7 @@ The readme for the bindings themselves is located in [`PUBLISHED_README.md`](./P You'll need to have emscripten set up, along with all of its dependencies. The easiest way to do that is with [emsdk](https://github.com/emscripten-core/emsdk). -Then run `npm i` to install dependencies, `npm run build-ts` to build the TypeScript wrapper, and `npm run build-wasm` to build the wasm artifact. +Then run `npm i` to install dependencies, `npm run build:ts` to build the TypeScript wrapper, and `npm run build:wasm` to build the wasm artifact. ## Tests From 393c63fe0cc5afe1c32b2a46f154d8e0bce267e6 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 18 Jul 2022 09:33:39 -0700 Subject: [PATCH 121/125] fix #6114 --- examples/c++/example.cpp | 4 ++-- src/api/c++/z3++.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/c++/example.cpp b/examples/c++/example.cpp index 0217c5cca..c71c3ff2f 100644 --- a/examples/c++/example.cpp +++ b/examples/c++/example.cpp @@ -929,8 +929,8 @@ void enum_sort_example() { sort s = ctx.enumeration_sort("enumT", 3, enum_names, enum_consts, enum_testers); // enum_consts[0] is a func_decl of arity 0. // we convert it to an expression using the operator() - expr a = enum_consts[0](); - expr b = enum_consts[1](); + expr a = enum_consts[0u](); + expr b = enum_consts[1u](); expr x = ctx.constant("x", s); expr test = (x==a) && (x==b); std::cout << "1: " << test << std::endl; diff --git a/src/api/c++/z3++.h b/src/api/c++/z3++.h index fc0601fe5..8aaeb31ab 100644 --- a/src/api/c++/z3++.h +++ b/src/api/c++/z3++.h @@ -2894,7 +2894,7 @@ namespace z3 { if (n == 0) return ctx().bool_val(true); else if (n == 1) - return operator[](0); + return operator[](0u); else { array args(n); for (unsigned i = 0; i < n; i++) From 7ded856bb14692c7310f62f89d167f76334fb0df Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 18 Jul 2022 09:51:34 -0700 Subject: [PATCH 122/125] script to test jsdoc Signed-off-by: Nikolaj Bjorner --- scripts/jsdoctest.yml | 49 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 scripts/jsdoctest.yml diff --git a/scripts/jsdoctest.yml b/scripts/jsdoctest.yml new file mode 100644 index 000000000..1a3a5286f --- /dev/null +++ b/scripts/jsdoctest.yml @@ -0,0 +1,49 @@ +variables: + + Major: '4' + Minor: '9' + Patch: '2' + NightlyVersion: $(Major).$(Minor).$(Patch).$(Build.BuildId)-$(Build.DefinitionName) + +stages: +- stage: Build + jobs: + + - job: UbuntuDoc + displayName: "Ubuntu Doc build" + pool: + vmImage: "Ubuntu-latest" + steps: +# TODO setup emscripten with no-install, then run + - script: npm --prefix=src/api/js ci + - script: npm --prefix=src/api/js run build:ts + + - script: sudo apt-get install ocaml opam libgmp-dev + - script: opam init -y + - script: eval `opam config env`; opam install zarith ocamlfind -y + - script: eval `opam config env`; python scripts/mk_make.py --ml + - script: sudo apt-get install doxygen + - script: sudo apt-get install graphviz + - script: | + set -e + cd build + eval `opam config env` + make -j3 + make -j3 examples + make -j3 test-z3 + cd .. + - script: | + set -e + eval `opam config env` + cd doc + python mk_api_doc.py --mld --z3py-package-path=../build/python/z3 --js + mkdir api/html/ml + ocamldoc -html -d api/html/ml -sort -hide Z3 -I $( ocamlfind query zarith ) -I ../build/api/ml ../build/api/ml/z3enums.mli ../build/api/ml/z3.mli + cd .. + - script: zip -r z3doc.zip doc/api + - script: cp z3doc.zip $(Build.ArtifactStagingDirectory)/. + - task: PublishPipelineArtifact@0 + inputs: + artifactName: 'UbuntuDoc' + targetPath: $(Build.ArtifactStagingDirectory) + From 7f1893d78175ee64bce836eb15a5daa9771a620c Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 18 Jul 2022 10:21:27 -0700 Subject: [PATCH 123/125] add missing MkSub to NativeContext Signed-off-by: Nikolaj Bjorner --- src/api/dotnet/NativeContext.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/api/dotnet/NativeContext.cs b/src/api/dotnet/NativeContext.cs index 93180e583..afae66e46 100644 --- a/src/api/dotnet/NativeContext.cs +++ b/src/api/dotnet/NativeContext.cs @@ -100,6 +100,17 @@ namespace Microsoft.Z3 return Native.Z3_mk_mul(nCtx, (uint)(ts?.Length ?? 0), ts); } + /// + /// Create an expression representing t[0] - t[1] - .... + /// + public Z3_ast MkSub(params Z3_ast[] t) + { + Debug.Assert(t != null); + Debug.Assert(t.All(a => a != IntPtr.Zero)); + var ts = t.ToArray(); + return Native.Z3_mk_sub(nCtx, (uint)(ts?.Length ?? 0), ts); + } + /// /// Create an expression representing t1 / t2. /// From afcfc80c4260db4041891ff2e7dce2be46a21efd Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 18 Jul 2022 11:21:16 -0700 Subject: [PATCH 124/125] the relative path seems out of sync with how it is set up in node.ts --- src/api/js/src/jest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/js/src/jest.ts b/src/api/js/src/jest.ts index 9cbab31f1..18f43e4c2 100644 --- a/src/api/js/src/jest.ts +++ b/src/api/js/src/jest.ts @@ -3,7 +3,7 @@ // @ts-ignore no-implicit-any import { createApi, Z3HighLevel } from './high-level'; import { init as initWrapper, Z3LowLevel } from './low-level'; -import initModule = require('../build/z3-built'); +import initModule = require('./z3-built'); export * from './high-level/types'; export { Z3Core, Z3LowLevel } from './low-level'; From dead0c9de230dc9ea03da8dc01d4b62f8646f261 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 18 Jul 2022 11:47:57 -0700 Subject: [PATCH 125/125] reverting relative path Signed-off-by: Nikolaj Bjorner --- src/api/js/src/jest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/js/src/jest.ts b/src/api/js/src/jest.ts index 18f43e4c2..2de6cdab1 100644 --- a/src/api/js/src/jest.ts +++ b/src/api/js/src/jest.ts @@ -3,7 +3,7 @@ // @ts-ignore no-implicit-any import { createApi, Z3HighLevel } from './high-level'; import { init as initWrapper, Z3LowLevel } from './low-level'; -import initModule = require('./z3-built'); +import initModule = require('../z3-built'); export * from './high-level/types'; export { Z3Core, Z3LowLevel } from './low-level';