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.
Files changed (275) hide show
  1. hpcflow/__init__.py +2 -11
  2. hpcflow/__pyinstaller/__init__.py +5 -0
  3. hpcflow/__pyinstaller/hook-hpcflow.py +40 -0
  4. hpcflow/_version.py +1 -1
  5. hpcflow/app.py +43 -0
  6. hpcflow/cli.py +2 -461
  7. hpcflow/data/demo_data_manifest/__init__.py +3 -0
  8. hpcflow/data/demo_data_manifest/demo_data_manifest.json +6 -0
  9. hpcflow/data/jinja_templates/test/test_template.txt +8 -0
  10. hpcflow/data/programs/hello_world/README.md +1 -0
  11. hpcflow/data/programs/hello_world/hello_world.c +87 -0
  12. hpcflow/data/programs/hello_world/linux/hello_world +0 -0
  13. hpcflow/data/programs/hello_world/macos/hello_world +0 -0
  14. hpcflow/data/programs/hello_world/win/hello_world.exe +0 -0
  15. hpcflow/data/scripts/__init__.py +1 -0
  16. hpcflow/data/scripts/bad_script.py +2 -0
  17. hpcflow/data/scripts/demo_task_1_generate_t1_infile_1.py +8 -0
  18. hpcflow/data/scripts/demo_task_1_generate_t1_infile_2.py +8 -0
  19. hpcflow/data/scripts/demo_task_1_parse_p3.py +7 -0
  20. hpcflow/data/scripts/do_nothing.py +2 -0
  21. hpcflow/data/scripts/env_specifier_test/input_file_generator_pass_env_spec.py +4 -0
  22. hpcflow/data/scripts/env_specifier_test/main_script_test_pass_env_spec.py +8 -0
  23. hpcflow/data/scripts/env_specifier_test/output_file_parser_pass_env_spec.py +4 -0
  24. hpcflow/data/scripts/env_specifier_test/v1/input_file_generator_basic.py +4 -0
  25. hpcflow/data/scripts/env_specifier_test/v1/main_script_test_direct_in_direct_out.py +7 -0
  26. hpcflow/data/scripts/env_specifier_test/v1/output_file_parser_basic.py +4 -0
  27. hpcflow/data/scripts/env_specifier_test/v2/main_script_test_direct_in_direct_out.py +7 -0
  28. hpcflow/data/scripts/generate_t1_file_01.py +7 -0
  29. hpcflow/data/scripts/import_future_script.py +7 -0
  30. hpcflow/data/scripts/input_file_generator_basic.py +3 -0
  31. hpcflow/data/scripts/input_file_generator_basic_FAIL.py +3 -0
  32. hpcflow/data/scripts/input_file_generator_test_stdout_stderr.py +8 -0
  33. hpcflow/data/scripts/main_script_test_direct_in.py +3 -0
  34. hpcflow/data/scripts/main_script_test_direct_in_direct_out.py +6 -0
  35. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2.py +6 -0
  36. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed.py +6 -0
  37. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed_group.py +7 -0
  38. hpcflow/data/scripts/main_script_test_direct_in_direct_out_3.py +6 -0
  39. hpcflow/data/scripts/main_script_test_direct_in_direct_out_all_iters_test.py +15 -0
  40. hpcflow/data/scripts/main_script_test_direct_in_direct_out_env_spec.py +7 -0
  41. hpcflow/data/scripts/main_script_test_direct_in_direct_out_labels.py +8 -0
  42. hpcflow/data/scripts/main_script_test_direct_in_group_direct_out_3.py +6 -0
  43. hpcflow/data/scripts/main_script_test_direct_in_group_one_fail_direct_out_3.py +6 -0
  44. hpcflow/data/scripts/main_script_test_direct_sub_param_in_direct_out.py +6 -0
  45. hpcflow/data/scripts/main_script_test_hdf5_in_obj.py +12 -0
  46. hpcflow/data/scripts/main_script_test_hdf5_in_obj_2.py +12 -0
  47. hpcflow/data/scripts/main_script_test_hdf5_in_obj_group.py +12 -0
  48. hpcflow/data/scripts/main_script_test_hdf5_out_obj.py +11 -0
  49. hpcflow/data/scripts/main_script_test_json_and_direct_in_json_out.py +14 -0
  50. hpcflow/data/scripts/main_script_test_json_in_json_and_direct_out.py +17 -0
  51. hpcflow/data/scripts/main_script_test_json_in_json_out.py +14 -0
  52. hpcflow/data/scripts/main_script_test_json_in_json_out_labels.py +16 -0
  53. hpcflow/data/scripts/main_script_test_json_in_obj.py +12 -0
  54. hpcflow/data/scripts/main_script_test_json_out_FAIL.py +3 -0
  55. hpcflow/data/scripts/main_script_test_json_out_obj.py +10 -0
  56. hpcflow/data/scripts/main_script_test_json_sub_param_in_json_out_labels.py +16 -0
  57. hpcflow/data/scripts/main_script_test_shell_env_vars.py +12 -0
  58. hpcflow/data/scripts/main_script_test_std_out_std_err.py +6 -0
  59. hpcflow/data/scripts/output_file_parser_basic.py +3 -0
  60. hpcflow/data/scripts/output_file_parser_basic_FAIL.py +7 -0
  61. hpcflow/data/scripts/output_file_parser_test_stdout_stderr.py +8 -0
  62. hpcflow/data/scripts/parse_t1_file_01.py +4 -0
  63. hpcflow/data/scripts/script_exit_test.py +5 -0
  64. hpcflow/data/template_components/__init__.py +1 -0
  65. hpcflow/data/template_components/command_files.yaml +26 -0
  66. hpcflow/data/template_components/environments.yaml +13 -0
  67. hpcflow/data/template_components/parameters.yaml +14 -0
  68. hpcflow/data/template_components/task_schemas.yaml +139 -0
  69. hpcflow/data/workflows/workflow_1.yaml +5 -0
  70. hpcflow/examples.ipynb +1037 -0
  71. hpcflow/sdk/__init__.py +149 -0
  72. hpcflow/sdk/app.py +4266 -0
  73. hpcflow/sdk/cli.py +1479 -0
  74. hpcflow/sdk/cli_common.py +385 -0
  75. hpcflow/sdk/config/__init__.py +5 -0
  76. hpcflow/sdk/config/callbacks.py +246 -0
  77. hpcflow/sdk/config/cli.py +388 -0
  78. hpcflow/sdk/config/config.py +1410 -0
  79. hpcflow/sdk/config/config_file.py +501 -0
  80. hpcflow/sdk/config/errors.py +272 -0
  81. hpcflow/sdk/config/types.py +150 -0
  82. hpcflow/sdk/core/__init__.py +38 -0
  83. hpcflow/sdk/core/actions.py +3857 -0
  84. hpcflow/sdk/core/app_aware.py +25 -0
  85. hpcflow/sdk/core/cache.py +224 -0
  86. hpcflow/sdk/core/command_files.py +814 -0
  87. hpcflow/sdk/core/commands.py +424 -0
  88. hpcflow/sdk/core/element.py +2071 -0
  89. hpcflow/sdk/core/enums.py +221 -0
  90. hpcflow/sdk/core/environment.py +256 -0
  91. hpcflow/sdk/core/errors.py +1043 -0
  92. hpcflow/sdk/core/execute.py +207 -0
  93. hpcflow/sdk/core/json_like.py +809 -0
  94. hpcflow/sdk/core/loop.py +1320 -0
  95. hpcflow/sdk/core/loop_cache.py +282 -0
  96. hpcflow/sdk/core/object_list.py +933 -0
  97. hpcflow/sdk/core/parameters.py +3371 -0
  98. hpcflow/sdk/core/rule.py +196 -0
  99. hpcflow/sdk/core/run_dir_files.py +57 -0
  100. hpcflow/sdk/core/skip_reason.py +7 -0
  101. hpcflow/sdk/core/task.py +3792 -0
  102. hpcflow/sdk/core/task_schema.py +993 -0
  103. hpcflow/sdk/core/test_utils.py +538 -0
  104. hpcflow/sdk/core/types.py +447 -0
  105. hpcflow/sdk/core/utils.py +1207 -0
  106. hpcflow/sdk/core/validation.py +87 -0
  107. hpcflow/sdk/core/values.py +477 -0
  108. hpcflow/sdk/core/workflow.py +4820 -0
  109. hpcflow/sdk/core/zarr_io.py +206 -0
  110. hpcflow/sdk/data/__init__.py +13 -0
  111. hpcflow/sdk/data/config_file_schema.yaml +34 -0
  112. hpcflow/sdk/data/config_schema.yaml +260 -0
  113. hpcflow/sdk/data/environments_spec_schema.yaml +21 -0
  114. hpcflow/sdk/data/files_spec_schema.yaml +5 -0
  115. hpcflow/sdk/data/parameters_spec_schema.yaml +7 -0
  116. hpcflow/sdk/data/task_schema_spec_schema.yaml +3 -0
  117. hpcflow/sdk/data/workflow_spec_schema.yaml +22 -0
  118. hpcflow/sdk/demo/__init__.py +3 -0
  119. hpcflow/sdk/demo/cli.py +242 -0
  120. hpcflow/sdk/helper/__init__.py +3 -0
  121. hpcflow/sdk/helper/cli.py +137 -0
  122. hpcflow/sdk/helper/helper.py +300 -0
  123. hpcflow/sdk/helper/watcher.py +192 -0
  124. hpcflow/sdk/log.py +288 -0
  125. hpcflow/sdk/persistence/__init__.py +18 -0
  126. hpcflow/sdk/persistence/base.py +2817 -0
  127. hpcflow/sdk/persistence/defaults.py +6 -0
  128. hpcflow/sdk/persistence/discovery.py +39 -0
  129. hpcflow/sdk/persistence/json.py +954 -0
  130. hpcflow/sdk/persistence/pending.py +948 -0
  131. hpcflow/sdk/persistence/store_resource.py +203 -0
  132. hpcflow/sdk/persistence/types.py +309 -0
  133. hpcflow/sdk/persistence/utils.py +73 -0
  134. hpcflow/sdk/persistence/zarr.py +2388 -0
  135. hpcflow/sdk/runtime.py +320 -0
  136. hpcflow/sdk/submission/__init__.py +3 -0
  137. hpcflow/sdk/submission/enums.py +70 -0
  138. hpcflow/sdk/submission/jobscript.py +2379 -0
  139. hpcflow/sdk/submission/schedulers/__init__.py +281 -0
  140. hpcflow/sdk/submission/schedulers/direct.py +233 -0
  141. hpcflow/sdk/submission/schedulers/sge.py +376 -0
  142. hpcflow/sdk/submission/schedulers/slurm.py +598 -0
  143. hpcflow/sdk/submission/schedulers/utils.py +25 -0
  144. hpcflow/sdk/submission/shells/__init__.py +52 -0
  145. hpcflow/sdk/submission/shells/base.py +229 -0
  146. hpcflow/sdk/submission/shells/bash.py +504 -0
  147. hpcflow/sdk/submission/shells/os_version.py +115 -0
  148. hpcflow/sdk/submission/shells/powershell.py +352 -0
  149. hpcflow/sdk/submission/submission.py +1402 -0
  150. hpcflow/sdk/submission/types.py +140 -0
  151. hpcflow/sdk/typing.py +194 -0
  152. hpcflow/sdk/utils/arrays.py +69 -0
  153. hpcflow/sdk/utils/deferred_file.py +55 -0
  154. hpcflow/sdk/utils/hashing.py +16 -0
  155. hpcflow/sdk/utils/patches.py +31 -0
  156. hpcflow/sdk/utils/strings.py +69 -0
  157. hpcflow/tests/api/test_api.py +32 -0
  158. hpcflow/tests/conftest.py +123 -0
  159. hpcflow/tests/data/__init__.py +0 -0
  160. hpcflow/tests/data/benchmark_N_elements.yaml +6 -0
  161. hpcflow/tests/data/benchmark_script_runner.yaml +26 -0
  162. hpcflow/tests/data/multi_path_sequences.yaml +29 -0
  163. hpcflow/tests/data/workflow_1.json +10 -0
  164. hpcflow/tests/data/workflow_1.yaml +5 -0
  165. hpcflow/tests/data/workflow_1_slurm.yaml +8 -0
  166. hpcflow/tests/data/workflow_1_wsl.yaml +8 -0
  167. hpcflow/tests/data/workflow_test_run_abort.yaml +42 -0
  168. hpcflow/tests/jinja_templates/test_jinja_templates.py +161 -0
  169. hpcflow/tests/programs/test_programs.py +180 -0
  170. hpcflow/tests/schedulers/direct_linux/test_direct_linux_submission.py +12 -0
  171. hpcflow/tests/schedulers/sge/test_sge_submission.py +36 -0
  172. hpcflow/tests/schedulers/slurm/test_slurm_submission.py +14 -0
  173. hpcflow/tests/scripts/test_input_file_generators.py +282 -0
  174. hpcflow/tests/scripts/test_main_scripts.py +1361 -0
  175. hpcflow/tests/scripts/test_non_snippet_script.py +46 -0
  176. hpcflow/tests/scripts/test_ouput_file_parsers.py +353 -0
  177. hpcflow/tests/shells/wsl/test_wsl_submission.py +14 -0
  178. hpcflow/tests/unit/test_action.py +1066 -0
  179. hpcflow/tests/unit/test_action_rule.py +24 -0
  180. hpcflow/tests/unit/test_app.py +132 -0
  181. hpcflow/tests/unit/test_cache.py +46 -0
  182. hpcflow/tests/unit/test_cli.py +172 -0
  183. hpcflow/tests/unit/test_command.py +377 -0
  184. hpcflow/tests/unit/test_config.py +195 -0
  185. hpcflow/tests/unit/test_config_file.py +162 -0
  186. hpcflow/tests/unit/test_element.py +666 -0
  187. hpcflow/tests/unit/test_element_iteration.py +88 -0
  188. hpcflow/tests/unit/test_element_set.py +158 -0
  189. hpcflow/tests/unit/test_group.py +115 -0
  190. hpcflow/tests/unit/test_input_source.py +1479 -0
  191. hpcflow/tests/unit/test_input_value.py +398 -0
  192. hpcflow/tests/unit/test_jobscript_unit.py +757 -0
  193. hpcflow/tests/unit/test_json_like.py +1247 -0
  194. hpcflow/tests/unit/test_loop.py +2674 -0
  195. hpcflow/tests/unit/test_meta_task.py +325 -0
  196. hpcflow/tests/unit/test_multi_path_sequences.py +259 -0
  197. hpcflow/tests/unit/test_object_list.py +116 -0
  198. hpcflow/tests/unit/test_parameter.py +243 -0
  199. hpcflow/tests/unit/test_persistence.py +664 -0
  200. hpcflow/tests/unit/test_resources.py +243 -0
  201. hpcflow/tests/unit/test_run.py +286 -0
  202. hpcflow/tests/unit/test_run_directories.py +29 -0
  203. hpcflow/tests/unit/test_runtime.py +9 -0
  204. hpcflow/tests/unit/test_schema_input.py +372 -0
  205. hpcflow/tests/unit/test_shell.py +129 -0
  206. hpcflow/tests/unit/test_slurm.py +39 -0
  207. hpcflow/tests/unit/test_submission.py +502 -0
  208. hpcflow/tests/unit/test_task.py +2560 -0
  209. hpcflow/tests/unit/test_task_schema.py +182 -0
  210. hpcflow/tests/unit/test_utils.py +616 -0
  211. hpcflow/tests/unit/test_value_sequence.py +549 -0
  212. hpcflow/tests/unit/test_values.py +91 -0
  213. hpcflow/tests/unit/test_workflow.py +827 -0
  214. hpcflow/tests/unit/test_workflow_template.py +186 -0
  215. hpcflow/tests/unit/utils/test_arrays.py +40 -0
  216. hpcflow/tests/unit/utils/test_deferred_file_writer.py +34 -0
  217. hpcflow/tests/unit/utils/test_hashing.py +65 -0
  218. hpcflow/tests/unit/utils/test_patches.py +5 -0
  219. hpcflow/tests/unit/utils/test_redirect_std.py +50 -0
  220. hpcflow/tests/unit/utils/test_strings.py +97 -0
  221. hpcflow/tests/workflows/__init__.py +0 -0
  222. hpcflow/tests/workflows/test_directory_structure.py +31 -0
  223. hpcflow/tests/workflows/test_jobscript.py +355 -0
  224. hpcflow/tests/workflows/test_run_status.py +198 -0
  225. hpcflow/tests/workflows/test_skip_downstream.py +696 -0
  226. hpcflow/tests/workflows/test_submission.py +140 -0
  227. hpcflow/tests/workflows/test_workflows.py +564 -0
  228. hpcflow/tests/workflows/test_zip.py +18 -0
  229. hpcflow/viz_demo.ipynb +6794 -0
  230. hpcflow-0.2.0a271.dist-info/LICENSE +375 -0
  231. hpcflow-0.2.0a271.dist-info/METADATA +65 -0
  232. hpcflow-0.2.0a271.dist-info/RECORD +237 -0
  233. {hpcflow-0.1.15.dist-info → hpcflow-0.2.0a271.dist-info}/WHEEL +4 -5
  234. hpcflow-0.2.0a271.dist-info/entry_points.txt +6 -0
  235. hpcflow/api.py +0 -490
  236. hpcflow/archive/archive.py +0 -307
  237. hpcflow/archive/cloud/cloud.py +0 -45
  238. hpcflow/archive/cloud/errors.py +0 -9
  239. hpcflow/archive/cloud/providers/dropbox.py +0 -427
  240. hpcflow/archive/errors.py +0 -5
  241. hpcflow/base_db.py +0 -4
  242. hpcflow/config.py +0 -233
  243. hpcflow/copytree.py +0 -66
  244. hpcflow/data/examples/_config.yml +0 -14
  245. hpcflow/data/examples/damask/demo/1.run.yml +0 -4
  246. hpcflow/data/examples/damask/demo/2.process.yml +0 -29
  247. hpcflow/data/examples/damask/demo/geom.geom +0 -2052
  248. hpcflow/data/examples/damask/demo/load.load +0 -1
  249. hpcflow/data/examples/damask/demo/material.config +0 -185
  250. hpcflow/data/examples/damask/inputs/geom.geom +0 -2052
  251. hpcflow/data/examples/damask/inputs/load.load +0 -1
  252. hpcflow/data/examples/damask/inputs/material.config +0 -185
  253. hpcflow/data/examples/damask/profiles/_variable_lookup.yml +0 -21
  254. hpcflow/data/examples/damask/profiles/damask.yml +0 -4
  255. hpcflow/data/examples/damask/profiles/damask_process.yml +0 -8
  256. hpcflow/data/examples/damask/profiles/damask_run.yml +0 -5
  257. hpcflow/data/examples/damask/profiles/default.yml +0 -6
  258. hpcflow/data/examples/thinking.yml +0 -177
  259. hpcflow/errors.py +0 -2
  260. hpcflow/init_db.py +0 -37
  261. hpcflow/models.py +0 -2595
  262. hpcflow/nesting.py +0 -9
  263. hpcflow/profiles.py +0 -455
  264. hpcflow/project.py +0 -81
  265. hpcflow/scheduler.py +0 -322
  266. hpcflow/utils.py +0 -103
  267. hpcflow/validation.py +0 -166
  268. hpcflow/variables.py +0 -543
  269. hpcflow-0.1.15.dist-info/METADATA +0 -168
  270. hpcflow-0.1.15.dist-info/RECORD +0 -45
  271. hpcflow-0.1.15.dist-info/entry_points.txt +0 -8
  272. hpcflow-0.1.15.dist-info/top_level.txt +0 -1
  273. /hpcflow/{archive → data/jinja_templates}/__init__.py +0 -0
  274. /hpcflow/{archive/cloud → data/programs}/__init__.py +0 -0
  275. /hpcflow/{archive/cloud/providers → data/workflows}/__init__.py +0 -0
@@ -0,0 +1,502 @@
1
+ from __future__ import annotations
2
+ from datetime import timedelta
3
+ from typing import Any
4
+ from typing_extensions import TypedDict
5
+ import pytest
6
+
7
+ from hpcflow.app import app as hf
8
+ from hpcflow.sdk.core.errors import (
9
+ MissingEnvironmentError,
10
+ MissingEnvironmentExecutableError,
11
+ MissingEnvironmentExecutableInstanceError,
12
+ )
13
+ from hpcflow.sdk.core.utils import timedelta_format, timedelta_parse
14
+ from hpcflow.sdk.submission.jobscript import group_resource_map_into_jobscripts
15
+
16
+
17
+ @pytest.fixture
18
+ def null_config(tmp_path):
19
+ if not hf.is_config_loaded:
20
+ hf.load_config(config_dir=tmp_path)
21
+
22
+
23
+ class _Example(TypedDict):
24
+ resources: list[list[int]]
25
+ expected: list[dict[str, Any]]
26
+
27
+
28
+ def test_group_resource_map_into_jobscripts(null_config) -> None:
29
+ # x-axis corresponds to elements; y-axis corresponds to actions:
30
+ examples: tuple[_Example, ...] = (
31
+ {
32
+ "resources": [
33
+ [1, 1, 1, 2, -1, 2, 4, -1, 1],
34
+ [1, 3, 1, 2, 2, 2, 4, 4, 1],
35
+ [1, 1, 3, 2, 2, 2, 4, -1, 1],
36
+ ],
37
+ "expected": [
38
+ {
39
+ "resources": 1,
40
+ "elements": {0: [0, 1, 2], 1: [0], 2: [0, 1], 8: [0, 1, 2]},
41
+ },
42
+ {"resources": 2, "elements": {3: [0, 1, 2], 4: [1, 2], 5: [0, 1, 2]}},
43
+ {"resources": 4, "elements": {6: [0, 1, 2], 7: [1]}},
44
+ {"resources": 3, "elements": {1: [1]}},
45
+ {"resources": 1, "elements": {1: [2]}},
46
+ {"resources": 3, "elements": {2: [2]}},
47
+ ],
48
+ },
49
+ {
50
+ "resources": [
51
+ [2, 2, -1],
52
+ [8, 8, 1],
53
+ [4, 4, 1],
54
+ ],
55
+ "expected": [
56
+ {"resources": 2, "elements": {0: [0], 1: [0]}},
57
+ {"resources": 1, "elements": {2: [1, 2]}},
58
+ {"resources": 8, "elements": {0: [1], 1: [1]}},
59
+ {"resources": 4, "elements": {0: [2], 1: [2]}},
60
+ ],
61
+ },
62
+ {
63
+ "resources": [
64
+ [2, 2, -1],
65
+ [2, 2, 1],
66
+ [4, 4, 1],
67
+ ],
68
+ "expected": [
69
+ {"resources": 2, "elements": {0: [0, 1], 1: [0, 1]}},
70
+ {"resources": 1, "elements": {2: [1, 2]}},
71
+ {"resources": 4, "elements": {0: [2], 1: [2]}},
72
+ ],
73
+ },
74
+ {
75
+ "resources": [
76
+ [2, 1, 2],
77
+ [1, 1, 1],
78
+ [1, 1, 1],
79
+ ],
80
+ "expected": [
81
+ {"resources": 1, "elements": {1: [0, 1, 2]}},
82
+ {"resources": 2, "elements": {0: [0], 2: [0]}},
83
+ {"resources": 1, "elements": {0: [1, 2], 2: [1, 2]}},
84
+ ],
85
+ },
86
+ {
87
+ "resources": [
88
+ [2, -1, 2],
89
+ [1, 1, 1],
90
+ [1, 1, 1],
91
+ ],
92
+ "expected": [
93
+ {"resources": 2, "elements": {0: [0], 2: [0]}},
94
+ {"resources": 1, "elements": {0: [1, 2], 1: [1, 2], 2: [1, 2]}},
95
+ ],
96
+ },
97
+ {
98
+ "resources": [
99
+ [1, 1],
100
+ [1, 1],
101
+ [1, 1],
102
+ ],
103
+ "expected": [{"resources": 1, "elements": {0: [0, 1, 2], 1: [0, 1, 2]}}],
104
+ },
105
+ {
106
+ "resources": [
107
+ [1, 1, 1],
108
+ [1, 1, -1],
109
+ [1, 1, 1],
110
+ ],
111
+ "expected": [
112
+ {"resources": 1, "elements": {0: [0, 1, 2], 1: [0, 1, 2], 2: [0, 2]}}
113
+ ],
114
+ },
115
+ {
116
+ "resources": [
117
+ [1, 1, -1],
118
+ [1, 1, 1],
119
+ [1, 1, 1],
120
+ ],
121
+ "expected": [
122
+ {"resources": 1, "elements": {0: [0, 1, 2], 1: [0, 1, 2], 2: [1, 2]}}
123
+ ],
124
+ },
125
+ {
126
+ "resources": [
127
+ [2, 2, -1],
128
+ [4, 4, 1],
129
+ [4, 4, -1],
130
+ [2, 2, 1],
131
+ ],
132
+ "expected": [
133
+ {"resources": 2, "elements": {0: [0], 1: [0]}},
134
+ {"resources": 1, "elements": {2: [1, 3]}},
135
+ {"resources": 4, "elements": {0: [1, 2], 1: [1, 2]}},
136
+ {"resources": 2, "elements": {0: [3], 1: [3]}},
137
+ ],
138
+ },
139
+ {
140
+ "resources": [
141
+ [2, 2, -1],
142
+ [4, 4, 1],
143
+ [4, 4, -1],
144
+ [1, 1, 1],
145
+ ],
146
+ "expected": [
147
+ {"resources": 2, "elements": {0: [0], 1: [0]}},
148
+ {"resources": 1, "elements": {2: [1, 3]}},
149
+ {"resources": 4, "elements": {0: [1, 2], 1: [1, 2]}},
150
+ {"resources": 1, "elements": {0: [3], 1: [3]}},
151
+ ],
152
+ },
153
+ {
154
+ "resources": [
155
+ [2, 2, -1],
156
+ [4, 4, 1],
157
+ [4, 8, -1],
158
+ [1, 1, 1],
159
+ ],
160
+ "expected": [
161
+ {"resources": 2, "elements": {0: [0], 1: [0]}},
162
+ {"resources": 1, "elements": {2: [1, 3]}},
163
+ {"resources": 4, "elements": {0: [1, 2], 1: [1]}},
164
+ {"resources": 8, "elements": {1: [2]}},
165
+ {"resources": 1, "elements": {0: [3], 1: [3]}},
166
+ ],
167
+ },
168
+ {
169
+ "resources": [
170
+ [2, 2, -1],
171
+ [4, 4, 1],
172
+ [4, -1, -1],
173
+ [1, 1, 1],
174
+ ],
175
+ "expected": [
176
+ {"resources": 2, "elements": {0: [0], 1: [0]}},
177
+ {"resources": 1, "elements": {2: [1, 3]}},
178
+ {"resources": 4, "elements": {0: [1, 2], 1: [1]}},
179
+ {"resources": 1, "elements": {0: [3], 1: [3]}},
180
+ ],
181
+ },
182
+ )
183
+ for i in examples:
184
+ jobscripts_i, _ = group_resource_map_into_jobscripts(i["resources"])
185
+ assert jobscripts_i == i["expected"]
186
+
187
+
188
+ def test_timedelta_parse_format_round_trip(null_config) -> None:
189
+ td = timedelta(days=2, hours=25, minutes=92, seconds=77)
190
+ td_str = timedelta_format(td)
191
+ assert td_str == timedelta_format(timedelta_parse(td_str))
192
+
193
+
194
+ def test_raise_missing_env_executable(new_null_config, tmp_path) -> None:
195
+ exec_name = (
196
+ "my_executable" # null_env (the default) has no executable "my_executable"
197
+ )
198
+ ts = hf.TaskSchema(
199
+ objective="test_sub",
200
+ actions=[hf.Action(commands=[hf.Command(command=f"<<executable:{exec_name}>>")])],
201
+ )
202
+ t1 = hf.Task(schema=ts)
203
+ wkt = hf.WorkflowTemplate(
204
+ name="test_sub",
205
+ tasks=[t1],
206
+ )
207
+ wk = hf.Workflow.from_template(wkt, path=tmp_path)
208
+ with pytest.raises(MissingEnvironmentExecutableError):
209
+ wk.add_submission()
210
+
211
+
212
+ def test_raise_missing_matching_env_executable(new_null_config, tmp_path) -> None:
213
+ """The executable label exists, but no a matching instance."""
214
+ env_name = "my_hpcflow_env"
215
+ exec_label = "my_exec_name"
216
+ env = hf.Environment(
217
+ name=env_name,
218
+ executables=[
219
+ hf.Executable(
220
+ label=exec_label,
221
+ instances=[
222
+ hf.ExecutableInstance(
223
+ command="command", num_cores=1, parallel_mode=None
224
+ )
225
+ ],
226
+ )
227
+ ],
228
+ )
229
+ hf.envs.add_object(env, skip_duplicates=True)
230
+
231
+ ts = hf.TaskSchema(
232
+ objective="test_sub",
233
+ actions=[
234
+ hf.Action(
235
+ environments=[hf.ActionEnvironment(environment=env_name)],
236
+ commands=[hf.Command(command=f"<<executable:{exec_label}>>")],
237
+ )
238
+ ],
239
+ )
240
+ t1 = hf.Task(schema=ts)
241
+ wkt = hf.WorkflowTemplate(
242
+ name="test_sub",
243
+ tasks=[t1],
244
+ resources={"any": {"num_cores": 2}},
245
+ )
246
+ wk = hf.Workflow.from_template(wkt, path=tmp_path)
247
+ with pytest.raises(MissingEnvironmentExecutableInstanceError):
248
+ wk.add_submission()
249
+
250
+ hf.reload_template_components() # remove extra envs
251
+
252
+
253
+ def test_no_raise_matching_env_executable(new_null_config, tmp_path) -> None:
254
+ env_name = "my_hpcflow_env"
255
+ exec_label = "my_exec_name"
256
+ env = hf.Environment(
257
+ name=env_name,
258
+ executables=[
259
+ hf.Executable(
260
+ label=exec_label,
261
+ instances=[
262
+ hf.ExecutableInstance(
263
+ command="command", num_cores=2, parallel_mode=None
264
+ )
265
+ ],
266
+ )
267
+ ],
268
+ )
269
+ hf.envs.add_object(env, skip_duplicates=True)
270
+
271
+ ts = hf.TaskSchema(
272
+ objective="test_sub",
273
+ actions=[
274
+ hf.Action(
275
+ environments=[hf.ActionEnvironment(environment=env_name)],
276
+ commands=[hf.Command(command=f"<<executable:{exec_label}>>")],
277
+ )
278
+ ],
279
+ )
280
+ t1 = hf.Task(schema=ts)
281
+ wkt = hf.WorkflowTemplate(
282
+ name="test_sub",
283
+ tasks=[t1],
284
+ resources={"any": {"num_cores": 2}},
285
+ )
286
+ wk = hf.Workflow.from_template(wkt, path=tmp_path)
287
+ wk.add_submission()
288
+
289
+ hf.reload_template_components() # remove extra envs
290
+
291
+
292
+ def test_raise_missing_env(new_null_config, tmp_path) -> None:
293
+ env_name = "my_hpcflow_env"
294
+ ts = hf.TaskSchema(
295
+ objective="test_sub",
296
+ actions=[hf.Action(environments=[hf.ActionEnvironment(environment=env_name)])],
297
+ )
298
+ t1 = hf.Task(schema=ts)
299
+ wkt = hf.WorkflowTemplate(
300
+ name="test_sub",
301
+ tasks=[t1],
302
+ )
303
+ wk = hf.Workflow.from_template(wkt, path=tmp_path)
304
+ with pytest.raises(MissingEnvironmentError):
305
+ wk.add_submission()
306
+
307
+
308
+ def test_custom_env_and_executable(new_null_config, tmp_path) -> None:
309
+ env_name = "my_hpcflow_env"
310
+ exec_label = "my_exec_name"
311
+ env = hf.Environment(
312
+ name=env_name,
313
+ executables=[
314
+ hf.Executable(
315
+ label=exec_label,
316
+ instances=[
317
+ hf.ExecutableInstance(
318
+ command="command", num_cores=1, parallel_mode=None
319
+ )
320
+ ],
321
+ )
322
+ ],
323
+ )
324
+ hf.envs.add_object(env, skip_duplicates=True)
325
+
326
+ ts = hf.TaskSchema(
327
+ objective="test_sub",
328
+ actions=[
329
+ hf.Action(
330
+ environments=[hf.ActionEnvironment(environment=env_name)],
331
+ commands=[hf.Command(command=f"<<executable:{exec_label}>>")],
332
+ )
333
+ ],
334
+ )
335
+ t1 = hf.Task(schema=ts)
336
+ wkt = hf.WorkflowTemplate(
337
+ name="test_sub",
338
+ tasks=[t1],
339
+ )
340
+ wk = hf.Workflow.from_template(wkt, path=tmp_path)
341
+ wk.add_submission()
342
+
343
+ hf.reload_template_components() # remove extra envs
344
+
345
+
346
+ def test_unique_schedulers_one_direct(new_null_config, tmp_path) -> None:
347
+ t1 = hf.Task(
348
+ schema=hf.task_schemas.test_t1_conditional_OS,
349
+ inputs={"p1": 1},
350
+ )
351
+ t2 = hf.Task(
352
+ schema=hf.task_schemas.test_t1_conditional_OS,
353
+ inputs={"p1": 1},
354
+ )
355
+ wkt = hf.WorkflowTemplate(name="temp", tasks=[t1, t2])
356
+ wk = hf.Workflow.from_template(
357
+ template=wkt,
358
+ path=tmp_path,
359
+ )
360
+ sub = wk.add_submission()
361
+ assert sub is not None
362
+ scheds = sub.get_unique_schedulers()
363
+
364
+ assert len(scheds) == 1
365
+
366
+
367
+ def test_unique_schedulers_one_direct_distinct_resources(
368
+ new_null_config, tmp_path
369
+ ) -> None:
370
+ t1 = hf.Task(
371
+ schema=hf.task_schemas.test_t1_conditional_OS,
372
+ inputs={"p1": 1},
373
+ resources={"any": {"num_cores": 1}},
374
+ )
375
+ t2 = hf.Task(
376
+ schema=hf.task_schemas.test_t1_conditional_OS,
377
+ inputs={"p1": 1},
378
+ resources={"any": {"num_cores": 2}},
379
+ )
380
+ wkt = hf.WorkflowTemplate(name="temp", tasks=[t1, t2])
381
+ wk = hf.Workflow.from_template(
382
+ template=wkt,
383
+ path=tmp_path,
384
+ )
385
+ sub = wk.add_submission()
386
+ assert sub is not None
387
+ scheds = sub.get_unique_schedulers()
388
+
389
+ assert len(scheds) == 1
390
+
391
+
392
+ @pytest.mark.slurm
393
+ def test_unique_schedulers_one_SLURM(new_null_config, tmp_path) -> None:
394
+ hf.config.add_scheduler("slurm")
395
+ t1 = hf.Task(
396
+ schema=hf.task_schemas.test_t1_conditional_OS,
397
+ inputs={"p1": 1},
398
+ resources={"any": {"scheduler": "slurm"}},
399
+ )
400
+ t2 = hf.Task(
401
+ schema=hf.task_schemas.test_t1_conditional_OS,
402
+ inputs={"p1": 1},
403
+ resources={"any": {"scheduler": "slurm"}},
404
+ )
405
+ wkt = hf.WorkflowTemplate(name="temp", tasks=[t1, t2])
406
+ wk = hf.Workflow.from_template(
407
+ template=wkt,
408
+ path=tmp_path,
409
+ )
410
+ sub = wk.add_submission()
411
+ assert sub is not None
412
+ scheds = sub.get_unique_schedulers()
413
+
414
+ assert len(scheds) == 1
415
+
416
+
417
+ @pytest.mark.slurm
418
+ def test_unique_schedulers_one_SLURM_distinct_resources(
419
+ new_null_config, tmp_path
420
+ ) -> None:
421
+ hf.config.add_scheduler("slurm")
422
+ t1 = hf.Task(
423
+ schema=hf.task_schemas.test_t1_conditional_OS,
424
+ inputs={"p1": 1},
425
+ resources={"any": {"scheduler": "slurm", "num_cores": 1}},
426
+ )
427
+ t2 = hf.Task(
428
+ schema=hf.task_schemas.test_t1_conditional_OS,
429
+ inputs={"p1": 1},
430
+ resources={"any": {"scheduler": "slurm", "num_cores": 2}},
431
+ )
432
+ wkt = hf.WorkflowTemplate(name="temp", tasks=[t1, t2])
433
+ wk = hf.Workflow.from_template(
434
+ template=wkt,
435
+ path=tmp_path,
436
+ )
437
+ sub = wk.add_submission()
438
+ assert sub is not None
439
+ scheds = sub.get_unique_schedulers()
440
+
441
+ assert len(scheds) == 1
442
+
443
+
444
+ @pytest.mark.slurm
445
+ def test_unique_schedulers_two_direct_and_SLURM(new_null_config, tmp_path) -> None:
446
+ hf.config.add_scheduler("slurm")
447
+ t1 = hf.Task(
448
+ schema=hf.task_schemas.test_t1_conditional_OS,
449
+ inputs={"p1": 1},
450
+ resources={"any": {"scheduler": "direct"}},
451
+ )
452
+ t2 = hf.Task(
453
+ schema=hf.task_schemas.test_t1_conditional_OS,
454
+ inputs={"p1": 1},
455
+ resources={"any": {"scheduler": "slurm"}},
456
+ )
457
+ wkt = hf.WorkflowTemplate(name="temp", tasks=[t1, t2])
458
+ wk = hf.Workflow.from_template(
459
+ template=wkt,
460
+ path=tmp_path,
461
+ )
462
+ sub = wk.add_submission()
463
+ assert sub is not None
464
+ scheds = sub.get_unique_schedulers()
465
+
466
+ assert len(scheds) == 2
467
+
468
+
469
+ def test_scheduler_config_defaults(new_null_config, tmp_path) -> None:
470
+ """Check default options defined in the config are merged into jobscript resources."""
471
+
472
+ # note we use the `shebang_executable` for this test. On Windows, this will not be
473
+ # included in the jobscript, so it is effectively ignored, but the test is still
474
+ # valid.
475
+ hf.config.set("schedulers.direct.defaults.shebang_executable", ["/bin/bash"])
476
+
477
+ t1 = hf.Task(
478
+ schema=hf.task_schemas.test_t1_ps,
479
+ inputs={"p1": 1},
480
+ resources={"any": {"scheduler": "direct"}},
481
+ )
482
+ t2 = hf.Task(
483
+ schema=hf.task_schemas.test_t1_ps,
484
+ inputs={"p1": 1},
485
+ resources={
486
+ "any": {
487
+ "scheduler": "direct",
488
+ "scheduler_args": {"shebang_executable": ["bash"]},
489
+ }
490
+ },
491
+ )
492
+ wkt = hf.WorkflowTemplate(name="temp", tasks=[t1, t2])
493
+ wk = hf.Workflow.from_template(
494
+ template=wkt,
495
+ path=tmp_path,
496
+ )
497
+ sub = wk.add_submission()
498
+ assert sub is not None
499
+ assert sub.jobscripts[0].resources.scheduler_args == {
500
+ "shebang_executable": ["/bin/bash"]
501
+ }
502
+ assert sub.jobscripts[1].resources.scheduler_args == {"shebang_executable": ["bash"]}