diff --git a/subprojects/common/analysis/src/main/java/hu/bme/mit/theta/analysis/algorithm/ic3/Frame.java b/subprojects/common/analysis/src/main/java/hu/bme/mit/theta/analysis/algorithm/ic3/Frame.java new file mode 100644 index 0000000000..4524e5d57a --- /dev/null +++ b/subprojects/common/analysis/src/main/java/hu/bme/mit/theta/analysis/algorithm/ic3/Frame.java @@ -0,0 +1,74 @@ +package hu.bme.mit.theta.analysis.algorithm.ic3; + +import hu.bme.mit.theta.analysis.algorithm.bounded.MonolithicExpr; +import hu.bme.mit.theta.core.model.MutableValuation; +import hu.bme.mit.theta.core.model.Valuation; +import hu.bme.mit.theta.core.type.Expr; +import hu.bme.mit.theta.core.type.booltype.BoolType; +import hu.bme.mit.theta.core.utils.PathUtils; +import hu.bme.mit.theta.solver.SolverStatus; +import hu.bme.mit.theta.solver.utils.WithPushPop; + +import java.util.*; + +import hu.bme.mit.theta.solver.UCSolver; + +import static hu.bme.mit.theta.core.type.booltype.SmartBoolExprs.*; +import static hu.bme.mit.theta.core.utils.ExprUtils.getConjuncts; + + +public class Frame { + private final Frame parent; + private final Set> exprs; + + private final UCSolver solver; + private final MonolithicExpr monolithicExpr; + + Frame(final Frame parent, UCSolver solver, MonolithicExpr monolithicExpr) { + this.parent = parent; + this.solver = solver; + this.monolithicExpr=monolithicExpr; + exprs = new HashSet<>(); + } + + public void refine(Expr expression) { + Collection> col = getConjuncts(expression); + for (Expr e : col) { + exprs.add(e); + } + } + + public Set> getExprs() { + return exprs; + } + + public Collection> check(Expr target) { + try (var wpp = new WithPushPop(solver)) { + solver.track(PathUtils.unfold(target, 0)); + for (Expr ex : exprs) { + solver.track(PathUtils.unfold(ex, 0)); + } + SolverStatus status = solver.check(); + + if (status.isSat()) { + final Valuation model = solver.getModel(); + final MutableValuation filteredModel = new MutableValuation(); + monolithicExpr.getVars().stream().map(varDecl -> varDecl.getConstDecl(0)).filter(model.toMap()::containsKey).forEach(decl -> filteredModel.put(decl, model.eval(decl).get())); + return getConjuncts(PathUtils.foldin(filteredModel.toExpr(), 0)); + } else { + return null; + } + } + } + + public boolean equalsParent() { + if (this.parent.parent == null) { + return false; + } + try (var wpp = new WithPushPop(solver)) { + solver.track(PathUtils.unfold(Not(And(parent.getExprs())), 0)); + solver.track(PathUtils.unfold(And(exprs), 0)); + return solver.check().isUnsat(); + } + } +} diff --git a/subprojects/common/analysis/src/main/java/hu/bme/mit/theta/analysis/algorithm/ic3/Ic3Checker.java b/subprojects/common/analysis/src/main/java/hu/bme/mit/theta/analysis/algorithm/ic3/Ic3Checker.java new file mode 100644 index 0000000000..d993d20680 --- /dev/null +++ b/subprojects/common/analysis/src/main/java/hu/bme/mit/theta/analysis/algorithm/ic3/Ic3Checker.java @@ -0,0 +1,287 @@ +package hu.bme.mit.theta.analysis.algorithm.ic3; + +import hu.bme.mit.theta.analysis.Trace; +import hu.bme.mit.theta.analysis.algorithm.EmptyProof; +import hu.bme.mit.theta.analysis.algorithm.SafetyChecker; +import hu.bme.mit.theta.analysis.algorithm.SafetyResult; +import hu.bme.mit.theta.analysis.algorithm.bounded.MonolithicExpr; +import hu.bme.mit.theta.analysis.expr.ExprAction; +import hu.bme.mit.theta.analysis.expr.ExprState; +import hu.bme.mit.theta.analysis.unit.UnitPrec; +import hu.bme.mit.theta.core.model.MutableValuation; +import hu.bme.mit.theta.core.model.Valuation; +import hu.bme.mit.theta.core.type.Expr; +import hu.bme.mit.theta.core.type.LitExpr; +import hu.bme.mit.theta.core.type.anytype.RefExpr; +import hu.bme.mit.theta.core.type.booltype.BoolLitExpr; +import hu.bme.mit.theta.core.type.booltype.BoolType; +import hu.bme.mit.theta.core.utils.PathUtils; +import hu.bme.mit.theta.solver.SolverFactory; +import hu.bme.mit.theta.solver.UCSolver; +import hu.bme.mit.theta.solver.utils.WithPushPop; + +import java.util.*; +import java.util.function.BiFunction; +import java.util.function.Function; + +import static hu.bme.mit.theta.core.type.booltype.SmartBoolExprs.*; +import static hu.bme.mit.theta.core.utils.ExprUtils.getConjuncts; + + +public class Ic3Checker implements SafetyChecker, UnitPrec> { + private final MonolithicExpr monolithicExpr; + private final List frames; + private final UCSolver solver; + private final Function valToState; + private final BiFunction biValToAction; + private final boolean formerFramesOpt; + private final boolean unSatOpt; + private final boolean notBOpt; + private final boolean propagateOpt; + private final boolean filterOpt; + private int currentFrameNumber; + private final boolean forwardTrace; + private final boolean propertyOpt; + + public Ic3Checker(MonolithicExpr monolithicExpr, boolean forwardTrace, SolverFactory solverFactory, Function valToState, BiFunction biValToAction) { + this(monolithicExpr, forwardTrace, solverFactory, valToState, biValToAction, true, true, true, true, true, false); + } + + public Ic3Checker(MonolithicExpr monolithicExpr, boolean forwardTrace, SolverFactory solverFactory, Function valToState, BiFunction biValToAction, boolean formerFramesOpt, boolean unSatOpt, boolean notBOpt, boolean propagateOpt, boolean filterOpt, boolean propertyOpt) { + this.monolithicExpr = monolithicExpr; + this.valToState = valToState; + this.biValToAction = biValToAction; + this.formerFramesOpt = formerFramesOpt; + this.unSatOpt = unSatOpt; + this.notBOpt = notBOpt; + this.propagateOpt = propagateOpt; + this.filterOpt = filterOpt; + this.forwardTrace = forwardTrace; + this.propertyOpt = propertyOpt; + frames = new ArrayList<>(); + solver = solverFactory.createUCSolver(); + frames.add(new Frame(null, solver, monolithicExpr)); + frames.get(0).refine(monolithicExpr.getInitExpr()); + currentFrameNumber = 0; + } + + + @Override + public SafetyResult> check(UnitPrec prec) { + //check if init violates prop + var firstTrace = checkFirst(); + if (firstTrace != null) { + return SafetyResult.unsafe(firstTrace, EmptyProof.getInstance()); + } + while (true) { + final Collection> counterExample = checkCurrentFrame(Not(monolithicExpr.getPropExpr())); + if (counterExample != null) { + var proofObligationsList = tryBlock(new ProofObligation(new HashSet<>(counterExample), currentFrameNumber)); + if (proofObligationsList != null) { + var trace = traceMaker(proofObligationsList); + return SafetyResult.unsafe(trace, EmptyProof.getInstance()); + } + } else { + if (propagate()) { + return SafetyResult.safe(EmptyProof.getInstance()); + } + } + } + } + + LinkedList tryBlock(ProofObligation mainProofObligation) { + final LinkedList proofObligationsQueue = new LinkedList(); + proofObligationsQueue.add(mainProofObligation); + while (!proofObligationsQueue.isEmpty()) { + final ProofObligation proofObligation = proofObligationsQueue.getLast(); + + if (proofObligation.getTime() == 0) { + return proofObligationsQueue; + } + + final Collection> b; + final Collection> unSatCore; + try (var wpp = new WithPushPop(solver)) { + + frames.get(proofObligation.getTime() - 1).getExprs().forEach(ex -> solver.track(PathUtils.unfold(ex, 0))); + if (notBOpt) { + solver.track(PathUtils.unfold(Not(And(proofObligation.getExpressions())), 0)); + } + if (proofObligation.getTime() > 2 && formerFramesOpt) { //lehet, hogy 1, vagy 2?? + solver.track(PathUtils.unfold(Not(And(frames.get(proofObligation.getTime() - 2).getExprs())), monolithicExpr.getTransOffsetIndex())); //2 vel korábbi frame-ban levő dolgok + } + + getConjuncts(monolithicExpr.getTransExpr()).forEach(ex -> solver.track(PathUtils.unfold(ex, 0))); + proofObligation.getExpressions().forEach(ex -> solver.track(PathUtils.unfold(ex, monolithicExpr.getTransOffsetIndex()))); + + if (solver.check().isSat()) { + final Valuation model = solver.getModel(); + + final MutableValuation filteredModel = new MutableValuation(); + monolithicExpr.getVars().stream().map(varDecl -> varDecl.getConstDecl(0)).filter(model.toMap()::containsKey).forEach(decl -> filteredModel.put(decl, model.eval(decl).get())); + if (filterOpt) { + var vars = new HashSet<>(filteredModel.toMap().keySet()); + for (var var : vars) { + if (!(var.getType() instanceof BoolType)) { + continue; + } + var origValue = model.eval(var).get(); + var negatedValue = BoolLitExpr.of(!((BoolLitExpr) origValue).getValue()); + filteredModel.put(var, negatedValue); + try (var wpp2 = new WithPushPop(solver)) { + solver.track(PathUtils.unfold(filteredModel.toExpr(), 0)); + if (solver.check().isSat()) { + filteredModel.remove(var); + } else { + filteredModel.put(var, origValue); + } + } + } + } + b = getConjuncts(PathUtils.foldin(PathUtils.extractValuation(filteredModel, 0).toExpr(), 0)); + unSatCore = null; + } else { + b = null; + unSatCore = solver.getUnsatCore(); + } + } + if (b == null) { + + final Collection> newCore = new ArrayList>(); + newCore.addAll(proofObligation.getExpressions()); + if (unSatOpt) { + for (Expr i : proofObligation.getExpressions()) { + if (!unSatCore.contains(PathUtils.unfold(i, monolithicExpr.getTransOffsetIndex()))) { + newCore.remove(i); + final boolean isSat; + try (var wpp = new WithPushPop(solver)) { + for (Expr solverex : newCore) { + solver.track(PathUtils.unfold(solverex, 0)); + } + solver.track(PathUtils.unfold(monolithicExpr.getInitExpr(), 0)); + isSat = solver.check().isSat(); + } + if (isSat) { + newCore.add(i); + } + } + } + } + for (int i = 1; i <= proofObligation.getTime(); ++i) { + frames.get(i).refine(Not(And(newCore))); + } + proofObligationsQueue.removeLast(); + } else { + proofObligationsQueue.add(new ProofObligation(new HashSet<>(b), proofObligation.getTime() - 1)); + } + } + return null; + + } + + public Trace checkFirst() { + try (var wpp = new WithPushPop(solver)) { + solver.track(PathUtils.unfold(monolithicExpr.getInitExpr(), 0)); + solver.track(PathUtils.unfold(Not(monolithicExpr.getPropExpr()), 0)); + if (solver.check().isSat()) { + return Trace.of(List.of(valToState.apply(solver.getModel())), List.of()); + } + } + if (propertyOpt) { + try (var wpp = new WithPushPop(solver)) { + solver.track(PathUtils.unfold(monolithicExpr.getInitExpr(), 0)); + solver.track(PathUtils.unfold(monolithicExpr.getTransExpr(), 0)); + solver.track(PathUtils.unfold(Not(monolithicExpr.getPropExpr()), monolithicExpr.getTransOffsetIndex())); + if (solver.check().isSat()) { + return Trace.of(List.of(valToState.apply(solver.getModel())), List.of()); + } else { + return null; + } + } + } else { + return null; + } + + } + + public Collection> checkCurrentFrame(Expr target) { + if (propertyOpt) { + try (var wpp = new WithPushPop(solver)) { + frames.get(currentFrameNumber).getExprs().forEach(ex -> solver.track(PathUtils.unfold(ex, 0))); + getConjuncts(monolithicExpr.getTransExpr()).forEach(ex -> solver.track(PathUtils.unfold(ex, 0))); + solver.track(PathUtils.unfold(target, monolithicExpr.getTransOffsetIndex())); + if (solver.check().isSat()) { + final Valuation model = solver.getModel(); + final MutableValuation filteredModel = new MutableValuation(); + monolithicExpr.getVars().stream().map(varDecl -> varDecl.getConstDecl(0)).filter(model.toMap()::containsKey).forEach(decl -> filteredModel.put(decl, model.eval(decl).get())); + return getConjuncts(PathUtils.foldin(filteredModel.toExpr(), 0)); + } else { + return null; + } + } + } else { + return frames.get(currentFrameNumber).check(target); + } + + } + + public boolean propagate() { + frames.add(new Frame(frames.get(currentFrameNumber), solver, monolithicExpr)); + currentFrameNumber++; + if (propertyOpt) { + for (int j = 1; j <= currentFrameNumber; j++) { + frames.get(j).refine(monolithicExpr.getPropExpr()); + } + } + + if (propagateOpt) { + for (int j = 1; j < currentFrameNumber; j++) { + for (var c : frames.get(j).getExprs()) { + try (var wpp = new WithPushPop(solver)) { + frames.get(j).getExprs().forEach(ex -> solver.track(PathUtils.unfold(ex, 0))); + getConjuncts(monolithicExpr.getTransExpr()).forEach(ex -> solver.track(PathUtils.unfold(ex, 0))); + solver.track(PathUtils.unfold(Not(c), monolithicExpr.getTransOffsetIndex())); + if (solver.check().isUnsat()) { + frames.get(j + 1).refine(c); + } + } + } + if (frames.get(j + 1).equalsParent()) { + return true; + } + } + } else if (currentFrameNumber > 1 && frames.get(currentFrameNumber - 1).equalsParent()) { + return true; + } + return false; + } + + public Trace traceMaker(LinkedList forwardProofObligations) { + var stateList = new ArrayList(); + var actionList = new ArrayList(); + Valuation lastValuation = null; + while (!forwardProofObligations.isEmpty()) { + final ProofObligation currentProofObligation; + if (forwardTrace) { + currentProofObligation = forwardProofObligations.getLast(); + forwardProofObligations.removeLast(); + } else { + currentProofObligation = forwardProofObligations.getFirst(); + forwardProofObligations.removeFirst(); + } + MutableValuation mutableValuation = new MutableValuation(); + for (Expr ex : currentProofObligation.getExpressions()) { + RefExpr refExpr = (RefExpr) ex.getOps().get(0); + LitExpr litExpr = (LitExpr) ex.getOps().get(1); + mutableValuation.put(refExpr.getDecl(), litExpr); + + } + stateList.add(valToState.apply(mutableValuation)); + if (lastValuation != null) { + actionList.add(biValToAction.apply(lastValuation, mutableValuation)); + } + lastValuation = mutableValuation; + } + return Trace.of(stateList, actionList); + } +} diff --git a/subprojects/common/analysis/src/main/java/hu/bme/mit/theta/analysis/algorithm/ic3/ProofObligation.java b/subprojects/common/analysis/src/main/java/hu/bme/mit/theta/analysis/algorithm/ic3/ProofObligation.java new file mode 100644 index 0000000000..9435525fc9 --- /dev/null +++ b/subprojects/common/analysis/src/main/java/hu/bme/mit/theta/analysis/algorithm/ic3/ProofObligation.java @@ -0,0 +1,23 @@ +package hu.bme.mit.theta.analysis.algorithm.ic3; + +import hu.bme.mit.theta.core.type.Expr; +import hu.bme.mit.theta.core.type.booltype.BoolType; + +import java.util.Set; + +public class ProofObligation { + private Set> expressions; + private int time; + ProofObligation(Set> expressions, int time){ + this.expressions = expressions; + this.time = time; + } + + public int getTime() { + return time; + } + + public Set> getExpressions() { + return expressions; + } +} diff --git a/subprojects/sts/sts-analysis/src/test/java/hu/bme/mit/theta/sts/analysis/Ic3Test.java b/subprojects/sts/sts-analysis/src/test/java/hu/bme/mit/theta/sts/analysis/Ic3Test.java new file mode 100644 index 0000000000..5390d34a82 --- /dev/null +++ b/subprojects/sts/sts-analysis/src/test/java/hu/bme/mit/theta/sts/analysis/Ic3Test.java @@ -0,0 +1,106 @@ +package hu.bme.mit.theta.sts.analysis; + +/* + * Copyright 2024 Budapest University of Technology and Economics + * + * 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. + */ + +import hu.bme.mit.theta.analysis.algorithm.bounded.MonolithicExpr; +import hu.bme.mit.theta.analysis.algorithm.bounded.ReversedMonolithicExprKt; +import hu.bme.mit.theta.analysis.algorithm.ic3.Ic3Checker; +import hu.bme.mit.theta.common.Utils; +import hu.bme.mit.theta.core.model.Valuation; +import hu.bme.mit.theta.solver.z3legacy.Z3LegacySolverFactory; +import hu.bme.mit.theta.sts.STS; +import hu.bme.mit.theta.sts.aiger.AigerParser; +import hu.bme.mit.theta.sts.aiger.AigerToSts; +import hu.bme.mit.theta.sts.dsl.StsDslManager; +import hu.bme.mit.theta.sts.dsl.StsSpec; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; + +@RunWith(value = Parameterized.class) +public class Ic3Test { + + @Parameterized.Parameter(value = 0) + public String filePath; + @Parameterized.Parameter(value = 1) + public boolean isSafe; + + @Parameterized.Parameters(name = "{index}: {0}, {1}") + public static Collection data() { + return Arrays.asList(new Object[][]{ + {"src/test/resources/hw1_false.aag", false}, + + {"src/test/resources/hw2_true.aag", true}, + + {"src/test/resources/boolean1.system", false}, + + {"src/test/resources/boolean2.system", false}, + + {"src/test/resources/counter.system", true}, + + {"src/test/resources/counter_bad.system", false}, + + {"src/test/resources/counter_parametric.system", true}, + +// {"src/test/resources/loop.system", true}, + + {"src/test/resources/loop_bad.system", false}, + + {"src/test/resources/multipleinitial.system", false}, + + {"src/test/resources/readerswriters.system", true}, + + {"src/test/resources/simple1.system", false}, + + {"src/test/resources/simple2.system", true}, + + {"src/test/resources/simple3.system", false}, + }); + } + + @Test + public void testConnected() throws IOException { + + final STS sts; + if (filePath.endsWith("aag")) { + sts = AigerToSts.createSts(AigerParser.parse(filePath)); + } else { + final StsSpec spec = StsDslManager.createStsSpec(new FileInputStream(filePath)); + if (spec.getAllSts().size() != 1) { + throw new UnsupportedOperationException("STS contains multiple properties."); + } + sts = Utils.singleElementOf(spec.getAllSts()); + } + final MonolithicExpr monolithicExpr = StsToMonolithicExprKt.toMonolithicExpr(sts); + MonolithicExpr reverseMonolithicExpr = ReversedMonolithicExprKt.createReversed(monolithicExpr); + var checker = new Ic3Checker<>( + monolithicExpr, + true, + Z3LegacySolverFactory.getInstance(), + valuation -> StsToMonolithicExprKt.valToState(sts, valuation), + (Valuation v1, Valuation v2) -> StsToMonolithicExprKt.valToAction(sts, v1, v2), + true,true,true,true,true, true); + Assert.assertEquals(isSafe, checker.check().isSafe()); + } +} +