From 551c35e8ab7797c123cf917e0c91e478ebc9f1a3 Mon Sep 17 00:00:00 2001 From: cbellot Date: Fri, 10 Jun 2022 17:36:18 +0200 Subject: [PATCH 1/2] add examples for operators in python --- examples/00-wrapping_numpy_capabilities.ipynb | 438 ++++++++++++++++++ examples/01-package_python_operators.ipynb | 335 ++++++++++++++ ...2-python_operators_with_dependencies.ipynb | 231 +++++++++ 3 files changed, 1004 insertions(+) create mode 100644 examples/00-wrapping_numpy_capabilities.ipynb create mode 100644 examples/01-package_python_operators.ipynb create mode 100644 examples/02-python_operators_with_dependencies.ipynb diff --git a/examples/00-wrapping_numpy_capabilities.ipynb b/examples/00-wrapping_numpy_capabilities.ipynb new file mode 100644 index 00000000..2f9f3b1d --- /dev/null +++ b/examples/00-wrapping_numpy_capabilities.ipynb @@ -0,0 +1,438 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "# Write user defined Operator\n", + "This example shows how to create a simple DPF python plugin holding a single Operator.\n", + "This Operator called \"easy_statistics\" computes simple statistics quantities on a scalar Field with\n", + "the help of numpy.\n", + "It's a simple example displaying how routines can be wrapped in DPF python plugins.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Write Operator\n", + "To write the simplest DPF python plugins, a single python script is necessary.\n", + "An Operator implementation deriving from `ansys.dpf.core.custom_operator.CustomOperatorBase`\n", + "and a call to `ansys.dpf.core.custom_operator.record_operator` are the 2 necessary steps to create a plugin.\n", + "The \"easy_statistics\" Operator will take a Field in input and return the first quartile, the median,\n", + "the third quartile and the variance. The python Operator and its recording seat in the\n", + "file plugins/easy_statistics.py. This file is:\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
import numpy as np\n",
+       "from ansys.dpf import core as dpf\n",
+       "from ansys.dpf.core.custom_operator import CustomOperatorBase, record_operator\n",
+       "from ansys.dpf.core.operator_specification import CustomSpecification, SpecificationProperties, \\\n",
+       "    PinSpecification\n",
+       "\n",
+       "\n",
+       "class EasyStatistics(CustomOperatorBase):\n",
+       "    @property\n",
+       "    def name(self):\n",
+       "        return "easy_statistics"\n",
+       "\n",
+       "    @property\n",
+       "    def specification(self) -> CustomSpecification:\n",
+       "        spec = CustomSpecification()\n",
+       "        spec.description = "Compute the first quartile, the median, the third quartile and the variance of a scalar Field with numpy"\n",
+       "        spec.inputs = {0: PinSpecification("field", [dpf.Field, dpf.FieldsContainer], "scalar Field on which the statistics quantities is computed.")}\n",
+       "        spec.outputs = {\n",
+       "            0: PinSpecification("first_quartile", [float]),\n",
+       "            1: PinSpecification("median", [float]),\n",
+       "            2: PinSpecification("third_quartile", [float]),\n",
+       "            3: PinSpecification("variance", [float]),\n",
+       "        }\n",
+       "        spec.properties = SpecificationProperties("easy statistics", "math")\n",
+       "        return spec\n",
+       "\n",
+       "    def run(self):\n",
+       "        field = self.get_input(0, dpf.Field)\n",
+       "        if field is None:\n",
+       "            field = self.get_input(0, dpf.FieldsContainer)[0]\n",
+       "        # compute stats\n",
+       "        first_quartile_val = np.quantile(field.data, 0.25)\n",
+       "        median_val = np.quantile(field.data, 0.5)\n",
+       "        third_quartile_val = np.quantile(field.data, 0.75)\n",
+       "        variance_val = np.var(field.data)\n",
+       "        self.set_output(0, first_quartile_val)\n",
+       "        self.set_output(1, median_val)\n",
+       "        self.set_output(2, third_quartile_val)\n",
+       "        self.set_output(3, float(variance_val))\n",
+       "        self.set_succeeded()\n",
+       "\n",
+       "\n",
+       "def load_operators(*args):\n",
+       "    record_operator(EasyStatistics, *args)\n",
+       "
\n" + ], + "text/latex": [ + "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", + "\\PY{k+kn}{import} \\PY{n+nn}{numpy} \\PY{k}{as} \\PY{n+nn}{np}\n", + "\\PY{k+kn}{from} \\PY{n+nn}{ansys}\\PY{n+nn}{.}\\PY{n+nn}{dpf} \\PY{k+kn}{import} \\PY{n}{core} \\PY{k}{as} \\PY{n}{dpf}\n", + "\\PY{k+kn}{from} \\PY{n+nn}{ansys}\\PY{n+nn}{.}\\PY{n+nn}{dpf}\\PY{n+nn}{.}\\PY{n+nn}{core}\\PY{n+nn}{.}\\PY{n+nn}{custom\\PYZus{}operator} \\PY{k+kn}{import} \\PY{n}{CustomOperatorBase}\\PY{p}{,} \\PY{n}{record\\PYZus{}operator}\n", + "\\PY{k+kn}{from} \\PY{n+nn}{ansys}\\PY{n+nn}{.}\\PY{n+nn}{dpf}\\PY{n+nn}{.}\\PY{n+nn}{core}\\PY{n+nn}{.}\\PY{n+nn}{operator\\PYZus{}specification} \\PY{k+kn}{import} \\PY{n}{CustomSpecification}\\PY{p}{,} \\PY{n}{SpecificationProperties}\\PY{p}{,} \\PYZbs{}\n", + " \\PY{n}{PinSpecification}\n", + "\n", + "\n", + "\\PY{k}{class} \\PY{n+nc}{EasyStatistics}\\PY{p}{(}\\PY{n}{CustomOperatorBase}\\PY{p}{)}\\PY{p}{:}\n", + " \\PY{n+nd}{@property}\n", + " \\PY{k}{def} \\PY{n+nf}{name}\\PY{p}{(}\\PY{n+nb+bp}{self}\\PY{p}{)}\\PY{p}{:}\n", + " \\PY{k}{return} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{easy\\PYZus{}statistics}\\PY{l+s+s2}{\\PYZdq{}}\n", + "\n", + " \\PY{n+nd}{@property}\n", + " \\PY{k}{def} \\PY{n+nf}{specification}\\PY{p}{(}\\PY{n+nb+bp}{self}\\PY{p}{)} \\PY{o}{\\PYZhy{}}\\PY{o}{\\PYZgt{}} \\PY{n}{CustomSpecification}\\PY{p}{:}\n", + " \\PY{n}{spec} \\PY{o}{=} \\PY{n}{CustomSpecification}\\PY{p}{(}\\PY{p}{)}\n", + " \\PY{n}{spec}\\PY{o}{.}\\PY{n}{description} \\PY{o}{=} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{Compute the first quartile, the median, the third quartile and the variance of a scalar Field with numpy}\\PY{l+s+s2}{\\PYZdq{}}\n", + " \\PY{n}{spec}\\PY{o}{.}\\PY{n}{inputs} \\PY{o}{=} \\PY{p}{\\PYZob{}}\\PY{l+m+mi}{0}\\PY{p}{:} \\PY{n}{PinSpecification}\\PY{p}{(}\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{field}\\PY{l+s+s2}{\\PYZdq{}}\\PY{p}{,} \\PY{p}{[}\\PY{n}{dpf}\\PY{o}{.}\\PY{n}{Field}\\PY{p}{,} \\PY{n}{dpf}\\PY{o}{.}\\PY{n}{FieldsContainer}\\PY{p}{]}\\PY{p}{,} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{scalar Field on which the statistics quantities is computed.}\\PY{l+s+s2}{\\PYZdq{}}\\PY{p}{)}\\PY{p}{\\PYZcb{}}\n", + " \\PY{n}{spec}\\PY{o}{.}\\PY{n}{outputs} \\PY{o}{=} \\PY{p}{\\PYZob{}}\n", + " \\PY{l+m+mi}{0}\\PY{p}{:} \\PY{n}{PinSpecification}\\PY{p}{(}\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{first\\PYZus{}quartile}\\PY{l+s+s2}{\\PYZdq{}}\\PY{p}{,} \\PY{p}{[}\\PY{n+nb}{float}\\PY{p}{]}\\PY{p}{)}\\PY{p}{,}\n", + " \\PY{l+m+mi}{1}\\PY{p}{:} \\PY{n}{PinSpecification}\\PY{p}{(}\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{median}\\PY{l+s+s2}{\\PYZdq{}}\\PY{p}{,} \\PY{p}{[}\\PY{n+nb}{float}\\PY{p}{]}\\PY{p}{)}\\PY{p}{,}\n", + " \\PY{l+m+mi}{2}\\PY{p}{:} \\PY{n}{PinSpecification}\\PY{p}{(}\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{third\\PYZus{}quartile}\\PY{l+s+s2}{\\PYZdq{}}\\PY{p}{,} \\PY{p}{[}\\PY{n+nb}{float}\\PY{p}{]}\\PY{p}{)}\\PY{p}{,}\n", + " \\PY{l+m+mi}{3}\\PY{p}{:} \\PY{n}{PinSpecification}\\PY{p}{(}\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{variance}\\PY{l+s+s2}{\\PYZdq{}}\\PY{p}{,} \\PY{p}{[}\\PY{n+nb}{float}\\PY{p}{]}\\PY{p}{)}\\PY{p}{,}\n", + " \\PY{p}{\\PYZcb{}}\n", + " \\PY{n}{spec}\\PY{o}{.}\\PY{n}{properties} \\PY{o}{=} \\PY{n}{SpecificationProperties}\\PY{p}{(}\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{easy statistics}\\PY{l+s+s2}{\\PYZdq{}}\\PY{p}{,} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{math}\\PY{l+s+s2}{\\PYZdq{}}\\PY{p}{)}\n", + " \\PY{k}{return} \\PY{n}{spec}\n", + "\n", + " \\PY{k}{def} \\PY{n+nf}{run}\\PY{p}{(}\\PY{n+nb+bp}{self}\\PY{p}{)}\\PY{p}{:}\n", + " \\PY{n}{field} \\PY{o}{=} \\PY{n+nb+bp}{self}\\PY{o}{.}\\PY{n}{get\\PYZus{}input}\\PY{p}{(}\\PY{l+m+mi}{0}\\PY{p}{,} \\PY{n}{dpf}\\PY{o}{.}\\PY{n}{Field}\\PY{p}{)}\n", + " \\PY{k}{if} \\PY{n}{field} \\PY{o+ow}{is} \\PY{k+kc}{None}\\PY{p}{:}\n", + " \\PY{n}{field} \\PY{o}{=} \\PY{n+nb+bp}{self}\\PY{o}{.}\\PY{n}{get\\PYZus{}input}\\PY{p}{(}\\PY{l+m+mi}{0}\\PY{p}{,} \\PY{n}{dpf}\\PY{o}{.}\\PY{n}{FieldsContainer}\\PY{p}{)}\\PY{p}{[}\\PY{l+m+mi}{0}\\PY{p}{]}\n", + " \\PY{c+c1}{\\PYZsh{} compute stats}\n", + " \\PY{n}{first\\PYZus{}quartile\\PYZus{}val} \\PY{o}{=} \\PY{n}{np}\\PY{o}{.}\\PY{n}{quantile}\\PY{p}{(}\\PY{n}{field}\\PY{o}{.}\\PY{n}{data}\\PY{p}{,} \\PY{l+m+mf}{0.25}\\PY{p}{)}\n", + " \\PY{n}{median\\PYZus{}val} \\PY{o}{=} \\PY{n}{np}\\PY{o}{.}\\PY{n}{quantile}\\PY{p}{(}\\PY{n}{field}\\PY{o}{.}\\PY{n}{data}\\PY{p}{,} \\PY{l+m+mf}{0.5}\\PY{p}{)}\n", + " \\PY{n}{third\\PYZus{}quartile\\PYZus{}val} \\PY{o}{=} \\PY{n}{np}\\PY{o}{.}\\PY{n}{quantile}\\PY{p}{(}\\PY{n}{field}\\PY{o}{.}\\PY{n}{data}\\PY{p}{,} \\PY{l+m+mf}{0.75}\\PY{p}{)}\n", + " \\PY{n}{variance\\PYZus{}val} \\PY{o}{=} \\PY{n}{np}\\PY{o}{.}\\PY{n}{var}\\PY{p}{(}\\PY{n}{field}\\PY{o}{.}\\PY{n}{data}\\PY{p}{)}\n", + " \\PY{n+nb+bp}{self}\\PY{o}{.}\\PY{n}{set\\PYZus{}output}\\PY{p}{(}\\PY{l+m+mi}{0}\\PY{p}{,} \\PY{n}{first\\PYZus{}quartile\\PYZus{}val}\\PY{p}{)}\n", + " \\PY{n+nb+bp}{self}\\PY{o}{.}\\PY{n}{set\\PYZus{}output}\\PY{p}{(}\\PY{l+m+mi}{1}\\PY{p}{,} \\PY{n}{median\\PYZus{}val}\\PY{p}{)}\n", + " \\PY{n+nb+bp}{self}\\PY{o}{.}\\PY{n}{set\\PYZus{}output}\\PY{p}{(}\\PY{l+m+mi}{2}\\PY{p}{,} \\PY{n}{third\\PYZus{}quartile\\PYZus{}val}\\PY{p}{)}\n", + " \\PY{n+nb+bp}{self}\\PY{o}{.}\\PY{n}{set\\PYZus{}output}\\PY{p}{(}\\PY{l+m+mi}{3}\\PY{p}{,} \\PY{n+nb}{float}\\PY{p}{(}\\PY{n}{variance\\PYZus{}val}\\PY{p}{)}\\PY{p}{)}\n", + " \\PY{n+nb+bp}{self}\\PY{o}{.}\\PY{n}{set\\PYZus{}succeeded}\\PY{p}{(}\\PY{p}{)}\n", + "\n", + "\n", + "\\PY{k}{def} \\PY{n+nf}{load\\PYZus{}operators}\\PY{p}{(}\\PY{o}{*}\\PY{n}{args}\\PY{p}{)}\\PY{p}{:}\n", + " \\PY{n}{record\\PYZus{}operator}\\PY{p}{(}\\PY{n}{EasyStatistics}\\PY{p}{,} \\PY{o}{*}\\PY{n}{args}\\PY{p}{)}\n", + "\\end{Verbatim}\n" + ], + "text/plain": [ + "import numpy as np\n", + "from ansys.dpf import core as dpf\n", + "from ansys.dpf.core.custom_operator import CustomOperatorBase, record_operator\n", + "from ansys.dpf.core.operator_specification import CustomSpecification, SpecificationProperties, \\\n", + " PinSpecification\n", + "\n", + "\n", + "class EasyStatistics(CustomOperatorBase):\n", + " @property\n", + " def name(self):\n", + " return \"easy_statistics\"\n", + "\n", + " @property\n", + " def specification(self) -> CustomSpecification:\n", + " spec = CustomSpecification()\n", + " spec.description = \"Compute the first quartile, the median, the third quartile and the variance of a scalar Field with numpy\"\n", + " spec.inputs = {0: PinSpecification(\"field\", [dpf.Field, dpf.FieldsContainer], \"scalar Field on which the statistics quantities is computed.\")}\n", + " spec.outputs = {\n", + " 0: PinSpecification(\"first_quartile\", [float]),\n", + " 1: PinSpecification(\"median\", [float]),\n", + " 2: PinSpecification(\"third_quartile\", [float]),\n", + " 3: PinSpecification(\"variance\", [float]),\n", + " }\n", + " spec.properties = SpecificationProperties(\"easy statistics\", \"math\")\n", + " return spec\n", + "\n", + " def run(self):\n", + " field = self.get_input(0, dpf.Field)\n", + " if field is None:\n", + " field = self.get_input(0, dpf.FieldsContainer)[0]\n", + " # compute stats\n", + " first_quartile_val = np.quantile(field.data, 0.25)\n", + " median_val = np.quantile(field.data, 0.5)\n", + " third_quartile_val = np.quantile(field.data, 0.75)\n", + " variance_val = np.var(field.data)\n", + " self.set_output(0, first_quartile_val)\n", + " self.set_output(1, median_val)\n", + " self.set_output(2, third_quartile_val)\n", + " self.set_output(3, float(variance_val))\n", + " self.set_succeeded()\n", + "\n", + "\n", + "def load_operators(*args):\n", + " record_operator(EasyStatistics, *args)" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import IPython\n", + "import os\n", + "IPython.display.Code(os.path.join(os.getcwd(),\"../plugins/easy_statistics.py\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load Plugin\n", + "Once a python plugin is written, it can be loaded with the function `ansys.dpf.core.core.load_library`\n", + "taking as first argument the path to the directory of the plugin, as second argument ``py_`` + the name of\n", + "the python script, and as last argument the function's name used to record operators.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Established connection to DPF gRPC\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/tmp/dataProcessingTemp140166535345920/easy_statistics.py\n" + ] + }, + { + "data": { + "text/plain": [ + "'py_easy_statistics successfully loaded'" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import os\n", + "from ansys.dpf import core as dpf\n", + "from ansys.dpf.core import examples\n", + "\n", + "operator_file = dpf.upload_file_in_tmp_folder(\n", + " os.path.join(os.getcwd(),\"../plugins/easy_statistics.py\"))\n", + "print(operator_file)\n", + "dpf.load_library(os.path.dirname(operator_file), \"py_easy_statistics\", \"load_operators\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once the Operator loaded, it can be instantiated with:\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "new_operator = dpf.Operator(\"easy_statistics\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To use this new Operator, a workflow computing the norm of the displacement\n", + "is connected to the \"easy_statistics\" Operator.\n", + "Methods of the class ``easy_statistics`` are dynamically added thanks to the Operator's\n", + "specification defined in the plugin.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Use the Custom Operator\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "first quartile is 0.0\n", + "median is 7.491665033689507e-09\n", + "third quartile is 1.4276663319275634e-08\n", + "variance is 3.054190175494998e-17\n" + ] + } + ], + "source": [ + "ds = dpf.DataSources(dpf.upload_file_in_tmp_folder(examples.static_rst))\n", + "displacement = dpf.operators.result.displacement(data_sources=ds)\n", + "norm = dpf.operators.math.norm(displacement)\n", + "new_operator.inputs.connect(norm)\n", + "\n", + "\n", + "print(\"first quartile is\", new_operator.outputs.first_quartile())\n", + "print(\"median is\", new_operator.outputs.median())\n", + "print(\"third quartile is\", new_operator.outputs.third_quartile())\n", + "print(\"variance is\", new_operator.outputs.variance())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/01-package_python_operators.ipynb b/examples/01-package_python_operators.ipynb new file mode 100644 index 00000000..b8c5080e --- /dev/null +++ b/examples/01-package_python_operators.ipynb @@ -0,0 +1,335 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "# Write user defined Operators as a package\n", + "This example shows how more complex DPF python plugins of Operators can be created as standard python packages.\n", + "The benefits of writing packages instead of simple scripts are: componentization (split the code in several\n", + "python modules or files), distribution (with packages, standard python tools can be used to upload and\n", + "download packages) and documentation (READMEs, docs, tests and examples can be added to the package).\n", + "\n", + "This plugin will hold 2 different Operators:\n", + " - One returning all the scoping ids having data higher than the average\n", + " - One returning all the scoping ids having data lower than the average\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Write Operator\n", + "For this more advanced use case, a python package is created.\n", + "Each Operator implementation derives from `ansys.dpf.core.custom_operator.CustomOperatorBase`\n", + "and a call to `ansys.dpf.core.custom_operator.record_operator` records the Operators of the plugin.\n", + "The complete package looks like:\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1m average_filter_plugin\n", + "\u001b[1m __init__.py:\n", + " \u001b[0m\n", + "from average_filter_plugin.operators_loader import load_operators\n", + "\n", + "\n", + "\n", + "\u001b[1m operators.py:\n", + " \u001b[0m\n", + "from ansys.dpf.core.custom_operator import CustomOperatorBase\n", + "from ansys.dpf.core.operator_specification import CustomSpecification, PinSpecification, SpecificationProperties\n", + "from ansys.dpf import core as dpf\n", + "from average_filter_plugin import common\n", + "\n", + "\n", + "class IdsWithDataHigherThanAverage(CustomOperatorBase):\n", + " def run(self):\n", + " field = self.get_input(0, dpf.Field)\n", + " average = common.compute_average_of_field(field)\n", + " ids_in = field.scoping.ids\n", + " data_in = field.data\n", + " out = []\n", + " for i, d in enumerate(data_in):\n", + " if d >= average:\n", + " out.append(ids_in[i])\n", + " scoping_out = dpf.Scoping(ids=out, location=field.scoping.location)\n", + " self.set_output(0, scoping_out)\n", + " self.set_succeeded()\n", + "\n", + " @property\n", + " def specification(self):\n", + " spec = CustomSpecification(\"Creates a scoping with all the ids having data higher or equal \"\n", + " \"to the average value of the scalar field's data in input.\")\n", + " spec.inputs = {\n", + " 0: PinSpecification(\"field\", type_names=dpf.Field, document=\"scalar Field.\"),\n", + " }\n", + " spec.outputs = {\n", + " 0: PinSpecification(\"scoping\", type_names=dpf.Scoping),\n", + " }\n", + " spec.properties = SpecificationProperties(user_name=\"ids with data higher than average\", category=\"logic\")\n", + " return spec\n", + "\n", + " @property\n", + " def name(self):\n", + " return \"ids_with_data_higher_than_average\"\n", + "\n", + "\n", + "class IdsWithDataLowerThanAverage(CustomOperatorBase):\n", + " def run(self):\n", + " field = self.get_input(0, dpf.Field)\n", + " average = common.compute_average_of_field(field)\n", + " ids_in = field.scoping.ids\n", + " data_in = field.data\n", + " out = []\n", + " for i, d in enumerate(data_in):\n", + " if d <= average:\n", + " out.append(ids_in[i])\n", + " scoping_out = dpf.Scoping(ids=out, location=field.scoping.location)\n", + " self.set_output(0, scoping_out)\n", + " self.set_succeeded()\n", + "\n", + " @property\n", + " def specification(self):\n", + " spec = CustomSpecification(\"Creates a scoping with all the ids having data lower or equal \"\n", + " \"to the average value of the scalar field's data in input.\")\n", + " spec.inputs = {\n", + " 0: PinSpecification(\"field\", type_names=dpf.Field, document=\"scalar Field.\"),\n", + " }\n", + " spec.outputs = {\n", + " 0: PinSpecification(\"scoping\", type_names=dpf.Scoping),\n", + " }\n", + " spec.properties = SpecificationProperties(user_name=\"ids with data lower than average\", category=\"logic\")\n", + " return spec\n", + "\n", + " @property\n", + " def name(self):\n", + " return \"ids_with_data_lower_than_average\"\n", + "\n", + "\n", + "\n", + "\u001b[1m operators_loader.py:\n", + " \u001b[0m\n", + "from average_filter_plugin import operators\n", + "from ansys.dpf.core.custom_operator import record_operator\n", + "\n", + "\n", + "def load_operators(*args):\n", + " record_operator(operators.IdsWithDataHigherThanAverage, *args)\n", + " record_operator(operators.IdsWithDataLowerThanAverage, *args)\n", + "\n", + "\n", + "\n", + "\u001b[1m common.py:\n", + " \u001b[0m\n", + "import numpy\n", + "\n", + "\n", + "def compute_average_of_field(field):\n", + " return numpy.average(field.data)\n", + "\n" + ] + } + ], + "source": [ + "import IPython\n", + "import os\n", + "print('\\033[1m average_filter_plugin')\n", + "print('\\033[1m __init__.py:\\n \\033[0m')\n", + "print(IPython.display.Code(os.path.join(os.getcwd(),\"../plugins/average_filter_plugin/__init__.py\")))\n", + "print('\\n\\n\\033[1m operators.py:\\n \\033[0m')\n", + "print(IPython.display.Code(os.path.join(os.getcwd(),\"../plugins/average_filter_plugin/operators.py\")))\n", + "print('\\n\\n\\033[1m operators_loader.py:\\n \\033[0m')\n", + "print(IPython.display.Code(os.path.join(os.getcwd(),\"../plugins/average_filter_plugin/operators_loader.py\")))\n", + "print('\\n\\n\\033[1m common.py:\\n \\033[0m')\n", + "print(IPython.display.Code(os.path.join(os.getcwd(),\"../plugins/average_filter_plugin/common.py\")))\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load Plugin\n", + "Once a python plugin is written as a package, it can be loaded with the function\n", + ":py:func:`ansys.dpf.core.core.load_library` taking as first argument the path to the directory of the plugin,\n", + "as second argument ``py_`` + any name identifying the plugin,\n", + "and as last argument the function's name exposed in the __init__ file and used to record operators.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Established connection to DPF gRPC\n" + ] + }, + { + "data": { + "text/plain": [ + "'py_average_filter successfully loaded'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import os\n", + "from ansys.dpf import core as dpf\n", + "from ansys.dpf.core import examples\n", + "\n", + "tmp = dpf.make_tmp_dir_server()\n", + "dpf.upload_files_in_folder(\n", + " dpf.path_utilities.join(tmp, \"average_filter_plugin\"),\n", + " os.path.join(os.getcwd(),\"../plugins\", \"average_filter_plugin\")\n", + ")\n", + "dpf.load_library(\n", + " os.path.join(dpf.path_utilities.join(tmp, \"average_filter_plugin\")),\n", + " \"py_average_filter\",\n", + " \"load_operators\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once the Plugin loaded, Operators recorded in the plugin can be used with:\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "new_operator = dpf.Operator(\"ids_with_data_lower_than_average\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To use this new Operator, a workflow computing the norm of the displacement\n", + "is connected to the \"ids_with_data_lower_than_average\" Operator.\n", + "Methods of the class ``ids_with_data_lower_than_average`` are dynamically added thanks to the Operator's\n", + "specification.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Use the Custom Operator\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "scoping in was: DPF Scoping: \n", + " with Nodal location and 81 entities\n", + "\n", + "----------------------------------------------\n", + "scoping out is: DPF Scoping: \n", + " with Nodal location and 35 entities\n", + "\n" + ] + } + ], + "source": [ + "ds = dpf.DataSources(dpf.upload_file_in_tmp_folder(examples.static_rst))\n", + "displacement = dpf.operators.result.displacement(data_sources=ds)\n", + "norm = dpf.operators.math.norm(displacement)\n", + "new_operator.inputs.connect(norm)\n", + "\n", + "\n", + "new_scoping = new_operator.outputs.scoping()\n", + "print(\"scoping in was:\", norm.outputs.field().scoping)\n", + "print(\"----------------------------------------------\")\n", + "print(\"scoping out is:\", new_scoping)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/02-python_operators_with_dependencies.ipynb b/examples/02-python_operators_with_dependencies.ipynb new file mode 100644 index 00000000..a9e46bd0 --- /dev/null +++ b/examples/02-python_operators_with_dependencies.ipynb @@ -0,0 +1,231 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "# Write user defined Operators having third party dependencies\n", + "This example shows how advanced DPF python plugins of Operators can be created as standard python packages\n", + "and how third party python modules dependencies can be added to the package.\n", + "\n", + "This plugin will hold an Operator which implementation depends on a third party python module named\n", + "`gltf `_. This Operator takes a path, a mesh and 3D vector field in input\n", + "and exports the mesh and the norm of the input field in a gltf file located at the given path.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Write Operator\n", + "For this more advanced use case, a python package is created.\n", + "Each Operator implementation derives from `ansys.dpf.core.custom_operator.CustomOperatorBase`\n", + "and a call to `ansys.dpf.core.custom_operator.record_operator` records the Operators of the plugin.\n", + "See the complete package in:\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load Plugin\n", + "Once a python plugin is written as a package, it can be loaded with the function\n", + ":py:func:`ansys.dpf.core.core.load_library` taking as first argument the path to the directory of the plugin,\n", + "as second argument ``py_`` + any name identifying the plugin,\n", + "and as last argument the function's name exposed in the ``__init__.py`` file and used to record operators.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Uploading...: 12057 of 12057 KB 100%|#########################################|\n" + ] + }, + { + "data": { + "text/plain": [ + "'py_dpf_gltf successfully loaded'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import os\n", + "from ansys.dpf import core as dpf\n", + "from ansys.dpf.core import examples\n", + "\n", + "tmp = dpf.make_tmp_dir_server()\n", + "dpf.upload_files_in_folder(\n", + " dpf.path_utilities.join(tmp, \"plugins\", \"gltf_plugin\"),\n", + " dpf.path_utilities.join(os.getcwd(), \"..\", \"plugins\", \"gltf_plugin\")\n", + ")\n", + "dpf.upload_file(\n", + " dpf.path_utilities.join(os.getcwd(), \"..\", \"plugins\", \"gltf_plugin.xml\"),\n", + " dpf.path_utilities.join(tmp, \"plugins\", \"gltf_plugin.xml\")\n", + ")\n", + "\n", + "dpf.load_library(\n", + " dpf.path_utilities.join(tmp, \"plugins\", \"gltf_plugin\"),\n", + " \"py_dpf_gltf\",\n", + " \"load_operators\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once the Plugin loaded, Operators recorded in the plugin can be used with:\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "new_operator = dpf.Operator(\"gltf_export\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This new Operator ``gltf_export`` requires a triangle surface mesh, a displacement Field on this surface mesh\n", + "as well as an export path as inputs.\n", + "To demo this new Operator, a `ansys.dpf.core.model.Model` on a simple file is created,\n", + "`ansys.dpf.core.operators.mesh.tri_mesh_skin` Operator is used to extract the surface of the mesh in triangles\n", + "elements.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Use the Custom Operator\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "operator ran successfully\n" + ] + } + ], + "source": [ + "import tempfile\n", + "import os\n", + "\n", + "model = dpf.Model(dpf.upload_file_in_tmp_folder(examples.static_rst))\n", + "\n", + "mesh = model.metadata.meshed_region\n", + "skin_mesh = dpf.operators.mesh.tri_mesh_skin(mesh=mesh)\n", + "\n", + "displacement = model.results.displacement()\n", + "displacement.inputs.mesh_scoping(skin_mesh)\n", + "displacement.inputs.mesh(skin_mesh)\n", + "new_operator.inputs.path(os.path.join(tmp, \"out\"))\n", + "new_operator.inputs.mesh(skin_mesh)\n", + "new_operator.inputs.field(displacement.outputs.fields_container()[0])\n", + "new_operator.run()\n", + "\n", + "print(\"operator ran successfully\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Downloading...: 2 of 2 KB 100%|###############################################|\n" + ] + } + ], + "source": [ + "dpf.download_file(os.path.join(tmp, \"out.glb\"), os.path.join(os.getcwd(), \"out.glb\"))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From 5dcedab00d3bea7b324e3079671f3e0a5e11c858 Mon Sep 17 00:00:00 2001 From: cbellot Date: Mon, 20 Jun 2022 14:07:33 +0200 Subject: [PATCH 2/2] make examples standalone --- examples/00-wrapping_numpy_capabilities.ipynb | 543 ++++-------------- examples/01-package_python_operators.ipynb | 446 ++++---------- ...2-python_operators_with_dependencies.ipynb | 380 +++++------- 3 files changed, 394 insertions(+), 975 deletions(-) diff --git a/examples/00-wrapping_numpy_capabilities.ipynb b/examples/00-wrapping_numpy_capabilities.ipynb index 2f9f3b1d..ccb8988c 100644 --- a/examples/00-wrapping_numpy_capabilities.ipynb +++ b/examples/00-wrapping_numpy_capabilities.ipynb @@ -1,438 +1,129 @@ { - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [], - "source": [ - "%matplotlib inline" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n", - "# Write user defined Operator\n", - "This example shows how to create a simple DPF python plugin holding a single Operator.\n", - "This Operator called \"easy_statistics\" computes simple statistics quantities on a scalar Field with\n", - "the help of numpy.\n", - "It's a simple example displaying how routines can be wrapped in DPF python plugins.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Write Operator\n", - "To write the simplest DPF python plugins, a single python script is necessary.\n", - "An Operator implementation deriving from `ansys.dpf.core.custom_operator.CustomOperatorBase`\n", - "and a call to `ansys.dpf.core.custom_operator.record_operator` are the 2 necessary steps to create a plugin.\n", - "The \"easy_statistics\" Operator will take a Field in input and return the first quartile, the median,\n", - "the third quartile and the variance. The python Operator and its recording seat in the\n", - "file plugins/easy_statistics.py. This file is:\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ + "cells": [ { - "data": { - "text/html": [ - "
import numpy as np\n",
-       "from ansys.dpf import core as dpf\n",
-       "from ansys.dpf.core.custom_operator import CustomOperatorBase, record_operator\n",
-       "from ansys.dpf.core.operator_specification import CustomSpecification, SpecificationProperties, \\\n",
-       "    PinSpecification\n",
-       "\n",
-       "\n",
-       "class EasyStatistics(CustomOperatorBase):\n",
-       "    @property\n",
-       "    def name(self):\n",
-       "        return "easy_statistics"\n",
-       "\n",
-       "    @property\n",
-       "    def specification(self) -> CustomSpecification:\n",
-       "        spec = CustomSpecification()\n",
-       "        spec.description = "Compute the first quartile, the median, the third quartile and the variance of a scalar Field with numpy"\n",
-       "        spec.inputs = {0: PinSpecification("field", [dpf.Field, dpf.FieldsContainer], "scalar Field on which the statistics quantities is computed.")}\n",
-       "        spec.outputs = {\n",
-       "            0: PinSpecification("first_quartile", [float]),\n",
-       "            1: PinSpecification("median", [float]),\n",
-       "            2: PinSpecification("third_quartile", [float]),\n",
-       "            3: PinSpecification("variance", [float]),\n",
-       "        }\n",
-       "        spec.properties = SpecificationProperties("easy statistics", "math")\n",
-       "        return spec\n",
-       "\n",
-       "    def run(self):\n",
-       "        field = self.get_input(0, dpf.Field)\n",
-       "        if field is None:\n",
-       "            field = self.get_input(0, dpf.FieldsContainer)[0]\n",
-       "        # compute stats\n",
-       "        first_quartile_val = np.quantile(field.data, 0.25)\n",
-       "        median_val = np.quantile(field.data, 0.5)\n",
-       "        third_quartile_val = np.quantile(field.data, 0.75)\n",
-       "        variance_val = np.var(field.data)\n",
-       "        self.set_output(0, first_quartile_val)\n",
-       "        self.set_output(1, median_val)\n",
-       "        self.set_output(2, third_quartile_val)\n",
-       "        self.set_output(3, float(variance_val))\n",
-       "        self.set_succeeded()\n",
-       "\n",
-       "\n",
-       "def load_operators(*args):\n",
-       "    record_operator(EasyStatistics, *args)\n",
-       "
\n" - ], - "text/latex": [ - "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", - "\\PY{k+kn}{import} \\PY{n+nn}{numpy} \\PY{k}{as} \\PY{n+nn}{np}\n", - "\\PY{k+kn}{from} \\PY{n+nn}{ansys}\\PY{n+nn}{.}\\PY{n+nn}{dpf} \\PY{k+kn}{import} \\PY{n}{core} \\PY{k}{as} \\PY{n}{dpf}\n", - "\\PY{k+kn}{from} \\PY{n+nn}{ansys}\\PY{n+nn}{.}\\PY{n+nn}{dpf}\\PY{n+nn}{.}\\PY{n+nn}{core}\\PY{n+nn}{.}\\PY{n+nn}{custom\\PYZus{}operator} \\PY{k+kn}{import} \\PY{n}{CustomOperatorBase}\\PY{p}{,} \\PY{n}{record\\PYZus{}operator}\n", - "\\PY{k+kn}{from} \\PY{n+nn}{ansys}\\PY{n+nn}{.}\\PY{n+nn}{dpf}\\PY{n+nn}{.}\\PY{n+nn}{core}\\PY{n+nn}{.}\\PY{n+nn}{operator\\PYZus{}specification} \\PY{k+kn}{import} \\PY{n}{CustomSpecification}\\PY{p}{,} \\PY{n}{SpecificationProperties}\\PY{p}{,} \\PYZbs{}\n", - " \\PY{n}{PinSpecification}\n", - "\n", - "\n", - "\\PY{k}{class} \\PY{n+nc}{EasyStatistics}\\PY{p}{(}\\PY{n}{CustomOperatorBase}\\PY{p}{)}\\PY{p}{:}\n", - " \\PY{n+nd}{@property}\n", - " \\PY{k}{def} \\PY{n+nf}{name}\\PY{p}{(}\\PY{n+nb+bp}{self}\\PY{p}{)}\\PY{p}{:}\n", - " \\PY{k}{return} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{easy\\PYZus{}statistics}\\PY{l+s+s2}{\\PYZdq{}}\n", - "\n", - " \\PY{n+nd}{@property}\n", - " \\PY{k}{def} \\PY{n+nf}{specification}\\PY{p}{(}\\PY{n+nb+bp}{self}\\PY{p}{)} \\PY{o}{\\PYZhy{}}\\PY{o}{\\PYZgt{}} \\PY{n}{CustomSpecification}\\PY{p}{:}\n", - " \\PY{n}{spec} \\PY{o}{=} \\PY{n}{CustomSpecification}\\PY{p}{(}\\PY{p}{)}\n", - " \\PY{n}{spec}\\PY{o}{.}\\PY{n}{description} \\PY{o}{=} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{Compute the first quartile, the median, the third quartile and the variance of a scalar Field with numpy}\\PY{l+s+s2}{\\PYZdq{}}\n", - " \\PY{n}{spec}\\PY{o}{.}\\PY{n}{inputs} \\PY{o}{=} \\PY{p}{\\PYZob{}}\\PY{l+m+mi}{0}\\PY{p}{:} \\PY{n}{PinSpecification}\\PY{p}{(}\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{field}\\PY{l+s+s2}{\\PYZdq{}}\\PY{p}{,} \\PY{p}{[}\\PY{n}{dpf}\\PY{o}{.}\\PY{n}{Field}\\PY{p}{,} \\PY{n}{dpf}\\PY{o}{.}\\PY{n}{FieldsContainer}\\PY{p}{]}\\PY{p}{,} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{scalar Field on which the statistics quantities is computed.}\\PY{l+s+s2}{\\PYZdq{}}\\PY{p}{)}\\PY{p}{\\PYZcb{}}\n", - " \\PY{n}{spec}\\PY{o}{.}\\PY{n}{outputs} \\PY{o}{=} \\PY{p}{\\PYZob{}}\n", - " \\PY{l+m+mi}{0}\\PY{p}{:} \\PY{n}{PinSpecification}\\PY{p}{(}\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{first\\PYZus{}quartile}\\PY{l+s+s2}{\\PYZdq{}}\\PY{p}{,} \\PY{p}{[}\\PY{n+nb}{float}\\PY{p}{]}\\PY{p}{)}\\PY{p}{,}\n", - " \\PY{l+m+mi}{1}\\PY{p}{:} \\PY{n}{PinSpecification}\\PY{p}{(}\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{median}\\PY{l+s+s2}{\\PYZdq{}}\\PY{p}{,} \\PY{p}{[}\\PY{n+nb}{float}\\PY{p}{]}\\PY{p}{)}\\PY{p}{,}\n", - " \\PY{l+m+mi}{2}\\PY{p}{:} \\PY{n}{PinSpecification}\\PY{p}{(}\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{third\\PYZus{}quartile}\\PY{l+s+s2}{\\PYZdq{}}\\PY{p}{,} \\PY{p}{[}\\PY{n+nb}{float}\\PY{p}{]}\\PY{p}{)}\\PY{p}{,}\n", - " \\PY{l+m+mi}{3}\\PY{p}{:} \\PY{n}{PinSpecification}\\PY{p}{(}\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{variance}\\PY{l+s+s2}{\\PYZdq{}}\\PY{p}{,} \\PY{p}{[}\\PY{n+nb}{float}\\PY{p}{]}\\PY{p}{)}\\PY{p}{,}\n", - " \\PY{p}{\\PYZcb{}}\n", - " \\PY{n}{spec}\\PY{o}{.}\\PY{n}{properties} \\PY{o}{=} \\PY{n}{SpecificationProperties}\\PY{p}{(}\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{easy statistics}\\PY{l+s+s2}{\\PYZdq{}}\\PY{p}{,} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{math}\\PY{l+s+s2}{\\PYZdq{}}\\PY{p}{)}\n", - " \\PY{k}{return} \\PY{n}{spec}\n", - "\n", - " \\PY{k}{def} \\PY{n+nf}{run}\\PY{p}{(}\\PY{n+nb+bp}{self}\\PY{p}{)}\\PY{p}{:}\n", - " \\PY{n}{field} \\PY{o}{=} \\PY{n+nb+bp}{self}\\PY{o}{.}\\PY{n}{get\\PYZus{}input}\\PY{p}{(}\\PY{l+m+mi}{0}\\PY{p}{,} \\PY{n}{dpf}\\PY{o}{.}\\PY{n}{Field}\\PY{p}{)}\n", - " \\PY{k}{if} \\PY{n}{field} \\PY{o+ow}{is} \\PY{k+kc}{None}\\PY{p}{:}\n", - " \\PY{n}{field} \\PY{o}{=} \\PY{n+nb+bp}{self}\\PY{o}{.}\\PY{n}{get\\PYZus{}input}\\PY{p}{(}\\PY{l+m+mi}{0}\\PY{p}{,} \\PY{n}{dpf}\\PY{o}{.}\\PY{n}{FieldsContainer}\\PY{p}{)}\\PY{p}{[}\\PY{l+m+mi}{0}\\PY{p}{]}\n", - " \\PY{c+c1}{\\PYZsh{} compute stats}\n", - " \\PY{n}{first\\PYZus{}quartile\\PYZus{}val} \\PY{o}{=} \\PY{n}{np}\\PY{o}{.}\\PY{n}{quantile}\\PY{p}{(}\\PY{n}{field}\\PY{o}{.}\\PY{n}{data}\\PY{p}{,} \\PY{l+m+mf}{0.25}\\PY{p}{)}\n", - " \\PY{n}{median\\PYZus{}val} \\PY{o}{=} \\PY{n}{np}\\PY{o}{.}\\PY{n}{quantile}\\PY{p}{(}\\PY{n}{field}\\PY{o}{.}\\PY{n}{data}\\PY{p}{,} \\PY{l+m+mf}{0.5}\\PY{p}{)}\n", - " \\PY{n}{third\\PYZus{}quartile\\PYZus{}val} \\PY{o}{=} \\PY{n}{np}\\PY{o}{.}\\PY{n}{quantile}\\PY{p}{(}\\PY{n}{field}\\PY{o}{.}\\PY{n}{data}\\PY{p}{,} \\PY{l+m+mf}{0.75}\\PY{p}{)}\n", - " \\PY{n}{variance\\PYZus{}val} \\PY{o}{=} \\PY{n}{np}\\PY{o}{.}\\PY{n}{var}\\PY{p}{(}\\PY{n}{field}\\PY{o}{.}\\PY{n}{data}\\PY{p}{)}\n", - " \\PY{n+nb+bp}{self}\\PY{o}{.}\\PY{n}{set\\PYZus{}output}\\PY{p}{(}\\PY{l+m+mi}{0}\\PY{p}{,} \\PY{n}{first\\PYZus{}quartile\\PYZus{}val}\\PY{p}{)}\n", - " \\PY{n+nb+bp}{self}\\PY{o}{.}\\PY{n}{set\\PYZus{}output}\\PY{p}{(}\\PY{l+m+mi}{1}\\PY{p}{,} \\PY{n}{median\\PYZus{}val}\\PY{p}{)}\n", - " \\PY{n+nb+bp}{self}\\PY{o}{.}\\PY{n}{set\\PYZus{}output}\\PY{p}{(}\\PY{l+m+mi}{2}\\PY{p}{,} \\PY{n}{third\\PYZus{}quartile\\PYZus{}val}\\PY{p}{)}\n", - " \\PY{n+nb+bp}{self}\\PY{o}{.}\\PY{n}{set\\PYZus{}output}\\PY{p}{(}\\PY{l+m+mi}{3}\\PY{p}{,} \\PY{n+nb}{float}\\PY{p}{(}\\PY{n}{variance\\PYZus{}val}\\PY{p}{)}\\PY{p}{)}\n", - " \\PY{n+nb+bp}{self}\\PY{o}{.}\\PY{n}{set\\PYZus{}succeeded}\\PY{p}{(}\\PY{p}{)}\n", - "\n", - "\n", - "\\PY{k}{def} \\PY{n+nf}{load\\PYZus{}operators}\\PY{p}{(}\\PY{o}{*}\\PY{n}{args}\\PY{p}{)}\\PY{p}{:}\n", - " \\PY{n}{record\\PYZus{}operator}\\PY{p}{(}\\PY{n}{EasyStatistics}\\PY{p}{,} \\PY{o}{*}\\PY{n}{args}\\PY{p}{)}\n", - "\\end{Verbatim}\n" - ], - "text/plain": [ - "import numpy as np\n", - "from ansys.dpf import core as dpf\n", - "from ansys.dpf.core.custom_operator import CustomOperatorBase, record_operator\n", - "from ansys.dpf.core.operator_specification import CustomSpecification, SpecificationProperties, \\\n", - " PinSpecification\n", - "\n", - "\n", - "class EasyStatistics(CustomOperatorBase):\n", - " @property\n", - " def name(self):\n", - " return \"easy_statistics\"\n", - "\n", - " @property\n", - " def specification(self) -> CustomSpecification:\n", - " spec = CustomSpecification()\n", - " spec.description = \"Compute the first quartile, the median, the third quartile and the variance of a scalar Field with numpy\"\n", - " spec.inputs = {0: PinSpecification(\"field\", [dpf.Field, dpf.FieldsContainer], \"scalar Field on which the statistics quantities is computed.\")}\n", - " spec.outputs = {\n", - " 0: PinSpecification(\"first_quartile\", [float]),\n", - " 1: PinSpecification(\"median\", [float]),\n", - " 2: PinSpecification(\"third_quartile\", [float]),\n", - " 3: PinSpecification(\"variance\", [float]),\n", - " }\n", - " spec.properties = SpecificationProperties(\"easy statistics\", \"math\")\n", - " return spec\n", - "\n", - " def run(self):\n", - " field = self.get_input(0, dpf.Field)\n", - " if field is None:\n", - " field = self.get_input(0, dpf.FieldsContainer)[0]\n", - " # compute stats\n", - " first_quartile_val = np.quantile(field.data, 0.25)\n", - " median_val = np.quantile(field.data, 0.5)\n", - " third_quartile_val = np.quantile(field.data, 0.75)\n", - " variance_val = np.var(field.data)\n", - " self.set_output(0, first_quartile_val)\n", - " self.set_output(1, median_val)\n", - " self.set_output(2, third_quartile_val)\n", - " self.set_output(3, float(variance_val))\n", - " self.set_succeeded()\n", - "\n", - "\n", - "def load_operators(*args):\n", - " record_operator(EasyStatistics, *args)" + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%matplotlib inline" ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import IPython\n", - "import os\n", - "IPython.display.Code(os.path.join(os.getcwd(),\"../plugins/easy_statistics.py\"))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Load Plugin\n", - "Once a python plugin is written, it can be loaded with the function `ansys.dpf.core.core.load_library`\n", - "taking as first argument the path to the directory of the plugin, as second argument ``py_`` + the name of\n", - "the python script, and as last argument the function's name used to record operators.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [ + }, { - "name": "stderr", - "output_type": "stream", - "text": [ - "Established connection to DPF gRPC\n" - ] + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n\n# Write user defined Operator\nThis example shows how to create a simple DPF python plugin holding a single Operator.\nThis Operator called \"easy_statistics\" computes simple statistics quantities on a scalar Field with\nthe help of numpy.\nIt's a simple example displaying how routines can be wrapped in DPF python plugins.\n" + ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "/tmp/dataProcessingTemp140166535345920/easy_statistics.py\n" - ] + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Write Operator\nTo write the simplest DPF python plugins, a single python script is necessary.\nAn Operator implementation deriving from :class:`ansys.dpf.core.custom_operator.CustomOperatorBase`\nand a call to :py:func:`ansys.dpf.core.custom_operator.record_operator` are the 2 necessary steps to create a plugin.\nThe \"easy_statistics\" Operator will take a Field in input and return the first quartile, the median,\nthe third quartile and the variance. The python Operator and its recording seat in the\nfile plugins/easy_statistics.py. This file `easy_statistics.py` is downloaded and displayed here:\n\n" + ] }, { - "data": { - "text/plain": [ - "'py_easy_statistics successfully loaded'" + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from ansys.dpf.core import examples\n\nGITHUB_SOURCE_URL = \"https://github.com/pyansys/pydpf-core/raw/examples/first_python_plugins/python-plugins\"\nEXAMPLE_FILE = GITHUB_SOURCE_URL + \"/easy_statistics.py\"\noperator_file_path = examples.downloads._retrieve_file(EXAMPLE_FILE, \"easy_statistics.py\", \"python-plugins\")\n\nimport IPython\nprint(IPython.display.Code(operator_file_path))" ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import os\n", - "from ansys.dpf import core as dpf\n", - "from ansys.dpf.core import examples\n", - "\n", - "operator_file = dpf.upload_file_in_tmp_folder(\n", - " os.path.join(os.getcwd(),\"../plugins/easy_statistics.py\"))\n", - "print(operator_file)\n", - "dpf.load_library(os.path.dirname(operator_file), \"py_easy_statistics\", \"load_operators\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Once the Operator loaded, it can be instantiated with:\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [], - "source": [ - "new_operator = dpf.Operator(\"easy_statistics\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To use this new Operator, a workflow computing the norm of the displacement\n", - "is connected to the \"easy_statistics\" Operator.\n", - "Methods of the class ``easy_statistics`` are dynamically added thanks to the Operator's\n", - "specification defined in the plugin.\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Use the Custom Operator\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [ + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load Plugin\nOnce a python plugin is written, it can be loaded with the function :py:func:`ansys.dpf.core.core.load_library`\ntaking as first argument the path to the directory of the plugin, as second argument ``py_`` + the name of\nthe python script, and as last argument the function's name used to record operators.\n\n" + ] + }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "first quartile is 0.0\n", - "median is 7.491665033689507e-09\n", - "third quartile is 1.4276663319275634e-08\n", - "variance is 3.054190175494998e-17\n" - ] + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import os\nfrom ansys.dpf import core as dpf\nfrom ansys.dpf.core import examples\n\noperator_server_file_path = dpf.upload_file_in_tmp_folder(operator_file_path)\ndpf.load_library(os.path.dirname(operator_server_file_path), \"py_easy_statistics\", \"load_operators\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once the Operator loaded, it can be instantiated with:\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "new_operator = dpf.Operator(\"easy_statistics\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To use this new Operator, a workflow computing the norm of the displacement\nis connected to the \"easy_statistics\" Operator.\nMethods of the class ``easy_statistics`` are dynamically added thanks to the Operator's\nspecification defined in the plugin.\n\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ".. graphviz::\n\n digraph foo {\n graph [pad=\"0.5\", nodesep=\"0.3\", ranksep=\"0.3\"]\n node [shape=box, style=filled, fillcolor=\"#ffcc00\", margin=\"0\"];\n rankdir=LR;\n splines=line;\n ds [label=\"ds\", shape=box, style=filled, fillcolor=cadetblue2];\n ds -> displacement [style=dashed];\n displacement -> norm;\n norm -> easy_statistics;\n }\n\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Use the Custom Operator\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "ds = dpf.DataSources(dpf.upload_file_in_tmp_folder(examples.static_rst))\ndisplacement = dpf.operators.result.displacement(data_sources=ds)\nnorm = dpf.operators.math.norm(displacement)\nnew_operator.inputs.connect(norm)\n\n\nprint(\"first quartile is\", new_operator.outputs.first_quartile())\nprint(\"median is\", new_operator.outputs.median())\nprint(\"third quartile is\", new_operator.outputs.third_quartile())\nprint(\"variance is\", new_operator.outputs.variance())" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" } - ], - "source": [ - "ds = dpf.DataSources(dpf.upload_file_in_tmp_folder(examples.static_rst))\n", - "displacement = dpf.operators.result.displacement(data_sources=ds)\n", - "norm = dpf.operators.math.norm(displacement)\n", - "new_operator.inputs.connect(norm)\n", - "\n", - "\n", - "print(\"first quartile is\", new_operator.outputs.first_quartile())\n", - "print(\"median is\", new_operator.outputs.median())\n", - "print(\"third quartile is\", new_operator.outputs.third_quartile())\n", - "print(\"variance is\", new_operator.outputs.variance())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.12" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/examples/01-package_python_operators.ipynb b/examples/01-package_python_operators.ipynb index b8c5080e..a2c6bea3 100644 --- a/examples/01-package_python_operators.ipynb +++ b/examples/01-package_python_operators.ipynb @@ -1,335 +1,129 @@ { - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [], - "source": [ - "%matplotlib inline" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n", - "# Write user defined Operators as a package\n", - "This example shows how more complex DPF python plugins of Operators can be created as standard python packages.\n", - "The benefits of writing packages instead of simple scripts are: componentization (split the code in several\n", - "python modules or files), distribution (with packages, standard python tools can be used to upload and\n", - "download packages) and documentation (READMEs, docs, tests and examples can be added to the package).\n", - "\n", - "This plugin will hold 2 different Operators:\n", - " - One returning all the scoping ids having data higher than the average\n", - " - One returning all the scoping ids having data lower than the average\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Write Operator\n", - "For this more advanced use case, a python package is created.\n", - "Each Operator implementation derives from `ansys.dpf.core.custom_operator.CustomOperatorBase`\n", - "and a call to `ansys.dpf.core.custom_operator.record_operator` records the Operators of the plugin.\n", - "The complete package looks like:\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ + "cells": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[1m average_filter_plugin\n", - "\u001b[1m __init__.py:\n", - " \u001b[0m\n", - "from average_filter_plugin.operators_loader import load_operators\n", - "\n", - "\n", - "\n", - "\u001b[1m operators.py:\n", - " \u001b[0m\n", - "from ansys.dpf.core.custom_operator import CustomOperatorBase\n", - "from ansys.dpf.core.operator_specification import CustomSpecification, PinSpecification, SpecificationProperties\n", - "from ansys.dpf import core as dpf\n", - "from average_filter_plugin import common\n", - "\n", - "\n", - "class IdsWithDataHigherThanAverage(CustomOperatorBase):\n", - " def run(self):\n", - " field = self.get_input(0, dpf.Field)\n", - " average = common.compute_average_of_field(field)\n", - " ids_in = field.scoping.ids\n", - " data_in = field.data\n", - " out = []\n", - " for i, d in enumerate(data_in):\n", - " if d >= average:\n", - " out.append(ids_in[i])\n", - " scoping_out = dpf.Scoping(ids=out, location=field.scoping.location)\n", - " self.set_output(0, scoping_out)\n", - " self.set_succeeded()\n", - "\n", - " @property\n", - " def specification(self):\n", - " spec = CustomSpecification(\"Creates a scoping with all the ids having data higher or equal \"\n", - " \"to the average value of the scalar field's data in input.\")\n", - " spec.inputs = {\n", - " 0: PinSpecification(\"field\", type_names=dpf.Field, document=\"scalar Field.\"),\n", - " }\n", - " spec.outputs = {\n", - " 0: PinSpecification(\"scoping\", type_names=dpf.Scoping),\n", - " }\n", - " spec.properties = SpecificationProperties(user_name=\"ids with data higher than average\", category=\"logic\")\n", - " return spec\n", - "\n", - " @property\n", - " def name(self):\n", - " return \"ids_with_data_higher_than_average\"\n", - "\n", - "\n", - "class IdsWithDataLowerThanAverage(CustomOperatorBase):\n", - " def run(self):\n", - " field = self.get_input(0, dpf.Field)\n", - " average = common.compute_average_of_field(field)\n", - " ids_in = field.scoping.ids\n", - " data_in = field.data\n", - " out = []\n", - " for i, d in enumerate(data_in):\n", - " if d <= average:\n", - " out.append(ids_in[i])\n", - " scoping_out = dpf.Scoping(ids=out, location=field.scoping.location)\n", - " self.set_output(0, scoping_out)\n", - " self.set_succeeded()\n", - "\n", - " @property\n", - " def specification(self):\n", - " spec = CustomSpecification(\"Creates a scoping with all the ids having data lower or equal \"\n", - " \"to the average value of the scalar field's data in input.\")\n", - " spec.inputs = {\n", - " 0: PinSpecification(\"field\", type_names=dpf.Field, document=\"scalar Field.\"),\n", - " }\n", - " spec.outputs = {\n", - " 0: PinSpecification(\"scoping\", type_names=dpf.Scoping),\n", - " }\n", - " spec.properties = SpecificationProperties(user_name=\"ids with data lower than average\", category=\"logic\")\n", - " return spec\n", - "\n", - " @property\n", - " def name(self):\n", - " return \"ids_with_data_lower_than_average\"\n", - "\n", - "\n", - "\n", - "\u001b[1m operators_loader.py:\n", - " \u001b[0m\n", - "from average_filter_plugin import operators\n", - "from ansys.dpf.core.custom_operator import record_operator\n", - "\n", - "\n", - "def load_operators(*args):\n", - " record_operator(operators.IdsWithDataHigherThanAverage, *args)\n", - " record_operator(operators.IdsWithDataLowerThanAverage, *args)\n", - "\n", - "\n", - "\n", - "\u001b[1m common.py:\n", - " \u001b[0m\n", - "import numpy\n", - "\n", - "\n", - "def compute_average_of_field(field):\n", - " return numpy.average(field.data)\n", - "\n" - ] - } - ], - "source": [ - "import IPython\n", - "import os\n", - "print('\\033[1m average_filter_plugin')\n", - "print('\\033[1m __init__.py:\\n \\033[0m')\n", - "print(IPython.display.Code(os.path.join(os.getcwd(),\"../plugins/average_filter_plugin/__init__.py\")))\n", - "print('\\n\\n\\033[1m operators.py:\\n \\033[0m')\n", - "print(IPython.display.Code(os.path.join(os.getcwd(),\"../plugins/average_filter_plugin/operators.py\")))\n", - "print('\\n\\n\\033[1m operators_loader.py:\\n \\033[0m')\n", - "print(IPython.display.Code(os.path.join(os.getcwd(),\"../plugins/average_filter_plugin/operators_loader.py\")))\n", - "print('\\n\\n\\033[1m common.py:\\n \\033[0m')\n", - "print(IPython.display.Code(os.path.join(os.getcwd(),\"../plugins/average_filter_plugin/common.py\")))\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Load Plugin\n", - "Once a python plugin is written as a package, it can be loaded with the function\n", - ":py:func:`ansys.dpf.core.core.load_library` taking as first argument the path to the directory of the plugin,\n", - "as second argument ``py_`` + any name identifying the plugin,\n", - "and as last argument the function's name exposed in the __init__ file and used to record operators.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [ + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%matplotlib inline" + ] + }, { - "name": "stderr", - "output_type": "stream", - "text": [ - "Established connection to DPF gRPC\n" - ] + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n\n# Write user defined Operators as a package\nThis example shows how more complex DPF python plugins of Operators can be created as standard python packages.\nThe benefits of writing packages instead of simple scripts are: componentization (split the code in several\npython modules or files), distribution (with packages, standard python tools can be used to upload and\ndownload packages) and documentation (READMEs, docs, tests and examples can be added to the package).\n\nThis plugin will hold 2 different Operators:\n - One returning all the scoping ids having data higher than the average\n - One returning all the scoping ids having data lower than the average\n" + ] }, { - "data": { - "text/plain": [ - "'py_average_filter successfully loaded'" + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Write Operator\nFor this more advanced use case, a python package is created.\nEach Operator implementation derives from :class:`ansys.dpf.core.custom_operator.CustomOperatorBase`\nand a call to :py:func:`ansys.dpf.core.custom_operator.record_operator` records the Operators of the plugin.\nThe python package `average_filter_plugin` is downloaded and displayed here:\n\n" ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import os\n", - "from ansys.dpf import core as dpf\n", - "from ansys.dpf.core import examples\n", - "\n", - "tmp = dpf.make_tmp_dir_server()\n", - "dpf.upload_files_in_folder(\n", - " dpf.path_utilities.join(tmp, \"average_filter_plugin\"),\n", - " os.path.join(os.getcwd(),\"../plugins\", \"average_filter_plugin\")\n", - ")\n", - "dpf.load_library(\n", - " os.path.join(dpf.path_utilities.join(tmp, \"average_filter_plugin\")),\n", - " \"py_average_filter\",\n", - " \"load_operators\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Once the Plugin loaded, Operators recorded in the plugin can be used with:\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [], - "source": [ - "new_operator = dpf.Operator(\"ids_with_data_lower_than_average\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To use this new Operator, a workflow computing the norm of the displacement\n", - "is connected to the \"ids_with_data_lower_than_average\" Operator.\n", - "Methods of the class ``ids_with_data_lower_than_average`` are dynamically added thanks to the Operator's\n", - "specification.\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Use the Custom Operator\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [ + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import IPython\nimport os\nfrom ansys.dpf.core import examples\n\nprint('\\033[1m average_filter_plugin')\nfile_list = [\"__init__.py\", \"operators.py\", \"operators_loader.py\", \"common.py\"]\nplugin_folder = None\nGITHUB_SOURCE_URL = \"https://github.com/pyansys/pydpf-core/raw/examples/first_python_plugins/python-plugins/average_filter_plugin\"\n\nfor file in file_list:\n EXAMPLE_FILE = GITHUB_SOURCE_URL + \"/average_filter_plugin/\" + file\n operator_file_path = examples.downloads._retrieve_file(EXAMPLE_FILE, file, \"python-plugins/average_filter_plugin\")\n plugin_folder = os.path.dirname(operator_file_path)\n print(f'\\033[1m {file}:\\n \\033[0m')\n print('\\t\\t\\t'.join(('\\n' + str(IPython.display.Code(operator_file_path)).lstrip()).splitlines(True)))\n print(\"\\n\\n\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load Plugin\nOnce a python plugin is written as a package, it can be loaded with the function\n:py:func:`ansys.dpf.core.core.load_library` taking as first argument the path to the directory of the plugin,\nas second argument ``py_`` + any name identifying the plugin,\nand as last argument the function's name exposed in the __init__ file and used to record operators.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import os\nfrom ansys.dpf import core as dpf\nfrom ansys.dpf.core import examples\n\n\ntmp = dpf.make_tmp_dir_server()\ndpf.upload_files_in_folder(\n dpf.path_utilities.join(tmp, \"average_filter_plugin\"),\n plugin_folder\n)\ndpf.load_library(\n os.path.join(dpf.path_utilities.join(tmp, \"average_filter_plugin\")),\n \"py_average_filter\",\n \"load_operators\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once the Plugin loaded, Operators recorded in the plugin can be used with:\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "new_operator = dpf.Operator(\"ids_with_data_lower_than_average\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To use this new Operator, a workflow computing the norm of the displacement\nis connected to the \"ids_with_data_lower_than_average\" Operator.\nMethods of the class ``ids_with_data_lower_than_average`` are dynamically added thanks to the Operator's\nspecification.\n\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ".. graphviz::\n\n digraph foo {\n graph [pad=\"0.5\", nodesep=\"0.3\", ranksep=\"0.3\"]\n node [shape=box, style=filled, fillcolor=\"#ffcc00\", margin=\"0\"];\n rankdir=LR;\n splines=line;\n ds [label=\"ds\", shape=box, style=filled, fillcolor=cadetblue2];\n ds -> displacement [style=dashed];\n displacement -> norm;\n norm -> ids_with_data_lower_than_average;\n }\n\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Use the Custom Operator\n\n" + ] + }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "scoping in was: DPF Scoping: \n", - " with Nodal location and 81 entities\n", - "\n", - "----------------------------------------------\n", - "scoping out is: DPF Scoping: \n", - " with Nodal location and 35 entities\n", - "\n" - ] + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "ds = dpf.DataSources(dpf.upload_file_in_tmp_folder(examples.static_rst))\ndisplacement = dpf.operators.result.displacement(data_sources=ds)\nnorm = dpf.operators.math.norm(displacement)\nnew_operator.inputs.connect(norm)\n\n\nnew_scoping = new_operator.outputs.scoping()\nprint(\"scoping in was:\", norm.outputs.field().scoping)\nprint(\"----------------------------------------------\")\nprint(\"scoping out is:\", new_scoping)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" } - ], - "source": [ - "ds = dpf.DataSources(dpf.upload_file_in_tmp_folder(examples.static_rst))\n", - "displacement = dpf.operators.result.displacement(data_sources=ds)\n", - "norm = dpf.operators.math.norm(displacement)\n", - "new_operator.inputs.connect(norm)\n", - "\n", - "\n", - "new_scoping = new_operator.outputs.scoping()\n", - "print(\"scoping in was:\", norm.outputs.field().scoping)\n", - "print(\"----------------------------------------------\")\n", - "print(\"scoping out is:\", new_scoping)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.12" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/examples/02-python_operators_with_dependencies.ipynb b/examples/02-python_operators_with_dependencies.ipynb index a9e46bd0..dabd4f61 100644 --- a/examples/02-python_operators_with_dependencies.ipynb +++ b/examples/02-python_operators_with_dependencies.ipynb @@ -1,231 +1,165 @@ { - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [], - "source": [ - "%matplotlib inline" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n", - "# Write user defined Operators having third party dependencies\n", - "This example shows how advanced DPF python plugins of Operators can be created as standard python packages\n", - "and how third party python modules dependencies can be added to the package.\n", - "\n", - "This plugin will hold an Operator which implementation depends on a third party python module named\n", - "`gltf `_. This Operator takes a path, a mesh and 3D vector field in input\n", - "and exports the mesh and the norm of the input field in a gltf file located at the given path.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Write Operator\n", - "For this more advanced use case, a python package is created.\n", - "Each Operator implementation derives from `ansys.dpf.core.custom_operator.CustomOperatorBase`\n", - "and a call to `ansys.dpf.core.custom_operator.record_operator` records the Operators of the plugin.\n", - "See the complete package in:\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Load Plugin\n", - "Once a python plugin is written as a package, it can be loaded with the function\n", - ":py:func:`ansys.dpf.core.core.load_library` taking as first argument the path to the directory of the plugin,\n", - "as second argument ``py_`` + any name identifying the plugin,\n", - "and as last argument the function's name exposed in the ``__init__.py`` file and used to record operators.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [ + "cells": [ { - "name": "stderr", - "output_type": "stream", - "text": [ - "Uploading...: 12057 of 12057 KB 100%|#########################################|\n" - ] + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%matplotlib inline" + ] }, { - "data": { - "text/plain": [ - "'py_dpf_gltf successfully loaded'" + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n\n# Write user defined Operators having third party dependencies\nThis example shows how advanced DPF python plugins of Operators can be created as standard python packages\nand how third party python modules dependencies can be added to the package.\nFor a first introduction on user defined python Operators see example `ref_wrapping_numpy_capabilities`\nand for a simpler example on user defined python Operators as a package see `ref_python_plugin_package`.\n\nThis plugin will hold an Operator which implementation depends on a third party python module named\n`gltf `_. This Operator takes a path, a mesh and 3D vector field in input\nand exports the mesh and the norm of the input field in a gltf file located at the given path.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Write Operator\nFor this more advanced use case, a python package is created.\nEach Operator implementation derives from :class:`ansys.dpf.core.custom_operator.CustomOperatorBase`\nand a call to :py:func:`ansys.dpf.core.custom_operator.record_operator` records the Operators of the plugin.\nThe python package `gltf_plugin` is downloaded and displayed here:\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import IPython\nimport os\nfrom ansys.dpf.core import examples\n\nprint('\\033[1m gltf_plugin')\nfile_list = [\"gltf_plugin/__init__.py\", \"gltf_plugin/operators.py\", \"gltf_plugin/operators_loader.py\",\n \"gltf_plugin/requirements.txt\", \"gltf_plugin/gltf_export.py\", \"gltf_plugin/texture.png\", \"gltf_plugin.xml\"]\nplugin_path = None\nGITHUB_SOURCE_URL = \"https://github.com/pyansys/pydpf-core/raw/examples/first_python_plugins/python-plugins\"\n\nfor file in file_list:\n EXAMPLE_FILE = GITHUB_SOURCE_URL + \"/gltf_plugin/\" + file\n operator_file_path = examples.downloads._retrieve_file(\n EXAMPLE_FILE, file, os.path.join(\"python-plugins\", os.path.dirname(file)))\n\n print(f'\\033[1m {file}\\n \\033[0m')\n if (os.path.splitext(file)[1] == \".py\" or os.path.splitext(file)[1] == \".xml\") and file != \"gltf_plugin/gltf_export.py\":\n print('\\t\\t\\t'.join(('\\n' + str(IPython.display.Code(operator_file_path)).lstrip()).splitlines(True)))\n print(\"\\n\\n\")\n if plugin_path is None:\n plugin_path = os.path.dirname(operator_file_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To add third party modules as dependencies to a custom DPF python plugin, a folder or zip file\nwith the sites of the dependencies needs to be created and referenced in an xml located next to the plugin's folder\nand having the same name as the plugin plus the ``.xml`` extension. The ``site`` python module is used by DPF when\ncalling :py:func:`ansys.dpf.core.core.load_library` function to add these custom sites to the python interpreter path.\nTo create these custom sites, the requirements of the custom plugin should be installed in a python virtual\nenvironment, the site-packages (with unnecessary folders removed) should be zipped and put with the plugin. The\npath to this zip should be referenced in the xml as done above.\n\nTo simplify this step, a requirements file can be added in the plugin, like:\n\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print(f'\\033[1m gltf_plugin/requirements.txt: \\n \\033[0m')\nprint('\\t', IPython.display.Code(os.path.join(plugin_path, \"requirements.txt\")))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And this :download:`powershell script ` for windows or\nthis :download:`shell script ` can be ran with the mandatory arguments:\n\n- -pluginpath : path to the folder of the plugin.\n- -zippath : output zip file name.\n\noptional arguments are:\n\n- -pythonexe : path to a python executable of your choice.\n- -tempfolder : path to a temporary folder to work on, default is the environment variable ``TEMP`` on Windows and /tmp/ on Linux.\n\nFor windows powershell, call::\n\n create_sites_for_python_operators.ps1 -pluginpath /path/to/plugin -zippath /path/to/plugin/assets/winx64.zip\n\nFor linux shell, call::\n\n create_sites_for_python_operators.sh -pluginpath /path/to/plugin -zippath /path/to/plugin/assets/linx64.zip\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import subprocess\n\nif os.name == \"nt\" and not os.path.exists(os.path.join(plugin_path, 'assets', 'gltf_sites_winx64.zip')):\n CMD_FILE_URL = GITHUB_SOURCE_URL + \"/create_sites_for_python_operators.ps1\"\n cmd_file = examples.downloads._retrieve_file(CMD_FILE_URL, \"create_sites_for_python_operators.ps1\",\n \"python-plugins\")\n run_cmd = f\"powershell {cmd_file}\"\n args = f\" -pluginpath \\\"{plugin_path}\\\" -zippath {os.path.join(plugin_path, 'assets', 'gltf_sites_winx64.zip')}\"\n print(run_cmd + args)\n process = subprocess.run(run_cmd + args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n if process.stderr:\n raise RuntimeError(\n \"Installing pygltf in a virtual environment failed with error:\\n\" + process.stderr.decode())\n else:\n print(\"Installing pygltf in a virtual environment succeeded\")\nelif os.name == \"posix\" and not os.path.exists(os.path.join(plugin_path, 'assets', 'gltf_sites_linx64.zip')):\n CMD_FILE_URL = GITHUB_SOURCE_URL + \"/create_sites_for_python_operators.sh\"\n cmd_file = examples.downloads._retrieve_file(CMD_FILE_URL, \"create_sites_for_python_operators.ps1\",\n \"python-plugins\")\n run_cmd = f\"{cmd_file}\"\n args = f\" -pluginpath \\\"{plugin_path}\\\" -zippath \\\"{os.path.join(plugin_path, 'assets', 'gltf_sites_linx64.zip')}\\\"\"\n print(run_cmd + args)\n os.system(f\"chmod u=rwx,o=x {cmd_file}\")\n os.system(run_cmd + args)\n print(\"\\nInstalling pygltf in a virtual environment succeeded\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load Plugin\nOnce a python plugin is written as a package, it can be loaded with the function\n:py:func:`ansys.dpf.core.core.load_library` taking as first argument the path to the directory of the plugin,\nas second argument ``py_`` + any name identifying the plugin,\nand as last argument the function's name exposed in the ``__init__.py`` file and used to record operators.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from ansys.dpf import core as dpf\nfrom ansys.dpf.core import examples\n\ntmp = dpf.make_tmp_dir_server()\ndpf.upload_files_in_folder(\n dpf.path_utilities.join(tmp, \"plugins\", \"gltf_plugin\"),\n plugin_path\n)\ndpf.upload_file(\n plugin_path + \".xml\",\n dpf.path_utilities.join(tmp, \"plugins\", \"gltf_plugin.xml\")\n)\n\ndpf.load_library(\n dpf.path_utilities.join(tmp, \"plugins\", \"gltf_plugin\"),\n \"py_dpf_gltf\",\n \"load_operators\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once the Plugin loaded, Operators recorded in the plugin can be used with:\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "new_operator = dpf.Operator(\"gltf_export\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This new Operator ``gltf_export`` requires a triangle surface mesh, a displacement Field on this surface mesh\nas well as an export path as inputs.\nTo demo this new Operator, a :class:`ansys.dpf.core.model.Model` on a simple file is created,\n:class:`ansys.dpf.core.operators.mesh.tri_mesh_skin` Operator is used to extract the surface of the mesh in triangles\nelements.\n\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Use the Custom Operator\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import os\n\nmodel = dpf.Model(dpf.upload_file_in_tmp_folder(examples.static_rst))\n\nmesh = model.metadata.meshed_region\nskin_mesh = dpf.operators.mesh.tri_mesh_skin(mesh=mesh)\n\ndisplacement = model.results.displacement()\ndisplacement.inputs.mesh_scoping(skin_mesh)\ndisplacement.inputs.mesh(skin_mesh)\nnew_operator.inputs.path(os.path.join(tmp, \"out\"))\nnew_operator.inputs.mesh(skin_mesh)\nnew_operator.inputs.field(displacement.outputs.fields_container()[0])\nnew_operator.run()\n\nprint(\"operator ran successfully\")\n\ndpf.download_file(os.path.join(tmp, \"out.glb\"), os.path.join(os.getcwd(), \"out.glb\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The gltf Operator output can be downloaded :download:`here `.\n\n" ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import os\n", - "from ansys.dpf import core as dpf\n", - "from ansys.dpf.core import examples\n", - "\n", - "tmp = dpf.make_tmp_dir_server()\n", - "dpf.upload_files_in_folder(\n", - " dpf.path_utilities.join(tmp, \"plugins\", \"gltf_plugin\"),\n", - " dpf.path_utilities.join(os.getcwd(), \"..\", \"plugins\", \"gltf_plugin\")\n", - ")\n", - "dpf.upload_file(\n", - " dpf.path_utilities.join(os.getcwd(), \"..\", \"plugins\", \"gltf_plugin.xml\"),\n", - " dpf.path_utilities.join(tmp, \"plugins\", \"gltf_plugin.xml\")\n", - ")\n", - "\n", - "dpf.load_library(\n", - " dpf.path_utilities.join(tmp, \"plugins\", \"gltf_plugin\"),\n", - " \"py_dpf_gltf\",\n", - " \"load_operators\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Once the Plugin loaded, Operators recorded in the plugin can be used with:\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [], - "source": [ - "new_operator = dpf.Operator(\"gltf_export\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This new Operator ``gltf_export`` requires a triangle surface mesh, a displacement Field on this surface mesh\n", - "as well as an export path as inputs.\n", - "To demo this new Operator, a `ansys.dpf.core.model.Model` on a simple file is created,\n", - "`ansys.dpf.core.operators.mesh.tri_mesh_skin` Operator is used to extract the surface of the mesh in triangles\n", - "elements.\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Use the Custom Operator\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "operator ran successfully\n" - ] } - ], - "source": [ - "import tempfile\n", - "import os\n", - "\n", - "model = dpf.Model(dpf.upload_file_in_tmp_folder(examples.static_rst))\n", - "\n", - "mesh = model.metadata.meshed_region\n", - "skin_mesh = dpf.operators.mesh.tri_mesh_skin(mesh=mesh)\n", - "\n", - "displacement = model.results.displacement()\n", - "displacement.inputs.mesh_scoping(skin_mesh)\n", - "displacement.inputs.mesh(skin_mesh)\n", - "new_operator.inputs.path(os.path.join(tmp, \"out\"))\n", - "new_operator.inputs.mesh(skin_mesh)\n", - "new_operator.inputs.field(displacement.outputs.fields_container()[0])\n", - "new_operator.run()\n", - "\n", - "print(\"operator ran successfully\")" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Downloading...: 2 of 2 KB 100%|###############################################|\n" - ] + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" } - ], - "source": [ - "dpf.download_file(os.path.join(tmp, \"out.glb\"), os.path.join(os.getcwd(), \"out.glb\"))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.12" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file