diff --git a/.gitignore b/.gitignore index d124eee7..12188671 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *~ .\#* *.o +*.mod *.lo *.la .libs @@ -29,19 +30,30 @@ ltmain.sh m4/libtool.m4 m4/lt*.m4 missing +ar-lib tutorials/tutorial[1-4] +opmcore-config.cmake + +# in-tree build with CMake +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake # Ignoring executables *_test +examples/compute_tof examples/scaneclipsedeck examples/spu_2p examples/reorder-qfs examples/refine_wells examples/sim_2p_incomp_reorder +examples/sim_2p_comp_reorder examples/sim_wateroil examples/wells_example +tests/test_agmg tests/test_cfs_tpfa tests/test_jacsys +tests/test_read_grid tests/test_readvector tests/test_sf2p tests/bo_fluid_p_and_z_deps @@ -51,4 +63,6 @@ tests/test_column_extract tests/test_lapack tests/test_read_vag tests/test_readpolymer +tests/test_velocityinterpolation +tests/test_wells tests/test_writeVtkData diff --git a/Makefile.am b/Makefile.am index a477f733..5a23deac 100644 --- a/Makefile.am +++ b/Makefile.am @@ -12,7 +12,9 @@ lib_LTLIBRARIES = lib/libopmcore.la # Build-time flags needed to build libopmcore.la AM_CPPFLAGS = \ -$(OPM_BOOST_CPPFLAGS) +$(ERT_CPPFLAGS) \ +$(OPM_BOOST_CPPFLAGS) \ +$(SUPERLU_CPPFLAGS) # ---------------------------------------------------------------------- # Link-time flags needed both to successfully link the library and to @@ -20,14 +22,17 @@ $(OPM_BOOST_CPPFLAGS) lib_libopmcore_la_LDFLAGS = \ -R $(OPM_BOOST_LIBDIR) \ -$(OPM_BOOST_LDFLAGS) +$(OPM_BOOST_LDFLAGS) \ +$(ERT_LDFLAGS) \ +$(SUPERLU_LDFLAGS) lib_libopmcore_la_LIBADD = \ $(BOOST_FILESYSTEM_LIB) \ $(BOOST_SYSTEM_LIB) \ $(BOOST_DATE_TIME_LIB) \ $(BOOST_UNIT_TEST_FRAMEWORK_LIB) \ -$(LAPACK_LIBS) $(BLAS_LIBS) $(LIBS) +$(ERT_LIBS) \ +$(LAPACK_LIBS) $(SUPERLU_LIBS) $(BLAS_LIBS) $(LIBS) # ---------------------------------------------------------------------- # Library constituents. SOURCES followed by HEADERS. @@ -53,10 +58,12 @@ opm/core/fluid/RockCompressibility.cpp \ opm/core/fluid/RockFromDeck.cpp \ opm/core/fluid/SaturationPropsBasic.cpp \ opm/core/fluid/SaturationPropsFromDeck.cpp \ +opm/core/fluid/SatFuncGwseg.cpp \ opm/core/fluid/SatFuncStone2.cpp \ opm/core/fluid/SatFuncSimple.cpp \ opm/core/fluid/blackoil/BlackoilPvtProperties.cpp \ opm/core/fluid/blackoil/SinglePvtDead.cpp \ +opm/core/fluid/blackoil/SinglePvtDeadSpline.cpp \ opm/core/fluid/blackoil/SinglePvtInterface.cpp \ opm/core/fluid/blackoil/SinglePvtLiveGas.cpp \ opm/core/fluid/blackoil/SinglePvtLiveOil.cpp \ @@ -103,6 +110,8 @@ opm/core/simulator/SimulatorReport.cpp \ opm/core/simulator/SimulatorTimer.cpp \ opm/core/transport/reorder/TransportModelCompressibleTwophase.cpp \ opm/core/transport/reorder/TransportModelInterface.cpp \ +opm/core/transport/reorder/TransportModelTracerTof.cpp \ +opm/core/transport/reorder/TransportModelTracerTofDiscGal.cpp \ opm/core/transport/reorder/TransportModelTwophase.cpp \ opm/core/transport/reorder/nlsolvers.c \ opm/core/transport/reorder/reordersequence.cpp \ @@ -112,6 +121,8 @@ opm/core/transport/spu_implicit.c \ opm/core/transport/transport_source.c \ opm/core/utility/MonotCubicInterpolator.cpp \ opm/core/utility/StopWatch.cpp \ +opm/core/utility/VelocityInterpolation.cpp \ +opm/core/utility/WachspressCoord.cpp \ opm/core/utility/miscUtilities.cpp \ opm/core/utility/miscUtilitiesBlackoil.cpp \ opm/core/utility/parameters/Parameter.cpp \ @@ -150,15 +161,19 @@ opm/core/fluid/PvtPropertiesIncompFromDeck.hpp \ opm/core/fluid/RockBasic.hpp \ opm/core/fluid/RockCompressibility.hpp \ opm/core/fluid/RockFromDeck.hpp \ -opm/core/fluid/SaturationPropsBasic.hpp \ -opm/core/fluid/SaturationPropsFromDeck.hpp \ +opm/core/fluid/SatFuncGwseg.hpp \ opm/core/fluid/SatFuncStone2.hpp \ opm/core/fluid/SatFuncSimple.hpp \ +opm/core/fluid/SaturationPropsBasic.hpp \ +opm/core/fluid/SaturationPropsFromDeck.hpp \ +opm/core/fluid/SaturationPropsFromDeck_impl.hpp \ +opm/core/fluid/SaturationPropsInterface.hpp \ opm/core/fluid/SimpleFluid2p.hpp \ opm/core/fluid/blackoil/BlackoilPhases.hpp \ opm/core/fluid/blackoil/BlackoilPvtProperties.hpp \ opm/core/fluid/blackoil/SinglePvtConstCompr.hpp \ opm/core/fluid/blackoil/SinglePvtDead.hpp \ +opm/core/fluid/blackoil/SinglePvtDeadSpline.hpp \ opm/core/fluid/blackoil/SinglePvtInterface.hpp \ opm/core/fluid/blackoil/SinglePvtLiveGas.hpp \ opm/core/fluid/blackoil/SinglePvtLiveOil.hpp \ @@ -221,6 +236,8 @@ opm/core/transport/SimpleFluid2pWrapper.hpp \ opm/core/transport/SinglePointUpwindTwoPhase.hpp \ opm/core/transport/reorder/TransportModelCompressibleTwophase.hpp \ opm/core/transport/reorder/TransportModelInterface.hpp \ +opm/core/transport/reorder/TransportModelTracerTof.hpp \ +opm/core/transport/reorder/TransportModelTracerTofDiscGal.hpp \ opm/core/transport/reorder/TransportModelTwophase.hpp \ opm/core/transport/reorder/nlsolvers.h \ opm/core/transport/reorder/reordersequence.h \ @@ -230,15 +247,19 @@ opm/core/transport/spu_implicit.h \ opm/core/transport/transport_source.h \ opm/core/utility/Average.hpp \ opm/core/utility/ColumnExtract.hpp \ +opm/core/utility/DataMap.hpp \ opm/core/utility/ErrorMacros.hpp \ opm/core/utility/Factory.hpp \ opm/core/utility/MonotCubicInterpolator.hpp \ +opm/core/utility/NonuniformTableLinear.hpp \ opm/core/utility/RootFinders.hpp \ opm/core/utility/SparseTable.hpp \ opm/core/utility/SparseVector.hpp \ opm/core/utility/StopWatch.hpp \ opm/core/utility/UniformTableLinear.hpp \ opm/core/utility/Units.hpp \ +opm/core/utility/VelocityInterpolation.hpp \ +opm/core/utility/WachspressCoord.hpp \ opm/core/utility/buildUniformMonotoneTable.hpp \ opm/core/utility/initState.hpp \ opm/core/utility/initState_impl.hpp \ @@ -279,6 +300,15 @@ opm/core/linalg/LinearSolverUmfpack.hpp endif +if HAVE_ERT +lib_libopmcore_la_SOURCES += \ +opm/core/utility/writeECLData.cpp + +nobase_include_HEADERS += \ +opm/core/utility/writeECLData.hpp +endif + + if DUNE_ISTL lib_libopmcore_la_SOURCES += \ opm/core/linalg/LinearSolverIstl.cpp @@ -302,3 +332,6 @@ opm/core/linalg/LinearSolverAGMG.hpp lib_libopmcore_la_LDFLAGS += \ $(FCLIBS) endif + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = lib/pkgconfig/opm-core.pc diff --git a/README b/README index 8c7c522f..72427507 100644 --- a/README +++ b/README @@ -46,46 +46,39 @@ sudo apt-get install -y build-essential gfortran pkg-config libtool \ sudo apt-get install -y doxygen ghostscript texlive-latex-recommended pgf # packages necessary for version control -sudo apt-get install -y git-core git-svn subversion +sudo apt-get install -y git-core -# libraries necessary for DUNE +# basic libraries necessary for both DUNE and OPM sudo apt-get install -y libboost-all-dev libsuperlu3-dev libsuitesparse-dev -# libraries necessary for OPM -sudo apt-get install -y libxml0-dev +# for server edition of Ubuntu add-apt-repository depends on +sudo apt-get install python-software-properties +# add this repository for necessary backports (required for Ubuntu Precise) +sudo add-apt-repository -y ppa:opm/ppa +sudo apt-get update + +# parts of DUNE needed +sudo apt-get install libdune-common-dev libdune-istl-dev libdune-grid-dev + +# libraries necessary for OPM +sudo apt-get install -y libxml2-dev + +Note: You should compile the OPM modules using the same toolchain that + was used to build DUNE. Otherwise, you can get strange ABI errors. DEPENDENCIES FOR SUSE BASED DISTRIBUTIONS ----------------------------------------- # libraries -sudo zypper install blas libblas3 lapack liblapack3 libboost libxml2 umfpack +sudo zypper in blas libblas3 lapack liblapack3 libboost libxml2 umfpack # tools -sudo zypper install gcc automake autoconf git doxygen +sudo zypper in gcc automake autoconf git doxygen - -RETRIEVING AND BUILDING DUNE PREREQUISITES ------------------------------------------- - -(only necessary if you want to use opm-core as a dune module) - -# trust DUNE certificate (sic) -echo p | svn list https://svn.dune-project.org/svn/dune-common - -# checkout DUNE libraries -for module in common istl geometry grid localfunctions; do - git svn clone -s \ - https://svn.dune-project.org/svn/dune-$module/branches/release-2.2/ \ - dune-$module -done - -# building DUNE libraries -for module in common istl geometry grid localfunctions; do - env CCACHE_DISABLE=1 dune-common/bin/dunecontrol --only=dune-$module \ - --configure-opts="--enable-fieldvector-size-is-method" \ - --make-opts="-j -l 0.8" autogen : configure : make -done +# DUNE libraries +sudo zypper ar http://download.opensuse.org/repositories/science/openSUSE_12.2/science.repo +sudo zypper in dune-common dune-istl DOWNLOADING @@ -100,20 +93,26 @@ If you want to contribute, fork OPM/opm-core on github. BUILDING -------- -(standalone opm-core:) +There are two ways to build the opm-core library: - cd ../opm-core +1. As a stand-alone library. + cd opm-core autoreconf -i ./configure - make + make -j -l 0.9 +If you want to install the library: + make install +or (if installing to /usr/local or similar) sudo make install -(using opm-core as a dune module:) - - # note: this is done from the parent directory of opm-core - env CCACHE_DISABLE=1 dune-common/bin/dunecontrol --only=opm-core \ - --configure-opts="" --make-opts="-j -l 0.8" autogen : configure : make - +2. As a dune module. + - Put the opm-core directory in the same directory + as the other dune modules to be built (e.g. dune-commmon, + dune-grid). Note that for Ubuntu you can install Dune + from the ppa as outlined above. + - Run dunecontrol as normal. For more information on + the dune build system, see + http://www.dune-project.org/doc/installation-notes.html DOCUMENTATION diff --git a/configure.ac b/configure.ac index cc4b92e1..856a9920 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.59]) -AC_INIT([OPM Core Library], [0.1], [atgeirr@sintef.no],dnl +AC_INIT([OPM Core Library], [0.1], [atgeirr@sintef.no], [opmcore], [https://public.ict.sintef.no/opm/hg/opmcore]) AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects]) @@ -20,8 +20,11 @@ AC_CONFIG_HEADERS([config.h]) AC_PROG_CC AM_PROG_CC_C_O +dnl Initialize libtool; the funny indentation here is to +dnl satisfy libtoolize' check for the presence of this macro m4_ifdef([LT_INIT], - [LT_INIT[]dnl + [ + LT_INIT[]dnl LT_LANG([C++])dnl LT_LANG([Fortran 77])dnl LT_LANG([Fortran])dnl @@ -36,6 +39,8 @@ OPM_CORE_CHECKS OPM_DYNLINK_BOOST_TEST +ERT + dnl Substitute Autoconf's abs_*dir variables into the Makefiles for the dnl benefit of external code that uses these variables to derive dnl locations (e.g., Dune's DUNE_CHECK_MODULES macro). Automakes prior @@ -51,6 +56,9 @@ AC_CONFIG_FILES([ tests/Makefile examples/Makefile tutorials/Makefile + opm-core.pc + lib/pkgconfig/opm-core.pc + opmcore-config.cmake ]) AC_OUTPUT diff --git a/examples/Makefile.am b/examples/Makefile.am index 38d41e22..043a44c3 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -1,24 +1,48 @@ # Build-time flags needed to form example programs -AM_CPPFLAGS = \ --I$(top_srcdir) \ +ERT_INCLUDE_PATH = $(ERT_ROOT)/include + +AM_CPPFLAGS = \ +-I$(top_srcdir) \ +-I$(ERT_INCLUDE_PATH) \ $(OPM_BOOST_CPPFLAGS) # All targets link to the library -LDADD = \ +LDADD = \ $(top_builddir)/lib/libopmcore.la +# Convenience definition for targets that use Boost.Filesystem directly. +# While libopmcore depends on (and references) Boost.Filesystem (through +# the $(BOOST_FILESYSTEM_LIB) macro) this indirect dependency is not +# sufficient to satisfy the requirements of targets that use the indirect +# libraries directly. +# +# Additional details at +# https://fedoraproject.org/wiki/UnderstandingDSOLinkChange +# +LINK_BOOST_FILESYSTEM = \ +$(OPM_BOOST_LDFLAGS) \ +$(BOOST_FILESYSTEM_LIB) \ +$(BOOST_SYSTEM_LIB) + # ---------------------------------------------------------------------- # Declare products (i.e., the example programs). # # Please keep the list sorted. -noinst_PROGRAMS = \ -refine_wells \ -scaneclipsedeck \ -sim_2p_comp_reorder \ -sim_2p_incomp_reorder \ -sim_wateroil \ -wells_example +noinst_PROGRAMS = \ +compute_tof \ +refine_wells \ +scaneclipsedeck \ +sim_2p_comp_reorder \ +sim_2p_incomp_reorder \ +sim_wateroil \ +wells_example + +if HAVE_ERT +noinst_PROGRAMS += import_rewrite +endif + + # ---------------------------------------------------------------------- # Product constituents. Must be specified for every product that's @@ -27,10 +51,25 @@ wells_example # # Please maintain sort order from "noinst_PROGRAMS". +compute_tof_SOURCES = compute_tof.cpp +compute_tof_LDADD = $(LDADD) $(LINK_BOOST_FILESYSTEM) + refine_wells_SOURCES = refine_wells.cpp + +if HAVE_ERT + import_rewrite_SOURCES = import_rewrite.cpp + import_rewrite_LDADD = $(LDADD) $(LINK_BOOST_FILESYSTEM) +endif + sim_2p_comp_reorder_SOURCES = sim_2p_comp_reorder.cpp +sim_2p_comp_reorder_LDADD = $(LDADD) $(LINK_BOOST_FILESYSTEM) + sim_2p_incomp_reorder_SOURCES = sim_2p_incomp_reorder.cpp +sim_2p_incomp_reorder_LDADD = $(LDADD) $(LINK_BOOST_FILESYSTEM) + sim_wateroil_SOURCES = sim_wateroil.cpp +sim_wateroil_LDADD = $(LDADD) $(LINK_BOOST_FILESYSTEM) + wells_example_SOURCES = wells_example.cpp # ---------------------------------------------------------------------- @@ -40,7 +79,8 @@ if UMFPACK noinst_PROGRAMS += spu_2p spu_2p_SOURCES = spu_2p.cpp -spu_2p_LDADD = \ -$(LDADD) \ +spu_2p_LDADD = \ +$(LDADD) \ +$(LINK_BOOST_FILESYSTEM) \ $(LAPACK_LIBS) $(BLAS_LIBS) $(LIBS) endif diff --git a/examples/compute_tof.cpp b/examples/compute_tof.cpp new file mode 100644 index 00000000..ce04aa59 --- /dev/null +++ b/examples/compute_tof.cpp @@ -0,0 +1,255 @@ +/* + Copyright 2012 SINTEF ICT, Applied Mathematics. + + 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 . +*/ + + + +#if HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + + +namespace +{ + void warnIfUnusedParams(const Opm::parameter::ParameterGroup& param) + { + if (param.anyUnused()) { + std::cout << "-------------------- Unused parameters: --------------------\n"; + param.displayUsage(); + std::cout << "----------------------------------------------------------------" << std::endl; + } + } +} // anon namespace + + + +// ----------------- Main program ----------------- +int +main(int argc, char** argv) +{ + using namespace Opm; + + std::cout << "\n================ Test program for incompressible tof computations ===============\n\n"; + parameter::ParameterGroup param(argc, argv, false); + std::cout << "--------------- Reading parameters ---------------" << std::endl; + + // If we have a "deck_filename", grid and props will be read from that. + bool use_deck = param.has("deck_filename"); + boost::scoped_ptr deck; + boost::scoped_ptr grid; + boost::scoped_ptr props; + boost::scoped_ptr wells; + TwophaseState state; + // bool check_well_controls = false; + // int max_well_control_iterations = 0; + double gravity[3] = { 0.0 }; + if (use_deck) { + std::string deck_filename = param.get("deck_filename"); + deck.reset(new EclipseGridParser(deck_filename)); + // Grid init + grid.reset(new GridManager(*deck)); + // Rock and fluid init + props.reset(new IncompPropertiesFromDeck(*deck, *grid->c_grid())); + // Wells init. + wells.reset(new Opm::WellsManager(*deck, *grid->c_grid(), props->permeability())); + // Gravity. + gravity[2] = deck->hasField("NOGRAV") ? 0.0 : unit::gravity; + // Init state variables (saturation and pressure). + if (param.has("init_saturation")) { + initStateBasic(*grid->c_grid(), *props, param, gravity[2], state); + } else { + initStateFromDeck(*grid->c_grid(), *props, *deck, gravity[2], state); + } + } else { + // Grid init. + const int nx = param.getDefault("nx", 100); + const int ny = param.getDefault("ny", 100); + const int nz = param.getDefault("nz", 1); + const double dx = param.getDefault("dx", 1.0); + const double dy = param.getDefault("dy", 1.0); + const double dz = param.getDefault("dz", 1.0); + grid.reset(new GridManager(nx, ny, nz, dx, dy, dz)); + // Rock and fluid init. + props.reset(new IncompPropertiesBasic(param, grid->c_grid()->dimensions, grid->c_grid()->number_of_cells)); + // Wells init. + wells.reset(new Opm::WellsManager()); + // Gravity. + gravity[2] = param.getDefault("gravity", 0.0); + // Init state variables (saturation and pressure). + initStateBasic(*grid->c_grid(), *props, param, gravity[2], state); + } + + // Warn if gravity but no density difference. + bool use_gravity = (gravity[0] != 0.0 || gravity[1] != 0.0 || gravity[2] != 0.0); + if (use_gravity) { + if (props->density()[0] == props->density()[1]) { + std::cout << "**** Warning: nonzero gravity, but zero density difference." << std::endl; + } + } + const double *grav = use_gravity ? &gravity[0] : 0; + + // Initialising src + std::vector porevol; + computePorevolume(*grid->c_grid(), props->porosity(), porevol); + int num_cells = grid->c_grid()->number_of_cells; + std::vector src(num_cells, 0.0); + if (use_deck) { + // Do nothing, wells will be the driving force, not source terms. + } else { + const double tot_porevol_init = std::accumulate(porevol.begin(), porevol.end(), 0.0); + const double default_injection = use_gravity ? 0.0 : 0.1; + const double flow_per_sec = param.getDefault("injected_porevolumes_per_day", default_injection) + *tot_porevol_init/unit::day; + src[0] = flow_per_sec; + src[num_cells - 1] = -flow_per_sec; + } + + // Boundary conditions. + FlowBCManager bcs; + if (param.getDefault("use_pside", false)) { + int pside = param.get("pside"); + double pside_pressure = param.get("pside_pressure"); + bcs.pressureSide(*grid->c_grid(), FlowBCManager::Side(pside), pside_pressure); + } + + // Linear solver. + LinearSolverFactory linsolver(param); + + // Pressure solver. + Opm::IncompTpfa psolver(*grid->c_grid(), *props, 0, linsolver, + 0.0, 0.0, 0, + grav, wells->c_wells(), src, bcs.c_bcs()); + + // Choice of tof solver. + bool use_dg = param.getDefault("use_dg", false); + int dg_degree = -1; + if (use_dg) { + dg_degree = param.getDefault("dg_degree", 0); + } + + // Write parameters used for later reference. + bool output = param.getDefault("output", true); + std::ofstream epoch_os; + std::string output_dir; + if (output) { + output_dir = + param.getDefault("output_dir", std::string("output")); + boost::filesystem::path fpath(output_dir); + try { + create_directories(fpath); + } + catch (...) { + THROW("Creating directories failed: " << fpath); + } + std::string filename = output_dir + "/epoch_timing.param"; + epoch_os.open(filename.c_str(), std::fstream::trunc | std::fstream::out); + // open file to clean it. The file is appended to in SimulatorTwophase + filename = output_dir + "/step_timing.param"; + std::fstream step_os(filename.c_str(), std::fstream::trunc | std::fstream::out); + step_os.close(); + param.writeParam(output_dir + "/simulation.param"); + } + + // Init wells. + Opm::WellState well_state; + well_state.init(wells->c_wells(), state); + + // Main solvers. + Opm::time::StopWatch pressure_timer; + double ptime = 0.0; + Opm::time::StopWatch transport_timer; + double ttime = 0.0; + Opm::time::StopWatch total_timer; + total_timer.start(); + std::cout << "\n\n================ Starting main solvers ===============" << std::endl; + + // Solve pressure. + pressure_timer.start(); + psolver.solve(1.0, state, well_state); + pressure_timer.stop(); + double pt = pressure_timer.secsSinceStart(); + std::cout << "Pressure solver took: " << pt << " seconds." << std::endl; + ptime += pt; + + // Process transport sources (to include bdy terms and well flows). + std::vector transport_src; + Opm::computeTransportSource(*grid->c_grid(), src, state.faceflux(), 1.0, + wells->c_wells(), well_state.perfRates(), transport_src); + + // Solve time-of-flight. + std::vector tof; + if (use_dg) { + bool use_cvi = param.getDefault("use_cvi", false); + Opm::TransportModelTracerTofDiscGal tofsolver(*grid->c_grid(), use_cvi); + transport_timer.start(); + tofsolver.solveTof(&state.faceflux()[0], &porevol[0], &transport_src[0], dg_degree, tof); + transport_timer.stop(); + } else { + Opm::TransportModelTracerTof tofsolver(*grid->c_grid()); + transport_timer.start(); + tofsolver.solveTof(&state.faceflux()[0], &porevol[0], &transport_src[0], tof); + transport_timer.stop(); + } + double tt = transport_timer.secsSinceStart(); + std::cout << "Transport solver took: " << tt << " seconds." << std::endl; + ttime += tt; + total_timer.stop(); + + // Output. + if (output) { + std::string tof_filename = output_dir + "/tof.txt"; + std::ofstream tof_stream(tof_filename.c_str()); + std::copy(tof.begin(), tof.end(), std::ostream_iterator(tof_stream, "\n")); + } + + std::cout << "\n\n================ End of simulation ===============\n" + << "Total time taken: " << total_timer.secsSinceStart() + << "\n Pressure time: " << ptime + << "\n Transport time: " << ttime << std::endl; +} diff --git a/examples/import_rewrite.cpp b/examples/import_rewrite.cpp new file mode 100644 index 00000000..edf708e0 --- /dev/null +++ b/examples/import_rewrite.cpp @@ -0,0 +1,252 @@ +#if HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include + +#include + +#ifdef HAVE_ERT +#include +#include +#include +#include +#include +#include +#endif + +/* + Small utility to read through an ECLIPSE input deck and replace + occurences of (large) numerical fields like COORD and ZCORN with + IMPORT statements of a binary versions of the relevant keywords. The + program will follow INCLUDE statements. + + Usage: import_rewrite eclipse_case.data +*/ + + +/* + The numerical keywords in the ECLIPSE datafile like e.g. PORO and + COORD are not annoted with type information; however when read and + written in binary form they are of type float. If the updated + datafile should be used with ECLIPSE these float values must be + exported as float; this is achieved by setting the outputFloatType + variable to ECL_FLOAT_TYPE. + + In OPM all numerical fields are treated as double, hence if the OPM + EclipseParser meets an import of a float keyword it will be + converted to double. If the output from this little utility will + only be used from OPM the output can be saved as double directly by + setting the outputFloatType to ECL_DOUBLE_TYPE. +*/ +const ecl_type_enum outputFloatType = ECL_DOUBLE_TYPE; + + +/* + Only keywords which have more >= minImportSize elements are + converted to binary form. This is to avoid convertion short keywords + like MAPAXES and TABDIMS. +*/ +const int minImportSize = 10; + + +using namespace Opm; + + +static void skipKeyword( std::ifstream& is) { + std::string keyword; + EclipseGridParser::readKeyword( is , keyword ); + while (true) { + std::ios::pos_type pos = is.tellg(); + + if (EclipseGridParser::readKeyword( is , keyword )) { + is.seekg( pos ); // Repos to start of keyword for next read. + break; + } else + is >> ignoreLine; + + if (!is.good()) { + is.clear(); + is.seekg( 0 , std::ios::end ); + break; + } + } +} + + +static void copyKeyword( std::ifstream& is , std::ofstream& os) { + std::ios::pos_type start_pos = is.tellg(); + skipKeyword( is ); + { + std::ios::pos_type end_pos = is.tellg(); + long length = end_pos - start_pos; + + { + char * buffer = new char[length]; + { + is.seekg( start_pos ); + is.read( buffer , length ); + } + os.write( buffer , length ); + delete[] buffer; + } + } +} + + + +static ecl_kw_type * loadFromcstdio( const std::string& filename , std::ios::pos_type& offset , ecl_type_enum ecl_type) { + ecl_kw_type * ecl_kw; + + FILE * cstream = util_fopen( filename.c_str() , "r"); + fseek( cstream , offset , SEEK_SET); + ecl_kw = ecl_kw_fscanf_alloc_current_grdecl( cstream , ecl_type ); + offset = ftell( cstream ); + fclose( cstream ); + + return ecl_kw; +} + + + +static bool convertKeyword( const std::string& inputFile , const std::string& outputPath , std::ifstream& is , FieldType fieldType , std::ofstream& os ) { + bool convert = true; + ecl_type_enum ecl_type; + + if (fieldType == Integer) + ecl_type = ECL_INT_TYPE; + else + ecl_type = outputFloatType; + + { + std::ios::pos_type inputPos = is.tellg(); + ecl_kw_type * ecl_kw = loadFromcstdio( inputFile , inputPos , ecl_type ); + + if (ecl_kw_get_size( ecl_kw ) >= minImportSize) { + { + std::string outputFile = outputPath + "/" + ecl_kw_get_header( ecl_kw ); + fortio_type * fortio = fortio_open_writer( outputFile.c_str() , false , ECL_ENDIAN_FLIP ); + ecl_kw_fwrite( ecl_kw , fortio ); + fortio_fclose( fortio ); + + os << "IMPORT" << std::endl << " '" << outputFile << "' /" << std::endl << std::endl; + } + is.seekg( inputPos ); + } else { + copyKeyword( is , os ); + convert = false; + } + + + ecl_kw_free( ecl_kw ); + } + return convert; +} + + + + + +static bool parseFile(const std::string& inputFile, std::string& outputFile, const std::string& indent = "") { + bool updateFile = false; + std::cout << indent << "Parsing " << inputFile << "\n"; + { + std::ifstream is(inputFile.c_str()); + if (is) { + std::ofstream os; + std::string keyword; + std::string path; + { + boost::filesystem::path inputPath(inputFile); + path = inputPath.parent_path().string(); + } + + { + std::string basename; + std::string extension; + + outputFile = inputFile; + size_t ext_pos = inputFile.rfind("."); + if (ext_pos == std::string::npos) { + basename = outputFile.substr(); + extension = ""; + } else { + basename = outputFile.substr(0,ext_pos); + extension = outputFile.substr(ext_pos); + } + + outputFile = basename + "_import" + extension; + } + os.open( outputFile.c_str() ); + + while(is.good()) { + is >> ignoreWhitespace; + { + std::ios::pos_type start_pos = is.tellg(); + if (EclipseGridParser::readKeyword( is , keyword )) { + FieldType fieldType = EclipseGridParser::classifyKeyword( keyword ); + switch (fieldType) { + case(Integer): + case(FloatingPoint): + { + is.seekg( start_pos ); + if (convertKeyword( inputFile , path , is , fieldType , os )) { + std::cout << indent + " " << "Writing binary file: " << path << "/" << keyword << std::endl; + updateFile = true; + } + break; + } + case(Include): + { + std::string includeFile = readString(is); + if (!path.empty()) { + includeFile = path + '/' + includeFile; + } + { + std::string __outputFile; + bool updateInclude = parseFile( includeFile , __outputFile , indent + " "); + if (updateInclude) { + is.seekg( start_pos ); + skipKeyword( is ); + os << "INCLUDE" << std::endl << " '" << __outputFile << "' /" << std::endl << std::endl; + } + updateFile |= updateInclude; + } + break; + } + default: + { + is.seekg( start_pos ); + copyKeyword( is , os); + break; + } + } + } else + is >> ignoreLine; // Not at a valid keyword + } + } + + os.close(); + is.close(); + if (updateFile) + std::cout << indent << "Written updated include file: " << outputFile << std::endl; + else + remove( outputFile.c_str() ); + } else + std::cerr << indent << "** WARNING: Failed to open include file: " << inputFile << " for reading **" << std::endl; + } + return updateFile; +} + + + +int +main(int argc, char** argv) +{ + if (argc != 2) + THROW("Need the name of ECLIPSE file on command line"); + { + std::string outputFile; + parseFile(argv[1] , outputFile); + } +} diff --git a/examples/sim_2p_comp_reorder.cpp b/examples/sim_2p_comp_reorder.cpp index 9f2b516b..4ca75be5 100644 --- a/examples/sim_2p_comp_reorder.cpp +++ b/examples/sim_2p_comp_reorder.cpp @@ -32,7 +32,6 @@ #include #include #include -#include #include #include @@ -94,7 +93,7 @@ main(int argc, char** argv) // Grid init grid.reset(new GridManager(*deck)); // Rock and fluid init - props.reset(new BlackoilPropertiesFromDeck(*deck, *grid->c_grid())); + props.reset(new BlackoilPropertiesFromDeck(*deck, *grid->c_grid(), param)); // check_well_controls = param.getDefault("check_well_controls", false); // max_well_control_iterations = param.getDefault("max_well_control_iterations", 10); // Rock compressibility. diff --git a/examples/sim_wateroil.cpp b/examples/sim_wateroil.cpp index f7e0172f..0fe1f51c 100644 --- a/examples/sim_wateroil.cpp +++ b/examples/sim_wateroil.cpp @@ -175,7 +175,7 @@ main(int argc, char** argv) // Grid init grid.reset(new Opm::GridManager(deck)); // Rock and fluid init - props.reset(new Opm::BlackoilPropertiesFromDeck(deck, *grid->c_grid())); + props.reset(new BlackoilPropertiesFromDeck(deck, *grid->c_grid(), param)); // Wells init. wells.reset(new Opm::WellsManager(deck, *grid->c_grid(), props->permeability())); check_well_controls = param.getDefault("check_well_controls", false); @@ -408,7 +408,7 @@ main(int argc, char** argv) state.saturation(), state.surfacevol()); // Opm::computeInjectedProduced(*props, state.saturation(), reorder_src, stepsize, injected, produced); if (use_segregation_split) { - reorder_model.solveGravity(columns, &state.pressure()[0], &initial_porevol[0], + reorder_model.solveGravity(columns, stepsize, state.saturation(), state.surfacevol()); } } diff --git a/examples/spu_2p.cpp b/examples/spu_2p.cpp index 25cd3f6f..755ef202 100644 --- a/examples/spu_2p.cpp +++ b/examples/spu_2p.cpp @@ -94,14 +94,19 @@ #include +#ifdef HAVE_ERT +#include +#endif + static void outputState(const UnstructuredGrid& grid, const Opm::TwophaseState& state, - const int step, + const Opm::SimulatorTimer& simtimer, const std::string& output_dir) { // Write data in VTK format. + int step = simtimer.currentStepNum(); std::ostringstream vtkfilename; vtkfilename << output_dir << "/output-" << std::setw(3) << std::setfill('0') << step << ".vtu"; std::ofstream vtkfile(vtkfilename.str().c_str()); @@ -115,6 +120,9 @@ static void outputState(const UnstructuredGrid& grid, Opm::estimateCellVelocity(grid, state.faceflux(), cell_velocity); dm["velocity"] = &cell_velocity; Opm::writeVtkData(grid, dm, vtkfile); +#ifdef HAVE_ERT + Opm::writeECLData(grid , dm , simtimer , output_dir , "OPM" ); +#endif // Write data (not grid) in Matlab format for (Opm::DataMap::const_iterator it = dm.begin(); it != dm.end(); ++it) { @@ -530,7 +538,7 @@ main(int argc, char** argv) // Report timestep and (optionally) write state to disk. simtimer.report(std::cout); if (output && (simtimer.currentStepNum() % output_interval == 0)) { - outputState(*grid->c_grid(), state, simtimer.currentStepNum(), output_dir); + outputState(*grid->c_grid(), state, simtimer , output_dir); } // Solve pressure. @@ -699,7 +707,7 @@ main(int argc, char** argv) << "\n Transport time: " << ttime << std::endl; if (output) { - outputState(*grid->c_grid(), state, simtimer.currentStepNum(), output_dir); + outputState(*grid->c_grid(), state, simtimer, output_dir); outputWaterCut(watercut, output_dir); if (wells->c_wells()) { outputWellReport(wellreport, output_dir); diff --git a/lib/pkgconfig/opm-core.pc.in b/lib/pkgconfig/opm-core.pc.in new file mode 100644 index 00000000..2f1d70da --- /dev/null +++ b/lib/pkgconfig/opm-core.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: @PACKAGE_NAME@ +Description: @PACKAGE_STRING@ +Version: @PACKAGE_VERSION@ +URL: @PACKAGE_URL@ +Libs: -L${libdir} -l@PACKAGE@ +Cflags: -I${includedir} diff --git a/m4/ax_blas.m4 b/m4/ax_blas.m4 index e4f96cbe..7e6fd549 100644 --- a/m4/ax_blas.m4 +++ b/m4/ax_blas.m4 @@ -98,6 +98,11 @@ if test "x$BLAS_LIBS" != x; then fi fi +# don't probe if explicitly defined; by bailing out here if the +# argument is set, we guard against typo, incompatible libs. etc. +# being inadvertedly overrided by another (random) implementation +if test "x$with_blas" == x; then + # BLAS linked to by default? (happens on some supercomputers) if test $ax_blas_ok = no; then save_LIBS="$LIBS"; LIBS="$LIBS" @@ -186,6 +191,9 @@ if test $ax_blas_ok = no; then AC_CHECK_LIB(blas, $sgemm, [ax_blas_ok=yes; BLAS_LIBS="-lblas"]) fi +# end of guard against automatic overriding explicit definitions +fi + AC_SUBST(BLAS_LIBS) LIBS="$ax_blas_save_LIBS" diff --git a/m4/ax_lapack.m4 b/m4/ax_lapack.m4 index 6aa16aaf..b642c38c 100644 --- a/m4/ax_lapack.m4 +++ b/m4/ax_lapack.m4 @@ -101,6 +101,11 @@ if test "x$LAPACK_LIBS" != x; then fi fi +# don't probe if explicitly defined; by bailing out here if the +# argument is set, we guard against typo, incompatible libs. etc. +# being inadvertedly overrided by another (random) implementation +if test "x$with_lapack" = x; then + # LAPACK linked to by default? (is sometimes included in BLAS lib) if test $ax_lapack_ok = no; then save_LIBS="$LIBS"; LIBS="$LIBS $BLAS_LIBS $FLIBS" @@ -118,6 +123,9 @@ for lapack in lapack lapack_rs6k; do fi done +# end of guard against automatic overriding explicit definitions +fi + AC_SUBST(LAPACK_LIBS) # Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: diff --git a/m4/cxx0x_compiler.m4 b/m4/cxx0x_compiler.m4 new file mode 100644 index 00000000..47a7e90c --- /dev/null +++ b/m4/cxx0x_compiler.m4 @@ -0,0 +1,66 @@ +# whether compiler accepts -std=c++11 or -std=c++0x +# can be disabled by --disable-gxx0xcheck + +AC_DEFUN([GXX0X],[ + save_CXX="$CXX" + + # put this check first, so we get disable C++11 if C++0x is + AC_ARG_ENABLE(gxx0xcheck, + AC_HELP_STRING([--disable-gxx0xcheck], + [try flag -std=c++0x to enable C++0x features [[default=yes]]]), + [gxx0xcheck=$enableval], + [gxx0xcheck=yes]) + AC_ARG_ENABLE(gxx11check, + AC_HELP_STRING([--disable-gxx11check], + [try flag -std=c++11 to enable C++11 features [[default=yes]]]), + [gxx11check=$enableval], + [gxx11check=yes]) + + # try flag -std=c++11 + AC_CACHE_CHECK([whether $CXX accepts -std=c++11], dune_cv_gplusplus_accepts_cplusplus11, [ + AC_REQUIRE([AC_PROG_CXX]) + if test "x$GXX" = xyes && test "x$gxx11check" = xyes && test "x$gxx0xcheck" = xyes ; then + AC_LANG_PUSH([C++]) + CXX="$CXX -std=c++11" + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([ + #include + #include + ],)], + [dune_cv_gplusplus_accepts_cplusplus11=yes], + [dune_cv_gplusplus_accepts_cplusplus11=no]) + AC_LANG_POP([C++]) + else + dune_cv_gplusplus_accepts_cplusplus11=disabled + fi + ]) + if test "x$dune_cv_gplusplus_accepts_cplusplus11" = "xyes" ; then + CXX="$save_CXX -std=c++11" + CXXCPP="$CXXCPP -std=c++11" + else + CXX="$save_CXX" + + # try flag -std=c++0x instead + AC_CACHE_CHECK([whether $CXX accepts -std=c++0x], dune_cv_gplusplus_accepts_cplusplus0x, [ + AC_REQUIRE([AC_PROG_CXX]) + if test "x$GXX" = xyes && test "x$gxx0xcheck" = xyes; then + AC_LANG_PUSH([C++]) + CXX="$CXX -std=c++0x" + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([ + #include + #include + ],)], + [dune_cv_gplusplus_accepts_cplusplus0x=yes], + [dune_cv_gplusplus_accepts_cplusplus0x=no]) + AC_LANG_POP([C++]) + else + dune_cv_gplusplus_accepts_cplusplus0x=disabled + fi + ]) + if test "x$dune_cv_gplusplus_accepts_cplusplus0x" = "xyes" ; then + CXX="$save_CXX -std=c++0x" + CXXCPP="$CXXCPP -std=c++0x" + else + CXX="$save_CXX" + fi + fi +]) diff --git a/m4/cxx0x_nullptr.m4 b/m4/cxx0x_nullptr.m4 new file mode 100644 index 00000000..18d44451 --- /dev/null +++ b/m4/cxx0x_nullptr.m4 @@ -0,0 +1,17 @@ +AC_DEFUN([NULLPTR_CHECK],[ + AC_CACHE_CHECK([whether nullptr is supported], dune_cv_nullptr_support, [ + AC_REQUIRE([AC_PROG_CXX]) + AC_REQUIRE([GXX0X]) + AC_LANG_PUSH([C++]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM(,[ + char* ch = nullptr; + if(ch!=nullptr) { ; } + ])], + [dune_cv_nullptr_support=yes], + [dune_cv_nullptr_support=no]) + AC_LANG_POP + ]) + if test "x$dune_cv_nullptr_support" = xyes; then + AC_DEFINE(HAVE_NULLPTR, 1, [Define to 1 if nullptr is supported]) + fi +]) diff --git a/m4/cxx0x_static_assert.m4 b/m4/cxx0x_static_assert.m4 new file mode 100644 index 00000000..b68d155b --- /dev/null +++ b/m4/cxx0x_static_assert.m4 @@ -0,0 +1,14 @@ +AC_DEFUN([STATIC_ASSERT_CHECK],[ + AC_CACHE_CHECK([whether static_assert is supported], dune_cv_static_assert_support, [ + AC_REQUIRE([AC_PROG_CXX]) + AC_REQUIRE([GXX0X]) + AC_LANG_PUSH([C++]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM(,[static_assert(true,"MSG")])], + [dune_cv_static_assert_support=yes], + [dune_cv_static_assert_support=no]) + AC_LANG_POP + ]) + if test "x$dune_cv_static_assert_support" = xyes; then + AC_DEFINE(HAVE_STATIC_ASSERT, 1, [Define to 1 if static_assert is supported]) + fi +]) diff --git a/m4/ert.m4 b/m4/ert.m4 new file mode 100644 index 00000000..b1ea21a6 --- /dev/null +++ b/m4/ert.m4 @@ -0,0 +1,67 @@ +AC_DEFUN([_ERT_SOURCE_TEXT], +[ +AC_LANG_PROGRAM( +[[ +#include +#include +]],dnl +[[ +int sz; +sz = ecl_util_get_sizeof_ctype(ECL_INT_TYPE); +]])[]dnl +])[]dnl + +# ---------------------------------------------------------------------- + +AC_DEFUN([ERT], +[ +AC_ARG_WITH([ert], + [AS_HELP_STRING([--with-ert=], [Use ERT libraries])], + [], [with_ert=no]) + +use_ert=no + +AS_IF([test x"${with_ert}" != x"no"], +[ + _ert_LDFLAGS_SAVE="${LDFLAGS}" + _ert_LIBS_SAVE="${LIBS}" + _ert_CPPFLAGS_SAVE="${CPPFLAGS}" + _ert_CFLAGS_SAVE="${CFLAGS}" + + ERT_CPPFLAGS= + ERT_LDFLAGS= + ERT_LIBS="-lecl -lgeometry -lert_util -lpthread -lz -lgomp" + AS_IF([test x"${with_ert}" != x"yes"], + [ERT_LDFLAGS="-L${with_ert}/lib" + ERT_CPPFLAGS="-I${with_ert}/include"], [:])[]dnl + + CFLAGS="-std=gnu99" + CPPFLAGS="${ERT_CPPFLAGS} ${CPPFLAGS}" + LDFLAGS="${ERT_LDFLAGS} ${LDFLAGS}" + LIBS="${ERT_LIBS} ${LIBS}" + + AC_LINK_IFELSE([_ERT_SOURCE_TEXT], + [use_ert=yes], [use_ert=no]) + + LIBS="${_ert_LIBS_SAVE}" + CPPFLAGS="${_ert_CPPFLAGS_SAVE}" + LDFLAGS="${_ert_LDFLAGS_SAVE}" + CFLAGS="${_ert_CFLAGS_SAVE}" + + AS_IF([test x"${use_ert}" = x"yes"], + [AC_SUBST([ERT_CPPFLAGS]) + AC_SUBST([ERT_LDFLAGS]) + AC_SUBST([ERT_LIBS]) + AC_DEFINE([HAVE_ERT], [1], + [Are the ERT libraries available for reading and writing ECLIPSE files.])],dnl + [:])[]dnl +], [:])[]dnl + +AM_CONDITIONAL([HAVE_ERT], [test x"${use_ert}" != x"no"]) + +# AC_MSG_ERROR( +# [**** ERT_CPPFLAGS = ${ERT_CPPFLAGS} **** +# **** ERT_LDFLAGS = ${ERT_LDFLAGS} **** +# **** ERT_LIBS = ${ERT_LIBS} **** +# ]) +]) diff --git a/m4/opm_core.m4 b/m4/opm_core.m4 index efb75c13..a5b24a0c 100644 --- a/m4/opm_core.m4 +++ b/m4/opm_core.m4 @@ -5,6 +5,11 @@ dnl -*- autoconf -*- AC_DEFUN([OPM_CORE_CHECKS], [ +# Language features +GXX0X +STATIC_ASSERT_CHECK +NULLPTR_CHECK + # Checks for libraries. # Bring in numerics support (standard library component) @@ -17,8 +22,8 @@ AX_BOOST_SYSTEM AX_BOOST_DATE_TIME AX_BOOST_FILESYSTEM AX_BOOST_UNIT_TEST_FRAMEWORK - AX_DUNE_ISTL +OPM_PATH_SUPERLU OPM_AGMG # Checks for header files. diff --git a/m4/opm_lapack.m4 b/m4/opm_lapack.m4 index b5fa5ca5..083ecf0b 100644 --- a/m4/opm_lapack.m4 +++ b/m4/opm_lapack.m4 @@ -1,4 +1,7 @@ AC_DEFUN([OPM_LAPACK], [AC_REQUIRE([AC_F77_WRAPPERS])dnl AC_REQUIRE([AX_LAPACK])dnl + if test x"$ax_lapack_ok" != xyes; then + AC_MSG_ERROR([BLAS/LAPACK required, but not found.]) + fi ])[]dnl diff --git a/m4/opm_superlu.m4 b/m4/opm_superlu.m4 new file mode 100644 index 00000000..ff4aff8e --- /dev/null +++ b/m4/opm_superlu.m4 @@ -0,0 +1,334 @@ +## -*- autoconf -*- +# $Id: superlu.m4 5908 2010-02-23 16:46:05Z joe $ +# searches for SuperLU headers and libs + +# _slu_lib_path(SUPERLU_ROOT, HEADER) +# +# Try to find the subpath unter SUPERLU_ROOT containing HEADER. Try +# SUPERLU_ROOT/"include/superlu", SUPERLU_ROOT/"include", and +# SUPERLU_ROOT/"SRC", in that order. Set the subpath for the library to +# "lib". If HEADER was found in SUPERLU_ROOT/"SRC", check whether +# SUPERLU_ROOT/"lib" is a directory, and set the subpath for the library to +# the empty string "" if it isn't. +# +# Shell variables: +# my_include_path +# The subpath HEADER was found in: "include/superlu", "include", or +# "SRC". Contents is only meaningful for my_slu_found=yes. +# my_lib_path +# The subpath for the library: "lib" or "". Contents is only meaningful +# for my_slu_found=yes. +# my_slu_found +# Whether HEADER was found at all. Either "yes" or "no". +AC_DEFUN([_slu_lib_path], + [ + my_include_path=include/superlu + my_lib_path=lib + my_slu_found=yes + if test ! -f "$1/$my_include_path/$2" ; then + #Try to find headers under superlu + my_include_path=include + if test ! -f "$1/$my_include_path/$2" ; then + my_include_path=SRC + if test ! -f "$1/$my_include_path/$2"; then + my_slu_found=no + else + if ! test -d "$1/$my_lib_path"; then + my_lib_path="" + fi + fi + fi + fi + ] +) + +# _slu_search_versions(SUPERLU_ROOT) +# +# Search for either "slu_ddefs.h" or "dsp_defs.h" using _slu_lib_path(). +# +# Shell variables: +# my_slu_header +# The name of the header that was found: first of "slu_ddefs.h" or +# "dsp_defs.h". Contents is only meaningful for my_slu_found=yes. +# my_include_path +# The subpath the header was found in: "include/superlu", "include", or +# "SRC". Contents is only meaningful for my_slu_found=yes. +# my_lib_path +# The subpath for the library: "lib" or "". Contents is only meaningful +# for my_slu_found=yes. +# my_slu_found +# Whether any of the headers. Either "yes" or "no". +AC_DEFUN([_slu_search_versions], + [ + my_slu_header=slu_ddefs.h + _slu_lib_path($1, $my_slu_header) + if test "$my_slu_found" != "yes"; then + my_slu_header="dsp_defs.h" + _slu_lib_path($1, $my_slu_header) + fi + ] +) + + +# _slu_search_default() +# +# Search for SuperLU in the default locations "/usr" and "/usr/local". +# +# Shell variables: +# with_superlu +# Root of the SuperLU installation: first of "/usr" and "/usr/local". +# Contents is only meaningful for my_slu_found=yes. +# For other output variables see documentation of _slu_search_versions(). +AC_DEFUN([_slu_search_default], + [ + with_superlu=/usr + _slu_search_versions($with_superlu) + + if test "$my_slu_found" = "no"; then + with_superlu=/usr/local + _slu_search_versions($with_superlu) + fi + ] +) + + +# OPM_PATH_SUPERLU() +# +# REQUIRES: AC_PROG_CC, AX_BLAS +# +# Shell variables: +# with_superlu +# "no", "yes (version 4.3 or newer)", "yes (version 4.2 or older, post 2005)" or "yes (pre 2005)" +# direct_SUPERLU_CPPFLAGS +# direct_SUPERLU_LIBS +# CPPFLAGS and LIBS necessary to link against SuperLU. This variable +# contains no indirect references and is suitable for use inside +# configure. Guaranteed empty if SuperLU was not found. +# SUPERLU_CPPFLAGS +# SUPERLU_LIBS +# CPPFLAGS and LIBS necessary to link against SuperLU. This variable may +# contain indirect references and is suitable for use inside makefiles. +# Guaranteed empty if SuperLU was not found. +# HAVE_SUPERLU +# "0" or "1" depending on whether SuperLU was found. +# +# Substitutions: +# SUPERLU_LIBS +# SUPERLU_CPPFLAGS +# Substitutes the values of the corresponding shell variables. +# ALL_PKG_LIBS +# ALL_PKG_CPPFLAGS +# Adds references to SuperLU's substitutions. +# +# Defines: +# HAVE_SUPERLU +# ENABLE_SUPERLU or undefined. Whether SuperLU was found. The correct +# way to check this is "#if HAVE_SUPERLU": This way SuperLU features will +# be disabled unless $(SUPERLU_CPPFLAGS) was given when compiling. +# SUPERLU_POST_2005_VERSION +# 1 or undefined. A post-2005 version of SuperLU uses the header +# "slu_ddefs.h" while earlier versions use "dsp_defs.h". +# SUPERLU_MIN_VERSION_4_3 +# 1 or undefined. SuperLU version 4.3 or newer uses the symbol +# "SLU_DOUBLE" while earlier versions use "DOUBLE". +# HAVE_MEM_USAGE_T_EXPANSIONS +# 1 or undefined. Whether "mem_usage_t.expansions" was found in +# "slu_ddefs.h" or "dsp_defs.h" as apropriate. +# +# Conditionals: +# SUPERLU +AC_DEFUN([OPM_PATH_SUPERLU],[ + AC_REQUIRE([AC_PROG_CC]) + # we need this for FLIBS + AC_REQUIRE([AC_F77_LIBRARY_LDFLAGS]) + AC_REQUIRE([AX_BLAS]) + + # + # User hints ... + # + my_lib_path="" + my_include_path="" + AC_ARG_WITH([superlu], + [AC_HELP_STRING([--with-superlu],[user defined path to SuperLU library])], + [dnl + if test x"$withval" != xno ; then + # get absolute path + with_superlu=`eval cd $withval > /dev/null 2>&1 && pwd` + if test x"$withval" = xyes; then + # Search in default locations + _slu_search_default + else + # Search for the headers in the specified location + _slu_search_versions(["$with_superlu"]) + fi + fi + ], [dnl + # Search in default locations + _slu_search_default + ]) + + AC_ARG_WITH([superlu-lib], + [AC_HELP_STRING([--with-superlu-lib], + [The name of the static SuperLU library to link to. By default + the static library with the name superlu.a is tried, but + only if shared linking has failed first. Giving this + options forces static linking for SuperLU.])], + [ + if test x"$withval" = xno ; then + with_superlu_lib= + fi + ]) + + AC_ARG_WITH([superlu-blaslib], + [AC_HELP_STRING([--with-superlu-blaslib], + [The name of the static blas library to link to. By default + we try to link to the systems blas library (see + --with-blas). Giving this options forces static linking + for SuperLU.])], + [ + if test "$withval" = no ; then + with_superlu_blaslib= + fi + ]) + + # store old values + ac_save_LDFLAGS="$LDFLAGS" + ac_save_CPPFLAGS="$CPPFLAGS" + ac_save_LIBS="$LIBS" + + # do nothing if --without-superlu is used + if test x"$with_superlu" != x"no" ; then + # defaultpath + SUPERLU_LIB_PATH="$with_superlu/$my_lib_path" + SUPERLU_INCLUDE_PATH="$with_superlu/$my_include_path" + + # set variables so that tests can use them + direct_SUPERLU_CPPFLAGS="-I$SUPERLU_INCLUDE_PATH -DENABLE_SUPERLU" + SUPERLU_CPPFLAGS="-I$SUPERLU_INCLUDE_PATH -DENABLE_SUPERLU" + CPPFLAGS="$CPPFLAGS $direct_SUPERLU_CPPFLAGS" + + # check for central header + AC_CHECK_HEADER([$my_slu_header], + [HAVE_SUPERLU="1"], + [ + HAVE_SUPERLU="0" + AC_MSG_WARN([$my_slu_header not found in $SUPERLU_INCLUDE_PATH with $CPPFLAGS]) + ]) + + # if header is found check for the libs + if test x$HAVE_SUPERLU = x1 ; then + HAVE_SUPERLU=0 + + # if neither --with-superlu-lib nor --with-superlu-blaslib was + # given, try to link dynamically or with properly names static libs + if test x"$with_superlu_lib$with_superlu_blaslib" = x; then + LDFLAGS="$ac_save_LDFLAGS -L$SUPERLU_LIB_PATH" + LIBS="$ac_save_LIBS" + AC_CHECK_LIB([superlu], [dgssvx], + [ + direct_SUPERLU_LIBS="-L$SUPERLU_LIB_PATH -lsuperlu $BLAS_LIBS $FLIBS" + SUPERLU_LIBS="-L$SUPERLU_LIB_PATH -lsuperlu \${BLAS_LIBS} \${FLIBS}" + HAVE_SUPERLU="1" + ], [], [$BLAS_LIBS $FLIBS]) + fi + + if test $HAVE_SUPERLU = 0 && test x"$with_superlu_lib" = x; then + # set the default + with_superlu_lib=superlu.a + fi + + if test $HAVE_SUPERLU = 0 && + test x"$with_superlu_blaslib" = x; then + # try system blas + LDFLAGS="$ac_save_LDFLAGS" + LIBS="$SUPERLU_LIB_PATH/$with_superlu_lib $BLAS_LIBS $FLIBS $ac_save_LIBS" + AC_CHECK_FUNC([dgssvx], + [ + direct_SUPERLU_LIBS="$SUPERLU_LIB_PATH/$with_superlu_lib $BLAS_LIBS $FLIBS" + SUPERLU_LIBS="$SUPERLU_LIB_PATH/$with_superlu_lib \${BLAS_LIBS} \${FLIBS}" + HAVE_SUPERLU="1" + ]) + fi + + # No default for with_superlu_blaslib + + if test $HAVE_SUPERLU = 0 && + test x"$with_superlu_blaslib" != x; then + # try internal blas + LDFLAGS="$ac_save_LDFLAGS" + LIBS="$SUPERLU_LIB_PATH/$with_superlu_lib $SUPERLU_LIB_PATH/$with_superlu_blaslib $FLIBS $ac_save_LIBS" + AC_CHECK_FUNC([dgssvx], + [ + direct_SUPERLU_LIBS="$SUPERLU_LIB_PATH/$with_superlu_lib $SUPERLU_LIB_PATH/$with_superlu_blaslib $FLIBS" + SUPERLU_LIBS="$SUPERLU_LIB_PATH/$with_superlu_lib $SUPERLU_LIB_PATH/$with_superlu_blaslib \${FLIBS}" + HAVE_SUPERLU="1" + ]) + fi + + # test whether SuperLU version is at least 4.3 + if test $HAVE_SUPERLU = "1" ; then + AC_CHECK_DECL([SLU_DOUBLE], [SUPERLU_MIN_VERSION_4_3="1"], [], [#include <$my_slu_header>]) + fi + fi + + else # $with_superlu = no + HAVE_SUPERLU=0 + fi + + # Inform the user whether SuperLU was sucessfully found + AC_MSG_CHECKING([SuperLU]) + if test x$HAVE_SUPERLU = x1 ; then + if test "$my_slu_header" = "slu_ddefs.h"; then + if test x$SUPERLU_MIN_VERSION_4_3 = x1 ; then + with_superlu="yes (version 4.3 or newer)" + else + with_superlu="yes (version 4.2 or older, post 2005)" + fi + else + with_superlu="yes (pre 2005)" + fi + else + with_superlu="no" + fi + AC_MSG_RESULT([$with_superlu]) + + # check for optional member + if test $HAVE_SUPERLU = 1 ; then + if test "$my_slu_header" = "slu_ddefs.h"; then + AC_CHECK_MEMBERS([mem_usage_t.expansions],[],[],[#include "slu_ddefs.h"]) + else + AC_CHECK_MEMBERS([mem_usage_t.expansions],[],[],[#include "dsp_defs.h"]) + fi + fi + + # substitute variables + if test x$HAVE_SUPERLU = x0 ; then + SUPERLU_LIBS= + SUPERLU_CPPFLAGS= + fi + AC_SUBST([SUPERLU_LIBS]) + AC_SUBST([SUPERLU_CPPFLAGS]) + #DUNE_ADD_ALL_PKG([SUPERLU], [\$(SUPERLU_CPPFLAGS)], [], [\$(SUPERLU_LIBS)]) + + # tell automake + AM_CONDITIONAL(SUPERLU, test x$HAVE_SUPERLU = x1) + + # tell the preprocessor + if test x$HAVE_SUPERLU = x1 ; then + AC_DEFINE([HAVE_SUPERLU], [ENABLE_SUPERLU], [Define to ENABLE_SUPERLU if SUPERLU is found]) + if test "$my_slu_header" = "slu_ddefs.h"; then + AC_DEFINE([SUPERLU_POST_2005_VERSION], 1, [define to 1 if there is a header slu_ddefs.h in SuperLU]) + if test x$SUPERLU_MIN_VERSION_4_3 = x1 ; then + AC_DEFINE([SUPERLU_MIN_VERSION_4_3], 1, [define to 1 if there SLU_DOUBLE imported by header slu_ddefs.h from SuperLU]) + fi + fi + fi + + # summary + #DUNE_ADD_SUMMARY_ENTRY([SuperLU],[$with_superlu]) + + # restore variables + LDFLAGS="$ac_save_LDFLAGS" + CPPFLAGS="$ac_save_CPPFLAGS" + LIBS="$ac_save_LIBS" +]) diff --git a/opm-core.pc.in b/opm-core.pc.in new file mode 100644 index 00000000..1dd258f7 --- /dev/null +++ b/opm-core.pc.in @@ -0,0 +1,18 @@ +# This is the configuration for local builds. Use this by putting the +# compilation output path (the directory in which you ran ./configure) +# into the environment variable PKG_CONFIG_PATH. This will enable you +# to use pkg-config in your code while making changes to opm-core. + +# This is NOT the file that is installed in the system directories when +# you do `make install`. That is the one in lib/pkgconfig. However, if +# you make changes here, you should consider that one as well. + +libdir=@abs_top_builddir@/lib/.libs +includedir=@abs_top_srcdir@ + +Name: @PACKAGE_NAME@ +Description: @PACKAGE_STRING@ +Version: @PACKAGE_VERSION@ +URL: @PACKAGE_URL@ +Libs: -L${libdir} -l@PACKAGE@ +Cflags: -I${includedir} diff --git a/opm/core/GridManager.cpp b/opm/core/GridManager.cpp index 04d8cd04..d286e504 100644 --- a/opm/core/GridManager.cpp +++ b/opm/core/GridManager.cpp @@ -37,7 +37,8 @@ namespace Opm { // We accept two different ways to specify the grid. // 1. Corner point format. - // Requires ZCORN, COORDS, DIMENS or SPECGRID, optionally ACTNUM. + // Requires ZCORN, COORDS, DIMENS or SPECGRID, optionally + // ACTNUM, optionally MAPAXES. // For this format, we will verify that DXV, DYV, DZV, // DEPTHZ and TOPS are not present. // 2. Tensor grid format. @@ -119,29 +120,8 @@ namespace Opm void GridManager::initFromDeckCornerpoint(const Opm::EclipseGridParser& deck) { // Extract data from deck. - const std::vector& zcorn = deck.getFloatingPointValue("ZCORN"); - const std::vector& coord = deck.getFloatingPointValue("COORD"); - const int* actnum = 0; - if (deck.hasField("ACTNUM")) { - actnum = &(deck.getIntegerValue("ACTNUM")[0]); - } - std::vector dims; - if (deck.hasField("DIMENS")) { - dims = deck.getIntegerValue("DIMENS"); - } else if (deck.hasField("SPECGRID")) { - dims = deck.getSPECGRID().dimensions; - } else { - THROW("Deck must have either DIMENS or SPECGRID."); - } - // Collect in input struct for preprocessing. - struct grdecl grdecl; - grdecl.zcorn = &zcorn[0]; - grdecl.coord = &coord[0]; - grdecl.actnum = actnum; - grdecl.dims[0] = dims[0]; - grdecl.dims[1] = dims[1]; - grdecl.dims[2] = dims[2]; + struct grdecl grdecl = deck.get_grdecl(); // Process grid. ug_ = create_grid_cornerpoint(&grdecl, 0.0); diff --git a/opm/core/eclipse/EclipseGridParser.cpp b/opm/core/eclipse/EclipseGridParser.cpp index b4ea9ee3..5af1e2ec 100644 --- a/opm/core/eclipse/EclipseGridParser.cpp +++ b/opm/core/eclipse/EclipseGridParser.cpp @@ -47,8 +47,19 @@ #include #include #include -#include #include +#include +#include + +#ifdef HAVE_ERT +#include +#include +#include +#include +#include +#include +#include +#endif using namespace std; @@ -86,7 +97,7 @@ namespace EclipseKeywords string("MULTPV"), string("PRESSURE"), string("SGAS"), string("SWAT"), string("SOIL"), string("RS"), string("DXV"), string("DYV"), string("DZV"), - string("DEPTHZ"), string("TOPS") + string("DEPTHZ"), string("TOPS"), string("MAPAXES") }; const int num_floating_fields = sizeof(floating_fields) / sizeof(floating_fields[0]); @@ -114,7 +125,7 @@ namespace EclipseKeywords const int num_special_fields = sizeof(special_fields) / sizeof(special_fields[0]); string ignore_with_data[] = - { string("MAPUNITS"), string("MAPAXES"), string("GRIDUNIT"), + { string("MAPUNITS"), string("GRIDUNIT"), string("NTG"), string("REGDIMS"), string("WELLDIMS"), string("NSTACK"), string("SATNUM"), string("RPTRST"), string("ROIP"), string("RWIP"), @@ -141,44 +152,14 @@ namespace EclipseKeywords string include_keywords[] = { string("INCLUDE") }; const int num_include_keywords = sizeof(include_keywords) / sizeof(include_keywords[0]); + string import_keywords[] = { string("IMPORT") }; + const int num_import_keywords = sizeof(import_keywords) / sizeof(import_keywords[0]); + } // namespace EclipseKeywords namespace { - enum FieldType { - Integer, - FloatingPoint, - Timestepping, - SpecialField, - IgnoreWithData, - IgnoreNoData, - Include, - Unknown - }; - - inline FieldType classifyKeyword(const string& keyword) - { - using namespace EclipseKeywords; - if (count(integer_fields, integer_fields + num_integer_fields, keyword)) { - return Integer; - } else if (count(floating_fields, floating_fields + num_floating_fields, keyword)) { - return FloatingPoint; - } else if (keyword == "TSTEP" || keyword == "DATES") { - return Timestepping; - } else if (count(special_fields, special_fields + num_special_fields, keyword)) { - return SpecialField; - } else if (count(ignore_with_data, ignore_with_data + num_ignore_with_data, keyword)) { - return IgnoreWithData; - } else if (count(ignore_no_data, ignore_no_data + num_ignore_no_data, keyword)) { - return IgnoreNoData; - } else if (count(include_keywords, include_keywords + num_include_keywords, keyword)) { - return Include; - } else { - return Unknown; - } - } - inline std::string upcase(const std::string& s) { std::string us(s); @@ -191,71 +172,7 @@ namespace { return us; } - inline bool readKeyword(std::istream& is, std::string& keyword) - { - char buf[9]; - int i, j; - char c; - /* Clear buf */ - for (i=0; i<9; ++i) { - buf[i] = '\0'; - } - - /* Read first character and check if it is uppercase*/ - //buf[0] = fgetc(fp); - is.get(buf[0]); - if ( !isupper( buf[0] ) ) { - is.unget(); - return false; /* NOT VALID CHARACTER */ - } - - /* Scan as much as possible possible keyword, 8 characters long */ - i = 1; - is.get(c); - while ( (is.good()) && - (c != EOF ) && - (!isblank(c) ) && - (isupper(c) || isdigit(c)) && - (c != '\n' ) && - (c != '/' ) && - (i < 8 )) { - buf[i++] = c; - is.get(c); - } - - /* Skip rest of line */ - if (c != '\n'){ - is.get(c); - while ( (is.good()) && - (c != EOF ) && - (c != '\n' )) { - is.get(c); - } - } - if(c == '\n') { - is.unget(); - } - - /* Find first non-uppercase or non-digit character */ - for (i=0; i<8; ++i) { - if ( !(isupper(buf[i]) || isdigit(buf[i])) ) { - break; - } - } - - /* Check if remaining characters are blank */ - for (j = i; j<8; ++j) { - if(!isspace(buf[j]) && buf[j] != '\0') { - return false; /* CHARACTER AFTER SPACE OR INVALID CHARACTER */ - } - buf[j] = '\0'; - } - keyword = std::string(buf); - std::string::size_type end = keyword.find_last_of('\0'); - if(end != keyword.npos) - keyword = keyword.substr(0, end+1); - return true; - } + } // anon namespace @@ -298,6 +215,98 @@ EclipseGridParser::EclipseGridParser(const string& filename, bool convert_to_SI) } + FieldType EclipseGridParser::classifyKeyword(const string& keyword) + { + using namespace EclipseKeywords; + if (count(integer_fields, integer_fields + num_integer_fields, keyword)) { + return Integer; + } else if (count(floating_fields, floating_fields + num_floating_fields, keyword)) { + return FloatingPoint; + } else if (keyword == "TSTEP" || keyword == "DATES") { + return Timestepping; + } else if (count(special_fields, special_fields + num_special_fields, keyword)) { + return SpecialField; + } else if (count(ignore_with_data, ignore_with_data + num_ignore_with_data, keyword)) { + return IgnoreWithData; + } else if (count(ignore_no_data, ignore_no_data + num_ignore_no_data, keyword)) { + return IgnoreNoData; + } else if (count(include_keywords, include_keywords + num_include_keywords, keyword)) { + return Include; + } else if (count(import_keywords, import_keywords + num_import_keywords, keyword)) { + return Import; + } else { + return Unknown; + } + } + + +bool EclipseGridParser::readKeyword(std::istream& is, std::string& keyword) +{ + char buf[9]; + int i, j; + char c; + /* Clear buf */ + for (i=0; i<9; ++i) { + buf[i] = '\0'; + } + + /* Read first character and check if it is uppercase*/ + //buf[0] = fgetc(fp); + is.get(buf[0]); + if ( !isupper( buf[0] ) ) { + is.unget(); + return false; /* NOT VALID CHARACTER */ + } + + /* Scan as much as possible possible keyword, 8 characters long */ + i = 1; + is.get(c); + while ( (is.good()) && + (c != EOF ) && + (!isblank(c) ) && + (isupper(c) || isdigit(c)) && + (c != '\n' ) && + (c != '/' ) && + (i < 8 )) { + buf[i++] = c; + is.get(c); + } + + /* Skip rest of line */ + if (c != '\n'){ + is.get(c); + while ( (is.good()) && + (c != EOF ) && + (c != '\n' )) { + is.get(c); + } + } + if(c == '\n') { + is.unget(); + } + + /* Find first non-uppercase or non-digit character */ + for (i=0; i<8; ++i) { + if ( !(isupper(buf[i]) || isdigit(buf[i])) ) { + break; + } + } + + /* Check if remaining characters are blank */ + for (j = i; j<8; ++j) { + if(!isspace(buf[j]) && buf[j] != '\0') { + return false; /* CHARACTER AFTER SPACE OR INVALID CHARACTER */ + } + buf[j] = '\0'; + } + keyword = std::string(buf); + std::string::size_type end = keyword.find_last_of('\0'); + if(end != keyword.npos) + keyword = keyword.substr(0, end+1); + return true; +} + + /// Read the given stream, overwriting any previous data. //--------------------------------------------------------------------------- void EclipseGridParser::read(istream& is, bool convert_to_SI) @@ -488,6 +497,14 @@ void EclipseGridParser::readImpl(istream& is) // is >> ignoreSlashLine; break; } + case Import: { + string import_filename = readString(is); + if (!directory_.empty()) { + import_filename = directory_ + '/' + import_filename; + } + getNumericErtFields(import_filename); + break; + } case Unknown: default: ignored_fields_.insert(keyword); @@ -542,6 +559,9 @@ void EclipseGridParser::convertToSI() do_convert = false; // Dimensionless keywords... } else if (key == "PRESSURE") { unit = units_.pressure; + } else if (key == "MAPAXES") { + MESSAGE("Not applying units to MAPAXES yet!"); + unit = 1.0; } else { THROW("Units for field " << key << " not specified. Cannon convert to SI."); } @@ -800,5 +820,311 @@ void EclipseGridParser::computeUnits() THROW("Unknown unit family " << unit_family); } } + + +struct grdecl EclipseGridParser::get_grdecl() const { + struct grdecl grdecl; + + // Extract data from deck. + const std::vector& zcorn = getFloatingPointValue("ZCORN"); + const std::vector& coord = getFloatingPointValue("COORD"); + const int* actnum = NULL; + if (hasField("ACTNUM")) { + actnum = &(getIntegerValue("ACTNUM")[0]); + } + + std::vector dims; + if (hasField("DIMENS")) { + dims = getIntegerValue("DIMENS"); + } else if (hasField("SPECGRID")) { + dims = getSPECGRID().dimensions; + } else { + THROW("Deck must have either DIMENS or SPECGRID."); + } + + // Collect in input struct for preprocessing. + + grdecl.zcorn = &zcorn[0]; + grdecl.coord = &coord[0]; + grdecl.actnum = actnum; + grdecl.dims[0] = dims[0]; + grdecl.dims[1] = dims[1]; + grdecl.dims[2] = dims[2]; + + if (hasField("MAPAXES")) { + const std::vector &mapaxes = getFloatingPointValue("MAPAXES"); + grdecl.mapaxes = &mapaxes[0]; + } else + grdecl.mapaxes = NULL; + + + return grdecl; +} + + +#ifdef HAVE_ERT +/* + This function will create a ecl_kw instance filled with the data + from input argument @keyword. The ecl_kw will get it's own copy of + the data. + + If the input type ecl_type == ECL_INT_TYPE the function will use the + getIntegerValue() method to get the keyword data, if ecl_type == + ECL_DOUBLE_TYPE || ECL_FLOAT_TYPE the getFloatingPointValue() + function is invoked. If ecl_type == ECL_FLOAT_TYPE the data will be + converted to when inserting into the ecl_kw. + + When the ecl_kw instance is no longer needed it should be discarded + with a call to ecl_kw_free( ). + + If you are asking for a non-existent field the function will return NULL +*/ + +ecl_kw_type * EclipseGridParser::newEclKW(const std::string &keyword , ecl_type_enum ecl_type) const { + ecl_kw_type * ecl_kw = NULL; + if (hasField(keyword)) { + if (ecl_type == ECL_INT_TYPE) { + std::vector data = getIntegerValue( keyword ); + ecl_kw = ecl_kw_alloc( keyword.c_str() , data.size() , ecl_type ); + ecl_kw_set_memcpy_data( ecl_kw , &data[0]); + } else { + std::vector data = getFloatingPointValue( keyword ); + if (ecl_type == ECL_DOUBLE_TYPE) { + ecl_kw = ecl_kw_alloc( keyword.c_str() , data.size() , ecl_type ); + ecl_kw_set_memcpy_data( ecl_kw , &data[0]); + } else if (ecl_type == ECL_FLOAT_TYPE) { + ecl_kw = ecl_kw_alloc( keyword.c_str() , data.size() , ecl_type ); + for (std::vector::size_type i=0; i < data.size(); i++) + ecl_kw_iset_float( ecl_kw , i , data[i] ); + } + } + } + return ecl_kw; +} + + +/** + This function will extract the COORD, ZCORN, ACTNUM and optionaly + MAPAXES keywords from the eclipse deck and create an ecl_grid + instance. + + When you are finished working with the ecl_grid instance it should + be disposed with ecl_grid_free( ). +*/ + +ecl_grid_type * EclipseGridParser::newGrid( ) { + struct grdecl grdecl = get_grdecl(); + ecl_kw_type * coord_kw = newEclKW( COORD_KW , ECL_FLOAT_TYPE ); + ecl_kw_type * zcorn_kw = newEclKW( ZCORN_KW , ECL_FLOAT_TYPE ); + ecl_kw_type * actnum_kw = newEclKW( ACTNUM_KW , ECL_INT_TYPE ); + ecl_kw_type * mapaxes_kw = NULL; + + ecl_grid_type * grid ; + if (grdecl.mapaxes != NULL) + mapaxes_kw = newEclKW( MAPAXES_KW , ECL_FLOAT_TYPE ); + + grid = ecl_grid_alloc_GRDECL_kw( grdecl.dims[0] , grdecl.dims[1] , grdecl.dims[2] , zcorn_kw , coord_kw , actnum_kw , mapaxes_kw ); + + ecl_kw_free( coord_kw ); + ecl_kw_free( zcorn_kw ); + ecl_kw_free( actnum_kw ); + if (mapaxes_kw != NULL) + ecl_kw_free( mapaxes_kw ); + + return grid; +} + + + +/** + This function will save an EGRID file based on the COORD, ZCORN, + ACTNUM and optionally MAPAXES keywords included in the deck. + + This function creates the EGRID file without going through a + ecl_grid instance; this is obviously somewhat faster and less + memory demanding. Alternatively you can create a ecl_grid instance + and then subsequently store that grid as an EGRID file: + + { + ecl_grid_type * grid = newGRID( ); + ecl_grid_fwrite_EGRID( grid , filename ); + ecl_grid_free( grid ); + } +*/ + +void EclipseGridParser::saveEGRID( const std::string & filename) { + bool endian_flip = true;//ECL_ENDIAN_FLIP; + bool fmt_file = ecl_util_fmt_file( filename.c_str() ); + struct grdecl grdecl = get_grdecl(); + fortio_type * fortio = fortio_open_writer( filename.c_str() , fmt_file , endian_flip ); + { + float * mapaxes = NULL; + if (grdecl.mapaxes != NULL) { + mapaxes = new float[6]; + for (int i=0; i < 6; i++) + mapaxes[i]= grdecl.mapaxes[i]; + } + + ecl_grid_fwrite_EGRID_header( grdecl.dims , mapaxes , fortio ); + + if (grdecl.mapaxes != NULL) + delete[] mapaxes; + } + { + ecl_kw_type * coord_kw = newEclKW( COORD_KW , ECL_FLOAT_TYPE ); + ecl_kw_type * zcorn_kw = newEclKW( ZCORN_KW , ECL_FLOAT_TYPE ); + ecl_kw_type * actnum_kw = newEclKW( ACTNUM_KW , ECL_INT_TYPE ); + ecl_kw_type * endgrid_kw = ecl_kw_alloc( ENDGRID_KW , 0 , ECL_INT_TYPE ); + + ecl_kw_fwrite( coord_kw , fortio ); + ecl_kw_fwrite( zcorn_kw , fortio ); + ecl_kw_fwrite( actnum_kw , fortio ); + ecl_kw_fwrite( endgrid_kw , fortio ); + + ecl_kw_free( coord_kw ); + ecl_kw_free( zcorn_kw ); + ecl_kw_free( actnum_kw ); + ecl_kw_free( endgrid_kw ); + } + fortio_fclose( fortio ); +} + +/** + Will query the deck for keyword @kw; and save it to the @fortio + instance if the keyword can be found. +*/ +void EclipseGridParser::save_kw( fortio_type * fortio , const std::string & kw , ecl_type_enum ecl_type) { + ecl_kw_type * ecl_kw = newEclKW( kw , ecl_type ); + if (ecl_kw != NULL) { + ecl_kw_fwrite( ecl_kw , fortio ); + ecl_kw_free( ecl_kw ); + } +} + + +/** + Will save an ECLIPSE INIT file to @filename. Observe that the main + focus of this function is to store grid properties like PERMX and + PORO, various tabular properties like e.g. relperm tables and + thermodynamic properties are ignored. +*/ + +void EclipseGridParser::saveINIT( const std::string & filename , const ecl_grid_type * ecl_grid) { + int phases = ECL_OIL_PHASE + ECL_WATER_PHASE; + bool fmt_file = ecl_util_fmt_file( filename.c_str() ); + bool endian_flip = true;//ECL_ENDIAN_FLIP; + fortio_type * fortio = fortio_open_writer( filename.c_str() , fmt_file , endian_flip ); + { + ecl_kw_type * poro_kw = newEclKW( PORO_KW , ECL_FLOAT_TYPE ); + time_t start_date; + + { + tm td_tm = to_tm( start_date_ ); + start_date = mktime( &td_tm ); + } + + ecl_init_file_fwrite_header( fortio , ecl_grid , poro_kw , phases , start_date ); + ecl_kw_free( poro_kw ); + } + + /* This collection of keywords is somewhat arbitrary and random. */ + save_kw( fortio , "PERMX" , ECL_FLOAT_TYPE); + save_kw( fortio , "PERMY" , ECL_FLOAT_TYPE); + save_kw( fortio , "PERMZ" , ECL_FLOAT_TYPE); + + save_kw( fortio , "FIPNUM" , ECL_INT_TYPE); + save_kw( fortio , "SATNUM" , ECL_INT_TYPE); + save_kw( fortio , "EQLNUM" , ECL_INT_TYPE); + + fortio_fclose( fortio ); +} + + +/** + This is the main function used to save the state of the ECLIPSE + deck in ECLIPSE format. The function will save an INIT file and an + EGRID file. + + The input arguments are the output directory to store files in, and + the basename to use for the files; the function will build up a + ECLIPSE standard filename internally. +*/ + +void EclipseGridParser::saveEGRID_INIT( const std::string& output_dir , const std::string& basename, bool fmt_file) { + ecl_grid_type * ecl_grid = newGrid(); + char * egrid_file = ecl_util_alloc_filename( output_dir.c_str() , basename.c_str() , ECL_EGRID_FILE , fmt_file , 0); + char * init_file = ecl_util_alloc_filename( output_dir.c_str() , basename.c_str() , ECL_INIT_FILE , fmt_file , 0); + + ecl_grid_fwrite_EGRID( ecl_grid , egrid_file ); + saveINIT( init_file , ecl_grid ); + + free( init_file ); + free( egrid_file ); + ecl_grid_free( ecl_grid ); +} +#endif + +// Read an imported fortio data file using Ert. +// Data stored in 'integer_field_map_' and 'floating_field_map_'. +void EclipseGridParser::getNumericErtFields(const string& filename) +{ +#ifdef HAVE_ERT + // Read file + ecl_file_type * ecl_file = ecl_file_open(filename.c_str()); + if (ecl_file == NULL) { + THROW("Could not open IMPORTed file " << filename); + } + const int num_kw = ecl_file_get_size(ecl_file); + std::vector double_vec; + std::vector int_vec; + for (int i=0; i. #include #include +#include +#ifdef HAVE_ERT +#include +#include +#endif + + namespace Opm { @@ -64,9 +71,23 @@ namespace Opm */ -class EclipseGridParser -{ -public: + enum FieldType { + Integer, + FloatingPoint, + Timestepping, + SpecialField, + IgnoreWithData, + IgnoreNoData, + Include, + Import, + Unknown + }; + + + + class EclipseGridParser + { + public: /// Default constructor. EclipseGridParser(); /// Constructor taking an eclipse filename. Unless the second @@ -74,6 +95,11 @@ public: /// converted to SI units. explicit EclipseGridParser(const std::string& filename, bool convert_to_SI = true); + + static FieldType classifyKeyword(const std::string& keyword); + static bool readKeyword(std::istream& is, std::string& keyword); + + /// Read the given stream, overwriting any previous data. Unless /// the second argument 'convert_to_SI' is false, all fields will /// be converted to SI units. @@ -191,11 +217,28 @@ public: /// The units specified by the eclipse file read. const EclipseUnits& units() const; + struct grdecl get_grdecl() const; + +#ifdef HAVE_ERT + void saveEGRID_INIT( const std::string& output_dir , const std::string& basename, bool fmt_file = false); + void saveEGRID( const std::string & filename ); + void saveINIT( const std::string & filename , const ecl_grid_type * ecl_grid); + ecl_grid_type * newGrid( ); +#endif + + private: + +#ifdef HAVE_ERT + ecl_kw_type * newEclKW(const std::string &keyword , ecl_type_enum ecl_type) const; + void save_kw( fortio_type * fortio , const std::string & kw , ecl_type_enum ecl_type); +#endif + SpecialFieldPtr createSpecialField(std::istream& is, const std::string& fieldname); SpecialFieldPtr cloneSpecialField(const std::string& fieldname, const std::tr1::shared_ptr original); void readImpl(std::istream& is); + void getNumericErtFields(const std::string& filename); std::string directory_; @@ -216,6 +259,7 @@ private: }; + } // namespace Opm #endif // SINTEF_ECLIPSEGRIDPARSER_HEADER diff --git a/opm/core/eclipse/EclipseGridParserHelpers.hpp b/opm/core/eclipse/EclipseGridParserHelpers.hpp index 6ab5fb78..7bc0e003 100644 --- a/opm/core/eclipse/EclipseGridParserHelpers.hpp +++ b/opm/core/eclipse/EclipseGridParserHelpers.hpp @@ -398,10 +398,18 @@ namespace yv.push_back(table[k][i]); } } - // Interpolate - for (int i=0; i double_data(10, -1.0E20); - const int num_to_read = 10; + const int num_to_read = 4; int num_read = readDefaultedVectorData(is, double_data, num_to_read); gconinje_line.surface_flow_max_rate_ = double_data[0]; gconinje_line.resv_flow_max_rate_ = double_data[1]; diff --git a/opm/core/fluid/BlackoilPropertiesBasic.cpp b/opm/core/fluid/BlackoilPropertiesBasic.cpp index be8211c1..2496d98e 100644 --- a/opm/core/fluid/BlackoilPropertiesBasic.cpp +++ b/opm/core/fluid/BlackoilPropertiesBasic.cpp @@ -26,20 +26,20 @@ namespace Opm { BlackoilPropertiesBasic::BlackoilPropertiesBasic(const parameter::ParameterGroup& param, - const int dim, - const int num_cells) + const int dim, + const int num_cells) { - double poro = param.getDefault("porosity", 1.0); - using namespace Opm::unit; - using namespace Opm::prefix; - double perm = param.getDefault("permeability", 100.0)*milli*darcy; + double poro = param.getDefault("porosity", 1.0); + using namespace Opm::unit; + using namespace Opm::prefix; + double perm = param.getDefault("permeability", 100.0)*milli*darcy; rock_.init(dim, num_cells, poro, perm); - pvt_.init(param); + pvt_.init(param); satprops_.init(param); - if (pvt_.numPhases() != satprops_.numPhases()) { - THROW("BlackoilPropertiesBasic::BlackoilPropertiesBasic() - Inconsistent number of phases in pvt data (" - << pvt_.numPhases() << ") and saturation-dependent function data (" << satprops_.numPhases() << ")."); - } + if (pvt_.numPhases() != satprops_.numPhases()) { + THROW("BlackoilPropertiesBasic::BlackoilPropertiesBasic() - Inconsistent number of phases in pvt data (" + << pvt_.numPhases() << ") and saturation-dependent function data (" << satprops_.numPhases() << ")."); + } } BlackoilPropertiesBasic::~BlackoilPropertiesBasic() @@ -90,11 +90,11 @@ namespace Opm /// \param[out] dmudp If non-null: array of nP viscosity derivative values, /// array must be valid before calling. void BlackoilPropertiesBasic::viscosity(const int n, - const double* p, - const double* z, - const int* /*cells*/, - double* mu, - double* dmudp) const + const double* p, + const double* z, + const int* /*cells*/, + double* mu, + double* dmudp) const { if (dmudp) { THROW("BlackoilPropertiesBasic::viscosity() -- derivatives of viscosity not yet implemented."); @@ -114,16 +114,16 @@ namespace Opm /// array must be valid before calling. The matrices are output /// in Fortran order. void BlackoilPropertiesBasic::matrix(const int n, - const double* /*p*/, - const double* /*z*/, - const int* /*cells*/, - double* A, - double* dAdp) const + const double* /*p*/, + const double* /*z*/, + const int* /*cells*/, + double* A, + double* dAdp) const { - const int np = numPhases(); - ASSERT(np <= 2); - double B[2]; // Must be enough since component classes do not handle more than 2. - pvt_.B(1, 0, 0, B); + const int np = numPhases(); + ASSERT(np <= 2); + double B[2]; // Must be enough since component classes do not handle more than 2. + pvt_.B(1, 0, 0, B); // Compute A matrix // #pragma omp parallel for for (int i = 0; i < n; ++i) { @@ -152,8 +152,8 @@ namespace Opm /// of a call to the method matrix(). /// \param[out] rho Array of nP density values, array must be valid before calling. void BlackoilPropertiesBasic::density(const int n, - const double* A, - double* rho) const + const double* A, + double* rho) const { const int np = numPhases(); const double* sdens = pvt_.surfaceDensities(); @@ -186,10 +186,10 @@ namespace Opm /// m_{ij} = \frac{dkr_i}{ds^j}, /// and is output in Fortran order (m_00 m_10 m_20 m01 ...) void BlackoilPropertiesBasic::relperm(const int n, - const double* s, - const int* /*cells*/, - double* kr, - double* dkrds) const + const double* s, + const int* /*cells*/, + double* kr, + double* dkrds) const { satprops_.relperm(n, s, kr, dkrds); } @@ -205,10 +205,10 @@ namespace Opm /// m_{ij} = \frac{dpc_i}{ds^j}, /// and is output in Fortran order (m_00 m_10 m_20 m01 ...) void BlackoilPropertiesBasic::capPress(const int n, - const double* s, - const int* /*cells*/, - double* pc, - double* dpcds) const + const double* s, + const int* /*cells*/, + double* pc, + double* dpcds) const { satprops_.capPress(n, s, pc, dpcds); } @@ -226,7 +226,7 @@ namespace Opm double* smin, double* smax) const { - satprops_.satRange(n, smin, smax); + satprops_.satRange(n, smin, smax); } diff --git a/opm/core/fluid/BlackoilPropertiesBasic.hpp b/opm/core/fluid/BlackoilPropertiesBasic.hpp index d879036e..44a76013 100644 --- a/opm/core/fluid/BlackoilPropertiesBasic.hpp +++ b/opm/core/fluid/BlackoilPropertiesBasic.hpp @@ -35,16 +35,16 @@ namespace Opm { public: /// Construct from parameters. - /// The following parameters are accepted (defaults): - /// num_phases (2) Must be 1 or 2. - /// relperm_func ("Linear") Must be "Constant", "Linear" or "Quadratic". - /// rho1 [rho2, rho3] (1.0e3) Density in kg/m^3 - /// mu1 [mu2, mu3] (1.0) Viscosity in cP - /// porosity (1.0) Porosity - /// permeability (100.0) Permeability in mD + /// The following parameters are accepted (defaults): + /// num_phases (2) Must be 1 or 2. + /// relperm_func ("Linear") Must be "Constant", "Linear" or "Quadratic". + /// rho1 [rho2, rho3] (1.0e3) Density in kg/m^3 + /// mu1 [mu2, mu3] (1.0) Viscosity in cP + /// porosity (1.0) Porosity + /// permeability (100.0) Permeability in mD BlackoilPropertiesBasic(const parameter::ParameterGroup& param, - const int dim, - const int num_cells); + const int dim, + const int num_cells); /// Destructor. virtual ~BlackoilPropertiesBasic(); @@ -151,7 +151,7 @@ namespace Opm double* dpcds) const; - /// Obtain the range of allowable saturation values. +/// Obtain the range of allowable saturation values. /// In cell cells[i], saturation of phase p is allowed to be /// in the interval [smin[i*P + p], smax[i*P + p]]. /// \param[in] n Number of data points. diff --git a/opm/core/fluid/BlackoilPropertiesFromDeck.cpp b/opm/core/fluid/BlackoilPropertiesFromDeck.cpp index 1f34e89e..0eba8ffb 100644 --- a/opm/core/fluid/BlackoilPropertiesFromDeck.cpp +++ b/opm/core/fluid/BlackoilPropertiesFromDeck.cpp @@ -19,6 +19,7 @@ #include #include + namespace Opm { @@ -26,15 +27,19 @@ namespace Opm const UnstructuredGrid& grid, bool init_rock) { - if (init_rock){ + if (init_rock){ rock_.init(deck, grid); } - pvt_.init(deck); - satprops_.init(deck, grid); - if (pvt_.numPhases() != satprops_.numPhases()) { - THROW("BlackoilPropertiesBasic::BlackoilPropertiesBasic() - Inconsistent number of phases in pvt data (" - << pvt_.numPhases() << ") and saturation-dependent function data (" << satprops_.numPhases() << ")."); - } + pvt_.init(deck, 200); + SaturationPropsFromDeck* ptr + = new SaturationPropsFromDeck(); + satprops_.reset(ptr); + ptr->init(deck, grid, 200); + + if (pvt_.numPhases() != satprops_->numPhases()) { + THROW("BlackoilPropertiesFromDeck::BlackoilPropertiesFromDeck() - Inconsistent number of phases in pvt data (" + << pvt_.numPhases() << ") and saturation-dependent function data (" << satprops_->numPhases() << ")."); + } } BlackoilPropertiesFromDeck::BlackoilPropertiesFromDeck(const EclipseGridParser& deck, @@ -45,13 +50,56 @@ namespace Opm if(init_rock){ rock_.init(deck, grid); } - const int samples = param.getDefault("dead_tab_size", 1025); - pvt_.init(deck, samples); - satprops_.init(deck, grid, param); - if (pvt_.numPhases() != satprops_.numPhases()) { - THROW("BlackoilPropertiesBasic::BlackoilPropertiesBasic() - Inconsistent number of phases in pvt data (" - << pvt_.numPhases() << ") and saturation-dependent function data (" << satprops_.numPhases() << ")."); + const int pvt_samples = param.getDefault("pvt_tab_size", 200); + pvt_.init(deck, pvt_samples); + + // Unfortunate lack of pointer smartness here... + const int sat_samples = param.getDefault("sat_tab_size", 200); + std::string threephase_model = param.getDefault("threephase_model", "simple"); + if (sat_samples > 1) { + if (threephase_model == "stone2") { + SaturationPropsFromDeck* ptr + = new SaturationPropsFromDeck(); + satprops_.reset(ptr); + ptr->init(deck, grid, sat_samples); + } else if (threephase_model == "simple") { + SaturationPropsFromDeck* ptr + = new SaturationPropsFromDeck(); + satprops_.reset(ptr); + ptr->init(deck, grid, sat_samples); + } else if (threephase_model == "gwseg") { + SaturationPropsFromDeck* ptr + = new SaturationPropsFromDeck(); + satprops_.reset(ptr); + ptr->init(deck, grid, sat_samples); + } else { + THROW("Unknown threephase_model: " << threephase_model); + } + } else { + if (threephase_model == "stone2") { + SaturationPropsFromDeck* ptr + = new SaturationPropsFromDeck(); + satprops_.reset(ptr); + ptr->init(deck, grid, sat_samples); + } else if (threephase_model == "simple") { + SaturationPropsFromDeck* ptr + = new SaturationPropsFromDeck(); + satprops_.reset(ptr); + ptr->init(deck, grid, sat_samples); + } else if (threephase_model == "gwseg") { + SaturationPropsFromDeck* ptr + = new SaturationPropsFromDeck(); + satprops_.reset(ptr); + ptr->init(deck, grid, sat_samples); + } else { + THROW("Unknown threephase_model: " << threephase_model); + } + } + + if (pvt_.numPhases() != satprops_->numPhases()) { + THROW("BlackoilPropertiesFromDeck::BlackoilPropertiesFromDeck() - Inconsistent number of phases in pvt data (" + << pvt_.numPhases() << ") and saturation-dependent function data (" << satprops_->numPhases() << ")."); } } @@ -256,7 +304,7 @@ namespace Opm double* kr, double* dkrds) const { - satprops_.relperm(n, s, cells, kr, dkrds); + satprops_->relperm(n, s, cells, kr, dkrds); } @@ -275,7 +323,7 @@ namespace Opm double* pc, double* dpcds) const { - satprops_.capPress(n, s, cells, pc, dpcds); + satprops_->capPress(n, s, cells, pc, dpcds); } @@ -291,7 +339,7 @@ namespace Opm double* smin, double* smax) const { - satprops_.satRange(n, cells, smin, smax); + satprops_->satRange(n, cells, smin, smax); } diff --git a/opm/core/fluid/BlackoilPropertiesFromDeck.hpp b/opm/core/fluid/BlackoilPropertiesFromDeck.hpp index 782407dc..f78e2cd7 100644 --- a/opm/core/fluid/BlackoilPropertiesFromDeck.hpp +++ b/opm/core/fluid/BlackoilPropertiesFromDeck.hpp @@ -27,6 +27,7 @@ #include #include #include +#include struct UnstructuredGrid; @@ -40,7 +41,7 @@ namespace Opm public: /// Initialize from deck and grid. /// \param[in] deck Deck input parser - /// \param[in] grid Grid to which property object applies, needed for the + /// \param[in] grid Grid to which property object applies, needed for the /// mapping from cell indices (typically from a processed grid) /// to logical cartesian indices consistent with the deck. BlackoilPropertiesFromDeck(const EclipseGridParser& deck, @@ -48,12 +49,15 @@ namespace Opm /// Initialize from deck, grid and parameters. /// \param[in] deck Deck input parser - /// \param[in] grid Grid to which property object applies, needed for the + /// \param[in] grid Grid to which property object applies, needed for the /// mapping from cell indices (typically from a processed grid) /// to logical cartesian indices consistent with the deck. /// \param[in] param Parameters. Accepted parameters include: - /// dead_tab_size (1025) number of uniform sample points for dead-oil pvt tables. - /// tab_size_kr (200) number of uniform sample points for saturation tables. + /// pvt_tab_size (200) number of uniform sample points for dead-oil pvt tables. + /// sat_tab_size (200) number of uniform sample points for saturation tables. + /// threephase_model("simple") three-phase relperm model (accepts "simple" and "stone2"). + /// For both size parameters, a 0 or negative value indicates that no spline fitting is to + /// be done, and the input fluid data used directly for linear interpolation. BlackoilPropertiesFromDeck(const EclipseGridParser& deck, const UnstructuredGrid& grid, const parameter::ParameterGroup& param, @@ -164,9 +168,9 @@ namespace Opm double* dpcds) const; - /// Obtain the range of allowable saturation values. - /// In cell cells[i], saturation of phase p is allowed to be - /// in the interval [smin[i*P + p], smax[i*P + p]]. + /// Obtain the range of allowable saturation values. + /// In cell cells[i], saturation of phase p is allowed to be + /// in the interval [smin[i*P + p], smax[i*P + p]]. /// \param[in] n Number of data points. /// \param[in] cells Array of n cell indices. /// \param[out] smin Array of nP minimum s values, array must be valid before calling. @@ -179,7 +183,7 @@ namespace Opm private: RockFromDeck rock_; BlackoilPvtProperties pvt_; - SaturationPropsFromDeck satprops_; + boost::scoped_ptr satprops_; mutable std::vector B_; mutable std::vector dB_; mutable std::vector R_; diff --git a/opm/core/fluid/BlackoilPropertiesInterface.hpp b/opm/core/fluid/BlackoilPropertiesInterface.hpp index dd8a857f..501c8a40 100644 --- a/opm/core/fluid/BlackoilPropertiesInterface.hpp +++ b/opm/core/fluid/BlackoilPropertiesInterface.hpp @@ -138,9 +138,9 @@ namespace Opm double* dpcds) const = 0; - /// Obtain the range of allowable saturation values. - /// In cell cells[i], saturation of phase p is allowed to be - /// in the interval [smin[i*P + p], smax[i*P + p]]. + /// Obtain the range of allowable saturation values. + /// In cell cells[i], saturation of phase p is allowed to be + /// in the interval [smin[i*P + p], smax[i*P + p]]. /// \param[in] n Number of data points. /// \param[in] cells Array of n cell indices. /// \param[out] smin Array of nP minimum s values, array must be valid before calling. diff --git a/opm/core/fluid/IncompPropertiesBasic.cpp b/opm/core/fluid/IncompPropertiesBasic.cpp index 0ce3c88a..ab68017c 100644 --- a/opm/core/fluid/IncompPropertiesBasic.cpp +++ b/opm/core/fluid/IncompPropertiesBasic.cpp @@ -28,22 +28,22 @@ namespace Opm { IncompPropertiesBasic::IncompPropertiesBasic(const parameter::ParameterGroup& param, - const int dim, - const int num_cells) + const int dim, + const int num_cells) { - double poro = param.getDefault("porosity", 1.0); - using namespace Opm::unit; - using namespace Opm::prefix; - double perm = param.getDefault("permeability", 100.0)*milli*darcy; + double poro = param.getDefault("porosity", 1.0); + using namespace Opm::unit; + using namespace Opm::prefix; + double perm = param.getDefault("permeability", 100.0)*milli*darcy; rock_.init(dim, num_cells, poro, perm); - pvt_.init(param); + pvt_.init(param); satprops_.init(param); - if (pvt_.numPhases() != satprops_.numPhases()) { - THROW("IncompPropertiesBasic::IncompPropertiesBasic() - Inconsistent number of phases in pvt data (" - << pvt_.numPhases() << ") and saturation-dependent function data (" << satprops_.numPhases() << ")."); - } - viscosity_.resize(pvt_.numPhases()); - pvt_.mu(1, 0, 0, &viscosity_[0]); + if (pvt_.numPhases() != satprops_.numPhases()) { + THROW("IncompPropertiesBasic::IncompPropertiesBasic() - Inconsistent number of phases in pvt data (" + << pvt_.numPhases() << ") and saturation-dependent function data (" << satprops_.numPhases() << ")."); + } + viscosity_.resize(pvt_.numPhases()); + pvt_.mu(1, 0, 0, &viscosity_[0]); } IncompPropertiesBasic::IncompPropertiesBasic(const int num_phases, @@ -56,14 +56,14 @@ namespace Opm const int num_cells) { rock_.init(dim, num_cells, por, perm); - pvt_.init(num_phases, rho, mu); + pvt_.init(num_phases, rho, mu); satprops_.init(num_phases, relpermfunc); - if (pvt_.numPhases() != satprops_.numPhases()) { - THROW("IncompPropertiesBasic::IncompPropertiesBasic() - Inconsistent number of phases in pvt data (" - << pvt_.numPhases() << ") and saturation-dependent function data (" << satprops_.numPhases() << ")."); - } - viscosity_.resize(pvt_.numPhases()); - pvt_.mu(1, 0, 0, &viscosity_[0]); + if (pvt_.numPhases() != satprops_.numPhases()) { + THROW("IncompPropertiesBasic::IncompPropertiesBasic() - Inconsistent number of phases in pvt data (" + << pvt_.numPhases() << ") and saturation-dependent function data (" << satprops_.numPhases() << ")."); + } + viscosity_.resize(pvt_.numPhases()); + pvt_.mu(1, 0, 0, &viscosity_[0]); } IncompPropertiesBasic::~IncompPropertiesBasic() @@ -109,7 +109,7 @@ namespace Opm /// \return Array of P viscosity values. const double* IncompPropertiesBasic::viscosity() const { - return &viscosity_[0]; + return &viscosity_[0]; } /// \return Array of P density values. @@ -117,7 +117,7 @@ namespace Opm { // No difference between reservoir and surface densities // modelled by this class. - return pvt_.surfaceDensities(); + return pvt_.surfaceDensities(); } /// \return Array of P density values. @@ -125,7 +125,7 @@ namespace Opm { // No difference between reservoir and surface densities // modelled by this class. - return pvt_.surfaceDensities(); + return pvt_.surfaceDensities(); } /// \param[in] n Number of data points. @@ -138,10 +138,10 @@ namespace Opm /// m_{ij} = \frac{dkr_i}{ds^j}, /// and is output in Fortran order (m_00 m_10 m_20 m_01 ...) void IncompPropertiesBasic::relperm(const int n, - const double* s, - const int* /*cells*/, - double* kr, - double* dkrds) const + const double* s, + const int* /*cells*/, + double* kr, + double* dkrds) const { satprops_.relperm(n, s, kr, dkrds); } @@ -157,10 +157,10 @@ namespace Opm /// m_{ij} = \frac{dpc_i}{ds^j}, /// and is output in Fortran order (m_00 m_10 m_20 m_01 ...) void IncompPropertiesBasic::capPress(const int n, - const double* s, - const int* /*cells*/, - double* pc, - double* dpcds) const + const double* s, + const int* /*cells*/, + double* pc, + double* dpcds) const { satprops_.capPress(n, s, pc, dpcds); } @@ -174,11 +174,11 @@ namespace Opm /// \param[out] smin Array of nP minimum s values, array must be valid before calling. /// \param[out] smax Array of nP maximum s values, array must be valid before calling. void IncompPropertiesBasic::satRange(const int n, - const int* /*cells*/, - double* smin, - double* smax) const + const int* /*cells*/, + double* smin, + double* smax) const { - satprops_.satRange(n, smin, smax); + satprops_.satRange(n, smin, smax); } } // namespace Opm diff --git a/opm/core/fluid/IncompPropertiesBasic.hpp b/opm/core/fluid/IncompPropertiesBasic.hpp index b90b0876..72c39b1b 100644 --- a/opm/core/fluid/IncompPropertiesBasic.hpp +++ b/opm/core/fluid/IncompPropertiesBasic.hpp @@ -42,29 +42,29 @@ namespace Opm { public: /// Construct from parameters. - /// The following parameters are accepted (defaults): - /// num_phases (2) Must be 1 or 2. - /// relperm_func ("Linear") Must be "Constant", "Linear" or "Quadratic". - /// rho1 [rho2, rho3] (1.0e3) Density in kg/m^3 - /// mu1 [mu2, mu3] (1.0) Viscosity in cP - /// porosity (1.0) Porosity - /// permeability (100.0) Permeability in mD + /// The following parameters are accepted (defaults): + /// num_phases (2) Must be 1 or 2. + /// relperm_func ("Linear") Must be "Constant", "Linear" or "Quadratic". + /// rho1 [rho2, rho3] (1.0e3) Density in kg/m^3 + /// mu1 [mu2, mu3] (1.0) Viscosity in cP + /// porosity (1.0) Porosity + /// permeability (100.0) Permeability in mD IncompPropertiesBasic(const parameter::ParameterGroup& param, - const int dim, - const int num_cells); + const int dim, + const int num_cells); /// Construct from arguments a basic two phase fluid. IncompPropertiesBasic(const int num_phases, const SaturationPropsBasic::RelPermFunc& relpermfunc, const std::vector& rho, - const std::vector& mu, + const std::vector& mu, const double porosity, const double permeability, const int dim, - const int num_cells); + const int num_cells); - /// Destructor. + /// Destructor. virtual ~IncompPropertiesBasic(); // ---- Rock interface ---- @@ -132,9 +132,9 @@ namespace Opm double* dpcds) const; - /// Obtain the range of allowable saturation values. - /// In cell cells[i], saturation of phase p is allowed to be - /// in the interval [smin[i*P + p], smax[i*P + p]]. + /// Obtain the range of allowable saturation values. + /// In cell cells[i], saturation of phase p is allowed to be + /// in the interval [smin[i*P + p], smax[i*P + p]]. /// \param[in] n Number of data points. /// \param[in] cells Array of n cell indices. /// \param[out] smin Array of nP minimum s values, array must be valid before calling. @@ -145,9 +145,9 @@ namespace Opm double* smax) const; private: RockBasic rock_; - PvtPropertiesBasic pvt_; + PvtPropertiesBasic pvt_; SaturationPropsBasic satprops_; - std::vector viscosity_; + std::vector viscosity_; }; diff --git a/opm/core/fluid/IncompPropertiesFromDeck.cpp b/opm/core/fluid/IncompPropertiesFromDeck.cpp index 570154bf..9aa69098 100644 --- a/opm/core/fluid/IncompPropertiesFromDeck.cpp +++ b/opm/core/fluid/IncompPropertiesFromDeck.cpp @@ -27,15 +27,15 @@ namespace Opm { IncompPropertiesFromDeck::IncompPropertiesFromDeck(const EclipseGridParser& deck, - const UnstructuredGrid& grid) + const UnstructuredGrid& grid) { rock_.init(deck, grid); - pvt_.init(deck); - satprops_.init(deck, grid); - if (pvt_.numPhases() != satprops_.numPhases()) { - THROW("IncompPropertiesFromDeck::IncompPropertiesFromDeck() - Inconsistent number of phases in pvt data (" - << pvt_.numPhases() << ") and saturation-dependent function data (" << satprops_.numPhases() << ")."); - } + pvt_.init(deck); + satprops_.init(deck, grid, 200); + if (pvt_.numPhases() != satprops_.numPhases()) { + THROW("IncompPropertiesFromDeck::IncompPropertiesFromDeck() - Inconsistent number of phases in pvt data (" + << pvt_.numPhases() << ") and saturation-dependent function data (" << satprops_.numPhases() << ")."); + } } IncompPropertiesFromDeck::~IncompPropertiesFromDeck() @@ -81,19 +81,19 @@ namespace Opm /// \return Array of P viscosity values. const double* IncompPropertiesFromDeck::viscosity() const { - return pvt_.viscosity(); + return pvt_.viscosity(); } /// \return Array of P density values. const double* IncompPropertiesFromDeck::density() const { - return pvt_.reservoirDensities(); + return pvt_.reservoirDensities(); } /// \return Array of P density values. const double* IncompPropertiesFromDeck::surfaceDensity() const { - return pvt_.surfaceDensities(); + return pvt_.surfaceDensities(); } /// \param[in] n Number of data points. @@ -106,10 +106,10 @@ namespace Opm /// m_{ij} = \frac{dkr_i}{ds^j}, /// and is output in Fortran order (m_00 m_10 m_20 m_01 ...) void IncompPropertiesFromDeck::relperm(const int n, - const double* s, - const int* cells, - double* kr, - double* dkrds) const + const double* s, + const int* cells, + double* kr, + double* dkrds) const { satprops_.relperm(n, s, cells, kr, dkrds); } @@ -125,10 +125,10 @@ namespace Opm /// m_{ij} = \frac{dpc_i}{ds^j}, /// and is output in Fortran order (m_00 m_10 m_20 m_01 ...) void IncompPropertiesFromDeck::capPress(const int n, - const double* s, - const int* cells, - double* pc, - double* dpcds) const + const double* s, + const int* cells, + double* pc, + double* dpcds) const { satprops_.capPress(n, s, cells, pc, dpcds); } @@ -142,11 +142,11 @@ namespace Opm /// \param[out] smin Array of nP minimum s values, array must be valid before calling. /// \param[out] smax Array of nP maximum s values, array must be valid before calling. void IncompPropertiesFromDeck::satRange(const int n, - const int* cells, - double* smin, - double* smax) const + const int* cells, + double* smin, + double* smax) const { - satprops_.satRange(n, cells, smin, smax); + satprops_.satRange(n, cells, smin, smax); } } // namespace Opm diff --git a/opm/core/fluid/IncompPropertiesFromDeck.hpp b/opm/core/fluid/IncompPropertiesFromDeck.hpp index 68623e5c..6680eaa4 100644 --- a/opm/core/fluid/IncompPropertiesFromDeck.hpp +++ b/opm/core/fluid/IncompPropertiesFromDeck.hpp @@ -47,13 +47,13 @@ namespace Opm public: /// Initialize from deck and grid. /// \param deck Deck input parser - /// \param grid Grid to which property object applies, needed for the + /// \param grid Grid to which property object applies, needed for the /// mapping from cell indices (typically from a processed grid) /// to logical cartesian indices consistent with the deck. IncompPropertiesFromDeck(const EclipseGridParser& deck, - const UnstructuredGrid& grid); + const UnstructuredGrid& grid); - /// Destructor. + /// Destructor. virtual ~IncompPropertiesFromDeck(); // ---- Rock interface ---- @@ -121,9 +121,9 @@ namespace Opm double* dpcds) const; - /// Obtain the range of allowable saturation values. - /// In cell cells[i], saturation of phase p is allowed to be - /// in the interval [smin[i*P + p], smax[i*P + p]]. + /// Obtain the range of allowable saturation values. + /// In cell cells[i], saturation of phase p is allowed to be + /// in the interval [smin[i*P + p], smax[i*P + p]]. /// \param[in] n Number of data points. /// \param[in] cells Array of n cell indices. /// \param[out] smin Array of nP minimum s values, array must be valid before calling. @@ -134,8 +134,8 @@ namespace Opm double* smax) const; private: RockFromDeck rock_; - PvtPropertiesIncompFromDeck pvt_; - SaturationPropsFromDeck satprops_; + PvtPropertiesIncompFromDeck pvt_; + SaturationPropsFromDeck satprops_; }; diff --git a/opm/core/fluid/IncompPropertiesInterface.hpp b/opm/core/fluid/IncompPropertiesInterface.hpp index a5025e8a..d3aaaa7b 100644 --- a/opm/core/fluid/IncompPropertiesInterface.hpp +++ b/opm/core/fluid/IncompPropertiesInterface.hpp @@ -62,7 +62,7 @@ namespace Opm /// \return Array of P viscosity values. virtual const double* viscosity() const = 0; - /// Densities of fluid phases at surface conditions. + /// Densities of fluid phases at reservoir conditions. /// \return Array of P density values. virtual const double* density() const = 0; @@ -109,9 +109,9 @@ namespace Opm double* pc, double* dpcds) const = 0; - /// Obtain the range of allowable saturation values. - /// In cell cells[i], saturation of phase p is allowed to be - /// in the interval [smin[i*P + p], smax[i*P + p]]. + /// Obtain the range of allowable saturation values. + /// In cell cells[i], saturation of phase p is allowed to be + /// in the interval [smin[i*P + p], smax[i*P + p]]. /// \param[in] n Number of data points. /// \param[in] cells Array of n cell indices. /// \param[out] smin Array of nP minimum s values, array must be valid before calling. diff --git a/opm/core/fluid/PvtPropertiesBasic.cpp b/opm/core/fluid/PvtPropertiesBasic.cpp index 3d7d7bb2..5220502a 100644 --- a/opm/core/fluid/PvtPropertiesBasic.cpp +++ b/opm/core/fluid/PvtPropertiesBasic.cpp @@ -34,41 +34,41 @@ namespace Opm void PvtPropertiesBasic::init(const parameter::ParameterGroup& param) { - int num_phases = param.getDefault("num_phases", 2); - if (num_phases > 3 || num_phases < 1) { - THROW("PvtPropertiesBasic::init() illegal num_phases: " << num_phases); - } - density_.resize(num_phases); - viscosity_.resize(num_phases); - // We currently do not allow the user to set B. - formation_volume_factor_.clear(); - formation_volume_factor_.resize(num_phases, 1.0); + int num_phases = param.getDefault("num_phases", 2); + if (num_phases > 3 || num_phases < 1) { + THROW("PvtPropertiesBasic::init() illegal num_phases: " << num_phases); + } + density_.resize(num_phases); + viscosity_.resize(num_phases); + // We currently do not allow the user to set B. + formation_volume_factor_.clear(); + formation_volume_factor_.resize(num_phases, 1.0); - // Setting mu and rho from parameters - using namespace Opm::prefix; - using namespace Opm::unit; - const double kgpm3 = kilogram/cubic(meter); - const double cP = centi*Poise; - std::string rname[3] = { "rho1", "rho2", "rho3" }; - double rdefault[3] = { 1.0e3, 1.0e3, 1.0e3 }; - std::string vname[3] = { "mu1", "mu2", "mu3" }; - double vdefault[3] = { 1.0, 1.0, 1.0 }; - for (int phase = 0; phase < num_phases; ++phase) { - density_[phase] = kgpm3*param.getDefault(rname[phase], rdefault[phase]); - viscosity_[phase] = cP*param.getDefault(vname[phase], vdefault[phase]); - } + // Setting mu and rho from parameters + using namespace Opm::prefix; + using namespace Opm::unit; + const double kgpm3 = kilogram/cubic(meter); + const double cP = centi*Poise; + std::string rname[3] = { "rho1", "rho2", "rho3" }; + double rdefault[3] = { 1.0e3, 1.0e3, 1.0e3 }; + std::string vname[3] = { "mu1", "mu2", "mu3" }; + double vdefault[3] = { 1.0, 1.0, 1.0 }; + for (int phase = 0; phase < num_phases; ++phase) { + density_[phase] = kgpm3*param.getDefault(rname[phase], rdefault[phase]); + viscosity_[phase] = cP*param.getDefault(vname[phase], vdefault[phase]); + } } void PvtPropertiesBasic::init(const int num_phases, const std::vector& rho, const std::vector& visc) { - if (num_phases > 3 || num_phases < 1) { - THROW("PvtPropertiesBasic::init() illegal num_phases: " << num_phases); - } - // We currently do not allow the user to set B. - formation_volume_factor_.clear(); - formation_volume_factor_.resize(num_phases, 1.0); + if (num_phases > 3 || num_phases < 1) { + THROW("PvtPropertiesBasic::init() illegal num_phases: " << num_phases); + } + // We currently do not allow the user to set B. + formation_volume_factor_.clear(); + formation_volume_factor_.resize(num_phases, 1.0); density_ = rho; viscosity_ = visc; } @@ -87,69 +87,69 @@ namespace Opm void PvtPropertiesBasic::mu(const int n, - const double* /*p*/, - const double* /*z*/, - double* output_mu) const + const double* /*p*/, + const double* /*z*/, + double* output_mu) const { - const int np = numPhases(); + const int np = numPhases(); for (int phase = 0; phase < np; ++phase) { // #pragma omp parallel for for (int i = 0; i < n; ++i) { output_mu[np*i + phase] = viscosity_[phase]; } - } + } } void PvtPropertiesBasic::B(const int n, - const double* /*p*/, - const double* /*z*/, - double* output_B) const + const double* /*p*/, + const double* /*z*/, + double* output_B) const { - const int np = numPhases(); + const int np = numPhases(); for (int phase = 0; phase < np; ++phase) { // #pragma omp parallel for for (int i = 0; i < n; ++i) { output_B[np*i + phase] = formation_volume_factor_[phase]; } - } + } } void PvtPropertiesBasic::dBdp(const int n, - const double* /*p*/, - const double* /*z*/, - double* output_B, - double* output_dBdp) const + const double* /*p*/, + const double* /*z*/, + double* output_B, + double* output_dBdp) const { - const int np = numPhases(); + const int np = numPhases(); for (int phase = 0; phase < np; ++phase) { // #pragma omp parallel for for (int i = 0; i < n; ++i) { output_B[np*i + phase] = formation_volume_factor_[phase]; output_dBdp[np*i + phase] = 0.0; } - } + } } void PvtPropertiesBasic::R(const int n, - const double* /*p*/, - const double* /*z*/, - double* output_R) const + const double* /*p*/, + const double* /*z*/, + double* output_R) const { - const int np = numPhases(); - std::fill(output_R, output_R + n*np, 0.0); + const int np = numPhases(); + std::fill(output_R, output_R + n*np, 0.0); } void PvtPropertiesBasic::dRdp(const int n, - const double* /*p*/, - const double* /*z*/, - double* output_R, - double* output_dRdp) const + const double* /*p*/, + const double* /*z*/, + double* output_R, + double* output_dRdp) const { - const int np = numPhases(); - std::fill(output_R, output_R + n*np, 0.0); - std::fill(output_dRdp, output_dRdp + n*np, 0.0); + const int np = numPhases(); + std::fill(output_R, output_R + n*np, 0.0); + std::fill(output_dRdp, output_dRdp + n*np, 0.0); } } // namespace Opm diff --git a/opm/core/fluid/PvtPropertiesBasic.hpp b/opm/core/fluid/PvtPropertiesBasic.hpp index d1a3e9f3..3e30e807 100644 --- a/opm/core/fluid/PvtPropertiesBasic.hpp +++ b/opm/core/fluid/PvtPropertiesBasic.hpp @@ -38,11 +38,11 @@ namespace Opm PvtPropertiesBasic(); /// Initialize from parameters. - /// The following parameters are accepted (defaults): - /// num_phases (2) Must be 1, 2 or 3. - /// rho1 [rho2, rho3] (1.0e3) Density in kg/m^3 - /// mu1 [mu2, mu3] (1.0) Viscosity in cP - void init(const parameter::ParameterGroup& param); + /// The following parameters are accepted (defaults): + /// num_phases (2) Must be 1, 2 or 3. + /// rho1 [rho2, rho3] (1.0e3) Density in kg/m^3 + /// mu1 [mu2, mu3] (1.0) Viscosity in cP + void init(const parameter::ParameterGroup& param); /// Initialize from arguments. /// Basic multi phase fluid pvt properties. @@ -55,7 +55,7 @@ namespace Opm /// Densities of stock components at surface conditions. /// \return Array of size numPhases(). - const double* surfaceDensities() const; + const double* surfaceDensities() const; /// Viscosity as a function of p and z. void mu(const int n, @@ -90,9 +90,9 @@ namespace Opm double* output_dRdp) const; private: - std::vector density_; - std::vector viscosity_; - std::vector formation_volume_factor_; + std::vector density_; + std::vector viscosity_; + std::vector formation_volume_factor_; }; } diff --git a/opm/core/fluid/PvtPropertiesIncompFromDeck.cpp b/opm/core/fluid/PvtPropertiesIncompFromDeck.cpp index 62cc1b23..34a4ba00 100644 --- a/opm/core/fluid/PvtPropertiesIncompFromDeck.cpp +++ b/opm/core/fluid/PvtPropertiesIncompFromDeck.cpp @@ -38,54 +38,54 @@ namespace Opm { typedef std::vector > > table_t; // If we need multiple regions, this class and the SinglePvt* classes must change. - int region_number = 0; + int region_number = 0; PhaseUsage phase_usage = phaseUsageFromDeck(deck); - if (phase_usage.phase_used[PhaseUsage::Vapour] || - !phase_usage.phase_used[PhaseUsage::Aqua] || - !phase_usage.phase_used[PhaseUsage::Liquid]) { - THROW("PvtPropertiesIncompFromDeck::init() -- must have gas and oil phases (only) in deck input.\n"); - } + if (phase_usage.phase_used[PhaseUsage::Vapour] || + !phase_usage.phase_used[PhaseUsage::Aqua] || + !phase_usage.phase_used[PhaseUsage::Liquid]) { + THROW("PvtPropertiesIncompFromDeck::init() -- must have gas and oil phases (only) in deck input.\n"); + } - // Surface densities. Accounting for different orders in eclipse and our code. - if (deck.hasField("DENSITY")) { - const std::vector& d = deck.getDENSITY().densities_[region_number]; - enum { ECL_oil = 0, ECL_water = 1, ECL_gas = 2 }; - surface_density_[phase_usage.phase_pos[PhaseUsage::Aqua]] = d[ECL_water]; - surface_density_[phase_usage.phase_pos[PhaseUsage::Liquid]] = d[ECL_oil]; - } else { - THROW("Input is missing DENSITY\n"); - } + // Surface densities. Accounting for different orders in eclipse and our code. + if (deck.hasField("DENSITY")) { + const std::vector& d = deck.getDENSITY().densities_[region_number]; + enum { ECL_oil = 0, ECL_water = 1, ECL_gas = 2 }; + surface_density_[phase_usage.phase_pos[PhaseUsage::Aqua]] = d[ECL_water]; + surface_density_[phase_usage.phase_pos[PhaseUsage::Liquid]] = d[ECL_oil]; + } else { + THROW("Input is missing DENSITY\n"); + } // Make reservoir densities the same as surface densities initially. // We will modify them with formation volume factors if found. reservoir_density_ = surface_density_; // Water viscosity. - if (deck.hasField("PVTW")) { - const std::vector& pvtw = deck.getPVTW().pvtw_[region_number]; - if (pvtw[2] != 0.0 || pvtw[4] != 0.0) { - MESSAGE("Compressibility effects in PVTW are ignored."); - } + if (deck.hasField("PVTW")) { + const std::vector& pvtw = deck.getPVTW().pvtw_[region_number]; + if (pvtw[2] != 0.0 || pvtw[4] != 0.0) { + MESSAGE("Compressibility effects in PVTW are ignored."); + } reservoir_density_[phase_usage.phase_pos[PhaseUsage::Aqua]] /= pvtw[1]; - viscosity_[phase_usage.phase_pos[PhaseUsage::Aqua]] = pvtw[3]; - } else { - // Eclipse 100 default. - // viscosity_[phase_usage.phase_pos[PhaseUsage::Aqua]] = 0.5*Opm::prefix::centi*Opm::unit::Poise; - THROW("Input is missing PVTW\n"); - } + viscosity_[phase_usage.phase_pos[PhaseUsage::Aqua]] = pvtw[3]; + } else { + // Eclipse 100 default. + // viscosity_[phase_usage.phase_pos[PhaseUsage::Aqua]] = 0.5*Opm::prefix::centi*Opm::unit::Poise; + THROW("Input is missing PVTW\n"); + } // Oil viscosity. - if (deck.hasField("PVCDO")) { - const std::vector& pvcdo = deck.getPVCDO().pvcdo_[region_number]; - if (pvcdo[2] != 0.0 || pvcdo[4] != 0.0) { - MESSAGE("Compressibility effects in PVCDO are ignored."); - } + if (deck.hasField("PVCDO")) { + const std::vector& pvcdo = deck.getPVCDO().pvcdo_[region_number]; + if (pvcdo[2] != 0.0 || pvcdo[4] != 0.0) { + MESSAGE("Compressibility effects in PVCDO are ignored."); + } reservoir_density_[phase_usage.phase_pos[PhaseUsage::Liquid]] /= pvcdo[1]; - viscosity_[phase_usage.phase_pos[PhaseUsage::Liquid]] = pvcdo[3]; - } else { - THROW("Input is missing PVCDO\n"); - } + viscosity_[phase_usage.phase_pos[PhaseUsage::Liquid]] = pvcdo[3]; + } else { + THROW("Input is missing PVCDO\n"); + } } const double* PvtPropertiesIncompFromDeck::surfaceDensities() const diff --git a/opm/core/fluid/PvtPropertiesIncompFromDeck.hpp b/opm/core/fluid/PvtPropertiesIncompFromDeck.hpp index acbabaa4..01bbeed1 100644 --- a/opm/core/fluid/PvtPropertiesIncompFromDeck.hpp +++ b/opm/core/fluid/PvtPropertiesIncompFromDeck.hpp @@ -39,14 +39,14 @@ namespace Opm PvtPropertiesIncompFromDeck(); /// Initialize from deck. - void init(const EclipseGridParser& deck); + void init(const EclipseGridParser& deck); /// Number of active phases. int numPhases() const; /// Densities of stock components at surface conditions. /// \return Array of size numPhases(). - const double* surfaceDensities() const; + const double* surfaceDensities() const; /// Densities of stock components at reservoir conditions. /// Note: a reasonable question to ask is why there can be @@ -58,15 +58,15 @@ namespace Opm /// reporting and using data given in terms of surface values, /// we need to handle this difference. /// \return Array of size numPhases(). - const double* reservoirDensities() const; + const double* reservoirDensities() const; /// Viscosities. const double* viscosity() const; private: - std::tr1::array surface_density_; - std::tr1::array reservoir_density_; - std::tr1::array viscosity_; + std::tr1::array surface_density_; + std::tr1::array reservoir_density_; + std::tr1::array viscosity_; }; } diff --git a/opm/core/fluid/RockBasic.cpp b/opm/core/fluid/RockBasic.cpp index a1423b76..b66cccce 100644 --- a/opm/core/fluid/RockBasic.cpp +++ b/opm/core/fluid/RockBasic.cpp @@ -25,29 +25,29 @@ namespace Opm /// Default constructor. RockBasic::RockBasic() - : dimensions_(-1) + : dimensions_(-1) { } /// Initialize with homogenous porosity and permeability. void RockBasic::init(const int dimensions, - const int num_cells, - const double poro, - const double perm) + const int num_cells, + const double poro, + const double perm) { - dimensions_ = dimensions; - porosity_.clear(); - porosity_.resize(num_cells, poro); - permeability_.clear(); - const int dsq = dimensions*dimensions; - permeability_.resize(num_cells*dsq, 0.0); + dimensions_ = dimensions; + porosity_.clear(); + porosity_.resize(num_cells, poro); + permeability_.clear(); + const int dsq = dimensions*dimensions; + permeability_.resize(num_cells*dsq, 0.0); // #pragma omp parallel for - for (int i = 0; i < num_cells; ++i) { - for (int d = 0; d < dimensions; ++d) { - permeability_[dsq*i + dimensions*d + d] = perm; - } - } + for (int i = 0; i < num_cells; ++i) { + for (int d = 0; d < dimensions; ++d) { + permeability_[dsq*i + dimensions*d + d] = perm; + } + } } diff --git a/opm/core/fluid/RockBasic.hpp b/opm/core/fluid/RockBasic.hpp index 96563780..2adc3ffd 100644 --- a/opm/core/fluid/RockBasic.hpp +++ b/opm/core/fluid/RockBasic.hpp @@ -35,9 +35,9 @@ namespace Opm /// Initialize with homogenous porosity and permeability. void init(const int dimensions, - const int num_cells, - const double poro, - const double perm); + const int num_cells, + const double poro, + const double perm); /// \return D, the number of spatial dimensions. int numDimensions() const @@ -66,7 +66,7 @@ namespace Opm } private: - int dimensions_; + int dimensions_; std::vector porosity_; std::vector permeability_; }; diff --git a/opm/core/fluid/RockCompressibility.cpp b/opm/core/fluid/RockCompressibility.cpp index 10486101..af65d852 100644 --- a/opm/core/fluid/RockCompressibility.cpp +++ b/opm/core/fluid/RockCompressibility.cpp @@ -69,8 +69,8 @@ namespace Opm const double cpnorm = rock_comp_*(pressure - pref_); return (1.0 + cpnorm + 0.5*cpnorm*cpnorm); } else { - // return Opm::linearInterpolation(p_, poromult_, pressure); - return Opm::linearInterpolationExtrap(p_, poromult_, pressure); + // return Opm::linearInterpolation(p_, poromult_, pressure); + return Opm::linearInterpolationExtrap(p_, poromult_, pressure); } } @@ -81,7 +81,7 @@ namespace Opm } else { //const double poromult = Opm::linearInterpolation(p_, poromult_, pressure); //const double dporomultdp = Opm::linearInterpolationDerivative(p_, poromult_, pressure); - const double poromult = Opm::linearInterpolationExtrap(p_, poromult_, pressure); + const double poromult = Opm::linearInterpolationExtrap(p_, poromult_, pressure); const double dporomultdp = Opm::linearInterpolationDerivativeExtrap(p_, poromult_, pressure); return dporomultdp/poromult; diff --git a/opm/core/fluid/RockFromDeck.cpp b/opm/core/fluid/RockFromDeck.cpp index 7ba8903f..ec7db9a6 100644 --- a/opm/core/fluid/RockFromDeck.cpp +++ b/opm/core/fluid/RockFromDeck.cpp @@ -51,7 +51,7 @@ namespace Opm /// Initialize from deck and cell mapping. /// \param deck Deck input parser - /// \param grid grid to which property object applies, needed for the + /// \param grid grid to which property object applies, needed for the /// mapping from cell indices (typically from a processed grid) /// to logical cartesian indices consistent with the deck. void RockFromDeck::init(const EclipseGridParser& deck, diff --git a/opm/core/fluid/RockFromDeck.hpp b/opm/core/fluid/RockFromDeck.hpp index 65027e42..63e71a6e 100644 --- a/opm/core/fluid/RockFromDeck.hpp +++ b/opm/core/fluid/RockFromDeck.hpp @@ -37,7 +37,7 @@ namespace Opm /// Initialize from deck and grid. /// \param deck Deck input parser - /// \param grid Grid to which property object applies, needed for the + /// \param grid Grid to which property object applies, needed for the /// mapping from cell indices (typically from a processed grid) /// to logical cartesian indices consistent with the deck. void init(const EclipseGridParser& deck, diff --git a/opm/core/fluid/SatFuncGwseg.cpp b/opm/core/fluid/SatFuncGwseg.cpp new file mode 100644 index 00000000..cd97e9af --- /dev/null +++ b/opm/core/fluid/SatFuncGwseg.cpp @@ -0,0 +1,440 @@ +/* + Copyright 2012 SINTEF ICT, Applied Mathematics. + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Opm +{ + + + + + void SatFuncGwsegUniform::init(const EclipseGridParser& deck, + const int table_num, + const PhaseUsage phase_usg, + const int samples) + { + phase_usage = phase_usg; + double swco = 0.0; + double swmax = 1.0; + if (phase_usage.phase_used[Aqua]) { + const SWOF::table_t& swof_table = deck.getSWOF().swof_; + const std::vector& sw = swof_table[table_num][0]; + const std::vector& krw = swof_table[table_num][1]; + const std::vector& krow = swof_table[table_num][2]; + const std::vector& pcow = swof_table[table_num][3]; + buildUniformMonotoneTable(sw, krw, samples, krw_); + buildUniformMonotoneTable(sw, krow, samples, krow_); + buildUniformMonotoneTable(sw, pcow, samples, pcow_); + krocw_ = krow[0]; // At connate water -> ecl. SWOF + swco = sw[0]; + smin_[phase_usage.phase_pos[Aqua]] = sw[0]; + swmax = sw.back(); + smax_[phase_usage.phase_pos[Aqua]] = sw.back(); + } + if (phase_usage.phase_used[Vapour]) { + const SGOF::table_t& sgof_table = deck.getSGOF().sgof_; + const std::vector& sg = sgof_table[table_num][0]; + const std::vector& krg = sgof_table[table_num][1]; + const std::vector& krog = sgof_table[table_num][2]; + const std::vector& pcog = sgof_table[table_num][3]; + buildUniformMonotoneTable(sg, krg, samples, krg_); + buildUniformMonotoneTable(sg, krog, samples, krog_); + buildUniformMonotoneTable(sg, pcog, samples, pcog_); + smin_[phase_usage.phase_pos[Vapour]] = sg[0]; + if (std::fabs(sg.back() + swco - 1.0) > 1e-3) { + THROW("Gas maximum saturation in SGOF table = " << sg.back() << + ", should equal (1.0 - connate water sat) = " << (1.0 - swco)); + } + smax_[phase_usage.phase_pos[Vapour]] = sg.back(); + } + // These only consider water min/max sats. Consider gas sats? + smin_[phase_usage.phase_pos[Liquid]] = 1.0 - swmax; + smax_[phase_usage.phase_pos[Liquid]] = 1.0 - swco; + } + + + void SatFuncGwsegUniform::evalKr(const double* s, double* kr) const + { + if (phase_usage.num_phases == 3) { + // Relative permeability model based on segregation of water + // and gas, with oil present in both water and gas zones. + const double swco = smin_[phase_usage.phase_pos[Aqua]]; + const double sw = std::max(s[Aqua], swco); + const double sg = s[Vapour]; + // xw and xg are the fractions occupied by water and gas zones. + const double eps = 1e-6; + const double xw = (sw - swco) / std::max(sg + sw - swco, eps); + const double xg = 1 - xw; + const double ssw = sg + sw; + const double ssg = sw - swco + sg; + const double krw = krw_(ssw); + const double krg = krg_(ssg); + const double krow = krow_(ssw); + const double krog = krog_(ssg); + kr[Aqua] = xw*krw; + kr[Vapour] = xg*krg; + kr[Liquid] = xw*krow + xg*krog; + return; + } + // We have a two-phase situation. We know that oil is active. + if (phase_usage.phase_used[Aqua]) { + int wpos = phase_usage.phase_pos[Aqua]; + int opos = phase_usage.phase_pos[Liquid]; + double sw = s[wpos]; + double krw = krw_(sw); + double krow = krow_(sw); + kr[wpos] = krw; + kr[opos] = krow; + } else { + ASSERT(phase_usage.phase_used[Vapour]); + int gpos = phase_usage.phase_pos[Vapour]; + int opos = phase_usage.phase_pos[Liquid]; + double sg = s[gpos]; + double krg = krg_(sg); + double krog = krog_(sg); + kr[gpos] = krg; + kr[opos] = krog; + } + } + + + void SatFuncGwsegUniform::evalKrDeriv(const double* s, double* kr, double* dkrds) const + { + const int np = phase_usage.num_phases; + std::fill(dkrds, dkrds + np*np, 0.0); + + if (np == 3) { + // Relative permeability model based on segregation of water + // and gas, with oil present in both water and gas zones. + const double swco = smin_[phase_usage.phase_pos[Aqua]]; + const double sw = std::max(s[Aqua], swco); + const double sg = s[Vapour]; + // xw and xg are the fractions occupied by water and gas zones. + const double eps = 1e-6; + const double xw = (sw - swco) / std::max(sg + sw - swco, eps); + const double xg = 1 - xw; + const double ssw = sg + sw; + const double ssg = sw - swco + sg; + const double krw = krw_(ssw); + const double krg = krg_(ssg); + const double krow = krow_(ssw); + const double krog = krog_(ssg); + kr[Aqua] = xw*krw; + kr[Vapour] = xg*krg; + kr[Liquid] = xw*krow + xg*krog; + + // Derivatives. + const double dkrww = krw_.derivative(ssw); + const double dkrgg = krg_.derivative(ssg); + const double dkrow = krow_.derivative(ssw); + const double dkrog = krog_.derivative(ssg); + const double d = ssg; // = sw - swco + sg (using 'd' for consistency with mrst docs). + dkrds[Aqua + Aqua*np] = (xg/d)*krw + xw*dkrww; + dkrds[Aqua + Vapour*np] = -(xw/d)*krw + xw*dkrww; + dkrds[Liquid + Aqua*np] = (xg/d)*krow + xw*dkrow - (xg/d)*krog + xg*dkrog; + dkrds[Liquid + Vapour*np] = -(xw/d)*krow + xw*dkrow + (xw/d)*krog + xg*dkrog; + dkrds[Vapour + Aqua*np] = -(xg/d)*krg + xg*dkrgg; + dkrds[Vapour + Vapour*np] = (xw/d)*krg + xg*dkrgg; + return; + } + // We have a two-phase situation. We know that oil is active. + if (phase_usage.phase_used[Aqua]) { + int wpos = phase_usage.phase_pos[Aqua]; + int opos = phase_usage.phase_pos[Liquid]; + double sw = s[wpos]; + double krw = krw_(sw); + double dkrww = krw_.derivative(sw); + double krow = krow_(sw); + double dkrow = krow_.derivative(sw); + kr[wpos] = krw; + kr[opos] = krow; + dkrds[wpos + wpos*np] = dkrww; + dkrds[opos + wpos*np] = dkrow; // Row opos, column wpos, fortran order. + } else { + ASSERT(phase_usage.phase_used[Vapour]); + int gpos = phase_usage.phase_pos[Vapour]; + int opos = phase_usage.phase_pos[Liquid]; + double sg = s[gpos]; + double krg = krg_(sg); + double dkrgg = krg_.derivative(sg); + double krog = krog_(sg); + double dkrog = krog_.derivative(sg); + kr[gpos] = krg; + kr[opos] = krog; + dkrds[gpos + gpos*np] = dkrgg; + dkrds[opos + gpos*np] = dkrog; + } + + } + + + void SatFuncGwsegUniform::evalPc(const double* s, double* pc) const + { + pc[phase_usage.phase_pos[Liquid]] = 0.0; + if (phase_usage.phase_used[Aqua]) { + int pos = phase_usage.phase_pos[Aqua]; + pc[pos] = pcow_(s[pos]); + } + if (phase_usage.phase_used[Vapour]) { + int pos = phase_usage.phase_pos[Vapour]; + pc[pos] = pcog_(s[pos]); + } + } + + void SatFuncGwsegUniform::evalPcDeriv(const double* s, double* pc, double* dpcds) const + { + // The problem of determining three-phase capillary pressures + // is very hard experimentally, usually one extends two-phase + // data (as for relative permeability). + // In our approach the derivative matrix is quite sparse, only + // the diagonal elements corresponding to non-oil phases are + // (potentially) nonzero. + const int np = phase_usage.num_phases; + std::fill(dpcds, dpcds + np*np, 0.0); + pc[phase_usage.phase_pos[Liquid]] = 0.0; + if (phase_usage.phase_used[Aqua]) { + int pos = phase_usage.phase_pos[Aqua]; + pc[pos] = pcow_(s[pos]); + dpcds[np*pos + pos] = pcow_.derivative(s[pos]); + } + if (phase_usage.phase_used[Vapour]) { + int pos = phase_usage.phase_pos[Vapour]; + pc[pos] = pcog_(s[pos]); + dpcds[np*pos + pos] = pcog_.derivative(s[pos]); + } + } + + + + + // ====== Methods for SatFuncGwsegNonuniform ====== + + + + + + void SatFuncGwsegNonuniform::init(const EclipseGridParser& deck, + const int table_num, + const PhaseUsage phase_usg, + const int /*samples*/) + { + phase_usage = phase_usg; + double swco = 0.0; + double swmax = 1.0; + if (phase_usage.phase_used[Aqua]) { + const SWOF::table_t& swof_table = deck.getSWOF().swof_; + const std::vector& sw = swof_table[table_num][0]; + const std::vector& krw = swof_table[table_num][1]; + const std::vector& krow = swof_table[table_num][2]; + const std::vector& pcow = swof_table[table_num][3]; + krw_ = NonuniformTableLinear(sw, krw); + krow_ = NonuniformTableLinear(sw, krow); + pcow_ = NonuniformTableLinear(sw, pcow); + krocw_ = krow[0]; // At connate water -> ecl. SWOF + swco = sw[0]; + smin_[phase_usage.phase_pos[Aqua]] = sw[0]; + swmax = sw.back(); + smax_[phase_usage.phase_pos[Aqua]] = sw.back(); + } + if (phase_usage.phase_used[Vapour]) { + const SGOF::table_t& sgof_table = deck.getSGOF().sgof_; + const std::vector& sg = sgof_table[table_num][0]; + const std::vector& krg = sgof_table[table_num][1]; + const std::vector& krog = sgof_table[table_num][2]; + const std::vector& pcog = sgof_table[table_num][3]; + krg_ = NonuniformTableLinear(sg, krg); + krog_ = NonuniformTableLinear(sg, krog); + pcog_ = NonuniformTableLinear(sg, pcog); + smin_[phase_usage.phase_pos[Vapour]] = sg[0]; + if (std::fabs(sg.back() + swco - 1.0) > 1e-3) { + THROW("Gas maximum saturation in SGOF table = " << sg.back() << + ", should equal (1.0 - connate water sat) = " << (1.0 - swco)); + } + smax_[phase_usage.phase_pos[Vapour]] = sg.back(); + } + // These only consider water min/max sats. Consider gas sats? + smin_[phase_usage.phase_pos[Liquid]] = 1.0 - swmax; + smax_[phase_usage.phase_pos[Liquid]] = 1.0 - swco; + } + + + void SatFuncGwsegNonuniform::evalKr(const double* s, double* kr) const + { + if (phase_usage.num_phases == 3) { + // Relative permeability model based on segregation of water + // and gas, with oil present in both water and gas zones. + const double swco = smin_[phase_usage.phase_pos[Aqua]]; + const double sw = std::max(s[Aqua], swco); + const double sg = s[Vapour]; + // xw and xg are the fractions occupied by water and gas zones. + const double eps = 1e-6; + const double xw = (sw - swco) / std::max(sg + sw - swco, eps); + const double xg = 1 - xw; + const double ssw = sg + sw; + const double ssg = sw - swco + sg; + const double krw = krw_(ssw); + const double krg = krg_(ssg); + const double krow = krow_(ssw); + const double krog = krog_(ssg); + kr[Aqua] = xw*krw; + kr[Vapour] = xg*krg; + kr[Liquid] = xw*krow + xg*krog; + return; + } + // We have a two-phase situation. We know that oil is active. + if (phase_usage.phase_used[Aqua]) { + int wpos = phase_usage.phase_pos[Aqua]; + int opos = phase_usage.phase_pos[Liquid]; + double sw = s[wpos]; + double krw = krw_(sw); + double krow = krow_(sw); + kr[wpos] = krw; + kr[opos] = krow; + } else { + ASSERT(phase_usage.phase_used[Vapour]); + int gpos = phase_usage.phase_pos[Vapour]; + int opos = phase_usage.phase_pos[Liquid]; + double sg = s[gpos]; + double krg = krg_(sg); + double krog = krog_(sg); + kr[gpos] = krg; + kr[opos] = krog; + } + } + + + void SatFuncGwsegNonuniform::evalKrDeriv(const double* s, double* kr, double* dkrds) const + { + const int np = phase_usage.num_phases; + std::fill(dkrds, dkrds + np*np, 0.0); + + if (np == 3) { + // Relative permeability model based on segregation of water + // and gas, with oil present in both water and gas zones. + const double swco = smin_[phase_usage.phase_pos[Aqua]]; + const double sw = std::max(s[Aqua], swco); + const double sg = s[Vapour]; + // xw and xg are the fractions occupied by water and gas zones. + const double eps = 1e-6; + const double xw = (sw - swco) / std::max(sg + sw - swco, eps); + const double xg = 1 - xw; + const double ssw = sg + sw; + const double ssg = sw - swco + sg; + const double krw = krw_(ssw); + const double krg = krg_(ssg); + const double krow = krow_(ssw); + const double krog = krog_(ssg); + kr[Aqua] = xw*krw; + kr[Vapour] = xg*krg; + kr[Liquid] = xw*krow + xg*krog; + + // Derivatives. + const double dkrww = krw_.derivative(ssw); + const double dkrgg = krg_.derivative(ssg); + const double dkrow = krow_.derivative(ssw); + const double dkrog = krog_.derivative(ssg); + const double d = ssg; // = sw - swco + sg (using 'd' for consistency with mrst docs). + dkrds[Aqua + Aqua*np] = (xg/d)*krw + xw*dkrww; + dkrds[Aqua + Vapour*np] = -(xw/d)*krw + xw*dkrww; + dkrds[Liquid + Aqua*np] = (xg/d)*krow + xw*dkrow - (xg/d)*krog + xg*dkrog; + dkrds[Liquid + Vapour*np] = -(xw/d)*krow + xw*dkrow + (xw/d)*krog + xg*dkrog; + dkrds[Vapour + Aqua*np] = -(xg/d)*krg + xg*dkrgg; + dkrds[Vapour + Vapour*np] = (xw/d)*krg + xg*dkrgg; + return; + } + // We have a two-phase situation. We know that oil is active. + if (phase_usage.phase_used[Aqua]) { + int wpos = phase_usage.phase_pos[Aqua]; + int opos = phase_usage.phase_pos[Liquid]; + double sw = s[wpos]; + double krw = krw_(sw); + double dkrww = krw_.derivative(sw); + double krow = krow_(sw); + double dkrow = krow_.derivative(sw); + kr[wpos] = krw; + kr[opos] = krow; + dkrds[wpos + wpos*np] = dkrww; + dkrds[opos + wpos*np] = dkrow; // Row opos, column wpos, fortran order. + } else { + ASSERT(phase_usage.phase_used[Vapour]); + int gpos = phase_usage.phase_pos[Vapour]; + int opos = phase_usage.phase_pos[Liquid]; + double sg = s[gpos]; + double krg = krg_(sg); + double dkrgg = krg_.derivative(sg); + double krog = krog_(sg); + double dkrog = krog_.derivative(sg); + kr[gpos] = krg; + kr[opos] = krog; + dkrds[gpos + gpos*np] = dkrgg; + dkrds[opos + gpos*np] = dkrog; + } + + } + + + void SatFuncGwsegNonuniform::evalPc(const double* s, double* pc) const + { + pc[phase_usage.phase_pos[Liquid]] = 0.0; + if (phase_usage.phase_used[Aqua]) { + int pos = phase_usage.phase_pos[Aqua]; + pc[pos] = pcow_(s[pos]); + } + if (phase_usage.phase_used[Vapour]) { + int pos = phase_usage.phase_pos[Vapour]; + pc[pos] = pcog_(s[pos]); + } + } + + void SatFuncGwsegNonuniform::evalPcDeriv(const double* s, double* pc, double* dpcds) const + { + // The problem of determining three-phase capillary pressures + // is very hard experimentally, usually one extends two-phase + // data (as for relative permeability). + // In our approach the derivative matrix is quite sparse, only + // the diagonal elements corresponding to non-oil phases are + // (potentially) nonzero. + const int np = phase_usage.num_phases; + std::fill(dpcds, dpcds + np*np, 0.0); + pc[phase_usage.phase_pos[Liquid]] = 0.0; + if (phase_usage.phase_used[Aqua]) { + int pos = phase_usage.phase_pos[Aqua]; + pc[pos] = pcow_(s[pos]); + dpcds[np*pos + pos] = pcow_.derivative(s[pos]); + } + if (phase_usage.phase_used[Vapour]) { + int pos = phase_usage.phase_pos[Vapour]; + pc[pos] = pcog_(s[pos]); + dpcds[np*pos + pos] = pcog_.derivative(s[pos]); + } + } + + + + + +} // namespace Opm diff --git a/opm/core/fluid/SatFuncGwseg.hpp b/opm/core/fluid/SatFuncGwseg.hpp new file mode 100644 index 00000000..1932e7f0 --- /dev/null +++ b/opm/core/fluid/SatFuncGwseg.hpp @@ -0,0 +1,80 @@ +/* + Copyright 2012 SINTEF ICT, Applied Mathematics. + + 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 . +*/ +#ifndef SATFUNCGWSEG_HPP +#define SATFUNCGWSEG_HPP + +#include +#include +#include +#include +#include + +namespace Opm +{ + class SatFuncGwsegUniform : public BlackoilPhases + { + public: + void init(const EclipseGridParser& deck, + const int table_num, + const PhaseUsage phase_usg, + const int samples); + void evalKr(const double* s, double* kr) const; + void evalKrDeriv(const double* s, double* kr, double* dkrds) const; + void evalPc(const double* s, double* pc) const; + void evalPcDeriv(const double* s, double* pc, double* dpcds) const; + double smin_[PhaseUsage::MaxNumPhases]; + double smax_[PhaseUsage::MaxNumPhases]; + private: + PhaseUsage phase_usage; // A copy of the outer class' phase_usage_. + UniformTableLinear krw_; + UniformTableLinear krow_; + UniformTableLinear pcow_; + UniformTableLinear krg_; + UniformTableLinear krog_; + UniformTableLinear pcog_; + double krocw_; // = krow_(s_wc) + }; + + + class SatFuncGwsegNonuniform : public BlackoilPhases + { + public: + void init(const EclipseGridParser& deck, + const int table_num, + const PhaseUsage phase_usg, + const int samples); + void evalKr(const double* s, double* kr) const; + void evalKrDeriv(const double* s, double* kr, double* dkrds) const; + void evalPc(const double* s, double* pc) const; + void evalPcDeriv(const double* s, double* pc, double* dpcds) const; + double smin_[PhaseUsage::MaxNumPhases]; + double smax_[PhaseUsage::MaxNumPhases]; + private: + PhaseUsage phase_usage; // A copy of the outer class' phase_usage_. + NonuniformTableLinear krw_; + NonuniformTableLinear krow_; + NonuniformTableLinear pcow_; + NonuniformTableLinear krg_; + NonuniformTableLinear krog_; + NonuniformTableLinear pcog_; + double krocw_; // = krow_(s_wc) + }; + +} // namespace Opm +#endif // SATFUNCGWSEG_HPP diff --git a/opm/core/fluid/SatFuncSimple.cpp b/opm/core/fluid/SatFuncSimple.cpp index 740af43c..7e226a4a 100644 --- a/opm/core/fluid/SatFuncSimple.cpp +++ b/opm/core/fluid/SatFuncSimple.cpp @@ -1,3 +1,22 @@ +/* + Copyright 2012 SINTEF ICT, Applied Mathematics. + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ + #include #include #include @@ -6,21 +25,14 @@ #include #include #include + namespace Opm { - void SatFuncSimple::init(const EclipseGridParser& deck, - const int table_num, - const PhaseUsage phase_usg) - { - init(deck, table_num, phase_usg, 200); - } - - - void SatFuncSimple::init(const EclipseGridParser& deck, + void SatFuncSimpleUniform::init(const EclipseGridParser& deck, const int table_num, const PhaseUsage phase_usg, const int samples) @@ -65,7 +77,7 @@ namespace Opm } - void SatFuncSimple::evalKr(const double* s, double* kr) const + void SatFuncSimpleUniform::evalKr(const double* s, double* kr) const { if (phase_usage.num_phases == 3) { // A simplified relative permeability model. @@ -106,7 +118,7 @@ namespace Opm } - void SatFuncSimple::evalKrDeriv(const double* s, double* kr, double* dkrds) const + void SatFuncSimpleUniform::evalKrDeriv(const double* s, double* kr, double* dkrds) const { const int np = phase_usage.num_phases; std::fill(dkrds, dkrds + np*np, 0.0); @@ -172,7 +184,7 @@ namespace Opm } - void SatFuncSimple::evalPc(const double* s, double* pc) const + void SatFuncSimpleUniform::evalPc(const double* s, double* pc) const { pc[phase_usage.phase_pos[Liquid]] = 0.0; if (phase_usage.phase_used[Aqua]) { @@ -185,7 +197,206 @@ namespace Opm } } - void SatFuncSimple::evalPcDeriv(const double* s, double* pc, double* dpcds) const + void SatFuncSimpleUniform::evalPcDeriv(const double* s, double* pc, double* dpcds) const + { + // The problem of determining three-phase capillary pressures + // is very hard experimentally, usually one extends two-phase + // data (as for relative permeability). + // In our approach the derivative matrix is quite sparse, only + // the diagonal elements corresponding to non-oil phases are + // (potentially) nonzero. + const int np = phase_usage.num_phases; + std::fill(dpcds, dpcds + np*np, 0.0); + pc[phase_usage.phase_pos[Liquid]] = 0.0; + if (phase_usage.phase_used[Aqua]) { + int pos = phase_usage.phase_pos[Aqua]; + pc[pos] = pcow_(s[pos]); + dpcds[np*pos + pos] = pcow_.derivative(s[pos]); + } + if (phase_usage.phase_used[Vapour]) { + int pos = phase_usage.phase_pos[Vapour]; + pc[pos] = pcog_(s[pos]); + dpcds[np*pos + pos] = pcog_.derivative(s[pos]); + } + } + + + + + + // ====== Methods for SatFuncSimpleNonuniform ====== + + + + + + + void SatFuncSimpleNonuniform::init(const EclipseGridParser& deck, + const int table_num, + const PhaseUsage phase_usg, + const int /*samples*/) + { + phase_usage = phase_usg; + double swco = 0.0; + double swmax = 1.0; + if (phase_usage.phase_used[Aqua]) { + const SWOF::table_t& swof_table = deck.getSWOF().swof_; + const std::vector& sw = swof_table[table_num][0]; + const std::vector& krw = swof_table[table_num][1]; + const std::vector& krow = swof_table[table_num][2]; + const std::vector& pcow = swof_table[table_num][3]; + krw_ = NonuniformTableLinear(sw, krw); + krow_ = NonuniformTableLinear(sw, krow); + pcow_ = NonuniformTableLinear(sw, pcow); + krocw_ = krow[0]; // At connate water -> ecl. SWOF + swco = sw[0]; + smin_[phase_usage.phase_pos[Aqua]] = sw[0]; + swmax = sw.back(); + smax_[phase_usage.phase_pos[Aqua]] = sw.back(); + } + if (phase_usage.phase_used[Vapour]) { + const SGOF::table_t& sgof_table = deck.getSGOF().sgof_; + const std::vector& sg = sgof_table[table_num][0]; + const std::vector& krg = sgof_table[table_num][1]; + const std::vector& krog = sgof_table[table_num][2]; + const std::vector& pcog = sgof_table[table_num][3]; + krg_ = NonuniformTableLinear(sg, krg); + krog_ = NonuniformTableLinear(sg, krog); + pcog_ = NonuniformTableLinear(sg, pcog); + smin_[phase_usage.phase_pos[Vapour]] = sg[0]; + if (std::fabs(sg.back() + swco - 1.0) > 1e-3) { + THROW("Gas maximum saturation in SGOF table = " << sg.back() << + ", should equal (1.0 - connate water sat) = " << (1.0 - swco)); + } + smax_[phase_usage.phase_pos[Vapour]] = sg.back(); + } + // These only consider water min/max sats. Consider gas sats? + smin_[phase_usage.phase_pos[Liquid]] = 1.0 - swmax; + smax_[phase_usage.phase_pos[Liquid]] = 1.0 - swco; + } + + + void SatFuncSimpleNonuniform::evalKr(const double* s, double* kr) const + { + if (phase_usage.num_phases == 3) { + // A simplified relative permeability model. + double sw = s[Aqua]; + double sg = s[Vapour]; + double krw = krw_(sw); + double krg = krg_(sg); + double krow = krow_(sw + sg); // = 1 - so + // double krog = krog_(sg); // = 1 - so - sw + // double krocw = krocw_; + kr[Aqua] = krw; + kr[Vapour] = krg; + kr[Liquid] = krow; + if (kr[Liquid] < 0.0) { + kr[Liquid] = 0.0; + } + return; + } + // We have a two-phase situation. We know that oil is active. + if (phase_usage.phase_used[Aqua]) { + int wpos = phase_usage.phase_pos[Aqua]; + int opos = phase_usage.phase_pos[Liquid]; + double sw = s[wpos]; + double krw = krw_(sw); + double krow = krow_(sw); + kr[wpos] = krw; + kr[opos] = krow; + } else { + ASSERT(phase_usage.phase_used[Vapour]); + int gpos = phase_usage.phase_pos[Vapour]; + int opos = phase_usage.phase_pos[Liquid]; + double sg = s[gpos]; + double krg = krg_(sg); + double krog = krog_(sg); + kr[gpos] = krg; + kr[opos] = krog; + } + } + + + void SatFuncSimpleNonuniform::evalKrDeriv(const double* s, double* kr, double* dkrds) const + { + const int np = phase_usage.num_phases; + std::fill(dkrds, dkrds + np*np, 0.0); + + if (np == 3) { + // A simplified relative permeability model. + double sw = s[Aqua]; + double sg = s[Vapour]; + double krw = krw_(sw); + double dkrww = krw_.derivative(sw); + double krg = krg_(sg); + double dkrgg = krg_.derivative(sg); + double krow = krow_(sw + sg); + double dkrow = krow_.derivative(sw + sg); + // double krog = krog_(sg); + // double dkrog = krog_.derivative(sg); + // double krocw = krocw_; + kr[Aqua] = krw; + kr[Vapour] = krg; + kr[Liquid] = krow; + //krocw*((krow/krocw + krw)*(krog/krocw + krg) - krw - krg); + if (kr[Liquid] < 0.0) { + kr[Liquid] = 0.0; + } + dkrds[Aqua + Aqua*np] = dkrww; + dkrds[Vapour + Vapour*np] = dkrgg; + //dkrds[Liquid + Aqua*np] = dkrow; + dkrds[Liquid + Liquid*np] = -dkrow; + //krocw*((dkrow/krocw + dkrww)*(krog/krocw + krg) - dkrww); + dkrds[Liquid + Vapour*np] = 0.0; + //krocw*((krow/krocw + krw)*(dkrog/krocw + dkrgg) - dkrgg) + //+ krocw*((dkrow/krocw + krw)*(krog/krocw + krg) - dkrgg); + return; + } + // We have a two-phase situation. We know that oil is active. + if (phase_usage.phase_used[Aqua]) { + int wpos = phase_usage.phase_pos[Aqua]; + int opos = phase_usage.phase_pos[Liquid]; + double sw = s[wpos]; + double krw = krw_(sw); + double dkrww = krw_.derivative(sw); + double krow = krow_(sw); + double dkrow = krow_.derivative(sw); + kr[wpos] = krw; + kr[opos] = krow; + dkrds[wpos + wpos*np] = dkrww; + dkrds[opos + wpos*np] = dkrow; // Row opos, column wpos, fortran order. + } else { + ASSERT(phase_usage.phase_used[Vapour]); + int gpos = phase_usage.phase_pos[Vapour]; + int opos = phase_usage.phase_pos[Liquid]; + double sg = s[gpos]; + double krg = krg_(sg); + double dkrgg = krg_.derivative(sg); + double krog = krog_(sg); + double dkrog = krog_.derivative(sg); + kr[gpos] = krg; + kr[opos] = krog; + dkrds[gpos + gpos*np] = dkrgg; + dkrds[opos + gpos*np] = dkrog; + } + + } + + + void SatFuncSimpleNonuniform::evalPc(const double* s, double* pc) const + { + pc[phase_usage.phase_pos[Liquid]] = 0.0; + if (phase_usage.phase_used[Aqua]) { + int pos = phase_usage.phase_pos[Aqua]; + pc[pos] = pcow_(s[pos]); + } + if (phase_usage.phase_used[Vapour]) { + int pos = phase_usage.phase_pos[Vapour]; + pc[pos] = pcog_(s[pos]); + } + } + + void SatFuncSimpleNonuniform::evalPcDeriv(const double* s, double* pc, double* dpcds) const { // The problem of determining three-phase capillary pressures // is very hard experimentally, usually one extends two-phase diff --git a/opm/core/fluid/SatFuncSimple.hpp b/opm/core/fluid/SatFuncSimple.hpp index a3388848..338a359e 100644 --- a/opm/core/fluid/SatFuncSimple.hpp +++ b/opm/core/fluid/SatFuncSimple.hpp @@ -18,17 +18,21 @@ */ #ifndef SATFUNCSIMPLE_HPP #define SATFUNCSIMPLE_HPP + #include #include +#include #include #include + namespace Opm { - class SatFuncSimple: public BlackoilPhases + class SatFuncSimpleUniform : public BlackoilPhases { public: - void init(const EclipseGridParser& deck, const int table_num, PhaseUsage phase_usg); - void init(const EclipseGridParser& deck, const int table_num, PhaseUsage phase_usg, + void init(const EclipseGridParser& deck, + const int table_num, + const PhaseUsage phase_usg, const int samples); void evalKr(const double* s, double* kr) const; void evalKrDeriv(const double* s, double* kr, double* dkrds) const; @@ -46,5 +50,31 @@ namespace Opm UniformTableLinear pcog_; double krocw_; // = krow_(s_wc) }; + + + class SatFuncSimpleNonuniform : public BlackoilPhases + { + public: + void init(const EclipseGridParser& deck, + const int table_num, + const PhaseUsage phase_usg, + const int samples); + void evalKr(const double* s, double* kr) const; + void evalKrDeriv(const double* s, double* kr, double* dkrds) const; + void evalPc(const double* s, double* pc) const; + void evalPcDeriv(const double* s, double* pc, double* dpcds) const; + double smin_[PhaseUsage::MaxNumPhases]; + double smax_[PhaseUsage::MaxNumPhases]; + private: + PhaseUsage phase_usage; // A copy of the outer class' phase_usage_. + NonuniformTableLinear krw_; + NonuniformTableLinear krow_; + NonuniformTableLinear pcow_; + NonuniformTableLinear krg_; + NonuniformTableLinear krog_; + NonuniformTableLinear pcog_; + double krocw_; // = krow_(s_wc) + }; + } // namespace Opm #endif // SATFUNCSIMPLE_HPP diff --git a/opm/core/fluid/SatFuncStone2.cpp b/opm/core/fluid/SatFuncStone2.cpp index a03bfb66..07b25214 100644 --- a/opm/core/fluid/SatFuncStone2.cpp +++ b/opm/core/fluid/SatFuncStone2.cpp @@ -1,3 +1,22 @@ +/* + Copyright 2012 SINTEF ICT, Applied Mathematics. + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ + #include #include #include @@ -6,23 +25,17 @@ #include #include #include + namespace Opm { - void SatFuncStone2::init(const EclipseGridParser& deck, - const int table_num, - const PhaseUsage phase_usg) - { - init(deck, table_num, phase_usg, 200); - } - - void SatFuncStone2::init(const EclipseGridParser& deck, - const int table_num, - const PhaseUsage phase_usg, - const int samples) + void SatFuncStone2Uniform::init(const EclipseGridParser& deck, + const int table_num, + const PhaseUsage phase_usg, + const int samples) { phase_usage = phase_usg; double swco = 0.0; @@ -64,7 +77,7 @@ namespace Opm } - void SatFuncStone2::evalKr(const double* s, double* kr) const + void SatFuncStone2Uniform::evalKr(const double* s, double* kr) const { if (phase_usage.num_phases == 3) { // Stone-II relative permeability model. @@ -105,7 +118,7 @@ namespace Opm } - void SatFuncStone2::evalKrDeriv(const double* s, double* kr, double* dkrds) const + void SatFuncStone2Uniform::evalKrDeriv(const double* s, double* kr, double* dkrds) const { const int np = phase_usage.num_phases; std::fill(dkrds, dkrds + np*np, 0.0); @@ -167,7 +180,7 @@ namespace Opm } - void SatFuncStone2::evalPc(const double* s, double* pc) const + void SatFuncStone2Uniform::evalPc(const double* s, double* pc) const { pc[phase_usage.phase_pos[Liquid]] = 0.0; if (phase_usage.phase_used[Aqua]) { @@ -180,7 +193,7 @@ namespace Opm } } - void SatFuncStone2::evalPcDeriv(const double* s, double* pc, double* dpcds) const + void SatFuncStone2Uniform::evalPcDeriv(const double* s, double* pc, double* dpcds) const { // The problem of determining three-phase capillary pressures // is very hard experimentally, usually one extends two-phase @@ -206,4 +219,199 @@ namespace Opm + + + // ====== Methods for SatFuncStone2Nonuniform ====== + + + + + void SatFuncStone2Nonuniform::init(const EclipseGridParser& deck, + const int table_num, + const PhaseUsage phase_usg, + const int /*samples*/) + { + phase_usage = phase_usg; + double swco = 0.0; + double swmax = 1.0; + if (phase_usage.phase_used[Aqua]) { + const SWOF::table_t& swof_table = deck.getSWOF().swof_; + const std::vector& sw = swof_table[table_num][0]; + const std::vector& krw = swof_table[table_num][1]; + const std::vector& krow = swof_table[table_num][2]; + const std::vector& pcow = swof_table[table_num][3]; + krw_ = NonuniformTableLinear(sw, krw); + krow_ = NonuniformTableLinear(sw, krow); + pcow_ = NonuniformTableLinear(sw, pcow); + krocw_ = krow[0]; // At connate water -> ecl. SWOF + swco = sw[0]; + smin_[phase_usage.phase_pos[Aqua]] = sw[0]; + swmax = sw.back(); + smax_[phase_usage.phase_pos[Aqua]] = sw.back(); + } + if (phase_usage.phase_used[Vapour]) { + const SGOF::table_t& sgof_table = deck.getSGOF().sgof_; + const std::vector& sg = sgof_table[table_num][0]; + const std::vector& krg = sgof_table[table_num][1]; + const std::vector& krog = sgof_table[table_num][2]; + const std::vector& pcog = sgof_table[table_num][3]; + krg_ = NonuniformTableLinear(sg, krg); + krog_ = NonuniformTableLinear(sg, krog); + pcog_ = NonuniformTableLinear(sg, pcog); + smin_[phase_usage.phase_pos[Vapour]] = sg[0]; + if (std::fabs(sg.back() + swco - 1.0) > 1e-3) { + THROW("Gas maximum saturation in SGOF table = " << sg.back() << + ", should equal (1.0 - connate water sat) = " << (1.0 - swco)); + } + smax_[phase_usage.phase_pos[Vapour]] = sg.back(); + } + // These only consider water min/max sats. Consider gas sats? + smin_[phase_usage.phase_pos[Liquid]] = 1.0 - swmax; + smax_[phase_usage.phase_pos[Liquid]] = 1.0 - swco; + } + + + void SatFuncStone2Nonuniform::evalKr(const double* s, double* kr) const + { + if (phase_usage.num_phases == 3) { + // Stone-II relative permeability model. + double sw = s[Aqua]; + double sg = s[Vapour]; + double krw = krw_(sw); + double krg = krg_(sg); + double krow = krow_(sw + sg); // = 1 - so + double krog = krog_(sg); // = 1 - so - sw + double krocw = krocw_; + kr[Aqua] = krw; + kr[Vapour] = krg; + kr[Liquid] = krocw*((krow/krocw + krw)*(krog/krocw + krg) - krw - krg); + if (kr[Liquid] < 0.0) { + kr[Liquid] = 0.0; + } + return; + } + // We have a two-phase situation. We know that oil is active. + if (phase_usage.phase_used[Aqua]) { + int wpos = phase_usage.phase_pos[Aqua]; + int opos = phase_usage.phase_pos[Liquid]; + double sw = s[wpos]; + double krw = krw_(sw); + double krow = krow_(sw); + kr[wpos] = krw; + kr[opos] = krow; + } else { + ASSERT(phase_usage.phase_used[Vapour]); + int gpos = phase_usage.phase_pos[Vapour]; + int opos = phase_usage.phase_pos[Liquid]; + double sg = s[gpos]; + double krg = krg_(sg); + double krog = krog_(sg); + kr[gpos] = krg; + kr[opos] = krog; + } + } + + + void SatFuncStone2Nonuniform::evalKrDeriv(const double* s, double* kr, double* dkrds) const + { + const int np = phase_usage.num_phases; + std::fill(dkrds, dkrds + np*np, 0.0); + + if (np == 3) { + // Stone-II relative permeability model. + double sw = s[Aqua]; + double sg = s[Vapour]; + double krw = krw_(sw); + double dkrww = krw_.derivative(sw); + double krg = krg_(sg); + double dkrgg = krg_.derivative(sg); + double krow = krow_(sw + sg); + double dkrow = krow_.derivative(sw + sg); + double krog = krog_(sg); + double dkrog = krog_.derivative(sg); + double krocw = krocw_; + kr[Aqua] = krw; + kr[Vapour] = krg; + kr[Liquid] = krocw*((krow/krocw + krw)*(krog/krocw + krg) - krw - krg); + if (kr[Liquid] < 0.0) { + kr[Liquid] = 0.0; + } + dkrds[Aqua + Aqua*np] = dkrww; + dkrds[Vapour + Vapour*np] = dkrgg; + dkrds[Liquid + Aqua*np] = krocw*((dkrow/krocw + dkrww)*(krog/krocw + krg) - dkrww); + dkrds[Liquid + Vapour*np] = krocw*((krow/krocw + krw)*(dkrog/krocw + dkrgg) - dkrgg) + + krocw*((dkrow/krocw + krw)*(krog/krocw + krg) - dkrgg); + return; + } + // We have a two-phase situation. We know that oil is active. + if (phase_usage.phase_used[Aqua]) { + int wpos = phase_usage.phase_pos[Aqua]; + int opos = phase_usage.phase_pos[Liquid]; + double sw = s[wpos]; + double krw = krw_(sw); + double dkrww = krw_.derivative(sw); + double krow = krow_(sw); + double dkrow = krow_.derivative(sw); + kr[wpos] = krw; + kr[opos] = krow; + dkrds[wpos + wpos*np] = dkrww; + dkrds[opos + wpos*np] = dkrow; // Row opos, column wpos, fortran order. + } else { + ASSERT(phase_usage.phase_used[Vapour]); + int gpos = phase_usage.phase_pos[Vapour]; + int opos = phase_usage.phase_pos[Liquid]; + double sg = s[gpos]; + double krg = krg_(sg); + double dkrgg = krg_.derivative(sg); + double krog = krog_(sg); + double dkrog = krog_.derivative(sg); + kr[gpos] = krg; + kr[opos] = krog; + dkrds[gpos + gpos*np] = dkrgg; + dkrds[opos + gpos*np] = dkrog; + } + + } + + + void SatFuncStone2Nonuniform::evalPc(const double* s, double* pc) const + { + pc[phase_usage.phase_pos[Liquid]] = 0.0; + if (phase_usage.phase_used[Aqua]) { + int pos = phase_usage.phase_pos[Aqua]; + pc[pos] = pcow_(s[pos]); + } + if (phase_usage.phase_used[Vapour]) { + int pos = phase_usage.phase_pos[Vapour]; + pc[pos] = pcog_(s[pos]); + } + } + + void SatFuncStone2Nonuniform::evalPcDeriv(const double* s, double* pc, double* dpcds) const + { + // The problem of determining three-phase capillary pressures + // is very hard experimentally, usually one extends two-phase + // data (as for relative permeability). + // In our approach the derivative matrix is quite sparse, only + // the diagonal elements corresponding to non-oil phases are + // (potentially) nonzero. + const int np = phase_usage.num_phases; + std::fill(dpcds, dpcds + np*np, 0.0); + pc[phase_usage.phase_pos[Liquid]] = 0.0; + if (phase_usage.phase_used[Aqua]) { + int pos = phase_usage.phase_pos[Aqua]; + pc[pos] = pcow_(s[pos]); + dpcds[np*pos + pos] = pcow_.derivative(s[pos]); + } + if (phase_usage.phase_used[Vapour]) { + int pos = phase_usage.phase_pos[Vapour]; + pc[pos] = pcog_(s[pos]); + dpcds[np*pos + pos] = pcog_.derivative(s[pos]); + } + } + + + + + } // namespace Opm diff --git a/opm/core/fluid/SatFuncStone2.hpp b/opm/core/fluid/SatFuncStone2.hpp index ae4a6d38..532bf7ce 100644 --- a/opm/core/fluid/SatFuncStone2.hpp +++ b/opm/core/fluid/SatFuncStone2.hpp @@ -18,17 +18,21 @@ */ #ifndef SATFUNCSTONE2_HPP #define SATFUNCSTONE2_HPP + #include #include +#include #include #include + namespace Opm { - class SatFuncStone2: public BlackoilPhases + class SatFuncStone2Uniform : public BlackoilPhases { public: - void init(const EclipseGridParser& deck, const int table_num, PhaseUsage phase_usg); - void init(const EclipseGridParser& deck, const int table_num, PhaseUsage phase_usg, + void init(const EclipseGridParser& deck, + const int table_num, + const PhaseUsage phase_usg, const int samples); void evalKr(const double* s, double* kr) const; void evalKrDeriv(const double* s, double* kr, double* dkrds) const; @@ -46,5 +50,31 @@ namespace Opm UniformTableLinear pcog_; double krocw_; // = krow_(s_wc) }; + + + class SatFuncStone2Nonuniform : public BlackoilPhases + { + public: + void init(const EclipseGridParser& deck, + const int table_num, + const PhaseUsage phase_usg, + const int samples); + void evalKr(const double* s, double* kr) const; + void evalKrDeriv(const double* s, double* kr, double* dkrds) const; + void evalPc(const double* s, double* pc) const; + void evalPcDeriv(const double* s, double* pc, double* dpcds) const; + double smin_[PhaseUsage::MaxNumPhases]; + double smax_[PhaseUsage::MaxNumPhases]; + private: + PhaseUsage phase_usage; // A copy of the outer class' phase_usage_. + NonuniformTableLinear krw_; + NonuniformTableLinear krow_; + NonuniformTableLinear pcow_; + NonuniformTableLinear krg_; + NonuniformTableLinear krog_; + NonuniformTableLinear pcog_; + double krocw_; // = krow_(s_wc) + }; + } // namespace Opm #endif // SATFUNCSTONE2_HPP diff --git a/opm/core/fluid/SaturationPropsBasic.cpp b/opm/core/fluid/SaturationPropsBasic.cpp index 760f934d..10ddf23d 100644 --- a/opm/core/fluid/SaturationPropsBasic.cpp +++ b/opm/core/fluid/SaturationPropsBasic.cpp @@ -29,64 +29,64 @@ namespace Opm namespace { - struct KrFunConstant - { - double kr(double) - { - return 1.0; - } - double dkrds(double) - { - return 0.0; - } - }; + struct KrFunConstant + { + double kr(double) + { + return 1.0; + } + double dkrds(double) + { + return 0.0; + } + }; - struct KrFunLinear - { - double kr(double s) - { - return s; - } - double dkrds(double) - { - return 1.0; - } - }; + struct KrFunLinear + { + double kr(double s) + { + return s; + } + double dkrds(double) + { + return 1.0; + } + }; - struct KrFunQuadratic - { - double kr(double s) - { - return s*s; - } - double dkrds(double s) - { - return 2.0*s; - } - }; + struct KrFunQuadratic + { + double kr(double s) + { + return s*s; + } + double dkrds(double s) + { + return 2.0*s; + } + }; - template - static inline void evalAllKrDeriv(const int n, const int np, - const double* s, double* kr, double* dkrds, Fun fun) - { - if (dkrds == 0) { + template + static inline void evalAllKrDeriv(const int n, const int np, + const double* s, double* kr, double* dkrds, Fun fun) + { + if (dkrds == 0) { // #pragma omp parallel for - for (int i = 0; i < n*np; ++i) { - kr[i] = fun.kr(s[i]); - } - return; - } + for (int i = 0; i < n*np; ++i) { + kr[i] = fun.kr(s[i]); + } + return; + } // #pragma omp parallel for - for (int i = 0; i < n; ++i) { - std::fill(dkrds + i*np*np, dkrds + (i+1)*np*np, 0.0); - for (int phase = 0; phase < np; ++phase) { - kr[i*np + phase] = fun.kr(s[i*np + phase]); - // Only diagonal elements in derivative. - dkrds[i*np*np + phase*np + phase] = fun.dkrds(s[i*np + phase]); - } - } - } + for (int i = 0; i < n; ++i) { + std::fill(dkrds + i*np*np, dkrds + (i+1)*np*np, 0.0); + for (int phase = 0; phase < np; ++phase) { + kr[i*np + phase] = fun.kr(s[i*np + phase]); + // Only diagonal elements in derivative. + dkrds[i*np*np + phase*np + phase] = fun.dkrds(s[i*np + phase]); + } + } + } } // anon namespace @@ -109,25 +109,25 @@ namespace Opm /// Initialize from parameters. void SaturationPropsBasic::init(const parameter::ParameterGroup& param) { - int num_phases = param.getDefault("num_phases", 2); - if (num_phases > 2 || num_phases < 1) { - THROW("SaturationPropsBasic::init() illegal num_phases: " << num_phases); - } + int num_phases = param.getDefault("num_phases", 2); + if (num_phases > 2 || num_phases < 1) { + THROW("SaturationPropsBasic::init() illegal num_phases: " << num_phases); + } num_phases_ = num_phases; - //std::string rpf = param.getDefault("relperm_func", std::string("Unset")); - std::string rpf = param.getDefault("relperm_func", std::string("Linear")); - if (rpf == "Constant") { - relperm_func_ = Constant; - if(num_phases!=1){ - THROW("Constant relperm with more than one phase???"); - } - } else if (rpf == "Linear") { - relperm_func_ = Linear; - } else if (rpf == "Quadratic") { - relperm_func_ = Quadratic; - } else { - THROW("SaturationPropsBasic::init() illegal relperm_func: " << rpf); - } + //std::string rpf = param.getDefault("relperm_func", std::string("Unset")); + std::string rpf = param.getDefault("relperm_func", std::string("Linear")); + if (rpf == "Constant") { + relperm_func_ = Constant; + if(num_phases!=1){ + THROW("Constant relperm with more than one phase???"); + } + } else if (rpf == "Linear") { + relperm_func_ = Linear; + } else if (rpf == "Quadratic") { + relperm_func_ = Quadratic; + } else { + THROW("SaturationPropsBasic::init() illegal relperm_func: " << rpf); + } } @@ -136,7 +136,7 @@ namespace Opm /// \return P, the number of phases. int SaturationPropsBasic::numPhases() const { - return num_phases_; + return num_phases_; } @@ -152,29 +152,29 @@ namespace Opm /// m_{ij} = \frac{dkr_i}{ds^j}, /// and is output in Fortran order (m_00 m_10 m_20 m01 ...) void SaturationPropsBasic::relperm(const int n, - const double* s, - double* kr, - double* dkrds) const + const double* s, + double* kr, + double* dkrds) const { - switch (relperm_func_) { - case Constant: - { - evalAllKrDeriv(n, num_phases_, s, kr, dkrds, KrFunConstant()); - break; - } - case Linear: - { - evalAllKrDeriv(n, num_phases_, s, kr, dkrds, KrFunLinear()); - break; - } - case Quadratic: - { - evalAllKrDeriv(n, num_phases_, s, kr, dkrds, KrFunQuadratic()); - break; - } - default: - THROW("SaturationPropsBasic::relperm() unhandled relperm func type: " << relperm_func_); - } + switch (relperm_func_) { + case Constant: + { + evalAllKrDeriv(n, num_phases_, s, kr, dkrds, KrFunConstant()); + break; + } + case Linear: + { + evalAllKrDeriv(n, num_phases_, s, kr, dkrds, KrFunLinear()); + break; + } + case Quadratic: + { + evalAllKrDeriv(n, num_phases_, s, kr, dkrds, KrFunQuadratic()); + break; + } + default: + THROW("SaturationPropsBasic::relperm() unhandled relperm func type: " << relperm_func_); + } } @@ -190,13 +190,13 @@ namespace Opm /// m_{ij} = \frac{dpc_i}{ds^j}, /// and is output in Fortran order (m_00 m_10 m_20 m01 ...) void SaturationPropsBasic::capPress(const int n, - const double* /*s*/, - double* pc, - double* dpcds) const + const double* /*s*/, + double* pc, + double* dpcds) const { - std::fill(pc, pc + num_phases_*n, 0.0); + std::fill(pc, pc + num_phases_*n, 0.0); if (dpcds) { - std::fill(dpcds, dpcds + num_phases_*num_phases_*n, 0.0); + std::fill(dpcds, dpcds + num_phases_*num_phases_*n, 0.0); } } @@ -207,11 +207,11 @@ namespace Opm /// \param[out] smin Array of nP minimum s values, array must be valid before calling. /// \param[out] smax Array of nP maximum s values, array must be valid before calling. void SaturationPropsBasic::satRange(const int n, - double* smin, - double* smax) const + double* smin, + double* smax) const { - std::fill(smin, smin + num_phases_*n, 0.0); - std::fill(smax, smax + num_phases_*n, 1.0); + std::fill(smin, smin + num_phases_*n, 0.0); + std::fill(smax, smax + num_phases_*n, 1.0); } diff --git a/opm/core/fluid/SaturationPropsBasic.hpp b/opm/core/fluid/SaturationPropsBasic.hpp index 789e0a35..2f90672d 100644 --- a/opm/core/fluid/SaturationPropsBasic.hpp +++ b/opm/core/fluid/SaturationPropsBasic.hpp @@ -40,16 +40,16 @@ namespace Opm SaturationPropsBasic(); /// Initialize from parameters. - /// The following parameters are accepted (defaults): - /// num_phases (2) Must be 1 or 2. - /// relperm_func ("Linear") Must be "Constant", "Linear" or "Quadratic". + /// The following parameters are accepted (defaults): + /// num_phases (2) Must be 1 or 2. + /// relperm_func ("Linear") Must be "Constant", "Linear" or "Quadratic". void init(const parameter::ParameterGroup& param); - enum RelPermFunc { Constant, Linear, Quadratic }; + enum RelPermFunc { Constant, Linear, Quadratic }; /// Initialize from arguments a basic Saturation property. void init(const int num_phases, - const RelPermFunc& relperm_func) + const RelPermFunc& relperm_func) { num_phases_ = num_phases; relperm_func_ = relperm_func; @@ -86,18 +86,18 @@ namespace Opm double* pc, double* dpcds) const; - /// Obtain the range of allowable saturation values. + /// Obtain the range of allowable saturation values. /// \param[in] n Number of data points. /// \param[out] smin Array of nP minimum s values, array must be valid before calling. /// \param[out] smax Array of nP maximum s values, array must be valid before calling. - void satRange(const int n, - double* smin, - double* smax) const; + void satRange(const int n, + double* smin, + double* smax) const; private: - int num_phases_; - RelPermFunc relperm_func_; + int num_phases_; + RelPermFunc relperm_func_; }; diff --git a/opm/core/fluid/SaturationPropsFromDeck.cpp b/opm/core/fluid/SaturationPropsFromDeck.cpp index 3481b874..83c942c5 100644 --- a/opm/core/fluid/SaturationPropsFromDeck.cpp +++ b/opm/core/fluid/SaturationPropsFromDeck.cpp @@ -19,7 +19,6 @@ #include #include -#include #include #include #include @@ -27,236 +26,9 @@ namespace Opm { - /// Default constructor. - SaturationPropsFromDeck::SaturationPropsFromDeck() - { - } + // This file should be removed in the future. + // Holding off until refactoring of SaturationPropsFromDeck class is done. - /// Initialize from deck. - void SaturationPropsFromDeck::init(const EclipseGridParser& deck, - const UnstructuredGrid& grid) - { - phase_usage_ = phaseUsageFromDeck(deck); - - // Extract input data. - // Oil phase should be active. - if (!phase_usage_.phase_used[Liquid]) { - THROW("SaturationPropsFromDeck::init() -- oil phase must be active."); - } - - // Obtain SATNUM, if it exists, and create cell_to_func_. - // Otherwise, let the cell_to_func_ mapping be just empty. - int satfuncs_expected = 1; - if (deck.hasField("SATNUM")) { - const std::vector& satnum = deck.getIntegerValue("SATNUM"); - satfuncs_expected = *std::max_element(satnum.begin(), satnum.end()); - const int num_cells = grid.number_of_cells; - cell_to_func_.resize(num_cells); - const int* gc = grid.global_cell; - for (int cell = 0; cell < num_cells; ++cell) { - const int deck_pos = (gc == NULL) ? cell : gc[cell]; - cell_to_func_[cell] = satnum[deck_pos] - 1; - } - } - - // Find number of tables, check for consistency. - enum { Uninitialized = -1 }; - int num_tables = Uninitialized; - if (phase_usage_.phase_used[Aqua]) { - const SWOF::table_t& swof_table = deck.getSWOF().swof_; - num_tables = swof_table.size(); - if (num_tables < satfuncs_expected) { - THROW("Found " << num_tables << " SWOF tables, SATNUM specifies at least " << satfuncs_expected); - } - } - if (phase_usage_.phase_used[Vapour]) { - const SGOF::table_t& sgof_table = deck.getSGOF().sgof_; - int num_sgof_tables = sgof_table.size(); - if (num_sgof_tables < satfuncs_expected) { - THROW("Found " << num_tables << " SGOF tables, SATNUM specifies at least " << satfuncs_expected); - } - if (num_tables == Uninitialized) { - num_tables = num_sgof_tables; - } else if (num_tables != num_sgof_tables) { - THROW("Inconsistent number of tables in SWOF and SGOF."); - } - } - - // Initialize tables. - satfuncset_.resize(num_tables); - for (int table = 0; table < num_tables; ++table) { - satfuncset_[table].init(deck, table, phase_usage_); - } - } - - - /// Initialize from deck, grid and parameters - void SaturationPropsFromDeck::init(const EclipseGridParser& deck, - const UnstructuredGrid& grid, - const parameter::ParameterGroup& param) - { - phase_usage_ = phaseUsageFromDeck(deck); - - // Extract input data. - // Oil phase should be active. - if (!phase_usage_.phase_used[Liquid]) { - THROW("SaturationPropsFromDeck::init() -- oil phase must be active."); - } - - // Obtain SATNUM, if it exists, and create cell_to_func_. - // Otherwise, let the cell_to_func_ mapping be just empty. - int satfuncs_expected = 1; - if (deck.hasField("SATNUM")) { - const std::vector& satnum = deck.getIntegerValue("SATNUM"); - satfuncs_expected = *std::max_element(satnum.begin(), satnum.end()); - const int num_cells = grid.number_of_cells; - cell_to_func_.resize(num_cells); - const int* gc = grid.global_cell; - for (int cell = 0; cell < num_cells; ++cell) { - const int deck_pos = (gc == NULL) ? cell : gc[cell]; - cell_to_func_[cell] = satnum[deck_pos] - 1; - } - } - - // Find number of tables, check for consistency. - enum { Uninitialized = -1 }; - int num_tables = Uninitialized; - if (phase_usage_.phase_used[Aqua]) { - const SWOF::table_t& swof_table = deck.getSWOF().swof_; - num_tables = swof_table.size(); - if (num_tables < satfuncs_expected) { - THROW("Found " << num_tables << " SWOF tables, SATNUM specifies at least " << satfuncs_expected); - } - } - if (phase_usage_.phase_used[Vapour]) { - const SGOF::table_t& sgof_table = deck.getSGOF().sgof_; - int num_sgof_tables = sgof_table.size(); - if (num_sgof_tables < satfuncs_expected) { - THROW("Found " << num_tables << " SGOF tables, SATNUM specifies at least " << satfuncs_expected); - } - if (num_tables == Uninitialized) { - num_tables = num_sgof_tables; - } else if (num_tables != num_sgof_tables) { - THROW("Inconsistent number of tables in SWOF and SGOF."); - } - } - - // Initialize tables. - const int tab_size = param.getDefault("tab_size_kr", 200); - satfuncset_.resize(num_tables); - for (int table = 0; table < num_tables; ++table) { - satfuncset_[table].init(deck, table, phase_usage_, tab_size); - } - } - - - - /// \return P, the number of phases. - int SaturationPropsFromDeck::numPhases() const - { - return phase_usage_.num_phases; - } - - - - - /// Relative permeability. - /// \param[in] n Number of data points. - /// \param[in] s Array of nP saturation values. - /// \param[in] cells Array of n cell indices to be associated with the s values. - /// \param[out] kr Array of nP relperm values, array must be valid before calling. - /// \param[out] dkrds If non-null: array of nP^2 relperm derivative values, - /// array must be valid before calling. - /// The P^2 derivative matrix is - /// m_{ij} = \frac{dkr_i}{ds^j}, - /// and is output in Fortran order (m_00 m_10 m_20 m01 ...) - void SaturationPropsFromDeck::relperm(const int n, - const double* s, - const int* cells, - double* kr, - double* dkrds) const - { - ASSERT (cells != 0); - - const int np = phase_usage_.num_phases; - if (dkrds) { -// #pragma omp parallel for - for (int i = 0; i < n; ++i) { - funcForCell(cells[i]).evalKrDeriv(s + np*i, kr + np*i, dkrds + np*np*i); - } - } else { -// #pragma omp parallel for - for (int i = 0; i < n; ++i) { - funcForCell(cells[i]).evalKr(s + np*i, kr + np*i); - } - } - } - - - - - /// Capillary pressure. - /// \param[in] n Number of data points. - /// \param[in] s Array of nP saturation values. - /// \param[in] cells Array of n cell indices to be associated with the s values. - /// \param[out] pc Array of nP capillary pressure values, array must be valid before calling. - /// \param[out] dpcds If non-null: array of nP^2 derivative values, - /// array must be valid before calling. - /// The P^2 derivative matrix is - /// m_{ij} = \frac{dpc_i}{ds^j}, - /// and is output in Fortran order (m_00 m_10 m_20 m01 ...) - void SaturationPropsFromDeck::capPress(const int n, - const double* s, - const int* cells, - double* pc, - double* dpcds) const - { - ASSERT (cells != 0); - - const int np = phase_usage_.num_phases; - if (dpcds) { -// #pragma omp parallel for - for (int i = 0; i < n; ++i) { - funcForCell(cells[i]).evalPcDeriv(s + np*i, pc + np*i, dpcds + np*np*i); - } - } else { -// #pragma omp parallel for - for (int i = 0; i < n; ++i) { - funcForCell(cells[i]).evalPc(s + np*i, pc + np*i); - } - } - } - - - - - /// Obtain the range of allowable saturation values. - /// \param[in] n Number of data points. - /// \param[in] cells Array of n cell indices. - /// \param[out] smin Array of nP minimum s values, array must be valid before calling. - /// \param[out] smax Array of nP maximum s values, array must be valid before calling. - void SaturationPropsFromDeck::satRange(const int n, - const int* cells, - double* smin, - double* smax) const - { - ASSERT (cells != 0); - - const int np = phase_usage_.num_phases; - for (int i = 0; i < n; ++i) { - for (int p = 0; p < np; ++p) { - smin[np*i + p] = funcForCell(cells[i]).smin_[p]; - smax[np*i + p] = funcForCell(cells[i]).smax_[p]; - } - } - } - - // Map the cell number to the correct function set. - const SaturationPropsFromDeck::satfunc_t& - SaturationPropsFromDeck::funcForCell(const int cell) const - { - return cell_to_func_.empty() ? satfuncset_[0] : satfuncset_[cell_to_func_[cell]]; - } } // namespace Opm diff --git a/opm/core/fluid/SaturationPropsFromDeck.hpp b/opm/core/fluid/SaturationPropsFromDeck.hpp index bd70d802..ea2acb34 100644 --- a/opm/core/fluid/SaturationPropsFromDeck.hpp +++ b/opm/core/fluid/SaturationPropsFromDeck.hpp @@ -19,12 +19,14 @@ #ifndef OPM_SATURATIONPROPSFROMDECK_HEADER_INCLUDED #define OPM_SATURATIONPROPSFROMDECK_HEADER_INCLUDED + +#include #include #include -#include #include #include #include +#include #include struct UnstructuredGrid; @@ -32,23 +34,31 @@ struct UnstructuredGrid; namespace Opm { - class SaturationPropsFromDeck : public BlackoilPhases + + + /// Interface to saturation functions from deck. + /// Possible values for template argument (for now): + /// SatFuncSetStone2Nonuniform, + /// SatFuncSetStone2Uniform. + /// SatFuncSetSimpleNonuniform, + /// SatFuncSetSimpleUniform. + template + class SaturationPropsFromDeck : public SaturationPropsInterface { public: /// Default constructor. SaturationPropsFromDeck(); /// Initialize from deck and grid. - /// \param deck Deck input parser - /// \param grid Grid to which property object applies, needed for the + /// \param[in] deck Deck input parser + /// \param[in] grid Grid to which property object applies, needed for the /// mapping from cell indices (typically from a processed grid) /// to logical cartesian indices consistent with the deck. - void init(const EclipseGridParser& deck, - const UnstructuredGrid& grid); - + /// \param[in] samples Number of uniform sample points for saturation tables. + /// NOTE: samples will only be used with the SatFuncSetUniform template argument. void init(const EclipseGridParser& deck, const UnstructuredGrid& grid, - const parameter::ParameterGroup& param); + const int samples); /// \return P, the number of phases. int numPhases() const; @@ -83,22 +93,23 @@ namespace Opm double* pc, double* dpcds) const; - /// Obtain the range of allowable saturation values. + /// Obtain the range of allowable saturation values. /// \param[in] n Number of data points. /// \param[out] smin Array of nP minimum s values, array must be valid before calling. /// \param[out] smax Array of nP maximum s values, array must be valid before calling. - void satRange(const int n, + void satRange(const int n, const int* cells, - double* smin, - double* smax) const; + double* smin, + double* smax) const; private: PhaseUsage phase_usage_; - typedef SatFuncSimple satfunc_t; - std::vector satfuncset_; + std::vector satfuncset_; std::vector cell_to_func_; // = SATNUM - 1 - const satfunc_t& funcForCell(const int cell) const; + typedef SatFuncSet Funcs; + + const Funcs& funcForCell(const int cell) const; }; @@ -106,6 +117,7 @@ namespace Opm } // namespace Opm +#include #endif // OPM_SATURATIONPROPSFROMDECK_HEADER_INCLUDED diff --git a/opm/core/fluid/SaturationPropsFromDeck_impl.hpp b/opm/core/fluid/SaturationPropsFromDeck_impl.hpp new file mode 100644 index 00000000..f7a80b45 --- /dev/null +++ b/opm/core/fluid/SaturationPropsFromDeck_impl.hpp @@ -0,0 +1,221 @@ +/* + Copyright 2012 SINTEF ICT, Applied Mathematics. + + 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 . +*/ + +#ifndef OPM_SATURATIONPROPSFROMDECK_IMPL_HEADER_INCLUDED +#define OPM_SATURATIONPROPSFROMDECK_IMPL_HEADER_INCLUDED + + +#include +#include +#include +#include + +namespace Opm +{ + + + // ----------- Methods of SaturationPropsFromDeck --------- + + + /// Default constructor. + template + SaturationPropsFromDeck::SaturationPropsFromDeck() + { + } + + /// Initialize from deck. + template + void SaturationPropsFromDeck::init(const EclipseGridParser& deck, + const UnstructuredGrid& grid, + const int samples) + { + phase_usage_ = phaseUsageFromDeck(deck); + + // Extract input data. + // Oil phase should be active. + if (!phase_usage_.phase_used[Liquid]) { + THROW("SaturationPropsFromDeck::init() -- oil phase must be active."); + } + + // Obtain SATNUM, if it exists, and create cell_to_func_. + // Otherwise, let the cell_to_func_ mapping be just empty. + int satfuncs_expected = 1; + if (deck.hasField("SATNUM")) { + const std::vector& satnum = deck.getIntegerValue("SATNUM"); + satfuncs_expected = *std::max_element(satnum.begin(), satnum.end()); + const int num_cells = grid.number_of_cells; + cell_to_func_.resize(num_cells); + const int* gc = grid.global_cell; + for (int cell = 0; cell < num_cells; ++cell) { + const int deck_pos = (gc == NULL) ? cell : gc[cell]; + cell_to_func_[cell] = satnum[deck_pos] - 1; + } + } + + // Find number of tables, check for consistency. + enum { Uninitialized = -1 }; + int num_tables = Uninitialized; + if (phase_usage_.phase_used[Aqua]) { + const SWOF::table_t& swof_table = deck.getSWOF().swof_; + num_tables = swof_table.size(); + if (num_tables < satfuncs_expected) { + THROW("Found " << num_tables << " SWOF tables, SATNUM specifies at least " << satfuncs_expected); + } + } + if (phase_usage_.phase_used[Vapour]) { + const SGOF::table_t& sgof_table = deck.getSGOF().sgof_; + int num_sgof_tables = sgof_table.size(); + if (num_sgof_tables < satfuncs_expected) { + THROW("Found " << num_tables << " SGOF tables, SATNUM specifies at least " << satfuncs_expected); + } + if (num_tables == Uninitialized) { + num_tables = num_sgof_tables; + } else if (num_tables != num_sgof_tables) { + THROW("Inconsistent number of tables in SWOF and SGOF."); + } + } + + // Initialize tables. + satfuncset_.resize(num_tables); + for (int table = 0; table < num_tables; ++table) { + satfuncset_[table].init(deck, table, phase_usage_, samples); + } + } + + + + + /// \return P, the number of phases. + template + int SaturationPropsFromDeck::numPhases() const + { + return phase_usage_.num_phases; + } + + + + + /// Relative permeability. + /// \param[in] n Number of data points. + /// \param[in] s Array of nP saturation values. + /// \param[in] cells Array of n cell indices to be associated with the s values. + /// \param[out] kr Array of nP relperm values, array must be valid before calling. + /// \param[out] dkrds If non-null: array of nP^2 relperm derivative values, + /// array must be valid before calling. + /// The P^2 derivative matrix is + /// m_{ij} = \frac{dkr_i}{ds^j}, + /// and is output in Fortran order (m_00 m_10 m_20 m01 ...) + template + void SaturationPropsFromDeck::relperm(const int n, + const double* s, + const int* cells, + double* kr, + double* dkrds) const + { + ASSERT (cells != 0); + + const int np = phase_usage_.num_phases; + if (dkrds) { +// #pragma omp parallel for + for (int i = 0; i < n; ++i) { + funcForCell(cells[i]).evalKrDeriv(s + np*i, kr + np*i, dkrds + np*np*i); + } + } else { +// #pragma omp parallel for + for (int i = 0; i < n; ++i) { + funcForCell(cells[i]).evalKr(s + np*i, kr + np*i); + } + } + } + + + + + /// Capillary pressure. + /// \param[in] n Number of data points. + /// \param[in] s Array of nP saturation values. + /// \param[in] cells Array of n cell indices to be associated with the s values. + /// \param[out] pc Array of nP capillary pressure values, array must be valid before calling. + /// \param[out] dpcds If non-null: array of nP^2 derivative values, + /// array must be valid before calling. + /// The P^2 derivative matrix is + /// m_{ij} = \frac{dpc_i}{ds^j}, + /// and is output in Fortran order (m_00 m_10 m_20 m01 ...) + template + void SaturationPropsFromDeck::capPress(const int n, + const double* s, + const int* cells, + double* pc, + double* dpcds) const + { + ASSERT (cells != 0); + + const int np = phase_usage_.num_phases; + if (dpcds) { +// #pragma omp parallel for + for (int i = 0; i < n; ++i) { + funcForCell(cells[i]).evalPcDeriv(s + np*i, pc + np*i, dpcds + np*np*i); + } + } else { +// #pragma omp parallel for + for (int i = 0; i < n; ++i) { + funcForCell(cells[i]).evalPc(s + np*i, pc + np*i); + } + } + } + + + + + /// Obtain the range of allowable saturation values. + /// \param[in] n Number of data points. + /// \param[in] cells Array of n cell indices. + /// \param[out] smin Array of nP minimum s values, array must be valid before calling. + /// \param[out] smax Array of nP maximum s values, array must be valid before calling. + template + void SaturationPropsFromDeck::satRange(const int n, + const int* cells, + double* smin, + double* smax) const + { + ASSERT (cells != 0); + + const int np = phase_usage_.num_phases; + for (int i = 0; i < n; ++i) { + for (int p = 0; p < np; ++p) { + smin[np*i + p] = funcForCell(cells[i]).smin_[p]; + smax[np*i + p] = funcForCell(cells[i]).smax_[p]; + } + } + } + + + // Map the cell number to the correct function set. + template + const typename SaturationPropsFromDeck::Funcs& + SaturationPropsFromDeck::funcForCell(const int cell) const + { + return cell_to_func_.empty() ? satfuncset_[0] : satfuncset_[cell_to_func_[cell]]; + } + + + +} // namespace Opm + +#endif // OPM_SATURATIONPROPSFROMDECK_IMPL_HEADER_INCLUDED diff --git a/opm/core/fluid/SaturationPropsInterface.hpp b/opm/core/fluid/SaturationPropsInterface.hpp new file mode 100644 index 00000000..30f277cc --- /dev/null +++ b/opm/core/fluid/SaturationPropsInterface.hpp @@ -0,0 +1,85 @@ +/* + Copyright 2012 SINTEF ICT, Applied Mathematics. + + 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 . +*/ + +#ifndef OPM_SATURATIONPROPSINTERFACE_HEADER_INCLUDED +#define OPM_SATURATIONPROPSINTERFACE_HEADER_INCLUDED + +#include + + +namespace Opm +{ + + class SaturationPropsInterface : public BlackoilPhases + { + public: + /// Virtual destructor. + virtual ~SaturationPropsInterface() {}; + + /// \return P, the number of phases. + virtual int numPhases() const = 0; + + /// Relative permeability. + /// \param[in] n Number of data points. + /// \param[in] s Array of nP saturation values. + /// \param[out] kr Array of nP relperm values, array must be valid before calling. + /// \param[out] dkrds If non-null: array of nP^2 relperm derivative values, + /// array must be valid before calling. + /// The P^2 derivative matrix is + /// m_{ij} = \frac{dkr_i}{ds^j}, + /// and is output in Fortran order (m_00 m_10 m_20 m01 ...) + virtual void relperm(const int n, + const double* s, + const int* cells, + double* kr, + double* dkrds) const = 0; + + /// Capillary pressure. + /// \param[in] n Number of data points. + /// \param[in] s Array of nP saturation values. + /// \param[out] pc Array of nP capillary pressure values, array must be valid before calling. + /// \param[out] dpcds If non-null: array of nP^2 derivative values, + /// array must be valid before calling. + /// The P^2 derivative matrix is + /// m_{ij} = \frac{dpc_i}{ds^j}, + /// and is output in Fortran order (m_00 m_10 m_20 m01 ...) + virtual void capPress(const int n, + const double* s, + const int* cells, + double* pc, + double* dpcds) const = 0; + + /// Obtain the range of allowable saturation values. + /// \param[in] n Number of data points. + /// \param[out] smin Array of nP minimum s values, array must be valid before calling. + /// \param[out] smax Array of nP maximum s values, array must be valid before calling. + virtual void satRange(const int n, + const int* cells, + double* smin, + double* smax) const = 0; + + }; + + + +} // namespace Opm + + + +#endif // OPM_SATURATIONPROPSINTERFACE_HEADER_INCLUDED diff --git a/opm/core/fluid/blackoil/BlackoilPvtProperties.cpp b/opm/core/fluid/blackoil/BlackoilPvtProperties.cpp index 39b4ba05..4a0004ee 100644 --- a/opm/core/fluid/blackoil/BlackoilPvtProperties.cpp +++ b/opm/core/fluid/blackoil/BlackoilPvtProperties.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -43,14 +44,14 @@ namespace Opm { typedef std::vector > > table_t; // If we need multiple regions, this class and the SinglePvt* classes must change. - region_number_ = 0; + region_number_ = 0; phase_usage_ = phaseUsageFromDeck(deck); - // Surface densities. Accounting for different orders in eclipse and our code. - if (deck.hasField("DENSITY")) { - const std::vector& d = deck.getDENSITY().densities_[region_number_]; - enum { ECL_oil = 0, ECL_water = 1, ECL_gas = 2 }; + // Surface densities. Accounting for different orders in eclipse and our code. + if (deck.hasField("DENSITY")) { + const std::vector& d = deck.getDENSITY().densities_[region_number_]; + enum { ECL_oil = 0, ECL_water = 1, ECL_gas = 2 }; if (phase_usage_.phase_used[Aqua]) { densities_[phase_usage_.phase_pos[Aqua]] = d[ECL_water]; } @@ -60,9 +61,9 @@ namespace Opm if (phase_usage_.phase_used[Liquid]) { densities_[phase_usage_.phase_pos[Liquid]] = d[ECL_oil]; } - } else { - THROW("Input is missing DENSITY\n"); - } + } else { + THROW("Input is missing DENSITY\n"); + } // Set the properties. props_.resize(phase_usage_.num_phases); @@ -78,7 +79,11 @@ namespace Opm // Oil PVT if (phase_usage_.phase_used[Liquid]) { if (deck.hasField("PVDO")) { - props_[phase_usage_.phase_pos[Liquid]].reset(new SinglePvtDead(deck.getPVDO().pvdo_, samples)); + if (samples > 0) { + props_[phase_usage_.phase_pos[Liquid]].reset(new SinglePvtDeadSpline(deck.getPVDO().pvdo_, samples)); + } else { + props_[phase_usage_.phase_pos[Liquid]].reset(new SinglePvtDead(deck.getPVDO().pvdo_)); + } } else if (deck.hasField("PVTO")) { props_[phase_usage_.phase_pos[Liquid]].reset(new SinglePvtLiveOil(deck.getPVTO().pvto_)); } else if (deck.hasField("PVCDO")) { @@ -87,10 +92,14 @@ namespace Opm THROW("Input is missing PVDO or PVTO\n"); } } - // Gas PVT + // Gas PVT if (phase_usage_.phase_used[Vapour]) { if (deck.hasField("PVDG")) { - props_[phase_usage_.phase_pos[Vapour]].reset(new SinglePvtDead(deck.getPVDG().pvdg_, samples)); + if (samples > 0) { + props_[phase_usage_.phase_pos[Vapour]].reset(new SinglePvtDeadSpline(deck.getPVDG().pvdg_, samples)); + } else { + props_[phase_usage_.phase_pos[Vapour]].reset(new SinglePvtDead(deck.getPVDG().pvdg_)); + } } else if (deck.hasField("PVTG")) { props_[phase_usage_.phase_pos[Vapour]].reset(new SinglePvtLiveGas(deck.getPVTG().pvtg_)); } else { diff --git a/opm/core/fluid/blackoil/BlackoilPvtProperties.hpp b/opm/core/fluid/blackoil/BlackoilPvtProperties.hpp index 191c6fa8..8633fd9e 100644 --- a/opm/core/fluid/blackoil/BlackoilPvtProperties.hpp +++ b/opm/core/fluid/blackoil/BlackoilPvtProperties.hpp @@ -47,7 +47,13 @@ namespace Opm BlackoilPvtProperties(); /// Initialize from deck. - void init(const EclipseGridParser& deck, const int samples = 16); + /// \param deck An input deck. + /// \param samples If greater than zero, indicates the number of + /// uniform samples to be taken from monotone spline + /// curves interpolating the fluid data. + /// Otherwise, interpolate linearly in the original + /// data without fitting a spline. + void init(const EclipseGridParser& deck, const int samples); /// Number of active phases. int numPhases() const; @@ -64,7 +70,7 @@ namespace Opm /// Densities of stock components at surface conditions. /// \return Array of size numPhases(). - const double* surfaceDensities() const; + const double* surfaceDensities() const; /// Viscosity as a function of p and z. void mu(const int n, @@ -105,11 +111,11 @@ namespace Opm PhaseUsage phase_usage_; - int region_number_; + int region_number_; std::vector > props_; - double densities_[MaxNumPhases]; + double densities_[MaxNumPhases]; mutable std::vector data1_; mutable std::vector data2_; }; diff --git a/opm/core/fluid/blackoil/SinglePvtConstCompr.hpp b/opm/core/fluid/blackoil/SinglePvtConstCompr.hpp index 557bc1e8..9226d0d8 100644 --- a/opm/core/fluid/blackoil/SinglePvtConstCompr.hpp +++ b/opm/core/fluid/blackoil/SinglePvtConstCompr.hpp @@ -40,12 +40,12 @@ namespace Opm public: typedef std::vector > table_t; - SinglePvtConstCompr(const table_t& pvtw) + SinglePvtConstCompr(const table_t& pvtw) { - const int region_number = 0; - if (pvtw.size() != 1) { - THROW("More than one PVD-region"); - } + const int region_number = 0; + if (pvtw.size() != 1) { + THROW("More than one PVD-region"); + } ref_press_ = pvtw[region_number][0]; ref_B_ = pvtw[region_number][1]; comp_ = pvtw[region_number][2]; @@ -53,7 +53,7 @@ namespace Opm visc_comp_ = pvtw[region_number][4]; } - SinglePvtConstCompr(double visc) + SinglePvtConstCompr(double visc) : ref_press_(0.0), ref_B_(1.0), comp_(0.0), @@ -62,7 +62,7 @@ namespace Opm { } - virtual ~SinglePvtConstCompr() + virtual ~SinglePvtConstCompr() { } diff --git a/opm/core/fluid/blackoil/SinglePvtDead.cpp b/opm/core/fluid/blackoil/SinglePvtDead.cpp index 1a0993be..34aeaf6e 100644 --- a/opm/core/fluid/blackoil/SinglePvtDead.cpp +++ b/opm/core/fluid/blackoil/SinglePvtDead.cpp @@ -1,5 +1,5 @@ /* - Copyright 2010, 2011, 2012 SINTEF ICT, Applied Mathematics. + Copyright 2012 SINTEF ICT, Applied Mathematics. This file is part of the Open Porous Media project (OPM). @@ -17,8 +17,8 @@ along with OPM. If not, see . */ + #include -#include #include // Extra includes for debug dumping of tables. @@ -33,25 +33,25 @@ namespace Opm // Member functions //------------------------------------------------------------------------- /// Constructor - SinglePvtDead::SinglePvtDead(const table_t& pvd_table, const int samples) + SinglePvtDead::SinglePvtDead(const table_t& pvd_table) { - const int region_number = 0; - if (pvd_table.size() != 1) { - THROW("More than one PVT-region"); - } + const int region_number = 0; + if (pvd_table.size() != 1) { + THROW("More than one PVT-region"); + } - // Copy data - const int sz = pvd_table[region_number][0].size(); + // Copy data + const int sz = pvd_table[region_number][0].size(); std::vector press(sz); std::vector B_inv(sz); std::vector visc(sz); - for (int i = 0; i < sz; ++i) { + for (int i = 0; i < sz; ++i) { press[i] = pvd_table[region_number][0][i]; B_inv[i] = 1.0 / pvd_table[region_number][1][i]; visc[i] = pvd_table[region_number][2][i]; - } - buildUniformMonotoneTable(press, B_inv, samples, one_over_B_); - buildUniformMonotoneTable(press, visc, samples, viscosity_); + } + one_over_B_ = NonuniformTableLinear(press, B_inv); + viscosity_ = NonuniformTableLinear(press, visc); // Dumping the created tables. // static int count = 0; diff --git a/opm/core/fluid/blackoil/SinglePvtDead.hpp b/opm/core/fluid/blackoil/SinglePvtDead.hpp index 90a7d695..db86b39d 100644 --- a/opm/core/fluid/blackoil/SinglePvtDead.hpp +++ b/opm/core/fluid/blackoil/SinglePvtDead.hpp @@ -1,5 +1,5 @@ /* - Copyright 2010, 2011, 2012 SINTEF ICT, Applied Mathematics. + Copyright 2012 SINTEF ICT, Applied Mathematics. This file is part of the Open Porous Media project (OPM). @@ -22,7 +22,7 @@ #include -#include +#include #include namespace Opm @@ -36,9 +36,9 @@ namespace Opm class SinglePvtDead : public SinglePvtInterface { public: - typedef std::vector > > table_t; - SinglePvtDead(const table_t& pvd_table, const int samples = 16); - virtual ~SinglePvtDead(); + typedef std::vector > > table_t; + SinglePvtDead(const table_t& pvd_table); + virtual ~SinglePvtDead(); /// Viscosity as a function of p and z. virtual void mu(const int n, @@ -72,12 +72,12 @@ namespace Opm double* output_R, double* output_dRdp) const; private: - // PVT properties of dry gas or dead oil - UniformTableLinear one_over_B_; - UniformTableLinear viscosity_; + // PVT properties of dry gas or dead oil + NonuniformTableLinear one_over_B_; + NonuniformTableLinear viscosity_; }; } -#endif // OPM_SINGLEPVTDEAD_HEADER_INCLUDED +#endif // OPM_SINGLEPVTDEAD_HEADER_INCLUDED diff --git a/opm/core/fluid/blackoil/SinglePvtDeadSpline.cpp b/opm/core/fluid/blackoil/SinglePvtDeadSpline.cpp new file mode 100644 index 00000000..132ffa51 --- /dev/null +++ b/opm/core/fluid/blackoil/SinglePvtDeadSpline.cpp @@ -0,0 +1,127 @@ +/* + Copyright 2010, 2011, 2012 SINTEF ICT, Applied Mathematics. + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ + +#include +#include +#include + +// Extra includes for debug dumping of tables. +// #include +// #include +// #include + +namespace Opm +{ + + //------------------------------------------------------------------------ + // Member functions + //------------------------------------------------------------------------- + + /// Constructor + SinglePvtDeadSpline::SinglePvtDeadSpline(const table_t& pvd_table, const int samples) + { + const int region_number = 0; + if (pvd_table.size() != 1) { + THROW("More than one PVT-region"); + } + + // Copy data + const int sz = pvd_table[region_number][0].size(); + std::vector press(sz); + std::vector B_inv(sz); + std::vector visc(sz); + for (int i = 0; i < sz; ++i) { + press[i] = pvd_table[region_number][0][i]; + B_inv[i] = 1.0 / pvd_table[region_number][1][i]; + visc[i] = pvd_table[region_number][2][i]; + } + buildUniformMonotoneTable(press, B_inv, samples, one_over_B_); + buildUniformMonotoneTable(press, visc, samples, viscosity_); + + // Dumping the created tables. +// static int count = 0; +// std::ofstream os((std::string("dump-") + boost::lexical_cast(count++)).c_str()); +// os.precision(15); +// os << "1/B\n\n" << one_over_B_ +// << "\n\nvisc\n\n" << viscosity_ << std::endl; + } + + // Destructor + SinglePvtDeadSpline::~SinglePvtDeadSpline() + { + } + + + + void SinglePvtDeadSpline::mu(const int n, + const double* p, + const double* /*z*/, + double* output_mu) const + { +// #pragma omp parallel for + for (int i = 0; i < n; ++i) { + output_mu[i] = viscosity_(p[i]); + } + } + + void SinglePvtDeadSpline::B(const int n, + const double* p, + const double* /*z*/, + double* output_B) const + { +// #pragma omp parallel for + for (int i = 0; i < n; ++i) { + output_B[i] = 1.0/one_over_B_(p[i]); + } + } + + void SinglePvtDeadSpline::dBdp(const int n, + const double* p, + const double* /*z*/, + double* output_B, + double* output_dBdp) const + { + B(n, p, 0, output_B); +// #pragma omp parallel for + for (int i = 0; i < n; ++i) { + double Bg = output_B[i]; + output_dBdp[i] = -Bg*Bg*one_over_B_.derivative(p[i]); + } + } + + + void SinglePvtDeadSpline::R(const int n, + const double* /*p*/, + const double* /*z*/, + double* output_R) const + { + std::fill(output_R, output_R + n, 0.0); + } + + void SinglePvtDeadSpline::dRdp(const int n, + const double* /*p*/, + const double* /*z*/, + double* output_R, + double* output_dRdp) const + { + std::fill(output_R, output_R + n, 0.0); + std::fill(output_dRdp, output_dRdp + n, 0.0); + } + +} diff --git a/opm/core/fluid/blackoil/SinglePvtDeadSpline.hpp b/opm/core/fluid/blackoil/SinglePvtDeadSpline.hpp new file mode 100644 index 00000000..18d7ecfe --- /dev/null +++ b/opm/core/fluid/blackoil/SinglePvtDeadSpline.hpp @@ -0,0 +1,84 @@ +/* + Copyright 2010, 2011, 2012 SINTEF ICT, Applied Mathematics. + + 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 . +*/ + +#ifndef OPM_SINGLEPVTDEADSPLINE_HEADER_INCLUDED +#define OPM_SINGLEPVTDEADSPLINE_HEADER_INCLUDED + + +#include +#include +#include + +namespace Opm +{ + + /// Class for immiscible dead oil and dry gas. + /// For all the virtual methods, the following apply: p and z + /// are expected to be of size n and n*num_phases, respectively. + /// Output arrays shall be of size n, and must be valid before + /// calling the method. + class SinglePvtDeadSpline : public SinglePvtInterface + { + public: + typedef std::vector > > table_t; + + SinglePvtDeadSpline(const table_t& pvd_table, const int samples); + virtual ~SinglePvtDeadSpline(); + + /// Viscosity as a function of p and z. + virtual void mu(const int n, + const double* p, + const double* z, + double* output_mu) const; + + /// Formation volume factor as a function of p and z. + virtual void B(const int n, + const double* p, + const double* z, + double* output_B) const; + + /// Formation volume factor and p-derivative as functions of p and z. + virtual void dBdp(const int n, + const double* p, + const double* z, + double* output_B, + double* output_dBdp) const; + + /// Solution factor as a function of p and z. + virtual void R(const int n, + const double* p, + const double* z, + double* output_R) const; + + /// Solution factor and p-derivative as functions of p and z. + virtual void dRdp(const int n, + const double* p, + const double* z, + double* output_R, + double* output_dRdp) const; + private: + // PVT properties of dry gas or dead oil + UniformTableLinear one_over_B_; + UniformTableLinear viscosity_; + }; + +} + +#endif // OPM_SINGLEPVTDEADSPLINE_HEADER_INCLUDED + diff --git a/opm/core/fluid/blackoil/SinglePvtInterface.hpp b/opm/core/fluid/blackoil/SinglePvtInterface.hpp index d9369d33..7bcbd44b 100644 --- a/opm/core/fluid/blackoil/SinglePvtInterface.hpp +++ b/opm/core/fluid/blackoil/SinglePvtInterface.hpp @@ -32,7 +32,7 @@ namespace Opm public: SinglePvtInterface(); - virtual ~SinglePvtInterface(); + virtual ~SinglePvtInterface(); /// \param[in] num_phases The number of active phases. /// \param[in] phase_pos Array of BlackpoilPhases::MaxNumPhases diff --git a/opm/core/fluid/blackoil/SinglePvtLiveGas.cpp b/opm/core/fluid/blackoil/SinglePvtLiveGas.cpp index 2526d34c..a6785467 100644 --- a/opm/core/fluid/blackoil/SinglePvtLiveGas.cpp +++ b/opm/core/fluid/blackoil/SinglePvtLiveGas.cpp @@ -1,13 +1,13 @@ //=========================================================================== -// -// File: MiscibilityLiveGas.cpp -// -// Created: Wed Feb 10 09:21:53 2010 -// +// +// File: MiscibilityLiveGas.cpp +// +// Created: Wed Feb 10 09:21:53 2010 +// // Author: Bjørn Spjelkavik -// +// // Revision: $Id$ -// +// //=========================================================================== /* Copyright 2010 SINTEF ICT, Applied Mathematics. @@ -47,37 +47,37 @@ namespace Opm /// Constructor SinglePvtLiveGas::SinglePvtLiveGas(const table_t& pvtg) { - // GAS, PVTG - const int region_number = 0; - if (pvtg.size() != 1) { - THROW("More than one PVD-region"); - } - saturated_gas_table_.resize(4); - const int sz = pvtg[region_number].size(); - for (int k=0; k<4; ++k) { - saturated_gas_table_[k].resize(sz); - } + // GAS, PVTG + const int region_number = 0; + if (pvtg.size() != 1) { + THROW("More than one PVD-region"); + } + saturated_gas_table_.resize(4); + const int sz = pvtg[region_number].size(); + for (int k=0; k<4; ++k) { + saturated_gas_table_[k].resize(sz); + } - for (int i=0; i saturated_gas_table_[0][ltp]) { - return linearInterpolationExtrap(undersat_gas_tables_[ltp][0], - undersat_gas_tables_[ltp][item], - maxR); - } + // Extrapolate from last table section + int ltp = saturated_gas_table_[0].size() - 1; + if (is+1 == ltp && press > saturated_gas_table_[0][ltp]) { + return linearInterpolationExtrap(undersat_gas_tables_[ltp][0], + undersat_gas_tables_[ltp][item], + maxR); + } - // Interpolate between table sections - double w = (press - saturated_gas_table_[0][is]) / - (saturated_gas_table_[0][is+1] - - saturated_gas_table_[0][is]); - if (undersat_gas_tables_[is][0].size() < 2) { - double val = saturated_gas_table_[item][is] + - w*(saturated_gas_table_[item][is+1] - - saturated_gas_table_[item][is]); - return val; - } - double val1 = - linearInterpolationExtrap(undersat_gas_tables_[is][0], - undersat_gas_tables_[is][item], - maxR); - double val2 = - linearInterpolationExtrap(undersat_gas_tables_[is+1][0], - undersat_gas_tables_[is+1][item], - maxR); - double val = val1 + w*(val2 - val1); - return val; - } - } + // Interpolate between table sections + double w = (press - saturated_gas_table_[0][is]) / + (saturated_gas_table_[0][is+1] - + saturated_gas_table_[0][is]); + if (undersat_gas_tables_[is][0].size() < 2) { + double val = saturated_gas_table_[item][is] + + w*(saturated_gas_table_[item][is+1] - + saturated_gas_table_[item][is]); + return val; + } + double val1 = + linearInterpolationExtrap(undersat_gas_tables_[is][0], + undersat_gas_tables_[is][item], + maxR); + double val2 = + linearInterpolationExtrap(undersat_gas_tables_[is+1][0], + undersat_gas_tables_[is+1][item], + maxR); + double val = val1 + w*(val2 - val1); + return val; + } + } } diff --git a/opm/core/fluid/blackoil/SinglePvtLiveGas.hpp b/opm/core/fluid/blackoil/SinglePvtLiveGas.hpp index 3eff52f4..c7761551 100644 --- a/opm/core/fluid/blackoil/SinglePvtLiveGas.hpp +++ b/opm/core/fluid/blackoil/SinglePvtLiveGas.hpp @@ -33,10 +33,10 @@ namespace Opm class SinglePvtLiveGas : public SinglePvtInterface { public: - typedef std::vector > > table_t; + typedef std::vector > > table_t; - SinglePvtLiveGas(const table_t& pvto); - virtual ~SinglePvtLiveGas(); + SinglePvtLiveGas(const table_t& pvto); + virtual ~SinglePvtLiveGas(); /// Viscosity as a function of p and z. virtual void mu(const int n, @@ -76,14 +76,14 @@ namespace Opm double evalR(double press, const double* surfvol) const; void evalRDeriv(double press, const double* surfvol, double& R, double& dRdp) const; - // item: 1=>B 2=>mu; - double miscible_gas(const double press, + // item: 1=>B 2=>mu; + double miscible_gas(const double press, const double* surfvol, const int item, - const bool deriv = false) const; - // PVT properties of wet gas (with vaporised oil) - std::vector > saturated_gas_table_; - std::vector > > undersat_gas_tables_; + const bool deriv = false) const; + // PVT properties of wet gas (with vaporised oil) + std::vector > saturated_gas_table_; + std::vector > > undersat_gas_tables_; }; diff --git a/opm/core/fluid/blackoil/SinglePvtLiveOil.cpp b/opm/core/fluid/blackoil/SinglePvtLiveOil.cpp index 2a393839..990ae114 100644 --- a/opm/core/fluid/blackoil/SinglePvtLiveOil.cpp +++ b/opm/core/fluid/blackoil/SinglePvtLiveOil.cpp @@ -38,122 +38,122 @@ namespace Opm /// Constructor SinglePvtLiveOil::SinglePvtLiveOil(const table_t& pvto) { - // OIL, PVTO - const int region_number = 0; - if (pvto.size() != 1) { - THROW("More than one PVD-region"); - } - saturated_oil_table_.resize(4); - const int sz = pvto[region_number].size(); - for (int k=0; k<4; ++k) { - saturated_oil_table_[k].resize(sz); - } - for (int i=0; i 1) { - iPrev = i; - continue; - } - - bool flagPrev = (iPrev >= 0); - bool flagNext = true; - if (iNext < i) { - iPrev = iNext; - flagPrev = true; - iNext = i+1; - while (undersat_oil_tables_[iNext][0].size() < 2) { - ++iNext; - } - } - double slopePrevBinv = 0.0; - double slopePrevVisc = 0.0; - double slopeNextBinv = 0.0; - double slopeNextVisc = 0.0; - while (flagPrev || flagNext) { - double pressure0 = undersat_oil_tables_[i][0].back(); - double pressure = 1.0e47; - if (flagPrev) { - std::vector::iterator itPrev = upper_bound(undersat_oil_tables_[iPrev][0].begin(), - undersat_oil_tables_[iPrev][0].end(),pressure0+1.); - if (itPrev == undersat_oil_tables_[iPrev][0].end()) { - --itPrev; // Extrapolation ... - } else if (itPrev == undersat_oil_tables_[iPrev][0].begin()) { - ++itPrev; - } - if (itPrev == undersat_oil_tables_[iPrev][0].end()-1) { - flagPrev = false; // Last data set for "prev" ... - } - double dPPrev = *itPrev - *(itPrev-1); - pressure = *itPrev; - int index = int(itPrev - undersat_oil_tables_[iPrev][0].begin()); - slopePrevBinv = (undersat_oil_tables_[iPrev][1][index] - undersat_oil_tables_[iPrev][1][index-1])/dPPrev; - slopePrevVisc = (undersat_oil_tables_[iPrev][2][index] - undersat_oil_tables_[iPrev][2][index-1])/dPPrev; - } - if (flagNext) { - std::vector::iterator itNext = upper_bound(undersat_oil_tables_[iNext][0].begin(), - undersat_oil_tables_[iNext][0].end(),pressure0+1.); - if (itNext == undersat_oil_tables_[iNext][0].end()) { - --itNext; // Extrapolation ... - } else if (itNext == undersat_oil_tables_[iNext][0].begin()) { - ++itNext; - } - if (itNext == undersat_oil_tables_[iNext][0].end()-1) { - flagNext = false; // Last data set for "next" ... - } - double dPNext = *itNext - *(itNext-1); - if (flagPrev) { - pressure = std::min(pressure,*itNext); - } else { - pressure = *itNext; - } - int index = int(itNext - undersat_oil_tables_[iNext][0].begin()); - slopeNextBinv = (undersat_oil_tables_[iNext][1][index] - undersat_oil_tables_[iNext][1][index-1])/dPNext; - slopeNextVisc = (undersat_oil_tables_[iNext][2][index] - undersat_oil_tables_[iNext][2][index-1])/dPNext; - } - double dP = pressure - pressure0; - if (iPrev >= 0) { - double w = (saturated_oil_table_[3][i] - saturated_oil_table_[3][iPrev]) / - (saturated_oil_table_[3][iNext] - saturated_oil_table_[3][iPrev]); - undersat_oil_tables_[i][0].push_back(pressure0+dP); - undersat_oil_tables_[i][1].push_back(undersat_oil_tables_[i][1].back() + - dP*(slopePrevBinv+w*(slopeNextBinv-slopePrevBinv))); - undersat_oil_tables_[i][2].push_back(undersat_oil_tables_[i][2].back() + - dP*(slopePrevVisc+w*(slopeNextVisc-slopePrevVisc))); - } else { - undersat_oil_tables_[i][0].push_back(pressure0+dP); - undersat_oil_tables_[i][1].push_back(undersat_oil_tables_[i][1].back()+dP*slopeNextBinv); - undersat_oil_tables_[i][2].push_back(undersat_oil_tables_[i][2].back()+dP*slopeNextVisc); - } - } - } + // OIL, PVTO + const int region_number = 0; + if (pvto.size() != 1) { + THROW("More than one PVD-region"); + } + saturated_oil_table_.resize(4); + const int sz = pvto[region_number].size(); + for (int k=0; k<4; ++k) { + saturated_oil_table_[k].resize(sz); + } + for (int i=0; i 1) { + iPrev = i; + continue; + } + + bool flagPrev = (iPrev >= 0); + bool flagNext = true; + if (iNext < i) { + iPrev = iNext; + flagPrev = true; + iNext = i+1; + while (undersat_oil_tables_[iNext][0].size() < 2) { + ++iNext; + } + } + double slopePrevBinv = 0.0; + double slopePrevVisc = 0.0; + double slopeNextBinv = 0.0; + double slopeNextVisc = 0.0; + while (flagPrev || flagNext) { + double pressure0 = undersat_oil_tables_[i][0].back(); + double pressure = 1.0e47; + if (flagPrev) { + std::vector::iterator itPrev = upper_bound(undersat_oil_tables_[iPrev][0].begin(), + undersat_oil_tables_[iPrev][0].end(),pressure0+1.); + if (itPrev == undersat_oil_tables_[iPrev][0].end()) { + --itPrev; // Extrapolation ... + } else if (itPrev == undersat_oil_tables_[iPrev][0].begin()) { + ++itPrev; + } + if (itPrev == undersat_oil_tables_[iPrev][0].end()-1) { + flagPrev = false; // Last data set for "prev" ... + } + double dPPrev = *itPrev - *(itPrev-1); + pressure = *itPrev; + int index = int(itPrev - undersat_oil_tables_[iPrev][0].begin()); + slopePrevBinv = (undersat_oil_tables_[iPrev][1][index] - undersat_oil_tables_[iPrev][1][index-1])/dPPrev; + slopePrevVisc = (undersat_oil_tables_[iPrev][2][index] - undersat_oil_tables_[iPrev][2][index-1])/dPPrev; + } + if (flagNext) { + std::vector::iterator itNext = upper_bound(undersat_oil_tables_[iNext][0].begin(), + undersat_oil_tables_[iNext][0].end(),pressure0+1.); + if (itNext == undersat_oil_tables_[iNext][0].end()) { + --itNext; // Extrapolation ... + } else if (itNext == undersat_oil_tables_[iNext][0].begin()) { + ++itNext; + } + if (itNext == undersat_oil_tables_[iNext][0].end()-1) { + flagNext = false; // Last data set for "next" ... + } + double dPNext = *itNext - *(itNext-1); + if (flagPrev) { + pressure = std::min(pressure,*itNext); + } else { + pressure = *itNext; + } + int index = int(itNext - undersat_oil_tables_[iNext][0].begin()); + slopeNextBinv = (undersat_oil_tables_[iNext][1][index] - undersat_oil_tables_[iNext][1][index-1])/dPNext; + slopeNextVisc = (undersat_oil_tables_[iNext][2][index] - undersat_oil_tables_[iNext][2][index-1])/dPNext; + } + double dP = pressure - pressure0; + if (iPrev >= 0) { + double w = (saturated_oil_table_[3][i] - saturated_oil_table_[3][iPrev]) / + (saturated_oil_table_[3][iNext] - saturated_oil_table_[3][iPrev]); + undersat_oil_tables_[i][0].push_back(pressure0+dP); + undersat_oil_tables_[i][1].push_back(undersat_oil_tables_[i][1].back() + + dP*(slopePrevBinv+w*(slopeNextBinv-slopePrevBinv))); + undersat_oil_tables_[i][2].push_back(undersat_oil_tables_[i][2].back() + + dP*(slopePrevVisc+w*(slopeNextVisc-slopePrevVisc))); + } else { + undersat_oil_tables_[i][0].push_back(pressure0+dP); + undersat_oil_tables_[i][1].push_back(undersat_oil_tables_[i][1].back()+dP*slopeNextBinv); + undersat_oil_tables_[i][2].push_back(undersat_oil_tables_[i][2].back()+dP*slopeNextVisc); + } + } + } } /// Destructor. @@ -238,30 +238,30 @@ namespace Opm double SinglePvtLiveOil::evalB(double press, const double* surfvol) const { // if (surfvol[phase_pos_[Liquid]] == 0.0) return 1.0; // To handle no-oil case. - return 1.0/miscible_oil(press, surfvol, 1, false); + return 1.0/miscible_oil(press, surfvol, 1, false); } void SinglePvtLiveOil::evalBDeriv(const double press, const double* surfvol, double& Bval, double& dBdpval) const { - Bval = evalB(press, surfvol); - dBdpval = -Bval*Bval*miscible_oil(press, surfvol, 1, true); + Bval = evalB(press, surfvol); + dBdpval = -Bval*Bval*miscible_oil(press, surfvol, 1, true); } double SinglePvtLiveOil::evalR(double press, const double* surfvol) const { if (surfvol[phase_pos_[Vapour]] == 0.0) { return 0.0; - } - double Rval = linearInterpolationExtrap(saturated_oil_table_[0], - saturated_oil_table_[3], press); - double maxR = surfvol[phase_pos_[Vapour]]/surfvol[phase_pos_[Liquid]]; - if (Rval < maxR ) { // Saturated case - return Rval; - } else { - return maxR; // Undersaturated case - } + } + double Rval = linearInterpolationExtrap(saturated_oil_table_[0], + saturated_oil_table_[3], press); + double maxR = surfvol[phase_pos_[Vapour]]/surfvol[phase_pos_[Liquid]]; + if (Rval < maxR ) { // Saturated case + return Rval; + } else { + return maxR; // Undersaturated case + } } void SinglePvtLiveOil::evalRDeriv(const double press, const double* surfvol, @@ -272,19 +272,19 @@ namespace Opm dRdpval = 0.0; return; } - Rval = linearInterpolationExtrap(saturated_oil_table_[0], + Rval = linearInterpolationExtrap(saturated_oil_table_[0], saturated_oil_table_[3], press); - double maxR = surfvol[phase_pos_[Vapour]]/surfvol[phase_pos_[Liquid]]; - if (Rval < maxR ) { + double maxR = surfvol[phase_pos_[Vapour]]/surfvol[phase_pos_[Liquid]]; + if (Rval < maxR ) { // Saturated case - dRdpval = linearInterpolDerivative(saturated_oil_table_[0], - saturated_oil_table_[3], - press); - } else { + dRdpval = linearInterpolDerivative(saturated_oil_table_[0], + saturated_oil_table_[3], + press); + } else { // Undersaturated case Rval = maxR; - dRdpval = 0.0; - } + dRdpval = 0.0; + } } @@ -293,57 +293,57 @@ namespace Opm const int item, const bool deriv) const { - int section; - double Rval = linearInterpolationExtrap(saturated_oil_table_[0], + int section; + double Rval = linearInterpolationExtrap(saturated_oil_table_[0], saturated_oil_table_[3], press, section); - double maxR = (surfvol[phase_pos_[Liquid]] == 0.0) ? 0.0 : surfvol[phase_pos_[Vapour]]/surfvol[phase_pos_[Liquid]]; - if (deriv) { - if (Rval < maxR ) { // Saturated case - return linearInterpolDerivative(saturated_oil_table_[0], - saturated_oil_table_[item], - press); - } else { // Undersaturated case - int is = tableIndex(saturated_oil_table_[3], maxR); - double w = (maxR - saturated_oil_table_[3][is]) / - (saturated_oil_table_[3][is+1] - saturated_oil_table_[3][is]); - ASSERT(undersat_oil_tables_[is][0].size() >= 2); - ASSERT(undersat_oil_tables_[is+1][0].size() >= 2); - double val1 = - linearInterpolDerivative(undersat_oil_tables_[is][0], - undersat_oil_tables_[is][item], - press); - double val2 = - linearInterpolDerivative(undersat_oil_tables_[is+1][0], - undersat_oil_tables_[is+1][item], - press); - double val = val1 + w*(val2 - val1); - return val; - } - } else { - if (Rval < maxR ) { // Saturated case - return linearInterpolationExtrap(saturated_oil_table_[0], - saturated_oil_table_[item], - press); - } else { // Undersaturated case - // Interpolate between table sections + double maxR = (surfvol[phase_pos_[Liquid]] == 0.0) ? 0.0 : surfvol[phase_pos_[Vapour]]/surfvol[phase_pos_[Liquid]]; + if (deriv) { + if (Rval < maxR ) { // Saturated case + return linearInterpolDerivative(saturated_oil_table_[0], + saturated_oil_table_[item], + press); + } else { // Undersaturated case int is = tableIndex(saturated_oil_table_[3], maxR); - double w = (maxR - saturated_oil_table_[3][is]) / - (saturated_oil_table_[3][is+1] - saturated_oil_table_[3][is]); + double w = (maxR - saturated_oil_table_[3][is]) / + (saturated_oil_table_[3][is+1] - saturated_oil_table_[3][is]); ASSERT(undersat_oil_tables_[is][0].size() >= 2); ASSERT(undersat_oil_tables_[is+1][0].size() >= 2); - double val1 = - linearInterpolationExtrap(undersat_oil_tables_[is][0], - undersat_oil_tables_[is][item], - press); - double val2 = - linearInterpolationExtrap(undersat_oil_tables_[is+1][0], - undersat_oil_tables_[is+1][item], - press); - double val = val1 + w*(val2 - val1); - return val; - } - } + double val1 = + linearInterpolDerivative(undersat_oil_tables_[is][0], + undersat_oil_tables_[is][item], + press); + double val2 = + linearInterpolDerivative(undersat_oil_tables_[is+1][0], + undersat_oil_tables_[is+1][item], + press); + double val = val1 + w*(val2 - val1); + return val; + } + } else { + if (Rval < maxR ) { // Saturated case + return linearInterpolationExtrap(saturated_oil_table_[0], + saturated_oil_table_[item], + press); + } else { // Undersaturated case + // Interpolate between table sections + int is = tableIndex(saturated_oil_table_[3], maxR); + double w = (maxR - saturated_oil_table_[3][is]) / + (saturated_oil_table_[3][is+1] - saturated_oil_table_[3][is]); + ASSERT(undersat_oil_tables_[is][0].size() >= 2); + ASSERT(undersat_oil_tables_[is+1][0].size() >= 2); + double val1 = + linearInterpolationExtrap(undersat_oil_tables_[is][0], + undersat_oil_tables_[is][item], + press); + double val2 = + linearInterpolationExtrap(undersat_oil_tables_[is+1][0], + undersat_oil_tables_[is+1][item], + press); + double val = val1 + w*(val2 - val1); + return val; + } + } } } // namespace Opm diff --git a/opm/core/fluid/blackoil/SinglePvtLiveOil.hpp b/opm/core/fluid/blackoil/SinglePvtLiveOil.hpp index f8be6353..cef9595e 100644 --- a/opm/core/fluid/blackoil/SinglePvtLiveOil.hpp +++ b/opm/core/fluid/blackoil/SinglePvtLiveOil.hpp @@ -34,10 +34,10 @@ namespace Opm class SinglePvtLiveOil : public SinglePvtInterface { public: - typedef std::vector > > table_t; + typedef std::vector > > table_t; - SinglePvtLiveOil(const table_t& pvto); - virtual ~SinglePvtLiveOil(); + SinglePvtLiveOil(const table_t& pvto); + virtual ~SinglePvtLiveOil(); /// Viscosity as a function of p and z. virtual void mu(const int n, @@ -77,15 +77,15 @@ namespace Opm double evalR(double press, const double* surfvol) const; void evalRDeriv(double press, const double* surfvol, double& R, double& dRdp) const; - // item: 1=>1/B 2=>mu; - double miscible_oil(const double press, + // item: 1=>1/B 2=>mu; + double miscible_oil(const double press, const double* surfvol, const int item, - const bool deriv = false) const; + const bool deriv = false) const; - // PVT properties of live oil (with dissolved gas) - std::vector > saturated_oil_table_; - std::vector > > undersat_oil_tables_; + // PVT properties of live oil (with dissolved gas) + std::vector > saturated_oil_table_; + std::vector > > undersat_oil_tables_; }; } diff --git a/opm/core/grid/cpgpreprocess/preprocess.h b/opm/core/grid/cpgpreprocess/preprocess.h index 741d1b3b..a0db38ce 100644 --- a/opm/core/grid/cpgpreprocess/preprocess.h +++ b/opm/core/grid/cpgpreprocess/preprocess.h @@ -56,8 +56,9 @@ extern "C" { struct grdecl { int dims[3]; /**< Cartesian box dimensions. */ const double *coord; /**< Pillar end-points. */ - const double *zcorn; /**< Explicit "active" map. May be NULL.*/ - const int *actnum; /**< Corner-point depths. */ + const double *zcorn; /**< Corner-point depths. */ + const int *actnum; /**< Explicit "active" map. May be NULL.*/ + const double *mapaxes; /**< 6 Element rotation vector - can be NULL. */ }; /** diff --git a/opm/core/linalg/LinearSolverIstl.cpp b/opm/core/linalg/LinearSolverIstl.cpp index 3c4435e8..a3f9583e 100644 --- a/opm/core/linalg/LinearSolverIstl.cpp +++ b/opm/core/linalg/LinearSolverIstl.cpp @@ -26,6 +26,10 @@ #include +// Silence compatibility warning from DUNE headers since we don't use +// the deprecated member anyway (in this compilation unit) +#define DUNE_COMMON_FIELDVECTOR_SIZE_IS_METHOD 1 + // TODO: clean up includes. #include #include diff --git a/opm/core/newwells.c b/opm/core/newwells.c index ac416e5a..c28e8566 100644 --- a/opm/core/newwells.c +++ b/opm/core/newwells.c @@ -593,3 +593,78 @@ clear_well_controls(int well_index, struct Wells *W) W->ctrls[well_index]->num = 0; } } + + +/* ---------------------------------------------------------------------- */ +struct Wells * +clone_wells(const struct Wells *W) +/* ---------------------------------------------------------------------- */ +{ + int c, np, nperf, ok, pos, w; + double target; + const int *cells; + const double *WI, *comp_frac, *distr; + enum WellControlType type; + const struct WellControls *ctrls; + + struct Wells *ret; + + if (W == NULL) { + ret = NULL; + } + else { + np = W->number_of_phases; + ret = create_wells(W->number_of_phases, W->number_of_wells, + W->well_connpos[ W->number_of_wells ]); + + if (ret != NULL) { + pos = W->well_connpos[ 0 ]; + ok = 1; + + for (w = 0; ok && (w < W->number_of_wells); w++) { + nperf = W->well_connpos[w + 1] - pos; + cells = W->well_cells + pos; + + WI = W->WI != NULL ? W->WI + pos : NULL; + comp_frac = W->comp_frac != NULL ? W->comp_frac + w*np : NULL; + + ok = add_well(W->type[ w ], W->depth_ref[ w ], nperf, + comp_frac, cells, WI, W->name[ w ], ret); + + /* Capacity should be sufficient from create_wells() */ + assert (ok); + + ctrls = W->ctrls[ w ]; + + if (ok) { + ok = well_controls_reserve(ctrls->num, np, ret->ctrls[ w ]); + + for (c = 0; ok && (c < ctrls->num); c++) { + type = ctrls->type [ c ]; + target = ctrls->target[ c ]; + distr = & ctrls->distr [ c * np ]; + + ok = append_well_controls(type, target, distr, w, ret); + + /* Capacity *should* be sufficient from + * well_controls_reserve() */ + assert (ok); + } + } + + if (ok) { + set_current_control(w, ctrls->current, ret); + } + + pos = W->well_connpos[w + 1]; + } + + if (! ok) { + destroy_wells(ret); + ret = NULL; + } + } + } + + return ret; +} diff --git a/opm/core/newwells.h b/opm/core/newwells.h index f16675b6..20cfce61 100644 --- a/opm/core/newwells.h +++ b/opm/core/newwells.h @@ -235,6 +235,19 @@ void destroy_wells(struct Wells *W); +/** + * Create a deep-copy (i.e., clone) of an existing Wells object, including its + * controls. + * + * @param[in] W Existing Wells object. + * @return Complete clone of the input object. Dispose of resources using + * function destroy_wells() when no longer needed. Returns @c NULL in case of + * allocation failure. + */ +struct Wells * +clone_wells(const struct Wells *W); + + #ifdef __cplusplus } #endif diff --git a/opm/core/pressure/CompressibleTpfa.cpp b/opm/core/pressure/CompressibleTpfa.cpp index 954e07fa..8edb41a0 100644 --- a/opm/core/pressure/CompressibleTpfa.cpp +++ b/opm/core/pressure/CompressibleTpfa.cpp @@ -36,6 +36,7 @@ #include #include #include +#include namespace Opm { @@ -122,7 +123,7 @@ namespace Opm WellState& well_state) { const int nc = grid_.number_of_cells; - const int nw = wells_->number_of_wells; + const int nw = (wells_ != 0) ? wells_->number_of_wells : 0; // Set up dynamic data. computePerSolveDynamicData(dt, state, well_state); @@ -446,30 +447,54 @@ namespace Opm /// Compute per-iteration dynamic properties for wells. void CompressibleTpfa::computeWellDynamicData(const double /*dt*/, const BlackoilState& /*state*/, - const WellState& /*well_state*/) + const WellState& well_state) { // These are the variables that get computed by this function: // // std::vector wellperf_A_; // std::vector wellperf_phasemob_; const int np = props_.numPhases(); - const int nw = wells_->number_of_wells; - const int nperf = wells_->well_connpos[nw]; + const int nw = (wells_ != 0) ? wells_->number_of_wells : 0; + const int nperf = (wells_ != 0) ? wells_->well_connpos[nw] : 0; wellperf_A_.resize(nperf*np*np); wellperf_phasemob_.resize(nperf*np); // The A matrix is set equal to the perforation grid cells' - // matrix, for both injectors and producers. + // matrix for producers, computed from bhp and injection + // component fractions from // The mobilities are set equal to the perforation grid cells' - // mobilities, for both injectors and producers. + // mobilities for producers. + std::vector mu(np); for (int w = 0; w < nw; ++w) { + bool producer = (wells_->type[w] == PRODUCER); + const double* comp_frac = &wells_->comp_frac[np*w]; for (int j = wells_->well_connpos[w]; j < wells_->well_connpos[w+1]; ++j) { const int c = wells_->well_cells[j]; - const double* cA = &cell_A_[np*np*c]; double* wpA = &wellperf_A_[np*np*j]; - std::copy(cA, cA + np*np, wpA); - const double* cM = &cell_phasemob_[np*c]; double* wpM = &wellperf_phasemob_[np*j]; - std::copy(cM, cM + np, wpM); + if (producer) { + const double* cA = &cell_A_[np*np*c]; + std::copy(cA, cA + np*np, wpA); + const double* cM = &cell_phasemob_[np*c]; + std::copy(cM, cM + np, wpM); + } else { + const double bhp = well_state.bhp()[w]; + double perf_p = bhp; + for (int phase = 0; phase < np; ++phase) { + perf_p += wellperf_gpot_[np*j + phase]*comp_frac[phase]; + } + // Hack warning: comp_frac is used as a component + // surface-volume variable in calls to matrix() and + // viscosity(), but as a saturation in the call to + // relperm(). This is probably ok as long as injectors + // only inject pure fluids. + props_.matrix(1, &perf_p, comp_frac, &c, wpA, NULL); + props_.viscosity(1, &perf_p, comp_frac, &c, &mu[0], NULL); + ASSERT(std::fabs(std::accumulate(comp_frac, comp_frac + np, 0.0) - 1.0) < 1e-6); + props_.relperm (1, comp_frac, &c, wpM , NULL); + for (int phase = 0; phase < np; ++phase) { + wpM[phase] /= mu[phase]; + } + } } } } @@ -487,9 +512,9 @@ namespace Opm const double* z = &state.surfacevol()[0]; UnstructuredGrid* gg = const_cast(&grid_); CompletionData completion_data; - completion_data.gpot = &wellperf_gpot_[0]; - completion_data.A = &wellperf_A_[0]; - completion_data.phasemob = &wellperf_phasemob_[0]; + completion_data.gpot = ! wellperf_gpot_.empty() ? &wellperf_gpot_[0] : 0; + completion_data.A = ! wellperf_A_.empty() ? &wellperf_A_[0] : 0; + completion_data.phasemob = ! wellperf_phasemob_.empty() ? &wellperf_phasemob_[0] : 0; cfs_tpfa_res_wells wells_tmp; wells_tmp.W = const_cast(wells_); wells_tmp.data = &completion_data; @@ -574,9 +599,9 @@ namespace Opm { UnstructuredGrid* gg = const_cast(&grid_); CompletionData completion_data; - completion_data.gpot = const_cast(&wellperf_gpot_[0]); - completion_data.A = const_cast(&wellperf_A_[0]); - completion_data.phasemob = const_cast(&wellperf_phasemob_[0]); + completion_data.gpot = ! wellperf_gpot_.empty() ? const_cast(&wellperf_gpot_[0]) : 0; + completion_data.A = ! wellperf_A_.empty() ? const_cast(&wellperf_A_[0]) : 0; + completion_data.phasemob = ! wellperf_phasemob_.empty() ? const_cast(&wellperf_phasemob_[0]) : 0; cfs_tpfa_res_wells wells_tmp; wells_tmp.W = const_cast(wells_); wells_tmp.data = &completion_data; @@ -584,6 +609,9 @@ namespace Opm forces.wells = &wells_tmp; forces.src = NULL; + double* wpress = ! well_state.bhp ().empty() ? & well_state.bhp ()[0] : 0; + double* wflux = ! well_state.perfRates().empty() ? & well_state.perfRates()[0] : 0; + cfs_tpfa_res_flux(gg, &forces, props_.numPhases(), @@ -592,9 +620,9 @@ namespace Opm &face_phasemob_[0], &face_gravcap_[0], &state.pressure()[0], - &well_state.bhp()[0], + wpress, &state.faceflux()[0], - &well_state.perfRates()[0]); + wflux); cfs_tpfa_res_fpress(gg, props_.numPhases(), &htrans_[0], @@ -604,6 +632,23 @@ namespace Opm &state.pressure()[0], &state.faceflux()[0], &state.facepressure()[0]); + + // Compute well perforation pressures (not done by the C code). + if (wells_ != 0) { + const int nw = wells_->number_of_wells; + const int np = props_.numPhases(); + for (int w = 0; w < nw; ++w) { + const double* comp_frac = &wells_->comp_frac[np*w]; + for (int j = wells_->well_connpos[w]; j < wells_->well_connpos[w+1]; ++j) { + const double bhp = well_state.bhp()[w]; + double perf_p = bhp; + for (int phase = 0; phase < np; ++phase) { + perf_p += wellperf_gpot_[np*j + phase]*comp_frac[phase]; + } + well_state.perfPress()[j] = perf_p; + } + } + } } } // namespace Opm diff --git a/opm/core/pressure/tpfa/cfs_tpfa_residual.c b/opm/core/pressure/tpfa/cfs_tpfa_residual.c index eb8dc8f3..bcbacec2 100644 --- a/opm/core/pressure/tpfa/cfs_tpfa_residual.c +++ b/opm/core/pressure/tpfa/cfs_tpfa_residual.c @@ -142,7 +142,7 @@ impl_allocate(struct UnstructuredGrid *G , nnu = G->number_of_cells; nwperf = 0; - if (wells != NULL) { assert (wells->W != NULL); + if ((wells != NULL) && (wells->W != NULL)) { nnu += wells->W->number_of_wells; nwperf = wells->W->well_connpos[ wells->W->number_of_wells ]; } @@ -185,13 +185,15 @@ construct_matrix(struct UnstructuredGrid *G , /* ---------------------------------------------------------------------- */ { int f, c1, c2, w, i, nc, nnu; + int wells_present; size_t nnz; struct CSRMatrix *A; nc = nnu = G->number_of_cells; - if (wells != NULL) { - assert (wells->W != NULL); + + wells_present = (wells != NULL) && (wells->W != NULL); + if (wells_present) { nnu += wells->W->number_of_wells; } @@ -214,7 +216,7 @@ construct_matrix(struct UnstructuredGrid *G , } } - if (wells != NULL) { + if (wells_present) { /* Well <-> cell connections */ struct Wells *W = wells->W; @@ -252,7 +254,7 @@ construct_matrix(struct UnstructuredGrid *G , } } - if (wells != NULL) { + if (wells_present) { /* Fill well <-> cell connections */ struct Wells *W = wells->W; @@ -741,6 +743,29 @@ assemble_completion_to_cell(int c, int wdof, int np, double dt, } +/* ---------------------------------------------------------------------- */ +static void +welleq_coeff_shut(int np, struct cfs_tpfa_res_data *h, + double *res, double *w2c, double *w2w) +/* ---------------------------------------------------------------------- */ +{ + int p; + double fwi; + const double *dpflux_w; + + /* Sum reservoir phase flux derivatives set by + * compute_darcyflux_and_deriv(). */ + dpflux_w = h->pimpl->flux_work + (1 * np); + for (p = 0, fwi = 0.0; p < np; p++) { + fwi += dpflux_w[ p ]; + } + + *res = 0.0; + *w2c = 0.0; + *w2w = fwi; +} + + /* ---------------------------------------------------------------------- */ static void welleq_coeff_bhp(int np, double dp, struct cfs_tpfa_res_data *h, @@ -795,7 +820,34 @@ welleq_coeff_resv(int np, struct cfs_tpfa_res_data *h, /* ---------------------------------------------------------------------- */ static void -assemble_completion_to_well(int w, int c, int nc, int np, +welleq_coeff_surfrate(int i, int np, struct cfs_tpfa_res_data *h, + struct WellControls *ctrl, + double *res, double *w2c, double *w2w) +/* ---------------------------------------------------------------------- */ +{ + int p; + double f; + const double *pflux, *dpflux_w, *dpflux_c, *distr; + + pflux = h->pimpl->compflux_p + (i * (1 * np)); + dpflux_w = h->pimpl->compflux_deriv_p + (i * (2 * np)); + dpflux_c = dpflux_w + (1 * (1 * np)); + distr = ctrl->distr + (ctrl->current * (1 * np)); + + *res = *w2c = *w2w = 0.0; + for (p = 0; p < np; p++) { + f = distr[ p ]; + + *res += f * pflux [ p ]; + *w2w += f * dpflux_w[ p ]; + *w2c += f * dpflux_c[ p ]; + } +} + + +/* ---------------------------------------------------------------------- */ +static void +assemble_completion_to_well(int i, int w, int c, int nc, int np, double pw, double dt, struct cfs_tpfa_res_wells *wells, struct cfs_tpfa_res_data *h ) @@ -815,23 +867,25 @@ assemble_completion_to_well(int w, int c, int nc, int np, ctrl = W->ctrls[ w ]; if (ctrl->current < 0) { - assert (0); /* Shut wells currently not supported */ + /* Interpreting a negative current control index to mean a shut well */ + welleq_coeff_shut(np, h, &res, &w2c, &w2w); } + else { + switch (ctrl->type[ ctrl->current ]) { + case BHP : + welleq_coeff_bhp(np, pw - ctrl->target[ ctrl->current ], + h, &res, &w2c, &w2w); + break; - switch (ctrl->type[ ctrl->current ]) { - case BHP : - welleq_coeff_bhp(np, pw - ctrl->target[ ctrl->current ], - h, &res, &w2c, &w2w); - break; + case RESERVOIR_RATE: + assert (W->number_of_phases == np); + welleq_coeff_resv(np, h, ctrl, &res, &w2c, &w2w); + break; - case RESERVOIR_RATE: - assert (W->number_of_phases == np); - welleq_coeff_resv(np, h, ctrl, &res, &w2c, &w2w); - break; - - case SURFACE_RATE: - assert (0); /* Surface rate currently not supported */ - break; + case SURFACE_RATE: + welleq_coeff_surfrate(i, np, h, ctrl, &res, &w2c, &w2w); + break; + } } /* Assemble completion contributions */ @@ -854,7 +908,7 @@ assemble_well_contrib(struct cfs_tpfa_res_wells *wells , struct cfs_tpfa_res_data *h ) { int w, i, c, np, np2, nc; - int is_neumann; + int is_neumann, is_open; double pw, dp; double *WI, *gpot, *pmobp; const double *Ac, *dAc; @@ -876,6 +930,7 @@ assemble_well_contrib(struct cfs_tpfa_res_wells *wells , for (w = i = 0; w < W->number_of_wells; w++) { pw = wpress[ w ]; + is_open = W->ctrls[w]->current >= 0; for (; i < W->well_connpos[w + 1]; i++, gpot += np, pmobp += np) { @@ -888,14 +943,16 @@ assemble_well_contrib(struct cfs_tpfa_res_wells *wells , init_completion_contrib(i, np, Ac, dAc, h->pimpl); - assemble_completion_to_cell(c, nc + w, np, dt, h); + if (is_open) { + assemble_completion_to_cell(c, nc + w, np, dt, h); + } /* Prepare for RESV controls */ compute_darcyflux_and_deriv(np, WI[i], dp, pmobp, gpot, h->pimpl->flux_work, h->pimpl->flux_work + np); - assemble_completion_to_well(w, c, nc, np, pw, dt, wells, h); + assemble_completion_to_well(i, w, c, nc, np, pw, dt, wells, h); } ctrl = W->ctrls[ w ]; @@ -1127,8 +1184,7 @@ cfs_tpfa_res_construct(struct UnstructuredGrid *G , nf = G->number_of_faces; nwperf = 0; - if (wells != NULL) { - assert (wells->W != NULL); + if ((wells != NULL) && (wells->W != NULL)) { nwperf = wells->W->well_connpos[ wells->W->number_of_wells ]; } @@ -1194,7 +1250,9 @@ cfs_tpfa_res_assemble(struct UnstructuredGrid *G , assemble_cell_contrib(G, c, h); } - if ((forces != NULL) && (forces->wells != NULL)) { + if ((forces != NULL) && + (forces->wells != NULL) && + (forces->wells->W != NULL)) { compute_well_compflux_and_deriv(forces->wells, cq->nphases, cpress, wpress, h->pimpl); @@ -1297,8 +1355,9 @@ cfs_tpfa_res_flux(struct UnstructuredGrid *G , { compute_flux(G, np, trans, pmobf, gravcap_f, cpress, fflux); - if ((forces != NULL) && (forces->wells != NULL) && - (wpress != NULL) && (wflux != NULL)) { + if ((forces != NULL) && + (forces->wells != NULL) && + (forces->wells->W != NULL) && (wpress != NULL) && (wflux != NULL)) { compute_wflux(np, forces->wells, pmobc, cpress, wpress, wflux); } diff --git a/opm/core/simulator/SimulatorCompressibleTwophase.cpp b/opm/core/simulator/SimulatorCompressibleTwophase.cpp index c5a77eb0..8b8e8736 100644 --- a/opm/core/simulator/SimulatorCompressibleTwophase.cpp +++ b/opm/core/simulator/SimulatorCompressibleTwophase.cpp @@ -65,7 +65,7 @@ namespace Opm Impl(const parameter::ParameterGroup& param, const UnstructuredGrid& grid, const BlackoilPropertiesInterface& props, - const RockCompressibility* rock_comp, + const RockCompressibility* rock_comp_props, WellsManager& wells_manager, const std::vector& src, const FlowBoundaryConditions* bcs, @@ -93,7 +93,7 @@ namespace Opm // Observed objects. const UnstructuredGrid& grid_; const BlackoilPropertiesInterface& props_; - const RockCompressibility* rock_comp_; + const RockCompressibility* rock_comp_props_; WellsManager& wells_manager_; const Wells* wells_; const std::vector& src_; @@ -114,14 +114,14 @@ namespace Opm SimulatorCompressibleTwophase::SimulatorCompressibleTwophase(const parameter::ParameterGroup& param, const UnstructuredGrid& grid, const BlackoilPropertiesInterface& props, - const RockCompressibility* rock_comp, + const RockCompressibility* rock_comp_props, WellsManager& wells_manager, const std::vector& src, const FlowBoundaryConditions* bcs, LinearSolverInterface& linsolver, const double* gravity) { - pimpl_.reset(new Impl(param, grid, props, rock_comp, wells_manager, src, bcs, linsolver, gravity)); + pimpl_.reset(new Impl(param, grid, props, rock_comp_props, wells_manager, src, bcs, linsolver, gravity)); } @@ -175,6 +175,7 @@ namespace Opm Opm::DataMap dm; dm["saturation"] = &state.saturation(); dm["pressure"] = &state.pressure(); + dm["surfvolume"] = &state.surfacevol(); std::vector cell_velocity; Opm::estimateCellVelocity(grid, state.faceflux(), cell_velocity); dm["velocity"] = &cell_velocity; @@ -195,6 +196,7 @@ namespace Opm if (!file) { THROW("Failed to open " << fname.str()); } + file.precision(15); const std::vector& d = *(it->second); std::copy(d.begin(), d.end(), std::ostream_iterator(file, "\n")); } @@ -232,7 +234,7 @@ namespace Opm SimulatorCompressibleTwophase::Impl::Impl(const parameter::ParameterGroup& param, const UnstructuredGrid& grid, const BlackoilPropertiesInterface& props, - const RockCompressibility* rock_comp, + const RockCompressibility* rock_comp_props, WellsManager& wells_manager, const std::vector& src, const FlowBoundaryConditions* bcs, @@ -240,12 +242,13 @@ namespace Opm const double* gravity) : grid_(grid), props_(props), - rock_comp_(rock_comp), + rock_comp_props_(rock_comp_props), wells_manager_(wells_manager), wells_(wells_manager.c_wells()), src_(src), bcs_(bcs), - psolver_(grid, props, rock_comp, linsolver, + gravity_(gravity), + psolver_(grid, props, rock_comp_props, linsolver, param.getDefault("nl_pressure_residual_tolerance", 0.0), param.getDefault("nl_pressure_change_tolerance", 1.0), param.getDefault("nl_pressure_maxiter", 10), @@ -301,8 +304,8 @@ namespace Opm // Initialisation. std::vector porevol; - if (rock_comp_ && rock_comp_->isActive()) { - computePorevolume(grid_, props_.porosity(), *rock_comp_, state.pressure(), porevol); + if (rock_comp_props_ && rock_comp_props_->isActive()) { + computePorevolume(grid_, props_.porosity(), *rock_comp_props_, state.pressure(), porevol); } else { computePorevolume(grid_, props_.porosity(), porevol); } @@ -317,15 +320,11 @@ namespace Opm Opm::time::StopWatch step_timer; Opm::time::StopWatch total_timer; total_timer.start(); - double init_satvol[2] = { 0.0 }; - double satvol[2] = { 0.0 }; - double injected[2] = { 0.0 }; - double produced[2] = { 0.0 }; + double init_surfvol[2] = { 0.0 }; + double inplace_surfvol[2] = { 0.0 }; double tot_injected[2] = { 0.0 }; double tot_produced[2] = { 0.0 }; - Opm::computeSaturatedVol(porevol, state.saturation(), init_satvol); - std::cout << "\nInitial saturations are " << init_satvol[0]/tot_porevol_init - << " " << init_satvol[1]/tot_porevol_init << std::endl; + Opm::computeSaturatedVol(porevol, state.surfacevol(), init_surfvol); Opm::Watercut watercut; watercut.push(0.0, 0.0, 0.0); Opm::WellReport wellreport; @@ -408,8 +407,8 @@ namespace Opm // Optionally, check if well controls are satisfied. if (check_well_controls_) { Opm::computePhaseFlowRatesPerWell(*wells_, - fractional_flows, well_state.perfRates(), + fractional_flows, well_resflows_phase); std::cout << "Checking well conditions." << std::endl; // For testing we set surface := reservoir @@ -427,14 +426,13 @@ namespace Opm } while (!well_control_passed); // Update pore volumes if rock is compressible. - if (rock_comp_ && rock_comp_->isActive()) { + if (rock_comp_props_ && rock_comp_props_->isActive()) { initial_porevol = porevol; - computePorevolume(grid_, props_.porosity(), *rock_comp_, state.pressure(), porevol); + computePorevolume(grid_, props_.porosity(), *rock_comp_props_, state.pressure(), porevol); } - // Process transport sources (to include bdy terms and well flows). - Opm::computeTransportSource(grid_, src_, state.faceflux(), 1.0, - wells_, well_state.perfRates(), transport_src); + // Process transport sources from well flows. + Opm::computeTransportSource(props_, wells_, well_state, transport_src); // Solve transport. transport_timer.start(); @@ -443,16 +441,22 @@ namespace Opm stepsize /= double(num_transport_substeps_); std::cout << "Making " << num_transport_substeps_ << " transport substeps." << std::endl; } + double injected[2] = { 0.0 }; + double produced[2] = { 0.0 }; for (int tr_substep = 0; tr_substep < num_transport_substeps_; ++tr_substep) { tsolver_.solve(&state.faceflux()[0], &state.pressure()[0], - &porevol[0], &initial_porevol[0], &transport_src[0], stepsize, + &initial_porevol[0], &porevol[0], &transport_src[0], stepsize, state.saturation(), state.surfacevol()); - Opm::computeInjectedProduced(props_, - state.pressure(), state.surfacevol(), state.saturation(), - transport_src, stepsize, injected, produced); + double substep_injected[2] = { 0.0 }; + double substep_produced[2] = { 0.0 }; + Opm::computeInjectedProduced(props_, state, transport_src, stepsize, + substep_injected, substep_produced); + injected[0] += substep_injected[0]; + injected[1] += substep_injected[1]; + produced[0] += substep_produced[0]; + produced[1] += substep_produced[1]; if (gravity_ != 0 && use_segregation_split_) { - tsolver_.solveGravity(columns_, &state.pressure()[0], &initial_porevol[0], - stepsize, state.saturation(), state.surfacevol()); + tsolver_.solveGravity(columns_, stepsize, state.saturation(), state.surfacevol()); } } transport_timer.stop(); @@ -461,35 +465,35 @@ namespace Opm std::cout << "Transport solver took: " << tt << " seconds." << std::endl; ttime += tt; // Report volume balances. - Opm::computeSaturatedVol(porevol, state.saturation(), satvol); + Opm::computeSaturatedVol(porevol, state.surfacevol(), inplace_surfvol); tot_injected[0] += injected[0]; tot_injected[1] += injected[1]; tot_produced[0] += produced[0]; tot_produced[1] += produced[1]; std::cout.precision(5); const int width = 18; - std::cout << "\nVolume balance report (all numbers relative to total pore volume).\n"; - std::cout << " Saturated volumes: " - << std::setw(width) << satvol[0]/tot_porevol_init - << std::setw(width) << satvol[1]/tot_porevol_init << std::endl; - std::cout << " Injected volumes: " - << std::setw(width) << injected[0]/tot_porevol_init - << std::setw(width) << injected[1]/tot_porevol_init << std::endl; - std::cout << " Produced volumes: " - << std::setw(width) << produced[0]/tot_porevol_init - << std::setw(width) << produced[1]/tot_porevol_init << std::endl; - std::cout << " Total inj volumes: " - << std::setw(width) << tot_injected[0]/tot_porevol_init - << std::setw(width) << tot_injected[1]/tot_porevol_init << std::endl; - std::cout << " Total prod volumes: " - << std::setw(width) << tot_produced[0]/tot_porevol_init - << std::setw(width) << tot_produced[1]/tot_porevol_init << std::endl; - std::cout << " In-place + prod - inj: " - << std::setw(width) << (satvol[0] + tot_produced[0] - tot_injected[0])/tot_porevol_init - << std::setw(width) << (satvol[1] + tot_produced[1] - tot_injected[1])/tot_porevol_init << std::endl; - std::cout << " Init - now - pr + inj: " - << std::setw(width) << (init_satvol[0] - satvol[0] - tot_produced[0] + tot_injected[0])/tot_porevol_init - << std::setw(width) << (init_satvol[1] - satvol[1] - tot_produced[1] + tot_injected[1])/tot_porevol_init + std::cout << "\nMass balance report.\n"; + std::cout << " Injected surface volumes: " + << std::setw(width) << injected[0] + << std::setw(width) << injected[1] << std::endl; + std::cout << " Produced surface volumes: " + << std::setw(width) << produced[0] + << std::setw(width) << produced[1] << std::endl; + std::cout << " Total inj surface volumes: " + << std::setw(width) << tot_injected[0] + << std::setw(width) << tot_injected[1] << std::endl; + std::cout << " Total prod surface volumes: " + << std::setw(width) << tot_produced[0] + << std::setw(width) << tot_produced[1] << std::endl; + const double balance[2] = { init_surfvol[0] - inplace_surfvol[0] - tot_produced[0] + tot_injected[0], + init_surfvol[1] - inplace_surfvol[1] - tot_produced[1] + tot_injected[1] }; + std::cout << " Initial - inplace + inj - prod: " + << std::setw(width) << balance[0] + << std::setw(width) << balance[1] + << std::endl; + std::cout << " Relative mass error: " + << std::setw(width) << balance[0]/(init_surfvol[0] + tot_injected[0]) + << std::setw(width) << balance[1]/(init_surfvol[1] + tot_injected[1]) << std::endl; std::cout.precision(8); diff --git a/opm/core/simulator/SimulatorCompressibleTwophase.hpp b/opm/core/simulator/SimulatorCompressibleTwophase.hpp index d38d74b2..944230e5 100644 --- a/opm/core/simulator/SimulatorCompressibleTwophase.hpp +++ b/opm/core/simulator/SimulatorCompressibleTwophase.hpp @@ -61,7 +61,7 @@ namespace Opm /// /// \param[in] grid grid data structure /// \param[in] props fluid and rock properties - /// \param[in] rock_comp if non-null, rock compressibility properties + /// \param[in] rock_comp_props if non-null, rock compressibility properties /// \param[in] well_manager well manager, may manage no (null) wells /// \param[in] src source terms /// \param[in] bcs boundary conditions, treat as all noflow if null @@ -70,7 +70,7 @@ namespace Opm SimulatorCompressibleTwophase(const parameter::ParameterGroup& param, const UnstructuredGrid& grid, const BlackoilPropertiesInterface& props, - const RockCompressibility* rock_comp, + const RockCompressibility* rock_comp_props, WellsManager& wells_manager, const std::vector& src, const FlowBoundaryConditions* bcs, diff --git a/opm/core/simulator/SimulatorIncompTwophase.cpp b/opm/core/simulator/SimulatorIncompTwophase.cpp index 1f7e1119..28e18a45 100644 --- a/opm/core/simulator/SimulatorIncompTwophase.cpp +++ b/opm/core/simulator/SimulatorIncompTwophase.cpp @@ -64,7 +64,7 @@ namespace Opm Impl(const parameter::ParameterGroup& param, const UnstructuredGrid& grid, const IncompPropertiesInterface& props, - const RockCompressibility* rock_comp, + const RockCompressibility* rock_comp_props, WellsManager& wells_manager, const std::vector& src, const FlowBoundaryConditions* bcs, @@ -91,7 +91,7 @@ namespace Opm // Observed objects. const UnstructuredGrid& grid_; const IncompPropertiesInterface& props_; - const RockCompressibility* rock_comp_; + const RockCompressibility* rock_comp_props_; WellsManager& wells_manager_; const Wells* wells_; const std::vector& src_; @@ -111,14 +111,14 @@ namespace Opm SimulatorIncompTwophase::SimulatorIncompTwophase(const parameter::ParameterGroup& param, const UnstructuredGrid& grid, const IncompPropertiesInterface& props, - const RockCompressibility* rock_comp, + const RockCompressibility* rock_comp_props, WellsManager& wells_manager, const std::vector& src, const FlowBoundaryConditions* bcs, LinearSolverInterface& linsolver, const double* gravity) { - pimpl_.reset(new Impl(param, grid, props, rock_comp, wells_manager, src, bcs, linsolver, gravity)); + pimpl_.reset(new Impl(param, grid, props, rock_comp_props, wells_manager, src, bcs, linsolver, gravity)); } @@ -244,6 +244,7 @@ namespace Opm if (!file) { THROW("Failed to open " << fname.str()); } + file.precision(15); const std::vector& d = *(it->second); std::copy(d.begin(), d.end(), std::ostream_iterator(file, "\n")); } @@ -311,7 +312,7 @@ namespace Opm SimulatorIncompTwophase::Impl::Impl(const parameter::ParameterGroup& param, const UnstructuredGrid& grid, const IncompPropertiesInterface& props, - const RockCompressibility* rock_comp, + const RockCompressibility* rock_comp_props, WellsManager& wells_manager, const std::vector& src, const FlowBoundaryConditions* bcs, @@ -319,12 +320,12 @@ namespace Opm const double* gravity) : grid_(grid), props_(props), - rock_comp_(rock_comp), + rock_comp_props_(rock_comp_props), wells_manager_(wells_manager), wells_(wells_manager.c_wells()), src_(src), bcs_(bcs), - psolver_(grid, props, rock_comp, linsolver, + psolver_(grid, props, rock_comp_props, linsolver, param.getDefault("nl_pressure_residual_tolerance", 0.0), param.getDefault("nl_pressure_change_tolerance", 1.0), param.getDefault("nl_pressure_maxiter", 10), @@ -380,8 +381,8 @@ namespace Opm // Initialisation. std::vector porevol; - if (rock_comp_ && rock_comp_->isActive()) { - computePorevolume(grid_, props_.porosity(), *rock_comp_, state.pressure(), porevol); + if (rock_comp_props_ && rock_comp_props_->isActive()) { + computePorevolume(grid_, props_.porosity(), *rock_comp_props_, state.pressure(), porevol); } else { computePorevolume(grid_, props_.porosity(), porevol); } @@ -398,8 +399,6 @@ namespace Opm total_timer.start(); double init_satvol[2] = { 0.0 }; double satvol[2] = { 0.0 }; - double injected[2] = { 0.0 }; - double produced[2] = { 0.0 }; double tot_injected[2] = { 0.0 }; double tot_produced[2] = { 0.0 }; Opm::computeSaturatedVol(porevol, state.saturation(), init_satvol); @@ -452,7 +451,7 @@ namespace Opm // there are no pressure conditions (bcs or wells). // It is deemed sufficient for now to renormalize // using geometric volume instead of pore volume. - if ((rock_comp_ == NULL || !rock_comp_->isActive()) + if ((rock_comp_props_ == NULL || !rock_comp_props_->isActive()) && allNeumannBCs(bcs_) && allRateWells(wells_)) { // Compute average pressures of previous and last // step, and total volume. @@ -486,8 +485,8 @@ namespace Opm // Optionally, check if well controls are satisfied. if (check_well_controls_) { Opm::computePhaseFlowRatesPerWell(*wells_, - fractional_flows, well_state.perfRates(), + fractional_flows, well_resflows_phase); std::cout << "Checking well conditions." << std::endl; // For testing we set surface := reservoir @@ -505,9 +504,9 @@ namespace Opm } while (!well_control_passed); // Update pore volumes if rock is compressible. - if (rock_comp_ && rock_comp_->isActive()) { + if (rock_comp_props_ && rock_comp_props_->isActive()) { initial_porevol = porevol; - computePorevolume(grid_, props_.porosity(), *rock_comp_, state.pressure(), porevol); + computePorevolume(grid_, props_.porosity(), *rock_comp_props_, state.pressure(), porevol); } // Process transport sources (to include bdy terms and well flows). @@ -521,10 +520,19 @@ namespace Opm stepsize /= double(num_transport_substeps_); std::cout << "Making " << num_transport_substeps_ << " transport substeps." << std::endl; } + double injected[2] = { 0.0 }; + double produced[2] = { 0.0 }; for (int tr_substep = 0; tr_substep < num_transport_substeps_; ++tr_substep) { tsolver_.solve(&state.faceflux()[0], &initial_porevol[0], &transport_src[0], stepsize, state.saturation()); - Opm::computeInjectedProduced(props_, state.saturation(), transport_src, stepsize, injected, produced); + double substep_injected[2] = { 0.0 }; + double substep_produced[2] = { 0.0 }; + Opm::computeInjectedProduced(props_, state.saturation(), transport_src, stepsize, + substep_injected, substep_produced); + injected[0] += substep_injected[0]; + injected[1] += substep_injected[1]; + produced[0] += substep_produced[0]; + produced[1] += substep_produced[1]; if (use_segregation_split_) { tsolver_.solveGravity(columns_, &initial_porevol[0], stepsize, state.saturation()); } diff --git a/opm/core/simulator/SimulatorIncompTwophase.hpp b/opm/core/simulator/SimulatorIncompTwophase.hpp index 10c46ff0..64030d68 100644 --- a/opm/core/simulator/SimulatorIncompTwophase.hpp +++ b/opm/core/simulator/SimulatorIncompTwophase.hpp @@ -61,7 +61,7 @@ namespace Opm /// /// \param[in] grid grid data structure /// \param[in] props fluid and rock properties - /// \param[in] rock_comp if non-null, rock compressibility properties + /// \param[in] rock_comp_props if non-null, rock compressibility properties /// \param[in] well_manager well manager, may manage no (null) wells /// \param[in] src source terms /// \param[in] bcs boundary conditions, treat as all noflow if null @@ -70,7 +70,7 @@ namespace Opm SimulatorIncompTwophase(const parameter::ParameterGroup& param, const UnstructuredGrid& grid, const IncompPropertiesInterface& props, - const RockCompressibility* rock_comp, + const RockCompressibility* rock_comp_props, WellsManager& wells_manager, const std::vector& src, const FlowBoundaryConditions* bcs, diff --git a/opm/core/simulator/WellState.hpp b/opm/core/simulator/WellState.hpp index 59d9f060..981ccd9b 100644 --- a/opm/core/simulator/WellState.hpp +++ b/opm/core/simulator/WellState.hpp @@ -37,12 +37,20 @@ namespace Opm if (wells) { const int nw = wells->number_of_wells; bhp_.resize(nw); - // Initialize bhp to be pressure in first perforation cell. + // Initialize bhp to be target pressure + // if bhp-controlled well, otherwise set + // to pressure in first perforation cell. for (int w = 0; w < nw; ++w) { - const int cell = wells->well_cells[wells->well_connpos[w]]; - bhp_[w] = state.pressure()[cell]; + const WellControls* ctrl = wells->ctrls[w]; + if (ctrl->type[ctrl->current] == BHP) { + bhp_[w] = ctrl->target[ctrl->current]; + } else { + const int cell = wells->well_cells[wells->well_connpos[w]]; + bhp_[w] = state.pressure()[cell]; + } } - perfrates_.resize(wells->well_connpos[nw]); + perfrates_.resize(wells->well_connpos[nw], 0.0); + perfpress_.resize(wells->well_connpos[nw], -1e100); } } @@ -54,9 +62,14 @@ namespace Opm std::vector& perfRates() { return perfrates_; } const std::vector& perfRates() const { return perfrates_; } + /// One pressure per well connection. + std::vector& perfPress() { return perfpress_; } + const std::vector& perfPress() const { return perfpress_; } + private: std::vector bhp_; std::vector perfrates_; + std::vector perfpress_; }; } // namespace Opm diff --git a/opm/core/transport/reorder/TransportModelCompressibleTwophase.cpp b/opm/core/transport/reorder/TransportModelCompressibleTwophase.cpp index 01882053..bad71263 100644 --- a/opm/core/transport/reorder/TransportModelCompressibleTwophase.cpp +++ b/opm/core/transport/reorder/TransportModelCompressibleTwophase.cpp @@ -152,8 +152,8 @@ namespace Opm B_cell = 1.0/tm.A_[np*np*cell + 0]; double src_flux = -tm.source_[cell]; bool src_is_inflow = src_flux < 0.0; - influx = src_is_inflow ? B_cell*src_flux : 0.0; - outflux = !src_is_inflow ? B_cell*src_flux : 0.0; + influx = src_is_inflow ? B_cell* src_flux : 0.0; + outflux = !src_is_inflow ? src_flux : 0.0; comp_term = (tm.porevolume_[cell] - tm.porevolume0_[cell])/tm.porevolume0_[cell]; dtpv = tm.dt_/tm.porevolume0_[cell]; for (int i = tm.grid_.cell_facepos[cell]; i < tm.grid_.cell_facepos[cell+1]; ++i) { @@ -349,7 +349,7 @@ namespace Opm gf[1] = gravflux[pos]; } s0 = tm.saturation_[cell]; - dtpv = tm.dt_/tm.porevolume0_[cell]; + dtpv = tm.dt_/tm.porevolume_[cell]; } double operator()(double s) const @@ -380,8 +380,8 @@ namespace Opm { double sat[2] = { s, 1.0 - s }; props_.relperm(1, sat, &cell, mob, 0); - mob[0] /= visc_[0]; - mob[1] /= visc_[1]; + mob[0] /= visc_[2*cell + 0]; + mob[1] /= visc_[2*cell + 1]; } @@ -407,7 +407,7 @@ namespace Opm { // Set up gravflux_ = T_ij g [ (b_w,i rho_w,S - b_o,i rho_o,S) (z_i - z_f) // + (b_w,j rho_w,S - b_o,j rho_o,S) (z_f - z_j) ] - // But b_w,i * rho_w,S = rho_w,i, which we conmpute with a call to props_.density(). + // But b_w,i * rho_w,S = rho_w,i, which we compute with a call to props_.density(). // We assume that we already have stored T_ij in trans_. // We also assume that the A_ matrices are updated from an earlier call to solve(). const int nc = grid_.number_of_cells; @@ -437,16 +437,15 @@ namespace Opm void TransportModelCompressibleTwophase::solveSingleCellGravity(const std::vector& cells, - const int pos, - const double* gravflux) + const int pos, + const double* gravflux) { const int cell = cells[pos]; GravityResidual res(*this, cells, pos, gravflux); if (std::fabs(res(saturation_[cell])) > tol_) { int iters_used; - saturation_[cell] = RootFinder::solve(res, smin_[2*cell], smax_[2*cell], maxit_, tol_, iters_used); + saturation_[cell] = RootFinder::solve(res, saturation_[cell], 0.0, 1.0, maxit_, tol_, iters_used); } - saturation_[cell] = std::min(std::max(saturation_[cell], smin_[2*cell]), smax_[2*cell]); mobility(saturation_[cell], cell, &mob_[2*cell]); } @@ -506,8 +505,6 @@ namespace Opm void TransportModelCompressibleTwophase::solveGravity(const std::vector >& columns, - const double* pressure, - const double* porevolume0, const double dt, std::vector& saturation, std::vector& surfacevol) @@ -522,18 +519,22 @@ namespace Opm cells[c] = c; } mob_.resize(2*nc); - std::vector boths; - Opm::toBothSat(saturation, boths); - props_.relperm(cells.size(), &boths[0], &cells[0], &mob_[0], 0); - props_.viscosity(props_.numCells(), pressure, NULL, &allcells_[0], &visc_[0], NULL); - for (int c = 0; c < nc; ++c) { - mob_[2*c + 0] /= visc_[2*c + 0]; - mob_[2*c + 1] /= visc_[2*c + 1]; + + // props_.relperm(cells.size(), &saturation[0], &cells[0], &mob_[0], 0); + + // props_.viscosity(props_.numCells(), pressure, NULL, &allcells_[0], &visc_[0], NULL); + // for (int c = 0; c < nc; ++c) { + // mob_[2*c + 0] /= visc_[2*c + 0]; + // mob_[2*c + 1] /= visc_[2*c + 1]; + // } + + const int np = props_.numPhases(); + for (int cell = 0; cell < nc; ++cell) { + mobility(saturation_[cell], cell, &mob_[np*cell]); } // Set up other variables. - porevolume0_ = porevolume0; dt_ = dt; toWaterSat(saturation, saturation_); diff --git a/opm/core/transport/reorder/TransportModelCompressibleTwophase.hpp b/opm/core/transport/reorder/TransportModelCompressibleTwophase.hpp index 4ee93e0a..147d7da1 100644 --- a/opm/core/transport/reorder/TransportModelCompressibleTwophase.hpp +++ b/opm/core/transport/reorder/TransportModelCompressibleTwophase.hpp @@ -74,13 +74,10 @@ namespace Opm /// vertical stack, that do not interact with other columns (for /// gravity segregation. /// \param[in] columns Vector of cell-columns. - /// \param[in] porevolume0 Array of pore volumes at start of timestep. /// \param[in] dt Time step. /// \param[in, out] saturation Phase saturations. /// \param[in, out] surfacevol Surface volume densities for each phase. void solveGravity(const std::vector >& columns, - const double* pressure, - const double* porevolume0, const double dt, std::vector& saturation, std::vector& surfacevol); diff --git a/opm/core/transport/reorder/TransportModelInterface.cpp b/opm/core/transport/reorder/TransportModelInterface.cpp index f8e138a3..8a90ae34 100644 --- a/opm/core/transport/reorder/TransportModelInterface.cpp +++ b/opm/core/transport/reorder/TransportModelInterface.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -31,7 +32,11 @@ void Opm::TransportModelInterface::reorderAndTransport(const UnstructuredGrid& g std::vector sequence(grid.number_of_cells); std::vector components(grid.number_of_cells + 1); int ncomponents; + time::StopWatch clock; + clock.start(); compute_sequence(&grid, darcyflux, &sequence[0], &components[0], &ncomponents); + clock.stop(); + std::cout << "Topological sort took: " << clock.secsSinceStart() << " seconds." << std::endl; // Invoke appropriate solve method for each interdependent component. for (int comp = 0; comp < ncomponents; ++comp) { diff --git a/opm/core/transport/reorder/TransportModelTracerTof.cpp b/opm/core/transport/reorder/TransportModelTracerTof.cpp new file mode 100644 index 00000000..8d93f248 --- /dev/null +++ b/opm/core/transport/reorder/TransportModelTracerTof.cpp @@ -0,0 +1,122 @@ +/* + Copyright 2012 SINTEF ICT, Applied Mathematics. + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ + +#include +#include +#include +#include +#include +#include + +namespace Opm +{ + + + /// Construct solver. + /// \param[in] grid A 2d or 3d grid. + TransportModelTracerTof::TransportModelTracerTof(const UnstructuredGrid& grid) + : grid_(grid) + { + } + + + + + /// Solve for time-of-flight. + /// \param[in] darcyflux Array of signed face fluxes. + /// \param[in] porevolume Array of pore volumes. + /// \param[in] source Source term. Sign convention is: + /// (+) inflow flux, + /// (-) outflow flux. + /// \param[out] tof Array of time-of-flight values. + void TransportModelTracerTof::solveTof(const double* darcyflux, + const double* porevolume, + const double* source, + std::vector& tof) + { + darcyflux_ = darcyflux; + porevolume_ = porevolume; + source_ = source; +#ifndef NDEBUG + // Sanity check for sources. + const double cum_src = std::accumulate(source, source + grid_.number_of_cells, 0.0); + if (std::fabs(cum_src) > *std::max_element(source, source + grid_.number_of_cells)*1e-2) { + THROW("Sources do not sum to zero: " << cum_src); + } +#endif + tof.resize(grid_.number_of_cells); + std::fill(tof.begin(), tof.end(), 0.0); + tof_ = &tof[0]; + reorderAndTransport(grid_, darcyflux); + } + + + + + void TransportModelTracerTof::solveSingleCell(const int cell) + { + // Compute flux terms. + // Sources have zero tof, and therefore do not contribute + // to upwind_term. Sinks on the other hand, must be added + // to the downwind_flux (note sign change resulting from + // different sign conventions: pos. source is injection, + // pos. flux is outflow). + double upwind_term = 0.0; + double downwind_flux = std::max(-source_[cell], 0.0); + for (int i = grid_.cell_facepos[cell]; i < grid_.cell_facepos[cell+1]; ++i) { + int f = grid_.cell_faces[i]; + double flux; + int other; + // Compute cell flux + if (cell == grid_.face_cells[2*f]) { + flux = darcyflux_[f]; + other = grid_.face_cells[2*f+1]; + } else { + flux =-darcyflux_[f]; + other = grid_.face_cells[2*f]; + } + // Add flux to upwind_term or downwind_flux, if interior. + if (other != -1) { + if (flux < 0.0) { + upwind_term += flux*tof_[other]; + } else { + downwind_flux += flux; + } + } + } + + // Compute tof. + tof_[cell] = (porevolume_[cell] - upwind_term)/downwind_flux; + } + + + + + void TransportModelTracerTof::solveMultiCell(const int num_cells, const int* cells) + { + std::cout << "Pretending to solve multi-cell dependent equation with " << num_cells << " cells." << std::endl; + for (int i = 0; i < num_cells; ++i) { + solveSingleCell(cells[i]); + } + } + + + + +} // namespace Opm diff --git a/opm/core/transport/reorder/TransportModelTracerTof.hpp b/opm/core/transport/reorder/TransportModelTracerTof.hpp new file mode 100644 index 00000000..270ac328 --- /dev/null +++ b/opm/core/transport/reorder/TransportModelTracerTof.hpp @@ -0,0 +1,74 @@ +/* + Copyright 2012 SINTEF ICT, Applied Mathematics. + + 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 . +*/ + +#ifndef OPM_TRANSPORTMODELTRACERTOF_HEADER_INCLUDED +#define OPM_TRANSPORTMODELTRACERTOF_HEADER_INCLUDED + +#include +#include +#include +#include +struct UnstructuredGrid; + +namespace Opm +{ + + class IncompPropertiesInterface; + + /// Implements a first-order finite volume solver for + /// (single-phase) time-of-flight using reordering. + /// The equation solved is: + /// v \cdot \grad\tau = \phi + /// where v is the fluid velocity, \tau is time-of-flight and + /// \phi is the porosity. This is a boundary value problem, where + /// \tau is specified to be zero on all inflow boundaries. + class TransportModelTracerTof : public TransportModelInterface + { + public: + /// Construct solver. + /// \param[in] grid A 2d or 3d grid. + TransportModelTracerTof(const UnstructuredGrid& grid); + + /// Solve for time-of-flight. + /// \param[in] darcyflux Array of signed face fluxes. + /// \param[in] porevolume Array of pore volumes. + /// \param[in] source Source term. Sign convention is: + /// (+) inflow flux, + /// (-) outflow flux. + /// \param[out] tof Array of time-of-flight values. + void solveTof(const double* darcyflux, + const double* porevolume, + const double* source, + std::vector& tof); + + private: + virtual void solveSingleCell(const int cell); + virtual void solveMultiCell(const int num_cells, const int* cells); + + private: + const UnstructuredGrid& grid_; + const double* darcyflux_; // one flux per grid face + const double* porevolume_; // one volume per cell + const double* source_; // one volumetric source term per cell + double* tof_; + }; + +} // namespace Opm + +#endif // OPM_TRANSPORTMODELTRACERTOF_HEADER_INCLUDED diff --git a/opm/core/transport/reorder/TransportModelTracerTofDiscGal.cpp b/opm/core/transport/reorder/TransportModelTracerTofDiscGal.cpp new file mode 100644 index 00000000..404452e0 --- /dev/null +++ b/opm/core/transport/reorder/TransportModelTracerTofDiscGal.cpp @@ -0,0 +1,657 @@ +/* + Copyright 2012 SINTEF ICT, Applied Mathematics. + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Opm +{ + + + // --------------- Helpers for TransportModelTracerTofDiscGal --------------- + + + + /// A class providing discontinuous Galerkin basis functions. + struct DGBasis + { + static int numBasisFunc(const int dimensions, + const int degree) + { + switch (dimensions) { + case 1: + return degree + 1; + case 2: + return (degree + 2)*(degree + 1)/2; + case 3: + return (degree + 3)*(degree + 2)*(degree + 1)/6; + default: + THROW("Dimensions must be 1, 2 or 3."); + } + } + + /// Evaluate all nonzero basis functions at x, + /// writing to f_x. The array f_x must have + /// size numBasisFunc(grid.dimensions, degree). + /// + /// The basis functions are the following + /// Degree 0: 1. + /// Degree 1: x - xc, y - yc, z - zc etc. + /// Further degrees await development. + static void eval(const UnstructuredGrid& grid, + const int cell, + const int degree, + const double* x, + double* f_x) + { + const int dim = grid.dimensions; + const double* cc = grid.cell_centroids + dim*cell; + // Note intentional fallthrough in this switch statement! + switch (degree) { + case 1: + for (int ix = 0; ix < dim; ++ix) { + f_x[1 + ix] = x[ix] - cc[ix]; + } + case 0: + f_x[0] = 1; + break; + default: + THROW("Maximum degree is 1 for now."); + } + } + + /// Evaluate gradients of all nonzero basis functions at x, + /// writing to grad_f_x. The array grad_f_x must have size + /// numBasisFunc(grid.dimensions, degree) * grid.dimensions. + /// The components of the first basis function + /// gradient come before the components of the second etc. + static void evalGrad(const UnstructuredGrid& grid, + const int /*cell*/, + const int degree, + const double* /*x*/, + double* grad_f_x) + { + const int dim = grid.dimensions; + const int num_basis = numBasisFunc(dim, degree); + std::fill(grad_f_x, grad_f_x + num_basis*dim, 0.0); + if (degree > 1) { + THROW("Maximum degree is 1 for now."); + } else if (degree == 1) { + for (int ix = 0; ix < dim; ++ix) { + grad_f_x[dim*(ix + 1) + ix] = 1.0; + } + } + } + }; + + + + static void cross(const double* a, const double* b, double* res) + { + res[0] = a[1]*b[2] - a[2]*b[1]; + res[1] = a[2]*b[0] - a[0]*b[2]; + res[2] = a[0]*b[1] - a[1]*b[0]; + } + + + + + static double triangleArea3d(const double* p0, + const double* p1, + const double* p2) + { + double a[3] = { p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2] }; + double b[3] = { p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2] }; + double cr[3]; + cross(a, b, cr); + return 0.5*std::sqrt(cr[0]*cr[0] + cr[1]*cr[1] + cr[2]*cr[2]); + } + + + + + /// Calculates the determinant of a 3 x 3 matrix, represented as + /// three three-dimensional arrays. + static double determinantOf(const double* a0, + const double* a1, + const double* a2) + { + return + a0[0] * (a1[1] * a2[2] - a2[1] * a1[2]) - + a0[1] * (a1[0] * a2[2] - a2[0] * a1[2]) + + a0[2] * (a1[0] * a2[1] - a2[0] * a1[1]); + } + + + + + /// Computes the volume of a tetrahedron consisting of 4 vertices + /// with 3-dimensional coordinates + static double tetVolume(const double* p0, + const double* p1, + const double* p2, + const double* p3) + { + double a[3] = { p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2] }; + double b[3] = { p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2] }; + double c[3] = { p3[0] - p0[0], p3[1] - p0[1], p3[2] - p0[2] }; + return std::fabs(determinantOf(a, b, c) / 6.0); + } + + + + + /// A class providing numerical quadrature for cells. + /// In general: \int_{cell} g(x) dx = \sum_{i=0}^{n-1} w_i g(x_i). + /// Note that this class does multiply weights by cell volume, + /// so weights always sum to cell volume. + /// Degree 1 method: + /// Midpoint (centroid) method. + /// n = 1, w_0 = cell volume, x_0 = cell centroid + /// Degree 2 method: + /// Based on subdivision of each cell face into triangles + /// with the face centroid as a common vertex, and then + /// subdividing the cell into tetrahedra with the cell + /// centroid as a common vertex. Then apply the tetrahedron + /// rule with the following 4 nodes (uniform weights): + /// a = 0.138196601125010515179541316563436 + /// x_i has all barycentric coordinates = a, except for + /// the i'th coordinate which is = 1 - 3a. + /// This rule is from http://nines.cs.kuleuven.be/ecf, + /// it is the second degree 2 4-point rule for tets, + /// referenced to Stroud(1971). + /// The tetrahedra are numbered T_{i,j}, and are given by the + /// cell centroid C, the face centroid FC_i, and two nodes + /// of face i: FN_{i,j}, FN_{i,j+1}. + class CellQuadrature + { + public: + CellQuadrature(const UnstructuredGrid& grid, + const int cell, + const int degree) + : grid_(grid), cell_(cell), degree_(degree) + { + if (degree > 2) { + THROW("CellQuadrature exact for polynomial degrees > 1 not implemented."); + } + if (degree == 2) { + // Prepare subdivision. + } + } + + int numQuadPts() const + { + if (degree_ < 2) { + return 1; + } + // Degree 2 case. + int sumnodes = 0; + for (int hf = grid_.cell_facepos[cell_]; hf < grid_.cell_facepos[cell_ + 1]; ++hf) { + const int face = grid_.cell_faces[hf]; + sumnodes += grid_.face_nodepos[face + 1] - grid_.face_nodepos[face]; + } + return 4*sumnodes; + } + + void quadPtCoord(const int index, double* coord) const + { + const int dim = grid_.dimensions; + const double* cc = grid_.cell_centroids + dim*cell_; + if (degree_ < 2) { + std::copy(cc, cc + dim, coord); + return; + } + // Degree 2 case. + int tetindex = index / 4; + const int subindex = index % 4; + const double* nc = grid_.node_coordinates; + for (int hf = grid_.cell_facepos[cell_]; hf < grid_.cell_facepos[cell_ + 1]; ++hf) { + const int face = grid_.cell_faces[hf]; + const int nfn = grid_.face_nodepos[face + 1] - grid_.face_nodepos[face]; + if (nfn <= tetindex) { + // Our tet is not associated with this face. + tetindex -= nfn; + continue; + } + const double* fc = grid_.face_centroids + dim*face; + const int* fnodes = grid_.face_nodes + grid_.face_nodepos[face]; + const int node0 = fnodes[tetindex]; + const int node1 = fnodes[(tetindex + 1) % nfn]; + const double* n0c = nc + dim*node0; + const double* n1c = nc + dim*node1; + const double a = 0.138196601125010515179541316563436; + // Barycentric coordinates of our point in the tet. + double baryc[4] = { a, a, a, a }; + baryc[subindex] = 1.0 - 3.0*a; + for (int dd = 0; dd < dim; ++dd) { + coord[dd] = baryc[0]*cc[dd] + baryc[1]*fc[dd] + baryc[2]*n0c[dd] + baryc[3]*n1c[dd]; + } + return; + } + THROW("Should never reach this point."); + } + + double quadPtWeight(const int index) const + { + if (degree_ < 2) { + return grid_.cell_volumes[cell_]; + } + // Degree 2 case. + const int dim = grid_.dimensions; + const double* cc = grid_.cell_centroids + dim*cell_; + int tetindex = index / 4; + const double* nc = grid_.node_coordinates; + for (int hf = grid_.cell_facepos[cell_]; hf < grid_.cell_facepos[cell_ + 1]; ++hf) { + const int face = grid_.cell_faces[hf]; + const int nfn = grid_.face_nodepos[face + 1] - grid_.face_nodepos[face]; + if (nfn <= tetindex) { + // Our tet is not associated with this face. + tetindex -= nfn; + continue; + } + const double* fc = grid_.face_centroids + dim*face; + const int* fnodes = grid_.face_nodes + grid_.face_nodepos[face]; + const int node0 = fnodes[tetindex]; + const int node1 = fnodes[(tetindex + 1) % nfn]; + const double* n0c = nc + dim*node0; + const double* n1c = nc + dim*node1; + return 0.25*tetVolume(cc, fc, n0c, n1c); + } + THROW("Should never reach this point."); + } + + private: + const UnstructuredGrid& grid_; + const int cell_; + const int degree_; + }; + + + + + + /// A class providing numerical quadrature for faces. + /// In general: \int_{face} g(x) dx = \sum_{i=0}^{n-1} w_i g(x_i). + /// Note that this class does multiply weights by face area, + /// so weights always sum to face area. + /// Degree 1 method: + /// Midpoint (centroid) method. + /// n = 1, w_0 = face area, x_0 = face centroid + /// Degree 2 method: + /// Based on subdivision of the face into triangles, + /// with the centroid as a common vertex, and the triangle + /// edge midpoint rule. + /// Triangle i consists of the centroid C, nodes N_i and N_{i+1}. + /// Its area is A_i. + /// n = 2 * nn (nn = num nodes in face) + /// For i = 0..(nn-1): + /// w_i = 1/3 A_i. + /// w_{nn+i} = 1/3 A_{i-1} + 1/3 A_i + /// x_i = (N_i + N_{i+1})/2 + /// x_{nn+i} = (C + N_i)/2 + /// All N and A indices are interpreted cyclic, modulus nn. + class FaceQuadrature + { + public: + FaceQuadrature(const UnstructuredGrid& grid, + const int face, + const int degree) + : grid_(grid), face_(face), degree_(degree) + { + if (grid_.dimensions != 3) { + THROW("FaceQuadrature only implemented for 3D case."); + } + if (degree_ > 2) { + THROW("FaceQuadrature exact for polynomial degrees > 2 not implemented."); + } + } + + int numQuadPts() const + { + if (degree_ < 2) { + return 1; + } + // Degree 2 case. + return 2 * (grid_.face_nodepos[face_ + 1] - grid_.face_nodepos[face_]); + } + + void quadPtCoord(const int index, double* coord) const + { + const int dim = grid_.dimensions; + const double* fc = grid_.face_centroids + dim*face_; + if (degree_ < 2) { + std::copy(fc, fc + dim, coord); + return; + } + // Degree 2 case. + const int nn = grid_.face_nodepos[face_ + 1] - grid_.face_nodepos[face_]; + const int* fnodes = grid_.face_nodes + grid_.face_nodepos[face_]; + const double* nc = grid_.node_coordinates; + if (index < nn) { + // Boundary edge midpoint. + const int node0 = fnodes[index]; + const int node1 = fnodes[(index + 1)%nn]; + for (int dd = 0; dd < dim; ++dd) { + coord[dd] = 0.5*(nc[dim*node0 + dd] + nc[dim*node1 + dd]); + } + } else { + // Interiour edge midpoint. + // Recall that index is now in [nn, 2*nn). + const int node = fnodes[index - nn]; + for (int dd = 0; dd < dim; ++dd) { + coord[dd] = 0.5*(nc[dim*node + dd] + fc[dd]); + } + } + } + + double quadPtWeight(const int index) const + { + if (degree_ < 2) { + return grid_.face_areas[face_]; + } + // Degree 2 case. + const int dim = grid_.dimensions; + const double* fc = grid_.face_centroids + dim*face_; + const int nn = grid_.face_nodepos[face_ + 1] - grid_.face_nodepos[face_]; + const int* fnodes = grid_.face_nodes + grid_.face_nodepos[face_]; + const double* nc = grid_.node_coordinates; + if (index < nn) { + // Boundary edge midpoint. + const int node0 = fnodes[index]; + const int node1 = fnodes[(index + 1)%nn]; + const double area = triangleArea3d(nc + dim*node1, nc + dim*node0, fc); + return area / 3.0; + } else { + // Interiour edge midpoint. + // Recall that index is now in [nn, 2*nn). + const int node0 = fnodes[(index - 1) % nn]; + const int node1 = fnodes[index - nn]; + const int node2 = fnodes[(index + 1) % nn]; + const double area0 = triangleArea3d(nc + dim*node1, nc + dim*node0, fc); + const double area1 = triangleArea3d(nc + dim*node2, nc + dim*node1, fc); + return (area0 + area1) / 3.0; + } + } + + private: + const UnstructuredGrid& grid_; + const int face_; + const int degree_; + }; + + + + + + // --------------- Methods of TransportModelTracerTofDiscGal --------------- + + + + /// Construct solver. + /// \param[in] grid A 2d or 3d grid. + /// \param[in] use_cvi If true, use corner point velocity interpolation. + /// Otherwise, use the basic constant interpolation. + TransportModelTracerTofDiscGal::TransportModelTracerTofDiscGal(const UnstructuredGrid& grid, + const bool use_cvi) + : grid_(grid), + coord_(grid.dimensions), + velocity_(grid.dimensions) + { + if (use_cvi) { + velocity_interpolation_.reset(new VelocityInterpolationECVI(grid)); + } else { + velocity_interpolation_.reset(new VelocityInterpolationConstant(grid)); + } + } + + + + + /// Solve for time-of-flight. + /// \param[in] darcyflux Array of signed face fluxes. + /// \param[in] porevolume Array of pore volumes. + /// \param[in] source Source term. Sign convention is: + /// (+) inflow flux, + /// (-) outflow flux. + /// \param[in] degree Polynomial degree of DG basis functions used. + /// \param[out] tof_coeff Array of time-of-flight solution coefficients. + /// The values are ordered by cell, meaning that + /// the K coefficients corresponding to the first + /// cell comes before the K coefficients corresponding + /// to the second cell etc. + /// K depends on degree and grid dimension. + void TransportModelTracerTofDiscGal::solveTof(const double* darcyflux, + const double* porevolume, + const double* source, + const int degree, + std::vector& tof_coeff) + { + darcyflux_ = darcyflux; + porevolume_ = porevolume; + source_ = source; +#ifndef NDEBUG + // Sanity check for sources. + const double cum_src = std::accumulate(source, source + grid_.number_of_cells, 0.0); + if (std::fabs(cum_src) > *std::max_element(source, source + grid_.number_of_cells)*1e-2) { + THROW("Sources do not sum to zero: " << cum_src); + } +#endif + degree_ = degree; + const int num_basis = DGBasis::numBasisFunc(grid_.dimensions, degree_); + tof_coeff.resize(num_basis*grid_.number_of_cells); + std::fill(tof_coeff.begin(), tof_coeff.end(), 0.0); + tof_coeff_ = &tof_coeff[0]; + rhs_.resize(num_basis); + jac_.resize(num_basis*num_basis); + orig_jac_.resize(num_basis*num_basis); + basis_.resize(num_basis); + basis_nb_.resize(num_basis); + grad_basis_.resize(num_basis*grid_.dimensions); + velocity_interpolation_->setupFluxes(darcyflux); + reorderAndTransport(grid_, darcyflux); + } + + + + + void TransportModelTracerTofDiscGal::solveSingleCell(const int cell) + { + // Residual: + // For each cell K, basis function b_j (spanning V_h), + // writing the solution u_h|K = \sum_i c_i b_i + // Res = - \int_K \sum_i c_i b_i v(x) \cdot \grad b_j dx + // + \int_{\partial K} F(u_h, u_h^{ext}, v(x) \cdot n) b_j ds + // - \int_K \phi b_j + // This is linear in c_i, so we do not need any nonlinear iterations. + // We assemble the jacobian and the right-hand side. The residual is + // equal to Res = Jac*c - rhs, and we compute rhs directly. + + const int dim = grid_.dimensions; + const int num_basis = DGBasis::numBasisFunc(dim, degree_); + + std::fill(rhs_.begin(), rhs_.end(), 0.0); + std::fill(jac_.begin(), jac_.end(), 0.0); + + // Compute cell residual contribution. + // Note: Assumes that \int_K b_j = 0 for all j > 0 + rhs_[0] += porevolume_[cell]; + + // Compute upstream residual contribution. + for (int hface = grid_.cell_facepos[cell]; hface < grid_.cell_facepos[cell+1]; ++hface) { + const int face = grid_.cell_faces[hface]; + double flux = 0.0; + int upstream_cell = -1; + if (cell == grid_.face_cells[2*face]) { + flux = darcyflux_[face]; + upstream_cell = grid_.face_cells[2*face+1]; + } else { + flux = -darcyflux_[face]; + upstream_cell = grid_.face_cells[2*face]; + } + if (upstream_cell < 0) { + // This is an outer boundary. Assumed tof = 0 on inflow, so no contribution. + continue; + } + if (flux >= 0.0) { + // This is an outflow boundary. + continue; + } + // Do quadrature over the face to compute + // \int_{\partial K} u_h^{ext} (v(x) \cdot n) b_j ds + // (where u_h^{ext} is the upstream unknown (tof)). + const double normal_velocity = flux / grid_.face_areas[face]; + FaceQuadrature quad(grid_, face, degree_); + for (int quad_pt = 0; quad_pt < quad.numQuadPts(); ++quad_pt) { + quad.quadPtCoord(quad_pt, &coord_[0]); + DGBasis::eval(grid_, cell, degree_, &coord_[0], &basis_[0]); + DGBasis::eval(grid_, upstream_cell, degree_, &coord_[0], &basis_nb_[0]); + const double tof_upstream = std::inner_product(basis_nb_.begin(), basis_nb_.end(), + tof_coeff_ + num_basis*upstream_cell, 0.0); + const double w = quad.quadPtWeight(quad_pt); + for (int j = 0; j < num_basis; ++j) { + rhs_[j] -= w * tof_upstream * normal_velocity * basis_[j]; + } + } + } + + // Compute cell jacobian contribution. We use Fortran ordering + // for jac_, i.e. rows cycling fastest. + { + CellQuadrature quad(grid_, cell, 2*degree_ - 1); + for (int quad_pt = 0; quad_pt < quad.numQuadPts(); ++quad_pt) { + // b_i (v \cdot \grad b_j) + quad.quadPtCoord(quad_pt, &coord_[0]); + DGBasis::eval(grid_, cell, degree_, &coord_[0], &basis_[0]); + DGBasis::evalGrad(grid_, cell, degree_, &coord_[0], &grad_basis_[0]); + velocity_interpolation_->interpolate(cell, &coord_[0], &velocity_[0]); + const double w = quad.quadPtWeight(quad_pt); + for (int j = 0; j < num_basis; ++j) { + for (int i = 0; i < num_basis; ++i) { + for (int dd = 0; dd < dim; ++dd) { + jac_[j*num_basis + i] -= w * basis_[j] * grad_basis_[dim*i + dd] * velocity_[dd]; + } + } + } + } + } + + // Compute downstream jacobian contribution from faces. + for (int hface = grid_.cell_facepos[cell]; hface < grid_.cell_facepos[cell+1]; ++hface) { + const int face = grid_.cell_faces[hface]; + double flux = 0.0; + if (cell == grid_.face_cells[2*face]) { + flux = darcyflux_[face]; + } else { + flux = -darcyflux_[face]; + } + if (flux <= 0.0) { + // This is an inflow boundary. + continue; + } + // Do quadrature over the face to compute + // \int_{\partial K} b_i (v(x) \cdot n) b_j ds + const double normal_velocity = flux / grid_.face_areas[face]; + FaceQuadrature quad(grid_, face, 2*degree_); + for (int quad_pt = 0; quad_pt < quad.numQuadPts(); ++quad_pt) { + // u^ext flux B (B = {b_j}) + quad.quadPtCoord(quad_pt, &coord_[0]); + DGBasis::eval(grid_, cell, degree_, &coord_[0], &basis_[0]); + const double w = quad.quadPtWeight(quad_pt); + for (int j = 0; j < num_basis; ++j) { + for (int i = 0; i < num_basis; ++i) { + jac_[j*num_basis + i] += w * basis_[i] * normal_velocity * basis_[j]; + } + } + } + } + + // Compute downstream jacobian contribution from sink terms. + // Contribution from inflow sources would be + // similar to the contribution from upstream faces, but + // it is zero since we let all external inflow be associated + // with a zero tof. + if (source_[cell] < 0.0) { + // A sink. + const double flux = -source_[cell]; // Sign convention for flux: outflux > 0. + const double flux_density = flux / grid_.cell_volumes[cell]; + // Do quadrature over the cell to compute + // \int_{K} b_i flux b_j dx + CellQuadrature quad(grid_, cell, 2*degree_); + for (int quad_pt = 0; quad_pt < quad.numQuadPts(); ++quad_pt) { + quad.quadPtCoord(quad_pt, &coord_[0]); + DGBasis::eval(grid_, cell, degree_, &coord_[0], &basis_[0]); + const double w = quad.quadPtWeight(quad_pt); + for (int j = 0; j < num_basis; ++j) { + for (int i = 0; i < num_basis; ++i) { + jac_[j*num_basis + i] += w * basis_[i] * flux_density * basis_[j]; + } + } + } + } + + // Solve linear equation. + MAT_SIZE_T n = num_basis; + MAT_SIZE_T nrhs = 1; + MAT_SIZE_T lda = num_basis; + std::vector piv(num_basis); + MAT_SIZE_T ldb = num_basis; + MAT_SIZE_T info = 0; + orig_jac_ = jac_; + dgesv_(&n, &nrhs, &jac_[0], &lda, &piv[0], &rhs_[0], &ldb, &info); + if (info != 0) { + // Print the local matrix and rhs. + std::cerr << "Failed solving single-cell system Ax = b in cell " << cell + << " with A = \n"; + for (int row = 0; row < n; ++row) { + for (int col = 0; col < n; ++col) { + std::cerr << " " << orig_jac_[row + n*col]; + } + std::cerr << '\n'; + } + std::cerr << "and b = \n"; + for (int row = 0; row < n; ++row) { + std::cerr << " " << rhs_[row] << '\n'; + } + THROW("Lapack error: " << info << " encountered in cell " << cell); + } + // The solution ends up in rhs_, so we must copy it. + std::copy(rhs_.begin(), rhs_.end(), tof_coeff_ + num_basis*cell); + } + + + + + void TransportModelTracerTofDiscGal::solveMultiCell(const int num_cells, const int* cells) + { + std::cout << "Pretending to solve multi-cell dependent equation with " << num_cells << " cells." << std::endl; + for (int i = 0; i < num_cells; ++i) { + solveSingleCell(cells[i]); + } + } + + + + +} // namespace Opm diff --git a/opm/core/transport/reorder/TransportModelTracerTofDiscGal.hpp b/opm/core/transport/reorder/TransportModelTracerTofDiscGal.hpp new file mode 100644 index 00000000..9a2e3b9c --- /dev/null +++ b/opm/core/transport/reorder/TransportModelTracerTofDiscGal.hpp @@ -0,0 +1,105 @@ +/* + Copyright 2012 SINTEF ICT, Applied Mathematics. + + 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 . +*/ + +#ifndef OPM_TRANSPORTMODELTRACERTOFDISCGAL_HEADER_INCLUDED +#define OPM_TRANSPORTMODELTRACERTOFDISCGAL_HEADER_INCLUDED + +#include +#include +#include +#include +#include + +struct UnstructuredGrid; + +namespace Opm +{ + + class IncompPropertiesInterface; + class VelocityInterpolationInterface; + + /// Implements a discontinuous Galerkin solver for + /// (single-phase) time-of-flight using reordering. + /// The equation solved is: + /// v \cdot \grad\tau = \phi + /// where v is the fluid velocity, \tau is time-of-flight and + /// \phi is the porosity. This is a boundary value problem, where + /// \tau is specified to be zero on all inflow boundaries. + /// The user may specify the polynomial degree of the basis function space + /// used, but only degrees 0 and 1 are supported so far. + class TransportModelTracerTofDiscGal : public TransportModelInterface + { + public: + /// Construct solver. + /// \param[in] grid A 2d or 3d grid. + /// \param[in] use_cvi If true, use corner point velocity interpolation. + /// Otherwise, use the basic constant interpolation. + TransportModelTracerTofDiscGal(const UnstructuredGrid& grid, + const bool use_cvi); + + + /// Solve for time-of-flight. + /// \param[in] darcyflux Array of signed face fluxes. + /// \param[in] porevolume Array of pore volumes. + /// \param[in] source Source term. Sign convention is: + /// (+) inflow flux, + /// (-) outflow flux. + /// \param[in] degree Polynomial degree of DG basis functions used. + /// \param[out] tof_coeff Array of time-of-flight solution coefficients. + /// The values are ordered by cell, meaning that + /// the K coefficients corresponding to the first + /// cell comes before the K coefficients corresponding + /// to the second cell etc. + /// K depends on degree and grid dimension. + void solveTof(const double* darcyflux, + const double* porevolume, + const double* source, + const int degree, + std::vector& tof_coeff); + + private: + virtual void solveSingleCell(const int cell); + virtual void solveMultiCell(const int num_cells, const int* cells); + + private: + // Disable copying and assignment. + TransportModelTracerTofDiscGal(const TransportModelTracerTofDiscGal&); + TransportModelTracerTofDiscGal& operator=(const TransportModelTracerTofDiscGal&); + + const UnstructuredGrid& grid_; + boost::shared_ptr velocity_interpolation_; + const double* darcyflux_; // one flux per grid face + const double* porevolume_; // one volume per cell + const double* source_; // one volumetric source term per cell + int degree_; + double* tof_coeff_; + std::vector rhs_; // single-cell right-hand-side + std::vector jac_; // single-cell jacobian + std::vector orig_jac_; // single-cell jacobian (copy) + // Below: storage for quantities needed by solveSingleCell(). + std::vector coord_; + std::vector basis_; + std::vector basis_nb_; + std::vector grad_basis_; + std::vector velocity_; + }; + +} // namespace Opm + +#endif // OPM_TRANSPORTMODELTRACERTOFDISCGAL_HEADER_INCLUDED diff --git a/opm/core/utility/DataMap.hpp b/opm/core/utility/DataMap.hpp new file mode 100644 index 00000000..833f7bcc --- /dev/null +++ b/opm/core/utility/DataMap.hpp @@ -0,0 +1,32 @@ +/* + Copyright 2012 SINTEF ICT, Applied Mathematics. + + 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 . +*/ + +#ifndef OPM_DATAMAP_HEADER_INCLUDED +#define OPM_DATAMAP_HEADER_INCLUDED + +#include +#include + +namespace Opm +{ + /// Intended to map strings (giving the output field names) to data. + typedef std::map*> DataMap; +} + +#endif diff --git a/opm/core/utility/NonuniformTableLinear.hpp b/opm/core/utility/NonuniformTableLinear.hpp new file mode 100644 index 00000000..71334460 --- /dev/null +++ b/opm/core/utility/NonuniformTableLinear.hpp @@ -0,0 +1,244 @@ +/* + Copyright 2009, 2010, 2011, 2012 SINTEF ICT, Applied Mathematics. + Copyright 2009, 2010, 2011, 2012 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 . +*/ + +#ifndef OPM_NONUNIFORMTABLELINEAR_HEADER_INCLUDED +#define OPM_NONUNIFORMTABLELINEAR_HEADER_INCLUDED + +#include +#include +#include +#include + +#include +#include + +namespace Opm +{ + + + /// @brief Exception used for domain errors. + struct OutsideDomainException : public std::exception {}; + + + /// @brief This class uses linear interpolation to compute the value + /// (and its derivative) of a function f sampled at possibly + /// nonuniform points. + /// @tparam T the range type of the function (should be an algebraic ring type) + template + class NonuniformTableLinear + { + public: + /// @brief Default constructor. + NonuniformTableLinear(); + + /// @brief Construct from vectors of x and y values. + /// @param x_values vector of domain values + /// @param y_values vector of corresponding range values. + NonuniformTableLinear(const std::vector& x_values, + const std::vector& y_values); + + /// @brief Get the domain. + /// @return the domain as a pair of doubles. + std::pair domain(); + + /// @brief Rescale the domain. + /// @param new_domain the new domain as a pair of doubles. + void rescaleDomain(std::pair new_domain); + + /// @brief Evaluate the value at x. + /// @param x a domain value + /// @return f(x) + double operator()(const double x) const; + + /// @brief Evaluate the derivative at x. + /// @param x a domain value + /// @return f'(x) + double derivative(const double x) const; + + /// @brief Evaluate the inverse at y. Requires T to be a double. + /// @param y a range value + /// @return f^{-1}(y) + double inverse(const double y) const; + + /// @brief Equality operator. + /// @param other another NonuniformTableLinear. + /// @return true if they are represented exactly alike. + bool operator==(const NonuniformTableLinear& other) const; + + /// @brief Policies for how to behave when trying to evaluate outside the domain. + enum RangePolicy {Throw = 0, ClosestValue = 1, Extrapolate = 2}; + + /// @brief Sets the behavioural policy for evaluation to the left of the domain. + /// @param rp the policy + void setLeftPolicy(RangePolicy rp); + + /// @brief Sets the behavioural policy for evaluation to the right of the domain. + /// @param rp the policy + void setRightPolicy(RangePolicy rp); + + protected: + std::vector x_values_; + std::vector y_values_; + mutable std::vector x_values_reversed_; + mutable std::vector y_values_reversed_; + RangePolicy left_; + RangePolicy right_; + }; + + + // A utility function + /// @brief Detect if a sequence is nondecreasing. + /// @tparam FI a forward iterator whose value type has operator< defined. + /// @param beg start of sequence + /// @param end one-beyond-end of sequence + /// @return false if there exists two consecutive values (v1, v2) in the sequence + /// for which v2 < v1, else returns true. + template + bool isNondecreasing(const FI beg, const FI end) + { + if (beg == end) return true; + FI it = beg; + ++it; + FI prev = beg; + for (; it != end; ++it, ++prev) { + if (*it < *prev) { + return false; + } + } + return true; + } + + + + // Member implementations. + + template + inline + NonuniformTableLinear + ::NonuniformTableLinear() + : left_(ClosestValue), right_(ClosestValue) + { + } + + template + inline + NonuniformTableLinear + ::NonuniformTableLinear(const std::vector& x_values, + const std::vector& y_values) + : x_values_(x_values), y_values_(y_values), + left_(ClosestValue), right_(ClosestValue) + { + ASSERT(isNondecreasing(x_values.begin(), x_values.end())); + } + + template + inline std::pair + NonuniformTableLinear + ::domain() + { + return std::make_pair(x_values_[0], x_values_.back()); + } + + template + inline void + NonuniformTableLinear + ::rescaleDomain(std::pair new_domain) + { + const double a = x_values_[0]; + const double b = x_values_.back(); + const double c = new_domain.first; + const double d = new_domain.second; + // x in [a, b] -> x in [c, d] + for (int i = 0; i < int(x_values_.size()); ++i) { + x_values_[i] = (x_values_[i] - a)*(d - c)/(b - a) + c; + } + } + + template + inline double + NonuniformTableLinear + ::operator()(const double x) const + { + return Opm::linearInterpolation(x_values_, y_values_, x); + } + + template + inline double + NonuniformTableLinear + ::derivative(const double x) const + { + return Opm::linearInterpolationDerivative(x_values_, y_values_, x); + } + + template + inline double + NonuniformTableLinear + ::inverse(const double y) const + { + if (y_values_.front() < y_values_.back()) { + return Opm::linearInterpolation(y_values_, x_values_, y); + } else { + if (y_values_reversed_.empty()) { + y_values_reversed_ = y_values_; + std::reverse(y_values_reversed_.begin(), y_values_reversed_.end()); + ASSERT(isNondecreasing(y_values_reversed_.begin(), y_values_reversed_.end())); + x_values_reversed_ = x_values_; + std::reverse(x_values_reversed_.begin(), x_values_reversed_.end()); + } + return Opm::linearInterpolation(y_values_reversed_, x_values_reversed_, y); + } + } + + template + inline bool + NonuniformTableLinear + ::operator==(const NonuniformTableLinear& other) const + { + return x_values_ == other.x_values_ + && y_values_ == other.y_values_ + && left_ == other.left_ + && right_ == other.right_; + } + + template + inline void + NonuniformTableLinear + ::setLeftPolicy(RangePolicy rp) + { + if (rp != ClosestValue) { + THROW("Only ClosestValue RangePolicy implemented."); + } + left_ = rp; + } + + template + inline void + NonuniformTableLinear + ::setRightPolicy(RangePolicy rp) + { + if (rp != ClosestValue) { + THROW("Only ClosestValue RangePolicy implemented."); + } + right_ = rp; + } + +} // namespace Opm + +#endif // OPM_NONUNIFORMTABLELINEAR_HEADER_INCLUDED diff --git a/opm/core/utility/Units.hpp b/opm/core/utility/Units.hpp index b1b0f1ee..b5a7c749 100644 --- a/opm/core/utility/Units.hpp +++ b/opm/core/utility/Units.hpp @@ -101,6 +101,7 @@ namespace Opm /// @{ const double gallon = 231 * cubic(inch); const double stb = 42 * gallon; + const double liter = 1 * cubic(prefix::deci*meter); /// @} /// \name Mass diff --git a/opm/core/utility/VelocityInterpolation.cpp b/opm/core/utility/VelocityInterpolation.cpp new file mode 100644 index 00000000..f895d88f --- /dev/null +++ b/opm/core/utility/VelocityInterpolation.cpp @@ -0,0 +1,180 @@ +/* + Copyright 2012 SINTEF ICT, Applied Mathematics. + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ + +#include +#include +#include + +namespace Opm +{ + + // -------- Methods of class VelocityInterpolationInterface -------- + + + VelocityInterpolationInterface::~VelocityInterpolationInterface() + { + } + + + + // -------- Methods of class VelocityInterpolationConstant -------- + + /// Constructor. + /// \param[in] grid A grid. + VelocityInterpolationConstant::VelocityInterpolationConstant(const UnstructuredGrid& grid) + : grid_(grid) + { + } + + /// Set up fluxes for interpolation. + /// \param[in] flux One signed flux per face in the grid. + void VelocityInterpolationConstant::setupFluxes(const double* flux) + { + flux_ = flux; + } + + /// Interpolate velocity. + /// \param[in] cell Cell in which to interpolate. + /// \param[in] x Coordinates of point at which to interpolate. + /// Must be array of length grid.dimensions. + /// \param[out] v Interpolated velocity. + /// Must be array of length grid.dimensions. + void VelocityInterpolationConstant::interpolate(const int cell, + const double* /*x*/, + double* v) const + { + const int dim = grid_.dimensions; + std::fill(v, v + dim, 0.0); + const double* cc = grid_.cell_centroids + cell*dim; + for (int hface = grid_.cell_facepos[cell]; hface < grid_.cell_facepos[cell+1]; ++hface) { + const int face = grid_.cell_faces[hface]; + const double* fc = grid_.face_centroids + face*dim; + double face_flux = 0.0; + if (cell == grid_.face_cells[2*face]) { + face_flux = flux_[face]; + } else { + ASSERT(cell == grid_.face_cells[2*face + 1]); + face_flux = -flux_[face]; + } + for (int dd = 0; dd < dim; ++dd) { + v[dd] += face_flux * (fc[dd] - cc[dd]) / grid_.cell_volumes[cell]; + } + } + } + + + // -------- Methods of class VelocityInterpolationECVI -------- + + + + /// Constructor. + /// \param[in] grid A grid. + VelocityInterpolationECVI::VelocityInterpolationECVI(const UnstructuredGrid& grid) + : bcmethod_(grid), grid_(grid) + { + } + + /// Set up fluxes for interpolation. + /// Computes the corner velocities. + /// \param[in] flux One signed flux per face in the grid. + void VelocityInterpolationECVI::setupFluxes(const double* flux) + { + // We must now update the velocity member of the CornerInfo + // for each corner. + const int dim = grid_.dimensions; + std::vector N(dim*dim); // Normals matrix. Fortran ordering! + std::vector orig_N(dim*dim); // Normals matrix. Fortran ordering! + std::vector f(dim); // Flux vector. + std::vector piv(dim); // For LAPACK solve + const SparseTable& all_ci = bcmethod_.cornerInfo(); + const std::vector& adj_faces = bcmethod_.adjacentFaces(); + corner_velocity_.resize(dim*all_ci.dataSize()); + const int num_cells = grid_.number_of_cells; + for (int cell = 0; cell < num_cells; ++cell) { + const int num_cell_corners = bcmethod_.numCorners(cell); + for (int cell_corner = 0; cell_corner < num_cell_corners; ++cell_corner) { + const int cid = all_ci[cell][cell_corner].corner_id; + for (int adj_ix = 0; adj_ix < dim; ++adj_ix) { + const int face = adj_faces[dim*cid + adj_ix]; + const double* fn = grid_.face_normals + dim*face; + for (int dd = 0; dd < dim; ++dd) { + N[adj_ix + dd*dim] = fn[dd]; // Row adj_ix, column dd + } + f[adj_ix] = flux[face]; + } + // Now we have built N and f. Solve Nv = f. + // Note that the face orientations do not matter, + // as changing an orientation would negate both a + // row in N and the corresponding element of f. + // Solving linear equation with LAPACK. + MAT_SIZE_T n = dim; + MAT_SIZE_T nrhs = 1; + MAT_SIZE_T lda = n; + MAT_SIZE_T ldb = n; + MAT_SIZE_T info = 0; + orig_N = N; + dgesv_(&n, &nrhs, &N[0], &lda, &piv[0], &f[0], &ldb, &info); + if (info != 0) { + // Print the local matrix and rhs. + std::cerr << "Failed solving single-cell system Nv = f in cell " << cell + << " with N = \n"; + for (int row = 0; row < n; ++row) { + for (int col = 0; col < n; ++col) { + std::cerr << " " << orig_N[row + n*col]; + } + std::cerr << '\n'; + } + std::cerr << "and f = \n"; + for (int row = 0; row < n; ++row) { + std::cerr << " " << f[row] << '\n'; + } + THROW("Lapack error: " << info << " encountered in cell " << cell); + } + // The solution ends up in f, so we must copy it. + std::copy(f.begin(), f.end(), corner_velocity_.begin() + dim*cid); + } + } + } + + /// Interpolate velocity. + /// \param[in] cell Cell in which to interpolate. + /// \param[in] x Coordinates of point at which to interpolate. + /// Must be array of length grid.dimensions. + /// \param[out] v Interpolated velocity. + /// Must be array of length grid.dimensions. + void VelocityInterpolationECVI::interpolate(const int cell, + const double* x, + double* v) const + { + const int n = bcmethod_.numCorners(cell); + const int dim = grid_.dimensions; + bary_coord_.resize(n); + bcmethod_.cartToBary(cell, x, &bary_coord_[0]); + std::fill(v, v + dim, 0.0); + const SparseTable& all_ci = bcmethod_.cornerInfo(); + for (int i = 0; i < n; ++i) { + const int cid = all_ci[cell][i].corner_id; + for (int dd = 0; dd < dim; ++dd) { + v[dd] += corner_velocity_[dim*cid + dd] * bary_coord_[i]; + } + } + } + + +} // namespace Opm diff --git a/opm/core/utility/VelocityInterpolation.hpp b/opm/core/utility/VelocityInterpolation.hpp new file mode 100644 index 00000000..3b7a5bc4 --- /dev/null +++ b/opm/core/utility/VelocityInterpolation.hpp @@ -0,0 +1,122 @@ +/* + Copyright 2012 SINTEF ICT, Applied Mathematics. + + 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 . +*/ + +#ifndef OPM_VELOCITYINTERPOLATION_HEADER_INCLUDED +#define OPM_VELOCITYINTERPOLATION_HEADER_INCLUDED + +#include +#include + +struct UnstructuredGrid; + +namespace Opm +{ + + /// Abstract interface for velocity interpolation method classes. + class VelocityInterpolationInterface + { + public: + virtual ~VelocityInterpolationInterface(); + + /// Set up fluxes for interpolation. + /// \param[in] flux One signed flux per face in the grid. + virtual void setupFluxes(const double* flux) = 0; + + /// Interpolate velocity. + /// \param[in] cell Cell in which to interpolate. + /// \param[in] x Coordinates of point at which to interpolate. + /// Must be array of length grid.dimensions. + /// \param[out] v Interpolated velocity. + /// Must be array of length grid.dimensions. + virtual void interpolate(const int cell, + const double* x, + double* v) const = 0; + }; + + + + + /// A constant velocity approximation. Will not actually interpolate + /// unless the fluxes are consistent with a constant velocity. + class VelocityInterpolationConstant : public VelocityInterpolationInterface + { + public: + /// Constructor. + /// \param[in] grid A grid. + explicit VelocityInterpolationConstant(const UnstructuredGrid& grid); + + /// Set up fluxes for interpolation. + /// \param[in] flux One signed flux per face in the grid. + virtual void setupFluxes(const double* flux); + + /// Interpolate velocity. + /// \param[in] cell Cell in which to interpolate. + /// \param[in] x Coordinates of point at which to interpolate. + /// Must be array of length grid.dimensions. + /// \param[out] v Interpolated velocity. + /// Must be array of length grid.dimensions. + virtual void interpolate(const int cell, + const double* x, + double* v) const; + private: + const UnstructuredGrid& grid_; + const double* flux_; + }; + + + + + + /// Interpolate velocity using the extended CVI scheme: + /// compute a corner velocity for each cell corner that + /// is consistent with fluxes of adjacent faces, then + /// interpolate with generalized barycentric coordinates. + class VelocityInterpolationECVI : public VelocityInterpolationInterface + { + public: + /// Constructor. + /// \param[in] grid A grid. + explicit VelocityInterpolationECVI(const UnstructuredGrid& grid); + + /// Set up fluxes for interpolation. + /// \param[in] flux One signed flux per face in the grid. + virtual void setupFluxes(const double* flux); + + /// Interpolate velocity. + /// \param[in] cell Cell in which to interpolate. + /// \param[in] x Coordinates of point at which to interpolate. + /// Must be array of length grid.dimensions. + /// \param[out] v Interpolated velocity. + /// Must be array of length grid.dimensions. + virtual void interpolate(const int cell, + const double* x, + double* v) const; + private: + WachspressCoord bcmethod_; + const UnstructuredGrid& grid_; + mutable std::vector bary_coord_; + std::vector corner_velocity_; // size = dim * #corners + }; + + + + +} // namespace Opm + +#endif // OPM_VELOCITYINTERPOLATION_HEADER_INCLUDED diff --git a/opm/core/utility/WachspressCoord.cpp b/opm/core/utility/WachspressCoord.cpp new file mode 100644 index 00000000..0d11b186 --- /dev/null +++ b/opm/core/utility/WachspressCoord.cpp @@ -0,0 +1,237 @@ +/* + Copyright 2012 SINTEF ICT, Applied Mathematics. + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ + +#include +#include +#include +#include +#include + +namespace Opm +{ + + + // -------- Helper methods for class WachspressCoord -------- + + namespace + { + /// Calculates the determinant of a 2 x 2 matrix, represented as + /// two two-dimensional arrays. + double determinantOf(const double* a0, + const double* a1) + { + return + a0[0] * a1[1] - a0[1] * a1[0]; + } + + /// Calculates the determinant of a 3 x 3 matrix, represented as + /// three three-dimensional arrays. + double determinantOf(const double* a0, + const double* a1, + const double* a2) + { + return + a0[0] * (a1[1] * a2[2] - a2[1] * a1[2]) - + a0[1] * (a1[0] * a2[2] - a2[0] * a1[2]) + + a0[2] * (a1[0] * a2[1] - a2[0] * a1[1]); + } + + /// Calculates the volume of the parallelepiped given by + /// the vectors n[i] for i = 0..(dim-1), each n[i] is of size dim. + double cornerVolume(double** n, const int dim) + { + ASSERT(dim == 2 || dim == 3); + double det = (dim == 2) ? determinantOf(n[0], n[1]) : determinantOf(n[0], n[1], n[2]); + return std::fabs(det); + } + + } // anonymous namespace + + + + // -------- Methods of class WachspressCoord -------- + + // The formula used is a modification of the formula given in: + // M. Meyer, A. Barr, H. Lee, and M. Desbrun. + // Generalized barycentric coordinates on irregular poly- + // gons. Journal of Graphics Tools, 7(1):13–22, 2002. + // + // The formula given there is, for a corner i, + // b_i = w_i / sum_{k} w_k + // w_i = V_i / (prod_{j \in adjacent faces} n_j * (x_i - x) ) + // ^^^ ^^^ ^^^ + // corner "volume" normal corner coordinates + // V_i = |Det({n_j}_{j \in adjacent faces})| + // The corner coordinate x_i above can be replaced with any point on face j + // without changing the value of w_i, and we replace it with c_j, the face + // centroid. + // However, this formula has the problem that the denominator of w_i becomes zero + // close to the boundary. Our solution is to multiply all w_i by + /// prod_{all j} n_j * (c_j - x), resulting in the formula: + // w_i = V_i * (prod_{j \in nonadjacent faces} n_j * (c_j - x) ). + // Another implementation note is that the above formulas assumes that + // the normals have length 1 (are unit normals). It is easy to see that this + // can be relaxed, since each normal occurs once in the formula for w_i, and + // all w_i will be scaled by the same number. In our implementation we therefore + // use the area-scaled normals directly as provided by the UnstructuredGrid. + + /// Constructor. + /// \param[in] grid A grid. + WachspressCoord::WachspressCoord(const UnstructuredGrid& grid) + : grid_(grid) + { + enum { Maxdim = 3 }; + const int dim = grid.dimensions; + if (dim > Maxdim) { + THROW("Grid has more than " << Maxdim << " dimensions."); + } + // Compute static data for each corner. + const int num_cells = grid.number_of_cells; + int corner_id_count = 0; + for (int cell = 0; cell < num_cells; ++cell) { + std::set cell_vertices; + std::vector cell_faces; + std::multimap vertex_adj_faces; + for (int hface = grid.cell_facepos[cell]; hface < grid.cell_facepos[cell + 1]; ++hface) { + const int face = grid.cell_faces[hface]; + cell_faces.push_back(face); + const int fn0 = grid.face_nodepos[face]; + const int fn1 = grid.face_nodepos[face + 1]; + cell_vertices.insert(grid.face_nodes + fn0, grid.face_nodes + fn1); + for (int fn = fn0; fn < fn1; ++fn) { + const int vertex = grid.face_nodes[fn]; + vertex_adj_faces.insert(std::make_pair(vertex, face)); + } + } + std::sort(cell_faces.begin(), cell_faces.end()); // set_difference requires sorted ranges + std::vector cell_corner_info; + std::set::const_iterator it = cell_vertices.begin(); + for (; it != cell_vertices.end(); ++it) { + CornerInfo ci; + ci.corner_id = corner_id_count++;; + ci.vertex = *it; + double* fnorm[Maxdim] = { 0 }; + typedef std::multimap::const_iterator MMIt; + std::pair frange = vertex_adj_faces.equal_range(ci.vertex); + int fi = 0; + std::vector vert_adj_faces(dim); + for (MMIt face_it = frange.first; face_it != frange.second; ++face_it, ++fi) { + if (fi >= dim) { + THROW("In cell " << cell << ", vertex " << ci.vertex << " has " + << " more than " << dim << " adjacent faces."); + } + fnorm[fi] = grid_.face_normals + dim*(face_it->second); + vert_adj_faces[fi] = face_it->second; + } + ASSERT(fi == dim); + adj_faces_.insert(adj_faces_.end(), vert_adj_faces.begin(), vert_adj_faces.end()); + const double corner_vol = cornerVolume(fnorm, dim); + ci.volume = corner_vol; + cell_corner_info.push_back(ci); + std::sort(vert_adj_faces.begin(), vert_adj_faces.end()); + std::vector vert_nonadj_faces(cell_faces.size() - vert_adj_faces.size()); + std::set_difference(cell_faces.begin(), cell_faces.end(), + vert_adj_faces.begin(), vert_adj_faces.end(), + vert_nonadj_faces.begin()); + nonadj_faces_.appendRow(vert_nonadj_faces.begin(), vert_nonadj_faces.end()); + } + corner_info_.appendRow(cell_corner_info.begin(), cell_corner_info.end()); + } + ASSERT(corner_id_count == corner_info_.dataSize()); + } + + + + + /// Count of vertices adjacent to a call. + /// \param[in] cell A cell index. + /// \return Number of corners of cell. + int WachspressCoord::numCorners(const int cell) const + { + return corner_info_[cell].size(); + } + + + /// The class stores some info for each corner. + /// \return The corner info container. + const SparseTable& WachspressCoord::cornerInfo() const + { + return corner_info_; + } + + + + /// The class stores some info for each corner. + /// \return The corner info container. + const std::vector& WachspressCoord::adjacentFaces() const + { + return adj_faces_; + } + + + + /// Compute generalized barycentric coordinates for some point x + /// with respect to the vertices of a grid cell. + /// \param[in] cell Cell in which to compute coordinates. + /// \param[in] x Coordinates of point in cartesian coordinates. + /// Must be array of length grid.dimensions. + /// \param[out] xb Coordinates of point in barycentric coordinates. + /// Must be array of length numCorners(cell). + void WachspressCoord::cartToBary(const int cell, + const double* x, + double* xb) const + { + // Note: + // A possible optimization is: compute all n_j * (c_j - x) factors + // once, instead of repeating computation for all corners (for + // which j is a nonadjacent face). + const int n = numCorners(cell); + const int dim = grid_.dimensions; + double totw = 0.0; + for (int i = 0; i < n; ++i) { + const CornerInfo& ci = corner_info_[cell][i]; + // Weight (unnormalized) is equal to: + // V_i * (prod_{j \in nonadjacent faces} n_j * (c_j - x) ) + // ^^^ ^^^ ^^^ + // corner "volume" normal centroid + xb[i] = ci.volume; + const int num_nonadj_faces = nonadj_faces_[ci.corner_id].size(); + for (int j = 0; j < num_nonadj_faces; ++j) { + const int face = nonadj_faces_[ci.corner_id][j]; + double factor = 0.0; + for (int dd = 0; dd < dim; ++dd) { + factor += grid_.face_normals[dim*face + dd]*(grid_.face_centroids[dim*face + dd] - x[dd]); + } + // Assumes outward-pointing normals, so negate factor if necessary. + if (grid_.face_cells[2*face] != cell) { + ASSERT(grid_.face_cells[2*face + 1] == cell); + factor = -factor; + } + xb[i] *= factor; + } + totw += xb[i]; + } + for (int i = 0; i < n; ++i) { + xb[i] /= totw; + } + } + + + +} // namespace Opm diff --git a/opm/core/utility/WachspressCoord.hpp b/opm/core/utility/WachspressCoord.hpp new file mode 100644 index 00000000..73312ef6 --- /dev/null +++ b/opm/core/utility/WachspressCoord.hpp @@ -0,0 +1,85 @@ +/* + Copyright 2012 SINTEF ICT, Applied Mathematics. + + 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 . +*/ + +#ifndef OPM_WACHSPRESSCOORD_HEADER_INCLUDED +#define OPM_WACHSPRESSCOORD_HEADER_INCLUDED + +#include +#include + +struct UnstructuredGrid; + +namespace Opm +{ + + /// Class capable of computing Wachspress coordinates in 2d and 3d. + /// The formula used is a modification of the formula given in: + /// M. Meyer, A. Barr, H. Lee, and M. Desbrun. + /// Generalized barycentric coordinates on irregular poly- + /// gons. Journal of Graphics Tools, 7(1):13–22, 2002. + class WachspressCoord + { + public: + /// Constructor. + /// \param[in] grid A grid. + explicit WachspressCoord(const UnstructuredGrid& grid); + + /// Count of vertices adjacent to a call. + /// \param[in] cell A cell index. + /// \return Number of corners of cell. + int numCorners(const int cell) const; + + /// Compute generalized barycentric coordinates for some point x + /// with respect to the vertices of a grid cell. + /// \param[in] cell Cell in which to compute coordinates. + /// \param[in] x Coordinates of point in cartesian coordinates. + /// Must be array of length grid.dimensions. + /// \param[out] xb Coordinates of point in barycentric coordinates. + /// Must be array of length numCorners(cell). + void cartToBary(const int cell, + const double* x, + double* xb) const; + + // A corner is here defined as a {cell, vertex} pair where the + // vertex is adjacent to the cell. + struct CornerInfo + { + int corner_id; // Unique for each corner. + int vertex; // Shared between corners belonging to different cells. + double volume; // Defined as det(N) where N is the matrix of adjacent face normals. + }; + + /// The class stores some info for each corner, made accessible for user convenience. + /// \return The corner info container. + const SparseTable& cornerInfo() const; + + /// The class stores adjacent faces for each corner, made accessible for user convenience. + /// \return The vector of adjacent faces. Size = dim * #corners. + const std::vector& adjacentFaces() const; + + private: + const UnstructuredGrid& grid_; + SparseTable corner_info_; // Corner info by cell. + std::vector adj_faces_; // Set of adjacent faces, by corner id. Contains dim face indices per corner. + SparseTable nonadj_faces_; // Set of nonadjacent faces, by corner id. + }; + +} // namespace Opm + +#endif // OPM_WACHSPRESSCOORD_HEADER_INCLUDED diff --git a/opm/core/utility/initState_impl.hpp b/opm/core/utility/initState_impl.hpp index 7d01ade0..575e18a4 100644 --- a/opm/core/utility/initState_impl.hpp +++ b/opm/core/utility/initState_impl.hpp @@ -29,6 +29,7 @@ #include #include #include +#include #include namespace Opm @@ -512,15 +513,23 @@ namespace Opm State& state) { const int num_phases = props.numPhases(); + const PhaseUsage pu = phaseUsageFromDeck(deck); + if (num_phases != pu.num_phases) { + THROW("initStateFromDeck(): user specified property object with " << num_phases << " phases, " + "found " << pu.num_phases << " phases in deck."); + } state.init(grid, num_phases); if (deck.hasField("EQUIL")) { if (num_phases != 2) { THROW("initStateFromDeck(): EQUIL-based init currently handling only two-phase scenarios."); } + if (pu.phase_used[BlackoilPhases::Vapour]) { + THROW("initStateFromDeck(): EQUIL-based init currently handling only oil-water scenario (no gas)."); + } // Set saturations depending on oil-water contact. const EQUIL& equil= deck.getEQUIL(); if (equil.equil.size() != 1) { - THROW("No region support yet."); + THROW("initStateFromDeck(): No region support yet."); } const double woc = equil.equil[0].water_oil_contact_depth_; initWaterOilContact(grid, props, woc, WaterBelow, state); @@ -528,37 +537,64 @@ namespace Opm const double datum_z = equil.equil[0].datum_depth_; const double datum_p = equil.equil[0].datum_depth_pressure_; initHydrostaticPressure(grid, props, woc, gravity, datum_z, datum_p, state); - } else if (deck.hasField("SWAT") && deck.hasField("PRESSURE")) { - // Set saturations from SWAT, pressure from PRESSURE. + } else if (deck.hasField("PRESSURE")) { + // Set saturations from SWAT/SGAS, pressure from PRESSURE. std::vector& s = state.saturation(); std::vector& p = state.pressure(); - const std::vector& sw_deck = deck.getFloatingPointValue("SWAT"); const std::vector& p_deck = deck.getFloatingPointValue("PRESSURE"); const int num_cells = grid.number_of_cells; if (num_phases == 2) { - for (int c = 0; c < num_cells; ++c) { - int c_deck = (grid.global_cell == NULL) ? c : grid.global_cell[c]; - s[2*c] = sw_deck[c_deck]; - s[2*c + 1] = 1.0 - s[2*c]; - p[c] = p_deck[c_deck]; + if (!pu.phase_used[BlackoilPhases::Aqua]) { + // oil-gas: we require SGAS + if (!deck.hasField("SGAS")) { + THROW("initStateFromDeck(): missing SGAS keyword in 2-phase init"); + } + const std::vector& sg_deck = deck.getFloatingPointValue("SGAS"); + const int gpos = pu.phase_pos[BlackoilPhases::Vapour]; + const int opos = pu.phase_pos[BlackoilPhases::Liquid]; + for (int c = 0; c < num_cells; ++c) { + int c_deck = (grid.global_cell == NULL) ? c : grid.global_cell[c]; + s[2*c + gpos] = sg_deck[c_deck]; + s[2*c + opos] = 1.0 - sg_deck[c_deck]; + p[c] = p_deck[c_deck]; + } + } else { + // water-oil or water-gas: we require SWAT + if (!deck.hasField("SWAT")) { + THROW("initStateFromDeck(): missing SWAT keyword in 2-phase init"); + } + const std::vector& sw_deck = deck.getFloatingPointValue("SWAT"); + const int wpos = pu.phase_pos[BlackoilPhases::Aqua]; + const int nwpos = (wpos + 1) % 2; + for (int c = 0; c < num_cells; ++c) { + int c_deck = (grid.global_cell == NULL) ? c : grid.global_cell[c]; + s[2*c + wpos] = sw_deck[c_deck]; + s[2*c + nwpos] = 1.0 - sw_deck[c_deck]; + p[c] = p_deck[c_deck]; + } } } else if (num_phases == 3) { - if (!deck.hasField("SGAS")) { - THROW("initStateFromDeck(): missing SGAS keyword in 3-phase init (only SWAT and PRESSURE found)."); + const bool has_swat_sgas = deck.hasField("SWAT") && deck.hasField("SGAS"); + if (!has_swat_sgas) { + THROW("initStateFromDeck(): missing SGAS or SWAT keyword in 3-phase init."); } + const int wpos = pu.phase_pos[BlackoilPhases::Aqua]; + const int gpos = pu.phase_pos[BlackoilPhases::Vapour]; + const int opos = pu.phase_pos[BlackoilPhases::Liquid]; + const std::vector& sw_deck = deck.getFloatingPointValue("SWAT"); const std::vector& sg_deck = deck.getFloatingPointValue("SGAS"); for (int c = 0; c < num_cells; ++c) { int c_deck = (grid.global_cell == NULL) ? c : grid.global_cell[c]; - s[3*c] = sw_deck[c_deck]; - s[3*c + 1] = 1.0 - (sw_deck[c_deck] + sg_deck[c_deck]); - s[3*c + 2] = sg_deck[c_deck]; + s[3*c + wpos] = sw_deck[c_deck]; + s[3*c + opos] = 1.0 - (sw_deck[c_deck] + sg_deck[c_deck]); + s[3*c + gpos] = sg_deck[c_deck]; p[c] = p_deck[c_deck]; } } else { THROW("initStateFromDeck(): init with SWAT etc. only available with 2 or 3 phases."); } } else { - THROW("initStateFromDeck(): we must either have EQUIL, or both SWAT and PRESSURE."); + THROW("initStateFromDeck(): we must either have EQUIL, or PRESSURE and SWAT/SOIL/SGAS."); } // Finally, init face pressures. diff --git a/opm/core/utility/miscUtilities.cpp b/opm/core/utility/miscUtilities.cpp index aeafbfca..73e10ac9 100644 --- a/opm/core/utility/miscUtilities.cpp +++ b/opm/core/utility/miscUtilities.cpp @@ -374,7 +374,10 @@ namespace Opm if (perf_rate > 0.0) { // perf_rate is a total inflow rate, we want a water rate. if (wells->type[w] != INJECTOR) { - std::cout << "**** Warning: crossflow in well with index " << w << " ignored." << std::endl; + std::cout << "**** Warning: crossflow in well " + << w << " perf " << perf - wells->well_connpos[w] + << " ignored. Rate was " + << perf_rate/Opm::unit::day << " m^3/day." << std::endl; perf_rate = 0.0; } else { ASSERT(std::fabs(comp_frac[0] + comp_frac[1] - 1.0) < 1e-6); @@ -550,6 +553,7 @@ namespace Opm { const int np = wells.number_of_phases; const int nw = wells.number_of_wells; + ASSERT(int(flow_rates_per_well_cell.size()) == wells.well_connpos[nw]); phase_flow_per_well.resize(nw * np); for (int wix = 0; wix < nw; ++wix) { for (int phase = 0; phase < np; ++phase) { diff --git a/opm/core/utility/miscUtilitiesBlackoil.cpp b/opm/core/utility/miscUtilitiesBlackoil.cpp index 6f4f7556..ae806e9a 100644 --- a/opm/core/utility/miscUtilitiesBlackoil.cpp +++ b/opm/core/utility/miscUtilitiesBlackoil.cpp @@ -21,7 +21,10 @@ #include #include #include +#include #include +#include +#include #include #include #include @@ -31,53 +34,74 @@ namespace Opm { - /// @brief Computes injected and produced volumes of all phases. + /// @brief Computes injected and produced surface volumes of all phases. /// Note 1: assumes that only the first phase is injected. /// Note 2: assumes that transport has been done with an /// implicit method, i.e. that the current state /// gives the mobilities used for the preceding timestep. - /// @param[in] props fluid and rock properties. - /// @param[in] p pressure (one value per cell) - /// @param[in] z surface-volume values (for all P phases) - /// @param[in] s saturation values (for all P phases) - /// @param[in] src if < 0: total outflow, if > 0: first phase inflow. - /// @param[in] dt timestep used - /// @param[out] injected must point to a valid array with P elements, - /// where P = s.size()/src.size(). - /// @param[out] produced must also point to a valid array with P elements. + /// Note 3: Gives surface volume values, not reservoir volumes + /// (as the incompressible version of the function does). + /// Also, assumes that transport_src is given in surface volumes + /// for injector terms! + /// @param[in] props fluid and rock properties. + /// @param[in] state state variables (pressure, sat, surfvol) + /// @param[in] transport_src if < 0: total resv outflow, if > 0: first phase surfv inflow + /// @param[in] dt timestep used + /// @param[out] injected must point to a valid array with P elements, + /// where P = s.size()/src.size(). + /// @param[out] produced must also point to a valid array with P elements. void computeInjectedProduced(const BlackoilPropertiesInterface& props, - const std::vector& press, - const std::vector& z, - const std::vector& s, - const std::vector& src, + const BlackoilState& state, + const std::vector& transport_src, const double dt, double* injected, double* produced) { - const int num_cells = src.size(); - const int np = s.size()/src.size(); - if (int(s.size()) != num_cells*np) { - THROW("Sizes of s and src vectors do not match."); + const int num_cells = transport_src.size(); + if (props.numCells() != num_cells) { + THROW("Size of transport_src vector does not match number of cells in props."); } + const int np = props.numPhases(); + if (int(state.saturation().size()) != num_cells*np) { + THROW("Sizes of state vectors do not match number of cells."); + } + const std::vector& press = state.pressure(); + const std::vector& s = state.saturation(); + const std::vector& z = state.surfacevol(); std::fill(injected, injected + np, 0.0); std::fill(produced, produced + np, 0.0); std::vector visc(np); std::vector mob(np); + std::vector A(np*np); + std::vector prod_resv_phase(np); + std::vector prod_surfvol(np); for (int c = 0; c < num_cells; ++c) { - if (src[c] > 0.0) { - injected[0] += src[c]*dt; - } else if (src[c] < 0.0) { - const double flux = -src[c]*dt; + if (transport_src[c] > 0.0) { + // Inflowing transport source is a surface volume flux + // for the first phase. + injected[0] += transport_src[c]*dt; + } else if (transport_src[c] < 0.0) { + // Outflowing transport source is a total reservoir + // volume flux. + const double flux = -transport_src[c]*dt; const double* sat = &s[np*c]; props.relperm(1, sat, &c, &mob[0], 0); props.viscosity(1, &press[c], &z[np*c], &c, &visc[0], 0); + props.matrix(1, &press[c], &z[np*c], &c, &A[0], 0); double totmob = 0.0; for (int p = 0; p < np; ++p) { mob[p] /= visc[p]; totmob += mob[p]; } + std::fill(prod_surfvol.begin(), prod_surfvol.end(), 0.0); for (int p = 0; p < np; ++p) { - produced[p] += (mob[p]/totmob)*flux; + prod_resv_phase[p] = (mob[p]/totmob)*flux; + for (int q = 0; q < np; ++q) { + prod_surfvol[q] += prod_resv_phase[p]*A[q + np*p]; + } + } + for (int p = 0; p < np; ++p) { + produced[p] += prod_surfvol[p]; } } } @@ -251,4 +275,58 @@ namespace Opm } } + + /// Compute two-phase transport source terms from well terms. + /// Note: Unlike the incompressible version of this function, + /// this version computes surface volume injection rates, + /// production rates are still total reservoir volumes. + /// \param[in] props Fluid and rock properties. + /// \param[in] wells Wells data structure. + /// \param[in] well_state Well pressures and fluxes. + /// \param[out] transport_src The transport source terms. They are to be interpreted depending on sign: + /// (+) positive inflow of first (water) phase (surface volume), + /// (-) negative total outflow of both phases (reservoir volume). + void computeTransportSource(const BlackoilPropertiesInterface& props, + const Wells* wells, + const WellState& well_state, + std::vector& transport_src) + { + int nc = props.numCells(); + transport_src.clear(); + transport_src.resize(nc, 0.0); + // Well contributions. + if (wells) { + const int nw = wells->number_of_wells; + const int np = wells->number_of_phases; + if (np != 2) { + THROW("computeTransportSource() requires a 2 phase case."); + } + std::vector A(np*np); + for (int w = 0; w < nw; ++w) { + const double* comp_frac = wells->comp_frac + np*w; + for (int perf = wells->well_connpos[w]; perf < wells->well_connpos[w + 1]; ++perf) { + const int perf_cell = wells->well_cells[perf]; + double perf_rate = well_state.perfRates()[perf]; + if (perf_rate > 0.0) { + // perf_rate is a total inflow reservoir rate, we want a surface water rate. + if (wells->type[w] != INJECTOR) { + std::cout << "**** Warning: crossflow in well " + << w << " perf " << perf - wells->well_connpos[w] + << " ignored. Reservoir rate was " + << perf_rate/Opm::unit::day << " m^3/day." << std::endl; + perf_rate = 0.0; + } else { + ASSERT(std::fabs(comp_frac[0] + comp_frac[1] - 1.0) < 1e-6); + perf_rate *= comp_frac[0]; // Water reservoir volume rate. + props.matrix(1, &well_state.perfPress()[perf], comp_frac, &perf_cell, &A[0], 0); + perf_rate *= A[0]; // Water surface volume rate. + } + } + transport_src[perf_cell] += perf_rate; + } + } + } + } + + } // namespace Opm diff --git a/opm/core/utility/miscUtilitiesBlackoil.hpp b/opm/core/utility/miscUtilitiesBlackoil.hpp index 565acc6e..f4949ce2 100644 --- a/opm/core/utility/miscUtilitiesBlackoil.hpp +++ b/opm/core/utility/miscUtilitiesBlackoil.hpp @@ -22,36 +22,40 @@ #include -struct UnstructuredGrid; +struct Wells; namespace Opm { class BlackoilPropertiesInterface; + class BlackoilState; + class WellState; - /// @brief Computes injected and produced volumes of all phases. + + /// @brief Computes injected and produced surface volumes of all phases. /// Note 1: assumes that only the first phase is injected. /// Note 2: assumes that transport has been done with an /// implicit method, i.e. that the current state /// gives the mobilities used for the preceding timestep. - /// @param[in] props fluid and rock properties. - /// @param[in] p pressure (one value per cell) - /// @param[in] z surface-volume values (for all P phases) - /// @param[in] s saturation values (for all P phases) - /// @param[in] src if < 0: total outflow, if > 0: first phase inflow. - /// @param[in] dt timestep used - /// @param[out] injected must point to a valid array with P elements, - /// where P = s.size()/src.size(). - /// @param[out] produced must also point to a valid array with P elements. + /// Note 3: Gives surface volume values, not reservoir volumes + /// (as the incompressible version of the function does). + /// Also, assumes that transport_src is given in surface volumes + /// for injector terms! + /// @param[in] props fluid and rock properties. + /// @param[in] state state variables (pressure, sat, surfvol) + /// @param[in] transport_src if < 0: total resv outflow, if > 0: first phase surfv inflow + /// @param[in] dt timestep used + /// @param[out] injected must point to a valid array with P elements, + /// where P = s.size()/src.size(). + /// @param[out] produced must also point to a valid array with P elements. void computeInjectedProduced(const BlackoilPropertiesInterface& props, - const std::vector& p, - const std::vector& z, - const std::vector& s, - const std::vector& src, + const BlackoilState& state, + const std::vector& transport_src, const double dt, double* injected, double* produced); + /// @brief Computes total mobility for a set of saturation values. /// @param[in] props rock and fluid properties /// @param[in] cells cells with which the saturation values are associated @@ -66,6 +70,7 @@ namespace Opm const std::vector& s, std::vector& totmob); + /// @brief Computes total mobility and omega for a set of saturation values. /// @param[in] props rock and fluid properties /// @param[in] cells cells with which the saturation values are associated @@ -131,6 +136,22 @@ namespace Opm const double* saturation, double* surfacevol); + + /// Compute two-phase transport source terms from well terms. + /// Note: Unlike the incompressible version of this function, + /// this version computes surface volume injection rates, + /// production rates are still total reservoir volumes. + /// \param[in] props Fluid and rock properties. + /// \param[in] wells Wells data structure. + /// \param[in] well_state Well pressures and fluxes. + /// \param[out] transport_src The transport source terms. They are to be interpreted depending on sign: + /// (+) positive inflow of first (water) phase (surface volume), + /// (-) negative total outflow of both phases (reservoir volume). + void computeTransportSource(const BlackoilPropertiesInterface& props, + const Wells* wells, + const WellState& well_state, + std::vector& transport_src); + } // namespace Opm #endif // OPM_MISCUTILITIESBLACKOIL_HEADER_INCLUDED diff --git a/opm/core/utility/writeECLData.cpp b/opm/core/utility/writeECLData.cpp new file mode 100644 index 00000000..5e9d4205 --- /dev/null +++ b/opm/core/utility/writeECLData.cpp @@ -0,0 +1,108 @@ +/* + Copyright 2012 SINTEF ICT, Applied Mathematics. + Copyright 2012 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 . +*/ + + + +#include +#include +#include +#include + +#include + +#include +#include +#include + + + +namespace Opm +{ + + static ecl_kw_type * ecl_kw_wrapper( const UnstructuredGrid& grid, + const std::string& kw_name , + const std::vector * data , + int offset , + int stride ) { + + ecl_kw_type * ecl_kw = ecl_kw_alloc( kw_name.c_str() , data->size() / stride , ECL_FLOAT_TYPE ); + if (grid.global_cell == NULL) { + for (int i=0; i < grid.number_of_cells; i++) + ecl_kw_iset_float( ecl_kw , i , (*data)[i*stride + offset]); + } else { + for (int i=0; i < grid.number_of_cells; i++) + ecl_kw_iset_float( ecl_kw , grid.global_cell[i] , (*data)[i*stride + offset]); + } + return ecl_kw; + } + + + + void writeECLData(const UnstructuredGrid& grid, + const DataMap& data, + const SimulatorTimer& simtimer, + const std::string& output_dir, + const std::string& base_name) { + + int step = simtimer.currentStepNum(); + ecl_file_enum file_type = ECL_UNIFIED_RESTART_FILE; + bool fmt_file = true; + char * filename = ecl_util_alloc_filename(output_dir.c_str() , base_name.c_str() , file_type , fmt_file , step ); + int phases = ECL_OIL_PHASE + ECL_WATER_PHASE; + double days = Opm::unit::convert::to(simtimer.currentTime(), Opm::unit::day); + time_t date = 0; + int nx = grid.cartdims[0]; + int ny = grid.cartdims[1]; + int nz = grid.cartdims[2]; + int nactive = grid.number_of_cells; + ecl_rst_file_type * rst_file; + + if (step > 0 && file_type == ECL_UNIFIED_RESTART_FILE) + rst_file = ecl_rst_file_open_append( filename ); + else + rst_file = ecl_rst_file_open_write( filename ); + + ecl_rst_file_fwrite_header( rst_file , step , date , days , nx , ny , nz , nactive , phases ); + ecl_rst_file_start_solution( rst_file ); + + { + DataMap::const_iterator i = data.find("pressure"); + if (i != data.end()) { + ecl_kw_type * pressure_kw = ecl_kw_wrapper( grid , "PRESSURE" , i->second , 0 , 1); + ecl_rst_file_add_kw( rst_file , pressure_kw ); + ecl_kw_free( pressure_kw ); + } + } + + { + DataMap::const_iterator i = data.find("saturation"); + if (i != data.end()) { + ecl_kw_type * swat_kw = ecl_kw_wrapper( grid , "SWAT" , i->second , 0 , 2); + ecl_rst_file_add_kw( rst_file , swat_kw ); + ecl_kw_free( swat_kw ); + } + } + + ecl_rst_file_end_solution( rst_file ); + ecl_rst_file_close( rst_file ); + free(filename); + } +} + diff --git a/opm/core/utility/writeECLData.hpp b/opm/core/utility/writeECLData.hpp new file mode 100644 index 00000000..1c963c0d --- /dev/null +++ b/opm/core/utility/writeECLData.hpp @@ -0,0 +1,47 @@ +/* + Copyright 2012 SINTEF ICT, Applied Mathematics. + Copyright 2012 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 . +*/ + +#ifndef OPM_WRITEECLDATA_HEADER_INCLUDED +#define OPM_WRITEECLDATA_HEADER_INCLUDED + + +#include +#include +#include +#include +#include +#include +#include + +struct UnstructuredGrid; + +namespace Opm +{ + + // ECLIPSE output for general grids. + void writeECLData(const UnstructuredGrid& grid, + const DataMap& data, + const SimulatorTimer& simtimer, + const std::string& output_dir, + const std::string& base_name); + +} + +#endif diff --git a/opm/core/utility/writeVtkData.cpp b/opm/core/utility/writeVtkData.cpp index 07e57172..3c70016e 100644 --- a/opm/core/utility/writeVtkData.cpp +++ b/opm/core/utility/writeVtkData.cpp @@ -18,6 +18,7 @@ */ #include +#include #include #include #include @@ -34,63 +35,63 @@ namespace Opm { void writeVtkData(const std::tr1::array& dims, - const std::tr1::array& cell_size, - const DataMap& data, - std::ostream& os) + const std::tr1::array& cell_size, + const DataMap& data, + std::ostream& os) { - // Dimension is hardcoded in the prototype and the next two lines, - // but the rest is flexible (allows dimension == 2 or 3). - int dimension = 3; - int num_cells = dims[0]*dims[1]*dims[2]; + // Dimension is hardcoded in the prototype and the next two lines, + // but the rest is flexible (allows dimension == 2 or 3). + int dimension = 3; + int num_cells = dims[0]*dims[1]*dims[2]; - ASSERT(dimension == 2 || dimension == 3); - ASSERT(num_cells == dims[0]*dims[1]* (dimension == 2 ? 1 : dims[2])); + ASSERT(dimension == 2 || dimension == 3); + ASSERT(num_cells == dims[0]*dims[1]* (dimension == 2 ? 1 : dims[2])); - os << "# vtk DataFile Version 2.0\n"; - os << "Structured Grid\n \n"; - os << "ASCII \n"; - os << "DATASET STRUCTURED_POINTS\n"; + os << "# vtk DataFile Version 2.0\n"; + os << "Structured Grid\n \n"; + os << "ASCII \n"; + os << "DATASET STRUCTURED_POINTS\n"; - os << "DIMENSIONS " - << dims[0] + 1 << " " - << dims[1] + 1 << " "; - if (dimension == 3) { - os << dims[2] + 1; - } else { - os << 1; - } - os << "\n"; - - os << "ORIGIN " << 0.0 << " " << 0.0 << " " << 0.0 << "\n"; + os << "DIMENSIONS " + << dims[0] + 1 << " " + << dims[1] + 1 << " "; + if (dimension == 3) { + os << dims[2] + 1; + } else { + os << 1; + } + os << "\n"; + + os << "ORIGIN " << 0.0 << " " << 0.0 << " " << 0.0 << "\n"; - os << "SPACING " << cell_size[0] << " " << cell_size[1]; - if (dimension == 3) { - os << " " << cell_size[2]; - } else { - os << " " << 0.0; - } - os << "\n"; + os << "SPACING " << cell_size[0] << " " << cell_size[1]; + if (dimension == 3) { + os << " " << cell_size[2]; + } else { + os << " " << 0.0; + } + os << "\n"; - os << "\nCELL_DATA " << num_cells << '\n'; - for (DataMap::const_iterator dit = data.begin(); dit != data.end(); ++dit) { - std::string name = dit->first; - os << "SCALARS " << name << " float" << '\n'; - os << "LOOKUP_TABLE " << name << "_table " << '\n'; - const std::vector& field = *(dit->second); - // We always print only the first data item for every - // cell, using 'stride'. - // This is a hack to get water saturation nicely. - // \TODO: Extend to properly printing vector data. - const int stride = field.size()/num_cells; - const int num_per_line = 5; - for (int c = 0; c < num_cells; ++c) { - os << field[stride*c] << ' '; - if (c % num_per_line == num_per_line - 1 - || c == num_cells - 1) { - os << '\n'; - } - } - } + os << "\nCELL_DATA " << num_cells << '\n'; + for (DataMap::const_iterator dit = data.begin(); dit != data.end(); ++dit) { + std::string name = dit->first; + os << "SCALARS " << name << " float" << '\n'; + os << "LOOKUP_TABLE " << name << "_table " << '\n'; + const std::vector& field = *(dit->second); + // We always print only the first data item for every + // cell, using 'stride'. + // This is a hack to get water saturation nicely. + // \TODO: Extend to properly printing vector data. + const int stride = field.size()/num_cells; + const int num_per_line = 5; + for (int c = 0; c < num_cells; ++c) { + os << field[stride*c] << ' '; + if (c % num_per_line == num_per_line - 1 + || c == num_cells - 1) { + os << '\n'; + } + } + } } typedef std::map PMap; @@ -98,219 +99,219 @@ namespace Opm struct Tag { - Tag(const std::string& tag, const PMap& props, std::ostream& os) - : name_(tag), os_(os) - { - indent(os); - os << "<" << tag; - for (PMap::const_iterator it = props.begin(); it != props.end(); ++it) { - os << " " << it->first << "=\"" << it->second << "\""; - } - os << ">\n"; - ++indent_; - } - Tag(const std::string& tag, std::ostream& os) - : name_(tag), os_(os) - { - indent(os); - os << "<" << tag << ">\n"; - ++indent_; - } - ~Tag() - { - --indent_; - indent(os_); - os_ << "\n"; - } - static void indent(std::ostream& os) - { - for (int i = 0; i < indent_; ++i) { - os << " "; - } - } + Tag(const std::string& tag, const PMap& props, std::ostream& os) + : name_(tag), os_(os) + { + indent(os); + os << "<" << tag; + for (PMap::const_iterator it = props.begin(); it != props.end(); ++it) { + os << " " << it->first << "=\"" << it->second << "\""; + } + os << ">\n"; + ++indent_; + } + Tag(const std::string& tag, std::ostream& os) + : name_(tag), os_(os) + { + indent(os); + os << "<" << tag << ">\n"; + ++indent_; + } + ~Tag() + { + --indent_; + indent(os_); + os_ << "\n"; + } + static void indent(std::ostream& os) + { + for (int i = 0; i < indent_; ++i) { + os << " "; + } + } private: - static int indent_; - std::string name_; - std::ostream& os_; + static int indent_; + std::string name_; + std::ostream& os_; }; int Tag::indent_ = 0; void writeVtkData(const UnstructuredGrid& grid, - const DataMap& data, - std::ostream& os) + const DataMap& data, + std::ostream& os) { - if (grid.dimensions != 3) { - THROW("Vtk output for 3d grids only"); - } - os.precision(12); - os << "\n"; - PMap pm; - pm["type"] = "UnstructuredGrid"; - Tag vtkfiletag("VTKFile", pm, os); - Tag ugtag("UnstructuredGrid", os); - int num_pts = grid.number_of_nodes; - int num_cells = grid.number_of_cells; - pm.clear(); - pm["NumberOfPoints"] = boost::lexical_cast(num_pts); - pm["NumberOfCells"] = boost::lexical_cast(num_cells); - Tag piecetag("Piece", pm, os); - { - Tag pointstag("Points", os); - pm.clear(); - pm["type"] = "Float64"; - pm["Name"] = "Coordinates"; - pm["NumberOfComponents"] = "3"; - pm["format"] = "ascii"; - Tag datag("DataArray", pm, os); - for (int i = 0; i < num_pts; ++i) { - Tag::indent(os); - os << grid.node_coordinates[3*i + 0] << ' ' - << grid.node_coordinates[3*i + 1] << ' ' - << grid.node_coordinates[3*i + 2] << '\n'; - } - } - { - Tag cellstag("Cells", os); - pm.clear(); - pm["type"] = "Int32"; - pm["NumberOfComponents"] = "1"; - pm["format"] = "ascii"; - std::vector cell_numpts; - cell_numpts.reserve(num_cells); - { - pm["Name"] = "connectivity"; - Tag t("DataArray", pm, os); - int hf = 0; - for (int c = 0; c < num_cells; ++c) { - std::set cell_pts; - for (; hf < grid.cell_facepos[c+1]; ++hf) { - int f = grid.cell_faces[hf]; - const int* fnbeg = grid.face_nodes + grid.face_nodepos[f]; - const int* fnend = grid.face_nodes + grid.face_nodepos[f+1]; - cell_pts.insert(fnbeg, fnend); - } - cell_numpts.push_back(cell_pts.size()); - Tag::indent(os); - std::copy(cell_pts.begin(), cell_pts.end(), - std::ostream_iterator(os, " ")); - os << '\n'; - } - } - { - pm["Name"] = "offsets"; - Tag t("DataArray", pm, os); - int offset = 0; - const int num_per_line = 10; - for (int c = 0; c < num_cells; ++c) { - if (c % num_per_line == 0) { - Tag::indent(os); - } - offset += cell_numpts[c]; - os << offset << ' '; - if (c % num_per_line == num_per_line - 1 - || c == num_cells - 1) { - os << '\n'; - } - } - } - std::vector cell_foffsets; - cell_foffsets.reserve(num_cells); - { - pm["Name"] = "faces"; - Tag t("DataArray", pm, os); - const int* fp = grid.cell_facepos; - int offset = 0; - for (int c = 0; c < num_cells; ++c) { - Tag::indent(os); - os << fp[c+1] - fp[c] << '\n'; - ++offset; - for (int hf = fp[c]; hf < fp[c+1]; ++hf) { - int f = grid.cell_faces[hf]; - const int* np = grid.face_nodepos; - int f_num_pts = np[f+1] - np[f]; - Tag::indent(os); - os << f_num_pts << ' '; - ++offset; - std::copy(grid.face_nodes + np[f], - grid.face_nodes + np[f+1], - std::ostream_iterator(os, " ")); - os << '\n'; - offset += f_num_pts; - } - cell_foffsets.push_back(offset); - } - } - { - pm["Name"] = "faceoffsets"; - Tag t("DataArray", pm, os); - const int num_per_line = 10; - for (int c = 0; c < num_cells; ++c) { - if (c % num_per_line == 0) { - Tag::indent(os); - } - os << cell_foffsets[c] << ' '; - if (c % num_per_line == num_per_line - 1 - || c == num_cells - 1) { - os << '\n'; - } - } - } - { - pm["type"] = "UInt8"; - pm["Name"] = "types"; - Tag t("DataArray", pm, os); - const int num_per_line = 10; - for (int c = 0; c < num_cells; ++c) { - if (c % num_per_line == 0) { - Tag::indent(os); - } - os << "42 "; - if (c % num_per_line == num_per_line - 1 - || c == num_cells - 1) { - os << '\n'; - } - } - } - } - { - pm.clear(); - if (data.find("saturation") != data.end()) { - pm["Scalars"] = "saturation"; - } else if (data.find("pressure") != data.end()) { - pm["Scalars"] = "pressure"; - } - Tag celldatatag("CellData", pm, os); - pm.clear(); - pm["NumberOfComponents"] = "1"; - pm["format"] = "ascii"; - pm["type"] = "Float64"; - for (DataMap::const_iterator dit = data.begin(); dit != data.end(); ++dit) { - pm["Name"] = dit->first; - const std::vector& field = *(dit->second); - const int num_comps = field.size()/grid.number_of_cells; - pm["NumberOfComponents"] = boost::lexical_cast(num_comps); - Tag ptag("DataArray", pm, os); - const int num_per_line = num_comps == 1 ? 5 : num_comps; - for (int item = 0; item < num_cells*num_comps; ++item) { - if (item % num_per_line == 0) { - Tag::indent(os); - } + if (grid.dimensions != 3) { + THROW("Vtk output for 3d grids only"); + } + os.precision(12); + os << "\n"; + PMap pm; + pm["type"] = "UnstructuredGrid"; + Tag vtkfiletag("VTKFile", pm, os); + Tag ugtag("UnstructuredGrid", os); + int num_pts = grid.number_of_nodes; + int num_cells = grid.number_of_cells; + pm.clear(); + pm["NumberOfPoints"] = boost::lexical_cast(num_pts); + pm["NumberOfCells"] = boost::lexical_cast(num_cells); + Tag piecetag("Piece", pm, os); + { + Tag pointstag("Points", os); + pm.clear(); + pm["type"] = "Float64"; + pm["Name"] = "Coordinates"; + pm["NumberOfComponents"] = "3"; + pm["format"] = "ascii"; + Tag datag("DataArray", pm, os); + for (int i = 0; i < num_pts; ++i) { + Tag::indent(os); + os << grid.node_coordinates[3*i + 0] << ' ' + << grid.node_coordinates[3*i + 1] << ' ' + << grid.node_coordinates[3*i + 2] << '\n'; + } + } + { + Tag cellstag("Cells", os); + pm.clear(); + pm["type"] = "Int32"; + pm["NumberOfComponents"] = "1"; + pm["format"] = "ascii"; + std::vector cell_numpts; + cell_numpts.reserve(num_cells); + { + pm["Name"] = "connectivity"; + Tag t("DataArray", pm, os); + int hf = 0; + for (int c = 0; c < num_cells; ++c) { + std::set cell_pts; + for (; hf < grid.cell_facepos[c+1]; ++hf) { + int f = grid.cell_faces[hf]; + const int* fnbeg = grid.face_nodes + grid.face_nodepos[f]; + const int* fnend = grid.face_nodes + grid.face_nodepos[f+1]; + cell_pts.insert(fnbeg, fnend); + } + cell_numpts.push_back(cell_pts.size()); + Tag::indent(os); + std::copy(cell_pts.begin(), cell_pts.end(), + std::ostream_iterator(os, " ")); + os << '\n'; + } + } + { + pm["Name"] = "offsets"; + Tag t("DataArray", pm, os); + int offset = 0; + const int num_per_line = 10; + for (int c = 0; c < num_cells; ++c) { + if (c % num_per_line == 0) { + Tag::indent(os); + } + offset += cell_numpts[c]; + os << offset << ' '; + if (c % num_per_line == num_per_line - 1 + || c == num_cells - 1) { + os << '\n'; + } + } + } + std::vector cell_foffsets; + cell_foffsets.reserve(num_cells); + { + pm["Name"] = "faces"; + Tag t("DataArray", pm, os); + const int* fp = grid.cell_facepos; + int offset = 0; + for (int c = 0; c < num_cells; ++c) { + Tag::indent(os); + os << fp[c+1] - fp[c] << '\n'; + ++offset; + for (int hf = fp[c]; hf < fp[c+1]; ++hf) { + int f = grid.cell_faces[hf]; + const int* np = grid.face_nodepos; + int f_num_pts = np[f+1] - np[f]; + Tag::indent(os); + os << f_num_pts << ' '; + ++offset; + std::copy(grid.face_nodes + np[f], + grid.face_nodes + np[f+1], + std::ostream_iterator(os, " ")); + os << '\n'; + offset += f_num_pts; + } + cell_foffsets.push_back(offset); + } + } + { + pm["Name"] = "faceoffsets"; + Tag t("DataArray", pm, os); + const int num_per_line = 10; + for (int c = 0; c < num_cells; ++c) { + if (c % num_per_line == 0) { + Tag::indent(os); + } + os << cell_foffsets[c] << ' '; + if (c % num_per_line == num_per_line - 1 + || c == num_cells - 1) { + os << '\n'; + } + } + } + { + pm["type"] = "UInt8"; + pm["Name"] = "types"; + Tag t("DataArray", pm, os); + const int num_per_line = 10; + for (int c = 0; c < num_cells; ++c) { + if (c % num_per_line == 0) { + Tag::indent(os); + } + os << "42 "; + if (c % num_per_line == num_per_line - 1 + || c == num_cells - 1) { + os << '\n'; + } + } + } + } + { + pm.clear(); + if (data.find("saturation") != data.end()) { + pm["Scalars"] = "saturation"; + } else if (data.find("pressure") != data.end()) { + pm["Scalars"] = "pressure"; + } + Tag celldatatag("CellData", pm, os); + pm.clear(); + pm["NumberOfComponents"] = "1"; + pm["format"] = "ascii"; + pm["type"] = "Float64"; + for (DataMap::const_iterator dit = data.begin(); dit != data.end(); ++dit) { + pm["Name"] = dit->first; + const std::vector& field = *(dit->second); + const int num_comps = field.size()/grid.number_of_cells; + pm["NumberOfComponents"] = boost::lexical_cast(num_comps); + Tag ptag("DataArray", pm, os); + const int num_per_line = num_comps == 1 ? 5 : num_comps; + for (int item = 0; item < num_cells*num_comps; ++item) { + if (item % num_per_line == 0) { + Tag::indent(os); + } double value = field[item]; if (std::fabs(value) < std::numeric_limits::min()) { // Avoiding denormal numbers to work around // bug in Paraview. value = 0.0; } - os << value << ' '; - if (item % num_per_line == num_per_line - 1 - || item == num_cells - 1) { - os << '\n'; - } - } - } - } + os << value << ' '; + if (item % num_per_line == num_per_line - 1 + || item == num_cells - 1) { + os << '\n'; + } + } + } + } } } // namespace Opm diff --git a/opm/core/utility/writeVtkData.hpp b/opm/core/utility/writeVtkData.hpp index 200b2b26..96cdfac2 100644 --- a/opm/core/utility/writeVtkData.hpp +++ b/opm/core/utility/writeVtkData.hpp @@ -26,25 +26,23 @@ #include #include #include +#include struct UnstructuredGrid; namespace Opm { - /// Intended to map strings (giving the output field names) to data. - typedef std::map*> DataMap; - /// Vtk output for cartesian grids. void writeVtkData(const std::tr1::array& dims, - const std::tr1::array& cell_size, - const DataMap& data, - std::ostream& os); + const std::tr1::array& cell_size, + const DataMap& data, + std::ostream& os); /// Vtk output for general grids. void writeVtkData(const UnstructuredGrid& grid, - const DataMap& data, - std::ostream& os); + const DataMap& data, + std::ostream& os); } // namespace Opm #endif // OPM_WRITEVTKDATA_HEADER_INCLUDED diff --git a/opm/core/vag_format/vag.cpp b/opm/core/vag_format/vag.cpp index 05edee4c..9beedbb5 100644 --- a/opm/core/vag_format/vag.cpp +++ b/opm/core/vag_format/vag.cpp @@ -64,7 +64,7 @@ namespace OPM } //cout << endl; } - if(!(int(pos_struct.value.size())==pos_struct.pos[n])){ + if(int(pos_struct.value.size()) != pos_struct.pos[n]){ cerr << "Failed to read pos structure" << endl; cerr << "pos_struct.value.size()" << pos_struct.value.size() << endl; cerr << "pos_struct.pos[n+1]" << pos_struct.pos[n] << endl; diff --git a/opm/core/wells/WellsGroup.cpp b/opm/core/wells/WellsGroup.cpp index afa17f12..8ffb3027 100644 --- a/opm/core/wells/WellsGroup.cpp +++ b/opm/core/wells/WellsGroup.cpp @@ -572,6 +572,7 @@ namespace Opm break; } const double total_produced = getTotalProductionFlow(well_surfacerates_phase, phase); + const double total_reinjected = - total_produced; // Production negative, injection positive const double my_guide_rate = injectionGuideRate(true); for (size_t i = 0; i < children_.size(); ++i) { // Apply for all children. @@ -580,11 +581,11 @@ namespace Opm const double children_guide_rate = children_[i]->injectionGuideRate(true); #ifdef DIRTY_WELLCTRL_HACK children_[i]->applyInjGroupControl(InjectionSpecification::RESV, - (children_guide_rate / my_guide_rate) * total_produced * injSpec().reinjection_fraction_target_, + (children_guide_rate / my_guide_rate) * total_reinjected * injSpec().reinjection_fraction_target_, false); #else children_[i]->applyInjGroupControl(InjectionSpecification::RATE, - (children_guide_rate / my_guide_rate) * total_produced * injSpec().reinjection_fraction_target_, + (children_guide_rate / my_guide_rate) * total_reinjected * injSpec().reinjection_fraction_target_, false); #endif } @@ -600,15 +601,15 @@ namespace Opm if (phaseUsage().phase_used[BlackoilPhases::Vapour]) { total_produced += getTotalProductionFlow(well_reservoirrates_phase, BlackoilPhases::Vapour); } - - const double my_guide_rate = injectionGuideRate(true); + const double total_reinjected = - total_produced; // Production negative, injection positive + const double my_guide_rate = injectionGuideRate(true); for (size_t i = 0; i < children_.size(); ++i) { // Apply for all children. // Note, we do _not_ want to call the applyProdGroupControl in this object, // as that would check if we're under group control, something we're not. const double children_guide_rate = children_[i]->injectionGuideRate(true); children_[i]->applyInjGroupControl(InjectionSpecification::RESV, - (children_guide_rate / my_guide_rate) * total_produced * injSpec().voidage_replacment_fraction_, + (children_guide_rate / my_guide_rate) * total_reinjected * injSpec().voidage_replacment_fraction_, false); } @@ -760,16 +761,17 @@ namespace Opm wells_->ctrls[self_index_]->current = ~ wells_->ctrls[self_index_]->current; } } - + std::pair WellNode::getWorstOffending(const std::vector& well_reservoirrates_phase, const std::vector& well_surfacerates_phase, ProductionSpecification::ControlMode mode) { const int np = phaseUsage().num_phases; const int index = self_index_*np; - return std::make_pair(this, rateByMode(&well_reservoirrates_phase[index], - &well_surfacerates_phase[index], - mode)); + return std::pair(this, + rateByMode(&well_reservoirrates_phase[index], + &well_surfacerates_phase[index], + mode)); } void WellNode::applyInjGroupControl(const InjectionSpecification::ControlMode control_mode, @@ -781,7 +783,7 @@ namespace Opm && (injSpec().control_mode_ != InjectionSpecification::GRUP && injSpec().control_mode_ != InjectionSpecification::NONE)) { return; } - if (!wells_->type[self_index_] == INJECTOR) { + if (wells_->type[self_index_] != INJECTOR) { ASSERT(target == 0.0); return; } @@ -858,12 +860,12 @@ namespace Opm std::cout << "Returning" << std::endl; return; } - if (!wells_->type[self_index_] == PRODUCER) { + if (wells_->type[self_index_] != PRODUCER) { ASSERT(target == 0.0); return; } // We're a producer, so we need to negate the input - double ntarget = target; + double ntarget = -target; double distr[3] = { 0.0, 0.0, 0.0 }; const int* phase_pos = phaseUsage().phase_pos; diff --git a/opm/core/wells/WellsManager.cpp b/opm/core/wells/WellsManager.cpp index 79387242..f4c7f069 100644 --- a/opm/core/wells/WellsManager.cpp +++ b/opm/core/wells/WellsManager.cpp @@ -230,6 +230,14 @@ namespace Opm + /// Construct from existing wells object. + WellsManager::WellsManager(struct Wells* W) + : w_(clone_wells(W)) + { + } + + + /// Construct wells from deck. WellsManager::WellsManager(const Opm::EclipseGridParser& deck, const UnstructuredGrid& grid, @@ -548,7 +556,7 @@ namespace Opm cf[pu.phase_pos[BlackoilPhases::Liquid]] = 1.0; } else if (wci_line.injector_type_[0] == 'G') { if (!pu.phase_used[BlackoilPhases::Vapour]) { - THROW("Water phase not used, yet found water-injecting well."); + THROW("Gas phase not used, yet found gas-injecting well."); } cf[pu.phase_pos[BlackoilPhases::Vapour]] = 1.0; } @@ -720,32 +728,31 @@ namespace Opm #endif if (deck.hasField("WELOPEN")) { - const WELOPEN& welopen = deck.getWELOPEN(); - - for (size_t i = 0; i < welopen.welopen.size(); ++i) { - WelopenLine line = welopen.welopen[i]; - std::string wellname = line.well_; - std::map::const_iterator it = well_names_to_index.find(wellname); - if (it == well_names_to_index.end()) { - THROW("Trying to open/shut well with name: \"" << wellname<<"\" but it's not registered under WELSPECS."); - } - int index = it->second; - if (line.openshutflag_ == "SHUT") { - // We currently don't care if the well is open or not. - /// \TODO Should this perhaps be allowed? I.e. should it be if(well_shut) { shutwell(); } else { /* do nothing*/ }? - //ASSERT(w_->ctrls[index]->current < 0); - } else if (line.openshutflag_ == "OPEN") { - //ASSERT(w_->ctrls[index]->current >= 0); - } else { - THROW("Unknown Open/close keyword: \"" << line.openshutflag_<< "\". Allowed values: OPEN, SHUT."); - } - - // We revert back to it's original control. - // Note that this is OK as ~~ = id. - w_->ctrls[index]->current = ~w_->ctrls[index]->current; - - - } + const WELOPEN& welopen = deck.getWELOPEN(); + for (size_t i = 0; i < welopen.welopen.size(); ++i) { + WelopenLine line = welopen.welopen[i]; + std::string wellname = line.well_; + std::map::const_iterator it = well_names_to_index.find(wellname); + if (it == well_names_to_index.end()) { + THROW("Trying to open/shut well with name: \"" << wellname<<"\" but it's not registered under WELSPECS."); + } + const int index = it->second; + if (line.openshutflag_ == "SHUT") { + int& cur_ctrl = w_->ctrls[index]->current; + if (cur_ctrl >= 0) { + cur_ctrl = ~cur_ctrl; + } + ASSERT(w_->ctrls[index]->current < 0); + } else if (line.openshutflag_ == "OPEN") { + int& cur_ctrl = w_->ctrls[index]->current; + if (cur_ctrl < 0) { + cur_ctrl = ~cur_ctrl; + } + ASSERT(w_->ctrls[index]->current >= 0); + } else { + THROW("Unknown Open/close keyword: \"" << line.openshutflag_<< "\". Allowed values: OPEN, SHUT."); + } + } } // Build the well_collection_ well group hierarchy. diff --git a/opm/core/wells/WellsManager.hpp b/opm/core/wells/WellsManager.hpp index 650eaf37..19289c5d 100644 --- a/opm/core/wells/WellsManager.hpp +++ b/opm/core/wells/WellsManager.hpp @@ -42,8 +42,12 @@ namespace Opm public: /// Default constructor -- no wells. WellsManager(); - /// Construct from mrst type output. - /// Wellmanger is not properly initialized + + /// Construct from existing wells object. + /// WellsManager is not properly initialised in the sense that the logic to + /// manage control switching does not exist. + /// + /// @param[in] W Existing wells object. WellsManager(struct Wells* W); /// Construct from input deck and grid. diff --git a/opmcore-config.cmake.in b/opmcore-config.cmake.in new file mode 100644 index 00000000..c6c91bb6 --- /dev/null +++ b/opmcore-config.cmake.in @@ -0,0 +1,40 @@ +# - Open Porous Media Initiative Core Library config mode +# +# Defines the following variables: +# @PACKAGE@_FOUND - true +# @PACKAGE@_VERSION - version of the opm-core library found, e.g. 0.2 +# @PACKAGE@_DEFINITIONS - defines to be made on the command line +# @PACKAGE@_INCLUDE_DIRS - header directories with which to compile +# @PACKAGE@_LIBRARY_DIRS - directories to search for libraries +# @PACKAGE@_LIBRARIES - names of the libraries with which to link +# +# You should put lines like this in your CMakeLists.txt +# set (@PACKAGE@_DIR "../@PACKAGE@" CACHE LOCATION "Build tree of @PACKAGE_NAME@") +# find_package (@PACKAGE@) + +# + +# propagate this property from one build system to the other +set (@PACKAGE@_VERSION @PACKAGE_VERSION@) + +# these definitions may be necessary to make the header files behave the +# same way as they did when the library was compiled +set (@PACKAGE@_DEFINITIONS "@OPM_BOOST_CPPFLAGS@ @SUPERLU_CPPFLAGS@ @ERT_CPPFLAGS@") + +# include files come from the source tree where the template is stored +set (@PACKAGE@_INCLUDE_DIRS "@abs_top_srcdir@") + +# user programs should link with this library (see comment in the header) +set (@PACKAGE@_LIBRARIES "@PACKAGE@") + +# convention is to have a variable named like this to add to link directories +set (@PACKAGE@_LIBRARY_DIRS "@abs_top_builddir@/lib/.libs") + +# libraries come from the build tree where this file was generated +find_library (@PACKAGE@_LOCATION NAMES "@PACKAGE@" PATHS "${@PACKAGE@_LIBRARY_DIRS}") +mark_as_advanced (@PACKAGE@_LOCATION) + +# add the library as a target, so that other things in the project including +# this file may depend on it and get rebuild if this library changes. +add_library (@PACKAGE@ UNKNOWN IMPORTED) +set_property (TARGET @PACKAGE@ PROPERTY IMPORTED_LOCATION "${@PACKAGE@_LOCATION}") diff --git a/tests/Makefile.am b/tests/Makefile.am index b1e70503..427818a1 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,8 +1,9 @@ AM_CPPFLAGS = \ -I$(top_srcdir) \ -$(OPM_BOOST_CPPFLAGS) +$(OPM_BOOST_CPPFLAGS) $(SUPERLU_CPPFLAGS) \ +$(ERT_CPPFLAGS) -AM_LDFLAGS = $(OPM_BOOST_LDFLAGS) +AM_LDFLAGS = $(OPM_BOOST_LDFLAGS) $(ERT_LDFLAGS) LDADD = $(top_builddir)/lib/libopmcore.la @@ -23,6 +24,9 @@ test_read_grid \ test_read_vag \ test_readpolymer \ test_sf2p \ +test_velocityinterpolation \ +test_wachspresscoord \ +test_wells \ test_writeVtkData \ unit_test @@ -57,8 +61,17 @@ test_read_vag_SOURCES = test_read_vag.cpp test_sf2p_SOURCES = test_sf2p.cpp +test_velocityinterpolation_SOURCES = test_velocityinterpolation.cpp +test_velocityinterpolation_LDADD = $(LDADD) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) + +test_wachspresscoord_SOURCES = test_wachspresscoord.cpp +test_wachspresscoord_LDADD = $(LDADD) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) + test_writeVtkData_SOURCES = test_writeVtkData.cpp +test_wells_SOURCES = test_wells.cpp +test_wells_LDADD = $(LDADD) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) + unit_test_SOURCES = unit_test.cpp @@ -73,3 +86,9 @@ if BUILD_AGMG noinst_PROGRAMS += test_agmg test_agmg_SOURCES = test_agmg.cpp endif + + +if HAVE_ERT +noinst_PROGRAMS += test_ert +test_ert_SOURCES = test_ert.cpp +endif diff --git a/tests/bo_resprop_test.cpp b/tests/bo_resprop_test.cpp index 5bf3aea1..02357e75 100644 --- a/tests/bo_resprop_test.cpp +++ b/tests/bo_resprop_test.cpp @@ -43,7 +43,7 @@ int main(int argc, char** argv) UnstructuredGrid grid; grid.number_of_cells = 1; grid.global_cell = NULL; - Opm::BlackoilPropertiesFromDeck props(deck, grid); + Opm::BlackoilPropertiesFromDeck props(deck, grid, param); const int n = 1; double p[n] = { 150e5 }; diff --git a/tests/test_ert.cpp b/tests/test_ert.cpp new file mode 100644 index 00000000..282549d1 --- /dev/null +++ b/tests/test_ert.cpp @@ -0,0 +1,260 @@ +/* + Copyright 2012 SINTEF ICT, Applied Mathematics. + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ + + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include + + +#include + +using namespace std; + +#if 0 +static +void cell_nodes(const UnstructuredGrid * c_grid , int cell , std::vector& nodes) { + int face_offset = c_grid->cell_facepos[cell]; + int num_faces = c_grid->cell_facepos[cell + 1] - face_offset; + + nodes.clear(); + //printf("cell: %d \n",cell); + for (int iface = 0; iface < num_faces; iface++) { + int face = c_grid->cell_faces[ face_offset + iface]; + //printf("face[%d] = %d \n",iface , face ); + + { + int node_offset = c_grid->face_nodepos[ face ]; + int num_nodes = c_grid->face_nodepos[ face + 1] - node_offset; + for (int inode = 0; inode < num_nodes; inode++) { + int node = c_grid->face_nodes[ inode + node_offset ]; + //printf(" node[%d] = %d \n",inode , node); + nodes.push_back( node ); + } + } + } + + { + /*for (int i =0; i < nodes.size(); i++) + std::cout << nodes[i] << " "; + std::cout << "\n"; + */ + sort( nodes.begin() , nodes.end()); + /*for (int i =0; i < nodes.size(); i++) + std::cout << nodes[i] << " "; + std::cout << "\n"; + */ + unique( nodes.begin() , nodes.end() ); + /*for (int i =0; i < nodes.size(); i++) + std::cout << nodes[i] << " "; + std::cout << "\n"; + */ + nodes.resize( 8 ); + /*for (int i =0; i < nodes.size(); i++) + std::cout << nodes[i] << " "; + std::cout << "\n"; + */ + } +} +#endif + +/* +static +void eclExport(Opm::GridManager& grid) { + const UnstructuredGrid * c_grid = grid.c_grid(); + + printf("dimensions : %d \n",c_grid->dimensions); + printf("number of cells : %d \n",c_grid->number_of_cells); + printf("number of nodes : %d \n",c_grid->number_of_nodes); + printf("number of faces : %d \n",c_grid->number_of_faces); + printf("length(face_nodes) : %d \n",c_grid->face_nodepos[ c_grid->number_of_faces ]); + printf("cartdims : %d %d %d \n", + c_grid->cartdims[0] , + c_grid->cartdims[1] , + c_grid->cartdims[2]); + + printf("global_cell : %d %d %d %d %d\n", + c_grid->global_cell[0] , + c_grid->global_cell[1] , + c_grid->global_cell[2] , + c_grid->global_cell[3] , + c_grid->global_cell[4]); + + { + std::vector nodes; + cell_nodes( c_grid , 10 , nodes ); + cell_nodes( c_grid , 15 , nodes ); + cell_nodes( c_grid , 20 , nodes ); + cell_nodes( c_grid , 25 , nodes ); + } + + { + ecl_grid_type * ecl_grid; + int num_coords = c_grid->number_of_cells; + int coords_size = 4; + int nx = c_grid->cartdims[0]; + int ny = c_grid->cartdims[1]; + int nz = c_grid->cartdims[2]; + + int ** coords; + float ** corners; + // float * mapaxes = NULL; + std::vector nodes; + + corners = (float **) malloc( num_coords * sizeof * corners ); + coords = (int **) malloc( num_coords * sizeof * coords ); + + { + int c; + for (c=0; c < num_coords; c++) { + corners[c] = (float *) malloc( 24 * sizeof * corners[c] ); + coords[c] = (int *) malloc( coords_size * sizeof * coords[c] ); + } + + for (c=0; c < num_coords; c++) { + cell_nodes( c_grid , c , nodes ); + for (int p=0; p < 8; p++) { + int n = nodes[p]; + for (int d=0; d < c_grid->dimensions; d++) + corners[c][3*p + d] = c_grid->node_coordinates[ c_grid->dimensions * n + d ]; + } + + { + int i,j,k; + { + int g = c_grid->global_cell[ c ]; + k = g / nx*ny; g -= k * nx*ny; + j = g / nx; g -= j * nx; + i = g; + } + + coords[c][0] = i + 1; + coords[c][1] = j + 1; + coords[c][2] = k + 1; + coords[c][3] = c_grid->global_cell[ c ] + 1; + } + } + } + + ecl_grid = ecl_grid_alloc( "/private/joaho/ERT/NR/libenkf/src/Gurbat/EXAMPLE_01_BASE.EGRID" ); + printf("Grid loaded ... \n"); + ecl_grid_free( ecl_grid ); + + printf("Grid discarded ... \n"); + + ecl_grid = ecl_grid_alloc_GRID_data( num_coords , nx , ny , nz , coords_size , coords , corners , NULL ); + ecl_grid_fwrite_GRID( ecl_grid , "/tmp/test.GRID" ); + + { + FILE * stream = fopen( "/tmp/test.grdecl" , "w"); + ecl_grid_fprintf_grdecl( ecl_grid , stream ); + fclose( stream ); + } + + ecl_grid_free( ecl_grid ); + + { + for (int c=0; c < num_coords; c++) { + free(corners[c]); + free(coords[c]); + } + } + free( corners ); + free( coords ); + } +} +*/ + + +/* + + #ifdef HAVE_ERT +ecl_grid_type * create_ecl_grid( const struct UnstructuredGrid * g) { + int num_coords = g->number_of_cells; + int nx = g->cartdims[0]; + int ny = g->cartdims[1]; + int nz = g->cartdims[2]; + int coords_size = 4; + int ** coords; + float ** corners; + float * mapaxes = NULL; + + corners = malloc( num_coords * sizeof * corners ); + coords = malloc( num_coords * sizeof * coords ); + + { + for (int c=0; c < num_coords; c++) { + corners[c] = malloc( 24 * sizeof * corners[0] ); + coords[c] = malloc( coords_size * sizeof * coords[0] ); + } + } + + { + for (int k=0; k < nz; k++) { + for (int j=0; j < ny; j++) { + for (int i=0; i < nx; i++) { + int global_index = i + j*nx + k*nx*ny; + + coords[global_index][0] = i; + coords[global_index][1] = j; + coords[global_index][2] = k; + coords[global_index][3] = 1; + + } + } + } + } + { + for (int c=0; c < num_coords; c++) { + free(corners[c]); + free(coords[c]); + } + } + free( corners ); + free( coords ); +} +#endif + + + */ + +// struct grdecl : opm/core/grid/cpgpreprocess/preprocess.h +// struct processes_grid : opm/core/grid/cpgpreprocess/preprocess.h + + + +int main(int /*argc*/ , char **argv) +{ + std::string filename( argv[1] ); + boost::scoped_ptr grid; + boost::scoped_ptr props; + Opm::EclipseGridParser eclParser(filename , false); + + //eclParser.saveEGRID_INIT("/tmp" , "OPM" ); + + grid.reset(new Opm::GridManager(eclParser)); + + props.reset(new Opm::IncompPropertiesFromDeck(eclParser , *grid->c_grid())); +} diff --git a/tests/test_velocityinterpolation.cpp b/tests/test_velocityinterpolation.cpp new file mode 100644 index 00000000..2e7f9aa1 --- /dev/null +++ b/tests/test_velocityinterpolation.cpp @@ -0,0 +1,482 @@ +/* + Copyright 2012 SINTEF ICT, Applied Mathematics. + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ + +#include + +#if HAVE_DYNAMIC_BOOST_TEST +#define BOOST_TEST_DYN_LINK +#endif +#define NVERBOSE // to suppress our messages when throwing + +#define BOOST_TEST_MODULE VelocityInterpolationTest +#include + +#include +#include +#include +#include + +using namespace Opm; + +namespace +{ + + // Compute flux corresponding to a constant velocity vector v. + void computeFlux(const UnstructuredGrid& grid, const std::vector& v, std::vector& flux) + { + const int dim = v.size(); + ASSERT(dim == grid.dimensions); + flux.resize(grid.number_of_faces); + for (int face = 0; face < grid.number_of_faces; ++face) { + flux[face] = std::inner_product(v.begin(), v.end(), grid.face_normals + face*dim, 0.0); + } + } + + // Compute a linear vector function v = v0 + x*v1. + void computeLinearVec(const std::vector& v0, + const std::vector& v1, + const std::vector& x, + std::vector& v) + { + ASSERT(v0.size() == v1.size() && v0.size() == x.size()); + const int dim = v0.size(); + v.resize(dim); + for (int dd = 0; dd < dim; ++dd) { + v[dd] = v0[dd] + x[dd]*v1[dd]; + } + } + + + // Compute flux corresponding to a velocity vector v = v0 + x*v1. + void computeFluxLinear(const UnstructuredGrid& grid, + const std::vector& v0, + const std::vector& v1, + std::vector& flux) + { + const int dim = v0.size(); + ASSERT(dim == grid.dimensions); + flux.resize(grid.number_of_faces); + std::vector x(dim); + std::vector v(dim); + for (int face = 0; face < grid.number_of_faces; ++face) { + const double* fc = grid.face_centroids + face*dim; + std::copy(fc, fc + dim, x.begin()); + computeLinearVec(v0, v1, x, v); + flux[face] = std::inner_product(v.begin(), v.end(), grid.face_normals + face*dim, 0.0); + } + } + + + double vectorDiff2(const std::vector& v1, const std::vector& v2) + { + ASSERT(v1.size() == v2.size()); + const int sz = v1.size(); + double vdiff = 0.0; + for (int i = 0; i < sz; ++i) { + vdiff += (v1[i] - v2[i])*(v1[i] - v2[i]); + } + return vdiff; + } + +} // anonymous namespace + + +template +void testConstantVelRepro2d() +{ + // Set up 2d 1-cell cartesian case. + GridManager g(1, 1); + const UnstructuredGrid& grid = *g.c_grid(); + std::vector v(2); + v[0] = 0.12345; + v[1] = -0.6789; + std::vector flux; + computeFlux(grid, v, flux); + VelInterp vic(grid); + vic.setupFluxes(&flux[0]); + + // Test a few points + std::vector x(2); + x[0] = 0.23456; + x[1] = 0.87654; + std::vector v_interp(2); + vic.interpolate(0, &x[0], &v_interp[0]); + BOOST_CHECK(vectorDiff2(v, v_interp) < 1e-12); + x[0] = 0.5; + x[1] = 0.5; + vic.interpolate(0, &x[0], &v_interp[0]); + BOOST_CHECK(vectorDiff2(v, v_interp) < 1e-12); + x[0] = 1.0; + x[1] = 0.5; + vic.interpolate(0, &x[0], &v_interp[0]); + BOOST_CHECK(vectorDiff2(v, v_interp) < 1e-12); + x[0] = 1.0; + x[1] = 1.0; + vic.interpolate(0, &x[0], &v_interp[0]); + BOOST_CHECK(vectorDiff2(v, v_interp) < 1e-12); +} + + +namespace +{ + + // Data for a pyramid. Node numbering goes + // lexicographic on bottom, then top. + // Face numbering goes xmin, xmax, ymin, ymax, bottom. + namespace Pyramid + { + static int face_nodes[] = { 0, 4, 2, 3, 4, 1, 0, 1, 4, 4, 3, 2, 0, 2, 3, 1, }; + static int face_nodepos[] = { 0, 3, 6, 9, 12, 16 }; + static int face_cells[] = { 0, -1, 0, -1, 0, -1, 0, -1, 0, -1 }; + static int cell_faces[] = { 0, 1, 2, 3, 4 }; + static int cell_facepos[] = { 0, 5 }; + static double node_coordinates[] = { 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0 }; + static double face_centroids[] = { 0, 1.0/3.0, 1.0/3.0, + 2.0/3.0, 1.0/3.0, 1.0/3.0, + 1.0/3.0, 0, 1.0/3.0, + 1.0/3.0, 2.0/3.0, 1.0/3.0, + 0.5, 0.5, 0 }; + static double face_areas[] = { 0.5, std::sqrt(2.0), 0.5, std::sqrt(2.0), 1.0 }; + static double face_normals[] = { -0.5000, 0, 0, + 0.5000, 0, 0.5000, + 0, -0.5000, 0, + 0, 0.5000, 0.5000, + 0, 0, -1.0000 }; + static double cell_centroids[] = { 0.375, 0.375, 0.25 }; + static double cell_volumes[] = { 1.0/3.0 }; + + } // namespace Pyramid + + UnstructuredGrid makePyramid() + { + // Make a 3d 1-cell grid, where the single cell is a pyramid. + UnstructuredGrid grid; + grid.dimensions = 3; + grid.number_of_cells = 1; + grid.number_of_faces = 5; + grid.number_of_nodes = 5; + grid.face_nodes = Pyramid::face_nodes; + grid.face_nodepos = Pyramid::face_nodepos; + grid.face_cells = Pyramid::face_cells; + grid.cell_faces = Pyramid::cell_faces; + grid.cell_facepos = Pyramid::cell_facepos; + grid.node_coordinates = Pyramid::node_coordinates; + grid.face_centroids = Pyramid::face_centroids; + grid.face_areas = Pyramid::face_areas; + grid.face_normals = Pyramid::face_normals; + grid.cell_centroids = Pyramid::cell_centroids; + grid.cell_volumes = Pyramid::cell_volumes; + return grid; + } + +} // anonymous namespace + + +template +void testConstantVelReproPyramid() +{ + // Set up a 3d 1-cell non-cartesian case (a pyramid). + UnstructuredGrid grid = makePyramid(); + std::vector v(3); + v[0] = 0.12345; + v[1] = -0.6789; + v[2] = 0.3456; + std::vector flux; + computeFlux(grid, v, flux); + VelInterp vic(grid); + vic.setupFluxes(&flux[0]); + + // Test a few points + std::vector x(3); + x[0] = 0.123; + x[1] = 0.0123; + x[2] = 0.213; + std::vector v_interp(3); + vic.interpolate(0, &x[0], &v_interp[0]); + BOOST_CHECK(vectorDiff2(v, v_interp) < 1e-12); + x[0] = 0.0; + x[1] = 0.0; + x[2] = 1.0; + vic.interpolate(0, &x[0], &v_interp[0]); + BOOST_CHECK(vectorDiff2(v, v_interp) < 1e-12); + x[0] = 0.5; + x[1] = 0.5; + x[2] = 0.0; + vic.interpolate(0, &x[0], &v_interp[0]); + BOOST_CHECK(vectorDiff2(v, v_interp) < 1e-12); + x[0] = 0.5; + x[1] = 0.5; + x[2] = 0.1; + vic.interpolate(0, &x[0], &v_interp[0]); + BOOST_CHECK(vectorDiff2(v, v_interp) < 1e-12); +} + + +namespace +{ + // Data for an irregular 2d polygon. + namespace Irreg2d + { + static int face_nodes[] = { 0, 1, 1, 2, 2, 3, 3, 4, 4, 0 }; + static int face_nodepos[] = { 0, 2, 4, 6, 8, 10 }; + static int face_cells[] = { 0, -1, 0, -1, 0, -1, 0, -1, 0, -1 }; + static int cell_faces[] = { 0, 1, 2, 3, 4 }; + static int cell_facepos[] = { 0, 5 }; + static double node_coordinates[] = { 0, 0, 3, 0, 3, 2, 1, 3, 0, 2 }; + static double face_centroids[] = { 1.5, 0, 3, 1, 2, 2.5, 0.5, 2.5, 0, 1 }; + static double face_areas[] = { 3, 2, std::sqrt(5.0), std::sqrt(2.0), 2 }; + static double face_normals[] = { 0, -3, 2, 0, 1, 2, -1, 1, -2, 0 }; + static double cell_centroids[] = { 22.0/15.0, 19.0/15.0 }; + static double cell_volumes[] = { 7.5 }; + } // namespace Irreg2d + + UnstructuredGrid makeIrreg2d() + { + // Make a 2d 1-cell grid, where the single cell is a polygon. + UnstructuredGrid grid; + grid.dimensions = 2; + grid.number_of_cells = 1; + grid.number_of_faces = 5; + grid.number_of_nodes = 5; + grid.face_nodes = Irreg2d::face_nodes; + grid.face_nodepos = Irreg2d::face_nodepos; + grid.face_cells = Irreg2d::face_cells; + grid.cell_faces = Irreg2d::cell_faces; + grid.cell_facepos = Irreg2d::cell_facepos; + grid.node_coordinates = Irreg2d::node_coordinates; + grid.face_centroids = Irreg2d::face_centroids; + grid.face_areas = Irreg2d::face_areas; + grid.face_normals = Irreg2d::face_normals; + grid.cell_centroids = Irreg2d::cell_centroids; + grid.cell_volumes = Irreg2d::cell_volumes; + return grid; + } + +} // anonymous namespace + + +template +void testConstantVelReproIrreg2d() +{ + // Set up a 2d 1-cell non-cartesian case (a pyramid). + UnstructuredGrid grid = makeIrreg2d(); + std::vector v(2); + v[0] = 0.12345; + v[1] = -0.6789; + std::vector flux; + computeFlux(grid, v, flux); + VelInterp vic(grid); + vic.setupFluxes(&flux[0]); + + // Test a few points + std::vector x(2); + x[0] = 1.2345; + x[1] = 2.0123; + std::vector v_interp(2); + vic.interpolate(0, &x[0], &v_interp[0]); + BOOST_CHECK(vectorDiff2(v, v_interp) < 1e-12); + x[0] = 0.0; + x[1] = 0.0; + vic.interpolate(0, &x[0], &v_interp[0]); + BOOST_CHECK(vectorDiff2(v, v_interp) < 1e-12); + x[0] = 1.0; + x[1] = 3.0; + vic.interpolate(0, &x[0], &v_interp[0]); + BOOST_CHECK(vectorDiff2(v, v_interp) < 1e-12); + x[0] = 3.0; + x[1] = 1.0; + vic.interpolate(0, &x[0], &v_interp[0]); + BOOST_CHECK(vectorDiff2(v, v_interp) < 1e-12); +} + + + +namespace +{ + // Data for an irregular 3d prism. + namespace IrregPrism + { + static int face_nodes[] = { 0, 4, 2, 1, 3, 5, 0, 1, 5, 4, 2, 4, 5, 3, 2, 3, 0, 1}; + static int face_nodepos[] = { 0, 3, 6, 10, 14, 18 }; + static int face_cells[] = { 0, -1, 0, -1, 0, -1, 0, -1, 0, -1 }; + static int cell_faces[] = { 0, 1, 2, 3, 4 }; + static int cell_facepos[] = { 0, 5 }; + static double node_coordinates[] = { 0, 0, 0, + 2, 0, 0, + 0, 1, 0, + 2, 1, 0, + 0, 0, 1, + 1, 0, 1 }; + static double face_centroids[] = { 0, 1.0/3.0, 1.0/3.0, + 5.0/3.0, 1.0/3.0, 1.0/3.0, + 7.0/9.0, 0, 4.0/9.0, + 7.0/9.0, 5.0/9.0, 4.0/9.0, + 1, 0.5, 0 }; + static double face_areas[] = { 0.500000000000000, + 0.707106781186548, + 1.500000000000000, + 2.121320343559642, + 2.000000000000000 }; + static double face_normals[] = { -0.500000000000000, 0, 0, + 0.500000000000000, 0.000000000000000, 0.500000000000000, + 0, -1.500000000000000, 0, + 0, 1.500000000000000, 1.500000000000000, + 0, 0, -2.000000000000000 }; + static double cell_centroids[] = { 0.85, 0.35, 0.3 }; + static double cell_volumes[] = { 5.0/6.0 }; + } // namespace IrregPrism + + UnstructuredGrid makeIrregPrism() + { + // Make a 3d 1-cell grid, where the single cell is a prism. + UnstructuredGrid grid; + grid.dimensions = 3; + grid.number_of_cells = 1; + grid.number_of_faces = 5; + grid.number_of_nodes = 6; + grid.face_nodes = IrregPrism::face_nodes; + grid.face_nodepos = IrregPrism::face_nodepos; + grid.face_cells = IrregPrism::face_cells; + grid.cell_faces = IrregPrism::cell_faces; + grid.cell_facepos = IrregPrism::cell_facepos; + grid.node_coordinates = IrregPrism::node_coordinates; + grid.face_centroids = IrregPrism::face_centroids; + grid.face_areas = IrregPrism::face_areas; + grid.face_normals = IrregPrism::face_normals; + grid.cell_centroids = IrregPrism::cell_centroids; + grid.cell_volumes = IrregPrism::cell_volumes; + return grid; + } + +} // anonymous namespace + +template +void testConstantVelReproIrregPrism() +{ + // Set up a 3d 1-cell non-cartesian case (a pyramid). + UnstructuredGrid grid = makeIrregPrism(); + std::vector v(3); + v[0] = 0.12345; + v[1] = -0.6789; + v[2] = 0.3456; + std::vector flux; + computeFlux(grid, v, flux); + VelInterp vic(grid); + vic.setupFluxes(&flux[0]); + + // Test a few points + std::vector x(3); + x[0] = 0.123; + x[1] = 0.0123; + x[2] = 0.213; + std::vector v_interp(3); + vic.interpolate(0, &x[0], &v_interp[0]); + BOOST_CHECK(vectorDiff2(v, v_interp) < 1e-12); + x[0] = 0.0; + x[1] = 0.0; + x[2] = 1.0; + vic.interpolate(0, &x[0], &v_interp[0]); + BOOST_CHECK(vectorDiff2(v, v_interp) < 1e-12); + x[0] = 1.0; + x[1] = 0.0; + x[2] = 1.0; + vic.interpolate(0, &x[0], &v_interp[0]); + BOOST_CHECK(vectorDiff2(v, v_interp) < 1e-12); + x[0] = 0.5; + x[1] = 0.5; + x[2] = 0.5; + vic.interpolate(0, &x[0], &v_interp[0]); + BOOST_CHECK(vectorDiff2(v, v_interp) < 1e-12); +} + +template +void testLinearVelReproIrregPrism() +{ + // Set up a 3d 1-cell non-cartesian case (a pyramid). + UnstructuredGrid grid = makeIrregPrism(); + std::vector v0(3); + // v0[0] = 0.12345; + // v0[1] = -0.6789; + // v0[2] = 0.423; + v0[0] = 0.0; + v0[1] = 0.0; + v0[2] = 0.0; + std::vector v1(3); + // v1[0] = -0.1; + // v1[1] = 0.454; + // v1[2] = 0.21; + v1[0] = 0.0; + v1[1] = 0.0; + v1[2] = 1.0; + std::vector flux; + computeFluxLinear(grid, v0, v1, flux); + VelInterp vic(grid); + vic.setupFluxes(&flux[0]); + + // Test a few points + std::vector v(3); + std::vector x(3); + x[0] = 0.123; + x[1] = 0.0123; + x[2] = 0.213; + computeLinearVec(v0, v1, x, v); + std::vector v_interp(3); + vic.interpolate(0, &x[0], &v_interp[0]); + BOOST_CHECK(vectorDiff2(v, v_interp) < 1e-12); + x[0] = 0.0; + x[1] = 0.0; + x[2] = 1.0; + computeLinearVec(v0, v1, x, v); + vic.interpolate(0, &x[0], &v_interp[0]); + BOOST_CHECK(vectorDiff2(v, v_interp) < 1e-12); + x[0] = 1.0; + x[1] = 0.0; + x[2] = 1.0; + computeLinearVec(v0, v1, x, v); + vic.interpolate(0, &x[0], &v_interp[0]); + BOOST_CHECK(vectorDiff2(v, v_interp) < 1e-12); + x[0] = 0.5; + x[1] = 0.5; + x[2] = 0.5; + computeLinearVec(v0, v1, x, v); + vic.interpolate(0, &x[0], &v_interp[0]); + BOOST_CHECK(vectorDiff2(v, v_interp) < 1e-12); +} + + +BOOST_AUTO_TEST_CASE(test_VelocityInterpolationConstant) +{ + testConstantVelRepro2d(); + testConstantVelReproPyramid(); + testConstantVelReproIrreg2d(); + testConstantVelReproIrregPrism(); +} + +BOOST_AUTO_TEST_CASE(test_VelocityInterpolationECVI) +{ + testConstantVelRepro2d(); + BOOST_CHECK_THROW(testConstantVelReproPyramid(), std::exception); + testConstantVelReproIrreg2d(); + testConstantVelReproIrregPrism(); + // Though the interpolation has linear precision, the corner velocity + // construction does not, so the below test cannot be expected to succeed. + // testLinearVelReproIrregPrism(); +} + + diff --git a/tests/test_wachspresscoord.cpp b/tests/test_wachspresscoord.cpp new file mode 100644 index 00000000..907c57e7 --- /dev/null +++ b/tests/test_wachspresscoord.cpp @@ -0,0 +1,363 @@ +/* + Copyright 2012 SINTEF ICT, Applied Mathematics. + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ + +#include + +#if HAVE_DYNAMIC_BOOST_TEST +#define BOOST_TEST_DYN_LINK +#endif +#define NVERBOSE // to suppress our messages when throwing + +#define BOOST_TEST_MODULE WachspressCoordTest +#include + +#include +#include +#include +#include + +using namespace Opm; + + +namespace +{ + class Interpolator + { + public: + explicit Interpolator(const UnstructuredGrid& grid) + : bcmethod_(grid), grid_(grid) + { + } + template + double interpolate(const Func& f, + const int cell, + const std::vector& x) const + { + const int ncor = bcmethod_.numCorners(cell); + bary_coord_.resize(ncor); + bcmethod_.cartToBary(cell, &x[0], &bary_coord_[0]); + double val = 0.0; + for (int cor = 0; cor < ncor; ++cor) { + const int vertex = bcmethod_.cornerInfo()[cell][cor].vertex; + const double vval = f(grid_.node_coordinates + grid_.dimensions*vertex); + val += vval*bary_coord_[cor]; + } + return val; + } + private: + WachspressCoord bcmethod_; + const UnstructuredGrid grid_; + mutable std::vector bary_coord_; + }; + +} // anonymous namespace + +struct LinearFunc +{ + double operator()(const double* x) const + { + return 1.0*x[0] + 2.0*x[1] + 3.0; + } +}; + +static void test2dCart() +{ + // Set up 2d 1-cell cartesian case. + GridManager g(1, 1); + const UnstructuredGrid& grid = *g.c_grid(); + Interpolator interp(grid); + LinearFunc f; + + // Test a few points + std::vector x(2); + x[0] = 0.23456; + x[1] = 0.87654; + double val = interp.interpolate(f, 0, x); + BOOST_CHECK(std::fabs(val - f(&x[0])) < 1e-12); + x[0] = 0.5; + x[1] = 0.5; + val = interp.interpolate(f, 0, x); + BOOST_CHECK(std::fabs(val - f(&x[0])) < 1e-12); + x[0] = 1.0; + x[1] = 0.5; + val = interp.interpolate(f, 0, x); + BOOST_CHECK(std::fabs(val - f(&x[0])) < 1e-12); + x[0] = 1.0; + x[1] = 1.0; + val = interp.interpolate(f, 0, x); + BOOST_CHECK(std::fabs(val - f(&x[0])) < 1e-12); +} + + +namespace +{ + + // Data for a pyramid. Node numbering goes + // lexicographic on bottom, then top. + // Face numbering goes xmin, xmax, ymin, ymax, bottom. + namespace Pyramid + { + static int face_nodes[] = { 0, 4, 2, 3, 4, 1, 0, 1, 4, 4, 3, 2, 0, 2, 3, 1, }; + static int face_nodepos[] = { 0, 3, 6, 9, 12, 16 }; + static int face_cells[] = { 0, -1, 0, -1, 0, -1, 0, -1, 0, -1 }; + static int cell_faces[] = { 0, 1, 2, 3, 4 }; + static int cell_facepos[] = { 0, 5 }; + static double node_coordinates[] = { 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0 }; + static double face_centroids[] = { 0, 1.0/3.0, 1.0/3.0, + 2.0/3.0, 1.0/3.0, 1.0/3.0, + 1.0/3.0, 0, 1.0/3.0, + 1.0/3.0, 2.0/3.0, 1.0/3.0, + 0.5, 0.5, 0 }; + static double face_areas[] = { 0.5, std::sqrt(2.0), 0.5, std::sqrt(2.0), 1.0 }; + static double face_normals[] = { -0.5000, 0, 0, + 0.5000, 0, 0.5000, + 0, -0.5000, 0, + 0, 0.5000, 0.5000, + 0, 0, -1.0000 }; + static double cell_centroids[] = { 0.375, 0.375, 0.25 }; + static double cell_volumes[] = { 1.0/3.0 }; + + } // namespace Pyramid + + UnstructuredGrid makePyramid() + { + // Make a 3d 1-cell grid, where the single cell is a pyramid. + UnstructuredGrid grid; + grid.dimensions = 3; + grid.number_of_cells = 1; + grid.number_of_faces = 5; + grid.number_of_nodes = 5; + grid.face_nodes = Pyramid::face_nodes; + grid.face_nodepos = Pyramid::face_nodepos; + grid.face_cells = Pyramid::face_cells; + grid.cell_faces = Pyramid::cell_faces; + grid.cell_facepos = Pyramid::cell_facepos; + grid.node_coordinates = Pyramid::node_coordinates; + grid.face_centroids = Pyramid::face_centroids; + grid.face_areas = Pyramid::face_areas; + grid.face_normals = Pyramid::face_normals; + grid.cell_centroids = Pyramid::cell_centroids; + grid.cell_volumes = Pyramid::cell_volumes; + return grid; + } + +} // anonymous namespace + + +static void testPyramid() +{ + // Set up a 3d 1-cell non-cartesian case (a pyramid). + UnstructuredGrid grid = makePyramid(); + Interpolator interp(grid); + LinearFunc f; + + // Test a few points + std::vector x(3); + x[0] = 0.123; + x[1] = 0.0123; + x[2] = 0.213; + double val = interp.interpolate(f, 0, x); + BOOST_CHECK(std::fabs(val - f(&x[0])) < 1e-12); + x[0] = 0.0; + x[1] = 0.0; + x[2] = 1.0; + val = interp.interpolate(f, 0, x); + BOOST_CHECK(std::fabs(val - f(&x[0])) < 1e-12); + x[0] = 0.5; + x[1] = 0.5; + x[2] = 0.0; + val = interp.interpolate(f, 0, x); + BOOST_CHECK(std::fabs(val - f(&x[0])) < 1e-12); + x[0] = 0.5; + x[1] = 0.5; + x[2] = 0.1; + val = interp.interpolate(f, 0, x); + BOOST_CHECK(std::fabs(val - f(&x[0])) < 1e-12); +} + + +namespace +{ + // Data for an irregular 2d polygon. + namespace Irreg2d + { + static int face_nodes[] = { 0, 1, 1, 2, 2, 3, 3, 4, 4, 0 }; + static int face_nodepos[] = { 0, 2, 4, 6, 8, 10 }; + static int face_cells[] = { 0, -1, 0, -1, 0, -1, 0, -1, 0, -1 }; + static int cell_faces[] = { 0, 1, 2, 3, 4 }; + static int cell_facepos[] = { 0, 5 }; + static double node_coordinates[] = { 0, 0, 3, 0, 3, 2, 1, 3, 0, 2 }; + static double face_centroids[] = { 1.5, 0, 3, 1, 2, 2.5, 0.5, 2.5, 0, 1 }; + static double face_areas[] = { 3, 2, std::sqrt(5.0), std::sqrt(2.0), 2 }; + static double face_normals[] = { 0, -3, 2, 0, 1, 2, -1, 1, -2, 0 }; + static double cell_centroids[] = { 22.0/15.0, 19.0/15.0 }; + static double cell_volumes[] = { 7.5 }; + } // namespace Irreg2d + + UnstructuredGrid makeIrreg2d() + { + // Make a 2d 1-cell grid, where the single cell is a polygon. + UnstructuredGrid grid; + grid.dimensions = 2; + grid.number_of_cells = 1; + grid.number_of_faces = 5; + grid.number_of_nodes = 5; + grid.face_nodes = Irreg2d::face_nodes; + grid.face_nodepos = Irreg2d::face_nodepos; + grid.face_cells = Irreg2d::face_cells; + grid.cell_faces = Irreg2d::cell_faces; + grid.cell_facepos = Irreg2d::cell_facepos; + grid.node_coordinates = Irreg2d::node_coordinates; + grid.face_centroids = Irreg2d::face_centroids; + grid.face_areas = Irreg2d::face_areas; + grid.face_normals = Irreg2d::face_normals; + grid.cell_centroids = Irreg2d::cell_centroids; + grid.cell_volumes = Irreg2d::cell_volumes; + return grid; + } + +} // anonymous namespace + + +static void testIrreg2d() +{ + // Set up a 2d 1-cell where the single cell is a polygon. + UnstructuredGrid grid = makeIrreg2d(); + Interpolator interp(grid); + LinearFunc f; + + // Test a few points + std::vector x(2); + x[0] = 1.2345; + x[1] = 2.0123; + double val = interp.interpolate(f, 0, x); + BOOST_CHECK(std::fabs(val - f(&x[0])) < 1e-12); + x[0] = 0.0; + x[1] = 0.0; + val = interp.interpolate(f, 0, x); + BOOST_CHECK(std::fabs(val - f(&x[0])) < 1e-12); + x[0] = 1.0; + x[1] = 3.0; + val = interp.interpolate(f, 0, x); + BOOST_CHECK(std::fabs(val - f(&x[0])) < 1e-12); + x[0] = 3.0; + x[1] = 1.0; + val = interp.interpolate(f, 0, x); + BOOST_CHECK(std::fabs(val - f(&x[0])) < 1e-12); +} + + +namespace +{ + // Data for an irregular 3d prism. + namespace IrregPrism + { + static int face_nodes[] = { 0, 4, 2, 1, 3, 5, 0, 1, 5, 4, 2, 4, 5, 3, 2, 3, 0, 1}; + static int face_nodepos[] = { 0, 3, 6, 10, 14, 18 }; + static int face_cells[] = { 0, -1, 0, -1, 0, -1, 0, -1, 0, -1 }; + static int cell_faces[] = { 0, 1, 2, 3, 4 }; + static int cell_facepos[] = { 0, 5 }; + static double node_coordinates[] = { 0, 0, 0, + 2, 0, 0, + 0, 1, 0, + 2, 1, 0, + 0, 0, 1, + 1, 0, 1 }; + static double face_centroids[] = { 0, 1.0/3.0, 1.0/3.0, + 5.0/3.0, 1.0/3.0, 1.0/3.0, + 7.0/9.0, 0, 4.0/9.0, + 7.0/9.0, 5.0/9.0, 4.0/9.0, + 1, 0.5, 0 }; + static double face_areas[] = { 0.500000000000000, + 0.707106781186548, + 1.500000000000000, + 2.121320343559642, + 2.000000000000000 }; + static double face_normals[] = { -0.500000000000000, 0, 0, + 0.500000000000000, 0.000000000000000, 0.500000000000000, + 0, -1.500000000000000, 0, + 0, 1.500000000000000, 1.500000000000000, + 0, 0, -2.000000000000000 }; + static double cell_centroids[] = { 0.85, 0.35, 0.3 }; + static double cell_volumes[] = { 5.0/6.0 }; + } // namespace IrregPrism + + UnstructuredGrid makeIrregPrism() + { + // Make a 3d 1-cell grid, where the single cell is a prism. + UnstructuredGrid grid; + grid.dimensions = 3; + grid.number_of_cells = 1; + grid.number_of_faces = 5; + grid.number_of_nodes = 6; + grid.face_nodes = IrregPrism::face_nodes; + grid.face_nodepos = IrregPrism::face_nodepos; + grid.face_cells = IrregPrism::face_cells; + grid.cell_faces = IrregPrism::cell_faces; + grid.cell_facepos = IrregPrism::cell_facepos; + grid.node_coordinates = IrregPrism::node_coordinates; + grid.face_centroids = IrregPrism::face_centroids; + grid.face_areas = IrregPrism::face_areas; + grid.face_normals = IrregPrism::face_normals; + grid.cell_centroids = IrregPrism::cell_centroids; + grid.cell_volumes = IrregPrism::cell_volumes; + return grid; + } + +} // anonymous namespace + +static void testIrregPrism() +{ + // Set up a 3d 1-cell non-cartesian case (a prism). + UnstructuredGrid grid = makeIrregPrism(); + Interpolator interp(grid); + LinearFunc f; + + // Test a few points + std::vector x(3); + x[0] = 0.123; + x[1] = 0.0123; + x[2] = 0.213; + double val = interp.interpolate(f, 0, x); + BOOST_CHECK(std::fabs(val - f(&x[0])) < 1e-12); + x[0] = 0.0; + x[1] = 0.0; + x[2] = 1.0; + val = interp.interpolate(f, 0, x); + BOOST_CHECK(std::fabs(val - f(&x[0])) < 1e-12); + x[0] = 1.0; + x[1] = 0.0; + x[2] = 1.0; + val = interp.interpolate(f, 0, x); + BOOST_CHECK(std::fabs(val - f(&x[0])) < 1e-12); + x[0] = 0.5; + x[1] = 0.5; + x[2] = 0.5; + val = interp.interpolate(f, 0, x); + BOOST_CHECK(std::fabs(val - f(&x[0])) < 1e-12); +} + + +BOOST_AUTO_TEST_CASE(test_WachspressCoord) +{ + test2dCart(); + BOOST_CHECK_THROW(testPyramid(), std::exception); + testIrreg2d(); + testIrregPrism(); +} diff --git a/tests/test_wells.cpp b/tests/test_wells.cpp new file mode 100644 index 00000000..4f8ad7bb --- /dev/null +++ b/tests/test_wells.cpp @@ -0,0 +1,205 @@ +/* + Copyright 2012 SINTEF ICT, Applied Mathematics. + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ + +#include + +#if HAVE_DYNAMIC_BOOST_TEST +#define BOOST_TEST_DYN_LINK +#endif + +#define NVERBOSE // Suppress own messages when throw()ing + +#define BOOST_TEST_MODULE WellsModuleTest +#include + +#include + +#include +#include +#include + +BOOST_AUTO_TEST_CASE(Construction) +{ + const int nphases = 2; + const int nwells = 2; + const int nperfs = 2; + + boost::shared_ptr W(create_wells(nphases, nwells, nperfs), + destroy_wells); + + if (W) { + int cells[] = { 0, 9 }; + double WI = 1.0; + const double ifrac[] = { 1.0, 0.0 }; + + const bool ok0 = add_well(INJECTOR, 0.0, 1, &ifrac[0], &cells[0], + &WI, "INJECTOR", W.get()); + + const double pfrac[] = { 0.0, 0.0 }; + const bool ok1 = add_well(PRODUCER, 0.0, 1, &pfrac[0], &cells[1], + &WI, "PRODUCER", W.get()); + + if (ok0 && ok1) { + BOOST_CHECK_EQUAL(W->number_of_phases, nphases); + BOOST_CHECK_EQUAL(W->number_of_wells , nwells ); + + BOOST_CHECK_EQUAL(W->well_connpos[0], 0); + BOOST_CHECK_EQUAL(W->well_connpos[1], 1); + BOOST_CHECK_EQUAL(W->well_connpos[W->number_of_wells], nperfs); + + BOOST_CHECK_EQUAL(W->well_cells[W->well_connpos[0]], cells[0]); + BOOST_CHECK_EQUAL(W->well_cells[W->well_connpos[1]], cells[1]); + + BOOST_CHECK_EQUAL(W->WI[W->well_connpos[0]], WI); + BOOST_CHECK_EQUAL(W->WI[W->well_connpos[1]], WI); + + using std::string; + BOOST_CHECK_EQUAL(string(W->name[0]), string("INJECTOR")); + BOOST_CHECK_EQUAL(string(W->name[1]), string("PRODUCER")); + } + } +} + + +BOOST_AUTO_TEST_CASE(Controls) +{ + const int nphases = 2; + const int nwells = 1; + const int nperfs = 2; + + boost::shared_ptr W(create_wells(nphases, nwells, nperfs), + destroy_wells); + + if (W) { + int cells[] = { 0 , 9 }; + double WI [] = { 1.0, 1.0 }; + const double ifrac[] = { 1.0, 0.0 }; + + const bool ok = add_well(INJECTOR, 0.0, nperfs, &ifrac[0], &cells[0], + &WI[0], "INJECTOR", W.get()); + + if (ok) { + const double distr[] = { 1.0, 0.0 }; + const bool ok1 = append_well_controls(BHP, 1, &distr[0], + 0, W.get()); + const bool ok2 = append_well_controls(SURFACE_RATE, 1, + &distr[0], 0, W.get()); + + if (ok1 && ok2) { + WellControls* ctrls = W->ctrls[0]; + + BOOST_CHECK_EQUAL(ctrls->num , 2); + BOOST_CHECK_EQUAL(ctrls->current, -1); + + set_current_control(0, 0, W.get()); + BOOST_CHECK_EQUAL(ctrls->current, 0); + + set_current_control(0, 1, W.get()); + BOOST_CHECK_EQUAL(ctrls->current, 1); + + BOOST_CHECK_EQUAL(ctrls->type[0], BHP); + BOOST_CHECK_EQUAL(ctrls->type[1], SURFACE_RATE); + + BOOST_CHECK_EQUAL(ctrls->target[0], 1.0); + BOOST_CHECK_EQUAL(ctrls->target[1], 1.0); + } + } + } +} + + +BOOST_AUTO_TEST_CASE(Copy) +{ + const int nphases = 2; + const int nwells = 2; + const int nperfs = 2; + + boost::shared_ptr W1(create_wells(nphases, nwells, nperfs), + destroy_wells); + boost::shared_ptr W2; + + if (W1) { + int cells[] = { 0, 9 }; + const double WI = 1.0; + const double ifrac[] = { 1.0, 0.0 }; + + const bool ok0 = add_well(INJECTOR, 0.0, 1, &ifrac[0], &cells[0], + &WI, "INJECTOR", W1.get()); + + const double pfrac[] = { 0.0, 0.0 }; + const bool ok1 = add_well(PRODUCER, 0.0, 1, &pfrac[0], &cells[1], + &WI, "PRODUCER", W1.get()); + + bool ok = ok0 && ok1; + for (int w = 0; ok && (w < W1->number_of_wells); ++w) { + const double distr[] = { 1.0, 0.0 }; + const bool okc1 = append_well_controls(BHP, 1, &distr[0], + w, W1.get()); + const bool okc2 = append_well_controls(SURFACE_RATE, 1, + &distr[0], w, + W1.get()); + + ok = okc1 && okc2; + } + + if (ok) { + W2.reset(clone_wells(W1.get()), destroy_wells); + } + } + + if (W2) { + BOOST_CHECK_EQUAL(W2->number_of_phases, W1->number_of_phases); + BOOST_CHECK_EQUAL(W2->number_of_wells , W1->number_of_wells ); + BOOST_CHECK_EQUAL(W2->well_connpos[0] , W1->well_connpos[0] ); + + for (int w = 0; w < W1->number_of_wells; ++w) { + using std::string; + BOOST_CHECK_EQUAL(string(W2->name[w]), string(W1->name[w])); + BOOST_CHECK_EQUAL( W2->type[w] , W1->type[w] ); + + BOOST_CHECK_EQUAL(W2->well_connpos[w + 1], + W1->well_connpos[w + 1]); + + for (int j = W1->well_connpos[w]; + j < W1->well_connpos[w + 1]; ++j) { + BOOST_CHECK_EQUAL(W2->well_cells[j], W1->well_cells[j]); + BOOST_CHECK_EQUAL(W2->WI [j], W1->WI [j]); + } + + BOOST_CHECK(W1->ctrls[w] != 0); + BOOST_CHECK(W2->ctrls[w] != 0); + + WellControls* c1 = W1->ctrls[w]; + WellControls* c2 = W2->ctrls[w]; + + BOOST_CHECK_EQUAL(c2->num , c1->num ); + BOOST_CHECK_EQUAL(c2->current, c1->current); + + for (int c = 0; c < c1->num; ++c) { + BOOST_CHECK_EQUAL(c2->type [c], c1->type [c]); + BOOST_CHECK_EQUAL(c2->target[c], c1->target[c]); + + for (int p = 0; p < W1->number_of_phases; ++p) { + BOOST_CHECK_EQUAL(c2->distr[c*W1->number_of_phases + p], + c1->distr[c*W1->number_of_phases + p]); + } + } + } + } +}