From 3a9d1977a65fb851a83b2e5f72dba40c10a91c51 Mon Sep 17 00:00:00 2001 From: "Oddvar Lia (ST MSU GEO)" Date: Thu, 26 Sep 2024 13:59:32 +0200 Subject: [PATCH] Generated test data is written to tmp directory, added subscript doc minimum version --- docs/scripts/field_statistics.rst | 8 ++ ...IELD_PARAM_STATISTICS => FIELD_STATISTICS} | 0 .../field_statistics/field_statistics.py | 86 ++++++++++++++----- .../wf_field_param_statistics | 1 - .../field_statistics/wf_field_statistics | 2 + tests/test_field_statistics.py | 83 +++++++++++------- 6 files changed, 124 insertions(+), 56 deletions(-) create mode 100644 docs/scripts/field_statistics.rst rename src/subscript/field_statistics/{WF_FIELD_PARAM_STATISTICS => FIELD_STATISTICS} (100%) delete mode 100644 src/subscript/field_statistics/wf_field_param_statistics create mode 100644 src/subscript/field_statistics/wf_field_statistics diff --git a/docs/scripts/field_statistics.rst b/docs/scripts/field_statistics.rst new file mode 100644 index 000000000..9baa83529 --- /dev/null +++ b/docs/scripts/field_statistics.rst @@ -0,0 +1,8 @@ +FIELD_STATISTICS +================= + +.. argparse:: + :module: subscript.field_statistics.field_statistics + :func: get_parser + :prog: field_statistics + \ No newline at end of file diff --git a/src/subscript/field_statistics/WF_FIELD_PARAM_STATISTICS b/src/subscript/field_statistics/FIELD_STATISTICS similarity index 100% rename from src/subscript/field_statistics/WF_FIELD_PARAM_STATISTICS rename to src/subscript/field_statistics/FIELD_STATISTICS diff --git a/src/subscript/field_statistics/field_statistics.py b/src/subscript/field_statistics/field_statistics.py index cc8ba98e5..e75edb8fc 100644 --- a/src/subscript/field_statistics/field_statistics.py +++ b/src/subscript/field_statistics/field_statistics.py @@ -18,10 +18,12 @@ import sys from pathlib import Path +import ert import fmu.config.utilities as utils import numpy as np import xtgeo import yaml +from ert.config import ErtScript import subscript @@ -139,7 +141,8 @@ "Volon": ["phit"] # Size of ertbox grid for (nx, ny, nz) - # Required. + # Required if the ERTBOX grid is not found as a file + # under rms/output/aps/ERTBOX.EGRID ertbox_size: [92, 146, 66] # Standard deviation estimator. @@ -163,16 +166,21 @@ LOAD_WORKFLOW ../../bin/workflows/wf_field_param_statistics -- The workflow file to be located under ert/bin/workflows: -WF_FIELD_PARAM_STATISTICS // - --- The workflow job file to be located under ert/bin/jobs: --- Workflow job for ERT to calculate --- - mean and stdev of ensemble of continuous 3D parameters with name saved for geogrid --- under /realization-*/iter-*/share/results/grids/geogrid--.roff --- - estimate facies probabilities of discrete 3D parameters with name saved for geogrid --- under /realization-*/iter-*/share/results/grids/geogrid--.roff +FIELD_STATISTICS -c + -p + -e // + -r +-- Workflow job for ERT to calculate: +-- Mean and standard deviatons of specified continuous 3D parameters. +-- Estimate of facies probabilities from discrete 3D parameter for facies. +-- The input realizations are found under: +-- /realization-*/iter-*/share/results/grids/geogrid--.roff +-- The output mean and standard deviations and facies probability estimates are saved +-- under a directory specified by the user. +-- The first three command line arguments are required, the last one () +-- has default 'share/grid_statistics' under . INTERNAL False -EXECUTABLE ../scripts/wf_field_param_statistics.py +EXECUTABLE ../scripts/field_statistics.py MIN_ARG 6 ARG_TYPE 0 STRING @@ -187,14 +195,18 @@ """ # noqa - +DEFAULT_RELATIVE_RESULT_PATH = "share/grid_statistics" +GLOBAL_VARIABLES_FILE = "/../../fmuconfig/output/global_variables.yml" def main(): """Invocated from the command line, parsing command line arguments""" parser = get_parser() args = parser.parse_args() - logger.setLevel(logging.INFO) + field_stat(args) + + +def field_stat(args): # parse the config file for this script if not Path(args.configfile).exists(): @@ -214,16 +226,14 @@ def main(): sys.exit("No such file:" + args.ensemblepath) ens_path = args.ensemblepath - # Relative path for result of ensemble statistics calculations - # relative to ensemble path on scratch disk + # Path for result of ensemble statistics calculations # Default path is defined. - result_path = "share/grid_statistics" + relative_result_path = DEFAULT_RELATIVE_RESULT_PATH if Path(args.resultpath).exists(): - result_path = args.resultpath + relative_result_path = args.resultpath + result_path = ens_path + "/" + relative_result_path + glob_var_config_path = Path(ert_config_path) / Path(GLOBAL_VARIABLES_FILE) - glob_var_config_path = ( - ert_config_path + "/../../fmuconfig/output/global_variables.yml" - ) cfg_global = utils.yaml_load(glob_var_config_path)["global"] keyword = "FACIES_ZONE" if keyword in cfg_global: @@ -236,7 +246,7 @@ def main(): ertbox_size = get_ertbox_size(ertbox_path) logger.info(f"Config path to FMU project: {ert_config_path}") logger.info(f"Ensemble path on scratch disk: {ens_path}") - logger.info(f"Result relative path on scratch disk: {result_path}") + logger.info(f"Result path on scratch disk: {result_path}") logger.info(f"ERTBOX size: {ertbox_size}") calc_stats( @@ -414,7 +424,7 @@ def write_mean_stdev_nactive( ncount_active_values, result_path, ): - output_path = ensemble_path / Path(result_path) + output_path = result_path if not output_path.exists(): # Create the directory output_path.mkdir() @@ -466,7 +476,7 @@ def write_fraction_nactive( result_path, ncount_active_values=None, ): - output_path = ensemble_path / Path(result_path) + output_path = result_path if not output_path.exists(): # Create the directory output_path.mkdir() @@ -881,5 +891,37 @@ def calc_stats( logger.info(txt) +class FieldStatistics(ErtScript): + """This class defines the ERT workflow hook. + + It is constructed to work identical to the command line except + + * field_statistics is upper-cased to FIELD_STATISTICS + * All option names with double-dash must be enclosed in "" to avoid + interference with the ERT comment characters "--". + """ + + # pylint: disable=too-few-public-methods + def run(self, *args): + # pylint: disable=no-self-use + """Pass the ERT workflow arguments on to the same parser as the command + line.""" + parser = get_parser() + parsed_args = parser.parse_args(args) + field_stat(parsed_args) + + +@ert.plugin(name="subscript") +def legacy_ertscript_workflow(config): + """A hook for usage of this script in an ERT workflow, + using the legacy hook format.""" + + workflow = config.add_workflow(FieldStatistics, "FIELD_STATISTICS") + workflow.parser = get_parser + workflow.description = DESCRIPTION + workflow.examples = EXAMPLES + workflow.category = CATEGORY + + if __name__ == "__main__": main() diff --git a/src/subscript/field_statistics/wf_field_param_statistics b/src/subscript/field_statistics/wf_field_param_statistics deleted file mode 100644 index 96948ff57..000000000 --- a/src/subscript/field_statistics/wf_field_param_statistics +++ /dev/null @@ -1 +0,0 @@ -WF_FIELD_PARAM_STATISTICS -c -p -e // diff --git a/src/subscript/field_statistics/wf_field_statistics b/src/subscript/field_statistics/wf_field_statistics new file mode 100644 index 000000000..c83727b3d --- /dev/null +++ b/src/subscript/field_statistics/wf_field_statistics @@ -0,0 +1,2 @@ +FIELD_STATISTICS -c -p -e // -r + diff --git a/tests/test_field_statistics.py b/tests/test_field_statistics.py index 4f3bec22f..f8ca849d0 100644 --- a/tests/test_field_statistics.py +++ b/tests/test_field_statistics.py @@ -1,5 +1,7 @@ # import logging +import os from pathlib import Path +import shutil import fmu.config.utilities as utils import numpy as np @@ -21,12 +23,13 @@ # logger = subscript.getLogger(__name__) # logger.setLevel(logging.INFO) -TESTDATA = Path(__file__).absolute().parent / "testdata_field_statistics" -ENS_PATH = Path(__file__).absolute().parent / "testdata_field_statistics" / "ensemble" -ERT_CONFIG_PATH = ( - Path(__file__).absolute().parent / "testdata_field_statistics" / "ert" / "model" -) +TESTDATA = Path("testdata_field_statistics") +ENSEMBLE = Path("ensemble") RESULT_PATH = Path("share/grid_statistics") +ERT_CONFIG_PATH = Path("ert/model") +DATADIR = Path(__file__).absolute().parent / TESTDATA +GLOBAL_VARIABLES_FILE = Path("../../fmuconfig/output/global_variables.yml") + CONFIG_DICT = { "nreal": 10, @@ -55,22 +58,21 @@ "ertbox_size": [5, 6, 5], "use_population_stdev": False, } -GLOB_VAR_CFG_PATH = ERT_CONFIG_PATH / Path( - "../../fmuconfig/output/global_variables.yml" -) -CFG_GLOBAL = utils.yaml_load(GLOB_VAR_CFG_PATH)["global"] -KEYWORD = "FACIES_ZONE" -if KEYWORD in CFG_GLOBAL: - FACIES_PER_ZONE = CFG_GLOBAL[KEYWORD] -else: - raise KeyError(f"Missing keyword: {KEYWORD} in {GLOB_VAR_CFG_PATH}") - - -def make_box_grid(dimensions, grid_name, ens_path): - filename = ens_path / Path("share/grid_statistics") / Path(grid_name + ".roff") - filename_egrid = ( - ens_path / Path("share/grid_statistics") / Path(grid_name.upper() + ".EGRID") - ) +#GLOB_VAR_CFG_PATH = ERT_CONFIG_PATH / Path( +# "../../fmuconfig/output/global_variables.yml" +#) +#CFG_GLOBAL = utils.yaml_load(GLOB_VAR_CFG_PATH)["global"] +#KEYWORD = "FACIES_ZONE" +#if KEYWORD in CFG_GLOBAL: +# FACIES_PER_ZONE = CFG_GLOBAL[KEYWORD] +#else: +# raise KeyError(f"Missing keyword: {KEYWORD} in {GLOB_VAR_CFG_PATH}") + + +def make_box_grid(dimensions, grid_name, result_path): + filename = result_path / Path(grid_name + ".roff") + filename_egrid = result_path / Path(grid_name.upper() + ".EGRID") + grid = xtgeo.create_box_grid(dimensions) grid.name = grid_name print(f"Grid name: {grid.name}") @@ -116,7 +118,6 @@ def make_ensemble_test_data( iteration_list = [0, 3] zone_code_names = config_dict["zone_code_names"] - facies_per_zone = facies_per_zone discrete_param_name_per_zone = config_dict["discrete_property_param_per_zone"] param_name_per_zone = config_dict["continuous_property_param_per_zone"] nreal = 10 @@ -434,30 +435,46 @@ def compare_with_referencedata(ens_path, result_path, print_check=False): @pytest.mark.parametrize( - "config_dict, ens_path, ert_config_path, facies_per_zone, result_path", - [(CONFIG_DICT, ENS_PATH, ERT_CONFIG_PATH, FACIES_PER_ZONE, RESULT_PATH)], + "config_dict", + [CONFIG_DICT], ) def test_calc_statistics( + tmp_path, config_dict, - ens_path, - ert_config_path, - facies_per_zone, - result_path, ertbox_size=None, ): """Main test script""" + tmp_testdata_path = tmp_path / TESTDATA + shutil.copytree(DATADIR, tmp_testdata_path) + + ens_path = tmp_testdata_path / ENSEMBLE + ert_config_path = tmp_testdata_path / ERT_CONFIG_PATH + result_path = ens_path / RESULT_PATH + + glob_cfg_path = ert_config_path / GLOBAL_VARIABLES_FILE + cfg_global = utils.yaml_load(glob_cfg_path)["global"] + keyword = "FACIES_ZONE" + if keyword in cfg_global: + facies_per_zone = cfg_global[keyword] + else: + raise KeyError(f"Missing keyword: {keyword} in {glob_cfg_path}") + + print(tmp_testdata_path) + print(ens_path) + print(ert_config_path) + print(result_path) (nx, ny, nz) = config_dict["ertbox_size"] # Write file with ERTBOX grid for the purpose to import to visualize # the test data in e.g. RMS. Saved in share directory at # top of ensemble directory - make_box_grid((nx, ny, nz), "ERTBOX", ens_path) + make_box_grid((nx, ny, nz), "ERTBOX", result_path) # Write file with geogrid for the purpose to import to visualize # the test data in e.g. RMS". Geogrid for the test data has 3 zones, # each with 5 layers. Saved in share directory at top of ensemble directory - make_box_grid((nx, ny, nz * 3), "Geogrid", ens_path) + make_box_grid((nx, ny, nz * 3), "Geogrid", result_path) # Make ensemble of test data make_ensemble_test_data(config_dict, facies_per_zone, nx, ny, nz, ens_path) @@ -873,7 +890,7 @@ def test_get_specification( Path(__file__).absolute().parent / "testdata_field_statistics" / "config_example.yml", - ENS_PATH, + DATADIR / ENSEMBLE, ) ], ) @@ -900,8 +917,8 @@ def test_main(config_path, ens_path, print_info=True): "-p", ERT_CONFIG_PATH, "-e", - ENS_PATH, + DATADIR / ENSEMBLE, ] ) - assert compare_with_referencedata(ENS_PATH, RESULT_PATH, print_check=True) + assert compare_with_referencedata(DATADIR / ENSEMBLE, RESULT_PATH, print_check=True)