Skip to content

Commit

Permalink
Ensemble Move short-task (#714)
Browse files Browse the repository at this point in the history
The Ensemble class is moved to /builders/ in this PR. The strategies module is moved to/builder/utils in this PR. as_jobs is changed to build_jobs. Documentation is updated.

[ reviewed by @mellis13 ]
[ committed by @amandarichardsonn ]
  • Loading branch information
amandarichardsonn authored Sep 23, 2024
1 parent f748789 commit 4faf95c
Show file tree
Hide file tree
Showing 19 changed files with 128 additions and 39 deletions.
3 changes: 2 additions & 1 deletion smartsim/_core/control/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@

from smartsim.entity._mock import Mock

from ...builders import Ensemble
from ...database import FeatureStore
from ...entity import Application, Ensemble, FSNode, SmartSimEntity
from ...entity import Application, FSNode, SmartSimEntity
from ...error import SmartSimError
from ..config import CONFIG
from ..utils import helpers as _helpers
Expand Down
3 changes: 2 additions & 1 deletion smartsim/_core/launcher/step/slurm_step.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
import typing as t
from shlex import split as sh_split

from ....entity import Application, Ensemble, FSNode
from ....builders import Ensemble
from ....entity import Application, FSNode
from ....error import AllocationError
from ....log import get_logger
from ....settings import RunSettings, SbatchSettings, Singularity, SrunSettings
Expand Down
3 changes: 2 additions & 1 deletion smartsim/_core/launcher/step/step.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
from smartsim._core.config import CONFIG
from smartsim.error.errors import SmartSimError, UnproxyableStepError

from ....entity import Application, Ensemble, FSNode
from ....builders import Ensemble
from ....entity import Application, FSNode
from ....log import get_logger
from ....settings import RunSettings, SettingsBase
from ...utils.helpers import encode_cmd, get_base_36_repr
Expand Down
3 changes: 2 additions & 1 deletion smartsim/_core/utils/serialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@

if t.TYPE_CHECKING:
from smartsim._core.control.manifest import LaunchedManifest as _Manifest
from smartsim.builders import Ensemble
from smartsim.database.orchestrator import FeatureStore
from smartsim.entity import Application, Ensemble, FSNode
from smartsim.entity import Application, FSNode
from smartsim.entity.dbobject import FSModel, FSScript
from smartsim.settings.base import BatchSettings, RunSettings

Expand Down
28 changes: 28 additions & 0 deletions smartsim/builders/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# BSD 2-Clause License
#
# Copyright (c) 2021-2024, Hewlett Packard Enterprise
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

from .ensemble import Ensemble
from .utils.strategies import ParamSet
70 changes: 62 additions & 8 deletions smartsim/entity/ensemble.py → smartsim/builders/ensemble.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,20 @@
import os.path
import typing as t

from smartsim.entity import entity, strategies
from smartsim.builders.utils import strategies
from smartsim.builders.utils.strategies import ParamSet
from smartsim.entity import entity
from smartsim.entity.application import Application
from smartsim.entity.files import EntityFiles
from smartsim.entity.strategies import ParamSet
from smartsim.launchable.job import Job

if t.TYPE_CHECKING:
from smartsim.settings.launch_settings import LaunchSettings


class Ensemble(entity.CompoundEntity):
"""Entity to help parameterize the creation multiple application
instances.
"""An Ensemble is a builder class that parameterizes the creation of multiple
Applications.
"""

def __init__(
Expand All @@ -59,7 +60,60 @@ def __init__(
max_permutations: int = -1,
replicas: int = 1,
) -> None:
"""Initialize an ``Ensemble`` of application instances
"""Initialize an ``Ensemble`` of Application instances
An Ensemble can be tailored to align with one of the following
creation strategies: parameter expansion or replicas.
**Parameter Expansion**
Parameter expansion allows users to assign different parameter values to
multiple Applications. This is done by specifying input to `Ensemble.file_parameters`,
`Ensemble.exe_arg_parameters` and `Ensemble.permutation_strategy`. The `permutation_strategy`
argument accepts three options:
1. "all_perm": Generates all possible parameter permutations for exhaustive exploration.
2. "step": Collects identically indexed values across parameter lists to create parameter sets.
3. "random": Enables random selection from predefined parameter spaces.
The example below demonstrates creating an Ensemble via parameter expansion, resulting in
the creation of two Applications:
.. highlight:: python
.. code-block:: python
file_params={"SPAM": ["a", "b"], "EGGS": ["c", "d"]}
exe_arg_parameters = {"EXE": [["a"], ["b", "c"]], "ARGS": [["d"], ["e", "f"]]}
ensemble = Ensemble(name="name",exe="python",exe_arg_parameters=exe_arg_parameters,
file_parameters=file_params,permutation_strategy="step")
This configuration will yield the following permutations:
.. highlight:: python
.. code-block:: python
[ParamSet(params={'SPAM': 'a', 'EGGS': 'c'}, exe_args={'EXE': ['a'], 'ARGS': ['d']}),
ParamSet(params={'SPAM': 'b', 'EGGS': 'd'}, exe_args={'EXE': ['b', 'c'], 'ARGS': ['e', 'f']})]
Each ParamSet contains the parameters assigned from file_params and the corresponding executable
arguments from exe_arg_parameters.
**Replication**
The replication strategy involves creating identical Applications within an Ensemble.
This is achieved by specifying the `replicas` argument in the Ensemble.
For example, by applying the `replicas` argument to the previous parameter expansion
example, we can double our Application output:
.. highlight:: python
.. code-block:: python
file_params={"SPAM": ["a", "b"], "EGGS": ["c", "d"]}
exe_arg_parameters = {"EXE": [["a"], ["b", "c"]], "ARGS": [["d"], ["e", "f"]]}
ensemble = Ensemble(name="name",exe="python",exe_arg_parameters=exe_arg_parameters,
file_parameters=file_params,permutation_strategy="step", replicas=2)
This configuration will result in each ParamSet being replicated, effectively doubling
the number of Applications created.
:param name: name of the ensemble
:param exe: executable to run
Expand Down Expand Up @@ -259,7 +313,7 @@ def _create_applications(self) -> tuple[Application, ...]:
for i, permutation in enumerate(permutations_)
)

def as_jobs(self, settings: LaunchSettings) -> tuple[Job, ...]:
def build_jobs(self, settings: LaunchSettings) -> tuple[Job, ...]:
"""Expand an Ensemble into a list of deployable Jobs and apply
identical LaunchSettings to each Job.
Expand All @@ -281,9 +335,9 @@ def as_jobs(self, settings: LaunchSettings) -> tuple[Job, ...]:
# Initialize the Ensemble
ensemble = Ensemble("my_name", "echo", "hello world", replicas=3)
# Expand Ensemble into Jobs
ensemble_as_jobs = ensemble.as_jobs(my_launch_settings)
ensemble_as_jobs = ensemble.build_jobs(my_launch_settings)
By calling `as_jobs` on `ensemble`, three Jobs are returned because
By calling `build_jobs` on `ensemble`, three Jobs are returned because
three replicas were specified. Each Job will have the provided LaunchSettings.
:param settings: LaunchSettings to apply to each Job
Expand Down
File renamed without changes.
1 change: 0 additions & 1 deletion smartsim/entity/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,5 @@
from .application import Application
from .dbnode import FSNode
from .dbobject import *
from .ensemble import Ensemble
from .entity import SmartSimEntity, TelemetryConfiguration
from .files import TaggedFilesHierarchy
4 changes: 2 additions & 2 deletions smartsim/entity/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,6 @@ class CompoundEntity(abc.ABC):
"""

@abc.abstractmethod
def as_jobs(self, settings: LaunchSettings) -> t.Collection[Job]: ...
def build_jobs(self, settings: LaunchSettings) -> t.Collection[Job]: ...
def as_job_group(self, settings: LaunchSettings) -> JobGroup:
return JobGroup(list(self.as_jobs(settings)))
return JobGroup(list(self.build_jobs(settings)))
2 changes: 1 addition & 1 deletion tests/_legacy/test_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@

from smartsim._core.control.controller import Controller
from smartsim._core.launcher.step import Step
from smartsim.builders.ensemble import Ensemble
from smartsim.database.orchestrator import FeatureStore
from smartsim.entity.ensemble import Ensemble
from smartsim.settings.slurmSettings import SbatchSettings, SrunSettings

controller = Controller()
Expand Down
2 changes: 1 addition & 1 deletion tests/_legacy/test_controller_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@
from smartsim._core.control import Controller, Manifest
from smartsim._core.launcher.step import Step
from smartsim._core.launcher.step.dragon_step import DragonStep
from smartsim.builders.ensemble import Ensemble
from smartsim.database import FeatureStore
from smartsim.entity import Application
from smartsim.entity.ensemble import Ensemble
from smartsim.error import SmartSimError, SSUnsupportedError
from smartsim.error.errors import SSUnsupportedError
from smartsim.settings import RunSettings, SrunSettings
Expand Down
3 changes: 2 additions & 1 deletion tests/_legacy/test_ensemble.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
import pytest

from smartsim import Experiment
from smartsim.entity import Application, Ensemble
from smartsim.builders import Ensemble
from smartsim.entity import Application
from smartsim.error import EntityExistsError, SSUnsupportedError, UserStrategyError
from smartsim.settings import RunSettings

Expand Down
3 changes: 2 additions & 1 deletion tests/_legacy/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
from smartsim import Experiment
from smartsim._core.control.manifest import LaunchedManifestBuilder
from smartsim._core.launcher.step import SbatchStep, SrunStep
from smartsim.entity import Application, Ensemble
from smartsim.builders import Ensemble
from smartsim.entity import Application
from smartsim.error import EntityExistsError, SSUnsupportedError
from smartsim.settings import RunSettings, SbatchSettings, SrunSettings
from smartsim.settings.mpiSettings import _BaseMPISettings
Expand Down
2 changes: 1 addition & 1 deletion tests/_legacy/test_output_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@
from smartsim._core.config import CONFIG
from smartsim._core.control.controller import Controller, _AnonymousBatchJob
from smartsim._core.launcher.step import Step
from smartsim.builders.ensemble import Ensemble
from smartsim.database.orchestrator import FeatureStore
from smartsim.entity.application import Application
from smartsim.entity.ensemble import Ensemble
from smartsim.settings.base import RunSettings
from smartsim.settings.slurmSettings import SbatchSettings, SrunSettings

Expand Down
3 changes: 2 additions & 1 deletion tests/_legacy/test_smartredis.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@

from smartsim import Experiment
from smartsim._core.utils import installed_redisai_backends
from smartsim.builders import Ensemble
from smartsim.database import FeatureStore
from smartsim.entity import Application, Ensemble
from smartsim.entity import Application
from smartsim.status import JobStatus

# The tests in this file belong to the group_b group
Expand Down
2 changes: 1 addition & 1 deletion tests/_legacy/test_symlinking.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@
from smartsim import Experiment
from smartsim._core.config import CONFIG
from smartsim._core.control.controller import Controller, _AnonymousBatchJob
from smartsim.builders.ensemble import Ensemble
from smartsim.database.orchestrator import FeatureStore
from smartsim.entity.application import Application
from smartsim.entity.ensemble import Ensemble
from smartsim.settings.base import RunSettings
from smartsim.settings.slurmSettings import SbatchSettings, SrunSettings

Expand Down
16 changes: 8 additions & 8 deletions tests/test_ensemble.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@

import pytest

from smartsim.entity.ensemble import Ensemble
from smartsim.builders.ensemble import Ensemble
from smartsim.builders.utils.strategies import ParamSet
from smartsim.entity.files import EntityFiles
from smartsim.entity.strategies import ParamSet
from smartsim.settings.launch_settings import LaunchSettings

pytestmark = pytest.mark.group_a
Expand Down Expand Up @@ -109,7 +109,7 @@ def test_ensemble_user_created_strategy(mock_launcher_settings, test_dir):
"echo",
("hello", "world"),
permutation_strategy=user_created_function,
).as_jobs(mock_launcher_settings)
).build_jobs(mock_launcher_settings)
assert len(jobs) == 1


Expand All @@ -125,7 +125,7 @@ def test_ensemble_without_any_members_raises_when_cast_to_jobs(
permutation_strategy="random",
max_permutations=30,
replicas=0,
).as_jobs(mock_launcher_settings)
).build_jobs(mock_launcher_settings)


def test_strategy_error_raised_if_a_strategy_that_dne_is_requested(test_dir):
Expand Down Expand Up @@ -208,7 +208,7 @@ def test_all_perm_strategy(
permutation_strategy="all_perm",
max_permutations=max_perms,
replicas=replicas,
).as_jobs(mock_launcher_settings)
).build_jobs(mock_launcher_settings)
assert len(jobs) == expected_num_jobs


Expand All @@ -222,7 +222,7 @@ def test_all_perm_strategy_contents():
permutation_strategy="all_perm",
max_permutations=16,
replicas=1,
).as_jobs(mock_launcher_settings)
).build_jobs(mock_launcher_settings)
assert len(jobs) == 16


Expand Down Expand Up @@ -262,7 +262,7 @@ def test_step_strategy(
permutation_strategy="step",
max_permutations=max_perms,
replicas=replicas,
).as_jobs(mock_launcher_settings)
).build_jobs(mock_launcher_settings)
assert len(jobs) == expected_num_jobs


Expand Down Expand Up @@ -301,5 +301,5 @@ def test_random_strategy(
permutation_strategy="random",
max_permutations=max_perms,
replicas=replicas,
).as_jobs(mock_launcher_settings)
).build_jobs(mock_launcher_settings)
assert len(jobs) == expected_num_jobs
15 changes: 8 additions & 7 deletions tests/test_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@

from smartsim import Experiment
from smartsim._core.generation.generator import Generator
from smartsim.entity import Application, Ensemble
from smartsim.builders import Ensemble
from smartsim.entity import Application
from smartsim.entity.files import EntityFiles
from smartsim.launchable import Job
from smartsim.settings import LaunchSettings
Expand Down Expand Up @@ -226,7 +227,7 @@ def test_exp_private_generate_method_ensemble(test_dir, wlmutils, generator_inst
"""Test that Job directory was created from Experiment."""
ensemble = Ensemble("ensemble-name", "echo", replicas=2)
launch_settings = LaunchSettings(wlmutils.get_test_launcher())
job_list = ensemble.as_jobs(launch_settings)
job_list = ensemble.build_jobs(launch_settings)
exp = Experiment(name="exp_name", exp_path=test_dir)
for i, job in enumerate(job_list):
job_run_path, _, _ = exp._generate(generator_instance, job, i)
Expand All @@ -239,7 +240,7 @@ def test_exp_private_generate_method_ensemble(test_dir, wlmutils, generator_inst
def test_generate_ensemble_directory(wlmutils, generator_instance):
ensemble = Ensemble("ensemble-name", "echo", replicas=2)
launch_settings = LaunchSettings(wlmutils.get_test_launcher())
job_list = ensemble.as_jobs(launch_settings)
job_list = ensemble.build_jobs(launch_settings)
for i, job in enumerate(job_list):
# Call Generator.generate_job
path, _, _ = generator_instance.generate_job(job, i)
Expand All @@ -263,7 +264,7 @@ def test_generate_ensemble_directory_start(test_dir, wlmutils, monkeypatch):
)
ensemble = Ensemble("ensemble-name", "echo", replicas=2)
launch_settings = LaunchSettings(wlmutils.get_test_launcher())
job_list = ensemble.as_jobs(launch_settings)
job_list = ensemble.build_jobs(launch_settings)
exp = Experiment(name="exp_name", exp_path=test_dir)
exp.start(*job_list)
run_dir = listdir(test_dir)
Expand All @@ -285,7 +286,7 @@ def test_generate_ensemble_copy(test_dir, wlmutils, monkeypatch, get_gen_copy_di
"ensemble-name", "echo", replicas=2, files=EntityFiles(copy=get_gen_copy_dir)
)
launch_settings = LaunchSettings(wlmutils.get_test_launcher())
job_list = ensemble.as_jobs(launch_settings)
job_list = ensemble.build_jobs(launch_settings)
exp = Experiment(name="exp_name", exp_path=test_dir)
exp.start(*job_list)
run_dir = listdir(test_dir)
Expand All @@ -310,7 +311,7 @@ def test_generate_ensemble_symlink(
files=EntityFiles(symlink=get_gen_symlink_dir),
)
launch_settings = LaunchSettings(wlmutils.get_test_launcher())
job_list = ensemble.as_jobs(launch_settings)
job_list = ensemble.build_jobs(launch_settings)
exp = Experiment(name="exp_name", exp_path=test_dir)
exp.start(*job_list)
run_dir = listdir(test_dir)
Expand Down Expand Up @@ -341,7 +342,7 @@ def test_generate_ensemble_configure(
file_parameters=params,
)
launch_settings = LaunchSettings(wlmutils.get_test_launcher())
job_list = ensemble.as_jobs(launch_settings)
job_list = ensemble.build_jobs(launch_settings)
exp = Experiment(name="exp_name", exp_path=test_dir)
id = exp.start(*job_list)
run_dir = listdir(test_dir)
Expand Down
Loading

0 comments on commit 4faf95c

Please sign in to comment.