diff --git a/resources/3rdparty/include_cudd.cmake b/resources/3rdparty/include_cudd.cmake index 0970cd9e8..1f02462b2 100644 --- a/resources/3rdparty/include_cudd.cmake +++ b/resources/3rdparty/include_cudd.cmake @@ -34,25 +34,13 @@ if (CMAKE_OSX_SYSROOT) set(CUDD_INCLUDE_FLAGS "CPPFLAGS=--sysroot=${CMAKE_OSX_SYSROOT}") endif() -set(CUDD_CXX_COMPILER "${CMAKE_CXX_COMPILER}") -if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") - if (CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 12.0.0.12000032) - if (CMAKE_HOST_SYSTEM_VERSION VERSION_GREATER_EQUAL 20.1.0) - message(WARNING "There are some known issues compiling CUDD on some setups. We implemented a workaround that mostly works, but if you still have problems compiling CUDD, especially if you do not use the default compiler of your system, please contact the Storm developers.") - # The issue is known to occur using the Command Line Tools for XCode 12.2. Apparently, it is fixed in the beta for XCode 12.3. - set(CUDD_CXX_COMPILER "c++") - endif() - endif() -endif() - - ExternalProject_Add( cudd3 DOWNLOAD_COMMAND "" SOURCE_DIR ${STORM_3RDPARTY_SOURCE_DIR}/cudd-3.0.0 PREFIX ${STORM_3RDPARTY_BINARY_DIR}/cudd-3.0.0 PATCH_COMMAND ${AUTORECONF} - CONFIGURE_COMMAND ${STORM_3RDPARTY_SOURCE_DIR}/cudd-3.0.0/configure --enable-shared --enable-obj --with-pic=yes --prefix=${STORM_3RDPARTY_BINARY_DIR}/cudd-3.0.0 --libdir=${CUDD_LIB_DIR} CC=${CMAKE_C_COMPILER} CXX=${CUDD_CXX_COMPILER} ${CUDD_INCLUDE_FLAGS} + CONFIGURE_COMMAND ${STORM_3RDPARTY_SOURCE_DIR}/cudd-3.0.0/configure --enable-shared --enable-obj --with-pic=yes --prefix=${STORM_3RDPARTY_BINARY_DIR}/cudd-3.0.0 --libdir=${CUDD_LIB_DIR} CC=${CMAKE_C_COMPILER} CXX=${CMAKE_CXX_COMPILER} ${CUDD_INCLUDE_FLAGS} BUILD_COMMAND make ${STORM_CUDD_FLAGS} INSTALL_COMMAND make install BUILD_IN_SOURCE 0 diff --git a/resources/examples/testfiles/pdtmc/brp16_2_mon_incr.pm b/resources/examples/testfiles/pdtmc/brp16_2_mon_incr.pm new file mode 100644 index 000000000..fde207637 --- /dev/null +++ b/resources/examples/testfiles/pdtmc/brp16_2_mon_incr.pm @@ -0,0 +1,148 @@ +// bounded retransmission protocol [D'AJJL01] +// gxn/dxp 23/05/2001 + +dtmc + +// number of chunks +const int N = 16; +// maximum number of retransmissions +const int MAX = 2; + +// reliability of channels +const double pL; +const double pK; + +// timeouts +const double TOMsg; +const double TOAck; + +module sender + + s : [0..6]; + // 0 idle + // 1 next_frame + // 2 wait_ack + // 3 retransmit + // 4 success + // 5 error + // 6 wait sync + srep : [0..3]; + // 0 bottom + // 1 not ok (nok) + // 2 do not know (dk) + // 3 ok (ok) + nrtr : [0..MAX]; + i : [0..N]; + bs : bool; + s_ab : bool; + fs : bool; + ls : bool; + + // idle + [NewFile] (s=0) -> (s'=1) & (i'=1) & (srep'=0); + // next_frame + [aF] (s=1) -> (s'=2) & (fs'=(i=1)) & (ls'=(i=N)) & (bs'=s_ab) & (nrtr'=0); + // wait_ack + [aB] (s=2) -> (s'=4) & (s_ab'=!s_ab); + [TO_Msg] (s=2) -> (s'=3); + [TO_Ack] (s=2) -> (s'=3); + // retransmit + [aF] (s=3) & (nrtr<MAX) -> (s'=2) & (fs'=(i=1)) & (ls'=(i=N)) & (bs'=s_ab) & (nrtr'=nrtr+1); + [] (s=3) & (nrtr=MAX) & (i<N) -> (s'=5) & (srep'=1); + [] (s=3) & (nrtr=MAX) & (i=N) -> (s'=5) & (srep'=2); + // success + [] (s=4) & (i<N) -> (s'=1) & (i'=i+1); + [] (s=4) & (i=N) -> (s'=0) & (srep'=3); + // error + [SyncWait] (s=5) -> (s'=6); + // wait sync + [SyncWait] (s=6) -> (s'=0) & (s_ab'=false); + +endmodule + +module receiver + + r : [0..5]; + // 0 new_file + // 1 fst_safe + // 2 frame_received + // 3 frame_reported + // 4 idle + // 5 resync + rrep : [0..4]; + // 0 bottom + // 1 fst + // 2 inc + // 3 ok + // 4 nok + fr : bool; + lr : bool; + br : bool; + r_ab : bool; + recv : bool; + + + // new_file + [SyncWait] (r=0) -> (r'=0); + [aG] (r=0) -> (r'=1) & (fr'=fs) & (lr'=ls) & (br'=bs) & (recv'=T); + // fst_safe_frame + [] (r=1) -> (r'=2) & (r_ab'=br); + // frame_received + [] (r=2) & (r_ab=br) & (fr=true) & (lr=false) -> (r'=3) & (rrep'=1); + [] (r=2) & (r_ab=br) & (fr=false) & (lr=false) -> (r'=3) & (rrep'=2); + [] (r=2) & (r_ab=br) & (fr=false) & (lr=true) -> (r'=3) & (rrep'=3); + [aA] (r=2) & !(r_ab=br) -> (r'=4); + // frame_reported + [aA] (r=3) -> (r'=4) & (r_ab'=!r_ab); + // idle + [aG] (r=4) -> (r'=2) & (fr'=fs) & (lr'=ls) & (br'=bs) & (recv'=T); + [SyncWait] (r=4) & (ls=true) -> (r'=5); + [SyncWait] (r=4) & (ls=false) -> (r'=5) & (rrep'=4); + // resync + [SyncWait] (r=5) -> (r'=0) & (rrep'=0); + +endmodule + +// prevents more than one file being sent +module tester + + T : bool; + + [NewFile] (T=false) -> (T'=true); + +endmodule + +module channelK + + k : [0..2]; + + // idle + [aF] (k=0) -> 1-pK : (k'=1) + pK : (k'=2); + // sending + [aG] (k=1) -> (k'=0); + // lost + [TO_Msg] (k=2) -> (k'=0); + +endmodule + +module channelL + + l : [0..2]; + + // idle + [aA] (l=0) -> 1-pL : (l'=1) + pL : (l'=2); + // sending + [aB] (l=1) -> (l'=0); + // lost + [TO_Ack] (l=2) -> (l'=0); + +endmodule + +label "error" = s=5; + +rewards + [TO_Msg] true : TOMsg; + [TO_Ack] true : TOAck; +endrewards + + diff --git a/resources/examples/testfiles/pdtmc/casestudy1.pm b/resources/examples/testfiles/pdtmc/casestudy1.pm new file mode 100644 index 000000000..17d890838 --- /dev/null +++ b/resources/examples/testfiles/pdtmc/casestudy1.pm @@ -0,0 +1,34 @@ +dtmc + +const double p; + +module test + + // local state + s : [0..4] init 0; + + [] s=0 -> p : (s'=1) + (1-p) : (s'=2); + [] s=1 -> p : (s'=3) + (1-p) : (s'=4); + [] s=2 -> 0.5*p : (s'=3) + (1-0.5*p) : (s'=4); + [] s=3 -> 1 : (s'=3); + [] s=4 -> 1 : (s'=4); + +endmodule + +// Dot output: +//digraph model { +// 0 [ label = "0: {init}" ]; +// 1 [ label = "1: {}" ]; +// 2 [ label = "2: {}" ]; +// 3 [ label = "3: {}" ]; +// 4 [ label = "4: {}" ]; +// 0 -> 1 [ label= "(p)/(1)" ]; +// 0 -> 2 [ label= "(-1 * (p+(-1)))/(1)" ]; +// 1 -> 3 [ label= "(p)/(1)" ]; +// 1 -> 4 [ label= "(-1 * (p+(-1)))/(1)" ]; +// 2 -> 3 [ label= "(p)/(2)" ]; +// 2 -> 4 [ label= "(-1 * (p+(-2)))/(2)" ]; +// 3 -> 3 [ label= "1" ]; +// 4 -> 4 [ label= "1" ]; +//} + diff --git a/resources/examples/testfiles/pdtmc/casestudy2.pm b/resources/examples/testfiles/pdtmc/casestudy2.pm new file mode 100644 index 000000000..9872270d7 --- /dev/null +++ b/resources/examples/testfiles/pdtmc/casestudy2.pm @@ -0,0 +1,39 @@ +dtmc + +const double p; + +module test + + // local state + s : [0..5] init 0; + + [] s=0 -> 0.4*p : (s'=1) + (1-p) : (s'=2) + 0.6*p : (s'=3); + [] s=1 -> 0.5*p : (s'=4) + 0.5*p : (s'=3) + (1-p) : (s'=5); + [] s=2 -> 0.3*p : (s'=4) + (1-0.3*p) : (s'=5); + [] s=3 -> 0.7*p : (s'=4) + (1-0.7*p) : (s'=5); + [] s=4 -> 1 : (s'=4); + [] s=5 -> 1 : (s'=5); + +endmodule + +// Dot output: +//digraph model { +// 0 [ label = "0: {init}" ]; +// 1 [ label = "1: {}" ]; +// 2 [ label = "2: {}" ]; +// 3 [ label = "3: {}" ]; +// 4 [ label = "4: {}" ]; +// 5 [ label = "5: {}" ]; +// 0 -> 1 [ label= "(2 * (p))/(5)" ]; +// 0 -> 2 [ label= "(-1 * (p+(-1)))/(1)" ]; +// 0 -> 3 [ label= "(3 * (p))/(5)" ]; +// 1 -> 3 [ label= "(p)/(2)" ]; +// 1 -> 4 [ label= "(p)/(2)" ]; +// 1 -> 5 [ label= "(-1 * (p+(-1)))/(1)" ]; +// 2 -> 4 [ label= "(3 * (p))/(10)" ]; +// 2 -> 5 [ label= "(-1 * (3*p+(-10)))/(10)" ]; +// 3 -> 4 [ label= "(7 * (p))/(10)" ]; +// 3 -> 5 [ label= "(-1 * (7*p+(-10)))/(10)" ]; +// 4 -> 4 [ label= "1" ]; +// 5 -> 5 [ label= "1" ]; +//} \ No newline at end of file diff --git a/resources/examples/testfiles/pdtmc/casestudy3.pm b/resources/examples/testfiles/pdtmc/casestudy3.pm new file mode 100644 index 000000000..312784b10 --- /dev/null +++ b/resources/examples/testfiles/pdtmc/casestudy3.pm @@ -0,0 +1,33 @@ +dtmc + +const double p; + +module test + + // local state + s : [0..4] init 0; + + [] s=0 -> p*(1-p) : (s'=1) + (1-p*(1-p)) : (s'=2); + [] s=1 -> p : (s'=3) + (1-p) : (s'=4); + [] s=2 -> 0.5*p : (s'=3) + (1-0.5*p) : (s'=4); + [] s=3 -> 1 : (s'=3); + [] s=4 -> 1 : (s'=4); + +endmodule + +// Dot output: +//digraph model { +// 0 [ label = "0: {init}" ]; +// 1 [ label = "1: {}" ]; +// 2 [ label = "2: {}" ]; +// 3 [ label = "3: {}" ]; +// 4 [ label = "4: {}" ]; +// 0 -> 1 [ label= "(-1 * ((p) * (p+(-1))))/(1)" ]; +// 0 -> 2 [ label= "(p^2+(-1)*p+1)/(1)" ]; +// 1 -> 3 [ label= "(p)/(1)" ]; +// 1 -> 4 [ label= "(1-p)/(1)" ]; +// 2 -> 3 [ label= "(1-(p)/(2))" ]; +// 2 -> 4 [ label= "(p)/(2)" ]; +// 3 -> 3 [ label= "1" ]; +// 4 -> 4 [ label= "1" ]; +//} \ No newline at end of file diff --git a/resources/examples/testfiles/pdtmc/parametric_die_2.pm b/resources/examples/testfiles/pdtmc/parametric_die_2.pm new file mode 100644 index 000000000..25b5ec8ec --- /dev/null +++ b/resources/examples/testfiles/pdtmc/parametric_die_2.pm @@ -0,0 +1,35 @@ +// Knuth's model of a fair die using only fair coins +dtmc + +const double p; +const double q; + +module die + + // local state + s : [0..7] init 0; + // value of the dice + d : [0..6] init 0; + + [] s=0 -> p : (s'=1) + (1-p) : (s'=2); + [] s=1 -> q : (s'=3) + (1-q) : (s'=4); + [] s=2 -> q : (s'=5) + (1-q) : (s'=6); + [] s=3 -> p : (s'=1) + (1-p) : (s'=7) & (d'=1); + [] s=4 -> p : (s'=7) & (d'=2) + (1-p) : (s'=7) & (d'=3); + [] s=5 -> p : (s'=7) & (d'=4) + (1-p) : (s'=7) & (d'=5); + [] s=6 -> p : (s'=2) + (1-p) : (s'=7) & (d'=6); + [] s=7 -> 1: (s'=7); + +endmodule + +rewards "coin_flips" + [] s<7 : 1; +endrewards + +label "one" = s=7&d=1; +label "two" = s=7&d=2; +label "three" = s=7&d=3; +label "four" = s=7&d=4; +label "five" = s=7&d=5; +label "six" = s=7&d=6; +label "done" = s=7; diff --git a/resources/examples/testfiles/pdtmc/simple1.pm b/resources/examples/testfiles/pdtmc/simple1.pm index 597da690d..a5a320f0a 100644 --- a/resources/examples/testfiles/pdtmc/simple1.pm +++ b/resources/examples/testfiles/pdtmc/simple1.pm @@ -15,3 +15,19 @@ module test endmodule +// Dot output: +//digraph model { +// 0 [ label = "0: {init}" ]; +// 1 [ label = "1: {}" ]; +// 2 [ label = "2: {}" ]; +// 3 [ label = "3: {}" ]; +// 4 [ label = "4: {}" ]; +// 0 -> 1 [ label= "(p)/(1)" ]; +// 0 -> 2 [ label= "(-1 * (p+(-1)))/(1)" ]; +// 1 -> 3 [ label= "(p)/(1)" ]; +// 1 -> 4 [ label= "(-1 * (p+(-1)))/(1)" ]; +// 2 -> 3 [ label= "(-1 * (p+(-1)))/(1)" ]; +// 2 -> 4 [ label= "(p)/(1)" ]; +// 3 -> 3 [ label= "1" ]; +// 4 -> 4 [ label= "1" ]; +//} \ No newline at end of file diff --git a/resources/examples/testfiles/pdtmc/simple3.pm b/resources/examples/testfiles/pdtmc/simple3.pm deleted file mode 100644 index 18f515800..000000000 --- a/resources/examples/testfiles/pdtmc/simple3.pm +++ /dev/null @@ -1,18 +0,0 @@ -dtmc - -const double p; - -module test - - // local state - s : [0..5] init 0; - - [] s=0 -> 0.4*p : (s'=1) + (1-p) : (s'=2) + 0.6*p : (s'=3); - [] s=1 -> 0.5*p : (s'=4) + 0.5*p : (s'=3) + (1-p) : (s'=5); - [] s=2 -> 0.3*p : (s'=4) + (1-0.3*p) : (s'=5); - [] s=3 -> 0.7*p : (s'=4) + (1-0.7*p) : (s'=5); - [] s=4 -> 1 : (s'=4); - [] s=5 -> 1 : (s'=5); - -endmodule - diff --git a/resources/examples/testfiles/pdtmc/simple4.pm b/resources/examples/testfiles/pdtmc/simple4.pm deleted file mode 100644 index 1bdc3e004..000000000 --- a/resources/examples/testfiles/pdtmc/simple4.pm +++ /dev/null @@ -1,17 +0,0 @@ -dtmc - -const double p; - -module test - - // local state - s : [0..4] init 0; - - [] s=0 -> p*(1-p) : (s'=1) + (1-p*(1-p)) : (s'=2); - [] s=1 -> p : (s'=3) + (1-p) : (s'=4); - [] s=2 -> (1-p) : (s'=3) + (p) : (s'=4); - [] s=3 -> 1 : (s'=3); - [] s=4 -> 1 : (s'=4); - -endmodule - diff --git a/resources/examples/testfiles/pdtmc/simple2.pm b/resources/examples/testfiles/pdtmc/simple5.pm similarity index 79% rename from resources/examples/testfiles/pdtmc/simple2.pm rename to resources/examples/testfiles/pdtmc/simple5.pm index 33002c6ce..370300659 100644 --- a/resources/examples/testfiles/pdtmc/simple2.pm +++ b/resources/examples/testfiles/pdtmc/simple5.pm @@ -1,6 +1,7 @@ dtmc const double p; +const double q; module test @@ -8,7 +9,7 @@ module test s : [0..4] init 0; [] s=0 -> p : (s'=1) + (1-p) : (s'=2); - [] s=1 -> p : (s'=3) + (1-p) : (s'=4); + [] s=1 -> q : (s'=3) + (1-q) : (s'=4); [] s=2 -> 0.5*p : (s'=3) + (1-0.5*p) : (s'=4); [] s=3 -> 1 : (s'=3); [] s=4 -> 1 : (s'=4); diff --git a/resources/examples/testfiles/pdtmc/zeroconf4.pm b/resources/examples/testfiles/pdtmc/zeroconf4.pm new file mode 100644 index 000000000..4245055de --- /dev/null +++ b/resources/examples/testfiles/pdtmc/zeroconf4.pm @@ -0,0 +1,26 @@ +// Model taken from Daws04 +// This version by Ernst Moritz Hahn (emh@cs.uni-sb.de) + +dtmc + +const double pK; +const double pL; +const int n; + +module main + s: [-2..n+1]; + + [b] (s=-1) -> (s'=-2); + [a] (s=0) -> 1-pL : (s'=-1) + pL : (s'=1); + [a] (s>0) & (s<n+1) -> 1-pK : (s'=0) + pK : (s'=s+1); + +endmodule + +init + s = 0 +endinit + +rewards + [a] true : 1; + [b] true : n-1; +endrewards \ No newline at end of file diff --git a/src/storm-pars-cli/storm-pars.cpp b/src/storm-pars-cli/storm-pars.cpp index 65c68a6c9..5aa2d6af9 100644 --- a/src/storm-pars-cli/storm-pars.cpp +++ b/src/storm-pars-cli/storm-pars.cpp @@ -1,11 +1,11 @@ -#include "storm-pars/analysis/MonotonicityChecker.h" #include "storm-cli-utilities/cli.h" #include "storm-cli-utilities/model-handling.h" #include "storm-pars/api/storm-pars.h" #include "storm-pars/api/region.h" +#include "storm-pars/analysis/MonotonicityHelper.h" #include "storm-pars/modelchecker/instantiation/SparseCtmcInstantiationModelChecker.h" #include "storm-pars/modelchecker/region/SparseParameterLiftingModelChecker.h" @@ -81,8 +81,14 @@ namespace storm { std::vector<storm::storage::ParameterRegion<ValueType>> parseRegions(std::shared_ptr<storm::models::ModelBase> const& model) { std::vector<storm::storage::ParameterRegion<ValueType>> result; auto regionSettings = storm::settings::getModule<storm::settings::modules::RegionSettings>(); + boost::optional<int> splittingThreshold; + if (regionSettings.isSplittingThresholdSet()) { + splittingThreshold = regionSettings.getSplittingThreshold(); + } if (regionSettings.isRegionSet()) { - result = storm::api::parseRegions<ValueType>(regionSettings.getRegionString(), *model); + result = storm::api::parseRegions<ValueType>(regionSettings.getRegionString(), *model, splittingThreshold); + } else if (regionSettings.isRegionBoundSet()) { + result = storm::api::createRegion<ValueType>(regionSettings.getRegionBoundString(), *model, splittingThreshold); } return result; } @@ -157,13 +163,123 @@ namespace storm { return sampleInfo; } + template <typename ValueType> + std::shared_ptr<storm::models::ModelBase> eliminateScc(std::shared_ptr<storm::models::ModelBase> const& model) { + storm::utility::Stopwatch eliminationWatch(true); + std::shared_ptr<storm::models::ModelBase> result; + if (model->isOfType(storm::models::ModelType::Dtmc)) { + STORM_PRINT("Applying scc elimination" << std::endl); + auto sparseModel = model->as<storm::models::sparse::Model<ValueType>>(); + auto matrix = sparseModel->getTransitionMatrix(); + auto backwardsTransitionMatrix = matrix.transpose(); + + storm::storage::StronglyConnectedComponentDecompositionOptions const options; + auto decomposition = storm::storage::StronglyConnectedComponentDecomposition<ValueType>(matrix, options); + + storm::storage::BitVector selectedStates(matrix.getRowCount()); + storm::storage::BitVector selfLoopStates(matrix.getRowCount()); + for (size_t i = 0; i < decomposition.size(); ++i) { + auto scc = decomposition.getBlock(i); + if (scc.size() > 1) { + auto statesScc = scc.getStates(); + std::vector<uint_fast64_t> entryStates; + for (auto state : statesScc) { + auto row = backwardsTransitionMatrix.getRow(state); + bool found = false; + for (auto backState : row) { + if (!scc.containsState(backState.getColumn())) { + found = true; + } + } + if (found) { + entryStates.push_back(state); + selfLoopStates.set(state); + } else { + selectedStates.set(state); + } + } + + if (entryStates.size() != 1) { + STORM_LOG_THROW(entryStates.size() > 1, storm::exceptions::NotImplementedException, + "state elimination not implemented for scc with more than 1 entry points"); + } + } + } + + storm::storage::FlexibleSparseMatrix<ValueType> flexibleMatrix(matrix); + storm::storage::FlexibleSparseMatrix<ValueType> flexibleBackwardTransitions(backwardsTransitionMatrix, true); + auto actionRewards = std::vector<ValueType>(matrix.getRowCount(), storm::utility::zero<ValueType>()); + storm::solver::stateelimination::NondeterministicModelStateEliminator<ValueType> stateEliminator(flexibleMatrix, flexibleBackwardTransitions, actionRewards); + for(auto state : selectedStates) { + stateEliminator.eliminateState(state, true); + } + for (auto state : selfLoopStates) { + auto row = flexibleMatrix.getRow(state); + stateEliminator.eliminateLoop(state); + } + selectedStates.complement(); + auto keptRows = matrix.getRowFilter(selectedStates); + storm::storage::SparseMatrix<ValueType> newTransitionMatrix = flexibleMatrix.createSparseMatrix(keptRows, selectedStates); + // TODO @Jip: note that rewards get lost + result = std::make_shared<storm::models::sparse::Dtmc<ValueType>>(std::move(newTransitionMatrix), sparseModel->getStateLabeling().getSubLabeling(selectedStates)); + + eliminationWatch.stop(); + STORM_PRINT(std::endl << "Time for scc elimination: " << eliminationWatch << "." << std::endl << std::endl); + result->printModelInformationToStream(std::cout); + } else if (model->isOfType(storm::models::ModelType::Mdp)) { + STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Unable to perform SCC elimination for monotonicity analysis on MDP: Not mplemented"); + } else { + STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Unable to perform monotonicity analysis on the provided model type."); + } + return result; + } + + template <typename ValueType> + std::shared_ptr<storm::models::ModelBase> simplifyModel(std::shared_ptr<storm::models::ModelBase> const& model, SymbolicInput const& input) { + storm::utility::Stopwatch simplifyingWatch(true); + std::shared_ptr<storm::models::ModelBase> result; + if (model->isOfType(storm::models::ModelType::Dtmc)) { + storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<ValueType>> simplifier(*(model->template as<storm::models::sparse::Dtmc<ValueType>>())); + + std::vector<std::shared_ptr<storm::logic::Formula const>> formulas = storm::api::extractFormulasFromProperties(input.properties); + STORM_LOG_THROW(formulas.begin()!=formulas.end(), storm::exceptions::NotSupportedException, "Only one formula at the time supported"); + + if (!simplifier.simplify(*(formulas[0]))) { + STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Simplifying the model was not successfull."); + } + result = simplifier.getSimplifiedModel(); + } else if (model->isOfType(storm::models::ModelType::Mdp)) { + storm::transformer::SparseParametricMdpSimplifier<storm::models::sparse::Mdp<ValueType>> simplifier(*(model->template as<storm::models::sparse::Mdp<ValueType>>())); + + std::vector<std::shared_ptr<storm::logic::Formula const>> formulas = storm::api::extractFormulasFromProperties(input.properties); + STORM_LOG_THROW(formulas.begin()!=formulas.end(), storm::exceptions::NotSupportedException, "Only one formula at the time supported"); + + if (!simplifier.simplify(*(formulas[0]))) { + STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Simplifying the model was not successfull."); + } + result = simplifier.getSimplifiedModel(); + } else { + STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Unable to perform monotonicity analysis on the provided model type."); + } + + simplifyingWatch.stop(); + STORM_PRINT(std::endl << "Time for model simplification: " << simplifyingWatch << "." << std::endl << std::endl); + result->printModelInformationToStream(std::cout); + return result; + } + template <typename ValueType> PreprocessResult preprocessSparseModel(std::shared_ptr<storm::models::sparse::Model<ValueType>> const& model, SymbolicInput const& input, storm::cli::ModelProcessingInformation const& mpi) { auto bisimulationSettings = storm::settings::getModule<storm::settings::modules::BisimulationSettings>(); auto parametricSettings = storm::settings::getModule<storm::settings::modules::ParametricSettings>(); auto transformationSettings = storm::settings::getModule<storm::settings::modules::TransformationSettings>(); + auto monSettings = storm::settings::getModule<storm::settings::modules::MonotonicitySettings>(); PreprocessResult result(model, false); + if (monSettings.isMonotonicityAnalysisSet() || parametricSettings.isUseMonotonicitySet()) { + result.model = storm::pars::simplifyModel<ValueType>(result.model, input); + result.changed = true; + } if (result.model->isOfType(storm::models::ModelType::MarkovAutomaton)) { result.model = storm::cli::preprocessSparseMarkovAutomaton(result.model->template as<storm::models::sparse::MarkovAutomaton<ValueType>>()); @@ -195,6 +311,11 @@ namespace storm { result.changed = true; } + if (monSettings.isSccEliminationSet()) { + result.model = storm::pars::eliminateScc<ValueType>(result.model); + result.changed = true; + } + return result; } @@ -424,36 +545,133 @@ namespace storm { } template <typename ValueType> - void computeRegionExtremumWithSparseEngine(std::shared_ptr<storm::models::sparse::Model<ValueType>> const& model, SymbolicInput const& input, std::vector<storm::storage::ParameterRegion<ValueType>> const& regions) { + void analyzeMonotonicity(std::shared_ptr<storm::models::sparse::Model<ValueType>> const& model, SymbolicInput const& input, std::vector<storm::storage::ParameterRegion<ValueType>> const& regions) { + std::ofstream outfile; + auto monSettings = storm::settings::getModule<storm::settings::modules::MonotonicitySettings>(); + + if (monSettings.isExportMonotonicitySet()) { + utility::openFile(monSettings.getExportMonotonicityFilename(), outfile); + } + std::vector<std::shared_ptr<storm::logic::Formula const>> formulas = storm::api::extractFormulasFromProperties(input.properties); + storm::utility::Stopwatch monotonicityWatch(true); + STORM_LOG_THROW(regions.size() <= 1, storm::exceptions::InvalidArgumentException, "Monotonicity analysis only allowed on single region"); + if (!monSettings.isMonSolutionSet()) { + auto monotonicityHelper = storm::analysis::MonotonicityHelper<ValueType, double>(model, formulas, regions, monSettings.getNumberOfSamples(), storm::settings::getModule<storm::settings::modules::GeneralSettings>().getPrecision(), monSettings.isDotOutputSet()); + if (monSettings.isExportMonotonicitySet()) { + monotonicityHelper.checkMonotonicityInBuild(outfile, monSettings.isUsePLABoundsSet(), monSettings.getDotOutputFilename()); + } else { + monotonicityHelper.checkMonotonicityInBuild(std::cout, monSettings.isUsePLABoundsSet(), monSettings.getDotOutputFilename()); + } + } else { + // Checking monotonicity based on solution function + + auto parametricSettings = storm::settings::getModule<storm::settings::modules::ParametricSettings>(); + auto regionSettings = storm::settings::getModule<storm::settings::modules::RegionSettings>(); + auto engine = regionSettings.getRegionCheckEngine(); + + std::function<std::unique_ptr<storm::modelchecker::CheckResult>(std::shared_ptr<storm::logic::Formula const> const& formula)> verificationCallback; + std::function<void(std::unique_ptr<storm::modelchecker::CheckResult> const&)> postprocessingCallback; + + // Check the given set of regions with or without refinement + verificationCallback = [&] (std::shared_ptr<storm::logic::Formula const> const& formula) { + std::unique_ptr<storm::modelchecker::CheckResult> result = storm::api::verifyWithSparseEngine<ValueType>(model, storm::api::createTask<ValueType>(formula, true)); + return result; + }; + + for (auto & property : input.properties) { + auto result = verificationCallback(property.getRawFormula())->asExplicitQuantitativeCheckResult<ValueType>().getValueVector(); + ValueType valuation; + + auto states= model->getInitialStates(); + for (auto state : states) { + valuation += result[state]; + } + + storm::analysis::MonotonicityResult<storm::RationalFunctionVariable> monRes; + for (auto & var : storm::models::sparse::getProbabilityParameters(*model)) { + auto res = storm::analysis::MonotonicityChecker<ValueType>::checkDerivative(valuation.derivative(var), regions[0]); + + if (res.first && res.second) { + monRes.addMonotonicityResult(var, analysis::MonotonicityResult<storm::RationalFunctionVariable>::Monotonicity::Constant); + } else if (res.first) { + monRes.addMonotonicityResult(var, analysis::MonotonicityResult<storm::RationalFunctionVariable>::Monotonicity::Incr); + } else if (res.second) { + monRes.addMonotonicityResult(var, analysis::MonotonicityResult<storm::RationalFunctionVariable>::Monotonicity::Decr); + } else { + monRes.addMonotonicityResult(var, analysis::MonotonicityResult<storm::RationalFunctionVariable>::Monotonicity::Not); + } + } + if (monSettings.isExportMonotonicitySet()) { + outfile << monRes.toString(); + } else { + STORM_PRINT(monRes.toString()); + } + } + } + + if (monSettings.isExportMonotonicitySet()) { + utility::closeFile(outfile); + } + + monotonicityWatch.stop(); + STORM_PRINT(std::endl << "Total time for monotonicity checking: " << monotonicityWatch << "." << std::endl << std::endl); + return; + } + + template <typename ValueType> + void computeRegionExtremumWithSparseEngine(std::shared_ptr<storm::models::sparse::Model<ValueType>> const& model, SymbolicInput const& input, std::vector<storm::storage::ParameterRegion<ValueType>> const& regions, storm::api::MonotonicitySetting monotonicitySettings = storm::api::MonotonicitySetting(), boost::optional<std::pair<std::set<typename storm::storage::ParameterRegion<ValueType>::VariableType>, std::set<typename storm::storage::ParameterRegion<ValueType>::VariableType>>>& monotoneParameters = boost::none) { STORM_LOG_ASSERT(!regions.empty(), "Can not analyze an empty set of regions."); auto regionSettings = storm::settings::getModule<storm::settings::modules::RegionSettings>(); + auto monSettings = storm::settings::getModule<storm::settings::modules::MonotonicitySettings>(); auto engine = regionSettings.getRegionCheckEngine(); storm::solver::OptimizationDirection direction = regionSettings.getExtremumDirection(); ValueType precision = storm::utility::convertNumber<ValueType>(regionSettings.getExtremumValuePrecision()); + bool generateSplitEstimates = regionSettings.isSplittingThresholdSet(); for (auto const& property : input.properties) { for (auto const& region : regions) { - STORM_PRINT_AND_LOG("Computing extremal value for property " << property.getName() << ": " << *property.getRawFormula() << " within region " << region << "..." << std::endl); + if (monotonicitySettings.useMonotonicity) { + STORM_PRINT_AND_LOG("Computing extremal value for property " << property.getName() << ": " + << *property.getRawFormula() + << " within region " << region + << " and using monotonicity ..." << std::endl); + } else { + STORM_PRINT_AND_LOG("Computing extremal value for property " << property.getName() << ": " + << *property.getRawFormula() + << " within region " << region + << "..." << std::endl); + } storm::utility::Stopwatch watch(true); - auto valueValuation = storm::api::computeExtremalValue<ValueType>(model, storm::api::createTask<ValueType>(property.getRawFormula(), true), region, engine, direction, precision); - watch.stop(); - std::stringstream valuationStr; - bool first = true; - for (auto const& v : valueValuation.second) { - if (first) { - first = false; + // TODO: hier eventueel checkExtremalValue van maken + if (regionSettings.isExtremumSuggestionSet()) { + ValueType suggestion = storm::utility::convertNumber<ValueType>(regionSettings.getExtremumSuggestion()); + if (storm::api::checkExtremalValue<ValueType>(model, storm::api::createTask<ValueType>(property.getRawFormula(), true), region, engine, direction, precision, suggestion, monotonicitySettings, generateSplitEstimates, monotoneParameters)) { + STORM_PRINT_AND_LOG(suggestion << " is the extremum "); } else { - valuationStr << ", "; + STORM_PRINT_AND_LOG(suggestion << " is NOT the extremum "); } - valuationStr << v.first << "=" << v.second; + + } else { + auto valueValuation = storm::api::computeExtremalValue<ValueType>(model, storm::api::createTask<ValueType>(property.getRawFormula(), true), region, engine, direction, precision, monotonicitySettings, generateSplitEstimates, monotoneParameters); + watch.stop(); + std::stringstream valuationStr; + bool first = true; + for (auto const& v : valueValuation.second) { + if (first) { + first = false; + } else { + valuationStr << ", "; + } + valuationStr << v.first << "=" << v.second; + } + STORM_PRINT_AND_LOG("Result at initial state: " << valueValuation.first << " ( approx. " << storm::utility::convertNumber<double>(valueValuation.first) << ") at [" << valuationStr.str() << "]." << std::endl) + STORM_PRINT_AND_LOG("Time for model checking: " << watch << "." << std::endl); } - STORM_PRINT_AND_LOG("Result at initial state: " << valueValuation.first << " ( approx. " << storm::utility::convertNumber<double>(valueValuation.first) << ") at [" << valuationStr.str() << "]." << std::endl) - STORM_PRINT_AND_LOG("Time for model checking: " << watch << "." << std::endl); } } } template <typename ValueType> - void verifyRegionsWithSparseEngine(std::shared_ptr<storm::models::sparse::Model<ValueType>> const& model, SymbolicInput const& input, std::vector<storm::storage::ParameterRegion<ValueType>> const& regions) { + void verifyRegionsWithSparseEngine(std::shared_ptr<storm::models::sparse::Model<ValueType>> const& model, SymbolicInput const& input, std::vector<storm::storage::ParameterRegion<ValueType>> const& regions, storm::api::MonotonicitySetting monotonicitySettings = storm::api::MonotonicitySetting(), uint64_t monThresh = 0) { STORM_LOG_ASSERT(!regions.empty(), "Can not analyze an empty set of regions."); auto parametricSettings = storm::settings::getModule<storm::settings::modules::ParametricSettings>(); @@ -475,6 +693,9 @@ namespace storm { } auto engine = regionSettings.getRegionCheckEngine(); STORM_PRINT_AND_LOG(" using " << engine); + if (monotonicitySettings.useMonotonicity) { + STORM_PRINT_AND_LOG(" with local monotonicity and"); + } // Check the given set of regions with or without refinement if (regionSettings.isRefineSet()) { @@ -486,7 +707,8 @@ namespace storm { if (regionSettings.isDepthLimitSet()) { optionalDepthLimit = regionSettings.getDepthLimit(); } - std::unique_ptr<storm::modelchecker::RegionRefinementCheckResult<ValueType>> result = storm::api::checkAndRefineRegionWithSparseEngine<ValueType>(model, storm::api::createTask<ValueType>(formula, true), regions.front(), engine, refinementThreshold, optionalDepthLimit, regionSettings.getHypothesis()); + // TODO @Jip: change allow model simplification when not using monotonicity, for benchmarking purposes simplification is moved forward. + std::unique_ptr<storm::modelchecker::RegionRefinementCheckResult<ValueType>> result = storm::api::checkAndRefineRegionWithSparseEngine<ValueType>(model, storm::api::createTask<ValueType>(formula, true), regions.front(), engine, refinementThreshold, optionalDepthLimit, regionSettings.getHypothesis(), false, monotonicitySettings, monThresh); return result; }; } else { @@ -507,15 +729,21 @@ namespace storm { } template <typename ValueType> - void verifyWithSparseEngine(std::shared_ptr<storm::models::sparse::Model<ValueType>> const& model, SymbolicInput const& input, std::vector<storm::storage::ParameterRegion<ValueType>> const& regions, SampleInformation<ValueType> const& samples) { + void verifyWithSparseEngine(std::shared_ptr<storm::models::sparse::Model<ValueType>> const& model, SymbolicInput const& input, std::vector<storm::storage::ParameterRegion<ValueType>> const& regions, SampleInformation<ValueType> const& samples, storm::api::MonotonicitySetting monotonicitySettings = storm::api::MonotonicitySetting(), boost::optional<std::pair<std::set<typename storm::storage::ParameterRegion<ValueType>::VariableType>, std::set<typename storm::storage::ParameterRegion<ValueType>::VariableType>>>& monotoneParameters = boost::none, uint64_t monThresh = 0) { if (regions.empty()) { storm::pars::verifyPropertiesWithSparseEngine(model, input, samples); } else { auto regionSettings = storm::settings::getModule<storm::settings::modules::RegionSettings>(); - if (regionSettings.isExtremumSet()) { - storm::pars::computeRegionExtremumWithSparseEngine(model, input, regions); + auto monSettings = storm::settings::getModule<storm::settings::modules::MonotonicitySettings>(); + if (monSettings.isMonotonicityAnalysisSet()) { + storm::pars::analyzeMonotonicity(model, input, regions); + } else if (regionSettings.isExtremumSet()) { + storm::pars::computeRegionExtremumWithSparseEngine(model, input, regions, monotonicitySettings, monotoneParameters); } else { - storm::pars::verifyRegionsWithSparseEngine(model, input, regions); + assert (monotoneParameters == boost::none); + assert (!monotonicitySettings.useOnlyGlobalMonotonicity); + assert (!monotonicitySettings.useBoundsFromPLA); + storm::pars::verifyRegionsWithSparseEngine(model, input, regions, monotonicitySettings, monThresh); } } } @@ -554,10 +782,12 @@ namespace storm { } template <storm::dd::DdType DdType, typename ValueType> - void verifyParametricModel(std::shared_ptr<storm::models::ModelBase> const& model, SymbolicInput const& input, std::vector<storm::storage::ParameterRegion<ValueType>> const& regions, SampleInformation<ValueType> const& samples) { + void verifyParametricModel(std::shared_ptr<storm::models::ModelBase> const& model, SymbolicInput const& input, std::vector<storm::storage::ParameterRegion<ValueType>> const& regions, SampleInformation<ValueType> const& samples, storm::api::MonotonicitySetting monotonicitySettings = storm::api::MonotonicitySetting(), boost::optional<std::pair<std::set<typename storm::storage::ParameterRegion<ValueType>::VariableType>, std::set<typename storm::storage::ParameterRegion<ValueType>::VariableType>>>& monotoneParameters = boost::none, uint64_t monThresh = 0) { if (model->isSparseModel()) { - storm::pars::verifyWithSparseEngine<ValueType>(model->as<storm::models::sparse::Model<ValueType>>(), input, regions, samples); + storm::pars::verifyWithSparseEngine<ValueType>(model->as<storm::models::sparse::Model<ValueType>>(), input, regions, samples, monotonicitySettings, monotoneParameters, monThresh); } else { + assert (!monotonicitySettings.useMonotonicity); + assert (monotoneParameters == boost::none); storm::pars::verifyWithDdEngine<DdType, ValueType>(model->as<storm::models::symbolic::Model<DdType, ValueType>>(), input, regions, samples); } } @@ -582,40 +812,6 @@ namespace storm { STORM_LOG_THROW(model || input.properties.empty(), storm::exceptions::InvalidSettingsException, "No input model."); - if (monSettings.isMonotonicityAnalysisSet()) { - // Simplify the model - storm::utility::Stopwatch simplifyingWatch(true); - if (model->isOfType(storm::models::ModelType::Dtmc)) { - auto consideredModel = (model->as<storm::models::sparse::Dtmc<ValueType>>()); - auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<ValueType>>(*consideredModel); - - std::vector<std::shared_ptr<storm::logic::Formula const>> formulas = storm::api::extractFormulasFromProperties(input.properties); - STORM_LOG_THROW(formulas.begin()!=formulas.end(), storm::exceptions::NotSupportedException, "Only one formula at the time supported"); - - if (!simplifier.simplify(*(formulas[0]))) { - STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Simplifying the model was not successfull."); - } - model = simplifier.getSimplifiedModel(); - } else if (model->isOfType(storm::models::ModelType::Mdp)) { - auto consideredModel = (model->as<storm::models::sparse::Mdp<ValueType>>()); - auto simplifier = storm::transformer::SparseParametricMdpSimplifier<storm::models::sparse::Mdp<ValueType>>(*consideredModel); - - std::vector<std::shared_ptr<storm::logic::Formula const>> formulas = storm::api::extractFormulasFromProperties(input.properties); - STORM_LOG_THROW(formulas.begin()!=formulas.end(), storm::exceptions::NotSupportedException, "Only one formula at the time supported"); - - if (!simplifier.simplify(*(formulas[0]))) { - STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Simplifying the model was not successfull."); - } - model = simplifier.getSimplifiedModel(); - } else { - STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Unable to perform monotonicity analysis on the provided model type."); - } - - simplifyingWatch.stop(); - STORM_PRINT(std::endl << "Time for model simplification: " << simplifyingWatch << "." << std::endl << std::endl); - model->printModelInformationToStream(std::cout); - } - if (model) { auto preprocessingResult = storm::pars::preprocessModel<DdType, ValueType>(model, input, mpi); if (preprocessingResult.changed) { @@ -636,103 +832,13 @@ namespace storm { } } - - if (model && monSettings.isSccEliminationSet()) { - storm::utility::Stopwatch eliminationWatch(true); - if (model->isOfType(storm::models::ModelType::Dtmc)) { - STORM_PRINT("Applying scc elimination" << std::endl); - auto sparseModel = model->as<storm::models::sparse::Model<ValueType>>(); - auto matrix = sparseModel->getTransitionMatrix(); - auto backwardsTransitionMatrix = matrix.transpose(); - - storm::storage::StronglyConnectedComponentDecompositionOptions const options; - auto decomposition = storm::storage::StronglyConnectedComponentDecomposition<ValueType>(matrix, options); - - storm::storage::BitVector selectedStates(matrix.getRowCount()); - storm::storage::BitVector selfLoopStates(matrix.getRowCount()); - for (size_t i = 0; i < decomposition.size(); ++i) { - auto scc = decomposition.getBlock(i); - if (scc.size() > 1) { - auto statesScc = scc.getStates(); - std::vector<uint_fast64_t> entryStates; - for (auto state : statesScc) { - auto row = backwardsTransitionMatrix.getRow(state); - bool found = false; - for (auto backState : row) { - if (!scc.containsState(backState.getColumn())) { - found = true; - } - } - if (found) { - entryStates.push_back(state); - selfLoopStates.set(state); - } else { - selectedStates.set(state); - } - } - - if (entryStates.size() != 1) { - STORM_LOG_THROW(entryStates.size() > 1, storm::exceptions::NotImplementedException, - "state elimination not implemented for scc with more than 1 entry points"); - } - } - } - - storm::storage::FlexibleSparseMatrix<ValueType> flexibleMatrix(matrix); - storm::storage::FlexibleSparseMatrix<ValueType> flexibleBackwardTransitions(backwardsTransitionMatrix, true); - auto actionRewards = std::vector<ValueType>(matrix.getRowCount(), storm::utility::zero<ValueType>()); - storm::solver::stateelimination::NondeterministicModelStateEliminator<ValueType> stateEliminator(flexibleMatrix, flexibleBackwardTransitions, actionRewards); - for(auto state : selectedStates) { - stateEliminator.eliminateState(state, true); - } - for (auto state : selfLoopStates) { - auto row = flexibleMatrix.getRow(state); - stateEliminator.eliminateLoop(state); - } - selectedStates.complement(); - auto keptRows = matrix.getRowFilter(selectedStates); - storm::storage::SparseMatrix<ValueType> newTransitionMatrix = flexibleMatrix.createSparseMatrix(keptRows, selectedStates); - // TODO: note that rewards get lost - model = std::make_shared<storm::models::sparse::Dtmc<ValueType>>(std::move(newTransitionMatrix), sparseModel->getStateLabeling().getSubLabeling(selectedStates)); - - eliminationWatch.stop(); - STORM_PRINT(std::endl << "Time for scc elimination: " << eliminationWatch << "." << std::endl << std::endl); - model->printModelInformationToStream(std::cout); - } else if (model->isOfType(storm::models::ModelType::Mdp)) { - STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Unable to perform SCC elimination for monotonicity analysis on MDP: Not mplemented"); - } else { - STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Unable to perform monotonicity analysis on the provided model type."); - } - - } - std::vector<storm::storage::ParameterRegion<ValueType>> regions = parseRegions<ValueType>(model); - if (monSettings.isMonotonicityAnalysisSet()) { - std::vector<std::shared_ptr<storm::logic::Formula const>> formulas = storm::api::extractFormulasFromProperties(input.properties); - // Monotonicity - storm::utility::Stopwatch monotonicityWatch(true); - - STORM_LOG_THROW(regions.size() <= 1, storm::exceptions::InvalidArgumentException, "Monotonicity analysis only allowed on single region"); - storm::analysis::MonotonicityChecker<ValueType> monotonicityChecker = storm::analysis::MonotonicityChecker<ValueType>(model, formulas, regions, monSettings.isValidateAssumptionsSet(), monSettings.getNumberOfSamples(), monSettings.getMonotonicityAnalysisPrecision()); - if (ioSettings.isExportMonotonicitySet()) { - std::ofstream outfile; - utility::openFile(ioSettings.getExportMonotonicityFilename(), outfile); - monotonicityChecker.checkMonotonicity(outfile); - utility::closeFile(outfile); - } else { - monotonicityChecker.checkMonotonicity(std::cout); - } - monotonicityWatch.stop(); - STORM_PRINT(std::endl << "Total time for monotonicity checking: " << monotonicityWatch << "." << std::endl - << std::endl); - return; - } - std::string samplesAsString = parSettings.getSamples(); SampleInformation<ValueType> samples; if (!samplesAsString.empty()) { - samples = parseSamples<ValueType>(model, samplesAsString, parSettings.isSamplesAreGraphPreservingSet()); + samples = parseSamples<ValueType>(model, samplesAsString, + parSettings.isSamplesAreGraphPreservingSet()); samples.exact = parSettings.isSampleExactSet(); } @@ -741,13 +847,24 @@ namespace storm { } if (parSettings.onlyObtainConstraints()) { - STORM_LOG_THROW(parSettings.exportResultToFile(), storm::exceptions::InvalidSettingsException, "When computing constraints, export path has to be specified."); - storm::api::exportParametricResultToFile<ValueType>(boost::none, storm::analysis::ConstraintCollector<ValueType>(*(model->as<storm::models::sparse::Model<ValueType>>())), parSettings.exportResultPath()); + STORM_LOG_THROW(parSettings.exportResultToFile(), storm::exceptions::InvalidSettingsException, + "When computing constraints, export path has to be specified."); + storm::api::exportParametricResultToFile<ValueType>(boost::none, + storm::analysis::ConstraintCollector<ValueType>( + *(model->as<storm::models::sparse::Model<ValueType>>())), + parSettings.exportResultPath()); return; } if (model) { - verifyParametricModel<DdType, ValueType>(model, input, regions, samples); + boost::optional<std::pair<std::set<storm::RationalFunctionVariable>, std::set<storm::RationalFunctionVariable>>> monotoneParameters; + if (monSettings.isMonotoneParametersSet()) { + monotoneParameters = std::move( + storm::api::parseMonotoneParameters<ValueType>(monSettings.getMonotoneParameterFilename(), + model->as<storm::models::sparse::Model<ValueType>>())); + } +// TODO: is onlyGlobalSet was used here + verifyParametricModel<DdType, ValueType>(model, input, regions, samples, storm::api::MonotonicitySetting(parSettings.isUseMonotonicitySet(), false, monSettings.isUsePLABoundsSet()), monotoneParameters, monSettings.getMonotonicityThreshold()); } } diff --git a/src/storm-pars/analysis/AssumptionChecker.cpp b/src/storm-pars/analysis/AssumptionChecker.cpp index fce4596ab..e83c323df 100644 --- a/src/storm-pars/analysis/AssumptionChecker.cpp +++ b/src/storm-pars/analysis/AssumptionChecker.cpp @@ -2,33 +2,30 @@ #include "AssumptionChecker.h" #include "storm-pars/utility/ModelInstantiator.h" -#include "storm-pars/analysis/MonotonicityChecker.h" - -#include "storm/environment/Environment.h" #include "storm/exceptions/NotSupportedException.h" #include "storm/modelchecker/CheckTask.h" #include "storm/modelchecker/prctl/SparseDtmcPrctlModelChecker.h" -#include "storm/modelchecker/prctl/SparseMdpPrctlModelChecker.h" #include "storm/modelchecker/results/CheckResult.h" #include "storm/modelchecker/results/ExplicitQuantitativeCheckResult.h" #include "storm/storage/expressions/SimpleValuation.h" #include "storm/storage/expressions/ExpressionManager.h" #include "storm/storage/expressions/VariableExpression.h" #include "storm/storage/expressions/RationalFunctionToExpression.h" -#include "storm/utility/constants.h" namespace storm { namespace analysis { - template <typename ValueType> - AssumptionChecker<ValueType>::AssumptionChecker(std::shared_ptr<logic::Formula const> formula, std::shared_ptr<models::sparse::Dtmc<ValueType>> model, storm::storage::ParameterRegion<ValueType> region, uint_fast64_t numberOfSamples) { - this->formula = formula; - this->matrix = model->getTransitionMatrix(); - this->region = region; + template <typename ValueType, typename ConstantType> + AssumptionChecker<ValueType, ConstantType>::AssumptionChecker(storage::SparseMatrix<ValueType> matrix){ + this->matrix = matrix; + useSamples = false; + } + template <typename ValueType, typename ConstantType> + void AssumptionChecker<ValueType, ConstantType>::initializeCheckingOnSamples(std::shared_ptr<logic::Formula const> formula, std::shared_ptr<models::sparse::Dtmc<ValueType>> model, storage::ParameterRegion<ValueType> region, uint_fast64_t numberOfSamples) { // Create sample points - auto instantiator = utility::ModelInstantiator<models::sparse::Dtmc<ValueType>, models::sparse::Dtmc<double>>(*model); + auto instantiator = utility::ModelInstantiator<models::sparse::Dtmc<ValueType>, models::sparse::Dtmc<ConstantType>>(*model); auto matrix = model->getTransitionMatrix(); - std::set<typename utility::parametric::VariableType<ValueType>::type> variables = models::sparse::getProbabilityParameters(*model); + std::set<VariableType> variables = models::sparse::getProbabilityParameters(*model); for (uint_fast64_t i = 0; i < numberOfSamples; ++i) { auto valuation = utility::parametric::Valuation<ValueType>(); @@ -36,106 +33,77 @@ namespace storm { auto lb = region.getLowerBoundary(var.name()); auto ub = region.getUpperBoundary(var.name()); // Creates samples between lb and ub, that is: lb, lb + (ub-lb)/(#samples -1), lb + 2* (ub-lb)/(#samples -1), ..., ub - auto val = - std::pair<typename utility::parametric::VariableType<ValueType>::type, typename utility::parametric::CoefficientType<ValueType>::type> - (var,utility::convertNumber<typename utility::parametric::CoefficientType<ValueType>::type>(lb + i*(ub-lb)/(numberOfSamples-1))); + auto val = std::pair<VariableType, CoefficientType>(var, utility::convertNumber<CoefficientType>(lb + i * (ub - lb) / (numberOfSamples - 1))); valuation.insert(val); } - models::sparse::Dtmc<double> sampleModel = instantiator.instantiate(valuation); - auto checker = modelchecker::SparseDtmcPrctlModelChecker<models::sparse::Dtmc<double>>(sampleModel); + models::sparse::Dtmc<ConstantType> sampleModel = instantiator.instantiate(valuation); + auto checker = modelchecker::SparseDtmcPrctlModelChecker<models::sparse::Dtmc<ConstantType>>(sampleModel); std::unique_ptr<modelchecker::CheckResult> checkResult; if (formula->isProbabilityOperatorFormula() && formula->asProbabilityOperatorFormula().getSubformula().isUntilFormula()) { - const modelchecker::CheckTask<logic::UntilFormula, double> checkTask = modelchecker::CheckTask<logic::UntilFormula, double>( + const modelchecker::CheckTask<logic::UntilFormula, ConstantType> checkTask = modelchecker::CheckTask<logic::UntilFormula, ConstantType>( (*formula).asProbabilityOperatorFormula().getSubformula().asUntilFormula()); checkResult = checker.computeUntilProbabilities(Environment(), checkTask); } else if (formula->isProbabilityOperatorFormula() && formula->asProbabilityOperatorFormula().getSubformula().isEventuallyFormula()) { - const modelchecker::CheckTask<logic::EventuallyFormula, double> checkTask = modelchecker::CheckTask<logic::EventuallyFormula, double>( + const modelchecker::CheckTask<logic::EventuallyFormula, ConstantType> checkTask = modelchecker::CheckTask<logic::EventuallyFormula, ConstantType>( (*formula).asProbabilityOperatorFormula().getSubformula().asEventuallyFormula()); checkResult = checker.computeReachabilityProbabilities(Environment(), checkTask); } else { STORM_LOG_THROW(false, exceptions::NotSupportedException, "Expecting until or eventually formula"); } - auto quantitativeResult = checkResult->asExplicitQuantitativeCheckResult<double>(); - std::vector<double> values = quantitativeResult.getValueVector(); + auto quantitativeResult = checkResult->asExplicitQuantitativeCheckResult<ConstantType>(); + std::vector<ConstantType> values = quantitativeResult.getValueVector(); samples.push_back(values); } + useSamples = true; } - template <typename ValueType> - AssumptionChecker<ValueType>::AssumptionChecker(std::shared_ptr<logic::Formula const> formula, std::shared_ptr<models::sparse::Mdp<ValueType>> model, uint_fast64_t numberOfSamples) { - STORM_LOG_THROW(false, exceptions::NotImplementedException, "Assumption checking for mdps not yet implemented"); - this->formula = formula; - this->matrix = model->getTransitionMatrix(); - - // Create sample points - auto instantiator = utility::ModelInstantiator<models::sparse::Mdp<ValueType>, models::sparse::Mdp<double>>(*model); - auto matrix = model->getTransitionMatrix(); - std::set<typename utility::parametric::VariableType<ValueType>::type> variables = models::sparse::getProbabilityParameters(*model); - - for (auto i = 0; i < numberOfSamples; ++i) { - auto valuation = utility::parametric::Valuation<ValueType>(); - for (auto itr = variables.begin(); itr != variables.end(); ++itr) { - auto val = std::pair<typename utility::parametric::VariableType<ValueType>::type, - typename utility::parametric::CoefficientType<ValueType>::type>((*itr), utility::convertNumber<typename utility::parametric::CoefficientType<ValueType>::type>(boost::lexical_cast<std::string>((i+1)/(double (numberOfSamples + 1))))); - valuation.insert(val); - } - models::sparse::Mdp<double> sampleModel = instantiator.instantiate(valuation); - auto checker = modelchecker::SparseMdpPrctlModelChecker<models::sparse::Mdp<double>>(sampleModel); - std::unique_ptr<modelchecker::CheckResult> checkResult; - if (formula->isProbabilityOperatorFormula() && - formula->asProbabilityOperatorFormula().getSubformula().isUntilFormula()) { - const modelchecker::CheckTask<logic::UntilFormula, double> checkTask = modelchecker::CheckTask<logic::UntilFormula, double>( - (*formula).asProbabilityOperatorFormula().getSubformula().asUntilFormula()); - checkResult = checker.computeUntilProbabilities(Environment(), checkTask); - } else if (formula->isProbabilityOperatorFormula() && - formula->asProbabilityOperatorFormula().getSubformula().isEventuallyFormula()) { - const modelchecker::CheckTask<logic::EventuallyFormula, double> checkTask = modelchecker::CheckTask<logic::EventuallyFormula, double>( - (*formula).asProbabilityOperatorFormula().getSubformula().asEventuallyFormula()); - checkResult = checker.computeReachabilityProbabilities(Environment(), checkTask); - } else { - STORM_LOG_THROW(false, exceptions::NotSupportedException, - "Expecting until or eventually formula"); - } - auto quantitativeResult = checkResult->asExplicitQuantitativeCheckResult<double>(); - std::vector<double> values = quantitativeResult.getValueVector(); - samples.push_back(values); - } + template <typename ValueType, typename ConstantType> + void AssumptionChecker<ValueType, ConstantType>::setSampleValues(std::vector<std::vector<ConstantType>> samples) { + this->samples = samples; + useSamples = true; } - template <typename ValueType> - AssumptionStatus AssumptionChecker<ValueType>::checkOnSamples(std::shared_ptr<expressions::BinaryRelationExpression> assumption) { - auto result = AssumptionStatus::UNKNOWN; - std::set<expressions::Variable> vars = std::set<expressions::Variable>({}); - assumption->gatherVariables(vars); - for (auto itr = samples.begin(); result == AssumptionStatus::UNKNOWN && itr != samples.end(); ++itr) { - std::shared_ptr<expressions::ExpressionManager const> manager = assumption->getManager().getSharedPointer(); - auto valuation = expressions::SimpleValuation(manager); - auto values = (*itr); - for (auto var = vars.begin(); result == AssumptionStatus::UNKNOWN && var != vars.end(); ++var) { - expressions::Variable par = *var; - auto index = std::stoi(par.getName()); - valuation.setRationalValue(par, values[index]); - } - assert(assumption->hasBooleanType()); - if (!assumption->evaluateAsBool(&valuation)) { - result = AssumptionStatus::INVALID; - } - } - return result; + template <typename ValueType, typename ConstantType> + AssumptionChecker<ValueType, ConstantType>::AssumptionChecker(std::shared_ptr<logic::Formula const> formula, std::shared_ptr<models::sparse::Mdp<ValueType>> model, uint_fast64_t numberOfSamples) { + STORM_LOG_THROW(false, exceptions::NotSupportedException, "Assumption checking for mdps not yet implemented"); } - template <typename ValueType> - AssumptionStatus AssumptionChecker<ValueType>::validateAssumption(std::shared_ptr<expressions::BinaryRelationExpression> assumption, Order* order) { + template <typename ValueType, typename ConstantType> + AssumptionStatus AssumptionChecker<ValueType, ConstantType>::validateAssumption(uint_fast64_t val1, uint_fast64_t val2,std::shared_ptr<expressions::BinaryRelationExpression> assumption, std::shared_ptr<Order> order, storage::ParameterRegion<ValueType> region, std::vector<ConstantType>const minValues, std::vector<ConstantType>const maxValues) const { // First check if based on sample points the assumption can be discharged - auto result = checkOnSamples(assumption); + assert (val1 == std::stoi(assumption->getFirstOperand()->asVariableExpression().getVariableName())); + assert (val2 == std::stoi(assumption->getSecondOperand()->asVariableExpression().getVariableName())); + AssumptionStatus result = AssumptionStatus::UNKNOWN; + if (useSamples) { + result = checkOnSamples(assumption); + } assert (result != AssumptionStatus::VALID); + if (minValues.size() != 0) { + if (assumption->getRelationType() == expressions::BinaryRelationExpression::RelationType::Greater) { + if (minValues[val1] > maxValues[val2]) { + return AssumptionStatus::VALID; + } else if (minValues[val1] == maxValues[val2] && minValues[val1] == maxValues[val1] && minValues[val2] == maxValues[val2]) { + return AssumptionStatus::INVALID; + } else if (minValues[val2] > maxValues[val1]) { + return AssumptionStatus::INVALID; + } + } else { + if (minValues[val1] == maxValues[val2] && minValues[val1] == maxValues[val1] && minValues[val2] == maxValues[val2]) { + return AssumptionStatus::VALID; + } else if (minValues[val1] > maxValues[val2]) { + return AssumptionStatus::INVALID; + } else if (minValues[val2] > maxValues[val1]) { + return AssumptionStatus::INVALID; + } + } + } + if (result == AssumptionStatus::UNKNOWN) { - // If result from sample checking was unknown, the assumption might hold, so we continue, - // otherwise we return INVALID + // If result from sample checking was unknown, the assumption might hold std::set<expressions::Variable> vars = std::set<expressions::Variable>({}); assumption->gatherVariables(vars); @@ -145,141 +113,111 @@ namespace storm { expressions::BinaryRelationExpression::RelationType::Equal, exceptions::NotSupportedException, "Only Greater Or Equal assumptions supported"); - - // Row with successors of the first state - auto row1 = matrix.getRow( - std::stoi(assumption->getFirstOperand()->asVariableExpression().getVariableName())); - // Row with successors of the second state - auto row2 = matrix.getRow( - std::stoi(assumption->getSecondOperand()->asVariableExpression().getVariableName())); - - if (row1.getNumberOfEntries() == 2 && row2.getNumberOfEntries() == 2) { - // If the states have the same successors for which we know the position in the order - // We can check with a function if the assumption holds - - auto state1succ1 = row1.begin(); - auto state1succ2 = (++row1.begin()); - auto state2succ1 = row2.begin(); - auto state2succ2 = (++row2.begin()); - - if (state1succ1->getColumn() == state2succ2->getColumn() - && state2succ1->getColumn() == state1succ2->getColumn()) { - std::swap(state1succ1, state1succ2); - } - - if (state1succ1->getColumn() == state2succ1->getColumn() && state1succ2->getColumn() == state2succ2->getColumn()) { - if (assumption->getRelationType() == expressions::BinaryRelationExpression::RelationType::Greater - && order->compare(state1succ1->getColumn(), state1succ2->getColumn()) != Order::NodeComparison::UNKNOWN) { - // The assumption should be the greater assumption - // If the result is unknown, we cannot compare, also SMTSolver will not help - result = validateAssumptionSMTSolver(assumption, order); - -// result = validateAssumptionFunction(order, state1succ1, state1succ2, state2succ1, -// state2succ2); - } else if (assumption->getRelationType() == expressions::BinaryRelationExpression::RelationType::Equal) { - // The assumption is equal, the successors are the same, - // so if the probability of reaching the successors is the same, we have a valid assumption - if (state1succ1->getValue() == state2succ1->getValue()) { - result = AssumptionStatus::VALID; - } - } else { - result = AssumptionStatus::UNKNOWN; - } - } else { - result = validateAssumptionSMTSolver(assumption, order); - } - } else { - result = validateAssumptionSMTSolver(assumption, order); - } + result = validateAssumptionSMTSolver(val1, val2, assumption, order, region, minValues, maxValues); } return result; } - template <typename ValueType> - AssumptionStatus AssumptionChecker<ValueType>::validateAssumptionFunction(Order* order, - typename storage::SparseMatrix<ValueType>::iterator state1succ1, - typename storage::SparseMatrix<ValueType>::iterator state1succ2, - typename storage::SparseMatrix<ValueType>::iterator state2succ1, - typename storage::SparseMatrix<ValueType>::iterator state2succ2) { - assert((state1succ1->getColumn() == state2succ1->getColumn() - && state1succ2->getColumn() == state2succ2->getColumn()) - || (state1succ1->getColumn() == state2succ2->getColumn() - && state1succ2->getColumn() == state2succ1->getColumn())); - - AssumptionStatus result; - - // Calculate the difference in probability for the "highest" successor state - ValueType prob; - auto comp = order->compare(state1succ1->getColumn(), state1succ2->getColumn()); - assert (comp == Order::NodeComparison::ABOVE || comp == Order::NodeComparison::BELOW); - if (comp == Order::NodeComparison::ABOVE) { - prob = state1succ1->getValue() - state2succ1->getValue(); - } else if (comp == Order::NodeComparison::BELOW) { - prob = state1succ2->getValue() - state2succ2->getValue(); - } - - auto vars = prob.gatherVariables(); - - // If the result in monotone increasing (decreasing), then choose 0 (1) for the substitutions - // This will give the smallest result - std::map<typename utility::parametric::VariableType<ValueType>::type, typename utility::parametric::CoefficientType<ValueType>::type> substitutions; - for (auto var : vars) { - auto monotonicity = MonotonicityChecker<ValueType>::checkDerivative(prob.derivative(var), region); - if (monotonicity.first) { - // monotone increasing - substitutions[var] = 0; - } else if (monotonicity.second) { - // monotone increasing - substitutions[var] = 1; - } else { - result = AssumptionStatus::UNKNOWN; + template <typename ValueType, typename ConstantType> + AssumptionStatus AssumptionChecker<ValueType, ConstantType>::checkOnSamples(std::shared_ptr<expressions::BinaryRelationExpression> assumption) const { + auto result = AssumptionStatus::UNKNOWN; + std::set<expressions::Variable> vars = std::set<expressions::Variable>({}); + assumption->gatherVariables(vars); + for (auto values : samples) { + auto valuation = expressions::SimpleValuation(assumption->getManager().getSharedPointer()); + for (auto var : vars) { + auto index = std::stoi(var.getName()); + valuation.setRationalValue(var, utility::convertNumber<double>(values[index])); } - } - if (prob.evaluate(substitutions) >= 0) { - result = AssumptionStatus::VALID; + assert (assumption->hasBooleanType()); + if (!assumption->evaluateAsBool(&valuation)) { + result = AssumptionStatus::INVALID; + break; + } } return result; } - - template <typename ValueType> - AssumptionStatus AssumptionChecker<ValueType>::validateAssumptionSMTSolver(std::shared_ptr<expressions::BinaryRelationExpression> assumption, Order* order) { + template <typename ValueType, typename ConstantType> + AssumptionStatus AssumptionChecker<ValueType, ConstantType>::validateAssumptionSMTSolver(uint_fast64_t val1, uint_fast64_t val2, std::shared_ptr<expressions::BinaryRelationExpression> assumption, std::shared_ptr<Order> order, storage::ParameterRegion<ValueType> region, std::vector<ConstantType>const minValues, std::vector<ConstantType>const maxValues) const { std::shared_ptr<utility::solver::SmtSolverFactory> smtSolverFactory = std::make_shared<utility::solver::MathsatSmtSolverFactory>(); std::shared_ptr<expressions::ExpressionManager> manager(new expressions::ExpressionManager()); - - AssumptionStatus result; + AssumptionStatus result = AssumptionStatus::UNKNOWN; auto var1 = assumption->getFirstOperand()->asVariableExpression().getVariableName(); auto var2 = assumption->getSecondOperand()->asVariableExpression().getVariableName(); - auto row1 = matrix.getRow(std::stoi(var1)); - auto row2 = matrix.getRow(std::stoi(var2)); + auto row1 = matrix.getRow(val1); + auto row2 = matrix.getRow(val2); bool orderKnown = true; + // if the state with number var1 (var2) occurs in the successors of the state with number var2 (var1) we need to add var1 == expr1 (var2 == expr2) to the bounds + bool addVar1 = false; + bool addVar2 = false; // Check if the order between the different successors is known // Also start creating expression for order of states - expressions::Expression exprOrderSucc = manager->boolean(true); + auto exprOrderSucc = manager->boolean(true); std::set<expressions::Variable> stateVariables; + std::set<expressions::Variable> topVariables; + std::set<expressions::Variable> bottomVariables; for (auto itr1 = row1.begin(); orderKnown && itr1 != row1.end(); ++itr1) { + addVar2 |= std::to_string(itr1->getColumn()) == var2; auto varname1 = "s" + std::to_string(itr1->getColumn()); if (!manager->hasVariable(varname1)) { - stateVariables.insert(manager->declareRationalVariable(varname1)); + if (order->isTopState(itr1->getColumn())) { + topVariables.insert(manager->declareRationalVariable(varname1)); + } else if (order->isBottomState(itr1->getColumn())) { + bottomVariables.insert(manager->declareRationalVariable(varname1)); + } else { + stateVariables.insert(manager->declareRationalVariable(varname1)); + } } + for (auto itr2 = row2.begin(); orderKnown && itr2 != row2.end(); ++itr2) { + addVar1 |= std::to_string(itr2->getColumn()) == var1; if (itr1->getColumn() != itr2->getColumn()) { auto varname2 = "s" + std::to_string(itr2->getColumn()); if (!manager->hasVariable(varname2)) { - stateVariables.insert(manager->declareRationalVariable(varname2)); + if (order->isTopState(itr2->getColumn())) { + topVariables.insert(manager->declareRationalVariable(varname2)); + } else if (order->isBottomState(itr2->getColumn())) { + bottomVariables.insert(manager->declareRationalVariable(varname2)); + } else { + stateVariables.insert(manager->declareRationalVariable(varname2)); + } } auto comp = order->compare(itr1->getColumn(), itr2->getColumn()); + if (minValues.size() > 0 && comp == Order::NodeComparison::UNKNOWN) { + // Couldn't add relation between varname1 and varname2 but maybe we can based on min/max values; + if (minValues[itr2->getColumn()] > maxValues[itr1->getColumn()]) { + if (!order->contains(itr1->getColumn())) { + order->add(itr1->getColumn()); + order->addStateToHandle(itr1->getColumn()); + } + if (!order->contains(itr2->getColumn())) { + order->add(itr2->getColumn()); + order->addStateToHandle(itr2->getColumn()); + } + order->addRelation(itr2->getColumn(), itr1->getColumn()); + comp = Order::NodeComparison::BELOW; + } else if (minValues[itr1->getColumn()] > maxValues[itr2->getColumn()]) { + if (!order->contains(itr1->getColumn())) { + order->add(itr1->getColumn()); + order->addStateToHandle(itr1->getColumn()); + } + if (!order->contains(itr2->getColumn())) { + order->add(itr2->getColumn()); + order->addStateToHandle(itr2->getColumn()); + } + order->addRelation(itr1->getColumn(), itr2->getColumn()); + comp = Order::NodeComparison::ABOVE; + } + } if (comp == Order::NodeComparison::ABOVE) { - exprOrderSucc = exprOrderSucc && !(manager->getVariable(varname1) <= - manager->getVariable(varname2)); + exprOrderSucc = exprOrderSucc && !(manager->getVariable(varname1) <= manager->getVariable(varname2)); } else if (comp == Order::NodeComparison::BELOW) { - exprOrderSucc = exprOrderSucc && !(manager->getVariable(varname1) >= - manager->getVariable(varname2)); + exprOrderSucc = exprOrderSucc && !(manager->getVariable(varname1) >= manager->getVariable(varname2)); } else if (comp == Order::NodeComparison::SAME) { - exprOrderSucc = exprOrderSucc && - (manager->getVariable(varname1) = manager->getVariable(varname2)); + exprOrderSucc = exprOrderSucc && (manager->getVariable(varname1) >= manager->getVariable(varname2)) && (manager->getVariable(varname1) <= manager->getVariable(varname2)); } else { orderKnown = false; } @@ -292,63 +230,96 @@ namespace storm { auto valueTypeToExpression = expressions::RationalFunctionToExpression<ValueType>(manager); expressions::Expression expr1 = manager->rational(0); for (auto itr1 = row1.begin(); itr1 != row1.end(); ++itr1) { - expr1 = expr1 + (valueTypeToExpression.toExpression(itr1->getValue()) * manager->getVariable("s" + std::to_string(itr1->getColumn()))); + expr1 = expr1 + (valueTypeToExpression.toExpression(itr1->getValue()) * + manager->getVariable("s" + std::to_string(itr1->getColumn()))); } expressions::Expression expr2 = manager->rational(0); for (auto itr2 = row2.begin(); itr2 != row2.end(); ++itr2) { - expr2 = expr2 + (valueTypeToExpression.toExpression(itr2->getValue()) * manager->getVariable("s" + std::to_string(itr2->getColumn()))); + expr2 = expr2 + (valueTypeToExpression.toExpression(itr2->getValue()) * + manager->getVariable("s" + std::to_string(itr2->getColumn()))); } // Create expression for the assumption based on the relation to successors // It is the negation of actual assumption - expressions::Expression exprToCheck ; - if (assumption->getRelationType() == - expressions::BinaryRelationExpression::RelationType::Greater) { + + expressions::Expression exprToCheck; + if (assumption->getRelationType() == expressions::BinaryRelationExpression::RelationType::Greater) { exprToCheck = expr1 <= expr2; } else { - assert (assumption->getRelationType() == - expressions::BinaryRelationExpression::RelationType::Equal); - exprToCheck = expr1 != expr2 ; + assert (assumption->getRelationType() == expressions::BinaryRelationExpression::RelationType::Equal); + exprToCheck = expr1 != expr2; } auto variables = manager->getVariables(); // Bounds for the state probabilities and parameters - expressions::Expression exprBounds = manager->boolean(true); + expressions::Expression exprBounds = manager->boolean(true); + if (addVar1) { + exprBounds = exprBounds && (manager->getVariable("s" + var1) == expr1); + } + if (addVar2) { + exprBounds = exprBounds && (manager->getVariable("s" + var2) == expr2); + } for (auto var : variables) { if (find(stateVariables.begin(), stateVariables.end(), var) != stateVariables.end()) { // the var is a state - exprBounds = exprBounds && manager->rational(0) <= var && var <= manager->rational(1); + if (minValues.size() > 0) { + std::string test = var.getName(); + auto val = std::stoi(test.substr(1,test.size()-1)); + exprBounds = exprBounds && manager->rational(minValues[val]) <= var && + var <= manager->rational(maxValues[val]); + } else { + exprBounds = exprBounds && manager->rational(0) <= var && + var <= manager->rational(1); + } + } else if (find(topVariables.begin(), topVariables.end(), var) != topVariables.end()) { + // the var is =) + exprBounds = exprBounds && var == manager->rational(1); + } else if (find(bottomVariables.begin(), bottomVariables.end(), var) != bottomVariables.end()) { + // the var is =( + exprBounds = exprBounds && var == manager->rational(0); } else { // the var is a parameter - auto lb = storm::utility::convertNumber<storm::RationalNumber>(region.getLowerBoundary(var.getName())); - auto ub = storm::utility::convertNumber<storm::RationalNumber>(region.getUpperBoundary(var.getName())); + auto lb = utility::convertNumber<RationalNumber>(region.getLowerBoundary(var.getName())); + auto ub = utility::convertNumber<RationalNumber>(region.getUpperBoundary(var.getName())); exprBounds = exprBounds && manager->rational(lb) < var && var < manager->rational(ub); } } s.add(exprOrderSucc); s.add(exprBounds); + s.setTimeout(100); // assert that sorting of successors in the order and the bounds on the expression are at least satisfiable - assert (s.check() == solver::SmtSolver::CheckResult::Sat); + // when this is not the case, the order is invalid + // however, it could be that the sat solver didn't finish in time, in that case we just continue. + if (s.check() == solver::SmtSolver::CheckResult::Unsat) { + return AssumptionStatus::INVALID; + } + assert (s.check() != solver::SmtSolver::CheckResult::Unsat); + s.add(exprToCheck); auto smtRes = s.check(); if (smtRes == solver::SmtSolver::CheckResult::Unsat) { // If there is no thing satisfying the negation we are safe. result = AssumptionStatus::VALID; } else if (smtRes == solver::SmtSolver::CheckResult::Sat) { - assert (smtRes == solver::SmtSolver::CheckResult::Sat); - result = AssumptionStatus::INVALID; - } else { - result = AssumptionStatus::UNKNOWN; + result = AssumptionStatus::INVALID; } - } else { - result = AssumptionStatus::UNKNOWN; } - return result; } - template class AssumptionChecker<RationalFunction>; + template<typename ValueType, typename ConstantType> + AssumptionStatus AssumptionChecker<ValueType, ConstantType>::validateAssumption( + std::shared_ptr<expressions::BinaryRelationExpression> assumption, std::shared_ptr<Order> order, + storage::ParameterRegion<ValueType> region) const { + auto var1 = std::stoi(assumption->getFirstOperand()->asVariableExpression().getVariableName()); + auto var2 = std::stoi(assumption->getSecondOperand()->asVariableExpression().getVariableName()); + std::vector<ConstantType> vals; + return validateAssumption(var1, var2, assumption, order, region, vals, vals); + } + + template class AssumptionChecker<RationalFunction, double>; + template class AssumptionChecker<RationalFunction, RationalNumber>; } } diff --git a/src/storm-pars/analysis/AssumptionChecker.h b/src/storm-pars/analysis/AssumptionChecker.h index 802291291..73e0e708d 100644 --- a/src/storm-pars/analysis/AssumptionChecker.h +++ b/src/storm-pars/analysis/AssumptionChecker.h @@ -8,6 +8,8 @@ #include "storm/storage/expressions/BinaryRelationExpression.h" #include "storm-pars/storage/ParameterRegion.h" #include "Order.h" +#include "storm/storage/SparseMatrix.h" + namespace storm { namespace analysis { @@ -19,18 +21,19 @@ namespace storm { INVALID, UNKNOWN, }; - template<typename ValueType> + + template<typename ValueType, typename ConstantType> class AssumptionChecker { public: + typedef typename utility::parametric::VariableType<ValueType>::type VariableType; + typedef typename utility::parametric::CoefficientType<ValueType>::type CoefficientType; /*! - * Constructs an AssumptionChecker based on the number of samples, for the given formula and model. + * Constructs an AssumptionChecker. * - * @param formula The formula to check. - * @param model The dtmc model to check the formula on. - * @param numberOfSamples Number of sample points. + * @param matrix The matrix of the considered model. */ - AssumptionChecker(std::shared_ptr<logic::Formula const> formula, std::shared_ptr<models::sparse::Dtmc<ValueType>> model, storm::storage::ParameterRegion<ValueType> region, uint_fast64_t numberOfSamples); + AssumptionChecker(storage::SparseMatrix<ValueType> matrix); /*! * Constructs an AssumptionChecker based on the number of samples, for the given formula and model. @@ -39,51 +42,46 @@ namespace storm { * @param model The mdp model to check the formula on. * @param numberOfSamples Number of sample points. */ - AssumptionChecker(std::shared_ptr<logic::Formula const> formula, std::shared_ptr<models::sparse::Mdp<ValueType>> model, uint_fast64_t numberOfSamples); + AssumptionChecker(std::shared_ptr<logic::Formula const> formula, std::shared_ptr<models::sparse::Mdp<ValueType>> model, uint_fast64_t const numberOfSamples); /*! - * Checks if the assumption holds at the sample points of the AssumptionChecker. + * Initializes the given number of sample points for a given model, formula and region. * - * @param assumption The assumption to check. - * @return AssumptionStatus::UNKNOWN or AssumptionStatus::INVALID + * @param formula The formula to compute the samples for. + * @param model The considered model. + * @param region The region of the model's parameters. + * @param numberOfSamples Number of sample points. */ - AssumptionStatus checkOnSamples(std::shared_ptr<expressions::BinaryRelationExpression> assumption); + void initializeCheckingOnSamples(std::shared_ptr<logic::Formula const> formula, std::shared_ptr<models::sparse::Dtmc<ValueType>> model, storage::ParameterRegion<ValueType> region, uint_fast64_t numberOfSamples); /*! - * Tries to validate an assumption based on the order and underlying transition matrix. + * Sets the sample values to the given vector and useSamples to true. * - * @param assumption The assumption to validate. - * @param order The order. - * @return AssumptionStatus::VALID, or AssumptionStatus::UNKNOWN, or AssumptionStatus::INVALID + * @param samples The new value for samples. */ - AssumptionStatus validateAssumption(std::shared_ptr<expressions::BinaryRelationExpression> assumption, Order* order); + void setSampleValues(std::vector<std::vector<ConstantType>> samples); /*! - * Tries to validate an assumption based on the order, and SMT solving techniques + * Tries to validate an assumption based on the order and underlying transition matrix. * * @param assumption The assumption to validate. * @param order The order. + * @param region The region of the considered model. * @return AssumptionStatus::VALID, or AssumptionStatus::UNKNOWN, or AssumptionStatus::INVALID */ - AssumptionStatus validateAssumptionSMTSolver(std::shared_ptr<expressions::BinaryRelationExpression> assumption, Order* order); + AssumptionStatus validateAssumption(uint_fast64_t val1, uint_fast64_t val2, std::shared_ptr<expressions::BinaryRelationExpression> assumption, std::shared_ptr<Order> order, storage::ParameterRegion<ValueType> region, std::vector<ConstantType> const minValues, std::vector<ConstantType> const maxValue) const; + AssumptionStatus validateAssumption(std::shared_ptr<expressions::BinaryRelationExpression> assumption, std::shared_ptr<Order> order, storage::ParameterRegion<ValueType> region) const; private: - std::shared_ptr<logic::Formula const> formula; + bool useSamples; - storage::SparseMatrix<ValueType> matrix; + std::vector<std::vector<ConstantType>> samples; - std::vector<std::vector<double>> samples; - - void createSamples(); - - AssumptionStatus validateAssumptionFunction(Order* order, - typename storage::SparseMatrix<ValueType>::iterator state1succ1, - typename storage::SparseMatrix<ValueType>::iterator state1succ2, - typename storage::SparseMatrix<ValueType>::iterator state2succ1, - typename storage::SparseMatrix<ValueType>::iterator state2succ2); + storage::SparseMatrix<ValueType> matrix; - storm::storage::ParameterRegion<ValueType> region; + AssumptionStatus validateAssumptionSMTSolver(uint_fast64_t val1, uint_fast64_t val2, std::shared_ptr<expressions::BinaryRelationExpression> assumption, std::shared_ptr<Order> order, storage::ParameterRegion<ValueType> region, std::vector<ConstantType>const minValues, std::vector<ConstantType>const maxValue) const; + AssumptionStatus checkOnSamples(std::shared_ptr<expressions::BinaryRelationExpression> assumption) const; }; } } diff --git a/src/storm-pars/analysis/AssumptionMaker.cpp b/src/storm-pars/analysis/AssumptionMaker.cpp index d889e3f13..479ebcdee 100644 --- a/src/storm-pars/analysis/AssumptionMaker.cpp +++ b/src/storm-pars/analysis/AssumptionMaker.cpp @@ -2,66 +2,86 @@ namespace storm { namespace analysis { - typedef std::shared_ptr<expressions::BinaryRelationExpression> AssumptionType; - template<typename ValueType> - AssumptionMaker<ValueType>::AssumptionMaker(AssumptionChecker<ValueType>* assumptionChecker, uint_fast64_t numberOfStates, bool validate) { - this->numberOfStates = numberOfStates; - this->assumptionChecker = assumptionChecker; - this->validate = validate; - this->expressionManager = std::make_shared<expressions::ExpressionManager>(expressions::ExpressionManager()); + template<typename ValueType, typename ConstantType> + AssumptionMaker<ValueType, ConstantType>::AssumptionMaker(storage::SparseMatrix<ValueType> matrix) : assumptionChecker(matrix){ + numberOfStates = matrix.getColumnCount(); + expressionManager = std::make_shared<expressions::ExpressionManager>(expressions::ExpressionManager()); for (uint_fast64_t i = 0; i < this->numberOfStates; ++i) { expressionManager->declareRationalVariable(std::to_string(i)); } } + template <typename ValueType, typename ConstantType> + std::map<std::shared_ptr<expressions::BinaryRelationExpression>, AssumptionStatus> AssumptionMaker<ValueType, ConstantType>::createAndCheckAssumptions(uint_fast64_t val1, uint_fast64_t val2, std::shared_ptr<Order> order, storage::ParameterRegion<ValueType> region) const { + auto vec1 = std::vector<ConstantType>(); + auto vec2 = std::vector<ConstantType>(); + return createAndCheckAssumptions(val1, val2, order, region, vec1, vec2); + } - template <typename ValueType> - std::map<std::shared_ptr<expressions::BinaryRelationExpression>, AssumptionStatus> AssumptionMaker<ValueType>::createAndCheckAssumption(uint_fast64_t val1, uint_fast64_t val2, Order* order) { + template <typename ValueType, typename ConstantType> + std::map<std::shared_ptr<expressions::BinaryRelationExpression>, AssumptionStatus> AssumptionMaker<ValueType, ConstantType>::createAndCheckAssumptions(uint_fast64_t val1, uint_fast64_t val2, std::shared_ptr<Order> order, storage::ParameterRegion<ValueType> region, std::vector<ConstantType> const minValues, std::vector<ConstantType> const maxValues) const { std::map<std::shared_ptr<expressions::BinaryRelationExpression>, AssumptionStatus> result; - - expressions::Variable var1 = expressionManager->getVariable(std::to_string(val1)); - expressions::Variable var2 = expressionManager->getVariable(std::to_string(val2)); - std::shared_ptr<expressions::BinaryRelationExpression> assumption1 - = std::make_shared<expressions::BinaryRelationExpression>(expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), - var1.getExpression().getBaseExpressionPointer(), var2.getExpression().getBaseExpressionPointer(), - expressions::BinaryRelationExpression::RelationType::Greater)); - AssumptionStatus result1; - AssumptionStatus result2; - AssumptionStatus result3; - if (validate) { - result1 = assumptionChecker->validateAssumption(assumption1, order); - } else { - result1 = AssumptionStatus::UNKNOWN; + STORM_LOG_INFO("Creating assumptions for " << val1 << " and " << val2); + assert (order->compare(val1, val2) == Order::UNKNOWN); + auto assumption = createAndCheckAssumption(val1, val2, expressions::BinaryRelationExpression::RelationType::Greater, order, region, minValues, maxValues); + if (assumption.second != AssumptionStatus::INVALID) { + result.insert(assumption); + if (assumption.second == AssumptionStatus::VALID) { + assert (createAndCheckAssumption(val2, val1, expressions::BinaryRelationExpression::RelationType::Greater, order, region, minValues, maxValues).second != AssumptionStatus::VALID + && createAndCheckAssumption(val1, val2, expressions::BinaryRelationExpression::RelationType::Equal, order, region, minValues, maxValues).second != AssumptionStatus::VALID); + STORM_LOG_INFO("Assumption " << assumption.first << "is valid" << std::endl); + return result; + } } - result[assumption1] = result1; - - - std::shared_ptr<expressions::BinaryRelationExpression> assumption2 - = std::make_shared<expressions::BinaryRelationExpression>(expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), - var2.getExpression().getBaseExpressionPointer(), var1.getExpression().getBaseExpressionPointer(), - expressions::BinaryRelationExpression::RelationType::Greater)); - - if (validate) { - result2 = assumptionChecker->validateAssumption(assumption2, order); - } else { - result2 = AssumptionStatus::UNKNOWN; + assert (order->compare(val1, val2) == Order::UNKNOWN); + assumption = createAndCheckAssumption(val2, val1, expressions::BinaryRelationExpression::RelationType::Greater, order, region, minValues, maxValues); + if (assumption.second != AssumptionStatus::INVALID) { + if (assumption.second == AssumptionStatus::VALID) { + result.clear(); + result.insert(assumption); + assert (createAndCheckAssumption(val1, val2, expressions::BinaryRelationExpression::RelationType::Equal, order, region, minValues, maxValues).second != AssumptionStatus::VALID); + STORM_LOG_INFO("Assumption " << assumption.first << "is valid" << std::endl); + return result; + } + result.insert(assumption); } - result[assumption2] = result2; - - std::shared_ptr<expressions::BinaryRelationExpression> assumption3 - = std::make_shared<expressions::BinaryRelationExpression>(expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), - var2.getExpression().getBaseExpressionPointer(), var1.getExpression().getBaseExpressionPointer(), - expressions::BinaryRelationExpression::RelationType::Equal)); - if (validate) { - result3 = assumptionChecker->validateAssumption(assumption3, order); - } else { - result3 = AssumptionStatus::UNKNOWN; + assert (order->compare(val1, val2) == Order::UNKNOWN); + assumption = createAndCheckAssumption(val1, val2, expressions::BinaryRelationExpression::RelationType::Equal, order, region, minValues, maxValues); + if (assumption.second != AssumptionStatus::INVALID) { + if (assumption.second == AssumptionStatus::VALID) { + result.clear(); + result.insert(assumption); + STORM_LOG_INFO("Assumption " << assumption.first << "is valid" << std::endl); + return result; + } + result.insert(assumption); } - result[assumption3] = result3; - + assert (order->compare(val1, val2) == Order::UNKNOWN); + STORM_LOG_INFO("None of the assumptions is valid, number of possible assumptions: " << result.size() << std::endl); return result; } - template class AssumptionMaker<RationalFunction>; + template <typename ValueType, typename ConstantType> + void AssumptionMaker<ValueType, ConstantType>::initializeCheckingOnSamples(std::shared_ptr<logic::Formula const> formula, std::shared_ptr<models::sparse::Dtmc<ValueType>> model, storage::ParameterRegion<ValueType> region, uint_fast64_t numberOfSamples){ + assumptionChecker.initializeCheckingOnSamples(formula, model, region, numberOfSamples); + } + + template <typename ValueType, typename ConstantType> + void AssumptionMaker<ValueType, ConstantType>::setSampleValues(std::vector<std::vector<ConstantType>>const & samples) { + assumptionChecker.setSampleValues(samples); + } + + template <typename ValueType, typename ConstantType> + std::pair<std::shared_ptr<expressions::BinaryRelationExpression>, AssumptionStatus> AssumptionMaker<ValueType, ConstantType>::createAndCheckAssumption(uint_fast64_t val1, uint_fast64_t val2, expressions::BinaryRelationExpression::RelationType relationType, std::shared_ptr<Order> order, storage::ParameterRegion<ValueType> region, std::vector<ConstantType> const minValues, std::vector<ConstantType> const maxValues) const { + assert (val1 != val2); + expressions::Variable var1 = expressionManager->getVariable(std::to_string(val1)); + expressions::Variable var2 = expressionManager->getVariable(std::to_string(val2)); + auto assumption = std::make_shared<expressions::BinaryRelationExpression>(expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), var1.getExpression().getBaseExpressionPointer(), var2.getExpression().getBaseExpressionPointer(), relationType)); + AssumptionStatus validationResult = assumptionChecker.validateAssumption(val1, val2, assumption, order, region, minValues, maxValues); + return std::pair<std::shared_ptr<expressions::BinaryRelationExpression>, AssumptionStatus>(assumption, validationResult); + } + + template class AssumptionMaker<RationalFunction, double>; + template class AssumptionMaker<RationalFunction, RationalNumber>; } } diff --git a/src/storm-pars/analysis/AssumptionMaker.h b/src/storm-pars/analysis/AssumptionMaker.h index 41643fb5a..97fa6f66f 100644 --- a/src/storm-pars/analysis/AssumptionMaker.h +++ b/src/storm-pars/analysis/AssumptionMaker.h @@ -3,47 +3,65 @@ #include "AssumptionChecker.h" #include "Order.h" -#include "OrderExtender.h" -#include "storm/storage/expressions/BinaryRelationExpression.h" -#include "storm-pars/utility/ModelInstantiator.h" +#include "storm/storage/expressions/BinaryRelationExpression.h" +#include "storm/storage/expressions/ExpressionManager.h" +#include "storm/storage/SparseMatrix.h" namespace storm { namespace analysis { - template<typename ValueType> + template<typename ValueType, typename ConstantType> class AssumptionMaker { typedef std::shared_ptr<expressions::BinaryRelationExpression> AssumptionType; public: /*! - * Constructs AssumptionMaker based on the order extender, the assumption checker and number of states of the mode + * Constructs AssumptionMaker based on the matrix of the model. + * + * @param matrix The matrix of the model. + */ + AssumptionMaker(storage::SparseMatrix<ValueType> matrix); + + /*! + * Creates assumptions, and checks them, only VALID and UNKNOWN assumptions are returned. + * If one assumption is VALID, this assumption will be returned as only assumption. + * Possible results: AssumptionStatus::VALID, AssumptionStatus::UNKNOWN. * - * @param orderExtender The OrderExtender which needs the assumptions made by the AssumptionMaker. - * @param checker The AssumptionChecker which checks the assumptions at sample points. - * @param numberOfStates The number of states of the model. + * @param val1 First state number. + * @param val2 Second state number. + * @param order The order on which the assumptions are checked. + * @param region The region for the parameters. + * @return Map with at most three assumptions, and the validation. */ - AssumptionMaker(AssumptionChecker<ValueType>* checker, uint_fast64_t numberOfStates, bool validate); + std::map<std::shared_ptr<expressions::BinaryRelationExpression>, AssumptionStatus> createAndCheckAssumptions(uint_fast64_t val1, uint_fast64_t val2, std::shared_ptr<Order> order, storage::ParameterRegion<ValueType> region) const; + std::map<std::shared_ptr<expressions::BinaryRelationExpression>, AssumptionStatus> createAndCheckAssumptions(uint_fast64_t val1, uint_fast64_t val2, std::shared_ptr<Order> order, storage::ParameterRegion<ValueType> region, std::vector<ConstantType> const minValues, std::vector<ConstantType> const maxValue) const; /*! - * Creates assumptions, and checks them if validate in constructor is true. - * Possible results: AssumptionStatus::VALID, AssumptionStatus::INVALID, AssumptionStatus::UNKNOWN - * If validate is false result is always AssumptionStatus::UNKNOWN + * Initializes the given number of sample points for a given model, formula and region. * - * @param val1 First state number - * @param val2 Second state number - * @param order The order on which the assumptions are checked - * @return Map with three assumptions, and the validation + * @param formula The formula to compute the samples for. + * @param model The considered model. + * @param region The region of the model's parameters. + * @param numberOfSamples Number of sample points. */ - std::map<std::shared_ptr<expressions::BinaryRelationExpression>, AssumptionStatus> createAndCheckAssumption(uint_fast64_t val1, uint_fast64_t val2, Order* order); + void initializeCheckingOnSamples(std::shared_ptr<logic::Formula const> formula, std::shared_ptr<models::sparse::Dtmc<ValueType>> model, storage::ParameterRegion<ValueType> region, uint_fast64_t numberOfSamples); + + /*! + * Sets the sample values to the given vector. + * + * @param samples The new value for samples. + */ + void setSampleValues(std::vector<std::vector<ConstantType>>const & samples); private: - AssumptionChecker<ValueType>* assumptionChecker; + std::pair<std::shared_ptr<expressions::BinaryRelationExpression>, AssumptionStatus> createAndCheckAssumption(uint_fast64_t val1, uint_fast64_t val2, expressions::BinaryRelationExpression::RelationType relationType, std::shared_ptr<Order> order, storage::ParameterRegion<ValueType> region, std::vector<ConstantType> const minValues, std::vector<ConstantType> const maxValue) const; + + AssumptionChecker<ValueType, ConstantType> assumptionChecker; std::shared_ptr<expressions::ExpressionManager> expressionManager; uint_fast64_t numberOfStates; - bool validate; }; } } diff --git a/src/storm-pars/analysis/LocalMonotonicityResult.cpp b/src/storm-pars/analysis/LocalMonotonicityResult.cpp new file mode 100644 index 000000000..8a5bb7f18 --- /dev/null +++ b/src/storm-pars/analysis/LocalMonotonicityResult.cpp @@ -0,0 +1,175 @@ +#include "LocalMonotonicityResult.h" + +namespace storm { + namespace analysis { + + template <typename VariableType> + LocalMonotonicityResult<VariableType>::LocalMonotonicityResult(uint_fast64_t numberOfStates) { + stateMonRes = std::vector<std::shared_ptr<MonotonicityResult<VariableType>>>(numberOfStates, nullptr); + globalMonotonicityResult = std::make_shared<MonotonicityResult<VariableType>>(); + statesMonotone = storm::storage::BitVector(numberOfStates, false); + dummyPointer = std::make_shared<MonotonicityResult<VariableType>>(); + done = false; + } + + template <typename VariableType> + typename LocalMonotonicityResult<VariableType>::Monotonicity LocalMonotonicityResult<VariableType>::getMonotonicity(uint_fast64_t state, VariableType var) const { + if (stateMonRes[state] == dummyPointer) { + return Monotonicity::Constant; + } else if (stateMonRes[state] != nullptr) { + auto res = stateMonRes[state]->getMonotonicity(var); + if (res == Monotonicity::Unknown && globalMonotonicityResult->isDoneForVar(var)) { + return globalMonotonicityResult->getMonotonicity(var); + } + return res; + } else { + return globalMonotonicityResult->isDoneForVar(var) ? globalMonotonicityResult->getMonotonicity(var) : Monotonicity::Unknown; + } + } + + template <typename VariableType> + std::shared_ptr<MonotonicityResult<VariableType>> LocalMonotonicityResult<VariableType>::getGlobalMonotonicityResult() const { + return globalMonotonicityResult; + } + + template <typename VariableType> + void LocalMonotonicityResult<VariableType>::setMonotonicity(uint_fast64_t state, VariableType var, typename LocalMonotonicityResult<VariableType>::Monotonicity mon) { + assert (stateMonRes[state] != dummyPointer); + if (stateMonRes[state] == nullptr) { + stateMonRes[state] = std::make_shared<MonotonicityResult<VariableType>>(); + } + stateMonRes[state]->addMonotonicityResult(var, mon); + globalMonotonicityResult->updateMonotonicityResult(var, mon); + if (mon == Monotonicity::Unknown || mon == Monotonicity::Not) { + statesMonotone.set(state, false); + } else { + bool stateMonotone = stateMonRes[state]->isAllMonotonicity(); + if (stateMonotone) { + statesMonotone.set(state); + done |= statesMonotone.full(); + } + if (isDone()) { + globalMonotonicityResult->setDone(); + } + } + } + + template <typename VariableType> + std::shared_ptr<LocalMonotonicityResult<VariableType>> LocalMonotonicityResult<VariableType>::copy() { + std::shared_ptr<LocalMonotonicityResult<VariableType>> copy = std::make_shared<LocalMonotonicityResult<VariableType>>(stateMonRes.size()); + for (auto state = 0; state < stateMonRes.size(); state++) { + if (stateMonRes[state] != nullptr) { + copy->setMonotonicityResult(state, stateMonRes[state]->copy()); + } + } + copy->setGlobalMonotonicityResult(this->getGlobalMonotonicityResult()->copy()); + copy->setStatesMonotone(statesMonotone); + return copy; + } + + template <typename VariableType> + bool LocalMonotonicityResult<VariableType>::isDone() const { + return done; + } + + template <typename VariableType> + void LocalMonotonicityResult<VariableType>::setIndexMinimize(int i) { + assert (indexMinimize == -1); + this->indexMinimize = i; + } + + template <typename VariableType> + void LocalMonotonicityResult<VariableType>::setIndexMaximize(int i) { + this->indexMaximize = i; + } + + template <typename VariableType> + int LocalMonotonicityResult<VariableType>::getIndexMinimize() const { + return indexMinimize; + } + + template <typename VariableType> + int LocalMonotonicityResult<VariableType>::getIndexMaximize() const { + return indexMaximize; + } + + template <typename VariableType> + bool LocalMonotonicityResult<VariableType>::isNoMonotonicity() const { + return statesMonotone.empty(); + } + + template <typename VariableType> + void LocalMonotonicityResult<VariableType>::setMonotonicityResult(uint_fast64_t state, std::shared_ptr<MonotonicityResult<VariableType>> monRes) { + this->stateMonRes[state] = monRes; + } + + template <typename VariableType> + void LocalMonotonicityResult<VariableType>::setGlobalMonotonicityResult(std::shared_ptr<MonotonicityResult<VariableType>> monRes) { + this->globalMonotonicityResult = monRes; + } + + template <typename VariableType> + void LocalMonotonicityResult<VariableType>::setStatesMonotone(storm::storage::BitVector statesMonotone) { + this->statesMonotone = statesMonotone; + } + + template <typename VariableType> + void LocalMonotonicityResult<VariableType>::setConstant(uint_fast64_t state) { + if (stateMonRes[state] == nullptr) { + stateMonRes[state] = dummyPointer; + } + this->statesMonotone.set(state); + } + + template<typename VariableType> + void LocalMonotonicityResult<VariableType>::setMonotoneIncreasing(VariableType var) { + globalMonotonicityResult->updateMonotonicityResult(var, Monotonicity::Incr); + globalMonotonicityResult->setDoneForVar(var); + setFixedParameters = true; + } + + template<typename VariableType> + void LocalMonotonicityResult<VariableType>::setMonotoneDecreasing(VariableType var) { + globalMonotonicityResult->updateMonotonicityResult(var, Monotonicity::Decr); + globalMonotonicityResult->setDoneForVar(var); + setFixedParameters = true; + } + + template <typename VariableType> + std::string LocalMonotonicityResult<VariableType>::toString() const { + std::string result = "Local Monotonicity Result: \n"; + for (auto i = 0; i < stateMonRes.size(); ++i) { + result += "state "; + result += std::to_string(i); + if (stateMonRes[i] != nullptr) { + result += stateMonRes[i]->toString(); + } else if (statesMonotone[i]) { + result += "constant"; + } else { + result += "not analyzed"; + } + result += "\n"; + } + return result; + } + + template<typename VariableType> + bool LocalMonotonicityResult<VariableType>::isFixedParametersSet() const { + return setFixedParameters; + } + + template<typename VariableType> + void LocalMonotonicityResult<VariableType>::setDone(bool done) { + this->done = done; + } + + template<typename VariableType> + std::shared_ptr<MonotonicityResult<VariableType>> + LocalMonotonicityResult<VariableType>::getMonotonicity(uint_fast64_t state) const { + return stateMonRes[state]; + } + + + template class LocalMonotonicityResult<storm::RationalFunctionVariable>; + } +} diff --git a/src/storm-pars/analysis/LocalMonotonicityResult.h b/src/storm-pars/analysis/LocalMonotonicityResult.h new file mode 100644 index 000000000..4caa8fba9 --- /dev/null +++ b/src/storm-pars/analysis/LocalMonotonicityResult.h @@ -0,0 +1,113 @@ +#ifndef STORM_LOCALMONOTONICITYRESULT_H +#define STORM_LOCALMONOTONICITYRESULT_H + +#include <vector> +#include "storm-pars/analysis/MonotonicityResult.h" +#include "storm/adapters/RationalFunctionAdapter.h" +#include "storm/storage/BitVector.h" + +namespace storm { + namespace analysis { + template <typename VariableType> + class LocalMonotonicityResult { + + public: + typedef typename MonotonicityResult<VariableType>::Monotonicity Monotonicity; + + /*! + * Constructs a new LocalMonotonicityResult object. + * + * @param numberOfStates The number of states in the considered model. + * @return The new object. + */ + LocalMonotonicityResult(uint_fast64_t numberOfStates); + + /*! + * Returns the local Monotonicity of a parameter at a given state. + * + * @param state The state. + * @param var The parameter. + * @return The local Monotonicity. + */ + Monotonicity getMonotonicity(uint_fast64_t state, VariableType var) const; + std::shared_ptr<MonotonicityResult<VariableType>> getMonotonicity(uint_fast64_t state) const; + + /*! + * Sets the local Monotonicity of a parameter at a given state. + * + * @param mon The Monotonicity. + * @param state The state. + * @param var The parameter. + */ + void setMonotonicity(uint_fast64_t state, VariableType var, Monotonicity mon); + + /*! + * Returns the Global MonotonicityResult object that corresponds to this object. + * + * @return The pointer to the global MonotonicityResult. + */ + std::shared_ptr<MonotonicityResult<VariableType>> getGlobalMonotonicityResult() const; + + /*! + * Constructs a new LocalMonotonicityResult object that is a copy of the current one. + * + * @return Pointer to the copy. + */ + std::shared_ptr<LocalMonotonicityResult<VariableType>> copy(); + + /*! + * Checks if the LocalMonotonicity is done yet. + * + * @return true if done is set to true, false otherwise. + */ + bool isDone() const; + void setDone(bool done = true); + + bool isNoMonotonicity() const; + + void setConstant(uint_fast64_t state); + + void setIndexMinimize(int index); + void setIndexMaximize(int index); + int getIndexMinimize() const; + int getIndexMaximize() const; + + /*! + * Constructs a string output of all variables and their corresponding Monotonicity + * @return Results so far + */ + std::string toString() const; + + void setMonotoneIncreasing(VariableType var); + void setMonotoneDecreasing(VariableType var); + + bool isFixedParametersSet() const; + + + + + private: + std::vector<std::shared_ptr<MonotonicityResult<VariableType>>> stateMonRes; + + std::shared_ptr<MonotonicityResult<VariableType>> globalMonotonicityResult; + + void setMonotonicityResult(uint_fast64_t state, std::shared_ptr<MonotonicityResult<VariableType>> monRes); + + void setGlobalMonotonicityResult(std::shared_ptr<MonotonicityResult<VariableType>> monRes); + + void setStatesMonotone(storm::storage::BitVector statesMonotone); + + storm::storage::BitVector statesMonotone; + bool done; + + int indexMinimize = -1; + int indexMaximize = -1; + std::shared_ptr<MonotonicityResult<VariableType>> dummyPointer; + + bool setFixedParameters = false; + + }; + } +} + +#endif //STORM_LOCALMONOTONICITYRESULT_H diff --git a/src/storm-pars/analysis/MonotonicityChecker.cpp b/src/storm-pars/analysis/MonotonicityChecker.cpp index f8ba84c7e..61e125e68 100644 --- a/src/storm-pars/analysis/MonotonicityChecker.cpp +++ b/src/storm-pars/analysis/MonotonicityChecker.cpp @@ -1,690 +1,143 @@ #include "MonotonicityChecker.h" -#include "storm-pars/analysis/AssumptionMaker.h" -#include "storm-pars/analysis/AssumptionChecker.h" -#include "storm-pars/analysis/Order.h" -#include "storm-pars/analysis/OrderExtender.h" - -#include "storm/exceptions/NotSupportedException.h" -#include "storm/exceptions/UnexpectedException.h" -#include "storm/exceptions/InvalidOperationException.h" - -#include "storm/utility/Stopwatch.h" -#include "storm/models/ModelType.h" - -#include "storm/api/verification.h" -#include "storm-pars/api/storm-pars.h" - -#include "storm/modelchecker/results/CheckResult.h" -#include "storm/modelchecker/results/ExplicitQuantitativeCheckResult.h" -#include "storm-pars/modelchecker/region/SparseDtmcParameterLiftingModelChecker.h" - - namespace storm { namespace analysis { + /*** Constructor ***/ template <typename ValueType> - MonotonicityChecker<ValueType>::MonotonicityChecker(std::shared_ptr<storm::models::ModelBase> model, std::vector<std::shared_ptr<storm::logic::Formula const>> formulas, std::vector<storm::storage::ParameterRegion<ValueType>> regions, bool validate, uint_fast64_t numberOfSamples, double const& precision) { - assert (model != nullptr); - this->model = model; - this->formulas = formulas; - this->validate = validate; - this->precision = precision; - std::shared_ptr<storm::models::sparse::Model<ValueType>> sparseModel = model->as<storm::models::sparse::Model<ValueType>>(); - - if (regions.size() == 1) { - this->region = *(regions.begin()); - } else { - assert (regions.size() == 0); - typename storm::storage::ParameterRegion<ValueType>::Valuation lowerBoundaries; - typename storm::storage::ParameterRegion<ValueType>::Valuation upperBoundaries; - std::set<typename storm::storage::ParameterRegion<ValueType>::VariableType> vars; - vars = storm::models::sparse::getProbabilityParameters(*sparseModel); - for (auto var : vars) { - typename storm::storage::ParameterRegion<ValueType>::CoefficientType lb = storm::utility::convertNumber<typename storm::storage::ParameterRegion<ValueType>::CoefficientType>(0 + precision); - typename storm::storage::ParameterRegion<ValueType>::CoefficientType ub = storm::utility::convertNumber<typename storm::storage::ParameterRegion<ValueType>::CoefficientType>(1 - precision); - lowerBoundaries.insert(std::make_pair(var, lb)); - upperBoundaries.insert(std::make_pair(var, ub)); - } - this->region = storm::storage::ParameterRegion<ValueType>(std::move(lowerBoundaries), std::move(upperBoundaries)); - } - - if (numberOfSamples > 0) { - // sampling - if (model->isOfType(storm::models::ModelType::Dtmc)) { - this->resultCheckOnSamples = std::map<typename utility::parametric::VariableType<ValueType>::type, std::pair<bool, bool>>( - checkOnSamples(model->as<storm::models::sparse::Dtmc<ValueType>>(), numberOfSamples)); - } else if (model->isOfType(storm::models::ModelType::Mdp)) { - this->resultCheckOnSamples = std::map<typename utility::parametric::VariableType<ValueType>::type, std::pair<bool, bool>>( - checkOnSamples(model->as<storm::models::sparse::Mdp<ValueType>>(), numberOfSamples)); - - } - checkSamples= true; - } else { - checkSamples= false; - } - - this->extender = new storm::analysis::OrderExtender<ValueType>(sparseModel); + MonotonicityChecker<ValueType>::MonotonicityChecker(storage::SparseMatrix<ValueType> matrix) { + this->matrix = matrix; } + /*** Public methods ***/ template <typename ValueType> - std::map<storm::analysis::Order*, std::map<typename utility::parametric::VariableType<ValueType>::type, std::pair<bool, bool>>> MonotonicityChecker<ValueType>::checkMonotonicity(std::ostream& outfile) { - auto map = createOrder(); - std::shared_ptr<storm::models::sparse::Model<ValueType>> sparseModel = model->as<storm::models::sparse::Model<ValueType>>(); - auto matrix = sparseModel->getTransitionMatrix(); - return checkMonotonicity(outfile, map, matrix); - } - - template <typename ValueType> - std::map<storm::analysis::Order*, std::map<typename utility::parametric::VariableType<ValueType>::type, std::pair<bool, bool>>> MonotonicityChecker<ValueType>::checkMonotonicity(std::ostream& outfile, std::map<storm::analysis::Order*, std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>>> map, storm::storage::SparseMatrix<ValueType> matrix) { - storm::utility::Stopwatch monotonicityCheckWatch(true); - std::map<storm::analysis::Order *, std::map<typename utility::parametric::VariableType<ValueType>::type, std::pair<bool, bool>>> result; - - - if (map.size() == 0) { - // Nothing is known - outfile << " No assumptions -"; - STORM_PRINT("No valid assumptions, couldn't build a sufficient order"); - if (resultCheckOnSamples.size() != 0) { - STORM_PRINT("\n" << "Based results on samples"); - } else { - outfile << " ?"; + typename MonotonicityChecker<ValueType>::Monotonicity MonotonicityChecker<ValueType>::checkLocalMonotonicity(std::shared_ptr<Order> const& order, uint_fast64_t state, VariableType const& var, storage::ParameterRegion<ValueType> const& region) { + // Create + fill Vector containing the Monotonicity of the transitions to the succs + auto row = matrix.getRow(state); + // Ignore if all entries are constant + bool ignore = true; + + std::vector<uint_fast64_t> succs; + std::vector<Monotonicity> succsMonUnsorted; + std::vector<uint_fast64_t> statesIncr; + std::vector<uint_fast64_t> statesDecr; + bool checkAllow = true; + for (auto entry : row) { + auto succState = entry.getColumn(); + auto mon = checkTransitionMonRes(entry.getValue(), var, region); + succsMonUnsorted.push_back(mon); + succs.push_back(succState); + ignore &= entry.getValue().isConstant(); + if (mon == Monotonicity::Incr) { + statesIncr.push_back(succState); + } else if (mon == Monotonicity::Decr) { + statesDecr.push_back(succState); + } else if (mon == Monotonicity::Not) { + checkAllow = false; } - - for (auto entry : resultCheckOnSamples) { - if (!entry.second.first && ! entry.second.second) { - outfile << " SX " << entry.first << " "; - } else if (entry.second.first && ! entry.second.second) { - outfile << " SI " << entry.first << " "; - } else if (entry.second.first && entry.second.second) { - outfile << " SC " << entry.first << " "; - } else { - outfile << " SD " << entry.first << " "; - } - } - - } else { - size_t i = 0; - for (auto itr = map.begin(); i < map.size() && itr != map.end(); ++itr) { - auto order = itr->first; - - auto addedStates = order->getAddedStates()->getNumberOfSetBits(); - assert (addedStates == order->getAddedStates()->size()); - std::map<typename utility::parametric::VariableType<ValueType>::type, std::pair<bool, bool>> varsMonotone = analyseMonotonicity(i, order, - matrix); - - auto assumptions = itr->second; - if (assumptions.size() > 0) { - bool first = true; - for (auto itr2 = assumptions.begin(); itr2 != assumptions.end(); ++itr2) { - if (!first) { - outfile << (" ^ "); - } else { - first = false; - } - outfile << (*(*itr2)); + } + if (ignore) { + return Monotonicity::Constant; + } + auto succsSorted = order->sortStates(&succs); + + uint_fast64_t succSize = succs.size(); + if (succsSorted[succSize - 1] == matrix.getColumnCount()) { + // Maybe we can still do something + // If one is decreasing and all others increasing, and this one is above all others or vice versa + if (checkAllow) { + if (statesIncr.size() == 1 && statesDecr.size() > 1) { + auto comp = order->allAboveBelow(statesDecr, statesIncr.back()); + if (comp.first) { + // All decreasing states are above the increasing state, therefore decreasing + return Monotonicity::Decr; + } else if (comp.second) { + // All decreasing states are below the increasing state, therefore increasing + return Monotonicity::Incr; } - outfile << " - "; - } else if (assumptions.size() == 0) { - outfile << "No assumptions - "; - } - - if (varsMonotone.size() == 0) { - outfile << "No params"; - } else { - auto itr2 = varsMonotone.begin(); - while (itr2 != varsMonotone.end()) { - if (checkSamples && - resultCheckOnSamples.find(itr2->first) != resultCheckOnSamples.end() && - (!resultCheckOnSamples[itr2->first].first && - !resultCheckOnSamples[itr2->first].second)) { - outfile << "X " << itr2->first; - } else { - if (itr2->second.first && itr2->second.second) { - outfile << "C " << itr2->first; - } else if (itr2->second.first) { - outfile << "I " << itr2->first; - } else if (itr2->second.second) { - outfile << "D " << itr2->first; - } else { - outfile << "? " << itr2->first; - } - } - ++itr2; - if (itr2 != varsMonotone.end()) { - outfile << " "; - } + } else if (statesDecr.size() == 1 && statesIncr.size() > 1) { + auto comp = order->allAboveBelow(statesDecr, statesIncr.back()); + if (comp.first) { + // All increasing states are below the decreasing state, therefore increasing + return Monotonicity::Incr; + } else if (comp.second) { + // All increasing states are above the decreasing state, therefore decreasing + return Monotonicity::Decr; } - result.insert( - std::pair<storm::analysis::Order *, std::map<typename utility::parametric::VariableType<ValueType>::type, std::pair<bool, bool>>>( - order, varsMonotone)); } - ++i; - outfile << ";"; } - } - outfile << ", "; - - monotonicityCheckWatch.stop(); - return result; - } - template <typename ValueType> - std::map<storm::analysis::Order*, std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>>> MonotonicityChecker<ValueType>::createOrder() { - // Transform to Orders - storm::utility::Stopwatch orderWatch(true); - - // Use parameter lifting modelchecker to get initial min/max values for order creation - storm::modelchecker::SparseDtmcParameterLiftingModelChecker<storm::models::sparse::Dtmc<ValueType>, double> plaModelChecker; - std::unique_ptr<storm::modelchecker::CheckResult> checkResult; - auto env = Environment(); - - auto formula = formulas[0]; - const storm::modelchecker::CheckTask<storm::logic::Formula, ValueType> checkTask - = storm::modelchecker::CheckTask<storm::logic::Formula, ValueType>(*formula); - STORM_LOG_THROW(plaModelChecker.canHandle(model, checkTask), storm::exceptions::NotSupportedException, - "Cannot handle this formula"); - plaModelChecker.specify(env, model, checkTask); - - std::unique_ptr<storm::modelchecker::CheckResult> minCheck = plaModelChecker.check(env, region,storm::solver::OptimizationDirection::Minimize); - std::unique_ptr<storm::modelchecker::CheckResult> maxCheck = plaModelChecker.check(env, region,storm::solver::OptimizationDirection::Maximize); - auto minRes = minCheck->asExplicitQuantitativeCheckResult<double>(); - auto maxRes = maxCheck->asExplicitQuantitativeCheckResult<double>(); - - std::vector<double> minValues = minRes.getValueVector(); - std::vector<double> maxValues = maxRes.getValueVector(); - // Create initial order - std::tuple<storm::analysis::Order*, uint_fast64_t, uint_fast64_t> criticalTuple = extender->toOrder(formulas, minValues, maxValues); - // Continue based on not (yet) sorted states - std::map<storm::analysis::Order*, std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>>> result; - - auto val1 = std::get<1>(criticalTuple); - auto val2 = std::get<2>(criticalTuple); - auto numberOfStates = model->getNumberOfStates(); - std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>> assumptions; - - if (val1 == numberOfStates && val2 == numberOfStates) { - result.insert(std::pair<storm::analysis::Order*, std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>>>(std::get<0>(criticalTuple), assumptions)); - } else if (val1 != numberOfStates && val2 != numberOfStates) { - - storm::analysis::AssumptionChecker<ValueType> *assumptionChecker; - if (model->isOfType(storm::models::ModelType::Dtmc)) { - auto dtmc = model->as<storm::models::sparse::Dtmc<ValueType>>(); - assumptionChecker = new storm::analysis::AssumptionChecker<ValueType>(formulas[0], dtmc, region, 3); - } else if (model->isOfType(storm::models::ModelType::Mdp)) { - auto mdp = model->as<storm::models::sparse::Mdp<ValueType>>(); - assumptionChecker = new storm::analysis::AssumptionChecker<ValueType>(formulas[0], mdp, 3); - } else { - STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, - "Unable to perform monotonicity analysis on the provided model type."); - } - auto assumptionMaker = new storm::analysis::AssumptionMaker<ValueType>(assumptionChecker, numberOfStates, validate); - result = extendOrderWithAssumptions(std::get<0>(criticalTuple), assumptionMaker, val1, val2, assumptions); - } else { - assert(false); + return Monotonicity::Unknown; } - orderWatch.stop(); - return result; - } - - template <typename ValueType> - std::map<storm::analysis::Order*, std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>>> MonotonicityChecker<ValueType>::extendOrderWithAssumptions(storm::analysis::Order* order, storm::analysis::AssumptionMaker<ValueType>* assumptionMaker, uint_fast64_t val1, uint_fast64_t val2, std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>> assumptions) { - std::map<storm::analysis::Order*, std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>>> result; - - auto numberOfStates = model->getNumberOfStates(); - if (val1 == numberOfStates || val2 == numberOfStates) { - assert (val1 == val2); - assert (order->getAddedStates()->size() == order->getAddedStates()->getNumberOfSetBits()); - result.insert(std::pair<storm::analysis::Order*, std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>>>(order, assumptions)); - } else { - // Make the three assumptions - auto assumptionTriple = assumptionMaker->createAndCheckAssumption(val1, val2, order); - assert (assumptionTriple.size() == 3); - auto itr = assumptionTriple.begin(); - auto assumption1 = *itr; - ++itr; - auto assumption2 = *itr; - ++itr; - auto assumption3 = *itr; - - if (assumption1.second != AssumptionStatus::INVALID) { - auto orderCopy = new Order(order); - auto assumptionsCopy = std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>>(assumptions); - - if (assumption1.second == AssumptionStatus::UNKNOWN) { - // only add assumption to the set of assumptions if it is unknown if it is valid - assumptionsCopy.push_back(assumption1.first); - } - auto criticalTuple = extender->extendOrder(orderCopy, assumption1.first); - if (somewhereMonotonicity(std::get<0>(criticalTuple))) { - auto map = extendOrderWithAssumptions(std::get<0>(criticalTuple), assumptionMaker, - std::get<1>(criticalTuple), std::get<2>(criticalTuple), - assumptionsCopy); - result.insert(map.begin(), map.end()); - } - } - - if (assumption2.second != AssumptionStatus::INVALID) { - auto orderCopy = new Order(order); - auto assumptionsCopy = std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>>(assumptions); - - if (assumption2.second == AssumptionStatus::UNKNOWN) { - assumptionsCopy.push_back(assumption2.first); - } - - auto criticalTuple = extender->extendOrder(orderCopy, assumption2.first); - if (somewhereMonotonicity(std::get<0>(criticalTuple))) { - auto map = extendOrderWithAssumptions(std::get<0>(criticalTuple), assumptionMaker, - std::get<1>(criticalTuple), std::get<2>(criticalTuple), - assumptionsCopy); - result.insert(map.begin(), map.end()); - } - } - - if (assumption3.second != AssumptionStatus::INVALID) { - // Here we can use the original order and assumptions set - if (assumption3.second == AssumptionStatus::UNKNOWN) { - assumptions.push_back(assumption3.first); - } - - auto criticalTuple = extender->extendOrder(order, assumption3.first); - if (somewhereMonotonicity(std::get<0>(criticalTuple))) { - auto map = extendOrderWithAssumptions(std::get<0>(criticalTuple), assumptionMaker, - std::get<1>(criticalTuple), std::get<2>(criticalTuple), - assumptions); - result.insert(map.begin(), map.end()); - } - } + if (succSize == 2) { + // In this case we can ignore the last entry, as this will have a probability of 1 - the other + succSize = 1; } - return result; - } - template <typename ValueType> - ValueType MonotonicityChecker<ValueType>::getDerivative(ValueType function, typename utility::parametric::VariableType<ValueType>::type var) { - if (function.isConstant()) { - return storm::utility::zero<ValueType>(); - } - if ((derivatives[function]).find(var) == (derivatives[function]).end()) { - (derivatives[function])[var] = function.derivative(var); - } - - return (derivatives[function])[var]; - } - - template <typename ValueType> - std::map<typename utility::parametric::VariableType<ValueType>::type, std::pair<bool, bool>> MonotonicityChecker<ValueType>::analyseMonotonicity(uint_fast64_t j, storm::analysis::Order* order, storm::storage::SparseMatrix<ValueType> matrix) { - std::map<typename utility::parametric::VariableType<ValueType>::type, std::pair<bool, bool>> varsMonotone; - - // go over all rows, check for each row local monotonicity - for (uint_fast64_t i = 0; i < matrix.getColumnCount(); ++i) { - auto row = matrix.getRow(i); - // only enter if you are in a state with at least two successors (so there must be successors, - // and first prob shouldn't be 1) - if (row.begin() != row.end() && !row.begin()->getValue().isOne()) { - std::map<uint_fast64_t, ValueType> transitions; - - // Gather all states which are reached with a non constant probability - auto states = new storm::storage::BitVector(matrix.getColumnCount()); - std::set<typename utility::parametric::VariableType<ValueType>::type> vars; - for (auto const& entry : row) { - if (!entry.getValue().isConstant()) { - // only analyse take non constant transitions - transitions.insert(std::pair<uint_fast64_t, ValueType>(entry.getColumn(), entry.getValue())); - for (auto const& var:entry.getValue().gatherVariables()) { - vars.insert(var); - states->set(entry.getColumn()); - } - } - } - - // Copy info from checkOnSamples - if (checkSamples) { - for (auto var : vars) { - assert (resultCheckOnSamples.find(var) != resultCheckOnSamples.end()); - if (varsMonotone.find(var) == varsMonotone.end()) { - varsMonotone[var].first = resultCheckOnSamples[var].first; - varsMonotone[var].second = resultCheckOnSamples[var].second; - } else { - varsMonotone[var].first &= resultCheckOnSamples[var].first; - varsMonotone[var].second &= resultCheckOnSamples[var].second; - } - } - } else { - for (auto var : vars) { - if (varsMonotone.find(var) == varsMonotone.end()) { - varsMonotone[var].first = true; - varsMonotone[var].second = true; - } - } - } - - - - // Sort the states based on the order - auto sortedStates = order->sortStates(states); - if (sortedStates[sortedStates.size() - 1] == matrix.getColumnCount()) { - // If the states are not all sorted, we still might obtain some monotonicity - for (auto var: vars) { - // current value of monotonicity - std::pair<bool, bool> *value = &varsMonotone.find(var)->second; - - // Go over all transitions to successor states, compare all of them - for (auto itr2 = transitions.begin(); (value->first || value->second) - && itr2 != transitions.end(); ++itr2) { - for (auto itr3 = transitions.begin(); (value->first || value->second) - && itr3 != transitions.end(); ++itr3) { - if (itr2->first < itr3->first) { - - auto derivative2 = getDerivative(itr2->second, var); - auto derivative3 = getDerivative(itr3->second, var); - - auto compare = order->compare(itr2->first, itr3->first); - - if (compare == Order::ABOVE) { - // As the first state (itr2) is above the second state (itr3) it - // is sufficient to look at the derivative of itr2. - std::pair<bool, bool> mon2; - mon2 = checkDerivative(derivative2, region); - value->first &= mon2.first; - value->second &= mon2.second; - } else if (compare == Order::BELOW) { - // As the second state (itr3) is above the first state (itr2) it - // is sufficient to look at the derivative of itr3. - std::pair<bool, bool> mon3; - - mon3 = checkDerivative(derivative3, region); - value->first &= mon3.first; - value->second &= mon3.second; - } else if (compare == Order::SAME) { - // Behaviour doesn't matter, as the states are at the same level. - } else { - // only if derivatives are the same we can continue - if (derivative2 != derivative3) { - // As the relation between the states is unknown, we can't claim - // anything about the monotonicity. - value->first = false; - value->second = false; - } - - } - } - } - } - } - } else { - // The states are all sorted - for (auto var : vars) { - std::pair<bool, bool> *value = &varsMonotone.find(var)->second; - bool change = false; - for (auto const &i : sortedStates) { - auto res = checkDerivative(getDerivative(transitions[i], var), region); - change = change || (!(value->first && value->second) // they do not hold both - && ((value->first && !res.first) - || (value->second && !res.second))); - - if (change) { - value->first &= res.second; - value->second &= res.first; - } else { - value->first &= res.first; - value->second &= res.second; - } - if (!value->first && !value->second) { - break; - } - } - - } - } + // First check as long as it stays constant and either incr or decr + bool allowedToSwap = true; + Monotonicity localMonotonicity = Monotonicity::Constant; + uint_fast64_t index = 0; + while (index < succSize && localMonotonicity == Monotonicity::Constant) { + auto itr = std::find(succs.begin(), succs.end(), succsSorted[index]); + auto newIndex = std::distance(succs.begin(), itr); + auto transitionMon = succsMonUnsorted[newIndex]; + localMonotonicity = transitionMon; + if (transitionMon == Monotonicity::Not && succSize != 1) { + localMonotonicity = Monotonicity::Unknown; } + index++; } - return varsMonotone; - } - template <typename ValueType> - bool MonotonicityChecker<ValueType>::somewhereMonotonicity(Order* order) { - std::shared_ptr<storm::models::sparse::Model<ValueType>> sparseModel = model->as<storm::models::sparse::Model<ValueType>>(); - auto matrix = sparseModel->getTransitionMatrix(); - - std::map<typename utility::parametric::VariableType<ValueType>::type, std::pair<bool, bool>> varsMonotone; + while (index < succSize && localMonotonicity != Monotonicity::Not && localMonotonicity != Monotonicity::Unknown) { + // We get here as soon as we have seen incr/decr once + auto itr = std::find(succs.begin(), succs.end(), succsSorted[index]); + auto newIndex = std::distance(succs.begin(), itr); + auto transitionMon = succsMonUnsorted[newIndex]; - for (uint_fast64_t i = 0; i < matrix.getColumnCount(); ++i) { - // go over all rows - auto row = matrix.getRow(i); - auto first = (*row.begin()); - if (first.getValue() != ValueType(1)) { - std::map<uint_fast64_t, ValueType> transitions; - - for (auto itr = row.begin(); itr != row.end(); ++itr) { - transitions.insert(std::pair<uint_fast64_t, ValueType>((*itr).getColumn(), (*itr).getValue())); + if (transitionMon == Monotonicity::Not || transitionMon == Monotonicity::Unknown) { + return Monotonicity::Unknown; + } + if (allowedToSwap) { + // So far we have only seen constant and either incr or decr, but not both + if (transitionMon != Monotonicity::Constant && transitionMon != localMonotonicity) { + allowedToSwap = false; } - - auto val = first.getValue(); - auto vars = val.gatherVariables(); - // Copy info from checkOnSamples - if (checkSamples) { - for (auto var : vars) { - assert (resultCheckOnSamples.find(var) != resultCheckOnSamples.end()); - if (varsMonotone.find(var) == varsMonotone.end()) { - varsMonotone[var].first = resultCheckOnSamples[var].first; - varsMonotone[var].second = resultCheckOnSamples[var].second; - } else { - varsMonotone[var].first &= resultCheckOnSamples[var].first; - varsMonotone[var].second &= resultCheckOnSamples[var].second; - } - } - } else { - for (auto var : vars) { - if (varsMonotone.find(var) == varsMonotone.end()) { - varsMonotone[var].first = true; - varsMonotone[var].second = true; - } - } - } - - for (auto var: vars) { - // current value of monotonicity - std::pair<bool, bool> *value = &varsMonotone.find(var)->second; - - // Go over all transitions to successor states, compare all of them - for (auto itr2 = transitions.begin(); (value->first || value->second) - && itr2 != transitions.end(); ++itr2) { - for (auto itr3 = transitions.begin(); (value->first || value->second) - && itr3 != transitions.end(); ++itr3) { - if (itr2->first < itr3->first) { - - auto derivative2 = getDerivative(itr2->second, var); - auto derivative3 = getDerivative(itr3->second, var); - - auto compare = order->compare(itr2->first, itr3->first); - - if (compare == Order::ABOVE) { - // As the first state (itr2) is above the second state (itr3) it - // is sufficient to look at the derivative of itr2. - std::pair<bool, bool> mon2; - mon2 = checkDerivative(derivative2, region); - value->first &= mon2.first; - value->second &= mon2.second; - } else if (compare == Order::BELOW) { - // As the second state (itr3) is above the first state (itr2) it - // is sufficient to look at the derivative of itr3. - std::pair<bool, bool> mon3; - - mon3 = checkDerivative(derivative3, region); - value->first &= mon3.first; - value->second &= mon3.second; - } else if (compare == Order::SAME) { - // Behaviour doesn't matter, as the states are at the same level. - } else { - // As the relation between the states is unknown, we don't do anything - } - } - } - } + } else if (!allowedToSwap) { + // So we have been at the point where we changed from incr to decr (or decr to incr) + if (transitionMon == localMonotonicity || transitionMon == Monotonicity::Not || transitionMon == Monotonicity::Unknown) { + localMonotonicity = Monotonicity::Unknown; } } + index++; } - - bool result = false; - - for (auto itr = varsMonotone.begin(); !result && itr != varsMonotone.end(); ++itr) { - result = itr->second.first || itr->second.second; - } - return result; + return localMonotonicity; } + /*** Private methods ***/ template <typename ValueType> - std::map<typename utility::parametric::VariableType<ValueType>::type, std::pair<bool, bool>> MonotonicityChecker<ValueType>::checkOnSamples(std::shared_ptr<storm::models::sparse::Dtmc<ValueType>> model, uint_fast64_t numberOfSamples) { - storm::utility::Stopwatch samplesWatch(true); - - std::map<typename utility::parametric::VariableType<ValueType>::type, std::pair<bool, bool>> result; - - auto instantiator = storm::utility::ModelInstantiator<storm::models::sparse::Dtmc<ValueType>, storm::models::sparse::Dtmc<double>>(*model); - auto matrix = model->getTransitionMatrix(); - std::set<typename utility::parametric::VariableType<ValueType>::type> variables = storm::models::sparse::getProbabilityParameters(*model); - - // For each of the variables create a model in which we only change the value for this specific variable - for (auto itr = variables.begin(); itr != variables.end(); ++itr) { - double previous = -1; - bool monDecr = true; - bool monIncr = true; - - // Check monotonicity in variable (*itr) by instantiating the model - // all other variables fixed on lb, only increasing (*itr) - for (uint_fast64_t i = 0; (monDecr || monIncr) && i < numberOfSamples; ++i) { - // Create valuation - auto valuation = storm::utility::parametric::Valuation<ValueType>(); - for (auto itr2 = variables.begin(); itr2 != variables.end(); ++itr2) { - // Only change value for current variable - if ((*itr) == (*itr2)) { - auto lb = region.getLowerBoundary(itr->name()); - auto ub = region.getUpperBoundary(itr->name()); - // Creates samples between lb and ub, that is: lb, lb + (ub-lb)/(#samples -1), lb + 2* (ub-lb)/(#samples -1), ..., ub - valuation[*itr2] = utility::convertNumber<typename utility::parametric::CoefficientType<ValueType>::type>(lb + i*(ub-lb)/(numberOfSamples-1)); - } else { - auto lb = region.getLowerBoundary(itr->name()); - valuation[*itr2] = utility::convertNumber<typename utility::parametric::CoefficientType<ValueType>::type>(lb); - } - } - - // Instantiate model and get result - storm::models::sparse::Dtmc<double> sampleModel = instantiator.instantiate(valuation); - auto checker = storm::modelchecker::SparseDtmcPrctlModelChecker<storm::models::sparse::Dtmc<double>>(sampleModel); - std::unique_ptr<storm::modelchecker::CheckResult> checkResult; - auto formula = formulas[0]; - if (formula->isProbabilityOperatorFormula() && - formula->asProbabilityOperatorFormula().getSubformula().isUntilFormula()) { - const storm::modelchecker::CheckTask<storm::logic::UntilFormula, double> checkTask = storm::modelchecker::CheckTask<storm::logic::UntilFormula, double>( - (*formula).asProbabilityOperatorFormula().getSubformula().asUntilFormula()); - checkResult = checker.computeUntilProbabilities(Environment(), checkTask); - } else if (formula->isProbabilityOperatorFormula() && - formula->asProbabilityOperatorFormula().getSubformula().isEventuallyFormula()) { - const storm::modelchecker::CheckTask<storm::logic::EventuallyFormula, double> checkTask = storm::modelchecker::CheckTask<storm::logic::EventuallyFormula, double>( - (*formula).asProbabilityOperatorFormula().getSubformula().asEventuallyFormula()); - checkResult = checker.computeReachabilityProbabilities(Environment(), checkTask); - } else { - STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, - "Expecting until or eventually formula"); - } - auto quantitativeResult = checkResult->asExplicitQuantitativeCheckResult<double>(); - std::vector<double> values = quantitativeResult.getValueVector(); - auto initialStates = model->getInitialStates(); - double initial = 0; - // Get total probability from initial states - for (auto j = initialStates.getNextSetIndex(0); j < model->getNumberOfStates(); j = initialStates.getNextSetIndex(j+1)) { - initial += values[j]; - } - // Calculate difference with result for previous valuation - assert (initial >= 0-precision && initial <= 1+precision); - double diff = previous - initial; - assert (previous == -1 || diff >= -1-precision && diff <= 1 + precision); - if (previous != -1 && (diff > precision || diff < -precision)) { - monDecr &= diff > precision; // then previous value is larger than the current value from the initial states - monIncr &= diff < -precision; - } - previous = initial; - } - result.insert(std::pair<typename utility::parametric::VariableType<ValueType>::type, std::pair<bool, bool>>(*itr, std::pair<bool,bool>(monIncr, monDecr))); + typename MonotonicityChecker<ValueType>::Monotonicity MonotonicityChecker<ValueType>::checkTransitionMonRes(ValueType function, typename MonotonicityChecker<ValueType>::VariableType param, typename MonotonicityChecker<ValueType>::Region region) { + std::pair<bool, bool> res = MonotonicityChecker<ValueType>::checkDerivative(getDerivative(function, param), region); + if (res.first && !res.second) { + return Monotonicity::Incr; + } else if (!res.first && res.second) { + return Monotonicity::Decr; + } else if (res.first && res.second) { + return Monotonicity::Constant; + } else { + return Monotonicity::Not; } - - samplesWatch.stop(); - resultCheckOnSamples = result; - return result; } template <typename ValueType> - std::map<typename utility::parametric::VariableType<ValueType>::type, std::pair<bool, bool>> MonotonicityChecker<ValueType>::checkOnSamples(std::shared_ptr<storm::models::sparse::Mdp<ValueType>> model, uint_fast64_t numberOfSamples) { - storm::utility::Stopwatch samplesWatch(true); - - std::map<typename utility::parametric::VariableType<ValueType>::type, std::pair<bool, bool>> result; - - auto instantiator = storm::utility::ModelInstantiator<storm::models::sparse::Mdp<ValueType>, storm::models::sparse::Mdp<double>>(*model); - auto matrix = model->getTransitionMatrix(); - std::set<typename utility::parametric::VariableType<ValueType>::type> variables = storm::models::sparse::getProbabilityParameters(*model); - - for (auto itr = variables.begin(); itr != variables.end(); ++itr) { - double previous = -1; - bool monDecr = true; - bool monIncr = true; - - for (uint_fast64_t i = 0; i < numberOfSamples; ++i) { - auto valuation = storm::utility::parametric::Valuation<ValueType>(); - for (auto itr2 = variables.begin(); itr2 != variables.end(); ++itr2) { - // Only change value for current variable - if ((*itr) == (*itr2)) { - auto val = std::pair<typename utility::parametric::VariableType<ValueType>::type, typename utility::parametric::CoefficientType<ValueType>::type>( - (*itr2), storm::utility::convertNumber<typename utility::parametric::CoefficientType<ValueType>::type>( - boost::lexical_cast<std::string>((i + 1) / (double(numberOfSamples + 1))))); - valuation.insert(val); - } else { - auto val = std::pair<typename utility::parametric::VariableType<ValueType>::type, typename utility::parametric::CoefficientType<ValueType>::type>( - (*itr2), storm::utility::convertNumber<typename utility::parametric::CoefficientType<ValueType>::type>( - boost::lexical_cast<std::string>((1) / (double(numberOfSamples + 1))))); - valuation.insert(val); - } - } - storm::models::sparse::Mdp<double> sampleModel = instantiator.instantiate(valuation); - auto checker = storm::modelchecker::SparseMdpPrctlModelChecker<storm::models::sparse::Mdp<double>>(sampleModel); - std::unique_ptr<storm::modelchecker::CheckResult> checkResult; - auto formula = formulas[0]; - if (formula->isProbabilityOperatorFormula() && - formula->asProbabilityOperatorFormula().getSubformula().isUntilFormula()) { - const storm::modelchecker::CheckTask<storm::logic::UntilFormula, double> checkTask = storm::modelchecker::CheckTask<storm::logic::UntilFormula, double>( - (*formula).asProbabilityOperatorFormula().getSubformula().asUntilFormula()); - checkResult = checker.computeUntilProbabilities(Environment(), checkTask); - } else if (formula->isProbabilityOperatorFormula() && - formula->asProbabilityOperatorFormula().getSubformula().isEventuallyFormula()) { - const storm::modelchecker::CheckTask<storm::logic::EventuallyFormula, double> checkTask = storm::modelchecker::CheckTask<storm::logic::EventuallyFormula, double>( - (*formula).asProbabilityOperatorFormula().getSubformula().asEventuallyFormula()); - checkResult = checker.computeReachabilityProbabilities(Environment(), checkTask); - } else { - STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, - "Expecting until or eventually formula"); - } - auto quantitativeResult = checkResult->asExplicitQuantitativeCheckResult<double>(); - std::vector<double> values = quantitativeResult.getValueVector(); - auto initialStates = model->getInitialStates(); - double initial = 0; - for (auto i = initialStates.getNextSetIndex(0); i < model->getNumberOfStates(); i = initialStates.getNextSetIndex(i+1)) { - initial += values[i]; - } - assert (initial >= precision && initial <= 1+precision); - double diff = previous - initial; - assert (previous == -1 || diff >= -1-precision && diff <= 1 + precision); - if (previous != -1 && (diff > precision || diff < -precision)) { - monDecr &= diff > precision; // then previous value is larger than the current value from the initial states - monIncr &= diff < -precision; - } - previous = initial; - } - result.insert(std::pair<typename utility::parametric::VariableType<ValueType>::type, std::pair<bool, bool>>(*itr, std::pair<bool,bool>(monIncr, monDecr))); + ValueType& MonotonicityChecker<ValueType>::getDerivative(ValueType function, typename MonotonicityChecker<ValueType>::VariableType var) { + auto& derivativeMap = derivatives[function]; + if (derivativeMap.find(var) == derivativeMap.end()) { + derivativeMap[var] = function.derivative(var); } - - samplesWatch.stop(); - resultCheckOnSamples = result; - return result; + return derivativeMap[var]; } - template class MonotonicityChecker<storm::RationalFunction>; + template class MonotonicityChecker<RationalFunction>; } } diff --git a/src/storm-pars/analysis/MonotonicityChecker.h b/src/storm-pars/analysis/MonotonicityChecker.h index 31fc0e193..9f6dc1ddd 100644 --- a/src/storm-pars/analysis/MonotonicityChecker.h +++ b/src/storm-pars/analysis/MonotonicityChecker.h @@ -2,22 +2,22 @@ #define STORM_MONOTONICITYCHECKER_H #include <map> +#include <boost/container/flat_map.hpp> #include "Order.h" -#include "OrderExtender.h" -#include "AssumptionMaker.h" +#include "LocalMonotonicityResult.h" +#include "MonotonicityResult.h" +#include "storm-pars/storage/ParameterRegion.h" + + +#include "storm/solver/Z3SmtSolver.h" + +#include "storm/storage/SparseMatrix.h" #include "storm/storage/expressions/BinaryRelationExpression.h" #include "storm/storage/expressions/ExpressionManager.h" - #include "storm/storage/expressions/RationalFunctionToExpression.h" -#include "storm/utility/constants.h" -#include "storm/models/ModelBase.h" -#include "storm/models/sparse/Dtmc.h" -#include "storm/models/sparse/Mdp.h" -#include "storm/logic/Formula.h" -#include "storm/storage/SparseMatrix.h" -#include "storm-pars/api/region.h" -#include "storm/solver/Z3SmtSolver.h" +#include "storm/utility/constants.h" +#include "storm/utility/solver.h" namespace storm { namespace analysis { @@ -26,34 +26,26 @@ namespace storm { class MonotonicityChecker { public: - /*! - * Constructor of MonotonicityChecker - * @param model the model considered - * @param formula the formula considered - * @param regions the regions to consider - * @param validate whether or not assumptions are to be validated - * @param numberOfSamples number of samples taken for monotonicity checking, default 0, - * if 0 then no check on samples is executed - * @param precision precision on which the samples are compared - */ - MonotonicityChecker(std::shared_ptr<storm::models::ModelBase> model, std::vector<std::shared_ptr<storm::logic::Formula const>> formulas, std::vector<storm::storage::ParameterRegion<ValueType>> regions, bool validate, uint_fast64_t numberOfSamples=0, double const& precision=0.000001); - - /*! - * Checks for model and formula as provided in constructor for monotonicity - */ - std::map<storm::analysis::Order*, std::map<typename utility::parametric::VariableType<ValueType>::type, std::pair<bool, bool>>> checkMonotonicity(std::ostream& outfile); + typedef typename utility::parametric::VariableType<ValueType>::type VariableType; + typedef typename utility::parametric::CoefficientType<ValueType>::type CoefficientType; + typedef typename MonotonicityResult<VariableType>::Monotonicity Monotonicity; + typedef typename storage::ParameterRegion<ValueType> Region; /*! - * Checks if monotonicity can be found in this order. Unordered states are not checked + * Constructs a new MonotonicityChecker object. + * + * @param matrix The Matrix of the model. */ - bool somewhereMonotonicity(storm::analysis::Order* order) ; + MonotonicityChecker(storage::SparseMatrix<ValueType> matrix); /*! - * Checks if a derivative >=0 or/and <=0 - * @param derivative The derivative you want to check - * @return pair of bools, >= 0 and <= 0 + * Checks if a derivative >=0 or/and <=0. + * + * @param derivative The derivative you want to check. + * @param reg The region of the parameters. + * @return Pair of bools, >= 0 and <= 0. */ - static std::pair<bool, bool> checkDerivative(ValueType derivative, storm::storage::ParameterRegion<ValueType> reg) { + static std::pair<bool, bool> checkDerivative(ValueType derivative, storage::ParameterRegion<ValueType> reg) { bool monIncr = false; bool monDecr = false; @@ -64,86 +56,58 @@ namespace storm { monIncr = derivative.constantPart() >= 0; monDecr = derivative.constantPart() <= 0; } else { + std::shared_ptr<utility::solver::SmtSolverFactory> smtSolverFactory = std::make_shared<utility::solver::MathsatSmtSolverFactory>(); + std::shared_ptr<expressions::ExpressionManager> manager(new expressions::ExpressionManager()); + solver::Z3SmtSolver s(*manager); + std::set<VariableType> variables = derivative.gatherVariables(); - std::shared_ptr<storm::utility::solver::SmtSolverFactory> smtSolverFactory = std::make_shared<storm::utility::solver::MathsatSmtSolverFactory>(); - std::shared_ptr<storm::expressions::ExpressionManager> manager( - new storm::expressions::ExpressionManager()); - - storm::solver::Z3SmtSolver s(*manager); - - std::set<typename utility::parametric::VariableType<ValueType>::type> variables = derivative.gatherVariables(); - - + expressions::Expression exprBounds = manager->boolean(true); for (auto variable : variables) { - manager->declareRationalVariable(variable.name()); - - } - storm::expressions::Expression exprBounds = manager->boolean(true); - auto managervars = manager->getVariables(); - for (auto var : managervars) { - auto lb = storm::utility::convertNumber<storm::RationalNumber>(reg.getLowerBoundary(var.getName())); - auto ub = storm::utility::convertNumber<storm::RationalNumber>(reg.getUpperBoundary(var.getName())); - exprBounds = exprBounds && manager->rational(lb) < var && var < manager->rational(ub); + auto managerVariable = manager->declareRationalVariable(variable.name()); + auto lb = utility::convertNumber<RationalNumber>(reg.getLowerBoundary(variable)); + auto ub = utility::convertNumber<RationalNumber>(reg.getUpperBoundary(variable)); + exprBounds = exprBounds && manager->rational(lb) < managerVariable && managerVariable < manager->rational(ub); } - assert (s.check() == storm::solver::SmtSolver::CheckResult::Sat); - auto converter = storm::expressions::RationalFunctionToExpression<ValueType>(manager); + auto converter = expressions::RationalFunctionToExpression<ValueType>(manager); - // < 0 so not monotone increasing - storm::expressions::Expression exprToCheck = - converter.toExpression(derivative) < manager->rational(0); + // < 0, so not monotone increasing. If this is unsat, then it should be monotone increasing. + expressions::Expression exprToCheck = converter.toExpression(derivative) < manager->rational(0); s.add(exprBounds); s.add(exprToCheck); - // If it is unsatisfiable then it should be monotone increasing - monIncr = s.check() == storm::solver::SmtSolver::CheckResult::Unsat; - - // > 0 so not monotone decreasing - exprToCheck = - converter.toExpression(derivative) > manager->rational(0); + monIncr = s.check() == solver::SmtSolver::CheckResult::Unsat; + // > 0, so not monotone decreasing. If this is unsat it should be monotone decreasing. + exprToCheck = converter.toExpression(derivative) > manager->rational(0); s.reset(); s.add(exprBounds); - assert (s.check() == storm::solver::SmtSolver::CheckResult::Sat); s.add(exprToCheck); - monDecr = s.check() == storm::solver::SmtSolver::CheckResult::Unsat; + monDecr = s.check() == solver::SmtSolver::CheckResult::Unsat; } assert (!(monIncr && monDecr) || derivative.isZero()); return std::pair<bool, bool>(monIncr, monDecr); } - private: - std::map<storm::analysis::Order*, std::map<typename utility::parametric::VariableType<ValueType>::type, std::pair<bool, bool>>> checkMonotonicity(std::ostream& outfile, std::map<storm::analysis::Order*, std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>>> map, storm::storage::SparseMatrix<ValueType> matrix); - - std::map<typename utility::parametric::VariableType<ValueType>::type, std::pair<bool, bool>> analyseMonotonicity(uint_fast64_t i, Order* order, storm::storage::SparseMatrix<ValueType> matrix) ; - - std::map<Order*, std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>>> createOrder(); - - std::map<typename utility::parametric::VariableType<ValueType>::type, std::pair<bool, bool>> checkOnSamples(std::shared_ptr<storm::models::sparse::Dtmc<ValueType>> model, uint_fast64_t numberOfSamples); - - std::map<typename utility::parametric::VariableType<ValueType>::type, std::pair<bool, bool>> checkOnSamples(std::shared_ptr<storm::models::sparse::Mdp<ValueType>> model, uint_fast64_t numberOfSamples); - - std::unordered_map<ValueType, std::unordered_map<typename utility::parametric::VariableType<ValueType>::type, ValueType>> derivatives; - - ValueType getDerivative(ValueType function, typename utility::parametric::VariableType<ValueType>::type var); - - std::map<Order*, std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>>> extendOrderWithAssumptions(Order* order, AssumptionMaker<ValueType>* assumptionMaker, uint_fast64_t val1, uint_fast64_t val2, std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>> assumptions); - - std::shared_ptr<storm::models::ModelBase> model; - - std::vector<std::shared_ptr<storm::logic::Formula const>> formulas; - - bool validate; - - bool checkSamples; + /*! + * Checks for local monotonicity at the given state. + * + * @param order The order on which the monotonicity should be checked. + * @param state The considerd state. + * @param var The variable in which we check for monotonicity. + * @param region The region on which we check the monotonicity. + * @return Incr, Decr, Constant, Unknown or Not + */ + Monotonicity checkLocalMonotonicity(std::shared_ptr<Order> const & order, uint_fast64_t state, VariableType const& var, storage::ParameterRegion<ValueType> const& region); - std::map<typename utility::parametric::VariableType<ValueType>::type, std::pair<bool, bool>> resultCheckOnSamples; + private: + Monotonicity checkTransitionMonRes(ValueType function, VariableType param, Region region); - OrderExtender<ValueType> *extender; + ValueType& getDerivative(ValueType function, VariableType var); - double precision; + storage::SparseMatrix<ValueType> matrix; - storm::storage::ParameterRegion<ValueType> region; + boost::container::flat_map<ValueType, boost::container::flat_map<VariableType, ValueType>> derivatives; }; } } diff --git a/src/storm-pars/analysis/MonotonicityHelper.cpp b/src/storm-pars/analysis/MonotonicityHelper.cpp new file mode 100644 index 000000000..07cf5eb04 --- /dev/null +++ b/src/storm-pars/analysis/MonotonicityHelper.cpp @@ -0,0 +1,325 @@ +#include "MonotonicityHelper.h" + +#include "storm/exceptions/NotSupportedException.h" +#include "storm/exceptions/InvalidOperationException.h" + +#include "storm/models/ModelType.h" + +#include "storm/modelchecker/results/CheckResult.h" + +#include "storm/modelchecker/results/ExplicitQuantitativeCheckResult.h" + +#include "storm-pars/analysis/AssumptionChecker.h" + + +namespace storm { + namespace analysis { + /*** Constructor ***/ + template <typename ValueType, typename ConstantType> + MonotonicityHelper<ValueType, ConstantType>::MonotonicityHelper(std::shared_ptr<models::sparse::Model<ValueType>> model, std::vector<std::shared_ptr<logic::Formula const>> formulas, std::vector<storage::ParameterRegion<ValueType>> regions, uint_fast64_t numberOfSamples, double const& precision, bool dotOutput) : assumptionMaker(model->getTransitionMatrix()){ + assert (model != nullptr); + STORM_LOG_THROW(regions.size() <= 1, exceptions::NotSupportedException, "Monotonicity checking is not (yet) supported for multiple regions"); + STORM_LOG_THROW(formulas.size() <= 1, exceptions::NotSupportedException, "Monotonicity checking is not (yet) supported for multiple formulas"); + + this->model = model; + this->formulas = formulas; + this->precision = utility::convertNumber<ConstantType>(precision); + this->matrix = model->getTransitionMatrix(); + this->dotOutput = dotOutput; + + if (regions.size() == 1) { + this->region = *(regions.begin()); + } else { + typename storage::ParameterRegion<ValueType>::Valuation lowerBoundaries; + typename storage::ParameterRegion<ValueType>::Valuation upperBoundaries; + std::set<VariableType> vars; + vars = models::sparse::getProbabilityParameters(*model); + for (auto var : vars) { + typename storage::ParameterRegion<ValueType>::CoefficientType lb = utility::convertNumber<CoefficientType>(0 + precision) ; + typename storage::ParameterRegion<ValueType>::CoefficientType ub = utility::convertNumber<CoefficientType>(1 - precision) ; + lowerBoundaries.insert(std::make_pair(var, lb)); + upperBoundaries.insert(std::make_pair(var, ub)); + } + this->region = storage::ParameterRegion<ValueType>(std::move(lowerBoundaries), std::move(upperBoundaries)); + } + + if (numberOfSamples > 2) { + // sampling + if (model->isOfType(models::ModelType::Dtmc)) { + checkMonotonicityOnSamples(model->template as<models::sparse::Dtmc<ValueType>>(), numberOfSamples); + } else if (model->isOfType(models::ModelType::Mdp)) { + checkMonotonicityOnSamples(model->template as<models::sparse::Mdp<ValueType>>(), numberOfSamples); + } + checkSamples = true; + } else { + if (numberOfSamples > 0) { + STORM_LOG_WARN("At least 3 sample points are needed to check for monotonicity on samples, not using samples for now"); + } + checkSamples = false; + } + + this->extender = new analysis::OrderExtender<ValueType, ConstantType>(model, formulas[0]); + + for (auto i = 0; i < matrix.getRowCount(); ++i) { + std::set<VariableType> occurringVariables; + + for (auto &entry : matrix.getRow(i)) { + storm::utility::parametric::gatherOccurringVariables(entry.getValue(), occurringVariables); + } + for (auto& var : occurringVariables) { + occuringStatesAtVariable[var].push_back(i); + } + } + } + + + /*** Public methods ***/ + template <typename ValueType, typename ConstantType> + std::map<std::shared_ptr<Order>, std::pair<std::shared_ptr<MonotonicityResult<typename MonotonicityHelper<ValueType, ConstantType>::VariableType>>, std::vector<std::shared_ptr<expressions::BinaryRelationExpression>>>> MonotonicityHelper<ValueType, ConstantType>::checkMonotonicityInBuild(std::ostream& outfile, bool usePLA, std::string dotOutfileName) { + if (usePLA) { + storm::utility::Stopwatch plaWatch(true); + this->extender->initializeMinMaxValues(region); + plaWatch.stop(); + STORM_PRINT(std::endl << "Total time for pla checking: " << plaWatch << "." << std::endl << std::endl); + } + createOrder(); + + //output of results + for (auto itr : monResults) { + if (itr.first != nullptr) { + std::cout << "Number of done states: " << itr.first->getNumberOfDoneStates() << std::endl; + } + if (checkSamples) { + for (auto & entry : resultCheckOnSamples.getMonotonicityResult()) { + if (entry.second == Monotonicity::Not) { + itr.second.first->updateMonotonicityResult(entry.first, entry.second, true); + } + } + } + std::string temp = itr.second.first->toString(); + bool first = true; + for (auto& assumption : itr.second.second) { + if (!first) { + outfile << " & "; + } else { + outfile << "Assumptions: " << std::endl << " "; + first = false; + } + outfile << *assumption; + } + if (!first) { + outfile << std::endl; + } else { + outfile << "No Assumptions" << std::endl; + } + outfile << "Monotonicity Result: " << std::endl << " " << temp << std::endl << std::endl; + } + + if (monResults.size() == 0) { + outfile << "No monotonicity found, as the order is insufficient" << std::endl; + if (checkSamples) { + outfile << "Monotonicity Result on samples: " << resultCheckOnSamples.toString() << std::endl; + } + } + + //dotoutput + if (dotOutput) { + STORM_LOG_WARN_COND(monResults.size() <= 10, "Too many Reachability Orders. Dot Output will only be created for 10."); + int i = 0; + auto orderItr = monResults.begin(); + while (i < 10 && orderItr != monResults.end()) { + std::ofstream dotOutfile; + std::string name = dotOutfileName + std::to_string(i); + utility::openFile(name, dotOutfile); + dotOutfile << "Assumptions:" << std::endl; + auto assumptionItr = orderItr->second.second.begin(); + while (assumptionItr != orderItr->second.second.end()) { + dotOutfile << *assumptionItr << std::endl; + dotOutfile << std::endl; + assumptionItr++; + } + dotOutfile << std::endl; + orderItr->first->dotOutputToFile(dotOutfile); + utility::closeFile(dotOutfile); + i++; + orderItr++; + } + } + return monResults; + } + + /*** Private methods ***/ + template <typename ValueType, typename ConstantType> + void MonotonicityHelper<ValueType, ConstantType>::createOrder() { + // Transform to Orders + std::tuple<std::shared_ptr<Order>, uint_fast64_t, uint_fast64_t> criticalTuple; + + // Create initial order + auto monRes = std::make_shared<MonotonicityResult<VariableType>>(MonotonicityResult<VariableType>()); + criticalTuple = extender->toOrder(region, monRes); + // Continue based on not (yet) sorted states + std::map<std::shared_ptr<Order>, std::vector<std::shared_ptr<expressions::BinaryRelationExpression>>> result; + + auto val1 = std::get<1>(criticalTuple); + auto val2 = std::get<2>(criticalTuple); + auto numberOfStates = model->getNumberOfStates(); + std::vector<std::shared_ptr<expressions::BinaryRelationExpression>> assumptions; + + if (val1 == numberOfStates && val2 == numberOfStates) { + auto resAssumptionPair = std::pair<std::shared_ptr<MonotonicityResult<VariableType>>, std::vector<std::shared_ptr<expressions::BinaryRelationExpression>>>(monRes, assumptions); + monResults.insert(std::pair<std::shared_ptr<Order>, std::pair<std::shared_ptr<MonotonicityResult<VariableType>>, std::vector<std::shared_ptr<expressions::BinaryRelationExpression>>>>(std::get<0>(criticalTuple), resAssumptionPair)); + } else if (val1 != numberOfStates && val2 != numberOfStates) { + extendOrderWithAssumptions(std::get<0>(criticalTuple), val1, val2, assumptions, monRes); + } else { + assert (false); + } + } + + template <typename ValueType, typename ConstantType> + void MonotonicityHelper<ValueType, ConstantType>::extendOrderWithAssumptions(std::shared_ptr<Order> order, uint_fast64_t val1, uint_fast64_t val2, std::vector<std::shared_ptr<expressions::BinaryRelationExpression>> assumptions, std::shared_ptr<MonotonicityResult<VariableType>> monRes) { + std::map<std::shared_ptr<Order>, std::vector<std::shared_ptr<expressions::BinaryRelationExpression>>> result; + if (order->isInvalid()) { + // We don't add anything as the order we created with assumptions turns out to be invalid + STORM_LOG_INFO(" The order was invalid, so we stop here"); + return; + } + auto numberOfStates = model->getNumberOfStates(); + if (val1 == numberOfStates || val2 == numberOfStates) { + assert (val1 == val2); + assert (order->getNumberOfAddedStates() == order->getNumberOfStates()); + auto resAssumptionPair = std::pair<std::shared_ptr<MonotonicityResult<VariableType>>, std::vector<std::shared_ptr<expressions::BinaryRelationExpression>>>(monRes, assumptions); + monResults.insert(std::pair<std::shared_ptr<Order>, std::pair<std::shared_ptr<MonotonicityResult<VariableType>>, std::vector<std::shared_ptr<expressions::BinaryRelationExpression>>>>(std::move(order), std::move(resAssumptionPair))); + } else { + // Make the three assumptions + STORM_LOG_INFO("Creating assumptions for " << val1 << " and " << val2 << ". "); + auto newAssumptions = assumptionMaker.createAndCheckAssumptions(val1, val2, order, region); + assert (newAssumptions.size() <= 3); + auto itr = newAssumptions.begin(); + if (newAssumptions.size() == 0) { + monRes = std::make_shared<MonotonicityResult<VariableType>>(MonotonicityResult<VariableType>()); + for (auto& entry : occuringStatesAtVariable) { + for (auto & state : entry.second) { + extender->checkParOnStateMonRes(state, order, entry.first, monRes); + if (monRes->getMonotonicity(entry.first) == Monotonicity::Unknown) { + break; + } + } + monRes->setDoneForVar(entry.first); + } + monResults.insert({order, {monRes, assumptions}}); + STORM_LOG_INFO(" None of the assumptions were valid, we stop exploring the current order"); + } else { + STORM_LOG_INFO(" Created " << newAssumptions.size() << " assumptions, we continue extending the current order"); + } + + while (itr != newAssumptions.end()) { + auto assumption = *itr; + ++itr; + if (assumption.second != AssumptionStatus::INVALID) { + if (itr != newAssumptions.end()) { + // We make a copy of the order and the assumptions + auto orderCopy = order->copy(); + auto assumptionsCopy = std::vector<std::shared_ptr<expressions::BinaryRelationExpression>>(assumptions); + auto monResCopy = monRes->copy(); + + if (assumption.second == AssumptionStatus::UNKNOWN) { + // only add assumption to the set of assumptions if it is unknown whether it holds or not + assumptionsCopy.push_back(std::move(assumption.first)); + } + auto criticalTuple = extender->extendOrder(orderCopy, region, monResCopy, assumption.first); + extendOrderWithAssumptions(std::get<0>(criticalTuple), std::get<1>(criticalTuple), std::get<2>(criticalTuple), assumptionsCopy, monResCopy); + } else { + // It is the last one, so we don't need to create a copy. + if (assumption.second == AssumptionStatus::UNKNOWN) { + // only add assumption to the set of assumptions if it is unknown whether it holds or not + assumptions.push_back(std::move(assumption.first)); + } + auto criticalTuple = extender->extendOrder(order, region, monRes, assumption.first); + extendOrderWithAssumptions(std::get<0>(criticalTuple), std::get<1>(criticalTuple), std::get<2>(criticalTuple), assumptions, monRes); + } + } + } + } + } + + template <typename ValueType, typename ConstantType> + void MonotonicityHelper<ValueType, ConstantType>::checkMonotonicityOnSamples(std::shared_ptr<models::sparse::Dtmc<ValueType>> model, uint_fast64_t numberOfSamples) { + assert (numberOfSamples > 2); + + auto instantiator = utility::ModelInstantiator<models::sparse::Dtmc<ValueType>, models::sparse::Dtmc<ConstantType>>(*model); + std::set<VariableType> variables = models::sparse::getProbabilityParameters(*model); + std::vector<std::vector<ConstantType>> samples; + // For each of the variables create a model in which we only change the value for this specific variable + for (auto itr = variables.begin(); itr != variables.end(); ++itr) { + ConstantType previous = -1; + bool monDecr = true; + bool monIncr = true; + + // Check monotonicity in variable (*itr) by instantiating the model + // all other variables fixed on lb, only increasing (*itr) + for (uint_fast64_t i = 0; (monDecr || monIncr) && i < numberOfSamples; ++i) { + // Create valuation + auto valuation = utility::parametric::Valuation<ValueType>(); + for (auto itr2 = variables.begin(); itr2 != variables.end(); ++itr2) { + // Only change value for current variable + if ((*itr) == (*itr2)) { + auto lb = region.getLowerBoundary(itr->name()); + auto ub = region.getUpperBoundary(itr->name()); + // Creates samples between lb and ub, that is: lb, lb + (ub-lb)/(#samples -1), lb + 2* (ub-lb)/(#samples -1), ..., ub + valuation[*itr2] = utility::convertNumber<typename utility::parametric::CoefficientType<ValueType>::type>(lb + i*(ub-lb)/(numberOfSamples-1)); + } else { + auto lb = region.getLowerBoundary(itr2->name()); + valuation[*itr2] = utility::convertNumber<typename utility::parametric::CoefficientType<ValueType>::type>(lb); + } + } + + // Instantiate model and get result + models::sparse::Dtmc<ConstantType> sampleModel = instantiator.instantiate(valuation); + auto checker = modelchecker::SparseDtmcPrctlModelChecker<models::sparse::Dtmc<ConstantType>>(sampleModel); + std::unique_ptr<modelchecker::CheckResult> checkResult; + auto formula = formulas[0]; + if (formula->isProbabilityOperatorFormula() && formula->asProbabilityOperatorFormula().getSubformula().isUntilFormula()) { + const modelchecker::CheckTask<logic::UntilFormula, ConstantType> checkTask = modelchecker::CheckTask<logic::UntilFormula, ConstantType>((*formula).asProbabilityOperatorFormula().getSubformula().asUntilFormula()); + checkResult = checker.computeUntilProbabilities(Environment(), checkTask); + } else if (formula->isProbabilityOperatorFormula() && formula->asProbabilityOperatorFormula().getSubformula().isEventuallyFormula()) { + const modelchecker::CheckTask<logic::EventuallyFormula, ConstantType> checkTask = modelchecker::CheckTask<logic::EventuallyFormula, ConstantType>((*formula).asProbabilityOperatorFormula().getSubformula().asEventuallyFormula()); + checkResult = checker.computeReachabilityProbabilities(Environment(), checkTask); + } else { + STORM_LOG_THROW(false, exceptions::NotSupportedException, "Expecting until or eventually formula"); + } + + auto quantitativeResult = checkResult->asExplicitQuantitativeCheckResult<ConstantType>(); + std::vector<ConstantType> values = quantitativeResult.getValueVector(); + auto initialStates = model->getInitialStates(); + ConstantType initial = 0; + // Get total probability from initial states + for (auto j = initialStates.getNextSetIndex(0); j < model->getNumberOfStates(); j = initialStates.getNextSetIndex(j + 1)) { + initial += values[j]; + } + // Calculate difference with result for previous valuation + assert (initial >= 0 - precision && initial <= 1 + precision); + ConstantType diff = previous - initial; + assert (previous == -1 || diff >= -1 - precision && diff <= 1 + precision); + + if (previous != -1 && (diff > precision || diff < -precision)) { + monDecr &= diff > precision; // then previous value is larger than the current value from the initial states + monIncr &= diff < -precision; + } + previous = initial; + samples.push_back(std::move(values)); + } + auto res = (!monIncr && !monDecr) ? MonotonicityResult<VariableType>::Monotonicity::Not : MonotonicityResult<VariableType>::Monotonicity::Unknown; + resultCheckOnSamples.addMonotonicityResult(*itr, res); + } + assumptionMaker.setSampleValues(std::move(samples)); + } + + template <typename ValueType, typename ConstantType> + void MonotonicityHelper<ValueType, ConstantType>::checkMonotonicityOnSamples(std::shared_ptr<models::sparse::Mdp<ValueType>> model, uint_fast64_t numberOfSamples) { + STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Checking monotonicity on samples not implemented for mdps"); + } + + template class MonotonicityHelper<RationalFunction, double>; + template class MonotonicityHelper<RationalFunction, RationalNumber>; + } +} diff --git a/src/storm-pars/analysis/MonotonicityHelper.h b/src/storm-pars/analysis/MonotonicityHelper.h new file mode 100644 index 000000000..2ebfcf594 --- /dev/null +++ b/src/storm-pars/analysis/MonotonicityHelper.h @@ -0,0 +1,171 @@ +#ifndef STORM_MONOTONICITYHELPER_H +#define STORM_MONOTONICITYHELPER_H + +#include <map> +#include "Order.h" +#include "LocalMonotonicityResult.h" +#include "OrderExtender.h" +#include "AssumptionMaker.h" +#include "MonotonicityResult.h" + + +#include "storm/logic/Formula.h" + +#include "storm/models/ModelBase.h" +#include "storm/models/sparse/Dtmc.h" +#include "storm/models/sparse/Mdp.h" + +#include "storm/solver/Z3SmtSolver.h" + +#include "storm/storage/SparseMatrix.h" +#include "storm/storage/expressions/BinaryRelationExpression.h" +#include "storm/storage/expressions/ExpressionManager.h" +#include "storm/storage/expressions/RationalFunctionToExpression.h" + +#include "storm/utility/constants.h" + +#include "storm-pars/api/region.h" + +namespace storm { + namespace analysis { + + template <typename ValueType, typename ConstantType> + class MonotonicityHelper { + + public: + typedef typename utility::parametric::VariableType<ValueType>::type VariableType; + typedef typename utility::parametric::CoefficientType<ValueType>::type CoefficientType; + typedef typename MonotonicityResult<VariableType>::Monotonicity Monotonicity; + typedef typename storage::ParameterRegion<ValueType> Region; + + /*! + * Constructor of MonotonicityHelper. + * + * @param model The model considered. + * @param formulas The formulas considered. + * @param regions The regions to consider. + * @param numberOfSamples Number of samples taken for monotonicity checking, default 0, + * if 0 then no check on samples is executed. + * @param precision Precision on which the samples are compared + * @param dotOutput Whether or not dot output should be generated for the ROs. + */ + MonotonicityHelper(std::shared_ptr<models::sparse::Model<ValueType>> model, std::vector<std::shared_ptr<logic::Formula const>> formulas, std::vector<storage::ParameterRegion<ValueType>> regions, uint_fast64_t numberOfSamples=0, double const& precision=0.000001, bool dotOutput = false); + + /*! + * Checks if a derivative >=0 or/and <=0 + * + * @param derivative The derivative you want to check + * @return pair of bools, >= 0 and <= 0 + */ + static std::pair<bool, bool> checkDerivative(ValueType derivative, storage::ParameterRegion<ValueType> reg) { + bool monIncr = false; + bool monDecr = false; + + if (derivative.isZero()) { + monIncr = true; + monDecr = true; + } else if (derivative.isConstant()) { + monIncr = derivative.constantPart() >= 0; + monDecr = derivative.constantPart() <= 0; + } else { + + std::shared_ptr<utility::solver::SmtSolverFactory> smtSolverFactory = std::make_shared<utility::solver::MathsatSmtSolverFactory>(); + std::shared_ptr<expressions::ExpressionManager> manager(new expressions::ExpressionManager()); + solver::Z3SmtSolver s(*manager); + std::set<VariableType> variables = derivative.gatherVariables(); + + expressions::Expression exprBounds = manager->boolean(true); + for (auto variable : variables) { + auto managerVariable = manager->declareRationalVariable(variable.name()); + auto lb = utility::convertNumber<RationalNumber>(reg.getLowerBoundary(variable)); + auto ub = utility::convertNumber<RationalNumber>(reg.getUpperBoundary(variable)); + exprBounds = exprBounds && manager->rational(lb) < managerVariable && managerVariable < manager->rational(ub); + } + + auto converter = expressions::RationalFunctionToExpression<ValueType>(manager); + + // < 0, so not monotone increasing. If this is unsat, then it should be monotone increasing. + expressions::Expression exprToCheck = converter.toExpression(derivative) < manager->rational(0); + s.add(exprBounds); + s.add(exprToCheck); + monIncr = s.check() == solver::SmtSolver::CheckResult::Unsat; + + // > 0, so not monotone decreasing. If this is unsat it should be monotone decreasing. + exprToCheck = converter.toExpression(derivative) > manager->rational(0); + s.reset(); + s.add(exprBounds); + s.add(exprToCheck); + monDecr = s.check() == solver::SmtSolver::CheckResult::Unsat; + } + assert (!(monIncr && monDecr) || derivative.isZero()); + + return std::pair<bool, bool>(monIncr, monDecr); + } + + /*! + * Builds Reachability Orders for the given model and simultaneously uses them to check for Monotonicity. + * + * @param outfile Outfile to which results are written. + * @param dotOutfileName Name for the files of the dot outputs should they be generated + * @return Map which maps each order to its Reachability Order and used assumptions. + */ + std::map<std::shared_ptr<Order>, std::pair<std::shared_ptr<MonotonicityResult<VariableType>>, std::vector<std::shared_ptr<expressions::BinaryRelationExpression>>>> checkMonotonicityInBuild(std::ostream& outfile, bool usePLA = false, std::string dotOutfileName = "dotOutput"); + + /*! + * Checks for local monotonicity at the given state. + * + * @param order the order on which the monotonicity should be checked + * @param state the considerd state + * @param var the variable in which we check for monotonicity + * @param region the region on which we check the monotonicity + * @return Incr, Decr, Constant, Unknown or Not + */ + Monotonicity checkLocalMonotonicity(std::shared_ptr<Order> order, uint_fast64_t state, VariableType var, storage::ParameterRegion<ValueType> region); + + private: + void createOrder(); + + void checkMonotonicityOnSamples(std::shared_ptr<models::sparse::Dtmc<ValueType>> model, uint_fast64_t numberOfSamples); + + void checkMonotonicityOnSamples(std::shared_ptr<models::sparse::Mdp<ValueType>> model, uint_fast64_t numberOfSamples); + + void extendOrderWithAssumptions(std::shared_ptr<Order> order, uint_fast64_t val1, uint_fast64_t val2, std::vector<std::shared_ptr<expressions::BinaryRelationExpression>> assumptions, std::shared_ptr<MonotonicityResult<VariableType>> monRes); + + Monotonicity checkTransitionMonRes(ValueType function, VariableType param, Region region); + + ValueType getDerivative(ValueType function, VariableType var); + + + std::shared_ptr<models::ModelBase> model; + + std::vector<std::shared_ptr<logic::Formula const>> formulas; + + bool dotOutput; + + bool checkSamples; + + bool onlyCheckOnOrder; + + MonotonicityResult<VariableType> resultCheckOnSamples; + + std::map<VariableType, std::vector<uint_fast64_t>> occuringStatesAtVariable; + + std::map<std::shared_ptr<Order>, std::pair<std::shared_ptr<MonotonicityResult<VariableType>>, std::vector<std::shared_ptr<expressions::BinaryRelationExpression>>>> monResults; + + OrderExtender<ValueType, ConstantType> *extender; + + ConstantType precision; + + Region region; + + storage::SparseMatrix<ValueType> matrix; + + std::unordered_map<ValueType, std::unordered_map<VariableType, ValueType>> derivatives; + + AssumptionMaker<ValueType, ConstantType> assumptionMaker; + + + }; + } +} +#endif //STORM_MONOTONICITYHELPER_H diff --git a/src/storm-pars/analysis/MonotonicityResult.cpp b/src/storm-pars/analysis/MonotonicityResult.cpp new file mode 100644 index 000000000..5a7e678c5 --- /dev/null +++ b/src/storm-pars/analysis/MonotonicityResult.cpp @@ -0,0 +1,231 @@ +#include "MonotonicityResult.h" + +#include "storm/utility/macros.h" +#include "storm/exceptions/NotImplementedException.h" +#include "storm/models/sparse/Dtmc.h" +#include "storm/models/sparse/Mdp.h" + +namespace storm { + namespace analysis { + + template <typename VariableType> + MonotonicityResult<VariableType>::MonotonicityResult() { + this->done = false; + this->somewhereMonotonicity = true; + this->allMonotonicity = true; + } + + template <typename VariableType> + void MonotonicityResult<VariableType>::addMonotonicityResult(VariableType var, MonotonicityResult<VariableType>::Monotonicity mon) { + monotonicityResult.insert(std::pair<VariableType, MonotonicityResult<VariableType>::Monotonicity>(std::move(var), std::move(mon))); + } + + template <typename VariableType> + void MonotonicityResult<VariableType>::updateMonotonicityResult(VariableType var, MonotonicityResult<VariableType>::Monotonicity mon, bool force) { + assert (!isDoneForVar(var)); + if (force) { + assert (mon == MonotonicityResult<VariableType>::Monotonicity::Not); + if (monotonicityResult.find(var) == monotonicityResult.end()) { + addMonotonicityResult(std::move(var), std::move(mon)); + } else { + monotonicityResult[var] = mon; + } + } else { + if (mon == MonotonicityResult<VariableType>::Monotonicity::Not) { + mon = MonotonicityResult<VariableType>::Monotonicity::Unknown; + } + + if (monotonicityResult.find(var) == monotonicityResult.end()) { + addMonotonicityResult(std::move(var), std::move(mon)); + } else { + auto monRes = monotonicityResult[var]; + if (monRes == MonotonicityResult<VariableType>::Monotonicity::Unknown || monRes == mon || + mon == MonotonicityResult<VariableType>::Monotonicity::Constant) { + return; + } else if (mon == MonotonicityResult<VariableType>::Monotonicity::Unknown || + monRes == MonotonicityResult<VariableType>::Monotonicity::Constant) { + monotonicityResult[var] = mon; + } else { + monotonicityResult[var] = MonotonicityResult<VariableType>::Monotonicity::Unknown; + } + } + if (monotonicityResult[var] == MonotonicityResult<VariableType>::Monotonicity::Unknown) { + setAllMonotonicity(false); + setSomewhereMonotonicity(false); + } else { + setSomewhereMonotonicity(true); + } + } + } + + template <typename VariableType> + typename MonotonicityResult<VariableType>::Monotonicity MonotonicityResult<VariableType>::getMonotonicity(VariableType var) const { + auto itr = monotonicityResult.find(var); + if (itr != monotonicityResult.end()) { + return itr->second; + } + return Monotonicity::Unknown; + } + + template <typename VariableType> + std::map<VariableType, typename MonotonicityResult<VariableType>::Monotonicity> MonotonicityResult<VariableType>::getMonotonicityResult() const { + return monotonicityResult; + } + + template <typename VariableType> + std::pair<std::set<VariableType>, std::set<VariableType>> MonotonicityResult<VariableType>::splitVariables(std::set<VariableType> const& consideredVariables) const { + std::set<VariableType> nonMonotoneVariables; + std::set<VariableType> monotoneVariables; + for (auto var : consideredVariables) { + if (isDoneForVar(var)) { + auto res = getMonotonicity(var); + if (res == Monotonicity::Not || res == Monotonicity::Unknown) { + nonMonotoneVariables.insert(var); + } else { + monotoneVariables.insert(var); + } + } else { + nonMonotoneVariables.insert(var); + } + } + return std::make_pair(std::move(monotoneVariables), std::move(nonMonotoneVariables)); + } + + template <typename VariableType> + std::string MonotonicityResult<VariableType>::toString() const { + std::string result; + auto countIncr = 0; + auto countDecr = 0; + for (auto res : getMonotonicityResult()) { + result += res.first.name(); + switch (res.second) { + case MonotonicityResult<VariableType>::Monotonicity::Incr: + countIncr++; + result += " MonIncr; "; + break; + case MonotonicityResult<VariableType>::Monotonicity::Decr: + countDecr++; + result += " MonDecr; "; + break; + case MonotonicityResult<VariableType>::Monotonicity::Constant: + result += " Constant; "; + break; + case MonotonicityResult<VariableType>::Monotonicity::Not: + result += " NotMon; "; + break; + case MonotonicityResult<VariableType>::Monotonicity::Unknown: + result += " Unknown; "; + break; + default: + STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Could not get a string from the region monotonicity check result. The case has not been implemented"); + } + } + result = "#Incr: " + std::to_string(countIncr) + " #Decr: " + std::to_string(countDecr) + "\n" + result; + return result; + } + + + + template <typename VariableType> + void MonotonicityResult<VariableType>::setDone(bool done) { + this->done = done; + } + + template <typename VariableType> + void MonotonicityResult<VariableType>::setDoneForVar(VariableType variable) { + doneVariables.insert(variable); + } + + template <typename VariableType> + bool MonotonicityResult<VariableType>::isDone() const { + return done; + } + + template <typename VariableType> + bool MonotonicityResult<VariableType>::isDoneForVar(VariableType var) const { + return doneVariables.find(var) != doneVariables.end(); + } + + template <typename VariableType> + void MonotonicityResult<VariableType>::setSomewhereMonotonicity(bool somewhereMonotonicity) { + this->somewhereMonotonicity = somewhereMonotonicity; + } + + template <typename VariableType> + bool MonotonicityResult<VariableType>::existsMonotonicity() { + if (!somewhereMonotonicity) { + + for (auto itr : monotonicityResult) { + if (isDoneForVar(itr.first) && itr.second != MonotonicityResult<VariableType>::Monotonicity::Unknown && itr.second != MonotonicityResult<VariableType>::Monotonicity::Not) { + setSomewhereMonotonicity(true); + break; + } + } + } + return monotonicityResult.size() > 0 && somewhereMonotonicity; + } + + template <typename VariableType> + void MonotonicityResult<VariableType>::setAllMonotonicity(bool allMonotonicity) { + this->allMonotonicity = allMonotonicity; + } + + template <typename VariableType> + bool MonotonicityResult<VariableType>::isAllMonotonicity() const { + return allMonotonicity; + } + + template <typename VariableType> + std::shared_ptr<MonotonicityResult<VariableType>> MonotonicityResult<VariableType>::copy() const { + std::shared_ptr<MonotonicityResult<VariableType>> copy = std::make_shared<MonotonicityResult<VariableType>>(); + copy->monotonicityResult = std::map<VariableType, Monotonicity>(monotonicityResult); + copy->setAllMonotonicity(allMonotonicity); + copy->setSomewhereMonotonicity(somewhereMonotonicity); + copy->setDone(done); + copy->setDoneVariables(doneVariables); + return copy; + } + + template<typename VariableType> + void MonotonicityResult<VariableType>::setDoneVariables(std::set<VariableType> doneVariables) { + this->doneVariables = doneVariables; + } + + template<typename VariableType> + void + MonotonicityResult<VariableType>::splitBasedOnMonotonicity(const std::set<VariableType> &consideredVariables, + std::set<VariableType> & monotoneIncr, + std::set<VariableType> & monotoneDecr, + std::set<VariableType> & notMonotone) const { + for (auto& var : consideredVariables) { + if (!isDoneForVar(var)) { + notMonotone.insert(var); + } else { + auto mon = getMonotonicity(var); + if (mon == Monotonicity::Unknown || mon == Monotonicity::Not) { + notMonotone.insert(var); + } else if (mon == Monotonicity::Incr) { + monotoneIncr.insert(var); + } else { + monotoneDecr.insert(var); + } + } + } + + } + + template<typename VariableType> + bool MonotonicityResult<VariableType>::isMonotone(VariableType var) const { + if (monotonicityResult.find(var) == monotonicityResult.end()) { + return false; + } else { + auto monRes = monotonicityResult.at(var); + return isDoneForVar(var) && (monRes == Monotonicity::Incr + || monRes == Monotonicity::Decr + || monRes == Monotonicity::Constant); + } + } + + template class MonotonicityResult<storm::RationalFunctionVariable>; + } +} diff --git a/src/storm-pars/analysis/MonotonicityResult.h b/src/storm-pars/analysis/MonotonicityResult.h new file mode 100644 index 000000000..6ec70945c --- /dev/null +++ b/src/storm-pars/analysis/MonotonicityResult.h @@ -0,0 +1,130 @@ +#pragma once + +#include <ostream> +#include <map> +#include <set> + +#include <memory> +#include <storm/storage/BitVector.h> + +namespace storm { + namespace analysis { + template <typename VariableType> + class MonotonicityResult { + public: + + /*! + * The results of monotonicity checking for a single Parameter Region + */ + enum class Monotonicity { + Incr, /*!< the result is monotonically increasing */ + Decr, /*!< the result is monotonically decreasing */ + Constant, /*!< the result is constant */ + Not, /*!< the result is not monotonic */ + Unknown /*!< the monotonicity result is unknown */ + }; + + /*! + * Constructs a new MonotonicityResult object. + */ + MonotonicityResult(); + + /*! + * Adds a new variable with a given Monotonicity to the map. + * + * @param var The variable that is to be added. + * @param mon The Monotonicity of the variable. + */ + void addMonotonicityResult(VariableType var, Monotonicity mon); + + + /*! + * Updates the Monotonicity of a variable based on its value so far and a new value. + * + * @param var The variable. + * @param mon The new Monotonicity to be considered. + */ + void updateMonotonicityResult(VariableType var, Monotonicity mon, bool force = false); + + /*! + * Returns the current monotonicity of a given parameter. + * + * @param var The parameter. + * @return Incr, Decr, Constant, Not or Unknown. + */ + Monotonicity getMonotonicity(VariableType var) const; + bool isMonotone(VariableType var) const; + + /*! + * Returns the results so far. + * + * @return The parameter / Monotonicity map + */ + std::map<VariableType, Monotonicity> getMonotonicityResult() const; + + + void splitBasedOnMonotonicity(std::set<VariableType> const& consideredVariables, std::set<VariableType>& monotoneIncr, std::set<VariableType>& monotoneDecr, std::set<VariableType> & notMontone) const; + + + std::pair<std::set<VariableType>, std::set<VariableType>> splitVariables(std::set<VariableType> const& consideredVariables) const; + /*! + * Constructs a string output of all variables and their corresponding Monotonicity + * + * @return Results so far + */ + std::string toString() const; + + /*! + * Checks if the result is complete + */ + bool isDone() const; + + bool isDoneForVar(VariableType) const; + + /*! + * Checks if there is any variable that is monotone + */ + bool existsMonotonicity(); + + /*! + * Returns if all Variables are monotone + */ + bool isAllMonotonicity() const; + + /*! + * Sets the done bool to the given truth value + */ + void setDone(bool done = true); + + /*! + * Sets the somewhereMonotonicity bool to the given truth value + */ + void setSomewhereMonotonicity(bool done = true); + + /*! + * Sets the allMonotonicity bool to the given truth value + */ + void setAllMonotonicity(bool done = true); + + void setDoneForVar(VariableType); + + void setDoneVariables(std::set<VariableType> doneVariables); + + /*! + * Constructs a new MonotonicityResult object that is a copy of the current one + * + * @return Pointer to the copy + */ + std::shared_ptr<MonotonicityResult<VariableType>> copy() const; + + private: + std::map<VariableType, Monotonicity> monotonicityResult; + std::set<VariableType> doneVariables; + bool done; + bool somewhereMonotonicity; + bool allMonotonicity; + }; + + } +} + diff --git a/src/storm-pars/analysis/Order.cpp b/src/storm-pars/analysis/Order.cpp index 150d2711d..b5e87244d 100644 --- a/src/storm-pars/analysis/Order.cpp +++ b/src/storm-pars/analysis/Order.cpp @@ -1,313 +1,681 @@ +#include "Order.h" + #include <iostream> -#include <fstream> -#include <algorithm> +#include <storm/utility/macros.h> +#include <queue> -#include "Order.h" namespace storm { namespace analysis { - Order::Order(storm::storage::BitVector* topStates, - storm::storage::BitVector* bottomStates, - storm::storage::BitVector* initialMiddleStates, - uint_fast64_t numberOfStates, - std::vector<uint_fast64_t>* statesSorted) { - nodes = std::vector<Node *>(numberOfStates); - - this->numberOfStates = numberOfStates; - this->addedStates = new storm::storage::BitVector(numberOfStates); - this->doneBuilding = false; - assert (statesSorted != nullptr); - this->statesSorted = *statesSorted; - this->statesToHandle = initialMiddleStates; - - top = new Node(); - bottom = new Node(); - - top->statesAbove = storm::storage::BitVector(numberOfStates); - bottom->statesAbove = storm::storage::BitVector(numberOfStates); - + Order::Order(storm::storage::BitVector* topStates, storm::storage::BitVector* bottomStates, uint_fast64_t numberOfStates, storage::Decomposition<storage::StronglyConnectedComponent> decomposition, std::vector<uint_fast64_t> statesSorted) { + init(numberOfStates, decomposition); + this->numberOfAddedStates = 0; + this->onlyBottomTopOrder = true; for (auto const& i : *topStates) { - addedStates->set(i); - bottom->statesAbove.set(i); - top->states.insert(i); - nodes[i] = top; + this->doneStates.set(i); + this->bottom->statesAbove.set(i); + this->top->states.insert(i); + this->nodes[i] = top; + numberOfAddedStates++; } + this->statesSorted = statesSorted; for (auto const& i : *bottomStates) { - addedStates->set(i); - bottom->states.insert(i); - nodes[i] = bottom; + this->doneStates.set(i); + this->bottom->states.insert(i); + this->nodes[i] = bottom; + numberOfAddedStates++; } - - for (auto const &state : *initialMiddleStates) { - add(state); + assert (numberOfAddedStates <= numberOfStates); + assert (doneStates.getNumberOfSetBits() == (topStates->getNumberOfSetBits() + bottomStates->getNumberOfSetBits())); + if (numberOfAddedStates == numberOfStates) { + doneBuilding = doneStates.full(); } } - Order::Order(uint_fast64_t topState, uint_fast64_t bottomState, uint_fast64_t numberOfStates, std::vector<uint_fast64_t>* statesSorted) { - nodes = std::vector<Node *>(numberOfStates); + Order::Order(uint_fast64_t topState, uint_fast64_t bottomState, uint_fast64_t numberOfStates, storage::Decomposition<storage::StronglyConnectedComponent> decomposition, std::vector<uint_fast64_t> statesSorted) { + init(numberOfStates, decomposition); - this->numberOfStates = numberOfStates; - this->addedStates = new storm::storage::BitVector(numberOfStates); - this->doneBuilding = false; - this->statesSorted = *statesSorted; - this->statesToHandle = new storm::storage::BitVector(numberOfStates); - - top = new Node(); - bottom = new Node(); - - top->statesAbove = storm::storage::BitVector(numberOfStates); - bottom->statesAbove = storm::storage::BitVector(numberOfStates); - - addedStates->set(topState); - bottom->statesAbove.set(topState); - top->states.insert(topState); - nodes[topState] = top; - - addedStates->set(bottomState); - bottom->states.insert(bottomState); - nodes[bottomState] = bottom; - assert (addedStates->getNumberOfSetBits() == 2); - } - - Order::Order(Order* order) { - numberOfStates = order->getAddedStates()->size(); - nodes = std::vector<Node *>(numberOfStates); - addedStates = new storm::storage::BitVector(numberOfStates); - this->doneBuilding = order->getDoneBuilding(); - - auto oldNodes = order->getNodes(); - // Create nodes - for (auto itr = oldNodes.begin(); itr != oldNodes.end(); ++itr) { - Node *oldNode = (*itr); - if (oldNode != nullptr) { - Node *newNode = new Node(); - newNode->states = oldNode->states; - for (auto const& i : newNode->states) { - addedStates->set(i); - nodes[i] = newNode; - } - if (oldNode == order->getTop()) { - top = newNode; - } else if (oldNode == order->getBottom()) { - bottom = newNode; - } - } - } - assert(*addedStates == *(order->getAddedStates())); + this->onlyBottomTopOrder = true; + this->doneStates.set(topState); - // set all states above and below - for (auto itr = oldNodes.begin(); itr != oldNodes.end(); ++itr) { - Node *oldNode = (*itr); - if (oldNode != nullptr) { - Node *newNode = getNode(*(oldNode->states.begin())); - newNode->statesAbove = storm::storage::BitVector((oldNode->statesAbove)); - } - } + this->bottom->statesAbove.set(topState); + this->top->states.insert(topState); + this->nodes[topState] = top; + + this->doneStates.set(bottomState); + + this->bottom->states.insert(bottomState); + this->nodes[bottomState] = bottom; + this->numberOfAddedStates = 2; + assert (numberOfAddedStates <= numberOfStates); - auto statesSortedOrder = order->getStatesSorted(); - for (auto itr = statesSortedOrder.begin(); itr != statesSortedOrder.end(); ++itr) { - this->statesSorted.push_back(*itr); + this->statesSorted = statesSorted; + assert (doneStates.getNumberOfSetBits() == 2); + if (numberOfAddedStates == numberOfStates) { + doneBuilding = doneStates.full(); } - this->statesToHandle = new storm::storage::BitVector(*(order->statesToHandle)); } - void Order::addBetween(uint_fast64_t state, Node *above, Node *below) { - assert(!(*addedStates)[state]); - assert(compare(above, below) == ABOVE); + Order::Order(){ + this->invalid = false; + } + + /*** Modifying the order ***/ + + void Order::add(uint_fast64_t state) { + assert (nodes[state] == nullptr); + addBetween(state, top, bottom); + addStateToHandle(state); + } + + void Order::addAbove(uint_fast64_t state, Node *node) { + STORM_LOG_INFO("Add " << state << " above " << *node->states.begin() << std::endl); + + assert (nodes[state] == nullptr); Node *newNode = new Node(); nodes[state] = newNode; newNode->states.insert(state); - newNode->statesAbove = storm::storage::BitVector((above->statesAbove)); - for (auto const& state : above->states) { + newNode->statesAbove = storm::storage::BitVector(numberOfStates, false); + for (auto const &state : top->states) { newNode->statesAbove.set(state); } - below->statesAbove.set(state); - addedStates->set(state); + node->statesAbove.set(state); + numberOfAddedStates++; + onlyBottomTopOrder = false; + if (numberOfAddedStates == numberOfStates) { + doneBuilding = doneStates.full(); + } + } - void Order::addBetween(uint_fast64_t state, uint_fast64_t above, uint_fast64_t below) { - assert(!(*addedStates)[state]); + void Order::addBelow(uint_fast64_t state, Node *node) { + STORM_LOG_INFO("Add " << state << " below " << *node->states.begin()<< std::endl); + + assert (nodes[state] == nullptr); + Node *newNode = new Node(); + nodes[state] = newNode; + newNode->states.insert(state); + newNode->statesAbove = storm::storage::BitVector((node->statesAbove)); + for (auto statesAbove : node->states) { + newNode->statesAbove.set(statesAbove); + } + bottom->statesAbove.set(state); + numberOfAddedStates++; + onlyBottomTopOrder = false; + if (numberOfAddedStates == numberOfStates) { + doneBuilding = doneStates.full(); + } + assert (numberOfAddedStates <= numberOfStates); + + } + + void Order::addBetween(uint_fast64_t state, Node *above, Node *below) { + STORM_LOG_INFO("Add " << state << " between (above) " << *above->states.begin() << " and " << *below->states.begin() << std::endl); + assert(compare(above, below) == ABOVE); + assert (above != nullptr && below != nullptr); + if (nodes[state] == nullptr) { + // State is not in the order yet + Node *newNode = new Node(); + nodes[state] = newNode; + + newNode->states.insert(state); + newNode->statesAbove = storm::storage::BitVector(above->statesAbove); + for (auto aboveStates : above->states) { + newNode->statesAbove.set(aboveStates); + } + below->statesAbove.set(state); + numberOfAddedStates++; + onlyBottomTopOrder = false; + if (numberOfAddedStates == numberOfStates) { + doneBuilding = doneStates.full(); + } + assert (numberOfAddedStates <= numberOfStates); + } else { + // State is in the order already, so we add the new relations + addRelationNodes(above, nodes[state]); + addRelationNodes(nodes[state], below); + } + } + void Order::addBetween(uint_fast64_t state, uint_fast64_t above, uint_fast64_t below) { + assert (compare(above, below) == ABOVE); assert (getNode(below)->states.find(below) != getNode(below)->states.end()); assert (getNode(above)->states.find(above) != getNode(above)->states.end()); - addBetween(state, getNode(above), getNode(below)); + addBetween(state, getNode(above), getNode(below)); } - void Order::addToNode(uint_fast64_t state, Node *node) { - assert(!(*addedStates)[state]); - node->states.insert(state); - nodes[state] = node; - addedStates->set(state); + void Order::addRelation(uint_fast64_t above, uint_fast64_t below, bool allowMerge) { + assert (getNode(above) != nullptr && getNode(below) != nullptr); + addRelationNodes(getNode(above), getNode(below), allowMerge); } - void Order::add(uint_fast64_t state) { - assert(!(*addedStates)[state]); - addBetween(state, top, bottom); - } + void Order::addRelationNodes(Order::Node *above, Order::Node * below, bool allowMerge) { + assert (allowMerge || compare(above, below) != BELOW); - void Order::addRelationNodes(Order::Node *above, Order::Node * below) { - assert (compare(above, below) == UNKNOWN); - for (auto const& state : above->states) { + STORM_LOG_INFO("Add relation between (above) " << *above->states.begin() << " and " << *below->states.begin() << std::endl); + + if (allowMerge) { + if (compare(below, above) == ABOVE) { + mergeNodes(above, below); + return; + } + } + below->statesAbove |= ((above->statesAbove)); + for (auto const &state : above->states) { below->statesAbove.set(state); } - below->statesAbove|=((above->statesAbove)); assert (compare(above, below) == ABOVE); } - void Order::addRelation(uint_fast64_t above, uint_fast64_t below) { - addRelationNodes(getNode(above), getNode(below)); + void Order::addToNode(uint_fast64_t state, Node *node) { + STORM_LOG_INFO("Add "<< state << " to between (above) " << *node->states.begin() << std::endl); + + if (nodes[state] == nullptr) { + // State is not in the order yet + node->states.insert(state); + nodes[state] = node; + numberOfAddedStates++; + if (numberOfAddedStates == numberOfStates) { + doneBuilding = doneStates.full(); + } + assert (numberOfAddedStates <= numberOfStates); + + } else { + // State is in the order already, so we merge the nodes + mergeNodes(nodes[state], node); + } + } + + bool Order::mergeNodes(storm::analysis::Order::Node *node1, storm::analysis::Order::Node *node2) { + STORM_LOG_INFO("Merge " << *node1->states.begin() << " and " << *node2->states.begin() << std::endl); + + // Merges node2 into node 1 + // everything above n2 also above n1 + node1->statesAbove |= ((node2->statesAbove)); + // add states of node 2 to node 1 + node1->states.insert(node2->states.begin(), node2->states.end()); + + for(auto const& i : node2->states) { + nodes[i] = node1; + } + + for (auto const& node : nodes) { + if (node != nullptr) { + for (auto state2 : node2->states) { + if (node->statesAbove[state2]) { + for (auto state1 : node1->states) { + node->statesAbove.set(state1); + } + break; + } + } + } + } + for (auto i = 0; i < numberOfStates; ++i) { + for (auto j= i + 1; j < numberOfStates; ++j) { + auto comp1 = compare(i,j); + auto comp2 = compare(j,i); + if (!((comp1 == BELOW && comp2 == ABOVE ) || + (comp1 == ABOVE && comp2 == BELOW) || + (comp1 == UNKNOWN && comp2 == UNKNOWN) || + (comp1 == SAME && comp2 == SAME))) { + invalid = true; + return false; + } + + } + } + return !invalid; + } + + bool Order::merge(uint_fast64_t var1, uint_fast64_t var2) { + return mergeNodes(getNode(var1), getNode(var2)); + } + + /*** Checking on the order ***/ + + Order::NodeComparison Order::compare(uint_fast64_t state1, uint_fast64_t state2, NodeComparison hypothesis){ + return compare(getNode(state1), getNode(state2), hypothesis); } - Order::NodeComparison Order::compare(uint_fast64_t state1, uint_fast64_t state2) { - return compare(getNode(state1), getNode(state2)); + Order::NodeComparison Order::compareFast(uint_fast64_t state1, uint_fast64_t state2, NodeComparison hypothesis) const { + auto res = compareFast(getNode(state1), getNode(state2), hypothesis); + return res; } - Order::NodeComparison Order::compare(Node* node1, Node* node2) { + Order::NodeComparison Order::compareFast(Node* node1, Node* node2, NodeComparison hypothesis) const { if (node1 != nullptr && node2 != nullptr) { if (node1 == node2) { return SAME; } - if (above(node1, node2)) { - assert(!above(node2, node1)); + if ((hypothesis == UNKNOWN || hypothesis == ABOVE) && ((node1 == top || node2 == bottom) || aboveFast(node1, node2))) { return ABOVE; } - if (above(node2, node1)) { + if ((hypothesis == UNKNOWN || hypothesis == BELOW) && ((node2 == top || node1 == bottom) || aboveFast(node2, node1))) { return BELOW; } + } else if (node1 == top || node2 == bottom) { + return ABOVE; + } else if (node2 == top || node1 == bottom) { + return BELOW; + } + return UNKNOWN; + } - // tweak for cyclic pmcs - if (doneBuilding) { - doneBuilding = false; - if (above(node1, node2)) { - assert(!above(node2, node1)); - doneBuilding = true; - return ABOVE; - } - if (above(node2, node1)) { - doneBuilding = true; - return BELOW; - } + Order::NodeComparison Order::compare(Node* node1, Node* node2, NodeComparison hypothesis) { + if (node1 != nullptr && node2 != nullptr) { + auto comp = compareFast(node1, node2, hypothesis); + if (comp != UNKNOWN) { + return comp; + } + if ((hypothesis == UNKNOWN || hypothesis == ABOVE) && above(node1, node2)) { + assert(!above(node2, node1)); + return ABOVE; + } + + if ((hypothesis == UNKNOWN || hypothesis == BELOW) && above(node2, node1)) { + return BELOW; } + } else if (node1 == top || node2 == bottom) { + return ABOVE; + } else if (node2 == top || node1 == bottom) { + return BELOW; } return UNKNOWN; } + bool Order::contains(uint_fast64_t state) const { + return state < numberOfStates && nodes[state] != nullptr; + } + + Order::Node *Order::getBottom() const { + return bottom; + } + + bool Order::getDoneBuilding() const { + assert (!doneStates.full() || numberOfAddedStates == numberOfStates); + return doneStates.full(); + } - bool Order::contains(uint_fast64_t state) { - return state < addedStates->size() && addedStates->get(state); + uint_fast64_t Order::getNextDoneState(uint_fast64_t state) const { + return doneStates.getNextSetIndex(state + 1); } - Order::Node *Order::getNode(uint_fast64_t stateNumber) { + Order::Node *Order::getNode(uint_fast64_t stateNumber) const { assert (stateNumber < numberOfStates); - return nodes.at(stateNumber); + return nodes[stateNumber]; } - Order::Node *Order::getTop() { + std::vector<Order::Node*> Order::getNodes() const { + return nodes; + } + + std::vector<uint_fast64_t>& Order::getStatesSorted() { + return statesSorted; + } + + Order::Node *Order::getTop() const { return top; } - Order::Node *Order::getBottom() { - return bottom; + uint_fast64_t Order::getNumberOfAddedStates() const { + return numberOfAddedStates; } - std::vector<Order::Node*> Order::getNodes() { - return nodes; + uint_fast64_t Order::getNumberOfStates() const { + return numberOfStates; } - storm::storage::BitVector* Order::getAddedStates() { - return addedStates; + bool Order::isBottomState(uint_fast64_t state) const { + auto states = bottom->states; + return states.find(state) != states.end(); } - bool Order::getDoneBuilding() { - return doneBuilding; + bool Order::isTopState(uint_fast64_t state) const { + auto states = top->states; + return states.find(state) != states.end(); } - void Order::setDoneBuilding(bool done) { - doneBuilding = done; + bool Order::isOnlyBottomTopOrder() const { + return onlyBottomTopOrder; } - std::vector<uint_fast64_t> Order::sortStates(storm::storage::BitVector* states) { - uint_fast64_t numberOfSetBits = states->getNumberOfSetBits(); - auto stateSize = states->size(); - auto result = std::vector<uint_fast64_t>(numberOfSetBits, stateSize); + std::vector<uint_fast64_t> Order::sortStates(std::vector<uint_fast64_t>* states) { + assert (states != nullptr); + uint_fast64_t numberOfStatesToSort = states->size(); + std::vector<uint_fast64_t> result; + // Go over all states for (auto state : *states) { - if (result[0] == stateSize) { - result[0] = state; + bool unknown = false; + if (result.size() == 0) { + result.push_back(state); } else { - uint_fast64_t i = 0; bool added = false; - while (i < numberOfSetBits && !added) { - if (result[i] == stateSize) { - result[i] = state; + for (auto itr = result.begin(); itr != result.end(); ++itr) { + auto compareRes = compare(state, (*itr)); + if (compareRes == ABOVE || compareRes == SAME) { + // insert at current pointer (while keeping other values) + result.insert(itr, state); added = true; - } else { - auto compareRes = compare(state, result[i]); - if (compareRes == ABOVE) { - auto temp = result[i]; - result[i] = state; - for (uint_fast64_t j = i + 1; j < numberOfSetBits && result[j + 1] != stateSize; j++) { - auto temp2 = result[j]; - result[j] = temp; - temp = temp2; - } - added = true; - } else if (compareRes == UNKNOWN) { - break; - } else if (compareRes == SAME) { - ++i; - auto temp = result[i]; - result[i] = state; - for (uint_fast64_t j = i + 1; j < numberOfSetBits && result[j + 1] != stateSize; j++) { - auto temp2 = result[j]; - result[j] = temp; - temp = temp2; - } - added = true; - } + break; + } else if (compareRes == UNKNOWN) { + unknown = true; + break; } - ++i; + } + if (unknown) { + break; + } + if (!added) { + result.push_back(state); } } } - + while (result.size() < numberOfStatesToSort) { + result.push_back(numberOfStates); + } + assert (result.size() == numberOfStatesToSort); return result; } - bool Order::above(Node *node1, Node *node2) { - bool found = false; - for (auto const& state : node1->states) { - found = ((node2->statesAbove))[state]; - if (found) { + std::pair<std::pair<uint_fast64_t,uint_fast64_t>, std::vector<uint_fast64_t>> Order::sortStatesForForward(uint_fast64_t currentState, std::vector<uint_fast64_t> const& states) { + std::vector<uint_fast64_t> statesSorted; + statesSorted.push_back(currentState); + // Go over all states + bool oneUnknown = false; + bool unknown = false; + uint_fast64_t s1 = numberOfStates; + uint_fast64_t s2 = numberOfStates; + for (auto & state : states) { + unknown = false; + bool added = false; + for (auto itr = statesSorted.begin(); itr != statesSorted.end(); ++itr) { + auto compareRes = compare(state, (*itr)); + if (compareRes == ABOVE || compareRes == SAME) { + if (!contains(state) && compareRes == ABOVE) { + add(state); + } + added = true; + // insert at current pointer (while keeping other values) + statesSorted.insert(itr, state); + break; + } else if (compareRes == UNKNOWN && !oneUnknown) { + // We miss state in the result. + s1 = state; + s2 = *itr; + oneUnknown = true; + added = true; + break; + } else if (compareRes == UNKNOWN && oneUnknown) { + unknown = true; + added = true; + break; + } + } + if (!added ) { + // State will be last in the list + statesSorted.push_back(state); + } + if (unknown && oneUnknown) { break; } } + if (!unknown && oneUnknown) { + assert (statesSorted.size() == states.size()); + s2 = numberOfStates; + } + assert (s1 == numberOfStates || (s1 != numberOfStates && s2 == numberOfStates && statesSorted.size() == states.size()) || (s1 !=numberOfStates && s2 != numberOfStates && statesSorted.size() < states.size())); - if (!found && !doneBuilding) { - storm::storage::BitVector statesSeen((node2->statesAbove)); - for (auto const &i: (node2->statesAbove)) { - auto nodeI = getNode(i); - if (((nodeI->statesAbove) & statesSeen) != (nodeI->statesAbove)) { - found = above(node1, nodeI, node2, &statesSeen); + return {{s1, s2}, statesSorted}; + } + + std::pair<std::pair<uint_fast64_t ,uint_fast64_t>,std::vector<uint_fast64_t>> Order::sortStatesUnorderedPair(const std::vector<uint_fast64_t>* states) { + assert (states != nullptr); + uint_fast64_t numberOfStatesToSort = states->size(); + std::vector<uint_fast64_t> result; + // Go over all states + for (auto state : *states) { + bool unknown = false; + if (result.size() == 0) { + result.push_back(state); + } else { + bool added = false; + for (auto itr = result.begin(); itr != result.end(); ++itr) { + auto compareRes = compare(state, (*itr)); + if (compareRes == ABOVE || compareRes == SAME) { + // insert at current pointer (while keeping other values) + result.insert(itr, state); + added = true; + break; + } else if (compareRes == UNKNOWN) { + return {{(*itr), state}, std::move(result)}; + } + } + if (unknown) { + break; + } + if (!added) { + result.push_back(state); } - if (found) { - for (auto const& state:node1->states) { - node2->statesAbove.set(state); + } + } + + assert (result.size() == numberOfStatesToSort); + return {{numberOfStates, numberOfStates}, std::move(result)}; + } + + std::vector<uint_fast64_t> Order::sortStates(storm::storage::BitVector* states) { + uint_fast64_t numberOfStatesToSort = states->getNumberOfSetBits(); + std::vector<uint_fast64_t> result; + // Go over all states + for (auto state : *states) { + bool unknown = false; + if (result.size() == 0) { + result.push_back(state); + } else { + bool added = false; + for (auto itr = result.begin(); itr != result.end(); ++itr) { + auto compareRes = compare(state, (*itr)); + if (compareRes == ABOVE || compareRes == SAME) { + // insert at current pointer (while keeping other values) + result.insert(itr, state); + added = true; + break; + } else if (compareRes == UNKNOWN) { + unknown = true; + break; } + } + if (unknown) { break; } + if (!added) { + result.push_back(state); + } } } - return found; + auto i = 0; + while (result.size() < numberOfStatesToSort) { + result.push_back(numberOfStates); + ++i; + } + assert (result.size() == numberOfStatesToSort); + return result; + } + + /*** Checking on helpfunctionality for building of order ***/ + + std::shared_ptr<Order> Order::copy() const { + assert (!isInvalid()); + std::shared_ptr<Order> copiedOrder = std::make_shared<Order>(); + copiedOrder->nodes = std::vector<Node *>(numberOfStates, nullptr); + copiedOrder->onlyBottomTopOrder = this->isOnlyBottomTopOrder(); + copiedOrder->numberOfStates = this->getNumberOfStates(); + copiedOrder->statesSorted = std::vector<uint_fast64_t>(this->statesSorted); + copiedOrder->statesToHandle = std::vector<uint_fast64_t>(this->statesToHandle); + copiedOrder->trivialStates = storm::storage::BitVector(trivialStates); + copiedOrder->doneStates = storm::storage::BitVector(doneStates); + copiedOrder->numberOfAddedStates = this->numberOfAddedStates; + copiedOrder->doneBuilding = this->doneBuilding; + + auto seenStates = storm::storage::BitVector(numberOfStates, false); + //copy nodes + for (auto state = 0; state < numberOfStates; ++state) { + Node *oldNode = nodes.at(state); + if (oldNode != nullptr) { + if (!seenStates[*(oldNode->states.begin())]) { + Node *newNode = new Node(); + if (oldNode == this->getTop()) { + copiedOrder->top = newNode; + } else if (oldNode == this->getBottom()) { + copiedOrder->bottom = newNode; + } + newNode->statesAbove = storm::storage::BitVector(oldNode->statesAbove); + for (auto i = 0; i < oldNode->statesAbove.size(); ++i) { + assert (newNode->statesAbove[i] == oldNode->statesAbove[i]); + } + for (auto const &i : oldNode->states) { + assert (!seenStates[i]); + newNode->states.insert(i); + seenStates.set(i); + copiedOrder->nodes[i] = newNode; + } + } + } else { + assert(copiedOrder->nodes[state] == nullptr); + } + } + + return copiedOrder; + } + + /*** Setters ***/ + void Order::setDoneState(uint_fast64_t stateNumber) { + doneStates.set(stateNumber); + } + + /*** Output ***/ + + void Order::toDotOutput() const { + // Graphviz Output start + STORM_PRINT("Dot Output:" << std::endl << "digraph model {" << std::endl); + + // Vertices of the digraph + storm::storage::BitVector stateCoverage = storm::storage::BitVector(doneStates); + for (auto i = 0; i < numberOfStates; ++i) { + if (nodes[i] != nullptr) { + stateCoverage.set(i); + } + } + for (uint_fast64_t i = stateCoverage.getNextSetIndex(0); i!= numberOfStates; i= stateCoverage.getNextSetIndex(i+1)) { + for (uint_fast64_t j = i + 1; j < numberOfStates; j++) { + if (getNode(j) == getNode(i)) stateCoverage.set(j, false); + } + STORM_PRINT("\t" << nodeName(*getNode(i)) << " [ label = \"" << nodeLabel(*getNode(i)) << "\" ];" << std::endl); + } + + // Edges of the digraph + for (uint_fast64_t i = stateCoverage.getNextSetIndex(0); i!= numberOfStates; i= stateCoverage.getNextSetIndex(i+1)) { + storm::storage::BitVector v = storm::storage::BitVector(numberOfStates, false); + Node* currentNode = getNode(i); + for (uint_fast64_t s1 : getNode(i)->statesAbove) { + v |= (currentNode->statesAbove & getNode(s1)->statesAbove); + } + + std::set<Node*> seenNodes; + for (uint_fast64_t state : currentNode->statesAbove) { + Node* n = getNode(state); + if (std::find(seenNodes.begin(), seenNodes.end(), n) == seenNodes.end()) { + seenNodes.insert(n); + if (!v[state]) { + STORM_PRINT("\t" << nodeName(*currentNode) << " -> " << nodeName(*getNode(state)) << ";" << std::endl); + } + } + } + } + // Graphviz Output end + STORM_PRINT("}" << std::endl); + } + + void Order::dotOutputToFile(std::ofstream& dotOutfile) const { + // Graphviz Output start + dotOutfile << "Dot Output:" << std::endl << "digraph model {" << std::endl; + + // Vertices of the digraph + storm::storage::BitVector stateCoverage = storm::storage::BitVector(numberOfStates, true); + for (uint_fast64_t i = stateCoverage.getNextSetIndex(0); i!= numberOfStates; i= stateCoverage.getNextSetIndex(i+1)) { + if (getNode(i) == NULL) { + continue; + } + for (uint_fast64_t j = i + 1; j < numberOfStates; j++) { + if (getNode(j) == getNode(i)) stateCoverage.set(j, false); + } + + dotOutfile << "\t" << nodeName(*getNode(i)) << " [ label = \"" << nodeLabel(*getNode(i)) << "\" ];" << std::endl; + } + + // Edges of the digraph + for (uint_fast64_t i = stateCoverage.getNextSetIndex(0); i!= numberOfStates; i= stateCoverage.getNextSetIndex(i+1)) { + storm::storage::BitVector v = storm::storage::BitVector(numberOfStates, false); + Node* currentNode = getNode(i); + if (currentNode == NULL){ + continue; + } + + for (uint_fast64_t s1 : getNode(i)->statesAbove) { + v |= (currentNode->statesAbove & getNode(s1)->statesAbove); + } + + std::set<Node*> seenNodes; + for (uint_fast64_t state : currentNode->statesAbove) { + Node* n = getNode(state); + if (std::find(seenNodes.begin(), seenNodes.end(), n) == seenNodes.end()) { + seenNodes.insert(n); + if (!v[state]) { + dotOutfile << "\t" << nodeName(*currentNode) << " -> " << nodeName(*getNode(state)) << ";" << std::endl; + } + + } + } + + } + + // Graphviz Output end + dotOutfile << "}" << std::endl; + } + + /*** Private methods ***/ + + void Order::init(uint_fast64_t numberOfStates, storage::Decomposition<storage::StronglyConnectedComponent> decomposition, bool doneBuilding) { + this->numberOfStates = numberOfStates; + this->invalid = false; + this->nodes = std::vector<Node *>(numberOfStates, nullptr); + this->doneStates = storm::storage::BitVector(numberOfStates, false); + assert (doneStates.getNumberOfSetBits() == 0); + if (decomposition.size() == 0) { + this->trivialStates = storm::storage::BitVector(numberOfStates, true); + } else { + this->trivialStates = storm::storage::BitVector(numberOfStates, false); + for (auto& scc : decomposition) { + if (scc.size() == 1) { + trivialStates.set(*(scc.begin())); + } + } + } + this->top = new Node(); + this->bottom = new Node(); + this->top->statesAbove = storm::storage::BitVector(numberOfStates, false); + this->bottom->statesAbove = storm::storage::BitVector(numberOfStates, false); + this->doneBuilding = doneBuilding; } - bool Order::above(storm::analysis::Order::Node *node1, storm::analysis::Order::Node *node2, - storm::analysis::Order::Node *nodePrev, storm::storage::BitVector *statesSeen) { + bool Order::aboveFast(Node* node1, Node* node2) const { bool found = false; for (auto const& state : node1->states) { found = ((node2->statesAbove))[state]; @@ -315,67 +683,123 @@ namespace storm { break; } } - if (!found) { - nodePrev->statesAbove|=((node2->statesAbove)); - statesSeen->operator|=(((node2->statesAbove))); + return found; + } - for (auto const &i: node2->statesAbove) { - if (!(*statesSeen)[i]) { - auto nodeI = getNode(i); - if (((nodeI->statesAbove) & *statesSeen) != (nodeI->statesAbove)) { - found = above(node1, nodeI, node2, statesSeen); - } + bool Order::above(Node *node1, Node *node2) { + assert (!aboveFast(node1, node2)); + // Check whether node1 is above node2 by going over all states that are above state 2 + bool above = false; + // Only do this when we have to deal with forward reasoning or we are not yet done with the building of the order + if (!trivialStates.full() || !doneBuilding) { + // First gather all states that are above node 2; + + storm::storage::BitVector statesSeen((node2->statesAbove)); + std::queue<uint_fast64_t> statesToHandle; + for (auto state : statesSeen) { + statesToHandle.push(state); + } + while (!above && !statesToHandle.empty()) { + // Get first item from the queue + auto state = statesToHandle.front(); + statesToHandle.pop(); + auto node = getNode(state); + if (aboveFast(node1, node)) { + above = true; + continue; } - if (found) { - break; + for (auto newState: node->statesAbove) { + if (!statesSeen[newState]) { + statesToHandle.push(newState); + statesSeen.set(newState); + } } - } } - return found; + if (above) { + for (auto state : node1->states) { + node2->statesAbove.set(state); + } + } + return above; } - void Order::mergeNodes(storm::analysis::Order::Node *node1, storm::analysis::Order::Node *node2) { - // Merges node2 into node 1 - // everything above n2 also above n1 - node1->statesAbove|=((node2->statesAbove)); - // everything below node 2 also below node 1 + std::string Order::nodeName(Node n) const { + auto itr = n.states.begin(); + std::string name = "n" + std::to_string(*itr); + return name; + } - // add states of node 2 to node 1 - node1->states.insert(node2->states.begin(), node2->states.end()); + std::string Order::nodeLabel(Node n) const { + if (n.states == top->states) return "=)"; + if (n.states == bottom->states) return "=("; + auto itr = n.states.begin(); + std::string label = "s" + std::to_string(*itr); + ++itr; + if (itr != n.states.end()) label = "[" + label + "]"; + return label; + } - for(auto const& i : node2->states) { - nodes[i] = node1; + bool Order::existsNextState() { + return !doneStates.full(); + } + + bool Order::isTrivial(uint_fast64_t state) { + return trivialStates[state]; + } + + std::pair<uint_fast64_t, bool> Order::getNextStateNumber() { + while (!statesSorted.empty()) { + auto state = statesSorted.back(); + statesSorted.pop_back(); + if (!doneStates[state]) { + return {state, true}; + } } + return {numberOfStates, true}; } - void Order::merge(uint_fast64_t var1, uint_fast64_t var2) { - mergeNodes(getNode(var1), getNode(var2)); + std::pair<uint_fast64_t, bool> Order::getStateToHandle() { + assert (existsStateToHandle()); + auto state = statesToHandle.back(); + statesToHandle.pop_back(); + return {state, false}; } - std::vector<uint_fast64_t> Order::getStatesSorted() { - return statesSorted; + bool Order::existsStateToHandle() { + while (!statesToHandle.empty() && doneStates[statesToHandle.back()]) { + statesToHandle.pop_back(); + } + return !statesToHandle.empty(); } - uint_fast64_t Order::getNextSortedState() { - if (statesSorted.begin() != statesSorted.end()) { - return *(statesSorted.begin()); - } else { - return numberOfStates; + void Order::addStateToHandle(uint_fast64_t state) { + if (!doneStates[state]) { + statesToHandle.push_back(state); } } - void Order::removeFirstStatesSorted() { - statesSorted.erase(statesSorted.begin()); + void Order::addStateSorted(uint_fast64_t state) { + statesSorted.push_back(state); + } + + std::pair<bool, bool> Order::allAboveBelow(std::vector<uint_fast64_t> const states, uint_fast64_t state) { + auto allAbove = true; + auto allBelow = true; + for (auto & checkState : states) { + auto comp = compare(checkState, state); + allAbove &= (comp == ABOVE || comp == SAME); + allBelow &= (comp == BELOW || comp == SAME); + } + return {allAbove, allBelow}; } - void Order::removeStatesSorted(uint_fast64_t state) { - assert(containsStatesSorted(state)); - statesSorted.erase(std::find(statesSorted.begin(), statesSorted.end(), state)); + uint_fast64_t Order::getNumberOfDoneStates() const { + return doneStates.getNumberOfSetBits(); } - bool Order::containsStatesSorted(uint_fast64_t state) { - return std::find(statesSorted.begin(), statesSorted.end(), state) != statesSorted.end(); + bool Order::isInvalid() const { + return invalid; } } } diff --git a/src/storm-pars/analysis/Order.h b/src/storm-pars/analysis/Order.h index fa4397498..0639760f6 100644 --- a/src/storm-pars/analysis/Order.h +++ b/src/storm-pars/analysis/Order.h @@ -1,22 +1,22 @@ #ifndef ORDER_ORDER_H #define ORDER_ORDER_H +#include <boost/container/flat_set.hpp> #include <iostream> -#include <set> #include <vector> -#include <unordered_map> -#include <boost/container/flat_set.hpp> +#include <memory> #include "storm/storage/BitVector.h" +#include "storm/storage/StronglyConnectedComponent.h" + namespace storm { namespace analysis { - class Order { public: /*! - * Constants for comparison of nodes/states + * Constants for comparison of nodes/states. */ enum NodeComparison { UNKNOWN, @@ -24,127 +24,177 @@ namespace storm { ABOVE, SAME, }; + + /*! + * Nodes of the Reachability Order. Contains all states with the same reachability. + */ struct Node { boost::container::flat_set<uint_fast64_t> states; storm::storage::BitVector statesAbove; }; /*! - * Constructs an order with the given top node and bottom node. + * Constructs an order with the given top and bottom states. * - * @param topNode The top node of the resulting order. - * @param bottomNode The bottom node of the resulting order. + * @param topStates A Bitvector with the top states of the resulting order. + * @param bottomStates A Bitvector with the bottom states of the resulting order. + * @param numberOfStates Maximum number of states in order. + * @param statesSorted Pointer to a vector which contains the states which still need to added to the order. */ - Order(storm::storage::BitVector* topStates, - storm::storage::BitVector* bottomStates, - storm::storage::BitVector* initialMiddleStates, - uint_fast64_t numberOfStates, - std::vector<uint_fast64_t>* statesSorted); + Order(storm::storage::BitVector* topStates, storm::storage::BitVector* bottomStates, uint_fast64_t numberOfStates, storage::Decomposition<storage::StronglyConnectedComponent> sccsSorted, std::vector<uint_fast64_t> statesSorted); /*! * Constructs an order with the given top state and bottom state. * * @param top The top state of the resulting order. * @param bottom The bottom state of the resulting order. - * @param numberOfStates Max number of states in order. + * @param numberOfStates Maximum number of states in order. + * @param statesSorted Pointer to a vector which contains the states which still need to added to the order. */ - Order(uint_fast64_t top, - uint_fast64_t bottom, - uint_fast64_t numberOfStates, - std::vector<uint_fast64_t>* statesSorted); + Order(uint_fast64_t top, uint_fast64_t bottom, uint_fast64_t numberOfStates, storage::Decomposition<storage::StronglyConnectedComponent> sccsSorted, std::vector<uint_fast64_t> statesSorted); /*! - * Constructs a copy of the given order. + * Constructs a new Order. + */ + Order(); + + /*! + * Adds state between the top and bottom node of the order. * - * @param order The original order. + * @param state The given state. */ - Order(Order* order); + void add(uint_fast64_t state); + + /*! + * Adds a node with the given state above the given node. + * + * @param state The state which is added. + * @param node The pointer to the node above which the state is added, should not be nullptr. + */ + void addAbove(uint_fast64_t state, Node *node); + + /*! + * Adds a node with the given state below the given node. + * + * @param state The state which is added. + * @param node The pointer to the node below which the state is added, should not be nullptr. + */ + void addBelow(uint_fast64_t state, Node *node); /*! * Adds a node with the given state below node1 and above node2. + * * @param state The given state. - * @param node1 The pointer to the node below which a new node (with state) is added - * @param node2 The pointer to the node above which a new node (with state) is added + * @param node1 The pointer to the node below which a new node (with state) is added. + * @param node2 The pointer to the node above which a new node (with state) is added. */ void addBetween(uint_fast64_t state, Node *node1, Node *node2); /*! * Adds a node with the given state between the nodes of below and above. * Result: below -> state -> above + * * @param state The given state. - * @param above The state number of the state below which a new node (with state) is added - * @param below The state number of the state above which a new node (with state) is added + * @param above The state number of the state below which a new node (with state) is added. + * @param below The state number of the state above which a new node (with state) is added. */ void addBetween(uint_fast64_t state, uint_fast64_t above, uint_fast64_t below); + /*! + * Adds a new relation between two nodes to the order. + * + * @param above The node closest to the top Node of the Order. + * @param below The node closest to the bottom Node of the Order. + */ + void addRelationNodes(storm::analysis::Order::Node *above, storm::analysis::Order::Node * below, bool allowMerge = false); + + /*! + * Adds a new relation between two states to the order. + * + * @param above The state closest to the top Node of the Order. + * @param below The state closest to the bottom Node of the Order. + */ + void addRelation(uint_fast64_t above, uint_fast64_t below, bool allowMerge = false); + /*! * Adds state to the states of the given node. + * * @param state The state which is added. * @param node The pointer to the node to which state is added, should not be nullptr. */ void addToNode(uint_fast64_t state, Node *node); /*! - * Adds state between the top and bottom node of the order - * @param state The given state + * Merges node2 into node1. + * @return false when merging leads to invalid order */ - void add(uint_fast64_t state); + bool mergeNodes(Node* node1, Node* node2); /*! - * Adds a new relation between two nodes to the order - * @param above The node closest to the top Node of the Order. - * @param below The node closest to the bottom Node of the Order. + * Merges node of var2 into node of var1. + * @return false when merging leads to invalid order */ - void addRelationNodes(storm::analysis::Order::Node *above, storm::analysis::Order::Node * below); + bool merge(uint_fast64_t var1, uint_fast64_t var2); /*! - * Adds a new relation between two states to the order - * @param above The state closest to the top Node of the Order. - * @param below The state closest to the bottom Node of the Order. - */ - void addRelation(uint_fast64_t above, uint_fast64_t below); + * Compares the level of the nodes of the states. + * Behaviour unknown when one or more of the states does not occur at any Node in the Order. + * + * @param State1 the first state. + * @param State2 the second state. + * @return SAME if the nodes are on the same level; + * ABOVE if the node of the first state is closer to top than the node of the second state; + * BELOW if the node of the second state is closer to top than the node of the first state; + * UNKNOWN if it is unclear from the structure of the order how the nodes relate. + */ + Order::NodeComparison compare(uint_fast64_t state1, uint_fast64_t state2, NodeComparison hypothesis = UNKNOWN); + Order::NodeComparison compareFast(uint_fast64_t state1, uint_fast64_t state2, NodeComparison hypothesis = UNKNOWN) const; /*! - * Compares the level of the nodes of the states. - * Behaviour unknown when one or more of the states doesnot occur at any Node in the Order. - * @param state1 The first state. - * @param state2 The second state. + * Compares the level of the two nodes. + * + * @param node1 The first node. + * @param node2 The second node. * @return SAME if the nodes are on the same level; - * ABOVE if the node of the first state is closer to top then the node of the second state; - * BELOW if the node of the second state is closer to top then the node of the first state; - * UNKNOWN if it is unclear from the structure of the order how the nodes relate. + * ABOVE if node1 is closer to top than node2; + * BELOW if node2 is closer to top than node1; + * UNKNOWN if it is unclear from the structure of the order how the nodes relate. */ - Order::NodeComparison compare(uint_fast64_t state1, uint_fast64_t state2); + NodeComparison compare(Node* node1, Node* node2, NodeComparison hypothesis = UNKNOWN); + NodeComparison compareFast(Node* node1, Node* node2, NodeComparison hypothesis = UNKNOWN) const; /*! - * Check if state is already in order - * @param state - * @return + * Check if state is already contained in order. */ - bool contains(uint_fast64_t state); + bool contains(uint_fast64_t state) const; + /*! - * Retrieves the pointer to a Node at which the state occurs. - * - * @param state The number of the state. + * Retrieves the bottom node of the order. * - * @return The pointer to the node of the state, nullptr if the node does not exist. + * @return The bottom node. */ - Node *getNode(uint_fast64_t state); + Node* getBottom() const; /*! - * Retrieves the top node of the order. - * - * @return The top node. + * Returns true if done building the order. */ - Node* getTop(); + bool getDoneBuilding() const; /*! - * Retrieves the bottom node of the order. + * Returns the next done state of the order, returns the number of state if end of done states is reached. + */ + uint_fast64_t getNextDoneState(uint_fast64_t state) const; + + uint_fast64_t getNumberOfDoneStates() const; + + /*! + * Retrieves the pointer to a Node at which the state occurs. * - * @return The bottom node. + * @param state The number of the state. + * @return The pointer to the node of the state, nullptr if the node does not exist. */ - Node* getBottom(); + Node *getNode(uint_fast64_t state) const; /*! * Returns the vector with the nodes of the order. @@ -153,82 +203,151 @@ namespace storm { * * @return The vector with nodes of the order. */ - std::vector<Node*> getNodes(); + std::vector<Node*> getNodes() const; + std::vector<uint_fast64_t>& getStatesSorted(); /*! - * Returns a BitVector in which all added states are set. + * Retrieves the top node of the order. * - * @return The BitVector with all added states. + * @return The top node. */ - storm::storage::BitVector* getAddedStates(); + Node* getTop() const; /*! - * Returns true if done building the order. - * @return + * Returns the number of added states. */ - bool getDoneBuilding(); + uint_fast64_t getNumberOfAddedStates() const; /*! - * Compares two nodes in the order - * @param node1 - * @param node2 - * @return BELOW, ABOVE, SAME or UNKNOWN + * Returns the number of possible states in the order. */ - NodeComparison compare(Node* node1, Node* node2); + uint_fast64_t getNumberOfStates() const; /*! - * Sorts the given stats if possible. + * Checks if the given state is a bottom state. + */ + bool isBottomState(uint_fast64_t) const; + + /*! + * Checks if the given state is a top state. + */ + bool isTopState(uint_fast64_t) const; + + /*! + * Returns if the order only consists of bottom and top states (so no in-between nodes). + */ + bool isOnlyBottomTopOrder() const; + + /*! + * Sorts the given states if possible. * - * @param states Bitvector of the states to sort + * @param states Vector of the states to be sorted. * @return Vector with states sorted, length equals number of states to sort. - * If states cannot be sorted, last state of the vector will always equal the length of the BitVector + * If states cannot be sorted, last state of the vector will always equal the length of the BitVector. */ - std::vector<uint_fast64_t> sortStates(storm::storage::BitVector* states); + std::vector<uint_fast64_t> sortStates(std::vector<uint_fast64_t>* states); + std::pair<bool, bool> allAboveBelow(std::vector<uint_fast64_t>const states, uint_fast64_t state); /*! - * If the order is fully build, this can be set to true. + * Sorts the given states if possible. + * + * @param states Vector of the states to be sorted. + * @return s1, s2, vector + * if s1 == numberOfSTates, all states could be sorted including current + * if s1 < numberOfStates && s2 == numberOfStates, all states excluding s1 could be sorted, forward reasonging can be continued + * else assumption is needed */ - void setDoneBuilding(bool done); + std::pair<std::pair<uint_fast64_t,uint_fast64_t>, std::vector<uint_fast64_t>> sortStatesForForward(uint_fast64_t currentState, std::vector<uint_fast64_t> const& successors); /*! - * Prints a string representation of the order to the output stream. + * Sorts the given states if possible. * - * @param out The stream to output to. + * @param states Vector of the states to be sorted. + * @return pair of unsortabe states, vector with states sorted (so far). + * If all states could be sorted, both values of the pair are numberOfStates and the vectors length will equal the number of states to sort. */ - void toString(std::ostream &out); + std::pair<std::pair<uint_fast64_t ,uint_fast64_t>,std::vector<uint_fast64_t>> sortStatesUnorderedPair(const std::vector<uint_fast64_t>* states); /*! - * Merges node2 into node1 - * @param node1 - * @param node2 + * Sorts the given states if possible. + * + * @param states Bitvector of the states to be sorted. + * @return vector with states sorted, length equals number of states to sort. + * If states cannot be sorted, last state of the vector will always equal the length of the BitVector. */ - void mergeNodes(Node* node1, Node* node2); + std::vector<uint_fast64_t> sortStates(storm::storage::BitVector* states); + + bool isTrivial(uint_fast64_t state); + std::pair<uint_fast64_t, bool> getNextStateNumber(); + +// bool existsNextSCC(); + bool existsNextState(); + bool existsStateToHandle(); + + std::pair<uint_fast64_t, bool> getStateToHandle(); + + void addStateToHandle(uint_fast64_t state); + void addStateSorted(uint_fast64_t state); + /*! - * Merges node of var2 into node of var1 - * @param var1 - * @param var2 + * If the order is fully built, this can be set to true. */ - void merge(uint_fast64_t var1, uint_fast64_t var2); - - storm::storage::BitVector* statesToHandle; + void setDoneBuilding(bool done = true); - uint_fast64_t getNextSortedState(); + /*! + * Prints the dot output to normal STORM_PRINT. + */ + void toDotOutput() const; - bool containsStatesSorted(uint_fast64_t state); + /*! + * Writes dotoutput to the given file. + * + * @param dotOutfile + */ + void dotOutputToFile(std::ofstream& dotOutfile) const; - void removeFirstStatesSorted(); + /*! + * Creates a copy of the calling Order. + * + * @return Pointer to the copy. + */ + std::shared_ptr<Order> copy() const; +// void setAddedSCC(uint_fast64_t sccNumber); + void setDoneState(uint_fast64_t sccNumber); - void removeStatesSorted(uint_fast64_t state); + bool isInvalid() const; protected: - std::vector<uint_fast64_t> getStatesSorted(); + storage::Decomposition<storage::StronglyConnectedComponent> getDecomposition() const; + private: - std::vector<Node*> nodes; + bool above(Node * node1, Node * node2); - std::vector<uint_fast64_t> statesSorted; + bool above(Node * node1, Node * node2, storm::analysis::Order::Node *nodePrev, storm::storage::BitVector *statesSeen); + + bool aboveFast(Node * node1, Node * node2) const; + + void init(uint_fast64_t numberOfStates, storage::Decomposition<storage::StronglyConnectedComponent>, bool doneBuilding = false); - storm::storage::BitVector* addedStates; + std::string nodeName(Node n) const; + + std::string nodeLabel(Node n) const; + + bool invalid; + + void nodeOutput(); + + bool doneBuilding; + + bool onlyBottomTopOrder; + + storm::storage::BitVector doneStates; + storm::storage::BitVector trivialStates; + + std::vector<Node*> nodes; + + std::vector<uint_fast64_t> statesToHandle; Node* top; @@ -236,11 +355,10 @@ namespace storm { uint_fast64_t numberOfStates; - bool above(Node * node1, Node * node2); + uint_fast64_t numberOfAddedStates; - bool above(Node * node1, Node * node2, storm::analysis::Order::Node *nodePrev, storm::storage::BitVector *statesSeen); + std::vector<uint_fast64_t> statesSorted; - bool doneBuilding; }; } } diff --git a/src/storm-pars/analysis/OrderExtender.cpp b/src/storm-pars/analysis/OrderExtender.cpp index ebf8dc1eb..cc4967697 100644 --- a/src/storm-pars/analysis/OrderExtender.cpp +++ b/src/storm-pars/analysis/OrderExtender.cpp @@ -1,272 +1,204 @@ -// -// Created by Jip Spel on 28.08.18. -// - #include "OrderExtender.h" -#include "storm/utility/macros.h" -#include "storm/utility/graph.h" -#include "storm/storage/SparseMatrix.h" -#include "storm/utility/graph.h" -#include <storm/logic/Formula.h> -#include <storm/modelchecker/propositional/SparsePropositionalModelChecker.h> -#include "storm/models/sparse/Model.h" -#include "storm/modelchecker/results/CheckResult.h" -#include "storm/modelchecker/results/ExplicitQualitativeCheckResult.h" - -#include "storm/exceptions/NotImplementedException.h" #include "storm/exceptions/NotSupportedException.h" - -#include <set> -#include <boost/container/flat_set.hpp> -#include "storm/storage/StronglyConnectedComponentDecomposition.h" -#include "storm/storage/StronglyConnectedComponent.h" - +#include "storm/modelchecker/results/ExplicitQualitativeCheckResult.h" +#include "storm/modelchecker/propositional/SparsePropositionalModelChecker.h" #include "storm/storage/BitVector.h" +#include "storm/storage/SparseMatrix.h" #include "storm/utility/macros.h" -#include "storm/utility/Stopwatch.h" +#include "storm/utility/graph.h" +#include "storm-pars/api/region.h" +#include "storm-pars/api/export.h" +#include "storm-pars/analysis/MonotonicityHelper.h" +#include "storm/storage/StronglyConnectedComponentDecomposition.h" + +#include "storm/modelchecker/results/ExplicitQuantitativeCheckResult.h" namespace storm { namespace analysis { - template<typename ValueType> - OrderExtender<ValueType>::OrderExtender(std::shared_ptr<storm::models::sparse::Model<ValueType>> model) { + template <typename ValueType, typename ConstantType> + OrderExtender<ValueType, ConstantType>::OrderExtender(std::shared_ptr<models::sparse::Model<ValueType>> model, std::shared_ptr<logic::Formula const> formula) : monotonicityChecker(MonotonicityChecker<ValueType>(model->getTransitionMatrix())) { this->model = model; this->matrix = model->getTransitionMatrix(); - this->assumptionSeen = false; - uint_fast64_t numberOfStates = this->model->getNumberOfStates(); + this->numberOfStates = this->model->getNumberOfStates(); + this->formula = formula; + this->assumptionMaker = new analysis::AssumptionMaker<ValueType, ConstantType>(matrix); + } - // Build stateMap - for (uint_fast64_t i = 0; i < numberOfStates; ++i) { - stateMap[i] = new storm::storage::BitVector(numberOfStates, false); - auto row = matrix.getRow(i); - for (auto rowItr = row.begin(); rowItr != row.end(); ++rowItr) { - // ignore self-loops when there are more transitions - if (i != rowItr->getColumn() || row.getNumberOfEntries() == 1) { - stateMap[i]->set(rowItr->getColumn(), true); - } - } - } + template <typename ValueType, typename ConstantType> + OrderExtender<ValueType, ConstantType>::OrderExtender(storm::storage::BitVector* topStates, storm::storage::BitVector* bottomStates, storm::storage::SparseMatrix<ValueType> matrix) : monotonicityChecker(MonotonicityChecker<ValueType>(matrix)) { + this->matrix = matrix; + this->model = nullptr; + this->monotonicityChecker = MonotonicityChecker<ValueType>(matrix); - // Check if MC contains cycles - storm::storage::StronglyConnectedComponentDecompositionOptions const options; - this->sccs = storm::storage::StronglyConnectedComponentDecomposition<ValueType>(matrix, options); - acyclic = true; - for (size_t i = 0; acyclic && i < sccs.size(); ++i) { - acyclic &= sccs.getBlock(i).size() <= 1; - } - } + storm::storage::StronglyConnectedComponentDecompositionOptions options; + options.forceTopologicalSort(); - template <typename ValueType> - std::tuple<Order*, uint_fast64_t, uint_fast64_t> OrderExtender<ValueType>::toOrder(std::vector<std::shared_ptr<storm::logic::Formula const>> formulas) { - STORM_LOG_THROW((++formulas.begin()) == formulas.end(), storm::exceptions::NotSupportedException, "Only one formula allowed for monotonicity analysis"); - STORM_LOG_THROW((*(formulas[0])).isProbabilityOperatorFormula() - && ((*(formulas[0])).asProbabilityOperatorFormula().getSubformula().isUntilFormula() - || (*(formulas[0])).asProbabilityOperatorFormula().getSubformula().isEventuallyFormula()), storm::exceptions::NotSupportedException, "Expecting until or eventually formula"); - - uint_fast64_t numberOfStates = this->model->getNumberOfStates(); - - storm::modelchecker::SparsePropositionalModelChecker<storm::models::sparse::Model<ValueType>> propositionalChecker(*model); - storm::storage::BitVector phiStates; - storm::storage::BitVector psiStates; - if ((*(formulas[0])).asProbabilityOperatorFormula().getSubformula().isUntilFormula()) { - phiStates = propositionalChecker.check((*(formulas[0])).asProbabilityOperatorFormula().getSubformula().asUntilFormula().getLeftSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector(); - psiStates = propositionalChecker.check((*(formulas[0])).asProbabilityOperatorFormula().getSubformula().asUntilFormula().getRightSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector(); - } else { - phiStates = storm::storage::BitVector(numberOfStates, true); - psiStates = propositionalChecker.check((*(formulas[0])).asProbabilityOperatorFormula().getSubformula().asEventuallyFormula().getSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector(); + this->numberOfStates = matrix.getColumnCount(); + std::vector<uint64_t> firstStates; + + storm::storage::BitVector subStates (topStates->size(), true); + for (auto state : *topStates) { + firstStates.push_back(state); + subStates.set(state, false); + } + for (auto state : *bottomStates) { + firstStates.push_back(state); + subStates.set(state, false); + } + cyclic = storm::utility::graph::hasCycle(matrix, subStates); + storm::storage::StronglyConnectedComponentDecomposition<ValueType> decomposition; + if (cyclic) { + decomposition = storm::storage::StronglyConnectedComponentDecomposition<ValueType>(matrix, options); } + auto statesSorted = storm::utility::graph::getTopologicalSort(matrix.transpose(), firstStates); + this->bottomTopOrder = std::shared_ptr<Order>(new Order(topStates, bottomStates, numberOfStates, std::move(decomposition), std::move(statesSorted))); - // Get the maybeStates - std::pair<storm::storage::BitVector, storm::storage::BitVector> statesWithProbability01 = storm::utility::graph::performProb01(this->model->getBackwardTransitions(), phiStates, psiStates); - storm::storage::BitVector topStates = statesWithProbability01.second; - storm::storage::BitVector bottomStates = statesWithProbability01.first; - - STORM_LOG_THROW(topStates.begin() != topStates.end(), storm::exceptions::NotImplementedException, "Formula yields to no 1 states"); - STORM_LOG_THROW(bottomStates.begin() != bottomStates.end(), storm::exceptions::NotImplementedException, "Formula yields to no zero states"); - - // Transform to Order - auto matrix = this->model->getTransitionMatrix(); - - auto initialMiddleStates = storm::storage::BitVector(numberOfStates); - // Add possible cycle breaking states - if (!acyclic) { - for (size_t i = 0; i < sccs.size(); ++i) { - auto scc = sccs.getBlock(i); - if (scc.size() > 1) { - auto states = scc.getStates(); - // check if the state has already one successor in bottom of top, in that case pick it - for (auto const& state : states) { - auto successors = stateMap[state]; - if (successors->getNumberOfSetBits() == 2) { - auto succ1 = successors->getNextSetIndex(0); - auto succ2 = successors->getNextSetIndex(succ1 + 1); - auto intersection = bottomStates | topStates; - if (intersection[succ1] || intersection[succ2]) { - initialMiddleStates.set(state); - break; - } - } + // Build stateMap + for (uint_fast64_t state = 0; state < numberOfStates; ++state) { + auto const& row = matrix.getRow(state); + stateMap[state] = std::vector<uint_fast64_t>(); + std::set<VariableType> occurringVariables; + + for (auto& entry : matrix.getRow(state)) { + + // ignore self-loops when there are more transitions + if (state != entry.getColumn() || row.getNumberOfEntries() == 1) { + if (!subStates[entry.getColumn()] && !bottomTopOrder->contains(state)) { + bottomTopOrder->add(state); } + stateMap[state].push_back(entry.getColumn()); } + storm::utility::parametric::gatherOccurringVariables(entry.getValue(), occurringVariables); + + } + if (occurringVariables.empty()) { + nonParametricStates.insert(state); + } + + for (auto& var : occurringVariables) { + occuringStatesAtVariable[var].push_back(state); } + occuringVariablesAtState.push_back(std::move(occurringVariables)); } - std::vector<uint_fast64_t> statesSorted = storm::utility::graph::getTopologicalSort(matrix); - Order *order = new Order(&topStates, &bottomStates, &initialMiddleStates, numberOfStates, &statesSorted); - return this->extendOrder(order); + this->assumptionMaker = new analysis::AssumptionMaker<ValueType, ConstantType>(matrix); } + template <typename ValueType, typename ConstantType> + std::shared_ptr<Order> OrderExtender<ValueType, ConstantType>::getBottomTopOrder() { + if (bottomTopOrder == nullptr) { + assert (model != nullptr); + STORM_LOG_THROW(matrix.getRowCount() == matrix.getColumnCount(), exceptions::NotSupportedException,"Creating order not supported for non-square matrix"); + modelchecker::SparsePropositionalModelChecker<models::sparse::Model<ValueType>> propositionalChecker(*model); + storage::BitVector phiStates; + storage::BitVector psiStates; + assert (formula->isProbabilityOperatorFormula()); + if (formula->asProbabilityOperatorFormula().getSubformula().isUntilFormula()) { + phiStates = propositionalChecker.check( + formula->asProbabilityOperatorFormula().getSubformula().asUntilFormula().getLeftSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector(); + psiStates = propositionalChecker.check( + formula->asProbabilityOperatorFormula().getSubformula().asUntilFormula().getRightSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector(); + } else { + assert (formula->asProbabilityOperatorFormula().getSubformula().isEventuallyFormula()); + phiStates = storage::BitVector(numberOfStates, true); + psiStates = propositionalChecker.check( + formula->asProbabilityOperatorFormula().getSubformula().asEventuallyFormula().getSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector(); + } + // Get the maybeStates + std::pair<storage::BitVector, storage::BitVector> statesWithProbability01 = utility::graph::performProb01(this->model->getBackwardTransitions(), phiStates, psiStates); + storage::BitVector topStates = statesWithProbability01.second; + storage::BitVector bottomStates = statesWithProbability01.first; + + STORM_LOG_THROW(topStates.begin() != topStates.end(), exceptions::NotSupportedException,"Formula yields to no 1 states"); + STORM_LOG_THROW(bottomStates.begin() != bottomStates.end(), exceptions::NotSupportedException,"Formula yields to no zero states"); + auto& matrix = this->model->getTransitionMatrix(); + std::vector<uint64_t> firstStates; + + storm::storage::BitVector subStates (topStates.size(), true); + for (auto state : topStates) { + firstStates.push_back(state); + subStates.set(state, false); + } + for (auto state : bottomStates) { + firstStates.push_back(state); + subStates.set(state, false); + } + cyclic = storm::utility::graph::hasCycle(matrix, subStates); + storm::storage::StronglyConnectedComponentDecomposition<ValueType> decomposition; + if (cyclic) { + storm::storage::StronglyConnectedComponentDecompositionOptions options; + options.forceTopologicalSort(); + decomposition = storm::storage::StronglyConnectedComponentDecomposition<ValueType>(matrix, options); + } + auto statesSorted = storm::utility::graph::getTopologicalSort(matrix.transpose(), firstStates); + bottomTopOrder = std::shared_ptr<Order>(new Order(&topStates, &bottomStates, numberOfStates, std::move(decomposition), std::move(statesSorted))); + + // Build stateMap + for (uint_fast64_t state = 0; state < numberOfStates; ++state) { + auto const& row = matrix.getRow(state); + stateMap[state] = std::vector<uint_fast64_t>(); + std::set<VariableType> occurringVariables; + + for (auto& entry : matrix.getRow(state)) { + + // ignore self-loops when there are more transitions + if (state != entry.getColumn() || row.getNumberOfEntries() == 1) { +// if (!subStates[entry.getColumn()] && !bottomTopOrder->contains(state)) { +// bottomTopOrder->add(state); +// } + stateMap[state].push_back(entry.getColumn()); + } + storm::utility::parametric::gatherOccurringVariables(entry.getValue(), occurringVariables); - template <typename ValueType> - std::tuple<Order*, uint_fast64_t, uint_fast64_t> OrderExtender<ValueType>::toOrder(std::vector<std::shared_ptr<storm::logic::Formula const>> formulas, std::vector<double> minValues, std::vector<double> maxValues) { - uint_fast64_t numberOfStates = this->model->getNumberOfStates(); - uint_fast64_t bottom = numberOfStates; - uint_fast64_t top = numberOfStates; - std::vector<uint_fast64_t> statesSorted = storm::utility::graph::getTopologicalSort(matrix); - Order *order = nullptr; - - for (auto state : statesSorted) { - if ((minValues[numberOfStates - 1 - state] == 1 || maxValues[numberOfStates - 1 - state] == 0) - && minValues[numberOfStates - 1 - state] == maxValues[numberOfStates - 1 - state]) { - if (maxValues[numberOfStates - 1 - state] == 0) { - assert (bottom == numberOfStates); - bottom = state; } - if (minValues[numberOfStates - 1 - state] == 1) { - assert (top == numberOfStates); - top = state; + if (occurringVariables.empty()) { + nonParametricStates.insert(state); } - if (bottom != numberOfStates && top != numberOfStates) { - order = new Order(top, bottom, numberOfStates, &statesSorted); - } - - } else { - assert (order != nullptr); - auto successors = stateMap[state]; - if (successors->getNumberOfSetBits() == 1) { - auto succ = successors->getNextSetIndex(0); - if (succ != state) { - if (!order->contains(succ)) { - order->add(succ); - } - order->addToNode(state, order->getNode(succ)); - } - } else if (successors->getNumberOfSetBits() > 1) { - uint_fast64_t min = numberOfStates; - uint_fast64_t max = numberOfStates; - bool allSorted = true; - - for (auto succ = successors->getNextSetIndex(0); - succ < numberOfStates; succ = successors->getNextSetIndex(succ + 1)) { - if (min == numberOfStates) { - assert (max == numberOfStates); - min = succ; - max = succ; - } else { - if (minValues[numberOfStates - 1 - succ] > maxValues[numberOfStates - 1 - max]) { - max = succ; - } else if (maxValues[numberOfStates - 1 - succ] < minValues[numberOfStates - 1 - min]) { - min = succ; - } else { - allSorted = false; - break; - } - } - } - if (allSorted && min != max) { - if (order->contains(min) && order->contains(max)) { - assert (order->compare(min,max) == Order::UNKNOWN || order->compare(min,max) == Order::BELOW); - if (order->compare(min, max) == Order::UNKNOWN) { - order->addRelation(max, min); - } - } - if (!order->contains(min)) { - if (order->contains(max)) { - order->addBetween(min, order->getNode(max), order->getBottom()); - } else { - order->add(min); - } - } - if (!order->contains(max)) { - // Because of construction min is in the order - order->addBetween(max, order->getTop(), order->getNode(min)); - } - assert (order->compare(max, min) == Order::ABOVE); - if (order->contains(state)) { - if (order->compare(max, state) == Order::UNKNOWN) { - order->addRelation(max, state); - } - if (order->compare(state, min) == Order::UNKNOWN) { - order->addRelation(state, min); - } - } else { - order->addBetween(state, max, min); - } - assert (order->compare(max, state) == Order::ABOVE); - assert (order->compare(state, min) == Order::ABOVE); - } + for (auto& var : occurringVariables) { + occuringStatesAtVariable[var].push_back(state); } + occuringVariablesAtState.push_back(std::move(occurringVariables)); } + } - assert (order != nullptr); - - // Handle sccs - auto addedStates = order->getAddedStates(); - for (auto scc : sccs) { - if (scc.size() > 1) { - auto states = scc.getStates(); - auto candidate = -1; - for (auto const& state : states) { - if (addedStates->get(state)) { - candidate = -1; - break; - // if there is a state of the scc already present in the order, there is no need to add one. - } - auto successors = stateMap[state]; - if (candidate == -1 && successors->getNumberOfSetBits() == 2) { - auto succ1 = successors->getNextSetIndex(0); - auto succ2 = successors->getNextSetIndex(succ1 + 1); - if (addedStates->get(succ1) || addedStates->get(succ2)) { - candidate = state; - } - } - } - if (candidate != -1) { - order->add(candidate); - order->statesToHandle->set(candidate); - } - } - } - return this->extendOrder(order); + if (minValuesInit && maxValuesInit) { + continueExtending[bottomTopOrder] = true; + usePLA[bottomTopOrder] = true; + minValues[bottomTopOrder] = std::move(minValuesInit.get()); + maxValues[bottomTopOrder] = std::move(maxValuesInit.get()); + } else { + usePLA[bottomTopOrder] = false; + } + return bottomTopOrder; } + template <typename ValueType, typename ConstantType> + std::tuple<std::shared_ptr<Order>, uint_fast64_t, uint_fast64_t> OrderExtender<ValueType, ConstantType>::toOrder(storage::ParameterRegion<ValueType> region, std::shared_ptr<MonotonicityResult<VariableType>> monRes) { + return this->extendOrder(nullptr, region, monRes, nullptr); + } - template <typename ValueType> - void OrderExtender<ValueType>::handleAssumption(Order* order, std::shared_ptr<storm::expressions::BinaryRelationExpression> assumption) { + template <typename ValueType, typename ConstantType> + void OrderExtender<ValueType, ConstantType>::handleAssumption(std::shared_ptr<Order> order, std::shared_ptr<expressions::BinaryRelationExpression> assumption) const { assert (assumption != nullptr); - assumptionSeen = true; + assert (assumption->getFirstOperand()->isVariable() && assumption->getSecondOperand()->isVariable()); - storm::expressions::BinaryRelationExpression expr = *assumption; - assert (expr.getRelationType() == storm::expressions::BinaryRelationExpression::RelationType::Greater - || expr.getRelationType() == storm::expressions::BinaryRelationExpression::RelationType::Equal); + expressions::Variable var1 = assumption->getFirstOperand()->asVariableExpression().getVariable(); + expressions::Variable var2 = assumption->getSecondOperand()->asVariableExpression().getVariable(); + auto const& val1 = std::stoul(var1.getName(), nullptr, 0); + auto const& val2 = std::stoul(var2.getName(), nullptr, 0); - if (expr.getRelationType() == storm::expressions::BinaryRelationExpression::RelationType::Equal) { - assert (expr.getFirstOperand()->isVariable() && expr.getSecondOperand()->isVariable()); - storm::expressions::Variable var1 = expr.getFirstOperand()->asVariableExpression().getVariable(); - storm::expressions::Variable var2 = expr.getSecondOperand()->asVariableExpression().getVariable(); - auto val1 = std::stoul(var1.getName(), nullptr, 0); - auto val2 = std::stoul(var2.getName(), nullptr, 0); - auto comp = order->compare(val1, val2); + assert (order->compare(val1, val2) == Order::UNKNOWN); - assert (comp == Order::UNKNOWN); - Order::Node *n1 = order->getNode(val1); - Order::Node *n2 = order->getNode(val2); + Order::Node* n1 = order->getNode(val1); + Order::Node* n2 = order->getNode(val2); + if (assumption->getRelationType() == expressions::BinaryRelationExpression::RelationType::Equal) { if (n1 != nullptr && n2 != nullptr) { order->mergeNodes(n1,n2); } else if (n1 != nullptr) { @@ -278,17 +210,7 @@ namespace storm { order->addToNode(val2, order->getNode(val1)); } } else { - assert (expr.getFirstOperand()->isVariable() && expr.getSecondOperand()->isVariable()); - storm::expressions::Variable largest = expr.getFirstOperand()->asVariableExpression().getVariable(); - storm::expressions::Variable smallest = expr.getSecondOperand()->asVariableExpression().getVariable(); - auto val1 = std::stoul(largest.getName(), nullptr, 0); - auto val2 = std::stoul(smallest.getName(), nullptr, 0); - auto compareRes = order->compare(val1, val2); - - assert(compareRes == Order::UNKNOWN); - Order::Node *n1 = order->getNode(val1); - Order::Node *n2 = order->getNode(val2); - + assert (assumption->getRelationType() == expressions::BinaryRelationExpression::RelationType::Greater); if (n1 != nullptr && n2 != nullptr) { order->addRelationNodes(n1, n2); } else if (n1 != nullptr) { @@ -302,200 +224,600 @@ namespace storm { } } - template <typename ValueType> - std::tuple<Order*, uint_fast64_t, uint_fast64_t> OrderExtender<ValueType>::extendAllSuccAdded(Order* order, uint_fast64_t const & stateNumber, storm::storage::BitVector* successors) { - auto numberOfStates = successors->size(); - assert (order->getAddedStates()->size() == numberOfStates); - - if (successors->getNumberOfSetBits() == 1) { - // As there is only one successor the current state and its successor must be at the same nodes. - order->addToNode(stateNumber, order->getNode(successors->getNextSetIndex(0))); - } else if (successors->getNumberOfSetBits() == 2) { - // Otherwise, check how the two states compare, and add if the comparison is possible. - uint_fast64_t successor1 = successors->getNextSetIndex(0); - uint_fast64_t successor2 = successors->getNextSetIndex(successor1 + 1); - - int compareResult = order->compare(successor1, successor2); - if (compareResult == Order::ABOVE) { - // successor 1 is closer to top than successor 2 - order->addBetween(stateNumber, order->getNode(successor1), - order->getNode(successor2)); - } else if (compareResult == Order::BELOW) { - // successor 2 is closer to top than successor 1 - order->addBetween(stateNumber, order->getNode(successor2), - order->getNode(successor1)); - } else if (compareResult == Order::SAME) { - // the successors are at the same level - order->addToNode(stateNumber, order->getNode(successor1)); - } else { - assert(order->compare(successor1, successor2) == Order::UNKNOWN); - return std::make_tuple(order, successor1, successor2); - } - } else if (successors->getNumberOfSetBits() > 2) { - for (auto const& i : *successors) { - for (auto j = successors->getNextSetIndex(i+1); j < numberOfStates; j = successors->getNextSetIndex(j+1)) { - if (order->compare(i,j) == Order::UNKNOWN) { - return std::make_tuple(order, i, j); + template <typename ValueType, typename ConstantType> + std::tuple<std::shared_ptr<Order>, uint_fast64_t, uint_fast64_t> OrderExtender<ValueType, ConstantType>::extendOrder(std::shared_ptr<Order> order, storm::storage::ParameterRegion<ValueType> region, std::shared_ptr<MonotonicityResult<VariableType>> monRes, std::shared_ptr<expressions::BinaryRelationExpression> assumption) { + this->region = region; + if (order == nullptr) { + order = getBottomTopOrder(); + if (usePLA[order]) { + auto &min = minValues[order]; + auto &max = maxValues[order]; + // Try to make the order as complete as possible based on pla results + auto &statesSorted = order->getStatesSorted(); + auto itr = statesSorted.begin(); + while (itr != statesSorted.end()) { + auto state = *itr; + auto &successors = stateMap[state]; + bool all = true; + for (auto i = 0; i < successors.size(); ++i) { + auto state1 = successors[i]; + for (auto j = i + 1; j < successors.size(); ++j) { + auto state2 = successors[j]; + if (min[state1] > max[state2]) { + if (!order->contains(state1)) { + order->add(state1); + } + if (!order->contains(state2)) { + order->add(state2); + } + order->addRelation(state1, state2, false); + } else if (min[state2] > max[state1]) { + if (!order->contains(state1)) { + order->add(state1); + } + if (!order->contains(state2)) { + order->add(state2); + } + order->addRelation(state2, state1, false); + } else if (min[state1] == max[state2] && max[state1] == min[state2]) { + if (!order->contains(state1) && !order->contains(state2)) { + order->add(state1); + order->addToNode(state2, order->getNode(state1)); + } else if (!order->contains(state1)) { + order->addToNode(state1, order->getNode(state2)); + } else if (!order->contains(state2)) { + order->addToNode(state2, order->getNode(state1)); + } else { + order->merge(state1, state2); + assert (!order->isInvalid()); + } + } else { + all = false; + } + } } + if (all) { + STORM_LOG_INFO("All successors of state " << state << " sorted based on min max values"); + order->setDoneState(state); + } + ++itr; } } + continueExtending[order] = true; + } + if (continueExtending[order] || assumption != nullptr) { + return extendOrder(order, monRes, assumption); + } else { + auto& res = unknownStatesMap[order]; + continueExtending[order] = false; + return {order, res.first, res.second}; + } + } - auto highest = successors->getNextSetIndex(0); - auto lowest = highest; - for (auto i = successors->getNextSetIndex(highest+1); i < numberOfStates; i = successors->getNextSetIndex(i+1)) { - if (order->compare(i, highest) == Order::ABOVE) { - highest = i; - } - if (order->compare(lowest, i) == Order::ABOVE) { - lowest = i; + template <typename ValueType, typename ConstantType> + std::tuple<std::shared_ptr<Order>, uint_fast64_t, uint_fast64_t> OrderExtender<ValueType, ConstantType>::extendOrder(std::shared_ptr<Order> order, std::shared_ptr<MonotonicityResult<VariableType>> monRes, std::shared_ptr<expressions::BinaryRelationExpression> assumption) { + if (assumption != nullptr) { + STORM_LOG_INFO("Handling assumption " << *assumption << std::endl); + handleAssumption(order, assumption); + } + + auto currentStateMode = getNextState(order, numberOfStates, false); + while (currentStateMode.first != numberOfStates) { + assert (currentStateMode.first < numberOfStates); + auto& currentState = currentStateMode.first; + auto& successors = stateMap[currentState]; + std::pair<uint_fast64_t, uint_fast64_t> result = {numberOfStates, numberOfStates}; + + if (successors.size() == 1) { + assert (order->contains(successors[0])); + handleOneSuccessor(order, currentState, successors[0]); + } else if (!successors.empty()) { + if (order->isOnlyBottomTopOrder()) { + order->add(currentState); + if (!order->isTrivial(currentState)) { + // This state is part of an scc, therefore, we could do forward reasoning here + result = extendByForwardReasoning(order, currentState, successors, assumption!=nullptr); + } else { + result = {numberOfStates, numberOfStates}; + } + } else { + result = extendNormal(order, currentState, successors, assumption != nullptr); } } - if (lowest == highest) { - order->addToNode(stateNumber, order->getNode(highest)); + + if (result.first == numberOfStates) { + // We did extend the order + assert (result.second == numberOfStates); + assert (order->sortStates(&successors).size() == successors.size()); + assert (order->contains(currentState) && order->getNode(currentState) != nullptr); + + if (monRes != nullptr && currentStateMode.second != -1) { + for (auto& param : occuringVariablesAtState[currentState]) { + checkParOnStateMonRes(currentState, order, param, monRes); + } + } + // Get the next state + currentStateMode = getNextState(order, currentState, true); } else { - order->addBetween(stateNumber, order->getNode(highest), order->getNode(lowest)); + assert (result.first < numberOfStates); + assert (result.second < numberOfStates); + assert (order->compare(result.first, result.second) == Order::UNKNOWN); + assert (order->compare(result.second, result.first) == Order::UNKNOWN); + // Try to add states based on min/max and assumptions, only if we are not in statesToHandle mode + if (currentStateMode.second && extendByAssumption(order, currentState, result.first, result.second)) { + continue; + } + // We couldn't extend the order + if (nonParametricStates.find(currentState) != nonParametricStates.end()) { + if (!order->contains(currentState)) { + // State is not parametric, so we hope that just adding it between =) and =( will help us + order->add(currentState); + } + currentStateMode = getNextState(order, currentState, true); + continue; + } else { + if (!currentStateMode.second) { + // The state was based on statesToHandle, so it is not bad if we cannot continue with this. + currentStateMode = getNextState(order, currentState, false); + continue; + } else { + // The state was based on the topological sorting, so we need to return, but first add this state to the states Sorted as we are not done with it + order->addStateSorted(currentState); + continueExtending[order] = false; + return {order, result.first, result.second}; + } + } } + assert (order->sortStates(&successors).size() == successors.size()); + } + + assert (order->getDoneBuilding()); + if (monRes != nullptr) { + // monotonicity result for the in-build checking of monotonicity + monRes->setDone(); } return std::make_tuple(order, numberOfStates, numberOfStates); } + template<typename ValueType, typename ConstantType> + std::pair<uint_fast64_t, uint_fast64_t> OrderExtender<ValueType, ConstantType>::extendNormal(std::shared_ptr<Order> order, uint_fast64_t currentState, const vector<uint_fast64_t> &successors, bool allowMerge) { + // when it is cyclic and the current state is part of an SCC we do forwardreasoning + if (cyclic && !order->isTrivial(currentState) && order->contains(currentState)) { + // Try to extend the order for this scc + return extendByForwardReasoning(order, currentState, successors, allowMerge); + } else { + assert (order->isTrivial(currentState) || !order->contains(currentState)); + // Do backward reasoning, all successor states must be in the order + return extendByBackwardReasoning(order, currentState, successors, allowMerge); + } + } - template <typename ValueType> - std::tuple<Order*, uint_fast64_t, uint_fast64_t> OrderExtender<ValueType>::extendOrder(Order* order, std::shared_ptr<storm::expressions::BinaryRelationExpression> assumption) { - auto numberOfStates = this->model->getNumberOfStates(); - - if (assumption != nullptr) { - handleAssumption(order, assumption); + template<typename ValueType, typename ConstantType> + void OrderExtender<ValueType, ConstantType>::handleOneSuccessor(std::shared_ptr<Order> order, uint_fast64_t currentState, uint_fast64_t successor) { + assert (order->contains(successor)); + if (currentState != successor) { + if (order->contains(currentState)) { + order->merge(currentState, successor); + } else { + order->addToNode(currentState, order->getNode(successor)); + } } + } - auto oldNumberSet = numberOfStates; - while (oldNumberSet != order->getAddedStates()->getNumberOfSetBits()) { - oldNumberSet = order->getAddedStates()->getNumberOfSetBits(); - - // Forward reasoning for cycles; - if (!acyclic) { - auto statesToHandle = order->statesToHandle; - auto stateNumber = statesToHandle->getNextSetIndex(0); - - while (stateNumber != numberOfStates) { - storm::storage::BitVector *successors = stateMap[stateNumber]; - // Checking for states which are already added to the order, and only have one successor left which haven't been added yet - auto succ1 = successors->getNextSetIndex(0); - auto succ2 = successors->getNextSetIndex(succ1 + 1); - - assert (order->contains(stateNumber)); - if (successors->getNumberOfSetBits() == 1) { - if (!order->contains(succ1)) { - order->addToNode(succ1, order->getNode(stateNumber)); - statesToHandle->set(succ1, true); - if (order->containsStatesSorted(succ1)) { - order->removeStatesSorted(succ1); - } + template <typename ValueType, typename ConstantType> + std::pair<uint_fast64_t, uint_fast64_t> OrderExtender<ValueType, ConstantType>::extendByBackwardReasoning(std::shared_ptr<Order> order, uint_fast64_t currentState, std::vector<uint_fast64_t> const& successors, bool allowMerge) { + assert (!order->isOnlyBottomTopOrder()); + assert (successors.size() > 1); + + bool pla = (usePLA.find(order) != usePLA.end() && usePLA.at(order)); + std::vector<uint_fast64_t> sortedSuccs; + + if (pla && (continueExtending.find(order) == continueExtending.end() || continueExtending.at(order))) { + for (auto& state1 : successors) { + if (sortedSuccs.size() == 0) { + sortedSuccs.push_back(state1); + } else { + bool added = false; + for (auto itr = sortedSuccs.begin(); itr != sortedSuccs.end(); ++itr) { + auto& state2 = *itr; + auto compareRes = order->compareFast(state1, state2); + if (compareRes == Order::NodeComparison::UNKNOWN) { + compareRes = addStatesBasedOnMinMax(order, state1, state2); } - statesToHandle->set(stateNumber, false); - stateNumber = statesToHandle->getNextSetIndex(0); - } else if (successors->getNumberOfSetBits() == 2 - && ((order->contains(succ1) && !order->contains(succ2)) - || (!order->contains(succ1) && order->contains(succ2)))) { - - if (!order->contains(succ1)) { - std::swap(succ1, succ2); + if (compareRes == Order::NodeComparison::UNKNOWN) { + // If fast comparison did not help, we continue by checking "slow" comparison + compareRes = order->compare(state1, state2); } - - auto compare = order->compare(stateNumber, succ1); - if (compare == Order::ABOVE) { - if (order->containsStatesSorted(succ2)) { - order->removeStatesSorted(succ2); - } - order->addBetween(succ2, order->getTop(), order->getNode(stateNumber)); - statesToHandle->set(succ2); - statesToHandle->set(stateNumber, false); - stateNumber = statesToHandle->getNextSetIndex(0); - } else if (compare == Order::BELOW) { - if (order->containsStatesSorted(succ2)) { - order->removeStatesSorted(succ2); - } - order->addBetween(succ2, order->getNode(stateNumber), order->getBottom()); - statesToHandle->set(succ2); - statesToHandle->set(stateNumber, false); - stateNumber = statesToHandle->getNextSetIndex(0); - } else { - // We don't know positions, so we set the current state number to false - statesToHandle->set(stateNumber, false); - stateNumber = statesToHandle->getNextSetIndex(0); + if (compareRes == Order::NodeComparison::ABOVE || + compareRes == Order::NodeComparison::SAME) { + // insert at current pointer (while keeping other values) + sortedSuccs.insert(itr, state1); + added = true; + break; + } else if (compareRes == Order::NodeComparison::UNKNOWN) { + continueExtending[order] = false; + return {state1, state2}; } - - } else if (!((order->contains(succ1) && !order->contains(succ2)) - || (!order->contains(succ1) && order->contains(succ2)))) { - stateNumber = statesToHandle->getNextSetIndex(stateNumber + 1); - } else { - statesToHandle->set(stateNumber, false); - stateNumber = statesToHandle->getNextSetIndex(0); + } + if (!added) { + sortedSuccs.push_back(state1); } } + } + } else { + auto temp = order->sortStatesUnorderedPair(&successors); + if (temp.first.first != numberOfStates) { + return temp.first; + } else { + sortedSuccs = std::move(temp.second); + } + } + if (order->compare(sortedSuccs[0], sortedSuccs[sortedSuccs.size() - 1]) == Order::SAME) { + if (!order->contains(currentState)) { + order->addToNode(currentState, order->getNode(sortedSuccs[0])); + } else { + order->merge(currentState, sortedSuccs[0]); + } + } else { + if (!order->contains(sortedSuccs[0])) { + assert (order->isBottomState(sortedSuccs[sortedSuccs.size() - 1])); + assert (sortedSuccs.size() == 2); + order->addAbove(sortedSuccs[0], order->getBottom()); + } + if (!order->contains(sortedSuccs[sortedSuccs.size() - 1])) { + assert (order->isTopState(sortedSuccs[0])); + assert (sortedSuccs.size() == 2); + order->addBelow(sortedSuccs[sortedSuccs.size() - 1], order->getTop()); + } + // sortedSuccs[0] is highest + if (!order->contains(currentState)) { + order->addBetween(currentState, sortedSuccs[0], sortedSuccs[sortedSuccs.size()-1]); + } else { + order->addRelation(sortedSuccs[0], currentState, allowMerge); + order->addRelation(currentState, sortedSuccs[sortedSuccs.size() - 1], allowMerge); } - // Normal backwardreasoning - auto stateNumber = order->getNextSortedState(); - while (stateNumber != numberOfStates && order->contains(stateNumber)) { - order->removeFirstStatesSorted(); - stateNumber = order->getNextSortedState(); + } + assert (order->contains(currentState) && order->compare(order->getNode(currentState), order->getBottom()) == Order::ABOVE && order->compare(order->getNode(currentState), order->getTop()) == Order::BELOW); + return {numberOfStates, numberOfStates}; + } - if (stateNumber != numberOfStates && order->contains(stateNumber)) { - auto resAllAdded = allSuccAdded(order, stateNumber); - if (!std::get<0>(resAllAdded)) { - return std::make_tuple(order, std::get<1>(resAllAdded), std::get<2>(resAllAdded)); + template <typename ValueType, typename ConstantType> + std::pair<uint_fast64_t, uint_fast64_t> OrderExtender<ValueType, ConstantType>::extendByForwardReasoning(std::shared_ptr<Order> order, uint_fast64_t currentState, std::vector<uint_fast64_t> const& successors, bool allowMerge) { + assert (successors.size() > 1); + assert (order->contains(currentState)); + assert (cyclic); + + std::vector<uint_fast64_t> statesSorted; + statesSorted.push_back(currentState); + bool pla = (usePLA.find(order) != usePLA.end() && usePLA.at(order)); + // Go over all states + bool oneUnknown = false; + bool unknown = false; + uint_fast64_t s1 = numberOfStates; + uint_fast64_t s2 = numberOfStates; + for (auto& state : successors) { + unknown = false; + bool added = false; + for (auto itr = statesSorted.begin(); itr != statesSorted.end(); ++itr) { + auto compareRes = order->compareFast(state, (*itr)); + if (pla && compareRes == Order::NodeComparison::UNKNOWN) { + compareRes = addStatesBasedOnMinMax(order, state, (*itr)); + } + if (compareRes == Order::NodeComparison::UNKNOWN) { + compareRes = order->compare(state, *itr); + } + if (compareRes == Order::NodeComparison::ABOVE || compareRes == Order::NodeComparison::SAME) { + if (!order->contains(state) && compareRes == Order::NodeComparison::ABOVE) { + order->add(state); + order->addStateToHandle(state); } + added = true; + // insert at current pointer (while keeping other values) + statesSorted.insert(itr, state); + break; + } else if (compareRes == Order::NodeComparison::UNKNOWN && !oneUnknown) { + // We miss state in the result. + s1 = state; + s2 = *itr; + oneUnknown = true; + added = true; + break; + } else if (compareRes == Order::NodeComparison::UNKNOWN && oneUnknown) { + unknown = true; + added = true; + break; } } + if (!(unknown && oneUnknown) && !added ) { + // State will be last in the list + statesSorted.push_back(state); + } + if (unknown && oneUnknown) { + break; + } + } + if (!unknown && oneUnknown) { + assert (statesSorted.size() == successors.size()); + s2 = numberOfStates; + } - if (stateNumber != numberOfStates && !order->contains(stateNumber)) { - auto successors = stateMap[stateNumber]; + if (s1 == numberOfStates) { + assert (statesSorted.size() == successors.size() + 1); + // all could be sorted, no need to do anything + } else if (s2 == numberOfStates) { + if (!order->contains(s1)) { + order->add(s1); + } - auto result = extendAllSuccAdded(order, stateNumber, successors); - if (std::get<1>(result) != numberOfStates) { - // So we don't know the relation between all successor states - return result; - } else { - assert (order->getNode(stateNumber) != nullptr); - if (!acyclic) { - order->statesToHandle->set(stateNumber); + if (statesSorted[0] == currentState) { + order->addRelation(s1, statesSorted[0], allowMerge); + auto res = order->compare(s1, statesSorted[0]); + assert ((order->compare(s1, statesSorted[0]) == Order::ABOVE) || (allowMerge && (order->compare(s1, statesSorted[statesSorted.size() - 1]) == Order::SAME))); + order->addRelation(s1, statesSorted[statesSorted.size() - 1], allowMerge); + assert ((order->compare(s1, statesSorted[statesSorted.size() - 1]) == Order::ABOVE) || (allowMerge && (order->compare(s1, statesSorted[statesSorted.size() - 1]) == Order::SAME))); + order->addStateToHandle(s1); + } else if (statesSorted[statesSorted.size() - 1] == currentState) { + order->addRelation(statesSorted[0], s1, allowMerge); + assert ((order->compare(s1, statesSorted[0]) == Order::BELOW) || (allowMerge && (order->compare(s1, statesSorted[statesSorted.size() - 1]) == Order::SAME))); + order->addRelation(statesSorted[statesSorted.size() - 1], s1, allowMerge); + assert ((order->compare(s1, statesSorted[statesSorted.size() - 1]) == Order::BELOW) || (allowMerge && (order->compare(s1, statesSorted[statesSorted.size() - 1]) == Order::SAME))); + order->addStateToHandle(s1); + } else { + bool continueSearch = true; + for (auto& entry : matrix.getRow(currentState)) { + if (entry.getColumn() == s1) { + if (entry.getValue().isConstant()) { + continueSearch = false; + } + } + } + if (continueSearch) { + for (auto& i : statesSorted) { + if (order->compare(i, s1) == Order::UNKNOWN) { + return {i, s1}; + } } - order->removeFirstStatesSorted(); } } - assert (stateNumber == numberOfStates || order->getNode(stateNumber) != nullptr); - assert (stateNumber == numberOfStates || order->contains(stateNumber)); + } else { + return {s1, s2}; + } + assert (order->contains(currentState) && order->compare(order->getNode(currentState), order->getBottom()) == Order::ABOVE && order->compare(order->getNode(currentState), order->getTop()) == Order::BELOW); + return {numberOfStates, numberOfStates}; + } + template<typename ValueType, typename ConstantType> + bool OrderExtender<ValueType, ConstantType>::extendByAssumption(std::shared_ptr<Order> order, uint_fast64_t currentState, uint_fast64_t stateSucc1, uint_fast64_t stateSucc2) { + bool usePLANow = usePLA.find(order) != usePLA.end() && usePLA[order]; + assert (order->compare(stateSucc1, stateSucc2) == Order::UNKNOWN); + auto assumptions = usePLANow ? assumptionMaker->createAndCheckAssumptions(stateSucc1, stateSucc2, order, region, minValues[order], maxValues[order]) : assumptionMaker->createAndCheckAssumptions(stateSucc1, stateSucc2, order, region); + if (assumptions.size() == 1 && assumptions.begin()->second == AssumptionStatus::VALID) { + handleAssumption(order, assumptions.begin()->first); + // Assumptions worked, we continue + return true; } - assert (order->getAddedStates()->getNumberOfSetBits() == numberOfStates); - order->setDoneBuilding(true); - return std::make_tuple(order, numberOfStates, numberOfStates); + return false; } - template <typename ValueType> - std::tuple<bool, uint_fast64_t, uint_fast64_t> OrderExtender<ValueType>::allSuccAdded(storm::analysis::Order *order, uint_fast64_t stateNumber) { - auto successors = stateMap[stateNumber]; - auto numberOfStates = successors->size(); - - if (successors->getNumberOfSetBits() == 1) { - auto succ = successors->getNextSetIndex(0); - return std::make_tuple(order->contains(succ), succ, succ); - } else if (successors->getNumberOfSetBits() > 2) { - for (auto const& i : *successors) { - for (auto j = successors->getNextSetIndex(i+1); j < numberOfStates; j = successors->getNextSetIndex(j+1)) { - if (order->compare(i,j) == Order::UNKNOWN) { - return std::make_tuple(false, i, j); - } + template <typename ValueType, typename ConstantType> + Order::NodeComparison OrderExtender<ValueType, ConstantType>::addStatesBasedOnMinMax(std::shared_ptr<Order> order, uint_fast64_t state1, uint_fast64_t state2) const { + assert (order->compareFast(state1, state2) == Order::UNKNOWN); + assert (minValues.find(order) != minValues.end()); + std::vector<ConstantType> const& mins = minValues.at(order); + std::vector<ConstantType> const& maxs = maxValues.at(order); + if (mins[state1] == maxs[state1] + && mins[state2] == maxs[state2] + && mins[state1] == mins[state2]) { + if (order->contains(state1)) { + if (order->contains(state2)) { + order->merge(state1, state2); + assert (!order->isInvalid()); + } else { + order->addToNode(state2, order->getNode(state1)); } } + return Order::SAME; + } else if (mins[state1] > maxs[state2]) { + // state 1 will always be larger than state2 + if (!order->contains(state1)) { + order->add(state1); + } + if (!order->contains(state2)) { + order->add(state2); + } + assert (order->compare(state1, state2) != Order::BELOW); + assert (order->compare(state1, state2) != Order::SAME); + order->addRelation(state1, state2); + + return Order::ABOVE; + } else if (mins[state2] > maxs[state1]) { + // state2 will always be larger than state1 + if (!order->contains(state1)) { + order->add(state1); + } + if (!order->contains(state2)) { + order->add(state2); + } + assert (order->compare(state2, state1) != Order::BELOW); + assert (order->compare(state2, state1) != Order::SAME); + order->addRelation(state2, state1); + return Order::BELOW; + } else { + // Couldn't add relation between state1 and state 2 based on min/max values; + return Order::UNKNOWN; + } + } + + template <typename ValueType, typename ConstantType> + void OrderExtender<ValueType, ConstantType>::initializeMinMaxValues(storage::ParameterRegion<ValueType> region) { + if (model != nullptr) { + // Use parameter lifting modelchecker to get initial min/max values for order creation + modelchecker::SparseDtmcParameterLiftingModelChecker<models::sparse::Dtmc<ValueType>, ConstantType> plaModelChecker; + std::unique_ptr<modelchecker::CheckResult> checkResult; + auto env = Environment(); + boost::optional<modelchecker::CheckTask<logic::Formula, ValueType>> checkTask; + if (this->formula->hasQuantitativeResult()) { + checkTask = modelchecker::CheckTask<logic::Formula, ValueType>(*formula); + } else { + storm::logic::OperatorInformation opInfo(boost::none, boost::none); + auto newFormula = std::make_shared<storm::logic::ProbabilityOperatorFormula>( + formula->asProbabilityOperatorFormula().getSubformula().asSharedPointer(), opInfo); + checkTask = modelchecker::CheckTask<logic::Formula, ValueType>(*newFormula); + } + STORM_LOG_THROW(plaModelChecker.canHandle(model, checkTask.get()), exceptions::NotSupportedException, "Cannot handle this formula"); + plaModelChecker.specify(env, model, checkTask.get(), false, false); + + modelchecker::ExplicitQuantitativeCheckResult<ConstantType> minCheck = plaModelChecker.check(env, region, solver::OptimizationDirection::Minimize)->template asExplicitQuantitativeCheckResult<ConstantType>(); + modelchecker::ExplicitQuantitativeCheckResult<ConstantType> maxCheck = plaModelChecker.check(env, region, solver::OptimizationDirection::Maximize)->template asExplicitQuantitativeCheckResult<ConstantType>(); + minValuesInit = minCheck.getValueVector(); + maxValuesInit = maxCheck.getValueVector(); + assert (minValuesInit->size() == numberOfStates); + assert (maxValuesInit->size() == numberOfStates); + } + } + + template <typename ValueType, typename ConstantType> + void OrderExtender<ValueType, ConstantType>::setMinMaxValues(std::shared_ptr<Order> order, std::vector<ConstantType>& minValues, std::vector<ConstantType>& maxValues) { + assert (minValues.size() == numberOfStates); + assert (maxValues.size() == numberOfStates); + usePLA[order] = true; + if (unknownStatesMap.find(order) != unknownStatesMap.end()) { + auto& unknownStates = unknownStatesMap[order]; + if (unknownStates.first != numberOfStates) { + continueExtending[order] = minValues[unknownStates.first] >= maxValues[unknownStates.second] || minValues[unknownStates.second] >= maxValues[unknownStates.first]; + } else { + continueExtending[order] = true; + } + } else { + continueExtending[order] = true; } - return std::make_tuple(true, numberOfStates, numberOfStates); + this->minValues[order] = std::move(minValues); + this->maxValues[order] = std::move(maxValues); + } + + template <typename ValueType, typename ConstantType> + void OrderExtender<ValueType, ConstantType>::setMinValues(std::shared_ptr<Order> order, std::vector<ConstantType>& minValues) { + assert (minValues.size() == numberOfStates); + auto& maxValues = this->maxValues[order]; + usePLA[order] = this->maxValues.find(order) != this->maxValues.end(); + if (maxValues.size() == 0) { + continueExtending[order] = false; + } else if (unknownStatesMap.find(order) != unknownStatesMap.end()) { + auto& unknownStates = unknownStatesMap[order]; + if (unknownStates.first != numberOfStates) { + continueExtending[order] = minValues[unknownStates.first] >= maxValues[unknownStates.second] || minValues[unknownStates.second] >= maxValues[unknownStates.first]; + } else { + continueExtending[order] = true; + } + } else { + continueExtending[order] = true; + } + this->minValues[order] = std::move(minValues); + } + + template <typename ValueType, typename ConstantType> + void OrderExtender<ValueType, ConstantType>::setMaxValues(std::shared_ptr<Order> order, std::vector<ConstantType>& maxValues) { + assert (maxValues.size() == numberOfStates); + usePLA[order] = this->minValues.find(order) != this->minValues.end(); + auto& minValues = this->minValues[order]; + if (minValues.size() == 0) { + continueExtending[order] = false; + } else if (unknownStatesMap.find(order) != unknownStatesMap.end()) { + auto& unknownStates = unknownStatesMap[order]; + if (unknownStates.first != numberOfStates) { + continueExtending[order] = + minValues[unknownStates.first] >= maxValues[unknownStates.second] || + minValues[unknownStates.second] >= maxValues[unknownStates.first]; + } else { + continueExtending[order] = true; + } + } else { + continueExtending[order] = true; + } + this->maxValues[order] = std::move(maxValues);//maxCheck->asExplicitQuantitativeCheckResult<ConstantType>().getValueVector(); + + } + template <typename ValueType, typename ConstantType> + void OrderExtender<ValueType, ConstantType>::setMinValuesInit(std::vector<ConstantType>& minValues) { + assert (minValues.size() == numberOfStates); + this->minValuesInit = std::move(minValues); + } + + template <typename ValueType, typename ConstantType> + void OrderExtender<ValueType, ConstantType>::setMaxValuesInit(std::vector<ConstantType>& maxValues) { + assert (maxValues.size() == numberOfStates); + this->maxValuesInit = std::move(maxValues);//maxCheck->asExplicitQuantitativeCheckResult<ConstantType>().getValueVector(); + } + + template <typename ValueType, typename ConstantType> + void OrderExtender<ValueType, ConstantType>::checkParOnStateMonRes(uint_fast64_t s, std::shared_ptr<Order> order, typename OrderExtender<ValueType, ConstantType>::VariableType param, std::shared_ptr<MonotonicityResult<VariableType>> monResult) { + auto mon = monotonicityChecker.checkLocalMonotonicity(order, s, param, region); + monResult->updateMonotonicityResult(param, mon); + } + + template<typename ValueType, typename ConstantType> + void OrderExtender<ValueType, ConstantType>::setUnknownStates(std::shared_ptr<Order> order, uint_fast64_t state1, uint_fast64_t state2) { + assert (state1 != numberOfStates && state2 != numberOfStates); + unknownStatesMap[order] = {state1, state2}; + } + + template<typename ValueType, typename ConstantType> + std::pair<uint_fast64_t, uint_fast64_t> OrderExtender<ValueType, ConstantType>::getUnknownStates(std::shared_ptr<Order> order) const { + if (unknownStatesMap.find(order) != unknownStatesMap.end()) { + return unknownStatesMap.at(order); + } + return {numberOfStates, numberOfStates}; + } + + template<typename ValueType, typename ConstantType> + void OrderExtender<ValueType, ConstantType>::setUnknownStates(std::shared_ptr<Order> orderOriginal, std::shared_ptr<Order> orderCopy) { + assert (unknownStatesMap.find(orderCopy) == unknownStatesMap.end()); + unknownStatesMap.insert({orderCopy,{unknownStatesMap[orderOriginal].first, unknownStatesMap[orderOriginal].second}}); + } + + template<typename ValueType, typename ConstantType> + void OrderExtender<ValueType, ConstantType>::copyMinMax(std::shared_ptr<Order> orderOriginal, + std::shared_ptr<Order> orderCopy) { + usePLA[orderCopy] = usePLA[orderOriginal]; + if (usePLA[orderCopy]) { + minValues[orderCopy] = minValues[orderOriginal]; + assert (maxValues.find(orderOriginal) != maxValues.end()); + maxValues[orderCopy] = maxValues[orderOriginal]; + } + continueExtending[orderCopy] = continueExtending[orderOriginal]; + } + + template<typename ValueType, typename ConstantType> + std::pair<uint_fast64_t, bool> OrderExtender<ValueType, ConstantType>::getNextState(std::shared_ptr<Order> order, uint_fast64_t currentState, bool done) { + if (done && currentState != numberOfStates) { + order->setDoneState(currentState); + } + if (cyclic && order->existsStateToHandle()) { + return order->getStateToHandle(); + } + if (currentState == numberOfStates) { + return order->getNextStateNumber(); + } + if (currentState != numberOfStates) { + return order->getNextStateNumber(); + } + return {numberOfStates, true}; + } + template<typename ValueType, typename ConstantType> + bool OrderExtender<ValueType, ConstantType>::isHope(std::shared_ptr<Order> order, storage::ParameterRegion<ValueType> region) { + assert (unknownStatesMap.find(order) != unknownStatesMap.end()); + assert (!order->getDoneBuilding()); + // First check if bounds helped us + bool yesThereIsHope = continueExtending[order]; + // TODO: maybe extend this + return yesThereIsHope; } - template class OrderExtender<storm::RationalFunction>; + template class OrderExtender<RationalFunction, double>; + template class OrderExtender<RationalFunction, RationalNumber>; } } diff --git a/src/storm-pars/analysis/OrderExtender.h b/src/storm-pars/analysis/OrderExtender.h index 30d3d4137..fe66211a1 100644 --- a/src/storm-pars/analysis/OrderExtender.h +++ b/src/storm-pars/analysis/OrderExtender.h @@ -1,81 +1,132 @@ -#ifndef STORM_LATTICEEXTENDER_H -#define STORM_LATTICEEXTENDER_H +#ifndef STORM_ORDEREXTENDER_H +#define STORM_ORDEREXTENDER_H -#include <storm/logic/Formula.h> -#include "storm/models/sparse/Dtmc.h" -#include "storm-pars/analysis/Order.h" +#include <boost/container/flat_set.hpp> #include "storm/api/storm.h" -#include "storm-pars/storage/ParameterRegion.h" -#include "storm/storage/StronglyConnectedComponentDecomposition.h" -#include "storm/storage/StronglyConnectedComponent.h" +#include "storm/logic/Formula.h" +#include "storm/models/sparse/Model.h" +#include "storm/storage/expressions/BinaryRelationExpression.h" +#include "storm/storage/expressions/VariableExpression.h" +#include "storm-pars/analysis/Order.h" +#include "storm-pars/analysis/MonotonicityResult.h" +#include "storm-pars/analysis/MonotonicityChecker.h" +#include "storm-pars/storage/ParameterRegion.h" +#include "AssumptionMaker.h" namespace storm { namespace analysis { - - - template<typename ValueType> + template<typename ValueType, typename ConstantType> class OrderExtender { public: + typedef typename utility::parametric::CoefficientType<ValueType>::type CoefficientType; + typedef typename utility::parametric::VariableType<ValueType>::type VariableType; + typedef typename MonotonicityResult<VariableType>::Monotonicity Monotonicity; + + /*! + * Constructs a new OrderExtender. + * + * @param model The model for which the order should be extended. + * @param formula The considered formula. + * @param region The Region of the model's parameters. + */ + OrderExtender(std::shared_ptr<models::sparse::Model<ValueType>> model, std::shared_ptr<logic::Formula const> formula); + /*! - * Constructs OrderExtender which can extend an order + * Constructs a new OrderExtender. * - * @param model The model for which the order should be extended. + * @param topStates The top states of the order. + * @param bottomStates The bottom states of the order. + * @param matrix The matrix of the considered model. */ - OrderExtender(std::shared_ptr<storm::models::sparse::Model<ValueType>> model); + OrderExtender(storm::storage::BitVector* topStates, storm::storage::BitVector* bottomStates, storm::storage::SparseMatrix<ValueType> matrix); /*! * Creates an order based on the given formula. * - * @param formulas The formulas based on which the order is created, only the first is used. + * @param monRes The monotonicity result so far. * @return A triple with a pointer to the order and two states of which the current place in the order * is unknown but needed. When the states have as number the number of states, no states are * unplaced but needed. */ - std::tuple<storm::analysis::Order*, uint_fast64_t, uint_fast64_t> toOrder(std::vector<std::shared_ptr<storm::logic::Formula const>> formulas); + std::tuple<std::shared_ptr<Order>, uint_fast64_t, uint_fast64_t> toOrder(storage::ParameterRegion<ValueType> region, std::shared_ptr<MonotonicityResult<VariableType>> monRes = nullptr); /*! - * Creates an order based on the given extremal values. + * Extends the order for the given region. * - * @return A triple with a pointer to the order and two states of which the current place in the order + * @param order pointer to the order. + * @param region The region on which the order needs to be extended. + * @return Two states of which the current place in the order * is unknown but needed. When the states have as number the number of states, no states are - * unplaced but needed. + * unplaced or needed. */ - std::tuple<storm::analysis::Order*, uint_fast64_t, uint_fast64_t> toOrder(std::vector<std::shared_ptr<storm::logic::Formula const>> formulas, std::vector<double> minValues, std::vector<double> maxValues); + std::tuple<std::shared_ptr<Order>, uint_fast64_t, uint_fast64_t> extendOrder(std::shared_ptr<Order> order, storm::storage::ParameterRegion<ValueType> region, std::shared_ptr<MonotonicityResult<VariableType>> monRes = nullptr, std::shared_ptr<expressions::BinaryRelationExpression> assumption = nullptr); + void setMinMaxValues(std::shared_ptr<Order> order, std::vector<ConstantType> &minValues, std::vector<ConstantType> &maxValues); + void setMinValues(std::shared_ptr<Order> order, std::vector<ConstantType> &minValues); + void setMaxValues(std::shared_ptr<Order> order,std::vector<ConstantType> &maxValues); + void setMinValuesInit(std::vector<ConstantType> &minValues); + void setMaxValuesInit(std::vector<ConstantType> &minValues); - /*! - * Extends the order based on the given assumption. - * - * @param order The order. - * @param assumption The assumption on states. - * @return A triple with a pointer to the order and two states of which the current place in the order - * is unknown but needed. When the states have as number the number of states, no states are - * unplaced but needed. - */ - std::tuple<storm::analysis::Order*, uint_fast64_t, uint_fast64_t> extendOrder(storm::analysis::Order* order, std::shared_ptr<storm::expressions::BinaryRelationExpression> assumption = nullptr); + void setUnknownStates(std::shared_ptr<Order> order, uint_fast64_t state1, uint_fast64_t state2); + std::pair<uint_fast64_t, uint_fast64_t> getUnknownStates(std::shared_ptr<Order> order) const; + void setUnknownStates(std::shared_ptr<Order> orderOriginal, std::shared_ptr<Order> orderCopy); + void copyMinMax(std::shared_ptr<Order> orderOriginal, std::shared_ptr<Order> orderCopy); + void initializeMinMaxValues(storage::ParameterRegion<ValueType> region); + void checkParOnStateMonRes(uint_fast64_t s, std::shared_ptr<Order> order, typename OrderExtender<ValueType, ConstantType>::VariableType param, std::shared_ptr<MonotonicityResult<VariableType>> monResult); + bool isHope(std::shared_ptr<Order> order, storage::ParameterRegion<ValueType>); private: - std::shared_ptr<storm::models::sparse::Model<ValueType>> model; - std::map<uint_fast64_t, storm::storage::BitVector*> stateMap; + Order::NodeComparison addStatesBasedOnMinMax(std::shared_ptr<Order> order, uint_fast64_t state1, uint_fast64_t state2) const; + std::tuple<std::shared_ptr<Order>, uint_fast64_t, uint_fast64_t> extendOrder(std::shared_ptr<Order> order, std::shared_ptr<MonotonicityResult<VariableType>> monRes, std::shared_ptr<expressions::BinaryRelationExpression> assumption = nullptr); + std::pair<uint_fast64_t, uint_fast64_t> extendNormal(std::shared_ptr<Order> order, uint_fast64_t currentState, std::vector<uint_fast64_t> const& successors, bool allowMerge); + std::pair<uint_fast64_t, uint_fast64_t> extendByBackwardReasoning(std::shared_ptr<Order> order, uint_fast64_t currentState, std::vector<uint_fast64_t> const& successors, bool allowMerge); + std::pair<uint_fast64_t, uint_fast64_t> extendByForwardReasoning(std::shared_ptr<Order> order, uint_fast64_t currentState, std::vector<uint_fast64_t> const& successors, bool allowMerge); + bool extendByAssumption(std::shared_ptr<Order> order, uint_fast64_t currentState, uint_fast64_t succState2, uint_fast64_t succState1); + + void handleOneSuccessor(std::shared_ptr<Order> order, uint_fast64_t currentState, uint_fast64_t successor); + void handleAssumption(std::shared_ptr<Order> order, std::shared_ptr<expressions::BinaryRelationExpression> assumption) const; + + std::pair<uint_fast64_t, bool> getNextState(std::shared_ptr<Order> order, uint_fast64_t stateNumber, bool done); + std::shared_ptr<Order> getBottomTopOrder(); + + + std::shared_ptr<Order> bottomTopOrder = nullptr; + + std::map<std::shared_ptr<Order>, std::vector<ConstantType>> minValues; + boost::optional<std::vector<ConstantType>> minValuesInit; + boost::optional<std::vector<ConstantType>> maxValuesInit; + std::map<std::shared_ptr<Order>, std::vector<ConstantType>> maxValues; + + storage::SparseMatrix<ValueType> matrix; + std::shared_ptr<models::sparse::Model<ValueType>> model; + + std::map<uint_fast64_t, std::vector<uint_fast64_t>> stateMap; + std::map<std::shared_ptr<Order>, std::pair<uint_fast64_t, uint_fast64_t>> unknownStatesMap; + + std::map<std::shared_ptr<Order>, bool> usePLA; + std::map<std::shared_ptr<Order>, bool> continueExtending; + bool cyclic; + + std::shared_ptr<logic::Formula const> formula; - bool acyclic; + storage::ParameterRegion<ValueType> region; - bool assumptionSeen; + uint_fast64_t numberOfStates; - storm::storage::StronglyConnectedComponentDecomposition<ValueType> sccs; + analysis::AssumptionMaker<ValueType, ConstantType>* assumptionMaker; - storm::storage::SparseMatrix<ValueType> matrix; - void handleAssumption(Order* order, std::shared_ptr<storm::expressions::BinaryRelationExpression> assumption); + boost::container::flat_set<uint_fast64_t> nonParametricStates; - std::tuple<Order*, uint_fast64_t, uint_fast64_t> extendAllSuccAdded(Order* order, uint_fast64_t const & stateNumber, storm::storage::BitVector* successors); + std::map<VariableType, std::vector<uint_fast64_t>> occuringStatesAtVariable; + std::vector<std::set<VariableType>> occuringVariablesAtState; + MonotonicityChecker<ValueType> monotonicityChecker; - std::tuple<bool, uint_fast64_t, uint_fast64_t> allSuccAdded(Order* order, uint_fast64_t stateNumber); }; } } diff --git a/src/storm-pars/api/analysis.h b/src/storm-pars/api/analysis.h new file mode 100644 index 000000000..396151e9c --- /dev/null +++ b/src/storm-pars/api/analysis.h @@ -0,0 +1,10 @@ +#pragma once + +#include "storm-pars/analysis/Order.h" +#include "storm-pars/analysis/OrderExtender.h" +#include "storm-pars/analysis/AssumptionChecker.h" +#include "storm-pars/analysis/AssumptionMaker.h" +#include "storm-pars/analysis/MonotonicityResult.h" +#include "storm-pars/analysis/MonotonicityHelper.h" +#include "storm-pars/analysis/LocalMonotonicityResult.h" + diff --git a/src/storm-pars/api/region.h b/src/storm-pars/api/region.h index b147b664f..3665790ab 100644 --- a/src/storm-pars/api/region.h +++ b/src/storm-pars/api/region.h @@ -15,6 +15,7 @@ #include "storm-pars/modelchecker/region/ValidatingSparseDtmcParameterLiftingModelChecker.h" #include "storm-pars/modelchecker/region/RegionResultHypothesis.h" #include "storm-pars/parser/ParameterRegionParser.h" +#include "storm-pars/parser/MonotonicityParser.h" #include "storm-pars/storage/ParameterRegion.h" #include "storm-pars/utility/parameterlifting.h" @@ -30,20 +31,31 @@ namespace storm { namespace api { - + struct MonotonicitySetting { + MonotonicitySetting(bool a = false, bool b = false, bool c = false) { useMonotonicity = a; useOnlyGlobalMonotonicity = b; useBoundsFromPLA = c;} + bool useMonotonicity; + bool useOnlyGlobalMonotonicity; + bool useBoundsFromPLA; + }; + template <typename ValueType> - std::vector<storm::storage::ParameterRegion<ValueType>> parseRegions(std::string const& inputString, std::set<typename storm::storage::ParameterRegion<ValueType>::VariableType> const& consideredVariables) { + std::vector<storm::storage::ParameterRegion<ValueType>> parseRegions(std::string const& inputString, std::set<typename storm::storage::ParameterRegion<ValueType>::VariableType> const& consideredVariables, boost::optional<int> splittingThreshold = boost::none) { // If the given input string looks like a file (containing a dot and there exists a file with that name), // we try to parse it as a file, otherwise we assume it's a region string. if (inputString.find(".") != std::string::npos && std::ifstream(inputString).good()) { - return storm::parser::ParameterRegionParser<ValueType>().parseMultipleRegionsFromFile(inputString, consideredVariables); + return storm::parser::ParameterRegionParser<ValueType>().parseMultipleRegionsFromFile(inputString, consideredVariables, splittingThreshold); } else { - return storm::parser::ParameterRegionParser<ValueType>().parseMultipleRegions(inputString, consideredVariables); + return storm::parser::ParameterRegionParser<ValueType>().parseMultipleRegions(inputString, consideredVariables, splittingThreshold); } } - + template <typename ValueType> - std::vector<storm::storage::ParameterRegion<ValueType>> parseRegions(std::string const& inputString, storm::models::ModelBase const& model) { + storm::storage::ParameterRegion<ValueType> createRegion(std::string const& inputString, std::set<typename storm::storage::ParameterRegion<ValueType>::VariableType> const& consideredVariables, boost::optional<int> splittingThreshold= boost::none) { + return storm::parser::ParameterRegionParser<ValueType>().createRegion(inputString, consideredVariables, splittingThreshold); + } + + template <typename ValueType> + std::vector<storm::storage::ParameterRegion<ValueType>> parseRegions(std::string const& inputString, storm::models::ModelBase const& model, boost::optional<int> splittingThreshold= boost::none) { std::set<typename storm::storage::ParameterRegion<ValueType>::VariableType> modelParameters; if (model.isSparseModel()) { auto const& sparseModel = dynamic_cast<storm::models::sparse::Model<ValueType> const&>(model); @@ -53,23 +65,37 @@ namespace storm { } else { STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Retrieving model parameters is not supported for the given model type."); } - return parseRegions<ValueType>(inputString, modelParameters); + return parseRegions<ValueType>(inputString, modelParameters, splittingThreshold); + } + + template <typename ValueType> + std::vector<storm::storage::ParameterRegion<ValueType>> createRegion(std::string const& inputString, storm::models::ModelBase const& model, boost::optional<int> splittingThreshold= boost::none) { + std::set<typename storm::storage::ParameterRegion<ValueType>::VariableType> modelParameters; + if (model.isSparseModel()) { + auto const& sparseModel = dynamic_cast<storm::models::sparse::Model<ValueType> const&>(model); + modelParameters = storm::models::sparse::getProbabilityParameters(sparseModel); + auto rewParameters = storm::models::sparse::getRewardParameters(sparseModel); + modelParameters.insert(rewParameters.begin(), rewParameters.end()); + } else { + STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Retrieving model parameters is not supported for the given model type."); + } + return std::vector<storm::storage::ParameterRegion<ValueType>>({createRegion<ValueType>(inputString, modelParameters, splittingThreshold)}); } template <typename ValueType> - storm::storage::ParameterRegion<ValueType> parseRegion(std::string const& inputString, std::set<typename storm::storage::ParameterRegion<ValueType>::VariableType> const& consideredVariables) { + storm::storage::ParameterRegion<ValueType> parseRegion(std::string const& inputString, std::set<typename storm::storage::ParameterRegion<ValueType>::VariableType> const& consideredVariables, boost::optional<int> splittingThreshold= boost::none) { // Handle the "empty region" case if (inputString == "" && consideredVariables.empty()) { return storm::storage::ParameterRegion<ValueType>(); } - auto res = parseRegions<ValueType>(inputString, consideredVariables); + auto res = parseRegions<ValueType>(inputString, consideredVariables, splittingThreshold); STORM_LOG_THROW(res.size() == 1, storm::exceptions::InvalidOperationException, "Parsed " << res.size() << " regions but exactly one was expected."); return res.front(); } template <typename ValueType> - storm::storage::ParameterRegion<ValueType> parseRegion(std::string const& inputString, storm::models::ModelBase const& model) { + storm::storage::ParameterRegion<ValueType> parseRegion(std::string const& inputString, storm::models::ModelBase const& model, boost::optional<int> splittingThreshold= boost::none) { // Handle the "empty region" case if (inputString == "" && !model.hasParameters()) { return storm::storage::ParameterRegion<ValueType>(); @@ -80,15 +106,27 @@ namespace storm { return res.front(); } + template <typename ValueType> + std::pair<std::set<typename storm::storage::ParameterRegion<ValueType>::VariableType>, std::set<typename storm::storage::ParameterRegion<ValueType>::VariableType>> parseMonotoneParameters(std::string const& fileName, std::shared_ptr<storm::models::sparse::Model<ValueType>> const& model) { + std::set<typename storm::storage::ParameterRegion<ValueType>::VariableType> modelParameters; + modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto rewParameters = storm::models::sparse::getRewardParameters(*model); + modelParameters.insert(rewParameters.begin(), rewParameters.end()); + return std::move(storm::parser::MonotonicityParser<typename storm::storage::ParameterRegion<ValueType>::VariableType>().parseMonotoneVariablesFromFile(fileName, modelParameters)); + } + template <typename ParametricType, typename ConstantType> - std::shared_ptr<storm::modelchecker::RegionModelChecker<ParametricType>> initializeParameterLiftingRegionModelChecker(Environment const& env, std::shared_ptr<storm::models::sparse::Model<ParametricType>> const& model, storm::modelchecker::CheckTask<storm::logic::Formula, ParametricType> const& task, bool generateSplitEstimates = false, bool allowModelSimplification = true) { + std::shared_ptr<storm::modelchecker::RegionModelChecker<ParametricType>> initializeParameterLiftingRegionModelChecker(Environment const& env, std::shared_ptr<storm::models::sparse::Model<ParametricType>> const& model, storm::modelchecker::CheckTask<storm::logic::Formula, ParametricType> const& task, bool generateSplitEstimates = false, bool allowModelSimplification = true, MonotonicitySetting monotonicitySetting = MonotonicitySetting(), boost::optional<std::pair<std::set<typename storm::storage::ParameterRegion<ParametricType>::VariableType>, std::set<typename storm::storage::ParameterRegion<ParametricType>::VariableType>>> monotoneParameters = boost::none) { STORM_LOG_WARN_COND(storm::utility::parameterlifting::validateParameterLiftingSound(*model, task.getFormula()), "Could not validate whether parameter lifting is applicable. Please validate manually..."); + STORM_LOG_WARN_COND(!(allowModelSimplification && monotonicitySetting.useMonotonicity), "Allowing model simplification when using monotonicity is not useful, as for monotonicity checking model simplification is done as preprocessing"); + STORM_LOG_WARN_COND(!(monotoneParameters && !monotonicitySetting.useMonotonicity), "Setting monotone parameters without setting monotonicity usage doesn't work"); std::shared_ptr<storm::models::sparse::Model<ParametricType>> consideredModel = model; // Treat continuous time models if (consideredModel->isOfType(storm::models::ModelType::Ctmc) || consideredModel->isOfType(storm::models::ModelType::MarkovAutomaton)) { + STORM_LOG_WARN_COND(!monotonicitySetting.useMonotonicity, "Usage of monotonicity not supported for this type of model, continuing without montonicity checking"); STORM_LOG_WARN("Parameter lifting not supported for continuous time models. Transforming continuous model to discrete model..."); std::vector<std::shared_ptr<storm::logic::Formula const>> taskFormulaAsVector { task.getFormula().asSharedPointer() }; consideredModel = storm::api::transformContinuousToDiscreteTimeSparseModel(consideredModel, taskFormulaAsVector).first; @@ -99,7 +137,14 @@ namespace storm { std::shared_ptr<storm::modelchecker::RegionModelChecker<ParametricType>> checker; if (consideredModel->isOfType(storm::models::ModelType::Dtmc)) { checker = std::make_shared<storm::modelchecker::SparseDtmcParameterLiftingModelChecker<storm::models::sparse::Dtmc<ParametricType>, ConstantType>>(); + checker->setUseMonotonicity(monotonicitySetting.useMonotonicity); + checker->setUseOnlyGlobal(monotonicitySetting.useOnlyGlobalMonotonicity); + checker->setUseBounds(monotonicitySetting.useBoundsFromPLA); + if (monotonicitySetting.useMonotonicity && monotoneParameters) { + checker->setMonotoneParameters(monotoneParameters.get()); + } } else if (consideredModel->isOfType(storm::models::ModelType::Mdp)) { + STORM_LOG_WARN_COND(!monotonicitySetting.useMonotonicity, "Usage of monotonicity not supported for this type of model, continuing without montonicity checking"); checker = std::make_shared<storm::modelchecker::SparseMdpParameterLiftingModelChecker<storm::models::sparse::Mdp<ParametricType>, ConstantType>>(); } else { STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Unable to perform parameterLifting on the provided model type."); @@ -134,21 +179,21 @@ namespace storm { } else { STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Unable to perform parameterLifting on the provided model type."); } - + checker->specify(env, consideredModel, task, generateSplitEstimates, allowModelSimplification); - return checker; } template <typename ValueType> - std::shared_ptr<storm::modelchecker::RegionModelChecker<ValueType>> initializeRegionModelChecker(Environment const& env, std::shared_ptr<storm::models::sparse::Model<ValueType>> const& model, storm::modelchecker::CheckTask<storm::logic::Formula, ValueType> const& task, storm::modelchecker::RegionCheckEngine engine) { + std::shared_ptr<storm::modelchecker::RegionModelChecker<ValueType>> initializeRegionModelChecker(Environment const& env, std::shared_ptr<storm::models::sparse::Model<ValueType>> const& model, storm::modelchecker::CheckTask<storm::logic::Formula, ValueType> const& task, storm::modelchecker::RegionCheckEngine engine, bool generateSplitEstimates = false, bool allowModelSimplification = true, MonotonicitySetting monotonicitySetting = MonotonicitySetting(), boost::optional<std::pair<std::set<typename storm::storage::ParameterRegion<ValueType>::VariableType>, std::set<typename storm::storage::ParameterRegion<ValueType>::VariableType>>> monotoneParameters = boost::none) { switch (engine) { + // TODO: now we always use regionsplitestimates case storm::modelchecker::RegionCheckEngine::ParameterLifting: - return initializeParameterLiftingRegionModelChecker<ValueType, double>(env, model, task); + return initializeParameterLiftingRegionModelChecker<ValueType, double>(env, model, task, generateSplitEstimates, allowModelSimplification, monotonicitySetting, monotoneParameters); case storm::modelchecker::RegionCheckEngine::ExactParameterLifting: - return initializeParameterLiftingRegionModelChecker<ValueType, storm::RationalNumber>(env, model, task); + return initializeParameterLiftingRegionModelChecker<ValueType, storm::RationalNumber>(env, model, task, generateSplitEstimates, allowModelSimplification, monotonicitySetting, monotoneParameters); case storm::modelchecker::RegionCheckEngine::ValidatingParameterLifting: - return initializeValidatingRegionModelChecker<ValueType, double, storm::RationalNumber>(env, model, task); + return initializeValidatingRegionModelChecker<ValueType, double, storm::RationalNumber>(env, model, task, generateSplitEstimates, allowModelSimplification); default: STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Unexpected region model checker type."); } @@ -180,14 +225,18 @@ namespace storm { * @param coverageThreshold if given, the refinement stops as soon as the fraction of the area of the subregions with inconclusive result is less then this threshold * @param refinementDepthThreshold if given, the refinement stops at the given depth. depth=0 means no refinement. * @param hypothesis if not 'unknown', it is only checked whether the hypothesis holds (and NOT the complementary result). + * @param allowModelSimplification + * @param useMonotonicity + * @param monThresh if given, determines at which depth to start using monotonicity */ template <typename ValueType> - std::unique_ptr<storm::modelchecker::RegionRefinementCheckResult<ValueType>> checkAndRefineRegionWithSparseEngine(std::shared_ptr<storm::models::sparse::Model<ValueType>> const& model, storm::modelchecker::CheckTask<storm::logic::Formula, ValueType> const& task, storm::storage::ParameterRegion<ValueType> const& region, storm::modelchecker::RegionCheckEngine engine, boost::optional<ValueType> const& coverageThreshold, boost::optional<uint64_t> const& refinementDepthThreshold = boost::none, storm::modelchecker::RegionResultHypothesis hypothesis = storm::modelchecker::RegionResultHypothesis::Unknown) { + std::unique_ptr<storm::modelchecker::RegionRefinementCheckResult<ValueType>> checkAndRefineRegionWithSparseEngine(std::shared_ptr<storm::models::sparse::Model<ValueType>> const& model, storm::modelchecker::CheckTask<storm::logic::Formula, ValueType> const& task, storm::storage::ParameterRegion<ValueType> const& region, storm::modelchecker::RegionCheckEngine engine, boost::optional<ValueType> const& coverageThreshold, boost::optional<uint64_t> const& refinementDepthThreshold = boost::none, storm::modelchecker::RegionResultHypothesis hypothesis = storm::modelchecker::RegionResultHypothesis::Unknown, bool allowModelSimplification = true, MonotonicitySetting monotonicitySetting = MonotonicitySetting(), uint64_t monThresh = 0) { Environment env; - auto regionChecker = initializeRegionModelChecker(env, model, task, engine); - return regionChecker->performRegionRefinement(env, region, coverageThreshold, refinementDepthThreshold, hypothesis); + auto regionChecker = initializeRegionModelChecker(env, model, task, engine, true, allowModelSimplification, monotonicitySetting); + return regionChecker->performRegionRefinement(env, region, coverageThreshold, refinementDepthThreshold, hypothesis, monThresh); } - + + // TODO: update documentation /*! * Finds the extremal value in the given region * @param engine The considered region checking engine @@ -196,11 +245,28 @@ namespace storm { * @param hypothesis if not 'unknown', it is only checked whether the hypothesis holds (and NOT the complementary result). */ template <typename ValueType> - std::pair<ValueType, typename storm::storage::ParameterRegion<ValueType>::Valuation> computeExtremalValue(std::shared_ptr<storm::models::sparse::Model<ValueType>> const& model, storm::modelchecker::CheckTask<storm::logic::Formula, ValueType> const& task, storm::storage::ParameterRegion<ValueType> const& region, storm::modelchecker::RegionCheckEngine engine, storm::solver::OptimizationDirection const& dir, boost::optional<ValueType> const& precision) { + std::pair<ValueType, typename storm::storage::ParameterRegion<ValueType>::Valuation> computeExtremalValue(std::shared_ptr<storm::models::sparse::Model<ValueType>> const& model, storm::modelchecker::CheckTask<storm::logic::Formula, ValueType> const& task, storm::storage::ParameterRegion<ValueType> const& region, storm::modelchecker::RegionCheckEngine engine, storm::solver::OptimizationDirection const& dir, boost::optional<ValueType> const& precision, MonotonicitySetting monotonicitySetting, bool generateSplitEstimates = false, boost::optional<std::pair<std::set<typename storm::storage::ParameterRegion<ValueType>::VariableType>, std::set<typename storm::storage::ParameterRegion<ValueType>::VariableType>>>& monotoneParameters = boost::none) { Environment env; - auto regionChecker = initializeRegionModelChecker(env, model, task, engine); + bool allowModelSimplification = !monotonicitySetting.useMonotonicity; + auto regionChecker = initializeRegionModelChecker(env, model, task, engine, generateSplitEstimates, allowModelSimplification, monotonicitySetting, monotoneParameters); return regionChecker->computeExtremalValue(env, region, dir, precision.is_initialized() ? precision.get() : storm::utility::zero<ValueType>()); } + + // TODO: update documentation + /*! + * Checks if a given extremal value is indeed the extremal value in the given region + * @param engine The considered region checking engine + * @param coverageThreshold if given, the refinement stops as soon as the fraction of the area of the subregions with inconclusive result is less then this threshold + * @param refinementDepthThreshold if given, the refinement stops at the given depth. depth=0 means no refinement. + * @param hypothesis if not 'unknown', it is only checked whether the hypothesis holds (and NOT the complementary result). + */ + template <typename ValueType> + bool checkExtremalValue(std::shared_ptr<storm::models::sparse::Model<ValueType>> const& model, storm::modelchecker::CheckTask<storm::logic::Formula, ValueType> const& task, storm::storage::ParameterRegion<ValueType> const& region, storm::modelchecker::RegionCheckEngine engine, storm::solver::OptimizationDirection const& dir, boost::optional<ValueType> const& precision, ValueType const& suggestion, MonotonicitySetting monotonicitySetting, bool generateSplitEstimates = false, boost::optional<std::pair<std::set<typename storm::storage::ParameterRegion<ValueType>::VariableType>, std::set<typename storm::storage::ParameterRegion<ValueType>::VariableType>>>& monotoneParameters = boost::none) { + Environment env; + bool allowModelSimplification = !monotonicitySetting.useMonotonicity; + auto regionChecker = initializeRegionModelChecker(env, model, task, engine, generateSplitEstimates, allowModelSimplification, monotonicitySetting, monotoneParameters); + return regionChecker->checkExtremalValue(env, region, dir, precision.is_initialized() ? precision.get() : storm::utility::zero<ValueType>(), suggestion); + } template <typename ValueType> void exportRegionCheckResultToFile(std::unique_ptr<storm::modelchecker::CheckResult> const& checkResult, std::string const& filename, bool onlyConclusiveResults = false) { @@ -211,6 +277,7 @@ namespace storm { std::ofstream filestream; storm::utility::openFile(filename, filestream); for (auto const& res : regionCheckResult->getRegionResults()) { + if (!onlyConclusiveResults || res.second == storm::modelchecker::RegionResult::AllViolated || res.second == storm::modelchecker::RegionResult::AllSat) { filestream << res.second << ": " << res.first << std::endl; } diff --git a/src/storm-pars/api/storm-pars.h b/src/storm-pars/api/storm-pars.h index a1a32ac05..bea608840 100644 --- a/src/storm-pars/api/storm-pars.h +++ b/src/storm-pars/api/storm-pars.h @@ -1,4 +1,5 @@ #pragma once #include "storm-pars/api/region.h" -#include "storm-pars/api/export.h" \ No newline at end of file +#include "storm-pars/api/export.h" +#include "storm-pars/api/analysis.h" diff --git a/src/storm-pars/modelchecker/region/RegionModelChecker.cpp b/src/storm-pars/modelchecker/region/RegionModelChecker.cpp index 23539650d..e97005830 100644 --- a/src/storm-pars/modelchecker/region/RegionModelChecker.cpp +++ b/src/storm-pars/modelchecker/region/RegionModelChecker.cpp @@ -1,21 +1,19 @@ #include <sstream> #include <queue> +#include "storm-pars/analysis/OrderExtender.cpp" #include "storm-pars/modelchecker/region/RegionModelChecker.h" #include "storm/adapters/RationalFunctionAdapter.h" - -#include "storm/utility/vector.h" #include "storm/models/sparse/StandardRewardModel.h" #include "storm/models/sparse/Dtmc.h" -#include "storm/models/sparse/Mdp.h" #include "storm/settings/SettingsManager.h" #include "storm/settings/modules/CoreSettings.h" + #include "storm/exceptions/NotImplementedException.h" #include "storm/exceptions/NotSupportedException.h" -#include "storm/exceptions/InvalidStateException.h" #include "storm/exceptions/InvalidArgumentException.h" @@ -50,7 +48,7 @@ namespace storm { } template <typename ParametricType> - std::unique_ptr<storm::modelchecker::RegionRefinementCheckResult<ParametricType>> RegionModelChecker<ParametricType>::performRegionRefinement(Environment const& env, storm::storage::ParameterRegion<ParametricType> const& region, boost::optional<ParametricType> const& coverageThreshold, boost::optional<uint64_t> depthThreshold, RegionResultHypothesis const& hypothesis) { + std::unique_ptr<storm::modelchecker::RegionRefinementCheckResult<ParametricType>> RegionModelChecker<ParametricType>::performRegionRefinement(Environment const& env, storm::storage::ParameterRegion<ParametricType> const& region, boost::optional<ParametricType> const& coverageThreshold, boost::optional<uint64_t> depthThreshold, RegionResultHypothesis const& hypothesis, uint64_t monThresh) { STORM_LOG_INFO("Applying refinement on region: " << region.toString(true) << " ."); auto thresholdAsCoefficient = coverageThreshold ? storm::utility::convertNumber<CoefficientType>(coverageThreshold.get()) : storm::utility::zero<CoefficientType>(); @@ -58,16 +56,18 @@ namespace storm { auto fractionOfUndiscoveredArea = storm::utility::one<CoefficientType>(); auto fractionOfAllSatArea = storm::utility::zero<CoefficientType>(); auto fractionOfAllViolatedArea = storm::utility::zero<CoefficientType>(); + numberOfRegionsKnownThroughMonotonicity = 0; // The resulting (sub-)regions std::vector<std::pair<storm::storage::ParameterRegion<ParametricType>, RegionResult>> result; // FIFO queues storing the data for the regions that we still need to process. std::queue<std::pair<storm::storage::ParameterRegion<ParametricType>, RegionResult>> unprocessedRegions; + std::queue<uint64_t> refinementDepths; unprocessedRegions.emplace(region, RegionResult::Unknown); refinementDepths.push(0); - + uint_fast64_t numOfAnalyzedRegions = 0; CoefficientType displayedProgress = storm::utility::zero<CoefficientType>(); if (storm::settings::getModule<storm::settings::modules::CoreSettings>().isShowStatisticsSet()) { @@ -84,13 +84,17 @@ namespace storm { displayedProgress = storm::utility::zero<CoefficientType>(); } - while (fractionOfUndiscoveredArea > thresholdAsCoefficient && !unprocessedRegions.empty()) { + // NORMAL WHILE LOOP + uint64_t currentDepth = refinementDepths.front(); + while ((!useMonotonicity || currentDepth < monThresh) && fractionOfUndiscoveredArea > thresholdAsCoefficient && !unprocessedRegions.empty()) { assert(unprocessedRegions.size() == refinementDepths.size()); - uint64_t currentDepth = refinementDepths.front(); STORM_LOG_INFO("Analyzing region #" << numOfAnalyzedRegions << " (Refinement depth " << currentDepth << "; " << storm::utility::convertNumber<double>(fractionOfUndiscoveredArea) * 100 << "% still unknown)"); auto& currentRegion = unprocessedRegions.front().first; auto& res = unprocessedRegions.front().second; + std::shared_ptr<storm::analysis::Order> order; + std::shared_ptr<storm::analysis::LocalMonotonicityResult<VariableType>> localMonotonicityResult; res = analyzeRegion(env, currentRegion, hypothesis, res, false); + switch (res) { case RegionResult::AllSat: fractionOfUndiscoveredArea -= currentRegion.area() / areaOfParameterSpace; @@ -106,11 +110,167 @@ namespace storm { // Split the region as long as the desired refinement depth is not reached. if (!depthThreshold || currentDepth < depthThreshold.get()) { std::vector<storm::storage::ParameterRegion<ParametricType>> newRegions; + RegionResult initResForNewRegions = (res == RegionResult::CenterSat) ? RegionResult::ExistsSat : + ((res == RegionResult::CenterViolated) ? RegionResult::ExistsViolated : + RegionResult::Unknown); + currentRegion.split(currentRegion.getCenterPoint(), newRegions); + + bool first = true; + for (auto& newRegion : newRegions) { + unprocessedRegions.emplace(std::move(newRegion), initResForNewRegions); + refinementDepths.push(currentDepth + 1); + } + + } else { + // If the region is not further refined, it is still added to the result + result.push_back(std::move(unprocessedRegions.front())); + } + break; + } + ++numOfAnalyzedRegions; + unprocessedRegions.pop(); + refinementDepths.pop(); + if (storm::settings::getModule<storm::settings::modules::CoreSettings>().isShowStatisticsSet()) { + while (displayedProgress < storm::utility::one<CoefficientType>() - fractionOfUndiscoveredArea) { + STORM_PRINT_AND_LOG("#"); + displayedProgress += storm::utility::convertNumber<CoefficientType>(0.01); + } + } + currentDepth = refinementDepths.front(); + } + + // FIFO queues for the order and local monotonicity results + std::queue<std::shared_ptr<storm::analysis::Order>> orders; + std::queue<std::shared_ptr<storm::analysis::LocalMonotonicityResult<VariableType>>> localMonotonicityResults; + std::shared_ptr<storm::analysis::Order> order; + std::shared_ptr<storm::analysis::LocalMonotonicityResult<VariableType>> localMonotonicityResult; + if (useMonotonicity && fractionOfUndiscoveredArea > thresholdAsCoefficient && !unprocessedRegions.empty()) { + storm::utility::Stopwatch monWatch(true); + + orders.emplace(extendOrder(env, nullptr, region)); + assert (orders.front() != nullptr); + auto monRes = std::shared_ptr< storm::analysis::LocalMonotonicityResult<VariableType>>(new storm::analysis::LocalMonotonicityResult<VariableType>(orders.front()->getNumberOfStates())); + extendLocalMonotonicityResult(region, orders.front(), monRes); + localMonotonicityResults.emplace(monRes); + order = orders.front(); + localMonotonicityResult = localMonotonicityResults.front(); + + if (!order->getDoneBuilding()) { + // we need to use copies for both order and local mon res + while(unprocessedRegions.size() > orders.size()){ + orders.emplace(order->copy()); + localMonotonicityResults.emplace(localMonotonicityResult->copy()); + } + } else if (!localMonotonicityResult->isDone()) { + // the order will not change anymore + while(unprocessedRegions.size() > orders.size()) { + orders.emplace(order); + localMonotonicityResults.emplace(localMonotonicityResult->copy()); + } + } else { + // both will not change anymore + while (unprocessedRegions.size() > orders.size()) { + orders.emplace(order); + localMonotonicityResults.emplace(localMonotonicityResult); + } + } + monWatch.stop(); + STORM_PRINT(std::endl << "Time for orderBuilding and monRes initialization: " << monWatch << "." << std::endl << std::endl); + } + bool useSameOrder = useMonotonicity && order->getDoneBuilding(); + bool useSameLocalMonotonicityResult = useSameOrder && localMonotonicityResult->isDone(); + + // USEMON WHILE LOOP + while (useMonotonicity && fractionOfUndiscoveredArea > thresholdAsCoefficient && !unprocessedRegions.empty()) { + assert ((useSameLocalMonotonicityResult && localMonotonicityResults.size() == 1)|| unprocessedRegions.size() == localMonotonicityResults.size()); + assert ((useSameOrder && orders.size() == 1) || unprocessedRegions.size() == orders.size()); + assert(unprocessedRegions.size() == refinementDepths.size()); + currentDepth = refinementDepths.front(); + STORM_LOG_INFO("Analyzing region #" << numOfAnalyzedRegions << " (Refinement depth " << currentDepth << "; " << storm::utility::convertNumber<double>(fractionOfUndiscoveredArea) * 100 << "% still unknown)"); + auto& currentRegion = unprocessedRegions.front().first; + auto& res = unprocessedRegions.front().second; + + assert(!orders.empty()); + if (!useSameOrder) { + order = orders.front(); + if (!order->getDoneBuilding()) { + extendOrder(env, order, currentRegion); + } + } + if (!useSameLocalMonotonicityResult) { + localMonotonicityResult = localMonotonicityResults.front(); + if (!localMonotonicityResult->isDone()) { + extendLocalMonotonicityResult(currentRegion, order, localMonotonicityResult); + } + } + + res = analyzeRegion(env, currentRegion, hypothesis, res, false, order, localMonotonicityResult); + + switch (res) { + case RegionResult::AllSat: + fractionOfUndiscoveredArea -= currentRegion.area() / areaOfParameterSpace; + fractionOfAllSatArea += currentRegion.area() / areaOfParameterSpace; + STORM_LOG_INFO("Region " << unprocessedRegions.front() << " is AllSat"); + result.push_back(std::move(unprocessedRegions.front())); + break; + case RegionResult::AllViolated: + fractionOfUndiscoveredArea -= currentRegion.area() / areaOfParameterSpace; + fractionOfAllViolatedArea += currentRegion.area() / areaOfParameterSpace; + STORM_LOG_INFO("Region " << unprocessedRegions.front() << " is AllViolated"); + + result.push_back(std::move(unprocessedRegions.front())); + break; + default: + // Split the region as long as the desired refinement depth is not reached. + if (!depthThreshold || currentDepth < depthThreshold.get()) { + std::vector<storm::storage::ParameterRegion<ParametricType>> newRegions; RegionResult initResForNewRegions = (res == RegionResult::CenterSat) ? RegionResult::ExistsSat : - ((res == RegionResult::CenterViolated) ? RegionResult::ExistsViolated : - RegionResult::Unknown); + ((res == RegionResult::CenterViolated) ? RegionResult::ExistsViolated : + RegionResult::Unknown); + + std::vector<storm::storage::ParameterRegion<ParametricType>> newKnownRegions; + // Only split in (non)monotone vars + splitSmart(currentRegion, newRegions, order, *(localMonotonicityResult->getGlobalMonotonicityResult()), false); + assert (newRegions.size() != 0); + + initResForNewRegions = (res == RegionResult::CenterSat) ? RegionResult::ExistsSat : + ((res == RegionResult::CenterViolated) ? RegionResult::ExistsViolated : + RegionResult::Unknown); + bool first = true; for (auto& newRegion : newRegions) { + if (!useSameOrder) { + if (first) { + orders.emplace(order); + localMonotonicityResults.emplace(localMonotonicityResult); + first = false; + } else { + if (!order->getDoneBuilding()) { + // we need to use copies for both order and local mon res + orders.emplace(order->copy()); + localMonotonicityResults.emplace(localMonotonicityResult->copy()); + } else if (!localMonotonicityResult->isDone()) { + // the order will not change anymore + orders.emplace(order); + localMonotonicityResults.emplace(localMonotonicityResult->copy()); + } else { + // both will not change anymore + orders.emplace(order); + localMonotonicityResults.emplace(localMonotonicityResult); + } + } + } else if (!useSameLocalMonotonicityResult) { + if (first) { + localMonotonicityResults.emplace(localMonotonicityResult); + first = false; + } else { + if (!localMonotonicityResult->isDone()) { + localMonotonicityResults.emplace(localMonotonicityResult->copy()); + } else { + localMonotonicityResults.emplace(localMonotonicityResult); + } + } + } unprocessedRegions.emplace(std::move(newRegion), initResForNewRegions); refinementDepths.push(currentDepth + 1); } @@ -120,9 +280,17 @@ namespace storm { } break; } + ++numOfAnalyzedRegions; unprocessedRegions.pop(); refinementDepths.pop(); + if (!useSameOrder) { + orders.pop(); + } + if (!useSameLocalMonotonicityResult) { + localMonotonicityResults.pop(); + } + if (storm::settings::getModule<storm::settings::modules::CoreSettings>().isShowStatisticsSet()) { while (displayedProgress < storm::utility::one<CoefficientType>() - fractionOfUndiscoveredArea) { STORM_PRINT_AND_LOG("#"); @@ -146,18 +314,35 @@ namespace storm { STORM_PRINT_AND_LOG("Region Refinement Statistics:" << std::endl); STORM_PRINT_AND_LOG(" Analyzed a total of " << numOfAnalyzedRegions << " regions." << std::endl); + + if (useMonotonicity) { + STORM_PRINT_AND_LOG(" " << numberOfRegionsKnownThroughMonotonicity << " regions where discovered with help of monotonicity." << std::endl); + + } } auto regionCopyForResult = region; return std::make_unique<storm::modelchecker::RegionRefinementCheckResult<ParametricType>>(std::move(result), std::move(regionCopyForResult)); } + + template <typename ParametricType> + void RegionModelChecker<ParametricType>::extendLocalMonotonicityResult(storm::storage::ParameterRegion<ParametricType> const& region, std::shared_ptr<storm::analysis::Order> order, std::shared_ptr<storm::analysis::LocalMonotonicityResult<VariableType>> localMonotonicityResult){ + STORM_LOG_WARN("Initializing local Monotonicity Results not implemented for RegionModelChecker."); + } + template <typename ParametricType> std::pair<ParametricType, typename storm::storage::ParameterRegion<ParametricType>::Valuation> RegionModelChecker<ParametricType>::computeExtremalValue(Environment const& env, storm::storage::ParameterRegion<ParametricType> const& region, storm::solver::OptimizationDirection const& dir, ParametricType const& precision) { STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Computing extremal values is not supported for this region model checker."); return std::pair<ParametricType, typename storm::storage::ParameterRegion<ParametricType>::Valuation>(); } + template <typename ParametricType> + bool RegionModelChecker<ParametricType>::checkExtremalValue(Environment const& env, storm::storage::ParameterRegion<ParametricType> const& region, storm::solver::OptimizationDirection const& dir, ParametricType const& precision, ParametricType const& valueToCheck) { + STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Checking extremal values is not supported for this region model checker."); + return false; + } + template <typename ParametricType> bool RegionModelChecker<ParametricType>::isRegionSplitEstimateSupported() const { @@ -170,7 +355,63 @@ namespace storm { return std::map<typename RegionModelChecker<ParametricType>::VariableType, double>(); } - + template <typename ParametricType> + std::shared_ptr<storm::analysis::Order> RegionModelChecker<ParametricType>::extendOrder(Environment const& env, std::shared_ptr<storm::analysis::Order> order, storm::storage::ParameterRegion<ParametricType> region) { + STORM_LOG_WARN("Extending order for RegionModelChecker not implemented"); + // Does nothing + return order; + } + + template <typename ParametricType> + void RegionModelChecker<ParametricType>::setConstantEntries(std::shared_ptr<storm::analysis::LocalMonotonicityResult<VariableType>> localMonotonicityResult) { + STORM_LOG_WARN("Setting constant entries fo local monotonicity result not implemented"); + // Does nothing + } + + template <typename ParametricType> + bool RegionModelChecker<ParametricType>::isUseMonotonicitySet() const{ + return useMonotonicity; + } + + template <typename ParametricType> + bool RegionModelChecker<ParametricType>::isUseBoundsSet() { + return useBounds; + } + + template <typename ParametricType> + bool RegionModelChecker<ParametricType>::isOnlyGlobalSet() { + return useOnlyGlobal; + } + + template <typename ParametricType> + void RegionModelChecker<ParametricType>::setUseMonotonicity(bool monotonicity) { + this->useMonotonicity = monotonicity; + } + + template <typename ParametricType> + void RegionModelChecker<ParametricType>::setUseBounds(bool bounds) { + assert (!bounds || useMonotonicity); + this->useBounds = bounds; + } + + template <typename ParametricType> + void RegionModelChecker<ParametricType>::setUseOnlyGlobal(bool global) { + assert (!global || useMonotonicity); + this->useOnlyGlobal = global; + } + + template <typename ParametricType> + void RegionModelChecker<ParametricType>::splitSmart(storm::storage::ParameterRegion<ParametricType> & currentRegion, std::vector<storm::storage::ParameterRegion<ParametricType>> ®ionVector, std::shared_ptr<storm::analysis::Order> order, storm::analysis::MonotonicityResult<VariableType> & monRes, bool splitForExtremum) const { + STORM_LOG_WARN("Smart splitting for this model checker not implemented"); + currentRegion.split(currentRegion.getCenterPoint(), regionVector); + } + + template<typename ParametricType> + void RegionModelChecker<ParametricType>::setMonotoneParameters(std::pair<std::set<typename storm::storage::ParameterRegion<ParametricType>::VariableType>, std::set<typename storm::storage::ParameterRegion<ParametricType>::VariableType>> monotoneParameters) { + monotoneIncrParameters = std::move(monotoneParameters.first); + monotoneDecrParameters = std::move(monotoneParameters.second); + } + #ifdef STORM_HAVE_CARL template class RegionModelChecker<storm::RationalFunction>; #endif diff --git a/src/storm-pars/modelchecker/region/RegionModelChecker.h b/src/storm-pars/modelchecker/region/RegionModelChecker.h index df3d670f9..d4b8c8325 100644 --- a/src/storm-pars/modelchecker/region/RegionModelChecker.h +++ b/src/storm-pars/modelchecker/region/RegionModelChecker.h @@ -2,6 +2,9 @@ #include <memory> +#include "storm-pars/analysis/Order.h" +#include "storm-pars/analysis/OrderExtender.h" +#include "storm-pars/analysis/LocalMonotonicityResult.h" #include "storm-pars/modelchecker/results/RegionCheckResult.h" #include "storm-pars/modelchecker/results/RegionRefinementCheckResult.h" #include "storm-pars/modelchecker/region/RegionResult.h" @@ -37,7 +40,7 @@ namespace storm { * @param initialResult encodes what is already known about this region * @param sampleVerticesOfRegion enables sampling of the vertices of the region in cases where AllSat/AllViolated could not be shown. */ - virtual RegionResult analyzeRegion(Environment const& env, storm::storage::ParameterRegion<ParametricType> const& region, RegionResultHypothesis const& hypothesis = RegionResultHypothesis::Unknown, RegionResult const& initialResult = RegionResult::Unknown, bool sampleVerticesOfRegion = false) = 0; + virtual RegionResult analyzeRegion(Environment const& env, storm::storage::ParameterRegion<ParametricType> const& region, RegionResultHypothesis const& hypothesis = RegionResultHypothesis::Unknown, RegionResult const& initialResult = RegionResult::Unknown, bool sampleVerticesOfRegion = false, std::shared_ptr<storm::analysis::Order> reachabilityOrder = nullptr, std::shared_ptr<storm::analysis::LocalMonotonicityResult<VariableType>> localMonotonicityResult = nullptr) = 0; /*! * Analyzes the given regions. @@ -55,17 +58,19 @@ namespace storm { * @param coverageThreshold if given, the refinement stops as soon as the fraction of the area of the subregions with inconclusive result is less then this threshold * @param depthThreshold if given, the refinement stops at the given depth. depth=0 means no refinement. * @param hypothesis if not 'unknown', it is only checked whether the hypothesis holds within the given region. - * + * @param monThresh if given, determines at which depth to start using monotonicity */ - std::unique_ptr<storm::modelchecker::RegionRefinementCheckResult<ParametricType>> performRegionRefinement(Environment const& env, storm::storage::ParameterRegion<ParametricType> const& region, boost::optional<ParametricType> const& coverageThreshold, boost::optional<uint64_t> depthThreshold = boost::none, RegionResultHypothesis const& hypothesis = RegionResultHypothesis::Unknown); - + std::unique_ptr<storm::modelchecker::RegionRefinementCheckResult<ParametricType>> performRegionRefinement(Environment const& env, storm::storage::ParameterRegion<ParametricType> const& region, boost::optional<ParametricType> const& coverageThreshold, boost::optional<uint64_t> depthThreshold = boost::none, RegionResultHypothesis const& hypothesis = RegionResultHypothesis::Unknown, uint64_t monThresh = 0); + + // TODO: documentation /*! * Finds the extremal value within the given region and with the given precision. * The returned value v corresponds to the value at the returned valuation. * The actual maximum (minimum) lies in the interval [v, v+precision] ([v-precision, v]) */ virtual std::pair<ParametricType, typename storm::storage::ParameterRegion<ParametricType>::Valuation> computeExtremalValue(Environment const& env, storm::storage::ParameterRegion<ParametricType> const& region, storm::solver::OptimizationDirection const& dir, ParametricType const& precision); - + virtual bool checkExtremalValue(Environment const& env, storm::storage::ParameterRegion<ParametricType> const& region, storm::solver::OptimizationDirection const& dir, ParametricType const& precision, ParametricType const& valueToCheck); + /*! * Returns true if region split estimation (a) was enabled when model and check task have been specified and (b) is supported by this region model checker. */ @@ -76,7 +81,36 @@ namespace storm { * If a parameter is assigned a high value, we should prefer splitting with respect to this parameter. */ virtual std::map<VariableType, double> getRegionSplitEstimate() const; - + + virtual std::shared_ptr<storm::analysis::Order> extendOrder(Environment const& env, std::shared_ptr<storm::analysis::Order> order, storm::storage::ParameterRegion<ParametricType> region); + + virtual void setConstantEntries(std::shared_ptr<storm::analysis::LocalMonotonicityResult<VariableType>> localMonotonicityResult); + + bool isUseMonotonicitySet() const; + bool isUseBoundsSet(); + bool isOnlyGlobalSet(); + + void setUseMonotonicity(bool monotonicity = true); + void setUseBounds(bool bounds = true); + void setUseOnlyGlobal(bool global = true); + + void setMonotoneParameters(std::pair<std::set<typename storm::storage::ParameterRegion<ParametricType>::VariableType>, std::set<typename storm::storage::ParameterRegion<ParametricType>::VariableType>> monotoneParameters); + + private: + bool useMonotonicity = false; + bool useOnlyGlobal = false; + bool useBounds = false; + + protected: + + uint_fast64_t numberOfRegionsKnownThroughMonotonicity; + boost::optional<std::set<typename storm::storage::ParameterRegion<ParametricType>::VariableType>> monotoneIncrParameters; + boost::optional<std::set<typename storm::storage::ParameterRegion<ParametricType>::VariableType>> monotoneDecrParameters; + + virtual void extendLocalMonotonicityResult(storm::storage::ParameterRegion<ParametricType> const& region, std::shared_ptr<storm::analysis::Order> order, std::shared_ptr<storm::analysis::LocalMonotonicityResult<VariableType>> localMonotonicityResult); + + virtual void splitSmart(storm::storage::ParameterRegion<ParametricType> & region, std::vector<storm::storage::ParameterRegion<ParametricType>> ®ionVector, std::shared_ptr<storm::analysis::Order> order, storm::analysis::MonotonicityResult<VariableType> & monRes, bool splitForExtremum) const; + }; } //namespace modelchecker diff --git a/src/storm-pars/modelchecker/region/SparseDtmcParameterLiftingModelChecker.cpp b/src/storm-pars/modelchecker/region/SparseDtmcParameterLiftingModelChecker.cpp index 1b938f717..ca9523cd5 100644 --- a/src/storm-pars/modelchecker/region/SparseDtmcParameterLiftingModelChecker.cpp +++ b/src/storm-pars/modelchecker/region/SparseDtmcParameterLiftingModelChecker.cpp @@ -38,7 +38,7 @@ namespace storm { } template <typename SparseModelType, typename ConstantType> - bool SparseDtmcParameterLiftingModelChecker<SparseModelType, ConstantType>::canHandle(std::shared_ptr<storm::models::ModelBase> parametricModel, CheckTask<storm::logic::Formula, typename SparseModelType::ValueType> const& checkTask) const { + bool SparseDtmcParameterLiftingModelChecker<SparseModelType, ConstantType>::canHandle(std::shared_ptr<storm::models::ModelBase> parametricModel, CheckTask<storm::logic::Formula, ValueType> const& checkTask) const { bool result = parametricModel->isOfType(storm::models::ModelType::Dtmc); result &= parametricModel->isSparseModel(); result &= parametricModel->supportsParameters(); @@ -47,21 +47,25 @@ namespace storm { result &= checkTask.getFormula().isInFragment(storm::logic::reachability().setRewardOperatorsAllowed(true).setReachabilityRewardFormulasAllowed(true).setBoundedUntilFormulasAllowed(true).setCumulativeRewardFormulasAllowed(true).setStepBoundedCumulativeRewardFormulasAllowed(true).setTimeBoundedCumulativeRewardFormulasAllowed(true).setTimeBoundedUntilFormulasAllowed(true).setStepBoundedUntilFormulasAllowed(true).setTimeBoundedUntilFormulasAllowed(true)); return result; } - + template <typename SparseModelType, typename ConstantType> - void SparseDtmcParameterLiftingModelChecker<SparseModelType, ConstantType>::specify(Environment const& env, std::shared_ptr<storm::models::ModelBase> parametricModel, CheckTask<storm::logic::Formula, typename SparseModelType::ValueType> const& checkTask, bool generateRegionSplitEstimates, bool allowModelSimplification) { + void SparseDtmcParameterLiftingModelChecker<SparseModelType, ConstantType>::specify(Environment const& env, std::shared_ptr<storm::models::ModelBase> parametricModel, CheckTask<storm::logic::Formula, ValueType> const& checkTask, bool generateRegionSplitEstimates, bool allowModelSimplification) { auto dtmc = parametricModel->template as<SparseModelType>(); + monotonicityChecker = std::make_unique<storm::analysis::MonotonicityChecker<ValueType>>(dtmc->getTransitionMatrix()); specify_internal(env, dtmc, checkTask, generateRegionSplitEstimates, !allowModelSimplification); + if (checkTask.isBoundSet()) { + thresholdTask = storm::utility::convertNumber<ConstantType>(checkTask.getBoundThreshold()); + } } - + template <typename SparseModelType, typename ConstantType> - void SparseDtmcParameterLiftingModelChecker<SparseModelType, ConstantType>::specify_internal(Environment const& env, std::shared_ptr<SparseModelType> parametricModel, CheckTask<storm::logic::Formula, typename SparseModelType::ValueType> const& checkTask, bool generateRegionSplitEstimates, bool skipModelSimplification) { + void SparseDtmcParameterLiftingModelChecker<SparseModelType, ConstantType>::specify_internal(Environment const& env, std::shared_ptr<SparseModelType> parametricModel, CheckTask<storm::logic::Formula, ValueType> const& checkTask, bool generateRegionSplitEstimates, bool skipModelSimplification) { STORM_LOG_ASSERT(this->canHandle(parametricModel, checkTask), "specified model and formula can not be handled by this."); - + reset(); - + regionSplitEstimationsEnabled = generateRegionSplitEstimates; - + if (skipModelSimplification) { this->parametricModel = parametricModel; this->specifyFormula(env, checkTask); @@ -74,11 +78,11 @@ namespace storm { this->specifyFormula(env, checkTask.substituteFormula(*simplifier.getSimplifiedFormula())); } } - - + + + template <typename SparseModelType, typename ConstantType> void SparseDtmcParameterLiftingModelChecker<SparseModelType, ConstantType>::specifyBoundedUntilFormula(Environment const& env, CheckTask<storm::logic::BoundedUntilFormula, ConstantType> const& checkTask) { - // get the step bound STORM_LOG_THROW(!checkTask.getFormula().hasLowerBound(), storm::exceptions::NotSupportedException, "Lower step bounds are not supported."); STORM_LOG_THROW(checkTask.getFormula().hasUpperBound(), storm::exceptions::NotSupportedException, "Expected a bounded until formula with an upper bound."); @@ -108,9 +112,8 @@ namespace storm { // if there are maybestates, create the parameterLifter if (!maybeStates.empty()) { // Create the vector of one-step probabilities to go to target states. - std::vector<typename SparseModelType::ValueType> b = this->parametricModel->getTransitionMatrix().getConstrainedRowSumVector(storm::storage::BitVector(this->parametricModel->getTransitionMatrix().getRowCount(), true), psiStates); - - parameterLifter = std::make_unique<storm::transformer::ParameterLifter<typename SparseModelType::ValueType, ConstantType>>(this->parametricModel->getTransitionMatrix(), b, maybeStates, maybeStates); + std::vector<ValueType> b = this->parametricModel->getTransitionMatrix().getConstrainedRowSumVector(storm::storage::BitVector(this->parametricModel->getTransitionMatrix().getRowCount(), true), psiStates); + parameterLifter = std::make_unique<storm::transformer::ParameterLifter<ValueType, ConstantType>>(this->parametricModel->getTransitionMatrix(), b, maybeStates, maybeStates, false, RegionModelChecker<ValueType>::isUseMonotonicitySet()); } // We know some bounds for the results so set them @@ -118,11 +121,14 @@ namespace storm { upperResultBound = storm::utility::one<ConstantType>(); // No requirements for bounded formulas solverFactory->setRequirementsChecked(true); + + // For monotonicity checking + std::pair<storm::storage::BitVector, storm::storage::BitVector> statesWithProbability01 = storm::utility::graph::performProb01(this->parametricModel->getBackwardTransitions(), phiStates, psiStates); + this->orderExtender = storm::analysis::OrderExtender<ValueType,ConstantType>(&statesWithProbability01.second, &statesWithProbability01.first, this->parametricModel->getTransitionMatrix()); } template <typename SparseModelType, typename ConstantType> void SparseDtmcParameterLiftingModelChecker<SparseModelType, ConstantType>::specifyUntilFormula(Environment const& env, CheckTask<storm::logic::UntilFormula, ConstantType> const& checkTask) { - // get the results for the subformulas storm::modelchecker::SparsePropositionalModelChecker<SparseModelType> propositionalChecker(*this->parametricModel); STORM_LOG_THROW(propositionalChecker.canHandle(checkTask.getFormula().getLeftSubformula()) && propositionalChecker.canHandle(checkTask.getFormula().getRightSubformula()), storm::exceptions::NotSupportedException, "Parameter lifting with non-propositional subformulas is not supported"); @@ -140,9 +146,8 @@ namespace storm { // if there are maybestates, create the parameterLifter if (!maybeStates.empty()) { // Create the vector of one-step probabilities to go to target states. - std::vector<typename SparseModelType::ValueType> b = this->parametricModel->getTransitionMatrix().getConstrainedRowSumVector(storm::storage::BitVector(this->parametricModel->getTransitionMatrix().getRowCount(), true), statesWithProbability01.second); - - parameterLifter = std::make_unique<storm::transformer::ParameterLifter<typename SparseModelType::ValueType, ConstantType>>(this->parametricModel->getTransitionMatrix(), b, maybeStates, maybeStates, regionSplitEstimationsEnabled); + std::vector<ValueType> b = this->parametricModel->getTransitionMatrix().getConstrainedRowSumVector(storm::storage::BitVector(this->parametricModel->getTransitionMatrix().getRowCount(), true), statesWithProbability01.second); + parameterLifter = std::make_unique<storm::transformer::ParameterLifter<ValueType, ConstantType>>(this->parametricModel->getTransitionMatrix(), b, maybeStates, maybeStates, regionSplitEstimationsEnabled, RegionModelChecker<ValueType>::isUseMonotonicitySet()); } // We know some bounds for the results so set them @@ -154,16 +159,16 @@ namespace storm { req.clearBounds(); STORM_LOG_THROW(!req.hasEnabledCriticalRequirement(), storm::exceptions::UncheckedRequirementException, "Solver requirements " + req.getEnabledRequirementsAsString() + " not checked."); solverFactory->setRequirementsChecked(true); + + this->orderExtender = storm::analysis::OrderExtender<ValueType,ConstantType>(&statesWithProbability01.second, &statesWithProbability01.first, this->parametricModel->getTransitionMatrix()); } template <typename SparseModelType, typename ConstantType> void SparseDtmcParameterLiftingModelChecker<SparseModelType, ConstantType>::specifyReachabilityRewardFormula(Environment const& env, CheckTask<storm::logic::EventuallyFormula, ConstantType> const& checkTask) { - // get the results for the subformula storm::modelchecker::SparsePropositionalModelChecker<SparseModelType> propositionalChecker(*this->parametricModel); STORM_LOG_THROW(propositionalChecker.canHandle(checkTask.getFormula().getSubformula()), storm::exceptions::NotSupportedException, "Parameter lifting with non-propositional subformulas is not supported"); storm::storage::BitVector targetStates = std::move(propositionalChecker.check(checkTask.getFormula().getSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector()); - // get the maybeStates storm::storage::BitVector infinityStates = storm::utility::graph::performProb1(this->parametricModel->getBackwardTransitions(), storm::storage::BitVector(this->parametricModel->getNumberOfStates(), true), targetStates); infinityStates.complement(); @@ -180,9 +185,9 @@ namespace storm { typename SparseModelType::RewardModelType const& rewardModel = checkTask.isRewardModelSet() ? this->parametricModel->getRewardModel(checkTask.getRewardModel()) : this->parametricModel->getUniqueRewardModel(); - std::vector<typename SparseModelType::ValueType> b = rewardModel.getTotalRewardVector(this->parametricModel->getTransitionMatrix()); + std::vector<ValueType> b = rewardModel.getTotalRewardVector(this->parametricModel->getTransitionMatrix()); - parameterLifter = std::make_unique<storm::transformer::ParameterLifter<typename SparseModelType::ValueType, ConstantType>>(this->parametricModel->getTransitionMatrix(), b, maybeStates, maybeStates, regionSplitEstimationsEnabled); + parameterLifter = std::make_unique<storm::transformer::ParameterLifter<ValueType, ConstantType>>(this->parametricModel->getTransitionMatrix(), b, maybeStates, maybeStates, regionSplitEstimationsEnabled); } // We only know a lower bound for the result @@ -203,7 +208,6 @@ namespace storm { template <typename SparseModelType, typename ConstantType> void SparseDtmcParameterLiftingModelChecker<SparseModelType, ConstantType>::specifyCumulativeRewardFormula(Environment const& env, CheckTask<storm::logic::CumulativeRewardFormula, ConstantType> const& checkTask) { - // Obtain the stepBound stepBound = checkTask.getFormula().getBound().evaluateAsInt(); if (checkTask.getFormula().isBoundStrict()) { @@ -219,10 +223,9 @@ namespace storm { // Create the reward vector STORM_LOG_THROW((checkTask.isRewardModelSet() && this->parametricModel->hasRewardModel(checkTask.getRewardModel())) || (!checkTask.isRewardModelSet() && this->parametricModel->hasUniqueRewardModel()), storm::exceptions::InvalidPropertyException, "The reward model specified by the CheckTask is not available in the given model."); typename SparseModelType::RewardModelType const& rewardModel = checkTask.isRewardModelSet() ? this->parametricModel->getRewardModel(checkTask.getRewardModel()) : this->parametricModel->getUniqueRewardModel(); - std::vector<typename SparseModelType::ValueType> b = rewardModel.getTotalRewardVector(this->parametricModel->getTransitionMatrix()); - - parameterLifter = std::make_unique<storm::transformer::ParameterLifter<typename SparseModelType::ValueType, ConstantType>>(this->parametricModel->getTransitionMatrix(), b, maybeStates, maybeStates); + std::vector<ValueType> b = rewardModel.getTotalRewardVector(this->parametricModel->getTransitionMatrix()); + parameterLifter = std::make_unique<storm::transformer::ParameterLifter<ValueType, ConstantType>>(this->parametricModel->getTransitionMatrix(), b, maybeStates, maybeStates); // We only know a lower bound for the result lowerResultBound = storm::utility::zero<ConstantType>(); @@ -231,25 +234,44 @@ namespace storm { solverFactory->setRequirementsChecked(true); } + template <typename SparseModelType, typename ConstantType> + storm::modelchecker::SparseInstantiationModelChecker<SparseModelType, ConstantType>& SparseDtmcParameterLiftingModelChecker<SparseModelType, ConstantType>::getInstantiationCheckerSAT() { + if (!instantiationCheckerSAT) { + instantiationCheckerSAT = std::make_unique<storm::modelchecker::SparseDtmcInstantiationModelChecker<SparseModelType, ConstantType>>(*this->parametricModel); + instantiationCheckerSAT->specifyFormula(this->currentCheckTask->template convertValueType<ValueType>()); + instantiationCheckerSAT->setInstantiationsAreGraphPreserving(true); + } + return *instantiationCheckerSAT; + } + + template <typename SparseModelType, typename ConstantType> + storm::modelchecker::SparseInstantiationModelChecker<SparseModelType, ConstantType>& SparseDtmcParameterLiftingModelChecker<SparseModelType, ConstantType>::getInstantiationCheckerVIO() { + if (!instantiationCheckerVIO) { + instantiationCheckerVIO = std::make_unique<storm::modelchecker::SparseDtmcInstantiationModelChecker<SparseModelType, ConstantType>>(*this->parametricModel); + instantiationCheckerVIO->specifyFormula(this->currentCheckTask->template convertValueType<ValueType>()); + instantiationCheckerVIO->setInstantiationsAreGraphPreserving(true); + } + return *instantiationCheckerVIO; + } + template <typename SparseModelType, typename ConstantType> storm::modelchecker::SparseInstantiationModelChecker<SparseModelType, ConstantType>& SparseDtmcParameterLiftingModelChecker<SparseModelType, ConstantType>::getInstantiationChecker() { if (!instantiationChecker) { instantiationChecker = std::make_unique<storm::modelchecker::SparseDtmcInstantiationModelChecker<SparseModelType, ConstantType>>(*this->parametricModel); - instantiationChecker->specifyFormula(this->currentCheckTask->template convertValueType<typename SparseModelType::ValueType>()); + instantiationChecker->specifyFormula(this->currentCheckTask->template convertValueType<ValueType>()); instantiationChecker->setInstantiationsAreGraphPreserving(true); } return *instantiationChecker; } template <typename SparseModelType, typename ConstantType> - std::unique_ptr<CheckResult> SparseDtmcParameterLiftingModelChecker<SparseModelType, ConstantType>::computeQuantitativeValues(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters) { - + std::unique_ptr<CheckResult> SparseDtmcParameterLiftingModelChecker<SparseModelType, ConstantType>::computeQuantitativeValues(Environment const& env, storm::storage::ParameterRegion<ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters, std::shared_ptr<storm::analysis::LocalMonotonicityResult<VariableType>> localMonotonicityResult) { + if (maybeStates.empty()) { return std::make_unique<storm::modelchecker::ExplicitQuantitativeCheckResult<ConstantType>>(resultsForNonMaybeStates); } - parameterLifter->specifyRegion(region, dirForParameters); - + if (stepBound) { assert(*stepBound > 0); x = std::vector<ConstantType>(maybeStates.getNumberOfSetBits(), storm::utility::zero<ConstantType>()); @@ -267,37 +289,96 @@ namespace storm { std::vector<ConstantType> oneStepProbs; oneStepProbs.reserve(parameterLifter->getMatrix().getRowCount()); for (uint64_t row = 0; row < parameterLifter->getMatrix().getRowCount(); ++row) { - oneStepProbs.push_back(storm::utility::one<ConstantType>() - parameterLifter->getMatrix().getRowSum(row)); + oneStepProbs.push_back( + storm::utility::one<ConstantType>() - parameterLifter->getMatrix().getRowSum(row)); } if (dirForParameters == storm::OptimizationDirection::Minimize) { - storm::modelchecker::helper::DsMpiMdpUpperRewardBoundsComputer<ConstantType> dsmpi(parameterLifter->getMatrix(), parameterLifter->getVector(), oneStepProbs); + storm::modelchecker::helper::DsMpiMdpUpperRewardBoundsComputer<ConstantType> dsmpi( + parameterLifter->getMatrix(), parameterLifter->getVector(), oneStepProbs); solver->setUpperBounds(dsmpi.computeUpperBounds()); } else { - storm::modelchecker::helper::BaierUpperRewardBoundsComputer<ConstantType> baier(parameterLifter->getMatrix(), parameterLifter->getVector(), oneStepProbs); + storm::modelchecker::helper::BaierUpperRewardBoundsComputer<ConstantType> baier( + parameterLifter->getMatrix(), parameterLifter->getVector(), oneStepProbs); solver->setUpperBound(baier.computeUpperBound()); } } solver->setTrackScheduler(true); - if (storm::solver::minimize(dirForParameters) && minSchedChoices) solver->setInitialScheduler(std::move(minSchedChoices.get())); - if (storm::solver::maximize(dirForParameters) && maxSchedChoices) solver->setInitialScheduler(std::move(maxSchedChoices.get())); + + if (localMonotonicityResult != nullptr && !this->isOnlyGlobalSet()) { + storm::storage::BitVector fixedStates(parameterLifter->getRowGroupCount(), false); + + bool useMinimize = storm::solver::minimize(dirForParameters); + if (useMinimize && !minSchedChoices) { + minSchedChoices = std::vector<uint_fast64_t>(parameterLifter->getRowGroupCount(), 0); + } + if (!useMinimize && !maxSchedChoices) { + maxSchedChoices = std::vector<uint_fast64_t>(parameterLifter->getRowGroupCount(), 0); + } + + // TODO: this only works since we decided to keep all columns + auto const & occuringVariables = parameterLifter->getOccurringVariablesAtState(); + for (auto state = 0; state < parameterLifter->getRowGroupCount(); ++state) { + auto oldStateNumber = parameterLifter->getOriginalStateNumber(state); + auto& variables = occuringVariables.at(oldStateNumber); + // point at which we start with rows for this state + + STORM_LOG_THROW(variables.size() <= 1, storm::exceptions::NotImplementedException, "Using localMonRes not yet implemented for states with 2 or more variables, please run without --use-monotonicity"); + + bool allMonotone = true; + for (auto var : variables) { + auto monotonicity = localMonotonicityResult->getMonotonicity(oldStateNumber, var); + + bool ignoreUpperBound = monotonicity == Monotonicity::Constant || (useMinimize && monotonicity == Monotonicity::Incr) || (!useMinimize && monotonicity == Monotonicity::Decr); + bool ignoreLowerBound = !ignoreUpperBound && ((useMinimize && monotonicity == Monotonicity::Decr) || (!useMinimize && monotonicity == Monotonicity::Incr)); + allMonotone &= (ignoreUpperBound || ignoreLowerBound); + if (ignoreLowerBound) { + if (useMinimize) { + minSchedChoices.get()[state] = 1; + } else { + maxSchedChoices.get()[state] = 1; + } + } else if (ignoreUpperBound) { + if (useMinimize) { + minSchedChoices.get()[state] = 0; + } else { + maxSchedChoices.get()[state] = 0; + } + } + } + if (allMonotone) { + fixedStates.set(state); + } + } + solver->setFixedStates(std::move(fixedStates)); + } + + if (storm::solver::minimize(dirForParameters) && minSchedChoices) + solver->setInitialScheduler(std::move(minSchedChoices.get())); + if (storm::solver::maximize(dirForParameters) && maxSchedChoices) + solver->setInitialScheduler(std::move(maxSchedChoices.get())); if (this->currentCheckTask->isBoundSet() && solver->hasInitialScheduler()) { // If we reach this point, we know that after applying the hint, the x-values can only become larger (if we maximize) or smaller (if we minimize). std::unique_ptr<storm::solver::TerminationCondition<ConstantType>> termCond; - storm::storage::BitVector relevantStatesInSubsystem = this->currentCheckTask->isOnlyInitialStatesRelevantSet() ? this->parametricModel->getInitialStates() % maybeStates : storm::storage::BitVector(maybeStates.getNumberOfSetBits(), true); + storm::storage::BitVector relevantStatesInSubsystem = this->currentCheckTask->isOnlyInitialStatesRelevantSet() + ? this->parametricModel->getInitialStates() % + maybeStates : storm::storage::BitVector( + maybeStates.getNumberOfSetBits(), true); if (storm::solver::minimize(dirForParameters)) { // Terminate if the value for ALL relevant states is already below the threshold - termCond = std::make_unique<storm::solver::TerminateIfFilteredExtremumBelowThreshold<ConstantType>> (relevantStatesInSubsystem, true, this->currentCheckTask->getBoundThreshold(), false); + termCond = std::make_unique<storm::solver::TerminateIfFilteredExtremumBelowThreshold<ConstantType>>( + relevantStatesInSubsystem, true, this->currentCheckTask->getBoundThreshold(), false); } else { // Terminate if the value for ALL relevant states is already above the threshold - termCond = std::make_unique<storm::solver::TerminateIfFilteredExtremumExceedsThreshold<ConstantType>> (relevantStatesInSubsystem, true, this->currentCheckTask->getBoundThreshold(), true); + termCond = std::make_unique<storm::solver::TerminateIfFilteredExtremumExceedsThreshold<ConstantType>>( + relevantStatesInSubsystem, true, this->currentCheckTask->getBoundThreshold(), true); } solver->setTerminationCondition(std::move(termCond)); } - + // Invoke the solver x.resize(maybeStates.getNumberOfSetBits(), storm::utility::zero<ConstantType>()); solver->solveEquations(env, dirForParameters, x, parameterLifter->getVector()); - if(storm::solver::minimize(dirForParameters)) { + if (storm::solver::minimize(dirForParameters)) { minSchedChoices = solver->getSchedulerChoices(); } else { maxSchedChoices = solver->getSchedulerChoices(); @@ -306,6 +387,7 @@ namespace storm { computeRegionSplitEstimates(x, solver->getSchedulerChoices(), region, dirForParameters); } } + // Get the result for the complete model (including maybestates) std::vector<ConstantType> result = resultsForNonMaybeStates; @@ -318,8 +400,8 @@ namespace storm { } template <typename SparseModelType, typename ConstantType> - void SparseDtmcParameterLiftingModelChecker<SparseModelType, ConstantType>::computeRegionSplitEstimates(std::vector<ConstantType> const& quantitativeResult, std::vector<uint_fast64_t> const& schedulerChoices, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters) { - std::map<typename RegionModelChecker<typename SparseModelType::ValueType>::VariableType, double> deltaLower, deltaUpper; + void SparseDtmcParameterLiftingModelChecker<SparseModelType, ConstantType>::computeRegionSplitEstimates(std::vector<ConstantType> const& quantitativeResult, std::vector<uint_fast64_t> const& schedulerChoices, storm::storage::ParameterRegion<ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters) { + std::map<VariableType, double> deltaLower, deltaUpper; for (auto const& p : region.getVariables()) { deltaLower.insert(std::make_pair(p, 0.0)); deltaUpper.insert(std::make_pair(p, 0.0)); @@ -327,7 +409,11 @@ namespace storm { auto const& choiceValuations = parameterLifter->getRowLabels(); auto const& matrix = parameterLifter->getMatrix(); auto const& vector = parameterLifter->getVector(); - + + auto i = 0; + for (auto & x : vector) { + ++i; + } std::vector<ConstantType> stateResults; for (uint64_t state = 0; state < schedulerChoices.size(); ++state) { uint64_t rowOffset = matrix.getRowGroupIndices()[state]; @@ -338,12 +424,13 @@ namespace storm { for (uint64_t row = rowOffset; row < matrix.getRowGroupIndices()[state + 1]; ++row) { stateResults.push_back(matrix.multiplyRowWithVector(row, quantitativeResult) + vector[row]); } + // Do this twice, once for upperbound once for lowerbound bool checkUpperParameters = false; do { auto const& consideredParameters = checkUpperParameters ? optimalChoiceVal.getUpperParameters() : optimalChoiceVal.getLowerParameters(); for (auto const& p : consideredParameters) { // Find the 'best' choice that assigns the parameter to the other bound - ConstantType bestValue; + ConstantType bestValue = 0; bool foundBestValue = false; for (uint64_t choice = 0; choice < stateResults.size(); ++choice) { if (choice != optimalChoice) { @@ -357,26 +444,36 @@ namespace storm { } } } - if (checkUpperParameters) { - deltaLower[p] += storm::utility::convertNumber<double>(bestValue); - } else { - deltaUpper[p] += storm::utility::convertNumber<double>(bestValue); + auto optimal = storm::utility::convertNumber<double>(stateResults[optimalChoice]); + auto diff = optimal - storm::utility::convertNumber<double>(bestValue); + if (foundBestValue) { + if (checkUpperParameters) { + deltaLower[p] += std::abs(diff); + } else { + deltaUpper[p] += std::abs(diff); + } } - } checkUpperParameters = !checkUpperParameters; } while (checkUpperParameters); } regionSplitEstimates.clear(); + useRegionSplitEstimates = false; for (auto const& p : region.getVariables()) { - if (deltaLower[p] > deltaUpper[p]) { - regionSplitEstimates.insert(std::make_pair(p, deltaUpper[p])); - } else { - regionSplitEstimates.insert(std::make_pair(p, deltaLower[p])); + if (this->possibleMonotoneParameters.find(p) != this->possibleMonotoneParameters.end()) { + if (deltaLower[p] > deltaUpper[p] && deltaUpper[p] >= 0.0001) { + regionSplitEstimates.insert(std::make_pair(p, deltaUpper[p])); + useRegionSplitEstimates = true; + } else if (deltaLower[p] <= deltaUpper[p] && deltaLower[p] >= 0.0001) { + { + regionSplitEstimates.insert(std::make_pair(p, deltaLower[p])); + useRegionSplitEstimates = true; + } + } } } - + // large regionsplitestimate implies that parameter p occurs as p and 1-p at least once } template <typename SparseModelType, typename ConstantType> @@ -437,9 +534,145 @@ namespace storm { return regionSplitEstimates; } - + template<typename SparseModelType, typename ConstantType> + std::shared_ptr<storm::analysis::Order> SparseDtmcParameterLiftingModelChecker<SparseModelType, ConstantType>::extendOrder(Environment const& env, std::shared_ptr<storm::analysis::Order> order, storm::storage::ParameterRegion<ValueType> region) { + if (this->orderExtender) { + auto res = this->orderExtender->extendOrder(order, region); + order = std::get<0>(res); + if (std::get<1>(res) != order->getNumberOfStates()) { + this->orderExtender.get().setUnknownStates(order, std::get<1>(res), std::get<2>(res)); + } + } else { + STORM_LOG_WARN("Extending order for RegionModelChecker not implemented"); + } + return order; + } + + template <typename SparseModelType, typename ConstantType> + void SparseDtmcParameterLiftingModelChecker<SparseModelType, ConstantType>::extendLocalMonotonicityResult(storm::storage::ParameterRegion<ValueType> const& region, std::shared_ptr<storm::analysis::Order> order, std::shared_ptr<storm::analysis::LocalMonotonicityResult<VariableType>> localMonotonicityResult) { + if (this->monotoneIncrParameters && !localMonotonicityResult->isFixedParametersSet()) { + for (auto & var : this->monotoneIncrParameters.get()) { + localMonotonicityResult->setMonotoneIncreasing(var); + } + for (auto & var : this->monotoneDecrParameters.get()) { + localMonotonicityResult->setMonotoneDecreasing(var); + } + } + auto state = order->getNextDoneState(-1); + auto const variablesAtState = parameterLifter->getOccurringVariablesAtState(); + while (state != order->getNumberOfStates()) { + if (localMonotonicityResult->getMonotonicity(state) == nullptr) { + auto variables = variablesAtState[state]; + if (variables.size() == 0 || order->isBottomState(state) || order->isTopState(state)) { + localMonotonicityResult->setConstant(state); + } else { + for (auto const &var : variables) { + auto monotonicity = localMonotonicityResult->getMonotonicity(state, var); + if (monotonicity == Monotonicity::Unknown || monotonicity == Monotonicity::Not) { + monotonicity = monotonicityChecker->checkLocalMonotonicity(order, state, var, region); + if (monotonicity == Monotonicity::Unknown || monotonicity == Monotonicity::Not) { + // TODO: Skip for now? + } else { + localMonotonicityResult->setMonotonicity(state, var, monotonicity); + } + } + } + } + } else { + // Do nothing, we already checked this one + } + state = order->getNextDoneState(state); + } + auto const statesAtVariable = parameterLifter->getOccuringStatesAtVariable(); + bool allDone = true; + for (auto const & entry : statesAtVariable) { + auto states = entry.second; + auto var = entry.first; + bool done = true; + for (auto const& state : states) { + done &= order->contains(state) && localMonotonicityResult->getMonotonicity(state, var) != Monotonicity::Unknown; + auto check = localMonotonicityResult->getMonotonicity(state, var); + if (!done) { + break; + } + } + + allDone &= done; + if (done) { + localMonotonicityResult->getGlobalMonotonicityResult()->setDoneForVar(var); + } + } + if (allDone) { + localMonotonicityResult->setDone(); + while (order->existsNextState()) { + // Simply add the states we couldn't add sofar between =) and =( as we could find local monotonicity for all parametric states + order->add(order->getNextStateNumber().second); + } + assert (order->getDoneBuilding()); + } + } + + template <typename SparseModelType, typename ConstantType> + void SparseDtmcParameterLiftingModelChecker<SparseModelType, ConstantType>::splitSmart ( + storm::storage::ParameterRegion<ValueType> ®ion, std::vector<storm::storage::ParameterRegion<ValueType>> ®ionVector, + std::shared_ptr<storm::analysis::Order> order, storm::analysis::MonotonicityResult<VariableType> & monRes, bool splitForExtremum) const { + assert (regionVector.size() == 0); + + std::multimap<double, VariableType> sortedOnValues; + std::set<VariableType> consideredVariables; + if (splitForExtremum) { + if (regionSplitEstimationsEnabled && useRegionSplitEstimates) { + STORM_LOG_INFO("Splitting based on region split estimates"); + for (auto &entry : regionSplitEstimates) { + assert (!this->isUseMonotonicitySet() || (!monRes.isMonotone(entry.first) && this->possibleMonotoneParameters.find(entry.first) != this->possibleMonotoneParameters.end())); +// sortedOnValues.insert({-(entry.second * storm::utility::convertNumber<double>(region.getDifference(entry.first))* storm::utility::convertNumber<double>(region.getDifference(entry.first))), entry.first}); + sortedOnValues.insert({-(entry.second ), entry.first}); + } + + for (auto itr = sortedOnValues.begin(); itr != sortedOnValues.end() && consideredVariables.size() < region.getSplitThreshold(); ++itr) { + consideredVariables.insert(itr->second); + } + assert (consideredVariables.size() > 0); + region.split(region.getCenterPoint(), regionVector, std::move(consideredVariables)); + } else { + STORM_LOG_INFO("Splitting based on sorting"); + + auto &sortedOnDifference = region.getVariablesSorted(); + for (auto itr = sortedOnDifference.begin(); itr != sortedOnDifference.end() && consideredVariables.size() < region.getSplitThreshold(); ++itr) { + if (!this->isUseMonotonicitySet() || !monRes.isMonotone(itr->second)) { + consideredVariables.insert(itr->second); + } + } + assert (consideredVariables.size() > 0 || (monRes.isDone() && monRes.isAllMonotonicity())); + region.split(region.getCenterPoint(), regionVector, std::move(consideredVariables)); + } + } else { + // split for pla + if (regionSplitEstimationsEnabled && useRegionSplitEstimates) { + STORM_LOG_INFO("Splitting based on region split estimates"); + ConstantType diff = this->lastValue - (this->currentCheckTask->getFormula().asOperatorFormula().template getThresholdAs<ConstantType>()); + for (auto &entry : regionSplitEstimates) { + if ((!this->isUseMonotonicitySet() || !monRes.isMonotone(entry.first)) && entry.second > diff) { + sortedOnValues.insert({-(entry.second * storm::utility::convertNumber<double>(region.getDifference(entry.first)) * storm::utility::convertNumber<double>(region.getDifference(entry.first))), entry.first}); + } + } + + for (auto itr = sortedOnValues.begin(); itr != sortedOnValues.end() && consideredVariables.size() < region.getSplitThreshold(); ++itr) { + consideredVariables.insert(itr->second); + } + } + if (consideredVariables.size() == 0) { + auto &sortedOnDifference = region.getVariablesSorted(); + for (auto itr = sortedOnDifference.begin(); itr != sortedOnDifference.end() && consideredVariables.size() < region.getSplitThreshold(); ++itr) { + consideredVariables.insert(itr->second); + } + } + assert (consideredVariables.size() > 0); + region.split(region.getCenterPoint(), regionVector, std::move(consideredVariables)); + } + } + template class SparseDtmcParameterLiftingModelChecker<storm::models::sparse::Dtmc<storm::RationalFunction>, double>; template class SparseDtmcParameterLiftingModelChecker<storm::models::sparse::Dtmc<storm::RationalFunction>, storm::RationalNumber>; - } } diff --git a/src/storm-pars/modelchecker/region/SparseDtmcParameterLiftingModelChecker.h b/src/storm-pars/modelchecker/region/SparseDtmcParameterLiftingModelChecker.h index 03dc1747a..10c04c75c 100644 --- a/src/storm-pars/modelchecker/region/SparseDtmcParameterLiftingModelChecker.h +++ b/src/storm-pars/modelchecker/region/SparseDtmcParameterLiftingModelChecker.h @@ -18,21 +18,31 @@ namespace storm { template <typename SparseModelType, typename ConstantType> class SparseDtmcParameterLiftingModelChecker : public SparseParameterLiftingModelChecker<SparseModelType, ConstantType> { public: + typedef typename SparseModelType::ValueType ValueType; + + typedef typename RegionModelChecker<ValueType>::VariableType VariableType; + typedef typename storm::analysis::MonotonicityResult<VariableType>::Monotonicity Monotonicity; + typedef typename storm::storage::ParameterRegion<ValueType>::CoefficientType CoefficientType; + SparseDtmcParameterLiftingModelChecker(); SparseDtmcParameterLiftingModelChecker(std::unique_ptr<storm::solver::MinMaxLinearEquationSolverFactory<ConstantType>>&& solverFactory); virtual ~SparseDtmcParameterLiftingModelChecker() = default; - virtual bool canHandle(std::shared_ptr<storm::models::ModelBase> parametricModel, CheckTask<storm::logic::Formula, typename SparseModelType::ValueType> const& checkTask) const override; + virtual bool canHandle(std::shared_ptr<storm::models::ModelBase> parametricModel, CheckTask<storm::logic::Formula, ValueType> const& checkTask) const override; - virtual void specify(Environment const& env, std::shared_ptr<storm::models::ModelBase> parametricModel, CheckTask<storm::logic::Formula, typename SparseModelType::ValueType> const& checkTask, bool generateRegionSplitEstimates = false, bool allowModelSimplification = true) override; - void specify_internal(Environment const& env, std::shared_ptr<SparseModelType> parametricModel, CheckTask<storm::logic::Formula, typename SparseModelType::ValueType> const& checkTask, bool generateRegionSplitEstimates, bool skipModelSimplification); + virtual void specify(Environment const& env, std::shared_ptr<storm::models::ModelBase> parametricModel, CheckTask<storm::logic::Formula, ValueType> const& checkTask, bool generateRegionSplitEstimates = false, bool allowModelSimplification = true) override; + void specify_internal(Environment const& env, std::shared_ptr<SparseModelType> parametricModel, CheckTask<storm::logic::Formula, ValueType> const& checkTask, bool generateRegionSplitEstimates, bool skipModelSimplification); boost::optional<storm::storage::Scheduler<ConstantType>> getCurrentMinScheduler(); boost::optional<storm::storage::Scheduler<ConstantType>> getCurrentMaxScheduler(); virtual bool isRegionSplitEstimateSupported() const override; - virtual std::map<typename RegionModelChecker<typename SparseModelType::ValueType>::VariableType, double> getRegionSplitEstimate() const override; - + virtual std::map<VariableType, double> getRegionSplitEstimate() const override; + + virtual std::shared_ptr<storm::analysis::Order> extendOrder(Environment const& env, std::shared_ptr<storm::analysis::Order> order, storm::storage::ParameterRegion<ValueType> region) override; + + virtual void extendLocalMonotonicityResult(storm::storage::ParameterRegion<ValueType> const& region, std::shared_ptr<storm::analysis::Order> order, std::shared_ptr<storm::analysis::LocalMonotonicityResult<VariableType>> localMonotonicityResult) override; + protected: virtual void specifyBoundedUntilFormula(Environment const& env, CheckTask<storm::logic::BoundedUntilFormula, ConstantType> const& checkTask) override; @@ -41,23 +51,30 @@ namespace storm { virtual void specifyCumulativeRewardFormula(Environment const& env, CheckTask<storm::logic::CumulativeRewardFormula, ConstantType> const& checkTask) override; virtual storm::modelchecker::SparseInstantiationModelChecker<SparseModelType, ConstantType>& getInstantiationChecker() override; - - virtual std::unique_ptr<CheckResult> computeQuantitativeValues(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters) override; + virtual storm::modelchecker::SparseInstantiationModelChecker<SparseModelType, ConstantType>& getInstantiationCheckerSAT() override; + virtual storm::modelchecker::SparseInstantiationModelChecker<SparseModelType, ConstantType>& getInstantiationCheckerVIO() override; + + virtual std::unique_ptr<CheckResult> computeQuantitativeValues(Environment const& env, storm::storage::ParameterRegion<ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters, std::shared_ptr<storm::analysis::LocalMonotonicityResult<VariableType>> localMonotonicityResult = nullptr) override; - void computeRegionSplitEstimates(std::vector<ConstantType> const& quantitativeResult, std::vector<uint_fast64_t> const& schedulerChoices, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters); + void computeRegionSplitEstimates(std::vector<ConstantType> const& quantitativeResult, std::vector<uint_fast64_t> const& schedulerChoices, storm::storage::ParameterRegion<ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters); virtual void reset() override; - - private: - + virtual void splitSmart(storm::storage::ParameterRegion<ValueType> & region, std::vector<storm::storage::ParameterRegion<ValueType>> ®ionVector, std::shared_ptr<storm::analysis::Order> order, storm::analysis::MonotonicityResult<VariableType> & monRes, bool splitForExtremum) const override; + + + private: storm::storage::BitVector maybeStates; std::vector<ConstantType> resultsForNonMaybeStates; boost::optional<uint_fast64_t> stepBound; + + boost::optional<ConstantType> thresholdTask; std::unique_ptr<storm::modelchecker::SparseDtmcInstantiationModelChecker<SparseModelType, ConstantType>> instantiationChecker; - - std::unique_ptr<storm::transformer::ParameterLifter<typename SparseModelType::ValueType, ConstantType>> parameterLifter; + std::unique_ptr<storm::modelchecker::SparseDtmcInstantiationModelChecker<SparseModelType, ConstantType>> instantiationCheckerSAT; + std::unique_ptr<storm::modelchecker::SparseDtmcInstantiationModelChecker<SparseModelType, ConstantType>> instantiationCheckerVIO; + + std::unique_ptr<storm::transformer::ParameterLifter<ValueType, ConstantType>> parameterLifter; std::unique_ptr<storm::solver::MinMaxLinearEquationSolverFactory<ConstantType>> solverFactory; bool solvingRequiresUpperRewardBounds; @@ -67,7 +84,13 @@ namespace storm { boost::optional<ConstantType> lowerResultBound, upperResultBound; bool regionSplitEstimationsEnabled; - std::map<typename RegionModelChecker<typename SparseModelType::ValueType>::VariableType, double> regionSplitEstimates; + std::map<VariableType, double> regionSplitEstimates; + + // Used for monotonicity + bool useRegionSplitEstimates; + std::unique_ptr<storm::analysis::MonotonicityChecker<ValueType>> monotonicityChecker; + + }; } } diff --git a/src/storm-pars/modelchecker/region/SparseMdpParameterLiftingModelChecker.cpp b/src/storm-pars/modelchecker/region/SparseMdpParameterLiftingModelChecker.cpp index c58426f79..9ebb19192 100644 --- a/src/storm-pars/modelchecker/region/SparseMdpParameterLiftingModelChecker.cpp +++ b/src/storm-pars/modelchecker/region/SparseMdpParameterLiftingModelChecker.cpp @@ -43,21 +43,21 @@ namespace storm { result &= checkTask.getFormula().isInFragment(storm::logic::reachability().setRewardOperatorsAllowed(true).setReachabilityRewardFormulasAllowed(true).setBoundedUntilFormulasAllowed(true).setCumulativeRewardFormulasAllowed(true).setStepBoundedUntilFormulasAllowed(true).setTimeBoundedCumulativeRewardFormulasAllowed(true).setStepBoundedCumulativeRewardFormulasAllowed(true).setTimeBoundedUntilFormulasAllowed(true)); return result; } - + template <typename SparseModelType, typename ConstantType> void SparseMdpParameterLiftingModelChecker<SparseModelType, ConstantType>::specify(Environment const& env, std::shared_ptr<storm::models::ModelBase> parametricModel, CheckTask<storm::logic::Formula, typename SparseModelType::ValueType> const& checkTask, bool generateRegionSplitEstimates, bool allowModelSimplifications) { auto mdp = parametricModel->template as<SparseModelType>(); specify_internal(env, mdp, checkTask, generateRegionSplitEstimates, !allowModelSimplifications); } - + template <typename SparseModelType, typename ConstantType> void SparseMdpParameterLiftingModelChecker<SparseModelType, ConstantType>::specify_internal(Environment const& env, std::shared_ptr<SparseModelType> parametricModel, CheckTask<storm::logic::Formula, typename SparseModelType::ValueType> const& checkTask, bool generateRegionSplitEstimates, bool skipModelSimplification) { STORM_LOG_ASSERT(this->canHandle(parametricModel, checkTask), "specified model and formula can not be handled by this."); - + reset(); - + if (skipModelSimplification) { this->parametricModel = parametricModel; this->specifyFormula(env, checkTask); @@ -240,7 +240,7 @@ namespace storm { } template <typename SparseModelType, typename ConstantType> - std::unique_ptr<CheckResult> SparseMdpParameterLiftingModelChecker<SparseModelType, ConstantType>::computeQuantitativeValues(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters) { + std::unique_ptr<CheckResult> SparseMdpParameterLiftingModelChecker<SparseModelType, ConstantType>::computeQuantitativeValues(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters, std::shared_ptr<storm::analysis::LocalMonotonicityResult<typename RegionModelChecker<typename SparseModelType::ValueType>::VariableType>> localMonotonicityResult) { if (maybeStates.empty()) { return std::make_unique<storm::modelchecker::ExplicitQuantitativeCheckResult<ConstantType>>(resultsForNonMaybeStates); diff --git a/src/storm-pars/modelchecker/region/SparseMdpParameterLiftingModelChecker.h b/src/storm-pars/modelchecker/region/SparseMdpParameterLiftingModelChecker.h index d29525e2e..c4954081a 100644 --- a/src/storm-pars/modelchecker/region/SparseMdpParameterLiftingModelChecker.h +++ b/src/storm-pars/modelchecker/region/SparseMdpParameterLiftingModelChecker.h @@ -41,7 +41,7 @@ namespace storm { virtual storm::modelchecker::SparseInstantiationModelChecker<SparseModelType, ConstantType>& getInstantiationChecker() override; - virtual std::unique_ptr<CheckResult> computeQuantitativeValues(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters) override; + virtual std::unique_ptr<CheckResult> computeQuantitativeValues(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters, std::shared_ptr<storm::analysis::LocalMonotonicityResult<typename RegionModelChecker<typename SparseModelType::ValueType>::VariableType>> localMonotonicityResult = nullptr) override; virtual void reset() override; diff --git a/src/storm-pars/modelchecker/region/SparseParameterLiftingModelChecker.cpp b/src/storm-pars/modelchecker/region/SparseParameterLiftingModelChecker.cpp index 2ca96906b..a38dbd5a7 100644 --- a/src/storm-pars/modelchecker/region/SparseParameterLiftingModelChecker.cpp +++ b/src/storm-pars/modelchecker/region/SparseParameterLiftingModelChecker.cpp @@ -1,6 +1,8 @@ #include "storm-pars/modelchecker/region/SparseParameterLiftingModelChecker.h" #include <queue> +#include <boost/container/flat_set.hpp> +#include <storm-pars/analysis/MonotonicityChecker.h> #include "storm/adapters/RationalFunctionAdapter.h" #include "storm/logic/FragmentSpecification.h" @@ -28,7 +30,7 @@ namespace storm { currentFormula = checkTask.getFormula().asSharedPointer(); currentCheckTask = std::make_unique<storm::modelchecker::CheckTask<storm::logic::Formula, ConstantType>>(checkTask.substituteFormula(*currentFormula).template convertValueType<ConstantType>()); - if(currentCheckTask->getFormula().isProbabilityOperatorFormula()) { + if (currentCheckTask->getFormula().isProbabilityOperatorFormula()) { auto const& probOpFormula = currentCheckTask->getFormula().asProbabilityOperatorFormula(); if(probOpFormula.getSubformula().isBoundedUntilFormula()) { specifyBoundedUntilFormula(env, currentCheckTask->substituteFormula(probOpFormula.getSubformula().asBoundedUntilFormula())); @@ -50,8 +52,11 @@ namespace storm { } template <typename SparseModelType, typename ConstantType> - RegionResult SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::analyzeRegion(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, RegionResultHypothesis const& hypothesis, RegionResult const& initialResult, bool sampleVerticesOfRegion) { - + RegionResult SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::analyzeRegion(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, RegionResultHypothesis const& hypothesis, RegionResult const& initialResult, bool sampleVerticesOfRegion, std::shared_ptr<storm::analysis::Order> reachabilityOrder, std::shared_ptr<storm::analysis::LocalMonotonicityResult<typename RegionModelChecker<typename SparseModelType::ValueType>::VariableType>> localMonotonicityResult) { + typedef typename RegionModelChecker<typename SparseModelType::ValueType>::VariableType VariableType; + typedef typename storm::analysis::MonotonicityResult<VariableType>::Monotonicity Monotonicity; + typedef typename storm::utility::parametric::Valuation<typename SparseModelType::ValueType> Valuation; + typedef typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::CoefficientType CoefficientType; STORM_LOG_THROW(this->currentCheckTask->isOnlyInitialStatesRelevantSet(), storm::exceptions::NotSupportedException, "Analyzing regions with parameter lifting requires a property where only the value in the initial states is relevant."); STORM_LOG_THROW(this->currentCheckTask->isBoundSet(), storm::exceptions::NotSupportedException, "Analyzing regions with parameter lifting requires a bounded property."); STORM_LOG_THROW(this->parametricModel->getInitialStates().getNumberOfSetBits() == 1, storm::exceptions::NotSupportedException, "Analyzing regions with parameter lifting requires a model with a single initial state."); @@ -60,22 +65,79 @@ namespace storm { // Check if we need to check the formula on one point to decide whether to show AllSat or AllViolated if (hypothesis == RegionResultHypothesis::Unknown && result == RegionResult::Unknown) { - result = getInstantiationChecker().check(env, region.getCenterPoint())->asExplicitQualitativeCheckResult()[*this->parametricModel->getInitialStates().begin()] ? RegionResult::CenterSat : RegionResult::CenterViolated; + result = getInstantiationChecker().check(env, region.getCenterPoint())->asExplicitQualitativeCheckResult()[*this->parametricModel->getInitialStates().begin()] ? RegionResult::CenterSat : RegionResult::CenterViolated; } - - // try to prove AllSat or AllViolated, depending on the hypothesis or the current result - if (hypothesis == RegionResultHypothesis::AllSat || result == RegionResult::ExistsSat || result == RegionResult::CenterSat) { + + bool existsSat = (hypothesis == RegionResultHypothesis::AllSat || result == RegionResult::ExistsSat || result == RegionResult::CenterSat); + bool existsViolated = (hypothesis == RegionResultHypothesis::AllViolated || result == RegionResult::ExistsViolated || result == RegionResult::CenterViolated); + + // Here we check on global monotonicity + if (localMonotonicityResult != nullptr && localMonotonicityResult->isDone()) { + // Try to check it with a global monotonicity result + auto monRes = localMonotonicityResult->getGlobalMonotonicityResult(); + bool lowerBound = isLowerBound(this->currentCheckTask->getBound().comparisonType); + + if (monRes->isDone() && monRes->isAllMonotonicity()) { + // Build valuations + auto monMap = monRes->getMonotonicityResult(); + Valuation valuationToCheckSat; + Valuation valuationToCheckViolated; + for (auto var : region.getVariables()) { + auto monVar = monMap[var]; + if (monVar == Monotonicity::Constant) { + valuationToCheckSat.insert(std::pair<VariableType, CoefficientType>(var, region.getLowerBoundary(var))); + valuationToCheckViolated.insert(std::pair<VariableType, CoefficientType>(var, region.getLowerBoundary(var))); + } else if (monVar == Monotonicity::Decr) { + if (lowerBound) { + valuationToCheckSat.insert(std::pair<VariableType, CoefficientType>(var, region.getUpperBoundary(var))); + valuationToCheckViolated.insert(std::pair<VariableType, CoefficientType>(var, region.getLowerBoundary(var))); + } else { + valuationToCheckSat.insert(std::pair<VariableType, CoefficientType>(var, region.getLowerBoundary(var))); + valuationToCheckViolated.insert(std::pair<VariableType, CoefficientType>(var, region.getUpperBoundary(var))); + } + } else if (monVar == Monotonicity::Incr) { + if (lowerBound) { + valuationToCheckSat.insert(std::pair<VariableType, CoefficientType>(var, region.getLowerBoundary(var))); + valuationToCheckViolated.insert(std::pair<VariableType, CoefficientType>(var, region.getUpperBoundary(var))); + } else { + valuationToCheckSat.insert(std::pair<VariableType, CoefficientType>(var, region.getUpperBoundary(var))); + valuationToCheckViolated.insert(std::pair<VariableType, CoefficientType>(var, region.getLowerBoundary(var))); + } + } + } + + // Check for result + if (existsSat && getInstantiationCheckerSAT().check(env, valuationToCheckSat)->asExplicitQualitativeCheckResult()[*this->parametricModel->getInitialStates().begin()]) { + STORM_LOG_INFO("Region " << region << " is AllSat, discovered with instantiation checker on " << valuationToCheckSat << " and help of monotonicity" << std::endl); + RegionModelChecker<typename SparseModelType::ValueType>::numberOfRegionsKnownThroughMonotonicity++; + return RegionResult::AllSat; + } + + if (existsViolated && !getInstantiationCheckerVIO().check(env, valuationToCheckViolated)->asExplicitQualitativeCheckResult()[*this->parametricModel->getInitialStates().begin()]) { + STORM_LOG_INFO("Region " << region << " is AllViolated, discovered with instantiation checker on " << valuationToCheckViolated << " and help of monotonicity" << std::endl); + RegionModelChecker<typename SparseModelType::ValueType>::numberOfRegionsKnownThroughMonotonicity++; + return RegionResult::AllViolated; + } + + return RegionResult::ExistsBoth; + } + } + + // Try to prove AllSat or AllViolated, depending on the hypothesis or the current result + if (existsSat) { // show AllSat: storm::solver::OptimizationDirection parameterOptimizationDirection = isLowerBound(this->currentCheckTask->getBound().comparisonType) ? storm::solver::OptimizationDirection::Minimize : storm::solver::OptimizationDirection::Maximize; - if (this->check(env, region, parameterOptimizationDirection)->asExplicitQualitativeCheckResult()[*this->parametricModel->getInitialStates().begin()]) { + auto checkResult = this->check(env, region, parameterOptimizationDirection, reachabilityOrder, localMonotonicityResult); + if (checkResult->asExplicitQualitativeCheckResult()[*this->parametricModel->getInitialStates().begin()]) { result = RegionResult::AllSat; } else if (sampleVerticesOfRegion) { result = sampleVertices(env, region, result); } - } else if (hypothesis == RegionResultHypothesis::AllViolated || result == RegionResult::ExistsViolated || result == RegionResult::CenterViolated) { + } else if (existsViolated) { // show AllViolated: storm::solver::OptimizationDirection parameterOptimizationDirection = isLowerBound(this->currentCheckTask->getBound().comparisonType) ? storm::solver::OptimizationDirection::Maximize : storm::solver::OptimizationDirection::Minimize; - if (!this->check(env, region, parameterOptimizationDirection)->asExplicitQualitativeCheckResult()[*this->parametricModel->getInitialStates().begin()]) { + auto checkResult = this->check(env, region, parameterOptimizationDirection, reachabilityOrder, localMonotonicityResult); + if (!checkResult->asExplicitQualitativeCheckResult()[*this->parametricModel->getInitialStates().begin()]) { result = RegionResult::AllViolated; } else if (sampleVerticesOfRegion) { result = sampleVertices(env, region, result); @@ -122,10 +184,10 @@ namespace storm { return result; } - template <typename SparseModelType, typename ConstantType> - std::unique_ptr<CheckResult> SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::check(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters) { - auto quantitativeResult = computeQuantitativeValues(env, region, dirForParameters); + std::unique_ptr<CheckResult> SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::check(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters, std::shared_ptr<storm::analysis::Order> reachabilityOrder, std::shared_ptr<storm::analysis::LocalMonotonicityResult<typename RegionModelChecker<typename SparseModelType::ValueType>::VariableType>> localMonotonicityResult) { + auto quantitativeResult = computeQuantitativeValues(env, region, dirForParameters, localMonotonicityResult); + lastValue = quantitativeResult->template asExplicitQuantitativeCheckResult<ConstantType>()[*this->parametricModel->getInitialStates().begin()]; if(currentCheckTask->getFormula().hasQuantitativeResult()) { return quantitativeResult; } else { @@ -134,9 +196,9 @@ namespace storm { } template <typename SparseModelType, typename ConstantType> - std::unique_ptr<QuantitativeCheckResult<ConstantType>> SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::getBound(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters) { + std::unique_ptr<QuantitativeCheckResult<ConstantType>> SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::getBound(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters, std::shared_ptr<storm::analysis::LocalMonotonicityResult<typename RegionModelChecker<typename SparseModelType::ValueType>::VariableType>> localMonotonicityResult) { STORM_LOG_WARN_COND(this->currentCheckTask->getFormula().hasQuantitativeResult(), "Computing quantitative bounds for a qualitative formula..."); - return std::make_unique<ExplicitQuantitativeCheckResult<ConstantType>>(std::move(computeQuantitativeValues(env, region, dirForParameters)->template asExplicitQuantitativeCheckResult<ConstantType>())); + return std::make_unique<ExplicitQuantitativeCheckResult<ConstantType>>(std::move(computeQuantitativeValues(env, region, dirForParameters, localMonotonicityResult)->template asExplicitQuantitativeCheckResult<ConstantType>())); } template <typename SparseModelType, typename ConstantType> @@ -144,80 +206,268 @@ namespace storm { STORM_LOG_THROW(this->parametricModel->getInitialStates().getNumberOfSetBits() == 1, storm::exceptions::NotSupportedException, "Getting a bound at the initial state requires a model with a single initial state."); return storm::utility::convertNumber<typename SparseModelType::ValueType>(getBound(env, region, dirForParameters)->template asExplicitQuantitativeCheckResult<ConstantType>()[*this->parametricModel->getInitialStates().begin()]); } - + + template <typename SparseModelType, typename ConstantType> + storm::modelchecker::SparseInstantiationModelChecker<SparseModelType, ConstantType>& SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::getInstantiationCheckerSAT() { + return getInstantiationChecker(); + } + + template <typename SparseModelType, typename ConstantType> + storm::modelchecker::SparseInstantiationModelChecker<SparseModelType, ConstantType>& SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::getInstantiationCheckerVIO() { + return getInstantiationChecker(); + } + template <typename SparseModelType, typename ConstantType> struct RegionBound { + typedef typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::VariableType VariableType; + RegionBound(RegionBound<SparseModelType, ConstantType> const& other) = default; - RegionBound(storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& r, ConstantType const& b) : region(r), bound(b) {} + RegionBound(storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& r, std::shared_ptr<storm::analysis::Order> o, std::shared_ptr<storm::analysis::LocalMonotonicityResult<VariableType>> l, ConstantType const& b) : region(r), order(o), localMonRes(l), bound(b) {} storm::storage::ParameterRegion<typename SparseModelType::ValueType> region; + std::shared_ptr<storm::analysis::Order> order; + std::shared_ptr<storm::analysis::LocalMonotonicityResult<VariableType>> localMonRes; ConstantType bound; }; - - template <typename SparseModelType, typename ConstantType> - std::pair<typename SparseModelType::ValueType, typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::Valuation> SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::computeExtremalValue(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dir, typename SparseModelType::ValueType const& precision) { + + template<typename SparseModelType, typename ConstantType> + std::pair<typename SparseModelType::ValueType, typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::Valuation> SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::computeExtremalValue(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dir, typename SparseModelType::ValueType const& precision, boost::optional<ConstantType> const& initialValue) { + typedef typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::CoefficientType CoefficientType; + typedef typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::Valuation Valuation; STORM_LOG_THROW(this->parametricModel->getInitialStates().getNumberOfSetBits() == 1, storm::exceptions::NotSupportedException, "Getting extremal values at the initial state requires a model with a single initial state."); - boost::optional<ConstantType> value; - typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::Valuation valuation; - - for (auto const& v : region.getVerticesOfRegion(region.getVariables())) { - auto currValue = getInstantiationChecker().check(env, v)->template asExplicitQuantitativeCheckResult<ConstantType>()[*this->parametricModel->getInitialStates().begin()]; - if (!value.is_initialized() || (storm::solver::minimize(dir) ? currValue < value.get() : currValue > value.get())) { - value = currValue; - valuation = v; - STORM_LOG_INFO("Current value for extremum: " << value.get() << "."); - } - } - + bool const useMonotonicity = this->isUseMonotonicitySet(); + bool const minimize = storm::solver::minimize(dir); + + // Comparator for the region queue auto cmp = storm::solver::minimize(dir) ? - [](RegionBound<SparseModelType, ConstantType> const& lhs, RegionBound<SparseModelType, ConstantType> const& rhs) { return lhs.bound > rhs.bound; } : - [](RegionBound<SparseModelType, ConstantType> const& lhs, RegionBound<SparseModelType, ConstantType> const& rhs) { return lhs.bound < rhs.bound; }; + [](RegionBound<SparseModelType, ConstantType> const& lhs, RegionBound<SparseModelType, ConstantType> const& rhs) { return lhs.bound > rhs.bound; } : + [](RegionBound<SparseModelType, ConstantType> const& lhs, RegionBound<SparseModelType, ConstantType> const& rhs) { return lhs.bound < rhs.bound; }; std::priority_queue<RegionBound<SparseModelType, ConstantType>, std::vector<RegionBound<SparseModelType, ConstantType>>, decltype(cmp)> regionQueue(cmp); - regionQueue.emplace(region, storm::utility::zero<ConstantType>()); - auto totalArea = storm::utility::convertNumber<ConstantType>(region.area()); - auto coveredArea = storm::utility::zero<ConstantType>(); - while (!regionQueue.empty()) { - auto const& currRegion = regionQueue.top().region; - - // Check whether this region contains a new 'good' value - auto currValue = getInstantiationChecker().check(env, currRegion.getCenterPoint())->template asExplicitQuantitativeCheckResult<ConstantType>()[*this->parametricModel->getInitialStates().begin()]; - if (storm::solver::minimize(dir) ? currValue < value.get() : currValue > value.get()) { - value = currValue; - valuation = currRegion.getCenterPoint(); - } + storm::utility::Stopwatch initialWatch(true); - // Check whether this region needs further investigation (i.e. splitting) - auto currBound = getBound(env, currRegion, dir)->template asExplicitQuantitativeCheckResult<ConstantType>()[*this->parametricModel->getInitialStates().begin()]; - std::vector<storm::storage::ParameterRegion<typename SparseModelType::ValueType>> newRegions; - if (storm::solver::minimize(dir)) { - if (currBound < value.get() - storm::utility::convertNumber<ConstantType>(precision)) { - currRegion.split(currRegion.getCenterPoint(), newRegions); + storm::utility::Stopwatch boundsWatch(false); + auto numberOfPLACallsBounds = 0; + ConstantType initBound; + if (minimize) { + initBound = storm::utility::zero<ConstantType>(); + } else { + initBound = storm::utility::one<ConstantType>(); + } + if (useMonotonicity) { + if (this->isUseBoundsSet()) { + numberOfPLACallsBounds++; + numberOfPLACallsBounds++; + auto minBound = getBound(env, region, storm::solver::OptimizationDirection::Minimize, nullptr)->template asExplicitQuantitativeCheckResult<ConstantType>().getValueVector(); + auto maxBound = getBound(env, region, storm::solver::OptimizationDirection::Maximize, nullptr)->template asExplicitQuantitativeCheckResult<ConstantType>().getValueVector(); + if (minimize) { + initBound = minBound[*this->parametricModel->getInitialStates().begin()]; + } else { + initBound = maxBound[*this->parametricModel->getInitialStates().begin()]; } + orderExtender->setMinValuesInit(minBound); + orderExtender->setMaxValuesInit(maxBound); + } + auto order = this->extendOrder(env, nullptr, region); + auto monRes = std::shared_ptr<storm::analysis::LocalMonotonicityResult<VariableType>>(new storm::analysis::LocalMonotonicityResult<VariableType>(order->getNumberOfStates())); + storm::utility::Stopwatch monotonicityWatch(true); + this->extendLocalMonotonicityResult(region, order, monRes); + monotonicityWatch.stop(); + STORM_LOG_INFO(std::endl << "Total time for monotonicity checking: " << monotonicityWatch << "." << std::endl << std::endl); + + if (minimize) { + regionQueue.emplace(region, order, monRes, initBound); } else { - if (currBound > value.get() + storm::utility::convertNumber<ConstantType>(precision)) { - currRegion.split(currRegion.getCenterPoint(), newRegions); - } + regionQueue.emplace(region, order, monRes, initBound); } - - if (newRegions.empty()) { - coveredArea += storm::utility::convertNumber<ConstantType>(currRegion.area()); + } else { + if (minimize) { + regionQueue.emplace(region, nullptr, nullptr, initBound); + } else { + regionQueue.emplace(region, nullptr, nullptr, initBound); } - regionQueue.pop(); - for (auto const& r : newRegions) { - regionQueue.emplace(r, currBound); + } + + // The results + boost::optional<ConstantType> value; + Valuation valuation; + if (!initialValue) { + auto init = getGoodInitialPoint(env, region, dir, regionQueue.top().localMonRes); + value = storm::utility::convertNumber<ConstantType>(init.first); + valuation = std::move(init.second); + } else { + value = initialValue; + } + + initialWatch.stop(); + STORM_LOG_INFO(std::endl << "Total time for initial points: " << initialWatch << "." << std::endl << std::endl); + if (!initialValue) { + STORM_LOG_INFO("Initial value: " << value.get() << " at " << valuation); + } else { + STORM_LOG_INFO("Initial value: " << value.get() << " as provided by the user"); + } + + auto numberOfSplits = 0; + auto numberOfPLACalls = 0; + auto numberOfOrderCopies = 0; + auto numberOfMonResCopies = 0; + bool first = true; + storm::utility::Stopwatch loopWatch(true); + if (!(useMonotonicity && regionQueue.top().localMonRes->getGlobalMonotonicityResult()->isDone() && regionQueue.top().localMonRes->getGlobalMonotonicityResult()->isAllMonotonicity())) { + // Doing the extremal computation, only when we don't use monotonicity or there are possibly not monotone variables. + auto totalArea = storm::utility::convertNumber<ConstantType>(region.area()); + auto coveredArea = storm::utility::zero<ConstantType>(); + while (!regionQueue.empty()) { + assert (value); + auto currRegion = regionQueue.top().region; + auto order = regionQueue.top().order; + auto localMonotonicityResult = regionQueue.top().localMonRes; + auto currBound = regionQueue.top().bound; + STORM_LOG_INFO("Currently looking at region: " << currRegion); + std::vector<storm::storage::ParameterRegion<typename SparseModelType::ValueType>> newRegions; + + // Check whether this region needs further investigation based on the bound of the parent region + bool investigateBounds = (minimize && currBound < value.get() - storm::utility::convertNumber<ConstantType>(precision)) + || (!minimize && currBound > value.get() + storm::utility::convertNumber<ConstantType>(precision)); + if (investigateBounds) { + numberOfPLACalls++; + auto bounds = getBound(env, currRegion, dir, localMonotonicityResult)->template asExplicitQuantitativeCheckResult<ConstantType>().getValueVector(); + currBound = bounds[*this->parametricModel->getInitialStates().begin()]; + // Check whether this region needs further investigation based on the bound of this region + bool lookAtRegion = (minimize && currBound < value.get() - storm::utility::convertNumber<ConstantType>(precision)) + || (!minimize && currBound > value.get() + storm::utility::convertNumber<ConstantType>(precision)); + if (lookAtRegion) { + if (useMonotonicity) { + // Continue extending order/monotonicity result + bool changedOrder = false; + if (!order->getDoneBuilding() && orderExtender->isHope(order, currRegion)) { + if (numberOfCopiesOrder[order] != 1) { + numberOfCopiesOrder[order]--; + order = copyOrder(order); + numberOfOrderCopies++; + } else { + assert (numberOfCopiesOrder[order] == 1); + } + this->extendOrder(env, order, currRegion); + changedOrder = true; + } + if (changedOrder) { + assert(!localMonotonicityResult->isDone()); + + if (numberOfCopiesMonRes[localMonotonicityResult] != 1) { + numberOfCopiesMonRes[localMonotonicityResult]--; + localMonotonicityResult = localMonotonicityResult->copy(); + numberOfMonResCopies++; + } else { + assert (numberOfCopiesMonRes[localMonotonicityResult] == 1); + } + this->extendLocalMonotonicityResult(currRegion, order, localMonotonicityResult); + STORM_LOG_INFO("Order and monotonicity result got extended"); + } + } + + // Check whether this region contains a new 'good' value and set this value + auto point = useMonotonicity ? currRegion.getPoint(dir, *(localMonotonicityResult->getGlobalMonotonicityResult())) : currRegion.getCenterPoint(); + auto currValue = getInstantiationChecker().check(env, point)->template asExplicitQuantitativeCheckResult<ConstantType>()[*this->parametricModel->getInitialStates().begin()]; + if (!value || (minimize ? currValue <= value.get() : currValue >= value.get())) { + value = currValue; + valuation = point; + } + + if ((minimize && currBound < value.get() - storm::utility::convertNumber<ConstantType>(precision)) + || (!minimize && currBound > value.get() + storm::utility::convertNumber<ConstantType>(precision))) { + + // We will split the region in this case, but first we set the bounds to extend the order for the new regions. + if (useMonotonicity && this->isUseBoundsSet() && !order->getDoneBuilding()) { + boundsWatch.start(); + numberOfPLACallsBounds++; + if (minimize) { + orderExtender->setMinMaxValues(order, bounds, getBound(env, currRegion, storm::solver::OptimizationDirection::Maximize, localMonotonicityResult)->template asExplicitQuantitativeCheckResult<ConstantType>().getValueVector()); + } else { + orderExtender->setMinMaxValues(order, getBound(env, currRegion, storm::solver::OptimizationDirection::Maximize, localMonotonicityResult)->template asExplicitQuantitativeCheckResult<ConstantType>().getValueVector(), bounds); + } + boundsWatch.stop(); + } + // Now split the region + if (useMonotonicity) { + this->splitSmart(currRegion, newRegions, order, *(localMonotonicityResult->getGlobalMonotonicityResult()), true); + } else if (this->isRegionSplitEstimateSupported()) { + auto empty = storm::analysis::MonotonicityResult<VariableType>(); + this->splitSmart(currRegion, newRegions, order, empty, true); + } else { + currRegion.split(currRegion.getCenterPoint(), newRegions); + } + } + } + } + + if (newRegions.empty()) { + // When the newRegions is empty we are done with the current region + coveredArea += storm::utility::convertNumber<ConstantType>(currRegion.area()); + if (order != nullptr) { + numberOfCopiesOrder[order]--; + numberOfCopiesMonRes[localMonotonicityResult]--; + } + regionQueue.pop(); + } else { + regionQueue.pop(); + STORM_LOG_INFO("Splitting region " << currRegion << " into " << newRegions.size()); + numberOfSplits++; + // Add the new regions to the queue + if (useMonotonicity) { + for (auto &r : newRegions) { + r.setBoundParent(storm::utility::convertNumber<CoefficientType>(currBound)); + regionQueue.emplace(r, order, localMonotonicityResult, currBound); + } + if (numberOfCopiesOrder.find(order) != numberOfCopiesOrder.end()) { + numberOfCopiesOrder[order] += newRegions.size(); + numberOfCopiesMonRes[localMonotonicityResult] += newRegions.size(); + } else { + numberOfCopiesOrder[order] = newRegions.size(); + numberOfCopiesMonRes[localMonotonicityResult] = newRegions.size(); + } + } else { + for (auto &r : newRegions) { + r.setBoundParent(storm::utility::convertNumber<CoefficientType>(currBound)); + regionQueue.emplace(r, nullptr, nullptr, currBound); + } + } + } + + STORM_LOG_INFO("Current value : " << value.get() << ", current bound: " << currBound << "."); + STORM_LOG_INFO("Covered " << (coveredArea * storm::utility::convertNumber<ConstantType>(100.0) / totalArea) << "% of the region." << std::endl); } - STORM_LOG_INFO("Current value : " << value.get() << ", current bound: " << regionQueue.top().bound << "."); - STORM_LOG_INFO("Covered " << (coveredArea * storm::utility::convertNumber<ConstantType>(100.0) / totalArea) << "% of the region."); + loopWatch.stop(); } - + + STORM_LOG_INFO("Total number of splits: " << numberOfSplits << std::endl); + STORM_PRINT("Total number of plaCalls: " << numberOfPLACalls << std::endl); + if (useMonotonicity) { + STORM_PRINT("Total number of plaCalls for bounds for monotonicity checking: " << numberOfPLACallsBounds << std::endl); + STORM_PRINT("Total number of copies of the order: " << numberOfOrderCopies << std::endl); + STORM_PRINT("Total number of copies of the local monotonicity result: " << numberOfMonResCopies + << std::endl); + } + STORM_LOG_INFO(std::endl << "Total time for region refinement: " << loopWatch << "." << std::endl << std::endl); + STORM_LOG_INFO(std::endl << "Total time for additional bounds: " << boundsWatch << "." << std::endl << std::endl); + return std::make_pair(storm::utility::convertNumber<typename SparseModelType::ValueType>(value.get()), valuation); } - + template <typename SparseModelType, typename ConstantType> + std::pair<typename SparseModelType::ValueType, typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::Valuation> SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::computeExtremalValue(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dir, typename SparseModelType::ValueType const& precision) { + return computeExtremalValue(env, region, dir, precision, boost::none); + } + + template <typename SparseModelType, typename ConstantType> + bool SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::checkExtremalValue(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dir, typename SparseModelType::ValueType const& precision, typename SparseModelType::ValueType const& valueToCheck) { + auto res = computeExtremalValue(env, region, dir, precision, storm::utility::convertNumber<ConstantType>(valueToCheck)).first; + return storm::solver::minimize(dir) ? storm::utility::convertNumber<ConstantType>(res) >= storm::utility::convertNumber<ConstantType>(valueToCheck) : storm::utility::convertNumber<ConstantType>(res) <= storm::utility::convertNumber<ConstantType>(valueToCheck); + } + template <typename SparseModelType, typename ConstantType> SparseModelType const& SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::getConsideredParametricModel() const { return *parametricModel; } - + template <typename SparseModelType, typename ConstantType> CheckTask<storm::logic::Formula, ConstantType> const& SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::getCurrentCheckTask() const { return *currentCheckTask; @@ -250,10 +500,108 @@ namespace storm { STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Parameter lifting is not supported for the given property."); } + template<typename SparseModelType, typename ConstantType> + std::shared_ptr<storm::analysis::Order> SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::copyOrder(std::shared_ptr<storm::analysis::Order> order) { + auto res = order->copy(); + if (orderExtender) { + orderExtender->setUnknownStates(order, res); + orderExtender->copyMinMax(order, res); + } + return res; + } + + template<typename SparseModelType, typename ConstantType> + std::pair<typename SparseModelType::ValueType, typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::Valuation> SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::checkForPossibleMonotonicity(Environment const& env, + const storage::ParameterRegion<typename SparseModelType::ValueType> ®ion, + std::set<VariableType>& possibleMonotoneIncrParameters, + std::set<VariableType>& possibleMonotoneDecrParameters, + std::set<VariableType>& possibleNotMonotoneParameters, + std::set<VariableType>const& consideredVariables, + storm::solver::OptimizationDirection const& dir) { + bool minimize = storm::solver::minimize(dir); + typedef typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::Valuation Valuation; + typedef typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::CoefficientType CoefficientType; + ConstantType value = storm::solver::minimize(dir) ? 1 : 0; + Valuation valuation; + for (auto& var : consideredVariables) { + ConstantType previousCenter = -1; + bool monDecr = true; + bool monIncr = true; + auto valuationCenter = region.getCenterPoint(); + + valuationCenter[var] = region.getLowerBoundary(var); + // TODO: make cmdline argument or 1/precision + int numberOfSamples = 50; + auto stepSize = (region.getUpperBoundary(var) - region.getLowerBoundary(var)) / (numberOfSamples - 1); + + while (valuationCenter[var] <= region.getUpperBoundary(var)) { + // Create valuation + ConstantType valueCenter = getInstantiationChecker().check(env, valuationCenter)->template asExplicitQuantitativeCheckResult<ConstantType>()[*this->parametricModel->getInitialStates().begin()]; + if (storm::solver::minimize(dir) ? valueCenter <= value : valueCenter >= value) { + value = valueCenter; + valuation = valuationCenter; + } + // Calculate difference with result for previous valuation + ConstantType diffCenter = previousCenter - valueCenter; + assert (previousCenter == -1 || (diffCenter >= -1 && diffCenter <= 1)); + if (previousCenter != -1) { + assert (previousCenter != -1 && previousCenter != -1); + monDecr &= diffCenter > 0 && diffCenter > 0 && diffCenter > 0; // then previous value is larger than the current value from the initial states + monIncr &= diffCenter < 0 && diffCenter < 0 && diffCenter < 0; + } + previousCenter = valueCenter; + if (!monDecr && ! monIncr) { + break; + } + valuationCenter[var] += stepSize; + } + if (monIncr) { + possibleMonotoneParameters.insert(var); + possibleMonotoneIncrParameters.insert(var); + } else if (monDecr) { + possibleMonotoneParameters.insert(var); + possibleMonotoneDecrParameters.insert(var); + } else { + possibleNotMonotoneParameters.insert(var); + } + } + return std::make_pair(storm::utility::convertNumber<typename SparseModelType::ValueType>(value), std::move(valuation)); + + } + + template<typename SparseModelType, typename ConstantType> + std::pair<typename SparseModelType::ValueType, typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::Valuation> SparseParameterLiftingModelChecker<SparseModelType, ConstantType>::getGoodInitialPoint(const Environment &env, const storage::ParameterRegion<typename SparseModelType::ValueType> ®ion, const OptimizationDirection &dir, std::shared_ptr<storm::analysis::LocalMonotonicityResult<VariableType>> localMonRes) { + typedef typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::Valuation Valuation; + typedef typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::CoefficientType CoefficientType; + ConstantType value = storm::solver::minimize(dir) ? 1 : 0; + Valuation valuation; + std::set<VariableType> monIncr, monDecr, notMon, notMonFirst; + STORM_LOG_INFO("Number of parameters: " << region.getVariables().size() << std::endl;); + + if (localMonRes != nullptr) { + localMonRes->getGlobalMonotonicityResult()->splitBasedOnMonotonicity(region.getVariables(), monIncr, monDecr, notMonFirst); + + auto numMon = monIncr.size() + monDecr.size(); + STORM_LOG_INFO("Number of monotone parameters: " << numMon << std::endl;); + + if (numMon < region.getVariables().size()) { + checkForPossibleMonotonicity(env, region, monIncr, monDecr, notMon, notMonFirst, dir); + STORM_LOG_INFO("Number of possible monotone parameters: " << (monIncr.size() + monDecr.size() - numMon) << std::endl;); + STORM_LOG_INFO("Number of definitely not monotone parameters: " << notMon.size() << std::endl;); + } + + valuation = region.getPoint(dir, monIncr, monDecr); + } else { + valuation = region.getCenterPoint(); + } + value = getInstantiationChecker().check(env, valuation)->template asExplicitQuantitativeCheckResult<ConstantType>()[*this->parametricModel->getInitialStates().begin()]; + + return std::make_pair(storm::utility::convertNumber<typename SparseModelType::ValueType>(value), std::move(valuation)); + } + template class SparseParameterLiftingModelChecker<storm::models::sparse::Dtmc<storm::RationalFunction>, double>; template class SparseParameterLiftingModelChecker<storm::models::sparse::Mdp<storm::RationalFunction>, double>; template class SparseParameterLiftingModelChecker<storm::models::sparse::Dtmc<storm::RationalFunction>, storm::RationalNumber>; template class SparseParameterLiftingModelChecker<storm::models::sparse::Mdp<storm::RationalFunction>, storm::RationalNumber>; - } } diff --git a/src/storm-pars/modelchecker/region/SparseParameterLiftingModelChecker.h b/src/storm-pars/modelchecker/region/SparseParameterLiftingModelChecker.h index 62e2a634d..bba0ac8fd 100644 --- a/src/storm-pars/modelchecker/region/SparseParameterLiftingModelChecker.h +++ b/src/storm-pars/modelchecker/region/SparseParameterLiftingModelChecker.h @@ -22,14 +22,17 @@ namespace storm { template <typename SparseModelType, typename ConstantType> class SparseParameterLiftingModelChecker : public RegionModelChecker<typename SparseModelType::ValueType> { public: + typedef typename RegionModelChecker<typename SparseModelType::ValueType>::VariableType VariableType; + typedef typename storm::analysis::MonotonicityResult<VariableType>::Monotonicity Monotonicity; + SparseParameterLiftingModelChecker(); virtual ~SparseParameterLiftingModelChecker() = default; - + /*! * Analyzes the given region by means of parameter lifting. */ - virtual RegionResult analyzeRegion(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, RegionResultHypothesis const& hypothesis = RegionResultHypothesis::Unknown, RegionResult const& initialResult = RegionResult::Unknown, bool sampleVerticesOfRegion = false) override; + virtual RegionResult analyzeRegion(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, RegionResultHypothesis const& hypothesis = RegionResultHypothesis::Unknown, RegionResult const& initialResult = RegionResult::Unknown, bool sampleVerticesOfRegion = false, std::shared_ptr<storm::analysis::Order> reachabilityOrder = nullptr, std::shared_ptr<storm::analysis::LocalMonotonicityResult<typename RegionModelChecker<typename SparseModelType::ValueType>::VariableType>> localMonotonicityResult = nullptr) override; /*! * Analyzes the 2^#parameters corner points of the given region. @@ -43,9 +46,9 @@ namespace storm { * @param region the region on which parameter lifting is applied * @param dirForParameters The optimization direction for the parameter choices. If this is, e.g., minimize, then the returned result will be a lower bound for all results induced by the parameter evaluations inside the region. */ - std::unique_ptr<CheckResult> check(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters); + std::unique_ptr<CheckResult> check(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters, std::shared_ptr<storm::analysis::Order> reachabilityOrder = nullptr, std::shared_ptr<storm::analysis::LocalMonotonicityResult<typename RegionModelChecker<typename SparseModelType::ValueType>::VariableType>> localMonotonicityResult = nullptr); - std::unique_ptr<QuantitativeCheckResult<ConstantType>> getBound(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters); + std::unique_ptr<QuantitativeCheckResult<ConstantType>> getBound(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters, std::shared_ptr<storm::analysis::LocalMonotonicityResult<typename RegionModelChecker<typename SparseModelType::ValueType>::VariableType>> localMonotonicityResult = nullptr); virtual typename SparseModelType::ValueType getBoundAtInitState(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters) override; @@ -55,7 +58,8 @@ namespace storm { * The actual maximum (minimum) lies in the interval [v, v+precision] ([v-precision, v]) */ virtual std::pair<typename SparseModelType::ValueType, typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::Valuation> computeExtremalValue(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters, typename SparseModelType::ValueType const& precision) override; - + virtual bool checkExtremalValue(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters, typename SparseModelType::ValueType const& precision, typename SparseModelType::ValueType const& valueToCheck) override; + SparseModelType const& getConsideredParametricModel() const; CheckTask<storm::logic::Formula, ConstantType> const& getCurrentCheckTask() const; @@ -73,17 +77,28 @@ namespace storm { virtual storm::modelchecker::SparseInstantiationModelChecker<SparseModelType, ConstantType>& getInstantiationChecker() = 0; - - virtual std::unique_ptr<CheckResult> computeQuantitativeValues(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters) = 0; - - + virtual storm::modelchecker::SparseInstantiationModelChecker<SparseModelType, ConstantType>& getInstantiationCheckerSAT(); + virtual storm::modelchecker::SparseInstantiationModelChecker<SparseModelType, ConstantType>& getInstantiationCheckerVIO(); + + virtual std::unique_ptr<CheckResult> computeQuantitativeValues(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters, std::shared_ptr<storm::analysis::LocalMonotonicityResult<typename RegionModelChecker<typename SparseModelType::ValueType>::VariableType>> localMonotonicityResult = nullptr) = 0; + + std::shared_ptr<SparseModelType> parametricModel; std::unique_ptr<CheckTask<storm::logic::Formula, ConstantType>> currentCheckTask; + ConstantType lastValue; + boost::optional<storm::analysis::OrderExtender<typename SparseModelType::ValueType, ConstantType>> orderExtender; + + std::pair<typename SparseModelType::ValueType, typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::Valuation> checkForPossibleMonotonicity(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, std::set<VariableType>& possibleMonotoneIncrParameters, std::set<VariableType>& possibleMonotoneDecrParameters, std::set<VariableType>& possibleNotMonotoneParameters, std::set<VariableType>const& consideredVariables, storm::solver::OptimizationDirection const& dir); + std::pair<typename SparseModelType::ValueType, typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::Valuation> getGoodInitialPoint(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dir, std::shared_ptr<storm::analysis::LocalMonotonicityResult<VariableType>> localMonRes); + std::set<VariableType> possibleMonotoneParameters; private: // store the current formula. Note that currentCheckTask only stores a reference to the formula. std::shared_ptr<storm::logic::Formula const> currentFormula; - + std::shared_ptr<storm::analysis::Order> copyOrder(std::shared_ptr<storm::analysis::Order> order); + std::map<std::shared_ptr<storm::analysis::Order>, uint_fast64_t> numberOfCopiesOrder; + std::map<std::shared_ptr<storm::analysis::LocalMonotonicityResult<VariableType>>, uint_fast64_t> numberOfCopiesMonRes; + std::pair<typename SparseModelType::ValueType, typename storm::storage::ParameterRegion<typename SparseModelType::ValueType>::Valuation> computeExtremalValue(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, storm::solver::OptimizationDirection const& dirForParameters, typename SparseModelType::ValueType const& precision, boost::optional<ConstantType> const& initialValue); }; } } diff --git a/src/storm-pars/modelchecker/region/ValidatingSparseDtmcParameterLiftingModelChecker.cpp b/src/storm-pars/modelchecker/region/ValidatingSparseDtmcParameterLiftingModelChecker.cpp index 2e9bf221b..f79f3059d 100644 --- a/src/storm-pars/modelchecker/region/ValidatingSparseDtmcParameterLiftingModelChecker.cpp +++ b/src/storm-pars/modelchecker/region/ValidatingSparseDtmcParameterLiftingModelChecker.cpp @@ -13,20 +13,20 @@ namespace storm { ValidatingSparseDtmcParameterLiftingModelChecker<SparseModelType, ImpreciseType, PreciseType>::ValidatingSparseDtmcParameterLiftingModelChecker() { // Intentionally left empty } - + template <typename SparseModelType, typename ImpreciseType, typename PreciseType> void ValidatingSparseDtmcParameterLiftingModelChecker<SparseModelType, ImpreciseType, PreciseType>::specify(Environment const& env, std::shared_ptr<storm::models::ModelBase> parametricModel, CheckTask<storm::logic::Formula, typename SparseModelType::ValueType> const& checkTask, bool generateRegionSplitEstimates, bool allowModelSimplifications) { STORM_LOG_ASSERT(this->canHandle(parametricModel, checkTask), "specified model and formula can not be handled by this."); - + auto dtmc = parametricModel->template as<SparseModelType>(); auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<SparseModelType>(*dtmc); if (!simplifier.simplify(checkTask.getFormula())) { STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Simplifying the model was not successfull."); } - + auto simplifiedTask = checkTask.substituteFormula(*simplifier.getSimplifiedFormula()); - + impreciseChecker.specify(env, simplifier.getSimplifiedModel(), simplifiedTask, false, true); preciseChecker.specify(env, simplifier.getSimplifiedModel(), simplifiedTask, false, true); } diff --git a/src/storm-pars/modelchecker/region/ValidatingSparseDtmcParameterLiftingModelChecker.h b/src/storm-pars/modelchecker/region/ValidatingSparseDtmcParameterLiftingModelChecker.h index e99792eed..a7182b08c 100644 --- a/src/storm-pars/modelchecker/region/ValidatingSparseDtmcParameterLiftingModelChecker.h +++ b/src/storm-pars/modelchecker/region/ValidatingSparseDtmcParameterLiftingModelChecker.h @@ -11,7 +11,7 @@ namespace storm { public: ValidatingSparseDtmcParameterLiftingModelChecker(); virtual ~ValidatingSparseDtmcParameterLiftingModelChecker() = default; - + virtual void specify(Environment const& env, std::shared_ptr<storm::models::ModelBase> parametricModel, CheckTask<storm::logic::Formula, typename SparseModelType::ValueType> const& checkTask, bool generateRegionSplitEstimates = false, bool allowModelSimplifications = true) override; protected: diff --git a/src/storm-pars/modelchecker/region/ValidatingSparseMdpParameterLiftingModelChecker.cpp b/src/storm-pars/modelchecker/region/ValidatingSparseMdpParameterLiftingModelChecker.cpp index 444dd334c..47414ef57 100644 --- a/src/storm-pars/modelchecker/region/ValidatingSparseMdpParameterLiftingModelChecker.cpp +++ b/src/storm-pars/modelchecker/region/ValidatingSparseMdpParameterLiftingModelChecker.cpp @@ -13,21 +13,21 @@ namespace storm { ValidatingSparseMdpParameterLiftingModelChecker<SparseModelType, ImpreciseType, PreciseType>::ValidatingSparseMdpParameterLiftingModelChecker() { // Intentionally left empty } - - + + template <typename SparseModelType, typename ImpreciseType, typename PreciseType> void ValidatingSparseMdpParameterLiftingModelChecker<SparseModelType, ImpreciseType, PreciseType>::specify(Environment const& env, std::shared_ptr<storm::models::ModelBase> parametricModel, CheckTask<storm::logic::Formula, typename SparseModelType::ValueType> const& checkTask, bool generateRegionSplitEstimates, bool allowModelSimplifications) { STORM_LOG_ASSERT(this->canHandle(parametricModel, checkTask), "specified model and formula can not be handled by this."); - + auto mdp = parametricModel->template as<SparseModelType>(); auto simplifier = storm::transformer::SparseParametricMdpSimplifier<SparseModelType>(*mdp); if (!simplifier.simplify(checkTask.getFormula())) { STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Simplifying the model was not successfull."); } - + auto simplifiedTask = checkTask.substituteFormula(*simplifier.getSimplifiedFormula()); - + impreciseChecker.specify(env, simplifier.getSimplifiedModel(), simplifiedTask, false, true); preciseChecker.specify(env, simplifier.getSimplifiedModel(), simplifiedTask, false, true); } diff --git a/src/storm-pars/modelchecker/region/ValidatingSparseMdpParameterLiftingModelChecker.h b/src/storm-pars/modelchecker/region/ValidatingSparseMdpParameterLiftingModelChecker.h index 9147e574e..e41bf5b8d 100644 --- a/src/storm-pars/modelchecker/region/ValidatingSparseMdpParameterLiftingModelChecker.h +++ b/src/storm-pars/modelchecker/region/ValidatingSparseMdpParameterLiftingModelChecker.h @@ -11,7 +11,7 @@ namespace storm { public: ValidatingSparseMdpParameterLiftingModelChecker(); virtual ~ValidatingSparseMdpParameterLiftingModelChecker() = default; - + virtual void specify(Environment const& env, std::shared_ptr<storm::models::ModelBase> parametricModel, CheckTask<storm::logic::Formula, typename SparseModelType::ValueType> const& checkTask, bool generateRegionSplitEstimates = false, bool allowModelSimplifications = true) override; protected: diff --git a/src/storm-pars/modelchecker/region/ValidatingSparseParameterLiftingModelChecker.cpp b/src/storm-pars/modelchecker/region/ValidatingSparseParameterLiftingModelChecker.cpp index 4065f236b..4532e40fe 100644 --- a/src/storm-pars/modelchecker/region/ValidatingSparseParameterLiftingModelChecker.cpp +++ b/src/storm-pars/modelchecker/region/ValidatingSparseParameterLiftingModelChecker.cpp @@ -28,9 +28,9 @@ namespace storm { } template <typename SparseModelType, typename ImpreciseType, typename PreciseType> - RegionResult ValidatingSparseParameterLiftingModelChecker<SparseModelType, ImpreciseType, PreciseType>::analyzeRegion(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, RegionResultHypothesis const& hypothesis, RegionResult const& initialResult, bool sampleVerticesOfRegion) { + RegionResult ValidatingSparseParameterLiftingModelChecker<SparseModelType, ImpreciseType, PreciseType>::analyzeRegion(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, RegionResultHypothesis const& hypothesis, RegionResult const& initialResult, bool sampleVerticesOfRegion, std::shared_ptr<storm::analysis::Order> reachabilityOrder, std::shared_ptr<storm::analysis::LocalMonotonicityResult<typename RegionModelChecker<typename SparseModelType::ValueType>::VariableType>> localMonotonicityResult) { + - RegionResult currentResult = getImpreciseChecker().analyzeRegion(env, region, hypothesis, initialResult, false); if (currentResult == RegionResult::AllSat || currentResult == RegionResult::AllViolated) { diff --git a/src/storm-pars/modelchecker/region/ValidatingSparseParameterLiftingModelChecker.h b/src/storm-pars/modelchecker/region/ValidatingSparseParameterLiftingModelChecker.h index f0dbf3534..09dcf4c64 100644 --- a/src/storm-pars/modelchecker/region/ValidatingSparseParameterLiftingModelChecker.h +++ b/src/storm-pars/modelchecker/region/ValidatingSparseParameterLiftingModelChecker.h @@ -23,7 +23,7 @@ namespace storm { * We first apply unsound solution methods (standard value iteratio with doubles) and then validate the obtained result * by means of exact and soud methods. */ - virtual RegionResult analyzeRegion(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, RegionResultHypothesis const& hypothesis = RegionResultHypothesis::Unknown, RegionResult const& initialResult = RegionResult::Unknown, bool sampleVerticesOfRegion = false) override; + virtual RegionResult analyzeRegion(Environment const& env, storm::storage::ParameterRegion<typename SparseModelType::ValueType> const& region, RegionResultHypothesis const& hypothesis = RegionResultHypothesis::Unknown, RegionResult const& initialResult = RegionResult::Unknown, bool sampleVerticesOfRegion = false, std::shared_ptr<storm::analysis::Order> reachabilityOrder = nullptr, std::shared_ptr<storm::analysis::LocalMonotonicityResult<typename RegionModelChecker<typename SparseModelType::ValueType>::VariableType>> localMonotonicityResult = nullptr) override; protected: diff --git a/src/storm-pars/parser/MonotonicityParser.cpp b/src/storm-pars/parser/MonotonicityParser.cpp new file mode 100644 index 000000000..b3d2e19b7 --- /dev/null +++ b/src/storm-pars/parser/MonotonicityParser.cpp @@ -0,0 +1,73 @@ +#include "storm-pars/parser/MonotonicityParser.h" +#include <storm/exceptions/WrongFormatException.h> + +#include "storm/utility/macros.h" +#include "storm/exceptions/InvalidArgumentException.h" +#include "storm/utility/constants.h" +#include "storm/io/file.h" + +namespace storm { + namespace parser { + + template<typename VariableType> + std::pair<std::set<VariableType>, std::set<VariableType>> MonotonicityParser<VariableType>::parseMonotoneVariablesFromFile(std::string const& fileName, std::set<VariableType> const& consideredVariables) { + + // Open file and initialize result. + std::ifstream inputFileStream; + storm::utility::openFile(fileName, inputFileStream); + + std::set<VariableType> monotoneIncrVars; + std::set<VariableType> monotoneDecrVars; + + // Now try to parse the contents of the file. + try { + std::string fileContent((std::istreambuf_iterator<char>(inputFileStream)), (std::istreambuf_iterator<char>())); + std::vector<std::string> fileSplitted; + + boost::split(fileSplitted, fileContent, boost::is_any_of(";")); + STORM_LOG_THROW(fileSplitted.size() == 2, storm::exceptions::WrongFormatException, "Expecting content to contain \";\" between monotone variables"); + std::vector<std::string> monotoneIncrVarsString; + boost::split(monotoneIncrVarsString, fileSplitted[0], boost::is_any_of(" ")); + std::vector<std::string> monotoneDecrVarsString; + boost::split(monotoneDecrVarsString, fileSplitted[0], boost::is_any_of(" ")); + // TODO: throw errors if file not formatted correctly + for (auto varString : monotoneIncrVarsString) { + VariableType var; + for (auto const& v : consideredVariables) { + std::stringstream stream; + stream << v; + if (varString == stream.str()) { + var = v; + break; + } + } + monotoneIncrVars.insert(var); + } + for (auto varString : monotoneDecrVarsString) { + VariableType var; + for (auto const& v : consideredVariables) { + std::stringstream stream; + stream << v; + if (varString == stream.str()) { + var = v; + break; + } + } + monotoneDecrVars.insert(var); + } + + } catch(std::exception& e) { + // In case of an exception properly close the file before passing exception. + storm::utility::closeFile(inputFileStream); + throw e; + } + + // Close the stream in case everything went smoothly and return result. + storm::utility::closeFile(inputFileStream); + return {std::move(monotoneIncrVars), std::move(monotoneDecrVars)}; + } + + + template class MonotonicityParser<storm::RationalFunctionVariable>; + } +} \ No newline at end of file diff --git a/src/storm-pars/parser/MonotonicityParser.h b/src/storm-pars/parser/MonotonicityParser.h new file mode 100644 index 000000000..1b3309ba1 --- /dev/null +++ b/src/storm-pars/parser/MonotonicityParser.h @@ -0,0 +1,16 @@ +#pragma once + +#include <set> +#include <string> + +namespace storm { + namespace parser { + template<typename VariableType> + class MonotonicityParser{ + public: + static std::pair<std::set<VariableType>, std::set<VariableType>> parseMonotoneVariablesFromFile(std::string const& fileName, std::set<VariableType> const& consideredVariables); + }; + } +} + + diff --git a/src/storm-pars/parser/ParameterRegionParser.cpp b/src/storm-pars/parser/ParameterRegionParser.cpp index 4c293ab54..4dcb95708 100644 --- a/src/storm-pars/parser/ParameterRegionParser.cpp +++ b/src/storm-pars/parser/ParameterRegionParser.cpp @@ -42,7 +42,7 @@ namespace storm { } template<typename ParametricType> - storm::storage::ParameterRegion<ParametricType> ParameterRegionParser<ParametricType>::parseRegion(std::string const& regionString, std::set<VariableType> const& consideredVariables) { + storm::storage::ParameterRegion<ParametricType> ParameterRegionParser<ParametricType>::parseRegion(std::string const& regionString, std::set<VariableType> const& consideredVariables, boost::optional<int> splittingThreshold) { Valuation lowerBoundaries; Valuation upperBoundaries; std::vector<std::string> parameterBoundaries; @@ -58,24 +58,46 @@ namespace storm { STORM_LOG_THROW(lowerBoundaries.count(v) > 0, storm::exceptions::WrongFormatException, "Variable " << v << " was not defined in region string."); STORM_LOG_ASSERT(upperBoundaries.count(v) > 0, "Variable " << v << " has a lower but not an upper bound."); } - return storm::storage::ParameterRegion<ParametricType>(std::move(lowerBoundaries), std::move(upperBoundaries)); + auto res = storm::storage::ParameterRegion<ParametricType>(std::move(lowerBoundaries), std::move(upperBoundaries)); + if (splittingThreshold) { + res.setSplitThreshold(splittingThreshold.get()); + } + return res; + } + + template<typename ParametricType> + storm::storage::ParameterRegion<ParametricType> ParameterRegionParser<ParametricType>::createRegion(std::string const& regionBound, std::set<VariableType> const& consideredVariables, boost::optional<int> splittingThreshold) { + Valuation lowerBoundaries; + Valuation upperBoundaries; + std::vector<std::string> parameterBoundaries; + CoefficientType bound = storm::utility::convertNumber<CoefficientType>(regionBound); + STORM_LOG_THROW(0 < bound && bound < 1, storm::exceptions::WrongFormatException, "Bound must be between 0 and 1, " << bound << " is not."); + for (auto const& v : consideredVariables) { + lowerBoundaries.emplace(std::make_pair(v, 0+bound)); + upperBoundaries.emplace(std::make_pair(v, 1-bound)); + } + auto res = storm::storage::ParameterRegion<ParametricType>(std::move(lowerBoundaries), std::move(upperBoundaries)); + if (splittingThreshold) { + res.setSplitThreshold(splittingThreshold.get()); + } + return res; } template<typename ParametricType> - std::vector<storm::storage::ParameterRegion<ParametricType>> ParameterRegionParser<ParametricType>::parseMultipleRegions(std::string const& regionsString, std::set<VariableType> const& consideredVariables) { + std::vector<storm::storage::ParameterRegion<ParametricType>> ParameterRegionParser<ParametricType>::parseMultipleRegions(std::string const& regionsString, std::set<VariableType> const& consideredVariables, boost::optional<int> splittingThreshold) { std::vector<storm::storage::ParameterRegion<ParametricType>> result; std::vector<std::string> regionsStrVec; boost::split(regionsStrVec, regionsString, boost::is_any_of(";")); for (auto const& regionStr : regionsStrVec){ if (!std::all_of(regionStr.begin(),regionStr.end(), ::isspace)){ //skip this string if it only consists of space - result.emplace_back(parseRegion(regionStr, consideredVariables)); + result.emplace_back(parseRegion(regionStr, consideredVariables, splittingThreshold)); } } return result; } template<typename ParametricType> - std::vector<storm::storage::ParameterRegion<ParametricType>> ParameterRegionParser<ParametricType>::parseMultipleRegionsFromFile(std::string const& fileName, std::set<VariableType> const& consideredVariables) { + std::vector<storm::storage::ParameterRegion<ParametricType>> ParameterRegionParser<ParametricType>::parseMultipleRegionsFromFile(std::string const& fileName, std::set<VariableType> const& consideredVariables, boost::optional<int> splittingThreshold) { // Open file and initialize result. std::ifstream inputFileStream; @@ -86,7 +108,7 @@ namespace storm { // Now try to parse the contents of the file. try { std::string fileContent((std::istreambuf_iterator<char>(inputFileStream)), (std::istreambuf_iterator<char>())); - result = parseMultipleRegions(fileContent, consideredVariables); + result = parseMultipleRegions(fileContent, consideredVariables, splittingThreshold); } catch(std::exception& e) { // In case of an exception properly close the file before passing exception. storm::utility::closeFile(inputFileStream); diff --git a/src/storm-pars/parser/ParameterRegionParser.h b/src/storm-pars/parser/ParameterRegionParser.h index 99063a4d6..fb574248e 100644 --- a/src/storm-pars/parser/ParameterRegionParser.h +++ b/src/storm-pars/parser/ParameterRegionParser.h @@ -25,20 +25,21 @@ namespace storm { * Parse a single region from a string of the form "0.3<=p<=0.5,0.4<=q<=0.7". * */ - static storm::storage::ParameterRegion<ParametricType> parseRegion(std::string const& regionString, std::set<VariableType> const& consideredVariables); + static storm::storage::ParameterRegion<ParametricType> parseRegion(std::string const& regionString, std::set<VariableType> const& consideredVariables, boost::optional<int> splittingThreshold = boost::none); + static storm::storage::ParameterRegion<ParametricType> createRegion(std::string const& regionBound, std::set<VariableType> const& consideredVariables, boost::optional<int> splittingThreshold = boost::none); /* * Parse a vector of region from a string of the form "0.3<=p<=0.5,0.4<=q<=0.7;0.1<=p<=0.3,0.2<=q<=0.4". * */ - static std::vector<storm::storage::ParameterRegion<ParametricType>> parseMultipleRegions(std::string const& regionsString, std::set<VariableType> const& consideredVariables); + static std::vector<storm::storage::ParameterRegion<ParametricType>> parseMultipleRegions(std::string const& regionsString, std::set<VariableType> const& consideredVariables, boost::optional<int> splittingThreshold = boost::none); /* * Parse multiple regions from a file * */ - static std::vector<storm::storage::ParameterRegion<ParametricType>> parseMultipleRegionsFromFile(std::string const& fileName, std::set<VariableType> const& consideredVariables); + static std::vector<storm::storage::ParameterRegion<ParametricType>> parseMultipleRegionsFromFile(std::string const& fileName, std::set<VariableType> const& consideredVariables, boost::optional<int> splittingThreshold = boost::none); }; } diff --git a/src/storm-pars/settings/modules/MonotonicitySettings.cpp b/src/storm-pars/settings/modules/MonotonicitySettings.cpp index dd7a91b80..764012972 100644 --- a/src/storm-pars/settings/modules/MonotonicitySettings.cpp +++ b/src/storm-pars/settings/modules/MonotonicitySettings.cpp @@ -11,43 +11,83 @@ namespace storm { namespace settings { namespace modules { - - const std::string MonotonicitySettings::moduleName = "monotonicity"; + // TODO @Svenja, check what the module prefix is, maybe instead of doing mon- we could set this to true for the onces where we now have mon-"optionname" + const std::string MonotonicitySettings::moduleName = "mon"; const std::string MonotonicitySettings::monotonicityAnalysis = "monotonicity-analysis"; - const std::string MonotonicitySettings::sccElimination = "mon-elim-scc"; - const std::string MonotonicitySettings::validateAssumptions = "mon-validate-assumptions"; - const std::string MonotonicitySettings::samplesMonotonicityAnalysis = "mon-samples"; - const std::string MonotonicitySettings::precision = "mon-precision"; + const std::string MonotonicitySettings::monotonicityAnalysisShortName = "ma"; + const std::string MonotonicitySettings::usePLABounds = "useBounds"; + const std::string MonotonicitySettings::sccElimination = "eliminateSCCs"; + const std::string MonotonicitySettings::samplesMonotonicityAnalysis = "samples"; + + const std::string MonotonicitySettings::dotOutput = "dotOutput"; + const std::string MonotonicitySettings::exportMonotonicityName = "exportMonotonicity"; + const std::string MonotonicitySettings::monSolution ="solutionFunction"; + const std::string MonotonicitySettings::monSolutionShortName ="msf"; + const std::string MonotonicitySettings::monotonicityThreshold ="depth"; + + const std::string MonotonicitySettings::monotoneParameters ="parameters"; MonotonicitySettings::MonotonicitySettings() : ModuleSettings(moduleName) { - this->addOption(storm::settings::OptionBuilder(moduleName, monotonicityAnalysis, false, "Sets whether monotonicity analysis is done").setIsAdvanced().build()); - this->addOption(storm::settings::OptionBuilder(moduleName, sccElimination, false, "Sets whether SCCs should be eliminated in the monotonicity analysis").setIsAdvanced().build()); - this->addOption(storm::settings::OptionBuilder(moduleName, validateAssumptions, false, "Sets whether assumptions made in monotonicity analysis are validated").setIsAdvanced().build()); - this->addOption(storm::settings::OptionBuilder(moduleName, samplesMonotonicityAnalysis, false, "Sets whether monotonicity should be checked on samples").setIsAdvanced() - .addArgument(storm::settings::ArgumentBuilder::createUnsignedIntegerArgument("mon-samples", "The number of samples taken in monotonicity-analysis can be given, default is 0, no samples").setDefaultValueUnsignedInteger(0).build()).build()); - this->addOption(storm::settings::OptionBuilder(moduleName, precision, false, "Sets precision of monotonicity checking on samples").setIsAdvanced() - .addArgument(storm::settings::ArgumentBuilder::createDoubleArgument("mon-precision", "The precision of checking monotonicity on samples, default is 1e-6").setDefaultValueDouble(0.000001).build()).build()); + this->addOption(storm::settings::OptionBuilder(moduleName, monotonicityAnalysis, false, "Sets whether monotonicity analysis is done").setIsAdvanced().setShortName(monotonicityAnalysisShortName).build()); + this->addOption(storm::settings::OptionBuilder(moduleName, usePLABounds, true, "Sets whether pla bounds should be used for monotonicity analysis").setIsAdvanced().build()); + this->addOption(storm::settings::OptionBuilder(moduleName, sccElimination, true, "Sets whether SCCs should be eliminated in the monotonicity analysis").setIsAdvanced().build()); + this->addOption(storm::settings::OptionBuilder(moduleName, samplesMonotonicityAnalysis, true, "Sets whether monotonicity should be checked on samples").setIsAdvanced() + .addArgument(storm::settings::ArgumentBuilder::createUnsignedIntegerArgument(samplesMonotonicityAnalysis, "The number of samples taken in monotonicity-analysis can be given, default is 0, no samples").setDefaultValueUnsignedInteger(0).build()).build()); + this->addOption(storm::settings::OptionBuilder(moduleName, monSolution, true, "Sets whether monotonicity should be checked on solution function or reachability order").setIsAdvanced().setShortName(monSolutionShortName).build()); + this->addOption(storm::settings::OptionBuilder(moduleName, dotOutput, true, "Sets whether a dot output of the ROs is needed").setIsAdvanced().addArgument(storm::settings::ArgumentBuilder::createStringArgument("dotFilename", "The output file.").setDefaultValueString("dotOutput").makeOptional().build()).build()); + this->addOption(storm::settings::OptionBuilder(moduleName, exportMonotonicityName, true, "Exports the result of monotonicity checking to the given file.").setIsAdvanced().addArgument(storm::settings::ArgumentBuilder::createStringArgument("filename", "The output file.").build()).build()); + this->addOption(storm::settings::OptionBuilder(moduleName, monotonicityThreshold, true, "Sets for region refinement after which depth whether monotonicity checking should be used.").setIsAdvanced() + .addArgument(storm::settings::ArgumentBuilder::createUnsignedIntegerArgument(monotonicityThreshold, "The depth threshold from which on monotonicity is used for Parameter Lifting").setDefaultValueUnsignedInteger(0).build()).build()); + this->addOption(storm::settings::OptionBuilder(moduleName, monotoneParameters, true, "Sets monotone parameters from file.").setIsAdvanced().addArgument(storm::settings::ArgumentBuilder::createStringArgument("monotoneParametersFilename", "The file where the monotone parameters are set").build()).build()); } bool MonotonicitySettings::isMonotonicityAnalysisSet() const { return this->getOption(monotonicityAnalysis).getHasOptionBeenSet(); } + bool MonotonicitySettings::isUsePLABoundsSet() const { + return this->getOption(usePLABounds).getHasOptionBeenSet(); + } + bool MonotonicitySettings::isSccEliminationSet() const { return this->getOption(sccElimination).getHasOptionBeenSet(); } - bool MonotonicitySettings::isValidateAssumptionsSet() const { - return this->getOption(validateAssumptions).getHasOptionBeenSet(); + bool MonotonicitySettings::isDotOutputSet() const { + return this->getOption(dotOutput).getHasOptionBeenSet(); + } + + bool MonotonicitySettings::isMonotoneParametersSet() const { + return this->getOption(monotoneParameters).getHasOptionBeenSet(); + } + + std::string MonotonicitySettings::getDotOutputFilename() const { + return this->getOption(dotOutput).getArgumentByName("dotFilename").getValueAsString(); + } + + std::string MonotonicitySettings::getMonotoneParameterFilename() const { + return this->getOption(monotoneParameters).getArgumentByName("monotoneParametersFilename").getValueAsString(); } uint_fast64_t MonotonicitySettings::getNumberOfSamples() const { - return this->getOption(samplesMonotonicityAnalysis).getArgumentByName("mon-samples").getValueAsUnsignedInteger(); + return this->getOption(samplesMonotonicityAnalysis).getArgumentByName("samples").getValueAsUnsignedInteger(); + } + + bool MonotonicitySettings::isExportMonotonicitySet() const { + return this->getOption(exportMonotonicityName).getHasOptionBeenSet(); + } + + std::string MonotonicitySettings::getExportMonotonicityFilename() const { + return this->getOption(exportMonotonicityName).getArgumentByName("filename").getValueAsString(); + } + + uint_fast64_t MonotonicitySettings::getMonotonicityThreshold() const { + return this->getOption(monotonicityThreshold).getArgumentByName("depth").getValueAsUnsignedInteger(); } - double MonotonicitySettings::getMonotonicityAnalysisPrecision() const { - return this->getOption(precision).getArgumentByName("mon-precision").getValueAsDouble(); + bool MonotonicitySettings::isMonSolutionSet() const { + return this->getOption(monSolution).getHasOptionBeenSet(); } } // namespace modules } // namespace settings diff --git a/src/storm-pars/settings/modules/MonotonicitySettings.h b/src/storm-pars/settings/modules/MonotonicitySettings.h index f420935cd..046628959 100644 --- a/src/storm-pars/settings/modules/MonotonicitySettings.h +++ b/src/storm-pars/settings/modules/MonotonicitySettings.h @@ -23,15 +23,26 @@ namespace storm { */ bool isMonotonicityAnalysisSet() const; + bool isUsePLABoundsSet() const; + /*! * Retrieves whether SCCs in the monotonicity analysis should be eliminated. */ bool isSccEliminationSet() const; /*! - * Retrieves whether assumptions in monotonicity analysis should be validated + * Retrieves whether a dot output of the reachability orders should be given + */ + bool isDotOutputSet() const; + + bool isMonotoneParametersSet() const; + + /*! + * Retrieves the name of the file for a possible dot output */ - bool isValidateAssumptionsSet() const; + std::string getDotOutputFilename() const; + + std::string getMonotoneParameterFilename() const; /*! * Retrieves the number of samples used for sampling in the monotonicity analysis @@ -39,18 +50,38 @@ namespace storm { uint_fast64_t getNumberOfSamples() const; /*! - * Retrieves the precision for the extremal value - */ - double getMonotonicityAnalysisPrecision() const; + * + */ + bool isExportMonotonicitySet() const; + + bool isMonSolutionSet() const; + + /*! + * + */ + std::string getExportMonotonicityFilename() const; + + /*! + * Retrieves the depth threshold from which on monotonicity should be used in parameter lifting + */ + uint64_t getMonotonicityThreshold() const; + const static std::string moduleName; private: const static std::string monotonicityAnalysis; + const static std::string monotonicityAnalysisShortName; + const static std::string usePLABounds; const static std::string sccElimination; - const static std::string validateAssumptions; const static std::string samplesMonotonicityAnalysis; - const static std::string precision; + const static std::string dotOutput; + static const std::string exportMonotonicityName; + const static std::string monotonicityThreshold; + const static std::string monotoneParameters; + const static std::string monSolution; + const static std::string monSolutionShortName; + }; } // namespace modules diff --git a/src/storm-pars/settings/modules/ParametricSettings.cpp b/src/storm-pars/settings/modules/ParametricSettings.cpp index 45528844c..b558f5b03 100644 --- a/src/storm-pars/settings/modules/ParametricSettings.cpp +++ b/src/storm-pars/settings/modules/ParametricSettings.cpp @@ -21,6 +21,8 @@ namespace storm { const std::string ParametricSettings::samplesOptionName = "samples"; const std::string ParametricSettings::samplesGraphPreservingOptionName = "samples-graph-preserving"; const std::string ParametricSettings::sampleExactOptionName = "sample-exact"; + const std::string ParametricSettings::useMonotonicityName = "use-monotonicity"; +// const std::string ParametricSettings::onlyGlobalName = "onlyGlobal"; ParametricSettings::ParametricSettings() : ModuleSettings(moduleName) { this->addOption(storm::settings::OptionBuilder(moduleName, exportResultOptionName, false, "A path to a file where the parametric result should be saved.") @@ -32,6 +34,8 @@ namespace storm { .addArgument(storm::settings::ArgumentBuilder::createStringArgument("samples", "The samples are semicolon-separated entries of the form 'Var1=Val1:Val2:...:Valk,Var2=... that span the sample spaces.").setDefaultValueString("").build()).build()); this->addOption(storm::settings::OptionBuilder(moduleName, samplesGraphPreservingOptionName, false, "Sets whether it can be assumed that the samples are graph-preserving.").build()); this->addOption(storm::settings::OptionBuilder(moduleName, sampleExactOptionName, false, "Sets whether to sample using exact arithmetic.").build()); + this->addOption(storm::settings::OptionBuilder(moduleName, useMonotonicityName, false, "If set, monotonicity will be used.").build()); +// this->addOption(storm::settings::OptionBuilder(moduleName, onlyGlobalName, false, "If set, only global monotonicity will be used.").build()); } bool ParametricSettings::exportResultToFile() const { @@ -66,6 +70,13 @@ namespace storm { return this->getOption(sampleExactOptionName).getHasOptionBeenSet(); } + bool ParametricSettings::isUseMonotonicitySet() const { + return this->getOption(useMonotonicityName).getHasOptionBeenSet(); + } +// bool ParametricSettings::isOnlyGlobalSet() const { +// return this->getOption(onlyGlobalName).getHasOptionBeenSet(); +// } + } // namespace modules } // namespace settings } // namespace storm diff --git a/src/storm-pars/settings/modules/ParametricSettings.h b/src/storm-pars/settings/modules/ParametricSettings.h index 3fe281e4e..88e3d5d8d 100644 --- a/src/storm-pars/settings/modules/ParametricSettings.h +++ b/src/storm-pars/settings/modules/ParametricSettings.h @@ -64,6 +64,13 @@ namespace storm { */ bool isSampleExactSet() const; + /*! + * Retrieves whether monotonicity should be used + */ + bool isUseMonotonicitySet() const; + +// bool isOnlyGlobalSet() const; + const static std::string moduleName; private: @@ -75,6 +82,9 @@ namespace storm { const static std::string samplesOptionName; const static std::string samplesGraphPreservingOptionName; const static std::string sampleExactOptionName; + const static std::string useMonotonicityName; +// const static std::string onlyGlobalName; + }; } // namespace modules diff --git a/src/storm-pars/settings/modules/RegionSettings.cpp b/src/storm-pars/settings/modules/RegionSettings.cpp index cad9f4abe..abb6039a9 100644 --- a/src/storm-pars/settings/modules/RegionSettings.cpp +++ b/src/storm-pars/settings/modules/RegionSettings.cpp @@ -1,10 +1,11 @@ #include <storm-pars/modelchecker/region/RegionResultHypothesis.h> #include "storm-pars/settings/modules/RegionSettings.h" -#include "storm/settings/Option.h" +#include "storm/settings/SettingsManager.h" +#include "storm/settings/modules/GeneralSettings.h" + #include "storm/settings/OptionBuilder.h" #include "storm/settings/ArgumentBuilder.h" -#include "storm/settings/Argument.h" #include "storm/utility/macros.h" #include "storm/exceptions/IllegalArgumentValueException.h" @@ -17,10 +18,13 @@ namespace storm { const std::string RegionSettings::moduleName = "region"; const std::string RegionSettings::regionOptionName = "region"; const std::string RegionSettings::regionShortOptionName = "reg"; + const std::string RegionSettings::regionBoundOptionName = "regionbound"; const std::string RegionSettings::hypothesisOptionName = "hypothesis"; const std::string RegionSettings::hypothesisShortOptionName = "hyp"; const std::string RegionSettings::refineOptionName = "refine"; const std::string RegionSettings::extremumOptionName = "extremum"; + const std::string RegionSettings::extremumSuggestionOptionName = "extremum-init"; + const std::string RegionSettings::splittingThresholdName = "splitting-threshold"; const std::string RegionSettings::checkEngineOptionName = "engine"; const std::string RegionSettings::printNoIllustrationOptionName = "noillustration"; const std::string RegionSettings::printFullResultOptionName = "printfullresult"; @@ -28,7 +32,10 @@ namespace storm { RegionSettings::RegionSettings() : ModuleSettings(moduleName) { this->addOption(storm::settings::OptionBuilder(moduleName, regionOptionName, false, "Sets the region(s) considered for analysis.").setShortName(regionShortOptionName) .addArgument(storm::settings::ArgumentBuilder::createStringArgument("regioninput", "The region(s) given in format a<=x<=b,c<=y<=d seperated by ';'. Can also be a file.").build()).build()); - + + this->addOption(storm::settings::OptionBuilder(moduleName, regionBoundOptionName, false, "Sets the region bound considered for analysis.") + .addArgument(storm::settings::ArgumentBuilder::createStringArgument("regionbound", "The bound for the region result for all variables: 0+bound <= var <=1-bound").build()).build()); + std::vector<std::string> hypotheses = {"unknown", "allsat", "allviolated"}; this->addOption(storm::settings::OptionBuilder(moduleName, hypothesisOptionName, false, "Sets a hypothesis for region analysis. If given, the region(s) are only analyzed w.r.t. that hypothesis.").setShortName(hypothesisShortOptionName) .addArgument(storm::settings::ArgumentBuilder::createStringArgument("hypothesis", "The hypothesis.").addValidatorString(ArgumentValidatorFactory::createMultipleChoiceValidator(hypotheses)).setDefaultValueString("unknown").build()).build()); @@ -41,7 +48,13 @@ namespace storm { this->addOption(storm::settings::OptionBuilder(moduleName, extremumOptionName, false, "Computes the extremum within the region.") .addArgument(storm::settings::ArgumentBuilder::createStringArgument("direction", "The optimization direction").addValidatorString(storm::settings::ArgumentValidatorFactory::createMultipleChoiceValidator(directions)).build()) .addArgument(storm::settings::ArgumentBuilder::createDoubleArgument("precision", "The desired precision").setDefaultValueDouble(0.05).makeOptional().addValidatorDouble(storm::settings::ArgumentValidatorFactory::createDoubleRangeValidatorIncluding(0.0,1.0)).build()).build()); - + + this->addOption(storm::settings::OptionBuilder(moduleName, extremumSuggestionOptionName, false, "Checks whether the provided value is indeed the extremum") + .addArgument(storm::settings::ArgumentBuilder::createDoubleArgument("extremum-suggestion", "The provided value for the extremum").addValidatorDouble(storm::settings::ArgumentValidatorFactory::createDoubleRangeValidatorIncluding(0.0,1.0)).build()).build()); + + this->addOption(storm::settings::OptionBuilder(moduleName, splittingThresholdName, false, "Sets the threshold for number of parameters in which to split regions.") + .addArgument(storm::settings::ArgumentBuilder::createIntegerArgument("splitting-threshold", "The threshold for splitting, should be an integer > 0").build()).build()); + std::vector<std::string> engines = {"pl", "exactpl", "validatingpl"}; this->addOption(storm::settings::OptionBuilder(moduleName, checkEngineOptionName, true, "Sets which engine is used for analyzing regions.") .addArgument(storm::settings::ArgumentBuilder::createStringArgument("name", "The name of the engine to use.").addValidatorString(ArgumentValidatorFactory::createMultipleChoiceValidator(engines)).setDefaultValueString("pl").build()).build()); @@ -58,6 +71,14 @@ namespace storm { std::string RegionSettings::getRegionString() const { return this->getOption(regionOptionName).getArgumentByName("regioninput").getValueAsString(); } + + bool RegionSettings::isRegionBoundSet() const { + return this->getOption(regionBoundOptionName).getHasOptionBeenSet(); + } + + std::string RegionSettings::getRegionBoundString() const { + return this->getOption(regionBoundOptionName).getArgumentByName("regionbound").getValueAsString(); + } bool RegionSettings::isHypothesisSet() const { return this->getOption(hypothesisOptionName).getHasOptionBeenSet(); @@ -114,9 +135,23 @@ namespace storm { } double RegionSettings::getExtremumValuePrecision() const { + auto generalSettings = storm::settings::getModule<storm::settings::modules::GeneralSettings>(); + if (!generalSettings.isPrecisionSet() && generalSettings.isSoundSet()) { + double prec = this->getOption(extremumOptionName).getArgumentByName("precision").getValueAsDouble() / 10; + generalSettings.setPrecision(std::to_string(prec)); + STORM_LOG_WARN("Reset precision for solver to " << prec << " this is sufficient for extremum value precision of " << (prec)*10 << std::endl); + } return this->getOption(extremumOptionName).getArgumentByName("precision").getValueAsDouble(); } + bool RegionSettings::isExtremumSuggestionSet() const { + return this->getOption(extremumSuggestionOptionName).getHasOptionBeenSet(); + } + + double RegionSettings::getExtremumSuggestion() const { + return this->getOption(extremumSuggestionOptionName).getArgumentByName("extremum-suggestion").getValueAsDouble(); + } + storm::modelchecker::RegionCheckEngine RegionSettings::getRegionCheckEngine() const { std::string engineString = this->getOption(checkEngineOptionName).getArgumentByName("name").getValueAsString(); @@ -139,6 +174,10 @@ namespace storm { STORM_LOG_ERROR("Can not compute extremum values AND perform region refinement."); return false; } + if (getExtremumValuePrecision() < storm::settings::getModule<storm::settings::modules::GeneralSettings>().getPrecision()) { + STORM_LOG_ERROR("Computing extremum value for precision " << getExtremumValuePrecision() << " makes no sense when solver precision is set to " << storm::settings::getModule<storm::settings::modules::GeneralSettings>().getPrecision()); + return false; + } return true; } @@ -149,7 +188,15 @@ namespace storm { bool RegionSettings::isPrintFullResultSet() const { return this->getOption(printFullResultOptionName).getHasOptionBeenSet(); } - + + int RegionSettings::getSplittingThreshold() const { + return this->getOption(splittingThresholdName).getArgumentByName("splitting-threshold").getValueAsInteger(); + } + + bool RegionSettings::isSplittingThresholdSet() const { + return this->getOption(splittingThresholdName).getHasOptionBeenSet(); + } + } // namespace modules } // namespace settings diff --git a/src/storm-pars/settings/modules/RegionSettings.h b/src/storm-pars/settings/modules/RegionSettings.h index b129b19d7..3d1e10b28 100644 --- a/src/storm-pars/settings/modules/RegionSettings.h +++ b/src/storm-pars/settings/modules/RegionSettings.h @@ -30,7 +30,16 @@ namespace storm { * Retrieves the region definition string */ std::string getRegionString() const; - + + /*! + * Retrieves whether region bound is declared + */ + bool isRegionBoundSet() const; + + /*! + * Retrieves the region definition string + */ + std::string getRegionBoundString() const; /*! * Retrieves whether region(s) were declared */ @@ -77,6 +86,14 @@ namespace storm { */ double getExtremumValuePrecision() const; + bool isExtremumSuggestionSet() const; + + double getExtremumSuggestion() const; + + bool isSplittingThresholdSet() const; + + int getSplittingThreshold() const; + /*! * Retrieves which type of region check should be performed */ @@ -99,10 +116,13 @@ namespace storm { private: const static std::string regionOptionName; const static std::string regionShortOptionName; + const static std::string regionBoundOptionName; const static std::string hypothesisOptionName; const static std::string hypothesisShortOptionName; const static std::string refineOptionName; + const static std::string splittingThresholdName; const static std::string extremumOptionName; + const static std::string extremumSuggestionOptionName; const static std::string checkEngineOptionName; const static std::string printNoIllustrationOptionName; const static std::string printFullResultOptionName; diff --git a/src/storm-pars/storage/ParameterRegion.cpp b/src/storm-pars/storage/ParameterRegion.cpp index 71030ff4d..ba64bf30e 100644 --- a/src/storm-pars/storage/ParameterRegion.cpp +++ b/src/storm-pars/storage/ParameterRegion.cpp @@ -33,10 +33,12 @@ namespace storm { STORM_LOG_THROW((variableWithUpperBoundary != upperBoundaries.end()), storm::exceptions::InvalidArgumentException, "Could not create region. No upper boundary specified for Variable " << variableWithLowerBoundary.first); STORM_LOG_THROW((variableWithLowerBoundary.second<=variableWithUpperBoundary->second), storm::exceptions::InvalidArgumentException, "Could not create region. The lower boundary for " << variableWithLowerBoundary.first << " is larger then the upper boundary"); this->variables.insert(variableWithLowerBoundary.first); + this->sortedOnDifference.insert({variableWithLowerBoundary.second - variableWithUpperBoundary->second, variableWithLowerBoundary.first}); } for (auto const& variableWithBoundary : this->upperBoundaries) { STORM_LOG_THROW((this->variables.find(variableWithBoundary.first) != this->variables.end()), storm::exceptions::InvalidArgumentException, "Could not create region. No lower boundary specified for Variable " << variableWithBoundary.first); } + this->splitThreshold = variables.size(); } template<typename ParametricType> @@ -45,7 +47,12 @@ namespace storm { } template<typename ParametricType> - typename ParameterRegion<ParametricType>::CoefficientType const& ParameterRegion<ParametricType>::getLowerBoundary(VariableType const& variable) const { + std::multimap<typename ParameterRegion<ParametricType>::CoefficientType , typename ParameterRegion<ParametricType>::VariableType> const& ParameterRegion<ParametricType>::getVariablesSorted() const { + return this->sortedOnDifference; + } + + template<typename ParametricType> + typename ParameterRegion<ParametricType>::CoefficientType const& ParameterRegion<ParametricType>::getLowerBoundary(VariableType const& variable) const { auto const& result = lowerBoundaries.find(variable); STORM_LOG_THROW(result != lowerBoundaries.end(), storm::exceptions::InvalidArgumentException, "Tried to find a lower boundary for variable " << variable << " which is not specified by this region"); return (*result).second; @@ -78,6 +85,16 @@ namespace storm { STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Tried to find an upper boundary for variableName " << varName << " which is not specified by this region"); } + template<typename ParametricType> + typename ParameterRegion<ParametricType>::CoefficientType ParameterRegion<ParametricType>::getDifference(VariableType const& variable) const { + return getUpperBoundary(variable) - getLowerBoundary(variable); + } + + template<typename ParametricType> + typename ParameterRegion<ParametricType>::CoefficientType ParameterRegion<ParametricType>::getDifference(const std::string varName) const { + return getUpperBoundary(varName) - getLowerBoundary(varName); + } + template<typename ParametricType> typename ParameterRegion<ParametricType>::Valuation const& ParameterRegion<ParametricType>::getUpperBoundaries() const { return upperBoundaries; @@ -100,7 +117,8 @@ namespace storm { //the consideredVariables.size() least significant bits of vertex will always represent the next vertex //(00...0 = lower boundaries for all variables, 11...1 = upper boundaries for all variables) uint_fast64_t variableIndex = 0; - for (auto const& variable : consideredVariables) { + + for (auto variable : consideredVariables) { if ((vertexId >> variableIndex) % 2 == 0) { resultingVector[vertexId].insert(std::pair<VariableType, CoefficientType>(variable, getLowerBoundary(variable))); } else { @@ -112,6 +130,7 @@ namespace storm { return resultingVector; } + template<typename ParametricType> typename ParameterRegion<ParametricType>::Valuation ParameterRegion<ParametricType>::getSomePoint() const { return this->getLowerBoundaries(); @@ -136,30 +155,38 @@ namespace storm { } template<typename ParametricType> - void ParameterRegion<ParametricType>::split(Valuation const& splittingPoint, std::vector<ParameterRegion<ParametricType> >& regionVector) const{ - //Check if splittingPoint is valid. - STORM_LOG_THROW(splittingPoint.size() == this->variables.size(), storm::exceptions::InvalidArgumentException, "Tried to split a region w.r.t. a point, but the point considers a different number of variables."); - for(auto const& variable : this->variables){ - auto splittingPointEntry=splittingPoint.find(variable); - STORM_LOG_THROW(splittingPointEntry != splittingPoint.end(), storm::exceptions::InvalidArgumentException, "Tried to split a region but a variable of this region is not defined by the splitting point."); - STORM_LOG_THROW(this->getLowerBoundary(variable) <=splittingPointEntry->second, storm::exceptions::InvalidArgumentException, "Tried to split a region but the splitting point is not contained in the region."); - STORM_LOG_THROW(this->getUpperBoundary(variable) >=splittingPointEntry->second, storm::exceptions::InvalidArgumentException, "Tried to split a region but the splitting point is not contained in the region."); - } - - //Now compute the subregions. - std::vector<Valuation> vertices(this->getVerticesOfRegion(this->variables)); - for(auto const& vertex : vertices){ + void ParameterRegion<ParametricType>::split(Valuation const& splittingPoint, std::vector<ParameterRegion<ParametricType>>& regionVector) const{ + return split(splittingPoint, regionVector, variables); + } + + template<typename ParametricType> + void ParameterRegion<ParametricType>::split(Valuation const& splittingPoint, std::vector<storm::storage::ParameterRegion<ParametricType>> ®ionVector, + const std::set<VariableType> &consideredVariables) const { + + auto vertices = getVerticesOfRegion(consideredVariables); + + for (auto const &vertex : vertices) { //The resulting subregion is the smallest region containing vertex and splittingPoint. Valuation subLower, subUpper; - for(auto variableBound : this->lowerBoundaries){ + for (auto variableBound : this->lowerBoundaries) { VariableType variable = variableBound.first; - auto vertexEntry=vertex.find(variable); - auto splittingPointEntry=splittingPoint.find(variable); - subLower.insert(typename Valuation::value_type(variable, std::min(vertexEntry->second, splittingPointEntry->second))); - subUpper.insert(typename Valuation::value_type(variable, std::max(vertexEntry->second, splittingPointEntry->second))); + auto vertexEntry = vertex.find(variable); + if (vertexEntry != vertex.end()) { + auto splittingPointEntry = splittingPoint.find(variable); + subLower.insert(typename Valuation::value_type(variable, std::min(vertexEntry->second, + splittingPointEntry->second))); + subUpper.insert(typename Valuation::value_type(variable, std::max(vertexEntry->second, + splittingPointEntry->second))); + } else { + subLower.insert(typename Valuation::value_type(variable, getLowerBoundary(variable))); + subUpper.insert(typename Valuation::value_type(variable, getUpperBoundary(variable))); + } } + ParameterRegion<ParametricType> subRegion(std::move(subLower), std::move(subUpper)); - if(!storm::utility::isZero(subRegion.area())){ + subRegion.setSplitThreshold(this->getSplitThreshold()); + + if (!storm::utility::isZero(subRegion.area())) { regionVector.push_back(std::move(subRegion)); } } @@ -192,18 +219,84 @@ namespace storm { regionstring = regionstring.substr(0, regionstring.length() - 1) + ";"; return regionstring; } - + + template <typename ParametricType> + bool ParameterRegion<ParametricType>::isSubRegion(ParameterRegion<ParametricType> subRegion) { + auto varsRegion = getVariables(); + auto varsSubRegion = subRegion.getVariables(); + for (auto var : varsRegion) { + if (std::find(varsSubRegion.begin(), varsSubRegion.end(), var) != varsSubRegion.end()) { + if (getLowerBoundary(var) > subRegion.getLowerBoundary(var) || getUpperBoundary(var) < getUpperBoundary(var)) { + return false; + } + } else { + return false; + } + } + return true; + } + + template<typename ParametricType> + typename ParameterRegion<ParametricType>::Valuation ParameterRegion<ParametricType>::getPoint(storm::solver::OptimizationDirection dir, storm::analysis::MonotonicityResult<VariableType> &monRes) const { + auto val = this->getCenterPoint(); + for (auto monResEntry : monRes.getMonotonicityResult()) { + if (monRes.isDoneForVar(monResEntry.first)) { + if (monResEntry.second == storm::analysis::MonotonicityResult<VariableType>::Monotonicity::Incr) { + val[monResEntry.first] = storm::solver::minimize(dir) ? getLowerBoundary(monResEntry.first) : getUpperBoundary(monResEntry.first); + } else if (monResEntry.second == storm::analysis::MonotonicityResult<VariableType>::Monotonicity::Decr) { + val[monResEntry.first] = storm::solver::maximize(dir) ? getLowerBoundary(monResEntry.first) : getUpperBoundary(monResEntry.first); + } + } + } + return val; + } + + template<typename ParametricType> + typename ParameterRegion<ParametricType>::Valuation ParameterRegion<ParametricType>::getPoint(storm::solver::OptimizationDirection dir, + std::set<VariableType> const &monIncrParameters, + std::set<VariableType> const &monDecrParameters) const{ + auto val = this->getCenterPoint(); + for (auto var : monIncrParameters) { + val[var] = storm::solver::minimize(dir) ? getLowerBoundary(var) : getUpperBoundary(var); + } + for (auto var : monDecrParameters) { + val[var] = storm::solver::maximize(dir) ? getLowerBoundary(var) : getUpperBoundary(var); + } + + return val; + } + + template<typename ParametricType> + typename ParameterRegion<ParametricType>::CoefficientType ParameterRegion<ParametricType>::getBoundParent() { + return parentBound; + } + + template<typename ParametricType> + void ParameterRegion<ParametricType>::setBoundParent(CoefficientType bound) { + parentBound = bound; + } + template <typename ParametricType> std::ostream& operator<<(std::ostream& out, ParameterRegion<ParametricType> const& region) { out << region.toString(); return out; } - + template <typename ParametricType> + void ParameterRegion<ParametricType>::setSplitThreshold(int splitThreshold) { + this->splitThreshold = splitThreshold; + } + + template <typename ParametricType> + int ParameterRegion<ParametricType>::getSplitThreshold() const { + return splitThreshold; + } + + #ifdef STORM_HAVE_CARL - template class ParameterRegion<storm::RationalFunction>; - template std::ostream& operator<<(std::ostream& out, ParameterRegion<storm::RationalFunction> const& region); + template class ParameterRegion<storm::RationalFunction>; + template std::ostream& operator<<(std::ostream& out, ParameterRegion<storm::RationalFunction> const& region); #endif + } } - diff --git a/src/storm-pars/storage/ParameterRegion.h b/src/storm-pars/storage/ParameterRegion.h index d4f3ce457..7545bb2ab 100644 --- a/src/storm-pars/storage/ParameterRegion.h +++ b/src/storm-pars/storage/ParameterRegion.h @@ -3,6 +3,9 @@ #include <map> #include "storm-pars/utility/parametric.h" +#include "storm-pars/analysis/MonotonicityResult.h" +#include "storm-pars/modelchecker/region/RegionResult.h" +#include "storm/solver/OptimizationDirection.h" namespace storm { namespace storage { @@ -23,10 +26,13 @@ namespace storm { virtual ~ParameterRegion() = default; std::set<VariableType> const& getVariables() const; + std::multimap<CoefficientType, VariableType> const& getVariablesSorted() const; CoefficientType const& getLowerBoundary(VariableType const& variable) const; CoefficientType const& getLowerBoundary(const std::string varName) const; CoefficientType const& getUpperBoundary(VariableType const& variable) const; CoefficientType const& getUpperBoundary(const std::string varName) const; + CoefficientType getDifference(const std::string varName) const; + CoefficientType getDifference(VariableType const& variable) const; Valuation const& getLowerBoundaries() const; Valuation const& getUpperBoundaries() const; @@ -50,7 +56,10 @@ namespace storm { * Returns the center point of this region */ Valuation getCenterPoint() const; - + + void setSplitThreshold(int splitThreshold); + int getSplitThreshold() const; + /*! * Returns the area of this region */ @@ -62,17 +71,31 @@ namespace storm { * Subregions with area()==0 are not inserted in the vector. */ void split(Valuation const& splittingPoint, std::vector<ParameterRegion<ParametricType>>& regionVector) const; + void split(Valuation const& splittingPoint, std::vector<ParameterRegion<ParametricType>>& regionVector, std::set<VariableType> const& consideredVariables) const; + + Valuation getPoint(storm::solver::OptimizationDirection dir, storm::analysis::MonotonicityResult<VariableType> & monRes) const; + Valuation getPoint(storm::solver::OptimizationDirection dir, std::set<VariableType> const& possibleMonotoneIncrParameters, std::set<VariableType>const & monDecrParameters) const; //returns the region as string in the format 0.3<=p<=0.4,0.2<=q<=0.5; std::string toString(bool boundariesAsDouble = false) const; + bool isSubRegion(ParameterRegion<ParametricType> region); + + CoefficientType getBoundParent(); + void setBoundParent(CoefficientType bound); + private: void init(); + + bool lastSplitMonotone = false; + int splitThreshold; Valuation lowerBoundaries; Valuation upperBoundaries; std::set<VariableType> variables; + std::multimap<CoefficientType, VariableType> sortedOnDifference; + CoefficientType parentBound; }; template<typename ParametricType> diff --git a/src/storm-pars/transformer/ParameterLifter.cpp b/src/storm-pars/transformer/ParameterLifter.cpp index 9eba6e8f2..c03493185 100644 --- a/src/storm-pars/transformer/ParameterLifter.cpp +++ b/src/storm-pars/transformer/ParameterLifter.cpp @@ -1,10 +1,7 @@ #include "storm-pars/transformer/ParameterLifter.h" - #include "storm/adapters/RationalFunctionAdapter.h" - #include "storm/utility/vector.h" - #include "storm/exceptions/UnexpectedException.h" #include "storm/exceptions/NotSupportedException.h" @@ -12,41 +9,54 @@ namespace storm { namespace transformer { template<typename ParametricType, typename ConstantType> - ParameterLifter<ParametricType, ConstantType>::ParameterLifter(storm::storage::SparseMatrix<ParametricType> const& pMatrix, std::vector<ParametricType> const& pVector, storm::storage::BitVector const& selectedRows, storm::storage::BitVector const& selectedColumns, bool generateRowLabels) { - - + ParameterLifter<ParametricType, ConstantType>::ParameterLifter(storm::storage::SparseMatrix<ParametricType> const& pMatrix, std::vector<ParametricType> const& pVector, storm::storage::BitVector const& selectedRows, storm::storage::BitVector const& selectedColumns, bool generateRowLabels, bool useMonotonicityInFuture) { // get a mapping from old column indices to new ones - std::vector<uint_fast64_t> oldToNewColumnIndexMapping(selectedColumns.size(), selectedColumns.size()); + oldToNewColumnIndexMapping = std::vector<uint_fast64_t>(selectedColumns.size(), selectedColumns.size()); uint_fast64_t newIndex = 0; for (auto const& oldColumn : selectedColumns) { oldToNewColumnIndexMapping[oldColumn] = newIndex++; } + + // create vector, such that the occuringVariables for all states can be stored + occurringVariablesAtState = std::vector<std::set<VariableType>>(pMatrix.getColumnCount()); // Stores which entries of the original matrix/vector are non-constant. Entries for non-selected rows/columns are omitted - storm::storage::BitVector nonConstMatrixEntries(pMatrix.getEntryCount(), false); //this vector has to be resized later - storm::storage::BitVector nonConstVectorEntries(selectedRows.getNumberOfSetBits(), false); + auto nonConstMatrixEntries = storm::storage::BitVector(pMatrix.getEntryCount(), false); //this vector has to be resized later + auto nonConstVectorEntries = storm::storage::BitVector(selectedRows.getNumberOfSetBits(), false); // Counters for selected entries in the pMatrix and the pVector uint_fast64_t pMatrixEntryCount = 0; uint_fast64_t pVectorEntryCount = 0; // The matrix builder for the new matrix. The correct number of rows and entries is not known yet. storm::storage::SparseMatrixBuilder<ConstantType> builder(0, selectedColumns.getNumberOfSetBits(), 0, true, true, selectedRows.getNumberOfSetBits()); + rowGroupToStateNumber = std::vector<uint_fast64_t>(); uint_fast64_t newRowIndex = 0; + uint_fast64_t countNonParam = 0; for (auto const& rowIndex : selectedRows) { builder.newRowGroup(newRowIndex); + rowGroupToStateNumber.push_back(rowIndex); // Gather the occurring variables within this row and set which entries are non-constant std::set<VariableType> occurringVariables; + bool constant = true; for (auto const& entry : pMatrix.getRow(rowIndex)) { if (selectedColumns.get(entry.getColumn())) { if (!storm::utility::isConstant(entry.getValue())) { storm::utility::parametric::gatherOccurringVariables(entry.getValue(), occurringVariables); nonConstMatrixEntries.set(pMatrixEntryCount, true); + constant = false; } ++pMatrixEntryCount; + } else { + if (!storm::utility::isConstant(entry.getValue())) { + storm::utility::parametric::gatherOccurringVariables(entry.getValue(), occurringVariables); + } } } - + + if (constant) { + countNonParam++; + } ParametricType const& pVectorEntry = pVector[rowIndex]; std::set<VariableType> vectorEntryVariables; if (!storm::utility::isConstant(pVectorEntry)) { @@ -59,15 +69,14 @@ namespace storm { nonConstVectorEntries.set(pVectorEntryCount, true); } ++pVectorEntryCount; - // Compute the (abstract) valuation for each row auto rowValuations = getVerticesOfAbstractRegion(occurringVariables); - for (auto const& val : rowValuations) { if (generateRowLabels) { rowLabels.push_back(val); } - + + auto countPlaceHolders = 0; // Insert matrix entries for each valuation. For non-constant entries, a dummy value is inserted and the function and the valuation are collected. // The placeholder for the collected function/valuation are stored in the matrixAssignment. The matrixAssignment is completed after the matrix is finished for (auto const& entry: pMatrix.getRow(rowIndex)) { @@ -78,6 +87,7 @@ namespace storm { builder.addNextValue(newRowIndex, oldToNewColumnIndexMapping[entry.getColumn()], storm::utility::one<ConstantType>()); ConstantType& placeholder = functionValuationCollector.add(entry.getValue(), val); matrixAssignment.push_back(std::pair<typename storm::storage::SparseMatrix<ConstantType>::iterator, ConstantType&>(typename storm::storage::SparseMatrix<ConstantType>::iterator(), placeholder)); + countPlaceHolders++; } } } @@ -100,15 +110,22 @@ namespace storm { ++newRowIndex; } + if (useMonotonicityInFuture) { + // Save the occuringVariables of a state, needed if we want to use monotonicity + for (auto& var : occurringVariables) { + occuringStatesAtVariable[var].insert(rowIndex); + } + occurringVariablesAtState[rowIndex] = std::move(occurringVariables); + } } - + // Matrix and vector are now filled with constant results from constant functions and place holders for non-constant functions. matrix = builder.build(newRowIndex); vector.shrink_to_fit(); matrixAssignment.shrink_to_fit(); vectorAssignment.shrink_to_fit(); nonConstMatrixEntries.resize(pMatrixEntryCount); - + // Now insert the correct iterators for the matrix and vector assignment auto matrixAssignmentIt = matrixAssignment.begin(); uint_fast64_t startEntryOfRow = 0; @@ -139,21 +156,41 @@ namespace storm { void ParameterLifter<ParametricType, ConstantType>::specifyRegion(storm::storage::ParameterRegion<ParametricType> const& region, storm::solver::OptimizationDirection const& dirForParameters) { // write the evaluation result of each function,evaluation pair into the placeholders functionValuationCollector.evaluateCollectedFunctions(region, dirForParameters); - + //apply the matrix and vector assignments to write the contents of the placeholder into the matrix/vector - - for(auto& assignment : matrixAssignment) { + for (auto &assignment : matrixAssignment) { STORM_LOG_WARN_COND(!storm::utility::isZero(assignment.second), "Parameter lifting on region " << region.toString() << " affects the underlying graph structure (the region is not strictly well defined). The result for this region might be incorrect."); assignment.first->setValue(assignment.second); } - for(auto& assignment : vectorAssignment) { + + for (auto &assignment : vectorAssignment) { *assignment.first = assignment.second; } } + + template<typename ParametricType, typename ConstantType> + uint_fast64_t ParameterLifter<ParametricType, ConstantType>::getRowGroupIndex(uint_fast64_t originalState) const { + return matrix.getRowGroupIndices()[oldToNewColumnIndexMapping[originalState]]; + } + + template<typename ParametricType, typename ConstantType> + uint_fast64_t ParameterLifter<ParametricType, ConstantType>::getOriginalStateNumber(uint_fast64_t newState) const { + return rowGroupToStateNumber[newState]; + } + + template<typename ParametricType, typename ConstantType> + uint_fast64_t ParameterLifter<ParametricType, ConstantType>::getRowGroupSize(uint_fast64_t originalState) const { + return matrix.getRowGroupSize(oldToNewColumnIndexMapping[originalState]); + } + template<typename ParametricType, typename ConstantType> + uint_fast64_t ParameterLifter<ParametricType, ConstantType>::getRowGroupCount() const { + return matrix.getRowGroupCount(); + } template<typename ParametricType, typename ConstantType> storm::storage::SparseMatrix<ConstantType> const& ParameterLifter<ParametricType, ConstantType>::getMatrix() const { return matrix; + } template<typename ParametricType, typename ConstantType> @@ -188,6 +225,16 @@ namespace storm { return result; } + template<typename ParametricType, typename ConstantType> + const std::vector<std::set<typename ParameterLifter<ParametricType, ConstantType>::VariableType>> & ParameterLifter<ParametricType, ConstantType>::getOccurringVariablesAtState() const { + return occurringVariablesAtState; + } + + template<typename ParametricType, typename ConstantType> + std::map<typename ParameterLifter<ParametricType, ConstantType>::VariableType, std::set<uint_fast64_t>> ParameterLifter<ParametricType, ConstantType>::getOccuringStatesAtVariable() const { + return occuringStatesAtVariable; + } + template<typename ParametricType, typename ConstantType> bool ParameterLifter<ParametricType, ConstantType>::AbstractValuation::operator==(AbstractValuation const& other) const { return this->lowerPars == other.lowerPars && this->upperPars == other.upperPars && this->unspecifiedPars == other.unspecifiedPars; @@ -268,7 +315,13 @@ namespace storm { } return result; } - + + template<typename ParametricType, typename ConstantType> + uint_fast64_t ParameterLifter<ParametricType, ConstantType>::AbstractValuation::getOriginalState( + uint_fast64_t newStateNumber) const { + return 0; + } + template<typename ParametricType, typename ConstantType> ConstantType& ParameterLifter<ParametricType, ConstantType>::FunctionValuationCollector::add(ParametricType const& function, AbstractValuation const& valuation) { ParametricType simplifiedFunction = function; @@ -284,17 +337,16 @@ namespace storm { template<typename ParametricType, typename ConstantType> void ParameterLifter<ParametricType, ConstantType>::FunctionValuationCollector::evaluateCollectedFunctions(storm::storage::ParameterRegion<ParametricType> const& region, storm::solver::OptimizationDirection const& dirForUnspecifiedParameters) { - for (auto& collectedFunctionValuationPlaceholder : collectedFunctions) { - ParametricType const& function = collectedFunctionValuationPlaceholder.first.first; - AbstractValuation const& abstrValuation = collectedFunctionValuationPlaceholder.first.second; - ConstantType& placeholder = collectedFunctionValuationPlaceholder.second; - + for (auto &collectedFunctionValuationPlaceholder : collectedFunctions) { + ParametricType const &function = collectedFunctionValuationPlaceholder.first.first; + AbstractValuation const &abstrValuation = collectedFunctionValuationPlaceholder.first.second; + ConstantType &placeholder = collectedFunctionValuationPlaceholder.second; auto concreteValuations = abstrValuation.getConcreteValuations(region); auto concreteValuationIt = concreteValuations.begin(); placeholder = storm::utility::convertNumber<ConstantType>(storm::utility::parametric::evaluate(function, *concreteValuationIt)); - for(++concreteValuationIt; concreteValuationIt != concreteValuations.end(); ++concreteValuationIt) { + for (++concreteValuationIt; concreteValuationIt != concreteValuations.end(); ++concreteValuationIt) { ConstantType currentResult = storm::utility::convertNumber<ConstantType>(storm::utility::parametric::evaluate(function, *concreteValuationIt)); - if(storm::solver::minimize(dirForUnspecifiedParameters)) { + if (storm::solver::minimize(dirForUnspecifiedParameters)) { placeholder = std::min(placeholder, currentResult); } else { placeholder = std::max(placeholder, currentResult); diff --git a/src/storm-pars/transformer/ParameterLifter.h b/src/storm-pars/transformer/ParameterLifter.h index c7837fa5c..4ce8d50c9 100644 --- a/src/storm-pars/transformer/ParameterLifter.h +++ b/src/storm-pars/transformer/ParameterLifter.h @@ -11,6 +11,9 @@ #include "storm/storage/BitVector.h" #include "storm/storage/SparseMatrix.h" #include "storm/solver/OptimizationDirection.h" +#include "storm-pars/analysis/Order.h" + +#include "storm-pars/analysis/MonotonicityChecker.h" namespace storm { namespace transformer { @@ -31,7 +34,8 @@ namespace storm { typedef typename storm::utility::parametric::VariableType<ParametricType>::type VariableType; typedef typename storm::utility::parametric::CoefficientType<ParametricType>::type CoefficientType; - + typedef typename storm::analysis::MonotonicityResult<VariableType>::Monotonicity Monotonicity; + /*! * Lifts the parameter choices to nondeterminisim. The computation is performed on the submatrix specified by the selected rows and columns * @param pMatrix the parametric matrix @@ -39,16 +43,41 @@ namespace storm { * @param selectedRows a Bitvector that specifies which rows of the matrix and the vector are considered. * @param selectedColumns a Bitvector that specifies which columns of the matrix are considered. */ - ParameterLifter(storm::storage::SparseMatrix<ParametricType> const& pMatrix, std::vector<ParametricType> const& pVector, storm::storage::BitVector const& selectedRows, storm::storage::BitVector const& selectedColumns, bool generateRowLabels = false); + ParameterLifter(storm::storage::SparseMatrix<ParametricType> const& pMatrix, std::vector<ParametricType> const& pVector, storm::storage::BitVector const& selectedRows, storm::storage::BitVector const& selectedColumns, bool generateRowLabels = false, bool useMonotonicity = false); void specifyRegion(storm::storage::ParameterRegion<ParametricType> const& region, storm::solver::OptimizationDirection const& dirForParameters); - + + /*! + * Specifies the region for the parameterlifter, the Bitvector works as a fixed (partial) scheduler, this might not give sound results! + * @param region the region + * @param dirForParameters the optimization direction + * @param selectedRows a Bitvector that specifies which rows of the matrix and the vector are considered. + */ + void specifyRegion(storm::storage::ParameterRegion<ParametricType> const& region, storm::solver::OptimizationDirection const& dirForParameters, storm::storage::BitVector const& selectedRows); + + /*! + * Specifies the region for the parameterlifter, the reachability order is used to see if there is local monotonicity, such that a fixed (partial) scheduler can be used + * @param region the region + * @param dirForParameters the optimization direction + * @param reachabilityOrder a (possibly insufficient) reachability order, used for local monotonicity + */ + void specifyRegion(storm::storage::ParameterRegion<ParametricType> const& region, storm::solver::OptimizationDirection const& dirForParameters, std::shared_ptr<storm::analysis::Order> reachabilityOrder, std::shared_ptr<storm::analysis::LocalMonotonicityResult<VariableType>> localMonotonicityResult); + // Returns the resulting matrix. Should only be called AFTER specifying a region storm::storage::SparseMatrix<ConstantType> const& getMatrix() const; // Returns the resulting vector. Should only be called AFTER specifying a region std::vector<ConstantType> const& getVector() const; - + + std::vector<std::set<VariableType>> const& getOccurringVariablesAtState() const; + + std::map<VariableType, std::set<uint_fast64_t>> getOccuringStatesAtVariable() const; + + uint_fast64_t getRowGroupIndex(uint_fast64_t originalState) const; + uint_fast64_t getOriginalStateNumber(uint_fast64_t newState) const; + uint_fast64_t getRowGroupSize(uint_fast64_t originalState) const; + uint_fast64_t getRowGroupCount() const; + /* * During initialization, the actual regions are not known. Hence, we consider abstract valuations, * where it is only known whether a parameter will be set to either the lower/upper bound of the region or whether this is unspecified @@ -68,7 +97,8 @@ namespace storm { std::set<VariableType> const& getLowerParameters() const; std::set<VariableType> const& getUpperParameters() const; std::set<VariableType> const& getUnspecifiedParameters() const; - + uint_fast64_t getOriginalState(uint_fast64_t newStateNumber) const; + /*! * Returns the concrete valuation(s) (w.r.t. the provided region) represented by this abstract valuation. * Note that an abstract valuation represents 2^(#unspecified parameters) many concrete valuations. @@ -82,7 +112,6 @@ namespace storm { // Returns for each row the abstract valuation for this row // Note: the returned vector might be empty if row label generaion was disabled initially std::vector<AbstractValuation> const& getRowLabels() const; - private: /* @@ -97,18 +126,19 @@ namespace storm { class FunctionValuationCollector { public: FunctionValuationCollector() = default; - + /*! * Adds the provided function and valuation. * Returns a reference to a placeholder in which the evaluation result will be written upon calling evaluateCollectedFunctions) */ ConstantType& add(ParametricType const& function, AbstractValuation const& valuation); - + void evaluateCollectedFunctions(storm::storage::ParameterRegion<ParametricType> const& region, storm::solver::OptimizationDirection const& dirForUnspecifiedParameters); private: // Stores a function and a valuation. The valuation is stored as an index of the collectedValuations-vector. typedef std::pair<ParametricType, AbstractValuation> FunctionValuation; + class FuncValHash{ public: std::size_t operator()(FunctionValuation const& fv) const { @@ -118,7 +148,7 @@ namespace storm { return seed; } }; - + // Stores the collected functions with the valuations together with a placeholder for the result. std::unordered_map<FunctionValuation, ConstantType, FuncValHash> collectedFunctions; }; @@ -130,12 +160,18 @@ namespace storm { std::vector<AbstractValuation> rowLabels; + std::vector<uint_fast64_t> oldToNewColumnIndexMapping; // Mapping from old to new columnIndex used for monotonicity + std::vector<uint_fast64_t> rowGroupToStateNumber; // Mapping from new to old columnIndex used for monotonicity + storm::storage::SparseMatrix<ConstantType> matrix; //The resulting matrix; std::vector<std::pair<typename storm::storage::SparseMatrix<ConstantType>::iterator, ConstantType&>> matrixAssignment; // Connection of matrix entries with placeholders - + std::vector<ConstantType> vector; //The resulting vector std::vector<std::pair<typename std::vector<ConstantType>::iterator, ConstantType&>> vectorAssignment; // Connection of vector entries with placeholders - + + // Used for monotonicity in sparsedtmcparameterlifter + std::vector<std::set<VariableType>> occurringVariablesAtState; + std::map<VariableType, std::set<uint_fast64_t>> occuringStatesAtVariable; }; } diff --git a/src/storm/settings/modules/GeneralSettings.cpp b/src/storm/settings/modules/GeneralSettings.cpp index 0392b61a3..ccdce224c 100644 --- a/src/storm/settings/modules/GeneralSettings.cpp +++ b/src/storm/settings/modules/GeneralSettings.cpp @@ -73,7 +73,14 @@ namespace storm { uint64_t GeneralSettings::getShowProgressDelay() const { return this->getOption(showProgressOptionName).getArgumentByName("delay").getValueAsUnsignedInteger(); } - + + bool GeneralSettings::isPrecisionSet() const { + return this->getOption(precisionOptionName).getHasOptionBeenSet(); + } + + void GeneralSettings::setPrecision(std::string precision) { + this->getOption(precisionOptionName).getArgumentByName("value").setFromStringValue(precision); + } double GeneralSettings::getPrecision() const { return this->getOption(precisionOptionName).getArgumentByName("value").getValueAsDouble(); } diff --git a/src/storm/settings/modules/GeneralSettings.h b/src/storm/settings/modules/GeneralSettings.h index 42f6a4dfe..45871e235 100644 --- a/src/storm/settings/modules/GeneralSettings.h +++ b/src/storm/settings/modules/GeneralSettings.h @@ -98,7 +98,11 @@ namespace storm { * @return True iff the option was set. */ bool isParametricSet() const; - + + bool isPrecisionSet() const; + + void setPrecision(std::string precision); + /*! * Retrieves whether the option enabling exact model checking is set and we should use infinite precision rationals. * diff --git a/src/storm/settings/modules/IOSettings.cpp b/src/storm/settings/modules/IOSettings.cpp index 40c1a006c..f0af3f4ad 100644 --- a/src/storm/settings/modules/IOSettings.cpp +++ b/src/storm/settings/modules/IOSettings.cpp @@ -26,7 +26,6 @@ namespace storm { const std::string IOSettings::exportCdfOptionShortName = "cdf"; const std::string IOSettings::exportSchedulerOptionName = "exportscheduler"; const std::string IOSettings::exportCheckResultOptionName = "exportresult"; - const std::string IOSettings::exportMonotonicityName = "exportmonotonicity"; const std::string IOSettings::explicitOptionName = "explicit"; const std::string IOSettings::explicitOptionShortName = "exp"; const std::string IOSettings::explicitDrnOptionName = "explicit-drn"; @@ -66,7 +65,6 @@ namespace storm { this->addOption(storm::settings::OptionBuilder(moduleName, exportCdfOptionName, false, "Exports the cumulative density function for reward bounded properties into a .csv file.").setIsAdvanced().setShortName(exportCdfOptionShortName).addArgument(storm::settings::ArgumentBuilder::createStringArgument("directory", "A path to an existing directory where the cdf files will be stored.").build()).build()); this->addOption(storm::settings::OptionBuilder(moduleName, exportSchedulerOptionName, false, "Exports the choices of an optimal scheduler to the given file (if supported by engine).").setIsAdvanced().addArgument(storm::settings::ArgumentBuilder::createStringArgument("filename", "The output file. Use file extension '.json' to export in json.").build()).build()); this->addOption(storm::settings::OptionBuilder(moduleName, exportCheckResultOptionName, false, "Exports the result to a given file (if supported by engine). The export will be in json.").setIsAdvanced().addArgument(storm::settings::ArgumentBuilder::createStringArgument("filename", "The output file.").build()).build()); - this->addOption(storm::settings::OptionBuilder(moduleName, exportMonotonicityName, false, "Exports the result of monotonicity checking to the given file.").setIsAdvanced().addArgument(storm::settings::ArgumentBuilder::createStringArgument("filename", "The output file.").build()).build()); this->addOption(storm::settings::OptionBuilder(moduleName, exportExplicitOptionName, "", "If given, the loaded model will be written to the specified file in the drn format.") .addArgument(storm::settings::ArgumentBuilder::createStringArgument("filename", "the name of the file to which the model is to be writen.").build()).build()); this->addOption(storm::settings::OptionBuilder(moduleName, preventDRNPlaceholderOptionName, true, "If given, the exported DRN contains no placeholders").setIsAdvanced().build()); @@ -179,23 +177,15 @@ namespace storm { std::string IOSettings::getExportSchedulerFilename() const { return this->getOption(exportSchedulerOptionName).getArgumentByName("filename").getValueAsString(); } - + bool IOSettings::isExportCheckResultSet() const { return this->getOption(exportCheckResultOptionName).getHasOptionBeenSet(); } - + std::string IOSettings::getExportCheckResultFilename() const { return this->getOption(exportCheckResultOptionName).getArgumentByName("filename").getValueAsString(); } - bool IOSettings::isExportMonotonicitySet() const { - return this->getOption(exportMonotonicityName).getHasOptionBeenSet(); - } - - std::string IOSettings::getExportMonotonicityFilename() const { - return this->getOption(exportMonotonicityName).getArgumentByName("filename").getValueAsString(); - } - bool IOSettings::isExplicitSet() const { return this->getOption(explicitOptionName).getHasOptionBeenSet(); } diff --git a/src/storm/settings/modules/IOSettings.h b/src/storm/settings/modules/IOSettings.h index 34875441f..3be8c646d 100644 --- a/src/storm/settings/modules/IOSettings.h +++ b/src/storm/settings/modules/IOSettings.h @@ -105,27 +105,17 @@ namespace storm { * Retrieves a filename to which an optimal scheduler will be exported. */ std::string getExportSchedulerFilename() const; - + /*! * Retrieves whether the check result should be exported. */ bool isExportCheckResultSet() const; - + /*! * Retrieves a filename to which the check result should be exported. */ std::string getExportCheckResultFilename() const; - /*! - * Retrieves whether an optimal scheduler is to be exported - */ - bool isExportMonotonicitySet() const; - - /*! - * Retrieves a filename to which an optimal scheduler will be exported. - */ - std::string getExportMonotonicityFilename() const; - /*! * Retrieves whether the explicit option was set. * @@ -376,7 +366,6 @@ namespace storm { static const std::string exportCdfOptionShortName; static const std::string exportSchedulerOptionName; static const std::string exportCheckResultOptionName; - static const std::string exportMonotonicityName; static const std::string explicitOptionName; static const std::string explicitOptionShortName; static const std::string explicitDrnOptionName; diff --git a/src/storm/solver/IterativeMinMaxLinearEquationSolver.cpp b/src/storm/solver/IterativeMinMaxLinearEquationSolver.cpp index 5e71c0503..5d2ff03d6 100644 --- a/src/storm/solver/IterativeMinMaxLinearEquationSolver.cpp +++ b/src/storm/solver/IterativeMinMaxLinearEquationSolver.cpp @@ -97,12 +97,19 @@ namespace storm { // Resolve the nondeterminism according to the given scheduler. bool convertToEquationSystem = this->linearEquationSolverFactory->getEquationProblemFormat(env) == LinearEquationSolverProblemFormat::EquationSystem; - storm::storage::SparseMatrix<ValueType> submatrix = this->A->selectRowsFromRowGroups(scheduler, convertToEquationSystem); + storm::storage::SparseMatrix<ValueType> submatrix; + if (this->fixedStates) { + for (auto state : this->fixedStates.get()) { + assert (this->A->getRowGroupSize(state) == 1); + } + } + + submatrix = this->A->selectRowsFromRowGroups(scheduler, convertToEquationSystem); if (convertToEquationSystem) { submatrix.convertToEquationSystem(); } storm::utility::vector::selectVectorValues<ValueType>(subB, scheduler, this->A->getRowGroupIndices(), originalB); - + // Check whether the linear equation solver is already initialized if (!linearEquationSolver) { // Initialize the equation solver @@ -165,29 +172,36 @@ namespace storm { // Go through the multiplication result and see whether we can improve any of the choices. bool schedulerImproved = false; + // Group staat voor de states? for (uint_fast64_t group = 0; group < this->A->getRowGroupCount(); ++group) { uint_fast64_t currentChoice = scheduler[group]; - for (uint_fast64_t choice = this->A->getRowGroupIndices()[group]; choice < this->A->getRowGroupIndices()[group + 1]; ++choice) { - // If the choice is the currently selected one, we can skip it. - if (choice - this->A->getRowGroupIndices()[group] == currentChoice) { - continue; - } - - // Create the value of the choice. - ValueType choiceValue = storm::utility::zero<ValueType>(); - for (auto const& entry : this->A->getRow(choice)) { - choiceValue += entry.getValue() * x[entry.getColumn()]; - } - choiceValue += b[choice]; - - // If the value is strictly better than the solution of the inner system, we need to improve the scheduler. - // TODO: If the underlying solver is not precise, this might run forever (i.e. when a state has two choices where the (exact) values are equal). - // only changing the scheduler if the values are not equal (modulo precision) would make this unsound. - if (valueImproved(dir, x[group], choiceValue)) { - schedulerImproved = true; - scheduler[group] = choice - this->A->getRowGroupIndices()[group]; - x[group] = std::move(choiceValue); + // TODO: remove, as this should already be fixed by implementation to determine matrix/vector + if (!this->fixedStates || (this->fixedStates && !(this->fixedStates.get()[group]))) { + for (uint_fast64_t choice = this->A->getRowGroupIndices()[group]; + choice < this->A->getRowGroupIndices()[group + 1]; ++choice) { + // If the choice is the currently selected one, we can skip it. + if (choice - this->A->getRowGroupIndices()[group] == currentChoice) { + continue; + } + + // Create the value of the choice. + ValueType choiceValue = storm::utility::zero<ValueType>(); + for (auto const &entry : this->A->getRow(choice)) { + choiceValue += entry.getValue() * x[entry.getColumn()]; + } + choiceValue += b[choice]; + + // If the value is strictly better than the solution of the inner system, we need to improve the scheduler. + // TODO: If the underlying solver is not precise, this might run forever (i.e. when a state has two choices where the (exact) values are equal). + // only changing the scheduler if the values are not equal (modulo precision) would make this unsound. + if (valueImproved(dir, x[group], choiceValue)) { + schedulerImproved = true; + scheduler[group] = choice - this->A->getRowGroupIndices()[group]; + x[group] = std::move(choiceValue); + } } + } else { + STORM_LOG_INFO("Ignoring state" << group << " as this state is locally monotone"); } } @@ -203,7 +217,8 @@ namespace storm { // Potentially show progress. this->showProgressIterative(iterations); } while (status == SolverStatus::InProgress); - + + STORM_LOG_INFO("Number of iterations: " << iterations); this->reportStatus(status, iterations); // If requested, we store the scheduler for retrieval. diff --git a/src/storm/solver/MinMaxLinearEquationSolver.cpp b/src/storm/solver/MinMaxLinearEquationSolver.cpp index c36e291c2..4366fa0db 100644 --- a/src/storm/solver/MinMaxLinearEquationSolver.cpp +++ b/src/storm/solver/MinMaxLinearEquationSolver.cpp @@ -128,6 +128,7 @@ namespace storm { template<typename ValueType> void MinMaxLinearEquationSolver<ValueType>::setInitialScheduler(std::vector<uint_fast64_t>&& choices) { + assert (!this->fixedStates || this->fixedStates.get().size() == choices.size()); initialScheduler = std::move(choices); } @@ -155,7 +156,21 @@ namespace storm { bool MinMaxLinearEquationSolver<ValueType>::isRequirementsCheckedSet() const { return requirementsChecked; } - + + template<class ValueType> + void MinMaxLinearEquationSolver<ValueType>::setFixedStates(storm::storage::BitVector&& states) { + this->fixedStates = std::move(states); + assert (this->fixedStates); + } + + template<class ValueType> + void MinMaxLinearEquationSolver<ValueType>::updateScheduler() { + assert (this->initialScheduler && this->fixedStates); + for (auto state : this->fixedStates.get()) { + this->initialScheduler.get()[state] = 0; + } + } + template<typename ValueType> MinMaxLinearEquationSolverFactory<ValueType>::MinMaxLinearEquationSolverFactory() : requirementsChecked(false) { // Intentionally left empty diff --git a/src/storm/solver/MinMaxLinearEquationSolver.h b/src/storm/solver/MinMaxLinearEquationSolver.h index 5ab51dc95..836570ffc 100644 --- a/src/storm/solver/MinMaxLinearEquationSolver.h +++ b/src/storm/solver/MinMaxLinearEquationSolver.h @@ -69,7 +69,10 @@ namespace storm { * Unsets the optimization direction to use for calls to methods that do not explicitly provide one. */ void unsetOptimizationDirection(); - + + void setFixedStates(storm::storage::BitVector&& states); + void updateScheduler(); + /*! * Sets whether the solution to the min max equation system is known to be unique. */ @@ -182,7 +185,9 @@ namespace storm { // A scheduler that can be used by solvers that require a valid initial scheduler. boost::optional<std::vector<uint_fast64_t>> initialScheduler; - + + boost::optional<storm::storage::BitVector> fixedStates; + private: /// Whether the solver can assume that the min-max equation system has a unique solution bool uniqueSolution; diff --git a/src/storm/solver/TopologicalMinMaxLinearEquationSolver.cpp b/src/storm/solver/TopologicalMinMaxLinearEquationSolver.cpp index 6cf09bd45..e186b9dd8 100644 --- a/src/storm/solver/TopologicalMinMaxLinearEquationSolver.cpp +++ b/src/storm/solver/TopologicalMinMaxLinearEquationSolver.cpp @@ -69,8 +69,8 @@ namespace storm { } bool returnValue = true; - if (this->sortedSccDecomposition->size() == 1) { - // Handle the case where there is just one large SCC + if (this->sortedSccDecomposition->size() == 1 && (!this->fixedStates || this->fixedStates.get().empty())) { + // Handle the case where there is just one large SCC, as there are no fixed states, we solve it like this returnValue = solveFullyConnectedEquationSystem(sccSolverEnvironment, dir, x, b); } else { // Solve each SCC individually @@ -89,15 +89,24 @@ namespace storm { progress.startNewMeasurement(0); for (auto const& scc : *this->sortedSccDecomposition) { if (scc.size() == 1) { + // TODO: directly use localMonRes on this returnValue = solveTrivialScc(*scc.begin(), dir, x, b) && returnValue; } else { STORM_LOG_TRACE("Solving SCC of size " << scc.size() << "."); sccRowGroupsAsBitVector.clear(); sccRowsAsBitVector.clear(); - for (auto const& group : scc) { + for (auto const& group : scc) { // Group refers to state + bool allIgnored = true; sccRowGroupsAsBitVector.set(group, true); - for (uint64_t row = this->A->getRowGroupIndices()[group]; row < this->A->getRowGroupIndices()[group + 1]; ++row) { + + if (!this->fixedStates || !this->fixedStates.get()[group]) { + for (uint64_t row = this->A->getRowGroupIndices()[group]; row < this->A->getRowGroupIndices()[group + 1]; ++row) { + sccRowsAsBitVector.set(row, true); + } + } else { + auto row = this->A->getRowGroupIndices()[group]+this->getInitialScheduler()[group]; sccRowsAsBitVector.set(row, true); + STORM_LOG_INFO("Fixing state " << group << " to option " << this->getInitialScheduler()[group] << " because of local monotonicity."); } } returnValue = solveScc(sccSolverEnvironment, dir, sccRowGroupsAsBitVector, sccRowsAsBitVector, x, b) && returnValue; @@ -141,12 +150,13 @@ namespace storm { ValueType& xi = globalX[sccState]; bool firstRow = true; uint64_t bestRow; - - for (uint64_t row = this->A->getRowGroupIndices()[sccState]; row < this->A->getRowGroupIndices()[sccState + 1]; ++row) { + if (this->fixedStates && this->fixedStates.get()[sccState]) { + assert (this->hasInitialScheduler()); + uint64_t row = this->A->getRowGroupIndices()[sccState] + this->initialScheduler.get()[sccState]; ValueType rowValue = globalB[row]; bool hasDiagonalEntry = false; ValueType denominator; - for (auto const& entry : this->A->getRow(row)) { + for (auto const &entry : this->A->getRow(row)) { if (entry.getColumn() == sccState) { hasDiagonalEntry = true; denominator = storm::utility::one<ValueType>() - entry.getValue(); @@ -155,38 +165,68 @@ namespace storm { } } if (hasDiagonalEntry) { - STORM_LOG_WARN_COND_DEBUG(storm::NumberTraits<ValueType>::IsExact || !storm::utility::isAlmostZero(denominator) || storm::utility::isZero(denominator), "State " << sccState << " has a selfloop with probability '1-(" << denominator << ")'. This could be an indication for numerical issues."); - if (storm::utility::isZero(denominator)) { - // In this case we have a selfloop on this state. This can never an optimal choice: - // When minimizing, we are looking for the largest fixpoint (which will never be attained by this action) - // When maximizing, this choice reflects probability zero (non-optimal) or reward infinity (should already be handled during preprocessing). - continue; - } else { - rowValue /= denominator; - } + STORM_LOG_WARN_COND_DEBUG( storm::NumberTraits<ValueType>::IsExact || !storm::utility::isAlmostZero(denominator) || + storm::utility::isZero(denominator), "State " << sccState << " has a selfloop with probability '1-(" << denominator << ")'. This could be an indication for numerical issues."); + assert (!storm::utility::isZero(denominator)); + rowValue /= denominator; } - if (firstRow) { + if (minimize(dir)) { xi = std::move(rowValue); - bestRow = row; - firstRow = false; } else { - if (minimize(dir)) { - if (rowValue < xi) { - xi = std::move(rowValue); - bestRow = row; + xi = std::move(rowValue); + } + STORM_LOG_INFO("Ignoring state" << sccState << " as the scheduler is fixed by monotonicity, current probability for this state is: " << this->schedulerChoices.get()[sccState]); + } else { + for (uint64_t row = this->A->getRowGroupIndices()[sccState]; row < this->A->getRowGroupIndices()[sccState + 1]; ++row) { + ValueType rowValue = globalB[row]; + bool hasDiagonalEntry = false; + ValueType denominator; + for (auto const &entry : this->A->getRow(row)) { + if (entry.getColumn() == sccState) { + hasDiagonalEntry = true; + denominator = storm::utility::one<ValueType>() - entry.getValue(); + } else { + rowValue += entry.getValue() * globalX[entry.getColumn()]; + } + } + if (hasDiagonalEntry) { + STORM_LOG_WARN_COND_DEBUG( + storm::NumberTraits<ValueType>::IsExact || !storm::utility::isAlmostZero(denominator) || + storm::utility::isZero(denominator), + "State " << sccState << " has a selfloop with probability '1-(" << denominator + << ")'. This could be an indication for numerical issues."); + if (storm::utility::isZero(denominator)) { + // In this case we have a selfloop on this state. This can never an optimal choice: + // When minimizing, we are looking for the largest fixpoint (which will never be attained by this action) + // When maximizing, this choice reflects probability zero (non-optimal) or reward infinity (should already be handled during preprocessing). + continue; + } else { + rowValue /= denominator; } + } + if (firstRow) { + xi = std::move(rowValue); + bestRow = row; + firstRow = false; } else { - if (rowValue > xi) { - xi = std::move(rowValue); - bestRow = row; + if (minimize(dir)) { + if (rowValue < xi) { + xi = std::move(rowValue); + bestRow = row; + } + } else { + if (rowValue > xi) { + xi = std::move(rowValue); + bestRow = row; + } } } } + if (this->isTrackSchedulerSet()) { + this->schedulerChoices.get()[sccState] = bestRow - this->A->getRowGroupIndices()[sccState]; + } + STORM_LOG_THROW(!firstRow, storm::exceptions::UnexpectedException, "Empty row group in MinMax equation system."); } - if (this->isTrackSchedulerSet()) { - this->schedulerChoices.get()[sccState] = bestRow - this->A->getRowGroupIndices()[sccState]; - } - STORM_LOG_THROW(!firstRow, storm::exceptions::UnexpectedException, "Empty row group in MinMax equation system."); //std::cout << "Solved trivial scc " << sccState << " with result " << globalX[sccState] << std::endl; return true; } @@ -231,19 +271,38 @@ namespace storm { template<typename ValueType> bool TopologicalMinMaxLinearEquationSolver<ValueType>::solveScc(storm::Environment const& sccSolverEnvironment, OptimizationDirection dir, storm::storage::BitVector const& sccRowGroups, storm::storage::BitVector const& sccRows, std::vector<ValueType>& globalX, std::vector<ValueType> const& globalB) const { - + // Set up the SCC solver if (!this->sccSolver) { this->sccSolver = GeneralMinMaxLinearEquationSolverFactory<ValueType>().create(sccSolverEnvironment); this->sccSolver->setCachingEnabled(true); } + if (this->fixedStates) { + // convert fixed states to only fixed states of sccs + storm::storage::BitVector fixedStatesSCC(sccRowGroups.getNumberOfSetBits()); + auto j = 0; + for (auto i : sccRowGroups) { + fixedStatesSCC.set(j, this->fixedStates.get()[i]); + j++; + } + assert (j = sccRowGroups.getNumberOfSetBits()); + this->sccSolver->setFixedStates(std::move(fixedStatesSCC)); + } this->sccSolver->setHasUniqueSolution(this->hasUniqueSolution()); this->sccSolver->setHasNoEndComponents(this->hasNoEndComponents()); this->sccSolver->setTrackScheduler(this->isTrackSchedulerSet()); // SCC Matrix - storm::storage::SparseMatrix<ValueType> sccA = this->A->getSubmatrix(true, sccRowGroups, sccRowGroups); - //std::cout << "Matrix is " << sccA << std::endl; + storm::storage::SparseMatrix<ValueType> sccA; + if (this->fixedStates) { + sccA = this->A->getSubmatrix(false, sccRows, sccRowGroups); + } else { + sccA = this->A->getSubmatrix(true, sccRowGroups, sccRowGroups); + + } + +// std::cout << "Matrix is " << sccA << std::endl; + this->sccSolver->setMatrix(std::move(sccA)); // x Vector @@ -266,6 +325,9 @@ namespace storm { if (this->hasInitialScheduler()) { auto sccInitChoices = storm::utility::vector::filterVector(this->getInitialScheduler(), sccRowGroups); this->sccSolver->setInitialScheduler(std::move(sccInitChoices)); + if (this->fixedStates) { + this->sccSolver->updateScheduler(); + } } // lower/upper bounds diff --git a/src/storm/utility/graph.cpp b/src/storm/utility/graph.cpp index dc8acb4f3..ddb1a4eca 100644 --- a/src/storm/utility/graph.cpp +++ b/src/storm/utility/graph.cpp @@ -1665,6 +1665,40 @@ namespace storm { } } + template <typename T> + std::vector<uint_fast64_t> getBFSSort(storm::storage::SparseMatrix<T> const& matrix, std::vector<uint_fast64_t> const& firstStates) { + storm::storage::BitVector seenStates(matrix.getRowGroupCount()); + + std::vector<uint_fast64_t> stateQueue; + stateQueue.reserve(matrix.getRowGroupCount()); + std::vector<uint_fast64_t> result; + result.reserve(matrix.getRowGroupCount()); + + storm::storage::sparse::state_type currentPosition = 0; + auto count = matrix.getRowGroupCount() - 1; + for (auto const& state : firstStates) { + stateQueue.push_back(state); + result[count] = state; + count--; + } + + // Perform a BFS. + while (!stateQueue.empty()) { + auto state = stateQueue.back(); + stateQueue.pop_back(); + seenStates.set(state); + for (auto const& successorEntry : matrix.getRowGroup(state)) { + auto succ = successorEntry.geColumn(); + if (!seenStates[succ]) { + result[count] = succ; + count--; + stateQueue.insert(stateQueue.begin(), succ); + } + } + } + return result; + } + template <typename T> std::vector<uint_fast64_t> getTopologicalSort(storm::storage::SparseMatrix<T> const& matrix, std::vector<uint64_t> const& firstStates) { if (matrix.getRowCount() != matrix.getColumnCount()) { @@ -1673,7 +1707,7 @@ namespace storm { } uint_fast64_t numberOfStates = matrix.getRowCount(); - + // Prepare the result. This relies on the matrix being square. std::vector<uint_fast64_t> topologicalSort; topologicalSort.reserve(numberOfStates); @@ -1696,7 +1730,6 @@ namespace storm { return topologicalSort; } - template storm::storage::BitVector getReachableStates(storm::storage::SparseMatrix<double> const& transitionMatrix, storm::storage::BitVector const& initialStates, storm::storage::BitVector const& constraintStates, storm::storage::BitVector const& targetStates, bool useStepBound, uint_fast64_t maximalSteps, boost::optional<storm::storage::BitVector> const& choiceFilter); template storm::storage::BitVector getBsccCover(storm::storage::SparseMatrix<double> const& transitionMatrix); @@ -1774,7 +1807,7 @@ namespace storm { template ExplicitGameProb01Result performProb1(storm::storage::SparseMatrix<double> const& transitionMatrix, std::vector<uint64_t> const& player1RowGrouping, storm::storage::SparseMatrix<double> const& player1BackwardTransitions, std::vector<uint64_t> const& player2BackwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, storm::OptimizationDirection const& player1Direction, storm::OptimizationDirection const& player2Direction, storm::abstraction::ExplicitGameStrategyPair* strategyPair, boost::optional<storm::storage::BitVector> const& player1Candidates); template std::vector<uint_fast64_t> getTopologicalSort(storm::storage::SparseMatrix<double> const& matrix, std::vector<uint64_t> const& firstStates) ; - + // Instantiations for storm::RationalNumber. #ifdef STORM_HAVE_CARL template storm::storage::BitVector getReachableStates(storm::storage::SparseMatrix<storm::RationalNumber> const& transitionMatrix, storm::storage::BitVector const& initialStates, storm::storage::BitVector const& constraintStates, storm::storage::BitVector const& targetStates, bool useStepBound, uint_fast64_t maximalSteps, boost::optional<storm::storage::BitVector> const& choiceFilter); @@ -1888,7 +1921,6 @@ namespace storm { template std::vector<uint_fast64_t> getTopologicalSort(storm::storage::SparseMatrix<storm::RationalFunction> const& matrix, std::vector<uint64_t> const& firstStates); - #endif // Instantiations for CUDD. diff --git a/src/storm/utility/graph.h b/src/storm/utility/graph.h index fdb9a2538..db8b77a12 100644 --- a/src/storm/utility/graph.h +++ b/src/storm/utility/graph.h @@ -3,6 +3,7 @@ #include <set> #include <limits> +#include <storm/storage/StronglyConnectedComponent.h> #include "storm/utility/OsDetection.h" @@ -706,6 +707,9 @@ namespace storm { template <typename T> std::vector<uint_fast64_t> getTopologicalSort(storm::storage::SparseMatrix<T> const& matrix, std::vector<uint64_t> const& firstStates = {}) ; + template <typename T> + std::vector<uint_fast64_t> getBFSSort(storm::storage::SparseMatrix<T> const& matrix, std::vector<uint_fast64_t> const& firstStates) ; + } // namespace graph } // namespace utility } // namespace storm diff --git a/src/test/storm-pars/CMakeLists.txt b/src/test/storm-pars/CMakeLists.txt index ba3994449..4850893c7 100644 --- a/src/test/storm-pars/CMakeLists.txt +++ b/src/test/storm-pars/CMakeLists.txt @@ -12,7 +12,7 @@ include_directories(${GTEST_INCLUDE_DIR}) foreach (testsuite analysis modelchecker utility) file(GLOB_RECURSE TEST_${testsuite}_FILES ${STORM_TESTS_BASE_PATH}/${testsuite}/*.h ${STORM_TESTS_BASE_PATH}/${testsuite}/*.cpp) - add_executable (test-pars-${testsuite} ${TEST_${testsuite}_FILES} ${STORM_TESTS_BASE_PATH}/storm-test.cpp) + add_executable (test-pars-${testsuite} ${TEST_${testsuite}_FILES} ${STORM_TESTS_BASE_PATH}/storm-test.cpp analysis/MonotonicityCheckerTest.cpp) target_link_libraries(test-pars-${testsuite} storm-pars storm-parsers) target_link_libraries(test-pars-${testsuite} ${STORM_TEST_LINK_LIBRARIES}) diff --git a/src/test/storm-pars/analysis/AssumptionCheckerTest.cpp b/src/test/storm-pars/analysis/AssumptionCheckerTest.cpp index f319587c3..8c549bd00 100644 --- a/src/test/storm-pars/analysis/AssumptionCheckerTest.cpp +++ b/src/test/storm-pars/analysis/AssumptionCheckerTest.cpp @@ -1,29 +1,22 @@ -#include "test/storm_gtest.h" +#include <storm/storage/StronglyConnectedComponentDecomposition.h> #include "storm-config.h" -#include "test/storm_gtest.h" -#include "storm-parsers/parser/FormulaParser.h" -#include "storm/logic/Formulas.h" -#include "storm/models/sparse/StandardRewardModel.h" -#include "storm/modelchecker/prctl/SparseDtmcPrctlModelChecker.h" -#include "storm/modelchecker/results/ExplicitQuantitativeCheckResult.h" +#include "storm-pars/api/analysis.h" +#include "storm-pars/api/region.h" +#include "storm-pars/transformer/SparseParametricDtmcSimplifier.h" + +#include "storm-parsers/api/storm-parsers.h" #include "storm-parsers/parser/AutoParser.h" #include "storm-parsers/parser/PrismParser.h" -#include "storm/storage/expressions/ExpressionManager.h" -#include "storm/api/builder.h" -#include "storm-pars/analysis/AssumptionChecker.h" -#include "storm-pars/analysis/Order.h" -#include "storm/storage/expressions/BinaryRelationExpression.h" -#include "storm-pars/transformer/SparseParametricDtmcSimplifier.h" -#include "storm-pars/storage/ParameterRegion.h" - -#include "storm-pars/api/storm-pars.h" +#include "storm/api/builder.h" #include "storm/api/storm.h" +#include "storm/logic/Formulas.h" +#include "storm/modelchecker/prctl/SparseDtmcPrctlModelChecker.h" +#include "storm/storage/expressions/BinaryRelationExpression.h" +#include "storm/storage/expressions/ExpressionManager.h" -#include "storm-parsers/api/storm-parsers.h" - -#include "storm-pars/api/region.h" +#include "test/storm_gtest.h" TEST(AssumptionCheckerTest, Brp_no_bisimulation) { @@ -43,35 +36,38 @@ TEST(AssumptionCheckerTest, Brp_no_bisimulation) { dtmc = model->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - ASSERT_EQ(dtmc->getNumberOfStates(), 193ull); - ASSERT_EQ(dtmc->getNumberOfTransitions(), 383ull); + ASSERT_EQ(dtmc->getNumberOfStates(), 193); + ASSERT_EQ(dtmc->getNumberOfTransitions(), 383); // Create the region - storm::storage::ParameterRegion<storm::RationalFunction>::Valuation lowerBoundaries; - storm::storage::ParameterRegion<storm::RationalFunction>::Valuation upperBoundaries; auto vars = storm::models::sparse::getProbabilityParameters(*dtmc); auto region = storm::api::parseRegion<storm::RationalFunction>("0.00001 <= pK <= 0.00001, 0.00001 <= pL <= 0.99999", vars); - auto checker = storm::analysis::AssumptionChecker<storm::RationalFunction>(formulas[0], dtmc, region, 3); - - // Check on samples + auto checker = storm::analysis::AssumptionChecker<storm::RationalFunction, double>(dtmc->getTransitionMatrix()); auto expressionManager = std::make_shared<storm::expressions::ExpressionManager>(storm::expressions::ExpressionManager()); expressionManager->declareRationalVariable("7"); expressionManager->declareRationalVariable("5"); + storm::storage::BitVector above(193); + above.set(0); + storm::storage::BitVector below(193); + below.set(1); - auto assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( - storm::expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), - expressionManager->getVariable("7").getExpression().getBaseExpressionPointer(), - expressionManager->getVariable("5").getExpression().getBaseExpressionPointer(), - storm::expressions::BinaryRelationExpression::RelationType::Greater)); - EXPECT_EQ(storm::analysis::AssumptionStatus::UNKNOWN, checker.checkOnSamples(assumption)); + storm::storage::StronglyConnectedComponentDecompositionOptions options; + options.forceTopologicalSort(); + auto decomposition = storm::storage::StronglyConnectedComponentDecomposition<storm::RationalFunction>(model->getTransitionMatrix(), options); + + auto statesSorted = storm::utility::graph::getTopologicalSort(model->getTransitionMatrix()); + auto dummyOrder = std::shared_ptr<storm::analysis::Order>(new storm::analysis::Order(&above, &below, 193, decomposition, statesSorted)); + + auto assumption = std::make_shared<storm::expressions::BinaryRelationExpression>(storm::expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), expressionManager->getVariable("7").getExpression().getBaseExpressionPointer(), expressionManager->getVariable("5").getExpression().getBaseExpressionPointer(), storm::expressions::BinaryRelationExpression::RelationType::Greater)); + EXPECT_EQ(storm::analysis::AssumptionStatus::UNKNOWN, checker.validateAssumption(assumption, dummyOrder, region)); assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( storm::expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), expressionManager->getVariable("5").getExpression().getBaseExpressionPointer(), expressionManager->getVariable("7").getExpression().getBaseExpressionPointer(), storm::expressions::BinaryRelationExpression::RelationType::Greater)); - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.checkOnSamples(assumption)); + EXPECT_EQ(storm::analysis::AssumptionStatus::UNKNOWN, checker.validateAssumption(assumption, dummyOrder, region)); assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( @@ -79,40 +75,31 @@ TEST(AssumptionCheckerTest, Brp_no_bisimulation) { expressionManager->getVariable("7").getExpression().getBaseExpressionPointer(), expressionManager->getVariable("5").getExpression().getBaseExpressionPointer(), storm::expressions::BinaryRelationExpression::RelationType::Equal)); - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.checkOnSamples(assumption)); + EXPECT_EQ(storm::analysis::AssumptionStatus::UNKNOWN, checker.validateAssumption(assumption, dummyOrder, region)); - storm::storage::BitVector above(8); - above.set(0); - storm::storage::BitVector below(8); - below.set(1); - storm::storage::BitVector initialMiddle(8); + checker.initializeCheckingOnSamples(formulas[0], dtmc, region, 3); + assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( + storm::expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), + expressionManager->getVariable("7").getExpression().getBaseExpressionPointer(), + expressionManager->getVariable("5").getExpression().getBaseExpressionPointer(), + storm::expressions::BinaryRelationExpression::RelationType::Greater)); + EXPECT_EQ(storm::analysis::AssumptionStatus::UNKNOWN, checker.validateAssumption(assumption, dummyOrder, region)); - std::vector<uint_fast64_t> statesSorted = storm::utility::graph::getTopologicalSort(model->getTransitionMatrix()); + assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( + storm::expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), + expressionManager->getVariable("5").getExpression().getBaseExpressionPointer(), + expressionManager->getVariable("7").getExpression().getBaseExpressionPointer(), + storm::expressions::BinaryRelationExpression::RelationType::Greater)); + EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumption(assumption, dummyOrder, region)); - auto dummyOrder = new storm::analysis::Order(&above, &below, &initialMiddle, 8, &statesSorted); - // Validate assumption - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumption(assumption, dummyOrder)); -// EXPECT_FALSE(checker.validated(assumption)); - expressionManager->declareRationalVariable("6"); - expressionManager->declareRationalVariable("8"); assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( storm::expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), - expressionManager->getVariable("6").getExpression().getBaseExpressionPointer(), - expressionManager->getVariable("8").getExpression().getBaseExpressionPointer(), - storm::expressions::BinaryRelationExpression::RelationType::Greater)); - above = storm::storage::BitVector(13); - above.set(12); - below = storm::storage::BitVector(13); - below.set(9); - initialMiddle = storm::storage::BitVector(13); - - dummyOrder = new storm::analysis::Order(&above, &below, &initialMiddle, 13, &statesSorted); - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.checkOnSamples(assumption)); - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumption(assumption, dummyOrder)); -// EXPECT_EQ(checker.validated(assumption)); -// EXPECT_FALSE(checker.valid(assumption)); + expressionManager->getVariable("7").getExpression().getBaseExpressionPointer(), + expressionManager->getVariable("5").getExpression().getBaseExpressionPointer(), + storm::expressions::BinaryRelationExpression::RelationType::Equal)); + EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumption(assumption, dummyOrder, region)); } TEST(AssumptionCheckerTest, Simple1) { @@ -131,44 +118,76 @@ TEST(AssumptionCheckerTest, Simple1) { model = simplifier.getSimplifiedModel(); dtmc = model->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - ASSERT_EQ(dtmc->getNumberOfStates(), 5ul); - ASSERT_EQ(dtmc->getNumberOfTransitions(), 8ul); + ASSERT_EQ(dtmc->getNumberOfStates(), 5); + ASSERT_EQ(dtmc->getNumberOfTransitions(), 8); // Create the region auto vars = storm::models::sparse::getProbabilityParameters(*dtmc); auto region = storm::api::parseRegion<storm::RationalFunction>("0.00001 <= p <= 0.99999", vars); - auto checker = storm::analysis::AssumptionChecker<storm::RationalFunction>(formulas[0], dtmc, region, 3); + auto checker = storm::analysis::AssumptionChecker<storm::RationalFunction, double>(dtmc->getTransitionMatrix()); auto expressionManager = std::make_shared<storm::expressions::ExpressionManager>(storm::expressions::ExpressionManager()); expressionManager->declareRationalVariable("1"); expressionManager->declareRationalVariable("2"); + storm::storage::StronglyConnectedComponentDecompositionOptions options; + options.forceTopologicalSort(); + auto decomposition = storm::storage::StronglyConnectedComponentDecomposition<storm::RationalFunction>(model->getTransitionMatrix(), options); - // Checking on samples + storm::storage::BitVector above(5); + above.set(3); + storm::storage::BitVector below(5); + below.set(4); + auto statesSorted = storm::utility::graph::getTopologicalSort(model->getTransitionMatrix()); + auto order = std::shared_ptr<storm::analysis::Order>(new storm::analysis::Order(&above, &below, 5, decomposition, statesSorted)); + + // Validating auto assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( storm::expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), expressionManager->getVariable("1").getExpression().getBaseExpressionPointer(), expressionManager->getVariable("2").getExpression().getBaseExpressionPointer(), storm::expressions::BinaryRelationExpression::RelationType::Greater)); - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.checkOnSamples(assumption)); + EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumption(assumption, order, region)); + + assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( + storm::expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), + expressionManager->getVariable("2").getExpression().getBaseExpressionPointer(), + expressionManager->getVariable("1").getExpression().getBaseExpressionPointer(), + storm::expressions::BinaryRelationExpression::RelationType::Greater)); + EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumption(assumption, order, region)); + + assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( + storm::expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), + expressionManager->getVariable("1").getExpression().getBaseExpressionPointer(), + expressionManager->getVariable("2").getExpression().getBaseExpressionPointer(), + storm::expressions::BinaryRelationExpression::RelationType::Equal)); + EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumption(assumption, order, region)); + + region = storm::api::parseRegion<storm::RationalFunction>("0.51 <= p <= 0.99", vars); + assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( + storm::expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), + expressionManager->getVariable("1").getExpression().getBaseExpressionPointer(), + expressionManager->getVariable("2").getExpression().getBaseExpressionPointer(), + storm::expressions::BinaryRelationExpression::RelationType::Greater)); + EXPECT_EQ(storm::analysis::AssumptionStatus::VALID, checker.validateAssumption(assumption, order, region)); assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( storm::expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), expressionManager->getVariable("2").getExpression().getBaseExpressionPointer(), expressionManager->getVariable("1").getExpression().getBaseExpressionPointer(), storm::expressions::BinaryRelationExpression::RelationType::Greater)); - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.checkOnSamples(assumption)); + EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumption(assumption, order, region)); assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( storm::expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), expressionManager->getVariable("1").getExpression().getBaseExpressionPointer(), expressionManager->getVariable("2").getExpression().getBaseExpressionPointer(), storm::expressions::BinaryRelationExpression::RelationType::Equal)); - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.checkOnSamples(assumption)); + EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumption(assumption, order, region)); } -TEST(AssumptionCheckerTest, Simple2) { - std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/simple2.pm"; +TEST(AssumptionCheckerTest, Casestudy1) { + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/casestudy1.pm"; std::string formulaAsString = "P=? [F s=3]"; std::string constantsAsString = ""; //e.g. pL=0.9,TOACK=0.5 @@ -183,14 +202,14 @@ TEST(AssumptionCheckerTest, Simple2) { model = simplifier.getSimplifiedModel(); dtmc = model->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - ASSERT_EQ(dtmc->getNumberOfStates(), 5ul); - ASSERT_EQ(dtmc->getNumberOfTransitions(), 8ul); + ASSERT_EQ(dtmc->getNumberOfStates(), 5); + ASSERT_EQ(dtmc->getNumberOfTransitions(), 8); // Create the region auto vars = storm::models::sparse::getProbabilityParameters(*dtmc); auto region = storm::api::parseRegion<storm::RationalFunction>("0.00001 <= p <= 0.99999", vars); - auto checker = storm::analysis::AssumptionChecker<storm::RationalFunction>(formulas[0], dtmc, region, 3); + auto checker = storm::analysis::AssumptionChecker<storm::RationalFunction, double>(dtmc->getTransitionMatrix()); auto expressionManager = std::make_shared<storm::expressions::ExpressionManager>(storm::expressions::ExpressionManager()); expressionManager->declareRationalVariable("1"); @@ -200,28 +219,27 @@ TEST(AssumptionCheckerTest, Simple2) { above.set(3); storm::storage::BitVector below(5); below.set(4); - storm::storage::BitVector initialMiddle(5); - std::vector<uint_fast64_t> statesSorted = storm::utility::graph::getTopologicalSort(model->getTransitionMatrix()); + storm::storage::StronglyConnectedComponentDecompositionOptions options; + options.forceTopologicalSort(); + auto decomposition = storm::storage::StronglyConnectedComponentDecomposition<storm::RationalFunction>(model->getTransitionMatrix(), options); + auto statesSorted = storm::utility::graph::getTopologicalSort(model->getTransitionMatrix()); + auto order = std::shared_ptr<storm::analysis::Order>(new storm::analysis::Order(&above, &below, 5, decomposition, statesSorted)); - auto order = new storm::analysis::Order(&above, &below, &initialMiddle, 5, &statesSorted); - - // Checking on samples and validate + // Validating auto assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( storm::expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), expressionManager->getVariable("1").getExpression().getBaseExpressionPointer(), expressionManager->getVariable("2").getExpression().getBaseExpressionPointer(), storm::expressions::BinaryRelationExpression::RelationType::Greater)); - EXPECT_EQ(storm::analysis::AssumptionStatus::UNKNOWN, checker.checkOnSamples(assumption)); - EXPECT_EQ(storm::analysis::AssumptionStatus::VALID, checker.validateAssumption(assumption, order)); + EXPECT_EQ(storm::analysis::AssumptionStatus::VALID, checker.validateAssumption(assumption, order, region)); assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( storm::expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), expressionManager->getVariable("2").getExpression().getBaseExpressionPointer(), expressionManager->getVariable("1").getExpression().getBaseExpressionPointer(), storm::expressions::BinaryRelationExpression::RelationType::Greater)); - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.checkOnSamples(assumption)); - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumption(assumption, order)); + EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumption(assumption, order, region)); assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( @@ -229,12 +247,34 @@ TEST(AssumptionCheckerTest, Simple2) { expressionManager->getVariable("1").getExpression().getBaseExpressionPointer(), expressionManager->getVariable("2").getExpression().getBaseExpressionPointer(), storm::expressions::BinaryRelationExpression::RelationType::Equal)); - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.checkOnSamples(assumption)); - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumption(assumption, order)); + EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumption(assumption, order, region)); + + checker.initializeCheckingOnSamples(formulas[0], dtmc, region, 3); + assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( + storm::expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), + expressionManager->getVariable("1").getExpression().getBaseExpressionPointer(), + expressionManager->getVariable("2").getExpression().getBaseExpressionPointer(), + storm::expressions::BinaryRelationExpression::RelationType::Greater)); + EXPECT_EQ(storm::analysis::AssumptionStatus::VALID, checker.validateAssumption(assumption, order, region)); + + assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( + storm::expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), + expressionManager->getVariable("2").getExpression().getBaseExpressionPointer(), + expressionManager->getVariable("1").getExpression().getBaseExpressionPointer(), + storm::expressions::BinaryRelationExpression::RelationType::Greater)); + EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumption(assumption, order, region)); + + + assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( + storm::expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), + expressionManager->getVariable("1").getExpression().getBaseExpressionPointer(), + expressionManager->getVariable("2").getExpression().getBaseExpressionPointer(), + storm::expressions::BinaryRelationExpression::RelationType::Equal)); + EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumption(assumption, order, region)); } -TEST(AssumptionCheckerTest, Simple3) { - std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/simple3.pm"; +TEST(AssumptionCheckerTest, Casestudy2) { + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/casestudy2.pm"; std::string formulaAsString = "P=? [F s=4]"; std::string constantsAsString = ""; //e.g. pL=0.9,TOACK=0.5 @@ -256,7 +296,7 @@ TEST(AssumptionCheckerTest, Simple3) { auto vars = storm::models::sparse::getProbabilityParameters(*dtmc); auto region = storm::api::parseRegion<storm::RationalFunction>("0.00001 <= p <= 0.99999", vars); - auto checker = storm::analysis::AssumptionChecker<storm::RationalFunction>(formulas[0], dtmc, region, 3); + auto checker = storm::analysis::AssumptionChecker<storm::RationalFunction, double>(dtmc->getTransitionMatrix()); auto expressionManager = std::make_shared<storm::expressions::ExpressionManager>(storm::expressions::ExpressionManager()); expressionManager->declareRationalVariable("1"); @@ -266,11 +306,12 @@ TEST(AssumptionCheckerTest, Simple3) { above.set(4); storm::storage::BitVector below(6); below.set(5); - storm::storage::BitVector initialMiddle(6); - std::vector<uint_fast64_t> statesSorted = storm::utility::graph::getTopologicalSort(model->getTransitionMatrix()); - - auto order = new storm::analysis::Order(&above, &below, &initialMiddle, 6, &statesSorted); + storm::storage::StronglyConnectedComponentDecompositionOptions options; + options.forceTopologicalSort(); + auto decomposition = storm::storage::StronglyConnectedComponentDecomposition<storm::RationalFunction>(model->getTransitionMatrix(), options); + auto statesSorted = storm::utility::graph::getTopologicalSort(model->getTransitionMatrix()); + auto order = std::shared_ptr<storm::analysis::Order>(new storm::analysis::Order(&above, &below, 6, decomposition, statesSorted)); order->add(3); // Checking on samples and validate @@ -279,31 +320,25 @@ TEST(AssumptionCheckerTest, Simple3) { expressionManager->getVariable("1").getExpression().getBaseExpressionPointer(), expressionManager->getVariable("2").getExpression().getBaseExpressionPointer(), storm::expressions::BinaryRelationExpression::RelationType::Greater)); - EXPECT_EQ(storm::analysis::AssumptionStatus::UNKNOWN, checker.checkOnSamples(assumption)); - EXPECT_EQ(storm::analysis::AssumptionStatus::VALID, checker.validateAssumption(assumption, order)); - EXPECT_EQ(storm::analysis::AssumptionStatus::VALID, checker.validateAssumptionSMTSolver(assumption, order)); + EXPECT_EQ(storm::analysis::AssumptionStatus::VALID, checker.validateAssumption(assumption, order, region)); assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( storm::expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), expressionManager->getVariable("2").getExpression().getBaseExpressionPointer(), expressionManager->getVariable("1").getExpression().getBaseExpressionPointer(), storm::expressions::BinaryRelationExpression::RelationType::Greater)); - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.checkOnSamples(assumption)); - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumption(assumption, order)); - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumptionSMTSolver(assumption, order)); + EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumption(assumption, order, region)); assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( storm::expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), expressionManager->getVariable("1").getExpression().getBaseExpressionPointer(), expressionManager->getVariable("2").getExpression().getBaseExpressionPointer(), storm::expressions::BinaryRelationExpression::RelationType::Equal)); - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.checkOnSamples(assumption)); - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumption(assumption, order)); - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumptionSMTSolver(assumption, order)); + EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumption(assumption, order, region)); } -TEST(AssumptionCheckerTest, Simple4) { - std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/simple4.pm"; +TEST(AssumptionCheckerTest, Casestudy3) { + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/casestudy3.pm"; std::string formulaAsString = "P=? [F s=3]"; std::string constantsAsString = ""; //e.g. pL=0.9,TOACK=0.5 @@ -318,14 +353,14 @@ TEST(AssumptionCheckerTest, Simple4) { model = simplifier.getSimplifiedModel(); dtmc = model->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - ASSERT_EQ(dtmc->getNumberOfStates(), 5ul); - ASSERT_EQ(dtmc->getNumberOfTransitions(), 8ul); + ASSERT_EQ(dtmc->getNumberOfStates(), 5); + ASSERT_EQ(dtmc->getNumberOfTransitions(), 8); // Create the region auto vars = storm::models::sparse::getProbabilityParameters(*dtmc); auto region = storm::api::parseRegion<storm::RationalFunction>("0.00001 <= p <= 0.4", vars); - auto checker = storm::analysis::AssumptionChecker<storm::RationalFunction>(formulas[0], dtmc, region, 3); + auto checker = storm::analysis::AssumptionChecker<storm::RationalFunction, double>(dtmc->getTransitionMatrix()); auto expressionManager = std::make_shared<storm::expressions::ExpressionManager>(storm::expressions::ExpressionManager()); expressionManager->declareRationalVariable("1"); @@ -336,38 +371,53 @@ TEST(AssumptionCheckerTest, Simple4) { above.set(3); storm::storage::BitVector below(5); below.set(4); - storm::storage::BitVector initialMiddle(5); - std::vector<uint_fast64_t> statesSorted = storm::utility::graph::getTopologicalSort(model->getTransitionMatrix()); - - auto order = new storm::analysis::Order(&above, &below, &initialMiddle, 5, &statesSorted); + storm::storage::StronglyConnectedComponentDecompositionOptions options; + options.forceTopologicalSort(); + auto decomposition = storm::storage::StronglyConnectedComponentDecomposition<storm::RationalFunction>(model->getTransitionMatrix(), options); + auto statesSorted = storm::utility::graph::getTopologicalSort(model->getTransitionMatrix()); + auto order = std::shared_ptr<storm::analysis::Order>(new storm::analysis::Order(&above, &below, 5, decomposition, statesSorted)); auto assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( storm::expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), expressionManager->getVariable("1").getExpression().getBaseExpressionPointer(), expressionManager->getVariable("2").getExpression().getBaseExpressionPointer(), storm::expressions::BinaryRelationExpression::RelationType::Greater)); - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.checkOnSamples(assumption)); - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumption(assumption, order)); - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumptionSMTSolver(assumption, order)); + EXPECT_EQ(storm::analysis::AssumptionStatus::VALID, checker.validateAssumption(assumption, order, region)); assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( storm::expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), expressionManager->getVariable("2").getExpression().getBaseExpressionPointer(), expressionManager->getVariable("1").getExpression().getBaseExpressionPointer(), storm::expressions::BinaryRelationExpression::RelationType::Greater)); - EXPECT_EQ(storm::analysis::AssumptionStatus::UNKNOWN, checker.checkOnSamples(assumption)); - EXPECT_EQ(storm::analysis::AssumptionStatus::VALID, checker.validateAssumption(assumption, order)); - EXPECT_EQ(storm::analysis::AssumptionStatus::VALID, checker.validateAssumptionSMTSolver(assumption, order)); + EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumption(assumption, order, region)); assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( storm::expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), expressionManager->getVariable("1").getExpression().getBaseExpressionPointer(), expressionManager->getVariable("2").getExpression().getBaseExpressionPointer(), storm::expressions::BinaryRelationExpression::RelationType::Equal)); - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.checkOnSamples(assumption)); - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumption(assumption, order)); - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumptionSMTSolver(assumption, order)); + EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumption(assumption, order, region)); + checker.initializeCheckingOnSamples(formulas[0], dtmc, region, 3); + assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( + storm::expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), + expressionManager->getVariable("1").getExpression().getBaseExpressionPointer(), + expressionManager->getVariable("2").getExpression().getBaseExpressionPointer(), + storm::expressions::BinaryRelationExpression::RelationType::Greater)); + EXPECT_EQ(storm::analysis::AssumptionStatus::VALID, checker.validateAssumption(assumption, order, region)); + assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( + storm::expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), + expressionManager->getVariable("2").getExpression().getBaseExpressionPointer(), + expressionManager->getVariable("1").getExpression().getBaseExpressionPointer(), + storm::expressions::BinaryRelationExpression::RelationType::Greater)); + EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumption(assumption, order, region)); + + assumption = std::make_shared<storm::expressions::BinaryRelationExpression>( + storm::expressions::BinaryRelationExpression(*expressionManager, expressionManager->getBooleanType(), + expressionManager->getVariable("1").getExpression().getBaseExpressionPointer(), + expressionManager->getVariable("2").getExpression().getBaseExpressionPointer(), + storm::expressions::BinaryRelationExpression::RelationType::Equal)); + EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, checker.validateAssumption(assumption, order, region)); } diff --git a/src/test/storm-pars/analysis/AssumptionMakerTest.cpp b/src/test/storm-pars/analysis/AssumptionMakerTest.cpp index 510d5757f..ecc764fca 100644 --- a/src/test/storm-pars/analysis/AssumptionMakerTest.cpp +++ b/src/test/storm-pars/analysis/AssumptionMakerTest.cpp @@ -1,32 +1,19 @@ -// -// Created by Jip Spel on 20.09.18. -// - -// TODO: cleanup includes -#include "test/storm_gtest.h" +#include <storm/storage/StronglyConnectedComponentDecomposition.h> #include "storm-config.h" #include "test/storm_gtest.h" -#include "storm-parsers/parser/FormulaParser.h" -#include "storm/logic/Formulas.h" -#include "storm/models/sparse/StandardRewardModel.h" -#include "storm/modelchecker/prctl/SparseDtmcPrctlModelChecker.h" -#include "storm/modelchecker/results/ExplicitQuantitativeCheckResult.h" +#include "storm-pars/api/analysis.h" +#include "storm-pars/transformer/SparseParametricDtmcSimplifier.h" + +#include "storm-parsers/api/storm-parsers.h" #include "storm-parsers/parser/AutoParser.h" #include "storm-parsers/parser/PrismParser.h" -#include "storm/storage/expressions/ExpressionManager.h" -#include "storm/api/builder.h" - -#include "storm-pars/analysis/Order.h" -#include "storm-pars/analysis/AssumptionMaker.h" -#include "storm-pars/analysis/AssumptionChecker.h" -#include "storm-pars/analysis/OrderExtender.h" -#include "storm-pars/transformer/SparseParametricDtmcSimplifier.h" -#include "storm-pars/api/storm-pars.h" +#include "storm/api/builder.h" #include "storm/api/storm.h" - -#include "storm-parsers/api/storm-parsers.h" +#include "storm/logic/Formulas.h" +#include "storm/modelchecker/prctl/SparseDtmcPrctlModelChecker.h" +#include "storm/models/sparse/StandardRewardModel.h" TEST(AssumptionMakerTest, Brp_without_bisimulation) { std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/brp16_2.pm"; @@ -38,280 +25,138 @@ TEST(AssumptionMakerTest, Brp_without_bisimulation) { program = storm::utility::prism::preprocess(program, constantsAsString); std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> dtmc = model->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*dtmc); + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*model); ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); model = simplifier.getSimplifiedModel(); - dtmc = model->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + model = model->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); // Create the region - auto vars = storm::models::sparse::getProbabilityParameters(*dtmc); + auto vars = storm::models::sparse::getProbabilityParameters(*model); auto region = storm::api::parseRegion<storm::RationalFunction>("0.00001 <= pK <= 0.999999, 0.00001 <= pL <= 0.999999", vars); - ASSERT_EQ(dtmc->getNumberOfStates(), 193ull); - ASSERT_EQ(dtmc->getNumberOfTransitions(), 383ull); + ASSERT_EQ(model->getNumberOfStates(), 193); + ASSERT_EQ(model->getNumberOfTransitions(), 383); - auto *extender = new storm::analysis::OrderExtender<storm::RationalFunction>(dtmc); - auto criticalTuple = extender->toOrder(formulas); - ASSERT_EQ(183ul, std::get<1>(criticalTuple)); - ASSERT_EQ(186ul, std::get<2>(criticalTuple)); + auto *extender = new storm::analysis::OrderExtender<storm::RationalFunction, double>(model, formulas[0]); + auto criticalTuple = extender->toOrder(region, nullptr); + ASSERT_EQ(183, std::get<1>(criticalTuple)); + ASSERT_EQ(186, std::get<2>(criticalTuple)); - auto assumptionChecker = storm::analysis::AssumptionChecker<storm::RationalFunction>(formulas[0], dtmc, region, 3); - auto assumptionMaker = storm::analysis::AssumptionMaker<storm::RationalFunction>(&assumptionChecker, dtmc->getNumberOfStates(), true); - auto result = assumptionMaker.createAndCheckAssumption(std::get<1>(criticalTuple), std::get<2>(criticalTuple), std::get<0>(criticalTuple)); + auto assumptionMaker = storm::analysis::AssumptionMaker<storm::RationalFunction, double>(model->getTransitionMatrix()); + auto result = assumptionMaker.createAndCheckAssumptions(std::get<1>(criticalTuple), std::get<2>(criticalTuple), std::get<0>(criticalTuple), region); - EXPECT_EQ(3ul, result.size()); + EXPECT_EQ(3, result.size()); - bool foundFirst = false; - bool foundSecond = false; - bool foundThird = false; - for (auto itr = result.begin(); itr != result.end(); ++itr) { - if (!foundFirst && itr->first->getFirstOperand()->asVariableExpression().getVariable().getName() == "183") { - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, itr->second); - EXPECT_EQ(true, itr->first->getFirstOperand()->isVariable()); - EXPECT_EQ(true, itr->first->getSecondOperand()->isVariable()); - EXPECT_EQ("186", itr->first->getSecondOperand()->asVariableExpression().getVariable().getName()); - EXPECT_EQ(storm::expressions::BinaryRelationExpression::RelationType::Greater, itr->first->getRelationType()); - foundFirst = true; - } else if (!foundSecond && itr->first->getRelationType() == storm::expressions::BinaryRelationExpression::RelationType::Greater) { - EXPECT_EQ(storm::analysis::AssumptionStatus::UNKNOWN, itr->second); - EXPECT_EQ(true, itr->first->getFirstOperand()->isVariable()); - EXPECT_EQ(true, itr->first->getSecondOperand()->isVariable()); - EXPECT_EQ("186", itr->first->getFirstOperand()->asVariableExpression().getVariable().getName()); - EXPECT_EQ("183", itr->first->getSecondOperand()->asVariableExpression().getVariable().getName()); - EXPECT_EQ(storm::expressions::BinaryRelationExpression::RelationType::Greater, itr->first->getRelationType()); - foundSecond = true; - } else if (!foundThird && itr->first->getRelationType() == storm::expressions::BinaryRelationExpression::RelationType::Equal) { - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, itr->second); - EXPECT_EQ(true, itr->first->getFirstOperand()->isVariable()); - EXPECT_EQ(true, itr->first->getSecondOperand()->isVariable()); - EXPECT_EQ("186", itr->first->getFirstOperand()->asVariableExpression().getVariable().getName()); - EXPECT_EQ("183", itr->first->getSecondOperand()->asVariableExpression().getVariable().getName()); - EXPECT_EQ(storm::expressions::BinaryRelationExpression::RelationType::Equal, itr->first->getRelationType()); - foundThird = true; - } else { - EXPECT_TRUE(false); - } + for (auto res : result) { + EXPECT_EQ(storm::analysis::AssumptionStatus::UNKNOWN, res.second); + EXPECT_EQ(true, res.first->getFirstOperand()->isVariable()); + EXPECT_EQ(true, res.first->getSecondOperand()->isVariable()); } - EXPECT_TRUE(foundFirst); - EXPECT_TRUE(foundSecond); - EXPECT_TRUE(foundThird); -} - -TEST(AssumptionMakerTest, Brp_without_bisimulation_no_validation) { - std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/brp16_2.pm"; - std::string formulaAsString = "P=? [F s=4 & i=N ]"; - std::string constantsAsString = ""; //e.g. pL=0.9,TOACK=0.5 - - // Program and formula - storm::prism::Program program = storm::api::parseProgram(programFile); - program = storm::utility::prism::preprocess(program, constantsAsString); - std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); - std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> dtmc = model->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*dtmc); - ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); - model = simplifier.getSimplifiedModel(); - dtmc = model->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - - // Create the region - auto vars = storm::models::sparse::getProbabilityParameters(*dtmc); - auto region = storm::api::parseRegion<storm::RationalFunction>("0.00001 <= pK <= 0.999999, 0.00001 <= pL <= 0.999999", vars); - ASSERT_EQ(dtmc->getNumberOfStates(), 193ull); - ASSERT_EQ(dtmc->getNumberOfTransitions(), 383ull); - - auto *extender = new storm::analysis::OrderExtender<storm::RationalFunction>(dtmc); - auto criticalTuple = extender->toOrder(formulas); - ASSERT_EQ(183ul, std::get<1>(criticalTuple)); - ASSERT_EQ(186ul, std::get<2>(criticalTuple)); - - auto assumptionChecker = storm::analysis::AssumptionChecker<storm::RationalFunction>(formulas[0], dtmc, region, 3); - // This one does not validate the assumptions! - auto assumptionMaker = storm::analysis::AssumptionMaker<storm::RationalFunction>(&assumptionChecker, dtmc->getNumberOfStates(), false); - auto result = assumptionMaker.createAndCheckAssumption(std::get<1>(criticalTuple), std::get<2>(criticalTuple), std::get<0>(criticalTuple)); - - EXPECT_EQ(3ul, result.size()); - - bool foundFirst = false; - bool foundSecond = false; - bool foundThird = false; - for (auto itr = result.begin(); itr != result.end(); ++itr) { - if (!foundFirst && itr->first->getFirstOperand()->asVariableExpression().getVariable().getName() == "183") { - EXPECT_EQ(storm::analysis::AssumptionStatus::UNKNOWN, itr->second); - EXPECT_EQ(true, itr->first->getFirstOperand()->isVariable()); - EXPECT_EQ(true, itr->first->getSecondOperand()->isVariable()); - EXPECT_EQ("186", itr->first->getSecondOperand()->asVariableExpression().getVariable().getName()); - EXPECT_EQ(storm::expressions::BinaryRelationExpression::RelationType::Greater, itr->first->getRelationType()); - foundFirst = true; - } else if (!foundSecond && itr->first->getRelationType() == storm::expressions::BinaryRelationExpression::RelationType::Greater) { - EXPECT_EQ(storm::analysis::AssumptionStatus::UNKNOWN, itr->second); - EXPECT_EQ(true, itr->first->getFirstOperand()->isVariable()); - EXPECT_EQ(true, itr->first->getSecondOperand()->isVariable()); - EXPECT_EQ("186", itr->first->getFirstOperand()->asVariableExpression().getVariable().getName()); - EXPECT_EQ("183", itr->first->getSecondOperand()->asVariableExpression().getVariable().getName()); - EXPECT_EQ(storm::expressions::BinaryRelationExpression::RelationType::Greater, itr->first->getRelationType()); - foundSecond = true; - } else if (!foundThird && itr->first->getRelationType() == storm::expressions::BinaryRelationExpression::RelationType::Equal) { - EXPECT_EQ(storm::analysis::AssumptionStatus::UNKNOWN, itr->second); - EXPECT_EQ(true, itr->first->getFirstOperand()->isVariable()); - EXPECT_EQ(true, itr->first->getSecondOperand()->isVariable()); - EXPECT_EQ("186", itr->first->getFirstOperand()->asVariableExpression().getVariable().getName()); - EXPECT_EQ("183", itr->first->getSecondOperand()->asVariableExpression().getVariable().getName()); - EXPECT_EQ(storm::expressions::BinaryRelationExpression::RelationType::Equal, itr->first->getRelationType()); - foundThird = true; - } else { - EXPECT_TRUE(false); - } - } - EXPECT_TRUE(foundFirst); - EXPECT_TRUE(foundSecond); - EXPECT_TRUE(foundThird); + assumptionMaker.initializeCheckingOnSamples(formulas[0], model, region, 10); + result = assumptionMaker.createAndCheckAssumptions(std::get<1>(criticalTuple), std::get<2>(criticalTuple), std::get<0>(criticalTuple), region); + EXPECT_EQ(1, result.size()); + auto itr = result.begin(); + EXPECT_EQ(storm::analysis::AssumptionStatus::UNKNOWN, itr->second); + EXPECT_EQ(true, itr->first->getFirstOperand()->isVariable()); + EXPECT_EQ(true, itr->first->getSecondOperand()->isVariable()); + EXPECT_EQ("186", itr->first->getFirstOperand()->asVariableExpression().getVariable().getName()); + EXPECT_EQ("183", itr->first->getSecondOperand()->asVariableExpression().getVariable().getName()); + EXPECT_EQ(storm::expressions::BinaryRelationExpression::RelationType::Greater, itr->first->getRelationType()); } + TEST(AssumptionMakerTest, Simple1) { std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/simple1.pm"; - std::string formulaAsString = "P=? [F s=3]"; - std::string constantsAsString = ""; //e.g. pL=0.9,TOACK=0.5 + std::string formulaAsString = "P=? [F s=3 ]"; + std::string constantsAsString = ""; storm::prism::Program program = storm::api::parseProgram(programFile); program = storm::utility::prism::preprocess(program, constantsAsString); std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> dtmc = model->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*dtmc); + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*model); ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); model = simplifier.getSimplifiedModel(); - dtmc = model->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); // Create the region - auto vars = storm::models::sparse::getProbabilityParameters(*dtmc); + auto vars = storm::models::sparse::getProbabilityParameters(*model); auto region = storm::api::parseRegion<storm::RationalFunction>("0.00001 <= p <= 0.999999", vars); - ASSERT_EQ(dtmc->getNumberOfStates(), 5ul); - ASSERT_EQ(dtmc->getNumberOfTransitions(), 8ul); + ASSERT_EQ(model->getNumberOfStates(), 5); + ASSERT_EQ(model->getNumberOfTransitions(), 8); storm::storage::BitVector above(5); above.set(3); storm::storage::BitVector below(5); below.set(4); - storm::storage::BitVector initialMiddle(5); - std::vector<uint_fast64_t> statesSorted = storm::utility::graph::getTopologicalSort(model->getTransitionMatrix()); - - auto order = new storm::analysis::Order(&above, &below, &initialMiddle, 5, &statesSorted); - - auto assumptionChecker = storm::analysis::AssumptionChecker<storm::RationalFunction>(formulas[0], dtmc, region, 3); - auto assumptionMaker = storm::analysis::AssumptionMaker<storm::RationalFunction>(&assumptionChecker, dtmc->getNumberOfStates(), true); - auto result = assumptionMaker.createAndCheckAssumption(1, 2, order); - - EXPECT_EQ(3ul, result.size()); - - bool foundFirst = false; - bool foundSecond = false; - bool foundThird = false; - for (auto itr = result.begin(); itr != result.end(); ++itr) { - if (!foundFirst && itr->first->getFirstOperand()->asVariableExpression().getVariable().getName() == "1") { - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, itr->second); - EXPECT_EQ(true, itr->first->getFirstOperand()->isVariable()); - EXPECT_EQ(true, itr->first->getSecondOperand()->isVariable()); - EXPECT_EQ("2", itr->first->getSecondOperand()->asVariableExpression().getVariable().getName()); - EXPECT_EQ(storm::expressions::BinaryRelationExpression::RelationType::Greater, itr->first->getRelationType()); - foundFirst = true; - } else if (!foundSecond && itr->first->getRelationType() == storm::expressions::BinaryRelationExpression::RelationType::Greater) { - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, itr->second); - EXPECT_EQ(true, itr->first->getFirstOperand()->isVariable()); - EXPECT_EQ(true, itr->first->getSecondOperand()->isVariable()); - EXPECT_EQ("2", itr->first->getFirstOperand()->asVariableExpression().getVariable().getName()); - EXPECT_EQ("1", itr->first->getSecondOperand()->asVariableExpression().getVariable().getName()); - EXPECT_EQ(storm::expressions::BinaryRelationExpression::RelationType::Greater, itr->first->getRelationType()); - foundSecond = true; - } else if (!foundThird && itr->first->getRelationType() == storm::expressions::BinaryRelationExpression::RelationType::Equal) { - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, itr->second); - EXPECT_EQ(true, itr->first->getFirstOperand()->isVariable()); - EXPECT_EQ(true, itr->first->getSecondOperand()->isVariable()); - EXPECT_EQ("2", itr->first->getFirstOperand()->asVariableExpression().getVariable().getName()); - EXPECT_EQ("1", itr->first->getSecondOperand()->asVariableExpression().getVariable().getName()); - EXPECT_EQ(storm::expressions::BinaryRelationExpression::RelationType::Equal, itr->first->getRelationType()); - foundThird = true; - } else { - EXPECT_TRUE(false); - } - } - EXPECT_TRUE(foundFirst); - EXPECT_TRUE(foundSecond); - EXPECT_TRUE(foundThird); + storm::storage::StronglyConnectedComponentDecompositionOptions options; + options.forceTopologicalSort(); + auto decomposition = storm::storage::StronglyConnectedComponentDecomposition<storm::RationalFunction>(model->getTransitionMatrix(), options); + auto statesSorted = storm::utility::graph::getTopologicalSort(model->getTransitionMatrix()); + auto order = std::shared_ptr<storm::analysis::Order>(new storm::analysis::Order(&above, &below, 5, decomposition, statesSorted)); + + auto assumptionMaker = storm::analysis::AssumptionMaker<storm::RationalFunction, double>(model->getTransitionMatrix()); + auto result = assumptionMaker.createAndCheckAssumptions(1, 2, order, region); + EXPECT_EQ(0, result.size()); + assumptionMaker.initializeCheckingOnSamples(formulas[0], model, region, 10); + result = assumptionMaker.createAndCheckAssumptions(1, 2, order, region); + EXPECT_EQ(0, result.size()); + + region = storm::api::parseRegion<storm::RationalFunction>("0.500001 <= p <= 0.999999", vars); + std::vector<std::vector<double>> samples; + assumptionMaker.setSampleValues(samples); + result = assumptionMaker.createAndCheckAssumptions(1, 2, order, region); + EXPECT_EQ(1, result.size()); + auto itr = result.begin(); + EXPECT_EQ(storm::analysis::AssumptionStatus::VALID, itr->second); + EXPECT_EQ(true, itr->first->getFirstOperand()->isVariable()); + EXPECT_EQ(true, itr->first->getSecondOperand()->isVariable()); + EXPECT_EQ("1", itr->first->getFirstOperand()->asVariableExpression().getVariable().getName()); + EXPECT_EQ("2", itr->first->getSecondOperand()->asVariableExpression().getVariable().getName()); + EXPECT_EQ(storm::expressions::BinaryRelationExpression::RelationType::Greater, itr->first->getRelationType()); } -TEST(AssumptionMakerTest, Simple2) { - std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/simple2.pm"; - std::string formulaAsString = "P=? [F s=3]"; - std::string constantsAsString = ""; //e.g. pL=0.9,TOACK=0.5 +TEST(AssumptionMakerTest, Casestudy1) { + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/casestudy1.pm"; + std::string formulaAsString = "P=? [F s=3 ]"; + std::string constantsAsString = ""; storm::prism::Program program = storm::api::parseProgram(programFile); program = storm::utility::prism::preprocess(program, constantsAsString); std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> dtmc = model->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*dtmc); + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*model); ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); model = simplifier.getSimplifiedModel(); - dtmc = model->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + model = model->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); // Create the region - auto vars = storm::models::sparse::getProbabilityParameters(*dtmc); + auto vars = storm::models::sparse::getProbabilityParameters(*model); auto region = storm::api::parseRegion<storm::RationalFunction>("0.00001 <= p <= 0.999999", vars); - ASSERT_EQ(dtmc->getNumberOfStates(), 5ul); - ASSERT_EQ(dtmc->getNumberOfTransitions(), 8ul); + ASSERT_EQ(model->getNumberOfStates(), 5); + ASSERT_EQ(model->getNumberOfTransitions(), 8); storm::storage::BitVector above(5); above.set(3); storm::storage::BitVector below(5); below.set(4); - storm::storage::BitVector initialMiddle(5); - std::vector<uint_fast64_t> statesSorted = storm::utility::graph::getTopologicalSort(model->getTransitionMatrix()); - - auto order = new storm::analysis::Order(&above, &below, &initialMiddle, 5, &statesSorted); - - auto assumptionChecker = storm::analysis::AssumptionChecker<storm::RationalFunction>(formulas[0], dtmc, region, 3); - auto assumptionMaker = storm::analysis::AssumptionMaker<storm::RationalFunction>(&assumptionChecker, dtmc->getNumberOfStates(), true); - auto result = assumptionMaker.createAndCheckAssumption(1, 2, order); - - EXPECT_EQ(3ul, result.size()); - - bool foundFirst = false; - bool foundSecond = false; - bool foundThird = false; - for (auto itr = result.begin(); itr != result.end(); ++itr) { - if (!foundFirst && itr->first->getFirstOperand()->asVariableExpression().getVariable().getName() == "1") { - EXPECT_EQ(storm::analysis::AssumptionStatus::VALID, itr->second); - EXPECT_EQ(true, itr->first->getFirstOperand()->isVariable()); - EXPECT_EQ(true, itr->first->getSecondOperand()->isVariable()); - EXPECT_EQ("2", itr->first->getSecondOperand()->asVariableExpression().getVariable().getName()); - EXPECT_EQ(storm::expressions::BinaryRelationExpression::RelationType::Greater, itr->first->getRelationType()); - foundFirst = true; - } else if (!foundSecond && itr->first->getRelationType() == storm::expressions::BinaryRelationExpression::RelationType::Greater) { - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, itr->second); - EXPECT_EQ(true, itr->first->getFirstOperand()->isVariable()); - EXPECT_EQ(true, itr->first->getSecondOperand()->isVariable()); - EXPECT_EQ("2", itr->first->getFirstOperand()->asVariableExpression().getVariable().getName()); - EXPECT_EQ("1", itr->first->getSecondOperand()->asVariableExpression().getVariable().getName()); - EXPECT_EQ(storm::expressions::BinaryRelationExpression::RelationType::Greater, itr->first->getRelationType()); - foundSecond = true; - } else if (!foundThird && itr->first->getRelationType() == storm::expressions::BinaryRelationExpression::RelationType::Equal) { - EXPECT_EQ(storm::analysis::AssumptionStatus::INVALID, itr->second); - EXPECT_EQ(true, itr->first->getFirstOperand()->isVariable()); - EXPECT_EQ(true, itr->first->getSecondOperand()->isVariable()); - EXPECT_EQ("2", itr->first->getFirstOperand()->asVariableExpression().getVariable().getName()); - EXPECT_EQ("1", itr->first->getSecondOperand()->asVariableExpression().getVariable().getName()); - EXPECT_EQ(storm::expressions::BinaryRelationExpression::RelationType::Equal, itr->first->getRelationType()); - foundThird = true; - } else { - EXPECT_TRUE(false); - } - } - EXPECT_TRUE(foundFirst); - EXPECT_TRUE(foundSecond); - EXPECT_TRUE(foundThird); + storm::storage::StronglyConnectedComponentDecompositionOptions options; + options.forceTopologicalSort(); + auto decomposition = storm::storage::StronglyConnectedComponentDecomposition<storm::RationalFunction>(model->getTransitionMatrix(), options); + auto statesSorted = storm::utility::graph::getTopologicalSort(model->getTransitionMatrix()); + auto order = std::shared_ptr<storm::analysis::Order>(new storm::analysis::Order(&above, &below, 5, decomposition, statesSorted)); + + auto assumptionMaker = storm::analysis::AssumptionMaker<storm::RationalFunction, double>(model->getTransitionMatrix()); + auto result = assumptionMaker.createAndCheckAssumptions(1, 2, order, region); + + EXPECT_EQ(1, result.size()); + auto itr = result.begin(); + EXPECT_EQ(storm::analysis::AssumptionStatus::VALID, itr->second); + EXPECT_EQ(true, itr->first->getFirstOperand()->isVariable()); + EXPECT_EQ(true, itr->first->getSecondOperand()->isVariable()); + EXPECT_EQ("1", itr->first->getFirstOperand()->asVariableExpression().getVariable().getName()); + EXPECT_EQ("2", itr->first->getSecondOperand()->asVariableExpression().getVariable().getName()); + EXPECT_EQ(storm::expressions::BinaryRelationExpression::RelationType::Greater, itr->first->getRelationType()); } - diff --git a/src/test/storm-pars/analysis/MonotonicityCheckerTest.cpp b/src/test/storm-pars/analysis/MonotonicityCheckerTest.cpp index 14973cfc2..6fda1867b 100644 --- a/src/test/storm-pars/analysis/MonotonicityCheckerTest.cpp +++ b/src/test/storm-pars/analysis/MonotonicityCheckerTest.cpp @@ -1,119 +1,73 @@ -// -// Created by Jip Spel on 20.09.18. -// - -#include "test/storm_gtest.h" #include "storm-config.h" -#include "test/storm_gtest.h" -#include "storm-pars/analysis/MonotonicityChecker.h" -#include "storm/storage/expressions/BinaryRelationExpression.h" -#include "storm/storage/SparseMatrix.h" -#include "storm/adapters/RationalFunctionAdapter.h" -#include "storm-parsers/parser/FormulaParser.h" -#include "storm/logic/Formulas.h" -#include "storm/models/sparse/StandardRewardModel.h" -#include "storm/modelchecker/prctl/SparseDtmcPrctlModelChecker.h" -#include "storm/modelchecker/results/ExplicitQuantitativeCheckResult.h" +#include "storm-pars/api/storm-pars.h" +#include "storm-pars/transformer/SparseParametricDtmcSimplifier.h" + +#include "storm-parsers/api/storm-parsers.h" #include "storm-parsers/parser/AutoParser.h" #include "storm-parsers/parser/PrismParser.h" -#include "storm/storage/expressions/ExpressionManager.h" + +#include "storm/adapters/RationalFunctionAdapter.h" #include "storm/api/builder.h" +#include "storm/api/storm.h" +#include "storm/logic/Formulas.h" +#include "storm/modelchecker/prctl/SparseDtmcPrctlModelChecker.h" +#include "storm/modelchecker/results/ExplicitQualitativeCheckResult.h" +#include "storm/storage/SparseMatrix.h" -#include "storm-pars/transformer/SparseParametricDtmcSimplifier.h" +#include "test/storm_gtest.h" -#include "storm-pars/api/storm-pars.h" -#include "storm/api/storm.h" +TEST(MonotonicityCheckerTest, Simple1_larger_region) { + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/simple1.pm"; + std::string formulaAsString = "P=? [F s=3 ]"; + std::string constantsAsString = ""; -#include "storm-parsers/api/storm-parsers.h" -TEST(MonotonicityCheckerTest, Derivative_checker) { + // model + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> dtmc = model->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*dtmc); + ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); + model = simplifier.getSimplifiedModel(); // Create the region - typename storm::storage::ParameterRegion<storm::RationalFunction>::Valuation lowerBoundaries; - typename storm::storage::ParameterRegion<storm::RationalFunction>::Valuation upperBoundaries; - auto region = storm::storage::ParameterRegion<storm::RationalFunction>(std::move(lowerBoundaries), std::move(upperBoundaries)); - - // Derivative 0 - auto constFunction = storm::RationalFunction(0); - auto constFunctionRes = storm::analysis::MonotonicityChecker<storm::RationalFunction>::checkDerivative(constFunction, region); - EXPECT_TRUE(constFunctionRes.first); - EXPECT_TRUE(constFunctionRes.second); - - // Derivative 5 - constFunction = storm::RationalFunction(5); - constFunctionRes = storm::analysis::MonotonicityChecker<storm::RationalFunction>::checkDerivative(constFunction, region); - EXPECT_TRUE(constFunctionRes.first); - EXPECT_FALSE(constFunctionRes.second); - - // Derivative -4 - constFunction = storm::RationalFunction(storm::RationalFunction(1)-constFunction); - constFunctionRes = storm::analysis::MonotonicityChecker<storm::RationalFunction>::checkDerivative(constFunction, region); - EXPECT_FALSE(constFunctionRes.first); - EXPECT_TRUE(constFunctionRes.second); - - std::shared_ptr<storm::RawPolynomialCache> cache = std::make_shared<storm::RawPolynomialCache>(); - carl::StringParser parser; - parser.setVariables({"p", "q"}); + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto region=storm::api::parseRegion<storm::RationalFunction>("0.1<=p<=0.9", modelParameters); + std::vector<storm::storage::ParameterRegion<storm::RationalFunction>> regions = {region}; - // Create the region - auto functionP = storm::RationalFunction(storm::Polynomial(parser.template parseMultivariatePolynomial<storm::RationalFunctionCoefficient>("p"), cache)); - auto functionQ = storm::RationalFunction(storm::Polynomial(parser.template parseMultivariatePolynomial<storm::RationalFunctionCoefficient>("q"), cache)); - - auto varsP = functionP.gatherVariables(); - auto varsQ = functionQ.gatherVariables(); - storm::utility::parametric::Valuation<storm::RationalFunction> lowerBoundaries2; - storm::utility::parametric::Valuation<storm::RationalFunction> upperBoundaries2; - for (auto var : varsP) { - typename storm::storage::ParameterRegion<storm::RationalFunction>::CoefficientType lb = storm::utility::convertNumber<typename storm::storage::ParameterRegion<storm::RationalFunction>::CoefficientType>(0 + 0.000001); - typename storm::storage::ParameterRegion<storm::RationalFunction>::CoefficientType ub = storm::utility::convertNumber<typename storm::storage::ParameterRegion<storm::RationalFunction>::CoefficientType>(1 - 0.000001); - lowerBoundaries2.emplace(std::make_pair(var, lb)); - upperBoundaries2.emplace(std::make_pair(var, ub)); - } - for (auto var : varsQ) { - typename storm::storage::ParameterRegion<storm::RationalFunction>::CoefficientType lb = storm::utility::convertNumber<typename storm::storage::ParameterRegion<storm::RationalFunction>::CoefficientType>(0 + 0.000001); - typename storm::storage::ParameterRegion<storm::RationalFunction>::CoefficientType ub = storm::utility::convertNumber<typename storm::storage::ParameterRegion<storm::RationalFunction>::CoefficientType>(1 - 0.000001); - lowerBoundaries2.emplace(std::make_pair(var, lb)); - upperBoundaries2.emplace(std::make_pair(var, ub)); - } - region = storm::storage::ParameterRegion<storm::RationalFunction>(std::move(lowerBoundaries2), std::move(upperBoundaries2)); - - // Derivative p - auto function = functionP; - auto functionRes = storm::analysis::MonotonicityChecker<storm::RationalFunction>::checkDerivative(function, region); - EXPECT_TRUE(functionRes.first); - EXPECT_FALSE(functionRes.second); - - // Derivative 1-p - auto functionDecr = storm::RationalFunction(storm::RationalFunction(1)-function); - auto functionDecrRes = storm::analysis::MonotonicityChecker<storm::RationalFunction>::checkDerivative(functionDecr, region); - EXPECT_TRUE(functionDecrRes.first); - EXPECT_FALSE(functionDecrRes.second); - - // Derivative 1-2p - auto functionNonMonotonic = storm::RationalFunction(storm::RationalFunction(1)-storm::RationalFunction(2)*function); - auto functionNonMonotonicRes = storm::analysis::MonotonicityChecker<storm::RationalFunction>::checkDerivative(functionNonMonotonic, region); - EXPECT_FALSE(functionNonMonotonicRes.first); - EXPECT_FALSE(functionNonMonotonicRes.second); - - // Derivative -p - functionDecr = storm::RationalFunction(storm::RationalFunction(0)-function); - functionDecrRes = storm::analysis::MonotonicityChecker<storm::RationalFunction>::checkDerivative(functionDecr, region); - EXPECT_FALSE(functionDecrRes.first); - EXPECT_TRUE(functionDecrRes.second); - - // Derivative p*q - function = functionP * functionQ ; - functionRes = storm::analysis::MonotonicityChecker<storm::RationalFunction>::checkDerivative(function, region); - EXPECT_TRUE(functionRes.first); - EXPECT_FALSE(functionRes.second); + // For order extender + storm::modelchecker::SparsePropositionalModelChecker<storm::models::sparse::Model<storm::RationalFunction>> propositionalChecker(*model); + storm::storage::BitVector phiStates; + storm::storage::BitVector psiStates; + phiStates = storm::storage::BitVector(model->getTransitionMatrix().getRowCount(), true); + storm::logic::EventuallyFormula formula = formulas[0]->asProbabilityOperatorFormula().getSubformula().asEventuallyFormula(); + psiStates = propositionalChecker.check(formula.getSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector(); + // Get the maybeStates + std::pair<storm::storage::BitVector, storm::storage::BitVector> statesWithProbability01 = storm::utility::graph::performProb01(model->getBackwardTransitions(), phiStates, psiStates); + storm::storage::BitVector topStates = statesWithProbability01.second; + storm::storage::BitVector bottomStates = statesWithProbability01.first; + // OrderExtender + storm::storage::SparseMatrix<storm::RationalFunction> matrix = model->getTransitionMatrix(); + auto orderExtender = storm::analysis::OrderExtender<storm::RationalFunction, double>(&topStates, &bottomStates, matrix); + // Order + auto order = std::get<0>(orderExtender.toOrder(region, nullptr)); + // monchecker + auto monChecker = new storm::analysis::MonotonicityChecker<storm::RationalFunction>(model->getTransitionMatrix()); + + //start testing + auto var = modelParameters.begin(); + EXPECT_EQ(storm::analysis::MonotonicityChecker<storm::RationalFunction>::Monotonicity::Incr, monChecker->checkLocalMonotonicity(order, 1, *var, region)); + EXPECT_EQ(storm::analysis::MonotonicityChecker<storm::RationalFunction>::Monotonicity::Decr, monChecker->checkLocalMonotonicity(order, 2, *var, region)); } -TEST(MonotonicityCheckerTest, Brp_with_bisimulation_no_samples) { - std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/brp16_2.pm"; - std::string formulaAsString = "P=? [true U s=4 & i=N ]"; - std::string constantsAsString = ""; //e.g. pL=0.9,TOACK=0.5 +TEST(MonotonicityCheckerTest, Simple1_small_region) { + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/simple1.pm"; + std::string formulaAsString = "P=? [F s=3 ]"; + std::string constantsAsString = ""; - // Program and formula + // model storm::prism::Program program = storm::api::parseProgram(programFile); program = storm::utility::prism::preprocess(program, constantsAsString); std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); @@ -123,46 +77,92 @@ TEST(MonotonicityCheckerTest, Brp_with_bisimulation_no_samples) { ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); model = simplifier.getSimplifiedModel(); - // Apply bisimulation - storm::storage::BisimulationType bisimType = storm::storage::BisimulationType::Strong; - if (storm::settings::getModule<storm::settings::modules::BisimulationSettings>().isWeakBisimulationSet()) { - bisimType = storm::storage::BisimulationType::Weak; - } + // Create the region + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto region=storm::api::parseRegion<storm::RationalFunction>("0.51<=p<=0.9", modelParameters); + std::vector<storm::storage::ParameterRegion<storm::RationalFunction>> regions = {region}; - dtmc = storm::api::performBisimulationMinimization<storm::RationalFunction>(model, formulas, bisimType)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + // For order extender + storm::modelchecker::SparsePropositionalModelChecker<storm::models::sparse::Model<storm::RationalFunction>> propositionalChecker(*model); + storm::storage::BitVector phiStates; + storm::storage::BitVector psiStates; + phiStates = storm::storage::BitVector(model->getTransitionMatrix().getRowCount(), true); + storm::logic::EventuallyFormula formula = formulas[0]->asProbabilityOperatorFormula().getSubformula().asEventuallyFormula(); + psiStates = propositionalChecker.check(formula.getSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector(); + // Get the maybeStates + std::pair<storm::storage::BitVector, storm::storage::BitVector> statesWithProbability01 = storm::utility::graph::performProb01(model->getBackwardTransitions(), phiStates, psiStates); + storm::storage::BitVector topStates = statesWithProbability01.second; + storm::storage::BitVector bottomStates = statesWithProbability01.first; + // OrderExtender + storm::storage::SparseMatrix<storm::RationalFunction> matrix = model->getTransitionMatrix(); + auto orderExtender = storm::analysis::OrderExtender<storm::RationalFunction, double>(&topStates, &bottomStates, matrix); + // Order + auto order = std::get<0>(orderExtender.toOrder(region, nullptr)); + // monchecker + auto monChecker = new storm::analysis::MonotonicityChecker<storm::RationalFunction>(model->getTransitionMatrix()); + + //start testing + auto var = modelParameters.begin(); + EXPECT_EQ(storm::analysis::MonotonicityChecker<storm::RationalFunction>::Monotonicity::Incr, monChecker->checkLocalMonotonicity(order, 0, *var, region)); + EXPECT_EQ(storm::analysis::MonotonicityChecker<storm::RationalFunction>::Monotonicity::Incr, monChecker->checkLocalMonotonicity(order, 1, *var, region)); + EXPECT_EQ(storm::analysis::MonotonicityChecker<storm::RationalFunction>::Monotonicity::Decr, monChecker->checkLocalMonotonicity(order, 2, *var, region)); +} + +TEST(MonotonicityCheckerTest, Casestudy1) { + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/casestudy1.pm"; + std::string formulaAsString = "P=? [F s=3 ]"; + std::string constantsAsString = ""; + // model + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> dtmc = model->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*dtmc); + ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); + model = simplifier.getSimplifiedModel(); // Create the region - typename storm::storage::ParameterRegion<storm::RationalFunction>::Valuation lowerBoundaries; - typename storm::storage::ParameterRegion<storm::RationalFunction>::Valuation upperBoundaries; - std::set<typename storm::storage::ParameterRegion<storm::RationalFunction>::VariableType> vars = storm::models::sparse::getProbabilityParameters(*dtmc); - for (auto var : vars) { - typename storm::storage::ParameterRegion<storm::RationalFunction>::CoefficientType lb = storm::utility::convertNumber<typename storm::storage::ParameterRegion<storm::RationalFunction>::CoefficientType>(0 + 0.000001); - typename storm::storage::ParameterRegion<storm::RationalFunction>::CoefficientType ub = storm::utility::convertNumber<typename storm::storage::ParameterRegion<storm::RationalFunction>::CoefficientType>(1 - 0.000001); - lowerBoundaries.emplace(std::make_pair(var, lb)); - upperBoundaries.emplace(std::make_pair(var, ub)); + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto region=storm::api::parseRegion<storm::RationalFunction>("0.1<=p<=0.9", modelParameters); + std::vector<storm::storage::ParameterRegion<storm::RationalFunction>> regions = {region}; + + // For order extender + storm::modelchecker::SparsePropositionalModelChecker<storm::models::sparse::Model<storm::RationalFunction>> propositionalChecker(*model); + storm::storage::BitVector phiStates; + storm::storage::BitVector psiStates; + phiStates = storm::storage::BitVector(model->getTransitionMatrix().getRowCount(), true); + storm::logic::EventuallyFormula formula = formulas[0]->asProbabilityOperatorFormula().getSubformula().asEventuallyFormula(); + psiStates = propositionalChecker.check(formula.getSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector(); + // Get the maybeStates + std::pair<storm::storage::BitVector, storm::storage::BitVector> statesWithProbability01 = storm::utility::graph::performProb01(model->getBackwardTransitions(), phiStates, psiStates); + storm::storage::BitVector topStates = statesWithProbability01.second; + storm::storage::BitVector bottomStates = statesWithProbability01.first; + // OrderExtender + storm::storage::SparseMatrix<storm::RationalFunction> matrix = model->getTransitionMatrix(); + auto orderExtender = storm::analysis::OrderExtender<storm::RationalFunction, double>(&topStates, &bottomStates, matrix); + // Order + auto res =orderExtender.extendOrder(nullptr, region); + auto order = std::get<0>(res); + ASSERT_TRUE(order->getDoneBuilding()); + + //monchecker + auto monChecker = new storm::analysis::MonotonicityChecker<storm::RationalFunction>(matrix); + + //start testing + auto var = modelParameters.begin(); + for (uint_fast64_t i = 0; i < 3; i++) { + EXPECT_EQ(storm::analysis::MonotonicityChecker<storm::RationalFunction>::Monotonicity::Incr, monChecker->checkLocalMonotonicity(order, i, *var, region)); } - auto region = storm::storage::ParameterRegion<storm::RationalFunction>(std::move(lowerBoundaries), std::move(upperBoundaries)); -std::vector<storm::storage::ParameterRegion<storm::RationalFunction>> regions = {region}; - - ASSERT_EQ(dtmc->getNumberOfStates(), 99ull); - ASSERT_EQ(dtmc->getNumberOfTransitions(), 195ull); - - storm::analysis::MonotonicityChecker<storm::RationalFunction> monotonicityChecker = storm::analysis::MonotonicityChecker<storm::RationalFunction>(dtmc, formulas, regions, true); - auto result = monotonicityChecker.checkMonotonicity(std::cout); - EXPECT_EQ(1ul, result.size()); - EXPECT_EQ(2ul, result.begin()->second.size()); - auto monotone = result.begin()->second.begin(); - EXPECT_EQ(true, monotone->second.first); - EXPECT_EQ(false, monotone->second.second); } -TEST(MonotonicityCheckerTest, Brp_with_bisimulation_samples) { - std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/brp16_2.pm"; - std::string formulaAsString = "P=? [true U s=4 & i=N ]"; - std::string constantsAsString = ""; //e.g. pL=0.9,TOACK=0.5 +TEST(MonotonicityCheckerTest, Casestudy2) { + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/casestudy2.pm"; + std::string formulaAsString = "P=? [F s=4 ]"; + std::string constantsAsString = ""; - // Program and formula + // model storm::prism::Program program = storm::api::parseProgram(programFile); program = storm::utility::prism::preprocess(program, constantsAsString); std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); @@ -172,35 +172,92 @@ TEST(MonotonicityCheckerTest, Brp_with_bisimulation_samples) { ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); model = simplifier.getSimplifiedModel(); - // Apply bisimulation - storm::storage::BisimulationType bisimType = storm::storage::BisimulationType::Strong; - if (storm::settings::getModule<storm::settings::modules::BisimulationSettings>().isWeakBisimulationSet()) { - bisimType = storm::storage::BisimulationType::Weak; + // Create the region + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto region=storm::api::parseRegion<storm::RationalFunction>("0.51<=p<=0.9", modelParameters); + std::vector<storm::storage::ParameterRegion<storm::RationalFunction>> regions = {region}; + + // For order extender + storm::modelchecker::SparsePropositionalModelChecker<storm::models::sparse::Model<storm::RationalFunction>> propositionalChecker(*model); + storm::storage::BitVector phiStates; + storm::storage::BitVector psiStates; + phiStates = storm::storage::BitVector(model->getTransitionMatrix().getRowCount(), true); + storm::logic::EventuallyFormula formula = formulas[0]->asProbabilityOperatorFormula().getSubformula().asEventuallyFormula(); + psiStates = propositionalChecker.check(formula.getSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector(); + // Get the maybeStates + std::pair<storm::storage::BitVector, storm::storage::BitVector> statesWithProbability01 = storm::utility::graph::performProb01(model->getBackwardTransitions(), phiStates, psiStates); + storm::storage::BitVector topStates = statesWithProbability01.second; + storm::storage::BitVector bottomStates = statesWithProbability01.first; + // OrderExtender + storm::storage::SparseMatrix<storm::RationalFunction> matrix = model->getTransitionMatrix(); + auto orderExtender = storm::analysis::OrderExtender<storm::RationalFunction, double>(&topStates, &bottomStates, matrix); + // Order + auto res =orderExtender.extendOrder(nullptr, region); + auto order = std::get<0>(res); + order->addRelation(1,3); + order->addRelation(3,2); + + //monchecker + auto monChecker = new storm::analysis::MonotonicityChecker<storm::RationalFunction>(matrix); + + //start testing + auto var = modelParameters.begin(); + for (uint_fast64_t i = 0; i < 3; i++) { + EXPECT_EQ(storm::analysis::MonotonicityChecker<storm::RationalFunction>::Monotonicity::Incr, monChecker->checkLocalMonotonicity(order, i, *var, region)); } +} - dtmc = storm::api::performBisimulationMinimization<storm::RationalFunction>(model, formulas, bisimType)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + +TEST(MonotonicityCheckerTest, Casestudy3) { + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/casestudy3.pm"; + std::string formulaAsString = "P=? [F s=3 ]"; + std::string constantsAsString = ""; + + // model + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> dtmc = model->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*dtmc); + ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); + model = simplifier.getSimplifiedModel(); // Create the region - typename storm::storage::ParameterRegion<storm::RationalFunction>::Valuation lowerBoundaries; - typename storm::storage::ParameterRegion<storm::RationalFunction>::Valuation upperBoundaries; - std::set<typename storm::storage::ParameterRegion<storm::RationalFunction>::VariableType> vars = storm::models::sparse::getProbabilityParameters(*dtmc); - for (auto var : vars) { - typename storm::storage::ParameterRegion<storm::RationalFunction>::CoefficientType lb = storm::utility::convertNumber<typename storm::storage::ParameterRegion<storm::RationalFunction>::CoefficientType>(0 + 0.000001); - typename storm::storage::ParameterRegion<storm::RationalFunction>::CoefficientType ub = storm::utility::convertNumber<typename storm::storage::ParameterRegion<storm::RationalFunction>::CoefficientType>(1 - 0.000001); - lowerBoundaries.emplace(std::make_pair(var, lb)); - upperBoundaries.emplace(std::make_pair(var, ub)); - } - auto region = storm::storage::ParameterRegion<storm::RationalFunction>(std::move(lowerBoundaries), std::move(upperBoundaries)); + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto region=storm::api::parseRegion<storm::RationalFunction>("0.1<=p<=0.9", modelParameters); std::vector<storm::storage::ParameterRegion<storm::RationalFunction>> regions = {region}; - ASSERT_EQ(dtmc->getNumberOfStates(), 99ull); - ASSERT_EQ(dtmc->getNumberOfTransitions(), 195ull); - - auto monotonicityChecker = storm::analysis::MonotonicityChecker<storm::RationalFunction>(dtmc, formulas, regions, true, 50); - auto result = monotonicityChecker.checkMonotonicity(std::cout); - EXPECT_EQ(1ul, result.size()); - EXPECT_EQ(2ul, result.begin()->second.size()); - auto monotone = result.begin()->second.begin(); - EXPECT_EQ(true, monotone->second.first); - EXPECT_EQ(false, monotone->second.second); + // For order extender + storm::modelchecker::SparsePropositionalModelChecker<storm::models::sparse::Model<storm::RationalFunction>> propositionalChecker(*model); + storm::storage::BitVector phiStates; + storm::storage::BitVector psiStates; + phiStates = storm::storage::BitVector(model->getTransitionMatrix().getRowCount(), true); + storm::logic::EventuallyFormula formula = formulas[0]->asProbabilityOperatorFormula().getSubformula().asEventuallyFormula(); + psiStates = propositionalChecker.check(formula.getSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector(); + // Get the maybeStates + std::pair<storm::storage::BitVector, storm::storage::BitVector> statesWithProbability01 = storm::utility::graph::performProb01(model->getBackwardTransitions(), phiStates, psiStates); + storm::storage::BitVector topStates = statesWithProbability01.second; + storm::storage::BitVector bottomStates = statesWithProbability01.first; + // OrderExtender + storm::storage::SparseMatrix<storm::RationalFunction> matrix = model->getTransitionMatrix(); + auto orderExtender = storm::analysis::OrderExtender<storm::RationalFunction, double>(&topStates, &bottomStates, matrix); + // Order + auto res =orderExtender.extendOrder(nullptr, region); + auto order = std::get<0>(res); + ASSERT_TRUE(order->getDoneBuilding()); + + //monchecker + auto monChecker = new storm::analysis::MonotonicityChecker<storm::RationalFunction>(matrix); + + //start testing + auto var = modelParameters.begin(); + EXPECT_EQ(storm::analysis::MonotonicityChecker<storm::RationalFunction>::Monotonicity::Not, monChecker->checkLocalMonotonicity(order, 0, *var, region)); + EXPECT_EQ(storm::analysis::MonotonicityChecker<storm::RationalFunction>::Monotonicity::Incr, monChecker->checkLocalMonotonicity(order, 1, *var, region)); + EXPECT_EQ(storm::analysis::MonotonicityChecker<storm::RationalFunction>::Monotonicity::Incr, monChecker->checkLocalMonotonicity(order, 2, *var, region)); + + region=storm::api::parseRegion<storm::RationalFunction>("0.51<=p<=0.9", modelParameters); + EXPECT_EQ(storm::analysis::MonotonicityChecker<storm::RationalFunction>::Monotonicity::Decr, monChecker->checkLocalMonotonicity(order, 0, *var, region)); + EXPECT_EQ(storm::analysis::MonotonicityChecker<storm::RationalFunction>::Monotonicity::Incr, monChecker->checkLocalMonotonicity(order, 1, *var, region)); + EXPECT_EQ(storm::analysis::MonotonicityChecker<storm::RationalFunction>::Monotonicity::Incr, monChecker->checkLocalMonotonicity(order, 2, *var, region)); } diff --git a/src/test/storm-pars/analysis/MonotonicityHelperTest.cpp b/src/test/storm-pars/analysis/MonotonicityHelperTest.cpp new file mode 100644 index 000000000..7078cf39c --- /dev/null +++ b/src/test/storm-pars/analysis/MonotonicityHelperTest.cpp @@ -0,0 +1,454 @@ +#include "test/storm_gtest.h" +#include "storm-config.h" +#include "test/storm_gtest.h" +#include "storm/storage/expressions/BinaryRelationExpression.h" +#include "storm/storage/SparseMatrix.h" +#include "storm/adapters/RationalFunctionAdapter.h" + +#include "storm-parsers/parser/FormulaParser.h" +#include "storm/logic/Formulas.h" +#include "storm/models/sparse/StandardRewardModel.h" +#include "storm/modelchecker/prctl/SparseDtmcPrctlModelChecker.h" +#include "storm/modelchecker/results/ExplicitQuantitativeCheckResult.h" +#include "storm-parsers/parser/AutoParser.h" +#include "storm-parsers/parser/PrismParser.h" +#include "storm/storage/expressions/ExpressionManager.h" +#include "storm/api/builder.h" + +#include "storm-pars/transformer/SparseParametricDtmcSimplifier.h" + +#include "storm-pars/api/storm-pars.h" +#include "storm/api/storm.h" + +#include "storm-parsers/api/storm-parsers.h" + +TEST(MonotonicityHelperTest, Derivative_checker) { + // Create the region + typename storm::storage::ParameterRegion<storm::RationalFunction>::Valuation lowerBoundaries; + typename storm::storage::ParameterRegion<storm::RationalFunction>::Valuation upperBoundaries; + auto region = storm::storage::ParameterRegion<storm::RationalFunction>(std::move(lowerBoundaries), std::move(upperBoundaries)); + + // Derivative 0 + auto constFunction = storm::RationalFunction(0); + auto constFunctionRes = storm::analysis::MonotonicityHelper<storm::RationalFunction, double>::checkDerivative(constFunction, region); + EXPECT_TRUE(constFunctionRes.first); + EXPECT_TRUE(constFunctionRes.second); + + // Derivative 5 + constFunction = storm::RationalFunction(5); + constFunctionRes = storm::analysis::MonotonicityHelper<storm::RationalFunction, double>::checkDerivative(constFunction, region); + EXPECT_TRUE(constFunctionRes.first); + EXPECT_FALSE(constFunctionRes.second); + + // Derivative -4 + constFunction = storm::RationalFunction(storm::RationalFunction(1)-constFunction); + constFunctionRes = storm::analysis::MonotonicityHelper<storm::RationalFunction, double>::checkDerivative(constFunction, region); + EXPECT_FALSE(constFunctionRes.first); + EXPECT_TRUE(constFunctionRes.second); + + std::shared_ptr<storm::RawPolynomialCache> cache = std::make_shared<storm::RawPolynomialCache>(); + carl::StringParser parser; + parser.setVariables({"p", "q"}); + + // Create the region + auto functionP = storm::RationalFunction(storm::Polynomial(parser.template parseMultivariatePolynomial<storm::RationalFunctionCoefficient>("p"), cache)); + auto functionQ = storm::RationalFunction(storm::Polynomial(parser.template parseMultivariatePolynomial<storm::RationalFunctionCoefficient>("q"), cache)); + + auto varsP = functionP.gatherVariables(); + auto varsQ = functionQ.gatherVariables(); + storm::utility::parametric::Valuation<storm::RationalFunction> lowerBoundaries2; + storm::utility::parametric::Valuation<storm::RationalFunction> upperBoundaries2; + for (auto var : varsP) { + typename storm::storage::ParameterRegion<storm::RationalFunction>::CoefficientType lb = storm::utility::convertNumber<typename storm::storage::ParameterRegion<storm::RationalFunction>::CoefficientType>(0 + 0.000001); + typename storm::storage::ParameterRegion<storm::RationalFunction>::CoefficientType ub = storm::utility::convertNumber<typename storm::storage::ParameterRegion<storm::RationalFunction>::CoefficientType>(1 - 0.000001); + lowerBoundaries2.emplace(std::make_pair(var, lb)); + upperBoundaries2.emplace(std::make_pair(var, ub)); + } + for (auto var : varsQ) { + typename storm::storage::ParameterRegion<storm::RationalFunction>::CoefficientType lb = storm::utility::convertNumber<typename storm::storage::ParameterRegion<storm::RationalFunction>::CoefficientType>(0 + 0.000001); + typename storm::storage::ParameterRegion<storm::RationalFunction>::CoefficientType ub = storm::utility::convertNumber<typename storm::storage::ParameterRegion<storm::RationalFunction>::CoefficientType>(1 - 0.000001); + lowerBoundaries2.emplace(std::make_pair(var, lb)); + upperBoundaries2.emplace(std::make_pair(var, ub)); + } + region = storm::storage::ParameterRegion<storm::RationalFunction>(std::move(lowerBoundaries2), std::move(upperBoundaries2)); + + // Derivative p + auto function = functionP; + auto functionRes = storm::analysis::MonotonicityHelper<storm::RationalFunction, double>::checkDerivative(function, region); + EXPECT_TRUE(functionRes.first); + EXPECT_FALSE(functionRes.second); + + // Derivative 1-p + auto functionDecr = storm::RationalFunction(storm::RationalFunction(1)-function); + auto functionDecrRes = storm::analysis::MonotonicityHelper<storm::RationalFunction, double>::checkDerivative(functionDecr, region); + EXPECT_TRUE(functionDecrRes.first); + EXPECT_FALSE(functionDecrRes.second); + + // Derivative 1-2p + auto functionNonMonotonic = storm::RationalFunction(storm::RationalFunction(1)-storm::RationalFunction(2)*function); + auto functionNonMonotonicRes = storm::analysis::MonotonicityHelper<storm::RationalFunction, double>::checkDerivative(functionNonMonotonic, region); + EXPECT_FALSE(functionNonMonotonicRes.first); + EXPECT_FALSE(functionNonMonotonicRes.second); + + // Derivative -p + functionDecr = storm::RationalFunction(storm::RationalFunction(0)-function); + functionDecrRes = storm::analysis::MonotonicityHelper<storm::RationalFunction, double>::checkDerivative(functionDecr, region); + EXPECT_FALSE(functionDecrRes.first); + EXPECT_TRUE(functionDecrRes.second); + + // Derivative p*q + function = functionP * functionQ ; + functionRes = storm::analysis::MonotonicityHelper<storm::RationalFunction, double>::checkDerivative(function, region); + EXPECT_TRUE(functionRes.first); + EXPECT_FALSE(functionRes.second); +} + +TEST(MonotonicityHelperTest, Brp_with_bisimulation_no_samples) { + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/brp16_2.pm"; + std::string formulaAsString = "P=? [true U s=4 & i=N ]"; + std::string constantsAsString = ""; //e.g. pL=0.9,TOACK=0.5 + + // Program and formula + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*model); + ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); + model = simplifier.getSimplifiedModel(); + + // Apply bisimulation + storm::storage::BisimulationType bisimType = storm::storage::BisimulationType::Strong; + if (storm::settings::getModule<storm::settings::modules::BisimulationSettings>().isWeakBisimulationSet()) { + bisimType = storm::storage::BisimulationType::Weak; + } + + model = storm::api::performBisimulationMinimization<storm::RationalFunction>(model, formulas, bisimType)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + ASSERT_EQ(model->getNumberOfStates(), 99); + ASSERT_EQ(model->getNumberOfTransitions(), 195); + + // Create the region + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto region=storm::api::parseRegion<storm::RationalFunction>("0.1<=pL<=0.9, 0.1<=pK<=0.9", modelParameters); + std::vector<storm::storage::ParameterRegion<storm::RationalFunction>> regions = {region}; + + // Start testing + storm::analysis::MonotonicityHelper<storm::RationalFunction, double> MonotonicityHelper = storm::analysis::MonotonicityHelper<storm::RationalFunction, double>(model, formulas, regions, true); + // Check if correct result size + auto result = MonotonicityHelper.checkMonotonicityInBuild(std::cout, false); + EXPECT_EQ(1, result.size()); + + // Check if the order and general monotonicity result is correct. + auto order = result.begin()->first; + auto monotonicityResult = result.begin()->second.first; + EXPECT_TRUE(monotonicityResult->isDone()); + EXPECT_TRUE(monotonicityResult->existsMonotonicity()); + EXPECT_TRUE(monotonicityResult->isAllMonotonicity()); + auto assumptions = result.begin()->second.second; + EXPECT_EQ(0, assumptions.size()); + + // Check if result for each variable is correct + auto monRes = monotonicityResult->getMonotonicityResult(); + for (auto entry : monRes) { + EXPECT_EQ(storm::analysis::MonotonicityResult<storm::RationalFunctionVariable>::Monotonicity::Incr, entry.second); + } +} + +TEST(MonotonicityHelperTest, Brp_with_bisimulation_samples) { + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/brp16_2.pm"; + std::string formulaAsString = "P=? [true U s=4 & i=N ]"; + std::string constantsAsString = ""; //e.g. pL=0.9,TOACK=0.5 + + // Program and formula + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*model); + ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); + model = simplifier.getSimplifiedModel(); + + // Apply bisimulation + storm::storage::BisimulationType bisimType = storm::storage::BisimulationType::Strong; + if (storm::settings::getModule<storm::settings::modules::BisimulationSettings>().isWeakBisimulationSet()) { + bisimType = storm::storage::BisimulationType::Weak; + } + + model = storm::api::performBisimulationMinimization<storm::RationalFunction>(model, formulas, bisimType)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + ASSERT_EQ(model->getNumberOfStates(), 99); + ASSERT_EQ(model->getNumberOfTransitions(), 195); + + // Create the region + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto region=storm::api::parseRegion<storm::RationalFunction>("0.1<=pL<=0.9, 0.1<=pK<=0.9", modelParameters); + std::vector<storm::storage::ParameterRegion<storm::RationalFunction>> regions = {region}; + + // Start testing + storm::analysis::MonotonicityHelper<storm::RationalFunction, double> MonotonicityHelper = storm::analysis::MonotonicityHelper<storm::RationalFunction, double>(model, formulas, regions, true, 50); + // Check if correct result size + auto result = MonotonicityHelper.checkMonotonicityInBuild(std::cout, false); + EXPECT_EQ(1, result.size()); + + // Check if the order and general monotonicity result is correct. + auto order = result.begin()->first; + auto monotonicityResult = result.begin()->second.first; + EXPECT_TRUE(monotonicityResult->isDone()); + EXPECT_TRUE(monotonicityResult->existsMonotonicity()); + EXPECT_TRUE(monotonicityResult->isAllMonotonicity()); + auto assumptions = result.begin()->second.second; + EXPECT_EQ(0, assumptions.size()); + + // Check if result for each variable is correct + auto monRes = monotonicityResult->getMonotonicityResult(); + for (auto entry : monRes) { + EXPECT_EQ(storm::analysis::MonotonicityResult<storm::RationalFunctionVariable>::Monotonicity::Incr, entry.second); + } +} + +TEST(MonotonicityHelperTest, zeroconf) { + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/zeroconf4.pm"; + std::string formulaAsString = "P > 0.5 [ F s=5 ]"; + std::string constantsAsString = "n = 4"; //e.g. pL=0.9,TOACK=0.5 + + // Program and formula + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*model); + ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); + model = simplifier.getSimplifiedModel(); + + storm::storage::BisimulationType bisimType = storm::storage::BisimulationType::Strong; + if (storm::settings::getModule<storm::settings::modules::BisimulationSettings>().isWeakBisimulationSet()) { + bisimType = storm::storage::BisimulationType::Weak; + } + + model = storm::api::performBisimulationMinimization<storm::RationalFunction>(model, formulas, bisimType)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + ASSERT_EQ(model->getNumberOfStates(), 7); + ASSERT_EQ(model->getNumberOfTransitions(), 12); + + // Create region + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto region=storm::api::parseRegion<storm::RationalFunction>("0.1<=pL<=0.9, 0.1<=pK<=0.9", modelParameters); + std::vector<storm::storage::ParameterRegion<storm::RationalFunction>> regions = {region}; + + // Start testing + auto MonotonicityHelper = storm::analysis::MonotonicityHelper<storm::RationalFunction, double>(model, formulas, regions, 50); + // Check if correct result size + auto result = MonotonicityHelper.checkMonotonicityInBuild(std::cout, false); + EXPECT_EQ(1, result.size()); + + // Check if the order and general monotonicity result is correct. + auto order = result.begin()->first; + auto monotonicityResult = result.begin()->second.first; + EXPECT_TRUE(monotonicityResult->isDone()); + EXPECT_TRUE(monotonicityResult->existsMonotonicity()); + EXPECT_TRUE(monotonicityResult->isAllMonotonicity()); + // TODO @Jip we have 1 assumption instead of 0 here + auto assumptions = result.begin()->second.second; + EXPECT_EQ(0, assumptions.size()); + + // Check if result for each variable is correct + auto monRes = monotonicityResult->getMonotonicityResult(); + for (auto entry : monRes) { + EXPECT_EQ(storm::analysis::MonotonicityResult<storm::RationalFunctionVariable>::Monotonicity::Incr, entry.second); + } +} + +TEST(MonotonicityHelperTest, Simple1) { + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/simple1.pm"; + std::string formulaAsString = "P > 0.5 [ F s=3 ]"; + std::string constantsAsString = ""; + + // Program and formula + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*model); + ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); + model = simplifier.getSimplifiedModel(); + ASSERT_EQ(model->getNumberOfStates(), 5); + ASSERT_EQ(model->getNumberOfTransitions(), 8); + + // Create region + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto region=storm::api::parseRegion<storm::RationalFunction>("0.1<=p<=0.49", modelParameters); + std::vector<storm::storage::ParameterRegion<storm::RationalFunction>> regions = {region}; + + // Start testing + auto MonotonicityHelper = storm::analysis::MonotonicityHelper<storm::RationalFunction, double>(model, formulas, regions, 10); + + // Check if correct result size + auto result = MonotonicityHelper.checkMonotonicityInBuild(std::cout, false); + EXPECT_EQ(1, result.size()); + + // Check if the order and general monotonicity result is correct. + auto order = result.begin()->first; + auto monotonicityResult = result.begin()->second.first; + EXPECT_TRUE(monotonicityResult->isDone()); + EXPECT_FALSE(monotonicityResult->existsMonotonicity()); + EXPECT_FALSE(monotonicityResult->isAllMonotonicity()); + auto assumptions = result.begin()->second.second; + EXPECT_EQ(0, assumptions.size()); + + // Check if result for each variable is correct + auto monRes = monotonicityResult->getMonotonicityResult(); + for (auto entry : monRes) { + EXPECT_EQ(storm::analysis::MonotonicityResult<storm::RationalFunctionVariable>::Monotonicity::Unknown, entry.second); + } +} + +TEST(MonotonicityHelperTest, Casestudy1) { + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/casestudy1.pm"; + std::string formulaAsString = "P > 0.5 [ F s=3 ]"; + std::string constantsAsString = ""; + + // Program and formula + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*model); + ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); + model = simplifier.getSimplifiedModel(); + + // Create region + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto region=storm::api::parseRegion<storm::RationalFunction>("0.1<=p<=0.9", modelParameters); + std::vector<storm::storage::ParameterRegion<storm::RationalFunction>> regions = {region}; + + ASSERT_EQ(model->getNumberOfStates(), 5); + ASSERT_EQ(model->getNumberOfTransitions(), 8); + + auto MonotonicityHelper = storm::analysis::MonotonicityHelper<storm::RationalFunction, double>(model, formulas, regions, 10); + auto result = MonotonicityHelper.checkMonotonicityInBuild(std::cout, false); + ASSERT_EQ(1, result.size()); + + auto order = result.begin()->first; + auto monotonicityResult = result.begin()->second.first; + EXPECT_TRUE(monotonicityResult->isDone()); + EXPECT_TRUE(monotonicityResult->existsMonotonicity()); + EXPECT_TRUE(monotonicityResult->isAllMonotonicity()); + auto assumptions = result.begin()->second.second; + EXPECT_EQ(0, assumptions.size()); + + auto monRes = monotonicityResult->getMonotonicityResult(); + for (auto entry : monRes) { + EXPECT_EQ(storm::analysis::MonotonicityResult<storm::RationalFunctionVariable>::Monotonicity::Incr, entry.second); + } +} + +TEST(MonotonicityHelperTest, CaseStudy2) { + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/casestudy2.pm"; + std::string formulaAsString = "P > 0.5 [ F s=4 ]"; + std::string constantsAsString = ""; + + // Program and formula + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*model); + ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); + model = simplifier.getSimplifiedModel(); + + // Create region + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto region=storm::api::parseRegion<storm::RationalFunction>("0.1<=p<=0.9", modelParameters); + std::vector<storm::storage::ParameterRegion<storm::RationalFunction>> regions = {region}; + + ASSERT_EQ(model->getNumberOfStates(), 6); + ASSERT_EQ(model->getNumberOfTransitions(), 12); + + // Start testing + auto monotonicityHelper = storm::analysis::MonotonicityHelper<storm::RationalFunction, double>(model, formulas, regions, 10); + + // Check if correct result size + auto result = monotonicityHelper.checkMonotonicityInBuild(std::cout, false); + EXPECT_EQ(1, result.size()); + EXPECT_FALSE(result.begin()->first->getDoneBuilding()); +} + +TEST(MonotonicityHelperTest, Casestudy3_not_monotone) { + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/casestudy3.pm"; + std::string formulaAsString = "P > 0.5 [ F s=3 ]"; + std::string constantsAsString = ""; + + // Program and formula + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*model); + ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); + model = simplifier.getSimplifiedModel(); + + // Create region + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto region=storm::api::parseRegion<storm::RationalFunction>("0.1<=p<=0.9", modelParameters); + std::vector<storm::storage::ParameterRegion<storm::RationalFunction>> regions = {region}; + + ASSERT_EQ(model->getNumberOfStates(), 5); + ASSERT_EQ(model->getNumberOfTransitions(), 8); + + auto MonotonicityHelper = storm::analysis::MonotonicityHelper<storm::RationalFunction, double>(model, formulas, regions, 10); + auto result = MonotonicityHelper.checkMonotonicityInBuild(std::cout, false); + + ASSERT_EQ(1, result.size()); + auto order = result.begin()->first; + + auto monotonicityResult = result.begin()->second.first; + EXPECT_TRUE(monotonicityResult->isDone()); + EXPECT_FALSE(monotonicityResult->existsMonotonicity()); + EXPECT_FALSE(monotonicityResult->isAllMonotonicity()); + auto assumptions = result.begin()->second.second; + EXPECT_EQ(0, assumptions.size()); + + auto monRes = monotonicityResult->getMonotonicityResult(); + for (auto entry : monRes) { + EXPECT_EQ(storm::analysis::MonotonicityResult<storm::RationalFunctionVariable>::Monotonicity::Unknown, entry.second); + } +} + +TEST(MonotonicityHelperTest, Casestudy3_monotone) { + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/casestudy3.pm"; + std::string formulaAsString = "P > 0.5 [ F s=3 ]"; + std::string constantsAsString = ""; + + // Program and formula + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*model); + ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); + model = simplifier.getSimplifiedModel(); + + // Create region + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto region=storm::api::parseRegion<storm::RationalFunction>("0.1<=p<=0.49", modelParameters); + std::vector<storm::storage::ParameterRegion<storm::RationalFunction>> regions = {region}; + + ASSERT_EQ(model->getNumberOfStates(), 5); + ASSERT_EQ(model->getNumberOfTransitions(), 8); + + auto MonotonicityHelper = storm::analysis::MonotonicityHelper<storm::RationalFunction, double>(model, formulas, regions, 10); + auto result = MonotonicityHelper.checkMonotonicityInBuild(std::cout, false); + + ASSERT_EQ(1, result.size()); + auto order = result.begin()->first; + + auto monotonicityResult = result.begin()->second.first; + EXPECT_TRUE(monotonicityResult->isDone()); + EXPECT_TRUE(monotonicityResult->existsMonotonicity()); + EXPECT_TRUE(monotonicityResult->isAllMonotonicity()); + auto assumptions = result.begin()->second.second; + EXPECT_EQ(0, assumptions.size()); + + auto monRes = monotonicityResult->getMonotonicityResult(); + for (auto entry : monRes) { + EXPECT_EQ(storm::analysis::MonotonicityResult<storm::RationalFunctionVariable>::Monotonicity::Incr, entry.second); + } +} diff --git a/src/test/storm-pars/analysis/OrderExtenderTest.cpp b/src/test/storm-pars/analysis/OrderExtenderTest.cpp index 96b18bb86..6bfb4d25c 100644 --- a/src/test/storm-pars/analysis/OrderExtenderTest.cpp +++ b/src/test/storm-pars/analysis/OrderExtenderTest.cpp @@ -1,27 +1,22 @@ -#include "test/storm_gtest.h" #include "storm-config.h" -#include "test/storm_gtest.h" -#include "storm-parsers/parser/FormulaParser.h" -#include "storm/logic/Formulas.h" -#include "storm/models/sparse/StandardRewardModel.h" -#include "storm/modelchecker/prctl/SparseDtmcPrctlModelChecker.h" -#include "storm/modelchecker/results/ExplicitQuantitativeCheckResult.h" +#include "storm-pars/api/storm-pars.h" +#include "storm-pars/transformer/SparseParametricDtmcSimplifier.h" + +#include "storm-parsers/api/storm-parsers.h" #include "storm-parsers/parser/AutoParser.h" #include "storm-parsers/parser/PrismParser.h" -#include "storm/storage/expressions/ExpressionManager.h" -#include "storm/api/builder.h" - -#include "storm-pars/analysis/Order.h" -#include "storm-pars/analysis/OrderExtender.h" -#include "storm-pars/transformer/SparseParametricDtmcSimplifier.h" -#include "storm-pars/api/storm-pars.h" +#include "storm/api/builder.h" #include "storm/api/storm.h" +#include "storm/logic/Formulas.h" +#include "storm/modelchecker/prctl/SparseDtmcPrctlModelChecker.h" +#include "storm/modelchecker/results/ExplicitQualitativeCheckResult.h" +#include "storm/models/sparse/StandardRewardModel.h" -#include "storm-parsers/api/storm-parsers.h" +#include "test/storm_gtest.h" -TEST(OrderExtenderTest, Brp_with_bisimulation) { +TEST(OrderExtenderTest, Brp_with_bisimulation_on_model) { std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/brp16_2.pm"; std::string formulaAsString = "P=? [F s=4 & i=N ]"; std::string constantsAsString = ""; //e.g. pL=0.9,TOACK=0.5 @@ -31,8 +26,7 @@ TEST(OrderExtenderTest, Brp_with_bisimulation) { program = storm::utility::prism::preprocess(program, constantsAsString); std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> dtmc = model->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*dtmc); + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*model); ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); model = simplifier.getSimplifiedModel(); @@ -42,19 +36,23 @@ TEST(OrderExtenderTest, Brp_with_bisimulation) { bisimType = storm::storage::BisimulationType::Weak; } - dtmc = storm::api::performBisimulationMinimization<storm::RationalFunction>(model, formulas, bisimType)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + model = storm::api::performBisimulationMinimization<storm::RationalFunction>(model, formulas, bisimType)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - ASSERT_EQ(dtmc->getNumberOfStates(), 99ull); - ASSERT_EQ(dtmc->getNumberOfTransitions(), 195ull); + ASSERT_EQ(model->getNumberOfStates(), 99); + ASSERT_EQ(model->getNumberOfTransitions(), 195); - auto *extender = new storm::analysis::OrderExtender<storm::RationalFunction>(dtmc); - auto criticalTuple = extender->toOrder(formulas); - EXPECT_EQ(dtmc->getNumberOfStates(), std::get<1>(criticalTuple)); - EXPECT_EQ(dtmc->getNumberOfStates(), std::get<2>(criticalTuple)); + auto vars = storm::models::sparse::getProbabilityParameters(*model); + auto region = storm::api::parseRegion<storm::RationalFunction>("0.00001 <= pK <= 0.999999, 0.00001 <= pL <= 0.999999", vars); + + auto extender = storm::analysis::OrderExtender<storm::RationalFunction, double>(model, formulas[0]); + auto monRes = new storm::analysis::MonotonicityResult<typename storm::analysis::OrderExtender<storm::RationalFunction, double>::VariableType>; + auto criticalTuple = extender.toOrder(region, make_shared<storm::analysis::MonotonicityResult<typename storm::analysis::OrderExtender<storm::RationalFunction, double>::VariableType>>(*monRes)); + EXPECT_EQ(model->getNumberOfStates(), std::get<1>(criticalTuple)); + EXPECT_EQ(model->getNumberOfStates(), std::get<2>(criticalTuple)); auto order = std::get<0>(criticalTuple); - for (uint_fast64_t i = 0; i < dtmc->getNumberOfStates(); ++i) { - EXPECT_TRUE((*order->getAddedStates())[i]); + for (uint_fast64_t i = 0; i < model->getNumberOfStates(); ++i) { + EXPECT_TRUE((order->contains(i))); } // Check on some nodes @@ -65,7 +63,7 @@ TEST(OrderExtenderTest, Brp_with_bisimulation) { EXPECT_EQ(storm::analysis::Order::NodeComparison::UNKNOWN, order->compare(7,13)); } -TEST(OrderExtenderTest, Brp_without_bisimulation) { +TEST(OrderExtenderTest, Brp_without_bisimulation_on_model) { std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/brp16_2.pm"; std::string formulaAsString = "P=? [F s=4 & i=N ]"; std::string constantsAsString = ""; //e.g. pL=0.9,TOACK=0.5 @@ -75,19 +73,315 @@ TEST(OrderExtenderTest, Brp_without_bisimulation) { program = storm::utility::prism::preprocess(program, constantsAsString); std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> dtmc = model->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*dtmc); + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*model); ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); model = simplifier.getSimplifiedModel(); - dtmc = model->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - ASSERT_EQ(dtmc->getNumberOfStates(), 193ull); - ASSERT_EQ(dtmc->getNumberOfTransitions(), 383ull); + ASSERT_EQ(model->getNumberOfStates(), 193); + ASSERT_EQ(model->getNumberOfTransitions(), 383); + + auto vars = storm::models::sparse::getProbabilityParameters(*model); + auto region = storm::api::parseRegion<storm::RationalFunction>("0.00001 <= pK <= 0.999999, 0.00001 <= pL <= 0.999999", vars); - auto *extender = new storm::analysis::OrderExtender<storm::RationalFunction>(dtmc); - auto criticalTuple = extender->toOrder(formulas); + auto extender = storm::analysis::OrderExtender<storm::RationalFunction, double>(model, formulas[0]); + auto monRes = new storm::analysis::MonotonicityResult<typename storm::analysis::OrderExtender<storm::RationalFunction, double>::VariableType>; + auto criticalTuple = extender.toOrder(region, make_shared<storm::analysis::MonotonicityResult<typename storm::analysis::OrderExtender<storm::RationalFunction, double>::VariableType>>(*monRes)); EXPECT_EQ(183ul, std::get<1>(criticalTuple)); EXPECT_EQ(186ul, std::get<2>(criticalTuple)); } +TEST(OrderExtenderTest, Brp_with_bisimulation_on_matrix) { + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/brp16_2.pm"; + std::string formulaAsString = "P=? [F s=4 & i=N ]"; + std::string constantsAsString = ""; //e.g. pL=0.9,TOACK=0.5 + + // Program and formula + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*model); + ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); + model = simplifier.getSimplifiedModel(); + + // Apply bisimulation + storm::storage::BisimulationType bisimType = storm::storage::BisimulationType::Strong; + if (storm::settings::getModule<storm::settings::modules::BisimulationSettings>().isWeakBisimulationSet()) { + bisimType = storm::storage::BisimulationType::Weak; + } + + model = storm::api::performBisimulationMinimization<storm::RationalFunction>(model, formulas, bisimType)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + + ASSERT_EQ(model->getNumberOfStates(), 99); + ASSERT_EQ(model->getNumberOfTransitions(), 195); + + auto vars = storm::models::sparse::getProbabilityParameters(*model); + auto region = storm::api::parseRegion<storm::RationalFunction>("0.00001 <= pK <= 0.999999, 0.00001 <= pL <= 0.999999", vars); + storm::modelchecker::SparsePropositionalModelChecker<storm::models::sparse::Model<storm::RationalFunction>> propositionalChecker(*model); + storm::storage::BitVector phiStates; + storm::storage::BitVector psiStates; + phiStates = storm::storage::BitVector(model->getTransitionMatrix().getRowCount(), true); + storm::logic::EventuallyFormula formula = formulas[0]->asProbabilityOperatorFormula().getSubformula().asEventuallyFormula(); + psiStates = propositionalChecker.check(formula.getSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector(); + // Get the maybeStates + std::pair<storm::storage::BitVector, storm::storage::BitVector> statesWithProbability01 = storm::utility::graph::performProb01(model->getBackwardTransitions(), phiStates, psiStates); + storm::storage::BitVector topStates = statesWithProbability01.second; + storm::storage::BitVector bottomStates = statesWithProbability01.first; + + auto extender = storm::analysis::OrderExtender<storm::RationalFunction, double>(&topStates, &bottomStates, model->getTransitionMatrix()); + auto res = extender.extendOrder(nullptr, region); + auto order = std::get<0>(res); + EXPECT_EQ(order->getNumberOfAddedStates(), model->getNumberOfStates()); + EXPECT_TRUE(order->getDoneBuilding()); + + // Check on some nodes + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(1,0)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(1,5)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(5,0)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(94,5)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::UNKNOWN, order->compare(7,13)); +} + +TEST(OrderExtenderTest, Brp_without_bisimulation_on_matrix) { + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/brp16_2.pm"; + std::string formulaAsString = "P=? [F s=4 & i=N ]"; + std::string constantsAsString = ""; //e.g. pL=0.9,TOACK=0.5 + + // Program and formula + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*model); + ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); + model = simplifier.getSimplifiedModel(); + + ASSERT_EQ(model->getNumberOfStates(), 193); + ASSERT_EQ(model->getNumberOfTransitions(), 383); + + auto vars = storm::models::sparse::getProbabilityParameters(*model); + auto region = storm::api::parseRegion<storm::RationalFunction>("0.00001 <= pK <= 0.999999, 0.00001 <= pL <= 0.999999", vars); + storm::modelchecker::SparsePropositionalModelChecker<storm::models::sparse::Model<storm::RationalFunction>> propositionalChecker(*model); + storm::storage::BitVector phiStates; + storm::storage::BitVector psiStates; + phiStates = storm::storage::BitVector(model->getTransitionMatrix().getRowCount(), true); + storm::logic::EventuallyFormula formula = formulas[0]->asProbabilityOperatorFormula().getSubformula().asEventuallyFormula(); + psiStates = propositionalChecker.check(formula.getSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector(); + // Get the maybeStates + std::pair<storm::storage::BitVector, storm::storage::BitVector> statesWithProbability01 = storm::utility::graph::performProb01(model->getBackwardTransitions(), phiStates, psiStates); + storm::storage::BitVector topStates = statesWithProbability01.second; + storm::storage::BitVector bottomStates = statesWithProbability01.first; + + auto extender = storm::analysis::OrderExtender<storm::RationalFunction, double>(&topStates, &bottomStates, model->getTransitionMatrix()); + auto res = extender.extendOrder(nullptr, region); + auto order = std::get<0>(res); + EXPECT_FALSE(order->getDoneBuilding()); +} +TEST(OrderExtenderTest, simple1_on_model) { + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/simple1.pm"; + std::string formulaAsString = "P=? [F s=3 ]"; + std::string constantsAsString = ""; + + // model + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*model); + ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); + model = simplifier.getSimplifiedModel(); + + // Create the region + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto region=storm::api::parseRegion<storm::RationalFunction>("0.51<=p<=0.9", modelParameters); + + auto extender = storm::analysis::OrderExtender<storm::RationalFunction, double>(model, formulas[0]); + auto order = std::get<0>(extender.toOrder(region)); + EXPECT_EQ(order->getNumberOfAddedStates(), 5); + EXPECT_TRUE(order->getDoneBuilding()); + + // Check on all states + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(3,0)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(3,1)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(3,2)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(3,4)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(1,0)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(1,2)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(1,4)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(0,2)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(0,4)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(2,4)); +} + +TEST(OrderExtenderTest, simple1_on_matrix) { + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/simple1.pm"; + std::string formulaAsString = "P=? [F s=3 ]"; + std::string constantsAsString = ""; + + // model + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*model); + ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); + model = simplifier.getSimplifiedModel(); + + // Create the region + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto region=storm::api::parseRegion<storm::RationalFunction>("0.51 <= p <= 0.9", modelParameters); + + // For order extender + storm::modelchecker::SparsePropositionalModelChecker<storm::models::sparse::Model<storm::RationalFunction>> propositionalChecker(*model); + storm::storage::BitVector phiStates; + storm::storage::BitVector psiStates; + phiStates = storm::storage::BitVector(model->getTransitionMatrix().getRowCount(), true); + storm::logic::EventuallyFormula formula = formulas[0]->asProbabilityOperatorFormula().getSubformula().asEventuallyFormula(); + psiStates = propositionalChecker.check(formula.getSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector(); + // Get the maybeStates + std::pair<storm::storage::BitVector, storm::storage::BitVector> statesWithProbability01 = storm::utility::graph::performProb01(model->getBackwardTransitions(), phiStates, psiStates); + storm::storage::BitVector topStates = statesWithProbability01.second; + storm::storage::BitVector bottomStates = statesWithProbability01.first; + + // OrderExtender + auto extender = storm::analysis::OrderExtender<storm::RationalFunction, double>(&topStates, &bottomStates, model->getTransitionMatrix()); + auto res = extender.extendOrder(nullptr, region); + auto order = std::get<0>(res); + EXPECT_EQ(order->getNumberOfAddedStates(), model->getNumberOfStates()); + EXPECT_TRUE(order->getDoneBuilding()); + + // Check on all states, as this one automatically handles assumptions (if there is one valid) all are ABOVE + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(3,0)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(3,1)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(3,2)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(3,4)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(1,0)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(1,2)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(1,4)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(0,2)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(0,4)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(2,4)); +} + +TEST(OrderExtenderTest, casestudy1_on_model) { + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/simple1.pm"; + std::string formulaAsString = "P=? [F s=3 ]"; + std::string constantsAsString = ""; + + // model + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*model); + ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); + model = simplifier.getSimplifiedModel(); + + // Create the region + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto region=storm::api::parseRegion<storm::RationalFunction>("0.51<=p<=0.9", modelParameters); + + auto extender = storm::analysis::OrderExtender<storm::RationalFunction, double>(model, formulas[0]); + auto order = std::get<0>(extender.toOrder(region)); + + EXPECT_EQ(order->getNumberOfAddedStates(), 5); + EXPECT_TRUE(order->getDoneBuilding()); + // Check on all states + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(3,0)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(3,1)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(3,2)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(3,4)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(1,0)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(1,2)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(1,4)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(0,2)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(0,4)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(2,4)); +} + +TEST(OrderExtenderTest, casestudy1_on_matrix) { + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/casestudy1.pm"; + std::string formulaAsString = "P=? [F s=3 ]"; + std::string constantsAsString = ""; + + // model + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*model); + ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); + model = simplifier.getSimplifiedModel(); + + // Create the region + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto region=storm::api::parseRegion<storm::RationalFunction>("0.51 <= p <= 0.9", modelParameters); + + // For order extender + storm::modelchecker::SparsePropositionalModelChecker<storm::models::sparse::Model<storm::RationalFunction>> propositionalChecker(*model); + storm::storage::BitVector phiStates; + storm::storage::BitVector psiStates; + phiStates = storm::storage::BitVector(model->getTransitionMatrix().getRowCount(), true); + storm::logic::EventuallyFormula formula = formulas[0]->asProbabilityOperatorFormula().getSubformula().asEventuallyFormula(); + psiStates = propositionalChecker.check(formula.getSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector(); + // Get the maybeStates + std::pair<storm::storage::BitVector, storm::storage::BitVector> statesWithProbability01 = storm::utility::graph::performProb01(model->getBackwardTransitions(), phiStates, psiStates); + storm::storage::BitVector topStates = statesWithProbability01.second; + storm::storage::BitVector bottomStates = statesWithProbability01.first; + + // OrderExtender + auto extender = storm::analysis::OrderExtender<storm::RationalFunction, double>(&topStates, &bottomStates, model->getTransitionMatrix()); + auto res = extender.extendOrder(nullptr, region); + auto order = std::get<0>(res); + EXPECT_EQ(order->getNumberOfAddedStates(), model->getNumberOfStates()); + EXPECT_TRUE(order->getDoneBuilding()); + + // Check on all states + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(3,0)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(3,1)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(3,2)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(3,4)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(1,0)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(1,2)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(1,4)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(0,2)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(0,4)); + EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order->compare(2,4)); +} + +TEST(OrderExtenderTest, casestudy2_on_matrix) { + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/casestudy2.pm"; + std::string formulaAsString = "P=? [F s=4 ]"; + std::string constantsAsString = ""; + + // model + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*model); + ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); + model = simplifier.getSimplifiedModel(); + + // Create the region + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto region=storm::api::parseRegion<storm::RationalFunction>("0.1 <= p <= 0.2", modelParameters); + + // For order extender + storm::modelchecker::SparsePropositionalModelChecker<storm::models::sparse::Model<storm::RationalFunction>> propositionalChecker(*model); + storm::storage::BitVector phiStates; + storm::storage::BitVector psiStates; + phiStates = storm::storage::BitVector(model->getTransitionMatrix().getRowCount(), true); + storm::logic::EventuallyFormula formula = formulas[0]->asProbabilityOperatorFormula().getSubformula().asEventuallyFormula(); + psiStates = propositionalChecker.check(formula.getSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector(); + // Get the maybeStates + std::pair<storm::storage::BitVector, storm::storage::BitVector> statesWithProbability01 = storm::utility::graph::performProb01(model->getBackwardTransitions(), phiStates, psiStates); + storm::storage::BitVector topStates = statesWithProbability01.second; + storm::storage::BitVector bottomStates = statesWithProbability01.first; + + // OrderExtender + auto extender = storm::analysis::OrderExtender<storm::RationalFunction, double>(&topStates, &bottomStates, model->getTransitionMatrix()); + auto res = extender.extendOrder(nullptr, region); + EXPECT_TRUE(std::get<0>(res)->getDoneBuilding()); +} diff --git a/src/test/storm-pars/analysis/OrderTest.cpp b/src/test/storm-pars/analysis/OrderTest.cpp index f4481d34d..1026c9bc7 100644 --- a/src/test/storm-pars/analysis/OrderTest.cpp +++ b/src/test/storm-pars/analysis/OrderTest.cpp @@ -1,7 +1,7 @@ +#include <storm/storage/StronglyConnectedComponentDecomposition.h> #include "test/storm_gtest.h" #include "storm-config.h" -#include "test/storm_gtest.h" -#include "storm-pars/analysis/Order.h" +#include "storm-pars/api/analysis.h" #include "storm/storage/BitVector.h" TEST(OrderTest, Simple) { @@ -10,10 +10,15 @@ TEST(OrderTest, Simple) { above.set(0); auto below = storm::storage::BitVector(numberOfStates); below.set(1); - auto initialMiddle = storm::storage::BitVector(numberOfStates); - std::vector<uint_fast64_t> statesSorted; - - auto order = storm::analysis::Order(&above, &below, &initialMiddle, numberOfStates, &statesSorted); + storm::storage::SparseMatrixBuilder<storm::RationalFunction> matrixBuilder(2,2,2); + matrixBuilder.addNextValue(0,0,storm::RationalFunction(1)); + matrixBuilder.addNextValue(1,1,storm::RationalFunction(1)); + storm::storage::StronglyConnectedComponentDecompositionOptions options; + options.forceTopologicalSort(); + auto matrix= matrixBuilder.build(); + auto decomposition = storm::storage::StronglyConnectedComponentDecomposition<storm::RationalFunction>(matrix, options); + auto statesSorted = storm::utility::graph::getTopologicalSort(matrix); + auto order = storm::analysis::Order(&above, &below, numberOfStates, decomposition, statesSorted); EXPECT_EQ(storm::analysis::Order::NodeComparison::ABOVE, order.compare(0,1)); EXPECT_EQ(storm::analysis::Order::NodeComparison::BELOW, order.compare(1,0)); EXPECT_EQ(nullptr, order.getNode(2)); @@ -77,10 +82,15 @@ TEST(OrderTest, copy_order) { above.set(0); auto below = storm::storage::BitVector(numberOfStates); below.set(1); - auto initialMiddle = storm::storage::BitVector(numberOfStates); - std::vector<uint_fast64_t> statesSorted; - - auto order = storm::analysis::Order(&above, &below, &initialMiddle, numberOfStates, &statesSorted); + storm::storage::SparseMatrixBuilder<storm::RationalFunction> matrixBuilder(2,2,2); + matrixBuilder.addNextValue(0,0,storm::RationalFunction(1)); + matrixBuilder.addNextValue(1,1,storm::RationalFunction(1)); + storm::storage::StronglyConnectedComponentDecompositionOptions options; + options.forceTopologicalSort(); + auto matrix= matrixBuilder.build(); + auto decomposition = storm::storage::StronglyConnectedComponentDecomposition<storm::RationalFunction>(matrix, options); + auto statesSorted = storm::utility::graph::getTopologicalSort(matrix); + auto order = storm::analysis::Order(&above, &below, numberOfStates, decomposition, statesSorted); order.add(2); order.add(3); order.addToNode(4, order.getNode(2)); @@ -143,10 +153,15 @@ TEST(OrderTest, merge_nodes) { above.set(0); auto below = storm::storage::BitVector(numberOfStates); below.set(1); - auto initialMiddle = storm::storage::BitVector(numberOfStates); - std::vector<uint_fast64_t> statesSorted; - - auto order = storm::analysis::Order(&above, &below, &initialMiddle, numberOfStates, &statesSorted); + storm::storage::SparseMatrixBuilder<storm::RationalFunction> matrixBuilder(2,2,2); + matrixBuilder.addNextValue(0,0,storm::RationalFunction(1)); + matrixBuilder.addNextValue(1,1,storm::RationalFunction(1)); + storm::storage::StronglyConnectedComponentDecompositionOptions options; + options.forceTopologicalSort(); + auto matrix= matrixBuilder.build(); + auto decomposition = storm::storage::StronglyConnectedComponentDecomposition<storm::RationalFunction>(matrix, options); + auto statesSorted = storm::utility::graph::getTopologicalSort(matrix); + auto order = storm::analysis::Order(&above, &below, numberOfStates, decomposition, statesSorted); order.add(2); order.add(3); order.addToNode(4, order.getNode(2)); @@ -171,3 +186,57 @@ TEST(OrderTest, merge_nodes) { EXPECT_EQ(storm::analysis::Order::NodeComparison::BELOW, order.compare(1,4)); EXPECT_EQ(storm::analysis::Order::NodeComparison::BELOW, order.compare(1,5)); } + +TEST(OrderTest, sort_states) { + auto numberOfStates = 7; + auto above = storm::storage::BitVector(numberOfStates); + above.set(0); + auto below = storm::storage::BitVector(numberOfStates); + below.set(1); + storm::storage::SparseMatrixBuilder<storm::RationalFunction> matrixBuilder(2,2,2); + matrixBuilder.addNextValue(0,0,storm::RationalFunction(1)); + matrixBuilder.addNextValue(1,1,storm::RationalFunction(1)); + storm::storage::StronglyConnectedComponentDecompositionOptions options; + options.forceTopologicalSort(); + auto matrix= matrixBuilder.build(); + auto decomposition = storm::storage::StronglyConnectedComponentDecomposition<storm::RationalFunction>(matrix, options); + auto statesSorted = storm::utility::graph::getTopologicalSort(matrix); + auto order = storm::analysis::Order(&above, &below, numberOfStates, decomposition, statesSorted); + order.add(2); + order.add(3); + order.addToNode(4, order.getNode(2)); + order.addBetween(5, order.getNode(0), order.getNode(3)); + order.addBetween(6, order.getNode(5), order.getNode(3)); + + std::vector<uint_fast64_t> statesToSort = std::vector<uint_fast64_t> {0,1,5,6}; + auto sortedStates = order.sortStates(&statesToSort); + EXPECT_EQ(sortedStates.size(), 4); + + auto itr = sortedStates.begin(); + EXPECT_EQ(*itr, 0); + EXPECT_EQ(*(++itr), 5); + EXPECT_EQ(*(++itr), 6); + EXPECT_EQ(*(++itr), 1); + + statesToSort = std::vector<uint_fast64_t> {0,1,5,6,2}; + sortedStates = order.sortStates(&statesToSort); + EXPECT_EQ(sortedStates.size(), 5); + + itr = sortedStates.begin(); + EXPECT_EQ(*itr, 0); + EXPECT_EQ(*(++itr), 5); + EXPECT_EQ(*(++itr), 6); + EXPECT_EQ(*(++itr), 1); + EXPECT_EQ(*(++itr), 7); + + statesToSort = std::vector<uint_fast64_t> {0,2,1,5,6}; + sortedStates = order.sortStates(&statesToSort); + EXPECT_EQ(sortedStates.size(), 5); + + itr = sortedStates.begin(); + EXPECT_EQ(*itr, 0); + EXPECT_EQ(*(++itr), 2); + EXPECT_EQ(*(++itr), 1); + EXPECT_EQ(*(++itr), 7); + EXPECT_EQ(*(++itr), 7); +} diff --git a/src/test/storm-pars/modelchecker/SparseDtmcParameterLiftingTest.cpp b/src/test/storm-pars/modelchecker/SparseDtmcParameterLiftingTest.cpp index b8b914e50..0dfd63fe7 100644 --- a/src/test/storm-pars/modelchecker/SparseDtmcParameterLiftingTest.cpp +++ b/src/test/storm-pars/modelchecker/SparseDtmcParameterLiftingTest.cpp @@ -12,6 +12,10 @@ #include "storm/environment/solver/MinMaxSolverEnvironment.h" #include "storm/storage/jani/Property.h" +#include "storm-pars/transformer/SparseParametricDtmcSimplifier.h" +#include "storm/solver/stateelimination/NondeterministicModelStateEliminator.h" +#include "storm/storage/StronglyConnectedComponentDecomposition.h" + namespace { @@ -25,7 +29,7 @@ namespace { return env; } }; - + class DoubleSVIEnvironment { public: typedef double ValueType; @@ -36,7 +40,7 @@ namespace { return env; } }; - + class RationalPiEnvironment { public: typedef storm::RationalNumber ValueType; @@ -57,44 +61,42 @@ namespace { private: storm::Environment _environment; }; - + typedef ::testing::Types< DoubleViEnvironment, DoubleSVIEnvironment, RationalPiEnvironment > TestingTypes; - + TYPED_TEST_SUITE(SparseDtmcParameterLiftingTest, TestingTypes,); - TYPED_TEST(SparseDtmcParameterLiftingTest, Brp_Prob) { typedef typename TestFixture::ValueType ValueType; std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/brp16_2.pm"; std::string formulaAsString = "P<=0.84 [F s=5 ]"; std::string constantsAsString = ""; //e.g. pL=0.9,TOACK=0.5 - + // Program and formula storm::prism::Program program = storm::api::parseProgram(programFile); program = storm::utility::prism::preprocess(program, constantsAsString); std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); auto rewParameters = storm::models::sparse::getRewardParameters(*model); modelParameters.insert(rewParameters.begin(), rewParameters.end()); - + auto regionChecker = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true)); - + //start testing auto allSatRegion=storm::api::parseRegion<storm::RationalFunction>("0.7<=pL<=0.9,0.75<=pK<=0.95", modelParameters); auto exBothRegion=storm::api::parseRegion<storm::RationalFunction>("0.4<=pL<=0.65,0.75<=pK<=0.95", modelParameters); auto allVioRegion=storm::api::parseRegion<storm::RationalFunction>("0.1<=pL<=0.73,0.2<=pK<=0.715", modelParameters); - + EXPECT_EQ(storm::modelchecker::RegionResult::AllSat, regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); EXPECT_EQ(storm::modelchecker::RegionResult::ExistsBoth, regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true)); EXPECT_EQ(storm::modelchecker::RegionResult::AllViolated, regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true)); - } TYPED_TEST(SparseDtmcParameterLiftingTest, Brp_Prob_no_simplification) { @@ -124,125 +126,122 @@ namespace { EXPECT_EQ(storm::modelchecker::RegionResult::AllSat, regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); EXPECT_EQ(storm::modelchecker::RegionResult::ExistsBoth, regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true)); EXPECT_EQ(storm::modelchecker::RegionResult::AllViolated, regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true)); - } - + TYPED_TEST(SparseDtmcParameterLiftingTest, Brp_Rew) { typedef typename TestFixture::ValueType ValueType; std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/brp_rewards16_2.pm"; std::string formulaAsString = "R>2.5 [F ((s=5) | (s=0&srep=3)) ]"; std::string constantsAsString = "pL=0.9,TOAck=0.5"; - + storm::prism::Program program = storm::api::parseProgram(programFile); program = storm::utility::prism::preprocess(program, constantsAsString); std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); auto rewParameters = storm::models::sparse::getRewardParameters(*model); modelParameters.insert(rewParameters.begin(), rewParameters.end()); - + auto regionChecker = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true)); - + //start testing auto allSatRegion=storm::api::parseRegion<storm::RationalFunction>("0.7<=pK<=0.875,0.75<=TOMsg<=0.95", modelParameters); auto exBothRegion=storm::api::parseRegion<storm::RationalFunction>("0.6<=pK<=0.9,0.5<=TOMsg<=0.95", modelParameters); auto allVioRegion=storm::api::parseRegion<storm::RationalFunction>("0.1<=pK<=0.3,0.2<=TOMsg<=0.3", modelParameters); - + EXPECT_EQ(storm::modelchecker::RegionResult::AllSat, regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); EXPECT_EQ(storm::modelchecker::RegionResult::ExistsBoth, regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); EXPECT_EQ(storm::modelchecker::RegionResult::AllViolated, regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); - } - + TYPED_TEST(SparseDtmcParameterLiftingTest, Brp_Rew_Bounded) { typedef typename TestFixture::ValueType ValueType; std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/brp_rewards16_2.pm"; std::string formulaAsString = "R>2.5 [ C<=300]"; std::string constantsAsString = "pL=0.9,TOAck=0.5"; - + storm::prism::Program program = storm::api::parseProgram(programFile); program = storm::utility::prism::preprocess(program, constantsAsString); std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); auto rewParameters = storm::models::sparse::getRewardParameters(*model); modelParameters.insert(rewParameters.begin(), rewParameters.end()); - + auto regionChecker = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true)); - + //start testing auto allSatRegion=storm::api::parseRegion<storm::RationalFunction>("0.7<=pK<=0.875,0.75<=TOMsg<=0.95", modelParameters); auto exBothRegion=storm::api::parseRegion<storm::RationalFunction>("0.6<=pK<=0.9,0.5<=TOMsg<=0.95", modelParameters); auto allVioRegion=storm::api::parseRegion<storm::RationalFunction>("0.1<=pK<=0.3,0.2<=TOMsg<=0.3", modelParameters); - + EXPECT_EQ(storm::modelchecker::RegionResult::AllSat, regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); EXPECT_EQ(storm::modelchecker::RegionResult::ExistsBoth, regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); EXPECT_EQ(storm::modelchecker::RegionResult::AllViolated, regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); - } - + TYPED_TEST(SparseDtmcParameterLiftingTest, Brp_Prob_exactValidation) { typedef typename TestFixture::ValueType ValueType; if (!std::is_same<ValueType, storm::RationalNumber>::value) { - + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/brp16_2.pm"; std::string formulaAsString = "P<=0.84 [F s=5 ]"; std::string constantsAsString = ""; //e.g. pL=0.9,TOACK=0.5 - + // Program and formula storm::prism::Program program = storm::api::parseProgram(programFile); program = storm::utility::prism::preprocess(program, constantsAsString); std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - + auto regionChecker = storm::api::initializeValidatingRegionModelChecker<storm::RationalFunction, ValueType, storm::RationalNumber>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true)); - + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); auto rewParameters = storm::models::sparse::getRewardParameters(*model); modelParameters.insert(rewParameters.begin(), rewParameters.end()); - + //start testing auto allSatRegion=storm::api::parseRegion<storm::RationalFunction>("0.7<=pL<=0.9,0.75<=pK<=0.95", modelParameters); auto exBothRegion=storm::api::parseRegion<storm::RationalFunction>("0.4<=pL<=0.65,0.75<=pK<=0.95", modelParameters); auto allVioRegion=storm::api::parseRegion<storm::RationalFunction>("0.1<=pL<=0.73,0.2<=pK<=0.715", modelParameters); - + EXPECT_EQ(storm::modelchecker::RegionResult::AllSat, regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); EXPECT_EQ(storm::modelchecker::RegionResult::ExistsBoth, regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); EXPECT_EQ(storm::modelchecker::RegionResult::AllViolated, regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); } } - + TYPED_TEST(SparseDtmcParameterLiftingTest, Brp_Rew_exactValidation) { typedef typename TestFixture::ValueType ValueType; if (!std::is_same<ValueType, storm::RationalNumber>::value) { std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/brp_rewards16_2.pm"; std::string formulaAsString = "R>2.5 [F ((s=5) | (s=0&srep=3)) ]"; std::string constantsAsString = "pL=0.9,TOAck=0.5"; - + storm::prism::Program program = storm::api::parseProgram(programFile); program = storm::utility::prism::preprocess(program, constantsAsString); std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); auto rewParameters = storm::models::sparse::getRewardParameters(*model); modelParameters.insert(rewParameters.begin(), rewParameters.end()); - + auto regionChecker = storm::api::initializeValidatingRegionModelChecker<storm::RationalFunction, ValueType, storm::RationalNumber>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true)); - + //start testing auto allSatRegion=storm::api::parseRegion<storm::RationalFunction>("0.7<=pK<=0.875,0.75<=TOMsg<=0.95", modelParameters); auto exBothRegion=storm::api::parseRegion<storm::RationalFunction>("0.6<=pK<=0.9,0.5<=TOMsg<=0.95", modelParameters); auto allVioRegion=storm::api::parseRegion<storm::RationalFunction>("0.1<=pK<=0.3,0.2<=TOMsg<=0.3", modelParameters); - + EXPECT_EQ(storm::modelchecker::RegionResult::AllSat, regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); EXPECT_EQ(storm::modelchecker::RegionResult::ExistsBoth, regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); EXPECT_EQ(storm::modelchecker::RegionResult::AllViolated, regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); } } - + TYPED_TEST(SparseDtmcParameterLiftingTest, Brp_Rew_Bounded_exactValidation) { typedef typename TestFixture::ValueType ValueType; @@ -250,32 +249,32 @@ namespace { std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/brp_rewards16_2.pm"; std::string formulaAsString = "R>2.5 [ C<=300]"; std::string constantsAsString = "pL=0.9,TOAck=0.5"; - + storm::prism::Program program = storm::api::parseProgram(programFile); program = storm::utility::prism::preprocess(program, constantsAsString); std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); auto rewParameters = storm::models::sparse::getRewardParameters(*model); modelParameters.insert(rewParameters.begin(), rewParameters.end()); - + auto regionChecker = storm::api::initializeValidatingRegionModelChecker<storm::RationalFunction, ValueType, storm::RationalNumber>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true)); - + //start testing auto allSatRegion=storm::api::parseRegion<storm::RationalFunction>("0.7<=pK<=0.875,0.75<=TOMsg<=0.95", modelParameters); auto exBothRegion=storm::api::parseRegion<storm::RationalFunction>("0.6<=pK<=0.9,0.5<=TOMsg<=0.95", modelParameters); auto allVioRegion=storm::api::parseRegion<storm::RationalFunction>("0.1<=pK<=0.3,0.2<=TOMsg<=0.3", modelParameters); - + EXPECT_EQ(storm::modelchecker::RegionResult::AllSat, regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); EXPECT_EQ(storm::modelchecker::RegionResult::ExistsBoth, regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); EXPECT_EQ(storm::modelchecker::RegionResult::AllViolated, regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); } } - + TYPED_TEST(SparseDtmcParameterLiftingTest, Brp_Rew_Infty) { typedef typename TestFixture::ValueType ValueType; - + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/brp_rewards16_2.pm"; std::string formulaAsString = "R>2.5 [F (s=0&srep=3) ]"; std::string constantsAsString = ""; @@ -283,23 +282,22 @@ namespace { program = storm::utility::prism::preprocess(program, constantsAsString); std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); auto rewParameters = storm::models::sparse::getRewardParameters(*model); modelParameters.insert(rewParameters.begin(), rewParameters.end()); - + auto regionChecker = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true)); - + //start testing auto allSatRegion=storm::api::parseRegion<storm::RationalFunction>("0.7<=pK<=0.9,0.6<=pL<=0.85,0.9<=TOMsg<=0.95,0.85<=TOAck<=0.9", modelParameters); - + EXPECT_EQ(storm::modelchecker::RegionResult::AllSat, regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); - } - + TYPED_TEST(SparseDtmcParameterLiftingTest, Brp_Rew_4Par) { typedef typename TestFixture::ValueType ValueType; - + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/brp_rewards16_2.pm"; std::string formulaAsString = "R>2.5 [F ((s=5) | (s=0&srep=3)) ]"; std::string constantsAsString = ""; //!! this model will have 4 parameters @@ -307,139 +305,689 @@ namespace { program = storm::utility::prism::preprocess(program, constantsAsString); std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); auto rewParameters = storm::models::sparse::getRewardParameters(*model); modelParameters.insert(rewParameters.begin(), rewParameters.end()); - + auto regionChecker = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true)); - + //start testing auto allSatRegion=storm::api::parseRegion<storm::RationalFunction>("0.7<=pK<=0.9,0.6<=pL<=0.85,0.9<=TOMsg<=0.95,0.85<=TOAck<=0.9", modelParameters); auto exBothRegion=storm::api::parseRegion<storm::RationalFunction>("0.1<=pK<=0.7,0.2<=pL<=0.8,0.15<=TOMsg<=0.65,0.3<=TOAck<=0.9", modelParameters); auto allVioRegion=storm::api::parseRegion<storm::RationalFunction>("0.1<=pK<=0.4,0.2<=pL<=0.3,0.15<=TOMsg<=0.3,0.1<=TOAck<=0.2", modelParameters); - + EXPECT_EQ(storm::modelchecker::RegionResult::AllSat, regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); EXPECT_EQ(storm::modelchecker::RegionResult::ExistsBoth, regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); EXPECT_EQ(storm::modelchecker::RegionResult::AllViolated, regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); - } - + TYPED_TEST(SparseDtmcParameterLiftingTest, Crowds_Prob) { typedef typename TestFixture::ValueType ValueType; - + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/crowds3_5.pm"; std::string formulaAsString = "P<0.5 [F \"observe0Greater1\" ]"; std::string constantsAsString = ""; //e.g. pL=0.9,TOACK=0.5 - + storm::prism::Program program = storm::api::parseProgram(programFile); program = storm::utility::prism::preprocess(program, constantsAsString); std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); auto rewParameters = storm::models::sparse::getRewardParameters(*model); modelParameters.insert(rewParameters.begin(), rewParameters.end()); - + auto regionChecker = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true)); - + //start testing auto allSatRegion=storm::api::parseRegion<storm::RationalFunction>("0.1<=PF<=0.75,0.15<=badC<=0.2", modelParameters); auto exBothRegion=storm::api::parseRegion<storm::RationalFunction>("0.75<=PF<=0.8,0.2<=badC<=0.3", modelParameters); auto allVioRegion=storm::api::parseRegion<storm::RationalFunction>("0.8<=PF<=0.95,0.2<=badC<=0.2", modelParameters); auto allVioHardRegion=storm::api::parseRegion<storm::RationalFunction>("0.8<=PF<=0.95,0.2<=badC<=0.9", modelParameters); - + EXPECT_EQ(storm::modelchecker::RegionResult::AllSat, regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); EXPECT_EQ(storm::modelchecker::RegionResult::ExistsBoth, regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); EXPECT_EQ(storm::modelchecker::RegionResult::AllViolated, regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); EXPECT_EQ(storm::modelchecker::RegionResult::CenterViolated, regionChecker->analyzeRegion(this->env(), allVioHardRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); - } - + TYPED_TEST(SparseDtmcParameterLiftingTest, Crowds_Prob_stepBounded) { typedef typename TestFixture::ValueType ValueType; - + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/crowds3_5.pm"; std::string formulaAsString = "P<0.5 [F<=300 \"observe0Greater1\" ]"; std::string constantsAsString = ""; //e.g. pL=0.9,TOACK=0.5 - + storm::prism::Program program = storm::api::parseProgram(programFile); program = storm::utility::prism::preprocess(program, constantsAsString); std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); auto rewParameters = storm::models::sparse::getRewardParameters(*model); modelParameters.insert(rewParameters.begin(), rewParameters.end()); - + auto regionChecker = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true)); - + //start testing auto allSatRegion=storm::api::parseRegion<storm::RationalFunction>("0.1<=PF<=0.75,0.15<=badC<=0.2", modelParameters); auto exBothRegion=storm::api::parseRegion<storm::RationalFunction>("0.75<=PF<=0.8,0.2<=badC<=0.3", modelParameters); auto allVioRegion=storm::api::parseRegion<storm::RationalFunction>("0.8<=PF<=0.95,0.2<=badC<=0.2", modelParameters); auto allVioHardRegion=storm::api::parseRegion<storm::RationalFunction>("0.8<=PF<=0.95,0.2<=badC<=0.9", modelParameters); - + EXPECT_EQ(storm::modelchecker::RegionResult::AllSat, regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); EXPECT_EQ(storm::modelchecker::RegionResult::ExistsBoth, regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); EXPECT_EQ(storm::modelchecker::RegionResult::AllViolated, regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); EXPECT_EQ(storm::modelchecker::RegionResult::CenterViolated, regionChecker->analyzeRegion(this->env(), allVioHardRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); - } - + TYPED_TEST(SparseDtmcParameterLiftingTest, Crowds_Prob_1Par) { typedef typename TestFixture::ValueType ValueType; - + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/crowds3_5.pm"; std::string formulaAsString = "P>0.75 [F \"observe0Greater1\" ]"; std::string constantsAsString = "badC=0.3"; //e.g. pL=0.9,TOACK=0.5 - - + + storm::prism::Program program = storm::api::parseProgram(programFile); program = storm::utility::prism::preprocess(program, constantsAsString); std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); auto rewParameters = storm::models::sparse::getRewardParameters(*model); modelParameters.insert(rewParameters.begin(), rewParameters.end()); - + auto regionChecker = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true)); - + //start testing auto allSatRegion=storm::api::parseRegion<storm::RationalFunction>("0.9<=PF<=0.99", modelParameters); auto exBothRegion=storm::api::parseRegion<storm::RationalFunction>("0.8<=PF<=0.9", modelParameters); auto allVioRegion=storm::api::parseRegion<storm::RationalFunction>("0.01<=PF<=0.8", modelParameters); - + EXPECT_EQ(storm::modelchecker::RegionResult::AllSat, regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); EXPECT_EQ(storm::modelchecker::RegionResult::ExistsBoth, regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); EXPECT_EQ(storm::modelchecker::RegionResult::AllViolated, regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); - } - + TYPED_TEST(SparseDtmcParameterLiftingTest, Crowds_Prob_Const) { typedef typename TestFixture::ValueType ValueType; - + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/crowds3_5.pm"; std::string formulaAsString = "P>0.6 [F \"observe0Greater1\" ]"; std::string constantsAsString = "PF=0.9,badC=0.2"; - + storm::prism::Program program = storm::api::parseProgram(programFile); program = storm::utility::prism::preprocess(program, constantsAsString); std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); - + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); auto rewParameters = storm::models::sparse::getRewardParameters(*model); modelParameters.insert(rewParameters.begin(), rewParameters.end()); - + auto regionChecker = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true)); - + //start testing auto allSatRegion=storm::api::parseRegion<storm::RationalFunction>("", modelParameters); - + + EXPECT_EQ(storm::modelchecker::RegionResult::AllSat, regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); + } + + TYPED_TEST(SparseDtmcParameterLiftingTest, ZeroConf) { + typedef typename TestFixture::ValueType ValueType; + + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/zeroconf4.pm"; + std::string formulaAsString = "P>0.5 [F s=5 ]"; + std::string constantsAsString = " n = 4"; //e.g. pL=0.9,TOACK=0.5 + + // Program and formula + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto rewParameters = storm::models::sparse::getRewardParameters(*model); + modelParameters.insert(rewParameters.begin(), rewParameters.end()); + + auto regionChecker = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true)); + + //start testing + auto allSatRegion=storm::api::parseRegion<storm::RationalFunction>("0.8<=pL<=0.95,0.8<=pK<=0.95", modelParameters); + auto exBothRegion=storm::api::parseRegion<storm::RationalFunction>("0.6<=pL<=0.9,0.6<=pK<=0.9", modelParameters); + auto allVioRegion=storm::api::parseRegion<storm::RationalFunction>("0.1<=pL<=0.7,0.1<=pK<=0.7", modelParameters); + EXPECT_EQ(storm::modelchecker::RegionResult::AllSat, regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true)); - + EXPECT_EQ(storm::modelchecker::RegionResult::ExistsBoth, regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true)); + EXPECT_EQ(storm::modelchecker::RegionResult::AllViolated, regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true)); + } + + TYPED_TEST(SparseDtmcParameterLiftingTest, Brp_Prob_Mon_LEQ) { + typedef typename TestFixture::ValueType ValueType; + + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/brp16_2.pm"; + std::string formulaAsString = "P<=0.84 [F s=5 ]"; + std::string constantsAsString = ""; //e.g. pL=0.9,TOACK=0.5 + + // Program and formula + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + + // Simplify model + std::vector<storm::storage::ParameterRegion<storm::RationalFunction>> regions; + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*model); + ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); + model = simplifier.getSimplifiedModel()->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + formulas[0] = simplifier.getSimplifiedFormula(); + + // Apply bisimulation + storm::storage::BisimulationType bisimType = storm::storage::BisimulationType::Strong; + if (storm::settings::getModule<storm::settings::modules::BisimulationSettings>().isWeakBisimulationSet()) { + bisimType = storm::storage::BisimulationType::Weak; + } + model = storm::api::performBisimulationMinimization<storm::RationalFunction>(model, formulas, bisimType)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + + // Model parameters + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto rewParameters = storm::models::sparse::getRewardParameters(*model); + modelParameters.insert(rewParameters.begin(), rewParameters.end()); + + // Reachability order, as it is already done building we don't need to recreate the order for each region + auto monHelper = new storm::analysis::MonotonicityHelper<storm::RationalFunction, ValueType>(model, formulas, regions); + auto order = monHelper->checkMonotonicityInBuild(std::cout).begin()->first; + ASSERT_EQ(order->getNumberOfAddedStates(), model->getTransitionMatrix().getColumnCount()); + ASSERT_TRUE(order->getDoneBuilding()); + + // Modelcheckers + auto regionCheckerMon = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true), false, false, true); + auto regionChecker = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true), false, false); + + //start testing + auto allSatRegion=storm::api::parseRegion<storm::RationalFunction>("0.7<=pL<=0.9,0.75<=pK<=0.95", modelParameters); + auto monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + auto expectedResult = regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true); + EXPECT_EQ(expectedResult, regionCheckerMon->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(expectedResult, regionCheckerMon->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + + auto exBothRegion=storm::api::parseRegion<storm::RationalFunction>("0.4<=pL<=0.65,0.75<=pK<=0.95", modelParameters); + monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + expectedResult = regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true); + EXPECT_EQ(expectedResult, regionCheckerMon->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(expectedResult, regionCheckerMon->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + + auto allVioRegion=storm::api::parseRegion<storm::RationalFunction>("0.1<=pL<=0.73,0.2<=pK<=0.715", modelParameters); + monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + expectedResult = regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true); + EXPECT_EQ(expectedResult, regionCheckerMon->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + } + + TYPED_TEST(SparseDtmcParameterLiftingTest, Brp_Prob_Mon_GEQ) { + typedef typename TestFixture::ValueType ValueType; + + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/brp16_2.pm"; + std::string formulaAsString = "P>=0.84 [F s=5 ]"; + std::string constantsAsString = ""; //e.g. pL=0.9,TOACK=0.5 + + // Program and formula + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + + // Simplify model + std::vector<storm::storage::ParameterRegion<storm::RationalFunction>> regions; + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*model); + ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); + model = simplifier.getSimplifiedModel()->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + formulas[0] = simplifier.getSimplifiedFormula(); + + // Apply bisimulation + storm::storage::BisimulationType bisimType = storm::storage::BisimulationType::Strong; + if (storm::settings::getModule<storm::settings::modules::BisimulationSettings>().isWeakBisimulationSet()) { + bisimType = storm::storage::BisimulationType::Weak; + } + model = storm::api::performBisimulationMinimization<storm::RationalFunction>(model, formulas, bisimType)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + + // Model parameters + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto rewParameters = storm::models::sparse::getRewardParameters(*model); + modelParameters.insert(rewParameters.begin(), rewParameters.end()); + + // Reachability order, as it is already done building we don't need to recreate the order for each region + auto o = new storm::analysis::OrderExtender<storm::RationalFunction, ValueType>(model, formulas[0]); + auto res = o->extendOrder(nullptr, storm::api::parseRegion<storm::RationalFunction>("0.01<=pL<=0.99,0.01<=pK<=0.99", modelParameters)); + auto order = std::get<0>(res); + ASSERT_EQ(order->getNumberOfAddedStates(), model->getTransitionMatrix().getColumnCount()); + ASSERT_TRUE(order->getDoneBuilding()); + + // Modelcheckers + auto regionCheckerMon = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true), false, false, true); + auto regionChecker = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true), false, false); + + //start testing + auto allSatRegion=storm::api::parseRegion<storm::RationalFunction>("0.1<=pL<=0.73,0.2<=pK<=0.715", modelParameters); + auto expectedResult = regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true); + auto monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + EXPECT_EQ(expectedResult, regionCheckerMon->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(expectedResult, regionCheckerMon->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + + auto exBothRegion=storm::api::parseRegion<storm::RationalFunction>("0.4<=pL<=0.65,0.75<=pK<=0.95", modelParameters); + monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + expectedResult = regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true); + EXPECT_EQ(expectedResult, regionCheckerMon->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(expectedResult, regionCheckerMon->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + + auto allVioRegion=storm::api::parseRegion<storm::RationalFunction>("0.7<=pL<=0.9,0.75<=pK<=0.95", modelParameters); + monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + expectedResult =regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true); + EXPECT_EQ(expectedResult, regionCheckerMon->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(expectedResult,regionCheckerMon->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + } + + TYPED_TEST(SparseDtmcParameterLiftingTest, Brp_Prob_Mon_LEQ_Incr) { + typedef typename TestFixture::ValueType ValueType; + + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/brp16_2_mon_incr.pm"; + std::string formulaAsString = "P<=0.84 [F s=5 ]"; + std::string constantsAsString = ""; //e.g. pL=0.9,TOACK=0.5 + + // Program and formula + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + + // Simplify model + std::vector<storm::storage::ParameterRegion<storm::RationalFunction>> regions; + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*model); + ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); + model = simplifier.getSimplifiedModel()->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + formulas[0] = simplifier.getSimplifiedFormula(); + + // Apply bisimulation + storm::storage::BisimulationType bisimType = storm::storage::BisimulationType::Strong; + if (storm::settings::getModule<storm::settings::modules::BisimulationSettings>().isWeakBisimulationSet()) { + bisimType = storm::storage::BisimulationType::Weak; + } + model = storm::api::performBisimulationMinimization<storm::RationalFunction>(model, formulas, bisimType)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + + // Model parameters + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto rewParameters = storm::models::sparse::getRewardParameters(*model); + modelParameters.insert(rewParameters.begin(), rewParameters.end()); + + // Reachability order, as it is already done building we don't need to recreate the order for each region + auto monHelper = new storm::analysis::MonotonicityHelper<storm::RationalFunction, ValueType>(model, formulas, regions); + auto order = monHelper->checkMonotonicityInBuild(std::cout).begin()->first; + ASSERT_EQ(order->getNumberOfAddedStates(), model->getTransitionMatrix().getColumnCount()); + ASSERT_TRUE(order->getDoneBuilding()); + + // Modelcheckers + auto regionCheckerMon = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true), false, false, true); + auto regionChecker = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true), false, false); + + //start testing + auto allSatRegion=storm::api::parseRegion<storm::RationalFunction>("0.7<=pL<=0.9,0.75<=pK<=0.95", modelParameters); + auto monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + auto expectedResult = regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true); + EXPECT_EQ(expectedResult, regionCheckerMon->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(expectedResult, regionCheckerMon->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + + auto exBothRegion=storm::api::parseRegion<storm::RationalFunction>("0.4<=pL<=0.65,0.75<=pK<=0.95", modelParameters); + monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + expectedResult = regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true); + EXPECT_EQ(expectedResult, regionCheckerMon->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(expectedResult, regionCheckerMon->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + + auto allVioRegion=storm::api::parseRegion<storm::RationalFunction>("0.1<=pL<=0.73,0.2<=pK<=0.715", modelParameters); + monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + expectedResult = regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true); + EXPECT_EQ(expectedResult, regionCheckerMon->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(expectedResult, regionCheckerMon->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + } + + TYPED_TEST(SparseDtmcParameterLiftingTest, Brp_Prob_Mon_GEQ_Incr) { + typedef typename TestFixture::ValueType ValueType; + + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/brp16_2_mon_incr.pm"; + std::string formulaAsString = "P>=0.84 [F s=5 ]"; + std::string constantsAsString = ""; //e.g. pL=0.9,TOACK=0.5 + + // Program and formula + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + + // Simplify model + std::vector<storm::storage::ParameterRegion<storm::RationalFunction>> regions; + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*model); + ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); + model = simplifier.getSimplifiedModel()->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + formulas[0] = simplifier.getSimplifiedFormula(); + + // Apply bisimulation + storm::storage::BisimulationType bisimType = storm::storage::BisimulationType::Strong; + if (storm::settings::getModule<storm::settings::modules::BisimulationSettings>().isWeakBisimulationSet()) { + bisimType = storm::storage::BisimulationType::Weak; + } + model = storm::api::performBisimulationMinimization<storm::RationalFunction>(model, formulas, bisimType)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + + // Model parameters + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto rewParameters = storm::models::sparse::getRewardParameters(*model); + modelParameters.insert(rewParameters.begin(), rewParameters.end()); + + // Reachability order, as it is already done building we don't need to recreate the order for each region + auto o = new storm::analysis::OrderExtender<storm::RationalFunction, ValueType>(model, formulas[0]); + auto res = o->extendOrder(nullptr, storm::api::parseRegion<storm::RationalFunction>("0.01<=pL<=0.99,0.01<=pK<=0.99", modelParameters)); + auto order = std::get<0>(res); + + ASSERT_EQ(order->getNumberOfAddedStates(), model->getTransitionMatrix().getColumnCount()); + ASSERT_TRUE(order->getDoneBuilding()); + + // Modelcheckers + auto regionCheckerMon = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true), false, false, true); + auto regionChecker = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true), false, false); + + //start testing + auto allSatRegion=storm::api::parseRegion<storm::RationalFunction>("0.1<=pL<=0.73,0.2<=pK<=0.715", modelParameters); + auto expectedResult = regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true); + auto monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + EXPECT_EQ(expectedResult, regionCheckerMon->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(expectedResult, regionCheckerMon->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + + auto exBothRegion=storm::api::parseRegion<storm::RationalFunction>("0.4<=pL<=0.65,0.75<=pK<=0.95", modelParameters); + monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + expectedResult = regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true); + EXPECT_EQ(expectedResult, regionCheckerMon->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(expectedResult, regionCheckerMon->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + + auto allVioRegion=storm::api::parseRegion<storm::RationalFunction>("0.7<=pL<=0.9,0.75<=pK<=0.95", modelParameters); + monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + expectedResult =regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true); + EXPECT_EQ(expectedResult, regionCheckerMon->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(expectedResult,regionCheckerMon->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + } + + TYPED_TEST(SparseDtmcParameterLiftingTest, Parametric_Die_Mon) { + typedef typename TestFixture::ValueType ValueType; + + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/parametric_die_2.pm"; + std::string formulaAsString = "P <=0.5 [F s=7 & d=2 ]"; + std::string constantsAsString = ""; //e.g. pL=0.9,TOACK=0.5 + + // Program and formula + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + + // Model parameters + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto rewParameters = storm::models::sparse::getRewardParameters(*model); + modelParameters.insert(rewParameters.begin(), rewParameters.end()); + + // Simplify model + std::vector<storm::storage::ParameterRegion<storm::RationalFunction>> regions; + auto simplifier = storm::transformer::SparseParametricDtmcSimplifier<storm::models::sparse::Dtmc<storm::RationalFunction>>(*model); + ASSERT_TRUE(simplifier.simplify(*(formulas[0]))); + model = simplifier.getSimplifiedModel()->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + formulas[0] = simplifier.getSimplifiedFormula(); + + // Apply bisimulation + storm::storage::BisimulationType bisimType = storm::storage::BisimulationType::Strong; + if (storm::settings::getModule<storm::settings::modules::BisimulationSettings>().isWeakBisimulationSet()) { + bisimType = storm::storage::BisimulationType::Weak; + } + model = storm::api::performBisimulationMinimization<storm::RationalFunction>(model, formulas, bisimType)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + + // Modelcheckers + auto regionCheckerMon = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true), false, false, true); + auto regionChecker = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true), false, false); + + // Start testing + auto allSatRegion = storm::api::parseRegion<storm::RationalFunction>("0.1<=p<=0.2,0.8<=q<=0.9", modelParameters); + auto o = new storm::analysis::OrderExtender<storm::RationalFunction, ValueType>(model, formulas[0]); + auto res = o->extendOrder(nullptr, allSatRegion); + auto order = std::get<0>(res); + auto monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + + auto exBothRegion = storm::api::parseRegion<storm::RationalFunction>("0.1<=p<=0.9,0.1<=q<=0.9", modelParameters); + res = o->extendOrder(nullptr, exBothRegion); + order = std::get<0>(res); + monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + + auto allVioRegion = storm::api::parseRegion<storm::RationalFunction>("0.8<=p<=0.9,0.1<=q<=0.2", modelParameters); + res = o->extendOrder(nullptr, allVioRegion); + order = std::get<0>(res); + monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + } + + TYPED_TEST(SparseDtmcParameterLiftingTest, Simple1_Mon) { + typedef typename TestFixture::ValueType ValueType; + + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/simple1.pm"; + std::string formulaAsString = "P<0.75 [F s=3 ]"; + std::string constantsAsString = ""; + + // Program and formula + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + + // Model parameters + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto rewParameters = storm::models::sparse::getRewardParameters(*model); + modelParameters.insert(rewParameters.begin(), rewParameters.end()); + + // Modelcheckers + auto regionCheckerMon = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true), false, false, true); + auto regionChecker = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true), false); + + // Start testing + auto allSatRegion=storm::api::parseRegion<storm::RationalFunction>("0.4<=p<=0.6", modelParameters); + auto o = new storm::analysis::OrderExtender<storm::RationalFunction, ValueType>(model, formulas[0]); + auto res = o->extendOrder(nullptr, allSatRegion); + auto order = std::get<0>(res); + auto monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + + auto exBothRegion=storm::api::parseRegion<storm::RationalFunction>("0.1<=p<=0.9", modelParameters); + res = o->extendOrder(nullptr, exBothRegion); + order = std::get<0>(res); + monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + + auto allVioRegion=storm::api::parseRegion<storm::RationalFunction>("0.05<=p<=0.1", modelParameters); + res = o->extendOrder(nullptr, allVioRegion); + order = std::get<0>(res); + monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + + } + + TYPED_TEST(SparseDtmcParameterLiftingTest, Casestudy1_Mon) { + typedef typename TestFixture::ValueType ValueType; + + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/casestudy1.pm"; + std::string formulaAsString = "P<0.5 [F s=3 ]"; + std::string constantsAsString = ""; + + // Program and formula + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + + // Model parameters + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto rewParameters = storm::models::sparse::getRewardParameters(*model); + modelParameters.insert(rewParameters.begin(), rewParameters.end()); + + // Modelcheckers + auto regionCheckerMon = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true), false, false, true); + auto regionChecker = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true), false); + + // Start testing + auto allSatRegion=storm::api::parseRegion<storm::RationalFunction>("0.1<=p<=0.5", modelParameters); + auto o = new storm::analysis::OrderExtender<storm::RationalFunction, ValueType>(model, formulas[0]); + auto res = o->extendOrder(nullptr, allSatRegion); + auto order = std::get<0>(res); + auto monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + + auto exBothRegion=storm::api::parseRegion<storm::RationalFunction>("0.4<=p<=0.8", modelParameters); + res = o->extendOrder(nullptr, exBothRegion); + order = std::get<0>(res); + monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + + auto allVioRegion=storm::api::parseRegion<storm::RationalFunction>("0.7<=p<=0.9", modelParameters); + res = o->extendOrder(nullptr, allVioRegion); + order = std::get<0>(res); + monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + } + + TYPED_TEST(SparseDtmcParameterLiftingTest, Casestudy2_Mon) { + typedef typename TestFixture::ValueType ValueType; + + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/casestudy2.pm"; + std::string formulaAsString = "P<0.5 [F s=4 ]"; + std::string constantsAsString = ""; + + // Program and formula + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + + // Model parameters + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto rewParameters = storm::models::sparse::getRewardParameters(*model); + modelParameters.insert(rewParameters.begin(), rewParameters.end()); + + // Modelcheckers + auto regionCheckerMon = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true), false, false, true); + auto regionChecker = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true), false); + + // Start testing + auto allSatRegion=storm::api::parseRegion<storm::RationalFunction>("0.1<=p<=0.4", modelParameters); + auto o = new storm::analysis::OrderExtender<storm::RationalFunction, ValueType>(model, formulas[0]); + auto res = o->extendOrder(nullptr, allSatRegion); + auto order = std::get<0>(res); + auto monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + + auto exBothRegion=storm::api::parseRegion<storm::RationalFunction>("0.4<=p<=0.9", modelParameters); + res = o->extendOrder(nullptr, exBothRegion); + order = std::get<0>(res); + monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + + auto allVioRegion=storm::api::parseRegion<storm::RationalFunction>("0.8<=p<=0.9", modelParameters); + res = o->extendOrder(nullptr, allVioRegion); + order = std::get<0>(res); + monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown,storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + } + + + TYPED_TEST(SparseDtmcParameterLiftingTest, Casestudy3_Mon) { + typedef typename TestFixture::ValueType ValueType; + + std::string programFile = STORM_TEST_RESOURCES_DIR "/pdtmc/casestudy3.pm"; + std::string formulaAsString = "P<0.5 [F s=3 ]"; + std::string constantsAsString = ""; + + // Program and formula + storm::prism::Program program = storm::api::parseProgram(programFile); + program = storm::utility::prism::preprocess(program, constantsAsString); + std::vector<std::shared_ptr<const storm::logic::Formula>> formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulaAsString, program)); + std::shared_ptr<storm::models::sparse::Dtmc<storm::RationalFunction>> model = storm::api::buildSparseModel<storm::RationalFunction>(program, formulas)->as<storm::models::sparse::Dtmc<storm::RationalFunction>>(); + + // Model parameters + auto modelParameters = storm::models::sparse::getProbabilityParameters(*model); + auto rewParameters = storm::models::sparse::getRewardParameters(*model); + modelParameters.insert(rewParameters.begin(), rewParameters.end()); + + // Modelcheckers + auto regionCheckerMon = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true), false, false, true); + auto regionChecker = storm::api::initializeParameterLiftingRegionModelChecker<storm::RationalFunction, ValueType>(this->env(), model, storm::api::createTask<storm::RationalFunction>(formulas[0], true), false, false); + + // Start testing + auto allSatRegion=storm::api::parseRegion<storm::RationalFunction>("0.6<=p<=0.9", modelParameters); + auto o = new storm::analysis::OrderExtender<storm::RationalFunction, ValueType>(model, formulas[0]); + auto res = o->extendOrder(nullptr, allSatRegion); + auto order = std::get<0>(res); + auto monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + ASSERT_TRUE(order->getDoneBuilding()); + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), allSatRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + + auto exBothRegion=storm::api::parseRegion<storm::RationalFunction>("0.3<=p<=0.7", modelParameters); + res = o->extendOrder(nullptr, exBothRegion); + order = std::get<0>(res); + monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + ASSERT_TRUE(order->getDoneBuilding()); + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), exBothRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + + auto allVioRegion=storm::api::parseRegion<storm::RationalFunction>("0.1<=p<=0.4", modelParameters); + res = o->extendOrder(nullptr, allVioRegion); + order = std::get<0>(res); + monRes = std::make_shared<storm::analysis::LocalMonotonicityResult<storm::RationalFunctionVariable>>(order->getNumberOfStates()); + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true, order, monRes)); + // Twice, as the monRes will be initialized now + EXPECT_EQ(regionChecker->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true), regionCheckerMon->analyzeRegion(this->env(), allVioRegion, storm::modelchecker::RegionResultHypothesis::Unknown, storm::modelchecker::RegionResult::Unknown, true, order, monRes)); } } #endif