Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove dead attributes in application #673

Merged
merged 13 commits into from
Aug 29, 2024
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ module = [
"smartsim._core.utils.telemetry.*",
"smartsim.database.*",
"smartsim.settings.sgeSettings",
"smartsim._core.control.controller_utils",
"smartsim.entity.dbnode",
]
ignore_missing_imports = true
ignore_errors = true
2 changes: 1 addition & 1 deletion smartsim/_core/generation/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ def _build_operations(cls, job: Job, job_path: pathlib.Path) -> None:
app = t.cast(Application, job.entity)
cls._copy_files(app.files, job_path)
cls._symlink_files(app.files, job_path)
cls._write_tagged_files(app.files, app.params, job_path)
cls._write_tagged_files(app.files, app.file_parameters, job_path)

@staticmethod
def _copy_files(files: t.Union[EntityFiles, None], dest: pathlib.Path) -> None:
Expand Down
51 changes: 51 additions & 0 deletions smartsim/_core/utils/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,14 @@

import base64
import collections.abc
import itertools
import os
import signal
import subprocess
import sys
import typing as t
import uuid
import warnings
from datetime import datetime
from functools import lru_cache
from shutil import which
Expand Down Expand Up @@ -284,6 +287,16 @@ def execute_platform_cmd(cmd: str) -> t.Tuple[str, int]:
return process.stdout.decode("utf-8"), process.returncode


def _stringify_id(_id: int) -> str:
"""Return the CPU id as a string if an int, otherwise raise a ValueError"""
juliaputko marked this conversation as resolved.
Show resolved Hide resolved
if isinstance(_id, int):
if _id < 0:
raise ValueError("CPU id must be a nonnegative number")
return str(_id)

raise TypeError(f"Argument is of type '{type(_id)}' not 'int'")


class CrayExPlatformResult:
locate_msg = "Unable to locate `{0}`."

Expand Down Expand Up @@ -515,3 +528,41 @@ def push_unique(self, fn: _TSignalHandlerFn) -> bool:
if did_push := fn not in self:
self.push(fn)
return did_push

def _create_pinning_string(
pin_ids: t.Optional[t.Iterable[t.Union[int, t.Iterable[int]]]], cpus: int
) -> t.Optional[str]:
"""Create a comma-separated string of CPU ids. By default, ``None``
juliaputko marked this conversation as resolved.
Show resolved Hide resolved
returns 0,1,...,cpus-1; an empty iterable will disable pinning
altogether, and an iterable constructs a comma separated string of
integers (e.g. ``[0, 2, 5]`` -> ``"0,2,5"``)
"""

try:
pin_ids = tuple(pin_ids) if pin_ids is not None else None
except TypeError:
raise TypeError(
"Expected a cpu pinning specification of type iterable of ints or "
f"iterables of ints. Instead got type `{type(pin_ids)}`"
) from None

# Deal with MacOSX limitations first. The "None" (default) disables pinning
# and is equivalent to []. The only invalid option is a non-empty pinning
if sys.platform == "darwin":
if pin_ids:
warnings.warn(
"CPU pinning is not supported on MacOSX. Ignoring pinning "
"specification.",
RuntimeWarning,
)
return None

# Flatten the iterable into a list and check to make sure that the resulting
# elements are all ints
if pin_ids is None:
return ",".join(_stringify_id(i) for i in range(cpus))
if not pin_ids:
return None
pin_ids = ((x,) if isinstance(x, int) else x for x in pin_ids)
to_fmt = itertools.chain.from_iterable(pin_ids)
return ",".join(sorted({_stringify_id(x) for x in to_fmt}))
2 changes: 1 addition & 1 deletion smartsim/entity/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@
from .entity import SmartSimEntity, TelemetryConfiguration
from .entityList import EntityList, EntitySequence
from .files import TaggedFilesHierarchy
from .model import Application
from .application import Application
288 changes: 288 additions & 0 deletions smartsim/entity/application.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
# 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 __future__ import annotations

import copy
import typing as t
from os import path as osp

from .._core.utils.helpers import expand_exe_path
from ..log import get_logger
from .entity import SmartSimEntity
from .files import EntityFiles

if t.TYPE_CHECKING:
from smartsim.types import TODO

RunSettings = TODO
BatchSettings = TODO
juliaputko marked this conversation as resolved.
Show resolved Hide resolved


logger = get_logger(__name__)


# TODO: Remove this supression when we strip fileds/functionality
# (run-settings/batch_settings/params_as_args/etc)!
# pylint: disable-next=too-many-public-methods
mellis13 marked this conversation as resolved.
Show resolved Hide resolved


class Application(SmartSimEntity):
def __init__(
self,
name: str,
exe: str,
exe_args: t.Optional[t.Union[str, t.Sequence[str]]] = None,
files: t.Optional[EntityFiles] = None,
file_parameters: t.Mapping[str, str] | None = None,
) -> None:
"""Initialize an ``Application``

:param name: name of the application
:param exe: executable to run
:param exe_args: executable arguments
:param files: files to be copied, symlinked, and/or configured prior to
execution
:param file_parameters: parameters and values to be used when configuring
files
"""
super().__init__(name)
self._exe = expand_exe_path(exe)
juliaputko marked this conversation as resolved.
Show resolved Hide resolved
self._exe_args = self._build_exe_args(exe_args) or []
self._files = copy.deepcopy(files) if files else None
self._file_parameters = (
copy.deepcopy(file_parameters) if file_parameters else {}
)
self._incoming_entities: t.List[SmartSimEntity] = []
self._key_prefixing_enabled = False

@property
def exe(self) -> str:
"""Return executable to run.

:returns: application executable to run
"""
return self._exe

@exe.setter
def exe(self, value: str) -> None:
"""Set executable to run.

:param value: executable to run
"""
self._exe = copy.deepcopy(value)

@property
def exe_args(self) -> t.Sequence[str]:
"""Return an immutable list of attached executable arguments.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the returned value really immutable?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At runtime, not technically, but the type hint here states that a sequence is return which implicitly promotes immutability. If we do drop the "immutable" wording from the docstring, I'm going to also recommend changing the return type to list[str] or at the very least MutableSequence[str]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I guess I was thinking that _build_exe_args returns a List which is mutable so that part was out of sync.

Do we want to be specific and risk API breaks later by changing to list[str] or generic to limit breaks later (i.e. MutableSequence[str])

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to mutablesequence[str]


:returns: application 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
"""
self._exe_args = self._build_exe_args(value)

@property
def files(self) -> t.Optional[EntityFiles]:
"""Return files to be copied, symlinked, and/or configured prior to
execution.

:returns: files
"""
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.

:param value: files
"""
self._files = copy.deepcopy(value)

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

:returns: application 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
"""
self._file_parameters = copy.deepcopy(value)

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

:returns: incoming entities
"""
return self._incoming_entities

@incoming_entities.setter
def incoming_entities(self, value: t.List[SmartSimEntity]) -> None:
"""Set the incoming entities.

:param value: incoming entities
"""
self._incoming_entities = copy.deepcopy(value)
juliaputko marked this conversation as resolved.
Show resolved Hide resolved

@property
def key_prefixing_enabled(self) -> bool:
"""Return whether key prefixing is enabled for the application.

:param value: key prefixing enabled
"""
return self._key_prefixing_enabled

@key_prefixing_enabled.setter
def key_prefixing_enabled(self, value: bool) -> None:
"""Set whether key prefixing is enabled for the application.

:param value: key prefixing enabled
"""
self.key_prefixing_enabled = copy.deepcopy(value)

def add_exe_args(self, args: t.Union[str, t.List[str], None]) -> None:
"""Add executable arguments to executable

:param args: executable arguments
"""
args = self._build_exe_args(args)
self._exe_args.extend(args)

def attach_generator_files(
self,
to_copy: t.Optional[t.List[str]] = None,
to_symlink: t.Optional[t.List[str]] = None,
to_configure: t.Optional[t.List[str]] = None,
) -> None:
"""Attach files to an entity for generation

Attach files needed for the entity that, upon generation,
will be located in the path of the entity. Invoking this method
after files have already been attached will overwrite
the previous list of entity files.

During generation, files "to_copy" are copied into
the path of the entity, and files "to_symlink" are
symlinked into the path of the entity.

Files "to_configure" are text based application input files where
parameters for the application are set. Note that only applications
support the "to_configure" field. These files must have
fields tagged that correspond to the values the user
would like to change. The tag is settable but defaults
to a semicolon e.g. THERMO = ;10;

:param to_copy: files to copy
:param to_symlink: files to symlink
:param to_configure: input files with tagged parameters
juliaputko marked this conversation as resolved.
Show resolved Hide resolved
"""
to_copy = to_copy or []
to_symlink = to_symlink or []
to_configure = to_configure or []

# Check that no file collides with the parameter file written
# by Generator. We check the basename, even though it is more
# restrictive than what we need (but it avoids relative path issues)
for strategy in [to_copy, to_symlink, to_configure]:
if strategy is not None and any(
osp.basename(filename) == "smartsim_params.txt" for filename in strategy
):
raise ValueError(
"`smartsim_params.txt` is a file automatically "
+ "generated by SmartSim and cannot be ovewritten."
)
self.files = EntityFiles(to_configure, to_copy, to_symlink)

@property
def attached_files_table(self) -> str:
"""Return a list of attached files as a plain text table

:returns: String version of table
"""
if not self.files:
return "No file attached to this application."
return str(self.files)

def print_attached_files(self) -> None:
"""Print a table of the attached files on std out"""
print(self.attached_files_table)

def __str__(self) -> str: # pragma: no cover
entity_str = "Name: " + self.name + "\n"
entity_str += "Type: " + self.type + "\n"
entity_str += "Executable:\n"
for ex in self.exe:
entity_str += f"{ex}\n"
entity_str += "Executable Arguments:\n"
for ex_arg in self.exe_args:
entity_str += f"{str(ex_arg)}\n"
entity_str += f"Entity Files: {self.files}\n"
entity_str += f"File Parameters: {self.file_parameters}\n"
entity_str += "Incoming Entities:\n"
for entity in self.incoming_entities:
entity_str += f"{entity}\n"
entity_str += f"Key Prefixing Enabled: {self.key_prefixing_enabled}\n"

return entity_str
juliaputko marked this conversation as resolved.
Show resolved Hide resolved

@staticmethod
def _build_exe_args(
exe_args: t.Optional[t.Union[str, t.Sequence[str], None]]
juliaputko marked this conversation as resolved.
Show resolved Hide resolved
) -> t.List[str]:
"""Check and convert exe_args input to a desired collection format"""
juliaputko marked this conversation as resolved.
Show resolved Hide resolved
if not exe_args:
return []

if isinstance(exe_args, list):
juliaputko marked this conversation as resolved.
Show resolved Hide resolved
exe_args = copy.deepcopy(exe_args)

if not (
isinstance(exe_args, str)
or (
isinstance(exe_args, list)
and all(isinstance(arg, str) for arg in exe_args)
)
):
raise TypeError("Executable arguments were not a list of str or a str.")

if isinstance(exe_args, str):
return exe_args.split()

return exe_args
juliaputko marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 2 additions & 1 deletion smartsim/entity/dbnode.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ def __init__(
fs_identifier: str = "",
) -> None:
"""Initialize a feature store node within an feature store."""
super().__init__(name, run_settings)
super().__init__(name)
juliaputko marked this conversation as resolved.
Show resolved Hide resolved
self.run_settings = run_settings
self.exe = [exe] if run_settings.container else [expand_exe_path(exe)]
self.exe_args = exe_args or []
self.ports = ports
Expand Down
Loading
Loading