fromcollectionsimportnamedtupleimportnumpyasnpfromtqdm.autoimporttqdmfromopenpnm._skgraph.simulationsimport(bond_percolation,find_connected_clusters,site_percolation,)fromopenpnm.algorithmsimportAlgorithmfromopenpnm.utilsimportDocorator,TypedSetdocstr=Docorator()__all__=['Drainage']@docstr.get_sections(base='DrainageSettings',sections=['Parameters'])@docstr.dedentclassDrainageSettings:r""" Parameters ---------- %(AlgorithmSettings.parameters)s throat_entry_pressure : str The dictionary key for the pore entry pressure array pore_volume : str The dictionary key for the pore volume array throat_volume : str The dictionary key for the throat volume array """phase=''throat_entry_pressure='throat.entry_pressure'pore_volume='pore.volume'throat_volume='throat.volume'variable_props=TypedSet()
[docs]classDrainage(Algorithm):"""A class to simulate drainage."""def__init__(self,phase,name='drainage_?',**kwargs):super().__init__(name=name,**kwargs)self.settings._update(DrainageSettings())self.settings['phase']=phase.nameself['pore.bc.inlet']=Falseself['pore.bc.outlet']=Falseself.reset()
[docs]defreset(self):r""" Resets the algorithm's main results so that it can be re-run """self['pore.invaded']=Falseself['throat.invaded']=False# self['pore.residual'] = False# self['throat.residual'] = Falseself['pore.trapped']=Falseself['throat.trapped']=Falseself['pore.invasion_pressure']=np.infself['throat.invasion_pressure']=np.infself['pore.invasion_sequence']=-1self['throat.invasion_sequence']=-1
def_set_residual(self,pores=None,throats=None,mode='add'):# pragma: no coverraiseNotImplementedError("The ability to add residual nwp is not ready yet")ifporesisnotNone:self['pore.invaded'][pores]=Trueself['pore.residual'][pores]=Trueself['pore.invasion_pressure'][self['pore.invaded']]=-np.infself['pore.invasion_sequence'][pores]=0ifthroatsisnotNone:self['throat.invaded'][throats]=Trueself['throat.residual'][throats]=Trueself['throat.invasion_pressure'][self['throat.invaded']]=-np.infself['throat.invasion_sequence'][throats]=0
[docs]defset_inlet_BC(self,pores,mode='add'):r""" Specify the pores from which invading fluid will enter the domain Parameters ---------- pores : ndarray List of pore indices mode : str How the boundary conditions should be applied. Options are: ============ ===================================================== mode meaning ============ ===================================================== 'add' (default) Adds the supplied boundary conditions to the given locations. Raises an exception if values of any type already exist in the given locations. 'overwrite' Adds supplied boundary conditions to the given locations, including overwriting conditions of the given type or any other type that may be present in the given locations. 'remove' Removes boundary conditions of the specified type from the specified locations. If ``bctype`` is not specified then *all* types are removed. If no locations are given then values are remvoed from *all* locations. ============ ===================================================== """self.set_BC(pores=pores,bcvalues=True,bctype='inlet',mode=mode)
[docs]defset_outlet_BC(self,pores,mode='add'):r""" Specify the pores from which defending fluid exits the network. This is optional and only required if trapping is to be considered. Parameters ---------- pores : ndarray The list of outlet pores mode : str How the boundary conditions should be applied. Options are: ============ ===================================================== mode meaning ============ ===================================================== 'add' (default) Adds the supplied boundary conditions to the given locations. Raises an exception if values of any type already exist in the given locations. 'overwrite' Adds supplied boundary conditions to the given locations, including overwriting conditions of the given type or any other type that may be present in the given locations. 'remove' Removes boundary conditions of the specified type from the specified locations. If ``bctype`` is not specified then *all* types are removed. If no locations are given then values are remvoed from *all* locations. ============ ===================================================== """self.set_BC(pores=pores,bcvalues=True,bctype='outlet',mode=mode)
[docs]defrun(self,pressures=25):r""" Runs the simulation for the pressure points Parameters ---------- pressures : int or ndarray The number of pressue steps to apply, or an array of specific points """ifisinstance(pressures,int):phase=self.project[self.settings.phase]hi=1.25*phase[self.settings.throat_entry_pressure].max()low=0.80*phase[self.settings.throat_entry_pressure].min()pressures=np.logspace(np.log10(low),np.log10(hi),pressures)pressures=np.array(pressures,ndmin=1)msg='Performing drainage simulation'fori,pinenumerate(tqdm(pressures,msg)):self._run_special(p)pmask=self['pore.invaded']*(self['pore.invasion_pressure']==np.inf)self['pore.invasion_pressure'][pmask]=pself['pore.invasion_sequence'][pmask]=itmask=self['throat.invaded']*(self['throat.invasion_pressure']==np.inf)self['throat.invasion_pressure'][tmask]=pself['throat.invasion_sequence'][tmask]=i# If any outlets were specified, evaluate trappingifnp.any(self['pore.bc.outlet']):self.apply_trapping()
def_run_special(self,pressure):phase=self.project[self.settings.phase]Tinv=phase[self.settings.throat_entry_pressure]<=pressure# Tinv += self['throat.invaded']# Remove trapped throats from this list, if any# Tinv[self['throat.trapped']] = False# Perform bond_percolation to label invaded clusterss_labels,b_labels=bond_percolation(self.network.conns,Tinv)# Remove label from any clusters not connected to the inletss_labels,b_labels=find_connected_clusters(b_labels,s_labels,self['pore.bc.inlet'],asmask=False)# Add result to existing invaded locationsself['pore.invaded'][s_labels>=0]=Trueself['throat.invaded'][b_labels>=0]=True
[docs]defapply_trapping(self):r""" Adjusts the invasion history of pores and throats that are trapped. Returns ------- This function returns nothing, but the following adjustments are made to the data on the object for trapped pores and throats: * ``'pore/throat.trapped'`` is set to ``True`` * ``'pore/throat.invaded'`` is set to ``False`` * ``'pore/throat.invasion_pressure'`` is set to ``np.inf`` * ``'pore/throat.invasion_sequence'`` is set to ``0`` Notes ----- This search proceeds by the following 3 steps: 1. A site percolation is applied to *uninvaded* pores and they are set to trapped if they belong to a cluster that is not connected to the outlets. 2. All throats which were invaded at a pressure *higher* than either of its two neighboring pores are set to trapped, regardless of whether the pores themselves are trapped. 3. All throats which are connected to trapped pores are set to trapped as these cannot be invaded since the fluid they contain cannot escape. """pseq=self['pore.invasion_pressure']tseq=self['throat.invasion_pressure']# Firstly, find any throats who were invaded at a pressure higher than# either of its two neighboring porestemp=(pseq[self.network.conns].T>tseq).Tself['throat.trapped'][np.all(temp,axis=1)]=True# Now scan through and use site percolation to find other trapped# clusters of poresforpinnp.unique(pseq):s,b=site_percolation(conns=self.network.conns,occupied_sites=pseq>p)# Identify cluster numbers connected to the outletsclusters=np.unique(s[self['pore.bc.outlet']])# Find ALL throats connected to any trapped site, since these# throats must also be trapped, and update their cluster numbersTs=self.network.find_neighbor_throats(pores=s>=0)b[Ts]=np.amax(s[self.network.conns],axis=1)[Ts]# Finally, mark pores and throats as trapped if their cluster# numbers are NOT connected to the outletsself['pore.trapped']+=np.isin(s,clusters,invert=True)*(s>=0)self['throat.trapped']+=np.isin(b,clusters,invert=True)*(b>=0)# Use the identified trapped pores and throats to update the other# data on the object accordingly# self['pore.trapped'][self['pore.residual']] = False# self['throat.trapped'][self['throat.residual']] = Falseself['pore.invaded'][self['pore.trapped']]=Falseself['throat.invaded'][self['throat.trapped']]=Falseself['pore.invasion_pressure'][self['pore.trapped']]=np.infself['throat.invasion_pressure'][self['throat.trapped']]=np.infself['pore.invasion_sequence'][self['pore.trapped']]=-1self['throat.invasion_sequence'][self['throat.trapped']]=-1