Source code for pyscal.pyscalcli

"""Command line tool for pyscal"""

import argparse
import io
import sys
import traceback
from pathlib import Path
from typing import Optional

import pandas as pd

from pyscal import (
    GasWater,
    SCALrecommendation,
    WaterOilGas,
    __version__,
    getLogger_pyscal,
    plotting,
)

from .factory import PyscalFactory

EPILOG = """
The parameter file should contain a table with at least the column
SATNUM, containing only consecutive integers starting at 1. Each row
provides the data for the corresponding SATNUM. Comments are put in a
column called TAG or COMMENT. Column headers are case insensitive.

Saturation endpoints are put in columns 'swirr', 'swl', 'swcr', 'sorw',
'sgcr' and 'sorg'. Relative permeability endpoints are put in columns
'krwend', 'krwmax', 'krowend', 'krogend', 'krgend' and 'krgmax'.
These columns are optional and are defaulted to 0 or 1.

Corey or LET parametrization are based on presence of the columns
'Nw', 'Now', 'Nog', 'Ng', 'Lw', 'Ew', 'Tw', 'Low', 'Eow', 'Tow',
'Log', 'Eog', 'Tog', 'Lg', 'Eg', 'Tg'.

Simple J-function for capillary pressure ("RMS" version) is used if the columns
'a', 'b', 'poro_ref', 'perm_ref' and 'drho' are found. If you provide
'a_petro', or 'b_petro', the petrophysical formulation of the simple J-function
is used. Check API for exact formulas. Normalized J-function is used if 'a',
'b', 'poro', 'perm' and 'sigma_costau' is provided.

Primary drainage and imbibition capillary pressure with LET parametrization are
based on presence of the columnns 'Lp', 'Ep', 'Tp','Lt', 'Et', 'Tt', 'Pcmax', 'Pct' and
'Ls', 'Es', 'Ts', 'Lf', 'Ef', 'Tf', 'Pcmax', 'Pct', 'Pcmin' respectively.

Capillary pressure from the Skjæveland correlation is based on the on the presence
of the columns 'cw', 'co', 'aw', 'ao'.

For SCAL recommendations, there should be exactly three rows for each SATNUM,
tagged with the strings 'low', 'base' and 'high' in the column 'CASE'

When interpolating in a SCAL recommendation, 'int_param_wo' is the main parameter
that is used for water-oil, gas-oil and gas-water, and for all SATNUMs if nothing
more is provided. Provide int_param_go in addition if separate interpolation
for WaterOil and GasOil is needed.
"""


[docs] def get_parser() -> argparse.ArgumentParser: """Construct the argparse parser for the command line script. Returns: argparse.Parser """ parser = argparse.ArgumentParser( prog="pyscal", description=( "pyscal (" + __version__ + ") is a tool to create Eclipse include " "files for relative permeability input from tabulated parameters." ), epilog=EPILOG, ) parser.add_argument( "parametertable", help=( "CSV or XLSX file with Corey or LET parameters for relperms. " "One SATNUM pr row." ), ) parser.add_argument( "-v", "--verbose", action="store_true", help="Print informational messages while processing input", ) parser.add_argument( "--debug", action="store_true", help="Print debug information", ) parser.add_argument( "--version", action="version", version="%(prog)s (version " + __version__ + ")", ) parser.add_argument( "-o", "--output", default="relperm.inc", help="Name of Eclipse include file to produce", ) parser.add_argument( "--delta_s", default=None, type=float, help="Saturation table step-length for sw/sg. Default 0.01", ) parser.add_argument( "--int_param_wo", default=None, type=float, help=( "Interpolation parameter for water-oil, needed if the parametertable " "contains pess/low, base and opt/high for each SATNUM. " "The parameter will be used for all SATNUMs and must be " "between -1 and 1. Also used for GasWater." ), ) parser.add_argument( "--int_param_go", default=None, type=float, help=( "Interpolation parameter for gas-oil. " "If not provided, the water-oil interpolation parameter will be used " "as default. Do not use for GasWater." ), ) parser.add_argument( "--sheet_name", type=str, default=None, help="Sheet name if reading XLSX file. Defaults to first sheet", ) parser.add_argument( "--slgof", action="store_true", default=False, help="If using family 1 keywords, use SLGOF instead of SGOF", ) parser.add_argument( "--family2", action="store_true", default=False, help=( "Output family 2 keywords, SWFN, SGFN and SOF3/SOF2. " "Family 1 (SWOF + SGOF) is written if this is not set. " "Implicit for gas-water input." ), ) # Plotting arguments parser.add_argument( "--plot", action="store_true", default=False, help=("Make and save relative permeability figures."), ) parser.add_argument( "--plot_pc", action="store_true", default=False, help=("Make and save capillary pressure figures."), ) parser.add_argument( "--plot_semilog", action="store_true", default=False, help=( "Plot relative permeability figures with log y-axis." "Run both with and without this flag to plot" "both linear and semi-log relperm plots." ), ) parser.add_argument( "--plot_outdir", default="./", help="Directory where the plot output figures will be saved.", ) return parser
[docs] def main() -> None: """Endpoint for pyscals command line utility. Translates from argparse API to Pyscal's Python API""" parser = get_parser() args = parser.parse_args() if isinstance(sys.stdout, io.TextIOWrapper): sys.stdout.reconfigure(encoding="UTF-8") if isinstance(sys.stdout, io.TextIOWrapper): sys.stdout.reconfigure(encoding="UTF-8") try: pyscal_main( parametertable=args.parametertable, verbose=args.verbose, debug=args.debug, output=args.output, delta_s=args.delta_s, int_param_wo=args.int_param_wo, int_param_go=args.int_param_go, sheet_name=args.sheet_name, slgof=args.slgof, family2=args.family2, plot=args.plot, plot_pc=args.plot_pc, plot_semilog=args.plot_semilog, plot_outdir=args.plot_outdir, ) except (OSError, ValueError) as err: print("".join(traceback.format_tb(err.__traceback__))) sys.exit(str(err))
[docs] def pyscal_main( parametertable: str, verbose: bool = False, debug: bool = False, output: str = "relperm.inc", delta_s: Optional[float] = None, int_param_wo: Optional[float] = None, int_param_go: Optional[float] = None, sheet_name: Optional[str] = None, slgof: bool = False, family2: bool = False, plot: bool = False, plot_pc: bool = False, plot_semilog: bool = False, plot_outdir: str = "./", ) -> None: """A "main()" method not relying on argparse. This can be used for testing, and also by an ERT forward model, e.g. in semeio (github.com/equinor/semeio) Args: parametertable: Filename (CSV or XLSX) to load verbose: verbose or not debug: debug mode or not output: Output filename delta_s: Saturation step-length int_param_wo: Interpolation parameter for wateroil int_param_go: Interpolation parameter for gasoil sheet_name: Which sheet in XLSX file slgof: Use SLGOF family2: Dump family 2 keywords plot: Plot relative permeability figures and save plot_pc: Plot capillary pressure curves in addition to relperm curves plot_semilog: Plot relative permeability figures with log y-axis """ logger = getLogger_pyscal( __name__, {"debug": debug, "verbose": verbose, "output": output} ) parametertable = PyscalFactory.load_relperm_df( parametertable, sheet_name=sheet_name ) assert isinstance(parametertable, pd.DataFrame) logger.debug("Input data:\n%s", parametertable.to_string(index=False)) if int_param_go is not None and int_param_wo is None: raise ValueError("Don't use int_param_go alone, only int_param_wo") if isinstance(int_param_wo, list) or isinstance(int_param_go, list): raise TypeError( "SATNUM specific interpolation parameters are not supported in pyscalcli" ) if int_param_wo is not None and "CASE" not in parametertable: raise ValueError( "Interpolation parameter provided but no CASE column in input data" ) if "SATNUM" not in parametertable: raise ValueError("There is no column called SATNUM in the input data") if "CASE" in parametertable: # Then we should do interpolation if int_param_wo is None: raise ValueError("No interpolation parameters provided") scalrec_list = PyscalFactory.create_scal_recommendation_list( parametertable, h=delta_s ) assert isinstance(scalrec_list[1], SCALrecommendation) if scalrec_list[1].type == WaterOilGas: logger.info( "Interpolating, wateroil=%s, gasoil=%s", str(int_param_wo), str(int_param_go), ) wog_list = scalrec_list.interpolate(int_param_wo, int_param_go, h=delta_s) elif scalrec_list[1].type == GasWater: logger.info( "Interpolating, gaswater=%s", str(int_param_wo), ) wog_list = scalrec_list.interpolate(int_param_wo, None, h=delta_s) else: wog_list = PyscalFactory.create_pyscal_list( parametertable, h=delta_s ) # can be both water-oil, water-oil-gas, or gas-water family = 2 if family2 or wog_list.pyscaltype == GasWater else 1 if output == "-": print(wog_list.build_eclipse_data(family=family, slgof=slgof)) else: if not Path(output).parent.exists(): raise IOError(f"Output directory not found '{Path(output).parent}'") with open(output, "w", newline="\n", encoding="utf-8") as fh: fh.write(wog_list.build_eclipse_data(family=family, slgof=slgof)) print("Written to " + output) if plot: plotting.plotter(wog_list, plot_pc, plot_semilog, plot_outdir)