Replaces checking if the report step is zero with maintaining some state and determining if any given step is the first time an UNRST file is written to or not. Extends the test to also cover this case. New behaviour will remove all time steps from a pre-existing UNRST file from the restart step being started from, but preserve all steps leading up to that point.
357 lines
12 KiB
C++
357 lines
12 KiB
C++
/*
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "config.h"
|
|
|
|
#if HAVE_DYNAMIC_BOOST_TEST
|
|
#define BOOST_TEST_DYN_LINK
|
|
#endif
|
|
|
|
#define BOOST_TEST_MODULE EclipseWriter
|
|
#include <boost/test/unit_test.hpp>
|
|
|
|
#include <opm/output/eclipse/EclipseWriter.hpp>
|
|
#include <opm/output/Cells.hpp>
|
|
|
|
#include <opm/parser/eclipse/Parser/ParseContext.hpp>
|
|
#include <opm/parser/eclipse/Parser/Parser.hpp>
|
|
#include <opm/parser/eclipse/Deck/Deck.hpp>
|
|
#include <opm/parser/eclipse/Deck/DeckKeyword.hpp>
|
|
#include <opm/parser/eclipse/EclipseState/Grid/EclipseGrid.hpp>
|
|
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
|
|
#include <opm/parser/eclipse/EclipseState/Schedule/TimeMap.hpp>
|
|
#include <opm/parser/eclipse/EclipseState/IOConfig/IOConfig.hpp>
|
|
#include <opm/parser/eclipse/Units/ConversionFactors.hpp>
|
|
#include <opm/parser/eclipse/Units/UnitSystem.hpp>
|
|
|
|
// ERT stuff
|
|
#include <ert/ecl/ecl_kw.h>
|
|
#include <ert/ecl/ecl_endian_flip.h>
|
|
#include <ert/ecl/ecl_file.h>
|
|
#include <ert/ecl/ecl_util.h>
|
|
#include <ert/util/ert_unique_ptr.hpp>
|
|
#include <ert/util/TestArea.hpp>
|
|
|
|
#include <memory>
|
|
|
|
using namespace Opm;
|
|
|
|
data::Solution createBlackoilState( int timeStepIdx, int numCells ) {
|
|
|
|
std::vector< double > pressure( numCells );
|
|
std::vector< double > swat( numCells );
|
|
std::vector< double > sgas( numCells );
|
|
std::vector< double > rs( numCells );
|
|
std::vector< double > rv( numCells );
|
|
|
|
for( int cellIdx = 0; cellIdx < numCells; ++cellIdx) {
|
|
|
|
pressure[cellIdx] = timeStepIdx*1e5 + 1e4 + cellIdx;
|
|
sgas[cellIdx] = timeStepIdx*1e5 +2.2e4 + cellIdx;
|
|
swat[cellIdx] = timeStepIdx*1e5 +2.3e4 + cellIdx;
|
|
|
|
// oil vaporization factor
|
|
rv[cellIdx] = timeStepIdx*1e5 +3e4 + cellIdx;
|
|
// gas dissolution factor
|
|
rs[cellIdx] = timeStepIdx*1e5 + 4e4 + cellIdx;
|
|
}
|
|
|
|
data::Solution solution;
|
|
using ds = data::Solution::key;
|
|
|
|
solution.insert( ds::PRESSURE, pressure );
|
|
solution.insert( ds::SWAT, swat );
|
|
solution.insert( ds::SGAS, sgas );
|
|
solution.insert( ds::RS, rs );
|
|
solution.insert( ds::RV, rv );
|
|
return solution;
|
|
}
|
|
|
|
template< typename T >
|
|
std::vector< T > getErtData( ecl_kw_type *eclKeyword ) {
|
|
size_t kwSize = ecl_kw_get_size(eclKeyword);
|
|
T* ertData = static_cast< T* >(ecl_kw_iget_ptr(eclKeyword, 0));
|
|
|
|
return { ertData, ertData + kwSize };
|
|
}
|
|
|
|
template< typename T, typename U >
|
|
void compareErtData(const std::vector< T > &src,
|
|
const std::vector< U > &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<int> &src, const std::vector<int> &dst)
|
|
{
|
|
BOOST_CHECK_EQUAL_COLLECTIONS( src.begin(), src.end(),
|
|
dst.begin(), dst.end() );
|
|
}
|
|
|
|
void checkEgridFile( const EclipseGrid& eclGrid ) {
|
|
// use ERT directly to inspect the EGRID file produced by EclipseWriter
|
|
auto egridFile = fortio_open_reader("FOO.EGRID", /*isFormated=*/0, ECL_ENDIAN_FLIP);
|
|
|
|
const auto numCells = eclGrid.getNX() * eclGrid.getNY() * eclGrid.getNZ();
|
|
|
|
while( auto* eclKeyword = ecl_kw_fread_alloc( egridFile ) ) {
|
|
std::string keywordName(ecl_kw_get_header(eclKeyword));
|
|
if (keywordName == "COORD") {
|
|
std::vector< double > sourceData;
|
|
eclGrid.exportCOORD( sourceData );
|
|
auto resultData = getErtData< float >( eclKeyword );
|
|
compareErtData(sourceData, resultData, 1e-6);
|
|
}
|
|
else if (keywordName == "ZCORN") {
|
|
std::vector< double > sourceData;
|
|
eclGrid.exportZCORN(sourceData);
|
|
auto resultData = getErtData< float >( eclKeyword );
|
|
compareErtData(sourceData, resultData, /*percentTolerance=*/1e-6);
|
|
}
|
|
else if (keywordName == "ACTNUM") {
|
|
std::vector< int > sourceData( numCells );
|
|
eclGrid.exportACTNUM(sourceData);
|
|
auto resultData = getErtData< int >( eclKeyword );
|
|
|
|
if( sourceData.empty() )
|
|
sourceData.assign( numCells, 1 );
|
|
|
|
compareErtData( sourceData, resultData );
|
|
}
|
|
|
|
ecl_kw_free(eclKeyword);
|
|
}
|
|
|
|
fortio_fclose(egridFile);
|
|
}
|
|
|
|
void checkInitFile( const Deck& deck, const std::vector<data::CellData>& simProps) {
|
|
// use ERT directly to inspect the INIT file produced by EclipseWriter
|
|
ERT::ert_unique_ptr<ecl_file_type , ecl_file_close> initFile(ecl_file_open( "FOO.INIT" , 0 ));
|
|
|
|
for (int k=0; k < ecl_file_get_size( initFile.get() ); k++) {
|
|
ecl_kw_type * eclKeyword = ecl_file_iget_kw( initFile.get( ) , k );
|
|
std::string keywordName(ecl_kw_get_header(eclKeyword));
|
|
|
|
if (keywordName == "PORO") {
|
|
const auto &sourceData = deck.getKeyword("PORO").getSIDoubleData();
|
|
auto resultData = getErtData< float >( eclKeyword );
|
|
compareErtData(sourceData, resultData, 1e-4);
|
|
}
|
|
|
|
if (keywordName == "PERMX") {
|
|
const auto& sourceData = deck.getKeyword("PERMX").getSIDoubleData();
|
|
auto resultData = getErtData< float >( eclKeyword );
|
|
|
|
// 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, 1e-4);
|
|
}
|
|
}
|
|
|
|
BOOST_CHECK( ecl_file_has_kw( initFile.get() , "FIPNUM" ));
|
|
BOOST_CHECK( ecl_file_has_kw( initFile.get() , "SATNUM" ));
|
|
|
|
for (const auto& prop : simProps) {
|
|
BOOST_CHECK( ecl_file_has_kw( initFile.get() , prop.name.c_str()) );
|
|
}
|
|
}
|
|
|
|
void checkRestartFile( int timeStepIdx ) {
|
|
using ds = data::Solution::key;
|
|
|
|
for (int i = 1; i <= timeStepIdx; ++i) {
|
|
auto sol = createBlackoilState( i, 3 * 3 * 3 );
|
|
|
|
// 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;
|
|
while( auto* eclKeyword = ecl_kw_fread_alloc(rstFile) ) {
|
|
std::string keywordName(ecl_kw_get_header(eclKeyword));
|
|
|
|
if (keywordName == "SEQNUM") {
|
|
curSeqnum = *static_cast<int*>(ecl_kw_iget_ptr(eclKeyword, 0));
|
|
}
|
|
if (curSeqnum != i)
|
|
continue;
|
|
|
|
if (keywordName == "PRESSURE") {
|
|
const auto resultData = getErtData< float >( eclKeyword );
|
|
for( auto& x : sol[ ds::PRESSURE ] )
|
|
x /= Metric::Pressure;
|
|
|
|
compareErtData( sol[ ds::PRESSURE ], resultData, 1e-4 );
|
|
}
|
|
|
|
if (keywordName == "SWAT") {
|
|
const auto resultData = getErtData< float >( eclKeyword );
|
|
compareErtData(sol[ ds::SWAT ], resultData, 1e-4);
|
|
}
|
|
|
|
if (keywordName == "SGAS") {
|
|
const auto resultData = getErtData< float >( eclKeyword );
|
|
compareErtData( sol[ ds::SGAS ], resultData, 1e-4 );
|
|
}
|
|
|
|
if (keywordName == "KRO")
|
|
BOOST_CHECK_EQUAL( 1.0 * i * ecl_kw_get_size( eclKeyword ) , ecl_kw_element_sum_float( eclKeyword ));
|
|
|
|
if (keywordName == "KRG")
|
|
BOOST_CHECK_EQUAL( 10.0 * i * ecl_kw_get_size( eclKeyword ) , ecl_kw_element_sum_float( eclKeyword ));
|
|
}
|
|
|
|
fortio_fclose(rstFile);
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(EclipseWriterIntegration) {
|
|
const char *deckString =
|
|
"RUNSPEC\n"
|
|
"UNIFOUT\n"
|
|
"OIL\n"
|
|
"GAS\n"
|
|
"WATER\n"
|
|
"METRIC\n"
|
|
"DIMENS\n"
|
|
"3 3 3/\n"
|
|
"GRID\n"
|
|
"INIT\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"
|
|
"REGIONS\n"
|
|
"SATNUM\n"
|
|
"27*2 /\n"
|
|
"FIPNUM\n"
|
|
"27*3 /\n"
|
|
"SOLUTION\n"
|
|
"RPTRST\n"
|
|
"BASIC=2\n"
|
|
"/\n"
|
|
"SCHEDULE\n"
|
|
"TSTEP\n"
|
|
"1.0 2.0 3.0 4.0 5.0 6.0 7.0 /\n"
|
|
"WELSPECS\n"
|
|
"'INJ' 'G' 1 1 2000 'GAS' /\n"
|
|
"'PROD' 'G' 3 3 1000 'OIL' /\n"
|
|
"/\n";
|
|
|
|
ERT::TestArea ta("test_ecl_writer");
|
|
|
|
auto write_and_check = [&]( int first = 1, int last = 5 ) {
|
|
auto deck = Parser().parseString( deckString, ParseContext() );
|
|
auto es = std::make_shared< EclipseState >( Parser::parse( *deck ) );
|
|
es->getIOConfig()->setBaseName( "FOO" );
|
|
|
|
auto& eclGrid = *es->getInputGrid();
|
|
|
|
EclipseWriter eclWriter( es, eclGrid );
|
|
|
|
auto start_time = ecl_util_make_date( 10, 10, 2008 );
|
|
std::vector<double> tranx(3*3*3);
|
|
std::vector<double> trany(3*3*3);
|
|
std::vector<double> tranz(3*3*3);
|
|
std::vector<data::CellData> eGridProps{
|
|
{"TRANX" , UnitSystem::measure::transmissibility, tranx},
|
|
{"TRANY" , UnitSystem::measure::transmissibility, trany},
|
|
{"TRANZ" , UnitSystem::measure::transmissibility, tranz}
|
|
};
|
|
|
|
eclWriter.writeInitAndEgrid( );
|
|
eclWriter.writeInitAndEgrid( eGridProps );
|
|
|
|
data::Wells wells;
|
|
|
|
for( int i = first; i < last; ++i ) {
|
|
std::vector<data::CellData> timesStepProps {
|
|
{"KRO" , UnitSystem::measure::identity , std::vector<double>(3*3*3 , i)},
|
|
{"KRG" , UnitSystem::measure::identity , std::vector<double>(3*3*3 , i*10)}};
|
|
|
|
auto first_step = ecl_util_make_date( 10 + i, 11, 2008 );
|
|
eclWriter.writeTimeStep( i,
|
|
false,
|
|
first_step - start_time,
|
|
createBlackoilState( i, 3 * 3 * 3 ),
|
|
wells,
|
|
timesStepProps);
|
|
|
|
checkRestartFile( i );
|
|
}
|
|
|
|
checkInitFile( *deck , eGridProps);
|
|
checkEgridFile( eclGrid );
|
|
|
|
std::ifstream file( "FOO.UNRST", std::ios::binary );
|
|
std::streampos file_size = 0;
|
|
|
|
file_size = file.tellg();
|
|
file.seekg( 0, std::ios::end );
|
|
file_size = file.tellg() - file_size;
|
|
|
|
return file_size;
|
|
};
|
|
|
|
/*
|
|
* write the file and calculate the file size. FOO.UNRST should be
|
|
* overwitten for every time step, i.e. the file size should not change
|
|
* between runs. This is to verify that UNRST files are properly
|
|
* overwritten, which they used not to.
|
|
*
|
|
* * https://github.com/OPM/opm-simulators/issues/753
|
|
* * https://github.com/OPM/opm-output/pull/61
|
|
*/
|
|
const auto file_size = write_and_check();
|
|
|
|
for( int i = 0; i < 3; ++i )
|
|
BOOST_CHECK_EQUAL( file_size, write_and_check() );
|
|
|
|
/*
|
|
* check that "restarting" and writing over previous timesteps does not
|
|
* change the file size, if the total amount of steps are the same
|
|
*/
|
|
BOOST_CHECK_EQUAL( file_size, write_and_check( 3, 5 ) );
|
|
|
|
/* verify that adding steps from restart also increases file size */
|
|
BOOST_CHECK( file_size < write_and_check( 3, 7 ) );
|
|
|
|
/*
|
|
* verify that restarting a simulation, then writing fewer steps truncates
|
|
* the file
|
|
*/
|
|
BOOST_CHECK_EQUAL( file_size, write_and_check( 3, 5 ) );
|
|
}
|