Skip to content

Commit

Permalink
Add Directive for OPM Common and OPM Embedded
Browse files Browse the repository at this point in the history
A rebuild of the documentation is also triggered when the file docstrings_common.json is updated in opm-common
  • Loading branch information
lisajulia committed Jul 1, 2024
1 parent a0d540c commit abc3ba6
Show file tree
Hide file tree
Showing 12 changed files with 165 additions and 64 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/python_sphinx_docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ on:
paths:
- 'python/**'
- '.github/workflows/python_sphinx_docs.yml'
repository_dispatch:
types: [docstrings_common_updated]
permissions:
contents: write
jobs:
Expand All @@ -21,6 +23,9 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch all history for all tags and branches
- name: Get docstrings_common.json from opm-common
run: |
curl -L -o python/docstrings_common.json https://raw.githubusercontent.com/${{ github.repository_owner }}/opm-common/master/python/docstrings_common.json
- name: Set up Python
uses: actions/setup-python@v5
with:
Expand All @@ -46,7 +51,6 @@ jobs:
run: |
cp -r python/sphinx_docs/docs/_build/* python/gh-pages/
- name: Deploy documentation
if: ${{ github.event_name == 'push' }}
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: gh-pages
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ build/
# Python sphinx build
python/sphinx_docs/docs/_build/

# docstrings_common.json - this is copied over from opm-common and not tracked here
python/docstrings_common.json

# Python cache directories
**/__pycache__/

45 changes: 25 additions & 20 deletions python/docstrings.json → python/docstrings_simulators.json
Original file line number Diff line number Diff line change
@@ -1,78 +1,83 @@
{
"PyBlackOilSimulator":{
"type": "class",
"signature": "opm.simulators.BlackOilSimulator",
"doc": "The SummaryState class - this is where the current summary results of the simulator are stored.\nThe SummaryState class has methods to get hold of well, group and general variables."
},
"PyBlackOilSimulator_filename_constructor": {
"signature": "__init__(deck_filename: str) -> None",
"signature": "opm.simulators.BlackOilSimulator.__init__(deck_filename: str) -> None",
"doc": "Constructor using a deck file name.\n\n:param deck_filename: The file name of the deck to be used for the simulation.\n:type deck_filename: str"
},
"PyBlackOilSimulator_objects_constructor": {
"signature": "__init__(deck: Deck, state: EclipseState, schedule: Schedule, summary_config: SummaryConfig) -> None",
"signature": "opm.simulators.BlackOilSimulator.__init__(deck: Deck, state: EclipseState, schedule: Schedule, summary_config: SummaryConfig) -> None",
"doc": "Constructor using Deck, EclipseState, Schedule, and SummaryConfig objects.\n\n:param deck: Deck object.\n:type deck: Deck\n:param state: EclipseState object.\n:type state: EclipseState\n:param schedule: Schedule object.\n:type schedule: Schedule\n:param summary_config: SummaryConfig object.\n:type summary_config: SummaryConfig"
},
"advance": {
"signature": "advance(report_step: int) -> None",
"signature": "opm.simulators.BlackOilSimulator.advance(report_step: int) -> None",
"doc": "Advance the simulation to a specific report step.\n\n:param report_step: Target report step to advance to.\n:type report_step: int"
},
"checkSimulationFinished": {
"signature": "check_simulation_finished() -> bool",
"signature": "opm.simulators.BlackOilSimulator.check_simulation_finished() -> bool",
"doc": "Check if the simulation has finished.\n\n:return: True if the simulation is finished, False otherwise."
},
"currentStep": {
"signature": "current_step() -> int",
"signature": "opm.simulators.BlackOilSimulator.current_step() -> int",
"doc": "Get the current simulation step.\n\n:return: The current step number."
},
"getCellVolumes": {
"signature": "get_cell_volumes() -> NDArray[float]",
"signature": "opm.simulators.BlackOilSimulator.get_cell_volumes() -> NDArray[float]",
"doc": "Retrieve the cell volumes of the simulation grid.\n\n:return: An array of cell volumes.\n:type return: NDArray[float]"
},
"getDT": {
"signature": "get_dt() -> float",
"signature": "opm.simulators.BlackOilSimulator.get_dt() -> float",
"doc": "Get the timestep size of the last completed step.\n\n:return: Timestep size in days.\n:type return: float"
},
"getFluidStateVariable": {
"signature": "get_fluid_state_variable(name: str) -> NDArray[float]",
"signature": "opm.simulators.BlackOilSimulator.get_fluid_state_variable(name: str) -> NDArray[float]",
"doc": "Retrieve a fluid state variable for the simulation grid.\n\n:param name: The name of the variable. Valid names are 'pw' (pressure water), 'pg' (pressure gas), 'po' (pressure oil), 'rho_w' (density water), 'rho_g' (density gas), 'rho_o' (density oil)'Rs' (soultion gas-oil ratio), 'Rv' (volatile gas-oil ratio), 'Sw' (water saturation), 'Sg' (gas saturation), 'So' (oil saturation), and 'T' (temperature).\n:type name: str\n\n:return: An array of fluid state variables.\n:type return: NDArray[float]"
},
"getPorosity": {
"signature": "get_porosity() -> numpy.ndarray",
"signature": "opm.simulators.BlackOilSimulator.get_porosity() -> NDArray[float]",
"doc": "Retrieve the porosity values of the simulation grid.\n\n:return: An array of porosity values.\n:type return: numpy.ndarray"
},
"getPrimaryVarMeaning": {
"signature": "get_primary_var_meaning(variable: str) -> NDArray[int]",
"signature": "opm.simulators.BlackOilSimulator.get_primary_var_meaning(variable: str) -> NDArray[int]",
"doc": "Retrieve the primary variable meaning of the simulation grid.\n\n:param variable: The name of the variable. Valid names are 'pressure', 'water', 'gas', and 'brine'.\n:type variable: str\n\n:return: An array of primary variable meanings. See ``get_primary_variable_meaning_map()`` for more information.\n:type return: NDArray[int]"
},
"getPrimaryVarMeaningMap": {
"signature": "get_primary_var_meaning_map(variable: str) -> dict[str, int]",
"signature": "opm.simulators.BlackOilSimulator.get_primary_var_meaning_map(variable: str) -> dict[str, int]",
"doc": "Retrieve the primary variable meaning map for each primary variable.\n\n:param variable: The name of the variable. Valid names are 'pressure', 'water', 'gas', and 'brine'.\n:type variable: str\n\n:return: A dictionary of primary variable meanings. The keys are the primary variable meanings and the values are the corresponding integer codes. The integer codes are used to represent the primary variable meanings in the simulation grid. For variable name 'pressure', the valid keys are: 'Po', 'Pg', and 'Pw', for variable name 'water', the valid keys are: 'Sw', 'Rvw', 'Rsw', and 'Disabled', for variable name 'gas', the valid keys are: 'Sg', 'Rs', 'Rv', and 'Disabled', for variable name 'brine', the valid keys are: 'Cs', 'Sp', and 'Disabled'.\n:type return: dict[str, int]"
},
"getPrimaryVariable": {
"signature": "get_primary_variable(variable: str) -> NDArray[float]",
"signature": "opm.simulators.BlackOilSimulator.get_primary_variable(variable: str) -> NDArray[float]",
"doc": "Retrieve the primary variable's values for the simulation grid.\n\n:param variable: The name of the variable. Valid names are 'pressure', 'water', 'gas', and 'brine'.\n:type variable: str\n\n:return: An array of primary variable values. See ``get_primary_variable_meaning()`` for more information.\n:type return: NDArray[float]"
},
"run": {
"signature": "run() -> int",
"signature": "opm.simulators.BlackOilSimulator.run() -> int",
"doc": "Runs the simulation to completion with the provided deck file or previously set deck.\n\n:return: EXIT_SUCCESS if the simulation completes successfully."
},
"setPorosity": {
"signature": "set_porosity(array: NDArray[float]) -> None",
"signature": "opm.simulators.BlackOilSimulator.set_porosity(array: NDArray[float]) -> None",
"doc": "Set the porosity values for the simulation grid.\n\n:param array: An array of porosity values to be set.\n:type array: NDArray[float]"
},
"setPrimaryVariable": {
"signature": "set_primary_variable(variable: str, value: NDArray[float]) -> None",
"signature": "opm.simulators.BlackOilSimulator.set_primary_variable(variable: str, value: NDArray[float]) -> None",
"doc": "Set the primary variable's values for the simulation grid.\n\n:param variable: The name of the variable. Valid names are 'pressure', 'water', 'gas', and 'brine'.\n:type variable: str\n:param value: An array of primary variable values to be set. See ``get_primary_variable()`` for more information.\n:type value: NDArray[float]"
},
"setupMpi": {
"signature": "mpi_init(init: bool, finalize: bool) -> None",
"signature": "opm.simulators.BlackOilSimulator.mpi_init(init: bool, finalize: bool) -> None",
"doc": "Setup MPI for parallel simulation. This method should be called before any other method.\n:param init: Whether to call ``MPI_Init()`` or not.\n:param finalize:Whether to call ``MPI_Finalize()```when the simulator object goes out of scope.\n\n:return: None"
},
"step": {
"signature": "step() -> int",
"signature": "opm.simulators.BlackOilSimulator.step() -> int",
"doc": "Execute the next simulation report step.\n\n:return: Result of the simulation step."
},
"stepCleanup": {
"signature": "step_cleanup() -> int",
"signature": "opm.simulators.BlackOilSimulator.step_cleanup() -> int",
"doc": "Perform cleanup after the last simulation step.\n\n:return: EXIT_SUCCESS if cleanup is successful."
},
"stepInit": {
"signature": "step_init() -> int",
"signature": "opm.simulators.BlackOilSimulator.step_init() -> int",
"doc": "Initialize the simulation before taking the first report step. This method should be called before the first call to ``step()``\n\n:return: EXIT_SUCCESS if the initialization is successful."
}
}
}
13 changes: 12 additions & 1 deletion python/simulators/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
set(PYTHON_OPM_SIMULATORS_PACKAGE_PATH ${PROJECT_BINARY_DIR}/python/opm/simulators)

# Set the path to the input docstrings.json file and the output .hpp file
set(PYTHON_DOCSTRINGS_FILE "${PROJECT_SOURCE_DIR}/python/docstrings.json")
set(PYTHON_DOCSTRINGS_FILE "${PROJECT_SOURCE_DIR}/python/docstrings_simulators.json")
set(PYTHON_DOCSTRINGS_GENERATED_HPP "${PROJECT_BINARY_DIR}/python/PyBlackOilSimulatorDoc.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 @@ -20,6 +20,17 @@ add_custom_command(
DEPENDS ${PYTHON_DOCSTRINGS_FILE}
COMMENT "Generating PyBlackOilSimulatorDoc.hpp from JSON file"
)

# This file from opm-common is needed to build the documentation using Sphinx
# For local builds of the documentation, it is copied to opm-simulators from opm-common
# For the online documentation, this file is copied using a github Action
# This file is not tracked by git here, it is in the .gitignore
find_file(DOCSTRINGS_JSON docstrings_common.json
PATHS ${opm-common_DIR} ${opm-common_PYTHON_COMMON_DIR}
PATH_SUFFIXES python NO_DEFAULT_PATH REQUIRED)

configure_file(${DOCSTRINGS_JSON} ${PROJECT_SOURCE_DIR}/python/docstrings_common.json COPYONLY)

# 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 Down
4 changes: 4 additions & 0 deletions python/sphinx_docs/docs/common.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
OPM Common Python Documentation
===============================

.. opm_common_docstrings::
5 changes: 3 additions & 2 deletions python/sphinx_docs/docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Configuration file for the Sphinx documentation builder.

project = "opm.simulators"
project = "OPM Python Documentation"
copyright = "2024 Equinor ASA"
author = "Håkon Hægland"

Expand Down Expand Up @@ -36,7 +36,8 @@ def extract_opm_simulators_release():
# Our sphinx extension that will use the docstrings.json file to generate documentation
extensions = ["opm_simulators_docs.sphinx_ext_docstrings"]
# Path to docstrings.json
opm_simulators_docstrings_path = os.path.abspath('../../docstrings.json')
opm_simulators_docstrings_path = os.path.abspath('../../docstrings_simulators.json')
opm_common_docstrings_path = os.path.abspath('../../docstrings_common.json')

templates_path = ["_templates"]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
Expand Down
54 changes: 54 additions & 0 deletions python/sphinx_docs/docs/embedded.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
OPM Embedded Python Documentation
=================================

The PYACTION keyword is a flow specific keyword which allows for executing embedded Python
code in the SCHEDULE section. The embedded Python code will then be executed at the end of each successful timestep.

The PYACTION keyword is inspired
by the ACTIONX keyword, but instead of a .DATA formatted condition you
are allowed to implement the condition with a general Python script. The
ACTIONX keywords are very clearly separated in a condition part and an
action part in the form of a list of keywords which are effectively injected in
the SCHEDULE section when the condition evaluates to true.
This is not so for PYACTION where there is one Python script in which both
conditions can be evaluated and changes applied.

In order to enable the PYACTION keyword:

1. OPM flow must be compiled with the cmake switches -DOPM ENABLE EMBEDDED PYTHON=ON and -DOPM ENABLE PYTHON=ON, the default is to build with these switches set to OFF.

2. The keyword PYACTION must be added to the SCHEDULE section:

.. code-block:: python
<PYACTION\_NAME> <SINGLE/UNLIMITED> /
<pythonscript> / -- path to the python script, relative to the location of the DATA-file
3. You need to provide the Python script.


To interact with the simulator in the embedded Python code, you can access four variables from the simulator:

.. code-block:: python
# Python module opm_embedded
import opm_embedded
# The current EclipseState
ecl_state = opm_embedded.current_ecl_state
# The current Schedule
schedule = opm_embedded.current_schedule
# The current SummaryState
summary_state = opm_embedded.current_summary_state
# The current report step
report_step = opm_embedded.current_report_step
- current_ecl_state: An instance of the `EclipseState <common.html#opm.io.ecl_state.EclipseState>`_ class - this is a representation of all static properties in the model, ranging from porosity to relperm tables. The content of the ecl state is immutable - you are not allowed to change the static properties at runtime.

- current_schedule: An instance of the `Schedule <common.html#opm.io.schedule.Schedule>`_ class - this is a representation of all the content from the SCHEDULE section, notably all well and group information and the timestepping.

- current_report_step: This is an integer for the report step we are currently working on. Observe that the PYACTION is called for every simulator timestep, i.e. it will typically be called multiple times with the same value for the report step argument.

- current_summary_state: An instance of the `SummaryState <common.html#opm.io.sim.SummaryState>`_ class, this is where the current summary results of the simulator are stored. The SummaryState class has methods to get hold of well, group and general variables.

See also: PYACTION in the `reference manual <https://opm-project.org/?page_id=955>`_ for more information.
10 changes: 6 additions & 4 deletions python/sphinx_docs/docs/index.rst
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
Welcome to the Python documentation for opm-simulators!
=======================================================
Welcome to the Python documentation for OPM Flow!
=================================================

.. toctree::
:maxdepth: 1
:caption: Contents:

introduction
python

common
simulators
embedded

Indices and tables
==================

Expand Down
5 changes: 4 additions & 1 deletion python/sphinx_docs/docs/introduction.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
Introduction
============

Documentation for the ``opm.simulators`` Python module.
Documentation for the OPM Python interfaces.

#TODO: expand on the introduction, add information about installation and requirements for example. Some example code would also be nice.

4 changes: 0 additions & 4 deletions python/sphinx_docs/docs/python.rst

This file was deleted.

4 changes: 4 additions & 0 deletions python/sphinx_docs/docs/simulators.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
OPM Simulators Python Documentation
===================================

.. opm_simulators_docstrings::
76 changes: 45 additions & 31 deletions python/sphinx_docs/src/opm_simulators_docs/sphinx_ext_docstrings.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,57 @@
from sphinx.util.docutils import SphinxDirective
from docutils import nodes

class PybindDocstringsDirective(SphinxDirective):
def read_doc_strings(directive, docstrings_path):
print(docstrings_path)
with open(docstrings_path, 'r') as file:
docstrings = json.load(file)
result = []
for name, item in docstrings.items():
# Create a ViewList instance for the function signature and docstring
rst = ViewList()

# Check if signature exists and prepend it to the docstring
signature = item.get('signature', '')
item_type = item.get('type', 'method')
signature_line = f".. py:{item_type}:: {signature}" if signature else f".. py:{item_type}:: {name}()"
rst.append(signature_line, source="")
rst.append("", source="")

# Add the docstring text if it exists
docstring = item.get('doc', '')
if docstring:
for line in docstring.split('\n'):
rst.append(f" {line}", source="")

# Create a node that will be populated by nested_parse_with_titles
node = nodes.section()
node.document = directive.state.document
# Parse the rst content
nested_parse_with_titles(directive.state, rst, node)

result.extend(node.children)
return result

class SimulatorsDirective(SphinxDirective):
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = False
option_spec = {}

def run(self):
return read_doc_strings(self, self.state.document.settings.env.app.config.opm_simulators_docstrings_path)

class CommonDirective(SphinxDirective):
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = False
option_spec = {}

def run(self):
env = self.state.document.settings.env
docstrings_path = env.app.config.opm_simulators_docstrings_path
with open(docstrings_path, 'r') as file:
docstrings = json.load(file)
result = []
for func_name, doc_info in docstrings.items():
signature = doc_info.get('signature', '')
docstring = doc_info.get('doc', '')

# Create a ViewList instance for the function signature and docstring
rst = ViewList()

# Check if signature exists and prepend it to the docstring
signature_line = f".. py:function:: {signature}" if signature else f".. py:function:: {func_name}()"
rst.append(signature_line, source="")
rst.append("", source="")
# Add the docstring text if it exists
if docstring:
for line in docstring.split('\n'):
rst.append(f" {line}", source="")

# Create a node that will be populated by nested_parse_with_titles
node = nodes.section()
node.document = self.state.document
# Parse the rst content
nested_parse_with_titles(self.state, rst, node)

result.extend(node.children)
return result
return read_doc_strings(self, self.state.document.settings.env.app.config.opm_common_docstrings_path)

def setup(app):
app.add_config_value('opm_simulators_docstrings_path', None, 'env')
app.add_directive("opm_simulators_docstrings", PybindDocstringsDirective)
app.add_config_value('opm_common_docstrings_path', None, 'env')
app.add_directive("opm_simulators_docstrings", SimulatorsDirective)
app.add_directive("opm_common_docstrings", CommonDirective)

0 comments on commit abc3ba6

Please sign in to comment.