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 @@
+
+
+
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