Skip to content

Commit

Permalink
Merge pull request #817 from bp/blocked_well_stat
Browse files Browse the repository at this point in the history
supporting time dependent blocked well connection open property
  • Loading branch information
andy-beer committed Aug 20, 2024
2 parents 0568048 + 74e0897 commit b90b650
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 52 deletions.
134 changes: 82 additions & 52 deletions resqpy/well/_blocked_well.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ def __find_gi_node_and_load_hdf5_array(self, node):

def __find_grid_node(self, node, unique_grid_indices):
"""Find the BlockedWell object's grid reference node(s)."""

grid_node_list = rqet.list_of_tag(node, 'Grid')
assert len(grid_node_list) > 0, 'blocked well grid reference(s) not found in xml'
assert unique_grid_indices[0] >= -1 and unique_grid_indices[-1] < len(
Expand All @@ -344,6 +345,7 @@ def __find_grid_node(self, node, unique_grid_indices):

def extract_property_collection(self, refresh = False):
"""Returns a property collection for the blocked well."""

if self.property_collection is None or refresh:
self.property_collection = rqp.PropertyCollection(support = self)
return self.property_collection
Expand Down Expand Up @@ -407,6 +409,7 @@ def grid_uuid_list(self):

def interval_for_cell(self, cell_index):
"""Returns the interval index for a given cell index (identical if there are no unblocked intervals)."""

assert 0 <= cell_index < self.cell_count
if self.node_count == self.cell_count + 1:
return cell_index
Expand All @@ -424,11 +427,13 @@ def entry_and_exit_mds(self, cell_index):
(float, float) being the entry and exit measured depths for the cell, along the trajectory;
uom is held in trajectory object
"""

interval = self.interval_for_cell(cell_index)
return (self.node_mds[interval], self.node_mds[interval + 1])

def _set_cell_interval_map(self):
"""Sets up an index mapping from blocked cell index to interval index, accounting for unblocked intervals."""

self.cell_interval_map = np.zeros(self.cell_count, dtype = int)
ci = 0
for ii in range(self.node_count - 1):
Expand Down Expand Up @@ -718,6 +723,7 @@ def derive_from_wellspec(self,

def __derive_from_wellspec_check_well_name(self, well_name):
"""Set the well name to be used in the wellspec file."""

if well_name:
self.well_name = well_name
else:
Expand Down Expand Up @@ -1211,6 +1217,7 @@ def import_from_rms_cellio(self,
@staticmethod
def __verify_header_lines_in_cellio_file(fp, well_name, cellio_file):
"""Find and verify the information in the header lines for the specified well in the RMS cellio file."""

while True:
kf.skip_blank_lines_and_comments(fp)
line = fp.readline() # file format version number?
Expand Down Expand Up @@ -1637,41 +1644,46 @@ def dataframe(self,
if skip_interval_due_to_min_kh:
continue

length, radw_i, skin_i, radb, wi, wbc = BlockedWell.__get_pc_arrays_for_interval(pc = pc,
pc_timeless = pc_timeless,
pc_titles = pc_titles,
ci = ci,
length = length,
radw = radw,
skin = skin,
length_uom = length_uom,
grid = grid,
traj_crs = traj_crs)
length, radw_i, skin_i, radb, wi, wbc, stat_i = \
BlockedWell.__get_pc_arrays_for_interval(pc = pc,
pc_timeless = pc_timeless,
pc_titles = pc_titles,
ci = ci,
length = length,
radw = radw,
skin = skin,
stat = stat,
length_uom = length_uom,
grid = grid,
traj_crs = traj_crs)
if skin_i is None:
skin_i = 0.0
if radw_i is None:
radw_i = (0.33 if length_uom == 'ft' else 0.1)

radb, wi, wbc = BlockedWell.__get_well_inflow_parameters_for_interval(do_well_inflow = do_well_inflow,
isotropic_perm = isotropic_perm,
ntg_is_one = ntg_is_one,
k_i = k_i,
k_j = k_j,
k_k = k_k,
sine_anglv = sine_anglv,
cosine_anglv = cosine_anglv,
sine_angla = sine_angla,
cosine_angla = cosine_angla,
grid = grid,
cell_kji0 = cell_kji0,
radw = radw_i,
radb = radb,
wi = wi,
wbc = wbc,
skin = skin_i,
kh = kh,
length_uom = length_uom,
column_list = column_list)
if stat_i is None:
stat_i = stat

radb, wi, wbc = \
BlockedWell.__get_well_inflow_parameters_for_interval(do_well_inflow = do_well_inflow,
isotropic_perm = isotropic_perm,
ntg_is_one = ntg_is_one,
k_i = k_i,
k_j = k_j,
k_k = k_k,
sine_anglv = sine_anglv,
cosine_anglv = cosine_anglv,
sine_angla = sine_angla,
cosine_angla = cosine_angla,
grid = grid,
cell_kji0 = cell_kji0,
radw = radw_i,
radb = radb,
wi = wi,
wbc = wbc,
skin = skin_i,
kh = kh,
length_uom = length_uom,
column_list = column_list)

xyz = self.__get_xyz_for_interval(doing_xyz = doing_xyz,
length_mode = length_mode,
Expand Down Expand Up @@ -1703,7 +1715,7 @@ def dataframe(self,
kh = kh,
xyz = xyz,
md = md,
stat = stat,
stat = stat_i,
part_perf_fraction = part_perf_fraction,
radb = radb,
wi = wi,
Expand Down Expand Up @@ -1751,6 +1763,7 @@ def frame_contributions_list(self, wbf):
fraction of wellbore frame interval in cell,
fraction of cell's wellbore interval in wellbore frame interval)
"""

return bwf.blocked_well_frame_contributions_list(self, wbf)

def add_properties_from_wellbore_frame(self,
Expand Down Expand Up @@ -1965,8 +1978,10 @@ def __check_skin_stat_radw_to_be_added_as_properties(skin, stat, radw, column_li
stat = str(stat).upper()
if 'STAT' not in column_list:
column_list.append('STAT')
else:
stat = 'ON'


# else:
#  stat = 'ON'

if radw is not None and 'RADW' not in column_list:
column_list.append('RADW')
Expand Down Expand Up @@ -2418,29 +2433,37 @@ def __get_kh_if_doing_kh(isotropic_perm, ntg_is_one, length, perm_i_uuid, grid,
return kh

@staticmethod
def __get_pc_arrays_for_interval(pc, pc_timeless, pc_titles, ci, length, radw, skin, length_uom, grid, traj_crs):
def __get_pc_arrays_for_interval(pc, pc_timeless, pc_titles, ci, length, radw, skin, stat, length_uom, grid,
traj_crs):
"""Get the property collection arrays for the interval."""

def get_item(v, title, pc_titles, pc, pc_timeless, ci, uom):

def pk_for_title(title):
d = {'RADW': 'wellbore radius', 'RADB': 'block equivalent radius', 'SKIN': 'skin'}
d = {
'RADW': 'wellbore radius',
'RADB': 'block equivalent radius',
'SKIN': 'skin',
'STAT': 'well connection open'
}
return d.get(title)

p = None
pk = pk_for_title(title)
pc_uom = None
if title in pc_titles:
p = pc.singleton(citation_title = title)
v = pc.cached_part_array_ref(p)[ci]
pc_uom = pc.uom_for_part(p)
elif pc_timeless is not None:
p = pc_timeless.singleton(citation_title = title)
if p is None:
pk = pk_for_title(title)
if pk is not None:
p = pc_timeless.singleton(property_kind = pk)
for try_pc in [pc, pc_timeless]:
if try_pc is None:
continue
if title in pc_titles:
p = try_pc.singleton(citation_title = title)
if p is None and pk is not None:
p = try_pc.singleton(property_kind = pk)
if p is not None:
v = pc_timeless.cached_part_array_ref(p)[ci]
pc_uom = pc.uom_for_part(p)
v = try_pc.cached_part_array_ref(p)[ci]
pc_uom = try_pc.uom_for_part(p)
break
if (title == 'STAT' or pk == 'well connection open') and v is not None and not isinstance(v, str):
v = 'ON' if v else 'OFF'
if pc_uom is not None and uom is not None and pc_uom != uom:
v = wam.convert_lengths(v, pc_uom, uom)
return v
Expand All @@ -2453,6 +2476,7 @@ def pk_for_title(title):
r_uom = length_uom
length = get_item(length, 'LENGTH', pc_titles, pc, pc_timeless, ci, l_uom)
radw = get_item(radw, 'RADW', pc_titles, pc, pc_timeless, ci, r_uom)
stat = get_item(stat, 'STAT', pc_titles, pc, pc_timeless, ci, None)
assert radw is None or radw > 0.0 # todo: allow zero for inactive intervals?
skin = get_item(skin, 'SKIN', pc_titles, pc, pc_timeless, ci, None)
if skin is None:
Expand All @@ -2463,7 +2487,7 @@ def pk_for_title(title):
wi = get_item(None, 'WI', pc_titles, pc, pc_timeless, ci, None)
wbc = get_item(None, 'WBC', pc_titles, pc, pc_timeless, ci, None)

return length, radw, skin, radb, wi, wbc
return length, radw, skin, radb, wi, wbc, stat

@staticmethod
def __get_well_inflow_parameters_for_interval(do_well_inflow, isotropic_perm, ntg_is_one, k_i, k_j, k_k, sine_anglv,
Expand Down Expand Up @@ -2689,6 +2713,7 @@ def add_df_properties(self,
this method currently only handles single grid situations;
dataframe rows must be in the same order as the cells in the blocked well
"""

# todo: enhance to handle multiple grids
assert len(self.grid_list) == 1
if columns is None or len(columns) == 0 or len(df) == 0:
Expand All @@ -2713,8 +2738,9 @@ def add_df_properties(self,
# 'SKIN': use defaults for now; todo: create local property kind for skin
if column == 'STAT':
col_as_list = list(df[column])
expanded = np.array([(0 if (str(st).upper() in ['OFF', '0']) else 1) for st in col_as_list],
dtype = int)
expanded = np.array([(0 if (str(st).upper() in ['OFF', '0', 'FALSE']) else 1) for st in col_as_list],
dtype = np.int8)
dtype = np.int8
else:
expanded = df[column].to_numpy(dtype = dtype, copy = True, na_value = na_value)
extra_pc.add_cached_array_to_imported_list(
Expand All @@ -2739,6 +2765,7 @@ def add_df_properties(self,

def _get_uom_pk_discrete_for_df_properties(self, extra, length_uom, temperature_uom = None):
"""Set the property kind and unit of measure for all properties in the dataframe."""

# todo: this is horribly inefficient, building a whole dictionary for every call but only using one entry
if length_uom not in ['m', 'ft']:
raise ValueError(f"The length_uom {length_uom} must be either 'm' or 'ft'.")
Expand Down Expand Up @@ -3297,6 +3324,7 @@ def __create_wellbore_feature_and_interpretation_xml_if_needed(self, add_as_part
def __create_trajectory_xml_if_needed(self, create_for_trajectory_if_needed, add_as_part, add_relationships,
originator, ext_uuid, title):
"""Create root node for associated Trajectory object if necessary."""

if create_for_trajectory_if_needed and self.trajectory_to_be_written and self.trajectory.root is None:
md_datum_root = self.trajectory.md_datum.create_xml(add_as_part = add_as_part,
add_relationships = add_relationships,
Expand All @@ -3311,6 +3339,7 @@ def __create_trajectory_xml_if_needed(self, create_for_trajectory_if_needed, add

def __create_bw_node_sub_elements(self, bw_node):
"""Append sub-elements to the BlockedWell object's root node."""

nc_node = rqet.SubElement(bw_node, ns['resqml2'] + 'NodeCount')
nc_node.set(ns['xsi'] + 'type', ns['xsd'] + 'positiveInteger')
nc_node.text = str(self.node_count)
Expand Down Expand Up @@ -3369,7 +3398,8 @@ def __create_bw_node_sub_elements(self, bw_node):
fis_values_node.set(ns['xsi'] + 'type', ns['eml'] + 'Hdf5Dataset')
fis_values_node.text = rqet.null_xml_text

return nc_node, mds_node, mds_values_node, cc_node, cis_node, cnull_node, cis_values_node, gis_node, gnull_node, gis_values_node, fis_node, fnull_node, fis_values_node
return (nc_node, mds_node, mds_values_node, cc_node, cis_node, cnull_node, cis_values_node, gis_node,
gnull_node, gis_values_node, fis_node, fnull_node, fis_values_node)

def __create_trajectory_grid_wellbore_interpretation_reference_nodes(self, bw_node):
"""Create nodes and add to BlockedWell object root node."""
Expand Down
14 changes: 14 additions & 0 deletions tests/unit_tests/well/test_blocked_well.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,20 @@ def test_set_for_column_and_other_things(example_model_and_crs):
wbf_ii = bw_pc.cached_part_array_ref(wbf_ii_part)
assert wbf_ii is not None and wbf_ii.shape == (bw.cell_count,)
assert np.all(wbf_ii == (0, -1, 2, 2, -1)) or np.all(wbf_ii == (0, -1, 2, 3, -1))
# do some dataframe checks
df = bw.dataframe(extra_columns_list = ['KH', 'SKIN', 'STAT'],
use_properties = True,
property_time_index = 1,
time_series_uuid = ts.uuid)
assert df is not None
assert 'KH' in df.columns
assert 'SKIN' in df.columns
assert 'STAT' in df.columns
assert_array_almost_equal(df['KH'], (1000.0, 0.0, 1200.0, 3800.0, 0.0))
assert_array_almost_equal(df['SKIN'], (3.5, np.NaN, -1.0, 0.5, np.NaN))
stat_col = list(df['STAT'])
assert len(stat_col) == 5
assert stat_col == ['ON', 'OFF', 'ON', 'ON', 'OFF']


def test_derive_from_cell_list(example_model_and_crs):
Expand Down

0 comments on commit b90b650

Please sign in to comment.