def test_grid_from_cp_simple_inactive_nogeom(example_model_and_crs): # Arrange model, crs = example_model_and_crs corns = simple_grid_corns(undefined=True) expected_bool = np.ones((2, 2, 2)) expected_bool[0, 0, 0] = 0 active_mask = np.ones((2, 2, 2)) active_mask[1, 1, 1] = 0 expected_active = active_mask.copy() expected_active[0, 0, 0] = 0 expected_inactive = ~expected_active.astype(bool) # Act grid = rqi.grid_from_cp(model, cp_array=corns, crs_uuid=crs.uuid, ijk_handedness=None, geometry_defined_everywhere=False, active_mask=active_mask) # Assert assert grid is not None assert_array_almost_equal(grid.extent_kji, (2, 2, 2)) assert grid.grid_is_right_handed assert grid.k_gaps is None assert grid.k_direction_is_down assert grid.pillar_shape == 'curved' assert grid.crs_uuid == crs.uuid assert not grid.geometry_defined_for_all_cells_cached assert_array_almost_equal(grid.array_cell_geometry_is_defined, expected_active) assert_array_almost_equal(grid.inactive, expected_inactive)
def test_grid_from_cp_simple(example_model_and_crs): # Arrange model, crs = example_model_and_crs corns = simple_grid_corns() expected_points = np.array([[[[0, 2, 0], [1, 2, 0], [2, 2, 0]], [[0, 1, 0], [1, 1, 0], [2, 1, 0]], [[0, 0, 0], [1, 0, 0], [2, 0, 0]]], [[[0, 2, 1], [1, 2, 1], [2, 2, 1]], [[0, 1, 1], [1, 1, 1], [2, 1, 1]], [[0, 0, 1], [1, 0, 1], [2, 0, 1]]], [[[0, 2, 2], [1, 2, 2], [2, 2, 2]], [[0, 1, 2], [1, 1, 2], [2, 1, 2]], [[0, 0, 2], [1, 0, 2], [2, 0, 2]]]]) # Act grid = rqi.grid_from_cp(model, cp_array=corns, crs_uuid=crs.uuid, ijk_handedness=None) # Assert assert grid is not None assert_array_almost_equal(grid.extent_kji, (2, 2, 2)) assert grid.grid_is_right_handed assert grid.k_gaps is None assert grid.k_direction_is_down assert grid.pillar_shape == 'curved' assert grid.crs_uuid == crs.uuid assert grid.geometry_defined_for_all_cells_cached assert_array_almost_equal(grid.points_cached, expected_points)
def test_grid_from_cp_kgap(example_model_and_crs): # Arrange model, crs = example_model_and_crs corns = simple_grid_corns(k_gap=True) # Act grid = rqi.grid_from_cp(model, cp_array=corns, crs_uuid=crs.uuid) # Assert assert grid is not None assert_array_almost_equal(grid.extent_kji, (2, 2, 2)) assert grid.grid_is_right_handed assert grid.k_gaps == 1 assert grid.k_direction_is_down
def _refined_faulted_grid(model, source_grid, fine_coarse): source_grid.corner_points(cache_cp_array=True) fnk, fnj, fni = fine_coarse.fine_extent_kji fine_cp = np.empty((fnk, fnj, fni, 2, 2, 2, 3)) for ck0 in range(source_grid.nk): fine_k_base = fine_coarse.fine_base_for_coarse_axial(0, ck0) k_ratio = fine_coarse.ratio(0, ck0) k_interp = np.ones((k_ratio + 1, )) k_interp[:-1] = fine_coarse.interpolation(0, ck0) for cj0 in range(source_grid.nj): fine_j_base = fine_coarse.fine_base_for_coarse_axial(1, cj0) j_ratio = fine_coarse.ratio(1, cj0) j_interp = np.ones((j_ratio + 1, )) j_interp[:-1] = fine_coarse.interpolation(1, cj0) for ci0 in range(source_grid.ni): fine_i_base = fine_coarse.fine_base_for_coarse_axial(2, ci0) i_ratio = fine_coarse.ratio(2, ci0) i_interpolation = fine_coarse.interpolation(2, ci0) i_interp = np.ones((i_ratio + 1, )) i_interp[:-1] = fine_coarse.interpolation(2, ci0) shared_fine_points = source_grid.interpolated_points( (ck0, cj0, ci0), (k_interp, j_interp, i_interp)) fine_cp[fine_k_base : fine_k_base + k_ratio, fine_j_base : fine_j_base + j_ratio, fine_i_base : fine_i_base + i_ratio, 0, 0, 0] = \ shared_fine_points[:-1, :-1, :-1] fine_cp[fine_k_base : fine_k_base + k_ratio, fine_j_base : fine_j_base + j_ratio, fine_i_base : fine_i_base + i_ratio, 0, 0, 1] = \ shared_fine_points[:-1, :-1, 1:] fine_cp[fine_k_base : fine_k_base + k_ratio, fine_j_base : fine_j_base + j_ratio, fine_i_base : fine_i_base + i_ratio, 0, 1, 0] = \ shared_fine_points[:-1, 1:, :-1] fine_cp[fine_k_base : fine_k_base + k_ratio, fine_j_base : fine_j_base + j_ratio, fine_i_base : fine_i_base + i_ratio, 0, 1, 1] = \ shared_fine_points[:-1, 1:, 1:] fine_cp[fine_k_base : fine_k_base + k_ratio, fine_j_base : fine_j_base + j_ratio, fine_i_base : fine_i_base + i_ratio, 1, 0, 0] = \ shared_fine_points[1:, :-1, :-1] fine_cp[fine_k_base : fine_k_base + k_ratio, fine_j_base : fine_j_base + j_ratio, fine_i_base : fine_i_base + i_ratio, 1, 0, 1] = \ shared_fine_points[1:, :-1, 1:] fine_cp[fine_k_base : fine_k_base + k_ratio, fine_j_base : fine_j_base + j_ratio, fine_i_base : fine_i_base + i_ratio, 1, 1, 0] = \ shared_fine_points[1:, 1:, :-1] fine_cp[fine_k_base : fine_k_base + k_ratio, fine_j_base : fine_j_base + j_ratio, fine_i_base : fine_i_base + i_ratio, 1, 1, 1] = \ shared_fine_points[1:, 1:, 1:] return rqi.grid_from_cp( model, fine_cp, source_grid.crs_uuid, ijk_handedness='right' if source_grid.grid_is_right_handed else 'left')
def test_grid_from_cp_simple_straight(example_model_and_crs): # Arrange model, crs = example_model_and_crs corns = simple_grid_corns() # Act grid = rqi.grid_from_cp(model, cp_array=corns, crs_uuid=crs.uuid, known_to_be_straight=True) # Assert assert grid is not None assert_array_almost_equal(grid.extent_kji, (2, 2, 2)) assert grid.grid_is_right_handed assert grid.k_gaps is None assert grid.k_direction_is_down assert grid.pillar_shape == 'straight'
def test_grid_from_cp_simple_left(example_model_and_crs): # Arrange model, crs = example_model_and_crs corns = simple_grid_corns(righthanded=False) # Act grid = rqi.grid_from_cp(model, cp_array=corns, crs_uuid=crs.uuid, ijk_handedness=None) # Assert assert grid is not None assert_array_almost_equal(grid.extent_kji, (2, 2, 2)) assert not grid.grid_is_right_handed assert grid.k_gaps is None assert grid.k_direction_is_down assert grid.pillar_shape == 'curved' assert grid.crs_uuid == crs.uuid
def test_grid_from_cp_kgap_zvoid(example_model_and_crs): # Arrange model, crs = example_model_and_crs corns = simple_grid_corns() # Add a k-gap corns[1, :, :, :, :, :, 2] += 0.5 # Act grid = rqi.grid_from_cp(model, cp_array=corns, crs_uuid=crs.uuid, max_z_void=1) # Assert assert grid is not None assert_array_almost_equal(grid.extent_kji, (2, 2, 2)) assert grid.grid_is_right_handed assert grid.k_gaps is None assert grid.k_direction_is_down
def s_bend_k_gap_grid(s_bend_model): nk = 5 nj = 12 ni_tail = 5 ni_bend = 18 ni_half_mid = 2 ni = 2 * (ni_tail + ni_bend + ni_half_mid) total_thickness = 12.0 layer_thickness = total_thickness / float(nk) flat_dx_di = 10.0 horst_dx_dk = 0.25 * flat_dx_di / float(nk) horst_dz = 1.73 * layer_thickness horst_half_dx = horst_dx_dk * horst_dz / layer_thickness dy_dj = 8.0 top_depth = 100.0 bend_theta_di = maths.pi / float(ni_bend) outer_radius = 2.0 * total_thickness bend_a_centre_xz = (flat_dx_di * float(ni_tail), top_depth + outer_radius) bend_b_centre_xz = (flat_dx_di * float(ni_tail - 2.0 * ni_half_mid), top_depth + 3.0 * outer_radius - total_thickness) points = np.empty((nk + 1, nj + 1, ni + 1, 3)) for k in range(nk + 1): if k == nk // 2 + 1: points[k] = points[k - 1] # pinched out layer else: for i in range(ni + 1): if i < ni_tail + 1: x = flat_dx_di * float(i) z = top_depth + float( k ) * layer_thickness # will introduce a thick layer after pinchout elif i < ni_tail + ni_bend: theta = (i - ni_tail) * bend_theta_di radius = outer_radius - float(k) * layer_thickness x = bend_a_centre_xz[0] + radius * maths.sin(theta) z = bend_a_centre_xz[1] - radius * maths.cos(theta) elif i < ni_tail + ni_bend + 2 * ni_half_mid + 1: x = flat_dx_di * float(ni_tail - (i - (ni_tail + ni_bend))) z = top_depth + 2.0 * outer_radius - float( k) * layer_thickness elif i < ni_tail + 2 * ni_bend + 2 * ni_half_mid: theta = ( i - (ni_tail + ni_bend + 2 * ni_half_mid)) * bend_theta_di radius = outer_radius - float(nk - k) * layer_thickness x = bend_b_centre_xz[0] - radius * maths.sin(theta) z = bend_b_centre_xz[1] - radius * maths.cos(theta) else: x = flat_dx_di * float((i - (ni - ni_tail)) + ni_tail - 2 * ni_half_mid) if i == ni - 1 or i == ni - 4: x += horst_dx_dk * float(k) elif i == ni - 2 or i == ni - 3: x -= horst_dx_dk * float(k) z = top_depth + 4.0 * outer_radius + float( k) * layer_thickness - 2.0 * total_thickness points[k, :, i] = (x, 0.0, z) for j in range(nj + 1): points[:, j, :, 1] = dy_dj * float(j) grid = grr.Grid(s_bend_model) crs = rqc.Crs(s_bend_model) crs_node = crs.create_xml() grid.grid_representation = 'IjkGrid' grid.extent_kji = np.array((nk, nj, ni), dtype='int') grid.nk, grid.nj, grid.ni = nk, nj, ni grid.k_direction_is_down = True # dominant layer direction, or paleo-direction grid.pillar_shape = 'straight' grid.has_split_coordinate_lines = False grid.k_gaps = None grid.crs_uuid = crs.uuid grid.crs_root = crs_node grid.points_cached = points grid.geometry_defined_for_all_pillars_cached = True grid.geometry_defined_for_all_cells_cached = True grid.grid_is_right_handed = crs.is_right_handed_xyz() cp = grid.corner_points(cache_cp_array=True).copy() bend_theta_di = maths.pi / float(ni_bend) # IK plane faults cp[:, 3:, :, :, :, :, :] += (flat_dx_di * 0.7, 0.0, layer_thickness * 1.3) cp[:, 5:, :, :, :, :, :] += (flat_dx_di * 0.4, 0.0, layer_thickness * 0.9) cp[:, 8:, :, :, :, :, :] += (flat_dx_di * 0.3, 0.0, layer_thickness * 0.6) # JK plane faults cp[:, :, ni_tail + ni_bend // 2:, :, :, :, 0] -= flat_dx_di * 0.57 # horizontal break mid top bend cp[:, :, ni_tail + ni_bend + ni_half_mid:, :, :, :, 2] += layer_thickness * 1.27 # vertical break in mid section # zig-zag fault j_step = nj // (ni_tail - 2) for i in range(ni_tail - 1): j_start = i * j_step if j_start >= nj: break cp[:, j_start:, i, :, :, :, 2] += 1.1 * total_thickness # JK horst blocks cp[:, :, ni - 4, :, :, :, :] -= (horst_half_dx, 0.0, horst_dz) cp[:, :, ni - 3:, :, :, :, 0] -= 2.0 * horst_half_dx cp[:, :, ni - 2, :, :, :, :] += (-horst_half_dx, 0.0, horst_dz) cp[:, :, ni - 1:, :, :, :, 0] -= 2.0 * horst_half_dx # JK horst block mid lower bend bend_horst_dz = horst_dz * maths.tan(bend_theta_di) cp[:, :, ni - (ni_tail + ni_bend // 2 + 1):ni - (ni_tail + ni_bend // 2 - 1), :, :, :, :] -= (horst_dz, 0.0, bend_horst_dz) cp[:, :, ni - (ni_tail + ni_bend // 2 - 1):, :, :, :, 2] -= 2.0 * bend_horst_dz k_gap_grid = rqi.grid_from_cp( s_bend_model, cp, crs.uuid, max_z_void=0.01, split_pillars=True, split_tolerance=0.01, ijk_handedness='right' if grid.grid_is_right_handed else 'left', known_to_be_straight=True) # convert second layer to a K gap k_gap_grid.nk -= 1 k_gap_grid.extent_kji[0] = k_gap_grid.nk k_gap_grid.k_gaps = 1 k_gap_grid.k_gap_after_array = np.zeros(k_gap_grid.nk - 1, dtype=bool) k_gap_grid.k_gap_after_array[0] = True k_gap_grid.k_raw_index_array = np.zeros(k_gap_grid.nk, dtype=int) for k in range(1, k_gap_grid.nk): k_gap_grid.k_raw_index_array[k] = k + 1 # clear some attributes which may no longer be valid k_gap_grid.pinchout = None k_gap_grid.inactive = None k_gap_grid.grid_skin = None if hasattr(k_gap_grid, 'array_thickness'): delattr(k_gap_grid, 'array_thickness') # k_gap_grid.write_hdf5_from_caches() # k_gap_grid.create_xml() return k_gap_grid
def test_s_bend_fn(tmp_path, epc=None): if epc is None: # use pytest temporary directory fixture # https://docs.pytest.org/en/stable/tmpdir.html epc = str(os.path.join(tmp_path, f"{bu.new_uuid()}.epc")) # create s-bend grid nk = 5 nj = 12 ni_tail = 5 ni_bend = 18 ni_half_mid = 2 ni = 2 * (ni_tail + ni_bend + ni_half_mid) total_thickness = 12.0 layer_thickness = total_thickness / float(nk) flat_dx_di = 10.0 horst_dx_dk = 0.25 * flat_dx_di / float(nk) horst_dz = 1.73 * layer_thickness horst_half_dx = horst_dx_dk * horst_dz / layer_thickness dy_dj = 8.0 top_depth = 100.0 assert ni_bend % 2 == 0, 'ni_bend must be even for horizontal faulting' assert ni_tail >= 5, 'ni_tail must be at least 5 for horst blocks' bend_theta_di = maths.pi / float(ni_bend) outer_radius = 2.0 * total_thickness bend_a_centre_xz = (flat_dx_di * float(ni_tail), top_depth + outer_radius) bend_b_centre_xz = (flat_dx_di * float(ni_tail - 2.0 * ni_half_mid), top_depth + 3.0 * outer_radius - total_thickness) points = np.empty((nk + 1, nj + 1, ni + 1, 3)) for k in range(nk + 1): if k == nk // 2 + 1: points[k] = points[k - 1] # pinched out layer else: for i in range(ni + 1): if i < ni_tail + 1: x = flat_dx_di * float(i) z = top_depth + float( k ) * layer_thickness # will introduce a thick layer after pinchout elif i < ni_tail + ni_bend: theta = (i - ni_tail) * bend_theta_di radius = outer_radius - float(k) * layer_thickness x = bend_a_centre_xz[0] + radius * maths.sin(theta) z = bend_a_centre_xz[1] - radius * maths.cos(theta) elif i < ni_tail + ni_bend + 2 * ni_half_mid + 1: x = flat_dx_di * float(ni_tail - (i - (ni_tail + ni_bend))) z = top_depth + 2.0 * outer_radius - float( k) * layer_thickness elif i < ni_tail + 2 * ni_bend + 2 * ni_half_mid: theta = ( i - (ni_tail + ni_bend + 2 * ni_half_mid)) * bend_theta_di radius = outer_radius - float(nk - k) * layer_thickness x = bend_b_centre_xz[0] - radius * maths.sin(theta) z = bend_b_centre_xz[1] - radius * maths.cos(theta) else: x = flat_dx_di * float((i - (ni - ni_tail)) + ni_tail - 2 * ni_half_mid) if i == ni - 1 or i == ni - 4: x += horst_dx_dk * float(k) elif i == ni - 2 or i == ni - 3: x -= horst_dx_dk * float(k) z = top_depth + 4.0 * outer_radius + float( k) * layer_thickness - 2.0 * total_thickness points[k, :, i] = (x, 0.0, z) for j in range(nj + 1): points[:, j, :, 1] = dy_dj * float(j) model = rq.Model(epc_file=epc, new_epc=True, create_basics=True, create_hdf5_ext=True) grid = grr.Grid(model) crs = rqc.Crs(model) crs_node = crs.create_xml() if model.crs_uuid is None: model.crs_uuid = crs.crs_uuid grid.grid_representation = 'IjkGrid' grid.extent_kji = np.array((nk, nj, ni), dtype='int') grid.nk, grid.nj, grid.ni = nk, nj, ni grid.k_direction_is_down = True # dominant layer direction, or paleo-direction grid.pillar_shape = 'straight' grid.has_split_coordinate_lines = False grid.k_gaps = None grid.crs_uuid = crs.uuid grid.crs = crs grid.points_cached = points grid.geometry_defined_for_all_pillars_cached = True grid.geometry_defined_for_all_cells_cached = True grid.grid_is_right_handed = crs.is_right_handed_xyz() grid.write_hdf5_from_caches() grid.create_xml() # create a well trajectory and md datum def df_trajectory(x, y, z): N = len(x) assert len(y) == N and len(z) == N df = pd.DataFrame(columns=['MD', 'X', 'Y', 'Z']) md = np.zeros(N) for n in range(N - 1): md[n + 1] = md[n] + vec.naive_length( (x[n + 1] - x[n], y[n + 1] - y[n], z[n + 1] - z[n])) df.MD = md df.X = x df.Y = y df.Z = z return df x = np.array([ 0.0, flat_dx_di * float(ni_tail) + outer_radius, flat_dx_di * (float(ni_tail) - 0.5), 0.0, -outer_radius ]) y = np.array([ 0.0, dy_dj * 0.5, dy_dj * float(nj) / 2.0, dy_dj * (float(nj) - 0.5), dy_dj * float(nj) ]) z = np.array([ 0.0, top_depth - total_thickness, top_depth + 2.0 * outer_radius - total_thickness / 2.0, top_depth + 3.0 * outer_radius - total_thickness, top_depth + 4.0 * outer_radius ]) df = df_trajectory(x, y, z) datum = rqw.MdDatum(model, crs_uuid=crs.uuid, location=(x[0], y[0], z[0])) datum.create_xml() trajectory = rqw.Trajectory(model, md_datum=datum, data_frame=df, length_uom='m', well_name='ANGLED_WELL') assert bu.matching_uuids(trajectory.crs_uuid, crs.uuid) trajectory.write_hdf5() trajectory.create_xml() # add more wells x = np.array([ 0.0, flat_dx_di * float(ni_tail), flat_dx_di * 2.0 * float(ni_tail - ni_half_mid) + outer_radius, -outer_radius ]) y = np.array([0.0, dy_dj * float(nj) * 0.59, dy_dj * 0.67, dy_dj * 0.5]) z = np.array([ 0.0, top_depth - total_thickness, top_depth + 4.0 * outer_radius - 1.7 * total_thickness, top_depth + 4.0 * outer_radius - 1.7 * total_thickness ]) df = df_trajectory(x, y, z) traj_2 = rqw.Trajectory(model, md_datum=datum, data_frame=df, length_uom='m', well_name='HORST_WELL') traj_2.write_hdf5() traj_2.create_xml() traj_2.control_points x = np.array([0.0, 0.0, 0.0]) y = np.array([0.0, dy_dj * float(nj) * 0.53, dy_dj * float(nj) * 0.53]) z = np.array( [0.0, top_depth - total_thickness, top_depth + 4.0 * outer_radius]) df = df_trajectory(x, y, z) traj_3 = rqw.Trajectory(model, md_datum=datum, data_frame=df, length_uom='m', well_name='VERTICAL_WELL') traj_3.write_hdf5() traj_3.create_xml() traj_3.control_points n_x = flat_dx_di * float(ni_tail) * 0.48 n_y = dy_dj * float(nj) / 9.1 o_y = -dy_dj * 0.45 nd_x = n_y / 3.0 x = np.array([ 0.0, n_x, n_x, n_x + nd_x, n_x + 2.0 * nd_x, n_x + 3.0 * nd_x, n_x + 4.0 * nd_x, n_x + 5.0 * nd_x, n_x + 6.0 * nd_x, n_x + 7.0 * nd_x, n_x + 8.0 * nd_x, n_x + 8.0 * nd_x ]) y = np.array([ 0.0, o_y, o_y + n_y, o_y + 2.0 * n_y, o_y + 3.0 * n_y, o_y + 4.0 * n_y, o_y + 5.0 * n_y, o_y + 6.0 * n_y, o_y + 7.0 * n_y, o_y + 8.0 * n_y, o_y + 9.0 * n_y, o_y + 10.0 * n_y ]) n_z1 = top_depth + total_thickness * 0.82 n_z2 = top_depth - total_thickness * 0.17 z = np.array([ 0.0, n_z1, n_z1, n_z2, n_z2, n_z1, n_z1, n_z2, n_z2, n_z1, n_z1, n_z2 ]) df = df_trajectory(x, y, z) traj_4 = rqw.Trajectory(model, md_datum=datum, data_frame=df, length_uom='m', well_name='NESSIE_WELL') traj_4.write_hdf5() traj_4.create_xml() traj_4.control_points # block wells against grid geometry log.info('unfaulted grid blocking of well ' + str(rqw.well_name(trajectory))) bw = rqw.BlockedWell(model, grid=grid, trajectory=trajectory) bw.write_hdf5() bw.create_xml() assert bw.cell_count == 19 assert len(bw.cell_indices) == bw.cell_count np.testing.assert_array_equal( bw.cell_indices, np.array([ 108, 708, 709, 1909, 1959, 2559, 2673, 2073, 2123, 923, 924, 974, 374, 587, 588, 1188, 2388, 2389, 2989 ])) log.info('unfaulted grid blocking of well ' + str(rqw.well_name(traj_2))) bw_2 = rqw.BlockedWell(model, grid=grid, trajectory=traj_2) bw_2.write_hdf5() bw_2.create_xml() assert bw_2.cell_count == 33 assert len(bw_2.cell_indices) == bw_2.cell_count np.testing.assert_array_equal( bw_2.cell_indices, np.array([ 306, 256, 856, 857, 2057, 2058, 2059, 2659, 2660, 2610, 2611, 2612, 2613, 2614, 2014, 2015, 2016, 1966, 766, 767, 167, 649, 648, 647, 646, 645, 644, 643, 642, 1842, 1841, 1840, 2440 ])) log.info('unfaulted grid blocking of well ' + str(rqw.well_name(traj_3))) bw_3 = rqw.BlockedWell(model, grid=grid, trajectory=traj_3) bw_3.write_hdf5() bw_3.create_xml() assert bw_3.cell_count == 18 assert len(bw_3.cell_indices) == bw_3.cell_count np.testing.assert_array_equal( bw_3.cell_indices, np.array([ 300, 900, 2100, 2700, 2729, 2129, 2130, 930, 931, 331, 332, 339, 340, 940, 941, 2141, 2142, 2742 ])) log.info('unfaulted grid blocking of well ' + str(rqw.well_name(traj_4))) bw_4 = rqw.BlockedWell(model, grid=grid, trajectory=traj_4) bw_4.write_hdf5() bw_4.create_xml() assert bw_4.cell_count == 26 assert len(bw_4.cell_indices) == bw_4.cell_count np.testing.assert_array_equal( bw_4.cell_indices, np.array([ 2402, 1802, 1852, 652, 52, 153, 753, 803, 2003, 2603, 2653, 2703, 2103, 903, 904, 304, 354, 454, 1054, 2254, 2304, 2904, 2905, 2955, 2355, 1155 ])) # derive a faulted version of the grid cp = grid.corner_points(cache_cp_array=True).copy() # IK plane faults cp[:, 3:, :, :, :, :, :] += (flat_dx_di * 0.7, 0.0, layer_thickness * 1.3) cp[:, 5:, :, :, :, :, :] += (flat_dx_di * 0.4, 0.0, layer_thickness * 0.9) cp[:, 8:, :, :, :, :, :] += (flat_dx_di * 0.3, 0.0, layer_thickness * 0.6) # JK plane faults cp[:, :, ni_tail + ni_bend // 2:, :, :, :, 0] -= flat_dx_di * 0.57 # horizontal break mid top bend cp[:, :, ni_tail + ni_bend + ni_half_mid:, :, :, :, 2] += layer_thickness * 1.27 # vertical break in mid section # zig-zag fault j_step = nj // (ni_tail - 2) for i in range(ni_tail - 1): j_start = i * j_step if j_start >= nj: break cp[:, j_start:, i, :, :, :, 2] += 1.1 * total_thickness # JK horst blocks cp[:, :, ni - 4, :, :, :, :] -= (horst_half_dx, 0.0, horst_dz) cp[:, :, ni - 3:, :, :, :, 0] -= 2.0 * horst_half_dx cp[:, :, ni - 2, :, :, :, :] += (-horst_half_dx, 0.0, horst_dz) cp[:, :, ni - 1:, :, :, :, 0] -= 2.0 * horst_half_dx # JK horst block mid lower bend bend_horst_dz = horst_dz * maths.tan(bend_theta_di) cp[:, :, ni - (ni_tail + ni_bend // 2 + 1):ni - (ni_tail + ni_bend // 2 - 1), :, :, :, :] -= (horst_dz, 0.0, bend_horst_dz) cp[:, :, ni - (ni_tail + ni_bend // 2 - 1):, :, :, :, 2] -= 2.0 * bend_horst_dz faulted_grid = rqi.grid_from_cp( model, cp, crs.uuid, max_z_void=0.01, split_pillars=True, split_tolerance=0.01, ijk_handedness='right' if grid.grid_is_right_handed else 'left', known_to_be_straight=True) faulted_grid.write_hdf5_from_caches() faulted_grid.create_xml() # create some grid connection sets f_gcs, _ = faulted_grid.fault_connection_set(add_to_model=True) assert f_gcs is not None and f_gcs.count > 0 p_gcs, _ = faulted_grid.pinchout_connection_set(add_to_model=True) assert p_gcs is not None and p_gcs.count > 0 # block wells against faulted grid log.info('faulted grid blocking of well ' + str(rqw.well_name(trajectory))) fbw = rqw.BlockedWell(model, grid=faulted_grid, trajectory=trajectory) fbw.write_hdf5() fbw.create_xml() assert fbw.cell_count == 32 assert len(fbw.cell_indices) == fbw.cell_count np.testing.assert_array_equal( fbw.cell_indices, np.array([ 108, 708, 709, 1909, 1956, 2556, 2673, 2674, 2724, 2124, 2174, 2775, 2175, 2225, 2226, 2276, 2277, 1077, 1078, 1079, 1129, 1130, 1131, 2331, 2332, 2382, 2383, 2384, 2984, 2385, 2386, 2986 ])) log.info('faulted grid blocking of well ' + str(rqw.well_name(traj_2))) fbw_2 = rqw.BlockedWell(model, grid=faulted_grid, trajectory=traj_2) fbw_2.write_hdf5() fbw_2.create_xml() assert fbw_2.cell_count == 26 assert len(fbw_2.cell_indices) == fbw_2.cell_count np.testing.assert_array_equal( fbw_2.cell_indices, np.array([ 254, 854, 2054, 2654, 2655, 2614, 2014, 2015, 1965, 1966, 766, 767, 167, 49, 47, 1846, 45, 44, 43, 643, 642, 641, 1841, 1840, 2440, 2439 ])) log.info('faulted grid blocking of well ' + str(rqw.well_name(traj_3))) fbw_3 = rqw.BlockedWell(model, grid=faulted_grid, trajectory=traj_3) fbw_3.write_hdf5() fbw_3.create_xml() assert fbw_3.cell_count == 14 assert len(fbw_3.cell_indices) == fbw_3.cell_count np.testing.assert_array_equal( fbw_3.cell_indices, np.array([ 2730, 2731, 2131, 2132, 2133, 933, 934, 937, 938, 2138, 2139, 2140, 2740, 2741 ])) log.info('faulted grid blocking of well ' + str(rqw.well_name(traj_4))) fbw_4 = rqw.BlockedWell(model, grid=faulted_grid, trajectory=traj_4) fbw_4.write_hdf5() fbw_4.create_xml() assert fbw_4.cell_count == 16 assert len(fbw_4.cell_indices) == fbw_4.cell_count np.testing.assert_array_equal( fbw_4.cell_indices, np.array([ 2402, 1802, 1852, 652, 52, 202, 802, 2002, 852, 902, 302, 453, 503, 1103, 1153, 553 ])) # create a version of the faulted grid with a k gap k_gap_grid = rqi.grid_from_cp( model, cp, crs.uuid, max_z_void=0.01, split_pillars=True, split_tolerance=0.01, ijk_handedness='right' if grid.grid_is_right_handed else 'left', known_to_be_straight=True) # convert second layer to a K gap k_gap_grid.nk -= 1 k_gap_grid.extent_kji[0] = k_gap_grid.nk k_gap_grid.k_gaps = 1 k_gap_grid.k_gap_after_array = np.zeros(k_gap_grid.nk - 1, dtype=bool) k_gap_grid.k_gap_after_array[0] = True k_gap_grid.k_raw_index_array = np.zeros(k_gap_grid.nk, dtype=int) for k in range(1, k_gap_grid.nk): k_gap_grid.k_raw_index_array[k] = k + 1 # clear some attributes which may no longer be valid k_gap_grid.pinchout = None k_gap_grid.inactive = None k_gap_grid.grid_skin = None if hasattr(k_gap_grid, 'array_thickness'): delattr(k_gap_grid, 'array_thickness') k_gap_grid.write_hdf5_from_caches() k_gap_grid.create_xml() k_gap_grid_uuid = k_gap_grid.uuid # reload k gap grid object to ensure it is properly initialised k_gap_grid = None k_gap_grid = grr.Grid(model, uuid=k_gap_grid_uuid) # block wells against faulted grid with k gap log.info('k gap grid blocking of well ' + str(rqw.well_name(trajectory))) try: gbw = rqw.BlockedWell(model, grid=k_gap_grid, trajectory=trajectory) gbw.write_hdf5() gbw.create_xml() assert gbw.cell_count == 24 except Exception: log.exception('failed to block well against k gap grid') log.info('k gap grid blocking of well ' + str(rqw.well_name(traj_2))) try: gbw_2 = rqw.BlockedWell(model, grid=k_gap_grid, trajectory=traj_2) gbw_2.write_hdf5() gbw_2.create_xml() assert gbw_2.cell_count == 20 except Exception: log.exception('failed to block well against k gap grid') log.info('k gap grid blocking of well ' + str(rqw.well_name(traj_3))) try: gbw_3 = rqw.BlockedWell(model, grid=k_gap_grid, trajectory=traj_3) gbw_3.write_hdf5() gbw_3.create_xml() assert gbw_3.cell_count == 10 except Exception: log.exception('failed to block well against k gap grid') log.info('k gap grid blocking of well ' + str(rqw.well_name(traj_4))) try: gbw_4 = rqw.BlockedWell(model, grid=k_gap_grid, trajectory=traj_4) gbw_4.write_hdf5() gbw_4.create_xml() assert gbw_4.cell_count == 10 except Exception: log.exception('failed to block well against k gap grid') # store model model.store_epc() assert k_gap_grid.k_gaps assert len(k_gap_grid.k_raw_index_array) == k_gap_grid.nk assert len(k_gap_grid.k_gap_after_array) == k_gap_grid.nk - 1 assert k_gap_grid.pinched_out((1, 0, 2)) # check gcs iterator gcs_list = list(model.iter_grid_connection_sets()) assert len(gcs_list) == 2 assert not bu.matching_uuids(gcs_list[0].uuid, gcs_list[1].uuid) # clean up model.h5_release() os.remove(model.h5_file_name())
def import_nexus( resqml_file_root, # output path and file name without .epc or .h5 extension extent_ijk = None, # 3 element numpy vector vdb_file = None, # vdb input file: either this or corp_file should be not None vdb_case = None, # if None, first case in vdb is used (usually a vdb only holds one case) corp_file = None, # corp ascii input file: nexus corp data without keyword corp_bin_file = None, # corp binary file: nexus corp data in bespoke binary format corp_xy_units = 'm', corp_z_units = 'm', corp_z_inc_down = True, ijk_handedness = 'right', corp_eight_mode = False, geometry_defined_everywhere = True, treat_as_nan = None, active_mask_file = None, use_binary = False, # this refers to pure binary arrays, not corp bin format resqml_xy_units = 'm', resqml_z_units = 'm', resqml_z_inc_down = True, shift_to_local = False, local_origin_place = 'centre', # 'centre' or 'minimum' max_z_void = 0.1, # vertical gaps greater than this will introduce k gaps intp resqml grid split_pillars = True, split_tolerance = 0.01, # applies to each of x, y, z differences property_array_files = None, # actually, list of (filename, keyword, uom, time_index, null_value, discrete) summary_file = None, # used to extract timestep dates when loading recurrent data from vdb vdb_static_properties = True, # if True, static vdb properties are imported (only relevant if vdb_file is not None) vdb_recurrent_properties = False, timestep_selection = 'all', # 'first', 'last', 'first and last', 'all', or list of ints being reporting timestep numbers use_compressed_time_series = True, decoarsen = True, # where ICOARSE is present, redistribute data to uncoarse cells ab_property_list = None, # list of (file_name, keyword, property_kind, facet_type, facet, uom, time_index, null_value, discrete) create_property_set = False, ensemble_case_dirs_root = None, # path upto but excluding realisation number ensemble_property_dictionary = None, # dictionary mapping title (or keyword) to (filename, property_kind, facet_type, facet, # uom, time_index, null_value, discrete) ensemble_size_limit = None, grid_title = 'ROOT', mode = 'w', progress_fn = None): """Read a simulation grid geometry and optionally grid properties. Input may be from nexus ascii input files, or nexus vdb output. Arguments: resqml_file_root (str): output path and file name without .epc or .h5 extension extent_ijk (triple float, optional): ijk extents (fortran ordering) vdb_file (str, optional): vdb input file, either this or corp_file should be not None. Required if importing from a vdb vdb_case (str, optional): required if the vdb contains more than one case. If None, first case in vdb is used corp_file (str, optional): required if importing from corp ascii file. corp ascii input file: nexus corp data without keyword corp_bin_file (str, optional): required if importing from corp binary file corp_xy_units (str, default 'm'): xy length units corp_z_units (str, default 'm'): z length units corp_z_inc_down (bool, default True): if True z values increase with depth ijk_handedness (str, default 'right'): 'right' or 'left' corp_eight_mode (bool, default False): if True the ordering of corner point data is in nexus EIGHT mode geometry_defined_everywhere (bool, default True): if False then inactive cells are marked as not having geometry treat_as_nan (float, default None): if a value is provided corner points with this value will be assigned nan active_mask_file (str, default None): ascii property file holding values 0 or 1, with 1 indicating active cells use_binary (bool, default False): if True a cached binary version of ascii files will be used (pure binary, not corp bin format) resqml_xy_units (str, default 'm'): output xy units for resqml file resqml_z_units (str, default 'm'): output z units for resqml file resqml_z_inc_down (bool, default True): if True z values increase with depth for output resqml file shift_to_local (bool, default False): if True then a local origin will be used in the CRS local_origin_place (str, default 'centre'): 'centre' or 'minimum'. If 'centre' the local origin is placed at the centre of the grid; ignored if shift_to_local is False max_z_void (float, default 0.1): maximum z gap between vertically neighbouring corner points. Vertical gaps greater than this will introduce k gaps into resqml grid. Units are corp z units split_pillars (bool, default True): if False an unfaulted grid will be generated split_tolerance (float, default 0.01): maximum distance between neighbouring corner points before a pillar is considered 'split'. Applies to each of x, y, z differences property_array_files (list, default None): list of (filename, keyword, uom, time_index, null_value, discrete) summary_file (str, default None): nexus output summary file, used to extract timestep dates when loading recurrent data from vdb vdb_static_properties (bool, default True): if True, static vdb properties are imported (only relevant if vdb_file is not None) vdb_recurrent_properties (bool, default False): # if True, recurrent vdb properties are imported (only relevant if vdb_file is not None) timestep_selection (str, default 'all): 'first', 'last', 'first and last', 'all', or list of ints being reporting timestep numbers. Ignored if vdb_recurrent_properties is False use_compressed_time_series (bool, default True): generates reduced time series containing timesteps with recurrent properties from vdb, rather than full nexus summary time series decoarsen (bool, default True): where ICOARSE is present, redistribute data to uncoarse cells ab_property_list (list, default None): list of (file_name, keyword, property_kind, facet_type, facet, uom, time_index, null_value, discrete) create_property_set (bool, default False): if True a resqml PropertySet is created ensemble_case_dirs_root (str, default None): path up to but excluding realisation number ensemble_property_dictionary (str, default None): dictionary mapping title (or keyword) to (filename, property_kind, facet_type, facet, uom, time_index, null_value, discrete) ensemble_size_limit (int, default None): if present processing of ensemble will terminate after this number of cases is reached grid_title (str, default 'ROOT'): grid citation title mode (str, default 'w'): 'w' or 'a', mode to write or append to hdf5 progress_fn (function, default None): if present function must have one floating argument with value increasing from 0 to 1, and is called at intervals to indicate progress Returns: resqml model in memory & written to disc """ if resqml_file_root.endswith('.epc'): resqml_file_root = resqml_file_root[:-4] assert mode in ['w', 'a'] if vdb_file: using_vdb = True corp_file = corp_bin_file = None grid_title = grid_title.upper() log.info('starting import of Nexus ' + str(grid_title) + ' corp from vdb ' + str(vdb_file)) tm.log_nexus_tm('info') vdbase = vdb.VDB(vdb_file) case_list = vdbase.cases() assert len(case_list) > 0, 'no cases found in vdb' if vdb_case is None: vdb_case = case_list[0] else: assert vdb_case in case_list, 'case ' + vdb_case + ' not found in vdb: ' + vdb_file vdbase.set_use_case(vdb_case) assert grid_title in vdbase.list_of_grids(), 'grid ' + str(grid_title) + ' not found in vdb' if extent_ijk is not None: vdbase.set_extent_kji(tuple(reversed(extent_ijk))) log.debug('using case ' + vdb_case + ' and grid ' + grid_title + ' from vdb') if vdb_recurrent_properties and not summary_file: if vdb_file.endswith('.vdb.zip'): summary_file = vdb_file[:-8] + '.sum' elif vdb_file.endswith('.vdb') or vdb_file.endswith('.zip'): summary_file = vdb_file[:-4] + '.sum' else: sep = vdb_file.rfind(os.sep) dot = vdb_file[sep + 1:].find('.') if dot > 0: summary_file = vdb_file[:sep + 1 + dot] + ',sum' else: summary_file = vdb_file + '.sum' cp_array = vdbase.grid_corp(grid_title) cp_extent_kji = cp_array.shape[:3] if cp_extent_kji[:2] == (1, 1): # auto determination of extent failed assert extent_ijk is not None, 'failed to determine extent of grid from corp data' (ni, nj, nk) = extent_ijk assert cp_extent_kji[2] == ni * nj * nk, 'number of cells in grid corp does not match extent' cp_extent = (nk, nj, ni, 2, 2, 2, 3) # (nk, nj, ni, kp, jp, ip, xyz) cp_array = cp_array.reshape(cp_extent) elif extent_ijk is not None: for axis in range(3): assert cp_extent_kji[axis] == extent_ijk[ 2 - axis], 'extent of grid corp data from vdb does not match that supplied' elif corp_file or corp_bin_file: if corp_bin_file: corp_file = None using_vdb = False # geometry_defined_everywhere = (active_mask_file is None) log.info('starting import of Nexus corp file ' + str(corp_file if corp_file else corp_bin_file)) tm.log_nexus_tm('info') if extent_ijk is None: # auto detect extent extent_kji = None cp_extent = None else: (ni, nj, nk) = extent_ijk extent_kji = np.array((nk, nj, ni), dtype = 'int') cp_extent = (nk, nj, ni, 2, 2, 2, 3) # (nk, nj, ni, kp, jp, ip, xyz) log.debug('reading and resequencing corp data') if corp_bin_file: # bespoke nexus corp bin format, not to be confused with pure binary files used below cp_array = ld.load_corp_array_from_file( corp_bin_file, extent_kji, corp_bin = True, comment_char = None, # comment char will be detected automatically data_free_of_comments = False, use_binary = use_binary) else: cp_binary_file = abt.cp_binary_filename( corp_file, nexus_ordering = False) # pure binary, not bespoke corp bin used above recent_binary_exists = ld.file_exists(cp_binary_file, must_be_more_recent_than_file = corp_file) cp_array = None if use_binary and (extent_ijk is not None) and recent_binary_exists: try: cp_array = ld.load_array_from_file(cp_binary_file, cp_extent, use_binary = True) except Exception: cp_array = None if cp_array is None: cp_array = ld.load_corp_array_from_file( corp_file, extent_kji, corp_bin = False, comment_char = None, # comment char will be detected automatically data_free_of_comments = False, use_binary = use_binary) if use_binary: wd.write_pure_binary_data(cp_binary_file, cp_array) # NB: this binary file is resequenced, not in nexus ordering! else: raise ValueError('vdb_file and corp_file are both None in import_nexus() call') if cp_array is None: log.error('failed to create corner point array') return None if extent_ijk is None: cp_extent = cp_array.shape extent_kji = cp_extent[:3] (nk, nj, ni) = extent_kji extent_ijk = (ni, nj, nk) else: ni, nj, nk = extent_ijk # convert units log.debug('Converting units') if corp_xy_units == corp_z_units and resqml_xy_units == resqml_z_units: bwam.convert_lengths(cp_array, corp_xy_units, resqml_xy_units) else: bwam.convert_lengths(cp_array[:, :, :, :, :, :, 0:1], corp_xy_units, resqml_xy_units) bwam.convert_lengths(cp_array[:, :, :, :, :, :, 2], corp_z_units, resqml_z_units) # invert z if required if resqml_z_inc_down != corp_z_inc_down: log.debug('Inverting z values') inversion = np.negative(cp_array[:, :, :, :, :, :, 2]) cp_array[:, :, :, :, :, :, 2] = inversion # read active cell mask log.debug('Setting up active cell mask') active_mask = inactive_mask = None if vdb_file: assert vdbase is not None, 'problem with vdb object' inactive_mask = vdbase.grid_kid_inactive_mask(grid_title) # TODO: check conversion of KID to boolean for LGRs if inactive_mask is not None: log.debug('using kid array as inactive cell mask') active_mask = np.logical_not(inactive_mask) else: log.warning('kid array not found, using unpack array as active cell indicator') unp = vdbase.grid_unpack(grid_title) assert unp is not None, 'failed to load active cell indicator mask from vdb kid or unpack arrays' active_mask = np.empty((nk, nj, ni), dtype = 'bool') active_mask[:] = (unp > 0) inactive_mask = np.logical_not(active_mask) elif active_mask_file: active_mask = ld.load_array_from_file(active_mask_file, extent_kji, data_type = 'bool', use_binary = use_binary) if active_mask is None: log.error('failed to load active cell indicator array from file: ' + active_mask_file) else: inactive_mask = np.logical_not(active_mask) # will crash if active mask load failed # shift grid geometry to local crs local_origin = np.zeros(3) if shift_to_local: log.debug('shifting to local origin at ' + local_origin_place) if local_origin_place == 'centre': local_origin = np.nanmean(cp_array, axis = (0, 1, 2, 3, 4, 5)) elif local_origin_place == 'minimum': local_origin = np.nanmin(cp_array, axis = (0, 1, 2, 3, 4, 5)) - 1.0 # The -1 ensures all coords are >0 else: assert (False) cp_array -= local_origin # create empty resqml model log.debug('creating an empty resqml model') if mode == 'w': model = rq.Model(resqml_file_root, new_epc = True, create_basics = True, create_hdf5_ext = True) else: model = rq.Model(resqml_file_root) assert model is not None ext_uuid = model.h5_uuid() assert ext_uuid is not None # create coodinate reference system (crs) in model and set references in grid object log.debug('creating coordinate reference system') crs_uuids = model.uuids(obj_type = 'LocalDepth3dCrs') new_crs = rqc.Crs(model, x_offset = local_origin[0], y_offset = local_origin[1], z_offset = local_origin[2], xy_units = resqml_xy_units, z_units = resqml_z_units, z_inc_down = resqml_z_inc_down) new_crs.create_xml(reuse = True) crs_uuid = new_crs.uuid grid = grid_from_cp(model, cp_array, crs_uuid, active_mask = active_mask, geometry_defined_everywhere = geometry_defined_everywhere, treat_as_nan = treat_as_nan, max_z_void = max_z_void, split_pillars = split_pillars, split_tolerance = split_tolerance, ijk_handedness = ijk_handedness, known_to_be_straight = False) # create hdf5 file using arrays cached in grid above log.info('writing grid geometry to hdf5 file ' + resqml_file_root + '.h5') grid.write_hdf5_from_caches(resqml_file_root + '.h5', mode = mode, write_active = False) # build xml for grid geometry log.debug('building xml for grid') ijk_node = grid.create_xml(ext_uuid = None, title = grid_title, add_as_part = True, add_relationships = True) assert ijk_node is not None, 'failed to create IjkGrid node in xml tree' # impprt property arrays into a collection prop_import_collection = None decoarsen_array = None ts_node = None ts_uuid = None if active_mask is None and grid.inactive is not None: active_mask = np.logical_not(grid.inactive) if using_vdb: prop_import_collection = rp.GridPropertyCollection() if vdb_static_properties: props = vdbase.grid_list_of_static_properties(grid_title) if len(props) > 0: prop_import_collection = rp.GridPropertyCollection() prop_import_collection.set_grid(grid) for keyword in props: prop_import_collection.import_vdb_static_property_to_cache(vdbase, keyword, grid_name = grid_title) # if active_mask is not None: # prop_import_collection.add_cached_array_to_imported_list(active_mask, active_mask_file, 'ACTIVE', property_kind = 'active', # discrete = True, uom = None, time_index = None, null_value = None) elif property_array_files is not None and len(property_array_files) > 0: prop_import_collection = rp.GridPropertyCollection() prop_import_collection.set_grid(grid) for (p_filename, p_keyword, p_uom, p_time_index, p_null_value, p_discrete) in property_array_files: prop_import_collection.import_nexus_property_to_cache(p_filename, p_keyword, grid.extent_kji, discrete = p_discrete, uom = p_uom, time_index = p_time_index, null_value = p_null_value, use_binary = use_binary) # if active_mask is not None: # prop_import_collection.add_cached_array_to_imported_list(active_mask, active_mask_file, 'ACTIVE', property_kind = 'active', # discrete = True, uom = None, time_index = None, null_value = None) # ab_property_list: list of (filename, keyword, property_kind, facet_type, facet, uom, time_index, null_value, discrete) elif ab_property_list is not None and len(ab_property_list) > 0: prop_import_collection = rp.GridPropertyCollection() prop_import_collection.set_grid(grid) for (p_filename, p_keyword, p_property_kind, p_facet_type, p_facet, p_uom, p_time_index, p_null_value, p_discrete) in ab_property_list: prop_import_collection.import_ab_property_to_cache(p_filename, p_keyword, grid.extent_kji, discrete = p_discrete, property_kind = p_property_kind, facet_type = p_facet_type, facet = p_facet, uom = p_uom, time_index = p_time_index, null_value = p_null_value) # if active_mask is not None: # prop_import_collection.add_cached_array_to_imported_list(active_mask, active_mask_file, 'ACTIVE', property_kind = 'active', # discrete = True, uom = None, time_index = None, null_value = None) # ensemble_property_dictionary: mapping title (or keyword) to # (filename, property_kind, facet_type, facet, uom, time_index, null_value, discrete) elif ensemble_case_dirs_root and ensemble_property_dictionary: case_path_list = glob.glob(ensemble_case_dirs_root + '*') assert len(case_path_list) > 0, 'no case directories found with path starting: ' + str(ensemble_case_dirs_root) case_number_place = len(ensemble_case_dirs_root) case_zero_used = False case_count = 0 for case_path in case_path_list: if ensemble_size_limit is not None and case_count >= ensemble_size_limit: log.warning('stopping after reaching ensemble size limit') break # NB. import each case individually rather than holding property arrays for whole ensemble in memory at once prop_import_collection = rp.GridPropertyCollection() prop_import_collection.set_grid(grid) tail = case_path[case_number_place:] try: case_number = int(tail) assert case_number >= 0, 'negative case number encountered' if case_number == 0: assert not case_zero_used, 'more than one case number evaluated to zero' case_zero_used = True except Exception: log.error('failed to determine case number for tail: ' + str(tail)) continue for keyword in ensemble_property_dictionary.keys(): (filename, p_property_kind, p_facet_type, p_facet, p_uom, p_time_index, p_null_value, p_discrete) = ensemble_property_dictionary[keyword] p_filename = os.path.join(case_path, filename) if not os.path.exists(p_filename): log.error('missing property file: ' + p_filename) continue prop_import_collection.import_nexus_property_to_cache(p_filename, keyword, grid.extent_kji, discrete = p_discrete, uom = p_uom, time_index = p_time_index, null_value = p_null_value, property_kind = p_property_kind, facet_type = p_facet_type, facet = p_facet, realization = case_number, use_binary = False) if len(prop_import_collection.imported_list) > 0: # create hdf5 file using arrays cached in grid above log.info('writing properties to hdf5 file ' + str(resqml_file_root) + '.h5 for case: ' + str(case_number)) grid.write_hdf5_from_caches(resqml_file_root + '.h5', geometry = False, imported_properties = prop_import_collection, write_active = False) # add imported properties parts to model, building property parts list prop_import_collection.create_xml_for_imported_list_and_add_parts_to_model(ext_uuid, time_series_uuid = ts_uuid) if create_property_set: prop_import_collection.create_property_set_xml('realisation ' + str(case_number)) case_count += 1 # remove cached static property arrays from memory # prop_import_collection.remove_all_cached_arrays() del prop_import_collection prop_import_collection = None log.info(f'Nexus ascii ensemble input processed {case_count} cases') tm.log_nexus_tm('info') # create hdf5 file using arrays cached in grid above if prop_import_collection is not None and len(prop_import_collection.imported_list) > 0: if decoarsen: decoarsen_array = prop_import_collection.decoarsen_imported_list() if decoarsen_array is not None: log.info('static properties decoarsened') prop_import_collection.add_cached_array_to_imported_list(decoarsen_array, 'decoarsen', 'DECOARSEN', discrete = True, uom = None, time_index = None, null_value = -1, property_kind = 'discrete') log.info('writing ' + str(len(prop_import_collection.imported_list)) + ' properties to hdf5 file ' + resqml_file_root + '.h5') elif not ensemble_case_dirs_root: log.info('no static grid properties to import') prop_import_collection = None grid.write_hdf5_from_caches(resqml_file_root + '.h5', geometry = False, imported_properties = prop_import_collection, write_active = True) # remove cached static property arrays from memory if prop_import_collection is not None: prop_import_collection.remove_all_cached_arrays() ts_selection = None if using_vdb and vdb_recurrent_properties and timestep_selection is not None and str(timestep_selection) != 'none': if prop_import_collection is None: prop_import_collection = rp.GridPropertyCollection() prop_import_collection.set_grid(grid) # extract timestep dates from summary file (this info might be hidden in the recurrent binary files but I couldn't find it # todo: create cut down time series from recurrent files and differentiate between reporting time index and mapped time step number full_time_series = rts.time_series_from_nexus_summary(summary_file) if full_time_series is None: log.error('failed to fetch time series from Nexus summary file; recurrent data excluded') tm.log_nexus_tm('error') else: full_time_series.set_model(model) timestep_list = vdbase.grid_list_of_timesteps( grid_title) # get list of timesteps for which recurrent files exist recur_time_series = None for timestep_number in timestep_list: if isinstance(timestep_selection, list): if timestep_number not in timestep_selection: continue else: if timestep_selection == 'first': if timestep_number != timestep_list[0]: break elif timestep_selection == 'last': if timestep_number != timestep_list[-1]: continue elif timestep_selection == 'first and last': if timestep_number != timestep_list[0] and timestep_number != timestep_list[-1]: continue # default to importing all timesteps stamp = full_time_series.timestamp(timestep_number) if stamp is None: log.error('timestamp number for which recurrent data exists was not found in summary file: ' + str(timestep_number)) continue recur_prop_list = vdbase.grid_list_of_recurrent_properties(grid_title, timestep_number) common_recur_prop_set = set() if recur_time_series is None: recur_time_series = rts.TimeSeries(model, first_timestamp = stamp) if recur_prop_list is not None: common_recur_prop_set = set(recur_prop_list) else: recur_time_series.add_timestamp(stamp) if recur_prop_list is not None: common_recur_prop_set = common_recur_prop_set.intersection(set(recur_prop_list)) step_import_collection = rp.GridPropertyCollection() step_import_collection.set_grid(grid) # for each property for this timestep, cache array and add to recur prop import collection for this time step if recur_prop_list: for keyword in recur_prop_list: if not keyword or not keyword.isalnum(): continue prop_kind, facet_type, facet = rp.property_kind_and_facet_from_keyword(keyword) step_import_collection.import_vdb_recurrent_property_to_cache( vdbase, timestep_number, # also used as time_index? keyword, grid_name = grid_title, property_kind = prop_kind, facet_type = facet_type, facet = facet) # extend hdf5 with cached arrays for this timestep log.info('number of recurrent grid property arrays for timestep: ' + str(timestep_number) + ' is: ' + str(step_import_collection.number_of_imports())) if decoarsen_array is not None: log.info('decoarsening recurrent properties for timestep: ' + str(timestep_number)) step_import_collection.decoarsen_imported_list(decoarsen_array = decoarsen_array) log.info('extending hdf5 file with recurrent properties for timestep: ' + str(timestep_number)) grid.write_hdf5_from_caches(resqml_file_root + '.h5', mode = 'a', geometry = False, imported_properties = step_import_collection, write_active = False) # add imported list for this timestep to full imported list prop_import_collection.inherit_imported_list_from_other_collection(step_import_collection) log.debug('total number of property arrays after timestep: ' + str(timestep_number) + ' is: ' + str(prop_import_collection.number_of_imports())) # remove cached copies of arrays step_import_collection.remove_all_cached_arrays() ts_node = full_time_series.create_xml(title = 'simulator full timestep series') model.time_series = ts_node # save as the primary time series for the model ts_uuid = rqet.uuid_for_part_root(ts_node) # create xml for recur_time_series (as well as for full_time_series) and add as part; not needed? if recur_time_series is not None: rts_node = recur_time_series.create_xml(title = 'simulator recurrent array timestep series') if use_compressed_time_series: ts_uuid = rqet.uuid_for_part_root(rts_node) ts_selection = timestep_list # add imported properties parts to model, building property parts list if prop_import_collection is not None and prop_import_collection.imported_list is not None: prop_import_collection.set_grid(grid) # update to pick up on recently created xml root node for grid prop_import_collection.create_xml_for_imported_list_and_add_parts_to_model( ext_uuid, time_series_uuid = ts_uuid, selected_time_indices_list = ts_selection) if create_property_set: prop_import_collection.create_property_set_xml('property set for import for grid ' + str(grid_title)) # mark model as modified (will already have happened anyway) model.set_modified() # create epc file log.info('storing model in epc file ' + resqml_file_root + '.epc') model.store_epc(resqml_file_root + '.epc') # return resqml model return model