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,348 @@
|
|
|
1
|
+
"""
|
|
2
|
+
idmtools SimulationBuilder definition.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
import inspect
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pandas as pd
|
|
9
|
+
from functools import partial
|
|
10
|
+
from inspect import signature
|
|
11
|
+
from itertools import product
|
|
12
|
+
from typing import Callable, Any, Iterable, Union, Dict, Sized, NoReturn
|
|
13
|
+
from idmtools.entities.simulation import Simulation
|
|
14
|
+
from idmtools.utils.collections import duplicate_list_of_generators
|
|
15
|
+
|
|
16
|
+
TSweepFunction = Union[
|
|
17
|
+
Callable[[Simulation, Any], Dict[str, Any]],
|
|
18
|
+
partial
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SimulationBuilder:
|
|
23
|
+
"""
|
|
24
|
+
Class that represents an experiment builder.
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
.. literalinclude:: ../../examples/builders/simulation_builder.py
|
|
28
|
+
|
|
29
|
+
Add tags with builder callbacks::
|
|
30
|
+
|
|
31
|
+
def update_sim(sim, parameter, value):
|
|
32
|
+
sim.task.set_parameter(parameter, value)
|
|
33
|
+
# set sim tasks,
|
|
34
|
+
return {'custom': 123, parameter:value)
|
|
35
|
+
|
|
36
|
+
builder = SimulationBuilder()
|
|
37
|
+
set_run_number = partial(update_sim, param="Run_Number")
|
|
38
|
+
builder.add_sweep_definition(set_run_number, range(0, 2))
|
|
39
|
+
# create experiment from builder
|
|
40
|
+
exp = Experiment.from_builder(builder, task, name=expname)
|
|
41
|
+
"""
|
|
42
|
+
# The keyword searched in the function used for sweeps
|
|
43
|
+
SIMULATION_ATTR = 'simulation'
|
|
44
|
+
|
|
45
|
+
def __init__(self):
|
|
46
|
+
"""
|
|
47
|
+
Constructor.
|
|
48
|
+
"""
|
|
49
|
+
self.sweeps = []
|
|
50
|
+
self.__count = 0
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def count(self):
|
|
54
|
+
"""
|
|
55
|
+
Get the count property.
|
|
56
|
+
Returns:
|
|
57
|
+
count
|
|
58
|
+
"""
|
|
59
|
+
return self.__count
|
|
60
|
+
|
|
61
|
+
@count.setter
|
|
62
|
+
def count(self, cnt):
|
|
63
|
+
"""
|
|
64
|
+
Set the count property.
|
|
65
|
+
Args:
|
|
66
|
+
cnt: count set
|
|
67
|
+
Returns:
|
|
68
|
+
int
|
|
69
|
+
"""
|
|
70
|
+
if self.__count == 0:
|
|
71
|
+
self.__count = cnt
|
|
72
|
+
else:
|
|
73
|
+
self.__count = self.__count * cnt
|
|
74
|
+
|
|
75
|
+
def add_sweep_definition(self, function: TSweepFunction, *args, **kwargs):
|
|
76
|
+
"""
|
|
77
|
+
Add a sweep definition callback that takes possible multiple parameters (None or many).
|
|
78
|
+
|
|
79
|
+
The sweep will be defined as a cross-product between the parameters passed.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
function: The sweep function, which must include a **simulation** parameter (or
|
|
83
|
+
whatever is specified in :attr:`~idmtools.builders.ExperimentBuilder.SIMULATION_ATTR`).
|
|
84
|
+
args: List of arguments to be passed
|
|
85
|
+
kwargs: List of keyword arguments to be passed
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
None. Updates the Sweeps
|
|
89
|
+
|
|
90
|
+
Examples:
|
|
91
|
+
Examples of valid functions::
|
|
92
|
+
|
|
93
|
+
# This function takes one parameter
|
|
94
|
+
def myFunction(simulation, parameter_a):
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
# This function takes one parameter with default value
|
|
98
|
+
def myFunction(simulation, parameter_a=6):
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
# This function takes two parameters (parameters may have default values)
|
|
102
|
+
def myFunction(simulation, parameter_a, parameter_b=9):
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
# Function that takes three parameters (parameters may have default values)
|
|
106
|
+
def three_param_callback(simulation, parameter_a, parameter_b, parameter_c=10):
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
Calling Sweeps that take multiple parameters::
|
|
110
|
+
|
|
111
|
+
# This example references the above valid function example
|
|
112
|
+
sb = SimulationBuilder()
|
|
113
|
+
|
|
114
|
+
# Add a sweep on the myFunction that takes parameter(s).
|
|
115
|
+
# Here we sweep the values 1-4 on parameter_a and a,b on parameter_b
|
|
116
|
+
sb.add_sweep_definition(myFunction, range(1,5), ["a", "b"])
|
|
117
|
+
|
|
118
|
+
sb2 = SimulationBuilder()
|
|
119
|
+
# Example calling using a dictionary instead
|
|
120
|
+
sb.add_sweep_definition(three_param_callback, dict(parameter_a=range(1,5), parameter_b=["a", "b"], parameter_c=range(4,5))
|
|
121
|
+
# The following is equivalent
|
|
122
|
+
sb.add_sweep_definition(three_param_callback, **dict(parameter_a=range(1,5), parameter_b=["a", "b"], parameter_c=range(4,5))
|
|
123
|
+
|
|
124
|
+
sb3 = SimulationBuilder()
|
|
125
|
+
# If all parameters have default values, we can even simply do
|
|
126
|
+
sb3.add_sweep_definition(three_param_callback)
|
|
127
|
+
|
|
128
|
+
Remark: in general::
|
|
129
|
+
|
|
130
|
+
def my_callback(simulation, parameter_1, parameter_2, ..., parameter_n):
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
Calling Sweeps that take multiple parameters::
|
|
134
|
+
|
|
135
|
+
sb = SimulationBuilder()
|
|
136
|
+
sb.add_sweep_definition(my_callback, Iterable_1, Iterable_2, ..., Iterable_m)
|
|
137
|
+
|
|
138
|
+
# Note: the # of Iterable object must match the parameters # of my_callback, which don't have default values
|
|
139
|
+
|
|
140
|
+
# Or use the key (parameter names)
|
|
141
|
+
|
|
142
|
+
sb = SimulationBuilder()
|
|
143
|
+
sb.add_sweep_definition(my_callback, parameter_1=Iterable_1, parameter_2=Iterable_2, ..., parameter_m=Iterable_m)
|
|
144
|
+
# The following is equivalent
|
|
145
|
+
sb.add_sweep_definition(my_callback, dict(parameter_1=Iterable_1, parameter_2=Iterable_2, ..., parameter_m=Iterable_m))
|
|
146
|
+
# and
|
|
147
|
+
sb.add_sweep_definition(my_callback, **dict(parameter_1=Iterable_1, parameter_2=Iterable_2, ..., parameter_m=Iterable_m))
|
|
148
|
+
"""
|
|
149
|
+
remaining_parameters = self._extract_remaining_parameters(function)
|
|
150
|
+
|
|
151
|
+
if len(args) > 0 and len(kwargs) > 0:
|
|
152
|
+
raise ValueError(
|
|
153
|
+
"Currently in multi-argument sweep definitions, you have to supply either a arguments or keyword arguments, but not both.")
|
|
154
|
+
if len(args) > 0:
|
|
155
|
+
# args is always a tuple
|
|
156
|
+
values = args
|
|
157
|
+
# Consider special case: make it easy to use
|
|
158
|
+
if len(values) == 1 and isinstance(values[0], dict):
|
|
159
|
+
values = values[0]
|
|
160
|
+
self.case_kwargs(function, remaining_parameters, values)
|
|
161
|
+
else:
|
|
162
|
+
self.case_args_tuple(function, remaining_parameters, values)
|
|
163
|
+
elif len(kwargs) > 0:
|
|
164
|
+
values = kwargs
|
|
165
|
+
self.case_kwargs(function, remaining_parameters, values)
|
|
166
|
+
else:
|
|
167
|
+
required_params = self._extract_required_parameters(remaining_parameters)
|
|
168
|
+
if len(required_params) > 0:
|
|
169
|
+
raise ValueError(f"Missing arguments: {list(required_params)}.")
|
|
170
|
+
self.sweeps.append((function,))
|
|
171
|
+
self.count = 1
|
|
172
|
+
|
|
173
|
+
def _extract_remaining_parameters(self, function):
|
|
174
|
+
# Retrieve all the parameters in the signature of the function
|
|
175
|
+
parameters = signature(function).parameters
|
|
176
|
+
# Ensure `simulation` is part of the parameter list
|
|
177
|
+
if self.SIMULATION_ATTR not in parameters:
|
|
178
|
+
raise ValueError(f"The callback function passed to SweepBuilder.add_sweep_definition "
|
|
179
|
+
f"needs to take a {self.SIMULATION_ATTR} argument!")
|
|
180
|
+
# Retrieve all the free parameters of the signature (other than `simulation`)
|
|
181
|
+
remaining_parameters = {name: param.default for name, param in parameters.items() if
|
|
182
|
+
name != self.SIMULATION_ATTR}
|
|
183
|
+
return remaining_parameters
|
|
184
|
+
|
|
185
|
+
def case_args_tuple(self, function: TSweepFunction, remaining_parameters, values) -> NoReturn:
|
|
186
|
+
"""
|
|
187
|
+
Handle the case where the values are passed as a tuple.
|
|
188
|
+
Args:
|
|
189
|
+
function: sweep function
|
|
190
|
+
remaining_parameters: parameters
|
|
191
|
+
values: values
|
|
192
|
+
Returns:
|
|
193
|
+
No return
|
|
194
|
+
"""
|
|
195
|
+
# this is len(values) > 0 case
|
|
196
|
+
required_params = self._extract_required_parameters(remaining_parameters)
|
|
197
|
+
_values = [self._validate_value(vals) for vals in values]
|
|
198
|
+
|
|
199
|
+
if len(required_params) > 0 and len(required_params) != len(values):
|
|
200
|
+
if len(remaining_parameters) != len(values):
|
|
201
|
+
raise ValueError(
|
|
202
|
+
f"Currently the callback has {len(required_params)} required parameters and callback has {len(remaining_parameters)} parameters but there were {len(values)} arguments passed.")
|
|
203
|
+
else:
|
|
204
|
+
# Handle special case
|
|
205
|
+
generated_values = product(*_values)
|
|
206
|
+
self.sweeps.append(
|
|
207
|
+
partial(function, **self._map_argument_array(list(remaining_parameters), v)) for v in
|
|
208
|
+
generated_values)
|
|
209
|
+
self.count = np.prod(list(map(len, _values)))
|
|
210
|
+
return
|
|
211
|
+
|
|
212
|
+
if len(required_params) == 0 and len(values) > 1:
|
|
213
|
+
raise ValueError(
|
|
214
|
+
f"Currently the callback {len(remaining_parameters)} parameters (all have default values) and there were {len(values)} arguments passed.")
|
|
215
|
+
|
|
216
|
+
if len(required_params) == 0 and len(remaining_parameters) != 1:
|
|
217
|
+
raise ValueError(
|
|
218
|
+
f"Currently the callback has {len(remaining_parameters)} parameters (all have default values) and there were {len(values)} arguments passed.")
|
|
219
|
+
|
|
220
|
+
# Now we come to two cases
|
|
221
|
+
# 1. len(required_params) > 0 and len(required_params) == len(values)
|
|
222
|
+
# 2. len(required_params) == 0 and len(remaining_parameters) == 1 and len(values) == 1
|
|
223
|
+
# create sweeps using the multi-index
|
|
224
|
+
generated_values = product(*_values)
|
|
225
|
+
if len(required_params) > 0:
|
|
226
|
+
self.sweeps.append(
|
|
227
|
+
partial(function, **self._map_argument_array(list(required_params), v)) for v in
|
|
228
|
+
generated_values)
|
|
229
|
+
else:
|
|
230
|
+
self.sweeps.append(
|
|
231
|
+
partial(function, **self._map_argument_array(list(remaining_parameters), v)) for v in
|
|
232
|
+
generated_values)
|
|
233
|
+
|
|
234
|
+
self.count = np.prod(list(map(len, _values)))
|
|
235
|
+
|
|
236
|
+
def case_kwargs(self, function: TSweepFunction, remaining_parameters, values) -> NoReturn:
|
|
237
|
+
"""
|
|
238
|
+
Handle the case where the values are passed as a dictionary.
|
|
239
|
+
Args:
|
|
240
|
+
function: sweep function
|
|
241
|
+
remaining_parameters: parameters
|
|
242
|
+
values: values
|
|
243
|
+
Returns:
|
|
244
|
+
No return
|
|
245
|
+
"""
|
|
246
|
+
required_params = self._extract_required_parameters(remaining_parameters)
|
|
247
|
+
extra_inputs = list(set(values) - set(remaining_parameters))
|
|
248
|
+
if len(extra_inputs) > 0:
|
|
249
|
+
raise ValueError(
|
|
250
|
+
f"Extra arguments passed: {extra_inputs if len(extra_inputs) > 1 else extra_inputs[0]}.")
|
|
251
|
+
|
|
252
|
+
missing_params = list(set(required_params) - set(values))
|
|
253
|
+
if len(missing_params) > 0:
|
|
254
|
+
raise ValueError(
|
|
255
|
+
f"Missing arguments: {missing_params if len(missing_params) > 1 else missing_params[0]}.")
|
|
256
|
+
|
|
257
|
+
# validate each values in a dict
|
|
258
|
+
_values = {key: self._validate_value(vals) for key, vals in values.items()}
|
|
259
|
+
generated_values = product(*_values.values())
|
|
260
|
+
self.sweeps.append(
|
|
261
|
+
partial(function, **self._map_argument_array(_values.keys(), v)) for v in generated_values)
|
|
262
|
+
self.count = np.prod(list(map(len, _values.values())))
|
|
263
|
+
|
|
264
|
+
def add_multiple_parameter_sweep_definition(self, function: TSweepFunction, *args, **kwargs):
|
|
265
|
+
"""
|
|
266
|
+
Add a sweep definition callback that takes possible multiple parameters (None or many).
|
|
267
|
+
|
|
268
|
+
The sweep will be defined as a cross-product between the parameters passed.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
function: The sweep function, which must include a **simulation** parameter (or
|
|
272
|
+
whatever is specified in :attr:`~idmtools.builders.ExperimentBuilder.SIMULATION_ATTR`).
|
|
273
|
+
args: List of arguments to be passed
|
|
274
|
+
kwargs: List of keyword arguments to be passed
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
None. Updates the Sweeps
|
|
278
|
+
|
|
279
|
+
Examples:
|
|
280
|
+
Refer to the comments in the add_sweep_definition function for examples
|
|
281
|
+
"""
|
|
282
|
+
self.add_sweep_definition(function, *args, **kwargs)
|
|
283
|
+
|
|
284
|
+
def _validate_value(self, value):
|
|
285
|
+
"""
|
|
286
|
+
Validate inputs.
|
|
287
|
+
Args:
|
|
288
|
+
value: input
|
|
289
|
+
Returns:
|
|
290
|
+
validated value
|
|
291
|
+
"""
|
|
292
|
+
if isinstance(value, str):
|
|
293
|
+
return [value]
|
|
294
|
+
elif not isinstance(value, Iterable):
|
|
295
|
+
return [value]
|
|
296
|
+
# elif hasattr(value, '__len__'):
|
|
297
|
+
elif isinstance(value, Sized):
|
|
298
|
+
if isinstance(value, (dict, pd.DataFrame)):
|
|
299
|
+
return [value]
|
|
300
|
+
else:
|
|
301
|
+
return value
|
|
302
|
+
else:
|
|
303
|
+
return list(value)
|
|
304
|
+
|
|
305
|
+
@staticmethod
|
|
306
|
+
def _map_argument_array(parameters, value_set, remainder: str = None) -> Dict[str, Iterable]:
|
|
307
|
+
"""
|
|
308
|
+
Map multi-argument calls to parameters in a callback.
|
|
309
|
+
Args:
|
|
310
|
+
parameters: Parameters
|
|
311
|
+
value_set: List of values that should be sent to parameter in calls
|
|
312
|
+
remainder: Remainder
|
|
313
|
+
Returns:
|
|
314
|
+
Dictionary to map our call to our callbacks
|
|
315
|
+
"""
|
|
316
|
+
call_args = dict()
|
|
317
|
+
for idx, parameter in enumerate(parameters):
|
|
318
|
+
call_args[parameter] = value_set[idx]
|
|
319
|
+
|
|
320
|
+
if remainder:
|
|
321
|
+
return {remainder: call_args}
|
|
322
|
+
else:
|
|
323
|
+
return call_args
|
|
324
|
+
|
|
325
|
+
def _extract_required_parameters(self, remaining_parameters: Dict) -> Dict:
|
|
326
|
+
required_params = {k: v for k, v in remaining_parameters.items() if
|
|
327
|
+
not isinstance(v, pd.DataFrame) and v == inspect.Parameter.empty}
|
|
328
|
+
return required_params
|
|
329
|
+
|
|
330
|
+
def __iter__(self):
|
|
331
|
+
"""
|
|
332
|
+
Iterator of the simulation builder.
|
|
333
|
+
We duplicate the generators here so that we can loop over multiple times.
|
|
334
|
+
Returns:
|
|
335
|
+
The iterator
|
|
336
|
+
"""
|
|
337
|
+
old_sw, new_sw = duplicate_list_of_generators(self.sweeps)
|
|
338
|
+
|
|
339
|
+
yield from product(*old_sw)
|
|
340
|
+
self.sweeps = new_sw
|
|
341
|
+
|
|
342
|
+
def __len__(self):
|
|
343
|
+
"""
|
|
344
|
+
Total simulations to be built by builder. This is a Product of all total values for each sweep.
|
|
345
|
+
Returns:
|
|
346
|
+
Simulation count
|
|
347
|
+
"""
|
|
348
|
+
return self.count
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""
|
|
2
|
+
idmtools SimulationBuilder definition.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from functools import partial
|
|
8
|
+
from itertools import product, tee
|
|
9
|
+
from typing import Callable, Any, Iterable, Union, List, Tuple, Dict
|
|
10
|
+
from idmtools.builders import SimulationBuilder
|
|
11
|
+
from idmtools.entities.simulation import Simulation
|
|
12
|
+
|
|
13
|
+
TSweepFunction = Union[
|
|
14
|
+
Callable[[Simulation, Any], Dict[str, Any]], partial
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ArmType(Enum):
|
|
19
|
+
"""
|
|
20
|
+
ArmTypes.
|
|
21
|
+
"""
|
|
22
|
+
cross = 0
|
|
23
|
+
pair = 1
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SweepArm(SimulationBuilder):
|
|
27
|
+
"""
|
|
28
|
+
Class that represents a section of simulation sweeping.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, type=ArmType.cross, funcs: List[Tuple[Callable, Iterable]] = None):
|
|
32
|
+
"""
|
|
33
|
+
Constructor.
|
|
34
|
+
"""
|
|
35
|
+
self.type = type
|
|
36
|
+
self.__count = 0
|
|
37
|
+
self.__functions = None
|
|
38
|
+
super().__init__()
|
|
39
|
+
|
|
40
|
+
if funcs is None:
|
|
41
|
+
funcs = []
|
|
42
|
+
for func, values in funcs:
|
|
43
|
+
self.add_sweep_definition(func, values)
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def count(self):
|
|
47
|
+
"""Return simulations count."""
|
|
48
|
+
return self.__count
|
|
49
|
+
|
|
50
|
+
@count.setter
|
|
51
|
+
def count(self, cnt):
|
|
52
|
+
"""
|
|
53
|
+
Set simulations count.
|
|
54
|
+
Args:
|
|
55
|
+
cnt: count to set
|
|
56
|
+
Returns:
|
|
57
|
+
None
|
|
58
|
+
"""
|
|
59
|
+
if self.__count == 0:
|
|
60
|
+
self.__count = cnt
|
|
61
|
+
elif self.type == ArmType.cross:
|
|
62
|
+
self.__count = self.__count * cnt
|
|
63
|
+
elif self.type == ArmType.pair:
|
|
64
|
+
if self.__count != cnt:
|
|
65
|
+
raise ValueError(f"For pair case, all function inputs must have the same size/length: {cnt} != {self.__count}")
|
|
66
|
+
else:
|
|
67
|
+
self.__count = cnt
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def functions(self):
|
|
71
|
+
"""
|
|
72
|
+
Get functions.
|
|
73
|
+
Returns:
|
|
74
|
+
functions
|
|
75
|
+
"""
|
|
76
|
+
old_sw, new_sw = tee(self.__functions, 2)
|
|
77
|
+
self.__functions = new_sw
|
|
78
|
+
return old_sw
|
|
79
|
+
|
|
80
|
+
@functions.setter
|
|
81
|
+
def functions(self, funcs):
|
|
82
|
+
self.__functions = funcs
|
|
83
|
+
|
|
84
|
+
def _update_sweep_functions(self):
|
|
85
|
+
result = []
|
|
86
|
+
if len(self.sweeps) == 0:
|
|
87
|
+
result = []
|
|
88
|
+
elif self.type == ArmType.cross:
|
|
89
|
+
result = product(*self.sweeps)
|
|
90
|
+
elif self.type == ArmType.pair:
|
|
91
|
+
result = zip(*self.sweeps)
|
|
92
|
+
|
|
93
|
+
self.__functions = result
|
|
94
|
+
|
|
95
|
+
def _update_count(self, values):
|
|
96
|
+
"""
|
|
97
|
+
Update count of sweeps.
|
|
98
|
+
Args:
|
|
99
|
+
values: Values to count
|
|
100
|
+
Returns:
|
|
101
|
+
None
|
|
102
|
+
"""
|
|
103
|
+
if self.count == 0:
|
|
104
|
+
if len(values) == 0:
|
|
105
|
+
self.count = 1
|
|
106
|
+
else:
|
|
107
|
+
self.count = len(values)
|
|
108
|
+
else:
|
|
109
|
+
self.count = len(values)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
idmtools YamlSimulationBuilder definition.
|
|
3
|
+
|
|
4
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
5
|
+
"""
|
|
6
|
+
import yaml
|
|
7
|
+
from logging import getLogger
|
|
8
|
+
from typing import Union, Callable, Dict, Any
|
|
9
|
+
from idmtools.builders import ArmSimulationBuilder, SweepArm
|
|
10
|
+
|
|
11
|
+
logger = getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DefaultParamFuncDict(dict):
|
|
15
|
+
"""
|
|
16
|
+
Enables a function that takes a single parameter and return another function.
|
|
17
|
+
|
|
18
|
+
Notes:
|
|
19
|
+
TODO Add Example and types
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, default):
|
|
23
|
+
"""
|
|
24
|
+
Initialize our DefaultParamFuncDict.
|
|
25
|
+
Args:
|
|
26
|
+
default: Default function to use
|
|
27
|
+
"""
|
|
28
|
+
super().__init__()
|
|
29
|
+
self.__default = default
|
|
30
|
+
|
|
31
|
+
def __getitem__(self, item):
|
|
32
|
+
"""
|
|
33
|
+
Get item from the DefaultParamFuncDict. It proxies most calls to the function we wrap.
|
|
34
|
+
Args:
|
|
35
|
+
item: Item to lookup
|
|
36
|
+
Returns:
|
|
37
|
+
None
|
|
38
|
+
"""
|
|
39
|
+
if item in self:
|
|
40
|
+
return super().__getitem__(item)
|
|
41
|
+
else:
|
|
42
|
+
return self.__default(item)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class YamlSimulationBuilder(ArmSimulationBuilder):
|
|
46
|
+
"""
|
|
47
|
+
Class that represents an experiment builder.
|
|
48
|
+
Examples:
|
|
49
|
+
.. literalinclude:: ../../examples/builders/yaml_builder_python.py
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def add_sweeps_from_file(self, file_path, func_map: Union[Dict[str, Callable], Callable[[Any], Dict]] = None):
|
|
53
|
+
"""
|
|
54
|
+
Add sweeps from a file.
|
|
55
|
+
Args:
|
|
56
|
+
file_path: Path to file
|
|
57
|
+
func_map: Optional function map
|
|
58
|
+
Returns:
|
|
59
|
+
None
|
|
60
|
+
"""
|
|
61
|
+
if func_map is None:
|
|
62
|
+
func_map = {}
|
|
63
|
+
# if the user passing a single function, map it to all values
|
|
64
|
+
elif callable(func_map):
|
|
65
|
+
func_map = DefaultParamFuncDict(func_map)
|
|
66
|
+
with open(file_path, 'r') as stream:
|
|
67
|
+
try:
|
|
68
|
+
parsed = yaml.safe_load(stream)
|
|
69
|
+
except yaml.YAMLError as exc:
|
|
70
|
+
logger.exception(exc)
|
|
71
|
+
exit()
|
|
72
|
+
|
|
73
|
+
d_funcs = parsed.values()
|
|
74
|
+
|
|
75
|
+
for sweeps in list(d_funcs):
|
|
76
|
+
sweeps_tuples = ((list(d.keys())[0], list(d.values())[0]) for d in sweeps)
|
|
77
|
+
funcs = []
|
|
78
|
+
for func, values in sweeps_tuples:
|
|
79
|
+
funcs.append((func_map[func], values))
|
|
80
|
+
|
|
81
|
+
arm = SweepArm(funcs=funcs)
|
|
82
|
+
self.add_arm(arm)
|