From a7c078d38fe45393bc050c916955fda1ecefb481 Mon Sep 17 00:00:00 2001 From: Bartek Vik <122297208+bartekvik@users.noreply.github.com> Date: Mon, 2 Sep 2024 11:11:10 +0200 Subject: [PATCH] Refactor PyscalFactory (#448) * Refactor create_water_oil * Code refactoring * Merge update * Fix ruff issue * test deprecated api work and warnings are emitted --------- Co-authored-by: Bartek Florczyk Vik (CCI RPT RES1) Co-authored-by: Bartek Florczyk Vik (CCI RPT RES1) --- docs/usage.rst | 12 +- src/pyscal/factory.py | 1823 ++++++++++++++++-------------- src/pyscal/pyscalcli.py | 17 +- tests/test_factory.py | 438 +++---- tests/test_interactive_plots.py | 5 +- tests/test_pyscallist.py | 180 ++- tests/test_scalrecommendation.py | 53 +- 7 files changed, 1340 insertions(+), 1188 deletions(-) diff --git a/docs/usage.rst b/docs/usage.rst index fb2365dc..e943ed09 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -188,10 +188,10 @@ equivalent to the code lines above (except for capillary pressure) is then: .. code-block:: python - from pyscal import PyscalFactory + from pyscal import create_water_oil params = dict(swl=0.05, sorw=0.03, h=0.1, nw=2.1, krwend=0.6, now=2.5, kroend=0.9, tag="Foobarites") - wo = PyscalFactory.create_water_oil(params) + wo = create_water_oil(params) print(wo.SWOF()) Note that parameter names to factory functions are case *insensitive*, while @@ -230,7 +230,7 @@ some of the saturation endpoints, as there are compatibility constraints. For this reason, it is recommended to initialize both the ``WaterOil`` and ``GasOil`` objects trough a ``WaterOilGas`` object. -There is a corresponding ``PyscalFactory.create_gas_oil()`` support function with +There is a corresponding ``create_gas_oil()`` support function with dictionary as argument. For plotting, ``GasOil`` object has a function ``.plotkrgkrog()``. @@ -305,9 +305,9 @@ the SCAL recommendation holds three GasWater objects, but works similarly. from pyscal import SCALrecommendation, PyscalFactory - low = PyscalFactory.create_water_oil_gas(dict(nw=1, now=1, ng=1, nog=1, tag='low')) - base = PyscalFactory.create_water_oil_gas(dict(nw=2, now=2, ng=2, nog=3, tag='base')) - high = PyscalFactory.create_water_oil_gas(dict(nw=3, now=3, ng=3, nog=3, tag='high')) + low = create_water_oil_gas(dict(nw=1, now=1, ng=1, nog=1, tag='low')) + base = create_water_oil_gas(dict(nw=2, now=2, ng=2, nog=3, tag='base')) + high = create_water_oil_gas(dict(nw=3, now=3, ng=3, nog=3, tag='high')) rec = SCALrecommendation(low, base, high) interpolant = rec.interpolate(-0.4) diff --git a/src/pyscal/factory.py b/src/pyscal/factory.py index 9c4d1531..3fc2e94c 100644 --- a/src/pyscal/factory.py +++ b/src/pyscal/factory.py @@ -1,6 +1,7 @@ """Factory functions for creating the pyscal objects""" import csv +import warnings import zipfile from pathlib import Path from typing import Any, Dict, Iterable, List, Optional, Set, Union @@ -128,968 +129,1096 @@ def slicedict(dct: dict, keys: Iterable): WOG_INIT = ["swirr", "swl", "swcr", "sorw", "socr", "sorg", "sgcr", "h", "tag"] -class PyscalFactory: - """Class for implementing the factory pattern for Pyscal objects - - The factory functions herein can take multiple parameter sets, - determine what kind of parametrization to be used, and set up - the full objects based on these parameters, instead of - explicitly having to call the API for each task. - - Example:: - - wo = WaterOil(sorw=0.05) - wo.add_corey_water(nw=3) - wo.add_corey_oil(now=2) - # is equivalent to: - wo = factory.create_water_oil(dict(sorw=0.05, nw=3, now=2)) - - Parameter names to factory functions are case *insensitive*, while - the add_*() parameters are not. This is because the add_*() parameters - are meant as a Python API, while the factory class is there to aid - users when input is written in a different context, like an Excel - spreadsheet. - """ +def create_water_oil( + params: Optional[Dict[str, float]] = None, fast: bool = False +) -> WaterOil: + """Create a WaterOil object from a dictionary of parameters. - @staticmethod - def create_water_oil( - params: Optional[Dict[str, float]] = None, fast: bool = False - ) -> WaterOil: - """Create a WaterOil object from a dictionary of parameters. - - Parameterization (Corey/LET) is inferred from presence - of certain parameters in the dictionary. + Parameterization (Corey/LET) is inferred from presence + of certain parameters in the dictionary. - Don't rely on behaviour of you supply both Corey and LET at - the same time. + Don't rely on behaviour of you supply both Corey and LET at + the same time. - Parameter names in the dictionary are case insensitive. You - can use Swirr, swirr, sWirR, swiRR etc. + Parameter names in the dictionary are case insensitive. You + can use Swirr, swirr, sWirR, swiRR etc. - NB: the add_LET_* methods have the names 'l', 'e' and 't' - in their signatures, which is not precise enough in this - context, so we require e.g. 'Lw' and 'Low' (which both will be - translated to 'l') + NB: the add_LET_* methods have the names 'l', 'e' and 't' + in their signatures, which is not precise enough in this + context, so we require e.g. 'Lw' and 'Low' (which both will be + translated to 'l') - Recognized parameters: - swirr, swl, swcr, sorw, socr, sgrw, h, tag, nw, now, krwmax, krwend, - lw, ew, tw, low, eow, tow, lo, eo, to, kroend, - a, a_petro, b, b_petro, poro_ref, perm_ref, drho, - a, b, poro, perm, sigma_costau + Recognized parameters: + swirr, swl, swcr, sorw, socr, sgrw, h, tag, nw, now, krwmax, krwend, + lw, ew, tw, low, eow, tow, lo, eo, to, kroend, + a, a_petro, b, b_petro, poro_ref, perm_ref, drho, + a, b, poro, perm, sigma_costau - Args: - params: Dictionary with parameters describing the WaterOil object. - fast: If fast-mode should be set for constructed object. - """ - if not params: - params = {} - if not isinstance(params, dict): - raise TypeError("Parameter to create_water_oil must be a dictionary") + Args: + params: Dictionary with parameters describing the WaterOil object. + fast: If fast-mode should be set for constructed object. + """ + if not params: + params = {} + if not isinstance(params, dict): + raise TypeError("Parameter to create_water_oil must be a dictionary") - check_deprecated(params) + check_deprecated(params) - sufficient_water_oil_params(params, failhard=True) + sufficient_water_oil_params(params, failhard=True) - # For case insensitiveness, all keys are converted to lower case: - params = {key.lower(): value for (key, value) in params.items()} + # For case insensitiveness, all keys are converted to lower case: + params = {key.lower(): value for (key, value) in params.items()} - # Allowing sending in NaN values, delete those keys. - params = filter_nan_from_dict(params) + # Allowing sending in NaN values, delete those keys. + params = filter_nan_from_dict(params) - usedparams: Set[str] = set() + usedparams: Set[str] = set() - # Check if we should initialize swl from a swlheight parameter: - if set(WO_SWL_FROM_HEIGHT).issubset(params): - if "swl" in params: - raise ValueError( - "Do not provide both swl and swlheight at the same time" - ) - if params["swlheight"] <= 0: - raise ValueError("swlheight must be larger than zero") - params_swl_from_height = slicedict(params, WO_SWL_FROM_HEIGHT) - params["swl"] = capillarypressure.swl_from_height_simpleJ( - **params_swl_from_height + # Check if we should initialize swl from a swlheight parameter: + if set(WO_SWL_FROM_HEIGHT).issubset(params): + if "swl" in params: + raise ValueError("Do not provide both swl and swlheight at the same time") + if params["swlheight"] <= 0: + raise ValueError("swlheight must be larger than zero") + params_swl_from_height = slicedict(params, WO_SWL_FROM_HEIGHT) + params["swl"] = capillarypressure.swl_from_height_simpleJ( + **params_swl_from_height + ) + logger.debug("Computed swl from swlwheight to %s", str(params["swl"])) + if "swcr" in params and params["swcr"] < params["swl"]: + raise ValueError( + f'Provided swcr={params["swcr"]} is lower than ' + f'computed swl={params["swl"]}' ) - logger.debug("Computed swl from swlwheight to %s", str(params["swl"])) - if "swcr" in params and params["swcr"] < params["swl"]: - raise ValueError( - f'Provided swcr={params["swcr"]} is lower than ' - f'computed swl={params["swl"]}' - ) - elif set(WO_SWLHEIGHT).issubset(params): + elif set(WO_SWLHEIGHT).issubset(params): + raise ValueError( + ( + "Can't initialize from SWLHEIGHT without sufficient " + f"simple-J parameters, needs all of: {WO_SWL_FROM_HEIGHT}" + ) + ) + + # Should we have a swcr relative to swl? + if set(WO_SWCR_ADD).issubset(params): + if "swl" not in params: raise ValueError( ( - "Can't initialize from SWLHEIGHT without sufficient " - f"simple-J parameters, needs all of: {WO_SWL_FROM_HEIGHT}" + "If swcr should be relative to swl, " + "both swcr_add and swl must be provided" ) ) + if "swcr" in params: + raise ValueError("Do not provide both swcr and swcr_add at the same time") + params["swcr"] = params["swl"] + params[WO_SWCR_ADD[0]] + + # No requirements to the base objects, defaults are ok. + wateroil = WaterOil(**alias_sgrw(slicedict(params, WO_INIT)), fast=fast) + usedparams = usedparams.union(set(slicedict(params, WO_INIT).keys())) + logger.debug( + "Initialized WaterOil object from parameters %s", str(list(usedparams)) + ) - # Should we have a swcr relative to swl? - if set(WO_SWCR_ADD).issubset(params): - if "swl" not in params: - raise ValueError( - ( - "If swcr should be relative to swl, " - "both swcr_add and swl must be provided" - ) - ) - if "swcr" in params: - raise ValueError( - "Do not provide both swcr and swcr_add at the same time" - ) - params["swcr"] = params["swl"] + params[WO_SWCR_ADD[0]] + # Water curve + params_corey_water = slicedict(params, WO_COREY_WATER + WO_WATER_ENDPOINTS) + params_let_water = slicedict(params, WO_LET_WATER + WO_WATER_ENDPOINTS) + if set(WO_COREY_WATER).issubset(set(params_corey_water)): + wateroil.add_corey_water(**params_corey_water) + logger.debug( + "Added Corey water to WaterOil object from parameters %s", + str(params_corey_water.keys()), + ) + elif set(WO_LET_WATER).issubset(set(params_let_water)): + params_let_water["l"] = params_let_water.pop("lw") + params_let_water["e"] = params_let_water.pop("ew") + params_let_water["t"] = params_let_water.pop("tw") + wateroil.add_LET_water(**params_let_water) + logger.debug( + "Added LET water to WaterOil object from parameters %s", + str(params_let_water.keys()), + ) - # No requirements to the base objects, defaults are ok. - wateroil = WaterOil( - **PyscalFactory.alias_sgrw(slicedict(params, WO_INIT)), fast=fast + # Oil curve: + params_corey_oil = slicedict(params, WO_COREY_OIL + WO_OIL_ENDPOINTS) + params_let_oil = slicedict(params, WO_LET_OIL + WO_LET_OIL_ALT + WO_OIL_ENDPOINTS) + if set(WO_COREY_OIL).issubset(set(params_corey_oil)): + wateroil.add_corey_oil(**params_corey_oil) + logger.debug( + "Added Corey water to WaterOil object from parameters %s", + str(params_corey_oil.keys()), + ) + elif set(WO_LET_OIL).issubset(set(params_let_oil)): + params_let_oil["l"] = params_let_oil.pop("low") + params_let_oil["e"] = params_let_oil.pop("eow") + params_let_oil["t"] = params_let_oil.pop("tow") + wateroil.add_LET_oil(**params_let_oil) + logger.debug( + "Added LET water to WaterOil object from parameters %s", + str(params_let_oil.keys()), ) - usedparams = usedparams.union(set(slicedict(params, WO_INIT).keys())) + elif set(WO_LET_OIL_ALT).issubset(set(params_let_oil)): + params_let_oil["l"] = params_let_oil.pop("lo") + params_let_oil["e"] = params_let_oil.pop("eo") + params_let_oil["t"] = params_let_oil.pop("to") + wateroil.add_LET_oil(**params_let_oil) logger.debug( - "Initialized WaterOil object from parameters %s", str(list(usedparams)) + "Added LET water to WaterOil object from parameters %s", + str(params_let_oil.keys()), ) - # Water curve - params_corey_water = slicedict(params, WO_COREY_WATER + WO_WATER_ENDPOINTS) - params_let_water = slicedict(params, WO_LET_WATER + WO_WATER_ENDPOINTS) - if set(WO_COREY_WATER).issubset(set(params_corey_water)): - wateroil.add_corey_water(**params_corey_water) - logger.debug( - "Added Corey water to WaterOil object from parameters %s", - str(params_corey_water.keys()), - ) - elif set(WO_LET_WATER).issubset(set(params_let_water)): - params_let_water["l"] = params_let_water.pop("lw") - params_let_water["e"] = params_let_water.pop("ew") - params_let_water["t"] = params_let_water.pop("tw") - wateroil.add_LET_water(**params_let_water) - logger.debug( - "Added LET water to WaterOil object from parameters %s", - str(params_let_water.keys()), + # Capillary pressure: + params_simple_j = slicedict(params, WO_SIMPLE_J + ["g"]) + params_norm_j = slicedict(params, WO_NORM_J) + params_simple_j_petro = slicedict(params, WO_SIMPLE_J_PETRO + ["g"]) + params_let_pc_pd = slicedict(params, WO_LET_PC_PD) + params_let_pc_imb = slicedict(params, WO_LET_PC_IMB) + params_skjaeveland_pc = slicedict(params, WO_SKJAEVELAND_PC) + + if set(WO_SIMPLE_J).issubset(set(params_simple_j)): + wateroil.add_simple_J(**params_simple_j) + elif set(WO_SIMPLE_J_PETRO).issubset(set(params_simple_j_petro)): + params_simple_j_petro["a"] = params_simple_j_petro.pop("a_petro") + params_simple_j_petro["b"] = params_simple_j_petro.pop("b_petro") + wateroil.add_simple_J_petro(**params_simple_j_petro) + elif set(WO_NORM_J).issubset(set(params_norm_j)): + wateroil.add_normalized_J(**params_norm_j) + elif set(WO_LET_PC_PD).issubset(set(params_let_pc_pd)): + params_let_pc_pd["Lp"] = params_let_pc_pd.pop("lpow") + params_let_pc_pd["Ep"] = params_let_pc_pd.pop("epow") + params_let_pc_pd["Tp"] = params_let_pc_pd.pop("tpow") + params_let_pc_pd["Lt"] = params_let_pc_pd.pop("ltow") + params_let_pc_pd["Et"] = params_let_pc_pd.pop("etow") + params_let_pc_pd["Tt"] = params_let_pc_pd.pop("ttow") + params_let_pc_pd["Pcmax"] = params_let_pc_pd.pop("pcowmax") + params_let_pc_pd["Pct"] = params_let_pc_pd.pop("pcowt") + wateroil.add_LET_pc_pd(**params_let_pc_pd) + elif set(WO_LET_PC_IMB).issubset(set(params_let_pc_imb)): + params_let_pc_imb["Ls"] = params_let_pc_imb.pop("lsow") + params_let_pc_imb["Es"] = params_let_pc_imb.pop("esow") + params_let_pc_imb["Ts"] = params_let_pc_imb.pop("tsow") + params_let_pc_imb["Lf"] = params_let_pc_imb.pop("lfow") + params_let_pc_imb["Ef"] = params_let_pc_imb.pop("efow") + params_let_pc_imb["Tf"] = params_let_pc_imb.pop("tfow") + params_let_pc_imb["Pcmax"] = params_let_pc_imb.pop("pcowmax") + params_let_pc_imb["Pct"] = params_let_pc_imb.pop("pcowt") + params_let_pc_imb["Pcmin"] = params_let_pc_imb.pop("pcowmin") + wateroil.add_LET_pc_imb(**params_let_pc_imb) + elif set(WO_SKJAEVELAND_PC).issubset(set(params_skjaeveland_pc)): + wateroil.add_skjaeveland_pc(**params_skjaeveland_pc) + else: + logger.info( + ( + "Missing or ambiguous parameters for capillary pressure in " + "WaterOil object. Using zero." ) - - # Oil curve: - params_corey_oil = slicedict(params, WO_COREY_OIL + WO_OIL_ENDPOINTS) - params_let_oil = slicedict( - params, WO_LET_OIL + WO_LET_OIL_ALT + WO_OIL_ENDPOINTS ) - if set(WO_COREY_OIL).issubset(set(params_corey_oil)): - wateroil.add_corey_oil(**params_corey_oil) - logger.debug( - "Added Corey water to WaterOil object from parameters %s", - str(params_corey_oil.keys()), - ) - elif set(WO_LET_OIL).issubset(set(params_let_oil)): - params_let_oil["l"] = params_let_oil.pop("low") - params_let_oil["e"] = params_let_oil.pop("eow") - params_let_oil["t"] = params_let_oil.pop("tow") - wateroil.add_LET_oil(**params_let_oil) - logger.debug( - "Added LET water to WaterOil object from parameters %s", - str(params_let_oil.keys()), - ) - elif set(WO_LET_OIL_ALT).issubset(set(params_let_oil)): - params_let_oil["l"] = params_let_oil.pop("lo") - params_let_oil["e"] = params_let_oil.pop("eo") - params_let_oil["t"] = params_let_oil.pop("to") - wateroil.add_LET_oil(**params_let_oil) - logger.debug( - "Added LET water to WaterOil object from parameters %s", - str(params_let_oil.keys()), - ) + if not wateroil.selfcheck(): + raise ValueError( + ("Incomplete WaterOil object, some parameters missing to factory") + ) + return wateroil - # Capillary pressure: - params_simple_j = slicedict(params, WO_SIMPLE_J + ["g"]) - params_norm_j = slicedict(params, WO_NORM_J) - params_simple_j_petro = slicedict(params, WO_SIMPLE_J_PETRO + ["g"]) - params_let_pc_pd = slicedict(params, WO_LET_PC_PD) - params_let_pc_imb = slicedict(params, WO_LET_PC_IMB) - params_skjaeveland_pc = slicedict(params, WO_SKJAEVELAND_PC) - - if set(WO_SIMPLE_J).issubset(set(params_simple_j)): - wateroil.add_simple_J(**params_simple_j) - elif set(WO_SIMPLE_J_PETRO).issubset(set(params_simple_j_petro)): - params_simple_j_petro["a"] = params_simple_j_petro.pop("a_petro") - params_simple_j_petro["b"] = params_simple_j_petro.pop("b_petro") - wateroil.add_simple_J_petro(**params_simple_j_petro) - elif set(WO_NORM_J).issubset(set(params_norm_j)): - wateroil.add_normalized_J(**params_norm_j) - elif set(WO_LET_PC_PD).issubset(set(params_let_pc_pd)): - params_let_pc_pd["Lp"] = params_let_pc_pd.pop("lpow") - params_let_pc_pd["Ep"] = params_let_pc_pd.pop("epow") - params_let_pc_pd["Tp"] = params_let_pc_pd.pop("tpow") - params_let_pc_pd["Lt"] = params_let_pc_pd.pop("ltow") - params_let_pc_pd["Et"] = params_let_pc_pd.pop("etow") - params_let_pc_pd["Tt"] = params_let_pc_pd.pop("ttow") - params_let_pc_pd["Pcmax"] = params_let_pc_pd.pop("pcowmax") - params_let_pc_pd["Pct"] = params_let_pc_pd.pop("pcowt") - wateroil.add_LET_pc_pd(**params_let_pc_pd) - elif set(WO_LET_PC_IMB).issubset(set(params_let_pc_imb)): - params_let_pc_imb["Ls"] = params_let_pc_imb.pop("lsow") - params_let_pc_imb["Es"] = params_let_pc_imb.pop("esow") - params_let_pc_imb["Ts"] = params_let_pc_imb.pop("tsow") - params_let_pc_imb["Lf"] = params_let_pc_imb.pop("lfow") - params_let_pc_imb["Ef"] = params_let_pc_imb.pop("efow") - params_let_pc_imb["Tf"] = params_let_pc_imb.pop("tfow") - params_let_pc_imb["Pcmax"] = params_let_pc_imb.pop("pcowmax") - params_let_pc_imb["Pct"] = params_let_pc_imb.pop("pcowt") - params_let_pc_imb["Pcmin"] = params_let_pc_imb.pop("pcowmin") - wateroil.add_LET_pc_imb(**params_let_pc_imb) - elif set(WO_SKJAEVELAND_PC).issubset(set(params_skjaeveland_pc)): - wateroil.add_skjaeveland_pc(**params_skjaeveland_pc) - else: - logger.info( - ( - "Missing or ambiguous parameters for capillary pressure in " - "WaterOil object. Using zero." - ) - ) - if not wateroil.selfcheck(): - raise ValueError( - ("Incomplete WaterOil object, some parameters missing to factory") - ) - return wateroil - @staticmethod - def create_gas_oil( - params: Optional[Dict[str, float]] = None, fast: bool = False - ) -> GasOil: - """Create a GasOil object from a dictionary of parameters. +def create_gas_oil( + params: Optional[Dict[str, float]] = None, fast: bool = False +) -> GasOil: + """Create a GasOil object from a dictionary of parameters. - Parameterization (Corey/LET) is inferred from presence - of certain parameters in the dictionary. + Parameterization (Corey/LET) is inferred from presence + of certain parameters in the dictionary. - Don't rely on behaviour of you supply both Corey and LET at - the same time. + Don't rely on behaviour of you supply both Corey and LET at + the same time. - NB: the add_LET_* methods have the names 'l', 'e' and 't' - in their signatures, which is not precise enough in this - context, so we require e.g. 'Lg' and 'Log' (which both will be - translated to 'l'). + NB: the add_LET_* methods have the names 'l', 'e' and 't' + in their signatures, which is not precise enough in this + context, so we require e.g. 'Lg' and 'Log' (which both will be + translated to 'l'). - Recognized parameters: - swirr, sgcr, sorg, swl, krgendanchor, h, tag, - ng, krgend, krgmax, nog, kroend, kromax - lg, eg, tg, log, eog, tog + Recognized parameters: + swirr, sgcr, sorg, swl, krgendanchor, h, tag, + ng, krgend, krgmax, nog, kroend, kromax + lg, eg, tg, log, eog, tog - Args: - params: Dictionary with parameters describing the GasOil object. - fast: If fast-mode should be set for constructed object. - """ - if not params: - params = {} - if not isinstance(params, dict): - raise TypeError("Parameter to create_gas_oil must be a dictionary") + Args: + params: Dictionary with parameters describing the GasOil object. + fast: If fast-mode should be set for constructed object. + """ + if not params: + params = {} + if not isinstance(params, dict): + raise TypeError("Parameter to create_gas_oil must be a dictionary") - check_deprecated(params) + check_deprecated(params) - sufficient_gas_oil_params(params, failhard=True) + sufficient_gas_oil_params(params, failhard=True) - # For case insensitiveness, all keys are converted to lower case: - params = {key.lower(): value for (key, value) in params.items()} + # For case insensitiveness, all keys are converted to lower case: + params = {key.lower(): value for (key, value) in params.items()} + + # Allowing sending in NaN values, delete those keys. + params = filter_nan_from_dict(params) - # Allowing sending in NaN values, delete those keys. - params = filter_nan_from_dict(params) + usedparams: Set[str] = set() + # No requirements to the base objects, defaults are ok. + gasoil = GasOil(**slicedict(params, GO_INIT), fast=fast) + usedparams = usedparams.union(set(slicedict(params, GO_INIT).keys())) + logger.debug("Initialized GasOil object from parameters %s", str(list(usedparams))) - usedparams: Set[str] = set() - # No requirements to the base objects, defaults are ok. - gasoil = GasOil(**slicedict(params, GO_INIT), fast=fast) - usedparams = usedparams.union(set(slicedict(params, GO_INIT).keys())) + # Gas curve + params_corey_gas = slicedict(params, GO_COREY_GAS + GO_GAS_ENDPOINTS) + params_let_gas = slicedict(params, GO_LET_GAS + GO_GAS_ENDPOINTS) + if set(GO_COREY_GAS).issubset(set(params_corey_gas)): + gasoil.add_corey_gas(**params_corey_gas) + logger.debug( + "Added Corey gas to GasOil object from parameters %s", + str(params_corey_gas.keys()), + ) + elif set(GO_LET_GAS).issubset(set(params_let_gas)): + params_let_gas["l"] = params_let_gas.pop("lg") + params_let_gas["e"] = params_let_gas.pop("eg") + params_let_gas["t"] = params_let_gas.pop("tg") + gasoil.add_LET_gas(**params_let_gas) logger.debug( - "Initialized GasOil object from parameters %s", str(list(usedparams)) + "Added LET gas to GasOil object from parameters %s", + str(params_let_gas.keys()), + ) + else: + logger.warning("Missing or ambiguous parameters for gas curve in GasOil object") + + # Oil curve: + params_corey_oil = slicedict(params, GO_COREY_OIL + GO_OIL_ENDPOINTS) + params_let_oil = slicedict(params, GO_LET_OIL + GO_OIL_ENDPOINTS) + if set(GO_COREY_OIL).issubset(set(params_corey_oil)): + gasoil.add_corey_oil(**params_corey_oil) + logger.debug( + "Added Corey gas to GasOil object from parameters %s", + str(params_corey_oil.keys()), + ) + elif set(GO_LET_OIL).issubset(set(params_let_oil)): + params_let_oil["l"] = params_let_oil.pop("log") + params_let_oil["e"] = params_let_oil.pop("eog") + params_let_oil["t"] = params_let_oil.pop("tog") + gasoil.add_LET_oil(**params_let_oil) + logger.debug( + "Added LET gas to GasOil object from parameters %s", + str(params_let_oil.keys()), + ) + else: + logger.warning("Missing or ambiguous parameters for oil curve in GasOil object") + if not gasoil.selfcheck(): + raise ValueError( + ("Incomplete GasOil object, some parameters missing to factory") ) - # Gas curve - params_corey_gas = slicedict(params, GO_COREY_GAS + GO_GAS_ENDPOINTS) - params_let_gas = slicedict(params, GO_LET_GAS + GO_GAS_ENDPOINTS) - if set(GO_COREY_GAS).issubset(set(params_corey_gas)): - gasoil.add_corey_gas(**params_corey_gas) - logger.debug( - "Added Corey gas to GasOil object from parameters %s", - str(params_corey_gas.keys()), - ) - elif set(GO_LET_GAS).issubset(set(params_let_gas)): - params_let_gas["l"] = params_let_gas.pop("lg") - params_let_gas["e"] = params_let_gas.pop("eg") - params_let_gas["t"] = params_let_gas.pop("tg") - gasoil.add_LET_gas(**params_let_gas) - logger.debug( - "Added LET gas to GasOil object from parameters %s", - str(params_let_gas.keys()), - ) - else: - logger.warning( - "Missing or ambiguous parameters for gas curve in GasOil object" - ) + return gasoil - # Oil curve: - params_corey_oil = slicedict(params, GO_COREY_OIL + GO_OIL_ENDPOINTS) - params_let_oil = slicedict(params, GO_LET_OIL + GO_OIL_ENDPOINTS) - if set(GO_COREY_OIL).issubset(set(params_corey_oil)): - gasoil.add_corey_oil(**params_corey_oil) - logger.debug( - "Added Corey gas to GasOil object from parameters %s", - str(params_corey_oil.keys()), - ) - elif set(GO_LET_OIL).issubset(set(params_let_oil)): - params_let_oil["l"] = params_let_oil.pop("log") - params_let_oil["e"] = params_let_oil.pop("eog") - params_let_oil["t"] = params_let_oil.pop("tog") - gasoil.add_LET_oil(**params_let_oil) - logger.debug( - "Added LET gas to GasOil object from parameters %s", - str(params_let_oil.keys()), - ) - else: - logger.warning( - "Missing or ambiguous parameters for oil curve in GasOil object" - ) - if not gasoil.selfcheck(): - raise ValueError( - ("Incomplete GasOil object, some parameters missing to factory") - ) - return gasoil +def create_water_oil_gas( + params: Optional[Dict[str, float]] = None, fast: bool = False +) -> WaterOilGas: + """Create a WaterOilGas object from a dictionary of parameters - @staticmethod - def create_water_oil_gas( - params: Optional[Dict[str, float]] = None, fast: bool = False - ) -> WaterOilGas: - """Create a WaterOilGas object from a dictionary of parameters + Parameterization (Corey/LET) is inferred from presence + of certain parameters in the dictionary. - Parameterization (Corey/LET) is inferred from presence - of certain parameters in the dictionary. + Check create_water_oil() and create_gas_oil() for lists + of supported parameters (case insensitive) - Check create_water_oil() and create_gas_oil() for lists - of supported parameters (case insensitive) + Params: + params: Dictionary with parameters describing the WaterOilGas object. + fast: If fast-mode should be set for constructed object. + """ + if not params: + params = {} + if not isinstance(params, dict): + raise TypeError("Parameter to create_water_oil_gas must be a dictionary") - Params: - params: Dictionary with parameters describing the WaterOilGas object. - fast: If fast-mode should be set for constructed object. - """ - if not params: - params = {} - if not isinstance(params, dict): - raise TypeError("Parameter to create_water_oil_gas must be a dictionary") + check_deprecated(params) - check_deprecated(params) + # For case insensitiveness, all keys are converted to lower case: + params = {key.lower(): value for (key, value) in params.items()} - # For case insensitiveness, all keys are converted to lower case: - params = {key.lower(): value for (key, value) in params.items()} + wateroil: Optional[WaterOil] + if sufficient_water_oil_params(params, failhard=False): + wateroil = create_water_oil(params, fast=fast) + else: + logger.info("No wateroil parameters. Assuming only gas-oil in wateroilgas") + wateroil = None + + # If the swl in WaterOil was initialized with swlheight, + # ensure that result is passed on to the GasOil object: + if "swl" not in params and "swlheight" in params and wateroil is not None: + params["swl"] = wateroil.swl + + gasoil: Optional[GasOil] + if sufficient_gas_oil_params(params, failhard=False): + gasoil = create_gas_oil(params, fast=fast) + else: + logger.info("No gasoil parameters, assuming two-phase oilwatergas") + gasoil = None + + wog_init_params = slicedict(params, WOG_INIT) + wateroilgas = WaterOilGas(**wog_init_params, fast=fast) + # The wateroilgas __init__ has already created WaterOil and GasOil objects + # but we overwrite the references with newly created ones, this factory function + # must then guarantee that they are compatible. + wateroilgas.wateroil = wateroil # This might be None + wateroilgas.gasoil = gasoil # This might be None + if not wateroilgas.selfcheck(): + raise ValueError(f"Inconsistent WaterOilGas object. Bug? Input was {params}") + return wateroilgas + + +def create_gas_water( + params: Optional[Dict[str, float]] = None, fast: bool = False +) -> GasWater: + """Create a GasWater object. + + Parameterization (Corey/LET) is inferred from presence + of certain parameters in the dictionary. - wateroil: Optional[WaterOil] - if sufficient_water_oil_params(params, failhard=False): - wateroil = PyscalFactory.create_water_oil(params, fast=fast) - else: - logger.info("No wateroil parameters. Assuming only gas-oil in wateroilgas") - wateroil = None + Args: + params: Dictionary with parameters for GasWater. + fast: If fast-mode should be set for constructed object. + """ + if not params: + params = {} + if not isinstance(params, dict): + raise TypeError("Parameter to create_gas_water must be a dictionary") - # If the swl in WaterOil was initialized with swlheight, - # ensure that result is passed on to the GasOil object: - if "swl" not in params and "swlheight" in params and wateroil is not None: - params["swl"] = wateroil.swl + # For case insensitiveness, all keys are converted to lower case: + params = {key.lower(): value for (key, value) in params.items()} - gasoil: Optional[GasOil] - if sufficient_gas_oil_params(params, failhard=False): - gasoil = PyscalFactory.create_gas_oil(params, fast=fast) - else: - logger.info("No gasoil parameters, assuming two-phase oilwatergas") - gasoil = None - - wog_init_params = slicedict(params, WOG_INIT) - wateroilgas = WaterOilGas(**wog_init_params, fast=fast) - # The wateroilgas __init__ has already created WaterOil and GasOil objects - # but we overwrite the references with newly created ones, this factory function - # must then guarantee that they are compatible. - wateroilgas.wateroil = wateroil # This might be None - wateroilgas.gasoil = gasoil # This might be None - if not wateroilgas.selfcheck(): - raise ValueError( - f"Inconsistent WaterOilGas object. Bug? Input was {params}" - ) - return wateroilgas + sufficient_gas_water_params(params, failhard=True) + + gw_init_params = slicedict(params, GW_INIT) + gaswater = GasWater(**gw_init_params, fast=fast) + + # We are using the create_water_oil_gas factory function + # to avoid replicating code. It works because GasWater and + # WaterOilGas internally are very similar. + wog_params = params.copy() + if "sgrw" in params: + wog_params["sorw"] = params["sgrw"] + # Set some dummy parameters for oil: + wog_params["nog"] = 1 + wog_params["now"] = 1 + wog = create_water_oil_gas(wog_params, fast=fast) + assert wog.wateroil is not None + assert wog.gasoil is not None + gaswater.wateroil = wog.wateroil + gaswater.gasoil = wog.gasoil + return gaswater + + +def create_scal_recommendation( + params: Dict[str, Dict[str, float]], + tag: str = "", + h: Optional[float] = None, + fast: bool = False, +) -> SCALrecommendation: + """ + Set up a SCAL recommendation curve set from input as a + dictionary of dictionary. - @staticmethod - def create_gas_water( - params: Optional[Dict[str, float]] = None, fast: bool = False - ) -> GasWater: - """Create a GasWater object. - - Parameterization (Corey/LET) is inferred from presence - of certain parameters in the dictionary. - - Args: - params: Dictionary with parameters for GasWater. - fast: If fast-mode should be set for constructed object. - """ - if not params: - params = {} - if not isinstance(params, dict): - raise TypeError("Parameter to create_gas_water must be a dictionary") - - # For case insensitiveness, all keys are converted to lower case: - params = {key.lower(): value for (key, value) in params.items()} - - sufficient_gas_water_params(params, failhard=True) - - gw_init_params = slicedict(params, GW_INIT) - gaswater = GasWater(**gw_init_params, fast=fast) - - # We are using the create_water_oil_gas factory function - # to avoid replicating code. It works because GasWater and - # WaterOilGas internally are very similar. - wog_params = params.copy() - if "sgrw" in params: - wog_params["sorw"] = params["sgrw"] - # Set some dummy parameters for oil: - wog_params["nog"] = 1 - wog_params["now"] = 1 - wog = PyscalFactory.create_water_oil_gas(wog_params, fast=fast) - assert wog.wateroil is not None - assert wog.gasoil is not None - gaswater.wateroil = wog.wateroil - gaswater.gasoil = wog.gasoil - return gaswater + The keys in in the dictionary *must* be "low", "base" and "high". - @staticmethod - def create_scal_recommendation( - params: Dict[str, Dict[str, float]], - tag: str = "", - h: Optional[float] = None, - fast: bool = False, - ) -> SCALrecommendation: - """ - Set up a SCAL recommendation curve set from input as a - dictionary of dictionary. - - The keys in in the dictionary *must* be "low", "base" and "high". - - The value for "low" must be a new dictionary with saturation - endpoints and LET/Corey parameters, as you would feed it to - the create_water_oil_gas() factory function, and then similarly - for base and high. - - For oil-water only, you may omit the parameters for gas-oil. - A WaterOilGas object for each case is created, but only the - WaterOil part of it will be used. - - For gas-water, a GasWater object is created for each pess, base - and high. - - Args: - params: keys low, base and high. - The value for "low" must be a new dictionary with saturation - endpoints and LET/Corey parameters, as you would feed it to - the create_water_oil_gas() factory function, and then similarly - for base and high. - tag: String to be used as the tag, will end up in comments. - h: Saturation step length - fast: If fast-mode should be set for constructed object. - """ - if not isinstance(params, dict): - raise ValueError("Input must be a dictionary (of dictionaries)") - - # For case insensitiveness, all keys are converted to lower case: - params = {key.lower(): value for (key, value) in params.items()} - - if "low" not in params: - raise ValueError('"low" case not supplied') - if "base" not in params: - raise ValueError('"base" case not supplied') - if "high" not in params: - raise ValueError('"high" case not supplied') - if not ( - isinstance(params["low"], dict) - and isinstance(params["base"], dict) - and isinstance(params["high"], dict) - ): - raise ValueError("All values in parameter dict must be dictionaries") - - check_deprecated(params["low"]) - check_deprecated(params["base"]) - check_deprecated(params["high"]) - - errored = False - if h is not None: - params["low"]["h"] = h - params["base"]["h"] = h - params["high"]["h"] = h + The value for "low" must be a new dictionary with saturation + endpoints and LET/Corey parameters, as you would feed it to + the create_water_oil_gas() factory function, and then similarly + for base and high. - # Check parameter availability, in order to determine phase configuration - gaswater = all(sufficient_gas_water_params(params[case]) for case in params) - gasoil = all(sufficient_gas_oil_params(params[case]) for case in params) - wateroil = all(sufficient_water_oil_params(params[case]) for case in params) + For oil-water only, you may omit the parameters for gas-oil. + A WaterOilGas object for each case is created, but only the + WaterOil part of it will be used. - wog_low: Union[WaterOilGas, GasWater] - wog_base: Union[WaterOilGas, GasWater] - wog_high: Union[WaterOilGas, GasWater] + For gas-water, a GasWater object is created for each pess, base + and high. - if wateroil or gasoil: - try: - wog_low = PyscalFactory.create_water_oil_gas(params["low"], fast=fast) - except ValueError as err: - raise ValueError(f"Problem with low/pess case: {err}") from err - try: - wog_base = PyscalFactory.create_water_oil_gas(params["base"], fast=fast) - except ValueError as err: - raise ValueError(f"Problem with base case: {err}") from err - try: - wog_high = PyscalFactory.create_water_oil_gas(params["high"], fast=fast) - except ValueError as err: - raise ValueError(f"Problem with high/opt case: {err}") from err - elif gaswater: - # Note that gaswater will be True in three-phase configs. - try: - wog_low = PyscalFactory.create_gas_water(params["low"], fast=fast) - except ValueError as err: - raise ValueError(f"Problem with low/pess case: {err}") from err - try: - wog_base = PyscalFactory.create_gas_water(params["base"], fast=fast) - except ValueError as err: - raise ValueError(f"Problem with base case: {err}") from err - try: - wog_high = PyscalFactory.create_gas_water(params["high"], fast=fast) - except ValueError as err: - raise ValueError(f"Problem with high/opt case: {err}") from err + Args: + params: keys low, base and high. + The value for "low" must be a new dictionary with saturation + endpoints and LET/Corey parameters, as you would feed it to + the create_water_oil_gas() factory function, and then similarly + for base and high. + tag: String to be used as the tag, will end up in comments. + h: Saturation step length + fast: If fast-mode should be set for constructed object. + """ + if not isinstance(params, dict): + raise ValueError("Input must be a dictionary (of dictionaries)") + + # For case insensitiveness, all keys are converted to lower case: + params = {key.lower(): value for (key, value) in params.items()} - errored = all(not wog.selfcheck() for wog in [wog_low, wog_base, wog_high]) + if "low" not in params: + raise ValueError('"low" case not supplied') + if "base" not in params: + raise ValueError('"base" case not supplied') + if "high" not in params: + raise ValueError('"high" case not supplied') + if not ( + isinstance(params["low"], dict) + and isinstance(params["base"], dict) + and isinstance(params["high"], dict) + ): + raise ValueError("All values in parameter dict must be dictionaries") - if errored: - raise ValueError("Incomplete SCAL recommendation") + check_deprecated(params["low"]) + check_deprecated(params["base"]) + check_deprecated(params["high"]) - return SCALrecommendation(wog_low, wog_base, wog_high, tag) + errored = False + if h is not None: + params["low"]["h"] = h + params["base"]["h"] = h + params["high"]["h"] = h - @staticmethod - def load_relperm_df( - inputfile: Union[str, pd.DataFrame], sheet_name: Optional[str] = None - ) -> pd.DataFrame: - """Read CSV or XLSX from file and return scal/relperm data - a dataframe. - - Checks validity in SATNUM and CASE columns. - Ensures case-insensitiveness SATNUM, CASE, TAG and COMMENT - - Merges COMMENT into TAG column, as only TAG is picked up downstream. - Adds a prefix "SATNUM " to all tags. - - All strings in CASE column are converted to lowercase. Applies - aliasing in the CASE column so that "pessimistic" and "pess" map to - "low", and "optimistic" and "opt" map to "high". - - Args: - inputfile: Filename for XLSX or CSV file, or a - pandas DataFrame. - sheet_name: Sheet-name, only used when loading xlsx files. - - Returns: - To be handed over to pyscal list factory methods. - Empty dataframe in case of errors (messages will be logged). - """ - if isinstance(inputfile, (str, Path)) and Path(inputfile).is_file(): - tabular_file_format = infer_tabular_file_format(inputfile) - if not tabular_file_format: - raise ValueError( - f"Impossible to infer file format for {inputfile}, not CSV/XLS/XLSX" - ) + # Check parameter availability, in order to determine phase configuration + gaswater = all(sufficient_gas_water_params(params[case]) for case in params) + gasoil = all(sufficient_gas_oil_params(params[case]) for case in params) + wateroil = all(sufficient_water_oil_params(params[case]) for case in params) + + wog_low: Union[WaterOilGas, GasWater] + wog_base: Union[WaterOilGas, GasWater] + wog_high: Union[WaterOilGas, GasWater] + + if wateroil or gasoil: + try: + wog_low = create_water_oil_gas(params["low"], fast=fast) + except ValueError as err: + raise ValueError(f"Problem with low/pess case: {err}") from err + try: + wog_base = create_water_oil_gas(params["base"], fast=fast) + except ValueError as err: + raise ValueError(f"Problem with base case: {err}") from err + try: + wog_high = create_water_oil_gas(params["high"], fast=fast) + except ValueError as err: + raise ValueError(f"Problem with high/opt case: {err}") from err + elif gaswater: + # Note that gaswater will be True in three-phase configs. + try: + wog_low = create_gas_water(params["low"], fast=fast) + except ValueError as err: + raise ValueError(f"Problem with low/pess case: {err}") from err + try: + wog_base = create_gas_water(params["base"], fast=fast) + except ValueError as err: + raise ValueError(f"Problem with base case: {err}") from err + try: + wog_high = create_gas_water(params["high"], fast=fast) + except ValueError as err: + raise ValueError(f"Problem with high/opt case: {err}") from err - if tabular_file_format == "csv" and sheet_name is not None: - logger.warning( - "Sheet name only relevant for XLSX files, ignoring %s", sheet_name + errored = all(not wog.selfcheck() for wog in [wog_low, wog_base, wog_high]) + + if errored: + raise ValueError("Incomplete SCAL recommendation") + + return SCALrecommendation(wog_low, wog_base, wog_high, tag) + + +def load_relperm_df( + inputfile: Union[str, pd.DataFrame], sheet_name: Optional[str] = None +) -> pd.DataFrame: + """Read CSV or XLSX from file and return scal/relperm data + a dataframe. + + Checks validity in SATNUM and CASE columns. + Ensures case-insensitiveness SATNUM, CASE, TAG and COMMENT + + Merges COMMENT into TAG column, as only TAG is picked up downstream. + Adds a prefix "SATNUM " to all tags. + + All strings in CASE column are converted to lowercase. Applies + aliasing in the CASE column so that "pessimistic" and "pess" map to + "low", and "optimistic" and "opt" map to "high". + + Args: + inputfile: Filename for XLSX or CSV file, or a + pandas DataFrame. + sheet_name: Sheet-name, only used when loading xlsx files. + + Returns: + To be handed over to pyscal list factory methods. + Empty dataframe in case of errors (messages will be logged). + """ + if isinstance(inputfile, (str, Path)) and Path(inputfile).is_file(): + tabular_file_format = infer_tabular_file_format(inputfile) + if not tabular_file_format: + raise ValueError( + f"Impossible to infer file format for {inputfile}, not CSV/XLS/XLSX" + ) + + if tabular_file_format == "csv" and sheet_name is not None: + logger.warning( + "Sheet name only relevant for XLSX files, ignoring %s", sheet_name + ) + excel_engines = {"xls": "xlrd", "xlsx": "openpyxl"} + if tabular_file_format != "csv" and sheet_name: + try: + input_df = pd.read_excel( + inputfile, + sheet_name=sheet_name, + engine=excel_engines[tabular_file_format], + ) + logger.info( + "Parsed %s file %s, sheet %s", + tabular_file_format.upper(), + inputfile, + sheet_name, ) - excel_engines = {"xls": "xlrd", "xlsx": "openpyxl"} - if tabular_file_format != "csv" and sheet_name: + except (KeyError, ValueError) as err: + raise ValueError( + f"Non-existing sheet-name {sheet_name} provided." + ) from err + elif tabular_file_format.startswith("xls"): + input_df = pd.read_excel( + inputfile, engine=excel_engines[tabular_file_format] + ) + logger.info("Parsed %s file %s", tabular_file_format.upper(), inputfile) + else: + with open(inputfile, "r", encoding="utf-8") as csvfile: try: - input_df = pd.read_excel( - inputfile, - sheet_name=sheet_name, - engine=excel_engines[tabular_file_format], - ) - logger.info( - "Parsed %s file %s, sheet %s", - tabular_file_format.upper(), - inputfile, - sheet_name, + delimiter = ( + csv.Sniffer() + .sniff(csvfile.read(), [",", ";", "\t"]) # type: ignore + .delimiter ) - except (KeyError, ValueError) as err: - raise ValueError( - f"Non-existing sheet-name {sheet_name} provided." - ) from err - elif tabular_file_format.startswith("xls"): - input_df = pd.read_excel( - inputfile, engine=excel_engines[tabular_file_format] - ) - logger.info("Parsed %s file %s", tabular_file_format.upper(), inputfile) - else: - with open(inputfile, "r", encoding="utf-8") as csvfile: - try: - delimiter = ( - csv.Sniffer() - .sniff(csvfile.read(), [",", ";", "\t"]) # type: ignore - .delimiter - ) - except Exception: - # Sniffer cannot identify the delimiter - # typically on single column csv - delimiter = "," - if delimiter != ",": - raise TypeError( - "Supplied file is not a valid CSV file. " - f"Detected delimiter is '{delimiter}'" - ) - csvfile.seek(0) - input_df = pd.read_csv( - csvfile, skipinitialspace=True, encoding="utf-8" + except Exception: + # Sniffer cannot identify the delimiter + # typically on single column csv + delimiter = "," + if delimiter != ",": + raise TypeError( + "Supplied file is not a valid CSV file. " + f"Detected delimiter is '{delimiter}'" ) - logger.info("Parsed CSV file %s", inputfile) + csvfile.seek(0) + input_df = pd.read_csv(csvfile, skipinitialspace=True, encoding="utf-8") + logger.info("Parsed CSV file %s", inputfile) + + elif isinstance(inputfile, pd.DataFrame): + input_df = inputfile + else: + if isinstance(inputfile, str) and not Path(inputfile).is_file(): + raise IOError("File not found " + str(inputfile)) + raise ValueError("Unsupported argument " + str(inputfile)) + assert isinstance(input_df, pd.DataFrame) + + if input_df.empty: + logger.error("Relperm input dataframe is empty!") + + # We will ignore case on the column names, solved by converting column + # name to uppercase + ignorecasecolumns = ["satnum", "case", "tag", "comment"] + for colname in input_df.columns: + if colname.lower() in ignorecasecolumns: + input_df = input_df.rename({colname: colname.upper()}, axis="columns") + + # Support both TAG and COLUMN (as TAG) + if "COMMENT" in input_df and "TAG" not in input_df: + input_df = input_df.rename({"COMMENT": "TAG"}, axis="columns") + if "COMMENT" in input_df and "TAG" in input_df: + # It might never happen that a user puts both tag and comment in + # the dataframe, but if so, merge them. + input_df["TAG"] = ( + "tag: " + input_df["TAG"] + "; comment: " + input_df["COMMENT"] + ) + # It is tempting to detect any NaN's at this point because that can + # indicate merged cells, which is not supported, but that could + # break optional comment columns which might only be defined on certain lines. + if "SATNUM" not in input_df: + raise ValueError("SATNUM must be present in CSV/XLSX file/dataframe") + + # Delete columns and rows that are all NaNs (this has been observed + # to occur from seemingly empty cells in Excel/LibreOffice and + # has been seen to vary with Pandas/Openpyxl versions in use) + input_df = input_df.dropna(axis="columns", how="all") + input_df = input_df.dropna(axis="index", how="all") + + if input_df["SATNUM"].isna().sum() > 0: + raise ValueError( + "Found not-a-number in the SATNUM column. This could be due to " + "merged cells in XLSX, which is not supported." + ) - elif isinstance(inputfile, pd.DataFrame): - input_df = inputfile - else: - if isinstance(inputfile, str) and not Path(inputfile).is_file(): - raise IOError("File not found " + str(inputfile)) - raise ValueError("Unsupported argument " + str(inputfile)) - assert isinstance(input_df, pd.DataFrame) - - if input_df.empty: - logger.error("Relperm input dataframe is empty!") - - # We will ignore case on the column names, solved by converting column - # name to uppercase - ignorecasecolumns = ["satnum", "case", "tag", "comment"] - for colname in input_df.columns: - if colname.lower() in ignorecasecolumns: - input_df = input_df.rename({colname: colname.upper()}, axis="columns") - - # Support both TAG and COLUMN (as TAG) - if "COMMENT" in input_df and "TAG" not in input_df: - input_df = input_df.rename({"COMMENT": "TAG"}, axis="columns") - if "COMMENT" in input_df and "TAG" in input_df: - # It might never happen that a user puts both tag and comment in - # the dataframe, but if so, merge them. - input_df["TAG"] = ( - "tag: " + input_df["TAG"] + "; comment: " + input_df["COMMENT"] - ) - # It is tempting to detect any NaN's at this point because that can - # indicate merged cells, which is not supported, but that could - # break optional comment columns which might only be defined on certain lines. - if "SATNUM" not in input_df: - raise ValueError("SATNUM must be present in CSV/XLSX file/dataframe") - - # Delete columns and rows that are all NaNs (this has been observed - # to occur from seemingly empty cells in Excel/LibreOffice and - # has been seen to vary with Pandas/Openpyxl versions in use) - input_df = input_df.dropna(axis="columns", how="all") - input_df = input_df.dropna(axis="index", how="all") - - if input_df["SATNUM"].isna().sum() > 0: + # Check that SATNUM's are consecutive and integers: + try: + input_df["SATNUM"] = input_df["SATNUM"].astype(int) + except ValueError as err: + raise ValueError("SATNUM must contain only integers") from err + + if min(input_df["SATNUM"]) != 1: + raise ValueError("SATNUM must start at 1") + + if max(input_df["SATNUM"]) != len(input_df["SATNUM"].unique()): + raise ValueError( + "Missing SATNUMs? Max SATNUM is not equal to number of unique SATNUMS" + ) + if "CASE" not in input_df and len(input_df["SATNUM"].unique()) != len(input_df): + raise ValueError("Non-unique SATNUMs?") + # If we are in a SCAL recommendation setting + if "CASE" in input_df: + # Enforce lower case: + if input_df["CASE"].isna().sum() > 0: raise ValueError( - "Found not-a-number in the SATNUM column. This could be due to " + "Found not-a-number in the CASE column. This could be due " "merged cells in XLSX, which is not supported." ) + input_df["CASE"] = remap_validate_cases(input_df["CASE"].to_numpy()) + + # Add the SATNUM index to the TAG column + if "TAG" not in input_df: + input_df["TAG"] = "" + input_df["TAG"] = input_df["TAG"].fillna(value="") + input_df["TAG"] = "SATNUM " + input_df["SATNUM"].astype(str) + " " + input_df["TAG"] + + # Check if fast is defined as a column + if "fast" in input_df: + logger.warning("Fast mode is not an option for individual SATNUMs") + logger.warning("it is implemented as a global option.") + logger.warning("The fast column in the dataframe will be ignored.") + logger.warning("Use fast=True in the function call instead.") + logger.warning("Fast mode is only available through the Python API") + + # Check that we are able to make something out of the first row: + firstrow = input_df.iloc[0, :] + error: bool = False + wo_ok = sufficient_water_oil_params(firstrow) + go_ok = sufficient_gas_oil_params(firstrow) + gw_ok = sufficient_gas_water_params(firstrow) + if error or not wo_ok and not go_ok and not gw_ok: + raise ValueError( + "Can't make neither WaterOil, GasOil or GasWater from " + "the given data. Check documentation for what you need to supply. " + f"You provided the columns {input_df.columns.to_numpy()}" + ) + logger.info( + "Loaded input data with %s SATNUMS, column %s", + str(len(input_df["SATNUM"].unique())), + str(input_df.columns.to_numpy()), + ) + return input_df.sort_values("SATNUM") + + +def alias_sgrw(params: Dict[str, Any]) -> Dict[str, Any]: + """Allow sgrw as an alias for sorw by remapping a + sgrw value to a sorw value in an incoming dict. + + Will error hard of sorw already exists and is not nan. + + This aliasing is relevant when GasWater is modelled + as three-phase to allow for condensate forming, and mirrors + GasWater.__init__() which performs the same aliasing. + + Args: + params: Keys must be lower case. + """ + if "sgrw" in params: + if "sorw" not in params or pd.isna(params["sorw"]): + params_copy = dict(params) + params_copy["sorw"] = params["sgrw"] + del params_copy["sgrw"] + return params_copy + if np.isclose(params["sgrw"], params["sorw"]): + params_copy = dict(params) + del params_copy["sgrw"] + return params_copy + raise ValueError( + f"sgrw ({params['sgrw']}) must equal sorw ({params['sorw']}) " + "when both are supplied to WaterOil." + ) + return params + + +def remap_validate_cases(casevalues: List[str]) -> List[str]: + """Remap values in the CASE column so that we can use aliases. + + All values are first made lower case, then + "pessimistic" and "pess" are mapped to "low" and + "optimistic" and "opt" are mapped to "high". + + Will raise ValueError if some values are not understood, and + if we don't have exactly three unique values. + + Args: + casevalues: values to remap. + """ + accepted = ["low", "base", "high"] + aliases = { + "pessimistic": "low", + "pess": "low", + "optimistic": "high", + "opt": "high", + } + lowered = [value.lower() for value in casevalues] + remapped = [aliases.get(value, value) for value in lowered] + not_understood = set(remapped) - set(accepted) + if not_understood: + raise ValueError(f"Invalid case values: {not_understood}") + if len(set(remapped)) != len(accepted): + raise ValueError( + f"You must supply low, base AND high, got only {set(remapped)}" + ) + return remapped + - # Check that SATNUM's are consecutive and integers: +def create_scal_recommendation_list( + input_df: pd.DataFrame, h: Optional[float] = None, fast: bool = False +) -> PyscalList: + """Requires SATNUM and CASE to be defined in the input data + + Args: + input_df: Input data, should have been processed + through load_relperm_df(). + h: Saturation step-value + fast: If fast-mode should be set for constructed object + + Returns: + PyscalList, consisting of SCALrecommendation objects + """ + scal_l = PyscalList() + assert isinstance(input_df, pd.DataFrame) + + scalinput = input_df.set_index(["SATNUM", "CASE"]) + + for satnum in scalinput.index.levels[0].to_numpy(): + # load_relperm_df only validates the CASE column for all SATNUMs at + # once, errors for particular SATNUMs are caught here. + if len(scalinput.loc[satnum, :]) > 3: + raise ValueError(f"Too many cases supplied for SATNUM {satnum}") + if len(scalinput.loc[satnum, :]) < 3: + raise ValueError(f"Too few cases supplied for SATNUM {satnum}") try: - input_df["SATNUM"] = input_df["SATNUM"].astype(int) + scal_l.append( + create_scal_recommendation( + scalinput.loc[satnum, :].to_dict(orient="index"), h=h, fast=fast + ) + ) except ValueError as err: - raise ValueError("SATNUM must contain only integers") from err + raise ValueError(f"Error for SATNUM {satnum}: {str(err)}") from err + + return scal_l + + +def create_pyscal_list( + relperm_params_df: pd.DataFrame, h: Optional[float] = None, fast: bool = False +): + """Create WaterOilGas, WaterOil, GasOil or GasWater list + based on what is available + + Args: + relperm_params_df: Input data, should have been processed + through load_relperm_df(). + h: Saturation step-value + fast: If fast-mode should be set for constructed object + + Returns: + PyscalList, consisting of either WaterOil, GasOil or WaterOilGas objects + """ + params = relperm_params_df.iloc[0, :] # first row + water_oil = sufficient_water_oil_params(params) + gas_oil = sufficient_gas_oil_params(params) + gas_water = sufficient_gas_water_params(params) + + if water_oil and gas_oil: + return create_wateroilgas_list(relperm_params_df, h, fast) + if water_oil: + return create_wateroil_list(relperm_params_df, h, fast) + if gas_oil: + return create_gasoil_list(relperm_params_df, h, fast) + if gas_water: + return create_gaswater_list(relperm_params_df, h, fast) + raise ValueError("Could not determine two or three phase from parameters") + + +def create_wateroilgas_list( + relperm_params_df: pd.DataFrame, h: Optional[float] = None, fast: bool = False +) -> PyscalList: + """Create a PyscalList with WaterOilGas objects from + a dataframe + + Args: + relperm_params_df: Input data, should have been processed + through load_relperm_df(). + h: Saturation step-value + fast: If fast-mode should be set for constructed object + + Returns: + PyscalList, consisting of WaterOilGas objects + """ + wogl = PyscalList() + for row_idx, params in relperm_params_df.sort_values("SATNUM").iterrows(): + if h is not None: + params["h"] = h + try: + wogl.append(create_water_oil_gas(params.to_dict(), fast=fast)) + except (AssertionError, ValueError, TypeError) as err: + raise ValueError(f"Error for SATNUM {row_idx + 1}: {str(err)}") from err + return wogl - if min(input_df["SATNUM"]) != 1: - raise ValueError("SATNUM must start at 1") - if max(input_df["SATNUM"]) != len(input_df["SATNUM"].unique()): +def create_wateroil_list( + relperm_params_df: pd.DataFrame, h: Optional[float] = None, fast: bool = False +) -> PyscalList: + """Create a PyscalList with WaterOil objects from + a dataframe + + Args: + relperm_params_df: A valid dataframe with + WaterOil parameters, processed through load_relperm_df() + h: Saturation steplength + fast: If fast-mode should be set for constructed object + + Returns: + PyscalList, consisting of WaterOil objects + """ + wol = PyscalList() + for _, params in relperm_params_df.iterrows(): + if h is not None: + params["h"] = h + try: + wol.append(create_water_oil(params.to_dict(), fast=fast)) + except (AssertionError, ValueError, TypeError) as err: raise ValueError( - "Missing SATNUMs? Max SATNUM is not equal to number of unique SATNUMS" - ) - if "CASE" not in input_df and len(input_df["SATNUM"].unique()) != len(input_df): - raise ValueError("Non-unique SATNUMs?") - # If we are in a SCAL recommendation setting - if "CASE" in input_df: - # Enforce lower case: - if input_df["CASE"].isna().sum() > 0: - raise ValueError( - "Found not-a-number in the CASE column. This could be due " - "merged cells in XLSX, which is not supported." - ) - input_df["CASE"] = PyscalFactory.remap_validate_cases( - input_df["CASE"].to_numpy() - ) + f"Error for SATNUM {params['SATNUM']}: {str(err)}" + ) from err + return wol - # Add the SATNUM index to the TAG column - if "TAG" not in input_df: - input_df["TAG"] = "" - input_df["TAG"] = input_df["TAG"].fillna(value="") - input_df["TAG"] = ( - "SATNUM " + input_df["SATNUM"].astype(str) + " " + input_df["TAG"] - ) - # Check if fast is defined as a column - if "fast" in input_df: - logger.warning("Fast mode is not an option for individual SATNUMs") - logger.warning("it is implemented as a global option.") - logger.warning("The fast column in the dataframe will be ignored.") - logger.warning("Use fast=True in the function call instead.") - logger.warning("Fast mode is only available through the Python API") - - # Check that we are able to make something out of the first row: - firstrow = input_df.iloc[0, :] - error: bool = False - wo_ok = sufficient_water_oil_params(firstrow) - go_ok = sufficient_gas_oil_params(firstrow) - gw_ok = sufficient_gas_water_params(firstrow) - if error or not wo_ok and not go_ok and not gw_ok: +def create_gasoil_list( + relperm_params_df: pd.DataFrame, h: Optional[float] = None, fast: bool = False +) -> PyscalList: + """Create a PyscalList with GasOil objects from + a dataframe + + Args: + relperm_params_df: A valid dataframe with GasOil parameters, + processed through load_relperm_df() + h: Saturation steplength + fast: If fast-mode should be set for constructed object + + Returns: + PyscalList, consisting of GasOil objects + """ + gol = PyscalList() + for _, params in relperm_params_df.iterrows(): + if h is not None: + params["h"] = h + try: + gol.append(create_gas_oil(params.to_dict(), fast=fast)) + except (AssertionError, ValueError, TypeError) as err: raise ValueError( - "Can't make neither WaterOil, GasOil or GasWater from " - "the given data. Check documentation for what you need to supply. " - f"You provided the columns {input_df.columns.to_numpy()}" - ) - logger.info( - "Loaded input data with %s SATNUMS, column %s", - str(len(input_df["SATNUM"].unique())), - str(input_df.columns.to_numpy()), + f"Error for SATNUM {params['SATNUM']}: {str(err)}" + ) from err + return gol + + +def create_gaswater_list( + relperm_params_df: pd.DataFrame, h: Optional[float] = None, fast: bool = False +) -> PyscalList: + """Create a PyscalList with WaterOilGas objects from + a dataframe, to be used for GasWater + + Args: + relperm_params_df: A valid dataframe with GasWater + parameters, processed through load_relperm_df() + h: Saturation steplength + fast: If fast-mode should be set for constructed object + + Returns: + PyscalList, consisting of GasWater objects + """ + gwl = PyscalList() + for _, params in relperm_params_df.iterrows(): + if h is not None: + params["h"] = h + try: + gwl.append(create_gas_water(params.to_dict(), fast=fast)) + except (AssertionError, ValueError, TypeError) as err: + raise ValueError( + f"Error for SATNUM {params['SATNUM']}: {str(err)}" + ) from err + return gwl + + +class PyscalFactory: + """Class for implementing the factory pattern for Pyscal objects + + The factory functions herein can take multiple parameter sets, + determine what kind of parametrization to be used, and set up + the full objects based on these parameters, instead of + explicitly having to call the API for each task. + + Example:: + + wo = WaterOil(sorw=0.05) + wo.add_corey_water(nw=3) + wo.add_corey_oil(now=2) + # is equivalent to: + wo = factory.create_water_oil(dict(sorw=0.05, nw=3, now=2)) + + Parameter names to factory functions are case *insensitive*, while + the add_*() parameters are not. This is because the add_*() parameters + are meant as a Python API, while the factory class is there to aid + users when input is written in a different context, like an Excel + spreadsheet. + """ + + @staticmethod + def create_water_oil( + params: Optional[Dict[str, float]] = None, fast: bool = False + ) -> WaterOil: + warnings.warn( + "PyscalFactory.create_water_oil is deprecated. " + "Please use create_water_oil function instead.", + DeprecationWarning, + ) + return create_water_oil(params, fast) + + @staticmethod + def create_gas_oil( + params: Optional[Dict[str, float]] = None, fast: bool = False + ) -> GasOil: + warnings.warn( + "PyscalFactory.create_gas_oil is deprecated. " + "Please use create_gas_oil function instead.", + DeprecationWarning, + ) + return create_gas_oil(params, fast) + + @staticmethod + def create_water_oil_gas( + params: Optional[Dict[str, float]] = None, fast: bool = False + ) -> WaterOilGas: + warnings.warn( + "PyscalFactory.create_water_oil_gas is deprecated. " + "Please use create_water_oil_gas function instead.", + DeprecationWarning, + ) + return create_water_oil_gas(params, fast) + + @staticmethod + def create_gas_water( + params: Optional[Dict[str, float]] = None, fast: bool = False + ) -> GasWater: + warnings.warn( + "PyscalFactory.create_gas_water is deprecated. " + "Please use create_gas_water function instead.", + DeprecationWarning, + ) + return create_gas_water(params, fast) + + @staticmethod + def create_scal_recommendation( + params: Dict[str, Dict[str, float]], + tag: str = "", + h: Optional[float] = None, + fast: bool = False, + ) -> SCALrecommendation: + warnings.warn( + "PyscalFactory.create_scal_recommendation is deprecated. " + "Please use create_scal_recommendation instead.", + DeprecationWarning, ) - return input_df.sort_values("SATNUM") + return create_scal_recommendation(params, tag, h, fast) + + @staticmethod + def load_relperm_df( + inputfile: Union[str, pd.DataFrame], sheet_name: Optional[str] = None + ) -> pd.DataFrame: + warnings.warn( + "PyscalFactory.load_relperm_df is deprecated. " + "Please use load_relperm_df instead.", + DeprecationWarning, + ) + return load_relperm_df(inputfile, sheet_name) @staticmethod def alias_sgrw(params: Dict[str, Any]) -> Dict[str, Any]: - """Allow sgrw as an alias for sorw by remapping a - sgrw value to a sorw value in an incoming dict. - - Will error hard of sorw already exists and is not nan. - - This aliasing is relevant when GasWater is modelled - as three-phase to allow for condensate forming, and mirrors - GasWater.__init__() which performs the same aliasing. - - Args: - params: Keys must be lower case. - """ - if "sgrw" in params: - if "sorw" not in params or pd.isna(params["sorw"]): - params_copy = dict(params) - params_copy["sorw"] = params["sgrw"] - del params_copy["sgrw"] - return params_copy - if np.isclose(params["sgrw"], params["sorw"]): - params_copy = dict(params) - del params_copy["sgrw"] - return params_copy - raise ValueError( - f"sgrw ({params['sgrw']}) must equal sorw ({params['sorw']}) " - "when both are supplied to WaterOil." - ) - return params + warnings.warn( + "PyscalFactory.alias_sgrw is deprecated. " "Please use alias_sgrw instead.", + DeprecationWarning, + ) + return alias_sgrw(params) @staticmethod def remap_validate_cases(casevalues: List[str]) -> List[str]: - """Remap values in the CASE column so that we can use aliases. - - All values are first made lower case, then - "pessimistic" and "pess" are mapped to "low" and - "optimistic" and "opt" are mapped to "high". - - Will raise ValueError if some values are not understood, and - if we don't have exactly three unique values. - - Args: - casevalues: values to remap. - """ - accepted = ["low", "base", "high"] - aliases = { - "pessimistic": "low", - "pess": "low", - "optimistic": "high", - "opt": "high", - } - lowered = [value.lower() for value in casevalues] - remapped = [aliases.get(value, value) for value in lowered] - not_understood = set(remapped) - set(accepted) - if not_understood: - raise ValueError(f"Invalid case values: {not_understood}") - if len(set(remapped)) != len(accepted): - raise ValueError( - f"You must supply low, base AND high, got only {set(remapped)}" - ) - return remapped + warnings.warn( + "PyscalFactory.remap_validate_cases is deprecated. " + "Please use remap_validate_cases instead.", + DeprecationWarning, + ) + return remap_validate_cases(casevalues) @staticmethod def create_scal_recommendation_list( input_df: pd.DataFrame, h: Optional[float] = None, fast: bool = False ) -> PyscalList: - """Requires SATNUM and CASE to be defined in the input data - - Args: - input_df: Input data, should have been processed - through load_relperm_df(). - h: Saturation step-value - fast: If fast-mode should be set for constructed object - - Returns: - PyscalList, consisting of SCALrecommendation objects - """ - scal_l = PyscalList() - assert isinstance(input_df, pd.DataFrame) - - scalinput = input_df.set_index(["SATNUM", "CASE"]) - - for satnum in scalinput.index.levels[0].to_numpy(): - # load_relperm_df only validates the CASE column for all SATNUMs at - # once, errors for particular SATNUMs are caught here. - if len(scalinput.loc[satnum, :]) > 3: - raise ValueError(f"Too many cases supplied for SATNUM {satnum}") - if len(scalinput.loc[satnum, :]) < 3: - raise ValueError(f"Too few cases supplied for SATNUM {satnum}") - try: - scal_l.append( - PyscalFactory.create_scal_recommendation( - scalinput.loc[satnum, :].to_dict(orient="index"), h=h, fast=fast - ) - ) - except ValueError as err: - raise ValueError(f"Error for SATNUM {satnum}: {str(err)}") from err - - return scal_l + warnings.warn( + "PyscalFactory.create_scal_recommendation_list is deprecated. " + "Please use create_scal_recommendation_list instead.", + DeprecationWarning, + ) + return create_scal_recommendation_list(input_df, h, fast) @staticmethod def create_pyscal_list( relperm_params_df: pd.DataFrame, h: Optional[float] = None, fast: bool = False ): - """Create WaterOilGas, WaterOil, GasOil or GasWater list - based on what is available - - Args: - relperm_params_df: Input data, should have been processed - through load_relperm_df(). - h: Saturation step-value - fast: If fast-mode should be set for constructed object - - Returns: - PyscalList, consisting of either WaterOil, GasOil or WaterOilGas objects - """ - params = relperm_params_df.iloc[0, :] # first row - water_oil = sufficient_water_oil_params(params) - gas_oil = sufficient_gas_oil_params(params) - gas_water = sufficient_gas_water_params(params) - - if water_oil and gas_oil: - return PyscalFactory.create_wateroilgas_list(relperm_params_df, h, fast) - if water_oil: - return PyscalFactory.create_wateroil_list(relperm_params_df, h, fast) - if gas_oil: - return PyscalFactory.create_gasoil_list(relperm_params_df, h, fast) - if gas_water: - return PyscalFactory.create_gaswater_list(relperm_params_df, h, fast) - raise ValueError("Could not determine two or three phase from parameters") + warnings.warn( + "PyscalFactory.create_pyscal_list is deprecated. " + "Please use create_pyscal_list instead.", + DeprecationWarning, + ) + return create_pyscal_list(relperm_params_df, h, fast) @staticmethod def create_wateroilgas_list( relperm_params_df: pd.DataFrame, h: Optional[float] = None, fast: bool = False ) -> PyscalList: - """Create a PyscalList with WaterOilGas objects from - a dataframe - - Args: - relperm_params_df: Input data, should have been processed - through load_relperm_df(). - h: Saturation step-value - fast: If fast-mode should be set for constructed object - - Returns: - PyscalList, consisting of WaterOilGas objects - """ - wogl = PyscalList() - for row_idx, params in relperm_params_df.sort_values("SATNUM").iterrows(): - if h is not None: - params["h"] = h - try: - wogl.append( - PyscalFactory.create_water_oil_gas(params.to_dict(), fast=fast) - ) - except (AssertionError, ValueError, TypeError) as err: - raise ValueError(f"Error for SATNUM {row_idx+1}: {str(err)}") from err - return wogl + warnings.warn( + "PyscalFactory.create_wateroilgas_list is deprecated. " + "Please use create_wateroilgas_list instead.", + DeprecationWarning, + ) + return create_wateroilgas_list(relperm_params_df, h, fast) @staticmethod def create_wateroil_list( relperm_params_df: pd.DataFrame, h: Optional[float] = None, fast: bool = False ) -> PyscalList: - """Create a PyscalList with WaterOil objects from - a dataframe - - Args: - relperm_params_df: A valid dataframe with - WaterOil parameters, processed through load_relperm_df() - h: Saturation steplength - fast: If fast-mode should be set for constructed object - - Returns: - PyscalList, consisting of WaterOil objects - """ - wol = PyscalList() - for _, params in relperm_params_df.iterrows(): - if h is not None: - params["h"] = h - try: - wol.append(PyscalFactory.create_water_oil(params.to_dict(), fast=fast)) - except (AssertionError, ValueError, TypeError) as err: - raise ValueError( - f"Error for SATNUM {params['SATNUM']}: {str(err)}" - ) from err - return wol + warnings.warn( + "PyscalFactory.create_wateroil_list is deprecated. " + "Please use create_wateroil_list instead.", + DeprecationWarning, + ) + return create_wateroil_list(relperm_params_df, h, fast) @staticmethod def create_gasoil_list( relperm_params_df: pd.DataFrame, h: Optional[float] = None, fast: bool = False ) -> PyscalList: - """Create a PyscalList with GasOil objects from - a dataframe - - Args: - relperm_params_df: A valid dataframe with GasOil parameters, - processed through load_relperm_df() - h: Saturation steplength - fast: If fast-mode should be set for constructed object - - Returns: - PyscalList, consisting of GasOil objects - """ - gol = PyscalList() - for _, params in relperm_params_df.iterrows(): - if h is not None: - params["h"] = h - try: - gol.append(PyscalFactory.create_gas_oil(params.to_dict(), fast=fast)) - except (AssertionError, ValueError, TypeError) as err: - raise ValueError( - f"Error for SATNUM {params['SATNUM']}: {str(err)}" - ) from err - return gol + warnings.warn( + "PyscalFactory.create_gasoil_list is deprecated. " + "Please use create_gasoil_list instead.", + DeprecationWarning, + ) + return create_gasoil_list(relperm_params_df, h, fast) @staticmethod def create_gaswater_list( relperm_params_df: pd.DataFrame, h: Optional[float] = None, fast: bool = False ) -> PyscalList: - """Create a PyscalList with WaterOilGas objects from - a dataframe, to be used for GasWater - - Args: - relperm_params_df: A valid dataframe with GasWater - parameters, processed through load_relperm_df() - h: Saturation steplength - fast: If fast-mode should be set for constructed object - - Returns: - PyscalList, consisting of GasWater objects - """ - gwl = PyscalList() - for _, params in relperm_params_df.iterrows(): - if h is not None: - params["h"] = h - try: - gwl.append(PyscalFactory.create_gas_water(params.to_dict(), fast=fast)) - except (AssertionError, ValueError, TypeError) as err: - raise ValueError( - f"Error for SATNUM {params['SATNUM']}: {str(err)}" - ) from err - return gwl + warnings.warn( + "PyscalFactory.create_gaswater_list is deprecated. " + "Please use create_gaswater_list instead.", + DeprecationWarning, + ) + return create_gaswater_list(relperm_params_df, h, fast) def sufficient_water_oil_params(params: dict, failhard: bool = False) -> bool: diff --git a/src/pyscal/pyscalcli.py b/src/pyscal/pyscalcli.py index 02997b72..83c61b9a 100644 --- a/src/pyscal/pyscalcli.py +++ b/src/pyscal/pyscalcli.py @@ -17,8 +17,11 @@ getLogger_pyscal, plotting, ) - -from .factory import PyscalFactory +from pyscal.factory import ( + create_pyscal_list, + create_scal_recommendation_list, + load_relperm_df, +) EPILOG = """ The parameter file should contain a table with at least the column @@ -258,9 +261,7 @@ def pyscal_main( __name__, {"debug": debug, "verbose": verbose, "output": output} ) - parametertable = PyscalFactory.load_relperm_df( - parametertable, sheet_name=sheet_name - ) + parametertable = load_relperm_df(parametertable, sheet_name=sheet_name) assert isinstance(parametertable, pd.DataFrame) logger.debug("Input data:\n%s", parametertable.to_string(index=False)) @@ -282,9 +283,7 @@ def pyscal_main( # 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 - ) + scalrec_list = create_scal_recommendation_list(parametertable, h=delta_s) assert isinstance(scalrec_list[1], SCALrecommendation) if scalrec_list[1].type == WaterOilGas: logger.info( @@ -300,7 +299,7 @@ def pyscal_main( ) wog_list = scalrec_list.interpolate(int_param_wo, None, h=delta_s) else: - wog_list = PyscalFactory.create_pyscal_list( + wog_list = create_pyscal_list( parametertable, h=delta_s ) # can be both water-oil, water-oil-gas, or gas-water diff --git a/tests/test_factory.py b/tests/test_factory.py index 318e41e2..9b6ef3c3 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -10,31 +10,43 @@ from pyscal import ( GasOil, GasWater, - PyscalFactory, + PyscalFactory, # PyscalFactory static functions are deprecated SCALrecommendation, WaterOil, WaterOilGas, - factory, +) +from pyscal.factory import ( + create_gas_oil, + create_gas_water, + create_pyscal_list, + create_scal_recommendation, + create_scal_recommendation_list, + create_water_oil, + create_water_oil_gas, + infer_tabular_file_format, + load_relperm_df, + slicedict, + sufficient_gas_oil_params, + sufficient_gas_water_params, + sufficient_water_oil_params, ) from pyscal.utils.testing import check_table, sat_table_str_ok def test_factory_wateroil(): """Test that we can create curves from dictionaries of parameters""" - pyscal_factory = PyscalFactory() - # Factory refuses to create incomplete defaulted objects. with pytest.raises(ValueError): - pyscal_factory.create_water_oil() + create_water_oil() with pytest.raises(TypeError): # (it must be a dictionary) - pyscal_factory.create_water_oil(swirr=0.01) + create_water_oil(swirr=0.01) with pytest.raises(TypeError): - pyscal_factory.create_water_oil(params="swirr 0.01") + create_water_oil(params="swirr 0.01") - wateroil = pyscal_factory.create_water_oil( + wateroil = create_water_oil( { "swirr": 0.01, "swl": 0.1, @@ -57,7 +69,7 @@ def test_factory_wateroil(): sat_table_str_ok(wateroil.SWOF()) sat_table_str_ok(wateroil.SWFN()) - wateroil = pyscal_factory.create_water_oil( + wateroil = create_water_oil( {"nw": 3, "now": 2, "sorw": 0.1, "krwend": 0.2, "krwmax": 0.5} ) assert isinstance(wateroil, WaterOil) @@ -70,9 +82,7 @@ def test_factory_wateroil(): # Ambiguous works, but we don't guarantee that this results # in LET or Corey. - wateroil = pyscal_factory.create_water_oil( - {"nw": 3, "Lw": 2, "Ew": 2, "Tw": 2, "now": 3} - ) + wateroil = create_water_oil({"nw": 3, "Lw": 2, "Ew": 2, "Tw": 2, "now": 3}) assert "KRW" in wateroil.table assert "Corey" in wateroil.krwcomment or "LET" in wateroil.krwcomment check_table(wateroil.table) @@ -80,9 +90,7 @@ def test_factory_wateroil(): sat_table_str_ok(wateroil.SWFN()) # Mixing Corey and LET - wateroil = pyscal_factory.create_water_oil( - {"Lw": 2, "Ew": 2, "Tw": 2, "krwend": 1, "now": 4} - ) + wateroil = create_water_oil({"Lw": 2, "Ew": 2, "Tw": 2, "krwend": 1, "now": 4}) assert isinstance(wateroil, WaterOil) assert "KRW" in wateroil.table assert wateroil.table["KRW"].max() == 1.0 @@ -91,7 +99,7 @@ def test_factory_wateroil(): sat_table_str_ok(wateroil.SWOF()) sat_table_str_ok(wateroil.SWFN()) - wateroil = pyscal_factory.create_water_oil( + wateroil = create_water_oil( {"Lw": 2, "Ew": 2, "Tw": 2, "Low": 3, "Eow": 3, "Tow": 3, "krwend": 0.5} ) assert isinstance(wateroil, WaterOil) @@ -106,7 +114,7 @@ def test_factory_wateroil(): sat_table_str_ok(wateroil.SWFN()) # Add capillary pressure - wateroil = pyscal_factory.create_water_oil( + wateroil = create_water_oil( { "swl": 0.1, "nw": 1, @@ -126,7 +134,7 @@ def test_factory_wateroil(): sat_table_str_ok(wateroil.SWFN()) # Test that the optional gravity g is picked up: - wateroil = pyscal_factory.create_water_oil( + wateroil = create_water_oil( { "swl": 0.1, "nw": 1, @@ -146,7 +154,7 @@ def test_factory_wateroil(): sat_table_str_ok(wateroil.SWFN()) # Test petrophysical simple J: - wateroil = pyscal_factory.create_water_oil( + wateroil = create_water_oil( { "swl": 0.1, "nw": 1, @@ -166,7 +174,7 @@ def test_factory_wateroil(): sat_table_str_ok(wateroil.SWFN()) # One pc param missing: - wateroil = pyscal_factory.create_water_oil( + wateroil = create_water_oil( { "swl": 0.1, "nw": 1, @@ -185,32 +193,32 @@ def test_fast_mode(): """Test that the fast-flag is passed on to constructed objects Each object's own test code tests the actual effects of the fast flag""" - wateroil = PyscalFactory.create_water_oil({"nw": 2, "now": 2}) + wateroil = create_water_oil({"nw": 2, "now": 2}) assert not wateroil.fast - wateroil = PyscalFactory.create_water_oil({"nw": 2, "now": 2}, fast=True) + wateroil = create_water_oil({"nw": 2, "now": 2}, fast=True) assert wateroil.fast - gasoil = PyscalFactory.create_gas_oil({"ng": 2, "nog": 2}) + gasoil = create_gas_oil({"ng": 2, "nog": 2}) assert not gasoil.fast - gasoil = PyscalFactory.create_gas_oil({"ng": 2, "nog": 2}, fast=True) + gasoil = create_gas_oil({"ng": 2, "nog": 2}, fast=True) assert gasoil.fast - gaswater = PyscalFactory.create_gas_water({"nw": 2, "ng": 2}) + gaswater = create_gas_water({"nw": 2, "ng": 2}) assert not gaswater.gasoil.fast assert not gaswater.wateroil.fast - gaswater = PyscalFactory.create_gas_water({"nw": 2, "ng": 2}, fast=True) + gaswater = create_gas_water({"nw": 2, "ng": 2}, fast=True) assert gaswater.gasoil.fast assert gaswater.wateroil.fast assert gaswater.fast - wateroilgas = PyscalFactory.create_water_oil_gas( + wateroilgas = create_water_oil_gas( {"nw": 2, "now": 2, "ng": 2, "nog": 2}, fast=True ) assert wateroilgas.fast assert wateroilgas.wateroil.fast assert wateroilgas.gasoil.fast - scalrec = PyscalFactory.create_scal_recommendation( + scalrec = create_scal_recommendation( { "low": {"nw": 2, "now": 2, "ng": 2, "nog": 2}, "base": {"nw": 2, "now": 2, "ng": 2, "nog": 2}, @@ -229,8 +237,7 @@ def test_fast_mode(): def test_init_with_swlheight(): """With sufficient parameters, swl will be calculated on the fly when initializing the WaterOil object""" - pyscal_factory = PyscalFactory() - wateroil = pyscal_factory.create_water_oil( + wateroil = create_water_oil( { "swlheight": 200, "nw": 1, @@ -251,11 +258,11 @@ def test_init_with_swlheight(): match="Can't initialize from SWLHEIGHT without sufficient simple-J parameters", ): # This should fail because capillary pressure parameters are not provided. - pyscal_factory.create_water_oil({"swlheight": 200, "nw": 1, "now": 1}) + create_water_oil({"swlheight": 200, "nw": 1, "now": 1}) # swcr must be larger than swl: with pytest.raises(ValueError, match="lower than computed swl"): - pyscal_factory.create_water_oil( + create_water_oil( { "swlheight": 200, "nw": 1, @@ -272,7 +279,7 @@ def test_init_with_swlheight(): # swlheight must be positive: with pytest.raises(ValueError, match="swlheight must be larger than zero"): - pyscal_factory.create_water_oil( + create_water_oil( { "swlheight": -200, "nw": 1, @@ -287,7 +294,7 @@ def test_init_with_swlheight(): ) # If swcr is large enough, it will pass: - wateroil = pyscal_factory.create_water_oil( + wateroil = create_water_oil( { "swlheight": 200, "nw": 1, @@ -306,7 +313,7 @@ def test_init_with_swlheight(): assert "swcr=0.3" in wateroil.SWOF() # Test that GasWater also can be initialized with swlheight: - gaswater = pyscal_factory.create_gas_water( + gaswater = create_gas_water( { "swlheight": 200, "nw": 1, @@ -329,7 +336,7 @@ def test_init_with_swlheight(): with pytest.raises( ValueError, match="Can't initialize from SWLHEIGHT without sufficient simple-J" ): - pyscal_factory.create_water_oil( + create_water_oil( { "swlheight": 200, "nw": 1, @@ -347,23 +354,20 @@ def test_relative_swcr(): """swcr can be initialized relative to swl Relevant when swl is initialized from swlheight.""" - pyscal_factory = PyscalFactory() with pytest.raises(ValueError, match="swl must be provided"): - pyscal_factory.create_water_oil( - {"swcr_add": 0.1, "nw": 1, "now": 1, "swirr": 0.01} - ) + create_water_oil({"swcr_add": 0.1, "nw": 1, "now": 1, "swirr": 0.01}) with pytest.raises(ValueError, match="swcr and swcr_add at the same time"): - pyscal_factory.create_water_oil( + create_water_oil( {"swcr_add": 0.1, "swcr": 0.1, "swl": 0.1, "nw": 1, "now": 1, "swirr": 0.01} ) - wateroil = pyscal_factory.create_water_oil( + wateroil = create_water_oil( {"swcr_add": 0.1, "swl": 0.1, "nw": 1, "now": 1, "swirr": 0.01} ) assert wateroil.swcr == 0.2 # Test when relative to swlheight: - wateroil = pyscal_factory.create_water_oil( + wateroil = create_water_oil( { "swlheight": 200, "swcr_add": 0.01, @@ -380,7 +384,7 @@ def test_relative_swcr(): assert np.isclose(wateroil.swl, 0.02480395) assert np.isclose(wateroil.swcr, 0.02480395 + 0.01) - gaswater = pyscal_factory.create_gas_water( + gaswater = create_gas_water( { "swlheight": 200, "nw": 1, @@ -401,8 +405,7 @@ def test_relative_swcr(): def test_ambiguity(): """Test how the factory handles ambiguity between Corey and LET parameters""" - pyscal_factory = PyscalFactory() - wateroil = pyscal_factory.create_water_oil( + wateroil = create_water_oil( {"swl": 0.1, "nw": 10, "Lw": 1, "Ew": 1, "Tw": 1, "now": 2, "h": 0.1, "no": 2} ) # Corey is picked here. @@ -412,20 +415,19 @@ def test_ambiguity(): def test_factory_gasoil(): """Test that we can create curves from dictionaries of parameters""" - pyscal_factory = PyscalFactory() # Factory refuses to create incomplete defaulted objects. with pytest.raises(ValueError): - pyscal_factory.create_gas_oil() + create_gas_oil() with pytest.raises(TypeError): # (this must be a dictionary) - pyscal_factory.create_gas_oil(swirr=0.01) + create_gas_oil(swirr=0.01) with pytest.raises(TypeError): - pyscal_factory.create_gas_oil(params="swirr 0.01") + create_gas_oil(params="swirr 0.01") - gasoil = pyscal_factory.create_gas_oil( + gasoil = create_gas_oil( {"swirr": 0.01, "swl": 0.1, "sgcr": 0.05, "tag": "Good sand", "ng": 1, "nog": 2} ) assert isinstance(gasoil, GasOil) @@ -441,7 +443,7 @@ def test_factory_gasoil(): assert "Corey krog" in sgof assert "Zero capillary pressure" in sgof - gasoil = pyscal_factory.create_gas_oil( + gasoil = create_gas_oil( {"ng": 1.2, "nog": 2, "krgend": 0.8, "krgmax": 0.9, "kroend": 0.6} ) sgof = gasoil.SGOF() @@ -450,16 +452,14 @@ def test_factory_gasoil(): assert "krgend=0.8" in sgof check_table(gasoil.table) - gasoil = pyscal_factory.create_gas_oil({"ng": 1.3, "Log": 2, "Eog": 2, "Tog": 2}) + gasoil = create_gas_oil({"ng": 1.3, "Log": 2, "Eog": 2, "Tog": 2}) sgof = gasoil.SGOF() check_table(gasoil.table) sat_table_str_ok(sgof) assert "Corey krg" in sgof assert "LET krog" in sgof - gasoil = pyscal_factory.create_gas_oil( - {"Lg": 1, "Eg": 1, "Tg": 1, "Log": 2, "Eog": 2, "Tog": 2} - ) + gasoil = create_gas_oil({"Lg": 1, "Eg": 1, "Tg": 1, "Log": 2, "Eog": 2, "Tog": 2}) sgof = gasoil.SGOF() sat_table_str_ok(sgof) check_table(gasoil.table) @@ -472,7 +472,7 @@ def test_factory_wog_gascondensate(): is the same as wateroilgas, except that we allow for aliasing in sgrw=sorw for the underlying WaterOil object, and also there are additional parameters sgro and kromax for GasOil.""" - wcg = PyscalFactory.create_water_oil_gas( + wcg = create_water_oil_gas( { "nw": 2, "now": 3, @@ -504,19 +504,19 @@ def test_factory_wog_gascondensate(): # Different sorw and sgrw is a hard error: with pytest.raises(ValueError, match="must equal"): - PyscalFactory.create_water_oil_gas( + create_water_oil_gas( {"nw": 2, "now": 3, "ng": 1, "nog": 2, "sorw": 0.2, "sgrw": 0.1, "swl": 0.1} ) # But it will pass if they both are supplied but are equal: - wcg_2 = PyscalFactory.create_water_oil_gas( + wcg_2 = create_water_oil_gas( {"nw": 2, "now": 3, "ng": 1, "nog": 2, "sorw": 0.2, "sgrw": 0.2, "swl": 0.1} ) assert "sorw=0.2" in wcg_2.SWOF() # kroend higher than kromax is an error: with pytest.raises(AssertionError): - PyscalFactory.create_water_oil_gas( + create_water_oil_gas( { "nw": 2, "now": 3, @@ -533,8 +533,7 @@ def test_factory_wog_gascondensate(): def test_factory_go_gascondensate(): """In gas condensate problems, the sgro and kromax parameters are relevant""" - pyscal_factory = PyscalFactory() - gasoil = pyscal_factory.create_gas_oil( + gasoil = create_gas_oil( { "sgro": 0.1, "sgcr": 0.1, @@ -559,20 +558,19 @@ def test_factory_go_gascondensate(): def test_factory_gaswater(): """Test that we can create gas-water curves from dictionaries of parameters""" - pyscal_factory = PyscalFactory() # Factory refuses to create incomplete defaulted objects. with pytest.raises(ValueError): - pyscal_factory.create_gas_water() + create_gas_water() with pytest.raises(TypeError): - pyscal_factory.create_gas_water(swirr=0.01) + create_gas_water(swirr=0.01) with pytest.raises(TypeError): # (it must be a dictionary) - pyscal_factory.create_gas_water(params="swirr 0.01") + create_gas_water(params="swirr 0.01") - gaswater = pyscal_factory.create_gas_water( + gaswater = create_gas_water( { "swirr": 0.01, "swl": 0.03, @@ -608,7 +606,7 @@ def test_factory_gaswater(): assert "ng=2" in sgfn assert "gassy sand" in sgfn - gaswater = pyscal_factory.create_gas_water({"lg": 1, "eg": 1, "tg": 1, "nw": 3}) + gaswater = create_gas_water({"lg": 1, "eg": 1, "tg": 1, "nw": 3}) sgfn = gaswater.SGFN() swfn = gaswater.SWFN() @@ -620,20 +618,19 @@ def test_factory_gaswater(): def test_factory_wateroilgas(): """Test creating discrete cases of WaterOilGas from factory""" - pyscal_factory = PyscalFactory() # Factory refuses to create incomplete defaulted objects. with pytest.raises(ValueError): - pyscal_factory.create_water_oil_gas() + create_water_oil_gas() with pytest.raises(TypeError): # (this must be a dictionary) - pyscal_factory.create_water_oil_gas(swirr=0.01) + create_water_oil_gas(swirr=0.01) with pytest.raises(TypeError): - pyscal_factory.create_water_oil_gas(params="swirr 0.01") + create_water_oil_gas(params="swirr 0.01") - wog = pyscal_factory.create_water_oil_gas({"nw": 2, "now": 3, "ng": 1, "nog": 2.5}) + wog = create_water_oil_gas({"nw": 2, "now": 3, "ng": 1, "nog": 2.5}) swof = wog.SWOF() sgof = wog.SGOF() sat_table_str_ok(swof) # sgof code works for swof also currently @@ -646,7 +643,7 @@ def test_factory_wateroilgas(): check_table(wog.wateroil.table) # Some users will mess up lower vs upper case: - wog = pyscal_factory.create_water_oil_gas({"NW": 2, "NOW": 3, "NG": 1, "nog": 2.5}) + wog = create_water_oil_gas({"NW": 2, "NOW": 3, "NG": 1, "nog": 2.5}) swof = wog.SWOF() sgof = wog.SGOF() sat_table_str_ok(swof) # sgof code works for swof also currently @@ -657,22 +654,21 @@ def test_factory_wateroilgas(): assert "Corey krow" in swof # Mangling data - wateroil = pyscal_factory.create_water_oil_gas({"nw": 2, "now": 3, "ng": 1}) + wateroil = create_water_oil_gas({"nw": 2, "now": 3, "ng": 1}) assert wateroil.gasoil is None def test_factory_wateroilgas_deprecated_krowgend(): """Using long-time deprecated krowend and krogend will fail""" with pytest.raises(ValueError): - PyscalFactory.create_water_oil_gas( + create_water_oil_gas( {"nw": 2, "now": 3, "ng": 1, "nog": 2.5, "krowend": 0.6, "krogend": 0.7} ) def test_factory_wateroilgas_wo(): """Test making only wateroil through the wateroilgas factory""" - pyscal_factory = PyscalFactory() - wog = pyscal_factory.create_water_oil_gas( + wog = create_water_oil_gas( {"nw": 2, "now": 3, "kroend": 0.5, "sorw": 0.04, "swcr": 0.1} ) swof = wog.SWOF() @@ -687,9 +683,8 @@ def test_factory_wateroilgas_wo(): def test_factory_wateroil_paleooil(caplog): """Test making a WaterOil object with socr different from sorw.""" - pyscal_factory = PyscalFactory() sorw = 0.09 - wateroil = pyscal_factory.create_water_oil( + wateroil = create_water_oil( { "nw": 2, "now": 3, @@ -707,7 +702,7 @@ def test_factory_wateroil_paleooil(caplog): # If socr is close to sorw, socr is reset to sorw. for socr in [sorw - 1e-9, sorw, sorw + 1e-9]: - wo_socrignored = pyscal_factory.create_water_oil( + wo_socrignored = create_water_oil( {"nw": 2, "now": 3, "kroend": 0.5, "sorw": 0.09, "socr": socr, "swcr": 0.1} ) swof = wo_socrignored.SWOF() @@ -719,7 +714,7 @@ def test_factory_wateroil_paleooil(caplog): assert "socr was close to sorw, reset to sorw" in caplog.text with pytest.raises(ValueError, match="socr must be equal to or larger than sorw"): - pyscal_factory.create_water_oil( + create_water_oil( { "nw": 2, "now": 3, @@ -738,12 +733,12 @@ def test_load_relperm_df(tmp_path, caplog): scalfile_xls = testdir / "data/scal-pc-input-example.xlsx" - scaldata = PyscalFactory.load_relperm_df(scalfile_xls) + scaldata = load_relperm_df(scalfile_xls) with pytest.raises(IOError): - PyscalFactory.load_relperm_df("not-existing-file") + load_relperm_df("not-existing-file") with pytest.raises(ValueError, match="Non-existing sheet-name"): - PyscalFactory.load_relperm_df(scalfile_xls, sheet_name="foo") + load_relperm_df(scalfile_xls, sheet_name="foo") assert "SATNUM" in scaldata assert "CASE" in scaldata @@ -751,53 +746,53 @@ def test_load_relperm_df(tmp_path, caplog): os.chdir(tmp_path) scaldata.to_csv("scal-input.csv") - scaldata_fromcsv = PyscalFactory.load_relperm_df("scal-input.csv") + scaldata_fromcsv = load_relperm_df("scal-input.csv") assert "CASE" in scaldata_fromcsv assert not scaldata_fromcsv.empty - scaldata_fromdf = PyscalFactory.load_relperm_df(scaldata_fromcsv) + scaldata_fromdf = load_relperm_df(scaldata_fromcsv) assert "CASE" in scaldata_fromdf assert "SATNUM" in scaldata_fromdf assert len(scaldata_fromdf) == len(scaldata_fromcsv) == len(scaldata) - scaldata_fromcsv = PyscalFactory.load_relperm_df("scal-input.csv", sheet_name="foo") + scaldata_fromcsv = load_relperm_df("scal-input.csv", sheet_name="foo") assert "Sheet name only relevant for XLSX files, ignoring foo" in caplog.text with pytest.raises(ValueError, match="Unsupported argument"): - PyscalFactory.load_relperm_df({"foo": 1}) + load_relperm_df({"foo": 1}) # Perturb the dataframe, this should trigger errors with pytest.raises(ValueError): - PyscalFactory.load_relperm_df(scaldata.drop("SATNUM", axis="columns")) + load_relperm_df(scaldata.drop("SATNUM", axis="columns")) wrongsatnums = scaldata.copy() wrongsatnums["SATNUM"] = wrongsatnums["SATNUM"] * 2 with pytest.raises(ValueError): - PyscalFactory.load_relperm_df(wrongsatnums) + load_relperm_df(wrongsatnums) wrongsatnums = scaldata.copy() wrongsatnums["SATNUM"] = wrongsatnums["SATNUM"].astype(int) wrongsatnums = wrongsatnums[wrongsatnums["SATNUM"] > 2] with pytest.raises(ValueError): - PyscalFactory.load_relperm_df(wrongsatnums) + load_relperm_df(wrongsatnums) wrongcases = scaldata.copy() wrongcases["CASE"] = wrongcases["CASE"] + "ffooo" with pytest.raises(ValueError): - PyscalFactory.load_relperm_df(wrongcases) + load_relperm_df(wrongcases) with pytest.raises(ValueError): - PyscalFactory.load_relperm_df(scaldata.drop(["Lw", "Lg"], axis="columns")) + load_relperm_df(scaldata.drop(["Lw", "Lg"], axis="columns")) # Insert a NaN, this replicates what happens if cells are merged mergedcase = scaldata.copy() mergedcase.loc[3, "SATNUM"] = np.nan with pytest.raises(ValueError): - PyscalFactory.load_relperm_df(mergedcase) + load_relperm_df(mergedcase) relpermfile_xls = testdir / "data/relperm-input-example.xlsx" - relpermdata = PyscalFactory.load_relperm_df(relpermfile_xls) + relpermdata = load_relperm_df(relpermfile_xls) assert "TAG" in relpermdata assert "SATNUM" in relpermdata assert "satnum" not in relpermdata # always converted to upper-case assert len(relpermdata) == 3 - swof_str = PyscalFactory.create_pyscal_list(relpermdata, h=0.2).SWOF() + swof_str = create_pyscal_list(relpermdata, h=0.2).SWOF() assert "Åre 1.8" in swof_str assert "SATNUM 2" in swof_str # Autogenerated in SWOF, generated by factory assert "SATNUM 3" in swof_str @@ -806,21 +801,21 @@ def test_load_relperm_df(tmp_path, caplog): # Make a dummy text file Path("dummy.txt").write_text("foo\nbar, com", encoding="utf8") with pytest.raises(ValueError): - PyscalFactory.load_relperm_df("dummy.txt") + load_relperm_df("dummy.txt") # Make an empty csv file Path("empty.csv").write_text("", encoding="utf8") with pytest.raises(ValueError, match="Impossible to infer file format"): - PyscalFactory.load_relperm_df("empty.csv") + load_relperm_df("empty.csv") with pytest.raises(ValueError, match="SATNUM must be present"): - PyscalFactory.load_relperm_df(pd.DataFrame()) + load_relperm_df(pd.DataFrame()) # Merge tags and comments if both are supplied Path("tagandcomment.csv").write_text( "SATNUM,nw,now,tag,comment\n1,1,1,a-tag,a-comment", encoding="utf8" ) - tagandcomment_df = PyscalFactory.load_relperm_df("tagandcomment.csv") + tagandcomment_df = load_relperm_df("tagandcomment.csv") assert ( tagandcomment_df["TAG"].to_numpy()[0] == "SATNUM 1 tag: a-tag; comment: a-comment" @@ -829,26 +824,26 @@ def test_load_relperm_df(tmp_path, caplog): # Missing SATNUMs: Path("wrongsatnum.csv").write_text("SATNUM,nw,now\n1,1,1\n3,1,1", encoding="utf8") with pytest.raises(ValueError, match="Missing SATNUMs?"): - PyscalFactory.load_relperm_df("wrongsatnum.csv") + load_relperm_df("wrongsatnum.csv") # Missing SATNUMs, like merged cells: Path("mergedcells.csv").write_text( "CASE,SATNUM,nw,now\nlow,,1,1\nlow,1,2,2\nlow,,3,32", encoding="utf8" ) with pytest.raises(ValueError, match="Found not-a-number"): - PyscalFactory.load_relperm_df("mergedcells.csv") + load_relperm_df("mergedcells.csv") # Missing SATNUMs, like merged cells: Path("mergedcellscase.csv").write_text( "CASE,SATNUM,nw,now\n,1,1,1\nlow,1,2,2\n,1,3,32", encoding="utf8" ) with pytest.raises(ValueError, match="Found not-a-number"): - PyscalFactory.load_relperm_df("mergedcellscase.csv") + load_relperm_df("mergedcellscase.csv") # Not valid CSV file Path("notvalidcsv.csv").write_text("SATNUM;nw;now\n1;1;1", encoding="utf-8") with pytest.raises(TypeError, match="Supplied file is not a valid CSV file"): - PyscalFactory.load_relperm_df("notvalidcsv.csv") + load_relperm_df("notvalidcsv.csv") def test_many_nans(): @@ -863,9 +858,7 @@ def test_many_nans(): {"SATNUM": np.nan, "nw": np.nan, "now": np.nan, "Unnamed: 15": np.nan}, ] ) - wateroil_list = PyscalFactory.create_pyscal_list( - PyscalFactory.load_relperm_df(nanframe) - ) + wateroil_list = create_pyscal_list(load_relperm_df(nanframe)) assert len(wateroil_list) == 1 sat_table_str_ok(wateroil_list.SWOF()) @@ -884,7 +877,7 @@ def test_xls_factory(): for (satnum, _), params in scalinput.iterrows(): assert satnum - wog = PyscalFactory.create_water_oil_gas(params.to_dict()) + wog = create_water_oil_gas(params.to_dict()) swof = wog.SWOF() assert "LET krw" in swof assert "LET krow" in swof @@ -899,15 +892,15 @@ def test_create_scal_recommendation_list(): """Test the factory methods for making scalrecommendation lists""" testdir = Path(__file__).absolute().parent scalfile_xls = testdir / "data/scal-pc-input-example.xlsx" - scaldata = PyscalFactory.load_relperm_df(scalfile_xls) + scaldata = load_relperm_df(scalfile_xls) - scalrec_list = PyscalFactory.create_scal_recommendation_list(scaldata) + scalrec_list = create_scal_recommendation_list(scaldata) assert len(scalrec_list) == 3 assert scalrec_list.pyscaltype == SCALrecommendation # Erroneous input: with pytest.raises(ValueError, match="Too many cases supplied for SATNUM 2"): - PyscalFactory.create_scal_recommendation_list( + create_scal_recommendation_list( pd.DataFrame( columns=["SATNUM", "CASE", "NW", "NOW"], data=[ @@ -922,7 +915,7 @@ def test_create_scal_recommendation_list(): ) ) with pytest.raises(ValueError, match="Too few cases supplied for SATNUM 2"): - PyscalFactory.create_scal_recommendation_list( + create_scal_recommendation_list( pd.DataFrame( columns=["SATNUM", "CASE", "NW", "NOW"], data=[ @@ -940,27 +933,27 @@ def test_create_pyscal_list(): """Test the factory methods for making pyscal lists""" testdir = Path(__file__).absolute().parent scalfile_xls = testdir / "data/scal-pc-input-example.xlsx" - scaldata = PyscalFactory.load_relperm_df(scalfile_xls) + scaldata = load_relperm_df(scalfile_xls) basecasedata = scaldata[scaldata["CASE"] == "base"].reset_index() - relpermlist = PyscalFactory.create_pyscal_list(basecasedata) + relpermlist = create_pyscal_list(basecasedata) assert len(relpermlist) == 3 assert relpermlist.pyscaltype == WaterOilGas - wo_list = PyscalFactory.create_pyscal_list( + wo_list = create_pyscal_list( basecasedata.drop(["Lg", "Eg", "Tg", "Log", "Eog", "Tog"], axis="columns") ) assert len(wo_list) == 3 assert wo_list.pyscaltype == WaterOil - go_list = PyscalFactory.create_pyscal_list( + go_list = create_pyscal_list( basecasedata.drop(["Lw", "Ew", "Tw", "Low", "Eow", "Tow"], axis="columns") ) assert len(go_list) == 3 assert go_list.pyscaltype == GasOil - gw_list = PyscalFactory.create_pyscal_list( + gw_list = create_pyscal_list( basecasedata.drop(["Low", "Eow", "Tow", "Log", "Eog", "Tog"], axis="columns") ) @@ -970,24 +963,21 @@ def test_create_pyscal_list(): with pytest.raises( ValueError, match="Could not determine two or three phase from parameters" ): - PyscalFactory.create_pyscal_list( - basecasedata.drop(["Ew", "Eg"], axis="columns") - ) + create_pyscal_list(basecasedata.drop(["Ew", "Eg"], axis="columns")) def test_scalrecommendation(): """Testing making SCAL rec from dict of dict.""" - pyscal_factory = PyscalFactory() scal_input = { "low": {"nw": 2, "now": 4, "ng": 1, "nog": 2}, "BASE": {"nw": 3, "NOW": 3, "ng": 1, "nog": 2}, "high": {"nw": 4, "now": 2, "ng": 1, "nog": 3}, } - scal = pyscal_factory.create_scal_recommendation(scal_input) + scal = create_scal_recommendation(scal_input) with pytest.raises(ValueError, match="Input must be a dict"): - pyscal_factory.create_scal_recommendation("low") + create_scal_recommendation("low") # (not supported yet to make WaterOil only..) interp = scal.interpolate(-0.5) @@ -1003,12 +993,12 @@ def test_scalrecommendation(): copy1 = scal_input.copy() del copy1[case] with pytest.raises(ValueError): - pyscal_factory.create_scal_recommendation(copy1) + create_scal_recommendation(copy1) go_only = scal_input.copy() del go_only["low"]["now"] del go_only["low"]["nw"] - gasoil = pyscal_factory.create_scal_recommendation(go_only) + gasoil = create_scal_recommendation(go_only) assert gasoil.low.wateroil is None assert gasoil.base.wateroil is not None assert gasoil.high.wateroil is not None @@ -1020,31 +1010,30 @@ def test_scalrecommendation(): basehigh = scal_input.copy() del basehigh["low"] with pytest.raises(ValueError, match='"low" case not supplied'): - pyscal_factory.create_scal_recommendation(basehigh) + create_scal_recommendation(basehigh) baselow = scal_input.copy() del baselow["high"] with pytest.raises(ValueError, match='"high" case not supplied'): - pyscal_factory.create_scal_recommendation(baselow) + create_scal_recommendation(baselow) with pytest.raises( ValueError, match="All values in parameter dict must be dictionaries" ): - pyscal_factory.create_scal_recommendation( + create_scal_recommendation( {"low": [1, 2], "base": {"swl": 0.1}, "high": {"swl": 0.1}} ) def test_scalrecommendation_gaswater(): """Testing making SCAL rec from dict of dict for gaswater input""" - pyscal_factory = PyscalFactory() scal_input = { "low": {"nw": 2, "ng": 1}, "BASE": {"nw": 3, "ng": 1}, "high": {"nw": 4, "ng": 1}, } - scal = pyscal_factory.create_scal_recommendation(scal_input, h=0.2) + scal = create_scal_recommendation(scal_input, h=0.2) interp = scal.interpolate(-0.5, h=0.2) sat_table_str_ok(interp.SWFN()) sat_table_str_ok(interp.SGFN()) @@ -1060,7 +1049,7 @@ def test_xls_scalrecommendation(): scalinput = pd.read_excel(xlsxfile, engine="openpyxl").set_index(["SATNUM", "CASE"]) for satnum in scalinput.index.levels[0].to_numpy(): dictofdict = scalinput.loc[satnum, :].to_dict(orient="index") - scalrec = PyscalFactory.create_scal_recommendation(dictofdict) + scalrec = create_scal_recommendation(dictofdict) scalrec.interpolate(+0.5) @@ -1072,7 +1061,7 @@ def test_no_gasoil(): Make sure we fail in that case.""" dframe = pd.DataFrame(columns=["SATNUM", "NOW", "NG"], data=[[1, 2, 2]]) with pytest.raises(ValueError): - PyscalFactory.load_relperm_df(dframe) + load_relperm_df(dframe) def test_check_deprecated_krowgend(): @@ -1082,20 +1071,20 @@ def test_check_deprecated_krowgend(): After pyscal 0.8 presence of krogend and krowend is a ValueError """ with pytest.raises(ValueError): - PyscalFactory.create_water_oil({"swl": 0.1, "nw": 2, "now": 2, "krowend": 0.4}) + create_water_oil({"swl": 0.1, "nw": 2, "now": 2, "krowend": 0.4}) with pytest.raises(ValueError): - PyscalFactory.create_gas_oil({"swl": 0.1, "ng": 2, "nog": 2, "krogend": 0.4}) + create_gas_oil({"swl": 0.1, "ng": 2, "nog": 2, "krogend": 0.4}) # If krogend and kroend are both present, krogend is to be silently ignored # (random columns are in general accepted and ignored by pyscal) - gasoil = PyscalFactory.create_gas_oil( + gasoil = create_gas_oil( {"swl": 0.1, "ng": 2, "nog": 2, "krogend": 0.4, "kroend": 0.3} ) assert gasoil.table["KROG"].max() == 0.3 - wateroil = PyscalFactory.create_water_oil( + wateroil = create_water_oil( {"swl": 0.1, "nw": 2, "now": 2, "krowend": 0.4, "kroend": 0.3} ) assert wateroil.table["KROW"].max() == 0.3 @@ -1155,12 +1144,10 @@ def test_gensatfunc(): """Test how the external tool gen_satfunc could use the factory functionality""" - pyscal_factory = PyscalFactory() - # Example config line for gen_satfunc: conf_line_pc = "RELPERM 4 2 1 3 2 1 0.15 0.10 0.5 20 100 0.2 0.22 -0.5 30" - wateroil = pyscal_factory.create_water_oil(parse_gensatfuncline(conf_line_pc)) + wateroil = create_water_oil(parse_gensatfuncline(conf_line_pc)) swof = wateroil.SWOF() assert "0.17580" in swof # krw at sw=0.65 assert "0.0127" in swof # krow at sw=0.65 @@ -1168,7 +1155,7 @@ def test_gensatfunc(): assert "2.0669" in swof # pc at swl conf_line_min = "RELPERM 1 2 3 1 2 3 0.1 0.15 0.5 20" - wateroil = pyscal_factory.create_water_oil(parse_gensatfuncline(conf_line_min)) + wateroil = create_water_oil(parse_gensatfuncline(conf_line_min)) swof = wateroil.SWOF() assert "Zero capillary pressure" in swof @@ -1178,9 +1165,7 @@ def test_gensatfunc(): # sigma_costau is missing here: conf_line_almost_pc = "RELPERM 4 2 1 3 2 1 0.15 0.10 0.5 20 100 0.2 0.22 -0.5" - wateroil = pyscal_factory.create_water_oil( - parse_gensatfuncline(conf_line_almost_pc) - ) + wateroil = create_water_oil(parse_gensatfuncline(conf_line_almost_pc)) swof = wateroil.SWOF() # The factory will not recognize the normalized J-function # when costau is missing. Any error message would be the responsibility @@ -1192,28 +1177,28 @@ def test_sufficient_params(): """Test the utility functions to determine whether WaterOil and GasOil object have sufficient parameters""" - assert factory.sufficient_gas_oil_params({"ng": 0, "nog": 0}) + assert sufficient_gas_oil_params({"ng": 0, "nog": 0}) # If it looks like the user meant to create GasOil, but only provided # data for krg, then might error hard. If the user did not provide # any data for GasOil, then the code returns False with pytest.raises(ValueError): - factory.sufficient_gas_oil_params({"ng": 0}, failhard=True) - assert not factory.sufficient_gas_oil_params({"ng": 0}, failhard=False) - assert not factory.sufficient_gas_oil_params({}) + sufficient_gas_oil_params({"ng": 0}, failhard=True) + assert not sufficient_gas_oil_params({"ng": 0}, failhard=False) + assert not sufficient_gas_oil_params({}) with pytest.raises(ValueError): - factory.sufficient_gas_oil_params({"lg": 0}, failhard=True) - assert not factory.sufficient_gas_oil_params({"lg": 0}, failhard=False) - assert factory.sufficient_gas_oil_params( + sufficient_gas_oil_params({"lg": 0}, failhard=True) + assert not sufficient_gas_oil_params({"lg": 0}, failhard=False) + assert sufficient_gas_oil_params( {"lg": 0, "eg": 0, "Tg": 0, "log": 0, "eog": 0, "tog": 0} ) - assert factory.sufficient_water_oil_params({"nw": 0, "now": 0}) + assert sufficient_water_oil_params({"nw": 0, "now": 0}) with pytest.raises(ValueError): - factory.sufficient_water_oil_params({"nw": 0}, failhard=True) - assert not factory.sufficient_water_oil_params({}) + sufficient_water_oil_params({"nw": 0}, failhard=True) + assert not sufficient_water_oil_params({}) with pytest.raises(ValueError): - factory.sufficient_water_oil_params({"lw": 0}, failhard=True) - assert factory.sufficient_water_oil_params( + sufficient_water_oil_params({"lw": 0}, failhard=True) + assert sufficient_water_oil_params( {"lw": 0, "ew": 0, "Tw": 0, "low": 0, "eow": 0, "tow": 0} ) @@ -1221,23 +1206,23 @@ def test_sufficient_params(): def test_sufficient_params_gaswater(): """Test that we can detect sufficient parameters for gas-water only""" - assert factory.sufficient_gas_water_params({"nw": 0, "ng": 0}) - assert not factory.sufficient_gas_water_params({"nw": 0, "nog": 0}) - assert factory.sufficient_gas_water_params( + assert sufficient_gas_water_params({"nw": 0, "ng": 0}) + assert not sufficient_gas_water_params({"nw": 0, "nog": 0}) + assert sufficient_gas_water_params( {"lw": 0, "ew": 0, "tw": 0, "lg": 0, "eg": 0, "tg": 0} ) - assert not factory.sufficient_gas_water_params({"lw": 0}) - assert not factory.sufficient_gas_water_params({"lw": 0, "lg": 0}) - assert not factory.sufficient_gas_water_params({"lw": 0, "lg": 0}) + assert not sufficient_gas_water_params({"lw": 0}) + assert not sufficient_gas_water_params({"lw": 0, "lg": 0}) + assert not sufficient_gas_water_params({"lw": 0, "lg": 0}) with pytest.raises(ValueError): - factory.sufficient_gas_water_params({"lw": 0}, failhard=True) + sufficient_gas_water_params({"lw": 0}, failhard=True) with pytest.raises(ValueError): - factory.sufficient_gas_water_params({"nw": 3}, failhard=True) + sufficient_gas_water_params({"nw": 3}, failhard=True) - assert factory.sufficient_gas_water_params({"lw": 0, "ew": 0, "tw": 0, "ng": 0}) - assert factory.sufficient_gas_water_params({"lg": 0, "eg": 0, "tg": 0, "nw": 0}) - assert not factory.sufficient_gas_water_params({"lg": 0, "eg": 0, "tg": 0, "ng": 0}) + assert sufficient_gas_water_params({"lw": 0, "ew": 0, "tw": 0, "ng": 0}) + assert sufficient_gas_water_params({"lg": 0, "eg": 0, "tg": 0, "nw": 0}) + assert not sufficient_gas_water_params({"lg": 0, "eg": 0, "tg": 0, "ng": 0}) def test_case_aliasing(): @@ -1251,8 +1236,8 @@ def test_case_aliasing(): [1, "opt", 3, 1, 1, 1], ], ) - relperm_data = PyscalFactory.load_relperm_df(dframe) - PyscalFactory.create_scal_recommendation_list(relperm_data, h=0.2).interpolate(-0.4) + relperm_data = load_relperm_df(dframe) + create_scal_recommendation_list(relperm_data, h=0.2).interpolate(-0.4) dframe = pd.DataFrame( columns=["SATNUM", "CASE", "Nw", "Now", "Ng", "Nog"], data=[ @@ -1261,11 +1246,11 @@ def test_case_aliasing(): [1, "optiMISTIc", 3, 1, 1, 1], ], ) - relperm_data = PyscalFactory.load_relperm_df(dframe) - PyscalFactory.create_scal_recommendation_list(relperm_data, h=0.2).interpolate(-0.4) + relperm_data = load_relperm_df(dframe) + create_scal_recommendation_list(relperm_data, h=0.2).interpolate(-0.4) with pytest.raises(ValueError): - PyscalFactory.load_relperm_df( + load_relperm_df( pd.DataFrame( columns=["SATNUM", "CASE", "Nw", "Now", "Ng", "Nog"], data=[ @@ -1278,7 +1263,7 @@ def test_case_aliasing(): # Ambigous data: with pytest.raises(ValueError): - amb = PyscalFactory.load_relperm_df( + amb = load_relperm_df( pd.DataFrame( columns=["SATNUM", "CASE", "Nw", "Now", "Ng", "Nog"], data=[ @@ -1289,11 +1274,11 @@ def test_case_aliasing(): ], ) ) - PyscalFactory.create_scal_recommendation_list(amb) + create_scal_recommendation_list(amb) # Missing a case with pytest.raises(ValueError): - PyscalFactory.load_relperm_df( + load_relperm_df( pd.DataFrame( columns=["SATNUM", "CASE", "Nw", "Now", "Ng", "Nog"], data=[[1, "base", 3, 1, 1, 1], [1, "optIMIstiC", 3, 1, 1, 1]], @@ -1301,7 +1286,7 @@ def test_case_aliasing(): ) # Missing a case with pytest.raises(ValueError): - PyscalFactory.load_relperm_df( + load_relperm_df( pd.DataFrame( columns=["SATNUM", "CASE", "Nw", "Now", "Ng", "Nog"], data=[[1, "base", 3, 1, 1, 1]], @@ -1311,8 +1296,8 @@ def test_case_aliasing(): def test_socr_via_dframe(): """Test that the "socr" parameter is picked up from a dataframe/xlsx input""" - p_list = PyscalFactory.create_pyscal_list( - PyscalFactory.load_relperm_df( + p_list = create_pyscal_list( + load_relperm_df( pd.DataFrame( columns=["SATNUM", "Nw", "Now", "socr"], data=[[1, 2, 2, 0.5]], @@ -1343,15 +1328,15 @@ def test_swirr_partially_missing(tmp_path): [2, 3, 3, 0.1, np.nan, np.nan, np.nan, np.nan, np.nan], ], ) - relperm_data = PyscalFactory.load_relperm_df(dframe) - p_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) + relperm_data = load_relperm_df(dframe) + p_list = create_pyscal_list(relperm_data, h=0.2) assert "a=2, b=-2" in p_list[1].pccomment assert p_list[2].pccomment == "" os.chdir(tmp_path) dframe.to_excel("partial_pc.xlsx") - relperm_data_via_xlsx = PyscalFactory.load_relperm_df("partial_pc.xlsx") - p_list = PyscalFactory.create_pyscal_list(relperm_data_via_xlsx, h=0.2) + relperm_data_via_xlsx = load_relperm_df("partial_pc.xlsx") + p_list = create_pyscal_list(relperm_data_via_xlsx, h=0.2) assert "a=2, b=-2" in p_list[1].pccomment assert p_list[2].pccomment == "" @@ -1363,8 +1348,8 @@ def test_corey_let_mix(): columns=["SATNUM", "Nw", "Now", "Lw", "Ew", "Tw", "Ng", "Nog"], data=[[1, 2, 2, np.nan, np.nan, np.nan, 1, 1], [2, np.nan, 3, 1, 1, 1, 2, 2]], ) - relperm_data = PyscalFactory.load_relperm_df(dframe) - p_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) + relperm_data = load_relperm_df(dframe) + p_list = create_pyscal_list(relperm_data, h=0.2) swof1 = p_list.pyscal_list[0].SWOF() swof2 = p_list.pyscal_list[1].SWOF() assert "Corey krw" in swof1 @@ -1377,41 +1362,37 @@ def test_infer_tabular_file_format(tmp_path, caplog): """Test code that infers the fileformat of files with tabular data""" testdir = Path(__file__).absolute().parent assert ( - factory.infer_tabular_file_format(testdir / "data/scal-pc-input-example.xlsx") - == "xlsx" + infer_tabular_file_format(testdir / "data/scal-pc-input-example.xlsx") == "xlsx" ) assert ( - factory.infer_tabular_file_format( - str(testdir / "data/scal-pc-input-example.xlsx") - ) + infer_tabular_file_format(str(testdir / "data/scal-pc-input-example.xlsx")) == "xlsx" ) assert ( - factory.infer_tabular_file_format(testdir / "data/scal-pc-input-example.xls") - == "xls" + infer_tabular_file_format(testdir / "data/scal-pc-input-example.xls") == "xls" ) os.chdir(tmp_path) pd.DataFrame([{"SATNUM": 1, "NW": 2}]).to_csv("some.csv", index=False) - assert factory.infer_tabular_file_format("some.csv") == "csv" + assert infer_tabular_file_format("some.csv") == "csv" Path("empty.csv").write_text("", encoding="utf8") - assert factory.infer_tabular_file_format("empty.csv") == "" + assert infer_tabular_file_format("empty.csv") == "" # Ensure Pandas's error message got through: assert "No columns to parse from file" in caplog.text # We don't want ISO-8859 files, ensure we fail norw_chars = "Dette,er,en,CSV,fil\nmed,iso-8859:,æ,ø,å" Path("iso8859.csv").write_bytes(norw_chars.encode("iso-8859-1")) - assert factory.infer_tabular_file_format("iso8859.csv") == "" + assert infer_tabular_file_format("iso8859.csv") == "" # Providing an error that this error was due to ISO-8859 and # nothing else is deemed too hard. Path("utf8.csv").write_bytes(norw_chars.encode("utf-8")) - assert factory.infer_tabular_file_format("utf8.csv") == "csv" + assert infer_tabular_file_format("utf8.csv") == "csv" # Write some random bytes to a file, this should with very # little probability give a valid xlsx/xls/csv file. Path("wrong.csv").write_bytes(os.urandom(100)) - assert factory.infer_tabular_file_format("wrong.csv") == "" + assert infer_tabular_file_format("wrong.csv") == "" @pytest.mark.parametrize( @@ -1426,4 +1407,67 @@ def test_infer_tabular_file_format(tmp_path, caplog): ) def test_slicedict(orig_dict, keylist, expected_dict): """Test that dictionaries can be sliced for subsets""" - assert factory.slicedict(orig_dict, keylist) == expected_dict + assert slicedict(orig_dict, keylist) == expected_dict + + +def test_deprecated_functions(): + """Test the deprecated functions""" + + # Load file used for testing + testdir = Path(__file__).absolute().parent + scalfile_xls = testdir / "data/scal-pc-input-example.xlsx" + + with pytest.warns(DeprecationWarning): + PyscalFactory.create_water_oil({"nw": 2, "now": 2}) + + with pytest.warns(DeprecationWarning): + PyscalFactory.create_gas_oil({"ng": 2, "nog": 2}) + + with pytest.warns(DeprecationWarning): + PyscalFactory.create_water_oil_gas( + { + "nw": 2, + "now": 2, + "ng": 2, + "nog": 2, + } + ) + + with pytest.warns(DeprecationWarning): + PyscalFactory.create_gas_water({"nw": 2, "ng": 2}) + + with pytest.warns(DeprecationWarning): + PyscalFactory.create_scal_recommendation( + { + "low": {"nw": 2, "now": 2, "ng": 2, "nog": 2}, + "base": {"nw": 2, "now": 2, "ng": 2, "nog": 2}, + "high": {"nw": 2, "now": 2, "ng": 2, "nog": 2}, + } + ) + + with pytest.warns(DeprecationWarning): + scaldata = PyscalFactory.load_relperm_df(scalfile_xls) + + with pytest.warns(DeprecationWarning): + PyscalFactory.create_scal_recommendation_list(scaldata) + + with pytest.warns(DeprecationWarning): + PyscalFactory.create_pyscal_list(scaldata) + + with pytest.warns(DeprecationWarning): + PyscalFactory.remap_validate_cases(scaldata["CASE"].to_numpy()) + + with pytest.warns(DeprecationWarning): + PyscalFactory.create_wateroilgas_list(scaldata) + + with pytest.warns(DeprecationWarning): + PyscalFactory.create_wateroil_list(scaldata) + + with pytest.warns(DeprecationWarning): + PyscalFactory.create_gasoil_list(scaldata) + + with pytest.warns(DeprecationWarning): + PyscalFactory.create_gaswater_list(scaldata) + + with pytest.warns(DeprecationWarning): + PyscalFactory.alias_sgrw({"sgrw": 0.1}) diff --git a/tests/test_interactive_plots.py b/tests/test_interactive_plots.py index e0785623..077a84cc 100644 --- a/tests/test_interactive_plots.py +++ b/tests/test_interactive_plots.py @@ -10,7 +10,8 @@ import pytest from matplotlib import pyplot -from pyscal import GasOil, GasWater, PyscalFactory, WaterOil, utils +from pyscal import GasOil, GasWater, WaterOil, utils +from pyscal.factory import create_scal_recommendation from .test_scalrecommendation import BASE_SAMPLE_LET, HIGH_SAMPLE_LET, LOW_SAMPLE_LET @@ -305,7 +306,7 @@ def test_SCAL_interpolation(): """Demonstration of interpolation between LET curves, 2x2 subplot""" matplotlib.style.use("ggplot") - rec = PyscalFactory.create_scal_recommendation( + rec = create_scal_recommendation( {"low": LOW_SAMPLE_LET, "base": BASE_SAMPLE_LET, "high": HIGH_SAMPLE_LET}, "FOO", h=0.001, diff --git a/tests/test_pyscallist.py b/tests/test_pyscallist.py index b4f2fe0e..65300d07 100644 --- a/tests/test_pyscallist.py +++ b/tests/test_pyscallist.py @@ -9,12 +9,16 @@ from pyscal import ( GasOil, GasWater, - PyscalFactory, PyscalList, SCALrecommendation, WaterOil, WaterOilGas, ) +from pyscal.factory import ( + create_pyscal_list, + create_scal_recommendation_list, + load_relperm_df, +) from pyscal.utils.testing import sat_table_str_ok try: @@ -72,17 +76,15 @@ def test_load_scalrec(): """Load a SATNUM range from xlsx""" testdir = Path(__file__).absolute().parent - scalrec_data = PyscalFactory.load_relperm_df( - testdir / "data/scal-pc-input-example.xlsx" - ) + scalrec_data = load_relperm_df(testdir / "data/scal-pc-input-example.xlsx") # Also check that we can read the old excel format - scalrec_data_legacy_xls = PyscalFactory.load_relperm_df( + scalrec_data_legacy_xls = load_relperm_df( testdir / "data/scal-pc-input-example.xls" ) pd.testing.assert_frame_equal(scalrec_data, scalrec_data_legacy_xls) - scalrec_list = PyscalFactory.create_scal_recommendation_list(scalrec_data) + scalrec_list = create_scal_recommendation_list(scalrec_data) wog_list = scalrec_list.interpolate(-0.3) with pytest.raises((AssertionError, ValueError)): @@ -127,8 +129,8 @@ def test_load_scalrec(): # Test slicing the scalrec to base, this is relevant for API usage. base_data = scalrec_data[scalrec_data["CASE"] == "base"].drop("CASE", axis=1) - PyscalFactory.load_relperm_df(base_data) # Ensure no errors. - pyscal_list = PyscalFactory.create_pyscal_list(base_data) + load_relperm_df(base_data) # Ensure no errors. + pyscal_list = create_pyscal_list(base_data) assert "LET" in pyscal_list.build_eclipse_data(family=1) @@ -136,10 +138,8 @@ def test_df(): """Test dataframe dumps""" testdir = Path(__file__).absolute().parent - scalrec_data = PyscalFactory.load_relperm_df( - testdir / "data/scal-pc-input-example.xlsx" - ) - scalrec_list = PyscalFactory.create_scal_recommendation_list(scalrec_data) + scalrec_data = load_relperm_df(testdir / "data/scal-pc-input-example.xlsx") + scalrec_list = create_scal_recommendation_list(scalrec_data) wog_list = scalrec_list.interpolate(-0.3) # Test dataframe dumps: @@ -201,8 +201,8 @@ def test_df(): # WaterOil list input_dframe = pd.DataFrame(columns=["SATNUM", "Nw", "Now"], data=[[1, 2, 2]]) - relperm_data = PyscalFactory.load_relperm_df(input_dframe) - wo_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) + relperm_data = load_relperm_df(input_dframe) + wo_list = create_pyscal_list(relperm_data, h=0.2) dframe = wo_list.df() assert "SW" in dframe assert "KRW" in dframe @@ -214,8 +214,8 @@ def test_df(): # GasOil list input_dframe = pd.DataFrame(columns=["SATNUM", "Ng", "Nog"], data=[[1, 2, 2]]) - relperm_data = PyscalFactory.load_relperm_df(input_dframe) - go_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) + relperm_data = load_relperm_df(input_dframe) + go_list = create_pyscal_list(relperm_data, h=0.2) dframe = go_list.df() assert "SG" in dframe assert "KRG" in dframe @@ -236,10 +236,8 @@ def test_load_scalrec_tags(): """Test tag handling for a SCAL recommendation with SATNUM range""" testdir = Path(__file__).absolute().parent - scalrec_data = PyscalFactory.load_relperm_df( - testdir / "data/scal-pc-input-example.xlsx" - ) - scalrec_list = PyscalFactory.create_scal_recommendation_list(scalrec_data) + scalrec_data = load_relperm_df(testdir / "data/scal-pc-input-example.xlsx") + scalrec_list = create_scal_recommendation_list(scalrec_data) wog_list = scalrec_list.interpolate(-1) @@ -285,7 +283,7 @@ def test_load_scalrec_tags(): "SAT3 base case", "SAT3 optimistic", ] - scalrec_list2 = PyscalFactory.create_scal_recommendation_list(scalrec_data) + scalrec_list2 = create_scal_recommendation_list(scalrec_data) swof = scalrec_list2.interpolate(-0.5, h=0.2).SWOF() assert swof.count("SCAL recommendation") == 3 for tag in scalrec_data["TAG"]: @@ -297,10 +295,8 @@ def test_deprecated_dump_to_file(tmpdir): functionality is deprecated in pyscallist""" testdir = Path(__file__).absolute().parent - relperm_data = PyscalFactory.load_relperm_df( - testdir / "data/relperm-input-example.xlsx" - ) - pyscal_list = PyscalFactory.create_pyscal_list(relperm_data) + relperm_data = load_relperm_df(testdir / "data/relperm-input-example.xlsx") + pyscal_list = create_pyscal_list(relperm_data) fam1 = pyscal_list.build_eclipse_data(family=1) sat_table_str_ok(fam1) @@ -344,9 +340,7 @@ def test_capillary_pressure(): ], data=[[1, 1, 1, 0.05, 3.6, -3.5, 0.25, 15, 150]], ) - pyscal_list = PyscalFactory.create_pyscal_list( - PyscalFactory.load_relperm_df(dframe) - ) + pyscal_list = create_pyscal_list(load_relperm_df(dframe)) swof = pyscal_list.build_eclipse_data(family=1) assert "Simplified J-function" in swof assert "petrophysical" not in swof @@ -365,9 +359,7 @@ def test_capillary_pressure(): ], data=[[1, 1, 1, 0.05, 3.6, -3.5, 0.25, 15, 150]], ) - pyscal_list = PyscalFactory.create_pyscal_list( - PyscalFactory.load_relperm_df(dframe) - ) + pyscal_list = create_pyscal_list(load_relperm_df(dframe)) swof = pyscal_list.build_eclipse_data(family=1) assert "Simplified J-function" in swof assert "petrophysical" in swof @@ -386,9 +378,7 @@ def test_capillary_pressure(): ], data=[[1, 1, 1, 0.05, 3.6, -3.5, 0.25, 15, 30]], ) - pyscal_list = PyscalFactory.create_pyscal_list( - PyscalFactory.load_relperm_df(dframe) - ) + pyscal_list = create_pyscal_list(load_relperm_df(dframe)) swof = pyscal_list.build_eclipse_data(family=1) assert "normalized J-function" in swof assert "sigma_costau" in swof @@ -414,9 +404,7 @@ def test_swl_from_height(): columns=df_columns, data=[[1, 1, 1, np.nan, 300, 0.00, 3.6, -3.5, 0.25, 15, 150]], ) - pyscal_list = PyscalFactory.create_pyscal_list( - PyscalFactory.load_relperm_df(dframe) - ) + pyscal_list = create_pyscal_list(load_relperm_df(dframe)) # Mix swlheight init and direct swl-init: assert np.isclose(pyscal_list[1].swl, 0.157461) @@ -427,9 +415,7 @@ def test_swl_from_height(): [2, 1, 1, 0.3, np.nan, 0.00, 3.6, -3.5, 0.25, 15, 150], ], ) - pyscal_list = PyscalFactory.create_pyscal_list( - PyscalFactory.load_relperm_df(dframe) - ) + pyscal_list = create_pyscal_list(load_relperm_df(dframe)) assert np.isclose(pyscal_list[1].swl, 0.157461) assert np.isclose(pyscal_list[2].swl, 0.3) @@ -439,7 +425,7 @@ def test_swl_from_height(): data=[[1, 1, 1, 0.3, 300, 0.00, 3.6, -3.5, 0.25, 15, 150]], ) with pytest.raises(ValueError): - PyscalFactory.create_pyscal_list(PyscalFactory.load_relperm_df(dframe)) + create_pyscal_list(load_relperm_df(dframe)) # WaterOilGas (gasoil is also dependant on the computed swl) df_wog_columns = [ @@ -460,9 +446,7 @@ def test_swl_from_height(): columns=df_wog_columns, data=[[1, 1, 1, 2, 2, 300, 0.00, 3.6, -3.5, 0.25, 15, 150]], ) - pyscal_list = PyscalFactory.create_pyscal_list( - PyscalFactory.load_relperm_df(dframe) - ) + pyscal_list = create_pyscal_list(load_relperm_df(dframe)) assert np.isclose(pyscal_list[1].wateroil.swl, 0.157461) assert np.isclose(pyscal_list[1].gasoil.swl, 0.157461) @@ -483,9 +467,7 @@ def test_swl_from_height(): columns=df_gw_columns, data=[[1, 2, 3, 300, 0.00, 3.6, -3.5, 0.25, 15, 150]], ) - pyscal_list = PyscalFactory.create_pyscal_list( - PyscalFactory.load_relperm_df(dframe) - ) + pyscal_list = create_pyscal_list(load_relperm_df(dframe)) assert np.isclose(pyscal_list[1].wateroil.swl, 0.157461) assert np.isclose(pyscal_list[1].swl, 0.157461) @@ -501,9 +483,7 @@ def test_twophase_scal_wateroil(): [1, "opt", 3, 3, "thetag"], ], ) - pyscal_list = PyscalFactory.create_scal_recommendation_list( - PyscalFactory.load_relperm_df(dframe) - ) + pyscal_list = create_scal_recommendation_list(load_relperm_df(dframe)) pyscal_list = pyscal_list.interpolate(-0.5) # This works, but provides an error message for Gas. @@ -525,9 +505,7 @@ def test_twophase_scal_gasoil(): [1, "opt", 3, 3, "thetag"], ], ) - pyscal_list = PyscalFactory.create_scal_recommendation_list( - PyscalFactory.load_relperm_df(dframe) - ) + pyscal_list = create_scal_recommendation_list(load_relperm_df(dframe)) pyscal_list = pyscal_list.interpolate(-0.5) sgof = pyscal_list.build_eclipse_data(family=1) @@ -542,9 +520,7 @@ def test_gaswater(): columns=["SATNUM", "NW", "NG", "TAG"], data=[[1, 2, 2, "thetag"], [2, 3, 3, "othertag"]], ) - pyscal_list = PyscalFactory.create_pyscal_list( - PyscalFactory.load_relperm_df(dframe), h=0.1 - ) + pyscal_list = create_pyscal_list(load_relperm_df(dframe), h=0.1) assert pyscal_list.pyscaltype == GasWater dump = pyscal_list.build_eclipse_data(family=2) assert "SATNUM 2" in dump @@ -581,9 +557,7 @@ def test_gaswater_scal(): [1, "opt", 4, 4, "thetag"], ], ) - pyscal_list = PyscalFactory.create_scal_recommendation_list( - PyscalFactory.load_relperm_df(dframe), h=0.1 - ) + pyscal_list = create_scal_recommendation_list(load_relperm_df(dframe), h=0.1) assert pyscal_list.pyscaltype == SCALrecommendation dump = pyscal_list.interpolate(-1).build_eclipse_data(family=2) @@ -597,56 +571,56 @@ def test_explicit_df(): dframe = pd.DataFrame(columns=["satnum"], data=[[1], [2]]) with pytest.raises(ValueError): # SATNUM column must be upper case, or should we allow lowercase?? - relperm_data = PyscalFactory.load_relperm_df(dframe) + relperm_data = load_relperm_df(dframe) dframe = pd.DataFrame(columns=["SATNUM"], data=[[0], [1]]) with pytest.raises(ValueError): # SATNUM must start at 1. - relperm_data = PyscalFactory.load_relperm_df(dframe) + relperm_data = load_relperm_df(dframe) dframe = pd.DataFrame(columns=["SATNUM"], data=[[1], ["foo"]]) with pytest.raises(ValueError): # SATNUM must contain only integers - relperm_data = PyscalFactory.load_relperm_df(dframe) + relperm_data = load_relperm_df(dframe) dframe = pd.DataFrame( columns=["SATNUM", "nw", "now"], data=[[1.01, 1, 1], [2.01, 1, 1]] ) # This one will pass, as these can be converted to ints - relperm_data = PyscalFactory.load_relperm_df(dframe) + relperm_data = load_relperm_df(dframe) dframe = pd.DataFrame( columns=["SATNUM", "nw", "now"], data=[[1.01, 1, 1], [1.3, 1, 1]] ) with pytest.raises(ValueError): # complains about non-uniqueness in SATNUM - relperm_data = PyscalFactory.load_relperm_df(dframe) + relperm_data = load_relperm_df(dframe) dframe = pd.DataFrame(columns=["SATNUM"], data=[[1], [2]]) with pytest.raises(ValueError): # Not enough data - relperm_data = PyscalFactory.load_relperm_df(dframe) + relperm_data = load_relperm_df(dframe) # Minimal amount of data: dframe = pd.DataFrame(columns=["SATNUM", "Nw", "Now"], data=[[1, 2, 2]]) - relperm_data = PyscalFactory.load_relperm_df(dframe) - p_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) + relperm_data = load_relperm_df(dframe) + p_list = create_pyscal_list(relperm_data, h=0.2) p_list.build_eclipse_data(family=1) with pytest.raises(ValueError): p_list.build_eclipse_data(family=2) # Case insensitive for parameters dframe = pd.DataFrame(columns=["SATNUM", "nw", "now"], data=[[1, 2, 2]]) - relperm_data = PyscalFactory.load_relperm_df(dframe) - p_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) + relperm_data = load_relperm_df(dframe) + p_list = create_pyscal_list(relperm_data, h=0.2) p_list.build_eclipse_data(family=1) # Minimal wateroilgas dframe = pd.DataFrame( columns=["SATNUM", "Nw", "Now", "ng", "nOG"], data=[[1, 2, 2, 2, 2]] ) - relperm_data = PyscalFactory.load_relperm_df(dframe) - p_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) + relperm_data = load_relperm_df(dframe) + p_list = create_pyscal_list(relperm_data, h=0.2) relperm_str = p_list.build_eclipse_data(family=1) assert "SWOF" in relperm_str assert "SGOF" in relperm_str @@ -670,8 +644,8 @@ def test_explicit_df(): ], data=[[1, 0.1, 2, 2, 2, 2, 1.5, -0.5, 0.1, 100, 300]], ) - relperm_data = PyscalFactory.load_relperm_df(dframe) - p_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) + relperm_data = load_relperm_df(dframe) + p_list = create_pyscal_list(relperm_data, h=0.2) relperm_str = p_list.build_eclipse_data(family=1) assert "SWOF" in relperm_str assert "Simplified" in relperm_str # Bad practice, testing for stuff in comments @@ -684,8 +658,8 @@ def test_comments_df(): columns=["SATNUM", "tag", "Nw", "Now", "ng", "nOG"], data=[[1, "thisisacomment", 2, 2, 2, 2]], ) - relperm_data = PyscalFactory.load_relperm_df(dframe) - p_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) + relperm_data = load_relperm_df(dframe) + p_list = create_pyscal_list(relperm_data, h=0.2) relperm_str = p_list.build_eclipse_data(family=1) assert "thisisacomment" in relperm_str @@ -694,8 +668,8 @@ def test_comments_df(): columns=["SATNUM", "comment", "Nw", "Now", "ng", "nOG"], data=[[1, "thisisacomment", 2, 2, 2, 2]], ) - relperm_data = PyscalFactory.load_relperm_df(dframe) - p_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) + relperm_data = load_relperm_df(dframe) + p_list = create_pyscal_list(relperm_data, h=0.2) relperm_str = p_list.build_eclipse_data(family=1) assert relperm_str.count("thisisacomment") == 2 @@ -704,8 +678,8 @@ def test_comments_df(): columns=["SAtnUM", "coMMent", "Nw", "Now", "ng", "nOG"], data=[[1, "thisisacomment", 2, 2, 2, 2]], ) - relperm_data = PyscalFactory.load_relperm_df(dframe) - p_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) + relperm_data = load_relperm_df(dframe) + p_list = create_pyscal_list(relperm_data, h=0.2) assert p_list.build_eclipse_data(family=1).count("thisisacomment") == 2 # UTF-8 stuff: @@ -713,8 +687,8 @@ def test_comments_df(): columns=["SATNUM", "TAG", "Nw", "Now", "Ng", "Nog"], data=[[1, "æøå", 2, 2, 2, 2]], ) - relperm_data = PyscalFactory.load_relperm_df(dframe) - p_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) + relperm_data = load_relperm_df(dframe) + p_list = create_pyscal_list(relperm_data, h=0.2) assert p_list.build_eclipse_data(family=1).count("æøå") == 2 @@ -727,34 +701,34 @@ def test_error_messages_pr_satnum(): columns=["SATNUM", "nw", "now"], data=[[1, 1, 1], [2, "foo", 1]] ) with pytest.raises(ValueError, match="SATNUM 2"): - PyscalFactory.create_pyscal_list(dframe, h=0.2) + create_pyscal_list(dframe, h=0.2) dframe = pd.DataFrame( columns=["SATNUM", "nw", "now"], data=[[1, 1, 1], [2, np.nan, 1]] ) with pytest.raises(ValueError, match="SATNUM 2"): - PyscalFactory.create_pyscal_list(dframe, h=0.2) + create_pyscal_list(dframe, h=0.2) # Mixed up order: dframe = pd.DataFrame( columns=["SATNUM", "nw", "now"], data=[[2, np.nan, 1], [1, 1, 1]] ) with pytest.raises(ValueError, match="SATNUM 2"): - PyscalFactory.create_pyscal_list(dframe, h=0.2) + create_pyscal_list(dframe, h=0.2) # Gasoil list dframe = pd.DataFrame( columns=["SATNUM", "ng", "nog"], data=[[1, 1, 1], [2, np.nan, 1]] ) with pytest.raises(ValueError, match="SATNUM 2"): - PyscalFactory.create_pyscal_list(dframe, h=0.2) + create_pyscal_list(dframe, h=0.2) # Gaswater list: dframe = pd.DataFrame( columns=["SATNUM", "ng", "nw"], data=[[1, 1, 1], [2, np.nan, 1]] ) with pytest.raises(ValueError, match="SATNUM 2"): - PyscalFactory.create_pyscal_list(dframe, h=0.2) + create_pyscal_list(dframe, h=0.2) # Wateroilgas list: dframe = pd.DataFrame( @@ -762,7 +736,7 @@ def test_error_messages_pr_satnum(): data=[[1, 1, 1, 1, 1], [2, np.nan, 1, 2, 3]], ) with pytest.raises(ValueError, match="SATNUM 2"): - PyscalFactory.create_pyscal_list(dframe, h=0.2) + create_pyscal_list(dframe, h=0.2) # SCAL rec list: dframe = pd.DataFrame( @@ -785,21 +759,17 @@ def test_error_messages_pr_satnum(): case = dframe.iloc[rowidx, 1] # The error should hint both to SATNUM and to low/base/high with pytest.raises(ValueError, match=f"SATNUM {satnum}"): - PyscalFactory.create_scal_recommendation_list(dframe_perturbed, h=0.2) + create_scal_recommendation_list(dframe_perturbed, h=0.2) with pytest.raises(ValueError, match=f"Problem with {case}"): - PyscalFactory.create_scal_recommendation_list(dframe_perturbed, h=0.2) + create_scal_recommendation_list(dframe_perturbed, h=0.2) def test_fast(): """Test fast mode for SCALrecmmendation""" testdir = Path(__file__).absolute().parent - scalrec_data = PyscalFactory.load_relperm_df( - testdir / "data/scal-pc-input-example.xlsx" - ) - scalrec_list_fast = PyscalFactory.create_scal_recommendation_list( - scalrec_data, fast=True - ) + scalrec_data = load_relperm_df(testdir / "data/scal-pc-input-example.xlsx") + scalrec_list_fast = create_scal_recommendation_list(scalrec_data, fast=True) for item in scalrec_list_fast: assert item.fast @@ -820,29 +790,29 @@ def test_fast(): [3, 2, 2, 2, 2], ], ) - relperm_data = PyscalFactory.load_relperm_df(dframe) - p_list_fast = PyscalFactory.create_pyscal_list(relperm_data, h=0.2, fast=True) + relperm_data = load_relperm_df(dframe) + p_list_fast = create_pyscal_list(relperm_data, h=0.2, fast=True) for item in p_list_fast: assert item.fast # GasOil list input_dframe = dframe[["SATNUM", "ng", "nog"]].copy() - relperm_data = PyscalFactory.load_relperm_df(input_dframe) - p_list_fast = PyscalFactory.create_pyscal_list(relperm_data, h=0.2, fast=True) + relperm_data = load_relperm_df(input_dframe) + p_list_fast = create_pyscal_list(relperm_data, h=0.2, fast=True) for item in p_list_fast: assert item.fast # WaterOil list input_dframe = dframe[["SATNUM", "nw", "now"]].copy() - relperm_data = PyscalFactory.load_relperm_df(input_dframe) - p_list_fast = PyscalFactory.create_pyscal_list(relperm_data, h=0.2, fast=True) + relperm_data = load_relperm_df(input_dframe) + p_list_fast = create_pyscal_list(relperm_data, h=0.2, fast=True) for item in p_list_fast: assert item.fast # GasWater list input_dframe = dframe[["SATNUM", "nw", "ng"]].copy() - relperm_data = PyscalFactory.load_relperm_df(input_dframe) - p_list_fast = PyscalFactory.create_pyscal_list(relperm_data, h=0.2, fast=True) + relperm_data = load_relperm_df(input_dframe) + p_list_fast = create_pyscal_list(relperm_data, h=0.2, fast=True) for item in p_list_fast: assert item.fast @@ -859,8 +829,8 @@ def test_fast(): [3, 2, 2, 2, 2, True], ], ) - relperm_data = PyscalFactory.load_relperm_df(dframe) + relperm_data = load_relperm_df(dframe) assert "fast" in relperm_data - p_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) + p_list = create_pyscal_list(relperm_data, h=0.2) for item in p_list: assert not item.fast diff --git a/tests/test_scalrecommendation.py b/tests/test_scalrecommendation.py index f3963f64..c90ed96f 100644 --- a/tests/test_scalrecommendation.py +++ b/tests/test_scalrecommendation.py @@ -5,8 +5,19 @@ import pytest from hypothesis import given -from pyscal import GasWater, PyscalFactory, SCALrecommendation, WaterOil, WaterOilGas -from pyscal.factory import slicedict +from pyscal import ( + GasWater, + SCALrecommendation, + WaterOil, + WaterOilGas, +) +from pyscal.factory import ( + create_scal_recommendation, + create_scal_recommendation_list, + create_water_oil_gas, + load_relperm_df, + slicedict, +) from pyscal.utils.testing import check_table, sat_table_str_ok, slow_hypothesis # Example SCAL recommendation, low case @@ -92,9 +103,9 @@ def test_make_scalrecommendation(): """Test that we can make scal recommendation objects from three WaterOilGas objects""" - low = PyscalFactory.create_water_oil_gas(LOW_SAMPLE_LET) - base = PyscalFactory.create_water_oil_gas(BASE_SAMPLE_LET) - high = PyscalFactory.create_water_oil_gas(HIGH_SAMPLE_LET) + low = create_water_oil_gas(LOW_SAMPLE_LET) + base = create_water_oil_gas(BASE_SAMPLE_LET) + high = create_water_oil_gas(HIGH_SAMPLE_LET) rec = SCALrecommendation(low, base, high) interpolant = rec.interpolate(-0.5, h=0.2) check_table(interpolant.wateroil.table) @@ -142,12 +153,12 @@ def test_make_scalrecommendation_wo(caplog): ] low_let_wo = slicedict(LOW_SAMPLE_LET, wo_param_names) - low = PyscalFactory.create_water_oil_gas(low_let_wo) + low = create_water_oil_gas(low_let_wo) base_let_wo = slicedict(BASE_SAMPLE_LET, wo_param_names) - base = PyscalFactory.create_water_oil_gas(base_let_wo) + base = create_water_oil_gas(base_let_wo) high_let_wo = slicedict(HIGH_SAMPLE_LET, wo_param_names) assert "Lg" not in high_let_wo - high = PyscalFactory.create_water_oil_gas(high_let_wo) + high = create_water_oil_gas(high_let_wo) rec = SCALrecommendation(low, base, high) interpolant = rec.interpolate(-0.5) check_table(interpolant.wateroil.table) @@ -185,12 +196,12 @@ def test_make_scalrecommendation_go(): ] low_let_go = slicedict(LOW_SAMPLE_LET, go_param_names) - low = PyscalFactory.create_water_oil_gas(low_let_go) + low = create_water_oil_gas(low_let_go) base_let_go = slicedict(BASE_SAMPLE_LET, go_param_names) - base = PyscalFactory.create_water_oil_gas(base_let_go) + base = create_water_oil_gas(base_let_go) high_let_go = slicedict(HIGH_SAMPLE_LET, go_param_names) assert "Lw" not in high_let_go - high = PyscalFactory.create_water_oil_gas(high_let_go) + high = create_water_oil_gas(high_let_go) rec = SCALrecommendation(low, base, high) assert rec.type == WaterOilGas interpolant = rec.interpolate(-0.5) @@ -214,7 +225,7 @@ def test_interpolation(param_wo, param_go): """Test interpolation with random interpolation parameters, looking for numerical corner cases""" - rec = PyscalFactory.create_scal_recommendation( + rec = create_scal_recommendation( {"low": LOW_SAMPLE_LET, "base": BASE_SAMPLE_LET, "high": HIGH_SAMPLE_LET}, "foo", h=0.1, @@ -247,7 +258,7 @@ def test_interpolation(param_wo, param_go): def test_boundary_cases(): """Test that interpolation is able to return the boundaries at +/- 1""" - rec = PyscalFactory.create_scal_recommendation( + rec = create_scal_recommendation( {"low": LOW_SAMPLE_LET, "base": BASE_SAMPLE_LET, "high": HIGH_SAMPLE_LET}, "foo", h=0.1, @@ -379,9 +390,7 @@ def test_gaswater_scal(caplog): [1, "opt", 4, 4, "sometag"], ], ) - rec_list = PyscalFactory.create_scal_recommendation_list( - PyscalFactory.load_relperm_df(dframe), h=0.1 - ) + rec_list = create_scal_recommendation_list(load_relperm_df(dframe), h=0.1) assert rec_list.pyscaltype == SCALrecommendation assert rec_list[1].type == GasWater low_list = rec_list.interpolate(-1) @@ -404,9 +413,9 @@ def test_gaswater_scal(caplog): def test_fast(): """Test the fast option""" - low_fast = PyscalFactory.create_water_oil_gas(LOW_SAMPLE_LET, fast=True) - base_fast = PyscalFactory.create_water_oil_gas(BASE_SAMPLE_LET, fast=True) - high_fast = PyscalFactory.create_water_oil_gas(HIGH_SAMPLE_LET, fast=True) + low_fast = create_water_oil_gas(LOW_SAMPLE_LET, fast=True) + base_fast = create_water_oil_gas(BASE_SAMPLE_LET, fast=True) + high_fast = create_water_oil_gas(HIGH_SAMPLE_LET, fast=True) rec = SCALrecommendation(low_fast, base_fast, high_fast) interp = rec.interpolate(-0.5) @@ -414,9 +423,9 @@ def test_fast(): assert interp.fast # test that one or more inputs not being set to fast does not trigger fast mode - low = PyscalFactory.create_water_oil_gas(LOW_SAMPLE_LET) - base = PyscalFactory.create_water_oil_gas(BASE_SAMPLE_LET) - high = PyscalFactory.create_water_oil_gas(HIGH_SAMPLE_LET) + low = create_water_oil_gas(LOW_SAMPLE_LET) + base = create_water_oil_gas(BASE_SAMPLE_LET) + high = create_water_oil_gas(HIGH_SAMPLE_LET) rec = SCALrecommendation(low_fast, base_fast, high) interp = rec.interpolate(-0.5)