diff --git a/applications/adjoint_tests/rose-meta/lfric-adjoint_tests/versions.py b/applications/adjoint_tests/rose-meta/lfric-adjoint_tests/versions.py index e33d8e39bc..3e9051d6c4 100644 --- a/applications/adjoint_tests/rose-meta/lfric-adjoint_tests/versions.py +++ b/applications/adjoint_tests/rose-meta/lfric-adjoint_tests/versions.py @@ -349,3 +349,82 @@ def upgrade(self, config, meta_config=None): ) return config, self.reports + + +class vn32_t670(MacroUpgrade): + """Upgrade macro for ticket #670 by Thomas Bendall.""" + + BEFORE_TAG = "vn3.2_t400" + AFTER_TAG = "vn3.2_t670" + + def upgrade(self, config, meta_config=None): + # Commands From: rose-meta/lfric-gungho + # Add new nudging namelist options + self.add_setting( + config, ["namelist:nudging", "nudging_method"], "'convolution'" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_theta"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_u"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_v"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_start"], "12.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_end"], "24.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_stop_time"], "10000.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_bottom"], "5" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_top"], "52" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_min_tropopause_level"], "48" + ) + self.change_setting_value( + config, ["namelist:nudging", "nudging_level_bottom"], "6" + ) + self.add_setting( + config, ["namelist:nudging", "num_ref_data_levels"], "137" + ) + self.add_setting(config, ["namelist:nudging", "spectral_kmax"], "20") + self.add_setting(config, ["namelist:nudging", "spectral_kmin"], "2") + self.add_setting( + config, ["namelist:nudging", "spectral_stencil_extent"], "12" + ) + self.add_setting( + config, ["namelist:nudging", "spectral_envelope_width"], "0.1" + ) + # Remove retired settings + self.remove_setting(config, ["namelist:nudging", "nudging_source"]) + self.remove_setting( + config, ["namelist:nudging", "nudging_width_bottom"] + ) + self.remove_setting(config, ["namelist:nudging", "nudging_width_top"]) + self.remove_setting(config, ["namelist:nudging", "nudge_data_levels"]) + # If nudging_mesh_name is still the default '' value, set it to + # match dynamics_mesh_name + nudging_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "nudging_mesh_name"] + ) + if nudging_mesh_name == "''": + dynamics_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "dynamics_mesh_name"] + ) + if dynamics_mesh_name is not None: + self.change_setting_value( + config, + ["namelist:multires_coupling", "nudging_mesh_name"], + dynamics_mesh_name, + ) + + return config, self.reports diff --git a/applications/gravity_wave/rose-meta/lfric-gravity_wave/versions.py b/applications/gravity_wave/rose-meta/lfric-gravity_wave/versions.py index e33d8e39bc..3e9051d6c4 100644 --- a/applications/gravity_wave/rose-meta/lfric-gravity_wave/versions.py +++ b/applications/gravity_wave/rose-meta/lfric-gravity_wave/versions.py @@ -349,3 +349,82 @@ def upgrade(self, config, meta_config=None): ) return config, self.reports + + +class vn32_t670(MacroUpgrade): + """Upgrade macro for ticket #670 by Thomas Bendall.""" + + BEFORE_TAG = "vn3.2_t400" + AFTER_TAG = "vn3.2_t670" + + def upgrade(self, config, meta_config=None): + # Commands From: rose-meta/lfric-gungho + # Add new nudging namelist options + self.add_setting( + config, ["namelist:nudging", "nudging_method"], "'convolution'" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_theta"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_u"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_v"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_start"], "12.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_end"], "24.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_stop_time"], "10000.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_bottom"], "5" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_top"], "52" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_min_tropopause_level"], "48" + ) + self.change_setting_value( + config, ["namelist:nudging", "nudging_level_bottom"], "6" + ) + self.add_setting( + config, ["namelist:nudging", "num_ref_data_levels"], "137" + ) + self.add_setting(config, ["namelist:nudging", "spectral_kmax"], "20") + self.add_setting(config, ["namelist:nudging", "spectral_kmin"], "2") + self.add_setting( + config, ["namelist:nudging", "spectral_stencil_extent"], "12" + ) + self.add_setting( + config, ["namelist:nudging", "spectral_envelope_width"], "0.1" + ) + # Remove retired settings + self.remove_setting(config, ["namelist:nudging", "nudging_source"]) + self.remove_setting( + config, ["namelist:nudging", "nudging_width_bottom"] + ) + self.remove_setting(config, ["namelist:nudging", "nudging_width_top"]) + self.remove_setting(config, ["namelist:nudging", "nudge_data_levels"]) + # If nudging_mesh_name is still the default '' value, set it to + # match dynamics_mesh_name + nudging_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "nudging_mesh_name"] + ) + if nudging_mesh_name == "''": + dynamics_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "dynamics_mesh_name"] + ) + if dynamics_mesh_name is not None: + self.change_setting_value( + config, + ["namelist:multires_coupling", "nudging_mesh_name"], + dynamics_mesh_name, + ) + + return config, self.reports diff --git a/applications/gungho_model/rose-meta/lfric-gungho_model/versions.py b/applications/gungho_model/rose-meta/lfric-gungho_model/versions.py index e33d8e39bc..3e9051d6c4 100644 --- a/applications/gungho_model/rose-meta/lfric-gungho_model/versions.py +++ b/applications/gungho_model/rose-meta/lfric-gungho_model/versions.py @@ -349,3 +349,82 @@ def upgrade(self, config, meta_config=None): ) return config, self.reports + + +class vn32_t670(MacroUpgrade): + """Upgrade macro for ticket #670 by Thomas Bendall.""" + + BEFORE_TAG = "vn3.2_t400" + AFTER_TAG = "vn3.2_t670" + + def upgrade(self, config, meta_config=None): + # Commands From: rose-meta/lfric-gungho + # Add new nudging namelist options + self.add_setting( + config, ["namelist:nudging", "nudging_method"], "'convolution'" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_theta"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_u"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_v"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_start"], "12.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_end"], "24.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_stop_time"], "10000.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_bottom"], "5" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_top"], "52" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_min_tropopause_level"], "48" + ) + self.change_setting_value( + config, ["namelist:nudging", "nudging_level_bottom"], "6" + ) + self.add_setting( + config, ["namelist:nudging", "num_ref_data_levels"], "137" + ) + self.add_setting(config, ["namelist:nudging", "spectral_kmax"], "20") + self.add_setting(config, ["namelist:nudging", "spectral_kmin"], "2") + self.add_setting( + config, ["namelist:nudging", "spectral_stencil_extent"], "12" + ) + self.add_setting( + config, ["namelist:nudging", "spectral_envelope_width"], "0.1" + ) + # Remove retired settings + self.remove_setting(config, ["namelist:nudging", "nudging_source"]) + self.remove_setting( + config, ["namelist:nudging", "nudging_width_bottom"] + ) + self.remove_setting(config, ["namelist:nudging", "nudging_width_top"]) + self.remove_setting(config, ["namelist:nudging", "nudge_data_levels"]) + # If nudging_mesh_name is still the default '' value, set it to + # match dynamics_mesh_name + nudging_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "nudging_mesh_name"] + ) + if nudging_mesh_name == "''": + dynamics_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "dynamics_mesh_name"] + ) + if dynamics_mesh_name is not None: + self.change_setting_value( + config, + ["namelist:multires_coupling", "nudging_mesh_name"], + dynamics_mesh_name, + ) + + return config, self.reports diff --git a/applications/jedi_lfric_tests/rose-meta/jedi_common/versions.py b/applications/jedi_lfric_tests/rose-meta/jedi_common/versions.py index e33d8e39bc..3e9051d6c4 100644 --- a/applications/jedi_lfric_tests/rose-meta/jedi_common/versions.py +++ b/applications/jedi_lfric_tests/rose-meta/jedi_common/versions.py @@ -349,3 +349,82 @@ def upgrade(self, config, meta_config=None): ) return config, self.reports + + +class vn32_t670(MacroUpgrade): + """Upgrade macro for ticket #670 by Thomas Bendall.""" + + BEFORE_TAG = "vn3.2_t400" + AFTER_TAG = "vn3.2_t670" + + def upgrade(self, config, meta_config=None): + # Commands From: rose-meta/lfric-gungho + # Add new nudging namelist options + self.add_setting( + config, ["namelist:nudging", "nudging_method"], "'convolution'" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_theta"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_u"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_v"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_start"], "12.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_end"], "24.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_stop_time"], "10000.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_bottom"], "5" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_top"], "52" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_min_tropopause_level"], "48" + ) + self.change_setting_value( + config, ["namelist:nudging", "nudging_level_bottom"], "6" + ) + self.add_setting( + config, ["namelist:nudging", "num_ref_data_levels"], "137" + ) + self.add_setting(config, ["namelist:nudging", "spectral_kmax"], "20") + self.add_setting(config, ["namelist:nudging", "spectral_kmin"], "2") + self.add_setting( + config, ["namelist:nudging", "spectral_stencil_extent"], "12" + ) + self.add_setting( + config, ["namelist:nudging", "spectral_envelope_width"], "0.1" + ) + # Remove retired settings + self.remove_setting(config, ["namelist:nudging", "nudging_source"]) + self.remove_setting( + config, ["namelist:nudging", "nudging_width_bottom"] + ) + self.remove_setting(config, ["namelist:nudging", "nudging_width_top"]) + self.remove_setting(config, ["namelist:nudging", "nudge_data_levels"]) + # If nudging_mesh_name is still the default '' value, set it to + # match dynamics_mesh_name + nudging_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "nudging_mesh_name"] + ) + if nudging_mesh_name == "''": + dynamics_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "dynamics_mesh_name"] + ) + if dynamics_mesh_name is not None: + self.change_setting_value( + config, + ["namelist:multires_coupling", "nudging_mesh_name"], + dynamics_mesh_name, + ) + + return config, self.reports diff --git a/applications/jedi_lfric_tests/rose-meta/jedi_forecast/versions.py b/applications/jedi_lfric_tests/rose-meta/jedi_forecast/versions.py index cf2f41cb8d..0454b6ccb5 100644 --- a/applications/jedi_lfric_tests/rose-meta/jedi_forecast/versions.py +++ b/applications/jedi_lfric_tests/rose-meta/jedi_forecast/versions.py @@ -358,3 +358,82 @@ def upgrade(self, config, meta_config=None): ) return config, self.reports + + +class vn32_t670(MacroUpgrade): + """Upgrade macro for ticket #670 by Thomas Bendall.""" + + BEFORE_TAG = "vn3.2_t400" + AFTER_TAG = "vn3.2_t670" + + def upgrade(self, config, meta_config=None): + # Commands From: rose-meta/lfric-gungho + # Add new nudging namelist options + self.add_setting( + config, ["namelist:nudging", "nudging_method"], "'convolution'" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_theta"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_u"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_v"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_start"], "12.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_end"], "24.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_stop_time"], "10000.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_bottom"], "5" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_top"], "52" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_min_tropopause_level"], "48" + ) + self.change_setting_value( + config, ["namelist:nudging", "nudging_level_bottom"], "6" + ) + self.add_setting( + config, ["namelist:nudging", "num_ref_data_levels"], "137" + ) + self.add_setting(config, ["namelist:nudging", "spectral_kmax"], "20") + self.add_setting(config, ["namelist:nudging", "spectral_kmin"], "2") + self.add_setting( + config, ["namelist:nudging", "spectral_stencil_extent"], "12" + ) + self.add_setting( + config, ["namelist:nudging", "spectral_envelope_width"], "0.1" + ) + # Remove retired settings + self.remove_setting(config, ["namelist:nudging", "nudging_source"]) + self.remove_setting( + config, ["namelist:nudging", "nudging_width_bottom"] + ) + self.remove_setting(config, ["namelist:nudging", "nudging_width_top"]) + self.remove_setting(config, ["namelist:nudging", "nudge_data_levels"]) + # If nudging_mesh_name is still the default '' value, set it to + # match dynamics_mesh_name + nudging_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "nudging_mesh_name"] + ) + if nudging_mesh_name == "''": + dynamics_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "dynamics_mesh_name"] + ) + if dynamics_mesh_name is not None: + self.change_setting_value( + config, + ["namelist:multires_coupling", "nudging_mesh_name"], + dynamics_mesh_name, + ) + + return config, self.reports diff --git a/applications/jedi_lfric_tests/rose-meta/jedi_forecast_pseudo/versions.py b/applications/jedi_lfric_tests/rose-meta/jedi_forecast_pseudo/versions.py index e33d8e39bc..3e9051d6c4 100644 --- a/applications/jedi_lfric_tests/rose-meta/jedi_forecast_pseudo/versions.py +++ b/applications/jedi_lfric_tests/rose-meta/jedi_forecast_pseudo/versions.py @@ -349,3 +349,82 @@ def upgrade(self, config, meta_config=None): ) return config, self.reports + + +class vn32_t670(MacroUpgrade): + """Upgrade macro for ticket #670 by Thomas Bendall.""" + + BEFORE_TAG = "vn3.2_t400" + AFTER_TAG = "vn3.2_t670" + + def upgrade(self, config, meta_config=None): + # Commands From: rose-meta/lfric-gungho + # Add new nudging namelist options + self.add_setting( + config, ["namelist:nudging", "nudging_method"], "'convolution'" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_theta"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_u"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_v"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_start"], "12.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_end"], "24.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_stop_time"], "10000.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_bottom"], "5" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_top"], "52" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_min_tropopause_level"], "48" + ) + self.change_setting_value( + config, ["namelist:nudging", "nudging_level_bottom"], "6" + ) + self.add_setting( + config, ["namelist:nudging", "num_ref_data_levels"], "137" + ) + self.add_setting(config, ["namelist:nudging", "spectral_kmax"], "20") + self.add_setting(config, ["namelist:nudging", "spectral_kmin"], "2") + self.add_setting( + config, ["namelist:nudging", "spectral_stencil_extent"], "12" + ) + self.add_setting( + config, ["namelist:nudging", "spectral_envelope_width"], "0.1" + ) + # Remove retired settings + self.remove_setting(config, ["namelist:nudging", "nudging_source"]) + self.remove_setting( + config, ["namelist:nudging", "nudging_width_bottom"] + ) + self.remove_setting(config, ["namelist:nudging", "nudging_width_top"]) + self.remove_setting(config, ["namelist:nudging", "nudge_data_levels"]) + # If nudging_mesh_name is still the default '' value, set it to + # match dynamics_mesh_name + nudging_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "nudging_mesh_name"] + ) + if nudging_mesh_name == "''": + dynamics_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "dynamics_mesh_name"] + ) + if dynamics_mesh_name is not None: + self.change_setting_value( + config, + ["namelist:multires_coupling", "nudging_mesh_name"], + dynamics_mesh_name, + ) + + return config, self.reports diff --git a/applications/jedi_lfric_tests/rose-meta/jedi_id_tlm_tests/versions.py b/applications/jedi_lfric_tests/rose-meta/jedi_id_tlm_tests/versions.py index e33d8e39bc..3e9051d6c4 100644 --- a/applications/jedi_lfric_tests/rose-meta/jedi_id_tlm_tests/versions.py +++ b/applications/jedi_lfric_tests/rose-meta/jedi_id_tlm_tests/versions.py @@ -349,3 +349,82 @@ def upgrade(self, config, meta_config=None): ) return config, self.reports + + +class vn32_t670(MacroUpgrade): + """Upgrade macro for ticket #670 by Thomas Bendall.""" + + BEFORE_TAG = "vn3.2_t400" + AFTER_TAG = "vn3.2_t670" + + def upgrade(self, config, meta_config=None): + # Commands From: rose-meta/lfric-gungho + # Add new nudging namelist options + self.add_setting( + config, ["namelist:nudging", "nudging_method"], "'convolution'" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_theta"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_u"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_v"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_start"], "12.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_end"], "24.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_stop_time"], "10000.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_bottom"], "5" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_top"], "52" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_min_tropopause_level"], "48" + ) + self.change_setting_value( + config, ["namelist:nudging", "nudging_level_bottom"], "6" + ) + self.add_setting( + config, ["namelist:nudging", "num_ref_data_levels"], "137" + ) + self.add_setting(config, ["namelist:nudging", "spectral_kmax"], "20") + self.add_setting(config, ["namelist:nudging", "spectral_kmin"], "2") + self.add_setting( + config, ["namelist:nudging", "spectral_stencil_extent"], "12" + ) + self.add_setting( + config, ["namelist:nudging", "spectral_envelope_width"], "0.1" + ) + # Remove retired settings + self.remove_setting(config, ["namelist:nudging", "nudging_source"]) + self.remove_setting( + config, ["namelist:nudging", "nudging_width_bottom"] + ) + self.remove_setting(config, ["namelist:nudging", "nudging_width_top"]) + self.remove_setting(config, ["namelist:nudging", "nudge_data_levels"]) + # If nudging_mesh_name is still the default '' value, set it to + # match dynamics_mesh_name + nudging_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "nudging_mesh_name"] + ) + if nudging_mesh_name == "''": + dynamics_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "dynamics_mesh_name"] + ) + if dynamics_mesh_name is not None: + self.change_setting_value( + config, + ["namelist:multires_coupling", "nudging_mesh_name"], + dynamics_mesh_name, + ) + + return config, self.reports diff --git a/applications/jedi_lfric_tests/rose-meta/jedi_lfric_tests/versions.py b/applications/jedi_lfric_tests/rose-meta/jedi_lfric_tests/versions.py index e33d8e39bc..3e9051d6c4 100644 --- a/applications/jedi_lfric_tests/rose-meta/jedi_lfric_tests/versions.py +++ b/applications/jedi_lfric_tests/rose-meta/jedi_lfric_tests/versions.py @@ -349,3 +349,82 @@ def upgrade(self, config, meta_config=None): ) return config, self.reports + + +class vn32_t670(MacroUpgrade): + """Upgrade macro for ticket #670 by Thomas Bendall.""" + + BEFORE_TAG = "vn3.2_t400" + AFTER_TAG = "vn3.2_t670" + + def upgrade(self, config, meta_config=None): + # Commands From: rose-meta/lfric-gungho + # Add new nudging namelist options + self.add_setting( + config, ["namelist:nudging", "nudging_method"], "'convolution'" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_theta"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_u"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_v"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_start"], "12.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_end"], "24.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_stop_time"], "10000.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_bottom"], "5" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_top"], "52" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_min_tropopause_level"], "48" + ) + self.change_setting_value( + config, ["namelist:nudging", "nudging_level_bottom"], "6" + ) + self.add_setting( + config, ["namelist:nudging", "num_ref_data_levels"], "137" + ) + self.add_setting(config, ["namelist:nudging", "spectral_kmax"], "20") + self.add_setting(config, ["namelist:nudging", "spectral_kmin"], "2") + self.add_setting( + config, ["namelist:nudging", "spectral_stencil_extent"], "12" + ) + self.add_setting( + config, ["namelist:nudging", "spectral_envelope_width"], "0.1" + ) + # Remove retired settings + self.remove_setting(config, ["namelist:nudging", "nudging_source"]) + self.remove_setting( + config, ["namelist:nudging", "nudging_width_bottom"] + ) + self.remove_setting(config, ["namelist:nudging", "nudging_width_top"]) + self.remove_setting(config, ["namelist:nudging", "nudge_data_levels"]) + # If nudging_mesh_name is still the default '' value, set it to + # match dynamics_mesh_name + nudging_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "nudging_mesh_name"] + ) + if nudging_mesh_name == "''": + dynamics_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "dynamics_mesh_name"] + ) + if dynamics_mesh_name is not None: + self.change_setting_value( + config, + ["namelist:multires_coupling", "nudging_mesh_name"], + dynamics_mesh_name, + ) + + return config, self.reports diff --git a/applications/jedi_lfric_tests/rose-meta/jedi_tlm_forecast_tl/versions.py b/applications/jedi_lfric_tests/rose-meta/jedi_tlm_forecast_tl/versions.py index e33d8e39bc..3e9051d6c4 100644 --- a/applications/jedi_lfric_tests/rose-meta/jedi_tlm_forecast_tl/versions.py +++ b/applications/jedi_lfric_tests/rose-meta/jedi_tlm_forecast_tl/versions.py @@ -349,3 +349,82 @@ def upgrade(self, config, meta_config=None): ) return config, self.reports + + +class vn32_t670(MacroUpgrade): + """Upgrade macro for ticket #670 by Thomas Bendall.""" + + BEFORE_TAG = "vn3.2_t400" + AFTER_TAG = "vn3.2_t670" + + def upgrade(self, config, meta_config=None): + # Commands From: rose-meta/lfric-gungho + # Add new nudging namelist options + self.add_setting( + config, ["namelist:nudging", "nudging_method"], "'convolution'" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_theta"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_u"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_v"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_start"], "12.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_end"], "24.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_stop_time"], "10000.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_bottom"], "5" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_top"], "52" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_min_tropopause_level"], "48" + ) + self.change_setting_value( + config, ["namelist:nudging", "nudging_level_bottom"], "6" + ) + self.add_setting( + config, ["namelist:nudging", "num_ref_data_levels"], "137" + ) + self.add_setting(config, ["namelist:nudging", "spectral_kmax"], "20") + self.add_setting(config, ["namelist:nudging", "spectral_kmin"], "2") + self.add_setting( + config, ["namelist:nudging", "spectral_stencil_extent"], "12" + ) + self.add_setting( + config, ["namelist:nudging", "spectral_envelope_width"], "0.1" + ) + # Remove retired settings + self.remove_setting(config, ["namelist:nudging", "nudging_source"]) + self.remove_setting( + config, ["namelist:nudging", "nudging_width_bottom"] + ) + self.remove_setting(config, ["namelist:nudging", "nudging_width_top"]) + self.remove_setting(config, ["namelist:nudging", "nudge_data_levels"]) + # If nudging_mesh_name is still the default '' value, set it to + # match dynamics_mesh_name + nudging_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "nudging_mesh_name"] + ) + if nudging_mesh_name == "''": + dynamics_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "dynamics_mesh_name"] + ) + if dynamics_mesh_name is not None: + self.change_setting_value( + config, + ["namelist:multires_coupling", "nudging_mesh_name"], + dynamics_mesh_name, + ) + + return config, self.reports diff --git a/applications/jedi_lfric_tests/rose-meta/jedi_tlm_tests/versions.py b/applications/jedi_lfric_tests/rose-meta/jedi_tlm_tests/versions.py index e33d8e39bc..3e9051d6c4 100644 --- a/applications/jedi_lfric_tests/rose-meta/jedi_tlm_tests/versions.py +++ b/applications/jedi_lfric_tests/rose-meta/jedi_tlm_tests/versions.py @@ -349,3 +349,82 @@ def upgrade(self, config, meta_config=None): ) return config, self.reports + + +class vn32_t670(MacroUpgrade): + """Upgrade macro for ticket #670 by Thomas Bendall.""" + + BEFORE_TAG = "vn3.2_t400" + AFTER_TAG = "vn3.2_t670" + + def upgrade(self, config, meta_config=None): + # Commands From: rose-meta/lfric-gungho + # Add new nudging namelist options + self.add_setting( + config, ["namelist:nudging", "nudging_method"], "'convolution'" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_theta"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_u"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_v"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_start"], "12.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_end"], "24.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_stop_time"], "10000.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_bottom"], "5" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_top"], "52" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_min_tropopause_level"], "48" + ) + self.change_setting_value( + config, ["namelist:nudging", "nudging_level_bottom"], "6" + ) + self.add_setting( + config, ["namelist:nudging", "num_ref_data_levels"], "137" + ) + self.add_setting(config, ["namelist:nudging", "spectral_kmax"], "20") + self.add_setting(config, ["namelist:nudging", "spectral_kmin"], "2") + self.add_setting( + config, ["namelist:nudging", "spectral_stencil_extent"], "12" + ) + self.add_setting( + config, ["namelist:nudging", "spectral_envelope_width"], "0.1" + ) + # Remove retired settings + self.remove_setting(config, ["namelist:nudging", "nudging_source"]) + self.remove_setting( + config, ["namelist:nudging", "nudging_width_bottom"] + ) + self.remove_setting(config, ["namelist:nudging", "nudging_width_top"]) + self.remove_setting(config, ["namelist:nudging", "nudge_data_levels"]) + # If nudging_mesh_name is still the default '' value, set it to + # match dynamics_mesh_name + nudging_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "nudging_mesh_name"] + ) + if nudging_mesh_name == "''": + dynamics_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "dynamics_mesh_name"] + ) + if dynamics_mesh_name is not None: + self.change_setting_value( + config, + ["namelist:multires_coupling", "nudging_mesh_name"], + dynamics_mesh_name, + ) + + return config, self.reports diff --git a/applications/jules/rose-meta/lfric-jules/versions.py b/applications/jules/rose-meta/lfric-jules/versions.py index e33d8e39bc..3e9051d6c4 100644 --- a/applications/jules/rose-meta/lfric-jules/versions.py +++ b/applications/jules/rose-meta/lfric-jules/versions.py @@ -349,3 +349,82 @@ def upgrade(self, config, meta_config=None): ) return config, self.reports + + +class vn32_t670(MacroUpgrade): + """Upgrade macro for ticket #670 by Thomas Bendall.""" + + BEFORE_TAG = "vn3.2_t400" + AFTER_TAG = "vn3.2_t670" + + def upgrade(self, config, meta_config=None): + # Commands From: rose-meta/lfric-gungho + # Add new nudging namelist options + self.add_setting( + config, ["namelist:nudging", "nudging_method"], "'convolution'" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_theta"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_u"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_v"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_start"], "12.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_end"], "24.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_stop_time"], "10000.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_bottom"], "5" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_top"], "52" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_min_tropopause_level"], "48" + ) + self.change_setting_value( + config, ["namelist:nudging", "nudging_level_bottom"], "6" + ) + self.add_setting( + config, ["namelist:nudging", "num_ref_data_levels"], "137" + ) + self.add_setting(config, ["namelist:nudging", "spectral_kmax"], "20") + self.add_setting(config, ["namelist:nudging", "spectral_kmin"], "2") + self.add_setting( + config, ["namelist:nudging", "spectral_stencil_extent"], "12" + ) + self.add_setting( + config, ["namelist:nudging", "spectral_envelope_width"], "0.1" + ) + # Remove retired settings + self.remove_setting(config, ["namelist:nudging", "nudging_source"]) + self.remove_setting( + config, ["namelist:nudging", "nudging_width_bottom"] + ) + self.remove_setting(config, ["namelist:nudging", "nudging_width_top"]) + self.remove_setting(config, ["namelist:nudging", "nudge_data_levels"]) + # If nudging_mesh_name is still the default '' value, set it to + # match dynamics_mesh_name + nudging_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "nudging_mesh_name"] + ) + if nudging_mesh_name == "''": + dynamics_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "dynamics_mesh_name"] + ) + if dynamics_mesh_name is not None: + self.change_setting_value( + config, + ["namelist:multires_coupling", "nudging_mesh_name"], + dynamics_mesh_name, + ) + + return config, self.reports diff --git a/applications/lfric2lfric/rose-meta/lfric-lfric2lfric/versions.py b/applications/lfric2lfric/rose-meta/lfric-lfric2lfric/versions.py index 061999577b..e69be641ee 100644 --- a/applications/lfric2lfric/rose-meta/lfric-lfric2lfric/versions.py +++ b/applications/lfric2lfric/rose-meta/lfric-lfric2lfric/versions.py @@ -430,3 +430,82 @@ def upgrade(self, config, meta_config=None): ) return config, self.reports + + +class vn32_t670(MacroUpgrade): + """Upgrade macro for ticket #670 by Thomas Bendall.""" + + BEFORE_TAG = "vn3.2_t400" + AFTER_TAG = "vn3.2_t670" + + def upgrade(self, config, meta_config=None): + # Commands From: rose-meta/lfric-gungho + # Add new nudging namelist options + self.add_setting( + config, ["namelist:nudging", "nudging_method"], "'convolution'" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_theta"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_u"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_v"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_start"], "12.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_end"], "24.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_stop_time"], "10000.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_bottom"], "5" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_top"], "52" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_min_tropopause_level"], "48" + ) + self.change_setting_value( + config, ["namelist:nudging", "nudging_level_bottom"], "6" + ) + self.add_setting( + config, ["namelist:nudging", "num_ref_data_levels"], "137" + ) + self.add_setting(config, ["namelist:nudging", "spectral_kmax"], "20") + self.add_setting(config, ["namelist:nudging", "spectral_kmin"], "2") + self.add_setting( + config, ["namelist:nudging", "spectral_stencil_extent"], "12" + ) + self.add_setting( + config, ["namelist:nudging", "spectral_envelope_width"], "0.1" + ) + # Remove retired settings + self.remove_setting(config, ["namelist:nudging", "nudging_source"]) + self.remove_setting( + config, ["namelist:nudging", "nudging_width_bottom"] + ) + self.remove_setting(config, ["namelist:nudging", "nudging_width_top"]) + self.remove_setting(config, ["namelist:nudging", "nudge_data_levels"]) + # If nudging_mesh_name is still the default '' value, set it to + # match dynamics_mesh_name + nudging_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "nudging_mesh_name"] + ) + if nudging_mesh_name == "''": + dynamics_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "dynamics_mesh_name"] + ) + if dynamics_mesh_name is not None: + self.change_setting_value( + config, + ["namelist:multires_coupling", "nudging_mesh_name"], + dynamics_mesh_name, + ) + + return config, self.reports diff --git a/applications/lfric_atm/metadata/field_def_diags.xml b/applications/lfric_atm/metadata/field_def_diags.xml index d73e489716..185430f98f 100644 --- a/applications/lfric_atm/metadata/field_def_diags.xml +++ b/applications/lfric_atm/metadata/field_def_diags.xml @@ -44,6 +44,11 @@ + + + + + diff --git a/applications/lfric_atm/metadata/lfric_dictionary.xml b/applications/lfric_atm/metadata/lfric_dictionary.xml index a2c95f76f1..abe4a50081 100644 --- a/applications/lfric_atm/metadata/lfric_dictionary.xml +++ b/applications/lfric_atm/metadata/lfric_dictionary.xml @@ -434,6 +434,5 @@ - diff --git a/applications/lfric_atm/rose-meta/lfric-lfric_atm/versions.py b/applications/lfric_atm/rose-meta/lfric-lfric_atm/versions.py index e33d8e39bc..3e9051d6c4 100644 --- a/applications/lfric_atm/rose-meta/lfric-lfric_atm/versions.py +++ b/applications/lfric_atm/rose-meta/lfric-lfric_atm/versions.py @@ -349,3 +349,82 @@ def upgrade(self, config, meta_config=None): ) return config, self.reports + + +class vn32_t670(MacroUpgrade): + """Upgrade macro for ticket #670 by Thomas Bendall.""" + + BEFORE_TAG = "vn3.2_t400" + AFTER_TAG = "vn3.2_t670" + + def upgrade(self, config, meta_config=None): + # Commands From: rose-meta/lfric-gungho + # Add new nudging namelist options + self.add_setting( + config, ["namelist:nudging", "nudging_method"], "'convolution'" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_theta"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_u"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_v"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_start"], "12.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_end"], "24.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_stop_time"], "10000.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_bottom"], "5" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_top"], "52" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_min_tropopause_level"], "48" + ) + self.change_setting_value( + config, ["namelist:nudging", "nudging_level_bottom"], "6" + ) + self.add_setting( + config, ["namelist:nudging", "num_ref_data_levels"], "137" + ) + self.add_setting(config, ["namelist:nudging", "spectral_kmax"], "20") + self.add_setting(config, ["namelist:nudging", "spectral_kmin"], "2") + self.add_setting( + config, ["namelist:nudging", "spectral_stencil_extent"], "12" + ) + self.add_setting( + config, ["namelist:nudging", "spectral_envelope_width"], "0.1" + ) + # Remove retired settings + self.remove_setting(config, ["namelist:nudging", "nudging_source"]) + self.remove_setting( + config, ["namelist:nudging", "nudging_width_bottom"] + ) + self.remove_setting(config, ["namelist:nudging", "nudging_width_top"]) + self.remove_setting(config, ["namelist:nudging", "nudge_data_levels"]) + # If nudging_mesh_name is still the default '' value, set it to + # match dynamics_mesh_name + nudging_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "nudging_mesh_name"] + ) + if nudging_mesh_name == "''": + dynamics_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "dynamics_mesh_name"] + ) + if dynamics_mesh_name is not None: + self.change_setting_value( + config, + ["namelist:multires_coupling", "nudging_mesh_name"], + dynamics_mesh_name, + ) + + return config, self.reports diff --git a/applications/linear_model/rose-meta/lfric-linear_model/versions.py b/applications/linear_model/rose-meta/lfric-linear_model/versions.py index e33d8e39bc..3e9051d6c4 100644 --- a/applications/linear_model/rose-meta/lfric-linear_model/versions.py +++ b/applications/linear_model/rose-meta/lfric-linear_model/versions.py @@ -349,3 +349,82 @@ def upgrade(self, config, meta_config=None): ) return config, self.reports + + +class vn32_t670(MacroUpgrade): + """Upgrade macro for ticket #670 by Thomas Bendall.""" + + BEFORE_TAG = "vn3.2_t400" + AFTER_TAG = "vn3.2_t670" + + def upgrade(self, config, meta_config=None): + # Commands From: rose-meta/lfric-gungho + # Add new nudging namelist options + self.add_setting( + config, ["namelist:nudging", "nudging_method"], "'convolution'" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_theta"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_u"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_v"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_start"], "12.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_end"], "24.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_stop_time"], "10000.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_bottom"], "5" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_top"], "52" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_min_tropopause_level"], "48" + ) + self.change_setting_value( + config, ["namelist:nudging", "nudging_level_bottom"], "6" + ) + self.add_setting( + config, ["namelist:nudging", "num_ref_data_levels"], "137" + ) + self.add_setting(config, ["namelist:nudging", "spectral_kmax"], "20") + self.add_setting(config, ["namelist:nudging", "spectral_kmin"], "2") + self.add_setting( + config, ["namelist:nudging", "spectral_stencil_extent"], "12" + ) + self.add_setting( + config, ["namelist:nudging", "spectral_envelope_width"], "0.1" + ) + # Remove retired settings + self.remove_setting(config, ["namelist:nudging", "nudging_source"]) + self.remove_setting( + config, ["namelist:nudging", "nudging_width_bottom"] + ) + self.remove_setting(config, ["namelist:nudging", "nudging_width_top"]) + self.remove_setting(config, ["namelist:nudging", "nudge_data_levels"]) + # If nudging_mesh_name is still the default '' value, set it to + # match dynamics_mesh_name + nudging_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "nudging_mesh_name"] + ) + if nudging_mesh_name == "''": + dynamics_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "dynamics_mesh_name"] + ) + if dynamics_mesh_name is not None: + self.change_setting_value( + config, + ["namelist:multires_coupling", "nudging_mesh_name"], + dynamics_mesh_name, + ) + + return config, self.reports diff --git a/applications/name_transport/rose-meta/lfric-name_transport/versions.py b/applications/name_transport/rose-meta/lfric-name_transport/versions.py index e33d8e39bc..3e9051d6c4 100644 --- a/applications/name_transport/rose-meta/lfric-name_transport/versions.py +++ b/applications/name_transport/rose-meta/lfric-name_transport/versions.py @@ -349,3 +349,82 @@ def upgrade(self, config, meta_config=None): ) return config, self.reports + + +class vn32_t670(MacroUpgrade): + """Upgrade macro for ticket #670 by Thomas Bendall.""" + + BEFORE_TAG = "vn3.2_t400" + AFTER_TAG = "vn3.2_t670" + + def upgrade(self, config, meta_config=None): + # Commands From: rose-meta/lfric-gungho + # Add new nudging namelist options + self.add_setting( + config, ["namelist:nudging", "nudging_method"], "'convolution'" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_theta"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_u"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_v"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_start"], "12.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_end"], "24.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_stop_time"], "10000.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_bottom"], "5" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_top"], "52" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_min_tropopause_level"], "48" + ) + self.change_setting_value( + config, ["namelist:nudging", "nudging_level_bottom"], "6" + ) + self.add_setting( + config, ["namelist:nudging", "num_ref_data_levels"], "137" + ) + self.add_setting(config, ["namelist:nudging", "spectral_kmax"], "20") + self.add_setting(config, ["namelist:nudging", "spectral_kmin"], "2") + self.add_setting( + config, ["namelist:nudging", "spectral_stencil_extent"], "12" + ) + self.add_setting( + config, ["namelist:nudging", "spectral_envelope_width"], "0.1" + ) + # Remove retired settings + self.remove_setting(config, ["namelist:nudging", "nudging_source"]) + self.remove_setting( + config, ["namelist:nudging", "nudging_width_bottom"] + ) + self.remove_setting(config, ["namelist:nudging", "nudging_width_top"]) + self.remove_setting(config, ["namelist:nudging", "nudge_data_levels"]) + # If nudging_mesh_name is still the default '' value, set it to + # match dynamics_mesh_name + nudging_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "nudging_mesh_name"] + ) + if nudging_mesh_name == "''": + dynamics_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "dynamics_mesh_name"] + ) + if dynamics_mesh_name is not None: + self.change_setting_value( + config, + ["namelist:multires_coupling", "nudging_mesh_name"], + dynamics_mesh_name, + ) + + return config, self.reports diff --git a/applications/ngarch/rose-meta/lfric-ngarch/versions.py b/applications/ngarch/rose-meta/lfric-ngarch/versions.py index e33d8e39bc..3e9051d6c4 100644 --- a/applications/ngarch/rose-meta/lfric-ngarch/versions.py +++ b/applications/ngarch/rose-meta/lfric-ngarch/versions.py @@ -349,3 +349,82 @@ def upgrade(self, config, meta_config=None): ) return config, self.reports + + +class vn32_t670(MacroUpgrade): + """Upgrade macro for ticket #670 by Thomas Bendall.""" + + BEFORE_TAG = "vn3.2_t400" + AFTER_TAG = "vn3.2_t670" + + def upgrade(self, config, meta_config=None): + # Commands From: rose-meta/lfric-gungho + # Add new nudging namelist options + self.add_setting( + config, ["namelist:nudging", "nudging_method"], "'convolution'" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_theta"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_u"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_v"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_start"], "12.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_end"], "24.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_stop_time"], "10000.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_bottom"], "5" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_top"], "52" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_min_tropopause_level"], "48" + ) + self.change_setting_value( + config, ["namelist:nudging", "nudging_level_bottom"], "6" + ) + self.add_setting( + config, ["namelist:nudging", "num_ref_data_levels"], "137" + ) + self.add_setting(config, ["namelist:nudging", "spectral_kmax"], "20") + self.add_setting(config, ["namelist:nudging", "spectral_kmin"], "2") + self.add_setting( + config, ["namelist:nudging", "spectral_stencil_extent"], "12" + ) + self.add_setting( + config, ["namelist:nudging", "spectral_envelope_width"], "0.1" + ) + # Remove retired settings + self.remove_setting(config, ["namelist:nudging", "nudging_source"]) + self.remove_setting( + config, ["namelist:nudging", "nudging_width_bottom"] + ) + self.remove_setting(config, ["namelist:nudging", "nudging_width_top"]) + self.remove_setting(config, ["namelist:nudging", "nudge_data_levels"]) + # If nudging_mesh_name is still the default '' value, set it to + # match dynamics_mesh_name + nudging_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "nudging_mesh_name"] + ) + if nudging_mesh_name == "''": + dynamics_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "dynamics_mesh_name"] + ) + if dynamics_mesh_name is not None: + self.change_setting_value( + config, + ["namelist:multires_coupling", "nudging_mesh_name"], + dynamics_mesh_name, + ) + + return config, self.reports diff --git a/applications/shallow_water/rose-meta/lfric-shallow_water/versions.py b/applications/shallow_water/rose-meta/lfric-shallow_water/versions.py index e33d8e39bc..3e9051d6c4 100644 --- a/applications/shallow_water/rose-meta/lfric-shallow_water/versions.py +++ b/applications/shallow_water/rose-meta/lfric-shallow_water/versions.py @@ -349,3 +349,82 @@ def upgrade(self, config, meta_config=None): ) return config, self.reports + + +class vn32_t670(MacroUpgrade): + """Upgrade macro for ticket #670 by Thomas Bendall.""" + + BEFORE_TAG = "vn3.2_t400" + AFTER_TAG = "vn3.2_t670" + + def upgrade(self, config, meta_config=None): + # Commands From: rose-meta/lfric-gungho + # Add new nudging namelist options + self.add_setting( + config, ["namelist:nudging", "nudging_method"], "'convolution'" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_theta"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_u"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_v"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_start"], "12.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_end"], "24.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_stop_time"], "10000.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_bottom"], "5" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_top"], "52" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_min_tropopause_level"], "48" + ) + self.change_setting_value( + config, ["namelist:nudging", "nudging_level_bottom"], "6" + ) + self.add_setting( + config, ["namelist:nudging", "num_ref_data_levels"], "137" + ) + self.add_setting(config, ["namelist:nudging", "spectral_kmax"], "20") + self.add_setting(config, ["namelist:nudging", "spectral_kmin"], "2") + self.add_setting( + config, ["namelist:nudging", "spectral_stencil_extent"], "12" + ) + self.add_setting( + config, ["namelist:nudging", "spectral_envelope_width"], "0.1" + ) + # Remove retired settings + self.remove_setting(config, ["namelist:nudging", "nudging_source"]) + self.remove_setting( + config, ["namelist:nudging", "nudging_width_bottom"] + ) + self.remove_setting(config, ["namelist:nudging", "nudging_width_top"]) + self.remove_setting(config, ["namelist:nudging", "nudge_data_levels"]) + # If nudging_mesh_name is still the default '' value, set it to + # match dynamics_mesh_name + nudging_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "nudging_mesh_name"] + ) + if nudging_mesh_name == "''": + dynamics_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "dynamics_mesh_name"] + ) + if dynamics_mesh_name is not None: + self.change_setting_value( + config, + ["namelist:multires_coupling", "nudging_mesh_name"], + dynamics_mesh_name, + ) + + return config, self.reports diff --git a/applications/transport/rose-meta/lfric-transport/versions.py b/applications/transport/rose-meta/lfric-transport/versions.py index e33d8e39bc..3e9051d6c4 100644 --- a/applications/transport/rose-meta/lfric-transport/versions.py +++ b/applications/transport/rose-meta/lfric-transport/versions.py @@ -349,3 +349,82 @@ def upgrade(self, config, meta_config=None): ) return config, self.reports + + +class vn32_t670(MacroUpgrade): + """Upgrade macro for ticket #670 by Thomas Bendall.""" + + BEFORE_TAG = "vn3.2_t400" + AFTER_TAG = "vn3.2_t670" + + def upgrade(self, config, meta_config=None): + # Commands From: rose-meta/lfric-gungho + # Add new nudging namelist options + self.add_setting( + config, ["namelist:nudging", "nudging_method"], "'convolution'" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_theta"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_u"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_v"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_start"], "12.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_end"], "24.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_stop_time"], "10000.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_bottom"], "5" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_top"], "52" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_min_tropopause_level"], "48" + ) + self.change_setting_value( + config, ["namelist:nudging", "nudging_level_bottom"], "6" + ) + self.add_setting( + config, ["namelist:nudging", "num_ref_data_levels"], "137" + ) + self.add_setting(config, ["namelist:nudging", "spectral_kmax"], "20") + self.add_setting(config, ["namelist:nudging", "spectral_kmin"], "2") + self.add_setting( + config, ["namelist:nudging", "spectral_stencil_extent"], "12" + ) + self.add_setting( + config, ["namelist:nudging", "spectral_envelope_width"], "0.1" + ) + # Remove retired settings + self.remove_setting(config, ["namelist:nudging", "nudging_source"]) + self.remove_setting( + config, ["namelist:nudging", "nudging_width_bottom"] + ) + self.remove_setting(config, ["namelist:nudging", "nudging_width_top"]) + self.remove_setting(config, ["namelist:nudging", "nudge_data_levels"]) + # If nudging_mesh_name is still the default '' value, set it to + # match dynamics_mesh_name + nudging_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "nudging_mesh_name"] + ) + if nudging_mesh_name == "''": + dynamics_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "dynamics_mesh_name"] + ) + if dynamics_mesh_name is not None: + self.change_setting_value( + config, + ["namelist:multires_coupling", "nudging_mesh_name"], + dynamics_mesh_name, + ) + + return config, self.reports diff --git a/documentation/source/science_guide/nudging/convolution.rst b/documentation/source/science_guide/nudging/convolution.rst new file mode 100644 index 0000000000..2a3dad92ba --- /dev/null +++ b/documentation/source/science_guide/nudging/convolution.rst @@ -0,0 +1,196 @@ +.. ----------------------------------------------------------------------------- + (c) Crown copyright Met Office. All rights reserved. + The file LICENCE, distributed with this code, contains details of the terms + under which the code may be used. + + Some of the content of this file has been produced with the assistance of + Met Office GitHub Copilot Enterprise. + ----------------------------------------------------------------------------- +.. _nudging_science_convolution: + +Filtering by Convolution +======================== + +The convolution method (see :ref:`nudging_science_formulation_convolution`) +applies a scale-selective filter :math:`\mathcal{L}` to the difference +between the model field and the reference field, so that only the large +scales are nudged. This section explains why the filter is applied as a +convolution in physical space, and derives the convolution kernel used on +the sphere. + +Consider filtering a field :math:`f` so that only its large scales are +retained. Writing the spectrum of :math:`f` as :math:`\mathcal{F}[f]`, +where :math:`\mathcal{F}` denotes a transform to spectral space, the filter +:math:`\mathcal{L}` acts as a multiplication of the spectrum by a function +:math:`\ell` that varies with wavenumber: + +.. math:: :label: eq:nudging_spectral_filter + + \mathcal{L}[f] = \mathcal{F}^{-1}\left[ \ell \; \mathcal{F}[f] \right] . + +It is vital that :math:`\ell` does not amplify any wavenumbers, which will +cause the numerical model to go unstable. However, it is not always +necessary for an analytic form of :math:`\ell` to be preserved +exactly by the discretisation. For example, an idealised "top-hat" may be +specified for :math:`\ell`, but it may be acceptable for the discretisation to +only approximate this. + +Why a convolution? +------------------ + +The most direct way to apply :eq:`eq:nudging_spectral_filter` would be to +transform :math:`f` to spectral space, multiply by :math:`\ell`, and +transform back. However, for a parallel implementation in which :math:`f` +is distributed geographically across many processors, this is very +inefficient: the transform requires global gather and scatter +communications and/or global summations, whose cost grows exponentially +with the number of processors. + +An alternative is to note the convolution theorem: the spectrum of a +convolved field is the product of the spectra of the field and of the +convolving function. Defining the convolution of :math:`f` with a function +:math:`c` as + +.. math:: :label: eq:nudging_convolution_def + + \mathcal{C}[f, c] := \int_\Omega f(\boldsymbol{y}) \; c(\boldsymbol{y} - \boldsymbol{x}) \; \mathrm{d}\boldsymbol{y} \equiv f \ast c , + +the convolution theorem gives +:math:`\mathcal{F}\!\left[\mathcal{C}[f, c]\right] = \mathcal{F}[f] \, +\mathcal{F}[c]`. Since the filter :eq:`eq:nudging_spectral_filter` acts as a +multiplication in spectral space, it can therefore be applied as a +convolution in physical space, provided the convolving function is chosen so +that :math:`\mathcal{F}[c] = \ell`, i.e. :math:`c = \mathcal{F}^{-1}[\ell]`. + +The key advantage is that, if :math:`c` has compact support, the integral in +:eq:`eq:nudging_convolution_def` reduces to an integral over a local +neighbourhood of each point. On a distributed mesh this requires only +communication of neighbouring data through *halo exchanges*, rather than the +global communications demanded by a spectral transform. + +In practice the convolution is truncated by restricting it to a local +stencil of finite extent. The truncated kernel is no longer exactly equal to +:math:`\mathcal{F}^{-1}[\ell]`, but can remain a good approximation provided +the truncation occurs where :math:`c` is already small. When the nudging of +the spectrum does not need to be exact, this can be acceptable. + +The filter on the sphere +------------------------ + +To apply this approach on the cubed-sphere, the convolution kernel must be +derived for the sphere. The cubed-sphere is non-orthogonal and has +discontinuities at panel edges, which makes a separable +longitude--latitude Fourier filter challenging. Instead, the field is +expanded in spherical harmonics, which are appropriate for quasi-uniform +meshes of the sphere. This use of a convolution for scale-selective +filtering on a cubed-sphere follows the approach of Thatcher and McGregor +(2009). + +The spherical harmonics, as functions of co-latitude :math:`\theta` and +longitude :math:`\varphi`, may be written as + +.. math:: :label: eq:nudging_spherical_harmonic + + Y_{lm}(\theta, \varphi) = N_{lm} \, e^{i m \varphi} \, P_{lm}(\cos\theta) , + +where :math:`P_{lm}` are the associated Legendre polynomials and +:math:`N_{lm}` is a normalisation chosen so that the :math:`Y_{lm}` are +orthonormal over the sphere. A field :math:`f` is expanded as +:math:`f(\theta,\varphi) = \sum_{l,m} a_{lm} Y_{lm}(\theta,\varphi)`. + +We seek the convolution kernel :math:`c` that retains only the spherical +harmonic components with total wavenumber between :math:`k_{min}` and +:math:`k_{max}`. Since the spherical harmonics form an orthonormal basis, +this filter kernel can be written + +.. math:: :label: eq:nudging_general_kernel + + c(\theta,\varphi,\theta',\varphi') = + \sum_{l=k_{min}}^{k_{max}} \sum_{m=-l}^{l} + F_{lm} \, Y_{lm}(\theta,\varphi) \, Y_{lm}^{\ast}(\theta',\varphi') . + +The kernel should depend only on the distance between two points, and not on +their orientation. This requires that there is no dependence on :math:`m` or +:math:`\varphi`, so that :math:`F_{lm} = F_{l0}`. The sum over :math:`m` can +then be carried out using the spherical harmonic addition theorem, + +.. math:: :label: eq:nudging_addition_theorem + + \sum_{m=-l}^{l} Y_{lm}(\theta,\varphi) \, Y_{lm}^{\ast}(\theta',\varphi') + = \frac{2l+1}{4\pi} \, P_l\!\left(\cos\gamma\right) , + +where :math:`P_l` is the Legendre polynomial of degree :math:`l` and +:math:`\gamma` is the great-circle (central) angle between the two points. +Taking an idealised top-hat filter in spectral space, :math:`F_{l0} = 1`, +the kernel becomes a weighted sum of Legendre polynomials of the central +angle: + +.. math:: :label: eq:nudging_kernel + + c(\gamma) = \sum_{l=k_{min}}^{k_{max}} \frac{2l+1}{4\pi} \, P_l(\cos\gamma) . + +The Legendre polynomials may be evaluated efficiently using the recurrence +relation + +.. math:: :label: eq:nudging_legendre_recurrence + + \begin{aligned} + P_0(\cos\gamma) &= 1 , \\ + P_1(\cos\gamma) &= \cos\gamma , \\ + P_l(\cos\gamma) &= \frac{2l-1}{l} \cos\gamma \, P_{l-1}(\cos\gamma) + - \frac{l-1}{l} \, P_{l-2}(\cos\gamma) . + \end{aligned} + +Equation :eq:`eq:nudging_kernel` is the kernel that corresponds exactly to +the idealised top-hat spectral filter. It resembles a :math:`\mathrm{sinc}` +function, (mainly!) decaying away from :math:`\gamma = 0`, and is evaluated at the +points of the stencil surrounding each grid point to give the discrete +convolution weights. + +Convolution Envelope +-------------------- + +However the series of Legendre polynomials does not strictly have compact support +and extends over the whole sphere. An accurate convolution would therefore +require a stencil covering the whole domain. + +Instead we truncate the filter to a local stencil. To avoid the spurious +amplification of any wavenumbers, the filter is multiplied by a Gaussian +envelope that decays to zero towards the edge of the stencil. The width of +this envelope is a configurable parameter of the scheme, and must be chosen +together with the stencil extent (see :ref:`nudging_user_index` for +guidance). + +Thus the actual filter used is + +.. math:: :label: eq:nudging_enveloped_kernel + + c(\gamma) = \left[ \sum_{l=k_{min}}^{k_{max}} \frac{2l+1}{4\pi} + P_l(\cos\gamma) \right] + \exp\left[ -\frac{1}{2} \left( \frac{\gamma}{\sigma} \right)^2 \right] , + +where :math:`\gamma` is the great-circle angle from the central point and +:math:`\sigma` is the width of the Gaussian envelope. + +:numref:`fig_nudging_envelope` illustrates the effect of the envelope. The +underlying Legendre filter :eq:`eq:nudging_kernel` oscillates and decays only +slowly with distance, so that it does not have compact support. Multiplying +by the Gaussian envelope gives the enveloped filter, which decays smoothly to +zero well within the stencil, allowing the convolution to be truncated to a +local region with minimal error, provided the stencil is wide enough for the +envelope to have decayed sufficiently by its edge. + +.. _fig_nudging_envelope: + +.. figure:: legendre_envelope_convolution_filter.svg + :width: 90% + :align: center + + The spherical-harmonic convolution filter for a top-hat retaining total + wavenumbers from :math:`k_{min} = 2` to :math:`k_{max} = 20`, plotted + against great-circle distance. The underlying Legendre filter + (black dashed, :eq:`eq:nudging_kernel`) oscillates and decays only slowly, + whereas the enveloped filter (blue, :eq:`eq:nudging_enveloped_kernel`) + decays smoothly to zero. The red circles show the discrete filter values + sampled at the stencil points for a stencil of depth 12 on a C56 mesh. + diff --git a/documentation/source/science_guide/nudging/formulation.rst b/documentation/source/science_guide/nudging/formulation.rst new file mode 100644 index 0000000000..db3800d591 --- /dev/null +++ b/documentation/source/science_guide/nudging/formulation.rst @@ -0,0 +1,104 @@ +.. ----------------------------------------------------------------------------- + (c) Crown copyright Met Office. All rights reserved. + The file LICENCE, distributed with this code, contains details of the terms + under which the code may be used. + + Some of the content of this file has been produced with the assistance of + Met Office GitHub Copilot Enterprise. + ----------------------------------------------------------------------------- +.. _nudging_science_formulation: + +Formulation +=========== + +Let :math:`X` denote a field being nudged (potential temperature +:math:`\theta`, or one of the horizontal wind components :math:`u`, :math:`v`), +and let :math:`X_{ref}` denote the corresponding reference field, having +already been vertically interpolated onto the model's levels (see +:ref:`nudging_science_vertical_treatment`). At each call to the nudging +scheme, an increment :math:`\Delta X` is calculated and added to the +model's tendency for :math:`X`. + +Two methods are available for calculating :math:`\Delta X`. + +.. _nudging_science_formulation_newtonian: + +Newtonian relaxation +--------------------- + +In Newtonian relaxation, the increment is simply proportional to the local +difference between the field and its reference value: + +.. math:: :label: eq:nudging_newtonian + + \Delta X = w \left( X_{ref} - X \right) + +where :math:`w` is a weight described in :ref:`nudging_science_weights` +below. This relaxes :math:`X` directly towards :math:`X_{ref}` at every +horizontal scale present in the reference data, with no spectral +selectivity. + +.. _nudging_science_formulation_convolution: + +Convolution +----------- + +In the convolution method, the local difference between the field and its +reference value is first filtered so that only the large scales are +retained, before being scaled by the same weight :math:`w`: + +.. math:: :label: eq:nudging_convolution + + \Delta X = w \, \mathcal{L}\left[ X_{ref} - X \right] + +where :math:`\mathcal{L}` is a scale-selective filter that removes the +short wavelengths. Rather than performing this filtering with a global +spectral transform, :math:`\mathcal{L}` is applied as a convolution in +physical space. The motivation for this choice, and the derivation of the +convolution kernel on the sphere, are described in +:ref:`nudging_science_convolution`. + +.. _nudging_science_weights: + +Nudging weights +---------------- + +In both of the above methods, the increment is scaled by a single weight +:math:`w`, which combines a vertical tapering factor, a spin-up ramp, and a +relaxation-timescale factor: + +.. math:: :label: eq:nudging_weight + + w = w_{taper} \; r_{spinup} \; \min\left(1, \frac{\Delta t}{\tau}\right) + +where: + +* :math:`w_{taper}` is a height-dependent factor, computed as described in + :ref:`nudging_science_vertical_treatment`, that ramps the nudging on and + off over a chosen range of vertical levels; +* :math:`r_{spinup}` is a factor that ramps linearly from 0 to 1 over a + configured spin-up period, described below; +* :math:`\Delta t` is the model timestep and :math:`\tau` is a configured + relaxation timescale. If :math:`\tau` is set to zero, this factor is + instead set to 1, so that the field is relaxed fully towards the + reference state within a single timestep. + +Spin-up ramp +~~~~~~~~~~~~ + +The nudging weight can be ramped up gradually over a configurable spin-up +period :math:`[t_{start}, t_{end}]`:, allowing forecasts over short time scales +to be unperturbed. + +.. math:: :label: eq:nudging_spinup + + r_{spinup} = + \begin{cases} + 0 & t \le t_{start} \\ + \dfrac{t - t_{start}}{t_{end} - t_{start}} & t_{start} < t < t_{end} \\ + 1 & t \ge t_{end} + \end{cases} + +where :math:`t` is the elapsed model time since the start of the run. If +:math:`t_{start}` and :math:`t_{end}` are equal, the ramp is skipped and +:math:`r_{spinup} = 1` once :math:`t` exceeds this value. diff --git a/documentation/source/science_guide/nudging/index.rst b/documentation/source/science_guide/nudging/index.rst new file mode 100644 index 0000000000..c27366b4f5 --- /dev/null +++ b/documentation/source/science_guide/nudging/index.rst @@ -0,0 +1,39 @@ +.. ----------------------------------------------------------------------------- + (c) Crown copyright Met Office. All rights reserved. + The file LICENCE, distributed with this code, contains details of the terms + under which the code may be used. + + Some of the content of this file has been produced with the assistance of + Met Office GitHub Copilot Enterprise. + ----------------------------------------------------------------------------- +.. _nudging_science_index: + +Nudging +================ + +Nudging is a technique for relaxing a free-running simulation towards an +external reference state. It can be targeted at a chosen +range of horizontal scales and a chosen range of vertical levels. This +allows the large-scale state of the simulation to be kept close to the +reference state, while still permitting the model to generate its own +small-scale variability. Two approaches are available for this nudging: +selecting only certain horizontal scales (known as spectral nudging), or +relaxing the model state directly towards the reference state at every +horizontal scale (known as Newtonian relaxation). + +This section describes the scientific formulation of nudging in GungHo. It +begins with an overview of the method and the relaxation increment, before +describing how the large scales can be selected using a convolution, how this +may be computed efficiently on a coarse mesh, and finally how the reference +data is prepared in the vertical. For details of how to configure and +enable nudging, see the :ref:`nudging_user_index` section of the User +Guide. + +.. toctree:: + :maxdepth: 1 + + overview + formulation + convolution + multiresolution + vertical_treatment diff --git a/documentation/source/science_guide/nudging/legendre_envelope_convolution_filter.svg b/documentation/source/science_guide/nudging/legendre_envelope_convolution_filter.svg new file mode 100644 index 0000000000..e382c30198 --- /dev/null +++ b/documentation/source/science_guide/nudging/legendre_envelope_convolution_filter.svg @@ -0,0 +1,1759 @@ + + + + + + + + 2026-07-31T15:13:27.955709 + image/svg+xml + + + Matplotlib v3.9.4, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/source/science_guide/nudging/multiresolution.rst b/documentation/source/science_guide/nudging/multiresolution.rst new file mode 100644 index 0000000000..4739c79161 --- /dev/null +++ b/documentation/source/science_guide/nudging/multiresolution.rst @@ -0,0 +1,67 @@ +.. ----------------------------------------------------------------------------- + (c) Crown copyright Met Office. All rights reserved. + The file LICENCE, distributed with this code, contains details of the terms + under which the code may be used. + + Some of the content of this file has been produced with the assistance of + Met Office GitHub Copilot Enterprise. + ----------------------------------------------------------------------------- +.. _nudging_science_multiresolution: + +Multi-resolution Nudging +========================== + +When the convolution method is used, the nudging increment can optionally be +computed on a mesh that is coarser than the one used for the rest of the +model. Since the nudging is usually desired only on large scales, this gives +similar scientific behaviour but at considerably reduced cost.. + +The convolution (see :ref:`nudging_science_convolution`) must span the range +of scales that are being nudged. Nudging is typically applied at horizontal +scales of the order of :math:`2000\,\text{km}`, which at a global +NWP resolution corresponds to a stencil spanning many cells and, in a +distributed implementation, many processor partitions. Computing the +convolution directly on the model mesh would therefore require a very large +halo depth and correspondingly large halo exchanges, which could be as +expensive as the global spectral transforms the convolution is intended to +avoid. + +The cost is greatly reduced by applying the convolution only to a +coarse-grained form of the field. If the coarse mesh has of the order of a +single cell per partition, then nudging over a given physical distance +requires halo exchanges of only a modest depth, which is far more efficient. + +Coarse-grained convolution +-------------------------- + +To describe this, let :math:`\mathcal{A}` be a restriction operator that +performs a local averaging to compute a coarse-grained field, and let +:math:`\mathcal{R}` be the corresponding prolongation operator, which +reconstructs a field on the fine mesh from a coarse-grained field using a +linear reconstruction. The filter :math:`\mathcal{L}` of +:eq:`eq:nudging_convolution` is then applied as + +.. math:: :label: eq:nudging_coarse_convolution + + \mathcal{L}[X_{ref} - X] = + \mathcal{R}\Big[\, \overline{\mathcal{C}}\big[\, + \mathcal{A}[X_{ref} - X], \; \overline{c} \,\big] \Big] , + +where :math:`\overline{\mathcal{C}}` denotes the convolution computed on the +coarse mesh, and :math:`\overline{c}` is the corresponding coarse-mesh +kernel. In words, the increment field is first restricted to the coarse +mesh, the convolution is performed there, and the result is prolongated back +onto the model's mesh, where it is applied to the model's tendency for +:math:`X`. + +When coarse-mesh nudging is not used, no restriction or prolongation takes +place, and the convolution is computed directly on the model's own mesh. + +The external reference fields used to compute the increment (see +:ref:`nudging_science_vertical_treatment`) are themselves prepared on the +coarse mesh when coarse-mesh nudging is used: in particular, the pressure +fields used to construct the model pressure profile are first mapped onto +the coarse mesh before being used in the vertical interpolation. + +For details of how to enable and configure coarse-mesh nudging, see the +:ref:`nudging_user_index` section of the User Guide. diff --git a/documentation/source/science_guide/nudging/overview.rst b/documentation/source/science_guide/nudging/overview.rst new file mode 100644 index 0000000000..87ceced994 --- /dev/null +++ b/documentation/source/science_guide/nudging/overview.rst @@ -0,0 +1,53 @@ +.. ----------------------------------------------------------------------------- + (c) Crown copyright Met Office. All rights reserved. + The file LICENCE, distributed with this code, contains details of the terms + under which the code may be used. + + Some of the content of this file has been produced with the assistance of + Met Office GitHub Copilot Enterprise. + ----------------------------------------------------------------------------- +.. _nudging_science_overview: + +Overview +======== + +Spectral nudging relaxes the potential temperature, :math:`\theta`, and the +horizontal wind components, :math:`(u,v)`, towards reference fields derived +from an external dataset (for example a reanalysis, or the driving model in +a regional or limited-area configuration). + +At each application of the scheme, an increment is calculated for a given +field :math:`X \in \{\theta, u, v\}` and added to the model's prognostic +tendencies. Two methods are available for calculating this increment: + +* **Newtonian relaxation:** a pointwise relaxation of the field towards the + reference field, described in + :ref:`nudging_science_formulation_newtonian`. +* **Convolution:** a physical-space convolution that emulates a spectral + low-pass (or band-pass) filter, so that only the large scales are nudged. + The rationale for this approach, and its derivation on the sphere, are + described in :ref:`nudging_science_convolution`. + +In both cases the increment is scaled by a set of weights which: + +* ramp the nudging increment smoothly up and down with height, targeting + nudging at a chosen range of vertical levels, optionally capped by the + model's diagnosed tropopause level (see + :ref:`nudging_science_vertical_treatment`); +* ramp the nudging on gradually at the start of a run, over a configurable + spin-up period; +* scale the increment according to the model timestep and a configurable + relaxation timescale. + +These weights are described fully in :ref:`nudging_science_formulation`. + +The external reference data is supplied on its own set of pressure levels, +and must be vertically interpolated onto the model's levels before it can be +used; this process is described in +:ref:`nudging_science_vertical_treatment`. + +Finally, when the convolution method is used, nudging can optionally be +performed on a coarser mesh than the mesh used for the rest of the model, +with the resulting increment prolongated back onto the model's mesh. This +improves efficiency and is described in +:ref:`nudging_science_multiresolution`. diff --git a/documentation/source/science_guide/nudging/vertical_treatment.rst b/documentation/source/science_guide/nudging/vertical_treatment.rst new file mode 100644 index 0000000000..14c15ceb60 --- /dev/null +++ b/documentation/source/science_guide/nudging/vertical_treatment.rst @@ -0,0 +1,148 @@ +.. ----------------------------------------------------------------------------- + (c) Crown copyright Met Office. All rights reserved. + The file LICENCE, distributed with this code, contains details of the terms + under which the code may be used. + + Some of the content of this file has been produced with the assistance of + Met Office GitHub Copilot Enterprise. + ----------------------------------------------------------------------------- +.. _nudging_science_vertical_treatment: + +Vertical Treatment +=================== + +The external reference data used for nudging may be supplied on its own set of +hybrid-pressure levels, which do not in general coincide with the model's +levels. Before the reference fields can be used in the nudging increment, +they must therefore be interpolated onto the model's levels, and the +temperature field converted to potential temperature. A height-dependent +weight is also computed, to allow nudging to be restricted to a chosen +range of vertical levels. + +Reference pressure profile +--------------------------- + +The reference dataset's hybrid-pressure levels are defined by coefficients +:math:`a_k`, :math:`b_k`. Given the reference surface pressure, +:math:`p_{s,ref}`, the pressure on reference half-level :math:`k` is + +.. math:: :label: eq:nudging_ref_pressure + + p_{ref,k} = a_k + b_k \, p_{s,ref} , + +and full-level values are formed by averaging :math:`a_k` and :math:`b_k` +between adjacent half levels. + +Model pressure profile +------------------------ + +The corresponding pressure on the model's levels is obtained from the +model's Exner pressure, :math:`\Pi`, evaluated on both sets of model +levels (those on which pressure is held, and those on which potential +temperature is held): + +.. math:: :label: eq:nudging_model_pressure + + p = p_0 \, \Pi^{1/\kappa} + +where :math:`p_0` is a reference pressure and +:math:`\kappa = R_d / c_p`, with :math:`R_d` the gas constant for dry air +and :math:`c_p` the specific heat capacity at constant pressure. + +Temperature to potential temperature +-------------------------------------- + +The reference dataset supplies temperature, :math:`T_{ref}`, rather than +potential temperature. This is converted using the reference pressure +profile computed above: + +.. math:: :label: eq:nudging_T_to_theta + + \theta_{ref} = T_{ref} \left( \frac{p_0}{p_{ref}} \right)^{\kappa} . + +Vertical interpolation +------------------------ + +The reference fields (:math:`\theta_{ref}`, :math:`u_{ref}`, +:math:`v_{ref}`) are interpolated from the reference pressure levels onto +the model's pressure levels by linear interpolation in log-pressure: + +.. math:: :label: eq:nudging_log_p_interp + + X_{ref}(p) = X_{ref,l} + \left( X_{ref,l+1} - X_{ref,l} \right) + \frac{\ln p - \ln p_{ref,l}}{\ln p_{ref,l+1} - \ln p_{ref,l}} + +where :math:`l` is the reference level immediately below the model level +being interpolated to (in pressure). To avoid unbounded extrapolation +above the top or below the bottom of the reference data, the interpolation +factor is limited to the range :math:`[-1, 2]`. The lowest model +:math:`\theta` level is set equal to the value interpolated for the level +immediately above it. + +.. _nudging_science_vertical_taper: + +Vertical tapering +------------------- + +To allow nudging to be targeted at a chosen range of vertical levels, a +height-dependent weight, :math:`w_{taper}` (introduced in +:eq:`eq:nudging_weight`), is computed for each function space +(:math:`\mathbb{W}_3` and :math:`\mathbb{W}_\theta`). The weight ramps +linearly from 0 to 1 between a lower taper level and a lower full-strength +level, remains at 1 between the full-strength levels, and ramps back down +to 0 between an upper full-strength level and an upper taper level: + +.. math:: :label: eq:nudging_vertical_taper + + w_{taper}(\tilde{k}) = + \begin{cases} + 0 & + \tilde{k} \le k_{taper,bot} \\ + \dfrac{\tilde{k} - k_{taper,bot}}{k_{bot} - k_{taper,bot}} & + k_{taper,bot} < \tilde{k} < k_{bot} \\ + 1 & + k_{bot} \le \tilde{k} \le k_{top} \\ + 1 - \dfrac{\tilde{k} - k_{top}}{k_{taper,top} - k_{top}} & + k_{top} < \tilde{k} < k_{taper,top} \\ + 0 & + \tilde{k} \ge k_{taper,top} + \end{cases} + +where :math:`k_{bot}` and :math:`k_{top}` are the model levels marking the +bottom and top of the region over which nudging is applied at full +strength, and :math:`k_{taper,bot}` and :math:`k_{taper,top}` are the +levels below and above which nudging is zero. The quantity +:math:`\tilde{k}` is the :math:`\mathbb{W}_\theta` level index starting from 0, +while :math:`\mathbb{W}_3` points are offset by half a level. Using this common +coordinate ensures that both sets of levels see a consistent, vertically-aligned +taper profile. + +.. _nudging_science_tropopause_cap: + +Tropopause capping +------------------- + +Nudging towards external reference data is generally undesirable well above +the tropopause: the stratosphere is only loosely constrained (if at all) by +the coarse vertical and temporal sampling of typical reference datasets, and +imposing large-scale forcing there can disrupt the model's own +stratospheric transport and composition. Rather than relying solely on a +fixed upper taper level, chosen in advance to sit safely below the +tropopause for an expected range of conditions, the upper extent of the +tapering profile described above may instead be capped dynamically by the +model's own, evolving tropopause height. + +When this option is used, the upper full-strength and taper levels, +:math:`k_{top}` and :math:`k_{taper,top}`, in :eq:`eq:nudging_vertical_taper` +are replaced at each application of the scheme by the lesser of their +configured values and the current tropopause level, diagnosed +independently for each atmospheric column from the model's evolving +temperature structure. This allows the vertical extent of nudging to track +a rising or falling tropopause, rather than remaining fixed, and ensures +nudging is switched off in the stratosphere even where the diagnosed +tropopause sits below the configured upper taper level. A fixed minimum +level may also be enforced as a floor for the diagnosed tropopause level, +guarding against spuriously low diagnoses; setting this floor high enough +disables the dynamic capping altogether, recovering the fixed taper profile +described above. + diff --git a/documentation/source/user_guide/index.rst b/documentation/source/user_guide/index.rst index 91834ca41e..97cd4f3564 100644 --- a/documentation/source/user_guide/index.rst +++ b/documentation/source/user_guide/index.rst @@ -13,3 +13,4 @@ User Guide applications/index checkpointing/index + nudging/index diff --git a/documentation/source/user_guide/nudging/index.rst b/documentation/source/user_guide/nudging/index.rst new file mode 100644 index 0000000000..aea844160b --- /dev/null +++ b/documentation/source/user_guide/nudging/index.rst @@ -0,0 +1,222 @@ +.. ----------------------------------------------------------------------------- + (c) Crown copyright Met Office. All rights reserved. + The file LICENCE, distributed with this code, contains details of the terms + under which the code may be used. + + Some of the content of this file has been produced with the assistance of + Met Office GitHub Copilot Enterprise. + ----------------------------------------------------------------------------- + +.. _nudging_user_index: + +Nudging +======= + +Nudging is a form of external forcing that relaxes model fields towards a +specified reference state, typically taken from analysis or reanalysis data. +It can be used, for example, to keep a forecast close to observed large-scale +conditions, or to spin up a model state consistent with an external data +source. + +A full mathematical description of the nudging scheme is given in the +:ref:`Science Guide `. This page describes how to +configure and use nudging. + +Enabling nudging +---------------- + +Nudging is enabled per-field via the ``namelist:external_forcing`` options +``theta_forcing`` and ``wind_forcing``. Setting either (or both) of these to +``'nudging'`` activates the nudging scheme for potential temperature or the +horizontal wind components respectively: + +.. code-block:: ini + + [namelist:external_forcing] + theta_forcing='nudging' + wind_forcing='nudging' + +The ``namelist:nudging`` namelist must then be configured as described +below. + +Reference data files +--------------------- + +The external reference data used for nudging is read from a file specified +in the ``namelist:files`` namelist: + +.. list-table:: + :header-rows: 1 + :widths: 25 75 + + * - Option + - Description + * - ``nudging_directory`` + - Path to the directory containing the nudging reference data file. + * - ``nudging_filename`` + - Name of the file (within ``nudging_directory``) containing the + reference temperature, wind and surface pressure fields. + +Configuring the nudging scheme +------------------------------- + +The behaviour of the nudging scheme is controlled by the +``namelist:nudging`` namelist: + +.. list-table:: + :header-rows: 1 + :widths: 25 75 + + * - Option + - Description + * - ``nudging_method`` + - Selects the nudging algorithm: ``'newtonian'`` for pointwise + relaxation towards the reference state, or ``'convolution'`` to + relax only the large scales of the field, using a convolution in + physical space that emulates a low-pass top-hat filter in spectral + space (see the :ref:`Science Guide `). + * - ``nudging_relax_time_theta`` / ``nudging_relax_time_u`` / + ``nudging_relax_time_v`` + - Timescale, in hours, over which potential temperature, zonal wind + and meridional wind respectively are relaxed towards the reference + state. Setting a timescale to zero disables nudging of that field + entirely. + * - ``nudging_spinup_start`` / ``nudging_spinup_end`` + - Times, in hours since the start of the run, over which the + strength of nudging is ramped up linearly from zero to full + strength. This allows nudging to be introduced gradually rather + than as a step change. + * - ``nudging_stop_time`` + - Time, in hours since the start of the run, at which nudging is + switched off. + * - ``num_ref_data_levels`` + - Number of vertical levels on which the reference data for temperature + and wind components are provided. + * - ``nudging_level_bottom`` / ``nudging_level_top`` + - Model levels defining the vertical extent over which nudging is + applied at full strength. + * - ``nudging_level_taper_bottom`` / ``nudging_level_taper_top`` + - Model levels below/above which nudging is zero. Nudging is tapered + linearly between ``nudging_level_taper_bottom`` and + ``nudging_level_bottom``, and between ``nudging_level_top`` and + ``nudging_level_taper_top`` (see the :ref:`Science Guide + `). + * - ``nudging_min_tropopause_level`` + - Model level enforced as a floor for the tropopause level used to + cap the vertical extent of nudging (see the :ref:`Science Guide + `). Set to ``0`` to always cap + nudging using the model-diagnosed tropopause level. Set to + ``nudging_level_taper_top`` (or above) to always apply nudging up + to that level, regardless of the diagnosed tropopause. + * - ``spectral_kmin`` / ``spectral_kmax`` + - (``nudging_method = 'convolution'`` only) Minimum and maximum + wavenumbers retained by the top-hat spectral filter. + * - ``spectral_stencil_extent`` + - (``nudging_method = 'convolution'`` only) Extent of the stencil + used to construct the physical-space convolution. + * - ``spectral_envelope_width`` + - (``nudging_method = 'convolution'`` only) Width, in radians, of the + Gaussian envelope applied to the convolution filter (see the + :ref:`Science Guide `). + +Choosing the stencil extent and envelope width +----------------------------------------------- + +The ``spectral_stencil_extent`` and ``spectral_envelope_width`` options must +be chosen together, and some care is needed: + +* The Legendre-polynomial filter that underlies the convolution does not + have compact support, so truncating it to a finite stencil is itself an + approximation. The Gaussian envelope is what makes this truncation + acceptable, by bringing the filter smoothly down to a small value by the + edge of the stencil. +* If ``spectral_envelope_width`` is too large relative to + ``spectral_stencil_extent``, the filter has not decayed sufficiently by + the edge of the stencil. The abrupt truncation this causes can amplify, + rather than damp, some wavenumbers, leading to noise or numerical + instability rather than the intended scale-selective nudging. +* Conversely, if ``spectral_envelope_width`` is too small, the envelope + suppresses part of the range of wavenumbers between + ``spectral_kmin`` and ``spectral_kmax`` that was intended to be retained, + making the filter less selective than requested. +* Increasing ``spectral_stencil_extent`` allows a larger + ``spectral_envelope_width`` to be used safely, but at the cost of a wider + halo exchange and increased computational expense. + +Because of this trade-off, it is recommended to test any new combination of +``spectral_kmin``, ``spectral_kmax``, ``spectral_stencil_extent`` and +``spectral_envelope_width`` for a given mesh resolution, checking that the +resulting weights decay smoothly across the stencil, before using it in +a forecast or research configuration. + +Choosing the vertical extent of nudging +----------------------------------------- + +The four vertical-level options must satisfy +``nudging_level_taper_bottom`` :math:`\le` ``nudging_level_bottom`` +:math:`\le` ``nudging_level_top`` :math:`\le` ``nudging_level_taper_top``; +this ordering is enforced automatically when the namelist is checked. +Nudging is at full strength between ``nudging_level_bottom`` and +``nudging_level_top``, tapers linearly to zero outside this range, and is +exactly zero at and beyond ``nudging_level_taper_bottom`` / +``nudging_level_taper_top``. Setting a taper level equal to the +corresponding full-strength level removes the ramp on that side, giving an +abrupt step instead of a gradual transition. + +Note that these levels are counted differently for the two sets of model +levels that nudging acts on: levels holding potential temperature are counted +from 0 at the surface, while levels holding density or the horizontal wind +components are counted from 1/2 in the lowest level, since they are +vertically staggered by half a level relative to the potential temperature +levels (see the :ref:`Science Guide `). The +same integer level values are used for both, so the two sets of levels see +a consistent, vertically-aligned taper profile. + +Capping nudging at the tropopause +------------------------------------ + +The upper end of the vertical range configured above (``nudging_level_top`` +and ``nudging_level_taper_top``) can additionally be capped by the model's +own diagnosed tropopause level, so that nudging is switched off in the +stratosphere even where the tropopause is currently lower than these +configured levels (see the :ref:`Science Guide +`). This behaviour is controlled by +``nudging_min_tropopause_level``, which sets a floor below which the +diagnosed tropopause level is never allowed to cap nudging: + +* Setting ``nudging_min_tropopause_level`` to ``0`` means nudging always + follows the diagnosed tropopause level, wherever it is. +* Setting ``nudging_min_tropopause_level`` to ``nudging_level_taper_top`` + (or higher) disables the dynamic capping, so nudging always extends up + to the fixed levels configured above, regardless of the diagnosed + tropopause. +* Intermediate values allow the diagnosed tropopause to lower the effective + top of the nudged region, but prevent it from doing so below the + specified floor. + +Coarse-mesh nudging +-------------------- + +For efficiency, nudging may be computed on a mesh coarser than +the one used by the dynamical core, with the resulting increment mapped +back onto the dynamics mesh (see the :ref:`Science Guide +`). + +Coarse-mesh nudging is controlled from the ``namelist:multires_coupling`` +namelist: + +.. list-table:: + :header-rows: 1 + :widths: 25 75 + + * - Option + - Description + * - ``coarse_nudging`` + - If ``.true.``, nudging increments are computed on a coarser mesh and + then mapped back onto the dynamics mesh. If ``.false.`` (the + default), nudging is computed directly on the dynamics mesh and no + coarse mesh needs to be specified. + * - ``nudging_mesh_name`` + - Tag-name of the coarser mesh to use when ``coarse_nudging`` is + ``.true.``. This mesh must also be listed in + ``multires_coupling_mesh_tags``. diff --git a/interfaces/coupled_interface/rose-meta/coupling/versions.py b/interfaces/coupled_interface/rose-meta/coupling/versions.py index e33d8e39bc..3e9051d6c4 100644 --- a/interfaces/coupled_interface/rose-meta/coupling/versions.py +++ b/interfaces/coupled_interface/rose-meta/coupling/versions.py @@ -349,3 +349,82 @@ def upgrade(self, config, meta_config=None): ) return config, self.reports + + +class vn32_t670(MacroUpgrade): + """Upgrade macro for ticket #670 by Thomas Bendall.""" + + BEFORE_TAG = "vn3.2_t400" + AFTER_TAG = "vn3.2_t670" + + def upgrade(self, config, meta_config=None): + # Commands From: rose-meta/lfric-gungho + # Add new nudging namelist options + self.add_setting( + config, ["namelist:nudging", "nudging_method"], "'convolution'" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_theta"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_u"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_v"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_start"], "12.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_end"], "24.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_stop_time"], "10000.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_bottom"], "5" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_top"], "52" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_min_tropopause_level"], "48" + ) + self.change_setting_value( + config, ["namelist:nudging", "nudging_level_bottom"], "6" + ) + self.add_setting( + config, ["namelist:nudging", "num_ref_data_levels"], "137" + ) + self.add_setting(config, ["namelist:nudging", "spectral_kmax"], "20") + self.add_setting(config, ["namelist:nudging", "spectral_kmin"], "2") + self.add_setting( + config, ["namelist:nudging", "spectral_stencil_extent"], "12" + ) + self.add_setting( + config, ["namelist:nudging", "spectral_envelope_width"], "0.1" + ) + # Remove retired settings + self.remove_setting(config, ["namelist:nudging", "nudging_source"]) + self.remove_setting( + config, ["namelist:nudging", "nudging_width_bottom"] + ) + self.remove_setting(config, ["namelist:nudging", "nudging_width_top"]) + self.remove_setting(config, ["namelist:nudging", "nudge_data_levels"]) + # If nudging_mesh_name is still the default '' value, set it to + # match dynamics_mesh_name + nudging_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "nudging_mesh_name"] + ) + if nudging_mesh_name == "''": + dynamics_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "dynamics_mesh_name"] + ) + if dynamics_mesh_name is not None: + self.change_setting_value( + config, + ["namelist:multires_coupling", "nudging_mesh_name"], + dynamics_mesh_name, + ) + + return config, self.reports diff --git a/interfaces/jedi_lfric_interface/rose-meta/jedi_lfric_interface/versions.py b/interfaces/jedi_lfric_interface/rose-meta/jedi_lfric_interface/versions.py index e33d8e39bc..3e9051d6c4 100644 --- a/interfaces/jedi_lfric_interface/rose-meta/jedi_lfric_interface/versions.py +++ b/interfaces/jedi_lfric_interface/rose-meta/jedi_lfric_interface/versions.py @@ -349,3 +349,82 @@ def upgrade(self, config, meta_config=None): ) return config, self.reports + + +class vn32_t670(MacroUpgrade): + """Upgrade macro for ticket #670 by Thomas Bendall.""" + + BEFORE_TAG = "vn3.2_t400" + AFTER_TAG = "vn3.2_t670" + + def upgrade(self, config, meta_config=None): + # Commands From: rose-meta/lfric-gungho + # Add new nudging namelist options + self.add_setting( + config, ["namelist:nudging", "nudging_method"], "'convolution'" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_theta"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_u"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_v"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_start"], "12.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_end"], "24.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_stop_time"], "10000.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_bottom"], "5" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_top"], "52" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_min_tropopause_level"], "48" + ) + self.change_setting_value( + config, ["namelist:nudging", "nudging_level_bottom"], "6" + ) + self.add_setting( + config, ["namelist:nudging", "num_ref_data_levels"], "137" + ) + self.add_setting(config, ["namelist:nudging", "spectral_kmax"], "20") + self.add_setting(config, ["namelist:nudging", "spectral_kmin"], "2") + self.add_setting( + config, ["namelist:nudging", "spectral_stencil_extent"], "12" + ) + self.add_setting( + config, ["namelist:nudging", "spectral_envelope_width"], "0.1" + ) + # Remove retired settings + self.remove_setting(config, ["namelist:nudging", "nudging_source"]) + self.remove_setting( + config, ["namelist:nudging", "nudging_width_bottom"] + ) + self.remove_setting(config, ["namelist:nudging", "nudging_width_top"]) + self.remove_setting(config, ["namelist:nudging", "nudge_data_levels"]) + # If nudging_mesh_name is still the default '' value, set it to + # match dynamics_mesh_name + nudging_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "nudging_mesh_name"] + ) + if nudging_mesh_name == "''": + dynamics_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "dynamics_mesh_name"] + ) + if dynamics_mesh_name is not None: + self.change_setting_value( + config, + ["namelist:multires_coupling", "nudging_mesh_name"], + dynamics_mesh_name, + ) + + return config, self.reports diff --git a/rose-stem/app/adjoint_tests/rose-app.conf b/rose-stem/app/adjoint_tests/rose-app.conf index 2f4cc32c07..98a5abe69c 100644 --- a/rose-stem/app/adjoint_tests/rose-app.conf +++ b/rose-stem/app/adjoint_tests/rose-app.conf @@ -1,4 +1,4 @@ -meta=lfric-adjoint_tests/vn3.2_t400 +meta=lfric-adjoint_tests/vn3.2_t670 [command] default=rose env-cat iodef_temp.xml -o iodef.xml; $LAUNCH_SCRIPT/launch-exe @@ -1514,18 +1514,30 @@ dynamics_mesh_name='dynamics' multires_coupling_mesh_tags='dynamics' multires_coupling_mode='test' negative_correction='none' -!!nudging_mesh_name='' +!!nudging_mesh_name='dynamics' physics_mesh_name='aerosol' reconstruction='simple' recovery_order='linear' [!!namelist:nudging] -nudge_data_levels=137 -nudging_level_bottom=5 +nudging_level_bottom=6 +nudging_level_taper_bottom=5 +nudging_level_taper_top=52 nudging_level_top=52 -nudging_source='era' -nudging_width_bottom=1 -nudging_width_top=0 +nudging_method='convolution' +nudging_min_tropopause_level=48 +nudging_relax_time_theta=6.0 +nudging_relax_time_u=6.0 +nudging_relax_time_v=6.0 +nudging_source='ecmwf' +nudging_spinup_end=24.0 +nudging_spinup_start=12.0 +nudging_stop_time=10000.0 +num_ref_data_levels=137 +!!spectral_envelope_width=0.1 +!!spectral_kmax=20 +!!spectral_kmin=2 +!!spectral_stencil_extent=12 [!!namelist:orbit] !!arg_periapsis=1.796767421 diff --git a/rose-stem/app/gravity_wave/rose-app.conf b/rose-stem/app/gravity_wave/rose-app.conf index 8d67eee73c..acf6ac8b38 100644 --- a/rose-stem/app/gravity_wave/rose-app.conf +++ b/rose-stem/app/gravity_wave/rose-app.conf @@ -1,4 +1,4 @@ -meta=lfric-gravity_wave/vn3.2_t400 +meta=lfric-gravity_wave/vn3.2_t670 [command] default=$CORE_ROOT_DIR/bin/tweak_iodef; \ @@ -1456,12 +1456,24 @@ coarse_rad_aerosol=.false. !!nudging_mesh_name='' [!!namelist:nudging] -nudge_data_levels=137 -nudging_level_bottom=5 +nudging_level_bottom=6 +nudging_level_taper_bottom=5 +nudging_level_taper_top=52 nudging_level_top=52 -nudging_source='era' -nudging_width_bottom=1 -nudging_width_top=0 +nudging_method='convolution' +nudging_min_tropopause_level=48 +nudging_relax_time_theta=6.0 +nudging_relax_time_u=6.0 +nudging_relax_time_v=6.0 +nudging_source='ecmwf' +nudging_spinup_end=24.0 +nudging_spinup_start=12.0 +nudging_stop_time=10000.0 +num_ref_data_levels=137 +!!spectral_envelope_width=0.1 +!!spectral_kmax=20 +!!spectral_kmin=2 +!!spectral_stencil_extent=12 [!!namelist:orbit] !!arg_periapsis=1.796767421 diff --git a/rose-stem/app/gungho_model/rose-app.conf b/rose-stem/app/gungho_model/rose-app.conf index ec9ee83ac1..aea433d724 100644 --- a/rose-stem/app/gungho_model/rose-app.conf +++ b/rose-stem/app/gungho_model/rose-app.conf @@ -1,4 +1,4 @@ -meta=lfric-gungho_model/vn3.2_t400 +meta=lfric-gungho_model/vn3.2_t670 [command] default=$CORE_ROOT_DIR/bin/tweak_iodef ; \ @@ -1478,12 +1478,24 @@ coarse_rad_aerosol=.false. orography_mesh_name='dynamics' [!!namelist:nudging] -nudge_data_levels=137 -nudging_level_bottom=5 +nudging_level_bottom=6 +nudging_level_taper_bottom=5 +nudging_level_taper_top=52 nudging_level_top=52 -nudging_source='era' -nudging_width_bottom=1 -nudging_width_top=0 +nudging_method='convolution' +nudging_min_tropopause_level=48 +nudging_relax_time_theta=6.0 +nudging_relax_time_u=6.0 +nudging_relax_time_v=6.0 +nudging_source='ecmwf' +nudging_spinup_end=24.0 +nudging_spinup_start=12.0 +nudging_stop_time=10000.0 +num_ref_data_levels=137 +!!spectral_envelope_width=0.1 +!!spectral_kmax=20 +!!spectral_kmin=2 +!!spectral_stencil_extent=12 [!!namelist:orbit] !!arg_periapsis=1.796767421 diff --git a/rose-stem/app/jedi_forecast/rose-app.conf b/rose-stem/app/jedi_forecast/rose-app.conf index fb0ca33a38..146e396937 100644 --- a/rose-stem/app/jedi_forecast/rose-app.conf +++ b/rose-stem/app/jedi_forecast/rose-app.conf @@ -1,4 +1,4 @@ -meta=jedi_forecast/vn3.2_t400 +meta=jedi_forecast/vn3.2_t670 [command] default=$CORE_ROOT_DIR/bin/tweak_iodef; \ @@ -1535,18 +1535,30 @@ dynamics_mesh_name='dynamics' multires_coupling_mesh_tags='dynamics' multires_coupling_mode='test' negative_correction='none' -!!nudging_mesh_name='' +!!nudging_mesh_name='dynamics' physics_mesh_name='aerosol' reconstruction='simple' recovery_order='linear' [!!namelist:nudging] -nudge_data_levels=137 -nudging_level_bottom=5 +nudging_level_bottom=6 +nudging_level_taper_bottom=5 +nudging_level_taper_top=52 nudging_level_top=52 -nudging_source='era' -nudging_width_bottom=1 -nudging_width_top=0 +nudging_method='convolution' +nudging_min_tropopause_level=48 +nudging_relax_time_theta=6.0 +nudging_relax_time_u=6.0 +nudging_relax_time_v=6.0 +nudging_source='ecmwf' +nudging_spinup_end=24.0 +nudging_spinup_start=12.0 +nudging_stop_time=10000.0 +num_ref_data_levels=137 +!!spectral_envelope_width=0.1 +!!spectral_kmax=20 +!!spectral_kmin=2 +!!spectral_stencil_extent=12 [namelist:orbit] !!arg_periapsis=1.796767421 diff --git a/rose-stem/app/jedi_forecast_pseudo/rose-app.conf b/rose-stem/app/jedi_forecast_pseudo/rose-app.conf index bb7506ff07..e081e29a2c 100644 --- a/rose-stem/app/jedi_forecast_pseudo/rose-app.conf +++ b/rose-stem/app/jedi_forecast_pseudo/rose-app.conf @@ -1,4 +1,4 @@ -meta=jedi_forecast_pseudo/vn3.2_t400 +meta=jedi_forecast_pseudo/vn3.2_t670 [command] default=$CORE_ROOT_DIR/bin/tweak_iodef; \ @@ -1529,12 +1529,24 @@ coarse_rad_aerosol=.false. !!nudging_mesh_name='' [!!namelist:nudging] -nudge_data_levels=137 -nudging_level_bottom=5 +nudging_level_bottom=6 +nudging_level_taper_bottom=5 +nudging_level_taper_top=52 nudging_level_top=52 -nudging_source='era' -nudging_width_bottom=1 -nudging_width_top=0 +nudging_method='convolution' +nudging_min_tropopause_level=48 +nudging_relax_time_theta=6.0 +nudging_relax_time_u=6.0 +nudging_relax_time_v=6.0 +nudging_source='ecmwf' +nudging_spinup_end=24.0 +nudging_spinup_start=12.0 +nudging_stop_time=10000.0 +num_ref_data_levels=137 +!!spectral_envelope_width=0.1 +!!spectral_kmax=20 +!!spectral_kmin=2 +!!spectral_stencil_extent=12 [!!namelist:orbit] !!arg_periapsis=1.796767421 diff --git a/rose-stem/app/jedi_id_tlm_tests/rose-app.conf b/rose-stem/app/jedi_id_tlm_tests/rose-app.conf index 76d906b050..ca099b04e6 100644 --- a/rose-stem/app/jedi_id_tlm_tests/rose-app.conf +++ b/rose-stem/app/jedi_id_tlm_tests/rose-app.conf @@ -1,4 +1,4 @@ -meta=jedi_id_tlm_tests/vn3.2_t400 +meta=jedi_id_tlm_tests/vn3.2_t670 [command] default=$CORE_ROOT_DIR/bin/tweak_iodef; \ @@ -1558,18 +1558,30 @@ dynamics_mesh_name='dynamics' multires_coupling_mesh_tags='dynamics' multires_coupling_mode='test' negative_correction='none' -!!nudging_mesh_name='' +!!nudging_mesh_name='dynamics' physics_mesh_name='aerosol' reconstruction='simple' recovery_order='linear' [!!namelist:nudging] -nudge_data_levels=137 -nudging_level_bottom=5 +nudging_level_bottom=6 +nudging_level_taper_bottom=5 +nudging_level_taper_top=52 nudging_level_top=52 -nudging_source='era' -nudging_width_bottom=1 -nudging_width_top=0 +nudging_method='convolution' +nudging_min_tropopause_level=48 +nudging_relax_time_theta=6.0 +nudging_relax_time_u=6.0 +nudging_relax_time_v=6.0 +nudging_source='ecmwf' +nudging_spinup_end=24.0 +nudging_spinup_start=12.0 +nudging_stop_time=10000.0 +num_ref_data_levels=137 +!!spectral_envelope_width=0.1 +!!spectral_kmax=20 +!!spectral_kmin=2 +!!spectral_stencil_extent=12 [!!namelist:orbit] !!arg_periapsis=1.796767421 diff --git a/rose-stem/app/jedi_lfric_tests/rose-app.conf b/rose-stem/app/jedi_lfric_tests/rose-app.conf index d37ecec260..35fa74f98b 100644 --- a/rose-stem/app/jedi_lfric_tests/rose-app.conf +++ b/rose-stem/app/jedi_lfric_tests/rose-app.conf @@ -1,4 +1,4 @@ -meta=jedi_lfric_tests/vn3.2_t400 +meta=jedi_lfric_tests/vn3.2_t670 [command] default=rose env-cat iodef_temp.xml -o iodef.xml; $LAUNCH_SCRIPT/launch-exe @@ -1560,18 +1560,30 @@ dynamics_mesh_name='dynamics' multires_coupling_mesh_tags='dynamics' multires_coupling_mode='test' negative_correction='none' -!!nudging_mesh_name='' +!!nudging_mesh_name='dynamics' physics_mesh_name='aerosol' reconstruction='simple' recovery_order='linear' [!!namelist:nudging] -nudge_data_levels=137 -nudging_level_bottom=5 +nudging_level_bottom=6 +nudging_level_taper_bottom=5 +nudging_level_taper_top=52 nudging_level_top=52 -nudging_source='era' -nudging_width_bottom=1 -nudging_width_top=0 +nudging_method='convolution' +nudging_min_tropopause_level=48 +nudging_relax_time_theta=6.0 +nudging_relax_time_u=6.0 +nudging_relax_time_v=6.0 +nudging_source='ecmwf' +nudging_spinup_end=24.0 +nudging_spinup_start=12.0 +nudging_stop_time=10000.0 +num_ref_data_levels=137 +!!spectral_envelope_width=0.1 +!!spectral_kmax=20 +!!spectral_kmin=2 +!!spectral_stencil_extent=12 [!!namelist:orbit] !!arg_periapsis=1.796767421 diff --git a/rose-stem/app/jedi_tlm_forecast_tl/rose-app.conf b/rose-stem/app/jedi_tlm_forecast_tl/rose-app.conf index 0ad97d8130..83027ba9b0 100644 --- a/rose-stem/app/jedi_tlm_forecast_tl/rose-app.conf +++ b/rose-stem/app/jedi_tlm_forecast_tl/rose-app.conf @@ -1,4 +1,4 @@ -meta=jedi_tlm_forecast_tl/vn3.2_t400 +meta=jedi_tlm_forecast_tl/vn3.2_t670 [command] default=$CORE_ROOT_DIR/bin/tweak_iodef; \ @@ -1558,18 +1558,30 @@ dynamics_mesh_name='dynamics' multires_coupling_mesh_tags='dynamics' multires_coupling_mode='test' negative_correction='none' -!!nudging_mesh_name='' +!!nudging_mesh_name='dynamics' physics_mesh_name='aerosol' reconstruction='simple' recovery_order='linear' [!!namelist:nudging] -nudge_data_levels=137 -nudging_level_bottom=5 +nudging_level_bottom=6 +nudging_level_taper_bottom=5 +nudging_level_taper_top=52 nudging_level_top=52 -nudging_source='era' -nudging_width_bottom=1 -nudging_width_top=0 +nudging_method='convolution' +nudging_min_tropopause_level=48 +nudging_relax_time_theta=6.0 +nudging_relax_time_u=6.0 +nudging_relax_time_v=6.0 +nudging_source='ecmwf' +nudging_spinup_end=24.0 +nudging_spinup_start=12.0 +nudging_stop_time=10000.0 +num_ref_data_levels=137 +!!spectral_envelope_width=0.1 +!!spectral_kmax=20 +!!spectral_kmin=2 +!!spectral_stencil_extent=12 [!!namelist:orbit] !!arg_periapsis=1.796767421 diff --git a/rose-stem/app/jedi_tlm_tests/rose-app.conf b/rose-stem/app/jedi_tlm_tests/rose-app.conf index 35f6b1ccf0..863f78479c 100644 --- a/rose-stem/app/jedi_tlm_tests/rose-app.conf +++ b/rose-stem/app/jedi_tlm_tests/rose-app.conf @@ -1,4 +1,4 @@ -meta=jedi_tlm_tests/vn3.2_t400 +meta=jedi_tlm_tests/vn3.2_t670 [command] default=$CORE_ROOT_DIR/bin/tweak_iodef; \ @@ -1558,18 +1558,30 @@ dynamics_mesh_name='dynamics' multires_coupling_mesh_tags='dynamics' multires_coupling_mode='test' negative_correction='none' -!!nudging_mesh_name='' +!!nudging_mesh_name='dynamics' physics_mesh_name='aerosol' reconstruction='simple' recovery_order='linear' [!!namelist:nudging] -nudge_data_levels=137 -nudging_level_bottom=5 +nudging_level_bottom=6 +nudging_level_taper_bottom=5 +nudging_level_taper_top=52 nudging_level_top=52 -nudging_source='era' -nudging_width_bottom=1 -nudging_width_top=0 +nudging_method='convolution' +nudging_min_tropopause_level=48 +nudging_relax_time_theta=6.0 +nudging_relax_time_u=6.0 +nudging_relax_time_v=6.0 +nudging_source='ecmwf' +nudging_spinup_end=24.0 +nudging_spinup_start=12.0 +nudging_stop_time=10000.0 +num_ref_data_levels=137 +!!spectral_envelope_width=0.1 +!!spectral_kmax=20 +!!spectral_kmin=2 +!!spectral_stencil_extent=12 [!!namelist:orbit] !!arg_periapsis=1.796767421 diff --git a/rose-stem/app/jules/rose-app.conf b/rose-stem/app/jules/rose-app.conf index 2a35f7e3d1..ae7601ee7e 100644 --- a/rose-stem/app/jules/rose-app.conf +++ b/rose-stem/app/jules/rose-app.conf @@ -1,4 +1,4 @@ -meta=lfric-jules/vn3.2_t400 +meta=lfric-jules/vn3.2_t670 [command] default=$CORE_ROOT_DIR/bin/tweak_iodef; \ @@ -1550,18 +1550,30 @@ dynamics_mesh_name='dynamics' multires_coupling_mesh_tags='dynamics' multires_coupling_mode='test' negative_correction='none' -!!nudging_mesh_name='' +!!nudging_mesh_name='dynamics' physics_mesh_name='aerosol' reconstruction='simple' recovery_order='linear' [!!namelist:nudging] -nudge_data_levels=137 -nudging_level_bottom=5 +nudging_level_bottom=6 +nudging_level_taper_bottom=5 +nudging_level_taper_top=52 nudging_level_top=52 -nudging_source='era' -nudging_width_bottom=1 -nudging_width_top=0 +nudging_method='convolution' +nudging_min_tropopause_level=48 +nudging_relax_time_theta=6.0 +nudging_relax_time_u=6.0 +nudging_relax_time_v=6.0 +nudging_source='ecmwf' +nudging_spinup_end=24.0 +nudging_spinup_start=12.0 +nudging_stop_time=10000.0 +num_ref_data_levels=137 +!!spectral_envelope_width=0.1 +!!spectral_kmax=20 +!!spectral_kmin=2 +!!spectral_stencil_extent=12 [!!namelist:orbit] !!arg_periapsis=1.796767421 diff --git a/rose-stem/app/lfric2lfric/rose-app.conf b/rose-stem/app/lfric2lfric/rose-app.conf index 3c704b4ad1..690e6476a0 100644 --- a/rose-stem/app/lfric2lfric/rose-app.conf +++ b/rose-stem/app/lfric2lfric/rose-app.conf @@ -1,4 +1,4 @@ -meta=lfric-lfric2lfric/vn3.2_t400 +meta=lfric-lfric2lfric/vn3.2_t670 [command] default=$CORE_ROOT_DIR/bin/tweak_iodef; \ @@ -960,12 +960,24 @@ coarse_rad_aerosol=.false. !!nudging_mesh_name='' [!!namelist:nudging] -nudge_data_levels=137 -nudging_level_bottom=5 +nudging_level_bottom=6 +nudging_level_taper_bottom=5 +nudging_level_taper_top=52 nudging_level_top=52 -nudging_source='era' -nudging_width_bottom=1 -nudging_width_top=0 +nudging_method='convolution' +nudging_min_tropopause_level=48 +nudging_relax_time_theta=6.0 +nudging_relax_time_u=6.0 +nudging_relax_time_v=6.0 +nudging_source='ecmwf' +nudging_spinup_end=24.0 +nudging_spinup_start=12.0 +nudging_stop_time=10000.0 +num_ref_data_levels=137 +!!spectral_envelope_width=0.1 +!!spectral_kmax=20 +!!spectral_kmin=2 +!!spectral_stencil_extent=12 [!!namelist:orbit] !!arg_periapsis=0 diff --git a/rose-stem/app/lfric_atm/opt/rose-app-nudging-era-coarse.conf b/rose-stem/app/lfric_atm/opt/rose-app-nudging-newtonian-coarse.conf similarity index 83% rename from rose-stem/app/lfric_atm/opt/rose-app-nudging-era-coarse.conf rename to rose-stem/app/lfric_atm/opt/rose-app-nudging-newtonian-coarse.conf index 9d4644405d..844163d738 100644 --- a/rose-stem/app/lfric_atm/opt/rose-app-nudging-era-coarse.conf +++ b/rose-stem/app/lfric_atm/opt/rose-app-nudging-newtonian-coarse.conf @@ -24,6 +24,12 @@ orography_mesh_name='dynamics' physics_mesh_name='dynamics' [namelist:nudging] +nudging_method='newtonian' +nudging_relax_time_theta=1.0 +nudging_relax_time_u=1.0 +nudging_relax_time_v=1.0 +nudging_spinup_end=2.0 +nudging_spinup_start=1.0 [namelist:section_choice] external_forcing=.true. diff --git a/rose-stem/app/lfric_atm/opt/rose-app-nudging-era.conf b/rose-stem/app/lfric_atm/opt/rose-app-nudging-newtonian.conf similarity index 63% rename from rose-stem/app/lfric_atm/opt/rose-app-nudging-era.conf rename to rose-stem/app/lfric_atm/opt/rose-app-nudging-newtonian.conf index 644e816e93..c34aad17a2 100644 --- a/rose-stem/app/lfric_atm/opt/rose-app-nudging-era.conf +++ b/rose-stem/app/lfric_atm/opt/rose-app-nudging-newtonian.conf @@ -10,14 +10,24 @@ wind_forcing='nudging' nudging_directory='/data/users/mohit.dalvi/lfric/data/nudge/era/C48' nudging_filename='era5_nudge_L137_C48_MG_2021032400-2706' -[!!namelist:multires_coupling] +[namelist:formulation] +use_multires_coupling=.true. + +[namelist:multires_coupling] aerosol_mesh_name='dynamics' coarse_nudging=.false. -!!nudging_mesh_name='dynamics' +lowest_order_aero_flag=.false. +nudging_mesh_name='dynamics' orography_mesh_name='dynamics' physics_mesh_name='dynamics' [namelist:nudging] +nudging_method='newtonian' +nudging_relax_time_theta=1.0 +nudging_relax_time_u=1.0 +nudging_relax_time_v=1.0 +nudging_spinup_end=2.0 +nudging_spinup_start=1.0 [namelist:section_choice] external_forcing=.true. diff --git a/rose-stem/app/lfric_atm/opt/rose-app-nudging-spectral-coarse.conf b/rose-stem/app/lfric_atm/opt/rose-app-nudging-spectral-coarse.conf new file mode 100644 index 0000000000..ebfab9b67a --- /dev/null +++ b/rose-stem/app/lfric_atm/opt/rose-app-nudging-spectral-coarse.conf @@ -0,0 +1,38 @@ +[file:iodef.xml] +source=$ROSE_SUITE_DIR/app/lfric_atm/file/iodef_gal_nwp_coarse_nudging.xml + +[namelist:external_forcing] +pc2_force_response=.false. +theta_forcing='nudging' +wind_forcing='nudging' + +[namelist:files] +!!coarse_ancil_directory='$BIG_DATA_DIR/ancils/basic-gal/yak/C12' +nudging_directory='/data/users/mohit.dalvi/lfric/data/nudge/era/C12' +nudging_filename='era5_nudge_L137_C12_2021032400-2706' + +[namelist:formulation] +use_multires_coupling=.true. + +[namelist:multires_coupling] +aerosol_mesh_name='dynamics' +coarse_nudging=.true. +lowest_order_aero_flag=.false. +multires_coupling_mesh_tags='dynamics','multigrid_l2' +nudging_mesh_name='multigrid_l2' +orography_mesh_name='dynamics' +physics_mesh_name='dynamics' + +[namelist:nudging] +nudging_relax_time_theta=1.0 +nudging_relax_time_u=1.0 +nudging_relax_time_v=1.0 +nudging_spinup_end=2.0 +nudging_spinup_start=1.0 +spectral_envelope_width=0.1 +spectral_kmax=20 +spectral_kmin=2 +spectral_stencil_extent=4 + +[namelist:section_choice] +external_forcing=.true. diff --git a/rose-stem/app/lfric_atm/opt/rose-app-nudging-spectral.conf b/rose-stem/app/lfric_atm/opt/rose-app-nudging-spectral.conf new file mode 100644 index 0000000000..e2087e53a6 --- /dev/null +++ b/rose-stem/app/lfric_atm/opt/rose-app-nudging-spectral.conf @@ -0,0 +1,36 @@ +[file:iodef.xml] +source=$ROSE_SUITE_DIR/app/lfric_atm/file/iodef_gal_nwp_nudging.xml + +[namelist:external_forcing] +pc2_force_response=.false. +theta_forcing='nudging' +wind_forcing='nudging' + +[namelist:files] +nudging_directory='/data/users/mohit.dalvi/lfric/data/nudge/era/C48' +nudging_filename='era5_nudge_L137_C48_MG_2021032400-2706' + +[namelist:formulation] +use_multires_coupling=.true. + +[namelist:multires_coupling] +aerosol_mesh_name='dynamics' +coarse_nudging=.false. +lowest_order_aero_flag=.false. +nudging_mesh_name='dynamics' +orography_mesh_name='dynamics' +physics_mesh_name='dynamics' + +[namelist:nudging] +nudging_relax_time_theta=1.0 +nudging_relax_time_u=1.0 +nudging_relax_time_v=1.0 +nudging_spinup_end=2.0 +nudging_spinup_start=1.0 +spectral_envelope_width=0.1 +spectral_kmax=20 +spectral_kmin=2 +spectral_stencil_extent=6 + +[namelist:section_choice] +external_forcing=.true. diff --git a/rose-stem/app/lfric_atm/rose-app.conf b/rose-stem/app/lfric_atm/rose-app.conf index 15c46ca5f8..f283a1c14f 100644 --- a/rose-stem/app/lfric_atm/rose-app.conf +++ b/rose-stem/app/lfric_atm/rose-app.conf @@ -1,4 +1,4 @@ -meta=lfric-lfric_atm/vn3.2_t400 +meta=lfric-lfric_atm/vn3.2_t670 [command] default=$CORE_ROOT_DIR/bin/tweak_iodef; \ @@ -1504,18 +1504,30 @@ dynamics_mesh_name='dynamics' multires_coupling_mesh_tags='dynamics' multires_coupling_mode='test' negative_correction='none' -!!nudging_mesh_name='' +!!nudging_mesh_name='dynamics' physics_mesh_name='aerosol' reconstruction='simple' recovery_order='linear' [!!namelist:nudging] -nudge_data_levels=137 -nudging_level_bottom=5 +nudging_level_bottom=6 +nudging_level_taper_bottom=5 +nudging_level_taper_top=52 nudging_level_top=52 -nudging_source='era' -nudging_width_bottom=1 -nudging_width_top=0 +nudging_method='convolution' +nudging_min_tropopause_level=48 +nudging_relax_time_theta=6.0 +nudging_relax_time_u=6.0 +nudging_relax_time_v=6.0 +nudging_source='ecmwf' +nudging_spinup_end=24.0 +nudging_spinup_start=12.0 +nudging_stop_time=10000.0 +num_ref_data_levels=137 +!!spectral_envelope_width=0.1 +!!spectral_kmax=20 +!!spectral_kmin=2 +!!spectral_stencil_extent=12 [namelist:orbit] !!arg_periapsis=1.796767421 diff --git a/rose-stem/app/linear_model/rose-app.conf b/rose-stem/app/linear_model/rose-app.conf index 6302bd6de9..4f16dee337 100644 --- a/rose-stem/app/linear_model/rose-app.conf +++ b/rose-stem/app/linear_model/rose-app.conf @@ -1,4 +1,4 @@ -meta=lfric-linear_model/vn3.2_t400 +meta=lfric-linear_model/vn3.2_t670 [command] default=rose env-cat iodef_temp.xml -o iodef.xml; $LAUNCH_SCRIPT/launch-exe @@ -1510,18 +1510,30 @@ dynamics_mesh_name='dynamics' multires_coupling_mesh_tags='dynamics' multires_coupling_mode='test' negative_correction='none' -!!nudging_mesh_name='' +!!nudging_mesh_name='dynamics' physics_mesh_name='aerosol' reconstruction='simple' recovery_order='linear' [!!namelist:nudging] -nudge_data_levels=137 -nudging_level_bottom=5 +nudging_level_bottom=6 +nudging_level_taper_bottom=5 +nudging_level_taper_top=52 nudging_level_top=52 -nudging_source='era' -nudging_width_bottom=1 -nudging_width_top=0 +nudging_method='convolution' +nudging_min_tropopause_level=48 +nudging_relax_time_theta=6.0 +nudging_relax_time_u=6.0 +nudging_relax_time_v=6.0 +nudging_source='ecmwf' +nudging_spinup_end=24.0 +nudging_spinup_start=12.0 +nudging_stop_time=10000.0 +num_ref_data_levels=137 +!!spectral_envelope_width=0.1 +!!spectral_kmax=20 +!!spectral_kmin=2 +!!spectral_stencil_extent=12 [!!namelist:orbit] !!arg_periapsis=1.796767421 diff --git a/rose-stem/app/name_transport/rose-app.conf b/rose-stem/app/name_transport/rose-app.conf index abe7c05b79..73093574a0 100644 --- a/rose-stem/app/name_transport/rose-app.conf +++ b/rose-stem/app/name_transport/rose-app.conf @@ -1,4 +1,4 @@ -meta=lfric-name_transport/vn3.2_t400 +meta=lfric-name_transport/vn3.2_t670 [command] default=$CORE_ROOT_DIR/bin/tweak_iodef; \ @@ -1464,7 +1464,7 @@ dynamics_mesh_name='dynamics' multires_coupling_mesh_tags='dynamics' multires_coupling_mode='test' negative_correction='none' -!!nudging_mesh_name='' +!!nudging_mesh_name='dynamics' physics_mesh_name='coarse' reconstruction='reversible' recovery_order='linear' @@ -1473,12 +1473,24 @@ recovery_order='linear' transport_density=.true. [!!namelist:nudging] -nudge_data_levels=137 -nudging_level_bottom=5 +nudging_level_bottom=6 +nudging_level_taper_bottom=5 +nudging_level_taper_top=52 nudging_level_top=52 -nudging_source='era' -nudging_width_bottom=1 -nudging_width_top=0 +nudging_method='convolution' +nudging_min_tropopause_level=48 +nudging_relax_time_theta=6.0 +nudging_relax_time_u=6.0 +nudging_relax_time_v=6.0 +nudging_source='ecmwf' +nudging_spinup_end=24.0 +nudging_spinup_start=12.0 +nudging_stop_time=10000.0 +num_ref_data_levels=137 +!!spectral_envelope_width=0.1 +!!spectral_kmax=20 +!!spectral_kmin=2 +!!spectral_stencil_extent=12 [!!namelist:orbit] !!arg_periapsis=1.796767421 diff --git a/rose-stem/app/ngarch/rose-app.conf b/rose-stem/app/ngarch/rose-app.conf index 9422d244b5..261f9625a2 100644 --- a/rose-stem/app/ngarch/rose-app.conf +++ b/rose-stem/app/ngarch/rose-app.conf @@ -1,4 +1,4 @@ -meta=lfric-ngarch/vn3.2_t400 +meta=lfric-ngarch/vn3.2_t670 [command] default=$CORE_ROOT_DIR/bin/tweak_iodef; \ @@ -1535,12 +1535,24 @@ orography_mesh_name='dynamics' method='casim' [!!namelist:nudging] -nudge_data_levels=137 -nudging_level_bottom=5 +nudging_level_bottom=6 +nudging_level_taper_bottom=5 +nudging_level_taper_top=52 nudging_level_top=52 -nudging_source='era' -nudging_width_bottom=1 -nudging_width_top=0 +nudging_method='convolution' +nudging_min_tropopause_level=48 +nudging_relax_time_theta=6.0 +nudging_relax_time_u=6.0 +nudging_relax_time_v=6.0 +nudging_source='ecmwf' +nudging_spinup_end=24.0 +nudging_spinup_start=12.0 +nudging_stop_time=10000.0 +num_ref_data_levels=137 +!!spectral_envelope_width=0.1 +!!spectral_kmax=20 +!!spectral_kmin=2 +!!spectral_stencil_extent=12 [!!namelist:orbit] !!arg_periapsis=1.796767421 diff --git a/rose-stem/app/shallow_water/rose-app.conf b/rose-stem/app/shallow_water/rose-app.conf index 8ddd7abd50..aed5a866bf 100644 --- a/rose-stem/app/shallow_water/rose-app.conf +++ b/rose-stem/app/shallow_water/rose-app.conf @@ -1,4 +1,4 @@ -meta=lfric-shallow_water/vn3.2_t400 +meta=lfric-shallow_water/vn3.2_t670 [command] default=$CORE_ROOT_DIR/bin/tweak_iodef; \ @@ -1448,12 +1448,24 @@ coarse_rad_aerosol=.false. !!nudging_mesh_name='' [!!namelist:nudging] -nudge_data_levels=137 -nudging_level_bottom=5 +nudging_level_bottom=6 +nudging_level_taper_bottom=5 +nudging_level_taper_top=52 nudging_level_top=52 -nudging_source='era' -nudging_width_bottom=1 -nudging_width_top=0 +nudging_method='convolution' +nudging_min_tropopause_level=48 +nudging_relax_time_theta=6.0 +nudging_relax_time_u=6.0 +nudging_relax_time_v=6.0 +nudging_source='ecmwf' +nudging_spinup_end=24.0 +nudging_spinup_start=12.0 +nudging_stop_time=10000.0 +num_ref_data_levels=137 +!!spectral_envelope_width=0.1 +!!spectral_kmax=20 +!!spectral_kmin=2 +!!spectral_stencil_extent=12 [!!namelist:orbit] !!arg_periapsis=1.796767421 diff --git a/rose-stem/app/transport/rose-app.conf b/rose-stem/app/transport/rose-app.conf index 2ccd2c6a10..8ccb3890db 100644 --- a/rose-stem/app/transport/rose-app.conf +++ b/rose-stem/app/transport/rose-app.conf @@ -1,4 +1,4 @@ -meta=lfric-transport/vn3.2_t400 +meta=lfric-transport/vn3.2_t670 [command] default=$CORE_ROOT_DIR/bin/tweak_iodef; \ @@ -1463,18 +1463,30 @@ dynamics_mesh_name='dynamics' multires_coupling_mesh_tags='dynamics' multires_coupling_mode='test' negative_correction='none' -!!nudging_mesh_name='' +!!nudging_mesh_name='dynamics' physics_mesh_name='coarse' reconstruction='reversible' recovery_order='linear' [!!namelist:nudging] -nudge_data_levels=137 -nudging_level_bottom=5 +nudging_level_bottom=6 +nudging_level_taper_bottom=5 +nudging_level_taper_top=52 nudging_level_top=52 -nudging_source='era' -nudging_width_bottom=1 -nudging_width_top=0 +nudging_method='convolution' +nudging_min_tropopause_level=48 +nudging_relax_time_theta=6.0 +nudging_relax_time_u=6.0 +nudging_relax_time_v=6.0 +nudging_source='ecmwf' +nudging_spinup_end=24.0 +nudging_spinup_start=12.0 +nudging_stop_time=10000.0 +num_ref_data_levels=137 +!!spectral_envelope_width=0.1 +!!spectral_kmax=20 +!!spectral_kmin=2 +!!spectral_stencil_extent=12 [!!namelist:orbit] !!arg_periapsis=1.796767421 diff --git a/rose-stem/site/common/lfric_atm/tasks_lfric_atm.cylc b/rose-stem/site/common/lfric_atm/tasks_lfric_atm.cylc index 3a6e74aeea..58b575d264 100644 --- a/rose-stem/site/common/lfric_atm/tasks_lfric_atm.cylc +++ b/rose-stem/site/common/lfric_atm/tasks_lfric_atm.cylc @@ -292,10 +292,10 @@ "plot_str": "plot_map.py $NODAL_DATA_DIR/lfric_diagnostics.nc $PLOT_DIR", }) %} -{% elif task_ns.conf_name == "nwp_gal10_nudging-era-C48_MG" %} +{% elif task_ns.conf_name == "nwp_gal10_nudging-spectral-C48_MG" %} {% do task_dict.update({ - "opt_confs": ["nudging-era","um_dump"], + "opt_confs": ["nudging-spectral","um_dump"], "resolution": "C48_MG", "DT": 1800, "tsteps": 12, @@ -305,10 +305,36 @@ "plot_str": "plot_map.py $NODAL_DATA_DIR/lfric_diagnostics.nc $PLOT_DIR", }) %} -{% elif task_ns.conf_name == "nwp_gal10_nudging-era-coarse-C48_MG" %} +{% elif task_ns.conf_name == "nwp_gal10_nudging-spectral-coarse-C48_MG" %} {% do task_dict.update({ - "opt_confs": ["nudging-era-coarse","um_dump"], + "opt_confs": ["nudging-spectral-coarse","um_dump"], + "resolution": "C48_MG", + "DT": 1800, + "tsteps": 12, + "mpi_parts": 24, + "log_level": "info", + "kgo_checks": ["checksum"], + "plot_str": "plot_map.py $NODAL_DATA_DIR/lfric_diagnostics.nc $PLOT_DIR", + }) %} + +{% elif task_ns.conf_name == "nwp_gal10_nudging-newtonian-C48_MG" %} + + {% do task_dict.update({ + "opt_confs": ["nudging-newtonian","um_dump"], + "resolution": "C48_MG", + "DT": 1800, + "tsteps": 12, + "mpi_parts": 24, + "log_level": "info", + "kgo_checks": ["checksum"], + "plot_str": "plot_map.py $NODAL_DATA_DIR/lfric_diagnostics.nc $PLOT_DIR", + }) %} + +{% elif task_ns.conf_name == "nwp_gal10_nudging-newtonian-coarse-C48_MG" %} + + {% do task_dict.update({ + "opt_confs": ["nudging-newtonian-coarse","um_dump"], "resolution": "C48_MG", "DT": 1800, "tsteps": 12, diff --git a/rose-stem/site/meto/groups/groups_lfric_atm.cylc b/rose-stem/site/meto/groups/groups_lfric_atm.cylc index cc6c685537..b25651e3e7 100644 --- a/rose-stem/site/meto/groups/groups_lfric_atm.cylc +++ b/rose-stem/site/meto/groups/groups_lfric_atm.cylc @@ -66,8 +66,10 @@ "lfric_atm_canned_azspice_gnu_fast-debug-32bit", ], "lfric_atm_azspice_nudging": [ - "lfric_atm_nwp_gal10_nudging-era-C48_MG_azspice_gnu_fast-debug-32bit", - "lfric_atm_nwp_gal10_nudging-era-coarse-C48_MG_azspice_gnu_fast-debug-32bit", + "lfric_atm_nwp_gal10_nudging-spectral-C48_MG_azspice_gnu_fast-debug-32bit", + "lfric_atm_nwp_gal10_nudging-spectral-coarse-C48_MG_azspice_gnu_fast-debug-32bit", + "lfric_atm_nwp_gal10_nudging-newtonian-C48_MG_azspice_gnu_fast-debug-32bit", + "lfric_atm_nwp_gal10_nudging-newtonian-coarse-C48_MG_azspice_gnu_fast-debug-32bit", ], "lfric_atm_azspice_developer": [ "lfric_atm_nwp_azspice_developer", diff --git a/rose-stem/site/meto/kgos/lfric_atm/azspice/checksum_lfric_atm_nwp_gal10_nudging-newtonian-C48_MG_azspice_gnu_fast-debug-32bit.txt b/rose-stem/site/meto/kgos/lfric_atm/azspice/checksum_lfric_atm_nwp_gal10_nudging-newtonian-C48_MG_azspice_gnu_fast-debug-32bit.txt new file mode 100644 index 0000000000..2d649163e9 --- /dev/null +++ b/rose-stem/site/meto/kgos/lfric_atm/azspice/checksum_lfric_atm_nwp_gal10_nudging-newtonian-C48_MG_azspice_gnu_fast-debug-32bit.txt @@ -0,0 +1,9 @@ +Inner product checksum rho = 48D818B5 +Inner product checksum theta = 539489C0 +Inner product checksum u = 6AA166FA +Inner product checksum mr1 = 41CA06A7 +Inner product checksum mr2 = 39A78CDA +Inner product checksum mr3 = 37752CF2 +Inner product checksum mr4 = 39012D34 +Inner product checksum mr5 = 0 +Inner product checksum mr6 = 0 diff --git a/rose-stem/site/meto/kgos/lfric_atm/azspice/checksum_lfric_atm_nwp_gal10_nudging-newtonian-coarse-C48_MG_azspice_gnu_fast-debug-32bit.txt b/rose-stem/site/meto/kgos/lfric_atm/azspice/checksum_lfric_atm_nwp_gal10_nudging-newtonian-coarse-C48_MG_azspice_gnu_fast-debug-32bit.txt new file mode 100644 index 0000000000..5d705d34ba --- /dev/null +++ b/rose-stem/site/meto/kgos/lfric_atm/azspice/checksum_lfric_atm_nwp_gal10_nudging-newtonian-coarse-C48_MG_azspice_gnu_fast-debug-32bit.txt @@ -0,0 +1,9 @@ +Inner product checksum rho = 48D824B7 +Inner product checksum theta = 53948A24 +Inner product checksum u = 6AA28F5C +Inner product checksum mr1 = 41C8CE76 +Inner product checksum mr2 = 39FDA9D0 +Inner product checksum mr3 = 378A3600 +Inner product checksum mr4 = 393E4026 +Inner product checksum mr5 = 0 +Inner product checksum mr6 = 0 diff --git a/rose-stem/site/meto/kgos/lfric_atm/azspice/checksum_lfric_atm_nwp_gal10_nudging-spectral-C48_MG_azspice_gnu_fast-debug-32bit.txt b/rose-stem/site/meto/kgos/lfric_atm/azspice/checksum_lfric_atm_nwp_gal10_nudging-spectral-C48_MG_azspice_gnu_fast-debug-32bit.txt new file mode 100644 index 0000000000..90f577228d --- /dev/null +++ b/rose-stem/site/meto/kgos/lfric_atm/azspice/checksum_lfric_atm_nwp_gal10_nudging-spectral-C48_MG_azspice_gnu_fast-debug-32bit.txt @@ -0,0 +1,9 @@ +Inner product checksum rho = 48D81B6A +Inner product checksum theta = 539488DE +Inner product checksum u = 6AA1B33F +Inner product checksum mr1 = 41C90D4D +Inner product checksum mr2 = 39B82B80 +Inner product checksum mr3 = 378B48CA +Inner product checksum mr4 = 3914278C +Inner product checksum mr5 = 0 +Inner product checksum mr6 = 0 diff --git a/rose-stem/site/meto/kgos/lfric_atm/azspice/checksum_lfric_atm_nwp_gal10_nudging-spectral-coarse-C48_MG_azspice_gnu_fast-debug-32bit.txt b/rose-stem/site/meto/kgos/lfric_atm/azspice/checksum_lfric_atm_nwp_gal10_nudging-spectral-coarse-C48_MG_azspice_gnu_fast-debug-32bit.txt new file mode 100644 index 0000000000..ce7eb98788 --- /dev/null +++ b/rose-stem/site/meto/kgos/lfric_atm/azspice/checksum_lfric_atm_nwp_gal10_nudging-spectral-coarse-C48_MG_azspice_gnu_fast-debug-32bit.txt @@ -0,0 +1,9 @@ +Inner product checksum rho = 48D82256 +Inner product checksum theta = 53948A2E +Inner product checksum u = 6AA2709D +Inner product checksum mr1 = 41C8E788 +Inner product checksum mr2 = 39E1359F +Inner product checksum mr3 = 378627B0 +Inner product checksum mr4 = 3931CE45 +Inner product checksum mr5 = 0 +Inner product checksum mr6 = 0 diff --git a/science/adjoint/rose-meta/lfric-adjoint/versions.py b/science/adjoint/rose-meta/lfric-adjoint/versions.py index e33d8e39bc..3e9051d6c4 100644 --- a/science/adjoint/rose-meta/lfric-adjoint/versions.py +++ b/science/adjoint/rose-meta/lfric-adjoint/versions.py @@ -349,3 +349,82 @@ def upgrade(self, config, meta_config=None): ) return config, self.reports + + +class vn32_t670(MacroUpgrade): + """Upgrade macro for ticket #670 by Thomas Bendall.""" + + BEFORE_TAG = "vn3.2_t400" + AFTER_TAG = "vn3.2_t670" + + def upgrade(self, config, meta_config=None): + # Commands From: rose-meta/lfric-gungho + # Add new nudging namelist options + self.add_setting( + config, ["namelist:nudging", "nudging_method"], "'convolution'" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_theta"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_u"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_v"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_start"], "12.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_end"], "24.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_stop_time"], "10000.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_bottom"], "5" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_top"], "52" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_min_tropopause_level"], "48" + ) + self.change_setting_value( + config, ["namelist:nudging", "nudging_level_bottom"], "6" + ) + self.add_setting( + config, ["namelist:nudging", "num_ref_data_levels"], "137" + ) + self.add_setting(config, ["namelist:nudging", "spectral_kmax"], "20") + self.add_setting(config, ["namelist:nudging", "spectral_kmin"], "2") + self.add_setting( + config, ["namelist:nudging", "spectral_stencil_extent"], "12" + ) + self.add_setting( + config, ["namelist:nudging", "spectral_envelope_width"], "0.1" + ) + # Remove retired settings + self.remove_setting(config, ["namelist:nudging", "nudging_source"]) + self.remove_setting( + config, ["namelist:nudging", "nudging_width_bottom"] + ) + self.remove_setting(config, ["namelist:nudging", "nudging_width_top"]) + self.remove_setting(config, ["namelist:nudging", "nudge_data_levels"]) + # If nudging_mesh_name is still the default '' value, set it to + # match dynamics_mesh_name + nudging_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "nudging_mesh_name"] + ) + if nudging_mesh_name == "''": + dynamics_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "dynamics_mesh_name"] + ) + if dynamics_mesh_name is not None: + self.change_setting_value( + config, + ["namelist:multires_coupling", "nudging_mesh_name"], + dynamics_mesh_name, + ) + + return config, self.reports diff --git a/science/gungho/rose-meta/lfric-gungho/HEAD/rose-meta.conf b/science/gungho/rose-meta/lfric-gungho/HEAD/rose-meta.conf index 96787974e6..909cb3fd00 100644 --- a/science/gungho/rose-meta/lfric-gungho/HEAD/rose-meta.conf +++ b/science/gungho/rose-meta/lfric-gungho/HEAD/rose-meta.conf @@ -1081,6 +1081,7 @@ trigger=namelist:external_forcing=hs_random: this == "'held_suarez'" ; =namelist:external_forcing=pc2_force_response: this != "'none'"; =namelist:nudging: this == "'nudging'"; =namelist:multires_coupling=coarse_nudging: this == "'nudging'"; + =namelist:multires_coupling=nudging_mesh_name: this == "'nudging'"; =namelist:files=nudging_directory: this == "'nudging'"; =namelist:files=nudging_filename: this == "'nudging'"; value-titles=None, Held-Suarez, Earth-Like, Tidally Locked Earth, @@ -1191,6 +1192,7 @@ trigger=namelist:external_forcing=filtering_order: this == "'filtering'"; =namelist:external_forcing=diffusion_coefficient: this == "'diffusion'"; =namelist:nudging: this == "'nudging'"; =namelist:multires_coupling=coarse_nudging: this == "'nudging'"; + =namelist:multires_coupling=nudging_mesh_name: this == "'nudging'"; =namelist:files=nudging_directory: this == "'nudging'"; =namelist:files=nudging_filename: this == "'nudging'", =namelist:wind_forcing: this == "'profile'"; @@ -4007,7 +4009,6 @@ description=Whether to apply nudging on a coarser mesh than that used help=The mesh is given by the nudging_mesh_name option. !kind=default sort-key=Section-A09 -trigger=namelist:multires_coupling=nudging_mesh_name: .true. ; type=logical [namelist:multires_coupling=coarse_rad_aerosol] @@ -4137,65 +4138,258 @@ ns=namelist/Science/Nudging sort-key=Section-A14 title=Nudging -[namelist:nudging=nudge_data_levels] +[namelist:nudging=nudging_level_bottom] compulsory=true -description=Number of levels for the Nudging reference data -help=The number of levels i.e. Z-dimension for the T,U,V reference fields - =to be read from the Nudging input files +description=Bottom level to apply full-strength nudging from +fail-if=this < 0; + =this > namelist:nudging=nudging_level_top; +help=Applies to both W3 and Wtheta fields. Wtheta fields are counted from 0 at + =the surface, while W3 fields are counted from 1/2 in the lowest level. !kind=default ns=namelist/Science/Nudging -sort-key=Panel-A01b +sort-key=Panel-A05a type=integer -[namelist:nudging=nudging_level_bottom] +[namelist:nudging=nudging_level_taper_bottom] compulsory=true -description=Bottom level to apply nudging from +description=Level above which to ramp up nudging from 0 to full strength fail-if=this < 0; -help= + =this > namelist:nudging=nudging_level_bottom; +help=Above this level, nudging is tapered up to nudging_level_bottom. + =At this level and below, nudging is 0. + =Applies to both W3 and Wtheta fields. Wtheta fields are counted from 0 at + =the surface, while W3 fields are counted from 1/2 in the lowest level. !kind=default ns=namelist/Science/Nudging -sort-key=Panel-A05a +sort-key=Panel-A05c +type=integer + +[namelist:nudging=nudging_level_taper_top] +compulsory=true +description=Level below which nudging is tapered +fail-if=this < 0; + =this < namelist:nudging=nudging_level_top; +help=Below this level, nudging is tapered from 1 at nudging_level_top to 0 + =at nudging_level_taper_top. At this level and above, nudging is 0, unless + =this is equal to nudging_level_top. + =Applies to both W3 and Wtheta fields. Wtheta fields are counted from 0 at + =the surface, while W3 fields are counted from 1/2 in the lowest level. +!kind=default +ns=namelist/Science/Nudging +sort-key=Panel-A05d type=integer [namelist:nudging=nudging_level_top] compulsory=true -description=Top level to apply nudging from +description=Top level to apply full-strength nudging to fail-if=this > namelist:extrusion=number_of_layers; -help= + =this < namelist:nudging=nudging_level_bottom; +help=Applies to both W3 and Wtheta fields. Wtheta fields are counted from 0 at + =the surface, while W3 fields are counted from 1/2 in the lowest level. !kind=default ns=namelist/Science/Nudging sort-key=Panel-A05b type=integer +[namelist:nudging=nudging_method] +compulsory=true +description=The method used for nudging +!enumeration=true +help=Nudging has been implemented with the following methods: + = (a) convolution: smooths a field towards a reference state using a + = convolution in physical space (which may be equivalent to a specific + = filter in spectral space) + = (b) newtonian: pointwise nudging towards a reference state +!kind=default +ns=namelist/Science/Nudging +sort-key=Panel-A02 +trigger=namelist:nudging=spectral_stencil_extent: this == "'convolution'"; + =namelist:nudging=spectral_kmax: this != "'newtonian'"; + =namelist:nudging=spectral_kmin: this != "'newtonian'"; + =namelist:nudging=spectral_envelope_width: this == "'convolution'"; +value-titles=Convolution, Newtonian +values='convolution', 'newtonian' + +[namelist:nudging=nudging_min_tropopause_level] +compulsory=true +description=Minimum level to enforce for tropopause, above which nudging is not + =applied. Controls whether to use tropopause as vertical cap of + =nudging. +fail-if=this > namelist:extrusion=number_of_layers; +help=Controls whether nudging is applied in the stratosphere, allowing the + =nudging to be applied to different vertical levels in different columns. + =Set to 0 to always cap nudging using the model-determined tropopause level. + =Set to nudging_level_taper_top (or above) to always apply nudging up to + =that level, regardless of the model-determined tropopause level. + =Applies to both W3 and Wtheta fields. Wtheta fields are counted from 0 at + =the surface, while W3 fields are counted from 1/2 in the lowest level. +!kind=default +ns=namelist/Science/Nudging +sort-key=Panel-A05e +type=integer + +[namelist:nudging=nudging_relax_time_theta] +compulsory=true +description=Time scale for nudging (hours) for potential temperature +fail-if=this < 0.0 ; +help=The nudging scheme applies an increment to nudge (parts of the + =spectrum of) a field towards a specified reference state. This parameter + =gives the time scale (in hours) after which the spectrum of the field + =would match that of the reference field. A value of zero sets the (spectrum + =of the) field to that of the reference state in a single time step. + =This option controls potential temperature nudging, and can be set to zero + =to disable nudging of potential temperature. +!kind=default +ns=namelist/Science/Nudging +sort-key=Panel-A03a +type=real + +[namelist:nudging=nudging_relax_time_u] +compulsory=true +description=Time scale for nudging (hours) for zonal wind +fail-if=this < 0.0 ; +help=The nudging scheme applies an increment to nudge (parts of the + =spectrum of) a field towards a specified reference state. This parameter + =gives the time scale (in hours) after which the spectrum of the field + =would match that of the reference field. A value of zero sets the (spectrum + =of the) field to that of the reference state in a single time step. + =This option controls zonal wind nudging, and can be set to zero + =to disable nudging of zonal wind. +!kind=default +ns=namelist/Science/Nudging +sort-key=Panel-A03b +type=real + +[namelist:nudging=nudging_relax_time_v] +compulsory=true +description=Time scale for nudging (hours) for meridional wind +fail-if=this < 0.0 ; +help=The nudging scheme applies an increment to nudge (parts of the + =spectrum of) a field towards a specified reference state. This parameter + =gives the time scale (in hours) after which the spectrum of the field + =would match that of the reference field. A value of zero sets the (spectrum + =of the) field to that of the reference state in a single time step. + =This option controls meridional wind nudging, and can be set to zero + =to disable nudging of meridional wind. +!kind=default +ns=namelist/Science/Nudging +sort-key=Panel-A03c +type=real + [namelist:nudging=nudging_source] compulsory=true description=The source of the reference states for nudging !enumeration=true -help=Nudging has been implemented to nudge - = (a) using Re-analyses (e.g. ERA) data - = (b) using model (e.g. NWP, M/L) data +help=This determines the processing necessary for the external data. For example + =the model could be nudged to ECMWF data on pressure levels (e.g. ERA + =re-analysis data or AIFS output), or to UM or LFRic re-analysis data on + =model levels. + =Only ECMWF data has currently been implemented. !kind=default ns=namelist/Science/Nudging -sort-key=Panel-A01 -value-titles=ERA, AIFS -values='era', 'aifs' +sort-key=Panel-A01c +value-titles=ECMWF +values='ecmwf' -[namelist:nudging=nudging_width_bottom] +[namelist:nudging=nudging_spinup_end] compulsory=true -description=Width of tapering of nudging at the bottom of the nudging region -help= +description=Time at which to end nudging spinup (hours) +fail-if=this < 0.0 ; +help=This option allows the nudging to be ramped up linearly over some + =spin-up period. This parameter specifies the length of time after the start + =of the model simulation to end the nudging spinup, in hours. !kind=default ns=namelist/Science/Nudging -sort-key=Panel-A05c +sort-key=Panel-A04b +type=real + +[namelist:nudging=nudging_spinup_start] +compulsory=true +description=Time at which to start nudging spinup (hours) +fail-if=this < 0.0 ; +help=This option allows the nudging to be ramped up linearly over some + =spin-up period. This parameter specifies the length of time after the start + =of the model simulation to begin the nudging spinup, in hours. +!kind=default +ns=namelist/Science/Nudging +sort-key=Panel-A04a +type=real + +[namelist:nudging=nudging_stop_time] +compulsory=true +description=Time at which to stop nudging (hours) +fail-if=this < 0.0 ; +help=This parameter specifies the length of time after the start + =of the model simulation to stop the nudging, in hours. +!kind=default +ns=namelist/Science/Nudging +sort-key=Panel-A04c +type=real + +[namelist:nudging=num_ref_data_levels] +compulsory=true +description=Number of levels for the Nudging reference data +help=The number of levels i.e. Z-dimension for the T,U,V reference fields + =to be read from the Nudging input files. +!kind=default +ns=namelist/Science/Nudging +sort-key=Panel-A01b type=integer -[namelist:nudging=nudging_width_top] +[namelist:nudging=spectral_envelope_width] compulsory=true -description=Width of tapering of nudging at the top of the nudging region -help= +description=Width of Gaussian envelope to apply to the spectral nudging filter +fail-if=this <= 0.0; +help=The filter used by the spectral nudging scheme is the product of the + =sum of Legendre polynomials with a Gaussian envelope. Since the convolution + =is performed on a finite stencil, it is possible for some wavenumbers to + =be amplified rather than damped. The Gaussian envelope helps to avoid this + =effect. This number is the width of the Gaussian envelope in radians, + =corresponding to physical space. !kind=default ns=namelist/Science/Nudging -sort-key=Panel-A05d +sort-key=Panel-A06c +type=real + +[namelist:nudging=spectral_kmax] +compulsory=true +description=Maximum wavenumber to use in spectral nudging filter +fail-if=this < 0; + =this < namelist:nudging=spectral_kmin; +help=The spectral nudging scheme uses a convolution to nudge parts of the + =spectrum towards a specified reference state. The convolution replicates + =a low-pass top-hat filter in spectral space. This parameter sets the + =maximum wavenumber for the top-hat filter. +!kind=default +ns=namelist/Science/Nudging +sort-key=Panel-A06a +type=integer + +[namelist:nudging=spectral_kmin] +compulsory=true +description=Minimum wavenumber to use in spectral nudging filter +fail-if=this < 0; +help=The spectral nudging scheme uses a convolution to nudge parts of the + =spectrum towards a specified reference state. The convolution replicates + =a low-pass top-hat filter in spectral space. This parameter sets the + =minimum wavenumber for the top-hat filter. +!kind=default +ns=namelist/Science/Nudging +sort-key=Panel-A06b +type=integer + +[namelist:nudging=spectral_stencil_extent] +compulsory=true +description=Sets the stencil extent to be used in spectral nudging +fail-if=this < 1; +help=The spectral nudging scheme uses a convolution to nudge parts of the + =spectrum towards a specified reference state. This parameter sets the + =stencil extent to be used in the convolution. Note that if this is not + =large enough to resolve the filter, it can cause numerical instability as + =some wavelengths can be amplified rather than damped through the nudging. +!kind=default +ns=namelist/Science/Nudging +sort-key=Panel-A07 type=integer #============================================================================== diff --git a/science/gungho/rose-meta/lfric-gungho/versions.py b/science/gungho/rose-meta/lfric-gungho/versions.py index 601f328321..4fab710ea7 100644 --- a/science/gungho/rose-meta/lfric-gungho/versions.py +++ b/science/gungho/rose-meta/lfric-gungho/versions.py @@ -286,7 +286,6 @@ def upgrade(self, config, meta_config=None): self.add_setting(config, ["namelist:vapour_relax", "times"], "0.0") self.add_setting(config, ["namelist:vapour_relax", "timescale"], "1.0") self.add_setting(config, ["namelist:vapour_relax", "variable"], "'mr'") - return config, self.reports @@ -332,5 +331,83 @@ def upgrade(self, config, meta_config=None): self.add_setting( config, ["namelist:jules_sea_seaice", "snowpatch"], "0.02" ) + return config, self.reports + + +class vn32_t670(MacroUpgrade): + """Upgrade macro for ticket #670 by Thomas Bendall.""" + + BEFORE_TAG = "vn3.2_t400" + AFTER_TAG = "vn3.2_t670" + + def upgrade(self, config, meta_config=None): + # Commands From: rose-meta/lfric-gungho + # Add new nudging namelist options + self.add_setting( + config, ["namelist:nudging", "nudging_method"], "'convolution'" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_theta"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_u"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_v"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_start"], "12.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_end"], "24.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_stop_time"], "10000.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_bottom"], "5" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_top"], "52" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_min_tropopause_level"], "48" + ) + self.change_setting_value( + config, ["namelist:nudging", "nudging_level_bottom"], "6" + ) + self.add_setting( + config, ["namelist:nudging", "num_ref_data_levels"], "137" + ) + self.add_setting(config, ["namelist:nudging", "spectral_kmax"], "20") + self.add_setting(config, ["namelist:nudging", "spectral_kmin"], "2") + self.add_setting( + config, ["namelist:nudging", "spectral_stencil_extent"], "12" + ) + self.add_setting( + config, ["namelist:nudging", "spectral_envelope_width"], "0.1" + ) + # Remove retired settings + self.remove_setting(config, ["namelist:nudging", "nudging_source"]) + self.remove_setting( + config, ["namelist:nudging", "nudging_width_bottom"] + ) + self.remove_setting(config, ["namelist:nudging", "nudging_width_top"]) + self.remove_setting(config, ["namelist:nudging", "nudge_data_levels"]) + # If nudging_mesh_name is still the default '' value, set it to + # match dynamics_mesh_name + nudging_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "nudging_mesh_name"] + ) + if nudging_mesh_name == "''": + dynamics_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "dynamics_mesh_name"] + ) + if dynamics_mesh_name is not None: + self.change_setting_value( + config, + ["namelist:multires_coupling", "nudging_mesh_name"], + dynamics_mesh_name, + ) return config, self.reports diff --git a/science/gungho/source/algorithm/core_dynamics/intermesh_mappings_alg_mod.x90 b/science/gungho/source/algorithm/core_dynamics/intermesh_mappings_alg_mod.x90 index 61ee45d90c..3c434fb059 100644 --- a/science/gungho/source/algorithm/core_dynamics/intermesh_mappings_alg_mod.x90 +++ b/science/gungho/source/algorithm/core_dynamics/intermesh_mappings_alg_mod.x90 @@ -48,7 +48,7 @@ module intermesh_mappings_alg_mod public :: obtain_shifted_rho ! Shifts rho from prime extrusion to shifted extrusion private :: restrict_scalar ! Non-conservative restriction of W3/Wtheta field private :: restrict_multidata ! Non-conservative restriction of W3/Wtheta multidata field - private :: prolong_scalar_simple ! Injection/recovery of W3/Wtheta field + public :: prolong_scalar_simple ! Injection/recovery of W3/Wtheta field private :: prolong_multidata_simple ! Injection/recovery of W3/Wtheta multidata field private :: prolong_scalar_reversible ! Reversible prolongation of non-conservative W3/Wtheta field public :: map_scalar_intermesh ! Public routine for non-conservative W3/Wtheta fields diff --git a/science/gungho/source/algorithm/physics/external_forcing_alg_mod.X90 b/science/gungho/source/algorithm/physics/external_forcing_alg_mod.X90 index 3b101aedf9..194bb2a862 100644 --- a/science/gungho/source/algorithm/physics/external_forcing_alg_mod.X90 +++ b/science/gungho/source/algorithm/physics/external_forcing_alg_mod.X90 @@ -6,8 +6,7 @@ !> @brief Interface to external forcing code. module external_forcing_alg_mod - use external_forcing_config_mod, only: theta_forcing, & - theta_forcing_none, & + use external_forcing_config_mod, only: theta_forcing_none, & theta_forcing_held_suarez, & theta_forcing_earth_like, & theta_forcing_tidally_locked_earth, & @@ -15,8 +14,6 @@ module external_forcing_alg_mod theta_forcing_deep_hot_jupiter, & theta_forcing_temp_tend, & theta_forcing_nudging, & - theta_relaxation, & - vapour_forcing, & vapour_forcing_none, & vapour_forcing_profile, & vapour_relaxation, & @@ -26,18 +23,18 @@ module external_forcing_alg_mod wind_forcing_filtering, & wind_forcing_diffusion, & wind_forcing_profile, & - wind_forcing_nudging, & - vertadvect_forcing, & - geostrophic_forcing, & - filtering_order, & - diffusion_order, & - diffusion_coefficient, & - pc2_force_response + wind_forcing_nudging use log_mod, only: log_event, log_scratch_space, & LOG_LEVEL_INFO, LOG_LEVEL_ERROR use lfric_xios_write_mod, only: write_field_generic use field_parent_mod, only: write_interface + use nudging_alg_mod, only: nudging_alg, & + set_nudging_reference_fields, & + set_nudging_trop_level, & + compute_nudging_spinup_factor + use map_fd_to_prognostics_alg_mod, only: set_wind + use held_suarez_fv_kernel_mod, only: held_suarez_fv_kernel_type use held_suarez_fv_wind_kernel_mod, only: held_suarez_fv_wind_kernel_type use earth_like_kernel_mod, only: earth_like_kernel_type @@ -50,7 +47,7 @@ module external_forcing_alg_mod #endif ! Derived Types use config_mod, only: config_type - use constants_mod, only: i_def, r_def, l_def + use constants_mod, only: i_def, r_def, l_def, IMDI, str_def use field_mod, only: field_type use field_collection_mod, only: field_collection_type use integer_field_mod, only: integer_field_type @@ -67,8 +64,9 @@ module external_forcing_alg_mod use mr_indices_mod, only: nummr, imr_v use planet_config_mod, only: kappa use io_config_mod, only: write_diag, use_xios_io - use section_choice_config_mod, only: cloud, cloud_um - use cloud_config_mod, only: scheme, scheme_pc2 + use section_choice_config_mod, only: cloud, cloud_um, section_choice_is_loaded + use cloud_config_mod, only: scheme, scheme_pc2, cloud_is_loaded + use nudging_config_mod, only: nudging_is_loaded use reference_element_mod, only: T use sci_set_any_dof_kernel_mod, only: set_any_dof_kernel_type @@ -132,50 +130,98 @@ contains implicit none - type(config_type), intent(in) :: config - - type( field_collection_type ), intent( in ) :: derived_fields, cloud_fields - type( field_type ), intent( in ) :: u, theta, exner, exner_in_wth, rho - type( field_type ), intent( in ) :: mr_n(nummr) - type( field_type ), intent( out ) :: dtheta_forcing, du_forcing - type( field_type ), intent( out ) :: dmv_forcing, dmcl_forcing, & - dcfl_forcing, dbcf_forcing - type( mesh_type ), intent( in ), pointer :: twod_mesh - real( kind=r_def ), intent( in ) :: dt, time_now - logical( kind=l_def ), intent(inout) :: theta_forcing_done, & - vapour_forcing_done, & - wind_forcing_done + type(config_type), intent(in) :: config + type(field_collection_type), intent(inout) :: derived_fields + type(field_collection_type), intent(in) :: cloud_fields + type(field_type), intent(in) :: u, theta, exner, exner_in_wth, rho + type(field_type), intent(in) :: mr_n(nummr) + type(field_type), intent(out) :: dtheta_forcing, du_forcing + type(field_type), intent(out) :: dmv_forcing, dmcl_forcing + type(field_type), intent(out) :: dcfl_forcing, dbcf_forcing + type(mesh_type), pointer, intent(in) :: twod_mesh + real(kind=r_def), intent(in) :: dt, time_now + logical(kind=l_def), intent(inout) :: theta_forcing_done + logical(kind=l_def), intent(inout) :: vapour_forcing_done + logical(kind=l_def), intent(inout) :: wind_forcing_done ! Temporary field to unpack from field collection - type( field_type ), pointer :: u_physics => null() - type( field_type ), pointer :: w2_rmultiplicity => null() - type( field_type ), pointer :: dA => null() - type( field_type ), pointer :: chi(:) => null() - type( field_type ), pointer :: panel_id => null() - type( field_type ), pointer :: height_wth => null() - type( field_type ), pointer :: height_w1 => null() - type( field_type ), pointer :: height_w2 => null() - type( field_type ), pointer :: dx_at_w2 => null() - - type( integer_field_type ), pointer :: face_selector_ew - type( integer_field_type ), pointer :: face_selector_ns - - type( field_type ) :: u_smoothed, dx, visc_m - - integer( kind=i_def ) :: order, nlayers - real( kind=r_def ) :: diffusion_coefficient_scaled, dx_sum, dx_size - - type( mesh_type ), pointer :: mesh => null() - procedure(write_interface), pointer :: write_behaviour => null() - integer(tik) :: id + type(field_type), pointer :: u_physics + type(field_type), pointer :: w2_rmultiplicity + type(field_type), pointer :: dA + type(field_type), pointer :: chi(:) + type(field_type), pointer :: panel_id + type(field_type), pointer :: height_wth + type(field_type), pointer :: height_w1 + type(field_type), pointer :: height_w2 + type(field_type), pointer :: dx_at_w2 + type(field_type), pointer :: u_in_w3 + type(field_type), pointer :: v_in_w3 + type(field_type), pointer :: u_in_w3_ref + type(field_type), pointer :: v_in_w3_ref + type(field_type), pointer :: theta_ref + type(integer_field_type), pointer :: face_selector_ew + type(integer_field_type), pointer :: face_selector_ns + type(mesh_type), pointer :: mesh + procedure(write_interface), pointer :: write_behaviour + + type(integer_field_type) :: trop_level + + type(field_type) :: u_smoothed, dx, visc_m + type(field_type) :: du_in_w3, dv_in_w3, dw_in_wth + integer(kind=i_def) :: order, nlayers + real(kind=r_def) :: nudging_time_weight + real(kind=r_def) :: diffusion_coefficient_scaled, dx_sum, dx_size + integer(tik) :: id + + ! Config variables + integer(kind=i_def) :: theta_forcing + integer(kind=i_def) :: vapour_forcing, wind_forcing + logical(kind=l_def) :: geostrophic_forcing, vertadvect_forcing + logical(kind=l_def) :: pc2_force_response, theta_relaxation + integer(kind=i_def) :: filtering_order, diffusion_order + real(kind=r_def) :: diffusion_coefficient + real(kind=r_def) :: kappa + real(kind=r_def) :: nudging_spinup_factor + real(kind=r_def) :: nudging_relax_time + logical(kind=l_def) :: write_diag, use_xios_io + integer(kind=i_def) :: cloud, scheme if ( LPROF ) call start_timing( id, 'external_forcing' ) + ! Get config variables + theta_forcing = config%external_forcing%theta_forcing() + theta_relaxation = config%external_forcing%theta_relaxation() + vapour_forcing = config%external_forcing%vapour_forcing() + wind_forcing = config%external_forcing%wind_forcing() + geostrophic_forcing = config%external_forcing%geostrophic_forcing() + vertadvect_forcing = config%external_forcing%vertadvect_forcing() + pc2_force_response = config%external_forcing%pc2_force_response() + filtering_order = config%external_forcing%filtering_order() + diffusion_order = config%external_forcing%diffusion_order() + diffusion_coefficient = config%external_forcing%diffusion_coefficient() + write_diag = config%io%write_diag() + use_xios_io = config%io%use_xios_io() + kappa = config%planet%kappa() + cloud = IMDI + scheme = IMDI + if (section_choice_is_loaded()) cloud = config%section_choice%cloud() + if (cloud_is_loaded()) scheme = config%cloud%scheme() + mesh => theta%get_mesh() height_wth => get_height_fv(config, mesh, Wtheta) nlayers = mesh%get_nlayers() - if ( theta_forcing /= theta_forcing_none .or. theta_relaxation .or. & + ! If nudging is to be applied, obtain reference fields + if ( theta_forcing == theta_forcing_nudging .or. & + wind_forcing == wind_forcing_nudging ) then + nudging_spinup_factor = compute_nudging_spinup_factor(config, time_now) + if (nudging_spinup_factor > 0.0_r_def) then + call set_nudging_reference_fields(config, derived_fields, exner) + call set_nudging_trop_level(config, trop_level, theta, exner_in_wth) + end if + end if + + if ( theta_forcing /= theta_forcing_none .or. theta_relaxation .or. & vertadvect_forcing ) then call dtheta_forcing%initialise( vector_space = theta%get_function_space() ) call invoke( setval_c(dtheta_forcing, 0.0_r_def) ) @@ -238,7 +284,22 @@ contains call temp_tend_profile_alg( config, dtheta_forcing, exner_in_wth, time_now ) case(theta_forcing_nudging) - ! DO nothing - placeholder for future, field will not be modified + ! Determine nudging time-weighting for theta + nudging_relax_time = 3600.0_r_def * config%nudging%nudging_relax_time_theta() + if (nudging_relax_time > 0.0_r_def) then + nudging_time_weight = dt * nudging_spinup_factor / nudging_relax_time + else + nudging_time_weight = 0.0_r_def + end if + + ! Perform nudging + if (nudging_time_weight > 0.0_r_def) then + call derived_fields%get_field('theta_nudging_ref', theta_ref) + call nudging_alg(config, dtheta_forcing, theta, theta_ref, & + nudging_time_weight, trop_level) + else + call invoke(setval_c(dtheta_forcing, 0.0_r_def)) + end if case default @@ -370,9 +431,58 @@ contains case ( wind_forcing_nudging ) - ! Placeholder for future, only initialise forcing field - call du_forcing%initialise( vector_space = u%get_function_space() ) - call invoke(setval_c(du_forcing, 0.0_r_def)) + call du_forcing%initialise(u%get_function_space()) + + if (nudging_spinup_factor > 0.0_r_def) then + ! Extract fields for spectral nudging + call derived_fields%get_field('u_in_w3', u_in_w3) + call derived_fields%get_field('v_in_w3', v_in_w3) + + ! Initialise components for wind increment + call du_in_w3%initialise(u_in_w3%get_function_space()) + call dv_in_w3%initialise(v_in_w3%get_function_space()) + call dw_in_wth%initialise(theta%get_function_space()) + + ! Apply nudging to calculate increments for each component + ! u ---------------------------------------------------------------- + nudging_relax_time = 3600.0_r_def * config%nudging%nudging_relax_time_u() + if (nudging_relax_time > 0.0_r_def) then + nudging_time_weight = dt * nudging_spinup_factor / nudging_relax_time + else + nudging_time_weight = 0.0_r_def + end if + + if (nudging_time_weight > 0.0_r_def) then + call derived_fields%get_field('u_in_w3_nudging_ref', u_in_w3_ref) + call nudging_alg(config, du_in_w3, u_in_w3, u_in_w3_ref, & + nudging_time_weight, trop_level) + else + call invoke(setval_c(du_in_w3, 0.0_r_def)) + end if + + ! v ---------------------------------------------------------------- + nudging_relax_time = 3600.0_r_def * config%nudging%nudging_relax_time_v() + if (nudging_relax_time > 0.0_r_def) then + nudging_time_weight = dt * nudging_spinup_factor / nudging_relax_time + else + nudging_time_weight = 0.0_r_def + end if + + if (nudging_time_weight > 0.0_r_def) then + call derived_fields%get_field('v_in_w3_nudging_ref', v_in_w3_ref) + call nudging_alg(config, dv_in_w3, v_in_w3, v_in_w3_ref, & + nudging_time_weight, trop_level) + else + call invoke(setval_c(dv_in_w3, 0.0_r_def)) + end if + + ! Combine increments to obtain full wind increment + call invoke(setval_c(dw_in_wth, 0.0_r_def)) + call set_wind(config, du_forcing, du_in_w3, dv_in_w3, dw_in_wth) + + else + call invoke(setval_c(du_forcing, 0.0_r_def)) + end if case( wind_forcing_profile ) diff --git a/science/gungho/source/algorithm/physics/nudging_alg_mod.X90 b/science/gungho/source/algorithm/physics/nudging_alg_mod.X90 new file mode 100644 index 0000000000..46a0f8e079 --- /dev/null +++ b/science/gungho/source/algorithm/physics/nudging_alg_mod.X90 @@ -0,0 +1,429 @@ +!------------------------------------------------------------------------------- +! (C) Crown copyright Met Office. All rights reserved. +! The file LICENCE, distributed with this code, contains details of the terms +! under which the code may be used. +!------------------------------------------------------------------------------- +!> @brief Computes the forcing from nudging to some external data. +module nudging_alg_mod + + ! Infrastructure + use config_mod, only: config_type + use constants_mod, only: i_def, l_def, r_def, str_def + use extrusion_mod, only: TWOD + use field_mod, only: field_type + use field_collection_mod, only: field_collection_type + use fs_continuity_mod, only: W3, Wtheta + use function_space_mod, only: function_space_type + use function_space_collection_mod, only: function_space_collection + use integer_field_mod, only: integer_field_type + use log_mod, only: log_event, LOG_LEVEL_ERROR + use mesh_mod, only: mesh_type + use mesh_collection_mod, only: mesh_collection + use timing_mod, only: start_timing, stop_timing, & + tik, LPROF + + ! Configs + use external_forcing_config_mod, only: theta_forcing_nudging, & + wind_forcing_nudging + use nudging_config_mod, only: nudging_method_newtonian, & + nudging_method_convolution + + ! Kernels + use convolution_2d_kernel_mod, only: convolution_2d_kernel_type + use newtonian_nudging_kernel_mod, only: newtonian_nudging_kernel_type + use nudging_ecmwf_vert_interp_kernel_mod, & + only: nudging_ecmwf_vert_interp_kernel_type + use psykal_lite_mod, only: invoke_newtonian_nudging_kernel_type, & + invoke_convolution_2d_kernel_type + + ! Other algorithms + use intermesh_mappings_alg_mod, only: map_scalar_intermesh, & + prolong_scalar_simple + + ! Getters to runtime constants + use physics_constants_mod, only: get_convolution_weights, & + get_nudging_weights + + implicit none + + private + public :: nudging_alg + public :: compute_nudging_spinup_factor + public :: set_nudging_trop_level + public :: set_nudging_reference_fields + +contains + + !> @details Computes the increment associated with nudging the spectrum of a + !! field towards that of a reference field. + !> @param[in] config Application namelist configuration object + !> @param[in,out] increment Increment field to be computed + !> @param[in] field_in Input field to be nudged + !> @param[in] field_ref Reference field to nudge towards + subroutine nudging_alg(config, increment, field_in, field_ref, & + time_weight, tropopause_level) + + use multires_coupling_config_mod, only: recovery_order_linear + + implicit none + + type(config_type), intent(in) :: config + type(field_type), intent(inout) :: increment + type(field_type), intent(in) :: field_in, field_ref + real(kind=r_def), intent(in) :: time_weight + type(integer_field_type), intent(in) :: tropopause_level + + type(mesh_type), pointer :: mesh, coarse_mesh + type(field_type), pointer :: convolution_weights + real(kind=r_def), pointer :: vertical_weights(:) + type(function_space_type), pointer :: fs + + type(field_type) :: coarse_field_in, coarse_increment, coarse_field_diff + integer(kind=i_def) :: fs_id, coarse_mesh_id + integer(kind=i_def) :: nvert(1) + integer(kind=tik) :: id + + ! Config variables + logical(kind=l_def) :: coarse_nudging, use_multires_coupling + integer(kind=i_def) :: nudging_method, spectral_stencil_extent + integer(kind=i_def) :: theta_forcing, wind_forcing + integer(kind=i_def) :: min_level, max_level + character(len=str_def) :: nudging_mesh_name + real(kind=r_def) :: p_zero, kappa + + if ( LPROF ) call start_timing( id, 'nudging' ) + + ! ------------------------------------------------------------------------ ! + ! Get config variables + ! ------------------------------------------------------------------------ ! + + use_multires_coupling = config%formulation%use_multires_coupling() + nudging_method = config%nudging%nudging_method() + spectral_stencil_extent = config%nudging%spectral_stencil_extent() + theta_forcing = config%external_forcing%theta_forcing() + wind_forcing = config%external_forcing%wind_forcing() + nudging_mesh_name = config%multires_coupling%nudging_mesh_name() + p_zero = config%planet%p_zero() + kappa = config%planet%kappa() + min_level = config%nudging%nudging_level_taper_bottom() + max_level = config%nudging%nudging_level_taper_top() + + ! Getting nudging mesh name + coarse_nudging = .false. + nudging_mesh_name = config%base_mesh%prime_mesh_name() + if (use_multires_coupling) then + coarse_nudging = config%multires_coupling%coarse_nudging() + if (coarse_nudging) then + nudging_mesh_name = config%multires_coupling%nudging_mesh_name() + end if + end if + + ! ------------------------------------------------------------------------ ! + ! Set pointers and initialise fields + ! ------------------------------------------------------------------------ ! + + if (use_multires_coupling .and. coarse_nudging) then + coarse_mesh => mesh_collection%get_mesh(trim(nudging_mesh_name)) + else + mesh => field_in%get_mesh() + coarse_mesh => mesh + end if + + ! Check reference field is on the correct mesh + coarse_mesh_id = coarse_mesh%get_id() + if (coarse_mesh_id /= field_ref%get_mesh_id()) then + + call log_event("nudging_alg: reference field is not on the mesh " // & + "specified for nudging", LOG_LEVEL_ERROR) + end if + + ! Initialise coarse fields + fs => field_ref%get_function_space() + call coarse_field_in%initialise(fs) + call coarse_increment%initialise(fs) + + ! Point to weights used for nudging + fs_id = field_ref%which_function_space() + vertical_weights => get_nudging_weights(config, coarse_mesh, fs_id) + nvert(:) = SIZE(vertical_weights) + + ! ------------------------------------------------------------------------ ! + ! Coarsen + ! ------------------------------------------------------------------------ ! + + if (coarse_nudging) then + call map_scalar_intermesh(coarse_field_in, field_in) + else + call invoke( setval_X(coarse_field_in, field_in) ) + end if + + select case (nudging_method) + + ! ------------------------------------------------------------------------ ! + ! Newtonian relaxation + ! ------------------------------------------------------------------------ ! + case (nudging_method_newtonian) + ! Simply nudge towards reference field, with no spectral impact + call invoke( setval_c(coarse_increment, 0.0_r_def) ) + call invoke_newtonian_nudging_kernel_type(coarse_increment, & + coarse_field_in, field_ref, & + vertical_weights, & + tropopause_level, & + min_level, max_level, & + time_weight, nvert) + + ! ------------------------------------------------------------------------ ! + ! Convolution + ! ------------------------------------------------------------------------ ! + case (nudging_method_convolution) + ! Filter difference using a convolution in physical space + convolution_weights => get_convolution_weights(config, coarse_mesh) + call coarse_field_diff%initialise(fs, halo_depth=spectral_stencil_extent) + call invoke( X_minus_Y(coarse_field_diff, field_ref, coarse_field_in), & + ! Compute convolution + setval_c(coarse_increment, 0.0_r_def) ) + call invoke_convolution_2d_kernel_type(coarse_increment, & + coarse_field_diff, & + spectral_stencil_extent, & + vertical_weights, & + convolution_weights, & + tropopause_level, & + min_level, max_level, & + time_weight, nvert) + end select + + ! ------------------------------------------------------------------------ ! + ! Reconstruction + ! ------------------------------------------------------------------------ ! + + if (coarse_nudging) then + call prolong_scalar_simple(increment, coarse_increment, & + recovery_order_linear) + else + call invoke( setval_X(increment, coarse_increment) ) + end if + + if ( LPROF ) call stop_timing( id, 'nudging' ) + + end subroutine nudging_alg + + !> @brief Computes a spinup factor for nudging based on the current time + !> @param[in] config Application namelist configuration object + !> @param[in] time Current model time in seconds + !> @return Spinup factor for nudging, between 0 and 1 + function compute_nudging_spinup_factor(config, time) result(spinup_factor) + + implicit none + + type(config_type), intent(in) :: config + real(kind=r_def), intent(in) :: time + + ! Local variables + real(kind=r_def) :: nudging_spinup_start + real(kind=r_def) :: nudging_spinup_end + real(kind=r_def) :: nudging_stop_time + real(kind=r_def) :: time_hours + real(kind=r_def) :: spinup_factor + + nudging_spinup_start = config%nudging%nudging_spinup_start() + nudging_spinup_end = config%nudging%nudging_spinup_end() + nudging_stop_time = config%nudging%nudging_stop_time() + + ! Convert time to hours + time_hours = time / 3600.0_r_def + + if (time_hours < nudging_spinup_start) then + spinup_factor = 0.0_r_def + else if (time_hours < nudging_spinup_end) then + spinup_factor = ( (time_hours - nudging_spinup_start) & + / (nudging_spinup_end - nudging_spinup_start) ) + else if (time_hours < nudging_stop_time) then + spinup_factor = 1.0_r_def + else + spinup_factor = 0.0_r_def + end if + + end function compute_nudging_spinup_factor + + !> @brief Sets the tropopause level for nudging based on current model state + !> @param[in] config Application namelist configuration object + !> @param[in,out] trop_level Level of tropopause + !> @param[in] theta Potential temperature + !> @param[in] exner Exner pressure field (in same space as theta) + subroutine set_nudging_trop_level(config, trop_level, theta, exner) + +#ifdef UM_PHYSICS + use locate_tropopause_alg_mod, only: locate_tropopause_alg +#endif + + implicit none + + type(config_type), intent(in) :: config + type(integer_field_type), intent(inout) :: trop_level + type(field_type), target, intent(in) :: theta, exner + + ! Local variables + integer(kind=i_def) :: nudging_min_tropopause_level + integer(kind=i_def) :: nudging_level_taper_top + character(str_def) :: nudging_mesh_name + logical(kind=l_def) :: coarse_nudging + + type(mesh_type), pointer :: nudging_mesh + type(mesh_type), pointer :: nudging_twod_mesh + type(function_space_type), pointer :: w3_2d_fs, wt_fs +#ifdef UM_PHYSICS + type(field_type), pointer :: exner_trop, theta_trop + type(field_type), target :: exner_coarse, theta_coarse +#endif + + nudging_min_tropopause_level = config%nudging%nudging_min_tropopause_level() + nudging_level_taper_top = config%nudging%nudging_level_taper_top() + coarse_nudging = config%multires_coupling%coarse_nudging() + + nudging_mesh_name = trim(config%multires_coupling%nudging_mesh_name()) + nudging_mesh => mesh_collection%get_mesh(nudging_mesh_name) + nudging_twod_mesh => mesh_collection%get_mesh(nudging_mesh, TWOD) + w3_2d_fs => function_space_collection%get_fs(nudging_twod_mesh, 0, 0, W3) + wt_fs => function_space_collection%get_fs(nudging_mesh, 0, 0, Wtheta) + + call trop_level%initialise(w3_2d_fs) + + if (nudging_min_tropopause_level < nudging_level_taper_top) then +#ifdef UM_PHYSICS + if (coarse_nudging) then + ! TODO: already have coarse exner from setting reference fields, and + ! will need coarse theta for nudging. So could save a small bit of work + ! by pulling these mappings out to somewhere else (but it would be messy) + call exner_coarse%initialise(wt_fs) + call theta_coarse%initialise(wt_fs) + call map_scalar_intermesh(exner_coarse, exner) + call map_scalar_intermesh(theta_coarse, theta) + exner_trop => exner_coarse + theta_trop => theta_coarse + else + exner_trop => exner + theta_trop => theta + end if + + call locate_tropopause_alg(config, trop_level, theta_trop, exner_trop, & + nudging_twod_mesh, trop_diag=.false.) + call invoke( int_inc_max_aX(nudging_min_tropopause_level, trop_level) ) +#else + call invoke( int_setval_c(trop_level, nudging_min_tropopause_level) ) +#endif + else + call invoke( int_setval_c(trop_level, nudging_min_tropopause_level) ) + end if + + end subroutine set_nudging_trop_level + + !> @brief Sets the reference fields for nudging from external data + !> @param[in] config Application namelist configuration object + !> @param[in,out] derived_fields Collection of derived fields, which holds + !! both the model's reference fields and any + !! external reference fields + !> @param[in] exner Current Exner pressure field + subroutine set_nudging_reference_fields(config, derived_fields, exner) + + use nudging_config_mod, only: nudging_source_ecmwf + + implicit none + + ! Arguments + type(config_type), intent(in) :: config + type(field_collection_type), intent(inout) :: derived_fields + type(field_type), target, intent(in) :: exner + + ! Local variables + type(field_type), pointer :: theta_ref + type(field_type), pointer :: u_in_w3_ref + type(field_type), pointer :: v_in_w3_ref + type(field_type), pointer :: T_nudging_ext_ref + type(field_type), pointer :: u_nudging_ext_ref + type(field_type), pointer :: v_nudging_ext_ref + type(field_type), pointer :: surfp_nudging_ext_ref + type(field_type), pointer :: exner_in_wth + type(field_type), pointer :: exner_in_w3 + type(field_type), target :: exner_in_wth_coarse + type(field_type), target :: exner_in_w3_coarse + + ! Config variables + integer(kind=i_def) :: theta_forcing, wind_forcing + integer(kind=i_def) :: num_ref_data_levels + real(kind=r_def) :: p_zero, kappa + logical(kind=l_def) :: coarse_nudging + integer(kind=i_def) :: nudging_source + + ! Get config variables + theta_forcing = config%external_forcing%theta_forcing() + wind_forcing = config%external_forcing%wind_forcing() + num_ref_data_levels = config%nudging%num_ref_data_levels() + p_zero = config%planet%p_zero() + kappa = config%planet%kappa() + coarse_nudging = config%multires_coupling%coarse_nudging() + nudging_source = config%nudging%nudging_source() + + ! Get target reference fields from derived field collection + if (theta_forcing == theta_forcing_nudging) then + call derived_fields%get_field('theta_nudging_ref', theta_ref) + end if + + if (wind_forcing == wind_forcing_nudging) then + call derived_fields%get_field('u_in_w3_nudging_ref', u_in_w3_ref) + call derived_fields%get_field('v_in_w3_nudging_ref', v_in_w3_ref) + end if + + ! ------------------------------------------------------------------------ ! + ! Set reference fields based on input external data + ! ------------------------------------------------------------------------ ! + if (.not. (theta_forcing == theta_forcing_nudging .or. & + wind_forcing == wind_forcing_nudging) ) then + call log_event("set_nudging_reference_fields: nudging to external " // & + "data. This is currently only implemented to nudge " // & + "both theta and wind", LOG_LEVEL_ERROR) + end if + + if (nudging_source == nudging_source_ecmwf) then + ! Get external fields + call derived_fields%get_field('temperature_nudging_ext_ref', & + T_nudging_ext_ref) + call derived_fields%get_field('u_nudging_ext_ref', u_nudging_ext_ref) + call derived_fields%get_field('v_nudging_ext_ref', v_nudging_ext_ref) + call derived_fields%get_field('exner_in_wth', exner_in_wth) + call derived_fields%get_field('surface_pressure_nudging_ext_ref', & + surfp_nudging_ext_ref) + + ! Map Exner fields to coarse mesh, if required + if (coarse_nudging) then + call exner_in_wth_coarse%initialise(theta_ref%get_function_space()) + call exner_in_w3_coarse%initialise(u_in_w3_ref%get_function_space()) + call map_scalar_intermesh(exner_in_wth_coarse, exner_in_wth) + call map_scalar_intermesh(exner_in_w3_coarse, exner) + exner_in_wth => exner_in_wth_coarse + exner_in_w3 => exner_in_w3_coarse + else + exner_in_w3 => exner + end if + + ! Vertical remapping of external fields to model levels + call invoke( nudging_ecmwf_vert_interp_kernel_type(theta_ref, & + u_in_w3_ref, & + v_in_w3_ref, & + T_nudging_ext_ref, & + u_nudging_ext_ref, & + v_nudging_ext_ref, & + exner_in_wth, & + exner_in_w3, & + surfp_nudging_ext_ref,& + p_zero, kappa, & + num_ref_data_levels) ) + else + call log_event("set_nudging_reference_fields: nudging_source is " // & + "only currently implemented for nudging_source_ecmwf", & + LOG_LEVEL_ERROR) + end if + + end subroutine set_nudging_reference_fields + + +end module nudging_alg_mod diff --git a/science/gungho/source/algorithm/runtime_constants/physics_constants_mod.X90 b/science/gungho/source/algorithm/runtime_constants/physics_constants_mod.X90 index 674d098ffe..5b8bf50cae 100644 --- a/science/gungho/source/algorithm/runtime_constants/physics_constants_mod.X90 +++ b/science/gungho/source/algorithm/runtime_constants/physics_constants_mod.X90 @@ -42,7 +42,7 @@ module physics_constants_mod ! Variables private to this module that can only be accessed by public ! functions returning pointers to them - real(kind=r_def) :: dt_stored = 0.0_r_def + real(kind=r_def) :: dt_stored = 0.0_r_def ! ========================================================================== ! ! Inventories for use in the rest of the model @@ -54,6 +54,12 @@ module physics_constants_mod type(inventory_by_mesh_type) :: rdz_w3_inventory type(inventory_by_mesh_type) :: max_diff_wtheta_inventory + type(inventory_by_local_mesh_type) :: convolution_weights_inventory + + ! 1D vertical nudging weights + real(kind=r_def), target, allocatable :: nudging_weights_w3(:) + real(kind=r_def), target, allocatable :: nudging_weights_wt(:) + #ifdef UM_PHYSICS ! Spherical harmonics, used by stochastic physics type(inventory_by_local_mesh_type) :: Pnm_star_inventory @@ -69,6 +75,9 @@ module physics_constants_mod public :: get_rdz_fd1 public :: get_rdz_w3 public :: get_max_diff + public :: get_convolution_weights + public :: get_nudging_weights + #ifdef UM_PHYSICS public :: get_Pnm_star #endif @@ -404,6 +413,211 @@ contains end function get_max_diff + !> @brief Returns the weights for performing a 2D convolution + !> @param[in] config Application namelist configuration object + !> @param[in] mesh The mesh to get the object for + !> @return The convolution_weights field + function get_convolution_weights(config, mesh) result(convolution_weights) + + use base_mesh_config_mod, only: geometry_spherical + use convolution_2d_weights_kernel_mod, only: convolution_2d_weights_kernel_type + + implicit none + + type(config_type), intent(in) :: config + type(mesh_type), intent(in) :: mesh + integer(kind=i_def) :: local_mesh_id + integer(kind=i_def) :: ndata + integer(kind=i_def) :: stencil_depth + type(mesh_type), pointer :: twod_mesh + type(local_mesh_type), pointer :: local_mesh + type(function_space_type), pointer :: fs + type(field_type), pointer :: convolution_weights + type(field_type), pointer :: chi(:) + type(field_type), pointer :: panel_id + logical(kind=l_def) :: constant_exists + integer(kind=i_def) :: geometry, topology, coord_system + integer(kind=i_def) :: spectral_kmax, spectral_kmin + real(kind=r_def) :: envelope_width + real(kind=r_def) :: scaled_radius + integer(kind=tik) :: id + + ! Initialise inventory if it hasn't been done so already + if (.not. convolution_weights_inventory%is_initialised()) then + call convolution_weights_inventory%initialise(name="convolution_weights") + end if + + local_mesh => mesh%get_local_mesh() + local_mesh_id = local_mesh%get_id() + constant_exists = convolution_weights_inventory%paired_object_exists(local_mesh_id) + + ! Create object if it doesn't already exist + if (.not. constant_exists) then + chi => get_coordinates(mesh) + panel_id => get_panel_id(mesh) + twod_mesh => mesh_collection%get_mesh(mesh, TWOD) + geometry = config%base_mesh%geometry() + topology = config%base_mesh%topology() + coord_system = config%finite_element%coord_system() + scaled_radius = config%planet%scaled_radius() + spectral_kmax = config%nudging%spectral_kmax() + spectral_kmin = config%nudging%spectral_kmin() + envelope_width = config%nudging%spectral_envelope_width() + stencil_depth = config%nudging%spectral_stencil_extent() + ndata = (2*stencil_depth + 1)**2 + + if (geometry /= geometry_spherical) then + call log_event("Spectral nudging only implemented for spherical " // & + "geometry", LOG_LEVEL_ERROR) + end if + + if ( LPROF ) call start_timing( id, 'runtime_constants.physics' ) + + fs => function_space_collection%get_fs(twod_mesh, 0, 0, W3, ndata) + call convolution_weights_inventory%add_field(convolution_weights, fs, local_mesh) + + call invoke( setval_c(convolution_weights, 0.0_r_def), & + convolution_2d_weights_kernel_type(convolution_weights, & + chi, stencil_depth, & + panel_id, stencil_depth, & + spectral_kmax, & + spectral_kmin, & + envelope_width, & + geometry, topology, & + coord_system, & + scaled_radius) ) + + if ( LPROF ) call stop_timing( id, 'runtime_constants.physics' ) + end if + + call convolution_weights_inventory%get_field(local_mesh, convolution_weights) + + end function get_convolution_weights + + !> @brief Returns the vertical weights for applying spectral nudging + !> @param[in] config Application namelist configuration object + !> @param[in] mesh The mesh to get the object for + !> @param[in] fs_id The function space ID to get the object for + !> @return The 1D vertical nudging_weights array + function get_nudging_weights(config, mesh, fs_id) result(weights) + + implicit none + + type(config_type), intent(in) :: config + type(mesh_type), intent(in) :: mesh + integer(kind=i_def), intent(in) :: fs_id + integer(kind=i_def) :: mesh_id + real(kind=r_def), pointer :: weights(:) + integer(kind=i_def) :: nudging_level_top + integer(kind=i_def) :: nudging_level_bottom + integer(kind=i_def) :: nudging_taper_top + integer(kind=i_def) :: nudging_taper_bottom + integer(kind=i_def) :: k, nlayers, nl + real(kind=r_def) :: k_real, w3_offset + real(kind=r_def) :: taper_bottom, nudging_bottom + real(kind=r_def) :: nudging_top, taper_top + logical(kind=l_def) :: compute_weights + integer(kind=tik) :: id + + ! To avoid floating point issues when comparing integers as reals, set + ! a generous tolerance + real(kind=r_def), parameter :: EPS_INT = 0.1_r_def + + mesh_id = mesh%get_id() + nudging_level_bottom = config%nudging%nudging_level_bottom() + nudging_level_top = config%nudging%nudging_level_top() + nudging_taper_bottom = config%nudging%nudging_level_taper_bottom() + nudging_taper_top = config%nudging%nudging_level_taper_top() + + ! Work out if object exists + compute_weights = .false. + select case (fs_id) + case (W3) + if (.not. allocated(nudging_weights_w3)) compute_weights = .true. + case (Wtheta) + if (.not. allocated(nudging_weights_wt)) compute_weights = .true. + case default + call log_event( & + 'Spectral nudging weights only implemented for W3 and Wtheta', & + LOG_LEVEL_ERROR & + ) + end select + + if (compute_weights) then + + if ( LPROF ) call start_timing( id, 'runtime_constants.physics' ) + + nlayers = mesh%get_nlayers() + select case (fs_id) + case (W3) + nl = nlayers + w3_offset = 0.5_r_def + allocate(nudging_weights_w3(nl)) + weights => nudging_weights_w3 + case (Wtheta) + nl = nlayers + 1 + w3_offset = 0.0_r_def + allocate(nudging_weights_wt(nl)) + weights => nudging_weights_wt + end select + + ! Convert nudging levels to reals + taper_bottom = real(nudging_taper_bottom, r_def) + nudging_bottom = real(nudging_level_bottom, r_def) + nudging_top = real(nudging_level_top, r_def) + taper_top = real(nudging_taper_top, r_def) + + ! NOTE: the weights array is indexed from 1 to nl. For the Wtheta weights, + ! this means the weights are shifted by 1 from our usual interpretation + ! of model level number. This is because PSyclone can't handle arrays + ! with an initial index of 0. + do k = 0, nl-1 + k_real = real(k, r_def) + w3_offset + + ! Below start of taper, weights = 0 + ! If taper_bottom == nudging_bottom, then ramp is skipped and weights=1 + if (k_real < taper_bottom + EPS_INT & + .and. k_real < nudging_bottom + EPS_INT) then + weights(1 + k) = 0.0_r_def + + ! Ramp up from 0 to 1 between taper_bottom and nudging_bottom + else if (k_real < nudging_bottom + EPS_INT) then + weights(1 + k) = (k_real - taper_bottom) / & + max(1.0_r_def, nudging_bottom - taper_bottom) + + ! Full nudging between nudging_bottom and nudging_top + else if (k_real < nudging_top + EPS_INT) then + weights(1 + k) = 1.0_r_def + + ! Ramp down from 1 to 0 between nudging_top and taper_top + else if (k_real < taper_top + EPS_INT) then + weights(1 + k) = 1.0_r_def - (k_real - nudging_top) / & + max(1.0_r_def, taper_top - nudging_top) + + ! Above taper_top, weights = 0 + else + weights(1 + k) = 0.0_r_def + + end if + end do + if ( LPROF ) call stop_timing( id, 'runtime_constants.physics' ) + end if + + ! Point to existing object + select case (fs_id) + case (W3) + weights => nudging_weights_w3 + case (Wtheta) + weights => nudging_weights_wt + case default + call log_event( & + 'Spectral nudging weights only implemented for W3 and Wtheta', & + LOG_LEVEL_ERROR & + ) + end select + + end function get_nudging_weights + #ifdef UM_PHYSICS !> @brief Returns the Legendre Polynomials times the SH amplitude !> @param[in] config Application namelist configuration object @@ -472,6 +686,9 @@ contains call rdz_fd1_inventory%clear() call rdz_w3_inventory%clear() call max_diff_wtheta_inventory%clear() + if (allocated(nudging_weights_w3)) deallocate(nudging_weights_w3) + if (allocated(nudging_weights_wt)) deallocate(nudging_weights_wt) + call convolution_weights_inventory%clear() #ifdef UM_PHYSICS call Pnm_star_inventory%clear() #endif diff --git a/science/gungho/source/configuration/check_configuration_mod.F90 b/science/gungho/source/configuration/check_configuration_mod.F90 index 890952ea8e..a06fdc4073 100644 --- a/science/gungho/source/configuration/check_configuration_mod.F90 +++ b/science/gungho/source/configuration/check_configuration_mod.F90 @@ -642,6 +642,11 @@ end subroutine check_configuration !=========================================================================== subroutine get_required_stencil_depth(stencil_depths, base_mesh_names, config) + use external_forcing_config_mod, only: theta_forcing_nudging, & + wind_forcing_nudging, & + external_forcing_is_loaded + use nudging_config_mod, only: nudging_method_convolution + implicit none integer(kind=i_def), intent(inout) :: stencil_depths(:) @@ -656,8 +661,11 @@ subroutine get_required_stencil_depth(stencil_depths, base_mesh_names, config) ! Configuration variables character(len=str_def) :: prime_mesh_name character(len=str_def) :: aerosol_mesh_name + character(len=str_def) :: nudging_mesh_name logical(kind=l_def) :: use_multires_coupling logical(kind=l_def) :: coarse_aerosol_transport + logical(kind=l_def) :: coarse_nudging + logical(kind=l_def) :: use_spectral_nudging integer(kind=i_def) :: operators integer(kind=i_def) :: fv_horizontal_order integer(kind=i_def) :: panel_edge_treatment @@ -665,6 +673,10 @@ subroutine get_required_stencil_depth(stencil_depths, base_mesh_names, config) integer(kind=i_def) :: dep_pt_stencil_extent integer(kind=i_def) :: ffsl_inner_order integer(kind=i_def) :: ffsl_outer_order + integer(kind=i_def) :: theta_forcing + integer(kind=i_def) :: wind_forcing + integer(kind=i_def) :: nudging_method + integer(kind=i_def) :: spectral_stencil_extent ! ------------------------------------------------------------------------ ! ! Get configuration variables @@ -683,6 +695,27 @@ subroutine get_required_stencil_depth(stencil_depths, base_mesh_names, config) if (use_multires_coupling) then aerosol_mesh_name = config%multires_coupling%aerosol_mesh_name() coarse_aerosol_transport = config%multires_coupling%coarse_aerosol_transport() + nudging_mesh_name = config%multires_coupling%nudging_mesh_name() + coarse_nudging = config%multires_coupling%coarse_nudging() + else + coarse_nudging = .false. + end if + + use_spectral_nudging = .false. + if (external_forcing_is_loaded()) then + theta_forcing = config%external_forcing%theta_forcing() + wind_forcing = config%external_forcing%wind_forcing() + if (theta_forcing == theta_forcing_nudging & + .or. wind_forcing == wind_forcing_nudging) then + nudging_method = config%nudging%nudging_method() + if (nudging_method == nudging_method_convolution) then + spectral_stencil_extent = config%nudging%spectral_stencil_extent() + use_spectral_nudging = .true. + if (.not. coarse_nudging) then + nudging_mesh_name = prime_mesh_name + end if + end if + end if end if ! ------------------------------------------------------------------------ ! @@ -755,6 +788,11 @@ subroutine get_required_stencil_depth(stencil_depths, base_mesh_names, config) ! No transport on this mesh, so set stencil depth to 2 stencil_depths(i) = 2 end if + + if ( use_spectral_nudging & + .and. trim(base_mesh_names(i)) == trim(nudging_mesh_name) ) then + stencil_depths(i) = MAX(stencil_depths(i), spectral_stencil_extent) + end if end do end subroutine get_required_stencil_depth diff --git a/science/gungho/source/driver/create_nudging_fields_mod.f90 b/science/gungho/source/driver/create_nudging_fields_mod.f90 index 8e9f55f7f2..fa30e43155 100644 --- a/science/gungho/source/driver/create_nudging_fields_mod.f90 +++ b/science/gungho/source/driver/create_nudging_fields_mod.f90 @@ -104,7 +104,7 @@ subroutine process_nudging_fields(config, processor) ! remapping from nudging to model grid. Hence this field is needed ! when any one of theta or wind is forced. call processor%apply(make_spec( & - 'surface_pressure_nudging_ext_ref', main%none, W3, & + 'surface_pressure_nudging_ext_ref', main%derived, W3, & coarse=coarse_nudging, & coarse_mesh_name=nudging_mesh_name, & twod=.true., time_axis=axis%nudging & @@ -112,7 +112,7 @@ subroutine process_nudging_fields(config, processor) if ( theta_forcing == theta_forcing_nudging ) then call processor%apply(make_spec( & - 'temperature_nudging_ext_ref', main%none, W3, twod=.true., & + 'temperature_nudging_ext_ref', main%derived, W3, twod=.true., & coarse=coarse_nudging, & coarse_mesh_name=nudging_mesh_name, & mult='nudging_levels', time_axis=axis%nudging & @@ -121,13 +121,13 @@ subroutine process_nudging_fields(config, processor) if ( wind_forcing == wind_forcing_nudging ) then call processor%apply(make_spec( & - 'u_nudging_ext_ref', main%none, W3, twod=.true., & + 'u_nudging_ext_ref', main%derived, W3, twod=.true., & coarse=coarse_nudging, & coarse_mesh_name=nudging_mesh_name, & mult='nudging_levels', time_axis=axis%nudging & )) call processor%apply(make_spec( & - 'v_nudging_ext_ref', main%none, W3, twod=.true., & + 'v_nudging_ext_ref', main%derived, W3, twod=.true., & coarse=coarse_nudging, & coarse_mesh_name=nudging_mesh_name, & mult='nudging_levels', time_axis=axis%nudging & diff --git a/science/gungho/source/driver/gungho_driver_mod.F90 b/science/gungho/source/driver/gungho_driver_mod.F90 index 81c4b31886..993862d999 100644 --- a/science/gungho/source/driver/gungho_driver_mod.F90 +++ b/science/gungho/source/driver/gungho_driver_mod.F90 @@ -362,13 +362,13 @@ subroutine step( modeldb ) type(modeldb_type), intent(inout) :: modeldb - type(mesh_type), pointer :: mesh => null() - type(mesh_type), pointer :: twod_mesh => null() + type(mesh_type), pointer :: mesh + type(mesh_type), pointer :: twod_mesh integer(kind=i_def) :: ts_start, rc integer(tik) :: tid_first, tid_rest #if defined(COUPLED) || defined(UM_PHYSICS) - type( field_collection_type ), pointer :: depository => null() + type( field_collection_type ), pointer :: depository #endif type( field_collection_type ), pointer :: lbc_fields @@ -379,7 +379,7 @@ subroutine step( modeldb ) type( field_collection_type ), pointer :: derived_fields #ifdef UM_PHYSICS - procedure(regridder), pointer :: regrid_operation => null() + procedure(regridder), pointer :: regrid_operation logical(l_def) :: regrid_lowest_order type( field_collection_type ), pointer :: surface_fields @@ -472,6 +472,7 @@ subroutine step( modeldb ) if ( theta_forcing == theta_forcing_nudging & .or. wind_forcing == wind_forcing_nudging) then + derived_fields => modeldb%fields%get_field_collection("derived_fields") call update_variable_fields( & model_axes%nudging_times_list, modeldb%clock, derived_fields & diff --git a/science/gungho/source/kernel/external_forcing/convolution_2d_kernel_mod.F90 b/science/gungho/source/kernel/external_forcing/convolution_2d_kernel_mod.F90 new file mode 100644 index 0000000000..6451803999 --- /dev/null +++ b/science/gungho/source/kernel/external_forcing/convolution_2d_kernel_mod.F90 @@ -0,0 +1,169 @@ +!----------------------------------------------------------------------------- +! (C) Crown copyright Met Office. All rights reserved. +! The file LICENCE, distributed with this code, contains details of the terms +! under which the code may be used. +!----------------------------------------------------------------------------- +!> @brief Computes weights for convolution for spectral nudging. +!> @details This kernel computes weights for performing a convolution that is +!! used for nudging the spectrum of a field towards the spectrum of +!! a reference field. +!! Only implemented for the lowest-order elements +module convolution_2d_kernel_mod + + use argument_mod, only: arg_type, & + GH_FIELD, GH_SCALAR_ARRAY, GH_SCALAR, & + GH_REAL, GH_INTEGER, GH_READ, GH_WRITE, & + CELL_COLUMN, STENCIL, REGION, & + ANY_DISCONTINUOUS_SPACE_1, & + ANY_DISCONTINUOUS_SPACE_2, & + ANY_DISCONTINUOUS_SPACE_9 + + use constants_mod, only: r_def, i_def + use kernel_mod, only: kernel_type + + implicit none + + private + + !----------------------------------------------------------------------------- + ! Public types + !----------------------------------------------------------------------------- + !> The type declaration for the kernel. + !> Contains the metadata needed by the Psy layer + type, public, extends(kernel_type) :: convolution_2d_kernel_type + private + type(arg_type) :: meta_args(8) = (/ & + arg_type(GH_FIELD, GH_REAL, GH_WRITE, ANY_DISCONTINUOUS_SPACE_1), & + arg_type(GH_FIELD, GH_REAL, GH_READ, ANY_DISCONTINUOUS_SPACE_1, & + STENCIL(REGION)), & + arg_type(GH_SCALAR_ARRAY, & + GH_REAL, GH_READ, 1), & + arg_type(GH_FIELD, GH_REAL, GH_READ, ANY_DISCONTINUOUS_SPACE_9), & + arg_type(GH_FIELD, GH_INTEGER, GH_READ, ANY_DISCONTINUOUS_SPACE_2), & + arg_type(GH_SCALAR, GH_INTEGER, GH_READ), & + arg_type(GH_SCALAR, GH_INTEGER, GH_READ), & + arg_type(GH_SCALAR, GH_REAL, GH_READ) & + /) + integer :: operates_on = CELL_COLUMN + contains + procedure, nopass :: convolution_2d_code + end type + + !----------------------------------------------------------------------------- + ! Contained functions/subroutines + !----------------------------------------------------------------------------- + public :: convolution_2d_code + +contains + +!> @brief Performs convolution for spectral nudging. +!> @param[in] nlayers Number of layers +!> @param[in,out] filtered_field The filtered field to compute +!> @param[in] field_in Field to be filtered +!> @param[in] stencil_size Size of the stencil used in the convolution +!> @param[in] stencil_map Map of the stencil used in the convolution +!> @param[in] nvert Number of vertical weights +!> @param[in] vert_weights 1D array of vertical weights for nudging +!> @param[in] conv_weights 2D weights for performing the convolution +!> @param[in] tropopause_level Tropopause level for each column +!> @param[in] min_level Minimum vertical level for nudging +!> @param[in] max_level Maximum vertical level for nudging +!> @param[in] time_weight Time weight for nudging +!> @param[in] ndf_3d Number of DoFs per cell in 3D field +!> @param[in] undf_3d Number of DoFs in this partition in 3D field +!> @param[in] map_3d DoF map for the 3D field +!> @param[in] ndf_2d_cw Num of DoFs per cell in weights field +!> @param[in] undf_2d_cw Num DoFs in this partition in weights field +!> @param[in] map_2d_cw DoF map for the weights field +!> @param[in] ndf_2d Num of DoFs per cell in 2D trop field +!> @param[in] undf_2d Num DoFs in this partition in 2D trop field +!> @param[in] map_2d DoF map for the 2D trop field +subroutine convolution_2d_code(nlayers, & + filtered_field, & + field_in, & + stencil_size, & + stencil_map, & + nvert, & + vert_weights, & + conv_weights, & + tropopause_level, & + min_level, & + max_level, & + time_weight, & + ndf_3d, & + undf_3d, & + map_3d, & + ndf_2d_cw, & + undf_2d_cw, & + map_2d_cw, & + ndf_2d, & + undf_2d, & + map_2d) + + implicit none + + ! Arguments + integer(kind=i_def), intent(in) :: nlayers + integer(kind=i_def), intent(in) :: ndf_2d, ndf_2d_cw, ndf_3d + integer(kind=i_def), intent(in) :: undf_2d, undf_2d_cw, undf_3d + integer(kind=i_def), intent(in) :: nvert(1) + integer(kind=i_def), intent(in) :: stencil_size + integer(kind=i_def), intent(in) :: min_level, max_level + integer(kind=i_def), intent(in) :: map_2d(ndf_2d) + integer(kind=i_def), intent(in) :: map_2d_cw(ndf_2d_cw) + integer(kind=i_def), intent(in) :: map_3d(ndf_3d) + integer(kind=i_def), intent(in) :: stencil_map(ndf_3d, stencil_size) + real(kind=r_def), intent(in) :: vert_weights(nvert(1)) + real(kind=r_def), intent(in) :: time_weight + real(kind=r_def), intent(in) :: conv_weights(undf_2d_cw) + integer(kind=i_def), intent(in) :: tropopause_level(undf_2d) + real(kind=r_def), intent(in) :: field_in(undf_3d) + real(kind=r_def), intent(inout) :: filtered_field(undf_3d) + + ! Local variables + integer(kind=i_def) :: i, nl + integer(kind=i_def) :: idx_w, idx_sb, idx_st, idx_fb, idx_ft, idx_vb, idx_vt + integer(kind=i_def) :: min_level_loc, max_level_loc + + nl = nlayers - 2 + ndf_3d ! nlayers for Wtheta, nlayers - 1 for W3 + + ! Wtheta: Level 0 corresponds to the surface + ! At min_level, Wtheta weights will be 0 unless taper_bottom == level_bottom + ! At max_level, Wtheta weights will be 0 unless taper_top == level_top + ! The tropopause level is included in the calculation if it is below max_level + ! W3: (k_wt < k_w3 < k_wt + 1) + ! Below min_level, W3 weights are set to 0, so we can ignore these levels + ! Above max_level, W3 weights are set to 0, so we can ignore these levels + ! Tropopause level is at a Wtheta level, so include W3 levels below this + + ! The following indices take the above into account: + min_level_loc = MAX(min_level, 0) + ! max_level, not above tropopause. Subtract 1 for W3 + max_level_loc = & + MAX(MIN(max_level, tropopause_level(map_2d(1)) - 2 + ndf_3d, nl), 0) + + idx_fb = map_3d(1) + min_level_loc + idx_ft = map_3d(1) + max_level_loc + + ! Index of vertical weights. These start from 1 (whether W3 or Wtheta) + idx_vb = 1 + min_level_loc + idx_vt = 1 + max_level_loc + + do i = 1, stencil_size + idx_w = map_2d_cw(1) + i - 1 ! Multidata index + idx_sb = stencil_map(1, i) + min_level_loc ! Stencil index bottom + idx_st = stencil_map(1, i) + max_level_loc ! Stencil index top + + filtered_field(idx_fb:idx_ft) = ( & + filtered_field(idx_fb:idx_ft) & + + field_in(idx_sb:idx_st) * conv_weights(idx_w) & + ) + end do + ! Apply vertical weights + filtered_field(idx_fb:idx_ft) = ( & + filtered_field(idx_fb:idx_ft) * time_weight * vert_weights(idx_vb:idx_vt) & + ) + +end subroutine convolution_2d_code + +end module convolution_2d_kernel_mod diff --git a/science/gungho/source/kernel/external_forcing/convolution_2d_weights_kernel_mod.F90 b/science/gungho/source/kernel/external_forcing/convolution_2d_weights_kernel_mod.F90 new file mode 100644 index 0000000000..8fc38f6d48 --- /dev/null +++ b/science/gungho/source/kernel/external_forcing/convolution_2d_weights_kernel_mod.F90 @@ -0,0 +1,267 @@ +!----------------------------------------------------------------------------- +! (C) Crown copyright Met Office. All rights reserved. +! The file LICENCE, distributed with this code, contains details of the terms +! under which the code may be used. +!----------------------------------------------------------------------------- +!> @brief Computes weights for convolution for spectral nudging. +!> @details This kernel computes weights for performing a convolution that is +!! used for nudging the spectrum of a field towards the spectrum of +!! a reference field. +!! The filter takes the form of a sum of Legendre polynomials which +!! help select certain wavenumbers to be nudged. The filter is +!! enveloped by a Gaussian function to ensure that the weights go +!! smoothly to zero. +!! Only implemented for the lowest-order elements +module convolution_2d_weights_kernel_mod + + use argument_mod, only: arg_type, func_type, & + GH_FIELD, GH_SCALAR, & + GH_REAL, GH_INTEGER, & + GH_READ, GH_WRITE, GH_BASIS, & + CELL_COLUMN, STENCIL, REGION, & + GH_EVALUATOR, & + ANY_DISCONTINUOUS_SPACE_3, & + ANY_DISCONTINUOUS_SPACE_7, & + ANY_DISCONTINUOUS_SPACE_9 + use constants_mod, only: r_def, i_def, PI, EPS + use coord_transform_mod, only: central_angle + use kernel_mod, only: kernel_type + use sci_chi_transform_mod, only: chi2llr + + + implicit none + + private + + !----------------------------------------------------------------------------- + ! Public types + !----------------------------------------------------------------------------- + !> The type declaration for the kernel. + !> Contains the metadata needed by the Psy layer + type, public, extends(kernel_type) :: convolution_2d_weights_kernel_type + private + type(arg_type) :: meta_args(10) = (/ & + arg_type(GH_FIELD, GH_REAL, GH_WRITE, ANY_DISCONTINUOUS_SPACE_9), & + arg_type(GH_FIELD*3, GH_REAL, GH_READ, ANY_DISCONTINUOUS_SPACE_7, & + STENCIL(REGION)), & + arg_type(GH_FIELD, GH_REAL, GH_READ, ANY_DISCONTINUOUS_SPACE_3, & + STENCIL(REGION)), & + arg_type(GH_SCALAR, GH_INTEGER, GH_READ), & + arg_type(GH_SCALAR, GH_INTEGER, GH_READ), & + arg_type(GH_SCALAR, GH_REAL, GH_READ), & + arg_type(GH_SCALAR, GH_INTEGER, GH_READ), & + arg_type(GH_SCALAR, GH_INTEGER, GH_READ), & + arg_type(GH_SCALAR, GH_INTEGER, GH_READ), & + arg_type(GH_SCALAR, GH_REAL, GH_READ) & + /) + type(func_type) :: meta_funcs(1) = (/ & + func_type(ANY_DISCONTINUOUS_SPACE_7, GH_BASIS) & + /) + integer :: operates_on = CELL_COLUMN + integer :: gh_shape = GH_EVALUATOR + contains + procedure, nopass :: convolution_2d_weights_code + end type + + !----------------------------------------------------------------------------- + ! Contained functions/subroutines + !----------------------------------------------------------------------------- + public :: convolution_2d_weights_code + +contains + +!> @brief Computes weights for performing convolution for spectral nudging. +!> @param[in] nlayers Number of layers +!> @param[in,out] weights Weights to be computed: a multidata 2D field +!> @param[in] chi1 First coordinate field +!> @param[in] chi2 Second coordinate field +!> @param[in] chi3 Third coordinate field +!> @param[in] stencil_size_chi Size of the stencil for the chi fields +!> @param[in] stencil_map_chi Stencil map for the coordinate fields +!> @param[in] panel_id Panel ID field +!> @param[in] stencil_size_pid Size of the stencil for the panel ID +!> @param[in] stencil_map_pid Stencil map for the panel ID field +!> @param[in] kmax Maximum wavenumber in filter +!> @param[in] kmin Minimum wavenumber in filter +!> @param[in] sigma Width of the Gaussian envelope to filter +!> @param[in] geometry Geometry of the mesh +!> @param[in] topology Topology of the mesh +!> @param[in] coord_system System used by coordinate fields +!> @param[in] scaled_radius Scaled radius of the planet +!> @param[in] ndf_2d Num DoFs per cell for weights field +!> @param[in] undf_2d Num DoFs in this partition for weights field +!> @param[in] map_2d DoFmap for weights field +!> @param[in] ndf_chi Num DoFs per cell for coordinate fields +!> @param[in] undf_chi Num DoFs in this partition for chi fields +!> @param[in] map_chi DoFmap for coordinate fields +!> @param[in] basis_chi Basis funcs for chi fields evaluated at W3 +!> @param[in] ndf_pid Num DoFs per cell for panel ID +!> @param[in] undf_pid Num DoFs in this partition for panel ID +!> @param[in] map_pid DoFmap for panel ID field +subroutine convolution_2d_weights_code(nlayers, & + weights, & + chi1, & + chi2, & + chi3, & + stencil_size_chi, & + stencil_map_chi, & + panel_id, & + stencil_size_pid, & + stencil_map_pid, & + kmax, & + kmin, & + sigma, & + geometry, & + topology, & + coord_system, & + scaled_radius, & + ndf_2d, & + undf_2d, & + map_2d, & + ndf_chi, & + undf_chi, & + map_chi, & + basis_chi, & + ndf_pid, & + undf_pid, & + map_pid) + + implicit none + + ! Arguments + integer(kind=i_def), intent(in) :: nlayers + integer(kind=i_def), intent(in) :: ndf_2d, ndf_chi, ndf_pid + integer(kind=i_def), intent(in) :: undf_2d, undf_chi, undf_pid + integer(kind=i_def), intent(in) :: stencil_size_chi, stencil_size_pid + integer(kind=i_def), intent(in) :: map_2d(ndf_2d) + integer(kind=i_def), intent(in) :: map_chi(ndf_chi) + integer(kind=i_def), intent(in) :: map_pid(ndf_pid) + integer(kind=i_def), intent(in) :: stencil_map_chi(ndf_chi, stencil_size_chi) + integer(kind=i_def), intent(in) :: stencil_map_pid(ndf_pid, stencil_size_pid) + real(kind=r_def), intent(in) :: basis_chi(1, ndf_chi, ndf_2d) + integer(kind=i_def), intent(in) :: kmax, kmin + real(kind=r_def), intent(in) :: sigma + integer(kind=i_def), intent(in) :: geometry, topology, coord_system + real(kind=r_def), intent(in) :: scaled_radius + real(kind=r_def), intent(in) :: panel_id(undf_pid) + real(kind=r_def), intent(in) :: chi1(undf_chi) + real(kind=r_def), intent(in) :: chi2(undf_chi) + real(kind=r_def), intent(in) :: chi3(undf_chi) + real(kind=r_def), intent(inout) :: weights(undf_2d) + + ! Internal variables + integer(kind=i_def) :: i, l, df, idx_w + integer(kind=i_def) :: panel_c, panel_i + real(kind=r_def) :: chi1_c, chi2_c, chi3_c + real(kind=r_def) :: chi1_i, chi2_i, chi3_i + real(kind=r_def) :: lon_c, lat_c, lon_i, lat_i + real(kind=r_def) :: radius + real(kind=r_def) :: dist, cos_dist, sum_weights + real(kind=r_def) :: P_0, P_1, P_l, P_lm1, P_lm2, l_real + + ! Convolution is a 2D operation, so we can just act in the lowest layer + ! Get coordinates for the centre of this column + chi1_c = 0.0_r_def + chi2_c = 0.0_r_def + chi3_c = 0.0_r_def + do df = 1, ndf_chi + chi1_c = chi1_c + basis_chi(1, df, 1) * chi1(map_chi(df)) + chi2_c = chi2_c + basis_chi(1, df, 1) * chi2(map_chi(df)) + chi3_c = chi3_c + basis_chi(1, df, 1) * chi3(map_chi(df)) + end do + + ! Convert to longitude and latitude + panel_c = INT(panel_id(map_pid(1)), i_def) + call chi2llr( & + chi1_c, chi2_c, chi3_c, panel_c, & + geometry, topology, coord_system, scaled_radius, & + lon_c, lat_c, radius & + ) + + ! Loop through cells in the stencil + do i = 1, stencil_size_chi + ! Get coordinates for this cell + chi1_i = 0.0_r_def + chi2_i = 0.0_r_def + chi3_i = 0.0_r_def + do df = 1, ndf_chi + chi1_i = chi1_i + basis_chi(1, df, 1) * chi1(stencil_map_chi(df, i)) + chi2_i = chi2_i + basis_chi(1, df, 1) * chi2(stencil_map_chi(df, i)) + chi3_i = chi3_i + basis_chi(1, df, 1) * chi3(stencil_map_chi(df, i)) + end do + + panel_i = INT(panel_id(stencil_map_pid(1, i)), i_def) + call chi2llr( & + chi1_i, chi2_i, chi3_i, panel_i, & + geometry, topology, coord_system, scaled_radius, & + lon_i, lat_i, radius & + ) + + ! Use central angle to get distance on sphere + if (ABS(lon_c - lon_i) < 10.0_r_def*EPSILON(1.0_r_def) & + .and. ABS(lat_c - lat_i) < 10.0_r_def*EPSILON(1.0_r_def)) then + dist = 0.0_r_def + else + ! Only call this for points that are not identical, which gets around + ! potential issues in the central angle routine + call central_angle(lon_c, lat_c, lon_i, lat_i, dist) + end if + + ! Convert distance to cos(dist) for use in spherical harmonics + cos_dist = COS(dist) + + ! Set weight for this cell in the stencil + idx_w = map_2d(1) + i - 1 ! Multidata index + + ! Convolving function: enveloped Legendre polynomial ----------------------- + weights(idx_w) = 0.0_r_def + + ! First Legendre polynomials + P_0 = 1.0_r_def + P_1 = cos_dist + + if (kmin == 0) then + weights(idx_w) = weights(idx_w) + 1.0_r_def/(4.0_r_def*PI) * P_0 + end if + if (kmin <= 1 .and. kmax >= 1) then + weights(idx_w) = weights(idx_w) + 3.0_r_def/(4.0_r_def*PI) * P_1 + end if + + ! Obtain further Legendre polynomials using recurrence relation + P_lm2 = P_0 + P_lm1 = P_1 + if (kmax >= 2) then + do l = 2, kmax + l_real = REAL(l, r_def) + ! Determine next Legendre polynomial P_l + P_l = ( & + (2.0_r_def*l_real - 1.0_r_def) * cos_dist * P_lm1 & + - (l_real - 1.0_r_def) * P_lm2 & + ) / l_real + ! Update convolution weight based on Legendre polynomial: + ! (2l+1)/(4pi)) P_l + weights(idx_w) = weights(idx_w) + ( & + (2.0_r_def*l_real + 1.0_r_def ) / (4.0_r_def*PI) * P_l & + ) + ! Update previous two polynomials + P_lm2 = P_lm1 + P_lm1 = P_l + end do + end if + + ! Apply envelope to weights, to ensure they go smoothly to zero at the + ! edge of the stencil, which can help avoid amplifying some wavenumbers + weights(idx_w) = weights(idx_w) * EXP(-0.5_r_def * (dist / sigma)**2) + end do + + ! Normalise the weights + idx_w = map_2d(1) + sum_weights = SUM(weights(idx_w : idx_w+stencil_size_chi-1)) + if (ABS(sum_weights) > 0.0_r_def) then + weights(idx_w : idx_w+stencil_size_chi-1) = & + weights(idx_w : idx_w+stencil_size_chi-1) / sum_weights + end if + +end subroutine convolution_2d_weights_code + +end module convolution_2d_weights_kernel_mod diff --git a/science/gungho/source/kernel/external_forcing/ecmwf_level_support_mod.F90 b/science/gungho/source/kernel/external_forcing/ecmwf_level_support_mod.F90 new file mode 100644 index 0000000000..699fe7010a --- /dev/null +++ b/science/gungho/source/kernel/external_forcing/ecmwf_level_support_mod.F90 @@ -0,0 +1,163 @@ +!------------------------------------------------------------------------------- +! (c) Crown copyright Met Office. All rights reserved. +! The file LICENCE, distributed with this code, contains details of the terms +! under which the code may be used. +!------------------------------------------------------------------------------- +!> @brief Contains coefficients used for vertically-interpolating from ECMWF +!! ERA or AIFS levels +module ecmwf_level_support_mod + + use constants_mod, only: r_def + + implicit none + + ! Height coefficients for ECMWF 137 level model + ! *************************************************************** + ! Values corresponding to ECMWF ERA-5 based 137-level dataset + ! ************************************************************** + + ! A(k) parameters for ECMWF half levels + real(kind=r_def), parameter :: ak_ec_137(0:137) = [ 0.000000_r_def, & + 2.000365_r_def, 3.102241_r_def, 4.666084_r_def, 6.827977_r_def, 9.746966_r_def, & + 13.605424_r_def, 18.608931_r_def, 24.985718_r_def, 32.985710_r_def, 42.879242_r_def, & + 54.955463_r_def, 69.520576_r_def, 86.895882_r_def, 107.415741_r_def, 131.425507_r_def, & + ! 16 + 159.279404_r_def, 191.338562_r_def, 227.968948_r_def, 269.539581_r_def, 316.420746_r_def, & + 368.982361_r_def, 427.592499_r_def, 492.616028_r_def, 564.413452_r_def, 643.339905_r_def, & + 729.744141_r_def, 823.967834_r_def, 926.344910_r_def, 1037.201172_r_def, 1156.853638_r_def, & + ! 31 + 1285.610352_r_def, 1423.770142_r_def, 1571.622925_r_def, 1729.448975_r_def, 1897.519287_r_def, & + 2076.095947_r_def, 2265.431641_r_def, 2465.770508_r_def, 2677.348145_r_def, 2900.391357_r_def, & + 3135.119385_r_def, 3381.743652_r_def, 3640.468262_r_def, 3911.490479_r_def, 4194.930664_r_def, & + ! 46 + 4490.817383_r_def, 4799.149414_r_def, 5119.895020_r_def, 5452.990723_r_def, 5798.344727_r_def, & + 6156.074219_r_def, 6526.946777_r_def, 6911.870605_r_def, 7311.869141_r_def, 7727.412109_r_def, & + 8159.354004_r_def, 8608.525391_r_def, 9076.400391_r_def, 9562.682617_r_def, 10065.978516_r_def, & + ! 61 + 10584.631836_r_def, 11116.662109_r_def, 11660.067383_r_def, 12211.547852_r_def, 12766.873047_r_def, & + 13324.668945_r_def, 13881.331055_r_def, 14432.139648_r_def, 14975.615234_r_def, 15508.256836_r_def, & + 16026.115234_r_def, 16527.322266_r_def, 17008.789063_r_def, 17467.613281_r_def, 17901.621094_r_def, & + ! 76 + 18308.433594_r_def, 18685.718750_r_def, 19031.289063_r_def, 19343.511719_r_def, 19620.042969_r_def, & + 19859.390625_r_def, 20059.931641_r_def, 20219.664063_r_def, 20337.863281_r_def, 20412.308594_r_def, & + 20442.078125_r_def, 20425.718750_r_def, 20361.816406_r_def, 20249.511719_r_def, 20087.085938_r_def, & + ! 91 + 19874.025391_r_def, 19608.572266_r_def, 19290.226563_r_def, 18917.460938_r_def, 18489.707031_r_def, & + 18006.925781_r_def, 17471.839844_r_def, 16888.687500_r_def, 16262.046875_r_def, 15596.695313_r_def, & + 14898.453125_r_def, 14173.324219_r_def, 13427.769531_r_def, 12668.257813_r_def, 11901.339844_r_def, & + ! 106 + 11133.304688_r_def, 10370.175781_r_def, 9617.515625_r_def, 8880.453125_r_def, 8163.375000_r_def, & + 7470.343750_r_def, 6804.421875_r_def, 6168.531250_r_def, 5564.382813_r_def, 4993.796875_r_def, & + 4457.375000_r_def, 3955.960938_r_def, 3489.234375_r_def, 3057.265625_r_def, 2659.140625_r_def, & + ! 121 + 2294.242188_r_def, 1961.500000_r_def, 1659.476563_r_def, 1387.546875_r_def, 1143.250000_r_def, & + 926.507813_r_def, 734.992188_r_def, 568.062500_r_def, 424.414063_r_def, 302.476563_r_def, & + 202.484375_r_def, 122.101563_r_def, 62.781250_r_def, 22.835938_r_def, 3.757813_r_def, & + ! 136 + 0.000000_r_def, 0.000000_r_def ] + + ! B(k) parameters for ECMWF half levels + real(kind=r_def), parameter :: bk_ec_137(0:137) = [ & + 0.000000_r_def, & + 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, & + 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, & + 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, & + ! 16 + 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, & + 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, & + 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, & + ! 31 + 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, & + 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, & + 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, & + ! 46 + 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, & + 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, 0.000007_r_def, & + 0.000024_r_def, 0.000059_r_def, 0.000112_r_def, 0.000199_r_def, 0.000340_r_def, & + ! 61 + 0.000562_r_def, 0.000890_r_def, 0.001353_r_def, 0.001992_r_def, 0.002857_r_def, & + 0.003971_r_def, 0.005378_r_def, 0.007133_r_def, 0.009261_r_def, 0.011806_r_def, & + 0.014816_r_def, 0.018318_r_def, 0.022355_r_def, 0.026964_r_def, 0.032176_r_def, & + ! 76 + 0.038026_r_def, 0.044548_r_def, 0.051773_r_def, 0.059728_r_def, 0.068448_r_def, & + 0.077958_r_def, 0.088286_r_def, 0.099462_r_def, 0.111505_r_def, 0.124448_r_def, & + 0.138313_r_def, 0.153125_r_def, 0.168910_r_def, 0.185689_r_def, 0.203491_r_def, & + ! 91 + 0.222333_r_def, 0.242244_r_def, 0.263242_r_def, 0.285354_r_def, 0.308598_r_def, & + 0.332939_r_def, 0.358254_r_def, 0.384363_r_def, 0.411125_r_def, 0.438391_r_def, & + 0.466003_r_def, 0.493800_r_def, 0.521619_r_def, 0.549301_r_def, 0.576692_r_def, & + ! 106 + 0.603648_r_def, 0.630036_r_def, 0.655736_r_def, 0.680643_r_def, 0.704669_r_def, & + 0.727739_r_def, 0.749797_r_def, 0.770798_r_def, 0.790717_r_def, 0.809536_r_def, & + 0.827256_r_def, 0.843881_r_def, 0.859432_r_def, 0.873929_r_def, 0.887408_r_def, & + ! 121 + 0.899900_r_def, 0.911448_r_def, 0.922096_r_def, 0.931881_r_def, 0.940860_r_def, & + 0.949064_r_def, 0.956550_r_def, 0.963352_r_def, 0.969513_r_def, 0.975078_r_def, & + 0.980072_r_def, 0.984542_r_def, 0.988500_r_def, 0.991984_r_def, 0.995003_r_def, & + ! 136 + 0.997630_r_def, 1.000000_r_def ] + + ! Height coefficients for ECMWF AIFS 88 level data + ! *************************************************************** + ! Values corresponding to ECMWF ERA-5 based 137-level dataset + ! From level 137 to 50, i.e. the lowest ~20km + ! ************************************************************** + ! A(k) parameters for ECMWF half levels + real(kind=r_def), parameter :: ak_ec_137_50(0:88) = [ & + ! 49 + 5452.990723_r_def, 5798.344727_r_def, & + 6156.074219_r_def, 6526.946777_r_def, 6911.870605_r_def, 7311.869141_r_def, 7727.412109_r_def, & + 8159.354004_r_def, 8608.525391_r_def, 9076.400391_r_def, 9562.682617_r_def, 10065.978516_r_def, & + ! 61 + 10584.631836_r_def, 11116.662109_r_def, 11660.067383_r_def, 12211.547852_r_def, 12766.873047_r_def, & + 13324.668945_r_def, 13881.331055_r_def, 14432.139648_r_def, 14975.615234_r_def, 15508.256836_r_def, & + 16026.115234_r_def, 16527.322266_r_def, 17008.789063_r_def, 17467.613281_r_def, 17901.621094_r_def, & + ! 76 + 18308.433594_r_def, 18685.718750_r_def, 19031.289063_r_def, 19343.511719_r_def, 19620.042969_r_def, & + 19859.390625_r_def, 20059.931641_r_def, 20219.664063_r_def, 20337.863281_r_def, 20412.308594_r_def, & + 20442.078125_r_def, 20425.718750_r_def, 20361.816406_r_def, 20249.511719_r_def, 20087.085938_r_def, & + ! 91 + 19874.025391_r_def, 19608.572266_r_def, 19290.226563_r_def, 18917.460938_r_def, 18489.707031_r_def, & + 18006.925781_r_def, 17471.839844_r_def, 16888.687500_r_def, 16262.046875_r_def, 15596.695313_r_def, & + 14898.453125_r_def, 14173.324219_r_def, 13427.769531_r_def, 12668.257813_r_def, 11901.339844_r_def, & + ! 106 + 11133.304688_r_def, 10370.175781_r_def, 9617.515625_r_def, 8880.453125_r_def, 8163.375000_r_def, & + 7470.343750_r_def, 6804.421875_r_def, 6168.531250_r_def, 5564.382813_r_def, 4993.796875_r_def, & + 4457.375000_r_def, 3955.960938_r_def, 3489.234375_r_def, 3057.265625_r_def, 2659.140625_r_def, & + ! 121 + 2294.242188_r_def, 1961.500000_r_def, 1659.476563_r_def, 1387.546875_r_def, 1143.250000_r_def, & + 926.507813_r_def, 734.992188_r_def, 568.062500_r_def, 424.414063_r_def, 302.476563_r_def, & + 202.484375_r_def, 122.101563_r_def, 62.781250_r_def, 22.835938_r_def, 3.757813_r_def, & + ! 136 + 0.000000_r_def, 0.000000_r_def ] + + ! B(k) parameters for ECMWF half levels + real(kind=r_def), parameter :: bk_ec_137_50(0:88) = [ & + ! 46 + 0.000000_r_def, 0.000000_r_def, & + 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, 0.000000_r_def, 0.000007_r_def, & + 0.000024_r_def, 0.000059_r_def, 0.000112_r_def, 0.000199_r_def, 0.000340_r_def, & + ! 61 + 0.000562_r_def, 0.000890_r_def, 0.001353_r_def, 0.001992_r_def, 0.002857_r_def, & + 0.003971_r_def, 0.005378_r_def, 0.007133_r_def, 0.009261_r_def, 0.011806_r_def, & + 0.014816_r_def, 0.018318_r_def, 0.022355_r_def, 0.026964_r_def, 0.032176_r_def, & + ! 76 + 0.038026_r_def, 0.044548_r_def, 0.051773_r_def, 0.059728_r_def, 0.068448_r_def, & + 0.077958_r_def, 0.088286_r_def, 0.099462_r_def, 0.111505_r_def, 0.124448_r_def, & + 0.138313_r_def, 0.153125_r_def, 0.168910_r_def, 0.185689_r_def, 0.203491_r_def, & + ! 91 + 0.222333_r_def, 0.242244_r_def, 0.263242_r_def, 0.285354_r_def, 0.308598_r_def, & + 0.332939_r_def, 0.358254_r_def, 0.384363_r_def, 0.411125_r_def, 0.438391_r_def, & + 0.466003_r_def, 0.493800_r_def, 0.521619_r_def, 0.549301_r_def, 0.576692_r_def, & + ! 106 + 0.603648_r_def, 0.630036_r_def, 0.655736_r_def, 0.680643_r_def, 0.704669_r_def, & + 0.727739_r_def, 0.749797_r_def, 0.770798_r_def, 0.790717_r_def, 0.809536_r_def, & + 0.827256_r_def, 0.843881_r_def, 0.859432_r_def, 0.873929_r_def, 0.887408_r_def, & + ! 121 + 0.899900_r_def, 0.911448_r_def, 0.922096_r_def, 0.931881_r_def, 0.940860_r_def, & + 0.949064_r_def, 0.956550_r_def, 0.963352_r_def, 0.969513_r_def, 0.975078_r_def, & + 0.980072_r_def, 0.984542_r_def, 0.988500_r_def, 0.991984_r_def, 0.995003_r_def, & + ! 136 + 0.997630_r_def, 1.000000_r_def ] + +end module ecmwf_level_support_mod diff --git a/science/gungho/source/kernel/external_forcing/newtonian_nudging_kernel_mod.F90 b/science/gungho/source/kernel/external_forcing/newtonian_nudging_kernel_mod.F90 new file mode 100644 index 0000000000..2f53a5d603 --- /dev/null +++ b/science/gungho/source/kernel/external_forcing/newtonian_nudging_kernel_mod.F90 @@ -0,0 +1,143 @@ +!----------------------------------------------------------------------------- +! (C) Crown copyright Met Office. All rights reserved. +! The file LICENCE, distributed with this code, contains details of the terms +! under which the code may be used. +!----------------------------------------------------------------------------- +!> @brief Performs Newtonian nudging. +!> @details This kernel computes increment for performing Newtonian nudging of +!! a field towards a reference field. +!! Only implemented for the lowest-order elements +module newtonian_nudging_kernel_mod + + use argument_mod, only: arg_type, & + GH_FIELD, GH_SCALAR, GH_SCALAR_ARRAY, & + GH_REAL, GH_INTEGER, & + GH_READ, GH_WRITE, & + ANY_DISCONTINUOUS_SPACE_1, & + ANY_DISCONTINUOUS_SPACE_2, & + CELL_COLUMN + use constants_mod, only: r_def, i_def + use kernel_mod, only: kernel_type + + implicit none + + private + + !----------------------------------------------------------------------------- + ! Public types + !----------------------------------------------------------------------------- + !> The type declaration for the kernel. + !> Contains the metadata needed by the Psy layer + type, public, extends(kernel_type) :: newtonian_nudging_kernel_type + private + type(arg_type) :: meta_args(8) = (/ & + arg_type(GH_FIELD, GH_REAL, GH_WRITE, ANY_DISCONTINUOUS_SPACE_1), & + arg_type(GH_FIELD, GH_REAL, GH_READ, ANY_DISCONTINUOUS_SPACE_1), & + arg_type(GH_FIELD, GH_REAL, GH_READ, ANY_DISCONTINUOUS_SPACE_1), & + arg_type(GH_SCALAR_ARRAY, & + GH_REAL, GH_READ, 1), & + arg_type(GH_FIELD, GH_INTEGER, GH_READ, ANY_DISCONTINUOUS_SPACE_2), & + arg_type(GH_SCALAR, GH_INTEGER, GH_READ), & + arg_type(GH_SCALAR, GH_INTEGER, GH_READ), & + arg_type(GH_SCALAR, GH_REAL, GH_READ) & + /) + integer :: operates_on = CELL_COLUMN + contains + procedure, nopass :: newtonian_nudging_code + end type + + !----------------------------------------------------------------------------- + ! Contained functions/subroutines + !----------------------------------------------------------------------------- + public :: newtonian_nudging_code + +contains + +!> @brief Performs Newtonian nudging +!> @param[in] nlayers Number of layers +!> @param[in,out] increment The increment to compute +!> @param[in] field_in Field to be nudged +!> @param[in] field_ref Reference field to nudge towards +!> @param[in] nvert Number of vertical levels +!> @param[in] vert_weights Vertical weights for nudging +!> @param[in] tropopause_level Tropopause level for each column +!> @param[in] min_level Minimum vertical level for nudging +!> @param[in] max_level Maximum vertical level for nudging +!> @param[in] time_weight Time weight for nudging +!> @param[in] ndf_3d Num of DoFs per cell in 3D field +!> @param[in] undf_3d Num of DoFs in this partition in 3D field +!> @param[in] map_3d DoF map for the 3D field +!> @param[in] ndf_2d Num of DoFs per cell in 2D field +!> @param[in] undf_2d Num of DoFs in this partition in 2D field +!> @param[in] map_2d DoF map for the 2D field +subroutine newtonian_nudging_code(nlayers, & + increment, & + field_in, & + field_ref, & + nvert, & + vert_weights, & + tropopause_level, & + min_level, & + max_level, & + time_weight, & + ndf_3d, & + undf_3d, & + map_3d, & + ndf_2d, & + undf_2d, & + map_2d) + + implicit none + + ! Arguments + integer(kind=i_def), intent(in) :: nlayers + integer(kind=i_def), intent(in) :: ndf_2d, ndf_3d + integer(kind=i_def), intent(in) :: undf_2d, undf_3d + integer(kind=i_def), intent(in) :: nvert(1) + integer(kind=i_def), intent(in) :: min_level, max_level + integer(kind=i_def), intent(in) :: map_2d(ndf_2d) + integer(kind=i_def), intent(in) :: map_3d(ndf_3d) + real(kind=r_def), intent(in) :: vert_weights(nvert(1)) + real(kind=r_def), intent(in) :: time_weight + integer(kind=i_def), intent(in) :: tropopause_level(undf_2d) + real(kind=r_def), intent(in) :: field_ref(undf_3d) + real(kind=r_def), intent(in) :: field_in(undf_3d) + real(kind=r_def), intent(inout) :: increment(undf_3d) + + ! Local variables + integer(kind=i_def) :: nl + integer(kind=i_def) :: idx_fb, idx_ft, idx_vb, idx_vt + integer(kind=i_def) :: min_level_loc, max_level_loc + + nl = nlayers - 2 + ndf_3d ! nlayers for Wtheta, nlayers - 1 for W3 + + ! Wtheta: Level 0 corresponds to the surface + ! At min_level, Wtheta weights will be 0 unless taper_bottom == level_bottom + ! At max_level, Wtheta weights will be 0 unless taper_top == level_top + ! The tropopause level is included in the calculation if it is below max_level + ! W3: (k_wt < k_w3 < k_wt + 1) + ! Below min_level, W3 weights are set to 0, so we can ignore these levels + ! Above max_level, W3 weights are set to 0, so we can ignore these levels + ! Tropopause level is at a Wtheta level, so include W3 levels below this + + ! The following indices take the above into account: + min_level_loc = MAX(min_level, 0) + ! max_level, not above tropopause. Subtract 1 for W3 + max_level_loc = & + MAX(MIN(max_level, tropopause_level(map_2d(1)) - 2 + ndf_3d, nl), 0) + + idx_fb = map_3d(1) + min_level_loc + idx_ft = map_3d(1) + max_level_loc + + ! Index of vertical weights. These start from 1 (whether W3 or Wtheta) + idx_vb = 1 + min_level_loc + idx_vt = 1 + max_level_loc + + increment(idx_fb:idx_ft) = ( & + (field_ref(idx_fb:idx_ft) - field_in(idx_fb:idx_ft)) & + * time_weight * vert_weights(idx_vb:idx_vt) & + ) + +end subroutine newtonian_nudging_code + +end module newtonian_nudging_kernel_mod diff --git a/science/gungho/source/kernel/external_forcing/nudging_ecmwf_vert_interp_kernel_mod.F90 b/science/gungho/source/kernel/external_forcing/nudging_ecmwf_vert_interp_kernel_mod.F90 new file mode 100644 index 0000000000..2dfc6bda67 --- /dev/null +++ b/science/gungho/source/kernel/external_forcing/nudging_ecmwf_vert_interp_kernel_mod.F90 @@ -0,0 +1,265 @@ +!------------------------------------------------------------------------------- +! (c) Crown copyright Met Office. All rights reserved. +! The file LICENCE, distributed with this code, contains details of the terms +! under which the code may be used. +!------------------------------------------------------------------------------- +!> @brief Performs vertical remapping of nudging reference fields +module nudging_ecmwf_vert_interp_kernel_mod + + use argument_mod, only: arg_type, & + GH_FIELD, GH_SCALAR, & + GH_REAL, GH_INTEGER, & + GH_WRITE, GH_READ, & + ANY_DISCONTINUOUS_SPACE_1, & + ANY_DISCONTINUOUS_SPACE_2, & + CELL_COLUMN + + use constants_mod, only: r_def, i_def, l_def + use fs_continuity_mod, only: Wtheta, W3 + use kernel_mod, only: kernel_type + + implicit none + + private + +!------------------------------------------------------------------------------- +! Public types +!------------------------------------------------------------------------------- + type, public, extends(kernel_type) :: nudging_ecmwf_vert_interp_kernel_type + private + type(arg_type) :: meta_args(12) = (/ & + arg_type(GH_FIELD, GH_REAL, GH_WRITE, Wtheta), & ! Interpolated theta field + arg_type(GH_FIELD, GH_REAL, GH_WRITE, W3), & ! Interpolated U field + arg_type(GH_FIELD, GH_REAL, GH_WRITE, W3), & ! Interpolated V field + arg_type(GH_FIELD, GH_REAL, GH_READ, ANY_DISCONTINUOUS_SPACE_1), & ! Input T/theta field + arg_type(GH_FIELD, GH_REAL, GH_READ, ANY_DISCONTINUOUS_SPACE_1), & ! Input U field + arg_type(GH_FIELD, GH_REAL, GH_READ, ANY_DISCONTINUOUS_SPACE_1), & ! Input V field + arg_type(GH_FIELD, GH_REAL, GH_READ, Wtheta), & ! Exner on wtheta + arg_type(GH_FIELD, GH_REAL, GH_READ, W3), & ! Exner on w3 + arg_type(GH_FIELD, GH_REAL, GH_READ, ANY_DISCONTINUOUS_SPACE_2), & ! Nudging pressure field + arg_type(GH_SCALAR, GH_REAL, GH_READ), & ! p_zero + arg_type(GH_SCALAR, GH_REAL, GH_READ), & ! kappa + arg_type(GH_SCALAR, GH_INTEGER, GH_READ) & ! nudge_levels + /) + integer :: operates_on = CELL_COLUMN +contains + procedure, nopass :: nudging_ecmwf_vert_interp_code +end type nudging_ecmwf_vert_interp_kernel_type + +!------------------------------------------------------------------------------- +! Contained functions/subroutines +!------------------------------------------------------------------------------- +public :: nudging_ecmwf_vert_interp_code + +contains + !----------------------------------------------------------------------------- + ! Contained functions/subroutines + !----------------------------------------------------------------------------- + !> @brief Performs vertical remapping of nudging reference fields + !> @param[in] nlayers Number of layers in model mesh + !> @param[in,out] theta_mod_levs theta field in Wtheta + !> @param[in,out] u_mod_levs zonal wind at W3 on model levels + !> @param[in,out] v_mod_levs meridional wind at W3 on model levels + !> @param[in] T_nudge_levs input nudging T or theta field + !> @param[in] u_nudge_levs input nudging zonal wind + !> @param[in] v_nudge_levs input nudging v wind + !> @param[in] exner_in_wth exner pressure in Wtheta + !> @param[in] exner_in_w3 exner pressure in W3 + !> @param[in] ln_surfp_nudge surface pressure from nudging data + !> @param[in] p_zero Reference pressure for Exner calculation + !> @param[in] kappa Rd/Cp, used in definition of Exner + !> @param[in] nudge_levels number of levels in reference data + !> @param[in] ndf_wth number of degrees of freedom per cell + !> @param[in] undf_wth num of DoFs in this partition for Wtheta + !> @param[in] map_wth index of DoFs for lowest cell for Wtheta + !> @param[in] ndf_w3 number of degrees of freedom per cell for W3 + !> @param[in] undf_w3 num of DoFs in this partition for W3 + !> @param[in] map_w3 index of DoFs for lowest cell for W3 + !> @param[in] ndf_ec num DoFs per cell for input nudging fields + !> @param[in] undf_ec num DoFs in this partition for nudging field + !> @param[in] map_ec dofmap for nudging fields + !> @param[in] ndf_ec_2d num DoFs per cell for surface pressure + !> @param[in] undf_ec_2d num DoFs in this partition for surf pressure + !> @param[in] map_ec_2d dofmap for surf pressure field + subroutine nudging_ecmwf_vert_interp_code( & + nlayers, & + theta_mod_levs, u_mod_levs, v_mod_levs, & + T_nudge_levs, u_nudge_levs, v_nudge_levs, & + exner_in_wth, exner_in_w3, ln_surfp_nudge, & + p_zero, kappa, nudge_levels, & + ndf_wth, undf_wth, map_wth, & + ndf_w3, undf_w3, map_w3, & + ndf_ec, undf_ec, map_ec, & + ndf_ec2d, undf_ec2d, map_ec2d & + ) + + use ecmwf_level_support_mod, only: ak_ec_137, bk_ec_137, & + ak_ec_137_50, bk_ec_137_50 + + implicit none + + ! Arguments + integer(kind=i_def), intent(in) :: nlayers + integer(kind=i_def), intent(in) :: ndf_wth, undf_wth + integer(kind=i_def), intent(in) :: map_wth(ndf_wth) + integer(kind=i_def), intent(in) :: ndf_w3, undf_w3 + integer(kind=i_def), intent(in) :: map_w3(ndf_w3) + integer(kind=i_def), intent(in) :: ndf_ec, undf_ec + integer(kind=i_def), intent(in) :: map_ec(ndf_ec) + integer(kind=i_def), intent(in) :: ndf_ec2d, undf_ec2d + integer(kind=i_def), intent(in) :: map_ec2d(ndf_ec2d) + integer(kind=i_def), intent(in) :: nudge_levels + + real(kind=r_def), intent(inout) :: theta_mod_levs(undf_wth) + real(kind=r_def), intent(inout) :: u_mod_levs(undf_w3) + real(kind=r_def), intent(inout) :: v_mod_levs(undf_w3) + real(kind=r_def), intent(inout) :: T_nudge_levs(undf_ec) + real(kind=r_def), intent(in) :: u_nudge_levs(undf_ec) + real(kind=r_def), intent(in) :: v_nudge_levs(undf_ec) + real(kind=r_def), intent(in) :: exner_in_wth(undf_wth) + real(kind=r_def), intent(in) :: exner_in_w3(undf_w3) + real(kind=r_def), intent(in) :: ln_surfp_nudge(undf_ec2d) + real(kind=r_def), intent(in) :: p_zero, kappa + + ! Local variables + ! Pointers to height coefficients for the current dataset + real(kind=r_def) :: ak_ec(0:nudge_levels) + real(kind=r_def) :: bk_ec(0:nudge_levels) + + ! Coefficients interpolated on full levels + real(kind=r_def), dimension(nudge_levels) :: ak_int, bk_int + + ! Pressure profiles + real(kind=r_def), dimension(nudge_levels) :: p_nudge_levels + real(kind=r_def), dimension(nlayers) :: press_in_wth + real(kind=r_def), dimension(0:nlayers-1) :: press_in_w3 + + ! Interim copies of nudging variables - for inverting data + real(kind=r_def), dimension(nudge_levels) :: theta_nudge_int + real(kind=r_def), dimension(nudge_levels) :: u_nudge_int + real(kind=r_def), dimension(nudge_levels) :: v_nudge_int + real(kind=r_def) :: step_wth, step_w3, delta + + ! Loop counters + integer(kind=i_def) :: ec_idx, wt_idx, w3_idx + integer(kind=i_def) :: j, k, l, m + integer(kind=i_def) :: kth, lth, mth + + ! Set height parameters, based on the number of nudging levels + select case (nudge_levels) + case (137) + ak_ec(:) = ak_ec_137(:) + bk_ec(:) = bk_ec_137(:) + case (88) + ak_ec(:) = ak_ec_137_50(:) + bk_ec(:) = bk_ec_137_50(:) + end select + + ec_idx = map_ec(1) + w3_idx = map_w3(1) + wt_idx = map_wth(1) + + ! Contruct height parameters on full levels + do k = 1, nudge_levels + + ! Convert the levels to the same order as model + ! (i.e start at the bottom of the atmosphere) + j = nudge_levels + 1 - k + + ak_int(k) = 0.5_r_def*(ak_ec(j-1) + ak_ec(j)) + bk_int(k) = 0.5_r_def*(bk_ec(j-1) + bk_ec(j)) + + ! construct pressure profile for nudging data + p_nudge_levels(k) = ak_int(k) & + + bk_int(k) * exp(ln_surfp_nudge( map_ec2d(1))) + end do + + ! Construct pressure profiles on model levels + do k = 0, nlayers - 1 + kth = k + 1 + press_in_wth(kth) = p_zero * exner_in_wth(wt_idx+kth)**(1.0_r_def/kappa) + press_in_w3(k) = p_zero * exner_in_w3(w3_idx+k)**(1.0_r_def/kappa) + end do + + ! Copy to interim arrays + ! Convert T to theta using the pressure profile generated here. + ! Input field on W3 mesh goes from 0 -> nudge_levels-1 + do k = 0, nudge_levels - 1 + theta_nudge_int(k+1) = & + T_nudge_levs(ec_idx+k) * (p_zero / p_nudge_levels(k+1))**kappa + u_nudge_int(k+1) = u_nudge_levs(ec_idx+k) + v_nudge_int(k+1) = v_nudge_levs(ec_idx+k) + end do + + ! Now interpolate from nudging levels to model levels + ! Initialise the data loop + l = 1 + m = l + 1 + lth = 1 + mth = lth + 1 + + ! Increment L until Nudging Pressures straddle 1st model Pressure level + ! Separately for wth and w3 grids + do while(press_in_w3(1) < p_nudge_levels(m) .and. m < nudge_levels) + l = l + 1 + m = l + 1 + end do + + do while (press_in_wth(1) < p_nudge_levels(mth) .and. mth < nudge_levels) + lth = lth + 1 + mth = lth + 1 + end do + + ! Loop over model levels interpolating on to these levels + do k = 0, nlayers - 1 + kth = k + 1 + + ! Calculate interpolation weights in log-pressure space + step_w3 = log(press_in_w3(k)) - log(p_nudge_levels(l)) + step_w3 = step_w3/(log(p_nudge_levels(l+1)) - log(p_nudge_levels(l))) + + step_wth = log(press_in_wth(kth)) - log(p_nudge_levels(lth)) + step_wth = step_wth/(log(p_nudge_levels(lth+1)) - log(p_nudge_levels(lth))) + + ! Limit large extrapolations - keep step between -1.0 <-> 2.0 + step_w3 = max( min(step_w3, 2.0_r_def), -1.0_r_def ) + step_wth = max( min(step_wth, 2.0_r_def), -1.0_r_def ) + + ! Interpolate the analyses on to the model levels + delta = u_nudge_int(m) - u_nudge_int(l) + u_mod_levs(w3_idx+k) = u_nudge_int( l ) + delta * step_w3 + + delta = v_nudge_int(m) - v_nudge_int(l) + v_mod_levs(w3_idx+k) = v_nudge_int(l) + delta * step_w3 + + delta = theta_nudge_int(mth) - theta_nudge_int(lth) + theta_mod_levs(wt_idx+kth) = theta_nudge_int(lth) + delta * step_wth + + ! If the pressure of the next nudge_level is greater than the next + ! model level then increment to the nudge level + ! Also check that we have not exceeded the number of nudge or model + ! levels + if (kth < nlayers) then + mth = lth + 1 + do while (press_in_wth(kth+1) < p_nudge_levels(mth) .and. mth < nudge_levels) + lth = lth + 1 + mth = lth + 1 + end do + end if + + if (k < nlayers - 1) then + m = l + 1 + do while (press_in_w3(k+1) < p_nudge_levels(m) .and. m < nudge_levels) + l = l + 1 + m = l + 1 + end do + end if + + end do ! Loop over levels + + ! Set bottom value of theta to the value of the next level up + theta_mod_levs(wt_idx) = theta_mod_levs(wt_idx+1) + + end subroutine nudging_ecmwf_vert_interp_code + +end module nudging_ecmwf_vert_interp_kernel_mod diff --git a/science/gungho/source/physics/multidata_field_dimensions_mod.F90 b/science/gungho/source/physics/multidata_field_dimensions_mod.F90 index c9b6f96df6..df80fea040 100644 --- a/science/gungho/source/physics/multidata_field_dimensions_mod.F90 +++ b/science/gungho/source/physics/multidata_field_dimensions_mod.F90 @@ -168,7 +168,7 @@ function get_multidata_field_dimension(multidata_item) result (dim) use aerosol_config_mod, only: l_radaer use chemistry_config_mod, only: chem_scheme, chem_scheme_strattrop use um_ukca_init_mod, only: n_phot_spc - use nudging_config_mod, only: nudge_data_levels + use nudging_config_mod, only: num_ref_data_levels #endif use log_mod, only: log_event, LOG_LEVEL_ERROR, & @@ -280,7 +280,7 @@ function get_multidata_field_dimension(multidata_item) result (dim) dim = 1 end if case ('nudging_levels') - dim = nudge_data_levels + dim = num_ref_data_levels case ('') dim = 1 ! ordinary (non-multidata) field #endif diff --git a/science/gungho/source/psy/psykal_lite_mod.F90 b/science/gungho/source/psy/psykal_lite_mod.F90 index 9c12f78d90..c623e6e062 100644 --- a/science/gungho/source/psy/psykal_lite_mod.F90 +++ b/science/gungho/source/psy/psykal_lite_mod.F90 @@ -795,4 +795,199 @@ subroutine invoke_prolong_multidata_linear_kernel_type(target_field, source_fiel ! end subroutine invoke_prolong_multidata_linear_kernel_type +! ============================================================================ ! +! NUDGING ROUTINES +! ============================================================================ ! +! These kernels use a 1D scalar array, which is not yet fully supported by +! Psyclone. This code can be removed once PSyclone issue #1312 is closed + + subroutine invoke_newtonian_nudging_kernel_type(coarse_increment, coarse_field_in, field_ref, vertical_weights, & +&tropopause_level, min_level, max_level, time_weight, dims_vertical_weights) + + use newtonian_nudging_kernel_mod, only: newtonian_nudging_code + use mesh_mod, only : mesh_type + + implicit none + + type(field_type), intent(in) :: coarse_increment + type(field_type), intent(in) :: coarse_field_in + type(field_type), intent(in) :: field_ref + type(integer_field_type), intent(in) :: tropopause_level + integer(kind=i_def), intent(in) :: min_level + integer(kind=i_def), intent(in) :: max_level + real(kind=r_def), intent(in) :: time_weight + integer(kind=i_def), dimension(1), intent(in) :: dims_vertical_weights + real(kind=r_def), dimension(dims_vertical_weights(1)), intent(in) :: vertical_weights + integer(kind=i_def) :: cell + type(mesh_type), pointer :: mesh => null() + integer(kind=i_def) :: max_halo_depth_mesh + real(kind=r_def), pointer, dimension(:) :: coarse_increment_data => null() + real(kind=r_def), pointer, dimension(:) :: coarse_field_in_data => null() + real(kind=r_def), pointer, dimension(:) :: field_ref_data => null() + integer(kind=i_def), pointer, dimension(:) :: tropopause_level_data => null() + integer(kind=i_def) :: nlayers_coarse_increment + integer(kind=i_def) :: ndf_a1_ce_it + integer(kind=i_def) :: undf_a1_ce_it + integer(kind=i_def) :: ndf_a2_te_ll + integer(kind=i_def) :: undf_a2_te_ll + integer(kind=i_def), pointer :: map_a1_ce_it(:,:) => null() + integer(kind=i_def), pointer :: map_a2_te_ll(:,:) => null() + type(field_proxy_type) :: coarse_increment_proxy + type(field_proxy_type) :: coarse_field_in_proxy + type(field_proxy_type) :: field_ref_proxy + type(integer_field_proxy_type) :: tropopause_level_proxy + integer(kind=i_def) :: loop0_start + integer(kind=i_def) :: loop0_stop + + ! Initialise field and/or operator proxies + coarse_increment_proxy = coarse_increment%get_proxy() + coarse_increment_data => coarse_increment_proxy%data + coarse_field_in_proxy = coarse_field_in%get_proxy() + coarse_field_in_data => coarse_field_in_proxy%data + field_ref_proxy = field_ref%get_proxy() + field_ref_data => field_ref_proxy%data + tropopause_level_proxy = tropopause_level%get_proxy() + tropopause_level_data => tropopause_level_proxy%data + + ! Initialise number of layers + nlayers_coarse_increment = coarse_increment_proxy%vspace%get_nlayers() + + ! Create a mesh object + mesh => coarse_increment_proxy%vspace%get_mesh() + max_halo_depth_mesh = mesh%get_halo_depth() + + ! Look-up dofmaps for each function space + map_a1_ce_it => coarse_increment_proxy%vspace%get_whole_dofmap() + map_a2_te_ll => tropopause_level_proxy%vspace%get_whole_dofmap() + + ! Initialise number of DoFs for a1_ce_it + ndf_a1_ce_it = coarse_increment_proxy%vspace%get_ndf() + undf_a1_ce_it = coarse_increment_proxy%vspace%get_undf() + + ! Initialise number of DoFs for a2_te_ll + ndf_a2_te_ll = tropopause_level_proxy%vspace%get_ndf() + undf_a2_te_ll = tropopause_level_proxy%vspace%get_undf() + + ! Set-up all of the loop bounds + loop0_start = 1 + loop0_stop = mesh%get_last_edge_cell() + + ! Call kernels and communication routines + do cell = loop0_start, loop0_stop, 1 + call newtonian_nudging_code(nlayers_coarse_increment, coarse_increment_data, coarse_field_in_data, field_ref_data, & +&dims_vertical_weights, vertical_weights, tropopause_level_data, min_level, max_level, time_weight, ndf_a1_ce_it, undf_a1_ce_it, & +&map_a1_ce_it(:,cell), ndf_a2_te_ll, undf_a2_te_ll, map_a2_te_ll(:,cell)) + enddo + + ! Set halos dirty/clean for fields modified in the above loop(s) + call coarse_increment_proxy%set_dirty() + + end subroutine invoke_newtonian_nudging_kernel_type + + subroutine invoke_convolution_2d_kernel_type(coarse_increment, coarse_field_diff, & +&spectral_stencil_extent, vertical_weights, convolution_weights, & +&tropopause_level, min_level, max_level, time_weight, dims_vertical_weights) + + use convolution_2d_kernel_mod, only: convolution_2d_code + use mesh_mod, only : mesh_type + use stencil_dofmap_mod, only : STENCIL_REGION, stencil_dofmap_type + + implicit none + + type(field_type), intent(in) :: coarse_increment + type(field_type), intent(in) :: coarse_field_diff + type(field_type), intent(in) :: convolution_weights + type(integer_field_type), intent(in) :: tropopause_level + integer(kind=i_def), intent(in) :: min_level + integer(kind=i_def), intent(in) :: max_level + real(kind=r_def), intent(in) :: time_weight + integer(kind=i_def), intent(in) :: spectral_stencil_extent + integer(kind=i_def), dimension(1), intent(in) :: dims_vertical_weights + real(kind=r_def), dimension(dims_vertical_weights(1)), intent(in) :: vertical_weights + integer(kind=i_def) :: cell + type(mesh_type), pointer :: mesh => null() + integer(kind=i_def) :: max_halo_depth_mesh + real(kind=r_def), pointer, dimension(:) :: coarse_increment_data => null() + real(kind=r_def), pointer, dimension(:) :: coarse_field_diff_data => null() + real(kind=r_def), pointer, dimension(:) :: convolution_weights_data => null() + integer(kind=i_def), pointer, dimension(:) :: tropopause_level_data => null() + integer(kind=i_def) :: nlayers_coarse_increment + type(stencil_dofmap_type), pointer :: coarse_field_diff_stencil_map => null() + integer(kind=i_def), pointer, dimension(:,:,:) :: coarse_field_diff_stencil_dofmap => null() + integer(kind=i_def) :: ndf_a1_ce_it + integer(kind=i_def) :: undf_a1_ce_it + integer(kind=i_def) :: ndf_a9_cn_ws + integer(kind=i_def) :: undf_a9_cn_ws + integer(kind=i_def) :: ndf_a2_te_ll + integer(kind=i_def) :: undf_a2_te_ll + integer(kind=i_def), pointer :: map_a1_ce_it(:,:) => null() + integer(kind=i_def), pointer :: map_a2_te_ll(:,:) => null() + integer(kind=i_def), pointer :: map_a9_cn_ws(:,:) => null() + type(field_proxy_type) :: coarse_increment_proxy + type(field_proxy_type) :: coarse_field_diff_proxy + type(field_proxy_type) :: convolution_weights_proxy + type(integer_field_proxy_type) :: tropopause_level_proxy + integer(kind=i_def), pointer, dimension(:) :: coarse_field_diff_stencil_size => null() + integer(kind=i_def) :: loop0_start + integer(kind=i_def) :: loop0_stop + + ! Initialise field and/or operator proxies + coarse_increment_proxy = coarse_increment%get_proxy() + coarse_increment_data => coarse_increment_proxy%data + coarse_field_diff_proxy = coarse_field_diff%get_proxy() + coarse_field_diff_data => coarse_field_diff_proxy%data + convolution_weights_proxy = convolution_weights%get_proxy() + convolution_weights_data => convolution_weights_proxy%data + tropopause_level_proxy = tropopause_level%get_proxy() + tropopause_level_data => tropopause_level_proxy%data + + ! Initialise number of layers + nlayers_coarse_increment = coarse_increment_proxy%vspace%get_nlayers() + + ! Create a mesh object + mesh => coarse_increment_proxy%vspace%get_mesh() + max_halo_depth_mesh = mesh%get_halo_depth() + + ! Initialise stencil dofmaps + coarse_field_diff_stencil_map => coarse_field_diff_proxy%vspace%get_stencil_dofmap(STENCIL_REGION, spectral_stencil_extent) + coarse_field_diff_stencil_dofmap => coarse_field_diff_stencil_map%get_whole_dofmap() + coarse_field_diff_stencil_size => coarse_field_diff_stencil_map%get_stencil_sizes() + + ! Look-up dofmaps for each function space + map_a1_ce_it => coarse_increment_proxy%vspace%get_whole_dofmap() + map_a9_cn_ws => convolution_weights_proxy%vspace%get_whole_dofmap() + map_a2_te_ll => tropopause_level_proxy%vspace%get_whole_dofmap() + + ! Initialise number of DoFs for a1_ce_it + ndf_a1_ce_it = coarse_increment_proxy%vspace%get_ndf() + undf_a1_ce_it = coarse_increment_proxy%vspace%get_undf() + + ! Initialise number of DoFs for a9_cn_ws + ndf_a9_cn_ws = convolution_weights_proxy%vspace%get_ndf() + undf_a9_cn_ws = convolution_weights_proxy%vspace%get_undf() + + ! Initialise number of DoFs for a2_te_ll + ndf_a2_te_ll = tropopause_level_proxy%vspace%get_ndf() + undf_a2_te_ll = tropopause_level_proxy%vspace%get_undf() + + ! Set-up all of the loop bounds + loop0_start = 1 + loop0_stop = mesh%get_last_edge_cell() + + ! Call kernels and communication routines + if (coarse_field_diff_proxy%is_dirty(depth=spectral_stencil_extent)) then + call coarse_field_diff_proxy%halo_exchange(depth=spectral_stencil_extent) + end if + do cell = loop0_start, loop0_stop, 1 + call convolution_2d_code(nlayers_coarse_increment, coarse_increment_data, coarse_field_diff_data, & +&coarse_field_diff_stencil_size(cell), coarse_field_diff_stencil_dofmap(:,:,cell), dims_vertical_weights, vertical_weights, & +&convolution_weights_data, tropopause_level_data, min_level, max_level, time_weight, ndf_a1_ce_it, undf_a1_ce_it, & +&map_a1_ce_it(:,cell), ndf_a9_cn_ws, undf_a9_cn_ws, map_a9_cn_ws(:,cell), ndf_a2_te_ll, undf_a2_te_ll, map_a2_te_ll(:,cell)) + enddo + + ! Set halos dirty/clean for fields modified in the above loop(s) + call coarse_increment_proxy%set_dirty() + + end subroutine invoke_convolution_2d_kernel_type + end module psykal_lite_mod diff --git a/science/gungho/unit-test/kernel/external_forcing/convolution_2d_kernel_mod_test.pf b/science/gungho/unit-test/kernel/external_forcing/convolution_2d_kernel_mod_test.pf new file mode 100644 index 0000000000..19d12fb604 --- /dev/null +++ b/science/gungho/unit-test/kernel/external_forcing/convolution_2d_kernel_mod_test.pf @@ -0,0 +1,118 @@ +!----------------------------------------------------------------------------- +! (C) Crown copyright Met Office. All rights reserved. +! The file LICENCE, distributed with this code, contains details of the terms +! under which the code may be used. +!------------------------------------------------------------------------------- +module convolution_2d_kernel_mod_test + + use constants_mod, only : i_def, r_def, PI + use funit + + + implicit none + + private + public :: test_all + +contains + + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + @Test + subroutine test_all( ) + + use convolution_2d_kernel_mod, only: convolution_2d_code + + implicit none + + integer(kind=i_def), parameter :: nlayers = 1 + integer(kind=i_def), parameter :: stencil_size = 25 + integer(kind=i_def), parameter :: ndf_2d_cw = 1 + integer(kind=i_def), parameter :: undf_2d_cw = stencil_size + integer(kind=i_def), parameter :: ndf_2d = 1 + integer(kind=i_def), parameter :: undf_2d = 1 + integer(kind=i_def), parameter :: ndf_3d = 1 ! This simplifies the test! + integer(kind=i_def), parameter :: undf_3d = stencil_size + integer(kind=i_def), parameter :: nvert(1) = 1 + integer(kind=i_def), parameter :: kmax = 40 + integer(kind=i_def), parameter :: kmin = 4 + real(kind=r_def), parameter :: dx = 20.0_r_def + real(kind=r_def), parameter :: dy = 10.0_r_def + ! With nlayers = 1 and ndf_3d = 1, only a single level (level 0) exists, so + ! min_level/max_level/tropopause_level are all capped down to that single + ! level regardless of their values here + integer(kind=i_def), parameter :: min_level = 0 + integer(kind=i_def), parameter :: max_level = 2 + real(kind=r_def), parameter :: time_weight = 3.0_r_def + + real(kind=r_def), parameter :: tol = 10.0_r_def*epsilon(1.0_r_def) + + integer(kind=i_def) :: map_3d(ndf_3d) + integer(kind=i_def) :: map_2d_cw(ndf_2d_cw) + integer(kind=i_def) :: map_2d(ndf_2d) + integer(kind=i_def) :: stencil_map(ndf_3d, stencil_size) + integer(kind=i_def) :: tropopause_level(undf_2d) + real(kind=r_def) :: answer + real(kind=r_def) :: filtered_field(undf_3d) + real(kind=r_def) :: field_in(undf_3d) + real(kind=r_def) :: conv_weights(undf_2d_cw) + real(kind=r_def) :: vert_weights(nvert(1)) + + integer(kind=i_def) :: i, j, ij + real(kind=r_def) :: dist, chi1, chi2 + + map_3d(1) = 13 + map_2d_cw(1) = 1 + map_2d(1) = 1 + tropopause_level(1) = 4 + vert_weights(1) = 2.0_r_def + + ! Populate maps / weights + ! Set up regular grid of coordinates + do i = 1, 5 + do j = 1, 5 + ij = (i-1)*5 + j + stencil_map(1, ij) = ij + ! Coordinates ---------------------------------------------------------- + chi1 = dx*REAL(i, r_def) + chi2 = dy*REAL(j, r_def) + ! Weights -------------------------------------------------------------- + dist = sqrt((chi1 - 3.0_r_def*dx)**2 + (chi2 - 3.0_r_def*dy)**2) + if (dist < 1.0e-3_r_def) then + conv_weights(ij) = 2.0_r_def*REAL(kmax - kmin, r_def) + else + conv_weights(ij) = ( & + SIN(2.0_r_def*PI*kmax*dist) / (PI*dist) & + - SIN(2.0_r_def*PI*kmin*dist) / (PI*dist) & + ) + end if + ! Input field ---------------------------------------------------------- + ! Make it linear in x + field_in(ij) = 100.0_r_def + 19.0_r_def*REAL(i, r_def) + end do + end do + + ! Field is linear and convolution is symmetric, so the convolution alone + ! should reproduce the central value; the kernel then scales this by the + ! time weight and vertical weight + answer = field_in(13) * time_weight * vert_weights(1) + + ! Normalise weights + conv_weights(:) = conv_weights(:) / SUM(conv_weights(:)) + + ! Initialise output field to zero + filtered_field(:) = 0.0_r_def + + call convolution_2d_code( & + nlayers, filtered_field, field_in, stencil_size, stencil_map, & + nvert, vert_weights, conv_weights, tropopause_level, & + min_level, max_level, time_weight, & + ndf_3d, undf_3d, map_3d, ndf_2d_cw, undf_2d_cw, map_2d_cw, & + ndf_2d, undf_2d, map_2d & + ) + + ! Only central value (13 out of 25) is set + @assertEqual(answer, filtered_field(13), tol) + + end subroutine test_all + +end module convolution_2d_kernel_mod_test diff --git a/science/gungho/unit-test/kernel/external_forcing/convolution_2d_weights_kernel_mod_test.pf b/science/gungho/unit-test/kernel/external_forcing/convolution_2d_weights_kernel_mod_test.pf new file mode 100644 index 0000000000..3e691bede9 --- /dev/null +++ b/science/gungho/unit-test/kernel/external_forcing/convolution_2d_weights_kernel_mod_test.pf @@ -0,0 +1,158 @@ +!----------------------------------------------------------------------------- +! (C) Crown copyright Met Office. All rights reserved. +! The file LICENCE, distributed with this code, contains details of the terms +! under which the code may be used. +!------------------------------------------------------------------------------- +module convolution_2d_weights_kernel_mod_test + + use constants_mod, only : i_def, r_def, PI + use funit + + + implicit none + + private + public :: test_all + + @TestCase + type, extends(TestCase), public :: convolution_2d_weights_test_type + private + contains + procedure setUp + procedure tearDown + procedure test_all + end type convolution_2d_weights_test_type + +contains + + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + subroutine setUp(this) + + implicit none + + class(convolution_2d_weights_test_type), intent(inout) :: this + + end subroutine setUp + + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + subroutine tearDown(this) + + implicit none + + class(convolution_2d_weights_test_type), intent(inout) :: this + + end subroutine tearDown + + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + @Test + subroutine test_all(this) + + use convolution_2d_weights_kernel_mod, only: convolution_2d_weights_code + use coord_transform_mod, only: central_angle + use base_mesh_config_mod, only: geometry_spherical, & + topology_non_periodic + use finite_element_config_mod, only: coord_system_native + + implicit none + + class(convolution_2d_weights_test_type), intent(inout) :: this + + integer(kind=i_def), parameter :: nlayers = 1 + integer(kind=i_def), parameter :: stencil_size = 25 + integer(kind=i_def), parameter :: ndf_2d = 1 + integer(kind=i_def), parameter :: undf_2d = stencil_size + integer(kind=i_def), parameter :: ndf_chi = 1 ! This simplifies the test! + integer(kind=i_def), parameter :: undf_chi = stencil_size + integer(kind=i_def), parameter :: ndf_pid = 1 + integer(kind=i_def), parameter :: undf_pid = stencil_size + integer(kind=i_def), parameter :: kmax = 4 + integer(kind=i_def), parameter :: kmin = 2 + real(kind=r_def), parameter :: lon_c = PI/6.0_r_def + real(kind=r_def), parameter :: lat_c = PI/12.0_r_def + real(kind=r_def), parameter :: dlon = PI/48.0_r_def + real(kind=r_def), parameter :: dlat = PI/96.0_r_def + + ! Gaussian envelope width, set to very large value to disable the envelope + ! for this unit-test + real(kind=r_def), parameter :: sigma = 1.0E16_r_def + + real(kind=r_def), parameter :: tol = 10.0_r_def*epsilon(1.0_r_def) + + integer(kind=i_def) :: map_chi(ndf_chi) + integer(kind=i_def) :: map_pid(ndf_pid) + integer(kind=i_def) :: map_2d(ndf_2d) + integer(kind=i_def) :: stencil_map_chi(ndf_chi, stencil_size) + integer(kind=i_def) :: stencil_map_pid(ndf_pid, stencil_size) + real(kind=r_def) :: answer(undf_2d) + real(kind=r_def) :: chi1(undf_chi), chi2(undf_chi), chi3(undf_chi) + real(kind=r_def) :: basis_chi(1, ndf_chi, ndf_2d) + real(kind=r_def) :: panel_id(undf_pid) + real(kind=r_def) :: weights(undf_2d) + + integer(kind=i_def) :: geometry, topology, coord_system + real(kind=r_def) :: scaled_radius + integer(kind=i_def) :: i, j, ij + real(kind=r_def) :: dist + + ! Map is just for central column + map_chi(1) = 13 + map_pid(1) = 13 + map_2d(1) = 1 + + panel_id(:) = 1.0_r_def + chi3(:) = 0.0_r_def + basis_chi(:,:,:) = 1.0_r_def ! Chi is at cell centres + + geometry = geometry_spherical + topology = topology_non_periodic + coord_system = coord_system_native + scaled_radius = 1.0_r_def + + ! Populate maps / coordinates / weights + ! Set up regular grid of coordinates + do i = 1, 5 + do j = 1, 5 + ij = (i-1)*5 + j + stencil_map_chi(1, ij) = ij + stencil_map_pid(1, ij) = ij + ! Coordinates ---------------------------------------------------------- + chi1(ij) = lon_c + dlon*REAL(i-3, r_def) + chi2(ij) = lat_c + dlat*REAL(j-3, r_def) + ! Known answer --------------------------------------------------------- + call central_angle(lon_c, lat_c, chi1(ij), chi2(ij), dist) + dist = COS(dist) + + ! The Legendre polynomials for degrees 2 to 4 are: + ! P_2(x) = 0.5*(3*x^2 - 1) + ! P_3(x) = 0.5*(5*x^3 - 3*x) + ! P_4(x) = (1/8)*(35*x^4 - 30*x^2 + 3) + ! For the convolution filter, each function is weighted by (2*l+1) + answer(ij) = ( & + 5.0_r_def*0.5_r_def*(3.0_r_def*dist**2 - 1.0_r_def) & + + 7.0_r_def*0.5_r_def*(5.0_r_def*dist**3 - 3.0_r_def*dist) & + + 9.0_r_def*(1.0_r_def/8.0_r_def)*( & + 35.0_r_def*dist**4 - 30.0_r_def*dist**2 + 3.0_r_def & + ) & + ) / (4.0_r_def*PI) + end do + end do + + ! Normalise answer, since stencil is restricted in space + answer(:) = answer(:) / SUM(answer(:)) + + ! Initialise weights to zero + weights(:) = 0.0_r_def + + call convolution_2d_weights_code( & + nlayers, weights, chi1, chi2, chi3, stencil_size, stencil_map_chi, & + panel_id, stencil_size, stencil_map_pid, kmax, kmin, sigma, & + geometry, topology, coord_system, scaled_radius, & + ndf_2d, undf_2d, map_2d, ndf_chi, undf_chi, map_chi, basis_chi, & + ndf_pid, undf_pid, map_pid & + ) + + @assertEqual(answer, weights, tol) + + end subroutine test_all + +end module convolution_2d_weights_kernel_mod_test diff --git a/science/gungho/unit-test/kernel/external_forcing/newtonian_nudging_kernel_mod_test.pf b/science/gungho/unit-test/kernel/external_forcing/newtonian_nudging_kernel_mod_test.pf new file mode 100644 index 0000000000..66930d76b2 --- /dev/null +++ b/science/gungho/unit-test/kernel/external_forcing/newtonian_nudging_kernel_mod_test.pf @@ -0,0 +1,90 @@ +!----------------------------------------------------------------------------- +! (C) Crown copyright Met Office. All rights reserved. +! The file LICENCE, distributed with this code, contains details of the terms +! under which the code may be used. +! +! Some of the content of this file has been produced with the assistance of +! GitHub Copilot (Claude Sonnet 5). +!------------------------------------------------------------------------------- +module newtonian_nudging_kernel_mod_test + + use constants_mod, only : i_def, r_def + use funit + + implicit none + + private + public :: test_all + +contains + + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + @Test + subroutine test_all( ) + + use newtonian_nudging_kernel_mod, only: newtonian_nudging_code + + implicit none + + integer(kind=i_def), parameter :: nlayers = 10 + integer(kind=i_def), parameter :: ndf_3d = 1 + integer(kind=i_def), parameter :: undf_3d = 10 + integer(kind=i_def), parameter :: ndf_2d = 1 + integer(kind=i_def), parameter :: undf_2d = 1 + integer(kind=i_def), parameter :: nvert(1) = nlayers + + ! Bottom and top levels for full-strength nudging: the tropopause level + ! is more restrictive than max_level (and both are within the number of + ! model levels), so it is the tropopause level that should determine the + ! top of the nudged range + integer(kind=i_def), parameter :: min_level = 2 + integer(kind=i_def), parameter :: max_level = 6 + integer(kind=i_def), parameter :: trop_level = 5 + + real(kind=r_def), parameter :: time_weight = 2.0_r_def + real(kind=r_def), parameter :: tol = 10.0_r_def*epsilon(1.0_r_def) + + integer(kind=i_def) :: map_3d(ndf_3d) + integer(kind=i_def) :: map_2d(ndf_2d) + integer(kind=i_def) :: tropopause_level(undf_2d) + + real(kind=r_def) :: increment(undf_3d) + real(kind=r_def) :: field_in(undf_3d) + real(kind=r_def) :: field_ref(undf_3d) + real(kind=r_def) :: vert_weights(nvert(1)) + real(kind=r_def) :: answer(undf_3d) + + integer(kind=i_def) :: k + + map_3d(1) = 1 + map_2d(1) = 1 + tropopause_level(1) = trop_level + + do k = 1, undf_3d + field_in(k) = 10.0_r_def + real(k, r_def) + field_ref(k) = 50.0_r_def + 2.0_r_def*real(k, r_def) + end do + + vert_weights(:) = (/ 0.0_r_def, 0.0_r_def, 0.5_r_def, 1.0_r_def, 1.0_r_def, & + 1.0_r_def, 0.5_r_def, 0.0_r_def, 0.0_r_def, 0.0_r_def /) + + answer(:) = 0.0_r_def + do k = 1, nlayers + if (k >= min_level .and. k <= max_level .and. k <= trop_level) then + answer(k) = (field_ref(k) - field_in(k)) * time_weight * vert_weights(k) + end if + end do + + increment(:) = 0.0_r_def + + call newtonian_nudging_code( & + nlayers, increment, field_in, field_ref, nvert, vert_weights, & + tropopause_level, min_level, max_level, time_weight, & + ndf_3d, undf_3d, map_3d, ndf_2d, undf_2d, map_2d & + ) + + @assertEqual(answer, increment, tol) + + end subroutine test_all + +end module newtonian_nudging_kernel_mod_test diff --git a/science/gungho/unit-test/kernel/external_forcing/nudging_ecmwf_vert_interp_kernel_mod_test.pf b/science/gungho/unit-test/kernel/external_forcing/nudging_ecmwf_vert_interp_kernel_mod_test.pf new file mode 100644 index 0000000000..f8f3796066 --- /dev/null +++ b/science/gungho/unit-test/kernel/external_forcing/nudging_ecmwf_vert_interp_kernel_mod_test.pf @@ -0,0 +1,146 @@ +!----------------------------------------------------------------------------- +! (C) Crown copyright Met Office. All rights reserved. +! The file LICENCE, distributed with this code, contains details of the terms +! under which the code may be used. +! +! Some of the content of this file has been produced with the assistance of +! GitHub Copilot (Claude Sonnet 5). +!------------------------------------------------------------------------------- +module nudging_ecmwf_vert_interp_kernel_mod_test + + use constants_mod, only : i_def, r_def + use funit + + implicit none + + private + public :: test_all + +contains + + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + @Test + subroutine test_all( ) + + use nudging_ecmwf_vert_interp_kernel_mod, only: nudging_ecmwf_vert_interp_code + use ecmwf_level_support_mod, only: ak_ec_137, bk_ec_137 + + implicit none + + integer(kind=i_def), parameter :: nlayers = 5 + integer(kind=i_def), parameter :: nudge_levels = 137 + + integer(kind=i_def), parameter :: ndf_wth = 1 + integer(kind=i_def), parameter :: undf_wth = nlayers + 1 + integer(kind=i_def), parameter :: ndf_w3 = 1 + integer(kind=i_def), parameter :: undf_w3 = nlayers + integer(kind=i_def), parameter :: ndf_ec = 1 + integer(kind=i_def), parameter :: undf_ec = nudge_levels + integer(kind=i_def), parameter :: ndf_ec2d = 1 + integer(kind=i_def), parameter :: undf_ec2d = 1 + + real(kind=r_def), parameter :: p_zero = 100000.0_r_def + real(kind=r_def), parameter :: kappa = 2.0_r_def/7.0_r_def + real(kind=r_def), parameter :: surfp_nudge = 101325.0_r_def + real(kind=r_def), parameter :: tol = 1.0e-6_r_def + + integer(kind=i_def) :: map_wth(ndf_wth), map_w3(ndf_w3) + integer(kind=i_def) :: map_ec(ndf_ec), map_ec2d(ndf_ec2d) + + real(kind=r_def) :: theta_mod_levs(undf_wth) + real(kind=r_def) :: u_mod_levs(undf_w3) + real(kind=r_def) :: v_mod_levs(undf_w3) + real(kind=r_def) :: T_nudge_levs(undf_ec) + real(kind=r_def) :: u_nudge_levs(undf_ec) + real(kind=r_def) :: v_nudge_levs(undf_ec) + real(kind=r_def) :: exner_in_wth(undf_wth) + real(kind=r_def) :: exner_in_w3(undf_w3) + real(kind=r_def) :: ln_surfp_nudge(undf_ec2d) + + real(kind=r_def) :: ak_int, bk_int, p_nudge, theta_target + real(kind=r_def) :: press_in_w3(0:nlayers-1) + real(kind=r_def) :: press_in_wth(nlayers) + + real(kind=r_def) :: theta_answer(undf_wth) + real(kind=r_def) :: u_answer(undf_w3) + real(kind=r_def) :: v_answer(undf_w3) + + integer(kind=i_def) :: k, j + + map_wth(1) = 1 + map_w3(1) = 1 + map_ec(1) = 1 + map_ec2d(1) = 1 + + ln_surfp_nudge(1) = log(surfp_nudge) + + ! Build synthetic nudging profiles for u, v and theta that vary linearly + ! with log(pressure), so that the kernel's log-pressure interpolation + ! reproduces them exactly (away from the top/bottom of the profile) + do k = 1, nudge_levels + j = nudge_levels + 1 - k + ak_int = 0.5_r_def*(ak_ec_137(j-1) + ak_ec_137(j)) + bk_int = 0.5_r_def*(bk_ec_137(j-1) + bk_ec_137(j)) + p_nudge = ak_int + bk_int*surfp_nudge + + u_nudge_levs(k) = 5.0_r_def*log(p_nudge) - 20.0_r_def + v_nudge_levs(k) = -2.0_r_def*log(p_nudge) + 15.0_r_def + theta_target = 500.0_r_def - 15.0_r_def*log(p_nudge) + T_nudge_levs(k) = theta_target * (p_nudge/p_zero)**kappa + end do + + ! Model level pressures + press_in_w3 = (/ 90000.0_r_def, 75000.0_r_def, 60000.0_r_def, & + 45000.0_r_def, 30000.0_r_def /) + press_in_wth = (/ 85000.0_r_def, 70000.0_r_def, 55000.0_r_def, & + 40000.0_r_def, 25000.0_r_def /) + + do k = 0, nlayers - 1 + exner_in_w3(map_w3(1)+k) = (press_in_w3(k)/p_zero)**kappa + end do + + do k = 1, nlayers + exner_in_wth(map_wth(1)+k) = (press_in_wth(k)/p_zero)**kappa + end do + ! Bottom Wtheta value is not used by the kernel, other than being + ! overwritten with the value of the level above at the end + exner_in_wth(map_wth(1)) = exner_in_wth(map_wth(1)+1) + + theta_mod_levs(:) = 0.0_r_def + u_mod_levs(:) = 0.0_r_def + v_mod_levs(:) = 0.0_r_def + + ! Expected answers, obtained from an independent reference implementation + ! of the interpolation algorithm using the same input data. Note that the + ! bottom u/v level does not reproduce the analytic profile exactly, since + ! the kernel's search for the bracketing nudging levels at the bottom + ! model level is based on the pressure of the level above + u_answer = (/ 36.261930665360950_r_def, 36.126216962592230_r_def, & + 35.010499206021194_r_def, 33.572088843762280_r_def, & + 31.544763303221465_r_def /) + v_answer = (/ -7.504772266144382_r_def, -7.450486785036894_r_def, & + -7.004199682408476_r_def, -6.428835537504913_r_def, & + -5.617905321288585_r_def /) + theta_answer = (/ 329.7439019679132_r_def, 329.7439019679132_r_def, & + 332.65624218452757_r_def, 336.2736730367809_r_def, & + 341.0504790035589_r_def, 348.100533442245_r_def /) + + call nudging_ecmwf_vert_interp_code( & + nlayers, & + theta_mod_levs, u_mod_levs, v_mod_levs, & + T_nudge_levs, u_nudge_levs, v_nudge_levs, & + exner_in_wth, exner_in_w3, ln_surfp_nudge, & + p_zero, kappa, nudge_levels, & + ndf_wth, undf_wth, map_wth, & + ndf_w3, undf_w3, map_w3, & + ndf_ec, undf_ec, map_ec, & + ndf_ec2d, undf_ec2d, map_ec2d & + ) + + @assertEqual(u_answer, u_mod_levs, tol) + @assertEqual(v_answer, v_mod_levs, tol) + @assertEqual(theta_answer, theta_mod_levs, tol) + + end subroutine test_all + +end module nudging_ecmwf_vert_interp_kernel_mod_test diff --git a/science/linear/rose-meta/lfric-linear/versions.py b/science/linear/rose-meta/lfric-linear/versions.py index e33d8e39bc..3e9051d6c4 100644 --- a/science/linear/rose-meta/lfric-linear/versions.py +++ b/science/linear/rose-meta/lfric-linear/versions.py @@ -349,3 +349,82 @@ def upgrade(self, config, meta_config=None): ) return config, self.reports + + +class vn32_t670(MacroUpgrade): + """Upgrade macro for ticket #670 by Thomas Bendall.""" + + BEFORE_TAG = "vn3.2_t400" + AFTER_TAG = "vn3.2_t670" + + def upgrade(self, config, meta_config=None): + # Commands From: rose-meta/lfric-gungho + # Add new nudging namelist options + self.add_setting( + config, ["namelist:nudging", "nudging_method"], "'convolution'" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_theta"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_u"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_relax_time_v"], "6.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_start"], "12.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_spinup_end"], "24.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_stop_time"], "10000.0" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_bottom"], "5" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_level_taper_top"], "52" + ) + self.add_setting( + config, ["namelist:nudging", "nudging_min_tropopause_level"], "48" + ) + self.change_setting_value( + config, ["namelist:nudging", "nudging_level_bottom"], "6" + ) + self.add_setting( + config, ["namelist:nudging", "num_ref_data_levels"], "137" + ) + self.add_setting(config, ["namelist:nudging", "spectral_kmax"], "20") + self.add_setting(config, ["namelist:nudging", "spectral_kmin"], "2") + self.add_setting( + config, ["namelist:nudging", "spectral_stencil_extent"], "12" + ) + self.add_setting( + config, ["namelist:nudging", "spectral_envelope_width"], "0.1" + ) + # Remove retired settings + self.remove_setting(config, ["namelist:nudging", "nudging_source"]) + self.remove_setting( + config, ["namelist:nudging", "nudging_width_bottom"] + ) + self.remove_setting(config, ["namelist:nudging", "nudging_width_top"]) + self.remove_setting(config, ["namelist:nudging", "nudge_data_levels"]) + # If nudging_mesh_name is still the default '' value, set it to + # match dynamics_mesh_name + nudging_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "nudging_mesh_name"] + ) + if nudging_mesh_name == "''": + dynamics_mesh_name = self.get_setting_value( + config, ["namelist:multires_coupling", "dynamics_mesh_name"] + ) + if dynamics_mesh_name is not None: + self.change_setting_value( + config, + ["namelist:multires_coupling", "nudging_mesh_name"], + dynamics_mesh_name, + ) + + return config, self.reports