Skip to content

Commit 7eb104b

Browse files
committed
Restructure the tutorial for creating a custom operator
1 parent 0f9d207 commit 7eb104b

File tree

3 files changed

+116
-71
lines changed

3 files changed

+116
-71
lines changed

doc/source/user_guide/tutorials/custom_operators_and_plugins/custom_operator_example.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from ansys.dpf import core as dpf
22
from ansys.dpf.core.changelog import Changelog
3-
from ansys.dpf.core.custom_operator import CustomOperatorBase, record_operator # noqa: F401
3+
from ansys.dpf.core.custom_operator import CustomOperatorBase
44
from ansys.dpf.core.operator_specification import CustomSpecification, SpecificationProperties, \
55
PinSpecification
66

@@ -28,12 +28,12 @@ def specification(self) -> CustomSpecification:
2828
spec.description = "What the Operator does. You can use MarkDown and LaTeX in descriptions."
2929
# Define the inputs of the operator if any
3030
spec.inputs = {
31-
0: PinSpecification(name="name_of_input_0", type_names=[dpf.Field, dpf.FieldsContainer],
31+
0: PinSpecification(name="input_0", type_names=[dpf.Field, dpf.FieldsContainer],
3232
document="Describe input pin 0."),
3333
}
3434
# Define the outputs of the operator if any
3535
spec.outputs = {
36-
0: PinSpecification(name="name_of_output_0", type_names=[dpf.Field], document="Describe output pin 0."),
36+
0: PinSpecification(name="output_0", type_names=[dpf.Field], document="Describe output pin 0."),
3737
}
3838
# Define the properties of the operator if any
3939
spec.properties = SpecificationProperties(
@@ -73,7 +73,7 @@ def run(self):
7373
# # If the input is optional, set its default value
7474
# # If the input is not optional and empty, raise an error
7575
if field is None:
76-
raise ValueError("my_custom_operator: mandatory input name_of_input_0 is empty or of an unsupported type.")
76+
raise ValueError("my_custom_operator: mandatory input 'input_0' is empty or of an unsupported type.")
7777

7878
# Perform some operations on the data
7979
field.name = "new_field_name"
@@ -83,3 +83,8 @@ def run(self):
8383

8484
# And declare the operator run a success
8585
self.set_succeeded()
86+
87+
88+
def load_operators(*args):
89+
from ansys.dpf.core.custom_operator import record_operator
90+
record_operator(operator_type=CustomOperator, *args)

doc/source/user_guide/tutorials/custom_operators_and_plugins/custom_operators.rst

Lines changed: 105 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -4,115 +4,153 @@
44
Custom operators
55
================
66

7-
In Ansys 2023 R1 and later, you can create custom operators in CPython. Creating custom operators
8-
consists of wrapping Python routines in a DPF-compliant way so that you can access them in the same way
9-
as you access the native operators in the :class:`Operator <ansys.dpf.core.dpf_operator.Operator>` class in
10-
PyDPF-Core or in any supported client API.
7+
This tutorial shows the basics of creating a custom operator in Python and loading it ont a server for use.
118

12-
With support for custom operators, PyDPF-Core becomes a development tool offering:
9+
.. note:
10+
You can create custom operators in CPython using PyDPF-Core for use with DPF in Ansys 2023 R1 and later.
1311
14-
- **Accessibility:** A simple script can define a basic operator plugin.
12+
It first presents how to :ref:`create a custom DPF operator<tutorials_custom_operators_and_plugins_custom_operator_create_custom_operator>`
13+
in Python using PyDPF-Core.
1514

16-
- **Componentization:** Operators with similar applications can be grouped in Python plug-in packages.
15+
It then shows how to :ref:`make a plugin<tutorials_custom_operators_and_plugins_custom_operator_create_custom_plugin>`
16+
out of this single operator.
1717

18-
- **Easy distribution:** Standard Python tools can be used to package, upload, and download custom operators.
18+
The next step is to :ref:`load the plugin on the server<tutorials_custom_operators_and_plugins_custom_operator_load_the_plugin>` to record its operators.
1919

20-
- **Dependency management:** Third-party Python modules can be added to the Python package.
20+
The final step is to instantiate the custom operator from the client API and :ref:`use it<tutorials_custom_operators_and_plugins_custom_operator_use_the_custom_operator>`.
2121

22-
- **Reusability:** A documented and packaged operator can be reused in an infinite number of workflows.
22+
.. note:
23+
In this tutorial the DPF client API used is PyDPF-Core but, once recorded on the server,
24+
you can call the operators of the plugin using any of the DPF client APIs
25+
(C++, CPython, IronPython), as you would any other operator.
2326
24-
- **Remotable and parallel computing:** Native DPF capabilities are inherited by custom operators.
2527
26-
The only prerequisite for creating custom operators is to be familiar with native operators.
27-
For more information, see :ref:`ref_user_guide_operators`.
28+
:jupyter-download-script:`Download tutorial as Python script<custom_operators>`
29+
:jupyter-download-notebook:`Download tutorial as Jupyter notebook<custom_operators>`
2830

29-
Install module
30-
--------------
3131

32-
Once an Ansys-unified installation is complete, you must install the ``ansys-dpf-core`` module in the Ansys
33-
installer's Python interpreter.
32+
.. _tutorials_custom_operators_and_plugins_custom_operator_create_custom_operator:
3433

35-
#. Download the script for you operating system:
34+
Create a custom operator
35+
------------------------
3636

37-
- For Windows, download this :download:`PowerShell script </user_guide/tutorials/enriching_dpf_capabilities/install_ansys_dpf_core_in_ansys.ps1>`.
38-
- For Linux, download this :download:`Shell script </user_guide/tutorials/enriching_dpf_capabilities/install_ansys_dpf_core_in_ansys.sh>`
37+
To create a custom DPF operator using PyDPF-Core, define a custom operator class inheriting from
38+
the :class:`CustomOperatorBase <ansys.dpf.core.custom_operator.CustomOperatorBase>` class in a dedicated Python file.
3939

40-
#. Run the downloaded script for installing with optional arguments:
40+
The following are sections of a file named `custom_operator_example.py`.
4141

42-
- ``-awp_root``: Path to the Ansys root installation folder. For example, the 2023 R1 installation folder ends
43-
with ``Ansys Inc/v231``, and the default environment variable is ``AWP_ROOT231``.
44-
- ``-pip_args``: Optional arguments to add to the ``pip`` command. For example, ``--extra-index-url`` or
45-
``--trusted-host``.
42+
First, create the custom operator class, with necessary imports and a first property to define the operator scripting name:
4643

47-
If you ever want to uninstall the ``ansys-dpf-core`` module from the Ansys installation, you can do so.
44+
.. literalinclude:: custom_operator_example.py
45+
:end-at: return "my_custom_operator"
46+
47+
Next, set the `specification` property of your operator with:
48+
- a description of what the operator does
49+
- a dictionary for each input and output pin. This dictionary includes the name, a list of supported types, a description,
50+
and whether it is optional and/or ellipsis (meaning that the specification is valid for pins going from pin
51+
number *x* to infinity)
52+
- a list for operator properties, including name to use in the documentation and code generation and the
53+
operator category. The optional ``license`` property lets you define a required license to check out
54+
when running the operator. Set it equal to ``any_dpf_supported_increments`` to allow any license
55+
currently accepted by DPF (see :ref:`here<target_to_ansys_license_increments_list>`)
56+
57+
.. literalinclude:: custom_operator_example.py
58+
:start-after: return "my_custom_operator"
59+
:end-at: return spec
4860

49-
#. Download the script for your operating system:
61+
Next, implement the operator behavior in its `run` method:
62+
63+
.. literalinclude:: custom_operator_example.py
64+
:start-after: return spec
65+
:end-at: self.set_succeeded()
5066

51-
- For Windows, download this :download:`PowerShell script </user_guide/tutorials/enriching_dpf_capabilities/uninstall_ansys_dpf_core_in_ansys.ps1>`.
52-
- For Linux, download this :download:`Shell script </user_guide/tutorials/enriching_dpf_capabilities/uninstall_ansys_dpf_core_in_ansys.sh>`.
53-
54-
#. Run the downloaded script for uninstalling with the optional argument:
67+
The `CustomOperator` class is now ready for packaging into any DPF Python plugin.
5568

56-
- ``-awp_root``: Path to the Ansys root installation folder. For example, the 2023 R1 installation folder ends
57-
with ``Ansys Inc/v231``, and the default environment variable is ``AWP_ROOT231``.
69+
.. _tutorials_custom_operators_and_plugins_custom_operator_create_custom_plugin:
5870

71+
Package as a plugin
72+
-------------------
5973

60-
Create operators
61-
----------------
74+
You must package your custom operator as a `plugin`,
75+
which is what you can later load onto a running DPF server,
76+
or configure your installation to automatically load when starting a DPF server.
6277

63-
Creating a basic operator plugin consists of writing a single Python script. An operator implementation
64-
derives from the :class:`CustomOperatorBase <ansys.dpf.core.custom_operator.CustomOperatorBase>` class and a call to
65-
the :func:`record_operator() <ansys.dpf.core.custom_operator.record_operator>` method.
78+
A DPF plugin contains Python modules with declarations of custom Python operators such as seen above.
79+
However, it also has to define an entry-point for the DPF server to call,
80+
which records the operators of the plugin into the server registry of available operators.
6681

67-
This example script shows how you create a basic operator plugin:
82+
This is done by defining a function (DPF looks for a function named ``load_operators`` by default)
83+
somewhere in the plugin with signature ``*args`` and a call to the
84+
:func:`record_operator() <ansys.dpf.core.custom_operator.record_operator>` method for each custom operator.
85+
86+
In this tutorial, the plugin is made of a single operator, in a single Python file.
87+
You can transform this single Python file into a DPF Python plugin very easily by adding
88+
``load_operators(*args)`` function with a call to the
89+
:func:`record_operator() <ansys.dpf.core.custom_operator.record_operator>` method at the end of the file.
6890

6991
.. literalinclude:: custom_operator_example.py
92+
:start-at: def load_operators(*args):
7093
94+
PS: You can declare several custom operator classes in the same file, with as many calls to
95+
:func:`record_operator() <ansys.dpf.core.custom_operator.record_operator>` as necessary.
7196

72-
.. code-block::
97+
.. _tutorials_custom_operators_and_plugins_custom_operator_load_the_plugin:
7398

74-
def load_operators(*args):
75-
record_operator(CustomOperator, *args)
99+
Load the plugin
100+
---------------
76101

102+
First, start a server in gRPC mode, which is the only server type supported for custom Python plugins.
77103

78-
In the various properties for the class, you specify the following:
104+
.. jupyter-execute::
79105

80-
- Name for the custom operator
81-
- Description of what the operator does
82-
- Dictionary for each input and output pin, which includes the name, a list of supported types, a description,
83-
and whether it is optional and/or ellipsis (meaning that the specification is valid for pins going from pin
84-
number *x* to infinity)
85-
- List for operator properties, including name to use in the documentation and code generation and the
86-
operator category. The optional ``license`` property allows to define a required license to check out
87-
when running the operator. Set it equal to ``any_dpf_supported_increments`` to allow any license
88-
currently accepted by DPF (see :ref:`here<target_to_ansys_license_increments_list>`)
106+
import ansys.dpf.core as dpf
89107

90-
For specific examples on writing operator plugins, see :ref:`python_operators`.
108+
# Python plugins are not supported in process.
109+
server = dpf.start_local_server(config=dpf.AvailableServerConfigs.GrpcServer)
91110

92-
Load the plug-in
93-
----------------
94111

95-
Once a custom operator is created, you can use the :func:`load_library() <ansys.dpf.core.core.load_library>` method to load it.
112+
With the server and custom plugin ready, use the :func:`load_library() <ansys.dpf.core.core.load_library>` method in a PyDPF-Core script to load it.
96113

97114
- The first argument is the path to the directory with the plugin.
98-
- The second argument is ``py_<plugin>``, where <plugin> is the name identifying the plug-in (same name of the Python file).
99-
- The third argument is the function name for recording operators.
115+
- The second argument is ``py_<plugin>``, where <plugin> is the name identifying the plugin (the name of the Python file for a single file).
116+
- The third argument is the name of the function in the plugin which records operators (``load_operators`` by default).
100117

101-
.. code::
118+
.. jupyter-execute::
102119

103120
dpf.load_library(
104-
r"path/to/plugins",
105-
"py_custom_plugin", #if the load_operators function is defined in path/to/plugins/custom_plugin.py
106-
"load_operators")
121+
filename=r".", # Look into the current directory
122+
name="py_custom_operator_example", # Look for a Python file named 'custom_operator_example.py'
123+
symbol="load_operators", # Look for the entry-point where operators are recorded
124+
server=server, # Load the plugin on the server previously started
125+
generate_operators=False, # Do not generate the Python module for this operator
126+
)
127+
128+
.. _tutorials_custom_operators_and_plugins_custom_operator_use_the_custom_operator:
129+
130+
Use the custom operator
131+
-----------------------
132+
133+
Once the plugin is loaded, you can instantiate the custom operator based on its name.
134+
135+
.. jupyter-execute::
107136

108-
Use custom operators
109-
--------------------
137+
my_custom_op = dpf.Operator(name="my_custom_operator") # as returned by the ``name`` property
138+
print(my_custom_op)
110139

111-
Once the plugin is loaded, you can instantiate the custom operator:
140+
Finally, run it as any other operator.
112141

113-
.. code::
142+
.. jupyter-execute::
114143

115-
new_operator = dpf.Operator("custom_operator") # if "custom_operator" is what is returned by the ``name`` property
144+
# Create a bogus field to use as input
145+
in_field = dpf.Field()
146+
# Give it a name
147+
in_field.name = "initial name"
148+
print(in_field)
149+
# Set it as input of the operator
150+
my_custom_op.inputs.input_0.connect(in_field)
151+
# Run the operator by requesting its output
152+
out_field = my_custom_op.outputs.output_0()
153+
print(out_field)
116154

117155
References
118156
----------

doc/source/user_guide/tutorials/custom_operators_and_plugins/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ For more information, see :ref:`ref_user_guide_operators`.
2828
2929
The following tutorials demonstrate how to develop such plugins using PyDPF-Core (CPython based) and how to use them.
3030

31+
For comprehensive examples on writing operator plugins, see :ref:`python_operators`.
32+
3133
.. grid:: 1 1 3 3
3234
:gutter: 2
3335
:padding: 2

0 commit comments

Comments
 (0)