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 plug-in 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 you 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
.
If you ever want to uninstall the ansys-dpf-core
module from the Ansys installation, you can do so.
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 plug-in package with multiple operators.
Basic operator plugin#
To create a basic operator plugin, you 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, you specify the following:
Name for the custom operator
Description of what the operator does
Dictionary for each input and output pin, which 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 allows to 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.
Plug-in package with multiple operators#
To create a plug-in package with multiple operators or with complex routines, you 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 plug-in package with dependencies consists of a folder with the necessary files. Assume
that the name of your plug-in 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 plug-in package, you should 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 plug-in package. The XML file must have the same
name as the plug-in 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, do the following:
Install the requirements of the plug-in package in a Python virtual environment.
Remove unnecessary folders from the site packages and compress them to a ZIP file.
Place the ZIP file in the plug-in 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 plug-in 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 plug-in 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 plug-in 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 plug-in 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 Custom Operator Base in the API reference and Examples of creating custom operator plugins in Examples.