/*! * TraParser.cpp * * Created on: 20.11.2012 * Author: Gereon Kremer */ #include "src/parser/NondeterministicSparseTransitionParser.h" #include <errno.h> #include <time.h> #include <sys/stat.h> #include <fcntl.h> #include <locale.h> #include <cstdlib> #include <cstdio> #include <cstring> #include <clocale> #include <iostream> #include <utility> #include <string> #include "src/utility/Settings.h" #include "src/exceptions/FileIoException.h" #include "src/exceptions/WrongFileFormatException.h" #include "boost/integer/integer_mask.hpp" #include "log4cplus/logger.h" #include "log4cplus/loggingmacros.h" extern log4cplus::Logger logger; namespace storm { namespace parser { /*! * @brief Perform first pass through the file and obtain overall number of * choices, number of non-zero cells and maximum node id. * * This method does the first pass through the transition file. * * It computes the overall number of nondeterministic choices, i.e. the * number of rows in the matrix that should be created. * It also calculates the overall number of non-zero cells, i.e. the number * of elements the matrix has to hold, and the maximum node id, i.e. the * number of columns of the matrix. * * @param buf Data to scan. Is expected to be some char array. * @param choices Overall number of choices. * @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, RewardMatrixInformationStruct* rewardMatrixInformation) { bool isRewardFile = rewardMatrixInformation != nullptr; /* * Check file header and extract number of transitions. */ if (!isRewardFile) { buf = strchr(buf, '\n') + 1; // skip format hint } /* * Read all transitions. */ int_fast64_t source, target, choice, lastchoice = -1; int_fast64_t lastsource = -1; uint_fast64_t nonzero = 0; double val; choices = 0; maxnode = 0; while (buf[0] != '\0') { /* * Read source state and choice. */ source = checked_strtol(buf, &buf); // Read the name of the nondeterministic choice. choice = checked_strtol(buf, &buf); // Check if we encountered a state index that is bigger than all previously seen. if (source > maxnode) { maxnode = source; } 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 // seen. target = checked_strtol(buf, &buf); if (target > maxnode) { maxnode = target; } // Read value and check whether it's positive. val = checked_strtod(buf, &buf); if ((val < 0.0) || (val > 1.0)) { LOG4CPLUS_ERROR(logger, "Expected a positive probability but got \"" << std::string(buf, 0, 16) << "\"."); return 0; } lastchoice = choice; lastsource = source; /* * Increase number of non-zero values. */ nonzero++; // The PRISM output format lists the name of the transition in the fourth column, // but omits the fourth column if it is an internal action. In either case, however, the third column // is followed by a space. We need to skip over that space first (instead of trimming whitespaces), // before we can skip to the line end, because trimming the white spaces will proceed to the next line // in case there is no action label in the fourth column. if (buf[0] == ' ') { ++buf; } /* * Proceed to beginning of next line. */ buf += strcspn(buf, " \t\n\r"); buf = trimWhitespaces(buf); } 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; } /*! * Reads a .tra file and produces a sparse matrix representing the described Markov Chain. * * Matrices created with this method have to be freed with the delete operator. * @param filename input .tra file's name. * @return a pointer to the created sparse matrix. */ 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. */ MappedFile file(filename.c_str()); char* buf = file.data; /* * Perform first pass, i.e. obtain number of columns, rows and non-zero elements. */ int_fast64_t maxnode; uint_fast64_t choices; uint_fast64_t nonzero = this->firstPass(file.data, choices, maxnode, rewardMatrixInformation); /* * If first pass returned zero, the file format was wrong. */ if (nonzero == 0) { LOG4CPLUS_ERROR(logger, "Error while parsing " << filename << ": erroneous file format."); throw storm::exceptions::WrongFileFormatException(); } /* * Perform second pass. * * From here on, we already know that the file header is correct. */ /* * Skip file header. */ 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::WrongFileFormatException() << "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::WrongFileFormatException() << "Reward matrix row count does not match transition matrix row count."; } else { maxnode = rewardMatrixInformation->columnCount - 1; } } /* * Create and initialize matrix. * The matrix should have as many columns as we have nodes and as many rows as we have choices. * Those two values, as well as the number of nonzero elements, was been calculated in the first run. */ LOG4CPLUS_INFO(logger, "Attempting to create matrix of size " << choices << " x " << (maxnode+1) << " with " << nonzero << " entries."); this->matrix = std::shared_ptr<storm::storage::SparseMatrix<double>>(new storm::storage::SparseMatrix<double>(choices, maxnode + 1)); if (this->matrix == nullptr) { LOG4CPLUS_ERROR(logger, "Could not create matrix of size " << choices << " x " << (maxnode+1) << "."); throw std::bad_alloc(); } this->matrix->initialize(nonzero); /* * Create row mapping. */ this->rowMapping = std::shared_ptr<std::vector<uint_fast64_t>>(new std::vector<uint_fast64_t>(maxnode+2,0)); /* * Parse file content. */ int_fast64_t source, target, lastsource = -1, choice, lastchoice = -1; uint_fast64_t curRow = -1; double val; bool fixDeadlocks = storm::settings::instance()->isSet("fix-deadlocks"); bool hadDeadlocks = false; /* * Read all transitions from file. */ while (buf[0] != '\0') { /* * Read source state and choice. */ source = checked_strtol(buf, &buf); choice = checked_strtol(buf, &buf); 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); } // 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; } /* * 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); ++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. target = checked_strtol(buf, &buf); val = checked_strtod(buf, &buf); this->matrix->addNextValue(curRow, target, val); lastsource = source; lastchoice = choice; /* * Proceed to beginning of next line in file and next row in matrix. */ if (buf[0] == ' ') { ++buf; } buf += strcspn(buf, " \t\n\r"); buf = trimWhitespaces(buf); } this->rowMapping->at(maxnode+1) = curRow + 1; if (!fixDeadlocks && hadDeadlocks && !isRewardFile) throw storm::exceptions::WrongFileFormatException() << "Some of the nodes had deadlocks. You can use --fix-deadlocks to insert self-loops on the fly."; /* * Finalize matrix. */ this->matrix->finalize(); } } // namespace parser } // namespace storm