Understand Compiling#

Open in Colab

Author: Zakariya Abugrin | Date: May 2025

Introduction#

In this tutorial, we see how a model can be compiled to calculate different types of solutions such as: analytical, numerical, and neurical which is one of the main research topics in reservoirflow.

Hint

Compiling solutions is the most interesting idea introduced in reservoirflow which allows to solve the same model using different solutions so we can compare them with each other and/or combine them together.

../../_images/tutorial_understand_compiling_solutions_diagram_dark.svg../../_images/tutorial_understand_compiling_solutions_diagram_light.svg

\

The following points are important to understand the compiling concept in reservoirflow:

  1. Every model from models module has a compile() method which can be used to select a solution.

  2. Every model come with an empty dictionary of solutions and compilers accessed by model.solutions and model.compilers.

  3. Every model must be compiled before the solution can be computed.

  4. After compiling a model, a new attribute called model.solution is added.

  5. Every model can be solved using different solutions where model.set_solution() can be used to switch to previous solutions.

  6. Solutions can be computed using model.solution.solve() for a single time step and model.solution.run() for multiple time steps.

  7. Solution functions are mapped directly to the corresponding model and can be accessed directly using model.solve() and model.run().

  8. Specifically, neurical solutions (i.e. based on neural networks) have model.solution.fit() to train based on physics-loss and model.solution.predict().

  9. Specifically, neurical solution functions are mapped directly to the corresponding model and can be accessed directly using model.train() and model.predict().

Now, let’s see how we can apply this concept.

Import reservoirflow#

We start with importing reservoirflow as rf. The abbreviation rf refers to reservoirflow where all modules under this library can be accessed. rf is also used throughout the API documentation. We recommend our users to stick with this convention.

1import reservoirflow as rf
2
3print(rf.__version__)
0.1.0b3
1# Check what is available in solutions:
2dir(rf.solutions)
['Compiler',
 'Solution',
 '__all__',
 '__builtins__',
 '__cached__',
 '__doc__',
 '__file__',
 '__loader__',
 '__name__',
 '__package__',
 '__path__',
 '__spec__',
 'compiler',
 'solution']

Warning

Modules under solutions are not loaded by default in reservoirflow. As a result, rf.solutions.numerical.FDM will fail. As can be seen below, only classes Compiler and Solution are loaded by default.

1# rf.solutions.numerical.fdm.FDM # this will not work
2# rf.solutions.numerical.FDM # this will not work
3
4# But, we can load these modules or classes explicitly:
5# from reservoirflow.solutions import numerical
6# from reservoirflow.solutions.numerical import FDM

Note

By default, modules numerical, analytical, and neurical are not available under solutions.

Tip

Specific solutions modules such as rf.solutions.numerical will only be available once a solution for that module (e.g. rf.solutions.numerical.FDM) was used to compile a model. Of course, this does not prevent loading these modules explicitly (e.g. from reservoirflow.solutions import numerical, or from reservoirflow.solutions.numerical import FDM).

Build a model#

A reservoir simulation model requires two objects: Grid and Fluid. The function below create_model() starts by creating these objects which are used to initiate a Model object using BlackOil class.

 1def create_model():
 2    # Grid:
 3    grid = rf.grids.RegularCartesian(
 4        nx=4,
 5        ny=1,
 6        nz=1,
 7        dx=300,
 8        dy=350,
 9        dz=40,
10        phi=0.27,
11        kx=270,
12        dtype="double",
13    )
14
15    # Fluid:
16    fluid = rf.fluids.SinglePhase(mu=0.5, B=1, dtype="double")
17
18    # Model:
19    model = rf.models.BlackOil(
20        grid,
21        fluid,
22        pi=4000,
23        dt=1,
24        start_date="10.10.2018",
25        verbose=False,
26        dtype="double",
27    )
28
29    # Production well:
30    model.set_well(cell_id=4, q=-600, s=1.5, r=3.5)
31
32    # Boundaries:
33    model.set_boundaries({0: ("pressure", 4000), 5: ("rate", 0)})
34
35    return model
36
37
38model = create_model()

Simulation Run#

To perform the simulation run, method model.run() can be used. The code below performs a simulation run for nsteps=10 (i.e. number of steps) and using isolver=cgs:

1model.solutions
{}
1model.solve()  # same as: model.run()
The model is not compiled. Use model.compile() to add solve() and run() methods.
1help(model.solve)  # same as: help(model.run)
Help on method solve in module reservoirflow.models.model:

solve(**kwargs) method of reservoirflow.models.black_oil.BlackOil instance
    Solve a single timestep.
    
    .. attention::
        This method is not available until the model is compiled
        using ``model.compile()``.
    
    Once the model is compiled, the documentation of the assigned
    solution can be accessed using one of the following methods:
    
    >>> help(model.solve) # or help(model.solution.solve)
    >>> print(model.solve.__doc__) # or print(model.solution.solve.__doc__)
1model.compile(stype="numerical", method="FDM", sparse=False)
2# model.compile(stype="analytical", method="1D1P", sparse=False)
[info] FDM was assigned as model.solution.
1model.compiler
rf.solutions.Compiler(model='BlackOil', stype='numerical', method='FDM', sparse=False)
1print(model.run.__doc__)
Perform a simulation run for nsteps.

        Parameters
        ----------
        nsteps : int, optional
            _description_
        threading : bool, optional
            _description_
        check_MB : bool, optional
            _description_
        isolver : str, optional
            iterative solver for sparse matrices. Available solvers are
            ["bicg", "bicgstab", "cg", "cgs", "gmres", "lgmres",
            "minres", "qmr", "gcrotmk", "tfqmr"].
            If None, direct solver is used. Only relevant when argument
            sparse=True. Direct solver is recommended for more accurate
            calculations. To improve performance, "cgs" is recommended
            to increase performance while option "minres" is not recommended due to
            high MB error. For more information check [1][2].

        References
        ----------
        - SciPy: `Solving Linear Problems <https://docs.scipy.org/doc/scipy/reference/sparse.linalg.html#solving-linear-problems>`_.
        - SciPy: `Iterative Solvers <https://scipy-lectures.org/advanced/scipy_sparse/solvers.html#iterative-solvers>`_.
        
1help(model.run)
Help on method run in module reservoirflow.solutions.numerical.fdm:

run(nsteps=10, threading=True, vectorize=True, check_MB=True, print_arrays=False, isolver=None) method of reservoirflow.solutions.numerical.fdm.FDM instance
    Perform a simulation run for nsteps.
    
    Parameters
    ----------
    nsteps : int, optional
        _description_
    threading : bool, optional
        _description_
    check_MB : bool, optional
        _description_
    isolver : str, optional
        iterative solver for sparse matrices. Available solvers are
        ["bicg", "bicgstab", "cg", "cgs", "gmres", "lgmres",
        "minres", "qmr", "gcrotmk", "tfqmr"].
        If None, direct solver is used. Only relevant when argument
        sparse=True. Direct solver is recommended for more accurate
        calculations. To improve performance, "cgs" is recommended
        to increase performance while option "minres" is not recommended due to
        high MB error. For more information check [1][2].
    
    References
    ----------
    - SciPy: `Solving Linear Problems <https://docs.scipy.org/doc/scipy/reference/sparse.linalg.html#solving-linear-problems>`_.
    - SciPy: `Iterative Solvers <https://scipy-lectures.org/advanced/scipy_sparse/solvers.html#iterative-solvers>`_.
1# print(rf.solutions.numerical.fdm.FDM.run.__doc__)
2# print(rf.solutions.numerical.FDM.run.__doc__)
3# print(model.solution.run.__doc__)
4print(model.run.__doc__)
Perform a simulation run for nsteps.

        Parameters
        ----------
        nsteps : int, optional
            _description_
        threading : bool, optional
            _description_
        check_MB : bool, optional
            _description_
        isolver : str, optional
            iterative solver for sparse matrices. Available solvers are
            ["bicg", "bicgstab", "cg", "cgs", "gmres", "lgmres",
            "minres", "qmr", "gcrotmk", "tfqmr"].
            If None, direct solver is used. Only relevant when argument
            sparse=True. Direct solver is recommended for more accurate
            calculations. To improve performance, "cgs" is recommended
            to increase performance while option "minres" is not recommended due to
            high MB error. For more information check [1][2].

        References
        ----------
        - SciPy: `Solving Linear Problems <https://docs.scipy.org/doc/scipy/reference/sparse.linalg.html#solving-linear-problems>`_.
        - SciPy: `Iterative Solvers <https://scipy-lectures.org/advanced/scipy_sparse/solvers.html#iterative-solvers>`_.
        
1dir(rf.solutions)
['Compiler',
 'Solution',
 '__all__',
 '__builtins__',
 '__cached__',
 '__doc__',
 '__file__',
 '__loader__',
 '__name__',
 '__package__',
 '__path__',
 '__spec__',
 'compiler',
 'numerical',
 'solution']
1dir(rf.solutions.numerical)
['FDM',
 'FEM',
 'FVM',
 '__all__',
 '__builtins__',
 '__cached__',
 '__doc__',
 '__file__',
 '__loader__',
 '__name__',
 '__package__',
 '__path__',
 '__spec__',
 'fdm',
 'fem',
 'fvm',
 'solvers']
 1print(
 2    " stype:",
 3    model.compiler.stype,
 4    "\n",
 5    "method:",
 6    model.compiler.method,
 7    # "\n",
 8    # "mode:",
 9    # model.compiler.mode,
10    # "\n",
11    # "solver:",
12    # model.compiler.solver,
13    "\n",
14    "solution:",
15    # model.compiler.solution,
16    model.compiler.model.solution,
17    model.compiler.model.name,
18    "\n",
19    model.solutions,
20)
 stype: numerical 
 method: FDM 
 solution: <reservoirflow.solutions.numerical.fdm.FDM object at 0x7f5b37118150> BlackOil 
 {'BlackOil-numerical-FDM-False': <reservoirflow.solutions.numerical.fdm.FDM object at 0x7f5b37118150>}
1model.run(
2    nsteps=10,
3    vectorize=True,
4    threading=True,
5    isolver="cgs",
6)
[info] Simulation run started: 10 timesteps.
[info] Simulation run of 10 steps finished in 0.04 seconds.
[info] Material Balance Error: 1.693933882052079e-11.
1model.get_df()
Time Q0 Q4 P0 P1 P2 P3 P4 Qw4 Pwf4
Step
0 0 0.0 0.0 4000.0 4000.000000 4000.000000 4000.000000 4000.000000 0.0 4000.000000
1 1 600.0 -600.0 4000.0 3989.436768 3968.310305 3947.183842 3926.057379 -600.0 3898.992647
2 2 600.0 -600.0 4000.0 3989.436768 3968.310305 3947.183842 3926.057379 -600.0 3898.992647
3 3 600.0 -600.0 4000.0 3989.436768 3968.310305 3947.183842 3926.057379 -600.0 3898.992647
4 4 600.0 -600.0 4000.0 3989.436768 3968.310305 3947.183842 3926.057379 -600.0 3898.992647
5 5 600.0 -600.0 4000.0 3989.436768 3968.310305 3947.183842 3926.057379 -600.0 3898.992647
6 6 600.0 -600.0 4000.0 3989.436768 3968.310305 3947.183842 3926.057379 -600.0 3898.992647
7 7 600.0 -600.0 4000.0 3989.436768 3968.310305 3947.183842 3926.057379 -600.0 3898.992647
8 8 600.0 -600.0 4000.0 3989.436768 3968.310305 3947.183842 3926.057379 -600.0 3898.992647
9 9 600.0 -600.0 4000.0 3989.436768 3968.310305 3947.183842 3926.057379 -600.0 3898.992647
10 10 600.0 -600.0 4000.0 3989.436768 3968.310305 3947.183842 3926.057379 -600.0 3898.992647
1model.solution.get_cells_eq()
{np.int64(1): (defaultdict(int, {p1: 85.2012000000000, p2: -28.4004000000000}),
  227203.200000000),
 np.int64(2): (defaultdict(int,
              {p1: 28.4004000000000,
               p3: 28.4004000000000,
               p2: -56.8008000000000}),
  0.0),
 np.int64(3): (defaultdict(int,
              {p2: 28.4004000000000,
               p4: 28.4004000000000,
               p3: -56.8008000000000}),
  0.0),
 np.int64(4): (defaultdict(int, {p3: 28.4004000000000, p4: -28.4004000000000}),
  600.000000000000)}
1model.solution.get_matrices_symb(True)
(array([[ 85.2012, -28.4004,   0.    ,   0.    ],
        [ 28.4004, -56.8008,  28.4004,   0.    ],
        [  0.    ,  28.4004, -56.8008,  28.4004],
        [  0.    ,   0.    ,  28.4004, -28.4004]]),
 array([[227203.2],
        [     0. ],
        [     0. ],
        [   600. ]]))
1model.solution.get_matrices_vect(True)
(array([[-85.2012,  28.4004,   0.    ,   0.    ],
        [ 28.4004, -56.8008,  28.4004,   0.    ],
        [  0.    ,  28.4004, -56.8008,  28.4004],
        [  0.    ,   0.    ,  28.4004, -28.4004]]),
 array([[-227203.2],
        [      0. ],
        [      0. ],
        [    600. ]]))
1model.solve(print_arrays=True)
step: 10
[[ 85.2012 -28.4004   0.       0.    ]
 [ 28.4004 -56.8008  28.4004   0.    ]
 [  0.      28.4004 -56.8008  28.4004]
 [  0.       0.      28.4004 -28.4004]
 [-85.2012  28.4004   0.       0.    ]
 [ 28.4004 -56.8008  28.4004   0.    ]
 [  0.      28.4004 -56.8008  28.4004]
 [  0.       0.      28.4004 -28.4004]
 [  0.       0.       0.       0.    ]
 [  0.       0.       0.       0.    ]
 [  0.       0.       0.       0.    ]
 [  0.       0.       0.       0.    ]]
[[ 227203.2 -227203.2       0. ]
 [      0.        0.        0. ]
 [      0.        0.        0. ]
 [    600.      600.        0. ]]

Comments πŸ’¬#

Feel free to make a comment, ask a question, or share your opinion about this specific content. Please keep in mind the Commenting Guidelines βš–.