org.sosy_lab.cpachecker.util.predicates.RCNFManager.java Source code

Java tutorial

Introduction

Here is the source code for org.sosy_lab.cpachecker.util.predicates.RCNFManager.java

Source

/*
 * CPAchecker is a tool for configurable software verification.
 *  This file is part of CPAchecker.
 *
 *  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.
 *
 *
 *  CPAchecker web page:
 *    http://cpachecker.sosy-lab.org
 */
package org.sosy_lab.cpachecker.util.predicates;

import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;
import com.google.common.math.LongMath;

import org.sosy_lab.common.configuration.Configuration;
import org.sosy_lab.common.configuration.InvalidConfigurationException;
import org.sosy_lab.common.configuration.Option;
import org.sosy_lab.common.configuration.Options;
import org.sosy_lab.common.time.Timer;
import org.sosy_lab.cpachecker.core.CPAcheckerResult.Result;
import org.sosy_lab.cpachecker.core.interfaces.Statistics;
import org.sosy_lab.cpachecker.core.interfaces.StatisticsProvider;
import org.sosy_lab.cpachecker.core.reachedset.ReachedSet;
import org.sosy_lab.cpachecker.util.predicates.pathformula.PathFormula;
import org.sosy_lab.cpachecker.util.predicates.pathformula.SSAMap;
import org.sosy_lab.cpachecker.util.predicates.smt.BooleanFormulaManagerView.BooleanFormulaTransformationVisitor;
import org.sosy_lab.cpachecker.util.predicates.smt.FormulaManagerView;
import org.sosy_lab.java_smt.api.BooleanFormula;
import org.sosy_lab.java_smt.api.BooleanFormulaManager;
import org.sosy_lab.java_smt.api.Formula;
import org.sosy_lab.java_smt.api.FunctionDeclaration;
import org.sosy_lab.java_smt.api.FunctionDeclarationKind;
import org.sosy_lab.java_smt.api.QuantifiedFormulaManager.Quantifier;
import org.sosy_lab.java_smt.api.SolverException;
import org.sosy_lab.java_smt.api.Tactic;
import org.sosy_lab.java_smt.api.visitors.DefaultBooleanFormulaVisitor;
import org.sosy_lab.java_smt.api.visitors.DefaultFormulaVisitor;
import org.sosy_lab.java_smt.api.visitors.TraversalProcess;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Convert the formula to a quantifier-free form *resembling* CNF (relaxed
 * conjunctive normal
 * form), but without exponential explosion and without introducing extra
 * existential quantifiers.
 */
@Options(prefix = "rcnf")
public class RCNFManager implements StatisticsProvider {

    @Option(description = "Limit on the size of the resulting number of lemmas "
            + "from the explicit expansion", secure = true)
    private int expansionResultSizeLimit = 100;

    @Option(secure = true, description = "Quantifier elimination strategy", toUppercase = true)
    private BOUND_VARS_HANDLING boundVarsHandling = BOUND_VARS_HANDLING.QE_LIGHT_THEN_DROP;

    @Option(secure = true, description = "Expand equality atoms. E.g. 'x=a' gets "
            + "expanded into 'x >= a AND x <= a'. Can lead to stronger weakenings.")
    private boolean expandEquality = false;

    public enum BOUND_VARS_HANDLING {

        /**
         * Run best-effort quantifier elimination and then over-approximate lemmas
         * which still have quantifiers.
         */
        QE_LIGHT_THEN_DROP,

        /**
         * Run proper quantifier elimination.
         */
        QE,

        /**
         * Over-approximate all lemmas with quantifiers.
          */
        DROP
    }

    private FormulaManagerView fmgr = null;
    private BooleanFormulaManager bfmgr = null;
    private final RCNFConversionStatistics statistics;
    private final HashMap<BooleanFormula, Set<BooleanFormula>> conversionCache;

    public RCNFManager(Configuration options) throws InvalidConfigurationException {
        options.inject(this);
        statistics = new RCNFConversionStatistics();
        conversionCache = new HashMap<>();
    }

    /**
     * Existentially quantify dead variables, and apply RCNF conversion.
     *
     * @return Set of lemmas, only have variables with latest SSA index.
     */
    public Set<BooleanFormula> toLemmasInstantiated(PathFormula pf, FormulaManagerView pFmgr)
            throws InterruptedException {
        BooleanFormula transition = pf.getFormula();
        SSAMap ssa = pf.getSsa();
        transition = pFmgr.filterLiterals(transition, input -> !hasDeadUf(input, ssa, pFmgr));
        BooleanFormula quantified = pFmgr.quantifyDeadVariables(transition, ssa);

        return toLemmas(quantified, pFmgr);
    }

    /**
     * Convert an input formula to RCNF form.
     * A formula in RCNF form is a conjunction over quantifier-free formulas.
     *
     * @param input Formula over-approximation with at most one parent-level
     *              existential quantifier.
     *              Contains only latest SSA indexes.
     * @param pFmgr Formula manager which performs the conversion.
     */
    public Set<BooleanFormula> toLemmas(BooleanFormula input, FormulaManagerView pFmgr)
            throws InterruptedException {
        Preconditions.checkNotNull(pFmgr);
        fmgr = pFmgr;
        bfmgr = pFmgr.getBooleanFormulaManager();

        Set<BooleanFormula> out = conversionCache.get(input);
        if (out != null) {
            statistics.conversionCacheHits++;
            return out;
        }

        BooleanFormula result;
        switch (boundVarsHandling) {
        case QE_LIGHT_THEN_DROP:
            try {
                statistics.lightQuantifierElimination.start();
                result = fmgr.applyTactic(input, Tactic.QE_LIGHT);
            } finally {
                statistics.lightQuantifierElimination.stop();
            }
            break;
        case QE:
            try {
                statistics.quantifierElimination.start();
                result = fmgr.getQuantifiedFormulaManager().eliminateQuantifiers(input);
            } catch (SolverException pE) {
                throw new UnsupportedOperationException("Unexpected solver error", pE);
            } finally {
                statistics.quantifierElimination.stop();
            }
            break;
        case DROP:
            result = input;
            break;
        default:
            throw new AssertionError("Unhandled case statement: " + boundVarsHandling);
        }
        BooleanFormula noBoundVars = dropBoundVariables(result);

        try {
            statistics.conversion.start();
            out = convert(noBoundVars);
        } finally {
            statistics.conversion.stop();
        }
        conversionCache.put(input, out);
        return out;
    }

    /**
     * @param input Formula with at most one outer-level existential
     *              quantifier, in NNF.
     */
    private BooleanFormula dropBoundVariables(BooleanFormula input) throws InterruptedException {

        Optional<BooleanFormula> body = fmgr.visit(input, quantifiedBodyExtractor);
        if (body.isPresent()) {
            return fmgr.filterLiterals(body.get(), input1 -> !hasBoundVariables(input1));
        } else {

            // Does not have quantified variables.
            return input;
        }
    }

    private BooleanFormula factorize(BooleanFormula input) {
        return bfmgr.transformRecursively(input, new BooleanFormulaTransformationVisitor(fmgr) {

            /**
             * Flatten AND-.
             */
            @Override
            public BooleanFormula visitAnd(List<BooleanFormula> processed) {
                return bfmgr.and(bfmgr.toConjunctionArgs(bfmgr.and(processed), false));
            }

            /**
             * Factorize OR-.
             */
            @Override
            public BooleanFormula visitOr(List<BooleanFormula> processed) {

                Set<BooleanFormula> intersection = null;
                ArrayList<Set<BooleanFormula>> argsAsConjunctions = new ArrayList<>();
                for (BooleanFormula op : processed) {
                    Set<BooleanFormula> args = bfmgr.toConjunctionArgs(op, false);

                    argsAsConjunctions.add(args);

                    // Factor out the common term.
                    if (intersection == null) {
                        intersection = args;
                    } else {
                        intersection = Sets.intersection(intersection, args);
                    }
                }

                assert intersection != null : "Should not be null for a non-zero number of operands.";

                BooleanFormula common = bfmgr.and(intersection);
                List<BooleanFormula> branches = new ArrayList<>();

                for (Set<BooleanFormula> args : argsAsConjunctions) {
                    Set<BooleanFormula> newEl = Sets.difference(args, intersection);
                    branches.add(bfmgr.and(newEl));
                }

                return bfmgr.and(common, bfmgr.or(branches));
            }
        });
    }

    private BooleanFormula expandClause(final BooleanFormula input) {
        return bfmgr.visit(input, new DefaultBooleanFormulaVisitor<BooleanFormula>() {
            @Override
            protected BooleanFormula visitDefault() {
                return input;
            }

            @Override
            public BooleanFormula visitOr(List<BooleanFormula> operands) {
                long sizeAfterExpansion = 1;

                List<Set<BooleanFormula>> asConjunctions = new ArrayList<>();
                for (BooleanFormula op : operands) {
                    Set<BooleanFormula> out = bfmgr.toConjunctionArgs(op, true);
                    try {
                        sizeAfterExpansion = LongMath.checkedMultiply(sizeAfterExpansion, out.size());
                    } catch (ArithmeticException ex) {
                        sizeAfterExpansion = expansionResultSizeLimit + 1;
                        break;
                    }
                    asConjunctions.add(out);
                }

                if (sizeAfterExpansion <= expansionResultSizeLimit) {
                    // Perform recursive expansion.
                    Set<List<BooleanFormula>> product = Sets.cartesianProduct(asConjunctions);
                    Set<BooleanFormula> newArgs = new HashSet<>(product.size());
                    for (List<BooleanFormula> l : product) {
                        newArgs.add(bfmgr.or(l));
                    }
                    return bfmgr.and(newArgs);
                } else {
                    return bfmgr.or(operands);
                }
            }
        });
    }

    private Set<BooleanFormula> convert(BooleanFormula input) {
        BooleanFormula factorized = factorize(input);
        Set<BooleanFormula> factorizedLemmas = bfmgr.toConjunctionArgs(factorized, true);
        Set<BooleanFormula> out = new HashSet<>();
        for (BooleanFormula lemma : factorizedLemmas) {
            BooleanFormula expanded = expandClause(lemma);
            Set<BooleanFormula> expandedLemmas = bfmgr.toConjunctionArgs(expanded, true);
            for (BooleanFormula l : expandedLemmas) {
                if (expandEquality) {
                    out.addAll(bfmgr.toConjunctionArgs(transformEquality(l), false));
                } else {
                    out.add(l);
                }
            }
        }
        return out;
    }

    /**
     * Transform {@code a = b} to {@code a >= b /\ a <= b}.
     */
    private BooleanFormula transformEquality(BooleanFormula input) {
        return fmgr.visit(input, new DefaultFormulaVisitor<BooleanFormula>() {
            @Override
            protected BooleanFormula visitDefault(Formula f) {
                return (BooleanFormula) f;
            }

            @Override
            public BooleanFormula visitFunction(Formula f, List<Formula> newArgs,
                    FunctionDeclaration<?> functionDeclaration) {
                if (functionDeclaration.getKind() == FunctionDeclarationKind.EQ
                        && fmgr.getFormulaType(newArgs.get(0)).isNumeralType()) {
                    Preconditions.checkState(newArgs.size() == 2);
                    Formula a = newArgs.get(0);
                    Formula b = newArgs.get(1);
                    return bfmgr.and(fmgr.makeGreaterOrEqual(a, b, true), fmgr.makeLessOrEqual(a, b, true));
                } else {
                    return (BooleanFormula) f;
                }
            }
        });
    }

    private boolean hasBoundVariables(BooleanFormula input) {
        final AtomicBoolean hasBound = new AtomicBoolean(false);
        fmgr.visitRecursively(input, new DefaultFormulaVisitor<TraversalProcess>() {
            @Override
            protected TraversalProcess visitDefault(Formula f) {
                return TraversalProcess.CONTINUE;
            }

            @Override
            public TraversalProcess visitBoundVariable(Formula f, int deBruijnIdx) {
                hasBound.set(true);
                return TraversalProcess.ABORT;
            }
        });
        return hasBound.get();
    }

    private final DefaultFormulaVisitor<Optional<BooleanFormula>> quantifiedBodyExtractor = new DefaultFormulaVisitor<Optional<BooleanFormula>>() {
        @Override
        protected Optional<BooleanFormula> visitDefault(Formula f) {
            return Optional.empty();
        }

        @Override
        public Optional<BooleanFormula> visitQuantifier(BooleanFormula f, Quantifier quantifier,
                List<Formula> boundVariables, BooleanFormula body) {
            return Optional.of(body);
        }
    };

    @Override
    public void collectStatistics(Collection<Statistics> statsCollection) {
        statsCollection.add(statistics);
    }

    private static class RCNFConversionStatistics implements Statistics {
        Timer lightQuantifierElimination = new Timer();
        Timer quantifierElimination = new Timer();
        Timer conversion = new Timer();
        int conversionCacheHits = 0;

        @Override
        public void printStatistics(PrintStream out, Result result, ReachedSet reached) {
            printTimer(out, conversion, "RCNF conversion");
            printTimer(out, lightQuantifierElimination, "light quantifier " + "elimination");
            printTimer(out, quantifierElimination, "quantifier elimination");

        }

        @Override
        public String getName() {
            return "RCNF Conversion";
        }

        private void printTimer(PrintStream out, Timer t, String name) {
            out.printf("Time spent in %s: %s (Max: %s), (Avg: %s), (#calls = %s), " + "(#cached = %d) %n", name,
                    t.getSumTime().formatAs(TimeUnit.SECONDS), t.getMaxTime().formatAs(TimeUnit.SECONDS),
                    t.getAvgTime().formatAs(TimeUnit.SECONDS), t.getNumberOfIntervals(), conversionCacheHits);
        }
    }

    private boolean hasDeadUf(BooleanFormula atom, final SSAMap pSSAMap, FormulaManagerView pFmgr) {
        final AtomicBoolean out = new AtomicBoolean(false);
        pFmgr.visitRecursively(atom, new DefaultFormulaVisitor<TraversalProcess>() {
            @Override
            protected TraversalProcess visitDefault(Formula f) {
                return TraversalProcess.CONTINUE;
            }

            @Override
            public TraversalProcess visitFunction(Formula f, List<Formula> args,
                    FunctionDeclaration<?> functionDeclaration) {
                if (functionDeclaration.getKind() == FunctionDeclarationKind.UF) {
                    if (pFmgr.isIntermediate(functionDeclaration.getName(), pSSAMap)) {
                        out.set(true);
                        return TraversalProcess.ABORT;
                    }
                }
                return TraversalProcess.CONTINUE;
            }
        });
        return out.get();
    }
}