/* * Copyright (C) 2009-2012 University of Freiburg * * This file is part of SMTInterpol. * * SMTInterpol is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * SMTInterpol is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with SMTInterpol. If not, see <http://www.gnu.org/licenses/>. */ package de.uni_freiburg.informatik.ultimate.smtinterpol.convert; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import de.uni_freiburg.informatik.ultimate.logic.AnnotatedTerm; import de.uni_freiburg.informatik.ultimate.logic.ApplicationTerm; import de.uni_freiburg.informatik.ultimate.logic.FunctionSymbol; import de.uni_freiburg.informatik.ultimate.logic.Term; import de.uni_freiburg.informatik.ultimate.logic.TermVariable; import de.uni_freiburg.informatik.ultimate.logic.Theory; import de.uni_freiburg.informatik.ultimate.smtinterpol.Config; import de.uni_freiburg.informatik.ultimate.smtinterpol.LogProxy; import de.uni_freiburg.informatik.ultimate.smtinterpol.util.CollectionsHelper; public class TriggerCandidateMap { private final LogProxy mLogger; private final HashMap<FunctionSymbol, List<ApplicationTerm>> mFuncs; private final Set<ApplicationTerm> mUnitCandidates; private final Theory mTheory; private Set<TermVariable> mVars; public TriggerCandidateMap(LogProxy logger,Theory theory, Set<TermVariable> vars) { mLogger = logger; mFuncs = new HashMap<FunctionSymbol,List<ApplicationTerm>>(); // We need insertion order iterators since we want to extract minimal // candidates first. Inserting child before parent guarantees this. mUnitCandidates = new LinkedHashSet<ApplicationTerm>(); mTheory = theory; mVars = vars; } public void insert(Term term) { assert term.getSort() == mTheory.getBooleanSort() : "Inserting non-boolean term"; // Remove let terms and lift term-ites... final InferencePreparation ip = new InferencePreparation(mTheory,mVars); recinsert(ip.prepare(term)); } // return true iff term contains an internal subterm private boolean recinsert(Term term) { if (term.getFreeVars() == null || term.getFreeVars().length == 00) { return false; } if (term instanceof AnnotatedTerm) { term = ((AnnotatedTerm)term).getSubterm(); } if (term instanceof ApplicationTerm) { final ApplicationTerm fat = (ApplicationTerm)term; final Term[] params = fat.getParameters(); boolean internal = false; for (final Term param : params) { internal |= recinsert(param); } if (internal) { return true; } final FunctionSymbol fs = fat.getFunction(); if (isFunctionAllowedInTrigger(fs)) { List<ApplicationTerm> fapps = mFuncs.get(fs); if (fapps == null) { fapps = new ArrayList<ApplicationTerm>(); mFuncs.put(fs, fapps); } if (mVars.containsAll(Arrays.asList( term.getFreeVars()))) { mUnitCandidates.add(fat); } fapps.add(fat); return false; } return true; } return false; } /** * Can this function contribute to a trigger. We allow all non-internal * symbols, equality and the select and store array internals. * @param fs Function symbol to check. * @return <code>true</code> if and only if <code>fs</code> can contribute. */ private final boolean isFunctionAllowedInTrigger(FunctionSymbol fs) { return !fs.isIntern() || fs.getName().equals("=") || fs.getName().equals("select") || fs.getName().equals("store"); } public boolean isLoopingPattern(ApplicationTerm candidate) { final List<ApplicationTerm> fapps = mFuncs.get(candidate.getFunction()); assert (fapps != null) : "Pattern candidate does not occur in the sub"; for (final ApplicationTerm at : fapps) { if (at == candidate) { continue; } if (hasVarMatchError(candidate,at)) { mLogger.debug("Pattern candidate %s dropped. It is looping against %s...", candidate,at); return true; } } return false; } private boolean hasVarMatchError(Term candidate, Term fat) { if (candidate instanceof TermVariable && !(fat instanceof TermVariable)) { return fat.getFreeVars() != null && Arrays.asList(fat.getFreeVars()).contains(candidate); } if (candidate instanceof ApplicationTerm && fat instanceof ApplicationTerm) { final ApplicationTerm capp = (ApplicationTerm)candidate; final ApplicationTerm fapp = (ApplicationTerm)fat; if (capp.getFunction() == fapp.getFunction()) { final Term[] cparams = capp.getParameters(); final Term[] fparams = fapp.getParameters(); assert(cparams.length == fparams.length); for (int i = 0; i < cparams.length; ++i) { if (hasVarMatchError(cparams[i], fparams[i])) { return true; } } } } return false; } /** * Infer <strong>one</strong> multi trigger. * @return Multi trigger or <code>null</code> if none could be inferred. */ public Term[] getMultiTrigger() { // NOPMD int trigsize = 2; while (trigsize <= mVars.size()) { final Set<Term> trigs = getMultiTrigger(trigsize); if (trigs != null) { assert(trigs.size() == trigsize); return trigs.toArray(new Term[trigs.size()]); } ++trigsize; } return null; } private Set<Term> getMultiTrigger(int trigsize) { final HashSet<TermVariable> uncovered = new HashSet<TermVariable>(mVars.size(),1); uncovered.addAll(mVars); final HashSet<Term> candidate = new HashSet<Term>(trigsize,1); for (final List<ApplicationTerm> fapps : mFuncs.values()) { for (final ApplicationTerm fat : fapps) { candidate.add(fat); assert(fat.getFreeVars() != null && fat.getFreeVars().length != 0); final Collection<TermVariable> termVars = Arrays.asList(fat.getFreeVars()); uncovered.removeAll(termVars); if (completeMultiTrigger(candidate,uncovered,trigsize - 1)) { return candidate; } uncovered.addAll(termVars); candidate.remove(fat); } } return null; } private boolean completeMultiTrigger(HashSet<Term> candidate, HashSet<TermVariable> uncovered,int trigsize) { if (trigsize == 0) { return uncovered.isEmpty(); } for (final List<ApplicationTerm> fapps : mFuncs.values()) { for (final ApplicationTerm fat : fapps) { final Collection<TermVariable> tvs = Arrays.asList(fat.getFreeVars()); if (!CollectionsHelper.containsAny(uncovered,tvs)) { // Would not reduce the problem... continue; } if (!candidate.add(fat)) { continue; } uncovered.removeAll(tvs); if (completeMultiTrigger(candidate,uncovered,trigsize - 1)) { return true; } uncovered.addAll(tvs); candidate.remove(fat); } } return false; } /** * Infer <strong>all possible</strong> unit-triggers. * * Looping pattern are blocked iff * ConvertFormula.FEATURE_BLOCK_LOOPING_PATTERN is true. * @return All possible unit triggers. */ public Term[] getAllUnitTriggers() { final HashSet<Term> unittrigs = new HashSet<Term>(); final HashSet<Term> considered = new HashSet<Term>(); candidates: for (final ApplicationTerm c : mUnitCandidates) { for (final Term t : c.getParameters()) { // All term with at least on considered children are considered as well... if (considered.contains(t)) { considered.add(c); continue candidates; } } final TermVariable[] fvars = c.getFreeVars(); final HashSet<TermVariable> vars = new HashSet<TermVariable>(fvars.length,1); for (final TermVariable tv : fvars) { vars.add(tv); } if (vars.equals(mVars) && (!Config.FEATURE_BLOCK_LOOPING_PATTERN || !isLoopingPattern(c))) { // Consider this candidate unittrigs.add(c); considered.add(c); } } return unittrigs.isEmpty() ? null : unittrigs.toArray(new Term[unittrigs.size()]); } /** * Reinitialize this map for inference for a different set of variables. * Note that you have to reinsert all terms. * @param vars Variable to infer pattern for. */ public void reinit(Set<TermVariable> vars) { mFuncs.clear(); mUnitCandidates.clear(); mVars = vars; } }