"""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)