def test_find_clusters_bonds(self): net = op.network.Cubic(shape=[10, 10, 10]) net['pore.seed'] = np.random.rand(net.Np) net['throat.seed'] = np.random.rand(net.Nt) clusters = topotools.find_clusters(network=net, mask=net['throat.seed'] < 0.5) assert len(clusters[0]) == net.Np assert len(clusters[1]) == net.Nt
def apply_trapping(self, partial=False): """ Apply trapping based on algorithm described by Y. Masson [1]. Parameters ---------- partial : boolean Indicating whether partially filled network Notes ----- It is applied as a post-process and runs the percolation algorithm in reverse assessing the occupancy of pore neighbors. 3 situations can happen on invasion without trapping: * The number of defending clusters stays the same and clusters can shrink * A cluster of size one is suppressed * A cluster is split into multiple clusters In reverse the following situations can happen: * The number of defending clusters stays the same and clusters can grow * A cluster of size one is created * Mutliple clusters merge into one cluster With trapping the reversed rules are adjusted so that: * Only clusters that do not connect to a sink can grow and merge. * At the point that a neighbor connected to a sink is touched, the trapped cluster stops growing as this is the point of trapping in forward invasion time. Logger info displays the invasion Sequence and pore index and a message with condition number based on the modified trapping rules and the assignment of the pore to a given cluster. Initially all invaded pores are given cluster label -1 Outlets / Sinks are given -2 New clusters that grow into fully trapped clusters are either identified at the point of breakthrough or grow from nothing if the full invasion sequence is run, they are assigned numbers from 0 up. References ---------- [1] Masson, Y., 2016. A fast two-step algorithm for invasion percolation with trapping. Computers & Geosciences, 90, pp.41-48 Returns ------- Creates a throat array called 'pore.clusters' in the Algorithm dictionary. Any positive number is a trapped cluster. Also creates 2 boolean arrays Np and Nt long called '<element>.trapped' """ net = self.project.network outlets = self["pore.outlets"] if np.sum(outlets) == 0: raise Exception( "Outlets must be set using the set_outlets method" + " before applying trapping") if partial: # Set occupancy invaded_ps = self["pore.invasion_sequence"] > -1 # Put defending phase into clusters clusters = find_clusters(network=net, pmask=~invaded_ps) # Identify clusters that are connected to an outlet and set to -2 # -1 is the invaded fluid # -2 is the defender fluid able to escape # All others now trapped clusters which grow as invasion is # reversed out_clusters = np.unique(clusters[outlets]) for c in out_clusters: if c >= 0: clusters[clusters == c] = -2 else: # Go from end clusters = np.ones(net.Np, dtype=int) * -1 clusters[outlets] = -2 # Turn into a list for indexing inv_seq = np.vstack(( self["pore.invasion_sequence"].astype(int), np.arange(0, net.Np, dtype=int), )).T # Reverse sort list inv_seq = inv_seq[inv_seq[:, 0].argsort()][::-1] next_cluster_num = np.max(clusters) + 1 # For all the steps after the inlets are set up to break-through # Reverse the sequence and assess the neighbors cluster state stopped_clusters = np.zeros(net.Np, dtype=bool) all_neighbors = net.find_neighbor_pores(net.pores(), flatten=False, include_input=True) for un_seq, pore in inv_seq: if ~outlets[pore] and un_seq > -1: # Don't include outlets nc = clusters[all_neighbors[pore]] # Neighboring clusters unique_ns = np.unique(nc[nc != -1]) # Unique Neighbors seq_pore = "S:" + str(un_seq) + " P:" + str(pore) if np.all(nc == -1): # This is the start of a new trapped cluster clusters[pore] = next_cluster_num next_cluster_num += 1 msg = seq_pore + " C:1 new cluster number: " + str( clusters[pore]) logger.info(msg) elif len(unique_ns) == 1: # Grow the only connected neighboring cluster if not stopped_clusters[unique_ns[0]]: clusters[pore] = unique_ns[0] msg = (seq_pore + " C:2 joins cluster number: " + str(clusters[pore])) logger.info(msg) else: clusters[pore] = -2 elif -2 in unique_ns: # We have reached a sink neighbor, stop growing cluster msg = seq_pore + " C:3 joins sink cluster" logger.info(msg) clusters[pore] = -2 # Stop growth and merging stopped_clusters[unique_ns[unique_ns > -1]] = True else: # We might be able to do some merging # Check if any stopped clusters are neighbors if np.any(stopped_clusters[unique_ns]): msg = seq_pore + " C:4 joins sink cluster" logger.info(msg) clusters[pore] = -2 # Stop growing all neighboring clusters stopped_clusters[unique_ns] = True else: # Merge multiple un-stopped trapped clusters new_num = unique_ns[0] clusters[pore] = new_num for c in unique_ns: clusters[clusters == c] = new_num msg = (seq_pore + " C:5 merge clusters: " + str(c) + " into " + str(new_num)) logger.info(msg) # And now return clusters clusters[outlets] = -2 num_trap = np.sum(np.unique(clusters) >= 0) if num_trap > 0: logger.info("Number of trapped clusters " + str(num_trap)) self["pore.trapped"] = clusters > -1 num_tPs = np.sum(self["pore.trapped"]) logger.info("Number of trapped pores: " + str(num_tPs)) self["pore.invasion_sequence"][self["pore.trapped"]] = -1 self["throat.trapped"] = np.zeros([net.Nt], dtype=bool) for c in np.unique(clusters[clusters >= 0]): c_ts = net.find_neighbor_throats(clusters == c, mode="xnor") self["throat.trapped"][c_ts] = True num_tTs = np.sum(self["throat.trapped"]) logger.info("Number of trapped throats: " + str(num_tTs)) self["throat.invasion_sequence"][self["throat.trapped"]] = -1 # Assumes invasion has run to the end phase = self.project.find_phase(self) phase["pore.occupancy"] = ~self["pore.trapped"] phase["throat.occupancy"] = ~self["throat.trapped"] else: logger.info("No trapped clusters found")
def apply_trapping(self, outlets): """ Apply trapping based on algorithm described by Y. Masson [1]. It is applied as a post-process and runs the percolation algorithm in reverse assessing the occupancy of pore neighbors. Consider the following scenario when running standard IP without trapping, 3 situations can happen after each invasion step: * The number of defending clusters stays the same and clusters can shrink * A cluster of size one is suppressed * A cluster is split into multiple clusters In reverse the following opposite situations can happen: * The number of defending clusters stays the same and clusters can grow * A cluster of size one is created * Mutliple clusters merge into one cluster With trapping the reversed rules are adjusted so that only clusters that do not connect to a sink can grow and merge. At the point that a neighbor connected to a sink is touched the trapped cluster stops growing as this is the point of trapping in forward invasion time. Logger info displays the invasion sequence and pore index and a message with condition number based on the modified trapping rules and the assignment of the pore to a given cluster. Initially all invaded pores are given cluster label -1 Outlets / Sinks are given -2 New clusters that grow into fully trapped clusters are either identified at the point of breakthrough or grow from nothing if the full invasion sequence is run, they are assigned numbers from 0 up. Ref: [1] Masson, Y., 2016. A fast two-step algorithm for invasion percolation with trapping. Computers & Geosciences, 90, pp.41-48 Parameters ---------- outlets : list or array of pore indices for defending fluid to escape through Returns ------- Creates a throat array called 'pore.clusters' in the Algorithm dictionary. Any positive number is a trapped cluster Also creates 2 boolean arrays Np and Nt long called '<element>.trapped' """ # First see if network is fully invaded net = self.project.network invaded_ps = self['pore.invasion_sequence'] > -1 if ~np.all(invaded_ps): # Put defending phase into clusters clusters = find_clusters(network=net, mask=~invaded_ps) # Identify clusters that are connected to an outlet and set to -2 # -1 is the invaded fluid # -2 is the defender fluid able to escape # All others now trapped clusters which grow as invasion is reversed out_clusters = sp.unique(clusters[outlets]) for c in out_clusters: if c >= 0: clusters[clusters == c] = -2 else: # Go from end clusters = np.ones(net.Np, dtype=int) * -1 clusters[outlets] = -2 # Turn into a list for indexing inv_seq = np.vstack((self['pore.invasion_sequence'].astype(int), np.arange(0, net.Np, dtype=int))).T # Reverse sort list inv_seq = inv_seq[inv_seq[:, 0].argsort()][::-1] next_cluster_num = np.max(clusters) + 1 # For all the steps after the inlets are set up to break-through # Reverse the sequence and assess the neighbors cluster state stopped_clusters = np.zeros(net.Np, dtype=bool) all_neighbors = net.find_neighbor_pores(net.pores(), flatten=False, include_input=True) for un_seq, pore in inv_seq: if pore not in outlets and un_seq > 0: # Skip inlets and outlets nc = clusters[all_neighbors[pore]] # Neighboring clusters unique_ns = np.unique(nc[nc != -1]) # Unique Neighbors seq_pore = "S:" + str(un_seq) + " P:" + str(pore) if np.all(nc == -1): # This is the start of a new trapped cluster clusters[pore] = next_cluster_num next_cluster_num += 1 msg = (seq_pore + " C:1 new cluster number: " + str(clusters[pore])) logger.info(msg) elif len(unique_ns) == 1: # Grow the only connected neighboring cluster if not stopped_clusters[unique_ns[0]]: clusters[pore] = unique_ns[0] msg = (seq_pore + " C:2 joins cluster number: " + str(clusters[pore])) logger.info(msg) else: clusters[pore] = -2 elif -2 in unique_ns: # We have reached a sink neighbor, stop growing cluster msg = (seq_pore + " C:3 joins sink cluster") logger.info(msg) clusters[pore] = -2 # Stop growth and merging stopped_clusters[unique_ns[unique_ns > -1]] = True else: # We might be able to do some merging # Check if any stopped clusters are neighbors if np.any(stopped_clusters[unique_ns]): msg = (seq_pore + " C:4 joins sink cluster") logger.info(msg) clusters[pore] = -2 # Stop growing all neighboring clusters stopped_clusters[unique_ns] = True else: # Merge multiple un-stopped trapped clusters new_num = unique_ns[0] clusters[pore] = new_num for c in unique_ns: clusters[clusters == c] = new_num msg = (seq_pore + " C:5 merge clusters: " + str(c) + " into " + str(new_num)) logger.info(msg) # And now return clusters self['pore.clusters'] = clusters logger.info("Number of trapped clusters" + str(np.sum(np.unique(clusters) >= 0))) self['pore.trapped'] = self['pore.clusters'] > -1 trapped_ts = net.find_neighbor_throats(self['pore.trapped']) self['throat.trapped'] = np.zeros([net.Nt], dtype=bool) self['throat.trapped'][trapped_ts] = True self['pore.invasion_sequence'][self['pore.trapped']] = -1 self['throat.invasion_sequence'][self['throat.trapped']] = -1