Aircraft sampling representativeness

%load_ext autoreload
%autoreload 2
import warnings

import numpy as np
import statsmodels.api as sm

import matplotlib.pyplot as plt
import matplotlib.colors as colors

import emergent_constraint as ec
import figure_panels
import models
import obs_aircraft
import util

Load model data

model_list = ['CT2019B'] 
model_objs = {}
for name in model_list:
    model_objs[name] = models.Model(name)
model_objs  
{'CT2019B': <models.core.Model at 0x2b89276a4b90>}
dsets_theta_bins = {}
for model in model_list:
    ds = model_objs[model].open_derived_dataset(
        'molefractions_theta_bins',
        kwargs_name='SO-10K-bins-300K_275K',
        lat_bounds=(-80., -45.),
        theta_bins=[(295., 305.), (270., 280.),],
    )
    dsets_theta_bins[model] = ds.sel(time=slice('2009', '2020')).compute()
dsets_theta_bins    
assuming cache is correct
reading cached file: /glade/u/home/mclong/codes/so-co2-airborne-obs/so-co2-airborne-obs/models/data-cache/CT2019B/molefractions_theta_bins.SO-10K-bins-300K_275K.nc
{'CT2019B': <xarray.Dataset>
 Dimensions:            (time: 3652, theta_bins: 2, d2: 2)
 Coordinates:
   * time               (time) datetime64[ns] 2009-01-01T12:00:00 ... 2018-12-...
   * theta_bins         (theta_bins) float64 300.0 275.0
     theta_bins_bounds  (theta_bins, d2) float64 295.0 305.0 270.0 280.0
 Dimensions without coordinates: d2
 Data variables:
     CO2                (time, theta_bins) float32 383.7 383.5 ... 406.2 405.9
     Z3                 (time, theta_bins) float32 4.937e+03 669.0 ... 807.7
     P                  (time, theta_bins) float32 5.344e+04 ... 8.992e+04
     T                  (time, theta_bins) float32 249.3 268.7 ... 245.7 267.5
     theta              (time, theta_bins) float32 299.8 275.8 ... 299.8 276.2
     CO2_OCN            (time, theta_bins) float32 -10.24 -10.45 ... -26.17
     CO2_LND            (time, theta_bins) float32 -5.733 -5.565 ... -12.81
     CO2_FFF            (time, theta_bins) float32 31.47 31.31 ... 76.79 76.64
     area               (time, theta_bins) float64 1.375e+11 ... 1.264e+11}
dsets_theta_bins_lon = {}
for model in model_list:
    ds = model_objs[model].open_derived_dataset(
        'molefractions_theta_bins_sectors',
        kwargs_name='SO-10K-bins-300K_275K',
        lat_bounds=(-80., -45.),
        theta_bins=[(295., 305.), (270., 280.),],
    )
    for v in ds.data_vars:
        if 'time' in ds[v].dims:
            newdims = ['time']  + [d for d in ds[v].dims if d != 'time']
            ds[v] = ds[v].transpose(*newdims)
    dsets_theta_bins_lon[model] = ds.sel(time=slice('2009', '2020')).compute()
dsets_theta_bins_lon    
assuming cache is correct
reading cached file: /glade/u/home/mclong/codes/so-co2-airborne-obs/so-co2-airborne-obs/models/data-cache/CT2019B/molefractions_theta_bins_sectors.SO-10K-bins-300K_275K.nc
{'CT2019B': <xarray.Dataset>
 Dimensions:                (lon_bins: 6, time: 3652, theta_bins: 2, d2: 2)
 Coordinates:
   * lon_bins               (lon_bins) float64 30.0 90.0 150.0 210.0 270.0 330.0
   * time                   (time) datetime64[ns] 2009-01-01T12:00:00 ... 2018...
   * theta_bins             (theta_bins) float64 300.0 275.0
     theta_bins_bounds      (theta_bins, d2) float64 295.0 305.0 270.0 280.0
 Dimensions without coordinates: d2
 Data variables:
     CO2                    (time, lon_bins, theta_bins) float32 383.7 ... 405.8
     Z3                     (time, lon_bins, theta_bins) float32 4.523e+03 ......
     P                      (time, lon_bins, theta_bins) float32 5.674e+04 ......
     T                      (time, lon_bins, theta_bins) float32 254.0 ... 269.1
     theta                  (time, lon_bins, theta_bins) float32 299.9 ... 275.2
     CO2_OCN                (time, lon_bins, theta_bins) float32 -10.19 ... -2...
     CO2_LND                (time, lon_bins, theta_bins) float32 -5.83 ... -12.85
     CO2_FFF                (time, lon_bins, theta_bins) float32 31.53 ... 76.65
     area                   (time, lon_bins, theta_bins) float64 1.331e+11 ......
     coord_lon_bins_bounds  (lon_bins, d2) float64 0.0 60.0 60.0 ... 300.0 360.0}

Compute a smoothed daily-climatology

window = 30 
dsets_theta_diff_clm = {}
dsets_theta_diff_lon_clm = {}
for model in model_list:
    with warnings.catch_warnings():
        warnings.simplefilter("ignore", category=RuntimeWarning)    
        dsets_theta_diff_clm[model] = util.mavg_periodic_ds(
            util.daily_climatology(dsets_theta_bins[model]).diff('theta_bins'), 
            window,
        )
        dsets_theta_diff_lon_clm[model] = util.mavg_periodic_ds(
            util.daily_climatology(dsets_theta_bins_lon[model]).diff('theta_bins'), 
            window,
        )
        
dsets_theta_diff_clm
skipping leap days
skipping leap days
{'CT2019B': <xarray.Dataset>
 Dimensions:            (theta_bins: 1, time: 365, d2: 2)
 Coordinates:
   * theta_bins         (theta_bins) float64 275.0
   * time               (time) int64 1 2 3 4 5 6 7 ... 360 361 362 363 364 365
 Dimensions without coordinates: d2
 Data variables:
     theta_bins_bounds  (theta_bins, d2) float64 -25.0 -25.0
     CO2                (time, theta_bins) float64 -0.5201 -0.5233 ... -0.5165
     Z3                 (time, theta_bins) float64 -4.4e+03 ... -4.406e+03
     P                  (time, theta_bins) float64 3.876e+04 ... 3.88e+04
     T                  (time, theta_bins) float64 21.03 20.98 ... 21.15 21.09
     theta              (time, theta_bins) float64 -23.77 -23.76 ... -23.77
     CO2_OCN            (time, theta_bins) float64 -0.4943 -0.4972 ... -0.4912
     CO2_LND            (time, theta_bins) float64 0.1532 0.1539 ... 0.1523
     CO2_FFF            (time, theta_bins) float64 -0.1792 -0.1804 ... -0.178
     area               (time, theta_bins) float64 -1.053e+10 ... -1.058e+10}
clobber = False 
clobber_deep = False
constraint_type = 'ocean_constraint'
campaign_info = obs_aircraft.get_campaign_info(verbose=False)
model_input_lists = ec.get_model_tracer_lists(constraint_type)

obj = ec.whole_enchilada(clobber=clobber_deep, **model_input_lists)
ac = obj.get_ac(**ec.get_parameters('default'), clobber=clobber)
ac.theta_bins
((295.0, 305.0), (-inf, 280.0))
model = 'CT2019B'

figs, axs = util.canvas(1, 1)
for lon_bin in dsets_theta_diff_lon_clm[model].lon_bins.values:
    figure_panels.vertical_gradient_seasonal(
        dsets_theta_diff_lon_clm[model].sel(lon_bins=lon_bin), 
        axs[0, 0], #co2_var_list=['CO2'],
)
_images/gradients-aircraft-sampling_11_0.png
df_air_model_flts = ac.get_flight_gradients({model: obj.dfs_model[f'{model}-CO2']})
df_model_camp = ac.get_campaign_gradients({model: obj.dfs_model[f'{model}-CO2']})
campaign_info = obs_aircraft.get_campaign_info(verbose=False)
model = 'CT2019B'
ds_ΔθCO2 = dsets_theta_bins[model].diff('theta_bins')
ΔθCO2_za = {}
for campaign, info in campaign_info.items():
    za_grad = (ds_ΔθCO2.CO2
               .sel(time=slice(info['time_bound'][0], info['time_bound'][1]))
               .mean('time')
              )
    ΔθCO2_za[campaign] = float(za_grad.values)
import cmocean
from matplotlib.colors import rgb2hex
color_lon = {}
bins = dsets_theta_diff_lon_clm[model].lon_bins.values
rgb = cmocean.tools.get_dict(cmocean.cm.phase, N=len(bins)+1)
for name, r, g, b in zip(bins, rgb['red'], rgb['green'], rgb['blue'], ):
    color_lon[name] = rgb2hex([r[1], g[1], b[1]])
color_lon
{30.0: '#a8780d',
 90.0: '#359943',
 150.0: '#1e93a8',
 210.0: '#7d73f0',
 270.0: '#d02fd0',
 330.0: '#d74957'}

Visualization

fig, axs = util.canvas(2, 1)

ax = axs[0, 0]
for lon_bin in dsets_theta_diff_lon_clm[model].lon_bins.values:
    ds = dsets_theta_diff_lon_clm[model].sel(lon_bins=lon_bin)
    bnds = ds.coord_lon_bins_bounds.values
    x, y = util.antyear_daily(ds.time, ds.CO2)
    h = ax.plot(x, y, 
                color=color_lon[lon_bin], 
                label=f'{bnds[0]:0.0f}-{bnds[1]:0.0f}°E',
                linestyle='-', 
                linewidth=2
               )   

ds = dsets_theta_diff_clm[model]
x, y = util.antyear_daily(ds.time, ds.CO2)    
h = ax.plot(x, y, 
            color='k',          
            label=f'Zonal mean',
            linestyle='-', 
            linewidth=2
           )   
figure_panels.obs_theta_gradient(df_air_model_flts, ax, just_the_median=True, median_size=6)
ax.set_xlim((-10, 375))
ax.set_xticks(figure_panels.bomday)
ax.set_xticklabels([f'        {m}' for m in figure_panels.monlabs_ant]+[''])
ax.set_ylabel('$\Delta_{ θ}$CO$_2$ [ppm]')
#ax.set_ylim(axs[0, 0].get_ylim())
ax.set_xlim((-10, 375))
ax.legend(loc='lower left');


ax = axs[1, 0]
marker_spec = figure_panels.marker_spec_campaigns()
X = []
Y = []
for c in campaign_info.keys():
    x, y = df_model_camp.loc[c].gradient_mean, ΔθCO2_za[c]
    ax.plot(x, y, **marker_spec[c], label=c, linestyle='None')
    X.append(x); Y.append(y)

ax.legend(loc='upper left', ncol=2, columnspacing=0.4, handletextpad=0.1)    

ols = sm.OLS(Y, sm.add_constant(X))
intercept, slope = ols.fit().params
stderr_intercept, stderr_slope = ols.fit().bse
r2 = ols.fit().rsquared

x = np.array([np.array(X).min(), np.array(X).max()])
ax.plot(x, x * slope + intercept, 'k-');
ax.set_ylabel('Zonal mean $\Delta_{ θ}$CO$_2$ [ppm]');
ax.set_xlabel('Aircraft sampling $\Delta_{ θ}$CO$_2$ [ppm]');

str_text = (
    f'y = {intercept:0.1f}±{stderr_intercept:0.1f} + {slope:0.2f}±{stderr_slope:0.2f} x\n' + 
    f'r$^2$={r2:0.3f}'
)
xoff = -np.diff(ax.get_xlim()) * 0.6
yoff = np.diff(ax.get_ylim()) * 0.08
ax.text(
    ax.get_xlim()[1]+xoff, ax.get_ylim()[0]+yoff, 
    str_text, 
    fontsize=11, fontweight='bold',
    bbox=dict(facecolor='w', alpha=0.75, edgecolor='None', boxstyle='square,pad=0'),
);

util.label_plots(fig, [ax for ax in axs.ravel()])
util.savefig('seasonal-cycle-zonal-representativeness')
_images/gradients-aircraft-sampling_17_0.png