idmtools 0.0.0.dev0__py3-none-any.whl → 0.0.3__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.
Files changed (118) hide show
  1. idmtools/__init__.py +27 -8
  2. idmtools/analysis/__init__.py +5 -0
  3. idmtools/analysis/add_analyzer.py +89 -0
  4. idmtools/analysis/analyze_manager.py +490 -0
  5. idmtools/analysis/csv_analyzer.py +103 -0
  6. idmtools/analysis/download_analyzer.py +96 -0
  7. idmtools/analysis/map_worker_entry.py +100 -0
  8. idmtools/analysis/platform_analysis_bootstrap.py +94 -0
  9. idmtools/analysis/platform_anaylsis.py +291 -0
  10. idmtools/analysis/tags_analyzer.py +93 -0
  11. idmtools/assets/__init__.py +9 -0
  12. idmtools/assets/asset.py +453 -0
  13. idmtools/assets/asset_collection.py +514 -0
  14. idmtools/assets/content_handlers.py +19 -0
  15. idmtools/assets/errors.py +23 -0
  16. idmtools/assets/file_list.py +191 -0
  17. idmtools/builders/__init__.py +11 -0
  18. idmtools/builders/arm_simulation_builder.py +152 -0
  19. idmtools/builders/csv_simulation_builder.py +76 -0
  20. idmtools/builders/simulation_builder.py +348 -0
  21. idmtools/builders/sweep_arm.py +109 -0
  22. idmtools/builders/yaml_simulation_builder.py +82 -0
  23. idmtools/config/__init__.py +7 -0
  24. idmtools/config/idm_config_parser.py +486 -0
  25. idmtools/core/__init__.py +10 -0
  26. idmtools/core/cache_enabled.py +114 -0
  27. idmtools/core/context.py +68 -0
  28. idmtools/core/docker_task.py +207 -0
  29. idmtools/core/enums.py +51 -0
  30. idmtools/core/exceptions.py +91 -0
  31. idmtools/core/experiment_factory.py +71 -0
  32. idmtools/core/id_file.py +70 -0
  33. idmtools/core/interfaces/__init__.py +5 -0
  34. idmtools/core/interfaces/entity_container.py +64 -0
  35. idmtools/core/interfaces/iassets_enabled.py +58 -0
  36. idmtools/core/interfaces/ientity.py +331 -0
  37. idmtools/core/interfaces/iitem.py +206 -0
  38. idmtools/core/interfaces/imetadata_operations.py +89 -0
  39. idmtools/core/interfaces/inamed_entity.py +17 -0
  40. idmtools/core/interfaces/irunnable_entity.py +159 -0
  41. idmtools/core/logging.py +387 -0
  42. idmtools/core/platform_factory.py +316 -0
  43. idmtools/core/system_information.py +104 -0
  44. idmtools/core/task_factory.py +145 -0
  45. idmtools/entities/__init__.py +10 -0
  46. idmtools/entities/command_line.py +229 -0
  47. idmtools/entities/command_task.py +155 -0
  48. idmtools/entities/experiment.py +787 -0
  49. idmtools/entities/generic_workitem.py +43 -0
  50. idmtools/entities/ianalyzer.py +163 -0
  51. idmtools/entities/iplatform.py +1106 -0
  52. idmtools/entities/iplatform_default.py +39 -0
  53. idmtools/entities/iplatform_ops/__init__.py +5 -0
  54. idmtools/entities/iplatform_ops/iplatform_asset_collection_operations.py +148 -0
  55. idmtools/entities/iplatform_ops/iplatform_experiment_operations.py +415 -0
  56. idmtools/entities/iplatform_ops/iplatform_simulation_operations.py +315 -0
  57. idmtools/entities/iplatform_ops/iplatform_suite_operations.py +322 -0
  58. idmtools/entities/iplatform_ops/iplatform_workflowitem_operations.py +301 -0
  59. idmtools/entities/iplatform_ops/utils.py +185 -0
  60. idmtools/entities/itask.py +316 -0
  61. idmtools/entities/iworkflow_item.py +167 -0
  62. idmtools/entities/platform_requirements.py +20 -0
  63. idmtools/entities/relation_type.py +14 -0
  64. idmtools/entities/simulation.py +255 -0
  65. idmtools/entities/suite.py +188 -0
  66. idmtools/entities/task_proxy.py +37 -0
  67. idmtools/entities/templated_simulation.py +325 -0
  68. idmtools/frozen/frozen_dict.py +71 -0
  69. idmtools/frozen/frozen_list.py +66 -0
  70. idmtools/frozen/frozen_set.py +86 -0
  71. idmtools/frozen/frozen_tuple.py +18 -0
  72. idmtools/frozen/frozen_utils.py +179 -0
  73. idmtools/frozen/ifrozen.py +66 -0
  74. idmtools/plugins/__init__.py +5 -0
  75. idmtools/plugins/git_commit.py +117 -0
  76. idmtools/registry/__init__.py +4 -0
  77. idmtools/registry/experiment_specification.py +105 -0
  78. idmtools/registry/functions.py +28 -0
  79. idmtools/registry/hook_specs.py +132 -0
  80. idmtools/registry/master_plugin_registry.py +51 -0
  81. idmtools/registry/platform_specification.py +138 -0
  82. idmtools/registry/plugin_specification.py +129 -0
  83. idmtools/registry/task_specification.py +104 -0
  84. idmtools/registry/utils.py +119 -0
  85. idmtools/services/__init__.py +5 -0
  86. idmtools/services/ipersistance_service.py +135 -0
  87. idmtools/services/platforms.py +13 -0
  88. idmtools/utils/__init__.py +5 -0
  89. idmtools/utils/caller.py +24 -0
  90. idmtools/utils/collections.py +246 -0
  91. idmtools/utils/command_line.py +45 -0
  92. idmtools/utils/decorators.py +300 -0
  93. idmtools/utils/display/__init__.py +22 -0
  94. idmtools/utils/display/displays.py +181 -0
  95. idmtools/utils/display/settings.py +25 -0
  96. idmtools/utils/dropbox_location.py +30 -0
  97. idmtools/utils/entities.py +127 -0
  98. idmtools/utils/file.py +72 -0
  99. idmtools/utils/file_parser.py +151 -0
  100. idmtools/utils/filter_simulations.py +182 -0
  101. idmtools/utils/filters/__init__.py +5 -0
  102. idmtools/utils/filters/asset_filters.py +88 -0
  103. idmtools/utils/general.py +286 -0
  104. idmtools/utils/gitrepo.py +336 -0
  105. idmtools/utils/hashing.py +239 -0
  106. idmtools/utils/info.py +124 -0
  107. idmtools/utils/json.py +82 -0
  108. idmtools/utils/language.py +107 -0
  109. idmtools/utils/local_os.py +40 -0
  110. idmtools/utils/time.py +22 -0
  111. idmtools-0.0.3.dist-info/METADATA +120 -0
  112. idmtools-0.0.3.dist-info/RECORD +116 -0
  113. idmtools-0.0.3.dist-info/entry_points.txt +9 -0
  114. idmtools-0.0.3.dist-info/licenses/LICENSE.TXT +3 -0
  115. idmtools-0.0.0.dev0.dist-info/METADATA +0 -41
  116. idmtools-0.0.0.dev0.dist-info/RECORD +0 -5
  117. {idmtools-0.0.0.dev0.dist-info → idmtools-0.0.3.dist-info}/WHEEL +0 -0
  118. {idmtools-0.0.0.dev0.dist-info → idmtools-0.0.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,787 @@
1
+ """
2
+ Our Experiment class definition.
3
+
4
+ Experiments can be thought of as a metadata object analogous to a folder on a filesystem. An experiment is a container that
5
+ contains one or more simulations. Before creations, *experiment.simulations* can be either a list of a TemplatedSimulations.
6
+ TemplatedSimulations are useful for building large numbers of similar simulations such as sweeps.
7
+
8
+ Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
9
+ """
10
+ import copy
11
+ from dataclasses import dataclass, field, InitVar, fields
12
+ from logging import getLogger, DEBUG
13
+ from types import GeneratorType
14
+ from typing import NoReturn, Set, Union, Iterator, Type, Dict, Any, List, TYPE_CHECKING, Generator
15
+ from idmtools import IdmConfigParser
16
+ from idmtools.assets import AssetCollection, Asset
17
+ from idmtools.builders import SimulationBuilder
18
+ from idmtools.core import ItemType, EntityStatus
19
+ from idmtools.core.interfaces.entity_container import EntityContainer
20
+ from idmtools.core.interfaces.iassets_enabled import IAssetsEnabled
21
+ from idmtools.core.interfaces.iitem import IItem
22
+ from idmtools.core.interfaces.inamed_entity import INamedEntity
23
+ from idmtools.core.interfaces.irunnable_entity import IRunnableEntity
24
+ from idmtools.core.logging import SUCCESS, NOTICE
25
+ from idmtools.entities.itask import ITask
26
+ from idmtools.entities.platform_requirements import PlatformRequirements
27
+ from idmtools.entities.templated_simulation import TemplatedSimulations
28
+ from idmtools.registry.experiment_specification import ExperimentPluginSpecification, get_model_impl, \
29
+ get_model_type_impl
30
+ from idmtools.registry.plugin_specification import get_description_impl
31
+ from idmtools.utils.caller import get_caller
32
+ from idmtools.utils.collections import ExperimentParentIterator
33
+ from idmtools.utils.entities import get_default_tags
34
+
35
+ if TYPE_CHECKING: # pragma: no cover
36
+ from idmtools.entities.iplatform import IPlatform
37
+ from idmtools.entities.simulation import Simulation # noqa: F401
38
+ from idmtools.entities.suite import Suite # noqa: F401
39
+
40
+ logger = getLogger(__name__)
41
+ user_logger = getLogger('user')
42
+ SUPPORTED_SIM_TYPE = Union[
43
+ EntityContainer,
44
+ Generator['Simulation', None, None],
45
+ TemplatedSimulations,
46
+ Iterator['Simulation']
47
+ ]
48
+
49
+
50
+ @dataclass(repr=False)
51
+ class Experiment(IAssetsEnabled, INamedEntity, IRunnableEntity):
52
+ """
53
+ Class that represents a generic experiment.
54
+
55
+ This class needs to be implemented for each model type with specifics.
56
+
57
+ Args:
58
+ name: The experiment name.
59
+ assets: The asset collection for assets global to this experiment.
60
+ """
61
+ #: Suite ID
62
+ suite_id: str = field(default=None)
63
+ #: Item Item(always an experiment)
64
+ item_type: ItemType = field(default=ItemType.EXPERIMENT, compare=False, init=False)
65
+ #: Task Type(defaults to command)
66
+ task_type: str = field(default='idmtools.entities.command_task.CommandTask')
67
+ #: List of Requirements for the task that a platform must meet to be able to run
68
+ platform_requirements: Set[PlatformRequirements] = field(default_factory=set)
69
+ #: Is the Experiment Frozen
70
+ frozen: bool = field(default=False, init=False)
71
+ #: Simulation in this experiment
72
+ simulations: InitVar[SUPPORTED_SIM_TYPE] = None
73
+ #: Internal storage of simulation
74
+ __simulations: SUPPORTED_SIM_TYPE = field(default_factory=lambda: EntityContainer(), compare=False)
75
+
76
+ #: Determines if we should gather assets from the first task. Only use when not using TemplatedSimulations
77
+ gather_common_assets_from_task: bool = field(default=None, compare=False)
78
+
79
+ #: Determines if we should gather assets from the first task. Only use when not using TemplatedSimulations
80
+ disable_default_pre_create: bool = field(default=False, compare=False)
81
+
82
+ #: Enable replacing the task with a proxy to reduce the memory footprint. Useful in provisioning large sets of
83
+ # simulations
84
+ __replace_task_with_proxy: bool = field(default=True, init=False, compare=False)
85
+
86
+ def __post_init__(self, simulations):
87
+ """
88
+ Initialize Experiment.
89
+
90
+ Args:
91
+ simulations: Simulations to initialize with
92
+
93
+ Returns:
94
+ None
95
+ """
96
+ super().__post_init__()
97
+ if simulations is not None and not isinstance(simulations, property):
98
+ self.simulations = simulations
99
+
100
+ if self.gather_common_assets_from_task is None:
101
+ self.gather_common_assets_from_task = isinstance(self.simulations.items, EntityContainer)
102
+ self.__simulations.parent = self
103
+
104
+ def post_creation(self, platform: 'IPlatform') -> None:
105
+ """
106
+ Post creation of experiments.
107
+
108
+ Args:
109
+ platform: Platform the experiment was created on
110
+
111
+ Returns:
112
+ None
113
+ """
114
+ IItem.post_creation(self, platform)
115
+
116
+ @property
117
+ def status(self):
118
+ """
119
+ Get status of experiment. Experiment status is based in simulations.
120
+
121
+ The first rule to be true is used. The rules are:
122
+ * If simulations is a TemplatedSimulations we assume status is None if _platform_object is not set.
123
+ * If simulations is a TemplatedSimulations we assume status is CREATED if _platform_object is set.
124
+ * If simulations length is 0 or all simulations have a status of None, experiment status is none
125
+ * If any simulation has a running status, experiment is considered running.
126
+ * If any simulation has a created status and any other simulation has a FAILED or SUCCEEDED status, experiment is considered running.
127
+ * If any simulation has a None status and any other simulation has a FAILED or SUCCEEDED status, experiment is considered Created.
128
+ * If any simulation has a status of failed, experiment is considered failed.
129
+ * If any simulation has a status of SUCCEEDED, experiment is considered SUCCEEDED.
130
+ * If any simulation has a status of CREATED, experiment is considered CREATED.
131
+
132
+
133
+ Returns:
134
+ Status
135
+ """
136
+ # still creating sims since we have a template. When adding new simulations, we will pre-create sim objects unless
137
+ # the item is new
138
+ if isinstance(self.simulations.items, TemplatedSimulations):
139
+ status = EntityStatus.CREATED if self._platform_object else None
140
+ return status
141
+
142
+ sim_statuses = set([s.status for s in self.simulations.items])
143
+ any_succeeded_failed = any([s in [EntityStatus.FAILED, EntityStatus.SUCCEEDED] for s in sim_statuses])
144
+ if len(self.simulations.items) == 0 or all([s is None for s in sim_statuses]):
145
+ status = None # this will trigger experiment creation on a platform
146
+ elif any([s == EntityStatus.RUNNING for s in sim_statuses]):
147
+ status = EntityStatus.RUNNING
148
+ elif any([s == EntityStatus.CREATED for s in sim_statuses]) and any_succeeded_failed:
149
+ status = EntityStatus.RUNNING
150
+ elif any([s is None for s in sim_statuses]) and any_succeeded_failed:
151
+ status = EntityStatus.CREATED
152
+ elif any([s == EntityStatus.FAILED for s in sim_statuses]):
153
+ status = EntityStatus.FAILED
154
+ elif all([s == EntityStatus.SUCCEEDED for s in sim_statuses]):
155
+ status = EntityStatus.SUCCEEDED
156
+ else:
157
+ status = EntityStatus.CREATED
158
+ return status
159
+
160
+ @status.setter
161
+ def status(self, value):
162
+ """
163
+ Set status of experiment. Experiments status is an aggregate of its children so you cannot set status.
164
+
165
+ Args:
166
+ value: Value to set
167
+
168
+ Returns:
169
+ None
170
+
171
+ Notes:
172
+ TODO: Deprecate this
173
+ """
174
+ # this method is needed because dataclasses will always try to set each field, even if not allowed to in
175
+ # the case of Experiment.
176
+
177
+ caller = get_caller()
178
+ if caller not in ['__init__']:
179
+ logger.warning('Experiment status cannot be directly altered. Status unchanged.')
180
+
181
+ def __repr__(self):
182
+ """Experiment as string."""
183
+ return f"<Experiment: {self.uid} - {self.name} / Sim count {len(self.simulations) if self.simulations else 0}>"
184
+
185
+ @property
186
+ def suite(self):
187
+ """
188
+ Suite the experiment belongs to.
189
+
190
+ Returns:
191
+ Suite
192
+ """
193
+ return self.parent
194
+
195
+ @suite.setter
196
+ def suite(self, suite):
197
+ """
198
+ Set suite of the experiment.
199
+
200
+ Args:
201
+ suite: Suite to set
202
+
203
+ Returns:
204
+ None
205
+ """
206
+ self.parent = suite
207
+
208
+ @property
209
+ def parent(self):
210
+ """
211
+ Return parent object for item.
212
+
213
+ Returns:
214
+ Parent Suite if set
215
+ """
216
+ if not self._parent:
217
+ self.parent_id = self.parent_id or self.suite_id
218
+ if not self.parent_id:
219
+ return None
220
+ if not self.platform:
221
+ from idmtools.core import NoPlatformException
222
+ raise NoPlatformException("The object has no platform set...")
223
+ suite = self.platform.get_item(self.parent_id, ItemType.SUITE, force=True)
224
+ suite.add_experiment(self)
225
+
226
+ return self._parent
227
+
228
+ @parent.setter
229
+ def parent(self, parent: 'Suite'):
230
+ """
231
+ Sets the parent object for Entity.
232
+
233
+ Args:
234
+ parent: Parent object
235
+
236
+ Returns:
237
+ None
238
+ """
239
+ if parent is not None:
240
+ parent.add_experiment(self)
241
+ else:
242
+ self._parent = self.parent_id = self.suite_id = None
243
+
244
+ def display(self):
245
+ """
246
+ Display the experiment.
247
+
248
+ Returns:
249
+ None
250
+ """
251
+ from idmtools.utils.display import display, experiment_table_display
252
+ display(self, experiment_table_display)
253
+
254
+ def pre_creation(self, platform: 'IPlatform', gather_assets=True) -> None:
255
+ """
256
+ Experiment pre_creation callback.
257
+
258
+ Args:
259
+ platform: Platform experiment is being created on
260
+ gather_assets: Determines if an experiment will try to gather the common assets or defer. It most cases, you want this enabled but when modifying existing experiments you may want to disable if there are new assets and the platform has performance hits to determine those assets
261
+
262
+ Returns:
263
+ None
264
+
265
+ Raises:
266
+ ValueError - If simulations length is 0
267
+ """
268
+ # Gather the assets
269
+ IItem.pre_creation(self, platform)
270
+ if not self.disable_default_pre_create:
271
+ self.gather_assets()
272
+
273
+ # to keep experiments clean, let's only do this is we have a special experiment class
274
+ if self.__class__ is not Experiment:
275
+ # Add a tag to keep the Experiment class name
276
+ self.tags["experiment_type"] = f'{self.__class__.__module__}.{self.__class__.__name__}'
277
+
278
+ # if it is a template, set task type on experiment
279
+ if gather_assets:
280
+ if isinstance(self.simulations.items, TemplatedSimulations):
281
+ if len(self.simulations.items) == 0:
282
+ raise ValueError("You cannot run an empty experiment")
283
+ if logger.isEnabledFor(DEBUG):
284
+ logger.debug("Using Base task from template for experiment level assets")
285
+ self.simulations.items.base_task.gather_common_assets()
286
+ self.assets.add_assets(self.simulations.items.base_task.common_assets, fail_on_duplicate=False)
287
+ for sim in self.simulations.items.extra_simulations():
288
+ self.assets.add_assets(sim.task.gather_common_assets(), fail_on_duplicate=False)
289
+ if "task_type" not in self.tags:
290
+ task_class = self.simulations.items.base_task.__class__
291
+ self.tags["task_type"] = f'{task_class.__module__}.{task_class.__name__}'
292
+ elif self.gather_common_assets_from_task and isinstance(self.simulations.items, List):
293
+ if len(self.simulations.items) == 0:
294
+ raise ValueError("You cannot run an empty experiment")
295
+ if logger.isEnabledFor(DEBUG):
296
+ logger.debug("Using all tasks to gather assets")
297
+ task_class = self.__simulations[0].task.__class__
298
+ self.tags["task_type"] = f'{task_class.__module__}.{task_class.__name__}'
299
+ pbar = self.__simulations
300
+ if not IdmConfigParser.is_progress_bar_disabled():
301
+ from tqdm import tqdm
302
+ pbar = tqdm(self.__simulations, desc="Discovering experiment assets from tasks",
303
+ unit="simulation")
304
+ for sim in pbar:
305
+ # don't gather assets from simulations that have been provisioned
306
+ if sim.status is None:
307
+ assets = sim.task.gather_common_assets()
308
+ if assets is not None:
309
+ self.assets.add_assets(assets, fail_on_duplicate=True, fail_on_deep_comparison=True)
310
+ elif isinstance(self.simulations.items, List) and len(self.simulations.items) == 0:
311
+ raise ValueError("You cannot run an empty experiment")
312
+
313
+ self.tags.update(get_default_tags())
314
+
315
+ @property
316
+ def done(self):
317
+ """
318
+ Return if an experiment has finished executing.
319
+
320
+ Returns:
321
+ True if all simulations have ran, False otherwise
322
+ """
323
+ return all([s.done for s in self.simulations])
324
+
325
+ @property
326
+ def succeeded(self) -> bool:
327
+ """
328
+ Return if an experiment has succeeded. An experiment is succeeded when all simulations have succeeded.
329
+
330
+ Returns:
331
+ True if all simulations have succeeded, False otherwise
332
+ """
333
+ return all([s.succeeded for s in self.simulations])
334
+
335
+ @property
336
+ def any_failed(self) -> bool:
337
+ """
338
+ Return if an experiment has any simulation in failed state.
339
+
340
+ Returns:
341
+ True if all simulations have succeeded, False otherwise
342
+ """
343
+ return any([s.failed for s in self.simulations])
344
+
345
+ @property
346
+ def simulations(self) -> ExperimentParentIterator: # noqa: F811
347
+ """
348
+ Get the experiment's simulations.
349
+
350
+ Returns:
351
+ ExperimentParentIterator: Iterator wrapping internal simulation container.
352
+ """
353
+ if self.__simulations is None:
354
+ return ExperimentParentIterator([], parent=self)
355
+ return ExperimentParentIterator(self.__simulations, parent=self)
356
+
357
+ def get_simulations(self) -> ExperimentParentIterator: # noqa: F811:
358
+ """
359
+ Resolve and return simulations from internal container.
360
+
361
+ Returns:
362
+ ExperimentParentIterator
363
+ """
364
+ return self.simulations
365
+
366
+ @simulations.setter
367
+ def simulations(self, simulations: SUPPORTED_SIM_TYPE):
368
+ """
369
+ Set and normalize the simulations input.
370
+
371
+ Args:
372
+ simulations (SUPPORTED_SIM_TYPE): Simulations, task list, or generator.
373
+
374
+ Raises:
375
+ ValueError: If unsupported input type or invalid simulation list item.
376
+ """
377
+ from idmtools.entities.simulation import Simulation
378
+ if isinstance(simulations, GeneratorType):
379
+ simulations = list(simulations)
380
+
381
+ if isinstance(simulations, (EntityContainer, TemplatedSimulations)):
382
+ self.__simulations = simulations
383
+ self.gather_common_assets_from_task = isinstance(simulations, EntityContainer)
384
+ elif isinstance(simulations, (list, set)):
385
+ self.gather_common_assets_from_task = True
386
+ container = EntityContainer()
387
+ for sim in simulations:
388
+ if isinstance(sim, ITask):
389
+ container.append(sim.to_simulation())
390
+ elif isinstance(sim, Simulation):
391
+ container.append(sim)
392
+ else:
393
+ raise ValueError("Only Simulation or Task objects are allowed in simulation list.")
394
+ self.__simulations = container
395
+ else:
396
+ raise ValueError(
397
+ "Simulations must be an EntityContainer, Generator, TemplatedSimulations, or a List/Set of Simulations."
398
+ )
399
+
400
+ @property
401
+ def simulation_count(self) -> int:
402
+ """
403
+ Return the total simulations.
404
+
405
+ Returns:
406
+ Length of simulations
407
+ """
408
+ return len(self.simulations)
409
+
410
+ def refresh_simulations(self) -> NoReturn:
411
+ """
412
+ Refresh the simulations from the platform.
413
+
414
+ Returns:
415
+ None
416
+ """
417
+ from idmtools.core import ItemType
418
+ self.simulations = self.platform.get_children(self.uid, ItemType.EXPERIMENT, force=True)
419
+
420
+ def refresh_simulations_status(self):
421
+ """
422
+ Refresh the simulation status.
423
+
424
+ Returns:
425
+ None
426
+ """
427
+ self.platform.refresh_status(item=self)
428
+
429
+ def pre_getstate(self):
430
+ """
431
+ Return default values for :meth:`~idmtools.interfaces.ientity.pickle_ignore_fields`.
432
+
433
+ Call before pickling.
434
+ """
435
+ from idmtools.assets import AssetCollection
436
+ return {"assets": AssetCollection(), "simulations": EntityContainer()}
437
+
438
+ def gather_assets(self) -> AssetCollection():
439
+ """
440
+ Gather all our assets for our experiment.
441
+
442
+ Returns:
443
+ Assets
444
+ """
445
+ return self.assets
446
+
447
+ @classmethod
448
+ def from_task(cls, task, name: str = None, tags: Dict[str, Any] = None, assets: AssetCollection = None,
449
+ gather_common_assets_from_task: bool = True) -> 'Experiment':
450
+ """
451
+ Creates an Experiment with one Simulation from a task.
452
+
453
+ Args:
454
+ task: Task to use
455
+ assets: Asset collection to use for common tasks. Defaults to gather assets from task
456
+ name: Name of experiment
457
+ tags: Tags for the items
458
+ gather_common_assets_from_task: Whether we should attempt to gather assets from the Task object for the
459
+ experiment. With large amounts of tasks, this can be expensive as we loop through all
460
+ Returns:
461
+
462
+ """
463
+ if tags is None:
464
+ tags = dict()
465
+ if name is None:
466
+ name = task.__class__.__name__
467
+ e = Experiment(name=name, tags=tags, assets=AssetCollection() if assets is None else assets,
468
+ gather_common_assets_from_task=gather_common_assets_from_task)
469
+ e.simulations = [task]
470
+ return e
471
+
472
+ @classmethod
473
+ def from_builder(cls, builders: Union[SimulationBuilder, List[SimulationBuilder]], base_task: ITask,
474
+ name: str = None,
475
+ assets: AssetCollection = None, tags: Dict[str, Any] = None) -> 'Experiment':
476
+ """
477
+ Creates an experiment from a SimulationBuilder object(or list of builders.
478
+
479
+ Args:
480
+ builders: List of builder to create experiment from
481
+ base_task: Base task to use as template
482
+ name: Experiment name
483
+ assets: Experiment level assets
484
+ tags: Experiment tags
485
+
486
+ Returns:
487
+ Experiment object from the builders
488
+ """
489
+ ts = TemplatedSimulations(base_task=base_task)
490
+ if not isinstance(builders, list):
491
+ builders = [builders]
492
+ for builder in builders:
493
+ ts.add_builder(builder)
494
+ if name is None:
495
+ name = base_task.__class__.__name__
496
+ if len(builders) == 1:
497
+ name += " " + builders[0].__class__.__name__
498
+ return cls.from_template(ts, name=name, tags=tags, assets=assets)
499
+
500
+ @classmethod
501
+ def from_template(cls, template: TemplatedSimulations, name: str = None, assets: AssetCollection = None,
502
+ tags: Dict[str, Any] = None) -> 'Experiment':
503
+ """
504
+ Creates an Experiment from a TemplatedSimulation object.
505
+
506
+ Args:
507
+ template: TemplatedSimulation object
508
+ name: Experiment name
509
+ assets: Experiment level assets
510
+ tags: Tags
511
+
512
+ Returns:
513
+ Experiment object from the TemplatedSimulation object
514
+ """
515
+ if tags is None:
516
+ tags = dict()
517
+ if name is None:
518
+ name = template.base_task.__class__.__name__
519
+ e = Experiment(name=name, tags=tags, assets=AssetCollection() if assets is None else assets)
520
+ e.simulations = template
521
+ return e
522
+
523
+ def __deepcopy__(self, memo):
524
+ """
525
+ Deep copy for experiments. It converts generators and templates to realized lists to allow copying.
526
+
527
+ Args:
528
+ memo: The memo object used for copying
529
+
530
+ Returns:
531
+ Copied experiment
532
+ """
533
+ cls = self.__class__
534
+ result = cls.__new__(cls)
535
+ memo[id(self)] = result
536
+ for k, v in self.__dict__.items():
537
+ if k in ['_Experiment__simulations'] and isinstance(v, (GeneratorType, TemplatedSimulations)):
538
+ v = list(v)
539
+ setattr(result, k, copy.deepcopy(v, memo))
540
+ result._task_log = getLogger(__name__)
541
+ return result
542
+
543
+ def list_static_assets(self, children: bool = False, platform: 'IPlatform' = None, **kwargs) -> List[Asset]:
544
+ """
545
+ List assets that have been uploaded to a server already.
546
+
547
+ Args:
548
+ children: When set to true, simulation assets will be loaded as well
549
+ platform: Optional platform to load assets list from
550
+ **kwargs:
551
+
552
+ Returns:
553
+ List of assets
554
+ """
555
+ if self.id is None:
556
+ raise ValueError("You can only list static assets on an existing experiment")
557
+ p = super()._check_for_platform_from_context(platform)
558
+ return p._experiments.list_assets(self, children, **kwargs)
559
+
560
+ def run(self, wait_until_done: bool = False, platform: 'IPlatform' = None, regather_common_assets: bool = None,
561
+ wait_on_done_progress: bool = True, **run_opts) -> NoReturn:
562
+ """
563
+ Runs an experiment on a platform.
564
+
565
+ Args:
566
+ wait_until_done: Whether we should wait on experiment to finish running as well. Defaults to False
567
+ platform: Platform object to use. If not specified, we first check object for platform object then the current context
568
+ regather_common_assets: Triggers gathering of assets for *existing* experiments. If not provided, we use the platforms default behaviour. See platform details for performance implications of this. For most platforms, it should be ok but for others, it could decrease performance when assets are not changing.
569
+ It is important to note that when using this feature, ensure the previous simulations have finished provisioning. Failure to do so can lead to unexpected behaviour
570
+ wait_on_done_progress: Should experiment status be shown when waiting
571
+ **run_opts: Options to pass to the platform
572
+
573
+ Returns:
574
+ None
575
+ """
576
+ p = super()._check_for_platform_from_context(platform)
577
+ if 'wait_on_done' in run_opts:
578
+ raise TypeError(
579
+ "The 'wait_on_done' parameter has been removed in idmtools 1.8.0. Please update your code with 'wait_until_done'.")
580
+ if regather_common_assets is None:
581
+ regather_common_assets = p.is_regather_assets_on_modify()
582
+ if regather_common_assets and not self.assets.is_editable():
583
+ message = "To modify an experiment's asset collection, you must make a copy of it first. For example\nexperiment.assets = experiment.assets.copy()"
584
+ user_logger.error(message) # Show it bold red to user
585
+ raise ValueError(message)
586
+ if not self.assets.is_editable() and isinstance(self.simulations.items,
587
+ TemplatedSimulations) and not regather_common_assets:
588
+ user_logger.warning(
589
+ "You are modifying and existing experiment by using a template without gathering common assets. Ensure your Template configuration is the same as existing experiments or enable gathering of new common assets through regather_common_assets.")
590
+ run_opts['regather_common_assets'] = regather_common_assets
591
+ p.run_items(self, **run_opts)
592
+ if wait_until_done:
593
+ _refresh_interval = run_opts.get('refresh_interval', None)
594
+ if _refresh_interval is None:
595
+ _refresh_interval = p.refresh_interval
596
+ self.wait(wait_on_done_progress=wait_on_done_progress, refresh_interval=_refresh_interval)
597
+
598
+ def to_dict(self):
599
+ """
600
+ Convert experiment to dictionary.
601
+
602
+ Returns:
603
+ Dictionary of experiment.
604
+ """
605
+ result = dict()
606
+ for f in fields(self):
607
+ # Include:
608
+ # - public fields (not starting with '_')
609
+ # Exclude:
610
+ # - fields named 'parent'
611
+ if not f.name.startswith("_") and f.name not in ['parent']:
612
+ result[f.name] = getattr(self, f.name)
613
+
614
+ result['_uid'] = self.uid
615
+ return result
616
+
617
+ # Define this here for better completion in IDEs for end users
618
+ @classmethod
619
+ def from_id(cls, item_id: str, platform: 'IPlatform' = None, copy_assets: bool = False,
620
+ **kwargs) -> 'Experiment':
621
+ """
622
+ Helper function to provide better intellisense to end users.
623
+
624
+ Args:
625
+ item_id: Item id to load
626
+ platform: Optional platform. Fallbacks to context
627
+ copy_assets: Allow copying assets on load. Makes modifying experiments easier when new assets are involved.
628
+ **kwargs: Optional arguments to be passed on to the platform
629
+
630
+ Returns:
631
+ Experiment loaded with ID
632
+ """
633
+ result = super().from_id(item_id, platform, **kwargs)
634
+ if copy_assets:
635
+ result.assets = result.assets.copy()
636
+ return result
637
+
638
+ def print(self, verbose: bool = False):
639
+ """
640
+ Print summary of experiment.
641
+
642
+ Args:
643
+ verbose: Verbose printing
644
+
645
+ Returns:
646
+ None
647
+ """
648
+ user_logger.info(f"Experiment <{self.id}>")
649
+ user_logger.info(f"Total Simulations: {self.simulation_count}")
650
+ user_logger.info(f"Tags: {self.tags}")
651
+ user_logger.info(f"Platform: {self.platform.__class__.__name__}")
652
+ # determine status
653
+ if self.status:
654
+ # if succeeded print that
655
+ if self.succeeded:
656
+ user_logger.log(SUCCESS, "Succeeded")
657
+ elif not self.done:
658
+ user_logger.log(NOTICE, "RUNNING")
659
+ else:
660
+ user_logger.critical("Experiment failed. Please check output")
661
+
662
+ if verbose:
663
+ user_logger.info(f"Simulation Type: {type(self.__simulations)}")
664
+ user_logger.info(f"Assets: {self.assets}")
665
+
666
+ def add_simulation(self, item: 'Simulation'): # noqa F821
667
+ """
668
+ Adds a simulation to an experiment.
669
+ Args:
670
+ item: Item to add
671
+ Returns:
672
+ None
673
+ """
674
+ # Append into underlying collection
675
+ self.simulations.append(item)
676
+
677
+ def add_simulations(self, item: Union[List['Simulation'], 'TemplatedSimulations']): # noqa F821
678
+ """
679
+ Extends experiment's simulations.
680
+ Args:
681
+ item: Item to extend
682
+ Returns:
683
+ None
684
+ """
685
+ self.simulations.extend(item)
686
+
687
+ def get_simulations_by_tags(self, tags=None, status=None, skip_sims=None, entity_type=False, max_simulations=None,
688
+ **kwargs) -> List[str]:
689
+ """
690
+ Retrieve a list of simulation IDs or simulation objects with matching tags.
691
+ This method filters simulations based on the provided tags, skipping specified simulations,
692
+ and limiting the number of results if `max_simulations` is set. The return type can be
693
+ either a list of simulation IDs or simulation objects, depending on the `entity_type` flag.
694
+ Args:
695
+ tags (dict, optional): A simulation's tags to filter by.
696
+ status (EntityStatus, Optional): Simulation status.
697
+ entity_type (bool, optional): If True, return simulation objects; otherwise, return simulation IDs. Defaults to False.
698
+ skip_sims (list, optional): A list of simulation IDs to exclude from the results.
699
+ max_simulations (int, optional): The maximum number of simulations to return.
700
+ **kwargs: Additional filter parameters.
701
+ Returns:
702
+ list: A list of simulation IDs or simulation objects, depending on the `entity_type` flag.
703
+ """
704
+ from idmtools.utils.filter_simulations import FilterItem
705
+ return FilterItem.filter_item(
706
+ platform=self.platform,
707
+ item=self,
708
+ tags=tags,
709
+ status=status,
710
+ entity_type=entity_type,
711
+ skip_sims=skip_sims,
712
+ max_simulations=max_simulations,
713
+ **kwargs
714
+ )
715
+
716
+ def check_duplicate(self, simulation_id: str) -> bool:
717
+ """
718
+ Check if a simulation ID already exists.
719
+ Args:
720
+ simulation_id: given Simulation ID
721
+ Returns:
722
+ True/False
723
+ """
724
+ if isinstance(self.simulations.items, (list, set)):
725
+ ids = [sim.id for sim in self.simulations.items]
726
+ return simulation_id in ids
727
+ elif isinstance(self.simulations.items, TemplatedSimulations):
728
+ return self.simulations.items.check_duplicate(simulation_id)
729
+ else:
730
+ return False
731
+
732
+ def clear_directory_cache(self):
733
+ """
734
+ Clear directory cache for Experiment and Simulations.
735
+ """
736
+ from idmtools.core.context import get_current_platform
737
+ platform = get_current_platform()
738
+
739
+ # Skip COMPS Platform
740
+ if platform and hasattr(platform, 'job_directory'):
741
+ # Clear experiment directory cache
742
+ r1 = getattr(self, "_platform_directory", None)
743
+ if r1:
744
+ self._platform_directory = None
745
+ logger.warning(f"The Experiment {self.uid} referenced a temporary directory {r1}.")
746
+
747
+ # Clear Children's directory cache
748
+ if isinstance(self.simulations.items, (list, set)):
749
+ for sim in self.simulations.items:
750
+ sim._platform_directory = None
751
+ elif isinstance(self.simulations.items, TemplatedSimulations):
752
+ self.simulations.items.clear_extra_simulation_directory_cache()
753
+ else:
754
+ pass
755
+
756
+
757
+ class ExperimentSpecification(ExperimentPluginSpecification):
758
+ """
759
+ ExperimentSpecification is the spec for Experiment plugins.
760
+ """
761
+
762
+ @get_description_impl
763
+ def get_description(self) -> str:
764
+ """
765
+ Description of our plugin.
766
+
767
+ Returns:
768
+ Description
769
+ """
770
+ return "Provides access to the Local Platform to IDM Tools"
771
+
772
+ @get_model_impl
773
+ def get(self, configuration: dict) -> Experiment: # noqa: F821
774
+ """
775
+ Get experiment with configuration.
776
+ """
777
+ return Experiment(**configuration)
778
+
779
+ @get_model_type_impl
780
+ def get_type(self) -> Type[Experiment]:
781
+ """
782
+ Return the experiment type.
783
+
784
+ Returns:
785
+ Experiment type.
786
+ """
787
+ return Experiment