Custom Operators#

Starting with Ansys 2022 R2, DPF offers the capability to create user-defined Operators in CPython. Writing Operators allows to wrap python routines in a DPF compliant way so that it can be accessed the same way as a native ansys.dpf.core.dpf_operator.Operator in pyDPF or in any supported client API. With this feature, DPF can be used as a development tool offering:

  • Accessibility: a single script defines an Operator and its documentation.

  • Componentization: Operators with similar applications can be grouped in python packages named Plugins.

  • Easy Distribution: standard python tools can be used to package, upload and download the user-defined operators.

  • Dependencies 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’s capabilities are inherited by the user-defined Operators.

A prerequisite to writing user-defined Operators is to be comfortable with the concept of Operator (Operators).

Installation#

Once Ansys unified installation completed, ansys-dpf-core module needs to be installed in the Ansys installer’s Python interpreter. Run this powershell script for windows or this shell script for linux with the optional arguments:

  • -awp_root : path to Ansys root installation path (usually ending with Ansys Inc/v222), defaults to environment variable AWP_ROOT222

  • -pip_args : optional arguments that add to pip command (ie. –extra-index-url, –trusted-host,…)

If you wish to uninstall ansys-dpf-core module of the Ansys installation, run this powershell script for windows or this shell script for linux with the optional argument:

  • -awp_root : path to Ansys root installation path (usually ending with Ansys Inc/v222), defaults to environment variable AWP_ROOT222

Writing the Operator#

Basic Implementation#

To write the simplest DPF python plugins, a single python script is necessary. An Operator implementation deriving from ansys.dpf.core.custom_operator.CustomOperatorBase and a call to ansys.dpf.core.custom_operator.record_operator() are the 2 necessary steps to create a 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", "category")
        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)

Input and output pins descriptions take a dictionary mapping pin numbers to their ansys.dpf.core.operator_specification.PinSpecification. PinSpecification takes a name (used in the documentation, and in the code generation), a list of supported types, a document, whether the pin is optional and/or ellipsis (meaning the pin specification is valid for pins going from pin number to infinity). ansys.dpf.core.operator_specification.SpecificationProperties allows to specify other properties of the Operator like its user name (mandatory) or its category (used in the documentation, and in the code generation).

See example of Custom Operators implementations in the Examples section Examples of custom python plugins of Operators.

Package Custom Operators#

To create a DPF plugin with several Operators or with complex routines, python packages of Operators can be created. The benefits of writing packages instead of simple scripts are:

  • componentization (split the code in several python modules or files).

  • distribution (with packages, standard python tools can be used to upload and download packages).

  • documentation (READMEs, docs, tests and examples can be added to the package).

A plugin as a package can be a folder with a structure like:

custom_plugin
__init__.py
from operators_loader import load_operators
operators.py
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", "category")
        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
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
#write needed python routines as classes and functions here.

Add Third Party Dependencies#

To add third party modules as dependencies to a custom DPF python plugin, a folder or zip file with the sites of the dependencies needs to be created and referenced in an xml located next to the plugin’s folder and having the same name as the plugin plus the .xml extension. The site python module is used by DPF when calling ansys.dpf.core.core.load_library() function to add these custom sites to the python interpreter path. To create these custom sites, the requirements of the custom plugin should be installed in a python virtual environment, the site-packages (with unnecessary folders removed) should be zipped and put with the plugin. The path to this zip should be referenced in the xml as done above.

To simplify this step, a requirements file can be added in the plugin, like:

requirements.txt
pygltflib

And this powershell script for windows or this shell script can be ran with the mandatory arguments:

  • -pluginpath : path to the folder of the plugin.

  • -zippath : output zip file name.

optional arguments are:

  • -pythonexe : path to a python executable of your choice.

  • -tempfolder : path to a temporary folder to work on, default is the environment variable TEMP on Windows and /tmp/ on Linux.

For windows powershell, call:

create_sites_for_python_operators.ps1 -pluginpath /path/to/plugin -zippath /path/to/plugin/assets/winx64.zip

For linux shell, call:

create_sites_for_python_operators.sh -pluginpath /path/to/plugin -zippath /path/to/plugin/assets/linx64.zip

A plugin as a package with dependencies can be a folder with a structure like:

plugins
custom_plugin
__init__.py
from operators_loader import load_operators
operators.py
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", "category")
        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
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
#write needed python routines as classes and functions here.
requirements.txt
pygltflib
assets
  • winx64.zip

  • linx64.zip

custom_plugin.xml
<?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 the Custom Operators#

Once a python plugin is written, it can be loaded with the function ansys.dpf.core.core.load_library() taking as first argument the path to the directory of the plugin, as second argument py_ + any name identifying the plugin, and as last argument the function’s name used to record operators.

If a single script has been used to create the plugin, then the second argument should be py_ + 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")

If a python package was written, then the second argument should be _py + 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 loaded, the Operator can be instantiated with:

new_operator = dpf.Operator("custom_operator") # if "custom_operator" is what is returned by the ``name`` property

References#

See the API reference at Custom Operator Base and examples of Custom Operators implementations in Examples of custom python plugins of Operators.