• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SRI-CSL / yices2 / 26471292351

26 May 2026 07:48PM UTC coverage: 69.002% (+0.2%) from 68.786%
26471292351

Pull #630

github

web-flow
Merge 4292db101 into 2137d2aaf
Pull Request #630: Add wide (truth-invariant) model generalization mode

419 of 551 new or added lines in 2 files covered. (76.04%)

95 existing lines in 2 files now uncovered.

88100 of 127678 relevant lines covered (69.0%)

1626871.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

69.73
/src/model/generalization.c
1
/*
2
 * This file is part of the Yices SMT Solver.
3
 * Copyright (C) 2017 SRI International.
4
 *
5
 * Yices is free software: you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation, either version 3 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * Yices is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with Yices.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18

19
/*
20
 * MODEL GENERALIZATION
21
 *
22
 * Given a model mdl for a formula F(x, y), this module computes
23
 * the generalization of mdl for the variables 'x'. The result
24
 * is a formula G(x) such that 
25
 * 1) G(x) is true in mdl
26
 * 2) G(x) implies (EXISTS y: F(x, y))
27
 *
28
 * If any variable in y is an arithmetic variable then G(x) is
29
 * computed using model-based projection. Otherwise, G(x) is
30
 * obtained by substitution: we replace every variable y_i
31
 * by its value in the model.
32
 *
33
 * NOTE: we could use model-based projection in both cases, but
34
 * experiments with the exists/forall solver seem to show that
35
 * substitution works better for Boolean and bitvector variables.
36
 *
37
 *
38
 * Two projection variants are implemented:
39
 *
40
 * - "local" (gen_model_by_proj_local): the legacy pipeline.
41
 *   Builds a single literal implicant of f[] at the model
42
 *   (one literal per disjunction) and projects that flat
43
 *   conjunction. Cheaper per call but commits to one disjunct,
44
 *   so the resulting cell is sign-invariant for the chosen
45
 *   implicant and is strictly narrower than the wide cell
46
 *   whenever F has Boolean structure the model satisfies in
47
 *   more than one way.
48
 *
49
 * - "wide" (gen_model_by_proj_sat_guided): the public default.
50
 *   Builds a model-pruned Boolean abstraction of f[], enumerates
51
 *   model-true Boolean implicants with a SAT solver and blocker
52
 *   clauses, projects each implicant as a cube through the legacy
53
 *   implicant-then-project pipeline, and unions the results at the
54
 *   term level.
55
 *
56
 *   The abstraction is *model-pruned*: every Boolean subterm of F
57
 *   is evaluated at the model up front, and model-false subterms are
58
 *   replaced by FALSE in the DAG (which AND simplifies away wherever
59
 *   they appear under a satisfied disjunction). Only model-true
60
 *   subterms are recursed into for Boolean structure. As a result
61
 *   the SAT enumerator only ever sees an abstraction of the part of
62
 *   F that the model satisfies. The dual precondition violations
63
 *   (F evaluates to false at the model; F is not even model-evaluable)
64
 *   are detected separately, see gen_model_by_proj_sat_guided.
65
 *
66
 *   On a per-cube projection error (a literal contains a term-kind
67
 *   the projector does not support) the failing cube is skipped:
68
 *   its implicant is added as a blocker and SAT enumeration continues
69
 *   with a different implicant of F.
70
 *
71
 *   If SAT-guided enumeration hits the iteration budget, the result
72
 *   is OR(collected, local) (broader than local alone). If no cube
73
 *   ever projects successfully, the wide path falls back to the
74
 *   local cell (which carries the underlying projector error code
75
 *   when both paths fail for the same reason).
76
 */
77

78
#include <assert.h>
79

80
#include "model/generalization.h"
81
#include "model/model_eval.h"
82
#include "model/model_queries.h"
83
#include "model/projection.h"
84
#include "model/val_to_term.h"
85
#include "solvers/cdcl/new_sat_solver.h"
86
#include "terms/term_manager.h"
87
#include "terms/term_substitution.h"
88
#include "terms/terms.h"
89
#include "utils/int_hash_map.h"
90
#include "utils/int_vectors.h"
91
#include "utils/memalloc.h"
92

93

94
/*
95
 * Split the array of variables v into discrete and real variables
96
 * - n = number of variables in v
97
 * - all variables of type REAL are added to vector reals
98
 * - all other variables are added to discretes
99
 */
NEW
100
static void split_elim_array(term_table_t *terms, uint32_t n, const term_t v[], ivector_t *reals, ivector_t *discretes) {
×
101
  uint32_t i;
102
  term_t t;
103

UNCOV
104
  for (i=0; i<n; i++) {
×
UNCOV
105
    t = v[i];
×
UNCOV
106
    if (is_real_term(terms, t)) {
×
UNCOV
107
      ivector_push(reals, t);
×
108
    } else {
109
      ivector_push(discretes, t);
×
110
    }
111
  }
UNCOV
112
}
×
113

114

115
/*
116
 * Conversion of error codes to GEN.. codes
117
 * - eval_errors are in the range [-7 ... -2]
118
 *   MDL_EVAL_FAILED = -7 and MDL_EVAL_INTERNAL_ERROR = -2
119
 *   they are kept unchanged
120
 * - conversion errors are in the range [-6 .. -2]
121
 *   CONVERT_FAILED = -6 and CONVERT_INTERNAL_ERROR = -2
122
 *   we renumber them to [-13 .. -9]
123
 * - implicant construction errors are in the range [-8 ... -2]
124
 *   MDL_EVAL_FORMULA_FALSE = -8 and MDL_EVAL_INTERNAL_ERROR = -2
125
 * - projection errors are in the range [-7 .. - 1]
126
 *   we renumber to [-20 .. -14]
127
 */
UNCOV
128
static inline int32_t gen_eval_error(int32_t error) {
×
129
  assert(MDL_EVAL_FAILED <= error && error <= MDL_EVAL_INTERNAL_ERROR);
UNCOV
130
  return error;
×
131
}
132

UNCOV
133
static inline int32_t gen_convert_error(int32_t error) {
×
134
  assert(CONVERT_FAILED <= error && error <= CONVERT_INTERNAL_ERROR);
UNCOV
135
  return error + (GEN_CONV_INTERNAL_ERROR - CONVERT_INTERNAL_ERROR);
×
136
}
137

UNCOV
138
static inline int32_t gen_implicant_error(int32_t error) {
×
139
  assert(MDL_EVAL_FORMULA_FALSE <= error && error <= MDL_EVAL_INTERNAL_ERROR);
UNCOV
140
  return error;
×
141
}
142

UNCOV
143
static inline int32_t gen_projection_error(proj_flag_t error) {
×
144
  assert(PROJ_ERROR_UNSUPPORTED_ARITH_TERM <= error && error <= PROJ_ERROR_NON_LINEAR);
UNCOV
145
  return error + (GEN_PROJ_ERROR_NON_LINEAR - PROJ_ERROR_NON_LINEAR);
×
146
}
147

148

149
/*
150
 * Generalization by substitution: core procedure
151
 * - mdl = model
152
 * - mngr = relevant term manager
153
 * - elim[0 ... nelim -1] = variables to eliminate
154
 * - on entry to the function, v must contain the formulas to project
155
 * - the result is returned in place (in vector v)
156
 *
157
 * - returned code: 0 if no error, an error code otherwise
158
 * - error codes are listed in generalization.h
159
 */
UNCOV
160
static int32_t gen_model_by_subst(model_t *mdl, term_manager_t *mngr, uint32_t nelims, const term_t elim[], ivector_t *v) {
×
161
  term_subst_t subst;
162
  ivector_t aux;
163
  term_table_t *terms;
164
  int32_t code;
165
  uint32_t k, n;
166
  term_t t;
167

168
  // get the value of elim[i] in aux.data[i]
169
  init_ivector(&aux, nelims);
×
UNCOV
170
  code = evaluate_term_array(mdl, nelims, elim, aux.data);
×
UNCOV
171
  if (code < 0) {
×
172
    // error in evaluator
UNCOV
173
    code = gen_eval_error(code);
×
174
    assert(GEN_EVAL_FAILED <= code  && code <= GEN_EVAL_INTERNAL_ERROR);
UNCOV
175
    goto done;
×
176
  }
177

178
  // convert every aux.data[i] to a constant term
179
  terms = term_manager_get_terms(mngr);
×
180
  k = convert_value_array(mngr, terms, model_get_vtbl(mdl), nelims, aux.data);
×
UNCOV
181
  if (k < nelims) {
×
182
    // aux.data[k] couldn't be converted to a term
183
    // the error code is in aux.data[k]
184
    code = gen_convert_error(aux.data[k]);
×
185
    assert(GEN_CONV_FAILED <= code && code <= GEN_CONV_INTERNAL_ERROR);
UNCOV
186
    goto done;
×
187
  }
188

189
  // build the substitution: elim[i] := aux.data[i]
190
  // then apply it to every term in vector v
UNCOV
191
  code = 0;
×
UNCOV
192
  init_term_subst(&subst, mngr, nelims, elim, aux.data);
×
193
  n = v->size;
×
UNCOV
194
  for (k=0; k<n; k++) {
×
195
    t = apply_term_subst(&subst, v->data[k]);
×
UNCOV
196
    v->data[k] = t;
×
UNCOV
197
    if (t < 0) {
×
UNCOV
198
      code = GEN_PROJ_ERROR_IN_SUBST;
×
UNCOV
199
      break;
×
200
    }
201
  }
202
  delete_term_subst(&subst);
×
203

204
 done:
×
205
  delete_ivector(&aux);
×
206

207
  return code;
×
208
}
209

210

211
/*
212
 * Generalization by local projection (legacy pipeline):
213
 *   - compute an implicant of v then project the implicant
214
 * - mdl = model
215
 * - mngr = relevant term manager
216
 * - elim[0 ... nelims-1] = variables to eliminate
217
 * - on entry to the function, v must contain the formulas to project
218
 *   the result is returned in place (in vector v)
219
 * - extra_error = to store another error code for diagnosis (see projection.h).
220
 *
221
 * Return code: 0 if no error, an error code otherwise
222
 *
223
 * The output cell is sign-invariant for the chosen implicant. If v[]
224
 * has Boolean structure (disjunctions, Boolean ITEs), only one branch
225
 * is captured: the one selected by get_implicant from the model.
226
 */
227
static int32_t gen_model_by_proj_local(model_t *mdl, term_manager_t *mngr, uint32_t nelims, const term_t elim[], ivector_t *v, int32_t *extra_error) {
10✔
228
  ivector_t implicant;
229
  int32_t code;
230
  proj_flag_t pflag;
231

232
  init_ivector(&implicant, 10);
10✔
233
  code = get_implicant(mdl, mngr, LIT_COLLECTOR_ALL_OPTIONS, v->size, v->data, &implicant);
10✔
234
  if (code < 0) {
10✔
235
    // implicant construction failed
NEW
236
    code = gen_implicant_error(code);
×
UNCOV
237
    goto done;
×
238
  }
239
  
240
  ivector_reset(v); // reset v to collect the projection result
10✔
241
  code = 0;
10✔
242
  pflag = project_literals(mdl, mngr, implicant.size, implicant.data, nelims, elim, v, extra_error);
10✔
243
  if (pflag != PROJ_NO_ERROR) {
10✔
UNCOV
244
    code = gen_projection_error(pflag);
×
245
  }
246

247
 done:
10✔
248
  delete_ivector(&implicant);
10✔
249

250
  return code;
10✔
251
  
252
}
253

254

255

256
/*
257
 * Default cube budget for the SAT-guided wide pipeline. If SAT-guided
258
 * implicant enumeration would emit more than this many cubes, the
259
 * loop stops and the wide result is OR(collected, local fallback).
260
 */
261
#define WIDE_CUBE_BUDGET 1024
262

263
/*
264
 * Project a single cube via the legacy implicant+project pipeline.
265
 * The cube's literals are normalized through get_implicant (which
266
 * expands ITE-inside-arith, etc.) and then projected.
267
 *
268
 * The projected literals are appended to *out (a list of literals
269
 * whose AND is the projected cube). out is not reset.
270
 */
271
static int32_t project_one_cube_into(model_t *mdl, term_manager_t *mngr,
1,036✔
272
                                     const term_t *cube_lits, uint32_t cube_size,
273
                                     uint32_t nelims, const term_t elim[],
274
                                     ivector_t *out, int32_t *extra_error) {
275
  ivector_t implicant;
276
  proj_flag_t pflag;
277
  int32_t code;
278

279
  init_ivector(&implicant, cube_size);
1,036✔
280

281
  code = get_implicant(mdl, mngr, LIT_COLLECTOR_ALL_OPTIONS, cube_size, cube_lits, &implicant);
1,036✔
282
  if (code < 0) {
1,036✔
NEW
283
    code = gen_implicant_error(code);
×
NEW
284
    goto cleanup;
×
285
  }
286

287
  pflag = project_literals(mdl, mngr, implicant.size, implicant.data,
1,036✔
288
                           nelims, elim, out, extra_error);
289
  if (pflag != PROJ_NO_ERROR) {
1,036✔
NEW
290
    code = gen_projection_error(pflag);
×
NEW
291
    goto cleanup;
×
292
  }
293

294
  code = 0;
1,036✔
295

296
 cleanup:
1,036✔
297
  delete_ivector(&implicant);
1,036✔
298
  return code;
1,036✔
299
}
300

301

302
/*
303
 * SAT-GUIDED WIDE PROJECTION
304
 *
305
 * Front end built on a model-pruned Boolean abstraction and SAT-guided
306
 * implicant enumeration. Each emitted Boolean implicant is mapped back
307
 * to a theory cube and projected via project_one_cube_into above. The
308
 * union of the projected cubes is returned as the wide cell.
309
 */
310

311
typedef int32_t bool_node_id_t;
312

313
typedef enum {
314
  BOOL_NODE_TRUE = 0,
315
  BOOL_NODE_FALSE = 1,
316
  BOOL_NODE_LIT = 2,
317
  BOOL_NODE_AND = 3,
318
  BOOL_NODE_OR = 4,
319
} bool_node_kind_t;
320

321
typedef struct bool_node_s {
322
  uint8_t kind;
323
  literal_t lit;
324
  uint32_t start;
325
  uint32_t count;
326
  literal_t tseitin;
327
} bool_node_t;
328

329
typedef struct bool_dag_s {
330
  bool_node_t *data;
331
  uint32_t size;
332
  uint32_t capacity;
333
  ivector_t children;
334
} bool_dag_t;
335

336
enum {
337
  BOOL_DAG_TRUE = 0,
338
  BOOL_DAG_FALSE = 1,
339
};
340

341
typedef enum {
342
  ABS_OK = 0,
343
  ABS_ERROR = 1,
344
} abs_status_t;
345

346
typedef enum {
347
  ABS_EVAL_FALSE = 0,
348
  ABS_EVAL_TRUE = 1,
349
  ABS_EVAL_ERROR = 2,
350
} abs_eval_t;
351

352
typedef struct abs_builder_s {
353
  term_manager_t *mngr;
354
  term_table_t *terms;
355
  evaluator_t *eval;
356
  int_hmap_t atom_to_bvar;
357
  ivector_t bvar_to_atom;
358
  int_hmap_t cache_signed;
359
  bool_dag_t dag;
360
  bool decomposed;
361
} abs_builder_t;
362

363
static void init_bool_dag(bool_dag_t *dag) {
8✔
364
  dag->capacity = 32;
8✔
365
  dag->size = 0;
8✔
366
  dag->data = (bool_node_t *) safe_malloc(dag->capacity * sizeof(bool_node_t));
8✔
367
  init_ivector(&dag->children, 64);
8✔
368

369
  // Constant TRUE node.
370
  dag->data[dag->size].kind = BOOL_NODE_TRUE;
8✔
371
  dag->data[dag->size].lit = null_literal;
8✔
372
  dag->data[dag->size].start = 0;
8✔
373
  dag->data[dag->size].count = 0;
8✔
374
  dag->data[dag->size].tseitin = null_literal;
8✔
375
  dag->size ++;
8✔
376

377
  // Constant FALSE node.
378
  dag->data[dag->size].kind = BOOL_NODE_FALSE;
8✔
379
  dag->data[dag->size].lit = null_literal;
8✔
380
  dag->data[dag->size].start = 0;
8✔
381
  dag->data[dag->size].count = 0;
8✔
382
  dag->data[dag->size].tseitin = null_literal;
8✔
383
  dag->size ++;
8✔
384
}
8✔
385

386
static void delete_bool_dag(bool_dag_t *dag) {
8✔
387
  safe_free(dag->data);
8✔
388
  dag->data = NULL;
8✔
389
  dag->size = 0;
8✔
390
  dag->capacity = 0;
8✔
391
  delete_ivector(&dag->children);
8✔
392
}
8✔
393

NEW
394
static void bool_dag_rollback(bool_dag_t *dag, uint32_t size, uint32_t children_size) {
×
395
  assert(BOOL_DAG_FALSE < size && size <= dag->size);
396
  assert(children_size <= dag->children.size);
397

NEW
398
  dag->size = size;
×
NEW
399
  dag->children.size = children_size;
×
NEW
400
}
×
401

402
static bool_node_id_t bool_dag_add_node(bool_dag_t *dag, bool_node_kind_t kind,
74✔
403
                                        literal_t lit, uint32_t n, const int32_t child[]) {
404
  bool_node_id_t id;
405
  uint32_t i;
406

407
  if (dag->size == dag->capacity) {
74✔
408
    dag->capacity <<= 1;
1✔
409
    dag->data = (bool_node_t *) safe_realloc(dag->data, dag->capacity * sizeof(bool_node_t));
1✔
410
  }
411

412
  id = (bool_node_id_t) dag->size;
74✔
413
  dag->data[id].kind = kind;
74✔
414
  dag->data[id].lit = lit;
74✔
415
  dag->data[id].start = dag->children.size;
74✔
416
  dag->data[id].count = n;
74✔
417
  dag->data[id].tseitin = null_literal;
74✔
418
  for (i = 0; i < n; i++) {
156✔
419
    ivector_push(&dag->children, child[i]);
82✔
420
  }
421
  dag->size ++;
74✔
422
  return id;
74✔
423
}
424

425
static bool_node_id_t bool_dag_add_lit(bool_dag_t *dag, literal_t lit) {
42✔
426
  return bool_dag_add_node(dag, BOOL_NODE_LIT, lit, 0, NULL);
42✔
427
}
428

429
static bool_node_id_t bool_dag_add_and(bool_dag_t *dag, uint32_t n, const int32_t child[]) {
15✔
430
  return bool_dag_add_node(dag, BOOL_NODE_AND, null_literal, n, child);
15✔
431
}
432

433
static bool_node_id_t bool_dag_add_or(bool_dag_t *dag, uint32_t n, const int32_t child[]) {
17✔
434
  return bool_dag_add_node(dag, BOOL_NODE_OR, null_literal, n, child);
17✔
435
}
436

437
static inline bool bool_node_is_true(bool_node_id_t id) {
80✔
438
  return id == BOOL_DAG_TRUE;
80✔
439
}
440

441
static inline bool bool_node_is_false(bool_node_id_t id) {
114✔
442
  return id == BOOL_DAG_FALSE;
114✔
443
}
444

445
static void init_abs_builder(abs_builder_t *b, model_t *mdl, term_manager_t *mngr, evaluator_t *eval) {
8✔
446
  b->mngr = mngr;
8✔
447
  b->terms = term_manager_get_terms(mngr);
8✔
448
  b->eval = eval;
8✔
449
  init_int_hmap(&b->atom_to_bvar, 0);
8✔
450
  init_ivector(&b->bvar_to_atom, 16);
8✔
451
  ivector_push(&b->bvar_to_atom, NULL_TERM); // var 0 is reserved by new_sat_solver
8✔
452
  init_int_hmap(&b->cache_signed, 0);
8✔
453
  init_bool_dag(&b->dag);
8✔
454
  b->decomposed = false;
8✔
455
  (void) mdl;
456
}
8✔
457

458
static void delete_abs_builder(abs_builder_t *b) {
8✔
459
  // After ABS_ERROR, discard the whole builder: rollback does not undo caches.
460
  delete_bool_dag(&b->dag);
8✔
461
  delete_int_hmap(&b->cache_signed);
8✔
462
  delete_ivector(&b->bvar_to_atom);
8✔
463
  delete_int_hmap(&b->atom_to_bvar);
8✔
464
}
8✔
465

466
static bvar_t abs_builder_get_atom_var(abs_builder_t *b, term_t atom) {
42✔
467
  int_hmap_pair_t *r;
468
  bvar_t v;
469

470
  assert(atom == unsigned_term(atom));
471
  r = int_hmap_get(&b->atom_to_bvar, atom);
42✔
472
  if (r->val < 0) {
42✔
473
    v = (bvar_t) b->bvar_to_atom.size;
42✔
474
    r->val = v;
42✔
475
    ivector_push(&b->bvar_to_atom, atom);
42✔
476
  } else {
NEW
477
    v = (bvar_t) r->val;
×
478
  }
479
  return v;
42✔
480
}
481

482
static abs_status_t abstract_signed(abs_builder_t *b, term_t t, bool_node_id_t *out);
483

484
static void ivector_push_unique(ivector_t *v, int32_t x) {
11,373✔
485
  uint32_t i, n;
486

487
  n = v->size;
11,373✔
488
  for (i = 0; i < n; i++) {
67,844✔
489
    if (v->data[i] == x) {
56,472✔
490
      return;
1✔
491
    }
492
  }
493
  ivector_push(v, x);
11,372✔
494
}
495

496
static abs_status_t bool_dag_mk_and(abs_builder_t *b, ivector_t *child, bool_node_id_t *out) {
16✔
497
  bool_dag_t *dag;
498
  ivector_t flat;
499
  uint32_t i, j, n;
500
  bool_node_id_t id;
501
  bool_node_t *node;
502

503
  dag = &b->dag;
16✔
504
  init_ivector(&flat, child->size);
16✔
505
  n = child->size;
16✔
506
  for (i = 0; i < n; i++) {
50✔
507
    id = child->data[i];
34✔
508
    if (bool_node_is_false(id)) {
34✔
NEW
509
      *out = BOOL_DAG_FALSE;
×
NEW
510
      delete_ivector(&flat);
×
NEW
511
      return ABS_OK;
×
512
    }
513
    if (bool_node_is_true(id)) {
34✔
NEW
514
      continue;
×
515
    }
516
    node = &dag->data[id];
34✔
517
    if (node->kind == BOOL_NODE_AND) {
34✔
518
      for (j = 0; j < node->count; j++) {
27✔
519
        ivector_push_unique(&flat, dag->children.data[node->start + j]);
21✔
520
      }
521
    } else {
522
      ivector_push_unique(&flat, id);
28✔
523
    }
524
  }
525

526
  if (flat.size == 0) {
16✔
NEW
527
    *out = BOOL_DAG_TRUE;
×
528
  } else if (flat.size == 1) {
16✔
529
    *out = flat.data[0];
1✔
530
  } else {
531
    *out = bool_dag_add_and(dag, flat.size, flat.data);
15✔
532
  }
533
  delete_ivector(&flat);
16✔
534
  return ABS_OK;
16✔
535
}
536

537
static abs_status_t bool_dag_mk_or(abs_builder_t *b, ivector_t *child, bool_node_id_t *out) {
19✔
538
  bool_dag_t *dag;
539
  ivector_t flat;
540
  uint32_t i, j, n;
541
  bool_node_id_t id;
542
  bool_node_t *node;
543

544
  dag = &b->dag;
19✔
545
  init_ivector(&flat, child->size);
19✔
546
  n = child->size;
19✔
547
  for (i = 0; i < n; i++) {
57✔
548
    id = child->data[i];
38✔
549
    if (bool_node_is_true(id)) {
38✔
NEW
550
      *out = BOOL_DAG_TRUE;
×
NEW
551
      delete_ivector(&flat);
×
NEW
552
      return ABS_OK;
×
553
    }
554
    if (bool_node_is_false(id)) {
38✔
555
      continue;
2✔
556
    }
557
    node = &dag->data[id];
36✔
558
    if (node->kind == BOOL_NODE_OR) {
36✔
NEW
559
      for (j = 0; j < node->count; j++) {
×
NEW
560
        ivector_push_unique(&flat, dag->children.data[node->start + j]);
×
561
      }
562
    } else {
563
      ivector_push_unique(&flat, id);
36✔
564
    }
565
  }
566

567
  if (flat.size == 0) {
19✔
NEW
568
    *out = BOOL_DAG_FALSE;
×
569
  } else if (flat.size == 1) {
19✔
570
    *out = flat.data[0];
2✔
571
  } else {
572
    *out = bool_dag_add_or(dag, flat.size, flat.data);
17✔
573
  }
574
  delete_ivector(&flat);
19✔
575
  return ABS_OK;
19✔
576
}
577

578
static abs_status_t abstract_or_term(abs_builder_t *b, composite_term_t *desc, bool_node_id_t *out) {
19✔
579
  ivector_t child;
580
  uint32_t i, n;
581
  bool_node_id_t c;
582
  abs_status_t st;
583

584
  n = desc->arity;
19✔
585
  init_ivector(&child, n);
19✔
586
  for (i = 0; i < n; i++) {
57✔
587
    st = abstract_signed(b, desc->arg[i], &c);
38✔
588
    if (st != ABS_OK) {
38✔
NEW
589
      delete_ivector(&child);
×
NEW
590
      return st;
×
591
    }
592
    ivector_push(&child, c);
38✔
593
  }
594
  st = bool_dag_mk_or(b, &child, out);
19✔
595
  delete_ivector(&child);
19✔
596
  return st;
19✔
597
}
598

599
static abs_status_t abstract_and_of_opposites(abs_builder_t *b, composite_term_t *desc, bool_node_id_t *out) {
8✔
600
  ivector_t child;
601
  uint32_t i, n;
602
  bool_node_id_t c;
603
  abs_status_t st;
604

605
  n = desc->arity;
8✔
606
  init_ivector(&child, n);
8✔
607
  for (i = 0; i < n; i++) {
33✔
608
    st = abstract_signed(b, opposite_term(desc->arg[i]), &c);
25✔
609
    if (st != ABS_OK) {
25✔
NEW
610
      delete_ivector(&child);
×
NEW
611
      return st;
×
612
    }
613
    ivector_push(&child, c);
25✔
614
    if (bool_node_is_false(c)) {
25✔
NEW
615
      break;
×
616
    }
617
  }
618
  st = bool_dag_mk_and(b, &child, out);
8✔
619
  delete_ivector(&child);
8✔
620
  return st;
8✔
621
}
622

NEW
623
static abs_status_t abstract_and2(abs_builder_t *b, term_t a, term_t c, bool_node_id_t *out) {
×
624
  ivector_t child;
625
  bool_node_id_t x;
626
  abs_status_t st;
627

NEW
628
  init_ivector(&child, 2);
×
NEW
629
  st = abstract_signed(b, a, &x);
×
NEW
630
  if (st != ABS_OK) goto done;
×
NEW
631
  ivector_push(&child, x);
×
NEW
632
  if (! bool_node_is_false(x)) {
×
NEW
633
    st = abstract_signed(b, c, &x);
×
NEW
634
    if (st != ABS_OK) goto done;
×
NEW
635
    ivector_push(&child, x);
×
636
  }
NEW
637
  st = bool_dag_mk_and(b, &child, out);
×
638

NEW
639
 done:
×
NEW
640
  delete_ivector(&child);
×
NEW
641
  return st;
×
642
}
643

NEW
644
static abs_status_t abstract_boolean_ite(abs_builder_t *b, term_t base, bool neg, bool_node_id_t *out) {
×
645
  composite_term_t *idesc;
646
  term_t cond, then_b, else_b;
647
  bool_node_id_t left, right;
648
  ivector_t child;
649
  abs_status_t st;
650

NEW
651
  idesc = ite_term_desc(b->terms, base);
×
NEW
652
  cond = idesc->arg[0];
×
NEW
653
  then_b = idesc->arg[1];
×
NEW
654
  else_b = idesc->arg[2];
×
655

NEW
656
  st = abstract_and2(b, cond, neg ? opposite_term(then_b) : then_b, &left);
×
NEW
657
  if (st != ABS_OK) return st;
×
658

NEW
659
  st = abstract_and2(b, opposite_term(cond), neg ? opposite_term(else_b) : else_b, &right);
×
NEW
660
  if (st != ABS_OK) return st;
×
661

NEW
662
  init_ivector(&child, 2);
×
NEW
663
  ivector_push(&child, left);
×
NEW
664
  ivector_push(&child, right);
×
NEW
665
  st = bool_dag_mk_or(b, &child, out);
×
NEW
666
  delete_ivector(&child);
×
NEW
667
  return st;
×
668
}
669

670
static abs_status_t abstract_leaf(abs_builder_t *b, term_t t, bool_node_id_t *out) {
42✔
671
  term_t atom;
672
  bvar_t v;
673
  literal_t lit;
674

675
  atom = unsigned_term(t);
42✔
676
  v = abs_builder_get_atom_var(b, atom);
42✔
677
  lit = is_neg_term(t) ? neg_lit(v) : pos_lit(v);
42✔
678
  *out = bool_dag_add_lit(&b->dag, lit);
42✔
679
  return ABS_OK;
42✔
680
}
681

682
static abs_eval_t eval_boolean_at_model(evaluator_t *eval, term_t t) {
71✔
683
  value_t v;
684

685
  v = eval_in_model(eval, t);
71✔
686
  if (! good_object(eval->vtbl, v) || ! object_is_boolean(eval->vtbl, v)) {
71✔
NEW
687
    return ABS_EVAL_ERROR;
×
688
  }
689
  return boolobj_value(eval->vtbl, v) ? ABS_EVAL_TRUE : ABS_EVAL_FALSE;
71✔
690
}
691

692
static abs_status_t abstract_signed(abs_builder_t *b, term_t t, bool_node_id_t *out) {
72✔
693
  int_hmap_pair_t *r;
694
  term_t base;
695
  bool neg;
696
  term_kind_t kind;
697
  uint32_t saved_dag_size, saved_children_size;
698
  abs_eval_t eval;
699
  abs_status_t st;
700

701
  r = int_hmap_find(&b->cache_signed, t);
72✔
702
  if (r != NULL) {
72✔
703
    *out = r->val;
1✔
704
    return ABS_OK;
1✔
705
  }
706

707
  if (t == true_term) {
71✔
NEW
708
    *out = BOOL_DAG_TRUE;
×
NEW
709
    goto cache_result;
×
710
  }
711
  if (t == false_term) {
71✔
NEW
712
    *out = BOOL_DAG_FALSE;
×
NEW
713
    goto cache_result;
×
714
  }
715

716
  eval = eval_boolean_at_model(b->eval, t);
71✔
717
  if (eval == ABS_EVAL_ERROR) {
71✔
NEW
718
    return ABS_ERROR;
×
719
  }
720
  if (eval == ABS_EVAL_FALSE) {
71✔
721
    *out = BOOL_DAG_FALSE;
2✔
722
  } else {
723
    saved_dag_size = b->dag.size;
69✔
724
    saved_children_size = b->dag.children.size;
69✔
725

726
    base = unsigned_term(t);
69✔
727
    neg = is_neg_term(t);
69✔
728
    kind = term_kind(b->terms, base);
69✔
729

730
    if (kind == OR_TERM) {
69✔
731
      b->decomposed = true;
27✔
732
      if (neg) {
27✔
733
        st = abstract_and_of_opposites(b, or_term_desc(b->terms, base), out);
8✔
734
      } else {
735
        st = abstract_or_term(b, or_term_desc(b->terms, base), out);
19✔
736
      }
737
      if (st != ABS_OK) goto error;
27✔
738
      goto cache_result;
27✔
739
    }
740

741
    if ((kind == ITE_TERM || kind == ITE_SPECIAL) && is_boolean_term(b->terms, base)) {
42✔
NEW
742
      b->decomposed = true;
×
NEW
743
      st = abstract_boolean_ite(b, base, neg, out);
×
NEW
744
      if (st != ABS_OK) goto error;
×
NEW
745
      goto cache_result;
×
746
    }
747

748
    (void) abstract_leaf(b, t, out);
42✔
749
  }
750

751
 cache_result:
71✔
752
  int_hmap_add(&b->cache_signed, t, *out);
71✔
753
  return ABS_OK;
71✔
754

NEW
755
 error:
×
756
  // Roll back only the DAG; successful subcalls may have populated caches.
757
  // The caller must discard this builder after ABS_ERROR.
NEW
758
  bool_dag_rollback(&b->dag, saved_dag_size, saved_children_size);
×
NEW
759
  return st;
×
760
}
761

762
static abs_status_t abstract_formula_array(abs_builder_t *b, uint32_t n, const term_t f[], bool_node_id_t *out) {
8✔
763
  ivector_t child;
764
  uint32_t i;
765
  bool_node_id_t c;
766
  abs_status_t st;
767

768
  init_ivector(&child, n);
8✔
769
  for (i = 0; i < n; i++) {
17✔
770
    st = abstract_signed(b, f[i], &c);
9✔
771
    if (st != ABS_OK) {
9✔
NEW
772
      delete_ivector(&child);
×
NEW
773
      return st;
×
774
    }
775
    ivector_push(&child, c);
9✔
776
    if (bool_node_is_false(c)) {
9✔
NEW
777
      break;
×
778
    }
779
  }
780
  st = bool_dag_mk_and(b, &child, out);
8✔
781
  delete_ivector(&child);
8✔
782
  return st;
8✔
783
}
784

785
static void sat_add_clause(sat_solver_t *sat, uint32_t n, literal_t *lit) {
1,127✔
786
  nsat_solver_simplify_and_add_clause(sat, n, lit);
1,127✔
787
}
1,127✔
788

789
static void sat_add_unit_clause(sat_solver_t *sat, literal_t l) {
7✔
790
  literal_t clause[1];
791

792
  clause[0] = l;
7✔
793
  sat_add_clause(sat, 1, clause);
7✔
794
}
7✔
795

796
static void sat_add_binary_clause(sat_solver_t *sat, literal_t l1, literal_t l2) {
59✔
797
  literal_t clause[2];
798

799
  clause[0] = l1;
59✔
800
  clause[1] = l2;
59✔
801
  sat_add_clause(sat, 2, clause);
59✔
802
}
59✔
803

804
static literal_t clausify_node(bool_dag_t *dag, sat_solver_t *sat, bool_node_id_t id) {
66✔
805
  bool_node_t *node;
806
  literal_t result, child_lit;
807
  ivector_t clause;
808
  uint32_t i;
809

810
  assert(0 <= id && (uint32_t) id < dag->size);
811
  node = &dag->data[id];
66✔
812
  if (node->tseitin != null_literal) {
66✔
813
    return node->tseitin;
1✔
814
  }
815

816
  switch (node->kind) {
65✔
817
  case BOOL_NODE_LIT:
40✔
818
    result = node->lit;
40✔
819
    break;
40✔
820

821
  case BOOL_NODE_AND:
8✔
822
    result = pos_lit(nsat_solver_new_var(sat));
8✔
823
    init_ivector(&clause, node->count + 1);
8✔
824
    ivector_push(&clause, result);
8✔
825
    for (i = 0; i < node->count; i++) {
33✔
826
      child_lit = clausify_node(dag, sat, dag->children.data[node->start + i]);
25✔
827
      sat_add_binary_clause(sat, not(result), child_lit);
25✔
828
      ivector_push(&clause, not(child_lit));
25✔
829
    }
830
    sat_add_clause(sat, clause.size, clause.data);
8✔
831
    delete_ivector(&clause);
8✔
832
    break;
8✔
833

834
  case BOOL_NODE_OR:
17✔
835
    result = pos_lit(nsat_solver_new_var(sat));
17✔
836
    init_ivector(&clause, node->count + 1);
17✔
837
    ivector_push(&clause, not(result));
17✔
838
    for (i = 0; i < node->count; i++) {
51✔
839
      child_lit = clausify_node(dag, sat, dag->children.data[node->start + i]);
34✔
840
      sat_add_binary_clause(sat, not(child_lit), result);
34✔
841
      ivector_push(&clause, child_lit);
34✔
842
    }
843
    sat_add_clause(sat, clause.size, clause.data);
17✔
844
    delete_ivector(&clause);
17✔
845
    break;
17✔
846

NEW
847
  default:
×
848
    assert(false);
NEW
849
    result = null_literal;
×
NEW
850
    break;
×
851
  }
852

853
  node->tseitin = result;
65✔
854
  return result;
65✔
855
}
856

857
static void bool_dag_reset_tseitin(bool_dag_t *dag) {
7✔
858
  uint32_t i;
859

860
  for (i = 0; i < dag->size; i++) {
92✔
861
    dag->data[i].tseitin = null_literal;
85✔
862
  }
863
}
7✔
864

865
static bool sat_literal_true(sat_solver_t *sat, literal_t lit) {
16,410✔
866
  return lit_is_true(sat, lit);
16,410✔
867
}
868

869
static bool sat_node_true(bool_dag_t *dag, sat_solver_t *sat, bool_node_id_t id) {
16,413✔
870
  bool_node_t *node;
871
  uint32_t i;
872

873
  node = &dag->data[id];
16,413✔
874
  switch (node->kind) {
16,413✔
NEW
875
  case BOOL_NODE_TRUE:
×
NEW
876
    return true;
×
NEW
877
  case BOOL_NODE_FALSE:
×
NEW
878
    return false;
×
879
  case BOOL_NODE_LIT:
16,410✔
880
    return sat_literal_true(sat, node->lit);
16,410✔
881
  case BOOL_NODE_AND:
3✔
882
    for (i = 0; i < node->count; i++) {
7✔
883
      if (! sat_node_true(dag, sat, dag->children.data[node->start + i])) {
5✔
884
        return false;
1✔
885
      }
886
    }
887
    return true;
2✔
NEW
888
  case BOOL_NODE_OR:
×
NEW
889
    for (i = 0; i < node->count; i++) {
×
NEW
890
      if (sat_node_true(dag, sat, dag->children.data[node->start + i])) {
×
NEW
891
        return true;
×
892
      }
893
    }
NEW
894
    return false;
×
NEW
895
  default:
×
896
    assert(false);
NEW
897
    return false;
×
898
  }
899
}
900

901
static void extract_implicant(bool_dag_t *dag, sat_solver_t *sat, bool_node_id_t id, ivector_t *implicant) {
23,604✔
902
  bool_node_t *node;
903
  uint32_t i;
904
  bool_node_id_t child;
905

906
  node = &dag->data[id];
23,604✔
907
  switch (node->kind) {
23,604✔
NEW
908
  case BOOL_NODE_TRUE:
×
NEW
909
    break;
×
910
  case BOOL_NODE_LIT:
11,288✔
911
    assert(sat_literal_true(sat, node->lit));
912
    ivector_push_unique(implicant, node->lit);
11,288✔
913
    break;
11,288✔
914
  case BOOL_NODE_AND:
1,036✔
915
    for (i = 0; i < node->count; i++) {
12,324✔
916
      extract_implicant(dag, sat, dag->children.data[node->start + i], implicant);
11,288✔
917
    }
918
    break;
1,036✔
919
  case BOOL_NODE_OR:
11,280✔
920
    for (i = 0; i < node->count; i++) {
16,408✔
921
      child = dag->children.data[node->start + i];
16,408✔
922
      if (sat_node_true(dag, sat, child)) {
16,408✔
923
        extract_implicant(dag, sat, child, implicant);
11,280✔
924
        break;
11,280✔
925
      }
926
    }
927
    break;
11,280✔
NEW
928
  default:
×
929
    assert(false);
NEW
930
    break;
×
931
  }
932
}
23,604✔
933

934
static void implicant_to_cube(abs_builder_t *b, const ivector_t *implicant, ivector_t *cube) {
1,036✔
935
  uint32_t i, n;
936
  literal_t lit;
937
  term_t atom;
938

939
  ivector_reset(cube);
1,036✔
940
  n = implicant->size;
1,036✔
941
  for (i = 0; i < n; i++) {
12,323✔
942
    lit = implicant->data[i];
11,287✔
943
    assert(var_of(lit) > const_bvar);
944
    atom = b->bvar_to_atom.data[var_of(lit)];
11,287✔
945
    ivector_push(cube, is_pos(lit) ? atom : opposite_term(atom));
11,287✔
946
  }
947
}
1,036✔
948

949
static void add_blocker_clause_to_sat(sat_solver_t *sat, const ivector_t *implicant) {
1,036✔
950
  ivector_t clause;
951
  uint32_t i, n;
952

953
  init_ivector(&clause, implicant->size);
1,036✔
954
  n = implicant->size;
1,036✔
955
  for (i = 0; i < n; i++) {
12,323✔
956
    ivector_push(&clause, not(implicant->data[i]));
11,287✔
957
  }
958
  sat_add_clause(sat, clause.size, clause.data);
1,036✔
959
  delete_ivector(&clause);
1,036✔
960
}
1,036✔
961

962
static term_t make_projected_cubes_term(term_manager_t *mngr, bool multiple,
1✔
963
                                        const ivector_t *first_projected, const ivector_t *cube_terms) {
964
  if (! multiple) {
1✔
NEW
965
    return mk_and_safe(mngr, first_projected->size, first_projected->data);
×
966
  }
967
  return mk_or_safe(mngr, cube_terms->size, cube_terms->data);
1✔
968
}
969

970
static int32_t append_projected_cube_term(model_t *mdl, term_manager_t *mngr,
1,036✔
971
                                          uint32_t nelims, const term_t elim[],
972
                                          ivector_t *cube, int32_t *extra_error,
973
                                          bool *have_first, bool *multiple,
974
                                          ivector_t *first_projected, ivector_t *cube_terms) {
975
  ivector_t projected;
976
  term_t cube_term;
977
  int32_t code;
978

979
  init_ivector(&projected, 4);
1,036✔
980
  code = project_one_cube_into(mdl, mngr, cube->data, cube->size, nelims, elim, &projected, extra_error);
1,036✔
981
  if (code != 0) {
1,036✔
NEW
982
    delete_ivector(&projected);
×
NEW
983
    return code;
×
984
  }
985

986
  if (! *have_first) {
1,036✔
987
    ivector_reset(first_projected);
7✔
988
    ivector_add(first_projected, projected.data, projected.size);
7✔
989
    *have_first = true;
7✔
990
  } else {
991
    if (! *multiple) {
1,029✔
992
      cube_term = mk_and_safe(mngr, first_projected->size, first_projected->data);
5✔
993
      if (cube_term == NULL_TERM) {
5✔
NEW
994
        delete_ivector(&projected);
×
NEW
995
        return GEN_EVAL_INTERNAL_ERROR;
×
996
      }
997
      ivector_push(cube_terms, cube_term);
5✔
998
      *multiple = true;
5✔
999
    }
1000
    cube_term = mk_and_safe(mngr, projected.size, projected.data);
1,029✔
1001
    if (cube_term == NULL_TERM) {
1,029✔
NEW
1002
      delete_ivector(&projected);
×
NEW
1003
      return GEN_EVAL_INTERNAL_ERROR;
×
1004
    }
1005
    ivector_push(cube_terms, cube_term);
1,029✔
1006
  }
1007

1008
  delete_ivector(&projected);
1,036✔
1009
  return 0;
1,036✔
1010
}
1011

1012
static int32_t gen_model_by_proj_sat_guided(model_t *mdl, term_manager_t *mngr,
8✔
1013
                                            uint32_t nelims, const term_t elim[],
1014
                                            ivector_t *v, uint32_t cube_budget,
1015
                                            int32_t *extra_error) {
1016
  evaluator_t eval;
1017
  abs_builder_t builder;
1018
  sat_solver_t sat;
1019
  ivector_t input, implicant, cube;
1020
  ivector_t first_projected, cube_terms, local;
1021
  bool_node_id_t root;
1022
  literal_t root_lit;
1023
  solver_status_t sat_status;
1024
  uint32_t num_attempts;
1025
  int32_t code;
1026
  bool builder_inited;
1027
  bool sat_inited;
1028
  bool have_first, multiple;
1029
  bool needs_fallback, exhausted_sat;
1030
  term_t collected, local_term, result_terms[2];
1031

1032
  init_ivector(&input, v->size);
8✔
1033
  ivector_add(&input, v->data, v->size);
8✔
1034
  ivector_reset(v);
8✔
1035

1036
  init_evaluator(&eval, mdl);
8✔
1037
  init_abs_builder(&builder, mdl, mngr, &eval);
8✔
1038
  builder_inited = true;
8✔
1039
  sat_inited = false;
8✔
1040
  code = 0;
8✔
1041

1042
  init_ivector(&implicant, 16);
8✔
1043
  init_ivector(&cube, 16);
8✔
1044
  init_ivector(&first_projected, 8);
8✔
1045
  init_ivector(&cube_terms, 8);
8✔
1046
  init_ivector(&local, 8);
8✔
1047
  have_first = false;
8✔
1048
  multiple = false;
8✔
1049
  needs_fallback = false;
8✔
1050
  exhausted_sat = false;
8✔
1051

1052
  if (input.size == 0) {
8✔
NEW
1053
    ivector_push(v, true_term);
×
NEW
1054
    goto cleanup;
×
1055
  }
1056

1057
  if (abstract_formula_array(&builder, input.size, input.data, &root) != ABS_OK) {
8✔
1058
    // ABS_ERROR means eval_boolean_at_model could not produce a Boolean
1059
    // value for a Boolean subterm of F. That is a precondition violation
1060
    // (the API requires F to be true at the model, hence model-evaluable).
1061
    // We delegate to gen_model_by_proj_local rather than asserting: the
1062
    // legacy pipeline runs the same model evaluator inside get_implicant
1063
    // and will surface the specific GEN_EVAL_* / MDL_EVAL_* error code,
1064
    // giving the caller a precise diagnostic instead of an abort.
NEW
1065
    ivector_reset(v);
×
NEW
1066
    ivector_add(v, input.data, input.size);
×
NEW
1067
    code = gen_model_by_proj_local(mdl, mngr, nelims, elim, v, extra_error);
×
NEW
1068
    goto cleanup;
×
1069
  }
1070

1071
  if (bool_node_is_false(root)) {
8✔
NEW
1072
    code = MDL_EVAL_FORMULA_FALSE;
×
NEW
1073
    goto cleanup;
×
1074
  }
1075

1076
  if (bool_node_is_true(root)) {
8✔
NEW
1077
    ivector_push(v, true_term);
×
NEW
1078
    goto cleanup;
×
1079
  }
1080

1081
  if (! builder.decomposed) {
8✔
1082
    ivector_reset(v);
1✔
1083
    ivector_add(v, input.data, input.size);
1✔
1084
    code = gen_model_by_proj_local(mdl, mngr, nelims, elim, v, extra_error);
1✔
1085
    goto cleanup;
1✔
1086
  }
1087

1088
  num_attempts = 0;
7✔
1089
  init_nsat_solver(&sat, builder.bvar_to_atom.size + builder.dag.size + 8, false);
7✔
1090
  sat_inited = true;
7✔
1091
  nsat_solver_add_vars(&sat, builder.bvar_to_atom.size - 1);
7✔
1092
  bool_dag_reset_tseitin(&builder.dag);
7✔
1093
  root_lit = clausify_node(&builder.dag, &sat, root);
7✔
1094
  sat_add_unit_clause(&sat, root_lit);
7✔
1095

1096
  // The loop bounds the number of SAT iterations (not successful
1097
  // projections) by cube_budget. On a projection error we skip the
1098
  // failing cube, block its implicant, and continue: a different
1099
  // implicant may use different literals and project cleanly. We
1100
  // only fall back to gen_model_by_proj_local if no cube ever projects
1101
  // successfully (have_first stays false) or if we hit the iteration
1102
  // budget (in which case OR(collected, local) gives a broader cell).
1103
  while (num_attempts < cube_budget) {
1,043✔
1104
    sat_status = nsat_solve(&sat);
1,042✔
1105
    if (sat_status == STAT_UNSAT) {
1,042✔
1106
      exhausted_sat = true;
6✔
1107
      break;
6✔
1108
    }
1109
    // nsat_solve is documented to return only STAT_SAT or STAT_UNSAT
1110
    // (see solvers/cdcl/new_sat_solver.h); anything else is a bug.
1111
    assert(sat_status == STAT_SAT);
1112

1113
    ivector_reset(&implicant);
1,036✔
1114
    extract_implicant(&builder.dag, &sat, root, &implicant);
1,036✔
1115

1116
    implicant_to_cube(&builder, &implicant, &cube);
1,036✔
1117
    num_attempts ++;
1,036✔
1118

1119
    code = append_projected_cube_term(mdl, mngr, nelims, elim, &cube, extra_error,
1,036✔
1120
                                      &have_first, &multiple, &first_projected, &cube_terms);
1121
    if (code != 0) {
1,036✔
1122
      // Projection error on this cube (typically a literal contains a
1123
      // term-kind the projector doesn't support, e.g. in non-MCSAT
1124
      // builds a non-linear arithmetic term -> PROJ_ERROR_NON_LINEAR,
1125
      // or a function application -> PROJ_ERROR_UNSUPPORTED_ARITH_TERM).
1126
      // Drop this cube and try other implicants of F: a different
1127
      // SAT-guided choice may avoid the offending literal entirely.
NEW
1128
      code = 0;
×
1129
    }
1130

1131
    if (implicant.size == 0) {
1,036✔
1132
      // Root was true under no propositional assumptions (e.g. the
1133
      // abstraction collapsed to a true constant). There is nothing
1134
      // to block, so SAT will only ever produce the same trivial
1135
      // model; stop enumerating.
NEW
1136
      exhausted_sat = true;
×
NEW
1137
      break;
×
1138
    }
1139
    // Must backtrack away from the SAT model before adding the blocker:
1140
    // otherwise y2sat simplifies all blocker literals to false and creates
1141
    // a spurious empty clause.
1142
    nsat_solver_prepare_for_next_search(&sat);
1,036✔
1143
    add_blocker_clause_to_sat(&sat, &implicant);
1,036✔
1144
  }
1145

1146
  // Fall back to gen_model_by_proj_local when either:
1147
  //   - we hit the iteration budget (might be more implicants to find);
1148
  //   - no cube ever projected successfully (so we have nothing useful
1149
  //     to return on our own, and local will surface the underlying
1150
  //     projector error code via its own pipeline; we are not expecting
1151
  //     local to succeed in that case).
1152
  if (!exhausted_sat || !have_first) {
7✔
1153
    needs_fallback = true;
1✔
1154
  }
1155

1156
  if (needs_fallback) {
7✔
1157
    ivector_reset(&local);
1✔
1158
    ivector_add(&local, input.data, input.size);
1✔
1159
    code = gen_model_by_proj_local(mdl, mngr, nelims, elim, &local, extra_error);
1✔
1160
    if (code != 0) {
1✔
NEW
1161
      goto cleanup;
×
1162
    }
1163
    if (! have_first) {
1✔
NEW
1164
      ivector_reset(v);
×
NEW
1165
      ivector_add(v, local.data, local.size);
×
1166
    } else {
1167
      collected = make_projected_cubes_term(mngr, multiple, &first_projected, &cube_terms);
1✔
1168
      local_term = mk_and_safe(mngr, local.size, local.data);
1✔
1169
      ivector_reset(v);
1✔
1170
      if (collected == NULL_TERM || local_term == NULL_TERM) {
1✔
NEW
1171
        ivector_add(v, local.data, local.size);
×
1172
      } else {
1173
        result_terms[0] = collected;
1✔
1174
        result_terms[1] = local_term;
1✔
1175
        collected = mk_or_safe(mngr, 2, result_terms);
1✔
1176
        if (collected == NULL_TERM) {
1✔
NEW
1177
          ivector_add(v, local.data, local.size);
×
1178
        } else {
1179
          ivector_push(v, collected);
1✔
1180
        }
1181
      }
1182
    }
1183
    goto cleanup;
1✔
1184
  }
1185

1186
  // SAT exhausted naturally and at least one cube projected.
1187
  assert(have_first);
1188
  if (! multiple) {
6✔
1189
    ivector_add(v, first_projected.data, first_projected.size);
2✔
1190
  } else {
1191
    collected = mk_or_safe(mngr, cube_terms.size, cube_terms.data);
4✔
1192
    if (collected == NULL_TERM) {
4✔
NEW
1193
      ivector_reset(&local);
×
NEW
1194
      ivector_add(&local, input.data, input.size);
×
NEW
1195
      code = gen_model_by_proj_local(mdl, mngr, nelims, elim, &local, extra_error);
×
NEW
1196
      if (code == 0) {
×
NEW
1197
        ivector_add(v, local.data, local.size);
×
1198
      }
1199
    } else {
1200
      ivector_push(v, collected);
4✔
1201
    }
1202
  }
1203

1204
 cleanup:
8✔
1205
  delete_ivector(&local);
8✔
1206
  delete_ivector(&cube_terms);
8✔
1207
  delete_ivector(&first_projected);
8✔
1208
  delete_ivector(&cube);
8✔
1209
  delete_ivector(&implicant);
8✔
1210
  if (sat_inited) delete_nsat_solver(&sat);
8✔
1211
  if (builder_inited) delete_abs_builder(&builder);
8✔
1212
  delete_evaluator(&eval);
8✔
1213
  delete_ivector(&input);
8✔
1214
  return code;
8✔
1215
}
1216

1217

1218

1219
/*
1220
 * Generalization by substitution
1221
 * - mdl = model
1222
 * - mngr = relevant term manager
1223
 * - f[0 ... n-1] = formula true in the model
1224
 * - elim[0 ... nelim -1] = variables to eliminate
1225
 * - v = result vector
1226
 *
1227
 * - returned code: 0 if no error, an error code otherwise
1228
 * - error codes are listed in generalization.h
1229
 */
NEW
1230
int32_t gen_model_by_substitution(model_t *mdl, term_manager_t *mngr, uint32_t n, const term_t f[], 
×
1231
                                  uint32_t nelims, const term_t elim[], ivector_t *v) {
1232

UNCOV
1233
  ivector_copy(v, f, n);
×
1234
  assert(v->size == n);
UNCOV
1235
  return gen_model_by_subst(mdl, mngr, nelims, elim, v);
×
1236
}
1237

1238

1239
/*
1240
 * Generalize by projection (wide variant, the public default).
1241
 *
1242
 * Walks the Boolean structure of f[], enumerates model-true Boolean
1243
 * implicants via a SAT-guided loop, projects each implicant as a cube
1244
 * through the legacy implicant+project pipeline, and unions the results
1245
 * at the term level. Wider output than gen_model_by_projection_local;
1246
 * recommended for CEGAR-style outer loops over quantifier prefixes.
1247
 *
1248
 * cube_budget caps the number of SAT-guided cubes the enumeration is
1249
 * allowed to emit before falling back to OR(collected, local). Pass 0
1250
 * to use the built-in default (WIDE_CUBE_BUDGET).
1251
 */
1252
int32_t gen_model_by_projection(model_t *mdl, term_manager_t *mngr, uint32_t n, const term_t f[],
8✔
1253
                                uint32_t nelims, const term_t elim[], ivector_t *v,
1254
                                uint32_t cube_budget, int32_t *extra_error) {
1255
  ivector_copy(v, f, n);
8✔
1256
  assert(v->size == n);
1257
  if (cube_budget == 0) cube_budget = WIDE_CUBE_BUDGET;
8✔
1258
  return gen_model_by_proj_sat_guided(mdl, mngr, nelims, elim, v, cube_budget, extra_error);
8✔
1259
}
1260

1261

1262
/*
1263
 * Generalize by projection (legacy local pipeline).
1264
 *
1265
 * Builds a single literal implicant of f[] at the model and projects
1266
 * that flat conjunction. The output is sign-invariant for the chosen
1267
 * implicant. Cheaper per call than the wide variant but commits to one
1268
 * disjunct when f[] has Boolean structure.
1269
 */
1270
int32_t gen_model_by_projection_local(model_t *mdl, term_manager_t *mngr, uint32_t n, const term_t f[],
8✔
1271
                                      uint32_t nelims, const term_t elim[], ivector_t *v, int32_t *extra_error) {
1272
  ivector_copy(v, f, n);
8✔
1273
  assert(v->size == n);
1274
  return gen_model_by_proj_local(mdl, mngr, nelims, elim, v, extra_error);
8✔
1275
}
1276

1277

1278

1279
/*
1280
 * Generalize mdl: two passes:
1281
 * - 1) eliminate the discrete variables by substitution
1282
 * - 2) use projection (wide variant) to eliminate the real variables
1283
 *
1284
 * cube_budget is the SAT-guided cube cap for pass 2 (pass 0 to use
1285
 * the built-in default).
1286
 */
UNCOV
1287
int32_t generalize_model(model_t *mdl, term_manager_t *mngr, uint32_t n, const term_t f[],
×
1288
                         uint32_t nelims, const term_t elim[], ivector_t *v,
1289
                         uint32_t cube_budget, int32_t *extra_error) {
1290
  term_table_t *terms;
1291
  ivector_t discretes;
1292
  ivector_t reals;
1293
  int32_t code;
1294

NEW
1295
  if (cube_budget == 0) cube_budget = WIDE_CUBE_BUDGET;
×
1296

1297
  // if n == 0, there's nothing to do
1298
  code = 0;
×
1299
  if (n > 0) {
×
NEW
1300
    terms = term_manager_get_terms(mngr);
×
NEW
1301
    init_ivector(&reals, 10);
×
UNCOV
1302
    init_ivector(&discretes, 10);
×
UNCOV
1303
    split_elim_array(terms, nelims, elim, &reals, &discretes);
×
1304
   
UNCOV
1305
    ivector_copy(v, f, n);
×
UNCOV
1306
    if (discretes.size > 0) {
×
UNCOV
1307
      code = gen_model_by_subst(mdl, mngr, discretes.size, discretes.data, v);
×
1308
    }
1309
    if (code == 0 && reals.size > 0) {
×
1310
      code = gen_model_by_proj_sat_guided(mdl, mngr, reals.size, reals.data, v, cube_budget, extra_error);
×
1311
    }
1312

1313
    delete_ivector(&reals);
×
UNCOV
1314
    delete_ivector(&discretes);
×
1315
  }
1316

1317
  return code;
×
1318
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc