diff --git a/graphics.py b/graphics.py index d30a0b8984bfc14bbda5cc9ac042b3731881f37d..d9a7876872f22530f425505c59f210ef3109ade8 100644 --- a/graphics.py +++ b/graphics.py @@ -130,7 +130,7 @@ def plot_CBL_assimilation(exp,figtitle,which_cycle=0,ax=None): #ax.set_title(r'$\theta$ (K), posterior') return ax -def plot_CBL_PE(exp,figtitle,parameter_id=0,plot_spread=False,ax=None): +def plot_CBL_PE(exp,figtitle,parameter_id=0,show_spread=False,show_true_parameter=False,show_transformed_parameter=False,plot_parameter_histogram = False,ax=None): if exp.nr.do_parameter_estimation: @@ -138,17 +138,18 @@ def plot_CBL_PE(exp,figtitle,parameter_id=0,plot_spread=False,ax=None): mean_color = 'royalblue' mean_color_2 = 'orange' + pt = exp.nr.parameter_transform if parameter_id==0: true_value = exp.nr.pfac + true_value_transformed = pt[0](exp.nr.pfac,kind='dir') - pt = exp.nr.parameter_transform parameter_number = exp.nr.parameter_number par_tran = exp.da.backgrounds[:,-parameter_number:,:][:,parameter_id,:] par_phys = pt[parameter_id](par_tran,kind='inv') initpar_phys = pt[parameter_id](exp.da.initial_perturbed_parameters,kind='inv') + initpar_tran = exp.da.initial_perturbed_parameters t = exp.da.time/3600. - plot_parameter_histogram = True if plot_parameter_histogram: p10beg_tran = np.percentile(par_tran[0,:], 10) p90beg_tran = np.percentile(par_tran[0,:], 90) @@ -190,7 +191,7 @@ def plot_CBL_PE(exp,figtitle,parameter_id=0,plot_spread=False,ax=None): printfigure = False if printfigure: - if plot_spread: + if show_spread: ii=2 else: ii=1 @@ -199,34 +200,69 @@ def plot_CBL_PE(exp,figtitle,parameter_id=0,plot_spread=False,ax=None): ax1 = fig.add_subplot(ii,1,1) else: ax1 = ax + + if show_transformed_parameter: + par = par_tran + initpar = initpar_tran + else: + par = par_phys + initpar = initpar_phys # Initial parameter distribution - ax1.step([0,t[0]],np.median(initpar_phys,axis=1)*np.array([1,1]),color=mean_color) - ax1.step([0,t[0]],np.mean(initpar_phys,axis=1)*np.array([1,1]),color=mean_color_2) - bmin = np.percentile(initpar_phys, 10) - bmax = np.percentile(initpar_phys, 90) + ax1.step([0,t[0]],np.median(initpar,axis=1)*np.array([1,1]),color=mean_color,label=r'ensemble median') + ax1.step([0,t[0]],np.mean(initpar,axis=1)*np.array([1,1]),color=mean_color_2,label=r'ensemble mean, $\mu$') + bmin = np.percentile(initpar, 10) + bmax = np.percentile(initpar, 90) ax1.fill_between([0,t[0]],\ y1=[bmin,bmin],\ y2=[bmax,bmax],\ color=spread_color, edgecolor=None, - alpha=0.3) + alpha=0.3, + label=r'10-90 percentile range') # Later times - ax1.step(t,np.median(par_phys,axis=1),color=mean_color) - ax1.step(t,np.mean(par_phys,axis=1),color=mean_color_2) + ax1.step(t,np.median(par,axis=1),color=mean_color) + ax1.step(t,np.mean(par,axis=1),color=mean_color_2) for i in range(t.size-1): - bmin = np.percentile(par_phys, 10, axis=1)[i+1] - bmax = np.percentile(par_phys, 90, axis=1)[i+1] + bmin = np.percentile(par, 10, axis=1)[i+1] + bmax = np.percentile(par, 90, axis=1)[i+1] ax1.fill_between(t[i:i+2],\ y1=[bmin,bmin],\ y2=[bmax,bmax],\ color=spread_color, edgecolor=None, alpha=0.3) - ax1.axhline(y=true_value,linestyle='--',color='black') + if show_true_parameter: + if show_transformed_parameter: + ax1.axhline(y=true_value_transformed,linestyle='--',color='black') + else: + ax1.axhline(y=true_value,linestyle='--',color='black') ax1.set_xlim([0,t.max()]) - if printfigure and plot_spread: + if (not printfigure) and show_spread: + bmin = (np.mean(initpar)-np.std(initpar)) + bmax = (np.mean(initpar)+np.std(initpar)) + ax1.fill_between([0,t[0]],\ + y1=[bmin,bmin],\ + y2=[bmax,bmax],\ + color='lightgrey', + edgecolor=None, + alpha=0.3, + zorder=10, + label=r'$\mu\pm\sigma$') + for i in range(t.size-1): + bmin = (np.mean(par,axis=1)-np.std(par,axis=1))[i+1] + bmax = (np.mean(par,axis=1)+np.std(par,axis=1))[i+1] + ax1.fill_between(t[i:i+2],\ + y1=[bmin,bmin],\ + y2=[bmax,bmax],\ + color='lightgrey', + edgecolor=None, + alpha=0.3, + zorder=10) + ax1.legend(loc=1,frameon=False,fontsize=6) + + if printfigure and show_spread: ax2 = fig.add_subplot(ii,1,2) ax2.plot(t,par_phys.std(axis=1),color=mean_color,label='physical') ax2.plot(t,par_tran.std(axis=1),color=mean_color_2,label='transformed', linestyle='--') @@ -261,39 +297,46 @@ def plot_CBL_identifiability(cbl,sigma_o,ax=None): times = times[:,None]+np.zeros((ntimes,nobs)) # Read model equivalents and state vector elements - theta = cbl.da.model_equivalents # ntim*nobs*nens + #theta = cbl.da.model_equivalents # ntim*nobs*nens pfac = cbl.da.backgrounds[:,-npar,:] # ntim*nx*nens pfac = cbl.nr.parameter_transform[0](pfac,kind='inv') # Read observation coordinates if cbl.da.obs_coordinates.ndim == 1: - zobs = cbl.da.obs_coordinates - zobs = zobs[None,:]+np.zeros(times.shape) + zz = cbl.da.obs_coordinates + zz = zz[None,:]+np.zeros(times.shape) elif cbl.da.obs_coordinates.ndim == 2: - zobs = cbl.da.obs_coordinates + zz = cbl.da.obs_coordinates # At each time, check if the coordinate array is sorted - # if not, resort both coordinates and model equivalents + # if not, resort it. + theta = np.zeros((ntimes,nobs,nens))+np.nan + zobs = np.zeros((ntimes,nobs))+np.nan + innov = np.zeros((ntimes,nobs))+np.nan + cov_yy = np.zeros((ntimes,nobs))+np.nan + cov_pp = np.zeros((ntimes,nobs,npar))+np.nan + cov_py = np.zeros((ntimes,nobs,npar))+np.nan + increments = np.zeros((ntimes,nobs,npar))+np.nan + for k in range(ntimes): - if is_sorted(zobs[k,:]): - pass + if is_sorted(zz[k,:]): + indices = np.arange(nobs) else: - indices = np.argsort(zobs[k,:]) - zobs[k,:] = zobs[k,indices] - theta[k,:] = theta[k,indices] - cbl.da.innov[k,:] = cbl.da.innov[k,indices] # ntim*nobs - cbl.da.cov_yy[k,:] = cbl.da.cov_yy[k,indices] # ntim*nobs - cbl.da.cov_pp[k,:] = cbl.da.cov_pp[k,indices] # ntim*nobs(*npar) - cbl.da.cov_py[k,:] = cbl.da.cov_py[k,indices] # ntim*nobs(*npar) - cbl.da.increments[k,:] = cbl.da.increments[k,indices] # ntim*nobs(*npar) + indices = np.argsort(zz[k,:]) + zobs[k,:] = zz[k,indices] + theta[k,:,:] = cbl.da.model_equivalents[k,indices,:] + innov[k,:] = cbl.da.innov[k,indices] # ntim*nobs + cov_yy[k,:] = cbl.da.cov_yy[k,indices] # ntim*nobs + cov_pp[k,:,:] = cbl.da.cov_pp[k,indices,:] # ntim*nobs(*npar) + cov_py[k,:,:] = cbl.da.cov_py[k,indices,:] # ntim*nobs(*npar) + increments[k,:,:] = cbl.da.increments[k,indices,:] # ntim*nobs(*npar) if hasattr(cbl.da,'cov_py'): - sigma2_yb = cbl.da.cov_yy # ntim*nobs - sigma2_p = np.squeeze(cbl.da.cov_pp) # ntim*nobs(*npar) - covariance = np.squeeze(cbl.da.cov_py) # ntim*nobs(*npar) + sigma2_yb = cov_yy # ntim*nobs + sigma2_p = np.squeeze(cov_pp) # ntim*nobs(*npar) + covariance = np.squeeze(cov_py) # ntim*nobs(*npar) correlation = covariance/(sigma2_yb*sigma2_p)**0.5 - innov = cbl.da.innov - deltap = np.squeeze(cbl.da.increments) # ntim*nobs(*npar) + deltap = np.squeeze(increments) # ntim*nobs(*npar) # Shorthand for plots A = correlation.T @@ -417,7 +460,10 @@ def plot_p(p_factors,theta_profiles,zt,figtitle,ax=None): ax.plot(theta_profiles[i],zt,label='$p=%4.1f$'%p_factors[i]) return ax -def plot_spread(cbl,plot='spread',ax=None): +def plot_free_ensemble(cbl,show='spread',ax=None,cmap='viridis'): + + # Read parameters + param = cbl.initial_perturbed_parameters # Read relevant dimensions times = cbl.history['0000']['time'] @@ -432,10 +478,19 @@ def plot_spread(cbl,plot='spread',ax=None): theta[k,:,:]= cbl.history['%04u'%k]['theta'] # Compute spread - if plot=='spread': + if show=='spread': bkgd = theta.std(axis=0) - elif plot=='mean': + contour_color = 'white' + elif show=='mean': bkgd = theta.mean(axis=0) + contour_color = 'white' + if show=='cov': + cov = np.zeros((nz,ntimes))+np.nan + for ii in range(nz): + for jj in range(ntimes): + cov[ii,jj] = np.cov(param,theta[:,ii,jj])[0,1] + bkgd = cov + contour_color = 'black' # Plot zmax = 2000 @@ -443,54 +498,211 @@ def plot_spread(cbl,plot='spread',ax=None): if ax is not None: # Make plots - c = ax.pcolormesh(times/3600.,zt,bkgd) + c = ax.pcolormesh(times/3600.,zt,bkgd,cmap=cmap) ax.contour(times/3600.,zt,theta.mean(axis=0), np.linspace(cbl.theta_0,cbl.theta_0+cbl.gamma*zmax,ncont), - colors='white', + colors=contour_color, linestyles='--', linewidths=0.75) ax.set_ylim([0,zmax]) return ax,c -def plot_diagnostics(experiments_pe,experiments_nope,labels,filename): +def plot_diagnostics(exp,show='errors',axes=None,label='',linecolor='blue'): + + # See if the experiment is an OSSE + if (not hasattr(exp.da,'truths')): + truth_exists = False + else: + truth_exists = True + + # Get the data + sigma_o = exp.dg.varo**0.5 + armsd = exp.dg.aRMSD_t + brmsd = exp.dg.bRMSD_t + bsprd = exp.dg.bSprd_t + if truth_exists: + armse = exp.dg.aRMSE_t + brmse = exp.dg.bRMSE_t + + # Read observation coordinates + # In the 2D case, it is assumed that the set of + # observation coordinates does not change + # from time to time. Anyway, coordinates need + # to be sorted. Diagnostic quantities are + # already computed in a way that takes the correct + # spatial sorting into account. + + if exp.da.obs_coordinates.ndim == 1: + z = np.sort(exp.da.obs_coordinates) + elif exp.da.obs_coordinates.ndim == 2: + z = np.sort(exp.da.obs_coordinates[0,:]) + + if axes is not None and len(axes)==4: + + # Plot errors (wrt to true state) only if true state exists + if show=='errors' and truth_exists: + + axes[0].plot(armse,z,label=label,color=linecolor)#,color=linecolors[i]) + axes[1].plot(brmse,z,color=linecolor) + axes[2].plot(brmse-armse,z,color=linecolor) + axes[3].plot(np.sqrt(bsprd**2+sigma_o**2)/brmse,z,color=linecolor) + + elif show=='deviations': + + # Compute time-average mean bias of background and analyses + mean_bias_a_i1 = exp.dg.omina.mean(axis=0) + mean_bias_b_i1 = exp.dg.ominb.mean(axis=0) + # Plots + axes[0].plot(armsd,z,label=label,color=linecolor) + axes[1].plot(brmsd,z,color=linecolor) + axes[2].plot(brmsd-armsd,z,color=linecolor) + axes[3].plot(np.sqrt(bsprd**2+sigma_o**2)/brmsd,z,color=linecolor) + + return axes + +def plot_linearity(cbl,ax=None): - linecolors = p.rcParams['axes.prop_cycle'].by_key()['color'] - - fig, [[ax1, ax2],[ax3, ax4]] = p.subplots(2,2,constrained_layout=True) - fig.set_size_inches(6,4) - z = experiments_pe[0].obs_coordinates - z_pbl = z*1. - z_pbl[z>1500] = np.nan - for i in range(len(experiments_pe)): - i1 = experiments_pe[i].dg - i2 = experiments_nope[i].dg - ax1.plot(i1.aRMSE_t,z,label=labels[i],color=linecolors[i]) - ax1.plot(i2.aRMSE_t,z,color=linecolors[i],dashes=[3,1],alpha=0.3) - # - ax2.plot(i1.bRMSE_t,z,label=labels[i],color=linecolors[i]) - ax2.plot(i2.bRMSE_t,z,color=linecolors[i],dashes=[3,1],alpha=0.3) - # - ax3.plot(i1.bRMSE_t-i1.aRMSE_t,z,label=labels[i],color=linecolors[i]) - ax3.plot(i2.bRMSE_t-i2.aRMSE_t,z,color=linecolors[i],dashes=[3,1],alpha=0.3) - # - ax4.plot(i1.bSprd_t/i1.bRMSE_t,z_pbl,label=labels[i],color=linecolors[i]) - ax4.plot(i2.bSprd_t/i2.bRMSE_t,z_pbl,color=linecolors[i],dashes=[3,1],alpha=0.3) - ax1.set_title('a) Analysis error') - ax1.set_xlabel(r'RMSE$^a_\theta$') - ax2.set_title('b) First-guess error') - ax2.set_xlabel(r'RMSE$^b_\theta$') - ax3.set_title('c) Error reduction') - ax3.set_xlabel(r'RMSE$^b_\theta-$RMSE$^a_\theta$') - ax4.set_title('d) Spread-error consistency') - ax4.set_xlabel(r'$\sigma^b_\theta$/RMSE$^b_\theta$') - ax1.set_ylabel('height (m)') - ax3.set_ylabel('height (m)') + # Read parameters + param = np.squeeze(cbl.parameter_transform[0](cbl.initial_perturbed_parameters,kind='inv')) + + # Get the indices that sort parameters in ascending order + indices = np.argsort(param) + + # Read relevant dimensions + times = cbl.history['0000']['time'] + zt = cbl.zt + nz = cbl.nz + ntimes = times.size + nens = cbl.nens + + # Reconstruct_history + theta = np.zeros((nens,nz,ntimes))+np.nan + for k in range(nens): + theta[k,:,:]= cbl.history['%04u'%k]['theta'] + + # Make plot + if ax is not None: + tt = -1 # pick the last time in the history + zz = [0,10,20,30] # pick a few heights + hh = [int(cbl.dz/2)+i*cbl.dz for i in zz] + labels = [r'$z=%s$ m'%i for i in hh] + for z,label in zip(zz,labels): + #ax.scatter(param[indices],theta[indices,z,tt],s=5,label=label) + ax.plot(param[indices],theta[indices,z,tt],label=label) + ax.legend(loc=3,frameon=False,fontsize=6,facecolor='lightgrey') + + return ax + +def plot_B_matrix(exp,timeid=-1): + + # Read the background state for the requested analysis time + xx = exp.da.backgrounds[timeid] + nvar,nens = xx.shape + + # Analysis time + tt = exp.da.time[timeid] + + # Compute deviations from ensemble mean + X = xx.T-np.tile(xx.mean(axis=1),(nens,1)) + + # Standardize deviations from ensemble mean + X = X/np.tile(xx.std(axis=1),(nens,1)) + + # Compute covariance matrix + Pb = np.matmul(X.T,X)/(nens-1) + v = np.abs(Pb[:-1,:-1]).max() + + # Plot + fig = p.figure(0) + fig.set_size_inches(4,3) + sp1 = fig.add_subplot(1,1,1) + c = sp1.pcolormesh(range(nvar),range(nvar),Pb,vmin=-v,vmax=v,cmap='RdBu_r',shading='nearest') + sp1.set_aspect('equal','box') + sp1.set_xlim(-0.5,nvar-0.5) + sp1.set_ylim(-0.5,nvar-0.5) + sp1.set_title('$1/(N-1)\cdot X^TX$, $t=%4.2f$ h'%(tt/3600)) + p.colorbar(c) + fig.tight_layout() + fig.savefig('Covariance_matrix_%05u_s.png'%tt,format='png',dpi=150) + p.close(fig) + +def plot_desroziers(dgs,filename='desroziers_diagnostics.png',labels=None,logscale=False,ax=None): + from matplotlib.ticker import MaxNLocator + + newfig = False + if ax is None: + newfig = True + fig = p.figure(0) + fig.set_size_inches(4,3) + ax = fig.add_subplot(1,1,1) + + for i,dg in zip( range(len(dgs)) , dgs ): + if labels is not None: + # Two formulations of the same thing; they give the same numbers! + #ax1.scatter( dg.dobdob_ta-dg.mean_ominb**2 , dg.varo+dg.varb_ta , s=5, label = labels[i]) + ax.scatter( dg.sigma_ominb**2 , dg.varo+dg.varb_ta , s=5, label = labels[i]) + else: + # Two formulations of the same thing; they give the same numbers! + #ax1.scatter( dg.dobdob_ta-dg.mean_ominb**2 , dg.varo+dg.varb_ta , s=5) + ax.scatter( dg.sigma_ominb**2 , dg.varo+dg.varb_ta , s=5) # + #ax.set_title("Consistency of innovations",fontsize=8) + ax.set_xlabel(r'${\langle}\sigma_d^2{\rangle}_t$') # '${\langle}d_{ob}^2{\rangle}_t$' + ax.set_ylabel(r'$\sigma_o^2+{\langle}\sigma_b^2{\rangle}_t$') + + if labels is not None: + ax.legend(frameon=False, fontsize=6, ncol=2) + + if logscale: + ax.axline((dgs[0].varo[0],dgs[0].varo[0]), slope=1, dashes=[3,2], color='k') + ax.set_xscale("log") + ax.set_yscale("log") + else: + ax.axline((0,0), slope=1, dashes=[3,2], color='k') + ax.axis("square") + ax.xaxis.set_major_locator(MaxNLocator(5)) + ax.yaxis.set_major_locator(MaxNLocator(5)) + + if newfig: + fig.tight_layout() + fig.savefig(filename,format='png',dpi=150) + p.close(fig) + + return ax + +def plot_overview(exp,label,filename,show_spread=False,show_transformed_parameter=False,show_true_parameter=True): + + maxtime = exp.nr.maxtime + fig, [[ax0, ax1], [ax2, ax3]] = p.subplots(2,2,constrained_layout=True) + fig.set_size_inches(6,6) # - ax4.axvline(x=1,color='k',linewidth=0.5,dashes=[3,1]) - ax2.sharey(ax1) - ax4.sharey(ax3) - p.setp(ax2.get_yticklabels(), visible=False) - p.setp(ax4.get_yticklabels(), visible=False) + [ax0,ax1,ax2],c0,c1,c2 = plot_CBL_identifiability(exp,exp.obs_error_sdev_assimilate[0],ax=[ax0,ax1,ax2]) + ax0.set_title(r'a) Exp. %s, $\rho(p\prime\prime,y_b}$)'%label) + ax0.set_xlabel('Time (h)') + ax0.set_ylabel('Height (m)') + ax1.set_title(r'b) Exp. %s, $\delta\overline{y}\cdot(\sigma_{p\prime\prime}/\sigma_{y^b})$'%label) + ax1.set_xlabel('Time (h)') + ax1.set_ylabel('Height (m)') + ax2.set_title(r'c) Exp. %s, $\delta\overline{p}\prime\prime$'%label) + ax2.set_xlabel('Time (h)') + ax2.set_ylabel('Height (m)') + ax3 = plot_CBL_PE(exp,None,ax=ax3,show_spread=show_spread,show_transformed_parameter=show_transformed_parameter,show_true_parameter=show_true_parameter) + if show_transformed_parameter: + ax3.set_title(r'd) Exp. %s, evolution of $p\prime\prime$'%label) + else: + ax3.set_title(r'd) Exp. %s, evolution of $p$'%label) + ax3.set_xlabel('Time (h)') + ax3.set_yticks(np.arange(int(maxtime/3600))) + ax0.set_xlim([0,maxtime/3600]) + ax1.set_xlim([0,maxtime/3600]) + ax2.set_xlim([0,maxtime/3600]) + ax3.set_xlim([0,maxtime/3600]) + ax0.set_xticks(np.arange((maxtime+1)/3600)) + ax1.set_xticks(np.arange((maxtime+1)/3600)) + ax2.set_xticks(np.arange((maxtime+1)/3600)) + ax3.set_xticks(np.arange((maxtime+1)/3600)) + p.colorbar(c0,orientation='horizontal') + p.colorbar(c1,orientation='horizontal') + p.colorbar(c2,orientation='horizontal') # fig.savefig(filename,format='png',dpi=300) p.close(fig)