diff --git a/src/swell/configuration/jedi/interfaces/geos_atmosphere/geos_atmosphere.yaml b/src/swell/configuration/jedi/interfaces/geos_atmosphere/geos_atmosphere.yaml index 2fc64df8e..9a1473bdb 100644 --- a/src/swell/configuration/jedi/interfaces/geos_atmosphere/geos_atmosphere.yaml +++ b/src/swell/configuration/jedi/interfaces/geos_atmosphere/geos_atmosphere.yaml @@ -11,3 +11,7 @@ executables: localensembleda: fv3jedi_letkf.x ensmeanvariance: fv3jedi_ensmeanvariance.x obsfilters: test_ObsFilters.x + eda3D: fv3jedi_var.x + eda4D: fv3jedi_var.x + edaControlPert: fv3jedi_controlpert.x + diffstates: fv3jedi_diffstates.x diff --git a/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/background_eda.py b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/background_eda.py new file mode 100644 index 000000000..71dd9d893 --- /dev/null +++ b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/background_eda.py @@ -0,0 +1,83 @@ +# (C) Copyright 2021- United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + +# -------------------------------------------------------------------------------------------------- + +from collections.abc import Mapping +from swell.configuration.jedi.interfaces.geos_atmosphere.model.shared import \ + field_io_names + +# -------------------------------------------------------------------------------------------------- + +state_variables = [ + 'eastward_wind', + 'northward_wind', + 'air_temperature', + 'air_pressure_at_surface', + 'air_pressure_levels', + 'water_vapor_mixing_ratio_wrt_moist_air', + 'cloud_liquid_ice', + 'cloud_liquid_water', + 'rain_water', + 'snow_water', + 'mole_fraction_of_ozone_in_air', + 'geopotential_height_times_gravity_at_surface', + 'initial_mass_fraction_of_large_scale_cloud_condensate', + 'initial_mass_fraction_of_convective_cloud_condensate', + 'convective_cloud_area_fraction', + 'fraction_of_ocean', + 'fraction_of_land', + 'isotropic_variance_of_filtered_topography', + 'surface_velocity_scale', + 'surface_buoyancy_scale', + 'planetary_boundary_layer_height', + 'surface_exchange_coefficient_for_momentum', + 'surface_exchange_coefficient_for_heat', + 'surface_exchange_coefficient_for_moisture', + 'KCBL_before_moist', + 'surface_temp_before_moist', + 'lower_index_where_Kh_greater_than_2', + 'upper_index_where_Kh_greater_than_2', + 'fraction_of_lake', + 'fraction_of_ice', + 'vtype', + 'stype', + 'vfrac', + 'sheleg', + 'skin_temperature_at_surface', + 'soilt', + 'soilm', + 'eastward_wind_at_surface', + 'northward_wind_at_surface', + # 'sea_surface_temperature', + # 'mole_fraction_of_carbon_dioxide_in_air', +] + +# -------------------------------------------------------------------------------------------------- + + +def background_eda(template_dict: Mapping) -> Mapping: + horizontal_resolution = template_dict['horizontal_resolution'] + imem = template_dict['ensemble_imember'] + + background = { + 'datetime': template_dict['local_background_time_iso'], + 'filetype': 'cube sphere history', + 'provider': 'geos', + 'compute edge pressure from surface pressure': True, + 'max allowable geometry difference': 1e-3, + 'datapath': template_dict['cycle_dir'], + 'filenames': [ + f'./ebkg/mem{imem:03d}/geos.mem{imem:03d}.%yyyy%mm%dd_%hh%MM%ssz.nc4', + f'./fv3-jedi/bkg/geos.crtmsrf.{horizontal_resolution}.nc4' + ], + 'state variables': state_variables, + 'field io names': field_io_names, + } + + return background + +# -------------------------------------------------------------------------------------------------- diff --git a/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/background_eda_control_pert.py b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/background_eda_control_pert.py new file mode 100644 index 000000000..8fe781366 --- /dev/null +++ b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/background_eda_control_pert.py @@ -0,0 +1,83 @@ +# (C) Copyright 2021- United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + +# -------------------------------------------------------------------------------------------------- + +from collections.abc import Mapping +from swell.configuration.jedi.interfaces.geos_atmosphere.model.shared import \ + field_io_names + +# -------------------------------------------------------------------------------------------------- + +state_variables = [ + 'eastward_wind', + 'northward_wind', + 'air_temperature', + 'air_pressure_at_surface', + 'air_pressure_levels', + 'water_vapor_mixing_ratio_wrt_moist_air', + 'cloud_liquid_ice', + 'cloud_liquid_water', + 'rain_water', + 'snow_water', + 'mole_fraction_of_ozone_in_air', + 'geopotential_height_times_gravity_at_surface', + 'initial_mass_fraction_of_large_scale_cloud_condensate', + 'initial_mass_fraction_of_convective_cloud_condensate', + 'convective_cloud_area_fraction', + 'fraction_of_ocean', + 'fraction_of_land', + 'isotropic_variance_of_filtered_topography', + 'surface_velocity_scale', + 'surface_buoyancy_scale', + 'planetary_boundary_layer_height', + 'surface_exchange_coefficient_for_momentum', + 'surface_exchange_coefficient_for_heat', + 'surface_exchange_coefficient_for_moisture', + 'KCBL_before_moist', + 'surface_temp_before_moist', + 'lower_index_where_Kh_greater_than_2', + 'upper_index_where_Kh_greater_than_2', + 'fraction_of_lake', + 'fraction_of_ice', + 'vtype', + 'stype', + 'vfrac', + 'sheleg', + 'skin_temperature_at_surface', + 'soilt', + 'soilm', + 'eastward_wind_at_surface', + 'northward_wind_at_surface', + # 'sea_surface_temperature', + # 'mole_fraction_of_carbon_dioxide_in_air', +] + +# -------------------------------------------------------------------------------------------------- + + +def background_eda_control_pert(template_dict: Mapping) -> Mapping: + horizontal_resolution = template_dict['horizontal_resolution'] + ichunk = template_dict['ensemble_ichunk'] + cycle_dir = template_dict['cycle_dir'] + background = { + 'datetime': template_dict['local_background_time_iso'], + 'filetype': 'cube sphere history', + 'provider': 'geos', + 'compute edge pressure from surface pressure': True, + 'max allowable geometry difference': 1e-3, + 'datapath': f'{cycle_dir}/ebkg_chunk/chunk{ichunk:03d}/geos.mem%mem_pad%/', + 'filenames': [ + f'%yyyy%mm%dd_%hh%MM%ssz.nc4', + f'../../../fv3-jedi/bkg/geos.crtmsrf.{horizontal_resolution}.nc4' + ], + 'state variables': state_variables, + 'field io names': field_io_names, + } + + return background + +# -------------------------------------------------------------------------------------------------- diff --git a/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/background_ensemble.py b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/background_ensemble.py index 9b904fb41..b4027016c 100644 --- a/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/background_ensemble.py +++ b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/background_ensemble.py @@ -17,7 +17,7 @@ def background_ensemble(template_dict: Mapping) -> Mapping: horizontal_resolution = template_dict['horizontal_resolution'] background_ensemble = { - 'date': template_dict['window_begin_iso'], + 'date': template_dict['local_background_time_iso'], 'members from template': { 'template': { 'datetime': template_dict['local_background_time_iso'], @@ -41,4 +41,7 @@ def background_ensemble(template_dict: Mapping) -> Mapping: return background_ensemble +# Note: 4D-window +# 'date': template_dict['window_begin_iso'], + # -------------------------------------------------------------------------------------------------- diff --git a/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/background_error_eda_gsiB.py b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/background_error_eda_gsiB.py new file mode 100644 index 000000000..6350dbedd --- /dev/null +++ b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/background_error_eda_gsiB.py @@ -0,0 +1,90 @@ +# (C) Copyright 2021- United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + +# -------------------------------------------------------------------------------------------------- + +from collections.abc import Mapping + +# -------------------------------------------------------------------------------------------------- + +state_variables_to_inverse = [ + 'eastward_wind', + 'northward_wind', + 'air_temperature', + 'air_pressure_at_surface', + 'air_pressure_levels', + 'water_vapor_mixing_ratio_wrt_moist_air', + 'cloud_liquid_ice', + 'cloud_liquid_water', + 'rain_water', + 'snow_water', + 'mole_fraction_of_ozone_in_air', + 'fraction_of_ocean', + 'fraction_of_lake', + 'fraction_of_ice', + 'geopotential_height_times_gravity_at_surface', + 'skin_temperature_at_surface', +] + +# -------------------------------------------------------------------------------------------------- + + +def background_error_eda_gsiB(template_dict: Mapping) -> Mapping: + vertical_resolution = template_dict['vertical_resolution'] + gsibec_nlons = template_dict['gsibec_nlons'] + gsibec_nlats = template_dict['gsibec_nlats'] + gsibec_configuration = template_dict['gsibec_configuration'] + gsibec_npx_proc = template_dict['gsibec_npx_proc'] + gsibec_npy_proc = template_dict['gsibec_npy_proc'] + + background_error = { + 'covariance model': 'SABER', + 'covariance type': 'gsi hybrid covariance', + 'saber central block': { + 'saber block name': 'gsi hybrid covariance', + 'read': { + 'gsi akbk': f'./fv3-jedi/fv3files/akbk{vertical_resolution}.nc4', + 'gsi error covariance file': + f'./fv3-jedi/gsibec/gsi-coeffs-gmao-global-l{vertical_resolution}x{gsibec_nlons}y{gsibec_nlats}.nc4', # noqa + 'gsi berror namelist file': + f'./fv3-jedi/gsibec/{gsibec_configuration}_l{vertical_resolution}x{gsibec_nlons}y{gsibec_nlats}.nml', # noqa + 'processor layout x direction': gsibec_npx_proc, + 'processor layout y direction': gsibec_npy_proc, + 'debugging mode': False + } + }, + 'saber outer blocks': [ + {'saber block name': 'interpolation', + 'inner geometry': { + 'function space': 'StructuredColumns', + 'custom grid matching gsi': { + 'type': 'latlon', + 'lats': int(gsibec_nlats), + 'lons': int(gsibec_nlons), + }, + 'custom partitioner matching gsi': { + 'bands': gsibec_npy_proc + }, + 'halo': 1, + }, + 'forward interpolator': { + 'local interpolator type': 'oops unstructured grid interpolator', + }, + 'inverse interpolator': { + 'local interpolator type': 'oops unstructured grid interpolator', + }, + 'state variables to inverse': state_variables_to_inverse} + ], + 'linear variable change': { + 'linear variable change name': 'Control2Analysis', + 'input variables': state_variables_to_inverse, + 'output variables': template_dict['analysis_variables'] + } + } + + return background_error + +# -------------------------------------------------------------------------------------------------- diff --git a/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/background_error_eda_hybridB.py b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/background_error_eda_hybridB.py new file mode 100644 index 000000000..e1c2acc68 --- /dev/null +++ b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/background_error_eda_hybridB.py @@ -0,0 +1,225 @@ +# (C) Copyright 2021- United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + +# -------------------------------------------------------------------------------------------------- +from ruamel.yaml import YAML +from jinja2 import Environment +from collections.abc import Mapping +from swell.configuration.jedi.interfaces.geos_atmosphere.model.shared import \ + field_io_names + +# -------------------------------------------------------------------------------------------------- +# Hybrid-B from rtodling: not tested + +state_variables_to_inverse = [ + 'eastward_wind', + 'northward_wind', + 'air_temperature', + 'air_pressure_at_surface', + 'water_vapor_mixing_ratio_wrt_moist_air', + 'cloud_liquid_ice', + 'cloud_liquid_water', + 'rain_water', + 'snow_water', + 'mole_fraction_of_ozone_in_air', + 'fraction_of_ocean', + 'fraction_of_lake', + 'fraction_of_ice', + 'geopotential_height_times_gravity_at_surface', + 'skin_temperature_at_surface' +] + +# -------------------------------------------------------------------------------------------------- + +background_error_template = """ +covariance model: hybrid +components: +- covariance: + covariance model: SABER + covariance type: gsi hybrid covariance + saber central block: + saber block name: gsi hybrid covariance + read: + gsi akbk: ./fv3-jedi/fv3files/akbk{{vertical_resolution}}.nc4 + gsi error covariance file: >- + ./fv3-jedi/gsibec/gsi-coeffs-gmao-global- + l{{vertical_resolution}}x{{gsibec_nlons}}y{{gsibec_nlats}}.nc4 + gsi berror namelist file: >- + ./fv3-jedi/gsibec/{{gsibec_configuration}}_ + l{{vertical_resolution}}x{{gsibec_nlons}}y{{gsibec_nlats}}.nml + processor layout x direction: {{gsibec_npx_proc}} + processor layout y direction: {{gsibec_npy_proc}} + debugging mode: false + saber outer blocks: + - saber block name: interpolation + inner geometry: + function space: StructuredColumns + custom grid matching gsi: + type: latlon + lats: {{gsibec_nlats}} + lons: {{gsibec_nlons}} + custom partitioner matching gsi: + bands: {{gsibec_npy_proc}} + halo: 1 + forward interpolator: + local interpolator type: oops unstructured grid interpolator + inverse interpolator: + local interpolator type: oops unstructured grid interpolator + state variables to inverse: &bvar + {%- for var in state_variables_to_inverse %} + - {{ var }} + {%- endfor %} + linear variable change: + linear variable change name: Control2Analysis + input variables: *bvar + weight: + value: 0.1 + +- covariance: + covariance model: ensemble + members from template: + template: + datetime: {{ local_background_time_iso }} + filetype: cube sphere history + provider: geos + state variables: *bvar + datapath: ./ebkg/mem%mem% + filename: geos.mem%mem%.%yyyy%mm%dd_%hh%MM%ssz.nc4 + max allowable geometry difference: 1.e-4 + pattern: '%mem%' + nmembers: {{ ensemble_num_members }} + zero padding: 3 + localization: + localization method: SABER + saber central block: + saber block name: BUMP_NICAS + active variables: *bvar + read: + general: + universe length-scale: 2500.0e3 + drivers: + multivariate strategy: duplicated + compute nicas: true + model: + level for 2d variables: last + nicas: + resolution: 6 + explicit length-scales: true + horizontal length-scale: + - groups: + - common + profile: + - 3350515.0 + - 3350515.0 + - 3350515.0 + - 3350515.0 + - 3350515.0 + - 3350515.0 + - 3350515.0 + - 3092783.0 + - 3092783.0 + - 3092783.0 + - 3092783.0 + - 2835052.0 + - 2835052.0 + - 2835052.0 + - 2577320.0 + - 2577320.0 + - 2577320.0 + - 2577320.0 + - 2577320.0 + - 2577320.0 + - 2577320.0 + - 2448454.0 + - 2319588.0 + - 2190722.0 + - 2190722.0 + - 2061856.0 + - 1804124.0 + - 1804124.0 + - 1804124.0 + - 1804124.0 + - 1804124.0 + - 1804124.0 + - 1804124.0 + - 1804124.0 + - 1804124.0 + - 1804124.0 + - 1804124.0 + - 1804124.0 + - 1675258.0 + - 1417526.0 + - 1288660.0 + - 1237113.0 + - 1185567.0 + - 1082474.0 + - 1030928.0 + - 1030928.0 + - 1030928.0 + - 1030928.0 + - 1030928.0 + - 1030928.0 + - 1030928.0 + - 1030928.0 + - 1030928.0 + - 1030928.0 + - 1030928.0 + - 1030928.0 + - 1030928.0 + - 1030928.0 + - 1030928.0 + - 1030928.0 + - 1030928.0 + - 1030928.0 + - 1030928.0 + - 1030928.0 + - 1030928.0 + - 1030928.0 + - 1030928.0 + - 1030928.0 + - 1030928.0 + - 2030928.0 + - 2030928.0 + - 2030928.0 + vertical length-scale: + - groups: + - common + value: 1.23 + linear variable change: + linear variable change name: Control2Analysis + input variables: *bvar + weight: + value: 0.9 +""" + + +def background_error_eda_hybridB(template_dict: Mapping) -> Mapping: + render_context = { + **template_dict, + 'state_variables_to_inverse': state_variables_to_inverse, + 'field_io_names': field_io_names + } + + # initialize Jinja and render the template + env = Environment() + template = env.from_string(background_error_template) + + # pass the combined dictionary here + rendered_yaml_string = template.render(**render_context) + # Use ruamel.yaml to load the string instead of standard pyyaml + ruamel_yaml = YAML() + background_error = ruamel_yaml.load(rendered_yaml_string) + cov_template = (background_error['components'][1]['covariance'] + ['members from template']['template']) + cov_template['field io names'] = field_io_names + a = background_error['components'][0]['covariance']['linear variable change'] + a['output variables'] = template_dict['analysis_variables'] + a = background_error['components'][1]['covariance']['linear variable change'] + a['output variables'] = template_dict['analysis_variables'] + + return background_error + +# -------------------------------------------------------------------------------------------------- diff --git a/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/comp_mean_output.py b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/comp_mean_output.py new file mode 100644 index 000000000..9ce9625ef --- /dev/null +++ b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/comp_mean_output.py @@ -0,0 +1,49 @@ +# (C) Copyright 2021- United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + +# -------------------------------------------------------------------------------------------------- + +from collections.abc import Mapping +from swell.configuration.jedi.interfaces.geos_atmosphere.model.shared import \ + field_io_names, field_io_names_ensemble + +# -------------------------------------------------------------------------------------------------- + + +def comp_mean_output(template_dict: Mapping) -> Mapping: + + prefix_output_mean = template_dict['ensmeanvariance_spec_item'].get('fn_output_mean') + state = template_dict['ensmeanvariance_spec_item'].get('state') + grid_type = template_dict['ensmeanvariance_spec_gridtype'] + + mean_output = {} + if grid_type == 'cs': + mean_output = { + 'filetype': 'cube sphere history', + 'provider': 'geos', + 'datapath': template_dict['cycle_dir'], + 'filename': f'{prefix_output_mean}.%yyyy%mm%dd_%hh%MM%ssz.nc4', + 'first': 'PT0H', + 'frequency': 'PT1H', + 'field io names': [] + } + elif grid_type == 'latlon': + mean_output = { + 'filetype': 'auxgrid', + 'gridtype': 'latlon', + 'datapath': template_dict['cycle_dir'], + 'filename': f'{prefix_output_mean}.ll.', + 'field io names': [] + } + + if state in ['bkg', 'analysis']: + mean_output['field io names'] = field_io_names + else: + mean_output['field io names'] = field_io_names_ensemble + + return mean_output + +# -------------------------------------------------------------------------------------------------- diff --git a/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/comp_variance_output.py b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/comp_variance_output.py new file mode 100644 index 000000000..4c359d11a --- /dev/null +++ b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/comp_variance_output.py @@ -0,0 +1,47 @@ +# (C) Copyright 2021- United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + +# -------------------------------------------------------------------------------------------------- + +from collections.abc import Mapping +from swell.configuration.jedi.interfaces.geos_atmosphere.model.shared import \ + field_io_names, field_io_names_ensemble + +# -------------------------------------------------------------------------------------------------- + + +def comp_variance_output(template_dict: Mapping) -> Mapping: + + prefix_output_variance = template_dict['ensmeanvariance_spec_item'].get('fn_output_variance') + state = template_dict['ensmeanvariance_spec_item'].get('state') + grid_type = template_dict['ensmeanvariance_spec_gridtype'] + + variance_output = {} + if grid_type == 'cs': + variance_output = { + 'filetype': 'cube sphere history', + 'provider': 'geos', + 'datapath': template_dict['cycle_dir'], + 'filename': f'{prefix_output_variance}.%yyyy%mm%dd_%hh%MM%ssz.nc4', + 'field io names': [] + } + elif grid_type == 'latlon': + variance_output = { + 'filetype': 'auxgrid', + 'gridtype': 'latlon', + 'datapath': template_dict['cycle_dir'], + 'filename': f'{prefix_output_variance}.ll.', + 'field io names': [] + } + + if state in ['bkg', 'analysis']: + variance_output['field io names'] = field_io_names + else: + variance_output['field io names'] = field_io_names_ensemble + + return variance_output + +# -------------------------------------------------------------------------------------------------- diff --git a/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/diffstates_output.py b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/diffstates_output.py new file mode 100644 index 000000000..403d3319b --- /dev/null +++ b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/diffstates_output.py @@ -0,0 +1,48 @@ +# (C) Copyright 2021- United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + +# -------------------------------------------------------------------------------------------------- + +from collections.abc import Mapping +from swell.configuration.jedi.interfaces.geos_atmosphere.model.shared import \ + field_io_names, field_io_names_sa1 + +# -------------------------------------------------------------------------------------------------- + + +def diffstates_output(template_dict: Mapping) -> Mapping: + + # diff states (inc) + grid_type = template_dict['diffstates_spec_gridtype'] + prefix_output = template_dict['diffstates_spec'].get('state_diff', {}).get('fn_output') + + stateType = template_dict.get('diffstates_spec_statetype') + if stateType == 'ensemble': + field_io_names_loc = field_io_names_sa1 + else: + field_io_names_loc = field_io_names + + output = {} + if grid_type == 'cs': + output = { + 'filetype': 'cube sphere history', + 'provider': 'geos', + 'datapath': template_dict['cycle_dir'], + 'filename': f'{prefix_output}.cs.%yyyy%mm%dd_%hh%MM%ssz.nc4', + 'field io names': field_io_names_loc + } + elif grid_type == 'latlon': + output = { + 'filetype': 'auxgrid', + 'gridtype': 'latlon', + 'datapath': template_dict['cycle_dir'], + 'filename': f'{prefix_output}.', + 'field io names': field_io_names_loc + } + + return output + +# -------------------------------------------------------------------------------------------------- diff --git a/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/ensemble_latlon_prior_mean_output.py b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/eda_analysis.py similarity index 61% rename from src/swell/configuration/jedi/interfaces/geos_atmosphere/model/ensemble_latlon_prior_mean_output.py rename to src/swell/configuration/jedi/interfaces/geos_atmosphere/model/eda_analysis.py index 38c1005be..924957ca9 100644 --- a/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/ensemble_latlon_prior_mean_output.py +++ b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/eda_analysis.py @@ -7,22 +7,24 @@ # -------------------------------------------------------------------------------------------------- from collections.abc import Mapping -from swell.configuration.jedi.interfaces.geos_atmosphere.model.shared import field_io_names_ensemble +from swell.configuration.jedi.interfaces.geos_atmosphere.model.shared import field_io_names # -------------------------------------------------------------------------------------------------- -def ensemble_latlon_prior_mean_output(template_dict: Mapping) -> Mapping: +def eda_analysis(template_dict: Mapping) -> Mapping: - ensemble_latlon_prior_mean_output = { - 'filetype': 'auxgrid', - 'gridtype': 'latlon', - 'datapath': template_dict['cycle_dir'], - 'filename': 'geos.prior.mean.', - 'field io names': field_io_names_ensemble + imem = template_dict.get('ensemble_imember', None) + analysis = { + 'filetype': 'cube sphere history', + 'provider': 'geos', + 'datapath': f'./analysis/mem{imem:03d}', + 'filename': f'eda.ana.mem{imem:03d}.%yyyy%mm%dd_%hh%MM%ssz.nc4', + 'first': 'PT0H', + 'frequency': 'PT1H', + 'field io names': field_io_names, } - return ensemble_latlon_prior_mean_output - + return analysis # -------------------------------------------------------------------------------------------------- diff --git a/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/ensemble_latlon_prior_variance_output.py b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/eda_analysis_control_pert.py similarity index 59% rename from src/swell/configuration/jedi/interfaces/geos_atmosphere/model/ensemble_latlon_prior_variance_output.py rename to src/swell/configuration/jedi/interfaces/geos_atmosphere/model/eda_analysis_control_pert.py index 75f1f77d8..c37718f20 100644 --- a/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/ensemble_latlon_prior_variance_output.py +++ b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/eda_analysis_control_pert.py @@ -7,22 +7,24 @@ # -------------------------------------------------------------------------------------------------- from collections.abc import Mapping -from swell.configuration.jedi.interfaces.geos_atmosphere.model.shared import field_io_names_ensemble +from swell.configuration.jedi.interfaces.geos_atmosphere.model.shared import field_io_names # -------------------------------------------------------------------------------------------------- -def ensemble_latlon_prior_variance_output(template_dict: Mapping) -> Mapping: +def eda_analysis_control_pert(template_dict: Mapping) -> Mapping: - ensemble_latlon_prior_variance_output = { - 'filetype': 'auxgrid', - 'gridtype': 'latlon', - 'datapath': template_dict['cycle_dir'], - 'filename': 'geos.prior.variance.', - 'field io names': field_io_names_ensemble + ichunk = template_dict.get('ensemble_ichunk', None) + analysis = { + 'filetype': 'cube sphere history', + 'provider': 'geos', + 'datapath': f'./analysis_chunk/chunk{ichunk:03d}/mem%mem_pad%', + 'filename': 'eda.ana.mem%mem_pad%.%yyyy%mm%dd_%hh%MM%ssz.nc4', + 'first': 'PT0H', + 'frequency': 'PT1H', + 'field io names': field_io_names, } - return ensemble_latlon_prior_variance_output - + return analysis # -------------------------------------------------------------------------------------------------- diff --git a/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/eda_varincrement1.py b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/eda_varincrement1.py new file mode 100644 index 000000000..d6f02a837 --- /dev/null +++ b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/eda_varincrement1.py @@ -0,0 +1,47 @@ +# (C) Copyright 2021- United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + +# -------------------------------------------------------------------------------------------------- + +from collections.abc import Mapping + +# -------------------------------------------------------------------------------------------------- + + +def eda_varincrement1(template_dict: Mapping) -> Mapping: + + experiment_id = template_dict['experiment_id'] + imem = template_dict.get('ensemble_imember', None) + varincrement1 = { + 'write increment': True, + 'increment': { + 'state component': { + 'filetype': 'auxgrid', + 'gridtype': 'latlon', + 'datapath': '.', + 'filename': f'analysis/mem{imem:03d}/{experiment_id}.inc_iter1.mem{imem:03d}.', + 'field io names': { + 'eastward_wind': 'ua', + 'northward_wind': 'va', + 'air_temperature': 't', + 'air_pressure_levels': 'pe', + 'water_vapor_mixing_ratio_wrt_moist_air': 'q', + 'cloud_liquid_ice': 'qi', + 'cloud_liquid_water': 'ql', + 'rain_water': 'qr', + 'snow_water': 'qs', + 'mole_fraction_of_ozone_in_air': 'o3ppmv', + 'geopotential_height_times_gravity_at_surface': 'phis', + 'skin_temperature_at_surface': 'ts', + 'air_pressure_at_surface': 'ps', + } + } + } + } + + return varincrement1 + +# -------------------------------------------------------------------------------------------------- diff --git a/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/ensemble_block.py b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/ensemble_block.py index bc6f365d1..96980149b 100644 --- a/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/ensemble_block.py +++ b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/ensemble_block.py @@ -13,6 +13,8 @@ def ensemble_block(template_dict: Mapping) -> Mapping: + fn_input = template_dict['ensmeanvariance_spec_item'].get('fn_input') + ensemble_block = { 'members from template': { 'template': { @@ -22,7 +24,7 @@ def ensemble_block(template_dict: Mapping) -> Mapping: 'compute edge pressure from surface pressure': True, 'max allowable geometry difference': 1e-3, 'datapath': template_dict['cycle_dir'], - 'filename': 'ebkg/mem%mem%/geos.mem%mem%.%yyyy%mm%dd_%hh%MM%ssz.nc4', + 'filename': fn_input, 'state variables': [ 'eastward_wind', 'northward_wind', diff --git a/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/ensemble_mean_increment_output.py b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/ensemble_mean_increment_output.py index 0c69152ce..c48884257 100644 --- a/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/ensemble_mean_increment_output.py +++ b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/ensemble_mean_increment_output.py @@ -7,7 +7,7 @@ # -------------------------------------------------------------------------------------------------- from collections.abc import Mapping -from swell.configuration.jedi.interfaces.geos_atmosphere.model.shared import field_io_names_ensemble +from swell.configuration.jedi.interfaces.geos_atmosphere.model.shared import field_io_names_sa1 # -------------------------------------------------------------------------------------------------- @@ -20,7 +20,7 @@ def ensemble_mean_increment_output(template_dict: Mapping) -> Mapping: 'filetype': 'auxgrid', 'gridtype': 'latlon', 'filename': f'{cycle_dir}/geos.mean-inc.', - 'field io names': field_io_names_ensemble + 'field io names': field_io_names_sa1 } return ensemble_mean_increment_output diff --git a/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/geometry_wofms.py b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/geometry_wofms.py new file mode 100644 index 000000000..3ca37c3f6 --- /dev/null +++ b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/geometry_wofms.py @@ -0,0 +1,33 @@ +# (C) Copyright 2021- United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + +# -------------------------------------------------------------------------------------------------- + +from collections.abc import Mapping + +# -------------------------------------------------------------------------------------------------- + + +# geometry wo 'fms initialization' segment +def geometry_wofms(template_dict: Mapping) -> Mapping: + + npx_proc = template_dict['npx_proc'] + npy_proc = template_dict['npy_proc'] + + horizontal_resolution = template_dict['horizontal_resolution'] + vertical_resolution = template_dict['vertical_resolution'] + + geometry = { + 'akbk': f'./fv3-jedi/fv3files/akbk{vertical_resolution}.nc4', + 'layout': [npx_proc, npy_proc], + 'npx': horizontal_resolution, + 'npy': horizontal_resolution, + 'npz': vertical_resolution + } + + return geometry + +# -------------------------------------------------------------------------------------------------- diff --git a/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/shared.py b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/shared.py index 4c32cf09c..e2e0335a5 100644 --- a/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/shared.py +++ b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/shared.py @@ -101,7 +101,77 @@ 'water_vapor_mixing_ratio_wrt_moist_air': 'q', 'cloud_liquid_ice': 'qi', 'cloud_liquid_water': 'ql', + 'rain_water': 'qr', + 'snow_water': 'qs', 'mole_fraction_of_ozone_in_air': 'o3ppmv', + 'geopotential_height_times_gravity_at_surface': 'phis', + 'fraction_of_ocean': 'frocean', + 'fraction_of_lake': 'frlake', + 'fraction_of_ice': 'frseaice', + 'skin_temperature_at_surface': 'ts', + 'eastward_wind_at_surface': 'u10m', + 'northward_wind_at_surface': 'v10m', + # 'sea_surface_temperature': 'ts_found', + # 'mole_fraction_of_carbon_dioxide_in_air': 'co2', +} + +# -------------------------------------------------------------------------------------------------- + +state_variables_ensemble = [ + 'eastward_wind', + 'northward_wind', + 'air_temperature', + 'air_pressure_at_surface', + 'air_pressure_levels', + 'water_vapor_mixing_ratio_wrt_moist_air', + 'cloud_liquid_ice', + 'cloud_liquid_water', + 'rain_water', + 'snow_water', + 'mole_fraction_of_ozone_in_air', + 'geopotential_height_times_gravity_at_surface', + 'fraction_of_ocean', + 'fraction_of_lake', + 'fraction_of_ice', + 'sheleg', + 'skin_temperature_at_surface', + 'soilt', + 'soilm', + 'eastward_wind_at_surface', + 'northward_wind_at_surface', + # 'sea_surface_temperature', + # 'mole_fraction_of_carbon_dioxide_in_air', + ] + +# -------------------------------------------------------------------------------------------------- + +# stand alone 1 +state_variables_sa1 = [ + 'eastward_wind', + 'northward_wind', + 'air_temperature', + 'air_pressure_at_surface', + 'air_pressure_levels', + 'water_vapor_mixing_ratio_wrt_moist_air', + 'cloud_liquid_ice', + 'cloud_liquid_water', + 'mole_fraction_of_ozone_in_air', + 'skin_temperature_at_surface', + ] + +# -------------------------------------------------------------------------------------------------- + +field_io_names_sa1 = { + 'eastward_wind': 'ua', + 'northward_wind': 'va', + 'air_temperature': 't', + 'air_pressure_at_surface': 'ps', + 'air_pressure_levels': 'pe', + 'water_vapor_mixing_ratio_wrt_moist_air': 'q', + 'cloud_liquid_ice': 'qi', + 'cloud_liquid_water': 'ql', + 'mole_fraction_of_ozone_in_air': 'o3ppmv', + 'skin_temperature_at_surface': 'ts', } # -------------------------------------------------------------------------------------------------- diff --git a/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/state_ensemble.py b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/state_ensemble.py new file mode 100644 index 000000000..608dd51f2 --- /dev/null +++ b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/state_ensemble.py @@ -0,0 +1,42 @@ +# (C) Copyright 2021- United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + +# -------------------------------------------------------------------------------------------------- + +from collections.abc import Mapping +from swell.configuration.jedi.interfaces.geos_atmosphere.model.shared import \ + state_variables, field_io_names + +# -------------------------------------------------------------------------------------------------- + + +def state_ensemble(template_dict: Mapping) -> Mapping: + + # full fname including subdir path + fn_input = template_dict['ensmeanvariance_spec_item'].get('fn_input') + + state_ensemble_dict = { + 'members from template': { + 'template': { + 'datetime': template_dict['local_background_time_iso'], + 'filetype': 'cube sphere history', + 'provider': 'geos', + 'compute edge pressure from surface pressure': True, + 'max allowable geometry difference': 1e-3, + 'datapath': template_dict['cycle_dir'], + 'filename': fn_input, + 'state variables': state_variables, + 'field io names': field_io_names + }, + 'pattern': '%mem%', + 'nmembers': template_dict['ensemble_num_members'], + 'zero padding': 3 + } + } + + return state_ensemble_dict + +# -------------------------------------------------------------------------------------------------- diff --git a/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/two_states.py b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/two_states.py new file mode 100644 index 000000000..95586185a --- /dev/null +++ b/src/swell/configuration/jedi/interfaces/geos_atmosphere/model/two_states.py @@ -0,0 +1,53 @@ +# (C) Copyright 2021- United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + +# -------------------------------------------------------------------------------------------------- + +from collections.abc import Mapping +from swell.configuration.jedi.interfaces.geos_atmosphere.model.shared import \ + state_variables, field_io_names, state_variables_sa1, field_io_names_sa1 + +# -------------------------------------------------------------------------------------------------- + + +def two_states(template_dict: Mapping) -> Mapping: + f1 = template_dict['diffstates_spec']['state1']['fn_input'] + f2 = template_dict['diffstates_spec']['state2']['fn_input'] + + stateType = template_dict.get('diffstates_spec_statetype') + if stateType == 'ensemble': + state_variables_loc = state_variables_sa1 + field_io_names_loc = field_io_names_sa1 + else: + state_variables_loc = state_variables + field_io_names_loc = field_io_names + + # state1: bkg + state1 = { + 'datetime': template_dict['local_background_time_iso'], + 'filetype': 'cube sphere history', + 'provider': 'geos', + 'datapath': template_dict['cycle_dir'], + 'filename': f1, + 'state variables': state_variables_loc, + 'field io names': field_io_names_loc, + } + + # state2: analysis + state2 = { + 'datetime': template_dict['local_background_time_iso'], + 'filetype': 'cube sphere history', + 'provider': 'geos', + 'datapath': template_dict['cycle_dir'], + 'filename': f2, + 'state variables': state_variables_loc, + 'field io names': field_io_names_loc, + } + + combo = {'state1': state1, 'state2': state2} + return combo + +# -------------------------------------------------------------------------------------------------- diff --git a/src/swell/configuration/jedi/interfaces/geos_atmosphere/task_questions.yaml b/src/swell/configuration/jedi/interfaces/geos_atmosphere/task_questions.yaml index 9eb9d343c..eccc84ec0 100644 --- a/src/swell/configuration/jedi/interfaces/geos_atmosphere/task_questions.yaml +++ b/src/swell/configuration/jedi/interfaces/geos_atmosphere/task_questions.yaml @@ -53,6 +53,9 @@ background_frequency: background_time_offset: default_value: PT9H +ebkg_time_offset: + default_value: PT3H + bufr_obs_classes: default_value: - disc_airs_bufr @@ -123,7 +126,11 @@ ensemble_hofx_strategy: - parallel ensemble_num_members: - default_value: 10 + default_value: 16 + options: None + +ensemble_num_chunks: + default_value: 8 options: None geovals_experiment: @@ -209,6 +216,7 @@ minimizer: default_value: DRPCG options: - DRPCG + - DRPLanczos ncdiag_experiments: default_value: x0050_fgat diff --git a/src/swell/configuration/jedi/oops/diffstates.py b/src/swell/configuration/jedi/oops/diffstates.py new file mode 100644 index 000000000..22efc092b --- /dev/null +++ b/src/swell/configuration/jedi/oops/diffstates.py @@ -0,0 +1,29 @@ +# (C) Copyright 2021- United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + +# -------------------------------------------------------------------------------------------------- + +from swell.utilities.oops_config import OopsConfig + +# -------------------------------------------------------------------------------------------------- + + +class diffstates(OopsConfig): + + def render_oops(self): + + two_states = self.interface_model('two_states') + oops = { + 'state geometry': self.interface_model('geometry'), + 'increment geometry': self.interface_model('geometry_wofms'), + **two_states, + 'output': self.interface_model('diffstates_output') + } + + return oops + + +# -------------------------------------------------------------------------------------------------- diff --git a/src/swell/configuration/jedi/oops/eda3D.py b/src/swell/configuration/jedi/oops/eda3D.py new file mode 100644 index 000000000..e87d0a57f --- /dev/null +++ b/src/swell/configuration/jedi/oops/eda3D.py @@ -0,0 +1,73 @@ +# (C) Copyright 2021- United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + +# -------------------------------------------------------------------------------------------------- + +from swell.utilities.oops_config import OopsConfig + +# -------------------------------------------------------------------------------------------------- + + +class eda3D(OopsConfig): + + def render_oops(self): + assert self.template_dict['ensemble_imember'] is not None, ( + f'ensemble_imember is None and eda3D.py fails' + ) + oops = { + 'cost function': { + 'cost type': '3D-Var', + 'jb evaluation': False, + 'time window': { + 'begin': self.template_dict['window_begin_iso'], + 'end': self.template_dict['window_end_iso'], + 'bound to include': 'begin' + }, + 'geometry': self.interface_model('geometry'), + 'analysis variables': self.template_dict['analysis_variables'], + 'background': self.interface_model('background_eda'), + # GSI:B + 'background error': self.interface_model('background_error_eda_gsiB'), + # + # Hybrid-B: not tested + # 'background error': self.interface_model('background_error_eda_hybridB'), + 'observations': { + 'obs perturbations': False, + 'get values': self.interface_model('getvalues'), + 'observers': self.special_observations(), + } + }, + 'variational': { + 'minimizer': { + 'algorithm': self.template_dict['minimizer'] + }, + 'iterations': [{ + 'geometry': self.interface_model('geometry_inner'), + 'gradient norm reduction': float(self.template_dict['gradient_norm_reduction']), + 'ninner': self.template_dict['number_of_iterations'], + 'diagnostics': { + 'departures': 'ombg' + }, + 'online diagnostics': self.interface_model('eda_varincrement1') + }], + }, + 'final': { + 'diagnostics': { + 'departures': 'oman' + }, + 'prints': { + 'frequency': 'PT3H' + } + }, + 'output': self.interface_model('eda_analysis') + } + + # TODO: Implement this more cleanly in the OOPS schema + if self.jedi_interface == 'geos_cf': + oops['final']['increment'] = {'geometry': self.interface_model('geometry'), + 'output': self.interface_model('increment_cs')} + + return oops diff --git a/src/swell/configuration/jedi/oops/eda_control_pert.py b/src/swell/configuration/jedi/oops/eda_control_pert.py new file mode 100644 index 000000000..5834aaa0d --- /dev/null +++ b/src/swell/configuration/jedi/oops/eda_control_pert.py @@ -0,0 +1,81 @@ +# (C) Copyright 2021- United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + +# -------------------------------------------------------------------------------------------------- + +from swell.utilities.oops_config import OopsConfig + +# -------------------------------------------------------------------------------------------------- + + +class eda_control_pert(OopsConfig): + + def render_oops(self): + nmember = self.template_dict['ensemble_num_members'] + nchunk = self.template_dict['ensemble_num_chunks'] + ichunk = self.template_dict['ensemble_ichunk'] + nstate = int(nmember/nchunk) + if ichunk == 1: + num_pert_mem = nstate - 1 + else: + num_pert_mem = nstate + pert_start_index = 1 + + oops = { + 'assimilation': { + 'cost function': { + 'cost type': '3D-Var', + 'jb evaluation': False, + 'time window': { + 'begin': self.template_dict['window_begin_iso'], + 'length': self.template_dict['window_length'], + 'bound to include': 'begin' + }, + 'geometry': self.interface_model('geometry'), + 'analysis variables': self.template_dict['analysis_variables'], + 'background': self.interface_model('background_eda_control_pert'), + 'background error': self.interface_model('background_error_eda_gsiB'), + 'observations': { + 'get values': self.interface_model('getvalues'), + 'observers': self.special_observations(), + } + }, + 'variational': { + 'minimizer': { + 'algorithm': self.template_dict['minimizer'] + }, + 'iterations': [{ + 'geometry': self.interface_model('geometry_inner'), + 'gradient norm reduction': float( + self.template_dict['gradient_norm_reduction']), + 'ninner': self.template_dict['number_of_iterations'], + }], + }, + 'final': { + 'diagnostics': { + 'departures': 'oman' + }, + 'prints': { + 'frequency': 'PT3H' + } + }, + 'output': self.interface_model('eda_analysis_control_pert') + }, + 'template': { + 'pattern with zero padding': "%mem_pad%", + 'pattern without zero padding': "%mem_wo_pad%", + 'number of pert members': num_pert_mem, + 'first pert member index': pert_start_index, + 'run pert members only': False + } + } + + # TODO: Implement this more cleanly in the OOPS schema + if self.jedi_interface == 'geos_cf': + oops['final']['increment'] = {'geometry': self.interface_model('geometry'), + 'output': self.interface_model('increment_cs')} + + return oops diff --git a/src/swell/configuration/jedi/oops/ensmeanvariance.py b/src/swell/configuration/jedi/oops/ensmeanvariance.py index 5e12051a4..b2272ad2f 100644 --- a/src/swell/configuration/jedi/oops/ensmeanvariance.py +++ b/src/swell/configuration/jedi/oops/ensmeanvariance.py @@ -18,8 +18,12 @@ def render_oops(self): oops = { 'geometry': self.interface_model('geometry'), 'ensemble': self.interface_model('ensemble_block'), - 'variance output': self.interface_model('ensemble_latlon_prior_variance_output'), - 'mean output': self.interface_model('ensemble_latlon_prior_mean_output') + # + # full state variables do not work in ensemble + # 'ensemble': self.interface_model('state_ensemble'), + # + 'variance output': self.interface_model('comp_variance_output'), + 'mean output': self.interface_model('comp_mean_output') } return oops diff --git a/src/swell/deployment/create_experiment.py b/src/swell/deployment/create_experiment.py index 101849248..a09fcedbc 100644 --- a/src/swell/deployment/create_experiment.py +++ b/src/swell/deployment/create_experiment.py @@ -580,6 +580,8 @@ def prepare_cylc_suite_jinja2( render_dictionary['scheduling']['RunGeos']['execution_time_limit'] = 'PT30M' render_dictionary['scheduling']['RunJediLocalEnsembleDaExecutable'][ 'execution_time_limit'] = 'PT1H' + render_dictionary['scheduling']['RunJediEdaControlPertExecutable'][ + 'execution_time_limit'] = 'PT10M' render_dictionary['scheduling']['EvaObservations'][ 'execution_time_limit'] = 'PT30M' diff --git a/src/swell/suites/eda_atmos/eva/increment-geos_atmosphere.yaml b/src/swell/suites/eda_atmos/eva/increment-geos_atmosphere.yaml new file mode 100644 index 000000000..126435876 --- /dev/null +++ b/src/swell/suites/eda_atmos/eva/increment-geos_atmosphere.yaml @@ -0,0 +1,760 @@ +datasets: + +- group: increment + type: LatLon + filename: {{increment_file_path}} + name: experiment_increment + variables: [ps, ts, ua, va, t, q, lat, lon] + +graphics: + + plotting_backend: Emcpy + figure_list: + + #map plot for surface pressure increment + - batch figure: + variables: [ps] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Surface Pressure Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::ps + label: PS increment + colorbar: true + cmap: 'bwr' + vmin: -100 + vmax: 100 + - batch figure: + variables: [ts] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Tskin Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::ts + label: TS increment + colorbar: true + cmap: 'bwr' + vmin: -2 + vmax: 2 + #map plot for temperature increment (lowest level) + - batch figure: + variables: [t] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_1000.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Temperature Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::t + slices: '[71,...]' + label: T increment (1000 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for temperature increment + - batch figure: + variables: [t] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_850.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Temperature Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::t + slices: '[62,...]' + label: T increment (850 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for temperature increment + - batch figure: + variables: [t] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_500.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Temperature Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::t + slices: '[49,...]' + label: T increment (500 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for temperature increment + - batch figure: + variables: [t] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_200.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Temperature Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::t + slices: '[42,...]' + label: T increment (200 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for temperature increment + - batch figure: + variables: [t] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_10.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Temperature Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::t + slices: '[24,...]' + label: T increment (10 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for temperature increment + - batch figure: + variables: [t] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_1.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Temperature Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::t + slices: '[14,...]' + label: T increment (1 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Zonal Wind increment (lowest level) + - batch figure: + variables: [ua] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_1000.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Zonal Wind Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::ua + slices: '[71,...]' + label: U increment (1000 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Zonal Wind increment + - batch figure: + variables: [ua] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_850.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Zonal Wind Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::ua + slices: '[62,...]' + label: U increment (850 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Zonal Wind increment + - batch figure: + variables: [ua] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_500.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Zonal Wind Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::ua + slices: '[49,...]' + label: U increment (500 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Zonal Wind increment + - batch figure: + variables: [ua] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_200.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Zonal Wind Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::ua + slices: '[42,...]' + label: U increment (200 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Zonal Wind increment + - batch figure: + variables: [ua] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_10.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Zonal Wind Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::ua + slices: '[24,...]' + label: U increment (10 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Zonal Wind increment + - batch figure: + variables: [ua] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_1.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Zonal Wind Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::ua + slices: '[14,...]' + label: U increment (1 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Meridional Wind increment (lowest level) + - batch figure: + variables: [va] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_1000.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Meridional Wind Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::va + slices: '[71,...]' + label: V increment (1000 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Meridional Wind increment + - batch figure: + variables: [va] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_850.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Meridional Wind Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::va + slices: '[62,...]' + label: V increment (850 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Meridional Wind increment + - batch figure: + variables: [va] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_500.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Meridional Wind Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::va + slices: '[49,...]' + label: V increment (500 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Meridional Wind increment + - batch figure: + variables: [va] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_200.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Meridional Wind Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::va + slices: '[42,...]' + label: V increment (200 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Meridional Wind increment + - batch figure: + variables: [va] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_10.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Meridional Wind Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::va + slices: '[24,...]' + label: V increment (10 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Meridional Wind increment + - batch figure: + variables: [va] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_1.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Meridional Wind Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::va + slices: '[14,...]' + label: V increment (1 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Specific Humidity increment (lowest level) + - batch figure: + variables: [q] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_1000.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Specific Humidity Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::q + slices: '[71,...]' + label: Q increment (1000 hPa) + colorbar: true + cmap: 'bwr' + vmin: -0.001 + vmax: 0.001 + #map plot for Specific Humidity increment + - batch figure: + variables: [q] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_850.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Specific Humidity Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::q + slices: '[62,...]' + label: Q increment (850 hPa) + colorbar: true + cmap: 'bwr' + vmin: -0.001 + vmax: 0.001 + #map plot for Specific Humidity increment + - batch figure: + variables: [q] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_500.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Specific Humidity Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::q + slices: '[49,...]' + label: Q increment (500 hPa) + colorbar: true + cmap: 'bwr' + vmin: -0.001 + vmax: 0.001 + #map plot for Specific Humidity increment + - batch figure: + variables: [q] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_200.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Specific Humidity Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::q + slices: '[42,...]' + label: Q increment (200 hPa) + colorbar: true + cmap: 'bwr' + vmin: -0.001 + vmax: 0.001 + #map plot for Specific Humidity increment + - batch figure: + variables: [q] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_10.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Specific Humidity Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::q + slices: '[24,...]' + label: Q increment (10 hPa) + colorbar: true + cmap: 'bwr' + vmin: -0.001 + vmax: 0.001 diff --git a/src/swell/suites/eda_atmos/eva/jedi_log-geos_atmosphere.yaml b/src/swell/suites/eda_atmos/eva/jedi_log-geos_atmosphere.yaml new file mode 100644 index 000000000..2d848d549 --- /dev/null +++ b/src/swell/suites/eda_atmos/eva/jedi_log-geos_atmosphere.yaml @@ -0,0 +1,89 @@ +datasets: + +- type: JediLog + collection_name: JediLogTest + jedi_log_to_parse: '{{cycle_dir}}/jedi_variational_log.log' + data_to_parse: + convergence: true + +transforms: +- transform: arithmetic + new name: JediLogTest::convergence::${variable}_log + equals: log(JediLogTest::convergence::${variable}) + for: + variable: [residual_norm, norm_reduction] + +graphics: + + plotting_backend: Emcpy + figure_list: + + - figure: + layout: [3,1] + figure size: [12,10] + title: 'Residual Norm and Norm Reduction Plots' + output name: '{{cycle_dir}}/eva/jedi_log/convergence/residual_norm_reduction.png' + plots: + - add_xlabel: 'Total inner iteration number' + add_ylabel: 'Residual norm' + layers: + - type: LinePlot + x: + variable: JediLogTest::convergence::total_iteration + y: + variable: JediLogTest::convergence::residual_norm + color: 'black' + + - add_xlabel: 'Total inner iteration number' + add_ylabel: 'Log(norm reduction)' + layers: + - type: LinePlot + x: + variable: JediLogTest::convergence::total_iteration + y: + variable: JediLogTest::convergence::norm_reduction + color: 'black' + + - add_xlabel: 'Total inner iteration number' + add_ylabel: 'Log(reduction)' + add_legend: + layers: + - type: LinePlot + x: + variable: JediLogTest::convergence::total_iteration + y: + variable: JediLogTest::convergence::residual_norm_log + color: 'red' + label: 'Log(residual norm)' + - type: LinePlot + x: + variable: JediLogTest::convergence::total_iteration + y: + variable: JediLogTest::convergence::norm_reduction_log + color: 'blue' + label: 'Log norm reduction' + + - figure: + title: 'Cost Function Plot' + output name: '{{cycle_dir}}/eva/jedi_log/cost_function/cost_function.png' + plots: + - add_xlabel: 'Total inner iteration number' + add_ylabel: 'Quadratic Cost Function' + add_legend: + layers: + - type: LinePlot + label: 'jojc' + x: + variable: JediLogTest::convergence::total_iteration + y: + variable: JediLogTest::convergence::jojc + color: 'blue' + markersize: 2 + - type: LinePlot + label: 'jb' + x: + variable: JediLogTest::convergence::total_iteration + y: + variable: JediLogTest::convergence::jb + color: 'red' + markersize: 2 diff --git a/src/swell/suites/eda_atmos/eva/observations-geos_atmosphere.yaml b/src/swell/suites/eda_atmos/eva/observations-geos_atmosphere.yaml new file mode 100644 index 000000000..edee73160 --- /dev/null +++ b/src/swell/suites/eda_atmos/eva/observations-geos_atmosphere.yaml @@ -0,0 +1,564 @@ +datasets: + +- name: experiment + type: IodaObsSpace + filenames: + - {{obs_path_file}} + channels: &channels {{channels}} + groups: + - name: ObsValue + variables: &variables {{simulated_variables}} + - name: GsiHofXBc + #- name: GsiEffectiveQC + - name: hofx0 + - name: hofx1 + - name: ombg + - name: oman + - name: EffectiveQC0 + - name: EffectiveQC1 + - name: MetaData + +transforms: + +# Generate hofx0 for GSI +- transform: arithmetic + new name: experiment::ObsValueMinusGsiHofXBc::${variable} + equals: experiment::ObsValue::${variable}-experiment::GsiHofXBc::${variable} + for: + variable: *variables + +# Generate hofx0 for JEDI +- transform: arithmetic + new name: experiment::ObsValueMinusHofx0::${variable} + equals: experiment::ObsValue::${variable}-experiment::hofx0::${variable} + for: + variable: *variables + +# Generate hofx difference +- transform: arithmetic + new name: experiment::Hofx0MinusGsiHofXBc::${variable} + equals: experiment::hofx0::${variable}-experiment::GsiHofXBc::${variable} + for: + variable: *variables + +# Generate hofx that passed QC for JEDI +- transform: accept where + new name: experiment::hofx0PassedQc::${variable} + starting field: experiment::hofx0::${variable} + where: + - experiment::EffectiveQC0::${variable} == 0 + for: + variable: *variables + +# Generate GSI hofx that passed JEDI QC +- transform: accept where + new name: experiment::GsiHofXBcPassedQc::${variable} + starting field: experiment::GsiHofXBc::${variable} + where: + - experiment::EffectiveQC0::${variable} == 0 + for: + variable: *variables + +# Generate hofx0 that passed QC for JEDI +- transform: accept where + new name: experiment::ObsValueMinushofx0PassedQc::${variable} + starting field: experiment::ObsValueMinusHofx0::${variable} + where: + - experiment::EffectiveQC0::${variable} == 0 + for: + variable: *variables + +# Generate hofx0 that passed QC for GSI +- transform: accept where + new name: experiment::ObsValueMinusGsiHofXBcPassedQc::${variable} + starting field: experiment::ObsValueMinusGsiHofXBc::${variable} + where: + - experiment::EffectiveQC0::${variable} == 0 + for: + variable: *variables + +# Generate ombg that passed QC for JEDI +- transform: accept where + new name: experiment::ombgPassedQc::${variable} + starting field: experiment::ombg::${variable} + where: + - experiment::EffectiveQC0::${variable} == 0 + for: + variable: *variables + +# Generate oman that passed QC for JEDI +- transform: accept where + new name: experiment::omanPassedQc::${variable} + starting field: experiment::oman::${variable} + where: + - experiment::EffectiveQC1::${variable} == 0 + for: + variable: *variables + +# Generate obs contribution to analysis (OmA*OmA)-(OmB*OmB) +- transform: arithmetic + new name: experiment::ResidualRMSdiff::${variable} + equals: (experiment::omanPassedQc::${variable})*(experiment::omanPassedQc::${variable})-(experiment::ombgPassedQc::${variable})*(experiment::ombgPassedQc::${variable}) + for: + variable: *variables + +graphics: + + plotting_backend: Emcpy + figure_list: + + # Correlation scatter plots + # ------------------------- + + # JEDI h(x) vs Observations + - batch figure: + variables: *variables + channels: *channels + figure: + layout: [1,1] + title: 'Observations vs. JEDI h(x) | {{instrument_title}} | ${variable_title}' + output name: '{{cycle_dir}}/eva/{{instrument}}/correlation_scatter/${variable}${channel}/jedi_hofx0_vs_obs_{{instrument}}_${variable}${channel}.png' + plots: + - add_xlabel: 'Observation Value' + add_ylabel: 'JEDI h(x)' + add_grid: + add_legend: + loc: 'upper left' + layers: + - type: Scatter + x: + variable: experiment::ObsValue::${variable} + y: + variable: experiment::hofx0::${variable} + channel: ${channel} + markersize: 5 + color: 'black' + label: 'JEDI h(x) versus obs (all obs)' + - type: Scatter + x: + variable: experiment::ObsValue::${variable} + y: + variable: experiment::hofx0PassedQc::${variable} + channel: ${channel} + markersize: 5 + color: 'red' + label: 'JEDI h(x) versus obs (passed QC in JEDI)' + + # GSI h(x) vs Observations + - batch figure: + variables: *variables + channels: *channels + figure: + layout: [1,1] + title: 'Observations vs. GSI h(x) | {{instrument_title}} | ${variable_title}' + output name: '{{cycle_dir}}/eva/{{instrument}}/correlation_scatter/${variable}${channel}/gsi_hofx0_vs_obs_{{instrument}}_${variable}${channel}.png' + plots: + - add_xlabel: 'Observation Value' + add_ylabel: 'GSI h(x)' + add_grid: + add_legend: + loc: 'upper left' + layers: + - type: Scatter + x: + variable: experiment::ObsValue::${variable} + y: + variable: experiment::GsiHofXBc::${variable} + channel: ${channel} + markersize: 5 + color: 'black' + label: 'GSI h(x) versus obs (all obs)' + - type: Scatter + x: + variable: experiment::ObsValue::${variable} + y: + variable: experiment::GsiHofXBcPassedQc::${variable} + channel: ${channel} + markersize: 5 + color: 'red' + label: 'GSI h(x) versus obs (passed QC in JEDI)' + + # JEDI h(x) vs GSI h(x) + - batch figure: + variables: *variables + channels: *channels + figure: + layout: [1,1] + title: 'JEDI h(x) vs. GSI h(x) | {{instrument_title}} | ${variable_title}' + output name: '{{cycle_dir}}/eva/{{instrument}}/correlation_scatter/${variable}${channel}/gsi_hofx_vs_jedi0_hofx_{{instrument}}_${variable}${channel}.png' + plots: + - add_xlabel: 'GSI h(x)' + add_ylabel: 'JEDI h(x)' + add_grid: + add_legend: + loc: 'upper left' + layers: + - type: Scatter + x: + variable: experiment::GsiHofXBc::${variable} + y: + variable: experiment::hofx0::${variable} + channel: ${channel} + markersize: 5 + color: 'black' + label: 'JEDI h(x) versus GSI h(x)' + - type: Scatter + x: + variable: experiment::GsiHofXBcPassedQc::${variable} + y: + variable: experiment::hofx0PassedQc::${variable} + channel: ${channel} + markersize: 5 + color: 'red' + label: 'JEDI h(x) versus GSI h(x) (passed QC in JEDI)' + + # JEDI hofx0 vs GSI hofx0 + - batch figure: + variables: *variables + channels: *channels + figure: + layout: [1,1] + title: 'JEDI hofx0 vs. GSI hofx0 | {{instrument_title}} | ${variable_title}' + output name: '{{cycle_dir}}/eva/{{instrument}}/correlation_scatter/${variable}${channel}/gsi_hofx0_vs_jedi_hofx0_{{instrument}}_${variable}${channel}.png' + plots: + - add_xlabel: 'GSI observation minus h(x)' + add_ylabel: 'JEDI observation minus h(x)' + add_grid: + add_legend: + loc: 'upper left' + layers: + - type: Scatter + x: + variable: experiment::ObsValueMinusGsiHofXBc::${variable} + y: + variable: experiment::ObsValueMinusHofx0::${variable} + channel: ${channel} + markersize: 5 + color: 'black' + label: 'GSI hofx0 vs JEDI hofx0 (all obs)' + - type: Scatter + x: + variable: experiment::ObsValueMinusGsiHofXBcPassedQc::${variable} + y: + variable: experiment::ObsValueMinushofx0PassedQc::${variable} + channel: ${channel} + markersize: 5 + color: 'red' + label: 'GSI hofx0 vs JEDI hofx0 (passed QC in JEDI)' + + # JEDI oma vs omb + - batch figure: + variables: *variables + channels: *channels + figure: + layout: [1,1] + title: 'OmA vs. OmB | {{instrument_title}} | ${variable_title}' + output name: '{{cycle_dir}}/eva/{{instrument}}/correlation_scatter/${variable}${channel}/jedi_oma_vs_omb_{{instrument}}_${variable}${channel}.png' + plots: + - add_xlabel: 'OmA' + add_ylabel: 'OmB' + add_grid: + add_legend: + loc: 'upper left' + layers: + - type: Scatter + x: + variable: experiment::oman::${variable} + y: + variable: experiment::ombg::${variable} + channel: ${channel} + markersize: 5 + color: 'black' + label: 'OmA versus OmB (all residuals)' + - type: Scatter + x: + variable: experiment::omanPassedQc::${variable} + y: + variable: experiment::ombgPassedQc::${variable} + channel: ${channel} + markersize: 5 + color: 'red' + label: 'OmA versus OmB (passed QC)' + +# Map plots# --------- + + # Observations + - batch figure: + variables: *variables + channels: *channels + dynamic options: + - type: vminvmaxcmap + channel: ${channel} + data variable: experiment::ObsValue::${variable} + figure: + figure size: [20,10] + layout: [1,1] + title: 'Observations | {{instrument_title}} | Obs Value' + output name: '{{cycle_dir}}/eva/{{instrument}}/map_plots/${variable}${channel}/observations_{{instrument}}_${variable}${channel}.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: ObsValue + add_grid: + layers: + - type: MapScatter + longitude: + variable: experiment::MetaData::longitude + latitude: + variable: experiment::MetaData::latitude + data: + variable: experiment::ObsValue::${variable} + channel: ${channel} + markersize: 2 + label: ObsValue + colorbar: true + cmap: ${dynamic_cmap} + vmin: ${dynamic_vmin} + vmax: ${dynamic_vmax} + + # hofx0 jedi + - batch figure: + variables: *variables + channels: *channels + dynamic options: + - type: vminvmaxcmap + channel: ${channel} + data variable: experiment::ObsValueMinusHofx0::${variable} + figure: + figure size: [20,10] + layout: [1,1] + title: 'JEDI hofx0 | {{instrument_title}} | ${variable_title}' + output name: '{{cycle_dir}}/eva/{{instrument}}/map_plots/${variable}${channel}/hofx0_jedi_{{instrument}}_${variable}${channel}.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: '${variable}' + add_grid: + layers: + - type: MapScatter + longitude: + variable: experiment::MetaData::longitude + latitude: + variable: experiment::MetaData::latitude + data: + variable: experiment::ObsValueMinusHofx0::${variable} + channel: ${channel} + markersize: 2 + label: '${variable}' + colorbar: true + cmap: ${dynamic_cmap} + vmin: ${dynamic_vmin} + vmax: ${dynamic_vmax} + + # hofx0 gsi + - batch figure: + variables: *variables + channels: *channels + dynamic options: + - type: vminvmaxcmap + channel: ${channel} + data variable: experiment::ObsValueMinusGsiHofXBc::${variable} + figure: + figure size: [20,10] + layout: [1,1] + title: 'GSI hofx0 | {{instrument_title}} | ${variable_title}' + output name: '{{cycle_dir}}/eva/{{instrument}}/map_plots/${variable}${channel}/hofx0_gsi_{{instrument}}_${variable}${channel}.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: '${variable}' + add_grid: + layers: + - type: MapScatter + longitude: + variable: experiment::MetaData::longitude + latitude: + variable: experiment::MetaData::latitude + data: + variable: experiment::ObsValueMinusGsiHofXBc::${variable} + channel: ${channel} + markersize: 2 + label: '${variable}' + colorbar: true + cmap: ${dynamic_cmap} + vmin: ${dynamic_vmin} + vmax: ${dynamic_vmax} + + # hofx difference + - batch figure: + variables: *variables + channels: *channels + dynamic options: + - type: vminvmaxcmap + channel: ${channel} + data variable: experiment::Hofx0MinusGsiHofXBc::${variable} + figure: + figure size: [20,10] + layout: [1,1] + title: 'Hofx0 Difference | {{instrument_title}} | ${variable_title}' + output name: '{{cycle_dir}}/eva/{{instrument}}/map_plots/${variable}${channel}/hofx0_difference_{{instrument}}_${variable}${channel}.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: '${variable}' + add_grid: + layers: + - type: MapScatter + longitude: + variable: experiment::MetaData::longitude + latitude: + variable: experiment::MetaData::latitude + data: + variable: experiment::Hofx0MinusGsiHofXBc::${variable} + channel: ${channel} + markersize: 2 + label: '${variable}' + colorbar: true + cmap: ${dynamic_cmap} + vmin: ${dynamic_vmin} + vmax: ${dynamic_vmax} + + + # RMS(oma)-RMS(omb) difference + - batch figure: + variables: *variables + channels: *channels + dynamic options: + - type: vminvmaxcmap + channel: ${channel} + data variable: experiment::ResidualRMSdiff::${variable} + figure: + figure size: [20,10] + layout: [1,1] + title: 'RMS Residual Difference | {{instrument_title}} | ${variable_title}' + output name: '{{cycle_dir}}/eva/{{instrument}}/map_plots/${variable}${channel}/rmsres_difference_{{instrument}}_${variable}${channel}.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: '${variable}' + add_grid: + layers: + - type: MapScatter + longitude: + variable: experiment::MetaData::longitude + latitude: + variable: experiment::MetaData::latitude + data: + variable: experiment::ResidualRMSdiff::${variable} + channel: ${channel} + markersize: 2 + label: '${variable}' + colorbar: true + cmap: ${dynamic_cmap} + vmin: ${dynamic_vmin} + vmax: ${dynamic_vmax} +# Histogram plots# --------------- + + # hofx0 vs hofx0 + - batch figure: + variables: *variables + channels: *channels + dynamic options: + - type: histogram_bins + channel: ${channel} + number of bins rule: sturges + data variable: experiment::ObsValueMinusHofx0::${variable} + figure: + layout: [1,1] + title: 'JEDI hofx0 vs. GSI hofx0 | {{instrument_title}} | ${variable_title}' + output name: '{{cycle_dir}}/eva/{{instrument}}/histograms/${variable}${channel}/gsi_hofx0_vs_jedi_hofx0_{{instrument}}_${variable}${channel}.png' + plots: + - add_xlabel: 'Observation minus h(x)' + add_ylabel: 'Count' + add_legend: + loc: 'upper left' + layers: + - type: Histogram + data: + variable: experiment::ObsValueMinusGsiHofXBc::${variable} + channel: ${channel} + color: 'blue' + label: 'GSI hofx0 (all obs)' + bins: ${dynamic_bins} + alpha: 0.5 + - type: Histogram + data: + variable: experiment::ObsValueMinusHofx0::${variable} + channel: ${channel} + color: 'red' + label: 'JEDI hofx0 (all obs)' + bins: ${dynamic_bins} + alpha: 0.5 + + # JEDI omb vs oma + - batch figure: + variables: *variables + dynamic options: + - type: histogram_bins + data variable: experiment::omanPassedQc::${variable} + number of bins rule: 'rice' + figure: + layout: [1,1] + title: 'OmB vs. OmA | {{instrument_title}} | ${variable_title}' + output name: '{{cycle_dir}}/eva/{{instrument}}/histograms/${variable}/ombg_oman_{{instrument}}_${variable}.png' + plots: + - add_xlabel: 'Difference' + add_ylabel: 'Count' + set_xlim: [-3, 3] + add_legend: + loc: 'upper left' + statistics: + fields: + - field_name: experiment::ombgPassedQc::${variable} + xloc: 0.5 + yloc: -0.10 + kwargs: + color: 'black' + fontsize: 8 + fontfamily: monospace + - field_name: experiment::omanPassedQc::${variable} + xloc: 0.5 + yloc: -0.13 + kwargs: + color: 'red' + fontsize: 8 + fontfamily: monospace + statistics_variables: + - n + - min + - mean + - max + - std + layers: + - type: Histogram + data: + variable: experiment::ombgPassedQc::${variable} + color: 'red' + label: 'observations minus background ' + bins: ${dynamic_bins} + alpha: 0.5 + density: true + - type: Histogram + data: + variable: experiment::omanPassedQc::${variable} + color: 'blue' + label: 'observations minus analysis' + bins: ${dynamic_bins} + alpha: 0.5 + density: true + diff --git a/src/swell/suites/eda_atmos/flow.cylc b/src/swell/suites/eda_atmos/flow.cylc new file mode 100644 index 000000000..0745ef14c --- /dev/null +++ b/src/swell/suites/eda_atmos/flow.cylc @@ -0,0 +1,254 @@ +# (C) Copyright 2021- United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + +# -------------------------------------------------------------------------------------------------- + +# Cylc suite for executing JEDI-based non-cycling variational data assimilation + +# -------------------------------------------------------------------------------------------------- + +[scheduler] + UTC mode = True + allow implicit tasks = False + +# -------------------------------------------------------------------------------------------------- + +[scheduling] + + initial cycle point = {{start_cycle_point}} + final cycle point = {{final_cycle_point}} + runahead limit = {{runahead_limit}} + + [[graph]] + R1 = """ + # Triggers for non cycle time dependent tasks + # ------------------------------------------- + # Clone JEDI source code + CloneJedi + + # Build JEDI source code by linking + CloneJedi => BuildJediByLinking? + + # If not able to link to build create the build + BuildJediByLinking:fail? => BuildJedi + + {% for model_component in model_components %} + # Clone geos ana for generating observing system records + CloneGeosMksi-{{model_component}} + {% endfor %} + """ + + {% for cycle_time in cycle_times %} + {{cycle_time.cycle_time}} = """ + {% for model_component in model_components %} + {% if cycle_time[model_component] %} + + # logic tree: + # prep (clone, stage, build, get bkg, get obs) -> SP1 + # -> mksi/observation + filter -> EDA_start + # -> [ submit/wait for N member runs ]-> EDA_end + # -> mean/variance + diffstate -> SP2 -> eva + + # Task triggers for: {{model_component}} + # ------------------ + # Generate satellite channel records + CloneGeosMksi-{{model_component}}[^] => GenerateObservingSystemRecords-{{model_component}} + + # Get observations + {% if cycling_varbc %} + # Cycling VarBC is active, biases from the previous cycle will be used + + RunJediEdaExecutable-{{model_component}}[-PT6H] => GetObservations-{{model_component}} + {% else %} + + # Cycling VarBC is inactive, static bias files will be used + GetObsNotInR2d2-{{model_component}}: fail? => GetObservations-{{model_component}} + {% endif %} + + # Perform staging that is cycle dependent + StageJediCycle-{{model_component}} + + # Run Jedi variational executable + BuildJediByLinking[^]? | BuildJedi[^] => SP1-{{model_component}} + CloneJedi[^] => StageJediCycle-{{model_component}} + StageJediCycle-{{model_component}} => SP1-{{model_component}} + GetEnsembleGeosExperiment-{{model_component}} => SP1-{{model_component}} + GetObsNotInR2d2-{{model_component}}? | GetObservations-{{model_component}} => SP1-{{model_component}} + SP1-{{model_component}} => GenerateObservingSystemRecords-{{model_component}} + GenerateObservingSystemRecords-{{model_component}} => RenderJediObservations-{{model_component}} + RenderJediObservations-{{model_component}} => RunJediObsfiltersExecutable-{{model_component}} => EDA_start-{{model_component}} + + {% if models[model_component]['ensemble_num_members'] is defined %} + {% for i in range( 1, models[model_component]['ensemble_num_members'] + 1 ) %} + EDA_start-{{model_component}} => RunJediEdaExecutable_mem{{i}}-{{model_component}} => EDA_end-{{model_component}} + {% endfor %} + {% endif %} + + # bkg/ana : mean/variance + EDA_end-{{model_component}} => RunJediEnsembleMeanVariance-{{model_component}} + RunJediEnsembleMeanVariance-{{model_component}} => RunJediDiffstates-{{model_component}} => SP2-{{model_component}} + + # CleanupEda directory + SP2-{{model_component}} => CleanEdaFiles-{{model_component}} + + # EvaIncrement + SP2-{{model_component}} => EvaIncrement-{{model_component}} + + {% endif %} + {% endfor %} + """ + {% endfor %} + +# -------------------------------------------------------------------------------------------------- + +[runtime] + + # Task defaults + # ------------- + [[root]] + pre-script = "source $CYLC_SUITE_DEF_PATH/modules" + + [[[environment]]] + datetime = $CYLC_TASK_CYCLE_POINT + config = $CYLC_SUITE_DEF_PATH/experiment.yaml + + # Tasks + # ----- + [[CloneJedi]] + script = "swell task CloneJedi $config" + + [[BuildJediByLinking]] + script = "swell task BuildJediByLinking $config" + + [[BuildJedi]] + script = "swell task BuildJedi $config" + platform = {{platform}} + execution time limit = {{scheduling["BuildJedi"]["execution_time_limit"]}} + [[[directives]]] + {%- for key, value in scheduling["BuildJedi"]["directives"]["all"].items() %} + --{{key}} = {{value}} + {%- endfor %} + + {% for model_component in model_components %} + + [[CloneGeosMksi-{{model_component}}]] + script = "swell task CloneGeosMksi $config -m {{model_component}}" + + [[GenerateObservingSystemRecords-{{model_component}}]] + script = "swell task GenerateObservingSystemRecords $config -d $datetime -m {{model_component}}" + + [[GetObsNotInR2d2-{{model_component}}]] + script = "swell task GetObsNotInR2d2 $config -d $datetime -m {{model_component}}" + + [[StageJediCycle-{{model_component}}]] + script = "swell task StageJedi $config -d $datetime -m {{model_component}}" + + [[GetBackground-{{model_component}} ]] + script = "swell task GetBackground $config -d $datetime -m {{model_component}}" + + [[GetBackgroundGeosExperiment-{{model_component}} ]] + script = "swell task GetBackgroundGeosExperiment $config -d $datetime -m {{model_component}}" + + [[GetEnsembleGeosExperiment-{{model_component}}]] + script = "swell task GetEnsembleGeosExperiment $config -d $datetime -m {{model_component}}" + + [[GetObservations-{{model_component}}]] + script = "swell task GetObservations $config -d $datetime -m {{model_component}}" + + [[RenderJediObservations-{{model_component}}]] + script = "swell task RenderJediObservations $config -d $datetime -m {{model_component}}" + + {% if models[model_component]['ensemble_num_members'] is defined %} + {% for i in range( 1, models[model_component]['ensemble_num_members'] + 1 ) %} + [[RunJediEdaExecutable_mem{{i}}-{{model_component}}]] + script = "swell task RunJediEdaExecutable $config -d $datetime -m {{model_component}} -imem {{i}}" + platform = {{platform}} + execution time limit = {{scheduling["RunJediEdaExecutable"]["execution_time_limit"]}} + [[[directives]]] + {%- for key, value in scheduling["RunJediEdaExecutable"]["directives"][model_component].items() %} + --{{key}} = {{value}} + {%- endfor %} + {% endfor %} + {% endif %} + + + [[RunJediEdaExecutable-{{model_component}}]] + script = "swell task RunJediEdaExecutable $config -d $datetime -m {{model_component}}" + platform = {{platform}} + execution time limit = {{scheduling["RunJediEdaExecutable"]["execution_time_limit"]}} + [[[directives]]] + {%- for key, value in scheduling["RunJediEdaExecutable"]["directives"][model_component].items() %} + --{{key}} = {{value}} + {%- endfor %} + + + [[RunJediObsfiltersExecutable-{{model_component}}]] + script = "swell task RunJediObsfiltersExecutable $config -d $datetime -m {{model_component}}" + platform = {{platform}} + execution time limit = {{scheduling["RunJediObsfiltersExecutable"]["execution_time_limit"]}} + [[[directives]]] + {%- for key, value in scheduling["RunJediObsfiltersExecutable"]["directives"][model_component].items() %} + --{{key}} = {{value}} + {%- endfor %} + + [[RunJediEnsembleMeanVariance-{{model_component}}]] + script = "swell task RunJediEnsembleMeanVariance $config -d $datetime -m {{model_component}}" + platform = {{platform}} + execution time limit = {{scheduling["RunJediEnsembleMeanVariance"]["execution_time_limit"]}} + [[[directives]]] + {%- for key, value in scheduling["RunJediEnsembleMeanVariance"]["directives"][model_component].items() %} + --{{key}} = {{value}} + {%- endfor %} + + [[RunJediDiffstates-{{model_component}}]] + script = "swell task RunJediDiffstates $config -d $datetime -m {{model_component}}" + platform = {{platform}} + execution time limit = {{scheduling["RunJediDiffstates"]["execution_time_limit"]}} + [[[directives]]] + {%- for key, value in scheduling["RunJediDiffstates"]["directives"][model_component].items() %} + --{{key}} = {{value}} + {%- endfor %} + + [[CleanEdaFiles-{{model_component}}]] + script = "swell task CleanEda $config -d $datetime -m {{model_component}}" + + [[EvaJediLog-{{model_component}}]] + script = "swell task EvaJediLog $config -d $datetime -m {{model_component}}" + + [[EvaIncrement-{{model_component}}]] + script = "swell task EvaIncrement $config -d $datetime -m {{model_component}}" + + [[EvaObservations-{{model_component}}]] + script = "swell task EvaObservations $config -d $datetime -m {{model_component}}" + platform = {{platform}} + execution time limit = {{scheduling["EvaObservations"]["execution_time_limit"]}} + [[[directives]]] + {%- for key, value in scheduling["EvaObservations"]["directives"][model_component].items() %} + --{{key}} = {{value}} + {%- endfor %} + + [[SaveObsDiags-{{model_component}}]] + script = "swell task SaveObsDiags $config -d $datetime -m {{model_component}}" + + [[CleanCycle-{{model_component}}]] + script = "swell task CleanCycle $config -d $datetime -m {{model_component}}" + + # SP: Sync Point + [[SP1-{{model_component}}]] + script = true + + [[EDA_start-{{model_component}}]] + script = true + + [[EDA_end-{{model_component}}]] + script = true + + [[SP2-{{model_component}}]] + script = true + + {% endfor %} + +# -------------------------------------------------------------------------------------------------- diff --git a/src/swell/suites/eda_atmos/suite_config.py b/src/swell/suites/eda_atmos/suite_config.py new file mode 100644 index 000000000..a632d8b86 --- /dev/null +++ b/src/swell/suites/eda_atmos/suite_config.py @@ -0,0 +1,223 @@ +# -------------------------------------------------------------------------------------------------- +# @package configuration +# +# Class containing the configuration. This is a dictionary that is converted from +# an input yaml configuration file. Various function are included for interacting with the +# dictionary. +# +# -------------------------------------------------------------------------------------------------- + + +from swell.utilities.swell_questions import QuestionContainer, QuestionList +from swell.utilities.question_defaults import QuestionDefaults as qd +from swell.suites.suite_questions import SuiteQuestions as sq + +from enum import Enum + + +# -------------------------------------------------------------------------------------------------- + +class SuiteConfig(QuestionContainer, Enum): + + # -------------------------------------------------------------------------------------------------- + + eda_atmos_tier1_fast = QuestionList( + list_name="eda_atmos_tier1_fast", + questions=[ + sq.common, + qd.start_cycle_point("2023-10-10T00:00:00Z"), + qd.final_cycle_point("2023-10-10T06:00:00Z"), + qd.runahead_limit("P2"), + qd.jedi_build_method("use_existing"), + qd.model_components(['geos_atmosphere']), + ], + geos_atmosphere=[ + qd.cycle_times([ + "T00", + ]), + qd.background_experiment('x0050'), + qd.geos_x_background_directory("/discover/nobackup/projects/gmao/dadev" + "rtodling/archive/Restarts/JEDI/541x"), + qd.geos_x_ensemble_directory("/discover/nobackup/projects/gmao/dadev/" + "rtodling/archive/541/Milan"), + qd.npx_proc(4), + qd.npy_proc(5), + qd.window_length("PT6H"), + qd.window_type("3D"), + qd.horizontal_resolution("91"), + qd.gsibec_nlats("91"), + qd.gsibec_nlons("144"), + qd.vertical_resolution("72"), + qd.ensemble_num_members(3), + qd.obs_pert_amplitude(0.5), + qd.number_of_iterations([5]), + qd.gradient_norm_reduction(1.e-3), + qd.analysis_variables([ + "eastward_wind", + "northward_wind", + "air_temperature", + "water_vapor_mixing_ratio_wrt_moist_air", + "air_pressure_at_surface", + "air_pressure_levels", + "cloud_liquid_ice", + "cloud_liquid_water", + "rain_water", + "snow_water", + "mole_fraction_of_ozone_in_air", + "geopotential_height_times_gravity_at_surface", + "fraction_of_ocean", + "fraction_of_lake", + "fraction_of_ice", + "skin_temperature_at_surface" + ]), + qd.observations([ + "aircraft_temperature", + "aircraft_wind", + ]), + qd.obs_thinning_rej_fraction(0.8), + qd.ensmeanvariance_spec([ + {"state": "bkg", + "fn_input": "ebkg/mem%mem%/geos.mem%mem%.%yyyy%mm%dd_%hh%MM%ssz.nc4", + "fn_output_mean": "geos.prior.mean", + "fn_output_variance": "geos.prior.variance", + "grid_type": ['cs', 'latlon']}, + {"state": "analysis", + "fn_input": "analysis/mem%mem%/eda.ana.mem%mem%.%yyyy%mm%dd_%hh%MM%ssz.nc4", + "fn_output_mean": "eda.ana.mean", + "fn_output_variance": "eda.ana.variance", + "grid_type": ['cs', 'latlon']}, + ]), + qd.diffstates_spec({ + "state1": + {"fn_input": "geos.prior.mean.%yyyy%mm%dd_%hh%MM%ssz.nc4"}, + "state2": + {"fn_input": "eda.ana.mean.%yyyy%mm%dd_%hh%MM%ssz.nc4"}, + "state_diff": + {"fn_output": "eda.mean-inc", "grid_type": ['cs', 'latlon']}, + "state_type": "ensemble" + }), + qd.clean_patterns(['*.txt', '*.csv']), + ] + ) + + eda_atmos_tier1 = QuestionList( + list_name="eda_atmos_tier1", + questions=[ + sq.common, + qd.start_cycle_point("2023-10-10T00:00:00Z"), + qd.final_cycle_point("2023-10-10T06:00:00Z"), + qd.runahead_limit("P2"), + qd.jedi_build_method("use_existing"), + qd.model_components(['geos_atmosphere']), + ], + geos_atmosphere=[ + qd.cycle_times([ + "T00", + ]), + qd.background_experiment('x0050'), + qd.geos_x_background_directory("/discover/nobackup/projects/gmao/dadev" + "rtodling/archive/Restarts/JEDI/541x"), + qd.geos_x_ensemble_directory("/discover/nobackup/projects/gmao/dadev/" + "rtodling/archive/541/Milan"), + qd.npx_proc(4), + qd.npy_proc(5), + qd.window_length("PT6H"), + qd.window_type("3D"), + qd.horizontal_resolution("91"), + qd.gsibec_nlats("91"), + qd.gsibec_nlons("144"), + qd.vertical_resolution("72"), + qd.ensemble_num_members(32), + qd.obs_pert_amplitude(0.5), + qd.number_of_iterations([100]), + qd.gradient_norm_reduction(1.e-3), + qd.analysis_variables([ + "eastward_wind", + "northward_wind", + "air_temperature", + "water_vapor_mixing_ratio_wrt_moist_air", + "air_pressure_at_surface", + "air_pressure_levels", + "cloud_liquid_ice", + "cloud_liquid_water", + "rain_water", + "snow_water", + "mole_fraction_of_ozone_in_air", + "geopotential_height_times_gravity_at_surface", + "fraction_of_ocean", + "fraction_of_lake", + "fraction_of_ice", + "skin_temperature_at_surface" + ]), + qd.observations([ + "aircraft_temperature", + "aircraft_wind", + "airs_aqua", + "amsr2_gcom-w1", + "amsua_aqua", + "amsua_metop-b", + "amsua_metop-c", + "amsua_n15", + "amsua_n18", + "amsua_n19", + "atms_n20", + "atms_npp", + "avhrr3_metop-b", + "avhrr3_n18", + "avhrr3_n19", + "cris-fsr_n20", + "cris-fsr_npp", + "gmi_gpm", + "gps", + "iasi_metop-b", + "iasi_metop-c", + "mhs_metop-b", + "mhs_metop-c", + "mhs_n19", + "mls55_aura", + "omi_aura", + "ompsnm_npp", + "pibal", + "satwind", + "scatwind", + "sfcship", + "sfc", + "sondes", + "ssmis_f17" + ]), + qd.obs_thinning_rej_fraction(0.8), + qd.ensmeanvariance_spec([ + {"state": "bkg", + "fn_input": "ebkg/mem%mem%/geos.mem%mem%.%yyyy%mm%dd_%hh%MM%ssz.nc4", + "fn_output_mean": "geos.prior.mean", + "fn_output_variance": "geos.prior.variance", + "grid_type": ['cs', 'latlon']}, + {"state": "analysis", + "fn_input": "analysis/mem%mem%/eda.ana.mem%mem%.%yyyy%mm%dd_%hh%MM%ssz.nc4", + "fn_output_mean": "eda.ana.mean", + "fn_output_variance": "eda.ana.variance", + "grid_type": ['cs', 'latlon']}, + ]), + qd.diffstates_spec({ + "state1": + {"fn_input": "geos.prior.mean.%yyyy%mm%dd_%hh%MM%ssz.nc4"}, + "state2": + {"fn_input": "eda.ana.mean.%yyyy%mm%dd_%hh%MM%ssz.nc4"}, + "state_diff": + {"fn_output": "eda.mean-inc", "grid_type": ['cs', 'latlon']}, + "state_type": "ensemble" + }), + qd.clean_patterns(['*.txt', '*.csv']), + ] + ) + + # -------------------------------------------------------------------------------------------------- + + eda_atmos = QuestionList( + list_name="eda_atmos", + questions=[ + eda_atmos_tier1_fast + ] + ) +# normal run: eda_atmos_tier1 + # -------------------------------------------------------------------------------------------------- diff --git a/src/swell/suites/eda_controlpert_atmos/eva/increment-geos_atmosphere.yaml b/src/swell/suites/eda_controlpert_atmos/eva/increment-geos_atmosphere.yaml new file mode 100644 index 000000000..126435876 --- /dev/null +++ b/src/swell/suites/eda_controlpert_atmos/eva/increment-geos_atmosphere.yaml @@ -0,0 +1,760 @@ +datasets: + +- group: increment + type: LatLon + filename: {{increment_file_path}} + name: experiment_increment + variables: [ps, ts, ua, va, t, q, lat, lon] + +graphics: + + plotting_backend: Emcpy + figure_list: + + #map plot for surface pressure increment + - batch figure: + variables: [ps] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Surface Pressure Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::ps + label: PS increment + colorbar: true + cmap: 'bwr' + vmin: -100 + vmax: 100 + - batch figure: + variables: [ts] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Tskin Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::ts + label: TS increment + colorbar: true + cmap: 'bwr' + vmin: -2 + vmax: 2 + #map plot for temperature increment (lowest level) + - batch figure: + variables: [t] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_1000.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Temperature Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::t + slices: '[71,...]' + label: T increment (1000 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for temperature increment + - batch figure: + variables: [t] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_850.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Temperature Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::t + slices: '[62,...]' + label: T increment (850 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for temperature increment + - batch figure: + variables: [t] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_500.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Temperature Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::t + slices: '[49,...]' + label: T increment (500 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for temperature increment + - batch figure: + variables: [t] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_200.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Temperature Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::t + slices: '[42,...]' + label: T increment (200 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for temperature increment + - batch figure: + variables: [t] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_10.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Temperature Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::t + slices: '[24,...]' + label: T increment (10 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for temperature increment + - batch figure: + variables: [t] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_1.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Temperature Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::t + slices: '[14,...]' + label: T increment (1 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Zonal Wind increment (lowest level) + - batch figure: + variables: [ua] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_1000.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Zonal Wind Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::ua + slices: '[71,...]' + label: U increment (1000 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Zonal Wind increment + - batch figure: + variables: [ua] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_850.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Zonal Wind Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::ua + slices: '[62,...]' + label: U increment (850 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Zonal Wind increment + - batch figure: + variables: [ua] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_500.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Zonal Wind Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::ua + slices: '[49,...]' + label: U increment (500 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Zonal Wind increment + - batch figure: + variables: [ua] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_200.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Zonal Wind Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::ua + slices: '[42,...]' + label: U increment (200 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Zonal Wind increment + - batch figure: + variables: [ua] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_10.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Zonal Wind Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::ua + slices: '[24,...]' + label: U increment (10 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Zonal Wind increment + - batch figure: + variables: [ua] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_1.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Zonal Wind Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::ua + slices: '[14,...]' + label: U increment (1 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Meridional Wind increment (lowest level) + - batch figure: + variables: [va] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_1000.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Meridional Wind Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::va + slices: '[71,...]' + label: V increment (1000 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Meridional Wind increment + - batch figure: + variables: [va] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_850.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Meridional Wind Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::va + slices: '[62,...]' + label: V increment (850 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Meridional Wind increment + - batch figure: + variables: [va] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_500.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Meridional Wind Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::va + slices: '[49,...]' + label: V increment (500 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Meridional Wind increment + - batch figure: + variables: [va] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_200.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Meridional Wind Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::va + slices: '[42,...]' + label: V increment (200 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Meridional Wind increment + - batch figure: + variables: [va] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_10.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Meridional Wind Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::va + slices: '[24,...]' + label: V increment (10 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Meridional Wind increment + - batch figure: + variables: [va] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_1.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Meridional Wind Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::va + slices: '[14,...]' + label: V increment (1 hPa) + colorbar: true + cmap: 'bwr' + vmin: -1 + vmax: 1 + #map plot for Specific Humidity increment (lowest level) + - batch figure: + variables: [q] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_1000.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Specific Humidity Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::q + slices: '[71,...]' + label: Q increment (1000 hPa) + colorbar: true + cmap: 'bwr' + vmin: -0.001 + vmax: 0.001 + #map plot for Specific Humidity increment + - batch figure: + variables: [q] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_850.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Specific Humidity Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::q + slices: '[62,...]' + label: Q increment (850 hPa) + colorbar: true + cmap: 'bwr' + vmin: -0.001 + vmax: 0.001 + #map plot for Specific Humidity increment + - batch figure: + variables: [q] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_500.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Specific Humidity Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::q + slices: '[49,...]' + label: Q increment (500 hPa) + colorbar: true + cmap: 'bwr' + vmin: -0.001 + vmax: 0.001 + #map plot for Specific Humidity increment + - batch figure: + variables: [q] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_200.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Specific Humidity Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::q + slices: '[42,...]' + label: Q increment (200 hPa) + colorbar: true + cmap: 'bwr' + vmin: -0.001 + vmax: 0.001 + #map plot for Specific Humidity increment + - batch figure: + variables: [q] + figure: + figure size: [20,10] + layout: [1,1] + title: 'Increment from JEDI' + output name: '{{cycle_dir}}/eva/increment/map_plots/${variable}/inc_${variable}_10.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: Specific Humidity Increment + add_grid: + layers: + - type: MapGridded + longitude: + variable: experiment_increment::increment::lon + latitude: + variable: experiment_increment::increment::lat + data: + variable: experiment_increment::increment::q + slices: '[24,...]' + label: Q increment (10 hPa) + colorbar: true + cmap: 'bwr' + vmin: -0.001 + vmax: 0.001 diff --git a/src/swell/suites/eda_controlpert_atmos/eva/jedi_log-geos_atmosphere.yaml b/src/swell/suites/eda_controlpert_atmos/eva/jedi_log-geos_atmosphere.yaml new file mode 100644 index 000000000..2d848d549 --- /dev/null +++ b/src/swell/suites/eda_controlpert_atmos/eva/jedi_log-geos_atmosphere.yaml @@ -0,0 +1,89 @@ +datasets: + +- type: JediLog + collection_name: JediLogTest + jedi_log_to_parse: '{{cycle_dir}}/jedi_variational_log.log' + data_to_parse: + convergence: true + +transforms: +- transform: arithmetic + new name: JediLogTest::convergence::${variable}_log + equals: log(JediLogTest::convergence::${variable}) + for: + variable: [residual_norm, norm_reduction] + +graphics: + + plotting_backend: Emcpy + figure_list: + + - figure: + layout: [3,1] + figure size: [12,10] + title: 'Residual Norm and Norm Reduction Plots' + output name: '{{cycle_dir}}/eva/jedi_log/convergence/residual_norm_reduction.png' + plots: + - add_xlabel: 'Total inner iteration number' + add_ylabel: 'Residual norm' + layers: + - type: LinePlot + x: + variable: JediLogTest::convergence::total_iteration + y: + variable: JediLogTest::convergence::residual_norm + color: 'black' + + - add_xlabel: 'Total inner iteration number' + add_ylabel: 'Log(norm reduction)' + layers: + - type: LinePlot + x: + variable: JediLogTest::convergence::total_iteration + y: + variable: JediLogTest::convergence::norm_reduction + color: 'black' + + - add_xlabel: 'Total inner iteration number' + add_ylabel: 'Log(reduction)' + add_legend: + layers: + - type: LinePlot + x: + variable: JediLogTest::convergence::total_iteration + y: + variable: JediLogTest::convergence::residual_norm_log + color: 'red' + label: 'Log(residual norm)' + - type: LinePlot + x: + variable: JediLogTest::convergence::total_iteration + y: + variable: JediLogTest::convergence::norm_reduction_log + color: 'blue' + label: 'Log norm reduction' + + - figure: + title: 'Cost Function Plot' + output name: '{{cycle_dir}}/eva/jedi_log/cost_function/cost_function.png' + plots: + - add_xlabel: 'Total inner iteration number' + add_ylabel: 'Quadratic Cost Function' + add_legend: + layers: + - type: LinePlot + label: 'jojc' + x: + variable: JediLogTest::convergence::total_iteration + y: + variable: JediLogTest::convergence::jojc + color: 'blue' + markersize: 2 + - type: LinePlot + label: 'jb' + x: + variable: JediLogTest::convergence::total_iteration + y: + variable: JediLogTest::convergence::jb + color: 'red' + markersize: 2 diff --git a/src/swell/suites/eda_controlpert_atmos/eva/observations-geos_atmosphere.yaml b/src/swell/suites/eda_controlpert_atmos/eva/observations-geos_atmosphere.yaml new file mode 100644 index 000000000..edee73160 --- /dev/null +++ b/src/swell/suites/eda_controlpert_atmos/eva/observations-geos_atmosphere.yaml @@ -0,0 +1,564 @@ +datasets: + +- name: experiment + type: IodaObsSpace + filenames: + - {{obs_path_file}} + channels: &channels {{channels}} + groups: + - name: ObsValue + variables: &variables {{simulated_variables}} + - name: GsiHofXBc + #- name: GsiEffectiveQC + - name: hofx0 + - name: hofx1 + - name: ombg + - name: oman + - name: EffectiveQC0 + - name: EffectiveQC1 + - name: MetaData + +transforms: + +# Generate hofx0 for GSI +- transform: arithmetic + new name: experiment::ObsValueMinusGsiHofXBc::${variable} + equals: experiment::ObsValue::${variable}-experiment::GsiHofXBc::${variable} + for: + variable: *variables + +# Generate hofx0 for JEDI +- transform: arithmetic + new name: experiment::ObsValueMinusHofx0::${variable} + equals: experiment::ObsValue::${variable}-experiment::hofx0::${variable} + for: + variable: *variables + +# Generate hofx difference +- transform: arithmetic + new name: experiment::Hofx0MinusGsiHofXBc::${variable} + equals: experiment::hofx0::${variable}-experiment::GsiHofXBc::${variable} + for: + variable: *variables + +# Generate hofx that passed QC for JEDI +- transform: accept where + new name: experiment::hofx0PassedQc::${variable} + starting field: experiment::hofx0::${variable} + where: + - experiment::EffectiveQC0::${variable} == 0 + for: + variable: *variables + +# Generate GSI hofx that passed JEDI QC +- transform: accept where + new name: experiment::GsiHofXBcPassedQc::${variable} + starting field: experiment::GsiHofXBc::${variable} + where: + - experiment::EffectiveQC0::${variable} == 0 + for: + variable: *variables + +# Generate hofx0 that passed QC for JEDI +- transform: accept where + new name: experiment::ObsValueMinushofx0PassedQc::${variable} + starting field: experiment::ObsValueMinusHofx0::${variable} + where: + - experiment::EffectiveQC0::${variable} == 0 + for: + variable: *variables + +# Generate hofx0 that passed QC for GSI +- transform: accept where + new name: experiment::ObsValueMinusGsiHofXBcPassedQc::${variable} + starting field: experiment::ObsValueMinusGsiHofXBc::${variable} + where: + - experiment::EffectiveQC0::${variable} == 0 + for: + variable: *variables + +# Generate ombg that passed QC for JEDI +- transform: accept where + new name: experiment::ombgPassedQc::${variable} + starting field: experiment::ombg::${variable} + where: + - experiment::EffectiveQC0::${variable} == 0 + for: + variable: *variables + +# Generate oman that passed QC for JEDI +- transform: accept where + new name: experiment::omanPassedQc::${variable} + starting field: experiment::oman::${variable} + where: + - experiment::EffectiveQC1::${variable} == 0 + for: + variable: *variables + +# Generate obs contribution to analysis (OmA*OmA)-(OmB*OmB) +- transform: arithmetic + new name: experiment::ResidualRMSdiff::${variable} + equals: (experiment::omanPassedQc::${variable})*(experiment::omanPassedQc::${variable})-(experiment::ombgPassedQc::${variable})*(experiment::ombgPassedQc::${variable}) + for: + variable: *variables + +graphics: + + plotting_backend: Emcpy + figure_list: + + # Correlation scatter plots + # ------------------------- + + # JEDI h(x) vs Observations + - batch figure: + variables: *variables + channels: *channels + figure: + layout: [1,1] + title: 'Observations vs. JEDI h(x) | {{instrument_title}} | ${variable_title}' + output name: '{{cycle_dir}}/eva/{{instrument}}/correlation_scatter/${variable}${channel}/jedi_hofx0_vs_obs_{{instrument}}_${variable}${channel}.png' + plots: + - add_xlabel: 'Observation Value' + add_ylabel: 'JEDI h(x)' + add_grid: + add_legend: + loc: 'upper left' + layers: + - type: Scatter + x: + variable: experiment::ObsValue::${variable} + y: + variable: experiment::hofx0::${variable} + channel: ${channel} + markersize: 5 + color: 'black' + label: 'JEDI h(x) versus obs (all obs)' + - type: Scatter + x: + variable: experiment::ObsValue::${variable} + y: + variable: experiment::hofx0PassedQc::${variable} + channel: ${channel} + markersize: 5 + color: 'red' + label: 'JEDI h(x) versus obs (passed QC in JEDI)' + + # GSI h(x) vs Observations + - batch figure: + variables: *variables + channels: *channels + figure: + layout: [1,1] + title: 'Observations vs. GSI h(x) | {{instrument_title}} | ${variable_title}' + output name: '{{cycle_dir}}/eva/{{instrument}}/correlation_scatter/${variable}${channel}/gsi_hofx0_vs_obs_{{instrument}}_${variable}${channel}.png' + plots: + - add_xlabel: 'Observation Value' + add_ylabel: 'GSI h(x)' + add_grid: + add_legend: + loc: 'upper left' + layers: + - type: Scatter + x: + variable: experiment::ObsValue::${variable} + y: + variable: experiment::GsiHofXBc::${variable} + channel: ${channel} + markersize: 5 + color: 'black' + label: 'GSI h(x) versus obs (all obs)' + - type: Scatter + x: + variable: experiment::ObsValue::${variable} + y: + variable: experiment::GsiHofXBcPassedQc::${variable} + channel: ${channel} + markersize: 5 + color: 'red' + label: 'GSI h(x) versus obs (passed QC in JEDI)' + + # JEDI h(x) vs GSI h(x) + - batch figure: + variables: *variables + channels: *channels + figure: + layout: [1,1] + title: 'JEDI h(x) vs. GSI h(x) | {{instrument_title}} | ${variable_title}' + output name: '{{cycle_dir}}/eva/{{instrument}}/correlation_scatter/${variable}${channel}/gsi_hofx_vs_jedi0_hofx_{{instrument}}_${variable}${channel}.png' + plots: + - add_xlabel: 'GSI h(x)' + add_ylabel: 'JEDI h(x)' + add_grid: + add_legend: + loc: 'upper left' + layers: + - type: Scatter + x: + variable: experiment::GsiHofXBc::${variable} + y: + variable: experiment::hofx0::${variable} + channel: ${channel} + markersize: 5 + color: 'black' + label: 'JEDI h(x) versus GSI h(x)' + - type: Scatter + x: + variable: experiment::GsiHofXBcPassedQc::${variable} + y: + variable: experiment::hofx0PassedQc::${variable} + channel: ${channel} + markersize: 5 + color: 'red' + label: 'JEDI h(x) versus GSI h(x) (passed QC in JEDI)' + + # JEDI hofx0 vs GSI hofx0 + - batch figure: + variables: *variables + channels: *channels + figure: + layout: [1,1] + title: 'JEDI hofx0 vs. GSI hofx0 | {{instrument_title}} | ${variable_title}' + output name: '{{cycle_dir}}/eva/{{instrument}}/correlation_scatter/${variable}${channel}/gsi_hofx0_vs_jedi_hofx0_{{instrument}}_${variable}${channel}.png' + plots: + - add_xlabel: 'GSI observation minus h(x)' + add_ylabel: 'JEDI observation minus h(x)' + add_grid: + add_legend: + loc: 'upper left' + layers: + - type: Scatter + x: + variable: experiment::ObsValueMinusGsiHofXBc::${variable} + y: + variable: experiment::ObsValueMinusHofx0::${variable} + channel: ${channel} + markersize: 5 + color: 'black' + label: 'GSI hofx0 vs JEDI hofx0 (all obs)' + - type: Scatter + x: + variable: experiment::ObsValueMinusGsiHofXBcPassedQc::${variable} + y: + variable: experiment::ObsValueMinushofx0PassedQc::${variable} + channel: ${channel} + markersize: 5 + color: 'red' + label: 'GSI hofx0 vs JEDI hofx0 (passed QC in JEDI)' + + # JEDI oma vs omb + - batch figure: + variables: *variables + channels: *channels + figure: + layout: [1,1] + title: 'OmA vs. OmB | {{instrument_title}} | ${variable_title}' + output name: '{{cycle_dir}}/eva/{{instrument}}/correlation_scatter/${variable}${channel}/jedi_oma_vs_omb_{{instrument}}_${variable}${channel}.png' + plots: + - add_xlabel: 'OmA' + add_ylabel: 'OmB' + add_grid: + add_legend: + loc: 'upper left' + layers: + - type: Scatter + x: + variable: experiment::oman::${variable} + y: + variable: experiment::ombg::${variable} + channel: ${channel} + markersize: 5 + color: 'black' + label: 'OmA versus OmB (all residuals)' + - type: Scatter + x: + variable: experiment::omanPassedQc::${variable} + y: + variable: experiment::ombgPassedQc::${variable} + channel: ${channel} + markersize: 5 + color: 'red' + label: 'OmA versus OmB (passed QC)' + +# Map plots# --------- + + # Observations + - batch figure: + variables: *variables + channels: *channels + dynamic options: + - type: vminvmaxcmap + channel: ${channel} + data variable: experiment::ObsValue::${variable} + figure: + figure size: [20,10] + layout: [1,1] + title: 'Observations | {{instrument_title}} | Obs Value' + output name: '{{cycle_dir}}/eva/{{instrument}}/map_plots/${variable}${channel}/observations_{{instrument}}_${variable}${channel}.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: ObsValue + add_grid: + layers: + - type: MapScatter + longitude: + variable: experiment::MetaData::longitude + latitude: + variable: experiment::MetaData::latitude + data: + variable: experiment::ObsValue::${variable} + channel: ${channel} + markersize: 2 + label: ObsValue + colorbar: true + cmap: ${dynamic_cmap} + vmin: ${dynamic_vmin} + vmax: ${dynamic_vmax} + + # hofx0 jedi + - batch figure: + variables: *variables + channels: *channels + dynamic options: + - type: vminvmaxcmap + channel: ${channel} + data variable: experiment::ObsValueMinusHofx0::${variable} + figure: + figure size: [20,10] + layout: [1,1] + title: 'JEDI hofx0 | {{instrument_title}} | ${variable_title}' + output name: '{{cycle_dir}}/eva/{{instrument}}/map_plots/${variable}${channel}/hofx0_jedi_{{instrument}}_${variable}${channel}.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: '${variable}' + add_grid: + layers: + - type: MapScatter + longitude: + variable: experiment::MetaData::longitude + latitude: + variable: experiment::MetaData::latitude + data: + variable: experiment::ObsValueMinusHofx0::${variable} + channel: ${channel} + markersize: 2 + label: '${variable}' + colorbar: true + cmap: ${dynamic_cmap} + vmin: ${dynamic_vmin} + vmax: ${dynamic_vmax} + + # hofx0 gsi + - batch figure: + variables: *variables + channels: *channels + dynamic options: + - type: vminvmaxcmap + channel: ${channel} + data variable: experiment::ObsValueMinusGsiHofXBc::${variable} + figure: + figure size: [20,10] + layout: [1,1] + title: 'GSI hofx0 | {{instrument_title}} | ${variable_title}' + output name: '{{cycle_dir}}/eva/{{instrument}}/map_plots/${variable}${channel}/hofx0_gsi_{{instrument}}_${variable}${channel}.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: '${variable}' + add_grid: + layers: + - type: MapScatter + longitude: + variable: experiment::MetaData::longitude + latitude: + variable: experiment::MetaData::latitude + data: + variable: experiment::ObsValueMinusGsiHofXBc::${variable} + channel: ${channel} + markersize: 2 + label: '${variable}' + colorbar: true + cmap: ${dynamic_cmap} + vmin: ${dynamic_vmin} + vmax: ${dynamic_vmax} + + # hofx difference + - batch figure: + variables: *variables + channels: *channels + dynamic options: + - type: vminvmaxcmap + channel: ${channel} + data variable: experiment::Hofx0MinusGsiHofXBc::${variable} + figure: + figure size: [20,10] + layout: [1,1] + title: 'Hofx0 Difference | {{instrument_title}} | ${variable_title}' + output name: '{{cycle_dir}}/eva/{{instrument}}/map_plots/${variable}${channel}/hofx0_difference_{{instrument}}_${variable}${channel}.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: '${variable}' + add_grid: + layers: + - type: MapScatter + longitude: + variable: experiment::MetaData::longitude + latitude: + variable: experiment::MetaData::latitude + data: + variable: experiment::Hofx0MinusGsiHofXBc::${variable} + channel: ${channel} + markersize: 2 + label: '${variable}' + colorbar: true + cmap: ${dynamic_cmap} + vmin: ${dynamic_vmin} + vmax: ${dynamic_vmax} + + + # RMS(oma)-RMS(omb) difference + - batch figure: + variables: *variables + channels: *channels + dynamic options: + - type: vminvmaxcmap + channel: ${channel} + data variable: experiment::ResidualRMSdiff::${variable} + figure: + figure size: [20,10] + layout: [1,1] + title: 'RMS Residual Difference | {{instrument_title}} | ${variable_title}' + output name: '{{cycle_dir}}/eva/{{instrument}}/map_plots/${variable}${channel}/rmsres_difference_{{instrument}}_${variable}${channel}.png' + plots: + - mapping: + projection: plcarr + domain: global + add_map_features: ['coastline'] + add_colorbar: + label: '${variable}' + add_grid: + layers: + - type: MapScatter + longitude: + variable: experiment::MetaData::longitude + latitude: + variable: experiment::MetaData::latitude + data: + variable: experiment::ResidualRMSdiff::${variable} + channel: ${channel} + markersize: 2 + label: '${variable}' + colorbar: true + cmap: ${dynamic_cmap} + vmin: ${dynamic_vmin} + vmax: ${dynamic_vmax} +# Histogram plots# --------------- + + # hofx0 vs hofx0 + - batch figure: + variables: *variables + channels: *channels + dynamic options: + - type: histogram_bins + channel: ${channel} + number of bins rule: sturges + data variable: experiment::ObsValueMinusHofx0::${variable} + figure: + layout: [1,1] + title: 'JEDI hofx0 vs. GSI hofx0 | {{instrument_title}} | ${variable_title}' + output name: '{{cycle_dir}}/eva/{{instrument}}/histograms/${variable}${channel}/gsi_hofx0_vs_jedi_hofx0_{{instrument}}_${variable}${channel}.png' + plots: + - add_xlabel: 'Observation minus h(x)' + add_ylabel: 'Count' + add_legend: + loc: 'upper left' + layers: + - type: Histogram + data: + variable: experiment::ObsValueMinusGsiHofXBc::${variable} + channel: ${channel} + color: 'blue' + label: 'GSI hofx0 (all obs)' + bins: ${dynamic_bins} + alpha: 0.5 + - type: Histogram + data: + variable: experiment::ObsValueMinusHofx0::${variable} + channel: ${channel} + color: 'red' + label: 'JEDI hofx0 (all obs)' + bins: ${dynamic_bins} + alpha: 0.5 + + # JEDI omb vs oma + - batch figure: + variables: *variables + dynamic options: + - type: histogram_bins + data variable: experiment::omanPassedQc::${variable} + number of bins rule: 'rice' + figure: + layout: [1,1] + title: 'OmB vs. OmA | {{instrument_title}} | ${variable_title}' + output name: '{{cycle_dir}}/eva/{{instrument}}/histograms/${variable}/ombg_oman_{{instrument}}_${variable}.png' + plots: + - add_xlabel: 'Difference' + add_ylabel: 'Count' + set_xlim: [-3, 3] + add_legend: + loc: 'upper left' + statistics: + fields: + - field_name: experiment::ombgPassedQc::${variable} + xloc: 0.5 + yloc: -0.10 + kwargs: + color: 'black' + fontsize: 8 + fontfamily: monospace + - field_name: experiment::omanPassedQc::${variable} + xloc: 0.5 + yloc: -0.13 + kwargs: + color: 'red' + fontsize: 8 + fontfamily: monospace + statistics_variables: + - n + - min + - mean + - max + - std + layers: + - type: Histogram + data: + variable: experiment::ombgPassedQc::${variable} + color: 'red' + label: 'observations minus background ' + bins: ${dynamic_bins} + alpha: 0.5 + density: true + - type: Histogram + data: + variable: experiment::omanPassedQc::${variable} + color: 'blue' + label: 'observations minus analysis' + bins: ${dynamic_bins} + alpha: 0.5 + density: true + diff --git a/src/swell/suites/eda_controlpert_atmos/flow.cylc b/src/swell/suites/eda_controlpert_atmos/flow.cylc new file mode 100644 index 000000000..da246ddf1 --- /dev/null +++ b/src/swell/suites/eda_controlpert_atmos/flow.cylc @@ -0,0 +1,252 @@ +# (C) Copyright 2021- United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + +# -------------------------------------------------------------------------------------------------- + +# Cylc suite for executing JEDI-based non-cycling variational data assimilation + +# -------------------------------------------------------------------------------------------------- + +[scheduler] + UTC mode = True + allow implicit tasks = False + +# -------------------------------------------------------------------------------------------------- + +[scheduling] + + initial cycle point = {{start_cycle_point}} + final cycle point = {{final_cycle_point}} + runahead limit = {{runahead_limit}} + + [[graph]] + R1 = """ + # Triggers for non cycle time dependent tasks + # ------------------------------------------- + # Clone JEDI source code + CloneJedi + + # Build JEDI source code by linking + CloneJedi => BuildJediByLinking? + + # If not able to link to build create the build + BuildJediByLinking:fail? => BuildJedi + + {% for model_component in model_components %} + # Clone geos ana for generating observing system records + CloneGeosMksi-{{model_component}} + {% endfor %} + """ + + {% for cycle_time in cycle_times %} + {{cycle_time.cycle_time}} = """ + {% for model_component in model_components %} + {% if cycle_time[model_component] %} + + # logic tree: + # prep (clone, stage, build, get bkg, get obs) -> SP1 + # -> mksi/observation + filter -> SP2 + # -> [ submit/wait for N chunk runs ]-> SP3 + # -> mean/variance + diffstate -> SP4 -> eva + + # Task triggers for: {{model_component}} + # ------------------ + # Generate satellite channel records + CloneGeosMksi-{{model_component}}[^] => GenerateObservingSystemRecords-{{model_component}} + + # Get observations + {% if cycling_varbc %} + # Cycling VarBC is active, biases from the previous cycle will be used + + {% if models[model_component]['ensemble_num_chunks'] is defined %} + {% for i in range( 1, models[model_component]['ensemble_num_chunks'] + 1 ) %} + RunJediEdaControlPertExecutable_chunk{{i}}-{{model_component}}[-PT6H] => GetObservations-{{model_component}} + {% endfor %} + {% endif %} + + {% else %} + + # Cycling VarBC is inactive, static bias files will be used + GetObsNotInR2d2-{{model_component}}: fail? => GetObservations-{{model_component}} + {% endif %} + + # Perform staging that is cycle dependent + StageJediCycle-{{model_component}} + + # Run Jedi variational executable + BuildJediByLinking[^]? | BuildJedi[^] => SP1-{{model_component}} + CloneJedi[^] => StageJediCycle-{{model_component}} + StageJediCycle-{{model_component}} => SP1-{{model_component}} + GetEnsembleGeosExperiment-{{model_component}} => SP1-{{model_component}} + GetObsNotInR2d2-{{model_component}}? | GetObservations-{{model_component}} => SP1-{{model_component}} + SP1-{{model_component}} => GenerateObservingSystemRecords-{{model_component}} + GenerateObservingSystemRecords-{{model_component}} => RenderJediObservations-{{model_component}} + RenderJediObservations-{{model_component}} => RunJediObsfiltersExecutable-{{model_component}} => SP2-{{model_component}} + + {% if models[model_component]['ensemble_num_chunks'] is defined %} + {% for i in range( 1, models[model_component]['ensemble_num_chunks'] + 1 ) %} + SP2-{{model_component}} => RunJediEdaControlPertExecutable_chunk{{i}}-{{model_component}} => SP3-{{model_component}} + {% endfor %} + {% endif %} + + # bkg/ana : mean/variance + SP3-{{model_component}} => ShuffleAnaMembers-{{model_component}} + ShuffleAnaMembers-{{model_component}} => RunJediEnsembleMeanVariance-{{model_component}} + RunJediEnsembleMeanVariance-{{model_component}} ==> RunJediDiffstates-{{model_component}} => SP4-{{model_component}} + + # CleanupEda directory + SP4-{{model_component}} => CleanEdaFiles-{{model_component}} + + # EvaIncrement + SP4-{{model_component}} => EvaIncrement-{{model_component}} + + {% endif %} + {% endfor %} + """ + {% endfor %} + +# -------------------------------------------------------------------------------------------------- + +[runtime] + + # Task defaults + # ------------- + [[root]] + pre-script = "source $CYLC_SUITE_DEF_PATH/modules" + + [[[environment]]] + datetime = $CYLC_TASK_CYCLE_POINT + config = $CYLC_SUITE_DEF_PATH/experiment.yaml + + # Tasks + # ----- + [[CloneJedi]] + script = "swell task CloneJedi $config" + + [[BuildJediByLinking]] + script = "swell task BuildJediByLinking $config" + + [[BuildJedi]] + script = "swell task BuildJedi $config" + platform = {{platform}} + execution time limit = {{scheduling["BuildJedi"]["execution_time_limit"]}} + [[[directives]]] + {%- for key, value in scheduling["BuildJedi"]["directives"]["all"].items() %} + --{{key}} = {{value}} + {%- endfor %} + + {% for model_component in model_components %} + + [[CloneGeosMksi-{{model_component}}]] + script = "swell task CloneGeosMksi $config -m {{model_component}}" + + [[GenerateObservingSystemRecords-{{model_component}}]] + script = "swell task GenerateObservingSystemRecords $config -d $datetime -m {{model_component}}" + + [[GetObsNotInR2d2-{{model_component}}]] + script = "swell task GetObsNotInR2d2 $config -d $datetime -m {{model_component}}" + + [[StageJediCycle-{{model_component}}]] + script = "swell task StageJedi $config -d $datetime -m {{model_component}}" + + [[GetBackground-{{model_component}} ]] + script = "swell task GetBackground $config -d $datetime -m {{model_component}}" + + [[GetBackgroundGeosExperiment-{{model_component}} ]] + script = "swell task GetBackgroundGeosExperiment $config -d $datetime -m {{model_component}}" + + [[GetEnsembleGeosExperiment-{{model_component}}]] + script = "swell task GetEnsembleGeosExperiment $config -d $datetime -m {{model_component}}" + + [[GetObservations-{{model_component}}]] + script = "swell task GetObservations $config -d $datetime -m {{model_component}}" + + [[RenderJediObservations-{{model_component}}]] + script = "swell task RenderJediObservations $config -d $datetime -m {{model_component}}" + + {% if models[model_component]['ensemble_num_chunks'] is defined %} + {% for i in range( 1, models[model_component]['ensemble_num_chunks'] + 1 ) %} + [[RunJediEdaControlPertExecutable_chunk{{i}}-{{model_component}}]] + script = "swell task RunJediEdaControlPertExecutable $config -d $datetime -m {{model_component}} -ichunk {{i}}" + platform = {{platform}} + execution time limit = {{scheduling["RunJediEdaControlPertExecutable"]["execution_time_limit"]}} + [[[directives]]] + {%- for key, value in scheduling["RunJediEdaControlPertExecutable"]["directives"][model_component].items() %} + --{{key}} = {{value}} + {%- endfor %} + {% endfor %} + {% endif %} + + [[ShuffleAnaMembers-{{model_component}}]] + script = "swell task RunJediEdaControlPertExecutable $config -d $datetime -m {{model_component}} -ichunk -1" + + [[RunJediObsfiltersExecutable-{{model_component}}]] + script = "swell task RunJediObsfiltersExecutable $config -d $datetime -m {{model_component}}" + platform = {{platform}} + execution time limit = {{scheduling["RunJediObsfiltersExecutable"]["execution_time_limit"]}} + [[[directives]]] + {%- for key, value in scheduling["RunJediObsfiltersExecutable"]["directives"][model_component].items() %} + --{{key}} = {{value}} + {%- endfor %} + + [[RunJediEnsembleMeanVariance-{{model_component}}]] + script = "swell task RunJediEnsembleMeanVariance $config -d $datetime -m {{model_component}}" + platform = {{platform}} + execution time limit = {{scheduling["RunJediEnsembleMeanVariance"]["execution_time_limit"]}} + [[[directives]]] + {%- for key, value in scheduling["RunJediEnsembleMeanVariance"]["directives"][model_component].items() %} + --{{key}} = {{value}} + {%- endfor %} + + [[RunJediDiffstates-{{model_component}}]] + script = "swell task RunJediDiffstates $config -d $datetime -m {{model_component}}" + platform = {{platform}} + execution time limit = {{scheduling["RunJediDiffstates"]["execution_time_limit"]}} + [[[directives]]] + {%- for key, value in scheduling["RunJediDiffstates"]["directives"][model_component].items() %} + --{{key}} = {{value}} + {%- endfor %} + + [[CleanEdaFiles-{{model_component}}]] + script = "swell task CleanEda $config -d $datetime -m {{model_component}}" + + [[EvaJediLog-{{model_component}}]] + script = "swell task EvaJediLog $config -d $datetime -m {{model_component}}" + + [[EvaIncrement-{{model_component}}]] + script = "swell task EvaIncrement $config -d $datetime -m {{model_component}}" + + [[EvaObservations-{{model_component}}]] + script = "swell task EvaObservations $config -d $datetime -m {{model_component}}" + platform = {{platform}} + execution time limit = {{scheduling["EvaObservations"]["execution_time_limit"]}} + [[[directives]]] + {%- for key, value in scheduling["EvaObservations"]["directives"][model_component].items() %} + --{{key}} = {{value}} + {%- endfor %} + + [[SaveObsDiags-{{model_component}}]] + script = "swell task SaveObsDiags $config -d $datetime -m {{model_component}}" + + [[CleanCycle-{{model_component}}]] + script = "swell task CleanCycle $config -d $datetime -m {{model_component}}" + + # SP: Sync Point + [[SP1-{{model_component}}]] + script = true + + [[SP2-{{model_component}}]] + script = true + + [[SP3-{{model_component}}]] + script = true + + [[SP4-{{model_component}}]] + script = true + + {% endfor %} + +# -------------------------------------------------------------------------------------------------- diff --git a/src/swell/suites/eda_controlpert_atmos/suite_config.py b/src/swell/suites/eda_controlpert_atmos/suite_config.py new file mode 100644 index 000000000..b6c234e9a --- /dev/null +++ b/src/swell/suites/eda_controlpert_atmos/suite_config.py @@ -0,0 +1,209 @@ +# -------------------------------------------------------------------------------------------------- +# @package configuration +# +# Class containing the configuration. This is a dictionary that is converted from +# an input yaml configuration file. Various function are included for interacting with the +# dictionary. +# +# -------------------------------------------------------------------------------------------------- + + +from swell.utilities.swell_questions import QuestionContainer, QuestionList +from swell.utilities.question_defaults import QuestionDefaults as qd +from swell.suites.suite_questions import SuiteQuestions as sq + +from enum import Enum + + +# -------------------------------------------------------------------------------------------------- + +class SuiteConfig(QuestionContainer, Enum): + + # -------------------------------------------------------------------------------------------------- + + eda_controlpert_atmos_tier1_fast = QuestionList( + list_name="eda_controlpert_atmos_tier1", + questions=[ + sq.common, + qd.start_cycle_point("2023-10-10T00:00:00Z"), + qd.final_cycle_point("2023-10-10T06:00:00Z"), + qd.runahead_limit("P2"), + qd.jedi_build_method("use_existing"), + qd.model_components(['geos_atmosphere']), + ], + geos_atmosphere=[ + qd.cycle_times([ + "T00", + ]), + qd.background_experiment('x0050'), + qd.geos_x_background_directory("/discover/nobackup/projects/gmao/dadev/" + "rtodling/archive/Restarts/JEDI/541x"), + qd.geos_x_ensemble_directory("/discover/nobackup/projects/gmao/dadev/" + 'rtodling/archive/541/Milan'), + qd.npx_proc(4), + qd.npy_proc(5), + qd.perhost(120), + qd.window_length("PT6H"), + qd.window_type("3D"), + qd.horizontal_resolution("91"), + qd.gsibec_nlats("91"), + qd.gsibec_nlons("144"), + qd.vertical_resolution("72"), + qd.ensemble_num_members(4), + qd.ensemble_num_chunks(2), + qd.obs_pert_amplitude(0.5), + qd.number_of_iterations([10]), + qd.gradient_norm_reduction(1.e-3), + qd.minimizer("DRPLanczos"), + qd.analysis_variables([ + "eastward_wind", + "northward_wind", + "air_temperature", + "water_vapor_mixing_ratio_wrt_moist_air", + "air_pressure_at_surface", + "air_pressure_levels", + "cloud_liquid_ice", + "cloud_liquid_water", + "rain_water", + "snow_water", + "mole_fraction_of_ozone_in_air", + "geopotential_height_times_gravity_at_surface", + "fraction_of_ocean", + "fraction_of_lake", + "fraction_of_ice", + "skin_temperature_at_surface" + ]), + qd.observations([ + "sondes", + ]), + qd.obs_thinning_rej_fraction(0.8), + qd.ensmeanvariance_spec([ + {"state": "bkg", + "fn_input": "ebkg/mem%mem%/geos.mem%mem%.%yyyy%mm%dd_%hh%MM%ssz.nc4", + "fn_output_mean": "geos.prior.mean", + "fn_output_variance": "geos.prior.variance", + "grid_type": ['cs', 'latlon']}, + {"state": "analysis", + "fn_input": "analysis/mem%mem%/eda.ana.mem%mem%.%yyyy%mm%dd_%hh%MM%ssz.nc4", + "fn_output_mean": "eda.ana.mean", + "fn_output_variance": "eda.ana.variance", + "grid_type": ['cs', 'latlon']}, + ]), + qd.diffstates_spec({ + "state1": + {"fn_input": "geos.prior.mean.%yyyy%mm%dd_%hh%MM%ssz.nc4"}, + "state2": + {"fn_input": "eda.ana.mean.%yyyy%mm%dd_%hh%MM%ssz.nc4"}, + "state_diff": + {"fn_output": "eda.mean-inc", "grid_type": ['cs', 'latlon']}, + "state_type": "ensemble" + }), + qd.clean_patterns(['*.txt', '*.csv']), + ] + ) + + eda_controlpert_atmos_tier1 = QuestionList( + list_name="eda_controlpert_atmos_tier1", + questions=[ + sq.common, + qd.start_cycle_point("2023-10-10T00:00:00Z"), + qd.final_cycle_point("2023-10-10T06:00:00Z"), + qd.runahead_limit("P2"), + qd.jedi_build_method("use_existing"), + qd.model_components(['geos_atmosphere']), + ], + geos_atmosphere=[ + qd.cycle_times([ + "T00", + ]), + qd.background_experiment('x0050'), + qd.geos_x_background_directory("/discover/nobackup/projects/gmao/dadev/" + "rtodling/archive/Restarts/JEDI/541x"), + qd.geos_x_ensemble_directory("/discover/nobackup/projects/gmao/dadev/" + 'rtodling/archive/541/Milan'), + qd.npx_proc(4), + qd.npy_proc(5), + qd.perhost(120), + qd.window_length("PT6H"), + qd.window_type("3D"), + qd.horizontal_resolution("91"), + qd.gsibec_nlats("91"), + qd.gsibec_nlons("144"), + qd.vertical_resolution("72"), + qd.ensemble_num_members(32), + qd.ensemble_num_chunks(16), + qd.obs_pert_amplitude(0.5), + qd.number_of_iterations([50]), + qd.gradient_norm_reduction(1.e-3), + qd.minimizer("DRPLanczos"), + qd.analysis_variables([ + "eastward_wind", + "northward_wind", + "air_temperature", + "water_vapor_mixing_ratio_wrt_moist_air", + "air_pressure_at_surface", + "air_pressure_levels", + "cloud_liquid_ice", + "cloud_liquid_water", + "rain_water", + "snow_water", + "mole_fraction_of_ozone_in_air", + "geopotential_height_times_gravity_at_surface", + "fraction_of_ocean", + "fraction_of_lake", + "fraction_of_ice", + "skin_temperature_at_surface" + ]), + # + # Report first crash point: Bound-1 + # + qd.observations([ + "aircraft_temperature", + "aircraft_wind", + "airs_aqua", + "amsr2_gcom-w1", + "amsua_aqua", + "amsua_metop-b", + "amsua_metop-c", + "amsua_n15", + "amsua_n18", + "amsua_n19", + "atms_n20", + "atms_npp", + ]), + qd.obs_thinning_rej_fraction(0.8), + qd.ensmeanvariance_spec([ + {"state": "bkg", + "fn_input": "ebkg/mem%mem%/geos.mem%mem%.%yyyy%mm%dd_%hh%MM%ssz.nc4", + "fn_output_mean": "geos.prior.mean", + "fn_output_variance": "geos.prior.variance", + "grid_type": ['cs', 'latlon']}, + {"state": "analysis", + "fn_input": "analysis/mem%mem%/eda.ana.mem%mem%.%yyyy%mm%dd_%hh%MM%ssz.nc4", + "fn_output_mean": "eda.ana.mean", + "fn_output_variance": "eda.ana.variance", + "grid_type": ['cs', 'latlon']}, + ]), + qd.diffstates_spec({ + "state1": + {"fn_input": "geos.prior.mean.%yyyy%mm%dd_%hh%MM%ssz.nc4"}, + "state2": + {"fn_input": "eda.ana.mean.%yyyy%mm%dd_%hh%MM%ssz.nc4"}, + "state_diff": + {"fn_output": "eda.mean-inc", "grid_type": ['cs', 'latlon']}, + "state_type": "ensemble" + }), + qd.clean_patterns(['*.txt', '*.csv']), + ] + ) + + # -------------------------------------------------------------------------------------------------- + + eda_controlpert_atmos = QuestionList( + list_name="eda_controlpert_atmos", + questions=[ + eda_controlpert_atmos_tier1_fast + ] + ) + + # -------------------------------------------------------------------------------------------------- diff --git a/src/swell/suites/localensembleda/suite_config.py b/src/swell/suites/localensembleda/suite_config.py index e4c670272..e80f67cb0 100644 --- a/src/swell/suites/localensembleda/suite_config.py +++ b/src/swell/suites/localensembleda/suite_config.py @@ -42,7 +42,6 @@ class SuiteConfig(QuestionContainer, Enum): qd.npx_proc(3), qd.npy_proc(3), qd.cycle_times(['T00']), - qd.background_time_offset("PT3H"), qd.ensemble_num_members(3), qd.skip_ensemble_hofx(True), qd.local_ensemble_solver("Deterministic GETKF"), @@ -56,6 +55,7 @@ class SuiteConfig(QuestionContainer, Enum): qd.observations([ "atms_n20", ]), + qd.window_length("PT6H"), qd.window_type("3D"), qd.clean_patterns(['*.txt']) ] @@ -83,7 +83,6 @@ class SuiteConfig(QuestionContainer, Enum): qd.npy_proc(4), # qd.perhost(32), qd.cycle_times(['T00']), - qd.background_time_offset("PT3H"), qd.ensemble_num_members(16), qd.skip_ensemble_hofx(True), qd.local_ensemble_solver("Deterministic GETKF"), @@ -123,6 +122,7 @@ class SuiteConfig(QuestionContainer, Enum): "amsua_metop-b", "amsua_metop-c" ]), + qd.window_length("PT6H"), qd.window_type("3D"), qd.clean_patterns(['*.txt']) ] diff --git a/src/swell/swell.py b/src/swell/swell.py index 9beb550f9..496dc8d3b 100644 --- a/src/swell/swell.py +++ b/src/swell/swell.py @@ -209,13 +209,17 @@ def launch( @click.option('-a', '--additional-parameter', 'additional_parameter', default=None, help=additional_parameter_help) @click.option('-p', '--ensemblePacket', 'ensemblePacket', default=None, help=ensemble_help) +@click.option('-ichunk', '--ensemble_ichunk', 'ichunk', type=int, default=None, help=ensemble_help) +@click.option('-imem', '--ensemble_imember', 'imember', type=int, default=None, help=ensemble_help) def task( task: str, config: str, datetime: Optional[str], model: Optional[str], additional_parameter: Optional[str], - ensemblePacket: Optional[str] + ensemblePacket: Optional[str], + ichunk: Optional[int], + imember: Optional[int] ) -> None: """ Run a workflow task @@ -228,7 +232,7 @@ def task( """ task_wrapper(task, config, datetime, model, additional_parameter, - ensemblePacket) + ensemblePacket, ichunk, imember) # -------------------------------------------------------------------------------------------------- @@ -276,9 +280,9 @@ def test(test: str) -> None: @click.option('-p', '--platform', 'platform', type=click.Choice(get_platforms()), default="nccs_discover_sles15", help=platform_help) @click.argument('suite', type=click.Choice(("hofx", "3dvar_marine", "3dvar_atmos", - "localensembleda", "3dvar_cycle"))) + "3dvar_cycle"))) def t1test( - suite: Literal["hofx", "3dvar_marine", "3dvar_atmos", "localensembleda", "3dvar_cycle"], + suite: Literal["hofx", "3dvar_marine", "3dvar_atmos", "3dvar_cycle"], platform: Optional[str] = "nccs_discover_sles15" ) -> None: """ diff --git a/src/swell/tasks/base/task_base.py b/src/swell/tasks/base/task_base.py index 705a24180..32454d5aa 100644 --- a/src/swell/tasks/base/task_base.py +++ b/src/swell/tasks/base/task_base.py @@ -42,6 +42,8 @@ def __init__( model: str, ensemblePacket: Optional[str], additional_parameter: Optional[str], + ichunk: int | None, + imember: int | None, task_name: str ) -> None: @@ -70,6 +72,8 @@ def __init__( # Keep copy of ensemblePacket # --------------------------- self.__ensemble_packet__ = ensemblePacket + self.__ensemble_ichunk__ = ichunk + self.__ensemble_imember__ = imember # Keep copy of model directive # ---------------------------- @@ -173,6 +177,16 @@ def get_ensemble_packet(self) -> Optional[str]: # ---------------------------------------------------------------------------------------------- + def get_ensemble_ichunk(self) -> int | None: + return self.__ensemble_ichunk__ + + # ---------------------------------------------------------------------------------------------- + + def get_ensemble_imember(self) -> int | None: + return self.__ensemble_imember__ + + # ---------------------------------------------------------------------------------------------- + def get_model(self) -> str: return self.__model__ @@ -285,7 +299,9 @@ def create_task( datetime: Union[str, dt, None], model: str, additional_parameter: str | None, - ensemblePacket: Optional[str] + ensemblePacket: Optional[str], + ichunk: int | None = None, + imember: int | None = None, ) -> taskBase: # Convert camel case string to snake case @@ -314,7 +330,8 @@ def create_task( factory_logger.info(f'Using module swell.tasks.{task_lower}') # Return task object - return task_class(config, datetime, model, ensemblePacket, additional_parameter, task) + return task_class(config, datetime, model, ensemblePacket, + additional_parameter, ichunk, imember, task) # -------------------------------------------------------------------------------------------------- @@ -355,14 +372,16 @@ def task_wrapper( datetime: Union[str, dt, None], model: Optional[str], additional_parameter: str | None, - ensemblePacket: Optional[str] + ensemblePacket: Optional[str], + ichunk: int | None = None, + imember: int | None = None, ) -> None: # Create the object constrc_start = time.perf_counter() creator = taskFactory() task_object = creator.create_task(task, config, datetime, model, additional_parameter, - ensemblePacket) + ensemblePacket, ichunk=ichunk, imember=imember) constrc_final = time.perf_counter() constrc_time = f'Constructed in {constrc_final - constrc_start:0.4f} seconds' diff --git a/src/swell/tasks/clean_eda.py b/src/swell/tasks/clean_eda.py new file mode 100644 index 000000000..b0558c021 --- /dev/null +++ b/src/swell/tasks/clean_eda.py @@ -0,0 +1,161 @@ +# (C) Copyright 2021- United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + + +# -------------------------------------------------------------------------------------------------- + +import os +import glob +import shutil +from pathlib import Path +from swell.tasks.base.task_base import taskBase + + +# -------------------------------------------------------------------------------------------------- + + +class CleanEda(taskBase): + + # ---------------------------------------------------------------------------------------------- + # this file handles both eda and eda_controlpert cases + # + + def execute(self) -> None: + + # Jedi application name + # --------------------- + jedi_application = 'eda' + + # Parse configuration + # ------------------- + window_type = self.config.window_type() + window_length = self.config.window_length() + forecast_length = self.config.forecast_length(window_length) + background_time_offset = self.config.background_time_offset() + number_of_iterations = self.config.number_of_iterations() + jedi_forecast_model = self.config.jedi_forecast_model(None) + + # Set the observing system records path + self.jedi_rendering.set_obs_records_path(self.config.observing_system_records_path(None)) + + gsibec_nlats = self.config.gsibec_nlats(None) + gsibec_nlons = self.config.gsibec_nlons(None) + gsibec_configuration = self.config.gsibec_configuration(None) + npx_proc = self.config.npx_proc(None) + npy_proc = self.config.npy_proc(None) + npx = self.config.npx(None) + npy = self.config.npy(None) + + # Compute data assimilation window parameters + # -------------------------------------------- + background_time = self.da_window_params.background_time(background_time_offset) + local_background_time = self.da_window_params.local_background_time(window_length, + window_type) + local_background_time_iso = self.da_window_params.local_background_time_iso(window_length, + window_type) + window_begin = self.da_window_params.window_begin(window_length) + window_begin_iso = self.da_window_params.window_begin_iso(window_length) + window_end_iso = self.da_window_params.window_end_iso(window_length) + + # Populate jedi interface templates dictionary + # -------------------------------------------- + self.jedi_rendering.add_key('window_begin_iso', window_begin_iso) + self.jedi_rendering.add_key('window_end_iso', window_end_iso) + self.jedi_rendering.add_key('window_length', window_length) + self.jedi_rendering.add_key('forecast_length', forecast_length) + self.jedi_rendering.add_key('minimizer', self.config.minimizer()) + self.jedi_rendering.add_key('number_of_iterations', number_of_iterations[0]) + self.jedi_rendering.add_key('analysis_variables', self.config.analysis_variables()) + self.jedi_rendering.add_key('saber_central_block', self.config.saber_central_block(None)) + self.jedi_rendering.add_key('saber_outer_block', self.config.saber_outer_block(None)) + self.jedi_rendering.add_key('gradient_norm_reduction', + self.config.gradient_norm_reduction()) + self.jedi_rendering.add_key('marine_models', self.config.marine_models(None)) + + # Background + # ---------- + self.jedi_rendering.add_key('horizontal_resolution', self.config.horizontal_resolution()) + self.jedi_rendering.add_key('local_background_time', local_background_time) + self.jedi_rendering.add_key('local_background_time_iso', local_background_time_iso) + self.jedi_rendering.add_key('ensemble_num_members', self.config.ensemble_num_members()) + self.jedi_rendering.add_key('ensemble_imember', 1) # pass an integer to jinja2 + + # Geometry + # -------- + self.jedi_rendering.add_key('vertical_resolution', self.config.vertical_resolution()) + self.jedi_rendering.add_key('gsibec_nlats', gsibec_nlats) + self.jedi_rendering.add_key('gsibec_nlons', gsibec_nlons) + self.jedi_rendering.add_key('npx_proc', npx_proc) + self.jedi_rendering.add_key('npy_proc', npy_proc) + self.jedi_rendering.add_key('npx', npx) + self.jedi_rendering.add_key('npy', npy) + self.jedi_rendering.add_key('total_processors', self.config.total_processors(None)) + + # Observations + # ------------ + self.jedi_rendering.add_key('background_time', background_time) + self.jedi_rendering.add_key('crtm_coeff_dir', self.config.crtm_coeff_dir(None)) + self.jedi_rendering.add_key('window_begin', window_begin) + + # Atmosphere background error model + # --------------------------------- + if gsibec_configuration is not None: + self.jedi_rendering.add_key('gsibec_configuration', gsibec_configuration) + self.jedi_rendering.add_key('gsibec_nlats', gsibec_nlats) + self.jedi_rendering.add_key('gsibec_nlons', gsibec_nlons) + self.jedi_rendering.add_key('gsibec_npx_proc', npx_proc) + self.jedi_rendering.add_key('gsibec_npy_proc', 6*npy_proc) + + # Model + # ----- + if window_type == '4D': + self.jedi_rendering.add_key('background_frequency', self.config.background_frequency()) + + # Open the JEDI config file and fill initial templates + # ---------------------------------------------------- + jedi_config_dict = self.jedi_rendering.render_oops_file(f'{jedi_application}{window_type}', + window_type, + jedi_forecast_model) + + # This special design works with either eda or eda_control_pert + # handle eda case: analysis/mem00x + # eda controlpert case: analysis_chunk/chunk00x/mem00y + # This avoids blindly search for mem0xx dir, as some of them + # only contains linked analysis files and donot require clean up + # -------------------------------------------------------------- + d1 = os.path.join(self.cycle_dir(), 'analysis_chunk') # control pert case + if not os.path.exists(d1): + d1 = os.path.join(self.cycle_dir(), 'analysis') # eda case + target_dirs = [str(p) for p in Path(d1).rglob('mem*') if p.is_dir()] + self.logger.info(f'target_dirs = {target_dirs}') + + for mem_dir in target_dirs: + d2 = os.path.join(mem_dir, 'fv3-jedi') + if os.path.exists(d2): + if os.path.islink(d2): + # Only remove the symlink itself, never follow it + os.unlink(d2) + self.logger.info(f"Deleted symlink dir: {d2}") + else: + shutil.rmtree(d2, ignore_errors=True) + self.logger.info(f"Deleted directory: {d2}") + for observer in jedi_config_dict['cost function']['observations']['observers']: + # Get observation name + observation = observer['observation_name'] + # Delete input obsfile (.nc4 .tlapse.txt acftbias acftbias_cov) + for file_path in glob.glob(os.path.join(mem_dir, f'{observation}.*')): + if os.path.islink(file_path): + os.unlink(file_path) # safe: removes only the link + self.logger.info(f"Deleted symlink file: {file_path}") + else: + try: + os.remove(file_path) + self.logger.info(f"Deleted file: {file_path}") + except FileNotFoundError: + pass # file disappeared between glob and remove + + +# -------------------------------------------------------------------------------------------------- diff --git a/src/swell/tasks/eva_increment.py b/src/swell/tasks/eva_increment.py index ae798fd3d..711e42473 100644 --- a/src/swell/tasks/eva_increment.py +++ b/src/swell/tasks/eva_increment.py @@ -68,6 +68,8 @@ def execute(self) -> None: incr_file = f'{self.experiment_id()}.increment-iter{iter_no}.{cycle_time_reformat}.nc4' if self.suite_name() == 'localensembleda': incr_file = f'geos.mean-inc.{local_bkg_time}.nc4' + elif self.suite_name().startswith('eda_') and 'atmos' in self.suite_name(): + incr_file = f'eda.mean-inc.{local_bkg_time}.nc4' if window_type == '4D' and 'atmos' in self.suite_name(): incr_file = f'{self.experiment_id()}.increment-iter{iter_no}.{window_begin}.nc4' diff --git a/src/swell/tasks/get_ensemble_geos_experiment.py b/src/swell/tasks/get_ensemble_geos_experiment.py index 11d69feab..178967987 100644 --- a/src/swell/tasks/get_ensemble_geos_experiment.py +++ b/src/swell/tasks/get_ensemble_geos_experiment.py @@ -45,7 +45,7 @@ def execute(self): # ------------ background_experiment = self.config.background_experiment() geos_x_ensemble_directory = self.config.geos_x_ensemble_directory() - background_time_offset = self.config.background_time_offset() + background_time_offset = self.config.ebkg_time_offset() # Since this is an optional task, check if the geos_x_ensemble_directory is # set to /dev/null, if so fail the task diff --git a/src/swell/tasks/run_jedi_diffstates.py b/src/swell/tasks/run_jedi_diffstates.py new file mode 100644 index 000000000..e86fc7319 --- /dev/null +++ b/src/swell/tasks/run_jedi_diffstates.py @@ -0,0 +1,140 @@ +# (C) Copyright 2021- United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + + +# -------------------------------------------------------------------------------------------------- + + +import os +from ruamel.yaml import YAML + +from swell.tasks.base.task_base import taskBase +from swell.utilities.run_jedi_executables import run_executable + + +# -------------------------------------------------------------------------------------------------- + + +class RunJediDiffstates(taskBase): + + # ---------------------------------------------------------------------------------------------- + + def execute(self) -> None: + + # Jedi application name + # --------------------- + jedi_application = 'diffstates' + + # Parse configuration + # ------------------- + jedi_forecast_model = self.config.jedi_forecast_model(None) + generate_yaml_and_exit = self.config.generate_yaml_and_exit(False) + npx_proc = self.config.npx_proc(None) + npy_proc = self.config.npy_proc(None) + + # Compute data assimilation window parameters + window_type = self.config.window_type() + window_length = self.config.window_length() + local_background_time = self.da_window_params.local_background_time(window_length, + window_type) + local_background_time_iso = self.da_window_params.local_background_time_iso(window_length, + window_type) + window_begin_iso = self.da_window_params.window_begin_iso(window_length) + window_end_iso = self.da_window_params.window_end_iso(window_length) + + # Set the observing system records path + self.jedi_rendering.set_obs_records_path(self.config.observing_system_records_path(None)) + + # Populate jedi interface templates dictionary + # -------------------------------------------- + self.jedi_rendering.add_key('window_begin_iso', window_begin_iso) + self.jedi_rendering.add_key('window_end_iso', window_end_iso) + self.jedi_rendering.add_key('window_length', window_length) + self.jedi_rendering.add_key('analysis_variables', self.config.analysis_variables()) + + # Background + self.jedi_rendering.add_key('horizontal_resolution', self.config.horizontal_resolution()) + self.jedi_rendering.add_key('local_background_time', local_background_time) + self.jedi_rendering.add_key('local_background_time_iso', local_background_time_iso) + + # Geometry + self.jedi_rendering.add_key('vertical_resolution', self.config.vertical_resolution()) + self.jedi_rendering.add_key('npx_proc', npx_proc) + self.jedi_rendering.add_key('npy_proc', npy_proc) + + # Diffstates + spec_dict = self.config.diffstates_spec() + self.jedi_rendering.add_key('diffstates_spec', spec_dict) + self.logger.info(f'diffstates = {spec_dict}') + + # Add placeholder names if mock experiment + # ---------------------------------------- + if self.config.mock_experiment(False): + self.jedi_rendering.add_key('experiment_root', 'experiment_root') + self.jedi_rendering.add_key('experiment_id', 'experiment_id') + self.jedi_rendering.add_key('cycle_dir', 'cycle_dir') + + state_type = spec_dict.get('state_type') + self.jedi_rendering.add_key('diffstates_spec_statetype', state_type) + + # loop output grid_type: + grid_type = spec_dict['state_diff'].get('grid_type') + self.logger.info(f'grid_type = {grid_type}') + + if grid_type is None: + grid_type = ['latlon'] + + for output_grid in grid_type: + + self.jedi_rendering.add_key('diffstates_spec_gridtype', output_grid) + + # Jedi configuration file + # ----------------------- + jedi_config_file = os.path.join( + self.cycle_dir(), f'jedi_{jedi_application}_output_{output_grid}_config.yaml') + + # Output log file + # --------------- + output_log_file = os.path.join(self.cycle_dir(), + f'jedi_{jedi_application}_output_{output_grid}.log') + + # Open the JEDI config file and fill templates + # -------------------------------------------- + jedi_config_dict = self.jedi_rendering.render_oops_file(f'{jedi_application}', + window_type, + jedi_forecast_model) + yaml = YAML() + yaml.default_flow_style = False + + # Write the expanded dictionary to YAML file + # ------------------------------------------ + with open(jedi_config_file, 'w') as jedi_config_file_open: + yaml.dump(jedi_config_dict, jedi_config_file_open) + + # Get the JEDI interface metadata + # ------------------------------- + model_component_meta = self.jedi_rendering.render_interface_meta() + + # Compute number of processors + # ---------------------------- + np = eval(str(model_component_meta['total_processors'])) + + # Jedi executable name + # -------------------- + jedi_executable = model_component_meta['executables'][f'{jedi_application}'] + jedi_executable_path = os.path.join(self.experiment_path(), + 'jedi_bundle', 'build', 'bin', jedi_executable) + + # Run the JEDI executable + # ----------------------- + if not generate_yaml_and_exit: + self.logger.info('Running '+jedi_executable_path+' with '+str(np)+' processors.') + run_executable(self.logger, self.cycle_dir(), np, jedi_executable_path, + jedi_config_file, output_log_file) + else: + self.logger.info('YAML generated, now exiting.') + +# -------------------------------------------------------------------------------------------------- diff --git a/src/swell/tasks/run_jedi_eda_control_pert_executable.py b/src/swell/tasks/run_jedi_eda_control_pert_executable.py new file mode 100644 index 000000000..ec9ca2e1d --- /dev/null +++ b/src/swell/tasks/run_jedi_eda_control_pert_executable.py @@ -0,0 +1,343 @@ +# (C) Copyright 2021- United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + + +# -------------------------------------------------------------------------------------------------- + +import os +import shutil +from glob import glob +from pathlib import Path +from ruamel.yaml import YAML + +from swell.tasks.base.task_base import taskBase +from swell.utilities.run_jedi_executables import run_executable +from swell.utilities.yaml_utils import replace_string_value + +# -------------------------------------------------------------------------------------------------- + + +class RunJediEdaControlPertExecutable(taskBase): + + # ---------------------------------------------------------------------------------------------- + # + # ichunk: + # 1:nchunk: normal chunking + # 0 : reorder chunk ana file to analysis/mem00x/ana.nc4 by symlink + # + def execute(self) -> None: + + # Jedi application name + # --------------------- + jedi_application = 'eda_control_pert' + + # Parse configuration + # ------------------- + window_type = self.config.window_type() + window_length = self.config.window_length() + forecast_length = self.config.forecast_length(window_length) + background_time_offset = self.config.background_time_offset() + number_of_iterations = self.config.number_of_iterations() + jedi_forecast_model = self.config.jedi_forecast_model(None) + generate_yaml_and_exit = self.config.generate_yaml_and_exit(False) + perhost = self.config.perhost(None) + + # Set the observing system records path + self.jedi_rendering.set_obs_records_path(self.config.observing_system_records_path(None)) + + gsibec_nlats = self.config.gsibec_nlats(None) + gsibec_nlons = self.config.gsibec_nlons(None) + gsibec_configuration = self.config.gsibec_configuration(None) + npx_proc = self.config.npx_proc(None) + npy_proc = self.config.npy_proc(None) + npx = self.config.npx(None) + npy = self.config.npy(None) + + # Compute data assimilation window parameters + # -------------------------------------------- + background_time = self.da_window_params.background_time(background_time_offset) + local_background_time = self.da_window_params.local_background_time(window_length, + window_type) + local_background_time_iso = self.da_window_params.local_background_time_iso(window_length, + window_type) + window_begin = self.da_window_params.window_begin(window_length) + window_begin_iso = self.da_window_params.window_begin_iso(window_length) + window_end_iso = self.da_window_params.window_end_iso(window_length) + nmember = self.config.ensemble_num_members() + nchunk = self.config.ensemble_num_chunks() + ichunk = self.get_ensemble_ichunk() + + # exit execute if ichunk=-1: meaning reoder analysis files and return + # ------------------------------------------------------------------- + if ichunk == -1: + npert = int(nmember/nchunk) + istart = 0 + imem = 0 + for xchunk in range(1, nchunk+1): + if xchunk == 1: + iend = npert - 1 # mem: [0, 1 ... npert-1] : [ctrl, all pert] + else: + iend = npert # mem: [0, 1 ... npert] : [ctrl, all pert] + for imem_in_chunk in range(istart, iend+1): + # skip extra ctrl, because of ichunk=-1 + if xchunk > 1 and imem_in_chunk == 0: + continue + else: + imem += 1 + dir_a = f'analysis_chunk/chunk{xchunk:03d}/mem{imem_in_chunk:03d}' + dir_b = f'analysis/mem{imem:03d}' + dir_a_full = os.path.join(self.cycle_dir(), dir_a, + f'eda.ana.mem{imem_in_chunk:03d}.*.nc4') + fa_list = glob(dir_a_full) + if fa_list: + fa = fa_list[0] + else: + fa = '' + self.logger.error( + f"analysis files not found ichunk={ichunk}, " + f"imem_in_chunk={imem_in_chunk}") + tail = '.'.join(os.path.basename(fa).split('.')[-2:]) + fb = os.path.join(self.cycle_dir(), dir_b, f'eda.ana.mem{imem:03d}.{tail}') + # link fa to fb + # Convert strings to Path objects + fa = Path(fa) + fb = Path(fb) + # a1. Make the directory if it does not exist + fb.parent.mkdir(parents=True, exist_ok=True) + # a2. If fb exists (or is a broken symlink), safely remove it + if fb.is_symlink() or fb.exists(): + fb.unlink() + # a3. Create the symbolic link + # .resolve() gets the absolute path (like realpath in bash) + fb.symlink_to(fa.resolve()) + self.logger.info(f"Successfully linked {fa} to {fb}") + return + + # Populate jedi interface templates dictionary + # -------------------------------------------- + self.jedi_rendering.add_key('window_begin_iso', window_begin_iso) + self.jedi_rendering.add_key('window_end_iso', window_end_iso) + self.jedi_rendering.add_key('window_length', window_length) + self.jedi_rendering.add_key('forecast_length', forecast_length) + self.jedi_rendering.add_key('minimizer', self.config.minimizer()) + self.jedi_rendering.add_key('number_of_iterations', number_of_iterations[0]) + self.jedi_rendering.add_key('analysis_variables', self.config.analysis_variables()) + self.jedi_rendering.add_key('saber_central_block', self.config.saber_central_block(None)) + self.jedi_rendering.add_key('saber_outer_block', self.config.saber_outer_block(None)) + self.jedi_rendering.add_key('gradient_norm_reduction', + self.config.gradient_norm_reduction()) + self.jedi_rendering.add_key('marine_models', self.config.marine_models(None)) + + # Background + # ---------- + self.jedi_rendering.add_key('horizontal_resolution', self.config.horizontal_resolution()) + self.jedi_rendering.add_key('local_background_time', local_background_time) + self.jedi_rendering.add_key('local_background_time_iso', local_background_time_iso) + self.jedi_rendering.add_key('ensemble_num_members', nmember) + self.jedi_rendering.add_key('ensemble_num_chunks', nchunk) + self.jedi_rendering.add_key('ensemble_ichunk', ichunk) + + # Geometry + # -------- + self.jedi_rendering.add_key('vertical_resolution', self.config.vertical_resolution()) + self.jedi_rendering.add_key('gsibec_nlats', gsibec_nlats) + self.jedi_rendering.add_key('gsibec_nlons', gsibec_nlons) + self.jedi_rendering.add_key('npx_proc', npx_proc) + self.jedi_rendering.add_key('npy_proc', npy_proc) + self.jedi_rendering.add_key('npx', npx) + self.jedi_rendering.add_key('npy', npy) + self.jedi_rendering.add_key('total_processors', self.config.total_processors(None)) + + # Observations + # ------------ + self.jedi_rendering.add_key('background_time', background_time) + self.jedi_rendering.add_key('crtm_coeff_dir', self.config.crtm_coeff_dir(None)) + self.jedi_rendering.add_key('window_begin', window_begin) + + # Atmosphere background error model + # --------------------------------- + if gsibec_configuration is not None: + self.jedi_rendering.add_key('gsibec_configuration', gsibec_configuration) + self.jedi_rendering.add_key('gsibec_nlats', gsibec_nlats) + self.jedi_rendering.add_key('gsibec_nlons', gsibec_nlons) + self.jedi_rendering.add_key('gsibec_npx_proc', npx_proc) + self.jedi_rendering.add_key('gsibec_npy_proc', 6*npy_proc) + + # Model + # ----- + if window_type == '4D': + self.jedi_rendering.add_key('background_frequency', self.config.background_frequency()) + # Jedi configuration file + # ----------------------- + fname = f'jedi_{jedi_application}{window_type}_config_chunk{ichunk:03d}.yaml' + jedi_config_file = os.path.join(self.cycle_dir(), fname) + + # Output log file + # --------------- + output_log_file = os.path.join( + self.cycle_dir(), f"jedi_{jedi_application}{window_type}_log_chunk{ichunk:03d}.log") + + # Open the JEDI config file and fill initial templates + # ---------------------------------------------------- + jedi_config_dict = self.jedi_rendering.render_oops_file(f'{jedi_application}', + window_type, + jedi_forecast_model) + + npert = int(nmember/nchunk) + istart = 0 + if ichunk == 1: + iend = npert - 1 # mem: [0, 1 ... npert-1] : [ctrl, all pert] + else: + iend = npert # mem: [0, 1 ... npert] : [ctrl, all pert] + + # round-1: link bkg, copy obs, fv3jedi dir (B and R) + # ---------------------------------------------------- + mem_temp_dir = f'analysis_chunk/chunk{ichunk:003d}/mem%mem_pad%' + for imem in range(istart, iend+1): + if ichunk == 1: + id = imem + 1 + else: + if imem == 0: + id = 1 + else: + id = npert*(ichunk-1) + imem + + # link ebkg to ebkg_chunk + # ----------------------- + ebkg_chunk_dir = f'ebkg_chunk/chunk{ichunk:003d}/' + xdir = os.path.join(self.cycle_dir(), ebkg_chunk_dir, f'geos.mem{imem:03d}') + os.makedirs(xdir, exist_ok=True) + + # copy bkg files to imem dir + f1_list = glob(os.path.join(self.cycle_dir(), f'ebkg/mem{id:03d}/geos.mem*.nc4')) + if not f1_list: + self.logger.error(f"ebkg dir is empty for member id: {id}") + for f1 in f1_list: + f2 = os.path.basename(f1) + f2 = f2.split('.')[2:] + f2 = '.'.join(f2) + f2 = os.path.join(xdir, f2) + if os.path.lexists(f2): + os.remove(f2) + os.symlink(f1, f2) + + # analysis_chunk / chunk00x / mem00y will have its own obs, B and R + # ----------------------------------------------------------------- + mem_dir = f'analysis_chunk/chunk{ichunk:003d}/mem{imem:03d}' + xdir = os.path.join(self.cycle_dir(), mem_dir) + os.makedirs(xdir, exist_ok=True) + + for observer in jedi_config_dict['assimilation'][ + 'cost function']['observations']['observers']: + # Get observation name + observation = observer['observation_name'] + # copy obs input file to avoid multi MPI reading the same file + files = glob(os.path.join(self.cycle_dir(), f'{observation}.*')) + for src_file in files: + self.loggerin.info(f'f= {src_file} is copied to {xdir}') + shutil.copy(src_file, xdir) + + # copy fv3-jedi dir, update dir names + d1 = os.path.join(self.cycle_dir(), 'fv3-jedi') + d2 = os.path.join(self.cycle_dir(), mem_dir, 'fv3-jedi') + shutil.copytree(d1, d2, dirs_exist_ok=True) + + # round-2: modify dir keys in yaml to chunk00x / mem%mem_pad% + # ------------------------------------------------------------ + for observer in jedi_config_dict['assimilation'][ + 'cost function']['observations']['observers']: + # Get observation name + observation = observer['observation_name'] + + # for now delete obsdataout + del observer['obs space']['obsdataout'] + # hxout = observer['obs space']['obsdataout']['engine']['obsfile'] + # dir1, fname = os.path.split(hxout) + # hxout = os.path.join(dir1, mem_temp_dir, fname) + # observer['obs space']['obsdataout']['engine']['obsfile'] = hxout + + obsFileIn = observer['obs space']['obsdatain']['engine']['obsfile'] + dir1, fname = os.path.split(obsFileIn) + obsFileIn = os.path.join(dir1, mem_temp_dir, fname) + observer['obs space']['obsdatain']['engine']['obsfile'] = obsFileIn + + obs_bias = observer.get('obs bias') + if obs_bias is not None: + File = obs_bias['input file'] + dir1, fname = os.path.split(File) + File = os.path.join(dir1, mem_temp_dir, fname) + obs_bias['input file'] = File + # + File = obs_bias['output file'] + dir1, fname = os.path.split(File) + File = os.path.join(dir1, mem_temp_dir, fname) + obs_bias['output file'] = File + # + File = obs_bias.get('covariance', {}).get('output file') + if File is not None: + dir1, fname = os.path.split(File) + File = os.path.join(dir1, mem_temp_dir, fname) + obs_bias['covariance']['output file'] = File + # + File = obs_bias.get('covariance', {}).get('prior', {}).get('input file') + if File is not None: + dir1, fname = os.path.split(File) + File = os.path.join(dir1, mem_temp_dir, fname) + obs_bias['covariance']['prior']['input file'] = File + + observer['obs space']['obs perturbations seed shift'] = (ichunk-1)*npert + + # round-3: point dir to newly created chunks dir + # ---------------------------------------------------- + dir_list = ["bkg", "fv3files", "gsibec", "rcov"] + for i in dir_list: + j = f"fv3-jedi/{i}" + k = f"{mem_temp_dir}/{j}" # Result: analysis/chunk00x/mem_pad/fv3-jedi/rcov + jedi_config_dict = replace_string_value(jedi_config_dict, j, k) + # Result: e.g., analysis/mem002/fv3-jedi/rcov + + ruamel_yaml = YAML() + ruamel_yaml.default_flow_style = False + + # Write the ordered dictionary to YAML file + with open(jedi_config_file, 'w') as jedi_config_file_open: + ruamel_yaml.dump(jedi_config_dict, jedi_config_file_open) + + # Get the JEDI interface metadata + # ------------------------------- + model_component_meta = self.jedi_rendering.render_interface_meta() + + self.logger.info(f"num of processors = {model_component_meta['total_processors']}") + # Compute number of processors + # ---------------------------- + np = eval(str(model_component_meta['total_processors'])) + # modify np by (nmember / nchunk) + 1 + if ichunk == 1: + np = np * npert + else: + np = np * (npert + 1) + + # Jedi executable name + # -------------------- + jedi_executable = model_component_meta['executables']['edaControlPert'] + jedi_executable_path = os.path.join(self.experiment_path(), 'jedi_bundle', + 'build', 'bin', jedi_executable) + + # Run the JEDI executable + # ----------------------- + if not generate_yaml_and_exit: + self.logger.info('Running '+jedi_executable_path+' with '+str(np)+' processors.') + run_executable(self.logger, self.cycle_dir(), np, jedi_executable_path, + jedi_config_file, output_log_file, perhost) + else: + mpi_command = "mpirun" + if not (perhost is None or perhost == "None"): + mpi_command += f" -perhost {perhost}" + mpi_command += f" -np {np} {jedi_executable_path} {jedi_config_file} {output_log_file}" + print(f'intended mpi_command = {mpi_command}') + self.logger.info('YAML generated, now exiting.') + +# -------------------------------------------------------------------------------------------------- diff --git a/src/swell/tasks/run_jedi_eda_executable.py b/src/swell/tasks/run_jedi_eda_executable.py new file mode 100644 index 000000000..a9011f723 --- /dev/null +++ b/src/swell/tasks/run_jedi_eda_executable.py @@ -0,0 +1,262 @@ +# (C) Copyright 2021- United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + + +# -------------------------------------------------------------------------------------------------- + +import os +import glob +import shutil +from ruamel.yaml import YAML + +from swell.tasks.base.task_base import taskBase +from swell.utilities.run_jedi_executables import run_executable +from swell.utilities.yaml_utils import replace_string_value + +# -------------------------------------------------------------------------------------------------- + + +class RunJediEdaExecutable(taskBase): + + # ---------------------------------------------------------------------------------------------- + + def execute(self) -> None: + + # Jedi application name + # --------------------- + jedi_application = 'eda' + + # Parse configuration + # ------------------- + window_type = self.config.window_type() + window_length = self.config.window_length() + forecast_length = self.config.forecast_length(window_length) + background_time_offset = self.config.background_time_offset() + number_of_iterations = self.config.number_of_iterations() + jedi_forecast_model = self.config.jedi_forecast_model(None) + generate_yaml_and_exit = self.config.generate_yaml_and_exit(False) + perhost = self.config.perhost(None) + + # Set the observing system records path + self.jedi_rendering.set_obs_records_path(self.config.observing_system_records_path(None)) + + gsibec_nlats = self.config.gsibec_nlats(None) + gsibec_nlons = self.config.gsibec_nlons(None) + gsibec_configuration = self.config.gsibec_configuration(None) + npx_proc = self.config.npx_proc(None) + npy_proc = self.config.npy_proc(None) + npx = self.config.npx(None) + npy = self.config.npy(None) + + # Compute data assimilation window parameters + # -------------------------------------------- + background_time = self.da_window_params.background_time(background_time_offset) + local_background_time = self.da_window_params.local_background_time(window_length, + window_type) + local_background_time_iso = self.da_window_params.local_background_time_iso(window_length, + window_type) + window_begin = self.da_window_params.window_begin(window_length) + window_begin_iso = self.da_window_params.window_begin_iso(window_length) + window_end_iso = self.da_window_params.window_end_iso(window_length) + obs_pert_amplitude = self.config.obs_pert_amplitude() + nmember = self.config.ensemble_num_members() + imember = self.get_ensemble_imember() + + # Populate jedi interface templates dictionary + # -------------------------------------------- + self.jedi_rendering.add_key('window_begin_iso', window_begin_iso) + self.jedi_rendering.add_key('window_end_iso', window_end_iso) + self.jedi_rendering.add_key('window_length', window_length) + self.jedi_rendering.add_key('forecast_length', forecast_length) + self.jedi_rendering.add_key('minimizer', self.config.minimizer()) + self.jedi_rendering.add_key('number_of_iterations', number_of_iterations[0]) + self.jedi_rendering.add_key('analysis_variables', self.config.analysis_variables()) + self.jedi_rendering.add_key('saber_central_block', self.config.saber_central_block(None)) + self.jedi_rendering.add_key('saber_outer_block', self.config.saber_outer_block(None)) + self.jedi_rendering.add_key('gradient_norm_reduction', + self.config.gradient_norm_reduction()) + self.jedi_rendering.add_key('marine_models', self.config.marine_models(None)) + + # Background + # ---------- + self.jedi_rendering.add_key('horizontal_resolution', self.config.horizontal_resolution()) + self.jedi_rendering.add_key('local_background_time', local_background_time) + self.jedi_rendering.add_key('local_background_time_iso', local_background_time_iso) + self.jedi_rendering.add_key('ensemble_num_members', self.config.ensemble_num_members()) + self.jedi_rendering.add_key('ensemble_imember', imember) + + # Geometry + # -------- + self.jedi_rendering.add_key('vertical_resolution', self.config.vertical_resolution()) + self.jedi_rendering.add_key('gsibec_nlats', gsibec_nlats) + self.jedi_rendering.add_key('gsibec_nlons', gsibec_nlons) + self.jedi_rendering.add_key('npx_proc', npx_proc) + self.jedi_rendering.add_key('npy_proc', npy_proc) + self.jedi_rendering.add_key('npx', npx) + self.jedi_rendering.add_key('npy', npy) + self.jedi_rendering.add_key('total_processors', self.config.total_processors(None)) + + # Observations + # ------------ + self.jedi_rendering.add_key('background_time', background_time) + self.jedi_rendering.add_key('crtm_coeff_dir', self.config.crtm_coeff_dir(None)) + self.jedi_rendering.add_key('window_begin', window_begin) + + # Atmosphere background error model + # --------------------------------- + if gsibec_configuration is not None: + self.jedi_rendering.add_key('gsibec_configuration', gsibec_configuration) + self.jedi_rendering.add_key('gsibec_nlats', gsibec_nlats) + self.jedi_rendering.add_key('gsibec_nlons', gsibec_nlons) + self.jedi_rendering.add_key('gsibec_npx_proc', npx_proc) + self.jedi_rendering.add_key('gsibec_npy_proc', 6*npy_proc) + + # Model + # ----- + if window_type == '4D': + self.jedi_rendering.add_key('background_frequency', self.config.background_frequency()) + + # Jedi configuration file + # ----------------------- + jedi_config_file = os.path.join( + self.cycle_dir(), f'jedi_{jedi_application}{window_type}_config_mem{imember:03d}.yaml') + + # Output log file + # --------------- + output_log_file = os.path.join( + self.cycle_dir(), f'jedi_{jedi_application}{window_type}_log_mem{imember:03d}.log') + + # Open the JEDI config file and fill initial templates + # ---------------------------------------------------- + jedi_config_dict = self.jedi_rendering.render_oops_file(f'{jedi_application}{window_type}', + window_type, + jedi_forecast_model) + + # imember: specify yaml + # ---------------------------------------------------- + if imember == 1: + jedi_config_dict['cost function']['observations'].pop('obs perturbations', None) + else: + jedi_config_dict['cost function']['observations'].update({'obs perturbations': True}) + + # create subdir + mem_dir = f'analysis/mem{imember:003d}/' + xdir = os.path.join(self.cycle_dir(), mem_dir) + os.makedirs(xdir, exist_ok=True) + + for observer in jedi_config_dict['cost function']['observations']['observers']: + # Get observation name + observation = observer['observation_name'] + # copy obs input file to avoid multi MPI reading the same file + files = glob.glob(os.path.join(self.cycle_dir(), f'{observation}.*')) + for src_file in files: + self.logger.info(f'f= {src_file}') + shutil.copy(src_file, xdir) + + if imember > 1: + obs_cov_model = observer.get('obs error', {}).get('covariance model') + self.logger.info(f'{observation}: obs_cov_model = {obs_cov_model}') + if obs_cov_model and 'cross variable covariances' in obs_cov_model: + self.logger.info( + f"Found cross covariance obs: {obs_cov_model}, skip perturbation" + ) + else: + self.logger.info( + f"No cross variable covariance found: {observation}, Obs Error Diagonal" + ) + obs_error_dict = { + 'covariance model': 'diagonal', + 'obs perturbations amplitude': obs_pert_amplitude, + 'zero-mean perturbations': True, + 'member': imember, + 'number of members': nmember + } + observer.update({'obs error': obs_error_dict}) + observer['obs space'].update({'obs perturbations seed': imember}) + + del observer['obs space']['obsdataout'] + # donot output obsdataout + # hxout = observer['obs space']['obsdataout']['engine']['obsfile'] + # dir1, fname = os.path.split(hxout) + # hxout = os.path.join(dir1, mem_dir, fname) + # observer['obs space']['obsdataout']['engine']['obsfile'] = hxout + + obsFileIn = observer['obs space']['obsdatain']['engine']['obsfile'] + dir1, fname = os.path.split(obsFileIn) + obsFileIn = os.path.join(dir1, mem_dir, fname) + observer['obs space']['obsdatain']['engine']['obsfile'] = obsFileIn + + obs_bias = observer.get('obs bias') + if obs_bias is not None: + File = obs_bias['input file'] + dir1, fname = os.path.split(File) + File = os.path.join(dir1, mem_dir, fname) + obs_bias['input file'] = File + # + File = obs_bias['output file'] + dir1, fname = os.path.split(File) + File = os.path.join(dir1, mem_dir, fname) + obs_bias['output file'] = File + # + File = obs_bias.get('covariance', {}).get('output file') + if File is not None: + dir1, fname = os.path.split(File) + File = os.path.join(dir1, mem_dir, fname) + obs_bias['covariance']['output file'] = File + File = obs_bias.get('covariance', {}).get('prior', {}).get('input file') + if File is not None: + dir1, fname = os.path.split(File) + File = os.path.join(dir1, mem_dir, fname) + obs_bias['covariance']['prior']['input file'] = File + + # copy fv3-jedi dir, update dir names + d1 = os.path.join(self.cycle_dir(), 'fv3-jedi') + d2 = os.path.join(self.cycle_dir(), mem_dir, 'fv3-jedi') + shutil.copytree(d1, d2, dirs_exist_ok=True) + + dir_list = ["bkg", "fv3files", "gsibec", "rcov"] + for i in dir_list: + j = f"fv3-jedi/{i}" + k = f"{mem_dir}{j}" + jedi_config_dict = replace_string_value(jedi_config_dict, j, k) + # Result: e.g., analysis/mem002/fv3-jedi/rcov + + ruamel_yaml = YAML() + ruamel_yaml.default_flow_style = False + + # Write the ordered dictionary to YAML file + with open(jedi_config_file, 'w') as jedi_config_file_open: + ruamel_yaml.dump(jedi_config_dict, jedi_config_file_open) + + # Get the JEDI interface metadata + # ------------------------------- + model_component_meta = self.jedi_rendering.render_interface_meta() + + # Compute number of processors + # ---------------------------- + np = eval(str(model_component_meta['total_processors'])) + + # Jedi executable name + # -------------------- + jedi_executable = model_component_meta['executables'][f'variational{window_type}'] + jedi_executable_path = os.path.join(self.experiment_path(), 'jedi_bundle', 'build', 'bin', + jedi_executable) + + # Run the JEDI executable + # ----------------------- + if not generate_yaml_and_exit: + self.logger.info('Running '+jedi_executable_path+' with '+str(np)+' processors.') + run_executable(self.logger, self.cycle_dir(), np, jedi_executable_path, + jedi_config_file, output_log_file, perhost) + else: + mpi_command = "mpirun" + if not (perhost is None or perhost == "None"): + mpi_command += f" -perhost {perhost}" + mpi_command += f" -np {np} {jedi_executable_path} {jedi_config_file} {output_log_file}" + print(f'intended mpi_command = {mpi_command}') + self.logger.info('YAML generated, now exiting.') + +# -------------------------------------------------------------------------------------------------- diff --git a/src/swell/tasks/run_jedi_ensemble_mean_variance.py b/src/swell/tasks/run_jedi_ensemble_mean_variance.py index 7d98c327c..d57444650 100644 --- a/src/swell/tasks/run_jedi_ensemble_mean_variance.py +++ b/src/swell/tasks/run_jedi_ensemble_mean_variance.py @@ -71,6 +71,9 @@ def execute(self) -> None: # Ensemble self.jedi_rendering.add_key('ensemble_num_members', self.config.ensemble_num_members(None)) + self.logger.info(f'self.config.ensmeanvariance_spec = {self.config.ensmeanvariance_spec()}') + meanvar_spec_dict = self.config.ensmeanvariance_spec() + # Add placeholder names if mock experiment # ---------------------------------------- if self.config.mock_experiment(False): @@ -78,49 +81,64 @@ def execute(self) -> None: self.jedi_rendering.add_key('experiment_id', 'experiment_id') self.jedi_rendering.add_key('cycle_dir', 'cycle_dir') - # Jedi configuration file - # ----------------------- - jedi_config_file = os.path.join(self.cycle_dir(), f'jedi_{jedi_application}_config.yaml') - - # Output log file - # --------------- - output_log_file = os.path.join(self.cycle_dir(), f'jedi_{jedi_application}.log') - - # Open the JEDI config file and fill templates - # -------------------------------------------- - jedi_config_dict = self.jedi_rendering.render_oops_file(f'{jedi_application}', - window_type, - jedi_forecast_model) - - yaml = YAML() - yaml.default_flow_style = False - - # Write the expanded dictionary to YAML file - # ------------------------------------------ - with open(jedi_config_file, 'w') as jedi_config_file_open: - yaml.dump(jedi_config_dict, jedi_config_file_open) - - # Get the JEDI interface metadata - # ------------------------------- - model_component_meta = self.jedi_rendering.render_interface_meta() - - # Compute number of processors - # ---------------------------- - np = eval(str(model_component_meta['total_processors'])) - - # Jedi executable name - # -------------------- - jedi_executable = model_component_meta['executables'][f'{jedi_application}'] - jedi_executable_path = os.path.join(self.experiment_path(), 'jedi_bundle', 'build', 'bin', - jedi_executable) - - # Run the JEDI executable + # loop item in ensmeanvariance_spec: state + grid_type # ----------------------- - if not generate_yaml_and_exit: - self.logger.info('Running '+jedi_executable_path+' with '+str(np)+' processors.') - run_executable(self.logger, self.cycle_dir(), np, jedi_executable_path, - jedi_config_file, output_log_file) - else: - self.logger.info('YAML generated, now exiting.') + for idx, spec in enumerate(meanvar_spec_dict, start=1): + + self.jedi_rendering.add_key('ensmeanvariance_spec_item', spec) + state_name = spec.get('state') + + for output_grid in spec.get('grid_type'): + + self.jedi_rendering.add_key('ensmeanvariance_spec_gridtype', output_grid) + + # Jedi configuration file + # ----------------------- + jedi_config_file = os.path.join( + self.cycle_dir(), + f'jedi_{jedi_application}_config_{state_name}_{output_grid}.yaml') + + # Output log file + # --------------- + output_log_file = os.path.join( + self.cycle_dir(), f'jedi_{jedi_application}_{state_name}_{output_grid}.log') + + # Open the JEDI config file and fill templates + # -------------------------------------------- + jedi_config_dict = self.jedi_rendering.render_oops_file(f'{jedi_application}', + window_type, + jedi_forecast_model) + + yaml = YAML() + yaml.default_flow_style = False + + # Write the expanded dictionary to YAML file + # ------------------------------------------ + with open(jedi_config_file, 'w') as jedi_config_file_open: + yaml.dump(jedi_config_dict, jedi_config_file_open) + + # Get the JEDI interface metadata + # ------------------------------- + model_component_meta = self.jedi_rendering.render_interface_meta() + + # Compute number of processors + # ---------------------------- + np = eval(str(model_component_meta['total_processors'])) + + # Jedi executable name + # -------------------- + jedi_executable = model_component_meta['executables'][f'{jedi_application}'] + jedi_executable_path = os.path.join(self.experiment_path(), + 'jedi_bundle', 'build', 'bin', jedi_executable) + + # Run the JEDI executable + # ----------------------- + if not generate_yaml_and_exit: + self.logger.info( + 'Running '+jedi_executable_path+' with '+str(np)+' processors.') + run_executable(self.logger, self.cycle_dir(), np, jedi_executable_path, + jedi_config_file, output_log_file) + else: + self.logger.info('YAML generated, now exiting.') # -------------------------------------------------------------------------------------------------- diff --git a/src/swell/tasks/run_jedi_local_ensemble_da_executable.py b/src/swell/tasks/run_jedi_local_ensemble_da_executable.py index 30bc0db45..754df2319 100644 --- a/src/swell/tasks/run_jedi_local_ensemble_da_executable.py +++ b/src/swell/tasks/run_jedi_local_ensemble_da_executable.py @@ -14,26 +14,11 @@ from swell.swell_path import get_swell_path from swell.tasks.base.task_base import taskBase from swell.utilities.run_jedi_executables import run_executable +from swell.utilities.yaml_utils import replace_key # -------------------------------------------------------------------------------------------------- -def replace_key(obj, old_key, new_key): - """ - Recursively replace dictionary keys in nested dictionaries/lists. - """ - if isinstance(obj, dict): - new_dict = {} - for k, v in obj.items(): - new_k = new_key if k == old_key else k - new_dict[new_k] = replace_key(v, old_key, new_key) - return new_dict - elif isinstance(obj, list): - return [replace_key(item, old_key, new_key) for item in obj] - else: - return obj - - class RunJediLocalEnsembleDaExecutable(taskBase): # ---------------------------------------------------------------------------------------------- diff --git a/src/swell/tasks/task_questions.py b/src/swell/tasks/task_questions.py index 0bd54cd9f..d876b9c99 100644 --- a/src/swell/tasks/task_questions.py +++ b/src/swell/tasks/task_questions.py @@ -392,6 +392,7 @@ class TaskQuestions(QuestionContainer, Enum): questions=[ qd.background_experiment(), qd.background_time_offset(), + qd.ebkg_time_offset(), qd.geos_x_ensemble_directory() ] ) @@ -673,6 +674,7 @@ class TaskQuestions(QuestionContainer, Enum): window_questions, qd.analysis_variables(), qd.ensemble_num_members(), + qd.ensmeanvariance_spec(), qd.generate_yaml_and_exit(), qd.jedi_forecast_model(), qd.observations(), @@ -684,6 +686,22 @@ class TaskQuestions(QuestionContainer, Enum): # -------------------------------------------------------------------------------------------------- + RunJediDiffstates = QuestionList( + list_name="RunJediDiffstates", + questions=[ + np_proc_resolution, + window_questions, + qd.analysis_variables(), + qd.diffstates_spec(), + qd.generate_yaml_and_exit(), + qd.jedi_forecast_model(), + qd.comparison_log_type('diffstates'), + qd.mock_experiment() + ] + ) + + # -------------------------------------------------------------------------------------------------- + RunJediFgatExecutable = QuestionList( list_name="RunJediFgatExecutable", questions=[ @@ -820,6 +838,32 @@ class TaskQuestions(QuestionContainer, Enum): # -------------------------------------------------------------------------------------------------- + RunJediEdaControlPertExecutable = QuestionList( + list_name="RunJediEdaControlPertExecutable", + questions=[ + run_jedi_executable, + qd.ensemble_num_members(), + qd.ensemble_num_chunks(), + qd.obs_pert_amplitude(), + qd.obs_thinning_rej_fraction(), + qd.perhost(), + qd.comparison_log_type('variational'), + qd.mock_experiment() + ] + ) + + # -------------------------------------------------------------------------------------------------- + + CleanEda = QuestionList( + list_name="CleanEda", + questions=[ + run_jedi_executable, + qd.ensemble_num_members() + ] + ) + + # -------------------------------------------------------------------------------------------------- + SaveObsDiags = QuestionList( list_name="SaveObsDiags", questions=[ diff --git a/src/swell/utilities/question_defaults.py b/src/swell/utilities/question_defaults.py index 9e596d227..f61a87f13 100644 --- a/src/swell/utilities/question_defaults.py +++ b/src/swell/utilities/question_defaults.py @@ -8,8 +8,8 @@ # -------------------------------------------------------------------------------------------------- -from dataclasses import dataclass -from typing import List, Dict +from dataclasses import dataclass, field +from typing import List, Dict, Any from swell.utilities.swell_questions import SuiteQuestion, TaskQuestion from swell.utilities.swell_questions import WidgetType as WType @@ -356,6 +356,19 @@ class background_time_offset(TaskQuestion): # -------------------------------------------------------------------------------------------------- + @dataclass + class ebkg_time_offset(TaskQuestion): + default_value: str = "defer_to_model" + question_name: str = "ebkg_time_offset" + models: List[str] = mutable_field([ + "all_models" + ]) + prompt: str = ("How long before the middle of the analysis window did" + " the ensemble background providing forecast begin?") + widget_type: WType = WType.ISO_DURATION + + # -------------------------------------------------------------------------------------------------- + @dataclass class rst_experiment(TaskQuestion): default_value: str = "defer_to_model" @@ -520,6 +533,19 @@ class ensemble_hofx_strategy(TaskQuestion): # -------------------------------------------------------------------------------------------------- + @dataclass + class ensemble_num_chunks(TaskQuestion): + default_value: str = "defer_to_model" + question_name: str = "ensemble_num_chunks" + options: str = "defer_to_model" + models: List[str] = mutable_field([ + "geos_atmosphere" + ]) + prompt: str = "How many chunks shall be used in EDA control pert calculations?" + widget_type: WType = WType.INTEGER + + # -------------------------------------------------------------------------------------------------- + @dataclass class ensemble_num_members(TaskQuestion): default_value: str = "defer_to_model" @@ -533,6 +559,19 @@ class ensemble_num_members(TaskQuestion): # -------------------------------------------------------------------------------------------------- + @dataclass + class obs_pert_amplitude(TaskQuestion): + default_value: str = "defer_to_model" + question_name: str = "obs_pert_amplitude" + options: str = "defer_to_model" + models: List[str] = mutable_field([ + "all_models" + ]) + prompt: str = "Enter obs perturbation amplitude for EDA:" + widget_type: WType = WType.FLOAT + + # -------------------------------------------------------------------------------------------------- + @dataclass class ensmean_only(TaskQuestion): default_value: bool = False @@ -565,6 +604,32 @@ class ensmeanvariance_only(TaskQuestion): # -------------------------------------------------------------------------------------------------- + @dataclass + class ensmeanvariance_spec(TaskQuestion): + default_value: List[Dict[str, str]] = field(default_factory=lambda: [{}]) + question_name: str = "ensmeanvariance_spec" + models: List[str] = mutable_field([ + "all_models" + ]) + ask_question: bool = True + prompt: str = "Configure the ensemble mean and variance specifications:" + widget_type: WType = WType.STRING + + # -------------------------------------------------------------------------------------------------- + + @dataclass + class diffstates_spec(TaskQuestion): + default_value: Dict[str, Any] = field(default_factory=dict) + question_name: str = "diffstates_spec" + models: List[str] = mutable_field([ + "all_models" + ]) + ask_question: bool = True + prompt: str = "Configure the diffstates specifications: [state1, state2]" + widget_type: WType = WType.STRING + + # -------------------------------------------------------------------------------------------------- + @dataclass class existing_geos_gcm_build_path(TaskQuestion): default_value: str = "defer_to_platform" diff --git a/src/swell/utilities/render_jedi_interface_files.py b/src/swell/utilities/render_jedi_interface_files.py index e303d754a..e94383635 100644 --- a/src/swell/utilities/render_jedi_interface_files.py +++ b/src/swell/utilities/render_jedi_interface_files.py @@ -65,6 +65,10 @@ def __init__( self.__template_dict__['experiment_id'] = experiment_id self.__template_dict__['experiment_root'] = experiment_root + # Predefine eda variables to dictionary + self.__template_dict__['ensemble_imember'] = None + self.__template_dict__['ensemble_ichunk'] = None + # List of all potential valid keys that can be used in templates self.valid_template_keys = [ 'analysis_variables', @@ -81,8 +85,16 @@ def __init__( 'ensemble_hofx_packets', 'ensemble_hofx_strategy', 'ensemble_num_members', + 'ensemble_num_chunks', + 'ensemble_ichunk', + 'ensemble_imember', 'ensmean_only', 'ensmeanvariance_only', + 'ensmeanvariance_spec_item', + 'ensmeanvariance_spec_gridtype', + 'diffstates_spec', + 'diffstates_spec_gridtype', + 'diffstates_spec_statetype', 'experiment_id', 'experiment_root', 'final_cycle_point', @@ -136,6 +148,7 @@ def __init__( 'window_end_iso', 'window_length', 'forecast_length', + 'suite_name', ] # List of all potential valid dynamic keys that can be used in templates diff --git a/src/swell/utilities/run_jedi_executables.py b/src/swell/utilities/run_jedi_executables.py index c4457a9c7..46cce2797 100644 --- a/src/swell/utilities/run_jedi_executables.py +++ b/src/swell/utilities/run_jedi_executables.py @@ -74,7 +74,7 @@ def run_executable( # Run the JEDI executable # ----------------------- - if perhost is None: + if (perhost is None or perhost == "None"): logger.info(f"Running {jedi_executable_path} with {str(np)} processors.") command = [ 'mpirun', diff --git a/src/swell/utilities/scripts/create_mock_configs.py b/src/swell/utilities/scripts/create_mock_configs.py index e929bded2..070fcda15 100644 --- a/src/swell/utilities/scripts/create_mock_configs.py +++ b/src/swell/utilities/scripts/create_mock_configs.py @@ -52,10 +52,6 @@ 'model': 'geos_cf', 'executable_type': 'variational'} -defaults_dict['localensembleda'] = {'datetime': atmosphere_default_datetime, - 'model': 'geos_atmosphere', - 'executable_type': 'localensembleda'} - # -------------------------------------------------------------------------------------------------- diff --git a/src/swell/utilities/slurm.py b/src/swell/utilities/slurm.py index da4fb5f20..750fa83b2 100644 --- a/src/swell/utilities/slurm.py +++ b/src/swell/utilities/slurm.py @@ -46,7 +46,9 @@ def prepare_scheduling_dict( task_defaults = { "RunJediVariationalExecutable": {"all": {"nodes": 3}}, "RunJediUfoTestsExecutable": {"all": {"ntasks-per-node": 1}}, - "RunJediConvertStateSoca2ciceExecutable": {"all": {"nodes": 1}} + "RunJediConvertStateSoca2ciceExecutable": {"all": {"nodes": 1}}, + "RunJediEdaExecutable": {"all": {"ntasks-per-node": 126}}, + "RunJediEdaControlPertExecutable": {"all": {"nodes": 3, "ntasks-per-node": 126}} } # Global SLURM settings stored in $HOME/.swell/swell-slurm.yaml @@ -79,7 +81,10 @@ def prepare_scheduling_dict( 'EvaTimeseries', 'GenerateBClimatology', 'RunGeos', + 'RunJediEdaExecutable', + 'RunJediEdaControlPertExecutable', 'RunJediEnsembleMeanVariance', + 'RunJediDiffstates', 'RunJediConvertStateSoca2ciceExecutable', 'RunJediFgatExecutable', 'RunJediHofxEnsembleExecutable', diff --git a/src/swell/utilities/yaml_utils.py b/src/swell/utilities/yaml_utils.py new file mode 100644 index 000000000..d404f33ed --- /dev/null +++ b/src/swell/utilities/yaml_utils.py @@ -0,0 +1,61 @@ +import yaml + +# -------------------------------------------------------------------------------------------------- + + +def replace_key(obj, old_key, new_key): + """ + Recursively replace dictionary keys in nested dictionaries/lists. + """ + if isinstance(obj, dict): + new_dict = {} + for k, v in obj.items(): + new_k = new_key if k == old_key else k + new_dict[new_k] = replace_key(v, old_key, new_key) + return new_dict + elif isinstance(obj, list): + return [replace_key(item, old_key, new_key) for item in obj] + else: + return obj + + +def replace_string_value(data, sa, sb): + """ + Recursively search through a dictionary or list and replaces specific + sa by sb in the value content. sa/sb = string_a/b + """ + if isinstance(data, dict): + # If it's a dictionary, apply recursively to all values + return {k: replace_string_value(v, sa, sb) for k, v in data.items()} + + elif isinstance(data, list): + # If it's a list, apply recursively to all items + return [replace_string_value(item, sa, sb) for item in data] + + elif isinstance(data, str): + # If it's a string, check for our target keys and replace + if sa in data: + # Replaces sa with sb + data = data.replace(sa, sb) + return data + else: + # Return integers, booleans, etc., as-is + return data + +# -------------------------------------------------------------------------------------------------- + + +def print_dict(*args, **kwargs): + return print_dict_as_yaml(*args, **kwargs) + + +def print_dict_as_yaml(data_dict): + # Convert the dictionary to a YAML string + # default_flow_style=False ensures block formatting instead of inline JSON-like formatting + # sort_keys=False preserves your dictionary's original key order (Python 3.7+) + yaml_string = yaml.dump(data_dict, default_flow_style=False, sort_keys=False) + + # Output to the terminal + print(yaml_string) + +# --------------------------------------------------------------------------------------------------