/* * Merge the exists and forall variables of constraint i * - store the variables in solver->all_vars * - store their values in solver->all_values */ static void ef_build_full_map(ef_solver_t *solver, uint32_t i) { ef_cnstr_t *cnstr; ivector_t *v; uint32_t n; assert(i < ef_prob_num_constraints(solver->prob)); cnstr = solver->prob->cnstr + i; // collect all variables of cnstr in solver->all_vars v = &solver->all_vars; ivector_reset(v); n = ef_constraint_num_evars(cnstr); ivector_add(v, cnstr->evars, n); n = ef_constraint_num_uvars(cnstr); ivector_add(v, cnstr->uvars, n); // project the evalues onto the exists variable of constraint i // store the results in all_values v = &solver->all_values; n = ef_constraint_num_evars(cnstr); resize_ivector(v, n); v->size = n; ef_project_exists_model(solver->prob, solver->evalue, cnstr->evars, v->data, n); // copy the uvalues for constraint i (from uvalue_aux to v) n = ef_constraint_num_uvars(cnstr); assert(n == solver->uvalue_aux.size); ivector_add(v, solver->uvalue_aux.data, n); #if EF_VERBOSE printf("Full map\n"); print_full_map(stdout, solver); fflush(stdout); #endif }
/* * Initialize the exists context * - asserts all conditions from solver->prob * - if max_samples is positive, also apply pre-sampling to all the universal constraints * * - solver->status is set as follows * EF_STATUS_SEARCHING: means not solved yet * EF_STATUS_UNSAT: if the exists context is trivially UNSAT * EF_STATUS_..._ERROR: if something goes wrong * EF_STATUS_INTERRUPTED if the pre-sampling is interrupted */ static void ef_solver_start(ef_solver_t *solver) { int32_t code; uint32_t i, n; assert(solver->status == EF_STATUS_IDLE); solver->status = EF_STATUS_SEARCHING; init_exists_context(solver); code = exists_context_assert_conditions(solver); // add all conditions if (code < 0) { // assertion error solver->status = EF_STATUS_ASSERT_ERROR; solver->error_code = code; } else if (code == TRIVIALLY_UNSAT) { solver->status = EF_STATUS_UNSAT; } else if (solver->max_samples > 0) { /* * run the pre-sampling stuff */ n = ef_prob_num_constraints(solver->prob); for (i=0; i<n; i++) { ef_sample_constraint(solver, i); if (solver->status != EF_STATUS_SEARCHING) break; } } }
/* * Option 2: generalize by substitution * - return (prob->cnstr[i].guarantee with y := value) */ static term_t ef_generalize2(ef_prob_t *prob, uint32_t i, term_t *value) { ef_cnstr_t *cnstr; uint32_t n; term_t g; assert(i < ef_prob_num_constraints(prob)); cnstr = prob->cnstr + i; n = ef_constraint_num_uvars(cnstr); g = ef_substitution(prob, cnstr->uvars, value, n, cnstr->guarantee); return g; }
/* * Learn a constraint for the exists variable based on the * existing forall witness for constraint i: * - the witness is defined by the uvars of constraint i * and the values stored in uvalue_aux. * * This function adds a constraint to the exists_context that will remove the * current exists model: * - if solver->option is EF_NOGEN_OPTION, the new constraint is * of the form (or (/= var[0] eval[k0]) ... (/= var[k-1] eval[k-1])) * where var[0 ... k-1] are the exist variables of constraint i * - if solver->option is EF_GEN_BY_SUBST_OPTION, we build a new * constraint by substitution (option 2) * * If something goes wrong, then solver->status is updated to EF_STATUS_ERROR. * If the new learned assertion makes the exist context trivially unsat * then context->status is set to EF_STATUS_UNSAT. * Otherwise context->status is kept as is. */ static void ef_solver_learn(ef_solver_t *solver, uint32_t i) { ef_cnstr_t *cnstr; term_t *val; term_t new_constraint; uint32_t n; int32_t code; assert(i < ef_prob_num_constraints(solver->prob)); cnstr = solver->prob->cnstr + i; switch (solver->option) { case EF_NOGEN_OPTION: /* * project solver->evalues on the existential * variables that occur in constraint i. * then build (or (/= evar[0] val[0]) ... (/= evar[n-1] val[n-1])) */ n = ef_constraint_num_evars(cnstr); resize_ivector(&solver->evalue_aux, n); solver->evalue_aux.size = n; val = solver->evalue_aux.data; ef_project_exists_model(solver->prob, solver->evalue, cnstr->evars, val, n); new_constraint = ef_generalize1(solver->prob, cnstr->evars, val, n); break; case EF_GEN_BY_SUBST_OPTION: val = solver->uvalue_aux.data; new_constraint = ef_generalize2(solver->prob, i, val); if (new_constraint < 0) { // error in substitution solver->status = EF_STATUS_SUBST_ERROR; solver->error_code = new_constraint; return; } break; case EF_GEN_BY_PROJ_OPTION: default: // added this to prevent bogus GCC warning new_constraint = ef_generalize3(solver, i); if (new_constraint < 0) { return; } break; } // add the new constraint to the exists context code = update_exists_context(solver, new_constraint); if (code == TRIVIALLY_UNSAT) { solver->status = EF_STATUS_UNSAT; } else if (code < 0) { solver->status = EF_STATUS_ASSERT_ERROR; solver->error_code = code; } }
/* * Show witness found for constraint i */ void print_forall_witness(FILE *f, ef_solver_t *solver, uint32_t i) { ef_prob_t *prob; ef_cnstr_t *cnstr; uint32_t j, n; prob = solver->prob; assert(i < ef_prob_num_constraints(prob)); cnstr = prob->cnstr + i; n = ef_constraint_num_uvars(cnstr); for (j=0; j<n; j++) { fprintf(f, "%s := ", yices_get_term_name(cnstr->uvars[j])); yices_pp_term(f, solver->uvalue_aux.data[j], 100, 1, 10); } }
/* * Check whether the current exists_model can be falsified by one * of the universal constraints. * - scan the universal constraints starting from solver->scan_idx * - the current exists model is defined by * the mapping from solver->prob->evars to solver->evalues * * Update the solver->status as follows: * - if no constraint falsifies the model, solver->status = EF_STATUS_SAT * - if some constraint falsifies the model, then solver->status may be * updated to EF_STATUS_UNSAT (trivially unsat after learning) * - if something goes wrong, solver->status = EF_STATUS_ERROR * * If constraint i falsifies the model then solver->scan_idx is * set to (i+1) modulo num_constraints. */ static void ef_solver_check_exists_model(ef_solver_t *solver) { smt_status_t status; uint32_t i, n; n = ef_prob_num_constraints(solver->prob); /* * Special case: if there are no universal constraints * we return SAT immediately. */ if (n == 0) { solver->status = EF_STATUS_SAT; return; } i = solver->scan_idx; do { tprintf(solver->trace, 4, "(EF: testing candidate against constraint %"PRIu32")\n", i); status = ef_solver_test_exists_model(solver, i); trace_candidate_check(solver, i, status); switch (status) { case STATUS_SAT: case STATUS_UNKNOWN: #if EF_VERBOSE printf("Counterexample for constraint[%"PRIu32"]\n", i); print_forall_witness(stdout, solver, i); printf("\n"); fflush(stdout); #endif ef_solver_learn(solver, i); default: i ++; if (i == n) { i = 0; } break; } } while (status == STATUS_UNSAT && i != solver->scan_idx); solver->scan_idx = i; // prepare for the next call if (status == STATUS_UNSAT) { // done a full scan solver->status = EF_STATUS_SAT; } }
/* * Compute an implicant for constraint i * - we must have an assignment for the exists variable in solver->evalue * and an assignment for the universal variables of constraints i in * solver->uvalue_aux. * - this builds and store the full model for constraint i in solver->full_model * then construct and store the implicant in solver->implicant * * Error codes: set solver->status to EF_STATUS_ERROR * - this may happen if yices_model_from_map or yices_implicant_for_fomuulas fail */ static void ef_build_implicant(ef_solver_t *solver, uint32_t i) { model_t *mdl; ef_cnstr_t *cnstr; term_vector_t *v; term_t a[2]; uint32_t n; int32_t code; assert(i < ef_prob_num_constraints(solver->prob)); // free the current full model if any if (solver->full_model != NULL) { yices_free_model(solver->full_model); solver->full_model = NULL; } // build the full_map and the correspongin model. ef_build_full_map(solver, i); n = solver->all_vars.size; assert(n == solver->all_values.size); mdl = yices_model_from_map(n, solver->all_vars.data, solver->all_values.data); if (mdl == NULL) { // error in the model construction solver->status = EF_STATUS_ERROR; // TODO: add another code solver->error_code = yices_error_code(); return; } solver->full_model = mdl; cnstr = solver->prob->cnstr + i; a[0] = cnstr->assumption; a[1] = opposite_term(cnstr->guarantee); v = &solver->implicant; v->size = 0; code = yices_implicant_for_formulas(mdl, 2, a, v); if (code < 0) { solver->status = EF_STATUS_ERROR; solver->error_code = yices_error_code(); } #if 1 printf("Implicant\n"); yices_pp_term_array(stdout, v->size, v->data, 120, UINT32_MAX, 0, 0); printf("(%"PRIu32" literals)\n", v->size); #endif }
/* * Sampling for the i-th constraint in solver->prob * - search for at most max_samples y's that satisfy the assumption of constraint i in prob * - for each such y, add a new constraint to the exist context ctx * - max_samples = bound on the number of samples to take (must be positive) * * Constraint i is of the form * (FORALL Y_i: B_i(Y_i) => C(X_i, Y_i)) * - every sample is a model y_i that satisfies B_i. * - for each sample, we learn that any good candidate X_i must satisfy C(X_i, y_i). * So we add the constraint C(X_i, y_i) to ctx. * * Update the solver->status to * - EF_STATUS_UNSAT if the exits context is trivially unsat * - EF_STATUS_..._ERRORif something goes wrong * - EF_STATUS_INTERRUPTED if a call to check/recheck context is interrupted * keep it unchanged otherwise (should be EF_STATUS_SEARCHING). */ static void ef_sample_constraint(ef_solver_t *solver, uint32_t i) { context_t *sampling_ctx; ef_cnstr_t *cnstr; term_t *value; uint32_t nvars, samples; int32_t ucode, ecode; smt_status_t status; term_t cnd; assert(i < ef_prob_num_constraints(solver->prob) && solver->max_samples > 0); cnstr = solver->prob->cnstr + i; /* * make uvalue_aux large enough * its size = number of universal variables in constraint i */ nvars = ef_constraint_num_uvars(cnstr); resize_ivector(&solver->uvalue_aux, nvars); solver->uvalue_aux.size = nvars; value = solver->uvalue_aux.data; samples = solver->max_samples; /* * assert the assumption in the forall context */ sampling_ctx = get_forall_context(solver); ucode = assert_formula(sampling_ctx, cnstr->assumption); while (ucode == CTX_NO_ERROR) { status = satisfy_context(sampling_ctx, solver->parameters, cnstr->uvars, nvars, value, NULL); switch (status) { case STATUS_SAT: case STATUS_UNKNOWN: // learned condition on X: cnd = ef_substitution(solver->prob, cnstr->uvars, value, nvars, cnstr->guarantee); if (cnd < 0) { solver->status = EF_STATUS_SUBST_ERROR; solver->error_code = cnd; goto done; } ecode = update_exists_context(solver, cnd); if (ecode < 0) { solver->status = EF_STATUS_ASSERT_ERROR; solver->error_code = ecode; goto done; } if (ecode == TRIVIALLY_UNSAT) { solver->status = EF_STATUS_UNSAT; goto done; } break; case STATUS_UNSAT: // no more samples for this constraints goto done; case STATUS_INTERRUPTED: solver->status = EF_STATUS_INTERRUPTED; goto done; default: solver->status = EF_STATUS_CHECK_ERROR; solver->error_code = status; goto done; } samples --; if (samples == 0) goto done; ucode = assert_blocking_clause(sampling_ctx); } /* * if ucode < 0, something went wrong in assert_formula * or in assert_blocking_clause */ if (ucode < 0) { solver->status = EF_STATUS_ASSERT_ERROR; solver->error_code = ucode; } done: clear_forall_context(solver, true); }
/* * Test the current exists model using universal constraint i * - i must be a valid index (i.e., 0 <= i < solver->prob->num_cnstr) * - this checks the assertion B_i and not C_i after replacing existential * variables by their values (stored in evalue) * - return code: * if STATUS_SAT (or STATUS_UNKNOWN): a model of (B_i and not C_i) * is found and stored in uvalue_aux * if STATUS_UNSAT: no model found (current exists model is good as * far as constraint i is concerned) * anything else: an error or interruption * * - if we get an error or interruption, solver->status is updated * otherwise, it is kept as is (should be EF_STATUS_SEARCHING) */ static smt_status_t ef_solver_test_exists_model(ef_solver_t *solver, uint32_t i) { context_t *forall_ctx; ef_cnstr_t *cnstr; term_t *value; term_t g; uint32_t n; int32_t code; smt_status_t status; assert(i < ef_prob_num_constraints(solver->prob)); cnstr = solver->prob->cnstr + i; n = ef_prob_num_evars(solver->prob); g = ef_substitution(solver->prob, solver->prob->all_evars, solver->evalue, n, cnstr->guarantee); if (g < 0) { // error in substitution solver->status = EF_STATUS_SUBST_ERROR; solver->error_code = g; return STATUS_ERROR; } /* * make uvalue_aux large enough */ n = ef_constraint_num_uvars(cnstr); resize_ivector(&solver->uvalue_aux, n); solver->uvalue_aux.size = n; value = solver->uvalue_aux.data; forall_ctx = get_forall_context(solver); code = forall_context_assert(solver, cnstr->assumption, g); // assert B_i(Y_i) and not g(Y_i) if (code == CTX_NO_ERROR) { status = satisfy_context(forall_ctx, solver->parameters, cnstr->uvars, n, value, NULL); switch (status) { case STATUS_SAT: case STATUS_UNKNOWN: case STATUS_UNSAT: break; case STATUS_INTERRUPTED: solver->status = EF_STATUS_INTERRUPTED; break; default: solver->status = EF_STATUS_CHECK_ERROR; solver->error_code = status; break; } } else if (code == TRIVIALLY_UNSAT) { assert(context_status(forall_ctx) == STATUS_UNSAT); status = STATUS_UNSAT; } else { // error in assertion solver->status = EF_STATUS_ASSERT_ERROR; solver->error_code = code; status = STATUS_ERROR; } clear_forall_context(solver, true); return status; }
/* * Sample exists models * - stop when we find one that's not refuted by the forall constraints * - or when we reach the iteration bound * * Result: * - solver->status = EF_STATUS_ERROR if something goes wrong * - solver->status = EF_STATUS_INTERRUPTED if one of the calls to * check_context is interrupted * - solver->status = EF_STATUS_UNSAT if all efmodels have been tried and none * of them worked * - solver->status = EF_STATUS_UNKNOWN if the iteration limit is reached * - solver->status = EF_STATUS_SAT if a good model is found * * In the later case, * - the model is stored in solver->exists_model * - also it's available as a mapping form solver->prob->evars to solver->evalues * * Also solver->iters stores the number of iterations used. */ static void ef_solver_search(ef_solver_t *solver) { smt_status_t stat; uint32_t i, max; max = solver->max_iters; i = 0; assert(max > 0); #if 0 printf("\nEF search: %"PRIu32" constraints, %"PRIu32" exists vars, %"PRIu32" forall vars\n", ef_prob_num_constraints(solver->prob), ef_prob_num_evars(solver->prob), ef_prob_num_uvars(solver->prob)); // printf("\nConditions on the exists variables:\n"); // yices_pp_term_array(stdout, ef_prob_num_conditions(solver->prob), solver->prob->conditions, 120, UINT32_MAX, 0, 0); #endif ef_solver_start(solver); while (solver->status == EF_STATUS_SEARCHING && i < max) { #if 0 printf("\n--- Iteration %"PRIu32" (scan_idx = %"PRIu32") ---\n", i, solver->scan_idx); #endif stat = ef_solver_check_exists(solver); switch (stat) { case STATUS_SAT: case STATUS_UNKNOWN: // we have a candidate exists model // check it and learn what we can #if 0 // FOR DEBUGGING printf("Candidate exists model:\n"); print_ef_solution(stdout, solver); printf("\n"); #endif ef_solver_check_exists_model(solver); break; case STATUS_UNSAT: solver->status = EF_STATUS_UNSAT; break; case STATUS_INTERRUPTED: solver->status = EF_STATUS_INTERRUPTED; break; default: solver->status = EF_STATUS_CHECK_ERROR; solver->error_code = stat; break; } i ++; } /* * Cleanup and set status if i == max */ if (solver->status != EF_STATUS_SAT && solver->exists_model != NULL) { yices_free_model(solver->exists_model); solver->exists_model = NULL; if (solver->status == EF_STATUS_SEARCHING) { assert(i == max); solver->status = EF_STATUS_UNKNOWN; } } solver->iters = i; }
/* * Option 3: generalize by computing an implicant then * applying projection. */ static term_t ef_generalize3(ef_solver_t *solver, uint32_t i) { model_t *mdl; ef_cnstr_t *cnstr; ivector_t *v, *w; term_t a[2]; uint32_t n; int32_t code; proj_flag_t pflag; term_t result; assert(i < ef_prob_num_constraints(solver->prob)); // free the current full model if any if (solver->full_model != NULL) { yices_free_model(solver->full_model); solver->full_model = NULL; } // build the full_map and the corresponding model. ef_build_full_map(solver, i); n = solver->all_vars.size; assert(n == solver->all_values.size); mdl = yices_model_from_map(n, solver->all_vars.data, solver->all_values.data); if (mdl == NULL) { // error in the model construction solver->status = EF_STATUS_MDL_ERROR; solver->error_code = yices_error_code(); return NULL_TERM; } solver->full_model = mdl; // Constraint cnstr = solver->prob->cnstr + i; a[0] = cnstr->assumption; // B(y) a[1] = opposite_term(cnstr->guarantee); // not C(x, y) #if EF_VERBOSE printf("Constraint:\n"); yices_pp_term_array(stdout, 2, a, 120, UINT32_MAX, 0, 0); printf("(%"PRIu32" literals)\n", 2); #endif // Compute the implicant v = &solver->implicant; ivector_reset(v); code = get_implicant(mdl, solver->prob->manager, LIT_COLLECTOR_ALL_OPTIONS, 2, a, v); if (code < 0) { solver->status = EF_STATUS_IMPLICANT_ERROR; solver->error_code = code; return NULL_TERM; } #if EF_VERBOSE printf("Implicant:\n"); yices_pp_term_array(stdout, v->size, v->data, 120, UINT32_MAX, 0, 0); printf("(%"PRIu32" literals)\n", v->size); #endif // Projection w = &solver->projection; ivector_reset(w); n = ef_constraint_num_uvars(cnstr); #if EF_VERBOSE printf("(%"PRIu32" universals)\n", n); yices_pp_term_array(stdout, n, cnstr->uvars, 120, UINT32_MAX, 0, 0); #endif pflag = project_literals(mdl, solver->prob->manager, v->size, v->data, n, cnstr->uvars, w); if (pflag != PROJ_NO_ERROR) { solver->status = EF_STATUS_PROJECTION_ERROR; solver->error_code = pflag; return NULL_TERM; } #if EF_VERBOSE printf("Projection:\n"); yices_pp_term_array(stdout, w->size, w->data, 120, UINT32_MAX, 0, 0); printf("(%"PRIu32" literals)\n", w->size); #endif switch (w->size) { case 0: result = true_term; break; case 1: result = w->data[0]; break; default: result = mk_and(solver->prob->manager, w->size, w->data); break; } return opposite_term(result); }