Custom operators#
In Ansys 2023 R1 and later, you can create custom operators in CPython. Creating custom operators
consists of wrapping Python routines in a DPF-compliant way so that you can access them in the same way
as you access the native operators in the ansys.dpf.core.dpf_operator.Operator
class in
PyDPF-Core or in any supported client API.
With support for custom operators, PyDPF-Core becomes a development tool offering:
Accessibility: A simple script can define a basic operator plugin.
Componentization: Operators with similar applications can be grouped in Python plugin packages.
Easy distribution: Standard Python tools can be used to package, upload, and download custom operators.
Dependency management: Third-party Python modules can be added to the Python package.
Reusability: A documented and packaged operator can be reused in an infinite number of workflows.
Remotable and parallel computing: Native DPF capabilities are inherited by custom operators.
The only prerequisite for creating custom operators is to be familiar with native operators. For more information, see Operators.
Install module#
Once an Ansys unified installation is complete, you must install the ansys-dpf-core
module in the Ansys
installer’s Python interpreter.
Download the script for your operating system:
For Windows, download this
PowerShell script
.For Linux, download this
Shell script
Run the downloaded script for installing with optional arguments:
-awp_root
: Path to the Ansys root installation folder. For example, the 2023 R1 installation folder ends withAnsys Inc/v231
, and the default environment variable isAWP_ROOT231
.-pip_args
: Optional arguments to add to thepip
command. For example,--extra-index-url
or--trusted-host
.
To uninstall the ansys-dpf-core
module from the Ansys installation:
Download the script for your operating system:
For Windows, download this
PowerShell script
.For Linux, download this
Shell script
.
Run the downloaded script for uninstalling with the optional argument:
-awp_root
: Path to the Ansys root installation folder. For example, the 2023 R1 installation folder ends withAnsys Inc/v231
, and the default environment variable isAWP_ROOT231
.
Create operators#
You can create a basic operator plugin or a plugin package with multiple operators.
Basic operator plugin#
To create a basic operator plugin, write a simple Python script. An operator implementation
derives from the ansys.dpf.core.custom_operator.CustomOperatorBase
class and a call to
the ansys.dpf.core.custom_operator.record_operator()
method.
This example script shows how you create a basic operator plugin:
from ansys.dpf import core as dpf
from ansys.dpf.core.custom_operator import CustomOperatorBase, record_operator # noqa: F401
from ansys.dpf.core.operator_specification import CustomSpecification, SpecificationProperties, \
PinSpecification
class CustomOperator(CustomOperatorBase):
@property
def name(self):
return "name_of_my_custom_operator"
@property
def specification(self) -> CustomSpecification:
spec = CustomSpecification()
spec.description = "What the Operator does."
spec.inputs = {
0: PinSpecification("name_of_pin_0", [dpf.Field, dpf.FieldsContainer],
"Describe pin 0."),
}
spec.outputs = {
0: PinSpecification("name_of_pin_0", [dpf.Field], "Describe pin 0."),
}
spec.properties = SpecificationProperties(
user_name="user name",
category="category",
license="license",
)
return spec
def run(self):
field = self.get_input(0, dpf.Field)
if field is None:
field = self.get_input(0, dpf.FieldsContainer)[0]
# compute data
self.set_output(0, dpf.Field())
self.set_succeeded()
def load_operators(*args):
record_operator(CustomOperator, *args)
In the various properties for the class, specify the following:
Name for the custom operator
Description of what the operator does
Dictionary for each input and output pin. This dictionary includes the name, a list of supported types, a description, and whether it is optional and/or ellipsis (meaning that the specification is valid for pins going from pin number x to infinity)
List for operator properties, including name to use in the documentation and code generation and the operator category. The optional
license
property lets you define a required license to check out when running the operator. Set it equal toany_dpf_supported_increments
to allow any license currently accepted by DPF (see here)
For comprehensive examples on writing operator plugins, see Examples of creating custom operator plugins.
Plugin package with multiple operators#
To create a plugin package with multiple operators or with complex routines, write a Python package. The benefits of writing packages rather than simple scripts are:
Componentization: You can split the code into several Python modules or files.
Distribution: You can use standard Python tools to upload and download packages.
Documentation: You can add README files, documentation, tests, and examples to the package.
A plugin package with dependencies consists of a folder with the necessary files. Assume
that the name of your plugin package is custom_plugin
. A folder with this name would
contain four files:
__init__.py
operators.py
operators_loader.py
common.py
__init__.py file
The __init__.py
file contains this code:
from operators_loader import load_operators
operators.py file
The operators.py
file contains code like this:
from ansys.dpf import core as dpf
from ansys.dpf.core.custom_operator import CustomOperatorBase, record_operator # noqa: F401
from ansys.dpf.core.operator_specification import CustomSpecification, SpecificationProperties, \
PinSpecification
class CustomOperator(CustomOperatorBase):
@property
def name(self):
return "name_of_my_custom_operator"
@property
def specification(self) -> CustomSpecification:
spec = CustomSpecification()
spec.description = "What the Operator does."
spec.inputs = {
0: PinSpecification("name_of_pin_0", [dpf.Field, dpf.FieldsContainer],
"Describe pin 0."),
}
spec.outputs = {
0: PinSpecification("name_of_pin_0", [dpf.Field], "Describe pin 0."),
}
spec.properties = SpecificationProperties(
user_name="user name",
category="category",
license="license",
)
return spec
def run(self):
field = self.get_input(0, dpf.Field)
if field is None:
field = self.get_input(0, dpf.FieldsContainer)[0]
# compute data
self.set_output(0, dpf.Field())
self.set_succeeded()
operators_loader.py file
The operators_loader.py
file contains code like this:
from custom_plugin import operators
from ansys.dpf.core.custom_operator import record_operator
def load_operators(*args):
record_operator(operators.CustomOperator, *args)
common.py file
The common.py
file contains the Python routines as classes and functions:
#write needed python routines as classes and functions here.
Third-party dependencies#
To add third-party modules as dependencies to a plugin package, create
and reference a folder or ZIP file with the sites of the dependencies in an XML file
located next to the folder for the plugin package. The XML file must have the same
name as the plugin package plus an .xml
extension.
When the ansys.dpf.core.core.load_library()
method is called, PyDPF-Core uses the
site
Python module to add custom sites to the path for the Python interpreter.
To create these custom sites:
Install the requirements of the plugin package in a Python virtual environment.
Remove unnecessary folders from the site packages and compress them into a ZIP file.
Place the ZIP file in the plugin package.
Reference the path to the ZIP file in the XML file as indicated above.
To simplify this step, you can add a requirements file in the plugin package:
wheel
pygltflib
For this approach, do the following:
Download the script for your operating system:
For Windows, download this
PowerShell script
.For Linux, download this
Shell script
.
Run the downloaded script with the mandatory arguments:
-pluginpath
: Path to the folder with the plugin package.-zippath
: Path and name for the ZIP file.
Optional arguments are:
-pythonexe
: Path to a Python executable of your choice.-tempfolder
: Path to a temporary folder to work in. The default is the environment variableTEMP
on Windows and/tmp/
on Linux.
Run the command for your operating system.
From Windows PowerShell, run:
create_sites_for_python_operators.ps1 -pluginpath /path/to/plugin -zippath /path/to/plugin/assets/winx64.zipFrom Linux Shell, run:
create_sites_for_python_operators.sh -pluginpath /path/to/plugin -zippath /path/to/plugin/assets/linx64.zip
Assume once again that the name of your plugin package is custom_plugin
.
A folder with this name would contain these files:
__init__.py
operators.py
operators_loader.py
common.py
winx64.zip
linx64.zip
custom_plugin.xml
__init__.py file
The __init__.py
file contains this code:
from operators_loader import load_operators
operators.py file
The operators.py
file contains code like this:
from ansys.dpf import core as dpf
from ansys.dpf.core.custom_operator import CustomOperatorBase, record_operator # noqa: F401
from ansys.dpf.core.operator_specification import CustomSpecification, SpecificationProperties, \
PinSpecification
class CustomOperator(CustomOperatorBase):
@property
def name(self):
return "name_of_my_custom_operator"
@property
def specification(self) -> CustomSpecification:
spec = CustomSpecification()
spec.description = "What the Operator does."
spec.inputs = {
0: PinSpecification("name_of_pin_0", [dpf.Field, dpf.FieldsContainer],
"Describe pin 0."),
}
spec.outputs = {
0: PinSpecification("name_of_pin_0", [dpf.Field], "Describe pin 0."),
}
spec.properties = SpecificationProperties(
user_name="user name",
category="category",
license="license",
)
return spec
def run(self):
field = self.get_input(0, dpf.Field)
if field is None:
field = self.get_input(0, dpf.FieldsContainer)[0]
# compute data
self.set_output(0, dpf.Field())
self.set_succeeded()
operators_loader.py file
The operators_loader.py
file contains code like this:
from custom_plugin import operators
from ansys.dpf.core.custom_operator import record_operator
def load_operators(*args):
record_operator(operators.CustomOperator, *args)
def load_operators(*args):
record_operator(operators.CustomOperator, *args)
common.py file
The common.py
file contains the Python routines as classes and functions:
#write needed python routines as classes and functions here.
requirements.txt file
The requirements.txt
file contains code like this:
wheel
pygltflib
The ZIP files for Windows and Linux are included as assets:
winx64.zip
linx64.zip
custom_plugin.xml file
The custom_plugin.xml
file contains code like this:
<?xml version="1.0"?>
<Environment>
<Windows>
<CUSTOM_SITE>$(THIS_XML_FOLDER)/custom_plugin/assets/winx64.zip;$(CUSTOM_SITE)</CUSTOM_SITE>
<LOAD_DEFAULT_DPF_SITE>true</LOAD_DEFAULT_DPF_SITE>
</Windows>
<Linux>
<CUSTOM_SITE>$(THIS_XML_FOLDER)/custom_plugin/assets/linx64.zip:$(CUSTOM_SITE)</CUSTOM_SITE>
<LOAD_DEFAULT_DPF_SITE>true</LOAD_DEFAULT_DPF_SITE>
</Linux>
</Environment>
Use custom operators#
Once a custom operator is created, you can use the ansys.dpf.core.core.load_library()
method to load it.
The first argument is the path to the directory with the plugin. The second argument is py_
plus any name
identifying the plugin. The last argument is the function name for recording operators.
For a plugin that is a single script, the second argument should be py_
plus the name of the Python file:
dpf.load_library(
r"path/to/plugins",
"py_custom_plugin", #if the load_operators function is defined in path/to/plugins/custom_plugin.py
"load_operators")
For a plugin package, the second argument should be py_
plus any name:
dpf.load_library(
r"path/to/plugins/custom_plugin",
"py_my_custom_plugin", #if the load_operators function is defined in path/to/plugins/custom_plugin/__init__.py
"load_operators")
Once the plugin is loaded, you can instantiate the custom operator:
new_operator = dpf.Operator("custom_operator") # if "custom_operator" is what is returned by the ``name`` property
References#
For more information, see ref_custom_operator in the API reference and Examples of creating custom operator plugins in Examples.