org.sosy_lab.java_smt.solvers.z3.Z3InterpolatingProver.java Source code

Java tutorial

Introduction

Here is the source code for org.sosy_lab.java_smt.solvers.z3.Z3InterpolatingProver.java

Source

/*
 *  JavaSMT is an API wrapper for a collection of SMT solvers.
 *  This file is part of JavaSMT.
 *
 *  Copyright (C) 2007-2016  Dirk Beyer
 *  All rights reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.sosy_lab.java_smt.solvers.z3;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.primitives.Longs;
import com.microsoft.z3.Native;
import com.microsoft.z3.Z3Exception;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import javax.annotation.Nullable;
import org.sosy_lab.common.io.MoreFiles;
import org.sosy_lab.common.io.PathCounterTemplate;
import org.sosy_lab.common.log.LogManager;
import org.sosy_lab.java_smt.api.BooleanFormula;
import org.sosy_lab.java_smt.api.Formula;
import org.sosy_lab.java_smt.api.InterpolatingProverEnvironment;
import org.sosy_lab.java_smt.api.QuantifiedFormulaManager.Quantifier;
import org.sosy_lab.java_smt.api.SolverException;
import org.sosy_lab.java_smt.api.visitors.DefaultFormulaVisitor;
import org.sosy_lab.java_smt.api.visitors.TraversalProcess;

class Z3InterpolatingProver extends Z3SolverBasedProver<Long> implements InterpolatingProverEnvironment<Long> {

    private final LogManager logger;

    private final @Nullable PathCounterTemplate dumpFailedInterpolationQueries;
    private final Deque<List<Long>> assertedFormulas = new ArrayDeque<>();

    Z3InterpolatingProver(Z3FormulaCreator creator, long z3params, LogManager pLogger,
            @Nullable PathCounterTemplate pDumpFailedInterpolationQueries) {
        super(creator, z3params);
        logger = pLogger;
        dumpFailedInterpolationQueries = pDumpFailedInterpolationQueries;

        // add basic level, needed for addConstraints(f) without previous push()
        assertedFormulas.push(new ArrayList<>());
    }

    @Override
    public void pop() {
        Preconditions.checkState(getLevel() == assertedFormulas.size() - 1);
        super.pop();
        assertedFormulas.pop();
    }

    @Override
    public Long addConstraint(BooleanFormula f) {
        long e = super.addConstraint0(f);
        assertedFormulas.peek().add(e);
        return e;
    }

    @Override
    public void push() {
        Preconditions.checkState(getLevel() == assertedFormulas.size() - 1);
        super.push();
        assertedFormulas.push(new ArrayList<>());
    }

    @Override
    @SuppressWarnings({ "unchecked", "varargs" })
    public BooleanFormula getInterpolant(final List<Long> formulasOfA)
            throws InterruptedException, SolverException {
        Preconditions.checkState(!closed);

        // calc difference: formulasOfB := assertedFormulas - formulasOfA
        // we have to handle equal formulas on the stack,
        // so we copy the whole stack and remove the formulas of A once.
        final List<Long> formulasOfB = new LinkedList<>();
        assertedFormulas.forEach(formulasOfB::addAll);
        for (long af : formulasOfA) {
            boolean check = formulasOfB.remove(af); // remove only first occurrence
            assert check : "formula from A must be part of all asserted formulas";
        }

        // binary interpolant is a sequence interpolant of only 2 elements
        return Iterables.getOnlyElement(
                getSeqInterpolants(ImmutableList.of(Sets.newHashSet(formulasOfA), Sets.newHashSet(formulasOfB))));
    }

    @Override
    public List<BooleanFormula> getSeqInterpolants(List<Set<Long>> partitionedFormulas)
            throws InterruptedException, SolverException {
        Preconditions.checkState(!closed);
        Preconditions.checkArgument(partitionedFormulas.size() >= 2,
                "at least 2 partitions needed for interpolation");

        // a 'tree' with all subtrees starting at 0 is called a 'sequence'
        return getTreeInterpolants(partitionedFormulas, new int[partitionedFormulas.size()]);
    }

    @Override
    public List<BooleanFormula> getTreeInterpolants(List<Set<Long>> partitionedFormulas, int[] startOfSubTree)
            throws InterruptedException, SolverException {
        Preconditions.checkState(!closed);
        final long[] conjunctionFormulas = new long[partitionedFormulas.size()];

        // build conjunction of each partition
        for (int i = 0; i < partitionedFormulas.size(); i++) {
            long conjunction = Native.mkAnd(z3context, partitionedFormulas.get(i).size(),
                    Longs.toArray(partitionedFormulas.get(i)));
            Native.incRef(z3context, conjunction);
            conjunctionFormulas[i] = conjunction;
        }

        // build tree of interpolation-points
        final long[] interpolationFormulas = new long[partitionedFormulas.size()];
        final Deque<Z3TreeInterpolant> stack = new ArrayDeque<>();

        int lastSubtree = -1; // subtree starts with 0. With -1<0 we start a new subtree.
        for (int i = 0; i < startOfSubTree.length; i++) {
            final int currentSubtree = startOfSubTree[i];
            final long conjunction;
            if (currentSubtree > lastSubtree) {
                // start of a new subtree -> first element has no children
                conjunction = conjunctionFormulas[i];

            } else { // if (currentSubtree <= lastSubtree) {
                // merge-point in tree, several children at a node -> pop from stack and conjunct
                final List<Long> children = new ArrayList<>();
                while (!stack.isEmpty() && currentSubtree <= stack.peek().getRootOfTree()) {
                    // adding at front is important for tree-structure!
                    children.add(0, stack.pop().getInterpolationPoint());
                }
                children.add(conjunctionFormulas[i]); // add the node itself
                conjunction = Native.mkAnd(z3context, children.size(), Longs.toArray(children));
            }

            final long interpolationPoint;
            if (i == startOfSubTree.length - 1) {
                // the last node in the tree (=root) does not need the interpolation-point-flag
                interpolationPoint = conjunction;
                Preconditions.checkState(currentSubtree == 0, "subtree of root should start at 0.");
                Preconditions.checkState(stack.isEmpty(), "root should be the last element in the stack.");
            } else {
                interpolationPoint = Native.mkInterpolant(z3context, conjunction);
            }

            Native.incRef(z3context, interpolationPoint);
            interpolationFormulas[i] = interpolationPoint;
            stack.push(new Z3TreeInterpolant(currentSubtree, interpolationPoint));
            lastSubtree = currentSubtree;
        }

        Preconditions.checkState(stack.peek().getRootOfTree() == 0, "subtree of root should start at 0.");
        long root = stack.pop().getInterpolationPoint();
        Preconditions.checkState(stack.isEmpty(), "root should have been the last element in the stack.");

        final long proof = Native.solverGetProof(z3context, z3solver);
        Native.incRef(z3context, proof);

        long interpolationResult;
        try {
            interpolationResult = Native.getInterpolant(z3context, proof, //refutation of premises := proof
                    root, // last element is end of chain (root of tree), pattern := interpolation tree
                    Native.mkParams(z3context));
        } catch (Z3Exception e) {
            if (dumpFailedInterpolationQueries != null && !creator.shutdownNotifier.shouldShutdown()) {
                try (Writer dumpFile = MoreFiles.openOutputFile(dumpFailedInterpolationQueries.getFreshPath(),
                        StandardCharsets.UTF_8)) {
                    dumpFile.write(Native.solverToString(z3context, z3solver));
                    dumpFile.write("\n(compute-interpolant ");
                    dumpFile.write(Native.astToString(z3context, root));
                    dumpFile.write(")\n");
                } catch (IOException e2) {
                    logger.logUserException(Level.WARNING, e2, "Could not dump failed interpolation query to file");
                }
            }
            if ("theory not supported by interpolation or bad proof".equals(e.getMessage())) {
                throw new SolverException(e.getMessage(), e);
            }
            throw creator.handleZ3Exception(e);
        }

        // n partitions -> n-1 interpolants
        // the given tree interpolants are sorted in post-order,
        // so we only need to copy them
        final List<BooleanFormula> result = new ArrayList<>();
        for (int i = 0; i < partitionedFormulas.size() - 1; i++) {
            result.add(creator.encapsulateBoolean(Native.astVectorGet(z3context, interpolationResult, i)));
        }

        // cleanup
        Native.decRef(z3context, proof);
        for (long partition : conjunctionFormulas) {
            Native.decRef(z3context, partition);
        }
        for (long partition : interpolationFormulas) {
            Native.decRef(z3context, partition);
        }

        checkInterpolantsForUnboundVariables(result); // Do this last after cleanup.

        return result;
    }

    /**
     * Check whether any formula in a given list contains unbound variables. Z3 has the problem that
     * it sometimes returns such invalid formulas as interpolants
     * (https://github.com/Z3Prover/z3/issues/665).
     */
    private void checkInterpolantsForUnboundVariables(List<BooleanFormula> itps) throws SolverException {
        List<Formula> unboundVariables = new ArrayList<>(1);
        final DefaultFormulaVisitor<TraversalProcess> unboundVariablesCollector = new DefaultFormulaVisitor<TraversalProcess>() {
            @Override
            public TraversalProcess visitBoundVariable(Formula f, int deBruijnIdx) {
                unboundVariables.add(f);
                return TraversalProcess.ABORT;
            }

            @Override
            public TraversalProcess visitQuantifier(BooleanFormula pF, Quantifier pQ, List<Formula> pBoundVariables,
                    BooleanFormula pBody) {
                return TraversalProcess.SKIP; // bound variables in quantifiers are probably ok
            }

            @Override
            protected TraversalProcess visitDefault(org.sosy_lab.java_smt.api.Formula pF) {
                return TraversalProcess.CONTINUE;
            }
        };

        for (BooleanFormula itp : itps) {
            creator.visitRecursively(unboundVariablesCollector, itp);
            if (!unboundVariables.isEmpty()) {
                throw new SolverException("Unbound variable " + unboundVariables.get(0) + " in interpolant " + itp);
            }
        }
    }

    @Override
    public void close() {
        super.close();
        Preconditions.checkState(assertedFormulas.size() == 1);
        assertedFormulas.clear();
    }

    private static class Z3TreeInterpolant {
        private final int rootOfSubTree;
        private final long interpolationPoint;

        private Z3TreeInterpolant(int pRootOfSubtree, long pInterpolationPoint) {
            rootOfSubTree = pRootOfSubtree;
            interpolationPoint = pInterpolationPoint;
        }

        private int getRootOfTree() {
            return rootOfSubTree;
        }

        private long getInterpolationPoint() {
            return interpolationPoint;
        }
    }
}