diff --git a/docs/describing_runs.rst b/docs/describing_runs.rst index 4882a98..cd4a2ec 100644 --- a/docs/describing_runs.rst +++ b/docs/describing_runs.rst @@ -4,7 +4,7 @@ Describing runs Once we have a model structure, a configuration for a specific scenario, and programs to do the calculations, there's only one thing left to configure and that is the run itself. In yMMSL, we can specify the compute resources to use for each program, and if -the programs support then it we can configure when to checkpoint the simulation. +the programs support it we can configure when to checkpoint the simulation. Resources ````````` @@ -23,12 +23,17 @@ threads: :caption: Resources for threaded processes resources: - macro: + model.macro: threads: 1 - micro: + model.micro: threads: 8 +Note that the component is designated by the name of the top-level model, followed by +the component name. If the component is inside of a submodel, then you have to specify +the path all the way from the top, e.g. ``model.micro.c1`` if ``micro`` is implemented +by a submodel that has a component named ``c1``. + If no resource is defined for a non-MPI component, a default of 1 thread will be assigned to it at runtime (e.g. by MUSCLE3). So, you could also define: @@ -36,7 +41,7 @@ be assigned to it at runtime (e.g. by MUSCLE3). So, you could also define: :caption: Resources for threaded processes resources: - micro: + model.micro: threads: 8 On the Python side, this is represented by :class:`.ymmsl.v0_2.ThreadedResReq` (short @@ -53,9 +58,9 @@ processes, and optionally the number of threads per MPI process: :caption: Core-based resources for MPI components resources: - macro: + model.macro: mpi_processes: 32 - micro: + model.micro: mpi_processes: 16 threads_per_mpi_process: 8 @@ -69,11 +74,11 @@ already specify them as follows: :caption: Node-based resources for MPI components resources: - macro: + model.macro: nodes: 8 mpi_processes_per_node: 4 threads_per_mpi_process: 8 - micro: + model.micro: nodes: 1 mpi_processes_per_node: 16 diff --git a/ymmsl/conversion/convert_v0_1_to_v0_2.py b/ymmsl/conversion/convert_v0_1_to_v0_2.py index 975bf95..2b2bcf1 100644 --- a/ymmsl/conversion/convert_v0_1_to_v0_2.py +++ b/ymmsl/conversion/convert_v0_1_to_v0_2.py @@ -1,5 +1,5 @@ from copy import deepcopy -from typing import Dict, List, Optional +from typing import Dict, List, MutableMapping, Optional import warnings import ymmsl.v0_1 as v0_1 @@ -30,7 +30,7 @@ def convert_v0_1_to_v0_2(config: v0_1.PartialConfiguration) -> v0_2.Configuratio ' incorrect wiring easier to debug. While there, add a description too!' ) - resources = deepcopy(config.resources) + resources = convert_resources(config.resources, models) checkpoints = deepcopy(config.checkpoints) resume = deepcopy(config.resume) @@ -162,3 +162,26 @@ def convert_ports(ports: v0_1.Ports) -> v0_2.Ports: list(map(str, ports.o_i)), list(map(str, ports.s)), list(map(str, ports.o_f))) + + +def convert_resources( + resources: MutableMapping[v0_1.Reference, v0_1.ResourceRequirements], + models: Optional[List[v0_2.Model]] + ) -> MutableMapping[v0_2.Reference, v0_2.ResourceRequirements]: + if not models: + warnings.warn( + 'When specifying resources in yMMSL v0.2, component names must be' + ' prefixed with the name of the top (outermost) model, e.g. as' + ' my_model.my_component rather than just my_component. This file' + ' does not contain a model, so its name cannot be added automatically.' + ' Please add the model name yourself.') + return deepcopy(resources) + else: + mname = models[0].name + result = dict() + for res_req in resources.values(): + new_res_req = deepcopy(res_req) + new_res_req.name = mname + res_req.name + result[new_res_req.name] = new_res_req + + return result diff --git a/ymmsl/conversion/tests/test_convert_v0_1_to_v0_2.py b/ymmsl/conversion/tests/test_convert_v0_1_to_v0_2.py index 378adb4..8f39a7c 100644 --- a/ymmsl/conversion/tests/test_convert_v0_1_to_v0_2.py +++ b/ymmsl/conversion/tests/test_convert_v0_1_to_v0_2.py @@ -15,6 +15,7 @@ @pytest.mark.filterwarnings('ignore:Comments.*') +@pytest.mark.filterwarnings('ignore:When specifying resources.*') def test_convert_simple_config(empty_config: v0_1.PartialConfiguration) -> None: v2 = convert_v0_1_to_v0_2(empty_config) @@ -66,16 +67,16 @@ def test_convert_full_config(full_config: v0_1.Configuration) -> None: assert v2.resources is not full_config.resources assert len(v2.resources) == 2 - ra = v2.resources[Ref2('A')] + ra = v2.resources[Ref2('test_model.A')] assert ra is not full_config.resources[Ref1('A')] assert isinstance(ra, v0_2.ThreadedResReq) - assert ra.name == 'A' + assert ra.name == 'test_model.A' assert ra.threads == 8 - rb = v2.resources[Ref2('B')] + rb = v2.resources[Ref2('test_model.B')] assert rb is not full_config.resources[Ref1('B')] assert isinstance(rb, v0_2.MPICoresResReq) - assert rb.name == 'B' + assert rb.name == 'test_model.B' assert rb.mpi_processes == 4 assert rb.threads_per_mpi_process == 8 diff --git a/ymmsl/tests/test_load_as.py b/ymmsl/tests/test_load_as.py index 23437f7..936a947 100644 --- a/ymmsl/tests/test_load_as.py +++ b/ymmsl/tests/test_load_as.py @@ -14,6 +14,7 @@ def test_load_as_v0_1(test_yaml1: str) -> None: @pytest.mark.filterwarnings('ignore:Comments.*') +@pytest.mark.filterwarnings('ignore:When specifying resources.*') def test_load_as_v0_2(test_yaml1: str) -> None: config = load_as(v0_2.Configuration, test_yaml1) assert isinstance(config, v0_2.Configuration)