diff --git a/PE_CBL.py b/PE_CBL.py index 72a584b0d782df10a44cd130c67f37e186b84dfa..8f89ef585e53134641a3339523fd67cf4b8a6b5f 100644 --- a/PE_CBL.py +++ b/PE_CBL.py @@ -4,446 +4,135 @@ import numpy as np from matplotlib import pyplot as p import pickle import os -from copy import deepcopy +import json # Own modules from models import CBL -from ENDA import experiment, transform_log, transform_none, transform_pit +from ENDA import experiment from graphics import * +def load_or_run(settings): + + try: + exp = pickle.load(open(settings['path']+'/'+settings['filename'], "rb")) + print('loaded experiment %s'%(settings['path']+'/'+settings['filename'])) + except: + print('loading %s failed, running it'%(settings['path']+'/'+settings['filename'])) + exp = experiment(settings) + pickle.dump(exp, open(settings['path']+'/'+settings['filename'], 'wb')) + + return exp + if __name__ == '__main__': # Global settings - nobs = 16 - nens = 200 + nens = 20 sigma_b_init = 0.1 # increased by a factor 10 in experiment B - sigma_o_gn = 0.0 # set equal to sigma_o_as in experiments C and F + sigma_o_gn = 0.1 # set equal to sigma_o_as in experiments C and F sigma_o_as = 0.1 # increased by a factor 10 in experiments C and F # Define seed for random number generation, to ensure reproducibility rnseed = 181612 - # Default CBL model settings - default_cbl_settings ={ - # Physical parameters - 'g' : 9.806, - 'f' : 1e-4, - 'kvonk' : 0.4, - 'z0' : 0.1, - 'ug' : 0, - 'vg' : 10, - 'theta_0' : 290, - 'gamma' : 3e-3, - 'Hmax' : 0.1, - 'H0_perturbation_ampl_init' : 0.0, - 'H0_perturbation_ampl_time' : 0.0, - 'exct' : 0.3, - 'pfac' : 1.5, - 'Kmin' : 0.1, - 'Kmax' : 200, - # Model formulation - 'is_bwind' : False, - 'is_cgrad' : True, - 'is_cycle' : False, - # Domain parameters (Kmax determines dt) - 'dz' : 50, - 'ztop' : 4000, - # Ensemble generation parameters (only effective for ensemble runs) - # Part 1: perturbation of initial state - # perturbation_type can be "smooth", "random", "uniform" - # the adjective refers to vertical variability - 'rnseed' : rnseed, - 'perturb_ensemble_state' : True, - 'perturbations_type' : "smooth", - 'perturbations_theta_amplitude' : sigma_b_init, - 'perturbations_uv_amplitude' : 0.1, - 'perturbations_smooth_number' : 11, - 'perturbations_symmetric' : True, - 'simulate_error_growth' : False, - 'error_growth_perturbations_amplitude' : 0.0, - # Part 2: perturbation of parameters - 'perturb_ensemble_parameters' : True, - 'parameter_number' : 1, - 'parameter_transform' : [ transform_pit ], - 'parameter_ensemble_min' : np.array([0.5]), - 'parameter_ensemble_max' : np.array([4.5]), - 'parameter_true' : np.array([1.5]), - # In case of parameter estimation - 'do_parameter_estimation' : True, - 'parameter_inflation_rtps_alpha' : np.array([0.8]), - 'return_covariances_increments_and_innovations' : True - } - integration_dt = 0.25*default_cbl_settings["dz"]**2/default_cbl_settings["Kmax"] - - # Default assimilation settings - default_da_settings = {'cbl_settings' : default_cbl_settings, - 'tspinup' : 3600, - 'trun' : 21600, - 'assimilation_interval' : 300, - 'randomize_obs': True, - 'obs_coordinates' : np.linspace(0,2000,nobs), - 'obs_kinds' : np.array(['theta']*nobs), - 'obs_error_sdev_generate' : np.ones(nobs)*0, - 'obs_error_sdev_assimilate' : np.ones(nobs)*sigma_o_as, - 'localization_cutoff' : 400, - 'nens' : nens, - 'FILTER' : 'EAKF', - 'inflation_rtps_alpha' : 0.2, - 'rnseed' : rnseed, - 'path' : "./runs/seed=%s_enssize=%s_EAKF_6hrs/"%(rnseed,nens) - } - # Safety check on time steps - assert np.mod(default_da_settings['assimilation_interval'],integration_dt)==0,\ - "Assimilation interval must be an integer multiplier of model dt" + #integration_dt = 0.25*default_cbl_settings["dz"]**2/default_cbl_settings["Kmax"] + #assert np.mod(default_da_settings['assimilation_interval'],integration_dt)==0,\ + # "Assimilation interval must be an integer multiplier of model dt" # Whether or not to run experiments without parameter estimation - # Applies only to sets B&C; no-PE run is always computed for experiments A&D + # Applies only to sets B-C-D; no-PE run is always computed for experiments A noPE_runs = False - # Check if output path exists, if not, create it. - if not os.path.exists(default_da_settings['path']): - os.makedirs(default_da_settings['path']) + # Check if working directory exists, if not, create it. + workdir = "./runs/seed=181612_enssize=20_EAKF_6hrs/" + if not os.path.exists(workdir): + os.makedirs(workdir) - ############################################################################ - # Experiment A (control) - # Create a copy of the default settings - cbl_settings_A = dict(default_cbl_settings) - da_settings_A = dict(default_da_settings) + ################################################################################## + # Load the experiments or run them - # Run it - try: - exp_A = pickle.load(open(default_da_settings['path']+"exp_A.pickle", "rb")) - except: - exp_A = experiment(da_settings_A) - setattr(exp_A,'label','A') - pickle.dump(exp_A, open(default_da_settings['path']+'exp_A.pickle', 'wb')) - - # Store the nature run, because it is used also in other experiments/figures - exp_A.nr.has_parameters_in_state = True - nature_run = exp_A.nr - truths = exp_A.truths - observations = exp_A.observations - - # Corresponding experiment without parameter estimation - cbl_settings_A_noPE = dict(cbl_settings_A) - da_settings_A_noPE = dict(da_settings_A) - da_settings_A_noPE['nr'] = deepcopy(nature_run) - da_settings_A_noPE['truths'] = truths - da_settings_A_noPE['observations'] = observations - da_settings_A_noPE['nr'].do_parameter_estimation = False - cbl_settings_A_noPE['do_parameter_estimation'] = False - cbl_settings_A_noPE['initial_perturbed_parameters'] = exp_A.da.initial_perturbed_parameters - da_settings_A_noPE['cbl_settings'] = cbl_settings_A_noPE - - # Run it - try: - exp_A_noPE = pickle.load(open(default_da_settings['path']+'exp_A_noPE.pickle', "rb")) - except: - exp_A_noPE = experiment(da_settings_A_noPE) - pickle.dump(exp_A_noPE, open(default_da_settings['path']+'exp_A_noPE.pickle', 'wb')) + with open('./config_files/da_exp_A1.json', 'r') as fp: + da_settings_A1 = json.load(fp) + exp_A1 = load_or_run(da_settings_A1) - ######################################################################## - # Experiment B1 - # Create a copy of the default settings - # Then re-use the available nature run and derived information - cbl_settings_B1 = dict(default_cbl_settings) - da_settings_B1 = dict(default_da_settings) - da_settings_B1['nr'] = deepcopy(nature_run) - da_settings_B1['truths'] = truths - da_settings_B1['observations'] = observations - - # Change settings as necessary - cbl_settings_B1['perturbations_theta_amplitude'] = sigma_b_init*10 - cbl_settings_B1['initial_perturbed_parameters'] = exp_A.da.initial_perturbed_parameters - da_settings_B1['cbl_settings'] = cbl_settings_B1 - da_settings_B1['obs_error_sdev_assimilate'] = np.ones(nobs)*sigma_o_as - - # Run and save to disk - try: - exp_B1 = pickle.load(open(default_da_settings['path']+'exp_B1.pickle', "rb")) - except: - exp_B1 = experiment(da_settings_B1) - setattr(exp_B1,'label','B1') - pickle.dump(exp_B1, open(default_da_settings['path']+'exp_B1.pickle', 'wb')) + with open('./config_files/da_exp_A2.json', 'r') as fp: + da_settings_A2 = json.load(fp) + exp_A2 = load_or_run(da_settings_A2) - if noPE_runs: - # Corresponding experiment without parameter estimation - cbl_settings_B1_noPE = dict(cbl_settings_B1) - da_settings_B1_noPE = dict(da_settings_B1) - cbl_settings_B1_noPE['do_parameter_estimation'] = False - da_settings_B1_noPE['nr'] = deepcopy(nature_run) - da_settings_B1_noPE['nr'].do_parameter_estimation = False - da_settings_B1_noPE['cbl_settings'] = cbl_settings_B1_noPE - - # Run and save to disk - try: - exp_B1_noPE = pickle.load(open(default_da_settings['path']+'exp_B1_noPE.pickle', "rb")) - except: - exp_B1_noPE = experiment(da_settings_B1_noPE) - pickle.dump(exp_B1_noPE, open(default_da_settings['path']+'exp_B1_noPE.pickle', 'wb')) + with open('./config_files/da_exp_B1.json', 'r') as fp: + da_settings_B1 = json.load(fp) + exp_B1 = load_or_run(da_settings_B1) - ######################################################################## - # Experiment B2 - # Create a copy of the default settings - # Then re-use the available nature run and derived information - cbl_settings_B2 = dict(default_cbl_settings) - da_settings_B2 = dict(default_da_settings) - da_settings_B2['nr'] = deepcopy(nature_run) - da_settings_B2['truths'] = truths - da_settings_B2['observations'] = observations - - # Change settings as necessary - cbl_settings_B2['perturbations_theta_amplitude'] = sigma_b_init - cbl_settings_B2['initial_perturbed_parameters'] = exp_A.da.initial_perturbed_parameters - da_settings_B2['cbl_settings'] = cbl_settings_B2 - da_settings_B2['obs_error_sdev_assimilate'] = np.ones(nobs)*sigma_o_as*10 - - # Run and save to disk - try: - exp_B2 = pickle.load(open(default_da_settings['path']+'exp_B2.pickle', "rb")) - except: - exp_B2 = experiment(da_settings_B2) - setattr(exp_B2,'label','B2') - pickle.dump(exp_B2, open(default_da_settings['path']+'exp_B2.pickle', 'wb')) + with open('./config_files/da_exp_B2.json', 'r') as fp: + da_settings_B2 = json.load(fp) + exp_B2 = load_or_run(da_settings_B2) - if noPE_runs: - # Corresponding experiment without parameter estimation - cbl_settings_B2_noPE = dict(cbl_settings_B2) - da_settings_B2_noPE = dict(da_settings_B2) - cbl_settings_B2_noPE['do_parameter_estimation'] = False - da_settings_B2_noPE['nr'] = deepcopy(nature_run) - da_settings_B2_noPE['nr'].do_parameter_estimation = False - da_settings_B2_noPE['cbl_settings'] = cbl_settings_B2_noPE - - # Run and save to disk - try: - exp_B2_noPE = pickle.load(open(default_da_settings['path']+'exp_B2_noPE.pickle', "rb")) - except: - exp_B2_noPE = experiment(da_settings_B2_noPE) - pickle.dump(exp_B2_noPE, open(default_da_settings['path']+'exp_B2_noPE.pickle', 'wb')) + with open('./config_files/da_exp_B3.json', 'r') as fp: + da_settings_B3 = json.load(fp) + exp_B3 = load_or_run(da_settings_B3) - ######################################################################## - # Experiment B3 - # Create a copy of the default settings - # Then re-use the available nature run and derived information - cbl_settings_B3 = dict(default_cbl_settings) - da_settings_B3 = dict(default_da_settings) - da_settings_B3['nr'] = deepcopy(nature_run) - da_settings_B3['truths'] = truths - da_settings_B3['observations'] = observations - - # Change settings as necessary - cbl_settings_B3['perturbations_theta_amplitude'] = sigma_b_init*10 - cbl_settings_B3['initial_perturbed_parameters'] = exp_A.da.initial_perturbed_parameters - da_settings_B3['cbl_settings'] = cbl_settings_B3 - da_settings_B3['obs_error_sdev_assimilate'] = np.ones(nobs)*sigma_o_as*10 - - # Run and save to disk - try: - exp_B3 = pickle.load(open(default_da_settings['path']+'exp_B3.pickle', "rb")) - except: - exp_B3 = experiment(da_settings_B3) - setattr(exp_B3,'label','B3') - pickle.dump(exp_B3, open(default_da_settings['path']+'exp_B3.pickle', 'wb')) + with open('./config_files/da_exp_B4.json', 'r') as fp: + da_settings_B4 = json.load(fp) + exp_B4 = load_or_run(da_settings_B4) - if noPE_runs: - # Corresponding experiment without parameter estimation - cbl_settings_B3_noPE = dict(cbl_settings_B3) - da_settings_B3_noPE = dict(da_settings_B3) - cbl_settings_B3_noPE['do_parameter_estimation'] = False - da_settings_B3_noPE['nr'] = deepcopy(nature_run) - da_settings_B3_noPE['nr'].do_parameter_estimation = False - da_settings_B3_noPE['cbl_settings'] = cbl_settings_B3_noPE - - # Run and save to disk - try: - exp_B3_noPE = pickle.load(open(default_da_settings['path']+'exp_B3_noPE.pickle', "rb")) - except: - exp_B3_noPE = experiment(da_settings_B3_noPE) - pickle.dump(exp_B3_noPE, open(default_da_settings['path']+'exp_B3_noPE.pickle', 'wb')) + with open('./config_files/da_exp_C1.json', 'r') as fp: + da_settings_C1 = json.load(fp) + exp_C1 = load_or_run(da_settings_C1) - ######################################################################## - # Experiment B4 - # Create a copy of the default settings - cbl_settings_B4 = dict(default_cbl_settings) - da_settings_B4 = dict(default_da_settings) - - # Change settings as necessary - # Changes include generation of observations, so the existing nature run - # can't be reused. - cbl_settings_B4['initial_perturbed_parameters'] = exp_A.da.initial_perturbed_parameters - da_settings_B4['cbl_settings'] = cbl_settings_B4 - da_settings_B4['obs_error_sdev_generate'] = np.ones(nobs)*sigma_o_as*5 - da_settings_B4['obs_error_sdev_assimilate'] = np.ones(nobs)*sigma_o_as*10 - - # Run and save to disk - try: - exp_B4 = pickle.load(open(default_da_settings['path']+'exp_B4.pickle', "rb")) - except: - exp_B4 = experiment(da_settings_B4) - setattr(exp_B4,'label','B4') - pickle.dump(exp_B4, open(default_da_settings['path']+'exp_B4.pickle', 'wb')) + with open('./config_files/da_exp_C2.json', 'r') as fp: + da_settings_C2 = json.load(fp) + exp_C2 = load_or_run(da_settings_C2) - if noPE_runs: - # Corresponding experiment without parameter estimation - cbl_settings_B4_noPE = dict(cbl_settings_B4) - da_settings_B4_noPE = dict(da_settings_B4) - cbl_settings_B4_noPE['do_parameter_estimation'] = False - da_settings_B4_noPE['cbl_settings'] = cbl_settings_B4_noPE - - # Run and save to disk - try: - exp_B4_noPE = pickle.load(open(default_da_settings['path']+'exp_B4_noPE.pickle', "rb")) - except: - exp_B4_noPE = experiment(da_settings_B4_noPE) - pickle.dump(exp_B4_noPE, open(default_da_settings['path']+'exp_B4_noPE.pickle', 'wb')) + with open('./config_files/da_exp_D1.json', 'r') as fp: + da_settings_D1 = json.load(fp) + exp_D1 = load_or_run(da_settings_D1) - ######################################################################## - # Experiment C1 - # Create a copy of the default settings - cbl_settings_C1 = dict(default_cbl_settings) - da_settings_C1 = dict(default_da_settings) - - # Re-use the available nature run and derived information - da_settings_C1['nr'] = deepcopy(nature_run) - da_settings_C1['truths'] = truths - da_settings_C1['observations'] = observations - - # Change settings as necessary - cbl_settings_C1['initial_perturbed_parameters'] = exp_A.da.initial_perturbed_parameters - cbl_settings_C1['is_cgrad'] = False - da_settings_C1['cbl_settings'] = cbl_settings_C1 - - # Run and save to disk - try: - exp_C1 = pickle.load(open(default_da_settings['path']+'exp_C1.pickle', "rb")) - except: - exp_C1 = experiment(da_settings_C1) - setattr(exp_C1,'label','C1') - pickle.dump(exp_C1, open(default_da_settings['path']+'exp_C1.pickle', 'wb')) + with open('./config_files/da_exp_D2.json', 'r') as fp: + da_settings_D2 = json.load(fp) + exp_D2 = load_or_run(da_settings_D2) if noPE_runs: - # Corresponding experiment without parameter estimation - cbl_settings_C1_noPE = dict(cbl_settings_C1) - da_settings_C1_noPE = dict(da_settings_C1) - cbl_settings_C1_noPE['do_parameter_estimation'] = False - da_settings_C1_noPE['cbl_settings'] = cbl_settings_C1_noPE - - # Run and save to disk - try: - exp_C1_noPE = pickle.load(open(default_da_settings['path']+'exp_C1_noPE.pickle', "rb")) - except: - exp_C1_noPE = experiment(da_settings_C1_noPE) - pickle.dump(exp_C1_noPE, open(default_da_settings['path']+'exp_C1_noPE.pickle', 'wb')) + with open('./config_files/da_exp_B1_noPE.json', 'r') as fp: + da_settings_B1_noPE = json.load(fp) + exp_B1_noPE = load_or_run(da_settings_B1_noPE) ######################################################################## - # Experiment C2 - # Create a copy of the default settings - cbl_settings_C2 = dict(default_cbl_settings) - da_settings_C2 = dict(default_da_settings) - - # Re-use the available nature run and derived information - da_settings_C2['nr'] = deepcopy(nature_run) - da_settings_C2['truths'] = truths - da_settings_C2['observations'] = observations - - # Change settings as necessary - cbl_settings_C2['initial_perturbed_parameters'] = exp_A.da.initial_perturbed_parameters - cbl_settings_C2['Hmax'] = 0.15 - da_settings_C2['cbl_settings'] = cbl_settings_C2 - - # Run and save to disk - try: - exp_C2 = pickle.load(open(default_da_settings['path']+'exp_C2.pickle', "rb")) - except: - exp_C2 = experiment(da_settings_C2) - setattr(exp_C2,'label','C2') - pickle.dump(exp_C2, open(default_da_settings['path']+'exp_C2.pickle', 'wb')) - - if noPE_runs: - # Corresponding experiment without parameter estimation - cbl_settings_C2_noPE = dict(cbl_settings_C2) - da_settings_C2_noPE = dict(da_settings_C2) - cbl_settings_C2_noPE['do_parameter_estimation'] = False - da_settings_C2_noPE['cbl_settings'] = cbl_settings_C2_noPE - - # Run and save to disk - try: - exp_C2_noPE = pickle.load(open(default_da_settings['path']+'exp_C2_noPE.pickle', "rb")) - except: - exp_C2_noPE = experiment(da_settings_C2_noPE) - pickle.dump(exp_C2_noPE, open(default_da_settings['path']+'exp_C2_noPE.pickle', 'wb')) - - ######################################################################## - # Experiment D - # Create a copy of the default settings - cbl_settings_D = dict(default_cbl_settings) - da_settings_D = dict(default_da_settings) - - # Change settings as necessary - # Changes include generation of observations, so the existing nature run - # can't be reused. - cbl_settings_D['initial_perturbed_parameters'] = exp_A.da.initial_perturbed_parameters - cbl_settings_D['perturbations_theta_amplitude'] = sigma_b_init*10 - cbl_settings_D['Hmax'] = 0.15 - cbl_settings_D['is_cgrad'] = False - cbl_settings_D['simulate_error_growth'] = True - cbl_settings_D['error_growth_perturbations_amplitude'] = sigma_b_init*5 - da_settings_D['cbl_settings'] = cbl_settings_D - da_settings_D['obs_error_sdev_generate'] = np.ones(nobs)*sigma_o_as*5 - da_settings_D['obs_error_sdev_assimilate'] = np.ones(nobs)*sigma_o_as*10 - - # Run and save to disk - try: - exp_D = pickle.load(open(default_da_settings['path']+'exp_D.pickle', "rb")) - except: - exp_D = experiment(da_settings_D) - setattr(exp_D,'label','D') - pickle.dump(exp_D, open(default_da_settings['path']+'exp_D.pickle', 'wb')) - - # Experiment matching D, but without parameter estimation - if noPE_runs: - cbl_settings_D_noPE = dict(cbl_settings_D) - da_settings_D_noPE = dict(da_settings_D) - cbl_settings_D_noPE['do_parameter_estimation'] = False - da_settings_D_noPE['cbl_settings'] = cbl_settings_D_noPE - - try: - exp_D_noPE = pickle.load(open(default_da_settings['path']+'exp_D_noPE.pickle', "rb")) - except: - exp_D_noPE = experiment(da_settings_D_noPE) - pickle.dump(exp_A_noPE, open(default_da_settings['path']+'exp_D_noPE.pickle', 'wb')) - - ######################################################################## - - # Decide what figures to plot - fig101 = False - fig102 = False - fig103 = False - fig104 = False - fig105 = False - fig106 = False - fig107 = False - fig108 = False - fig109 = False + # Manuscript figures + fig01 = False # CHECKED OK # Sensitivity to p + fig02 = False # CHECKED OK # Successful PE experiment + fig03 = False # CHECKED OK # DA diagnostics + fig04 = False # CHECKED OK # Experiments with varying weights + fig05 = False # CHECKED OK # Experiments with systematic model errors + fig06 = False # CHECKED OK # Analysis increments in model error experiments + fig07 = False # CHECKED OK # Experiment with error growth + fig08 = False # CHECKED OK # Comparison successful vs unsuccessful PE + fig09 = False # Comparison EAKF vs LETKF - # Other possible, optional, plots - opt01 = False # assimilation of a single observation - opt02 = False # assimilation of profiles at two times - opt03 = False # assimilation of real observations - opt04 = True # assimilation of real observations, different spinup times + # Other figures + opt01 = False # CHECKED OK # assimilation of a single observation + opt02 = False # CHECKED OK # assimilation of profiles at two times + opt03 = False # CHECKED OK # NOT EXPECTED TO WORK # assimilation of real observations + opt04 = True # NOT EXPECTED TO WORK # assimilation of real observations, different spinup times + opt05 = False # CHECKED OK # Check impact of localization cutoff + opt06 = False # CHECKED OK # Check impact of RTPS variance inflation factor for parameters + opt07 = False # CHECKED OK # Check impact of RTPS variance inflation factor for state vector + opt08 = False # CHECKED OK # Check sensitivity to observation sorting + opt09 = False # CHECKED OK # Plot covariance matrix + opt10 = False # CHECKED OK # Consistency checks + opt11 = False # CHECKED OK # Consistency checks + + # A test to check if results are exactly reproducible + check_reproducibility = False - if fig101: + if fig01: # Create a copy of the default settings - cbl_settings = dict(default_cbl_settings) + # and update as necessary + with open('./config_files/default_cbl.json', 'r') as fp: + cbl_settings = json.load(fp) maxtime = 21600 cbl_settings['maxtime'] = maxtime - # Disable parameter estimation (not used here) - cbl_settings['do_parameter_estimation'] = False - # Panel a) deterministic run using default settings cbl_det = CBL(cbl_settings) cbl_det.initialize(1) @@ -466,17 +155,19 @@ if __name__ == '__main__': # Panel c) spread induced by p # Do a free ensemble run (ensemble size set expliclity) - cbl_settings_free = dict(default_cbl_settings) + # Set size to 200 members + with open('./config_files/default_cbl.json', 'r') as fp: + cbl_settings_free = json.load(fp) cbl_settings_free['maxtime'] = maxtime cbl_settings_free['perturb_ensemble_state'] = False cbl_free = CBL(cbl_settings_free) - cbl_free.initialize(nens) + cbl_free.initialize(200) cbl_free.run(output_full_history=True) # Make plots ncont = 13 - fig, [[ax4, ax2],[ax1, ax3]] = p.subplots(2,2,constrained_layout=True) - fig.set_size_inches(6,6) + fig, [[ax4, ax2],[ax1, ax3], [ax5, ax6] ] = p.subplots(3,2,constrained_layout=True) + fig.set_size_inches(6,9) c1 = ax1.pcolormesh(cbl_det.history['time']/3600,cbl_det.zt,cbl_det.history['theta'],vmin=290,vmax=296) ax1.set_ylim([0,zmax]) @@ -499,7 +190,7 @@ if __name__ == '__main__': ax2.legend(loc=4,frameon=False) ax2.set_title(r'b) $\overline{\theta}$ sensitivity to $p$') - ax3,c3 = plot_spread(cbl_free,ax=ax3) + ax3,c3 = plot_free_ensemble(cbl_free,show='spread',ax=ax3) ax3.set_ylabel(r'Height (m)') ax3.set_title(r'd) $\sigma_\theta$ (K)') ax3.set_xlabel('Time (h)') @@ -516,85 +207,101 @@ if __name__ == '__main__': ax4.set_xlim([0,0.5]) ax4.legend(loc=4,frameon=False) + ax5,c5 = plot_free_ensemble(cbl_free,show='cov',ax=ax5,cmap='RdBu_r') + ax5.set_ylabel(r'Height (m)') + ax5.set_title(r'e) cov$(p,\overline{\theta})$ (K)') + ax5.set_xlabel('Time (h)') + ax5.set_xticks(np.arange((maxtime+1)/3600)) + p.colorbar(c5,orientation='horizontal') + + ax6 = plot_linearity(cbl_free,ax=ax6) + ax6.set_ylabel(r'$\overline{\theta}$ (K)') + ax6.set_title(r'f) $p$ vs $\overline{\theta}$, $t=6$ h') + ax6.set_xlabel(r'$p$') + #p.setp(ax2.get_yticklabels(), visible=False) #p.setp(ax3.get_yticklabels(), visible=False) fig.savefig('fig01.png',format='png',dpi=300) p.close(fig) - if fig102: - - exp_A = pickle.load(open(default_da_settings['path']+"exp_A.pickle", "rb")) + if fig02: - fig, [[ax0, ax1], [ax2, ax3]] = p.subplots(2,2,constrained_layout=True) - fig.set_size_inches(6,6) - # - [ax0,ax1,ax2],c0,c1,c2 = plot_CBL_identifiability(exp_A,da_settings_A['obs_error_sdev_assimilate'][0],ax=[ax0,ax1,ax2]) - ax0.set_title(r'a) Exp. A$_1$, $\rho(p\prime\prime,y_b}$)') - ax0.set_xlabel('Time (h)') - ax0.set_ylabel('Height (m)') - ax1.set_title(r'b) Exp. A$_1$, $\delta\overline{y}\cdot(\sigma_{p\prime\prime}/\sigma_{y^b})$') - ax1.set_xlabel('Time (h)') - ax1.set_ylabel('Height (m)') - ax2.set_title(r'c) Exp. A$_1$, $\delta\overline{p}\prime\prime$') - ax2.set_xlabel('Time (h)') - ax2.set_ylabel('Height (m)') - ax3 = plot_CBL_PE(exp_A,None,ax=ax3) - ax3.set_title(r'd) Exp. A$_1$, evolution of $p$') - ax3.set_xlabel('Time (h)') - ax3.set_yticks([0,1,2,3,4,5]) - maxtime = exp_A.trun - 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('fig02.png',format='png',dpi=300) - p.close(fig) + exp_A1 = pickle.load(open(workdir+"exp_A1.pickle", "rb")) + plot_overview(exp_A1,"A$_1$",'fig02.png') - if fig103: + if fig03: - exp_A = pickle.load(open(default_da_settings['path']+"exp_A.pickle", "rb")) - exp_A_noPE = pickle.load(open(default_da_settings['path']+"exp_A_noPE.pickle", "rb")) + exp1 = pickle.load(open(workdir+"exp_A1.pickle", "rb")) + exp2 = pickle.load(open(workdir+"exp_A2.pickle", "rb")) - experiments_pe = [exp_A] - experiments_nope = [exp_A_noPE] - labels = ["Exp. A"] + fig, [[ax1, ax2], [ax3, ax4]] = p.subplots(2,2,constrained_layout=True) + fig.set_size_inches(6,4) + plot_diagnostics(exp1,show='errors',axes=[ax1,ax2,ax3,ax4],label='with PE',linecolor='#1f77b4') + plot_diagnostics(exp2,show='errors',axes=[ax1,ax2,ax3,ax4],label='without PE',linecolor='#9EC9E9') + 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)') + #ax1.set_xlim([0,0.2]) + #ax2.set_xlim([0,0.2]) + #ax3.set_xlim([0,0.02]) + #ax4.set_xlim([0,10]) + fig.savefig('fig03_with_errors.png',format='png',dpi=300) + p.close(fig) - plot_diagnostics(experiments_pe,experiments_nope,labels,'fig03.png') + fig, [[ax1, ax2], [ax3, ax4]] = p.subplots(2,2,constrained_layout=True) + fig.set_size_inches(6,4) + plot_diagnostics(exp1,show='deviations',axes=[ax1,ax2,ax3,ax4],label='with PE',linecolor=u'#1f77b4') + plot_diagnostics(exp2,show='deviations',axes=[ax1,ax2,ax3,ax4],label='without PE',linecolor=u'#9EC9E9') + ax1.set_title('a) Analysis error') + ax1.set_xlabel(r'RMSD$^a_\theta$') + ax2.set_title('b) First-guess error') + ax2.set_xlabel(r'RMSD$^b_\theta$') + ax3.set_title('c) Error reduction') + ax3.set_xlabel(r'RMSD$^b_\theta-$RMSD$^a_\theta$') + ax4.set_title('d) Spread-error consistency') + ax4.set_xlabel(r'$\sigma^b_\theta$/RMSD$^b_\theta$') + ax1.set_ylabel('height (m)') + ax3.set_ylabel('height (m)') + #ax1.set_xlim([0,0.2]) + #ax2.set_xlim([0,0.2]) + #ax3.set_xlim([0,0.02]) + #ax4.set_xlim([0,10]) + fig.savefig('fig03_with_deviations.png',format='png',dpi=300) + p.close(fig) - if fig104: + if fig04: - exp_B1 = pickle.load(open(default_da_settings['path']+"exp_B1.pickle", "rb")) - exp_B2 = pickle.load(open(default_da_settings['path']+"exp_B2.pickle", "rb")) - exp_B3 = pickle.load(open(default_da_settings['path']+"exp_B3.pickle", "rb")) - exp_B4 = pickle.load(open(default_da_settings['path']+"exp_B4.pickle", "rb")) + exp_B1 = pickle.load(open(workdir+"exp_B1.pickle", "rb")) + exp_B2 = pickle.load(open(workdir+"exp_B2.pickle", "rb")) + exp_B3 = pickle.load(open(workdir+"exp_B3.pickle", "rb")) + exp_B4 = pickle.load(open(workdir+"exp_B4.pickle", "rb")) fig, [[ax1, ax2], [ax3, ax4]] = p.subplots(2,2,constrained_layout=True) fig.set_size_inches(6,4) # - ax1 = plot_CBL_PE(exp_B1,None,ax=ax1) + ax1 = plot_CBL_PE(exp_B1,None,ax=ax1,show_true_parameter=True) ax1.set_title(r'a) Exp. B$_1$') ax1.set_xlabel('Time (h)') ax1.set_ylabel(r'$p$') # - ax2 = plot_CBL_PE(exp_B2,None,ax=ax2) + ax2 = plot_CBL_PE(exp_B2,None,ax=ax2,show_true_parameter=True) ax2.set_title(r'b) Exp. B$_2$') ax2.set_xlabel('Time (h)') ax2.set_ylabel(r'$p$') # - ax3 = plot_CBL_PE(exp_B3,None,ax=ax3) + ax3 = plot_CBL_PE(exp_B3,None,ax=ax3,show_true_parameter=True) ax3.set_title(r'c) Exp. B$_3$') ax3.set_xlabel('Time (h)') ax3.set_ylabel(r'$p$') # - ax4 = plot_CBL_PE(exp_B4,None,ax=ax4) + ax4 = plot_CBL_PE(exp_B4,None,ax=ax4,show_true_parameter=True) ax4.set_title(r'd) Exp. B$_4$') ax4.set_xlabel('Time (h)') ax4.set_ylabel(r'$p$') @@ -607,20 +314,20 @@ if __name__ == '__main__': fig.savefig('fig04.png',format='png',dpi=300) p.close(fig) - if fig105: + if fig05: - exp_C1 = pickle.load(open(default_da_settings['path']+"exp_C1.pickle", "rb")) - exp_C2 = pickle.load(open(default_da_settings['path']+"exp_C2.pickle", "rb")) + exp_C1 = pickle.load(open(workdir+"exp_C1.pickle", "rb")) + exp_C2 = pickle.load(open(workdir+"exp_C2.pickle", "rb")) fig, [ax1, ax2] = p.subplots(1,2,constrained_layout=True) fig.set_size_inches(6,2) # - ax1 = plot_CBL_PE(exp_C1,None,ax=ax1) + ax1 = plot_CBL_PE(exp_C1,None,ax=ax1,show_true_parameter=True) ax1.set_title(r'a) Exp. C$_1$') ax1.set_xlabel('Time (h)') ax1.set_ylabel(r'$p$') # - ax2 = plot_CBL_PE(exp_C2,None,ax=ax2) + ax2 = plot_CBL_PE(exp_C2,None,ax=ax2,show_true_parameter=True) ax2.set_title(r'b) Exp. C$_2$') ax2.set_xlabel('Time (h)') ax2.set_ylabel(r'$p$') @@ -631,10 +338,10 @@ if __name__ == '__main__': fig.savefig('fig05.png',format='png',dpi=300) p.close(fig) - if fig106: + if fig06: - exp_C1 = pickle.load(open(default_da_settings['path']+"exp_C1.pickle", "rb")) - exp_C2 = pickle.load(open(default_da_settings['path']+"exp_C2.pickle", "rb")) + exp_C1 = pickle.load(open(workdir+"exp_C1.pickle", "rb")) + exp_C2 = pickle.load(open(workdir+"exp_C2.pickle", "rb")) npar2 = exp_C1.nr.parameter_number npar3 = exp_C2.nr.parameter_number @@ -667,18 +374,18 @@ if __name__ == '__main__': fig.savefig('fig06.png',format='png',dpi=300) p.close(fig) - if fig107: + if fig07: - exp_D = pickle.load(open(default_da_settings['path']+"exp_D.pickle", "rb")) + exp_D = pickle.load(open(workdir+"exp_D2.pickle", "rb")) fig, [ax0, ax1] = p.subplots(1,2,constrained_layout=True) fig.set_size_inches(6,3) # - ax0,c0 = plot_CBL_identifiability(exp_D,da_settings_D['obs_error_sdev_assimilate'][0],ax=ax0) + ax0,c0 = plot_CBL_identifiability(exp_D,da_settings_D2['obs_error_sdev_assimilate'],ax=ax0) ax0.set_title(r'a) Exp. D, $\rho(p\prime\prime,y_b}$)') ax0.set_xlabel('Time (h)') ax0.set_ylabel('Height (m)') - ax1 = plot_CBL_PE(exp_D,None,ax=ax1) + ax1 = plot_CBL_PE(exp_D,None,ax=ax1,show_true_parameter=True) ax1.set_title(r'b) Exp. D, evolution of $p$') ax1.set_xlabel('Time (h)') ax1.set_yticks([0,1,2,3,4,5]) @@ -692,15 +399,15 @@ if __name__ == '__main__': fig.savefig('fig07.png',format='png',dpi=300) p.close(fig) - if fig108: + if fig08: - exp_A = pickle.load(open(default_da_settings['path']+"exp_A.pickle", "rb")) - exp_D = pickle.load(open(default_da_settings['path']+"exp_D.pickle", "rb")) + exp_A = pickle.load(open(workdir+"exp_A1.pickle", "rb")) + exp_D = pickle.load(open(workdir+"exp_D2.pickle", "rb")) fig, [[ax0, ax1],[ax2,ax3]] = p.subplots(2,2,constrained_layout=True) fig.set_size_inches(6,6) # - [ax1,ax0],c0,c1 = plot_CBL_identifiability(exp_A,da_settings_A['obs_error_sdev_assimilate'][0],ax=[ax1,ax0]) + [ax1,ax0],c0,c1 = plot_CBL_identifiability(exp_A,da_settings_A1['obs_error_sdev_assimilate'],ax=[ax1,ax0]) ax0.set_title(r'a) Exp. A$_1$, $\delta\overline{y}$ (K)') ax0.set_xlabel('Time (h)') ax0.set_ylabel('Height (m)') @@ -712,11 +419,11 @@ if __name__ == '__main__': ax1.set_xlim([0,maxtime/3600]) ax0.set_xticks(np.arange((maxtime+1)/3600)) ax1.set_xticks(np.arange((maxtime+1)/3600)) - [ax3,ax2],c2,c3 = plot_CBL_identifiability(exp_D,da_settings_D['obs_error_sdev_assimilate'][0],ax=[ax3,ax2]) - ax2.set_title(r'c) Exp. D, $\delta\overline{y}$ (K)') + [ax3,ax2],c2,c3 = plot_CBL_identifiability(exp_D,da_settings_D2['obs_error_sdev_assimilate'],ax=[ax3,ax2]) + ax2.set_title(r'c) Exp. D$_2$, $\delta\overline{y}$ (K)') ax2.set_xlabel('Time (h)') ax2.set_ylabel('Height (m)') - ax3.set_title(r'd) Exp. D, $\sigma_{p\prime\prime}/\sigma_{y^b}$ (K$^{-1}$)') + ax3.set_title(r'd) Exp. D$_2$, $\sigma_{p\prime\prime}/\sigma_{y^b}$ (K$^{-1}$)') ax3.set_xlabel('Time (h)') ax3.set_ylabel('Height (m)') maxtime = exp_D.trun @@ -732,36 +439,40 @@ if __name__ == '__main__': fig.savefig('fig08.png',format='png',dpi=300) p.close(fig) - if fig109: + if fig09: - path_EAKF = "./runs/seed=181612_enssize=200_EAKF_6hrs/" - path_LETKF = "./runs/seed=181612_enssize=200_LETKF_6hrs/" + path_EAKF = "" + path_LETKF = "./runs/seed=181612_enssize=20_LETKF_6hrs/" - exp_Aa = pickle.load(open(path_EAKF+"exp_A.pickle", "rb")) + exp_Aa = pickle.load(open(path_EAKF+"exp_A1.pickle", "rb")) exp_B1a = pickle.load(open(path_EAKF+"exp_B1.pickle", "rb")) exp_B2a = pickle.load(open(path_EAKF+"exp_B2.pickle", "rb")) exp_B3a = pickle.load(open(path_EAKF+"exp_B3.pickle", "rb")) exp_B4a = pickle.load(open(path_EAKF+"exp_B4.pickle", "rb")) exp_C1a = pickle.load(open(path_EAKF+"exp_C1.pickle", "rb")) exp_C2a = pickle.load(open(path_EAKF+"exp_C2.pickle", "rb")) - exp_Da = pickle.load(open(path_EAKF+"exp_D.pickle", "rb")) + exp_D1a = pickle.load(open(path_EAKF+"exp_D1.pickle", "rb")) + exp_D2a = pickle.load(open(path_EAKF+"exp_D2.pickle", "rb")) - exp_Ab = pickle.load(open(path_LETKF+"exp_A.pickle", "rb")) + exp_Ab = pickle.load(open(path_LETKF+"exp_A1.pickle", "rb")) exp_B1b = pickle.load(open(path_LETKF+"exp_B1.pickle", "rb")) exp_B2b= pickle.load(open(path_LETKF+"exp_B2.pickle", "rb")) exp_B3b = pickle.load(open(path_LETKF+"exp_B3.pickle", "rb")) exp_B4b = pickle.load(open(path_LETKF+"exp_B4.pickle", "rb")) exp_C1b = pickle.load(open(path_LETKF+"exp_C1.pickle", "rb")) exp_C2b = pickle.load(open(path_LETKF+"exp_C2.pickle", "rb")) - exp_Db = pickle.load(open(path_LETKF+"exp_D.pickle", "rb")) + exp_D1b = pickle.load(open(path_LETKF+"exp_D1.pickle", "rb")) + exp_D2b = pickle.load(open(path_LETKF+"exp_D2.pickle", "rb")) fig, [ax1, ax2] = p.subplots(1,2) fig.set_size_inches(6,3) # - cc = 0 - linecolors = ['darkviolet','chocolate','darkorange','orange','gold','darkgreen','seagreen','royalblue'] - labels = [r'Exp. A',r'Exp. B$_1$',r'Exp. B$_2$',r'Exp. B$_3$',r'Exp. B$_4$',r'Exp. C$_1$',r'Exp. C$_2$',r'Exp. D'] - for exp in [exp_Aa,exp_B1a,exp_B2a,exp_B3a,exp_B4a,exp_C1a,exp_C2a,exp_Da]: + linecolors = ['darkviolet','chocolate','darkorange','orange','gold','darkgreen','seagreen','royalblue','black'] + labels = [r'Exp. A',r'Exp. B$_1$',r'Exp. B$_2$',r'Exp. B$_3$',r'Exp. B$_4$',r'Exp. C$_1$',r'Exp. C$_2$',r'Exp. D$_1$',r'Exp. D$_2$'] + experiments1 = [exp_Aa,exp_B1a,exp_B2a,exp_B3a,exp_B4a,exp_C1a,exp_C2a,exp_D1a,exp_D2a] + experiments2 = [exp_Ab,exp_B1b,exp_B2b,exp_B3b,exp_B4b,exp_C1b,exp_C2b,exp_D1b,exp_D2b] + # + for exp,label,linecolor in zip(experiments1,labels,linecolors): # Experiment settings true_value = exp.nr.pfac parameter_number = exp.nr.parameter_number @@ -773,14 +484,12 @@ if __name__ == '__main__': t = exp.da.time/3600. # Initial parameter distribution - ax1.step([0,t[0]],np.mean(initpar_phys,axis=1)*np.array([1,1]),color=linecolors[cc],label=labels[cc]) + ax1.step([0,t[0]],np.mean(initpar_phys,axis=1)*np.array([1,1]),color=linecolor,label=label) # Later times - ax1.step(t,np.mean(par_phys,axis=1),color=linecolors[cc]) - cc += 1 + ax1.step(t,np.mean(par_phys,axis=1),color=linecolor) - cc = 0 - for exp in [exp_Ab,exp_B1b,exp_B2b,exp_B3b,exp_B4b,exp_C1b,exp_C2b,exp_Db]: + for exp,label,linecolor in zip(experiments2,labels,linecolors): # Experiment settings true_value = exp.nr.pfac parameter_number = exp.nr.parameter_number @@ -792,11 +501,10 @@ if __name__ == '__main__': t = exp.da.time/3600. # Initial parameter distribution - ax2.step([0,t[0]],np.mean(initpar_phys,axis=1)*np.array([1,1]),color=linecolors[cc]) + ax2.step([0,t[0]],np.mean(initpar_phys,axis=1)*np.array([1,1]),color=linecolor) # Later times - ax2.step(t,np.mean(par_phys,axis=1),color=linecolors[cc]) - cc += 1 + ax2.step(t,np.mean(par_phys,axis=1),color=linecolor) # Other stuff ax1.axhline(y=true_value,linestyle='--',color='black') @@ -822,351 +530,290 @@ if __name__ == '__main__': if opt01: - da_settings = {'cbl_settings' : dict(default_cbl_settings), - 'tspinup' : 10800, - 'trun' : 3600, - 'assimilation_interval' : 3600, - 'obs_coordinates' : np.array([433.]), - 'obs_kinds' : np.array(['theta']), - 'obs_error_sdev_generate' : np.array([.2]), - 'obs_error_sdev_assimilate' : np.array([1.]), - 'localization_cutoff' : 400, - 'nens' : 50, - 'FILTER' : 'EAKF', - 'inflation_rtps_alpha' : 0.1, - } - exp = experiment(da_settings) + with open('./default_da.json', 'r') as fp: + da_settings = json.load(fp) + exp = load_or_run(da_settings) + plot_CBL_assimilation(exp,'single_assimilation.png') if opt02: - # Make plots - fig, [ax1, ax2] = p.subplots(1,2,constrained_layout=True) - fig.set_size_inches(6,3) + plot_profiles(exp_A1,da_settings_A1,'profiles.png') - which_cycle = 0 - tt1 = (which_cycle+1)*da_settings_A['assimilation_interval'] - ax1 = plot_CBL_assimilation(exp_A,None,which_cycle=which_cycle,ax=ax1) - ax1.set_ylim([0,2000]) - ax1.set_xlim([288,298]) - ax1.set_ylabel(r'height (m)') - ax1.set_title(r'a) Analysis at $t=$%u s'%tt1) - ax1.legend(frameon=False) - - which_cycle = 31 - tt2 = (which_cycle+1)*da_settings_A['assimilation_interval'] - ax2 = plot_CBL_assimilation(exp_A,None,which_cycle=which_cycle,ax=ax2) - ax2.set_ylim([0,2000]) - ax2.set_xlim([288,298]) - ax2.set_ylabel(r'height (m)') - ax2.set_title(r'b) Analysis at $t=$%u s'%tt2) + if opt03: - fig.savefig('profiles.png',format='png',dpi=300) - p.close(fig) + # Control experiment (an OSSE with nature run) + with open('./runs/real/da_exp_RC.json', 'r') as fp: + da_settings_RC = json.load(fp) + exp_RC = load_or_run(da_settings_RC) - if opt03: # Test with "real" observations (no nature run) - - nens = 20 - ############################################################################ - # Control experiment (perfect-model) - # Create a copy of the default settings - # and change some of the settings as needed - cbl_settings_RC = dict(default_cbl_settings) - cbl_settings_RC['theta_0'] = 290 - cbl_settings_RC['gamma'] = 3e-3 - cbl_settings_RC['Hmax'] = 0.12 - #cbl_settings_RC['parameter_ensemble_min'] = np.array([0.5]) - #cbl_settings_RC['parameter_ensemble_max'] = np.array([2.5]) - da_settings_RC = dict(default_da_settings) - da_settings_RC['cbl_settings'] = dict(cbl_settings_RC) - da_settings_RC['nens'] = nens - da_settings_RC['type'] = 'OSSE' - da_settings_RC['path'] = "./runs/real_init_exp_seed=%s_enssize=%s_EAKF_6hrs/"%(rnseed,nens) - - # Run and save to disk - try: - exp_RC = pickle.load(open(da_settings_RC['path']+"exp_RC.pickle", "rb")) - except: - exp_RC = experiment(da_settings_RC) - setattr(exp_RC,'label','RC') - pickle.dump(exp_RC, open(da_settings_RC['path']+'exp_RC.pickle', 'wb')) - - ############################################################################ - # Imperfect-model experiment - # Create a copy of the default settings - # and change some of the settings as needed - cbl_settings_R = dict(default_cbl_settings) - cbl_settings_R['theta_0'] = 290 - cbl_settings_R['gamma'] = 3e-3 - cbl_settings_R['Hmax'] = 0.12 - #cbl_settings_R['parameter_ensemble_min'] = np.array([0.5]) - #cbl_settings_R['parameter_ensemble_max'] = np.array([2.5]) - da_settings_R = dict(default_da_settings) - da_settings_R['cbl_settings'] = dict(cbl_settings_R) - da_settings_R['nens'] = nens - da_settings_R['type'] = 'real' - da_settings_R['observation_files'] = './LES_data/Theta/*csv' - da_settings_R['obs_coordinates'] = None - da_settings_R['obs_error_sdev_generate'] = None - da_settings_R['path'] = "./runs/real_init_exp_seed=%s_enssize=%s_EAKF_6hrs/"%(rnseed,nens) - - # Run and save to disk - try: - exp_R = pickle.load(open(da_settings_R['path']+'exp_R.pickle', "rb")) - except: - exp_R = experiment(da_settings_R) - setattr(exp_R,'label','R') - pickle.dump(exp_R, open(da_settings_R['path']+'exp_R.pickle', 'wb')) - - ############################################################################ - # Run a free ensemble if desired - freens = False - if freens: - maxtime = 3600+21600 # spinup + analysis run - # Do a free ensemble run (ensemble size set expliclity) - cbl_settings_free = dict(cbl_settings_R) - cbl_settings_free['maxtime'] = maxtime - cbl_settings_free['perturb_ensemble_state'] = False - cbl_settings_free['parameter_ensemble_min'] = np.array([0.5]) - cbl_settings_free['parameter_ensemble_max'] = np.array([2.5]) - cbl_free = CBL(cbl_settings_free) - cbl_free.initialize(nens) - cbl_free.run(output_full_history=True) - # Make a plot about the free ensemble - fig, ax1 = p.subplots(1,1,constrained_layout=True) - fig.set_size_inches(4,3) - ax1,c1 = plot_spread(cbl_free,plot='mean',ax=ax1) - ax1.set_ylabel(r'Height (m)') - ax1.set_title(r'$\overline{\theta}$ (K)') - ax1.set_xlabel('Time (h)') - ax1.set_xticks(np.arange((maxtime+1)/3600)) - c1.set_clim([290,296]) - p.colorbar(c1,orientation='horizontal') - fig.savefig('expR_free_ens.png',format='png',dpi=300) - p.close(fig) + with open('./runs/real/da_exp_R.json', 'r') as fp: + da_settings_R = json.load(fp) + exp_R = load_or_run(da_settings_R) #########################################################################################à - # Make plots: perfect-model experiment (RC) - fig, [ax1, ax2] = p.subplots(1,2,constrained_layout=True) - fig.set_size_inches(6,3) + # Plots + for exp, label in zip([exp_RC, exp_R],['RC','R']): + plot_overview(exp,label,'exp_%s.png'%label) - which_cycle = 0 - tt1 = (which_cycle+1)*da_settings_RC['assimilation_interval'] - ax1 = plot_CBL_assimilation(exp_RC,None,which_cycle=which_cycle,ax=ax1) - ax1.set_ylim([0,2000]) - ax1.set_xlim([288,298]) - ax1.set_ylabel(r'height (m)') - ax1.set_title(r'a) Analysis at $t=$%u s'%tt1) - ax1.legend(frameon=False) - - which_cycle = 31 - tt2 = (which_cycle+1)*da_settings_RC['assimilation_interval'] - ax2 = plot_CBL_assimilation(exp_RC,None,which_cycle=which_cycle,ax=ax2) - ax2.set_ylim([0,2000]) - ax2.set_xlim([288,298]) - ax2.set_ylabel(r'height (m)') - ax2.set_title(r'b) Analysis at $t=$%u s'%tt2) + plot_profiles(exp_RC,da_settings_RC,'expRC_profiles.png') + plot_profiles(exp_R,da_settings_R,'expR_profiles.png') - fig.savefig('expRC_profiles.png',format='png',dpi=300) - p.close(fig) + if opt04: - fig, [[ax0, ax1], [ax2, ax3]] = p.subplots(2,2,constrained_layout=True) - fig.set_size_inches(6,6) - # - [ax0,ax1,ax2],c0,c1,c2 = plot_CBL_identifiability(exp_RC,da_settings_RC['obs_error_sdev_assimilate'][0],ax=[ax0,ax1,ax2]) - ax0.set_title(r'a) Exp. RC, $\rho(p\prime\prime,y_b}$)') - ax0.set_xlabel('Time (h)') - ax0.set_ylabel('Height (m)') - ax1.set_title(r'b) Exp. RC, $\delta\overline{y}\cdot(\sigma_{p\prime\prime}/\sigma_{y^b})$') - ax1.set_xlabel('Time (h)') - ax1.set_ylabel('Height (m)') - ax2.set_title(r'c) Exp. RC, $\delta\overline{p}\prime\prime$') - ax2.set_xlabel('Time (h)') - ax2.set_ylabel('Height (m)') - ax3 = plot_CBL_PE(exp_RC,None,ax=ax3) - ax3.set_title(r'd) Exp. RC, evolution of $p$') - ax3.set_xlabel('Time (h)') - ax3.set_yticks([0,1,2,3,4,5]) - maxtime = exp_R.trun - 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)) + # Test with "real" observations (no nature run) + with open('./runs/real/da_exp_R.json', 'r') as fp: + da_settings_S0 = json.load(fp) + exp_S0 = load_or_run(da_settings_S0) + exp_S0.label = 'S0' + + # Additional tests with longer spinupt time + with open('./runs/real/da_exp_S1.json', 'r') as fp: + da_settings_S1 = json.load(fp) + exp_S1 = load_or_run(da_settings_S1) + + with open('./runs/real/da_exp_S2.json', 'r') as fp: + da_settings_S2 = json.load(fp) + exp_S2 = load_or_run(da_settings_S2) + + with open('./runs/real/da_exp_S3.json', 'r') as fp: + da_settings_S3 = json.load(fp) + exp_S3 = load_or_run(da_settings_S3) + + for exp,label in zip([exp_S0,exp_S1,exp_S2,exp_S3],['S0','S1','S2','S3']): + plot_overview(exp,label,'exp_%s.png'%label) + + if opt05: + + with open('./config_files/da_exp_L1.json', 'r') as fp: + da_settings_localization_1 = json.load(fp) + exp_localization_1 = load_or_run(da_settings_localization_1) + + with open('./config_files/da_exp_A1.json', 'r') as fp: + da_settings_localization_2 = json.load(fp) + exp_localization_2 = load_or_run(da_settings_localization_2) + exp_localization_2.label = 'L2' + + with open('./config_files/da_exp_L3.json', 'r') as fp: + da_settings_localization_3 = json.load(fp) + exp_localization_3 = load_or_run(da_settings_localization_3) + + with open('./config_files/da_exp_L4.json', 'r') as fp: + da_settings_localization_4 = json.load(fp) + exp_localization_4 = load_or_run(da_settings_localization_4) + + for exp,settings,label in zip([exp_localization_1,exp_localization_2,exp_localization_3,exp_localization_4],\ + [da_settings_localization_1,da_settings_localization_2,da_settings_localization_3,da_settings_localization_4], + [r"L$_1$",r"L$_2$",r"L$_3$",r"L$_4$"]): + print(exp.localization_cutoff) + + plot_overview(exp,label,'exp_%s.png'%exp.label) + + if opt06: + + with open('./config_files/da_exp_I1.json', 'r') as fp: + da_settings_inflation_1 = json.load(fp) + exp_inflation_1 = load_or_run(da_settings_inflation_1) + + with open('./config_files/da_exp_I2.json', 'r') as fp: + da_settings_inflation_2 = json.load(fp) + exp_inflation_2 = load_or_run(da_settings_inflation_2) + + with open('./config_files/da_exp_A1.json', 'r') as fp: + da_settings_inflation_3 = json.load(fp) + exp_inflation_3 = load_or_run(da_settings_inflation_3) + exp_inflation_3.label = 'I3' + + with open('./config_files/da_exp_I4.json', 'r') as fp: + da_settings_inflation_4 = json.load(fp) + exp_inflation_4 = load_or_run(da_settings_inflation_4) + + for exp,settings,label in zip([exp_inflation_1,exp_inflation_2,exp_inflation_3,exp_inflation_4],\ + [da_settings_inflation_1,da_settings_inflation_2,da_settings_inflation_3,da_settings_inflation_4], + [r"I$_1$",r"I$_2$",r"I$_3$",r"I$_4$"]): + print(exp.cbl_settings["parameter_inflation_rtps_alpha"]) + + plot_overview(exp,label,'exp_%s.png'%exp.label,show_spread=True,show_transformed_parameter=True) + + if opt07: + + with open('./config_files/da_exp_I5.json', 'r') as fp: + da_settings_inflation_5 = json.load(fp) + exp_inflation_5 = load_or_run(da_settings_inflation_5) + + with open('./config_files/da_exp_A1.json', 'r') as fp: + da_settings_inflation_6 = json.load(fp) + exp_inflation_6 = load_or_run(da_settings_inflation_6) + exp_inflation_6.label = 'I6' + + with open('./config_files/da_exp_I7.json', 'r') as fp: + da_settings_inflation_7 = json.load(fp) + exp_inflation_7 = load_or_run(da_settings_inflation_7) + + with open('./config_files/da_exp_I8.json', 'r') as fp: + da_settings_inflation_8 = json.load(fp) + exp_inflation_8 = load_or_run(da_settings_inflation_8) + + for exp,settings,label in zip([exp_inflation_5,exp_inflation_6,exp_inflation_7,exp_inflation_8],\ + [da_settings_inflation_5,da_settings_inflation_6,da_settings_inflation_7,da_settings_inflation_8], + [r"I$_5$",r"I$_6$",r"I$_7$",r"I$_8$"]): + print(exp.inflation_rtps_alpha) + + plot_overview(exp,label,'exp_%s.png'%exp.label) + + if opt08: + + with open('./config_files/da_exp_A1.json', 'r') as fp: + da_settings_O1 = json.load(fp) + exp_O1 = load_or_run(da_settings_O1) + exp_O1.label = 'O1' + + with open('./config_files/da_exp_O2.json', 'r') as fp: + da_settings_O2 = json.load(fp) + exp_O2 = load_or_run(da_settings_O2) + + # Plots + for exp,settings,label in zip([exp_O1,exp_O2],\ + [da_settings_O1,da_settings_O2],\ + [r"O$_1$",r"O$_2$"]): + print(exp.randomize_obs) + + plot_overview(exp,label,'exp_%s.png'%exp.label) + + # Difference between two experiments + nobs = exp_O1.nobs + maxtime = exp_O1.nr.maxtime + ntimes = exp_O1.da.ncycles + me1 = np.zeros(exp_O1.da.model_equivalents.shape)+np.nan + me2 = np.zeros(exp_O2.da.model_equivalents.shape)+np.nan + + # To get the plot right, we need to re-sort the + # model equivalents and observation coordinates + zobs1 = exp_O1.da.obs_coordinates + zobs2 = exp_O2.da.obs_coordinates + for k in range(ntimes): + if is_sorted(zobs1[k,:]): + indices = np.arange(nobs) + else: + indices = np.argsort(zobs1[k,:]) + zobs1[k,:] = zobs1[k,indices] + me1[k,:,:] = exp_O1.da.model_equivalents[k,indices,:] + for k in range(ntimes): + if is_sorted(zobs2[k,:]): + indices = np.arange(nobs) + else: + indices = np.argsort(zobs2[k,:]) + zobs2[k,:] = zobs2[k,indices] + me2[k,:,:] = exp_O2.da.model_equivalents[k,indices,:] + + times = exp_O1.da.time + times = times[:,None]+np.zeros((ntimes,nobs)) + delta_theta = me1.mean(axis=2) - me2.mean(axis=2) + + fig, ax = p.subplots(1,1,constrained_layout=True) + fig.set_size_inches(4,3) + c0 = ax.pcolormesh(times/3600.,zobs1,delta_theta,cmap='RdBu_r') + ax.set_xlim([0,maxtime/3600]) + ax.set_xticks(np.arange((maxtime+1)/3600)) + ax.set_xlabel('Time (h)') + ax.set_ylabel('Height (m)') + ax.set_title(r'$\overline{y}_b$(O2)$ - \overline{y}_b$(O1) (K)') p.colorbar(c0,orientation='horizontal') - p.colorbar(c1,orientation='horizontal') - p.colorbar(c2,orientation='horizontal') - # - fig.savefig('expRC_PE.png',format='png',dpi=300) + fig.savefig('impact_of_random_obs.png',format='png',dpi=300) p.close(fig) - - #########################################################################################à - # Make plots: imperfect-model experiment (R) - fig, [ax1, ax2] = p.subplots(1,2,constrained_layout=True) + + if opt09: + + plot_B_matrix(exp_A1,timeid=71) + + if opt10: + + # Plots: Scatter plots of Desroziers statistics + #experiments = [exp_A1, exp_B1, exp_B2, exp_B3, exp_B4, exp_C1, exp_C2, exp_D1, exp_D2 ] + #labels = [r'A$_1$', r'B$_1$', r'B$_2$', r'B$_3$', r'B$_4$', r'C$_1$', r'C$_2$', r'D$_1$', r'D$_2$' ] + experiments1 = [exp_A1, exp_B1, exp_B4, exp_C1, exp_C2 ] + labels1 = [r'A$_1$', r'B$_1$', r'B$_4$', r'C$_1$', r'C$_2$' ] + experiments2 = [exp_B2, exp_B3, exp_D1, exp_D2 ] + labels2 = [r'B$_2$', r'B$_3$', r'D$_1$', r'D$_2$' ] + + fig = p.figure(0) fig.set_size_inches(6,3) + ax1 = fig.add_subplot(1,2,1) + ax2 = fig.add_subplot(1,2,2) - which_cycle = 0 - tt1 = (which_cycle+1)*da_settings_R['assimilation_interval'] - ax1 = plot_CBL_assimilation(exp_R,None,which_cycle=which_cycle,ax=ax1) - ax1.set_ylim([0,2000]) - ax1.set_xlim([288,298]) - ax1.set_ylabel(r'height (m)') - ax1.set_title(r'a) Analysis at $t=$%u s'%tt1) - ax1.legend(frameon=False) - - which_cycle = 31 - tt2 = (which_cycle+1)*da_settings_R['assimilation_interval'] - ax2 = plot_CBL_assimilation(exp_R,None,which_cycle=which_cycle,ax=ax2) - ax2.set_ylim([0,2000]) - ax2.set_xlim([288,298]) - ax2.set_ylabel(r'height (m)') - ax2.set_title(r'b) Analysis at $t=$%u s'%tt2) + ax1 = plot_desroziers([i.dg for i in experiments1],labels=labels1,ax=ax1) + ax2 = plot_desroziers([i.dg for i in experiments2],labels=labels2,ax=ax2) - fig.savefig('expR_profiles.png',format='png',dpi=300) + fig.tight_layout() + fig.savefig('Diag_desroziers.png',format='png',dpi=150) p.close(fig) - fig, [[ax0, ax1], [ax2, ax3]] = p.subplots(2,2,constrained_layout=True) - fig.set_size_inches(6,6) - # - [ax0,ax1,ax2],c0,c1,c2 = plot_CBL_identifiability(exp_R,da_settings_R['obs_error_sdev_assimilate'][0],ax=[ax0,ax1,ax2]) - ax0.set_title(r'a) Exp. R, $\rho(p\prime\prime,y_b}$)') - ax0.set_xlabel('Time (h)') - ax0.set_ylabel('Height (m)') - ax1.set_title(r'b) Exp. R, $\delta\overline{y}\cdot(\sigma_{p\prime\prime}/\sigma_{y^b})$') - ax1.set_xlabel('Time (h)') - ax1.set_ylabel('Height (m)') - ax2.set_title(r'c) Exp. R, $\delta\overline{p}\prime\prime$') - ax2.set_xlabel('Time (h)') - ax2.set_ylabel('Height (m)') - ax3 = plot_CBL_PE(exp_R,None,ax=ax3) - ax3.set_title(r'd) Exp. R, evolution of $p$') - ax3.set_xlabel('Time (h)') - ax3.set_yticks([0,1,2,3,4,5]) - maxtime = exp_R.trun - 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('expR_PE.png',format='png',dpi=300) - p.close(fig) + if opt11: - if opt04: + # Plots + plot_overview(exp_D1,r"D$_1$",'exp_D1.png') + plot_overview(exp_D2,r"D$_2$",'exp_D2.png') + plot_overview(exp_B3,r"B$_3$",'exp_B3.png') - # Use only 20 ensemble members for these experiments - nens = 20 - - # Base settings - cbl_settings_base = dict(default_cbl_settings) - cbl_settings_base['theta_0'] = 290 - cbl_settings_base['gamma'] = 3e-3 - cbl_settings_base['Hmax'] = 0.12 - da_settings_base = dict(default_da_settings) - da_settings_base['cbl_settings'] = dict(cbl_settings_base) - da_settings_base['nens'] = nens - da_settings_base['type'] = 'real' - da_settings_base['observation_files'] = './LES_data/Theta/*csv' - da_settings_base['obs_coordinates'] = None - da_settings_base['obs_error_sdev_generate'] = None - - # Base experiment (in opt03 has tspinup = 1 hour). Try other lengths. - da_settings_spinup_1 = dict(da_settings_base) - da_settings_spinup_1['tspinup'] = 7200 - da_settings_spinup_1['trun'] = 25200-7200 - da_settings_spinup_1['path'] = "./runs/spinup_seed=%s_enssize=%s_EAKF_6hrs/"%(rnseed,nens) - - da_settings_spinup_2 = dict(da_settings_base) - da_settings_spinup_2['tspinup'] = 10800 - da_settings_spinup_2['trun'] = 25200-10800 - da_settings_spinup_2['path'] = "./runs/spinup_seed=%s_enssize=%s_EAKF_6hrs/"%(rnseed,nens) - - da_settings_spinup_3 = dict(da_settings_base) - da_settings_spinup_3['tspinup'] = 14400 - da_settings_spinup_3['trun'] = 25200-14400 - da_settings_spinup_3['path'] = "./runs/spinup_seed=%s_enssize=%s_EAKF_6hrs/"%(rnseed,nens) - - # Run and save to disk - try: - exp_spinup_1 = pickle.load(open(da_settings_spinup_1['path']+'exp_spinup_1.pickle', "rb")) - except: - exp_spinup_1 = experiment(da_settings_spinup_1) - setattr(exp_spinup_1,'label','spinup_1') - pickle.dump(exp_spinup_1, open(da_settings_spinup_1['path']+'exp_spinup_1.pickle', 'wb')) - try: - exp_spinup_2 = pickle.load(open(da_settings_spinup_2['path']+'exp_spinup_2.pickle', "rb")) - except: - exp_spinup_2 = experiment(da_settings_spinup_2) - setattr(exp_spinup_2,'label','spinup_2') - pickle.dump(exp_spinup_2, open(da_settings_spinup_2['path']+'exp_spinup_2.pickle', 'wb')) - try: - exp_spinup_3 = pickle.load(open(da_settings_spinup_3['path']+'exp_spinup_3.pickle', "rb")) - except: - exp_spinup_3 = experiment(da_settings_spinup_3) - setattr(exp_spinup_3,'label','spinup_3') - pickle.dump(exp_spinup_3, open(da_settings_spinup_3['path']+'exp_spinup_3.pickle', 'wb')) - - for exp,settings in zip([exp_spinup_1,exp_spinup_2,exp_spinup_3],[da_settings_spinup_1,da_settings_spinup_2,da_settings_spinup_3]): - label = exp.label - maxtime = exp.trun - - fig, [[ax0, ax1], [ax2, ax3]] = p.subplots(2,2,constrained_layout=True) - fig.set_size_inches(6,6) - # - [ax0,ax1,ax2],c0,c1,c2 = plot_CBL_identifiability(exp,settings['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) - ax3.set_title(r'd) Exp. %s, evolution of $p$'%label) - ax3.set_xlabel('Time (h)') - ax3.set_yticks([0,1,2,3,4,5]) - 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('exp_%s_PE.png'%label,format='png',dpi=300) - p.close(fig) + fig, [[ax1, ax2], [ax3, ax4]] = p.subplots(2,2,constrained_layout=True) + fig.set_size_inches(6,4) + plot_diagnostics(exp_D1,show='errors',axes=[ax1,ax2,ax3,ax4],label='with PE',linecolor='#1f77b4') + plot_diagnostics(exp_D2,show='errors',axes=[ax1,ax2,ax3,ax4],label='without PE',linecolor='#9EC9E9') + plot_diagnostics(exp_B3,show='errors',axes=[ax1,ax2,ax3,ax4],label='without PE',linecolor='orange') + 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)') + #ax1.set_xlim([0,0.2]) + #ax2.set_xlim([0,0.2]) + #ax3.set_xlim([0,0.02]) + #ax4.set_xlim([0,10]) + fig.savefig('profiles_errors.png',format='png',dpi=300) + p.close(fig) + + fig, [[ax1, ax2], [ax3, ax4]] = p.subplots(2,2,constrained_layout=True) + fig.set_size_inches(6,4) + plot_diagnostics(exp_D1,show='deviations',axes=[ax1,ax2,ax3,ax4],label='with PE',linecolor=u'#1f77b4') + plot_diagnostics(exp_D2,show='deviations',axes=[ax1,ax2,ax3,ax4],label='without PE',linecolor=u'#9EC9E9') + plot_diagnostics(exp_B3,show='deviations',axes=[ax1,ax2,ax3,ax4],label='without PE',linecolor='orange') + ax1.set_title('a) Analysis error') + ax1.set_xlabel(r'RMSD$^a_\theta$') + ax2.set_title('b) First-guess error') + ax2.set_xlabel(r'RMSD$^b_\theta$') + ax3.set_title('c) Error reduction') + ax3.set_xlabel(r'RMSD$^b_\theta-$RMSD$^a_\theta$') + ax4.set_title('d) Spread-error consistency') + ax4.set_xlabel(r'$\sigma^b_\theta$/RMSD$^b_\theta$') + ax1.set_ylabel('height (m)') + ax3.set_ylabel('height (m)') + #ax1.set_xlim([0,0.2]) + #ax2.set_xlim([0,0.2]) + #ax3.set_xlim([0,0.02]) + #ax4.set_xlim([0,10]) + fig.savefig('profiles_deviations.png',format='png',dpi=300) + p.close(fig) - check_reproducibility = False if check_reproducibility: - path = default_da_settings['path'] - for i in ["exp_A.pickle", - "exp_A_noPE.pickle", + + for i in ["exp_A1.pickle", + "exp_A2.pickle", "exp_B1.pickle", "exp_B2.pickle", "exp_B3.pickle", "exp_B4.pickle", "exp_C1.pickle", "exp_C2.pickle", - "exp_D.pickle"]: - experiment = pickle.load(open(path+i, "rb")) + "exp_D1.pickle", + "exp_D2.pickle"]: + experiment = pickle.load(open(workdir+i, "rb")) print(i) print("OBS :",experiment.da.observations[:,0]) print("PARAMETERS:",experiment.da.initial_perturbed_parameters[0,:5]) - print("STATE :",experiment.da.initial_state[0,0,:5]) - + print("STATE :",experiment.da.initial_state[0,0,:5]) \ No newline at end of file