Import opm-output

This commit is contained in:
Arne Morten Kvarving 2018-03-05 10:30:59 +01:00
commit dbba64e5c3
52 changed files with 16054 additions and 1 deletions

16
.gitignore vendored
View File

@ -45,7 +45,6 @@ Testing/
# Build directory in source.
build/
.dir-locals.el
gmon.out
log.log
build
@ -55,3 +54,18 @@ install
/testdata/statoil
.idea
/Debug/
# Compiled Dynamic libraries
*.so
*.dylib
# Compiled Static libraries
*.lai
*.la
*.a
# Mac OS X debug info
*.dSYM
# emacs directory setting:
.dir-locals.el

View File

@ -0,0 +1,273 @@
/*
Copyright 2016 Statoil ASA.
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 <opm/test_util/EclFilesComparator.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <ert/util/util.h>
#include <ert/util/stringlist.h>
#include <ert/ecl/ecl_endian_flip.h>
#include <ert/ecl/ecl_file.h>
#include <iostream>
#include <string>
#include <getopt.h>
static void printHelp() {
std::cout << "\ncompareECL compares ECLIPSE files (restart (.RST), unified restart (.UNRST), initial (.INIT) or .RFT) and gridsizes (from .EGRID or .GRID file) from two simulations.\n"
<< "The program takes four arguments:\n\n"
<< "1. Case number 1 (full path without extension)\n"
<< "2. Case number 2 (full path without extension)\n"
<< "3. Absolute tolerance\n"
<< "4. Relative tolerance (between 0 and 1)\n\n"
<< "In addition, the program takes these options (which must be given before the arguments):\n\n"
<< "-h Print help and exit.\n"
<< "-i Execute integration test (regression test is default).\n"
<< " The integration test compares SGAS, SWAT and PRESSURE in unified restart files, so this option can not be used in combination with -t.\n"
<< "-I Same as -i, but throws an exception when the number of keywords in the two cases differ. Can not be used in combination with -t.\n"
<< "-k Specify specific keyword to compare (capitalized), for example -k PRESSURE.\n"
<< "-l Only do comparison for the last occurrence. This option is only for the regression test, and can therefore not be used in combination with -i or -I.\n"
<< "-n Do not throw on errors.\n"
<< "-p Print keywords in both cases and exit. Can not be used in combination with -P.\n"
<< "-P Print common and uncommon keywords in both cases and exit. Can not be used in combination with -p.\n"
<< "-t Specify ECLIPSE filetype to compare (unified restart is default). Can not be used in combination with -i or -I. Different possible arguments are:\n"
<< " -t UNRST \t Compare two unified restart files (.UNRST). This the default value, so it is the same as not passing option -t.\n"
<< " -t INIT \t Compare two initial files (.INIT).\n"
<< " -t RFT \t Compare two RFT files (.RFT).\n"
<< " -t RST \t Compare two cases consisting of restart files (.Xnnnn).\n"
<< " -t RST1 \t Compare two cases where the first case consists of restart files (.Xnnnn), and the second case consists of a unified restart file (.UNRST).\n"
<< " -t RST2 \t Compare two cases where the first case consists of a unified restart file (.UNRST), and the second case consists of restart files (.Xnnnn).\n"
<< " Note that when dealing with restart files (.Xnnnn), the program concatenates all of them into one unified restart file, which is used for comparison and stored in the same directory as the restart files.\n"
<< " This will overwrite any existing unified restart file in that directory.\n\n"
<< "Example usage of the program: \n\n"
<< "compareECL -k PRESSURE <path to first casefile> <path to second casefile> 1e-3 1e-5\n"
<< "compareECL -t INIT -k PORO <path to first casefile> <path to second casefile> 1e-3 1e-5\n"
<< "compareECL -i <path to first casefile> <path to second casefile> 0.01 1e-6\n\n"
<< "Exceptions are thrown (and hence program exits) when deviations are larger than the specified "
<< "tolerances, or when the number of cells does not match -- either in the grid file or for a "
<< "specific keyword. Information about the keyword, keyword occurrence (zero based) and cell "
<< "coordinate is printed when an exception is thrown. For more information about how the cases "
<< "are compared, see the documentation of the EclFilesComparator class.\n\n";
}
void splitBasename(const std::string& basename, std::string& path, std::string& filename) {
const size_t lastSlashIndex = basename.find_last_of("/\\");
path = basename.substr(0,lastSlashIndex);
filename = basename.substr(lastSlashIndex+1);
}
// Inspired by the ecl_pack application in the ERT library
void concatenateRestart(const std::string& basename) {
std::string inputPath, inputBase;
splitBasename(basename, inputPath, inputBase);
stringlist_type* inputFiles = stringlist_alloc_new();
const int numFiles = ecl_util_select_filelist(inputPath.c_str(), inputBase.c_str(), ECL_RESTART_FILE, false, inputFiles);
const char* target_file_name = ecl_util_alloc_filename(inputPath.c_str(), inputBase.c_str(), ECL_UNIFIED_RESTART_FILE, false, -1);
fortio_type* target = fortio_open_writer(target_file_name, false, ECL_ENDIAN_FLIP);
int dummy;
ecl_kw_type* seqnum_kw = ecl_kw_alloc_new("SEQNUM", 1, ECL_INT, &dummy);
int reportStep = 0;
for (int i = 0; i < numFiles; ++i) {
ecl_util_get_file_type(stringlist_iget(inputFiles, i), nullptr, &reportStep);
ecl_file_type* src_file = ecl_file_open(stringlist_iget(inputFiles, i), 0);
ecl_kw_iset_int(seqnum_kw, 0, reportStep);
ecl_kw_fwrite(seqnum_kw, target);
ecl_file_fwrite_fortio(src_file, target, 0);
ecl_file_close(src_file);
}
fortio_fclose(target);
stringlist_free(inputFiles);
}
//------------------------------------------------//
int main(int argc, char** argv) {
// Restart is default
ecl_file_enum file_type = ECL_UNIFIED_RESTART_FILE;
// RegressionTest is default
bool integrationTest = false;
bool checkNumKeywords = false;
bool onlyLastOccurrence = false;
bool printKeywords = false;
bool printKeywordsDifference = false;
bool specificKeyword = false;
bool specificFileType = false;
bool throwOnError = true;
char* keyword = nullptr;
char* fileTypeCstr = nullptr;
int c = 0;
while ((c = getopt(argc, argv, "hiIk:lnpPt:")) != -1) {
switch (c) {
case 'h':
printHelp();
return 0;
case 'i':
integrationTest = true;
break;
case 'I':
integrationTest = true;
checkNumKeywords = true;
break;
case 'n':
throwOnError = false;
break;
case 'k':
specificKeyword = true;
keyword = optarg;
break;
case 'l':
onlyLastOccurrence = true;
break;
case 'p':
printKeywords = true;
break;
case 'P':
printKeywordsDifference = true;
break;
case 't':
specificFileType = true;
fileTypeCstr = optarg;
break;
case '?':
if (optopt == 'k') {
std::cerr << "Option k requires a keyword as argument, see manual (-h) for more information." << std::endl;
return EXIT_FAILURE;
}
else if (optopt == 't') {
std::cerr << "Option t requires an ECLIPSE filetype as argument, see manual (-h) for more information." << std::endl;
return EXIT_FAILURE;
}
else {
std::cerr << "Unknown option." << std::endl;
return EXIT_FAILURE;
}
default:
return EXIT_FAILURE;
}
}
int argOffset = optind;
if ((printKeywords && printKeywordsDifference) ||
(integrationTest && specificFileType) ||
(integrationTest && onlyLastOccurrence)) {
std::cerr << "Error: Options given which can not be combined. "
<< "Please see the manual (-h) for more information." << std::endl;
return EXIT_FAILURE;
}
if (argc != argOffset + 4) {
std::cerr << "Error: The number of options and arguments given is not correct. "
<< "Please run compareECL -h to see manual." << std::endl;
return EXIT_FAILURE;
}
std::string basename1 = argv[argOffset];
std::string basename2 = argv[argOffset + 1];
double absTolerance = strtod(argv[argOffset + 2], nullptr);
double relTolerance = strtod(argv[argOffset + 3], nullptr);
if (specificFileType) {
std::string fileTypeString(fileTypeCstr);
for (auto& ch: fileTypeString) ch = toupper(ch);
if (fileTypeString== "UNRST") {} //Do nothing
else if (fileTypeString == "RST") {
concatenateRestart(basename1);
concatenateRestart(basename2);
}
else if (fileTypeString == "RST1") {
concatenateRestart(basename1);
}
else if (fileTypeString == "RST2") {
concatenateRestart(basename2);
}
else if (fileTypeString == "INIT") {
file_type = ECL_INIT_FILE;
}
else if (fileTypeString == "RFT") {
file_type = ECL_RFT_FILE;
}
else {
std::cerr << "Unknown ECLIPSE filetype specified with option -t. Please run compareECL -h to see manual." << std::endl;
return EXIT_FAILURE;
}
}
std::cout << "Comparing '" << basename1 << "' to '" << basename2 << "'." << std::endl;
try {
if (integrationTest) {
IntegrationTest comparator(basename1, basename2, absTolerance, relTolerance);
if (printKeywords) {
comparator.printKeywords();
return 0;
}
if (printKeywordsDifference) {
comparator.printKeywordsDifference();
return 0;
}
if (checkNumKeywords) {
comparator.equalNumKeywords();
}
if (specificKeyword) {
if (comparator.elementInWhitelist(keyword)) {
comparator.resultsForKeyword(keyword);
}
else {
std::cerr << "Keyword " << keyword << " is not supported for the integration test. Use SGAS, SWAT or PRESSURE." << std::endl;
return EXIT_FAILURE;
}
}
else {
comparator.results();
}
}
else {
RegressionTest comparator(file_type, basename1, basename2, absTolerance, relTolerance);
comparator.throwOnErrors(throwOnError);
if (printKeywords) {
comparator.printKeywords();
return 0;
}
if (printKeywordsDifference) {
comparator.printKeywordsDifference();
return 0;
}
if (onlyLastOccurrence) {
comparator.setOnlyLastOccurrence(true);
}
if (specificKeyword) {
comparator.gridCompare();
comparator.resultsForKeyword(keyword);
}
else {
comparator.gridCompare();
comparator.results();
}
if (comparator.getNoErrors() > 0)
OPM_THROW(std::runtime_error, comparator.getNoErrors() << " errors encountered in comparisons.");
}
}
catch (const std::exception& e) {
std::cerr << "Program threw an exception: " << e.what() << std::endl;
return EXIT_FAILURE;
}
return 0;
}

View File

@ -0,0 +1,223 @@
/*
Copyright 2016 Statoil ASA.
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 <opm/test_util/summaryRegressionTest.hpp>
#include <opm/test_util/summaryIntegrationTest.hpp>
#include <string>
#include <algorithm>
#include <stdexcept>
#include <getopt.h>
void printHelp(){
std::cout << "\n\nThe program can handle both unified and non-unified summary files."<< std::endl;
std::cout <<"In the case of non-unified summary files all the files must be located in the same directory. Only the basename (full path without extension) is needed as input." << std::endl << std::endl;
std::cout << "\nThe program takes four arguments" << std::endl;
std::cout << "1) <path to file1>/<base_name>, basename without extension" << std::endl;
std::cout << "2) <path to file2>/<base_name>, basename without extension" << std::endl;
std::cout << "3) absolute tolerance" << std::endl;
std::cout << "4) relative tolerance (between 0 and 1)" << std::endl;
std::cout << "The program will only throw an exception when both the absolute and relative tolerance are exceeded." << std::endl;
std::cout << "The program is capable of performing both a regression test and an integration test, \nhowever only one type of test at a time. ";
std::cout << "By default the program will run a regression test."<< std::endl;
std::cout << "\nThe program have command line options:" << std::endl;
std::cout << "-h \t\tPrint help message." << std::endl << std::endl;
std::cout << "For the regression test: " << std::endl;
std::cout << "-r \t\tChoosing regression test (this is default)."<< std::endl;
std::cout << "-k keyword \tSpecify a specific keyword to compare, for example - k WOPR:PRODU1."<< std::endl;
std::cout << "-p \t\tWill print the keywords of the files." << std::endl;
std::cout << "-R \t\tWill allow comparison between a restarted simulation and a normal simulation. The files must end at the same time." << std::endl << std::endl;
std::cout << "For the integration test:"<< std::endl;
std::cout << "-i \t\tChoosing integration test." << std::endl;
std::cout << "-d \t\tThe program will not throw an exception when the volume error ratio exceeds the limit." << std::endl;
std::cout << "-g \t\tWill print the vector with the greatest error ratio." << std::endl;
std::cout << "-k keyword \tSpecify a specific keyword to compare, for example - k WOPR:PRODU1."<< std::endl;
std::cout << "-K \t\tWill not allow different amount of keywords in the two files. Throws an exception if the amount are different." << std::endl;
std::cout << "-m mainVar \tWill calculate the error ratio for one main variable. Valid input is WOPR, WWPR, WGPR or WBHP." << std::endl;
std::cout << "-n \tDo not throw on errors." << std::endl;
std::cout << "-p \t\tWill print the keywords of the files." << std::endl;
std::cout << "-P keyword \tWill print the summary vectors of a specified kewyord, for example -P WOPR:B-3H." << std::endl;
std::cout << "-s int \t\tSets the number of spikes that are allowed for each keyword, for example: -s 5." << std::endl;
std::cout << "-v \t\tFor the rate keywords WOPR, WGPR, WWPR and WBHP. Calculates the error volume of \n\t\tthe two summary files. This is printed to screen." << std::endl;
std::cout << "-V keyword \tWill calculate the error rate for a specific keyword." << std::endl << std::endl;
std::cout << "Suggested combination of command line options:"<< std::endl;
std::cout << " -i -g -m mainVariable, will print the vector which have the greatest error ratio of the main variable of interest.\n"<< std::endl;
}
//---------------------------------------------------
int main (int argc, char ** argv){
//------------------------------------------------
//Defines some constants
bool specificKeyword = false;
bool allowSpikes = false;
bool findVolumeError = false;
bool integrationTest = false;
bool regressionTest = true;
bool allowDifferentAmountOfKeywords = true;
bool printKeywords = false;
bool printSpecificKeyword = false;
bool findVectorWithGreatestErrorRatio = false;
bool oneOfTheMainVariables = false;
bool throwExceptionForTooGreatErrorRatio = true;
bool isRestartFile = false;
bool throwOnError = true;
const char* keyword = nullptr;
const char* mainVariable = nullptr;
int c = 0;
int limit = -1;
//------------------------------------------------
//------------------------------------------------
//For setting the options selected
while ((c = getopt(argc, argv, "dghik:Km:npP:rRs:vV:")) != -1) {
switch (c) {
case 'd':
throwExceptionForTooGreatErrorRatio = false;
break;
case 'g':
findVectorWithGreatestErrorRatio = true;
throwExceptionForTooGreatErrorRatio = false;
break;
case 'h':
printHelp();
return 0;
case 'i':
integrationTest = true;
regressionTest = false;
break;
case 'k':
specificKeyword = true;
keyword = optarg;
break;
case 'K':
allowDifferentAmountOfKeywords = false;
break;
case 'm':
oneOfTheMainVariables = true;
mainVariable = optarg;
break;
case 'n':
throwOnError = false;
break;
case 'p':
printKeywords = true;
break;
case 'P':
specificKeyword = true;
printSpecificKeyword = true;
keyword = optarg;
break;
case 'r':
integrationTest = false;
regressionTest = true;
break;
case 'R':
isRestartFile = true;
break;
case 's':
allowSpikes = true;
limit = atof(optarg);
break;
case 'v':
findVolumeError = true;
break;
case 'V':
findVolumeError = true;
specificKeyword = true;
keyword = optarg;
break;
case '?':
if (optopt == 'k' || optopt == 'm' || optopt == 'P'
|| optopt == 's' || optopt == 'V') {
std::cout << "Option -"<<optopt<<" requires an keyword." << std::endl;
return EXIT_FAILURE;
}
else {
std::cout << "Unknown option." << std::endl;
return EXIT_FAILURE;
}
default:
return EXIT_FAILURE;
}
}
//------------------------------------------------
int argOffset = optind;
if (argc != argOffset + 4) {
printHelp();
return EXIT_FAILURE;
}
const char * basename1 = argv[argOffset];
const char * basename2 = argv[argOffset+1];
double absoluteTolerance = strtod(argv[argOffset+2], nullptr);
double relativeTolerance = strtod(argv[argOffset+3], nullptr);
std::cout << "Comparing '" << basename1 << "' to '" << basename2 << "'." << std::endl;
try {
if(regressionTest){
RegressionTest compare(basename1,basename2, absoluteTolerance, relativeTolerance);
compare.throwOnErrors(throwOnError);
if(printKeywords){compare.setPrintKeywords(true);}
if(isRestartFile){compare.setIsRestartFile(true);}
if(specificKeyword){
compare.getRegressionTest(keyword);
}
else{
if(printKeywords){compare.setPrintKeywords(true);}
compare.getRegressionTest();
}
}
if(integrationTest){
IntegrationTest compare(basename1,basename2, absoluteTolerance, relativeTolerance);
compare.throwOnErrors(throwOnError);
if(findVectorWithGreatestErrorRatio){compare.setFindVectorWithGreatestErrorRatio(true);}
if(allowSpikes){compare.setAllowSpikes(true);}
if(oneOfTheMainVariables){
compare.setOneOfTheMainVariables(true);
std::string str(mainVariable);
std::transform(str.begin(), str.end(),str.begin(), ::toupper);
if(str == "WOPR" ||str=="WWPR" ||str=="WGPR" || str == "WBHP"){
compare.setMainVariable(str);
}else{
throw std::invalid_argument("The input is not a main variable. -m option requires a valid main variable.");
}
}
if(findVolumeError){compare.setFindVolumeError(true);}
if(limit != -1){compare.setSpikeLimit(limit);}
if(!allowDifferentAmountOfKeywords){compare.setAllowDifferentAmountOfKeywords(false);}
if(printKeywords){compare.setPrintKeywords(true);}
if(!throwExceptionForTooGreatErrorRatio){compare.setThrowExceptionForTooGreatErrorRatio(false);}
if(specificKeyword){
if(printSpecificKeyword){compare.setPrintSpecificKeyword(true);}
compare.getIntegrationTest(keyword);
return 0;
}
compare.getIntegrationTest();
}
}
catch(const std::exception& e) {
std::cerr << "Program threw an exception: " << e.what() << std::endl;
return EXIT_FAILURE;
}
return 0;
}

103
opm/output/OutputWriter.hpp Normal file
View File

@ -0,0 +1,103 @@
/*
Copyright (c) 2013 Uni Research AS
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/>.
*/
#ifndef OPM_OUTPUT_WRITER_HPP
#define OPM_OUTPUT_WRITER_HPP
#include <opm/parser/eclipse/EclipseState/Grid/NNC.hpp>
#include <opm/output/Cells.hpp>
#include <opm/output/Wells.hpp>
struct UnstructuredGrid;
namespace Opm {
// forward declaration
class EclipseState;
namespace parameter { class ParameterGroup; }
class WellState;
struct PhaseUsage;
/*!
* Interface for writing non-compositional (blackoil, two-phase) simulation
* state to files.
*
* Use the create() function to setup a chain of writer based on the
* configuration values, e.g.
*
* \example
* \code{.cpp}
* ParameterGroup params (argc, argv, false);
* auto parser = std::make_shared <const Deck> (
* params.get <string> ("deck_filename"));
*
* std::unique_ptr <OutputWriter> writer =
* OutputWriter::create (params, parser);
*
* // before the first timestep
* writer->writeInit( current_posix_time, time_since_epoch_at_start );
*
* // after each timestep
* writer->writeTimeStep
*
* \endcode
*/
class OutputWriter {
public:
/// Allow derived classes to be used in the unique_ptr that is returned
/// from the create() method. (Every class that should be delete'd should
/// have a proper constructor, and if the base class isn't virtual then
/// the compiler won't call the right one when the unique_ptr goes out of
/// scope).
virtual ~OutputWriter () { }
/**
* Write the static data (grid, PVT curves, etc) to disk.
*
* This routine should be called before the first timestep (i.e. when
* timer.currentStepNum () == 0)
*/
virtual void writeInit( const NNC& nnc ) = 0;
/*!
* \brief Write a blackoil reservoir state to disk for later inspection with
* visualization tools like ResInsight
*
* \param[in] report_step The current report step
* \param[in] current_posix_time Seconds elapsed since epoch
* \param[in] seconds_elapsed Seconds elapsed since simulation start
* \param[in] reservoirState The thermodynamic state of the reservoir
* \param[in] wells Well data
*
* This routine should be called after the timestep has been advanced,
* i.e. timer.currentStepNum () > 0.
*/
virtual void writeTimeStep( int report_step,
time_t current_posix_time,
double seconds_elapsed,
data::Solution reservoirState,
data::Wells,
bool isSubstep) = 0;
};
} // namespace Opm
#endif /* OPM_OUTPUT_WRITER_HPP */

77
opm/output/data/Cells.hpp Normal file
View File

@ -0,0 +1,77 @@
/*
Copyright 2016 Statoil ASA.
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/>.
*/
#ifndef OPM_OUTPUT_CELLS_HPP
#define OPM_OUTPUT_CELLS_HPP
#include <map>
#include <vector>
#include <opm/parser/eclipse/Units/UnitSystem.hpp>
namespace Opm {
namespace data {
/*
The 3D data which are saved to file are assembled in one large
container. In the container the data is tagged with an element
from the TargetType enum which indicates why they they have been
added to the container - and where they are headed.
RESTART_SOLUTION : Fields which represent primary variables, and
are reqired for restart. Typically pressure and
suturation. WIll end up in the SOLUTION section of the restart
file.
RESTART_AUXILIARY : Fields with extra information, not required
for restart. Examples of this include fluid in place values or
evaluations of relative permeability. Will end up in the
restart file.
SUMMARY : Fields which are added only to serve as input data for
calculations of summary results. The Summary implementation can
use data with any tag value, but if it is tagged as SUMMARY it
will not be output anywhere else.
INIT : Fields which should go to the INIT file.
*/
enum class TargetType {
RESTART_SOLUTION,
RESTART_AUXILIARY,
SUMMARY,
INIT,
};
/**
* Small struct that keeps track of data for output to restart/summary files.
*/
struct CellData {
UnitSystem::measure dim; //< Dimension of the data to write
std::vector<double> data; //< The actual data itself
TargetType target;
};
}
}
#endif //OPM_OUTPUT_CELLS_HPP

View File

@ -0,0 +1,65 @@
/*
Copyright 2016 Statoil ASA.
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/>.
*/
#ifndef OPM_OUTPUT_DATA_SOLUTION_HPP
#define OPM_OUTPUT_DATA_SOLUTION_HPP
#include <string>
#include <map>
#include <opm/output/data/Cells.hpp>
#include <opm/parser/eclipse/Units/UnitSystem.hpp>
namespace Opm {
namespace data {
class Solution : public std::map< std::string, data::CellData > {
using Base = std::map< std::string, data::CellData >;
public:
Solution() = default;
explicit Solution( bool si );
using Base::map;
using Base::insert;
bool has( const std::string& ) const;
/*
* Get the data field of the struct matching the requested key. Will
* throw std::out_of_range if they key does not exist.
*/
std::vector< double >& data(const std::string& );
const std::vector< double >& data(const std::string& ) const;
std::pair< iterator, bool > insert( std::string name,
UnitSystem::measure,
std::vector< double >,
TargetType );
void convertToSI( const UnitSystem& );
void convertFromSI( const UnitSystem& );
private:
bool si = true;
};
}
}
#endif

361
opm/output/data/Wells.hpp Normal file
View File

@ -0,0 +1,361 @@
/*
Copyright 2016 Statoil ASA.
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/>.
*/
#ifndef OPM_OUTPUT_WELLS_HPP
#define OPM_OUTPUT_WELLS_HPP
#include <initializer_list>
#include <map>
#include <algorithm>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <vector>
namespace Opm {
namespace data {
class Rates {
/* Methods are defined inline for performance, as the actual *work* done
* is trivial, but somewhat frequent (typically once per time step per
* completion per well).
*
* To add a new rate type, add an entry in the enum with the correct
* shift, and if needed, increase the size type. Add a member variable
* and a new case in get_ref.
*/
public:
Rates() = default;
enum class opt : uint32_t {
wat = (1 << 0),
oil = (1 << 1),
gas = (1 << 2),
polymer = (1 << 3),
solvent = (1 << 4),
energy = (1 << 5),
dissolved_gas = (1 << 6),
vaporized_oil = (1 << 7),
reservoir_water = (1 << 8),
reservoir_oil = (1 << 9),
reservoir_gas = (1 << 10),
};
using enum_size = std::underlying_type< opt >::type;
/// Query if a value is set.
inline bool has( opt ) const;
/// Read the value indicated by m. Throws an exception if
/// if the requested value is unset.
inline double get( opt m ) const;
/// Read the value indicated by m. Returns a default value if
/// the requested value is unset.
inline double get( opt m, double default_value ) const;
/// Set the value specified by m. Throws an exception if multiple
/// values are requested. Returns a self-reference to support
/// chaining.
inline Rates& set( opt m, double value );
/// true if any option is set; false otherwise
inline bool any() const noexcept;
template <class MessageBufferType>
void write(MessageBufferType& buffer) const;
template <class MessageBufferType>
void read(MessageBufferType& buffer);
private:
double& get_ref( opt );
const double& get_ref( opt ) const;
opt mask = static_cast< opt >( 0 );
double wat = 0.0;
double oil = 0.0;
double gas = 0.0;
double polymer = 0.0;
double solvent = 0.0;
double energy = 0.0;
double dissolved_gas = 0.0;
double vaporized_oil = 0.0;
double reservoir_water = 0.0;
double reservoir_oil = 0.0;
double reservoir_gas = 0.0;
};
struct Completion {
using global_index = size_t;
static const constexpr int restart_size = 2;
global_index index;
Rates rates;
double pressure;
double reservoir_rate;
double cell_pressure;
double cell_saturation_water;
double cell_saturation_gas;
template <class MessageBufferType>
void write(MessageBufferType& buffer) const;
template <class MessageBufferType>
void read(MessageBufferType& buffer);
};
struct Well {
Rates rates;
double bhp;
double thp;
double temperature;
int control;
std::vector< Completion > completions;
inline bool flowing() const noexcept;
template <class MessageBufferType>
void write(MessageBufferType& buffer) const;
template <class MessageBufferType>
void read(MessageBufferType& buffer);
};
class WellRates : public std::map<std::string , Well> {
public:
double get(const std::string& well_name , Rates::opt m) const {
const auto& well = this->find( well_name );
if( well == this->end() ) return 0.0;
return well->second.rates.get( m, 0.0 );
}
double get(const std::string& well_name , Completion::global_index completion_grid_index, Rates::opt m) const {
const auto& witr = this->find( well_name );
if( witr == this->end() ) return 0.0;
const auto& well = witr->second;
const auto& completion = std::find_if( well.completions.begin() ,
well.completions.end() ,
[=]( const Completion& c ) {
return c.index == completion_grid_index; });
if( completion == well.completions.end() )
return 0.0;
return completion->rates.get( m, 0.0 );
}
template <class MessageBufferType>
void write(MessageBufferType& buffer) const {
unsigned int size = this->size();
buffer.write(size);
for (const auto& witr : *this) {
const std::string& name = witr.first;
buffer.write(name);
const Well& well = witr.second;
well.write(buffer);
}
}
template <class MessageBufferType>
void read(MessageBufferType& buffer) {
unsigned int size;
buffer.read(size);
for (size_t i = 0; i < size; ++i) {
std::string name;
buffer.read(name);
Well well;
well.read(buffer);
this->emplace(name, well);
}
}
};
using Wells = WellRates;
/* IMPLEMENTATIONS */
inline bool Rates::has( opt m ) const {
const auto mand = static_cast< enum_size >( this->mask )
& static_cast< enum_size >( m );
return static_cast< opt >( mand ) == m;
}
inline double Rates::get( opt m ) const {
if( !this->has( m ) )
throw std::invalid_argument( "Uninitialized value." );
return this->get_ref( m );
}
inline double Rates::get( opt m, double default_value ) const {
if( !this->has( m ) ) return default_value;
return this->get_ref( m );
}
inline Rates& Rates::set( opt m, double value ) {
this->get_ref( m ) = value;
/* mask |= m */
this->mask = static_cast< opt >(
static_cast< enum_size >( this->mask ) |
static_cast< enum_size >( m )
);
return *this;
}
/*
* To avoid error-prone and repetitve work when extending rates with new
* values, the get+set methods use this helper get_ref to determine what
* member to manipulate. To add a new option, just add another case
* corresponding to the enum entry in Rates to this function.
*
* This is an implementation detail and understanding this has no
* significant impact on correct use of the class.
*/
inline const double& Rates::get_ref( opt m ) const {
switch( m ) {
case opt::wat: return this->wat;
case opt::oil: return this->oil;
case opt::gas: return this->gas;
case opt::polymer: return this->polymer;
case opt::solvent: return this->solvent;
case opt::energy: return this->energy;
case opt::dissolved_gas: return this->dissolved_gas;
case opt::vaporized_oil: return this->vaporized_oil;
case opt::reservoir_water: return this->reservoir_water;
case opt::reservoir_oil: return this->reservoir_oil;
case opt::reservoir_gas: return this->reservoir_gas;
}
throw std::invalid_argument(
"Unknown value type '"
+ std::to_string( static_cast< enum_size >( m ) )
+ "'" );
}
inline double& Rates::get_ref( opt m ) {
return const_cast< double& >(
static_cast< const Rates* >( this )->get_ref( m )
);
}
inline bool Rates::any() const noexcept {
return static_cast< enum_size >( this->mask ) != 0;
}
inline bool Well::flowing() const noexcept {
return this->rates.any();
}
template <class MessageBufferType>
void Rates::write(MessageBufferType& buffer) const {
buffer.write(this->mask);
buffer.write(this->wat);
buffer.write(this->oil);
buffer.write(this->gas);
buffer.write(this->polymer);
buffer.write(this->solvent);
buffer.write(this->energy);
buffer.write(this->dissolved_gas);
buffer.write(this->vaporized_oil);
buffer.write(this->reservoir_water);
buffer.write(this->reservoir_oil);
buffer.write(this->reservoir_gas);
}
template <class MessageBufferType>
void Completion::write(MessageBufferType& buffer) const {
buffer.write(this->index);
this->rates.write(buffer);
buffer.write(this->pressure);
buffer.write(this->reservoir_rate);
buffer.write(this->cell_pressure);
buffer.write(this->cell_saturation_water);
buffer.write(this->cell_saturation_gas);
}
template <class MessageBufferType>
void Well::write(MessageBufferType& buffer) const {
this->rates.write(buffer);
buffer.write(this->bhp);
buffer.write(this->thp);
buffer.write(this->temperature);
buffer.write(this->control);
unsigned int size = this->completions.size();
buffer.write(size);
for (const Completion& comp : this->completions)
comp.write(buffer);
}
template <class MessageBufferType>
void Rates::read(MessageBufferType& buffer) {
buffer.read(this->mask);
buffer.read(this->wat);
buffer.read(this->oil);
buffer.read(this->gas);
buffer.read(this->polymer);
buffer.read(this->solvent);
buffer.read(this->energy);
buffer.read(this->dissolved_gas);
buffer.read(this->vaporized_oil);
buffer.read(this->reservoir_water);
buffer.read(this->reservoir_oil);
buffer.read(this->reservoir_gas);
}
template <class MessageBufferType>
void Completion::read(MessageBufferType& buffer) {
buffer.read(this->index);
this->rates.read(buffer);
buffer.read(this->pressure);
buffer.read(this->reservoir_rate);
buffer.read(this->cell_pressure);
buffer.read(this->cell_saturation_water);
buffer.read(this->cell_saturation_gas);
}
template <class MessageBufferType>
void Well::read(MessageBufferType& buffer) {
this->rates.read(buffer);
buffer.read(this->bhp);
buffer.read(this->thp);
buffer.read(this->temperature);
buffer.read(this->control);
unsigned int size = 0.0; //this->completions.size();
buffer.read(size);
this->completions.resize(size);
for (size_t i = 0; i < size; ++i)
{
auto& comp = this->completions[ i ];
comp.read(buffer);
}
}
}
}
#endif //OPM_OUTPUT_WELLS_HPP

View File

@ -0,0 +1,103 @@
//===========================================================================
//
// File: EclipseGridInspector.h
//
// Created: Mon Jun 2 09:46:08 2008
//
// Author: Atgeirr F Rasmussen <atgeirr@sintef.no>
//
// $Date$
//
// Revision: $Id: EclipseGridInspector.h,v 1.2 2008/08/18 14:16:12 atgeirr Exp $
//
//===========================================================================
/*
Copyright 2009, 2010 SINTEF ICT, Applied Mathematics.
Copyright 2009, 2010 Statoil ASA.
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/>.
*/
#ifndef OPM_ECLIPSEGRIDINSPECTOR_HEADER
#define OPM_ECLIPSEGRIDINSPECTOR_HEADER
#include <vector>
#include <array>
#include <opm/parser/eclipse/Deck/Deck.hpp>
namespace Opm
{
/**
@brief A class for inspecting the contents of an eclipse file.
Given an Eclipse deck, this class may be used to answer certain
queries about its contents.
@author Atgeirr F. Rasmussen <atgeirr@sintef.no>
@date 2008/06/02 09:46:08
*/
class EclipseGridInspector
{
public:
/// Constructor taking a parser as argument.
/// The parser must already have read an Eclipse file.
explicit EclipseGridInspector(Opm::Deck);
/// Assuming that the pillars are vertical, compute the
/// volume of the cell given by logical coordinates (i, j, k).
double cellVolumeVerticalPillars(int i, int j, int k) const;
/// Assuming that the pillars are vertical, compute the
/// volume of the cell given by the cell index
double cellVolumeVerticalPillars(int cell_idx) const;
/// Compute the average dip in x- and y-direction of the
/// cell tops and bottoms relative to the xy-plane
std::pair<double,double> cellDips(int i, int j, int k) const;
std::pair<double,double> cellDips(int cell_idx) const;
// Convert global cell index to logical ijk-coordinates
std::array<int, 3> cellIdxToLogicalCoords(int cell_idx) const;
/// Returns a vector with the outer limits of grid (in the grid's unit).
/// The vector contains [xmin, xmax, ymin, ymax, zmin, zmax], as
/// read from COORDS and ZCORN
std::array<double, 6> getGridLimits() const;
/// Returns the extent of the logical cartesian grid
/// as number of cells in the (i, j, k) directions.
std::array<int, 3> gridSize() const;
/// Returns the eight z-values associated with a given cell.
/// The ordering is such that i runs fastest. That is, with
/// L = low and H = high:
/// {LLL, HLL, LHL, HHL, LLH, HLH, LHH, HHH }.
std::array<double, 8> cellZvals(int i, int j, int k) const;
private:
Opm::Deck deck_;
int logical_gridsize_[3];
void init_();
void checkLogicalCoords(int i, int j, int k) const;
};
} // namespace Opm
#endif // OPM_ECLIPSEGRIDINSPECTOR_HEADER

View File

@ -0,0 +1,238 @@
/*
Copyright (c) 2013 Andreas Lauser
Copyright (c) 2013 Uni Research AS
Copyright (c) 2014 IRIS AS
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/>.
*/
#ifndef OPM_ECLIPSE_WRITER_HPP
#define OPM_ECLIPSE_WRITER_HPP
#include <map>
#include <string>
#include <vector>
#include <array>
#include <memory>
#include <opm/parser/eclipse/EclipseState/Grid/EclipseGrid.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/NNC.hpp>
#include <opm/output/data/Cells.hpp>
#include <opm/output/data/Solution.hpp>
#include <opm/output/data/Wells.hpp>
#include <opm/output/eclipse/RestartValue.hpp>
namespace Opm {
class EclipseState;
class SummaryConfig;
class Schedule;
/*!
* \brief A class to write the reservoir state and the well state of a
* blackoil simulation to disk using the Eclipse binary format.
*/
class EclipseIO {
public:
/*!
* \brief Sets the common attributes required to write eclipse
* binary files using ERT.
*/
EclipseIO( const EclipseState& es,
EclipseGrid grid,
const Schedule& schedule,
const SummaryConfig& summary_config);
/** \brief Output static properties in EGRID and INIT file.
*
* Write the static eclipse data (grid, PVT curves, etc) to disk,
* and set up additional initial properties. There are no specific
* keywords to configure the output to the INIT files, it seems
* like the algorithm is: "All available keywords are written to
* INIT file". For the current code the algorithm is as such:
*
*
* 1. 3D properties which can be simply calculated in the output
* layer are unconditionally written to disk, that currently
* includes the DX, DY, DZ and DEPTH keyword.
*
* 2. All integer propeerties from the deck are written
* unconditionally, that typically includes the MULTNUM, PVTNUM,
* SATNUM and so on. Observe that the keywords PVTNUM, SATNUM,
* EQLNUM and FIPNUM are autocreated in the output layer, so
* they will be on disk even if they are not explicitly included
* in the deck.
*
* 3. The PORV keyword will *always* be present in the INIT file,
* and that keyword will have nx*ny*nz elements; all other 3D
* properties will only have nactive elements.
*
* 4. For floating point 3D keywords from the deck - like PORO and
* PERMX there is a *hardcoded* list in the writeINIFile( )
* implementation of which keywords to output - if they are
* available.
*
* 5. The container simProps contains additional 3D floating point
* properties which have been calculated by the simulator, this
* typically includes the transmissibilities TRANX, TRANY and
* TRANZ but could in principle be anye floating point
* property.
*
* If you want the FOE keyword in the summary output the
* simProps container must contain the initial OIP field.
*
* In addition:
*
* - The NNC argument is distributed between the EGRID and INIT
* files.
*
* - PVT curves are written unconditionally, saturation functions
* are not yet written to disk.
*/
void writeInitial( data::Solution simProps = data::Solution(), std::map<std::string, std::vector<int> > int_data = {}, const NNC& nnc = NNC());
/**
* \brief Overwrite the initial OIP values.
*
* These are also written when we call writeInitial if the simProps
* contains them. If not these are assumed to zero and the simulator
* can update them with this methods.
* \param simProps The properties containing at least OIP.
*/
void overwriteInitialOIP( const data::Solution& simProps );
/*!
* \brief Write a reservoir state and summary information to disk.
*
*
* The reservoir state can be inspected with visualization tools like
* ResInsight.
*
* The summary information can then be visualized using tools from
* ERT or ECLIPSE. Note that calling this method is only
* meaningful after the first time step has been completed.
*
* The optional simProps vector contains fields which have been
* calculated by the simulator and are written to the restart
* file. Examples of such fields would be the relative
* permeabilities KRO, KRW and KRG and fluxes. The keywords which
* can be added here are represented with mnenonics in the RPTRST
* keyword.
*
* The extra_restart argument is an optional aergument which can
* be used to store arbitrary double vectors in the restart
* file. The following rules apply for the extra data:
*
* 1. There is no size constraints.
*
* 2. The keys must be unqiue across the SOlution container, and
* also built in default keys like 'INTEHEAD'.
*
* 3. There is no unit conversion applied - the vectors are
* written to disk verbatim.
*
* If the optional argument write_double is sent in as true the
* fields in the solution container will be written in double
* precision. OPM can load and restart from files with double
* precision keywords, but this is non-standard, and other third
* party applications might choke on those.
*
* The misc_summary_values argument is used to pass pass various
* summary values which are of type 'ECL_SMSPEC_MISC_VAR' to the
* summary writer. The ability to pass miscellanous values to the
* summary writer is not very flexible:
*
* 1. The keyword must be an already well defined ECLIPSE keyword
* like e.g. one of the performance related keywords.
*
* 2. The keyword must have been requested in the SUMMARY section
* of the input deck.
*
* 3. The dimension of the keyword must have specified in the
* hardcoded static map misc_units in Summary.cpp.
*/
void writeTimeStep( int report_step,
bool isSubstep,
double seconds_elapsed,
data::Solution,
data::Wells,
const std::map<std::string, double>& single_summary_values,
const std::map<std::string, std::vector<double>>& region_summary_values,
const std::map<std::pair<std::string, int>, double>& block_summary_values,
const std::map<std::string, std::vector<double>>& extra_restart = {},
bool write_double = false);
/*
Will load solution data and wellstate from the restart
file. This method will consult the IOConfig object to get
filename and report step to restart from.
The map keys should be a map of keyword names and their
corresponding dimension object, i.e. to load the state from a
simple two phase simulation you would pass:
keys = {{"PRESSURE" , UnitSystem::measure::pressure},
{"SWAT" , UnitSystem::measure::identity }}
For a three phase black oil simulation you would add pairs for
SGAS, RS and RV. If you ask for keys which are not found in the
restart file an exception will be raised, the happens if the
size of a vector is wrong.
The function will consult the InitConfig object in the
EclipseState object to determine which file and report step to
load.
The return value is of type 'data::Solution', which is the same
container type which is used by the EclipseIO, but observe
that the dim and target elements carry no information:
- The returned double data has been converted to SI.
. The target is unconditionally set to 'RESTART_SOLUTION'
The extra_keys argument can be used to request additional
kewyords from the restart value. The extra vectors will be
stored in the 'extra' field of the RestartValue return
value. These values must have been added to the restart file
previosuly with the extra argument to the writeTimeStep()
method. If the bool value in the map is true the value is
required, and the output layer will throw an exception if it is
missing, if the bool is false missing keywords will be ignored
(there will *not* be an empty vector in the return value).
*/
RestartValue loadRestart(const std::map<std::string, RestartKey>& keys, const std::map<std::string, bool>& extra_keys = {}) const;
EclipseIO( const EclipseIO& ) = delete;
~EclipseIO();
private:
class Impl;
std::unique_ptr< Impl > impl;
};
} // namespace Opm
#endif // OPM_ECLIPSE_WRITER_HPP

View File

@ -0,0 +1,35 @@
#ifndef ECLIPSE_IO_UTIL_HPP
#define ECLIPSE_IO_UTIL_HPP
#include <vector>
#include <string>
#include <iostream>
namespace Opm
{
namespace EclipseIOUtil
{
template <typename T>
void addToStripedData(const std::vector<T>& data, std::vector<T>& result, size_t offset, size_t stride) {
int dataindex = 0;
for (size_t index = offset; index < result.size(); index += stride) {
result[index] = data[dataindex];
++dataindex;
}
}
template <typename T>
void extractFromStripedData(const std::vector<T>& data, std::vector<T>& result, size_t offset, size_t stride) {
for (size_t index = offset; index < data.size(); index += stride) {
result.push_back(data[index]);
}
}
} //namespace EclipseIOUtil
} //namespace Opm
#endif //ECLIPSE_IO_UTIL_HPP

View File

@ -0,0 +1,168 @@
/*
Copyright 2017 Statoil ASA.
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/>.
*/
#ifndef LINEARISED_OUTPUT_TABLE_HPP_INCLUDED
#define LINEARISED_OUTPUT_TABLE_HPP_INCLUDED
#include <cstddef>
#include <vector>
namespace Opm {
/// Manage tables of column data, possibly with sub-tables, all with
/// equal number of rows (i.e., with padding), and possibly with
/// multiple tables pertaining to multiple subsets (i.e., cell regions).
///
/// Mainly intended for use with the output facility for tabular data.
class LinearisedOutputTable
{
public:
/// Constructor.
///
/// \param[in] numTables Number of tables managed by internal
/// buffer. Typically corresponds to maximum value of a region
/// ID vector such as SATNUM, IMBNUM, or PVTNUM.
///
/// \param[in] numPrimary Number of primary look-up keys for the
/// tabular data managed by the internal buffer. Mostly relevant
/// (i.e., greater than one) for miscible oil ("PVTO") or
/// miscible gas ("PVTG") tables and typically corresponds to the
/// number of Rs/Rv entries from the TABDIMS keyword.
///
/// \param[in] numRows Number of rows in each sub-table
/// corresponding to a single primary look-up key. Typically the
/// number of nodes (e.g., NSSFUN or NPPVT) of the corresponding
/// input keyword.
///
/// \param[in] numCols Number of columns in each sub-table (and main
/// table). Typically 5.
LinearisedOutputTable(const std::size_t numTables,
const std::size_t numPrimary,
const std::size_t numRows,
const std::size_t numCols);
/// Retrieve iterator to start of \c numRows (contiguous) column
/// elements of a particular sub-table of a particular main table.
///
/// \param[in] tableID Numeric ID of main table for which to
/// retrieve a column. Must be strictly less than \c numTables
/// constructor argument.
///
/// \param[in] primID Numeric ID of primary look-up key (sub-table)
/// for which to retrieve a column. Must be strictly less than
/// \c numPrimary constructor argument.
///
/// \param[in] colID Numeric ID of column to be retrieved from
/// particular sub-table of particular main table. Must be
/// strictly less than \c numCols constructor argument.
///
/// \return Iterator to start of contiguous column elements.
std::vector<double>::iterator
column(const std::size_t tableID,
const std::size_t primID,
const std::size_t colID);
/// Read-only access to internal data buffer.
///
/// Mostly to support outputting all table data to external storage.
const std::vector<double>& getData() const;
/// Destructive access to internal data buffer.
///
/// Mostly to support outputting all table data to external storage.
///
/// \return \code std::move() \endcode of the internal data buffer.
std::vector<double> getDataDestructively();
private:
/// Internal buffer for tabular data.
std::vector<double> data;
/// Number of tables managed by \c data.
std::size_t numTables;
/// Number of primary look-up keys/sub-tables in \c data_.
std::size_t numPrimary;
/// Number of rows per sub-table in \c data_.
std::size_t numRows;
};
/// Apply piecewise linear differentiation (i.e., compute slopes) on a
/// set of dependent variables in a linearised output table.
///
namespace DifferentiateOutputTable {
/// Columnar data differentantiation table request.
///
/// Refers to the sub-table with a specific primary ID within a
/// specific table of a \c LinearisedOutputTable.
struct Descriptor {
/// Table ID--usually corresponds to the region ID of a
/// tabulated function pertaining to a specific region of a
/// simulation model.
std::size_t tableID{ 0 };
/// Primary ID--nontrivial (\c != 0) only for miscible PVT
/// tables for oil or gas in which case this entry refers to a
/// particular dissolved gas-oil ratio (Rs) or gas pressure
/// (Pg) node.
std::size_t primID{ 0 };
/// Number of active rows in this subtable.
std::size_t numActRows{ 0 };
};
/// Apply piecewise linear differentiation (i.e., compute slopes) on
/// a set of dependent variables in a linearised output table.
///
/// Assumes that the independent variable is stored in the first
/// column (column ID zero).
///
/// \param[in] numDependent Number of dependent variables. Usually
/// one or two. Dependent variables are assumed to be stored in
/// columns one through \p numDependent.
///
/// \param[in] desc Columnar data differentantiation table request.
/// Must refer to a particular sub-table of the linearised output
/// table.
///
/// \param[in,out] table Linearised output table. On input, column
/// zero contains the independent variable in each of \code
/// numActRows.size() \endcode sub-tables and columns one through
/// \p numDependent contain the dependent variables. On output,
/// columns \code numDependent + 1 \endcode through \code 2 *
/// numDependent \endcode contain the slopes of the dependent
/// variables.
///
/// In partcular, column \code numDependent + j \endcode for
/// \code j = 1..numDependent \endcode contains the derivatives
/// of column \c j with respect to column zero. We define the
/// slopes as
/// \code
/// s(i,j) = (c(i + 1, j) - c(i,j)) / (c(i + 1, 0) - c(i,0))
/// \endcode
/// for all \code i = 0 .. numActRows[k] - 2 \endcode (in table
/// \p k).
void calcSlopes(const std::size_t numDependent,
const Descriptor& desc,
LinearisedOutputTable& table);
} // DifferentiateOutputTable
} // Opm
#endif // LINEARISED_OUTPUT_TABLE_HPP_INCLUDED

View File

@ -0,0 +1,45 @@
/*
Copyright 2016 Statoil ASA.
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/>.
*/
#ifndef OPM_REGION_CACHE_HPP
#define OPM_REGION_CACHE_HPP
#include <vector>
namespace Opm {
class Eclipse3DProperties;
class Schedule;
class EclipseGrid;
namespace out {
class RegionCache {
public:
RegionCache() = default;
RegionCache(const Eclipse3DProperties& properties, const EclipseGrid& grid, const Schedule& schedule);
const std::vector<std::pair<std::string,size_t>>& completions( int region_id ) const;
private:
std::vector<std::pair<std::string,size_t>> completions_empty;
std::map<int , std::vector<std::pair<std::string,size_t>>> completion_map;
};
}
}
#endif

View File

@ -0,0 +1,97 @@
/*
Copyright (c) 2016 Statoil ASA
Copyright (c) 2013-2015 Andreas Lauser
Copyright (c) 2013 SINTEF ICT, Applied Mathematics.
Copyright (c) 2013 Uni Research AS
Copyright (c) 2015 IRIS AS
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/>.
*/
#ifndef RESTART_IO_HPP
#define RESTART_IO_HPP
#include <vector>
#include <map>
#include <opm/parser/eclipse/Units/UnitSystem.hpp>
#include <opm/parser/eclipse/EclipseState/Runspec.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Well.hpp>
#include <opm/output/data/Cells.hpp>
#include <opm/output/data/Solution.hpp>
#include <opm/output/data/Wells.hpp>
#include <opm/output/eclipse/RestartValue.hpp>
#include <ert/ecl/EclKW.hpp>
#include <ert/ecl/ecl_rsthead.h>
#include <ert/ecl/ecl_rst_file.h>
#include <ert/util/util.h>
namespace Opm {
class EclipseGrid;
class EclipseState;
class Phases;
class Schedule;
namespace RestartIO {
/*
The two loose functions RestartIO::save() and RestartIO::load() can
be used to save and load reservoir and well state from restart
files. Observe that these functions 'just do it', i.e. the checking
of which report step to load from, if output is enabled at all and
so on is handled by an outer scope.
If the filename corresponds to unified eclipse restart file,
i.e. UNRST the functions will seek correctly to the correct report
step, and truncate in the case of save. For any other filename the
functions will start reading and writing from file offset zero. If
the input filename does not correspond to a unified restart file
there is no consistency checking between filename and report step;
i.e. these calls:
load("CASE.X0010" , 99 , ...)
save("CASE.X0010" , 99 , ...)
will read and write to the file "CASE.X0010" - completely ignoring
the report step argument '99'.
*/
void save(const std::string& filename,
int report_step,
double seconds_elapsed,
data::Solution cells,
data::Wells wells,
const EclipseState& es,
const EclipseGrid& grid,
const Schedule& schedule,
std::map<std::string, std::vector<double>> extra_data = {},
bool write_double = false);
RestartValue load( const std::string& filename,
int report_step,
const std::map<std::string, RestartKey>& keys,
const EclipseState& es,
const EclipseGrid& grid,
const Schedule& schedule,
const std::map<std::string, bool>& extra_keys = {});
}
}
#endif

View File

@ -0,0 +1,96 @@
/*
Copyright (c) 2017 Statoil ASA
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/>.
*/
#ifndef RESTART_VALUE_HPP
#define RESTART_VALUE_HPP
#include <string>
#include <map>
#include <vector>
namespace Opm {
/*
Small convenience class used in the map passed to the
RestartIO::load( ) function. A class instance can be constructed
from a UnitSystem::measure value, which will default to a
required key, but it can also be constructed from a
two-parameter constructor, and then the required/not required
can controlled explicitly:
RestartIO::load(..., {{"PRESSURE" , UnitSystem::measure::pressure},
{"MAX_SWAT" , {UnitSystem::measure::identity, false}} )
The RestartKey( ) for pressure is implicitly created from
UnitSystem::measure::pressure and will be required, whereas the
MAX_SWAT keyword is optional.
*/
class RestartKey {
public:
UnitSystem::measure dim;
bool required = true;
explicit RestartKey( UnitSystem::measure _dim)
: dim(_dim)
{}
RestartKey( UnitSystem::measure _dim, bool _required)
: dim(_dim),
required(_required)
{}
};
/*
A simple struct - the only purpose is to facilitate return by value from the
RestartIO::load( ) function.
*/
struct RestartValue {
data::Solution solution;
data::Wells wells;
std::map<std::string,std::vector<double>> extra = {};
RestartValue(data::Solution sol, data::Wells wells_arg, std::map<std::string, std::vector<double>> extra_arg) :
solution(std::move(sol)),
wells(std::move(wells_arg)),
extra(std::move(extra_arg))
{
}
RestartValue(data::Solution sol, data::Wells wells_arg) :
solution(std::move(sol)),
wells(std::move(wells_arg))
{
}
};
}
#endif

View File

@ -0,0 +1,78 @@
/*
Copyright 2016 Statoil ASA.
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/>.
*/
#ifndef OPM_OUTPUT_SUMMARY_HPP
#define OPM_OUTPUT_SUMMARY_HPP
#include <string>
#include <vector>
#include <unordered_map>
#include <ert/ecl/ecl_sum.h>
#include <ert/ecl/Smspec.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/EclipseGrid.hpp>
#include <opm/output/data/Wells.hpp>
#include <opm/output/eclipse/RegionCache.hpp>
namespace Opm {
class EclipseState;
class Schedule;
class SummaryConfig;
class EclipseGrid;
class Schedule;
namespace out {
class Summary {
public:
Summary( const EclipseState&, const SummaryConfig&, const EclipseGrid&, const Schedule& );
Summary( const EclipseState&, const SummaryConfig&, const EclipseGrid&, const Schedule&, const std::string& );
Summary( const EclipseState&, const SummaryConfig&, const EclipseGrid&, const Schedule&, const char* basename );
void add_timestep(int report_step,
double secs_elapsed,
const EclipseState& es,
const Schedule& schedule,
const data::Wells&,
const std::map<std::string, double>& single_values,
const std::map<std::string, std::vector<double>>& region_values = {},
const std::map<std::pair<std::string, int>, double>& block_values = {});
void write();
~Summary();
private:
class keyword_handlers;
const EclipseGrid& grid;
out::RegionCache regionCache;
ERT::ert_unique_ptr< ecl_sum_type, ecl_sum_free > ecl_sum;
std::unique_ptr< keyword_handlers > handlers;
const ecl_sum_tstep_type* prev_tstep = nullptr;
double prev_time_elapsed = 0;
};
}
}
#endif //OPM_OUTPUT_SUMMARY_HPP

View File

@ -0,0 +1,121 @@
/*
Copyright 2016 Statoil ASA.
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/>.
*/
#ifndef OUTPUT_TABLES_HPP
#define OUTPUT_TABLES_HPP
#include <vector>
#include <ert/ecl/FortIO.hpp>
#include <ert/ecl/EclKW.hpp>
#include <opm/parser/eclipse/EclipseState/Tables/PvtoTable.hpp>
#include <opm/parser/eclipse/EclipseState/Tables/PvtgTable.hpp>
#include <opm/parser/eclipse/EclipseState/Tables/FlatTable.hpp>
namespace Opm {
class UnitSystem;
class EclipseState;
class Tables {
public:
explicit Tables( const UnitSystem& units);
void addPVTO(const std::vector<PvtoTable>& pvtoTables);
void addPVTG(const std::vector<PvtgTable>& pvtgTables);
void addPVTW(const PvtwTable& pvtwTable);
void addDensity(const DensityTable& density);
/// Add normalised saturation function tables to INIT file's TAB
/// vector.
///
/// \param[in] es Valid \c EclipseState object with accurate RUNSPEC
/// information on active phases and table dimensions ("TABDIMS").
void addSatFunc(const EclipseState& es);
/// Acquire read-only reference to internal TABDIMS vector.
const std::vector<int>& tabdims() const;
/// Acquire read-only reference to internal TAB vector.
const std::vector<double>& tab() const;
private:
/// Convention for units of measure of the result set.
const UnitSystem& units;
/// Offset and size information for the tabular data.
std::vector<int> m_tabdims;
/// Linearised tabular data of PVT and saturation functions.
std::vector<double> data;
void addData(const std::size_t offset_index,
const std::vector<double>& new_data);
/// Add saturation function tables corresponding to family I (SGOF,
/// SWOF) to the tabular data (TABDIMS and TAB vectors).
///
/// \param[in] es Valid \c EclipseState object with accurate table
/// dimensions ("TABDIMS" keyword) and an initialised \c
/// TableManager sub-object.
///
/// \param[in] gas Whether or not gas is active the current run.
///
/// \param[in] oil Whether or not oil is active the current run.
///
/// \param[in] wat Whether or not water is active the current run.
void addSatFunc_FamilyOne(const EclipseState& es,
const bool gas,
const bool oil,
const bool wat);
/// Add saturation function tables corresponding to family II (SGFN,
/// SOF{2,3}, SWFN) to the tabular data (TABDIMS and TAB vectors).
///
/// \param[in] es Valid \c EclipseState object with accurate table
/// dimensions ("TABDIMS" keyword) and an initialised \c
/// TableManager sub-object.
///
/// \param[in] gas Whether or not gas is active the current run.
///
/// \param[in] oil Whether or not oil is active the current run.
///
/// \param[in] wat Whether or not water is active the current run.
void addSatFunc_FamilyTwo(const EclipseState& es,
const bool gas,
const bool oil,
const bool wat);
};
/// Emit normalised tabular information (TABDIMS and TAB vectors) to
/// ECL-like result set file (typically INIT file).
///
/// \param[in] tables Collection of normalised tables. Its \code
/// tabdims() \endcode and \code tab() \endcode vectors will be
/// emitted to \p fortio as ECL keywords "TABDIMS" and "TAB",
/// respectively.
///
/// \param[in,out] fortio ECL-like result set file. Typically
/// corresponds to a preopened stream attached to the INIT file.
void fwrite(const Tables& tables,
ERT::FortIO& fortio);
}
#endif

View File

@ -0,0 +1,245 @@
/*
Copyright 2016 Statoil ASA.
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/>.
*/
#ifndef ECLFILESCOMPARATOR_HPP
#define ECLFILESCOMPARATOR_HPP
#include <vector>
#include <string>
struct ecl_file_struct; //!< Prototype for eclipse file struct, from ERT library.
typedef struct ecl_file_struct ecl_file_type;
struct ecl_grid_struct; //!< Prototype for eclipse grid struct, from ERT library.
typedef struct ecl_grid_struct ecl_grid_type;
struct ecl_kw_struct; //!< Prototype for eclipse keyword struct, from ERT library.
typedef struct ecl_kw_struct ecl_kw_type;
/*! \brief Deviation struct.
\details The member variables are default initialized to -1,
which is an invalid deviation value.
*/
struct Deviation {
double abs = -1; //!< Absolute deviation
double rel = -1; //!< Relative deviation
};
/*! \brief A class for comparing ECLIPSE files.
\details ECLFilesComparator opens ECLIPSE files
(unified restart, initial and RFT in addition to grid file)
from two simulations. This class has only the functions
printKeywords() and printKeywordsDifference(), in addition to a
couple of get-functions: the comparison logic is implemented in
the subclasses RegressionTest and IntegrationTest. */
class ECLFilesComparator {
private:
int file_type;
double absTolerance = 0;
double relTolerance = 0;
protected:
ecl_file_type* ecl_file1 = nullptr;
ecl_grid_type* ecl_grid1 = nullptr;
ecl_file_type* ecl_file2 = nullptr;
ecl_grid_type* ecl_grid2 = nullptr;
std::vector<std::string> keywords1, keywords2;
bool throwOnError = true; //!< Throw on first error
mutable size_t num_errors = 0;
//! \brief Checks if the keyword exists in both cases.
//! \param[in] keyword Keyword to check.
//! \details If the keyword does not exist in one of the cases, the function throws an exception.
void keywordValidForComparing(const std::string& keyword) const;
//! \brief Stores keyword data for a given occurrence
//! \param[out] ecl_kw1 Pointer to a ecl_kw_type, which stores keyword data for first case given the occurrence.
//! \param[out] ecl_kw2 Pointer to a ecl_kw_type, which stores keyword data for second case given the occurrence.
//! \param[in] keyword Which keyword to consider.
//! \param[in] occurrence Which keyword occurrence to consider.
//! \details This function stores keyword data for the given keyword and occurrence in #ecl_kw1 and #ecl_kw2, and returns the number of cells (for which the keyword has a value at the occurrence). If the number of cells differ for the two cases, an exception is thrown.
unsigned int getEclKeywordData(ecl_kw_type*& ecl_kw1, ecl_kw_type*& ecl_kw2, const std::string& keyword, int occurrence1, int occurrence2) const;
//! \brief Prints values for a given keyword, occurrence and cell
//! \param[in] keyword Which keyword to consider.
//! \param[in] occurrence Which keyword occurrence to consider.
//! \param[in] cell Which cell occurrence to consider (numbered by global index).
//! \param[in] value1 Value for first file, the data type can be bool, int, double or std::string.
//! \param[in] value2 Value for second file, the data type can be bool, int, double or std::string.
//! \details Templatefunction for printing values when exceptions are thrown. The function is defined for bool, int, double and std::string.
template <typename T>
void printValuesForCell(const std::string& keyword, int occurrence1, int occurrence2, size_t cell, const T& value1, const T& value2) const;
public:
//! \brief Open ECLIPSE files and set tolerances and keywords.
//! \param[in] file_type Specifies which filetype to be compared, possible inputs are UNRSTFILE, INITFILE and RFTFILE.
//! \param[in] basename1 Full path without file extension to the first case.
//! \param[in] basename2 Full path without file extension to the second case.
//! \param[in] absTolerance Tolerance for absolute deviation.
//! \param[in] relTolerance Tolerance for relative deviation.
//! \details The content of the ECLIPSE files specified in the input is stored in the ecl_file_type and ecl_grid_type member variables. In addition the keywords and absolute and relative tolerances (member variables) are set. If the constructor is unable to open one of the ECLIPSE files, an exception will be thrown.
ECLFilesComparator(int file_type, const std::string& basename1, const std::string& basename2, double absTolerance, double relTolerance);
//! \brief Closing the ECLIPSE files.
~ECLFilesComparator();
//! \brief Set whether to throw on errors or not.
void throwOnErrors(bool dothrow) { throwOnError = dothrow; }
//! \brief Returns the number of errors encountered in the performed comparisons.
size_t getNoErrors() const { return num_errors; }
//! \brief Returns the ECLIPSE filetype of this
int getFileType() const {return file_type;}
//! \brief Returns the absolute tolerance stored as a private member variable in the class
double getAbsTolerance() const {return absTolerance;}
//! \brief Returns the relative tolerance stored as a private member variable in the class
double getRelTolerance() const {return relTolerance;}
//! \brief Print all keywords and their respective Eclipse type for the two input cases.
void printKeywords() const;
//! \brief Print common and uncommon keywords for the two input cases.
void printKeywordsDifference() const;
//! \brief Calculate deviations for two values.
//! \details Using absolute values of the input arguments: If one of the values are non-zero, the Deviation::abs returned is the difference between the two input values. In addition, if both values are non-zero, the Deviation::rel returned is the absolute deviation divided by the largest value.
static Deviation calculateDeviations(double val1, double val2);
//! \brief Calculate median of a vector.
//! \details Returning the median of the input vector, i.e. the middle value of the sorted vector if the number of elements is odd or the mean of the two middle values if the number of elements are even.
static double median(std::vector<double> vec);
//! \brief Calculate average of a vector.
//! \details Returning the average of the input vector, i.e. the sum of all values divided by the number of elements.
static double average(const std::vector<double>& vec);
};
/*! \brief A class for executing a regression test for two ECLIPSE files.
\details This class inherits from ECLFilesComparator, which opens and
closes the input cases and stores keywordnames.
The three public functions gridCompare(), results() and
resultsForKeyword() can be invoked to compare griddata
or keyworddata for all keywords or a given keyword (resultsForKeyword()).
*/
class RegressionTest: public ECLFilesComparator {
private:
// These vectors store absolute and relative deviations, respecively. Note that they are whiped clean for every new keyword comparison.
std::vector<double> absDeviation, relDeviation;
// Keywords which should not contain negative values, i.e. uses allowNegativeValues = false in deviationsForCell():
const std::vector<std::string> keywordDisallowNegatives = {"SGAS", "SWAT", "PRESSURE"};
// Only compare last occurrence
bool onlyLastOccurrence = false;
// Prints results stored in absDeviation and relDeviation.
void printResultsForKeyword(const std::string& keyword) const;
// Function which compares data at specific occurrences and for a specific keyword type. The functions takes two occurrence inputs to also be able to
// compare keywords which are shifted relative to each other in the two files. This is for instance handy when running flow with restart from different timesteps,
// and comparing the last timestep from the two runs.
void boolComparisonForOccurrence(const std::string& keyword, int occurrence1, int occurrence2) const;
void charComparisonForOccurrence(const std::string& keyword, int occurrence1, int occurrence2) const;
void intComparisonForOccurrence(const std::string& keyword, int occurrence1, int occurrence2) const;
void doubleComparisonForOccurrence(const std::string& keyword, int occurrence1, int occurrence2);
// deviationsForCell throws an exception if both the absolute deviation AND the relative deviation
// are larger than absTolerance and relTolerance, respectively. In addition,
// if allowNegativeValues is passed as false, an exception will be thrown when the absolute value
// of a negative value exceeds absTolerance. If no exceptions are thrown, the absolute and relative deviations are added to absDeviation and relDeviation.
void deviationsForCell(double val1, double val2, const std::string& keyword, int occurrence1, int occurrence2, size_t cell, bool allowNegativeValues = true);
public:
//! \brief Sets up the regression test.
//! \param[in] file_type Specifies which filetype to be compared, possible inputs are UNRSTFILE, INITFILE and RFTFILE.
//! \param[in] basename1 Full path without file extension to the first case.
//! \param[in] basename2 Full path without file extension to the second case.
//! \param[in] absTolerance Tolerance for absolute deviation.
//! \param[in] relTolerance Tolerance for relative deviation.
//! \details This constructor only calls the constructor of the superclass, see the docs for ECLFilesComparator for more information.
RegressionTest(int file_type, const std::string& basename1, const std::string& basename2, double absTolerance, double relTolerance):
ECLFilesComparator(file_type, basename1, basename2, absTolerance, relTolerance) {}
//! \brief Option to only compare last occurrence
void setOnlyLastOccurrence(bool onlyLastOccurrenceArg) {this->onlyLastOccurrence = onlyLastOccurrenceArg;}
//! \brief Compares grid properties of the two cases.
// gridCompare() checks if both the number of active and global cells in the two cases are the same. If they are, all cells are looped over to calculate the cell volume deviation for the two cases. If the both the relative and absolute deviation exceeds the tolerances, an exception is thrown.
void gridCompare() const;
//! \brief Calculates deviations for all keywords.
// This function checks if the number of keywords of the two cases are equal, and if it is, resultsForKeyword() is called for every keyword. If not, an exception is thrown.
void results();
//! \brief Calculates deviations for a specific keyword.
//! \param[in] keyword Keyword which should be compared, if this keyword is absent in one of the cases, an exception will be thrown.
//! \details This function loops through every report step and every cell and compares the values for the given keyword from the two input cases. If the absolute or relative deviation between the two values for each step exceeds both the absolute tolerance and the relative tolerance (stored in ECLFilesComparator), an exception is thrown. In addition, some keywords are marked for "disallow negative values" -- these are SGAS, SWAT and PRESSURE. An exception is thrown if a value of one of these keywords is both negative and has an absolute value larger than the absolute tolerance. If no exceptions are thrown, resultsForKeyword() uses the private member funtion printResultsForKeyword to print the average and median deviations.
void resultsForKeyword(const std::string& keyword);
};
/*! \brief A class for executing a integration test for two ECLIPSE files.
\details This class inherits from ECLFilesComparator, which opens and closes
the input cases and stores keywordnames. The three public functions
equalNumKeywords(), results() and resultsForKeyword() can be invoked
to compare griddata or keyworddata for all keywords or a given
keyword (resultsForKeyword()).
*/
class IntegrationTest: public ECLFilesComparator {
private:
std::vector<double> cellVolumes; //!< Vector of cell volumes in second input case (indexed by global index)
std::vector<double> initialCellValues; //!< Keyword values for all cells at first occurrence (index by global index)
// These are the only keywords which are compared, since SWAT should be "1 - SOIL - SGAS", this keyword is omitted.
const std::vector<std::string> keywordWhitelist = {"SGAS", "SWAT", "PRESSURE"};
void setCellVolumes();
void initialOccurrenceCompare(const std::string& keyword);
void occurrenceCompare(const std::string& keyword, int occurrence) const;
public:
//! \brief Sets up the integration test.
//! \param[in] basename1 Full path without file extension to the first case.
//! \param[in] basename2 Full path without file extension to the second case.
//! \param[in] absTolerance Tolerance for absolute deviation.
//! \param[in] relTolerance Tolerance for relative deviation.
//! \details This constructor calls the constructor of the superclass, with input filetype unified restart. See the docs for ECLFilesComparator for more information.
IntegrationTest(const std::string& basename1, const std::string& basename2, double absTolerance, double relTolerance);
//! \brief Checks if a keyword is supported for comparison.
//! \param[in] keyword Keyword to check.
bool elementInWhitelist(const std::string& keyword) const;
//! \brief Checks if the number of keywords equal in the two input cases.
//! \param[in] keyword Keyword to check.
void equalNumKeywords() const;
//! \brief Finds deviations for all supported keywords.
//! \details results() loops through all supported keywords for integration test (defined in keywordWhitelist -- this is SGAS, SWAT and PRESSURE) and calls resultsForKeyword() for each keyword.
void results();
//! \brief Finds deviations for a specific keyword.
//! \param[in] keyword Keyword to check.
/*! \details First, resultsForKeyword() checks if the keyword exits in both cases, and if the number of keyword occurrences in the two cases differ. If these tests fail, an exception is thrown. Then deviaitons are calculated as described below for each occurrence, and an exception is thrown if the relative error ratio \f$E\f$ is larger than the relative tolerance.
* Calculation:\n
* Let the keyword values for occurrence \f$n\f$ and cell \f$i\f$ be \f$p_{n,i}\f$ and \f$q_{n,i}\f$ for input case 1 and 2, respectively.
* Consider first the initial occurrence (\f$n=0\f$). The function uses the second cases as reference, and calculates the volume weighted sum of \f$q_{0,i}\f$ over all cells \f$i\f$:
* \f[ S_0 = \sum_{i} q_{0,i} v_i \f]
* where \f$v_{i}\f$ is the volume of cell \f$i\f$ in case 2. Then, the deviations between the cases for each cell are calculated:
* \f[ \Delta = \sum_{i} |p_{0,i} - q_{0,i}| v_i.\f]
* The error ratio is then \f$E = \Delta/S_0\f$.\n
* For all other occurrences \f$n\f$, the deviation value \f$\Delta\f$ is calculated the same way, but the total value \f$S\f$ is calculated relative to the initial occurrence total \f$S_0\f$:
* \f[ S = \sum_{i} |q_{n,i} - q_{0,i}| v_i. \f]
* The error ratio is \f$ E = \Delta/S\f$. */
void resultsForKeyword(const std::string& keyword);
};
#endif

View File

@ -0,0 +1,180 @@
/*
Copyright 2016 Statoil ASA.
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/>.
*/
#ifndef SUMMARYCOMPARATOR_HPP
#define SUMMARYCOMPARATOR_HPP
#include <iostream>
#include <iomanip>
#include <vector>
#include <algorithm>
#include <string>
// helper macro to handle error throws or not
#define HANDLE_ERROR(type, message) \
{ \
if (throwOnError) \
OPM_THROW(type, message); \
else \
std::cerr << message << std::endl; \
}
//! \brief Prototyping struct, encapsuling the stringlist libraries.
struct stringlist_struct;
typedef struct stringlist_struct stringlist_type;
//! \brief Prototyping struct, encapsuling the ert libraries.
struct ecl_sum_struct;
typedef struct ecl_sum_struct ecl_sum_type;
//! \brief Struct for storing the deviation between two values.
struct Deviation {
double abs = 0;//!< The absolute deviation
double rel = 0; //!< The relative deviation
};
class SummaryComparator {
private:
double absoluteTolerance = 0; //!< The maximum absolute deviation that is allowed between two values.
double relativeTolerance = 0; //!< The maximum relative deviation that is allowed between twi values.
protected:
ecl_sum_type * ecl_sum1 = nullptr; //!< Struct that contains file1
ecl_sum_type * ecl_sum2 = nullptr; //!< Struct that contains file2
ecl_sum_type * ecl_sum_fileShort = nullptr; //!< For keeping track of the file with most/fewest timesteps
ecl_sum_type * ecl_sum_fileLong = nullptr; //!< For keeping track of the file with most/fewest timesteps
stringlist_type* keys1 = nullptr; //!< For storing all the keywords of file1
stringlist_type* keys2 = nullptr; //!< For storing all the keywords of file2
stringlist_type * keysShort = nullptr; //!< For keeping track of the file with most/fewest keywords
stringlist_type * keysLong = nullptr; //!< For keeping track of the file with most/fewest keywords
const std::vector<double> * referenceVec = nullptr; //!< For storing the values of each time step for the file containing the fewer time steps.
const std::vector<double> * referenceDataVec = nullptr; //!< For storing the data corresponding to each time step for the file containing the fewer time steps.
const std::vector<double> * checkVec = nullptr; //!< For storing the values of each time step for the file containing the more time steps.
const std::vector<double> * checkDataVec = nullptr; //!< For storing the data values corresponding to each time step for the file containing the more time steps.
bool printKeyword = false; //!< Boolean value for choosing whether to print the keywords or not
bool printSpecificKeyword = false; //!< Boolean value for choosing whether to print the vectors of a keyword or not
bool throwOnError = true; //!< Throw on first error
//! \brief Calculate deviation between two data values and stores it in a Deviation struct.
//! \param[in] refIndex Index in reference data
//! \param[in] checkindex Index in data to be checked.
//! \param[out] dev Holds the result from the comparison on return.
//! \details Uses the #referenceVec as basis, and checks its values against the values in #checkDataVec. The function is reccursive, and will update the iterative index j of the #checkVec until #checkVec[j] >= #referenceVec[i]. \n When #referenceVec and #checkVec have the same time value (i.e. #referenceVec[i] == #checkVec[j]) a direct comparison is used, \n when this is not the case, when #referenceVec[i] do not excist as an element in #checkVec, a value is generated, either by the principle of unit step or by interpolation.
void getDeviation(size_t refIndex, size_t &checkIndex, Deviation &dev);
//! \brief Figure out which data file contains the most / less timesteps and assign member variable pointers accordingly.
//! \param[in] timeVec1 Data from first file
//! \param[in] timeVec2 Data from second file
//! \details Figure out which data file that contains the more/fewer time steps and assigns the private member variable pointers #ecl_sum_fileShort / #ecl_sum_fileLong to the correct data sets #ecl_sum1 / #ecl_sum2.
void setDataSets(const std::vector<double>& timeVec1,
const std::vector<double>& timeVec2);
//! \brief Reads in the time values of each time step.
//! \param[in] timeVec1 Vector for storing the time steps from file1
//! \param[in] timeVec2 Vector for storing the time steps from file2
void setTimeVecs(std::vector<double> &timeVec1,std::vector<double> &timeVec2);
//! \brief Read the data for one specific keyword into two separate vectors.
//! \param[in] dataVec1 Vector for storing the data for one specific keyword from file1
//! \param[in] dataVec2 Vector for storing the data for one specific keyword from file2
//! \details The two data files do not necessarily have the same amount of data values, but the values must correspond to the same interval in time. Thus possible to interpolate values.
void getDataVecs(std::vector<double> &dataVec1,
std::vector<double> &dataVec2, const char* keyword);
//! \brief Sets one data set as a basis and the other as values to check against.
//! \param[in] timeVec1 Used to figure out which dataset that have the more/fewer time steps.
//! \param[in] timeVec2 Used to figure out which dataset that have the more/fewer time steps.
//! \param[in] dataVec1 For assiging the the correct pointer to the data vector.
//! \param[in] dataVec2 For assiging the the correct pointer to the data vector.
//! \details Figures out which time vector that contains the fewer elements. Sets this as #referenceVec and its corresponding data as #referenceDataVec. \n The remaining data set is set as #checkVec (the time vector) and #checkDataVec.
void chooseReference(const std::vector<double> &timeVec1,
const std::vector<double> &timeVec2,
const std::vector<double> &dataVec1,
const std::vector<double> &dataVec2);
//! \brief Returns the relative tolerance.
double getRelTolerance(){return this->relativeTolerance;}
//! \brief Returns the absolute tolerance.
double getAbsTolerance(){return this->absoluteTolerance;}
//! \brief Returns the unit of the values of a keyword
//! \param[in] keyword The keyword of interest.
//! \param[out] ret The unit of the keyword as a const char*.
const char* getUnit(const char* keyword);
//! \brief Prints the units of the files.
void printUnits();
//! \brief Prints the keywords of the files.
//! \details The function prints first the common keywords, than the keywords that are different.
void printKeywords();
//! \brief Prints the summary vectors from the two files.
//! \details The function requires that the summary vectors of the specific file have been read into the member variables referenceVec etc.
void printDataOfSpecificKeyword(const std::vector<double>& timeVec1,
const std::vector<double>& timeVec2,
const char* keyword);
public:
//! \brief Creates an SummaryComparator class object
//! \param[in] basename1 Path to file1 without extension.
//! \param[in] basename1 Path to file2 without extension.
//! \param[in] absoluteTolerance The absolute tolerance which is to be used in the test.
//! \param[in] relativeTolerance The relative tolerance which is to be used in the test.
//! \details The constructor creates an object of the class, and openes the files, an exception is thrown if the opening of the files fails. \n It creates stringlists, in which keywords are to be stored, and figures out which keylist that contains the more/less keywords. \n Also the private member variables aboluteTolerance and relativeTolerance are set.
SummaryComparator(const char* basename1, const char* basename2, double absoluteTolerance, double relativeTolerance);
//! \brief Destructor
//! \details The destructor takes care of the allocated memory in which data has been stored.
~SummaryComparator();
//! \brief Calculates the deviation between two values
//! \param[in] val1 The first value of interest.
//! \param[in] val2 The second value if interest.
//! \param[out] ret Returns a Deviation struct.
//! \details The function takes two values, calculates the absolute and relative deviation and returns the result as a Deviation struct.
static Deviation calculateDeviations( double val1, double val2);
//! \brief Sets the private member variable printKeywords
//! \param[in] boolean Boolean value
//! \details The function sets the private member variable printKeywords. When it is true the function printKeywords will be called.
void setPrintKeywords(bool boolean){this->printKeyword = boolean;}
//! \brief Sets the private member variable printSpecificKeyword
//! \param[in] boolean Boolean value
//! \details The function sets the private member variable printSpecificKeyword. When true, the summary vector of the keyword for both files will be printed.
void setPrintSpecificKeyword(bool boolean){this->printSpecificKeyword = boolean;}
//! \brief Unit step function
//! \param[in] value The input value should be the last know value
//! \param[out] ret Return the unit-step-function value.
//! \details In this case: The unit step function is used when the data from the two data set, which is to be compared, don't match in time. \n The unit step function is then to be called on the #checkDataVec 's value at the last time step which is before the time of comparison.
//! \brief Returns a value based on the unit step principle.
static double unitStep(double value){return value;}
//! \brief Set whether to throw on errors or not.
void throwOnErrors(bool dothrow) { throwOnError = dothrow; }
};
#endif

View File

@ -0,0 +1,216 @@
/*
Copyright 2016 Statoil ASA.
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 <opm/test_util/summaryComparator.hpp>
//! \brief Struct for storing the total area under a graph.
//! \details Used when plotting summary vector against time. In most cases this represents a volume.
struct WellProductionVolume{
double total=0; //!< The total area under the graph when plotting the summary vector against time. In most cases the total production volume.
double error=0; //!< The total area under the graph when plotting the deviation vector against time. In most cases the total error volume.
//! \brief Overloaded operator
//! \param[in] rhs WellProductionVolume struct
WellProductionVolume& operator+=(const WellProductionVolume& rhs){
this->total += rhs.total;
this->error += rhs.error;
return *this;
}
};
//! \details The class inherits from the SummaryComparator class, which takes care of all file reading. \n The IntegrationTest class compares values from the two different files and throws exceptions when the deviation is unsatisfying.
class IntegrationTest: public SummaryComparator {
private:
bool allowSpikes = false; //!< Boolean value, when true checkForSpikes is included as a sub test in the integration test. By default: false.
bool findVolumeError = false; //!< Boolean value, when true volumeErrorCheck() is included as a sub test in the integration test. By default: false.
bool allowDifferentAmountOfKeywords = true; //!< Boolean value, when false the integration test will check wheter the two files have the same amount of keywords. \nIf they don't, an exception will be thrown. By default: true.
bool findVectorWithGreatestErrorRatio = false; //!< Boolean value, when true the integration test will find the vector that has the greatest error ratio. By default: false.
bool oneOfTheMainVariables = false; //!< Boolean value, when true the integration test will only check for one of the primary variables (WOPR, WGPR, WWPR. WBHP), which will be specified by user. By default: false.
bool throwExceptionForTooGreatErrorRatio = true; //!< Boolean value, when true any volume error ratio that exceeds the relativeTolerance will cause an exception to be thrown. By default: true.
std::string mainVariable; //!< String variable, where the name of the main variable of interest (one of WOPR, WBHP, WWPR, WGPR) is stored. Can be empty.
int spikeLimit = 13370; //!< The limit for how many spikes to allow in the data set of a certain keyword. By default: Set to a high number, \n should not trig the (if spikeOccurrences > spikeLimit){ // throw exception }.
WellProductionVolume WOP; //!< WellProductionVolume struct for storing the total production volume and total error volume of all the keywords which start with WOPR
WellProductionVolume WWP; //!< WellProductionVolume struct for storing the total production volume and total error volume of all the keywords which start with WWPR
WellProductionVolume WGP;//!< WellProductionVolume struct for storing the total production volume and total error volume of all the keywords which start with WGPR
WellProductionVolume WBHP; //!< WellProductionVolume struct for storing the value of the area under the graph when plotting summary vector/deviation vector against time.This is for keywords starting with WBHP. \nNote: the name of the struct may be misleading, this is not an actual volume.
//! \brief The function gathers the correct data for comparison for a specific keyword
//! \param[in] timeVec1 A std::vector<double> that contains the time steps of file 1.
//! \param[in] timeVec2 A std::vector<double> that contains the time steps of file 2.
//! \param[in] keyword The keyword of interest
//! \details The function requires an outer loop which iterates over the keywords of the files. It prepares an integration test by gathering the data, stroing it into two vectors, \n deciding which is to be used as a reference/basis and calling the test function.
void checkForKeyword(const std::vector<double>& timeVec1,
const std::vector<double>& timeVec2, const char* keyword);
//! \brief The function compares the volume error to the total production volume of a certain type of keyword.
//! param[in] keyword The keyword of interest.
//! \details The function takes in a keyword and checks if it is of interest. Only keywords which say something about the well oil production, well water production, \n well gas production and the well BHP are of interest. The function sums up the total production in the cases where it is possible, \n and sums up the error volumes by a trapezoid integration method. The resulting values are stored in member variable structs of type WellProductionVolume, and double variables. For proper use of the function all the keywords of the file should be checked. This is satisfied if it is called by checkForKeyword.
void volumeErrorCheck(const char* keyword);
//! \brief The function calculates the total production volume and total error volume of a specific keyword
//! \param[in] timeVec1 A std::vector<double> that contains the time steps of file 1.
//! \param[in] timeVec2 A std::vector<double> that contains the time steps of file 2.
//! \param[in] keyword The keyword of interest
//! \param[out] ret Returns a WellProductionWolume struct
//! \details The function reads the data from the two files into the member variable vectors (of the super class). It returns a WellProductionVolume struct calculated from the vectors corresponding to the keyword.
WellProductionVolume getSpecificWellVolume(const std::vector<double>& timeVec1,
const std::vector<double>& timeVec2,
const char* keyword);
//! \brief The function is a regression test which allows spikes.
//! \param[in] keyword The keyword of interest, the keyword the summary vectors "belong" to.
//! \details The function requires the protected member variables referenceVec, referenceDataVec, checkVec and checkDataVec to be stored with data, which is staisfied if it is called by checkForKeyword. \n It compares the two vectors value by value, and if the deviation is unsatisfying, the errorOccurrenceCounter is incremented. If the errorOccurrenceCounter becomes greater than the errorOccurrenceLimit, \n a exception is thrown. The function will allow spike values, however, if two values in a row exceed the deviation limit, they are no longer spikes, and an exception is thrown.
void checkWithSpikes(const char* keyword);
//! \brief Caluculates a deviation, throws exceptions and writes and error message.
//! \param[in] deviation Deviation struct
//! \param[out] int Returns 0/1, depending on wheter the deviation exceeded the limit or not.
//! \details The function checks the values of the Deviation struct against the absolute and relative tolerance, which are private member values of the super class. \n When comparing against the relative tolerance an additional term is added, the absolute deviation has to be greater than 1e-6 for the function to throw an exception. \n When the deviations are too great, the function returns 1.
int checkDeviation(const Deviation& deviation);
//! \brief Calculates the keyword's total production volume and error volume
//! \param[in] keyword The keyword of interest.
//! \param[out] wellProductionVolume A struct containing the total production volume and the total error volume.
//! \details The function calculates the total production volume and total error volume of a keyword, by the trapezoid integral method. \n The function throws and exception if the total error volume is negative. The function returns the results as a struct.
WellProductionVolume getWellProductionVolume(const char* keyword);
//! \brief The function function works properly when the private member variables are set (after running the integration test which findVolumeError = true). \n It prints out the total production volume, the total error volume and the error ratio.
void evaluateWellProductionVolume();
//! \brief The function calculates the total production volume and total error volume
//! \param keyword The keyword of interest
//! \details The function uses the data that is stored in the member variable vectors. It calculates the total production volume \n and the total error volume of the specified keyword, and adds the result to the private member WellProductionVolume variables of the class.
void updateVolumeError(const char* keyword);
//! \brief Finds the keyword which has the greates error volume ratio
//! \param[in] volume WellProductionVolume struct which contains the data used for comparison
//! \param[in] greatestRatio Double value taken in by reference. Stores the greatest error ratio value.
//! \param[in] currentKeyword The keyword that is under evaluation
//! \param[in] greatestErrorRatio String which contains the name of the keyword which has the greatest error ratio
//! \details The function requires an outer loop which iterates over the keywords in the files, and calls the function for each keyword. \nThe valiables double greatestRatio and std::string keywordWithTheGreatestErrorRatio must be declared outside the loop. \nWhen the current error ratio is greater than the value stored in greatestRatio, the gratestRatio value is updated with the current error ratio.
void findGreatestErrorRatio(const WellProductionVolume& volume,
double &greatestRatio,
const char* currentKeyword,
std::string &greatestErrorRatio);
#if 0
//! \brief Checks whether the unit of the two data vectors is the same
//! \param[in] keyword The keyword of interest
//! \param[out] boolean True/false, depending on whether the units are equal or not
bool checkUnits(const char* keyword);
#endif
public:
//! \brief Constructor, creates an object of IntegrationTest class.
//! \param[in] basename1 Path to file1 without extension.
//! \param[in] basename1 Path to file2 without extension.
//! \param[in] atol The absolute tolerance which is to be used in the test.
//! \param[in] rtol The relative tolerance which is to be used in the test.
//! \details The constructor calls the constructor of the super class.
IntegrationTest(const char* basename1, const char* basename2,
double atol, double rtol) :
SummaryComparator(basename1, basename2, atol, rtol) {}
//! \brief This function sets the private member variable allowSpikes.
//! \param[in] allowSpikes Boolean value
//! \details When allowSpikes is true, the integration test checkWithSpikes is excecuted.
void setAllowSpikes(bool allowSpikesArg){this->allowSpikes = allowSpikesArg;}
//! \brief This function sets the private member variable findVolumeError.
//! \param[in] findVolumeError Boolean value
//! \details When findVolumeError is true, the integration test volumeErrorCheck and the function evaluateWellProductionVolume are excecuted.
void setFindVolumeError(bool findVolumeErrorArg){this->findVolumeError = findVolumeErrorArg;}
//! \brief This function sets the private member variable oneOfTheMainVariables
//! \param[in] oneOfTheMainVariables Boolean value
//! \details When oneOfTheMainVariables is true, the integration test runs the substest volumeErrorCheckForOneSpecificVariable.
void setOneOfTheMainVariables(bool oneOfTheMainVariablesArg){this->oneOfTheMainVariables = oneOfTheMainVariablesArg;}
//! \brief This function sets the member variable string #mainVariable
//! \param[in] mainVar This is the string should contain one of the main variables. e.g. WOPR
void setMainVariable(std::string mainVar){this->mainVariable = mainVar;}
//! \brief This function sets the private member variable spikeLimit.
//! \param[in] lim The value which the spike limit is to be given.
void setSpikeLimit(int lim){this->spikeLimit = lim;}
//! \brief This function sets the private member variable findVectorWithGreatestErrorRatio
//! \param[in] findVolumeError Boolean value
//! \details When findVectorWithGreatestErrorRatio is true, the integration test will print the vector with the greatest error ratio.
void setFindVectorWithGreatestErrorRatio(bool boolean){this->findVectorWithGreatestErrorRatio = boolean;}
//! \brief This function sets the private member variable allowDifferentAmountsOfKeywords
//! \param[in] boolean Boolean value
//! \details When allowDifferentAmountOfKeywords is false, the amount of kewyord in the two files will be compared. \nIf the number of keywords are different an exception will be thrown.
void setAllowDifferentAmountOfKeywords(bool boolean){this->allowDifferentAmountOfKeywords = boolean;}
//! \brief This function sets the private member variable throwExceptionForTooGreatErrorRatio
//! \param[in] boolean Boolean value
//! \details When throwExceptionForTooGreatErrorRatio is false, the function getWellProductionVolume will throw an exception.
void setThrowExceptionForTooGreatErrorRatio(bool boolean){this->throwExceptionForTooGreatErrorRatio = boolean;}
//! \brief This function executes a integration test for all the keywords. If the two files do not match in amount of keywords, an exception is thrown. \n Uses the boolean member variables to know which tests to execute.
void getIntegrationTest();
//! \brief This function executes a integration test for one specific keyword. If one or both of the files do not have the keyword, an exception is thorwn. \n Uses the boolean member variables to know which tests to execute.
void getIntegrationTest(const char* keyword);
//! \brief This function calculates the area of an rectangle of height height and width time-timePrev
//! \param[in] height The height of the rectangle. See important statement of use below.
//! \param[in] width The width of the rectangle
//! \param[out] area Returns the area of the rectangle
//! \details This function is simple. When using it on a summary vector (data values plotted againt time), calculating the area between the two points i and i+1 note this:\nThe width is time_of_i+1 - time_of_i, the height is data_of_i+1 NOT data_of_i. The upper limit must be used.
static double getRectangleArea(double height, double width){return height*width;}
//! \brief This function calculates the area under a graph by doing a Riemann sum
//! \param[in] timeVec Contains the time values
//! \param[in] dataVec Contains the data values
//! \details The function does a Riemann sum integration of the graph formed
//! by the points (timeVec[i] , dataVec[i]).
//! In the case of a summary vector, the summary vector of quantity
//! corresponding to a rate, is a piecewise continus function consisting
//! of unit step functions. Thus the Riemann sum will become an
//! exact expression for the integral of the graph.
//! Important: For the data values correspoding to time i and i-1,
//! the fixed value of the height of the rectangles in the Riemann sum
//! is set by the data value i. The upper limit must be used.
static double integrate(const std::vector<double>& timeVec,
const std::vector<double>& dataVec);
//! \brief This function calculates the Riemann sum of the error between two graphs.
//! \param[in] timeVec1 Contains the time values of graph 1
//! \param[in] dataVec1 Contains the data values of graph 1
//! \param[in] timeVec2 Contains the time values of graph 2
//! \param[in] dataVec2 Contains the data values of graph 2
//! \details This function takes in two graphs and returns the integrated error.
//! In case of ecl summary vectors: if the vectors correspond to a
//! quantity which is a rate, the vectors will be piecewise
//! continous unit step functions. In this case the error will also
//! be a piecewise continous unit step function. The function uses
//! a Riemann sum when calculating the integral. Thus the integral
//! will become exact. Important: For the data values corresponding
//! to time i and i-1, the fixed value of the height of the rectangles
//! in the Riemann sum is set by the data value i.
//! The upper limit must be used.
static double integrateError(const std::vector<double>& timeVec1,
const std::vector<double>& dataVec1,
const std::vector<double>& timeVec2,
const std::vector<double>& dataVec2);
};

View File

@ -0,0 +1,72 @@
/*
Copyright 2016 Statoil ASA.
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/>.
*/
#ifndef SUMMARYREGRESSIONTEST_HPP
#define SUMMARYREGRESSIONTEST_HPP
#include <opm/test_util/summaryComparator.hpp>
//! \details The class inherits from the SummaryComparator class, which takes care of all file reading. \n The RegressionTest class compares the values from the two different files and throws exceptions when the deviation is unsatisfying.
class RegressionTest: public SummaryComparator {
private:
//! \brief Gathers the correct data for comparison for a specific keyword
//! \param[in] timeVec1 The time steps of file 1.
//! \param[in] timeVec2 The time steps of file 2.
//! \param[in] keyword The keyword of interest
//! \details The function prepares a regression test by gathering the data, stroing it into two vectors, \n deciding which is to be used as a reference/basis and calling the test function.
//! \return True if check passed, false otherwise.
bool checkForKeyword(std::vector<double>& timeVec1, std::vector<double>& timeVec2, const char* keyword);
//! \brief The regression test
//! \param[in] keyword The keyword common for both the files. The vectors associated with the keyword are used for comparison.
//! \details Start test uses the private member variables, pointers of std::vector<double> type, which are set to point to the correct vectors in SummaryComparison::chooseReference(...). \n The function iterates over the referenceVev/basis and for each iteration it calculates the deviation with SummaryComparison::getDeviation(..) and stors it in a Deviation struct. \n SummaryComparison::getDeviation takes the int jvar as an reference input, and using it as an iterative index for the values which are to be compared with the basis. \n Thus, by updating the jvar variable every time a deviation is calculated, one keep track jvar and do not have to iterate over already checked values.
bool startTest(const char* keyword);
//! \brief Caluculates a deviation, throws exceptions and writes and error message.
//! \param[in] deviation Deviation struct
//! \param[in] keyword The keyword that the data that are being compared belongs to.
//! \param[in] refIndex The report step of which the deviation originates from in #referenceDataVec.
//! \param[in] checkIndex The report step of which the deviation originates from in #checkDataVec.
//! \details The function checks the values of the Deviation struct against the absolute and relative tolerance, which are private member values of the super class. \n When comparing against the relative tolerance an additional term is added, the absolute deviation has to be greater than 1e-6 for the function to throw an exception. \n When the deviations are too great, the function writes out which keyword, and at what report step the deviation is too great before optionally throwing an exception.
//! \return True if check passed, false otherwise.
bool checkDeviation(Deviation deviation, const char* keyword, int refIndex, int checkIndex);
bool isRestartFile = false; //!< Private member variable, when true the files that are being compared is a restart file vs a normal file
public:
//! \brief Constructor, creates an object of RefressionTest class.
//! \param[in] basename1 Path to file1 without extension.
//! \param[in] basename1 Path to file2 without extension.
//! \param[in] relativeTol The relative tolerance which is to be used in the test.
//! \param[in] absoluteTol The absolute tolerance which is to be used in the test.
//! \details The constructor calls the constructor of the super class.
RegressionTest(const char* basename1, const char* basename2, double relativeTol, double absoluteTol):
SummaryComparator(basename1, basename2, relativeTol, absoluteTol) {}
//! \details The function executes a regression test for all the keywords. If the two files do not match in amount of keywords, an exception is thrown.
void getRegressionTest();
//! \details The function executes a regression test for one specific keyword. If one or both of the files do not have the keyword, an exception is thrown.
void getRegressionTest(const char* keyword);///< Regression test for a certain keyword of the files.
//! \brief This function sets the private member variable isRestartFiles
//! \param[in] boolean Boolean value
void setIsRestartFile(bool boolean){this->isRestartFile = boolean;}
};
#endif

View File

@ -0,0 +1,77 @@
/*
Copyright 2016 Statoil ASA.
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 <algorithm>
#include <opm/output/data/Solution.hpp>
#include <opm/output/data/Cells.hpp>
namespace Opm {
namespace data {
Solution::Solution( bool init_si ) : si( init_si ) {}
bool Solution::has(const std::string& keyword) const {
return this->count( keyword ) > 0;
}
std::vector<double>& Solution::data(const std::string& keyword) {
return this->at( keyword ).data;
}
const std::vector<double>& Solution::data(const std::string& keyword) const {
return this->at( keyword ).data;
}
std::pair< Solution::iterator, bool > Solution::insert( std::string name,
UnitSystem::measure m,
std::vector< double > xs,
TargetType type ) {
return this->emplace( name, CellData{ m, std::move( xs ), type } );
}
void data::Solution::convertToSI( const UnitSystem& units ) {
if (this->si) return;
for( auto& elm : *this ) {
UnitSystem::measure dim = elm.second.dim;
if (dim != UnitSystem::measure::identity)
units.to_si( dim , elm.second.data );
}
this->si = true;
}
void data::Solution::convertFromSI( const UnitSystem& units ) {
if (!this->si) return;
for (auto& elm : *this ) {
UnitSystem::measure dim = elm.second.dim;
if (dim != UnitSystem::measure::identity)
units.from_si( dim , elm.second.data );
}
this->si = false;
}
}
}

View File

@ -0,0 +1,350 @@
//===========================================================================
//
// File: EclipseGridInspector.C
//
// Created: Mon Jun 2 12:17:51 2008
//
// Author: Atgeirr F Rasmussen <atgeirr@sintef.no>
//
// $Date$
//
// $Revision$
//
// Revision: $Id: EclipseGridInspector.C,v 1.2 2008/08/18 14:16:13 atgeirr Exp $
//
//===========================================================================
/*
Copyright 2009, 2010 SINTEF ICT, Applied Mathematics.
Copyright 2009, 2010 Statoil ASA.
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/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <opm/output/eclipse/EclipseGridInspector.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/parser/eclipse/Parser/Parser.hpp>
#include <opm/parser/eclipse/Deck/Deck.hpp>
#include <opm/parser/eclipse/Deck/DeckItem.hpp>
#include <opm/parser/eclipse/Deck/DeckKeyword.hpp>
#include <opm/parser/eclipse/Deck/DeckRecord.hpp>
#include <stdexcept>
#include <numeric>
#include <cmath>
#include <cfloat>
#include <algorithm>
#include <array>
#include <iostream>
namespace Opm
{
EclipseGridInspector::EclipseGridInspector(Opm::Deck deck)
: deck_( std::move( deck ) )
{
init_();
}
void EclipseGridInspector::init_()
{
if (!deck_.hasKeyword("COORD")) {
OPM_THROW(std::runtime_error, "Needed field \"COORD\" is missing in file");
}
if (!deck_.hasKeyword("ZCORN")) {
OPM_THROW(std::runtime_error, "Needed field \"ZCORN\" is missing in file");
}
if (deck_.hasKeyword("SPECGRID")) {
const auto& specgridRecord =
deck_.getKeyword("SPECGRID").getRecord(0);
logical_gridsize_[0] = specgridRecord.getItem("NX").get< int >(0);
logical_gridsize_[1] = specgridRecord.getItem("NY").get< int >(0);
logical_gridsize_[2] = specgridRecord.getItem("NZ").get< int >(0);
} else if (deck_.hasKeyword("DIMENS")) {
const auto& dimensRecord =
deck_.getKeyword("DIMENS").getRecord(0);
logical_gridsize_[0] = dimensRecord.getItem("NX").get< int >(0);
logical_gridsize_[1] = dimensRecord.getItem("NY").get< int >(0);
logical_gridsize_[2] = dimensRecord.getItem("NZ").get< int >(0);
} else {
OPM_THROW(std::runtime_error, "Found neither SPECGRID nor DIMENS in file. At least one is needed.");
}
}
/**
Return the dip slopes for the cell relative to xy-plane in x- and y- direction.
Dip slope is average rise in positive x-direction over cell length in x-direction.
Similarly for y.
Current implementation is for vertical pillars, but is not difficult to fix.
@returns a std::pair<double,double> with x-dip in first component and y-dip in second.
*/
std::pair<double,double> EclipseGridInspector::cellDips(int i, int j, int k) const
{
checkLogicalCoords(i, j, k);
const std::vector<double>& pillc =
deck_.getKeyword("COORD").getSIDoubleData();
int num_pillars = (logical_gridsize_[0] + 1)*(logical_gridsize_[1] + 1);
if (6*num_pillars != int(pillc.size())) {
throw std::runtime_error("Wrong size of COORD field.");
}
const std::vector<double>& z =
deck_.getKeyword("ZCORN").getSIDoubleData();
int num_cells = logical_gridsize_[0]*logical_gridsize_[1]*logical_gridsize_[2];
if (8*num_cells != int(z.size())) {
throw std::runtime_error("Wrong size of ZCORN field");
}
// Pick ZCORN-value for all 8 corners of the given cell
std::array<double, 8> cellz = cellZvals(i, j, k);
// Compute rise in positive x-direction for all four edges (and then find mean)
// Current implementation is for regularly placed and vertical pillars!
int numxpill = logical_gridsize_[0] + 1;
int pix = i + j*numxpill;
double cell_xlength = pillc[6*(pix + 1)] - pillc[6*pix];
flush(std::cout);
double xrise[4] = { (cellz[1] - cellz[0])/cell_xlength, // LLL -> HLL
(cellz[3] - cellz[2])/cell_xlength, // LHL -> HHL
(cellz[5] - cellz[4])/cell_xlength, // LLH -> HLH
(cellz[7] - cellz[6])/cell_xlength}; // LHH -> HHH
double cell_ylength = pillc[6*(pix + numxpill) + 1] - pillc[6*pix + 1];
double yrise[4] = { (cellz[2] - cellz[0])/cell_ylength, // LLL -> LHL
(cellz[3] - cellz[1])/cell_ylength, // HLL -> HHL
(cellz[6] - cellz[4])/cell_ylength, // LLH -> LHH
(cellz[7] - cellz[5])/cell_ylength}; // HLH -> HHH
// Now ignore those edges that touch the global top or bottom surface
// of the entire grdecl model. This is to avoid bias, as these edges probably
// don't follow an overall dip for the model if it exists.
int x_edges = 4;
int y_edges = 4;
std::array<double, 6> gridlimits = getGridLimits();
double zmin = gridlimits[4];
double zmax = gridlimits[5];
// LLL -> HLL
if ((cellz[1] == zmin) || (cellz[0] == zmin)) {
xrise[0] = 0; x_edges--;
}
// LHL -> HHL
if ((cellz[2] == zmin) || (cellz[3] == zmin)) {
xrise[1] = 0; x_edges--;
}
// LLH -> HLH
if ((cellz[4] == zmax) || (cellz[5] == zmax)) {
xrise[2] = 0; x_edges--;
}
// LHH -> HHH
if ((cellz[6] == zmax) || (cellz[7] == zmax)) {
xrise[3] = 0; x_edges--;
}
// LLL -> LHL
if ((cellz[0] == zmin) || (cellz[2] == zmin)) {
yrise[0] = 0; y_edges--;
}
// HLL -> HHL
if ((cellz[1] == zmin) || (cellz[3] == zmin)) {
yrise[1] = 0; y_edges--;
}
// LLH -> LHH
if ((cellz[6] == zmax) || (cellz[4] == zmax)) {
yrise[2] = 0; y_edges--;
}
// HLH -> HHH
if ((cellz[7] == zmax) || (cellz[5] == zmax)) {
yrise[3] = 0; y_edges--;
}
return std::make_pair( (xrise[0] + xrise[1] + xrise[2] + xrise[3])/x_edges,
(yrise[0] + yrise[1] + yrise[2] + yrise[3])/y_edges);
}
/**
Wrapper for cellDips(i, j, k).
*/
std::pair<double,double> EclipseGridInspector::cellDips(int cell_idx) const
{
std::array<int, 3> idxs = cellIdxToLogicalCoords(cell_idx);
return cellDips(idxs[0], idxs[1], idxs[2]);
}
std::array<int, 3> EclipseGridInspector::cellIdxToLogicalCoords(int cell_idx) const
{
int i,j,k; // Position of cell in cell hierarchy
int horIdx = (cell_idx+1) - int(std::floor(((double)(cell_idx+1))/((double)(logical_gridsize_[0]*logical_gridsize_[1]))))*logical_gridsize_[0]*logical_gridsize_[1]; // index in the corresponding horizon
if (horIdx == 0) {
horIdx = logical_gridsize_[0]*logical_gridsize_[1];
}
i = horIdx - int(std::floor(((double)horIdx)/((double)logical_gridsize_[0])))*logical_gridsize_[0];
if (i == 0) {
i = logical_gridsize_[0];
}
j = (horIdx-i)/logical_gridsize_[0]+1;
k = ((cell_idx+1)-logical_gridsize_[0]*(j-1)-1)/(logical_gridsize_[0]*logical_gridsize_[1])+1;
std::array<int, 3> a = {{i-1, j-1, k-1}};
return a; //std::array<int, 3> {{i-1, j-1, k-1}};
}
double EclipseGridInspector::cellVolumeVerticalPillars(int i, int j, int k) const
{
// Checking parameters and obtaining values from parser.
checkLogicalCoords(i, j, k);
const std::vector<double>& pillc =
deck_.getKeyword("COORD").getSIDoubleData();
int num_pillars = (logical_gridsize_[0] + 1)*(logical_gridsize_[1] + 1);
if (6*num_pillars != int(pillc.size())) {
throw std::runtime_error("Wrong size of COORD field.");
}
const std::vector<double>& z =
deck_.getKeyword("ZCORN").getSIDoubleData();
int num_cells = logical_gridsize_[0]*logical_gridsize_[1]*logical_gridsize_[2];
if (8*num_cells != int(z.size())) {
throw std::runtime_error("Wrong size of ZCORN field");
}
// Computing the base area as half the 2d cross product of the diagonals.
int numxpill = logical_gridsize_[0] + 1;
int pix = i + j*numxpill;
double px[4] = { pillc[6*pix],
pillc[6*(pix + 1)],
pillc[6*(pix + numxpill)],
pillc[6*(pix + numxpill + 1)] };
double py[4] = { pillc[6*pix + 1],
pillc[6*(pix + 1) + 1],
pillc[6*(pix + numxpill) + 1],
pillc[6*(pix + numxpill + 1) + 1] };
double diag1[2] = { px[3] - px[0], py[3] - py[0] };
double diag2[2] = { px[2] - px[1], py[2] - py[1] };
double area = 0.5*(diag1[0]*diag2[1] - diag1[1]*diag2[0]);
// Computing the average of the z-differences along each pillar.
int delta[3] = { 1,
2*logical_gridsize_[0],
4*logical_gridsize_[0]*logical_gridsize_[1] };
int ix = 2*(i*delta[0] + j*delta[1] + k*delta[2]);
double cellz[8] = { z[ix], z[ix + delta[0]],
z[ix + delta[1]], z[ix + delta[1] + delta[0]],
z[ix + delta[2]], z[ix + delta[2] + delta[0]],
z[ix + delta[2] + delta[1]], z[ix + delta[2] + delta[1] + delta[0]] };
double diffz[4] = { cellz[4] - cellz[0],
cellz[5] - cellz[1],
cellz[6] - cellz[2],
cellz[7] - cellz[3] };
double averzdiff = 0.25*std::accumulate(diffz, diffz + 4, 0.0);
return averzdiff*area;
}
double EclipseGridInspector::cellVolumeVerticalPillars(int cell_idx) const
{
std::array<int, 3> idxs = cellIdxToLogicalCoords(cell_idx);
return cellVolumeVerticalPillars(idxs[0], idxs[1], idxs[2]);
}
void EclipseGridInspector::checkLogicalCoords(int i, int j, int k) const
{
if (i < 0 || i >= logical_gridsize_[0])
throw std::runtime_error("First coordinate out of bounds");
if (j < 0 || j >= logical_gridsize_[1])
throw std::runtime_error("Second coordinate out of bounds");
if (k < 0 || k >= logical_gridsize_[2])
throw std::runtime_error("Third coordinate out of bounds");
}
std::array<double, 6> EclipseGridInspector::getGridLimits() const
{
if (! (deck_.hasKeyword("COORD") && deck_.hasKeyword("ZCORN") && deck_.hasKeyword("SPECGRID")) ) {
throw std::runtime_error("EclipseGridInspector: Grid does not have SPECGRID, COORD, and ZCORN, can't find dimensions.");
}
std::vector<double> coord = deck_.getKeyword("COORD").getSIDoubleData();
std::vector<double> zcorn = deck_.getKeyword("ZCORN").getSIDoubleData();
double xmin = +DBL_MAX;
double xmax = -DBL_MAX;
double ymin = +DBL_MAX;
double ymax = -DBL_MAX;
int pillars = (logical_gridsize_[0]+1) * (logical_gridsize_[1]+1);
for (int pillarindex = 0; pillarindex < pillars; ++pillarindex) {
if (coord[pillarindex * 6 + 0] > xmax)
xmax = coord[pillarindex * 6 + 0];
if (coord[pillarindex * 6 + 0] < xmin)
xmin = coord[pillarindex * 6 + 0];
if (coord[pillarindex * 6 + 1] > ymax)
ymax = coord[pillarindex * 6 + 1];
if (coord[pillarindex * 6 + 1] < ymin)
ymin = coord[pillarindex * 6 + 1];
if (coord[pillarindex * 6 + 3] > xmax)
xmax = coord[pillarindex * 6 + 3];
if (coord[pillarindex * 6 + 3] < xmin)
xmin = coord[pillarindex * 6 + 3];
if (coord[pillarindex * 6 + 4] > ymax)
ymax = coord[pillarindex * 6 + 4];
if (coord[pillarindex * 6 + 4] < ymin)
ymin = coord[pillarindex * 6 + 4];
}
std::array<double, 6> gridlimits = {{ xmin, xmax, ymin, ymax,
*min_element(zcorn.begin(), zcorn.end()),
*max_element(zcorn.begin(), zcorn.end()) }};
return gridlimits;
}
std::array<int, 3> EclipseGridInspector::gridSize() const
{
std::array<int, 3> retval = {{ logical_gridsize_[0],
logical_gridsize_[1],
logical_gridsize_[2] }};
return retval;
}
std::array<double, 8> EclipseGridInspector::cellZvals(int i, int j, int k) const
{
// Get the zcorn field.
const std::vector<double>& z = deck_.getKeyword("ZCORN").getSIDoubleData();
int num_cells = logical_gridsize_[0]*logical_gridsize_[1]*logical_gridsize_[2];
if (8*num_cells != int(z.size())) {
throw std::runtime_error("Wrong size of ZCORN field");
}
// Make the coordinate array.
int delta[3] = { 1,
2*logical_gridsize_[0],
4*logical_gridsize_[0]*logical_gridsize_[1] };
int ix = 2*(i*delta[0] + j*delta[1] + k*delta[2]);
std::array<double, 8> cellz = {{ z[ix], z[ix + delta[0]],
z[ix + delta[1]], z[ix + delta[1] + delta[0]],
z[ix + delta[2]], z[ix + delta[2] + delta[0]],
z[ix + delta[2] + delta[1]], z[ix + delta[2] + delta[1] + delta[0]] }};
return cellz;
}
} // namespace Opm

View File

@ -0,0 +1,543 @@
/*
Copyright (c) 2016 Statoil ASA
Copyright (c) 2013-2015 Andreas Lauser
Copyright (c) 2013 SINTEF ICT, Applied Mathematics.
Copyright (c) 2013 Uni Research AS
Copyright (c) 2015 IRIS AS
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 <unordered_map>
#include "config.h"
#include "EclipseIO.hpp"
#include <opm/parser/eclipse/Deck/DeckKeyword.hpp>
#include <opm/parser/eclipse/Units/Dimension.hpp>
#include <opm/parser/eclipse/Units/UnitSystem.hpp>
#include <opm/parser/eclipse/EclipseState/Eclipse3DProperties.hpp>
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
#include <opm/parser/eclipse/EclipseState/IOConfig/IOConfig.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/GridProperty.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/CompletionSet.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/ScheduleEnums.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Well.hpp>
#include <opm/parser/eclipse/Utility/Functional.hpp>
#include <opm/output/eclipse/Summary.hpp>
#include <opm/output/eclipse/Tables.hpp>
#include <opm/output/eclipse/RestartIO.hpp>
#include <cstdlib>
#include <memory> // unique_ptr
#include <utility> // move
#include <ert/ecl/EclKW.hpp>
#include <ert/ecl/EclFilename.hpp>
#include <ert/ecl/ecl_kw_magic.h>
#include <ert/ecl/ecl_init_file.h>
#include <ert/ecl/ecl_file.h>
#include <ert/ecl/ecl_grid.h>
#include <ert/ecl/ecl_rft_file.h>
#include <ert/ecl/ecl_rst_file.h>
#include <ert/ecl_well/well_const.h>
#include <ert/ecl/ecl_rsthead.h>
#include <ert/util/util.h>
#include <ert/ecl/fortio.h>
#define OPM_XWEL "OPM_XWEL"
#define OPM_IWEL "OPM_IWEL"
// namespace start here since we don't want the ERT headers in it
namespace Opm {
namespace {
void writeKeyword( ERT::FortIO& fortio ,
const std::string& keywordName,
const std::vector<int> &data ) {
ERT::EclKW< int > kw( keywordName, data );
kw.fwrite( fortio );
}
/*
This overload hardcodes the common assumption that properties which
are stored internally as double values in OPM should be stored as
float values in the ECLIPSE formatted binary files.
*/
void writeKeyword( ERT::FortIO& fortio ,
const std::string& keywordName,
const std::vector<double> &data) {
ERT::EclKW< float > kw( keywordName, data );
kw.fwrite( fortio );
}
class RFT {
public:
RFT( const std::string& output_dir,
const std::string& basename,
bool format );
void writeTimeStep( std::vector< const Well* >,
const EclipseGrid& grid,
int report_step,
time_t current_time,
double days,
const UnitSystem& units,
data::Wells wellData);
private:
std::string filename;
bool fmt_file;
};
RFT::RFT( const std::string& output_dir,
const std::string& basename,
bool format ) :
filename( ERT::EclFilename( output_dir, basename, ECL_RFT_FILE, format ) ),
fmt_file( format )
{}
void RFT::writeTimeStep( std::vector< const Well* > wells,
const EclipseGrid& grid,
int report_step,
time_t current_time,
double days,
const UnitSystem& units,
data::Wells wellDatas) {
using rft = ERT::ert_unique_ptr< ecl_rft_node_type, ecl_rft_node_free >;
fortio_type * fortio;
int first_report_step = report_step;
for (const auto* well : wells)
first_report_step = std::min( first_report_step, well->firstRFTOutput());
if (report_step > first_report_step)
fortio = fortio_open_append( filename.c_str() , fmt_file , ECL_ENDIAN_FLIP );
else
fortio = fortio_open_writer( filename.c_str() , fmt_file , ECL_ENDIAN_FLIP );
for ( const auto& well : wells ) {
if( !( well->getRFTActive( report_step )
|| well->getPLTActive( report_step ) ) )
continue;
auto* rft_node = ecl_rft_node_alloc_new( well->name().c_str(), "RFT",
current_time, days );
const auto& wellData = wellDatas.at(well->name());
if (wellData.completions.empty())
continue;
for( const auto& completion : well->getCompletions( report_step ) ) {
const size_t i = size_t( completion.getI() );
const size_t j = size_t( completion.getJ() );
const size_t k = size_t( completion.getK() );
if( !grid.cellActive( i, j, k ) ) continue;
const auto index = grid.getGlobalIndex( i, j, k );
const double depth = grid.getCellDepth( i, j, k );
const auto& completionData = std::find_if( wellData.completions.begin(),
wellData.completions.end(),
[=]( const data::Completion& c ) {
return c.index == index;
} );
const double press = units.from_si(UnitSystem::measure::pressure,completionData->cell_pressure);
const double satwat = units.from_si(UnitSystem::measure::identity, completionData->cell_saturation_water);
const double satgas = units.from_si(UnitSystem::measure::identity, completionData->cell_saturation_gas);
auto* cell = ecl_rft_cell_alloc_RFT(
i, j, k, depth, press, satwat, satgas );
ecl_rft_node_append_cell( rft_node, cell );
}
rft ecl_node( rft_node );
ecl_rft_node_fwrite( ecl_node.get(), fortio, units.getEclType() );
}
fortio_fclose( fortio );
}
inline std::string uppercase( std::string x ) {
std::transform( x.begin(), x.end(), x.begin(),
[]( char c ) { return std::toupper( c ); } );
return x;
}
}
class EclipseIO::Impl {
public:
Impl( const EclipseState&, EclipseGrid, const Schedule&, const SummaryConfig& );
void writeINITFile( const data::Solution& simProps, std::map<std::string, std::vector<int> > int_data, const NNC& nnc) const;
void writeEGRIDFile( const NNC& nnc ) const;
const EclipseState& es;
EclipseGrid grid;
const Schedule& schedule;
std::string outputDir;
std::string baseName;
out::Summary summary;
RFT rft;
bool output_enabled;
};
EclipseIO::Impl::Impl( const EclipseState& eclipseState,
EclipseGrid grid_,
const Schedule& schedule_,
const SummaryConfig& summary_config)
: es( eclipseState )
, grid( std::move( grid_ ) )
, schedule( schedule_ )
, outputDir( eclipseState.getIOConfig().getOutputDir() )
, baseName( uppercase( eclipseState.getIOConfig().getBaseName() ) )
, summary( eclipseState, summary_config, grid , schedule )
, rft( outputDir.c_str(), baseName.c_str(), es.getIOConfig().getFMTOUT() )
, output_enabled( eclipseState.getIOConfig().getOutputEnabled() )
{}
void EclipseIO::Impl::writeINITFile( const data::Solution& simProps, std::map<std::string, std::vector<int> > int_data, const NNC& nnc) const {
const auto& units = this->es.getUnits();
const IOConfig& ioConfig = this->es.cfg().io();
std::string initFile( ERT::EclFilename( this->outputDir,
this->baseName,
ECL_INIT_FILE,
ioConfig.getFMTOUT() ));
ERT::FortIO fortio( initFile,
std::ios_base::out,
ioConfig.getFMTOUT(),
ECL_ENDIAN_FLIP );
// Write INIT header. Observe that the PORV vector is treated
// specially; that is because for this particulat vector we write
// a total of nx*ny*nz values, where the PORV vector has been
// explicitly set to zero for inactive cells. The convention is
// that the active/inactive cell mapping can be inferred by
// reading the PORV vector.
{
const auto& opm_data = this->es.get3DProperties().getDoubleGridProperty("PORV").getData();
auto ecl_data = opm_data;
for (size_t global_index = 0; global_index < opm_data.size(); global_index++)
if (!this->grid.cellActive( global_index ))
ecl_data[global_index] = 0;
ecl_init_file_fwrite_header( fortio.get(),
this->grid.c_ptr(),
NULL,
units.getEclType(),
this->es.runspec( ).eclPhaseMask( ),
this->schedule.posixStartTime( ));
units.from_si( UnitSystem::measure::volume, ecl_data );
writeKeyword( fortio, "PORV" , ecl_data );
}
// Writing quantities which are calculated by the grid to the INIT file.
ecl_grid_fwrite_depth( this->grid.c_ptr() , fortio.get() , units.getEclType( ) );
ecl_grid_fwrite_dims( this->grid.c_ptr() , fortio.get() , units.getEclType( ) );
// Write properties from the input deck.
{
const auto& properties = this->es.get3DProperties().getDoubleProperties();
using double_kw = std::pair<std::string, UnitSystem::measure>;
/*
This is a rather arbitrary hardcoded list of 3D keywords
which are written to the INIT file, if they are in the
current EclipseState.
*/
std::vector<double_kw> doubleKeywords = {{"PORO" , UnitSystem::measure::identity },
{"PERMX" , UnitSystem::measure::permeability },
{"PERMY" , UnitSystem::measure::permeability },
{"PERMZ" , UnitSystem::measure::permeability },
{"NTG" , UnitSystem::measure::identity }};
// The INIT file should always contain the NTG property, we
// therefor invoke the auto create functionality to ensure
// that "NTG" is included in the properties container.
properties.assertKeyword("NTG");
for (const auto& kw_pair : doubleKeywords) {
if (properties.hasKeyword( kw_pair.first)) {
const auto& opm_property = properties.getKeyword(kw_pair.first);
auto ecl_data = opm_property.compressedCopy( this->grid );
units.from_si( kw_pair.second, ecl_data );
writeKeyword( fortio, kw_pair.first, ecl_data );
}
}
}
// Write properties which have been initialized by the simulator.
{
for (const auto& prop : simProps) {
auto ecl_data = this->grid.compressedVector( prop.second.data );
writeKeyword( fortio, prop.first, ecl_data );
}
}
// Write tables
{
Tables tables( this->es.getUnits() );
tables.addPVTO( this->es.getTableManager().getPvtoTables() );
tables.addPVTG( this->es.getTableManager().getPvtgTables() );
tables.addPVTW( this->es.getTableManager().getPvtwTable() );
tables.addDensity( this->es.getTableManager().getDensityTable( ) );
tables.addSatFunc(this->es);
fwrite(tables, fortio);
}
// Write all integer field properties from the input deck.
{
const auto& properties = this->es.get3DProperties().getIntProperties();
// It seems that the INIT file should always contain these
// keywords, we therefor call getKeyword() here to invoke the
// autocreation property, and ensure that the keywords exist
// in the properties container.
properties.assertKeyword("PVTNUM");
properties.assertKeyword("SATNUM");
properties.assertKeyword("EQLNUM");
properties.assertKeyword("FIPNUM");
for (const auto& property : properties) {
auto ecl_data = property.compressedCopy( this->grid );
writeKeyword( fortio , property.getKeywordName() , ecl_data );
}
}
//Write Integer Vector Map
{
for( const auto& pair : int_data) {
const std::string& key = pair.first;
const std::vector<int>& int_vector = pair.second;
if (key.size() > ECL_STRING8_LENGTH)
throw std::invalid_argument("Keyword is too long.");
writeKeyword( fortio , key , int_vector );
}
}
// Write NNC transmissibilities
{
std::vector<double> tran;
for( const NNCdata& nd : nnc.nncdata() )
tran.push_back( nd.trans );
units.from_si( UnitSystem::measure::transmissibility , tran );
writeKeyword( fortio, "TRANNNC" , tran );
}
}
void EclipseIO::Impl::writeEGRIDFile( const NNC& nnc ) const {
const auto& ioConfig = this->es.getIOConfig();
std::string egridFile( ERT::EclFilename( this->outputDir,
this->baseName,
ECL_EGRID_FILE,
ioConfig.getFMTOUT() ));
{
int idx = 0;
auto* ecl_grid = const_cast< ecl_grid_type* >( this->grid.c_ptr() );
for (const NNCdata& n : nnc.nncdata())
ecl_grid_add_self_nnc( ecl_grid, n.cell1, n.cell2, idx++);
ecl_grid_fwrite_EGRID2(ecl_grid, egridFile.c_str(), this->es.getDeckUnitSystem().getEclType() );
}
}
/*
int_data: Writes key(string) and integers vector to INIT file as eclipse keywords
- Key: Max 8 chars.
- Wrong input: invalid_argument exception.
*/
void EclipseIO::writeInitial( data::Solution simProps, std::map<std::string, std::vector<int> > int_data, const NNC& nnc) {
if( !this->impl->output_enabled )
return;
{
const auto& es = this->impl->es;
const IOConfig& ioConfig = es.cfg().io();
simProps.convertFromSI( es.getUnits() );
if( ioConfig.getWriteINITFile() )
this->impl->writeINITFile( simProps , int_data, nnc );
if( ioConfig.getWriteEGRIDFile( ) )
this->impl->writeEGRIDFile( nnc );
}
}
// implementation of the writeTimeStep method
void EclipseIO::writeTimeStep(int report_step,
bool isSubstep,
double secs_elapsed,
data::Solution cells,
data::Wells wells,
const std::map<std::string, double>& single_summary_values,
const std::map<std::string, std::vector<double> >& region_summary_values,
const std::map<std::pair<std::string, int>, double>& block_summary_values,
const std::map<std::string, std::vector<double>>& extra_restart,
bool write_double)
{
if( !this->impl->output_enabled )
return;
const auto& es = this->impl->es;
const auto& grid = this->impl->grid;
const auto& schedule = this->impl->schedule;
const auto& units = es.getUnits();
const auto& ioConfig = es.getIOConfig();
const auto& restart = es.cfg().restart();
/*
Summary data is written unconditionally for every timestep.
*/
{
this->impl->summary.add_timestep( report_step,
secs_elapsed,
es,
schedule,
wells ,
single_summary_values ,
region_summary_values,
block_summary_values);
this->impl->summary.write();
}
/*
Current implementation will not write restart files for substep,
but there is an unsupported option to the RPTSCHED keyword which
will request restart output from every timestep.
*/
if(!isSubstep && restart.getWriteRestartFile(report_step))
{
std::string filename = ERT::EclFilename( this->impl->outputDir,
this->impl->baseName,
ioConfig.getUNIFOUT() ? ECL_UNIFIED_RESTART_FILE : ECL_RESTART_FILE,
report_step,
ioConfig.getFMTOUT() );
RestartIO::save( filename , report_step, secs_elapsed, cells, wells, es , grid , schedule, extra_restart , write_double);
}
/*
RFT files are not written for substep.
*/
if( isSubstep )
return;
{
std::vector<const Well*> sched_wells = this->impl->schedule.getWells( report_step );
const auto rft_active = [report_step] (const Well* w) { return w->getRFTActive( report_step ) || w->getPLTActive( report_step ); };
if (std::any_of(sched_wells.begin(), sched_wells.end(), rft_active)) {
this->impl->rft.writeTimeStep( sched_wells,
grid,
report_step,
secs_elapsed + this->impl->schedule.posixStartTime(),
units.from_si( UnitSystem::measure::time, secs_elapsed ),
units,
wells );
}
}
}
RestartValue EclipseIO::loadRestart(const std::map<std::string, RestartKey>& keys, const std::map<std::string, bool>& extra_keys) const {
const auto& es = this->impl->es;
const auto& grid = this->impl->grid;
const auto& schedule = this->impl->schedule;
const InitConfig& initConfig = es.getInitConfig();
const auto& ioConfig = es.getIOConfig();
const int report_step = initConfig.getRestartStep();
const std::string filename = ioConfig.getRestartFileName( initConfig.getRestartRootName(),
report_step,
false );
return RestartIO::load( filename , report_step , keys , es, grid , schedule, extra_keys);
}
EclipseIO::EclipseIO( const EclipseState& es,
EclipseGrid grid,
const Schedule& schedule,
const SummaryConfig& summary_config)
: impl( new Impl( es, std::move( grid ), schedule , summary_config) )
{
if( !this->impl->output_enabled )
return;
{
const auto& outputDir = this->impl->outputDir;
// make sure that the output directory exists, if not try to create it
if ( !util_entry_exists( outputDir.c_str() ) ) {
util_make_path( outputDir.c_str() );
}
if( !util_is_directory( outputDir.c_str() ) ) {
throw std::runtime_error( "The path specified as output directory '"
+ outputDir + "' is not a directory");
}
}
}
EclipseIO::~EclipseIO() {}
} // namespace Opm

View File

@ -0,0 +1,104 @@
#include <opm/output/eclipse/LinearisedOutputTable.hpp>
#include <algorithm>
#include <cmath>
#include <cassert>
#include <utility>
Opm::LinearisedOutputTable::
LinearisedOutputTable(const std::size_t numTables0,
const std::size_t numPrimary0,
const std::size_t numRows0,
const std::size_t numCols0)
: data (numTables0 * numPrimary0 * numRows0 * numCols0, 1.0e20)
, numTables (numTables0)
, numPrimary(numPrimary0)
, numRows (numRows0)
{}
std::vector<double>::iterator
Opm::LinearisedOutputTable::column(const std::size_t tableID,
const std::size_t primID,
const std::size_t colID)
{
// Table format: numRows * numPrimary * numTables values for first
// column (ID == 0), followed by same number of entries for second
// column &c.
const auto offset =
0 + this->numRows*(primID + this->numPrimary*(tableID + this->numTables*colID));
assert (offset + this->numRows <= this->data.size());
return this->data.begin() + offset;
}
const std::vector<double>&
Opm::LinearisedOutputTable::getData() const
{
return this->data;
}
std::vector<double>
Opm::LinearisedOutputTable::getDataDestructively()
{
return std::move(this->data);
}
// ---------------------------------------------------------------------
void
Opm::DifferentiateOutputTable::calcSlopes(const std::size_t nDep,
const Descriptor& desc,
LinearisedOutputTable& table)
{
if ((nDep == 0) || (desc.numActRows < 2)) {
// No dependent columns or too few rows to compute any derivatives.
// Likely to be user error. Can't do anything here.
return;
}
auto x = table.column(desc.tableID, desc.primID, 0);
using colIT = decltype(x);
auto y = std::vector<colIT>{}; y .reserve(nDep);
auto dy = std::vector<colIT>{}; dy.reserve(nDep);
auto y0 = std::vector<double>(nDep);
auto y1 = y0;
for (auto j = 0*nDep; j < nDep; ++j) {
y .push_back(table.column(desc.tableID, desc.primID, j + 1 + 0*nDep));
dy.push_back(table.column(desc.tableID, desc.primID, j + 1 + 1*nDep));
y1[j] = *y.back(); ++y.back();
// Store derivatives at right interval end-point.
++dy.back();
}
using std::swap;
auto x1 = *x; ++x; auto x0 = 0 * x1;
// Recall: Number of slope intervals one less than number of active
// table rows.
for (auto n = desc.numActRows - 1, i = 0*n; i < n; ++i) {
// Save previous right-hand point as this left-hand point, clear
// space for new right-hand point.
swap(x0, x1); x1 = *x; ++x;
swap(y0, y1);
const auto dx = x1 - x0;
for (auto j = 0*nDep; j < nDep; ++j) {
y1[j] = *y[j];
const auto delta = y1[j] - y0[j];
// Choice for dx==0 somewhat debatable.
*dy[j] = (std::abs(dx) > 0.0) ? (delta / dx) : 0.0;
// Advance column iterators;
++y[j]; ++dy[j];
}
}
}

View File

@ -0,0 +1,61 @@
/*
Copyright 2016 Statoil ASA.
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 <opm/parser/eclipse/EclipseState/EclipseState.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/CompletionSet.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Completion.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Well.hpp>
#include <opm/parser/eclipse/EclipseState/Eclipse3DProperties.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/GridProperty.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/EclipseGrid.hpp>
#include <opm/output/eclipse/RegionCache.hpp>
namespace Opm {
namespace out {
RegionCache::RegionCache(const Eclipse3DProperties& properties, const EclipseGrid& grid, const Schedule& schedule) {
const auto& fipnum = properties.getIntGridProperty("FIPNUM");
const auto& wells = schedule.getWells();
for (const auto& well : wells) {
const auto& completions = well->getCompletions( );
for (const auto& c : completions) {
size_t global_index = grid.getGlobalIndex( c.getI() , c.getJ() , c.getK());
if (grid.cellActive( global_index )) {
size_t active_index = grid.activeIndex( global_index );
int region_id =fipnum.iget( global_index );
auto& well_index_list = this->completion_map[ region_id ];
well_index_list.push_back( { well->name() , active_index } );
}
}
}
}
const std::vector<std::pair<std::string,size_t>>& RegionCache::completions( int region_id ) const {
const auto iter = this->completion_map.find( region_id );
if (iter == this->completion_map.end())
return this->completions_empty;
else
return iter->second;
}
}
}

View File

@ -0,0 +1,597 @@
/*
Copyright (c) 2016 Statoil ASA
Copyright (c) 2013-2015 Andreas Lauser
Copyright (c) 2013 SINTEF ICT, Applied Mathematics.
Copyright (c) 2013 Uni Research AS
Copyright (c) 2015 IRIS AS
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 <string>
#include <vector>
#include <opm/parser/eclipse/EclipseState/Grid/EclipseGrid.hpp>
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
#include <opm/output/eclipse/RestartIO.hpp>
#include <ert/ecl/EclKW.hpp>
#include <ert/ecl/FortIO.hpp>
#include <ert/ecl/EclFilename.hpp>
#include <ert/ecl/ecl_kw_magic.h>
#include <ert/ecl/ecl_init_file.h>
#include <ert/ecl/ecl_file.h>
#include <ert/ecl/ecl_kw.h>
#include <ert/ecl/ecl_type.h>
#include <ert/ecl/ecl_grid.h>
#include <ert/ecl/ecl_util.h>
#include <ert/ecl/ecl_rft_file.h>
#include <ert/ecl/ecl_file_view.h>
#include <ert/ecl_well/well_const.h>
#include <ert/ecl/ecl_rsthead.h>
#include <ert/util/util.h>
#define OPM_XWEL "OPM_XWEL"
#define OPM_IWEL "OPM_IWEL"
namespace Opm {
namespace RestartIO {
namespace {
static const int NIWELZ = 11; //Number of data elements per well in IWEL array in restart file
static const int NZWELZ = 3; //Number of 8-character words per well in ZWEL array restart file
static const int NICONZ = 15; //Number of data elements per completion in ICON array restart file
/**
* The constants NIWELZ and NZWELZ referes to the number of
* elements per well that we write to the IWEL and ZWEL eclipse
* restart file data arrays. The constant NICONZ refers to the
* number of elements per completion in the eclipse restart file
* ICON data array.These numbers are written to the INTEHEAD
* header.
*
* Observe that all of these values are our "current-best-guess"
* for how many numbers are needed; there might very well be third
* party applications out there which have a hard expectation for
* these values.
*/
inline int to_ert_welltype( const Well& well, size_t timestep ) {
if( well.isProducer( timestep ) ) return IWEL_PRODUCER;
switch( well.getInjectionProperties( timestep ).injectorType ) {
case WellInjector::WATER:
return IWEL_WATER_INJECTOR;
case WellInjector::GAS:
return IWEL_GAS_INJECTOR;
case WellInjector::OIL:
return IWEL_OIL_INJECTOR;
default:
return IWEL_UNDOCUMENTED_ZERO;
}
}
std::vector<double> double_vector( const ecl_kw_type * ecl_kw ) {
size_t size = ecl_kw_get_size( ecl_kw );
if (ecl_type_get_type( ecl_kw_get_data_type( ecl_kw ) ) == ECL_DOUBLE_TYPE ) {
const double * ecl_data = ecl_kw_get_double_ptr( ecl_kw );
return { ecl_data , ecl_data + size };
} else {
const float * ecl_data = ecl_kw_get_float_ptr( ecl_kw );
return { ecl_data , ecl_data + size };
}
}
inline data::Solution restoreSOLUTION( ecl_file_view_type* file_view,
const std::map<std::string, RestartKey>& keys,
const UnitSystem& units,
int numcells) {
data::Solution sol;
for (const auto& pair : keys) {
const std::string& key = pair.first;
UnitSystem::measure dim = pair.second.dim;
bool required = pair.second.required;
if( !ecl_file_view_has_kw( file_view, key.c_str() ) ) {
if (required)
throw std::runtime_error("Read of restart file: "
"File does not contain "
+ key
+ " data" );
else
continue;
}
const ecl_kw_type * ecl_kw = ecl_file_view_iget_named_kw( file_view , key.c_str() , 0 );
if( ecl_kw_get_size(ecl_kw) != numcells)
throw std::runtime_error("Restart file: Could not restore "
+ std::string( ecl_kw_get_header( ecl_kw ) )
+ ", mismatched number of cells" );
std::vector<double> data = double_vector( ecl_kw );
units.to_si( dim , data );
sol.insert( key, dim, data , data::TargetType::RESTART_SOLUTION );
}
return sol;
}
using rt = data::Rates::opt;
data::Wells restore_wells( const ecl_kw_type * opm_xwel,
const ecl_kw_type * opm_iwel,
int restart_step,
const EclipseState& es,
const EclipseGrid& grid,
const Schedule& schedule) {
const auto& sched_wells = schedule.getWells( restart_step );
std::vector< rt > phases;
{
const auto& phase = es.runspec().phases();
if( phase.active( Phase::WATER ) ) phases.push_back( rt::wat );
if( phase.active( Phase::OIL ) ) phases.push_back( rt::oil );
if( phase.active( Phase::GAS ) ) phases.push_back( rt::gas );
}
const auto well_size = [&]( size_t acc, const Well* w ) {
return acc
+ 2 + phases.size()
+ ( w->getCompletions( restart_step ).size()
* (phases.size() + data::Completion::restart_size) );
};
const auto expected_xwel_size = std::accumulate( sched_wells.begin(),
sched_wells.end(),
0,
well_size );
if( ecl_kw_get_size( opm_xwel ) != expected_xwel_size ) {
throw std::runtime_error(
"Mismatch between OPM_XWEL and deck; "
"OPM_XWEL size was " + std::to_string( ecl_kw_get_size( opm_xwel ) ) +
", expected " + std::to_string( expected_xwel_size ) );
}
if( ecl_kw_get_size( opm_iwel ) != int(sched_wells.size()) )
throw std::runtime_error(
"Mismatch between OPM_IWEL and deck; "
"OPM_IWEL size was " + std::to_string( ecl_kw_get_size( opm_iwel ) ) +
", expected " + std::to_string( sched_wells.size() ) );
data::Wells wells;
const double * opm_xwel_data = ecl_kw_get_double_ptr( opm_xwel );
const int * opm_iwel_data = ecl_kw_get_int_ptr( opm_iwel );
for( const auto* sched_well : sched_wells ) {
data::Well& well = wells[ sched_well->name() ];
well.bhp = *opm_xwel_data++;
well.temperature = *opm_xwel_data++;
well.control = *opm_iwel_data++;
for( auto phase : phases )
well.rates.set( phase, *opm_xwel_data++ );
for( const auto& sc : sched_well->getCompletions( restart_step ) ) {
const auto i = sc.getI(), j = sc.getJ(), k = sc.getK();
if( !grid.cellActive( i, j, k ) || sc.getState() == WellCompletion::SHUT ) {
opm_xwel_data += data::Completion::restart_size + phases.size();
continue;
}
const auto active_index = grid.activeIndex( i, j, k );
well.completions.emplace_back();
auto& completion = well.completions.back();
completion.index = active_index;
completion.pressure = *opm_xwel_data++;
completion.reservoir_rate = *opm_xwel_data++;
for( auto phase : phases )
completion.rates.set( phase, *opm_xwel_data++ );
}
}
return wells;
}
}
/* should take grid as argument because it may be modified from the simulator */
RestartValue load( const std::string& filename,
int report_step,
const std::map<std::string, RestartKey>& keys,
const EclipseState& es,
const EclipseGrid& grid,
const Schedule& schedule,
const std::map<std::string, bool>& extra_keys) {
const bool unified = ( ERT::EclFiletype( filename ) == ECL_UNIFIED_RESTART_FILE );
ERT::ert_unique_ptr< ecl_file_type, ecl_file_close > file(ecl_file_open( filename.c_str(), 0 ));
ecl_file_view_type * file_view;
if( !file )
throw std::runtime_error( "Restart file " + filename + " not found!" );
if( unified ) {
file_view = ecl_file_get_restart_view( file.get() , -1 , report_step , -1 , -1 );
if (!file_view)
throw std::runtime_error( "Restart file " + filename
+ " does not contain data for report step "
+ std::to_string( report_step ) + "!" );
} else
file_view = ecl_file_get_global_view( file.get() );
const ecl_kw_type * intehead = ecl_file_view_iget_named_kw( file_view , "INTEHEAD", 0 );
const ecl_kw_type * opm_xwel = ecl_file_view_iget_named_kw( file_view , "OPM_XWEL", 0 );
const ecl_kw_type * opm_iwel = ecl_file_view_iget_named_kw( file_view, "OPM_IWEL", 0 );
UnitSystem units( static_cast<ert_ecl_unit_enum>(ecl_kw_iget_int( intehead , INTEHEAD_UNIT_INDEX )));
RestartValue rst_value( restoreSOLUTION( file_view, keys, units , grid.getNumActive( )),
restore_wells( opm_xwel, opm_iwel, report_step , es, grid, schedule));
for (const auto& pair : extra_keys) {
const std::string& key = pair.first;
bool required = pair.second;
if (ecl_file_view_has_kw( file_view , key.c_str())) {
const ecl_kw_type * ecl_kw = ecl_file_view_iget_named_kw( file_view , key.c_str() , 0 );
const double * data_ptr = ecl_kw_get_double_ptr( ecl_kw );
const double * end_ptr = data_ptr + ecl_kw_get_size( ecl_kw );
rst_value.extra[ key ] = { data_ptr, end_ptr };
} else if (required)
throw std::runtime_error("No such key in file: " + key);
}
return rst_value;
}
namespace {
std::vector<int> serialize_ICON( int report_step,
int ncwmax,
const std::vector<const Well*>& sched_wells) {
size_t well_offset = 0;
std::vector<int> data( sched_wells.size() * ncwmax * NICONZ , 0 );
for (const Well* well : sched_wells) {
const auto& completions = well->getCompletions( report_step );
size_t completion_offset = 0;
for( const auto& completion : completions) {
size_t offset = well_offset + completion_offset;
data[ offset + ICON_IC_INDEX ] = 1;
data[ offset + ICON_I_INDEX ] = completion.getI() + 1;
data[ offset + ICON_J_INDEX ] = completion.getJ() + 1;
data[ offset + ICON_K_INDEX ] = completion.getK() + 1;
data[ offset + ICON_DIRECTION_INDEX ] = completion.getDirection();
{
const auto open = WellCompletion::StateEnum::OPEN;
data[ offset + ICON_STATUS_INDEX ] = completion.getState() == open
? 1
: 0;
}
completion_offset += NICONZ;
}
well_offset += ncwmax * NICONZ;
}
return data;
}
std::vector<int> serialize_IWEL( size_t step,
const std::vector<const Well *>& wells) {
std::vector<int> data( wells.size() * NIWELZ , 0 );
size_t offset = 0;
for (const auto well : wells) {
const auto& completions = well->getCompletions( step );
data[ offset + IWEL_HEADI_INDEX ] = well->getHeadI( step ) + 1;
data[ offset + IWEL_HEADJ_INDEX ] = well->getHeadJ( step ) + 1;
data[ offset + IWEL_CONNECTIONS_INDEX ] = completions.size();
data[ offset + IWEL_GROUP_INDEX ] = 1;
data[ offset + IWEL_TYPE_INDEX ] = to_ert_welltype( *well, step );
data[ offset + IWEL_STATUS_INDEX ] =
well->getStatus( step ) == WellCommon::OPEN ? 1 : 0;
offset += NIWELZ;
}
return data;
}
std::vector< int > serialize_OPM_IWEL( const data::Wells& wells,
const std::vector< const Well* >& sched_wells ) {
const auto getctrl = [&]( const Well* w ) {
const auto itr = wells.find( w->name() );
return itr == wells.end() ? 0 : itr->second.control;
};
std::vector< int > iwel( sched_wells.size(), 0.0 );
std::transform( sched_wells.begin(), sched_wells.end(), iwel.begin(), getctrl );
return iwel;
}
std::vector< double > serialize_OPM_XWEL( const data::Wells& wells,
int report_step,
const std::vector< const Well* >& sched_wells,
const Phases& phase_spec,
const EclipseGrid& grid ) {
using rt = data::Rates::opt;
std::vector< rt > phases;
if( phase_spec.active( Phase::WATER ) ) phases.push_back( rt::wat );
if( phase_spec.active( Phase::OIL ) ) phases.push_back( rt::oil );
if( phase_spec.active( Phase::GAS ) ) phases.push_back( rt::gas );
std::vector< double > xwel;
for( const auto* sched_well : sched_wells ) {
if( wells.count( sched_well->name() ) == 0 || sched_well->getStatus(report_step) == Opm::WellCommon::SHUT) {
const auto elems = (sched_well->getCompletions( report_step ).size()
* (phases.size() + data::Completion::restart_size))
+ 2 /* bhp, temperature */
+ phases.size();
// write zeros if no well data is provided or it is shut
xwel.insert( xwel.end(), elems, 0.0 );
continue;
}
const auto& well = wells.at( sched_well->name() );
xwel.push_back( well.bhp );
xwel.push_back( well.temperature );
for( auto phase : phases )
xwel.push_back( well.rates.get( phase ) );
for( const auto& sc : sched_well->getCompletions( report_step ) ) {
const auto i = sc.getI(), j = sc.getJ(), k = sc.getK();
const auto rs_size = phases.size() + data::Completion::restart_size;
if( !grid.cellActive( i, j, k ) || sc.getState() == WellCompletion::SHUT ) {
xwel.insert( xwel.end(), rs_size, 0.0 );
continue;
}
const auto active_index = grid.activeIndex( i, j, k );
const auto at_index = [=]( const data::Completion& c ) {
return c.index == active_index;
};
const auto& completion = std::find_if( well.completions.begin(),
well.completions.end(),
at_index );
if( completion == well.completions.end() ) {
xwel.insert( xwel.end(), rs_size, 0.0 );
continue;
}
xwel.push_back( completion->pressure );
xwel.push_back( completion->reservoir_rate );
for( auto phase : phases )
xwel.push_back( completion->rates.get( phase ) );
}
}
return xwel;
};
std::vector<const char*> serialize_ZWEL( const std::vector<const Well *>& wells) {
std::vector<const char*> data( wells.size( ) * NZWELZ , "");
size_t offset = 0;
for (const auto& well : wells) {
data[ offset ] = well->name().c_str();
offset += NZWELZ;
}
return data;
}
template< typename T >
void write_kw(ecl_rst_file_type * rst_file , ERT::EclKW< T >&& kw) {
ecl_rst_file_add_kw( rst_file, kw.get() );
}
void writeHeader(ecl_rst_file_type * rst_file,
int report_step,
time_t posix_time,
double sim_days,
int ert_phase_mask,
const UnitSystem& units,
const Schedule& schedule,
const EclipseGrid& grid) {
ecl_rsthead_type rsthead_data = {};
rsthead_data.sim_time = posix_time;
rsthead_data.nactive = grid.getNumActive();
rsthead_data.nx = grid.getNX();
rsthead_data.ny = grid.getNY();
rsthead_data.nz = grid.getNZ();
rsthead_data.nwells = schedule.numWells(report_step);
rsthead_data.niwelz = NIWELZ;
rsthead_data.nzwelz = NZWELZ;
rsthead_data.niconz = NICONZ;
rsthead_data.ncwmax = schedule.getMaxNumCompletionsForWells(report_step);
rsthead_data.phase_sum = ert_phase_mask;
rsthead_data.sim_days = sim_days;
rsthead_data.unit_system = units.getEclType( );
ecl_util_set_date_values( rsthead_data.sim_time,
&rsthead_data.day,
&rsthead_data.month,
&rsthead_data.year );
ecl_rst_file_fwrite_header( rst_file, report_step , &rsthead_data );
}
ERT::ert_unique_ptr< ecl_kw_type, ecl_kw_free > ecl_kw( const std::string& kw, const std::vector<double>& data, bool write_double) {
ERT::ert_unique_ptr< ecl_kw_type, ecl_kw_free > kw_ptr;
if (write_double) {
ecl_kw_type * ecl_kw = ecl_kw_alloc( kw.c_str() , data.size() , ECL_DOUBLE );
ecl_kw_set_memcpy_data( ecl_kw , data.data() );
kw_ptr.reset( ecl_kw );
} else {
ecl_kw_type * ecl_kw = ecl_kw_alloc( kw.c_str() , data.size() , ECL_FLOAT );
float * float_data = ecl_kw_get_float_ptr( ecl_kw );
for (size_t i=0; i < data.size(); i++)
float_data[i] = data[i];
kw_ptr.reset( ecl_kw );
}
return kw_ptr;
}
void writeSolution(ecl_rst_file_type* rst_file, const data::Solution& solution, bool write_double) {
ecl_rst_file_start_solution( rst_file );
for (const auto& elm: solution) {
if (elm.second.target == data::TargetType::RESTART_SOLUTION)
ecl_rst_file_add_kw( rst_file , ecl_kw(elm.first, elm.second.data, write_double).get());
}
ecl_rst_file_end_solution( rst_file );
for (const auto& elm: solution) {
if (elm.second.target == data::TargetType::RESTART_AUXILIARY)
ecl_rst_file_add_kw( rst_file , ecl_kw(elm.first, elm.second.data, write_double).get());
}
}
void writeExtraData(ecl_rst_file_type* rst_file, const std::map<std::string,std::vector<double>>& extra_data) {
for (const auto& pair : extra_data) {
const std::string& key = pair.first;
const std::vector<double>& data = pair.second;
{
ecl_kw_type * ecl_kw = ecl_kw_alloc_new_shared( key.c_str() , data.size() , ECL_DOUBLE , const_cast<double *>(data.data()));
ecl_rst_file_add_kw( rst_file , ecl_kw);
ecl_kw_free( ecl_kw );
}
}
}
void writeWell(ecl_rst_file_type* rst_file, int report_step, const EclipseState& es , const EclipseGrid& grid, const Schedule& schedule, const data::Wells& wells) {
const auto sched_wells = schedule.getWells(report_step);
const auto& phases = es.runspec().phases();
const size_t ncwmax = schedule.getMaxNumCompletionsForWells(report_step);
const auto opm_xwel = serialize_OPM_XWEL( wells, report_step, sched_wells, phases, grid );
const auto opm_iwel = serialize_OPM_IWEL( wells, sched_wells );
const auto iwel_data = serialize_IWEL(report_step, sched_wells);
const auto icon_data = serialize_ICON(report_step , ncwmax, sched_wells);
const auto zwel_data = serialize_ZWEL( sched_wells );
write_kw( rst_file, ERT::EclKW< int >( IWEL_KW, iwel_data) );
write_kw( rst_file, ERT::EclKW< const char* >(ZWEL_KW, zwel_data ) );
write_kw( rst_file, ERT::EclKW< double >( OPM_XWEL, opm_xwel ) );
write_kw( rst_file, ERT::EclKW< int >( OPM_IWEL, opm_iwel ) );
write_kw( rst_file, ERT::EclKW< int >( ICON_KW, icon_data ) );
}
void checkSaveArguments(const data::Solution& cells,
const EclipseGrid& grid,
const std::map<std::string, std::vector<double>>& extra_data) {
const std::set<std::string> reserved_keys = {"LOGIHEAD", "INTEHEAD" ,"DOUBHEAD", "IWEL", "XWEL","ICON", "XCON" , "OPM_IWEL" , "OPM_XWEL", "ZWEL"};
for (const auto& pair : extra_data) {
const std::string& key = pair.first;
if (key.size() > 8)
throw std::runtime_error("The keys in extra data must have maximum eight characaters");
if (cells.has( key ))
throw std::runtime_error("The keys used must unique across Solution and extra_data");
if (reserved_keys.find( key ) != reserved_keys.end())
throw std::runtime_error("The extra_data uses a reserved key");
}
for (const auto& elm: cells)
if (elm.second.data.size() != grid.getNumActive())
throw std::runtime_error("Wrong size on solution vector: " + elm.first);
}
}
void save(const std::string& filename,
int report_step,
double seconds_elapsed,
data::Solution cells,
data::Wells wells,
const EclipseState& es,
const EclipseGrid& grid,
const Schedule& schedule,
std::map<std::string, std::vector<double>> extra_data,
bool write_double)
{
checkSaveArguments( cells, grid, extra_data );
{
int ert_phase_mask = es.runspec().eclPhaseMask( );
const auto& units = es.getUnits();
time_t posix_time = schedule.posixStartTime() + seconds_elapsed;
const auto sim_time = units.from_si( UnitSystem::measure::time, seconds_elapsed );
ERT::ert_unique_ptr< ecl_rst_file_type, ecl_rst_file_close > rst_file;
if (ERT::EclFiletype( filename ) == ECL_UNIFIED_RESTART_FILE)
rst_file.reset( ecl_rst_file_open_write_seek( filename.c_str(), report_step ) );
else
rst_file.reset( ecl_rst_file_open_write( filename.c_str() ) );
cells.convertFromSI( units );
writeHeader( rst_file.get() , report_step, posix_time , sim_time, ert_phase_mask, units, schedule , grid );
writeWell( rst_file.get() , report_step, es , grid, schedule, wells);
writeSolution( rst_file.get() , cells , write_double );
writeExtraData( rst_file.get() , extra_data );
}
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,670 @@
/*
Copyright 2016 Statoil ASA.
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 <opm/test_util/EclFilesComparator.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <stdio.h>
#include <set>
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <cmath>
#include <numeric>
#include <ert/ecl/ecl_file.h>
#include <ert/ecl/ecl_grid.h>
#include <ert/ecl/ecl_type.h>
#include <ert/ecl_well/well_info.h>
// helper macro to handle error throws or not
#define HANDLE_ERROR(type, message) \
{ \
if (throwOnError) \
OPM_THROW(type, message); \
else { \
std::cerr << message << std::endl; \
++num_errors; \
} \
}
namespace {
/*
This is just a basic survival test; we verify that the ERT well
loader which is used in Resinsight can load the well description
from the restart file.
*/
void loadWells( const ecl_grid_type * grid , ecl_file_type * rst_file ) {
well_info_type * well_info = well_info_alloc( grid );
well_info_add_UNRST_wells2( well_info , ecl_file_get_global_view( rst_file ), true );
well_info_free( well_info );
}
}
void ECLFilesComparator::keywordValidForComparing(const std::string& keyword) const {
auto it = std::find(keywords1.begin(), keywords1.end(), keyword);
if (it == keywords1.end()) {
OPM_THROW(std::runtime_error, "Keyword " << keyword << " does not exist in first file.");
}
it = find(keywords2.begin(), keywords2.end(), keyword);
if (it == keywords2.end()) {
OPM_THROW(std::runtime_error, "Keyword " << keyword << " does not exist in second file.");
}
}
unsigned int ECLFilesComparator::getEclKeywordData(ecl_kw_type*& ecl_kw1, ecl_kw_type*& ecl_kw2, const std::string& keyword, int occurrence1, int occurrence2) const {
ecl_kw1 = ecl_file_iget_named_kw(ecl_file1, keyword.c_str(), occurrence1);
ecl_kw2 = ecl_file_iget_named_kw(ecl_file2, keyword.c_str(), occurrence2);
const unsigned int numCells1 = ecl_kw_get_size(ecl_kw1);
const unsigned int numCells2 = ecl_kw_get_size(ecl_kw2);
if (numCells1 != numCells2) {
OPM_THROW(std::runtime_error, "For keyword " << keyword << ":"
<< "\nOccurrence in first file " << occurrence1
<< "\nOccurrence in second file " << occurrence2
<< "\nCells in first file: " << numCells1
<< "\nCells in second file: " << numCells2
<< "\nThe number of cells differ.");
}
return numCells1;
}
template <typename T>
void ECLFilesComparator::printValuesForCell(const std::string& /*keyword*/, int occurrence1, int occurrence2, size_t cell, const T& value1, const T& value2) const {
int i, j, k;
ecl_grid_get_ijk1(ecl_grid1, cell, &i, &j, &k);
// Coordinates from this function are zero-based, hence incrementing
i++, j++, k++;
std::cout << std::endl
<< "Occurrence in first file = " << occurrence1 << "\n"
<< "Occurrence in second file = " << occurrence2 << "\n"
<< "Grid coordinate = (" << i << ", " << j << ", " << k << ")" << "\n"
<< "(first value, second value) = (" << value1 << ", " << value2 << ")\n\n";
}
template void ECLFilesComparator::printValuesForCell<bool> (const std::string& keyword, int occurrence1, int occurrence2, size_t cell, const bool& value1, const bool& value2) const;
template void ECLFilesComparator::printValuesForCell<int> (const std::string& keyword, int occurrence1, int occurrence2, size_t cell, const int& value1, const int& value2) const;
template void ECLFilesComparator::printValuesForCell<double> (const std::string& keyword, int occurrence1, int occurrence2, size_t cell, const double& value1, const double& value2) const;
template void ECLFilesComparator::printValuesForCell<std::string>(const std::string& keyword, int occurrence1, int occurrence2, size_t cell, const std::string& value1, const std::string& value2) const;
ECLFilesComparator::ECLFilesComparator(int file_type_arg, const std::string& basename1,
const std::string& basename2,
double absToleranceArg, double relToleranceArg) :
file_type(file_type_arg), absTolerance(absToleranceArg), relTolerance(relToleranceArg) {
std::string file1, file2;
if (file_type == ECL_UNIFIED_RESTART_FILE) {
file1 = basename1 + ".UNRST";
file2 = basename2 + ".UNRST";
}
else if (file_type == ECL_INIT_FILE) {
file1 = basename1 + ".INIT";
file2 = basename2 + ".INIT";
}
else if (file_type == ECL_RFT_FILE) {
file1 = basename1 + ".RFT";
file2 = basename2 + ".RFT";
}
else {
OPM_THROW(std::invalid_argument, "Unsupported filetype sent to ECLFilesComparator's constructor."
<< "Only unified restart (.UNRST), initial (.INIT) and .RFT files are supported.");
}
ecl_file1 = ecl_file_open(file1.c_str(), 0);
ecl_file2 = ecl_file_open(file2.c_str(), 0);
ecl_grid1 = ecl_grid_load_case(basename1.c_str());
ecl_grid2 = ecl_grid_load_case(basename2.c_str());
if (ecl_file1 == nullptr) {
OPM_THROW(std::invalid_argument, "Error opening first file: " << file1);
}
if (ecl_file2 == nullptr) {
OPM_THROW(std::invalid_argument, "Error opening second file: " << file2);
}
if (ecl_grid1 == nullptr) {
OPM_THROW(std::invalid_argument, "Error opening first grid file: " << basename1);
}
if (ecl_grid2 == nullptr) {
OPM_THROW(std::invalid_argument, "Error opening second grid file. " << basename2);
}
unsigned int numKeywords1 = ecl_file_get_num_distinct_kw(ecl_file1);
unsigned int numKeywords2 = ecl_file_get_num_distinct_kw(ecl_file2);
keywords1.reserve(numKeywords1);
keywords2.reserve(numKeywords2);
for (unsigned int i = 0; i < numKeywords1; ++i) {
std::string keyword(ecl_file_iget_distinct_kw(ecl_file1, i));
keywords1.push_back(keyword);
}
for (unsigned int i = 0; i < numKeywords2; ++i) {
std::string keyword(ecl_file_iget_distinct_kw(ecl_file2, i));
keywords2.push_back(keyword);
}
if (file_type == ECL_UNIFIED_RESTART_FILE) {
loadWells( ecl_grid1 , ecl_file1 );
loadWells( ecl_grid2 , ecl_file2 );
}
}
ECLFilesComparator::~ECLFilesComparator() {
ecl_file_close(ecl_file1);
ecl_file_close(ecl_file2);
ecl_grid_free(ecl_grid1);
ecl_grid_free(ecl_grid2);
}
void ECLFilesComparator::printKeywords() const {
std::cout << "\nKeywords in the first file:\n";
for (const auto& it : keywords1) {
std::cout << std::setw(15) << std::left << it << " of type " << ecl_type_get_name( ecl_file_iget_named_data_type(ecl_file1, it.c_str(), 0)) << std::endl;
}
std::cout << "\nKeywords in second file:\n";
for (const auto& it : keywords2) {
std::cout << std::setw(15) << std::left << it << " of type " << ecl_type_get_name( ecl_file_iget_named_data_type(ecl_file2, it.c_str(), 0)) << std::endl;
}
}
void ECLFilesComparator::printKeywordsDifference() const {
std::vector<std::string> common;
std::vector<std::string> uncommon;
const std::vector<std::string>* keywordsShort = &keywords1;
const std::vector<std::string>* keywordsLong = &keywords2;
if (keywords1.size() > keywords2.size()) {
keywordsLong = &keywords1;
keywordsShort = &keywords2;
}
for (const auto& it : *keywordsLong) {
const auto position = std::find(keywordsShort->begin(), keywordsShort->end(), it);
if (position != keywordsShort->end()) {
common.push_back(*position);
}
else {
uncommon.push_back(it);
}
}
std::cout << "\nCommon keywords for the two cases:\n";
for (const auto& it : common) std::cout << it << std::endl;
std::cout << "\nUncommon keywords for the two cases:\n";
for (const auto& it : uncommon) std::cout << it << std::endl;
}
Deviation ECLFilesComparator::calculateDeviations(double val1, double val2) {
val1 = std::abs(val1);
val2 = std::abs(val2);
Deviation deviation;
if (val1 != 0 || val2 != 0) {
deviation.abs = std::abs(val1 - val2);
if (val1 != 0 && val2 != 0) {
deviation.rel = deviation.abs/(std::max(val1, val2));
}
}
return deviation;
}
double ECLFilesComparator::median(std::vector<double> vec) {
if (vec.empty()) {
return 0;
}
else {
size_t n = vec.size()/2;
nth_element(vec.begin(), vec.begin() + n, vec.end());
if (vec.size() % 2 == 0) {
return 0.5*(vec[n-1]+vec[n]);
}
else {
return vec[n];
}
}
}
double ECLFilesComparator::average(const std::vector<double>& vec) {
if (vec.empty()) {
return 0;
}
double sum = std::accumulate(vec.begin(), vec.end(), 0.0);
return sum/vec.size();
}
void RegressionTest::printResultsForKeyword(const std::string& keyword) const {
std::cout << "Deviation results for keyword " << keyword << " of type "
<< ecl_type_get_name(ecl_file_iget_named_data_type(ecl_file1, keyword.c_str(), 0))
<< ":\n";
const double absDeviationAverage = average(absDeviation);
const double relDeviationAverage = average(relDeviation);
std::cout << "Average absolute deviation = " << absDeviationAverage << std::endl;
std::cout << "Median absolute deviation = " << median(absDeviation) << std::endl;
std::cout << "Average relative deviation = " << relDeviationAverage << std::endl;
std::cout << "Median relative deviation = " << median(relDeviation) << "\n\n";
}
void RegressionTest::boolComparisonForOccurrence(const std::string& keyword,
int occurrence1, int occurrence2) const {
ecl_kw_type* ecl_kw1 = nullptr;
ecl_kw_type* ecl_kw2 = nullptr;
const unsigned int numCells = getEclKeywordData(ecl_kw1, ecl_kw2, keyword, occurrence1, occurrence2);
for (size_t cell = 0; cell < numCells; cell++) {
bool data1 = ecl_kw_iget_bool(ecl_kw1, cell);
bool data2 = ecl_kw_iget_bool(ecl_kw2, cell);
if (data1 != data2) {
printValuesForCell(keyword, occurrence1, occurrence2, cell, data1, data2);
HANDLE_ERROR(std::runtime_error, "Values of bool type differ.");
}
}
}
void RegressionTest::charComparisonForOccurrence(const std::string& keyword, int occurrence1, int occurrence2) const {
ecl_kw_type* ecl_kw1 = nullptr;
ecl_kw_type* ecl_kw2 = nullptr;
const unsigned int numCells = getEclKeywordData(ecl_kw1, ecl_kw2, keyword, occurrence1, occurrence2);
for (size_t cell = 0; cell < numCells; cell++) {
std::string data1(ecl_kw_iget_char_ptr(ecl_kw1, cell));
std::string data2(ecl_kw_iget_char_ptr(ecl_kw2, cell));
if (data1.compare(data2) != 0) {
printValuesForCell(keyword, occurrence1, occurrence2, cell, data1, data2);
HANDLE_ERROR(std::runtime_error, "Values of char type differ.");
}
}
}
void RegressionTest::intComparisonForOccurrence(const std::string& keyword, int occurrence1, int occurrence2) const {
ecl_kw_type* ecl_kw1 = nullptr;
ecl_kw_type* ecl_kw2 = nullptr;
const unsigned int numCells = getEclKeywordData(ecl_kw1, ecl_kw2, keyword, occurrence1, occurrence2);
std::vector<int> values1(numCells), values2(numCells);
ecl_kw_get_memcpy_int_data(ecl_kw1, values1.data());
ecl_kw_get_memcpy_int_data(ecl_kw2, values2.data());
for (size_t cell = 0; cell < values1.size(); cell++) {
if (values1[cell] != values2[cell]) {
printValuesForCell(keyword, occurrence1, occurrence2, cell, values1[cell], values2[cell]);
HANDLE_ERROR(std::runtime_error, "Values of int type differ.");
}
}
}
void RegressionTest::doubleComparisonForOccurrence(const std::string& keyword, int occurrence1, int occurrence2) {
ecl_kw_type* ecl_kw1 = nullptr;
ecl_kw_type* ecl_kw2 = nullptr;
const unsigned int numCells = getEclKeywordData(ecl_kw1, ecl_kw2, keyword, occurrence1, occurrence2);
std::vector<double> values1(numCells), values2(numCells);
ecl_kw_get_data_as_double(ecl_kw1, values1.data());
ecl_kw_get_data_as_double(ecl_kw2, values2.data());
auto it = std::find(keywordDisallowNegatives.begin(), keywordDisallowNegatives.end(), keyword);
for (size_t cell = 0; cell < values1.size(); cell++) {
deviationsForCell(values1[cell], values2[cell], keyword, occurrence1, occurrence2, cell, it == keywordDisallowNegatives.end());
}
}
void RegressionTest::deviationsForCell(double val1, double val2, const std::string& keyword, int occurrence1, int occurrence2, size_t cell, bool allowNegativeValues) {
double absTolerance = getAbsTolerance();
double relTolerance = getRelTolerance();
if (!allowNegativeValues) {
if (val1 < 0) {
if (std::abs(val1) > absTolerance) {
printValuesForCell(keyword, occurrence1, occurrence2, cell, val1, val2);
HANDLE_ERROR(std::runtime_error, "Negative value in first file, "
<< "which in absolute value exceeds the absolute tolerance of " << absTolerance << ".");
}
val1 = 0;
}
if (val2 < 0) {
if (std::abs(val2) > absTolerance) {
printValuesForCell(keyword, occurrence1, occurrence2, cell, val1, val2);
HANDLE_ERROR(std::runtime_error, "Negative value in second file, "
<< "which in absolute value exceeds the absolute tolerance of " << absTolerance << ".");
}
val2 = 0;
}
}
Deviation dev = calculateDeviations(val1, val2);
if (dev.abs > absTolerance && dev.rel > relTolerance) {
printValuesForCell(keyword, occurrence1, occurrence2, cell, val1, val2);
HANDLE_ERROR(std::runtime_error, "Deviations exceed tolerances."
<< "\nThe absolute deviation is " << dev.abs << ", and the tolerance limit is " << absTolerance << "."
<< "\nThe relative deviation is " << dev.rel << ", and the tolerance limit is " << relTolerance << ".");
}
if (dev.abs != -1) {
absDeviation.push_back(dev.abs);
}
if (dev.rel != -1) {
relDeviation.push_back(dev.rel);
}
}
void RegressionTest::gridCompare() const {
double absTolerance = getAbsTolerance();
double relTolerance = getRelTolerance();
const unsigned int globalGridCount1 = ecl_grid_get_global_size(ecl_grid1);
const unsigned int activeGridCount1 = ecl_grid_get_active_size(ecl_grid1);
const unsigned int globalGridCount2 = ecl_grid_get_global_size(ecl_grid2);
const unsigned int activeGridCount2 = ecl_grid_get_active_size(ecl_grid2);
if (globalGridCount1 != globalGridCount2) {
OPM_THROW(std::runtime_error, "In grid file:"
<< "\nCells in first file: " << globalGridCount1
<< "\nCells in second file: " << globalGridCount2
<< "\nThe number of cells differ.");
}
if (activeGridCount1 != activeGridCount2) {
OPM_THROW(std::runtime_error, "In grid file:"
<< "\nCells in first file: " << activeGridCount1
<< "\nCells in second file: " << activeGridCount2
<< "\nThe number of cells differ.");
}
for (unsigned int cell = 0; cell < globalGridCount1; ++cell) {
const double cellVolume1 = ecl_grid_get_cell_volume1(ecl_grid1, cell);
const double cellVolume2 = ecl_grid_get_cell_volume1(ecl_grid2, cell);
Deviation dev = calculateDeviations(cellVolume1, cellVolume2);
if (dev.abs > absTolerance && dev.rel > relTolerance) {
int i, j, k;
ecl_grid_get_ijk1(ecl_grid1, cell, &i, &j, &k);
// Coordinates from this function are zero-based, hence incrementing
i++, j++, k++;
HANDLE_ERROR(std::runtime_error, "In grid file: Deviations of cell volume exceed tolerances. "
<< "\nFor cell with coordinate (" << i << ", " << j << ", " << k << "):"
<< "\nCell volume in first file: " << cellVolume1
<< "\nCell volume in second file: " << cellVolume2
<< "\nThe absolute deviation is " << dev.abs << ", and the tolerance limit is " << absTolerance << "."
<< "\nThe relative deviation is " << dev.rel << ", and the tolerance limit is " << relTolerance << ".");
}
}
}
void RegressionTest::results() {
if (keywords1.size() != keywords2.size()) {
std::set<std::string> keys(keywords1.begin() , keywords1.end());
for (const auto& key2: keywords2)
keys.insert( key2 );
for (const auto& key : keys)
fprintf(stderr," %8s:%3d %8s:%3d \n",key.c_str() , ecl_file_get_num_named_kw( ecl_file1 , key.c_str()),
key.c_str() , ecl_file_get_num_named_kw( ecl_file2 , key.c_str()));
OPM_THROW(std::runtime_error, "\nKeywords in first file: " << keywords1.size()
<< "\nKeywords in second file: " << keywords2.size()
<< "\nThe number of keywords differ.");
}
for (const auto& it : keywords1)
resultsForKeyword(it);
}
void RegressionTest::resultsForKeyword(const std::string& keyword) {
keywordValidForComparing(keyword);
const unsigned int occurrences1 = ecl_file_get_num_named_kw(ecl_file1, keyword.c_str());
const unsigned int occurrences2 = ecl_file_get_num_named_kw(ecl_file2, keyword.c_str());
if (!onlyLastOccurrence && occurrences1 != occurrences2) {
OPM_THROW(std::runtime_error, "For keyword " << keyword << ":"
<< "\nKeyword occurrences in first file: " << occurrences1
<< "\nKeyword occurrences in second file: " << occurrences2
<< "\nThe number of occurrences differ.");
}
// Assuming keyword type is constant for every occurrence:
const ecl_type_enum kw_type = ecl_type_get_type( ecl_file_iget_named_data_type(ecl_file1, keyword.c_str(), 0) );
switch(kw_type) {
case ECL_DOUBLE_TYPE:
case ECL_FLOAT_TYPE:
std::cout << "Comparing " << keyword << "...";
if (onlyLastOccurrence) {
doubleComparisonForOccurrence(keyword, occurrences1 - 1, occurrences2 - 1);
}
else {
for (unsigned int occurrence = 0; occurrence < occurrences1; ++occurrence) {
doubleComparisonForOccurrence(keyword, occurrence, occurrence);
}
}
std::cout << "done." << std::endl;
printResultsForKeyword(keyword);
absDeviation.clear();
relDeviation.clear();
return;
case ECL_INT_TYPE:
std::cout << "Comparing " << keyword << "...";
if (onlyLastOccurrence) {
intComparisonForOccurrence(keyword, occurrences1 - 1, occurrences2 - 1);
}
else {
for (unsigned int occurrence = 0; occurrence < occurrences1; ++occurrence) {
intComparisonForOccurrence(keyword, occurrence, occurrence);
}
}
break;
case ECL_CHAR_TYPE:
std::cout << "Comparing " << keyword << "...";
if (onlyLastOccurrence) {
charComparisonForOccurrence(keyword, occurrences1 - 1, occurrences2 - 1);
}
else {
for (unsigned int occurrence = 0; occurrence < occurrences1; ++occurrence) {
charComparisonForOccurrence(keyword, occurrence, occurrence);
}
}
break;
case ECL_BOOL_TYPE:
std::cout << "Comparing " << keyword << "...";
if (onlyLastOccurrence) {
boolComparisonForOccurrence(keyword, occurrences1 - 1, occurrences2 - 1);
}
else {
for (unsigned int occurrence = 0; occurrence < occurrences1; ++occurrence) {
boolComparisonForOccurrence(keyword, occurrence, occurrence);
}
}
break;
case ECL_MESS_TYPE:
std::cout << "\nKeyword " << keyword << " is of type MESS"
<< ", which is not supported in regression test." << "\n\n";
return;
default:
std::cout << "\nKeyword " << keyword << "has undefined type." << std::endl;
return;
}
std::cout << "done." << std::endl;
}
void IntegrationTest::setCellVolumes() {
double absTolerance = getAbsTolerance();
double relTolerance = getRelTolerance();
const unsigned int globalGridCount1 = ecl_grid_get_global_size(ecl_grid1);
const unsigned int activeGridCount1 = ecl_grid_get_active_size(ecl_grid1);
const unsigned int globalGridCount2 = ecl_grid_get_global_size(ecl_grid2);
const unsigned int activeGridCount2 = ecl_grid_get_active_size(ecl_grid2);
if (globalGridCount1 != globalGridCount2) {
OPM_THROW(std::runtime_error, "In grid file:"
<< "\nCells in first file: " << globalGridCount1
<< "\nCells in second file: " << globalGridCount2
<< "\nThe number of global cells differ.");
}
if (activeGridCount1 != activeGridCount2) {
OPM_THROW(std::runtime_error, "In grid file:"
<< "\nCells in first file: " << activeGridCount1
<< "\nCells in second file: " << activeGridCount2
<< "\nThe number of active cells differ.");
}
for (unsigned int cell = 0; cell < globalGridCount1; ++cell) {
const double cellVolume1 = ecl_grid_get_cell_volume1(ecl_grid1, cell);
const double cellVolume2 = ecl_grid_get_cell_volume1(ecl_grid2, cell);
Deviation dev = calculateDeviations(cellVolume1, cellVolume2);
if (dev.abs > absTolerance && dev.rel > relTolerance) {
int i, j, k;
ecl_grid_get_ijk1(ecl_grid1, cell, &i, &j, &k);
// Coordinates from this function are zero-based, hence incrementing
i++, j++, k++;
OPM_THROW(std::runtime_error, "In grid file: Deviations of cell volume exceed tolerances. "
<< "\nFor cell with coordinate (" << i << ", " << j << ", " << k << "):"
<< "\nCell volume in first file: " << cellVolume1
<< "\nCell volume in second file: " << cellVolume2
<< "\nThe absolute deviation is " << dev.abs << ", and the tolerance limit is " << absTolerance << "."
<< "\nThe relative deviation is " << dev.rel << ", and the tolerance limit is " << relTolerance << ".");
} // The second input case is used as reference.
cellVolumes.push_back(cellVolume2);
}
}
void IntegrationTest::initialOccurrenceCompare(const std::string& keyword) {
ecl_kw_type* ecl_kw1 = nullptr;
ecl_kw_type* ecl_kw2 = nullptr;
const unsigned int numCells = getEclKeywordData(ecl_kw1, ecl_kw2, keyword, 0, 0);
std::vector<double> values1(numCells);
initialCellValues.resize(numCells);
ecl_kw_get_data_as_double(ecl_kw1, values1.data());
ecl_kw_get_data_as_double(ecl_kw2, initialCellValues.data());
// This variable sums up the difference between the keyword value for the first case and the keyword value for the second case, for each cell. The sum is weighted with respect to the cell volume of each cell.
double weightedDifference = 0;
// This variable sums up the keyword value for the first case for each cell. The sum is weighted with respect to the cell volume of each cell.
double weightedTotal = 0;
for (size_t cell = 0; cell < initialCellValues.size(); ++cell) {
weightedTotal += initialCellValues[cell]*cellVolumes[cell];
weightedDifference += std::abs(values1[cell] - initialCellValues[cell])*cellVolumes[cell];
}
if (weightedTotal != 0) {
double ratioValue = weightedDifference/weightedTotal;
if ((ratioValue) > getRelTolerance()) {
OPM_THROW(std::runtime_error, "\nFor keyword " << keyword << " and occurrence 0:"
<< "\nThe ratio of the deviation and the total value is " << ratioValue
<< ", which exceeds the relative tolerance of " << getRelTolerance() << "."
<< "\nSee the docs for more information about how the ratio is computed.");
}
}
}
void IntegrationTest::occurrenceCompare(const std::string& keyword, int occurrence) const {
ecl_kw_type* ecl_kw1 = nullptr;
ecl_kw_type* ecl_kw2 = nullptr;
const unsigned int numCells = getEclKeywordData(ecl_kw1, ecl_kw2, keyword, occurrence, occurrence);
std::vector<double> values1(numCells), values2(numCells);
ecl_kw_get_data_as_double(ecl_kw1, values1.data());
ecl_kw_get_data_as_double(ecl_kw2, values2.data());
// This variable sums up the difference between the keyword value for the first case and the keyword value for the second case, for each cell. The sum is weighted with respect to the cell volume of each cell.
double weightedDifference = 0;
// This variable sums up the difference between the keyword value for the occurrence and the initial keyword value for each cell. The sum is weighted with respect to the cell volume of each cell.
double relativeWeightedTotal = 0;
for (size_t cell = 0; cell < values1.size(); ++cell) {
relativeWeightedTotal += std::abs(values1[cell] - initialCellValues[cell])*cellVolumes[cell];
weightedDifference += std::abs(values1[cell] - values2[cell])*cellVolumes[cell];
}
if (relativeWeightedTotal != 0) {
double ratioValue = weightedDifference/relativeWeightedTotal;
if ((ratioValue) > getRelTolerance()) {
OPM_THROW(std::runtime_error, "\nFor keyword " << keyword << " and occurrence " << occurrence << ":"
<< "\nThe ratio of the deviation and the total value is " << ratioValue
<< ", which exceeds the relative tolerance of " << getRelTolerance() << "."
<< "\nSee the docs for more information about how the ratio is computed.");
}
}
}
IntegrationTest::IntegrationTest(const std::string& basename1, const std::string& basename2, double absTolerance, double relTolerance):
ECLFilesComparator(ECL_UNIFIED_RESTART_FILE, basename1, basename2, absTolerance, relTolerance) {
std::cout << "\nUsing cell volumes and keyword values from case " << basename2
<< " as reference." << std::endl << std::endl;
setCellVolumes();
}
bool IntegrationTest::elementInWhitelist(const std::string& keyword) const {
auto it = std::find(keywordWhitelist.begin(), keywordWhitelist.end(), keyword);
return it != keywordWhitelist.end();
}
void IntegrationTest::equalNumKeywords() const {
if (keywords1.size() != keywords2.size()) {
OPM_THROW(std::runtime_error, "\nKeywords in first file: " << keywords1.size()
<< "\nKeywords in second file: " << keywords2.size()
<< "\nThe number of keywords differ.");
}
}
void IntegrationTest::results() {
for (const auto& it : keywordWhitelist)
resultsForKeyword(it);
}
void IntegrationTest::resultsForKeyword(const std::string& keyword) {
std::cout << "Comparing " << keyword << "...";
keywordValidForComparing(keyword);
const unsigned int occurrences1 = ecl_file_get_num_named_kw(ecl_file1, keyword.c_str());
const unsigned int occurrences2 = ecl_file_get_num_named_kw(ecl_file2, keyword.c_str());
if (occurrences1 != occurrences2) {
OPM_THROW(std::runtime_error, "For keyword " << keyword << ":"
<< "\nKeyword occurrences in first file: " << occurrences1
<< "\nKeyword occurrences in second file: " << occurrences2
<< "\nThe number of occurrences differ.");
}
initialOccurrenceCompare(keyword);
for (unsigned int occurrence = 1; occurrence < occurrences1; ++occurrence) {
occurrenceCompare(keyword, occurrence);
}
std::cout << "done." << std::endl;
}

View File

@ -0,0 +1,238 @@
/*
Copyright 2016 Statoil ASA.
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 <opm/test_util/summaryComparator.hpp>
#include <ert/ecl/ecl_sum.h>
#include <ert/util/stringlist.h>
#include <ert/util/int_vector.h>
#include <ert/util/bool_vector.h>
#include <opm/common/ErrorMacros.hpp>
#include <cmath>
#include <numeric>
SummaryComparator::SummaryComparator(const char* basename1, const char* basename2, double absoluteTol, double relativeTol){
ecl_sum1 = ecl_sum_fread_alloc_case(basename1, ":");
ecl_sum2 = ecl_sum_fread_alloc_case(basename2, ":");
if (ecl_sum1 == nullptr || ecl_sum2 == nullptr) {
OPM_THROW(std::runtime_error, "Not able to open files");
}
absoluteTolerance = absoluteTol;
relativeTolerance = relativeTol;
keys1 = stringlist_alloc_new();
keys2 = stringlist_alloc_new();
ecl_sum_select_matching_general_var_list( ecl_sum1 , "*" , this->keys1);
stringlist_sort(this->keys1 , nullptr );
ecl_sum_select_matching_general_var_list( ecl_sum2 , "*" , this->keys2);
stringlist_sort(this->keys2 , nullptr );
if(stringlist_get_size(keys1) <= stringlist_get_size(keys2)){
this->keysShort = this->keys1;
this->keysLong = this->keys2;
}else{
this->keysShort = this->keys2;
this->keysLong = this->keys1;
}
}
SummaryComparator::~SummaryComparator(){
ecl_sum_free(ecl_sum1);
ecl_sum_free(ecl_sum2);
stringlist_free(keys1);
stringlist_free(keys2);
}
Deviation SummaryComparator::calculateDeviations(double val1, double val2){
double absDev;
Deviation deviation;
absDev = std::abs(val1 - val2);
deviation.abs = absDev;
if (val1 != 0 || val2 != 0) {
deviation.rel = absDev/double(std::max(std::abs(val1), std::abs(val2)));
}
return deviation;
}
void SummaryComparator::setTimeVecs(std::vector<double> &timeVec1,
std::vector<double> &timeVec2){
timeVec1.reserve(ecl_sum_get_data_length(ecl_sum1));
for (int time_index = 0; time_index < ecl_sum_get_data_length(ecl_sum1); time_index++){
timeVec1.push_back(ecl_sum_iget_sim_days(ecl_sum1 , time_index ));
}
timeVec2.reserve(ecl_sum_get_data_length(ecl_sum2));
for (int time_index = 0; time_index < ecl_sum_get_data_length(ecl_sum2); time_index++){
timeVec2.push_back(ecl_sum_iget_sim_days(ecl_sum2 , time_index ));
}
}
void SummaryComparator::getDataVecs(std::vector<double> &dataVec1,
std::vector<double> &dataVec2,
const char* keyword){
dataVec1.reserve(ecl_sum_get_data_length(ecl_sum1));
for (int time_index = 0; time_index < ecl_sum_get_data_length(ecl_sum1); time_index++){
dataVec1.push_back(ecl_sum_iget(ecl_sum1, time_index, ecl_sum_get_general_var_params_index( ecl_sum1 , keyword )));
}
dataVec2.reserve(ecl_sum_get_data_length(ecl_sum2));
for (int time_index = 0; time_index < ecl_sum_get_data_length(ecl_sum2); time_index++){
dataVec2.push_back(ecl_sum_iget(ecl_sum2, time_index, ecl_sum_get_general_var_params_index( ecl_sum2 , keyword )));
}
}
void SummaryComparator::setDataSets(const std::vector<double>& timeVec1,
const std::vector<double>& timeVec2){
if(timeVec1.size() < timeVec2.size()){
ecl_sum_fileShort = this->ecl_sum1;
ecl_sum_fileLong = this->ecl_sum2;
}
else{
ecl_sum_fileShort = this->ecl_sum2;
ecl_sum_fileLong = this->ecl_sum1;
}
}
void SummaryComparator::chooseReference(const std::vector<double>& timeVec1,
const std::vector<double>& timeVec2,
const std::vector<double>& dataVec1,
const std::vector<double>& dataVec2){
if(timeVec1.size() <= timeVec2.size()){
referenceVec = &timeVec1; // time vector
referenceDataVec = &dataVec1; //data vector
checkVec = &timeVec2;
checkDataVec = &dataVec2;
}
else{
referenceVec = &timeVec2;
referenceDataVec = &dataVec2;
checkVec = &timeVec1;
checkDataVec = &dataVec1;
}
}
void SummaryComparator::getDeviation(size_t refIndex, size_t &checkIndex, Deviation &dev){
if((*referenceVec)[refIndex] == (*checkVec)[checkIndex]){
dev = SummaryComparator::calculateDeviations((*referenceDataVec)[refIndex], (*checkDataVec)[checkIndex]);
checkIndex++;
return;
}
else if((*referenceVec)[refIndex]<(*checkVec)[checkIndex]){
double value = SummaryComparator::unitStep((*checkDataVec)[checkIndex]);
/*Must be a little careful here. Flow writes out old value first,
than changes value. Say there should be a change in production rate from A to B at timestep 300.
Then the data of time step 300 is A and the next timestep will have value B. Must use the upper limit. */
dev = SummaryComparator::calculateDeviations((*referenceDataVec)[refIndex], value);
checkIndex++;
return;
}
else{
checkIndex++;
getDeviation(refIndex, checkIndex , dev);
}
if(checkIndex == checkVec->size() -1 ){
return;
}
}
void SummaryComparator::printUnits(){
std::vector<double> timeVec1, timeVec2;
setTimeVecs(timeVec1, timeVec2); // Sets the time vectors, they are equal for all keywords (WPOR:PROD01 etc)
setDataSets(timeVec1, timeVec2);
for (int jvar = 0; jvar < stringlist_get_size(keysLong); jvar++){
std::cout << stringlist_iget(keysLong, jvar) << " unit: " << ecl_sum_get_unit(ecl_sum_fileShort, stringlist_iget(keysLong, jvar)) << std::endl;
}
}
//Called only when the keywords are equal in the getDeviations()-function
const char* SummaryComparator::getUnit(const char* keyword){
return ecl_sum_get_unit(ecl_sum_fileShort, keyword);
}
void SummaryComparator::printKeywords(){
int ivar = 0;
std::vector<std::string> noMatchString;
std::cout << "Keywords that are common for the files:" << std::endl;
while(ivar < stringlist_get_size(keysLong)){
const char* keyword = stringlist_iget(keysLong, ivar);
if (stringlist_contains(keysLong, keyword) && stringlist_contains(keysShort, keyword)){
std::cout << keyword << std::endl;
ivar++;
}
else{
noMatchString.push_back(keyword);
ivar++;
}
}
if(noMatchString.size() == 0){
std::cout << "No keywords were different" << std::endl;
return;
}
std::cout << "Keywords that are different: " << std::endl;
for (const auto& it : noMatchString) std::cout << it << std::endl;
std::cout << "\nOf the " << stringlist_get_size(keysLong) << " keywords " << stringlist_get_size(keysLong)-noMatchString.size() << " were equal and " << noMatchString.size() << " were different" << std::endl;
}
void SummaryComparator::printDataOfSpecificKeyword(const std::vector<double>& timeVec1,
const std::vector<double>& timeVec2,
const char* keyword){
std::vector<double> dataVec1, dataVec2;
getDataVecs(dataVec1,dataVec2,keyword);
chooseReference(timeVec1, timeVec2,dataVec1,dataVec2);
size_t ivar = 0;
size_t jvar = 0;
const char separator = ' ';
const int numWidth = 14;
std::cout << std::left << std::setw(numWidth) << std::setfill(separator) << "Time";
std::cout << std::left << std::setw(numWidth) << std::setfill(separator) << "Ref data";
std::cout << std::left << std::setw(numWidth) << std::setfill(separator) << "Check data" << std::endl;
while(ivar < referenceVec->size()){
if(ivar == referenceVec->size() || jvar == checkVec->size() ){
break;
}
if((*referenceVec)[ivar] == (*checkVec)[jvar]){
std::cout << std::left << std::setw(numWidth) << std::setfill(separator) << (*referenceVec)[ivar];
std::cout << std::left << std::setw(numWidth) << std::setfill(separator) << (*referenceDataVec)[ivar];
std::cout << std::left << std::setw(numWidth) << std::setfill(separator) << (*checkDataVec)[jvar] << std::endl;
ivar++;
jvar++;
}else if((*referenceVec)[ivar] < (*checkVec)[jvar]){
std::cout << std::left << std::setw(numWidth) << std::setfill(separator) << (*referenceVec)[ivar];
std::cout << std::left << std::setw(numWidth) << std::setfill(separator) << (*referenceDataVec)[ivar];
std::cout << std::left << std::setw(numWidth) << std::setfill(separator) << "" << std::endl;
ivar++;
}
else{
std::cout << std::left << std::setw(numWidth) << std::setfill(separator) << (*checkVec)[jvar];
std::cout << std::left << std::setw(numWidth) << std::setfill(separator) << "";
std::cout << std::left << std::setw(numWidth) << std::setfill(separator) << (*checkDataVec)[jvar] << std::endl;
jvar++;
}
}
}

View File

@ -0,0 +1,401 @@
/*
Copyright 2016 Statoil ASA.
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 <opm/test_util/summaryIntegrationTest.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <ert/ecl/ecl_sum.h>
#include <ert/util/stringlist.h>
#include <cmath>
void IntegrationTest::getIntegrationTest(){
std::vector<double> timeVec1, timeVec2;
setTimeVecs(timeVec1, timeVec2); // Sets the time vectors, they are equal for all keywords (WPOR:PROD01 etc)
setDataSets(timeVec1, timeVec2);
int ivar = 0;
if(!allowDifferentAmountOfKeywords){
if(stringlist_get_size(keysShort) != stringlist_get_size(keysLong)){
OPM_THROW(std::invalid_argument, "Different ammont of keywords in the two summary files.");
}
}
if(printKeyword){
printKeywords();
return;
}
std::string keywordWithGreatestErrorRatio;
double greatestRatio = 0;
//Iterates over all keywords from the restricted file, use iterator "ivar". Searches for a match in the file with more keywords, use the itarator "jvar".
while(ivar < stringlist_get_size(keysShort)){
const char* keyword = stringlist_iget(keysShort, ivar);
if(oneOfTheMainVariables){
std::string keywordString(keyword);
std::string substr = keywordString.substr(0,4);
if(substr!= mainVariable){
ivar++;
continue;
}
}
for (int jvar = 0; jvar < stringlist_get_size(keysLong); jvar++){
if (strcmp(keyword, stringlist_iget(keysLong, jvar)) == 0){ //When the keywords are equal, proceed in comparing summary files.
/* if(!checkUnits(keyword)){
OPM_THROW(std::runtime_error, "For keyword " << keyword << " the unit of the two files is not equal. Not possible to compare.");
} //Comparing the unit of the two vectors.*/
checkForKeyword(timeVec1, timeVec2, keyword);
if(findVectorWithGreatestErrorRatio){
WellProductionVolume volume = getSpecificWellVolume(timeVec1,timeVec2, keyword);
findGreatestErrorRatio(volume,greatestRatio, keyword, keywordWithGreatestErrorRatio);
}
break;
}
//will only enter here if no keyword match
if(jvar == stringlist_get_size(keysLong)-1){
if(!allowDifferentAmountOfKeywords){
OPM_THROW(std::invalid_argument, "No match on keyword");
}
}
}
ivar++;
}
if(findVectorWithGreatestErrorRatio){
std::cout << "The keyword " << keywordWithGreatestErrorRatio << " had the greatest error ratio, which was " << greatestRatio << std::endl;
}
if((findVolumeError || oneOfTheMainVariables) && !findVectorWithGreatestErrorRatio){
evaluateWellProductionVolume();
}
if(allowSpikes){
std::cout << "checkWithSpikes succeeded." << std::endl;
}
}
void IntegrationTest::getIntegrationTest(const char* keyword){
if(stringlist_contains(keysShort,keyword) && stringlist_contains(keysLong, keyword)){
std::vector<double> timeVec1, timeVec2;
setTimeVecs(timeVec1, timeVec2); // Sets the time vectors, they are equal for all keywords (WPOR:PROD01 etc)
setDataSets(timeVec1, timeVec2);
if(printSpecificKeyword){
printDataOfSpecificKeyword(timeVec1, timeVec2, keyword);
}
if(findVolumeError){
WellProductionVolume volume = getSpecificWellVolume(timeVec1, timeVec2, keyword);
if(volume.error == 0){
std::cout << "For keyword " << keyword << " the total production volume is 0" << std::endl;
}
else{
std::cout << "For keyword " << keyword << " the total production volume is "<< volume.total;
std::cout << ", the error volume is " << volume.error << " the error ratio is " << volume.error/volume.total << std::endl;
}
}
checkForKeyword(timeVec1, timeVec2, keyword);
return;
}
OPM_THROW(std::invalid_argument, "The keyword used is not common for the two files.");
}
void IntegrationTest::checkForKeyword(const std::vector<double>& timeVec1,
const std::vector<double>& timeVec2,
const char* keyword){
std::vector<double> dataVec1, dataVec2;
getDataVecs(dataVec1,dataVec2,keyword);
chooseReference(timeVec1, timeVec2,dataVec1,dataVec2);
if(allowSpikes){
checkWithSpikes(keyword);
}
if(findVolumeError ||oneOfTheMainVariables ){
volumeErrorCheck(keyword);
}
}
int IntegrationTest::checkDeviation(const Deviation& deviation){
double absTol = getAbsTolerance();
double relTol = getRelTolerance();
if (deviation.rel> relTol && deviation.abs > absTol){
return 1;
}
return 0;
}
void IntegrationTest::findGreatestErrorRatio(const WellProductionVolume& volume,
double &greatestRatio,
const char* currentKeyword,
std::string &greatestErrorRatio){
if (volume.total != 0 && (volume.total - volume.error > getAbsTolerance()) ){
if(volume.error/volume.total > greatestRatio){
greatestRatio = volume.error/volume.total;
std::string currentKeywordStr(currentKeyword);
greatestErrorRatio = currentKeywordStr;
}
}
}
void IntegrationTest::volumeErrorCheck(const char* keyword){
const smspec_node_type * node = ecl_sum_get_general_var_node (ecl_sum_fileShort ,keyword);//doesn't matter which ecl_sum_file one uses, the kewyord SHOULD be equal in terms of smspec data.
bool hist = smspec_node_is_historical(node);
/* returns true if the keyword corresponds to a summary vector "history".
E.g. WOPRH, where the last character, 'H', indicates that it is a HISTORY vector.*/
if(hist){
return;//To make sure we do not include history vectors.
}
if (!mainVariable.empty()){
std::string keywordString(keyword);
std::string firstFour = keywordString.substr(0,4);
if(mainVariable == firstFour && firstFour == "WOPR"){
if(firstFour == "WOPR"){
WellProductionVolume result = getWellProductionVolume(keyword);
WOP += result;
return;
}
}
if(mainVariable == firstFour && firstFour == "WWPR"){
if(firstFour == "WWPR"){
WellProductionVolume result = getWellProductionVolume(keyword);
WWP += result;
return;
}
}
if(mainVariable == firstFour && firstFour == "WGPR"){
if(firstFour == "WGPR"){
WellProductionVolume result = getWellProductionVolume(keyword);
WGP += result;
return;
}
}
if(mainVariable == firstFour && firstFour == "WBHP"){
if(firstFour == "WBHP"){
WellProductionVolume result = getWellProductionVolume(keyword);
WBHP += result;
return;
}
}
}
updateVolumeError(keyword);
}
void IntegrationTest::updateVolumeError(const char* keyword){
std::string keywordString(keyword);
std::string firstFour = keywordString.substr(0,4);
if(firstFour == "WOPR"){
WellProductionVolume result = getWellProductionVolume(keyword);
WOP += result;
}
if(firstFour == "WWPR"){
WellProductionVolume result = getWellProductionVolume(keyword);
WWP += result;
}
if(firstFour == "WGPR"){
WellProductionVolume result = getWellProductionVolume(keyword);
WGP += result;
}
if(firstFour == "WBHP"){
WellProductionVolume result = getWellProductionVolume(keyword);
WBHP += result;
}
}
WellProductionVolume IntegrationTest::getWellProductionVolume(const char * keyword){
double total = integrate(*referenceVec, *referenceDataVec);
double error = integrateError(*referenceVec, *referenceDataVec,
*checkVec, *checkDataVec);
WellProductionVolume wPV;
wPV.total = total;
wPV.error = error;
if(wPV.total != 0 && wPV.total-wPV.error > getAbsTolerance()){
if( (wPV.error/wPV.total > getRelTolerance()) && throwExceptionForTooGreatErrorRatio){
OPM_THROW(std::runtime_error, "For the keyword "<< keyword << " the error ratio was " << wPV.error/wPV.total << " which is greater than the tolerance " << getRelTolerance());
}
}
return wPV;
}
void IntegrationTest::evaluateWellProductionVolume(){
if(mainVariable.empty()){
double ratioWOP, ratioWWP, ratioWGP, ratioWBHP;
ratioWOP = WOP.error/WOP.total;
ratioWWP = WWP.error/WWP.total;
ratioWGP = WGP.error/WGP.total;
ratioWBHP = WBHP.error/WBHP.total;
std::cout << "\n The total oil volume is " << WOP.total << ". The error volume is "<< WOP.error << ". The error ratio is " << ratioWOP << std::endl;
std::cout << "\n The total water volume is " << WWP.total << ". The error volume is "<< WWP.error << ". The error ratio is " << ratioWWP << std::endl;
std::cout << "\n The total gas volume is " << WGP.total <<". The error volume is "<< WGP.error << ". The error ratio is " << ratioWGP << std::endl;
std::cout << "\n The total area under the WBHP curve is " << WBHP.total << ". The area under the error curve is "<< WBHP.error << ". The error ratio is " << ratioWBHP << std::endl << std::endl;
}
if(mainVariable == "WOPR"){
std::cout << "\nThe total oil volume is " << WOP.total << ". The error volume is "<< WOP.error << ". The error ratio is " << WOP.error/WOP.total << std::endl<< std::endl;
}
if(mainVariable == "WWPR"){
std::cout << "\nThe total water volume is " << WWP.total << ". The error volume is "<< WWP.error << ". The error ratio is " << WWP.error/WWP.total << std::endl<< std::endl;
}
if(mainVariable == "WGPR"){
std::cout << "\nThe total gas volume is " << WGP.total <<". The error volume is "<< WGP.error << ". The error ratio is " << WGP.error/WGP.total << std::endl<< std::endl;
}
if(mainVariable == "WBHP"){
std::cout << "\nThe total area under the WBHP curve " << WBHP.total << ". The area under the error curve is "<< WBHP.error << ". The error ratio is " << WBHP.error/WBHP.total << std::endl << std::endl;
}
}
void IntegrationTest::checkWithSpikes(const char* keyword){
int errorOccurrences = 0;
size_t jvar = 0 ;
bool spikeCurrent = false;
Deviation deviation;
for (size_t ivar = 0; ivar < referenceVec->size(); ivar++){
int errorOccurrencesPrev = errorOccurrences;
bool spikePrev = spikeCurrent;
getDeviation(ivar,jvar, deviation);
errorOccurrences += checkDeviation(deviation);
if (errorOccurrences != errorOccurrencesPrev){
spikeCurrent = true;
} else{
spikeCurrent = false;
}
if(spikePrev&&spikeCurrent){
std::cout << "For keyword " << keyword << " at time step " << (*referenceVec)[ivar] <<std::endl;
OPM_THROW(std::invalid_argument, "For keyword " << keyword << " at time step " << (*referenceVec)[ivar] << ", wwo deviations in a row exceed the limit. Not a spike value. Integration test fails." );
}
if(errorOccurrences > this->spikeLimit){
std::cout << "For keyword " << keyword << std::endl;
OPM_THROW(std::invalid_argument, "For keyword " << keyword << " too many spikes in the vector. Integration test fails.");
}
}
}
WellProductionVolume
IntegrationTest::getSpecificWellVolume(const std::vector<double>& timeVec1,
const std::vector<double>& timeVec2,
const char* keyword){
std::vector<double> dataVec1, dataVec2;
getDataVecs(dataVec1,dataVec2,keyword);
chooseReference(timeVec1, timeVec2,dataVec1,dataVec2);
return getWellProductionVolume(keyword);
}
#if 0
bool IntegrationTest::checkUnits(const char * keyword){
const smspec_node_type * node1 = ecl_sum_get_general_var_node (ecl_sum_fileShort ,keyword);
const smspec_node_type * node2 = ecl_sum_get_general_var_node (ecl_sum_fileLong ,keyword);
if(strcmp(smspec_node_get_unit(node1),smspec_node_get_unit(node2)) == 0){
return true;
}
return false;
}
#endif
double IntegrationTest::integrate(const std::vector<double>& timeVec,
const std::vector<double>& dataVec){
double totalSum = 0;
if(timeVec.size() != dataVec.size()){
OPM_THROW(std::runtime_error, "The size of the time vector does not match the size of the data vector.");
}
for(size_t i = 0; i < timeVec.size()-1; i++){
double width = timeVec[i+1] - timeVec[i];
double height = dataVec[i+1];
totalSum += getRectangleArea(height, width);
}
return totalSum;
}
double IntegrationTest::integrateError(const std::vector<double>& timeVec1,
const std::vector<double>& dataVec1,
const std::vector<double>& timeVec2,
const std::vector<double>& dataVec2){
// When the data corresponds to a rate the integration will become a Riemann
// sum. This function calculates the Riemann sum of the error. The reason why
// a Riemann sum is used is because of the way the data is written to file.
// When a change occur (e.g. change of a rate), the data (value and time) is
// written to file, THEN the change happens in the simulator, i.e., we will
// notice the change at the next step.
//
// Keep in mind that the summary vector is NOT a continuous curve, only points
// of data (time, value). We have to guess what happens between the data
// points, we do this by saying: "There are no change, the only change happens
// at the data points." As stated above, the value of this constant "height" of
// the rectangle corresponds to the value of the last time step. Thus we have
// to use the "right hand side value of the rectangle as height
//
// someDataVector[ivar] instead of someDataVector[ivar-1]
//
// (which intuition is saying is the correct value to use).
if(timeVec1.size() != dataVec1.size() || timeVec2.size() != dataVec2.size() ){
OPM_THROW(std::runtime_error, "The size of the time vector does not match the size of the data vector.");
}
double errorSum = 0;
double rightEdge, leftEdge, width;
size_t i = 1;
size_t j = 1;
leftEdge = timeVec1[0];
while(i < timeVec1.size()){
if(j == timeVec2.size() ){
break;
}
if(timeVec1[i] == timeVec2[j]){
rightEdge = timeVec1[i];
width = rightEdge - leftEdge;
double dev = std::fabs(dataVec1[i] - dataVec2[j]);
errorSum += getRectangleArea(dev, width);
leftEdge = rightEdge;
i++;
j++;
continue;
}
if(timeVec1[i] < timeVec2[j]){
rightEdge = timeVec1[i];
width = rightEdge - leftEdge;
double value = unitStep(dataVec2[j]);
double dev = std::fabs(dataVec1[i]-value);
errorSum += getRectangleArea(dev, width);
leftEdge = rightEdge;
i++;
continue;
}
if(timeVec2[j] < timeVec1[i]){
rightEdge = timeVec2[j];
width = rightEdge - leftEdge;
double value = unitStep(dataVec1[i]);
double dev = std::fabs(dataVec2[j]-value);
errorSum += getRectangleArea(dev, width);
leftEdge = rightEdge;
j++;
continue;
}
}
return errorSum;
}

View File

@ -0,0 +1,143 @@
/*
Copyright 2016 Statoil ASA.
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 <opm/test_util/summaryRegressionTest.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <ert/ecl/ecl_sum.h>
#include <ert/util/stringlist.h>
#include <string>
void RegressionTest::getRegressionTest(){
std::vector<double> timeVec1, timeVec2;
setTimeVecs(timeVec1, timeVec2); // Sets the time vectors, they are equal for all keywords (WPOR:PROD01 etc)
setDataSets(timeVec1, timeVec2); //Figures which dataset that contains more/less values pr keyword vector.
std::cout << "Comparing " << timeVec1.size() << " steps." << std::endl;
int ivar = 0;
if(stringlist_get_size(keysShort) != stringlist_get_size(keysLong)){
int missing_count = 0;
std::cout << "Keywords missing from one case: " << std::endl;
for (int i=0; i < stringlist_get_size( keysLong); i++) {
const char * key = stringlist_iget( keysLong , i );
if (!stringlist_contains( keysShort , key)) {
std::cout << key << " ";
missing_count++;
if ((missing_count % 8) == 0)
std::cout << std::endl;
}
}
std::cout << std::endl;
HANDLE_ERROR(std::runtime_error, "Different amount of keywords in the two summary files.");
}
if(printKeyword){
printKeywords();
}
//Iterates over all keywords from the restricted file, use iterator "ivar". Searches for a match in the file with more keywords, use the iterator "jvar".
bool throwAtEnd = false;
while(ivar < stringlist_get_size(keysShort)){
const char* keyword = stringlist_iget(keysShort, ivar);
std::string keywordString(keyword);
for (int jvar = 0; jvar < stringlist_get_size(keysLong); jvar++){
if (strcmp(keyword, stringlist_iget(keysLong, jvar)) == 0){ //When the keywords are equal, proceed in comparing summary files.
if (isRestartFile && keywordString.substr(3,1)=="T"){
break;
}
throwAtEnd |= !checkForKeyword(timeVec1, timeVec2, keyword);
break;
}
//will only enter here if no keyword match
if(jvar == stringlist_get_size(keysLong)-1){
std::cout << "Could not find keyword: " << stringlist_iget(keysShort, ivar) << std::endl;
OPM_THROW(std::runtime_error, "No match on keyword");
}
}
ivar++;
}
if (throwAtEnd)
OPM_THROW(std::runtime_error, "Regression test failed.");
else
std::cout << "Regression test succeeded." << std::endl;
}
void RegressionTest::getRegressionTest(const char* keyword){
std::vector<double> timeVec1, timeVec2;
setTimeVecs(timeVec1, timeVec2); // Sets the time vectors, they are equal for all keywords (WPOR:PROD01 etc)
setDataSets(timeVec1, timeVec2); //Figures which dataset that contains more/less values pr keyword vector.
std::string keywordString(keyword);
if(stringlist_contains(keysShort,keyword) && stringlist_contains(keysLong, keyword)){
if (isRestartFile && keywordString.substr(3,1)=="T"){
return;
}
if (checkForKeyword(timeVec1, timeVec2, keyword))
std::cout << "Regression test succeeded." << std::endl;
else
OPM_THROW(std::runtime_error, "Regression test failed");
return;
}
std::cout << "The keyword suggested, " << keyword << ", is not supported by one or both of the summary files. Please use a different keyword." << std::endl;
OPM_THROW(std::runtime_error, "Input keyword from user does not exist in/is not common for the two summary files.");
}
bool RegressionTest::checkDeviation(Deviation deviation, const char* keyword, int refIndex, int checkIndex){
double absTol = getAbsTolerance();
double relTol = getRelTolerance();
if (deviation.rel > relTol && deviation.abs > absTol){
std::cout << "For keyword " << keyword << std::endl;
std::cout << "(days, reference value) and (days, check value) = (" << (*referenceVec)[refIndex] << ", " << (*referenceDataVec)[refIndex]
<< ") and (" << (*checkVec)[checkIndex-1] << ", " << (*checkDataVec)[checkIndex-1] << ")\n";
// -1 in [checkIndex -1] because checkIndex is updated after leaving getDeviation function
std::cout << "The absolute deviation is " << deviation.abs << ". The tolerance limit is " << absTol << std::endl;
std::cout << "The relative deviation is " << deviation.rel << ". The tolerance limit is " << relTol << std::endl;
HANDLE_ERROR(std::runtime_error, "Deviation exceed the limit.");
return false;
}
return true;
}
bool RegressionTest::checkForKeyword(std::vector<double>& timeVec1, std::vector<double>& timeVec2, const char* keyword){
std::vector<double> dataVec1, dataVec2;
getDataVecs(dataVec1,dataVec2,keyword);
chooseReference(timeVec1, timeVec2,dataVec1,dataVec2);
return startTest(keyword);
}
bool RegressionTest::startTest(const char* keyword){
size_t jvar = 0;
Deviation deviation;
bool result = true;
for (size_t ivar = 0; ivar < referenceVec->size(); ivar++){
getDeviation(ivar, jvar, deviation);//Reads from the protected member variables in the super class.
result = checkDeviation(deviation, keyword,ivar, jvar);
}
return result;
}

125
tests/FIRST_SIM.DATA Normal file
View File

@ -0,0 +1,125 @@
RUNSPEC
OIL
GAS
WATER
DISGAS
VAPOIL
UNIFOUT
UNIFIN
DIMENS
10 10 10 /
GRID
DXV
10*0.25 /
DYV
10*0.25 /
DZV
10*0.25 /
TOPS
100*0.25 /
PORO
1000*0.2 /
SOLUTION
RESTART
FIRST_SIM 1/
START -- 0
1 NOV 1979 /
SCHEDULE
SKIPREST
RPTRST
BASIC=1
/
DATES -- 1
10 OKT 2008 /
/
WELSPECS
'OP_1' 'OP' 9 9 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* /
'OP_2' 'OP' 9 9 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* /
/
COMPDAT
'OP_1' 9 9 1 1 'OPEN' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 /
'OP_2' 9 9 2 2 'OPEN' 1* 46.825 0.311 4332.346 1* 1* 'X' 22.123 /
'OP_1' 9 9 3 3 'OPEN' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 /
/
WCONPROD
'OP_1' 'OPEN' 'ORAT' 20000 4* 1000 /
/
WCONINJE
'OP_2' 'GAS' 'OPEN' 'RATE' 100 200 400 /
/
DATES -- 2
20 JAN 2011 /
/
WELSPECS
'OP_3' 'OP' 9 9 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* /
/
COMPDAT
'OP_3' 9 9 1 1 'OPEN' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 /
/
WCONPROD
'OP_3' 'OPEN' 'ORAT' 20000 4* 1000 /
/
DATES -- 3
15 JUN 2013 /
/
COMPDAT
'OP_2' 9 9 3 9 'OPEN' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 /
'OP_1' 9 9 7 7 'SHUT' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 /
/
DATES -- 4
22 APR 2014 /
/
WELSPECS
'OP_4' 'OP' 9 9 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* /
/
COMPDAT
'OP_4' 9 9 3 9 'OPEN' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 /
'OP_3' 9 9 3 9 'OPEN' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 /
/
WCONPROD
'OP_4' 'OPEN' 'ORAT' 20000 4* 1000 /
/
DATES -- 5
30 AUG 2014 /
/
WELSPECS
'OP_5' 'OP' 9 9 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* /
/
COMPDAT
'OP_5' 9 9 3 9 'OPEN' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 /
/
WCONPROD
'OP_5' 'OPEN' 'ORAT' 20000 4* 1000 /
/
DATES -- 6
15 SEP 2014 /
/
WCONPROD
'OP_3' 'SHUT' 'ORAT' 20000 4* 1000 /
/
DATES -- 7
9 OCT 2014 /
/
WELSPECS
'OP_6' 'OP' 9 9 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* /
/
COMPDAT
'OP_6' 9 9 3 9 'OPEN' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 /
/
WCONPROD
'OP_6' 'OPEN' 'ORAT' 20000 4* 1000 /
/
TSTEP -- 8
10 /

138
tests/SUMMARY_EFF_FAC.DATA Normal file
View File

@ -0,0 +1,138 @@
START
10 MAI 2007 /
RUNSPEC
TITLE
SUMMARYTESTS
-- A simple 10x10x10 cube. Simple to reason about, large enough for all tests
DIMENS
10 10 10 /
REGDIMS
10 /
OIL
GAS
WATER
GRID
DX
1000*1 /
DY
1000*1 /
DZ
1000*1 /
TOPS
100*1 /
PORO
1000*0.2 /
REGIONS
FIPNUM
1*2
/
SUMMARY
DATE
ROPR
/
RWIR
/
WOPR
/
WOPT
/
WOIR
/
WOIT
/
GOPR
/
GOPT
/
GOIR
/
GOIT
/
GWCT
/
GGOR
/
FOPR
FOPT
FOIR
FOIT
COPR
'W_2' /
/
COPT
'W_2' /
/
SCHEDULE
GRUPTREE
'G_1' 'G' /
'G_2' 'G' /
'G_3' 'G_4' /
/
WELSPECS
'W_1' 'G_1' 1 1 3.33 'OIL' 7* /
'W_2' 'G_2' 2 1 3.33 'OIL' 7* /
'W_3' 'G_3' 2 1 3.33 'OIL' 7* /
/
WEFAC
'W_2' 0.2 /
'W_3' 0.3 /
/
GEFAC
G_2 0.01 /
G_3 0.02 /
G_4 0.03 /
/
WCONPROD
W_1 'OPEN' ORAT 10.1 /
W_2 'OPEN' ORAT 10.1 /
W_3 'OPEN' ORAT 10.1 /
/
-- Completion data.
COMPDAT
-- 'Well' I J K1 K2
-- Passing 0 to I/J means they'll get the well head I/J
W_1 0 0 1 1 / -- Active index: 0
W_2 0 0 1 1 / -- Active index: 1
W_2 0 0 2 2 / -- Active index: 101
W_3 0 0 1 1 / -- Active index: 2
/
TSTEP
-- register time steps (in days). This allows us to write *two* report steps (1
-- and 2. Without this, totals/accumulations would fail (segfault) when looking
-- up historical rates and volumes. These volumes however don't change, i.e.
-- every time step has the same set of values
10 10 /
TSTEP
10 10 /

525
tests/group_group.DATA Normal file
View File

@ -0,0 +1,525 @@
-- Synthetic test deck based on Norne. This data set is meant to be a simple,
-- well-documented deck for the behaviour of SUMMARY specified output. Data
-- is mostly entered to *traceable* and does not necessarily make sense from
-- a simulation point of view.
START
10 MAI 2007 /
RUNSPEC
TITLE
SUMMARYTESTS
-- A simple 10x10x10 cube. Simple to reason about, large enough for all tests
DIMENS
10 10 10 /
REGDIMS
10 /
OIL
GAS
WATER
GRID
DX
1000*1 /
DY
1000*1 /
DZ
1000*1 /
TOPS
100*1 /
-- Cell 2,1,10 is inactive
ACTNUM
901*1 0 98*1 /
PORO
1000*0.2 /
REGIONS
FIPNUM
100*1
100*2
100*3
100*4
100*5
100*6
100*7
100*8
100*9
100*10 /
SUMMARY
DATE
PERFORMA
--
-- Field Data
-- Production Rates
FVPR
FWPR
FWPRH
FOPR
FOPRH
FGPR
FGPRH
FLPR
FLPRH
FGSR
FGCR
FNPR -- solvent
--FTPRSEA
-- Injection Rates
FVIR
FWIR
FWIRH
FGIR
FNIR -- solvent
FGIRH
-- Production Cummulatives
FVPT
FWPT
FOPT
FLPT
FLPTH
FGPT
FNPT
FOPTH
FGPTH
FWPTH
FGST
FGCT
-- Injection Cummulatives
FVIT
FWIT
FWITH
FGIT
FNIT
FGITH
-- In place
FWIP
FOIP
FGIP
-- Ratios
FWCT
FWCTH
FGOR
FGORH
-- From model2
FMWPR
FMWIN
FOE
-- Pressures
FPR
BPR
1 1 1 /
1 1 2 /
1 1 3 /
1 1 4 /
1 1 5 /
1 1 6 /
1 1 7 /
1 1 8 /
1 1 9 /
1 1 10 /
2 1 10 / -- This cell is not ACTIVE
/
BSGAS
1 1 1 /
/
BSWAT
1 1 1 /
/
-- Region data
RPR
/
ROPT
/
RGPT
/
RWPT
/
RGFT
/
RWFT
/
ROIP
/
ROP
/
ROPR
/
RGPR
/
RWPR
/
RGIR
/
RGIT
/
RWIR
/
RWIT
/
RWPT
/
ROIPL
/
ROIPG
/
RGIP
/
RGIPL
/
RGIPG
/
RWIP
/
RPPO
/
-- Group data --
GPR
/
GLPR
/
GOPT
/
GGPT
/
GWPT
/
GNPT
/
GOPR
/
GGPR
/
GWPR
/
GWPRH
/
GGIR
/
GNPR
/
GNIR
/
GGIRH
/
GGIT
/
GNIT
/
GGITH
/
GWCT
/
GWCTH
/
GGOR
/
GGORH
/
GWIR
/
GWIT
/
GWIRH
/
GWITH
/
GOPRH
/
GGPRH
/
GLPRH
/
GWPTH
/
GOPTH
/
GGPTH
/
GLPTH
/
GPRG
/
GPRW
/
GOPTF
/
GOPTS
/
GOPTH
/
GOPRF
/
GOPRS
/
GOPRH
/
GGPTF
/
GGPTS
/
GGPTH
/
GGPTF
/
GGPTS
/
GGPTH
/
GGLR
/
GGLIR
/
GGLRH
/
GVPR
/
GVPT
/
GMCTP
/
GOPP
/
GVIR
/
GVIT
/
GVPRT
/
GMWPR
/
GMWIN
/
-- Well Data
-- Production Rates
WWPR
/
WWPRH
/
WOPR
/
WOPRH
/
WGPR
/
WNPR
/
WGPRH
/
WLPR
/
WLPRH
/
WLPT
/
WLPTH
/
-- Production Cummulatives
WWPT
/
WWPTH
/
WOPT
/
WOPTH
/
WGPT
/
WGPTH
/
WNPT
/
-- Tracers
--WTPRSEA
--/
--WTPTSEA
--/
-- Injection Cummulatives
-- Ratios
WWCT
/
WWCTH
/
WGOR
/
WGORH
/
WGLR
/
WGLRH
/
-- Performance
WBHP
/
WBHPH
/
WTHP
/
WTHPH
/
WPI
/
WBP
/
WBP4
/
-- from model2
WOPTF
/
WOPTS
/
WOPTH
/
WOPRS
/
WOPRF
/
WGPTF
/
WGPTS
/
WGPRF
/
WTPRS
/
WGLIR
/
WVPR
/
WVPT
/
WOPP
/
WVIR
/
WVIT
/
WMCTL
/
-- Water injection per connection
CWIR
* /
/
CGIT
* /
/
-- Production per connection
-- Using all the different ways of specifying connections here
-- as an informal test that we still get the data we want
CWPR
'W_1' 1 1 1 /
/
COPR
'W_1' /
'W_2' /
/
CGPR
'*' /
/
CNFR
'*' /
/
CNPT
'*' /
/
CNIT
'*' /
/
CWPT
'W_1' 1 1 1 /
/
COPT
'W_1' /
/
CGPT
'W_1' /
'W_2' /
/
---- Connection production rates
----CGFR
----'E-4AH' /
----/
----CWFR
----'E-2H' /
----/
SCHEDULE
GRUPTREE
'G_1' 'G' /
'G_2' 'G' /
/
WELSPECS
'W_1' 'G_1' 1 1 3.33 'OIL' 7* /
'W_2' 'G_2' 2 1 3.33 'OIL' 7* /
/
-- Completion data.
COMPDAT
-- 'Well' I J K1 K2
-- Passing 0 to I/J means they'll get the well head I/J
W_1 0 0 1 1 / -- Active index: 0
W_2 0 0 1 1 / -- Active index: 1
W_2 0 0 2 2 / -- Active index: 101
/
WCONHIST
-- history rates are set so that W_1 produces 1, W_2 produces 2 etc.
-- index.offset.
-- organised as oil-water-gas
W_1 'OPEN' ORAT 10.1 10 10.2 /
W_2 'OPEN' ORAT 20.1 20 20.2 /
/
TSTEP
-- register time steps (in days). This allows us to write *two* report steps (1
-- and 2. Without this, totals/accumulations would fail (segfault) when looking
-- up historical rates and volumes. These volumes however don't change, i.e.
-- every time step has the same set of values
10 10 /
TSTEP
10 10 /

624
tests/summary_deck.DATA Normal file
View File

@ -0,0 +1,624 @@
-- Synthetic test deck based on Norne. This data set is meant to be a simple,
-- well-documented deck for the behaviour of SUMMARY specified output. Data
-- is mostly entered to *traceable* and does not necessarily make sense from
-- a simulation point of view.
START
10 MAI 2007 /
RUNSPEC
TITLE
SUMMARYTESTS
-- A simple 10x10x10 cube. Simple to reason about, large enough for all tests
DIMENS
10 10 10 /
REGDIMS
10 /
OIL
GAS
WATER
GRID
DX
1000*1 /
DY
1000*1 /
DZ
1000*1 /
TOPS
100*1 /
-- Cell 2,1,10 is inactive
ACTNUM
901*1 0 98*1 /
PORO
1000*0.2 /
REGIONS
FIPNUM
100*1
100*2
100*3
100*4
100*5
100*6
100*7
100*8
100*9
100*10 /
SUMMARY
DATE
PERFORMA
--
-- Field Data
-- Production Rates
FVPR
FWPR
FWPRH
FOPR
FOPRH
FGPR
FGPRH
FLPR
FLPRH
FGSR
FGCR
FNPR -- solvent
FGPRF
FGPRS
FOPRF
FOPRS
--FTPRSEA
-- Injection Rates
FVIR
FWIR
FWIRH
FGIR
FNIR -- solvent
FGIRH
-- Production Cummulatives
FVPT
FWPT
FOPT
FLPT
FLPTH
FGPT
FNPT
FOPTH
FGPTH
FWPTH
FGST
FGCT
FGPTS
FGPTF
FOPTF
FOPTS
FVPRT
-- Injection Cummulatives
FVIT
FWIT
FWITH
FGIT
FNIT
FGITH
-- In place
FWIP
FOIP
FOIPL
FGIP
FGIPG
-- Ratios
FWCT
FWCTH
FGOR
FGORH
-- From model2
FMWPR
FMWIN
FOE
-- Pressures
FPR
BPR
1 1 1 /
1 1 2 /
1 1 3 /
1 1 4 /
1 1 5 /
1 1 6 /
1 1 7 /
1 1 8 /
1 1 9 /
1 1 10 /
2 1 10 / -- This cell is not ACTIVE
/
BSGAS
1 1 1 /
/
BSWAT
1 1 1 /
/
-- Region data
RPR
/
ROPT
/
RGPT
/
RWPT
/
RGFT
/
RWFT
/
ROIP
/
ROP
/
ROPR
/
RGPR
/
RWPR
/
RGIR
/
RGIT
/
RWIR
/
RWIT
/
RWPT
/
ROIPL
/
ROIPG
/
RGIP
/
RGIPL
/
RGIPG
/
RWIP
/
RPPO
/
-- Group data --
GPR
/
GLPR
/
GOPT
/
GGPT
/
GWPT
/
GNPT
/
GOPR
/
GGPR
/
GWPR
/
GWPRH
/
GGIR
/
GNPR
/
GNIR
/
GGIRH
/
GGIT
/
GNIT
/
GGITH
/
GWCT
/
GWCTH
/
GGOR
/
GGORH
/
GWIR
/
GWIT
/
GWIRH
/
GWITH
/
GOPRH
/
GGPRH
/
GLPRH
/
GWPTH
/
GOPTH
/
GGPTH
/
GLPTH
/
GPRG
/
GPRW
/
GOPTF
/
GOPTS
/
GOPTH
/
GOPRF
/
GOPRS
/
GOPRH
/
GGPTF
/
GGPTS
/
GGPTH
/
GGPRF
/
GGPRS
/
GGPTF
/
GGPTS
/
GGPTH
/
GGLR
/
GGLIR
/
GGLRH
/
GVPR
/
GVPT
/
GMCTP
/
GOPP
/
GVIR
/
GVIT
/
GVPRT
/
GMWPR
/
GMWIN
/
-- Well Data
-- Production Rates
WWPR
/
WWPRH
/
WOPR
/
WGVPR
W_1 W_2 /
WOPRH
/
WGPR
/
WNPR
/
WGPRH
/
WLPR
/
WLPRH
/
WLPT
/
WLPTH
/
WGPRS
/
WGPRF
/
-- Injection Rates
WWIR
W_3
/
WWIT
W_3
/
WWIRH
W_3
/
WGVIR
W_3
/
WWVIR
W_3
/
WWITH
W_3
/
WGIT
W_3
/
WGIR
W_3
/
WGIRH
W_3
/
WGITH
W_3
/
WNIR
W_3
/
WNIT
W_3
/
-- Production Cummulatives
WWPT
/
WWPTH
/
WOPT
/
WOPTH
/
WGPT
/
WGPTH
/
WNPT
/
WGPTF
/
WGPTS
/
WOPTF
/
WOPTS
/
-- Tracers
--WTPRSEA
--/
--WTPTSEA
--/
-- Injection Cummulatives
WWIT
W_3
/
-- Ratios
WWCT
/
WWCTH
/
WGOR
/
WGORH
/
WGLR
/
WGLRH
/
-- Performance
WBHP
/
WBHPH
/
WTHP
/
WTHPH
/
WPI
/
WBP
/
WBP4
/
-- from model2
WOPTF
/
WOPTS
/
WOPTH
/
WOPRS
/
WOPRF
/
WGPTF
/
WGPTS
/
WGPRF
/
WTPRS
/
WGLIR
/
WVPR
/
WVPT
/
WVPRT
/
WOPP
/
WVIR
/
WVIT
/
WMCTL
/
-- Water injection per connection
CWIR
* /
/
-- Gas injection on 3 1 1 (45)
CGIR
'W_3' 3 1 1 /
/
CWIT
'W_3' /
/
CGIT
* /
/
-- Production per connection
-- Using all the different ways of specifying connections here
-- as an informal test that we still get the data we want
CWPR
'W_1' 1 1 1 /
/
COPR
'W_1' /
'W_2' /
'W_3' /
/
CGPR
'*' /
/
CNFR
'*' /
/
CNPT
'*' /
/
CNIT
'*' /
/
CWPT
'W_1' 1 1 1 /
/
COPT
'W_1' /
/
CGPT
'W_1' /
'W_2' /
'W_3' /
/
---- Connection production rates
----CGFR
----'E-4AH' /
----/
----CWFR
----'E-2H' /
----/
SCHEDULE
-- Three wells, two producers (so that we can form a group) and one injector
WELSPECS
'W_1' 'G_1' 1 1 3.33 'OIL' 7* /
'W_2' 'G_1' 2 1 3.33 'OIL' 7* /
'W_3' 'G_2' 3 1 3.92 'WATER' 7* /
'W_5' 'G_3' 4 1 3.92 'OIL' 7* /
/
-- Completion data.
COMPDAT
-- 'Well' I J K1 K2
-- Passing 0 to I/J means they'll get the well head I/J
W_1 0 0 1 1 / -- Active index: 0
W_2 0 0 1 1 / -- Active index: 1
W_2 0 0 2 2 / -- Active index: 101
W_3 0 0 1 1 / -- Active index: 2
/
WCONHIST
-- history rates are set so that W_1 produces 1, W_2 produces 2 etc.
-- index.offset.
-- organised as oil-water-gas
W_1 SHUT ORAT 10.1 10 10.2 2* 0.2 0.1 /
W_2 SHUT ORAT 20.1 20 20.2 2* 1.2 1.1 /
/
WCONINJH
-- Injection historical rates (water only, as we only support pure injectors)
W_3 WATER STOP 30.0 2.1 2.2 /
/
WCONPROD
W_5 SHUT ORAT 0.0 0.0 0.0 1* 30.1 /
/
TSTEP
-- register time steps (in days). This allows us to write *two* report steps (1
-- and 2. Without this, totals/accumulations would fail (segfault) when looking
-- up historical rates and volumes. These volumes however don't change, i.e.
-- every time step has the same set of values
10 10 /
-- Register a fourth well with completions later. This ensure we handle when
-- wells are registered or activated later in a simulation
WELSPECS
'W_4' 'G_3' 1 1 3.33 'OIL' 7* /
/
COMPDAT
W_4 1 1 3 3 /
/
TSTEP
10 10 /

View File

@ -0,0 +1,573 @@
-- Synthetic test deck based on Norne. This data set is meant to be a simple,
-- well-documented deck for the behaviour of SUMMARY specified output. Data
-- is mostly entered to *traceable* and does not necessarily make sense from
-- a simulation point of view.
START
10 MAI 2007 /
RUNSPEC
TITLE
SUMMARYTESTS
-- A simple 10x10x10 cube. Simple to reason about, large enough for all tests
DIMENS
10 10 10 /
REGDIMS
3 /
OIL
GAS
WATER
GRID
DX
1000*1 /
DY
1000*1 /
DZ
1000*1 /
TOPS
100*1 /
ACTNUM
1000*1/
PORO
500*0.1 500*0.2/
REGIONS
FIPNUM
400*1
200*2
400*3 /
SUMMARY
DATE
PERFORMA
--
-- Field Data
-- Production Rates
FVPR
FWPR
FWPRH
FOPR
FOPRH
FGPR
FGPRH
FLPR
FLPRH
FGSR
FGCR
FNPR -- solvent
--FTPRSEA
-- Injection Rates
FVIR
FWIR
FWIRH
FGIR
FNIR -- solvent
FGIRH
-- Production Cummulatives
FVPT
FWPT
FOPT
FLPT
FLPTH
FGPT
FNPT
FOPTH
FGPTH
FWPTH
FGST
FGCT
-- Injection Cummulatives
FVIT
FWIT
FWITH
FGIT
FNIT
FGITH
-- In place
FWIP
FOIP
FGIP
-- Ratios
FWCT
FWCTH
FGOR
FGORH
-- From model2
FMWPR
FMWIN
FOE
-- Pressures
FPR
FPRP
BPR
1 1 1 /
1 1 2 /
1 1 3 /
1 1 4 /
1 1 5 /
1 1 6 /
1 1 7 /
1 1 8 /
1 1 9 /
1 1 10 /
2 1 10 / -- This cell is not ACTIVE
/
BSGAS
1 1 1 /
/
BSWAT
1 1 1 /
/
-- Region data
RPR
/
ROPT
/
RGPT
/
RWPT
/
RGFT
/
RWFT
/
ROIP
/
ROP
/
ROPR
/
RGPR
/
RWPR
/
RGIR
/
RGIT
/
RWIR
/
RWIT
/
RWPT
/
ROIPL
/
ROIPG
/
RGIP
/
RGIPL
/
RGIPG
/
RWIP
/
RPPO
/
-- Group data --
GPR
/
GLPR
/
GOPT
/
GGPT
/
GWPT
/
GNPT
/
GOPR
/
GGPR
/
GWPR
/
GWPRH
/
GGIR
/
GNPR
/
GNIR
/
GGIRH
/
GGIT
/
GNIT
/
GGITH
/
GWCT
/
GWCTH
/
GGOR
/
GGORH
/
GWIR
/
GWIT
/
GWIRH
/
GWITH
/
GOPRH
/
GGPRH
/
GLPRH
/
GWPTH
/
GOPTH
/
GGPTH
/
GLPTH
/
GPRG
/
GPRW
/
GOPTF
/
GOPTS
/
GOPTH
/
GOPRF
/
GOPRS
/
GOPRH
/
GGPTF
/
GGPTS
/
GGPTH
/
GGPTF
/
GGPTS
/
GGPTH
/
GGLR
/
GGLIR
/
GGLRH
/
GVPR
/
GVPT
/
GMCTP
/
GOPP
/
GVIR
/
GVIT
/
GVPRT
/
GMWPR
/
GMWIN
/
-- Well Data
-- Production Rates
WWPR
/
WWPRH
/
WOPR
/
WOPRH
/
WGPR
/
WNPR
/
WGPRH
/
WLPR
/
WLPRH
/
WLPT
/
WLPTH
/
-- Injection Rates
WWIR
W_3
/
WWIT
W_3
/
WWIRH
W_3
/
WWITH
W_3
/
WGIT
W_3
/
WGIR
W_3
/
WGIRH
W_3
/
WGITH
W_3
/
WNIR
W_3
/
WNIT
W_3
/
-- Production Cummulatives
WWPT
/
WWPTH
/
WOPT
/
WOPTH
/
WGPT
/
WGPTH
/
WNPT
/
-- Tracers
--WTPRSEA
--/
--WTPTSEA
--/
-- Injection Cummulatives
WWIT
W_3
/
-- Ratios
WWCT
/
WWCTH
/
WGOR
/
WGORH
/
WGLR
/
WGLRH
/
-- Performance
WBHP
/
WBHPH
/
WTHP
/
WTHPH
/
WPI
/
WBP
/
WBP4
/
-- from model2
WOPTF
/
WOPTS
/
WOPTH
/
WOPRS
/
WOPRF
/
WGPTF
/
WGPTS
/
WGPRF
/
WTPRS
/
WGLIR
/
WVPR
/
WVPT
/
WOPP
/
WVIR
/
WVIT
/
WMCTL
/
-- Water injection per connection
CWIR
* /
/
-- Gas injection on 3 1 1 (45)
CGIR
'W_3' 3 1 1 /
/
CWIT
'W_3' /
/
CGIT
* /
/
-- Production per connection
-- Using all the different ways of specifying connections here
-- as an informal test that we still get the data we want
CWPR
'W_1' 1 1 1 /
/
COPR
'W_1' /
'W_2' /
'W_3' /
/
CGPR
'*' /
/
CNFR
'*' /
/
CNPT
'*' /
/
CNIT
'*' /
/
CWPT
'W_1' 1 1 1 /
/
COPT
'W_1' /
/
CGPT
'W_1' /
'W_2' /
'W_3' /
/
---- Connection production rates
----CGFR
----'E-4AH' /
----/
----CWFR
----'E-2H' /
----/
SCHEDULE
-- Three wells, two producers (so that we can form a group) and one injector
WELSPECS
'W_1' 'G_1' 1 1 3.33 'OIL' 7* /
'W_2' 'G_1' 2 1 3.33 'OIL' 7* /
'W_3' 'G_2' 3 1 3.92 'WATER' 7* /
/
-- Completion data.
COMPDAT
-- 'Well' I J K1 K2
-- Passing 0 to I/J means they'll get the well head I/J
W_1 0 0 1 1 / -- Active index: 0
W_2 0 0 1 1 / -- Active index: 1
W_2 0 0 2 2 / -- Active index: 101
W_3 0 0 1 1 / -- Active index: 2
/
WCONHIST
-- history rates are set so that W_1 produces 1, W_2 produces 2 etc.
-- index.offset.
-- organised as oil-water-gas
W_1 SHUT ORAT 10.1 10 10.2 /
W_2 SHUT ORAT 20.1 20 20.2 /
/
WCONINJH
-- Injection historical rates (water only, as we only support pure injectors)
W_3 WATER STOP 30.0 /
/
TSTEP
-- register time steps (in days). This allows us to write *two* report steps (1
-- and 2. Without this, totals/accumulations would fail (segfault) when looking
-- up historical rates and volumes. These volumes however don't change, i.e.
-- every time step has the same set of values
10 10 /
-- Register a fourth well with completions later. This ensure we handle when
-- wells are registered or activated later in a simulation
WELSPECS
'W_4' 'G_3' 1 1 3.33 'OIL' 7* /
/
COMPDAT
W_4 1 1 3 3 /
/
TSTEP
10 10 /

81
tests/table_deck.DATA Normal file
View File

@ -0,0 +1,81 @@
RUNSPEC
TITLE
SIMPLE TEST
DIMENS
10 10 10 /
OIL
WATER
GAS
DISGAS
VAPOIL
METRIC
EQLDIMS
1 100 2 1 1 /
TABDIMS
1 1 33 60 16 60 /
GRID
DX
1000*1 /
DY
1000*1 /
DZ
1000*1 /
TOPS
100*1 /
PORO
1000*0.2 /
PROPS ===============================================================
PVTO
-- RSO PRESSURE B-OIL VISCOSITY
-- (BAR) (CP)
20.59 50.00 1.10615 1.180
75.00 1.10164 1.247
100.00 1.09744 1.315
125.00 1.09351 1.384
150.00 1.08984 1.453 /
28.19 70.00 1.12522 1.066
95.00 1.12047 1.124
120.00 1.11604 1.182
170.00 1.10804 1.300 /
/
PVTG
--
20.00 0.00002448 0.061895 0.01299
0.00001224 0.061810 0.01300
0.00000000 0.061725 0.01300 /
40.00 0.00000628 0.030252 0.01383
0.00000314 0.030249 0.01383
0.00000000 0.030245 0.01383 /
/
PVTW
247.7 1.03665 0.41726E-04 0.29120 0.99835E-04 /
DENSITY
859.5 1033.0 0.854 /

View File

@ -0,0 +1,68 @@
/*
Copyright 2016 Statoil ASA.
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"
#define BOOST_TEST_MODULE EclFilesComparatorTest
#include <boost/test/unit_test.hpp>
#include <opm/test_util/EclFilesComparator.hpp>
BOOST_AUTO_TEST_CASE(deviation) {
double a = 1;
double b = 3;
const double tol = 1.0e-14;
Deviation dev = ECLFilesComparator::calculateDeviations(a,b);
BOOST_CHECK_EQUAL(dev.abs, 2.0);
BOOST_CHECK_CLOSE(dev.rel, 2.0/3, tol);
a = 0;
dev = ECLFilesComparator::calculateDeviations(a,b);
BOOST_CHECK_EQUAL(dev.abs, 3.0);
BOOST_CHECK_EQUAL(dev.rel, -1);
}
BOOST_AUTO_TEST_CASE(median) {
std::vector<double> vec = {1,3,4,5};
double med = ECLFilesComparator::median(vec);
BOOST_CHECK_EQUAL(med, 3.5);
vec = {1,4,5};
med = ECLFilesComparator::median(vec);
BOOST_CHECK_EQUAL(med, 4);
}
BOOST_AUTO_TEST_CASE(average) {
std::vector<double> vec = {1,3,4,5};
const double tol = 1.0e-14;
double avg = ECLFilesComparator::average(vec);
BOOST_CHECK_CLOSE(avg, 13.0/4, tol);
}

402
tests/test_EclipseIO.cpp Normal file
View File

@ -0,0 +1,402 @@
/*
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"
#define BOOST_TEST_MODULE EclipseIO
#include <boost/test/unit_test.hpp>
#include <opm/output/eclipse/EclipseIO.hpp>
#include <opm/output/data/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/SummaryConfig/SummaryConfig.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/TimeMap.hpp>
#include <opm/parser/eclipse/EclipseState/IOConfig/IOConfig.hpp>
#include <opm/parser/eclipse/Units/Units.hpp>
#include <opm/parser/eclipse/Units/UnitSystem.hpp>
// ERT stuff
#include <ert/util/ert_unique_ptr.hpp>
#include <ert/util/TestArea.hpp>
#include <ert/ecl/ecl_kw.h>
#include <ert/ecl/ecl_grid.h>
#include <ert/ecl/ecl_endian_flip.h>
#include <ert/ecl/ecl_file.h>
#include <ert/ecl/ecl_util.h>
#include <ert/ecl_well/well_info.h>
#include <memory>
#include <map>
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;
solution.insert( "PRESSURE" , UnitSystem::measure::pressure , pressure, data::TargetType::RESTART_SOLUTION );
solution.insert( "SWAT" , UnitSystem::measure::identity , swat, data::TargetType::RESTART_SOLUTION );
solution.insert( "SGAS" , UnitSystem::measure::identity , sgas, data::TargetType::RESTART_SOLUTION );
solution.insert( "RS" , UnitSystem::measure::identity , rs, data::TargetType::RESTART_SOLUTION );
solution.insert( "RV" , UnitSystem::measure::identity , rv, data::TargetType::RESTART_SOLUTION );
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 EclipseIO
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 loadWells( const char* grid_file , const char* restart_file ) {
ecl_grid_type * grid = ecl_grid_alloc( grid_file );
well_info_type * well_info = well_info_alloc( grid );
well_info_load_rstfile( well_info , restart_file, true);
well_info_free( well_info );
ecl_grid_free( grid );
}
void checkInitFile( const Deck& deck, const data::Solution& simProps) {
// use ERT directly to inspect the INIT file produced by EclipseIO
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);
}
}
/*
These keyword should always be in the INIT file, irrespective of
whether they appear in the inut deck or not.
*/
BOOST_CHECK( ecl_file_has_kw( initFile.get() , "NTG" ));
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.first.c_str()) );
}
}
void checkRestartFile( int timeStepIdx ) {
for (int i = 1; i <= timeStepIdx; ++i) {
auto sol = createBlackoilState( i, 3 * 3 * 3 );
// use ERT directly to inspect the restart file produced by EclipseIO
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.data("PRESSURE") )
x /= Metric::Pressure;
compareErtData( sol.data("PRESSURE"), resultData, 1e-4 );
}
if (keywordName == "SWAT") {
const auto resultData = getErtData< float >( eclKeyword );
compareErtData(sol.data("SWAT"), resultData, 1e-4);
}
if (keywordName == "SGAS") {
const auto resultData = getErtData< float >( eclKeyword );
compareErtData( sol.data("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(EclipseIOIntegration) {
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 ) {
ParseContext parse_context;
auto deck = Parser().parseString( deckString, parse_context );
auto es = Parser::parse( deck );
auto& eclGrid = es.getInputGrid();
Schedule schedule(deck, eclGrid, es.get3DProperties(), es.runspec().phases(), parse_context);
SummaryConfig summary_config( deck, schedule, es.getTableManager( ), parse_context);
es.getIOConfig().setBaseName( "FOO" );
EclipseIO eclWriter( es, eclGrid , schedule, summary_config);
using measure = UnitSystem::measure;
using TargetType = data::TargetType;
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);
data::Solution eGridProps {
{ "TRANX", { measure::transmissibility, tranx, TargetType::INIT } },
{ "TRANY", { measure::transmissibility, trany, TargetType::INIT } },
{ "TRANZ", { measure::transmissibility, tranz, TargetType::INIT } },
};
std::map<std::string, std::vector<int>> int_data = {{"STR_ULONGNAME" , {1,1,1,1,1,1,1,1} } };
std::vector<int> v(27); v[2] = 67; v[26] = 89;
int_data["STR_V"] = v;
eclWriter.writeInitial( );
BOOST_CHECK_THROW( eclWriter.writeInitial( eGridProps , int_data) , std::invalid_argument);
int_data.erase("STR_ULONGNAME");
eclWriter.writeInitial( eGridProps , int_data );
data::Wells wells;
for( int i = first; i < last; ++i ) {
data::Solution sol = createBlackoilState( i, 3 * 3 * 3 );
sol.insert("KRO", measure::identity , std::vector<double>(3*3*3 , i), TargetType::RESTART_AUXILIARY);
sol.insert("KRG", measure::identity , std::vector<double>(3*3*3 , i*10), TargetType::RESTART_AUXILIARY);
auto first_step = ecl_util_make_date( 10 + i, 11, 2008 );
eclWriter.writeTimeStep( i,
false,
first_step - start_time,
sol,
wells,
{},
{},
{});
checkRestartFile( i );
}
checkInitFile( deck , eGridProps);
checkEgridFile( eclGrid );
loadWells( "FOO.EGRID", "FOO.UNRST" );
ecl_file_type * ecl_file = ecl_file_open("FOO.INIT", 0);
BOOST_CHECK( ecl_file_has_kw(ecl_file, "STR_V") );
ecl_kw_type * kw = ecl_file_iget_named_kw(ecl_file, "STR_V", 0);
BOOST_CHECK(67 == ecl_kw_iget_as_double(kw, 2));
BOOST_CHECK(89 == ecl_kw_iget_as_double(kw, 26));
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 ) );
}
BOOST_AUTO_TEST_CASE(OPM_XWEL) {
}

View File

@ -0,0 +1,604 @@
/*
Copyright 2017 Statoil ASA.
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"
#define NVERBOSE
#define BOOST_TEST_MODULE TEST_LINEARISEDOUTPUTTABLE
#include <boost/test/unit_test.hpp>
#include <opm/output/eclipse/LinearisedOutputTable.hpp>
#include <cstddef>
#include <initializer_list>
#include <vector>
namespace {
template <class Collection1, class Collection2>
void check_is_close(const Collection1& c1, const Collection2& c2)
{
BOOST_REQUIRE_EQUAL(c1.size(), c2.size());
if (! c1.empty()) {
auto i1 = c1.begin(), e1 = c1.end();
auto i2 = c2.begin();
for (; i1 != e1; ++i1, ++i2) {
BOOST_CHECK_CLOSE(*i1, *i2, 1.0e-10);
}
}
}
std::vector<double>
makeTable(const std::size_t ncol,
std::initializer_list<double> data)
{
auto result = std::vector<double>(data.size(), 0.0);
const auto nrows = data.size() / ncol;
auto di = std::begin(data);
for (auto i = 0*nrows; i < nrows; ++i) {
for (auto j = 0*ncol; j < ncol; ++j, ++di) {
result[i + j*nrows] = *di;
}
}
return result;
}
std::vector<double> norne_pvto_1_1_incl_der()
{
return makeTable(5, {
5.00e+01, 9.040365230755323e-01, 7.661326466741799e-01, 2.000000000000000e+20, 2.000000000000000e+20,
7.50e+01, 9.077375549181221e-01, 7.279370929575959e-01, 1.480412737035941e-04, -1.527822148663360e-03,
1.00e+02, 9.112115468727220e-01, 6.929365375457962e-01, 1.389596781839941e-04, -1.400022216471988e-03,
1.25e+02, 9.144863787253888e-01, 6.607560539923331e-01, 1.309932741066744e-04, -1.287219342138526e-03,
1.50e+02, 9.175658812302724e-01, 6.314975094496025e-01, 1.231801001953414e-04, -1.170341781709223e-03,
});
}
std::vector<double> norne_pvto_1_2_incl_der()
{
return makeTable(5, {
7.00e+01, 8.887150957146157e-01, 8.336914593945738e-01, -2.015977284331128e-03, 8.889317463209720e-03,
9.50e+01, 8.924826189009969e-01, 7.940236822962605e-01, 1.507009274552473e-04, -1.586711083932530e-03,
1.20e+02, 8.960252320705352e-01, 7.580585719716880e-01, 1.417045267815320e-04, -1.438604412982900e-03,
1.45e+02, 8.993533649306149e-01, 7.247005358022682e-01, 1.331253144031885e-04, -1.334321446776792e-03,
1.70e+02, 9.024944947835819e-01, 6.942265344489091e-01, 1.256451941186798e-04, -1.218960054134364e-03,
});
}
std::vector<double> norne_pvto_1_3_incl_der()
{
return makeTable(5, {
9.00e+01, 8.736829229935872e-01, 9.063100860929328e-01, -1.922272726474237e-03, 9.286269398767148e-03,
1.15e+02, 8.775085776463464e-01, 8.653930746019195e-01, 1.530261861103677e-04, -1.636680459640534e-03,
1.40e+02, 8.811038468993955e-01, 8.281051192663491e-01, 1.438107701219638e-04, -1.491518213422815e-03,
1.65e+02, 8.844861135680170e-01, 7.932610884018090e-01, 1.352906667448606e-04, -1.393761234581605e-03,
1.90e+02, 8.876816418559648e-01, 7.613050101680658e-01, 1.278211315179112e-04, -1.278243129349725e-03,
});
}
std::vector<double> norne_sgfn_incl_der()
{
return makeTable(5, {
0, 0, 0, 0, 0,
5.000000000000000e-02, 1.655000000000000e-03, 0, 3.310000000000000e-02, 0,
1.000000000000000e-01, 6.913000000000000e-03, 0, 1.051600000000000e-01, 0,
1.500000000000000e-01, 1.621300000000000e-02, 0, 1.860000000000001e-01, 0,
2.000000000000000e-01, 2.999000000000000e-02, 0, 2.755399999999998e-01, 0,
2.500000000000000e-01, 4.865500000000000e-02, 0, 3.733000000000000e-01, 0,
3.000000000000000e-01, 7.257300000000000e-02, 0, 4.783600000000001e-01, 0,
3.500000000000000e-01, 1.020460000000000e-01, 0, 5.894600000000001e-01, 0,
4.000000000000000e-01, 1.372870000000000e-01, 0, 7.048199999999992e-01, 0,
4.500000000000000e-01, 1.784020000000000e-01, 0, 8.223000000000005e-01, 0,
5.000000000000000e-01, 2.253680000000000e-01, 0, 9.393200000000004e-01, 0,
5.500000000000000e-01, 2.780300000000000e-01, 0, 1.053239999999999e+00, 0,
6.000000000000000e-01, 3.360930000000000e-01, 0, 1.161260000000001e+00, 0,
6.500000000000000e-01, 3.991350000000000e-01, 0, 1.260840000000000e+00, 0,
7.000000000000000e-01, 4.666310000000000e-01, 0, 1.349920000000002e+00, 0,
7.500000000000000e-01, 5.380000000000000e-01, 0, 1.427379999999999e+00, 0,
8.000000000000000e-01, 6.126650000000000e-01, 0, 1.493299999999998e+00, 0,
8.500000000000000e-01, 6.901690000000000e-01, 0, 1.550080000000002e+00, 0,
9.000000000000000e-01, 7.703950000000001e-01, 0, 1.604519999999999e+00, 0,
9.500000000000000e-01, 8.542180000000000e-01, 0, 1.676460000000002e+00, 0,
9.999000000000000e-01, 9.499000000000000e-01, 0, 1.917474949899796e+00, 0,
1.000000000000000e+00, 9.500000000000000e-01, 0, 1.000000000000000e+00, 0,
});
}
} // Anonymous
// ---------------------------------------------------------------------
// Saturation Functions
BOOST_AUTO_TEST_SUITE (Tabulated_Functions)
BOOST_AUTO_TEST_CASE (SGFN)
{
const auto inputData = norne_sgfn_incl_der();
const auto tableRows = inputData.size() / 5;
const auto numTables = std::size_t{ 1};
const auto numPrimary = std::size_t{ 1};
const auto numRows = std::size_t{30};
const auto numCols = std::size_t{ 5};
auto sgfn = ::Opm::LinearisedOutputTable {
numTables, numPrimary, numRows, numCols
};
// Sg
{
const auto tableID = std::size_t{0};
const auto primID = std::size_t{0};
const auto colID = std::size_t{0};
std::copy(inputData.begin() + 0*tableRows,
inputData.begin() + 1*tableRows,
sgfn.column(tableID, primID, colID));
}
// Krg
{
const auto tableID = std::size_t{0};
const auto primID = std::size_t{0};
const auto colID = std::size_t{1};
std::copy(inputData.begin() + 1*tableRows,
inputData.begin() + 2*tableRows,
sgfn.column(tableID, primID, colID));
}
// Pcgo
{
const auto tableID = std::size_t{0};
const auto primID = std::size_t{0};
const auto colID = std::size_t{2};
std::copy(inputData.begin() + 2*tableRows,
inputData.begin() + 3*tableRows,
sgfn.column(tableID, primID, colID));
}
// d[Krg]/dSg
{
const auto tableID = std::size_t{0};
const auto primID = std::size_t{0};
const auto colID = std::size_t{3};
std::copy(inputData.begin() + 3*tableRows,
inputData.begin() + 4*tableRows,
sgfn.column(tableID, primID, colID));
}
// d[Pcgo]/dSg
{
const auto tableID = std::size_t{0};
const auto primID = std::size_t{0};
const auto colID = std::size_t{4};
std::copy(inputData.begin() + 4*tableRows,
inputData.begin() + 5*tableRows,
sgfn.column(tableID, primID, colID));
}
const auto expect = makeTable(5,
{
0, 0, 0, 0, 0,
5.000e-02, 1.65500e-03, 0, 3.310000000000000e-02, 0,
1.000e-01, 6.91300e-03, 0, 1.051600000000000e-01, 0,
1.500e-01, 1.62130e-02, 0, 1.860000000000001e-01, 0,
2.000e-01, 2.99900e-02, 0, 2.755399999999998e-01, 0,
2.500e-01, 4.86550e-02, 0, 3.733000000000000e-01, 0,
3.000e-01, 7.25730e-02, 0, 4.783600000000001e-01, 0,
3.500e-01, 1.02046e-01, 0, 5.894600000000001e-01, 0,
4.000e-01, 1.37287e-01, 0, 7.048199999999992e-01, 0,
4.500e-01, 1.78402e-01, 0, 8.223000000000005e-01, 0,
5.000e-01, 2.25368e-01, 0, 9.393200000000004e-01, 0,
5.500e-01, 2.78030e-01, 0, 1.053239999999999e+00, 0,
6.000e-01, 3.36093e-01, 0, 1.161260000000001e+00, 0,
6.500e-01, 3.99135e-01, 0, 1.260840000000000e+00, 0,
7.000e-01, 4.66631e-01, 0, 1.349920000000002e+00, 0,
7.500e-01, 5.38000e-01, 0, 1.427379999999999e+00, 0,
8.000e-01, 6.12665e-01, 0, 1.493299999999998e+00, 0,
8.500e-01, 6.90169e-01, 0, 1.550080000000002e+00, 0,
9.000e-01, 7.70395e-01, 0, 1.604519999999999e+00, 0,
9.500e-01, 8.54218e-01, 0, 1.676460000000002e+00, 0,
9.999e-01, 9.49900e-01, 0, 1.917474949899796e+00, 0,
1.000e+00, 9.50000e-01, 0, 1.000000000000000e+00, 0,
1.000e+20, 1.00000e+20, 1.e+20, 1.000000000000000e+20, 1.e+20,
1.000e+20, 1.00000e+20, 1.e+20, 1.000000000000000e+20, 1.e+20,
1.000e+20, 1.00000e+20, 1.e+20, 1.000000000000000e+20, 1.e+20,
1.000e+20, 1.00000e+20, 1.e+20, 1.000000000000000e+20, 1.e+20,
1.000e+20, 1.00000e+20, 1.e+20, 1.000000000000000e+20, 1.e+20,
1.000e+20, 1.00000e+20, 1.e+20, 1.000000000000000e+20, 1.e+20,
1.000e+20, 1.00000e+20, 1.e+20, 1.000000000000000e+20, 1.e+20,
1.000e+20, 1.00000e+20, 1.e+20, 1.000000000000000e+20, 1.e+20,
});
check_is_close(sgfn.getData(), expect);
}
// ---------------------------------------------------------------------
// PVT
BOOST_AUTO_TEST_CASE (PVTO)
{
const auto inputData = std::vector< std::vector<double> >
{
norne_pvto_1_1_incl_der(),
norne_pvto_1_2_incl_der(),
norne_pvto_1_3_incl_der(),
};
const auto numTables = std::size_t{1};
const auto numPrimary = std::size_t{5};
const auto numRows = std::size_t{8};
const auto numCols = std::size_t{5};
auto pvto = ::Opm::LinearisedOutputTable {
numTables, numPrimary, numRows, numCols
};
{
auto primID = std::size_t{0};
for (const auto& subTab : inputData) {
const auto tableRows = subTab.size() / numCols;
// Po
{
const auto tableID = std::size_t{0};
const auto colID = std::size_t{0};
std::copy(subTab.begin() + 0*tableRows,
subTab.begin() + 1*tableRows,
pvto.column(tableID, primID, colID));
}
// 1/Bo
{
const auto tableID = std::size_t{0};
const auto colID = std::size_t{1};
std::copy(subTab.begin() + 1*tableRows,
subTab.begin() + 2*tableRows,
pvto.column(tableID, primID, colID));
}
// 1/(Bo*mu_o)
{
const auto tableID = std::size_t{0};
const auto colID = std::size_t{2};
std::copy(subTab.begin() + 2*tableRows,
subTab.begin() + 3*tableRows,
pvto.column(tableID, primID, colID));
}
// d[1/Bo]/dPo
{
const auto tableID = std::size_t{0};
const auto colID = std::size_t{3};
std::copy(subTab.begin() + 3*tableRows,
subTab.begin() + 4*tableRows,
pvto.column(tableID, primID, colID));
}
// d[1/(Bo*mu_o)]/dPo
{
const auto tableID = std::size_t{0};
const auto colID = std::size_t{4};
std::copy(subTab.begin() + 4*tableRows,
subTab.begin() + 5*tableRows,
pvto.column(tableID, primID, colID));
}
primID += 1;
}
}
const auto expect = makeTable(5, {
5.000000000000000e+01, 9.040365230755323e-01, 7.661326466741799e-01, 2.000000000000000e+20, 2.000000000000000e+20,
7.500000000000000e+01, 9.077375549181221e-01, 7.279370929575959e-01, 1.480412737035941e-04, -1.527822148663360e-03,
1.000000000000000e+02, 9.112115468727220e-01, 6.929365375457962e-01, 1.389596781839941e-04, -1.400022216471988e-03,
1.250000000000000e+02, 9.144863787253888e-01, 6.607560539923331e-01, 1.309932741066744e-04, -1.287219342138526e-03,
1.500000000000000e+02, 9.175658812302724e-01, 6.314975094496025e-01, 1.231801001953414e-04, -1.170341781709223e-03,
1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
7.000000000000000e+01, 8.887150957146157e-01, 8.336914593945738e-01, -2.015977284331128e-03, 8.889317463209720e-03,
9.500000000000000e+01, 8.924826189009969e-01, 7.940236822962605e-01, 1.507009274552473e-04, -1.586711083932530e-03,
1.200000000000000e+02, 8.960252320705352e-01, 7.580585719716880e-01, 1.417045267815320e-04, -1.438604412982900e-03,
1.450000000000000e+02, 8.993533649306149e-01, 7.247005358022682e-01, 1.331253144031885e-04, -1.334321446776792e-03,
1.700000000000000e+02, 9.024944947835819e-01, 6.942265344489091e-01, 1.256451941186798e-04, -1.218960054134364e-03,
1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
9.000000000000000e+01, 8.736829229935872e-01, 9.063100860929328e-01, -1.922272726474237e-03, 9.286269398767148e-03,
1.150000000000000e+02, 8.775085776463464e-01, 8.653930746019195e-01, 1.530261861103677e-04, -1.636680459640534e-03,
1.400000000000000e+02, 8.811038468993955e-01, 8.281051192663491e-01, 1.438107701219638e-04, -1.491518213422815e-03,
1.650000000000000e+02, 8.844861135680170e-01, 7.932610884018090e-01, 1.352906667448606e-04, -1.393761234581605e-03,
1.900000000000000e+02, 8.876816418559648e-01, 7.613050101680658e-01, 1.278211315179112e-04, -1.278243129349725e-03,
1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
});
check_is_close(pvto.getData(), expect);
}
BOOST_AUTO_TEST_SUITE_END ()
// ---------------------------------------------------------------------
// Derivatives of tabulated, piecewise linear functions
BOOST_AUTO_TEST_SUITE (Calculated_Slopes)
BOOST_AUTO_TEST_CASE (No_Derivatives)
{
auto descr = ::Opm::DifferentiateOutputTable::Descriptor{};
descr.tableID = 0;
descr.primID = 0;
descr.numActRows = 1;
auto linTable = ::Opm::LinearisedOutputTable {
1, 1, 3, 3 // Single table, one prim. key, 3 declared rows, 3 cols.
};
{
auto x = 0.0;
auto y = x;
*linTable.column(descr.tableID, descr.primID, 0) = x;
*linTable.column(descr.tableID, descr.primID, 1) = y;
}
// Argument dependent symbol lookup.
calcSlopes(1, descr, linTable); // One dependent column.
// Too few active rows (< 2). Leave derivatives alone
const auto expect = makeTable(3, {
0, 0, 1.e+20,
1.e+20, 1.e+20, 1.e+20,
1.e+20, 1.e+20, 1.e+20,
});
check_is_close(linTable.getData(), expect);
}
BOOST_AUTO_TEST_CASE (Constant_Function)
{
auto descr = ::Opm::DifferentiateOutputTable::Descriptor{};
descr.tableID = 0;
descr.primID = 0;
descr.numActRows = 11;
auto linTable = ::Opm::LinearisedOutputTable {
1, 1, 15, 3 // Single table, one prim. key, 15 declared rows, 3 cols.
};
{
auto x = std::vector<double> {
0.0, 0.1, 0.2, 0.3, 0.4,
0.5, 0.6, 0.7, 0.8, 0.9, 1.0 };
auto y = std::vector<double>(x.size(), 1.25);
std::copy(x.begin(), x.end(),
linTable.column(descr.tableID, descr.primID, 0));
std::copy(y.begin(), y.end(),
linTable.column(descr.tableID, descr.primID, 1));
}
// Argument dependent symbol lookup.
calcSlopes(1, descr, linTable); // One dependent column.
// Compute slopes for all intervals, store in left end point.
// Non-active rows left at defaulted values.
const auto expect = makeTable(3, {
0, 1.25e+00, 1.0e+20,
1.0e-01, 1.25e+00, 0,
2.0e-01, 1.25e+00, 0,
3.0e-01, 1.25e+00, 0,
4.0e-01, 1.25e+00, 0,
5.0e-01, 1.25e+00, 0,
6.0e-01, 1.25e+00, 0,
7.0e-01, 1.25e+00, 0,
8.0e-01, 1.25e+00, 0,
9.0e-01, 1.25e+00, 0,
1.0e+00, 1.25e+00, 0,
1.0e+20, 1.00e+20, 1.0e+20,
1.0e+20, 1.00e+20, 1.0e+20,
1.0e+20, 1.00e+20, 1.0e+20,
1.0e+20, 1.00e+20, 1.0e+20,
});
check_is_close(linTable.getData(), expect);
}
BOOST_AUTO_TEST_CASE (Linear_Function)
{
auto descr = ::Opm::DifferentiateOutputTable::Descriptor{};
descr.tableID = 0;
descr.primID = 0;
descr.numActRows = 11;
auto linTable = ::Opm::LinearisedOutputTable {
1, 1, 11, 3 // Single table, one prim. key, 11 declared rows, 3 cols.
};
{
auto x = std::vector<double> {
0.0, 0.1, 0.2, 0.3, 0.4,
0.5, 0.6, 0.7, 0.8, 0.9, 1.0 };
auto y = x;
std::copy(x.begin(), x.end(),
linTable.column(descr.tableID, descr.primID, 0));
std::copy(y.begin(), y.end(),
linTable.column(descr.tableID, descr.primID, 1));
}
// Argument dependent symbol lookup.
calcSlopes(1, descr, linTable); // One dependent column.
// Compute slopes for all intervals, store in left end point.
const auto expect = makeTable(3, {
0, 0, 1.0e+20,
1.0e-01, 1.0e-01, 1.0,
2.0e-01, 2.0e-01, 1.0,
3.0e-01, 3.0e-01, 1.0,
4.0e-01, 4.0e-01, 1.0,
5.0e-01, 5.0e-01, 1.0,
6.0e-01, 6.0e-01, 1.0,
7.0e-01, 7.0e-01, 1.0,
8.0e-01, 8.0e-01, 1.0,
9.0e-01, 9.0e-01, 1.0,
1.0e+00, 1.0e+00, 1.0,
});
check_is_close(linTable.getData(), expect);
}
BOOST_AUTO_TEST_CASE (Nonlinear_Functions)
{
auto descr = ::Opm::DifferentiateOutputTable::Descriptor{};
descr.tableID = 0;
descr.primID = 0;
descr.numActRows = 11;
auto linTable = ::Opm::LinearisedOutputTable {
1, 1, 15, 7 // Single table, one prim. key, 15 declared rows, 7 cols.
};
{
const auto x = std::vector<double> {
0.0, 0.1, 0.2, 0.3, 0.4,
0.5, 0.6, 0.7, 0.8, 0.9, 1.0 };
// sin(2*pi * x)
const auto s = std::vector<double> {
0,
5.877852522924731e-01,
9.510565162951535e-01,
9.510565162951536e-01,
5.877852522924732e-01,
1.224646799147353e-16,
-5.877852522924730e-01,
-9.510565162951535e-01,
-9.510565162951536e-01,
-5.877852522924734e-01,
-2.449293598294706e-16,
};
// cos(4*pi * x)^2
const auto c = std::vector<double> {
1.000000000000000e+00,
9.549150281252630e-02,
6.545084971874736e-01,
6.545084971874737e-01,
9.549150281252616e-02,
1.000000000000000e+00,
9.549150281252648e-02,
6.545084971874734e-01,
6.545084971874742e-01,
9.549150281252602e-02,
1.000000000000000e+00,
};
// exp(-x) / (1 + x)
const auto f = std::vector<double> {
1.000000000000000e+00,
8.225794709417813e-01,
6.822756275649848e-01,
5.698601697551676e-01,
4.788000328825995e-01,
4.043537731417556e-01,
3.430072725587666e-01,
2.921090022302409e-01,
2.496272022873453e-01,
2.139840314424206e-01,
1.839397205857212e-01,
};
std::copy(x.begin(), x.end(),
linTable.column(descr.tableID, descr.primID, 0));
std::copy(s.begin(), s.end(),
linTable.column(descr.tableID, descr.primID, 1));
std::copy(c.begin(), c.end(),
linTable.column(descr.tableID, descr.primID, 2));
std::copy(f.begin(), f.end(),
linTable.column(descr.tableID, descr.primID, 3));
}
// Argument dependent symbol lookup.
calcSlopes(3, descr, linTable); // Three dependent columns.
// Compute slopes for all intervals, store in left end point.
const auto expect = makeTable(7, {
0, 0, 1.000000000000000e+00, 1.000000000000000e+00, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.0e-01, 5.877852522924731e-01, 9.549150281252630e-02, 8.225794709417813e-01, 5.877852522924731e+00, -9.045084971874736e+00, -1.774205290582187e+00,
2.0e-01, 9.510565162951535e-01, 6.545084971874736e-01, 6.822756275649848e-01, 3.632712640026804e+00, 5.590169943749473e+00, -1.403038433767965e+00,
3.0e-01, 9.510565162951536e-01, 6.545084971874737e-01, 5.698601697551676e-01, 1.110223024625157e-15, 1.110223024625157e-15, -1.124154578098172e+00,
4.0e-01, 5.877852522924732e-01, 9.549150281252616e-02, 4.788000328825995e-01, -3.632712640026803e+00, -5.590169943749474e+00, -9.106013687256805e-01,
5.0e-01, 1.224646799147353e-16, 1.000000000000000e+00, 4.043537731417556e-01, -5.877852522924733e+00, 9.045084971874740e+00, -7.444625974084391e-01,
6.0e-01, -5.877852522924730e-01, 9.549150281252648e-02, 3.430072725587666e-01, -5.877852522924733e+00, -9.045084971874736e+00, -6.134650058298908e-01,
7.0e-01, -9.510565162951535e-01, 6.545084971874734e-01, 2.921090022302409e-01, -3.632712640026806e+00, 5.590169943749470e+00, -5.089827032852569e-01,
8.0e-01, -9.510565162951536e-01, 6.545084971874742e-01, 2.496272022873453e-01, -1.110223024625156e-15, 7.771561172376089e-15, -4.248179994289554e-01,
9.0e-01, -5.877852522924734e-01, 9.549150281252602e-02, 2.139840314424206e-01, 3.632712640026804e+00, -5.590169943749483e+00, -3.564317084492472e-01,
1.0e+00, -2.449293598294706e-16, 1.000000000000000e+00, 1.839397205857212e-01, 5.877852522924733e+00, 9.045084971874742e+00, -3.004431085669943e-01,
1.0e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.0e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.0e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
1.0e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20, 1.000000000000000e+20,
});
check_is_close(linTable.getData(), expect);
}
BOOST_AUTO_TEST_SUITE_END ()

253
tests/test_RFT.cpp Normal file
View File

@ -0,0 +1,253 @@
/*
Copyright 2015 Statoil ASA.
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"
#define BOOST_TEST_MODULE EclipseRFTWriter
#include <boost/test/unit_test.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <opm/output/eclipse/EclipseIO.hpp>
#include <opm/output/data/Wells.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/EclipseState/Schedule/Schedule.hpp>
#include <opm/parser/eclipse/EclipseState/SummaryConfig/SummaryConfig.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Well.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/EclipseGrid.hpp>
#include <opm/parser/eclipse/EclipseState/IOConfig/IOConfig.hpp>
#include <ert/ecl/ecl_rft_file.h>
#include <ert/ecl/ecl_util.h>
#include <ert/util/test_work_area.h>
#include <ert/util/util.h>
#include <ert/util/TestArea.hpp>
#include <vector>
using namespace Opm;
namespace {
void verifyRFTFile(const std::string& rft_filename) {
ecl_rft_file_type * new_rft_file = ecl_rft_file_alloc(rft_filename.c_str());
std::shared_ptr<ecl_rft_file_type> rft_file;
rft_file.reset(new_rft_file, ecl_rft_file_free);
//Get RFT node for well/time OP_1/10 OKT 2008
time_t recording_time = ecl_util_make_date(10, 10, 2008);
ecl_rft_node_type * ecl_rft_node = ecl_rft_file_get_well_time_rft(rft_file.get() , "OP_1" , recording_time);
BOOST_CHECK(ecl_rft_node_is_RFT(ecl_rft_node));
//Verify RFT data for completions (ijk) 9 9 1, 9 9 2 and 9 9 3 for OP_1
const ecl_rft_cell_type * ecl_rft_cell1 = ecl_rft_node_lookup_ijk(ecl_rft_node, 8, 8, 0);
const ecl_rft_cell_type * ecl_rft_cell2 = ecl_rft_node_lookup_ijk(ecl_rft_node, 8, 8, 1);
const ecl_rft_cell_type * ecl_rft_cell3 = ecl_rft_node_lookup_ijk(ecl_rft_node, 8, 8, 2);
double tol = 0.00001;
BOOST_CHECK_CLOSE(ecl_rft_cell_get_pressure(ecl_rft_cell1), 0.00000, tol);
BOOST_CHECK_CLOSE(ecl_rft_cell_get_pressure(ecl_rft_cell2), 0.00001, tol);
BOOST_CHECK_CLOSE(ecl_rft_cell_get_pressure(ecl_rft_cell3), 0.00002, tol);
BOOST_CHECK_CLOSE(ecl_rft_cell_get_sgas(ecl_rft_cell1), 0.0, tol);
BOOST_CHECK_CLOSE(ecl_rft_cell_get_sgas(ecl_rft_cell2), 0.2, tol);
BOOST_CHECK_CLOSE(ecl_rft_cell_get_sgas(ecl_rft_cell3), 0.4, tol);
BOOST_CHECK_CLOSE(ecl_rft_cell_get_swat(ecl_rft_cell1), 0.0, tol);
BOOST_CHECK_CLOSE(ecl_rft_cell_get_swat(ecl_rft_cell2), 0.1, tol);
BOOST_CHECK_CLOSE(ecl_rft_cell_get_swat(ecl_rft_cell3), 0.2, tol);
BOOST_CHECK_CLOSE(ecl_rft_cell_get_soil(ecl_rft_cell1), 1.0, tol);
BOOST_CHECK_CLOSE(ecl_rft_cell_get_soil(ecl_rft_cell2), 0.7, tol);
BOOST_CHECK_CLOSE(ecl_rft_cell_get_soil(ecl_rft_cell3), 0.4, tol);
BOOST_CHECK_EQUAL(ecl_rft_cell_get_depth(ecl_rft_cell1), (0.250 + (0.250/2)));
BOOST_CHECK_EQUAL(ecl_rft_cell_get_depth(ecl_rft_cell2), (2*0.250 + (0.250/2)));
BOOST_CHECK_EQUAL(ecl_rft_cell_get_depth(ecl_rft_cell3), (3*0.250 + (0.250/2)));
}
data::Solution createBlackoilState( int timeStepIdx, int numCells ) {
std::vector< double > pressure( numCells );
std::vector< double > swat( numCells, 0 );
std::vector< double > sgas( numCells, 0 );
for( int i = 0; i < numCells; ++i )
pressure[ i ] = timeStepIdx * 1e5 + 1e4 + i;
data::Solution sol;
sol.insert( "PRESSURE", UnitSystem::measure::pressure, pressure , data::TargetType::RESTART_SOLUTION );
sol.insert( "SWAT", UnitSystem::measure::identity, swat , data::TargetType::RESTART_SOLUTION );
sol.insert( "SGAS", UnitSystem::measure::identity, sgas, data::TargetType::RESTART_SOLUTION );
return sol;
}
}
BOOST_AUTO_TEST_CASE(test_RFT) {
ParseContext parse_context;
std::string eclipse_data_filename = "testrft.DATA";
ERT::TestArea test_area("test_RFT");
test_area.copyFile( eclipse_data_filename );
auto deck = Parser().parseFile( eclipse_data_filename, parse_context );
auto eclipseState = Parser::parse( deck );
{
/* eclipseWriter is scoped here to ensure it is destroyed after the
* file itself has been written, because we're going to reload it
* immediately. first upon destruction can we guarantee it being
* written to disk and flushed.
*/
const auto& grid = eclipseState.getInputGrid();
const auto numCells = grid.getCartesianSize( );
Schedule schedule(deck, grid, eclipseState.get3DProperties(), eclipseState.runspec().phases(), parse_context);
SummaryConfig summary_config( deck, schedule, eclipseState.getTableManager( ), parse_context);
EclipseIO eclipseWriter( eclipseState, grid, schedule, summary_config );
time_t start_time = schedule.posixStartTime();
time_t step_time = ecl_util_make_date(10, 10, 2008 );
data::Rates r1, r2;
r1.set( data::Rates::opt::wat, 4.11 );
r1.set( data::Rates::opt::oil, 4.12 );
r1.set( data::Rates::opt::gas, 4.13 );
r2.set( data::Rates::opt::wat, 4.21 );
r2.set( data::Rates::opt::oil, 4.22 );
r2.set( data::Rates::opt::gas, 4.23 );
std::vector<Opm::data::Completion> well1_comps(9);
for (size_t i = 0; i < 9; ++i) {
Opm::data::Completion well_comp { grid.getGlobalIndex(8,8,i) ,r1, 0.0 , 0.0, (double)i, 0.1*i,0.2*i};
well1_comps[i] = well_comp;
}
std::vector<Opm::data::Completion> well2_comps(6);
for (size_t i = 0; i < 6; ++i) {
Opm::data::Completion well_comp { grid.getGlobalIndex(3,3,i+3) ,r2, 0.0 , 0.0, (double)i, i*0.1,i*0.2};
well2_comps[i] = well_comp;
}
Opm::data::Wells wells;
wells["OP_1"] = { r1, 1.0, 1.1, 3.1, 1, well1_comps };
wells["OP_2"] = { r2, 1.0, 1.1, 3.2, 1, well2_comps };
eclipseWriter.writeTimeStep( 2,
false,
step_time - start_time,
createBlackoilState( 2, numCells ),
wells,
{},
{},
{});
}
verifyRFTFile("TESTRFT.RFT");
}
namespace {
void verifyRFTFile2(const std::string& rft_filename) {
ecl_rft_file_type * rft_file = ecl_rft_file_alloc(rft_filename.c_str());
/*
Expected occurences:
WELL | DATE
---------------------
OP_1 | 10. OKT 2008
OP_2 | 10. OKT 2008
OP_2 | 10. NOV 2008
---------------------
*/
BOOST_CHECK_EQUAL( 3 , ecl_rft_file_get_size( rft_file ));
BOOST_CHECK( ecl_rft_file_get_well_time_rft(rft_file , "OP_1" , ecl_util_make_date(10, 10, 2008)) != NULL );
BOOST_CHECK( ecl_rft_file_get_well_time_rft(rft_file , "OP_2" , ecl_util_make_date(10, 10, 2008)) != NULL );
BOOST_CHECK( ecl_rft_file_get_well_time_rft(rft_file , "OP_2" , ecl_util_make_date(10, 11, 2008)) != NULL );
ecl_rft_file_free( rft_file );
}
}
BOOST_AUTO_TEST_CASE(test_RFT2) {
ParseContext parse_context;
std::string eclipse_data_filename = "testrft.DATA";
ERT::TestArea test_area("test_RFT");
test_area.copyFile( eclipse_data_filename );
auto deck = Parser().parseFile( eclipse_data_filename, parse_context );
auto eclipseState = Parser::parse( deck );
{
/* eclipseWriter is scoped here to ensure it is destroyed after the
* file itself has been written, because we're going to reload it
* immediately. first upon destruction can we guarantee it being
* written to disk and flushed.
*/
const auto& grid = eclipseState.getInputGrid();
const auto numCells = grid.getCartesianSize( );
Schedule schedule(deck, grid, eclipseState.get3DProperties(), eclipseState.runspec().phases(), parse_context);
SummaryConfig summary_config( deck, schedule, eclipseState.getTableManager( ), parse_context);
EclipseIO eclipseWriter( eclipseState, grid, schedule, summary_config );
time_t start_time = schedule.posixStartTime();
const auto& time_map = schedule.getTimeMap( );
for (int counter = 0; counter < 2; counter++) {
for (size_t step = 0; step < time_map.size(); step++) {
time_t step_time = time_map[step];
data::Rates r1, r2;
r1.set( data::Rates::opt::wat, 4.11 );
r1.set( data::Rates::opt::oil, 4.12 );
r1.set( data::Rates::opt::gas, 4.13 );
r2.set( data::Rates::opt::wat, 4.21 );
r2.set( data::Rates::opt::oil, 4.22 );
r2.set( data::Rates::opt::gas, 4.23 );
std::vector<Opm::data::Completion> well1_comps(9);
for (size_t i = 0; i < 9; ++i) {
Opm::data::Completion well_comp { grid.getGlobalIndex(8,8,i) ,r1, 0.0 , 0.0, (double)i, 0.1*i,0.2*i};
well1_comps[i] = well_comp;
}
std::vector<Opm::data::Completion> well2_comps(6);
for (size_t i = 0; i < 6; ++i) {
Opm::data::Completion well_comp { grid.getGlobalIndex(3,3,i+3) ,r2, 0.0 , 0.0, (double)i, i*0.1,i*0.2};
well2_comps[i] = well_comp;
}
Opm::data::Wells wells;
wells["OP_1"] = { r1, 1.0, 1.1, 3.1, 1, well1_comps };
wells["OP_2"] = { r2, 1.0, 1.1, 3.2, 1, well2_comps };
eclipseWriter.writeTimeStep( step,
false,
step_time - start_time,
createBlackoilState( 2, numCells ),
wells,
{},
{},
{});
}
verifyRFTFile2("TESTRFT.RFT");
}
}
}

605
tests/test_Restart.cpp Normal file
View File

@ -0,0 +1,605 @@
/*
Copyright 2014 Statoil IT
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"
#include <cstdlib>
#define BOOST_TEST_MODULE EclipseIO
#include <boost/test/unit_test.hpp>
#include <opm/output/eclipse/EclipseIO.hpp>
#include <opm/output/eclipse/RestartIO.hpp>
#include <opm/output/eclipse/RestartValue.hpp>
#include <opm/output/data/Cells.hpp>
#include <opm/output/data/Wells.hpp>
#include <opm/parser/eclipse/Deck/Deck.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/EclipseGrid.hpp>
#include <opm/parser/eclipse/EclipseState/IOConfig/IOConfig.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
#include <opm/parser/eclipse/EclipseState/SummaryConfig/SummaryConfig.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Well.hpp>
#include <opm/parser/eclipse/Parser/ParseContext.hpp>
#include <opm/parser/eclipse/Parser/Parser.hpp>
#include <opm/parser/eclipse/Utility/Functional.hpp>
// ERT stuff
#include <ert/ecl/ecl_kw.h>
#include <ert/ecl/ecl_file.h>
#include <ert/ecl/ecl_util.h>
#include <ert/ecl/ecl_kw_magic.h>
#include <ert/ecl_well/well_info.h>
#include <ert/ecl_well/well_state.h>
#include <ert/util/TestArea.hpp>
using namespace Opm;
inline std::string input( const std::string& rst_name = "FIRST_SIM" ) {
return std::string(
"RUNSPEC\n"
"OIL\n"
"GAS\n"
"WATER\n"
"DISGAS\n"
"VAPOIL\n"
"UNIFOUT\n"
"UNIFIN\n"
"DIMENS\n"
" 10 10 10 /\n"
"GRID\n"
"DXV\n"
"10*0.25 /\n"
"DYV\n"
"10*0.25 /\n"
"DZV\n"
"10*0.25 /\n"
"TOPS\n"
"100*0.25 /\n"
"\n"
"SOLUTION\n"
"RESTART\n"
) + rst_name + std::string(
" 1/\n"
"\n"
"START -- 0 \n"
"1 NOV 1979 / \n"
"SCHEDULE\n"
"SKIPREST\n"
"RPTRST\n"
"BASIC=1\n"
"/\n"
"DATES -- 1\n"
" 10 OKT 2008 / \n"
"/\n"
"WELSPECS\n"
" 'OP_1' 'OP' 9 9 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* / \n"
" 'OP_2' 'OP' 9 9 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* / \n"
"/\n"
"COMPDAT\n"
" 'OP_1' 9 9 1 1 'OPEN' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 / \n"
" 'OP_2' 9 9 2 2 'OPEN' 1* 46.825 0.311 4332.346 1* 1* 'X' 22.123 / \n"
" 'OP_1' 9 9 3 3 'OPEN' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 / \n"
"/\n"
"WCONPROD\n"
"'OP_1' 'OPEN' 'ORAT' 20000 4* 1000 /\n"
"/\n"
"WCONINJE\n"
"'OP_2' 'GAS' 'OPEN' 'RATE' 100 200 400 /\n"
"/\n"
"DATES -- 2\n"
" 20 JAN 2011 / \n"
"/\n"
"WELSPECS\n"
" 'OP_3' 'OP' 9 9 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* / \n"
"/\n"
"COMPDAT\n"
" 'OP_3' 9 9 1 1 'SHUT' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 / \n"
"/\n"
"WCONPROD\n"
"'OP_3' 'OPEN' 'ORAT' 20000 4* 1000 /\n"
"/\n"
"DATES -- 3\n"
" 15 JUN 2013 / \n"
"/\n"
"COMPDAT\n"
" 'OP_2' 9 9 3 9 SHUT 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 / \n"
" 'OP_1' 9 9 7 7 OPEN 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 / \n"
"/\n"
"DATES -- 4\n"
" 22 APR 2014 / \n"
"/\n"
"WELSPECS\n"
" 'OP_4' 'OP' 9 9 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* / \n"
"/\n"
"COMPDAT\n"
" 'OP_4' 9 9 3 9 'SHUT' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 / \n"
" 'OP_3' 9 9 3 9 'SHUT' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 / \n"
"/\n"
"WCONPROD\n"
"'OP_4' 'OPEN' 'ORAT' 20000 4* 1000 /\n"
"/\n"
"DATES -- 5\n"
" 30 AUG 2014 / \n"
"/\n"
"WELSPECS\n"
" 'OP_5' 'OP' 9 9 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* / \n"
"/\n"
"COMPDAT\n"
" 'OP_5' 9 9 3 9 'OPEN' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 / \n"
"/\n"
"WCONPROD\n"
"'OP_5' 'OPEN' 'ORAT' 20000 4* 1000 /\n"
"/\n"
"DATES -- 6\n"
" 15 SEP 2014 / \n"
"/\n"
"WCONPROD\n"
"'OP_3' 'SHUT' 'ORAT' 20000 4* 1000 /\n"
"/\n"
"DATES -- 7\n"
" 9 OCT 2014 / \n"
"/\n"
"WELSPECS\n"
" 'OP_6' 'OP' 9 9 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* / \n"
"/\n"
"COMPDAT\n"
" 'OP_6' 9 9 3 9 'OPEN' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 / \n"
"/\n"
"WCONPROD\n"
"'OP_6' 'OPEN' 'ORAT' 20000 4* 1000 /\n"
"/\n"
"TSTEP -- 8\n"
"10 /"
"/\n"
);
}
namespace Opm {
namespace data {
/*
* Some test specific equivalence definitions and pretty-printing. Not fit as a
* general purpose implementation, but does its job for testing and
* pretty-pringing for debugging purposes.
*/
std::ostream& operator<<( std::ostream& stream, const Rates& r ) {
return stream << "{ "
<< "wat: " << r.get( Rates::opt::wat, 0.0 ) << ", "
<< "oil: " << r.get( Rates::opt::oil, 0.0 ) << ", "
<< "gas: " << r.get( Rates::opt::gas, 0.0 ) << " "
<< "}";
}
std::ostream& operator<<( std::ostream& stream, const Completion& c ) {
return stream << "{ index: "
<< c.index << ", "
<< c.rates << ", "
<< c.pressure << " }";
}
std::ostream& operator<<( std::ostream& stream,
const std::map< std::string, Well >& m ) {
stream << "\n";
for( const auto& p : m ) {
stream << p.first << ": \n"
<< "\t" << "bhp: " << p.second.bhp << "\n"
<< "\t" << "temp: " << p.second.temperature << "\n"
<< "\t" << "rates: " << p.second.rates << "\n"
<< "\t" << "completions: [\n";
for( const auto& c : p.second.completions )
stream << c << " ";
stream << "]\n";
}
return stream;
}
bool operator==( const Rates& lhs, const Rates& rhs ) {
using rt = Rates::opt;
BOOST_CHECK_EQUAL( lhs.has( rt::wat ), rhs.has( rt::wat ) );
BOOST_CHECK_EQUAL( lhs.has( rt::oil ), rhs.has( rt::oil ) );
BOOST_CHECK_EQUAL( lhs.has( rt::gas ), rhs.has( rt::gas ) );
BOOST_CHECK_EQUAL( lhs.has( rt::polymer ), rhs.has( rt::polymer ) );
BOOST_CHECK_EQUAL( lhs.get( rt::wat, 0.0 ), rhs.get( rt::wat, 0.0 ) );
BOOST_CHECK_EQUAL( lhs.get( rt::oil, 0.0 ), rhs.get( rt::oil, 0.0 ) );
BOOST_CHECK_EQUAL( lhs.get( rt::gas, 0.0 ), rhs.get( rt::gas, 0.0 ) );
BOOST_CHECK_EQUAL( lhs.get( rt::polymer, 0.0 ), rhs.get( rt::polymer, 0.0 ) );
return true;
}
bool operator==( const Completion& lhs, const Completion& rhs ) {
BOOST_CHECK_EQUAL( lhs.index, rhs.index );
BOOST_CHECK_EQUAL( lhs.rates, rhs.rates );
BOOST_CHECK_EQUAL( lhs.pressure, rhs.pressure );
BOOST_CHECK_EQUAL( lhs.reservoir_rate, rhs.reservoir_rate );
return true;
}
bool operator!=( const Completion& lhs, const Completion& rhs ) {
return !( lhs == rhs );
}
bool operator==( const Well& lhs, const Well& rhs ) {
BOOST_CHECK_EQUAL( lhs.rates, rhs.rates );
BOOST_CHECK_EQUAL( lhs.bhp, rhs.bhp );
BOOST_CHECK_EQUAL( lhs.temperature, rhs.temperature );
BOOST_CHECK_EQUAL( lhs.control, rhs.control );
BOOST_CHECK_EQUAL_COLLECTIONS(
lhs.completions.begin(), lhs.completions.end(),
rhs.completions.begin(), rhs.completions.end() );
return true;
}
}
data::Wells mkWells() {
data::Rates r1, r2, rc1, rc2, rc3;
r1.set( data::Rates::opt::wat, 5.67 );
r1.set( data::Rates::opt::oil, 6.78 );
r1.set( data::Rates::opt::gas, 7.89 );
r2.set( data::Rates::opt::wat, 8.90 );
r2.set( data::Rates::opt::oil, 9.01 );
r2.set( data::Rates::opt::gas, 10.12 );
rc1.set( data::Rates::opt::wat, 20.41 );
rc1.set( data::Rates::opt::oil, 21.19 );
rc1.set( data::Rates::opt::gas, 22.41 );
rc2.set( data::Rates::opt::wat, 23.19 );
rc2.set( data::Rates::opt::oil, 24.41 );
rc2.set( data::Rates::opt::gas, 25.19 );
rc3.set( data::Rates::opt::wat, 26.41 );
rc3.set( data::Rates::opt::oil, 27.19 );
rc3.set( data::Rates::opt::gas, 28.41 );
data::Well w1, w2;
w1.rates = r1;
w1.bhp = 1.23;
w1.temperature = 3.45;
w1.control = 1;
/*
* the completion keys (active indices) and well names correspond to the
* input deck. All other entries in the well structures are arbitrary.
*/
w1.completions.push_back( { 88, rc1, 30.45, 123.4 } );
w1.completions.push_back( { 288, rc2, 33.19, 123.4 } );
w2.rates = r2;
w2.bhp = 2.34;
w2.temperature = 4.56;
w2.control = 2;
w2.completions.push_back( { 188, rc3, 36.22, 123.4 } );
{
data::Wells wellRates;
wellRates["OP_1"] = w1;
wellRates["OP_2"] = w2;
return wellRates;
}
}
data::Solution mkSolution( int numCells ) {
using measure = UnitSystem::measure;
using namespace data;
data::Solution sol = {
{ "PRESSURE", { measure::pressure, std::vector<double>( numCells ), TargetType::RESTART_SOLUTION } },
{ "TEMP", { measure::temperature, std::vector<double>( numCells ), TargetType::RESTART_SOLUTION } },
{ "SWAT", { measure::identity, std::vector<double>( numCells ), TargetType::RESTART_SOLUTION } },
{ "SGAS", { measure::identity, std::vector<double>( numCells ), TargetType::RESTART_SOLUTION } }
};
sol.data("PRESSURE").assign( numCells, 6.0 );
sol.data("TEMP").assign( numCells, 7.0 );
sol.data("SWAT").assign( numCells, 8.0 );
sol.data("SGAS").assign( numCells, 9.0 );
fun::iota rsi( 300, 300 + numCells );
fun::iota rvi( 400, 400 + numCells );
sol.insert( "RS", measure::identity, { rsi.begin(), rsi.end() } , TargetType::RESTART_SOLUTION );
sol.insert( "RV", measure::identity, { rvi.begin(), rvi.end() } , TargetType::RESTART_SOLUTION );
return sol;
}
RestartValue first_sim(const EclipseState& es, EclipseIO& eclWriter, bool write_double) {
const auto& grid = es.getInputGrid();
auto num_cells = grid.getNumActive( );
auto start_time = ecl_util_make_date( 1, 11, 1979 );
auto first_step = ecl_util_make_date( 10, 10, 2008 );
auto sol = mkSolution( num_cells );
auto wells = mkWells();
eclWriter.writeTimeStep( 1,
false,
first_step - start_time,
sol, wells , {}, {}, {}, {}, write_double);
return { sol, wells , {}};
}
RestartValue second_sim(const EclipseIO& writer, const std::map<std::string, RestartKey>& keys) {
return writer.loadRestart( keys );
}
void compare( const RestartValue& fst,
const RestartValue& snd,
const std::map<std::string, RestartKey>& keys) {
for (const auto& pair : keys) {
double tol = 0.00001;
auto first = fst.solution.data( pair.first ).begin();
auto second = snd.solution.data( pair.first ).begin();
if (pair.first == "TEMP")
tol *= 10;
for( ; first != fst.solution.data( pair.first ).end(); ++first, ++second )
BOOST_CHECK_CLOSE( *first, *second, tol );
}
BOOST_CHECK_EQUAL( fst.wells, snd.wells );
}
struct Setup {
Deck deck;
EclipseState es;
const EclipseGrid& grid;
Schedule schedule;
SummaryConfig summary_config;
Setup( const char* path, const ParseContext& parseContext = ParseContext( )) :
deck( Parser().parseFile( path, parseContext ) ),
es( deck, parseContext ),
grid( es.getInputGrid( ) ),
schedule( deck, grid, es.get3DProperties(), es.runspec().phases(), parseContext),
summary_config( deck, schedule, es.getTableManager( ), parseContext)
{
}
};
BOOST_AUTO_TEST_CASE(EclipseReadWriteWellStateData) {
std::map<std::string, RestartKey> keys {{"PRESSURE" , RestartKey(UnitSystem::measure::pressure)},
{"SWAT" , RestartKey(UnitSystem::measure::identity)},
{"SGAS" , RestartKey(UnitSystem::measure::identity)},
{"TEMP" , RestartKey(UnitSystem::measure::temperature)}};
ERT::TestArea testArea("test_Restart");
testArea.copyFile( "FIRST_SIM.DATA" );
Setup setup("FIRST_SIM.DATA");
EclipseIO eclWriter( setup.es, setup.grid, setup.schedule, setup.summary_config);
auto state1 = first_sim( setup.es , eclWriter , false );
auto state2 = second_sim( eclWriter , keys );
compare(state1, state2 , keys);
BOOST_CHECK_THROW( second_sim( eclWriter, {{"SOIL" , RestartKey(UnitSystem::measure::pressure)}} ) , std::runtime_error );
BOOST_CHECK_THROW( second_sim( eclWriter, {{"SOIL" , { UnitSystem::measure::pressure, true}}} ) , std::runtime_error );
}
void compare_equal( const RestartValue& fst,
const RestartValue& snd ,
const std::map<std::string, RestartKey>& keys) {
for (const auto& pair : keys) {
auto first = fst.solution.data( pair.first ).begin();
auto second = snd.solution.data( pair.first ).begin();
for( ; first != fst.solution.data( pair.first ).end(); ++first, ++second )
BOOST_CHECK_EQUAL( *first, *second);
}
BOOST_CHECK_EQUAL( fst.wells, snd.wells );
//BOOST_CHECK_EQUAL( fst.extra, snd.extra );
}
BOOST_AUTO_TEST_CASE(EclipseReadWriteWellStateData_double) {
/*
Observe that the purpose of this test is to verify that with
write_double == true we can load solution fields which are
bitwise equal to those we stored. Unfortunately the scaling back
and forth between SI units and output units is enough to break
this equality for the pressure. For this test we therefor only
consider the saturations which have identity unit.
*/
std::map<std::string, RestartKey> keys {{"SWAT" , RestartKey(UnitSystem::measure::identity)},
{"SGAS" , RestartKey(UnitSystem::measure::identity)}};
ERT::TestArea testArea("test_Restart");
testArea.copyFile( "FIRST_SIM.DATA" );
Setup setup("FIRST_SIM.DATA");
EclipseIO eclWriter( setup.es, setup.grid, setup.schedule, setup.summary_config);
auto state1 = first_sim( setup.es , eclWriter , true);
auto state2 = second_sim( eclWriter , keys );
compare_equal( state1 , state2 , keys);
}
BOOST_AUTO_TEST_CASE(WriteWrongSOlutionSize) {
// This test leads to a segmentation violation on travis, disable until
// the cause has been found and fixed.
if (std::getenv("TRAVIS_CI"))
return;
Setup setup("FIRST_SIM.DATA");
{
ERT::TestArea testArea("test_Restart");
auto num_cells = setup.grid.getNumActive( ) + 1;
auto cells = mkSolution( num_cells );
auto wells = mkWells();
BOOST_CHECK_THROW( RestartIO::save("FILE.UNRST", 1 ,
100,
cells ,
wells ,
setup.es,
setup.grid ,
setup.schedule),
std::runtime_error);
}
}
BOOST_AUTO_TEST_CASE(ExtraData_KEYS) {
Setup setup("FIRST_SIM.DATA");
{
ERT::TestArea testArea("test_Restart");
auto num_cells = setup.grid.getNumActive( );
auto cells = mkSolution( num_cells );
auto wells = mkWells();
/* To fit with the eclipse format limitations the keys must be max 8 characters long. */
{
std::map<std::string , std::vector<double>> extra;
extra["TOO_LONG_KEY"] = {0,1,2,3};
BOOST_CHECK_THROW( RestartIO::save("FILE.UNRST", 1 ,
100,
cells ,
wells ,
setup.es,
setup.grid,
setup.schedule,
extra),
std::runtime_error);
}
/* The keys must be unique across solution and extra_data */
{
std::map<std::string , std::vector<double>> extra;
extra["PRESSURE"] = {0,1,2,3};
BOOST_CHECK_THROW( RestartIO::save("FILE.UNRST", 1 ,
100,
cells ,
wells ,
setup.es,
setup.grid,
setup.schedule,
extra),
std::runtime_error);
}
/* Must avoid using reserved keys like 'LOGIHEAD' */
{
std::map<std::string , std::vector<double>> extra;
extra["LOGIHEAD"] = {0,1,2,3};
BOOST_CHECK_THROW( RestartIO::save("FILE.UNRST", 1 ,
100,
cells ,
wells ,
setup.es,
setup.grid,
setup.schedule,
extra),
std::runtime_error);
}
}
}
BOOST_AUTO_TEST_CASE(ExtraData_content) {
Setup setup("FIRST_SIM.DATA");
{
ERT::TestArea testArea("test_Restart");
auto num_cells = setup.grid.getNumActive( );
auto cells = mkSolution( num_cells );
auto wells = mkWells();
{
std::map<std::string , std::vector<double>> extra;
extra["EXTRA"] = {0,1,2,3};
RestartIO::save("FILE.UNRST", 1 ,
100,
cells ,
wells ,
setup.es,
setup.grid,
setup.schedule,
extra);
{
ecl_file_type * f = ecl_file_open( "FILE.UNRST" , 0 );
BOOST_CHECK( ecl_file_has_kw( f , "EXTRA"));
{
ecl_kw_type * ex = ecl_file_iget_named_kw( f , "EXTRA" , 0 );
BOOST_CHECK_EQUAL( ecl_kw_get_header( ex) , "EXTRA" );
BOOST_CHECK_EQUAL( 4, ecl_kw_get_size( ex ));
BOOST_CHECK_EQUAL( 0 , ecl_kw_iget_double( ex, 0 ));
BOOST_CHECK_EQUAL( 3 , ecl_kw_iget_double( ex, 3 ));
}
ecl_file_close( f );
}
BOOST_CHECK_THROW( RestartIO::load( "FILE.UNRST" , 1 , {}, setup.es, setup.grid , setup.schedule, {{"NOT-THIS", true}}) , std::runtime_error );
{
const auto rst_value = RestartIO::load( "FILE.UNRST" , 1 , {{"SWAT" , RestartKey(UnitSystem::measure::identity)},
{"NO" , {UnitSystem::measure::identity, false}}},
setup.es, setup.grid , setup.schedule, {{"EXTRA", true}, {"EXTRA2", false}});
const auto pair = rst_value.extra.find( "EXTRA" );
const std::vector<double> extraval = pair->second;
const std::vector<double> expected = {0,1,2,3};
BOOST_CHECK_EQUAL( rst_value.solution.has("SWAT") , true );
BOOST_CHECK_EQUAL( rst_value.solution.has("NO") , false );
BOOST_CHECK_EQUAL( rst_value.extra.size() , 1 );
BOOST_CHECK_EQUAL( extraval.size() , 4 );
BOOST_CHECK_EQUAL_COLLECTIONS( extraval.begin(), extraval.end(), expected.begin() , expected.end());
const auto missing = rst_value.extra.find( "EXTRA2");
BOOST_CHECK( missing == rst_value.extra.end() );
}
}
}
}
}

97
tests/test_Solution.cpp Normal file
View File

@ -0,0 +1,97 @@
/*
Copyright 2016 Statoil ASA
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"
#define BOOST_TEST_MODULE Solution
#include <boost/test/unit_test.hpp>
#include <vector>
#include <opm/output/data/Solution.hpp>
#include <opm/parser/eclipse/Units/UnitSystem.hpp>
using namespace Opm;
BOOST_AUTO_TEST_CASE(Create)
{
data::Solution c;
BOOST_CHECK_EQUAL( c.size() , 0U );
BOOST_CHECK_EQUAL( c.has("NO") , false);
{
std::vector<double> data(100);
c.insert("NAME", UnitSystem::measure::identity, data , data::TargetType::RESTART_SOLUTION);
BOOST_CHECK_EQUAL( c.size() , 1U );
BOOST_CHECK_EQUAL( c.has("NAME") , true);
BOOST_CHECK_EQUAL( c.find("NAME")->first, "NAME");
BOOST_CHECK_THROW( c.at("NotLikeThis") , std::out_of_range );
c.insert( "NAME2" , UnitSystem::measure::identity, data , data::TargetType::RESTART_SOLUTION );
c.insert( "NAME3" , UnitSystem::measure::identity, data , data::TargetType::RESTART_SOLUTION );
BOOST_CHECK_EQUAL( c.size() , 3U );
std::vector<std::string> expected = {"NAME" , "NAME2" , "NAME3"};
std::vector<std::string> actual;
for (const auto& prop : c)
actual.push_back( prop.first );
BOOST_CHECK_EQUAL_COLLECTIONS( expected.begin() , expected.end() , actual.begin() , actual.end() );
}
}
BOOST_AUTO_TEST_CASE(Create2)
{
std::vector<double> data(100);
data::Solution c = {
{ "TRANX", { UnitSystem::measure::transmissibility, data, data::TargetType::RESTART_SOLUTION } },
{ "TRANY", { UnitSystem::measure::transmissibility, data, data::TargetType::RESTART_SOLUTION } },
{ "TRANZ", { UnitSystem::measure::transmissibility, data, data::TargetType::RESTART_SOLUTION } }
};
auto c2 = c;
BOOST_CHECK_EQUAL( c2.size() , 3U );
BOOST_CHECK( c2.has("TRANX") );
}
BOOST_AUTO_TEST_CASE(UNITS) {
std::vector<double> data(100,1);
data::Solution c;
auto metric = UnitSystem::newMETRIC();
c.insert("NAME", UnitSystem::measure::pressure, data , data::TargetType::RESTART_SOLUTION);
double si0 = c.data("NAME")[0];
c.convertFromSI( metric );
double metric0 = c.data("NAME")[0];
c.convertFromSI( metric );
BOOST_CHECK_EQUAL( metric0 , c.data("NAME")[0] );
c.convertToSI( metric );
BOOST_CHECK_EQUAL( si0 , c.data("NAME")[0] );
}

1224
tests/test_Summary.cpp Normal file

File diff suppressed because it is too large Load Diff

1607
tests/test_Tables.cpp Normal file

File diff suppressed because it is too large Load Diff

125
tests/test_Wells.cpp Normal file
View File

@ -0,0 +1,125 @@
/*
Copyright 2016 Statoil ASA.
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"
#define BOOST_TEST_MODULE Wells
#include <boost/test/unit_test.hpp>
#include <stdexcept>
#include <opm/output/data/Wells.hpp>
using namespace Opm;
using rt = data::Rates::opt;
BOOST_AUTO_TEST_CASE(has) {
data::Rates rates;
rates.set( rt::wat, 10 );
BOOST_CHECK( rates.has( rt::wat ) );
BOOST_CHECK( !rates.has( rt::gas ) );
BOOST_CHECK( !rates.has( rt::oil ) );
rates.set( rt::gas, 0 );
BOOST_CHECK( rates.has( rt::gas ) );
BOOST_CHECK( !rates.has( rt::oil ) );
}
BOOST_AUTO_TEST_CASE(set_and_get) {
data::Rates rates;
const double wat = 10;
const double gas = 10;
rates.set( rt::wat, wat );
rates.set( rt::gas, gas );
BOOST_CHECK_EQUAL( wat, rates.get( rt::wat ) );
BOOST_CHECK_EQUAL( gas, rates.get( rt::gas ) );
}
BOOST_AUTO_TEST_CASE(get_wrong) {
data::Rates rates;
const double wat = 10;
const double gas = 10;
rates.set( rt::wat, wat );
rates.set( rt::gas, gas );
const double def = 1;
BOOST_CHECK_EQUAL( def, rates.get( rt::oil, def ) );
BOOST_CHECK_THROW( rates.get( rt::oil ), std::invalid_argument );
}
BOOST_AUTO_TEST_CASE(get_completions) {
data::Rates r1, r2, rc1, rc2, rc3;
r1.set( data::Rates::opt::wat, 5.67 );
r1.set( data::Rates::opt::oil, 6.78 );
r1.set( data::Rates::opt::gas, 7.89 );
r2.set( data::Rates::opt::wat, 8.90 );
r2.set( data::Rates::opt::oil, 9.01 );
r2.set( data::Rates::opt::gas, 10.12 );
rc1.set( data::Rates::opt::wat, 20.41 );
rc1.set( data::Rates::opt::oil, 21.19 );
rc1.set( data::Rates::opt::gas, 22.41 );
rc2.set( data::Rates::opt::wat, 23.19 );
rc2.set( data::Rates::opt::oil, 24.41 );
rc2.set( data::Rates::opt::gas, 25.19 );
rc3.set( data::Rates::opt::wat, 26.41 );
rc3.set( data::Rates::opt::oil, 27.19 );
rc3.set( data::Rates::opt::gas, 28.41 );
data::Well w1, w2;
w1.rates = r1;
w1.bhp = 1.23;
w1.temperature = 3.45;
w1.control = 1;
/*
* the completion keys (active indices) and well names correspond to the
* input deck. All other entries in the well structures are arbitrary.
*/
w1.completions.push_back( { 88, rc1, 30.45, 123.45 } );
w1.completions.push_back( { 288, rc2, 33.19, 67.89 } );
w2.rates = r2;
w2.bhp = 2.34;
w2.temperature = 4.56;
w2.control = 2;
w2.completions.push_back( { 188, rc3, 36.22, 19.28 } );
data::Wells wellRates;
wellRates["OP_1"] = w1;
wellRates["OP_2"] = w2;
BOOST_CHECK_EQUAL( 0.0, wellRates.get("NO_SUCH_WELL" , data::Rates::opt::wat) );
BOOST_CHECK_EQUAL( 5.67 , wellRates.get( "OP_1" , data::Rates::opt::wat));
BOOST_CHECK_EQUAL( 0.0, wellRates.get("OP_2" , 10000 , data::Rates::opt::wat) );
BOOST_CHECK_EQUAL( 26.41 , wellRates.get( "OP_2" , 188 , data::Rates::opt::wat));
}

View File

@ -0,0 +1,95 @@
/*
+ Copyright 2016 Statoil ASA.
+
+ 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"
#include <opm/test_util/summaryComparator.hpp>
#include <opm/test_util/summaryIntegrationTest.hpp>
#define BOOST_TEST_MODULE CalculationTest
#include <boost/test/unit_test.hpp>
BOOST_AUTO_TEST_CASE(deviation){
double a = 5;
double b = 10;
const double tol = 1.0e-14;
Deviation dev = SummaryComparator::calculateDeviations(a,b);
BOOST_CHECK_EQUAL(dev.abs, 5);
BOOST_CHECK_CLOSE(dev.rel, 5.0/10.0, tol);
}
BOOST_AUTO_TEST_CASE(area) {
double width1 = 0;
double width2 = 2;
double width3 = 10;
double area1 = IntegrationTest::getRectangleArea(1,width2);
double area2 = IntegrationTest::getRectangleArea(10,width1);
double area3 = IntegrationTest::getRectangleArea(4,width3);
BOOST_CHECK_EQUAL(area1,2);
BOOST_CHECK_EQUAL(area2,0);
BOOST_CHECK_EQUAL(area3,40);
}
BOOST_AUTO_TEST_CASE(operatorOverload) {
WellProductionVolume volumeA;
WellProductionVolume volumeB;
volumeA.total = 2;
volumeA.error = 2;
volumeB.total = 3;
volumeB.error = 1;
volumeA += volumeB;
BOOST_CHECK_EQUAL(volumeA.total,5);
BOOST_CHECK_EQUAL(volumeA.error,3);
}
BOOST_AUTO_TEST_CASE(integration) {
std::vector<double> data1 = {2,2,2,2,2,2};
std::vector<double> time1 = {0,1,2,3,4,5};
double val1 = IntegrationTest::integrate(time1, data1);
std::vector<double> data2 = {3, 3, 4, 3, 2, 1, 0, 4};
std::vector<double> time2 = {0, 0.5, 1, 2, 3, 3.5, 4.5, 5};
std::vector<double> data3 = {0, 0, 0, 0, 0, 0, 0, 0};
std::vector<double> data4 = {0, 1, 4, 1, 2, 4, 0, 2, 5, 6, 3};
std::vector<double> time4 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10};
std::vector<double> data5 = {0, 4, 3, 2, 5, 6, 3, 1, 0, 4, 2, 1};
std::vector<double> time5 = {0, 0.5, 1, 2.5, 3, 4, 4.5, 6, 7.5,8, 9.5 ,10};
double val2 = IntegrationTest::integrateError(time1, data1, time2, data2);
double val3 = IntegrationTest::integrateError(time1, data1, time2, data3);
double val4 = IntegrationTest::integrateError(time1, data1, time1, data1);
double val5 = IntegrationTest::integrateError(time4, data4, time5, data5);
BOOST_CHECK_EQUAL(val1,10);
BOOST_CHECK_EQUAL(val2,6);
BOOST_CHECK_EQUAL(val3,10);
BOOST_CHECK_EQUAL(val4,0);
BOOST_CHECK_EQUAL(val5,24.5);
}

View File

@ -0,0 +1,64 @@
/*
Copyright 2016 Statoil ASA.
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"
#define BOOST_TEST_MODULE RegionCache
#include <boost/test/unit_test.hpp>
#include <stdexcept>
#include <opm/parser/eclipse/Deck/Deck.hpp>
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
#include <opm/output/eclipse/RegionCache.hpp>
#include <opm/parser/eclipse/Parser/ParseContext.hpp>
#include <opm/parser/eclipse/Parser/Parser.hpp>
#include <opm/output/eclipse/RegionCache.hpp>
using namespace Opm;
const char* path = "summary_deck.DATA";
BOOST_AUTO_TEST_CASE(create) {
ParseContext parseContext;
Parser parser;
Deck deck( parser.parseFile( path, parseContext ));
EclipseState es(deck , parseContext );
const EclipseGrid& grid = es.getInputGrid();
Schedule schedule( deck, grid, es.get3DProperties(), es.runspec().phases(), ParseContext() );
out::RegionCache rc(es.get3DProperties() , grid, schedule);
{
const auto& empty = rc.completions( 4 );
BOOST_CHECK_EQUAL( empty.size() , 0 );
}
{
const auto& top_layer = rc.completions( 1 );
BOOST_CHECK_EQUAL( top_layer.size() , 3 );
{
auto pair = top_layer[0];
BOOST_CHECK_EQUAL( pair.first , "W_1");
BOOST_CHECK_EQUAL( pair.second , grid.activeIndex( 0,0,0));
}
}
}

View File

@ -0,0 +1,168 @@
/*
Copyright 2014 Statoil IT
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"
#define BOOST_TEST_MODULE EclipseWriter
#include <opm/common/utility/platform_dependent/disable_warnings.h>
#include <boost/test/unit_test.hpp>
#include <opm/common/utility/platform_dependent/reenable_warnings.h>
#include <opm/output/eclipse/EclipseIO.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/EclipseGrid.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/CompletionSet.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/TimeMap.hpp>
#include <opm/parser/eclipse/EclipseState/SummaryConfig/SummaryConfig.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Well.hpp>
#include <opm/parser/eclipse/Parser/ParseContext.hpp>
#include <opm/parser/eclipse/Parser/Parser.hpp>
// ERT stuff
#include <ert/ecl/ecl_kw.h>
#include <ert/ecl/ecl_file.h>
#include <ert/ecl/ecl_kw_magic.h>
#include <ert/ecl_well/well_info.h>
#include <ert/ecl_well/well_state.h>
#include <ert/util/test_work_area.h>
using namespace Opm;
void verifyWellState(const std::string& rst_filename,
const EclipseGrid& ecl_grid,
const Schedule& schedule) {
well_info_type* well_info = well_info_alloc(ecl_grid.c_ptr());
well_info_load_rstfile(well_info, rst_filename.c_str(), false);
//Verify numwells
int numwells = well_info_get_num_wells(well_info);
BOOST_CHECK_EQUAL( numwells, schedule.numWells() );
auto wells = schedule.getWells();
for (int i = 0; i < numwells; ++i) {
//Verify wellnames
const char * wellname = well_info_iget_well_name(well_info, i);
auto* well = wells.at(i);
BOOST_CHECK_EQUAL( wellname, well->name() );
// Verify well-head position data
well_ts_type* well_ts = well_info_get_ts(well_info , wellname);
well_state_type* well_state = well_ts_iget_state(well_ts, 0);
const well_conn_type* well_head = well_state_get_wellhead(well_state, ECL_GRID_GLOBAL_GRID);
BOOST_CHECK_EQUAL(well_conn_get_i(well_head), well->getHeadI());
BOOST_CHECK_EQUAL(well_conn_get_j(well_head), well->getHeadJ());
for (int j = 0; j < well_ts_get_size(well_ts); ++j) {
well_state = well_ts_iget_state(well_ts, j);
//Verify welltype
int well_type = ERT_UNDOCUMENTED_ZERO;
if( well->isProducer( j ) ) {
well_type = ERT_PRODUCER;
}
else {
switch( well->getInjectionProperties( j ).injectorType ) {
case WellInjector::WATER:
well_type = ERT_WATER_INJECTOR;
break;
case WellInjector::GAS:
well_type = ERT_GAS_INJECTOR;
break;
case WellInjector::OIL:
well_type = ERT_OIL_INJECTOR;
break;
default:
break;
}
}
int ert_well_type = well_state_get_type( well_state );
BOOST_CHECK_EQUAL( ert_well_type, well_type );
//Verify wellstatus
int ert_well_status = well_state_is_open(well_state) ? 1 : 0;
int wellstatus = well->getStatus( j ) == WellCommon::OPEN ? 1 : 0;
BOOST_CHECK_EQUAL(ert_well_status, wellstatus);
//Verify number of completion connections
const well_conn_collection_type * well_connections = well_state_get_global_connections( well_state );
size_t num_wellconnections = well_conn_collection_get_size(well_connections);
int report_nr = well_state_get_report_nr(well_state);
const auto& completions_set = well->getCompletions((size_t)report_nr);
BOOST_CHECK_EQUAL(num_wellconnections, completions_set.size());
//Verify coordinates for each completion connection
for (size_t k = 0; k < num_wellconnections; ++k) {
const well_conn_type * well_connection = well_conn_collection_iget_const(well_connections , k);
const auto& completion = completions_set.get(k);
BOOST_CHECK_EQUAL(well_conn_get_i(well_connection), completion.getI());
BOOST_CHECK_EQUAL(well_conn_get_j(well_connection), completion.getJ());
BOOST_CHECK_EQUAL(well_conn_get_k(well_connection), completion.getK());
}
}
}
well_info_free(well_info);
}
BOOST_AUTO_TEST_CASE(EclipseWriteRestartWellInfo) {
std::string eclipse_data_filename = "testblackoilstate3.DATA";
std::string eclipse_restart_filename = "TESTBLACKOILSTATE3.X0004";
test_work_area_type * test_area = test_work_area_alloc("TEST_EclipseWriteNumWells");
test_work_area_copy_file(test_area, eclipse_data_filename.c_str());
ParseContext parseContext;
Parser parser;
Deck deck( parser.parseFile( eclipse_data_filename, parseContext ));
EclipseState es(deck , parseContext );
const EclipseGrid& grid = es.getInputGrid();
Schedule schedule( deck, grid, es.get3DProperties(), es.runspec().phases(), parseContext);
SummaryConfig summary_config( deck, schedule, es.getTableManager( ), parseContext);
const auto num_cells = grid.getCartesianSize();
EclipseIO eclipseWriter( es, grid , schedule, summary_config);
int countTimeStep = schedule.getTimeMap().numTimesteps();
data::Solution solution;
solution.insert( "PRESSURE",UnitSystem::measure::pressure , std::vector< double >( num_cells, 1 ) , data::TargetType::RESTART_SOLUTION);
solution.insert( "SWAT" ,UnitSystem::measure::identity , std::vector< double >( num_cells, 1 ) , data::TargetType::RESTART_SOLUTION);
solution.insert( "SGAS" ,UnitSystem::measure::identity , std::vector< double >( num_cells, 1 ) , data::TargetType::RESTART_SOLUTION);
data::Wells wells;
for(int timestep = 0; timestep <= countTimeStep; ++timestep){
eclipseWriter.writeTimeStep( timestep,
false,
timestep,
solution,
wells ,
{},
{},
{} );
}
verifyWellState(eclipse_restart_filename, grid, schedule);
test_work_area_free(test_area);
}

View File

@ -0,0 +1,118 @@
RUNSPEC
TITLE
UNIT TEST
START
1 NOV 1979 /
OIL
GAS
WATER
METRIC
DIMENS
10 10 10 /
GRID
DXV
10*10 /
DYV
10*10 /
DZV
10*10 /
DEPTHZ
121*1000 /
PORO
1000*0.2 /
SCHEDULE
RPTSCHED
RESTART=2/
DATES
1 DES 1979/
/
WELSPECS
'OP_1' 'OP' 9 9 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* /
'OP_2' 'OP' 8 8 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* /
'OP_3' 'OP' 7 7 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* /
/
COMPDAT
'OP_1' 9 9 1 1 'OPEN' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 /
'OP_1' 9 9 2 2 'OPEN' 1* 46.825 0.311 4332.346 1* 1* 'X' 22.123 /
'OP_1' 9 9 3 3 'OPEN' 1* 51.867 0.311 4799.764 1* 1* 'X' 22.143 /
'OP_1' 9 9 4 4 'OPEN' 1* 34.243 0.311 3169.482 1* 1* 'X' 22.166 /
'OP_1' 9 9 5 5 'OPEN' 1* 36.435 0.311 3375.309 1* 1* 'X' 22.262 /
'OP_1' 9 9 6 6 'OPEN' 1* 39.630 0.311 3672.067 1* 1* 'X' 22.283 /
'OP_1' 9 9 7 7 'OPEN' 1* 33.975 0.311 3148.671 1* 1* 'X' 22.307 /
'OP_1' 9 9 8 8 'OPEN' 1* 24.869 0.311 2305.242 1* 1* 'X' 22.329 /
'OP_1' 9 9 9 9 'OPEN' 1* 38.301 0.311 3551.043 1* 1* 'X' 22.351 /
'OP_1' 9 9 10 10 'OPEN' 1* 6.642 0.311 615.914 1* 1* 'X' 22.372 /
'OP_2' 8 8 1 3 'OPEN' 1* 1.168 0.311 107.872 1* 1* 'Y' 21.925 /
'OP_2' 8 7 3 3 'OPEN' 1* 15.071 0.311 1391.859 1* 1* 'Y' 21.920 /
'OP_2' 8 7 3 6 'OPEN' 1* 6.242 0.311 576.458 1* 1* 'Y' 21.915 /
'OP_3' 7 7 1 1 'OPEN' 1* 27.412 0.311 2445.337 1* 1* 'Y' 18.521 /
'OP_3' 7 7 2 2 'OPEN' 1* 55.195 0.311 4923.842 1* 1* 'Y' 18.524 /
'OP_3' 7 7 3 3 'OPEN' 1* 18.032 0.311 1608.615 1* 1* 'Y' 18.526 /
'OP_3' 7 7 4 4 'OPEN' 1* 56.817 0.311 5047.177 1* 1* 'Y' 18.155 /
'OP_3' 7 7 5 5 'OPEN' 1* 4.728 0.311 420.067 1* 1* 'Y' 18.162 /
/
DATES
1 JUN 1980/
/
WELSPECS
'OP_4' 'OP' 2 2 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* /
'OP_5' 'OP' 5 4 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* /
'OP_6' 'OP' 8 2 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* /
/
COMPDAT
'OP_4' 2 2 1 10 'OPEN' 1* 6.642 0.311 615.914 1* 1* 'X' 22.372 /
'OP_5' 5 4 1 3 'OPEN' 1* 1.168 0.311 107.872 1* 1* 'Y' 21.925 /
'OP_6' 8 2 1 3 'OPEN' 1* 27.412 0.311 2445.337 1* 1* 'Y' 18.521 /
'OP_6' 8 3 3 3 'OPEN' 1* 55.195 0.311 4923.842 1* 1* 'Y' 18.524 /
'OP_6' 8 4 3 3 'OPEN' 1* 18.032 0.311 1608.615 1* 1* 'Y' 18.526 /
'OP_6' 8 5 3 5 'OPEN' 1* 56.817 0.311 5047.177 1* 1* 'Y' 18.155 /
'OP_6' 8 5 5 6 'OPEN' 1* 4.728 0.311 420.067 1* 1* 'Y' 18.162 /
/
DATES
1 NOV 1980/
/
WELSPECS
'WI_1' 'WI' 3 3 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* /
'WI_2' 'WI' 3 9 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* /
'WI_3' 'WI' 3 6 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* /
/
COMPDAT
'WI_1' 3 3 1 10 'OPEN' 1* 6.642 0.311 615.914 1* 1* 'X' 22.372 /
'WI_2' 3 9 1 7 'OPEN' 1* 1.168 0.311 107.872 1* 1* 'Y' 21.925 /
'WI_3' 3 6 1 3 'OPEN' 1* 27.412 0.311 2445.337 1* 1* 'Y' 18.521 /
/
DATES
1 NOV 1982/
/
END

62
tests/testrft.DATA Normal file
View File

@ -0,0 +1,62 @@
RUNSPEC
OIL
GAS
WATER
DIMENS
10 10 10 /
GRID
DXV
10*0.25 /
DYV
10*0.25 /
DZV
10*0.25 /
TOPS
100*0.25 /
PORO
1000*0.2 /
START -- 0
1 NOV 1979 /
SCHEDULE
DATES -- 1
1 DES 1979/
/
WELSPECS
'OP_1' 'OP' 9 9 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* /
'OP_2' 'OP' 4 4 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* /
/
COMPDAT
'OP_1' 9 9 1 1 'OPEN' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 /
'OP_1' 9 9 2 2 'OPEN' 1* 46.825 0.311 4332.346 1* 1* 'X' 22.123 /
'OP_1' 9 9 3 9 'OPEN' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 /
'OP_2' 4 4 4 9 'OPEN' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 /
/
DATES -- 2
10 OKT 2008 /
/
WRFTPLT
'OP_1' 'YES' /
'OP_2' 'REPT' /
/
WELOPEN
'OP_1' OPEN /
'OP_2' OPEN /
/
DATES -- 3
10 NOV 2008 /
/
DATES -- 4
20 NOV 2008 /
/
WRFTPLT
'OP_2' 'NO' /
/
DATES -- 5
30 NOV 2008 /
/