diff --git a/config/cfg.py b/config/cfg.py index 8a833a6659cd82e4498917e0e87079e07c6f0a5c..a74f175765168b542b0fa402f10d1aa81e011fcc 100755 --- a/config/cfg.py +++ b/config/cfg.py @@ -1,20 +1,21 @@ from dartwrf import utils exp = utils.Experiment() -exp.expname = "obs-cold_localization-narrow" +exp.expname = "test_WV73_many_inf3" exp.model_dx = 2000 -exp.n_ens = 10 +exp.n_ens = 20 exp.filter_kind = 1 -exp.prior_inflation = 0 -exp.post_inflation = 4 +exp.prior_inflation = 3 +exp.inf_initial = 4 +exp.post_inflation = 0 exp.sec = True -exp.cov_loc_vert_km_horiz_km = (4, 40) +exp.cov_loc_vert_km_horiz_km = False #(2, 10) exp.superob_km = False # False or int (spatial averaging of observations) exp.adjust_obs_impact = False exp.use_existing_obsseq = False # False or pathname (use precomputed obs_seq.out files) -exp.use_existing_obsseq = '/users/students/lehre/advDA_s2023/dartwrf_tutorial/very_cold_observation.out' +#exp.use_existing_obsseq = '/users/students/lehre/advDA_s2023/dartwrf_tutorial/very_cold_observation.out' # path to the nature run, where we take observations from exp.nature = '/users/students/lehre/advDA_s2023/data/sample_nature/' @@ -39,9 +40,9 @@ wv62 = dict(plotname='Brightness temperature WV 6.2µm', plotunits='[K]', wv73 = dict(plotname='Brightness temperature WV 7.3µm', plotunits='[K]', kind='MSG_4_SEVIRI_TB', sat_channel=6, - n_obs=961, obs_locations='square_array_evenly_on_grid', - error_generate=1., error_assimilate=3., - cov_loc_radius_km=20) + n_obs=256, obs_locations='square_array_evenly_on_grid', + error_generate=2., error_assimilate=2., + cov_loc_radius_km=10) ir108 = dict(plotname='Brightness temperature IR 10.8µm', plotunits='[K]', kind='MSG_4_SEVIRI_TB', sat_channel=9, @@ -58,29 +59,30 @@ radar = dict(plotname='Radar reflectivity', plotunits='[dBz]', t = dict(plotname='Temperature', plotunits='[K]', kind='RADIOSONDE_TEMPERATURE', - #n_obs=22500, obs_locations='square_array_evenly_on_grid', + #n_obs=961, obs_locations='square_array_evenly_on_grid', n_obs=1, obs_locations=[(45., 0.)], error_generate=0.2, error_assimilate=0.2, - heights=[1000,], #range(1000, 17001, 2000), - cov_loc_radius_km=50) + heights=range(700, 17701, 500), + cov_loc_radius_km=100) q = dict(plotname='Specific humidity', plotunits='[kg/kg]', kind='RADIOSONDE_SPECIFIC_HUMIDITY', n_obs=1, error_generate=0., error_assimilate=5*1e-5, - heights=[1000], #range(1000, 17001, 2000), + heights=[1500], #range(1000, 17001, 2000), cov_loc_radius_km=0.1) t2m = dict(plotname='SYNOP Temperature', plotunits='[K]', - kind='SYNOP_TEMPERATURE', n_obs=1, - error_generate=0.1, error_assimilate=0.1, - cov_loc_radius_km=40) + kind='SYNOP_TEMPERATURE', + #n_obs=1, obs_locations=[(45., 0.)], + n_obs=256, obs_locations='square_array_evenly_on_grid', + error_generate=1, error_assimilate=1, + cov_loc_radius_km=10) psfc = dict(plotname='SYNOP Pressure', plotunits='[Pa]', kind='SYNOP_SURFACE_PRESSURE', n_obs=1, error_generate=50., error_assimilate=100., cov_loc_radius_km=32) -exp.observations = [t2m] +exp.observations = [wv73] exp.update_vars = ['U', 'V', 'W', 'THM', 'PH', 'MU', 'QVAPOR', 'QCLOUD', 'QICE', 'PSFC'] -#exp.update_vars = ['U', 'V', 'W', 'T', 'PH', 'MU', 'QVAPOR', 'PSFC'] diff --git a/dartwrf/assim_synth_obs.py b/dartwrf/assim_synth_obs.py index 7fcc052fb996cee0e9342832c4ffbb2a0ebf7c06..3396f82d542abc84030fc5590b1bb980b87ede78 100755 --- a/dartwrf/assim_synth_obs.py +++ b/dartwrf/assim_synth_obs.py @@ -46,6 +46,7 @@ def set_DART_nml(just_prior_values=False): "<sampling_error_correction>": '.true.' if exp.sec else '.false.', "<prior_inflation>": str(exp.prior_inflation), "<post_inflation>": str(exp.post_inflation), + "<inf_initial>": str(exp.inf_initial), "<n_ens>": str(int(exp.n_ens)), "<cov_loc_radian>": "0.00000001", # dummy value, used for types not mentioned below "<list_obstypes>": "'" + "','".join(list_obstypes) + "'", @@ -220,11 +221,11 @@ def archive_filteroutput(time): ) try: # not necessary for next forecast run - for iens in range(1, exp.n_ens + 1): - copy( - cluster.dartrundir + "/postassim_member_" + str(iens).zfill(4) + ".nc", - archive_assim + "/postassim_member_" + str(iens).zfill(4) + ".nc", - ) + ftypes = ['preassim', 'postassim'] + for ftype in ftypes: + for iens in range(1, exp.n_ens + 1): + fname = "/"+ftype+"_member_" + str(iens).zfill(4) + ".nc" + copy(cluster.dartrundir + fname, archive_assim + fname) for f in ["output_mean.nc", "output_sd.nc"]: # copy mean and sd to archive copy(cluster.dartrundir + "/" + f, archive_assim + "/" + f) @@ -258,26 +259,32 @@ def get_parametrized_error(obscfg, osf_prior): NotImplementedError('sat_channel not implemented', obscfg.get("sat_channel")) -def set_obserr_assimilate_in_obsseqout(oso, osf_prior, outfile="./obs_seq.out"): +def set_obserr_assimilate_in_obsseqout(oso, outfile="./obs_seq.out"): """"Overwrite existing variance values in obs_seq.out files Args: oso (ObsSeq): python representation of obs_seq.out file, will be modified and written to file - osf_prior (ObsSeq): python representation of obs_seq.final (output of filter in evaluate-mode without posterior) - contains prior values; used for parameterized errors Returns: None (writes to file) + + Variables: + osf_prior (ObsSeq): python representation of obs_seq.final (output of filter in evaluate-mode without posterior) + contains prior values; used for parameterized errors """ + for obscfg in exp.observations: - kind_str = obscfg['kind'] - kind = osq.obs_kind_nrs[kind_str] + kind_str = obscfg['kind'] # e.g. 'RADIOSONDE_TEMPERATURE' + kind = osq.obs_kind_nrs[kind_str] # e.g. 263 - # modify each kind separately, one after each other + # modify observation error of each kind sequentially where_oso_iskind = oso.df.kind == kind - where_osf_iskind = osf_prior.df.kind == kind - + if obscfg["error_assimilate"] == False: + osf_prior = obsseq.ObsSeq(cluster.dartrundir + "/obs_seq.final") # this file will be generated by `evaluate()` + + where_osf_iskind = osf_prior.df.kind == kind + assim_err = get_parametrized_error(obscfg, osf_prior.df[where_osf_iskind]) oso.df.loc[where_oso_iskind, 'variance'] = assim_err**2 #assert np.allclose(assim_err, oso.df['variance']**2) # check @@ -287,7 +294,9 @@ def set_obserr_assimilate_in_obsseqout(oso, osf_prior, outfile="./obs_seq.out"): oso.to_dart(outfile) -def qc_obs(time, oso, osf_prior): +def qc_obs(time, oso): + osf_prior = obsseq.ObsSeq(cluster.dartrundir + "/obs_seq.final") + # obs should be superobbed already! for i, obscfg in enumerate(exp.observations): if i > 0: @@ -372,9 +381,6 @@ def evaluate(assim_time, copy(cluster.dartrundir + "/obs_seq.final", fout) print(fout, "saved.") - osf = obsseq.ObsSeq(cluster.dartrundir + "/obs_seq.final") - return osf - def generate_obsseq_out(time): @@ -470,7 +476,7 @@ def prepare_inflation_2(time, prior_init_time): if os.path.isfile(f_prior): copy(f_prior, f_new) print(f_prior, 'copied to', f_new) - else: + else: # no prior inflation file at the first assimilation warnings.warn(f_prior + ' does not exist. Using default file instead.') copy(f_default, f_new) @@ -521,9 +527,14 @@ def main(time, prior_init_time, prior_valid_time, prior_path_exp): None """ nproc = cluster.max_nproc + do_QC = getattr(exp, "reject_smallFGD", False) # True: triggers additional evaluations of prior & posterior + # for which observation type do we have a parametrized observation error? + error_is_parametrized = [obscfg["error_assimilate"] == False for obscfg in exp.observations] + + # create directory to run DART in archive_time = cluster.archivedir + time.strftime("/%Y-%m-%d_%H:%M/") - os.makedirs(cluster.dartrundir, exist_ok=True) # create directory to run DART in + os.makedirs(cluster.dartrundir, exist_ok=True) os.chdir(cluster.dartrundir) # link DART binaries to run_DART @@ -539,18 +550,20 @@ def main(time, prior_init_time, prior_valid_time, prior_path_exp): print("prepare prior ensemble") prepare_prior_ensemble(time, prior_init_time, prior_valid_time, prior_path_exp) - print(" 1) get observations with specified obs-error") + print(" get observations with specified obs-error") oso = get_obsseq_out(time) - print(" 2.1) evaluate prior for all observations (incl rejected)") - osf_prior = evaluate(time, output_format="%Y-%m-%d_%H:%M_obs_seq.final-eval_prior_allobs") + # is any observation error parametrized? + if any(error_is_parametrized) or do_QC: + print(" (optional) evaluate prior for all observations (incl rejected)") + evaluate(time, output_format="%Y-%m-%d_%H:%M_obs_seq.final-eval_prior_allobs") - print(" 2.2) assign observation-errors for assimilation ") - set_obserr_assimilate_in_obsseqout(oso, osf_prior, outfile=cluster.dartrundir + "/obs_seq.out") + print(" assign observation-errors for assimilation ") + set_obserr_assimilate_in_obsseqout(oso, outfile=cluster.dartrundir + "/obs_seq.out") - if getattr(exp, "reject_smallFGD", False): + if do_QC: print(" 2.3) reject observations? ") - qc_obs(time, oso, osf_prior) + qc_obs(time, oso) if exp.prior_inflation == 2: prepare_inflation_2(time, prior_init_time) @@ -563,12 +576,13 @@ def main(time, prior_init_time, prior_valid_time, prior_path_exp): if exp.prior_inflation == 2: archive_inflation_2(time) - print(" 4) evaluate posterior observations for all observations (incl rejected)") - write_list_of_inputfiles_posterior(time) - if getattr(exp, "reject_smallFGD", False): + if do_QC: + print(" 4) evaluate posterior observations for all observations (incl rejected)") + write_list_of_inputfiles_posterior(time) + copy(cluster.archivedir+'/obs_seq_out/'+time.strftime('%Y-%m-%d_%H:%M_obs_seq.out-beforeQC'), cluster.dartrundir+'/obs_seq.out') - evaluate(time, output_format="%Y-%m-%d_%H:%M_obs_seq.final-eval_posterior_allobs") + evaluate(time, output_format="%Y-%m-%d_%H:%M_obs_seq.final-eval_posterior_allobs") if __name__ == "__main__": diff --git a/templates/input.nml b/templates/input.nml index 38edbde9bd8e107176766e6fadeb563c7d3f3670..2c987da02a3247b5cd5fcbdefdbd36e50724992c 100644 --- a/templates/input.nml +++ b/templates/input.nml @@ -51,19 +51,19 @@ output_timestamps = .false., trace_execution = .false., - stages_to_write = 'preassim', 'postassim' + stages_to_write = 'preassim', 'postassim', 'output' output_members = .true. output_mean = .true. output_sd = .true. write_all_stages_at_end = .false. inf_flavor = <prior_inflation>, <post_inflation>, - inf_initial_from_restart = .true., .false., - inf_sd_initial_from_restart = .true., .false., - inf_initial = 1.00, 0.90, + inf_initial_from_restart = .false., .false., + inf_sd_initial_from_restart = .false., .false., + inf_initial = <inf_initial>, 0.90, inf_lower_bound = 1.0, 1.0, inf_upper_bound = 1000000.0, 1000000.0, - inf_damping = 0.9, 1.0, + inf_damping = 1.0, 1.0, inf_sd_initial = 0.6, 0.0, inf_sd_lower_bound = 0.6, 0.0, inf_sd_max_change = 1.05, 1.05,