idmtools 0.0.0.dev0__py3-none-any.whl → 0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- idmtools/__init__.py +27 -8
- idmtools/analysis/__init__.py +5 -0
- idmtools/analysis/add_analyzer.py +89 -0
- idmtools/analysis/analyze_manager.py +490 -0
- idmtools/analysis/csv_analyzer.py +103 -0
- idmtools/analysis/download_analyzer.py +96 -0
- idmtools/analysis/map_worker_entry.py +100 -0
- idmtools/analysis/platform_analysis_bootstrap.py +94 -0
- idmtools/analysis/platform_anaylsis.py +291 -0
- idmtools/analysis/tags_analyzer.py +93 -0
- idmtools/assets/__init__.py +9 -0
- idmtools/assets/asset.py +453 -0
- idmtools/assets/asset_collection.py +514 -0
- idmtools/assets/content_handlers.py +19 -0
- idmtools/assets/errors.py +23 -0
- idmtools/assets/file_list.py +191 -0
- idmtools/builders/__init__.py +11 -0
- idmtools/builders/arm_simulation_builder.py +152 -0
- idmtools/builders/csv_simulation_builder.py +76 -0
- idmtools/builders/simulation_builder.py +348 -0
- idmtools/builders/sweep_arm.py +109 -0
- idmtools/builders/yaml_simulation_builder.py +82 -0
- idmtools/config/__init__.py +7 -0
- idmtools/config/idm_config_parser.py +486 -0
- idmtools/core/__init__.py +10 -0
- idmtools/core/cache_enabled.py +114 -0
- idmtools/core/context.py +68 -0
- idmtools/core/docker_task.py +207 -0
- idmtools/core/enums.py +51 -0
- idmtools/core/exceptions.py +91 -0
- idmtools/core/experiment_factory.py +71 -0
- idmtools/core/id_file.py +70 -0
- idmtools/core/interfaces/__init__.py +5 -0
- idmtools/core/interfaces/entity_container.py +64 -0
- idmtools/core/interfaces/iassets_enabled.py +58 -0
- idmtools/core/interfaces/ientity.py +331 -0
- idmtools/core/interfaces/iitem.py +206 -0
- idmtools/core/interfaces/imetadata_operations.py +89 -0
- idmtools/core/interfaces/inamed_entity.py +17 -0
- idmtools/core/interfaces/irunnable_entity.py +159 -0
- idmtools/core/logging.py +387 -0
- idmtools/core/platform_factory.py +316 -0
- idmtools/core/system_information.py +104 -0
- idmtools/core/task_factory.py +145 -0
- idmtools/entities/__init__.py +10 -0
- idmtools/entities/command_line.py +229 -0
- idmtools/entities/command_task.py +155 -0
- idmtools/entities/experiment.py +787 -0
- idmtools/entities/generic_workitem.py +43 -0
- idmtools/entities/ianalyzer.py +163 -0
- idmtools/entities/iplatform.py +1106 -0
- idmtools/entities/iplatform_default.py +39 -0
- idmtools/entities/iplatform_ops/__init__.py +5 -0
- idmtools/entities/iplatform_ops/iplatform_asset_collection_operations.py +148 -0
- idmtools/entities/iplatform_ops/iplatform_experiment_operations.py +415 -0
- idmtools/entities/iplatform_ops/iplatform_simulation_operations.py +315 -0
- idmtools/entities/iplatform_ops/iplatform_suite_operations.py +322 -0
- idmtools/entities/iplatform_ops/iplatform_workflowitem_operations.py +301 -0
- idmtools/entities/iplatform_ops/utils.py +185 -0
- idmtools/entities/itask.py +316 -0
- idmtools/entities/iworkflow_item.py +167 -0
- idmtools/entities/platform_requirements.py +20 -0
- idmtools/entities/relation_type.py +14 -0
- idmtools/entities/simulation.py +255 -0
- idmtools/entities/suite.py +188 -0
- idmtools/entities/task_proxy.py +37 -0
- idmtools/entities/templated_simulation.py +325 -0
- idmtools/frozen/frozen_dict.py +71 -0
- idmtools/frozen/frozen_list.py +66 -0
- idmtools/frozen/frozen_set.py +86 -0
- idmtools/frozen/frozen_tuple.py +18 -0
- idmtools/frozen/frozen_utils.py +179 -0
- idmtools/frozen/ifrozen.py +66 -0
- idmtools/plugins/__init__.py +5 -0
- idmtools/plugins/git_commit.py +117 -0
- idmtools/registry/__init__.py +4 -0
- idmtools/registry/experiment_specification.py +105 -0
- idmtools/registry/functions.py +28 -0
- idmtools/registry/hook_specs.py +132 -0
- idmtools/registry/master_plugin_registry.py +51 -0
- idmtools/registry/platform_specification.py +138 -0
- idmtools/registry/plugin_specification.py +129 -0
- idmtools/registry/task_specification.py +104 -0
- idmtools/registry/utils.py +119 -0
- idmtools/services/__init__.py +5 -0
- idmtools/services/ipersistance_service.py +135 -0
- idmtools/services/platforms.py +13 -0
- idmtools/utils/__init__.py +5 -0
- idmtools/utils/caller.py +24 -0
- idmtools/utils/collections.py +246 -0
- idmtools/utils/command_line.py +45 -0
- idmtools/utils/decorators.py +300 -0
- idmtools/utils/display/__init__.py +22 -0
- idmtools/utils/display/displays.py +181 -0
- idmtools/utils/display/settings.py +25 -0
- idmtools/utils/dropbox_location.py +30 -0
- idmtools/utils/entities.py +127 -0
- idmtools/utils/file.py +72 -0
- idmtools/utils/file_parser.py +151 -0
- idmtools/utils/filter_simulations.py +182 -0
- idmtools/utils/filters/__init__.py +5 -0
- idmtools/utils/filters/asset_filters.py +88 -0
- idmtools/utils/general.py +286 -0
- idmtools/utils/gitrepo.py +336 -0
- idmtools/utils/hashing.py +239 -0
- idmtools/utils/info.py +124 -0
- idmtools/utils/json.py +82 -0
- idmtools/utils/language.py +107 -0
- idmtools/utils/local_os.py +40 -0
- idmtools/utils/time.py +22 -0
- idmtools-0.0.2.dist-info/METADATA +120 -0
- idmtools-0.0.2.dist-info/RECORD +116 -0
- idmtools-0.0.2.dist-info/entry_points.txt +9 -0
- idmtools-0.0.2.dist-info/licenses/LICENSE.TXT +3 -0
- idmtools-0.0.0.dev0.dist-info/METADATA +0 -41
- idmtools-0.0.0.dev0.dist-info/RECORD +0 -5
- {idmtools-0.0.0.dev0.dist-info → idmtools-0.0.2.dist-info}/WHEEL +0 -0
- {idmtools-0.0.0.dev0.dist-info → idmtools-0.0.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Here we define the Platform interface.
|
|
3
|
+
|
|
4
|
+
IPlatform is responsible for all the communication to our platform and translation from idmtools objects to platform specific objects and vice versa.
|
|
5
|
+
|
|
6
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
7
|
+
"""
|
|
8
|
+
import os
|
|
9
|
+
import copy
|
|
10
|
+
from abc import ABCMeta
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from dataclasses import fields, field
|
|
13
|
+
from functools import partial
|
|
14
|
+
from os import PathLike
|
|
15
|
+
import pandas as pd
|
|
16
|
+
from pathlib import PureWindowsPath, PurePath
|
|
17
|
+
from itertools import groupby
|
|
18
|
+
from logging import getLogger, DEBUG
|
|
19
|
+
from typing import Dict, List, NoReturn, Type, TypeVar, Any, Union, Tuple, Set, Iterator, Callable, Optional
|
|
20
|
+
from idmtools.core.context import set_current_platform
|
|
21
|
+
from idmtools import IdmConfigParser
|
|
22
|
+
from idmtools.core import CacheEnabled, UnknownItemException, EntityContainer, UnsupportedPlatformType
|
|
23
|
+
from idmtools.core.enums import ItemType, EntityStatus
|
|
24
|
+
from idmtools.core.interfaces.ientity import IEntity
|
|
25
|
+
from idmtools.core.interfaces.iitem import IItem
|
|
26
|
+
from idmtools.core.interfaces.irunnable_entity import IRunnableEntity
|
|
27
|
+
from idmtools.entities.experiment import Experiment
|
|
28
|
+
from idmtools.core.id_file import read_id_file
|
|
29
|
+
from idmtools.entities.iplatform_default import IPlatformDefault
|
|
30
|
+
from idmtools.entities.iplatform_ops.iplatform_asset_collection_operations import IPlatformAssetCollectionOperations
|
|
31
|
+
from idmtools.entities.iplatform_ops.iplatform_experiment_operations import IPlatformExperimentOperations
|
|
32
|
+
from idmtools.entities.iplatform_ops.iplatform_simulation_operations import IPlatformSimulationOperations
|
|
33
|
+
from idmtools.entities.iplatform_ops.iplatform_suite_operations import IPlatformSuiteOperations
|
|
34
|
+
from idmtools.entities.iplatform_ops.iplatform_workflowitem_operations import IPlatformWorkflowItemOperations
|
|
35
|
+
from idmtools.entities.itask import ITask
|
|
36
|
+
from idmtools.entities.iworkflow_item import IWorkflowItem
|
|
37
|
+
from idmtools.entities.platform_requirements import PlatformRequirements
|
|
38
|
+
from idmtools.entities.relation_type import RelationType
|
|
39
|
+
from idmtools.entities.simulation import Simulation
|
|
40
|
+
from idmtools.entities.suite import Suite
|
|
41
|
+
from idmtools.assets.asset_collection import AssetCollection
|
|
42
|
+
from idmtools.services.platforms import PlatformPersistService
|
|
43
|
+
from idmtools.utils.caller import get_caller
|
|
44
|
+
from idmtools.utils.entities import validate_user_inputs_against_dataclass
|
|
45
|
+
|
|
46
|
+
logger = getLogger(__name__)
|
|
47
|
+
user_logger = getLogger('user')
|
|
48
|
+
|
|
49
|
+
CALLER_LIST = ['_create_platform_from_block', # create platform through Platform Factory
|
|
50
|
+
'fetch', # create platform through un-pickle
|
|
51
|
+
'get', # create platform through platform spec' get method
|
|
52
|
+
'__newobj__', # create platform through copy.deepcopy
|
|
53
|
+
'_main', # create platform through analyzer manager
|
|
54
|
+
'<module>'] # create platform through specific module
|
|
55
|
+
|
|
56
|
+
# Maps an object type to a platform interface object. We use strings to use getattr. This also let's us also reduce
|
|
57
|
+
# all the if else crud
|
|
58
|
+
ITEM_TYPE_TO_OBJECT_INTERFACE = {
|
|
59
|
+
ItemType.EXPERIMENT: '_experiments',
|
|
60
|
+
ItemType.SIMULATION: '_simulations',
|
|
61
|
+
ItemType.SUITE: '_suites',
|
|
62
|
+
ItemType.WORKFLOW_ITEM: '_workflow_items',
|
|
63
|
+
ItemType.ASSETCOLLECTION: '_assets'
|
|
64
|
+
}
|
|
65
|
+
STANDARD_TYPE_TO_INTERFACE = {
|
|
66
|
+
Experiment: ItemType.EXPERIMENT,
|
|
67
|
+
Simulation: ItemType.SIMULATION,
|
|
68
|
+
IWorkflowItem: ItemType.WORKFLOW_ITEM,
|
|
69
|
+
Suite: ItemType.SUITE
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass(repr=False)
|
|
74
|
+
class IPlatform(IItem, CacheEnabled, metaclass=ABCMeta):
|
|
75
|
+
"""
|
|
76
|
+
Interface defining a platform.
|
|
77
|
+
|
|
78
|
+
A platform needs to implement basic operation such as:
|
|
79
|
+
|
|
80
|
+
- Creating experiment
|
|
81
|
+
- Creating simulation
|
|
82
|
+
- Commissioning
|
|
83
|
+
- File handling
|
|
84
|
+
"""
|
|
85
|
+
#: Maps the platform types to idmtools types
|
|
86
|
+
platform_type_map: Dict[Type, ItemType] = field(default=None, repr=False, init=False)
|
|
87
|
+
_object_cache_expiration: 'int' = field(default=60, repr=False, init=False)
|
|
88
|
+
|
|
89
|
+
supported_types: Set[ItemType] = field(default_factory=lambda: set(), repr=False, init=False)
|
|
90
|
+
_platform_supports: List[PlatformRequirements] = field(default_factory=list, repr=False, init=False)
|
|
91
|
+
_platform_defaults: List[IPlatformDefault] = field(default_factory=list)
|
|
92
|
+
|
|
93
|
+
_experiments: IPlatformExperimentOperations = field(default=None, repr=False, init=False, compare=False)
|
|
94
|
+
_simulations: IPlatformSimulationOperations = field(default=None, repr=False, init=False, compare=False)
|
|
95
|
+
_suites: IPlatformSuiteOperations = field(default=None, repr=False, init=False, compare=False)
|
|
96
|
+
_workflow_items: IPlatformWorkflowItemOperations = field(default=None, repr=False, init=False, compare=False)
|
|
97
|
+
_assets: IPlatformAssetCollectionOperations = field(default=None, repr=False, init=False, compare=False)
|
|
98
|
+
#: Controls what platform should do we re-running experiments by default
|
|
99
|
+
_regather_assets_on_modify: bool = field(default=False, repr=False, init=False, compare=False)
|
|
100
|
+
# store the config block used to create platform
|
|
101
|
+
_config_block: str = field(default=None)
|
|
102
|
+
#: Defines the path to common assets
|
|
103
|
+
_common_asset_path: str = field(default="Assets", repr=True, init=False, compare=False)
|
|
104
|
+
|
|
105
|
+
refresh_interval: int = field(default=5, repr=False, init=True, compare=False, metadata=dict(help="Refresh Interval during wait."))
|
|
106
|
+
|
|
107
|
+
def __new__(cls, *args, **kwargs):
|
|
108
|
+
"""
|
|
109
|
+
Create a new object.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
args: User inputs.
|
|
113
|
+
kwargs: User inputs.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
The object created.
|
|
117
|
+
|
|
118
|
+
Raises:
|
|
119
|
+
ValueError - If the platform was not created as expected.
|
|
120
|
+
"""
|
|
121
|
+
# Check the caller
|
|
122
|
+
caller = get_caller()
|
|
123
|
+
|
|
124
|
+
# Action based on the caller
|
|
125
|
+
if caller not in CALLER_LIST:
|
|
126
|
+
logger.warning(
|
|
127
|
+
"Please use Factory to create Platform! For example: \n platform = Platform('COMPS', **kwargs)")
|
|
128
|
+
return super().__new__(cls)
|
|
129
|
+
|
|
130
|
+
def __post_init__(self) -> NoReturn:
|
|
131
|
+
"""
|
|
132
|
+
Work to be done after object creation.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
None
|
|
136
|
+
"""
|
|
137
|
+
# build item type map and determined supported features
|
|
138
|
+
self.platform_type_map = dict()
|
|
139
|
+
for item_type, interface in ITEM_TYPE_TO_OBJECT_INTERFACE.items():
|
|
140
|
+
if getattr(self, interface) is not None and getattr(self, interface).platform_type is not None:
|
|
141
|
+
self.platform_type_map[getattr(self, interface).platform_type] = item_type
|
|
142
|
+
|
|
143
|
+
self.validate_inputs_types()
|
|
144
|
+
set_current_platform(self)
|
|
145
|
+
# Initialize the cache
|
|
146
|
+
self.initialize_cache()
|
|
147
|
+
|
|
148
|
+
# Save itself
|
|
149
|
+
PlatformPersistService.save(self)
|
|
150
|
+
|
|
151
|
+
def validate_inputs_types(self) -> NoReturn:
|
|
152
|
+
"""
|
|
153
|
+
Validate user inputs and case attributes with the correct data types.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
None
|
|
157
|
+
"""
|
|
158
|
+
# retrieve field values, default values and types
|
|
159
|
+
fds = fields(self)
|
|
160
|
+
field_value = {f.name: getattr(self, f.name) for f in fds}
|
|
161
|
+
field_type = {f.name: f.type for f in fds}
|
|
162
|
+
|
|
163
|
+
# Make sure the user values have the requested type
|
|
164
|
+
fs_kwargs = validate_user_inputs_against_dataclass(field_type, field_value)
|
|
165
|
+
|
|
166
|
+
# Update attr with validated data types
|
|
167
|
+
for fn in fs_kwargs:
|
|
168
|
+
setattr(self, fn, field_value[fn])
|
|
169
|
+
|
|
170
|
+
def _get_platform_item(self, item_id: str, item_type: ItemType, **kwargs) -> Any:
|
|
171
|
+
"""
|
|
172
|
+
Get an item by its ID.
|
|
173
|
+
|
|
174
|
+
The implementing classes must know how to distinguish items of different levels (e.g. simulation, experiment, suite).
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
item_id: The ID of the item to retrieve.
|
|
178
|
+
item_type: The type of object to retrieve.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
The specified item found on the platform or None.
|
|
182
|
+
|
|
183
|
+
Raises:
|
|
184
|
+
ValueError: If the item type is not supported
|
|
185
|
+
"""
|
|
186
|
+
if item_type not in self.platform_type_map.values():
|
|
187
|
+
raise ValueError(f"Unsupported Item Type. {self.__class__.__name__} only supports "
|
|
188
|
+
f"{self.platform_type_map.values()}")
|
|
189
|
+
interface = ITEM_TYPE_TO_OBJECT_INTERFACE[item_type]
|
|
190
|
+
return getattr(self, interface).get(item_id, **kwargs)
|
|
191
|
+
|
|
192
|
+
def get_item(self, item_id: str, item_type: ItemType = None, force: bool = False, raw: bool = False,
|
|
193
|
+
**kwargs) -> Union[Experiment, Suite, Simulation, IWorkflowItem, AssetCollection, None]:
|
|
194
|
+
"""
|
|
195
|
+
Retrieve an object from the platform.
|
|
196
|
+
|
|
197
|
+
This function is cached; force allows you to force the refresh of the cache.
|
|
198
|
+
If no **object_type** is passed, the function will try all the types (experiment, suite, simulation).
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
item_id: The ID of the object to retrieve.
|
|
202
|
+
item_type: The type of the object to be retrieved.
|
|
203
|
+
force: If True, force the object fetching from the platform.
|
|
204
|
+
raw: Return either an |IT_s| object or a platform object.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
The object found on the platform or None.
|
|
208
|
+
|
|
209
|
+
Raises:
|
|
210
|
+
ValueError: If the item type is not supported
|
|
211
|
+
UnknownItemException: If the item type is not found on platform
|
|
212
|
+
"""
|
|
213
|
+
self.validate_type(item_type)
|
|
214
|
+
|
|
215
|
+
cache_key = self.get_cache_key(force, item_id, item_type, kwargs, raw, 'r' if raw else 'o')
|
|
216
|
+
|
|
217
|
+
# If force -> delete in the cache
|
|
218
|
+
if force:
|
|
219
|
+
if logger.isEnabledFor(DEBUG):
|
|
220
|
+
logger.debug(f"Removing {cache_key} from cache")
|
|
221
|
+
self.cache.delete(cache_key)
|
|
222
|
+
|
|
223
|
+
# If we cannot find the object in the cache -> retrieve depending on the type
|
|
224
|
+
if cache_key not in self.cache:
|
|
225
|
+
if logger.isEnabledFor(DEBUG):
|
|
226
|
+
logger.debug(f"Retrieve item {item_id} of type {item_type}")
|
|
227
|
+
ce = self._get_platform_item(item_id, item_type, **kwargs)
|
|
228
|
+
|
|
229
|
+
# Nothing was found on the platform
|
|
230
|
+
if not ce:
|
|
231
|
+
raise UnknownItemException(f"Object {item_type} {item_id} not found on the platform...")
|
|
232
|
+
|
|
233
|
+
# Create the object if we do not want it raw
|
|
234
|
+
if raw:
|
|
235
|
+
return_object = ce
|
|
236
|
+
else:
|
|
237
|
+
return_object = self._convert_platform_item_to_entity(ce, **kwargs)
|
|
238
|
+
if return_object._platform_object is None:
|
|
239
|
+
return_object._platform_object = ce
|
|
240
|
+
return_object.platform = self
|
|
241
|
+
|
|
242
|
+
# Persist
|
|
243
|
+
self.cache.set(cache_key, return_object, expire=self._object_cache_expiration)
|
|
244
|
+
|
|
245
|
+
else:
|
|
246
|
+
return_object = self.cache.get(cache_key)
|
|
247
|
+
return_object.platform = self
|
|
248
|
+
|
|
249
|
+
return return_object
|
|
250
|
+
|
|
251
|
+
def _get_children_for_platform_item(self, item: Any, raw: bool = True, **kwargs) -> List[Any]:
|
|
252
|
+
"""
|
|
253
|
+
Returns the children for a platform object.
|
|
254
|
+
|
|
255
|
+
For example, A COMPS Experiment or Simulation.
|
|
256
|
+
|
|
257
|
+
The children can either be returns as native platform objects or as idmtools objects.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
item: Item to fetch children for
|
|
261
|
+
raw: When true, return the native platform representation of the children, otherwise return the idmtools
|
|
262
|
+
implementation
|
|
263
|
+
**kwargs:
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
Children of platform object
|
|
267
|
+
"""
|
|
268
|
+
item_type, interface = self._get_operation_interface(item)
|
|
269
|
+
if item_type in [ItemType.EXPERIMENT, ItemType.SUITE]:
|
|
270
|
+
children = getattr(self, ITEM_TYPE_TO_OBJECT_INTERFACE[item_type]).get_children(item, **kwargs)
|
|
271
|
+
else:
|
|
272
|
+
return []
|
|
273
|
+
if not raw:
|
|
274
|
+
ret = []
|
|
275
|
+
for e in children:
|
|
276
|
+
n = self._convert_platform_item_to_entity(e, **kwargs)
|
|
277
|
+
if n._platform_object is None:
|
|
278
|
+
n._platform_object = e
|
|
279
|
+
ret.append(n)
|
|
280
|
+
return EntityContainer(ret)
|
|
281
|
+
else:
|
|
282
|
+
return children
|
|
283
|
+
|
|
284
|
+
def _get_operation_interface(self, item: Any) -> Tuple[ItemType, str]:
|
|
285
|
+
"""
|
|
286
|
+
Get the base item type and the interface string for said item.
|
|
287
|
+
|
|
288
|
+
For example, on COMPSPlatform, if you passed a COMPSExperiment object, the function would return ItemType.Experiment, _experiments
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
item: Item to look up
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
Tuple with the Base item type and the string path to the interface
|
|
296
|
+
|
|
297
|
+
Raises:
|
|
298
|
+
ValueError: If the item 's interface cannot be found
|
|
299
|
+
"""
|
|
300
|
+
# check both base types and platform specs
|
|
301
|
+
for interface_type_mapping in [STANDARD_TYPE_TO_INTERFACE, self.platform_type_map]:
|
|
302
|
+
for interface, item_type in interface_type_mapping.items():
|
|
303
|
+
if isinstance(item, interface):
|
|
304
|
+
return item_type, ITEM_TYPE_TO_OBJECT_INTERFACE[item_type]
|
|
305
|
+
raise ValueError(f"{self.__class__.__name__} has no mapping for {item.__class__.__name__}")
|
|
306
|
+
|
|
307
|
+
def get_children(self, item_id: str, item_type: ItemType,
|
|
308
|
+
force: bool = False, raw: bool = False, item: Any = None, **kwargs) -> Any:
|
|
309
|
+
"""
|
|
310
|
+
Retrieve the children of a given object.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
item_id: The ID of the object for which we want the children.
|
|
314
|
+
force: If True, force the object fetching from the platform.
|
|
315
|
+
raw: Return either an |IT_s| object or a platform object.
|
|
316
|
+
item_type: Pass the type of the object for quicker retrieval.
|
|
317
|
+
item: optional platform or idm item to use instead of loading
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
The children of the object or None.
|
|
321
|
+
"""
|
|
322
|
+
cache_key = self.get_cache_key(force, item_id, item_type, kwargs, raw, 'c')
|
|
323
|
+
|
|
324
|
+
if force:
|
|
325
|
+
self.cache.delete(cache_key)
|
|
326
|
+
|
|
327
|
+
if cache_key not in self.cache:
|
|
328
|
+
ce = item or self.get_item(item_id, raw=raw, item_type=item_type)
|
|
329
|
+
ce.platform = self
|
|
330
|
+
kwargs['parent'] = ce
|
|
331
|
+
if raw:
|
|
332
|
+
children = self._get_children_for_platform_item(ce, raw=raw, **kwargs)
|
|
333
|
+
else:
|
|
334
|
+
children = self._get_children_for_platform_item(ce.get_platform_object(), raw=raw, **kwargs)
|
|
335
|
+
self.cache.set(cache_key, children, expire=self._object_cache_expiration)
|
|
336
|
+
return children
|
|
337
|
+
|
|
338
|
+
return self.cache.get(cache_key)
|
|
339
|
+
|
|
340
|
+
def get_children_by_object(self, parent: IEntity) -> List[IEntity]:
|
|
341
|
+
"""
|
|
342
|
+
Returns a list of children for an entity.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
parent: Parent object
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
List of children
|
|
349
|
+
"""
|
|
350
|
+
return self._get_children_for_platform_item(parent.get_platform_object(), raw=False)
|
|
351
|
+
|
|
352
|
+
def get_parent_by_object(self, child: IEntity) -> IEntity:
|
|
353
|
+
"""
|
|
354
|
+
Parent of object.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
child: Child object to find parent for
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
Returns parent object
|
|
361
|
+
"""
|
|
362
|
+
return self._get_parent_for_platform_item(child.get_platform_object(), raw=False)
|
|
363
|
+
|
|
364
|
+
def _get_parent_for_platform_item(self, platform_item: Any, raw: bool = True, **kwargs) -> Any:
|
|
365
|
+
"""
|
|
366
|
+
Return the parent item for a given platform_item.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
platform_item: Child item
|
|
370
|
+
raw: Return a platform item if True, an idmtools entity if false
|
|
371
|
+
**kwargs: Additional platform specific parameters
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
Parent or None
|
|
375
|
+
"""
|
|
376
|
+
item_type, interface = self._get_operation_interface(platform_item)
|
|
377
|
+
if item_type not in [ItemType.EXPERIMENT, ItemType.SIMULATION, ItemType.WORKFLOW_ITEM]:
|
|
378
|
+
raise ValueError("Currently only Experiments, Simulations and Work Items support parents")
|
|
379
|
+
obj = getattr(self, interface).get_parent(platform_item, **kwargs)
|
|
380
|
+
if obj is not None:
|
|
381
|
+
parent_item_type, parent_interface = self._get_operation_interface(obj)
|
|
382
|
+
if not raw:
|
|
383
|
+
return getattr(self, parent_interface).to_entity(obj)
|
|
384
|
+
return obj
|
|
385
|
+
|
|
386
|
+
def get_parent(self, item_id: str, item_type: ItemType = None, force: bool = False,
|
|
387
|
+
raw: bool = False, **kwargs):
|
|
388
|
+
"""
|
|
389
|
+
Retrieve the parent of a given object.
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
item_id: The ID of the object for which we want the parent.
|
|
393
|
+
force: If True, force the object fetching from the platform.
|
|
394
|
+
raw: Return either an |IT_s| object or a platform object.
|
|
395
|
+
item_type: Pass the type of the object for quicker retrieval.
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
The parent of the object or None.
|
|
399
|
+
|
|
400
|
+
"""
|
|
401
|
+
self.validate_type(item_type)
|
|
402
|
+
|
|
403
|
+
# Create the cache key based on everything we pass to the function
|
|
404
|
+
cache_key = f'p_{item_id}' + ('r' if raw else 'o') + '_'.join(f"{k}_{v}" for k, v in kwargs.items())
|
|
405
|
+
|
|
406
|
+
if force:
|
|
407
|
+
self.cache.delete(cache_key)
|
|
408
|
+
|
|
409
|
+
if cache_key not in self.cache:
|
|
410
|
+
ce = self.get_item(item_id, raw=True, item_type=item_type)
|
|
411
|
+
parent = self._get_parent_for_platform_item(ce, raw=raw, **kwargs)
|
|
412
|
+
self.cache.set(cache_key, parent, expire=self._object_cache_expiration)
|
|
413
|
+
return parent
|
|
414
|
+
|
|
415
|
+
return self.cache.get(cache_key)
|
|
416
|
+
|
|
417
|
+
def get_cache_key(self, force, item_id, item_type, kwargs, raw, prefix='p'):
|
|
418
|
+
"""
|
|
419
|
+
Get cache key for an item.
|
|
420
|
+
|
|
421
|
+
Args:
|
|
422
|
+
force: Should we force the load
|
|
423
|
+
item_id: Item id
|
|
424
|
+
item_type: Item type
|
|
425
|
+
kwargs: Kwargs
|
|
426
|
+
raw: Should we use raw storage?
|
|
427
|
+
prefix: Prefix for the item
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
Cache Key
|
|
431
|
+
"""
|
|
432
|
+
if not item_type or item_type not in self.supported_types:
|
|
433
|
+
raise Exception("The provided type is invalid or not supported by this platform...")
|
|
434
|
+
# Create the cache key based on everything we pass to the function
|
|
435
|
+
cache_key = f'{prefix}_{item_id}' + ('r' if raw else 'o') + '_'.join(f"{k}_{v}" for k, v in kwargs.items())
|
|
436
|
+
if force:
|
|
437
|
+
self.cache.delete(cache_key)
|
|
438
|
+
return cache_key
|
|
439
|
+
|
|
440
|
+
def create_items(self, items: Union[List[IEntity], IEntity], **kwargs) -> List[IEntity]:
|
|
441
|
+
"""
|
|
442
|
+
Create items (simulations, experiments, or suites) on the platform.
|
|
443
|
+
|
|
444
|
+
The function will batch the items based on type and call the self._create_batch for creation.
|
|
445
|
+
|
|
446
|
+
Args:
|
|
447
|
+
items: The list of items to create.
|
|
448
|
+
kwargs: Extra arguments
|
|
449
|
+
Returns:
|
|
450
|
+
List of item IDs created.
|
|
451
|
+
"""
|
|
452
|
+
if isinstance(items, IEntity):
|
|
453
|
+
items = [items]
|
|
454
|
+
if not isinstance(items, Iterator):
|
|
455
|
+
self._is_item_list_supported(items)
|
|
456
|
+
|
|
457
|
+
result = []
|
|
458
|
+
for key, group in groupby(items, lambda x: x.item_type):
|
|
459
|
+
result.extend(self._create_items_of_type(group, key, **kwargs))
|
|
460
|
+
return result
|
|
461
|
+
|
|
462
|
+
def _create_items_of_type(self, items: Iterator[IEntity], item_type: ItemType, **kwargs):
|
|
463
|
+
"""
|
|
464
|
+
Creates items of specific type using batches.
|
|
465
|
+
|
|
466
|
+
Args:
|
|
467
|
+
items: Items to create
|
|
468
|
+
item_type: Item type to create
|
|
469
|
+
|
|
470
|
+
Returns:
|
|
471
|
+
Items created
|
|
472
|
+
"""
|
|
473
|
+
interface = ITEM_TYPE_TO_OBJECT_INTERFACE[item_type]
|
|
474
|
+
ni = getattr(self, interface).batch_create(items, **kwargs)
|
|
475
|
+
return ni
|
|
476
|
+
|
|
477
|
+
def _is_item_list_supported(self, items: List[IEntity]):
|
|
478
|
+
"""
|
|
479
|
+
Checks if all items in a list are supported by the platform.
|
|
480
|
+
|
|
481
|
+
Args:
|
|
482
|
+
items: Items to verify
|
|
483
|
+
|
|
484
|
+
Returns:
|
|
485
|
+
True if items supported, false otherwise
|
|
486
|
+
|
|
487
|
+
Raises:
|
|
488
|
+
ValueError: If the item type is not supported
|
|
489
|
+
"""
|
|
490
|
+
for item in items:
|
|
491
|
+
if item.item_type not in self.platform_type_map.values():
|
|
492
|
+
raise ValueError(
|
|
493
|
+
f'Unable to create items of type: {item.item_type} for platform: {self.__class__.__name__}')
|
|
494
|
+
|
|
495
|
+
def run_items(self, items: Union[IEntity, List[IEntity]], **kwargs):
|
|
496
|
+
"""
|
|
497
|
+
Run items on the platform.
|
|
498
|
+
|
|
499
|
+
Args:
|
|
500
|
+
items: Items to run
|
|
501
|
+
|
|
502
|
+
Returns:
|
|
503
|
+
None
|
|
504
|
+
"""
|
|
505
|
+
if isinstance(items, IEntity):
|
|
506
|
+
items = [items]
|
|
507
|
+
self._is_item_list_supported(items)
|
|
508
|
+
|
|
509
|
+
for item in items:
|
|
510
|
+
item.platform = self
|
|
511
|
+
item._platform_directory = None
|
|
512
|
+
interface = ITEM_TYPE_TO_OBJECT_INTERFACE[item.item_type]
|
|
513
|
+
getattr(self, interface).run_item(item, **kwargs)
|
|
514
|
+
|
|
515
|
+
def __repr__(self):
|
|
516
|
+
"""Platform as string."""
|
|
517
|
+
return f"<Platform {self.__class__.__name__} - id: {self.uid}>"
|
|
518
|
+
|
|
519
|
+
def _convert_platform_item_to_entity(self, platform_item: Any, **kwargs) -> IEntity:
|
|
520
|
+
"""
|
|
521
|
+
Convert a Native Platform Object to an idmtools object.
|
|
522
|
+
|
|
523
|
+
Args:
|
|
524
|
+
platform_item: Item to convert
|
|
525
|
+
**kwargs: Optional items to be used in to_entity calls
|
|
526
|
+
|
|
527
|
+
Returns:
|
|
528
|
+
IDMTools representation of object
|
|
529
|
+
"""
|
|
530
|
+
for src_type, dest_type in self.platform_type_map.items():
|
|
531
|
+
if isinstance(platform_item, src_type):
|
|
532
|
+
interface = ITEM_TYPE_TO_OBJECT_INTERFACE[dest_type]
|
|
533
|
+
return getattr(self, interface).to_entity(platform_item, **kwargs)
|
|
534
|
+
return platform_item
|
|
535
|
+
|
|
536
|
+
def validate_item_for_analysis(self, item: object, analyze_failed_items=False):
|
|
537
|
+
"""
|
|
538
|
+
Check if item is valid for analysis.
|
|
539
|
+
|
|
540
|
+
Args:
|
|
541
|
+
item: Which item to flatten
|
|
542
|
+
analyze_failed_items: bool
|
|
543
|
+
|
|
544
|
+
Returns: bool
|
|
545
|
+
|
|
546
|
+
"""
|
|
547
|
+
result = False
|
|
548
|
+
if item.succeeded:
|
|
549
|
+
result = True
|
|
550
|
+
else:
|
|
551
|
+
if analyze_failed_items and item.status == EntityStatus.FAILED:
|
|
552
|
+
result = True
|
|
553
|
+
|
|
554
|
+
return result
|
|
555
|
+
|
|
556
|
+
def flatten_item(self, item: object, **kwargs) -> List[object]:
|
|
557
|
+
"""
|
|
558
|
+
Flatten an item: resolve the children until getting to the leaves.
|
|
559
|
+
|
|
560
|
+
For example, for an experiment, will return all the simulations.
|
|
561
|
+
For a suite, will return all the simulations contained in the suites experiments.
|
|
562
|
+
|
|
563
|
+
Args:
|
|
564
|
+
item: Which item to flatten
|
|
565
|
+
kwargs: extra parameters
|
|
566
|
+
|
|
567
|
+
Returns:
|
|
568
|
+
List of leaves
|
|
569
|
+
|
|
570
|
+
"""
|
|
571
|
+
children = self.get_children(item.uid, item.item_type, force=True)
|
|
572
|
+
if children is None or (isinstance(children, list) and len(children) == 0):
|
|
573
|
+
items = [item]
|
|
574
|
+
else:
|
|
575
|
+
items = list()
|
|
576
|
+
for child in children:
|
|
577
|
+
items += self.flatten_item(item=child)
|
|
578
|
+
return items
|
|
579
|
+
|
|
580
|
+
def refresh_status(self, item: IEntity) -> NoReturn:
|
|
581
|
+
"""
|
|
582
|
+
Populate the platform item and specified item with its status.
|
|
583
|
+
|
|
584
|
+
Args:
|
|
585
|
+
item: The item to check status for.
|
|
586
|
+
"""
|
|
587
|
+
self.validate_type(item)
|
|
588
|
+
interface = ITEM_TYPE_TO_OBJECT_INTERFACE[item.item_type]
|
|
589
|
+
if item.platform is None:
|
|
590
|
+
item.platform = self
|
|
591
|
+
getattr(self, interface).refresh_status(item)
|
|
592
|
+
|
|
593
|
+
def get_files(self, item: IEntity, files: Union[Set[str], List[str]], output: str = None, **kwargs) -> \
|
|
594
|
+
Union[Dict[str, Dict[str, bytearray]], Dict[str, bytearray]]:
|
|
595
|
+
"""
|
|
596
|
+
Get files for a platform entity.
|
|
597
|
+
|
|
598
|
+
Args:
|
|
599
|
+
item: Item to fetch files for
|
|
600
|
+
files: List of file names to get
|
|
601
|
+
output: save files to
|
|
602
|
+
kwargs: Platform arguments
|
|
603
|
+
|
|
604
|
+
Returns:
|
|
605
|
+
For simulations, this returns a dictionary with filename as key and values being binary data from file or a
|
|
606
|
+
dict.
|
|
607
|
+
|
|
608
|
+
For experiments, this returns a dictionary with key as sim id and then the values as a dict of the
|
|
609
|
+
simulations described above
|
|
610
|
+
"""
|
|
611
|
+
self.validate_type(item)
|
|
612
|
+
interface = ITEM_TYPE_TO_OBJECT_INTERFACE[item.item_type]
|
|
613
|
+
ret = getattr(self, interface).get_assets(item, files, **kwargs)
|
|
614
|
+
|
|
615
|
+
if output:
|
|
616
|
+
if item.item_type not in (ItemType.SIMULATION, ItemType.WORKFLOW_ITEM, ItemType.ASSETCOLLECTION):
|
|
617
|
+
user_logger.info("Currently 'output' only supports Simulation, WorkItem and AssetCollection!")
|
|
618
|
+
else:
|
|
619
|
+
for ofi, ofc in ret.items():
|
|
620
|
+
file_path = os.path.join(output, str(item.uid), ofi)
|
|
621
|
+
parent_path = os.path.dirname(file_path)
|
|
622
|
+
if not os.path.exists(parent_path):
|
|
623
|
+
os.makedirs(parent_path)
|
|
624
|
+
|
|
625
|
+
with open(file_path, 'wb') as outfile:
|
|
626
|
+
outfile.write(ofc)
|
|
627
|
+
|
|
628
|
+
return ret
|
|
629
|
+
|
|
630
|
+
def get_files_by_id(self, item_id: str, item_type: ItemType, files: Union[Set[str], List[str]],
|
|
631
|
+
output: str = None) -> \
|
|
632
|
+
Union[Dict[str, Dict[str, bytearray]], Dict[str, bytearray]]:
|
|
633
|
+
"""
|
|
634
|
+
Get files by item id (str).
|
|
635
|
+
|
|
636
|
+
Args:
|
|
637
|
+
item_id: COMPS Item, say, Simulation Id or WorkItem Id
|
|
638
|
+
item_type: Item Type
|
|
639
|
+
files: List of files to retrieve
|
|
640
|
+
output: save files to
|
|
641
|
+
|
|
642
|
+
Returns: dict with key/value: file_name/file_content
|
|
643
|
+
"""
|
|
644
|
+
if item_id is None or item_id == "":
|
|
645
|
+
raise ValueError("item_id cannot be None or empty")
|
|
646
|
+
idm_item = self.get_item(item_id, item_type, raw=True)
|
|
647
|
+
return self.get_files(idm_item, files, output)
|
|
648
|
+
|
|
649
|
+
def are_requirements_met(self, requirements: Union[PlatformRequirements, Set[PlatformRequirements]]) -> bool:
|
|
650
|
+
"""
|
|
651
|
+
Does the platform support the list of requirements.
|
|
652
|
+
|
|
653
|
+
Args:
|
|
654
|
+
requirements: Requirements should be a list of PlatformRequirements or a single PlatformRequirements
|
|
655
|
+
|
|
656
|
+
Returns:
|
|
657
|
+
True if all the requirements are supported
|
|
658
|
+
"""
|
|
659
|
+
if isinstance(requirements, PlatformRequirements):
|
|
660
|
+
requirements = [requirements]
|
|
661
|
+
return all([x in self._platform_supports for x in requirements])
|
|
662
|
+
|
|
663
|
+
def is_task_supported(self, task: ITask) -> bool:
|
|
664
|
+
"""
|
|
665
|
+
Is a task supported on this platform.
|
|
666
|
+
|
|
667
|
+
This depends on the task properly setting its requirements. See :py:attr:`idmtools.entities.itask.ITask.platform_requirements` and
|
|
668
|
+
:py:class:`idmtools.entities.platform_requirements.PlatformRequirements`
|
|
669
|
+
|
|
670
|
+
Args:
|
|
671
|
+
task: Task to check support of
|
|
672
|
+
|
|
673
|
+
Returns:
|
|
674
|
+
True if the task is supported, False otherwise.
|
|
675
|
+
"""
|
|
676
|
+
return self.are_requirements_met(task.platform_requirements)
|
|
677
|
+
|
|
678
|
+
def __wait_till_callback(
|
|
679
|
+
self, item: Union[Experiment, IWorkflowItem, Suite],
|
|
680
|
+
callback: Union[partial, Callable[[Union[Experiment, IWorkflowItem, Suite]], bool]],
|
|
681
|
+
timeout: int = 60 * 60 * 24,
|
|
682
|
+
refresh_interval: int = 5
|
|
683
|
+
):
|
|
684
|
+
"""
|
|
685
|
+
Runs a loop until a timeout is met where the item's status is refreshed. A callback is then called with the items as the arguments and if the returns is true, we stop waiting.
|
|
686
|
+
|
|
687
|
+
Args:
|
|
688
|
+
item: Item to monitor
|
|
689
|
+
callback: Callback to determine if item is done. It should return true is item is complete
|
|
690
|
+
timeout: Timeout for waiting. Defaults to 24 hours
|
|
691
|
+
refresh_interval: Refresh the status how often
|
|
692
|
+
|
|
693
|
+
Returns:
|
|
694
|
+
None
|
|
695
|
+
|
|
696
|
+
Raises:
|
|
697
|
+
TimeoutError: If a timeout occurs
|
|
698
|
+
|
|
699
|
+
See Also:
|
|
700
|
+
:meth:`idmtools.entities.iplatform.IPlatform.wait_till_done_progress`
|
|
701
|
+
:meth:`idmtools.entities.iplatform.IPlatform.__wait_until_done_progress_callback`
|
|
702
|
+
:meth:`idmtools.entities.iplatform.IPlatform.wait_till_done`
|
|
703
|
+
"""
|
|
704
|
+
import time
|
|
705
|
+
start_time = time.time()
|
|
706
|
+
while time.time() - start_time < timeout:
|
|
707
|
+
if logger.isEnabledFor(DEBUG):
|
|
708
|
+
logger.debug("Refreshing simulation status")
|
|
709
|
+
self.refresh_status(item)
|
|
710
|
+
if callback(item):
|
|
711
|
+
return
|
|
712
|
+
time.sleep(refresh_interval)
|
|
713
|
+
raise TimeoutError(f"Timeout of {timeout} seconds exceeded")
|
|
714
|
+
|
|
715
|
+
def wait_till_done(self, item: IRunnableEntity, timeout: int = 60 * 60 * 24,
|
|
716
|
+
refresh_interval: int = 5, progress: bool = True):
|
|
717
|
+
"""
|
|
718
|
+
Wait for the experiment to be done.
|
|
719
|
+
|
|
720
|
+
Args:
|
|
721
|
+
item: Experiment/Workitem to wait on
|
|
722
|
+
refresh_interval: How long to wait between polling.
|
|
723
|
+
timeout: How long to wait before failing.
|
|
724
|
+
progress: Should we display progress
|
|
725
|
+
|
|
726
|
+
See Also:
|
|
727
|
+
:meth:`idmtools.entities.iplatform.IPlatform.wait_till_done_progress`
|
|
728
|
+
:meth:`idmtools.entities.iplatform.IPlatform.__wait_until_done_progress_callback`
|
|
729
|
+
:meth:`idmtools.entities.iplatform.IPlatform.__wait_till_callback`
|
|
730
|
+
"""
|
|
731
|
+
if progress:
|
|
732
|
+
self.wait_till_done_progress(item, timeout, refresh_interval)
|
|
733
|
+
else:
|
|
734
|
+
self.__wait_till_callback(item, lambda e: e.done, timeout, refresh_interval)
|
|
735
|
+
|
|
736
|
+
@staticmethod
|
|
737
|
+
def __wait_until_done_progress_callback(item: Union[Experiment, IWorkflowItem], progress_bar: 'tqdm', # noqa: F821
|
|
738
|
+
child_attribute: str = 'simulations',
|
|
739
|
+
done_states: List[EntityStatus] = None,
|
|
740
|
+
failed_warning: Dict[str, bool] = False) -> bool:
|
|
741
|
+
"""
|
|
742
|
+
A callback for progress bar(when an item has children) and checking if an item has completed execution.
|
|
743
|
+
|
|
744
|
+
This is mainly meant for aggregate types where the status is from the children.
|
|
745
|
+
|
|
746
|
+
Args:
|
|
747
|
+
item: Item to monitor
|
|
748
|
+
progress_bar:
|
|
749
|
+
child_attribute: What is the name of the child attribute. For examples, if item was an Experiment, the
|
|
750
|
+
child_attribute would be 'simulations'
|
|
751
|
+
done_states: What states are considered done
|
|
752
|
+
failed_warning: Used to track if we have warned user of failure. We use dict to pass by refernce since we cannot do that with a bool
|
|
753
|
+
|
|
754
|
+
Returns:
|
|
755
|
+
True is item has completed execution
|
|
756
|
+
|
|
757
|
+
See Also:
|
|
758
|
+
:meth:`idmtools.entities.iplatform.IPlatform.wait_till_done_progress`
|
|
759
|
+
:meth:`idmtools.entities.iplatform.IPlatform.wait_till_done`
|
|
760
|
+
:meth:`idmtools.entities.iplatform.IPlatform.__wait_till_callback`
|
|
761
|
+
"""
|
|
762
|
+
# ensure we have done states. Default to failed or SUCCEEDED
|
|
763
|
+
if done_states is None:
|
|
764
|
+
done_states = [EntityStatus.FAILED, EntityStatus.SUCCEEDED]
|
|
765
|
+
# if we do not have a progress bar, return items state
|
|
766
|
+
if child_attribute is None:
|
|
767
|
+
if isinstance(item, IWorkflowItem):
|
|
768
|
+
if item.status in done_states:
|
|
769
|
+
if progress_bar:
|
|
770
|
+
progress_bar.update(1)
|
|
771
|
+
progress_bar.close()
|
|
772
|
+
return True
|
|
773
|
+
return False
|
|
774
|
+
else:
|
|
775
|
+
return item.done
|
|
776
|
+
|
|
777
|
+
# if we do have a progress bar, update it
|
|
778
|
+
done = 0
|
|
779
|
+
# iterate over the children
|
|
780
|
+
for child in getattr(item, child_attribute):
|
|
781
|
+
# if the item is an experiment, use the status
|
|
782
|
+
if isinstance(item, Experiment) and child.status in done_states:
|
|
783
|
+
done += 1
|
|
784
|
+
# otherwise use the done attribute
|
|
785
|
+
elif isinstance(item, Suite) and child.done:
|
|
786
|
+
done += 1
|
|
787
|
+
# check if we need to update the progress bar
|
|
788
|
+
if hasattr(progress_bar, 'last_print_n') and done > progress_bar.last_print_n:
|
|
789
|
+
progress_bar.update(done - progress_bar.last_print_n)
|
|
790
|
+
# Alert user to failing simulations so they can stop execution if wanted
|
|
791
|
+
if isinstance(item, Experiment) and item.any_failed and not failed_warning['failed_warning']:
|
|
792
|
+
user_logger.warning(f"The Experiment {item.uid} has failed simulations. Check Experiment in platform")
|
|
793
|
+
failed_warning['failed_warning'] = True
|
|
794
|
+
return item.done
|
|
795
|
+
|
|
796
|
+
def wait_till_done_progress(self, item: IRunnableEntity, timeout: int = 60 * 60 * 24, refresh_interval: int = 5,
|
|
797
|
+
wait_progress_desc: str = None):
|
|
798
|
+
"""
|
|
799
|
+
Wait on an item to complete with progress bar.
|
|
800
|
+
|
|
801
|
+
Args:
|
|
802
|
+
item: Item to monitor
|
|
803
|
+
timeout: Timeout on waiting
|
|
804
|
+
refresh_interval: How often to refresh
|
|
805
|
+
wait_progress_desc: Wait Progress Description
|
|
806
|
+
|
|
807
|
+
Returns:
|
|
808
|
+
None
|
|
809
|
+
|
|
810
|
+
See Also:
|
|
811
|
+
:meth:`idmtools.entities.iplatform.IPlatform.__wait_until_done_progress_callback`
|
|
812
|
+
:meth:`idmtools.entities.iplatform.IPlatform.wait_till_done`
|
|
813
|
+
:meth:`idmtools.entities.iplatform.IPlatform.__wait_till_callback`
|
|
814
|
+
"""
|
|
815
|
+
# set prog to list
|
|
816
|
+
prog = []
|
|
817
|
+
# check that the user has not disable progress bars
|
|
818
|
+
child_attribute = None
|
|
819
|
+
if not IdmConfigParser.is_progress_bar_disabled():
|
|
820
|
+
from tqdm import tqdm
|
|
821
|
+
if isinstance(item, Experiment):
|
|
822
|
+
prog = tqdm([], total=len(item.simulations),
|
|
823
|
+
desc=wait_progress_desc if wait_progress_desc else f"Waiting on Experiment {item.name} to Finish running",
|
|
824
|
+
unit="simulation")
|
|
825
|
+
child_attribute = 'simulations'
|
|
826
|
+
elif isinstance(item, Suite):
|
|
827
|
+
prog = tqdm([], total=len(item.experiments),
|
|
828
|
+
desc=wait_progress_desc if wait_progress_desc else f"Waiting on Suite {item.name} to Finish running",
|
|
829
|
+
unit="experiment")
|
|
830
|
+
child_attribute = 'experiments'
|
|
831
|
+
elif isinstance(item, IWorkflowItem):
|
|
832
|
+
prog = tqdm([], total=1,
|
|
833
|
+
desc=wait_progress_desc if wait_progress_desc else f"Waiting on WorkItem {item.name}",
|
|
834
|
+
unit="workitem")
|
|
835
|
+
else:
|
|
836
|
+
child_attribute = None
|
|
837
|
+
|
|
838
|
+
failed_warning = dict(failed_warning=False)
|
|
839
|
+
self.__wait_till_callback(
|
|
840
|
+
item,
|
|
841
|
+
partial(self.__wait_until_done_progress_callback, progress_bar=prog, child_attribute=child_attribute,
|
|
842
|
+
failed_warning=failed_warning),
|
|
843
|
+
timeout,
|
|
844
|
+
refresh_interval
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
def get_related_items(self, item: IWorkflowItem, relation_type: RelationType) -> Dict[str, Dict[str, str]]:
|
|
848
|
+
"""
|
|
849
|
+
Retrieve all related objects.
|
|
850
|
+
|
|
851
|
+
Args:
|
|
852
|
+
item: SSMTWorkItem
|
|
853
|
+
relation_type: Depends or Create
|
|
854
|
+
|
|
855
|
+
Returns: dict with key the object type
|
|
856
|
+
"""
|
|
857
|
+
self.validate_type(item, ItemType.WORKFLOW_ITEM)
|
|
858
|
+
interface = ITEM_TYPE_TO_OBJECT_INTERFACE[item.item_type]
|
|
859
|
+
return getattr(self, interface).get_related_items(item, relation_type)
|
|
860
|
+
|
|
861
|
+
def validate_type(self, item: Union[IEntity, ItemType], target: ItemType = None) -> NoReturn:
|
|
862
|
+
"""
|
|
863
|
+
Validate if the item is supported by the platform.
|
|
864
|
+
|
|
865
|
+
Args:
|
|
866
|
+
item: Item to validate
|
|
867
|
+
target: Target type to validate against
|
|
868
|
+
|
|
869
|
+
Returns:
|
|
870
|
+
No return
|
|
871
|
+
"""
|
|
872
|
+
valid = True
|
|
873
|
+
_type = item if isinstance(item, (str, ItemType)) else item.item_type
|
|
874
|
+
if target is not None and _type != target:
|
|
875
|
+
valid = False
|
|
876
|
+
elif _type not in self.platform_type_map.values():
|
|
877
|
+
valid = False
|
|
878
|
+
|
|
879
|
+
if not valid:
|
|
880
|
+
raise UnsupportedPlatformType(
|
|
881
|
+
f"The provided type {_type} is invalid or not supported by platform {self.__class__.__name__}. It only supports ItemType: {', '.join([value.name for value in self.platform_type_map.values()])}")
|
|
882
|
+
|
|
883
|
+
def __enter__(self):
|
|
884
|
+
"""
|
|
885
|
+
Enable our platform to work on contexts.
|
|
886
|
+
|
|
887
|
+
Returns:
|
|
888
|
+
Platform
|
|
889
|
+
"""
|
|
890
|
+
set_current_platform(self)
|
|
891
|
+
return self
|
|
892
|
+
|
|
893
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
894
|
+
"""
|
|
895
|
+
Enable our platform to work on contexts.
|
|
896
|
+
|
|
897
|
+
Args:
|
|
898
|
+
exc_type: Type of exception type
|
|
899
|
+
exc_val:Value
|
|
900
|
+
exc_tb: Traceback
|
|
901
|
+
|
|
902
|
+
Returns:
|
|
903
|
+
None
|
|
904
|
+
"""
|
|
905
|
+
from idmtools.core.context import remove_current_platform
|
|
906
|
+
remove_current_platform()
|
|
907
|
+
|
|
908
|
+
def is_regather_assets_on_modify(self) -> bool:
|
|
909
|
+
"""
|
|
910
|
+
Return default behaviour for platform when rerunning experiment and gathering assets.
|
|
911
|
+
|
|
912
|
+
Returns:
|
|
913
|
+
True or false
|
|
914
|
+
"""
|
|
915
|
+
return self._regather_assets_on_modify
|
|
916
|
+
|
|
917
|
+
def is_windows_platform(self, item: IEntity = None) -> bool:
|
|
918
|
+
"""
|
|
919
|
+
Returns is the target platform is a windows system.
|
|
920
|
+
"""
|
|
921
|
+
return self.are_requirements_met(PlatformRequirements.WINDOWS)
|
|
922
|
+
|
|
923
|
+
@property
|
|
924
|
+
def common_asset_path(self):
|
|
925
|
+
"""
|
|
926
|
+
Return the path to common assets stored on the platform.
|
|
927
|
+
|
|
928
|
+
Returns:
|
|
929
|
+
Common Asset Path
|
|
930
|
+
"""
|
|
931
|
+
return self._common_asset_path
|
|
932
|
+
|
|
933
|
+
@common_asset_path.setter
|
|
934
|
+
def common_asset_path(self, value):
|
|
935
|
+
"""
|
|
936
|
+
Set path to common assets stored on the platform. This path should be in defined in relation to individual items(simulations, workflow items).
|
|
937
|
+
|
|
938
|
+
For example, on COMPS, we would set "Assets"
|
|
939
|
+
|
|
940
|
+
Args:
|
|
941
|
+
value: Path to use for common path
|
|
942
|
+
|
|
943
|
+
Returns:
|
|
944
|
+
None
|
|
945
|
+
"""
|
|
946
|
+
if not isinstance(value, property):
|
|
947
|
+
logger.warning("Cannot set common asset path")
|
|
948
|
+
|
|
949
|
+
def join_path(self, *args) -> str:
|
|
950
|
+
"""
|
|
951
|
+
Join path using platform rules.
|
|
952
|
+
|
|
953
|
+
Args:
|
|
954
|
+
*args:List of paths to join
|
|
955
|
+
|
|
956
|
+
Returns:
|
|
957
|
+
Joined path as string
|
|
958
|
+
"""
|
|
959
|
+
if len(args) < 2:
|
|
960
|
+
raise ValueError("at least two items required to join")
|
|
961
|
+
if self.is_windows_platform():
|
|
962
|
+
return str(PureWindowsPath(*args))
|
|
963
|
+
else:
|
|
964
|
+
return str(PurePath(*args))
|
|
965
|
+
|
|
966
|
+
def id_from_file(self, filename: str):
|
|
967
|
+
"""
|
|
968
|
+
Load just the id portion of an id file.
|
|
969
|
+
|
|
970
|
+
Args:
|
|
971
|
+
filename: Filename
|
|
972
|
+
|
|
973
|
+
Returns:
|
|
974
|
+
Item id laoded from file
|
|
975
|
+
"""
|
|
976
|
+
item_id, item_type, platform_block, extra_args = read_id_file(filename)
|
|
977
|
+
return item_id
|
|
978
|
+
|
|
979
|
+
def get_item_from_id_file(self, id_filename: Union[PathLike, str], item_type: Optional[ItemType] = None) -> IEntity:
|
|
980
|
+
"""
|
|
981
|
+
Load an item from an id file. This ignores the platform in the file.
|
|
982
|
+
|
|
983
|
+
Args:
|
|
984
|
+
id_filename: Filename to load
|
|
985
|
+
item_type: Optional item type
|
|
986
|
+
|
|
987
|
+
Returns:
|
|
988
|
+
Item from id file.
|
|
989
|
+
"""
|
|
990
|
+
item_id, file_item_type, platform_block, extra_args = read_id_file(id_filename)
|
|
991
|
+
return self.get_item(item_id, item_type if item_type else ItemType[file_item_type.upper()])
|
|
992
|
+
|
|
993
|
+
def get_defaults_by_type(self, default_type: Type) -> List[IPlatformDefault]:
|
|
994
|
+
"""
|
|
995
|
+
Returns any platform defaults for specific types.
|
|
996
|
+
Args:
|
|
997
|
+
default_type: Default type
|
|
998
|
+
|
|
999
|
+
Returns:
|
|
1000
|
+
List of default of that type
|
|
1001
|
+
"""
|
|
1002
|
+
return [x for x in self._platform_defaults if isinstance(x, default_type)]
|
|
1003
|
+
|
|
1004
|
+
def create_sim_directory_map(self, item_id: str, item_type: ItemType) -> Dict:
|
|
1005
|
+
"""
|
|
1006
|
+
Build simulation working directory mapping.
|
|
1007
|
+
Args:
|
|
1008
|
+
item_id: Entity id
|
|
1009
|
+
item_type: ItemType
|
|
1010
|
+
Returns:
|
|
1011
|
+
Dict of simulation id as key and working dir as value
|
|
1012
|
+
"""
|
|
1013
|
+
interface = ITEM_TYPE_TO_OBJECT_INTERFACE[item_type]
|
|
1014
|
+
return getattr(self, interface).create_sim_directory_map(item_id)
|
|
1015
|
+
|
|
1016
|
+
def create_sim_directory_df(self, exp_id: str, include_tags: bool = True) -> pd.DataFrame:
|
|
1017
|
+
"""
|
|
1018
|
+
Build simulation working directory mapping.
|
|
1019
|
+
Args:
|
|
1020
|
+
exp_id: experiment id
|
|
1021
|
+
include_tags: True/False
|
|
1022
|
+
Returns:
|
|
1023
|
+
DataFrame
|
|
1024
|
+
"""
|
|
1025
|
+
tag_df = None
|
|
1026
|
+
if include_tags:
|
|
1027
|
+
tags_list = []
|
|
1028
|
+
sims = self.get_children(exp_id, ItemType.EXPERIMENT)
|
|
1029
|
+
for sim in sims:
|
|
1030
|
+
tags = copy.deepcopy(sim.tags)
|
|
1031
|
+
tags["simid"] = sim.id
|
|
1032
|
+
tags_list.append(tags)
|
|
1033
|
+
tag_df = pd.DataFrame(tags_list)
|
|
1034
|
+
|
|
1035
|
+
dir_map = self.create_sim_directory_map(exp_id, ItemType.EXPERIMENT)
|
|
1036
|
+
|
|
1037
|
+
dir_list = [dict(simid=sim_id, outpath=str(path)) for sim_id, path in dir_map.items()]
|
|
1038
|
+
dir_df = pd.DataFrame(dir_list)
|
|
1039
|
+
|
|
1040
|
+
if tag_df is not None and len(tag_df) > 0:
|
|
1041
|
+
result_df = pd.merge(left=tag_df, right=dir_df, on='simid')
|
|
1042
|
+
else:
|
|
1043
|
+
result_df = dir_df
|
|
1044
|
+
|
|
1045
|
+
return result_df
|
|
1046
|
+
|
|
1047
|
+
def save_sim_directory_df_to_csv(self, exp_id: str, include_tags: bool = True,
|
|
1048
|
+
output: str = os.getcwd(), save_header=False, file_name: str = None) -> None:
|
|
1049
|
+
"""
|
|
1050
|
+
Save simulation directory df to csv file.
|
|
1051
|
+
Args:
|
|
1052
|
+
exp_id: experiment id
|
|
1053
|
+
include_tags: True/False
|
|
1054
|
+
output: output directory
|
|
1055
|
+
save_header: True/False
|
|
1056
|
+
file_name: user csv file name
|
|
1057
|
+
Returns:
|
|
1058
|
+
None
|
|
1059
|
+
"""
|
|
1060
|
+
df = self.create_sim_directory_df(exp_id, include_tags=include_tags)
|
|
1061
|
+
try:
|
|
1062
|
+
os.mkdir(output)
|
|
1063
|
+
except OSError:
|
|
1064
|
+
pass
|
|
1065
|
+
|
|
1066
|
+
if file_name is None:
|
|
1067
|
+
file_name = f'{exp_id}.csv'
|
|
1068
|
+
df.to_csv(os.path.join(output, file_name), header=save_header, index=False)
|
|
1069
|
+
|
|
1070
|
+
def filter_simulations_by_tags(self, item_id: str, item_type: ItemType, tags: Dict = None, status=None,
|
|
1071
|
+
entity_type=False, skip_sims=None, max_simulations=None, **kwargs):
|
|
1072
|
+
"""
|
|
1073
|
+
Filter simulations associated with a given Experiment or Suite using tag-based conditions.
|
|
1074
|
+
|
|
1075
|
+
This method is a platform-level convenience wrapper that delegates filtering logic to the
|
|
1076
|
+
`get_simulations_by_tags` method on the retrieved item. It supports:
|
|
1077
|
+
- Exact tag value matching
|
|
1078
|
+
- Callable filters for flexible conditions (e.g., lambda expressions)
|
|
1079
|
+
- Optionally limiting the number of returned simulations
|
|
1080
|
+
- Returning either simulation entities or just their IDs
|
|
1081
|
+
|
|
1082
|
+
Args:
|
|
1083
|
+
item_id (str): The unique ID of the Experiment or Suite.
|
|
1084
|
+
item_type (ItemType): The type of the item (ItemType.EXPERIMENT or ItemType.SUITE).
|
|
1085
|
+
tags (Dict, optional): Dictionary of tag filters to apply. Values can be:
|
|
1086
|
+
- Exact values (e.g., {"Coverage": 0.8})
|
|
1087
|
+
- Callable functions (e.g., {"Run_Number": lambda v: 0 <= v <= 10})
|
|
1088
|
+
- Ellipsis (...) or None to match presence of key only.
|
|
1089
|
+
status (EntityStatus, optional): Filter by status. If provided, only simulations with the specified status will be returned.
|
|
1090
|
+
entity_type (bool, optional): If True, return full simulation entities; otherwise, return simulation IDs.
|
|
1091
|
+
skip_sims (list, optional): A list of simulation IDs to exclude from the results.
|
|
1092
|
+
max_simulations (int, optional): Maximum number of simulations to return.
|
|
1093
|
+
**kwargs: Additional keyword arguments passed to the item's `get_simulations_by_tags` method.
|
|
1094
|
+
|
|
1095
|
+
Returns:
|
|
1096
|
+
Union[List[str], List[Simulation], Dict[str, List[Simulation]]]:
|
|
1097
|
+
- A list of simulation IDs (default),
|
|
1098
|
+
- Or a list/dictionary of Simulation objects if `entity_type=True`.
|
|
1099
|
+
"""
|
|
1100
|
+
item = self.get_item(item_id, item_type, force=True)
|
|
1101
|
+
return item.get_simulations_by_tags(tags=tags, status=status, entity_type=entity_type, skip_sims=skip_sims,
|
|
1102
|
+
max_simulations=max_simulations, **kwargs)
|
|
1103
|
+
|
|
1104
|
+
|
|
1105
|
+
TPlatform = TypeVar("TPlatform", bound=IPlatform)
|
|
1106
|
+
TPlatformClass = Type[TPlatform]
|