/** * Copyright (c) 2012-2016 Marsha Chechik, Alessio Di Sandro, Michalis Famelis, * Rick Salay, Naama Ben-David. * 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. * Naama Ben-David - Concretization highlighting. */ package edu.toronto.cs.se.modelepedia.z3.reasoning; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import org.eclipse.core.runtime.IStatus; import org.eclipse.emf.common.util.BasicEList; import org.eclipse.emf.common.util.EList; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import edu.toronto.cs.se.mavo.LogicElement; import edu.toronto.cs.se.mavo.MAVOCollection; import edu.toronto.cs.se.mavo.MAVODecision; import edu.toronto.cs.se.mavo.MAVOElement; import edu.toronto.cs.se.mavo.MAVORoot; import edu.toronto.cs.se.mmint.MMINTException; import edu.toronto.cs.se.mmint.MIDTypeHierarchy; import edu.toronto.cs.se.mmint.MIDTypeRegistry; import edu.toronto.cs.se.mmint.mavo.library.MAVOUtils; import edu.toronto.cs.se.mmint.mavo.reasoning.IMAVOReasoningEngine; import edu.toronto.cs.se.mmint.mid.ExtendibleElementConstraint; import edu.toronto.cs.se.mmint.mid.MIDLevel; import edu.toronto.cs.se.mmint.mid.Model; import edu.toronto.cs.se.mmint.mid.editor.Diagram; import edu.toronto.cs.se.mmint.mid.operator.Operator; import edu.toronto.cs.se.mmint.mid.operator.OperatorConstraint; import edu.toronto.cs.se.mmint.mid.operator.OperatorInput; import edu.toronto.cs.se.mmint.mid.ui.MIDDialogs; import edu.toronto.cs.se.mmint.mid.utils.MIDRegistry; import edu.toronto.cs.se.modelepedia.z3.Z3IncrementalSolver; import edu.toronto.cs.se.modelepedia.z3.Z3IncrementalSolver.Z3IncrementalBehavior; import edu.toronto.cs.se.modelepedia.z3.Z3Model; import edu.toronto.cs.se.modelepedia.z3.Z3Utils; import edu.toronto.cs.se.modelepedia.z3.mavo.EcoreMAVOToSMTLIB; import edu.toronto.cs.se.modelepedia.z3.mavo.MAVOConcretizationHighlighter; import edu.toronto.cs.se.modelepedia.z3.mavo.MAVORefiner; import edu.toronto.cs.se.modelepedia.z3.mavo.Z3MAVOModelParser; import edu.toronto.cs.se.modelepedia.z3.mavo.Z3MAVOUtils; //TODO MMINT[Z3] Support refinement and highlighting for the complex full-MAVO encoding public class Z3ReasoningEngine implements IMAVOReasoningEngine { public enum MAVOCheckStrategy { DOUBLE_CHECK, SINGLE_CHECK_IF_FALSE, SINGLE_CHECK } public final static @NonNull String ECOREMAVOTOSMTLIB_OPERATOR_URI = "http://se.cs.toronto.edu/modelepedia/Operator_EcoreMAVOToSMTLIB"; private Z3Model z3ConstraintModel; private Z3Model z3NotConstraintModel; public @NonNull Z3MAVOModelParser generateSMTLIBEncoding(@NonNull Model model) throws Exception { if (!(model.getEMFInstanceRoot() instanceof MAVORoot)) { //TODO MMINT[Z3] Support non-mavo models (create acceleo transformation, check constraint once) throw new MMINTException("Model " + model.getName() + " is not a MAVO model"); } EcoreMAVOToSMTLIB ecore2smt = (EcoreMAVOToSMTLIB) MIDTypeRegistry.<Operator>getType(ECOREMAVOTOSMTLIB_OPERATOR_URI); if (ecore2smt == null) { throw new MMINTException("Can't find " + ECOREMAVOTOSMTLIB_OPERATOR_URI + " operator type"); } EcoreMAVOToSMTLIB encoder = ecore2smt; EList<Model> inputModels = new BasicEList<>(); inputModels.add(model); EList<OperatorInput> inputs = null; for (EcoreMAVOToSMTLIB ecore2smtCustom : MIDTypeHierarchy.getSubtypes(ecore2smt)) { // check for custom encoders //TODO MMINT[Z3] Sort by subtype level to allow most specific to run inputs = ecore2smtCustom.checkAllowedInputs(inputModels); if (inputs != null) { encoder = ecore2smtCustom; break; } } if (inputs == null) { // use default encoder inputs = ecore2smt.checkAllowedInputs(inputModels); } encoder = (EcoreMAVOToSMTLIB) encoder.startInstance(inputs, null, new BasicEList<>(), new HashMap<>(), null); encoder.cleanup(); return encoder.getZ3MAVOModelParser(); } public @NonNull MAVOTruthValue checkMAVOConstraintWithSolver(@NonNull Z3IncrementalSolver z3IncSolver, @NonNull String smtMacros, @NonNull String smtConstraint, MAVOCheckStrategy checkStrategy) { Z3Model z3Model = z3IncSolver.checkSatAndGetModel(smtMacros + " " + Z3Utils.assertion(smtConstraint), Z3IncrementalBehavior.POP); boolean constraintTruthValue = z3Model.getZ3Result().isSAT(); z3ConstraintModel = (constraintTruthValue) ? z3Model : null; switch (checkStrategy) { case SINGLE_CHECK: // skips the next check because the model has no mavo annotation z3NotConstraintModel = null; return MAVOTruthValue.toMAVOTruthValue(constraintTruthValue); case SINGLE_CHECK_IF_FALSE: // skips the next check because we don't care about losing a possible MAVOTruthValue.ERROR status if (!constraintTruthValue) { z3NotConstraintModel = null; return MAVOTruthValue.toMAVOTruthValue(constraintTruthValue); } case DOUBLE_CHECK: // just continue } z3Model = z3IncSolver.checkSatAndGetModel(smtMacros + " " + Z3Utils.assertion(Z3Utils.not(smtConstraint)), Z3IncrementalBehavior.POP); boolean notConstraintTruthValue = z3Model.getZ3Result().isSAT(); z3NotConstraintModel = (notConstraintTruthValue) ? z3Model : null; return MAVOTruthValue.toMAVOTruthValue(constraintTruthValue, notConstraintTruthValue); } public @NonNull MAVOTruthValue checkMAVOConstraint(@NonNull String smtEncoding, @NonNull String smtMacros, @NonNull String smtConstraint, MAVOCheckStrategy checkStrategy) { Z3IncrementalSolver z3IncSolver = new Z3IncrementalSolver(); z3IncSolver.firstCheckSatAndGetModel(smtEncoding); return checkMAVOConstraintWithSolver(z3IncSolver, smtMacros, smtConstraint, checkStrategy); } @Override public @NonNull MAVOTruthValue checkMAVOModelConstraint(@NonNull Model model, ExtendibleElementConstraint constraint, @NonNull MIDLevel constraintLevel) { Z3MAVOModelParser z3ModelParser; try { z3ModelParser = generateSMTLIBEncoding(model); } catch (Exception e) { MMINTException.print(IStatus.ERROR, "Can't generate SMTLIB encoding, evaluating to false", e); return MAVOTruthValue.FALSE; } MAVOCheckStrategy checkStrategy = (z3ModelParser.isAnnotated()) ? MAVOCheckStrategy.DOUBLE_CHECK : MAVOCheckStrategy.SINGLE_CHECK; MAVOTruthValue constraintTruthValue = checkMAVOConstraint(z3ModelParser.getSMTLIBEncoding(), z3ModelParser.getSMTLIBMacros(), constraint.getImplementation(), checkStrategy); // show example if: maybe, has a diagram, user accepts if (constraintTruthValue != MAVOTruthValue.MAYBE) { return constraintTruthValue; } Diagram modelDiagram = MIDRegistry.getModelDiagram(model); if (modelDiagram == null) { return constraintTruthValue; } //TODO MMINT[MAVO] This needs to be skipped when in a cycle (e.g. looking for all valid super/sub types) if (!MIDDialogs.getBooleanInput( "Concretization highlighter", "The result is MAYBE, do you want to see a concretization that violates the constraint?" )) { return constraintTruthValue; } // show example MAVOConcretizationHighlighter highlighter; try { highlighter = new MAVOConcretizationHighlighter(); highlighter.highlightMAVOCounterExample(modelDiagram, z3ModelParser.getZ3MAVOModelObjects(z3NotConstraintModel)); } catch (Exception e) { MMINTException.print(IStatus.WARNING, "Can't highlight concretization, skipping it", e); } return constraintTruthValue; } @Override public boolean checkModelConstraint(@NonNull Model model, ExtendibleElementConstraint constraint, @NonNull MIDLevel constraintLevel) { return checkMAVOModelConstraint(model, constraint, constraintLevel).toBoolean(); } @Override public boolean checkOperatorInputConstraint(@NonNull Map<String, Model> inputsByName, @NonNull OperatorConstraint constraint) { return true; } @Override public boolean checkModelConstraintConsistency(@NonNull Model modelType, String constraint) { return true; } public Set<String> allSATWithSolver(@NonNull Z3IncrementalSolver z3IncSolver, @NonNull Z3MAVOModelParser z3ModelParser, @NonNull Z3Model z3Model, @NonNull Set<MAVOElement> mavoModelObjs, MAVORoot rootMavoModelObj) { Set<String> smtConcretizations = new HashSet<>(); do { String smtConcretization = ""; Map<String, Set<String>> z3ModelObjs = z3ModelParser.getZ3MAVOModelObjects(z3Model); for (MAVOElement mavoModelObj : mavoModelObjs) { String formulaVar = mavoModelObj.getFormulaVariable(); int counterMS = 0; Set<String> mergedV = null; for (Set<String> formulaVars : z3ModelObjs.values()) { //TODO MMINT[Z3] Understand why we don't break here, is it only to increase counterMS in the S case? if (formulaVars.contains(formulaVar)) { counterMS++; mergedV = formulaVars; } } try { boolean isNegation; if (mavoModelObj.isMay()) { isNegation = (counterMS == 0); smtConcretization += Z3MAVOUtils.getSMTLIBMayModelObjectConstraint(mavoModelObj, z3ModelParser.isMayOnly(), isNegation); } if (mavoModelObj.isSet() && counterMS > 0) { isNegation = (counterMS > 1); smtConcretization += Z3MAVOUtils.getSMTLIBSetModelObjectConstraint(mavoModelObj, isNegation); } if (mavoModelObj.isVar() && counterMS > 0) { isNegation = (mergedV.size() > 1); if (!isNegation) { mergedV = MAVOUtils.getMergeableFormulaVars(rootMavoModelObj, mavoModelObj); if (mergedV.size() == 0) { continue; } } smtConcretization += Z3MAVOUtils.getSMTLIBVarModelObjectConstraint(mavoModelObj, mergedV, isNegation); } } catch (MMINTException e) { MMINTException.print(IStatus.WARNING, "Can't generate SMTLIB encoding for may model object " + formulaVar + ", skipping it", e); continue; } } smtConcretization = Z3Utils.and(smtConcretization); smtConcretizations.add(smtConcretization); z3Model = z3IncSolver.checkSatAndGetModel(Z3Utils.assertion(Z3Utils.not(smtConcretization)), Z3IncrementalBehavior.NORMAL); } while (z3Model.getZ3Result().isSAT()); return smtConcretizations; } public Set<String> allSAT(@NonNull String smtEncoding, @NonNull Z3MAVOModelParser z3ModelParser, @NonNull Set<MAVOElement> mavoModelObjs, @NonNull MAVORoot rootMavoModelObj) { Z3IncrementalSolver z3IncSolver = new Z3IncrementalSolver(); Z3Model z3Model = z3IncSolver.firstCheckSatAndGetModel(smtEncoding); if (!z3Model.getZ3Result().isSAT()) { return new HashSet<>(); } return allSATWithSolver(z3IncSolver, z3ModelParser, z3Model, mavoModelObjs, rootMavoModelObj); } public Set<String> allSAT(@NonNull Model model) { try { Z3MAVOModelParser z3ModelParser = generateSMTLIBEncoding(model); MAVORoot rootMavoModelObj = (MAVORoot) model.getEMFInstanceRoot(); return allSAT(z3ModelParser.getSMTLIBEncoding(), z3ModelParser, new HashSet<>(MAVOUtils.getAnnotatedMAVOModelObjects(rootMavoModelObj).values()), rootMavoModelObj); } catch (Exception e) { MMINTException.print(IStatus.ERROR, "Can't generate SMTLIB encoding, returning 0 concretizations", e); return new HashSet<>(); } } private void optimizeMayBackbone(@NonNull Z3MAVOModelParser z3ModelParser, @NonNull Z3Model z3Model, @NonNull Set<MAVOElement> mayModelObjs, @NonNull Set<String> baselineFormulaVars, @NonNull Map<String, MAVOTruthValue> backboneTruthValues) { Set<String> currentFormulaVars = z3ModelParser.getZ3MAVOModelObjects(z3Model).values().stream() .flatMap(Set::stream) .collect(Collectors.toSet()); mayModelObjs.stream() .filter(mayModelObj -> !backboneTruthValues.containsKey(mayModelObj.getFormulaVariable())) .filter(mayModelObj -> baselineFormulaVars.contains(mayModelObj.getFormulaVariable()) != currentFormulaVars.contains(mayModelObj.getFormulaVariable())) .forEach(mayModelObj -> backboneTruthValues.put(mayModelObj.getFormulaVariable(), MAVOTruthValue.MAYBE)); } public @NonNull Map<String, MAVOTruthValue> mayBackboneWithSolver(@NonNull Z3IncrementalSolver z3IncSolver, @Nullable Z3MAVOModelParser z3ModelParser, @Nullable Z3Model z3Model, @NonNull Set<MAVOElement> mayModelObjs) { Set<String> baselineFormulaVars = (z3ModelParser == null) ? null : z3ModelParser.getZ3MAVOModelObjects(z3Model).values().stream() .flatMap(Set::stream) .collect(Collectors.toSet()); Map<String, MAVOTruthValue> backboneTruthValues = new HashMap<>(); // for each may element, assert it and its negation for (MAVOElement mayModelObj : mayModelObjs) { String formulaVar = mayModelObj.getFormulaVariable(); if (backboneTruthValues.containsKey(formulaVar)) { // optimized continue; } String smtConstraint; try { smtConstraint = Z3MAVOUtils.getSMTLIBMayModelObjectConstraint(mayModelObj, true, false); } catch (MMINTException e) { MMINTException.print(IStatus.WARNING, "Can't generate SMTLIB encoding for may model object " + formulaVar + ", skipping it", e); continue; } MAVOTruthValue backboneTruthValue = this.checkMAVOConstraintWithSolver(z3IncSolver, "", smtConstraint, MAVOCheckStrategy.SINGLE_CHECK_IF_FALSE); backboneTruthValues.put(formulaVar, backboneTruthValue); if (backboneTruthValue != MAVOTruthValue.FALSE && z3ModelParser != null) { // optimize optimizeMayBackbone(z3ModelParser, z3ConstraintModel, mayModelObjs, baselineFormulaVars, backboneTruthValues); if (backboneTruthValue == MAVOTruthValue.MAYBE) { optimizeMayBackbone(z3ModelParser, z3NotConstraintModel, mayModelObjs, baselineFormulaVars, backboneTruthValues); } } } return backboneTruthValues; } public @NonNull Map<String, MAVOTruthValue> mayBackbone(@NonNull String smtEncoding, @Nullable Z3MAVOModelParser z3ModelParser, @NonNull Set<MAVOElement> mayModelObjs) { Z3IncrementalSolver z3IncSolver = new Z3IncrementalSolver(); Z3Model z3Model = z3IncSolver.firstCheckSatAndGetModel(smtEncoding); if (!z3Model.getZ3Result().isSAT()) { return new HashMap<>(); } return mayBackboneWithSolver(z3IncSolver, z3ModelParser, z3Model, mayModelObjs); } public @NonNull Map<String, MAVOTruthValue> mayBackbone(@NonNull Model model) { try { Z3MAVOModelParser z3ModelParser = generateSMTLIBEncoding(model); if (!z3ModelParser.isMayOnly()) { throw new MMINTException("The backbone is for may models only"); } MAVORoot rootMavoModelObj = (MAVORoot) model.getEMFInstanceRoot(); return mayBackbone(z3ModelParser.getSMTLIBEncoding(), z3ModelParser, new HashSet<>(MAVOUtils.getAnnotatedMAVOModelObjects(rootMavoModelObj).values())); } catch (Exception e) { MMINTException.print(IStatus.ERROR, "Can't generate SMTLIB encoding, returning 0 backbone elements", e); return new HashMap<>(); } } @Override public @Nullable Model refineModelByConstraint(@NonNull Model model) { Z3MAVOModelParser z3ModelParser; try { z3ModelParser = generateSMTLIBEncoding(model); } catch (Exception e) { MMINTException.print(IStatus.ERROR, "Can't generate SMTLIB encoding, aborting refinement", e); return null; } MAVOCheckStrategy checkStrategy = (z3ModelParser.isAnnotated()) ? MAVOCheckStrategy.DOUBLE_CHECK : MAVOCheckStrategy.SINGLE_CHECK; MAVOTruthValue constraintTruthValue = checkMAVOConstraint(z3ModelParser.getSMTLIBEncoding(), z3ModelParser.getSMTLIBMacros(), model.getConstraint().getImplementation(), checkStrategy); // refine if: maybe if (constraintTruthValue != MAVOTruthValue.MAYBE) { MMINTException.print(IStatus.ERROR, "The constraint is not MAYBE, aborting refinement", null); return null; } // refine Diagram modelDiagram = MIDRegistry.getModelDiagram(model); String smtEncoding = z3ModelParser.getSMTLIBEncoding() + Z3Utils.assertion(model.getConstraint().getImplementation()); MAVORefiner refiner = new MAVORefiner(this); try { return refiner.refine(model, modelDiagram, null, smtEncoding, z3ModelParser); } catch (Exception e) { MMINTException.print(IStatus.ERROR, "Can't refine the model by constraint, aborting (some incomplete result could appear in your instance MID)", e); return null; } } private @NonNull String generateMayRefinementSMTLIBEncoding(@NonNull Model model, @NonNull List<? extends LogicElement> mayLogicElems) throws Exception { // base encoding Z3MAVOModelParser z3ModelParser = generateSMTLIBEncoding(model); String smtEncoding = z3ModelParser.getSMTLIBEncoding(); // constraints if (model.getConstraint() != null) { smtEncoding += Z3Utils.assertion(model.getConstraint().getImplementation()); } String smtMayLogicElem = ""; for (LogicElement mayLogicElem : mayLogicElems) { if (mayLogicElem instanceof MAVOCollection) { smtMayLogicElem = mayLogicElem.getFormulaVariable(); } else if (mayLogicElem instanceof MAVOElement) { smtMayLogicElem = Z3MAVOUtils.getSMTLIBMayModelObjectConstraint((MAVOElement) mayLogicElem, z3ModelParser.isMayOnly(), false); } smtEncoding += Z3Utils.assertion(smtMayLogicElem); } return smtEncoding; } @Override public @Nullable Model refineModelByMayAlternative(@NonNull Model model, @NonNull MAVOCollection mayAlternative) { String smtEncoding; try { List<MAVOCollection> mayLogicElems = new ArrayList<>(); mayLogicElems.add(mayAlternative); smtEncoding = generateMayRefinementSMTLIBEncoding(model, mayLogicElems); } catch (Exception e) { MMINTException.print(IStatus.ERROR, "Can't generate SMTLIB encoding, aborting refinement", e); return null; } // refine Diagram modelDiagram = MIDRegistry.getModelDiagram(model); MAVORefiner refiner = new MAVORefiner(this); try { return refiner.refine(model, modelDiagram, mayAlternative, smtEncoding, null); } catch (Exception e) { MMINTException.print(IStatus.ERROR, "Can't refine the model by may alternative, aborting (some incomplete result could appear in your instance MID)", e); return null; } } @Override public @Nullable Model refineModelByMayModelObjects(@NonNull Model model, @NonNull List<MAVOElement> mayModelObjs) { String smtEncoding; try { smtEncoding = generateMayRefinementSMTLIBEncoding(model, mayModelObjs); } catch (Exception e) { MMINTException.print(IStatus.ERROR, "Can't generate SMTLIB encoding, aborting refinement", e); return null; } // refine Diagram modelDiagram = MIDRegistry.getModelDiagram(model); MAVORefiner refiner = new MAVORefiner(this); try { return refiner.refine(model, modelDiagram, null, smtEncoding, null); } catch (Exception e) { MMINTException.print(IStatus.ERROR, "Can't refine the model by may model objects, aborting (some incomplete result could appear in your instance MID)", e); return null; } } @Override public @Nullable Model refineModelByVarDomain(@NonNull Model model, @NonNull MAVOCollection varDomain, @NonNull MAVOElement mergedModelObj, @NonNull List<MAVOElement> varModelObjs) { //TODO MMINT[VAR-MMINT] Need to come up with a different approach than the may constraint-based: first merge in the model and remove Vs, then run the solver to cascade //TODO MMINT[MU-MMINT] Migrate var approach to may too (first remove elements and Ms in the model, then run the solver to cascade) return null; } @Override public void highlightMAVODecision(@NonNull Diagram modelDiagram, @NonNull MAVODecision mavoDecision) { MAVOConcretizationHighlighter highlighter = new MAVOConcretizationHighlighter(); try { highlighter.highlight(modelDiagram, mavoDecision); } catch (Exception e) { MMINTException.print(IStatus.WARNING, "Can't highlight MAVO decision, skipping it", e); } } @Override public void highlightMAVOCollection(@NonNull Diagram modelDiagram, @NonNull MAVOCollection mavoCollection) { MAVOConcretizationHighlighter highlighter = new MAVOConcretizationHighlighter(); try { highlighter.highlight(modelDiagram, mavoCollection); } catch (Exception e) { MMINTException.print(IStatus.WARNING, "Can't highlight MAVO collection, skipping it", e); } } @Override public void highlightMAVOElement(@NonNull Diagram modelDiagram, @NonNull MAVOElement mavoModelObj) { MAVOConcretizationHighlighter highlighter = new MAVOConcretizationHighlighter(); try { highlighter.highlight(modelDiagram, mavoModelObj); } catch (Exception e) { MMINTException.print(IStatus.WARNING, "Can't highlight MAVO element, skipping it", e); } } }