Skip to content

Commit

Permalink
Short task to update BatchSettings (#704)
Browse files Browse the repository at this point in the history
This PR makes small updates to the existing BatchSettings
implementation.

[ reviewed by @mellis13 @MattToast ]
[ committed by @amandarichardsonn ]
  • Loading branch information
amandarichardsonn authored Sep 20, 2024
1 parent 3bfdff9 commit f748789
Show file tree
Hide file tree
Showing 18 changed files with 413 additions and 183 deletions.
54 changes: 36 additions & 18 deletions smartsim/entity/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@


class Application(SmartSimEntity):
"""The Application class enables users to execute computational tasks in an
Experiment workflow, such as launching compiled applications, running scripts,
or performing general computational operations.
Applications are designed to be added to Jobs, where LaunchSettings are also
provided to inject launcher-specific behavior into the Job.
"""

def __init__(
self,
name: str,
Expand All @@ -56,6 +64,16 @@ def __init__(
) -> None:
"""Initialize an ``Application``
Applications require a name and an executable. Optionally, users may provide
executable arguments, files and file parameters. To create a simple Application
that echos `Hello World!`, consider the example below:
.. highlight:: python
.. code-block:: python
# Create an application that runs the 'echo' command
my_app = Application(name="my_app", exe="echo", exe_args="Hello World!")
:param name: name of the application
:param exe: executable to run
:param exe_args: executable arguments
Expand Down Expand Up @@ -83,33 +101,33 @@ def __init__(

@property
def exe(self) -> str:
"""Return executable to run.
"""Return the executable.
:returns: application executable to run
:return: the executable
"""
return self._exe

@exe.setter
def exe(self, value: str) -> None:
"""Set executable to run.
"""Set the executable.
:param value: executable to run
:param value: the executable
"""
self._exe = copy.deepcopy(value)

@property
def exe_args(self) -> t.MutableSequence[str]:
"""Return a list of attached executable arguments.
"""Return the executable arguments.
:returns: application executable arguments
:return: the executable arguments
"""
return self._exe_args

@exe_args.setter
def exe_args(self, value: t.Union[str, t.Sequence[str], None]) -> None:
"""Set the executable arguments.
:param value: executable arguments
:param value: the executable arguments
"""
self._exe_args = self._build_exe_args(value)

Expand All @@ -122,44 +140,44 @@ def add_exe_args(self, args: t.Union[str, t.List[str], None]) -> None:
self._exe_args.extend(args)

@property
def files(self) -> t.Optional[EntityFiles]:
"""Return files to be copied, symlinked, and/or configured prior to
execution.
def files(self) -> t.Union[EntityFiles, None]:
"""Return attached EntityFiles object.
:returns: files
:return: the EntityFiles object of files to be copied, symlinked,
and/or configured prior to execution
"""
return self._files

@files.setter
def files(self, value: t.Optional[EntityFiles]) -> None:
"""Set files to be copied, symlinked, and/or configured prior to
execution.
"""Set the EntityFiles object.
:param value: files
:param value: the EntityFiles object of files to be copied, symlinked,
and/or configured prior to execution
"""
self._files = copy.deepcopy(value)

@property
def file_parameters(self) -> t.Mapping[str, str]:
"""Return file parameters.
:returns: application file parameters
:return: the file parameters
"""
return self._file_parameters

@file_parameters.setter
def file_parameters(self, value: t.Mapping[str, str]) -> None:
"""Set the file parameters.
:param value: file parameters
:param value: the file parameters
"""
self._file_parameters = copy.deepcopy(value)

@property
def incoming_entities(self) -> t.List[SmartSimEntity]:
"""Return incoming entities.
:returns: incoming entities
:return: incoming entities
"""
return self._incoming_entities

Expand Down Expand Up @@ -244,7 +262,7 @@ def attach_generator_files(
def attached_files_table(self) -> str:
"""Return a list of attached files as a plain text table
:returns: String version of table
:return: String version of table
"""
if not self.files:
return "No file attached to this application."
Expand Down
98 changes: 66 additions & 32 deletions smartsim/entity/ensemble.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def __init__(
copy.deepcopy(exe_arg_parameters) if exe_arg_parameters else {}
)
"""The parameters and values to be used when configuring entities"""
self._files = copy.deepcopy(files) if files else EntityFiles()
self._files = copy.deepcopy(files) if files else None
"""The files to be copied, symlinked, and/or configured prior to execution"""
self._file_parameters = (
copy.deepcopy(file_parameters) if file_parameters else {}
Expand All @@ -98,93 +98,93 @@ def __init__(

@property
def exe(self) -> str:
"""Return executable to run.
"""Return the attached executable.
:returns: application executable to run
:return: the executable
"""
return self._exe

@exe.setter
def exe(self, value: str | os.PathLike[str]) -> None:
"""Set executable to run.
"""Set the executable.
:param value: executable to run
:param value: the executable
"""
self._exe = os.fspath(value)

@property
def exe_args(self) -> t.List[str]:
"""Return a list of attached executable arguments.
"""Return attached list of executable arguments.
:returns: application executable arguments
:return: the executable arguments
"""
return self._exe_args

@exe_args.setter
def exe_args(self, value: t.Sequence[str]) -> None:
"""Set the executable arguments.
:param value: executable arguments
:param value: the executable arguments
"""
self._exe_args = list(value)

@property
def exe_arg_parameters(self) -> t.Mapping[str, t.Sequence[t.Sequence[str]]]:
"""Return the executable argument parameters
"""Return attached executable argument parameters.
:returns: executable arguments parameters
:return: the executable argument parameters
"""
return self._exe_arg_parameters

@exe_arg_parameters.setter
def exe_arg_parameters(
self, value: t.Mapping[str, t.Sequence[t.Sequence[str]]]
) -> None:
"""Set the executable arguments.
"""Set the executable argument parameters.
:param value: executable arguments
:param value: the executable argument parameters
"""
self._exe_arg_parameters = copy.deepcopy(value)

@property
def files(self) -> EntityFiles:
"""Return files to be copied, symlinked, and/or configured prior to
execution.
def files(self) -> t.Union[EntityFiles, None]:
"""Return attached EntityFiles object.
:returns: files
:return: the EntityFiles object of files to be copied, symlinked,
and/or configured prior to execution
"""
return self._files

@files.setter
def files(self, value: EntityFiles) -> None:
"""Set files to be copied, symlinked, and/or configured prior to
execution.
def files(self, value: t.Optional[EntityFiles]) -> None:
"""Set the EntityFiles object.
:param value: files
:param value: the EntityFiles object of files to be copied, symlinked,
and/or configured prior to execution
"""
self._files = copy.deepcopy(value)

@property
def file_parameters(self) -> t.Mapping[str, t.Sequence[str]]:
"""Return file parameters.
"""Return the attached file parameters.
:returns: application file parameters
:return: the file parameters
"""
return self._file_parameters

@file_parameters.setter
def file_parameters(self, value: t.Mapping[str, t.Sequence[str]]) -> None:
"""Set the file parameters.
:param value: file parameters
:param value: the file parameters
"""
self._file_parameters = dict(value)

@property
def permutation_strategy(self) -> str | strategies.PermutationStrategyType:
"""Return the permutation strategy
:return: permutation strategy
:return: the permutation strategy
"""
return self._permutation_strategy

Expand All @@ -194,45 +194,50 @@ def permutation_strategy(
) -> None:
"""Set the permutation strategy
:param value: permutation strategy
:param value: the permutation strategy
"""
self._permutation_strategy = value

@property
def max_permutations(self) -> int:
"""Return the maximum permutations
:return: max permutations
:return: the max permutations
"""
return self._max_permutations

@max_permutations.setter
def max_permutations(self, value: int) -> None:
"""Set the maximum permutations
:param value: the maxpermutations
:param value: the max permutations
"""
self._max_permutations = value

@property
def replicas(self) -> int:
"""Return the number of replicas
"""Return the number of replicas.
:return: number of replicas
:return: the number of replicas
"""
return self._replicas

@replicas.setter
def replicas(self, value: int) -> None:
"""Set the number of replicas
"""Set the number of replicas.
:return: the number of replicas
"""
self._replicas = value

def _create_applications(self) -> tuple[Application, ...]:
"""Concretize the ensemble attributes into a collection of
application instances.
"""Generate a collection of Application instances based on the Ensembles attributes.
This method uses a permutation strategy to create various combinations of file
parameters and executable arguments. Each combination is then replicated according
to the specified number of replicas, resulting in a set of Application instances.
:return: A tuple of Application instances
"""
permutation_strategy = strategies.resolve(self.permutation_strategy)

Expand All @@ -255,6 +260,35 @@ def _create_applications(self) -> tuple[Application, ...]:
)

def as_jobs(self, settings: LaunchSettings) -> tuple[Job, ...]:
"""Expand an Ensemble into a list of deployable Jobs and apply
identical LaunchSettings to each Job.
The number of Jobs returned is controlled by the Ensemble attributes:
- Ensemble.exe_arg_parameters
- Ensemble.file_parameters
- Ensemble.permutation_strategy
- Ensemble.max_permutations
- Ensemble.replicas
Consider the example below:
.. highlight:: python
.. code-block:: python
# Create LaunchSettings
my_launch_settings = LaunchSettings(...)
# 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)
By calling `as_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
:return: Sequence of Jobs with the provided LaunchSettings
"""
apps = self._create_applications()
if not apps:
raise ValueError("There are no members as part of this ensemble")
Expand Down
Loading

0 comments on commit f748789

Please sign in to comment.