You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

347 lines
20 KiB

  1. #include "storm/parser/NondeterministicSparseTransitionParser.h"
  2. #include <string>
  3. #include "storm/parser/MappedFile.h"
  4. #include "storm/settings/SettingsManager.h"
  5. #include "storm/settings/modules/CoreSettings.h"
  6. #include "storm/exceptions/FileIoException.h"
  7. #include "storm/exceptions/OutOfRangeException.h"
  8. #include "storm/exceptions/InvalidArgumentException.h"
  9. #include "storm/exceptions/WrongFormatException.h"
  10. #include "storm/utility/cstring.h"
  11. #include "storm/adapters/CarlAdapter.h"
  12. #include "storm/utility/macros.h"
  13. namespace storm {
  14. namespace parser {
  15. using namespace storm::utility::cstring;
  16. template<typename ValueType>
  17. storm::storage::SparseMatrix<ValueType> NondeterministicSparseTransitionParser<ValueType>::parseNondeterministicTransitions(std::string const& filename) {
  18. storm::storage::SparseMatrix<ValueType> emptyMatrix;
  19. return NondeterministicSparseTransitionParser::parse(filename, false, emptyMatrix);
  20. }
  21. template<typename ValueType>
  22. template<typename MatrixValueType>
  23. storm::storage::SparseMatrix<ValueType> NondeterministicSparseTransitionParser<ValueType>::parseNondeterministicTransitionRewards(std::string const& filename, storm::storage::SparseMatrix<MatrixValueType> const& modelInformation) {
  24. return NondeterministicSparseTransitionParser::parse(filename, true, modelInformation);
  25. }
  26. template<typename ValueType>
  27. template<typename MatrixValueType>
  28. storm::storage::SparseMatrix<ValueType> NondeterministicSparseTransitionParser<ValueType>::parse(std::string const& filename, bool isRewardFile, storm::storage::SparseMatrix<MatrixValueType> const& modelInformation) {
  29. // Enforce locale where decimal point is '.'.
  30. setlocale(LC_NUMERIC, "C");
  31. if (!MappedFile::fileExistsAndIsReadable(filename.c_str())) {
  32. STORM_LOG_ERROR("Error while parsing " << filename << ": File does not exist or is not readable.");
  33. throw storm::exceptions::FileIoException() << "Error while parsing " << filename << ": File does not exist or is not readable.";
  34. }
  35. // Open file.
  36. MappedFile file(filename.c_str());
  37. char const* buf = file.getData();
  38. // Perform first pass, i.e. obtain number of columns, rows and non-zero elements.
  39. NondeterministicSparseTransitionParser::FirstPassResult firstPass = NondeterministicSparseTransitionParser::firstPass(file.getData(), isRewardFile, modelInformation);
  40. // If first pass returned zero, the file format was wrong.
  41. if (firstPass.numberOfNonzeroEntries == 0) {
  42. STORM_LOG_ERROR("Error while parsing " << filename << ": erroneous file format.");
  43. throw storm::exceptions::WrongFormatException() << "Error while parsing " << filename << ": erroneous file format.";
  44. }
  45. // Perform second pass.
  46. // Skip the format hint if it is there.
  47. buf = trimWhitespaces(buf);
  48. if (buf[0] < '0' || buf[0] > '9') {
  49. buf = forwardToLineEnd(buf);
  50. buf = trimWhitespaces(buf);
  51. }
  52. if (isRewardFile) {
  53. // The reward matrix should match the size of the transition matrix.
  54. if (firstPass.choices > modelInformation.getRowCount() || (uint_fast64_t) (firstPass.highestStateIndex + 1) > modelInformation.getColumnCount()) {
  55. STORM_LOG_ERROR("Reward matrix size exceeds transition matrix size.");
  56. throw storm::exceptions::OutOfRangeException() << "Reward matrix size exceeds transition matrix size.";
  57. } else if (firstPass.choices != modelInformation.getRowCount()) {
  58. STORM_LOG_ERROR("Reward matrix row count does not match transition matrix row count.");
  59. throw storm::exceptions::OutOfRangeException() << "Reward matrix row count does not match transition matrix row count.";
  60. } else if (firstPass.numberOfNonzeroEntries > modelInformation.getEntryCount()) {
  61. STORM_LOG_ERROR("The reward matrix has more entries than the transition matrix. There must be a reward for a non existent transition");
  62. throw storm::exceptions::OutOfRangeException() << "The reward matrix has more entries than the transition matrix.";
  63. } else {
  64. firstPass.highestStateIndex = modelInformation.getColumnCount() - 1;
  65. }
  66. }
  67. // Create the matrix builder.
  68. // The matrix to be build should have as many columns as we have nodes and as many rows as we have choices.
  69. // Those two values, as well as the number of nonzero elements, was been calculated in the first run.
  70. STORM_LOG_INFO("Attempting to create matrix of size " << firstPass.choices << " x " << (firstPass.highestStateIndex + 1) << " with " << firstPass.numberOfNonzeroEntries << " entries.");
  71. storm::storage::SparseMatrixBuilder<ValueType> matrixBuilder;
  72. if (!isRewardFile) {
  73. matrixBuilder = storm::storage::SparseMatrixBuilder<ValueType>(firstPass.choices, firstPass.highestStateIndex + 1, firstPass.numberOfNonzeroEntries, true, true, firstPass.highestStateIndex + 1);
  74. } else {
  75. matrixBuilder = storm::storage::SparseMatrixBuilder<ValueType>(firstPass.choices, firstPass.highestStateIndex + 1, firstPass.numberOfNonzeroEntries, true, true, modelInformation.getRowGroupCount());
  76. }
  77. // Initialize variables for the parsing run.
  78. uint_fast64_t source = 0, target = 0, lastSource = 0, choice = 0, lastChoice = 0, curRow = 0;
  79. double val = 0.0;
  80. bool dontFixDeadlocks = storm::settings::getModule<storm::settings::modules::CoreSettings>().isDontFixDeadlocksSet();
  81. bool hadDeadlocks = false;
  82. // The first state already starts a new row group of the matrix.
  83. matrixBuilder.newRowGroup(0);
  84. // Read all transitions from file.
  85. while (buf[0] != '\0') {
  86. // Read source state and choice.
  87. source = checked_strtol(buf, &buf);
  88. choice = checked_strtol(buf, &buf);
  89. if (isRewardFile) {
  90. // If we have switched the source state, we possibly need to insert the rows of the last
  91. // source state.
  92. if (source != lastSource) {
  93. curRow += ((modelInformation.getRowGroupIndices())[lastSource + 1] - (modelInformation.getRowGroupIndices())[lastSource]) - (lastChoice + 1);
  94. }
  95. // If we skipped some states, we need to reserve empty rows for all their nondeterministic
  96. // choices and create the row groups.
  97. for (uint_fast64_t i = lastSource + 1; i < source; ++i) {
  98. matrixBuilder.newRowGroup(modelInformation.getRowGroupIndices()[i]);
  99. curRow += ((modelInformation.getRowGroupIndices())[i + 1] - (modelInformation.getRowGroupIndices())[i]);
  100. }
  101. // If we moved to the next source, we need to open the next row group.
  102. if (source != lastSource) {
  103. matrixBuilder.newRowGroup(modelInformation.getRowGroupIndices()[source]);
  104. }
  105. // If we advanced to the next state, but skipped some choices, we have to reserve rows
  106. // for them
  107. if (source != lastSource) {
  108. curRow += choice + 1;
  109. } else if (choice != lastChoice) {
  110. curRow += choice - lastChoice;
  111. }
  112. } else {
  113. // Increase line count if we have either finished reading the transitions of a certain state
  114. // or we have finished reading one nondeterministic choice of a state.
  115. if ((source != lastSource || choice != lastChoice)) {
  116. ++curRow;
  117. }
  118. // Check if we have skipped any source node, i.e. if any node has no
  119. // outgoing transitions. If so, insert a self-loop.
  120. // Also begin a new rowGroup for the skipped state.
  121. for (uint_fast64_t node = lastSource + 1; node < source; node++) {
  122. hadDeadlocks = true;
  123. if (!dontFixDeadlocks) {
  124. matrixBuilder.newRowGroup(curRow);
  125. matrixBuilder.addNextValue(curRow, node, 1);
  126. ++curRow;
  127. STORM_LOG_WARN("Warning while parsing " << filename << ": node " << node << " has no outgoing transitions. A self-loop was inserted.");
  128. } else {
  129. STORM_LOG_ERROR("Error while parsing " << filename << ": node " << node << " has no outgoing transitions.");
  130. }
  131. }
  132. if (source != lastSource) {
  133. // Create a new rowGroup for the source, if this is the first choice we encounter for this state.
  134. matrixBuilder.newRowGroup(curRow);
  135. }
  136. }
  137. // Read target and value and write it to the matrix.
  138. target = checked_strtol(buf, &buf);
  139. val = checked_strtod(buf, &buf);
  140. matrixBuilder.addNextValue(curRow, target, val);
  141. lastSource = source;
  142. lastChoice = choice;
  143. // Proceed to beginning of next line in file and next row in matrix.
  144. buf = forwardToLineEnd(buf);
  145. buf = trimWhitespaces(buf);
  146. }
  147. if (dontFixDeadlocks && hadDeadlocks && !isRewardFile) throw storm::exceptions::WrongFormatException() << "Some of the states do not have outgoing transitions.";
  148. // Since we assume the transition rewards are for the transitions of the model, we copy the rowGroupIndices.
  149. if (isRewardFile) {
  150. for (uint_fast64_t node = lastSource + 1; node < modelInformation.getRowGroupCount(); node++) {
  151. matrixBuilder.newRowGroup(modelInformation.getRowGroupIndices()[node]);
  152. }
  153. }
  154. // Finally, build the actual matrix, test and return it.
  155. storm::storage::SparseMatrix<ValueType> resultMatrix = matrixBuilder.build();
  156. // Since we cannot check if each transition for which there is a reward in the reward file also exists in the transition matrix during parsing, we have to do it afterwards.
  157. if (isRewardFile && !resultMatrix.isSubmatrixOf(modelInformation)) {
  158. STORM_LOG_ERROR("There are rewards for non existent transitions given in the reward file.");
  159. throw storm::exceptions::WrongFormatException() << "There are rewards for non existent transitions given in the reward file.";
  160. }
  161. return resultMatrix;
  162. }
  163. template<typename ValueType>
  164. template<typename MatrixValueType>
  165. typename NondeterministicSparseTransitionParser<ValueType>::FirstPassResult NondeterministicSparseTransitionParser<ValueType>::firstPass(char const* buf, bool isRewardFile, storm::storage::SparseMatrix<MatrixValueType> const& modelInformation) {
  166. // Check file header and extract number of transitions.
  167. // Skip the format hint if it is there.
  168. buf = trimWhitespaces(buf);
  169. if (buf[0] < '0' || buf[0] > '9') {
  170. buf = forwardToLineEnd(buf);
  171. buf = trimWhitespaces(buf);
  172. }
  173. // Read all transitions.
  174. uint_fast64_t source = 0, target = 0, choice = 0, lastChoice = 0, lastSource = 0, lastTarget = -1;
  175. double val = 0.0;
  176. typename NondeterministicSparseTransitionParser<ValueType>::FirstPassResult result;
  177. // Since the first line is already a new choice but is not covered below, that has to be covered here.
  178. result.choices = 1;
  179. while (buf[0] != '\0') {
  180. // Read source state and choice.
  181. source = checked_strtol(buf, &buf);
  182. // Read the name of the nondeterministic choice.
  183. choice = checked_strtol(buf, &buf);
  184. if (source < lastSource) {
  185. STORM_LOG_ERROR("The current source state " << source << " is smaller than the last one " << lastSource << ".");
  186. throw storm::exceptions::InvalidArgumentException() << "The current source state " << source << " is smaller than the last one " << lastSource << ".";
  187. }
  188. // Check if we encountered a state index that is bigger than all previously seen.
  189. if (source > result.highestStateIndex) {
  190. result.highestStateIndex = source;
  191. }
  192. if (isRewardFile) {
  193. // Make sure that the highest state index of the reward file is not higher than the highest state index of the corresponding model.
  194. if (result.highestStateIndex > modelInformation.getColumnCount() - 1) {
  195. STORM_LOG_ERROR("State index " << result.highestStateIndex << " found. This exceeds the highest state index of the model, which is " << modelInformation.getColumnCount() - 1 << " .");
  196. throw storm::exceptions::OutOfRangeException() << "State index " << result.highestStateIndex << " found. This exceeds the highest state index of the model, which is " << modelInformation.getColumnCount() - 1 << " .";
  197. }
  198. // If we have switched the source state, we possibly need to insert rows for skipped choices of the last
  199. // source state.
  200. if (source != lastSource) {
  201. // number of choices skipped = number of choices of last state - number of choices read
  202. result.choices += ((modelInformation.getRowGroupIndices())[lastSource + 1] - (modelInformation.getRowGroupIndices())[lastSource]) - (lastChoice + 1);
  203. }
  204. // If we skipped some states, we need to reserve empty rows for all their nondeterministic
  205. // choices.
  206. for (uint_fast64_t i = lastSource + 1; i < source; ++i) {
  207. result.choices += ((modelInformation.getRowGroupIndices())[i + 1] - (modelInformation.getRowGroupIndices())[i]);
  208. }
  209. // If we advanced to the next state, but skipped some choices, we have to reserve rows
  210. // for them.
  211. if (source != lastSource) {
  212. result.choices += choice + 1;
  213. } else if (choice != lastChoice) {
  214. result.choices += choice - lastChoice;
  215. }
  216. } else {
  217. // If we have skipped some states, we need to reserve the space for the self-loop insertion
  218. // in the second pass.
  219. if (source > lastSource + 1) {
  220. result.numberOfNonzeroEntries += source - lastSource - 1;
  221. result.choices += source - lastSource - 1;
  222. }
  223. if (source != lastSource || choice != lastChoice) {
  224. // If we have switched the source state or the nondeterministic choice, we need to
  225. // reserve one row more.
  226. ++result.choices;
  227. }
  228. }
  229. // Read target and check if we encountered a state index that is bigger than all previously seen.
  230. target = checked_strtol(buf, &buf);
  231. if (target > result.highestStateIndex) {
  232. result.highestStateIndex = target;
  233. }
  234. // Also, have we already seen this transition?
  235. if (target == lastTarget && choice == lastChoice && source == lastSource) {
  236. STORM_LOG_ERROR("The same transition (" << source << ", " << choice << ", " << target << ") is given twice.");
  237. throw storm::exceptions::InvalidArgumentException() << "The same transition (" << source << ", " << choice << ", " << target << ") is given twice.";
  238. }
  239. // Read value and check whether it's positive.
  240. val = checked_strtod(buf, &buf);
  241. if (!isRewardFile && (val < 0.0 || val > 1.0)) {
  242. STORM_LOG_ERROR("Expected a positive probability but got \"" << std::string(buf, 0, 16) << "\".");
  243. NondeterministicSparseTransitionParser::FirstPassResult nullResult;
  244. return nullResult;
  245. } else if (val < 0.0) {
  246. STORM_LOG_ERROR("Expected a positive reward value but got \"" << std::string(buf, 0, 16) << "\".");
  247. NondeterministicSparseTransitionParser::FirstPassResult nullResult;
  248. return nullResult;
  249. }
  250. lastChoice = choice;
  251. lastSource = source;
  252. lastTarget = target;
  253. // Increase number of non-zero values.
  254. result.numberOfNonzeroEntries++;
  255. // The PRISM output format lists the name of the transition in the fourth column,
  256. // but omits the fourth column if it is an internal action. In either case we can skip to the end of the line.
  257. buf = forwardToLineEnd(buf);
  258. buf = trimWhitespaces(buf);
  259. }
  260. if (isRewardFile) {
  261. // If not all rows were filled for the last state, we need to insert them.
  262. result.choices += ((modelInformation.getRowGroupIndices())[lastSource + 1] - (modelInformation.getRowGroupIndices())[lastSource]) - (lastChoice + 1);
  263. // If we skipped some states, we need to reserve empty rows for all their nondeterministic
  264. // choices.
  265. for (uint_fast64_t i = lastSource + 1; i < modelInformation.getRowGroupIndices().size() - 1; ++i) {
  266. result.choices += ((modelInformation.getRowGroupIndices())[i + 1] - (modelInformation.getRowGroupIndices())[i]);
  267. }
  268. }
  269. return result;
  270. }
  271. template class NondeterministicSparseTransitionParser<double>;
  272. template storm::storage::SparseMatrix<double> NondeterministicSparseTransitionParser<double>::parseNondeterministicTransitionRewards(std::string const& filename, storm::storage::SparseMatrix<double> const& modelInformation);
  273. template storm::storage::SparseMatrix<double> NondeterministicSparseTransitionParser<double>::parse(std::string const& filename, bool isRewardFile, storm::storage::SparseMatrix<double> const& modelInformation);
  274. #ifdef STORM_HAVE_CARL
  275. template class NondeterministicSparseTransitionParser<storm::Interval>;
  276. template storm::storage::SparseMatrix<storm::Interval> NondeterministicSparseTransitionParser<storm::Interval>::parseNondeterministicTransitionRewards<double>(std::string const& filename, storm::storage::SparseMatrix<double> const& modelInformation);
  277. template storm::storage::SparseMatrix<storm::Interval> NondeterministicSparseTransitionParser<storm::Interval>::parse<double>(std::string const& filename, bool isRewardFile, storm::storage::SparseMatrix<double> const& modelInformation);
  278. #endif
  279. } // namespace parser
  280. } // namespace storm