diff --git a/.gitignore b/.gitignore index 750826475..28b6c3dcc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,8 @@ ##Third-Party libs -resources/3rdparty/log4cplus-1.1.0/** -resources/3rdparty/gtest-1.6.0/** -resources/3rdparty/eigen/** -resources/3rdparty/gmm-4.2/** +resources/3rdparty/log4cplus-1.1.0/ +resources/3rdparty/gtest-1.6.0/ +resources/3rdparty/eigen/ +resources/3rdparty/gmm-4.2/ #Visual Studio files *.[Oo]bj *.user @@ -23,10 +23,12 @@ resources/3rdparty/gmm-4.2/** *.lib *.sbr *.sdf +*.tlog +*.lastbuildstate +*.pdb +*.idb *.opensdf *.unsuccessfulbuild -# XCode directories and files -storm/** ipch/ obj/ CMakeFiles/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e1c2d59c..6fc1a8305 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required (VERSION 2.6) +cmake_minimum_required (VERSION 2.8.6) # Set project name project (storm CXX C) @@ -50,18 +50,19 @@ option(USE_POPCNT "Sets whether the popcnt instruction is going to be used." ON) option(USE_BOOST_STATIC_LIBRARIES "Sets whether the Boost libraries should be linked statically." ON) # If the DEBUG option was turned on, we will target a debug version and a release version otherwise -#if (DEBUG) -# set (CMAKE_BUILD_TYPE "DEBUG") -# message(STATUS "Building DEBUG version.") -#else() -# set (CMAKE_BUILD_TYPE "RELEASE") -# message(STATUS "Building RELEASE version.") -#endif() +if (DEBUG) + set (CMAKE_BUILD_TYPE "DEBUG") + message(STATUS "Building DEBUG version.") +else() + set (CMAKE_BUILD_TYPE "RELEASE") + message(STATUS "Building RELEASE version.") +endif() message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") message(STATUS "CMAKE_BUILD_TYPE (ENV): $ENV{CMAKE_BUILD_TYPE}") if(CMAKE_COMPILER_IS_GNUCC) + message(STATUS "Using GCC") # Set standard flags for GCC set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -funroll-loops") set (CMAKE_CXX_FLAGS "-std=c++0x -Wall -pedantic") @@ -74,9 +75,11 @@ if(CMAKE_COMPILER_IS_GNUCC) set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mpopcnt") endif(USE_POPCNT) elseif(MSVC) + message(STATUS "Using MSVC") # required for GMM to compile, ugly error directive in their code add_definitions(/D_SCL_SECURE_NO_DEPRECATE) else(CLANG) + message(STATUS "Using CLANG") # Set standard flags for clang set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -funroll-loops -O4") set (CMAKE_CXX_FLAGS "-std=c++11 -stdlib=libc++ -Wall -Werror -pedantic -Wno-unused-variable") @@ -152,6 +155,24 @@ add_executable(storm-tests ${STORM_TEST_SOURCES} ${STORM_TEST_HEADERS}) target_link_libraries(storm ${Boost_LIBRARIES}) target_link_libraries(storm-tests ${Boost_LIBRARIES}) +set (STORM_USE_COTIRE ON) +if (APPLE) + set(STORM_USE_COTIRE OFF) +endif(APPLE) + +# Print Cotire Usage Status +message (STATUS "Using Cotire: ${STORM_USE_COTIRE}") + +if (STORM_USE_COTIRE) + # Include Cotire for PCH Generation + set (CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/resources/cmake") + include(cotire) + + cotire(storm) + target_link_libraries(storm_unity ${Boost_LIBRARIES}) + #cotire(storm-tests) +endif() + # Add a target to generate API documentation with Doxygen if(DOXYGEN_FOUND) set(CMAKE_DOXYGEN_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/doc") @@ -179,24 +200,33 @@ endif(GTEST_INCLUDE_DIR) if (LOG4CPLUS_INCLUDE_DIR) include_directories(${LOG4CPLUS_INCLUDE_DIR}) target_link_libraries(storm ${LOG4CPLUS_LIBRARIES}) + if (STORM_USE_COTIRE) + target_link_libraries(storm_unity ${LOG4CPLUS_LIBRARIES}) + endif(STORM_USE_COTIRE) target_link_libraries(storm-tests ${LOG4CPLUS_LIBRARIES}) # On Linux, we have to link against librt if (UNIX AND NOT APPLE) - target_link_libraries(storm rt) - target_link_libraries(storm-tests rt) + target_link_libraries(storm rt) + if (STORM_USE_COTIRE) + target_link_libraries(storm_unity rt) + endif(STORM_USE_COTIRE) + target_link_libraries(storm-tests rt) endif(UNIX AND NOT APPLE) endif(LOG4CPLUS_INCLUDE_DIR) if (THREADS_FOUND) include_directories(${THREADS_INCLUDE_DIRS}) - target_link_libraries (storm ${CMAKE_THREAD_LIBS_INIT}) - target_link_libraries (storm-tests ${CMAKE_THREAD_LIBS_INIT}) + target_link_libraries(storm ${CMAKE_THREAD_LIBS_INIT}) + if (STORM_USE_COTIRE) + target_link_libraries(storm_unity ${CMAKE_THREAD_LIBS_INIT}) + endif(STORM_USE_COTIRE) + target_link_libraries(storm-tests ${CMAKE_THREAD_LIBS_INIT}) endif(THREADS_FOUND) # Configure a header file to pass some of the CMake settings to the source code configure_file ( - "${PROJECT_SOURCE_DIR}/storm-config.h.in" - "${PROJECT_BINARY_DIR}/storm-config.h" + "${PROJECT_SOURCE_DIR}/storm-config.h.in" + "${PROJECT_BINARY_DIR}/storm-config.h" ) add_custom_target(memcheck valgrind --leak-check=full --show-reachable=yes ${PROJECT_BINARY_DIR}/storm -v --fix-deadlocks ${PROJECT_SOURCE_DIR}/examples/dtmc/crowds/crowds5_5.tra examples/dtmc/crowds/crowds5_5.lab diff --git a/examples/dtmc/crowds/crowds.pctl b/examples/dtmc/crowds/crowds.pctl new file mode 100644 index 000000000..a7c83e667 --- /dev/null +++ b/examples/dtmc/crowds/crowds.pctl @@ -0,0 +1,3 @@ +P=? [ F "observe0Greater1" ] +P=? [ F "observeIGreater1" ] +P=? [ F "observeOnlyTrueSender" ] diff --git a/examples/dtmc/crowds/crowds.res b/examples/dtmc/crowds/crowds.res new file mode 100644 index 000000000..8e41056f7 --- /dev/null +++ b/examples/dtmc/crowds/crowds.res @@ -0,0 +1,19 @@ +// 5/5 +P=? [ F "observe0Greater1" ] // 0.3328777473921436 +P=? [ F "observeIGreater1" ] // 0.15221847380560186 +P=? [ F "observeOnlyTrueSender" ] // 0.3215351607995943 + +// 10/5 +P=? [ F "observe0Greater1" ] // 0.26345583706046355 +P=? [ F "observeIGreater1" ] // 0.09236405558901994 +P=? [ F "observeOnlyTrueSender" ] // 0.25849872034453947 + +// 15/5 +P=? [ F "observe0Greater1" ] // 0.2408422942249347 +P=? [ F "observeIGreater1" ] // 0.0655686905854717 +P=? [ F "observeOnlyTrueSender" ] // 0.2377298605519743 + +// 20/5 +P=? [ F "observe0Greater1" ] // 0.22967858575985317 +P=? [ F "observeIGreater1" ] // 0.05073192927314383 +P=? [ F "observeOnlyTrueSender" ] // 0.22742031678667812 diff --git a/examples/dtmc/crowds/crowds10_5.pm b/examples/dtmc/crowds/crowds10_5.pm new file mode 100644 index 000000000..752b3f9bb --- /dev/null +++ b/examples/dtmc/crowds/crowds10_5.pm @@ -0,0 +1,80 @@ +dtmc + +// probability of forwarding +const double PF = 0.8; +const double notPF = .2; // must be 1-PF +// probability that a crowd member is bad +const double badC = .167; + // probability that a crowd member is good +const double goodC = 0.833; +// Total number of protocol runs to analyze +const int TotalRuns = 5; +// size of the crowd +const int CrowdSize = 10; + +module crowds + // protocol phase + phase: [0..4] init 0; + + // crowd member good (or bad) + good: bool init false; + + // number of protocol runs + runCount: [0..TotalRuns] init 0; + + // observe_i is the number of times the attacker observed crowd member i + observe0: [0..TotalRuns] init 0; + + observe1: [0..TotalRuns] init 0; + + observe2: [0..TotalRuns] init 0; + + observe3: [0..TotalRuns] init 0; + + observe4: [0..TotalRuns] init 0; + + observe5: [0..TotalRuns] init 0; + + observe6: [0..TotalRuns] init 0; + + observe7: [0..TotalRuns] init 0; + + observe8: [0..TotalRuns] init 0; + + observe9: [0..TotalRuns] init 0; + + // the last seen crowd member + lastSeen: [0..CrowdSize - 1] init 0; + + // get the protocol started + [] phase=0 & runCount (phase'=1) & (runCount'=runCount+1) & (lastSeen'=0); + + // decide whether crowd member is good or bad according to given probabilities + [] phase=1 -> goodC : (phase'=2) & (good'=true) + badC : (phase'=2) & (good'=false); + + // if the current member is a good member, update the last seen index (chosen uniformly) + [] phase=2 & good -> 1/10 : (lastSeen'=0) & (phase'=3) + 1/10 : (lastSeen'=1) & (phase'=3) + 1/10 : (lastSeen'=2) & (phase'=3) + 1/10 : (lastSeen'=3) & (phase'=3) + 1/10 : (lastSeen'=4) & (phase'=3) + 1/10 : (lastSeen'=5) & (phase'=3) + 1/10 : (lastSeen'=6) & (phase'=3) + 1/10 : (lastSeen'=7) & (phase'=3) + 1/10 : (lastSeen'=8) & (phase'=3) + 1/10 : (lastSeen'=9) & (phase'=3); + + // if the current member is a bad member, record the most recently seen index + [] phase=2 & !good & lastSeen=0 & observe0 < TotalRuns -> (observe0'=observe0+1) & (phase'=4); + [] phase=2 & !good & lastSeen=1 & observe1 < TotalRuns -> (observe1'=observe1+1) & (phase'=4); + [] phase=2 & !good & lastSeen=2 & observe2 < TotalRuns -> (observe2'=observe2+1) & (phase'=4); + [] phase=2 & !good & lastSeen=3 & observe3 < TotalRuns -> (observe3'=observe3+1) & (phase'=4); + [] phase=2 & !good & lastSeen=4 & observe4 < TotalRuns -> (observe4'=observe4+1) & (phase'=4); + [] phase=2 & !good & lastSeen=5 & observe5 < TotalRuns -> (observe5'=observe5+1) & (phase'=4); + [] phase=2 & !good & lastSeen=6 & observe6 < TotalRuns -> (observe6'=observe6+1) & (phase'=4); + [] phase=2 & !good & lastSeen=7 & observe7 < TotalRuns -> (observe7'=observe7+1) & (phase'=4); + [] phase=2 & !good & lastSeen=8 & observe8 < TotalRuns -> (observe8'=observe8+1) & (phase'=4); + [] phase=2 & !good & lastSeen=9 & observe9 < TotalRuns -> (observe9'=observe9+1) & (phase'=4); + + // good crowd members forward with probability PF and deliver otherwise + [] phase=3 -> PF : (phase'=1) + notPF : (phase'=4); + + // deliver the message and start over + [] phase=4 -> (phase'=0); + +endmodule + +label "observe0Greater1" = observe0 > 1; +label "observeIGreater1" = observe1 > 1 | observe2 > 1 | observe3 > 1 | observe4 > 1 | observe5 > 1 | observe6 > 1 | observe7 > 1 | observe8 > 1 | observe9 > 1; +label "observeOnlyTrueSender" = observe0 > 1 & observe1 <= 1 & observe2 <= 1 & observe3 <= 1 & observe4 <= 1 & observe5 <= 1 & observe6 <= 1 & observe7 <= 1 & observe8 <= 1 & observe9 <= 1; \ No newline at end of file diff --git a/examples/dtmc/crowds/crowds15_5.pm b/examples/dtmc/crowds/crowds15_5.pm new file mode 100644 index 000000000..aa4921cbf --- /dev/null +++ b/examples/dtmc/crowds/crowds15_5.pm @@ -0,0 +1,95 @@ +dtmc + +// probability of forwarding +const double PF = 0.8; +const double notPF = .2; // must be 1-PF +// probability that a crowd member is bad +const double badC = .167; + // probability that a crowd member is good +const double goodC = 0.833; +// Total number of protocol runs to analyze +const int TotalRuns = 5; +// size of the crowd +const int CrowdSize = 15; + +module crowds + // protocol phase + phase: [0..4] init 0; + + // crowd member good (or bad) + good: bool init false; + + // number of protocol runs + runCount: [0..TotalRuns] init 0; + + // observe_i is the number of times the attacker observed crowd member i + observe0: [0..TotalRuns] init 0; + + observe1: [0..TotalRuns] init 0; + + observe2: [0..TotalRuns] init 0; + + observe3: [0..TotalRuns] init 0; + + observe4: [0..TotalRuns] init 0; + + observe5: [0..TotalRuns] init 0; + + observe6: [0..TotalRuns] init 0; + + observe7: [0..TotalRuns] init 0; + + observe8: [0..TotalRuns] init 0; + + observe9: [0..TotalRuns] init 0; + + observe10: [0..TotalRuns] init 0; + + observe11: [0..TotalRuns] init 0; + + observe12: [0..TotalRuns] init 0; + + observe13: [0..TotalRuns] init 0; + + observe14: [0..TotalRuns] init 0; + + // the last seen crowd member + lastSeen: [0..CrowdSize - 1] init 0; + + // get the protocol started + [] phase=0 & runCount (phase'=1) & (runCount'=runCount+1) & (lastSeen'=0); + + // decide whether crowd member is good or bad according to given probabilities + [] phase=1 -> goodC : (phase'=2) & (good'=true) + badC : (phase'=2) & (good'=false); + + // if the current member is a good member, update the last seen index (chosen uniformly) + [] phase=2 & good -> 1/15 : (lastSeen'=0) & (phase'=3) + 1/15 : (lastSeen'=1) & (phase'=3) + 1/15 : (lastSeen'=2) & (phase'=3) + 1/15 : (lastSeen'=3) & (phase'=3) + 1/15 : (lastSeen'=4) & (phase'=3) + 1/15 : (lastSeen'=5) & (phase'=3) + 1/15 : (lastSeen'=6) & (phase'=3) + 1/15 : (lastSeen'=7) & (phase'=3) + 1/15 : (lastSeen'=8) & (phase'=3) + 1/15 : (lastSeen'=9) & (phase'=3) + 1/15 : (lastSeen'=10) & (phase'=3) + 1/15 : (lastSeen'=11) & (phase'=3) + 1/15 : (lastSeen'=12) & (phase'=3) + 1/15 : (lastSeen'=13) & (phase'=3) + 1/15 : (lastSeen'=14) & (phase'=3); + + // if the current member is a bad member, record the most recently seen index + [] phase=2 & !good & lastSeen=0 & observe0 < TotalRuns -> (observe0'=observe0+1) & (phase'=4); + [] phase=2 & !good & lastSeen=1 & observe1 < TotalRuns -> (observe1'=observe1+1) & (phase'=4); + [] phase=2 & !good & lastSeen=2 & observe2 < TotalRuns -> (observe2'=observe2+1) & (phase'=4); + [] phase=2 & !good & lastSeen=3 & observe3 < TotalRuns -> (observe3'=observe3+1) & (phase'=4); + [] phase=2 & !good & lastSeen=4 & observe4 < TotalRuns -> (observe4'=observe4+1) & (phase'=4); + [] phase=2 & !good & lastSeen=5 & observe5 < TotalRuns -> (observe5'=observe5+1) & (phase'=4); + [] phase=2 & !good & lastSeen=6 & observe6 < TotalRuns -> (observe6'=observe6+1) & (phase'=4); + [] phase=2 & !good & lastSeen=7 & observe7 < TotalRuns -> (observe7'=observe7+1) & (phase'=4); + [] phase=2 & !good & lastSeen=8 & observe8 < TotalRuns -> (observe8'=observe8+1) & (phase'=4); + [] phase=2 & !good & lastSeen=9 & observe9 < TotalRuns -> (observe9'=observe9+1) & (phase'=4); + [] phase=2 & !good & lastSeen=10 & observe10 < TotalRuns -> (observe10'=observe10+1) & (phase'=4); + [] phase=2 & !good & lastSeen=11 & observe11 < TotalRuns -> (observe11'=observe11+1) & (phase'=4); + [] phase=2 & !good & lastSeen=12 & observe12 < TotalRuns -> (observe12'=observe12+1) & (phase'=4); + [] phase=2 & !good & lastSeen=13 & observe13 < TotalRuns -> (observe13'=observe13+1) & (phase'=4); + [] phase=2 & !good & lastSeen=14 & observe14 < TotalRuns -> (observe14'=observe14+1) & (phase'=4); + + // good crowd members forward with probability PF and deliver otherwise + [] phase=3 -> PF : (phase'=1) + notPF : (phase'=4); + + // deliver the message and start over + [] phase=4 -> (phase'=0); + +endmodule + +label "observe0Greater1" = observe0 > 1; +label "observeIGreater1" = observe1 > 1 | observe2 > 1 | observe3 > 1 | observe4 > 1 | observe5 > 1 | observe6 > 1 | observe7 > 1 | observe8 > 1 | observe9 > 1 | observe10 > 1 | observe11 > 1 | observe12 > 1 | observe13 > 1 | observe14 > 1; +label "observeOnlyTrueSender" = observe0 > 1 & observe1 <= 1 & observe2 <= 1 & observe3 <= 1 & observe4 <= 1 & observe5 <= 1 & observe6 <= 1 & observe7 <= 1 & observe8 <= 1 & observe9 <= 1 & observe10 <= 1 & observe11 <= 1 & observe12 <= 1 & observe13 <= 1 & observe14 <= 1; \ No newline at end of file diff --git a/examples/dtmc/crowds/crowds20_5.pm b/examples/dtmc/crowds/crowds20_5.pm new file mode 100644 index 000000000..cdbbb61f6 --- /dev/null +++ b/examples/dtmc/crowds/crowds20_5.pm @@ -0,0 +1,110 @@ +dtmc + +// probability of forwarding +const double PF = 0.8; +const double notPF = .2; // must be 1-PF +// probability that a crowd member is bad +const double badC = .167; + // probability that a crowd member is good +const double goodC = 0.833; +// Total number of protocol runs to analyze +const int TotalRuns = 5; +// size of the crowd +const int CrowdSize = 20; + +module crowds + // protocol phase + phase: [0..4] init 0; + + // crowd member good (or bad) + good: bool init false; + + // number of protocol runs + runCount: [0..TotalRuns] init 0; + + // observe_i is the number of times the attacker observed crowd member i + observe0: [0..TotalRuns] init 0; + + observe1: [0..TotalRuns] init 0; + + observe2: [0..TotalRuns] init 0; + + observe3: [0..TotalRuns] init 0; + + observe4: [0..TotalRuns] init 0; + + observe5: [0..TotalRuns] init 0; + + observe6: [0..TotalRuns] init 0; + + observe7: [0..TotalRuns] init 0; + + observe8: [0..TotalRuns] init 0; + + observe9: [0..TotalRuns] init 0; + + observe10: [0..TotalRuns] init 0; + + observe11: [0..TotalRuns] init 0; + + observe12: [0..TotalRuns] init 0; + + observe13: [0..TotalRuns] init 0; + + observe14: [0..TotalRuns] init 0; + + observe15: [0..TotalRuns] init 0; + + observe16: [0..TotalRuns] init 0; + + observe17: [0..TotalRuns] init 0; + + observe18: [0..TotalRuns] init 0; + + observe19: [0..TotalRuns] init 0; + + // the last seen crowd member + lastSeen: [0..CrowdSize - 1] init 0; + + // get the protocol started + [] phase=0 & runCount (phase'=1) & (runCount'=runCount+1) & (lastSeen'=0); + + // decide whether crowd member is good or bad according to given probabilities + [] phase=1 -> goodC : (phase'=2) & (good'=true) + badC : (phase'=2) & (good'=false); + + // if the current member is a good member, update the last seen index (chosen uniformly) + [] phase=2 & good -> 1/20 : (lastSeen'=0) & (phase'=3) + 1/20 : (lastSeen'=1) & (phase'=3) + 1/20 : (lastSeen'=2) & (phase'=3) + 1/20 : (lastSeen'=3) & (phase'=3) + 1/20 : (lastSeen'=4) & (phase'=3) + 1/20 : (lastSeen'=5) & (phase'=3) + 1/20 : (lastSeen'=6) & (phase'=3) + 1/20 : (lastSeen'=7) & (phase'=3) + 1/20 : (lastSeen'=8) & (phase'=3) + 1/20 : (lastSeen'=9) & (phase'=3) + 1/20 : (lastSeen'=10) & (phase'=3) + 1/20 : (lastSeen'=11) & (phase'=3) + 1/20 : (lastSeen'=12) & (phase'=3) + 1/20 : (lastSeen'=13) & (phase'=3) + 1/20 : (lastSeen'=14) & (phase'=3) + 1/20 : (lastSeen'=15) & (phase'=3) + 1/20 : (lastSeen'=16) & (phase'=3) + 1/20 : (lastSeen'=17) & (phase'=3) + 1/20 : (lastSeen'=18) & (phase'=3) + 1/20 : (lastSeen'=19) & (phase'=3); + + // if the current member is a bad member, record the most recently seen index + [] phase=2 & !good & lastSeen=0 & observe0 < TotalRuns -> (observe0'=observe0+1) & (phase'=4); + [] phase=2 & !good & lastSeen=1 & observe1 < TotalRuns -> (observe1'=observe1+1) & (phase'=4); + [] phase=2 & !good & lastSeen=2 & observe2 < TotalRuns -> (observe2'=observe2+1) & (phase'=4); + [] phase=2 & !good & lastSeen=3 & observe3 < TotalRuns -> (observe3'=observe3+1) & (phase'=4); + [] phase=2 & !good & lastSeen=4 & observe4 < TotalRuns -> (observe4'=observe4+1) & (phase'=4); + [] phase=2 & !good & lastSeen=5 & observe5 < TotalRuns -> (observe5'=observe5+1) & (phase'=4); + [] phase=2 & !good & lastSeen=6 & observe6 < TotalRuns -> (observe6'=observe6+1) & (phase'=4); + [] phase=2 & !good & lastSeen=7 & observe7 < TotalRuns -> (observe7'=observe7+1) & (phase'=4); + [] phase=2 & !good & lastSeen=8 & observe8 < TotalRuns -> (observe8'=observe8+1) & (phase'=4); + [] phase=2 & !good & lastSeen=9 & observe9 < TotalRuns -> (observe9'=observe9+1) & (phase'=4); + [] phase=2 & !good & lastSeen=10 & observe10 < TotalRuns -> (observe10'=observe10+1) & (phase'=4); + [] phase=2 & !good & lastSeen=11 & observe11 < TotalRuns -> (observe11'=observe11+1) & (phase'=4); + [] phase=2 & !good & lastSeen=12 & observe12 < TotalRuns -> (observe12'=observe12+1) & (phase'=4); + [] phase=2 & !good & lastSeen=13 & observe13 < TotalRuns -> (observe13'=observe13+1) & (phase'=4); + [] phase=2 & !good & lastSeen=14 & observe14 < TotalRuns -> (observe14'=observe14+1) & (phase'=4); + [] phase=2 & !good & lastSeen=15 & observe15 < TotalRuns -> (observe15'=observe15+1) & (phase'=4); + [] phase=2 & !good & lastSeen=16 & observe16 < TotalRuns -> (observe16'=observe16+1) & (phase'=4); + [] phase=2 & !good & lastSeen=17 & observe17 < TotalRuns -> (observe17'=observe17+1) & (phase'=4); + [] phase=2 & !good & lastSeen=18 & observe18 < TotalRuns -> (observe18'=observe18+1) & (phase'=4); + [] phase=2 & !good & lastSeen=19 & observe19 < TotalRuns -> (observe19'=observe19+1) & (phase'=4); + + // good crowd members forward with probability PF and deliver otherwise + [] phase=3 -> PF : (phase'=1) + notPF : (phase'=4); + + // deliver the message and start over + [] phase=4 -> (phase'=0); + +endmodule + +label "observe0Greater1" = observe0 > 1; +label "observeIGreater1" = observe1 > 1 | observe2 > 1 | observe3 > 1 | observe4 > 1 | observe5 > 1 | observe6 > 1 | observe7 > 1 | observe8 > 1 | observe9 > 1 | observe10 > 1 | observe11 > 1 | observe12 > 1 | observe13 > 1 | observe14 > 1 | observe15 > 1 | observe16 > 1 | observe17 > 1 | observe18 > 1 | observe19 > 1; +label "observeOnlyTrueSender" = observe0 > 1 & observe1 <= 1 & observe2 <= 1 & observe3 <= 1 & observe4 <= 1 & observe5 <= 1 & observe6 <= 1 & observe7 <= 1 & observe8 <= 1 & observe9 <= 1 & observe10 <= 1 & observe11 <= 1 & observe12 <= 1 & observe13 <= 1 & observe14 <= 1 & observe15 <= 1 & observe16 <= 1 & observe17 <= 1 & observe18 <= 1 & observe19 <= 1; \ No newline at end of file diff --git a/examples/dtmc/crowds/crowds5_5.pm b/examples/dtmc/crowds/crowds5_5.pm new file mode 100644 index 000000000..e1146b586 --- /dev/null +++ b/examples/dtmc/crowds/crowds5_5.pm @@ -0,0 +1,65 @@ +dtmc + +// probability of forwarding +const double PF = 0.8; +const double notPF = .2; // must be 1-PF +// probability that a crowd member is bad +const double badC = .167; + // probability that a crowd member is good +const double goodC = 0.833; +// Total number of protocol runs to analyze +const int TotalRuns = 5; +// size of the crowd +const int CrowdSize = 5; + +module crowds + // protocol phase + phase: [0..4] init 0; + + // crowd member good (or bad) + good: bool init false; + + // number of protocol runs + runCount: [0..TotalRuns] init 0; + + // observe_i is the number of times the attacker observed crowd member i + observe0: [0..TotalRuns] init 0; + + observe1: [0..TotalRuns] init 0; + + observe2: [0..TotalRuns] init 0; + + observe3: [0..TotalRuns] init 0; + + observe4: [0..TotalRuns] init 0; + + // the last seen crowd member + lastSeen: [0..CrowdSize - 1] init 0; + + // get the protocol started + [] phase=0 & runCount (phase'=1) & (runCount'=runCount+1) & (lastSeen'=0); + + // decide whether crowd member is good or bad according to given probabilities + [] phase=1 -> goodC : (phase'=2) & (good'=true) + badC : (phase'=2) & (good'=false); + + // if the current member is a good member, update the last seen index (chosen uniformly) + [] phase=2 & good -> 1/5 : (lastSeen'=0) & (phase'=3) + 1/5 : (lastSeen'=1) & (phase'=3) + 1/5 : (lastSeen'=2) & (phase'=3) + 1/5 : (lastSeen'=3) & (phase'=3) + 1/5 : (lastSeen'=4) & (phase'=3); + + // if the current member is a bad member, record the most recently seen index + [] phase=2 & !good & lastSeen=0 & observe0 < TotalRuns -> (observe0'=observe0+1) & (phase'=4); + [] phase=2 & !good & lastSeen=1 & observe1 < TotalRuns -> (observe1'=observe1+1) & (phase'=4); + [] phase=2 & !good & lastSeen=2 & observe2 < TotalRuns -> (observe2'=observe2+1) & (phase'=4); + [] phase=2 & !good & lastSeen=3 & observe3 < TotalRuns -> (observe3'=observe3+1) & (phase'=4); + [] phase=2 & !good & lastSeen=4 & observe4 < TotalRuns -> (observe4'=observe4+1) & (phase'=4); + + // good crowd members forward with probability PF and deliver otherwise + [] phase=3 -> PF : (phase'=1) + notPF : (phase'=4); + + // deliver the message and start over + [] phase=4 -> (phase'=0); + +endmodule + +label "observe0Greater1" = observe0 > 1; +label "observeIGreater1" = observe1 > 1 | observe2 > 1 | observe3 > 1 | observe4 > 1; +label "observeOnlyTrueSender" = observe0 > 1 & observe1 <= 1 & observe2 <= 1 & observe3 <= 1 & observe4 <= 1; \ No newline at end of file diff --git a/examples/dtmc/die/die.pctl b/examples/dtmc/die/die.pctl new file mode 100644 index 000000000..b0eb88dcd --- /dev/null +++ b/examples/dtmc/die/die.pctl @@ -0,0 +1,4 @@ +P=? [ F "one" ] +P=? [ F "two" ] +P=? [ F "three" ] +R=? [ F "done" ] diff --git a/examples/dtmc/die/die.pm b/examples/dtmc/die/die.pm new file mode 100644 index 000000000..f32a1c04b --- /dev/null +++ b/examples/dtmc/die/die.pm @@ -0,0 +1,31 @@ +dtmc + +module die + + // local state + s : [0..7] init 0; + // value of the die + d : [0..6] init 0; + + [] s=0 -> 0.5 : (s'=1) + 0.5 : (s'=2); + [] s=1 -> 0.5 : (s'=3) + 0.5 : (s'=4); + [] s=2 -> 0.5 : (s'=5) + 0.5 : (s'=6); + [] s=3 -> 0.5 : (s'=1) + 0.5 : (s'=7) & (d'=1); + [] s=4 -> 0.5 : (s'=7) & (d'=2) + 0.5 : (s'=7) & (d'=3); + [] s=5 -> 0.5 : (s'=7) & (d'=4) + 0.5 : (s'=7) & (d'=5); + [] s=6 -> 0.5 : (s'=2) + 0.5 : (s'=7) & (d'=6); + [] s=7 -> (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; \ No newline at end of file diff --git a/examples/dtmc/die/die.res b/examples/dtmc/die/die.res new file mode 100644 index 000000000..003af5dbd --- /dev/null +++ b/examples/dtmc/die/die.res @@ -0,0 +1,4 @@ +P=? [ F "one" ] // 0.16666650772094727 +P=? [ F "two" ] // 0.16666650772094727 +P=? [ F "three" ] // 0.16666650772094727 +R=? [ F "done" ] // 3.6666650772094727 diff --git a/examples/dtmc/synchronous_leader/leader.pctl b/examples/dtmc/synchronous_leader/leader.pctl new file mode 100644 index 000000000..e8b1899e6 --- /dev/null +++ b/examples/dtmc/synchronous_leader/leader.pctl @@ -0,0 +1,3 @@ +P=? [ F "elected" ] +P=? [ F<=(4*(N+1)) "elected" ] +R=? [ F "elected" ] diff --git a/examples/dtmc/synchronous_leader/leader.res b/examples/dtmc/synchronous_leader/leader.res new file mode 100644 index 000000000..26451a894 --- /dev/null +++ b/examples/dtmc/synchronous_leader/leader.res @@ -0,0 +1,14 @@ +// 3/5 +P=? [ F "elected" ] // 1.0 +P=? [ F<=(4*(N+1)) "elected" ] // 0.999997440000001 +R=? [ F "elected" ] // 1.0416666623999995 + +// 4/8 +P=? [ F "elected" ] // 1.0 +P=? [ F<=(4*(N+1)) "elected" ] // 0.9999965911265463 +R=? [ F "elected" ] // 1.0448979526072435 + +// 5/8 +P=? [ F "elected" ] // 1.0 +P=? [ F<=(4*(N+1)) "elected" ] // 0.9999999097195733 +R=? [ F "elected" ] // 1.0176397499602707 \ No newline at end of file diff --git a/examples/dtmc/synchronous_leader/leader3_5.pm b/examples/dtmc/synchronous_leader/leader3_5.pm new file mode 100644 index 000000000..0a45ad217 --- /dev/null +++ b/examples/dtmc/synchronous_leader/leader3_5.pm @@ -0,0 +1,85 @@ +// synchronous leader election protocol (itai & Rodeh) +// dxp/gxn 25/01/01 + +dtmc + +// CONSTANTS +const N = 3; // number of processes +const K = 5; // range of probabilistic choice + +// counter module used to count the number of processes that have been read +// and to know when a process has decided +module counter + + // counter (c=i means process j reading process (i-1)+j next) + c : [1..N-1]; + + // reading + [read] c (c'=c+1); + // finished reading + [read] c=N-1 -> (c'=c); + //decide + [done] u1|u2|u3 -> (c'=c); + // pick again reset counter + [retry] !(u1|u2|u3) -> (c'=1); + // loop (when finished to avoid deadlocks) + [loop] s1=3 -> (c'=c); + +endmodule + +// processes form a ring and suppose: +// process 1 reads process 2 +// process 2 reads process 3 +// process 3 reads process 1 +module process1 + + // local state + s1 : [0..3]; + // s1=0 make random choice + // s1=1 reading + // s1=2 deciding + // s1=3 finished + + // has a unique id so far (initially true) + u1 : bool; + + // value to be sent to next process in the ring (initially sets this to its own value) + v1 : [0..K-1]; + + // random choice + p1 : [0..K-1]; + + // pick value + [pick] s1=0 -> 1/K : (s1'=1) & (p1'=0) & (v1'=0) & (u1'=true) + + 1/K : (s1'=1) & (p1'=1) & (v1'=1) & (u1'=true) + + 1/K : (s1'=1) & (p1'=2) & (v1'=2) & (u1'=true) + + 1/K : (s1'=1) & (p1'=3) & (v1'=3) & (u1'=true) + + 1/K : (s1'=1) & (p1'=4) & (v1'=4) & (u1'=true); + // read + [read] s1=1 & u1 & c (u1'=(p1!=v2)) & (v1'=v2); + [read] s1=1 & !u1 & c (u1'=false) & (v1'=v2) & (p1'=0); + // read and move to decide + [read] s1=1 & u1 & c=N-1 -> (s1'=2) & (u1'=(p1!=v2)) & (v1'=0) & (p1'=0); + [read] s1=1 & !u1 & c=N-1 -> (s1'=2) & (u1'=false) & (v1'=0); + // deciding + // done + [done] s1=2 -> (s1'=3) & (u1'=false) & (v1'=0) & (p1'=0); + //retry + [retry] s1=2 -> (s1'=0) & (u1'=false) & (v1'=0) & (p1'=0); + // loop (when finished to avoid deadlocks) + [loop] s1=3 -> (s1'=3); + +endmodule + +// construct remaining processes through renaming +module process2 = process1 [ s1=s2,p1=p2,v1=v2,u1=u2,v2=v3 ] endmodule +module process3 = process1 [ s1=s3,p1=p3,v1=v3,u1=u3,v2=v1 ] endmodule + +// expected number of rounds +rewards "num_rounds" + [pick] true : 1; +endrewards + +// labels +label "elected" = s1=3&s2=3&s3=3; + diff --git a/examples/dtmc/synchronous_leader/leader4_8.pm b/examples/dtmc/synchronous_leader/leader4_8.pm new file mode 100644 index 000000000..fc8f167a0 --- /dev/null +++ b/examples/dtmc/synchronous_leader/leader4_8.pm @@ -0,0 +1,89 @@ +// synchronous leader election protocol (itai & Rodeh) +// dxp/gxn 25/01/01 + +dtmc + +// CONSTANTS +const N = 4; // number of processes +const K = 8; // range of probabilistic choice + +// counter module used to count the number of processes that have been read +// and to know when a process has decided +module counter + + // counter (c=i means process j reading process (i-1)+j next) + c : [1..N-1]; + + // reading + [read] c (c'=c+1); + // finished reading + [read] c=N-1 -> (c'=c); + //decide + [done] u1|u2|u3|u4 -> (c'=c); + // pick again reset counter + [retry] !(u1|u2|u3|u4) -> (c'=1); + // loop (when finished to avoid deadlocks) + [loop] s1=3 -> (c'=c); + +endmodule + +// processes form a ring and suppose: +// process 1 reads process 2 +// process 2 reads process 3 +// process 3 reads process 1 +module process1 + + // local state + s1 : [0..3]; + // s1=0 make random choice + // s1=1 reading + // s1=2 deciding + // s1=3 finished + + // has a unique id so far (initially true) + u1 : bool; + + // value to be sent to next process in the ring (initially sets this to its own value) + v1 : [0..K-1]; + + // random choice + p1 : [0..K-1]; + + // pick value + [pick] s1=0 -> 1/K : (s1'=1) & (p1'=0) & (v1'=0) & (u1'=true) + + 1/K : (s1'=1) & (p1'=1) & (v1'=1) & (u1'=true) + + 1/K : (s1'=1) & (p1'=2) & (v1'=2) & (u1'=true) + + 1/K : (s1'=1) & (p1'=3) & (v1'=3) & (u1'=true) + + 1/K : (s1'=1) & (p1'=4) & (v1'=4) & (u1'=true) + + 1/K : (s1'=1) & (p1'=5) & (v1'=5) & (u1'=true) + + 1/K : (s1'=1) & (p1'=6) & (v1'=6) & (u1'=true) + + 1/K : (s1'=1) & (p1'=7) & (v1'=7) & (u1'=true); + // read + [read] s1=1 & u1 & c (u1'=(p1!=v2)) & (v1'=v2); + [read] s1=1 & !u1 & c (u1'=false) & (v1'=v2) & (p1'=0); + // read and move to decide + [read] s1=1 & u1 & c=N-1 -> (s1'=2) & (u1'=(p1!=v2)) & (v1'=0) & (p1'=0); + [read] s1=1 & !u1 & c=N-1 -> (s1'=2) & (u1'=false) & (v1'=0); + // deciding + // done + [done] s1=2 -> (s1'=3) & (u1'=false) & (v1'=0) & (p1'=0); + //retry + [retry] s1=2 -> (s1'=0) & (u1'=false) & (v1'=0) & (p1'=0); + // loop (when finished to avoid deadlocks) + [loop] s1=3 -> (s1'=3); + +endmodule + +// construct remaining processes through renaming +module process2 = process1 [ s1=s2,p1=p2,v1=v2,u1=u2,v2=v3 ] endmodule +module process3 = process1 [ s1=s3,p1=p3,v1=v3,u1=u3,v2=v4 ] endmodule +module process4 = process1 [ s1=s4,p1=p4,v1=v4,u1=u4,v2=v1 ] endmodule + +// expected number of rounds +rewards "num_rounds" + [pick] true : 1; +endrewards + +// labels +label "elected" = s1=3&s2=3&s3=3&s4=3; + diff --git a/examples/dtmc/synchronous_leader/leader5_8.pm b/examples/dtmc/synchronous_leader/leader5_8.pm new file mode 100644 index 000000000..72139fda9 --- /dev/null +++ b/examples/dtmc/synchronous_leader/leader5_8.pm @@ -0,0 +1,90 @@ +// synchronous leader election protocol (itai & Rodeh) +// dxp/gxn 25/01/01 + +dtmc + +// CONSTANTS +const N = 5; // number of processes +const K = 8; // range of probabilistic choice + +// counter module used to count the number of processes that have been read +// and to know when a process has decided +module counter + + // counter (c=i means process j reading process (i-1)+j next) + c : [1..N-1]; + + // reading + [read] c (c'=c+1); + // finished reading + [read] c=N-1 -> (c'=c); + //decide + [done] u1|u2|u3|u4|u5 -> (c'=c); + // pick again reset counter + [retry] !(u1|u2|u3|u4|u5) -> (c'=1); + // loop (when finished to avoid deadlocks) + [loop] s1=3 -> (c'=c); + +endmodule + +// processes form a ring and suppose: +// process 1 reads process 2 +// process 2 reads process 3 +// process 3 reads process 1 +module process1 + + // local state + s1 : [0..3]; + // s1=0 make random choice + // s1=1 reading + // s1=2 deciding + // s1=3 finished + + // has a unique id so far (initially true) + u1 : bool; + + // value to be sent to next process in the ring (initially sets this to its own value) + v1 : [0..K-1]; + + // random choice + p1 : [0..K-1]; + + // pick value + [pick] s1=0 -> 1/K : (s1'=1) & (p1'=0) & (v1'=0) & (u1'=true) + + 1/K : (s1'=1) & (p1'=1) & (v1'=1) & (u1'=true) + + 1/K : (s1'=1) & (p1'=2) & (v1'=2) & (u1'=true) + + 1/K : (s1'=1) & (p1'=3) & (v1'=3) & (u1'=true) + + 1/K : (s1'=1) & (p1'=4) & (v1'=4) & (u1'=true) + + 1/K : (s1'=1) & (p1'=5) & (v1'=5) & (u1'=true) + + 1/K : (s1'=1) & (p1'=6) & (v1'=6) & (u1'=true) + + 1/K : (s1'=1) & (p1'=7) & (v1'=7) & (u1'=true); + // read + [read] s1=1 & u1 & c (u1'=(p1!=v2)) & (v1'=v2); + [read] s1=1 & !u1 & c (u1'=false) & (v1'=v2) & (p1'=0); + // read and move to decide + [read] s1=1 & u1 & c=N-1 -> (s1'=2) & (u1'=(p1!=v2)) & (v1'=0) & (p1'=0); + [read] s1=1 & !u1 & c=N-1 -> (s1'=2) & (u1'=false) & (v1'=0); + // deciding + // done + [done] s1=2 -> (s1'=3) & (u1'=false) & (v1'=0) & (p1'=0); + //retry + [retry] s1=2 -> (s1'=0) & (u1'=false) & (v1'=0) & (p1'=0); + // loop (when finished to avoid deadlocks) + [loop] s1=3 -> (s1'=3); + +endmodule + +// construct remaining processes through renaming +module process2 = process1 [ s1=s2,p1=p2,v1=v2,u1=u2,v2=v3 ] endmodule +module process3 = process1 [ s1=s3,p1=p3,v1=v3,u1=u3,v2=v4 ] endmodule +module process4 = process1 [ s1=s4,p1=p4,v1=v4,u1=u4,v2=v5 ] endmodule +module process5 = process1 [ s1=s5,p1=p5,v1=v5,u1=u5,v2=v1 ] endmodule + +// expected number of rounds +rewards "num_rounds" + [pick] true : 1; +endrewards + +// labels +label "elected" = s1=3&s2=3&s3=3&s4=3&s5=3; + diff --git a/examples/dtmc/synchronous_leader/leader6_8.pm b/examples/dtmc/synchronous_leader/leader6_8.pm new file mode 100644 index 000000000..283042ad5 --- /dev/null +++ b/examples/dtmc/synchronous_leader/leader6_8.pm @@ -0,0 +1,91 @@ +// synchronous leader election protocol (itai & Rodeh) +// dxp/gxn 25/01/01 + +dtmc + +// CONSTANTS +const N = 6; // number of processes +const K = 8; // range of probabilistic choice + +// counter module used to count the number of processes that have been read +// and to know when a process has decided +module counter + + // counter (c=i means process j reading process (i-1)+j next) + c : [1..N-1]; + + // reading + [read] c (c'=c+1); + // finished reading + [read] c=N-1 -> (c'=c); + //decide + [done] u1|u2|u3|u4|u5|u6 -> (c'=c); + // pick again reset counter + [retry] !(u1|u2|u3|u4|u5|u6) -> (c'=1); + // loop (when finished to avoid deadlocks) + [loop] s1=3 -> (c'=c); + +endmodule + +// processes form a ring and suppose: +// process 1 reads process 2 +// process 2 reads process 3 +// process 3 reads process 1 +module process1 + + // local state + s1 : [0..3]; + // s1=0 make random choice + // s1=1 reading + // s1=2 deciding + // s1=3 finished + + // has a unique id so far (initially true) + u1 : bool; + + // value to be sent to next process in the ring (initially sets this to its own value) + v1 : [0..K-1]; + + // random choice + p1 : [0..K-1]; + + // pick value + [pick] s1=0 -> 1/K : (s1'=1) & (p1'=0) & (v1'=0) & (u1'=true) + + 1/K : (s1'=1) & (p1'=1) & (v1'=1) & (u1'=true) + + 1/K : (s1'=1) & (p1'=2) & (v1'=2) & (u1'=true) + + 1/K : (s1'=1) & (p1'=3) & (v1'=3) & (u1'=true) + + 1/K : (s1'=1) & (p1'=4) & (v1'=4) & (u1'=true) + + 1/K : (s1'=1) & (p1'=5) & (v1'=5) & (u1'=true) + + 1/K : (s1'=1) & (p1'=6) & (v1'=6) & (u1'=true) + + 1/K : (s1'=1) & (p1'=7) & (v1'=7) & (u1'=true); + // read + [read] s1=1 & u1 & c (u1'=(p1!=v2)) & (v1'=v2); + [read] s1=1 & !u1 & c (u1'=false) & (v1'=v2) & (p1'=0); + // read and move to decide + [read] s1=1 & u1 & c=N-1 -> (s1'=2) & (u1'=(p1!=v2)) & (v1'=0) & (p1'=0); + [read] s1=1 & !u1 & c=N-1 -> (s1'=2) & (u1'=false) & (v1'=0); + // deciding + // done + [done] s1=2 -> (s1'=3) & (u1'=false) & (v1'=0) & (p1'=0); + //retry + [retry] s1=2 -> (s1'=0) & (u1'=false) & (v1'=0) & (p1'=0); + // loop (when finished to avoid deadlocks) + [loop] s1=3 -> (s1'=3); + +endmodule + +// construct remaining processes through renaming +module process2 = process1 [ s1=s2,p1=p2,v1=v2,u1=u2,v2=v3 ] endmodule +module process3 = process1 [ s1=s3,p1=p3,v1=v3,u1=u3,v2=v4 ] endmodule +module process4 = process1 [ s1=s4,p1=p4,v1=v4,u1=u4,v2=v5 ] endmodule +module process5 = process1 [ s1=s5,p1=p5,v1=v5,u1=u5,v2=v6 ] endmodule +module process6 = process1 [ s1=s6,p1=p6,v1=v6,u1=u6,v2=v1 ] endmodule + +// expected number of rounds +rewards "num_rounds" + [pick] true : 1; +endrewards + +// labels +label "elected" = s1=3&s2=3&s3=3&s4=3&s5=3&s6=3; + diff --git a/examples/mdp/asynchronous_leader/leader.pctl b/examples/mdp/asynchronous_leader/leader.pctl new file mode 100644 index 000000000..49e4dbeaa --- /dev/null +++ b/examples/mdp/asynchronous_leader/leader.pctl @@ -0,0 +1,8 @@ +Pmin=? [ F "elected" ] + +const int K = 25; +Pmin=? [ F<=K "elected" ] +Pmax=? [ F<=K "elected" ] + +Rmin=? [ F "elected" ] +Rmax=? [ F "elected" ] diff --git a/examples/mdp/asynchronous_leader/leader.res b/examples/mdp/asynchronous_leader/leader.res new file mode 100644 index 000000000..447403e11 --- /dev/null +++ b/examples/mdp/asynchronous_leader/leader.res @@ -0,0 +1,49 @@ +// 3 +Pmin=? [ F "elected" ] // 1.0 + +const int K = 25; +Pmin=? [ F<=K "elected" ] // 0.5625 +Pmax=? [ F<=K "elected" ] // 0.5625 + +Rmin=? [ F "elected" ] // 3.3333212586585432 +Rmax=? [ F "elected" ] // 3.3333206579554826 + +// 4 +Pmin=? [ F "elected" ] // 1.0 + +const int K = 25; +Pmin=? [ F<=K "elected" ] // 0.0625 +Pmax=? [ F<=K "elected" ] // 0.0625 + +Rmin=? [ F "elected" ] // 4.2856896106114934 +Rmax=? [ F "elected" ] // 4.28569043544414 + +// 5 +Pmin=? [ F "elected" ] // 1.0 + +const int K = 25; +Pmin=? [ F<=K "elected" ] // 0.0 +Pmax=? [ F<=K "elected" ] // 0.0 + +Rmin=? [ F "elected" ] // 5.034886386278894 +Rmax=? [ F "elected" ] // 5.034881859133309 + +// 6 +Pmin=? [ F "elected" ] // 1.0 + +const int K = 25; +Pmin=? [ F<=K "elected" ] // 0.0 +Pmax=? [ F<=K "elected" ] // 0.0 + +Rmin=? [ F "elected" ] // 5.649720120334257 +Rmax=? [ F "elected" ] // 5.649719114527437 + +// 7 +Pmin=? [ F "elected" ] // 1.0 + +const int K = 25; +Pmin=? [ F<=K "elected" ] // 0.0 +Pmax=? [ F<=K "elected" ] // 0.0 + +Rmin=? [ F "elected" ] // 6.172433512043686 +Rmax=? [ F "elected" ] // 6.172434400085756 \ No newline at end of file diff --git a/examples/mdp/asynchronous_leader/leader3.nm b/examples/mdp/asynchronous_leader/leader3.nm new file mode 100644 index 000000000..25a3ec89d --- /dev/null +++ b/examples/mdp/asynchronous_leader/leader3.nm @@ -0,0 +1,96 @@ +// asynchronous leader election +// 4 processes +// gxn/dxp 29/01/01 + +mdp + +const N= 3; // number of processes + +//---------------------------------------------------------------------------------------------------------------------------- +module process1 + + // COUNTER + c1 : [0..3-1]; + + // STATES + s1 : [0..4]; + // 0 make choice + // 1 have not received neighbours choice + // 2 active + // 3 inactive + // 4 leader + + // PREFERENCE + p1 : [0..1]; + + // VARIABLES FOR SENDING AND RECEIVING + receive1 : [0..2]; + // not received anything + // received choice + // received counter + sent1 : [0..2]; + // not send anything + // sent choice + // sent counter + + // pick value + [] (s1=0) -> 0.5 : (s1'=1) & (p1'=0) + 0.5 : (s1'=1) & (p1'=1); + + // send preference + [p12] (s1=1) & (sent1=0) -> (sent1'=1); + // receive preference + // stay active + [p31] (s1=1) & (receive1=0) & !( (p1=0) & (p3=1) ) -> (s1'=2) & (receive1'=1); + // become inactive + [p31] (s1=1) & (receive1=0) & (p1=0) & (p3=1) -> (s1'=3) & (receive1'=1); + + // send preference (can now reset preference) + [p12] (s1=2) & (sent1=0) -> (sent1'=1) & (p1'=0); + // send counter (already sent preference) + // not received counter yet + [c12] (s1=2) & (sent1=1) & (receive1=1) -> (sent1'=2); + // received counter (pick again) + [c12] (s1=2) & (sent1=1) & (receive1=2) -> (s1'=0) & (p1'=0) & (c1'=0) & (sent1'=0) & (receive1'=0); + + // receive counter and not sent yet (note in this case do not pass it on as will send own counter) + [c31] (s1=2) & (receive1=1) & (sent1<2) -> (receive1'=2); + // receive counter and sent counter + // only active process (decide) + [c31] (s1=2) & (receive1=1) & (sent1=2) & (c3=N-1) -> (s1'=4) & (p1'=0) & (c1'=0) & (sent1'=0) & (receive1'=0); + // other active process (pick again) + [c31] (s1=2) & (receive1=1) & (sent1=2) & (c3 (s1'=0) & (p1'=0) & (c1'=0) & (sent1'=0) & (receive1'=0); + + // send preference (must have received preference) and can now reset + [p12] (s1=3) & (receive1>0) & (sent1=0) -> (sent1'=1) & (p1'=0); + // send counter (must have received counter first) and can now reset + [c12] (s1=3) & (receive1=2) & (sent1=1) -> (s1'=3) & (p1'=0) & (c1'=0) & (sent1'=0) & (receive1'=0); + + // receive preference + [p31] (s1=3) & (receive1=0) -> (p1'=p3) & (receive1'=1); + // receive counter + [c31] (s1=3) & (receive1=1) & (c3 (c1'=c3+1) & (receive1'=2); + + // done + [done] (s1=4) -> (s1'=s1); + // add loop for processes who are inactive + [done] (s1=3) -> (s1'=s1); + +endmodule + +//---------------------------------------------------------------------------------------------------------------------------- + +// construct further stations through renaming +module process2=process1[s1=s2,p1=p2,c1=c2,sent1=sent2,receive1=receive2,p12=p23,p31=p12,c12=c23,c31=c12,p3=p1,c3=c1] endmodule +module process3=process1[s1=s3,p1=p3,c1=c3,sent1=sent3,receive1=receive3,p12=p31,p31=p23,c12=c31,c31=c23,p3=p2,c3=c2] endmodule + +//---------------------------------------------------------------------------------------------------------------------------- + +// reward - expected number of rounds (equals the number of times a process receives a counter) +rewards + [c12] true : 1; +endrewards + +//---------------------------------------------------------------------------------------------------------------------------- +formula leaders = (s1=4?1:0)+(s2=4?1:0)+(s3=4?1:0); +label "elected" = s1=4|s2=4|s3=4; + diff --git a/examples/mdp/asynchronous_leader/leader4.nm b/examples/mdp/asynchronous_leader/leader4.nm new file mode 100644 index 000000000..81ade2ea7 --- /dev/null +++ b/examples/mdp/asynchronous_leader/leader4.nm @@ -0,0 +1,97 @@ +// asynchronous leader election +// 4 processes +// gxn/dxp 29/01/01 + +mdp + +const N= 4; // number of processes + +//---------------------------------------------------------------------------------------------------------------------------- +module process1 + + // COUNTER + c1 : [0..4-1]; + + // STATES + s1 : [0..4]; + // 0 make choice + // 1 have not received neighbours choice + // 2 active + // 3 inactive + // 4 leader + + // PREFERENCE + p1 : [0..1]; + + // VARIABLES FOR SENDING AND RECEIVING + receive1 : [0..2]; + // not received anything + // received choice + // received counter + sent1 : [0..2]; + // not send anything + // sent choice + // sent counter + + // pick value + [] (s1=0) -> 0.5 : (s1'=1) & (p1'=0) + 0.5 : (s1'=1) & (p1'=1); + + // send preference + [p12] (s1=1) & (sent1=0) -> (sent1'=1); + // receive preference + // stay active + [p41] (s1=1) & (receive1=0) & !( (p1=0) & (p4=1) ) -> (s1'=2) & (receive1'=1); + // become inactive + [p41] (s1=1) & (receive1=0) & (p1=0) & (p4=1) -> (s1'=3) & (receive1'=1); + + // send preference (can now reset preference) + [p12] (s1=2) & (sent1=0) -> (sent1'=1) & (p1'=0); + // send counter (already sent preference) + // not received counter yet + [c12] (s1=2) & (sent1=1) & (receive1=1) -> (sent1'=2); + // received counter (pick again) + [c12] (s1=2) & (sent1=1) & (receive1=2) -> (s1'=0) & (p1'=0) & (c1'=0) & (sent1'=0) & (receive1'=0); + + // receive counter and not sent yet (note in this case do not pass it on as will send own counter) + [c41] (s1=2) & (receive1=1) & (sent1<2) -> (receive1'=2); + // receive counter and sent counter + // only active process (decide) + [c41] (s1=2) & (receive1=1) & (sent1=2) & (c4=N-1) -> (s1'=4) & (p1'=0) & (c1'=0) & (sent1'=0) & (receive1'=0); + // other active process (pick again) + [c41] (s1=2) & (receive1=1) & (sent1=2) & (c4 (s1'=0) & (p1'=0) & (c1'=0) & (sent1'=0) & (receive1'=0); + + // send preference (must have received preference) and can now reset + [p12] (s1=3) & (receive1>0) & (sent1=0) -> (sent1'=1) & (p1'=0); + // send counter (must have received counter first) and can now reset + [c12] (s1=3) & (receive1=2) & (sent1=1) -> (s1'=3) & (p1'=0) & (c1'=0) & (sent1'=0) & (receive1'=0); + + // receive preference + [p41] (s1=3) & (receive1=0) -> (p1'=p4) & (receive1'=1); + // receive counter + [c41] (s1=3) & (receive1=1) & (c4 (c1'=c4+1) & (receive1'=2); + + // done + [done] (s1=4) -> (s1'=s1); + // add loop for processes who are inactive + [done] (s1=3) -> (s1'=s1); + +endmodule + +//---------------------------------------------------------------------------------------------------------------------------- + +// construct further stations through renaming +module process2=process1[s1=s2,p1=p2,c1=c2,sent1=sent2,receive1=receive2,p12=p23,p41=p12,c12=c23,c41=c12,p4=p1,c4=c1] endmodule +module process3=process1[s1=s3,p1=p3,c1=c3,sent1=sent3,receive1=receive3,p12=p34,p41=p23,c12=c34,c41=c23,p4=p2,c4=c2] endmodule +module process4=process1[s1=s4,p1=p4,c1=c4,sent1=sent4,receive1=receive4,p12=p41,p41=p34,c12=c41,c41=c34,p4=p3,c4=c3] endmodule + +//---------------------------------------------------------------------------------------------------------------------------- + +// reward - expected number of rounds (equals the number of times a process receives a counter) +rewards + [c12] true : 1; +endrewards + +//---------------------------------------------------------------------------------------------------------------------------- +formula leaders = (s1=4?1:0)+(s2=4?1:0)+(s3=4?1:0)+(s4=4?1:0); +label "elected" = s1=4|s2=4|s3=4|s4=4; + diff --git a/examples/mdp/asynchronous_leader/leader5.nm b/examples/mdp/asynchronous_leader/leader5.nm new file mode 100644 index 000000000..558bab560 --- /dev/null +++ b/examples/mdp/asynchronous_leader/leader5.nm @@ -0,0 +1,98 @@ +// asynchronous leader election +// 4 processes +// gxn/dxp 29/01/01 + +mdp + +const N= 5; // number of processes + +//---------------------------------------------------------------------------------------------------------------------------- +module process1 + + // COUNTER + c1 : [0..5-1]; + + // STATES + s1 : [0..4]; + // 0 make choice + // 1 have not received neighbours choice + // 2 active + // 3 inactive + // 4 leader + + // PREFERENCE + p1 : [0..1]; + + // VARIABLES FOR SENDING AND RECEIVING + receive1 : [0..2]; + // not received anything + // received choice + // received counter + sent1 : [0..2]; + // not send anything + // sent choice + // sent counter + + // pick value + [] (s1=0) -> 0.5 : (s1'=1) & (p1'=0) + 0.5 : (s1'=1) & (p1'=1); + + // send preference + [p12] (s1=1) & (sent1=0) -> (sent1'=1); + // receive preference + // stay active + [p51] (s1=1) & (receive1=0) & !( (p1=0) & (p5=1) ) -> (s1'=2) & (receive1'=1); + // become inactive + [p51] (s1=1) & (receive1=0) & (p1=0) & (p5=1) -> (s1'=3) & (receive1'=1); + + // send preference (can now reset preference) + [p12] (s1=2) & (sent1=0) -> (sent1'=1) & (p1'=0); + // send counter (already sent preference) + // not received counter yet + [c12] (s1=2) & (sent1=1) & (receive1=1) -> (sent1'=2); + // received counter (pick again) + [c12] (s1=2) & (sent1=1) & (receive1=2) -> (s1'=0) & (p1'=0) & (c1'=0) & (sent1'=0) & (receive1'=0); + + // receive counter and not sent yet (note in this case do not pass it on as will send own counter) + [c51] (s1=2) & (receive1=1) & (sent1<2) -> (receive1'=2); + // receive counter and sent counter + // only active process (decide) + [c51] (s1=2) & (receive1=1) & (sent1=2) & (c5=N-1) -> (s1'=4) & (p1'=0) & (c1'=0) & (sent1'=0) & (receive1'=0); + // other active process (pick again) + [c51] (s1=2) & (receive1=1) & (sent1=2) & (c5 (s1'=0) & (p1'=0) & (c1'=0) & (sent1'=0) & (receive1'=0); + + // send preference (must have received preference) and can now reset + [p12] (s1=3) & (receive1>0) & (sent1=0) -> (sent1'=1) & (p1'=0); + // send counter (must have received counter first) and can now reset + [c12] (s1=3) & (receive1=2) & (sent1=1) -> (s1'=3) & (p1'=0) & (c1'=0) & (sent1'=0) & (receive1'=0); + + // receive preference + [p51] (s1=3) & (receive1=0) -> (p1'=p5) & (receive1'=1); + // receive counter + [c51] (s1=3) & (receive1=1) & (c5 (c1'=c5+1) & (receive1'=2); + + // done + [done] (s1=4) -> (s1'=s1); + // add loop for processes who are inactive + [done] (s1=3) -> (s1'=s1); + +endmodule + +//---------------------------------------------------------------------------------------------------------------------------- + +// construct further stations through renaming +module process2=process1[s1=s2,p1=p2,c1=c2,sent1=sent2,receive1=receive2,p12=p23,p51=p12,c12=c23,c51=c12,p5=p1,c5=c1] endmodule +module process3=process1[s1=s3,p1=p3,c1=c3,sent1=sent3,receive1=receive3,p12=p34,p51=p23,c12=c34,c51=c23,p5=p2,c5=c2] endmodule +module process4=process1[s1=s4,p1=p4,c1=c4,sent1=sent4,receive1=receive4,p12=p45,p51=p34,c12=c45,c51=c34,p5=p3,c5=c3] endmodule +module process5=process1[s1=s5,p1=p5,c1=c5,sent1=sent5,receive1=receive5,p12=p51,p51=p45,c12=c51,c51=c45,p5=p4,c5=c4] endmodule + +//---------------------------------------------------------------------------------------------------------------------------- + +// reward - expected number of rounds (equals the number of times a process receives a counter) +rewards + [c12] true : 1; +endrewards + +//---------------------------------------------------------------------------------------------------------------------------- +formula leaders = (s1=4?1:0)+(s2=4?1:0)+(s3=4?1:0)+(s4=4?1:0)+(s5=4?1:0); +label "elected" = s1=4|s2=4|s3=4|s4=4|s5=4; + diff --git a/examples/mdp/asynchronous_leader/leader6.nm b/examples/mdp/asynchronous_leader/leader6.nm new file mode 100644 index 000000000..84b3283ad --- /dev/null +++ b/examples/mdp/asynchronous_leader/leader6.nm @@ -0,0 +1,99 @@ +// asynchronous leader election +// 4 processes +// gxn/dxp 29/01/01 + +mdp + +const N= 6; // number of processes + +//---------------------------------------------------------------------------------------------------------------------------- +module process1 + + // COUNTER + c1 : [0..6-1]; + + // STATES + s1 : [0..4]; + // 0 make choice + // 1 have not received neighbours choice + // 2 active + // 3 inactive + // 4 leader + + // PREFERENCE + p1 : [0..1]; + + // VARIABLES FOR SENDING AND RECEIVING + receive1 : [0..2]; + // not received anything + // received choice + // received counter + sent1 : [0..2]; + // not send anything + // sent choice + // sent counter + + // pick value + [] (s1=0) -> 0.5 : (s1'=1) & (p1'=0) + 0.5 : (s1'=1) & (p1'=1); + + // send preference + [p12] (s1=1) & (sent1=0) -> (sent1'=1); + // receive preference + // stay active + [p61] (s1=1) & (receive1=0) & !( (p1=0) & (p6=1) ) -> (s1'=2) & (receive1'=1); + // become inactive + [p61] (s1=1) & (receive1=0) & (p1=0) & (p6=1) -> (s1'=3) & (receive1'=1); + + // send preference (can now reset preference) + [p12] (s1=2) & (sent1=0) -> (sent1'=1) & (p1'=0); + // send counter (already sent preference) + // not received counter yet + [c12] (s1=2) & (sent1=1) & (receive1=1) -> (sent1'=2); + // received counter (pick again) + [c12] (s1=2) & (sent1=1) & (receive1=2) -> (s1'=0) & (p1'=0) & (c1'=0) & (sent1'=0) & (receive1'=0); + + // receive counter and not sent yet (note in this case do not pass it on as will send own counter) + [c61] (s1=2) & (receive1=1) & (sent1<2) -> (receive1'=2); + // receive counter and sent counter + // only active process (decide) + [c61] (s1=2) & (receive1=1) & (sent1=2) & (c6=N-1) -> (s1'=4) & (p1'=0) & (c1'=0) & (sent1'=0) & (receive1'=0); + // other active process (pick again) + [c61] (s1=2) & (receive1=1) & (sent1=2) & (c6 (s1'=0) & (p1'=0) & (c1'=0) & (sent1'=0) & (receive1'=0); + + // send preference (must have received preference) and can now reset + [p12] (s1=3) & (receive1>0) & (sent1=0) -> (sent1'=1) & (p1'=0); + // send counter (must have received counter first) and can now reset + [c12] (s1=3) & (receive1=2) & (sent1=1) -> (s1'=3) & (p1'=0) & (c1'=0) & (sent1'=0) & (receive1'=0); + + // receive preference + [p61] (s1=3) & (receive1=0) -> (p1'=p6) & (receive1'=1); + // receive counter + [c61] (s1=3) & (receive1=1) & (c6 (c1'=c6+1) & (receive1'=2); + + // done + [done] (s1=4) -> (s1'=s1); + // add loop for processes who are inactive + [done] (s1=3) -> (s1'=s1); + +endmodule + +//---------------------------------------------------------------------------------------------------------------------------- + +// construct further stations through renaming +module process2=process1[s1=s2,p1=p2,c1=c2,sent1=sent2,receive1=receive2,p12=p23,p61=p12,c12=c23,c61=c12,p6=p1,c6=c1] endmodule +module process3=process1[s1=s3,p1=p3,c1=c3,sent1=sent3,receive1=receive3,p12=p34,p61=p23,c12=c34,c61=c23,p6=p2,c6=c2] endmodule +module process4=process1[s1=s4,p1=p4,c1=c4,sent1=sent4,receive1=receive4,p12=p45,p61=p34,c12=c45,c61=c34,p6=p3,c6=c3] endmodule +module process5=process1[s1=s5,p1=p5,c1=c5,sent1=sent5,receive1=receive5,p12=p56,p61=p45,c12=c56,c61=c45,p6=p4,c6=c4] endmodule +module process6=process1[s1=s6,p1=p6,c1=c6,sent1=sent6,receive1=receive6,p12=p61,p61=p56,c12=c61,c61=c56,p6=p5,c6=c5] endmodule + +//---------------------------------------------------------------------------------------------------------------------------- + +// reward - expected number of rounds (equals the number of times a process receives a counter) +rewards + [c12] true : 1; +endrewards + +//---------------------------------------------------------------------------------------------------------------------------- +formula leaders = (s1=4?1:0)+(s2=4?1:0)+(s3=4?1:0)+(s4=4?1:0)+(s5=4?1:0)+(s6=4?1:0); +label "elected" = s1=4|s2=4|s3=4|s4=4|s5=4|s6=4; + diff --git a/examples/mdp/asynchronous_leader/leader7.nm b/examples/mdp/asynchronous_leader/leader7.nm new file mode 100644 index 000000000..fd1a84bed --- /dev/null +++ b/examples/mdp/asynchronous_leader/leader7.nm @@ -0,0 +1,100 @@ +// asynchronous leader election +// 4 processes +// gxn/dxp 29/01/01 + +mdp + +const N= 7; // number of processes + +//---------------------------------------------------------------------------------------------------------------------------- +module process1 + + // COUNTER + c1 : [0..7-1]; + + // STATES + s1 : [0..4]; + // 0 make choice + // 1 have not received neighbours choice + // 2 active + // 3 inactive + // 4 leader + + // PREFERENCE + p1 : [0..1]; + + // VARIABLES FOR SENDING AND RECEIVING + receive1 : [0..2]; + // not received anything + // received choice + // received counter + sent1 : [0..2]; + // not send anything + // sent choice + // sent counter + + // pick value + [] (s1=0) -> 0.5 : (s1'=1) & (p1'=0) + 0.5 : (s1'=1) & (p1'=1); + + // send preference + [p12] (s1=1) & (sent1=0) -> (sent1'=1); + // receive preference + // stay active + [p71] (s1=1) & (receive1=0) & !( (p1=0) & (p7=1) ) -> (s1'=2) & (receive1'=1); + // become inactive + [p71] (s1=1) & (receive1=0) & (p1=0) & (p7=1) -> (s1'=3) & (receive1'=1); + + // send preference (can now reset preference) + [p12] (s1=2) & (sent1=0) -> (sent1'=1) & (p1'=0); + // send counter (already sent preference) + // not received counter yet + [c12] (s1=2) & (sent1=1) & (receive1=1) -> (sent1'=2); + // received counter (pick again) + [c12] (s1=2) & (sent1=1) & (receive1=2) -> (s1'=0) & (p1'=0) & (c1'=0) & (sent1'=0) & (receive1'=0); + + // receive counter and not sent yet (note in this case do not pass it on as will send own counter) + [c71] (s1=2) & (receive1=1) & (sent1<2) -> (receive1'=2); + // receive counter and sent counter + // only active process (decide) + [c71] (s1=2) & (receive1=1) & (sent1=2) & (c7=N-1) -> (s1'=4) & (p1'=0) & (c1'=0) & (sent1'=0) & (receive1'=0); + // other active process (pick again) + [c71] (s1=2) & (receive1=1) & (sent1=2) & (c7 (s1'=0) & (p1'=0) & (c1'=0) & (sent1'=0) & (receive1'=0); + + // send preference (must have received preference) and can now reset + [p12] (s1=3) & (receive1>0) & (sent1=0) -> (sent1'=1) & (p1'=0); + // send counter (must have received counter first) and can now reset + [c12] (s1=3) & (receive1=2) & (sent1=1) -> (s1'=3) & (p1'=0) & (c1'=0) & (sent1'=0) & (receive1'=0); + + // receive preference + [p71] (s1=3) & (receive1=0) -> (p1'=p7) & (receive1'=1); + // receive counter + [c71] (s1=3) & (receive1=1) & (c7 (c1'=c7+1) & (receive1'=2); + + // done + [done] (s1=4) -> (s1'=s1); + // add loop for processes who are inactive + [done] (s1=3) -> (s1'=s1); + +endmodule + +//---------------------------------------------------------------------------------------------------------------------------- + +// construct further stations through renaming +module process2=process1[s1=s2,p1=p2,c1=c2,sent1=sent2,receive1=receive2,p12=p23,p71=p12,c12=c23,c71=c12,p7=p1,c7=c1] endmodule +module process3=process1[s1=s3,p1=p3,c1=c3,sent1=sent3,receive1=receive3,p12=p34,p71=p23,c12=c34,c71=c23,p7=p2,c7=c2] endmodule +module process4=process1[s1=s4,p1=p4,c1=c4,sent1=sent4,receive1=receive4,p12=p45,p71=p34,c12=c45,c71=c34,p7=p3,c7=c3] endmodule +module process5=process1[s1=s5,p1=p5,c1=c5,sent1=sent5,receive1=receive5,p12=p56,p71=p45,c12=c56,c71=c45,p7=p4,c7=c4] endmodule +module process6=process1[s1=s6,p1=p6,c1=c6,sent1=sent6,receive1=receive6,p12=p67,p71=p56,c12=c67,c71=c56,p7=p5,c7=c5] endmodule +module process7=process1[s1=s7,p1=p7,c1=c7,sent1=sent7,receive1=receive7,p12=p71,p71=p67,c12=c71,c71=c67,p7=p6,c7=c6] endmodule + +//---------------------------------------------------------------------------------------------------------------------------- + +// reward - expected number of rounds (equals the number of times a process receives a counter) +rewards + [c12] true : 1; +endrewards + +//---------------------------------------------------------------------------------------------------------------------------- +formula leaders = (s1=4?1:0)+(s2=4?1:0)+(s3=4?1:0)+(s4=4?1:0)+(s5=4?1:0)+(s6=4?1:0)+(s7=4?1:0); +label "elected" = s1=4|s2=4|s3=4|s4=4|s5=4|s6=4|s7=4; + diff --git a/examples/mdp/consensus/coin.pctl b/examples/mdp/consensus/coin.pctl new file mode 100644 index 000000000..52a308559 --- /dev/null +++ b/examples/mdp/consensus/coin.pctl @@ -0,0 +1,20 @@ +// C1 (with probability 1, all N processes finish the protocol) +Pmin=? [ F "finished" ] + +// C2 (minimum probability that the protocol finishes with all coins equal to v) (v=1,2) +// Results are same for v=1 and v=2 by symmetry +// Analytic bound is (K-1)/(2*K) +Pmin=? [ F "finished"&"all_coins_equal_0" ] +Pmin=? [ F "finished"&"all_coins_equal_1" ] + +// Max probability of finishing protocol with coins not all equal +Pmax=? [ F "finished"&!"agree" ] + +// Min/max probability of finishing within k steps +Pmin=? [ F<=k "finished" ] +Pmax=? [ F<=k "finished" ] + +// Min/max expected steps to finish +Rmin=? [ F "finished" ] +Rmax=? [ F "finished" ] + diff --git a/examples/mdp/consensus/coin2.nm b/examples/mdp/consensus/coin2.nm new file mode 100644 index 000000000..07ec59e17 --- /dev/null +++ b/examples/mdp/consensus/coin2.nm @@ -0,0 +1,60 @@ +// COIN FLIPPING PROTOCOL FOR POLYNOMIAL RANDOMIZED CONSENSUS [AH90] +// gxn/dxp 20/11/00 + +mdp + +// constants +const int N=2; +const int K; +const int range = 2*(K+1)*N; +const int counter_init = (K+1)*N; +const int left = N; +const int right = 2*(K+1)*N - N; + +// shared coin +global counter : [0..range] init counter_init; + +module process1 + + // program counter + pc1 : [0..3]; + // 0 - flip + // 1 - write + // 2 - check + // 3 - finished + + // local coin + coin1 : [0..1]; + + // flip coin + [] (pc1=0) -> 0.5 : (coin1'=0) & (pc1'=1) + 0.5 : (coin1'=1) & (pc1'=1); + // write tails -1 (reset coin to add regularity) + [] (pc1=1) & (coin1=0) & (counter>0) -> (counter'=counter-1) & (pc1'=2) & (coin1'=0); + // write heads +1 (reset coin to add regularity) + [] (pc1=1) & (coin1=1) & (counter (counter'=counter+1) & (pc1'=2) & (coin1'=0); + // check + // decide tails + [] (pc1=2) & (counter<=left) -> (pc1'=3) & (coin1'=0); + // decide heads + [] (pc1=2) & (counter>=right) -> (pc1'=3) & (coin1'=1); + // flip again + [] (pc1=2) & (counter>left) & (counter (pc1'=0); + // loop (all loop together when done) + [done] (pc1=3) -> (pc1'=3); + +endmodule + +// construct remaining processes through renaming +module process2 = process1[pc1=pc2,coin1=coin2] endmodule + +// labels +label "finished" = pc1=3 & pc2=3 ; +label "all_coins_equal_0" = coin1=0 & coin2=0 ; +label "all_coins_equal_1" = coin1=1 & coin2=1 ; +label "agree" = coin1=coin2 ; + +// rewards +rewards "steps" + true : 1; +endrewards + diff --git a/examples/mdp/consensus/coin4.nm b/examples/mdp/consensus/coin4.nm new file mode 100644 index 000000000..8639b867b --- /dev/null +++ b/examples/mdp/consensus/coin4.nm @@ -0,0 +1,62 @@ +// COIN FLIPPING PROTOCOL FOR POLYNOMIAL RANDOMIZED CONSENSUS [AH90] +// gxn/dxp 20/11/00 + +mdp + +// constants +const int N=4; +const int K; +const int range = 2*(K+1)*N; +const int counter_init = (K+1)*N; +const int left = N; +const int right = 2*(K+1)*N - N; + +// shared coin +global counter : [0..range] init counter_init; + +module process1 + + // program counter + pc1 : [0..3]; + // 0 - flip + // 1 - write + // 2 - check + // 3 - finished + + // local coin + coin1 : [0..1]; + + // flip coin + [] (pc1=0) -> 0.5 : (coin1'=0) & (pc1'=1) + 0.5 : (coin1'=1) & (pc1'=1); + // write tails -1 (reset coin to add regularity) + [] (pc1=1) & (coin1=0) & (counter>0) -> (counter'=counter-1) & (pc1'=2) & (coin1'=0); + // write heads +1 (reset coin to add regularity) + [] (pc1=1) & (coin1=1) & (counter (counter'=counter+1) & (pc1'=2) & (coin1'=0); + // check + // decide tails + [] (pc1=2) & (counter<=left) -> (pc1'=3) & (coin1'=0); + // decide heads + [] (pc1=2) & (counter>=right) -> (pc1'=3) & (coin1'=1); + // flip again + [] (pc1=2) & (counter>left) & (counter (pc1'=0); + // loop (all loop together when done) + [done] (pc1=3) -> (pc1'=3); + +endmodule + +// construct remaining processes through renaming +module process2 = process1[pc1=pc2,coin1=coin2] endmodule +module process3 = process1[pc1=pc3,coin1=coin3] endmodule +module process4 = process1[pc1=pc4,coin1=coin4] endmodule + +// labels +label "finished" = pc1=3 & pc2=3 & pc3=3 & pc4=3 ; +label "all_coins_equal_0" = coin1=0 & coin2=0 & coin3=0 & coin4=0 ; +label "all_coins_equal_1" = coin1=1 & coin2=1 & coin3=1 & coin4=1 ; +label "agree" = coin1=coin2 & coin2=coin3 & coin3=coin4 ; + +// rewards +rewards "steps" + true : 1; +endrewards + diff --git a/examples/mdp/consensus/coin6.nm b/examples/mdp/consensus/coin6.nm new file mode 100644 index 000000000..b373c40d5 --- /dev/null +++ b/examples/mdp/consensus/coin6.nm @@ -0,0 +1,64 @@ +// COIN FLIPPING PROTOCOL FOR POLYNOMIAL RANDOMIZED CONSENSUS [AH90] +// gxn/dxp 20/11/00 + +mdp + +// constants +const int N=6; +const int K; +const int range = 2*(K+1)*N; +const int counter_init = (K+1)*N; +const int left = N; +const int right = 2*(K+1)*N - N; + +// shared coin +global counter : [0..range] init counter_init; + +module process1 + + // program counter + pc1 : [0..3]; + // 0 - flip + // 1 - write + // 2 - check + // 3 - finished + + // local coin + coin1 : [0..1]; + + // flip coin + [] (pc1=0) -> 0.5 : (coin1'=0) & (pc1'=1) + 0.5 : (coin1'=1) & (pc1'=1); + // write tails -1 (reset coin to add regularity) + [] (pc1=1) & (coin1=0) & (counter>0) -> (counter'=counter-1) & (pc1'=2) & (coin1'=0); + // write heads +1 (reset coin to add regularity) + [] (pc1=1) & (coin1=1) & (counter (counter'=counter+1) & (pc1'=2) & (coin1'=0); + // check + // decide tails + [] (pc1=2) & (counter<=left) -> (pc1'=3) & (coin1'=0); + // decide heads + [] (pc1=2) & (counter>=right) -> (pc1'=3) & (coin1'=1); + // flip again + [] (pc1=2) & (counter>left) & (counter (pc1'=0); + // loop (all loop together when done) + [done] (pc1=3) -> (pc1'=3); + +endmodule + +// construct remaining processes through renaming +module process2 = process1[pc1=pc2,coin1=coin2] endmodule +module process3 = process1[pc1=pc3,coin1=coin3] endmodule +module process4 = process1[pc1=pc4,coin1=coin4] endmodule +module process5 = process1[pc1=pc5,coin1=coin5] endmodule +module process6 = process1[pc1=pc6,coin1=coin6] endmodule + +// labels +label "finished" = pc1=3 & pc2=3 & pc3=3 & pc4=3 & pc5=3 & pc6=3 ; +label "all_coins_equal_0" = coin1=0 & coin2=0 & coin3=0 & coin4=0 & coin5=0 & coin6=0 ; +label "all_coins_equal_1" = coin1=1 & coin2=1 & coin3=1 & coin4=1 & coin5=1 & coin6=1 ; +label "agree" = coin1=coin2 & coin2=coin3 & coin3=coin4 & coin4=coin5 & coin5=coin6 ; + +// rewards +rewards "steps" + true : 1; +endrewards + diff --git a/examples/mdp/two_dice/two_dice.nm b/examples/mdp/two_dice/two_dice.nm new file mode 100644 index 000000000..e1bf34aea --- /dev/null +++ b/examples/mdp/two_dice/two_dice.nm @@ -0,0 +1,40 @@ +// sum of two dice as the asynchronous parallel composition of +// two copies of Knuth's model of a fair die using only fair coins + +mdp + +module die1 + + // local state + s1 : [0..7] init 0; + // value of the dice + d1 : [0..6] init 0; + + [] s1=0 -> 0.5 : (s1'=1) + 0.5 : (s1'=2); + [] s1=1 -> 0.5 : (s1'=3) + 0.5 : (s1'=4); + [] s1=2 -> 0.5 : (s1'=5) + 0.5 : (s1'=6); + [] s1=3 -> 0.5 : (s1'=1) + 0.5 : (s1'=7) & (d1'=1); + [] s1=4 -> 0.5 : (s1'=7) & (d1'=2) + 0.5 : (s1'=7) & (d1'=3); + [] s1=5 -> 0.5 : (s1'=7) & (d1'=4) + 0.5 : (s1'=7) & (d1'=5); + [] s1=6 -> 0.5 : (s1'=2) + 0.5 : (s1'=7) & (d1'=6); + [] s1=7 & s2=7 -> (s1'=7); +endmodule + +module die2 = die1 [ s1=s2, s2=s1, d1=d2 ] endmodule + +rewards "coinflips" + [] s1<7 | s2<7 : 1; +endrewards + +label "done" = s1=7 & s2=7; +label "two" = s1=7 & s2=7 & d1+d2=2; +label "three" = s1=7 & s2=7 & d1+d2=3; +label "four" = s1=7 & s2=7 & d1+d2=4; +label "five" = s1=7 & s2=7 & d1+d2=5; +label "six" = s1=7 & s2=7 & d1+d2=6; +label "seven" = s1=7 & s2=7 & d1+d2=7; +label "eight" = s1=7 & s2=7 & d1+d2=8; +label "nine" = s1=7 & s2=7 & d1+d2=9; +label "ten" = s1=7 & s2=7 & d1+d2=10; +label "eleven" = s1=7 & s2=7 & d1+d2=11; +label "twelve" = s1=7 & s2=7 & d1+d2=12; diff --git a/examples/mdp/two_dice/two_dice.pctl b/examples/mdp/two_dice/two_dice.pctl new file mode 100644 index 000000000..10cdff821 --- /dev/null +++ b/examples/mdp/two_dice/two_dice.pctl @@ -0,0 +1,13 @@ +Pmin=? [ F "two" ] +Pmax=? [ F "two" ] +Pmin=? [ F "three" ] +Pmax=? [ F "three" ] +Pmin=? [ F "four" ] +Pmax=? [ F "four" ] +Pmin=? [ F "five" ] +Pmax=? [ F "five" ] +Pmin=? [ F "six" ] +Pmax=? [ F "six" ] + +Rmin=? [ F "done" ] +Rmax=? [ F "done" ] diff --git a/examples/mdp/two_dice/two_dice.res b/examples/mdp/two_dice/two_dice.res new file mode 100644 index 000000000..9f38b8153 --- /dev/null +++ b/examples/mdp/two_dice/two_dice.res @@ -0,0 +1,13 @@ +Pmin=? [ F "two" ] // 0.027777761220932007 +Pmax=? [ F "two" ] // 0.027777761220932007 +Pmin=? [ F "three" ] // 0.055555522441864014 +Pmax=? [ F "three" ] // 0.055555522441864014 +Pmin=? [ F "four" ] // 0.08333328366279602 +Pmax=? [ F "four" ] // 0.08333328366279602 +Pmin=? [ F "five" ] // 0.11111104488372803 +Pmax=? [ F "five" ] // 0.11111104488372803 +Pmin=? [ F "six" ] // 0.13888880610466003 +Pmax=? [ F "six" ] // 0.13888880610466003 + +Rmin=? [ F "done" ] // 7.333329498767853 +Rmax=? [ F "done" ] // 7.333329498767853 diff --git a/src/adapters/EigenAdapter.h b/src/adapters/EigenAdapter.h index d7c8169e0..b258c6aa2 100644 --- a/src/adapters/EigenAdapter.h +++ b/src/adapters/EigenAdapter.h @@ -32,9 +32,9 @@ public: LOG4CPLUS_DEBUG(logger, "Converting matrix with " << realNonZeros << " non-zeros to Eigen format."); // Prepare the resulting matrix. - Eigen::SparseMatrix* result = new Eigen::SparseMatrix(matrix.rowCount, matrix.colCount); + Eigen::SparseMatrix* result = new Eigen::SparseMatrix(static_cast(matrix.rowCount), static_cast(matrix.colCount)); - result->resizeNonZeros(realNonZeros); + result->resizeNonZeros(static_cast(realNonZeros)); //result->reserve(realNonZeros); // Copy Row Indications @@ -54,4 +54,4 @@ public: } //namespace storm -#endif /* STORM_ADAPTERS_GMMXXADAPTER_H_ */ +#endif /* STORM_ADAPTERS_EIGENADAPTER_H_ */ diff --git a/src/adapters/GmmxxAdapter.h b/src/adapters/GmmxxAdapter.h index 5c5faa670..74f0e6254 100644 --- a/src/adapters/GmmxxAdapter.h +++ b/src/adapters/GmmxxAdapter.h @@ -22,7 +22,7 @@ namespace adapters { class GmmxxAdapter { public: /*! - * Converts a sparse matrix into the sparse matrix in the gmm++ format. + * Converts a sparse matrix into a sparse matrix in the gmm++ format. * @return A pointer to a row-major sparse matrix in gmm++ format. */ template @@ -46,6 +46,46 @@ public: return result; } + + /*! + * Converts a sparse matrix in the gmm++ format to Storm Sparse Matrix format. + * @return A pointer to a row-major sparse matrix in our format. + */ + template + static storm::storage::SparseMatrix* fromGmmxxSparseMatrix(gmm::csr_matrix const& matrix) { + uint_fast64_t realNonZeros = gmm::nnz(matrix); + LOG4CPLUS_DEBUG(logger, "Converting matrix with " << realNonZeros << " non-zeros from gmm++ format into Storm."); + + // Prepare the resulting matrix. + storm::storage::SparseMatrix* result = new storm::storage::SparseMatrix(matrix.nrows(), matrix.ncols()); + + // Set internal NonZero Counter + result->nonZeroEntryCount = realNonZeros; + result->setState(result->Initialized); + + if (!result->prepareInternalStorage(false)) { + LOG4CPLUS_ERROR(logger, "Unable to allocate internal storage while converting GMM++ Matrix to Storm."); + delete result; + return nullptr; + } else { + + // Copy Row Indications + std::copy(matrix.jc.begin(), matrix.jc.end(), std::back_inserter(result->rowIndications)); + // Copy Columns Indications + std::copy(matrix.ir.begin(), matrix.ir.end(), std::back_inserter(result->columnIndications)); + // And do the same thing with the actual values. + std::copy(matrix.pr.begin(), matrix.pr.end(), std::back_inserter(result->valueStorage)); + + result->currentSize = realNonZeros; + result->lastRow = matrix.nrows() - 1; + } + + result->finalize(); + + LOG4CPLUS_DEBUG(logger, "Done converting matrix to storm format."); + + return result; + } }; } //namespace adapters diff --git a/src/adapters/StormAdapter.h b/src/adapters/StormAdapter.h new file mode 100644 index 000000000..c8ab870a1 --- /dev/null +++ b/src/adapters/StormAdapter.h @@ -0,0 +1,220 @@ +/* + * StormAdapter.h + * + * Created on: 02.03.2013 + * Author: Philipp Berger + */ + +#ifndef STORM_ADAPTERS_STORMADAPTER_H_ +#define STORM_ADAPTERS_STORMADAPTER_H_ + +#include "src/storage/SparseMatrix.h" + +#include "log4cplus/logger.h" +#include "log4cplus/loggingmacros.h" + +extern log4cplus::Logger logger; + +namespace storm { + +namespace adapters { + +class StormAdapter { +public: + /*! + * Converts a sparse matrix into a sparse matrix in the storm format. + * @return A pointer to a row-major sparse matrix in storm format. + */ + template + static storm::storage::SparseMatrix* toStormSparseMatrix(gmm::csr_matrix const& matrix) { + uint_fast64_t realNonZeros = gmm::nnz(matrix); + LOG4CPLUS_DEBUG(logger, "Converting matrix with " << realNonZeros << " non-zeros from gmm++ format into Storm."); + + // Prepare the resulting matrix. + storm::storage::SparseMatrix* result = new storm::storage::SparseMatrix(matrix.nrows(), matrix.ncols()); + + // Set internal NonZero Counter + result->nonZeroEntryCount = realNonZeros; + result->setState(result->Initialized); + + if (!result->prepareInternalStorage(false)) { + LOG4CPLUS_ERROR(logger, "Unable to allocate internal storage while converting GMM++ Matrix to Storm."); + delete result; + return nullptr; + } else { + + // Copy Row Indications + std::copy(matrix.jc.begin(), matrix.jc.end(), std::back_inserter(result->rowIndications)); + // Copy Columns Indications + std::copy(matrix.ir.begin(), matrix.ir.end(), std::back_inserter(result->columnIndications)); + // And do the same thing with the actual values. + std::copy(matrix.pr.begin(), matrix.pr.end(), std::back_inserter(result->valueStorage)); + + result->currentSize = realNonZeros; + result->lastRow = matrix.nrows() - 1; + } + + result->finalize(); + + LOG4CPLUS_DEBUG(logger, "Done converting matrix to storm format."); + + return result; + } + + /*! + * Helper function to determine whether the given Eigen matrix is in RowMajor + * format. Always returns true, but is overloaded, so the compiler will + * only call it in case the Eigen matrix is in RowMajor format. + * @return True. + */ + template + static bool isEigenRowMajor(Eigen::SparseMatrix<_Scalar, Eigen::RowMajor, _Index>) { + return true; + } + + /*! + * Helper function to determine whether the given Eigen matrix is in RowMajor + * format. Always returns false, but is overloaded, so the compiler will + * only call it in case the Eigen matrix is in ColMajor format. + * @return False. + */ + template + static bool isEigenRowMajor( + Eigen::SparseMatrix<_Scalar, Eigen::ColMajor, _Index>) { + return false; + } + + /*! + * Converts a sparse matrix in the eigen format to Storm Sparse Matrix format. + * @return A pointer to a row-major sparse matrix in our format. + */ + template + static storm::storage::SparseMatrix* toStormSparseMatrix(Eigen::SparseMatrix const& matrix) { + uint_fast64_t realNonZeros = matrix.nonZeros(); + LOG4CPLUS_DEBUG(logger, "Converting matrix with " << realNonZeros << " non-zeros from Eigen3 format into Storm."); + + // Throw an error in case the matrix is not in compressed format. + if (!matrix.isCompressed()) { + LOG4CPLUS_ERROR(logger, "Trying to convert from an Eigen matrix that is not in compressed form."); + return nullptr; + } + + /* + * Try to prepare the internal storage and throw an error in case of + * failure. + */ + + // Get necessary pointers to the contents of the Eigen matrix. + const T* valuePtr = matrix.valuePtr(); + const _Index* indexPtr = matrix.innerIndexPtr(); + const _Index* outerPtr = matrix.outerIndexPtr(); + + const uint_fast64_t outerSize = matrix.outerSize(); + + storm::storage::SparseMatrix* result = nullptr; + + // If the given matrix is in RowMajor format, copying can simply + // be done by adding all values in order. + // Direct copying is, however, prevented because we have to + // separate the diagonal entries from others. + if (isEigenRowMajor(matrix)) { + /* Because of the RowMajor format outerSize evaluates to the + * number of rows. + * Prepare the resulting matrix. + */ + result = new storm::storage::SparseMatrix(matrix.outerSize(), matrix.innerSize()); + + // Set internal NonZero Counter + result->nonZeroEntryCount = realNonZeros; + result->setState(result->Initialized); + + if (!result->prepareInternalStorage(false)) { + LOG4CPLUS_ERROR(logger, "Unable to allocate internal storage while converting Eigen3 RM Matrix to Storm."); + delete result; + return nullptr; + } else { + + // Copy Row Indications + std::copy(outerPtr, outerPtr + outerSize, std::back_inserter(result->rowIndications)); + // Copy Columns Indications + std::copy(indexPtr, indexPtr + realNonZeros, std::back_inserter(result->columnIndications)); + // And do the same thing with the actual values. + std::copy(valuePtr, valuePtr + realNonZeros, std::back_inserter(result->valueStorage)); + + // This is our Sentinel Element. + result->rowIndications.push_back(realNonZeros); + + result->currentSize = realNonZeros; + result->lastRow = outerSize - 1; + } + } else { + /* Because of the RowMajor format outerSize evaluates to the + * number of columns. + * Prepare the resulting matrix. + */ + const uint_fast64_t colCount = matrix.outerSize(); + result = new storm::storage::SparseMatrix(matrix.innerSize(), colCount); + + // Set internal NonZero Counter + result->nonZeroEntryCount = realNonZeros; + result->setState(result->Initialized); + if (!result->prepareInternalStorage()) { + LOG4CPLUS_ERROR(logger, "Unable to allocate internal storage while converting Eigen3 CM Matrix to Storm."); + delete result; + return nullptr; + } else { + // Set internal NonZero Counter + result->nonZeroEntryCount = realNonZeros; + result->setState(result->Initialized); + + // Create an array to remember which elements have to still + // be searched in each column and initialize it with the starting + // index for every column. + _Index* positions = new _Index[colCount](); + for (_Index i = 0; i < colCount; ++i) { + positions[i] = outerPtr[i]; + } + + // Now copy the elements. As the matrix is in ColMajor format, + // we need to iterate over the columns to find the next non-zero + // entry. + uint_fast64_t i = 0; + int currentRow = 0; + int currentColumn = 0; + while (i < realNonZeros) { + // If the current element belongs the the current column, + // add it in case it is also in the current row. + if ((positions[currentColumn] < outerPtr[currentColumn + 1]) + && (indexPtr[positions[currentColumn]] == currentRow)) { + result->addNextValue(currentRow, currentColumn, valuePtr[positions[currentColumn]]); + // Remember that we found one more non-zero element. + ++i; + // Mark this position as "used". + ++positions[currentColumn]; + } + + // Now we can advance to the next column and also row, + // in case we just iterated through the last column. + ++currentColumn; + if (currentColumn == colCount) { + currentColumn = 0; + ++currentRow; + } + } + delete[] positions; + } + } + result->setState(result->Initialized); + result->finalize(); + + LOG4CPLUS_DEBUG(logger, "Done converting matrix to storm format."); + + return result; + } +}; + +} //namespace adapters + +} //namespace storm + +#endif /* STORM_ADAPTERS_STORMADAPTER_H_ */ diff --git a/src/formula/NoBoundOperator.h b/src/formula/NoBoundOperator.h index a61ae6797..5261f3736 100644 --- a/src/formula/NoBoundOperator.h +++ b/src/formula/NoBoundOperator.h @@ -11,6 +11,7 @@ #include "src/formula/AbstractFormula.h" #include "src/formula/AbstractPathFormula.h" #include "src/formula/AbstractFormulaChecker.h" +#include "src/formula/OptimizingOperator.h" #include "src/modelchecker/ForwardDeclarations.h" @@ -69,7 +70,7 @@ class INoBoundOperatorModelChecker { * @see AbstractFormula */ template -class NoBoundOperator: public storm::formula::AbstractFormula { +class NoBoundOperator: public storm::formula::AbstractFormula, public OptimizingOperator { public: /*! * Empty constructor diff --git a/src/formula/Not.h b/src/formula/Not.h index 463f0ca7a..0ddb803c9 100644 --- a/src/formula/Not.h +++ b/src/formula/Not.h @@ -113,7 +113,7 @@ public: virtual AbstractStateFormula* clone() const { Not* result = new Not(); if (child != NULL) { - result->setChild(child); + result->setChild(child->clone()); } return result; } diff --git a/src/formula/OptimizingOperator.h b/src/formula/OptimizingOperator.h new file mode 100644 index 000000000..030655fce --- /dev/null +++ b/src/formula/OptimizingOperator.h @@ -0,0 +1,56 @@ +#ifndef STORM_FORMULA_OPTIMIZINGOPERATOR_H_ +#define STORM_FORMULA_OPTIMIZINGOPERATOR_H_ + +namespace storm { + +namespace formula { + +class OptimizingOperator { +public: + /*! + * Empty constructor + */ + OptimizingOperator() : optimalityOperator(false), minimumOperator(false) { + } + + /*! + * Constructor + * + * @param minimumOperator A flag indicating whether this operator is a minimizing or a maximizing operator. + */ + OptimizingOperator(bool minimumOperator) : optimalityOperator(true), minimumOperator(minimumOperator) { + } + + /*! + * Retrieves whether the operator is to be interpreted as an optimizing (i.e. min/max) operator. + * @returns True if the operator is an optimizing operator. + */ + bool isOptimalityOperator() const { + return optimalityOperator; + } + + /*! + * Retrieves whether the operator is a minimizing operator given that it is an optimality + * operator. + * @returns True if the operator is an optimizing operator and it is a minimizing operator and + * false otherwise, i.e. if it is either not an optimizing operator or not a minimizing operator. + */ + bool isMinimumOperator() const { + return optimalityOperator && minimumOperator; + } + +private: + // A flag that indicates whether this operator is meant as an optimizing (i.e. min/max) operator + // over a nondeterministic model. + bool optimalityOperator; + + // In the case this operator is an optimizing operator, this flag indicates whether it is + // looking for the minimum or the maximum value. + bool minimumOperator; +}; + +} /* namespace formula */ + +} /* namespace storm */ + +#endif /* STORM_FORMULA_OPTIMIZINGOPERATOR_H_ */ diff --git a/src/formula/PathBoundOperator.h b/src/formula/PathBoundOperator.h index d94b9ca94..2bece5c61 100644 --- a/src/formula/PathBoundOperator.h +++ b/src/formula/PathBoundOperator.h @@ -11,6 +11,9 @@ #include "src/formula/AbstractStateFormula.h" #include "src/formula/AbstractPathFormula.h" #include "src/formula/AbstractFormulaChecker.h" + +#include "src/formula/OptimizingOperator.h" + #include "src/modelchecker/ForwardDeclarations.h" #include "src/utility/ConstTemplates.h" @@ -42,13 +45,13 @@ template class PathBoundOperator; * @see AbstractFormula */ template -class PathBoundOperator : public AbstractStateFormula { +class PathBoundOperator : public AbstractStateFormula, public OptimizingOperator { public: enum ComparisonType { LESS, LESS_EQUAL, GREATER, GREATER_EQUAL }; /*! - * Constructor + * Constructor for non-optimizing operator. * * @param comparisonOperator The relation for the bound. * @param bound The bound for the probability @@ -58,6 +61,19 @@ public: : comparisonOperator(comparisonOperator), bound(bound), pathFormula(pathFormula) { // Intentionally left empty } + + /*! + * Constructor for optimizing operator. + * + * @param comparisonOperator The relation for the bound. + * @param bound The bound for the probability + * @param pathFormula The child node + * @param minimumOperator Indicator, if operator should be minimum or maximum operator. + */ + PathBoundOperator(ComparisonType comparisonOperator, T bound, AbstractPathFormula* pathFormula, bool minimumOperator) + : comparisonOperator(comparisonOperator), bound(bound), pathFormula(pathFormula), OptimizingOperator(minimumOperator) { + // Intentionally left empty + } /*! * Destructor diff --git a/src/formula/ProbabilisticBoundOperator.h b/src/formula/ProbabilisticBoundOperator.h index 7104da47a..14429c677 100644 --- a/src/formula/ProbabilisticBoundOperator.h +++ b/src/formula/ProbabilisticBoundOperator.h @@ -11,6 +11,7 @@ #include "AbstractStateFormula.h" #include "AbstractPathFormula.h" #include "src/formula/PathBoundOperator.h" +#include "src/formula/OptimizingOperator.h" #include "utility/ConstTemplates.h" namespace storm { @@ -72,8 +73,14 @@ public: * @param pathFormula The child node */ ProbabilisticBoundOperator( - typename PathBoundOperator::ComparisonType comparisonRelation, T bound, AbstractPathFormula* pathFormula) : - PathBoundOperator(comparisonRelation, bound, pathFormula) { + typename PathBoundOperator::ComparisonType comparisonRelation, T bound, AbstractPathFormula* pathFormula) + : PathBoundOperator(comparisonRelation, bound, pathFormula) { + // Intentionally left empty + } + + ProbabilisticBoundOperator( + typename PathBoundOperator::ComparisonType comparisonRelation, T bound, AbstractPathFormula* pathFormula, bool minimumOperator) + : PathBoundOperator(comparisonRelation, bound, pathFormula, minimumOperator){ // Intentionally left empty } diff --git a/src/formula/RewardBoundOperator.h b/src/formula/RewardBoundOperator.h index ec507176a..f602f89b9 100644 --- a/src/formula/RewardBoundOperator.h +++ b/src/formula/RewardBoundOperator.h @@ -74,6 +74,12 @@ public: // Intentionally left empty } + RewardBoundOperator( + typename PathBoundOperator::ComparisonType comparisonRelation, T bound, AbstractPathFormula* pathFormula, bool minimumOperator) + : PathBoundOperator(comparisonRelation, bound, pathFormula, minimumOperator) { + // Intentionally left empty + } + /*! * @returns a string representation of the formula */ diff --git a/src/modelchecker/AbstractModelChecker.h b/src/modelchecker/AbstractModelChecker.h index 5c468959f..213cb9938 100644 --- a/src/modelchecker/AbstractModelChecker.h +++ b/src/modelchecker/AbstractModelChecker.h @@ -72,6 +72,88 @@ public: } /*! +<<<<<<< HEAD +======= + * Checks the given state formula on the DTMC and prints the result (true/false) for all initial + * states. + * @param stateFormula The formula to be checked. + */ + void check(const storm::formula::AbstractStateFormula& stateFormula) const { + std::cout << std::endl; + LOG4CPLUS_INFO(logger, "Model checking formula\t" << stateFormula.toString()); + std::cout << "Model checking formula:\t" << stateFormula.toString() << std::endl; + storm::storage::BitVector* result = nullptr; + try { + result = stateFormula.check(*this); + LOG4CPLUS_INFO(logger, "Result for initial states:"); + std::cout << "Result for initial states:" << std::endl; + for (auto initialState : model.getLabeledStates("init")) { + LOG4CPLUS_INFO(logger, "\t" << initialState << ": " << (result->get(initialState) ? "satisfied" : "not satisfied")); + std::cout << "\t" << initialState << ": " << (*result)[initialState] << std::endl; + } + delete result; + } catch (std::exception& e) { + std::cout << "Error during computation: " << e.what() << "Skipping property." << std::endl; + if (result != nullptr) { + delete result; + } + } + std::cout << std::endl; + storm::utility::printSeparationLine(std::cout); + } + + /*! + * Checks the given operator (with no bound) on the DTMC and prints the result + * (probability/rewards) for all initial states. + * @param noBoundFormula The formula to be checked. + */ + void check(const storm::formula::NoBoundOperator& noBoundFormula) const { + std::cout << std::endl; + LOG4CPLUS_INFO(logger, "Model checking formula\t" << noBoundFormula.toString()); + std::cout << "Model checking formula:\t" << noBoundFormula.toString() << std::endl; + std::vector* result = nullptr; + try { + result = noBoundFormula.check(*this); + LOG4CPLUS_INFO(logger, "Result for initial states:"); + std::cout << "Result for initial states:" << std::endl; + for (auto initialState : *model.getLabeledStates("init")) { + LOG4CPLUS_INFO(logger, "\t" << initialState << ": " << (*result)[initialState]); + std::cout << "\t" << initialState << ": " << (*result)[initialState] << std::endl; + } + delete result; + } catch (std::exception& e) { + std::cout << "Error during computation: " << e.what() << " Skipping property." << std::endl; + if (result != nullptr) { + delete result; + } + } + std::cout << std::endl; + storm::utility::printSeparationLine(std::cout); + } + + /*! + * The check method for a formula with an AP node as root in its formula tree + * + * @param formula The Ap state formula to check + * @returns The set of states satisfying the formula, represented by a bit vector + */ + storm::storage::BitVector* checkAp(const storm::formula::Ap& formula) const { + if (formula.getAp().compare("true") == 0) { + return new storm::storage::BitVector(model.getNumberOfStates(), true); + } else if (formula.getAp().compare("false") == 0) { + return new storm::storage::BitVector(model.getNumberOfStates()); + } + + if (!model.hasAtomicProposition(formula.getAp())) { + LOG4CPLUS_ERROR(logger, "Atomic proposition '" << formula.getAp() << "' is invalid."); + throw storm::exceptions::InvalidPropertyException() << "Atomic proposition '" << formula.getAp() << "' is invalid."; + } + + return new storm::storage::BitVector(*model.getLabeledStates(formula.getAp())); + } + + /*! +>>>>>>> master * The check method for a state formula with an And node as root in its formula tree * * @param formula The And formula to check @@ -111,13 +193,6 @@ public: return result; } - /*! - * Checks the given state formula on the DTMC and prints the result (true/false) for all initial - * states. (Abstract) - * @param stateFormula The formula to be checked. - */ - virtual void check(const storm::formula::AbstractStateFormula& stateFormula) const = 0; - /*! * The check method for a state formula with a bound operator node as root in * its formula tree diff --git a/src/modelchecker/DtmcPrctlModelChecker.h b/src/modelchecker/DtmcPrctlModelChecker.h index 0e16dca5f..bc5486cf5 100644 --- a/src/modelchecker/DtmcPrctlModelChecker.h +++ b/src/modelchecker/DtmcPrctlModelChecker.h @@ -8,6 +8,8 @@ #ifndef STORM_MODELCHECKER_DTMCPRCTLMODELCHECKER_H_ #define STORM_MODELCHECKER_DTMCPRCTLMODELCHECKER_H_ +#include + #include "src/formula/Formulas.h" #include "src/utility/Vector.h" #include "src/storage/SparseMatrix.h" @@ -16,12 +18,11 @@ #include "src/storage/BitVector.h" #include "src/exceptions/InvalidPropertyException.h" #include "src/utility/Vector.h" +#include "src/utility/GraphAnalyzer.h" #include "src/modelchecker/AbstractModelChecker.h" -#include #include "log4cplus/logger.h" #include "log4cplus/loggingmacros.h" - extern log4cplus::Logger logger; namespace storm { @@ -38,8 +39,8 @@ namespace modelChecker { * @attention This class is abstract. */ template -class DtmcPrctlModelChecker : - public AbstractModelChecker { +class DtmcPrctlModelChecker : public AbstractModelChecker { + public: /*! @@ -47,8 +48,7 @@ public: * * @param model The dtmc model which is checked. */ - explicit DtmcPrctlModelChecker(storm::models::Dtmc& model) - : AbstractModelChecker(model) { + explicit DtmcPrctlModelChecker(storm::models::Dtmc& model) : AbstractModelChecker(model) { // Intentionally left empty. } @@ -58,6 +58,7 @@ public: * @param modelChecker The model checker that is copied. */ explicit DtmcPrctlModelChecker(const storm::modelChecker::DtmcPrctlModelChecker* modelChecker) : AbstractModelChecker(modelChecker) { + // Intentionally left empty. } /*! @@ -182,7 +183,31 @@ public: * @param formula The Bounded Until path formula to check * @returns for each state the probability that the path formula holds. */ - virtual std::vector* checkBoundedUntil(const storm::formula::BoundedUntil& formula, bool qualitative) const = 0; + virtual std::vector* checkBoundedUntil(const storm::formula::BoundedUntil& formula, bool qualitative) const { + // First, we need to compute the states that satisfy the sub-formulas of the until-formula. + storm::storage::BitVector* leftStates = formula.getLeft().check(*this); + storm::storage::BitVector* rightStates = formula.getRight().check(*this); + + // Copy the matrix before we make any changes. + storm::storage::SparseMatrix tmpMatrix(*this->getModel().getTransitionMatrix()); + + // Make all rows absorbing that violate both sub-formulas or satisfy the second sub-formula. + tmpMatrix.makeRowsAbsorbing(~(*leftStates | *rightStates) | *rightStates); + + // Delete obsolete intermediates. + delete leftStates; + delete rightStates; + + // Create the vector with which to multiply. + std::vector* result = new std::vector(this->getModel().getNumberOfStates()); + storm::utility::setVectorValues(result, *rightStates, storm::utility::constGetOne()); + + // Perform the matrix vector multiplication as often as required by the formula bound. + this->performMatrixVectorMultiplication(tmpMatrix, *result, nullptr, formula.getBound()); + + // Return result. + return result; + } /*! * The check method for a path formula with a Next operator node as root in its formula tree @@ -190,7 +215,23 @@ public: * @param formula The Next path formula to check * @returns for each state the probability that the path formula holds. */ - virtual std::vector* checkNext(const storm::formula::Next& formula, bool qualitative) const = 0; + virtual std::vector* checkNext(const storm::formula::Next& formula, bool qualitative) const { + // First, we need to compute the states that satisfy the child formula of the next-formula. + storm::storage::BitVector* nextStates = formula.getChild().check(*this); + + // Create the vector with which to multiply and initialize it correctly. + std::vector* result = new std::vector(this->getModel().getNumberOfStates()); + storm::utility::setVectorValues(result, *nextStates, storm::utility::constGetOne()); + + // Delete obsolete intermediate. + delete nextStates; + + // Perform one single matrix-vector multiplication. + this->performMatrixVectorMultiplication(*this->getModel().getTransitionMatrix(), *result); + + // Return result. + return result; + } /*! * The check method for a path formula with a Bounded Eventually operator node as root in its @@ -239,7 +280,68 @@ public: * @param formula The Until path formula to check * @returns for each state the probability that the path formula holds. */ - virtual std::vector* checkUntil(const storm::formula::Until& formula, bool qualitative) const = 0; + virtual std::vector* checkUntil(const storm::formula::Until& formula, bool qualitative) const { + // First, we need to compute the states that satisfy the sub-formulas of the until-formula. + storm::storage::BitVector* leftStates = formula.getLeft().check(*this); + storm::storage::BitVector* rightStates = formula.getRight().check(*this); + + // Then, we need to identify the states which have to be taken out of the matrix, i.e. + // all states that have probability 0 and 1 of satisfying the until-formula. + storm::storage::BitVector statesWithProbability0(this->getModel().getNumberOfStates()); + storm::storage::BitVector statesWithProbability1(this->getModel().getNumberOfStates()); + storm::utility::GraphAnalyzer::performProb01(this->getModel(), *leftStates, *rightStates, &statesWithProbability0, &statesWithProbability1); + + // Delete intermediate results that are obsolete now. + delete leftStates; + delete rightStates; + + // Perform some logging. + LOG4CPLUS_INFO(logger, "Found " << statesWithProbability0.getNumberOfSetBits() << " 'no' states."); + LOG4CPLUS_INFO(logger, "Found " << statesWithProbability1.getNumberOfSetBits() << " 'yes' states."); + storm::storage::BitVector maybeStates = ~(statesWithProbability0 | statesWithProbability1); + LOG4CPLUS_INFO(logger, "Found " << maybeStates.getNumberOfSetBits() << " 'maybe' states."); + + // Create resulting vector. + std::vector* result = new std::vector(this->getModel().getNumberOfStates()); + + // Only try to solve system if there are states for which the probability is unknown. + uint_fast64_t maybeStatesSetBitCount = maybeStates.getNumberOfSetBits(); + if (maybeStatesSetBitCount > 0 && !qualitative) { + // Now we can eliminate the rows and columns from the original transition probability matrix. + storm::storage::SparseMatrix* submatrix = this->getModel().getTransitionMatrix()->getSubmatrix(maybeStates); + // Converting the matrix from the fixpoint notation to the form needed for the equation + // system. That is, we go from x = A*x + b to (I-A)x = b. + submatrix->convertToEquationSystem(); + + // Initialize the x vector with 0.5 for each element. This is the initial guess for + // the iterative solvers. It should be safe as for all 'maybe' states we know that the + // probability is strictly larger than 0. + std::vector x(maybeStatesSetBitCount, Type(0.5)); + + // Prepare the right-hand side of the equation system. For entry i this corresponds to + // the accumulated probability of going from state i to some 'yes' state. + std::vector b(maybeStatesSetBitCount); + this->getModel().getTransitionMatrix()->getConstrainedRowSumVector(maybeStates, statesWithProbability1, &b); + + this->solveEquationSystem(*submatrix, x, b); + + // Delete the created submatrix. + delete submatrix; + + // Set values of resulting vector according to result. + storm::utility::setVectorValues(result, maybeStates, x); + } else if (qualitative) { + // If we only need a qualitative result, we can safely assume that the results will only be compared to + // bounds which are either 0 or 1. Setting the value to 0.5 is thus safe. + storm::utility::setVectorValues(result, maybeStates, Type(0.5)); + } + + // Set values of resulting vector that are known exactly. + storm::utility::setVectorValues(result, statesWithProbability0, storm::utility::constGetZero()); + storm::utility::setVectorValues(result, statesWithProbability1, storm::utility::constGetOne()); + + return result; + } /*! * The check method for a path formula with an Instantaneous Reward operator node as root in its @@ -248,7 +350,22 @@ public: * @param formula The Instantaneous Reward formula to check * @returns for each state the reward that the instantaneous reward yields */ - virtual std::vector* checkInstantaneousReward(const storm::formula::InstantaneousReward& formula, bool qualitative) const = 0; + virtual std::vector* checkInstantaneousReward(const storm::formula::InstantaneousReward& formula, bool qualitative) const { + // Only compute the result if the model has a state-based reward model. + if (!this->getModel().hasStateRewards()) { + LOG4CPLUS_ERROR(logger, "Missing (state-based) reward model for formula."); + throw storm::exceptions::InvalidPropertyException() << "Missing (state-based) reward model for formula."; + } + + // Initialize result to state rewards of the model. + std::vector* result = new std::vector(*this->getModel().getStateRewardVector()); + + // Perform the actual matrix-vector multiplication as long as the bound of the formula is met. + this->performMatrixVectorMultiplication(*this->getModel().getTransitionMatrix(), *result, nullptr, formula.getBound()); + + // Return result. + return result; + } /*! * The check method for a path formula with a Cumulative Reward operator node as root in its @@ -257,7 +374,32 @@ public: * @param formula The Cumulative Reward formula to check * @returns for each state the reward that the cumulative reward yields */ - virtual std::vector* checkCumulativeReward(const storm::formula::CumulativeReward& formula, bool qualitative) const = 0; + virtual std::vector* checkCumulativeReward(const storm::formula::CumulativeReward& formula, bool qualitative) const { + // Only compute the result if the model has at least one reward model. + if (!this->getModel().hasStateRewards() && !this->getModel().hasTransitionRewards()) { + LOG4CPLUS_ERROR(logger, "Missing reward model for formula."); + throw storm::exceptions::InvalidPropertyException() << "Missing reward model for formula."; + } + + // Compute the reward vector to add in each step based on the available reward models. + std::vector* totalRewardVector = nullptr; + if (this->getModel().hasTransitionRewards()) { + totalRewardVector = this->getModel().getTransitionMatrix()->getPointwiseProductRowSumVector(*this->getModel().getTransitionRewardMatrix()); + if (this->getModel().hasStateRewards()) { + gmm::add(*this->getModel().getStateRewardVector(), *totalRewardVector); + } + } else { + totalRewardVector = new std::vector(*this->getModel().getStateRewardVector()); + } + + std::vector* result = new std::vector(*this->getModel().getStateRewardVector()); + + this->performMatrixVectorMultiplication(*this->getModel().getTransitionMatrix(), *result, totalRewardVector, formula.getBound()); + + // Delete temporary variables and return result. + delete totalRewardVector; + return result; + } /*! * The check method for a path formula with a Reachability Reward operator node as root in its @@ -266,10 +408,88 @@ public: * @param formula The Reachbility Reward formula to check * @returns for each state the reward that the reachability reward yields */ - virtual std::vector* checkReachabilityReward(const storm::formula::ReachabilityReward& formula, bool qualitative) const = 0; + virtual std::vector* checkReachabilityReward(const storm::formula::ReachabilityReward& formula, bool qualitative) const { + // Only compute the result if the model has at least one reward model. + if (!this->getModel().hasStateRewards() && !this->getModel().hasTransitionRewards()) { + LOG4CPLUS_ERROR(logger, "Missing reward model for formula. Skipping formula"); + throw storm::exceptions::InvalidPropertyException() << "Missing reward model for formula."; + } + + // Determine the states for which the target predicate holds. + storm::storage::BitVector* targetStates = formula.getChild().check(*this); + + // Determine which states have a reward of infinity by definition. + storm::storage::BitVector infinityStates(this->getModel().getNumberOfStates()); + storm::storage::BitVector trueStates(this->getModel().getNumberOfStates(), true); + storm::utility::GraphAnalyzer::performProb1(this->getModel(), trueStates, *targetStates, &infinityStates); + infinityStates.complement(); + + // Create resulting vector. + std::vector* result = new std::vector(this->getModel().getNumberOfStates()); + + // Check whether there are states for which we have to compute the result. + storm::storage::BitVector maybeStates = ~(*targetStates) & ~infinityStates; + const int maybeStatesSetBitCount = maybeStates.getNumberOfSetBits(); + if (maybeStatesSetBitCount > 0) { + // Now we can eliminate the rows and columns from the original transition probability matrix. + storm::storage::SparseMatrix* submatrix = this->getModel().getTransitionMatrix()->getSubmatrix(maybeStates); + // Converting the matrix from the fixpoint notation to the form needed for the equation + // system. That is, we go from x = A*x + b to (I-A)x = b. + submatrix->convertToEquationSystem(); + + // Initialize the x vector with 1 for each element. This is the initial guess for + // the iterative solvers. + std::vector x(maybeStatesSetBitCount, storm::utility::constGetOne()); + + // Prepare the right-hand side of the equation system. + std::vector b(maybeStatesSetBitCount); + if (this->getModel().hasTransitionRewards()) { + // If a transition-based reward model is available, we initialize the right-hand + // side to the vector resulting from summing the rows of the pointwise product + // of the transition probability matrix and the transition reward matrix. + std::vector* pointwiseProductRowSumVector = this->getModel().getTransitionMatrix()->getPointwiseProductRowSumVector(*this->getModel().getTransitionRewardMatrix()); + storm::utility::selectVectorValues(&b, maybeStates, *pointwiseProductRowSumVector); + delete pointwiseProductRowSumVector; + + if (this->getModel().hasStateRewards()) { + // If a state-based reward model is also available, we need to add this vector + // as well. As the state reward vector contains entries not just for the states + // that we still consider (i.e. maybeStates), we need to extract these values + // first. + std::vector subStateRewards(maybeStatesSetBitCount); + storm::utility::selectVectorValues(&subStateRewards, maybeStates, *this->getModel().getStateRewardVector()); + gmm::add(subStateRewards, b); + } + } else { + // If only a state-based reward model is available, we take this vector as the + // right-hand side. As the state reward vector contains entries not just for the + // states that we still consider (i.e. maybeStates), we need to extract these values + // first. + storm::utility::selectVectorValues(&b, maybeStates, *this->getModel().getStateRewardVector()); + } + + this->solveEquationSystem(*submatrix, x, b); + + // Set values of resulting vector according to result. + storm::utility::setVectorValues(result, maybeStates, x); + + // Delete temporary matrix and right-hand side. + delete submatrix; + } + + // Set values of resulting vector that are known exactly. + storm::utility::setVectorValues(result, *targetStates, storm::utility::constGetZero()); + storm::utility::setVectorValues(result, infinityStates, storm::utility::constGetInfinity()); + + // Delete temporary storages and return result. + delete targetStates; + return result; + } private: -// storm::models::Dtmc& model; + virtual void performMatrixVectorMultiplication(storm::storage::SparseMatrix const& matrix, std::vector& vector, std::vector* summand = nullptr, uint_fast64_t repetitions = 1) const = 0; + + virtual void solveEquationSystem(storm::storage::SparseMatrix const& matrix, std::vector& vector, std::vector const& b) const = 0; }; } //namespace modelChecker diff --git a/src/modelchecker/EigenDtmcPrctlModelChecker.h b/src/modelchecker/EigenDtmcPrctlModelChecker.h index 6965b3a0c..167b02f72 100644 --- a/src/modelchecker/EigenDtmcPrctlModelChecker.h +++ b/src/modelchecker/EigenDtmcPrctlModelChecker.h @@ -38,183 +38,101 @@ namespace modelChecker { template class EigenDtmcPrctlModelChecker : public DtmcPrctlModelChecker { -public: - explicit EigenDtmcPrctlModelChecker(storm::models::Dtmc& dtmc) : DtmcPrctlModelChecker(dtmc) { } - - virtual ~EigenDtmcPrctlModelChecker() { } - - virtual std::vector* checkBoundedUntil(const storm::formula::BoundedUntil& formula) const { - // First, we need to compute the states that satisfy the sub-formulas of the until-formula. - storm::storage::BitVector* leftStates = this->checkStateFormula(formula.getLeft()); - storm::storage::BitVector* rightStates = this->checkStateFormula(formula.getRight()); - - // Copy the matrix before we make any changes. - storm::storage::SparseMatrix tmpMatrix(*this->getModel().getTransitionProbabilityMatrix()); - - // Make all rows absorbing that violate both sub-formulas or satisfy the second sub-formula. - tmpMatrix.makeRowsAbsorbing((~*leftStates | *rightStates) | *rightStates); - - // Transform the transition probability matrix to the eigen format to use its arithmetic. - Eigen::SparseMatrix* eigenMatrix = storm::adapters::EigenAdapter::toEigenSparseMatrix(tmpMatrix); - - // Create the vector with which to multiply. - uint_fast64_t stateCount = this->getModel().getNumberOfStates(); - - typedef Eigen::Matrix VectorType; - typedef Eigen::Map MapType; - - std::vector* result = new std::vector(stateCount); - - storm::utility::setVectorValues(result, *rightStates, storm::utility::constGetOne()); - - Type *p = &((*result)[0]); // get the address storing the data for result - MapType vectorMap(p, result->size()); // vectorMap shares data - +typedef Eigen::Matrix VectorType; +typedef Eigen::Map MapType; - // Now perform matrix-vector multiplication as long as we meet the bound of the formula. - for (uint_fast64_t i = 0, bound = formula.getBound(); i < bound; ++i) { - vectorMap = (*eigenMatrix) * vectorMap; - } - - // Delete intermediate results. - delete leftStates; - delete rightStates; - delete eigenMatrix; - - return result; +public: + explicit EigenDtmcPrctlModelChecker(storm::models::Dtmc& dtmc) : DtmcPrctlModelChecker(dtmc) { + // Intentionally left empty. } - virtual std::vector* checkNext(const storm::formula::Next& formula) const { - // First, we need to compute the states that satisfy the sub-formula of the next-formula. - storm::storage::BitVector* nextStates = this->checkStateFormula(formula.getChild()); + virtual ~EigenDtmcPrctlModelChecker() { + // Intentionally left empty. + } +private: + virtual void performMatrixVectorMultiplication(storm::storage::SparseMatrix const& matrix, std::vector** vector, std::vector* summand, uint_fast64_t repetitions = 1) const { // Transform the transition probability matrix to the eigen format to use its arithmetic. - Eigen::SparseMatrix* eigenMatrix = storm::adapters::EigenAdapter::toEigenSparseMatrix(this->getModel().getTransitionProbabilityMatrix()); - - // Create the vector with which to multiply and initialize it correctly. - std::vector x(this->getModel().getNumberOfStates()); - - storm::utility::setVectorValues(&x, *nextStates, storm::utility::constGetOne()); - - // Delete not needed next states bit vector. - delete nextStates; - - typedef Eigen::Matrix VectorType; - typedef Eigen::Map MapType; - - Type *px = &(x[0]); // get the address storing the data for x - MapType vectorX(px, x.size()); // vectorX shares data + Eigen::SparseMatrix* eigenMatrix = storm::adapters::EigenAdapter::toEigenSparseMatrix(matrix); - // Create resulting vector. - std::vector* result = new std::vector(this->getModel().getNumberOfStates()); + Type* p = &((**vector)[0]); // get the address storing the data for result + MapType vectorMap(p, (*vector)->size()); // vectorMap shares data - // Type *pr = &((*result)[0]); // get the address storing the data for result - MapType vectorResult(px, result->size()); // vectorResult shares data + p = &((*summand)[0]); + MapType summandMap(p, summand->size()); - // Perform the actual computation. - vectorResult = (*eigenMatrix) * vectorX; + // Now perform matrix-vector multiplication as long as we meet the bound. + std::vector* swap = nullptr; + std::vector* tmpResult = new std::vector(this->getModel().getNumberOfStates()); + for (uint_fast64_t i = 0; i < repetitions; ++i) { + vectorMap = (*eigenMatrix) * (vectorMap); - // Delete temporary matrix and return result. + // If requested, add an offset to the current result vector. + if (summand != nullptr) { + vectorMap = vectorMap + summandMap; + } + } delete eigenMatrix; - return result; } - virtual std::vector* checkUntil(const storm::formula::Until& formula) const { - // First, we need to compute the states that satisfy the sub-formulas of the until-formula. - storm::storage::BitVector* leftStates = this->checkStateFormula(formula.getLeft()); - storm::storage::BitVector* rightStates = this->checkStateFormula(formula.getRight()); - - // Then, we need to identify the states which have to be taken out of the matrix, i.e. - // all states that have probability 0 and 1 of satisfying the until-formula. - storm::storage::BitVector statesWithProbability0(this->getModel().getNumberOfStates()); - storm::storage::BitVector statesWithProbability1(this->getModel().getNumberOfStates()); - storm::utility::GraphAnalyzer::performProb01(this->getModel(), *leftStates, *rightStates, &statesWithProbability0, &statesWithProbability1); - - delete leftStates; - delete rightStates; - - LOG4CPLUS_INFO(logger, "Found " << statesWithProbability0.getNumberOfSetBits() << " 'no' states."); - LOG4CPLUS_INFO(logger, "Found " << statesWithProbability1.getNumberOfSetBits() << " 'yes' states."); - storm::storage::BitVector maybeStates = ~(statesWithProbability0 | statesWithProbability1); - LOG4CPLUS_INFO(logger, "Found " << maybeStates.getNumberOfSetBits() << " 'maybe' states."); - - // Create resulting vector and set values accordingly. - uint_fast64_t stateCount = this->getModel().getNumberOfStates(); - std::vector* result = new std::vector(stateCount); - - // Only try to solve system if there are states for which the probability is unknown. - if (maybeStates.getNumberOfSetBits() > 0) { - typedef Eigen::Matrix VectorType; - typedef Eigen::Map MapType; - - // Now we can eliminate the rows and columns from the original transition probability matrix. - storm::storage::SparseMatrix* submatrix = this->getModel().getTransitionProbabilityMatrix()->getSubmatrix(maybeStates); - // Converting the matrix to the form needed for the equation system. That is, we go from - // x = A*x + b to (I-A)x = b. - submatrix->convertToEquationSystem(); - - // Transform the submatric matrix to the eigen format to use its solvers - Eigen::SparseMatrix* eigenSubMatrix = storm::adapters::EigenAdapter::toEigenSparseMatrix(submatrix); - delete submatrix; - - // Initialize the x vector with 0.5 for each element. This is the initial guess for - // the iterative solvers. It should be safe as for all 'maybe' states we know that the - // probability is strictly larger than 0. - std::vector x(maybeStates.getNumberOfSetBits(), Type(0.5)); - - // Map for x - Type *px = &(x[0]); // get the address storing the data for x - MapType vectorX(px, x.size()); // vectorX shares data - - - // Prepare the right-hand side of the equation system. For entry i this corresponds to - // the accumulated probability of going from state i to some 'yes' state. - std::vector b(maybeStates.getNumberOfSetBits()); - - Type *pb = &(b[0]); // get the address storing the data for b - MapType vectorB(pb, b.size()); // vectorB shares data - - this->getModel().getTransitionProbabilityMatrix()->getConstrainedRowCountVector(maybeStates, statesWithProbability1, &x); - - Eigen::BiCGSTAB> solver; - solver.compute(*eigenSubMatrix); - if(solver.info()!= Eigen::ComputationInfo::Success) { - // decomposition failed - LOG4CPLUS_ERROR(logger, "Decomposition of Submatrix failed!"); - } - - // Now do the actual solving. - LOG4CPLUS_INFO(logger, "Starting iterative solver."); - - solver.setTolerance(0.000001); - - vectorX = solver.solveWithGuess(vectorB, vectorX); - - if(solver.info() == Eigen::ComputationInfo::InvalidInput) { - // solving failed - LOG4CPLUS_ERROR(logger, "Solving of Submatrix failed: InvalidInput"); - } else if(solver.info() == Eigen::ComputationInfo::NoConvergence) { - // NoConvergence - throw storm::exceptions::NoConvergenceException("Solving of Submatrix with Eigen failed", solver.iterations(), solver.maxIterations()); - } else if(solver.info() == Eigen::ComputationInfo::NumericalIssue) { - // NumericalIssue - LOG4CPLUS_ERROR(logger, "Solving of Submatrix failed: NumericalIssue"); - } else if(solver.info() == Eigen::ComputationInfo::Success) { - // solving Success - LOG4CPLUS_INFO(logger, "Solving of Submatrix succeeded: Success"); - } - - // Set values of resulting vector according to result. - storm::utility::setVectorValues(result, maybeStates, x); - - // Delete temporary matrix. - delete eigenSubMatrix; + /*! + * Solves the linear equation system Ax=b with the given parameters. + * + * @param A The matrix A specifying the coefficients of the linear equations. + * @param x The vector x for which to solve the equations. The initial value of the elements of + * this vector are used as the initial guess and might thus influence performance and convergence. + * @param b The vector b specifying the values on the right-hand-sides of the equations. + * @return The solution of the system of linear equations in form of the elements of the vector + * x. + */ + virtual void solveEquationSystem(storm::storage::SparseMatrix const& matrix, std::vector** vector, std::vector& b) const { + // Get the settings object to customize linear solving. + storm::settings::Settings* s = storm::settings::instance(); + + // Transform the submatric matrix to the eigen format to use its solvers + Eigen::SparseMatrix* eigenMatrix = storm::adapters::EigenAdapter::toEigenSparseMatrix(matrix); + + Eigen::BiCGSTAB> solver; + solver.compute(*eigenMatrix); + if(solver.info()!= Eigen::ComputationInfo::Success) { + // decomposition failed + LOG4CPLUS_ERROR(logger, "Decomposition of matrix failed!"); + } + solver.setMaxIterations(s->get("maxiter")); + solver.setTolerance(s->get("precision")); + + std::cout << matrix.toString(nullptr) << std::endl; + std::cout << **vector << std::endl; + std::cout << b << std::endl; + + std::cout << *eigenMatrix << std::endl; + + // Map for x + Type *px = &((**vector)[0]); // get the address storing the data for x + MapType vectorX(px, (*vector)->size()); // vectorX shares data + + Type *pb = &(b[0]); // get the address storing the data for b + MapType vectorB(pb, b.size()); // vectorB shares data + + vectorX = solver.solveWithGuess(vectorB, vectorX); + + std::cout << **vector << std::endl; + + if(solver.info() == Eigen::ComputationInfo::InvalidInput) { + // solving failed + LOG4CPLUS_ERROR(logger, "Solving of Submatrix failed: InvalidInput"); + } else if(solver.info() == Eigen::ComputationInfo::NoConvergence) { + // NoConvergence + throw storm::exceptions::NoConvergenceException() << "Failed to converge within " << solver.iterations() << " out of a maximum of " << solver.maxIterations() << " iterations."; + } else if(solver.info() == Eigen::ComputationInfo::NumericalIssue) { + // NumericalIssue + LOG4CPLUS_ERROR(logger, "Solving of Submatrix failed: NumericalIssue"); + } else if(solver.info() == Eigen::ComputationInfo::Success) { + // solving Success + LOG4CPLUS_INFO(logger, "Solving of Submatrix succeeded: Success"); } - storm::utility::setVectorValues(result, statesWithProbability0, storm::utility::constGetZero()); - storm::utility::setVectorValues(result, statesWithProbability1, storm::utility::constGetOne()); - - return result; + delete eigenMatrix; } }; diff --git a/src/modelchecker/GmmxxDtmcPrctlModelChecker.h b/src/modelchecker/GmmxxDtmcPrctlModelChecker.h index d4aeef764..3c912a3c3 100644 --- a/src/modelchecker/GmmxxDtmcPrctlModelChecker.h +++ b/src/modelchecker/GmmxxDtmcPrctlModelChecker.h @@ -12,7 +12,6 @@ #include "src/models/Dtmc.h" #include "src/modelchecker/DtmcPrctlModelChecker.h" -#include "src/utility/GraphAnalyzer.h" #include "src/utility/Vector.h" #include "src/utility/ConstTemplates.h" #include "src/utility/Settings.h" @@ -40,296 +39,12 @@ class GmmxxDtmcPrctlModelChecker : public DtmcPrctlModelChecker { public: - explicit GmmxxDtmcPrctlModelChecker(storm::models::Dtmc& dtmc) - : DtmcPrctlModelChecker(dtmc) { + explicit GmmxxDtmcPrctlModelChecker(storm::models::Dtmc& dtmc) : DtmcPrctlModelChecker(dtmc) { // Intentionally left empty. } - virtual ~GmmxxDtmcPrctlModelChecker() { } - - virtual std::vector* checkBoundedUntil(const storm::formula::BoundedUntil& formula, bool qualitative) const { - // First, we need to compute the states that satisfy the sub-formulas of the until-formula. - storm::storage::BitVector* leftStates = formula.getLeft().check(*this); - storm::storage::BitVector* rightStates = formula.getRight().check(*this); - - // Copy the matrix before we make any changes. - storm::storage::SparseMatrix tmpMatrix(*this->getModel().getTransitionMatrix()); - - // Make all rows absorbing that violate both sub-formulas or satisfy the second sub-formula. - tmpMatrix.makeRowsAbsorbing(~(*leftStates | *rightStates) | *rightStates); - - // Transform the transition probability matrix to the gmm++ format to use its arithmetic. - gmm::csr_matrix* gmmxxMatrix = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(tmpMatrix); - - // Create the vector with which to multiply. - std::vector* result = new std::vector(this->getModel().getNumberOfStates()); - storm::utility::setVectorValues(result, *rightStates, storm::utility::constGetOne()); - - // Now perform matrix-vector multiplication as long as we meet the bound of the formula. - std::vector* swap = nullptr; - std::vector* tmpResult = new std::vector(this->getModel().getNumberOfStates()); - for (uint_fast64_t i = 0; i < formula.getBound(); ++i) { - gmm::mult(*gmmxxMatrix, *result, *tmpResult); - swap = tmpResult; - tmpResult = result; - result = swap; - } - delete tmpResult; - - // Delete intermediate results and return result. - delete gmmxxMatrix; - delete leftStates; - delete rightStates; - return result; - } - - virtual std::vector* checkNext(const storm::formula::Next& formula, bool qualitative) const { - // First, we need to compute the states that satisfy the sub-formula of the next-formula. - storm::storage::BitVector* nextStates = formula.getChild().check(*this); - - // Transform the transition probability matrix to the gmm++ format to use its arithmetic. - gmm::csr_matrix* gmmxxMatrix = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(*this->getModel().getTransitionMatrix()); - - // Create the vector with which to multiply and initialize it correctly. - std::vector x(this->getModel().getNumberOfStates()); - storm::utility::setVectorValues(&x, *nextStates, storm::utility::constGetOne()); - - // Delete obsolete sub-result. - delete nextStates; - - // Create resulting vector. - std::vector* result = new std::vector(this->getModel().getNumberOfStates()); - - // Perform the actual computation, namely matrix-vector multiplication. - gmm::mult(*gmmxxMatrix, x, *result); - - // Delete temporary matrix and return result. - delete gmmxxMatrix; - return result; - } - - virtual std::vector* checkUntil(const storm::formula::Until& formula, bool qualitative) const { - // First, we need to compute the states that satisfy the sub-formulas of the until-formula. - storm::storage::BitVector* leftStates = formula.getLeft().check(*this); - storm::storage::BitVector* rightStates = formula.getRight().check(*this); - - // Then, we need to identify the states which have to be taken out of the matrix, i.e. - // all states that have probability 0 and 1 of satisfying the until-formula. - storm::storage::BitVector statesWithProbability0(this->getModel().getNumberOfStates()); - storm::storage::BitVector statesWithProbability1(this->getModel().getNumberOfStates()); - storm::utility::GraphAnalyzer::performProb01(this->getModel(), *leftStates, *rightStates, &statesWithProbability0, &statesWithProbability1); - - // Delete sub-results that are obsolete now. - delete leftStates; - delete rightStates; - - LOG4CPLUS_INFO(logger, "Found " << statesWithProbability0.getNumberOfSetBits() << " 'no' states."); - LOG4CPLUS_INFO(logger, "Found " << statesWithProbability1.getNumberOfSetBits() << " 'yes' states."); - storm::storage::BitVector maybeStates = ~(statesWithProbability0 | statesWithProbability1); - LOG4CPLUS_INFO(logger, "Found " << maybeStates.getNumberOfSetBits() << " 'maybe' states."); - - // Create resulting vector. - std::vector* result = new std::vector(this->getModel().getNumberOfStates()); - - // Only try to solve system if there are states for which the probability is unknown. - uint_fast64_t mayBeStatesSetBitCount = maybeStates.getNumberOfSetBits(); - if (mayBeStatesSetBitCount > 0 && !qualitative) { - // Now we can eliminate the rows and columns from the original transition probability matrix. - storm::storage::SparseMatrix* submatrix = this->getModel().getTransitionMatrix()->getSubmatrix(maybeStates); - // Converting the matrix from the fixpoint notation to the form needed for the equation - // system. That is, we go from x = A*x + b to (I-A)x = b. - submatrix->convertToEquationSystem(); - - // Transform the submatrix to the gmm++ format to use its solvers. - gmm::csr_matrix* gmmxxMatrix = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(*submatrix); - delete submatrix; - - // Initialize the x vector with 0.5 for each element. This is the initial guess for - // the iterative solvers. It should be safe as for all 'maybe' states we know that the - // probability is strictly larger than 0. - std::vector x(mayBeStatesSetBitCount, Type(0.5)); - - // Prepare the right-hand side of the equation system. For entry i this corresponds to - // the accumulated probability of going from state i to some 'yes' state. - std::vector b(mayBeStatesSetBitCount); - this->getModel().getTransitionMatrix()->getConstrainedRowSumVector(maybeStates, statesWithProbability1, &b); - - // Solve the corresponding system of linear equations. - this->solveLinearEquationSystem(*gmmxxMatrix, x, b); - - // Set values of resulting vector according to result. - storm::utility::setVectorValues(result, maybeStates, x); - - // Delete temporary matrix. - delete gmmxxMatrix; - } else if (qualitative) { - // If we only need a qualitative result, we can safely assume that the results will only be compared to - // bounds which are either 0 or 1. Setting the value to 0.5 is thus safe. - storm::utility::setVectorValues(result, maybeStates, Type(0.5)); - } - - // Set values of resulting vector that are known exactly. - storm::utility::setVectorValues(result, statesWithProbability0, storm::utility::constGetZero()); - storm::utility::setVectorValues(result, statesWithProbability1, storm::utility::constGetOne()); - - return result; - } - - virtual std::vector* checkInstantaneousReward(const storm::formula::InstantaneousReward& formula, bool qualitative) const { - // Only compute the result if the model has a state-based reward model. - if (!this->getModel().hasStateRewards()) { - LOG4CPLUS_ERROR(logger, "Missing (state-based) reward model for formula."); - throw storm::exceptions::InvalidPropertyException() << "Missing (state-based) reward model for formula."; - } - - // Transform the transition probability matrix to the gmm++ format to use its arithmetic. - gmm::csr_matrix* gmmxxMatrix = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(*this->getModel().getTransitionMatrix()); - - // Initialize result to state rewards of the model. - std::vector* result = new std::vector(*this->getModel().getStateRewardVector()); - - // Now perform matrix-vector multiplication as long as we meet the bound of the formula. - std::vector* swap = nullptr; - std::vector* tmpResult = new std::vector(this->getModel().getNumberOfStates()); - for (uint_fast64_t i = 0; i < formula.getBound(); ++i) { - gmm::mult(*gmmxxMatrix, *result, *tmpResult); - swap = tmpResult; - tmpResult = result; - result = swap; - } - - // Delete temporary variables and return result. - delete tmpResult; - delete gmmxxMatrix; - return result; - } - - virtual std::vector* checkCumulativeReward(const storm::formula::CumulativeReward& formula, bool qualitative) const { - // Only compute the result if the model has at least one reward model. - if (!this->getModel().hasStateRewards() && !this->getModel().hasTransitionRewards()) { - LOG4CPLUS_ERROR(logger, "Missing reward model for formula."); - throw storm::exceptions::InvalidPropertyException() << "Missing reward model for formula."; - } - - // Transform the transition probability matrix to the gmm++ format to use its arithmetic. - gmm::csr_matrix* gmmxxMatrix = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(*this->getModel().getTransitionMatrix()); - - // Compute the reward vector to add in each step based on the available reward models. - std::vector* totalRewardVector = nullptr; - if (this->getModel().hasTransitionRewards()) { - totalRewardVector = this->getModel().getTransitionMatrix()->getPointwiseProductRowSumVector(*this->getModel().getTransitionRewardMatrix()); - if (this->getModel().hasStateRewards()) { - gmm::add(*this->getModel().getStateRewardVector(), *totalRewardVector); - } - } else { - totalRewardVector = new std::vector(*this->getModel().getStateRewardVector()); - } - - std::vector* result = new std::vector(this->getModel().getNumberOfStates()); - - // Now perform matrix-vector multiplication as long as we meet the bound of the formula. - std::vector* swap = nullptr; - std::vector* tmpResult = new std::vector(this->getModel().getNumberOfStates()); - for (uint_fast64_t i = 0; i < formula.getBound(); ++i) { - gmm::mult(*gmmxxMatrix, *result, *tmpResult); - swap = tmpResult; - tmpResult = result; - result = swap; - - // Add the reward vector to the result. - gmm::add(*totalRewardVector, *result); - } - - // Delete temporary variables and return result. - delete tmpResult; - delete gmmxxMatrix; - delete totalRewardVector; - return result; - } - - virtual std::vector* checkReachabilityReward(const storm::formula::ReachabilityReward& formula, bool qualitative) const { - // Only compute the result if the model has at least one reward model. - if (!this->getModel().hasStateRewards() && !this->getModel().hasTransitionRewards()) { - LOG4CPLUS_ERROR(logger, "Missing reward model for formula. Skipping formula"); - throw storm::exceptions::InvalidPropertyException() << "Missing reward model for formula."; - } - - // Determine the states for which the target predicate holds. - storm::storage::BitVector* targetStates = formula.getChild().check(*this); - - // Determine which states have a reward of infinity by definition. - storm::storage::BitVector infinityStates(this->getModel().getNumberOfStates()); - storm::storage::BitVector trueStates(this->getModel().getNumberOfStates(), true); - storm::utility::GraphAnalyzer::performProb1(this->getModel(), trueStates, *targetStates, &infinityStates); - infinityStates.complement(); - - // Create resulting vector. - std::vector* result = new std::vector(this->getModel().getNumberOfStates()); - - // Check whether there are states for which we have to compute the result. - storm::storage::BitVector maybeStates = ~(*targetStates) & ~infinityStates; - const int maybeStatesSetBitCount = maybeStates.getNumberOfSetBits(); - if (maybeStatesSetBitCount > 0) { - // Now we can eliminate the rows and columns from the original transition probability matrix. - storm::storage::SparseMatrix* submatrix = this->getModel().getTransitionMatrix()->getSubmatrix(maybeStates); - // Converting the matrix from the fixpoint notation to the form needed for the equation - // system. That is, we go from x = A*x + b to (I-A)x = b. - submatrix->convertToEquationSystem(); - - // Transform the submatrix to the gmm++ format to use its solvers. - gmm::csr_matrix* gmmxxMatrix = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(*submatrix); - delete submatrix; - - // Initialize the x vector with 1 for each element. This is the initial guess for - // the iterative solvers. - std::vector x(maybeStatesSetBitCount, storm::utility::constGetOne()); - - // Prepare the right-hand side of the equation system. - std::vector* b = new std::vector(maybeStatesSetBitCount); - if (this->getModel().hasTransitionRewards()) { - // If a transition-based reward model is available, we initialize the right-hand - // side to the vector resulting from summing the rows of the pointwise product - // of the transition probability matrix and the transition reward matrix. - std::vector* pointwiseProductRowSumVector = this->getModel().getTransitionMatrix()->getPointwiseProductRowSumVector(*this->getModel().getTransitionRewardMatrix()); - storm::utility::selectVectorValues(b, maybeStates, *pointwiseProductRowSumVector); - delete pointwiseProductRowSumVector; - - if (this->getModel().hasStateRewards()) { - // If a state-based reward model is also available, we need to add this vector - // as well. As the state reward vector contains entries not just for the states - // that we still consider (i.e. maybeStates), we need to extract these values - // first. - std::vector* subStateRewards = new std::vector(maybeStatesSetBitCount); - storm::utility::setVectorValues(subStateRewards, maybeStates, *this->getModel().getStateRewardVector()); - gmm::add(*subStateRewards, *b); - delete subStateRewards; - } - } else { - // If only a state-based reward model is available, we take this vector as the - // right-hand side. As the state reward vector contains entries not just for the - // states that we still consider (i.e. maybeStates), we need to extract these values - // first. - storm::utility::setVectorValues(b, maybeStates, *this->getModel().getStateRewardVector()); - } - - // Solve the corresponding system of linear equations. - this->solveLinearEquationSystem(*gmmxxMatrix, x, *b); - - // Set values of resulting vector according to result. - storm::utility::setVectorValues(result, maybeStates, x); - - // Delete temporary matrix and right-hand side. - delete gmmxxMatrix; - delete b; - } - - // Set values of resulting vector that are known exactly. - storm::utility::setVectorValues(result, *targetStates, storm::utility::constGetZero()); - storm::utility::setVectorValues(result, infinityStates, storm::utility::constGetInfinity()); - - // Delete temporary storages and return result. - delete targetStates; - return result; + virtual ~GmmxxDtmcPrctlModelChecker() { + // Intentionally left empty } /*! @@ -353,7 +68,7 @@ public: * Registers all options associated with the gmm++ matrix library. */ static void putOptions(boost::program_options::options_description* desc) { - desc->add_options()("lemethod", boost::program_options::value()->default_value("bicgstab")->notifier(&validateLeMethod), "Sets the method used for linear equation solving. Must be in {bicgstab, qmr}."); + desc->add_options()("lemethod", boost::program_options::value()->default_value("bicgstab")->notifier(&validateLeMethod), "Sets the method used for linear equation solving. Must be in {bicgstab, qmr, jacobi}."); desc->add_options()("maxiter", boost::program_options::value()->default_value(10000), "Sets the maximal number of iterations for iterative equation solving."); desc->add_options()("precision", boost::program_options::value()->default_value(1e-6), "Sets the precision for iterative equation solving."); desc->add_options()("precond", boost::program_options::value()->default_value("ilu")->notifier(&validatePreconditioner), "Sets the preconditioning technique for linear equation solving. Must be in {ilu, diagonal, ildlt, none}."); @@ -365,7 +80,7 @@ public: * Throws an exception of type InvalidSettings in case the selected method is illegal. */ static void validateLeMethod(const std::string& lemethod) { - if ((lemethod != "bicgstab") && (lemethod != "qmr")) { + if ((lemethod != "bicgstab") && (lemethod != "qmr") && (lemethod != "jacobi")) { throw exceptions::InvalidSettingsException() << "Argument " << lemethod << " for option 'lemethod' is invalid."; } } @@ -381,6 +96,37 @@ public: } private: + + virtual void performMatrixVectorMultiplication(storm::storage::SparseMatrix const& matrix, std::vector& vector, std::vector* summand, uint_fast64_t repetitions = 1) const { + // Transform the transition probability matrix to the gmm++ format to use its arithmetic. + gmm::csr_matrix* gmmxxMatrix = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(matrix); + + // Now perform matrix-vector multiplication as long as we meet the bound. + std::vector* swap = nullptr; + std::vector* currentVector = &vector; + std::vector* tmpVector = new std::vector(this->getModel().getNumberOfStates()); + for (uint_fast64_t i = 0; i < repetitions; ++i) { + gmm::mult(*gmmxxMatrix, *currentVector, *tmpVector); + swap = tmpVector; + tmpVector = currentVector; + currentVector = swap; + + // If requested, add an offset to the current result vector. + if (summand != nullptr) { + gmm::add(*summand, *currentVector); + } + } + + if (repetitions % 2 == 1) { + std::swap(vector, *currentVector); + delete currentVector; + } else { + delete tmpVector; + } + + delete gmmxxMatrix; + } + /*! * Solves the linear equation system Ax=b with the given parameters. * @@ -391,7 +137,7 @@ private: * @return The solution of the system of linear equations in form of the elements of the vector * x. */ - void solveLinearEquationSystem(gmm::csr_matrix const& A, std::vector& x, std::vector const& b) const { + virtual void solveEquationSystem(storm::storage::SparseMatrix const& matrix, std::vector& vector, std::vector const& b) const { // Get the settings object to customize linear solving. storm::settings::Settings* s = storm::settings::instance(); @@ -414,45 +160,49 @@ private: if (s->getString("lemethod") == "bicgstab") { LOG4CPLUS_INFO(logger, "Using BiCGStab method."); + // Transform the transition probability matrix to the gmm++ format to use its arithmetic. + gmm::csr_matrix* A = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(matrix); if (precond == "ilu") { - gmm::bicgstab(A, x, b, gmm::ilu_precond>(A), iter); + gmm::bicgstab(*A, vector, b, gmm::ilu_precond>(*A), iter); } else if (precond == "diagonal") { - gmm::bicgstab(A, x, b, gmm::diagonal_precond>(A), iter); + gmm::bicgstab(*A, vector, b, gmm::diagonal_precond>(*A), iter); } else if (precond == "ildlt") { - gmm::bicgstab(A, x, b, gmm::ildlt_precond>(A), iter); + gmm::bicgstab(*A, vector, b, gmm::ildlt_precond>(*A), iter); } else if (precond == "none") { - gmm::bicgstab(A, x, b, gmm::identity_matrix(), iter); + gmm::bicgstab(*A, vector, b, gmm::identity_matrix(), iter); } - // FIXME: gmres has been disabled, because it triggers gmm++ compilation errors - /* } else if (s->getString("lemethod").compare("gmres") == 0) { - LOG4CPLUS_INFO(logger, "Using GMRES method."); - if (precond.compare("ilu")) { - gmm::gmres(A, x, b, gmm::ilu_precond>(A), s->get("restart"), iter); - } else if (precond == "diagonal") { - gmm::gmres(A, x, b, gmm::diagonal_precond>(A), s->get("restart"), iter); - } else if (precond == "ildlt") { - gmm::gmres(A, x, b, gmm::ildlt_precond>(A), s->get("restart"), iter); - } else if (precond == "none") { - gmm::gmres(A, x, b, gmm::identity_matrix(), s->get("restart"), iter); - } */ + + // Check if the solver converged and issue a warning otherwise. + if (iter.converged()) { + LOG4CPLUS_INFO(logger, "Iterative solver converged after " << iter.get_iteration() << " iterations."); + } else { + LOG4CPLUS_WARN(logger, "Iterative solver did not converge."); + } + delete A; } else if (s->getString("lemethod") == "qmr") { LOG4CPLUS_INFO(logger, "Using QMR method."); + // Transform the transition probability matrix to the gmm++ format to use its arithmetic. + gmm::csr_matrix* A = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(matrix); if (precond == "ilu") { - gmm::qmr(A, x, b, gmm::ilu_precond>(A), iter); + gmm::qmr(*A, vector, b, gmm::ilu_precond>(*A), iter); } else if (precond == "diagonal") { - gmm::qmr(A, x, b, gmm::diagonal_precond>(A), iter); + gmm::qmr(*A, vector, b, gmm::diagonal_precond>(*A), iter); } else if (precond == "ildlt") { - gmm::qmr(A, x, b, gmm::ildlt_precond>(A), iter); + gmm::qmr(*A, vector, b, gmm::ildlt_precond>(*A), iter); } else if (precond == "none") { - gmm::qmr(A, x, b, gmm::identity_matrix(), iter); + gmm::qmr(*A, vector, b, gmm::identity_matrix(), iter); } - } - // Check if the solver converged and issue a warning otherwise. - if (iter.converged()) { - LOG4CPLUS_INFO(logger, "Iterative solver converged after " << iter.get_iteration() << " iterations."); - } else { - LOG4CPLUS_WARN(logger, "Iterative solver did not converge."); + // Check if the solver converged and issue a warning otherwise. + if (iter.converged()) { + LOG4CPLUS_INFO(logger, "Iterative solver converged after " << iter.get_iteration() << " iterations."); + } else { + LOG4CPLUS_WARN(logger, "Iterative solver did not converge."); + } + delete A; + } else if (s->getString("lemethod") == "jacobi") { + LOG4CPLUS_INFO(logger, "Using Jacobi method."); + solveLinearEquationSystemWithJacobi(matrix, vector, b); } } @@ -467,7 +217,7 @@ private: * @return The solution of the system of linear equations in form of the elements of the vector * x. */ - void solveLinearEquationSystemWithJacobi(storm::storage::SparseMatrix const& A, std::vector& x, std::vector const& b) const { + void solveLinearEquationSystemWithJacobi(storm::storage::SparseMatrix const& matrix, std::vector& x, std::vector const& b) const { // Get the settings object to customize linear solving. storm::settings::Settings* s = storm::settings::instance(); @@ -475,41 +225,65 @@ private: if (precision <= 0) { LOG4CPLUS_ERROR(logger, "Selected precision for linear equation solving must be strictly greater than zero for Jacobi method."); } - - // Get a Jacobi Decomposition of the Input Matrix A - storm::storage::JacobiDecomposition* jacobiDecomposition = A.getJacobiDecomposition(); + + // Get a Jacobi Decomposition of the Input Matrix + storm::storage::JacobiDecomposition* jacobiDecomposition = matrix.getJacobiDecomposition(); + + // Convert the Input Matrix to GMM Format so we can calculate the Residuum + gmm::csr_matrix* A = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(matrix); // Convert the Diagonal matrix to GMM format - gmm::csr_matrix* gmmxxDiagonalInverted = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(jacobiDecomposition->getJacobiDInv()); + gmm::csr_matrix* gmmxxDiagonalInverted = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(jacobiDecomposition->getJacobiDInvReference()); // Convert the LU Matrix to GMM format - gmm::csr_matrix* gmmxxLU = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(jacobiDecomposition->getJacobiLU()); + gmm::csr_matrix* gmmxxLU = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(jacobiDecomposition->getJacobiLUReference()); - delete jacobiDecomposition; LOG4CPLUS_INFO(logger, "Starting iterative Jacobi Solver."); // x_(k + 1) = D^-1 * (b - R * x_k) + // In x we keep a copy of the result for swapping in the loop (e.g. less copy-back) std::vector* xNext = new std::vector(x.size()); const std::vector* xCopy = xNext; std::vector* xCurrent = &x; + // Target vector for precision calculation + std::vector* residuum = new std::vector(x.size()); + uint_fast64_t iterationCount = 0; do { - // R * x_k -> xCurrent + // R * x_k (xCurrent is x_k) -> xNext gmm::mult(*gmmxxLU, *xCurrent, *xNext); - // b - R * x_k + // b - R * x_k (xNext contains R * x_k) -> xNext gmm::add(b, gmm::scaled(*xNext, -1.0), *xNext); - // D^-1 * (b - R * x_k) + // D^-1 * (b - R * x_k) -> xNext gmm::mult(*gmmxxDiagonalInverted, *xNext, *xNext); - std::vector* swap = xNext; + // swap xNext with xCurrent so that the next iteration can use xCurrent again without having to copy the vector + std::vector *const swap = xNext; xNext = xCurrent; xCurrent = swap; ++iterationCount; - } while (gmm::vect_norminf(*xCurrent) > precision); + // Precision calculation via ||A * x_k - b|| < precision + gmm::mult(*A, *xCurrent, *residuum); + gmm::add(gmm::scaled(*residuum, -1.0), b, *residuum); + } while (gmm::vect_norminf(*residuum) > precision); + + // If the last iteration did not write to the original x + // we have to swith them + if (xCurrent == xCopy) { + x.swap(*xCurrent); + } + // xCopy always points to the Swap-Copy of x we created delete xCopy; + // Delete the residuum vector + delete residuum; + // Delete the decompositions + delete jacobiDecomposition; + // and the GMM Matrices + delete gmmxxDiagonalInverted; + delete gmmxxLU; LOG4CPLUS_INFO(logger, "Iterative solver converged after " << iterationCount << " iterations."); } diff --git a/src/modelchecker/GmmxxMdpPrctlModelChecker.h b/src/modelchecker/GmmxxMdpPrctlModelChecker.h index e22da7263..a0c2cae85 100644 --- a/src/modelchecker/GmmxxMdpPrctlModelChecker.h +++ b/src/modelchecker/GmmxxMdpPrctlModelChecker.h @@ -43,313 +43,37 @@ public: virtual ~GmmxxMdpPrctlModelChecker() { } - virtual std::vector* checkBoundedUntil(const storm::formula::BoundedUntil& formula, bool qualitative) const { - // First, we need to compute the states that satisfy the sub-formulas of the until-formula. - storm::storage::BitVector* leftStates = formula.getLeft().check(*this); - storm::storage::BitVector* rightStates = formula.getRight().check(*this); - - // Copy the matrix before we make any changes. - storm::storage::SparseMatrix tmpMatrix(*this->getModel().getTransitionMatrix()); - +private: + virtual void performMatrixVectorMultiplication(storm::storage::SparseMatrix const& matrix, std::vector& vector, std::vector* summand = nullptr, uint_fast64_t repetitions = 1) const { // Get the starting row indices for the non-deterministic choices to reduce the resulting // vector properly. std::shared_ptr> nondeterministicChoiceIndices = this->getModel().getNondeterministicChoiceIndices(); - // Make all rows absorbing that violate both sub-formulas or satisfy the second sub-formula. - tmpMatrix.makeRowsAbsorbing(~(*leftStates | *rightStates) | *rightStates, *nondeterministicChoiceIndices); // Transform the transition probability matrix to the gmm++ format to use its arithmetic. - gmm::csr_matrix* gmmxxMatrix = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(tmpMatrix); - - // Create the vector with which to multiply. - std::vector* result = new std::vector(this->getModel().getNumberOfStates()); - storm::utility::setVectorValues(result, *rightStates, storm::utility::constGetOne()); + gmm::csr_matrix* gmmxxMatrix = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(matrix); // Create vector for result of multiplication, which is reduced to the result vector after // each multiplication. - std::vector* multiplyResult = new std::vector(this->getModel().getTransitionMatrix()->getRowCount(), 0); + std::vector multiplyResult(matrix.getRowCount()); // Now perform matrix-vector multiplication as long as we meet the bound of the formula. - for (uint_fast64_t i = 0; i < formula.getBound(); ++i) { - gmm::mult(*gmmxxMatrix, *result, *multiplyResult); + for (uint_fast64_t i = 0; i < repetitions; ++i) { + gmm::mult(*gmmxxMatrix, vector, multiplyResult); + if (summand != nullptr) { + gmm::add(*summand, multiplyResult); + } if (this->minimumOperatorStack.top()) { - storm::utility::reduceVectorMin(*multiplyResult, result, *nondeterministicChoiceIndices); + storm::utility::reduceVectorMin(multiplyResult, &vector, *nondeterministicChoiceIndices); } else { - storm::utility::reduceVectorMax(*multiplyResult, result, *nondeterministicChoiceIndices); + storm::utility::reduceVectorMax(multiplyResult, &vector, *nondeterministicChoiceIndices); } } - delete multiplyResult; // Delete intermediate results and return result. delete gmmxxMatrix; - delete leftStates; - delete rightStates; - return result; - } - - virtual std::vector* checkNext(const storm::formula::Next& formula, bool qualitative) const { - // First, we need to compute the states that satisfy the sub-formula of the next-formula. - storm::storage::BitVector* nextStates = formula.getChild().check(*this); - - // Transform the transition probability matrix to the gmm++ format to use its arithmetic. - gmm::csr_matrix* gmmxxMatrix = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(*this->getModel().getTransitionMatrix()); - - // Create the vector with which to multiply and initialize it correctly. - std::vector* result = new std::vector(this->getModel().getNumberOfStates()); - storm::utility::setVectorValues(result, *nextStates, storm::utility::constGetOne()); - - // Delete obsolete sub-result. - delete nextStates; - - // Create resulting vector. - std::vector* temporaryResult = new std::vector(this->getModel().getTransitionMatrix()->getRowCount()); - - // Perform the actual computation, namely matrix-vector multiplication. - gmm::mult(*gmmxxMatrix, *result, *temporaryResult); - - // Get the starting row indices for the non-deterministic choices to reduce the resulting - // vector properly. - std::shared_ptr> nondeterministicChoiceIndices = this->getModel().getNondeterministicChoiceIndices(); - - if (this->minimumOperatorStack.top()) { - storm::utility::reduceVectorMin(*temporaryResult, result, *nondeterministicChoiceIndices); - } else { - storm::utility::reduceVectorMax(*temporaryResult, result, *nondeterministicChoiceIndices); - } - - // Delete temporary matrix plus temporary result and return result. - delete gmmxxMatrix; - delete temporaryResult; - return result; - } - - virtual std::vector* checkUntil(const storm::formula::Until& formula, bool qualitative) const { - // First, we need to compute the states that satisfy the sub-formulas of the until-formula. - storm::storage::BitVector* leftStates = formula.getLeft().check(*this); - storm::storage::BitVector* rightStates = formula.getRight().check(*this); - - // Then, we need to identify the states which have to be taken out of the matrix, i.e. - // all states that have probability 0 and 1 of satisfying the until-formula. - storm::storage::BitVector statesWithProbability0(this->getModel().getNumberOfStates()); - storm::storage::BitVector statesWithProbability1(this->getModel().getNumberOfStates()); - if (this->minimumOperatorStack.top()) { - storm::utility::GraphAnalyzer::performProb01Min(this->getModel(), *leftStates, *rightStates, &statesWithProbability0, &statesWithProbability1); - } else { - storm::utility::GraphAnalyzer::performProb01Max(this->getModel(), *leftStates, *rightStates, &statesWithProbability0, &statesWithProbability1); - } - - // Delete sub-results that are obsolete now. - delete leftStates; - delete rightStates; - - LOG4CPLUS_INFO(logger, "Found " << statesWithProbability0.getNumberOfSetBits() << " 'no' states."); - LOG4CPLUS_INFO(logger, "Found " << statesWithProbability1.getNumberOfSetBits() << " 'yes' states."); - storm::storage::BitVector maybeStates = ~(statesWithProbability0 | statesWithProbability1); - LOG4CPLUS_INFO(logger, "Found " << maybeStates.getNumberOfSetBits() << " 'maybe' states."); - - // Create resulting vector. - std::vector* result = new std::vector(this->getModel().getNumberOfStates()); - - // Only try to solve system if there are states for which the probability is unknown. - uint_fast64_t maybeStatesSetBitCount = maybeStates.getNumberOfSetBits(); - if (maybeStatesSetBitCount > 0) { - // First, we can eliminate the rows and columns from the original transition probability matrix for states - // whose probabilities are already known. - storm::storage::SparseMatrix* submatrix = this->getModel().getTransitionMatrix()->getSubmatrix(maybeStates, *this->getModel().getNondeterministicChoiceIndices()); - - // Get the "new" nondeterministic choice indices for the submatrix. - std::shared_ptr> subNondeterministicChoiceIndices = this->computeNondeterministicChoiceIndicesForConstraint(maybeStates); - - // Transform the submatrix to the gmm++ format to use its capabilities. - gmm::csr_matrix* gmmxxMatrix = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(*submatrix); - - // Create vector for results for maybe states. - std::vector* x = new std::vector(maybeStatesSetBitCount); - - // Prepare the right-hand side of the equation system. For entry i this corresponds to - // the accumulated probability of going from state i to some 'yes' state. - std::vector b(submatrix->getRowCount()); - this->getModel().getTransitionMatrix()->getConstrainedRowSumVector(maybeStates, *this->getModel().getNondeterministicChoiceIndices(), statesWithProbability1, &b); - delete submatrix; - - // Solve the corresponding system of equations. - this->solveEquationSystem(*gmmxxMatrix, x, b, *subNondeterministicChoiceIndices); - - // Set values of resulting vector according to result. - storm::utility::setVectorValues(result, maybeStates, *x); - - // Delete temporary matrix and vector. - delete gmmxxMatrix; - delete x; - } - - // Set values of resulting vector that are known exactly. - storm::utility::setVectorValues(result, statesWithProbability0, storm::utility::constGetZero()); - storm::utility::setVectorValues(result, statesWithProbability1, storm::utility::constGetOne()); - - return result; } - virtual std::vector* checkInstantaneousReward(const storm::formula::InstantaneousReward& formula, bool qualitative) const { - // Only compute the result if the model has a state-based reward model. - if (!this->getModel().hasStateRewards()) { - LOG4CPLUS_ERROR(logger, "Missing (state-based) reward model for formula."); - throw storm::exceptions::InvalidPropertyException() << "Missing (state-based) reward model for formula."; - } - - // Transform the transition probability matrix to the gmm++ format to use its arithmetic. - gmm::csr_matrix* gmmxxMatrix = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(*this->getModel().getTransitionMatrix()); - - // Initialize result to state rewards of the model. - std::vector* result = new std::vector(*this->getModel().getStateRewardVector()); - - // Now perform matrix-vector multiplication as long as we meet the bound of the formula. - std::vector* swap = nullptr; - std::vector* tmpResult = new std::vector(this->getModel().getNumberOfStates()); - for (uint_fast64_t i = 0; i < formula.getBound(); ++i) { - gmm::mult(*gmmxxMatrix, *result, *tmpResult); - swap = tmpResult; - tmpResult = result; - result = swap; - } - - // Delete temporary variables and return result. - delete tmpResult; - delete gmmxxMatrix; - return result; - } - - virtual std::vector* checkCumulativeReward(const storm::formula::CumulativeReward& formula, bool qualitative) const { - // Only compute the result if the model has at least one reward model. - if (!this->getModel().hasStateRewards() && !this->getModel().hasTransitionRewards()) { - LOG4CPLUS_ERROR(logger, "Missing reward model for formula."); - throw storm::exceptions::InvalidPropertyException() << "Missing reward model for formula."; - } - - // Transform the transition probability matrix to the gmm++ format to use its arithmetic. - gmm::csr_matrix* gmmxxMatrix = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(*this->getModel().getTransitionMatrix()); - - // Compute the reward vector to add in each step based on the available reward models. - std::vector* totalRewardVector = nullptr; - if (this->getModel().hasTransitionRewards()) { - totalRewardVector = this->getModel().getTransitionMatrix()->getPointwiseProductRowSumVector(*this->getModel().getTransitionRewardMatrix()); - if (this->getModel().hasStateRewards()) { - gmm::add(*this->getModel().getStateRewardVector(), *totalRewardVector); - } - } else { - totalRewardVector = new std::vector(*this->getModel().getStateRewardVector()); - } - - std::vector* result = new std::vector(this->getModel().getNumberOfStates()); - - // Now perform matrix-vector multiplication as long as we meet the bound of the formula. - std::vector* swap = nullptr; - std::vector* tmpResult = new std::vector(this->getModel().getNumberOfStates()); - for (uint_fast64_t i = 0; i < formula.getBound(); ++i) { - gmm::mult(*gmmxxMatrix, *result, *tmpResult); - swap = tmpResult; - tmpResult = result; - result = swap; - - // Add the reward vector to the result. - gmm::add(*totalRewardVector, *result); - } - - // Delete temporary variables and return result. - delete tmpResult; - delete gmmxxMatrix; - delete totalRewardVector; - return result; - } - - virtual std::vector* checkReachabilityReward(const storm::formula::ReachabilityReward& formula, bool qualitative) const { - // Only compute the result if the model has at least one reward model. - if (!this->getModel().hasStateRewards() && !this->getModel().hasTransitionRewards()) { - LOG4CPLUS_ERROR(logger, "Missing reward model for formula. Skipping formula"); - throw storm::exceptions::InvalidPropertyException() << "Missing reward model for formula."; - } - - // Determine the states for which the target predicate holds. - storm::storage::BitVector* targetStates = formula.getChild().check(*this); - - // Determine which states have a reward of infinity by definition. - storm::storage::BitVector infinityStates(this->getModel().getNumberOfStates()); - storm::storage::BitVector trueStates(this->getModel().getNumberOfStates(), true); - // TODO: just commented out to make it compile - //storm::utility::GraphAnalyzer::performProb1(this->getModel(), trueStates, *targetStates, &infinityStates); - infinityStates.complement(); - - // Create resulting vector. - std::vector* result = new std::vector(this->getModel().getNumberOfStates()); - - // Check whether there are states for which we have to compute the result. - storm::storage::BitVector maybeStates = ~(*targetStates) & ~infinityStates; - const int maybeStatesSetBitCount = maybeStates.getNumberOfSetBits(); - if (maybeStatesSetBitCount > 0) { - // Now we can eliminate the rows and columns from the original transition probability matrix. - storm::storage::SparseMatrix* submatrix = this->getModel().getTransitionMatrix()->getSubmatrix(maybeStates); - // Converting the matrix from the fixpoint notation to the form needed for the equation - // system. That is, we go from x = A*x + b to (I-A)x = b. - submatrix->convertToEquationSystem(); - - // Transform the submatrix to the gmm++ format to use its solvers. - gmm::csr_matrix* gmmxxMatrix = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(*submatrix); - delete submatrix; - - // Initialize the x vector with 1 for each element. This is the initial guess for - // the iterative solvers. - std::vector x(maybeStatesSetBitCount, storm::utility::constGetOne()); - - // Prepare the right-hand side of the equation system. - std::vector* b = new std::vector(maybeStatesSetBitCount); - if (this->getModel().hasTransitionRewards()) { - // If a transition-based reward model is available, we initialize the right-hand - // side to the vector resulting from summing the rows of the pointwise product - // of the transition probability matrix and the transition reward matrix. - std::vector* pointwiseProductRowSumVector = this->getModel().getTransitionMatrix()->getPointwiseProductRowSumVector(*this->getModel().getTransitionRewardMatrix()); - storm::utility::selectVectorValues(b, maybeStates, *pointwiseProductRowSumVector); - delete pointwiseProductRowSumVector; - - if (this->getModel().hasStateRewards()) { - // If a state-based reward model is also available, we need to add this vector - // as well. As the state reward vector contains entries not just for the states - // that we still consider (i.e. maybeStates), we need to extract these values - // first. - std::vector* subStateRewards = new std::vector(maybeStatesSetBitCount); - storm::utility::setVectorValues(subStateRewards, maybeStates, *this->getModel().getStateRewardVector()); - gmm::add(*subStateRewards, *b); - delete subStateRewards; - } - } else { - // If only a state-based reward model is available, we take this vector as the - // right-hand side. As the state reward vector contains entries not just for the - // states that we still consider (i.e. maybeStates), we need to extract these values - // first. - storm::utility::setVectorValues(b, maybeStates, *this->getModel().getStateRewardVector()); - } - - // Solve the corresponding system of linear equations. - // TODO: just commented out to make it compile - // this->solveEquationSystem(*gmmxxMatrix, x, *b); - - // Set values of resulting vector according to result. - storm::utility::setVectorValues(result, maybeStates, x); - - // Delete temporary matrix and right-hand side. - delete gmmxxMatrix; - delete b; - } - - // Set values of resulting vector that are known exactly. - storm::utility::setVectorValues(result, *targetStates, storm::utility::constGetZero()); - storm::utility::setVectorValues(result, infinityStates, storm::utility::constGetInfinity()); - - // Delete temporary storages and return result. - delete targetStates; - return result; - } - -private: /*! * Solves the given equation system under the given parameters using the power method. * @@ -360,7 +84,7 @@ private: * @return The solution of the system of linear equations in form of the elements of the vector * x. */ - void solveEquationSystem(gmm::csr_matrix const& A, std::vector* x, std::vector const& b, std::vector const& nondeterministicChoiceIndices) const { + void solveEquationSystem(storm::storage::SparseMatrix const& matrix, std::vector& x, std::vector const& b, std::vector const& nondeterministicChoiceIndices) const { // Get the settings object to customize solving. storm::settings::Settings* s = storm::settings::instance(); @@ -369,38 +93,49 @@ private: unsigned maxIterations = s->get("maxiter"); bool relative = s->get("relative"); + + // Transform the transition probability matrix to the gmm++ format to use its arithmetic. + gmm::csr_matrix* gmmxxMatrix = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(matrix); + // Set up the environment for the power method. - std::vector* temporaryResult = new std::vector(b.size()); - std::vector* newX = new std::vector(x->size()); + std::vector multiplyResult(matrix.getRowCount()); + std::vector* currentX = &x; + std::vector* newX = new std::vector(x.size()); std::vector* swap = nullptr; - bool converged = false; uint_fast64_t iterations = 0; + bool converged = false; // Proceed with the iterations as long as the method did not converge or reach the // user-specified maximum number of iterations. while (!converged && iterations < maxIterations) { // Compute x' = A*x + b. - gmm::mult(A, *x, *temporaryResult); - gmm::add(b, *temporaryResult); + gmm::mult(*gmmxxMatrix, *currentX, multiplyResult); + gmm::add(b, multiplyResult); // Reduce the vector x' by applying min/max for all non-deterministic choices. if (this->minimumOperatorStack.top()) { - storm::utility::reduceVectorMin(*temporaryResult, newX, nondeterministicChoiceIndices); + storm::utility::reduceVectorMin(multiplyResult, newX, nondeterministicChoiceIndices); } else { - storm::utility::reduceVectorMax(*temporaryResult, newX, nondeterministicChoiceIndices); + storm::utility::reduceVectorMax(multiplyResult, newX, nondeterministicChoiceIndices); } // Determine whether the method converged. - converged = storm::utility::equalModuloPrecision(*x, *newX, precision, relative); + converged = storm::utility::equalModuloPrecision(*currentX, *newX, precision, relative); // Update environment variables. - swap = x; - x = newX; + swap = currentX; + currentX = newX; newX = swap; ++iterations; } - delete temporaryResult; + if (iterations % 2 == 1) { + std::swap(x, *currentX); + delete currentX; + } else { + delete newX; + } + delete gmmxxMatrix; // Check if the solver converged and issue a warning otherwise. if (converged) { @@ -409,22 +144,6 @@ private: LOG4CPLUS_WARN(logger, "Iterative solver did not converge."); } } - - std::shared_ptr> computeNondeterministicChoiceIndicesForConstraint(storm::storage::BitVector constraint) const { - std::shared_ptr> nondeterministicChoiceIndices = this->getModel().getNondeterministicChoiceIndices(); - std::shared_ptr> subNondeterministicChoiceIndices(new std::vector(constraint.getNumberOfSetBits() + 1)); - uint_fast64_t currentRowCount = 0; - uint_fast64_t currentIndexCount = 1; - (*subNondeterministicChoiceIndices)[0] = 0; - for (auto index : constraint) { - (*subNondeterministicChoiceIndices)[currentIndexCount] = currentRowCount + (*nondeterministicChoiceIndices)[index + 1] - (*nondeterministicChoiceIndices)[index]; - currentRowCount += (*nondeterministicChoiceIndices)[index + 1] - (*nondeterministicChoiceIndices)[index]; - ++currentIndexCount; - } - (*subNondeterministicChoiceIndices)[constraint.getNumberOfSetBits()] = currentRowCount; - - return subNondeterministicChoiceIndices; - } }; } //namespace modelChecker diff --git a/src/modelchecker/MdpPrctlModelChecker.h b/src/modelchecker/MdpPrctlModelChecker.h index b71d1d934..acc75b2fb 100644 --- a/src/modelchecker/MdpPrctlModelChecker.h +++ b/src/modelchecker/MdpPrctlModelChecker.h @@ -189,7 +189,28 @@ public: * @param formula The Bounded Until path formula to check * @returns for each state the probability that the path formula holds. */ - virtual std::vector* checkBoundedUntil(const storm::formula::BoundedUntil& formula, bool qualitative) const = 0; + virtual std::vector* checkBoundedUntil(const storm::formula::BoundedUntil& formula, bool qualitative) const { + // First, we need to compute the states that satisfy the sub-formulas of the until-formula. + storm::storage::BitVector* leftStates = formula.getLeft().check(*this); + storm::storage::BitVector* rightStates = formula.getRight().check(*this); + + // Copy the matrix before we make any changes. + storm::storage::SparseMatrix tmpMatrix(*this->getModel().getTransitionMatrix()); + + // Make all rows absorbing that violate both sub-formulas or satisfy the second sub-formula. + tmpMatrix.makeRowsAbsorbing(~(*leftStates | *rightStates) | *rightStates, *this->getModel().getNondeterministicChoiceIndices()); + + // Create the vector with which to multiply. + std::vector* result = new std::vector(this->getModel().getNumberOfStates()); + storm::utility::setVectorValues(result, *rightStates, storm::utility::constGetOne()); + + this->performMatrixVectorMultiplication(*this->getModel().getTransitionMatrix(), *result, nullptr, formula.getBound()); + + // Delete intermediate results and return result. + delete leftStates; + delete rightStates; + return result; + } /*! * The check method for a path formula with a Next operator node as root in its formula tree @@ -197,7 +218,22 @@ public: * @param formula The Next path formula to check * @returns for each state the probability that the path formula holds. */ - virtual std::vector* checkNext(const storm::formula::Next& formula, bool qualitative) const = 0; + virtual std::vector* checkNext(const storm::formula::Next& formula, bool qualitative) const { + // First, we need to compute the states that satisfy the sub-formula of the next-formula. + storm::storage::BitVector* nextStates = formula.getChild().check(*this); + + // Create the vector with which to multiply and initialize it correctly. + std::vector* result = new std::vector(this->getModel().getNumberOfStates()); + storm::utility::setVectorValues(result, *nextStates, storm::utility::constGetOne()); + + // Delete obsolete sub-result. + delete nextStates; + + this->performMatrixVectorMultiplication(*this->getModel().getTransitionMatrix(), *result); + + // Return result. + return result; + } /*! * The check method for a path formula with a Bounded Eventually operator node as root in its @@ -246,7 +282,67 @@ public: * @param formula The Until path formula to check * @returns for each state the probability that the path formula holds. */ - virtual std::vector* checkUntil(const storm::formula::Until& formula, bool qualitative) const = 0; + virtual std::vector* checkUntil(const storm::formula::Until& formula, bool qualitative) const { + // First, we need to compute the states that satisfy the sub-formulas of the until-formula. + storm::storage::BitVector* leftStates = formula.getLeft().check(*this); + storm::storage::BitVector* rightStates = formula.getRight().check(*this); + + // Then, we need to identify the states which have to be taken out of the matrix, i.e. + // all states that have probability 0 and 1 of satisfying the until-formula. + storm::storage::BitVector statesWithProbability0(this->getModel().getNumberOfStates()); + storm::storage::BitVector statesWithProbability1(this->getModel().getNumberOfStates()); + if (this->minimumOperatorStack.top()) { + storm::utility::GraphAnalyzer::performProb01Min(this->getModel(), *leftStates, *rightStates, &statesWithProbability0, &statesWithProbability1); + } else { + storm::utility::GraphAnalyzer::performProb01Max(this->getModel(), *leftStates, *rightStates, &statesWithProbability0, &statesWithProbability1); + } + + // Delete sub-results that are obsolete now. + delete leftStates; + delete rightStates; + + LOG4CPLUS_INFO(logger, "Found " << statesWithProbability0.getNumberOfSetBits() << " 'no' states."); + LOG4CPLUS_INFO(logger, "Found " << statesWithProbability1.getNumberOfSetBits() << " 'yes' states."); + storm::storage::BitVector maybeStates = ~(statesWithProbability0 | statesWithProbability1); + LOG4CPLUS_INFO(logger, "Found " << maybeStates.getNumberOfSetBits() << " 'maybe' states."); + + // Create resulting vector. + std::vector* result = new std::vector(this->getModel().getNumberOfStates()); + + // Only try to solve system if there are states for which the probability is unknown. + uint_fast64_t maybeStatesSetBitCount = maybeStates.getNumberOfSetBits(); + if (maybeStatesSetBitCount > 0) { + // First, we can eliminate the rows and columns from the original transition probability matrix for states + // whose probabilities are already known. + storm::storage::SparseMatrix* submatrix = this->getModel().getTransitionMatrix()->getSubmatrix(maybeStates, *this->getModel().getNondeterministicChoiceIndices()); + + // Get the "new" nondeterministic choice indices for the submatrix. + std::shared_ptr> subNondeterministicChoiceIndices = this->computeNondeterministicChoiceIndicesForConstraint(maybeStates); + + // Create vector for results for maybe states. + std::vector x(maybeStatesSetBitCount); + + // Prepare the right-hand side of the equation system. For entry i this corresponds to + // the accumulated probability of going from state i to some 'yes' state. + std::vector b(submatrix->getRowCount()); + this->getModel().getTransitionMatrix()->getConstrainedRowSumVector(maybeStates, *this->getModel().getNondeterministicChoiceIndices(), statesWithProbability1, &b); + + // Solve the corresponding system of equations. + this->solveEquationSystem(*submatrix, x, b, *subNondeterministicChoiceIndices); + + // Set values of resulting vector according to result. + storm::utility::setVectorValues(result, maybeStates, x); + + // Delete temporary matrix. + delete submatrix; + } + + // Set values of resulting vector that are known exactly. + storm::utility::setVectorValues(result, statesWithProbability0, storm::utility::constGetZero()); + storm::utility::setVectorValues(result, statesWithProbability1, storm::utility::constGetOne()); + + return result; + } /*! * The check method for a path formula with an Instantaneous Reward operator node as root in its @@ -255,7 +351,21 @@ public: * @param formula The Instantaneous Reward formula to check * @returns for each state the reward that the instantaneous reward yields */ - virtual std::vector* checkInstantaneousReward(const storm::formula::InstantaneousReward& formula, bool qualitative) const = 0; + virtual std::vector* checkInstantaneousReward(const storm::formula::InstantaneousReward& formula, bool qualitative) const { + // Only compute the result if the model has a state-based reward model. + if (!this->getModel().hasStateRewards()) { + LOG4CPLUS_ERROR(logger, "Missing (state-based) reward model for formula."); + throw storm::exceptions::InvalidPropertyException() << "Missing (state-based) reward model for formula."; + } + + // Initialize result to state rewards of the model. + std::vector* result = new std::vector(*this->getModel().getStateRewardVector()); + + this->performMatrixVectorMultiplication(*this->getModel().getTransitionMatrix(), *result, nullptr, formula.getBound()); + + // Return result. + return result; + } /*! * The check method for a path formula with a Cumulative Reward operator node as root in its @@ -264,7 +374,32 @@ public: * @param formula The Cumulative Reward formula to check * @returns for each state the reward that the cumulative reward yields */ - virtual std::vector* checkCumulativeReward(const storm::formula::CumulativeReward& formula, bool qualitative) const = 0; + virtual std::vector* checkCumulativeReward(const storm::formula::CumulativeReward& formula, bool qualitative) const { + // Only compute the result if the model has at least one reward model. + if (!this->getModel().hasStateRewards() && !this->getModel().hasTransitionRewards()) { + LOG4CPLUS_ERROR(logger, "Missing reward model for formula."); + throw storm::exceptions::InvalidPropertyException() << "Missing reward model for formula."; + } + + // Compute the reward vector to add in each step based on the available reward models. + std::vector* totalRewardVector = nullptr; + if (this->getModel().hasTransitionRewards()) { + totalRewardVector = this->getModel().getTransitionMatrix()->getPointwiseProductRowSumVector(*this->getModel().getTransitionRewardMatrix()); + if (this->getModel().hasStateRewards()) { + gmm::add(*this->getModel().getStateRewardVector(), *totalRewardVector); + } + } else { + totalRewardVector = new std::vector(*this->getModel().getStateRewardVector()); + } + + std::vector* result = new std::vector(*this->getModel().getStateRewardVector()); + + this->performMatrixVectorMultiplication(*this->getModel().getTransitionMatrix(), *result, totalRewardVector, formula.getBound()); + + // Delete temporary variables and return result. + delete totalRewardVector; + return result; + } /*! * The check method for a path formula with a Reachability Reward operator node as root in its @@ -273,10 +408,198 @@ public: * @param formula The Reachbility Reward formula to check * @returns for each state the reward that the reachability reward yields */ - virtual std::vector* checkReachabilityReward(const storm::formula::ReachabilityReward& formula, bool qualitative) const = 0; + virtual std::vector* checkReachabilityReward(const storm::formula::ReachabilityReward& formula, bool qualitative) const { + // Only compute the result if the model has at least one reward model. + if (!this->getModel().hasStateRewards() && !this->getModel().hasTransitionRewards()) { + LOG4CPLUS_ERROR(logger, "Missing reward model for formula. Skipping formula"); + throw storm::exceptions::InvalidPropertyException() << "Missing reward model for formula."; + } + + // Determine the states for which the target predicate holds. + storm::storage::BitVector* targetStates = formula.getChild().check(*this); + + // Determine which states have a reward of infinity by definition. + storm::storage::BitVector infinityStates(this->getModel().getNumberOfStates()); + storm::storage::BitVector trueStates(this->getModel().getNumberOfStates(), true); + if (this->minimumOperatorStack.top()) { + storm::utility::GraphAnalyzer::performProb1A(this->getModel(), trueStates, *targetStates, &infinityStates); + } else { + storm::utility::GraphAnalyzer::performProb1E(this->getModel(), trueStates, *targetStates, &infinityStates); + } + infinityStates.complement(); + + LOG4CPLUS_INFO(logger, "Found " << infinityStates.getNumberOfSetBits() << " 'infinity' states."); + LOG4CPLUS_INFO(logger, "Found " << targetStates->getNumberOfSetBits() << " 'target' states."); + storm::storage::BitVector maybeStates = ~(*targetStates) & ~infinityStates; + LOG4CPLUS_INFO(logger, "Found " << maybeStates.getNumberOfSetBits() << " 'maybe' states."); + + // Create resulting vector. + std::vector* result = new std::vector(this->getModel().getNumberOfStates()); + + // Check whether there are states for which we have to compute the result. + const int maybeStatesSetBitCount = maybeStates.getNumberOfSetBits(); + if (maybeStatesSetBitCount > 0) { + // First, we can eliminate the rows and columns from the original transition probability matrix for states + // whose probabilities are already known. + storm::storage::SparseMatrix* submatrix = this->getModel().getTransitionMatrix()->getSubmatrix(maybeStates, *this->getModel().getNondeterministicChoiceIndices()); + + // Get the "new" nondeterministic choice indices for the submatrix. + std::shared_ptr> subNondeterministicChoiceIndices = this->computeNondeterministicChoiceIndicesForConstraint(maybeStates); + + // Create vector for results for maybe states. + std::vector x(maybeStatesSetBitCount); + + // Prepare the right-hand side of the equation system. For entry i this corresponds to + // the accumulated probability of going from state i to some 'yes' state. + std::vector b(submatrix->getRowCount()); + + if (this->getModel().hasTransitionRewards()) { + // If a transition-based reward model is available, we initialize the right-hand + // side to the vector resulting from summing the rows of the pointwise product + // of the transition probability matrix and the transition reward matrix. + std::vector* pointwiseProductRowSumVector = this->getModel().getTransitionMatrix()->getPointwiseProductRowSumVector(*this->getModel().getTransitionRewardMatrix()); + storm::utility::selectVectorValues(&b, maybeStates, *this->getModel().getNondeterministicChoiceIndices(), *pointwiseProductRowSumVector); + delete pointwiseProductRowSumVector; + + if (this->getModel().hasStateRewards()) { + // If a state-based reward model is also available, we need to add this vector + // as well. As the state reward vector contains entries not just for the states + // that we still consider (i.e. maybeStates), we need to extract these values + // first. + std::vector* subStateRewards = new std::vector(b.size()); + storm::utility::selectVectorValuesRepeatedly(subStateRewards, maybeStates, *this->getModel().getNondeterministicChoiceIndices(), *this->getModel().getStateRewardVector()); + gmm::add(*subStateRewards, b); + delete subStateRewards; + } + } else { + // If only a state-based reward model is available, we take this vector as the + // right-hand side. As the state reward vector contains entries not just for the + // states that we still consider (i.e. maybeStates), we need to extract these values + // first. + storm::utility::selectVectorValuesRepeatedly(&b, maybeStates, *this->getModel().getNondeterministicChoiceIndices(), *this->getModel().getStateRewardVector()); + } + + // Solve the corresponding system of equations. + this->solveEquationSystem(*submatrix, x, b, *subNondeterministicChoiceIndices); + + // Set values of resulting vector according to result. + storm::utility::setVectorValues(result, maybeStates, x); + delete submatrix; + } + + // Set values of resulting vector that are known exactly. + storm::utility::setVectorValues(result, *targetStates, storm::utility::constGetZero()); + storm::utility::setVectorValues(result, infinityStates, storm::utility::constGetInfinity()); + + // Delete temporary storages and return result. + delete targetStates; + return result; + } protected: mutable std::stack minimumOperatorStack; + +private: + virtual void performMatrixVectorMultiplication(storm::storage::SparseMatrix const& matrix, std::vector& vector, std::vector* summand = nullptr, uint_fast64_t repetitions = 1) const { + // Get the starting row indices for the non-deterministic choices to reduce the resulting + // vector properly. + std::shared_ptr> nondeterministicChoiceIndices = this->getModel().getNondeterministicChoiceIndices(); + + // Create vector for result of multiplication, which is reduced to the result vector after + // each multiplication. + std::vector multiplyResult(matrix.getRowCount()); + + // Now perform matrix-vector multiplication as long as we meet the bound of the formula. + for (uint_fast64_t i = 0; i < repetitions; ++i) { + matrix.multiplyWithVector(vector, multiplyResult); + if (summand != nullptr) { + gmm::add(*summand, multiplyResult); + } + if (this->minimumOperatorStack.top()) { + storm::utility::reduceVectorMin(multiplyResult, &vector, *nondeterministicChoiceIndices); + } else { + storm::utility::reduceVectorMax(multiplyResult, &vector, *nondeterministicChoiceIndices); + } + } + } + + virtual void solveEquationSystem(storm::storage::SparseMatrix const& matrix, std::vector& x, std::vector const& b, std::vector const& nondeterministicChoiceIndices) const { + // Get the settings object to customize solving. + storm::settings::Settings* s = storm::settings::instance(); + + // Get relevant user-defined settings for solving the equations. + double precision = s->get("precision"); + unsigned maxIterations = s->get("maxiter"); + bool relative = s->get("relative"); + + // Set up the environment for the power method. + std::vector multiplyResult(matrix.getRowCount()); + std::vector* currentX = &x; + std::vector* newX = new std::vector(x.size()); + std::vector* swap = nullptr; + uint_fast64_t iterations = 0; + bool converged = false; + + // Proceed with the iterations as long as the method did not converge or reach the + // user-specified maximum number of iterations. + while (!converged && iterations < maxIterations) { + // Compute x' = A*x + b. + matrix.multiplyWithVector(*currentX, multiplyResult); + // matrix.multiplyAddAndReduceInPlace(nondeterministicChoiceIndices, *currentX, b, this->minimumOperatorStack.top()); + + gmm::add(b, multiplyResult); + + // Reduce the vector x' by applying min/max for all non-deterministic choices. + if (this->minimumOperatorStack.top()) { + storm::utility::reduceVectorMin(multiplyResult, newX, nondeterministicChoiceIndices); + } else { + storm::utility::reduceVectorMax(multiplyResult, newX, nondeterministicChoiceIndices); + } + + // Determine whether the method converged. + converged = storm::utility::equalModuloPrecision(*currentX, *newX, precision, relative); + + + // Update environment variables. + swap = currentX; + currentX = newX; + newX = swap; + ++iterations; + + // *newX = *currentX, + + } + + if (iterations % 2 == 1) { + std::swap(x, *currentX); + delete currentX; + } else { + delete newX; + } + + // Check if the solver converged and issue a warning otherwise. + if (converged) { + LOG4CPLUS_INFO(logger, "Iterative solver converged after " << iterations << " iterations."); + } else { + LOG4CPLUS_WARN(logger, "Iterative solver did not converge."); + } + } + + std::shared_ptr> computeNondeterministicChoiceIndicesForConstraint(storm::storage::BitVector constraint) const { + std::shared_ptr> nondeterministicChoiceIndices = this->getModel().getNondeterministicChoiceIndices(); + std::shared_ptr> subNondeterministicChoiceIndices(new std::vector(constraint.getNumberOfSetBits() + 1)); + uint_fast64_t currentRowCount = 0; + uint_fast64_t currentIndexCount = 1; + (*subNondeterministicChoiceIndices)[0] = 0; + for (auto index : constraint) { + (*subNondeterministicChoiceIndices)[currentIndexCount] = currentRowCount + (*nondeterministicChoiceIndices)[index + 1] - (*nondeterministicChoiceIndices)[index]; + currentRowCount += (*nondeterministicChoiceIndices)[index + 1] - (*nondeterministicChoiceIndices)[index]; + ++currentIndexCount; + } + (*subNondeterministicChoiceIndices)[constraint.getNumberOfSetBits()] = currentRowCount; + + return subNondeterministicChoiceIndices; + } }; } //namespace modelChecker diff --git a/src/modelchecker/TopologicalValueIterationMdpPrctlModelChecker.h b/src/modelchecker/TopologicalValueIterationMdpPrctlModelChecker.h new file mode 100644 index 000000000..23b1ac6fc --- /dev/null +++ b/src/modelchecker/TopologicalValueIterationMdpPrctlModelChecker.h @@ -0,0 +1,139 @@ +/* + * GmmxxDtmcPrctlModelChecker.h + * + * Created on: 06.12.2012 + * Author: Christian Dehnert + */ + +#ifndef STORM_MODELCHECKER_TOPOLOGICALVALUEITERATIONSMDPPRCTLMODELCHECKER_H_ +#define STORM_MODELCHECKER_TOPOLOGICALVALUEITERATIONSMDPPRCTLMODELCHECKER_H_ + +#include + +#include "src/models/Mdp.h" +#include "src/modelchecker/MdpPrctlModelChecker.h" +#include "src/utility/GraphAnalyzer.h" +#include "src/utility/Vector.h" +#include "src/utility/ConstTemplates.h" +#include "src/utility/Settings.h" +#include "src/adapters/GmmxxAdapter.h" +#include "src/exceptions/InvalidPropertyException.h" +#include "src/storage/JacobiDecomposition.h" + +#include "gmm/gmm_matrix.h" +#include "gmm/gmm_iter_solvers.h" + +#include "log4cplus/logger.h" +#include "log4cplus/loggingmacros.h" + +extern log4cplus::Logger logger; + +namespace storm { + +namespace modelChecker { + +/* + * A model checking engine that makes use of the gmm++ backend. + */ +template +class TopologicalValueIterationMdpPrctlModelChecker : public MdpPrctlModelChecker { + +public: + explicit TopologicalValueIterationMdpPrctlModelChecker(storm::models::Mdp& mdp) : MdpPrctlModelChecker(mdp) { } + + virtual ~TopologicalValueIterationMdpPrctlModelChecker() { } + +private: + /*! + * Solves the given equation system under the given parameters using the power method. + * + * @param A The matrix A specifying the coefficients of the equations. + * @param x The vector x for which to solve the equations. The initial value of the elements of + * this vector are used as the initial guess and might thus influence performance and convergence. + * @param b The vector b specifying the values on the right-hand-sides of the equations. + * @return The solution of the system of linear equations in form of the elements of the vector + * x. + */ + void solveEquationSystem(storm::storage::SparseMatrix const& matrix, std::vector& x, std::vector const& b, std::vector const& nondeterministicChoiceIndices) const { + // Get the settings object to customize solving. + storm::settings::Settings* s = storm::settings::instance(); + + // Get relevant user-defined settings for solving the equations. + double precision = s->get("precision"); + unsigned maxIterations = s->get("maxiter"); + bool relative = s->get("relative"); + + std::vector> stronglyConnectedComponents; + storm::models::GraphTransitions stronglyConnectedComponentsDependencyGraph; + storm::utility::GraphAnalyzer::performSccDecomposition(matrix, nondeterministicChoiceIndices, stronglyConnectedComponents, stronglyConnectedComponentsDependencyGraph); + + std::vector topologicalSort; + storm::utility::GraphAnalyzer::getTopologicalSort(stronglyConnectedComponentsDependencyGraph, topologicalSort); + + // Set up the environment for the power method. + std::vector multiplyResult(matrix.getRowCount()); + std::vector* currentX = &x; + std::vector* newX = new std::vector(x.size()); + std::vector* swap = nullptr; + uint_fast64_t currentMaxLocalIterations = 0; + uint_fast64_t localIterations = 0; + uint_fast64_t globalIterations = 0; + bool converged = true; + + for (auto sccIndexIt = topologicalSort.begin(); sccIndexIt != topologicalSort.end() && converged; ++sccIndexIt) { + std::vector const& scc = stronglyConnectedComponents[*sccIndexIt]; + + localIterations = 0; + converged = false; + while (!converged && localIterations < maxIterations) { + // Compute x' = A*x + b. + matrix.multiplyWithVector(scc, nondeterministicChoiceIndices, *currentX, multiplyResult); + storm::utility::addVectors(scc, nondeterministicChoiceIndices, multiplyResult, b); + + // Reduce the vector x' by applying min/max for all non-deterministic choices. + if (this->minimumOperatorStack.top()) { + storm::utility::reduceVectorMin(multiplyResult, newX, scc, nondeterministicChoiceIndices); + } else { + storm::utility::reduceVectorMax(multiplyResult, newX, scc, nondeterministicChoiceIndices); + } + + // Determine whether the method converged. + // converged = storm::utility::equalModuloPrecision(*currentX, *newX, scc, precision, relative); + converged = storm::utility::equalModuloPrecision(*currentX, *newX, precision, relative); + + // Update environment variables. + swap = currentX; + currentX = newX; + newX = swap; + ++localIterations; + ++globalIterations; + } + + std::cout << "converged locally for scc of size " << scc.size() << std::endl; + + if (localIterations > currentMaxLocalIterations) { + currentMaxLocalIterations = localIterations; + } + } + + if (globalIterations % 2 == 1) { + std::swap(x, *currentX); + delete currentX; + } else { + delete newX; + } + + // Check if the solver converged and issue a warning otherwise. + if (converged) { + LOG4CPLUS_INFO(logger, "Iterative solver converged after " << currentMaxLocalIterations << " iterations."); + } else { + LOG4CPLUS_WARN(logger, "Iterative solver did not converge."); + } + } +}; + +} //namespace modelChecker + +} //namespace storm + +#endif /* STORM_MODELCHECKER_TOPOLOGICALVALUEITERATIONSMDPPRCTLMODELCHECKER_H_ */ diff --git a/src/models/GraphTransitions.h b/src/models/GraphTransitions.h index 3af0524b4..ec8bb2bfd 100644 --- a/src/models/GraphTransitions.h +++ b/src/models/GraphTransitions.h @@ -18,7 +18,7 @@ namespace storm { namespace models { /*! - * This class stores the predecessors of all states in a state space of the + * This class stores the successors of all states in a state space of the * given size. */ template @@ -28,7 +28,7 @@ public: /*! * Just typedef the iterator as a pointer to the index type. */ - typedef const uint_fast64_t * const statePredecessorIterator; + typedef const uint_fast64_t * stateSuccessorIterator; //! Constructor /*! @@ -39,8 +39,8 @@ public: * @param forward If set to true, this objects will store the graph structure * of the backwards transition relation. */ - GraphTransitions(std::shared_ptr> transitionMatrix, bool forward) - : successorList(nullptr), stateIndications(nullptr), numberOfStates(transitionMatrix->getColumnCount()), numberOfTransitions(transitionMatrix->getNonZeroEntryCount()) { + GraphTransitions(storm::storage::SparseMatrix const& transitionMatrix, bool forward) + : numberOfStates(transitionMatrix.getColumnCount()), numberOfTransitions(transitionMatrix.getNonZeroEntryCount()), successorList(numberOfTransitions), stateIndications(numberOfStates + 1) { if (forward) { this->initializeForward(transitionMatrix); } else { @@ -48,26 +48,45 @@ public: } } - GraphTransitions(std::shared_ptr> transitionMatrix, std::shared_ptr> choiceIndices, bool forward) - : successorList(nullptr), stateIndications(nullptr), numberOfStates(transitionMatrix->getColumnCount()), numberOfTransitions(transitionMatrix->getNonZeroEntryCount()) { + GraphTransitions(storm::storage::SparseMatrix const& transitionMatrix, std::vector const& nondeterministicChoiceIndices, bool forward) + : numberOfStates(transitionMatrix.getColumnCount()), numberOfTransitions(transitionMatrix.getNonZeroEntryCount()), successorList(numberOfTransitions), stateIndications(numberOfStates + 1) { if (forward) { - this->initializeForward(transitionMatrix, choiceIndices); + this->initializeForward(transitionMatrix, nondeterministicChoiceIndices); } else { - this->initializeBackward(transitionMatrix, choiceIndices); + this->initializeBackward(transitionMatrix, nondeterministicChoiceIndices); } } + GraphTransitions(GraphTransitions const& transitions, std::vector> const& stronglyConnectedComponents, std::map const& stateToSccMap) + : numberOfStates(stronglyConnectedComponents.size()), numberOfTransitions(0), successorList(), stateIndications(numberOfStates + 1) { + this->initializeFromSccDecomposition(transitions, stronglyConnectedComponents, stateToSccMap); + } + + GraphTransitions() : numberOfStates(0), numberOfTransitions(0), successorList(), stateIndications() { + // Intentionally left empty. + } + + GraphTransitions(GraphTransitions&& other) { + this->numberOfStates = other.numberOfStates; + this->numberOfTransitions = other.numberOfTransitions; + std::swap(successorList, other.successorList); + std::swap(stateIndications, other.stateIndications); + } + + GraphTransitions& operator=(GraphTransitions&& other) { + this->numberOfStates = other.numberOfStates; + this->numberOfTransitions = other.numberOfTransitions; + std::swap(successorList, other.successorList); + std::swap(stateIndications, other.stateIndications); + return *this; + } + //! Destructor /*! * Destructor. Frees the internal storage. */ ~GraphTransitions() { - if (this->successorList != nullptr) { - delete[] this->successorList; - } - if (this->stateIndications != nullptr) { - delete[] this->stateIndications; - } + // Intentionally left empty. } /*! @@ -80,13 +99,21 @@ public: return result; } + uint_fast64_t getNumberOfStates() const { + return numberOfStates; + } + + uint_fast64_t getNumberOfTransitions() const { + return numberOfTransitions; + } + /*! * Returns an iterator to the successors of the given state. * @param state The state for which to get the successor iterator. * @return An iterator to the predecessors of the given states. */ - statePredecessorIterator beginStateSuccessorsIterator(uint_fast64_t state) const { - return this->successorList + this->stateIndications[state]; + stateSuccessorIterator beginStateSuccessorsIterator(uint_fast64_t state) const { + return &(this->successorList[0]) + this->stateIndications[state]; } /*! @@ -96,46 +123,85 @@ public: * @return An iterator referring to the element after the successors of * the given state. */ - statePredecessorIterator endStateSuccessorsIterator(uint_fast64_t state) const { - return this->successorList + this->stateIndications[state + 1]; + stateSuccessorIterator endStateSuccessorsIterator(uint_fast64_t state) const { + return &(this->successorList[0]) + this->stateIndications[state + 1]; + } + + /*! + * Returns a (naive) string representation of the transitions in this object. + * @returns a (naive) string representation of the transitions in this object. + */ + std::string toString() const { + std::stringstream stream; + for (uint_fast64_t state = 0; state < numberOfStates; ++state) { + for (auto succIt = this->beginStateSuccessorsIterator(state), succIte = this->endStateSuccessorsIterator(state); succIt != succIte; ++succIt) { + stream << state << " -> " << *succIt << std::endl; + } + } + return stream.str(); } private: + void initializeFromSccDecomposition(GraphTransitions const& transitions, std::vector> const& stronglyConnectedComponents, std::map const& stateToSccMap) { + for (uint_fast64_t currentSccIndex = 0; currentSccIndex < stronglyConnectedComponents.size(); ++currentSccIndex) { + // Mark beginning of current SCC. + stateIndications[currentSccIndex] = successorList.size(); + + // Get the actual SCC. + std::vector const& scc = stronglyConnectedComponents[currentSccIndex]; + + // Now, we determine the SCCs which are reachable (in one step) from the current SCC. + std::set allTargetSccs; + for (auto state : scc) { + for (stateSuccessorIterator succIt = transitions.beginStateSuccessorsIterator(state), succIte = transitions.endStateSuccessorsIterator(state); succIt != succIte; ++succIt) { + uint_fast64_t targetScc = stateToSccMap.find(*succIt)->second; + + // We only need to consider transitions that are actually leaving the SCC. + if (targetScc != currentSccIndex) { + allTargetSccs.insert(targetScc); + } + } + } + + // Now we can just enumerate all the target SCCs and insert the corresponding transitions. + for (auto targetScc : allTargetSccs) { + successorList.push_back(targetScc); + } + } + + // Put the sentinel element at the end and initialize the number of transitions. + stateIndications[numberOfStates] = successorList.size(); + numberOfTransitions = successorList.size(); + } /*! * Initializes this graph transitions object using the forward transition * relation given by means of a sparse matrix. */ - void initializeForward(std::shared_ptr> transitionMatrix) { - this->successorList = new uint_fast64_t[numberOfTransitions]; - this->stateIndications = new uint_fast64_t[numberOfStates + 1]; - + void initializeForward(storm::storage::SparseMatrix const& transitionMatrix) { // First, we copy the index list from the sparse matrix as this will // stay the same. - std::copy(transitionMatrix->getRowIndications().begin(), transitionMatrix->getRowIndications().end(), this->stateIndications); + std::copy(transitionMatrix.getRowIndications().begin(), transitionMatrix.getRowIndications().end(), this->stateIndications.begin()); - // Now we can iterate over all rows of the transition matrix and record - // the target state. + // Now we can iterate over all rows of the transition matrix and record the target state. for (uint_fast64_t i = 0, currentNonZeroElement = 0; i < numberOfStates; i++) { - for (auto rowIt = transitionMatrix->beginConstColumnIterator(i); rowIt != transitionMatrix->endConstColumnIterator(i); ++rowIt) { + for (auto rowIt = transitionMatrix.beginConstColumnIterator(i); rowIt != transitionMatrix.endConstColumnIterator(i); ++rowIt) { this->successorList[currentNonZeroElement++] = *rowIt; } } } - void initializeForward(std::shared_ptr> transitionMatrix, std::shared_ptr> choiceIndices) { - this->successorList = new uint_fast64_t[numberOfTransitions]; - this->stateIndications = new uint_fast64_t[numberOfStates + 1]; - + void initializeForward(storm::storage::SparseMatrix const& transitionMatrix, std::vector const& nondeterministicChoiceIndices) { for (uint_fast64_t i = 0; i < numberOfStates; ++i) { - this->stateIndications[i] = transitionMatrix->getRowIndications().at(choiceIndices->at(i)); + this->stateIndications[i] = transitionMatrix.getRowIndications().at(nondeterministicChoiceIndices[i]); } + this->stateIndications[numberOfStates] = numberOfTransitions; // Now we can iterate over all rows of the transition matrix and record // the target state. for (uint_fast64_t i = 0, currentNonZeroElement = 0; i < numberOfStates; i++) { - for (uint_fast64_t j = choiceIndices->at(i); j < choiceIndices->at(i + 1); ++j) { - for (auto rowIt = transitionMatrix->beginConstColumnIterator(j); rowIt != transitionMatrix->endConstColumnIterator(j); ++rowIt) { + for (uint_fast64_t j = nondeterministicChoiceIndices[i]; j < nondeterministicChoiceIndices[i + 1]; ++j) { + for (auto rowIt = transitionMatrix.beginConstColumnIterator(j); rowIt != transitionMatrix.endConstColumnIterator(j); ++rowIt) { this->successorList[currentNonZeroElement++] = *rowIt; } } @@ -147,13 +213,10 @@ private: * relation, whose forward transition relation is given by means of a sparse * matrix. */ - void initializeBackward(std::shared_ptr> transitionMatrix) { - this->successorList = new uint_fast64_t[numberOfTransitions]; - this->stateIndications = new uint_fast64_t[numberOfStates + 1](); - + void initializeBackward(storm::storage::SparseMatrix const& transitionMatrix) { // First, we need to count how many backward transitions each state has. for (uint_fast64_t i = 0; i < numberOfStates; ++i) { - for (auto rowIt = transitionMatrix->beginConstColumnIterator(i); rowIt != transitionMatrix->endConstColumnIterator(i); ++rowIt) { + for (auto rowIt = transitionMatrix.beginConstColumnIterator(i); rowIt != transitionMatrix.endConstColumnIterator(i); ++rowIt) { this->stateIndications[*rowIt + 1]++; } } @@ -170,29 +233,22 @@ private: // Create an array that stores the next index for each state. Initially // this corresponds to the previously computed accumulated offsets. - uint_fast64_t* nextIndicesList = new uint_fast64_t[numberOfStates]; - std::copy(stateIndications, stateIndications + numberOfStates, nextIndicesList); + std::vector nextIndices = stateIndications; // Now we are ready to actually fill in the list of predecessors for // every state. Again, we start by considering all but the last row. for (uint_fast64_t i = 0; i < numberOfStates; ++i) { - for (auto rowIt = transitionMatrix->beginConstColumnIterator(i); rowIt != transitionMatrix->endConstColumnIterator(i); ++rowIt) { - this->successorList[nextIndicesList[*rowIt]++] = i; + for (auto rowIt = transitionMatrix.beginConstColumnIterator(i); rowIt != transitionMatrix.endConstColumnIterator(i); ++rowIt) { + this->successorList[nextIndices[*rowIt]++] = i; } } - - // Now we can dispose of the auxiliary array. - delete[] nextIndicesList; } - void initializeBackward(std::shared_ptr> transitionMatrix, std::shared_ptr> choiceIndices) { - this->successorList = new uint_fast64_t[numberOfTransitions]; - this->stateIndications = new uint_fast64_t[numberOfStates + 1](); - + void initializeBackward(storm::storage::SparseMatrix const& transitionMatrix, std::vector const& nondeterministicChoiceIndices) { // First, we need to count how many backward transitions each state has. for (uint_fast64_t i = 0; i < numberOfStates; ++i) { - for (uint_fast64_t j = choiceIndices->at(i); j < choiceIndices->at(i + 1); ++j) { - for (auto rowIt = transitionMatrix->beginConstColumnIterator(j); rowIt != transitionMatrix->endConstColumnIterator(j); ++rowIt) { + for (uint_fast64_t j = nondeterministicChoiceIndices[i]; j < nondeterministicChoiceIndices[i + 1]; ++j) { + for (auto rowIt = transitionMatrix.beginConstColumnIterator(j); rowIt != transitionMatrix.endConstColumnIterator(j); ++rowIt) { this->stateIndications[*rowIt + 1]++; } } @@ -210,32 +266,19 @@ private: // Create an array that stores the next index for each state. Initially // this corresponds to the previously computed accumulated offsets. - uint_fast64_t* nextIndicesList = new uint_fast64_t[numberOfStates]; - std::copy(stateIndications, stateIndications + numberOfStates, nextIndicesList); + std::vector nextIndices = stateIndications; // Now we are ready to actually fill in the list of predecessors for // every state. Again, we start by considering all but the last row. for (uint_fast64_t i = 0; i < numberOfStates; i++) { - for (uint_fast64_t j = (*choiceIndices)[i]; j < (*choiceIndices)[i + 1]; ++j) { - for (auto rowIt = transitionMatrix->beginConstColumnIterator(j); rowIt != transitionMatrix->endConstColumnIterator(j); ++rowIt) { - this->successorList[nextIndicesList[*rowIt]++] = i; + for (uint_fast64_t j = nondeterministicChoiceIndices[i]; j < nondeterministicChoiceIndices[i + 1]; ++j) { + for (auto rowIt = transitionMatrix.beginConstColumnIterator(j); rowIt != transitionMatrix.endConstColumnIterator(j); ++rowIt) { + this->successorList[nextIndices[*rowIt]++] = i; } } } - - // Now we can dispose of the auxiliary array. - delete[] nextIndicesList; } - /*! A list of successors for *all* states. */ - uint_fast64_t* successorList; - - /*! - * A list of indices indicating at which position in the global array the - * successors of a state can be found. - */ - uint_fast64_t* stateIndications; - /*! * Store the number of states to determine the highest index at which the * state_indices_list may be accessed. @@ -247,6 +290,15 @@ private: * index at which the predecessor_list may be accessed. */ uint_fast64_t numberOfTransitions; + + /*! A list of successors for *all* states. */ + std::vector successorList; + + /*! + * A list of indices indicating at which position in the global array the + * successors of a state can be found. + */ + std::vector stateIndications; }; } // namespace models diff --git a/src/parser/DeterministicModelParser.cpp b/src/parser/DeterministicModelParser.cpp index 2372cde3e..35818ffbf 100644 --- a/src/parser/DeterministicModelParser.cpp +++ b/src/parser/DeterministicModelParser.cpp @@ -45,7 +45,9 @@ DeterministicModelParser::DeterministicModelParser(std::string const & transitio this->stateRewards = srp.getStateRewards(); } if (transitionRewardFile != "") { - storm::parser::DeterministicSparseTransitionParser trp(transitionRewardFile); + RewardMatrixInformationStruct* rewardMatrixInfo = new RewardMatrixInformationStruct(stateCount, stateCount, nullptr); + storm::parser::DeterministicSparseTransitionParser trp(transitionRewardFile, false, rewardMatrixInfo); + delete rewardMatrixInfo; this->transitionRewards = trp.getMatrix(); } this->dtmc = nullptr; diff --git a/src/parser/DeterministicSparseTransitionParser.cpp b/src/parser/DeterministicSparseTransitionParser.cpp index d0f340698..cdefb7b59 100644 --- a/src/parser/DeterministicSparseTransitionParser.cpp +++ b/src/parser/DeterministicSparseTransitionParser.cpp @@ -44,31 +44,22 @@ namespace parser { * @param buf Data to scan. Is expected to be some char array. * @param maxnode Is set to highest id of all nodes. */ -uint_fast64_t DeterministicSparseTransitionParser::firstPass(char* buf, int_fast64_t& maxnode) { +uint_fast64_t DeterministicSparseTransitionParser::firstPass(char* buf, uint_fast64_t& maxnode, RewardMatrixInformationStruct* rewardMatrixInformation) { + bool isRewardMatrix = rewardMatrixInformation != nullptr; + uint_fast64_t nonZeroEntryCount = 0; - uint_fast64_t inputFileNonZeroEntryCount = 0; /* * Check file header and extract number of transitions. */ - buf = strchr(buf, '\n') + 1; // skip format hint - if (strncmp(buf, "STATES ", 7) != 0) { - LOG4CPLUS_ERROR(logger, "Expected \"STATES\" but got \"" << std::string(buf, 0, 16) << "\"."); - return 0; - } - buf += 7; // skip "STATES " - if (strtol(buf, &buf, 10) == 0) return 0; - buf = trimWhitespaces(buf); - if (strncmp(buf, "TRANSITIONS ", 12) != 0) { - LOG4CPLUS_ERROR(logger, "Expected \"TRANSITIONS\" but got \"" << std::string(buf, 0, 16) << "\"."); - return 0; + if (!isRewardMatrix) { + buf = strchr(buf, '\n') + 1; // skip format hint } - buf += 12; // skip "TRANSITIONS " - if ((inputFileNonZeroEntryCount = strtol(buf, &buf, 10)) == 0) return 0; /* * Check all transitions for non-zero diagonal entries and deadlock states. */ - int_fast64_t row, lastRow = -1, col; + int_fast64_t lastRow = -1; + uint_fast64_t row, col; uint_fast64_t readTransitionCount = 0; bool rowHadDiagonalEntry = false; double val; @@ -80,24 +71,26 @@ uint_fast64_t DeterministicSparseTransitionParser::firstPass(char* buf, int_fast row = checked_strtol(buf, &buf); col = checked_strtol(buf, &buf); - if (lastRow != row) { - if ((lastRow != -1) && (!rowHadDiagonalEntry)) { - ++nonZeroEntryCount; - rowHadDiagonalEntry = true; + if (!isRewardMatrix) { + if (lastRow != (int_fast64_t)row) { + if ((lastRow != -1) && (!rowHadDiagonalEntry)) { + ++nonZeroEntryCount; + rowHadDiagonalEntry = true; + } + for (uint_fast64_t skippedRow = (uint_fast64_t)(lastRow + 1); skippedRow < row; ++skippedRow) { + ++nonZeroEntryCount; + } + lastRow = row; + rowHadDiagonalEntry = false; } - for (int_fast64_t skippedRow = lastRow + 1; skippedRow < row; ++skippedRow) { + + if (col == row) { + rowHadDiagonalEntry = true; + } else if (col > row && !rowHadDiagonalEntry) { + rowHadDiagonalEntry = true; ++nonZeroEntryCount; } - lastRow = row; - rowHadDiagonalEntry = false; - } - - if (col == row) { - rowHadDiagonalEntry = true; - } else if (col > row && !rowHadDiagonalEntry) { - rowHadDiagonalEntry = true; - ++nonZeroEntryCount; - } + } /* * Check if one is larger than the current maximum id. @@ -119,15 +112,10 @@ uint_fast64_t DeterministicSparseTransitionParser::firstPass(char* buf, int_fast buf = trimWhitespaces(buf); } - if (!rowHadDiagonalEntry) { + if (!rowHadDiagonalEntry && !isRewardMatrix) { ++nonZeroEntryCount; } - if (inputFileNonZeroEntryCount != readTransitionCount) { - LOG4CPLUS_ERROR(logger, "Input File TRANSITIONS line stated " << inputFileNonZeroEntryCount << " but there were " << readTransitionCount << " transitions afterwards."); - return 0; - } - return nonZeroEntryCount; } @@ -141,13 +129,15 @@ uint_fast64_t DeterministicSparseTransitionParser::firstPass(char* buf, int_fast * @return a pointer to the created sparse matrix. */ -DeterministicSparseTransitionParser::DeterministicSparseTransitionParser(std::string const &filename, bool insertDiagonalEntriesIfMissing) +DeterministicSparseTransitionParser::DeterministicSparseTransitionParser(std::string const &filename, bool insertDiagonalEntriesIfMissing, RewardMatrixInformationStruct* rewardMatrixInformation) : matrix(nullptr) { /* * Enforce locale where decimal point is '.'. */ setlocale(LC_NUMERIC, "C"); + bool isRewardMatrix = rewardMatrixInformation != nullptr; + /* * Open file. */ @@ -157,8 +147,8 @@ DeterministicSparseTransitionParser::DeterministicSparseTransitionParser(std::st /* * Perform first pass, i.e. count entries that are not zero. */ - int_fast64_t maxStateId; - uint_fast64_t nonZeroEntryCount = this->firstPass(file.data, maxStateId); + uint_fast64_t maxStateId; + uint_fast64_t nonZeroEntryCount = this->firstPass(file.data, maxStateId, rewardMatrixInformation); LOG4CPLUS_INFO(logger, "First pass on " << filename << " shows " << nonZeroEntryCount << " NonZeros."); @@ -179,12 +169,20 @@ DeterministicSparseTransitionParser::DeterministicSparseTransitionParser(std::st /* * Read file header, extract number of states. */ - buf = strchr(buf, '\n') + 1; // skip format hint - buf += 7; // skip "STATES " - checked_strtol(buf, &buf); - buf = trimWhitespaces(buf); - buf += 12; // skip "TRANSITIONS " - checked_strtol(buf, &buf); + if (!isRewardMatrix) { + buf = strchr(buf, '\n') + 1; // skip format hint + } + + // If the matrix that is being parsed is a reward matrix, it should match the size of the + // transition matrix. + if (isRewardMatrix) { + if (maxStateId + 1 > rewardMatrixInformation->rowCount || maxStateId + 1 > rewardMatrixInformation->columnCount) { + LOG4CPLUS_ERROR(logger, "Reward matrix has more rows or columns than transition matrix."); + throw storm::exceptions::WrongFormatException() << "Reward matrix has more rows or columns than transition matrix."; + } else { + maxStateId = rewardMatrixInformation->rowCount - 1; + } + } /* * Creating matrix here. @@ -219,10 +217,10 @@ DeterministicSparseTransitionParser::DeterministicSparseTransitionParser(std::st if (lastRow != row) { if ((lastRow != -1) && (!rowHadDiagonalEntry)) { - if (insertDiagonalEntriesIfMissing) { + if (insertDiagonalEntriesIfMissing && !isRewardMatrix) { this->matrix->addNextValue(lastRow, lastRow, storm::utility::constGetZero()); LOG4CPLUS_DEBUG(logger, "While parsing " << filename << ": state " << lastRow << " has no transition to itself. Inserted a 0-transition. (1)"); - } else { + } else if (!isRewardMatrix) { LOG4CPLUS_WARN(logger, "Warning while parsing " << filename << ": state " << lastRow << " has no transition to itself."); } // No increment for lastRow @@ -230,11 +228,11 @@ DeterministicSparseTransitionParser::DeterministicSparseTransitionParser(std::st } for (int_fast64_t skippedRow = lastRow + 1; skippedRow < row; ++skippedRow) { hadDeadlocks = true; - if (fixDeadlocks) { + if (fixDeadlocks && !isRewardMatrix) { this->matrix->addNextValue(skippedRow, skippedRow, storm::utility::constGetOne()); rowHadDiagonalEntry = true; LOG4CPLUS_WARN(logger, "Warning while parsing " << filename << ": state " << skippedRow << " has no outgoing transitions. A self-loop was inserted."); - } else { + } else if (!isRewardMatrix) { LOG4CPLUS_ERROR(logger, "Error while parsing " << filename << ": state " << skippedRow << " has no outgoing transitions."); // FIXME Why no exception at this point? This will break the App. } @@ -247,10 +245,10 @@ DeterministicSparseTransitionParser::DeterministicSparseTransitionParser(std::st rowHadDiagonalEntry = true; } else if (col > row && !rowHadDiagonalEntry) { rowHadDiagonalEntry = true; - if (insertDiagonalEntriesIfMissing) { + if (insertDiagonalEntriesIfMissing && !isRewardMatrix) { this->matrix->addNextValue(row, row, storm::utility::constGetZero()); LOG4CPLUS_DEBUG(logger, "While parsing " << filename << ": state " << row << " has no transition to itself. Inserted a 0-transition. (2)"); - } else { + } else if (!isRewardMatrix) { LOG4CPLUS_WARN(logger, "Warning while parsing " << filename << ": state " << row << " has no transition to itself."); } } @@ -260,10 +258,10 @@ DeterministicSparseTransitionParser::DeterministicSparseTransitionParser(std::st } if (!rowHadDiagonalEntry) { - if (insertDiagonalEntriesIfMissing) { + if (insertDiagonalEntriesIfMissing && !isRewardMatrix) { this->matrix->addNextValue(lastRow, lastRow, storm::utility::constGetZero()); LOG4CPLUS_DEBUG(logger, "While parsing " << filename << ": state " << lastRow << " has no transition to itself. Inserted a 0-transition. (3)"); - } else { + } else if (!isRewardMatrix) { LOG4CPLUS_WARN(logger, "Warning while parsing " << filename << ": state " << lastRow << " has no transition to itself."); } } diff --git a/src/parser/DeterministicSparseTransitionParser.h b/src/parser/DeterministicSparseTransitionParser.h index 2407f3398..870b1415e 100644 --- a/src/parser/DeterministicSparseTransitionParser.h +++ b/src/parser/DeterministicSparseTransitionParser.h @@ -17,7 +17,7 @@ namespace parser { */ class DeterministicSparseTransitionParser : public Parser { public: - DeterministicSparseTransitionParser(std::string const &filename, bool insertDiagonalEntriesIfMissing = true); + DeterministicSparseTransitionParser(std::string const &filename, bool insertDiagonalEntriesIfMissing = true, RewardMatrixInformationStruct* rewardMatrixInformation = nullptr); std::shared_ptr> getMatrix() { return this->matrix; @@ -26,7 +26,7 @@ class DeterministicSparseTransitionParser : public Parser { private: std::shared_ptr> matrix; - uint_fast64_t firstPass(char* buf, int_fast64_t &maxnode); + uint_fast64_t firstPass(char* buf, uint_fast64_t &maxnode, RewardMatrixInformationStruct* rewardMatrixInformation); }; diff --git a/src/parser/NondeterministicModelParser.cpp b/src/parser/NondeterministicModelParser.cpp index 87eb3cc57..a0ce35674 100644 --- a/src/parser/NondeterministicModelParser.cpp +++ b/src/parser/NondeterministicModelParser.cpp @@ -30,7 +30,7 @@ namespace parser { NondeterministicModelParser::NondeterministicModelParser(std::string const & transitionSystemFile, std::string const & labelingFile, std::string const & stateRewardFile, std::string const & transitionRewardFile) { storm::parser::NondeterministicSparseTransitionParser tp(transitionSystemFile); - uint_fast64_t stateCount = tp.getMatrix()->getRowCount(); + uint_fast64_t stateCount = tp.getMatrix()->getColumnCount(); storm::parser::AtomicPropositionLabelingParser lp(stateCount, labelingFile); if (stateRewardFile != "") { @@ -38,7 +38,9 @@ NondeterministicModelParser::NondeterministicModelParser(std::string const & tra this->stateRewards = srp.getStateRewards(); } if (transitionRewardFile != "") { - storm::parser::NondeterministicSparseTransitionParser trp(transitionRewardFile); + RewardMatrixInformationStruct* rewardMatrixInfo = new RewardMatrixInformationStruct(tp.getMatrix()->getRowCount(), tp.getMatrix()->getColumnCount(), tp.getRowMapping()); + storm::parser::NondeterministicSparseTransitionParser trp(transitionRewardFile, rewardMatrixInfo); + delete rewardMatrixInfo; this->transitionRewardMatrix = trp.getMatrix(); } diff --git a/src/parser/NondeterministicSparseTransitionParser.cpp b/src/parser/NondeterministicSparseTransitionParser.cpp index f899aa73b..7899c3df8 100644 --- a/src/parser/NondeterministicSparseTransitionParser.cpp +++ b/src/parser/NondeterministicSparseTransitionParser.cpp @@ -49,30 +49,15 @@ namespace parser { * @param maxnode Is set to highest id of all nodes. * @return The number of non-zero elements. */ -uint_fast64_t NondeterministicSparseTransitionParser::firstPass(char* buf, uint_fast64_t& choices, int_fast64_t& maxnode) { +uint_fast64_t NondeterministicSparseTransitionParser::firstPass(char* buf, uint_fast64_t& choices, int_fast64_t& maxnode, RewardMatrixInformationStruct* rewardMatrixInformation) { + bool isRewardFile = rewardMatrixInformation != nullptr; + /* * Check file header and extract number of transitions. */ - buf = strchr(buf, '\n') + 1; // skip format hint - if (strncmp(buf, "STATES ", 7) != 0) { - LOG4CPLUS_ERROR(logger, "Expected \"STATES\" but got \"" << std::string(buf, 0, 16) << "\"."); - return 0; - } - buf += 7; // skip "STATES " - if (strtol(buf, &buf, 10) == 0) return 0; - buf = trimWhitespaces(buf); - if (strncmp(buf, "TRANSITIONS ", 12) != 0) { - LOG4CPLUS_ERROR(logger, "Expected \"TRANSITIONS\" but got \"" << std::string(buf, 0, 16) << "\"."); - return 0; + if (!isRewardFile) { + buf = strchr(buf, '\n') + 1; // skip format hint } - buf += 12; // skip "TRANSITIONS " - /* - * Parse number of transitions. - * We will not actually use this value, but we will compare it to the - * number of transitions we count and issue a warning if this parsed - * value is wrong. - */ - uint_fast64_t parsed_nonzero = strtol(buf, &buf, 10); /* * Read all transitions. @@ -97,16 +82,37 @@ uint_fast64_t NondeterministicSparseTransitionParser::firstPass(char* buf, uint_ maxnode = source; } - // If we have skipped some states, we need to reserve the space for the self-loop insertion - // in the second pass. - if (source > lastsource + 1) { - nonzero += source - lastsource - 1; - choices += source - lastsource - 1; - parsed_nonzero += source - lastsource - 1; - } else if (source != lastsource || choice != lastchoice) { - // If we have switched the source state or the nondeterministic choice, we need to - // reserve one row more. - ++choices; + if (isRewardFile) { + // If we have switched the source state, we possibly need to insert the rows of the last + // last source state. + if (source != lastsource && lastsource != -1) { + choices += lastchoice - ((*rewardMatrixInformation->nondeterministicChoiceIndices)[lastsource + 1] - (*rewardMatrixInformation->nondeterministicChoiceIndices)[lastsource] - 1); + } + + // If we skipped some states, we need to reserve empty rows for all their nondeterministic + // choices. + for (int_fast64_t i = lastsource + 1; i < source; ++i) { + choices += ((*rewardMatrixInformation->nondeterministicChoiceIndices)[i + 1] - (*rewardMatrixInformation->nondeterministicChoiceIndices)[i]); + } + + // If we advanced to the next state, but skipped some choices, we have to reserve rows + // for them + if (source != lastsource) { + choices += choice + 1; + } else if (choice != lastchoice) { + choices += choice - lastchoice; + } + } else { + // If we have skipped some states, we need to reserve the space for the self-loop insertion + // in the second pass. + if (source > lastsource + 1) { + nonzero += source - lastsource - 1; + choices += source - lastsource - 1; + } else if (source != lastsource || choice != lastchoice) { + // If we have switched the source state or the nondeterministic choice, we need to + // reserve one row more. + ++choices; + } } // Read target and check if we encountered a state index that is bigger than all previously @@ -147,12 +153,17 @@ uint_fast64_t NondeterministicSparseTransitionParser::firstPass(char* buf, uint_ buf = trimWhitespaces(buf); } - /* - * Check if the number of transitions given in the file is correct. - */ - if (nonzero != parsed_nonzero) { - LOG4CPLUS_WARN(logger, "File states to have " << parsed_nonzero << " transitions, but I counted " << nonzero << ". Maybe want to fix your file?"); + if (isRewardFile) { + // If not all rows were filled for the last state, we need to insert them. + choices += lastchoice - ((*rewardMatrixInformation->nondeterministicChoiceIndices)[lastsource + 1] - (*rewardMatrixInformation->nondeterministicChoiceIndices)[lastsource] - 1); + + // If we skipped some states, we need to reserve empty rows for all their nondeterministic + // choices. + for (uint_fast64_t i = lastsource + 1; i < rewardMatrixInformation->nondeterministicChoiceIndices->size() - 1; ++i) { + choices += ((*rewardMatrixInformation->nondeterministicChoiceIndices)[i + 1] - (*rewardMatrixInformation->nondeterministicChoiceIndices)[i]); + } } + return nonzero; } @@ -166,13 +177,15 @@ uint_fast64_t NondeterministicSparseTransitionParser::firstPass(char* buf, uint_ * @return a pointer to the created sparse matrix. */ -NondeterministicSparseTransitionParser::NondeterministicSparseTransitionParser(std::string const &filename) +NondeterministicSparseTransitionParser::NondeterministicSparseTransitionParser(std::string const &filename, RewardMatrixInformationStruct* rewardMatrixInformation) : matrix(nullptr) { /* * Enforce locale where decimal point is '.'. */ setlocale(LC_NUMERIC, "C"); + bool isRewardFile = rewardMatrixInformation != nullptr; + /* * Open file. */ @@ -184,7 +197,7 @@ NondeterministicSparseTransitionParser::NondeterministicSparseTransitionParser(s */ int_fast64_t maxnode; uint_fast64_t choices; - uint_fast64_t nonzero = this->firstPass(file.data, choices, maxnode); + uint_fast64_t nonzero = this->firstPass(file.data, choices, maxnode, rewardMatrixInformation); /* * If first pass returned zero, the file format was wrong. @@ -201,14 +214,23 @@ NondeterministicSparseTransitionParser::NondeterministicSparseTransitionParser(s */ /* - * Read file header, ignore values within. + * Skip file header. */ - buf = strchr(buf, '\n') + 1; // skip format hint - buf += 7; // skip "STATES " - checked_strtol(buf, &buf); - buf = trimWhitespaces(buf); - buf += 12; // skip "TRANSITIONS " - checked_strtol(buf, &buf); + if (!isRewardFile) { + buf = strchr(buf, '\n') + 1; // skip format hint + } + + if (isRewardFile) { + if (choices > rewardMatrixInformation->rowCount || (uint_fast64_t)(maxnode + 1) > rewardMatrixInformation->columnCount) { + LOG4CPLUS_ERROR(logger, "Reward matrix size exceeds transition matrix size."); + throw storm::exceptions::WrongFormatException() << "Reward matrix size exceeds transition matrix size."; + } else if (choices != rewardMatrixInformation->rowCount) { + LOG4CPLUS_ERROR(logger, "Reward matrix row count does not match transition matrix row count."); + throw storm::exceptions::WrongFormatException() << "Reward matrix row count does not match transition matrix row count."; + } else { + maxnode = rewardMatrixInformation->columnCount - 1; + } + } /* * Create and initialize matrix. @@ -247,33 +269,54 @@ NondeterministicSparseTransitionParser::NondeterministicSparseTransitionParser(s source = checked_strtol(buf, &buf); choice = checked_strtol(buf, &buf); - // Increase line count if we have either finished reading the transitions of a certain state - // or we have finished reading one nondeterministic choice of a state. - if ((source != lastsource || choice != lastchoice)) { - ++curRow; - } + if (isRewardFile) { + // If we have switched the source state, we possibly need to insert the rows of the last + // last source state. + if (source != lastsource && lastsource != -1) { + curRow += lastchoice - ((*rewardMatrixInformation->nondeterministicChoiceIndices)[lastsource + 1] - (*rewardMatrixInformation->nondeterministicChoiceIndices)[lastsource] - 1); + } - /* - * Check if we have skipped any source node, i.e. if any node has no - * outgoing transitions. If so, insert a self-loop. - * Also add self-loops to rowMapping. - */ - for (int_fast64_t node = lastsource + 1; node < source; node++) { - hadDeadlocks = true; - if (fixDeadlocks) { - this->rowMapping->at(node) = curRow; - this->matrix->addNextValue(curRow, node, 1); + // If we skipped some states, we need to reserve empty rows for all their nondeterministic + // choices. + for (int_fast64_t i = lastsource + 1; i < source; ++i) { + curRow += ((*rewardMatrixInformation->nondeterministicChoiceIndices)[i + 1] - (*rewardMatrixInformation->nondeterministicChoiceIndices)[i]); + } + + // If we advanced to the next state, but skipped some choices, we have to reserve rows + // for them + if (source != lastsource) { + curRow += choice + 1; + } else if (choice != lastchoice) { + curRow += choice - lastchoice; + } + } else { + // Increase line count if we have either finished reading the transitions of a certain state + // or we have finished reading one nondeterministic choice of a state. + if ((source != lastsource || choice != lastchoice)) { ++curRow; - LOG4CPLUS_WARN(logger, "Warning while parsing " << filename << ": node " << node << " has no outgoing transitions. A self-loop was inserted."); - } else { - LOG4CPLUS_ERROR(logger, "Error while parsing " << filename << ": node " << node << " has no outgoing transitions."); } - } - if (source != lastsource) { /* - * Add this source to rowMapping, if this is the first choice we encounter for this state. + * Check if we have skipped any source node, i.e. if any node has no + * outgoing transitions. If so, insert a self-loop. + * Also add self-loops to rowMapping. */ - this->rowMapping->at(source) = curRow; + for (int_fast64_t node = lastsource + 1; node < source; node++) { + hadDeadlocks = true; + if (fixDeadlocks) { + this->rowMapping->at(node) = curRow; + this->matrix->addNextValue(curRow, node, 1); + ++curRow; + LOG4CPLUS_WARN(logger, "Warning while parsing " << filename << ": node " << node << " has no outgoing transitions. A self-loop was inserted."); + } else { + LOG4CPLUS_ERROR(logger, "Error while parsing " << filename << ": node " << node << " has no outgoing transitions."); + } + } + if (source != lastsource) { + /* + * Add this source to rowMapping, if this is the first choice we encounter for this state. + */ + this->rowMapping->at(source) = curRow; + } } // Read target and value and write it to the matrix. @@ -294,9 +337,11 @@ NondeterministicSparseTransitionParser::NondeterministicSparseTransitionParser(s buf = trimWhitespaces(buf); } - this->rowMapping->at(maxnode+1) = curRow + 1; + for (int_fast64_t node = lastsource + 1; node <= maxnode + 1; node++) { + this->rowMapping->at(node) = curRow + 1; + } - if (!fixDeadlocks && hadDeadlocks) throw storm::exceptions::WrongFormatException() << "Some of the nodes had deadlocks. You can use --fix-deadlocks to insert self-loops on the fly."; + if (!fixDeadlocks && hadDeadlocks && !isRewardFile) throw storm::exceptions::WrongFormatException() << "Some of the nodes had deadlocks. You can use --fix-deadlocks to insert self-loops on the fly."; /* * Finalize matrix. diff --git a/src/parser/NondeterministicSparseTransitionParser.h b/src/parser/NondeterministicSparseTransitionParser.h index 209c0e7a0..5731017ec 100644 --- a/src/parser/NondeterministicSparseTransitionParser.h +++ b/src/parser/NondeterministicSparseTransitionParser.h @@ -19,7 +19,7 @@ namespace parser { */ class NondeterministicSparseTransitionParser : public Parser { public: - NondeterministicSparseTransitionParser(std::string const &filename); + NondeterministicSparseTransitionParser(std::string const &filename, RewardMatrixInformationStruct* rewardMatrixInformation = nullptr); inline std::shared_ptr> getMatrix() const { return this->matrix; @@ -33,7 +33,7 @@ class NondeterministicSparseTransitionParser : public Parser { std::shared_ptr> matrix; std::shared_ptr> rowMapping; - uint_fast64_t firstPass(char* buf, uint_fast64_t& choices, int_fast64_t& maxnode); + uint_fast64_t firstPass(char* buf, uint_fast64_t& choices, int_fast64_t& maxnode, RewardMatrixInformationStruct* rewardMatrixInformation); }; diff --git a/src/parser/Parser.cpp b/src/parser/Parser.cpp index f659df13f..3a824dd58 100644 --- a/src/parser/Parser.cpp +++ b/src/parser/Parser.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "src/exceptions/FileIoException.h" #include "src/exceptions/WrongFormatException.h" @@ -75,21 +76,21 @@ storm::parser::MappedFile::MappedFile(const char* filename) { #else if (stat64(filename, &(this->st)) != 0) { #endif - LOG4CPLUS_ERROR(logger, "Error in stat(" << filename << ")."); - throw exceptions::FileIoException("storm::parser::MappedFile Error in stat()"); + LOG4CPLUS_ERROR(logger, "Error in stat(" << filename << "): " << std::strerror(errno)); + throw exceptions::FileIoException() << "storm::parser::MappedFile Error in stat(): " << std::strerror(errno); } this->file = open(filename, O_RDONLY); if (this->file < 0) { - LOG4CPLUS_ERROR(logger, "Error in open(" << filename << ")."); - throw exceptions::FileIoException("storm::parser::MappedFile Error in open()"); + LOG4CPLUS_ERROR(logger, "Error in open(" << filename << "): " << std::strerror(errno)); + throw exceptions::FileIoException() << "storm::parser::MappedFile Error in open(): " << std::strerror(errno); } this->data = reinterpret_cast(mmap(NULL, this->st.st_size, PROT_READ, MAP_PRIVATE, this->file, 0)); if (this->data == reinterpret_cast(-1)) { close(this->file); - LOG4CPLUS_ERROR(logger, "Error in mmap(" << filename << ")."); - throw exceptions::FileIoException("storm::parser::MappedFile Error in mmap()"); + LOG4CPLUS_ERROR(logger, "Error in mmap(" << filename << "): " << std::strerror(errno)); + throw exceptions::FileIoException() << "storm::parser::MappedFile Error in mmap(): " << std::strerror(errno); } this->dataend = this->data + this->st.st_size; #elif defined WINDOWS diff --git a/src/parser/Parser.h b/src/parser/Parser.h index fd17cd089..0d3a70a39 100644 --- a/src/parser/Parser.h +++ b/src/parser/Parser.h @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include #include "src/exceptions/FileIoException.h" @@ -30,6 +32,21 @@ namespace storm { */ namespace parser { + struct RewardMatrixInformationStruct { + RewardMatrixInformationStruct() : rowCount(0), columnCount(0), nondeterministicChoiceIndices(nullptr) { + // Intentionally left empty. + } + + RewardMatrixInformationStruct(uint_fast64_t rowCount, uint_fast64_t columnCount, std::shared_ptr> nondeterministicChoiceIndices) + : rowCount(rowCount), columnCount(columnCount), nondeterministicChoiceIndices(nondeterministicChoiceIndices) { + // Intentionally left empty. + } + + uint_fast64_t rowCount; + uint_fast64_t columnCount; + std::shared_ptr> nondeterministicChoiceIndices; + }; + /*! * @brief Opens a file and maps it to memory providing a char* * containing the file content. diff --git a/src/storage/BitVector.h b/src/storage/BitVector.h index 801ec2bb2..570283c4d 100644 --- a/src/storage/BitVector.h +++ b/src/storage/BitVector.h @@ -137,7 +137,7 @@ public: * @param bv A reference to the bit vector to be copied. */ BitVector(BitVector const& bv) : bucketCount(bv.bucketCount), bitCount(bv.bitCount), endIterator(*this, bitCount, bitCount, false), truncateMask((1ll << (bitCount & mod64mask)) - 1ll) { - LOG4CPLUS_WARN(logger, "Invoking copy constructor."); + LOG4CPLUS_DEBUG(logger, "Invoking copy constructor."); bucketArray = new uint64_t[bucketCount]; std::copy(bv.bucketArray, bv.bucketArray + this->bucketCount, this->bucketArray); } @@ -392,6 +392,40 @@ public: return result; } + /*! + * Checks whether all bits that are set in the current bit vector are also set in the given bit + * vector. + * @param bv A reference to the bit vector whose bits are (possibly) a superset of the bits of + * the current bit vector. + * @returns True iff all bits that are set in the current bit vector are also set in the given bit + * vector. + */ + bool isContainedIn(BitVector const& bv) const { + for (uint_fast64_t i = 0; i < this->bucketCount; ++i) { + if ((this->bucketArray[i] & bv.bucketArray[i]) != bv.bucketArray[i]) { + return false; + } + } + return true; + } + + /*! + * Checks whether none of the bits that are set in the current bit vector are also set in the + * given bit vector. + * @param bv A reference to the bit vector whose bits are (possibly) disjoint from the bits in + * the current bit vector. + * @returns True iff none of the bits that are set in the current bit vector are also set in the + * given bit vector. + */ + bool isDisjointFrom(BitVector const& bv) const { + for (uint_fast64_t i = 0; i < this->bucketCount; ++i) { + if ((this->bucketArray[i] & bv.bucketArray[i]) != 0) { + return false; + } + } + return true; + } + /*! * Adds all indices of bits set to one to the provided list. * @param list The list to which to append the indices. @@ -513,7 +547,7 @@ private: startingIndex >>= 6; uint64_t* bucketPtr = this->bucketArray + startingIndex; - do { + while ((startingIndex << 6) < endIndex) { // Compute the remaining bucket content by a right shift // to the current bit. uint_fast64_t remainingInBucket = *bucketPtr >> currentBitInByte; @@ -536,7 +570,7 @@ private: // Advance to the next bucket. ++startingIndex; ++bucketPtr; currentBitInByte = 0; - } while ((startingIndex << 6) < endIndex); + } return endIndex; } diff --git a/src/storage/JacobiDecomposition.h b/src/storage/JacobiDecomposition.h index 9aec6f061..ef4f8bb99 100644 --- a/src/storage/JacobiDecomposition.h +++ b/src/storage/JacobiDecomposition.h @@ -28,6 +28,11 @@ template class JacobiDecomposition { public: + /*! + * Standard constructor + * Initializes this object with the two given sparse matrices + * Ownership of both matrices stay with THIS object. + */ JacobiDecomposition(storm::storage::SparseMatrix * const jacobiLuMatrix, storm::storage::SparseMatrix * const jacobiDInvMatrix) : jacobiLuMatrix(jacobiLuMatrix), jacobiDInvMatrix(jacobiDInvMatrix) { } diff --git a/src/storage/SparseMatrix.h b/src/storage/SparseMatrix.h index 62ed99eca..5259a8052 100644 --- a/src/storage/SparseMatrix.h +++ b/src/storage/SparseMatrix.h @@ -29,6 +29,7 @@ namespace storm { namespace adapters{ class GmmxxAdapter; class EigenAdapter; + class StormAdapter; } } @@ -48,6 +49,7 @@ public: */ friend class storm::adapters::GmmxxAdapter; friend class storm::adapters::EigenAdapter; + friend class storm::adapters::StormAdapter; /*! * If we only want to iterate over the columns of the non-zero entries of @@ -60,6 +62,44 @@ public: */ typedef const T* const constIterator; + class constRowsIterator { + public: + constRowsIterator(SparseMatrix const& matrix) : matrix(matrix), offset(0) { + // Intentionally left empty. + } + + constRowsIterator& operator++() { + ++offset; + return *this; + } + + bool operator==(constRowsIterator const& other) const { + return offset == other.offset; + } + + bool operator!=(uint_fast64_t offset) const { + return this->offset != offset; + } + + uint_fast64_t column() const { + return matrix.columnIndications[offset]; + } + + T const& value() const { + return matrix.valueStorage[offset]; + } + + void setOffset(uint_fast64_t offset) { + this->offset = offset; + } + + private: + SparseMatrix const& matrix; + uint_fast64_t offset; + }; + + friend class constRowsIterator; + /*! * An enum representing the internal state of the Matrix. * After creating the Matrix using the Constructor, the Object is in state UnInitialized. After calling initialize(), that state changes to Initialized and after all entries have been entered and finalize() has been called, to ReadReady. @@ -145,8 +185,7 @@ public: * Initializes the sparse matrix with the given number of non-zero entries * and prepares it for use with addNextValue() and finalize(). * NOTE: Calling this method before any other member function is mandatory. - * This version is to be used together with addNextValue(). For - * initialization from an Eigen SparseMatrix, use initialize(Eigen::SparseMatrix &). + * This version is to be used together with addNextValue(). * @param nonZeroEntries The number of non-zero entries that are not on the * diagonal. */ @@ -179,135 +218,6 @@ public: } } - /*! - * Initializes the sparse matrix with the given Eigen sparse matrix. - * NOTE: Calling this method before any other member function is mandatory. - * This version is only to be used when copying an Eigen sparse matrix. For - * initialization with addNextValue() and finalize() use initialize(uint_fast32_t) - * instead. - * @param eigenSparseMatrix The Eigen sparse matrix to be copied. - * *NOTE* Has to be in compressed form! - */ - template - void initialize(const Eigen::SparseMatrix& eigenSparseMatrix) { - // Throw an error in case the matrix is not in compressed format. - if (!eigenSparseMatrix.isCompressed()) { - triggerErrorState(); - LOG4CPLUS_ERROR(logger, "Trying to initialize from an Eigen matrix that is not in compressed form."); - throw storm::exceptions::InvalidArgumentException("Trying to initialize from an Eigen matrix that is not in compressed form."); - } - - if (static_cast(eigenSparseMatrix.rows()) > this->rowCount) { - triggerErrorState(); - LOG4CPLUS_ERROR(logger, "Trying to initialize from an Eigen matrix that has more rows than the target matrix."); - throw storm::exceptions::InvalidArgumentException("Trying to initialize from an Eigen matrix that has more rows than the target matrix."); - } - if (static_cast(eigenSparseMatrix.cols()) > this->colCount) { - triggerErrorState(); - LOG4CPLUS_ERROR(logger, "Trying to initialize from an Eigen matrix that has more columns than the target matrix."); - throw storm::exceptions::InvalidArgumentException("Trying to initialize from an Eigen matrix that has more columns than the target matrix."); - } - - const uint_fast64_t entryCount = eigenSparseMatrix.nonZeros(); - nonZeroEntryCount = entryCount; - lastRow = 0; - - // Try to prepare the internal storage and throw an error in case of - // failure. - - // Get necessary pointers to the contents of the Eigen matrix. - const T* valuePtr = eigenSparseMatrix.valuePtr(); - const _Index* indexPtr = eigenSparseMatrix.innerIndexPtr(); - const _Index* outerPtr = eigenSparseMatrix.outerIndexPtr(); - - // If the given matrix is in RowMajor format, copying can simply - // be done by adding all values in order. - // Direct copying is, however, prevented because we have to - // separate the diagonal entries from others. - if (isEigenRowMajor(eigenSparseMatrix)) { - // Because of the RowMajor format outerSize evaluates to the - // number of rows. - if (!prepareInternalStorage(false)) { - triggerErrorState(); - LOG4CPLUS_ERROR(logger, "Unable to allocate internal storage."); - throw std::bad_alloc(); - } else { - if ((static_cast(eigenSparseMatrix.innerSize()) > nonZeroEntryCount) || (static_cast(entryCount) > nonZeroEntryCount)) { - triggerErrorState(); - LOG4CPLUS_ERROR(logger, "Invalid internal composition of Eigen Sparse Matrix"); - throw storm::exceptions::InvalidArgumentException("Invalid internal composition of Eigen Sparse Matrix"); - } - std::vector eigenColumnTemp; - std::vector eigenRowTemp; - std::vector eigenValueTemp; - uint_fast64_t outerSize = eigenSparseMatrix.outerSize() + 1; - - uint_fast64_t entryCountUnsigned = static_cast(entryCount); - for (uint_fast64_t i = 0; i < entryCountUnsigned; ++i) { - eigenColumnTemp.push_back(indexPtr[i]); - eigenValueTemp.push_back(valuePtr[i]); - } - for (uint_fast64_t i = 0; i < outerSize; ++i) { - eigenRowTemp.push_back(outerPtr[i]); - } - - std::copy(eigenRowTemp.begin(), eigenRowTemp.end(), std::back_inserter(this->rowIndications)); - std::copy(eigenColumnTemp.begin(), eigenColumnTemp.end(), std::back_inserter(this->columnIndications)); - std::copy(eigenValueTemp.begin(), eigenValueTemp.end(), std::back_inserter(this->valueStorage)); - - currentSize = entryCount; - lastRow = rowCount; - } - } else { - if (!prepareInternalStorage()) { - triggerErrorState(); - LOG4CPLUS_ERROR(logger, "Unable to allocate internal storage."); - throw std::bad_alloc(); - } else { - // Because of the ColMajor format outerSize evaluates to the - // number of columns. - const _Index colCount = eigenSparseMatrix.outerSize(); - - // Create an array to remember which elements have to still - // be searched in each column and initialize it with the starting - // index for every column. - _Index* positions = new _Index[colCount](); - for (_Index i = 0; i < colCount; ++i) { - positions[i] = outerPtr[i]; - } - - // Now copy the elements. As the matrix is in ColMajor format, - // we need to iterate over the columns to find the next non-zero - // entry. - uint_fast64_t i = 0; - int currentRow = 0; - int currentColumn = 0; - while (i < entryCount) { - // If the current element belongs the the current column, - // add it in case it is also in the current row. - if ((positions[currentColumn] < outerPtr[currentColumn + 1]) - && (indexPtr[positions[currentColumn]] == currentRow)) { - addNextValue(currentRow, currentColumn, valuePtr[positions[currentColumn]]); - // Remember that we found one more non-zero element. - ++i; - // Mark this position as "used". - ++positions[currentColumn]; - } - - // Now we can advance to the next column and also row, - // in case we just iterated through the last column. - ++currentColumn; - if (currentColumn == colCount) { - currentColumn = 0; - ++currentRow; - } - } - delete[] positions; - } - } - setState(MatrixStatus::Initialized); - } - /*! * Sets the matrix element at the given row and column to the given value. * NOTE: This is a linear setter. It must be called consecutively for each element, @@ -354,8 +264,8 @@ public: throw storm::exceptions::InvalidStateException("Trying to finalize an uninitialized matrix."); } else if (currentSize != nonZeroEntryCount) { triggerErrorState(); - LOG4CPLUS_ERROR(logger, "Trying to finalize a matrix that was initialized with more non-zero entries than given."); - throw storm::exceptions::InvalidStateException("Trying to finalize a matrix that was initialized with more non-zero entries than given."); + LOG4CPLUS_ERROR(logger, "Trying to finalize a matrix that was initialized with more non-zero entries than given (expected " << nonZeroEntryCount << " but got " << currentSize << " instead)"); + throw storm::exceptions::InvalidStateException() << "Trying to finalize a matrix that was initialized with more non-zero entries than given (expected " << nonZeroEntryCount << " but got " << currentSize << " instead)."; } else { // Fill in the missing entries in the row_indications array. // (Can happen because of empty rows at the end.) @@ -535,96 +445,6 @@ public: return (internalStatus == MatrixStatus::Error); } - /*! - * Exports this sparse matrix to Eigens sparse matrix format. - * NOTE: this requires this matrix to be in the ReadReady state. - * @return The sparse matrix in Eigen format. - */ - Eigen::SparseMatrix* toEigenSparseMatrix() { - // Check whether it is safe to export this matrix. - if (!isReadReady()) { - triggerErrorState(); - LOG4CPLUS_ERROR(logger, "Trying to convert a matrix that is not in a readable state to an Eigen matrix."); - throw storm::exceptions::InvalidStateException("Trying to convert a matrix that is not in a readable state to an Eigen matrix."); - } else { - // Create the resulting matrix. - int_fast32_t eigenRows = static_cast(rowCount); - Eigen::SparseMatrix* mat = new Eigen::SparseMatrix(eigenRows, eigenRows); - - // There are two ways of converting this matrix to Eigen's format. - // 1. Compute a list of triplets (row, column, value) for all - // non-zero elements and pass it to Eigen to create a sparse matrix. - // 2. Tell Eigen to reserve the average number of non-zero elements - // per row in advance and insert the values via a call to Eigen's - // insert method then. As the given reservation number is only an - // estimate, the actual number may be different and Eigen may have - // to shift a lot. - // In most cases, the second alternative is faster (about 1/2 of the - // first, but if there are "heavy" rows that are several times larger - // than an average row, the other solution might be faster. - // The desired conversion method may be set by an appropriate define. - -#define STORM_USE_TRIPLETCONVERT -# ifdef STORM_USE_TRIPLETCONVERT - - // Prepare the triplet storage. - typedef Eigen::Triplet IntTriplet; - std::vector tripletList; - tripletList.reserve(nonZeroEntryCount + rowCount); - - // First, iterate over all elements that are not on the diagonal - // and add the corresponding triplet. - uint_fast64_t rowStart; - uint_fast64_t rowEnd; - uint_fast64_t zeroCount = 0; - for (uint_fast64_t row = 0; row < rowCount; ++row) { - rowStart = rowIndications[row]; - rowEnd = rowIndications[row + 1]; - while (rowStart < rowEnd) { - if (valueStorage[rowStart] == 0) zeroCount++; - tripletList.push_back(IntTriplet(static_cast(row), static_cast(columnIndications[rowStart]), valueStorage[rowStart])); - ++rowStart; - } - } - - // Let Eigen create a matrix from the given list of triplets. - mat->setFromTriplets(tripletList.begin(), tripletList.end()); - -# else // NOT STORM_USE_TRIPLETCONVERT - - // Reserve the average number of non-zero elements per row for each - // row. - mat->reserve(Eigen::VectorXi::Constant(eigenRows, static_cast((nonZeroEntryCount + rowCount) / eigenRows))); - - // Iterate over the all non-zero elements in this matrix and add - // them to the matrix individually. - uint_fast64_t rowStart; - uint_fast64_t rowEnd; - uint_fast64_t count = 0; - for (uint_fast64_t row = 0; row < rowCount; ++row) { - rowStart = rowIndications[row]; - rowEnd = rowIndications[row + 1]; - - // Insert the elements that are not on the diagonal - while (rowStart < rowEnd) { - mat->insert(row, columnIndications[rowStart]) = valueStorage[rowStart]; - count++; - ++rowStart; - } - } -# endif // STORM_USE_TRIPLETCONVERT - - // Make the matrix compressed, i.e. remove remaining zero-entries. - mat->makeCompressed(); - - return mat; - } - - // This point can never be reached as both if-branches end in a return - // statement. - return nullptr; - } - /*! * Returns the number of non-zero entries that are not on the diagonal. * @returns The number of non-zero entries that are not on the diagonal. @@ -932,20 +752,25 @@ public: /*! * Calculates the Jacobi-Decomposition of this sparse matrix. + * The source Sparse Matrix must be square. * @return A pointer to a class containing the matrix L+U and the inverted diagonal matrix D^-1 */ storm::storage::JacobiDecomposition* getJacobiDecomposition() const { uint_fast64_t rowCount = this->getRowCount(); - SparseMatrix *resultLU = new SparseMatrix(this); - SparseMatrix *resultDinv = new SparseMatrix(rowCount); - // no entries apart from those on the diagonal - resultDinv->initialize(0); + uint_fast64_t colCount = this->getColumnCount(); + if (rowCount != colCount) { + throw storm::exceptions::InvalidArgumentException() << "SparseMatrix::getJacobiDecomposition requires the Matrix to be square!"; + } + storm::storage::SparseMatrix *resultLU = new storm::storage::SparseMatrix(*this); + storm::storage::SparseMatrix *resultDinv = new storm::storage::SparseMatrix(rowCount, colCount); + // no entries apart from those on the diagonal (rowCount) + resultDinv->initialize(rowCount); // constant 1 for diagonal inversion T constOne = storm::utility::constGetOne(); // copy diagonal entries to other matrix - for (int i = 0; i < rowCount; ++i) { + for (unsigned int i = 0; i < rowCount; ++i) { resultDinv->addNextValue(i, i, constOne / resultLU->getValue(i, i)); resultLU->getValue(i, i) = storm::utility::constGetZero(); } @@ -964,7 +789,7 @@ public: */ std::vector* getPointwiseProductRowSumVector(storm::storage::SparseMatrix const& otherMatrix) { // Prepare result. - std::vector* result = new std::vector(rowCount); + std::vector* result = new std::vector(rowCount, storm::utility::constGetZero()); // Iterate over all elements of the current matrix and either continue with the next element // in case the given matrix does not have a non-zero element at this column position, or @@ -977,7 +802,7 @@ public: // If the precondition of this method (i.e. that the given matrix is a submatrix // of the current one) was fulfilled, we know now that the two elements are in // the same column, so we can multiply and add them to the row sum vector. - (*result)[row] += otherMatrix.valueStorage[element] * valueStorage[nextOtherElement]; + (*result)[row] += otherMatrix.valueStorage[nextOtherElement] * valueStorage[element]; ++nextOtherElement; } } @@ -986,18 +811,80 @@ public: return result; } - T getRowVectorProduct(uint_fast64_t row, std::vector& vector) { - T result = storm::utility::constGetZero();; - auto valueIterator = valueStorage.begin() + rowIndications[row]; - const auto valueIteratorEnd = valueStorage.begin() + rowIndications[row + 1]; - auto columnIterator = columnIndications.begin() + rowIndications[row]; - const auto columnIteratorEnd = columnIndications.begin() + rowIndications[row + 1]; - for (; valueIterator != valueIteratorEnd; ++valueIterator, ++columnIterator) { - result += *valueIterator * vector[*columnIterator]; + T multiplyRowWithVector(constRowsIterator& rowsIt, uint_fast64_t rowsIte, std::vector& vector) const { + T result = storm::utility::constGetZero(); + for (; rowsIt != rowsIte; ++rowsIt) { + result += (rowsIt.value()) * vector[rowsIt.column()]; } return result; } + void multiplyWithVector(std::vector& vector, std::vector& result) const { + typename std::vector::iterator resultIt = result.begin(); + typename std::vector::iterator resultIte = result.end(); + constRowsIterator rowIt = this->constRowsIteratorBegin(); + uint_fast64_t nextRow = 1; + + for (; resultIt != resultIte; ++resultIt, ++nextRow) { + *resultIt = multiplyRowWithVector(rowIt, this->rowIndications[nextRow], vector); + } + } + + void multiplyWithVectorInPlace(std::vector& vector) const { + typename std::vector::iterator resultIt = vector.begin(); + typename std::vector::iterator resultIte = vector.end(); + constRowsIterator rowIt = this->constRowsIteratorBegin(); + uint_fast64_t nextRow = 1; + + for (; resultIt != resultIte; ++resultIt, ++nextRow) { + *resultIt = multiplyRowWithVector(rowIt, this->rowIndications[nextRow], vector); + } + } + + void multiplyWithVector(std::vector const& states, std::vector const& nondeterministicChoiceIndices, std::vector& vector, std::vector& result) const { + constRowsIterator rowsIt = this->constRowsIteratorBegin(); + uint_fast64_t nextRow = 1; + + for (auto stateIt = states.cbegin(), stateIte = states.cend(); stateIt != stateIte; ++stateIt) { + rowsIt.setOffset(this->rowIndications[nondeterministicChoiceIndices[*stateIt]]); + nextRow = nondeterministicChoiceIndices[*stateIt] + 1; + for (auto rowIt = nondeterministicChoiceIndices[*stateIt], rowIte = nondeterministicChoiceIndices[*stateIt + 1]; rowIt != rowIte; ++rowIt, ++nextRow) { + result[rowIt] = multiplyRowWithVector(rowsIt, this->rowIndications[nextRow], vector); + } + } + } + + void multiplyWithVectorInPlace(std::vector const& states, std::vector const& nondeterministicChoiceIndices, std::vector& vector) const { + constRowsIterator rowsIt = this->constRowsIteratorBegin(); + uint_fast64_t nextRow = 1; + + for (auto stateIt = states.cbegin(), stateIte = states.cend(); stateIt != stateIte; ++stateIt) { + rowsIt.setOffset(this->rowIndications[nondeterministicChoiceIndices[*stateIt]]); + nextRow = nondeterministicChoiceIndices[*stateIt] + 1; + for (auto rowIt = nondeterministicChoiceIndices[*stateIt], rowIte = nondeterministicChoiceIndices[*stateIt + 1]; rowIt != rowIte; ++rowIt, ++nextRow) { + vector[rowIt] = multiplyRowWithVector(rowsIt, this->rowIndications[nextRow], vector); + } + } + } + + void multiplyAddAndReduceInPlace(std::vector const& nondeterministicChoiceIndices, std::vector& vector, std::vector const& summand, bool minimize) const { + constRowsIterator rowsIt = this->constRowsIteratorBegin(); + uint_fast64_t nextRow = 1; + + for (uint_fast64_t stateIt = 0, stateIte = nondeterministicChoiceIndices.size() - 1; stateIt < stateIte; ++stateIt) { + vector[stateIt] = multiplyRowWithVector(rowsIt, this->rowIndications[nextRow], vector) + summand[nondeterministicChoiceIndices[stateIt]]; + ++nextRow; + for (uint_fast64_t rowIt = nondeterministicChoiceIndices[stateIt] + 1, rowIte = nondeterministicChoiceIndices[stateIt + 1]; rowIt != rowIte; ++rowIt, ++nextRow) { + T value = multiplyRowWithVector(rowsIt, this->rowIndications[nextRow], vector) + summand[rowIt]; + if (minimize && value < vector[stateIt]) { + vector[stateIt] = value; + } else if (!minimize && value > vector[stateIt]) { + vector[stateIt] = value; + } + } + } + } + /*! * Returns the size of the matrix in memory measured in bytes. * @return The size of the matrix in memory measured in bytes. @@ -1013,6 +900,10 @@ public: return size; } + constRowsIterator constRowsIteratorBegin() const { + return constRowsIterator(*this); + } + /*! * Returns an iterator to the columns of the non-zero entries of the given * row. @@ -1078,15 +969,28 @@ public: * @param matrix Matrix to check against. * @return True iff this is a submatrix of matrix. */ - bool isSubmatrixOf(SparseMatrix const & matrix) const { + bool isSubmatrixOf(SparseMatrix const& matrix) const { if (this->getRowCount() != matrix.getRowCount()) return false; if (this->getColumnCount() != matrix.getColumnCount()) return false; + /* for (uint_fast64_t row = 0; row < this->getRowCount(); ++row) { for (uint_fast64_t elem = rowIndications[row], elem2 = matrix.rowIndications[row]; elem < rowIndications[row + 1] && elem < matrix.rowIndications[row + 1]; ++elem, ++elem2) { if (columnIndications[elem] < matrix.columnIndications[elem2]) return false; } } + */ + + for (uint_fast64_t row = 0; row < this->getRowCount(); ++row) { + for (uint_fast64_t elem = rowIndications[row], elem2 = matrix.rowIndications[row]; elem < rowIndications[row + 1] && elem < matrix.rowIndications[row + 1]; ++elem) { + // Skip over all entries of the other matrix that are before the current entry in + // the current matrix. + while (elem2 < matrix.rowIndications[row + 1] && matrix.columnIndications[elem2] < columnIndications[elem]) { + ++elem2; + } + if (!(elem2 < matrix.rowIndications[row + 1]) || columnIndications[elem] != matrix.columnIndications[elem2]) return false; + } + } return true; } @@ -1110,7 +1014,7 @@ public: * to separate groups will be separated by a dashed line. * @return a (non-compressed) string representation of the matrix. */ - std::string toString(std::shared_ptr> nondeterministicChoiceIndices) const { + std::string toString(std::vector const* nondeterministicChoiceIndices) const { std::stringstream result; uint_fast64_t currentNondeterministicChoiceIndex = 0; @@ -1146,7 +1050,7 @@ public: result << i << "\t(\t"; uint_fast64_t currentRealIndex = 0; while (currentRealIndex < colCount) { - if (currentRealIndex == columnIndications[nextIndex] && nextIndex < rowIndications[i + 1]) { + if (nextIndex < rowIndications[i + 1] && currentRealIndex == columnIndications[nextIndex]) { result << valueStorage[nextIndex] << "\t"; ++nextIndex; } else { @@ -1271,29 +1175,6 @@ private: return this->prepareInternalStorage(true); } - /*! - * Helper function to determine whether the given Eigen matrix is in RowMajor - * format. Always returns true, but is overloaded, so the compiler will - * only call it in case the Eigen matrix is in RowMajor format. - * @return True. - */ - template - bool isEigenRowMajor(Eigen::SparseMatrix<_Scalar, Eigen::RowMajor, _Index>) { - return true; - } - - /*! - * Helper function to determine whether the given Eigen matrix is in RowMajor - * format. Always returns false, but is overloaded, so the compiler will - * only call it in case the Eigen matrix is in ColMajor format. - * @return False. - */ - template - bool isEigenRowMajor( - Eigen::SparseMatrix<_Scalar, Eigen::ColMajor, _Index>) { - return false; - } - }; } // namespace storage diff --git a/src/storm.cpp b/src/storm.cpp index d75982d34..28a95c01c 100644 --- a/src/storm.cpp +++ b/src/storm.cpp @@ -28,6 +28,7 @@ #include "src/parser/AutoParser.h" #include "src/parser/PrctlParser.h" #include "src/utility/Settings.h" +#include "src/utility/ErrorHandling.h" #include "src/formula/Formulas.h" #include "log4cplus/logger.h" @@ -47,6 +48,7 @@ void initializeLogger() { logger.setLogLevel(log4cplus::INFO_LOG_LEVEL); log4cplus::SharedAppenderPtr consoleLogAppender(new log4cplus::ConsoleAppender()); consoleLogAppender->setName("mainConsoleAppender"); + consoleLogAppender->setThreshold(log4cplus::WARN_LOG_LEVEL); consoleLogAppender->setLayout(std::auto_ptr(new log4cplus::PatternLayout("%-5p - %D{%H:%M:%S} (%r ms) - %b:%L: %m%n"))); logger.addAppender(consoleLogAppender); } @@ -111,18 +113,18 @@ bool parseOptions(const int argc, const char* argv[]) { return false; } - if (!s->isSet("verbose") && !s->isSet("logfile")) { - logger.setLogLevel(log4cplus::FATAL_LOG_LEVEL); - } else if (!s->isSet("verbose")) { - logger.removeAppender("mainConsoleAppender"); - setUpFileLogging(); - } else if (!s->isSet("logfile")) { + if (s->isSet("verbose")) { + logger.getAppender("mainConsoleAppender")->setThreshold(log4cplus::INFO_LOG_LEVEL); LOG4CPLUS_INFO(logger, "Enable verbose mode, log output gets printed to console."); - } else { + } + if (s->isSet("debug")) { + logger.setLogLevel(log4cplus::DEBUG_LOG_LEVEL); + logger.getAppender("mainConsoleAppender")->setThreshold(log4cplus::DEBUG_LOG_LEVEL); + LOG4CPLUS_DEBUG(logger, "Enable very verbose mode, log output gets printed to console."); + } + if (s->isSet("logfile")) { setUpFileLogging(); - LOG4CPLUS_INFO(logger, "Enable verbose mode, log output gets printed to console."); } - return true; } @@ -138,21 +140,36 @@ void cleanUp() { } void testCheckingDie(storm::models::Dtmc& dtmc) { + storm::modelChecker::GmmxxDtmcPrctlModelChecker* mc = new storm::modelChecker::GmmxxDtmcPrctlModelChecker(dtmc); + storm::formula::Ap* oneFormula = new storm::formula::Ap("one"); storm::formula::Eventually* eventuallyFormula = new storm::formula::Eventually(oneFormula); storm::formula::ProbabilisticNoBoundOperator* probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula); + mc->check(*probFormula); + delete probFormula; + + oneFormula = new storm::formula::Ap("two"); + eventuallyFormula = new storm::formula::Eventually(oneFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula); + + mc->check(*probFormula); + delete probFormula; + + oneFormula = new storm::formula::Ap("three"); + eventuallyFormula = new storm::formula::Eventually(oneFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula); + + mc->check(*probFormula); + delete probFormula; + storm::formula::Ap* done = new storm::formula::Ap("done"); storm::formula::ReachabilityReward* reachabilityRewardFormula = new storm::formula::ReachabilityReward(done); storm::formula::RewardNoBoundOperator* rewardFormula = new storm::formula::RewardNoBoundOperator(reachabilityRewardFormula); - storm::modelChecker::GmmxxDtmcPrctlModelChecker* mc = new storm::modelChecker::GmmxxDtmcPrctlModelChecker(dtmc); - mc->check(*probFormula); mc->check(*rewardFormula); - - delete mc; - delete probFormula; delete rewardFormula; + delete mc; } void testCheckingCrowds(storm::models::Dtmc& dtmc) { @@ -191,13 +208,10 @@ void testCheckingSynchronousLeader(storm::models::Dtmc& dtmc, uint_fast6 delete probFormula; electedFormula = new storm::formula::Ap("elected"); - storm::formula::BoundedUntil* boundedUntilFormula = new storm::formula::BoundedUntil(new storm::formula::Ap("true"), electedFormula, 1); + storm::formula::BoundedUntil* boundedUntilFormula = new storm::formula::BoundedUntil(new storm::formula::Ap("true"), electedFormula, n * 4); probFormula = new storm::formula::ProbabilisticNoBoundOperator(boundedUntilFormula); - for (uint_fast64_t L = 1; L < 5; ++L) { - boundedUntilFormula->setBound(L*(n + 1)); - mc->check(*probFormula); - } + mc->check(*probFormula); delete probFormula; electedFormula = new storm::formula::Ap("elected"); @@ -211,15 +225,93 @@ void testCheckingSynchronousLeader(storm::models::Dtmc& dtmc, uint_fast6 } void testCheckingDice(storm::models::Mdp& mdp) { - storm::formula::Ap* threeFormula = new storm::formula::Ap("three"); - storm::formula::Eventually* eventuallyFormula = new storm::formula::Eventually(threeFormula); - storm::formula::ProbabilisticNoBoundOperator* probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula, false); + storm::formula::Ap* twoFormula = new storm::formula::Ap("two"); + storm::formula::Eventually* eventuallyFormula = new storm::formula::Eventually(twoFormula); + storm::formula::ProbabilisticNoBoundOperator* probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula, true); storm::modelChecker::GmmxxMdpPrctlModelChecker* mc = new storm::modelChecker::GmmxxMdpPrctlModelChecker(mdp); mc->check(*probFormula); delete probFormula; + + twoFormula = new storm::formula::Ap("two"); + eventuallyFormula = new storm::formula::Eventually(twoFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula, false); + + mc->check(*probFormula); + delete probFormula; + + twoFormula = new storm::formula::Ap("three"); + eventuallyFormula = new storm::formula::Eventually(twoFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula, true); + + mc->check(*probFormula); + delete probFormula; + + twoFormula = new storm::formula::Ap("three"); + eventuallyFormula = new storm::formula::Eventually(twoFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula, false); + + mc->check(*probFormula); + delete probFormula; + + twoFormula = new storm::formula::Ap("four"); + eventuallyFormula = new storm::formula::Eventually(twoFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula, true); + + mc->check(*probFormula); + delete probFormula; + + twoFormula = new storm::formula::Ap("four"); + eventuallyFormula = new storm::formula::Eventually(twoFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula, false); + + mc->check(*probFormula); + delete probFormula; + + twoFormula = new storm::formula::Ap("five"); + eventuallyFormula = new storm::formula::Eventually(twoFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula, true); + + mc->check(*probFormula); + delete probFormula; + + twoFormula = new storm::formula::Ap("five"); + eventuallyFormula = new storm::formula::Eventually(twoFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula, false); + + mc->check(*probFormula); + delete probFormula; + + twoFormula = new storm::formula::Ap("six"); + eventuallyFormula = new storm::formula::Eventually(twoFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula, true); + + mc->check(*probFormula); + delete probFormula; + + twoFormula = new storm::formula::Ap("six"); + eventuallyFormula = new storm::formula::Eventually(twoFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula, false); + + mc->check(*probFormula); + delete probFormula; + + storm::formula::Ap* doneFormula = new storm::formula::Ap("done"); + storm::formula::ReachabilityReward* reachabilityRewardFormula = new storm::formula::ReachabilityReward(doneFormula); + storm::formula::RewardNoBoundOperator* rewardFormula = new storm::formula::RewardNoBoundOperator(reachabilityRewardFormula, true); + + mc->check(*rewardFormula); + delete rewardFormula; + + doneFormula = new storm::formula::Ap("done"); + reachabilityRewardFormula = new storm::formula::ReachabilityReward(doneFormula); + rewardFormula = new storm::formula::RewardNoBoundOperator(reachabilityRewardFormula, false); + + mc->check(*rewardFormula); + delete rewardFormula; + delete mc; } @@ -241,19 +333,102 @@ void testCheckingAsynchLeader(storm::models::Mdp& mdp) { delete probMaxFormula; electedFormula = new storm::formula::Ap("elected"); - storm::formula::BoundedEventually* boundedEventuallyFormula = new storm::formula::BoundedEventually(electedFormula, 50); + storm::formula::BoundedEventually* boundedEventuallyFormula = new storm::formula::BoundedEventually(electedFormula, 25); probMinFormula = new storm::formula::ProbabilisticNoBoundOperator(boundedEventuallyFormula, true); mc->check(*probMinFormula); delete probMinFormula; electedFormula = new storm::formula::Ap("elected"); - boundedEventuallyFormula = new storm::formula::BoundedEventually(electedFormula, 50); + boundedEventuallyFormula = new storm::formula::BoundedEventually(electedFormula, 25); probMaxFormula = new storm::formula::ProbabilisticNoBoundOperator(boundedEventuallyFormula, false); mc->check(*probMaxFormula); delete probMaxFormula; + electedFormula = new storm::formula::Ap("elected"); + storm::formula::ReachabilityReward* reachabilityRewardFormula = new storm::formula::ReachabilityReward(electedFormula); + storm::formula::RewardNoBoundOperator* rewardFormula = new storm::formula::RewardNoBoundOperator(reachabilityRewardFormula, true); + + mc->check(*rewardFormula); + delete rewardFormula; + + electedFormula = new storm::formula::Ap("elected"); + reachabilityRewardFormula = new storm::formula::ReachabilityReward(electedFormula); + rewardFormula = new storm::formula::RewardNoBoundOperator(reachabilityRewardFormula, false); + + mc->check(*rewardFormula); + delete rewardFormula; + + delete mc; +} + +void testCheckingConsensus(storm::models::Mdp& mdp) { + storm::formula::Ap* finishedFormula = new storm::formula::Ap("finished"); + storm::formula::Eventually* eventuallyFormula = new storm::formula::Eventually(finishedFormula); + storm::formula::ProbabilisticNoBoundOperator* probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula, true); + + storm::modelChecker::GmmxxMdpPrctlModelChecker* mc = new storm::modelChecker::GmmxxMdpPrctlModelChecker(mdp); + + mc->check(*probFormula); + delete probFormula; + + finishedFormula = new storm::formula::Ap("finished"); + storm::formula::Ap* allCoinsEqual0Formula = new storm::formula::Ap("all_coins_equal_0"); + storm::formula::And* conjunctionFormula = new storm::formula::And(finishedFormula, allCoinsEqual0Formula); + eventuallyFormula = new storm::formula::Eventually(conjunctionFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula, true); + + mc->check(*probFormula); + delete probFormula; + + finishedFormula = new storm::formula::Ap("finished"); + storm::formula::Ap* allCoinsEqual1Formula = new storm::formula::Ap("all_coins_equal_1"); + conjunctionFormula = new storm::formula::And(finishedFormula, allCoinsEqual1Formula); + eventuallyFormula = new storm::formula::Eventually(conjunctionFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula, true); + + mc->check(*probFormula); + delete probFormula; + + finishedFormula = new storm::formula::Ap("finished"); + storm::formula::Ap* agree = new storm::formula::Ap("agree"); + storm::formula::Not* notAgree = new storm::formula::Not(agree); + conjunctionFormula = new storm::formula::And(finishedFormula, notAgree); + eventuallyFormula = new storm::formula::Eventually(conjunctionFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula, false); + + mc->check(*probFormula); + delete probFormula; + + finishedFormula = new storm::formula::Ap("finished"); + storm::formula::BoundedEventually* boundedEventuallyFormula = new storm::formula::BoundedEventually(finishedFormula, 50); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(boundedEventuallyFormula, true); + + mc->check(*probFormula); + delete probFormula; + + finishedFormula = new storm::formula::Ap("finished"); + boundedEventuallyFormula = new storm::formula::BoundedEventually(finishedFormula, 50); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(boundedEventuallyFormula, false); + + mc->check(*probFormula); + delete probFormula; + + finishedFormula = new storm::formula::Ap("finished"); + storm::formula::ReachabilityReward* reachabilityRewardFormula = new storm::formula::ReachabilityReward(finishedFormula); + storm::formula::RewardNoBoundOperator* rewardFormula = new storm::formula::RewardNoBoundOperator(reachabilityRewardFormula, true); + + mc->check(*rewardFormula); + delete rewardFormula; + + finishedFormula = new storm::formula::Ap("finished"); + reachabilityRewardFormula = new storm::formula::ReachabilityReward(finishedFormula); + rewardFormula = new storm::formula::RewardNoBoundOperator(reachabilityRewardFormula, false); + + mc->check(*rewardFormula); + delete rewardFormula; + delete mc; } @@ -270,14 +445,14 @@ void testChecking() { // testCheckingDie(*dtmc); // testCheckingCrowds(*dtmc); - // testCheckingSynchronousLeader(*dtmc, 4); - } - else if (parser.getType() == storm::models::MDP) { + // testCheckingSynchronousLeader(*dtmc, 6); + } else if (parser.getType() == storm::models::MDP) { std::shared_ptr> mdp = parser.getModel>(); mdp->printModelInformationToStream(std::cout); // testCheckingDice(*mdp); // testCheckingAsynchLeader(*mdp); + // testCheckingConsensus(*mdp); } else { std::cout << "Input is neither a DTMC nor an MDP." << std::endl; } @@ -287,20 +462,32 @@ void testChecking() { * Main entry point. */ int main(const int argc, const char* argv[]) { + // Catch segfaults and display a backtrace. + installSignalHandler(); + + printHeader(argc, argv); + initializeLogger(); if (!parseOptions(argc, argv)) { return 0; } setUp(); - LOG4CPLUS_INFO(logger, "StoRM was invoked."); - printHeader(argc, argv); - testChecking(); + try { + LOG4CPLUS_INFO(logger, "StoRM was invoked."); + + testChecking(); + - cleanUp(); + cleanUp(); - LOG4CPLUS_INFO(logger, "StoRM quit."); + LOG4CPLUS_INFO(logger, "StoRM quit."); - return 0; + return 0; + } catch (std::exception& e) { + LOG4CPLUS_FATAL(logger, "An exception was thrown but not catched. All we can do now is show it to you and die in peace..."); + LOG4CPLUS_FATAL(logger, "\t" << e.what()); + } + return 1; } diff --git a/src/utility/ConstTemplates.h b/src/utility/ConstTemplates.h index ace4c7bd9..847c38174 100644 --- a/src/utility/ConstTemplates.h +++ b/src/utility/ConstTemplates.h @@ -18,6 +18,9 @@ #include +#include "src/exceptions/InvalidArgumentException.h" +#include "src/storage/BitVector.h" + namespace storm { namespace utility { diff --git a/src/utility/ErrorHandling.h b/src/utility/ErrorHandling.h new file mode 100644 index 000000000..fdc7762a5 --- /dev/null +++ b/src/utility/ErrorHandling.h @@ -0,0 +1,64 @@ +/* + * File: ErrorHandling.h + * Author: Gereon Kremer + * + * Created on March 15, 2013, 4:10 PM + */ + +#ifndef ERRORHANDLING_H +#define ERRORHANDLING_H + +#include +#include +#include + +std::string demangle(const char* symbol) { + int status; + // Attention: sscanf format strings rely on size being 128 + char temp[128]; + char* demangled; + // Check for C++ symbol + if (sscanf(symbol, "%*[^(]%*[^_]%127[^)+]", temp) == 1) { + if (NULL != (demangled = abi::__cxa_demangle(temp, NULL, NULL, &status))) { + std::string result(demangled); + free(demangled); + return result; + } + } + // Check for C symbol + if (sscanf(symbol, "%127s", temp) == 1) { + return temp; + } + // Return plain symbol + return symbol; +} + +void signalHandler(int sig) { +#define SIZE 128 + LOG4CPLUS_FATAL(logger, "We recieved a segfault. To start with, here is a backtrace."); + + void *buffer[SIZE]; + char **strings; + int nptrs; + nptrs = backtrace(buffer, SIZE); + + strings = backtrace_symbols(buffer, nptrs); + if (strings == nullptr) { + std::cerr << "Obtaining the backtrace symbols failed. Well, shit." << std::endl; + exit(2); + } + // j = 2: skip the handler itself. + for (int j = 1; j < nptrs; j++) { + LOG4CPLUS_FATAL(logger, nptrs-j << ": " << demangle(strings[j])); + } + free(strings); + LOG4CPLUS_FATAL(logger, "That's all we can do. Bye."); + exit(2); +} + +void installSignalHandler() { + signal(SIGSEGV, signalHandler); +} + +#endif /* ERRORHANDLING_H */ + diff --git a/src/utility/GraphAnalyzer.h b/src/utility/GraphAnalyzer.h index c7325befd..365e95294 100644 --- a/src/utility/GraphAnalyzer.h +++ b/src/utility/GraphAnalyzer.h @@ -34,7 +34,7 @@ public: * @param statesWithProbability1 A pointer to a bit vector that is initially empty and will contain all states with * probability 1 after the invocation of the function. */ - template + template static void performProb01(storm::models::AbstractDeterministicModel& model, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, storm::storage::BitVector* statesWithProbability0, storm::storage::BitVector* statesWithProbability1) { // Check for valid parameters. if (statesWithProbability0 == nullptr) { @@ -62,7 +62,7 @@ public: * @param statesWithProbabilityGreater0 A pointer to the result of the search for states that possess * a positive probability of satisfying phi until psi. */ - template + template static void performProbGreater0(storm::models::AbstractDeterministicModel& model, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, storm::storage::BitVector* statesWithProbabilityGreater0) { // Check for valid parameter. if (statesWithProbabilityGreater0 == nullptr) { @@ -71,7 +71,7 @@ public: } // Get the backwards transition relation from the model to ease the search. - storm::models::GraphTransitions backwardTransitions(model.getTransitionMatrix(), false); + storm::models::GraphTransitions backwardTransitions(*model.getTransitionMatrix(), false); // Add all psi states as the already satisfy the condition. *statesWithProbabilityGreater0 |= psiStates; @@ -109,7 +109,7 @@ public: * @param alwaysPhiUntilPsiStates A pointer to the result of the search for states that only * have paths satisfying phi until psi. */ - template + template static void performProb1(storm::models::AbstractDeterministicModel& model, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, storm::storage::BitVector const& statesWithProbabilityGreater0, storm::storage::BitVector* statesWithProbability1) { // Check for valid parameter. if (statesWithProbability1 == nullptr) { @@ -133,7 +133,7 @@ public: * @param alwaysPhiUntilPsiStates A pointer to the result of the search for states that only * have paths satisfying phi until psi. */ - template + template static void performProb1(storm::models::AbstractDeterministicModel& model, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, storm::storage::BitVector* statesWithProbability1) { // Check for valid parameter. if (statesWithProbability1 == nullptr) { @@ -148,7 +148,7 @@ public: statesWithProbability1->complement(); } - template + template static void performProb01Max(storm::models::AbstractNondeterministicModel& model, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, storm::storage::BitVector* statesWithProbability0, storm::storage::BitVector* statesWithProbability1) { // Check for valid parameters. if (statesWithProbability0 == nullptr) { @@ -165,7 +165,7 @@ public: GraphAnalyzer::performProb1E(model, phiStates, psiStates, statesWithProbability1); } - template + template static void performProb0A(storm::models::AbstractNondeterministicModel& model, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, storm::storage::BitVector* statesWithProbability0) { // Check for valid parameter. if (statesWithProbability0 == nullptr) { @@ -174,7 +174,7 @@ public: } // Get the backwards transition relation from the model to ease the search. - storm::models::GraphTransitions backwardTransitions(model.getTransitionMatrix(), model.getNondeterministicChoiceIndices(), false); + storm::models::GraphTransitions backwardTransitions(*model.getTransitionMatrix(), *model.getNondeterministicChoiceIndices(), false); // Add all psi states as the already satisfy the condition. *statesWithProbability0 |= psiStates; @@ -200,7 +200,7 @@ public: statesWithProbability0->complement(); } - template + template static void performProb1E(storm::models::AbstractNondeterministicModel& model, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, storm::storage::BitVector* statesWithProbability1) { // Check for valid parameters. if (statesWithProbability1 == nullptr) { @@ -213,7 +213,7 @@ public: std::shared_ptr> nondeterministicChoiceIndices = model.getNondeterministicChoiceIndices(); // Get the backwards transition relation from the model to ease the search. - storm::models::GraphTransitions backwardTransitions(model.getTransitionMatrix(), model.getNondeterministicChoiceIndices(), false); + storm::models::GraphTransitions backwardTransitions(*model.getTransitionMatrix(), *model.getNondeterministicChoiceIndices(), false); storm::storage::BitVector* currentStates = new storm::storage::BitVector(model.getNumberOfStates(), true); @@ -262,13 +262,14 @@ public: } else { *currentStates = *nextStates; } + delete nextStates; } *statesWithProbability1 = *currentStates; delete currentStates; } - template + template static void performProb01Min(storm::models::AbstractNondeterministicModel& model, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, storm::storage::BitVector* statesWithProbability0, storm::storage::BitVector* statesWithProbability1) { // Check for valid parameters. if (statesWithProbability0 == nullptr) { @@ -285,7 +286,7 @@ public: GraphAnalyzer::performProb1A(model, phiStates, psiStates, statesWithProbability1); } - template + template static void performProb0E(storm::models::AbstractNondeterministicModel& model, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, storm::storage::BitVector* statesWithProbability0) { // Check for valid parameter. if (statesWithProbability0 == nullptr) { @@ -298,7 +299,7 @@ public: std::shared_ptr> nondeterministicChoiceIndices = model.getNondeterministicChoiceIndices(); // Get the backwards transition relation from the model to ease the search. - storm::models::GraphTransitions backwardTransitions(transitionMatrix, nondeterministicChoiceIndices, false); + storm::models::GraphTransitions backwardTransitions(*transitionMatrix, *nondeterministicChoiceIndices, false); // Add all psi states as the already satisfy the condition. *statesWithProbability0 |= psiStates; @@ -345,7 +346,7 @@ public: statesWithProbability0->complement(); } - template + template static void performProb1A(storm::models::AbstractNondeterministicModel& model, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, storm::storage::BitVector* statesWithProbability1) { // Check for valid parameters. if (statesWithProbability1 == nullptr) { @@ -358,7 +359,7 @@ public: std::shared_ptr> nondeterministicChoiceIndices = model.getNondeterministicChoiceIndices(); // Get the backwards transition relation from the model to ease the search. - storm::models::GraphTransitions backwardTransitions(model.getTransitionMatrix(), model.getNondeterministicChoiceIndices(), false); + storm::models::GraphTransitions backwardTransitions(*model.getTransitionMatrix(), *model.getNondeterministicChoiceIndices(), false); storm::storage::BitVector* currentStates = new storm::storage::BitVector(model.getNumberOfStates(), true); @@ -407,11 +408,200 @@ public: } else { *currentStates = *nextStates; } + delete nextStates; } *statesWithProbability1 = *currentStates; delete currentStates; } + + template + static void performSccDecomposition(storm::storage::SparseMatrix const& matrix, std::vector const& nondeterministicChoiceIndices, std::vector>& stronglyConnectedComponents, storm::models::GraphTransitions& stronglyConnectedComponentsDependencyGraph) { + LOG4CPLUS_INFO(logger, "Computing SCC decomposition."); + + // Get the forward transition relation from the model to ease the search. + storm::models::GraphTransitions forwardTransitions(matrix, nondeterministicChoiceIndices, true); + + // Perform the actual SCC decomposition based on the graph-transitions of the system. + performSccDecomposition(nondeterministicChoiceIndices.size() - 1, forwardTransitions, stronglyConnectedComponents, stronglyConnectedComponentsDependencyGraph); + + LOG4CPLUS_INFO(logger, "Done computing SCC decomposition."); + } + + template + static void getTopologicalSort(storm::models::GraphTransitions const& transitions, std::vector& topologicalSort) { + topologicalSort.reserve(transitions.getNumberOfStates()); + + std::vector recursionStack; + recursionStack.reserve(transitions.getNumberOfStates()); + + std::vector::stateSuccessorIterator> iteratorRecursionStack; + iteratorRecursionStack.reserve(transitions.getNumberOfStates()); + + storm::storage::BitVector visitedStates(transitions.getNumberOfStates()); + for (uint_fast64_t state = 0; state < transitions.getNumberOfStates(); ++state) { + if (!visitedStates.get(state)) { + recursionStack.push_back(state); + iteratorRecursionStack.push_back(transitions.beginStateSuccessorsIterator(state)); + + recursionStepForward: + while (!recursionStack.empty()) { + uint_fast64_t currentState = recursionStack.back(); + typename storm::models::GraphTransitions::stateSuccessorIterator currentIt = iteratorRecursionStack.back(); + + visitedStates.set(currentState, true); + + recursionStepBackward: + for (; currentIt != transitions.endStateSuccessorsIterator(currentState); ++currentIt) { + if (!visitedStates.get(*currentIt)) { + // Put unvisited successor on top of our recursion stack and remember that. + recursionStack.push_back(*currentIt); + + // Save current iterator position so we can continue where we left off later. + iteratorRecursionStack.pop_back(); + iteratorRecursionStack.push_back(currentIt + 1); + + // Also, put initial value for iterator on corresponding recursion stack. + iteratorRecursionStack.push_back(transitions.beginStateSuccessorsIterator(*currentIt)); + + goto recursionStepForward; + } + } + + topologicalSort.push_back(currentState); + + // If we reach this point, we have completed the recursive descent for the current state. + // That is, we need to pop it from the recursion stacks. + recursionStack.pop_back(); + iteratorRecursionStack.pop_back(); + + // If there is at least one state under the current one in our recursion stack, we need + // to restore the topmost state as the current state and jump to the part after the + // original recursive call. + if (recursionStack.size() > 0) { + currentState = recursionStack.back(); + currentIt = iteratorRecursionStack.back(); + + goto recursionStepBackward; + } + } + } + } + } + +private: + template + static void performSccDecomposition(uint_fast64_t numberOfStates, storm::models::GraphTransitions const& forwardTransitions, std::vector>& stronglyConnectedComponents, storm::models::GraphTransitions& stronglyConnectedComponentsDependencyGraph) { + std::vector tarjanStack; + tarjanStack.reserve(numberOfStates); + storm::storage::BitVector tarjanStackStates(numberOfStates); + + std::vector stateIndices(numberOfStates); + std::vector lowlinks(numberOfStates); + storm::storage::BitVector visitedStates(numberOfStates); + + std::map stateToSccMap; + + uint_fast64_t currentIndex = 0; + for (uint_fast64_t state = 0; state < numberOfStates; ++state) { + if (!visitedStates.get(state)) { + performSccDecompositionHelper(state, currentIndex, stateIndices, lowlinks, tarjanStack, tarjanStackStates, visitedStates, forwardTransitions, stronglyConnectedComponents, stateToSccMap); + } + } + + stronglyConnectedComponentsDependencyGraph = storm::models::GraphTransitions(forwardTransitions, stronglyConnectedComponents, stateToSccMap); + } + + template + static void performSccDecompositionHelper(uint_fast64_t startState, uint_fast64_t& currentIndex, std::vector& stateIndices, std::vector& lowlinks, std::vector& tarjanStack, storm::storage::BitVector& tarjanStackStates, storm::storage::BitVector& visitedStates, storm::models::GraphTransitions const& forwardTransitions, std::vector>& stronglyConnectedComponents, std::map& stateToSccMap) { + // Create the stacks needed for turning the recursive formulation of Tarjan's algorithm + // into an iterative version. In particular, we keep one stack for states and one stack + // for the iterators. The last one is not strictly needed, but reduces iteration work when + // all successors of a particular state are considered. + std::vector recursionStateStack; + recursionStateStack.reserve(lowlinks.size()); + std::vector::stateSuccessorIterator> recursionIteratorStack; + recursionIteratorStack.reserve(lowlinks.size()); + std::vector statesInStack(lowlinks.size()); + + // Initialize the recursion stacks with the given initial state (and its successor iterator). + recursionStateStack.push_back(startState); + recursionIteratorStack.push_back(forwardTransitions.beginStateSuccessorsIterator(startState)); + + recursionStepForward: + while (!recursionStateStack.empty()) { + uint_fast64_t currentState = recursionStateStack.back(); + typename storm::models::GraphTransitions::stateSuccessorIterator currentIt = recursionIteratorStack.back(); + + // Perform the treatment of newly discovered state as defined by Tarjan's algorithm + visitedStates.set(currentState, true); + stateIndices[currentState] = currentIndex; + lowlinks[currentState] = currentIndex; + ++currentIndex; + tarjanStack.push_back(currentState); + tarjanStackStates.set(currentState, true); + + // Now, traverse all successors of the current state. + for(; currentIt != forwardTransitions.endStateSuccessorsIterator(currentState); ++currentIt) { + // If we have not visited the successor already, we need to perform the procedure + // recursively on the newly found state. + if (!visitedStates.get(*currentIt)) { + // Save current iterator position so we can continue where we left off later. + recursionIteratorStack.pop_back(); + recursionIteratorStack.push_back(currentIt); + + // Put unvisited successor on top of our recursion stack and remember that. + recursionStateStack.push_back(*currentIt); + statesInStack[*currentIt] = true; + + // Also, put initial value for iterator on corresponding recursion stack. + recursionIteratorStack.push_back(forwardTransitions.beginStateSuccessorsIterator(*currentIt)); + + // Perform the actual recursion step in an iterative way. + goto recursionStepForward; + + recursionStepBackward: + lowlinks[currentState] = std::min(lowlinks[currentState], lowlinks[*currentIt]); + } else if (tarjanStackStates.get(*currentIt)) { + // Update the lowlink of the current state. + lowlinks[currentState] = std::min(lowlinks[currentState], stateIndices[*currentIt]); + } + } + + // If the current state is the root of a SCC, we need to pop all states of the SCC from + // the algorithm's stack. + if (lowlinks[currentState] == stateIndices[currentState]) { + stronglyConnectedComponents.push_back(std::vector()); + + uint_fast64_t lastState = 0; + do { + // Pop topmost state from the algorithm's stack. + lastState = tarjanStack.back(); + tarjanStack.pop_back(); + tarjanStackStates.set(lastState, false); + + // Add the state to the current SCC. + stronglyConnectedComponents.back().push_back(lastState); + stateToSccMap[lastState] = stronglyConnectedComponents.size() - 1; + } while (lastState != currentState); + } + + // If we reach this point, we have completed the recursive descent for the current state. + // That is, we need to pop it from the recursion stacks. + recursionStateStack.pop_back(); + recursionIteratorStack.pop_back(); + + // If there is at least one state under the current one in our recursion stack, we need + // to restore the topmost state as the current state and jump to the part after the + // original recursive call. + if (recursionStateStack.size() > 0) { + currentState = recursionStateStack.back(); + currentIt = recursionIteratorStack.back(); + + goto recursionStepBackward; + } + } + } }; } // namespace utility diff --git a/src/utility/Settings.cpp b/src/utility/Settings.cpp index 26f733071..d6e2f5bc3 100644 --- a/src/utility/Settings.cpp +++ b/src/utility/Settings.cpp @@ -131,6 +131,7 @@ void Settings::initDescriptions() { Settings::desc->add_options() ("help,h", "produce help message") ("verbose,v", "be verbose") + ("debug", "be very verbose, intended for debugging") ("logfile,l", bpo::value(), "name of the log file") ("configfile,c", bpo::value(), "name of config file") ("test-prctl", bpo::value(), "name of prctl file") diff --git a/src/utility/Settings.h b/src/utility/Settings.h index b35e893c4..900b34673 100644 --- a/src/utility/Settings.h +++ b/src/utility/Settings.h @@ -70,6 +70,23 @@ namespace settings { inline const bool isSet(std::string const & name) const { return this->vm.count(name) > 0; } + + /*! + * @brief Set an option. + */ + inline void set(std::string const & name) { + bpo::variable_value val; + this->vm.insert( std::make_pair(name, val) ); + } + + /*! + * @brief Set value for an option. + */ + template + inline void set(std::string const & name, T const & value) { + bpo::variable_value val(value, false); + this->vm.insert( std::make_pair(name, val) ); + } /*! * @brief Register a new module. diff --git a/src/utility/Vector.h b/src/utility/Vector.h index 7e5224af4..ab1fc461a 100644 --- a/src/utility/Vector.h +++ b/src/utility/Vector.h @@ -51,6 +51,26 @@ void selectVectorValues(std::vector* vector, const storm::storage::BitVector& } } +template +void selectVectorValues(std::vector* vector, const storm::storage::BitVector& positions, const std::vector& rowMapping, std::vector const& values) { + uint_fast64_t oldPosition = 0; + for (auto position : positions) { + for (uint_fast64_t i = rowMapping[position]; i < rowMapping[position + 1]; ++i) { + (*vector)[oldPosition++] = values[i]; + } + } +} + +template +void selectVectorValuesRepeatedly(std::vector* vector, const storm::storage::BitVector& positions, const std::vector& rowMapping, std::vector const& values) { + uint_fast64_t oldPosition = 0; + for (auto position : positions) { + for (uint_fast64_t i = rowMapping[position]; i < rowMapping[position + 1]; ++i) { + (*vector)[oldPosition++] = values[position]; + } + } +} + template void subtractFromConstantOneVector(std::vector* vector) { for (auto it = vector->begin(); it != vector->end(); ++it) { @@ -58,6 +78,15 @@ void subtractFromConstantOneVector(std::vector* vector) { } } +template +void addVectors(std::vector const& states, std::vector const& nondeterministicChoiceIndices, std::vector& original, std::vector const& summand) { + for (auto stateIt = states.cbegin(), stateIte = states.cend(); stateIt != stateIte; ++stateIt) { + for (auto rowIt = nondeterministicChoiceIndices[*stateIt], rowIte = nondeterministicChoiceIndices[*stateIt + 1]; rowIt != rowIte; ++rowIt) { + original[rowIt] += summand[rowIt]; + } + } +} + template void reduceVectorMin(std::vector const& source, std::vector* target, std::vector const& filter) { uint_fast64_t currentSourceRow = 0; @@ -78,6 +107,21 @@ void reduceVectorMin(std::vector const& source, std::vector* target, std:: } } +template +void reduceVectorMin(std::vector const& source, std::vector* target, std::vector const& scc, std::vector const& filter) { + for (auto stateIt = scc.cbegin(); stateIt != scc.cend(); ++stateIt) { + (*target)[*stateIt] = source[filter[*stateIt]]; + + for (auto row = filter[*stateIt] + 1; row < filter[*stateIt + 1]; ++row) { + // We have to minimize the value, so only overwrite the current value if the + // value is actually lower. + if (source[row] < (*target)[*stateIt]) { + (*target)[*stateIt] = source[row]; + } + } + } +} + template void reduceVectorMax(std::vector const& source, std::vector* target, std::vector const& filter) { uint_fast64_t currentSourceRow = 0; @@ -98,10 +142,25 @@ void reduceVectorMax(std::vector const& source, std::vector* target, std:: } } +template +void reduceVectorMax(std::vector const& source, std::vector* target, std::vector const& scc, std::vector const& filter) { + for (auto stateIt = scc.cbegin(); stateIt != scc.cend(); ++stateIt) { + (*target)[*stateIt] = source[filter[*stateIt]]; + + for (auto row = filter[*stateIt] + 1; row < filter[*stateIt + 1]; ++row) { + // We have to maximize the value, so only overwrite the current value if the + // value is actually lower. + if (source[row] > (*target)[*stateIt]) { + (*target)[*stateIt] = source[row]; + } + } + } +} + template bool equalModuloPrecision(std::vector const& vectorLeft, std::vector const& vectorRight, T precision, bool relativeError) { if (vectorLeft.size() != vectorRight.size()) { - LOG4CPLUS_ERROR(logger, "Length of vectors does not match and makes comparison impossible."); + LOG4CPLUS_ERROR(logger, "Lengths of vectors does not match and makes comparison impossible."); throw storm::exceptions::InvalidArgumentException() << "Length of vectors does not match and makes comparison impossible."; } @@ -116,6 +175,24 @@ bool equalModuloPrecision(std::vector const& vectorLeft, std::vector const return true; } +template +bool equalModuloPrecision(std::vector const& vectorLeft, std::vector const& vectorRight, std::vector const& scc, T precision, bool relativeError) { + if (vectorLeft.size() != vectorRight.size()) { + LOG4CPLUS_ERROR(logger, "Lengths of vectors does not match and makes comparison impossible."); + throw storm::exceptions::InvalidArgumentException() << "Length of vectors does not match and makes comparison impossible."; + } + + for (uint_fast64_t state : scc) { + if (relativeError) { + if (std::abs(vectorLeft[state] - vectorRight[state])/vectorRight[state] > precision) return false; + } else { + if (std::abs(vectorLeft[state] - vectorRight[state]) > precision) return false; + } + } + + return true; +} + } //namespace utility } //namespace storm diff --git a/test/functional/EigenDtmcPrctModelCheckerTest.cpp b/test/functional/EigenDtmcPrctModelCheckerTest.cpp new file mode 100644 index 000000000..224084e5c --- /dev/null +++ b/test/functional/EigenDtmcPrctModelCheckerTest.cpp @@ -0,0 +1,164 @@ +/* +#include "gtest/gtest.h" +#include "storm-config.h" + +#include "src/utility/Settings.h" +#include "src/modelchecker/EigenDtmcPrctlModelChecker.h" +#include "src/parser/AutoParser.h" + +TEST(EigenDtmcPrctModelCheckerTest, Die) { + storm::settings::Settings* s = storm::settings::instance(); + s->set("fix-deadlocks"); + storm::parser::AutoParser parser(STORM_CPP_TESTS_BASE_PATH "/functional/die/die.tra", STORM_CPP_TESTS_BASE_PATH "/functional/die/die.lab", "", STORM_CPP_TESTS_BASE_PATH "/functional/die/die.coin_flips.trans.rew"); + + ASSERT_EQ(parser.getType(), storm::models::DTMC); + + std::shared_ptr> dtmc = parser.getModel>(); + + ASSERT_EQ(dtmc->getNumberOfStates(), 14); + ASSERT_EQ(dtmc->getNumberOfTransitions(), 28); + + storm::modelChecker::EigenDtmcPrctlModelChecker mc(*dtmc); + + storm::formula::Ap* apFormula = new storm::formula::Ap("one"); + storm::formula::Eventually* eventuallyFormula = new storm::formula::Eventually(apFormula); + storm::formula::ProbabilisticNoBoundOperator* probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula); + + std::vector* result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[1] - ((double)1/6)), s->get("precision")); + + delete probFormula; + delete result; + + apFormula = new storm::formula::Ap("two"); + eventuallyFormula = new storm::formula::Eventually(apFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula); + + result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[1] - ((double)1/6)), s->get("precision")); + + delete probFormula; + delete result; + + apFormula = new storm::formula::Ap("three"); + eventuallyFormula = new storm::formula::Eventually(apFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula); + + result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[1] - ((double)1/6)), s->get("precision")); + + delete probFormula; + delete result; + + storm::formula::Ap* done = new storm::formula::Ap("done"); + storm::formula::ReachabilityReward* reachabilityRewardFormula = new storm::formula::ReachabilityReward(done); + storm::formula::RewardNoBoundOperator* rewardFormula = new storm::formula::RewardNoBoundOperator(reachabilityRewardFormula); + + result = rewardFormula->check(mc); + + + ASSERT_LT(std::abs((*result)[1] - ((double)11/3)), s->get("precision")); + + delete rewardFormula; + delete result; +} + +TEST(EigenDtmcPrctModelCheckerTest, Crowds) { + storm::settings::Settings* s = storm::settings::instance(); + s->set("fix-deadlocks"); + storm::parser::AutoParser parser(STORM_CPP_TESTS_BASE_PATH "/functional/crowds/crowds5_5.tra", STORM_CPP_TESTS_BASE_PATH "/functional/crowds/crowds5_5.lab", "", ""); + + ASSERT_EQ(parser.getType(), storm::models::DTMC); + + std::shared_ptr> dtmc = parser.getModel>(); + + ASSERT_EQ(dtmc->getNumberOfStates(), 8608); + ASSERT_EQ(dtmc->getNumberOfTransitions(), 22461); + + storm::modelChecker::EigenDtmcPrctlModelChecker mc(*dtmc); + + storm::formula::Ap* apFormula = new storm::formula::Ap("observe0Greater1"); + storm::formula::Eventually* eventuallyFormula = new storm::formula::Eventually(apFormula); + storm::formula::ProbabilisticNoBoundOperator* probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula); + + std::vector* result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[1] - 0.3328800375801578281), s->get("precision")); + + delete probFormula; + delete result; + + apFormula = new storm::formula::Ap("observeIGreater1"); + eventuallyFormula = new storm::formula::Eventually(apFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula); + + result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[1] - 0.1522173670950556501), s->get("precision")); + + delete probFormula; + delete result; + + apFormula = new storm::formula::Ap("observeOnlyTrueSender"); + eventuallyFormula = new storm::formula::Eventually(apFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula); + + result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[1] - 0.32153724292835045), s->get("precision")); + + delete probFormula; + delete result; +} + +TEST(EigenDtmcPrctModelCheckerTest, SynchronousLeader) { + storm::settings::Settings* s = storm::settings::instance(); + s->set("fix-deadlocks"); + storm::parser::AutoParser parser(STORM_CPP_TESTS_BASE_PATH "/functional/synchronous_leader/leader4_8.tra", STORM_CPP_TESTS_BASE_PATH "/functional/synchronous_leader/leader4_8.lab", "", STORM_CPP_TESTS_BASE_PATH "/functional/synchronous_leader/leader4_8.pick.trans.rew"); + + ASSERT_EQ(parser.getType(), storm::models::DTMC); + + std::shared_ptr> dtmc = parser.getModel>(); + + ASSERT_EQ(dtmc->getNumberOfStates(), 12401); + ASSERT_EQ(dtmc->getNumberOfTransitions(), 28895); + + storm::modelChecker::EigenDtmcPrctlModelChecker mc(*dtmc); + + storm::formula::Ap* apFormula = new storm::formula::Ap("elected"); + storm::formula::Eventually* eventuallyFormula = new storm::formula::Eventually(apFormula); + storm::formula::ProbabilisticNoBoundOperator* probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula); + + std::vector* result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[1] - 1), s->get("precision")); + + delete probFormula; + delete result; + + apFormula = new storm::formula::Ap("elected"); + storm::formula::BoundedUntil* boundedUntilFormula = new storm::formula::BoundedUntil(new storm::formula::Ap("true"), apFormula, 20); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(boundedUntilFormula); + + result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[1] - 0.9999965911265462636), s->get("precision")); + + delete probFormula; + delete result; + + apFormula = new storm::formula::Ap("elected"); + storm::formula::ReachabilityReward* reachabilityRewardFormula = new storm::formula::ReachabilityReward(apFormula); + storm::formula::RewardNoBoundOperator* rewardFormula = new storm::formula::RewardNoBoundOperator(reachabilityRewardFormula); + + result = rewardFormula->check(mc); + + ASSERT_LT(std::abs((*result)[1] - 1.0448979591835938496), s->get("precision")); + + delete rewardFormula; + delete result; +} +*/ diff --git a/test/functional/GmmxxDtmcPrctModelCheckerTest.cpp b/test/functional/GmmxxDtmcPrctModelCheckerTest.cpp new file mode 100644 index 000000000..3050b3e49 --- /dev/null +++ b/test/functional/GmmxxDtmcPrctModelCheckerTest.cpp @@ -0,0 +1,162 @@ +#include "gtest/gtest.h" +#include "storm-config.h" + +#include "src/utility/Settings.h" +#include "src/modelchecker/GmmxxDtmcPrctlModelChecker.h" +#include "src/parser/AutoParser.h" + +TEST(GmmxxDtmcPrctModelCheckerTest, Die) { + storm::settings::Settings* s = storm::settings::instance(); + s->set("fix-deadlocks"); + storm::parser::AutoParser parser(STORM_CPP_TESTS_BASE_PATH "/functional/die/die.tra", STORM_CPP_TESTS_BASE_PATH "/functional/die/die.lab", "", STORM_CPP_TESTS_BASE_PATH "/functional/die/die.coin_flips.trans.rew"); + + ASSERT_EQ(parser.getType(), storm::models::DTMC); + + std::shared_ptr> dtmc = parser.getModel>(); + + ASSERT_EQ(dtmc->getNumberOfStates(), 13); + ASSERT_EQ(dtmc->getNumberOfTransitions(), 27); + + storm::modelChecker::GmmxxDtmcPrctlModelChecker mc(*dtmc); + + storm::formula::Ap* apFormula = new storm::formula::Ap("one"); + storm::formula::Eventually* eventuallyFormula = new storm::formula::Eventually(apFormula); + storm::formula::ProbabilisticNoBoundOperator* probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula); + + std::vector* result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[0] - ((double)1/6)), s->get("precision")); + + delete probFormula; + delete result; + + apFormula = new storm::formula::Ap("two"); + eventuallyFormula = new storm::formula::Eventually(apFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula); + + result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[0] - ((double)1/6)), s->get("precision")); + + delete probFormula; + delete result; + + apFormula = new storm::formula::Ap("three"); + eventuallyFormula = new storm::formula::Eventually(apFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula); + + result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[0] - ((double)1/6)), s->get("precision")); + + delete probFormula; + delete result; + + storm::formula::Ap* done = new storm::formula::Ap("done"); + storm::formula::ReachabilityReward* reachabilityRewardFormula = new storm::formula::ReachabilityReward(done); + storm::formula::RewardNoBoundOperator* rewardFormula = new storm::formula::RewardNoBoundOperator(reachabilityRewardFormula); + + result = rewardFormula->check(mc); + + + ASSERT_LT(std::abs((*result)[0] - ((double)11/3)), s->get("precision")); + + delete rewardFormula; + delete result; +} + +TEST(GmmxxDtmcPrctModelCheckerTest, Crowds) { + storm::settings::Settings* s = storm::settings::instance(); + s->set("fix-deadlocks"); + storm::parser::AutoParser parser(STORM_CPP_TESTS_BASE_PATH "/functional/crowds/crowds5_5.tra", STORM_CPP_TESTS_BASE_PATH "/functional/crowds/crowds5_5.lab", "", ""); + + ASSERT_EQ(parser.getType(), storm::models::DTMC); + + std::shared_ptr> dtmc = parser.getModel>(); + + ASSERT_EQ(dtmc->getNumberOfStates(), 8607); + ASSERT_EQ(dtmc->getNumberOfTransitions(), 22460); + + storm::modelChecker::GmmxxDtmcPrctlModelChecker mc(*dtmc); + + storm::formula::Ap* apFormula = new storm::formula::Ap("observe0Greater1"); + storm::formula::Eventually* eventuallyFormula = new storm::formula::Eventually(apFormula); + storm::formula::ProbabilisticNoBoundOperator* probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula); + + std::vector* result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[0] - 0.3328800375801578281), s->get("precision")); + + delete probFormula; + delete result; + + apFormula = new storm::formula::Ap("observeIGreater1"); + eventuallyFormula = new storm::formula::Eventually(apFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula); + + result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[0] - 0.1522173670950556501), s->get("precision")); + + delete probFormula; + delete result; + + apFormula = new storm::formula::Ap("observeOnlyTrueSender"); + eventuallyFormula = new storm::formula::Eventually(apFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula); + + result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[0] - 0.32153724292835045), s->get("precision")); + + delete probFormula; + delete result; +} + +TEST(GmmxxDtmcPrctModelCheckerTest, SynchronousLeader) { + storm::settings::Settings* s = storm::settings::instance(); + s->set("fix-deadlocks"); + storm::parser::AutoParser parser(STORM_CPP_TESTS_BASE_PATH "/functional/synchronous_leader/leader4_8.tra", STORM_CPP_TESTS_BASE_PATH "/functional/synchronous_leader/leader4_8.lab", "", STORM_CPP_TESTS_BASE_PATH "/functional/synchronous_leader/leader4_8.pick.trans.rew"); + + ASSERT_EQ(parser.getType(), storm::models::DTMC); + + std::shared_ptr> dtmc = parser.getModel>(); + + ASSERT_EQ(dtmc->getNumberOfStates(), 12400); + ASSERT_EQ(dtmc->getNumberOfTransitions(), 28894); + + storm::modelChecker::GmmxxDtmcPrctlModelChecker mc(*dtmc); + + storm::formula::Ap* apFormula = new storm::formula::Ap("elected"); + storm::formula::Eventually* eventuallyFormula = new storm::formula::Eventually(apFormula); + storm::formula::ProbabilisticNoBoundOperator* probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula); + + std::vector* result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[0] - 1), s->get("precision")); + + delete probFormula; + delete result; + + apFormula = new storm::formula::Ap("elected"); + storm::formula::BoundedUntil* boundedUntilFormula = new storm::formula::BoundedUntil(new storm::formula::Ap("true"), apFormula, 20); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(boundedUntilFormula); + + result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[0] - 0.9999965911265462636), s->get("precision")); + + delete probFormula; + delete result; + + apFormula = new storm::formula::Ap("elected"); + storm::formula::ReachabilityReward* reachabilityRewardFormula = new storm::formula::ReachabilityReward(apFormula); + storm::formula::RewardNoBoundOperator* rewardFormula = new storm::formula::RewardNoBoundOperator(reachabilityRewardFormula); + + result = rewardFormula->check(mc); + + ASSERT_LT(std::abs((*result)[0] - 1.0448979591835938496), s->get("precision")); + + delete rewardFormula; + delete result; +} diff --git a/test/functional/GmmxxMdpPrctModelCheckerTest.cpp b/test/functional/GmmxxMdpPrctModelCheckerTest.cpp new file mode 100644 index 000000000..1fee30acd --- /dev/null +++ b/test/functional/GmmxxMdpPrctModelCheckerTest.cpp @@ -0,0 +1,248 @@ +#include "gtest/gtest.h" +#include "storm-config.h" + +#include "src/utility/Settings.h" +#include "src/modelchecker/GmmxxMdpPrctlModelChecker.h" +#include "src/parser/AutoParser.h" + +TEST(GmmxxMdpPrctModelCheckerTest, Dice) { + storm::settings::Settings* s = storm::settings::instance(); + storm::parser::AutoParser parser(STORM_CPP_TESTS_BASE_PATH "/functional/two_dice/two_dice.tra", STORM_CPP_TESTS_BASE_PATH "/functional/two_dice/two_dice.lab", "", STORM_CPP_TESTS_BASE_PATH "/functional/two_dice/two_dice.flip.trans.rew"); + + ASSERT_EQ(parser.getType(), storm::models::MDP); + + std::shared_ptr> mdp = parser.getModel>(); + + ASSERT_EQ(mdp->getNumberOfStates(), 169); + ASSERT_EQ(mdp->getNumberOfTransitions(), 436); + + storm::modelChecker::GmmxxMdpPrctlModelChecker mc(*mdp); + + storm::formula::Ap* apFormula = new storm::formula::Ap("two"); + storm::formula::Eventually* eventuallyFormula = new storm::formula::Eventually(apFormula); + storm::formula::ProbabilisticNoBoundOperator* probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula, true); + + std::vector* result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[0] - 0.0277777612209320068), s->get("precision")); + + delete probFormula; + delete result; + + apFormula = new storm::formula::Ap("two"); + eventuallyFormula = new storm::formula::Eventually(apFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula, false); + + result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[0] - 0.0277777612209320068), s->get("precision")); + + delete probFormula; + delete result; + + apFormula = new storm::formula::Ap("three"); + eventuallyFormula = new storm::formula::Eventually(apFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula, true); + + result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[0] - 0.0555555224418640136), s->get("precision")); + + delete probFormula; + delete result; + + apFormula = new storm::formula::Ap("three"); + eventuallyFormula = new storm::formula::Eventually(apFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula, false); + + result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[0] - 0.0555555224418640136), s->get("precision")); + + delete probFormula; + delete result; + + apFormula = new storm::formula::Ap("four"); + eventuallyFormula = new storm::formula::Eventually(apFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula, true); + + result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[0] - 0.083333283662796020508), s->get("precision")); + + delete probFormula; + delete result; + + apFormula = new storm::formula::Ap("four"); + eventuallyFormula = new storm::formula::Eventually(apFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula, false); + + result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[0] - 0.083333283662796020508), s->get("precision")); + + delete probFormula; + delete result; + + apFormula = new storm::formula::Ap("done"); + storm::formula::ReachabilityReward* reachabilityRewardFormula = new storm::formula::ReachabilityReward(apFormula); + storm::formula::RewardNoBoundOperator* rewardFormula = new storm::formula::RewardNoBoundOperator(reachabilityRewardFormula, true); + + result = rewardFormula->check(mc); + + ASSERT_LT(std::abs((*result)[0] - 7.3333294987678527832), s->get("precision")); + + delete rewardFormula; + delete result; + + apFormula = new storm::formula::Ap("done"); + reachabilityRewardFormula = new storm::formula::ReachabilityReward(apFormula); + rewardFormula = new storm::formula::RewardNoBoundOperator(reachabilityRewardFormula, false); + + result = rewardFormula->check(mc); + + ASSERT_LT(std::abs((*result)[0] - 7.3333294987678527832), s->get("precision")); + + delete rewardFormula; + delete result; + + storm::parser::AutoParser stateRewardParser(STORM_CPP_TESTS_BASE_PATH "/functional/two_dice/two_dice.tra", STORM_CPP_TESTS_BASE_PATH "/functional/two_dice/two_dice.lab", STORM_CPP_TESTS_BASE_PATH "/functional/two_dice/two_dice.flip.state.rew", ""); + + ASSERT_EQ(stateRewardParser.getType(), storm::models::MDP); + + std::shared_ptr> stateRewardMdp = stateRewardParser.getModel>(); + + storm::modelChecker::GmmxxMdpPrctlModelChecker stateRewardModelChecker(*stateRewardMdp); + + apFormula = new storm::formula::Ap("done"); + reachabilityRewardFormula = new storm::formula::ReachabilityReward(apFormula); + rewardFormula = new storm::formula::RewardNoBoundOperator(reachabilityRewardFormula, true); + + result = rewardFormula->check(stateRewardModelChecker); + + ASSERT_LT(std::abs((*result)[0] - 7.3333294987678527832), s->get("precision")); + + delete rewardFormula; + delete result; + + apFormula = new storm::formula::Ap("done"); + reachabilityRewardFormula = new storm::formula::ReachabilityReward(apFormula); + rewardFormula = new storm::formula::RewardNoBoundOperator(reachabilityRewardFormula, false); + + result = rewardFormula->check(stateRewardModelChecker); + + ASSERT_LT(std::abs((*result)[0] - 7.3333294987678527832), s->get("precision")); + + delete rewardFormula; + delete result; + + storm::parser::AutoParser stateAndTransitionRewardParser(STORM_CPP_TESTS_BASE_PATH "/functional/two_dice/two_dice.tra", STORM_CPP_TESTS_BASE_PATH "/functional/two_dice/two_dice.lab", STORM_CPP_TESTS_BASE_PATH "/functional/two_dice/two_dice.flip.state.rew", STORM_CPP_TESTS_BASE_PATH "/functional/two_dice/two_dice.flip.trans.rew"); + + ASSERT_EQ(stateAndTransitionRewardParser.getType(), storm::models::MDP); + + std::shared_ptr> stateAndTransitionRewardMdp = stateAndTransitionRewardParser.getModel>(); + + storm::modelChecker::GmmxxMdpPrctlModelChecker stateAndTransitionRewardModelChecker(*stateAndTransitionRewardMdp); + + apFormula = new storm::formula::Ap("done"); + reachabilityRewardFormula = new storm::formula::ReachabilityReward(apFormula); + rewardFormula = new storm::formula::RewardNoBoundOperator(reachabilityRewardFormula, true); + + result = rewardFormula->check(stateAndTransitionRewardModelChecker); + + ASSERT_LT(std::abs((*result)[0] - (2 * 7.3333294987678527832)), s->get("precision")); + + delete rewardFormula; + delete result; + + apFormula = new storm::formula::Ap("done"); + reachabilityRewardFormula = new storm::formula::ReachabilityReward(apFormula); + rewardFormula = new storm::formula::RewardNoBoundOperator(reachabilityRewardFormula, false); + + result = rewardFormula->check(stateAndTransitionRewardModelChecker); + + ASSERT_LT(std::abs((*result)[0] - (2 * 7.3333294987678527832)), s->get("precision")); + + delete rewardFormula; + delete result; +} + +TEST(GmmxxMdpPrctModelCheckerTest, AsynchronousLeader) { + storm::settings::Settings* s = storm::settings::instance(); + storm::parser::AutoParser parser(STORM_CPP_TESTS_BASE_PATH "/functional/asynchronous_leader/leader4.tra", STORM_CPP_TESTS_BASE_PATH "/functional/asynchronous_leader/leader4.lab", "", STORM_CPP_TESTS_BASE_PATH "/functional/asynchronous_leader/leader4.trans.rew"); + + ASSERT_EQ(parser.getType(), storm::models::MDP); + + std::shared_ptr> mdp = parser.getModel>(); + + ASSERT_EQ(mdp->getNumberOfStates(), 3172); + ASSERT_EQ(mdp->getNumberOfTransitions(), 7144); + + storm::modelChecker::GmmxxMdpPrctlModelChecker mc(*mdp); + + storm::formula::Ap* apFormula = new storm::formula::Ap("elected"); + storm::formula::Eventually* eventuallyFormula = new storm::formula::Eventually(apFormula); + storm::formula::ProbabilisticNoBoundOperator* probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula, true); + + std::vector* result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[0] - 1), s->get("precision")); + + delete probFormula; + delete result; + + apFormula = new storm::formula::Ap("elected"); + eventuallyFormula = new storm::formula::Eventually(apFormula); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(eventuallyFormula, false); + + result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[0] - 1), s->get("precision")); + + delete probFormula; + delete result; + + apFormula = new storm::formula::Ap("elected"); + storm::formula::BoundedEventually* boundedEventuallyFormula = new storm::formula::BoundedEventually(apFormula, 25); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(boundedEventuallyFormula, true); + + result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[0] - 0.0625), s->get("precision")); + + delete probFormula; + delete result; + + apFormula = new storm::formula::Ap("elected"); + boundedEventuallyFormula = new storm::formula::BoundedEventually(apFormula, 25); + probFormula = new storm::formula::ProbabilisticNoBoundOperator(boundedEventuallyFormula, false); + + result = probFormula->check(mc); + + ASSERT_LT(std::abs((*result)[0] - 0.0625), s->get("precision")); + + delete probFormula; + delete result; + + apFormula = new storm::formula::Ap("elected"); + storm::formula::ReachabilityReward* reachabilityRewardFormula = new storm::formula::ReachabilityReward(apFormula); + storm::formula::RewardNoBoundOperator* rewardFormula = new storm::formula::RewardNoBoundOperator(reachabilityRewardFormula, true); + + result = rewardFormula->check(mc); + + ASSERT_LT(std::abs((*result)[0] - 4.28568908480604982), s->get("precision")); + + delete rewardFormula; + delete result; + + apFormula = new storm::formula::Ap("elected"); + reachabilityRewardFormula = new storm::formula::ReachabilityReward(apFormula); + rewardFormula = new storm::formula::RewardNoBoundOperator(reachabilityRewardFormula, false); + + result = rewardFormula->check(mc); + + ASSERT_LT(std::abs((*result)[0] - 4.2856904354441400784), s->get("precision")); + + delete rewardFormula; + delete result; +} diff --git a/test/parser/PrctlParserTest.cpp b/test/parser/PrctlParserTest.cpp index dbb6c2a10..c3f7d9fcf 100644 --- a/test/parser/PrctlParserTest.cpp +++ b/test/parser/PrctlParserTest.cpp @@ -9,7 +9,6 @@ #include "gtest/gtest.h" #include "storm-config.h" #include "src/parser/PrctlParser.h" -#include "src/parser/PrctlFileParser.h" TEST(PrctlParserTest, parseApOnlyTest) { std::string ap = "ap"; @@ -29,104 +28,104 @@ TEST(PrctlParserTest, parseApOnlyTest) { } TEST(PrctlParserTest, parsePropositionalFormulaTest) { - storm::parser::PrctlFileParser* prctlFileParser = nullptr; + storm::parser::PrctlParser* prctlParser = nullptr; ASSERT_NO_THROW( - prctlFileParser = new storm::parser::PrctlFileParser(STORM_CPP_TESTS_BASE_PATH "/parser/prctl_files/propositionalFormula.prctl") + prctlParser = new storm::parser::PrctlParser("!(a & b) | a & ! c") ); - ASSERT_NE(prctlFileParser->getFormula(), nullptr); + ASSERT_NE(prctlParser->getFormula(), nullptr); - ASSERT_EQ(prctlFileParser->getFormula()->toString(), "(!(a & b) | (a & !c))"); + ASSERT_EQ(prctlParser->getFormula()->toString(), "(!(a & b) | (a & !c))"); - delete prctlFileParser->getFormula(); - delete prctlFileParser; + delete prctlParser->getFormula(); + delete prctlParser; } TEST(PrctlParserTest, parseProbabilisticFormulaTest) { - storm::parser::PrctlFileParser* prctlFileParser = nullptr; + storm::parser::PrctlParser* prctlParser = nullptr; ASSERT_NO_THROW( - prctlFileParser = new storm::parser::PrctlFileParser(STORM_CPP_TESTS_BASE_PATH "/parser/prctl_files/probabilisticFormula.prctl") + prctlParser = new storm::parser::PrctlParser("P > 0.5 [ F a ]") ); - ASSERT_NE(prctlFileParser->getFormula(), nullptr); + ASSERT_NE(prctlParser->getFormula(), nullptr); - storm::formula::ProbabilisticBoundOperator* op = static_cast*>(prctlFileParser->getFormula()); + storm::formula::ProbabilisticBoundOperator* op = static_cast*>(prctlParser->getFormula()); ASSERT_EQ(storm::formula::PathBoundOperator::GREATER, op->getComparisonOperator()); ASSERT_EQ(0.5, op->getBound()); - ASSERT_EQ(prctlFileParser->getFormula()->toString(), "P > 0.500000 [F a]"); + ASSERT_EQ(prctlParser->getFormula()->toString(), "P > 0.500000 [F a]"); - delete prctlFileParser->getFormula(); - delete prctlFileParser; + delete prctlParser->getFormula(); + delete prctlParser; } TEST(PrctlParserTest, parseRewardFormulaTest) { - storm::parser::PrctlFileParser* prctlFileParser = nullptr; + storm::parser::PrctlParser* prctlParser = nullptr; ASSERT_NO_THROW( - prctlFileParser = new storm::parser::PrctlFileParser(STORM_CPP_TESTS_BASE_PATH "/parser/prctl_files/rewardFormula.prctl") + prctlParser = new storm::parser::PrctlParser("R >= 15 [ a U !b ]") ); - ASSERT_NE(prctlFileParser->getFormula(), nullptr); + ASSERT_NE(prctlParser->getFormula(), nullptr); - storm::formula::RewardBoundOperator* op = static_cast*>(prctlFileParser->getFormula()); + storm::formula::RewardBoundOperator* op = static_cast*>(prctlParser->getFormula()); ASSERT_EQ(storm::formula::PathBoundOperator::GREATER_EQUAL, op->getComparisonOperator()); ASSERT_EQ(15.0, op->getBound()); - ASSERT_EQ(prctlFileParser->getFormula()->toString(), "R >= 15.000000 [a U !b]"); + ASSERT_EQ(prctlParser->getFormula()->toString(), "R >= 15.000000 [a U !b]"); - delete prctlFileParser->getFormula(); - delete prctlFileParser; + delete prctlParser->getFormula(); + delete prctlParser; } TEST(PrctlParserTest, parseRewardNoBoundFormulaTest) { - storm::parser::PrctlFileParser* prctlFileParser = nullptr; + storm::parser::PrctlParser* prctlParser = nullptr; ASSERT_NO_THROW( - prctlFileParser = new storm::parser::PrctlFileParser(STORM_CPP_TESTS_BASE_PATH "/parser/prctl_files/rewardNoBoundFormula.prctl") + prctlParser = new storm::parser::PrctlParser("R = ? [ a U <= 4 b & (!c) ]") ); - ASSERT_NE(prctlFileParser->getFormula(), nullptr); + ASSERT_NE(prctlParser->getFormula(), nullptr); - ASSERT_EQ(prctlFileParser->getFormula()->toString(), "R = ? [a U<=4 (b & !c)]"); + ASSERT_EQ(prctlParser->getFormula()->toString(), "R = ? [a U<=4 (b & !c)]"); - delete prctlFileParser->getFormula(); - delete prctlFileParser; + delete prctlParser->getFormula(); + delete prctlParser; } TEST(PrctlParserTest, parseProbabilisticNoBoundFormulaTest) { - storm::parser::PrctlFileParser* prctlFileParser = nullptr; + storm::parser::PrctlParser* prctlParser = nullptr; ASSERT_NO_THROW( - prctlFileParser = new storm::parser::PrctlFileParser(STORM_CPP_TESTS_BASE_PATH "/parser/prctl_files/probabilisticNoBoundFormula.prctl") + prctlParser = new storm::parser::PrctlParser("P = ? [ F <= 42 !a ]") ); - ASSERT_NE(prctlFileParser->getFormula(), nullptr); + ASSERT_NE(prctlParser->getFormula(), nullptr); - ASSERT_EQ(prctlFileParser->getFormula()->toString(), "P = ? [F<=42 !a]"); + ASSERT_EQ(prctlParser->getFormula()->toString(), "P = ? [F<=42 !a]"); - delete prctlFileParser->getFormula(); - delete prctlFileParser; + delete prctlParser->getFormula(); + delete prctlParser; } TEST(PrctlParserTest, parseComplexFormulaTest) { - storm::parser::PrctlFileParser* prctlFileParser = nullptr; + storm::parser::PrctlParser* prctlParser = nullptr; ASSERT_NO_THROW( - prctlFileParser = new storm::parser::PrctlFileParser(STORM_CPP_TESTS_BASE_PATH "/parser/prctl_files/complexFormula.prctl") + prctlParser = new storm::parser::PrctlParser("P<=0.5 [ F a ] & (R > 15 [ G P>0.9 [F<=7 a & b] ] | !P < 0.4 [ G !b ])") ); - ASSERT_NE(prctlFileParser->getFormula(), nullptr); + ASSERT_NE(prctlParser->getFormula(), nullptr); - ASSERT_EQ(prctlFileParser->getFormula()->toString(), "(P <= 0.500000 [F a] & (R > 15.000000 [G P > 0.900000 [F<=7 (a & b)]] | !P < 0.400000 [G !b]))"); - delete prctlFileParser->getFormula(); - delete prctlFileParser; + ASSERT_EQ(prctlParser->getFormula()->toString(), "(P <= 0.500000 [F a] & (R > 15.000000 [G P > 0.900000 [F<=7 (a & b)]] | !P < 0.400000 [G !b]))"); + delete prctlParser->getFormula(); + delete prctlParser; } @@ -144,7 +143,7 @@ TEST(PrctlParserTest, wrongProbabilisticFormulaTest) { TEST(PrctlParserTest, wrongFormulaTest) { storm::parser::PrctlParser* prctlParser = nullptr; ASSERT_THROW( - prctlParser = new storm::parser::PrctlFileParser("(a | b) & ΓΌ"), + prctlParser = new storm::parser::PrctlParser("(a | b) & +"), storm::exceptions::WrongFormatException ); delete prctlParser; @@ -153,7 +152,7 @@ TEST(PrctlParserTest, wrongFormulaTest) { TEST(PrctlParserTest, wrongFormulaTest2) { storm::parser::PrctlParser* prctlParser = nullptr; ASSERT_THROW( - prctlParser = new storm::parser::PrctlFileParser("P>0 [ F & a ]"), + prctlParser = new storm::parser::PrctlParser("P>0 [ F & a ]"), storm::exceptions::WrongFormatException ); delete prctlParser; diff --git a/test/storage/SparseMatrixTest.cpp b/test/storage/SparseMatrixTest.cpp index 69d11cc37..96a1cea43 100644 --- a/test/storage/SparseMatrixTest.cpp +++ b/test/storage/SparseMatrixTest.cpp @@ -3,6 +3,7 @@ #include "src/exceptions/InvalidArgumentException.h" #include "src/exceptions/OutOfRangeException.h" #include "src/adapters/EigenAdapter.h" +#include "src/adapters/StormAdapter.h" TEST(SparseMatrixTest, ZeroRowsTest) { storm::storage::SparseMatrix *ssm = new storm::storage::SparseMatrix(0); @@ -138,9 +139,6 @@ TEST(SparseMatrixTest, Test) { TEST(SparseMatrixTest, ConversionFromDenseEigen_ColMajor_SparseMatrixTest) { // 10 rows, 100 non zero entries - storm::storage::SparseMatrix *ssm = new storm::storage::SparseMatrix(10); - ASSERT_EQ(ssm->getState(), storm::storage::SparseMatrix::MatrixStatus::UnInitialized); - Eigen::SparseMatrix esm(10, 10); for (int row = 0; row < 10; ++row) { for (int col = 0; col < 10; ++col) { @@ -151,9 +149,8 @@ TEST(SparseMatrixTest, ConversionFromDenseEigen_ColMajor_SparseMatrixTest) { // make compressed, important for initialize() esm.makeCompressed(); - ASSERT_NO_THROW(ssm->initialize(esm)); - - ASSERT_NO_THROW(ssm->finalize()); + storm::storage::SparseMatrix *ssm = nullptr; + ASSERT_NO_THROW(ssm = storm::adapters::StormAdapter::toStormSparseMatrix(esm)); ASSERT_EQ(ssm->getState(), storm::storage::SparseMatrix::MatrixStatus::ReadReady); @@ -170,9 +167,6 @@ TEST(SparseMatrixTest, ConversionFromDenseEigen_ColMajor_SparseMatrixTest) { TEST(SparseMatrixTest, ConversionFromDenseEigen_RowMajor_SparseMatrixTest) { // 10 rows, 100 non zero entries - storm::storage::SparseMatrix *ssm = new storm::storage::SparseMatrix(10); - ASSERT_EQ(ssm->getState(), storm::storage::SparseMatrix::MatrixStatus::UnInitialized); - Eigen::SparseMatrix esm(10, 10); for (int row = 0; row < 10; ++row) { for (int col = 0; col < 10; ++col) { @@ -183,9 +177,8 @@ TEST(SparseMatrixTest, ConversionFromDenseEigen_RowMajor_SparseMatrixTest) { // make compressed, important for initialize() esm.makeCompressed(); - ASSERT_NO_THROW(ssm->initialize(esm)); - - ASSERT_NO_THROW(ssm->finalize()); + storm::storage::SparseMatrix *ssm = nullptr; + ASSERT_NO_THROW(ssm = storm::adapters::StormAdapter::toStormSparseMatrix(esm)); ASSERT_EQ(ssm->getState(), storm::storage::SparseMatrix::MatrixStatus::ReadReady); @@ -202,9 +195,6 @@ TEST(SparseMatrixTest, ConversionFromDenseEigen_RowMajor_SparseMatrixTest) { TEST(SparseMatrixTest, ConversionFromSparseEigen_ColMajor_SparseMatrixTest) { // 10 rows, 15 non zero entries - storm::storage::SparseMatrix *ssm = new storm::storage::SparseMatrix(10); - ASSERT_EQ(ssm->getState(), storm::storage::SparseMatrix::MatrixStatus::UnInitialized); - Eigen::SparseMatrix esm(10, 10); typedef Eigen::Triplet IntTriplet; @@ -233,9 +223,8 @@ TEST(SparseMatrixTest, ConversionFromSparseEigen_ColMajor_SparseMatrixTest) { // make compressed, important for initialize() esm.makeCompressed(); - ASSERT_NO_THROW(ssm->initialize(esm)); - - ASSERT_NO_THROW(ssm->finalize()); + storm::storage::SparseMatrix *ssm = nullptr; + ASSERT_NO_THROW(ssm = storm::adapters::StormAdapter::toStormSparseMatrix(esm)); ASSERT_EQ(ssm->getState(), storm::storage::SparseMatrix::MatrixStatus::ReadReady); @@ -251,9 +240,6 @@ TEST(SparseMatrixTest, ConversionFromSparseEigen_ColMajor_SparseMatrixTest) { TEST(SparseMatrixTest, ConversionFromSparseEigen_RowMajor_SparseMatrixTest) { // 10 rows, 15 non zero entries - storm::storage::SparseMatrix *ssm = new storm::storage::SparseMatrix(10, 10); - ASSERT_EQ(ssm->getState(), storm::storage::SparseMatrix::MatrixStatus::UnInitialized); - Eigen::SparseMatrix esm(10, 10); typedef Eigen::Triplet IntTriplet; @@ -282,9 +268,8 @@ TEST(SparseMatrixTest, ConversionFromSparseEigen_RowMajor_SparseMatrixTest) { // make compressed, important for initialize() esm.makeCompressed(); - ASSERT_NO_THROW(ssm->initialize(esm)); - - ASSERT_NO_THROW(ssm->finalize()); + storm::storage::SparseMatrix *ssm = nullptr; + ASSERT_NO_THROW(ssm = storm::adapters::StormAdapter::toStormSparseMatrix(esm)); ASSERT_EQ(ssm->getState(), storm::storage::SparseMatrix::MatrixStatus::ReadReady); diff --git a/test/eigen/EigenAdapterTest.cpp b/test/storage/adapters/EigenAdapterTest.cpp similarity index 100% rename from test/eigen/EigenAdapterTest.cpp rename to test/storage/adapters/EigenAdapterTest.cpp diff --git a/test/storage/adapters/GmmAdapterTest.cpp b/test/storage/adapters/GmmAdapterTest.cpp new file mode 100644 index 000000000..44488026b --- /dev/null +++ b/test/storage/adapters/GmmAdapterTest.cpp @@ -0,0 +1,111 @@ +#include "gtest/gtest.h" + +#include "gmm/gmm_matrix.h" +#include "src/adapters/GmmxxAdapter.h" +#include "src/exceptions/InvalidArgumentException.h" +#include "boost/integer/integer_mask.hpp" + +#define STORM_GMMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE 5 +#define STORM_GMMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE 5 + +double getValue(gmm::csr_matrix const& gmmSparseMatrix, uint_fast64_t row, uint_fast64_t col) { + uint_fast64_t rowStart = gmmSparseMatrix.jc.at(row); + uint_fast64_t rowEnd = gmmSparseMatrix.jc.at(row + 1); + while (rowStart < rowEnd) { + if (gmmSparseMatrix.ir.at(rowStart) == col) { + return gmmSparseMatrix.pr.at(rowStart); + } + if (gmmSparseMatrix.ir.at(rowStart) > col) { + break; + } + ++rowStart; + } + + return 0.0; +} + +TEST(GmmAdapterTest, SimpleDenseSquareCopy) { + // 5 rows + storm::storage::SparseMatrix sm(STORM_GMMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE); + + double values[STORM_GMMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE * STORM_GMMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE]; + sm.initialize(STORM_GMMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE * STORM_GMMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE); + + int row = 0; + int col = 0; + for (int i = 0; i < STORM_GMMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE * STORM_GMMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE; ++i) { + values[i] = static_cast(i + 1); + + sm.addNextValue(row, col, values[i]); + ++col; + if (col == STORM_GMMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE) { + ++row; + col = 0; + } + } + sm.finalize(); + + auto gsm = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(sm); + + ASSERT_EQ(gsm->nrows(), STORM_GMMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE); + ASSERT_EQ(gsm->ncols(), STORM_GMMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE); + ASSERT_EQ(gmm::nnz(*gsm), STORM_GMMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE * STORM_GMMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE); + row = 0; + col = 0; + for (int i = 0; i < STORM_GMMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE * STORM_GMMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE; ++i) { + ASSERT_EQ(values[i], getValue(*gsm, row, col)); + ++col; + if (col == STORM_GMMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE) { + ++row; + col = 0; + } + } +} + +TEST(GmmAdapterTest, SimpleSparseSquareCopy) { + // 5 rows + storm::storage::SparseMatrix sm(STORM_GMMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE); + + double values[STORM_GMMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE * STORM_GMMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE]; + sm.initialize((STORM_GMMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE * STORM_GMMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE + 1) / 2); + + int row = 0; + int col = 0; + + bool everySecondElement = true; + + for (int i = 0; i < STORM_GMMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE * STORM_GMMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE; ++i) { + values[i] = static_cast(i + 1); + if (everySecondElement) { + sm.addNextValue(row, col, values[i]); + } + everySecondElement = !everySecondElement; + ++col; + if (col == STORM_GMMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE) { + ++row; + col = 0; + } + } + sm.finalize(); + + auto gsm = storm::adapters::GmmxxAdapter::toGmmxxSparseMatrix(sm); + + ASSERT_EQ(gsm->nrows(), STORM_GMMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE); + ASSERT_EQ(gsm->ncols(), STORM_GMMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE); + ASSERT_EQ(gmm::nnz(*gsm), (STORM_GMMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE * STORM_GMMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE + 1) / 2); + + row = 0; + col = 0; + everySecondElement = true; + for (int i = 0; i < STORM_GMMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE * STORM_GMMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE; ++i) { + if (everySecondElement) { + ASSERT_EQ(values[i], getValue(*gsm, row, col)); + } + everySecondElement = !everySecondElement; + ++col; + if (col == STORM_GMMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE) { + ++row; + col = 0; + } + } +} diff --git a/test/storage/adapters/StormAdapterTest.cpp b/test/storage/adapters/StormAdapterTest.cpp new file mode 100644 index 000000000..fec55360e --- /dev/null +++ b/test/storage/adapters/StormAdapterTest.cpp @@ -0,0 +1,104 @@ +#include "gtest/gtest.h" + +#include "gmm/gmm_matrix.h" +#include "src/adapters/StormAdapter.h" +#include "src/exceptions/InvalidArgumentException.h" +#include "boost/integer/integer_mask.hpp" + +#define STORM_STORMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE 5 +#define STORM_STORMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE 5 + +TEST(StormAdapterTest, SimpleDenseSquareCopy) { + // 5 rows + gmm::csr_matrix gmmSparseMatrix; + + /* + * As CSR_Matrix is read-only we have to prepare the data in this row_matrix and then do a copy. + */ + gmm::row_matrix> gmmPreMatrix(STORM_STORMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE, STORM_STORMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE); + + double values[STORM_STORMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE * STORM_STORMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE]; + + int row = 0; + int col = 0; + for (int i = 0; i < STORM_STORMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE * STORM_STORMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE; ++i) { + values[i] = static_cast(i + 1); + + gmmPreMatrix(row, col) = values[i]; + ++col; + if (col == STORM_STORMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE) { + ++row; + col = 0; + } + } + gmm::copy(gmmPreMatrix, gmmSparseMatrix); + + auto stormSparseMatrix = storm::adapters::StormAdapter::toStormSparseMatrix(gmmSparseMatrix); + + ASSERT_EQ(stormSparseMatrix->getRowCount(), STORM_STORMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE); + ASSERT_EQ(stormSparseMatrix->getColumnCount(), STORM_STORMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE); + ASSERT_EQ(stormSparseMatrix->getNonZeroEntryCount(), STORM_STORMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE * STORM_STORMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE); + row = 0; + col = 0; + for (int i = 0; i < STORM_STORMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE * STORM_STORMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE; ++i) { + ASSERT_EQ(values[i], stormSparseMatrix->getValue(row, col)); + ++col; + if (col == STORM_STORMADAPTERTEST_SIMPLEDENSESQUARECOPY_SIZE) { + ++row; + col = 0; + } + } +} + +TEST(StormAdapterTest, SimpleSparseSquareCopy) { + // 5 rows + gmm::csr_matrix gmmSparseMatrix; + + /* + * As CSR_Matrix is read-only we have to prepare the data in this row_matrix and then do a copy. + */ + gmm::row_matrix> gmmPreMatrix(STORM_STORMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE, STORM_STORMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE); + + double values[STORM_STORMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE * STORM_STORMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE]; + + int row = 0; + int col = 0; + + bool everySecondElement = true; + + for (int i = 0; i < STORM_STORMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE * STORM_STORMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE; ++i) { + values[i] = static_cast(i + 1); + if (everySecondElement) { + gmmPreMatrix(row, col) = values[i]; + } + everySecondElement = !everySecondElement; + ++col; + if (col == STORM_STORMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE) { + ++row; + col = 0; + } + } + gmm::copy(gmmPreMatrix, gmmSparseMatrix); + + auto stormSparseMatrix = storm::adapters::StormAdapter::toStormSparseMatrix(gmmSparseMatrix); + + ASSERT_EQ(stormSparseMatrix->getRowCount(), STORM_STORMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE); + ASSERT_EQ(stormSparseMatrix->getColumnCount(), STORM_STORMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE); + ASSERT_EQ(stormSparseMatrix->getNonZeroEntryCount(), (STORM_STORMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE * STORM_STORMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE + 1) / 2); + + + row = 0; + col = 0; + everySecondElement = true; + for (int i = 0; i < STORM_STORMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE * STORM_STORMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE; ++i) { + if (everySecondElement) { + ASSERT_EQ(values[i], stormSparseMatrix->getValue(row, col)); + } + everySecondElement = !everySecondElement; + ++col; + if (col == STORM_STORMADAPTERTEST_SIMPLESPARSESQUARECOPY_SIZE) { + ++row; + col = 0; + } + } +}