Inversions with surface stations

1. Prepare the data to assimilate

Constraints are put into a CIF monitor file for a set of core stations:

Show/Hide Code

  1#####################################################
  2# This code is provided by: I. Pison
  3# contact: isabelle.pison@lsce.ipsl.fr
  4# date: June 2022
  5# project: H2020 VERIFY
  6# relation to CIF: uses plugins, not embedded in.
  7# It is an example, to be used as a basis
  8# for developing other work.
  9# No guarantee as to its compatibility or results
 10# is attached since it is not part of the CIF
 11# Please don't forget acknowledgements if you use it.
 12#####################################################
 13
 14import pandas as pd
 15import numpy as np
 16from pycif.utils.datastores.dump import dump_datastore, read_datastore
 17from pycif.utils.datastores.empty import init_empty
 18import glob
 19import datetime
 20import sys
 21
 22dir_data = '/home/chimereicos/VERIFYdataWP4/Obs2021/core/'
 23spec = 'ch4'
 24max_unc = 2000.
 25
 26# Initializes an empty datastore
 27ds = pd.DataFrame()
 28
 29# put errors from Szenasi et al., 2021: 
 30# A pragmatic protocol for characterising errors 
 31# in atmospheric inversions of methane emissions over Europe, 
 32# Tellus B: Chemical and Physical Meteorology, 
 33# 10.1080/16000889.2021.1914989 
 34TNO_obs_error_file = 'Obs_error_file.txt'
 35f = open(TNO_obs_error_file, 'r')
 36TNO_obs_error = f.readlines()[1:]
 37f.close()
 38TNO_error_station = []
 39TNO_station_category = []
 40TNO_LBC_error = np.empty([len(TNO_obs_error)])
 41TNO_T_error = np.empty([len(TNO_obs_error)])
 42TNO_repr_error = np.empty([len(TNO_obs_error)])
 43for ii in range(len(TNO_obs_error)):
 44    TNO_obs_error_list = TNO_obs_error[ii].split()
 45    TNO_error_station.append(TNO_obs_error_list[0])
 46    TNO_station_category.append(TNO_obs_error_list[1])
 47    if TNO_obs_error_list[2] != 'NaN' : 
 48        TNO_LBC_error[ii] = int(TNO_obs_error_list[2])
 49        TNO_T_error[ii] = int(TNO_obs_error_list[3])
 50        TNO_repr_error[ii] = int(TNO_obs_error_list[4])
 51    else:
 52        TNO_LBC_error[ii] = np.nan
 53        TNO_T_error[ii] = np.nan
 54        TNO_repr_error[ii] = np.nan
 55# squares of errors
 56montain_seor_mean = np.empty([len(TNO_obs_error)])
 57costal_seor_mean = np.empty([len(TNO_obs_error)])
 58other_seor_mean = np.empty([len(TNO_obs_error)])
 59for jj in range(len(TNO_obs_error)):
 60    if TNO_station_category[jj] == 'M':
 61        montain_seor_mean[jj] = np.sqrt((TNO_LBC_error[jj])**2 +\
 62                                    (TNO_T_error[jj])**2 +\
 63                                    (TNO_repr_error[jj])**2)
 64    else:
 65        montain_seor_mean[jj] = np.nan
 66    if TNO_station_category[jj] == 'C':
 67        costal_seor_mean[jj] = np.sqrt((TNO_LBC_error[jj])**2 +\
 68                                   (TNO_T_error[jj])**2 +\
 69                                   (TNO_repr_error[jj])**2)
 70    else:
 71        costal_seor_mean[jj] = np.nan
 72    if TNO_station_category[jj] == 'O':
 73        other_seor_mean[jj] = np.sqrt((TNO_LBC_error[jj])**2 +\
 74                                  (TNO_T_error[jj])**2 +\
 75                                  (TNO_repr_error[jj])**2)
 76    else:
 77        other_seor_mean[jj] = np.nan
 78# Mountain station or not, according to S. Houweling information
 79dt = pd.read_csv('/home/users/ipison/CHIMERE/VERIFY/intercomp_EU/CH4_observation_sitelist.csv', sep=',')
 80
 81# Read VERIFY provided files
 82list_files = glob.glob("{}/{}_*.csv".format(dir_data,spec))
 83nb_header_lines = 9
 84
 85
 86for fi in list_files:
 87
 88   fc=open(fi,'r')
 89   alllines = fc.readlines()
 90   data = alllines[nb_header_lines - 1]
 91   columns =  data.split(' ')
 92   fc.close()
 93   station = fi.split('_')[nbstat].upper()
 94   # time zone for selecting night/day hours
 95   for l in alllines[:nb_header_lines]:
 96      info = l.split(' ')[0]
 97      if info == 'timezone':
 98         utc = l.split('+')
 99         if len(utc) > 1:
100            shift = int(utc[1])
101         else:
102            shift = 0
103   df = pd.read_csv(fi, delimiter=',', header= nb_header_lines-1, names = columns) 
104   # Information to put into the monitor file:
105   # date
106   df.loc[df['minute'] > 60, 'minute'] = 0
107   df['Time']=df['year'].astype(str)+'/'+\
108         df['month'].astype(str)+'/'+\
109         df['day'].astype(str)+'/'+df['hour'].astype(str)+'/'+df['minute'].astype(str)
110   df['date'] = pd.to_datetime(df['Time'],format='%Y/%m/%d/%H/%M')
111   # duration (hours)
112   df = df.assign(duration = 1.) 
113   # station
114   df.rename(columns={'siteid': 'station', 'longitude': 'lon', 'latitude': 'lat'}, inplace=True)
115   # filter on time and type of station
116   # 12:00 to 16:00 local time for not-mountain, 0:00 to 6:00 local time for mountain
117   is_mountain = dt['Mountain site'].loc[dt['ID'] == station].values[0]
118   df['local time'] =  df['hour'] + shift
119   if is_mountain == 'No':
120     df = df.loc[ (df['local time'] < 16) & (df['local time'] >= 12)]
121   else:
122     df = df.loc[ (df['local time'] < 6) & (df['local time'] >= 0)]
123   # network
124   df = df.assign(network = 'VERIFYcore')
125   # parameter
126   df = df.assign(parameter = spec.upper())
127   # obs
128   df.rename(columns={'value':'obs'}, inplace = True)
129   # filter out negative concentrations!
130   df = df.loc[df['obs'] >= 0.]
131   # obserror
132   # filter out negative uncertainties
133   df = df.loc[df['value_unc'] >=0. ]
134   if station in TNO_error_station:
135       print(station + ' is in TNO_error_station')
136       if (len(df) == (df['value_unc'] >= 0.).sum()) and \
137          (len(df) == (df['value_unc'] - df['obs'] <0.).sum()) and \
138          (len(df) == (df['value_unc'] < max_unc).sum()):
139              print('OK error')
140              if ~np.isnan(TNO_LBC_error[TNO_error_station.index(station)]):
141                  df['obserror'] = np.sqrt( \
142                        (TNO_LBC_error[TNO_error_station.index(station)])**2 + \
143                        (TNO_T_error[TNO_error_station.index(station)])**2 + \
144                        (TNO_repr_error[TNO_error_station.index(station)])**2 +
145                        df['value_unc']**2 \
146                        )
147              elif TNO_station_category[TNO_error_station.index(station)] == 'C':
148                  df['obserror'] = np.sqrt( \
149                        (np.nanmean(costal_seor_mean))**2 +
150                        df['value_unc']**2 \
151                        )
152              elif TNO_station_category[TNO_error_station.index(station)] == 'M':
153                  df['obserror'] = np.sqrt( \
154                        (np.nanmean(montain_seor_mean))**2 +
155                        df['value_unc']**2 \
156                        )          
157              elif TNO_station_category[TNO_error_station.index(station)] == 'O':
158                  df['obserror'] = np.sqrt( \
159                        (np.nanmean(other_seor_mean))**2 +
160                        df['value_unc']**2 \
161                        )
162       else:
163         print('not OK for error')
164         sys.exit()
165         
166                  
167   else:
168       print(station + ' is NOT in TNO_error_station')
169       sys.exit()
170   # alt (m a.s.l)
171   df.rename(columns={'altitude': 'alt'}, inplace = True)
172
173   # extend the output datastore
174   column2save = ['date', 'duration', 'station', 'network', 'parameter', 'lon', 'lat', 'obs', 'obserror', 'alt']
175   ds = ds.append(df.loc[:, column2save])
176
177# Dump the datastore
178dump_datastore(ds, "{}/monitor_VERIFYcore_{}.nc".format(dir_data, spec), dump_default=False, col2dump=ds.columns)
179
180# Checking the consistency of the datastore when reading
181ds2 = read_datastore("monitor.nc")
182
183

2. Automatically make one job per year

One job per year is run, based on the following configuration file yml.sed:

Show/Hide Code

  1#####################
  2# PYCIF config file #
  3#####################
  4
  5#####################################################################
  6#####################################################################
  7# PYCIF parameters
  8pycif_root: &pycif_root /home/users/ipison/cif/
  9workdir: &workdir /home/chimereicos/VERIFYoutputs/surface-inv3-_YYYY_
 10outdir: &outdir !join [*workdir, /outdir]
 11
 12# Verbose level
 13verbose: 1
 14
 15# Log file (to be saved in $wordkir)
 16#logfile: tltestlog
 17logfile: pyCIF_inv.logtest
 18
 19# Initial date
 20datei: _YYYY_-01-01 00:00:00
 21datef: _YYNN_-01-01 00:00:00
 22
 23#####################################################################
 24#####################################################################
 25# Running mode for PYCIF
 26mode:
 27  minimizer:
 28    maxiter: 20
 29    epsg: 0.1
 30    df1: 0.01
 31    plugin:
 32      name: M1QN3
 33      version: std
 34    simulator:
 35      plugin:
 36        name: gausscost
 37        version: std
 38      reload_from_previous: true
 39  plugin:
 40    name: 4dvar
 41    version: std
 42  save_out_netcdf: true
 43
 44#####################################################################
 45#####################################################################
 46# Observation operator
 47obsoperator:
 48  plugin:
 49    name: standard
 50    version: std
 51    type: obsoperator
 52  autorestart: True
 53#####################################################################
 54#####################################################################
 55controlvect:
 56  plugin:
 57    name: standard
 58    version: std
 59    type: controlvect
 60#  save_out_netcdf: True
 61  
 62#####################################################################
 63#####################################################################
 64# Transport model
 65model :
 66  plugin:
 67    name: CHIMERE
 68    version : std
 69    type: model
 70
 71  direxec: !join [*pycif_root, /model_sources/chimereGES/]
 72  nphour_ref: 6
 73  ichemstep: 2
 74  ideepconv: 0
 75  nivout: 17
 76  nlevemis: 1
 77
 78  nzdoms: 6
 79  nmdoms: 1
 80 
 81  force_clean_run: True
 82  useRAMonly: True 
 83  ignore_input_dates: True
 84
 85####################################################################
 86#####################################################################
 87obsvect:
 88  plugin:
 89    name: standard
 90    version: std
 91    type: obsvect
 92  dir_obsvect: !join [*outdir, /ref_obsvect]
 93  dump_type: nc
 94
 95#####################################################################
 96#####################################################################
 97platform:
 98  plugin:
 99    name: LSCE
100    version: obelix
101
102#####################################################################
103#####################################################################
104# Domain definition
105domain :
106  plugin:
107   name    : CHIMERE
108   version : std
109  repgrid : /home/chimereges/PYCIF_TEST_DATA/CHIMERE/domains
110  domid : EUROCOMex3
111  nlev: 17
112  p1: 997
113  pmax: 200
114
115#####################################################################
116#####################################################################
117# Chemical scheme
118chemistry:
119  plugin:
120    name: CHIMERE
121    version: gasJtab
122  schemeid: ges.espigrad.humid
123  dir_precomp : /home/users/ipison/CHIMERE/VERIFY/WP4_surface/
124
125#####################################################################
126#####################################################################
127datavect:
128  plugin:
129    name: standard
130    version: std
131  components:
132    meteo:
133      plugin:
134        name: CHIMERE
135        version: std      
136        type: meteo
137      dir: /home/satellites15/afortems/data_EUROCOM/%Y/
138      file: METEO.%Y%m%d%H.24.nc
139      file_freq: 1D
140
141    concs:
142      parameters:
143        CH4: 
144          dir: /home/chimereicos/VERIFYdataWP4/Obs2021/core/
145          file: monitor_VERIFYcore_ch4.nc
146          vertical_interpolation:
147            file_statlev: /home/users/ipison/CHIMERE/VERIFY/WP4_surface/stations_levels_CHIMERE17lev.txt
148 
149    flux:
150      parameters:
151        CH4:
152          plugin:
153            name: CHIMERE
154            version: AEMISSIONS
155            type: flux
156          dir: /home/chimereicos/VERIFYdataWP4/Fluxes_intercomp_EU/
157          file: AEMISSIONS.%Y%m0100.24.nc
158          file_freq: 1D
159          hresol : hpixels
160          vresol : vpixels
161          tresol: 24H
162          nlev : 1
163          err : 1.0 
164          hcorrelations:
165             landsea: True 
166             sigma_land: 200 
167             sigma_sea: 1000
168             filelsm: /home/users/ipison/CHIMERE/VERIFY/intercomp_EU/lsmEUROCOMex3.nc
169             dump_hcorr: True
170          tcorrelations:
171             sigma_t: 2500H
172             dump_tcorr: True
173    inicond:
174      parameters:
175        CH4: 
176          plugin:
177            name: CAMS
178            version: netcdf
179            type: field        
180          dir: /home/comdata2/Fields/CAMSv19r1/
181          comp_type: inicond
182          file_freq: 1MS
183          file:  cams73_v19r1_ch4_conc_surface_inst_%Y01.nc
184          hresol: hpixels
185          vresol: vpixels
186          nlev: 34
187          err: 0.1
188          hcorrelations:
189             sigma: 1000 
190    latcond:
191      parameters:
192        CH4: 
193          plugin:
194            name: CAMS
195            version: netcdf
196            type: field        
197          dir: /home/comdata2/Fields/CAMSv19r1/
198          file:  cams73_v19r1_ch4_conc_surface_inst_%Y%m.nc
199          comp_type: latcond
200          file_freq: 1MS
201          hresol: hpixels
202          vresol: vpixels
203          tresol : 2D
204          nlev: 34 
205          err: 0.1
206          hcorrelations: 
207             sigma: 1000 
208          tcorrelations: 
209             sigma_t: 120H
210
211    topcond:
212      parameters:
213        CH4: 
214          plugin:
215            name: CAMS
216            version: netcdf
217            type: field     
218          dir: /home/comdata2/Fields/CAMSv19r1/
219          file: cams73_v19r1_ch4_conc_surface_inst_%Y%m.nc
220          comp_type: topcond
221          file_freq: 1MS
222          hresol: global
223          tresol : 2D
224          err: 0.1
225          tcorrelations:
226            sigma_t: 120H
227        

To fill-in a .sed file, you may use cat to replace the variables, as illustrated in the script for a job in 2015:

Show/Hide Code

 1#!/bin/sh -login
 2
 3echo "Machine on which the job is running:"
 4hostname
 5
 6# User' choices:
 7# Give year to run
 8year=2015
 9# Give directory for the .yml.sed file
10dirsed=$HOME/VERIFY/
11
12# next year for end of simulation
13ynext=$[ year + 1 ]
14
15# Path of the yml.sed file to use
16fyml=${dirsed}/config_chimere_inv_1Y
17
18# Fill-in the yaml.sed into a .yml
19cat << EOD > ${fyml}${year}.yml.sed-commands
20s,_YYYY_,${year},
21s,_YYNN_,${ynext},
22s,_INI_,INI_CONCS.${year}0101_${year}0101.00_EUROCOM.nc,
23EOD
24sed -f ${fyml}${year}.yml.sed-commands ${fyml}.yaml.sed > ${fyml}${year}.yml
25
26# Run the CIF
27time python -m pycif ${fyml}${year}.yml

3. Post-processing

Post-processing of the results include:

a. Maps of fluxes and increments

Show/Hide Code

  1#####################################################
  2# This code is provided by: I. Pison
  3# contact: isabelle.pison@lsce.ipsl.fr
  4# date: June 2022
  5# project: H2020 VERIFY
  6# relation to CIF: uses plugins, not embedded in.
  7# It is an example, to be used as a basis
  8# for developing other work.
  9# No guarantee as to its compatibility or results
 10# is attached since it is not part of the CIF
 11# Please don't forget acknowledgements if you use it.
 12#####################################################
 13
 14
 15import numpy as np
 16import pandas as pd
 17from pycif.utils.netcdf import readnc
 18from pycif.utils.datastores import dump
 19import matplotlib.pyplot as plt
 20import matplotlib.colors as colors
 21from matplotlib import ticker
 22import datetime
 23import xarray as xr
 24import cartopy.crs as crs
 25from mpl_toolkits.axes_grid1.inset_locator import InsetPosition
 26import cartopy
 27from cartopy.feature import ShapelyFeature
 28from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
 29
 30dir_results = '/home/chimereicos/VERIFYoutputs/surface-inv2-' 
 31dir_out = '/controlvect/flux/'
 32
 33year_beg = 2005
 34year_end = 2017
 35Na = 6.022e23
 36fs = 15
 37list_pr = []
 38list_po =[]
 39list_diff= []
 40
 41for yy in range(year_beg, year_end + 1, 1):
 42  print(yy)
 43  fcv = '{}{}{}/controlvect_flux_CH4.nc'.format(dir_results, yy, dir_out)
 44  flpr = readnc(fcv,['xb_phys'])
 45  flpo = readnc(fcv,['x_phys'])
 46  list_pr.append(flpr.mean(axis=0)[0,:,:]/ Na * 16.e6) # molec -> microgCH4
 47  list_po.append(flpo.mean(axis=0)[0,:,:]/ Na * 16.e6) # molec -> microgCH4
 48  diff = (flpo - flpr)/flpr * 100.
 49  list_diff.append(diff.mean(axis=0)[0,:,:])
 50
 51clon = readnc(fcv,['longitudes_corner'])
 52clat = readnc(fcv,['latitudes_corner'])
 53
 54############
 55# To plot: #
 56############
 57rivers = cartopy.feature.NaturalEarthFeature(
 58    category='physical', name='rivers_lake_centerlines',
 59    scale='10m', facecolor='none', edgecolor='blue')
 60borders = cartopy.feature.NaturalEarthFeature(
 61    category='cultural', name='admin_0_boundary_lines_land',
 62    scale='10m', facecolor='none', edgecolor='black')
 63
 64#############
 65# plot maps #
 66#############
 67# color scale with Ncolor colors from cmap base colormap for fluxes
 68Ncolor = 15
 69cmap = plt.cm.jet
 70cmaplist = cmap(np.linspace(0, 1, Ncolor))
 71cmap_plot = cmap.from_list('custom', cmaplist, Ncolor)
 72
 73# same for increments
 74Ncolor2 = 11
 75cmap2 = plt.cm.bwr
 76cmaplist2 = cmap2(np.linspace(0, 1, Ncolor2))
 77cmap2 = cmap2.from_list('custom', cmaplist2, Ncolor2)
 78
 79lognorm = colors.LogNorm()
 80
 81##plot a given year
 82yy = 2010
 83nby = yy - year_beg
 84fig = plt.figure(figsize=(21, 10))
 85gs = fig.add_gridspec(nrows = 2, ncols = 4, width_ratios = [2,2,2,0.1], 
 86                 left=0., right=0.97, bottom=0, top=1,
 87                  height_ratios = [1, 1 ] ,hspace = 0.01, wspace = 0.25)
 88subprior = fig.add_subplot(gs[0,1], projection=crs.PlateCarree())
 89subprior.pcolor(clon, clat, list_pr[nby],
 90                         norm=lognorm,
 91                         cmap = cmap,
 92                         transform=crs.PlateCarree())
 93subprior.coastlines(resolution='50m')
 94subprior.add_feature(borders,linewidth=0.5,linestyle='--')
 95subprior.set_title("Prior", fontsize = fs)
 96
 97subpost = fig.add_subplot(gs[0,2], projection=crs.PlateCarree())
 98im = subpost.pcolor(clon, clat, list_po[nby],
 99                         norm=lognorm,
100                         cmap = cmap,
101                         transform=crs.PlateCarree())
102subpost.coastlines(resolution='50m')
103subpost.add_feature(borders,linewidth=0.5,linestyle='--')
104subpost.set_title("Posterior", fontsize = f)
105
106echelle = fig.add_subplot(gs[0,3])
107ip = InsetPosition(echelle, [0,0,1,1]) 
108echelle.set_axes_locator(ip)
109p00 = echelle.get_position()
110p01 = echelle.get_position()
111p02 = subpost.get_position()
112p00_new = [p00.x0, p02.y0, p00.width, p02.height]
113echelle.set_position(p00_new)
114formatter = ticker.LogFormatter(10, labelOnlyBase=False) 
115cbar = plt.colorbar(im, format=formatter, cax=echelle)
116cbar.ax.set_title('molec.cm$^{-2}$.s$^{-1}$', fontsize = fs) 
117
118subd = fig.add_subplot(gs[1,2], projection=crs.PlateCarree())
119im2 = subd.pcolor(clon, clat, list_diff[nby],
120                           vmin=-100, vmax=100,
121                           cmap = cmap2,
122                           transform=crs.PlateCarree())
123subd.coastlines(resolution='50m')
124subd.add_feature(borders,linewidth=0.5,linestyle='--')
125subd.set_title("Increments: posterior - prior (%)", fontsize = fs)
126echelle2 = fig.add_subplot(gs[1,3])
127ip = InsetPosition(echelle2, [0,0,1,1]) 
128echelle2.set_axes_locator(ip)
129p00 = echelle2.get_position()
130p01 = echelle2.get_position()
131p02 = subd.get_position()
132p00_new = [p00.x0, p02.y0, p00.width, p02.height]
133echelle2.set_position(p00_new)
134cbar2 = plt.colorbar(im2, cax=echelle2, ax = subd)
135cbar2.ax.set_title('%', fontsize = fs) 
136
137plt.savefig('flux{}.png'.format(yy))
138
139
140

b. Time series of mixing ratios and maps of statistical indicators at the stations

Time series of mixing ratios at the stations make it easy to compare the observation, the prior and the analysis (obtained with the posterior emissions).

The maps of statistical indicators at the stations show the ratios of the posterior to the prior values of the bias, RMS, correlation for example.

Show/Hide Code

  1#####################################################
  2# This code is provided by: I. Pison
  3# contact: isabelle.pison@lsce.ipsl.fr
  4# date: June 2022
  5# project: H2020 VERIFY
  6# relation to CIF: uses plugins, not embedded in.
  7# It is an example, to be used as a basis
  8# for developing other work.
  9# No guarantee as to its compatibility or results
 10# is attached since it is not part of the CIF
 11# Please don't forget acknowledgements if you use it.
 12#####################################################
 13
 14import numpy as np
 15import pandas as pd
 16from pycif.utils.netcdf import readnc
 17from pycif.utils.datastores import dump
 18import matplotlib.pyplot as plt
 19import datetime
 20import cartopy.crs
 21from cartopy.feature import ShapelyFeature
 22from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
 23from mpl_toolkits.axes_grid1.inset_locator import InsetPosition
 24
 25dir_results = '/home/chimereicos/VERIFYoutputs/surface-inv2-' 
 26dir_out = '/obsoperator/fwd_'
 27dir_monit = '/obsvect/concs/CH4/'
 28
 29year_beg = 2005
 30year_end = 2017
 31
 32fs = 30 # fontsize
 33
 34# read monitors of all years
 35ds_all_years = pd.DataFrame()
 36for yy in range(year_beg, year_end + 1, 1):
 37  print(yy)
 38  fmonitpr = '{}{}{}-001{}/monitor.nc'.format(dir_results, yy, dir_out, dir_monit)
 39  fmonitpo = '{}{}{}-002{}/monitor.nc'.format(dir_results, yy, dir_out, dir_monit) 
 40  dspr = dump.read_datastore(fmonitpr)
 41  dspo = dump.read_datastore(fmonitpo)
 42  dsprpo = pd.concat( [dspr, dspo['maindata']['sim']], axis = 1)
 43  ds_all_years = pd.concat( [dsprpo, ds_all_years], axis = 0)
 44
 45list_stations = list(ds_all_years[('metadata', 'station')].unique())
 46list_lat = []
 47list_lon = []
 48lat_etiq = []
 49lon_etiq = []
 50for stat in list_stations:
 51  lat = ds_all_years[('metadata', 'lat')].loc[ds_all_years[('metadata', 'station')] == stat].iloc[0]
 52  lon = ds_all_years[('metadata', 'lon')].loc[ds_all_years[('metadata', 'station')] == stat].iloc[0]
 53  list_lat.append(lat)
 54  list_lon.append(lon)
 55  if stat =='ZSF':
 56    lat_etiq.append(lat - 0.3)
 57    lon_etiq.append(lon + 0.2) 
 58  elif stat =='KIT':
 59    lat_etiq.append(lat - 0.5)
 60    lon_etiq.append(lon + 0.2) 
 61  else:
 62    lat_etiq.append(lat + 0.2)
 63    lon_etiq.append(lon + 0.2)
 64    
 65dstat = pd.DataFrame(columns = ['station','Prior bias', 'Posterior bias', 'Prior rms', 'Posterior rms', 'Prior corr', 'Posterior corr'])
 66dstat['station'] = list_stations
 67for stat in list_stations:
 68  print(stat)
 69  ds = ds_all_years.loc[ ds_all_years[('metadata', 'station')] == stat ]
 70  ds['var'] = ds[('maindata','obserror')].pow(2)
 71  # compute bias, standard deviation, time correlation
 72  ds['prior difference'] = ds[('maindata', 'obs')] - ds[('maindata', 'sim')]
 73  ds['post difference'] = ds[('maindata', 'obs')] - ds['sim']
 74  dstat['Prior bias'].loc[dstat['station'] == stat ] = ds['prior difference'].mean()
 75  dstat['Posterior bias'].loc[dstat['station'] == stat ] = ds['post difference'].mean()
 76  dstat['Prior rms'].loc[dstat['station'] == stat ] =  np.sqrt(ds['prior difference'].pow(2).mean())
 77  dstat['Posterior rms'].loc[dstat['station'] == stat ] =  np.sqrt(ds['post difference'].pow(2).mean())
 78  dstat['Prior corr'].loc[dstat['station'] == stat ] =  ds[('maindata', 'obs')].corr(ds[('maindata', 'sim')])
 79  dstat['Posterior corr'].loc[dstat['station'] == stat] = ds[('maindata', 'obs')].corr(ds['sim'])
 80  # monthly means
 81  ds2 = ds.resample('MS', on = ('metadata', 'date'), closed = 'left')
 82  dsm = ds2.mean()
 83  counts = ds2.size()
 84  dsm['meanobserror'] =  dsm['var'].pow(0.5)/counts.pow(0.5)
 85# plot times series at each station
 86  plt.figure(figsize=(21,11))
 87  ax = plt.gca()
 88  ax.tick_params(axis = 'both', which = 'major', labelsize = fs/2)
 89  ax.set_xticks([datetime.datetime(i,1,1) for i in range(year_beg, year_end + 1)])
 90  plt.title('Methane mixing ratios at {}'.format(stat), fontsize = fs)
 91  plt.xlabel('Date', size = fs)
 92  plt.ylabel('[CH4] (ppb)', size = fs)
 93  plt.errorbar(dsm.index, dsm[('maindata','obs')], \
 94               dsm['meanobserror'], label = 'measurements', \
 95               linestyle = '-', marker = 'o', ms = 1)
 96  plt.plot(dsm.index, dsm[('maindata','sim')], \
 97               label = 'prior', \
 98               linestyle = '-', marker = 'o', ms = 1)
 99  plt.plot(dsm.index, dsm['sim'], \
100               label = 'analysis', \
101               linestyle = '-', marker = 'o', ms = 1)
102  plt.xlim(datetime.datetime(2005,1,1,0),datetime.datetime(2018,1,1,0))  
103  plt.legend(fontsize = fs/2)
104  plt.savefig('ts_{}.png'.format(stat))
105  plt.close()  
106
107# maps for statistics
108Ncolor = 10
109cmap = plt.cm.bwr
110cmaplist = cmap(np.linspace(0, 1, Ncolor))
111cmap_plot = cmap.from_list('custom', cmaplist, Ncolor)
112cmin, cmax = 0, 2
113rivers = cartopy.feature.NaturalEarthFeature(
114    category='physical', name='rivers_lake_centerlines',
115    scale='10m', facecolor='none', edgecolor='blue')
116borders = cartopy.feature.NaturalEarthFeature(
117    category='cultural', name='admin_0_boundary_lines_land',
118    scale='10m', facecolor='none', edgecolor='black')
119latmin=33.
120latmax=73.
121longmin=-15.
122longmax=35.
123fig = plt.figure(figsize=(21, 10))
124gs = fig.add_gridspec(nrows = 2, ncols = 3, height_ratios = [1, 0.02 ] )
125s0 = fig.add_subplot(gs[0, 0], projection=cartopy.crs.PlateCarree())
126s0.scatter(list_lon, list_lat, c=np.abs(dstat['Posterior bias']/dstat['Prior bias']), 
127                    s=20, cmap = cmap, marker = 's', vmin = 0, vmax = 2, edgecolors = 'k',
128                    linewidths = 0.4)
129s0.set_title('Ratio of posterior to prior mean difference\n between simulation and measurements')
130s0.set_extent([longmin, longmax, latmin, latmax])
131s0.coastlines(linewidth=0.5, resolution='50m')
132s0.add_feature(borders, linewidth=0.5, linestyle='--')
133
134s1 = fig.add_subplot(gs[0, 1], projection=cartopy.crs.PlateCarree())
135im = s1.scatter(list_lon, list_lat, c=dstat['Posterior rms']/dstat['Prior rms'], 
136                    s=20, cmap = cmap, marker = 's',vmin = 0, vmax = 2, edgecolors = 'k',
137                    linewidths = 0.4)
138s1.set_title('Ratio of posterior to prior quadratic mean\n of the differences\n between simulation and measurements')
139s1.set_extent([longmin, longmax, latmin, latmax])
140s1.coastlines(linewidth=0.5, resolution='50m')
141s1.add_feature(borders, linewidth=0.5, linestyle='--')
142
143s2 = fig.add_subplot(gs[0, 2], projection=cartopy.crs.PlateCarree())
144s2.scatter(list_lon, list_lat, c=np.abs(dstat['Posterior corr'])/np.abs(dstat['Prior corr']), 
145                    s=20, cmap = cmap, marker = 's', vmin = 0, vmax = 2, edgecolors = 'k',
146                    linewidths = 0.4)
147s2.set_title('Ratio of posterior to prior absolute values\n  of time correlation\n between simulation and measurements')
148s2.set_extent([longmin, longmax, latmin, latmax])
149s2.coastlines(linewidth=0.5, resolution='50m')
150s2.add_feature(borders, linewidth=0.5, linestyle='--')
151
152cb = fig.add_subplot(gs[1,1])
153cbar = plt.colorbar(im, cax = cb, ax = s0 , orientation = 'horizontal')
154cbar.ax.tick_params(labelsize=18)
155plt.savefig('map_statistics.png')
156
157

c. Yearly emission budgets for various regions

Examples here of regions are EU27+UK, EU27 countries, groups of countries:

Show/Hide Code

  1#####################################################
  2# This code is provided by: I. Pison
  3# contact: isabelle.pison@lsce.ipsl.fr
  4# date: June 2022
  5# project: H2020 VERIFY
  6# relation to CIF: uses plugins, not embedded in.
  7# It is an example, to be used as a basis
  8# for developing other work.
  9# No guarantee as to its compatibility or results
 10# is attached since it is not part of the CIF
 11# Please don't forget acknowledgements if you use it.
 12#####################################################
 13
 14import numpy as np
 15import pandas as pd
 16from pycif.utils.netcdf import readnc
 17from pycif.utils.datastores import dump
 18import matplotlib.pyplot as plt
 19import matplotlib.colors as colors
 20import datetime
 21import xarray as xr
 22import cartopy.crs as crs
 23import netCDF4 as nc
 24from mpl_toolkits.axes_grid1.inset_locator import InsetPosition
 25import calendar
 26
 27# read masks for various regions
 28fmasks = '/home/chimereicos/VERIFYoutputs/masks_for_EU27+UK.nc'
 29fm = nc.Dataset(fmasks)
 30nom_reg = 'EU_27+UK'
 31reg = np.array(fm[nom_reg])
 32mask = (reg > 0 )
 33
 34dir_results = '/home/chimereicos/VERIFYoutputs/surface-inv2-' 
 35dir_out = '/controlvect/flux/'
 36
 37year_beg = 2005
 38year_end = 2017
 39dy = 0.5
 40dx = 0.5
 41rearth=637103000. 
 42Na = 6.022e23
 43fcv = '{}{}{}/controlvect_flux_CH4.nc'.format(dir_results, year_beg, dir_out)
 44zlon = readnc(fcv,['longitudes'])[0,:]
 45zlat = readnc(fcv,['latitudes'])[:,0]
 46nlat = len(zlat)
 47nlon = len(zlon)
 48area = np.zeros((nlat,nlon))
 49for i in range(nlat):
 50  for j in range(nlon):
 51     lat1 = zlat[i]
 52     lat2 =  zlat[i] + dy
 53     # formula with lat between -90:90
 54     area[i,j] = rearth * rearth * dx * np.pi/180. * \
 55                 (np.cos(lat1 * np.pi/180. + np.pi/2.) - \
 56                  np.cos(lat2 * np.pi/180. + np.pi/2.))
 57                  
 58yrflregpr = []
 59yrflregpo = []
 60summ = np.zeros((2,12))
 61# read controlvect
 62for yy in range(year_beg, year_end + 1, 1):
 63  print(yy)
 64  fcv = '{}{}{}/controlvect_flux_CH4.nc'.format(dir_results, yy, dir_out)
 65  ds = xr.open_dataset(fcv)
 66  flpr = readnc(fcv,['xb_phys'])
 67  flpo = readnc(fcv,['x_phys'])
 68  flpran = np.sum(np.sum(flpr,axis=1),axis=0) * area * 3600. / Na * 16.e-3
 69  flpoan = np.sum(np.sum(flpo,axis=1),axis=0) * area * 3600. / Na * 16.e-3
 70  flreg = np.sum(flpran * mask) * 1e-3 * 1e-3 # kgCH4.yr -> t/an -> kt/an
 71  yrflregpr.append(flreg)
 72  flreg = np.sum(flpoan * mask) * 1e-3 * 1e-3 # kgCH4.yr -> t/an -> kt/an
 73  yrflregpo.append(flreg)
 74  beg = 0
 75  for m in range(12):
 76    mm = m + 1
 77    nbd = calendar.mdays[mm] + ( mm ==2 and calendar.isleap(yy))
 78    flprmm = np.sum(np.sum(flpr,axis=1)[beg:beg + nbd * 24],axis=0) * area * 3600. / Na * 16.e-3
 79    flpomm = np.sum(np.sum(flpo,axis=1)[beg:beg + nbd * 24],axis=0) * area * 3600. / Na * 16.e-3
 80    beg = beg + nbd * 24
 81    summ[0, m] = summ[0, m] + np.sum(flprmm * mask) * 1e-3 * 1e-3
 82    summ[1, m] = summ[1, m] + np.sum(flpomm * mask) * 1e-3 * 1e-3
 83
 84####################
 85# plot time series #
 86####################
 87fs = 25
 88plt.figure(figsize=(21,11))
 89plt.title('Methane emissions in '+nom_reg, fontsize=fs)
 90plt.xlabel('Year', fontsize=fs)
 91plt.ylabel('ktCH$_4$', fontsize=fs)
 92list_ticks = [ i  for i in range(year_end - year_beg + 1)]
 93list_names = [ year_beg + i for i in range(year_end - year_beg + 1)]
 94plt.xticks(list_ticks, list_names, fontsize=15)
 95plt.tick_params(axis='y', labelsize=30)
 96plt.yticks(fontsize=fs)
 97plt.bar(list_ticks, yrflregpr, label = 'prior', width=-0.4, align='edge')
 98plt.bar(list_ticks, yrflregpo, label = 'posterior', width=0.4, align='edge')
 99plt.legend(fontsize=fs)
100plt.savefig('ts_fluxes_{}_yearly.png'.format(nom_reg))
101plt.close()
102
103plt.figure(figsize=(21,11))
104plt.title('Methane emissions: average seasonal cycle over '+str(year_beg)+'-'+str(year_end)+' in '+nom_reg, fontsize=fs)
105plt.xlabel('Month', fontsize=fs)
106plt.ylabel('ktCH$_4$', fontsize=fs)
107ax = plt.gca()
108ax.tick_params(axis = 'both', which = 'major', labelsize = fs/2)
109plt.xticks([i+1 for i in range(12)] , [str(i+1) for i in range(12) ])
110nbyears = year_end - year_beg + 1
111plt.plot([i+1 for i in range(12)], summ[0,:]/nbyears, label = 'prior', \
112               linestyle = '--', marker = 'o', ms = 8)
113plt.plot([i+1 for i in range(12)], summ[1,:]/nbyears, label = 'posterior', \
114              linestyle = '-', marker = 'o', ms = 8)
115plt.legend(fontsize=fs-5)
116plt.savefig('ts_seasonal_{}_mass.png'.format(nom_reg))
117

d. Time series of emissions for various regions

Examples here of regions are EU27+UK, EU27 countries, groups of countries:

Show/Hide Code

 1#####################################################
 2# This code is provided by: I. Pison
 3# contact: isabelle.pison@lsce.ipsl.fr
 4# date: June 2022
 5# project: H2020 VERIFY
 6# relation to CIF: uses plugins, not embedded in.
 7# It is an example, to be used as a basis
 8# for developing other work.
 9# No guarantee as to its compatibility or results
10# is attached since it is not part of the CIF
11# Please don't forget acknowledgements if you use it.
12#####################################################
13
14import numpy as np
15import pandas as pd
16from pycif.utils.netcdf import readnc
17from pycif.utils.datastores import dump
18import matplotlib.pyplot as plt
19import matplotlib.colors as colors
20import datetime
21import xarray as xr
22import cartopy.crs as crs
23import netCDF4 as nc
24from mpl_toolkits.axes_grid1.inset_locator import InsetPosition
25
26# read masks for various regions
27fmasks = '/home/chimereicos/VERIFYoutputs/masks_for_EU27.nc'
28fm = nc.Dataset(fmasks)
29reg_name = 'EU_27'
30reg = np.array(fm[reg_name])
31
32dir_results = '/home/chimereicos/VERIFYoutputs/surface-inv-' 
33dir_out = '/controlvect/flux/'
34
35year_beg = 2005
36year_end = 2017
37
38# read controlvect
39list_dates = []
40tsprEU = []
41tspoEU = []
42for yy in range(year_beg, year_end + 1, 1):
43  print(yy)
44  fcv = '{}{}{}/controlvect_flux_CH4.nc'.format(dir_results, yy, dir_out)
45  ds = xr.open_dataset(fcv)
46  dates = ds['time_phys'].to_dataframe()
47  list_dates.append(dates['time_phys'].loc[(dates.index.day == 1) & (dates.index.hour == 1)].tolist())
48  flpr = readnc(fcv,['xb_phys'])
49  flpo = readnc(fcv,['x_phys'])
50  # over the selected region
51  flprEU = flpr[:-1,:,:,:] * reg
52  tsprEU.append(np.mean(flprEU))
53  flpoEU = flpo[:-1,:,:,:] * reg
54  tspoEU.append(np.mean(flpoEU))
55   
56####################
57# plot time series #
58####################
59plt.figure(figsize=(21,11))
60plt.title('Methane emissions: average in {}'.format(reg_name), fontsize=15)
61plt.xlabel('Date', fontsize=15)
62plt.ylabel('molec.cm$^{-2}$.s$^{-1}$', fontsize=15)
63list_ticks = []
64for ld in list_dates:
65    list_ticks.append(ld[0])
66list_names = []
67for d in list_ticks:
68 list_names.append(d.strftime('%Y'))
69plt.xticks(list_ticks, list_names, fontsize=15)
70plt.tick_params(axis='y', labelsize=30)
71plt.yticks(fontsize=15)
72plt.bar(list_ticks, tsprEU, label = 'prior', width=-100, align='edge')
73plt.bar(list_tciks, tspoEU, label = 'posterior', width=100, align='edge')
74plt.legend(fontsize=15)
75plt.savefig('ts_fluxes_{}.png'.format(reg_name))
76plt.close()
77