diff --git a/examples/multiobjective/ma/polling/polling.ma b/examples/multiobjective/ma/polling/polling.ma new file mode 100644 index 000000000..ef9c4958e --- /dev/null +++ b/examples/multiobjective/ma/polling/polling.ma @@ -0,0 +1,105 @@ +// Translation of the MAPA Specification of a polling system into PRISM code +// http://wwwhome.cs.utwente.nl/~timmer/scoop/papers/qest13/index.html + +ma + +const int J; // number of job types (should be in [1..6]) +const int Q; // Maximum queue size in each station +const bool samerate=false; // true if all stations should have the same rate + +// Formulae to control the LIFO queue of the stations. +// The queue is represented by some integer whose base J representation has at most Q digits, each representing one of the job types 0, 1, ..., J-1. +// In addition, we store the current size of the queue which is needed to distinguish an empty queue from a queue holding job of type 0 +formula queue1_empty = q1Size=0; +formula queue1_full = q1Size=Q; +formula queue1_pop = floor(q1/J); +formula queue1_head = q1 - (queue1_pop * J); // i.e. q1 modulo J +formula queue1_push = q1*J; +formula queue2_empty = q2Size=0; +formula queue2_full = q2Size=Q; +formula queue2_pop = floor(q2/J); +formula queue2_head = q2 - (queue2_pop * J); // i.e. q2 modulo J +formula queue2_push = q2*J; + +const int queue_maxValue = (J^Q)-1; + + + +const double inRate1 = 3; // = (2*1) + 1; +const double inRate2 = 5; // = (2*2) + 1; +const double inRate3 = 7; // = (2*3) + 1; + +module pollingsys + // The queues for the stations + q1 : [0..queue_maxValue]; + q1Size : [0..Q]; + q2 : [0..queue_maxValue]; + q2Size : [0..Q]; + + // Store the job that is currently processed by the server. j=J means that no job is processed. + j : [0..J] init J; + + // Flag indicating whether a new job arrived + newJob1 : bool init false; + newJob2 : bool init false; + + //<> !newJob1 & !newJob2 & !queue1_full & queue2_full & j=J -> inRate1 : (newJob1'=true); + //<> !newJob1 & !newJob2 & queue1_full & !queue2_full & j=J -> inRate2 : (newJob2'=true); + <> !newJob1 & !newJob2 & !queue1_full & !queue2_full & j=J -> inRate1 : (newJob1'=true) + inRate2 : (newJob2'=true); + <> !newJob1 & !newJob2 & queue1_full & queue2_full & j<J -> 2*(j+1) : (j'=J); + <> !newJob1 & !newJob2 & !queue1_full & queue2_full & j<J -> inRate1 : (newJob1'=true) + 2*(j+1) : (j'=J); + <> !newJob1 & !newJob2 & queue1_full & !queue2_full & j<J -> inRate2 : (newJob2'=true) + 2*(j+1) : (j'=J); + <> !newJob1 & !newJob2 & !queue1_full & !queue2_full & j<J -> inRate1 : (newJob1'=true) + inRate2 : (newJob2'=true) + 2*(j+1) : (j'=J); + + [] newJob1 & J>=1 -> 1 : (q1Size'=q1Size+1) & (q1'=queue1_push+0) & (newJob1'=false); + [] newJob1 & J>=2 -> 1 : (q1Size'=q1Size+1) & (q1'=queue1_push+1) & (newJob1'=false); + [] newJob1 & J>=3 -> 1 : (q1Size'=q1Size+1) & (q1'=queue1_push+2) & (newJob1'=false); + [] newJob1 & J>=4 -> 1 : (q1Size'=q1Size+1) & (q1'=queue1_push+3) & (newJob1'=false); + [] newJob1 & J>=5 -> 1 : (q1Size'=q1Size+1) & (q1'=queue1_push+4) & (newJob1'=false); + [] newJob1 & J>=6 -> 1 : (q1Size'=q1Size+1) & (q1'=queue1_push+5) & (newJob1'=false); + + [] newJob2 & J>=1 -> 1 : (q2Size'=q2Size+1) & (q2'=queue2_push+0) & (newJob2'=false); + [] newJob2 & J>=2 -> 1 : (q2Size'=q2Size+1) & (q2'=queue2_push+1) & (newJob2'=false); + [] newJob2 & J>=3 -> 1 : (q2Size'=q2Size+1) & (q2'=queue2_push+2) & (newJob2'=false); + [] newJob2 & J>=4 -> 1 : (q2Size'=q2Size+1) & (q2'=queue2_push+3) & (newJob2'=false); + [] newJob2 & J>=5 -> 1 : (q2Size'=q2Size+1) & (q2'=queue2_push+4) & (newJob2'=false); + [] newJob2 & J>=6 -> 1 : (q2Size'=q2Size+1) & (q2'=queue2_push+5) & (newJob2'=false); + + [copy1] !newJob1 & !newJob2 & !queue1_empty & j=J -> 0.9 : (j'=queue1_head) & (q1Size'=q1Size-1) & (q1'=queue1_pop) + 0.1 : (j'=queue1_head); + [copy2] !newJob1 & !newJob2 & !queue2_empty & j=J -> 0.9 : (j'=queue2_head) & (q2Size'=q2Size-1) & (q2'=queue2_pop) + 0.1 : (j'=queue2_head); + +endmodule + + + +label "q1full" = q1Size=Q; +label "q2full" = q2Size=Q; +label "allqueuesfull" = q1Size=Q & q2Size=Q; + + +// Rewards adapted from Guck et al.: Modelling and Analysis of Markov Reward Automata + +rewards "processedjobs1" + [copy1] true : 1; +endrewards + +rewards "processedjobs2" + [copy1] true : 1; +endrewards + +rewards "processedjobs" + [copy1] true : 1; + [copy2] true : 1; +endrewards + +rewards "waiting1" + true : (q1Size); +endrewards + +rewards "waiting2" + true : (q2Size); +endrewards + +rewards "waiting" + true : (q1Size + q2Size); +endrewards \ No newline at end of file diff --git a/src/modelchecker/multiobjective/helper/SparseMaMultiObjectiveWeightVectorChecker.cpp b/src/modelchecker/multiobjective/helper/SparseMaMultiObjectiveWeightVectorChecker.cpp index af76131ba..a891f1b1c 100644 --- a/src/modelchecker/multiobjective/helper/SparseMaMultiObjectiveWeightVectorChecker.cpp +++ b/src/modelchecker/multiobjective/helper/SparseMaMultiObjectiveWeightVectorChecker.cpp @@ -78,10 +78,10 @@ namespace storm { // Only perform such a step if there is time left. if(currentEpoch>0) { performMSStep(MS, PS, consideredObjectives); - if(currentEpoch % 1000 == 0) { - STORM_LOG_DEBUG(currentEpoch << " digitized time steps left. Current weighted value is " << PS.weightedSolutionVector[0]); - std::cout << std::endl << currentEpoch << " digitized time steps left. Current weighted value is " << PS.weightedSolutionVector[0] << std::endl; - } + // if(currentEpoch % 1000 == 0) { + // STORM_LOG_DEBUG(currentEpoch << " digitized time steps left. Current weighted value is " << PS.weightedSolutionVector[0]); + // std::cout << std::endl << currentEpoch << " digitized time steps left. Current weighted value is " << PS.weightedSolutionVector[0] << std::endl; + // } --currentEpoch; } else { break; @@ -261,17 +261,19 @@ namespace storm { VT const maxRate = this->data.preprocessedModel.getMaximalExitRate(); for(uint_fast64_t objIndex = 0; objIndex < this->data.objectives.size(); ++objIndex) { auto const& obj = this->data.objectives[objIndex]; + VT errorTowardsZero; + VT errorAwayFromZero; if(obj.lowerTimeBound) { uint_fast64_t digitizedBound = storm::utility::convertNumber<uint_fast64_t>((*obj.lowerTimeBound)/digitizationConstant); auto timeBoundIt = lowerTimeBounds.insert(std::make_pair(digitizedBound, storm::storage::BitVector(this->data.objectives.size(), false))).first; timeBoundIt->second.set(objIndex); VT digitizationError = storm::utility::one<VT>(); digitizationError -= std::exp(-maxRate * (*obj.lowerTimeBound)) * storm::utility::pow(storm::utility::one<VT>() + maxRate * digitizationConstant, digitizedBound); - this->offsetsToLowerBound[objIndex] = -digitizationError; - this->offsetsToUpperBound[objIndex] = storm::utility::one<VT>() - std::exp(-maxRate * digitizationConstant);; + errorTowardsZero = -digitizationError; + errorAwayFromZero = storm::utility::one<VT>() - std::exp(-maxRate * digitizationConstant);; } else { - this->offsetsToLowerBound[objIndex] = storm::utility::zero<VT>(); - this->offsetsToUpperBound[objIndex] = storm::utility::zero<VT>(); + errorTowardsZero = storm::utility::zero<VT>(); + errorAwayFromZero = storm::utility::zero<VT>(); } if(obj.upperTimeBound) { uint_fast64_t digitizedBound = storm::utility::convertNumber<uint_fast64_t>((*obj.upperTimeBound)/digitizationConstant); @@ -279,9 +281,16 @@ namespace storm { timeBoundIt->second.set(objIndex); VT digitizationError = storm::utility::one<VT>(); digitizationError -= std::exp(-maxRate * (*obj.upperTimeBound)) * storm::utility::pow(storm::utility::one<VT>() + maxRate * digitizationConstant, digitizedBound); - this->offsetsToUpperBound[objIndex] += digitizationError; + errorAwayFromZero += digitizationError; + } + STORM_LOG_ASSERT(errorTowardsZero + errorAwayFromZero <= this->maximumLowerUpperBoundGap, "Precision not sufficient."); + if (obj.rewardsArePositive) { + this->offsetsToLowerBound[objIndex] = -errorTowardsZero; + this->offsetsToUpperBound[objIndex] = errorAwayFromZero; + } else { + this->offsetsToLowerBound[objIndex] = -errorAwayFromZero; + this->offsetsToUpperBound[objIndex] = errorTowardsZero; } - STORM_LOG_ASSERT(this->offsetsToUpperBound[objIndex] - this->offsetsToLowerBound[objIndex] <= this->maximumLowerUpperBoundGap, "Precision not sufficient."); } } @@ -364,14 +373,14 @@ namespace storm { // check whether the linEqSolver needs to be updated, i.e., whether the scheduler has changed if(newOptimalChoices != optimalChoicesAtCurrentEpoch) { - std::cout << "X"; + // std::cout << "X"; optimalChoicesAtCurrentEpoch.swap(newOptimalChoices); linEq.solver = nullptr; storm::storage::SparseMatrix<ValueType> linEqMatrix = PS.toPS.selectRowsFromRowGroups(optimalChoicesAtCurrentEpoch, true); linEqMatrix.convertToEquationSystem(); linEq.solver = linEq.factory.create(std::move(linEqMatrix)); } else { - std::cout << " "; + // std::cout << " "; } for(auto objIndex : consideredObjectives) { PS.toMS.multiplyWithVector(MS.objectiveSolutionVectors[objIndex], PS.auxChoiceValues); diff --git a/src/modelchecker/multiobjective/helper/SparseMultiObjectiveHelper.cpp b/src/modelchecker/multiobjective/helper/SparseMultiObjectiveHelper.cpp index 02a05bd3d..627db4eba 100644 --- a/src/modelchecker/multiobjective/helper/SparseMultiObjectiveHelper.cpp +++ b/src/modelchecker/multiobjective/helper/SparseMultiObjectiveHelper.cpp @@ -57,8 +57,8 @@ namespace storm { template <class SparseModelType, typename RationalNumberType> void SparseMultiObjectiveHelper<SparseModelType, RationalNumberType>::achievabilityQuery(PreprocessorData const& preprocessorData, WeightVectorCheckerType weightVectorChecker, ResultData& resultData) { - //Set the maximum gap between lower and upper bound of the weightVectorChecker result we initially allow for this query type. - weightVectorChecker->setMaximumLowerUpperBoundGap(storm::utility::convertNumber<SparseModelValueType>(0.1)); // TODO try other values? + //Set the maximum gap between lower and upper bound of the weightVectorChecker result we initially allow for this query type. Note that we could also try other values + weightVectorChecker->setMaximumLowerUpperBoundGap(storm::utility::convertNumber<SparseModelValueType>(0.1)); // Get a point that represents the thresholds Point thresholds; thresholds.reserve(preprocessorData.objectives.size()); @@ -84,8 +84,8 @@ namespace storm { template <class SparseModelType, typename RationalNumberType> void SparseMultiObjectiveHelper<SparseModelType, RationalNumberType>::numericalQuery(PreprocessorData const& preprocessorData, WeightVectorCheckerType weightVectorChecker, ResultData& resultData) { STORM_LOG_ASSERT(preprocessorData.indexOfOptimizingObjective, "Detected numerical query but index of optimizing objective is not set."); - // Set the maximum gap between lower and upper bound of the weightVectorChecker result we initially allow for this query type. - weightVectorChecker->setMaximumLowerUpperBoundGap(storm::utility::convertNumber<SparseModelValueType>(0.1)); // TODO try other values? + // Set the maximum gap between lower and upper bound of the weightVectorChecker result we initially allow for this query type. Note that we could also try other values + weightVectorChecker->setMaximumLowerUpperBoundGap(storm::utility::convertNumber<SparseModelValueType>(0.1)); // initialize some data uint_fast64_t optimizingObjIndex = *preprocessorData.indexOfOptimizingObjective; Point thresholds; @@ -131,11 +131,12 @@ namespace storm { // Note that we do not have to care whether a threshold is strict anymore, because the resulting optimum should be // the supremum over all strategies. Hence, one could combine a scheduler inducing the optimum value (but possibly violating strict // thresholds) and (with very low probability) a scheduler that satisfies all (possibly strict) thresholds. - // The euclidean distance between the lower and upper bounds of the results of the weightVectorChecker should be less than the precision given in the settings + + // Pick the maximum gap between lower and upper bound of the weightVectorChecker result. SparseModelValueType gap = storm::utility::convertNumber<SparseModelValueType>(storm::settings::getModule<storm::settings::modules::MultiObjectiveSettings>().getPrecision()); - gap -= storm::utility::convertNumber<SparseModelValueType>(storm::settings::getModule<storm::settings::modules::GeneralSettings>().getPrecision()); - gap /= storm::utility::sqrt(static_cast<SparseModelValueType>(preprocessorData.objectives.size())); - weightVectorChecker->setMaximumLowerUpperBoundGap(gap); // TODO try other values? + gap /= (storm::utility::one<SparseModelValueType>() + storm::utility::one<SparseModelValueType>()); + weightVectorChecker->setMaximumLowerUpperBoundGap(gap); + while(true) { std::pair<Point, bool> optimizationRes = resultData.underApproximation()->intersection(thresholdsAsPolytope)->optimize(directionOfOptimizingObjective); STORM_LOG_THROW(optimizationRes.second, storm::exceptions::UnexpectedException, "The underapproximation is either unbounded or empty."); @@ -160,12 +161,13 @@ namespace storm { template <class SparseModelType, typename RationalNumberType> void SparseMultiObjectiveHelper<SparseModelType, RationalNumberType>::paretoQuery(PreprocessorData const& preprocessorData, WeightVectorCheckerType weightVectorChecker, ResultData& resultData) { - // Set the maximum gap between lower and upper bound of the weightVectorChecker result we initially allow for this query type. - // The euclidean distance between the lower and upper bounds of the results of the weightVectorChecker should be less than the precision given in the settings + // Set the maximum gap between lower and upper bound of the weightVectorChecker result. + // This is the maximal edge length of the box we have to consider around each computed point + // We pick the gap such that the maximal distance between two points within this box is less than the given precision divided by two. SparseModelValueType gap = storm::utility::convertNumber<SparseModelValueType>(storm::settings::getModule<storm::settings::modules::MultiObjectiveSettings>().getPrecision()); - gap -= storm::utility::convertNumber<SparseModelValueType>(storm::settings::getModule<storm::settings::modules::GeneralSettings>().getPrecision()); + gap /= (storm::utility::one<SparseModelValueType>() + storm::utility::one<SparseModelValueType>()); gap /= storm::utility::sqrt(static_cast<SparseModelValueType>(preprocessorData.objectives.size())); - weightVectorChecker->setMaximumLowerUpperBoundGap(gap); // TODO try other values? + weightVectorChecker->setMaximumLowerUpperBoundGap(gap); //First consider the objectives individually for(uint_fast64_t objIndex = 0; objIndex<preprocessorData.objectives.size() && !maxStepsPerformed(resultData); ++objIndex) { diff --git a/src/utility/storm.h b/src/utility/storm.h index 502cdd215..c78906434 100644 --- a/src/utility/storm.h +++ b/src/utility/storm.h @@ -327,11 +327,11 @@ namespace storm { template<typename ValueType> std::unique_ptr<storm::modelchecker::CheckResult> verifySparseMarkovAutomaton(std::shared_ptr<storm::models::sparse::MarkovAutomaton<ValueType>> ma, storm::modelchecker::CheckTask<storm::logic::Formula> const& task) { std::unique_ptr<storm::modelchecker::CheckResult> result; + // Close the MA, if it is not already closed. + if (!ma->isClosed()) { + ma->close(); + } if(task.getFormula().isMultiObjectiveFormula()) { - // Close the MA, if it is not already closed. - if (!ma->isClosed()) { - ma->close(); - } storm::modelchecker::SparseMaMultiObjectiveModelChecker<storm::models::sparse::MarkovAutomaton<ValueType>> modelchecker(*ma); if (modelchecker.canHandle(task)) { result = modelchecker.check(task);