hpcflow 0.1.15__py3-none-any.whl → 0.2.0a271__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.
- hpcflow/__init__.py +2 -11
- hpcflow/__pyinstaller/__init__.py +5 -0
- hpcflow/__pyinstaller/hook-hpcflow.py +40 -0
- hpcflow/_version.py +1 -1
- hpcflow/app.py +43 -0
- hpcflow/cli.py +2 -461
- hpcflow/data/demo_data_manifest/__init__.py +3 -0
- hpcflow/data/demo_data_manifest/demo_data_manifest.json +6 -0
- hpcflow/data/jinja_templates/test/test_template.txt +8 -0
- hpcflow/data/programs/hello_world/README.md +1 -0
- hpcflow/data/programs/hello_world/hello_world.c +87 -0
- hpcflow/data/programs/hello_world/linux/hello_world +0 -0
- hpcflow/data/programs/hello_world/macos/hello_world +0 -0
- hpcflow/data/programs/hello_world/win/hello_world.exe +0 -0
- hpcflow/data/scripts/__init__.py +1 -0
- hpcflow/data/scripts/bad_script.py +2 -0
- hpcflow/data/scripts/demo_task_1_generate_t1_infile_1.py +8 -0
- hpcflow/data/scripts/demo_task_1_generate_t1_infile_2.py +8 -0
- hpcflow/data/scripts/demo_task_1_parse_p3.py +7 -0
- hpcflow/data/scripts/do_nothing.py +2 -0
- hpcflow/data/scripts/env_specifier_test/input_file_generator_pass_env_spec.py +4 -0
- hpcflow/data/scripts/env_specifier_test/main_script_test_pass_env_spec.py +8 -0
- hpcflow/data/scripts/env_specifier_test/output_file_parser_pass_env_spec.py +4 -0
- hpcflow/data/scripts/env_specifier_test/v1/input_file_generator_basic.py +4 -0
- hpcflow/data/scripts/env_specifier_test/v1/main_script_test_direct_in_direct_out.py +7 -0
- hpcflow/data/scripts/env_specifier_test/v1/output_file_parser_basic.py +4 -0
- hpcflow/data/scripts/env_specifier_test/v2/main_script_test_direct_in_direct_out.py +7 -0
- hpcflow/data/scripts/generate_t1_file_01.py +7 -0
- hpcflow/data/scripts/import_future_script.py +7 -0
- hpcflow/data/scripts/input_file_generator_basic.py +3 -0
- hpcflow/data/scripts/input_file_generator_basic_FAIL.py +3 -0
- hpcflow/data/scripts/input_file_generator_test_stdout_stderr.py +8 -0
- hpcflow/data/scripts/main_script_test_direct_in.py +3 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_2.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed_group.py +7 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_3.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_all_iters_test.py +15 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_env_spec.py +7 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_labels.py +8 -0
- hpcflow/data/scripts/main_script_test_direct_in_group_direct_out_3.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_group_one_fail_direct_out_3.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_sub_param_in_direct_out.py +6 -0
- hpcflow/data/scripts/main_script_test_hdf5_in_obj.py +12 -0
- hpcflow/data/scripts/main_script_test_hdf5_in_obj_2.py +12 -0
- hpcflow/data/scripts/main_script_test_hdf5_in_obj_group.py +12 -0
- hpcflow/data/scripts/main_script_test_hdf5_out_obj.py +11 -0
- hpcflow/data/scripts/main_script_test_json_and_direct_in_json_out.py +14 -0
- hpcflow/data/scripts/main_script_test_json_in_json_and_direct_out.py +17 -0
- hpcflow/data/scripts/main_script_test_json_in_json_out.py +14 -0
- hpcflow/data/scripts/main_script_test_json_in_json_out_labels.py +16 -0
- hpcflow/data/scripts/main_script_test_json_in_obj.py +12 -0
- hpcflow/data/scripts/main_script_test_json_out_FAIL.py +3 -0
- hpcflow/data/scripts/main_script_test_json_out_obj.py +10 -0
- hpcflow/data/scripts/main_script_test_json_sub_param_in_json_out_labels.py +16 -0
- hpcflow/data/scripts/main_script_test_shell_env_vars.py +12 -0
- hpcflow/data/scripts/main_script_test_std_out_std_err.py +6 -0
- hpcflow/data/scripts/output_file_parser_basic.py +3 -0
- hpcflow/data/scripts/output_file_parser_basic_FAIL.py +7 -0
- hpcflow/data/scripts/output_file_parser_test_stdout_stderr.py +8 -0
- hpcflow/data/scripts/parse_t1_file_01.py +4 -0
- hpcflow/data/scripts/script_exit_test.py +5 -0
- hpcflow/data/template_components/__init__.py +1 -0
- hpcflow/data/template_components/command_files.yaml +26 -0
- hpcflow/data/template_components/environments.yaml +13 -0
- hpcflow/data/template_components/parameters.yaml +14 -0
- hpcflow/data/template_components/task_schemas.yaml +139 -0
- hpcflow/data/workflows/workflow_1.yaml +5 -0
- hpcflow/examples.ipynb +1037 -0
- hpcflow/sdk/__init__.py +149 -0
- hpcflow/sdk/app.py +4266 -0
- hpcflow/sdk/cli.py +1479 -0
- hpcflow/sdk/cli_common.py +385 -0
- hpcflow/sdk/config/__init__.py +5 -0
- hpcflow/sdk/config/callbacks.py +246 -0
- hpcflow/sdk/config/cli.py +388 -0
- hpcflow/sdk/config/config.py +1410 -0
- hpcflow/sdk/config/config_file.py +501 -0
- hpcflow/sdk/config/errors.py +272 -0
- hpcflow/sdk/config/types.py +150 -0
- hpcflow/sdk/core/__init__.py +38 -0
- hpcflow/sdk/core/actions.py +3857 -0
- hpcflow/sdk/core/app_aware.py +25 -0
- hpcflow/sdk/core/cache.py +224 -0
- hpcflow/sdk/core/command_files.py +814 -0
- hpcflow/sdk/core/commands.py +424 -0
- hpcflow/sdk/core/element.py +2071 -0
- hpcflow/sdk/core/enums.py +221 -0
- hpcflow/sdk/core/environment.py +256 -0
- hpcflow/sdk/core/errors.py +1043 -0
- hpcflow/sdk/core/execute.py +207 -0
- hpcflow/sdk/core/json_like.py +809 -0
- hpcflow/sdk/core/loop.py +1320 -0
- hpcflow/sdk/core/loop_cache.py +282 -0
- hpcflow/sdk/core/object_list.py +933 -0
- hpcflow/sdk/core/parameters.py +3371 -0
- hpcflow/sdk/core/rule.py +196 -0
- hpcflow/sdk/core/run_dir_files.py +57 -0
- hpcflow/sdk/core/skip_reason.py +7 -0
- hpcflow/sdk/core/task.py +3792 -0
- hpcflow/sdk/core/task_schema.py +993 -0
- hpcflow/sdk/core/test_utils.py +538 -0
- hpcflow/sdk/core/types.py +447 -0
- hpcflow/sdk/core/utils.py +1207 -0
- hpcflow/sdk/core/validation.py +87 -0
- hpcflow/sdk/core/values.py +477 -0
- hpcflow/sdk/core/workflow.py +4820 -0
- hpcflow/sdk/core/zarr_io.py +206 -0
- hpcflow/sdk/data/__init__.py +13 -0
- hpcflow/sdk/data/config_file_schema.yaml +34 -0
- hpcflow/sdk/data/config_schema.yaml +260 -0
- hpcflow/sdk/data/environments_spec_schema.yaml +21 -0
- hpcflow/sdk/data/files_spec_schema.yaml +5 -0
- hpcflow/sdk/data/parameters_spec_schema.yaml +7 -0
- hpcflow/sdk/data/task_schema_spec_schema.yaml +3 -0
- hpcflow/sdk/data/workflow_spec_schema.yaml +22 -0
- hpcflow/sdk/demo/__init__.py +3 -0
- hpcflow/sdk/demo/cli.py +242 -0
- hpcflow/sdk/helper/__init__.py +3 -0
- hpcflow/sdk/helper/cli.py +137 -0
- hpcflow/sdk/helper/helper.py +300 -0
- hpcflow/sdk/helper/watcher.py +192 -0
- hpcflow/sdk/log.py +288 -0
- hpcflow/sdk/persistence/__init__.py +18 -0
- hpcflow/sdk/persistence/base.py +2817 -0
- hpcflow/sdk/persistence/defaults.py +6 -0
- hpcflow/sdk/persistence/discovery.py +39 -0
- hpcflow/sdk/persistence/json.py +954 -0
- hpcflow/sdk/persistence/pending.py +948 -0
- hpcflow/sdk/persistence/store_resource.py +203 -0
- hpcflow/sdk/persistence/types.py +309 -0
- hpcflow/sdk/persistence/utils.py +73 -0
- hpcflow/sdk/persistence/zarr.py +2388 -0
- hpcflow/sdk/runtime.py +320 -0
- hpcflow/sdk/submission/__init__.py +3 -0
- hpcflow/sdk/submission/enums.py +70 -0
- hpcflow/sdk/submission/jobscript.py +2379 -0
- hpcflow/sdk/submission/schedulers/__init__.py +281 -0
- hpcflow/sdk/submission/schedulers/direct.py +233 -0
- hpcflow/sdk/submission/schedulers/sge.py +376 -0
- hpcflow/sdk/submission/schedulers/slurm.py +598 -0
- hpcflow/sdk/submission/schedulers/utils.py +25 -0
- hpcflow/sdk/submission/shells/__init__.py +52 -0
- hpcflow/sdk/submission/shells/base.py +229 -0
- hpcflow/sdk/submission/shells/bash.py +504 -0
- hpcflow/sdk/submission/shells/os_version.py +115 -0
- hpcflow/sdk/submission/shells/powershell.py +352 -0
- hpcflow/sdk/submission/submission.py +1402 -0
- hpcflow/sdk/submission/types.py +140 -0
- hpcflow/sdk/typing.py +194 -0
- hpcflow/sdk/utils/arrays.py +69 -0
- hpcflow/sdk/utils/deferred_file.py +55 -0
- hpcflow/sdk/utils/hashing.py +16 -0
- hpcflow/sdk/utils/patches.py +31 -0
- hpcflow/sdk/utils/strings.py +69 -0
- hpcflow/tests/api/test_api.py +32 -0
- hpcflow/tests/conftest.py +123 -0
- hpcflow/tests/data/__init__.py +0 -0
- hpcflow/tests/data/benchmark_N_elements.yaml +6 -0
- hpcflow/tests/data/benchmark_script_runner.yaml +26 -0
- hpcflow/tests/data/multi_path_sequences.yaml +29 -0
- hpcflow/tests/data/workflow_1.json +10 -0
- hpcflow/tests/data/workflow_1.yaml +5 -0
- hpcflow/tests/data/workflow_1_slurm.yaml +8 -0
- hpcflow/tests/data/workflow_1_wsl.yaml +8 -0
- hpcflow/tests/data/workflow_test_run_abort.yaml +42 -0
- hpcflow/tests/jinja_templates/test_jinja_templates.py +161 -0
- hpcflow/tests/programs/test_programs.py +180 -0
- hpcflow/tests/schedulers/direct_linux/test_direct_linux_submission.py +12 -0
- hpcflow/tests/schedulers/sge/test_sge_submission.py +36 -0
- hpcflow/tests/schedulers/slurm/test_slurm_submission.py +14 -0
- hpcflow/tests/scripts/test_input_file_generators.py +282 -0
- hpcflow/tests/scripts/test_main_scripts.py +1361 -0
- hpcflow/tests/scripts/test_non_snippet_script.py +46 -0
- hpcflow/tests/scripts/test_ouput_file_parsers.py +353 -0
- hpcflow/tests/shells/wsl/test_wsl_submission.py +14 -0
- hpcflow/tests/unit/test_action.py +1066 -0
- hpcflow/tests/unit/test_action_rule.py +24 -0
- hpcflow/tests/unit/test_app.py +132 -0
- hpcflow/tests/unit/test_cache.py +46 -0
- hpcflow/tests/unit/test_cli.py +172 -0
- hpcflow/tests/unit/test_command.py +377 -0
- hpcflow/tests/unit/test_config.py +195 -0
- hpcflow/tests/unit/test_config_file.py +162 -0
- hpcflow/tests/unit/test_element.py +666 -0
- hpcflow/tests/unit/test_element_iteration.py +88 -0
- hpcflow/tests/unit/test_element_set.py +158 -0
- hpcflow/tests/unit/test_group.py +115 -0
- hpcflow/tests/unit/test_input_source.py +1479 -0
- hpcflow/tests/unit/test_input_value.py +398 -0
- hpcflow/tests/unit/test_jobscript_unit.py +757 -0
- hpcflow/tests/unit/test_json_like.py +1247 -0
- hpcflow/tests/unit/test_loop.py +2674 -0
- hpcflow/tests/unit/test_meta_task.py +325 -0
- hpcflow/tests/unit/test_multi_path_sequences.py +259 -0
- hpcflow/tests/unit/test_object_list.py +116 -0
- hpcflow/tests/unit/test_parameter.py +243 -0
- hpcflow/tests/unit/test_persistence.py +664 -0
- hpcflow/tests/unit/test_resources.py +243 -0
- hpcflow/tests/unit/test_run.py +286 -0
- hpcflow/tests/unit/test_run_directories.py +29 -0
- hpcflow/tests/unit/test_runtime.py +9 -0
- hpcflow/tests/unit/test_schema_input.py +372 -0
- hpcflow/tests/unit/test_shell.py +129 -0
- hpcflow/tests/unit/test_slurm.py +39 -0
- hpcflow/tests/unit/test_submission.py +502 -0
- hpcflow/tests/unit/test_task.py +2560 -0
- hpcflow/tests/unit/test_task_schema.py +182 -0
- hpcflow/tests/unit/test_utils.py +616 -0
- hpcflow/tests/unit/test_value_sequence.py +549 -0
- hpcflow/tests/unit/test_values.py +91 -0
- hpcflow/tests/unit/test_workflow.py +827 -0
- hpcflow/tests/unit/test_workflow_template.py +186 -0
- hpcflow/tests/unit/utils/test_arrays.py +40 -0
- hpcflow/tests/unit/utils/test_deferred_file_writer.py +34 -0
- hpcflow/tests/unit/utils/test_hashing.py +65 -0
- hpcflow/tests/unit/utils/test_patches.py +5 -0
- hpcflow/tests/unit/utils/test_redirect_std.py +50 -0
- hpcflow/tests/unit/utils/test_strings.py +97 -0
- hpcflow/tests/workflows/__init__.py +0 -0
- hpcflow/tests/workflows/test_directory_structure.py +31 -0
- hpcflow/tests/workflows/test_jobscript.py +355 -0
- hpcflow/tests/workflows/test_run_status.py +198 -0
- hpcflow/tests/workflows/test_skip_downstream.py +696 -0
- hpcflow/tests/workflows/test_submission.py +140 -0
- hpcflow/tests/workflows/test_workflows.py +564 -0
- hpcflow/tests/workflows/test_zip.py +18 -0
- hpcflow/viz_demo.ipynb +6794 -0
- hpcflow-0.2.0a271.dist-info/LICENSE +375 -0
- hpcflow-0.2.0a271.dist-info/METADATA +65 -0
- hpcflow-0.2.0a271.dist-info/RECORD +237 -0
- {hpcflow-0.1.15.dist-info → hpcflow-0.2.0a271.dist-info}/WHEEL +4 -5
- hpcflow-0.2.0a271.dist-info/entry_points.txt +6 -0
- hpcflow/api.py +0 -490
- hpcflow/archive/archive.py +0 -307
- hpcflow/archive/cloud/cloud.py +0 -45
- hpcflow/archive/cloud/errors.py +0 -9
- hpcflow/archive/cloud/providers/dropbox.py +0 -427
- hpcflow/archive/errors.py +0 -5
- hpcflow/base_db.py +0 -4
- hpcflow/config.py +0 -233
- hpcflow/copytree.py +0 -66
- hpcflow/data/examples/_config.yml +0 -14
- hpcflow/data/examples/damask/demo/1.run.yml +0 -4
- hpcflow/data/examples/damask/demo/2.process.yml +0 -29
- hpcflow/data/examples/damask/demo/geom.geom +0 -2052
- hpcflow/data/examples/damask/demo/load.load +0 -1
- hpcflow/data/examples/damask/demo/material.config +0 -185
- hpcflow/data/examples/damask/inputs/geom.geom +0 -2052
- hpcflow/data/examples/damask/inputs/load.load +0 -1
- hpcflow/data/examples/damask/inputs/material.config +0 -185
- hpcflow/data/examples/damask/profiles/_variable_lookup.yml +0 -21
- hpcflow/data/examples/damask/profiles/damask.yml +0 -4
- hpcflow/data/examples/damask/profiles/damask_process.yml +0 -8
- hpcflow/data/examples/damask/profiles/damask_run.yml +0 -5
- hpcflow/data/examples/damask/profiles/default.yml +0 -6
- hpcflow/data/examples/thinking.yml +0 -177
- hpcflow/errors.py +0 -2
- hpcflow/init_db.py +0 -37
- hpcflow/models.py +0 -2595
- hpcflow/nesting.py +0 -9
- hpcflow/profiles.py +0 -455
- hpcflow/project.py +0 -81
- hpcflow/scheduler.py +0 -322
- hpcflow/utils.py +0 -103
- hpcflow/validation.py +0 -166
- hpcflow/variables.py +0 -543
- hpcflow-0.1.15.dist-info/METADATA +0 -168
- hpcflow-0.1.15.dist-info/RECORD +0 -45
- hpcflow-0.1.15.dist-info/entry_points.txt +0 -8
- hpcflow-0.1.15.dist-info/top_level.txt +0 -1
- /hpcflow/{archive → data/jinja_templates}/__init__.py +0 -0
- /hpcflow/{archive/cloud → data/programs}/__init__.py +0 -0
- /hpcflow/{archive/cloud/providers → data/workflows}/__init__.py +0 -0
hpcflow/scheduler.py
DELETED
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
"""`hpcflow.scheduler.py`"""
|
|
2
|
-
|
|
3
|
-
from datetime import datetime
|
|
4
|
-
from subprocess import run, PIPE
|
|
5
|
-
|
|
6
|
-
from hpcflow.config import Config as CONFIG
|
|
7
|
-
from hpcflow._version import __version__
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class Scheduler(object):
|
|
11
|
-
|
|
12
|
-
options = None
|
|
13
|
-
output_dir = None
|
|
14
|
-
error_dir = None
|
|
15
|
-
|
|
16
|
-
def __repr__(self):
|
|
17
|
-
out = ('{}('
|
|
18
|
-
'options={!r}, '
|
|
19
|
-
'output_dir={!r}, '
|
|
20
|
-
'error_dir={!r}'
|
|
21
|
-
')').format(
|
|
22
|
-
self.__class__.__name__,
|
|
23
|
-
self.options,
|
|
24
|
-
self.output_dir,
|
|
25
|
-
self.error_dir,
|
|
26
|
-
)
|
|
27
|
-
return out
|
|
28
|
-
|
|
29
|
-
def __init__(self, options=None, output_dir=None, error_dir=None):
|
|
30
|
-
|
|
31
|
-
self.output_dir = output_dir or CONFIG.get('default_output_dir')
|
|
32
|
-
self.error_dir = error_dir or CONFIG.get('default_error_dir')
|
|
33
|
-
self.options = options
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class SunGridEngine(Scheduler):
|
|
37
|
-
|
|
38
|
-
_NAME = 'sge'
|
|
39
|
-
SHEBANG = '#!/bin/bash --login'
|
|
40
|
-
|
|
41
|
-
STATS_DELIM = '==============================================================\n'
|
|
42
|
-
|
|
43
|
-
# Options that determine how to set the output/error directories:
|
|
44
|
-
STDOUT_OPT = 'o'
|
|
45
|
-
STDERR_OPT = 'e'
|
|
46
|
-
STDOUT_OPT_FMT = '{}/'
|
|
47
|
-
STDERR_OPT_FMT = '{}/'
|
|
48
|
-
|
|
49
|
-
# Required options to ensure the job scripts work with hpcflow:
|
|
50
|
-
REQ_OPT = ['cwd']
|
|
51
|
-
REQ_PARAMETRISED_OPT = {}
|
|
52
|
-
|
|
53
|
-
ALLOWED_USER_OPTS = [
|
|
54
|
-
'pe', # Parallel environment
|
|
55
|
-
'l', # Resource request
|
|
56
|
-
'tc', # Max running tasks
|
|
57
|
-
'P', # Project name (e.g. to which account jobs are accounted against)
|
|
58
|
-
]
|
|
59
|
-
|
|
60
|
-
NUM_CORES_VAR = '$NSLOTS'
|
|
61
|
-
|
|
62
|
-
def __init__(self, options=None, output_dir=None, error_dir=None):
|
|
63
|
-
|
|
64
|
-
for i in options:
|
|
65
|
-
if i not in SunGridEngine.ALLOWED_USER_OPTS:
|
|
66
|
-
msg = ('Option "{}" is not allowed for scheduler "{}". Allowed options '
|
|
67
|
-
'are: {}.')
|
|
68
|
-
raise ValueError(
|
|
69
|
-
msg.format(i, SunGridEngine._NAME, SunGridEngine.ALLOWED_USER_OPTS))
|
|
70
|
-
|
|
71
|
-
super().__init__(options=options, output_dir=output_dir, error_dir=error_dir)
|
|
72
|
-
|
|
73
|
-
def get_formatted_options(self, max_num_tasks, task_step_size, user_opt=True,
|
|
74
|
-
name=None):
|
|
75
|
-
|
|
76
|
-
opts = ['#$ -{}'.format(i) for i in SunGridEngine.REQ_OPT]
|
|
77
|
-
opts.append('#$ -{} {}'.format(
|
|
78
|
-
SunGridEngine.STDOUT_OPT,
|
|
79
|
-
SunGridEngine.STDOUT_OPT_FMT.format(self.output_dir)),
|
|
80
|
-
)
|
|
81
|
-
opts.append('#$ -{} {}'.format(
|
|
82
|
-
SunGridEngine.STDERR_OPT,
|
|
83
|
-
SunGridEngine.STDERR_OPT_FMT.format(self.error_dir)),
|
|
84
|
-
)
|
|
85
|
-
opts += ['#$ -{} {}'.format(i, j)
|
|
86
|
-
for i, j in SunGridEngine.REQ_PARAMETRISED_OPT.items()]
|
|
87
|
-
|
|
88
|
-
if name:
|
|
89
|
-
opts += [f'#$ -N {name}']
|
|
90
|
-
|
|
91
|
-
if user_opt:
|
|
92
|
-
opts += ['#$ -{} {}'.format(k, v).strip()
|
|
93
|
-
for k, v in sorted(self.options.items())]
|
|
94
|
-
|
|
95
|
-
opts += ['', '#$ -t 1-{}:{}'.format(max_num_tasks, task_step_size)]
|
|
96
|
-
|
|
97
|
-
return opts
|
|
98
|
-
|
|
99
|
-
def write_jobscript(self, dir_path, workflow_directory, command_group_order,
|
|
100
|
-
max_num_tasks, task_step_size, environment, archive,
|
|
101
|
-
alternate_scratch_dir, command_group_submission_id, name):
|
|
102
|
-
"""Write the jobscript.
|
|
103
|
-
|
|
104
|
-
Parameters
|
|
105
|
-
----------
|
|
106
|
-
archive : bool
|
|
107
|
-
|
|
108
|
-
"""
|
|
109
|
-
|
|
110
|
-
js_ext = CONFIG.get('jobscript_ext')
|
|
111
|
-
js_name = 'js_{}'.format(command_group_order)
|
|
112
|
-
js_fn = js_name + js_ext
|
|
113
|
-
js_path = dir_path.joinpath(js_fn)
|
|
114
|
-
|
|
115
|
-
cmd_name = 'cmd_{}'.format(command_group_order)
|
|
116
|
-
cmd_fn = cmd_name + js_ext
|
|
117
|
-
|
|
118
|
-
submit_dir_relative = dir_path.relative_to(workflow_directory).as_posix()
|
|
119
|
-
|
|
120
|
-
wk_dirs_path = ('${{ITER_DIR}}/working_dirs_{}{}').format(
|
|
121
|
-
command_group_order, CONFIG.get('working_dirs_file_ext'))
|
|
122
|
-
|
|
123
|
-
dt_stamp = datetime.now().strftime(r'%Y.%m.%d at %H:%M:%S')
|
|
124
|
-
about_msg = ['# --- jobscript generated by `hpcflow` (version: {}) '
|
|
125
|
-
'on {} ---'.format(__version__, dt_stamp)]
|
|
126
|
-
|
|
127
|
-
define_dirs_A = [
|
|
128
|
-
'ROOT_DIR=`pwd`',
|
|
129
|
-
'SUBMIT_DIR=$ROOT_DIR/{}'.format(submit_dir_relative),
|
|
130
|
-
'ITER_DIR=$SUBMIT_DIR/iter_$ITER_IDX',
|
|
131
|
-
'LOG_PATH=$ITER_DIR/log_{}.$SGE_TASK_ID'.format(command_group_order),
|
|
132
|
-
'TASK_IDX=$((($SGE_TASK_ID - 1)/{}))'.format(task_step_size),
|
|
133
|
-
]
|
|
134
|
-
|
|
135
|
-
write_cmd_exec = [(
|
|
136
|
-
f'hpcflow write-runtime-files --directory $ROOT_DIR '
|
|
137
|
-
f'--config-dir {CONFIG.get("config_dir")} '
|
|
138
|
-
f'{command_group_submission_id} $TASK_IDX $ITER_IDX > $LOG_PATH 2>&1'
|
|
139
|
-
)]
|
|
140
|
-
|
|
141
|
-
define_dirs_B = [
|
|
142
|
-
'INPUTS_DIR_REL=`sed -n "${{SGE_TASK_ID}}p" {}`'.format(wk_dirs_path),
|
|
143
|
-
'INPUTS_DIR=$ROOT_DIR/$INPUTS_DIR_REL',
|
|
144
|
-
]
|
|
145
|
-
|
|
146
|
-
if alternate_scratch_dir:
|
|
147
|
-
alt_scratch_exc_path = '$ITER_DIR/{}_{}_$TASK_IDX{}'.format(
|
|
148
|
-
CONFIG.get('alt_scratch_exc_file'),
|
|
149
|
-
command_group_order,
|
|
150
|
-
CONFIG.get('alt_scratch_exc_file_ext'),
|
|
151
|
-
)
|
|
152
|
-
define_dirs_B.append('ALT_SCRATCH_EXC=' + alt_scratch_exc_path)
|
|
153
|
-
in_dir_scratch = 'INPUTS_DIR_SCRATCH={}/$INPUTS_DIR_REL'.format(
|
|
154
|
-
alternate_scratch_dir)
|
|
155
|
-
copy_to_alt = [
|
|
156
|
-
('rsync -avviz --exclude-from="${ALT_SCRATCH_EXC}" '
|
|
157
|
-
'$INPUTS_DIR/ $INPUTS_DIR_SCRATCH >> $LOG_PATH 2>&1'),
|
|
158
|
-
'',
|
|
159
|
-
]
|
|
160
|
-
move_from_alt = [
|
|
161
|
-
'',
|
|
162
|
-
('rsync -avviz $INPUTS_DIR_SCRATCH/ $INPUTS_DIR --remove-source-files'
|
|
163
|
-
' >> $LOG_PATH 2>&1'),
|
|
164
|
-
'',
|
|
165
|
-
]
|
|
166
|
-
else:
|
|
167
|
-
in_dir_scratch = 'INPUTS_DIR_SCRATCH=$INPUTS_DIR'
|
|
168
|
-
copy_to_alt = []
|
|
169
|
-
move_from_alt = []
|
|
170
|
-
|
|
171
|
-
define_dirs_B.append(in_dir_scratch)
|
|
172
|
-
|
|
173
|
-
log_stuff = [
|
|
174
|
-
r'printf "Jobscript variables:\n" >> $LOG_PATH 2>&1',
|
|
175
|
-
r'printf "ITER_IDX:\t ${ITER_IDX}\n" >> $LOG_PATH 2>&1',
|
|
176
|
-
r'printf "ROOT_DIR:\t ${ROOT_DIR}\n" >> $LOG_PATH 2>&1',
|
|
177
|
-
r'printf "SUBMIT_DIR:\t ${SUBMIT_DIR}\n" >> $LOG_PATH 2>&1',
|
|
178
|
-
r'printf "ITER_DIR:\t ${ITER_DIR}\n" >> $LOG_PATH 2>&1',
|
|
179
|
-
r'printf "LOG_PATH:\t ${LOG_PATH}\n" >> $LOG_PATH 2>&1',
|
|
180
|
-
r'printf "SGE_TASK_ID:\t ${SGE_TASK_ID}\n" >> $LOG_PATH 2>&1',
|
|
181
|
-
r'printf "TASK_IDX:\t ${TASK_IDX}\n" >> $LOG_PATH 2>&1',
|
|
182
|
-
r'printf "INPUTS_DIR_REL:\t ${INPUTS_DIR_REL}\n" >> $LOG_PATH 2>&1',
|
|
183
|
-
r'printf "INPUTS_DIR:\t ${INPUTS_DIR}\n" >> $LOG_PATH 2>&1',
|
|
184
|
-
r'printf "INPUTS_DIR_SCRATCH:\t ${INPUTS_DIR_SCRATCH}\n" >> $LOG_PATH 2>&1',
|
|
185
|
-
]
|
|
186
|
-
|
|
187
|
-
if alternate_scratch_dir:
|
|
188
|
-
log_stuff.append(
|
|
189
|
-
r'printf "ALT_SCRATCH_EXC:\t ${ALT_SCRATCH_EXC}\n" >> $LOG_PATH 2>&1',
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
log_stuff.append(r'printf "\n" >> $LOG_PATH 2>&1')
|
|
193
|
-
|
|
194
|
-
if environment:
|
|
195
|
-
loads = [''] + environment + ['']
|
|
196
|
-
else:
|
|
197
|
-
loads = []
|
|
198
|
-
|
|
199
|
-
set_task_args = (f'--directory $ROOT_DIR '
|
|
200
|
-
f'--config-dir {CONFIG.get("config_dir")} '
|
|
201
|
-
f'{command_group_submission_id} '
|
|
202
|
-
f'$TASK_IDX $ITER_IDX >> $LOG_PATH 2>&1')
|
|
203
|
-
cmd_exec = [
|
|
204
|
-
f'hpcflow set-task-start {set_task_args}',
|
|
205
|
-
f'',
|
|
206
|
-
f'cd $INPUTS_DIR_SCRATCH',
|
|
207
|
-
f'. $SUBMIT_DIR/{cmd_fn}',
|
|
208
|
-
f'',
|
|
209
|
-
f'hpcflow set-task-end {set_task_args}',
|
|
210
|
-
]
|
|
211
|
-
|
|
212
|
-
arch_lns = []
|
|
213
|
-
if archive:
|
|
214
|
-
arch_lns = [
|
|
215
|
-
(f'hpcflow archive --directory $ROOT_DIR '
|
|
216
|
-
f'--config-dir {CONFIG.get("config_dir")} '
|
|
217
|
-
f'{command_group_submission_id} '
|
|
218
|
-
f'$TASK_IDX $ITER_IDX >> $LOG_PATH 2>&1'),
|
|
219
|
-
''
|
|
220
|
-
]
|
|
221
|
-
|
|
222
|
-
js_lines = ([SunGridEngine.SHEBANG, ''] +
|
|
223
|
-
about_msg + [''] +
|
|
224
|
-
self.get_formatted_options(max_num_tasks, task_step_size, name=name) +
|
|
225
|
-
[''] +
|
|
226
|
-
define_dirs_A + [''] +
|
|
227
|
-
write_cmd_exec + [''] +
|
|
228
|
-
define_dirs_B + [''] +
|
|
229
|
-
log_stuff + [''] +
|
|
230
|
-
loads + [''] +
|
|
231
|
-
copy_to_alt +
|
|
232
|
-
cmd_exec +
|
|
233
|
-
move_from_alt +
|
|
234
|
-
arch_lns)
|
|
235
|
-
|
|
236
|
-
# Write jobscript:
|
|
237
|
-
with js_path.open('w') as handle:
|
|
238
|
-
handle.write('\n'.join(js_lines))
|
|
239
|
-
|
|
240
|
-
return js_path
|
|
241
|
-
|
|
242
|
-
def write_stats_jobscript(self, dir_path, workflow_directory, command_group_order,
|
|
243
|
-
max_num_tasks, task_step_size, command_group_submission_id,
|
|
244
|
-
name):
|
|
245
|
-
|
|
246
|
-
js_ext = CONFIG.get('jobscript_ext')
|
|
247
|
-
js_name = 'st_{}'.format(command_group_order)
|
|
248
|
-
js_fn = js_name + js_ext
|
|
249
|
-
js_path = dir_path.joinpath(js_fn)
|
|
250
|
-
|
|
251
|
-
dt_stamp = datetime.now().strftime(r'%Y.%m.%d at %H:%M:%S')
|
|
252
|
-
about_msg = ['# --- jobscript generated by `hpcflow` (version: {}) '
|
|
253
|
-
'on {} ---'.format(__version__, dt_stamp)]
|
|
254
|
-
|
|
255
|
-
submit_dir_relative = dir_path.relative_to(workflow_directory).as_posix()
|
|
256
|
-
|
|
257
|
-
define_dirs = [
|
|
258
|
-
'ROOT_DIR=`pwd`',
|
|
259
|
-
'SUBMIT_DIR=$ROOT_DIR/{}'.format(submit_dir_relative),
|
|
260
|
-
'ITER_DIR=$SUBMIT_DIR/iter_$ITER_IDX',
|
|
261
|
-
'LOG_PATH=$ITER_DIR/log_{}.$SGE_TASK_ID'.format(command_group_order),
|
|
262
|
-
'TASK_IDX=$((($SGE_TASK_ID - 1)/{}))'.format(task_step_size),
|
|
263
|
-
]
|
|
264
|
-
|
|
265
|
-
log_stuff = [
|
|
266
|
-
r'printf "Jobscript variables:\n" >> $LOG_PATH 2>&1',
|
|
267
|
-
r'printf "ITER_IDX:\t ${ITER_IDX}\n" >> $LOG_PATH 2>&1',
|
|
268
|
-
r'printf "ROOT_DIR:\t ${ROOT_DIR}\n" >> $LOG_PATH 2>&1',
|
|
269
|
-
r'printf "SUBMIT_DIR:\t ${SUBMIT_DIR}\n" >> $LOG_PATH 2>&1',
|
|
270
|
-
r'printf "ITER_DIR:\t ${ITER_DIR}\n" >> $LOG_PATH 2>&1',
|
|
271
|
-
r'printf "LOG_PATH:\t ${LOG_PATH}\n" >> $LOG_PATH 2>&1',
|
|
272
|
-
r'printf "SGE_TASK_ID:\t ${SGE_TASK_ID}\n" >> $LOG_PATH 2>&1',
|
|
273
|
-
r'printf "TASK_IDX:\t ${TASK_IDX}\n" >> $LOG_PATH 2>&1',
|
|
274
|
-
]
|
|
275
|
-
|
|
276
|
-
cmd_exec = [(
|
|
277
|
-
f'hpcflow get-scheduler-stats --directory $ROOT_DIR '
|
|
278
|
-
f'--config-dir {CONFIG.get("config_dir")} '
|
|
279
|
-
f'{command_group_submission_id} $TASK_IDX $ITER_IDX >> $LOG_PATH 2>&1'
|
|
280
|
-
)]
|
|
281
|
-
|
|
282
|
-
opt = self.get_formatted_options(max_num_tasks, task_step_size, user_opt=False,
|
|
283
|
-
name=name)
|
|
284
|
-
opt.append('#$ -l short') # Temp (should be a profile option)
|
|
285
|
-
|
|
286
|
-
js_lines = ([SunGridEngine.SHEBANG, ''] +
|
|
287
|
-
about_msg + [''] +
|
|
288
|
-
opt + [''] +
|
|
289
|
-
define_dirs + [''] +
|
|
290
|
-
log_stuff + [''] +
|
|
291
|
-
cmd_exec)
|
|
292
|
-
|
|
293
|
-
# Write jobscript:
|
|
294
|
-
with js_path.open('w') as handle:
|
|
295
|
-
handle.write('\n'.join(js_lines))
|
|
296
|
-
|
|
297
|
-
return js_path
|
|
298
|
-
|
|
299
|
-
def get_scheduler_stats(self, scheduler_job_id, task_id):
|
|
300
|
-
|
|
301
|
-
cmd = ['/opt/site/sge/bin/lx-amd64/qacct', '-j', str(scheduler_job_id)]
|
|
302
|
-
proc = run(cmd, stdout=PIPE, stderr=PIPE)
|
|
303
|
-
out = proc.stdout.decode().strip()
|
|
304
|
-
_ = proc.stderr.decode().strip()
|
|
305
|
-
|
|
306
|
-
info = {}
|
|
307
|
-
qacct = out.split(SunGridEngine.STATS_DELIM)
|
|
308
|
-
for i in qacct[1:]:
|
|
309
|
-
keep = False
|
|
310
|
-
for ln in i.splitlines():
|
|
311
|
-
key, val = ln.strip().split(None, 1)
|
|
312
|
-
val = val.strip()
|
|
313
|
-
info.update({key: val})
|
|
314
|
-
if key == 'taskid':
|
|
315
|
-
if val == 'undefined' or int(val) == task_id:
|
|
316
|
-
keep = True
|
|
317
|
-
if keep:
|
|
318
|
-
break
|
|
319
|
-
else:
|
|
320
|
-
info = {}
|
|
321
|
-
|
|
322
|
-
return info
|
hpcflow/utils.py
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
"""`hpcflow.utils.py`
|
|
2
|
-
|
|
3
|
-
Utility functions that are not particularly specific to `hpcflow`.
|
|
4
|
-
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import random
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def coerce_same_length(all_lists):
|
|
11
|
-
"""
|
|
12
|
-
TODO: add docstring and examples
|
|
13
|
-
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
all_len = [len(i) for i in all_lists]
|
|
17
|
-
uniq_lens = set(all_len)
|
|
18
|
-
num_uniq_lens = len(uniq_lens)
|
|
19
|
-
|
|
20
|
-
if num_uniq_lens == 1:
|
|
21
|
-
out = all_lists
|
|
22
|
-
|
|
23
|
-
elif num_uniq_lens == 2:
|
|
24
|
-
|
|
25
|
-
if min(uniq_lens) != 1:
|
|
26
|
-
raise ValueError('bad!')
|
|
27
|
-
|
|
28
|
-
max_len = max(uniq_lens)
|
|
29
|
-
out = []
|
|
30
|
-
for i in all_lists:
|
|
31
|
-
if len(i) != max_len:
|
|
32
|
-
i = i * max_len
|
|
33
|
-
out.append(i)
|
|
34
|
-
|
|
35
|
-
else:
|
|
36
|
-
raise ValueError('bad!')
|
|
37
|
-
|
|
38
|
-
return out
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def zeropad(num, largest):
|
|
42
|
-
"""Return a zero-padded string of a number, given the largest number.
|
|
43
|
-
|
|
44
|
-
TODO: want to support floating-point numbers as well? Or rename function
|
|
45
|
-
accordingly.
|
|
46
|
-
|
|
47
|
-
Parameters
|
|
48
|
-
----------
|
|
49
|
-
num : int
|
|
50
|
-
The number to be formatted with zeros padding on the left.
|
|
51
|
-
largest : int
|
|
52
|
-
The number that determines the number of zeros to pad with.
|
|
53
|
-
|
|
54
|
-
Returns
|
|
55
|
-
-------
|
|
56
|
-
padded : str
|
|
57
|
-
The original number, `num`, formatted as a string with zeros added
|
|
58
|
-
on the left.
|
|
59
|
-
|
|
60
|
-
"""
|
|
61
|
-
|
|
62
|
-
num_digits = len('{:.0f}'.format(largest))
|
|
63
|
-
padded = '{0:0{width}}'.format(num, width=num_digits)
|
|
64
|
-
|
|
65
|
-
return padded
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def datetime_to_dict(dt):
|
|
69
|
-
return {
|
|
70
|
-
'year': dt.year,
|
|
71
|
-
'month': dt.month,
|
|
72
|
-
'day': dt.day,
|
|
73
|
-
'hour': dt.hour,
|
|
74
|
-
'minute': dt.minute,
|
|
75
|
-
'second': dt.second,
|
|
76
|
-
'microsecond': dt.microsecond,
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def timedelta_to_dict(td):
|
|
81
|
-
return {
|
|
82
|
-
'days': td.days,
|
|
83
|
-
'seconds': td.seconds,
|
|
84
|
-
'microseconds': td.microseconds,
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def format_time_delta(time_delta):
|
|
89
|
-
|
|
90
|
-
days, days_rem = divmod(time_delta.total_seconds(), 3600 * 24)
|
|
91
|
-
hours, hours_rem = divmod(days_rem, 3600)
|
|
92
|
-
mins, seconds = divmod(hours_rem, 60)
|
|
93
|
-
|
|
94
|
-
time_diff_fmt = '{:02.0f}:{:02.0f}:{:02.0f}'.format(hours, mins, round(seconds))
|
|
95
|
-
if days > 0:
|
|
96
|
-
days_str = 'day' if days == 1 else 'days'
|
|
97
|
-
time_diff_fmt = '{} {}, '.format(int(days), days_str) + time_diff_fmt
|
|
98
|
-
|
|
99
|
-
return time_diff_fmt
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def get_random_hex(n=10):
|
|
103
|
-
return ''.join([random.choice('0123456789abcdef') for i in range(n)])
|
hpcflow/validation.py
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
"""`hpcflow.validation.py`
|
|
2
|
-
|
|
3
|
-
These validation functions are invoked during initialisation of the `Workflow`
|
|
4
|
-
and `CommandGroup` instances. They are extracted out as functions here to allow
|
|
5
|
-
re-use by the `visualise` module, whose code may be invoked without necessarily
|
|
6
|
-
constructing `Workflow` and `CommandGroup` instances.
|
|
7
|
-
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from hpcflow.config import Config as CONFIG
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def validate_workflow(workflow_dict):
|
|
14
|
-
"""Validate the `nesting`, `order` and `num_tasks` for each in a list of
|
|
15
|
-
workflow command groups."""
|
|
16
|
-
|
|
17
|
-
return workflow_dict
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def validate_job_profile(job_profile):
|
|
21
|
-
"""Validate the keys of a job profile.
|
|
22
|
-
|
|
23
|
-
Parameters
|
|
24
|
-
----------
|
|
25
|
-
job_profile : dict
|
|
26
|
-
Dictionary representing a job profile.
|
|
27
|
-
|
|
28
|
-
Returns
|
|
29
|
-
-------
|
|
30
|
-
job_profile : dict
|
|
31
|
-
Dictionary representing a job profile.
|
|
32
|
-
|
|
33
|
-
"""
|
|
34
|
-
|
|
35
|
-
# Validate profile keys:
|
|
36
|
-
keys = job_profile.keys()
|
|
37
|
-
missing_keys = list(set(CONFIG.get('profile_keys_required')) - set(keys))
|
|
38
|
-
bad_keys = list(set(keys) - set(CONFIG.get('profile_keys_allowed')))
|
|
39
|
-
|
|
40
|
-
# Check required keys exist:
|
|
41
|
-
if len(missing_keys) > 0:
|
|
42
|
-
msg = ('Job profile is missing required key(s): {}')
|
|
43
|
-
raise ValueError(msg.format(missing_keys))
|
|
44
|
-
|
|
45
|
-
# Check for unknown keys:
|
|
46
|
-
if len(bad_keys) > 0:
|
|
47
|
-
msg = ('Job profile contains unknown key(s): {}')
|
|
48
|
-
raise ValueError(msg.format(bad_keys))
|
|
49
|
-
|
|
50
|
-
return job_profile
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def validate_job_profile_list(job_profiles):
|
|
54
|
-
"""Validate a list of job profiles and sort by `profile_order`.
|
|
55
|
-
|
|
56
|
-
Parameters
|
|
57
|
-
----------
|
|
58
|
-
job_profiles : list of dict
|
|
59
|
-
List of dictionaries representing job profiles.
|
|
60
|
-
|
|
61
|
-
Returns
|
|
62
|
-
-------
|
|
63
|
-
job_profiles : list of dict
|
|
64
|
-
List of dictionaries representing job profiles, sorted by
|
|
65
|
-
`profile_order`.
|
|
66
|
-
|
|
67
|
-
"""
|
|
68
|
-
|
|
69
|
-
# Some validation:
|
|
70
|
-
profile_orders = []
|
|
71
|
-
profile_names = []
|
|
72
|
-
for i in job_profiles:
|
|
73
|
-
|
|
74
|
-
# Check all profiles have a name:
|
|
75
|
-
if i.get('profile_name') is None:
|
|
76
|
-
msg = ('`profile_name` must be set for each job profile.')
|
|
77
|
-
raise ValueError(msg)
|
|
78
|
-
else:
|
|
79
|
-
profile_names.append(i['profile_name'])
|
|
80
|
-
|
|
81
|
-
if len(job_profiles) > 1:
|
|
82
|
-
# Check all profiles have an order:
|
|
83
|
-
if i.get('profile_order') is None:
|
|
84
|
-
msg = ('`profile_order` must be set for each profile if there '
|
|
85
|
-
'are multiple job profiles, but was not specified for '
|
|
86
|
-
'the job profile "{}"')
|
|
87
|
-
raise ValueError(msg.format(i['profile_name']))
|
|
88
|
-
else:
|
|
89
|
-
profile_orders.append(i['profile_order'])
|
|
90
|
-
|
|
91
|
-
if len(job_profiles) > 1:
|
|
92
|
-
# Check distinct profile names:
|
|
93
|
-
if len(set(profile_names)) < len(job_profiles):
|
|
94
|
-
msg = ('Each job profile must have a distinct `profile_name` if '
|
|
95
|
-
'there are multiple job profiles, but job profile names '
|
|
96
|
-
'are: {}')
|
|
97
|
-
raise ValueError(msg.format(profile_names))
|
|
98
|
-
|
|
99
|
-
# Check distinct profile orders:
|
|
100
|
-
if len(set(profile_orders)) < len(job_profiles):
|
|
101
|
-
msg = ('Each job profile must have a distinct `profile_order` if '
|
|
102
|
-
'there are multiple job profiles, but job profile orders '
|
|
103
|
-
'are: {}')
|
|
104
|
-
raise ValueError(msg.format(profile_orders))
|
|
105
|
-
|
|
106
|
-
# Sort by profile order:
|
|
107
|
-
job_profiles = sorted(job_profiles, key=lambda x: x['profile_order'])
|
|
108
|
-
|
|
109
|
-
return job_profiles
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def validate_task_multiplicity(cmd_groups, common_err_msg='',
|
|
113
|
-
max_num_channels=1):
|
|
114
|
-
"""Validate an ordered list of command groups that represent (part of) a
|
|
115
|
-
Workflow.
|
|
116
|
-
|
|
117
|
-
Parameters
|
|
118
|
-
----------
|
|
119
|
-
cmd_groups : list of dict
|
|
120
|
-
List of dicts representing command groups.
|
|
121
|
-
common_err_msg : str, optional
|
|
122
|
-
Optional error message to prepend to any exception raised in here. By
|
|
123
|
-
default, empty.
|
|
124
|
-
max_num_channels : int, optional
|
|
125
|
-
Restrict the allowed number of channels to a given number.
|
|
126
|
-
|
|
127
|
-
Returns
|
|
128
|
-
-------
|
|
129
|
-
cmd_groups : list of dict
|
|
130
|
-
List of dicts representing command groups.
|
|
131
|
-
|
|
132
|
-
"""
|
|
133
|
-
|
|
134
|
-
if not common_err_msg.endswith(' '):
|
|
135
|
-
common_err_msg += ' '
|
|
136
|
-
|
|
137
|
-
exec_orders_all = [i.get('exec_order') for i in cmd_groups]
|
|
138
|
-
exec_orders = [i for i in exec_orders_all if i is not None]
|
|
139
|
-
|
|
140
|
-
if exec_orders:
|
|
141
|
-
# Check `exec_order` starts from zero and increases by 0 or 1:
|
|
142
|
-
uniq_exec_orders = list(set(sorted(exec_orders)))
|
|
143
|
-
if uniq_exec_orders != list(range(len(uniq_exec_orders))):
|
|
144
|
-
msg = common_err_msg + ('If specified, `exec_order` must start at '
|
|
145
|
-
'zero and increase by zero or one across '
|
|
146
|
-
'command groups.')
|
|
147
|
-
raise ValueError(msg)
|
|
148
|
-
|
|
149
|
-
for i in uniq_exec_orders:
|
|
150
|
-
# Get cmd_group indices that have this `exec_order`:
|
|
151
|
-
exec_order_idx = [idx for idx, j in enumerate(exec_orders_all)
|
|
152
|
-
if j == i]
|
|
153
|
-
|
|
154
|
-
if len(exec_order_idx) > 1:
|
|
155
|
-
|
|
156
|
-
if len(exec_order_idx) > max_num_channels:
|
|
157
|
-
# Temporarily restrict number of channels to 1:
|
|
158
|
-
msg = ('Multiple channels are not yet implemented.')
|
|
159
|
-
raise NotImplementedError(msg)
|
|
160
|
-
|
|
161
|
-
else:
|
|
162
|
-
# Set execution orders for command groups:
|
|
163
|
-
for i in range(len(cmd_groups)):
|
|
164
|
-
cmd_groups[i]['exec_order'] = i
|
|
165
|
-
|
|
166
|
-
return cmd_groups
|