From d8e9b5be211ce1ecd70a041526baa44808198409 Mon Sep 17 00:00:00 2001 From: Andreas Lauser Date: Mon, 14 Jul 2014 14:11:52 +0200 Subject: [PATCH] EclipseWriter: add unit test --- CMakeLists_files.cmake | 3 +- tests/test_EclipseWriter.cpp | 414 +++++++++++++++++++++++++++++++++++ 2 files changed, 416 insertions(+), 1 deletion(-) create mode 100644 tests/test_EclipseWriter.cpp diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index a91fce05..dd8baf0d 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -147,11 +147,12 @@ list (APPEND MAIN_SOURCE_FILES # originally generated with the command: # find tests -name '*.cpp' -a ! -wholename '*/not-unit/*' -printf '\t%p\n' | sort list (APPEND TEST_SOURCE_FILES + tests/test_EclipseWriter.cpp tests/test_spline.cpp tests/test_propertysystem.cpp tests/test_dgbasis.cpp tests/test_cartgrid.cpp - tests/test_ug.cpp + tests/test_ug.cpp tests/test_cubic.cpp tests/test_event.cpp tests/test_nonuniformtablelinear.cpp diff --git a/tests/test_EclipseWriter.cpp b/tests/test_EclipseWriter.cpp new file mode 100644 index 00000000..5a529669 --- /dev/null +++ b/tests/test_EclipseWriter.cpp @@ -0,0 +1,414 @@ +/* + Copyright 2014 Andreas Lauser + + This file is part of the Open Porous Media project (OPM). + + OPM 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. + + OPM 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 for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ +#include "config.h" + +#if HAVE_DYNAMIC_BOOST_TEST +#define BOOST_TEST_DYN_LINK +#endif + +#define BOOST_TEST_MODULE EclipseWriter +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +// ERT stuff +#include +#include +#include + +#include + +std::shared_ptr eclWriter; +std::shared_ptr simTimer; +std::shared_ptr deck; +std::shared_ptr eclGrid; +std::shared_ptr ourFineGridManagerPtr; +std::shared_ptr blackoilState; +std::shared_ptr wellState; + +void createEclipseWriter(const char *deckString) +{ + Opm::ParserConstPtr parser(new Opm::Parser()); + deck = parser->parseString(deckString); + + Opm::parameter::ParameterGroup params; + params.insertParameter("deck_filename", "foo.data"); + + auto runspecSection = std::make_shared(deck); + auto gridSection = std::make_shared(deck); + eclGrid.reset(new Opm::EclipseGrid(runspecSection, gridSection)); + + BOOST_CHECK(eclGrid->getNX() == 3); + BOOST_CHECK(eclGrid->getNY() == 3); + BOOST_CHECK(eclGrid->getNZ() == 3); + BOOST_CHECK(eclGrid->getCartesianSize() == 3*3*3); + + std::array cartSize; + cartSize[0] = eclGrid->getNX(); + cartSize[1] = eclGrid->getNY(); + cartSize[2] = eclGrid->getNZ(); + + simTimer.reset(new Opm::SimulatorTimer()); + Opm::TimeMapConstPtr timeMap(new Opm::TimeMap(deck)); + simTimer->init(timeMap); + + eclWriter.reset(new Opm::EclipseWriter(params, + deck, + eclGrid->getCartesianSize(), + 0, + &cartSize[0])); + + // also create an UnstructuredGrid (required to create a BlackoilState) + Opm::EclipseGridConstPtr constEclGrid(eclGrid); + ourFineGridManagerPtr.reset(new Opm::GridManager(constEclGrid)); + + const UnstructuredGrid &ourFinerUnstructuredGrid = *ourFineGridManagerPtr->c_grid(); + BOOST_CHECK(ourFinerUnstructuredGrid.cartdims[0] == 3); + BOOST_CHECK(ourFinerUnstructuredGrid.cartdims[1] == 3); + BOOST_CHECK(ourFinerUnstructuredGrid.cartdims[2] == 3); + + BOOST_CHECK(ourFinerUnstructuredGrid.number_of_cells == 3*3*3); + + // this check is disabled so far, because UnstructuredGrid uses some weird definition + // of the term "face". For this grid, "number_of_faces" is 108 which is + // 2*6*numCells... + //BOOST_CHECK(ourFinerUnstructuredGrid.number_of_faces == 4*4*4); + + int numCells = ourFinerUnstructuredGrid.number_of_cells; + for (int cellIdx = 0; cellIdx < numCells; ++cellIdx) + BOOST_CHECK(ourFinerUnstructuredGrid.global_cell[cellIdx] == cellIdx); +} + +void createBlackoilState(int timeStepIdx) +{ + // allocate a new BlackoilState object + const UnstructuredGrid &ourFinerUnstructuredGrid = *ourFineGridManagerPtr->c_grid(); + blackoilState.reset(new Opm::BlackoilState); + blackoilState->init(ourFinerUnstructuredGrid, 3); + + size_t numCells = ourFinerUnstructuredGrid.number_of_cells; + size_t numFaces = ourFinerUnstructuredGrid.number_of_faces; + + BOOST_CHECK(blackoilState->pressure().size() == numCells); + BOOST_CHECK(blackoilState->facepressure().size() == numFaces); + BOOST_CHECK(blackoilState->faceflux().size() == numFaces); + BOOST_CHECK(blackoilState->saturation().size() == numCells*3); + BOOST_CHECK(blackoilState->gasoilratio().size() == numCells); + BOOST_CHECK(blackoilState->rv().size() == numCells); + + // this check is disabled because BlackoilState does not seem to allocate memory for + // this field. This means that it is probably unused and unneeded. + //BOOST_CHECK(blackoilState->surfacevol().size() == numCells*3); + + // fill the state object with some data. The fun with this class is that it does not + // exhibit a proper c++ way to do this (i.e., getter + setter methods). Instead + // references to the arrays must be retrieved from the object and manipulated + // directly. Don't try to call resize() or anything else which is not politically + // correct on them! + auto &pressure = blackoilState->pressure(); + auto &facepressure = blackoilState->facepressure(); + auto &faceflux = blackoilState->faceflux(); + auto &saturation = blackoilState->saturation(); + auto &gasoilratio = blackoilState->gasoilratio(); + auto &rv = blackoilState->rv(); + for (size_t cellIdx = 0; cellIdx < numCells; ++cellIdx) { + pressure[cellIdx] = timeStepIdx*1e5 + 1e4 + cellIdx; + + // set the phase saturations. Some fun with direct index manipulation is to be + // had... + saturation[3*cellIdx + 0] = timeStepIdx*1e5 +2.1e4 + cellIdx; // oil + saturation[3*cellIdx + 1] = timeStepIdx*1e5 +2.2e4 + cellIdx; // gas + saturation[3*cellIdx + 2] = timeStepIdx*1e5 +2.3e4 + cellIdx; // water + + // oil vaporization factor + rv[cellIdx] = timeStepIdx*1e5 +3e4 + cellIdx; + + // gas dissolution factor + gasoilratio[cellIdx] = timeStepIdx*1e5 + 4e4 + cellIdx; + } + + // face specific data + for (size_t faceIdx = 0; faceIdx < numFaces; ++faceIdx) { + facepressure[faceIdx] = timeStepIdx*1e5 + 5e4 + faceIdx; + faceflux[faceIdx] = timeStepIdx*1e5 + 6e4 + faceIdx; + } +} + +void createWellState(int timeStepIdx) +{ + // allocate a new BlackoilState object + wellState.reset(new Opm::WellState); + wellState->init(0, *blackoilState); +} + +void getErtData(ecl_kw_type *eclKeyword, std::vector &data) +{ + size_t kwSize = ecl_kw_get_size(eclKeyword); + float* ertData = static_cast(ecl_kw_iget_ptr(eclKeyword, 0)); + + data.resize(kwSize); + std::copy(ertData, ertData + kwSize, data.begin()); +} + +void getErtData(ecl_kw_type *eclKeyword, std::vector &data) +{ + size_t kwSize = ecl_kw_get_size(eclKeyword); + int* ertData = static_cast(ecl_kw_iget_ptr(eclKeyword, 0)); + + data.resize(kwSize); + std::copy(ertData, ertData + kwSize, data.begin()); +} + +void compareErtData(const std::vector &src, const std::vector &dst, double tolerance) +{ + BOOST_CHECK_EQUAL(src.size(), dst.size()); + if (src.size() != dst.size()) + return; + + for (size_t i = 0; i < src.size(); ++i) + BOOST_CHECK_CLOSE(src[i], dst[i], tolerance); +} + +void compareErtData(const std::vector &src, const std::vector &dst) +{ + BOOST_CHECK_EQUAL(src.size(), dst.size()); + if (src.size() != dst.size()) + return; + + for (size_t i = 0; i < src.size(); ++i) + BOOST_CHECK_EQUAL(src[i], dst[i]); +} + +void checkEgridFile() +{ + size_t numCells = ourFineGridManagerPtr->c_grid()->number_of_cells; + + // use ERT directly to inspect the EGRID file produced by EclipseWriter + auto egridFile = fortio_open_reader("FOO.EGRID", /*isFormated=*/0, ECL_ENDIAN_FLIP); + + ecl_kw_type *eclKeyword; + // yes, that's an assignment! + while ((eclKeyword = ecl_kw_fread_alloc(egridFile))) { + std::string keywordName(ecl_kw_get_header(eclKeyword)); + if (keywordName == "COORD") { + std::vector sourceData, resultData; + eclGrid->exportCOORD(sourceData); + getErtData(eclKeyword, resultData); + compareErtData(sourceData, resultData, /*percentTolerance=*/1e-6); + } + else if (keywordName == "ZCORN") { + std::vector sourceData, resultData; + eclGrid->exportZCORN(sourceData); + getErtData(eclKeyword, resultData); + compareErtData(sourceData, resultData, /*percentTolerance=*/1e-6); + } + else if (keywordName == "ACTNUM") { + std::vector sourceData, resultData; + eclGrid->exportACTNUM(sourceData); + getErtData(eclKeyword, resultData); + + if (resultData.size() == numCells && sourceData.size() == 0) { + sourceData.resize(numCells); + std::fill(sourceData.begin(), sourceData.end(), 1); + } + + compareErtData(sourceData, resultData); + } + + ecl_kw_free(eclKeyword); + } + + fortio_fclose(egridFile); +} + +void checkInitFile() +{ + // use ERT directly to inspect the INIT file produced by EclipseWriter + auto initFile = fortio_open_reader("FOO.INIT", /*isFormated=*/0, ECL_ENDIAN_FLIP); + + ecl_kw_type *eclKeyword; + // yes, that's an assignment! + while ((eclKeyword = ecl_kw_fread_alloc(initFile))) { + std::string keywordName(ecl_kw_get_header(eclKeyword)); + + if (keywordName == "PORO") { + const std::vector &sourceData = deck->getKeyword("PORO")->getSIDoubleData(); + std::vector resultData; + getErtData(eclKeyword, resultData); + + compareErtData(sourceData, resultData, /*percentTolerance=*/1e-4); + } + + if (keywordName == "PERMX") { + std::vector sourceData = deck->getKeyword("PERMX")->getSIDoubleData(); + std::vector resultData; + getErtData(eclKeyword, resultData); + + // convert the data from ERT from Field to SI units (mD to m^2) + for (size_t i = 0; i < resultData.size(); ++i) { + resultData[i] *= 9.869233e-16; + } + + compareErtData(sourceData, resultData, /*percentTolerance=*/1e-4); + } + + ecl_kw_free(eclKeyword); + } + + fortio_fclose(initFile); +} + +void checkRestartFile(int timeStepIdx) +{ + size_t numCells = ourFineGridManagerPtr->c_grid()->number_of_cells; + + Opm::PhaseUsage phaseUsage = Opm::phaseUsageFromDeck(deck); + int numActivePhases = phaseUsage.num_phases; + int waterPhaseIdx = phaseUsage.phase_pos[Opm::BlackoilPhases::Aqua]; + int gasPhaseIdx = phaseUsage.phase_pos[Opm::BlackoilPhases::Vapour]; + + for (int i = 0; i <= timeStepIdx; ++i) { + createBlackoilState(i); + + // use ERT directly to inspect the restart file produced by EclipseWriter + auto rstFile = fortio_open_reader("FOO.UNRST", /*isFormated=*/0, ECL_ENDIAN_FLIP); + + int curSeqnum = -1; + ecl_kw_type *eclKeyword; + // yes, that's an assignment! + while ((eclKeyword = ecl_kw_fread_alloc(rstFile))) { + std::string keywordName(ecl_kw_get_header(eclKeyword)); + + if (keywordName == "SEQNUM") { + curSeqnum = *static_cast(ecl_kw_iget_ptr(eclKeyword, 0)); + } + if (curSeqnum != i) + continue; + + if (keywordName == "PRESSURE") { + std::vector sourceData = blackoilState->pressure(); + std::vector resultData; + getErtData(eclKeyword, resultData); + + // convert the data from ERT from Metric to SI units (bar to Pa) + for (size_t i = 0; i < resultData.size(); ++i) { + resultData[i] *= 1e5; + } + + compareErtData(sourceData, resultData, /*percentTolerance=*/1e-4); + } + + if (keywordName == "SWAT") { + std::vector sourceData; + std::vector resultData; + getErtData(eclKeyword, resultData); + + // extract the water saturation from the black-oil state + sourceData.resize(numCells); + for (size_t i = 0; i < sourceData.size(); ++i) { + // again, fun with direct index manipulation... + sourceData[i] = blackoilState->saturation()[i*numActivePhases + waterPhaseIdx]; + } + + compareErtData(sourceData, resultData, /*percentTolerance=*/1e-4); + } + + if (keywordName == "SGAS") { + std::vector sourceData; + std::vector resultData; + getErtData(eclKeyword, resultData); + + // extract the water saturation from the black-oil state + sourceData.resize(numCells); + for (size_t i = 0; i < sourceData.size(); ++i) { + // again, fun with direct index manipulation... + sourceData[i] = blackoilState->saturation()[i*numActivePhases + gasPhaseIdx]; + } + + compareErtData(sourceData, resultData, /*percentTolerance=*/1e-4); + } + } + + fortio_fclose(rstFile); + } +} + +void checkSummaryFile(int timeStepIdx) +{ + // TODO +} + +BOOST_AUTO_TEST_CASE(EclipseWriterIntegration) +{ + const char *deckString = + "RUNSPEC\n" + "OIL\n" + "GAS\n" + "WATER\n" + "METRIC\n" + "DIMENS\n" + "3 3 3/\n" + "GRID\n" + "DXV\n" + "1.0 2.0 3.0 /\n" + "DYV\n" + "4.0 5.0 6.0 /\n" + "DZV\n" + "7.0 8.0 9.0 /\n" + "TOPS\n" + "9*100 /\n" + "PROPS\n" + "PORO\n" + "27*0.3 /\n" + "PERMX\n" + "27*1 /\n" + "SCHEDULE\n" + "TSTEP\n" + "1.0 2.0 3.0 4.0 /\n" + "WELSPECS\n" + "'INJ' 'G' 1 1 2000 'GAS' /\n" + "'PROD' 'G' 3 3 1000 'OIL' /\n" + "/\n"; + + createEclipseWriter(deckString); + + eclWriter->writeInit(*simTimer); + + checkEgridFile(); + checkInitFile(); + + for (; simTimer->currentStepNum() < simTimer->numSteps(); ++ (*simTimer)) { + createBlackoilState(simTimer->currentStepNum()); + createWellState(simTimer->currentStepNum()); + eclWriter->writeTimeStep(*simTimer, *blackoilState, *wellState); + checkRestartFile(simTimer->currentStepNum()); + checkSummaryFile(simTimer->currentStepNum()); + } +}