Merge pull request #3918 from hakonhagland/python_rates3

Dynamically update Opm::Schedule from Python (part II)
This commit is contained in:
Markus Blatt 2022-06-10 14:56:24 +02:00 committed by GitHub
commit 4121be612b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 121 additions and 21 deletions

View File

@ -25,11 +25,39 @@ option(BUILD_FLOW_VARIANTS "Build the variants for flow by default?" OFF)
option(BUILD_FLOW_POLY_GRID "Build flow blackoil with polyhedral grid" OFF)
option(OPM_ENABLE_PYTHON "Enable python bindings?" OFF)
option(OPM_ENABLE_PYTHON_TESTS "Enable tests for the python bindings?" ON)
option(OPM_INSTALL_PYTHON "Install python bindings?" ON)
option(ENABLE_FPGA "Enable FPGA kernels integration?" OFF)
option(USE_CHOW_PATEL_ILU "Use the iterative ILU by Chow and Patel?" OFF)
option(USE_CHOW_PATEL_ILU_GPU "Run iterative ILU decomposition on GPU? Requires USE_CHOW_PATEL_ILU" OFF)
option(USE_CHOW_PATEL_ILU_GPU_PARALLEL "Try to use more parallelism on the GPU during the iterative ILU decomposition? Requires USE_CHOW_PATEL_ILU_GPU" OFF)
# The following was copied from CMakeLists.txt in opm-common.
# TODO: factor out the common parts in opm-common and opm-simulator as a cmake module
if (OPM_ENABLE_PYTHON)
# We need to be compatible with older CMake versions
# that do not offer FindPython3
# e.g. Ubuntu LTS 18.04 uses cmake 3.10
if(${CMAKE_VERSION} VERSION_LESS "3.12.0")
find_package(PythonInterp REQUIRED)
if(PYTHON_VERSION_MAJOR LESS 3)
message(SEND_ERROR "OPM requires version 3 of Python but only version ${PYTHON_VERSION_STRING} was found")
endif()
set(Python3_EXECUTABLE ${PYTHON_EXECUTABLE})
set(Python3_LIBRARIES ${PYTHON_LIBRARIES})
else()
# Be backwards compatible.
if(PYTHON_EXECUTABLE AND NOT Python3_EXECUTABLE)
set(Python3_EXECUTABLE ${PYTHON_EXECUTABLE})
endif()
find_package(Python3 REQUIRED COMPONENTS Interpreter Development)
endif()
# Compatibility settings for PythonInterp and PythonLibs
# used e.g. in FindCwrap, pybind11
set(PYTHON_EXECUTABLE ${Python3_EXECUTABLE})
endif()
if(SIBLING_SEARCH AND NOT opm-common_DIR)
# guess the sibling dir
get_filename_component(_leaf_dir_name ${PROJECT_BINARY_DIR} NAME)

View File

@ -5,8 +5,10 @@ if(NOT pybind11_FOUND)
# SYSTEM is supported for embedded pybind source (>= 2.3.0)
set(PYBIND11_SYSTEM "SYSTEM")
elseif(pybind11_VERSION VERSION_GREATER_EQUAL "2.3.0")
# SYSTEM is only support for pybind >= 2.3.0 (Unfortunately querying the
# version does not work. AT least we know the embedded one.
set(PYBIND11_SYSTEM "SYSTEM")
if(CMAKE_VERSION VERSION_LESS 3.12)
# SYSTEM is only support for pybind >= 2.3.0 and cmake <= 3.12.0 (Unfortunately querying the
# version does not work. AT least we know the embedded one.
set(PYBIND11_SYSTEM "SYSTEM")
endif()
endif()
add_subdirectory( simulators )

View File

@ -39,17 +39,6 @@ if (PYTHON_SITE_PACKAGES_PATH MATCHES ".*/dist-packages/?" AND
else()
set(PYTHON_PACKAGE_PATH "site-packages")
endif()
set(PYTHON_INSTALL_PREFIX "lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/${PYTHON_PACKAGE_PATH}" CACHE STRING "Subdirectory to install Python modules in")
install(TARGETS simulators DESTINATION ${DEST_PREFIX}${CMAKE_INSTALL_PREFIX}/${PYTHON_INSTALL_PREFIX}/opm)
file( COPY ${PROJECT_SOURCE_DIR}/python/test
DESTINATION ${PROJECT_BINARY_DIR}/python)
file( COPY ${PROJECT_SOURCE_DIR}/python/test_data
DESTINATION ${PROJECT_BINARY_DIR}/python)
file( MAKE_DIRECTORY ${PYTHON_OPM_SIMULATORS_PACKAGE_PATH} )
file( COPY ${PROJECT_SOURCE_DIR}/python/simulators/__init__.py
DESTINATION ${PYTHON_OPM_SIMULATORS_PACKAGE_PATH})
if(OPM_ENABLE_PYTHON_TESTS)
if(Python3_EXECUTABLE AND NOT PYTHON_EXECUTABLE)
@ -71,3 +60,32 @@ if(OPM_ENABLE_PYTHON_TESTS)
-E env PYTHONPATH=${PYTHON_PATH} ${PYTHON_EXECUTABLE}
-m unittest test/test_schedule.py)
endif()
set(_opm_common_python_script_dir "${opm-common_DIR}/python")
if (NOT EXISTS "${_opm_common_python_script_dir}/install.py" )
# if the install.py script does not exist, assume that we are building against an installed opm-common
set(_opm_common_python_script_dir "${opm-common_PYTHON_COMMON_DIR}")
endif()
# NOTE: instead of using file( COPY ...) which copies the files at configure time (not at build time)
# we should add copying of the files at build time such that running "make" or "make all" will
# update the files if they have been modified. We use the install.py script in opm-common, see also
# CMakeLists.txt in opm-common
add_custom_target(copy_python ALL
COMMAND ${PYTHON_EXECUTABLE} "${_opm_common_python_script_dir}/install.py"
${PROJECT_SOURCE_DIR}/python ${PROJECT_BINARY_DIR} 0)
# Since the installation of Python code is nonstandard it is protected by an
# extra cmake switch, OPM_INSTALL_PYTHON. If you prefer you can still invoke
# setup.py install manually - optionally with the generated script
# setup-install.sh - and completely bypass cmake in the installation phase.
if (OPM_INSTALL_PYTHON)
include(PyInstallPrefix) # from opm-common
install(TARGETS simulators DESTINATION ${DEST_PREFIX}${CMAKE_INSTALL_PREFIX}/${PYTHON_INSTALL_PREFIX}/opm)
install(
CODE "execute_process(
COMMAND ${PYTHON_EXECUTABLE}
${_opm_common_python_script_dir}/install.py
${PROJECT_BINARY_DIR}/python/opm2
${DEST_PREFIX}${CMAKE_INSTALL_PREFIX}/${PYTHON_INSTALL_PREFIX} 1)")
endif()

View File

@ -3,6 +3,7 @@ import unittest
from contextlib import contextmanager
import datetime as dt
from pathlib import Path
import re
from opm.simulators import BlackOilSimulator
from opm.io.parser import Parser
from opm.io.ecl_state import EclipseState
@ -36,6 +37,7 @@ class TestBasic(unittest.TestCase):
self.schedule = Schedule( self.deck, state )
summary_config = SummaryConfig(self.deck, state, self.schedule)
self.unit_system = self.deck.active_unit_system()
self.assertTrue('PROD' in self.schedule)
self.assertTrue('INJ' in self.schedule)
self.assertEqual(dt.datetime(2015, 1, 1), self.schedule.start)
@ -55,13 +57,32 @@ class TestBasic(unittest.TestCase):
#schedule.shut_well("PROD", 3)
#prod = schedule.get_well("PROD", 3)
#self.assertEqual(prod.status(), "SHUT")
self.subtest_modify_schedule_dynamically(well_name, report_step)
self.subtest_modify_prod_weltarg_dynamically(well_name, report_step)
self.sim.step()
report_step = self.sim.current_step()
well_name = "INJ"
self.subtest_modify_inj_weltarg_dynamically(well_name, report_step)
self.sim.advance(report_step=last_step)
self.sim.step_cleanup()
def subtest_modify_inj_weltarg_dynamically(self, well_name, report_step):
prop = self.schedule.get_injection_properties(well_name, report_step)
self.assertEqual(prop['surf_inj_rate'], 100000.0) # Mscf/day
self.assertEqual(prop['resv_inj_rate'], 0.0) # rb/day
self.assertEqual(prop['bhp_target'], 9014.0) # psi
self.assertEqual(prop['thp_target'], 0.0)
new_grat_target = prop['surf_inj_rate'] - 100 # stb/day
self.update_inj_grat_target_wconinje(well_name, new_grat_target)
self.sim.step()
prop2 = self.schedule.get_injection_properties(well_name, report_step+1)
self.assertEqual(prop2['surf_inj_rate'], new_grat_target)
new_grat_target += 200
self.update_inj_grat_target_weltarg(well_name, new_grat_target)
self.sim.step()
prop3 = self.schedule.get_injection_properties(well_name, report_step+2)
self.assertEqual(prop3['surf_inj_rate'], new_grat_target)
def subtest_modify_schedule_dynamically(self, well_name, report_step):
def subtest_modify_prod_weltarg_dynamically(self, well_name, report_step):
prop = self.schedule.get_production_properties(well_name, report_step)
self.assertEqual(prop['alq_value'], 0.0)
self.assertEqual(prop['bhp_target'], 1000.0)
@ -72,13 +93,43 @@ class TestBasic(unittest.TestCase):
self.assertEqual(prop['thp_target'], 0.0)
self.assertEqual(prop['water_rate'], 0.0)
new_oil_target = prop['oil_rate'] + 10000 # stb/day
#self.update_oil_target_wconprod(well_name, new_oil_target)
self.update_oil_target_weltarg(well_name, new_oil_target)
self.update_prod_orat_target_wconprod(well_name, new_oil_target)
self.sim.step()
prop2 = self.schedule.get_production_properties(well_name, report_step+1)
self.assertEqual(prop2['oil_rate'], 30000.0)
self.assertEqual(prop2['oil_rate'], new_oil_target)
new_oil_target += 1000
self.update_prod_orat_target_weltarg(well_name, new_oil_target)
self.sim.step()
prop3 = self.schedule.get_production_properties(well_name, report_step+2)
self.assertEqual(prop3['oil_rate'], new_oil_target)
def update_oil_target_weltarg(self, well_name, oil_target):
# This is an alternative to using WELTARG
def update_inj_grat_target_wconinje(self, well_name, new_surf_flow_rate):
data = self.deck["WCONINJE"]
# assumes data looks like this:
# WCONINJE
# 'INJ' 'GAS' 'OPEN' 'RATE' 100000 1* 9014 /
# /
# The initial rate can also be obtained from data[0][4].get_uda(0).get_double()
data = re.sub(pattern='100000', repl=str(new_surf_flow_rate), string=str(data), count=1)
report_step = self.sim.current_step()
self.schedule.insert_keywords(
data, step=report_step, unit_system=self.unit_system)
# This is an alternative to using WCONINJE to modify injection properties
def update_inj_grat_target_weltarg(self, well_name, net_surf_flow_rate):
data = """
WELTARG
'{}' GRAT {} /
/
""".format(well_name, net_surf_flow_rate)
report_step = self.sim.current_step()
self.schedule.insert_keywords(
data, step=report_step, unit_system=self.unit_system)
# This is an alternative to using WCONPROD to modify production properties
def update_prod_orat_target_weltarg(self, well_name, oil_target):
data = """
WELTARG
'{}' ORAT {} /
@ -88,7 +139,8 @@ WELTARG
self.schedule.insert_keywords(
data, step=report_step, unit_system=self.unit_system)
def update_oil_target_wconprod(self, well_name, oil_target):
# This is an alternative to using WELTARG to modify production properties
def update_prod_orat_target_wconprod(self, well_name, oil_target):
well_status = "OPEN"
control_mode = "ORAT"
bhp_limit = 1000 # psia