diff --git a/docs/generate_petro_jobs_for_field_update.rst b/docs/generate_petro_jobs_for_field_update.rst index 88a3eee3..35c2e988 100644 --- a/docs/generate_petro_jobs_for_field_update.rst +++ b/docs/generate_petro_jobs_for_field_update.rst @@ -5,16 +5,18 @@ 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 +To simplify the work with the RMS project, the function +*generate_petro_jobs* from *fmu.tools* +can be used in a python job in RMS. +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 +The function *generate_petro_jobs* 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 @@ -25,9 +27,9 @@ 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. +Import the function *generate_petro_jobs* into +a python job in RMS. Specify a configuration file and specify the +name of this configuration file in the python job. Run the python job in RMS to generate the new petrosim jobs and finally update the workflowin RMS by using the generated jobs. @@ -36,56 +38,54 @@ Example of python script in RMS .. code-block:: python - from fmu.tools.rms.generate_petro_jobs_for_field_update import main + from fmu.tools.rms import generate_petro_jobs CONFIG_FILE = "generate_petro_jobs.yml" if __name__ == "__main__": - main(CONFIG_FILE) + generate_petro_jobs(CONFIG_FILE) Example of configuration file for multi zone grid in RMS .. code-block:: yaml - # Name of grid model for the petrophysics jobs - grid_name: "MultiZoneBox" + # 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" + # 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 + # 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"] + # 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"] Example of configuration file for single zone grid in RMS .. code-block:: yaml - # 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"] - - + # 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: + "default": + F1: ["P1", "P2"] + F2: ["P1", "P2"] + F3: ["P1", "P2"] diff --git a/pyproject.toml b/pyproject.toml index af78bf9f..5128e657 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,6 @@ ensemble_well_props = "fmu.tools.ensembles.ensemble_well_props:main" rename_rms_scripts = "fmu.tools.rms.rename_rms_scripts:main" rmsvolumetrics2csv = "fmu.tools.rms.volumetrics:rmsvolumetrics2csv_main" - [tool.ruff] line-length = 88 diff --git a/src/fmu/tools/rms/__init__.py b/src/fmu/tools/rms/__init__.py index 847641b2..0f2d6419 100644 --- a/src/fmu/tools/rms/__init__.py +++ b/src/fmu/tools/rms/__init__.py @@ -2,7 +2,14 @@ Processing of volumetrics from RMS """ +from .generate_petro_jobs_for_field_update import ( + main as generate_petro_jobs, +) from .import_localmodules import import_localmodule from .volumetrics import rmsvolumetrics_txt2df -__all__ = ["rmsvolumetrics_txt2df", "import_localmodule"] +__all__ = [ + "rmsvolumetrics_txt2df", + "import_localmodule", + "generate_petro_jobs", +] 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 index 4a2a8646..7061d3a5 100644 --- a/src/fmu/tools/rms/generate_petro_jobs_for_field_update.py +++ b/src/fmu/tools/rms/generate_petro_jobs_for_field_update.py @@ -1,66 +1,7 @@ """ Description: - This script can be used to generate new petrosim jobs in RMS from an existing - petrosim job. The input (original) petrosim job is assumed to be a job for either - a single zone or multi zone grid where the petrophysical properties are conditioned - to an existing facies realization. - The new generated petrosim jobs will not use facies realization as input, but assume - that all grid cells belongs to the same facies. Hence, if the original petrosim job - specify model parameters for petrophysical properties conditioned to a facies - realization with N different facies, this script can generate up to N new - petrosim jobs, one per facies the user wants to use in field parameter - updating in ERT. - -Input: - An existing RMS project with a petrophysical job conditioning petrophysical - properties on an existing facies realization. - - A yaml format configuration file specifying necessary input to the script. - This includes name of original petrosim job, grid model name and for each zone and - each facies which petrophysical variables to use in the new petrosim jobs that are - created. It is possible to not use all petrophysical variables in the original - job and this can vary from facies to facies and zone to zone. - -Output: - A set of new petrosim jobs, one per facies that is specified in at least - one of the zones. For a multi zone grid, each of the new petrosim jobs will - contain specification of the petrophysical properties that is to be modelled - for each zone for a given facies. The new jobs will get a name reflecting which - facies it belongs to. - -How to use the new petrosim jobs in the RMS workflow: - - When initial ensemble is created (ERT iteration = 0): - The RMS workflow should first use the original petrosim job to generate realizations - of petrophysical properties as usual when not all of the petrophysical properties - are going to be used as field parameters in RMS. If all petrophysical properties are - going to be updated as field parameters in ERT, it is not necessary to do this step. - Add all the new petrosim jobs (one per facies) into the workflow in RMS. - Copy the petrophysical property realizations for each facies from the geomodel grid - into the ERTBOX grid used for field parameter update in ERT. - Export the petrophysical properties for each of the facies to ERT in ROFF format. - Use a script that take as input the facies realization and the petrophysical - properties per facies and use the facies realization as filter to select which - values to copy from the petrophysical realizations to put into the petrophysical - property parameter that was generated by the original job. This will overwrite the - petrophysical properties in the realization from the original job for those - parameters that are used as field parameters to be updated by ERT. - The other petrophysical parameters generated by the original job but not used as - field parameters in ERT will be untouched. - - When updated ensemble is fetched from ERT (ERT iteration > 0): - Run the original petrosim job to generate all petrophysical parameters - in the geogrid. Import the updated field parameters for petrophysical properties - per facies into ERTBOX grid. - Copy the petrophysical property parameters that were updated as - field parameters by ERT into geomodel grid from the ERTBOX grid. - This operation is the same as the last step - when initial ensemble realization was created. This step will then overwrite the - petrophysical property parameters already created by the original job by the new - values updated by ERT. All petrophysical property parameters not updated as field - parameters by ERT will be untouched and will therefore have the same values as they - had after the original petrosim job was run. - + Implementation of a function to create new petrosim jobs, one per facies + given an existing petrosim job using facies realization as input. Summary: @@ -94,6 +35,102 @@ def main(config_file, debug=False, report_unused=False): + """Generate new RMS petrosim jobs from an original petrosim job. + + Description + + This function can be used to generate new petrosim jobs in RMS from an existing + petrosim job. The input (original) petrosim job is assumed to be a job for + either a single zone or multi zone grid where the petrophysical properties + are conditioned to an existing facies realization. + The new generated petrosim jobs will not use facies realization as input, + but assume that all grid cells belongs to the same facies. + Hence, if the original petrosim job specify model parameters for + petrophysical properties conditioned to a facies + realization with N different facies, this script can generate up to N new + petrosim jobs, one per facies the user wants to use in field parameter + updating in ERT. + + Input + + An existing RMS project with a petrophysical job conditioning petrophysical + properties on an existing facies realization. + + A yaml format configuration file specifying necessary input to the function. + This includes name of original petrosim job, grid model name and for each zone + and each facies which petrophysical variables to use in the new petrosim jobs + that are created. It is possible to not use all petrophysical variables in the + original job and this can vary from facies to facies and zone to zone. + + Output + + A set of new petrosim jobs, one per facies that is specified in at least + one of the zones. For a multi zone grid, each of the new petrosim jobs will + contain specification of the petrophysical properties that is to be modelled + for each zone for a given facies. The new jobs will get a name reflecting which + facies it belongs to. + + How to use the new petrosim jobs in the RMS workflow + + When initial ensemble is created (ERT iteration = 0) + + The RMS workflow should first use the original petrosim job to generate + realizations of petrophysical properties as usual when not all of the + petrophysical properties are going to be used as field parameters in RMS. + If all petrophysical properties are going to be updated as field parameters + in ERT, it is not necessary to do this step. + Add all the new petrosim jobs (one per facies) into the workflow in RMS. + Copy the petrophysical property realizations for each facies from the + geomodel grid into the ERTBOX grid used for field parameter update in ERT. + Export the petrophysical properties for each of the facies to ERT in + ROFF format. Use a script that take as input the facies realization and + the petrophysical properties per facies and use the facies realization + as filter to select which values to copy from the petrophysical realizations + to put into the petrophysical property parameter that was generated by + the original job. This will overwrite the petrophysical properties + in the realization from the original job for those + parameters that are used as field parameters to be updated by ERT. + The other petrophysical parameters generated by the original job but not + used as field parameters in ERT will be untouched. + + When updated ensemble is fetched from ERT (ERT iteration > 0): + + Run the original petrosim job to generate all petrophysical parameters + in the geogrid. Import the updated field parameters for petrophysical + properties per facies into ERTBOX grid. + Copy the petrophysical property parameters that were updated as + field parameters by ERT into geomodel grid from the ERTBOX grid. + This operation is the same as the last step + when initial ensemble realization was created. This step will then + overwrite the petrophysical property parameters already created by + the original job by the new values updated by ERT. All petrophysical + property parameters not updated as field parameters by ERT will be + untouched and will therefore have the same values as they + had after the original petrosim job was run. + + + Summary + + The current function is made to simplify the preparation step of the + RMS project by creating the necessary petrosim jobs for a workflow + supporting simultaneous update of both facies and petrophysical + properties in ERT. + + Usage of this function + + Input + Name of config file with specification of which field parameters + to simulate per zone per facies. + + Optional parameters + debug info (True/False), + report field parameters not used (True/False) + + Output + A set of new petro sim jobs to appear in RMS project. + + """ + spec_dict = read_specification_file(config_file) create_new_petro_job_per_facies( spec_dict, debug_print=debug, report_unused=report_unused @@ -310,6 +347,14 @@ def create_new_petro_job_per_facies(spec_dict, debug_print=False, report_unused= original_job_name = spec_dict["original_job_name"] used_petro_per_zone_per_facies_dict = spec_dict["used_petro_var"] + if len(used_petro_per_zone_per_facies_dict.keys()) == 1: + # Only one zone, set zone name to empty + zone_name = list(used_petro_per_zone_per_facies_dict.keys())[0] + tmp = {} + tmp[""] = used_petro_per_zone_per_facies_dict[zone_name] + used_petro_per_zone_per_facies_dict = tmp + tmp = None + # Original job parameter setting owner_string_list = [GRID_MODELS, grid_name, GRID] check_consistency( 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 index dfa2bb1c..643dd500 100644 --- a/tests/rms/generate_jobs_testdata/generate_petro_single_zone_jobs.yml +++ b/tests/rms/generate_jobs_testdata/generate_petro_single_zone_jobs.yml @@ -4,11 +4,11 @@ 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. +# Use default 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: - "": + "default": F1: ["P1", "P2"] F2: ["P1", "P2"] F3: ["P1", "P2"] diff --git a/tests/rms/test_generate_petro_jobs_for_field_update.py b/tests/rms/test_generate_petro_jobs_for_field_update.py index c697bc6d..ebe318b8 100644 --- a/tests/rms/test_generate_petro_jobs_for_field_update.py +++ b/tests/rms/test_generate_petro_jobs_for_field_update.py @@ -44,7 +44,7 @@ RESULTDIR = TMPD / "jobs" RESULTDIR.mkdir(parents=True, exist_ok=True) -REFERENCE_DIR = Path("rms/generate_jobs_testdata") +REFERENCE_DIR = Path("tests/rms/generate_jobs_testdata") CONFIG_FILE_ORIGINAL_SINGLE_ZONE_JOB = ( REFERENCE_DIR / "generate_original_single_zone_job.yml" ) @@ -73,7 +73,7 @@ @pytest.mark.parametrize( "original_variable_names, facies_name, new_variable_names_input, " - "original_Lcorr_mat, reference_var_names, reference_corr_mat", + "original_lower_corr_mat, reference_var_names, reference_corr_mat", [ ( ["phit", "vsh", "klogh", "vphyl"], @@ -105,23 +105,23 @@ def test_define_new_variable_names_and_correlation_matrix( original_variable_names, facies_name, new_variable_names_input, - original_Lcorr_mat, + original_lower_corr_mat, reference_var_names, reference_corr_mat, ): - new_variable_names, new_Lcorr_mat = ( + new_variable_names, new_lower_corr_mat = ( define_new_variable_names_and_correlation_matrix( original_variable_names, facies_name, new_variable_names_input, - original_Lcorr_mat, + original_lower_corr_mat, ) ) print(f"Original var names: {original_variable_names}") print(f"New var names: {new_variable_names}") - print(f"Original correlations: {original_Lcorr_mat}") - print(f"New correlations: {new_Lcorr_mat}") + print(f"Original correlations: {original_lower_corr_mat}") + print(f"New correlations: {new_lower_corr_mat}") # Compare lists with reference l1 = new_variable_names @@ -130,13 +130,13 @@ def test_define_new_variable_names_and_correlation_matrix( assert len(res) == 0 # Compare corr matrix with reference - nrows = len(new_Lcorr_mat) + nrows = len(new_lower_corr_mat) assert nrows == len(reference_corr_mat) for j in range(nrows): - ncol = len(new_Lcorr_mat[j]) + ncol = len(new_lower_corr_mat[j]) assert ncol == len(reference_corr_mat[j]) for i in range(ncol): - assert new_Lcorr_mat[j][i] == reference_corr_mat[j][i] + assert new_lower_corr_mat[j][i] == reference_corr_mat[j][i] def create_original_petro_job(spec_dict): @@ -269,8 +269,6 @@ def rename_subgrids(xtgeo_grd): # 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. @@ -363,11 +361,11 @@ def test_generate_jobs(): filename = RESULTDIR / Path(job_name + "_single.txt") reference_filename = 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 + # 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 for n, job_name in enumerate(job_name_list): petro_job = roxar.jobs.Job.get_job(OWNER_STRING_SINGLE_ZONE, JOB_TYPE, job_name) @@ -381,11 +379,11 @@ def test_generate_jobs(): filename = RESULTDIR / Path(job_name + "_multi.txt") reference_filename = 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 - check = filecmp.cmp(filename, reference_filename) - if check: - print("Check OK for multi zone grid") - assert check + # Compare text files with job parameters with reference for single zone jobs + check = filecmp.cmp(filename, reference_filename) + if check: + print("Check OK for multi zone grid") + assert check for n, job_name in enumerate(job_name_list): petro_job = roxar.jobs.Job.get_job(OWNER_STRING_MULTI_ZONE, JOB_TYPE, job_name)