Source code for res2df.inferdims

"""
Support module for inferring EQLDIMS and TABDIMS from incomplete
reservoir simulator decks (typically single include-files)
"""

import logging

import opm.io

from .resdatafiles import ResdataFiles

logger = logging.getLogger(__name__)

# Constants to use for pointing to positions in the xxxDIMS keyword
DIMS_POS: dict[str, int] = {"NTPVT": 1, "NTSFUN": 0, "NTEQUL": 0}


[docs] def guess_dim(deckstring: str, dimkeyword: str, dimitem: int = 0) -> int: """Guess the correct dimension count for an incoming :term:`deck` (string) The incoming :term:`deck` must in string form, if not, extra data is most likely already removed by the opm.io parser. TABDIMS or EQLDIMS must not be present This function will inject TABDIMS or EQLDIMS into it and reparse it in a stricter mode, to detect the correct table dimensionality Arguments: deck: String containing a :term:`.DATA file` or only a few keywords dimkeyword: Either TABDIMS or EQLDIMS dimitem: The element number in TABDIMS/EQLDIMS to modify Returns: The lowest number for which stricter opm.io parsing succeeds """ if dimkeyword not in ["TABDIMS", "EQLDIMS"]: raise ValueError("Only supports TABDIMS and EQLDIMS") if dimkeyword == "TABDIMS" and dimitem not in [0, 1]: raise ValueError("Only support item 0 and 1 in TABDIMS") if dimkeyword == "EQLDIMS" and dimitem != 0: raise ValueError("Only item 0 in EQLDIMS can be estimated") # A less than res2df-standard permissive opm.io, when using # this one opm.io will fail if there are extra records # in tables (if NTSFUN in TABDIMS is wrong f.ex): opmioparser_recovery_fail_extra_records = [ ("PARSE_INVALID_KEYWORD_COMBINATION", opm.io.action.ignore), ("PARSE_MISSING_INCLUDE", opm.io.action.ignore), ("PARSE_MISSING_SECTIONS", opm.io.action.ignore), ("PARSE_RANDOM_TEXT", opm.io.action.ignore), ("PARSE_UNKNOWN_KEYWORD", opm.io.action.ignore), ("SUMMARY_UNKNOWN_GROUP", opm.io.action.ignore), ("UNSUPPORTED_*", opm.io.action.ignore), ] max_guess = 640 # This ought to be enough for everybody dimcountguess = 0 for dimcountguess in range(1, max_guess + 1): deck_candidate = inject_dimcount( deckstring, dimkeyword, dimitem, dimcountguess, nowarn=True ) try: ResdataFiles.str2deck( deck_candidate, parsecontext=opm.io.ParseContext( opmioparser_recovery_fail_extra_records ), ) # If we succeed, then the dimcountguess was correct break except (ValueError, RuntimeError): # ValueError in opm-common <= 2020.04 # RuntimeError in opm-common >= 2020.10 # Typically we get the error PARSE_EXTRA_RECORDS because we did not guess # high enough dimnumcount continue # If we get here, try another dimnumcount if dimcountguess == max_guess: logger.warning( "Unable to guess dim count for %s, or larger than %d", dimkeyword, max_guess ) logger.info("Guessed dimension count count for %s to %d", dimkeyword, dimcountguess) return dimcountguess
[docs] def inject_dimcount( deckstr: str, dimkeyword: str, dimitem: int, dimvalue: int, nowarn: bool = False ) -> str: """Insert a TABDIMS with NTSFUN into a :term:`deck` string This is simple string manipulation, not opm.io :term:`deck` manipulation (which might be possible to do). This function is to be wrapped by inject_xxxdims_ntxxx() Arguments: deckstr: A string containing a partial :term:`deck` (f.ex only the SWOF keyword). dimkeyword: Either TABDIMS or EQLDIMS dimitem: Item 0 (NTSSFUN) or 1 (NTPVT) of TABDIMS, only 0 for EQLDIMS. dimvalue: The NTSFUN/NTPVT/NTEQUIL number to use (this function does not care if it is correct or not but it must be larger than zero) nowarn: By default it will warn if this function is run on a deckstr with TABDIMS/EQLDIMS present. Mute this if True. Returns: New :term:`deck` string with TABDIMS/EQLDIMS prepended. """ assert dimvalue > 0, "dimvalue must be larger than zero" if dimkeyword not in ["TABDIMS", "EQLDIMS"]: raise ValueError("Only supports TABDIMS and EQLDIMS") if dimkeyword == "TABDIMS" and dimitem not in [0, 1]: raise ValueError("Only support item 0 and 1 in TABDIMS") if dimkeyword == "EQLDIMS" and dimitem != 0: raise ValueError("Only item 0 in EQLDIMS can be injected") if dimkeyword in deckstr: if not nowarn: logger.warning( "Not inserting %s in a deck where already exists", dimkeyword ) return deckstr return ( dimkeyword + "\n " + int(dimitem) * "1* " + str(dimvalue) + " /\n\n" + str(deckstr) )
[docs] def inject_xxxdims_ntxxx( xxxdims: str, ntxxx_name: str, deck: "str | opm.opmcommon_python.Deck", ntxxx_value: int | None = None, ) -> "opm.opmcommon_python.Deck": """Ensures TABDIMS/EQLDIMS is present in a :term:`deck`. If ntxxx_value=None and ntxxx_name not in the :term:`deck`, ntxxx_name will be inferred through trial-and-error parsing of the :term:`deck`, and then injected into the :term:`deck`. Args: xxxdims: TABDIMS or EQLDIMS ntxxx_name: NTPVT, NTEQUL or NTSFUN deck: A data :term:`deck`. If ntxxx_name is to be estimated this *must* be a string and not a fully parsed :term:`deck`. npxxx_value: Supply this if ntxxx_name is known, but not present in the deck, this will override any guessing. If the :term:`deck` already contains XXXDIMS, this will be ignored. Returns: opm.io Deck object """ assert xxxdims in ["TABDIMS", "EQLDIMS"] assert ntxxx_name in ["NTPVT", "NTEQUL", "NTSFUN"] if xxxdims in deck and ntxxx_value is None: # Then we have nothing to do, but ensure we parse a potential string to a deck if isinstance(deck, str): deck = ResdataFiles.str2deck(deck) return deck if xxxdims in deck and ntxxx_value is not None: logger.warning("Ignoring %s argument, it is already in the deck", ntxxx_name) return deck if not isinstance(deck, str): # The deck must be converted to a string deck in order # to estimate dimensions. deck = str(deck) # Estimate if ntxxx_value is not provided: if ntxxx_value is None: ntxxx_estimate = guess_dim(deck, xxxdims, DIMS_POS[ntxxx_name]) logger.warning("Estimated %s=%s", ntxxx_name, ntxxx_estimate) else: ntxxx_estimate = ntxxx_value augmented_strdeck = inject_dimcount( str(deck), xxxdims, DIMS_POS[ntxxx_name], ntxxx_estimate, nowarn=True ) # Overwrite the deck object deck = ResdataFiles.str2deck(augmented_strdeck) return deck