"""Illustrate a coupled freezing/melting similarity solution. Python 3.14.4; matplotlib 3.10.7 and numpy 2.3.5. Output: paper-71-fields.png in the current directory. Honor MPLCONFIGDIR. """ import math from pathlib import Path def dilution(A): return 1/(1+math.sqrt(math.pi)*A*math.exp(A*A)*math.erfc(-A)) def solve_model(S=40.,eps=2.5e-4,R=2.): lo,hi=0.,6. for _ in range(90): A=(lo+hi)/2 if R*dilution(A)>2/(math.pi*S)+2*eps*A/math.sqrt(math.pi):lo=A else:hi=A A=(lo+hi)/2;B=eps*A+1/(S*math.sqrt(math.pi)) def residual(A,B): a=eps*A theta=math.sqrt(math.pi)*S*B*math.exp(B*B)*(math.erf(B)-math.erf(a)) return (S*a-S*B*math.exp(B*B-a*a)+(1-theta)*math.exp(-a*a)/(math.sqrt(math.pi)*math.erfc(-a)),theta-R*dilution(A)) for _ in range(30): f,g=residual(A,B) if max(abs(f),abs(g))<1e-13:break dA=1e-6;dB=1e-7 af,ag=residual(A+dA,B);bf,bg=residual(A,B+dB) fa,ga=(af-f)/dA,(ag-g)/dA fb,gb=(bf-f)/dB,(bg-g)/dB det=fa*gb-fb*ga A+=(-f*gb+g*fb)/det B+=(-g*fa+f*ga)/det a=eps*A theta=math.sqrt(math.pi)*S*B*math.exp(B*B)*(math.erf(B)-math.erf(a)) assert A>0 and B>a and 00$',xy=(1,r),xytext=(.12,-.2),fontsize=10) ax.annotate('',xy=(1.07,1.3),xytext=(1.07,1.02),arrowprops=dict(arrowstyle='->',color='#b44b35',lw=2)) ax.annotate('',xy=(1.07,r+.17),xytext=(1.07,r+.01),arrowprops=dict(arrowstyle='->',color='#2786a8')) ax.text(.02,-.08,'initial contact $x=0$',fontsize=9,color='#555') ax.set(xlim=(-.03,1.12),ylim=(-1.25,1.8),xticks=[],yticks=[],title='Layers and moving boundaries') ax.text(.5,-1.42,'Layer cartoon, not a common scale',ha='center',fontsize=9) ax=axes[1];us=np.linspace(-2,.065,700);vals=[temperature(float(u),m) for u in us] ax.plot(vals,us,color='#b44b35',lw=2) ax.axhline(m['a'],color='#2786a8',ls='--',lw=1);ax.axhline(m['B'],color='#b44b35',ls='--',lw=1) ax.set(xlabel='$(T-T_m)/\\Delta T$',ylabel='$x/(2\\sqrt{\\kappa t})$',title='Temperature field',xlim=(-1.06,.05),ylim=(-2,.065)) ax.grid(alpha=.2) ins=ax.inset_axes([.51,.15,.43,.33]);ui=np.linspace(m['a'],m['B'],100) ins.plot([temperature(float(u),m) for u in ui],ui/m['B'],color='#b44b35') ins.set(xlabel='$(T-T_m)/\\Delta T$',ylabel='$x/b$',title='Ice: enlarged',xlim=(-m['theta']*1.2,.001),ylim=(0,1.04)) ins.tick_params(labelsize=8);ins.xaxis.label.set_size(8);ins.yaxis.label.set_size(8);ins.title.set_size(9) ax=axes[2];vs=np.linspace(-3,m['A'],600) ax.plot([concentration(float(v),m) for v in vs],vs,color='#2786a8',lw=2) ax.plot([m['c'],0],[m['A'],m['A']],color='#2786a8',lw=1) ax.plot([0,0],[m['A'],m['A']+.6],color='#2786a8') ax.axhline(m['A'],color='#2786a8',ls='--',lw=1) ax.annotate('$C_i/C_0=%.4f$'%m['c'],xy=(m['c'],m['A']),xytext=(.12,m['A']+.2),fontsize=10) ax.set(xlabel='$C/C_0$',ylabel='$x/(2\\sqrt{Dt})$',title='Salinity field',xlim=(-.04,1.05),ylim=(-3,m['A']+.6)) ax.grid(alpha=.2) fig.suptitle('Ice freezes into fresh water while its lower surface melts into brine',fontsize=13) fig.text(.5,.025,'Illustrative exact similarity solution: $S=40$, $\\epsilon=2.5\\times10^{-4}$, $R=mC_0/\\Delta T=2$. Profile panels have different diffusion scales.',ha='center',fontsize=10) fig.subplots_adjust(left=.045,right=.97,bottom=.16,top=.88,wspace=.45) fig.savefig(Path('paper-71-fields.png'),dpi=100,facecolor='white',transparent=False) plt.close(fig) if cache:cache.cleanup() if __name__=='__main__':main()