aiida-pythonjob 0.1.7__tar.gz → 0.2.0__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.
Files changed (53) hide show
  1. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/.gitignore +2 -0
  2. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/PKG-INFO +3 -2
  3. aiida_pythonjob-0.2.0/docs/gallery/autogen/pyfunction.py +191 -0
  4. aiida_pythonjob-0.1.7/docs/gallery/autogen/how_to.py → aiida_pythonjob-0.2.0/docs/gallery/autogen/pythonjob.py +33 -8
  5. aiida_pythonjob-0.2.0/docs/source/index.rst +52 -0
  6. aiida_pythonjob-0.2.0/docs/source/tutorial/dft.ipynb +1291 -0
  7. aiida_pythonjob-0.2.0/docs/source/tutorial/html/atomization_energy.html +290 -0
  8. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/docs/source/tutorial/html/pythonjob_eos_emt.html +10 -10
  9. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/pyproject.toml +3 -1
  10. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/src/aiida_pythonjob/__init__.py +3 -1
  11. aiida_pythonjob-0.2.0/src/aiida_pythonjob/calculations/pyfunction.py +268 -0
  12. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/src/aiida_pythonjob/data/serializer.py +13 -10
  13. aiida_pythonjob-0.2.0/src/aiida_pythonjob/decorator.py +134 -0
  14. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/src/aiida_pythonjob/launch.py +70 -1
  15. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/src/aiida_pythonjob/utils.py +28 -19
  16. aiida_pythonjob-0.2.0/tests/test_pyfunction.py +87 -0
  17. aiida_pythonjob-0.2.0/tests/test_utils.py +46 -0
  18. aiida_pythonjob-0.1.7/docs/source/index.rst +0 -26
  19. aiida_pythonjob-0.1.7/docs/source/tutorial/dft.ipynb +0 -1128
  20. aiida_pythonjob-0.1.7/docs/source/tutorial/html/atomization_energy.html +0 -290
  21. aiida_pythonjob-0.1.7/tests/test_utils.py +0 -17
  22. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/.github/workflows/ci.yml +0 -0
  23. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/.github/workflows/python-publish.yml +0 -0
  24. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/.pre-commit-config.yaml +0 -0
  25. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/.readthedocs.yml +0 -0
  26. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/LICENSE +0 -0
  27. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/README.md +0 -0
  28. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/docs/Makefile +0 -0
  29. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/docs/environment.yml +0 -0
  30. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/docs/gallery/autogen/GALLERY_HEADER.rst +0 -0
  31. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/docs/make.bat +0 -0
  32. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/docs/requirements.txt +0 -0
  33. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/docs/source/conf.py +0 -0
  34. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/docs/source/installation.rst +0 -0
  35. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/docs/source/tutorial/index.rst +0 -0
  36. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/examples/test_add.py +0 -0
  37. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/src/aiida_pythonjob/calculations/__init__.py +0 -0
  38. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/src/aiida_pythonjob/calculations/pythonjob.py +0 -0
  39. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/src/aiida_pythonjob/calculations/utils.py +0 -0
  40. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/src/aiida_pythonjob/config.py +0 -0
  41. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/src/aiida_pythonjob/data/__init__.py +0 -0
  42. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/src/aiida_pythonjob/data/atoms.py +0 -0
  43. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/src/aiida_pythonjob/data/deserializer.py +0 -0
  44. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/src/aiida_pythonjob/data/pickled_data.py +0 -0
  45. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/src/aiida_pythonjob/parsers/__init__.py +0 -0
  46. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/src/aiida_pythonjob/parsers/pythonjob.py +0 -0
  47. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/tests/conftest.py +0 -0
  48. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/tests/inputs_folder/another_input.txt +0 -0
  49. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/tests/test_create_env.py +0 -0
  50. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/tests/test_data.py +0 -0
  51. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/tests/test_entry_points.py +0 -0
  52. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/tests/test_parser.py +0 -0
  53. {aiida_pythonjob-0.1.7 → aiida_pythonjob-0.2.0}/tests/test_pythonjob.py +0 -0
@@ -70,6 +70,8 @@ instance/
70
70
 
71
71
  # Sphinx documentation
72
72
  docs/_build/
73
+ docs/source/autogen/
74
+ docs/source/sg_execution_times.rst
73
75
 
74
76
  # PyBuilder
75
77
  .pybuilder/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiida-pythonjob
3
- Version: 0.1.7
3
+ Version: 0.2.0
4
4
  Summary: Run Python functions on a remote computer.
5
5
  Project-URL: Source, https://github.com/aiidateam/aiida-pythonjob
6
6
  Author-email: Xing Wang <xingwang1991@gmail.com>
@@ -37,7 +37,8 @@ Requires-Python: >=3.9
37
37
  Requires-Dist: aiida-core<3,>=2.3
38
38
  Requires-Dist: ase
39
39
  Requires-Dist: cloudpickle
40
- Requires-Dist: voluptuous
40
+ Provides-Extra: dev
41
+ Requires-Dist: hatch; extra == 'dev'
41
42
  Provides-Extra: docs
42
43
  Requires-Dist: furo; extra == 'docs'
43
44
  Requires-Dist: markupsafe<2.1; extra == 'docs'
@@ -0,0 +1,191 @@
1
+ """
2
+ PyFunction
3
+ ===============
4
+
5
+ """
6
+
7
+ ######################################################################
8
+ # Default outputs
9
+ # --------------
10
+ #
11
+ # The default output of the function is `result`. The `pyfunction` task
12
+ # will store the result as one node in the database with the key `result`.
13
+ #
14
+ from aiida import load_profile
15
+ from aiida.engine import run_get_node
16
+ from aiida_pythonjob import pyfunction
17
+
18
+ load_profile()
19
+
20
+
21
+ @pyfunction()
22
+ def add(x, y):
23
+ return x + y
24
+
25
+
26
+ result, node = run_get_node(add, x=1, y=2)
27
+ print("result: ", result)
28
+
29
+ ######################################################################
30
+ # Custom outputs
31
+ # --------------
32
+ # If the function return a dictionary with fixed number of keys, and you
33
+ # want to store the values as separate outputs, you can specify the `function_outputs` parameter.
34
+ # For a dynamic number of outputs, you can use the namespace output, which is explained later.
35
+ #
36
+
37
+
38
+ @pyfunction(outputs=[{"name": "sum"}, {"name": "diff"}])
39
+ def add(x, y):
40
+ return {"sum": x + y, "diff": x - y}
41
+
42
+
43
+ result, node = run_get_node(add, x=1, y=2)
44
+
45
+ print("result: ")
46
+ print("sum: ", result["sum"])
47
+ print("diff: ", result["diff"])
48
+
49
+ ######################################################################
50
+ # Namespace Output
51
+ # --------------
52
+ #
53
+ # The `pyfunction` allows users to define namespace outputs. A namespace output
54
+ # is a dictionary with keys and values returned by a function. Each value in
55
+ # this dictionary will be serialized to AiiDA data, and the key-value pair
56
+ # will be stored in the database.
57
+ #
58
+ # Why Use Namespace Outputs?
59
+ #
60
+ # - **Dynamic and Flexible**: The keys and values in the namespace output are not fixed and can change based on the task's execution. # noqa
61
+ # - **Querying**: The data in the namespace output is stored as an AiiDA data node, allowing for easy querying and retrieval. # noqa
62
+ # - **Data Provenance**: When the data is used as input for subsequent tasks, the origin of data is tracked.
63
+ #
64
+ # For example: Consider a molecule adsorption calculation where the namespace
65
+ # output stores the surface slabs of the molecule adsorbed on different surface
66
+ # sites. The number of surface slabs can vary depending on the surface. These
67
+ # output surface slabs can be utilized as input to the next task to calculate the energy.
68
+
69
+ from ase import Atoms # noqa: E402
70
+ from ase.build import bulk # noqa: E402
71
+
72
+
73
+ @pyfunction(outputs=[{"name": "scaled_structures", "identifier": "namespace"}])
74
+ def generate_structures(structure: Atoms, factor_lst: list) -> dict:
75
+ """Scale the structure by the given factor_lst."""
76
+ scaled_structures = {}
77
+ for i in range(len(factor_lst)):
78
+ atoms = structure.copy()
79
+ atoms.set_cell(atoms.cell * factor_lst[i], scale_atoms=True)
80
+ scaled_structures[f"s_{i}"] = atoms
81
+ return {"scaled_structures": scaled_structures}
82
+
83
+
84
+ result, node = run_get_node(generate_structures, structure=bulk("Al"), factor_lst=[0.95, 1.0, 1.05])
85
+ print("scaled_structures: ")
86
+ for key, value in result["scaled_structures"].items():
87
+ print(key, value)
88
+
89
+
90
+ ######################################################################
91
+ # Exit Code
92
+ # --------------
93
+ # Users can define custom exit codes to indicate the status of the task.
94
+ #
95
+ # When the function returns a dictionary with an `exit_code` key, the system
96
+ # automatically parses and uses this code to indicate the task's status. In
97
+ # the case of an error, the non-zero `exit_code` value helps identify the specific problem.
98
+ #
99
+ #
100
+
101
+
102
+ @pyfunction()
103
+ def add(x, y):
104
+ sum = x + y
105
+ if sum < 0:
106
+ exit_code = {"status": 410, "message": "Some elements are negative"}
107
+ return {"sum": sum, "exit_code": exit_code}
108
+ return {"sum": sum}
109
+
110
+
111
+ result, node = run_get_node(add, x=1, y=-2)
112
+ print("exit_status:", node.exit_status)
113
+ print("exit_message:", node.exit_message)
114
+
115
+
116
+ ######################################################################
117
+ # Define your data serializer and deserializer
118
+ # --------------
119
+ #
120
+ # PythonJob search data serializer from the `aiida.data` entry point by the
121
+ # module name and class name (e.g., `ase.atoms.Atoms`).
122
+ #
123
+ # In order to let the PythonJob find the serializer, you must register the
124
+ # AiiDA data with the following format:
125
+ #
126
+ # .. code-block:: ini
127
+ #
128
+ # [project.entry-points."aiida.data"]
129
+ # abc.ase.atoms.Atoms = "abc.xyz:MyAtomsData"
130
+ #
131
+ # This will register a data serializer for `ase.atoms.Atoms` data. `abc` is
132
+ # the plugin name, the module name is `xyz`, and the AiiDA data class name is
133
+ # `AtomsData`. Learn how to create an AiiDA data class `here <https://aiida.readthedocs.io/projects/aiida-core/en/stable/topics/data_types.html#adding-support-for-custom-data-types>`_.
134
+ #
135
+ # *Avoid duplicate data serializer*: If you have multiple plugins that
136
+ # register the same data serializer, the PythonJob will raise an error.
137
+ # You can avoid this by selecting the plugin that you want to use in the configuration file.
138
+ #
139
+ #
140
+ # .. code-block:: json
141
+ #
142
+ # {
143
+ # "serializers": {
144
+ # "ase.atoms.Atoms": "abc.ase.atoms.AtomsData" # use the full path to the serializer
145
+ # }
146
+ # }
147
+ #
148
+ # Save the configuration file as `pythonjob.json` in the aiida configuration
149
+ # directory (by default, `~/.aiida` directory).
150
+ #
151
+ # If you want to pass AiiDA Data node as input, and the node does not have a `value` attribute,
152
+ # then one must provide a deserializer for it.
153
+ #
154
+
155
+ from aiida import orm # noqa: E402
156
+
157
+
158
+ @pyfunction()
159
+ def make_supercell(structure, n=2):
160
+ return structure * [n, n, n]
161
+
162
+
163
+ structure = orm.StructureData(cell=[[1, 0, 0], [0, 1, 0], [0, 0, 1]])
164
+ structure.append_atom(position=(0.0, 0.0, 0.0), symbols="Li")
165
+
166
+ result, node = run_get_node(
167
+ make_supercell,
168
+ structure=structure,
169
+ deserializers={
170
+ "aiida.orm.nodes.data.structure.StructureData": "aiida_pythonjob.data.deserializer.structure_data_to_atoms"
171
+ },
172
+ )
173
+ print("result: ", result)
174
+
175
+ ######################################################################
176
+ # One can also set the deserializer in the configuration file.
177
+ #
178
+ #
179
+ # .. code-block:: json
180
+ #
181
+ # {
182
+ # "serializers": {
183
+ # "ase.atoms.Atoms": "abc.ase.atoms.Atoms"
184
+ # },
185
+ # "deserializers": {
186
+ # "aiida.orm.nodes.data.structure.StructureData": "aiida_pythonjob.data.deserializer.structure_data_to_pymatgen" # noqa
187
+ # }
188
+ # }
189
+ #
190
+ # The `orm.List`, `orm.Dict`and `orm.StructureData` data types already have built-in deserializers.
191
+ #
@@ -1,5 +1,5 @@
1
1
  """
2
- How to guides
2
+ PythonJob
3
3
  ===============
4
4
 
5
5
  """
@@ -241,14 +241,12 @@ print("retrieved files: ", result["retrieved"].list_object_names())
241
241
  # is a dictionary with keys and values returned by a function. Each value in
242
242
  # this dictionary will be serialized to AiiDA data, and the key-value pair
243
243
  # will be stored in the database.
244
+ #
244
245
  # Why Use Namespace Outputs?
245
246
  #
246
- # - **Dynamic and Flexible**: The keys and values in the namespace output are
247
- # not fixed and can change based on the task's execution.
248
- # - **Querying**: The data in the namespace output is stored as an AiiDA data
249
- # node, allowing for easy querying and retrieval.
250
- # - **Data Provenance**: When the data is used as input for subsequent tasks,
251
- # the origin of data is tracked.
247
+ # - **Dynamic and Flexible**: The keys and values in the namespace output are not fixed and can change based on the task's execution. # noqa
248
+ # - **Querying**: The data in the namespace output is stored as an AiiDA data node, allowing for easy querying and retrieval. # noqa
249
+ # - **Data Provenance**: When the data is used as input for subsequent tasks, the origin of data is tracked.
252
250
  #
253
251
  # For example: Consider a molecule adsorption calculation where the namespace
254
252
  # output stores the surface slabs of the molecule adsorbed on different surface
@@ -332,7 +330,7 @@ for key, value in result["scaled_structures"].items():
332
330
 
333
331
  def add(x, y):
334
332
  sum = x + y
335
- if (sum < 0).any():
333
+ if sum < 0:
336
334
  exit_code = {"status": 410, "message": "Some elements are negative"}
337
335
  return {"sum": sum, "exit_code": exit_code}
338
336
  return {"sum": sum}
@@ -347,6 +345,33 @@ result, node = run_get_node(PythonJob, inputs=inputs)
347
345
  print("exit_status:", node.exit_status)
348
346
  print("exit_message:", node.exit_message)
349
347
 
348
+ ######################################################################
349
+ # Using `register_pickle_by_value`
350
+ # --------------------------------
351
+ #
352
+ # If the function is defined inside an external module that is **not installed** on
353
+ # the remote computer, this can cause import errors during execution.
354
+ #
355
+ # **Solution:**
356
+ # By enabling `register_pickle_by_value=True`, the function is serialized **by value**
357
+ # instead of being referenced by its module path. This embeds the function unpickled
358
+ # even if the original module is unavailable on the remote computer.
359
+ #
360
+ # **Example:**
361
+ #
362
+ # .. code-block:: python
363
+ #
364
+ # inputs = prepare_pythonjob_inputs(
365
+ # my_function,
366
+ # function_inputs={"x": 1, "y": 2},
367
+ # computer="localhost",
368
+ # register_pickle_by_value=True, # Ensures function is embedded
369
+ # )
370
+ #
371
+ # **Important Considerations:**: If the function **contains import statements**,
372
+ # the imported modules **must still be installed** on the remote computer.
373
+ #
374
+
350
375
 
351
376
  ######################################################################
352
377
  # Define your data serializer and deserializer
@@ -0,0 +1,52 @@
1
+ AiiDA PythonJob & PyFunction
2
+ ============================
3
+
4
+ `PythonJob` and `pyfunction` enable users to run Python functions—either locally or remotely—with automatic serialization, provenance tracking, and workflow integration.
5
+
6
+ These tools are designed to simplify the experience for users not familiar with AiiDA's internal types, allowing them to write pure Python functions while still benefiting from AiiDA's powerful infrastructure.
7
+
8
+ **Key Features**
9
+
10
+ 1. **Remote Execution with PythonJob**
11
+ Seamlessly run Python functions on a remote computer via `PythonJob`. A working directory is automatically created for the job, allowing full support for:
12
+
13
+ - File upload and retrieval
14
+ - Parent/remote folders
15
+ - Integration with external codes (e.g. ASE + DFT engines)
16
+
17
+ 2. **Local Execution with PyFunction**
18
+ Use `@pyfunction` to run pure Python functions locally. All inputs and outputs are automatically serialized/deserialized, enabling users to work with native Python types.
19
+ This decorator is ideal for functions that are:
20
+
21
+ .. note::
22
+ `pyfunction` does **not** create a dedicated working directory. It executes in the current local Python environment.
23
+ File I/O or relative path access should be avoided unless explicitly handled.
24
+
25
+ 3. **User-Friendly Interface**
26
+ Designed for users unfamiliar with AiiDA’s internal `Data` classes. The decorators handle all conversion and provenance tracking behind the scenes.
27
+
28
+ 4. **Workflow Management with Checkpoints**
29
+ Combine `pyfunction` and `pythonjob` in `WorkGraph` workflows to build robust, restartable workflows with full checkpoint support.
30
+
31
+ 5. **Full Data Provenance**
32
+ All inputs, outputs, and intermediate results are stored in the AiiDA provenance graph, ensuring reproducibility and traceability.
33
+
34
+ **When to Use What**
35
+
36
+ +----------------+--------------------+---------------------+------------------+---------------------------------------------+
37
+ | Decorator | Execution | Input/Output Types | AiiDA Provenance | Recommended Use Case |
38
+ +================+====================+=====================+==================+=============================================+
39
+ | ``@pyfunction``| Local | Python native | ✅ Yes | Pure functions |
40
+ +----------------+--------------------+---------------------+------------------+---------------------------------------------+
41
+ | ``@pythonjob`` | Remote | Python native | ✅ Yes | Remote jobs with file handling |
42
+ +----------------+--------------------+---------------------+------------------+---------------------------------------------+
43
+
44
+ .. toctree::
45
+ :maxdepth: 1
46
+ :caption: Contents:
47
+ :hidden:
48
+
49
+ installation
50
+ autogen/pythonjob
51
+ autogen/pyfunction
52
+ tutorial/index