From 91ac3a2a32a3b96e517f08e6f626cc37c0aea3d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20H=C3=A6gland?= Date: Mon, 2 May 2022 18:43:04 +0200 Subject: [PATCH] Dynamically update schedule from Python Adds some methods that enables Opm::Schedule to be dynamically modified from Python. A test case in test_schedule.py illustrates the use case. --- opm/simulators/flow/FlowMainEbos.hpp | 4 + .../flow/python/PyBlackOilSimulator.hpp | 5 +- python/simulators/CMakeLists.txt | 6 +- python/simulators/PyBlackOilSimulator.cpp | 34 +++++++- python/simulators/__init__.py | 12 +++ python/test/test_basic.py | 2 +- python/test/test_schedule.py | 80 +++++++++++++++---- 7 files changed, 124 insertions(+), 19 deletions(-) create mode 100644 python/simulators/__init__.py diff --git a/opm/simulators/flow/FlowMainEbos.hpp b/opm/simulators/flow/FlowMainEbos.hpp index 91841d4b7..ad78c5db4 100644 --- a/opm/simulators/flow/FlowMainEbos.hpp +++ b/opm/simulators/flow/FlowMainEbos.hpp @@ -387,6 +387,10 @@ namespace Opm return ebosSimulator_.get(); } + SimulatorTimer* getSimTimer() { + return simtimer_.get(); + } + private: // called by execute() or executeInitStep() int execute_(int (FlowMainEbos::* runOrInitFunc)(), bool cleanup) diff --git a/opm/simulators/flow/python/PyBlackOilSimulator.hpp b/opm/simulators/flow/python/PyBlackOilSimulator.hpp index 5e2a06c27..6f0d1806a 100644 --- a/opm/simulators/flow/python/PyBlackOilSimulator.hpp +++ b/opm/simulators/flow/python/PyBlackOilSimulator.hpp @@ -44,11 +44,14 @@ public: std::shared_ptr state, std::shared_ptr schedule, std::shared_ptr summary_config); + bool checkSimulationFinished(); py::array_t getPorosity(); int run(); void setPorosity( py::array_t array); int step(); + void advance(int report_step); + int currentStep(); int stepInit(); int stepCleanup(); const Opm::FlowMainEbos& getFlowMainEbos() const; @@ -57,7 +60,7 @@ private: const std::string deckFilename_; bool hasRunInit_ = false; bool hasRunCleanup_ = false; - + bool debug_ = false; // This *must* be declared before other pointers // to simulator objects. This in order to deinitialize // MPI at the correct time (ie after the other objects). diff --git a/python/simulators/CMakeLists.txt b/python/simulators/CMakeLists.txt index be275ddb4..be05e49c4 100644 --- a/python/simulators/CMakeLists.txt +++ b/python/simulators/CMakeLists.txt @@ -14,7 +14,8 @@ pybind11_add_module(simulators ${PYBIND11_SYSTEM} PyBlackOilSimulator.cpp Pybind11Exporter.cpp) -set_target_properties( simulators PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/python/opm2 ) +set(PYTHON_OPM_SIMULATORS_PACKAGE_PATH ${PROJECT_BINARY_DIR}/python/opm2/simulators) +set_target_properties( simulators PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PYTHON_OPM_SIMULATORS_PACKAGE_PATH} ) target_sources(simulators PRIVATE @@ -46,6 +47,9 @@ 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) diff --git a/python/simulators/PyBlackOilSimulator.cpp b/python/simulators/PyBlackOilSimulator.cpp index e7f5db63c..c70eae996 100644 --- a/python/simulators/PyBlackOilSimulator.cpp +++ b/python/simulators/PyBlackOilSimulator.cpp @@ -52,6 +52,11 @@ PyBlackOilSimulator::PyBlackOilSimulator( { } +bool PyBlackOilSimulator::checkSimulationFinished() +{ + return this->mainEbos_->getSimTimer()->done(); +} + const Opm::FlowMainEbos& PyBlackOilSimulator::getFlowMainEbos() const { @@ -85,6 +90,13 @@ void PyBlackOilSimulator::setPorosity( py::array_tsetPorosity(poro, size_); } +void PyBlackOilSimulator::advance(int report_step) +{ + while (currentStep() < report_step) { + step(); + } +} + int PyBlackOilSimulator::step() { if (!hasRunInit_) { @@ -93,9 +105,27 @@ int PyBlackOilSimulator::step() if (hasRunCleanup_) { throw std::logic_error("step() called after step_cleanup()"); } - return mainEbos_->executeStep(); + if(checkSimulationFinished()) { + throw std::logic_error("step() called, but simulation is done"); + } + //if (this->debug_) + // this->mainEbos_->getSimTimer()->report(std::cout); + auto result = mainEbos_->executeStep(); + return result; } +// This returns the report step number that will be executed next time step() +// is called. +int PyBlackOilSimulator::currentStep() +{ + return this->mainEbos_->getSimTimer()->currentStepNum(); + // NOTE: this->ebosSimulator_->episodeIndex() would also return the current + // report step number, but this number is always delayed by 1 step relative + // to this->mainEbos_->getSimTimer()->currentStepNum() + // See details in runStep() in file SimulatorFullyImplicitBlackoilEbos.hpp +} + + int PyBlackOilSimulator::stepCleanup() { hasRunCleanup_ = true; @@ -153,7 +183,9 @@ void export_PyBlackOilSimulator(py::module& m) py::return_value_policy::copy) .def("run", &PyBlackOilSimulator::run) .def("set_porosity", &PyBlackOilSimulator::setPorosity) + .def("current_step", &PyBlackOilSimulator::currentStep) .def("step", &PyBlackOilSimulator::step) + .def("advance", &PyBlackOilSimulator::advance, py::arg("report_step")) .def("step_init", &PyBlackOilSimulator::stepInit) .def("step_cleanup", &PyBlackOilSimulator::stepCleanup); } diff --git a/python/simulators/__init__.py b/python/simulators/__init__.py new file mode 100644 index 000000000..a6ca336e6 --- /dev/null +++ b/python/simulators/__init__.py @@ -0,0 +1,12 @@ +# Instead of having the pybind11 extension module, e.g. +# simulators.cpython-310-x86_64-linux-gnu.so located +# directly in the opm2 directory, we create a package (sub +# directory) with the same name and place it there. +# In this way we can do (if needed in the future) +# +# from opm.simulators import BlackOilSimulator, FoamSimulator, PurePythonUtils, ... +# +# where FoamSimulator and PurePythonUtils does not currently exists, +# but could be possible future extensions.. +# +from .simulators import BlackOilSimulator diff --git a/python/test/test_basic.py b/python/test/test_basic.py index d769f0456..42a772bc6 100755 --- a/python/test/test_basic.py +++ b/python/test/test_basic.py @@ -2,7 +2,7 @@ import os import unittest from contextlib import contextmanager from pathlib import Path -from opm2.simulators import BlackOilSimulator +from opm.simulators import BlackOilSimulator @contextmanager def pushd(path): diff --git a/python/test/test_schedule.py b/python/test/test_schedule.py index 99aa1c262..f0232364a 100755 --- a/python/test/test_schedule.py +++ b/python/test/test_schedule.py @@ -3,7 +3,7 @@ import unittest from contextlib import contextmanager import datetime as dt from pathlib import Path -from opm2.simulators import BlackOilSimulator +from opm.simulators import BlackOilSimulator from opm.io.parser import Parser from opm.io.ecl_state import EclipseState from opm.io.schedule import Schedule @@ -31,22 +31,72 @@ class TestBasic(unittest.TestCase): def test_all(self): with pushd(self.data_dir): - deck = Parser().parse('SPE1CASE1.DATA') - state = EclipseState(deck) - schedule = Schedule( deck, state ) - summary_config = SummaryConfig(deck, state, schedule) - self.assertTrue('PROD' in schedule) - self.assertTrue('INJ' in schedule) - self.assertEqual(dt.datetime(2015, 1, 1), schedule.start) - self.assertEqual(dt.datetime(2016, 1, 1), schedule.end) - sim = BlackOilSimulator( deck, state, schedule, summary_config ) - sim.step_init() - sim.step() - prod = schedule.get_well("PROD", 2) + self.deck = Parser().parse('SPE1CASE1.DATA') + state = EclipseState(self.deck) + 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) + self.assertEqual(dt.datetime(2016, 1, 1), self.schedule.end) + self.sim = BlackOilSimulator( + self.deck, state, self.schedule, summary_config ) + tsteps = self.schedule.timesteps + self.assertEqual(dt.datetime(2015, 1, 1), tsteps[0]) + last_step = len(tsteps) - 1 + self.assertEqual(dt.datetime(2016, 1, 1), tsteps[last_step]) + self.sim.step_init() + report_step = 4 + self.sim.advance(report_step=report_step) + well_name = "PROD" + prod = self.schedule.get_well(well_name, 2) self.assertEqual(prod.status(), "OPEN") #schedule.shut_well("PROD", 3) #prod = schedule.get_well("PROD", 3) #self.assertEqual(prod.status(), "SHUT") - sim.step() - sim.step() + self.subtest_modify_schedule_dynamically(well_name, report_step) + self.sim.step() + self.sim.advance(report_step=last_step) + self.sim.step_cleanup() + + def subtest_modify_schedule_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) + self.assertEqual(prop['gas_rate'], 0.0) + self.assertEqual(prop['liquid_rate'], 0.0) + self.assertEqual(prop['oil_rate'], 20000.0) + self.assertEqual(prop['resv_rate'], 0.0) + 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.sim.step() + prop2 = self.schedule.get_production_properties(well_name, report_step+1) + self.assertEqual(prop2['oil_rate'], 30000.0) + + def update_oil_target_weltarg(self, well_name, oil_target): + data = """ +WELTARG + '{}' ORAT {} / +/ + """.format(well_name, oil_target) + report_step = self.sim.current_step() + self.schedule.insert_keywords( + data, step=report_step, unit_system=self.unit_system) + + def update_oil_target_wconprod(self, well_name, oil_target): + well_status = "OPEN" + control_mode = "ORAT" + bhp_limit = 1000 # psia + data = """ +WCONPROD + '{}' '{}' '{}' {} 4* {} / +/ + """.format(well_name, well_status, control_mode, oil_target, bhp_limit) + report_step = self.sim.current_step() + self.schedule.insert_keywords( + data, step=report_step, unit_system=self.unit_system)