Skip to content

Commit 1c43e2d

Browse files
committed
Use active realization list and add difference calculation of GRF posterior minus GRF prior
1 parent 88003f2 commit 1c43e2d

2 files changed

Lines changed: 209 additions & 38 deletions

File tree

src/subscript/field_statistics/field_statistics.py

Lines changed: 85 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,6 +1080,7 @@ def get_specifications(
10801080
use_population_stdev = input_dict[key]
10811081

10821082
use_geogrid_fields = False
1083+
geogrid_name = None
10831084
zone_names_used = None
10841085
zone_conformity = None
10851086
zone_code_names = None
@@ -1339,6 +1340,11 @@ def calc_stats(
13391340
if not use_geogrid_fields:
13401341
return
13411342

1343+
# Get list of active realization (Must be active for all iterations in iter_list)
1344+
active_real, number_of_skipped = get_active_real(
1345+
iter_list, ens_path, nreal, geogrid_name
1346+
)
1347+
13421348
ensemble_path = ens_path
13431349

13441350
logger.info(f"Number of realizations: {nreal}")
@@ -1356,8 +1362,8 @@ def calc_stats(
13561362
(ertbox_size[0], ertbox_size[1], ertbox_size[2], nreal),
13571363
dtype=np.float32,
13581364
)
1359-
number_of_skipped = 0
1360-
for real_number in range(nreal):
1365+
1366+
for real_number in active_real:
13611367
grid_dimensions, subgrids, property_param = (
13621368
read_ensemble_realization(
13631369
ensemble_path,
@@ -1368,11 +1374,7 @@ def calc_stats(
13681374
geogrid_name,
13691375
)
13701376
)
1371-
if grid_dimensions is None:
1372-
txt = f" Skip non-existing realization: {real_number}"
1373-
logger.info(txt)
1374-
number_of_skipped += 1
1375-
continue
1377+
assert grid_dimensions is not None
13761378
ertbox_prop_values = get_values_in_ertbox(
13771379
grid_dimensions,
13781380
subgrids,
@@ -1445,8 +1447,8 @@ def calc_stats(
14451447
(ertbox_size[0], ertbox_size[1], ertbox_size[2], nreal),
14461448
dtype=np.int32,
14471449
)
1448-
number_of_skipped = 0
1449-
for real_number in range(nreal):
1450+
1451+
for real_number in active_real:
14501452
grid_dimensions, subgrids, property_param = (
14511453
read_ensemble_realization(
14521454
ensemble_path,
@@ -1457,11 +1459,7 @@ def calc_stats(
14571459
geogrid_name,
14581460
)
14591461
)
1460-
1461-
if grid_dimensions is None:
1462-
logger.info(f" Skip realization: {real_number}")
1463-
number_of_skipped += 1
1464-
continue
1462+
assert grid_dimensions is not None
14651463
ertbox_prop_values = get_values_in_ertbox(
14661464
grid_dimensions,
14671465
subgrids,
@@ -1576,7 +1574,8 @@ def calc_temporary_field_stats(
15761574
# Check if any need to continue to calculation
15771575
if not use_temporary_fields:
15781576
return
1579-
1577+
# Get list of active realization (Must be active for all iterations in iter_list)
1578+
active_real, number_of_skipped = get_active_real(iter_list, ens_path, nreal)
15801579
# Import realizations of temporary field parameters
15811580
for param_name in param_list:
15821581
for iteration in iter_list:
@@ -1590,21 +1589,15 @@ def calc_temporary_field_stats(
15901589
(ertbox_size[0], ertbox_size[1], ertbox_size[2], nreal),
15911590
dtype=np.float32,
15921591
)
1593-
1594-
number_of_skipped = 0
1595-
for real_number in range(nreal):
1592+
for real_number in active_real:
15961593
filepath = (
15971594
ens_path
15981595
/ Path(
15991596
"realization-" + str(real_number) + "/iter-" + str(iteration)
16001597
)
16011598
/ Path(full_param_filename)
16021599
)
1603-
if not filepath.exists():
1604-
txt = f" Skip non-existing realization: {real_number}"
1605-
logger.info(txt)
1606-
number_of_skipped += 1
1607-
continue
1600+
assert filepath.exists()
16081601
property = xtgeo.gridproperty_from_file(filepath, fformat="roff")
16091602
values = property.values
16101603
all_values[:, :, :, real_number] = values
@@ -1657,6 +1650,38 @@ def calc_temporary_field_stats(
16571650
xtgeo_ertbox_stdev.to_file(result_stdev_file_path, fformat="roff")
16581651

16591652

1653+
def get_active_real(
1654+
iter_list: list, ens_path: Path, nreal: int, geogrid_name: str = ""
1655+
):
1656+
"""Get a list of active realizations"""
1657+
number_of_skipped = 0
1658+
active_real = []
1659+
for real_number in range(nreal):
1660+
real_exist = True
1661+
for iteration in iter_list:
1662+
ensemble_path = ens_path / Path(
1663+
"realization-" + str(real_number) + "/iter-" + str(iteration)
1664+
)
1665+
if len(geogrid_name) > 0:
1666+
grid_path = Path("share/results/grids/" + geogrid_name + ".roff")
1667+
file_path_grid = ensemble_path / grid_path
1668+
txt = f" Skip non-existing realization of grid: {real_number}"
1669+
else:
1670+
file_path_grid = ensemble_path
1671+
txt = f" Skip non-existing realization: {real_number}"
1672+
if not file_path_grid.exists():
1673+
logger.info(txt)
1674+
number_of_skipped += 1
1675+
# No need to check other iterations since active_real should
1676+
# only be those realizations that exists for all specified
1677+
# iterations in iter_list
1678+
real_exist = False
1679+
continue
1680+
if real_exist:
1681+
active_real.append(real_number)
1682+
return active_real, number_of_skipped
1683+
1684+
16601685
def generate_script(
16611686
rms_load_script, ert_config_path, result_path, field_stat_config_file
16621687
):
@@ -1853,25 +1878,47 @@ def main():
18531878
if init_path and param_names:
18541879
for param_name in param_names:
18551880
for iteration in iter_list:
1856-
new_name = "mean_" + param_name + "_" + str(iteration)
1857-
param_file_name = Path(result_path) / Path(new_name + ".roff")
1858-
prop_param = xtgeo.gridproperty_from_file(
1859-
param_file_name, fformat="roff"
1881+
mean_name = "mean_" + param_name + "_" + str(iteration)
1882+
std_name = "stdev_" + param_name + "_" + str(iteration)
1883+
1884+
mean_param_file_name = Path(result_path) / Path(mean_name + ".roff")
1885+
mean_prop_param = xtgeo.gridproperty_from_file(
1886+
mean_param_file_name, fformat="roff"
18601887
)
1861-
print(f"Read: {{new_name}} into {{GRIDNAME}}")
1862-
if label:
1863-
new_name = new_name + "_" + label
1864-
prop_param.to_roxar(PRJ, GRIDNAME, new_name)
1888+
print(f"Read: {{mean_name}} into {{GRIDNAME}}")
18651889
1866-
new_name = "stdev_" + param_name + "_" + str(iteration)
1867-
param_file_name = Path(result_path) / Path(new_name + ".roff")
1868-
prop_param = xtgeo.gridproperty_from_file(
1869-
param_file_name, fformat="roff"
1890+
std_param_file_name = Path(result_path) / Path(std_name + ".roff")
1891+
std_prop_param = xtgeo.gridproperty_from_file(
1892+
std_param_file_name, fformat="roff"
18701893
)
1871-
print(f"Read: {{new_name}} into {{GRIDNAME}}")
1894+
print(f"Read: {{std_name}} into {{GRIDNAME}}")
1895+
18721896
if label:
1873-
new_name = new_name + "_" + label
1874-
prop_param.to_roxar(PRJ, GRIDNAME, new_name)
1897+
new_mean_name = mean_name + "_" + label
1898+
new_std_name = std_name + "_" + label
1899+
difference_mean_name = "diff_" + new_mean_name
1900+
difference_std_name = "diff_" + new_std_name
1901+
1902+
mean_prop_param.to_roxar(PRJ, GRIDNAME, new_mean_name)
1903+
std_prop_param.to_roxar(PRJ, GRIDNAME, new_std_name)
1904+
1905+
if iteration == iter_list[0]:
1906+
# Init
1907+
mean_prop_param_init = mean_prop_param
1908+
std_prop_param_init = std_prop_param
1909+
elif iteration == iter_list[-1]:
1910+
mean_prop_param_upd = mean_prop_param
1911+
std_prop_param_upd = std_prop_param
1912+
diff_mean_prop_param = mean_prop_param_upd.copy()
1913+
diff_std_prop_param = std_prop_param_upd.copy()
1914+
diff_mean_prop_param.values = \
1915+
mean_prop_param_upd.values - mean_prop_param_init.values
1916+
diff_std_prop_param.values = \
1917+
std_prop_param_upd.values - std_prop_param_init.values
1918+
diff_mean_prop_param.to_roxar(PRJ,
1919+
GRIDNAME, difference_mean_name)
1920+
diff_std_prop_param.to_roxar(PRJ,
1921+
GRIDNAME, difference_std_name)
18751922
18761923
if __name__ == "__main__":
18771924
main()

tests/test_field_statistics.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
from pathlib import Path
55

66
import fmu.config.utilities as utils
7+
import gaussianfft as sim
78
import numpy as np
89
import pytest
910
import xtgeo
1011

1112
from subscript.field_statistics.field_statistics import (
1213
calc_stats,
14+
calc_temporary_field_stats,
1315
check_disc_param_name_dict,
1416
check_param_name_dict,
1517
check_use_zones,
@@ -1101,3 +1103,125 @@ def test_main(tmp_path, config_file, config_dict, print_info=True):
11011103
# For this test not to fail, the CONFIG_DICT and the specified
11021104
# config file in yaml format must define the same setup
11031105
assert compare_with_referencedata(ens_path, result_path, print_check=True)
1106+
1107+
1108+
def simulate_ensembles(
1109+
nreal: int, nx: int, ny: int, nz: int, nreal_lost: int, result_dir: str
1110+
) -> None:
1111+
sim.seed(123456)
1112+
variogram = sim.variogram("exponential", 100.0, 50.0, 5.0, 45.0, 0.0)
1113+
dx = 5.0
1114+
dy = 5.0
1115+
dz = 1.0
1116+
init_ens_field_3d = np.zeros((nx, ny, nz, nreal), dtype=np.float32)
1117+
final_ens_field_3d = np.zeros((nx, ny, nz, nreal - nreal_lost), dtype=np.float32)
1118+
for i in range(nreal):
1119+
gauss_vector = sim.simulate(variogram, nx, dx, ny, dy, nz, dz)
1120+
field_3d = gauss_vector.reshape((nx, ny, nz), order="F")
1121+
init_ens_field_3d[:, :, :, i] = field_3d
1122+
if i < (nreal - nreal_lost):
1123+
final_ens_field_3d[:, :, :, i] = field_3d
1124+
write_ensemble(init_ens_field_3d, final_ens_field_3d, result_dir)
1125+
1126+
1127+
def write_ensemble(init_ens_field_3d, final_ens_field_3d, result_dir):
1128+
nx, ny, nz, nreal = init_ens_field_3d.shape
1129+
_, _, _, nreal_final = final_ens_field_3d.shape
1130+
1131+
init_real_defined = np.arange(nreal)
1132+
final_real_defined = np.arange(nreal_final)
1133+
for iter in [0, 1]:
1134+
if iter == 0:
1135+
for i in init_real_defined:
1136+
real_path = (
1137+
Path(result_dir)
1138+
/ Path("realization-" + str(i))
1139+
/ Path("iter-" + str(iter))
1140+
)
1141+
real_path /= Path("rms/output/aps")
1142+
values = init_ens_field_3d[:, :, :, i]
1143+
name = "GRF"
1144+
xtgeo_param = xtgeo.GridProperty(
1145+
ncol=nx,
1146+
nrow=ny,
1147+
nlay=nz,
1148+
name=name,
1149+
roxar_dtype=np.float32,
1150+
values=values,
1151+
)
1152+
if not real_path.exists():
1153+
real_path.mkdir(parents=True, exist_ok=True)
1154+
real_path /= Path("GRF.roff")
1155+
xtgeo_param.to_file(real_path, fformat="roff")
1156+
else:
1157+
for i in final_real_defined:
1158+
real_path = (
1159+
Path(result_dir)
1160+
/ Path("realization-" + str(i))
1161+
/ Path("iter-" + str(iter))
1162+
)
1163+
values = final_ens_field_3d[:, :, :, i]
1164+
name = "GRF"
1165+
xtgeo_param = xtgeo.GridProperty(
1166+
ncol=nx,
1167+
nrow=ny,
1168+
nlay=nz,
1169+
name=name,
1170+
roxar_dtype=np.float32,
1171+
values=values,
1172+
)
1173+
if not real_path.exists():
1174+
real_path.mkdir(parents=True, exist_ok=True)
1175+
real_path /= Path("GRF.roff")
1176+
xtgeo_param.to_file(real_path, fformat="roff")
1177+
1178+
1179+
@pytest.mark.parametrize(
1180+
"nreal, nreal_lost",
1181+
[
1182+
(10, 0),
1183+
(10, 2),
1184+
(10, 8), # Number of realizations must be at least 2
1185+
],
1186+
)
1187+
def test_compare_mean_stdev_of_ensembles(tmp_path, nreal, nreal_lost):
1188+
nx = 5
1189+
ny = 5
1190+
nz = 2
1191+
field_name = "GRF"
1192+
simulate_ensembles(nreal, nx, ny, nz, nreal_lost, tmp_path)
1193+
ertbox_size = (nx, ny, nz)
1194+
input_dict = {
1195+
"nreal": nreal,
1196+
"iterations": [0, 1],
1197+
"use_population_stdev": False,
1198+
"temporary_ertbox_fields": {
1199+
"initial_relative_path": "rms/output/aps",
1200+
"parameter_names": [field_name],
1201+
},
1202+
}
1203+
ens_path = tmp_path
1204+
result_path = tmp_path
1205+
ert_config_path = None
1206+
print("Calculate mean and stdev of ensembles")
1207+
calc_temporary_field_stats(
1208+
input_dict, ens_path, result_path, ert_config_path, ertbox_size
1209+
)
1210+
compare_ensemble_stats(result_path, field_name)
1211+
1212+
1213+
def compare_ensemble_stats(result_path, field_name, tolerance=1e-8):
1214+
print("Compare mean and stdev of two ensembles")
1215+
mean_file_name1 = Path(result_path) / Path("mean_" + field_name + "_0.roff")
1216+
mean_file_name2 = Path(result_path) / Path("mean_" + field_name + "_1.roff")
1217+
xtgeo_mean1_field = xtgeo.gridproperty_from_file(mean_file_name1, fformat="roff")
1218+
xtgeo_mean2_field = xtgeo.gridproperty_from_file(mean_file_name2, fformat="roff")
1219+
diff_values = np.abs(xtgeo_mean1_field.values - xtgeo_mean2_field.values)
1220+
assert np.all(diff_values < tolerance)
1221+
1222+
sdev_file_name1 = Path(result_path) / Path("stdev_" + field_name + "_0.roff")
1223+
sdev_file_name2 = Path(result_path) / Path("stdev_" + field_name + "_1.roff")
1224+
xtgeo_sdev1_field = xtgeo.gridproperty_from_file(sdev_file_name1, fformat="roff")
1225+
xtgeo_sdev2_field = xtgeo.gridproperty_from_file(sdev_file_name2, fformat="roff")
1226+
diff_values = np.abs(xtgeo_sdev1_field.values - xtgeo_sdev2_field.values)
1227+
assert np.all(diff_values < tolerance)

0 commit comments

Comments
 (0)