From b9f70c1847b4a9b1204d97d6d11039a61e8cf690 Mon Sep 17 00:00:00 2001 From: Christina Holt <56881914+christinaholtNOAA@users.noreply.github.com> Date: Tue, 26 Nov 2024 09:06:36 -0700 Subject: [PATCH] Add global option for MPAS driver (#660) --- .../cli/drivers/mpas/show-schema.out | 32 +++++++++---------- .../user_guide/yaml/components/mpas.rst | 11 +++++-- .../user_guide/yaml/components/mpas_init.rst | 10 +++--- docs/shared/mpas.yaml | 1 + src/uwtools/drivers/mpas.py | 19 ++++++++++- src/uwtools/drivers/mpas_base.py | 10 +----- src/uwtools/drivers/mpas_init.py | 15 +++++++++ .../resources/jsonschema/mpas.jsonschema | 24 ++++++++++++++ src/uwtools/tests/drivers/test_mpas.py | 11 +++++-- src/uwtools/tests/test_schemas.py | 18 ++++++++++- 10 files changed, 114 insertions(+), 37 deletions(-) diff --git a/docs/sections/user_guide/cli/drivers/mpas/show-schema.out b/docs/sections/user_guide/cli/drivers/mpas/show-schema.out index e9aba87d1..d242e5cdf 100644 --- a/docs/sections/user_guide/cli/drivers/mpas/show-schema.out +++ b/docs/sections/user_guide/cli/drivers/mpas/show-schema.out @@ -2,19 +2,19 @@ "properties": { "mpas": { "additionalProperties": false, - "properties": { - "execution": { - "additionalProperties": false, - "properties": { - "batchargs": { - "additionalProperties": true, - "properties": { - "cores": { - "type": "integer" - }, - "debug": { - "type": "boolean" - }, - "exclusive": { - "type": "boolean" - }, + "allOf": [ + { + "if": { + "properties": { + "domain": { + "const": "regional" + } + } + }, + "then": { + "required": [ + "lateral_boundary_conditions" + ] + } + } + ], diff --git a/docs/sections/user_guide/yaml/components/mpas.rst b/docs/sections/user_guide/yaml/components/mpas.rst index 2f42b2232..115fc07a7 100644 --- a/docs/sections/user_guide/yaml/components/mpas.rst +++ b/docs/sections/user_guide/yaml/components/mpas.rst @@ -17,15 +17,20 @@ An MPAS build provides prototype versions of certain required runtime files. Her UW YAML for the ``mpas:`` Block ------------------------------- +domain: +^^^^^^^ + +Accepted values are ``global`` and ``regional``. + execution: ^^^^^^^^^^ See :ref:`this page ` for details. -boundary_conditions: -^^^^^^^^^^^^^^^^^^^^ +lateral_boundary_conditions: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Describes the boundary condition files needed for the forecast. These will be the output from the ``init_atmosphere`` executable, which may be run using the ``mpas_init`` UW driver. Please see its documentation :ref:`here `. +Describes the boundary condition files needed for a regional forecast. This section is not used when ``domain`` is set to ``global``. These will be the output from the ``init_atmosphere`` executable, which may be run using the ``mpas_init`` UW driver. Please see its documentation :ref:`here `. **interval_hours:** diff --git a/docs/sections/user_guide/yaml/components/mpas_init.rst b/docs/sections/user_guide/yaml/components/mpas_init.rst index 0be656b2a..94dbe336f 100644 --- a/docs/sections/user_guide/yaml/components/mpas_init.rst +++ b/docs/sections/user_guide/yaml/components/mpas_init.rst @@ -17,11 +17,6 @@ An MPAS build provides prototype versions of certain required runtime files. Her UW YAML for the ``mpas_init:`` Block ------------------------------------ -execution: -^^^^^^^^^^ - -See :ref:`this page ` for details. - boundary_conditions: ^^^^^^^^^^^^^^^^^^^^ @@ -43,6 +38,11 @@ Describes the boundary condition files needed for the forecast. These will most An absolute path to the output of the ``ungrib`` tool that will be used to prepare MPAS-ready initial and lateral boundary conditions. The names of the files are specified in the ``streams.init_atmosphere`` XML file, and may be specified in the ``streams: values:`` block of the driver YAML. +execution: +^^^^^^^^^^ + +See :ref:`this page ` for details. + files_to_copy: ^^^^^^^^^^^^^^ diff --git a/docs/shared/mpas.yaml b/docs/shared/mpas.yaml index 93ad1b0ea..67fd699d0 100644 --- a/docs/shared/mpas.yaml +++ b/docs/shared/mpas.yaml @@ -1,4 +1,5 @@ mpas: + domain: regional execution: batchargs: cores: 32 diff --git a/src/uwtools/drivers/mpas.py b/src/uwtools/drivers/mpas.py index 96005a78f..2ab15c9d0 100644 --- a/src/uwtools/drivers/mpas.py +++ b/src/uwtools/drivers/mpas.py @@ -5,7 +5,7 @@ from datetime import timedelta from pathlib import Path -from iotaa import asset, task +from iotaa import asset, task, tasks from uwtools.config.formats.nml import NMLConfig from uwtools.drivers.mpas_base import MPASBase @@ -66,6 +66,23 @@ def namelist_file(self): schema=self.namelist_schema(), ) + @tasks + def provisioned_rundir(self): + """ + Run directory provisioned with all required content. + """ + yield self.taskname("provisioned run directory") + required = [ + self.files_copied(), + self.files_linked(), + self.namelist_file(), + self.runscript(), + self.streams_file(), + ] + if self.config["domain"] == "regional": + required.append(self.boundary_files()) + yield required + # Public helper methods @classmethod diff --git a/src/uwtools/drivers/mpas_base.py b/src/uwtools/drivers/mpas_base.py index 2f7a85804..0da4bf25b 100644 --- a/src/uwtools/drivers/mpas_base.py +++ b/src/uwtools/drivers/mpas_base.py @@ -57,19 +57,11 @@ def namelist_file(self): """ @tasks + @abstractmethod def provisioned_rundir(self): """ Run directory provisioned with all required content. """ - yield self.taskname("provisioned run directory") - yield [ - self.boundary_files(), - self.files_copied(), - self.files_linked(), - self.namelist_file(), - self.runscript(), - self.streams_file(), - ] @task def streams_file(self): diff --git a/src/uwtools/drivers/mpas_init.py b/src/uwtools/drivers/mpas_init.py index be4ec4320..d6fa09a33 100644 --- a/src/uwtools/drivers/mpas_init.py +++ b/src/uwtools/drivers/mpas_init.py @@ -68,6 +68,21 @@ def namelist_file(self): schema=self.namelist_schema(), ) + @tasks + def provisioned_rundir(self): + """ + Run directory provisioned with all required content. + """ + yield self.taskname("provisioned run directory") + yield [ + self.boundary_files(), + self.files_copied(), + self.files_linked(), + self.namelist_file(), + self.runscript(), + self.streams_file(), + ] + # Public helper methods @classmethod diff --git a/src/uwtools/resources/jsonschema/mpas.jsonschema b/src/uwtools/resources/jsonschema/mpas.jsonschema index b3aabb4f2..8d1d03f80 100644 --- a/src/uwtools/resources/jsonschema/mpas.jsonschema +++ b/src/uwtools/resources/jsonschema/mpas.jsonschema @@ -2,7 +2,30 @@ "properties": { "mpas": { "additionalProperties": false, + "allOf": [ + { + "if": { + "properties": { + "domain": { + "const": "regional" + } + } + }, + "then": { + "required": [ + "lateral_boundary_conditions" + ] + } + } + ], "properties": { + "domain": { + "enum": [ + "global", + "regional" + ], + "type": "string" + }, "execution": { "$ref": "urn:uwtools:execution-parallel" }, @@ -73,6 +96,7 @@ } }, "required": [ + "domain", "execution", "namelist", "rundir", diff --git a/src/uwtools/tests/drivers/test_mpas.py b/src/uwtools/tests/drivers/test_mpas.py index ef225ef4b..776fd8ff3 100644 --- a/src/uwtools/tests/drivers/test_mpas.py +++ b/src/uwtools/tests/drivers/test_mpas.py @@ -51,6 +51,7 @@ def streams_file(config, driverobj, drivername): def config(tmp_path): return { "mpas": { + "domain": "regional", "execution": { "executable": "atmosphere_model", "batchargs": { @@ -233,7 +234,9 @@ def test_MPAS_output(driverobj): assert str(e.value) == "The output() method is not yet implemented for this driver" -def test_MPAS_provisioned_rundir(driverobj): +@mark.parametrize("domain", ("global", "regional")) +def test_MPAS_provisioned_rundir(domain, driverobj): + driverobj._config["domain"] = domain with patch.multiple( driverobj, boundary_files=D, @@ -244,8 +247,12 @@ def test_MPAS_provisioned_rundir(driverobj): streams_file=D, ) as mocks: driverobj.provisioned_rundir() + excluded = ["boundary_files"] if domain == "global" else [] for m in mocks: - mocks[m].assert_called_once_with() + if m in excluded: + mocks[m].assert_not_called() + else: + mocks[m].assert_called_once_with() def test_MPAS_streams_file(config, driverobj): diff --git a/src/uwtools/tests/test_schemas.py b/src/uwtools/tests/test_schemas.py index 83e81a307..0dea9b146 100644 --- a/src/uwtools/tests/test_schemas.py +++ b/src/uwtools/tests/test_schemas.py @@ -1254,7 +1254,9 @@ def test_schema_makedirs(): def test_schema_mpas(mpas_streams): config = { + "domain": "regional", "execution": {"executable": "atmosphere_model"}, + "lateral_boundary_conditions": {"interval_hours": 3, "offset": 3, "path": "/path/to/lbcs"}, "namelist": {"base_file": "path/to/simple.nml", "validate": True}, "rundir": "path/to/rundir", "streams": mpas_streams, @@ -1263,10 +1265,18 @@ def test_schema_mpas(mpas_streams): # Basic correctness: assert not errors(config) # All top-level keys are required: - for key in ("execution", "namelist", "rundir", "streams"): + for key in ("domain", "execution", "namelist", "rundir", "streams"): assert f"'{key}' is a required property" in errors(with_del(config, key)) # Additional top-level keys are not allowed: assert "Additional properties are not allowed" in errors({**config, "foo": "bar"}) + # lateral_boundary_conditions are optional when domain is global: + assert not errors({**with_del(config, "lateral_boundary_conditions"), "domain": "global"}) + + +def test_schema_mpas_domain(mpas_prop): + errors = mpas_prop("domain") + # There is a fixed set of domain values: + assert "'foo' is not one of ['global', 'regional']" in errors("foo") def test_schema_mpas_lateral_boundary_conditions(mpas_prop): @@ -1348,6 +1358,12 @@ def test_schema_mpas_rundir(mpas_prop): def test_schema_mpas_init(mpas_streams): config = { + "boundary_conditions": { + "interval_hours": 6, + "length": 12, + "offset": 0, + "path": "/path/to/bcs", + }, "execution": {"executable": "mpas_init"}, "namelist": {"base_file": "path/to/simple.nml", "validate": True}, "rundir": "path/to/rundir",