[docs]classDocorator(DocstringProcessor):"""OpenPNM's customized docstring processor."""__instance__=Nonedef__new__(cls,*args,**kwargs):ifDocorator.__instance__isNone:Docorator.__instance__=DocstringProcessor()# Add custom parameter type sectionsa=DocstringProcessor.param_like_sectionsDocorator.__instance__.param_like_sections=a+[]# ["Attributes", "Settings"]# Add custom text type sectionsa=Docorator.__instance__.text_sectionsDocorator.__instance__.text_sections=a+[]# Create a single list of all section typesa=Docorator.__instance__.param_like_sectionsb=Docorator.__instance__.text_sectionsDocorator.__instance__.all_sections=a+breturnDocorator.__instance__
[docs]classPrintableList(list):r""" Simple subclass of ``list`` that has nice printing. Only works flat lists. Examples -------- >>> from openpnm.utils import PrintableList >>> temp = ['item1', 'item2', 'item3'] >>> print(PrintableList(temp)) ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― 1 : item1 2 : item2 3 : item3 ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― Each line contains the result of ``print(item)`` on each item in the list """def__str__(self):horizontal_rule="―"*78lines=[horizontal_rule]self.sort()fori,iteminenumerate(self):lines.append("{0:<5s} : {1}".format(str(i+1),item))lines.append(horizontal_rule)return"\n".join(lines)
# def __repr__(self): # pragma: no cover# return self.__str__()
[docs]classPrintableDict(dict):r""" Simple subclass of ``dict`` that has nicer printing. Examples -------- >>> from openpnm.utils import PrintableDict >>> from numpy import array as arr >>> d = {'item1': 1, 'item2': '1', 'item3': [1, 1], 'item4': arr([1, 1])} >>> print(PrintableDict(d)) ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― Key Value ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― item1 1 item2 1 item3 [1, 1] item4 (2,) ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― If the item is a Numpy array the value column will contain the items' shape, otherwise it will contain the result of ``print(item)`` """def__init__(self,*args,key="Key",value="Value",**kwargs):self._value=valueself._key=keysuper().__init__(*args,**kwargs)# def __repr__(self): # pragma: no cover# return self.__str__()def__str__(self):header="―"*78lines=[header,"{0:<35s}{1}".format(self._key,self._value),header]foriteminlist(self.keys()):ifitem.startswith('_'):continueifisinstance(self[item],np.ndarray):lines.append("{0:<35s}{1}".format(item,np.shape(self[item])))else:lines.append("{0:<35s}{1}".format(item,self[item]))lines.append(header)return"\n".join(lines)
[docs]classNestedDict(dict):"""Brief explanation of 'NestedDict'"""def__init__(self,mapping={},delimiter="/"):super().__init__()self.delimiter=delimiterself.update(mapping)self.unravel()def__setitem__(self,key,value):path=key.split(self.delimiter,1)iflen(path)>1:ifpath[0]notinself.keys():self[path[0]]=NestedDict(delimiter=self.delimiter)self[path[0]][path[1]]=valueelse:super().__setitem__(key,value)def__missing__(self,key):self[key]=NestedDict(delimiter=self.delimiter)returnself[key]
[docs]classHealthDict(PrintableDict):r""" This class adds a 'health' check to a standard dictionary. This check looks into the dict values, and considers empty lists as healthy and all else as unhealthy. If one or more entries is 'unhealthy' the health method returns False. """def__init__(self,**kwargs):super().__init__(**kwargs)def_get_health(self):health=Trueforiteminlist(self.keys()):try:iflen(self[item])>0:health=FalseexceptTypeError:ifself[item]:health=Falsereturnhealthhealth=property(fget=_get_health)def__bool__(self):returnself.health
[docs]defflat_list(input_list):r""" Given a list of nested lists of arbitrary depth, returns a single level or 'flat' list. """def_flatten(l):forelinl:ifisinstance(el,Iterable)andnotisinstance(el,(str,bytes)):yield from_flatten(el)else:yieldelreturnlist(_flatten(input_list))
[docs]defsanitize_dict(input_dict):r""" Given a nested dictionary, ensures that all nested dicts are normal Python dicts. This is necessary for pickling, or just converting an 'auto-vivifying' dict to something that acts normal. """plain_dict=dict()forkeyininput_dict.keys():value=input_dict[key]ifhasattr(value,"keys"):plain_dict[key]=sanitize_dict(value)else:plain_dict[key]=valuereturnplain_dict
[docs]defmethods_to_table(obj):r""" Converts a methods on an object to a ReST compatible table Parameters ---------- obj : Base Any object that has a methods params : bool Indicates whether or not to include a list of parameter values in the table. Set to False for just a list of models, and True for a more verbose table with all parameter values. """parent=obj.__class__.__mro__[1]temp=inspect.getmembers(parent,predicate=inspect.isroutine)parent_funcs=[i[0]foriintempifnoti[0].startswith("_")]temp=inspect.getmembers(obj.__class__,predicate=inspect.isroutine)obj_funcs=[i[0]foriintempifnoti[0].startswith("_")]funcs=set(obj_funcs).difference(set(parent_funcs))row="+"+"-"*22+"+"+"-"*49+"+"fmt="{0:1s}{1:20s}{2:1s}{3:47s}{4:1s}"lines=[]lines.append(row)lines.append(fmt.format("|","Method","|","Description","|"))lines.append(row.replace("-","="))fori,iteminenumerate(funcs):try:s=getattr(obj,item).__doc__.strip()end=s.find("\n")ifend>47:s=s[:44]+"..."lines.append(fmt.format("|",item,"|",s[:end],"|"))lines.append(row)exceptAttributeError:passreturn"\n".join(lines)
[docs]defmodels_to_table(obj,params=True):r""" Converts a all the models on an object to a ReST compatible table Parameters ---------- obj : Base Any object that has a ``models`` attribute params : bool Indicates whether or not to include a list of parameter values in the table. Set to False for just a list of models, and True for a more verbose table with all parameter values. """ifnothasattr(obj,"models"):raiseException("Received object does not have any models")row="+"+"-"*4+"+"+"-"*22+"+"+"-"*18+"+"+"-"*26+"+"fmt="{0:1s}{1:2s}{2:1s}{3:20s}{4:1s}{5:16s}{6:1s}{7:24s}{8:1s}"lines=[]lines.append(row)lines.append(fmt.format("|","#","|","Property Name","|","Parameter","|","Value","|"))lines.append(row.replace("-","="))fori,iteminenumerate(obj.models.keys()):prop=itemiflen(prop)>20:prop=item[:17]+"..."temp=obj.models[item].copy()model=str(temp.pop("model")).split(" ")[1]lines.append(fmt.format("|",str(i+1),"|",prop,"|","model:","|",model,"|"))lines.append(row)ifparams:forparamintemp.keys():p1=paramiflen(p1)>16:p1=p1[:13]+"..."p2=str(temp[param])iflen(p2)>24:p2=p2[:21]+"..."lines.append(fmt.format("|","","|","","|",p1,"|",p2,"|"))lines.append(row)return"\n".join(lines)
[docs]defignore_warnings(warning=RuntimeWarning):r""" Decorator for catching warnings. Useful in pore-scale models where nans are inevitable, and numpy gets annoying by throwing lots of RuntimeWarnings. Parameters ---------- warning : Warning Python warning type that you want to temporarily ignore Examples -------- >>> from openpnm.utils import ignore_warnings >>> @ignore_warnings() ... def myfun(x): ... return 1/x >>> import numpy as np >>> x = np.arange(5) >>> myfun(x) array([ inf, 1. , 0.5 , 0.33333333, 0.25 ]) """def_ignore_warning(function):@functools.wraps(function)def__ignore_warning(*args,**kwargs):withwarnings.catch_warnings(record=True):# Catch all warnings of this typewarnings.simplefilter("always",warning)# Execute the functionresult=function(*args,**kwargs)returnresultreturn__ignore_warningreturn_ignore_warning
[docs]defis_symmetric(a,rtol=1e-10):r""" Is ``a`` a symmetric matrix? Parameters ---------- a : ndarray or sparse matrix Object to check for being a symmetric matrix. rtol : float Relative tolerance with respect to the smallest entry in ``a`` that is used to determine if ``a`` is symmetric. Returns ------- bool ``True`` if ``a`` is a symmetric matrix, ``False`` otherwise. """ifnotisinstance(a,np.ndarray)andnotsparse.issparse(a):raiseException("'a' must be either a sparse matrix or an ndarray.")ifa.shape[0]!=a.shape[1]:raiseException("'a' must be a square matrix.")atol=np.amin(np.absolute(a.data))*rtolifsparse.issparse(a):issym=Falseif((a-a.T)>atol).nnzelseTrueelifisinstance(a,np.ndarray):issym=Falseifnp.any((a-a.T)>atol)elseTruereturnissym
[docs]defget_mixture_model_args(phase,composition='xs',args={'mus':'pore.viscosity','MWs':'param.molecular_weight',}):r""" This is used in tests to run models generically """fromopenpnm.models.phase.miscimportmole_to_mass_fractionvals={}ifcompositionin['ws']:temp=np.vstack(list(mole_to_mass_fraction(phase=phase).values()))[:,0]vals[composition]=tempelse:temp=np.vstack(list(phase['pore.mole_fraction'].values()))[:,0]vals[composition]=tempforiteminargs.keys():temp=np.vstack(list(phase.get_comp_vals(args[item]).values()))[:,0]vals[item]=tempreturnvals
[docs]defdict_to_struct(d):r""" Converts a dictionary of numpy arrays to a numpy struct Parameters ---------- d : dict A dictionary wtih numpy arrays in each key. The arrays must be all the same size. Returns ------- s : numpy struct A numpy struct with the fields or names take from the dictionary keys """struct=rf.unstructured_to_structured(np.vstack(list(d.values())).T,names=list(d.keys()))returnstruct
[docs]defstruct_to_dict(s):r""" Converts a numpy struct array into a dictionary using the struct labels as keys Parameters ---------- s : numpy struct The struct array Returns ------- d : dict A dictionary with the struct labels or fields as the keys """d={}forkeyins.dtype.names:d[key]=s[key]returnd
[docs]defget_printable_props(item,suffix='',hr=78*'―'):r""" This function is used by the __str__ methods on all classes to get a nicely formatted list of properties on the object. Parameters ---------- item : dict The OpenPNM dictionary object with each dictionary key containing a numpy array suffix : str, optional If provided, this will be attached to the end of every dictionary key so that 'pore.viscosity' becomes 'pore.viscosity.phase_01'. This is a workaround to enhance the printing of component information on mixtures. hr : str, optional The horizontal rule to use between the table heading and body Returns ------- table : str A formatted string that will output a 78 character wide table when printed Notes ----- The table returned by this function only contains items that are numerical arrays. Any boolean arrays are ignored. See Also -------- get_printable_labels """ifsuffixandnotsuffix.startswith('.'):suffix='.'+suffixheader=[' ']*78header[2]='#'header[5:15]='Properties'header[-12:]='Valid Values'lines=''.join(header)+'\n'+hri=0fork,vinitem.items():if(v.dtype!=bool)andnot('._'ink):i+=1s=[' ']*78s[:3]=str(i+1).rjust(3)prop=k+suffixs[5:5+len(prop)]=propelement=k.split('.',1)[0]nans=np.any(np.isnan(np.atleast_2d(v.T)),axis=0)valid=str(np.sum(~nans))+' / '+str(item._count(element))s[-20:]=valid.rjust(20)a=''.join(s)lines='\n'.join((lines,a))returnlines
[docs]defget_printable_labels(item,suffix='',hr=78*'―'):r""" This function is used by the __str__ methods on all classes to get a nicely formatted list of labels on the object. Parameters ---------- item : dict The OpenPNM dictionary object with each dictionary key containing a numpy array suffix : str, optional If provided, this will be attached to the end of every dictionary key so that 'pore.viscosity' becomes 'pore.viscosity.phase_01'. This is a workaround to enhance the printing of component information on mixtures. hr : str, optional The horizontal rule to use between the table heading and body Returns ------- table : str A formatted string that will output a 78 character wide table when printed Notes ----- The table returned by this function only contains items that boolean arrays. Any numerical arrays are ignored. See Also -------- get_printable_props """ifsuffixandnotsuffix.startswith('.'):suffix='.'+suffixheader=[' ']*78header[2]='#'header[5:11]='Labels'header[-18:]='Assigned Locations'lines=''.join(header)+'\n'+hri=0fork,vinitem.items():if(v.dtype==bool)andnot('._'ink):i+=1s=[' ']*78s[:3]=str(i+1).rjust(3)prop=k+suffixs[5:5+len(prop)]=propvalid=str(np.sum(v))s[-12:]=valid.rjust(12)a=''.join(s)lines='\n'.join((lines,a))returnlines
[docs]defis_transient(algorithms):# check that algorithms is a listiftype(algorithms)isnotlist:algorithms=[algorithms]# return True if any algorithm is transientforalginalgorithms:ifhasattr(alg,'soln'):soln_type=type(alg.soln[alg.settings['quantity']])if'TransientSolution'instr(soln_type):returnTruereturnFalse
[docs]defis_valid_propname(propname):r""" Checks if ``propname`` is a valid OpenPNM propname, i.e. starts with 'pore.' or 'throat.' Parameters ---------- propname : str Property name to check whether it's a valid OpenPNM propname. Returns ------- bool Whether or not ``propname`` is a valid name """ifnotisinstance(propname,str):returnFalsetemp=propname.split(".")iftemp[0]notin["pore","throat"]:returnFalseiflen(temp)==1:returnFalseforfieldintemp:iflen(field)==0:returnFalsereturnTrue
defnbr_to_str(nbr,t_precision=12):r""" Converts a scalar into a string in scientific (exponential) notation without the decimal point. Parameters ---------- nbr : scalar The number to be converted into a scalar. t_precision : integer The time precision (number of decimal places). Default value is 12. Returns ------- num : str The string represenation of the given number in scientific notation """fromdecimalimportDecimalasdcn=int(-dc(str(round(nbr,t_precision))).as_tuple().exponent*(round(nbr,t_precision)!=int(nbr)))nbr_str=(str(int(round(nbr,t_precision)*10**n))+('e-'+str(n))*(n!=0))returnnbr_str