Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists_files.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,7 @@ list (APPEND PUBLIC_HEADER_FILES
opm/simulators/flow/SubDomain.hpp
opm/simulators/flow/TTagFlowProblemTPFA.hpp
opm/simulators/flow/TTagFlowProblemGasWater.hpp
opm/simulators/flow/TTagFlowProblemOnePhase.hpp
opm/simulators/flow/TracerContainer.hpp
opm/simulators/flow/TracerModel.hpp
opm/simulators/flow/Transmissibility.hpp
Expand Down
38 changes: 38 additions & 0 deletions opm/simulators/flow/TTagFlowProblemOnePhase.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
Copyright 2025 Equinor ASA.

This file is part of the Open Porous Media project (OPM).

OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef TTAG_FLOW_PROBLEM_ONE_PHASE_HPP
#define TTAG_FLOW_PROBLEM_ONE_PHASE_HPP

#include <tuple>

namespace Opm::Properties::TTag {
struct FlowProblem;

/// Specialized type tag for one phase (water) simulations.
///
/// All properties are otherwise the same as for the regular
/// FlowProblem.
struct FlowOnePhaseProblem {
using InheritsFrom = std::tuple<FlowProblem>;
};

} // namespace Opm::Properties::TTag

#endif // TTAG_FLOW_ONE_PHASE_HPP
53 changes: 53 additions & 0 deletions opm/simulators/flow/python/PyOnePhaseSimulator.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
Copyright 2025 Equinor ASA.

This file is part of the Open Porous Media project (OPM).

OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef OPM_PY_ONE_PHASE_SIMULATOR_HEADER_INCLUDED
#define OPM_PY_ONE_PHASE_SIMULATOR_HEADER_INCLUDED

#include <opm/simulators/flow/python/PyBaseSimulator.hpp>
#include <opm/simulators/flow/TTagFlowProblemOnePhase.hpp>

#include <memory>

namespace Opm::Pybind {

class PyOnePhaseSimulator : public PyBaseSimulator<Opm::Properties::TTag::FlowOnePhaseProblem>
{
private:
using BaseType = PyBaseSimulator<Opm::Properties::TTag::FlowOnePhaseProblem>;
using TypeTag = Opm::Properties::TTag::FlowOnePhaseProblem;

public:
PyOnePhaseSimulator(const std::string& deck_filename,
const std::vector<std::string>& args)
: BaseType(deck_filename, args)
{}

PyOnePhaseSimulator(std::shared_ptr<Opm::Deck> deck,
std::shared_ptr<Opm::EclipseState> state,
std::shared_ptr<Opm::Schedule> schedule,
std::shared_ptr<Opm::SummaryConfig> summary_config,
const std::vector<std::string>& args)
: BaseType(deck, state, schedule, summary_config, args)
{}
};

} // namespace Opm::Pybind

#endif // OPM_PY_ONEPHASE_SIMULATOR_HEADER_INCLUDED
1 change: 1 addition & 0 deletions opm/simulators/flow/python/Pybind11Exporter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace py = pybind11;
namespace Opm::Pybind {
void export_PyBlackOilSimulator(py::module& m);
void export_PyGasWaterSimulator(py::module& m);
void export_PyOnePhaseSimulator(py::module& m);
}

#endif //OPM_PYBIND11_EXPORTER_HEADER_INCLUDED
5 changes: 5 additions & 0 deletions python/docstrings_simulators.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
"class": "PyGasWaterSimulator",
"name": "GasWaterSimulator",
"doc": "The GasWaterSimulator class to run simulations using a given Deck."
},
"OnePhase": {
"class": "PyOnePhaseSimulator",
"name": "OnePhaseSimulator",
"doc": "The OnePhaseSimulator class to run simulations using a given Deck."
}
},
"constructors": {
Expand Down
1 change: 1 addition & 0 deletions python/opm/simulators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
#
from .BlackOil import BlackOilSimulator
from .GasWater import GasWaterSimulator
from .OnePhase import OnePhaseSimulator
3 changes: 2 additions & 1 deletion python/setup.py.in
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ setup(
package_data={
'opm': [
'$<TARGET_FILE_NAME:BlackOil>',
'$<TARGET_FILE_NAME:GasWater>'
'$<TARGET_FILE_NAME:GasWater>',
'$<TARGET_FILE_NAME:OnePhase>',
]
},
include_package_data=True,
Expand Down
19 changes: 17 additions & 2 deletions python/simulators/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ set(PYTHON_OPM_SIMULATORS_PACKAGE_PATH ${PROJECT_BINARY_DIR}/python/opm/simulato
set(PYTHON_DOCSTRINGS_FILE "${PROJECT_SOURCE_DIR}/python/docstrings_simulators.json")
set(PYTHON_DOCSTRINGS_GENERATED_HPP "${PROJECT_BINARY_DIR}/python/PyBlackOilSimulatorDoc.hpp")
set(PYTHON_GW_DOCSTRINGS_GENERATED_HPP "${PROJECT_BINARY_DIR}/python/PyGasWaterSimulatorDoc.hpp")
set(PYTHON_OP_DOCSTRINGS_GENERATED_HPP "${PROJECT_BINARY_DIR}/python/PyOnePhaseSimulatorDoc.hpp")
# Note: If the new find_package(Python3) is used in the top level CMakeLists.txt, the variable
# ${PYTHON_EXECUTABLE} is set there to ${Python3_EXECUTABLE}
#
Expand All @@ -29,6 +30,14 @@ add_custom_command(
DEPENDS ${PYTHON_DOCSTRINGS_FILE}
COMMENT "Generating PyGasWaterSimulatorDoc.hpp from JSON file"
)
add_custom_command(
OUTPUT ${PYTHON_OP_DOCSTRINGS_GENERATED_HPP}
COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${CMAKE_SOURCE_DIR}
${PYTHON_EXECUTABLE} ${PYTHON_GENERATE_DOCSTRINGS_PY}
${PYTHON_DOCSTRINGS_FILE} ${PYTHON_OP_DOCSTRINGS_GENERATED_HPP} PYONEPHASESIMULATORDOC_HPP "Opm::Pybind::DocStrings" "OnePhase"
DEPENDS ${PYTHON_DOCSTRINGS_FILE}
COMMENT "Generating PyOnePhaseSimulatorDoc.hpp from JSON file"
)
# NOTE: The variable ${PYBIND11_SYSTEM} is set in python/CMakeLists.txt
# to the value "SYSTEM" or unset, depending on the current version of Pybind11.
# The value is then forwarded to target_include_directories(), see
Expand All @@ -48,9 +57,15 @@ pybind11_add_module(GasWater ${PYBIND11_SYSTEM}
${PYTHON_GW_DOCSTRINGS_GENERATED_HPP} # Include the generated .hpp as a source file
)

pybind11_add_module(OnePhase ${PYBIND11_SYSTEM}
$<TARGET_OBJECTS:moduleVersion>
PyOnePhaseSimulator.cpp
${PYTHON_OP_DOCSTRINGS_GENERATED_HPP} # Include the generated .hpp as a source file
)

# Create a convenience target to build all Python simulator modules
add_custom_target(python_simulator_modules
DEPENDS BlackOil GasWater
DEPENDS BlackOil GasWater OnePhase
COMMENT "Building all Python simulator modules (BlackOil, GasWater, etc.)"
)

Expand All @@ -71,7 +86,7 @@ add_custom_target(copy_python ALL
${PROJECT_SOURCE_DIR}/python/test_data ${PROJECT_BINARY_DIR}/python 0
)

foreach(target_name IN ITEMS BlackOil GasWater)
foreach(target_name IN ITEMS BlackOil GasWater OnePhase)
set_target_properties(
${target_name} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PYTHON_OPM_SIMULATORS_PACKAGE_PATH}
)
Expand Down
135 changes: 135 additions & 0 deletions python/simulators/PyOnePhaseSimulator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
Copyright 2025 Equinor ASA.

This file is part of the Open Porous Media project (OPM).

OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <opm/models/blackoil/blackoilonephaseindices.hh>
#include <opm/simulators/flow/python/PyOnePhaseSimulator.hpp>
// NOTE: This file will be generated at compile time and placed in the build directory
// See python/generate_docstring_hpp.py, and python/simulators/CMakeLists.txt for details
#include <PyOnePhaseSimulatorDoc.hpp>
// NOTE: EXIT_SUCCESS, EXIT_FAILURE is defined in cstdlib
#include <cstdlib>
#include <stdexcept>
#include <string>

namespace Opm::Properties {

//! The indices required by the model
template<class TypeTag>
struct Indices<TypeTag, TTag::FlowOnePhaseProblem>
{
private:
// it is unfortunately not possible to simply use 'TypeTag' here because this leads
// to cyclic definitions of some properties. if this happens the compiler error
// messages unfortunately are *really* confusing and not really helpful.
using BaseTypeTag = TTag::FlowProblem;
using FluidSystem = GetPropType<BaseTypeTag, Properties::FluidSystem>;

public:
using type = BlackOilOnePhaseIndices<getPropValue<TypeTag, Properties::EnableSolvent>(),
getPropValue<TypeTag, Properties::EnableExtbo>(),
getPropValue<TypeTag, Properties::EnablePolymer>(),
getPropValue<TypeTag, Properties::EnableEnergy>(),
getPropValue<TypeTag, Properties::EnableFoam>(),
getPropValue<TypeTag, Properties::EnableBrine>(),
/*PVOffset=*/0,
/*enabledCompIdx=*/FluidSystem::waterCompIdx,
getPropValue<TypeTag, Properties::EnableBioeffects>()>;
}; // struct Indices

} // namespace Opm::Properties

// NOTE: We need the below explicit instantiations or else the symbols
// will not be available in the shared library and we will get
// undefined symbol errors when trying to import the module in Python.
namespace Opm::Pybind {

template class PyBaseSimulator<Opm::Properties::TTag::FlowOnePhaseProblem>;

} // namespace Opm::Pybind

// Define main function
namespace Opm {

template class PyMain<Opm::Properties::TTag::FlowOnePhaseProblem>;
template std::unique_ptr<FlowMain<Opm::Properties::TTag::FlowOnePhaseProblem>>
flowMainInit<Opm::Properties::TTag::FlowOnePhaseProblem>(
int argc, char** argv, bool outputCout, bool outputFiles);

} // namespace Opm

namespace py = pybind11;

namespace Opm::Pybind {

// Exported functions
void export_PyOnePhaseSimulator(py::module& m)
{
using namespace Opm::Pybind::DocStrings;
using TypeTag = Opm::Properties::TTag::FlowOnePhaseProblem;

py::class_<PyBaseSimulator<TypeTag>>(
m,
"_BaseSimulatorOP",
py::module_local()
);
py::class_<PyOnePhaseSimulator, PyBaseSimulator<TypeTag> >(m, "OnePhaseSimulator")
.def(py::init<const std::string&,
const std::vector<std::string>&>(),
PyOnePhaseSimulator_filename_constructor_docstring,
py::arg("filename"), py::arg("args") = std::vector<std::string>{})
.def(py::init<
std::shared_ptr<Opm::Deck>,
std::shared_ptr<Opm::EclipseState>,
std::shared_ptr<Opm::Schedule>,
std::shared_ptr<Opm::SummaryConfig>,
const std::vector<std::string>&>(),
PyOnePhaseSimulator_objects_constructor_docstring,
py::arg("Deck"), py::arg("EclipseState"), py::arg("Schedule"), py::arg("SummaryConfig"),
py::arg("args") = std::vector<std::string>{})
.def("advance", &PyBaseSimulator<TypeTag>::advance, advance_docstring, py::arg("report_step"))
.def("check_simulation_finished", &PyBaseSimulator<TypeTag>::checkSimulationFinished,
checkSimulationFinished_docstring)
.def("current_step", &PyBaseSimulator<TypeTag>::currentStep, currentStep_docstring)
.def("get_cell_volumes", &PyBaseSimulator<TypeTag>::getCellVolumes, getCellVolumes_docstring)
.def("get_dt", &PyBaseSimulator<TypeTag>::getDT, getDT_docstring)
.def("get_fluidstate_variable", &PyBaseSimulator<TypeTag>::getFluidStateVariable,
py::return_value_policy::copy, getFluidStateVariable_docstring, py::arg("name"))
.def("get_porosity", &PyBaseSimulator<TypeTag>::getPorosity, getPorosity_docstring)
.def("get_primary_variable_meaning", &PyBaseSimulator<TypeTag>::getPrimaryVarMeaning,
py::return_value_policy::copy, getPrimaryVarMeaning_docstring, py::arg("variable"))
.def("get_primary_variable_meaning_map", &PyBaseSimulator<TypeTag>::getPrimaryVarMeaningMap,
py::return_value_policy::copy, getPrimaryVarMeaningMap_docstring, py::arg("variable"))
.def("get_primary_variable", &PyBaseSimulator<TypeTag>::getPrimaryVariable,
py::return_value_policy::copy, getPrimaryVariable_docstring, py::arg("variable"))
.def("run", &PyBaseSimulator<TypeTag>::run, run_docstring)
.def("set_porosity", &PyBaseSimulator<TypeTag>::setPorosity, setPorosity_docstring, py::arg("array"))
.def("set_primary_variable", &PyBaseSimulator<TypeTag>::setPrimaryVariable,
py::arg("variable"), setPrimaryVariable_docstring, py::arg("value"))
.def("setup_mpi", &PyBaseSimulator<TypeTag>::setupMpi, setupMpi_docstring, py::arg("init"), py::arg("finalize"))
.def("step", &PyBaseSimulator<TypeTag>::step, step_docstring)
.def("step_cleanup", &PyBaseSimulator<TypeTag>::stepCleanup, stepCleanup_docstring)
.def("step_init", &PyBaseSimulator<TypeTag>::stepInit, stepInit_docstring);
}

PYBIND11_MODULE(OnePhase, m)
{
export_PyOnePhaseSimulator(m);
}

} // namespace Opm::Pybind
16 changes: 16 additions & 0 deletions python/test/pytest_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,19 @@ def create_gas_water_simulator(*args, **kwargs):

return GasWaterSimulator(*args, **kwargs)

def create_onephase_simulator(*args, **kwargs):
"""Create OnePhaseSimulator with test-safe default arguments.

Automatically disables async ECL output to prevent race conditions
with pushd context manager in tests.
"""
from opm.simulators import OnePhaseSimulator

flag_to_add = ENABLE_ASYNC_ECL_OUTPUT_FLAG
# Add our flag to args
kwargs['args'] = kwargs.get('args', [])
if flag_to_add not in kwargs['args']:
kwargs['args'].append(flag_to_add)

return OnePhaseSimulator(*args, **kwargs)

23 changes: 22 additions & 1 deletion python/test/test_basic.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import os
import unittest
from pathlib import Path
from .pytest_common import pushd, create_black_oil_simulator, create_gas_water_simulator
from .pytest_common import pushd, create_black_oil_simulator, create_gas_water_simulator, create_onephase_simulator

class TestBasic(unittest.TestCase):
@classmethod
def setUpClass(cls):
test_dir = Path(os.path.dirname(__file__))
cls.data_dir_bo = test_dir.parent.joinpath("test_data/SPE1CASE1a")
cls.data_dir_op = test_dir.parent.joinpath("test_data/SPE1CASE1")
cls.data_dir_gw = test_dir.parent.joinpath("test_data/SPE1CASE2")

# IMPORTANT: Since all the python unittests run in the same process we must be
Expand Down Expand Up @@ -43,6 +44,26 @@ def test_01_blackoil(self):
sim.step()
poro2 = sim.get_porosity()
self.assertAlmostEqual(poro2[0], 0.285, places=7, msg='value of porosity 2')

def test_02_onephase(self):
with pushd(self.data_dir_op):
sim = create_onephase_simulator(args=['--linear-solver=ilu0'], filename="SPE1CASE1_WATER.DATA")
sim.setup_mpi(init=False, finalize=False)
sim.step_init()
sim.step()
dt = sim.get_dt() # 31 days = 31 * 24 * 60 * 60 = 2678400 seconds
self.assertAlmostEqual(dt, 2678400., places=7, msg='value of timestep')
vol = sim.get_cell_volumes()
self.assertEqual(len(vol), 300, 'length of volume vector')
self.assertAlmostEqual(vol[0], 566336.93, places=2, msg='value of volume')
poro = sim.get_porosity()
self.assertEqual(len(poro), 300, 'length of porosity vector')
self.assertAlmostEqual(poro[0], 0.3, places=7, msg='value of porosity')
poro = poro *.95
sim.set_porosity(poro)
sim.step()
poro2 = sim.get_porosity()
self.assertAlmostEqual(poro2[0], 0.285, places=7, msg='value of porosity 2')

# IMPORTANT: This test must be run last since it calls MPI_Finalize()
def test_99_gaswater(self):
Expand Down
Loading