hpcflow-new2 0.2.0a164__tar.gz → 0.2.0a167__tar.gz
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_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/PKG-INFO +1 -1
- hpcflow_new2-0.2.0a167/hpcflow/_version.py +1 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/core/errors.py +16 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/core/parameters.py +26 -2
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/core/task.py +205 -57
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_element_set.py +10 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_input_source.py +601 -1
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_task.py +1 -1
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/pyproject.toml +2 -2
- hpcflow_new2-0.2.0a164/hpcflow/_version.py +0 -1
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/README.md +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/__init__.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/__pyinstaller/__init__.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/__pyinstaller/hook-hpcflow.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/app.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/cli.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/demo_data_manifest/__init__.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/demo_data_manifest/demo_data_manifest.json +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/scripts/__init__.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/scripts/demo_task_1_generate_t1_infile_1.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/scripts/demo_task_1_generate_t1_infile_2.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/scripts/demo_task_1_parse_p3.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/scripts/generate_t1_file_01.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/scripts/main_script_test_direct_in_direct_out.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/scripts/main_script_test_direct_in_direct_out_all_iters_test.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/scripts/main_script_test_direct_in_direct_out_env_spec.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/scripts/main_script_test_direct_in_direct_out_labels.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/scripts/main_script_test_direct_sub_param_in_direct_out.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/scripts/main_script_test_hdf5_in_obj.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/scripts/main_script_test_hdf5_out_obj.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/scripts/main_script_test_json_and_direct_in_json_out.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/scripts/main_script_test_json_in_json_and_direct_out.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/scripts/main_script_test_json_in_json_out.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/scripts/main_script_test_json_in_json_out_labels.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/scripts/main_script_test_json_in_obj.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/scripts/main_script_test_json_out_obj.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/scripts/main_script_test_json_sub_param_in_json_out_labels.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/scripts/parse_t1_file_01.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/template_components/__init__.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/template_components/command_files.yaml +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/template_components/environments.yaml +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/template_components/parameters.yaml +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/template_components/task_schemas.yaml +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/workflows/__init__.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/data/workflows/workflow_1.yaml +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/examples.ipynb +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/__init__.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/app.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/cli.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/cli_common.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/config/__init__.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/config/callbacks.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/config/cli.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/config/config.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/config/config_file.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/config/errors.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/core/__init__.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/core/actions.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/core/command_files.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/core/commands.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/core/element.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/core/environment.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/core/json_like.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/core/loop.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/core/object_list.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/core/parallel.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/core/rule.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/core/run_dir_files.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/core/task_schema.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/core/test_utils.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/core/utils.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/core/validation.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/core/workflow.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/core/zarr_io.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/data/__init__.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/data/config_file_schema.yaml +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/data/config_schema.yaml +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/data/environments_spec_schema.yaml +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/data/files_spec_schema.yaml +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/data/parameters_spec_schema.yaml +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/data/task_schema_spec_schema.yaml +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/data/workflow_spec_schema.yaml +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/demo/__init__.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/demo/cli.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/helper/__init__.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/helper/cli.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/helper/helper.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/helper/watcher.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/log.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/persistence/__init__.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/persistence/base.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/persistence/json.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/persistence/pending.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/persistence/store_resource.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/persistence/utils.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/persistence/zarr.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/runtime.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/submission/__init__.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/submission/jobscript.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/submission/jobscript_info.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/submission/schedulers/__init__.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/submission/schedulers/direct.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/submission/schedulers/sge.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/submission/schedulers/slurm.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/submission/schedulers/utils.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/submission/shells/__init__.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/submission/shells/base.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/submission/shells/bash.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/submission/shells/os_version.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/submission/shells/powershell.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/submission/submission.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/sdk/typing.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/conftest.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/data/__init__.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/data/benchmark_N_elements.yaml +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/data/workflow_1.json +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/data/workflow_1.yaml +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/data/workflow_1_slurm.yaml +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/data/workflow_1_wsl.yaml +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/data/workflow_test_run_abort.yaml +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/schedulers/direct_linux/test_direct_linux_submission.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/schedulers/slurm/test_slurm_submission.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/scripts/test_main_scripts.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/shells/wsl/test_wsl_submission.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_action.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_action_rule.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_app.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_cli.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_command.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_config.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_config_file.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_element.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_element_iteration.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_input_value.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_json_like.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_loop.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_object_list.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_parameter.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_persistence.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_resources.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_run.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_runtime.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_schema_input.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_shell.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_slurm.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_submission.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_task_schema.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_utils.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_value_sequence.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_workflow.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/unit/test_workflow_template.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/workflows/test_jobscript.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/tests/workflows/test_workflows.py +0 -0
- {hpcflow_new2-0.2.0a164 → hpcflow_new2-0.2.0a167}/hpcflow/viz_demo.ipynb +0 -0
@@ -0,0 +1 @@
|
|
1
|
+
__version__ = "0.2.0a167"
|
@@ -55,6 +55,18 @@ class ExtraInputs(Exception):
|
|
55
55
|
super().__init__(message)
|
56
56
|
|
57
57
|
|
58
|
+
class UnavailableInputSource(ValueError):
|
59
|
+
pass
|
60
|
+
|
61
|
+
|
62
|
+
class InapplicableInputSourceElementIters(ValueError):
|
63
|
+
pass
|
64
|
+
|
65
|
+
|
66
|
+
class NoCoincidentInputSources(ValueError):
|
67
|
+
pass
|
68
|
+
|
69
|
+
|
58
70
|
class TaskTemplateInvalidNesting(ValueError):
|
59
71
|
pass
|
60
72
|
|
@@ -131,6 +143,10 @@ class MalformedParameterPathError(ValueError):
|
|
131
143
|
pass
|
132
144
|
|
133
145
|
|
146
|
+
class MalformedNestingOrderPath(ValueError):
|
147
|
+
pass
|
148
|
+
|
149
|
+
|
134
150
|
class UnknownResourceSpecItemError(ValueError):
|
135
151
|
pass
|
136
152
|
|
@@ -869,6 +869,11 @@ class ValueSequence(JSONLike):
|
|
869
869
|
vals = (vals.T).tolist()
|
870
870
|
return vals
|
871
871
|
|
872
|
+
@classmethod
|
873
|
+
def _values_from_random_uniform(cls, num, low=0.0, high=1.0, seed=None):
|
874
|
+
rng = np.random.default_rng(seed)
|
875
|
+
return rng.uniform(low=low, high=high, size=num).tolist()
|
876
|
+
|
872
877
|
@classmethod
|
873
878
|
def from_linear_space(
|
874
879
|
cls,
|
@@ -1027,6 +1032,25 @@ class ValueSequence(JSONLike):
|
|
1027
1032
|
obj._values_method_args = args
|
1028
1033
|
return obj
|
1029
1034
|
|
1035
|
+
@classmethod
|
1036
|
+
def from_random_uniform(
|
1037
|
+
cls,
|
1038
|
+
path,
|
1039
|
+
num,
|
1040
|
+
low=0.0,
|
1041
|
+
high=1.0,
|
1042
|
+
seed=None,
|
1043
|
+
nesting_order=0,
|
1044
|
+
label=None,
|
1045
|
+
**kwargs,
|
1046
|
+
):
|
1047
|
+
args = {"low": low, "high": high, "num": num, "seed": seed, **kwargs}
|
1048
|
+
values = cls._values_from_random_uniform(**args)
|
1049
|
+
obj = cls(values=values, path=path, nesting_order=nesting_order, label=label)
|
1050
|
+
obj._values_method = "from_random_uniform"
|
1051
|
+
obj._values_method_args = args
|
1052
|
+
return obj
|
1053
|
+
|
1030
1054
|
|
1031
1055
|
@dataclass
|
1032
1056
|
class AbstractInputValue(JSONLike):
|
@@ -1793,7 +1817,7 @@ class InputSource(JSONLike):
|
|
1793
1817
|
f"task_source_type={self.task_source_type.name.lower()!r}",
|
1794
1818
|
)
|
1795
1819
|
|
1796
|
-
if self.element_iters:
|
1820
|
+
if self.element_iters is not None:
|
1797
1821
|
args_lst.append(f"element_iters={self.element_iters}")
|
1798
1822
|
|
1799
1823
|
if self.where is not None:
|
@@ -1831,7 +1855,7 @@ class InputSource(JSONLike):
|
|
1831
1855
|
out = [self.source_type.name.lower()]
|
1832
1856
|
if self.source_type is InputSourceType.TASK:
|
1833
1857
|
out += [str(self.task_ref), self.task_source_type.name.lower()]
|
1834
|
-
if self.element_iters:
|
1858
|
+
if self.element_iters is not None:
|
1835
1859
|
out += ["[" + ",".join(f"{i}" for i in self.element_iters) + "]"]
|
1836
1860
|
elif self.source_type is InputSourceType.IMPORT:
|
1837
1861
|
out += [str(self.import_ref)]
|
@@ -1,7 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
+
from collections import defaultdict
|
2
3
|
import copy
|
3
4
|
from dataclasses import dataclass
|
4
|
-
import os
|
5
5
|
from pathlib import Path
|
6
6
|
from typing import Any, Dict, Iterable, Iterator, List, Optional, Tuple, Union
|
7
7
|
|
@@ -11,20 +11,23 @@ from valida.rules import Rule
|
|
11
11
|
from hpcflow.sdk import app
|
12
12
|
from hpcflow.sdk.core.task_schema import TaskSchema
|
13
13
|
from hpcflow.sdk.log import TimeIt
|
14
|
-
from hpcflow.sdk.submission.shells import DEFAULT_SHELL_NAMES
|
15
14
|
from .json_like import ChildObjectSpec, JSONLike
|
16
15
|
from .element import ElementGroup
|
17
16
|
from .errors import (
|
18
17
|
ContainerKeyError,
|
19
18
|
ExtraInputs,
|
19
|
+
InapplicableInputSourceElementIters,
|
20
|
+
MalformedNestingOrderPath,
|
20
21
|
MayNeedObjectError,
|
21
22
|
MissingInputs,
|
22
23
|
NoAvailableElementSetsError,
|
24
|
+
NoCoincidentInputSources,
|
23
25
|
TaskTemplateInvalidNesting,
|
24
26
|
TaskTemplateMultipleInputValues,
|
25
27
|
TaskTemplateMultipleSchemaObjectives,
|
26
28
|
TaskTemplateUnexpectedInput,
|
27
29
|
TaskTemplateUnexpectedSequenceInput,
|
30
|
+
UnavailableInputSource,
|
28
31
|
UnknownEnvironmentPresetError,
|
29
32
|
UnrequiredInputSources,
|
30
33
|
UnsetParameterDataError,
|
@@ -269,6 +272,16 @@ class ElementSet(JSONLike):
|
|
269
272
|
}
|
270
273
|
]
|
271
274
|
|
275
|
+
# check `nesting_order` paths:
|
276
|
+
allowed_nesting_paths = ("inputs", "resources", "repeats")
|
277
|
+
for k in self.nesting_order:
|
278
|
+
if k.split(".")[0] not in allowed_nesting_paths:
|
279
|
+
raise MalformedNestingOrderPath(
|
280
|
+
f"Element set: nesting order path {k!r} not understood. Each key in "
|
281
|
+
f"`nesting_order` must be start with one of "
|
282
|
+
f"{allowed_nesting_paths!r}."
|
283
|
+
)
|
284
|
+
|
272
285
|
inp_paths = [i.normalised_inputs_path for i in self.inputs]
|
273
286
|
dup_inp_paths = get_duplicate_items(inp_paths)
|
274
287
|
if dup_inp_paths:
|
@@ -358,6 +371,7 @@ class ElementSet(JSONLike):
|
|
358
371
|
nesting_order=None,
|
359
372
|
env_preset=None,
|
360
373
|
environments=None,
|
374
|
+
allow_non_coincident_task_sources=None,
|
361
375
|
element_sets=None,
|
362
376
|
sourceable_elem_iters=None,
|
363
377
|
):
|
@@ -381,10 +395,22 @@ class ElementSet(JSONLike):
|
|
381
395
|
"If providing an `element_set`, no other arguments are allowed."
|
382
396
|
)
|
383
397
|
else:
|
384
|
-
element_sets = [
|
398
|
+
element_sets = [
|
399
|
+
cls(
|
400
|
+
*args,
|
401
|
+
sourceable_elem_iters=sourceable_elem_iters,
|
402
|
+
allow_non_coincident_task_sources=allow_non_coincident_task_sources,
|
403
|
+
)
|
404
|
+
]
|
385
405
|
else:
|
386
406
|
if element_sets is None:
|
387
|
-
element_sets = [
|
407
|
+
element_sets = [
|
408
|
+
cls(
|
409
|
+
*args,
|
410
|
+
sourceable_elem_iters=sourceable_elem_iters,
|
411
|
+
allow_non_coincident_task_sources=allow_non_coincident_task_sources,
|
412
|
+
)
|
413
|
+
]
|
388
414
|
|
389
415
|
return element_sets
|
390
416
|
|
@@ -552,6 +578,7 @@ class Task(JSONLike):
|
|
552
578
|
nesting_order: Optional[List] = None,
|
553
579
|
env_preset: Optional[str] = None,
|
554
580
|
environments: Optional[Dict[str, Dict[str, Any]]] = None,
|
581
|
+
allow_non_coincident_task_sources: Optional[bool] = False,
|
555
582
|
element_sets: Optional[List[app.ElementSet]] = None,
|
556
583
|
output_labels: Optional[List[app.OutputLabel]] = None,
|
557
584
|
sourceable_elem_iters: Optional[List[int]] = None,
|
@@ -565,6 +592,10 @@ class Task(JSONLike):
|
|
565
592
|
schema names that uniquely identify a task schema. If strings are provided,
|
566
593
|
the `TaskSchema` object will be fetched from the known task schemas loaded by
|
567
594
|
the app configuration.
|
595
|
+
allow_non_coincident_task_sources
|
596
|
+
If True, if more than one parameter is sourced from the same task, then allow
|
597
|
+
these sources to come from distinct element sub-sets. If False (default),
|
598
|
+
only the intersection of element sub-sets for all parameters are included.
|
568
599
|
merge_envs
|
569
600
|
If True, merge environment presets (set via the element set `env_preset` key)
|
570
601
|
into `resources` using the "any" scope. If False, these presets are ignored.
|
@@ -616,6 +647,7 @@ class Task(JSONLike):
|
|
616
647
|
env_preset=env_preset,
|
617
648
|
environments=environments,
|
618
649
|
element_sets=element_sets,
|
650
|
+
allow_non_coincident_task_sources=allow_non_coincident_task_sources,
|
619
651
|
sourceable_elem_iters=sourceable_elem_iters,
|
620
652
|
)
|
621
653
|
self._output_labels = output_labels or []
|
@@ -821,10 +853,6 @@ class Task(JSONLike):
|
|
821
853
|
|
822
854
|
return names
|
823
855
|
|
824
|
-
def _get_nesting_order(self, seq):
|
825
|
-
"""Find the nesting order for a task sequence."""
|
826
|
-
return self.nesting_order[seq.normalised_path] if len(seq.values) > 1 else -1
|
827
|
-
|
828
856
|
@TimeIt.decorator
|
829
857
|
def _prepare_persistent_outputs(self, workflow, local_element_idx_range):
|
830
858
|
# TODO: check that schema is present when adding task? (should this be here?)
|
@@ -904,6 +932,9 @@ class Task(JSONLike):
|
|
904
932
|
def _get_task_source_element_iters(
|
905
933
|
self, in_or_out: str, src_task, labelled_path, element_set
|
906
934
|
) -> List[int]:
|
935
|
+
"""Get a sorted list of element iteration IDs that provide either inputs or
|
936
|
+
outputs from the provided source task."""
|
937
|
+
|
907
938
|
if in_or_out == "input":
|
908
939
|
# input parameter might not be provided e.g. if it is only used
|
909
940
|
# to generate an input file, and that input file is passed
|
@@ -921,7 +952,7 @@ class Task(JSONLike):
|
|
921
952
|
src_elem_iters = []
|
922
953
|
for es_idx_i in es_idx:
|
923
954
|
es_i = src_task.element_sets[es_idx_i]
|
924
|
-
src_elem_iters += es_i.elem_iter_IDs
|
955
|
+
src_elem_iters += es_i.elem_iter_IDs # should be sorted already
|
925
956
|
|
926
957
|
if element_set.sourceable_elem_iters is not None:
|
927
958
|
# can only use a subset of element iterations (this is the
|
@@ -929,8 +960,8 @@ class Task(JSONLike):
|
|
929
960
|
# element set, in which case we only want to consider newly
|
930
961
|
# added upstream elements when adding elements from this
|
931
962
|
# element set):
|
932
|
-
src_elem_iters =
|
933
|
-
set(element_set.sourceable_elem_iters) & set(src_elem_iters)
|
963
|
+
src_elem_iters = sorted(
|
964
|
+
list(set(element_set.sourceable_elem_iters) & set(src_elem_iters))
|
934
965
|
)
|
935
966
|
|
936
967
|
return src_elem_iters
|
@@ -1269,10 +1300,14 @@ class Task(JSONLike):
|
|
1269
1300
|
for es_i in self.element_sets:
|
1270
1301
|
for inp_j in es_i.inputs:
|
1271
1302
|
if inp_j.is_sub_value:
|
1272
|
-
|
1303
|
+
val_j = ("input", inp_j.normalised_inputs_path)
|
1304
|
+
if val_j not in out:
|
1305
|
+
out.append(val_j)
|
1273
1306
|
for seq_j in es_i.sequences:
|
1274
1307
|
if seq_j.is_sub_value:
|
1275
|
-
|
1308
|
+
val_j = ("input", seq_j.normalised_inputs_path)
|
1309
|
+
if val_j not in out:
|
1310
|
+
out.append(val_j)
|
1276
1311
|
|
1277
1312
|
return tuple(out)
|
1278
1313
|
|
@@ -1399,10 +1434,12 @@ class WorkflowTask:
|
|
1399
1434
|
return self.dir_name
|
1400
1435
|
return self.dir_name + "_" + "_".join((f"{k}-{v}" for k, v in loop_idx.items()))
|
1401
1436
|
|
1402
|
-
def get_all_element_iterations(self):
|
1403
|
-
return
|
1437
|
+
def get_all_element_iterations(self) -> Dict[int, app.ElementIteration]:
|
1438
|
+
return {j.id_: j for i in self.elements for j in i.iterations}
|
1404
1439
|
|
1405
|
-
def _make_new_elements_persistent(
|
1440
|
+
def _make_new_elements_persistent(
|
1441
|
+
self, element_set, element_set_idx, padded_elem_iters
|
1442
|
+
):
|
1406
1443
|
"""Save parameter data to the persistent workflow."""
|
1407
1444
|
|
1408
1445
|
# TODO: rewrite. This method is a little hard to follow and results in somewhat
|
@@ -1496,9 +1533,8 @@ class WorkflowTask:
|
|
1496
1533
|
if inp_src.element_iters:
|
1497
1534
|
# only include "sourceable" element iterations:
|
1498
1535
|
src_elem_iters = [
|
1499
|
-
i for i in
|
1536
|
+
src_elem_iters[i] for i in inp_src.element_iters
|
1500
1537
|
]
|
1501
|
-
|
1502
1538
|
src_elem_set_idx = [
|
1503
1539
|
i.element.element_set_idx for i in src_elem_iters
|
1504
1540
|
]
|
@@ -1512,8 +1548,14 @@ class WorkflowTask:
|
|
1512
1548
|
else:
|
1513
1549
|
src_key = f"{task_source_type}s.{labelled_path_i}"
|
1514
1550
|
|
1551
|
+
padded_iters = padded_elem_iters.get(labelled_path_i, [])
|
1515
1552
|
grp_idx = [
|
1516
|
-
|
1553
|
+
(
|
1554
|
+
iter_i.get_data_idx()[src_key]
|
1555
|
+
if iter_i_idx not in padded_iters
|
1556
|
+
else -1
|
1557
|
+
)
|
1558
|
+
for iter_i_idx, iter_i in enumerate(src_elem_iters)
|
1517
1559
|
]
|
1518
1560
|
|
1519
1561
|
if inp_group_name:
|
@@ -1523,7 +1565,6 @@ class WorkflowTask:
|
|
1523
1565
|
):
|
1524
1566
|
src_es = src_task.template.element_sets[src_set_idx_i]
|
1525
1567
|
if inp_group_name in [i.name for i in src_es.groups or []]:
|
1526
|
-
# print(f"IN GROUP; {dat_idx_i}; {src_set_idx_i=}")
|
1527
1568
|
group_dat_idx.append(dat_idx_i)
|
1528
1569
|
else:
|
1529
1570
|
# if for any recursive iteration dependency, this group is
|
@@ -1570,7 +1611,7 @@ class WorkflowTask:
|
|
1570
1611
|
input_data_idx[key] += grp_idx
|
1571
1612
|
source_idx[key] += [inp_src_idx] * len(grp_idx)
|
1572
1613
|
|
1573
|
-
else:
|
1614
|
+
else: # BUG: doesn't work for multiple task inputs sources
|
1574
1615
|
# overwrite existing local source (if it exists):
|
1575
1616
|
input_data_idx[key] = grp_idx
|
1576
1617
|
source_idx[key] = [inp_src_idx] * len(grp_idx)
|
@@ -1594,7 +1635,7 @@ class WorkflowTask:
|
|
1594
1635
|
|
1595
1636
|
return (input_data_idx, sequence_idx, source_idx)
|
1596
1637
|
|
1597
|
-
def ensure_input_sources(self, element_set):
|
1638
|
+
def ensure_input_sources(self, element_set) -> Dict[str, List[int]]:
|
1598
1639
|
"""Check valid input sources are specified for a new task to be added to the
|
1599
1640
|
workflow in a given position. If none are specified, set them according to the
|
1600
1641
|
default behaviour.
|
@@ -1649,29 +1690,38 @@ class WorkflowTask:
|
|
1649
1690
|
specified_source, self.unique_name
|
1650
1691
|
)
|
1651
1692
|
avail_idx = specified_source.is_in(avail_i)
|
1652
|
-
|
1653
|
-
|
1654
|
-
|
1693
|
+
try:
|
1694
|
+
available_source = avail_i[avail_idx]
|
1695
|
+
except TypeError:
|
1696
|
+
raise UnavailableInputSource(
|
1655
1697
|
f"The input source {specified_source.to_string()!r} is not "
|
1656
1698
|
f"available for input path {path_i!r}. Available "
|
1657
1699
|
f"input sources are: {[i.to_string() for i in avail_i]}."
|
1658
|
-
)
|
1659
|
-
|
1660
|
-
|
1661
|
-
|
1662
|
-
#
|
1663
|
-
|
1664
|
-
if specified_source.
|
1665
|
-
|
1666
|
-
|
1700
|
+
) from None
|
1701
|
+
|
1702
|
+
elem_iters_IDs = available_source.element_iters
|
1703
|
+
if specified_source.element_iters:
|
1704
|
+
# user-specified iter IDs; these must be a subset of available
|
1705
|
+
# element_iters:
|
1706
|
+
if not set(specified_source.element_iters).issubset(elem_iters_IDs):
|
1707
|
+
raise InapplicableInputSourceElementIters(
|
1708
|
+
f"The specified `element_iters` for input source "
|
1709
|
+
f"{specified_source.to_string()!r} are not all applicable. "
|
1710
|
+
f"Applicable element iteration IDs for this input source "
|
1711
|
+
f"are: {elem_iters_IDs!r}."
|
1667
1712
|
)
|
1668
|
-
|
1669
|
-
filtered_IDs = [i.id_ for i in filtered]
|
1713
|
+
elem_iters_IDs = specified_source.element_iters
|
1670
1714
|
|
1671
|
-
|
1672
|
-
|
1715
|
+
if specified_source.where:
|
1716
|
+
# filter iter IDs by user-specified rules, maintaining order:
|
1717
|
+
elem_iters = self.workflow.get_element_iterations_from_IDs(
|
1718
|
+
elem_iters_IDs
|
1719
|
+
)
|
1720
|
+
filtered = specified_source.where.filter(elem_iters)
|
1721
|
+
elem_iters_IDs = [i.id_ for i in filtered]
|
1673
1722
|
|
1674
|
-
|
1723
|
+
available_source.element_iters = elem_iters_IDs
|
1724
|
+
element_set.input_sources[path_i][s_idx] = available_source
|
1675
1725
|
|
1676
1726
|
# sorting ensures that root parameters come before sub-parameters, which is
|
1677
1727
|
# necessary when considering if we want to include a sub-parameter, when setting
|
@@ -1729,23 +1779,98 @@ class WorkflowTask:
|
|
1729
1779
|
if not has_root_param:
|
1730
1780
|
set_root_params.append(input_type)
|
1731
1781
|
|
1782
|
+
# for task sources that span multiple element sets, pad out sub-parameter
|
1783
|
+
# `element_iters` to include the element iterations from other element sets in
|
1784
|
+
# which the "root" parameter is defined:
|
1785
|
+
sources_by_task = defaultdict(dict)
|
1786
|
+
elem_iter_by_task = defaultdict(dict)
|
1787
|
+
all_elem_iters = set()
|
1788
|
+
for inp_type, sources in element_set.input_sources.items():
|
1789
|
+
source = sources[0]
|
1790
|
+
if source.source_type is InputSourceType.TASK:
|
1791
|
+
sources_by_task[source.task_ref][inp_type] = source
|
1792
|
+
all_elem_iters.update(source.element_iters)
|
1793
|
+
elem_iter_by_task[source.task_ref][inp_type] = source.element_iters
|
1794
|
+
|
1795
|
+
all_elem_iter_objs = self.workflow.get_element_iterations_from_IDs(all_elem_iters)
|
1796
|
+
all_elem_iters_by_ID = {i.id_: i for i in all_elem_iter_objs}
|
1797
|
+
|
1798
|
+
# element set indices:
|
1799
|
+
padded_elem_iters = defaultdict(list)
|
1800
|
+
es_idx_by_task = defaultdict(dict)
|
1801
|
+
for task_ref, task_iters in elem_iter_by_task.items():
|
1802
|
+
for inp_type, inp_iters in task_iters.items():
|
1803
|
+
es_indices = [
|
1804
|
+
all_elem_iters_by_ID[i].element.element_set_idx for i in inp_iters
|
1805
|
+
]
|
1806
|
+
es_idx_by_task[task_ref][inp_type] = (es_indices, set(es_indices))
|
1807
|
+
root_params = {k for k in task_iters if "." not in k}
|
1808
|
+
root_param_nesting = {
|
1809
|
+
k: element_set.nesting_order.get(f"inputs.{k}", None) for k in root_params
|
1810
|
+
}
|
1811
|
+
for root_param_i in root_params:
|
1812
|
+
sub_params = {
|
1813
|
+
k
|
1814
|
+
for k in task_iters
|
1815
|
+
if k.split(".")[0] == root_param_i and k != root_param_i
|
1816
|
+
}
|
1817
|
+
rp_elem_sets = es_idx_by_task[task_ref][root_param_i][0]
|
1818
|
+
rp_elem_sets_uniq = es_idx_by_task[task_ref][root_param_i][1]
|
1819
|
+
|
1820
|
+
for sub_param_j in sub_params:
|
1821
|
+
sub_param_nesting = element_set.nesting_order.get(
|
1822
|
+
f"inputs.{sub_param_j}", None
|
1823
|
+
)
|
1824
|
+
if sub_param_nesting == root_param_nesting[root_param_i]:
|
1825
|
+
|
1826
|
+
sp_elem_sets_uniq = es_idx_by_task[task_ref][sub_param_j][1]
|
1827
|
+
|
1828
|
+
if sp_elem_sets_uniq != rp_elem_sets_uniq:
|
1829
|
+
|
1830
|
+
# replace elem_iters in sub-param sequence with those from the
|
1831
|
+
# root parameter, but re-order the elem iters to match their
|
1832
|
+
# original order:
|
1833
|
+
iters_copy = elem_iter_by_task[task_ref][root_param_i][:]
|
1834
|
+
|
1835
|
+
# "mask" iter IDs corresponding to the sub-parameter's element
|
1836
|
+
# sets, and keep track of the extra indices so they can be
|
1837
|
+
# ignored later:
|
1838
|
+
sp_iters_new = []
|
1839
|
+
for idx, (i, j) in enumerate(zip(iters_copy, rp_elem_sets)):
|
1840
|
+
if j in sp_elem_sets_uniq:
|
1841
|
+
sp_iters_new.append(None)
|
1842
|
+
else:
|
1843
|
+
sp_iters_new.append(i)
|
1844
|
+
padded_elem_iters[sub_param_j].append(idx)
|
1845
|
+
|
1846
|
+
# fill in sub-param elem_iters in their specified order
|
1847
|
+
sub_iters_it = iter(elem_iter_by_task[task_ref][sub_param_j])
|
1848
|
+
sp_iters_new = [
|
1849
|
+
i if i is not None else next(sub_iters_it)
|
1850
|
+
for i in sp_iters_new
|
1851
|
+
]
|
1852
|
+
|
1853
|
+
# update sub-parameter element iters:
|
1854
|
+
for src_idx, src in enumerate(
|
1855
|
+
element_set.input_sources[sub_param_j]
|
1856
|
+
):
|
1857
|
+
if src.source_type is InputSourceType.TASK:
|
1858
|
+
element_set.input_sources[sub_param_j][
|
1859
|
+
src_idx
|
1860
|
+
].element_iters = sp_iters_new
|
1861
|
+
# assumes only a single task-type source for this
|
1862
|
+
# parameter
|
1863
|
+
break
|
1864
|
+
|
1732
1865
|
# TODO: collate all input sources separately, then can fall back to a different
|
1733
1866
|
# input source (if it was not specified manually) and if the "top" input source
|
1734
1867
|
# results in no available elements due to `allow_non_coincident_task_sources`.
|
1735
1868
|
|
1736
1869
|
if not element_set.allow_non_coincident_task_sources:
|
1737
|
-
sources_by_task = {}
|
1738
|
-
for inp_type, sources in element_set.input_sources.items():
|
1739
|
-
source = sources[0]
|
1740
|
-
if source.source_type is InputSourceType.TASK:
|
1741
|
-
if source.task_ref not in sources_by_task:
|
1742
|
-
sources_by_task[source.task_ref] = {}
|
1743
|
-
sources_by_task[source.task_ref][inp_type] = source
|
1744
|
-
|
1745
1870
|
# if multiple parameters are sourced from the same upstream task, only use
|
1746
1871
|
# element iterations for which all parameters are available (the set
|
1747
1872
|
# intersection):
|
1748
|
-
for sources in sources_by_task.
|
1873
|
+
for task_ref, sources in sources_by_task.items():
|
1749
1874
|
# if a parameter has multiple labels, disregard from this by removing all
|
1750
1875
|
# parameters:
|
1751
1876
|
seen_labelled = {}
|
@@ -1773,12 +1898,24 @@ class WorkflowTask:
|
|
1773
1898
|
intersect_task_i = set(first_src.element_iters)
|
1774
1899
|
for src_i in sources.values():
|
1775
1900
|
intersect_task_i.intersection_update(src_i.element_iters)
|
1901
|
+
if not intersect_task_i:
|
1902
|
+
raise NoCoincidentInputSources(
|
1903
|
+
f"Task {self.name!r}: input sources from task {task_ref!r} have "
|
1904
|
+
f"no coincident applicable element iterations. Consider setting "
|
1905
|
+
f"the element set (or task) argument "
|
1906
|
+
f"`allow_non_coincident_task_sources` to `True`, which will "
|
1907
|
+
f"allow for input sources from the same task to use different "
|
1908
|
+
f"(non-coinciding) subsets of element iterations from the "
|
1909
|
+
f"source task."
|
1910
|
+
)
|
1776
1911
|
|
1777
|
-
# now change elements for the affected input sources
|
1912
|
+
# now change elements for the affected input sources.
|
1913
|
+
# sort by original order of first_src.element_iters
|
1914
|
+
int_task_i_lst = [
|
1915
|
+
i for i in first_src.element_iters if i in intersect_task_i
|
1916
|
+
]
|
1778
1917
|
for inp_type in sources.keys():
|
1779
|
-
element_set.input_sources[inp_type][0].element_iters =
|
1780
|
-
intersect_task_i
|
1781
|
-
)
|
1918
|
+
element_set.input_sources[inp_type][0].element_iters = int_task_i_lst
|
1782
1919
|
|
1783
1920
|
if missing:
|
1784
1921
|
missing_str = ", ".join(f"{i!r}" for i in missing)
|
@@ -1787,6 +1924,8 @@ class WorkflowTask:
|
|
1787
1924
|
missing_inputs=missing,
|
1788
1925
|
)
|
1789
1926
|
|
1927
|
+
return padded_elem_iters
|
1928
|
+
|
1790
1929
|
def generate_new_elements(
|
1791
1930
|
self,
|
1792
1931
|
input_data_indices,
|
@@ -1799,7 +1938,11 @@ class WorkflowTask:
|
|
1799
1938
|
element_sequence_indices = {}
|
1800
1939
|
element_src_indices = {}
|
1801
1940
|
for i_idx, i in enumerate(element_data_indices):
|
1802
|
-
elem_i = {
|
1941
|
+
elem_i = {
|
1942
|
+
k: input_data_indices[k][v]
|
1943
|
+
for k, v in i.items()
|
1944
|
+
if input_data_indices[k][v] != -1
|
1945
|
+
}
|
1803
1946
|
elem_i.update({k: v[i_idx] for k, v in output_data_indices.items()})
|
1804
1947
|
new_elements.append(elem_i)
|
1805
1948
|
|
@@ -1815,7 +1958,11 @@ class WorkflowTask:
|
|
1815
1958
|
if k in source_indices:
|
1816
1959
|
if k not in element_src_indices:
|
1817
1960
|
element_src_indices[k] = []
|
1818
|
-
|
1961
|
+
if input_data_indices[k][v] != -1:
|
1962
|
+
src_idx_k = source_indices[k][v]
|
1963
|
+
else:
|
1964
|
+
src_idx_k = -1
|
1965
|
+
element_src_indices[k].append(src_idx_k)
|
1819
1966
|
|
1820
1967
|
return new_elements, element_sequence_indices, element_src_indices
|
1821
1968
|
|
@@ -2003,13 +2150,14 @@ class WorkflowTask:
|
|
2003
2150
|
|
2004
2151
|
self.template.set_sequence_parameters(element_set)
|
2005
2152
|
|
2006
|
-
|
2153
|
+
# may modify element_set.input_sources:
|
2154
|
+
padded_elem_iters = self.ensure_input_sources(element_set)
|
2007
2155
|
|
2008
2156
|
(input_data_idx, seq_idx, src_idx) = self._make_new_elements_persistent(
|
2009
2157
|
element_set=element_set,
|
2010
2158
|
element_set_idx=self.num_element_sets,
|
2159
|
+
padded_elem_iters=padded_elem_iters,
|
2011
2160
|
)
|
2012
|
-
|
2013
2161
|
element_set.task_template = self.template # may modify element_set.nesting_order
|
2014
2162
|
|
2015
2163
|
multiplicities = self.template.prepare_element_resolution(
|
@@ -2047,7 +2195,7 @@ class WorkflowTask:
|
|
2047
2195
|
task_ID=self.insert_ID,
|
2048
2196
|
es_idx=self.num_element_sets - 1,
|
2049
2197
|
seq_idx={k: v[elem_idx] for k, v in element_seq_idx.items()},
|
2050
|
-
src_idx={k: v[elem_idx] for k, v in element_src_idx.items()},
|
2198
|
+
src_idx={k: v[elem_idx] for k, v in element_src_idx.items() if v != -1},
|
2051
2199
|
)
|
2052
2200
|
iter_ID_i = self.workflow._store.add_element_iteration(
|
2053
2201
|
element_ID=elem_ID_i,
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import pytest
|
2
2
|
from hpcflow.app import app as hf
|
3
|
+
from hpcflow.sdk.core.errors import MalformedNestingOrderPath
|
3
4
|
|
4
5
|
|
5
6
|
@pytest.fixture
|
@@ -101,3 +102,12 @@ def test_merge_envs_no_envs_with_resource_envs(null_config):
|
|
101
102
|
def test_raise_env_and_envs_specified(null_config):
|
102
103
|
with pytest.raises(ValueError):
|
103
104
|
hf.ElementSet(env_preset="my_preset", environments={"my_env": {"version": 1}})
|
105
|
+
|
106
|
+
|
107
|
+
def test_nesting_order_paths_raise(null_config):
|
108
|
+
with pytest.raises(MalformedNestingOrderPath):
|
109
|
+
hf.ElementSet(nesting_order={"bad_path.p1": 1})
|
110
|
+
|
111
|
+
|
112
|
+
def test_nesting_order_paths_no_raise(null_config):
|
113
|
+
hf.ElementSet(nesting_order={"inputs.p1": 1, "resources.any": 2, "repeats": 3})
|