From 3ecdf58fab56aa225534f3b34c668a6c7a4db5e0 Mon Sep 17 00:00:00 2001 From: "Oddvar Lia (ST MSU GEO)" Date: Tue, 19 Nov 2024 12:38:07 +0100 Subject: [PATCH] Added generate_petro_jobs_for_field_update and tests Add testdata for generate_petro_jobs_for_field_update. Add documentation Update doc index --- docs/generate_petro_jobs_for_field_update.rst | 33 ++ docs/index.rst | 1 + pyproject.toml | 1 + .../generate_petro_jobs_for_field_update.py | 328 +++++++++++++++ .../F1_multi_zone_petro.txt | 70 ++++ .../F1_single_zone_petro.txt | 36 ++ .../F2_multi_zone_petro.txt | 70 ++++ .../F2_single_zone_petro.txt | 36 ++ .../F3_multi_zone_petro.txt | 70 ++++ .../F3_single_zone_petro.txt | 36 ++ .../generate_original_multi_zone_job.yml | 29 ++ .../generate_original_single_zone_job.yml | 18 + .../generate_petro_multi_zone_jobs.yml | 25 ++ .../generate_petro_single_zone_jobs.yml | 14 + .../multizonefacies.roff | Bin 0 -> 528 bytes .../generate_jobs_testdata/multizonegrid.roff | Bin 0 -> 3697 bytes .../singlezonefacies.roff | Bin 0 -> 503 bytes .../singlezonegrid.roff | Bin 0 -> 3202 bytes ...st_generate_petro_jobs_for_field_update.py | 378 ++++++++++++++++++ 19 files changed, 1145 insertions(+) create mode 100644 docs/generate_petro_jobs_for_field_update.rst create mode 100644 src/fmu/tools/rms/generate_petro_jobs_for_field_update.py create mode 100644 tests/rms/generate_jobs_testdata/F1_multi_zone_petro.txt create mode 100644 tests/rms/generate_jobs_testdata/F1_single_zone_petro.txt create mode 100644 tests/rms/generate_jobs_testdata/F2_multi_zone_petro.txt create mode 100644 tests/rms/generate_jobs_testdata/F2_single_zone_petro.txt create mode 100644 tests/rms/generate_jobs_testdata/F3_multi_zone_petro.txt create mode 100644 tests/rms/generate_jobs_testdata/F3_single_zone_petro.txt create mode 100644 tests/rms/generate_jobs_testdata/generate_original_multi_zone_job.yml create mode 100644 tests/rms/generate_jobs_testdata/generate_original_single_zone_job.yml create mode 100644 tests/rms/generate_jobs_testdata/generate_petro_multi_zone_jobs.yml create mode 100644 tests/rms/generate_jobs_testdata/generate_petro_single_zone_jobs.yml create mode 100644 tests/rms/generate_jobs_testdata/multizonefacies.roff create mode 100644 tests/rms/generate_jobs_testdata/multizonegrid.roff create mode 100644 tests/rms/generate_jobs_testdata/singlezonefacies.roff create mode 100644 tests/rms/generate_jobs_testdata/singlezonegrid.roff create mode 100644 tests/rms/test_generate_petro_jobs_for_field_update.py diff --git a/docs/generate_petro_jobs_for_field_update.rst b/docs/generate_petro_jobs_for_field_update.rst new file mode 100644 index 00000000..36191bae --- /dev/null +++ b/docs/generate_petro_jobs_for_field_update.rst @@ -0,0 +1,33 @@ +rms.generate_petro_jobs_for_field_update +========================================= + +When running FMU project where field parameters for both facies and +petrophysical properties is updated in ERT simultaneously, +some adjustments are needed in the RMS project to support this type +of workflow. It is necessary to have one petrosim job per facies. +To simplify the work with the RMS project, +the script *generate_petro_jobs_for_field_update* can be imported into +RMS as a python job. It requires a small configuration file and will then +read an existing petrosim job from the RMS project and generate one new +petrosim job per facies. The new jobs are ready to be used +(put into the RMS workflow) and they will use the same model parameters +for the petrophysical properties as the original (existing) job, +but only for one facies. + +This script will modify your RMS project when run from your RMS project +by adding new petrosim jobs, one per facies as specified in the +configuration file for the script. The configuration file is a yaml format +file defining which grid model and which facies 3D parameter to use and the +name of the original petrosim job. For each zone and each facies per zone +a list is specified of which petrophysical parameters to use in the new +petrosim jobs that are generated. + + +Usage +^^^^^ +Load the script *generate_petro_jobs_for_field_update* into a python job. +Specify a configuration file and specify the name of this configuration file in the +python job for the global variable **CONFIG_FILE** + +Run the python job in RMS to generate the new petrosim jobs and +finally update the workflowin RMS by using the generated jobs. diff --git a/docs/index.rst b/docs/index.rst index fe2f7785..22c6a21a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -11,6 +11,7 @@ Contents: qcforward qcproperties qcreset + generate_petro_jobs_for_field_update properties domain_conversion create_rft_ertobs diff --git a/pyproject.toml b/pyproject.toml index c4747eca..57896318 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,6 +79,7 @@ ensemble_well_props = "fmu.tools.ensembles.ensemble_well_props:main" fmudesign = "fmu.tools.sensitivities.fmudesignrunner:main" rename_rms_scripts = "fmu.tools.rms.rename_rms_scripts:main" rmsvolumetrics2csv = "fmu.tools.rms.volumetrics:rmsvolumetrics2csv_main" +generate_petro_jobs_for_field_update = "fmu.tools.rms.generate_petro_jobs_for_field_update:main" [tool.ruff] line-length = 88 diff --git a/src/fmu/tools/rms/generate_petro_jobs_for_field_update.py b/src/fmu/tools/rms/generate_petro_jobs_for_field_update.py new file mode 100644 index 00000000..afac0ef4 --- /dev/null +++ b/src/fmu/tools/rms/generate_petro_jobs_for_field_update.py @@ -0,0 +1,328 @@ +import copy +import pprint +import warnings + +import yaml + +try: + import _roxar # type: ignore +except ModuleNotFoundError: + try: + import _rmsapi as _roxar # type: ignore + import roxar.jobs # type: ignore + except ModuleNotFoundError: + warnings.warn("This script only supports interactive RMS usage", UserWarning) +from typing import no_type_check + +# User defined global variables +DEBUG_PRINT = False +REPORT_UNUSED = True +CONFIG_FILE = "generate_petro_jobs.yml" + +# Fixed global variables +GRID_MODELS = "Grid models" +GRID = "Grid" +JOB_TYPE = "Petrophysical Modeling" +PP = pprint.PrettyPrinter(depth=7) + + +def main(): + spec_dict = read_specification_file(CONFIG_FILE) + create_new_petro_job_per_facies(spec_dict) + + +@no_type_check +def check_rms_project(project): + if not isinstance(project, _roxar.Project): # type: ignore + raise RuntimeError("This run must be ran in an RoxAPI environment!") + + +def read_specification_file(config_file_name): + with open(config_file_name, encoding="utf-8") as yml_file: + return yaml.safe_load(yml_file) + + +def define_new_variable_names_and_correlation_matrix( + orig_var_names, var_names_to_keep, facies_name, orig_corr_matrix +): + nvar_new = len(var_names_to_keep) + if nvar_new == 1: + # No correlation matrix + new_variable_names = [] + new_variable_names.append(facies_name + "_" + var_names_to_keep[0]) + return new_variable_names, [] + + # Order the var_names_to_keep list to be in same sequence + # as orig_var_names for those variables that are common with the var_names_to_keep + # Example: the keep list is: ["A", "C", "B"] + # and the original var name list + # is ["A", "D", "F", "B", "C"] + # The sorted keep list should be: ["A", "B", "C"] + + sorted_keep_list = sort_new_var_names(orig_var_names, var_names_to_keep) + new_corr_matrix = [] + index_in_orig_var = [] + for i, var_name in enumerate(orig_var_names): + if var_name in sorted_keep_list: + index_in_orig_var.append(i) + + for row in range(nvar_new): + row_list = [] + for col in range(row): + row_list.append( + orig_corr_matrix[index_in_orig_var[row]][index_in_orig_var[col]] + ) + new_corr_matrix.append(row_list) + + new_variable_names = [] + # Here it is important to use the sorted version of the keep list + # to get correct variables connected to each row and column in + # the new correlation matrix + for var_name in sorted_keep_list: + new_variable_names.append(facies_name + "_" + var_name) + return new_variable_names, new_corr_matrix + + +def sort_new_var_names(original_variable_names, variable_names_to_keep): + sorted_keep_list = [] + for varname in original_variable_names: + if varname in variable_names_to_keep: + sorted_keep_list.append(varname) + return sorted_keep_list + + +def get_original_job_settings(owner_string_list, job_type, job_name): + original_job = roxar.jobs.Job.get_job(owner_string_list, job_type, job_name) + return original_job.get_arguments(skip_defaults=False) + + +def create_copy_of_job( + owner_string_list, job_type, original_job_arguments, new_job_name +): + new_job = roxar.jobs.Job.create(owner_string_list, job_type, new_job_name) + new_job.set_arguments(original_job_arguments) + return new_job + + +def get_zone_names_per_facies(used_petro_per_zone_per_facies_dict): + zone_names_with_facies_dict = {} + for zone_name, facies_dict in used_petro_per_zone_per_facies_dict.items(): + for facies_name, _ in facies_dict.items(): + if facies_name not in zone_names_with_facies_dict: + zone_names_with_facies_dict[facies_name] = [] + zone_names_with_facies_dict[facies_name].append(zone_name) + return zone_names_with_facies_dict + + +def get_used_petro_names(used_petro_per_zone_per_facies_dict): + all_petro_var_list = [] + for _, petro_per_facies_dict in used_petro_per_zone_per_facies_dict.items(): + for _, petro_list in petro_per_facies_dict.items(): + for petro_name in petro_list: + if petro_name not in all_petro_var_list: + all_petro_var_list.append(petro_name) + return all_petro_var_list + + +def check_consistency( + owner_string_list, job_name, used_petro_per_zone_per_facies_dict, report_unused=True +): + job_arguments = get_original_job_settings(owner_string_list, JOB_TYPE, job_name) + zone_models_list = job_arguments["Zone Models"] + + if report_unused: + # First report which field parameters from original model + # that is not specified to be used + print("Report of unused petrophysical variables in generated jobs:") + for zone_model in zone_models_list: + zone_name = zone_model["ZoneName"] + if zone_name not in used_petro_per_zone_per_facies_dict: + print(f" No field parameters are used from zone: {zone_name}") + else: + petro_per_facies_dict = used_petro_per_zone_per_facies_dict[zone_name] + facies_models_list = zone_model["Facies Models"] + for facies_model in facies_models_list: + facies_name = facies_model["FaciesName"] + petro_model_list = facies_model["Variable Models"] + if facies_name not in petro_per_facies_dict: + print( + " No field parameters are used for facies " + f"{facies_name} for zone {zone_name}" + ) + else: + petro_list = petro_per_facies_dict[facies_name] + for petro_model in petro_model_list: + var_name = petro_model["VariableName"] + if var_name not in petro_list: + print( + f" Field parameter {var_name} is not used " + f"for facies {facies_name} for zone {zone_name}" + ) + print("") + + # Check if there are specified field parameters which does not exist + # in the original job and report errors if this is the case + specified_petro_var_list = get_used_petro_names(used_petro_per_zone_per_facies_dict) + err_list = [] + for specified_petro_var in specified_petro_var_list: + found = False + for zone_model in zone_models_list: + facies_models_list = zone_model["Facies Models"] + for facies_model in facies_models_list: + petro_model_list = facies_model["Variable Models"] + for petro_model in petro_model_list: + if specified_petro_var == petro_model["VariableName"]: + found = True + break + if found: + break + if found: + break + if not found: + err_list.append(specified_petro_var) + if len(err_list) > 0: + print("Error in specification of used petrophysical variables.") + print("Unknown petrophysical variables:") + for name in err_list: + print(f"{name}") + raise ValueError("Unknown petrophysical variable names are specified.") + + +def create_new_petro_job_per_facies(spec_dict): + debug_print = DEBUG_PRINT + grid_name = spec_dict["grid_name"] + original_job_name = spec_dict["original_job_name"] + used_petro_per_zone_per_facies_dict = spec_dict["used_petro_var"] + + # Original job parameter setting + owner_string_list = [GRID_MODELS, grid_name, GRID] + check_consistency( + owner_string_list, + original_job_name, + used_petro_per_zone_per_facies_dict, + report_unused=REPORT_UNUSED, + ) + orig_job_arguments = get_original_job_settings( + owner_string_list, JOB_TYPE, original_job_name + ) + + zone_names_per_facies_dict = get_zone_names_per_facies( + used_petro_per_zone_per_facies_dict + ) + + # for each facies used in any zone, find the zone models having the facies + original_zone_models_list = orig_job_arguments["Zone Models"] + new_job_name_list = [] + for facies_name, zone_name_list in zone_names_per_facies_dict.items(): + if debug_print: + print(f"Facies: {facies_name}") + new_job_arguments_current = copy.deepcopy(orig_job_arguments) + # Remove unused keys or keys that should be set to default for new job + del new_job_arguments_current["InputFaciesProperty"] + del new_job_arguments_current["PrefixOutputName"] + + # Only keep specification for zones having the facies + new_job_arguments_current["Zone Models"] = [] + for zone_model_dict in original_zone_models_list: + zone_name = zone_model_dict["ZoneName"] + if zone_name in zone_name_list: + zone_model_dict_current = copy.deepcopy(zone_model_dict) + new_job_arguments_current["Zone Models"].append(zone_model_dict_current) + # for this zone model remove all specifications not relevant + # for current facies_name + + used_petro_var_list = used_petro_per_zone_per_facies_dict[zone_name][ + facies_name + ] + + tmp_list = zone_model_dict_current["Facies Models"] + # Loop over facies for this zone and keep only current facies + new_facies_model_list = [] + for facies_model_dict in tmp_list: + if facies_model_dict["FaciesName"] == facies_name: + new_facies_model_list.append(facies_model_dict) + break + # Here at least one facies model must exist in the list if + # not there is a consistency error in input dictionary + # used_petro_per_zone_per_facies_dict related to the + # specified original job. + if len(new_facies_model_list) == 0: + raise ValueError( + "There are some facies name errors in input dict" + " 'used_petro_per_zone_per_facies_dict'. " + "Check consistency with original job " + f"'{original_job_name}' and facies name '{facies_name}'" + ) + zone_model_dict_current["Facies Models"] = new_facies_model_list + # Use new property names consisting of facies_name + petro_name + original_variable_names = orig_job_arguments["VariableNames"] + + corr_model = zone_model_dict_current["Facies Models"][0] + original_corr_model_dict = corr_model["Correlation Model"][ + 0 + ] # Only one element in this always ?????????? + original_corr_matrix = original_corr_model_dict["CorrelationMatrix"] + new_variable_names, new_corr_matrix = ( + define_new_variable_names_and_correlation_matrix( + original_variable_names, + used_petro_var_list, + facies_name, + original_corr_matrix, + ) + ) + + original_corr_model_dict["CorrelationMatrix"] = new_corr_matrix + new_job_arguments_current["VariableNames"] = new_variable_names + + # Replace old petro names with new petro names + variable_models_list = corr_model["Variable Models"] + variable_models_list_to_keep = [] + for indx, variable_model in enumerate(variable_models_list): + var_name = variable_model["VariableName"] + new_var_name = facies_name + "_" + var_name + if new_var_name in new_variable_names: + variable_model["VariableName"] = new_var_name + variable_models_list_to_keep.append(variable_model) + corr_model["Variable Models"] = variable_models_list_to_keep + + new_job_name = facies_name + "_petro" + new_job_name = new_job_name.lower() + new_job_name_list.append(new_job_name) + print(f"Create job: {new_job_name}") + if debug_print: + PP.pprint(new_job_arguments_current) + print("-" * 100) + new_job = create_copy_of_job( + owner_string_list, JOB_TYPE, new_job_arguments_current, new_job_name + ) + ok, err_msg_list, warn_msg_list = roxar.jobs.Job.check(new_job) + if not ok: + print("Error messages from created job object:") + for err_msg in err_msg_list: + print(f"{err_msg}") + print("\n") + print("Warnings from created job object:") + for warn_msg in warn_msg_list: + print(f"{warn_msg}") + print(f"\nThe job with name {new_job_name} is not saved.") + else: + print(f"Save new job: {new_job_name}") + new_job.save() + return new_job_name_list + + +def write_petro_job_to_file(owner_string_list, job_type, job_name): + job_instance = roxar.jobs.Job.get_job( + owner=owner_string_list, type=job_type, name=job_name + ) + arguments = job_instance.get_arguments(True) + filename = job_name + ".txt" + print(f"Write file: {filename}") + with open(filename, "w") as outfile: + pprint.pp(arguments, depth=15, width=150, indent=3, stream=outfile) + outfile.write("_" * 150) + outfile.write("\n") + + +if __name__ == "__main__": + main() diff --git a/tests/rms/generate_jobs_testdata/F1_multi_zone_petro.txt b/tests/rms/generate_jobs_testdata/F1_multi_zone_petro.txt new file mode 100644 index 00000000..541ef494 --- /dev/null +++ b/tests/rms/generate_jobs_testdata/F1_multi_zone_petro.txt @@ -0,0 +1,70 @@ +{ 'BlockedWells': ['Grid models', 'MultiZoneBox', 'BW'], + 'VariableNames': ['F1_P1', 'F1_P2'], + 'Algorithm': 'SIMULATION', + 'Zone Models': [ { 'ZoneName': 'ZoneA', + 'Facies Models': [ { 'FaciesName': 'F1', + 'Correlation Model': [{'CorrelationMatrix': [[], [0.5]]}], + 'Variable Models': [ { 'VariableName': 'F1_P1', + 'Transform Sequence': [ { 'EstimationMode': 'FIXED', + 'WeightLog': '- none -', + 'Mean': [ { 'SequenceNumber': 1, + 'Mean': 0.30000001192092896, + 'Automated': False}], + 'Truncate': [{}]}], + 'Variogram Models': [ { 'RangeAzimuth': 2500.0, + 'RangeVertical': 25.0, + 'Type': 'GENERAL_EXPONENTIAL', + 'GeneralExponentialPower': 1.7999999523162842, + 'Mode': 'STANDARD', + 'TextureRoughness': 0.7999999523162842, + 'VariogramSillType': 'CONSTANT', + 'VariogramSill': 0.029999999329447746}]}, + { 'VariableName': 'F1_P2', + 'Transform Sequence': [ { 'EstimationMode': 'FIXED', + 'WeightLog': '- none -', + 'Mean': [ { 'SequenceNumber': 1, + 'Mean': 0.25, + 'Automated': False}], + 'Truncate': [{}]}], + 'Variogram Models': [ { 'RangeAzimuth': 2500.0, + 'RangeVertical': 25.0, + 'Type': 'GENERAL_EXPONENTIAL', + 'GeneralExponentialPower': 1.7999999523162842, + 'Mode': 'STANDARD', + 'TextureRoughness': 0.7999999523162842, + 'VariogramSillType': 'CONSTANT', + 'VariogramSill': 0.02500000037252903}]}]}]}, + { 'ZoneName': 'ZoneB', + 'Facies Models': [ { 'FaciesName': 'F1', + 'Correlation Model': [{'CorrelationMatrix': [[], [0.5]]}], + 'Variable Models': [ { 'VariableName': 'F1_P1', + 'Transform Sequence': [ { 'EstimationMode': 'FIXED', + 'WeightLog': '- none -', + 'Mean': [ { 'SequenceNumber': 1, + 'Mean': 0.30000001192092896, + 'Automated': False}], + 'Truncate': [{}]}], + 'Variogram Models': [ { 'RangeAzimuth': 2500.0, + 'RangeVertical': 25.0, + 'Type': 'GENERAL_EXPONENTIAL', + 'GeneralExponentialPower': 1.7999999523162842, + 'Mode': 'STANDARD', + 'TextureRoughness': 0.7999999523162842, + 'VariogramSillType': 'CONSTANT', + 'VariogramSill': 0.029999999329447746}]}, + { 'VariableName': 'F1_P2', + 'Transform Sequence': [ { 'EstimationMode': 'FIXED', + 'WeightLog': '- none -', + 'Mean': [ { 'SequenceNumber': 1, + 'Mean': 0.25, + 'Automated': False}], + 'Truncate': [{}]}], + 'Variogram Models': [ { 'RangeAzimuth': 2500.0, + 'RangeVertical': 25.0, + 'Type': 'GENERAL_EXPONENTIAL', + 'GeneralExponentialPower': 1.7999999523162842, + 'Mode': 'STANDARD', + 'TextureRoughness': 0.7999999523162842, + 'VariogramSillType': 'CONSTANT', + 'VariogramSill': 0.02500000037252903}]}]}]}]} +______________________________________________________________________________________________________________________________________________________ diff --git a/tests/rms/generate_jobs_testdata/F1_single_zone_petro.txt b/tests/rms/generate_jobs_testdata/F1_single_zone_petro.txt new file mode 100644 index 00000000..07f67f5e --- /dev/null +++ b/tests/rms/generate_jobs_testdata/F1_single_zone_petro.txt @@ -0,0 +1,36 @@ +{ 'BlockedWells': ['Grid models', 'SingleZoneBox', 'BW'], + 'VariableNames': ['F1_P1', 'F1_P2'], + 'Algorithm': 'SIMULATION', + 'Zone Models': [ { 'Facies Models': [ { 'FaciesName': 'F1', + 'Correlation Model': [{'CorrelationMatrix': [[], [0.5]]}], + 'Variable Models': [ { 'VariableName': 'F1_P1', + 'Transform Sequence': [ { 'EstimationMode': 'FIXED', + 'WeightLog': '- none -', + 'Mean': [ { 'SequenceNumber': 1, + 'Mean': 0.30000001192092896, + 'Automated': False}], + 'Truncate': [{}]}], + 'Variogram Models': [ { 'RangeAzimuth': 2500.0, + 'RangeVertical': 25.0, + 'Type': 'GENERAL_EXPONENTIAL', + 'GeneralExponentialPower': 1.7999999523162842, + 'Mode': 'STANDARD', + 'TextureRoughness': 0.7999999523162842, + 'VariogramSillType': 'CONSTANT', + 'VariogramSill': 0.029999999329447746}]}, + { 'VariableName': 'F1_P2', + 'Transform Sequence': [ { 'EstimationMode': 'FIXED', + 'WeightLog': '- none -', + 'Mean': [ { 'SequenceNumber': 1, + 'Mean': 0.25, + 'Automated': False}], + 'Truncate': [{}]}], + 'Variogram Models': [ { 'RangeAzimuth': 2500.0, + 'RangeVertical': 25.0, + 'Type': 'GENERAL_EXPONENTIAL', + 'GeneralExponentialPower': 1.7999999523162842, + 'Mode': 'STANDARD', + 'TextureRoughness': 0.7999999523162842, + 'VariogramSillType': 'CONSTANT', + 'VariogramSill': 0.02500000037252903}]}]}]}]} +______________________________________________________________________________________________________________________________________________________ diff --git a/tests/rms/generate_jobs_testdata/F2_multi_zone_petro.txt b/tests/rms/generate_jobs_testdata/F2_multi_zone_petro.txt new file mode 100644 index 00000000..08969896 --- /dev/null +++ b/tests/rms/generate_jobs_testdata/F2_multi_zone_petro.txt @@ -0,0 +1,70 @@ +{ 'BlockedWells': ['Grid models', 'MultiZoneBox', 'BW'], + 'VariableNames': ['F2_P1', 'F2_P2'], + 'Algorithm': 'SIMULATION', + 'Zone Models': [ { 'ZoneName': 'ZoneA', + 'Facies Models': [ { 'FaciesName': 'F2', + 'Correlation Model': [{'CorrelationMatrix': [[], [0.5]]}], + 'Variable Models': [ { 'VariableName': 'F2_P1', + 'Transform Sequence': [ { 'EstimationMode': 'FIXED', + 'WeightLog': '- none -', + 'Mean': [ { 'SequenceNumber': 1, + 'Mean': 0.10000000149011612, + 'Automated': False}], + 'Truncate': [{}]}], + 'Variogram Models': [ { 'RangeAzimuth': 2500.0, + 'RangeVertical': 25.0, + 'Type': 'GENERAL_EXPONENTIAL', + 'GeneralExponentialPower': 1.7999999523162842, + 'Mode': 'STANDARD', + 'TextureRoughness': 0.7999999523162842, + 'VariogramSillType': 'CONSTANT', + 'VariogramSill': 0.009999999776482582}]}, + { 'VariableName': 'F2_P2', + 'Transform Sequence': [ { 'EstimationMode': 'FIXED', + 'WeightLog': '- none -', + 'Mean': [ { 'SequenceNumber': 1, + 'Mean': 0.10000000149011612, + 'Automated': False}], + 'Truncate': [{}]}], + 'Variogram Models': [ { 'RangeAzimuth': 2500.0, + 'RangeVertical': 25.0, + 'Type': 'GENERAL_EXPONENTIAL', + 'GeneralExponentialPower': 1.7999999523162842, + 'Mode': 'STANDARD', + 'TextureRoughness': 0.7999999523162842, + 'VariogramSillType': 'CONSTANT', + 'VariogramSill': 0.019999999552965164}]}]}]}, + { 'ZoneName': 'ZoneB', + 'Facies Models': [ { 'FaciesName': 'F2', + 'Correlation Model': [{'CorrelationMatrix': [[], [0.5]]}], + 'Variable Models': [ { 'VariableName': 'F2_P1', + 'Transform Sequence': [ { 'EstimationMode': 'FIXED', + 'WeightLog': '- none -', + 'Mean': [ { 'SequenceNumber': 1, + 'Mean': 0.10000000149011612, + 'Automated': False}], + 'Truncate': [{}]}], + 'Variogram Models': [ { 'RangeAzimuth': 2500.0, + 'RangeVertical': 25.0, + 'Type': 'GENERAL_EXPONENTIAL', + 'GeneralExponentialPower': 1.7999999523162842, + 'Mode': 'STANDARD', + 'TextureRoughness': 0.7999999523162842, + 'VariogramSillType': 'CONSTANT', + 'VariogramSill': 0.009999999776482582}]}, + { 'VariableName': 'F2_P2', + 'Transform Sequence': [ { 'EstimationMode': 'FIXED', + 'WeightLog': '- none -', + 'Mean': [ { 'SequenceNumber': 1, + 'Mean': 0.10000000149011612, + 'Automated': False}], + 'Truncate': [{}]}], + 'Variogram Models': [ { 'RangeAzimuth': 2500.0, + 'RangeVertical': 25.0, + 'Type': 'GENERAL_EXPONENTIAL', + 'GeneralExponentialPower': 1.7999999523162842, + 'Mode': 'STANDARD', + 'TextureRoughness': 0.7999999523162842, + 'VariogramSillType': 'CONSTANT', + 'VariogramSill': 0.019999999552965164}]}]}]}]} +______________________________________________________________________________________________________________________________________________________ diff --git a/tests/rms/generate_jobs_testdata/F2_single_zone_petro.txt b/tests/rms/generate_jobs_testdata/F2_single_zone_petro.txt new file mode 100644 index 00000000..3bac72a9 --- /dev/null +++ b/tests/rms/generate_jobs_testdata/F2_single_zone_petro.txt @@ -0,0 +1,36 @@ +{ 'BlockedWells': ['Grid models', 'SingleZoneBox', 'BW'], + 'VariableNames': ['F2_P1', 'F2_P2'], + 'Algorithm': 'SIMULATION', + 'Zone Models': [ { 'Facies Models': [ { 'FaciesName': 'F2', + 'Correlation Model': [{'CorrelationMatrix': [[], [0.5]]}], + 'Variable Models': [ { 'VariableName': 'F2_P1', + 'Transform Sequence': [ { 'EstimationMode': 'FIXED', + 'WeightLog': '- none -', + 'Mean': [ { 'SequenceNumber': 1, + 'Mean': 0.10000000149011612, + 'Automated': False}], + 'Truncate': [{}]}], + 'Variogram Models': [ { 'RangeAzimuth': 2500.0, + 'RangeVertical': 25.0, + 'Type': 'GENERAL_EXPONENTIAL', + 'GeneralExponentialPower': 1.7999999523162842, + 'Mode': 'STANDARD', + 'TextureRoughness': 0.7999999523162842, + 'VariogramSillType': 'CONSTANT', + 'VariogramSill': 0.009999999776482582}]}, + { 'VariableName': 'F2_P2', + 'Transform Sequence': [ { 'EstimationMode': 'FIXED', + 'WeightLog': '- none -', + 'Mean': [ { 'SequenceNumber': 1, + 'Mean': 0.10000000149011612, + 'Automated': False}], + 'Truncate': [{}]}], + 'Variogram Models': [ { 'RangeAzimuth': 2500.0, + 'RangeVertical': 25.0, + 'Type': 'GENERAL_EXPONENTIAL', + 'GeneralExponentialPower': 1.7999999523162842, + 'Mode': 'STANDARD', + 'TextureRoughness': 0.7999999523162842, + 'VariogramSillType': 'CONSTANT', + 'VariogramSill': 0.019999999552965164}]}]}]}]} +______________________________________________________________________________________________________________________________________________________ diff --git a/tests/rms/generate_jobs_testdata/F3_multi_zone_petro.txt b/tests/rms/generate_jobs_testdata/F3_multi_zone_petro.txt new file mode 100644 index 00000000..5de9d00c --- /dev/null +++ b/tests/rms/generate_jobs_testdata/F3_multi_zone_petro.txt @@ -0,0 +1,70 @@ +{ 'BlockedWells': ['Grid models', 'MultiZoneBox', 'BW'], + 'VariableNames': ['F3_P1', 'F3_P2'], + 'Algorithm': 'SIMULATION', + 'Zone Models': [ { 'ZoneName': 'ZoneA', + 'Facies Models': [ { 'FaciesName': 'F3', + 'Correlation Model': [{'CorrelationMatrix': [[], [0.5]]}], + 'Variable Models': [ { 'VariableName': 'F3_P1', + 'Transform Sequence': [ { 'EstimationMode': 'FIXED', + 'WeightLog': '- none -', + 'Mean': [ { 'SequenceNumber': 1, + 'Mean': 0.05000000074505806, + 'Automated': False}], + 'Truncate': [{}]}], + 'Variogram Models': [ { 'RangeAzimuth': 2500.0, + 'RangeVertical': 25.0, + 'Type': 'GENERAL_EXPONENTIAL', + 'GeneralExponentialPower': 1.7999999523162842, + 'Mode': 'STANDARD', + 'TextureRoughness': 0.7999999523162842, + 'VariogramSillType': 'CONSTANT', + 'VariogramSill': 0.009999999776482582}]}, + { 'VariableName': 'F3_P2', + 'Transform Sequence': [ { 'EstimationMode': 'FIXED', + 'WeightLog': '- none -', + 'Mean': [ { 'SequenceNumber': 1, + 'Mean': 0.07000000029802322, + 'Automated': False}], + 'Truncate': [{}]}], + 'Variogram Models': [ { 'RangeAzimuth': 2500.0, + 'RangeVertical': 25.0, + 'Type': 'GENERAL_EXPONENTIAL', + 'GeneralExponentialPower': 1.7999999523162842, + 'Mode': 'STANDARD', + 'TextureRoughness': 0.7999999523162842, + 'VariogramSillType': 'CONSTANT', + 'VariogramSill': 0.014999999664723873}]}]}]}, + { 'ZoneName': 'ZoneB', + 'Facies Models': [ { 'FaciesName': 'F3', + 'Correlation Model': [{'CorrelationMatrix': [[], [0.5]]}], + 'Variable Models': [ { 'VariableName': 'F3_P1', + 'Transform Sequence': [ { 'EstimationMode': 'FIXED', + 'WeightLog': '- none -', + 'Mean': [ { 'SequenceNumber': 1, + 'Mean': 0.05000000074505806, + 'Automated': False}], + 'Truncate': [{}]}], + 'Variogram Models': [ { 'RangeAzimuth': 2500.0, + 'RangeVertical': 25.0, + 'Type': 'GENERAL_EXPONENTIAL', + 'GeneralExponentialPower': 1.7999999523162842, + 'Mode': 'STANDARD', + 'TextureRoughness': 0.7999999523162842, + 'VariogramSillType': 'CONSTANT', + 'VariogramSill': 0.009999999776482582}]}, + { 'VariableName': 'F3_P2', + 'Transform Sequence': [ { 'EstimationMode': 'FIXED', + 'WeightLog': '- none -', + 'Mean': [ { 'SequenceNumber': 1, + 'Mean': 0.07000000029802322, + 'Automated': False}], + 'Truncate': [{}]}], + 'Variogram Models': [ { 'RangeAzimuth': 2500.0, + 'RangeVertical': 25.0, + 'Type': 'GENERAL_EXPONENTIAL', + 'GeneralExponentialPower': 1.7999999523162842, + 'Mode': 'STANDARD', + 'TextureRoughness': 0.7999999523162842, + 'VariogramSillType': 'CONSTANT', + 'VariogramSill': 0.014999999664723873}]}]}]}]} +______________________________________________________________________________________________________________________________________________________ diff --git a/tests/rms/generate_jobs_testdata/F3_single_zone_petro.txt b/tests/rms/generate_jobs_testdata/F3_single_zone_petro.txt new file mode 100644 index 00000000..07bcaeef --- /dev/null +++ b/tests/rms/generate_jobs_testdata/F3_single_zone_petro.txt @@ -0,0 +1,36 @@ +{ 'BlockedWells': ['Grid models', 'SingleZoneBox', 'BW'], + 'VariableNames': ['F3_P1', 'F3_P2'], + 'Algorithm': 'SIMULATION', + 'Zone Models': [ { 'Facies Models': [ { 'FaciesName': 'F3', + 'Correlation Model': [{'CorrelationMatrix': [[], [0.5]]}], + 'Variable Models': [ { 'VariableName': 'F3_P1', + 'Transform Sequence': [ { 'EstimationMode': 'FIXED', + 'WeightLog': '- none -', + 'Mean': [ { 'SequenceNumber': 1, + 'Mean': 0.05000000074505806, + 'Automated': False}], + 'Truncate': [{}]}], + 'Variogram Models': [ { 'RangeAzimuth': 2500.0, + 'RangeVertical': 25.0, + 'Type': 'GENERAL_EXPONENTIAL', + 'GeneralExponentialPower': 1.7999999523162842, + 'Mode': 'STANDARD', + 'TextureRoughness': 0.7999999523162842, + 'VariogramSillType': 'CONSTANT', + 'VariogramSill': 0.009999999776482582}]}, + { 'VariableName': 'F3_P2', + 'Transform Sequence': [ { 'EstimationMode': 'FIXED', + 'WeightLog': '- none -', + 'Mean': [ { 'SequenceNumber': 1, + 'Mean': 0.07000000029802322, + 'Automated': False}], + 'Truncate': [{}]}], + 'Variogram Models': [ { 'RangeAzimuth': 2500.0, + 'RangeVertical': 25.0, + 'Type': 'GENERAL_EXPONENTIAL', + 'GeneralExponentialPower': 1.7999999523162842, + 'Mode': 'STANDARD', + 'TextureRoughness': 0.7999999523162842, + 'VariogramSillType': 'CONSTANT', + 'VariogramSill': 0.014999999664723873}]}]}]}]} +______________________________________________________________________________________________________________________________________________________ diff --git a/tests/rms/generate_jobs_testdata/generate_original_multi_zone_job.yml b/tests/rms/generate_jobs_testdata/generate_original_multi_zone_job.yml new file mode 100644 index 00000000..1fea8dbe --- /dev/null +++ b/tests/rms/generate_jobs_testdata/generate_original_multi_zone_job.yml @@ -0,0 +1,29 @@ +grid_name: "MultiZoneBox" +grid_file_name: "multizonegrid.roff" +facies_real_name: "Facies" +facies_file_name: "multizonefacies.roff" +original_job_name: "original_multi_zone" +original_job_settings: + zones: + Zone1: + F1: + P1: [0.3, 0.03] + P2: [0.25, 0.025] + F2: + P1: [0.1, 0.01] + P2: [0.1, 0.02] + F3: + P1: [0.05, 0.01] + P2: [0.07, 0.015] + Zone2: + F1: + P1: [0.3, 0.03] + P2: [0.25, 0.025] + F2: + P1: [0.1, 0.01] + P2: [0.1, 0.02] + F3: + P1: [0.05, 0.01] + P2: [0.07, 0.015] + + diff --git a/tests/rms/generate_jobs_testdata/generate_original_single_zone_job.yml b/tests/rms/generate_jobs_testdata/generate_original_single_zone_job.yml new file mode 100644 index 00000000..2e10f852 --- /dev/null +++ b/tests/rms/generate_jobs_testdata/generate_original_single_zone_job.yml @@ -0,0 +1,18 @@ +grid_name: "SingleZoneBox" +grid_file_name: "singlezonegrid.roff" +facies_real_name: "Facies" +facies_file_name: "singlezonefacies.roff" +original_job_name: "original_single_zone" +original_job_settings: + zones: + "": + F1: + P1: [0.3, 0.03] + P2: [0.25, 0.025] + F2: + P1: [0.1, 0.01] + P2: [0.1, 0.02] + F3: + P1: [0.05, 0.01] + P2: [0.07, 0.015] + diff --git a/tests/rms/generate_jobs_testdata/generate_petro_multi_zone_jobs.yml b/tests/rms/generate_jobs_testdata/generate_petro_multi_zone_jobs.yml new file mode 100644 index 00000000..f541c65f --- /dev/null +++ b/tests/rms/generate_jobs_testdata/generate_petro_multi_zone_jobs.yml @@ -0,0 +1,25 @@ +# Name of grid model for the petrophysics jobs +grid_name: "MultiZoneBox" + +# Name of original petro job using facies realization as input +original_job_name: "original_multi_zone" + +# Use empty string as zone name for single zone grids and zone name for multizone grids. +# For each zone, specify facies and for each facies specify +# petro variables to be used as field parameters in ERT update + +# Example for a multizone grid with three zones: +used_petro_var: + Zone1: + F1: ["P1", "P2"] + F2: ["P1", "P2"] + F3: ["P1", "P2"] + Zone2: + F1: ["P1", "P2"] + F2: ["P1", "P2"] + F3: ["P1", "P2"] + + + + + diff --git a/tests/rms/generate_jobs_testdata/generate_petro_single_zone_jobs.yml b/tests/rms/generate_jobs_testdata/generate_petro_single_zone_jobs.yml new file mode 100644 index 00000000..dfa2bb1c --- /dev/null +++ b/tests/rms/generate_jobs_testdata/generate_petro_single_zone_jobs.yml @@ -0,0 +1,14 @@ +# Name of grid model for the petrophysics jobs +grid_name: "SingleZoneBox" + +# Name of original petro job using facies realization as input +original_job_name: "original_single_zone" + +# Use empty string as zone name for single zone grids and zone name for multizone grids. +# For each zone, specify facies and for each facies specify +# petro variables to be used as field parameters in ERT update +used_petro_var: + "": + F1: ["P1", "P2"] + F2: ["P1", "P2"] + F3: ["P1", "P2"] diff --git a/tests/rms/generate_jobs_testdata/multizonefacies.roff b/tests/rms/generate_jobs_testdata/multizonefacies.roff new file mode 100644 index 0000000000000000000000000000000000000000..f167bd67c5ce78b82cb379dd3fd4166e8da38830 GIT binary patch literal 528 zcmZ9I-A=z&%^ z<#km;GzVaf)b(Wg#WV2g1-JVQ3+7@YUk~`J8=~@Mx z+6KzeR8B!^9Mro`7mw5qemWQffW`x5eTeJsXxO%Cv$PsCBxYJxPDAbLOtm^wo2|_y z)#`k;Ha|ND+B7LUH6GLt&{hv~3+FI14cAT$exoZ2Z>DW+h_di8<}RG)aC2`c@5A`< zZq6!GWQ5boa^>JLma5PWZeL!!Q950A-n`skmYY057caGkT9GV19c!I`wubC%k@tKGGi#%VXg(ql^7asDxmORWuJ~H_EntX<}(|Jkz2j5A*o^%yVfGxtJ$tjQ5&(f)<~7ywCL^Ej&R>Jg;ZY z{IM59!n17S7s> LNOazkKPEo`?o2y;kD@Ux<`xz#ob)%d2w5m zO6#S*UzQ6X+lYjGlk%w2;ivPE@_NzijOCl0GkRk>vSKxi9Qz7a6WzZ3weOJqAGCzHvVF0}3BsZCb%hR#ycO4hrD)9s0*qivDqCb?ainW~n- zsTNR;hH?s0o2`I04XRFqC-oDw)x+GvDXdJ>wNryUn>M9Fmh9?-f!c|6%UNGKA zHR`!F+FnnMEJHm5cwD`>9GtA>P^90R#;UQmmNDEKUQlEKz9bTU|=k^wbCuq!j%{)Pi&phOx z=YEkEo}f7|Jmh;Vb(n{IWbpl(c!6ul{R$fMUNcY7;xo@UUMoD55gzhoeaIJ{DbZ4g zd3=55xv+p-%o8-`y=I=E#b+Mx^ZJk$o}eYq_cPb=_m9`i6Ex`~Z<^o)aI zGfUmF>A@@9if8_CltvDTj@YH6`f^?T|F`EwpB}~doQTK9#>+uG-i|*X#Dn(qfpK#E zv9BNXi+beyg8ew``EmFEu)L%B6aM`@OluR5c<+XLB;LbExL^LY^*`?Z|IhPB?=w|* fX-D&CWi8J$@L!eJ@Fg+&TKbFgXhNMg 0: + raise ValueError( + "Expecting same set of petro variables " + "for all facies and all zones in original petro " + f"job {original_job_name}" + ) + for petro_name, petro_model_param_list in petro_dict.items(): + model_param_mean = petro_model_param_list[0] + model_param_stdev = petro_model_param_list[1] + var_model_list.append( + { + "ModelingMode": "PREDICTION/SIMULATION", + "VariableName": petro_name, + "Variogram Models": [ + { + "Mode": "STANDARD", + "RangeAzimuth": 2500.0, + "RangeAzimuthNormal": 1000, + "RangeVertical": 25.0, + "Type": "GENERAL_EXPONENTIAL", + "GeneralExponentialPower": 1.8, + "VariogramSillType": "CONSTANT", + "VariogramSill": model_param_stdev, + }, + ], + "Transform Sequence": [ + { + "EstimationMode": "FIXED", + "WeightLog": "- none -", + "Truncate": [ + { + "Active": True, + "AppliedTo": "INPUT_AND_OUTPUT", + "Automated": False, + "Mode": "MIN", + "Min": 0, + "SequenceNumber": 0, + } + ], + "Mean": [ + { + "Active": True, + "Automated": False, + "Mean": model_param_mean, + "SequenceNumber": 1, + } + ], + }, + ], + } + ) + corr_model = [{"CorrelationMatrix": [[], [0.5]]}] + facies_model_list.append( + { + "FaciesName": facies_name, + "Variable Models": var_model_list, + "Correlation Model": corr_model, + } + ) + zone_model_list.append( + {"ZoneName": zone_name, "Facies Models": facies_model_list} + ) + + return { + "Algorithm": "SIMULATION", + "ConditionOnBlockedWells": False, + "InputFaciesProperty": ["Grid models", grid_name, facies_real_name], + "VariableNames": common_petro_list, + "Zone Models": zone_model_list, + "PrefixOutputName": "original", + } + + +def write_petro_job_to_file(owner_string_list, job_type, job_name, filename): + job_instance = roxar.jobs.Job.get_job( + owner=owner_string_list, type=job_type, name=job_name + ) + arguments = job_instance.get_arguments(True) + + print(f"Write file: {filename}") + with open(filename, "w") as outfile: + pprint.pp(arguments, depth=15, width=150, indent=3, stream=outfile) + outfile.write("_" * 150) + outfile.write("\n") + + +# Here the temporary RMS project is created. It contains a grid, +# a facies parameter and an original petrophysical job +@pytest.mark.skipunlessroxar +@pytest.fixture +def create_project(): + """Create a tmp RMS project for testing, populate with basic data. + + After the yield command, the teardown phase will remove the tmp RMS project. + """ + prj1 = str(PRJ) + + print("\n******** Setup RMS project!\n") + if isdir(prj1): + print("Remove existing project! (1)") + shutil.rmtree(prj1) + + project = roxar.Project.create() + + rox = xtgeo.RoxUtils(project) + print("Roxar version is", rox.roxversion) + print("RMS version is", rox.rmsversion(rox.roxversion)) + assert "1." in rox.roxversion + + # Read specification for original petrophysical job + # using facies realization as input for single zone grid + spec_original_dict = read_specification_file(CONFIG_FILE_ORIGINAL_SINGLE_ZONE_JOB) + rms_grid_name = spec_original_dict["grid_name"] + rms_facies_real_name = spec_original_dict["facies_real_name"] + grid_file_name = spec_original_dict["grid_file_name"] + facies_file_name = spec_original_dict["facies_file_name"] + + # populate with grid and props + grid_data = REFERENCE_DIR / grid_file_name + grd = xtgeo.grid_from_file(grid_data) + grd.to_roxar(project, rms_grid_name) + facies_data = REFERENCE_DIR / facies_file_name + facies = xtgeo.gridproperty_from_file(facies_data, name=rms_facies_real_name) + facies.to_roxar(project, rms_grid_name, rms_facies_real_name) + + create_original_petro_job(spec_original_dict) + + # Read specification for original petrophysical job + # using facies realization as input for multi zone grid + spec_original_dict = read_specification_file(CONFIG_FILE_ORIGINAL_MULTI_ZONE_JOB) + rms_grid_name = spec_original_dict["grid_name"] + rms_facies_real_name = spec_original_dict["facies_real_name"] + grid_file_name = spec_original_dict["grid_file_name"] + facies_file_name = spec_original_dict["facies_file_name"] + + # populate with grid and props + grid_data = REFERENCE_DIR / grid_file_name + grd = xtgeo.grid_from_file(grid_data) + grd.to_roxar(project, rms_grid_name) + facies_data = REFERENCE_DIR / facies_file_name + facies = xtgeo.gridproperty_from_file(facies_data, name=rms_facies_real_name) + facies.to_roxar(project, rms_grid_name, rms_facies_real_name) + + create_original_petro_job(spec_original_dict) + + project.save_as(prj1) + project.close() + + yield project + print("\n******* Teardown RMS project!\n") + + if isdir(prj1): + print("Remove existing project! (1)") + shutil.rmtree(prj1) + + +# Now run the script to generate one petro job per facies +# using the same settings (same model parameters) +# as the original petro job for the +# petrophysical properties. +@pytest.mark.skipunlessroxar +def test_generate_jobs(create_project): + """Test generate_petro_jobs_for_field_update""" + with create_project as project: + check_rms_project(project) + # Now run script to generate one petro job per facies for single zone grid + spec_case = read_specification_file(CONFIG_FILE_SINGLE_ZONE) + job_name_list = create_new_petro_job_per_facies(spec_case) + for n, job_name in enumerate(job_name_list): + filename = job_name + ".txt" + reference_filename = REFERENCE_DIR / REFERENCE_FILES_SINGLE_ZONE_GRID[n] + write_petro_job_to_file( + OWNER_STRING_SINGLE_ZONE, JOB_TYPE, job_name, filename + ) + + # Compare text files with job parameters with reference for single zone jobs + check = filecmp.cmp(filename, reference_filename) + if check: + print("Check OK for single zone grid") + assert check + + # Now run script to generate one petro job per facies for single zone grid + spec_case = read_specification_file(CONFIG_FILE_MULTI_ZONE) + job_name_list = create_new_petro_job_per_facies(spec_case) + for n, job_name in enumerate(job_name_list): + filename = job_name + ".txt" + reference_filename = REFERENCE_DIR / REFERENCE_FILES_MULTI_ZONE_GRID[n] + write_petro_job_to_file( + OWNER_STRING_MULTI_ZONE, JOB_TYPE, job_name, filename + ) + + # Compare text files with job parameters with reference for single zone jobs + if check: + print("Check OK for multi zone grid") + assert check