From fee00918e9c81dd31f03df045c92bed9e90b4d8e Mon Sep 17 00:00:00 2001 From: Magne Sjaastad Date: Thu, 2 Nov 2017 19:00:27 +0100 Subject: [PATCH] #2066 Observed Data : Major rewrite of varying column width parsing Add TableInfo to hold a table with several ColumnInfo objects Improved detection of RifEclipseSummaryAddress from all variants --- .../FileInterface/CMakeLists_files.cmake | 2 + .../FileInterface/RifColumnBasedUserData.cpp | 250 +++++---- .../FileInterface/RifColumnBasedUserData.h | 3 + .../RifColumnBasedUserDataParser.cpp | 82 ++- .../RifColumnBasedUserDataParser.h | 11 +- .../RifEclipseSummaryAddress.cpp | 12 + .../FileInterface/RifEclipseSummaryAddress.h | 1 + .../RifEclipseUserDataKeywordTools.cpp | 248 +++++++++ .../RifEclipseUserDataKeywordTools.h | 42 ++ .../RifEclipseUserDataParserTools.cpp | 473 +++++++++--------- .../RifEclipseUserDataParserTools.h | 89 +++- .../UnitTests/CMakeLists_files.cmake | 3 +- .../UnitTests/ObservedDataParser-Test.cpp | 145 +++--- .../RifEclipseUserDataKeywordTools-Test.cpp | 280 +++++++++++ 14 files changed, 1146 insertions(+), 495 deletions(-) create mode 100644 ApplicationCode/FileInterface/RifEclipseUserDataKeywordTools.cpp create mode 100644 ApplicationCode/FileInterface/RifEclipseUserDataKeywordTools.h create mode 100644 ApplicationCode/UnitTests/RifEclipseUserDataKeywordTools-Test.cpp diff --git a/ApplicationCode/FileInterface/CMakeLists_files.cmake b/ApplicationCode/FileInterface/CMakeLists_files.cmake index 6dff8aae33..d61bddc1e0 100644 --- a/ApplicationCode/FileInterface/CMakeLists_files.cmake +++ b/ApplicationCode/FileInterface/CMakeLists_files.cmake @@ -36,6 +36,7 @@ ${CEE_CURRENT_LIST_DIR}RifColumnBasedUserData.h ${CEE_CURRENT_LIST_DIR}RifKeywordVectorUserData.h ${CEE_CURRENT_LIST_DIR}RifWellRftAddress.h ${CEE_CURRENT_LIST_DIR}RifWellRftAddressQMetaType.h +${CEE_CURRENT_LIST_DIR}RifEclipseUserDataKeywordTools.h # HDF5 file reader is directly included in ResInsight main CmakeList.txt #${CEE_CURRENT_LIST_DIR}RifHdf5Reader.h @@ -77,6 +78,7 @@ ${CEE_CURRENT_LIST_DIR}RifHdf5ReaderInterface.cpp ${CEE_CURRENT_LIST_DIR}RifColumnBasedUserData.cpp ${CEE_CURRENT_LIST_DIR}RifKeywordVectorUserData.cpp ${CEE_CURRENT_LIST_DIR}RifWellRftAddress.cpp +${CEE_CURRENT_LIST_DIR}RifEclipseUserDataKeywordTools.cpp # HDF5 file reader is directly included in ResInsight main CmakeList.txt diff --git a/ApplicationCode/FileInterface/RifColumnBasedUserData.cpp b/ApplicationCode/FileInterface/RifColumnBasedUserData.cpp index 90823d5dbd..ee5e17b06a 100644 --- a/ApplicationCode/FileInterface/RifColumnBasedUserData.cpp +++ b/ApplicationCode/FileInterface/RifColumnBasedUserData.cpp @@ -18,10 +18,12 @@ #include "RifColumnBasedUserData.h" -#include "RiaQDateTimeTools.h" +#include "RiaDateStringParser.h" #include "RiaLogging.h" +#include "RiaQDateTimeTools.h" #include "RifColumnBasedUserDataParser.h" +#include "RifEclipseUserDataKeywordTools.h" #include "RifEclipseUserDataParserTools.h" #include "cafUtils.h" @@ -64,130 +66,7 @@ bool RifColumnBasedUserData::parse(const QString& data, const QString& customWel return false; } - for (size_t tableIndex = 0; tableIndex < m_parser->tables().size(); tableIndex++) - { - // Find time index - size_t dateColumnIndex = m_parser->tables()[tableIndex].size(); - size_t dayOrYearColumnIndex = m_parser->tables()[tableIndex].size(); - - for (size_t columIndex = 0; columIndex < m_parser->tables()[tableIndex].size(); columIndex++) - { - const ColumnInfo& ci = m_parser->tables()[tableIndex][columIndex]; - if (!ci.isAVector) - { - QString unit = QString::fromStdString(ci.unitName).trimmed().toUpper(); - if (unit == "DATE" || - unit == "DATES") - { - dateColumnIndex = columIndex; - } - else if (unit == "DAY" || - unit == "DAYS" || - unit == "YEAR" || - unit == "YEARS") - { - dayOrYearColumnIndex = columIndex; - break; - } - } - } - - size_t timeColumnIndex = m_parser->tables()[tableIndex].size(); - if (dayOrYearColumnIndex < m_parser->tables()[tableIndex].size()) - { - timeColumnIndex = dayOrYearColumnIndex; - } - else if (dateColumnIndex < m_parser->tables()[tableIndex].size()) - { - timeColumnIndex = dateColumnIndex; - } - - if (timeColumnIndex == m_parser->tables()[tableIndex].size()) - { - RiaLogging::warning(QString("Failed to find time data for table %1 in file %2").arg(tableIndex)); - RiaLogging::warning(QString("No data for this table is imported")); - } - else - { - m_timeSteps.resize(m_timeSteps.size() + 1); - std::vector& timeSteps = m_timeSteps.back(); - - const ColumnInfo& ci = m_parser->tables()[tableIndex][timeColumnIndex]; - - if (timeColumnIndex == dateColumnIndex) - { - for (const auto& timeStepValue : ci.observationDateTimes) - { - timeSteps.push_back(timeStepValue.toTime_t()); - } - } - else - { - QDateTime startDate; - if (ci.startQDateTime.isValid()) - { - startDate = ci.startQDateTime; - } - else - { - QString startDateString = QString::fromStdString(ci.startDateString); - if (!startDateString.isEmpty()) - { - QString dateFormatString = "ddMMyyyy"; - - startDate = RiaQDateTimeTools::fromString(startDateString, dateFormatString); - } - else - { - startDate = RiaQDateTimeTools::epoch(); - } - } - - QString unit = QString::fromStdString(ci.unitName).trimmed().toUpper(); - - if (unit == "DAY" || unit == "DAYS") - { - for (const auto& timeStepValue : ci.values) - { - QDateTime dateTime = RiaQDateTimeTools::addDays(startDate, timeStepValue); - timeSteps.push_back(dateTime.toTime_t()); - } - } - else if (unit == "YEAR" || unit == "YEARS") - { - for (const auto& timeStepValue : ci.values) - { - QDateTime dateTime = RiaQDateTimeTools::addYears(startDate, timeStepValue); - timeSteps.push_back(dateTime.toTime_t()); - } - } - } - - for (size_t columIndex = 0; columIndex < m_parser->tables()[tableIndex].size(); columIndex++) - { - const ColumnInfo& ci = m_parser->tables()[tableIndex][columIndex]; - if (ci.isAVector) - { - RifEclipseSummaryAddress sumAddress = ci.summaryAddress; - - if (customWellName.size() > 0) - { - sumAddress.setWellName(customWellName.toStdString()); - } - - if (customWellGroupName.size() > 0) - { - sumAddress.setWellGroupName(customWellGroupName.toStdString()); - } - - m_allResultAddresses.push_back(sumAddress); - - m_mapFromAddressToTimeStepIndex[sumAddress] = m_timeSteps.size() - 1; - m_mapFromAddressToResultIndex[sumAddress] = std::make_pair(tableIndex, columIndex); - } - } - } - } + buildTimeStepsFromTables(); return true; @@ -203,8 +82,10 @@ bool RifColumnBasedUserData::values(const RifEclipseSummaryAddress& resultAddres { std::pair tableColIndices = search->second; - const ColumnInfo& ci = m_parser->tables()[tableColIndices.first][tableColIndices.second]; - for (const auto& v : ci.values) + const ColumnInfo* ci = m_parser->columnInfo(tableColIndices.first, tableColIndices.second); + if (!ci) return false; + + for (const auto& v : ci->values) { values->push_back(v); } @@ -239,10 +120,119 @@ std::string RifColumnBasedUserData::unitName(const RifEclipseSummaryAddress& res { std::pair tableColIndices = search->second; - const ColumnInfo& ci = m_parser->tables()[tableColIndices.first][tableColIndices.second]; - - return ci.unitName; + const ColumnInfo* ci = m_parser->columnInfo(tableColIndices.first, tableColIndices.second); + if (ci) + { + return ci->unitName; + } } return ""; } + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RifColumnBasedUserData::buildTimeStepsFromTables() +{ + for (size_t tableIndex = 0; tableIndex < m_parser->tableData().size(); tableIndex++) + { + auto tableData = m_parser->tableData()[tableIndex]; + + // Find time index + size_t dateColumnIndex = tableData.columnInfos().size(); + size_t dayOrYearColumnIndex = tableData.columnInfos().size(); + + for (size_t columIndex = 0; columIndex < tableData.columnInfos().size(); columIndex++) + { + const ColumnInfo& ci = tableData.columnInfos()[columIndex]; + if (dateColumnIndex == tableData.columnInfos().size() && + RifEclipseUserDataKeywordTools::isDate(ci.summaryAddress.quantityName())) + { + dateColumnIndex = columIndex; + } + else if (dayOrYearColumnIndex == tableData.columnInfos().size() && + RifEclipseUserDataParserTools::hasTimeUnit(ci.unitName)) + { + dayOrYearColumnIndex = columIndex; + } + } + + if (dateColumnIndex == tableData.columnInfos().size() && + dayOrYearColumnIndex == tableData.columnInfos().size()) + { + RiaLogging::warning(QString("Failed to find time data for table %1 in file %2").arg(tableIndex)); + RiaLogging::warning(QString("No data for this table is imported")); + } + else + { + m_timeSteps.resize(m_timeSteps.size() + 1); + std::vector& timeSteps = m_timeSteps.back(); + + if (dateColumnIndex != tableData.columnInfos().size()) + { + const ColumnInfo& ci = tableData.columnInfos()[dateColumnIndex]; + + QString dateFormat; + for (auto s : ci.stringValues) + { + QDateTime dt = RiaDateStringParser::parseDateString(s); + + timeSteps.push_back(dt.toTime_t()); + } + } + else + { + QDateTime startDate = RiaQDateTimeTools::epoch(); + + if (!tableData.startDate().empty()) + { + QDateTime candidate = RiaDateStringParser::parseDateString(tableData.startDate()); + if (candidate.isValid()) + { + startDate = candidate; + } + } + + if (dayOrYearColumnIndex != tableData.columnInfos().size()) + { + const ColumnInfo& ci = tableData.columnInfos()[dayOrYearColumnIndex]; + + QString unit = QString::fromStdString(ci.unitName).trimmed().toUpper(); + + if (unit == "DAY" || unit == "DAYS") + { + for (const auto& timeStepValue : ci.values) + { + QDateTime dateTime = RiaQDateTimeTools::addDays(startDate, timeStepValue); + timeSteps.push_back(dateTime.toTime_t()); + } + } + else if (unit == "YEAR" || unit == "YEARS") + { + for (const auto& timeStepValue : ci.values) + { + QDateTime dateTime = RiaQDateTimeTools::addYears(startDate, timeStepValue); + timeSteps.push_back(dateTime.toTime_t()); + } + } + + } + } + + for (size_t columIndex = 0; columIndex < tableData.columnInfos().size(); columIndex++) + { + const ColumnInfo& ci = tableData.columnInfos()[columIndex]; + if (!ci.isStringData) + { + RifEclipseSummaryAddress sumAddress = ci.summaryAddress; + + m_allResultAddresses.push_back(sumAddress); + + m_mapFromAddressToTimeStepIndex[sumAddress] = m_timeSteps.size() - 1; + m_mapFromAddressToResultIndex[sumAddress] = std::make_pair(tableIndex, columIndex); + } + } + } + } +} diff --git a/ApplicationCode/FileInterface/RifColumnBasedUserData.h b/ApplicationCode/FileInterface/RifColumnBasedUserData.h index f9308a227e..95f019a458 100644 --- a/ApplicationCode/FileInterface/RifColumnBasedUserData.h +++ b/ApplicationCode/FileInterface/RifColumnBasedUserData.h @@ -49,6 +49,9 @@ public: std::string unitName(const RifEclipseSummaryAddress& resultAddress) const override; +private: + void buildTimeStepsFromTables(); + private: std::unique_ptr m_parser; std::vector< std::vector > m_timeSteps; diff --git a/ApplicationCode/FileInterface/RifColumnBasedUserDataParser.cpp b/ApplicationCode/FileInterface/RifColumnBasedUserDataParser.cpp index e32a2ae65b..ea90606cdf 100644 --- a/ApplicationCode/FileInterface/RifColumnBasedUserDataParser.cpp +++ b/ApplicationCode/FileInterface/RifColumnBasedUserDataParser.cpp @@ -35,85 +35,71 @@ //-------------------------------------------------------------------------------------------------- RifColumnBasedUserDataParser::RifColumnBasedUserDataParser(const QString& data) { - parseData(data); + parseTableData(data); } + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -const std::vector< std::vector >& RifColumnBasedUserDataParser::tables() const +const std::vector& RifColumnBasedUserDataParser::tableData() const { - return m_tables; + return m_tableDatas; +} + + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +const ColumnInfo* RifColumnBasedUserDataParser::columnInfo(size_t tableIndex, size_t columnIndex) const +{ + if (tableIndex >= m_tableDatas.size()) return nullptr; + + if (columnIndex >= m_tableDatas[tableIndex].columnInfos().size()) return nullptr; + + return &(m_tableDatas[tableIndex].columnInfos()[columnIndex]); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -void RifColumnBasedUserDataParser::parseData(const QString& data) +void RifColumnBasedUserDataParser::parseTableData(const QString& data) { std::stringstream streamData; streamData.str(data.toStdString()); - do + do { - std::vector table = RifEclipseUserDataParserTools::columnInfoForTable(streamData); - size_t columnCount = table.size(); + auto table = RifEclipseUserDataParserTools::tableDataFromText(streamData); + std::vector& columnInfos = table.columnInfos(); + int columnCount = static_cast(columnInfos.size()); if (columnCount == 0) break; - + std::string line; std::getline(streamData, line); - size_t dateColumnIndex = table.size(); - for (size_t i = 0; i < columnCount; i++) - { - if (table[i].summaryAddress.quantityName() == "DATE" || - table[i].summaryAddress.quantityName() == "DATES") - { - dateColumnIndex = i; - } - } - - // If a DATE column is present, use the first date as the start date of the samples - // This date is then used as basis for times defined by days or years given as double values - QDateTime startDate; - - std::vector values; - QString qLine; do { - qLine = QString::fromStdString(line); + QString qLine = QString::fromStdString(line); QStringList entries = qLine.split(" ", QString::SkipEmptyParts); - if (entries.size() < static_cast(columnCount)) break; + if (entries.size() < columnCount) break; - for (size_t i = 0; i < columnCount; i++) + for (int i = 0; i < columnCount; i++) { - if (dateColumnIndex < columnCount) + if (columnInfos[i].isStringData) { - QDateTime observationDate = RiaDateStringParser::parseDateString(entries[static_cast(dateColumnIndex)]); - - if (observationDate.isValid() && !startDate.isValid()) - { - startDate = observationDate; - } - - table[i].observationDateTimes.push_back(observationDate); + columnInfos[i].stringValues.push_back(entries[i].toStdString()); + } + else + { + double entry = entries[i].toDouble(); + columnInfos[i].values.push_back(entry); } - - double entry = entries.at(static_cast(i)).toDouble(); - table[i].values.push_back(entry); } } while (std::getline(streamData, line)); - if (startDate.isValid()) - { - for (auto& ci : table) - { - ci.startQDateTime = startDate; - } - } - - m_tables.push_back(table); + m_tableDatas.push_back(table); } while (streamData.good()); } diff --git a/ApplicationCode/FileInterface/RifColumnBasedUserDataParser.h b/ApplicationCode/FileInterface/RifColumnBasedUserDataParser.h index 624bf7909c..789a92e790 100644 --- a/ApplicationCode/FileInterface/RifColumnBasedUserDataParser.h +++ b/ApplicationCode/FileInterface/RifColumnBasedUserDataParser.h @@ -24,7 +24,8 @@ #include -struct ColumnInfo; +class ColumnInfo; +class TableData; //================================================================================================== /// @@ -33,11 +34,13 @@ class RifColumnBasedUserDataParser { public: RifColumnBasedUserDataParser(const QString& data); - const std::vector< std::vector >& tables() const; + const std::vector& tableData() const; + + const ColumnInfo* columnInfo(size_t tableIndex, size_t columnIndex) const; private: - void parseData(const QString& data); + void parseTableData(const QString& data); private: - std::vector< std::vector > m_tables; + std::vector m_tableDatas; }; diff --git a/ApplicationCode/FileInterface/RifEclipseSummaryAddress.cpp b/ApplicationCode/FileInterface/RifEclipseSummaryAddress.cpp index a3c28370f3..f11402aece 100644 --- a/ApplicationCode/FileInterface/RifEclipseSummaryAddress.cpp +++ b/ApplicationCode/FileInterface/RifEclipseSummaryAddress.cpp @@ -115,6 +115,18 @@ RifEclipseSummaryAddress RifEclipseSummaryAddress::calculatedCurveAddress(const return fieldAddr; } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RifEclipseSummaryAddress RifEclipseSummaryAddress::miscAddress(const std::string& quantityName) +{ + RifEclipseSummaryAddress fieldAddr; + fieldAddr.m_variableCategory = SUMMARY_MISC; + fieldAddr.m_quantityName = quantityName; + + return fieldAddr; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationCode/FileInterface/RifEclipseSummaryAddress.h b/ApplicationCode/FileInterface/RifEclipseSummaryAddress.h index e2f23663ff..b0f1a076bd 100644 --- a/ApplicationCode/FileInterface/RifEclipseSummaryAddress.h +++ b/ApplicationCode/FileInterface/RifEclipseSummaryAddress.h @@ -106,6 +106,7 @@ public: static RifEclipseSummaryAddress fieldVarAddress(const std::string& fieldVarName); static RifEclipseSummaryAddress calculatedCurveAddress(const std::string& curveName); + static RifEclipseSummaryAddress miscAddress(const std::string& quantityName); // Access methods diff --git a/ApplicationCode/FileInterface/RifEclipseUserDataKeywordTools.cpp b/ApplicationCode/FileInterface/RifEclipseUserDataKeywordTools.cpp new file mode 100644 index 0000000000..36a0300d23 --- /dev/null +++ b/ApplicationCode/FileInterface/RifEclipseUserDataKeywordTools.cpp @@ -0,0 +1,248 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2017- Statoil ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RifEclipseUserDataKeywordTools.h" + +#include "RiaLogging.h" + +#include "RifEclipseUserDataParserTools.h" + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RifEclipseUserDataKeywordTools::requiredItemsPerLineForKeyword(const std::string& identifier) +{ + if (identifier.size() < 2) return {}; + + char firstLetter = identifier[0]; + switch (firstLetter) + { + case 'B': return { 3 }; // Block triplet + case 'C': return { 1, 3 }; // Well Name and completion triplet + case 'G': return { 1 }; // Group + case 'R': return { 1 }; // Region number + case 'S': return { 1, 1 }; // Well name and segment number + case 'W': return { 1 }; // Well Name + } + + std::string firstTwoLetters = identifier.substr(0, 2); + + if (firstTwoLetters == "LB") return { 1, 3 }; // LGR name and block triplet + else if (firstTwoLetters == "LC") return { 1, 1, 3 }; // LGR name, well name and block triplet + else if (firstTwoLetters == "LW") return { 1, 1 }; // LGR name and well name + + return { }; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector> RifEclipseUserDataKeywordTools::buildColumnHeaderText(const std::vector& quantityNames, + const std::vector>& restOfHeaderRows, + std::vector* errorText) +{ + std::vector> tableHeaderText; + + std::vector headerLineWordIndices(restOfHeaderRows.size(), 0); + + for (size_t i = 0; i < quantityNames.size(); i++) + { + std::vector columnHeaderText; + + auto quantityName = quantityNames[i]; + columnHeaderText.push_back(quantityName); + + auto itemCountPerLine = RifEclipseUserDataKeywordTools::requiredItemsPerLineForKeyword(quantityName); + if (itemCountPerLine.size() > restOfHeaderRows.size()) + { + std::string text = "Detected too few header lines"; + if (errorText) errorText->push_back(text); + + return std::vector>(); + } + + for (size_t lineIdx = 0; lineIdx < itemCountPerLine.size(); lineIdx++) + { + auto line = restOfHeaderRows[lineIdx]; + + for (size_t itemIndex = 0; itemIndex < itemCountPerLine[lineIdx]; itemIndex++) + { + size_t wordIndex = headerLineWordIndices[lineIdx]; + if (wordIndex >= line.size()) + { + std::string text = "Detected too few items for header line"; + if (errorText) errorText->push_back(text); + + return std::vector>(); + } + + auto word = line[wordIndex]; + + columnHeaderText.push_back(word); + + headerLineWordIndices[lineIdx]++; + } + } + + tableHeaderText.push_back(columnHeaderText); + } + + return tableHeaderText; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RifEclipseUserDataKeywordTools::isDate(const std::string& identifier) +{ + if (identifier.find("DATE") != std::string::npos) return true; + + return false; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RifEclipseSummaryAddress RifEclipseUserDataKeywordTools::makeAndFillAddress(const std::string quantityName, const std::vector& columnHeaderText) +{ + RifEclipseSummaryAddress::SummaryVarCategory category = RifEclipseUserDataParserTools::identifyCategory(quantityName); + + if (category == RifEclipseSummaryAddress::SUMMARY_INVALID) + { + return RifEclipseSummaryAddress::miscAddress(quantityName); + } + + int regionNumber = -1; + int regionNumber2 = -1; + std::string wellGroupName = ""; + std::string wellName = ""; + int wellSegmentNumber = -1; + std::string lgrName = ""; + int cellI = -1; + int cellJ = -1; + int cellK = -1; + + switch (category) + { + case RifEclipseSummaryAddress::SUMMARY_FIELD: + break; + case RifEclipseSummaryAddress::SUMMARY_AQUIFER: + break; + case RifEclipseSummaryAddress::SUMMARY_NETWORK: + break; + case RifEclipseSummaryAddress::SUMMARY_MISC: + break; + case RifEclipseSummaryAddress::SUMMARY_REGION: + { + if (columnHeaderText.size() > 0) + { + regionNumber = std::stoi(columnHeaderText[0]); + } + break; + } + case RifEclipseSummaryAddress::SUMMARY_REGION_2_REGION: + break; + case RifEclipseSummaryAddress::SUMMARY_WELL_GROUP: + { + if (columnHeaderText.size() > 0) + { + wellGroupName = columnHeaderText[0]; + } + break; + } + case RifEclipseSummaryAddress::SUMMARY_WELL: + { + if (columnHeaderText.size() > 0) + { + wellName = columnHeaderText[0]; + } + break; + } + case RifEclipseSummaryAddress::SUMMARY_WELL_COMPLETION: + { + if (columnHeaderText.size() > 3) + { + wellName = columnHeaderText[0]; + cellI = std::stoi(columnHeaderText[1]); + cellJ = std::stoi(columnHeaderText[2]); + cellK = std::stoi(columnHeaderText[3]); + } + break; + } + break; + case RifEclipseSummaryAddress::SUMMARY_WELL_LGR: + if (columnHeaderText.size() > 1) + { + wellName = columnHeaderText[0]; + lgrName = columnHeaderText[1]; + } + break; + case RifEclipseSummaryAddress::SUMMARY_WELL_COMPLETION_LGR: + if (columnHeaderText.size() > 4) + { + wellName = columnHeaderText[0]; + lgrName = columnHeaderText[1]; + cellI = std::stoi(columnHeaderText[2]); + cellJ = std::stoi(columnHeaderText[3]); + cellK = std::stoi(columnHeaderText[4]); + } + break; + case RifEclipseSummaryAddress::SUMMARY_WELL_SEGMENT: + if (columnHeaderText.size() > 1) + { + wellName = columnHeaderText[0]; + wellSegmentNumber = std::stoi(columnHeaderText[1]); + } + break; + case RifEclipseSummaryAddress::SUMMARY_BLOCK: + if (columnHeaderText.size() > 2) + { + cellI = std::stoi(columnHeaderText[0]); + cellJ = std::stoi(columnHeaderText[1]); + cellK = std::stoi(columnHeaderText[2]); + } + break; + case RifEclipseSummaryAddress::SUMMARY_BLOCK_LGR: + if (columnHeaderText.size() > 3) + { + lgrName = columnHeaderText[0]; + cellI = std::stoi(columnHeaderText[1]); + cellJ = std::stoi(columnHeaderText[2]); + cellK = std::stoi(columnHeaderText[3]); + } + break; + case RifEclipseSummaryAddress::SUMMARY_CALCULATED: + break; + default: + break; + } + + return RifEclipseSummaryAddress(category, + quantityName, + regionNumber, + regionNumber2, + wellGroupName, + wellName, + wellSegmentNumber, + lgrName, + cellI, + cellJ, + cellK); + +} + diff --git a/ApplicationCode/FileInterface/RifEclipseUserDataKeywordTools.h b/ApplicationCode/FileInterface/RifEclipseUserDataKeywordTools.h new file mode 100644 index 0000000000..86375eb98c --- /dev/null +++ b/ApplicationCode/FileInterface/RifEclipseUserDataKeywordTools.h @@ -0,0 +1,42 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2017- Statoil ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "RifEclipseUserDataParserTools.h" + +#include +#include + +//================================================================================================== +/// +//================================================================================================== +class RifEclipseUserDataKeywordTools +{ +public: + static std::vector requiredItemsPerLineForKeyword(const std::string& identifier); + + static std::vector> buildColumnHeaderText(const std::vector& quantityNames, + const std::vector>& restOfHeaderRows, + std::vector* errorText = nullptr); + + static bool isDate(const std::string& identifier); + + static RifEclipseSummaryAddress makeAndFillAddress(const std::string quantityName, const std::vector& columnHeaderText); +}; + diff --git a/ApplicationCode/FileInterface/RifEclipseUserDataParserTools.cpp b/ApplicationCode/FileInterface/RifEclipseUserDataParserTools.cpp index 7a24be7ab7..c14317ed6b 100644 --- a/ApplicationCode/FileInterface/RifEclipseUserDataParserTools.cpp +++ b/ApplicationCode/FileInterface/RifEclipseUserDataParserTools.cpp @@ -18,8 +18,11 @@ #include "RifEclipseUserDataParserTools.h" +#include "RiaDateStringParser.h" #include "RiaLogging.h" +#include "RifEclipseUserDataKeywordTools.h" + #include "cvfAssert.h" #include @@ -80,7 +83,7 @@ bool RifEclipseUserDataParserTools::isLineSkippable(const std::string& line) //-------------------------------------------------------------------------------------------------- bool RifEclipseUserDataParserTools::isAComment(const std::string& word) { - if (word.size() > 1 && word.substr(0, 2) == "--") + if (word.find("--") != std::string::npos) { return true; } @@ -161,77 +164,6 @@ size_t RifEclipseUserDataParserTools::findFirstNonEmptyEntryIndex(std::vector headerColumn) -{ - int regionNumber = -1; - int regionNumber2 = -1; - std::string wellGroupName = ""; - std::string wellName = ""; - int wellSegmentNumber = -1; - std::string lgrName = ""; - int cellI = -1; - int cellJ = -1; - int cellK = -1; - - RifEclipseSummaryAddress::SummaryVarCategory category = identifyCategory(quantityName); - - switch (category) //TODO: More categories - { - case (RifEclipseSummaryAddress::SUMMARY_INVALID): - { - break; - } - case (RifEclipseSummaryAddress::SUMMARY_WELL): - { - size_t index = findFirstNonEmptyEntryIndex(headerColumn); - if (index < headerColumn.size()) - { - wellName = headerColumn[index]; - } - break; - } - case (RifEclipseSummaryAddress::SUMMARY_WELL_GROUP): - { - size_t index = findFirstNonEmptyEntryIndex(headerColumn); - if (index < headerColumn.size()) - { - wellGroupName = headerColumn[index]; - } - break; - } - case (RifEclipseSummaryAddress::SUMMARY_REGION): - { - size_t index = findFirstNonEmptyEntryIndex(headerColumn); - if (index < headerColumn.size()) - { - try - { - regionNumber = std::stoi(headerColumn[index]); - } - catch (...){} - } - break; - } - default: - break; - } - - return RifEclipseSummaryAddress(category, - quantityName, - regionNumber, - regionNumber2, - wellGroupName, - wellName, - wellSegmentNumber, - lgrName, - cellI, - cellJ, - cellK); -} - //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -248,7 +180,19 @@ bool RifEclipseUserDataParserTools::keywordParser(const std::string& line, std:: else if (words[0] == "STARTDATE") { words.erase(words.begin()); - startDate = std::accumulate(words.begin(), words.end(), std::string("")); + + for (size_t i = 0; i < words.size(); i++) + { + std::string s = words[i]; + + startDate += s; + + if (i < words.size() - 1) + { + startDate += " "; + } + } + return true; } else if (words[0] == "DATEFORMAT") @@ -259,162 +203,6 @@ bool RifEclipseUserDataParserTools::keywordParser(const std::string& line, std:: return false; } -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -std::vector RifEclipseUserDataParserTools::columnInfoForTable(std::stringstream& streamData) -{ - std::vector table; - - std::string origin = ""; - std::string dateFormat = ""; - std::string startDate = ""; - - std::string line; - std::getline(streamData, line); - - while (isLineSkippable(line) || keywordParser(line, origin, dateFormat, startDate)) - { - if (!streamData.good()) return table; - - std::getline(streamData, line); - } - - std::vector quantityNames = splitLineAndRemoveComments(line); - size_t columnCount = quantityNames.size(); - - std::vector< std::vector< std::string > > allHeaderRows; - - { - std::stringstream::pos_type posAtStartOfLine = streamData.tellg(); - - std::string secondLine; - std::getline(streamData, line); - - std::stringstream::pos_type posAtStartOfSecondLine = streamData.tellg(); - std::getline(streamData, secondLine); - - bool header = true; - while (header) - { - std::vector words = splitLineAndRemoveComments(line); - std::vector wordsSecondLine = splitLineAndRemoveComments(secondLine); - - if (words.size() == columnCount && - wordsSecondLine.size() == columnCount && - hasOnlyValidDoubleValues(words) && - hasOnlyValidDoubleValues(wordsSecondLine)) - { - header = false; - break; - } - else - { - if (words.size() > columnCount) break; - - size_t diff = columnCount - words.size(); - - if (diff == columnCount) - { - std::vector< std::string > vectorOfEmptyStrings(columnCount, ""); - allHeaderRows.push_back(vectorOfEmptyStrings); - } - else - { - words.insert(words.begin(), diff, ""); - allHeaderRows.push_back(words); - } - } - - posAtStartOfLine = posAtStartOfSecondLine; - line = secondLine; - - posAtStartOfSecondLine = streamData.tellg(); - std::getline(streamData, secondLine); - } - - streamData.seekg(posAtStartOfLine); - } - - std::vector unitNames; - std::vector scaleFactors; - std::vector< std::vector< std::string > > restOfHeaderRows; - - for (const auto& wordsForRow : allHeaderRows) - { - bool excludeFromHeader = false; - if (unitNames.size() == 0) - { - for (const std::string& word : wordsForRow) - { - if (hasTimeUnit(word)) - { - unitNames = wordsForRow; - excludeFromHeader = true; - } - } - } - - if (scaleFactors.size() == 0) - { - std::vector values; - - if (hasOnlyValidDoubleValues(wordsForRow, &values)) - { - scaleFactors = values; - excludeFromHeader = true; - } - } - - if (!excludeFromHeader) - { - restOfHeaderRows.push_back(wordsForRow); - } - } - - for (const std::string& unit : unitNames) - { - ColumnInfo columnInfo; - columnInfo.unitName = unit; - columnInfo.origin = origin; - columnInfo.dateFormatString = dateFormat; - columnInfo.startDateString = startDate; - table.push_back(columnInfo); - } - - for (size_t i = 0; i < table.size(); i++) - { - if (scaleFactors.size() == table.size()) - { - table[i].scaleFactor = scaleFactors[i]; - } - else - { - table[i].scaleFactor = 1.0; - } - } - - for (size_t i = 0; i < table.size(); i++) - { - std::vector< std::string > restOfHeaderColumn; - for (std::vector< std::string > restOfHeaderRow : restOfHeaderRows) - { - restOfHeaderColumn.push_back(restOfHeaderRow.at(i)); - } - table[i].summaryAddress = makeAndFillAddress(quantityNames.at(i), restOfHeaderColumn); - } - - for (ColumnInfo& column : table) - { - if (column.summaryAddress.category() != RifEclipseSummaryAddress::SUMMARY_INVALID) - { - column.isAVector = true; - } - } - - return table; -} - //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -486,19 +274,234 @@ bool RifEclipseUserDataParserTools::hasOnlyValidDoubleValues(const std::vectorpush_back(doubleVal); } } - return true; + return onlyValidValues; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RifEclipseUserDataParserTools::hasDateUnit(const std::string& word) +{ + if (word.find("DATE") != std::string::npos) return true; + + return false; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RifEclipseUserDataParserTools::isValidTableData(size_t columnCount, const std::string& line) +{ + std::vector words = splitLineAndRemoveComments(line); + + if (words.size() != columnCount) return false; + + std::vector doubleValues; + RifEclipseUserDataParserTools::hasOnlyValidDoubleValues(words, &doubleValues); + if (doubleValues.size() == columnCount) return true; + + size_t columnsWithDate = 0; + for (auto w : words) + { + if (RiaDateStringParser::parseDateString(w).isValid()) + { + columnsWithDate++; + } + } + + if (columnsWithDate == 1 && doubleValues.size() == columnCount - 1) + { + return true; + } + + return false; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TableData RifEclipseUserDataParserTools::tableDataFromText(std::stringstream& streamData, std::vector* errorText) +{ + TableData emptyTable; + + std::string origin = ""; + std::string dateFormat = ""; + std::string startDate = ""; + + std::string firstLine; + std::getline(streamData, firstLine); + + while (isLineSkippable(firstLine) || keywordParser(firstLine, origin, dateFormat, startDate)) + { + if (!streamData.good()) + { + if (errorText) errorText->push_back("Failed to detect start of table header"); + + return emptyTable; + } + + std::getline(streamData, firstLine); + } + + std::vector quantityNames = splitLineAndRemoveComments(firstLine); + size_t columnCount = quantityNames.size(); + + if (columnCount == 0) + { + if (errorText) errorText->push_back("No quantities detected in table"); + + return emptyTable; + } + + std::vector< std::vector< std::string > > allHeaderRows; + + { + std::stringstream::pos_type posAtStartOfFirstLine = streamData.tellg(); + + std::string secondLine; + std::getline(streamData, firstLine); + + std::stringstream::pos_type posAtStartOfSecondLine = streamData.tellg(); + std::getline(streamData, secondLine); + + bool header = true; + while (header) + { + if (isValidTableData(columnCount, firstLine) && + isValidTableData(columnCount, secondLine)) + { + header = false; + break; + } + else + { + std::vector words = splitLineAndRemoveComments(firstLine); + if (words.size() > 0) + { + allHeaderRows.push_back(words); + } + } + + posAtStartOfFirstLine = posAtStartOfSecondLine; + firstLine = secondLine; + + posAtStartOfSecondLine = streamData.tellg(); + std::getline(streamData, secondLine); + + if (!streamData.good()) + { + header = false; + } + } + + streamData.seekg(posAtStartOfFirstLine); + } + + std::vector unitNames; + std::vector scaleFactors; + std::vector< std::vector< std::string > > headerRows; + + for (const auto& rowWords : allHeaderRows) + { + bool excludeFromHeader = false; + + if (rowWords.size() == columnCount) + { + if (unitNames.size() == 0) + { + for (const std::string& word : rowWords) + { + if (hasTimeUnit(word)) + { + unitNames = rowWords; + excludeFromHeader = true; + } + } + } + + if (scaleFactors.size() == 0) + { + std::vector values; + + if (hasOnlyValidDoubleValues(rowWords, &values)) + { + scaleFactors = values; + excludeFromHeader = true; + } + } + } + + if (!excludeFromHeader) + { + headerRows.push_back(rowWords); + } + } + + if (columnCount != unitNames.size()) + { + if (errorText) errorText->push_back("Number of quantities is different from number of units"); + + return emptyTable; + } + + + std::vector columnInfos; + + // Create string vectors for each column + { + std::vector parserErrors; + std::vector> tableHeaderText = RifEclipseUserDataKeywordTools::buildColumnHeaderText(quantityNames, headerRows, &parserErrors); + if (parserErrors.size() > 0) + { + if (errorText) errorText->insert(errorText->end(), parserErrors.begin(), parserErrors.end()); + + return emptyTable; + } + + + // For each column header, create rif adress and date time + for (size_t i = 0; i < tableHeaderText.size(); i++) + { + auto columnText = tableHeaderText[i]; + if (columnText.size() == 0) + { + if (errorText) errorText->push_back("Detected column with no content"); + continue; + } + + std::string quantity = columnText[0]; + std::string unit = unitNames[i]; + + std::vector columnHeader; + + if (columnText.size() > 1) columnHeader.insert(columnHeader.begin(), columnText.begin() + 1, columnText.end()); + + RifEclipseSummaryAddress adr = RifEclipseUserDataKeywordTools::makeAndFillAddress(quantity, columnHeader); + + ColumnInfo ci(adr, unit); + if (quantity == "DATE") + { + ci.isStringData = true; + } + + columnInfos.push_back(ci); + } + } + + return TableData(origin, dateFormat, startDate, columnInfos); } diff --git a/ApplicationCode/FileInterface/RifEclipseUserDataParserTools.h b/ApplicationCode/FileInterface/RifEclipseUserDataParserTools.h index 9d8a3221f0..5b8293fe38 100644 --- a/ApplicationCode/FileInterface/RifEclipseUserDataParserTools.h +++ b/ApplicationCode/FileInterface/RifEclipseUserDataParserTools.h @@ -28,18 +28,86 @@ #include #include -struct ColumnInfo +//================================================================================================== +/// +//================================================================================================== +class ColumnInfo { - bool isAVector = false; +public: + ColumnInfo() + : scaleFactor(1.0), + isStringData(false) + { + } + + ColumnInfo(const RifEclipseSummaryAddress& adr, const std::string& unit) + : summaryAddress(adr), + scaleFactor(1.0), + unitName(unit), + isStringData(false) + { + } + +public: RifEclipseSummaryAddress summaryAddress; std::string unitName; double scaleFactor; std::vector values; - std::string origin; - std::string dateFormatString; - std::string startDateString; - QDateTime startQDateTime; - std::vector observationDateTimes; + bool isStringData; + std::vector stringValues; +}; + +//================================================================================================== +/// +//================================================================================================== +class TableData +{ +public: + TableData() + {} + + TableData(const std::string& origin, + const std::string& dateFormat, + const std::string& startDate, + const std::vector& columnInfos) + : m_origin(origin), + m_dateFormat(dateFormat), + m_startDate(startDate), + m_columnInfos(columnInfos) + { + } + + std::string origin() const + { + return m_origin; + } + + std::string startDate() const + { + return m_startDate; + } + + std::string dateFormat() const + { + return m_dateFormat; + } + + std::vector& columnInfos() + { + return m_columnInfos; + } + + const std::vector& columnInfos() const + { + return m_columnInfos; + } + +private: + std::string m_origin; + std::string m_dateFormat; + std::string m_startDate; + + std::vector m_columnInfos; }; //================================================================================================== @@ -54,12 +122,15 @@ public: static RifEclipseSummaryAddress::SummaryVarCategory identifyCategory(const std::string& word); static void splitLineToDoubles(const std::string& line, std::vector& values); static size_t findFirstNonEmptyEntryIndex(std::vector& list); - static RifEclipseSummaryAddress makeAndFillAddress(std::string quantityName, std::vector< std::string > headerColumn); static bool keywordParser(const std::string& line, std::string& origin, std::string& dateFormat, std::string& startDate); - static std::vector columnInfoForTable(std::stringstream& data); static bool isANumber(const std::string& line); static std::vector headerReader(std::stringstream& streamData, std::string& line); static bool hasTimeUnit(const std::string& line); static bool hasOnlyValidDoubleValues(const std::vector& words, std::vector* doubleValues = nullptr); + + static bool hasDateUnit(const std::string& word); + static bool isValidTableData(size_t columnCount, const std::string& line); + + static TableData tableDataFromText(std::stringstream& data, std::vector* errorText = nullptr); }; diff --git a/ApplicationCode/UnitTests/CMakeLists_files.cmake b/ApplicationCode/UnitTests/CMakeLists_files.cmake index 13c98869e1..3f783c73a9 100644 --- a/ApplicationCode/UnitTests/CMakeLists_files.cmake +++ b/ApplicationCode/UnitTests/CMakeLists_files.cmake @@ -31,7 +31,8 @@ ${CEE_CURRENT_LIST_DIR}RigHexIntersectionTools-Test.cpp ${CEE_CURRENT_LIST_DIR}ObservedDataParser-Test.cpp ${CEE_CURRENT_LIST_DIR}EclipseRftReader-Test.cpp ${CEE_CURRENT_LIST_DIR}RicExpressionParser-Test.cpp -${CEE_CURRENT_LIST_DIR}RiuSummaryVectorDescriptionMap-Test +${CEE_CURRENT_LIST_DIR}RiuSummaryVectorDescriptionMap-Test.cpp +${CEE_CURRENT_LIST_DIR}RifEclipseUserDataKeywordTools-Test.cpp ) if (RESINSIGHT_ENABLE_PROTOTYPE_FEATURE_FRACTURES) diff --git a/ApplicationCode/UnitTests/ObservedDataParser-Test.cpp b/ApplicationCode/UnitTests/ObservedDataParser-Test.cpp index 490a96d71f..64de3178de 100644 --- a/ApplicationCode/UnitTests/ObservedDataParser-Test.cpp +++ b/ApplicationCode/UnitTests/ObservedDataParser-Test.cpp @@ -8,6 +8,8 @@ #include #include +#include +#include "RifEclipseUserDataKeywordTools.h" //-------------------------------------------------------------------------------------------------- /// @@ -246,7 +248,7 @@ TEST(RifRsmspecParserToolsTest, TestSplitLineToDoubles) //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -TEST(RifColumnBasedRsmspecParserTest, TestKeywordsAndMissingUnitName) +TEST(RifColumnBasedRsmspecParserTest, TestTwoPages) { QString data; @@ -258,7 +260,7 @@ TEST(RifColumnBasedRsmspecParserTest, TestKeywordsAndMissingUnitName) out << "DATEFORMAT DD/MM/YY\n"; out << "\n"; out << "TIME YEARX WGT1 WGT2 WGT4 WR12 WR22 WR42 \n"; - out << "DAYS YEARS kg/Sm3 kg/Sm3 kg/Sm3 kg/Sm3 kg/Sm3 \n"; + out << "DAYS YEARS kg/Sm3 kg/Sm3 kg/Sm3 kg/Sm3 kg/Sm3 kg/Sm3 \n"; out << "1 1 1.00E+03 1.00E+03 1.00E+03 1.00E+03 1.00E+03 1.00E+03 \n"; out << " OP-1 OP-1 OP-1 OP-1 OP-1 OP-1 \n"; out << "\n"; @@ -289,21 +291,18 @@ TEST(RifColumnBasedRsmspecParserTest, TestKeywordsAndMissingUnitName) RifColumnBasedUserDataParser parser = RifColumnBasedUserDataParser(data); - std::vector< std::vector > tables = parser.tables(); + auto tables = parser.tableData(); ASSERT_EQ(2, tables.size()); - EXPECT_EQ("112000", tables[0].at(0).startDateString); - EXPECT_EQ("OP-1_TR", tables[0].at(0).origin); - EXPECT_EQ("DD/MM/YY", tables[0].at(0).dateFormatString); + EXPECT_EQ("1 1 2000", tables[0].startDate()); + EXPECT_EQ("OP-1_TR", tables[0].origin()); + EXPECT_EQ("DD/MM/YY", tables[0].dateFormat()); - EXPECT_EQ("112000", tables[1].at(0).startDateString); - EXPECT_EQ("OP-2_TR", tables[1].at(0).origin); - EXPECT_EQ("DD/MM/YY", tables[1].at(0).dateFormatString); + EXPECT_EQ("1 1 2000", tables[1].startDate()); + EXPECT_EQ("OP-2_TR", tables[1].origin()); + EXPECT_EQ("DD/MM/YY", tables[1].dateFormat()); - // Assume missing units at start of row - EXPECT_EQ("", tables[0].at(0).unitName); - - ASSERT_EQ(8, tables.at(0).size()); - EXPECT_EQ(1.0E-12, tables.at(0).at(4).values[0]); + ASSERT_EQ(8, tables.at(0).columnInfos().size()); + EXPECT_EQ(1.0E-12, tables.at(0).columnInfos().at(4).values[0]); } @@ -358,28 +357,28 @@ TEST(RifColumnBasedRsmspecParserTest, TestTableValues) RifColumnBasedUserDataParser parser = RifColumnBasedUserDataParser(data); - std::vector< std::vector > tables = parser.tables(); + auto tables = parser.tableData(); ASSERT_EQ(tables.size(), 2); - ASSERT_EQ(tables.at(0).size(), 6); - ASSERT_EQ(tables.at(1).size(), 6); + ASSERT_EQ(tables.at(0).columnInfos().size(), 6); + ASSERT_EQ(tables.at(1).columnInfos().size(), 6); - ASSERT_EQ(18, tables.at(0).at(0).values.size()); - ASSERT_EQ(4, tables.at(1).at(0).values.size()); + ASSERT_EQ(18, tables.at(0).columnInfos().at(0).values.size()); + ASSERT_EQ(4, tables.at(1).columnInfos().at(0).values.size()); - EXPECT_TRUE(tables.at(0).at(2).isAVector); - EXPECT_FALSE(tables.at(1).at(0).isAVector); +// EXPECT_TRUE(tables.at(0).columnInfos().at(2).isAVector); +// EXPECT_FALSE(tables.at(1).columnInfos().at(0).isAVector); - EXPECT_EQ(0.0, tables.at(0).at(1).values.at(6)); - EXPECT_EQ(282, tables.at(0).at(3).values.at(6)); + EXPECT_EQ(0.0, tables.at(0).columnInfos().at(1).values.at(6)); + EXPECT_EQ(282, tables.at(0).columnInfos().at(3).values.at(6)); - EXPECT_EQ(3, tables.at(1).at(0).values.at(2)); - EXPECT_EQ(370, tables.at(1).at(3).values.at(3)); + EXPECT_EQ(3, tables.at(1).columnInfos().at(0).values.at(2)); + EXPECT_EQ(370, tables.at(1).columnInfos().at(3).values.at(3)); - EXPECT_EQ("WLVP", tables.at(0).at(1).summaryAddress.quantityName()); - EXPECT_EQ("P-15P", tables.at(0).at(5).summaryAddress.wellName()); - EXPECT_EQ("P-9P", tables.at(1).at(1).summaryAddress.wellName()); - EXPECT_NE("P-9P", tables.at(1).at(0).summaryAddress.wellName()); + EXPECT_EQ("WLVP", tables.at(0).columnInfos().at(1).summaryAddress.quantityName()); + EXPECT_EQ("P-15P", tables.at(0).columnInfos().at(5).summaryAddress.wellName()); + EXPECT_EQ("P-9P", tables.at(1).columnInfos().at(1).summaryAddress.wellName()); + EXPECT_NE("P-9P", tables.at(1).columnInfos().at(0).summaryAddress.wellName()); } //-------------------------------------------------------------------------------------------------- @@ -419,9 +418,10 @@ TEST(RifColumnBasedRsmspecParserTest, TestTableMissingWellNames) RifColumnBasedUserDataParser parser = RifColumnBasedUserDataParser(data); - std::vector< std::vector > tables = parser.tables(); - ASSERT_EQ(tables.size(), 1); - ASSERT_EQ(tables.at(0).size(), 6); + auto tables = parser.tableData(); + + // Missing header line with well name, returning empty table + ASSERT_EQ(tables.size(), 0); } //-------------------------------------------------------------------------------------------------- @@ -437,7 +437,7 @@ TEST(RifColumnBasedRsmspecParserTest, TestTableValuesHeaderWithSpaces) out << " SUMMARY OF RUN NORNE_ATW2013_RFTPLT_V3 EC\n"; out << " -----------------------------------------\n"; out << " DATE YEARS WBHP \n"; - out << " YEARS BARSA \n"; + out << " DATE YEARS BARSA \n"; out << " B-1H \n"; out << " \n"; out << " -----------------------------------------\n"; @@ -470,7 +470,7 @@ TEST(RifColumnBasedRsmspecParserTest, TestTableValuesHeaderWithSpaces) RifColumnBasedUserDataParser parser = RifColumnBasedUserDataParser(data); - std::vector< std::vector > tables = parser.tables(); + auto tables = parser.tableData(); ASSERT_EQ(tables.size(), 1); } @@ -521,7 +521,7 @@ TEST(RifColumnBasedRsmspecParserTest, TestTableDateOnly) RifColumnBasedUserDataParser parser = RifColumnBasedUserDataParser(data); - std::vector< std::vector > tables = parser.tables(); + auto tables = parser.tableData(); ASSERT_EQ(tables.size(), 1); } @@ -673,17 +673,17 @@ TEST(RifKeywordBasedRsmspecParserTest, TestShutins) RifColumnBasedUserDataParser parser = RifColumnBasedUserDataParser(data); - std::vector< std::vector > tables = parser.tables(); + auto tables = parser.tableData(); ASSERT_EQ(1, tables.size()); - ASSERT_EQ(3, tables.at(0).size()); - ASSERT_EQ(18, tables.at(0).at(2).values.size()); + ASSERT_EQ(3, tables[0].columnInfos().size()); + ASSERT_EQ(18, tables.at(0).columnInfos().at(2).values.size()); - EXPECT_EQ(2014.39, tables.at(0).at(1).values[2]); + EXPECT_EQ(2014.39, tables.at(0).columnInfos().at(1).values[2]); - EXPECT_EQ("WBP9L", tables.at(0).at(2).summaryAddress.quantityName()); + EXPECT_EQ("WBP9L", tables.at(0).columnInfos().at(2).summaryAddress.quantityName()); - EXPECT_EQ("OP-1", tables.at(0).at(2).summaryAddress.wellName()); - EXPECT_NE("OP-1", tables.at(0).at(1).summaryAddress.wellName()); + EXPECT_EQ("OP-1", tables.at(0).columnInfos().at(2).summaryAddress.wellName()); + EXPECT_NE("OP-1", tables.at(0).columnInfos().at(1).summaryAddress.wellName()); } @@ -732,13 +732,20 @@ TEST(RifKeywordBasedRsmspecParserTest, TestTimeSteps) std::vector< std::string > headerColumn; headerColumn.push_back("OP-1"); - RifEclipseSummaryAddress address = RifEclipseUserDataParserTools::makeAndFillAddress(quantityName, headerColumn); + RifEclipseSummaryAddress address = RifEclipseUserDataKeywordTools::makeAndFillAddress(quantityName, headerColumn); RifColumnBasedUserData columnBasedUserdata; columnBasedUserdata.parse(data, "", ""); std::vector timeSteps = columnBasedUserdata.timeSteps(address); + for (auto t : timeSteps) + { + QDateTime dt = QDateTime::fromTime_t(t); + + qDebug() << dt; + } + QDateTime startDate = QDateTime::fromString("01012004", "ddMMyyyy"); startDate.setTimeSpec(Qt::UTC); @@ -756,33 +763,35 @@ TEST(RifKeywordBasedRsmspecParserTest, TestTimeSteps) //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -TEST(RifColumnBasedRsmspecParserTest, TestTooManyColumnsInFile) +TEST(RifColumnBasedRsmspecParserTest, IsTableData) { - QString data; - QTextStream out(&data); + { + std::string line(" 6-NOV-1997 0 "); + EXPECT_TRUE(RifEclipseUserDataParserTools::isValidTableData(2, line)); + } - out << "1 \n"; - out << "--------------------------------------- \n"; - out << "SUMMARY OF RUN BHP_THP USER FILE DATA VECTORS \n"; - out << "--------------------------------------- \n"; - out << "TIME WTHPH WBHPH \n"; - out << "DAYS BARSA BARSA \n"; - out << " \n"; - out << " K-6HWG K-6HWG \n"; - out << " 1 0.0 0.0 0.0 \n"; - out << " 2 0.0 0.0 0.0 \n"; - out << " 3 0.0 0.0 0.0 \n"; - out << " 4 0.0 0.0 0.0 \n"; - out << " 5 0.0 0.0 0.0 \n"; - out << " 6 0.0 0.0 0.0 \n"; - out << " 7 0.0 0.0 0.0 \n"; - out << " 8 0.0 0.0 0.0 \n"; - out << " 9 0.0 0.0 0.0 \n"; - out << " 10 0.0 0.0 0.0 \n"; - out << " \n"; + { + std::string line(" DATE BARSA "); + EXPECT_FALSE(RifEclipseUserDataParserTools::isValidTableData(2, line)); + } - RifColumnBasedUserDataParser parser = RifColumnBasedUserDataParser(data); + { + std::string line(" 1.2 0 "); + EXPECT_TRUE(RifEclipseUserDataParserTools::isValidTableData(2, line)); + } - std::vector< std::vector > tables = parser.tables(); - ASSERT_EQ(tables.size(), 1); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST(RifColumnBasedRsmspecParserTest, HasOnlyValidDoubleValues) +{ + { + std::vector doubleValues; + std::string line(" 6-NOV-1997 0 "); + std::vector words = RifEclipseUserDataParserTools::splitLineAndRemoveComments(line); + + EXPECT_FALSE(RifEclipseUserDataParserTools::hasOnlyValidDoubleValues(words, &doubleValues)); + } } diff --git a/ApplicationCode/UnitTests/RifEclipseUserDataKeywordTools-Test.cpp b/ApplicationCode/UnitTests/RifEclipseUserDataKeywordTools-Test.cpp new file mode 100644 index 0000000000..f39192f34f --- /dev/null +++ b/ApplicationCode/UnitTests/RifEclipseUserDataKeywordTools-Test.cpp @@ -0,0 +1,280 @@ +#include "gtest/gtest.h" + +#include "RifEclipseUserDataKeywordTools.h" + + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST(RifEclipseUserDataKeywordToolsTest, TestIdentifierItemsPerLine) +{ + { + std::string s = "AA"; + EXPECT_EQ(0, RifEclipseUserDataKeywordTools::requiredItemsPerLineForKeyword(s).size()); + } + { + std::string s = "BB"; + EXPECT_EQ(3, RifEclipseUserDataKeywordTools::requiredItemsPerLineForKeyword(s)[0]); + } + { + std::string s = "CC"; + EXPECT_EQ(1, RifEclipseUserDataKeywordTools::requiredItemsPerLineForKeyword(s)[0]); + EXPECT_EQ(3, RifEclipseUserDataKeywordTools::requiredItemsPerLineForKeyword(s)[1]); + } + { + std::string s = "FF"; + EXPECT_EQ(0, RifEclipseUserDataKeywordTools::requiredItemsPerLineForKeyword(s).size()); + } + { + std::string s = "GG"; + EXPECT_EQ(1, RifEclipseUserDataKeywordTools::requiredItemsPerLineForKeyword(s)[0]); + } + { + std::string s = "NN"; + EXPECT_EQ(0, RifEclipseUserDataKeywordTools::requiredItemsPerLineForKeyword(s).size()); + } + { + std::string s = "RR"; + EXPECT_EQ(1, RifEclipseUserDataKeywordTools::requiredItemsPerLineForKeyword(s)[0]); + } + { + std::string s = "SS"; + EXPECT_EQ(1, RifEclipseUserDataKeywordTools::requiredItemsPerLineForKeyword(s)[0]); + EXPECT_EQ(1, RifEclipseUserDataKeywordTools::requiredItemsPerLineForKeyword(s)[1]); + } + { + std::string s = "WW"; + EXPECT_EQ(1, RifEclipseUserDataKeywordTools::requiredItemsPerLineForKeyword(s)[0]); + } + + + { + std::string s = "LB"; + EXPECT_EQ(1, RifEclipseUserDataKeywordTools::requiredItemsPerLineForKeyword(s)[0]); + EXPECT_EQ(3, RifEclipseUserDataKeywordTools::requiredItemsPerLineForKeyword(s)[1]); + } + { + std::string s = "LC"; + EXPECT_EQ(1, RifEclipseUserDataKeywordTools::requiredItemsPerLineForKeyword(s)[0]); + EXPECT_EQ(1, RifEclipseUserDataKeywordTools::requiredItemsPerLineForKeyword(s)[1]); + EXPECT_EQ(3, RifEclipseUserDataKeywordTools::requiredItemsPerLineForKeyword(s)[2]); + } + { + std::string s = "LW"; + EXPECT_EQ(1, RifEclipseUserDataKeywordTools::requiredItemsPerLineForKeyword(s)[0]); + EXPECT_EQ(1, RifEclipseUserDataKeywordTools::requiredItemsPerLineForKeyword(s)[1]); + } +} + + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST(RifEclipseUserDataKeywordToolsTest, BuildTableHeaderText) +{ + std::vector keywordNames = { "TIME", "YEARX", "WGT1", "WR42" }; + std::vector firstheader = { "OP-1", "OP-1" }; + std::vector> headerLines = { firstheader }; + + auto tableHeaderData = RifEclipseUserDataKeywordTools::buildColumnHeaderText(keywordNames, headerLines); + EXPECT_EQ(4, tableHeaderData.size()); + EXPECT_EQ(1, tableHeaderData[0].size()); + EXPECT_EQ(1, tableHeaderData[1].size()); + EXPECT_EQ(2, tableHeaderData[2].size()); + EXPECT_EQ(2, tableHeaderData[3].size()); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST(RifEclipseUserDataKeywordToolsTest, BuildTableHeaderTextComplex) +{ + std::vector keywordNames = { "TIME", "WGT1", "FVIR", "RPR", "GOPR", "CWIR", "FTPTS36", "CWIR" }; + std::vector firstheader = { "OP-1", "8", "MANI-D2", "F-2H", "2H" }; + std::vector secondHeader = { "18", "83","3", "9", "8","7" }; + std::vector> headerLines = { firstheader, secondHeader }; + + auto tableHeaderData = RifEclipseUserDataKeywordTools::buildColumnHeaderText(keywordNames, headerLines); + EXPECT_EQ(8, tableHeaderData.size()); + + EXPECT_EQ(1, tableHeaderData[0].size()); + EXPECT_EQ(2, tableHeaderData[1].size()); + EXPECT_EQ(1, tableHeaderData[2].size()); + EXPECT_EQ(2, tableHeaderData[3].size()); + EXPECT_EQ(2, tableHeaderData[4].size()); + EXPECT_EQ(5, tableHeaderData[5].size()); + EXPECT_EQ(1, tableHeaderData[6].size()); + EXPECT_EQ(5, tableHeaderData[7].size()); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST(RifEclipseUserDataKeywordToolsTest, MissingHeaderData) +{ + { + std::vector keywordNames = { "TIME", "WGT1" }; + std::vector firstheader = { }; // Missing well name + std::vector> headerLines = { firstheader }; + + auto tableHeaderData = RifEclipseUserDataKeywordTools::buildColumnHeaderText(keywordNames, headerLines); + EXPECT_EQ(0, tableHeaderData.size()); + } + + + { + std::vector keywordNames = { "TIME", "WGT1", "FVIR", "RPR", "GOPR", "CWIR", "FTPTS36", "CWIR" }; + std::vector firstheader = { "OP-1", "8", "MANI-D2", "F-2H", "2H" }; + std::vector secondHeader = { "18", "83","3", "9", "8" }; // Missing value from last triplet + std::vector> headerLines = { firstheader, secondHeader }; + + auto tableHeaderData = RifEclipseUserDataKeywordTools::buildColumnHeaderText(keywordNames, headerLines); + EXPECT_EQ(0, tableHeaderData.size()); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST(RifEclipseUserDataKeywordToolsTest, CreationOfSummaryAddresses) +{ + // Region + { + std::string quantity = "RGT1"; + std::vector columnData = {"1"}; + + auto address = RifEclipseUserDataKeywordTools::makeAndFillAddress(quantity, columnData); + + EXPECT_EQ(address.category(), RifEclipseSummaryAddress::SUMMARY_REGION); + EXPECT_STREQ(quantity.data(), address.quantityName().data()); + EXPECT_EQ(1, address.regionNumber()); + } + + // Well group + { + std::string quantity = "GT1"; + std::vector columnData = { "OP-1" }; + + auto address = RifEclipseUserDataKeywordTools::makeAndFillAddress(quantity, columnData); + + EXPECT_EQ(address.category(), RifEclipseSummaryAddress::SUMMARY_WELL_GROUP); + EXPECT_STREQ(quantity.data(), address.quantityName().data()); + EXPECT_STREQ(columnData[0].data(), address.wellGroupName().data()); + } + + + // Well + { + std::string quantity = "WGT1"; + std::vector columnData = { "OP-1" }; + + auto address = RifEclipseUserDataKeywordTools::makeAndFillAddress(quantity, columnData); + + EXPECT_EQ(address.category(), RifEclipseSummaryAddress::SUMMARY_WELL); + EXPECT_STREQ(quantity.data(), address.quantityName().data()); + EXPECT_STREQ(columnData[0].data(), address.wellName().data()); + } + + // Well completion + { + std::string quantity = "CWIT"; + std::vector columnData = { "F-3H", "1", "2", "3" }; + + auto address = RifEclipseUserDataKeywordTools::makeAndFillAddress(quantity, columnData); + + EXPECT_EQ(address.category(), RifEclipseSummaryAddress::SUMMARY_WELL_COMPLETION); + EXPECT_STREQ(quantity.data(), address.quantityName().data()); + EXPECT_STREQ(columnData[0].data(), address.wellName().data()); + EXPECT_EQ(1, address.cellI()); + EXPECT_EQ(2, address.cellJ()); + EXPECT_EQ(3, address.cellK()); + } + + // Well LGR + { + std::string quantity = "LWGT1"; + std::vector columnData = { "OP-1", "LGR-NAME" }; + + auto address = RifEclipseUserDataKeywordTools::makeAndFillAddress(quantity, columnData); + + EXPECT_EQ(address.category(), RifEclipseSummaryAddress::SUMMARY_WELL_LGR); + EXPECT_STREQ(quantity.data(), address.quantityName().data()); + EXPECT_STREQ(columnData[0].data(), address.wellName().data()); + EXPECT_STREQ(columnData[1].data(), address.lgrName().data()); + } + + // Well completion LGR + { + std::string quantity = "LC"; + std::vector columnData = { "F-3H", "LGR-NAME", "1", "2", "3" }; + + auto address = RifEclipseUserDataKeywordTools::makeAndFillAddress(quantity, columnData); + + EXPECT_EQ(address.category(), RifEclipseSummaryAddress::SUMMARY_WELL_COMPLETION_LGR); + EXPECT_STREQ(quantity.data(), address.quantityName().data()); + EXPECT_STREQ(columnData[0].data(), address.wellName().data()); + EXPECT_STREQ(columnData[1].data(), address.lgrName().data()); + EXPECT_EQ(1, address.cellI()); + EXPECT_EQ(2, address.cellJ()); + EXPECT_EQ(3, address.cellK()); + } + + // Well segment + { + std::string quantity = "SCWIT"; + std::vector columnData = { "F-3H", "1" }; + + auto address = RifEclipseUserDataKeywordTools::makeAndFillAddress(quantity, columnData); + + EXPECT_EQ(address.category(), RifEclipseSummaryAddress::SUMMARY_WELL_SEGMENT); + EXPECT_STREQ(quantity.data(), address.quantityName().data()); + EXPECT_STREQ(columnData[0].data(), address.wellName().data()); + EXPECT_EQ(1, address.wellSegmentNumber()); + } + + // Block + { + std::string quantity = "BWIT"; + std::vector columnData = { "1", "2", "3" }; + + auto address = RifEclipseUserDataKeywordTools::makeAndFillAddress(quantity, columnData); + + EXPECT_EQ(address.category(), RifEclipseSummaryAddress::SUMMARY_BLOCK); + EXPECT_STREQ(quantity.data(), address.quantityName().data()); + EXPECT_EQ(1, address.cellI()); + EXPECT_EQ(2, address.cellJ()); + EXPECT_EQ(3, address.cellK()); + } + + // Block LGR + { + std::string quantity = "LBWIT"; + std::vector columnData = { "LGR-name", "1", "2", "3" }; + + auto address = RifEclipseUserDataKeywordTools::makeAndFillAddress(quantity, columnData); + + EXPECT_EQ(address.category(), RifEclipseSummaryAddress::SUMMARY_BLOCK_LGR); + EXPECT_STREQ(quantity.data(), address.quantityName().data()); + EXPECT_STREQ(columnData[0].data(), address.lgrName().data()); + EXPECT_EQ(1, address.cellI()); + EXPECT_EQ(2, address.cellJ()); + EXPECT_EQ(3, address.cellK()); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST(RifEclipseUserDataKeywordToolsTest, CreationOfMisc) +{ + // Misc + { + std::string quantity = "JI-NOT-REQOGNIZED"; + std::vector columnData = { }; + auto address = RifEclipseUserDataKeywordTools::makeAndFillAddress(quantity, columnData); + + EXPECT_EQ(address.category(), RifEclipseSummaryAddress::SUMMARY_MISC); + EXPECT_STREQ(quantity.data(), address.quantityName().data()); + } + +}