/* * Copyright (C) 2024 University of Freiburg * * This file is part of the ULTIMATE Regression Test Library. * * The ULTIMATE Regression Test Library 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. * * The ULTIMATE Regression Test Library 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 the ULTIMATE Regression Test Library. If not, see . * * Additional permission under GNU GPL version 3 section 7: * If you modify the ULTIMATE Regression Test Library, or any covered work, by linking * or combining it with Eclipse RCP (or a modified version of Eclipse RCP), * containing parts covered by the terms of the Eclipse Public License, the * licensors of the ULTIMATE Regression Test Library grant you additional permission * to convey the resulting work. */ package de.uni_freiburg.informatik.ultimate.lib.pea; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import de.uni_freiburg.informatik.ultimate.util.datastructures.relation.Pair; /** * This class implements an algorithm for complementing Phase Event Automata as described in the bachelor thesis * "Complementation of Phase Event Automata" (L. Funk, 2023). * * @author Lena Funk */ public class PEAComplement { public static final String TOTAL_POSTFIX = "_total"; public static final String COMPLEMENT_POSTFIX = "_complement"; public static final String SINK_NAME = "sink"; private final PhaseEventAutomata mPEAtoComplement; private final PhaseEventAutomata mTotalisedPEA; private final PhaseEventAutomata mComplementPEA; private final Set mConstVars; public PEAComplement(final PhaseEventAutomata peaToComplement) { mPEAtoComplement = peaToComplement; mConstVars = Collections.emptySet(); mTotalisedPEA = totalise(mPEAtoComplement); mComplementPEA = complement(mTotalisedPEA); } public PEAComplement(final PhaseEventAutomata peaToComplement, final Set constVars) { mPEAtoComplement = peaToComplement; mConstVars = constVars; mTotalisedPEA = totalise(mPEAtoComplement); mComplementPEA = complement(mTotalisedPEA); } /** * Totalisation of input pea. In a totalised PEA, each location has exactly one outgoing transition enabled for each * valuation of variables and clock. * * @return the Totalised PEA of mPEAtoComplement */ public PhaseEventAutomata totalise(final PhaseEventAutomata sourcePea) { // add sink with loop transition final Phase sinkPhase = new Phase(SINK_NAME, CDD.TRUE, CDD.TRUE); sinkPhase.addTransition(sinkPhase, CDD.TRUE, new String[] {}); // for the Totalised PEA, the sink is not terminal sinkPhase.setTerminal(false); computeInitialTransitionSink(sourcePea, sinkPhase); final Map totalisedPhases = copyPhases(sourcePea.getPhases(), TOTAL_POSTFIX); totalisedPhases.put(sinkPhase.getName(), sinkPhase); // prepare initial phases final List totalisedInit = new ArrayList<>(); for (final Phase p : sourcePea.getInit()) { totalisedInit.add(new InitialTransition(CDD.TRUE, totalisedPhases.get(p.getName()))); } if (sinkPhase.isInit()) { totalisedInit.add(new InitialTransition(sinkPhase.getInitialTransition().getGuard(), sinkPhase)); } // needed for priming and unpriming final Set clockVarSet = new HashSet<>(sourcePea.getClocks()); for (final Phase phase : sourcePea.getPhases()) { totalizeTransition(phase, sinkPhase, totalisedPhases, clockVarSet); } final List newClocks = new ArrayList<>(); for (final String s : sourcePea.getClocks()) { newClocks.add(addSuffixString(s, TOTAL_POSTFIX)); } return new PhaseEventAutomata(addSuffixString(sourcePea.getName(), TOTAL_POSTFIX), new ArrayList<>(totalisedPhases.values()), totalisedInit, newClocks, new HashMap<>(sourcePea.getVariables())); } private void totalizeTransition(final Phase phase, final Phase sinkPhase, final Map totalisedPhases, final Set clockVarSet) { final Phase totalisedPhase = totalisedPhases.get(phase.getName()); final CDD clockInv = phase.getClockInvariant(); CDD guardToSink = phase.getStateInv().and(RangeDecision.strict(clockInv)); // special case: PEAs with strict clock invariants. Clock invariants must be // non-strictified and guards of outgoing transitions must be modified to // explicitly contain the original clock invariant. if (phase.isStrict()) { final CDD totalisedClockInvariant = totalisedPhase.getClockInvariant(); final CDD strictClockInvariantDNF = totalisedClockInvariant.toDNF_CDD(); final List modifiedClockConstraints = new ArrayList<>(); final CDD nonStrictClockInvariant = strictConstraintHandling(strictClockInvariantDNF, CDD.TRUE, modifiedClockConstraints); totalisedPhase.setModifiedConstraints(modifiedClockConstraints); totalisedPhase.setClockInv(nonStrictClockInvariant); for (final Transition transition : phase.getTransitions()) { CDD modifiedGuard = transition.getGuard(); for (final RangeDecision clockConstraint : modifiedClockConstraints) { final CDD clockConstraintCdd = RangeDecision.create(clockConstraint.getVar(), RangeDecision.OP_LT, clockConstraint.getVal(0)); modifiedGuard = modifiedGuard.and(clockConstraintCdd); } transition.setGuard(modifiedGuard); } } for (final Transition transition : phase.getTransitions()) { // add transition to new phase final Phase totalisedSuccessor = totalisedPhases.get(transition.getDest().getName()); final Transition totalisedTransition = totalisedPhase.addTransition(totalisedSuccessor, addClockSuffixCDD(transition.getGuard(), TOTAL_POSTFIX), addClockSuffix(transition.getResets(), TOTAL_POSTFIX)); final String[] reset = totalisedTransition.getResets(); final CDD successorStateInv = totalisedSuccessor.getStateInv(); final CDD successorClockInv = totalisedSuccessor.getClockInv(); // compute guard to sink // we do not use the clock invariant of the successor phase // if the same clock is in the reset set of the transition final CDD guardCdd = transition.getGuard(); final CDD guardUnprimed = guardCdd.unprime(clockVarSet); if (reset.length > 0) { final CDD noResetClockInv = RangeDecision.filterCdd(successorClockInv, reset); guardToSink = guardToSink.or(guardUnprimed.and(successorStateInv).and(RangeDecision.strict(noResetClockInv))); } else { guardToSink = guardToSink.or(guardUnprimed.and(successorStateInv) .and(RangeDecision.strict(successorClockInv))); } } final Set unprimedVars = clockVarSet; unprimedVars.addAll(mConstVars); final CDD guardToSinkPrimed = guardToSink.prime(unprimedVars); final CDD guardToSinkWithClockSuffix = addClockSuffixCDD(guardToSinkPrimed, TOTAL_POSTFIX); // make transition to sink totalisedPhase.addTransition(sinkPhase, guardToSinkWithClockSuffix.negate(), new String[] {}); totalisedPhases.put(totalisedPhase.getName(), totalisedPhase); } /** * Complements a totalized PEA * * @return the complement automaton of mPEAtoComplement */ public PhaseEventAutomata complement(final PhaseEventAutomata sourcePea) { final List phases = new ArrayList<>(); final Map complementPhases = copyPhases(sourcePea.mPhases, COMPLEMENT_POSTFIX); for (final Phase sourcePhase : sourcePea.getPhases()) { final Phase complementPhase = complementPhases.get(sourcePhase.getName()); final boolean newTerminal = !sourcePhase.getTerminal(); complementPhase.setTerminal(newTerminal); for (final Transition transition : sourcePhase.getTransitions()) { complementPhase.addTransition(complementPhases.get(transition.getDest().getName()), addClockSuffixCDD(transition.getGuard(), COMPLEMENT_POSTFIX), addClockSuffix(transition.getResets(), COMPLEMENT_POSTFIX)); } phases.add(complementPhase); } final List complementedInit = new ArrayList<>(); for (final Phase p : sourcePea.getInit()) { complementedInit .add(new InitialTransition(p.getInitialTransition().getGuard(), complementPhases.get(p.getName()))); } final List newClocks = new ArrayList<>(); for (final String s : sourcePea.getClocks()) { newClocks.add(addSuffixString(s, TOTAL_POSTFIX)); } return new PhaseEventAutomata(addSuffixString(sourcePea.getName(), COMPLEMENT_POSTFIX), phases, complementedInit, newClocks, new HashMap<>(sourcePea.getVariables())); } /** * Computes guard for initial transition to sink * * @param sinkPhase */ private void computeInitialTransitionSink(final PhaseEventAutomata pea, final Phase sinkPhase) { CDD initialTransitionSinkGuard = CDD.FALSE; final List initialPhases = pea.getInit(); for (final Phase phase : initialPhases) { assert phase.getInitialTransition() != null; final InitialTransition initialTransition = phase.getInitialTransition(); final CDD conjunction = phase.getStateInvariant().and(initialTransition.getGuard()); initialTransitionSinkGuard = initialTransitionSinkGuard.or(conjunction); } if (initialTransitionSinkGuard != CDD.TRUE) { final InitialTransition sinkInitialTransition = new InitialTransition(initialTransitionSinkGuard.negate(), sinkPhase); sinkPhase.setInitialTransition(sinkInitialTransition); } else { sinkPhase.setInit(false); } } private CDD strictConstraintHandling(final CDD clockInvariantDNF, CDD nonStrictClockInvariant, final List strictClockConstraints) { if (clockInvariantDNF == CDD.TRUE || clockInvariantDNF == CDD.FALSE) { return nonStrictClockInvariant; } if (clockInvariantDNF.isAtomic()) { final RangeDecision clockConstraint = (RangeDecision) clockInvariantDNF.getDecision(); if (clockConstraint.getOp(0) == RangeDecision.OP_LT) { final CDD nonStrictClockConstraint = RangeDecision.create(clockConstraint.getVar(), RangeDecision.OP_LTEQ, clockConstraint.getVal(0)); nonStrictClockInvariant = nonStrictClockInvariant.and(nonStrictClockConstraint); strictClockConstraints.add(clockConstraint); } return nonStrictClockInvariant; } if (clockInvariantDNF.getChilds() != null) { final RangeDecision clockConstraint = (RangeDecision) clockInvariantDNF.getDecision(); if (clockConstraint.getOp(0) == RangeDecision.OP_LT) { final CDD nonStrictClockConstraint = RangeDecision.create(clockConstraint.getVar(), RangeDecision.OP_LTEQ, clockConstraint.getVal(0)); nonStrictClockInvariant = nonStrictClockInvariant.and(nonStrictClockConstraint); strictClockConstraints.add(clockConstraint); } final CDD trueChild = clockInvariantDNF.getChilds()[0]; strictConstraintHandling(trueChild, nonStrictClockInvariant, strictClockConstraints); } return nonStrictClockInvariant; } private Map copyPhases(final List phases, final String suffix) { final Map copy = new HashMap<>(); for (final Phase phase : phases) { final Phase copiedPhase = new Phase(phase.getName(), phase.getStateInv(), addClockSuffixCDD(phase.getClockInv(), suffix)); copiedPhase.setTerminal(phase.getTerminal()); copy.put(copiedPhase.getName(), copiedPhase); if (phase.getInitialTransition() != null) { final InitialTransition initialTransition = phase.getInitialTransition(); final InitialTransition newInitialTransition = new InitialTransition(initialTransition.getGuard(), copiedPhase); copiedPhase.setInitialTransition(newInitialTransition); } copiedPhase.setModifiedConstraints(phase.getModifiedConstraints()); } return copy; } public String addSuffixString(final String varName, final String suffix) { String stringWithSuffix; if (varName.contains(COMPLEMENT_POSTFIX)) { stringWithSuffix = varName.split(COMPLEMENT_POSTFIX)[0]; stringWithSuffix = stringWithSuffix + suffix; } if (varName.contains(TOTAL_POSTFIX)) { stringWithSuffix = varName.split(TOTAL_POSTFIX)[0]; stringWithSuffix = stringWithSuffix + suffix; } else { stringWithSuffix = varName + suffix; } return stringWithSuffix; } private String[] addClockSuffix(final String[] clocks, final String suffix) { final List clocksWithSuffix = new ArrayList<>(); for (final String clock : clocks) { final String clockWithSuffix = addSuffixString(clock, suffix); clocksWithSuffix.add(clockWithSuffix); } final String[] clocksWithSuffixArray = new String[clocksWithSuffix.size()]; return clocksWithSuffix.toArray(clocksWithSuffixArray); } @SuppressWarnings("unchecked") private CDD addClockSuffixCDD(final CDD cdd, final String suffix) { final List, int[]>>> dnfDecisions = cdd.getDecisionsDNF(); final List conjunctionsWithSuffix = new ArrayList<>(); for (final List, int[]>> conjunction : dnfDecisions) { CDD conjunctionWithSuffix = CDD.TRUE; for (final Pair, int[]> pair : conjunction) { if (pair.getFirst() instanceof RangeDecision) { final Decision decision = (Decision) pair.getFirst(); final RangeDecision rangeDecision = (RangeDecision) decision; final int trueChildIndex = pair.getSecond()[0]; final int op = rangeDecision.getOp(trueChildIndex); final int value = rangeDecision.getVal(trueChildIndex); final String clockVarWithSuffix = addSuffixString(decision.getVar(), suffix); final CDD rangeDecisionWithSuffix = RangeDecision.create(clockVarWithSuffix, op, value); conjunctionWithSuffix = conjunctionWithSuffix.and(rangeDecisionWithSuffix); } else if (pair.getFirst() instanceof BoogieBooleanExpressionDecision) { final BoogieBooleanExpressionDecision decision = (BoogieBooleanExpressionDecision) pair.getFirst(); CDD booleanDecision = BoogieBooleanExpressionDecision.create(decision.getExpression()); if (pair.getSecond()[0] == 1) { // when the index of the true child is 1, the decision is negated booleanDecision = booleanDecision.negate(); } conjunctionWithSuffix = conjunctionWithSuffix.and(booleanDecision); } else { // boolean decision CDD booleanDecision = BooleanDecision.create(pair.getFirst().getVar()); if (pair.getSecond()[0] == 1) { // when the index of the true child is 1, the decision is negated booleanDecision = booleanDecision.negate(); } conjunctionWithSuffix = conjunctionWithSuffix.and(booleanDecision); } } conjunctionsWithSuffix.add(conjunctionWithSuffix); } CDD disjunctionWithSuffix = CDD.FALSE; for (final CDD conjunction : conjunctionsWithSuffix) { disjunctionWithSuffix = disjunctionWithSuffix.or(conjunction); } return disjunctionWithSuffix; } public PhaseEventAutomata getTotalisedPEA() { return mTotalisedPEA; } public PhaseEventAutomata getComplementPEA() { return mComplementPEA; } }