[docs]defgeneral_toroidal(phase,profile_equation='elliptical',mode='max',target_Pc=None,num_points=1000,throat_scale_a='throat.scale_a',throat_scale_b='throat.scale_b',throat_diameter='throat.diameter',touch_length='throat.touch_length',surface_tension='throat.surface_tension',contact_angle='throat.contact_angle'):r""" The general model for meniscus properties inside a toroidal throat Parameters ---------- %(phase)s profile_equation : str Options are 'elliptical' (default), 'sinusoidal'. mode : str Determines what information to send back. Options are: ======== ========================================================= Mode Description ======== ========================================================= 'max' (default) The maximum capillary pressure along the throat axis 'touch' The maximum capillary pressure a meniscus can sustain before touching a solid feature ======== ========================================================= target_Pc : float The target capillary pressure to return data for when mode is 'men' num_points : float The number of divisions to make along the profile length to assess the meniscus properties in order to find target pressures, touch lengths, minima and maxima. The default is 1000. throat_scale_a : str %(dict_blurb)s scale factor for adjusting the profile along the throat axis (x). throat_scale_b : str %(dict_blurb)s scale factor for adjusting the profile perpendicular to the throat axis (y). throat_diameter : str %(dict_blurb)s throat diameter values to be used. touch_length : str %(dict_blurb)s maximum length that a meniscus can protrude into the connecting pore before touching a solid feature and therfore invading surface_tension : str %(dict_blurb)s surface tension values to be used. If a pore property is given, it is interpolated to a throat list. contact_angle : str %(dict_blurb)s contact angle values to be used. If a pore property is given, it is interpolated to a throat list. Returns ------- values : dict A dictionary containing the following entries: ============ ========================================================= Item Description ============ ========================================================= 'pos' xpos 'rx' rx(xpos, fa, fb, throatRad) 'alpha' fill_angle(xpos, fa, fb, throatRad) 'alpha_min' fill_angle(xmin, fa, fb, throatRad) 'alpha_max' fill_angle(xmax, fa, fb, throatRad) 'c2x' c2x(xpos, fa, fb, throatRad, contact) 'gamma' cap_angle(xpos, fa, fb, throatRad, contact) 'radius' rad_curve(xpos, fa, fb, throatRad, contact) 'center' (xpos - men_data['c2x']) 'men_max' ?? ============ ========================================================= """fromsympyimportsymbols,lambdifyfromsympyimportatanassym_atanfromsympyimportcosassym_cosfromsympyimportsinassym_sinfromsympyimportsqrtassym_sqrtfromsympyimportpiassym_pi# Get data from dictionary keysnetwork=phase.networksurface_tension=phase[surface_tension]contact=phase[contact_angle]# Contact Angle in radianscontact=np.deg2rad(contact)# Network propertiesthroatRad=network[throat_diameter]/2# Scaling parameters for throat profilefa=phase[throat_scale_a]fb=phase[throat_scale_b]# Governing equationsx,a,b,rt,sigma,theta=symbols('x, a, b, rt, sigma, theta')ifprofile_equation=='elliptical':y=sym_sqrt(1-(x/a)**2)*belifprofile_equation=='sinusoidal':y=(sym_cos((sym_pi/2)*(x/a)))*belse:logger.error('Profile equation is not valid, default to elliptical')y=sym_sqrt(1-(x/a)**2)*b# Throat radius profiler=rt+(b-y)# Derivative of profilerprime=r.diff(x)# Filling anglealpha=sym_atan(rprime)# Angle between y axis and contact point to meniscus centereta=sym_pi-alpha-thetagamma=sym_pi/2-eta# Radius of curvature of meniscusrm=r/sym_cos(eta)# distance from center of curvature to meniscus contact point (Pythagoras)d=rm*sym_sin(eta)# angle between throat axis, meniscus center and meniscus contact point# Capillary Pressurep=2*sigma/rm# Callable functionsrx=lambdify((x,a,b,rt),r,'numpy')fill_angle=lambdify((x,a,b,rt),alpha,'numpy')rad_curve=lambdify((x,a,b,rt,theta),rm,'numpy')c2x=lambdify((x,a,b,rt,theta),d,'numpy')cap_angle=lambdify((x,a,b,rt,theta),gamma,'numpy')Pc=lambdify((x,a,b,rt,theta,sigma),p,'numpy')# All relative positions along throathp=int(num_points/2)log_pos=np.logspace(-4,-1,hp+1)[:-1]lin_pos=np.arange(0.1,1.0,1/hp)half_pos=np.concatenate((log_pos,lin_pos))pos=np.concatenate((-half_pos[::-1],half_pos))# Now find the positions of the menisci along each throat axisY,X=np.meshgrid(throatRad,pos)X*=fa# throat Capillary Pressuret_Pc=Pc(X,fa,fb,Y,contact,surface_tension)# Values of minima and maximaPc_min=np.min(t_Pc,axis=0)Pc_max=np.max(t_Pc,axis=0)# Arguments of minima and maximaa_min=np.argmin(t_Pc,axis=0)a_max=np.argmax(t_Pc,axis=0)ifmode=='max':returnPc_maxelifmode=='touch':all_rad=rad_curve(X,fa,fb,Y,contact)all_c2x=c2x(X,fa,fb,Y,contact)all_cen=X-all_c2xdist=all_cen+all_rad# Only count lengths where meniscus bulges into poredist[all_rad<0]=0.0touch_len=network[touch_length]mask=dist>touch_lenarg_touch=np.argmax(mask,axis=0)# Make sure we only count ones that happen before max pressure# And above min pressure (which will be erroneous)arg_in_range=(arg_touch<a_max)*(arg_touch>a_min)arg_touch[~arg_in_range]=a_max[~arg_in_range]x_touch=pos[arg_touch]*fa# Return the pressure at which a touch happensPc_touch=Pc(x_touch,fa,fb,throatRad,contact,surface_tension)returnPc_touchelif(mode=='men'):iftarget_PcisNone:logger.error(msg='Please supply a target capillary pressure'+' when mode is "men", defaulting to 1.0e-6')target_Pc=1.0e-6else:raiseException('Unrecognized mode')ifnp.abs(target_Pc)<1.0e-6:logger.error(msg='Please supply a target capillary pressure'+' with absolute value greater than 1.0e-6,'+' default to 1.0e-6')target_Pc=1.0e-6# Find the position in-between the minima and maxima corresponding to# the target pressureinds=np.indices(np.shape(t_Pc))# Change values outside the range between minima and maxima to be those# Valuesmask=inds[0]<np.ones(len(pos))[:,np.newaxis]*a_mint_Pc[mask]=(np.ones(len(pos))[:,np.newaxis]*Pc_min)[mask]mask=inds[0]>np.ones(len(pos))[:,np.newaxis]*a_maxt_Pc[mask]=(np.ones(len(pos))[:,np.newaxis]*Pc_max)[mask]# Find the argument at or above the target Pressuremask=t_Pc>=target_Pcarg_x=np.argmax(mask,axis=0)# If outside range change to minima or maxima accordinglyarg_x[target_Pc<Pc_min]=a_min[target_Pc<Pc_min]arg_x[target_Pc>Pc_max]=a_max[target_Pc>Pc_max]xpos=pos[arg_x]*faxmin=pos[a_min]*faxmax=pos[a_max]*fa# Outputmen_data={}men_data['pos']=xposmen_data['rx']=rx(xpos,fa,fb,throatRad)men_data['alpha']=fill_angle(xpos,fa,fb,throatRad)men_data['alpha_min']=fill_angle(xmin,fa,fb,throatRad)men_data['alpha_max']=fill_angle(xmax,fa,fb,throatRad)men_data['c2x']=c2x(xpos,fa,fb,throatRad,contact)men_data['gamma']=cap_angle(xpos,fa,fb,throatRad,contact)men_data['radius']=rad_curve(xpos,fa,fb,throatRad,contact)# xpos is relative to the throat centermen_data['center']=(xpos-men_data['c2x'])men_data['men_max']=men_data['center']-men_data['radius']logger.info(mode+' calculated for Pc: '+str(target_Pc))returnmen_data
[docs]defsinusoidal(phase,mode='max',target_Pc=None,num_points=1e3,r_toroid=5e-6,throat_diameter='throat.diameter',pore_diameter='pore.diameter',touch_length='throat.touch_length',surface_tension='throat.surface_tension',contact_angle='throat.contact_angle'):r""" Wrapper for the toroidal model to implement a sinusoidal profile. The quarter-wavelength is equal to toroidal radius r_toroid. The amplitude is equal to the toroidal radius multiplied by the ratio of the throat radius and average connecting pore radius. Notes ----- The capillary pressure equation for a sinusoidal throat is extended from the Washburn equation as [1]_: .. math:: P_c = -\frac{2\sigma(cos(\alpha + \theta))}{r(x)} where alpha is: .. math:: \alpha = arctan(\frac{dr}{dx}) References ---------- .. [1] A. Forner-Cuenca et. al, Advanced Water Management in PEFCs: Diffusion Layers with Patterned Wettability. J. ECS. 163, 9, F1038-F1048 (2016). """phase['throat.scale_a']=r_toroidphase['throat.scale_b']=r_toroidoutput=general_toroidal(phase=phase,mode=mode,profile_equation='sinusoidal',target_Pc=target_Pc,num_points=num_points,throat_diameter=throat_diameter,touch_length=touch_length,surface_tension=surface_tension,contact_angle=contact_angle)returnoutput
[docs]defpurcell(phase,mode='max',target_Pc=None,num_points=1e3,r_toroid=5e-6,throat_diameter='throat.diameter',touch_length='throat.touch_length',surface_tension='throat.surface_tension',contact_angle='throat.contact_angle'):r""" Wrapper for the general toroidal model to implement the Purcell equation for a torus with cylindrical profile and toroidal radius r_toroid. Notes ----- This approach accounts for the converging-diverging nature of many throat types. Advancing the meniscus beyond the apex of the toroid requires an increase in capillary pressure beyond that for a cylindical tube of the same radius. The details of this equation are described by Mason and Morrow [1], and explored by Gostick [2] in the context of a pore network model. References ---------- [1] Missing reference [2] Missing reference """phase['throat.scale_a']=r_toroidphase['throat.scale_b']=r_toroidoutput=general_toroidal(phase=phase,mode=mode,profile_equation='elliptical',target_Pc=target_Pc,num_points=num_points,throat_diameter=throat_diameter,touch_length=touch_length,surface_tension=surface_tension,contact_angle=contact_angle)returnoutput