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
hpcflow/examples.ipynb ADDED
@@ -0,0 +1,1037 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": null,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "%load_ext autoreload\n",
10
+ "%autoreload 2"
11
+ ]
12
+ },
13
+ {
14
+ "cell_type": "code",
15
+ "execution_count": null,
16
+ "metadata": {},
17
+ "outputs": [],
18
+ "source": [
19
+ "from tempfile import gettempdir\n",
20
+ "\n",
21
+ "from hpcflow.api import (\n",
22
+ " WorkflowTemplate, Workflow, TaskSchema, Task, Parameter, SchemaInput, load_config,\n",
23
+ " InputValue, ElementSet, InputSource, ValueSequence, ElementPropagation, Action,\n",
24
+ " Command, ActionEnvironment\n",
25
+ ")\n",
26
+ "\n",
27
+ "# Usually the user will have a known configuration directory; this step won't be \n",
28
+ "# necessary. (I am doing this to avoid hpcflow reading the config file for the old version\n",
29
+ "# of hpcflow.)\n",
30
+ "load_config(config_dir=gettempdir())"
31
+ ]
32
+ },
33
+ {
34
+ "cell_type": "code",
35
+ "execution_count": null,
36
+ "metadata": {},
37
+ "outputs": [],
38
+ "source": [
39
+ "# Note we can also import the App instance like this:\n",
40
+ "from hpcflow.api import hpcflow\n",
41
+ "\n",
42
+ "# In MatFlow, this will be *slightly* less confusing, like this: `from matflow import MatFlow`\n",
43
+ "# The App instance shouldn't normally need to be imported.\n",
44
+ "\n",
45
+ "print(hpcflow.version)"
46
+ ]
47
+ },
48
+ {
49
+ "attachments": {},
50
+ "cell_type": "markdown",
51
+ "metadata": {},
52
+ "source": [
53
+ "### Example 1: simple workflow"
54
+ ]
55
+ },
56
+ {
57
+ "attachments": {},
58
+ "cell_type": "markdown",
59
+ "metadata": {},
60
+ "source": [
61
+ "#### Workflow generation using the API"
62
+ ]
63
+ },
64
+ {
65
+ "cell_type": "code",
66
+ "execution_count": null,
67
+ "metadata": {},
68
+ "outputs": [],
69
+ "source": [
70
+ "# Define some parameters (when end users use MatFlow, parameter types they/we define will \n",
71
+ "# be domain-specific and more descriptive -- not just p1, p2 etc! The same applies for\n",
72
+ "# task schemas.):\n",
73
+ "p1 = Parameter('p1')\n",
74
+ "p2 = Parameter('p2')\n",
75
+ "p3 = Parameter('p3')\n",
76
+ "p4 = Parameter('p4')\n",
77
+ "\n",
78
+ "\n",
79
+ "# Define some actions that transform the parameters in some way via commands:\n",
80
+ "act_1 = Action(\n",
81
+ " commands=[\n",
82
+ " Command(\n",
83
+ " command='echo \"$((<<parameter:p1>> + 100))\"',\n",
84
+ " stdout=\"<<parameter:p2>>\",\n",
85
+ " ),\n",
86
+ " ],\n",
87
+ " environments=[ActionEnvironment(environment=hpcflow.envs.null_env)],\n",
88
+ ")\n",
89
+ "\n",
90
+ "act_2 = Action(\n",
91
+ " commands=[\n",
92
+ " Command(\n",
93
+ " command='echo \"$((<<parameter:p2>> + <<parameter:p3>>))\"',\n",
94
+ " stdout=\"<<parameter:p4>>\",\n",
95
+ " ),\n",
96
+ " ],\n",
97
+ " environments=[ActionEnvironment(environment=hpcflow.envs.null_env)],\n",
98
+ ")"
99
+ ]
100
+ },
101
+ {
102
+ "cell_type": "code",
103
+ "execution_count": null,
104
+ "metadata": {},
105
+ "outputs": [],
106
+ "source": [
107
+ "# Define some task schemas\n",
108
+ "s1 = TaskSchema(objective='t1', actions=[act_1], inputs=[p1], outputs=[p2])\n",
109
+ "s2 = TaskSchema(objective='t2', actions=[act_2], inputs=[p2, p3], outputs=[p4])\n",
110
+ "\n",
111
+ "# Define some tasks:\n",
112
+ "t1 = Task(schemas=[s1], inputs=[InputValue(p1, 101)])\n",
113
+ "t2 = Task(schemas=[s2], inputs=[InputValue(p3, 301)]) # note: p2 will originate from t1 output\n",
114
+ "\n",
115
+ "# Generate a workflow template:\n",
116
+ "wkt = WorkflowTemplate(name='w1', tasks=[t1, t2])\n",
117
+ "\n",
118
+ "# Write a persistent workflow from the template:\n",
119
+ "# note: by default the workflow directory will be the workflow name + a date-timestamp\n",
120
+ "# here we pass the name explicitly, so there will be no date-timestamp, and we\n",
121
+ "# overwrite. This avoids generating loads of workflow directories during testing.\n",
122
+ "wk = Workflow.from_template(wkt, name=wkt.name, overwrite=True)"
123
+ ]
124
+ },
125
+ {
126
+ "attachments": {},
127
+ "cell_type": "markdown",
128
+ "metadata": {},
129
+ "source": [
130
+ "#### Examining the workflow structure"
131
+ ]
132
+ },
133
+ {
134
+ "cell_type": "markdown",
135
+ "metadata": {},
136
+ "source": [
137
+ "We can access tasks by their name:"
138
+ ]
139
+ },
140
+ {
141
+ "cell_type": "code",
142
+ "execution_count": null,
143
+ "metadata": {},
144
+ "outputs": [],
145
+ "source": [
146
+ "wk.tasks.t1"
147
+ ]
148
+ },
149
+ {
150
+ "attachments": {},
151
+ "cell_type": "markdown",
152
+ "metadata": {},
153
+ "source": [
154
+ "... or by index:"
155
+ ]
156
+ },
157
+ {
158
+ "cell_type": "code",
159
+ "execution_count": null,
160
+ "metadata": {},
161
+ "outputs": [],
162
+ "source": [
163
+ "wk.tasks[0]"
164
+ ]
165
+ },
166
+ {
167
+ "cell_type": "markdown",
168
+ "metadata": {},
169
+ "source": [
170
+ "Each task is associated with one or more elements:"
171
+ ]
172
+ },
173
+ {
174
+ "cell_type": "code",
175
+ "execution_count": null,
176
+ "metadata": {},
177
+ "outputs": [],
178
+ "source": [
179
+ "wk.tasks.t1.elements"
180
+ ]
181
+ },
182
+ {
183
+ "cell_type": "markdown",
184
+ "metadata": {},
185
+ "source": [
186
+ "Each element has input and output values, for each of the input/output types defined in the schema:"
187
+ ]
188
+ },
189
+ {
190
+ "cell_type": "code",
191
+ "execution_count": null,
192
+ "metadata": {},
193
+ "outputs": [],
194
+ "source": [
195
+ "wk.tasks.t1.elements[0].inputs, wk.tasks.t1.elements[0].outputs"
196
+ ]
197
+ },
198
+ {
199
+ "cell_type": "markdown",
200
+ "metadata": {},
201
+ "source": [
202
+ "The parameter (input or output) values can be accessed by name:"
203
+ ]
204
+ },
205
+ {
206
+ "cell_type": "code",
207
+ "execution_count": null,
208
+ "metadata": {},
209
+ "outputs": [],
210
+ "source": [
211
+ "wk.tasks.t1.elements[0].inputs.p1"
212
+ ]
213
+ },
214
+ {
215
+ "attachments": {},
216
+ "cell_type": "markdown",
217
+ "metadata": {},
218
+ "source": [
219
+ "The `ElementParameter` object contains metadata about the parameter as well as the parameter value, which can be accessed by the `value` attribute:"
220
+ ]
221
+ },
222
+ {
223
+ "cell_type": "code",
224
+ "execution_count": null,
225
+ "metadata": {},
226
+ "outputs": [],
227
+ "source": [
228
+ "wk.tasks.t1.elements[0].inputs.p1.value"
229
+ ]
230
+ },
231
+ {
232
+ "attachments": {},
233
+ "cell_type": "markdown",
234
+ "metadata": {},
235
+ "source": [
236
+ "There is a lower-level `Element` function for accessing parameter data by \"path\":"
237
+ ]
238
+ },
239
+ {
240
+ "cell_type": "code",
241
+ "execution_count": null,
242
+ "metadata": {},
243
+ "outputs": [],
244
+ "source": [
245
+ "wk.tasks.t1.elements[0].get(\"inputs.p1\")"
246
+ ]
247
+ },
248
+ {
249
+ "attachments": {},
250
+ "cell_type": "markdown",
251
+ "metadata": {},
252
+ "source": [
253
+ "Using `get` with no path argument returns all parameter data for that element:"
254
+ ]
255
+ },
256
+ {
257
+ "cell_type": "code",
258
+ "execution_count": null,
259
+ "metadata": {},
260
+ "outputs": [],
261
+ "source": [
262
+ "wk.tasks.t1.elements[0].get()"
263
+ ]
264
+ },
265
+ {
266
+ "attachments": {},
267
+ "cell_type": "markdown",
268
+ "metadata": {},
269
+ "source": [
270
+ "#### Checking dependencies"
271
+ ]
272
+ },
273
+ {
274
+ "attachments": {},
275
+ "cell_type": "markdown",
276
+ "metadata": {},
277
+ "source": [
278
+ "We can find the dependencies/dependents between tasks like this:"
279
+ ]
280
+ },
281
+ {
282
+ "cell_type": "code",
283
+ "execution_count": null,
284
+ "metadata": {},
285
+ "outputs": [],
286
+ "source": [
287
+ "wk.tasks.t2.get_task_dependencies(as_objects=True)"
288
+ ]
289
+ },
290
+ {
291
+ "cell_type": "code",
292
+ "execution_count": null,
293
+ "metadata": {},
294
+ "outputs": [],
295
+ "source": [
296
+ "wk.tasks.t1.get_dependent_tasks(as_objects=True)"
297
+ ]
298
+ },
299
+ {
300
+ "attachments": {},
301
+ "cell_type": "markdown",
302
+ "metadata": {},
303
+ "source": [
304
+ "These dependencies derive from the `p2` parameter, which is output by task `t1` and \"consumed\" by task `t2`. We can also check dependency relationship at the element level:"
305
+ ]
306
+ },
307
+ {
308
+ "cell_type": "code",
309
+ "execution_count": null,
310
+ "metadata": {},
311
+ "outputs": [],
312
+ "source": [
313
+ "wk.tasks.t2.elements[0].get_element_dependencies(as_objects=True)"
314
+ ]
315
+ },
316
+ {
317
+ "cell_type": "code",
318
+ "execution_count": null,
319
+ "metadata": {},
320
+ "outputs": [],
321
+ "source": [
322
+ "wk.tasks.t1.elements[0].get_dependent_elements(as_objects=True)"
323
+ ]
324
+ },
325
+ {
326
+ "cell_type": "markdown",
327
+ "metadata": {},
328
+ "source": [
329
+ "#### Adding more elements to an existing workflow"
330
+ ]
331
+ },
332
+ {
333
+ "attachments": {},
334
+ "cell_type": "markdown",
335
+ "metadata": {},
336
+ "source": [
337
+ "We can add more elements to a given task, using the `WorkflowTask.add_elements` method:"
338
+ ]
339
+ },
340
+ {
341
+ "cell_type": "code",
342
+ "execution_count": null,
343
+ "metadata": {},
344
+ "outputs": [],
345
+ "source": [
346
+ "wk.tasks.t2.add_elements(inputs=[InputValue(p3, 302)])"
347
+ ]
348
+ },
349
+ {
350
+ "attachments": {},
351
+ "cell_type": "markdown",
352
+ "metadata": {},
353
+ "source": [
354
+ "Now, two elements exist in the second task:"
355
+ ]
356
+ },
357
+ {
358
+ "cell_type": "code",
359
+ "execution_count": null,
360
+ "metadata": {},
361
+ "outputs": [],
362
+ "source": [
363
+ "wk.tasks.t2.elements"
364
+ ]
365
+ },
366
+ {
367
+ "cell_type": "markdown",
368
+ "metadata": {},
369
+ "source": [
370
+ "Both elements in task `t2` depend on the single element in task `t1`:"
371
+ ]
372
+ },
373
+ {
374
+ "cell_type": "code",
375
+ "execution_count": null,
376
+ "metadata": {},
377
+ "outputs": [],
378
+ "source": [
379
+ "wk.tasks.t1.elements[0].get_dependent_elements(as_objects=True)"
380
+ ]
381
+ },
382
+ {
383
+ "attachments": {},
384
+ "cell_type": "markdown",
385
+ "metadata": {},
386
+ "source": [
387
+ "#### Accessing the original `Task` and `WorkflowTemplate` objects"
388
+ ]
389
+ },
390
+ {
391
+ "attachments": {},
392
+ "cell_type": "markdown",
393
+ "metadata": {},
394
+ "source": [
395
+ "When a `Task` object is added to a workflow, it is converted into a `WorkflowTask` object. However, we can still access (a copy of) the original `Task` object, using the `template` attribute:"
396
+ ]
397
+ },
398
+ {
399
+ "cell_type": "code",
400
+ "execution_count": null,
401
+ "metadata": {},
402
+ "outputs": [],
403
+ "source": [
404
+ "wk.tasks.t1.template"
405
+ ]
406
+ },
407
+ {
408
+ "attachments": {},
409
+ "cell_type": "markdown",
410
+ "metadata": {},
411
+ "source": [
412
+ "We can also access (a copy of) the original workflow template in a similar way:"
413
+ ]
414
+ },
415
+ {
416
+ "cell_type": "code",
417
+ "execution_count": null,
418
+ "metadata": {},
419
+ "outputs": [],
420
+ "source": [
421
+ "wk.template"
422
+ ]
423
+ },
424
+ {
425
+ "cell_type": "code",
426
+ "execution_count": null,
427
+ "metadata": {},
428
+ "outputs": [],
429
+ "source": [
430
+ "# TODO: write out a workflow template YAML string here! e.g. `wk.template.to_YAML_string()`"
431
+ ]
432
+ },
433
+ {
434
+ "attachments": {},
435
+ "cell_type": "markdown",
436
+ "metadata": {},
437
+ "source": [
438
+ "#### Element sets"
439
+ ]
440
+ },
441
+ {
442
+ "attachments": {},
443
+ "cell_type": "markdown",
444
+ "metadata": {},
445
+ "source": [
446
+ "You might notice that the `Task` object accessed from a `Workflow` has no `inputs` attribute, even though we originally passed an `inputs` argument to its constructor, e.g. like this: `t1 = Task(schemas=[s1], inputs=[InputValue(p1, 101)])`. This is because arguments passed to the `Task` object are converted to an *element set*. Element sets, defined by the `ElementSet` class, represent the arguments that we can pass when constructing a task, or when adding new elements to an existing task. An equivalent way of constructing the above task would be like this:"
447
+ ]
448
+ },
449
+ {
450
+ "cell_type": "code",
451
+ "execution_count": null,
452
+ "metadata": {},
453
+ "outputs": [],
454
+ "source": [
455
+ "# t1 = Task(schemas=[s1], inputs=[InputValue(p1, 101)])\n",
456
+ "t1_b = Task(schemas=[s1], element_sets=[ElementSet(inputs=[InputValue(p1, 101)])])"
457
+ ]
458
+ },
459
+ {
460
+ "attachments": {},
461
+ "cell_type": "markdown",
462
+ "metadata": {},
463
+ "source": [
464
+ "This is more verbose, and so will not usually be used, but it shows how the arguments to the task are stored internally. Element set are also what are used when adding elements to an existing task. Previously, we wrote:\n",
465
+ "\n",
466
+ "```wk.tasks.t2.add_elements(inputs=[InputValue(p3, 302)])```.\n",
467
+ "\n",
468
+ "This could be written more verbosely with an element set like this:\n",
469
+ "\n",
470
+ "```wk.tasks.t2.add_elements(element_sets=[ElementSet(inputs=[InputValue(p3, 302)])])```.\n",
471
+ "\n",
472
+ "The element set concept exists to support adding elements after a task is initially created, and helps with knowing where elements originate from."
473
+ ]
474
+ },
475
+ {
476
+ "attachments": {},
477
+ "cell_type": "markdown",
478
+ "metadata": {},
479
+ "source": [
480
+ "We can access element sets via the `Task` object (via the `WorkflowTask.template` attribute):"
481
+ ]
482
+ },
483
+ {
484
+ "cell_type": "code",
485
+ "execution_count": null,
486
+ "metadata": {},
487
+ "outputs": [],
488
+ "source": [
489
+ "wk.tasks.t2.template.element_sets"
490
+ ]
491
+ },
492
+ {
493
+ "attachments": {},
494
+ "cell_type": "markdown",
495
+ "metadata": {},
496
+ "source": [
497
+ "Element sets contain the original parametrisation:"
498
+ ]
499
+ },
500
+ {
501
+ "cell_type": "code",
502
+ "execution_count": null,
503
+ "metadata": {},
504
+ "outputs": [],
505
+ "source": [
506
+ "wk.tasks.t2.template.element_sets[0].inputs"
507
+ ]
508
+ },
509
+ {
510
+ "attachments": {},
511
+ "cell_type": "markdown",
512
+ "metadata": {},
513
+ "source": [
514
+ "### Example 2: using default values and overriding input sources"
515
+ ]
516
+ },
517
+ {
518
+ "cell_type": "code",
519
+ "execution_count": null,
520
+ "metadata": {},
521
+ "outputs": [],
522
+ "source": [
523
+ "# Define some parameters\n",
524
+ "p1 = Parameter('p1')\n",
525
+ "p2 = Parameter('p2')\n",
526
+ "p3 = Parameter('p3')\n",
527
+ "p4 = Parameter('p4')"
528
+ ]
529
+ },
530
+ {
531
+ "attachments": {},
532
+ "cell_type": "markdown",
533
+ "metadata": {},
534
+ "source": [
535
+ "The `inputs` argument of the `TaskSchema` constructor in fact takes a list `SchemaInput` objects, but if we pass a `Parameter` object instead, it will transformed into a `SchemaInput`. Likewise, the `outputs` argument more precisely takes a list of `SchemaOutput` objects.\n",
536
+ "\n",
537
+ "The distinction allows us to provide default values for a give input type within a particular schema:"
538
+ ]
539
+ },
540
+ {
541
+ "cell_type": "code",
542
+ "execution_count": null,
543
+ "metadata": {},
544
+ "outputs": [],
545
+ "source": [
546
+ "p1_input = SchemaInput(p1, default_value=10)\n",
547
+ "p2_input = SchemaInput(p2, default_value=30)\n",
548
+ "\n",
549
+ "# Define some task schemas\n",
550
+ "s1 = TaskSchema(objective='t1', actions=[act_1], inputs=[p1_input], outputs=[p2])\n",
551
+ "s2 = TaskSchema(objective='t2', actions=[act_2], inputs=[p2_input, p3], outputs=[p4])"
552
+ ]
553
+ },
554
+ {
555
+ "attachments": {},
556
+ "cell_type": "markdown",
557
+ "metadata": {},
558
+ "source": [
559
+ "Now we can define a `Task` object without passing an input value for the `p1` parameter, because the default value can be used instead:"
560
+ ]
561
+ },
562
+ {
563
+ "cell_type": "code",
564
+ "execution_count": null,
565
+ "metadata": {},
566
+ "outputs": [],
567
+ "source": [
568
+ "# Define some tasks:\n",
569
+ "t1 = Task(schemas=[s1])\n",
570
+ "t2 = Task(schemas=[s2], inputs=[InputValue(p3, 301)])"
571
+ ]
572
+ },
573
+ {
574
+ "cell_type": "code",
575
+ "execution_count": null,
576
+ "metadata": {},
577
+ "outputs": [],
578
+ "source": [
579
+ "# Generate the workflow:\n",
580
+ "wkt = WorkflowTemplate(name='w1', tasks=[t1, t2])\n",
581
+ "wk = Workflow.from_template(wkt, name=wkt.name, overwrite=True) "
582
+ ]
583
+ },
584
+ {
585
+ "attachments": {},
586
+ "cell_type": "markdown",
587
+ "metadata": {},
588
+ "source": [
589
+ "We can check that the default value was used by inspecting the relevent `ElementSet` object. In particular, the `input_sources` attribute is a map between each input parameter type and a list of `InputSource` objects:"
590
+ ]
591
+ },
592
+ {
593
+ "cell_type": "code",
594
+ "execution_count": null,
595
+ "metadata": {},
596
+ "outputs": [],
597
+ "source": [
598
+ "wk.tasks.t1.template.element_sets[0].input_sources"
599
+ ]
600
+ },
601
+ {
602
+ "attachments": {},
603
+ "cell_type": "markdown",
604
+ "metadata": {},
605
+ "source": [
606
+ "There is some default behaviour regarding the input sources. If an parameter is defined locally (meaning within the task constructor), it's local value will be used. It the parameter is not provided locally, but is output from an upstream task, that value will be used. If a parameter is not defined locally nor is output from an upstream task, the default value will be used, if defined. Otherwise a `MissingInputs` exception will be raised."
607
+ ]
608
+ },
609
+ {
610
+ "cell_type": "code",
611
+ "execution_count": null,
612
+ "metadata": {},
613
+ "outputs": [],
614
+ "source": [
615
+ "wk.tasks.t2.template.element_sets[0].input_sources"
616
+ ]
617
+ },
618
+ {
619
+ "attachments": {},
620
+ "cell_type": "markdown",
621
+ "metadata": {},
622
+ "source": [
623
+ "We can override the default behaviour, and choose a different input source if required. We can demonstrate this by adding another element (set) to task `t2` and specify input sources manually:"
624
+ ]
625
+ },
626
+ {
627
+ "cell_type": "code",
628
+ "execution_count": null,
629
+ "metadata": {},
630
+ "outputs": [],
631
+ "source": [
632
+ "wk.tasks.t2.add_elements(\n",
633
+ " inputs=[\n",
634
+ " InputValue(p2_input, 201),\n",
635
+ " InputValue(p3, 301),\n",
636
+ " ],\n",
637
+ " input_sources={\n",
638
+ " 'p2': [InputSource.default()],\n",
639
+ " }\n",
640
+ ")"
641
+ ]
642
+ },
643
+ {
644
+ "attachments": {},
645
+ "cell_type": "markdown",
646
+ "metadata": {},
647
+ "source": [
648
+ "In the above case, the locally specified value of `p2` is not used, because we specified that the default value should be used. The default behaviour will still be used for any parameter types not included in the `input_sources` dict."
649
+ ]
650
+ },
651
+ {
652
+ "cell_type": "code",
653
+ "execution_count": null,
654
+ "metadata": {},
655
+ "outputs": [],
656
+ "source": [
657
+ "wk.tasks.t2.template.element_sets[1].input_sources"
658
+ ]
659
+ },
660
+ {
661
+ "attachments": {},
662
+ "cell_type": "markdown",
663
+ "metadata": {},
664
+ "source": [
665
+ "### Example 3: multiple elements per task: sequences"
666
+ ]
667
+ },
668
+ {
669
+ "cell_type": "code",
670
+ "execution_count": null,
671
+ "metadata": {},
672
+ "outputs": [],
673
+ "source": [
674
+ "# Define some parameters\n",
675
+ "p1 = Parameter('p1')\n",
676
+ "p2 = Parameter('p2')\n",
677
+ "p3 = Parameter('p3')\n",
678
+ "p4 = Parameter('p4')\n",
679
+ "\n",
680
+ "# Define some task schemas\n",
681
+ "s1 = TaskSchema(objective='t1', actions=[act_1], inputs=[p1], outputs=[p2])\n",
682
+ "s2 = TaskSchema(objective='t2', actions=[act_2], inputs=[p2, p3], outputs=[p4])"
683
+ ]
684
+ },
685
+ {
686
+ "attachments": {},
687
+ "cell_type": "markdown",
688
+ "metadata": {},
689
+ "source": [
690
+ "Multiple elements can be generated for a task by using the `sequences` argument:"
691
+ ]
692
+ },
693
+ {
694
+ "cell_type": "code",
695
+ "execution_count": null,
696
+ "metadata": {},
697
+ "outputs": [],
698
+ "source": [
699
+ "# Define some tasks:\n",
700
+ "t1 = Task(schemas=[s1], inputs=[InputValue(p1, 101)])\n",
701
+ "t2 = Task(\n",
702
+ " schemas=[s2],\n",
703
+ " sequences=[\n",
704
+ " ValueSequence('inputs.p3', values=[301, 302, 303], nesting_order=0), \n",
705
+ " ]\n",
706
+ ")\n",
707
+ "\n",
708
+ "# Note the \"parameter path\" (e.g. \"inputs.p3\") must be given to a `ValueSequence` object;\n",
709
+ "# this is because sequences can also be generated for resources (e.g. \"resources.num_cores\")"
710
+ ]
711
+ },
712
+ {
713
+ "cell_type": "code",
714
+ "execution_count": null,
715
+ "metadata": {},
716
+ "outputs": [],
717
+ "source": [
718
+ "# Generate the workflow:\n",
719
+ "wkt = WorkflowTemplate(name='w1', tasks=[t1, t2])\n",
720
+ "wk = Workflow.from_template(wkt, name=wkt.name, overwrite=True)"
721
+ ]
722
+ },
723
+ {
724
+ "attachments": {},
725
+ "cell_type": "markdown",
726
+ "metadata": {},
727
+ "source": [
728
+ "### Example 4: Propagating new elements downstream"
729
+ ]
730
+ },
731
+ {
732
+ "attachments": {},
733
+ "cell_type": "markdown",
734
+ "metadata": {},
735
+ "source": [
736
+ "When adding elements to a task, we can optionally propagate new elements to downstream tasks. Consider the workflow below, with three tasks, where each task depends on the previous:"
737
+ ]
738
+ },
739
+ {
740
+ "cell_type": "code",
741
+ "execution_count": null,
742
+ "metadata": {},
743
+ "outputs": [],
744
+ "source": [
745
+ "# define an action for the new task schema \"t3\":\n",
746
+ "act_3 = Action(\n",
747
+ " commands=[Command(\"echo <<parameter:p4>>\")],\n",
748
+ " environments=[ActionEnvironment(hpcflow.envs.null_env)]\n",
749
+ ")\n",
750
+ "\n",
751
+ "s1 = TaskSchema(objective='t1', actions=[act_1], inputs=[p1], outputs=[p2])\n",
752
+ "s2 = TaskSchema(objective='t2', actions=[act_2], inputs=[p2, p3], outputs=[p4])\n",
753
+ "s3 = TaskSchema(objective='t3', actions=[act_3], inputs=[p4], outputs=[])\n",
754
+ "\n",
755
+ "t1 = Task(schemas=[s1], inputs=[InputValue(p1, 101)])\n",
756
+ "t2 = Task(schemas=[s2], inputs=[InputValue(p3, 301)])\n",
757
+ "t3 = Task(schemas=[s3], inputs=[])\n",
758
+ "\n",
759
+ "wkt = WorkflowTemplate(name='w1', tasks=[t1, t2, t3])\n",
760
+ "wk = Workflow.from_template(wkt, name=wkt.name, overwrite=True)"
761
+ ]
762
+ },
763
+ {
764
+ "attachments": {},
765
+ "cell_type": "markdown",
766
+ "metadata": {},
767
+ "source": [
768
+ "There is a single element in each task:"
769
+ ]
770
+ },
771
+ {
772
+ "cell_type": "code",
773
+ "execution_count": null,
774
+ "metadata": {},
775
+ "outputs": [],
776
+ "source": [
777
+ "print([task.num_elements for task in wk.tasks])"
778
+ ]
779
+ },
780
+ {
781
+ "attachments": {},
782
+ "cell_type": "markdown",
783
+ "metadata": {},
784
+ "source": [
785
+ "Now if we add a new element to the first task, we can specify with the `propagate_to` argument that we wish to generate new elements in downstream tasks as well. The elements generated in the downstream tasks will be restricted to depending only on the new elements generated in their respective upstream tasks."
786
+ ]
787
+ },
788
+ {
789
+ "cell_type": "code",
790
+ "execution_count": null,
791
+ "metadata": {},
792
+ "outputs": [],
793
+ "source": [
794
+ "for elem in wk.tasks.t1.elements:\n",
795
+ " print(elem)"
796
+ ]
797
+ },
798
+ {
799
+ "cell_type": "code",
800
+ "execution_count": null,
801
+ "metadata": {},
802
+ "outputs": [],
803
+ "source": [
804
+ "wk.tasks"
805
+ ]
806
+ },
807
+ {
808
+ "cell_type": "code",
809
+ "execution_count": null,
810
+ "metadata": {},
811
+ "outputs": [],
812
+ "source": [
813
+ "# TODO: waiting for feat/submissions fix\n",
814
+ "wk.tasks.t1.add_elements(\n",
815
+ " inputs=[InputValue(p1, 102)],\n",
816
+ " propagate_to={\"t2\": {}, \"t3\": {}},\n",
817
+ ")"
818
+ ]
819
+ },
820
+ {
821
+ "attachments": {},
822
+ "cell_type": "markdown",
823
+ "metadata": {},
824
+ "source": [
825
+ "Now we have an additional element in each task:"
826
+ ]
827
+ },
828
+ {
829
+ "cell_type": "code",
830
+ "execution_count": null,
831
+ "metadata": {},
832
+ "outputs": [],
833
+ "source": [
834
+ "print([task.num_elements for task in wk.tasks])"
835
+ ]
836
+ },
837
+ {
838
+ "attachments": {},
839
+ "cell_type": "markdown",
840
+ "metadata": {},
841
+ "source": [
842
+ "Note that it is sufficient to specify `propagate_to` as an empty list to signify that you wish to propagate new elements downstream. There are cases where you will need to specify a nesting order for the newly generated downstream elements, in which case, `propagate_to` can be specified as a list of `ElementPropagation` objects, like this:"
843
+ ]
844
+ },
845
+ {
846
+ "cell_type": "code",
847
+ "execution_count": null,
848
+ "metadata": {},
849
+ "outputs": [],
850
+ "source": [
851
+ "wk.tasks.t1.add_elements(\n",
852
+ " inputs=[InputValue(p1, 103)],\n",
853
+ " propagate_to=[\n",
854
+ " ElementPropagation(task=wk.tasks.t2),\n",
855
+ " ElementPropagation(task=wk.tasks.t3)\n",
856
+ " ]\n",
857
+ ")"
858
+ ]
859
+ },
860
+ {
861
+ "cell_type": "code",
862
+ "execution_count": null,
863
+ "metadata": {},
864
+ "outputs": [],
865
+ "source": [
866
+ "print([task.num_elements for task in wk.tasks])"
867
+ ]
868
+ },
869
+ {
870
+ "attachments": {},
871
+ "cell_type": "markdown",
872
+ "metadata": {},
873
+ "source": [
874
+ "The `ElementPropagation` object accepts a `nesting_order` argument, which in this case is not needed, because there are no `sequences` in the workflow. If an `ElementPropagation` object is missing for a given downstream task, a default object will be generated, with no nesting order specified. Note that there is not currently a way to propagate to only some downstream tasks. We should refine this behaviour later."
875
+ ]
876
+ },
877
+ {
878
+ "attachments": {},
879
+ "cell_type": "markdown",
880
+ "metadata": {},
881
+ "source": [
882
+ "Let's look at the input sources used for all tasks."
883
+ ]
884
+ },
885
+ {
886
+ "cell_type": "code",
887
+ "execution_count": null,
888
+ "metadata": {},
889
+ "outputs": [],
890
+ "source": [
891
+ "for task in wk.tasks:\n",
892
+ " print(f\"{task.name=}\")\n",
893
+ " for elem_set in task.template.element_sets:\n",
894
+ " print(f\"{elem_set.index=}\")\n",
895
+ " for k, v in elem_set.input_sources.items():\n",
896
+ " print(f\"\\t{k}: {v}\")\n",
897
+ " print()"
898
+ ]
899
+ },
900
+ {
901
+ "attachments": {},
902
+ "cell_type": "markdown",
903
+ "metadata": {},
904
+ "source": [
905
+ "We can see that a second (and third) element set has been generated for each task due to the `add_elements` calls on the first task. The input sources for each element set in a given task look the same, except an additional argument `element_iters` in the second (and third) element sets for task input sources. For example, for the second element set of the second task, the input sources for `p2` look like this:\n",
906
+ "\n",
907
+ "```\n",
908
+ "[InputSource.task(task_ref=0, task_source_type=output, elements=[3])]\n",
909
+ "```\n",
910
+ "\n",
911
+ "The `element_iters` argument here serves as a *contraint* on the elements from which that input can be sourced. In particular, element index 3, in this case, is the newly added element from the first task."
912
+ ]
913
+ },
914
+ {
915
+ "attachments": {},
916
+ "cell_type": "markdown",
917
+ "metadata": {},
918
+ "source": [
919
+ "### Example 5: inserting tasks in the middle of the workflow"
920
+ ]
921
+ },
922
+ {
923
+ "cell_type": "code",
924
+ "execution_count": null,
925
+ "metadata": {},
926
+ "outputs": [],
927
+ "source": [
928
+ "s1 = TaskSchema(objective='t1', actions=[act_1], inputs=[p1], outputs=[p2])\n",
929
+ "s2 = TaskSchema(objective='t2', actions=[act_2], inputs=[p2, p3], outputs=[p4])\n",
930
+ "\n",
931
+ "t1 = Task(schemas=[s1], inputs=[InputValue(p1, 101)])\n",
932
+ "t2 = Task(schemas=[s2], inputs=[InputValue(p3, 301)])\n",
933
+ "\n",
934
+ "wkt = WorkflowTemplate(name='w1', tasks=[t1, t2])\n",
935
+ "wk = Workflow.from_template(wkt, name=wkt.name, overwrite=True)"
936
+ ]
937
+ },
938
+ {
939
+ "cell_type": "code",
940
+ "execution_count": null,
941
+ "metadata": {},
942
+ "outputs": [],
943
+ "source": [
944
+ "# define an action for the new task schema \"t3\":\n",
945
+ "act_3 = Action(\n",
946
+ " commands=[\n",
947
+ " Command(\n",
948
+ " command=\"echo <<parameter:p2>>\",\n",
949
+ " stdout=\"<<parameter:p2>>\",\n",
950
+ " stderr=\"<<parameter:p3>>\",\n",
951
+ " ),\n",
952
+ " ],\n",
953
+ " environments=[ActionEnvironment(hpcflow.envs.null_env)]\n",
954
+ ")\n",
955
+ "\n",
956
+ "s3 = TaskSchema(objective='t3', actions=[act_3], inputs=[p2], outputs=[p2, p3])\n",
957
+ "t3 = Task(schemas=[s3])\n",
958
+ "wk.add_task(t3, new_index=1) # this will add the task in between the existing tasks"
959
+ ]
960
+ },
961
+ {
962
+ "attachments": {},
963
+ "cell_type": "markdown",
964
+ "metadata": {},
965
+ "source": [
966
+ "Note that when adding a new task in the middle of the workflow, no new elements will be propagated to downstream tasks. However, in this case, we might want to add a new element to `t2` that uses outputs from the newly inserted task `t3`, which we can do like this:"
967
+ ]
968
+ },
969
+ {
970
+ "cell_type": "code",
971
+ "execution_count": null,
972
+ "metadata": {},
973
+ "outputs": [],
974
+ "source": [
975
+ "wk.tasks.t2.add_elements()"
976
+ ]
977
+ },
978
+ {
979
+ "attachments": {},
980
+ "cell_type": "markdown",
981
+ "metadata": {},
982
+ "source": [
983
+ "We can inspect the element sets and input sources to see what this has done (note that input source `task_ref` refers to task `insert_ID`s, since this does not change as we add/remove tasks):"
984
+ ]
985
+ },
986
+ {
987
+ "cell_type": "code",
988
+ "execution_count": null,
989
+ "metadata": {},
990
+ "outputs": [],
991
+ "source": [
992
+ "for task in wk.tasks:\n",
993
+ " print(f\"{task.name=} {task.insert_ID=} {task.num_elements=}\")\n",
994
+ " for elem_set in task.template.element_sets:\n",
995
+ " print(f\"{elem_set.index=}\")\n",
996
+ " for k, v in elem_set.input_sources.items():\n",
997
+ " print(f\"\\t{k}: {v}\")\n",
998
+ " print()"
999
+ ]
1000
+ },
1001
+ {
1002
+ "attachments": {},
1003
+ "cell_type": "markdown",
1004
+ "metadata": {},
1005
+ "source": [
1006
+ "So the second element set that we added to `t2` gets its `p2` and `p3` values from the newly added middle task `t3`."
1007
+ ]
1008
+ }
1009
+ ],
1010
+ "metadata": {
1011
+ "kernelspec": {
1012
+ "display_name": "hpcflow-new2-zEAkg0Xp-py3.10",
1013
+ "language": "python",
1014
+ "name": "python3"
1015
+ },
1016
+ "language_info": {
1017
+ "codemirror_mode": {
1018
+ "name": "ipython",
1019
+ "version": 3
1020
+ },
1021
+ "file_extension": ".py",
1022
+ "mimetype": "text/x-python",
1023
+ "name": "python",
1024
+ "nbconvert_exporter": "python",
1025
+ "pygments_lexer": "ipython3",
1026
+ "version": "3.10.8"
1027
+ },
1028
+ "orig_nbformat": 4,
1029
+ "vscode": {
1030
+ "interpreter": {
1031
+ "hash": "ddf37d14159059670ae5501c968490e36316a9e314066be903fdc4622421273c"
1032
+ }
1033
+ }
1034
+ },
1035
+ "nbformat": 4,
1036
+ "nbformat_minor": 2
1037
+ }