/**
* Copyright (c) 2012-2016 Marsha Chechik, Alessio Di Sandro, Michalis Famelis,
* Rick Salay.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Alessio Di Sandro - Implementation.
*/
package edu.toronto.cs.se.modelepedia.z3;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jdt.annotation.NonNull;
import com.microsoft.z3.BoolExpr;
import com.microsoft.z3.Context;
import com.microsoft.z3.FuncDecl;
import com.microsoft.z3.Model;
import com.microsoft.z3.Solver;
import com.microsoft.z3.Sort;
import com.microsoft.z3.Status;
import com.microsoft.z3.Symbol;
import com.microsoft.z3.Z3Exception;
import edu.toronto.cs.se.mmint.MMINTException;
public class Z3IncrementalSolver {
public enum Z3IncrementalBehavior {NORMAL, PUSH, POP, POP_IF_UNSAT};
private Context context;
private Solver solver;
private Deque<Set<Sort>> sorts;
private Deque<Set<FuncDecl>> decls;
private void reset() {
context = null;
solver = null;
sorts.clear();
decls.clear();
}
private boolean isValidSymbol(@NonNull String smtSymbol) {
if (smtSymbol.contains(Z3Utils.Z3_MODEL_SEPARATOR)) {
return false;
}
try {
Integer.parseInt(smtSymbol);
return false;
}
catch (NumberFormatException e) {
return true;
}
}
private @NonNull Z3Model runCheckSatAndGetModel(@NonNull String smtEncoding) throws Z3Exception {
BoolExpr expr;
if (sorts.isEmpty() || decls.isEmpty()) {
// parse baseline encoding
expr = context.parseSMTLIB2String(smtEncoding, null, null, null, null);
}
else {
// parse incremental encoding
Set<Sort> lastSorts = sorts.getLast();
Set<FuncDecl> lastDecls = decls.getLast();
expr = context.parseSMTLIB2String(
smtEncoding,
lastSorts.stream().map(Sort::getName).toArray(size -> new Symbol[size]),
lastSorts.toArray(new Sort[0]),
lastDecls.stream().map(FuncDecl::getName).toArray(size -> new Symbol[size]),
lastDecls.toArray(new FuncDecl[0]));
}
// check sat and get model
solver.add(expr);
Status status = solver.check();
Model returnModel = null;
if (status == Status.SATISFIABLE) {
returnModel = solver.getModel();
Set<Sort> returnSorts = (sorts.isEmpty()) ? new HashSet<>() : new HashSet<>(sorts.getLast());
Set<FuncDecl> returnDecls = (decls.isEmpty()) ? new HashSet<>() : new HashSet<>(decls.getLast());
for (Sort returnSort : returnModel.getSorts()) {
if (!isValidSymbol(returnSort.getName().toString()) || returnSorts.contains(returnSort)) {
continue;
}
returnSorts.add(returnSort);
}
for (FuncDecl returnDecl : returnModel.getDecls()) {
if (!isValidSymbol(returnDecl.getName().toString()) || returnDecls.contains(returnDecl)) {
continue;
}
returnDecls.add(returnDecl);
}
sorts.add(returnSorts);
decls.add(returnDecls);
}
return new Z3Model(status, returnModel);
}
// first check sat and get model as baseline
public @NonNull Z3Model firstCheckSatAndGetModel(@NonNull String smtEncoding) {
Map<String, String> config = new HashMap<>();
config.put("model", "true");
try {
context = new Context(config);
solver = context.mkSolver();
sorts = new ArrayDeque<>();
decls = new ArrayDeque<>();
return runCheckSatAndGetModel(smtEncoding);
}
catch (Z3Exception e) {
MMINTException.print(IStatus.WARNING, "Z3 problem, returning unknown result and resetting the solver", e);
reset();
return new Z3Model(Status.UNKNOWN, null);
}
}
// incremental check sat and get model
public @NonNull Z3Model checkSatAndGetModel(@NonNull String smtEncoding, @NonNull Z3IncrementalBehavior incBehavior) {
if (sorts.isEmpty() || decls.isEmpty()) {
MMINTException.print(IStatus.WARNING, "No incremental model found, invoking firstCheckSatAndGetModel() instead", null);
return firstCheckSatAndGetModel(smtEncoding);
}
try {
// push
if (incBehavior != Z3IncrementalBehavior.NORMAL) {
solver.push();
}
// run
Z3Model z3Model = runCheckSatAndGetModel(smtEncoding);
if (z3Model.getZ3Result().isSAT()) {
switch (incBehavior) {
case NORMAL:
case POP_IF_UNSAT:
// keep current model
sorts.removeFirst();
decls.removeFirst();
break;
case POP:
// keep previous model
sorts.removeLast();
decls.removeLast();
break;
case PUSH:
// do nothing: keep previous and current models
}
}
// pop
if (
incBehavior == Z3IncrementalBehavior.POP ||
(incBehavior == Z3IncrementalBehavior.POP_IF_UNSAT && !z3Model.getZ3Result().isSAT())
) {
solver.pop();
}
return z3Model;
}
catch (Z3Exception e) {
MMINTException.print(IStatus.WARNING, "Z3 problem, returning unknown result and resetting the solver", e);
reset();
return new Z3Model(Status.UNKNOWN, null);
}
}
// manual pop
public void finalizePreviousPush() {
//TODO MMINT[Z3] Will break if there is a POP_IF_UNSAT with SAT result between the PUSH and this
sorts.removeFirst();
decls.removeFirst();
}
// manual pop
public void revertToPreviousPush() {
//TODO MMINT[Z3] Will break if there is a POP_IF_UNSAT with SAT result between the PUSH and this
try {
sorts.removeLast();
decls.removeLast();
solver.pop();
}
catch (Z3Exception e) {
MMINTException.print(IStatus.WARNING, "Z3 problem, resetting the solver", e);
reset();
}
}
}