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.
- 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.3.dist-info/METADATA +120 -0
- idmtools-0.0.3.dist-info/RECORD +116 -0
- idmtools-0.0.3.dist-info/entry_points.txt +9 -0
- idmtools-0.0.3.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.3.dist-info}/WHEEL +0 -0
- {idmtools-0.0.0.dev0.dist-info → idmtools-0.0.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""idmtools CSVAnalyzer.
|
|
2
|
+
|
|
3
|
+
Example of a csv analyzer to concatenate csv results into one csv from your experiment's simulations.
|
|
4
|
+
|
|
5
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
6
|
+
"""
|
|
7
|
+
import os
|
|
8
|
+
from typing import Dict
|
|
9
|
+
import pandas as pd
|
|
10
|
+
from idmtools.entities import IAnalyzer
|
|
11
|
+
from idmtools.entities.ianalyzer import ANALYSIS_ITEM_MAP_DATA_TYPE, ANALYZABLE_ITEM
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CSVAnalyzer(IAnalyzer):
|
|
15
|
+
"""
|
|
16
|
+
Provides an analyzer for CSV output.
|
|
17
|
+
|
|
18
|
+
Examples:
|
|
19
|
+
.. _simple-csv-example:
|
|
20
|
+
|
|
21
|
+
Simple Example
|
|
22
|
+
This example covers the basic usage of the CSVAnalyzer
|
|
23
|
+
|
|
24
|
+
.. literalinclude:: ../../examples/analyzers/example_analysis_CSVAnalyzer.py
|
|
25
|
+
|
|
26
|
+
.. _multiple-csvs:
|
|
27
|
+
|
|
28
|
+
Multiple CSVs
|
|
29
|
+
This example covers analyzing multiple CSVs
|
|
30
|
+
|
|
31
|
+
.. literalinclude:: ../../examples/analyzers/example_analysis_MultiCSVAnalyzer.py
|
|
32
|
+
"""
|
|
33
|
+
# Arg option for analyzer init are uid, working_dir, parse (True to leverage the :class:`OutputParser`;
|
|
34
|
+
# False to get the raw data in the :meth:`select_simulation_data`), and filenames
|
|
35
|
+
# In this case, we want parse=True, and the filename(s) to analyze
|
|
36
|
+
def __init__(self, filenames, output_path="output_csv"):
|
|
37
|
+
"""
|
|
38
|
+
Initialize our analyzer.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
filenames: Filenames we want to pull
|
|
42
|
+
output_path: Output path to write the csv
|
|
43
|
+
"""
|
|
44
|
+
super().__init__(parse=True, filenames=filenames)
|
|
45
|
+
# Raise exception early if files are not csv files
|
|
46
|
+
if not all(['csv' in os.path.splitext(f)[1].lower() for f in self.filenames]):
|
|
47
|
+
raise Exception('Please ensure all filenames provided to CSVAnalyzer have a csv extension.')
|
|
48
|
+
|
|
49
|
+
self.output_path = output_path
|
|
50
|
+
|
|
51
|
+
def initialize(self):
|
|
52
|
+
"""
|
|
53
|
+
Initialize on run. Create an output directory.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
None
|
|
57
|
+
"""
|
|
58
|
+
self.output_path = os.path.join(self.working_dir, self.output_path)
|
|
59
|
+
|
|
60
|
+
# Create the output path
|
|
61
|
+
if not os.path.exists(self.output_path):
|
|
62
|
+
os.makedirs(self.output_path)
|
|
63
|
+
|
|
64
|
+
# Map is called to get for each simulation a data object (all the metadata of the simulations) and simulation object
|
|
65
|
+
def map(self, data: ANALYSIS_ITEM_MAP_DATA_TYPE, simulation: ANALYZABLE_ITEM) -> pd.DataFrame:
|
|
66
|
+
"""
|
|
67
|
+
Map each simulation/workitem data here.
|
|
68
|
+
|
|
69
|
+
The data is a mapping of files -> content(in this case, dataframes since it is csvs parsed).
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
data: Data mapping of files -> content
|
|
73
|
+
simulation: Simulation/Workitem we are mapping
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Items joined together into a dataframe.
|
|
77
|
+
"""
|
|
78
|
+
# If there are 1 to many csv files, concatenate csv data columns into one dataframe
|
|
79
|
+
concatenated_df = pd.concat(list(data.values()), axis=0, ignore_index=True, sort=True)
|
|
80
|
+
return concatenated_df
|
|
81
|
+
|
|
82
|
+
# In reduce, we are printing the simulation and result data filtered in map
|
|
83
|
+
def reduce(self, all_data: Dict[ANALYZABLE_ITEM, pd.DataFrame]):
|
|
84
|
+
"""
|
|
85
|
+
Reduce(combine) all the data from our mapping.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
all_data: Mapping of our data in form Item(Simulation/Workitem) -> Mapped dataframe
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
None
|
|
92
|
+
"""
|
|
93
|
+
results = pd.concat(list(all_data.values()), axis=0, # Combine a list of all the sims csv data column values
|
|
94
|
+
keys=[str(k.uid) for k in all_data.keys()], # Add a hierarchical index with the keys option
|
|
95
|
+
names=['SimId']) # Label the index keys you create with the names option
|
|
96
|
+
results.index = results.index.droplevel(1) # Remove default index
|
|
97
|
+
|
|
98
|
+
# Make a directory labeled the exp id to write the csv results to
|
|
99
|
+
first_sim = list(all_data.keys())[0] # get first Simulation
|
|
100
|
+
exp_id = first_sim.experiment.id # Set the exp id from the first sim data
|
|
101
|
+
output_folder = os.path.join(self.output_path, exp_id)
|
|
102
|
+
os.makedirs(output_folder, exist_ok=True)
|
|
103
|
+
results.to_csv(os.path.join(output_folder, self.__class__.__name__ + '.csv'))
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""idmtools Download analyzer.
|
|
2
|
+
|
|
3
|
+
Download Analyzer.
|
|
4
|
+
|
|
5
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
6
|
+
"""
|
|
7
|
+
import os
|
|
8
|
+
from logging import getLogger
|
|
9
|
+
from idmtools.entities.ianalyzer import IAnalyzer, ANALYSIS_REDUCE_DATA_TYPE, ANALYZABLE_ITEM, ANALYSIS_ITEM_MAP_DATA_TYPE
|
|
10
|
+
|
|
11
|
+
logger = getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DownloadAnalyzer(IAnalyzer):
|
|
15
|
+
"""
|
|
16
|
+
A simple base class that will download the files specified in filenames without further treatment.
|
|
17
|
+
|
|
18
|
+
Can be used by creating a child class:
|
|
19
|
+
|
|
20
|
+
.. code-block:: python
|
|
21
|
+
|
|
22
|
+
class InsetDownloader(DownloadAnalyzer):
|
|
23
|
+
filenames = ['output/InsetChart.json']
|
|
24
|
+
|
|
25
|
+
Or by directly calling it:
|
|
26
|
+
|
|
27
|
+
.. code-block:: python
|
|
28
|
+
|
|
29
|
+
analyzer = DownloadAnalyzer(filenames=['output/InsetChart.json'])
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
Examples:
|
|
33
|
+
.. literalinclude:: ../../examples/analyzers/example_analysis_DownloadAnalyzer.py
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def reduce(self, all_data: ANALYSIS_REDUCE_DATA_TYPE):
|
|
37
|
+
"""
|
|
38
|
+
Combine the :meth:`map` data for a set of items into an aggregate result. In this case, for downloading, we just ignore it because there is no reduction.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
all_data: Dictionary in form item->map result where item is Simulations or WorkItems
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
None
|
|
45
|
+
"""
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
def __init__(self, filenames=None, output_path="output", **kwargs):
|
|
49
|
+
"""Constructor of the analyzer."""
|
|
50
|
+
super().__init__(filenames=filenames, parse=False, **kwargs)
|
|
51
|
+
self.output_path = output_path
|
|
52
|
+
|
|
53
|
+
def initialize(self):
|
|
54
|
+
"""
|
|
55
|
+
Initialize our sim. In this case, we create our output directory.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
None
|
|
59
|
+
"""
|
|
60
|
+
self.output_path = os.path.join(self.working_dir, self.output_path)
|
|
61
|
+
os.makedirs(self.output_path, exist_ok=True)
|
|
62
|
+
|
|
63
|
+
def get_item_folder(self, item: ANALYZABLE_ITEM):
|
|
64
|
+
"""
|
|
65
|
+
Concatenate the specified top-level output folder with the item ID.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
item: A simulation output parsing thread.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
The name of the folder to download this simulation's output to.
|
|
72
|
+
"""
|
|
73
|
+
return os.path.join(self.output_path, str(item.uid))
|
|
74
|
+
|
|
75
|
+
def map(self, data: ANALYSIS_ITEM_MAP_DATA_TYPE, item: ANALYZABLE_ITEM):
|
|
76
|
+
"""
|
|
77
|
+
Provide a map of filenames->data for each item. We then download each of these files to our output folder.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
data: Map filenames->data
|
|
81
|
+
item: Item we are mapping.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
None
|
|
85
|
+
"""
|
|
86
|
+
# Create a folder for the current simulation/item
|
|
87
|
+
sim_folder = self.get_item_folder(item)
|
|
88
|
+
os.makedirs(sim_folder, exist_ok=True)
|
|
89
|
+
|
|
90
|
+
# Create the requested files
|
|
91
|
+
for filename in self.filenames:
|
|
92
|
+
file_path = os.path.join(sim_folder, os.path.basename(filename))
|
|
93
|
+
|
|
94
|
+
logger.debug(f'Writing to path: {file_path}')
|
|
95
|
+
with open(file_path, 'wb') as outfile:
|
|
96
|
+
outfile.write(data[filename])
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""
|
|
2
|
+
We define our map entry items here for analysis framework.
|
|
3
|
+
|
|
4
|
+
Most of these function are used either to initialize a thread or to handle exceptions while executing.
|
|
5
|
+
|
|
6
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
7
|
+
"""
|
|
8
|
+
import itertools
|
|
9
|
+
from logging import getLogger, DEBUG
|
|
10
|
+
from idmtools.core.interfaces.ientity import IEntity
|
|
11
|
+
from idmtools.utils.file_parser import FileParser
|
|
12
|
+
from typing import TYPE_CHECKING, Dict
|
|
13
|
+
from idmtools.core.interfaces.iitem import IItem
|
|
14
|
+
from idmtools.entities.ianalyzer import TAnalyzerList
|
|
15
|
+
from idmtools.utils.general import FilterSafeItem
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
18
|
+
from idmtools.entities.iplatform import IPlatform
|
|
19
|
+
|
|
20
|
+
logger = getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def map_item(item: IItem) -> Dict[str, Dict]:
|
|
24
|
+
"""
|
|
25
|
+
Initialize some worker-global values; a worker process entry point for analyzer item-mapping.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
item: The item (often simulation) to process.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Dict[str, Dict]
|
|
32
|
+
"""
|
|
33
|
+
# Retrieve the global variables coming from the pool initialization
|
|
34
|
+
|
|
35
|
+
if logger.isEnabledFor(DEBUG):
|
|
36
|
+
logger.debug(f"Init item {item.uid} in worker")
|
|
37
|
+
analyzers = map_item.analyzers
|
|
38
|
+
platform = map_item.platform
|
|
39
|
+
return _get_mapped_data_for_item(item, analyzers, platform)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _get_mapped_data_for_item(item: IEntity, analyzers: TAnalyzerList, platform: 'IPlatform') -> Dict[str, Dict]:
|
|
43
|
+
"""
|
|
44
|
+
Get mapped data from an item.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
item: The :class:`~idmtools.entities.iitem.IItem` object to call analyzer
|
|
48
|
+
:meth:`~idmtools.analysis.AddAnalyzer.map` methods on.
|
|
49
|
+
analyzers: The :class:`~idmtools.analysis.IAnalyzer` items with
|
|
50
|
+
:meth:`~idmtools.analysis.AddAnalyzer.map` methods to call on the provided items.
|
|
51
|
+
platform: A platform object to query for information.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Dict[str, Dict] - Array mapping file data to from str to contents
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
try:
|
|
58
|
+
# determine which analyzers (and by extension, which filenames) are applicable to this item
|
|
59
|
+
# ensure item has a platform
|
|
60
|
+
item.platform = platform
|
|
61
|
+
analyzers_to_use = [a for a in analyzers if a.filter(FilterSafeItem(item))]
|
|
62
|
+
analyzer_uids = [a.uid for a in analyzers]
|
|
63
|
+
|
|
64
|
+
filenames = set(itertools.chain(*(a.filenames for a in analyzers_to_use)))
|
|
65
|
+
filenames = [f.replace("\\", '/') for f in filenames]
|
|
66
|
+
|
|
67
|
+
if logger.isEnabledFor(DEBUG):
|
|
68
|
+
logger.debug(f"Analyzers to use on item: {str(analyzer_uids)}")
|
|
69
|
+
logger.debug(f"Filenames to analyze: {filenames}")
|
|
70
|
+
|
|
71
|
+
# The byte_arrays will associate filename with content
|
|
72
|
+
if len(filenames) > 0:
|
|
73
|
+
file_data = platform.get_files(item, filenames)
|
|
74
|
+
else:
|
|
75
|
+
file_data = dict()
|
|
76
|
+
|
|
77
|
+
# Selected data will be a dict with analyzer.uid: data entries
|
|
78
|
+
selected_data = {}
|
|
79
|
+
for analyzer in analyzers_to_use:
|
|
80
|
+
# If the analyzer needs the parsed data, parse
|
|
81
|
+
if analyzer.parse:
|
|
82
|
+
logger.debug(f'Parsing content for {analyzer.uid}')
|
|
83
|
+
data = {filename: FileParser.parse(filename, content) for filename, content in file_data.items() if filename in analyzer.filenames}
|
|
84
|
+
else:
|
|
85
|
+
# If the analyzer doesnt wish to parse, give the raw data
|
|
86
|
+
data = {filename: content for filename, content in file_data.items() if filename in analyzer.filenames}
|
|
87
|
+
|
|
88
|
+
# run the mapping routine for this analyzer and item
|
|
89
|
+
logger.debug("Running map on selected data")
|
|
90
|
+
selected_data[analyzer.uid] = analyzer.map(data, item)
|
|
91
|
+
|
|
92
|
+
# Store all analyzer results for this item in the result cache
|
|
93
|
+
if logger.isEnabledFor(DEBUG):
|
|
94
|
+
logger.debug(f"Setting result to cache on {item.id}")
|
|
95
|
+
logger.debug(f"Wrote Setting result to cache on {item.id}")
|
|
96
|
+
except Exception as e:
|
|
97
|
+
e.item = item
|
|
98
|
+
logger.error(e)
|
|
99
|
+
raise e
|
|
100
|
+
return selected_data
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This script is executed as entrypoint in the docker SSMT worker.
|
|
3
|
+
|
|
4
|
+
Its role is to collect the experiment ids and analyzers and run the analysis.
|
|
5
|
+
|
|
6
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
7
|
+
"""
|
|
8
|
+
import argparse
|
|
9
|
+
import os
|
|
10
|
+
import pickle
|
|
11
|
+
import sys
|
|
12
|
+
from logging import getLogger
|
|
13
|
+
from pydoc import locate
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
sys.path.append(os.path.dirname(__file__))
|
|
17
|
+
|
|
18
|
+
if __name__ == "__main__":
|
|
19
|
+
parser = argparse.ArgumentParser("PlatformAnalysis bootstrap")
|
|
20
|
+
parser.add_argument("--experiment-ids", default=None, help="A comma separated list of experiments to analyze")
|
|
21
|
+
parser.add_argument("--simulation-ids", default=None, help="A comma separated list of simulations to analyze")
|
|
22
|
+
parser.add_argument("--work-item-ids", default=None, help="A comma separated list of work items to analyze")
|
|
23
|
+
parser.add_argument("--analyzers", help="Commas separated list of analyzers")
|
|
24
|
+
parser.add_argument("--block", help="Configuration block to use")
|
|
25
|
+
parser.add_argument("--verbose", default=False, action="store_true", help="Verbose logging")
|
|
26
|
+
parser.add_argument("--pre-run-func", default=None, help="List of function to run before starting analysis. Useful to load packages up in docker container before run")
|
|
27
|
+
parser.add_argument("--analyzer-manager-args-file", default=None, help="Path to extra arguments for analyzer manager")
|
|
28
|
+
parser.add_argument("--platform-args", default=None, help="Arguments used to create Platform")
|
|
29
|
+
|
|
30
|
+
args = parser.parse_args()
|
|
31
|
+
if args.verbose:
|
|
32
|
+
# enable verbose logging before we load idmtools
|
|
33
|
+
os.environ['IDMTOOLS_LOGGING_LEVEL'] = 'DEBUG'
|
|
34
|
+
os.environ['IDMTOOLS_LOGGING_CONSOLE'] = '1'
|
|
35
|
+
|
|
36
|
+
# delay loading idmtools so we can change log level through environment
|
|
37
|
+
from idmtools.core import ItemType
|
|
38
|
+
from idmtools.core.platform_factory import Platform
|
|
39
|
+
from idmtools.analysis.analyze_manager import AnalyzeManager
|
|
40
|
+
|
|
41
|
+
logger = getLogger('SSMT Analysis')
|
|
42
|
+
|
|
43
|
+
if args.pre_run_func:
|
|
44
|
+
import pre_run
|
|
45
|
+
getattr(pre_run, args.pre_run_func)()
|
|
46
|
+
|
|
47
|
+
item_ids = []
|
|
48
|
+
# Get the experiments, analyzers and platform
|
|
49
|
+
if args.experiment_ids:
|
|
50
|
+
experiments = args.experiment_ids.split(",")
|
|
51
|
+
for experiment in experiments:
|
|
52
|
+
experiment_tuple = (experiment, ItemType.EXPERIMENT)
|
|
53
|
+
item_ids.append(experiment_tuple)
|
|
54
|
+
|
|
55
|
+
# Get the simulations, analyzers and platform
|
|
56
|
+
if args.simulation_ids:
|
|
57
|
+
simulations = args.simulation_ids.split(",")
|
|
58
|
+
for simulation in simulations:
|
|
59
|
+
simulation_tuple = (simulation, ItemType.SIMULATION)
|
|
60
|
+
item_ids.append(simulation_tuple)
|
|
61
|
+
|
|
62
|
+
# Get the experiments, analyzers and platform
|
|
63
|
+
if args.work_item_ids:
|
|
64
|
+
work_items = args.work_item_ids.split(",")
|
|
65
|
+
for wi in work_items:
|
|
66
|
+
wi_tuple = (wi, ItemType.WORKFLOW_ITEM)
|
|
67
|
+
item_ids.append(wi_tuple)
|
|
68
|
+
|
|
69
|
+
# load analyzer args pickle file
|
|
70
|
+
analyzer_config = pickle.load(open(r"analyzer_args.pkl", 'rb'))
|
|
71
|
+
|
|
72
|
+
# Create analyzers
|
|
73
|
+
analyzers = []
|
|
74
|
+
for analyzer in args.analyzers.split(","):
|
|
75
|
+
A = locate(analyzer)
|
|
76
|
+
a = A(**analyzer_config[analyzer])
|
|
77
|
+
analyzers.append(a)
|
|
78
|
+
|
|
79
|
+
if not all(analyzers):
|
|
80
|
+
raise Exception("Not all analyzers could be found...\n{}".format(",".join(analyzers)))
|
|
81
|
+
|
|
82
|
+
if args.analyzer_manager_args_file is not None:
|
|
83
|
+
logger.info(f"Loading extra AnalyzerManager args from {args.analyzer_manager_args_file}")
|
|
84
|
+
with open(args.analyzer_manager_args_file, 'rb') as pin:
|
|
85
|
+
extra_args = pickle.load(pin)
|
|
86
|
+
|
|
87
|
+
# load platform args pickle file
|
|
88
|
+
platform_args = pickle.load(open(r"platform_args.pkl", 'rb'))
|
|
89
|
+
|
|
90
|
+
# get platform
|
|
91
|
+
platform = Platform(args.block, **platform_args)
|
|
92
|
+
logger.info(f"Analyzer Manager called with the following extra arguments: {extra_args}")
|
|
93
|
+
am = AnalyzeManager(platform=platform, ids=item_ids, analyzers=analyzers, **extra_args)
|
|
94
|
+
am.analyze()
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Platform Analysis is a wrapper to allow execution of analysis through SSMT vs Locally.
|
|
3
|
+
|
|
4
|
+
Running remotely has great advantages over local execution with the biggest being more compute resources and less data transfer.
|
|
5
|
+
Platform Analysis tries to make the process of running remotely similar to local execution.
|
|
6
|
+
|
|
7
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
8
|
+
"""
|
|
9
|
+
import re
|
|
10
|
+
from typing import List, Callable, Union, Type, Dict, Any
|
|
11
|
+
import inspect
|
|
12
|
+
import os
|
|
13
|
+
import pickle
|
|
14
|
+
from logging import getLogger, DEBUG
|
|
15
|
+
from idmtools.assets import Asset, AssetCollection
|
|
16
|
+
from idmtools.assets.file_list import FileList
|
|
17
|
+
from idmtools.config import IdmConfigParser
|
|
18
|
+
from idmtools.entities import IAnalyzer
|
|
19
|
+
from idmtools.entities.iplatform import IPlatform
|
|
20
|
+
from idmtools.entities.iplatform_default import AnalyzerManagerPlatformDefault
|
|
21
|
+
from idmtools.utils.info import get_help_version_url
|
|
22
|
+
|
|
23
|
+
logger = getLogger(__name__)
|
|
24
|
+
user_logger = getLogger('user')
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class PlatformAnalysis:
|
|
28
|
+
"""
|
|
29
|
+
PlatformAnalysis allows remote Analysis on the server.
|
|
30
|
+
|
|
31
|
+
See Also:
|
|
32
|
+
:py:class:`idmtools.analysis.analyze_manager.AnalyzeManager`
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, platform: IPlatform, analyzers: List[Type[IAnalyzer]],
|
|
36
|
+
experiment_ids: List['str'] = [], simulation_ids: List['str'] = [], work_item_ids: List['str'] = [],
|
|
37
|
+
analyzers_args=None, analysis_name: str = 'WorkItem Test', tags=None,
|
|
38
|
+
additional_files: Union[FileList, AssetCollection, List[str]] = None, asset_collection_id=None,
|
|
39
|
+
asset_files: Union[FileList, AssetCollection, List[str]] = None, wait_till_done: bool = True,
|
|
40
|
+
idmtools_config: str = None, pre_run_func: Callable = None, wrapper_shell_script: str = None,
|
|
41
|
+
verbose: bool = False, extra_args: Dict[str, Any] = None):
|
|
42
|
+
"""
|
|
43
|
+
Initialize our platform analysis.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
platform: Platform
|
|
47
|
+
experiment_ids: Experiment ids
|
|
48
|
+
simulation_ids: Simulation ids
|
|
49
|
+
work_item_ids: WorkItem ids
|
|
50
|
+
analyzers: Analyzers to run
|
|
51
|
+
analyzers_args: Arguments for our analyzers
|
|
52
|
+
analysis_name: Analysis name
|
|
53
|
+
tags: Tags for the workitem
|
|
54
|
+
additional_files: Additional files for server analysis
|
|
55
|
+
asset_collection_id: Asset Collection to use
|
|
56
|
+
asset_files: Asset files to attach
|
|
57
|
+
wait_till_done: Wait until analysis is done
|
|
58
|
+
idmtools_config: Optional path to idmtools.ini to use on server. Mostly useful for development
|
|
59
|
+
pre_run_func: A function (with no arguments) to be executed before analysis starts on the remote server
|
|
60
|
+
wrapper_shell_script: Optional path to a wrapper shell script. This script should redirect all arguments to command passed to it. Mostly useful for development purposes
|
|
61
|
+
verbose: Enables verbose logging remotely
|
|
62
|
+
extra_args: Optional extra arguments to pass to AnalyzerManager on the server side. See :meth:`~idmtools.analysis.analyze_manager.AnalyzeManager.__init__`
|
|
63
|
+
|
|
64
|
+
See Also:
|
|
65
|
+
:meth:`idmtools.analysis.analyze_manager.AnalyzeManager.__init__`
|
|
66
|
+
"""
|
|
67
|
+
self.platform = platform
|
|
68
|
+
self.experiment_ids = experiment_ids or []
|
|
69
|
+
self.simulation_ids = simulation_ids or []
|
|
70
|
+
self.work_item_ids = work_item_ids or []
|
|
71
|
+
self.analyzers = analyzers
|
|
72
|
+
self.analyzers_args = analyzers_args
|
|
73
|
+
self.analysis_name = analysis_name
|
|
74
|
+
self.tags = tags
|
|
75
|
+
if isinstance(additional_files, list):
|
|
76
|
+
additional_files = AssetCollection(additional_files)
|
|
77
|
+
elif isinstance(additional_files, FileList):
|
|
78
|
+
additional_files = additional_files.to_asset_collection()
|
|
79
|
+
self.additional_files: AssetCollection = additional_files or AssetCollection()
|
|
80
|
+
self.asset_collection_id = asset_collection_id
|
|
81
|
+
if isinstance(asset_files, list):
|
|
82
|
+
asset_files = AssetCollection(asset_files)
|
|
83
|
+
elif isinstance(asset_files, FileList):
|
|
84
|
+
asset_files = asset_files.to_asset_collection()
|
|
85
|
+
self.asset_files: AssetCollection = asset_files or AssetCollection()
|
|
86
|
+
self.wi = None
|
|
87
|
+
self.wait_till_done = wait_till_done
|
|
88
|
+
self.idmtools_config = idmtools_config
|
|
89
|
+
self.pre_run_func = pre_run_func
|
|
90
|
+
self.wrapper_shell_script = wrapper_shell_script
|
|
91
|
+
self.shell_script_binary = "/bin/bash"
|
|
92
|
+
self.verbose = verbose
|
|
93
|
+
self.extra_args = extra_args if extra_args else dict()
|
|
94
|
+
|
|
95
|
+
self.validate_args()
|
|
96
|
+
|
|
97
|
+
def analyze(self, check_status=True):
|
|
98
|
+
"""
|
|
99
|
+
Analyze remotely.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
check_status: Should we check status
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
None
|
|
106
|
+
|
|
107
|
+
Notes:
|
|
108
|
+
TODO: check_status is not being used
|
|
109
|
+
"""
|
|
110
|
+
command = self._prep_analyze()
|
|
111
|
+
|
|
112
|
+
logger.debug(f"Command: {command}")
|
|
113
|
+
from idmtools_platform_comps.ssmt_work_items.comps_workitems import SSMTWorkItem
|
|
114
|
+
|
|
115
|
+
ac = AssetCollection.from_id(self.asset_collection_id,
|
|
116
|
+
platform=self.platform) if self.asset_collection_id else AssetCollection()
|
|
117
|
+
ac.add_assets(self.asset_files)
|
|
118
|
+
self.wi = SSMTWorkItem(name=self.analysis_name, command=command, tags=self.tags,
|
|
119
|
+
transient_assets=self.additional_files, assets=ac,
|
|
120
|
+
related_experiments=self.experiment_ids,
|
|
121
|
+
related_simulations=self.simulation_ids,
|
|
122
|
+
related_work_items=self.work_item_ids
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Run the workitem
|
|
126
|
+
self.platform.run_items(self.wi)
|
|
127
|
+
if self.wait_till_done:
|
|
128
|
+
self.platform.wait_till_done(self.wi)
|
|
129
|
+
logger.debug(f"Status: {self.wi.status}")
|
|
130
|
+
|
|
131
|
+
def _prep_analyze(self):
|
|
132
|
+
"""
|
|
133
|
+
Prepare for analysis.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
None
|
|
137
|
+
"""
|
|
138
|
+
# Add the platform_analysis_bootstrap.py file to the collection
|
|
139
|
+
dir_path = os.path.dirname(os.path.realpath(__file__))
|
|
140
|
+
self.additional_files.add_or_replace_asset(os.path.join(dir_path, "platform_analysis_bootstrap.py"))
|
|
141
|
+
# check if user gave us an override to idmtools config
|
|
142
|
+
if self.idmtools_config:
|
|
143
|
+
self.additional_files.add_or_replace_asset(self.idmtools_config)
|
|
144
|
+
else:
|
|
145
|
+
# look for one from idmtools.
|
|
146
|
+
config_path = IdmConfigParser.get_config_path()
|
|
147
|
+
if config_path and os.path.exists(config_path):
|
|
148
|
+
if logger.isEnabledFor(DEBUG):
|
|
149
|
+
logger.debug(f"Adding config file: {config_path}")
|
|
150
|
+
self.additional_files.add_or_replace_asset(config_path)
|
|
151
|
+
|
|
152
|
+
# build analyzer args dict
|
|
153
|
+
args_dict = {}
|
|
154
|
+
a_args = zip(self.analyzers, self.analyzers_args)
|
|
155
|
+
for a, g in a_args:
|
|
156
|
+
args_dict[f"{inspect.getmodulename(inspect.getfile(a))}.{a.__name__}"] = g
|
|
157
|
+
if self.pre_run_func:
|
|
158
|
+
self.__pickle_pre_run()
|
|
159
|
+
# save pickle file as a temp file
|
|
160
|
+
self.__pickle_analyzers(args_dict)
|
|
161
|
+
|
|
162
|
+
# Add all the analyzers files
|
|
163
|
+
for a in self.analyzers:
|
|
164
|
+
self.additional_files.add_or_replace_asset(inspect.getfile(a))
|
|
165
|
+
|
|
166
|
+
# add our extra arguments for analyzer manager
|
|
167
|
+
if 'max_workers' not in self.extra_args:
|
|
168
|
+
am_defaults: List[AnalyzerManagerPlatformDefault] = self.platform.get_defaults_by_type(
|
|
169
|
+
AnalyzerManagerPlatformDefault)
|
|
170
|
+
if len(am_defaults):
|
|
171
|
+
if logger.isEnabledFor(DEBUG):
|
|
172
|
+
logger.debug(f"Setting max workers to comps default of: {am_defaults[0].max_workers}")
|
|
173
|
+
self.extra_args['max_workers'] = am_defaults[0].max_workers
|
|
174
|
+
|
|
175
|
+
# Create the command
|
|
176
|
+
command = ''
|
|
177
|
+
if self.wrapper_shell_script:
|
|
178
|
+
self.additional_files.add_or_replace_asset(self.wrapper_shell_script)
|
|
179
|
+
command += f'{self.shell_script_binary} {os.path.basename(self.wrapper_shell_script)} '
|
|
180
|
+
command += "python3 platform_analysis_bootstrap.py"
|
|
181
|
+
# Add the experiments
|
|
182
|
+
if self.experiment_ids:
|
|
183
|
+
command += f' --experiment-ids {",".join(self.experiment_ids)}'
|
|
184
|
+
# Add the simulations
|
|
185
|
+
if self.simulation_ids:
|
|
186
|
+
command += f' --simulation-ids {",".join(self.simulation_ids)}'
|
|
187
|
+
# Add the work items
|
|
188
|
+
if self.work_item_ids:
|
|
189
|
+
command += f' --work-item-ids {",".join(self.work_item_ids)}'
|
|
190
|
+
# Add the analyzers
|
|
191
|
+
command += " --analyzers {}".format(
|
|
192
|
+
",".join(f"{inspect.getmodulename(inspect.getfile(a))}.{a.__name__}" for a in self.analyzers))
|
|
193
|
+
|
|
194
|
+
if self.pre_run_func:
|
|
195
|
+
command += f" --pre-run-func {self.pre_run_func.__name__}"
|
|
196
|
+
|
|
197
|
+
# Pickle the extra args
|
|
198
|
+
if len(self.extra_args):
|
|
199
|
+
from idmtools.analysis.analyze_manager import AnalyzeManager
|
|
200
|
+
argspec = inspect.signature(AnalyzeManager.__init__)
|
|
201
|
+
for argname, value in self.extra_args.items():
|
|
202
|
+
if argname not in argspec.parameters:
|
|
203
|
+
raise ValueError(
|
|
204
|
+
f"AnalyzerManager does not support the argument {argname}. Valid args are {' '.join([str(s) for s in argspec.parameters.keys()])}. See {get_help_version_url('idmtools.analysis.analyze_manager.html#idmtools.analysis.analyze_manager.AnalyzeManager')} for a valid list of arguments.")
|
|
205
|
+
# TODO do type validations later
|
|
206
|
+
self.additional_files.add_or_replace_asset(
|
|
207
|
+
Asset(filename="extra_args.pkl", content=pickle.dumps(self.extra_args)))
|
|
208
|
+
command += " --analyzer-manager-args-file extra_args.pkl"
|
|
209
|
+
|
|
210
|
+
self.__pickle_platform_args()
|
|
211
|
+
command += " --platform-args platform_args.pkl"
|
|
212
|
+
|
|
213
|
+
# Add platform
|
|
214
|
+
ssmt_config_block = f"{self.platform._config_block}_SSMT"
|
|
215
|
+
command += " --block {}".format(ssmt_config_block)
|
|
216
|
+
if self.verbose:
|
|
217
|
+
command += " --verbose"
|
|
218
|
+
|
|
219
|
+
return command
|
|
220
|
+
|
|
221
|
+
def __pickle_analyzers(self, args_dict):
|
|
222
|
+
"""
|
|
223
|
+
Pickle our analyzers and add as assets.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
args_dict: Analyzer and args
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
None
|
|
230
|
+
"""
|
|
231
|
+
self.additional_files.add_or_replace_asset(Asset(filename='analyzer_args.pkl', content=pickle.dumps(args_dict)))
|
|
232
|
+
|
|
233
|
+
def __pickle_pre_run(self):
|
|
234
|
+
"""
|
|
235
|
+
Pickle objects before we run and add items as assets.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
None
|
|
239
|
+
"""
|
|
240
|
+
source = inspect.getsource(self.pre_run_func).splitlines()
|
|
241
|
+
space_base = 0
|
|
242
|
+
while source[0][space_base] == " ":
|
|
243
|
+
space_base += 1
|
|
244
|
+
replace_expr = re.compile("^[ ]{" + str(space_base) + "}")
|
|
245
|
+
new_source = []
|
|
246
|
+
for line in source:
|
|
247
|
+
new_source.append(replace_expr.sub("", line))
|
|
248
|
+
|
|
249
|
+
self.additional_files.add_or_replace_asset(Asset(filename="pre_run.py", content="\n".join(new_source)))
|
|
250
|
+
|
|
251
|
+
def __pickle_platform_args(self):
|
|
252
|
+
"""
|
|
253
|
+
Pickle platform args and add as assets.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
None
|
|
257
|
+
"""
|
|
258
|
+
# Pickle the platform args
|
|
259
|
+
platform_kwargs = self.platform._kwargs
|
|
260
|
+
self.additional_files.add_or_replace_asset(
|
|
261
|
+
Asset(filename="platform_args.pkl", content=pickle.dumps(platform_kwargs)))
|
|
262
|
+
|
|
263
|
+
def validate_args(self):
|
|
264
|
+
"""
|
|
265
|
+
Validate arguments for the platform analysis and analyzers.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
None
|
|
269
|
+
"""
|
|
270
|
+
if self.analyzers_args is None:
|
|
271
|
+
self.analyzers_args = [{}] * len(self.analyzers)
|
|
272
|
+
return
|
|
273
|
+
|
|
274
|
+
self.analyzers_args = [g if g is not None else {} for g in self.analyzers_args]
|
|
275
|
+
|
|
276
|
+
if len(self.analyzers_args) < len(self.analyzers):
|
|
277
|
+
self.analyzers_args = self.analyzers_args + [{}] * (len(self.analyzers) - len(self.analyzers_args))
|
|
278
|
+
return
|
|
279
|
+
|
|
280
|
+
if len(self.analyzers) < len(self.analyzers_args):
|
|
281
|
+
user_logger.error("two list 'analyzers' and 'analyzers_args' must have the same length.")
|
|
282
|
+
exit()
|
|
283
|
+
|
|
284
|
+
def get_work_item(self):
|
|
285
|
+
"""
|
|
286
|
+
Get work item being using to run analysis job on server.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
Workflow item
|
|
290
|
+
"""
|
|
291
|
+
return self.wi
|