Skip to content

Commit c6e7b74

Browse files
authored
reorg about for Sphinx (#4)
1 parent f6ee33d commit c6e7b74

29 files changed

+1095
-943
lines changed

README.rst

Lines changed: 17 additions & 613 deletions
Large diffs are not rendered by default.

doc/make_pdf.bat

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
make.bat latex
2+
pdflatex.exe build/latex/pyansys_sphinx_theme.tex
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
Application Interface Abstraction
2+
=================================
3+
Many Ansys applications are designed around user interaction within a
4+
desktop GUI-based environment. Consequently, scripts are recorded
5+
directly from user sessions and are in the context of manipulating the
6+
desktop application. Instead, scripts should be written for an API
7+
that is structured around data represented as classes and methods.
8+
9+
PyAnsys seeks to make the API a "first class citizen" in regard to
10+
interacting with an Ansys product by presenting the product as a
11+
stateful data model. Consider the following comparison between using a
12+
recorded script from AEDT versus using the PyAEDT libary to create an
13+
open region in the active editor:
14+
15+
+------------------------------------------------------+----------------------------------------------+
16+
| Using AEDT with MS COM Methods | Using AEDT with the `PyAEDT`_ Library |
17+
+------------------------------------------------------+----------------------------------------------+
18+
| .. code:: python | .. code:: python |
19+
| | |
20+
| import sys | from pyaedt import Hfss |
21+
| import pythoncom | |
22+
| import win32com.client | hfss = Hfss() |
23+
| | hfss.create_open_region(frequency="1GHz") |
24+
| # initialize the desktop using pythoncom | |
25+
| Module = sys.modules['__main__'] | |
26+
| oDesktop = Module.oDesktop | |
27+
| oProject = oDesktop.SetActiveProject("Project1") | |
28+
| oDesign = oProject.SetActiveDesign("HFSSDesign1") | |
29+
| oEditor = oDesign.SetActiveEditor("3D Modeler") | |
30+
| oModule = oDesign.GetModule("BoundarySetup") | |
31+
| | |
32+
| # create an open region | |
33+
| parm = [ | |
34+
| "NAME:Settings", | |
35+
| "OpFreq:=", "1GHz", | |
36+
| "Boundary:=", "Radition", | |
37+
| "ApplyInfiniteGP:=", False | |
38+
| ] | |
39+
| oModule.CreateOpenRegion(parm) | |
40+
+------------------------------------------------------+----------------------------------------------+
41+
42+
Besides length and readability, the biggest difference between the two
43+
approaches is how the methods and attributes from the ``Hfss`` class
44+
are encapsulated. For example, AEDT no longer needs to be
45+
explicitly instantiated and is hidden as a protected attribute
46+
``_desktop``. The connection to the application takes place
47+
automatically when ``Hfss`` is instantiated, and the active AEDT
48+
project, editor, and module are automatically used to create the
49+
open region.
50+
51+
Furthermore, the ``create_open_region`` method within ``Hfss``
52+
contains a full Python documentation string with keyword arguments,
53+
clear ``numpydoc`` parameters and returns, and a basic example.
54+
These are unavailable when directly using COM methods, preventing
55+
the use of contextual help from within a Python IDE.
56+
57+
The source of the ``hfss.py`` method within`PyAEDT`_ follows.
58+
Note how calls to the COM object are all encapsulated
59+
within this method.
60+
61+
.. code:: python
62+
63+
def create_open_region(self, frequency="1GHz", boundary="Radiation",
64+
apply_infinite_gp=False, gp_axis="-z"):
65+
"""Create an open region on the active editor.
66+
67+
Parameters
68+
----------
69+
frequency : str, optional
70+
Frequency with units. The default is ``"1GHz"``.
71+
boundary : str, optional
72+
Type of the boundary. The default is ``"Radiation"``.
73+
apply_infinite_gp : bool, optional
74+
Whether to apply an infinite ground plane. The default is ``False``.
75+
gp_axis : str, optional
76+
The default is ``"-z"``.
77+
78+
Returns
79+
-------
80+
bool
81+
``True`` when successful, ``False`` when failed.
82+
83+
Examples
84+
--------
85+
Create an open region in the active editor at 1 GHz.
86+
87+
>>> hfss.create_open_region(frequency="1GHz")
88+
89+
"""
90+
vars = [
91+
"NAME:Settings",
92+
"OpFreq:=", frequency,
93+
"Boundary:=", boundary,
94+
"ApplyInfiniteGP:=", apply_infinite_gp
95+
]
96+
if apply_infinite_gp:
97+
vars.append("Direction:=")
98+
vars.append(gp_axis)
99+
100+
self._omodelsetup.CreateOpenRegion(vars)
101+
return True
102+
103+
Here, the COM ``CreateOpenRegion`` method is abstracted, encapsulating
104+
the model setup object. There's no reason why a user needs direct
105+
access to ``_omodelsetup``, which is why it's protected in the
106+
``Hfss`` class. Additionally, calling the method is simplified by
107+
providing (and documenting) the defaults using keyword arguments and
108+
placing them into the ``vars`` list, all while following the `Style
109+
Guide for Python Code (PEP8)`_.
110+
111+
.. _PyAEDT: https://github.com/pyansys/PyAEDT
112+
.. _Style Guide for Python Code (PEP8): https://www.python.org/dev/peps/pep-0008
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
Data Transfer and Representation
2+
================================
3+
When transferring data from a local application or remote service, you
4+
do not want to return raw JSON, gRPC classes, Python lists, or, even worse,
5+
strings. A best practice is to represent arrays as ``numpy.ndarray`` or
6+
``pandas.DataFrame`` objects.
7+
8+
This example generates a simple mesh in MAPDL:
9+
10+
.. code:: python
11+
12+
>>> mapdl.prep7()
13+
>>> mapdl.block(0, 1, 0, 1, 0, 1)
14+
>>> mapdl.et(1, 186)
15+
>>> mapdl.vmesh('ALL')
16+
17+
At this point, the only two ways within MAPDL to access the nodes and
18+
connectivity of the mesh are either to print it using the ``NLIST``
19+
command or to write it to disk using the ``CDWRITE`` command. Both
20+
methods are remarkably inefficient, requiring:
21+
22+
- Serializing the data to ASCII on the server
23+
- Transfering the data
24+
- Deserializing the data within Python
25+
- Converting the data to an array
26+
27+
This example prints node coordinates using the ``NLIST`` command:
28+
29+
.. code:: python
30+
31+
>>> print(mapdl.nlist())
32+
NODE X Y Z
33+
1 0.0000 1.0000 0.0000
34+
2 0.0000 0.0000 0.0000
35+
3 0.0000 0.75000 0.0000
36+
37+
It's more efficient to transfer the node array as either a
38+
series of repeated ``Node`` messages or, better yet, to serialize
39+
the entire array into bytes and then deserialize it on the client
40+
side. For a concrete and standalone example of this in C++ and Python,
41+
see `grpc_chunk_stream_demo`_.
42+
43+
While raw byte streams are vastly more efficient, one major disadvantage
44+
is that the structure of the data is lost when serializing the array.
45+
This should be considered when deciding how to write your API.
46+
47+
Regardless of the serialization or message format, users will
48+
expect Python native types (or a common type for a common library like
49+
``pandas.DataFrame`` or ``numpy.ndarray``). Here, within `PyMAPDL`_,
50+
the nodes of the mesh are accessible as the ``nodes`` attribute within
51+
the ``mesh`` attribute, which provides an encapsulation of the mesh
52+
within the MAPDL database.
53+
54+
.. code:: python
55+
56+
>>> mapdl.mesh.nodes
57+
array([[0. , 1. , 0. ],
58+
[0. , 0. , 0. ],
59+
[0. , 0.75, 0. ],
60+
...
61+
[0.5 , 0.5 , 0.75],
62+
[0.5 , 0.75, 0.5 ],
63+
[0.75, 0.5 , 0.5 ]])
64+
65+
66+
67+
.. _gRPC: https://grpc.io/
68+
.. _pythoncom: http://timgolden.me.uk/pywin32-docs/pythoncom.html
69+
.. _SWIG: http://www.swig.org/
70+
.. _C extensions: https://docs.python.org/3/extending/extending.html
71+
.. _Anaconda Distribution: https://www.anaconda.com/products/individual
72+
.. _REST: https://en.wikipedia.org/wiki/Representational_state_transfer
73+
.. _PyAEDT: https://github.com/pyansys/PyAEDT
74+
.. _PyMAPDL: https://github.com/pyansys/pymapdl
75+
.. _pymapdl: https://github.com/pyansys/pymapdl
76+
.. _Style Guide for Python Code (PEP8): https://www.python.org/dev/peps/pep-0008
77+
.. _grpc_chunk_stream_demo: https://github.com/pyansys/grpc_chunk_stream_demo
78+
.. _numpydoc: https://numpydoc.readthedocs.io/en/latest/format.html
79+
.. _Namespace Packages: https://packaging.python.org/guides/packaging-namespace-packages/

doc/source/abstraction/index.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Abstraction and Encapsulation
2+
#############################
3+
Abstraction in Python is the process of hiding the real implementation
4+
of an application from the user and emphasizing only usage.
5+
6+
One of the main objectives of PyAnsys libraries is to wrap (encapsulate)
7+
data and methods within units of execution while hiding data or parameters
8+
in protected variables. The topics in this section demonstrate how
9+
applications and complex services expose functionalities that matter to
10+
users and hide all else. For example, because background details,
11+
implementation, and hidden states do not need to be exposed to users,
12+
they are hidden.
13+
14+
.. toctree::
15+
:hidden:
16+
:maxdepth: 3
17+
18+
app_interface_abstraction
19+
service_abstraction
20+
data_transfer_and_representation
21+
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
Service Abstraction
2+
===================
3+
Some Ansys products are exposed as services that permit remote
4+
execution using technologies like `REST`_ or `gRPC`_. These services
5+
are typically exposed in a manner where the API has already been
6+
abstracted because not all methods can be exposed through a remote API.
7+
Here, the abstraction of the service is as crucial as in the case of
8+
the "desktop API". In this case, remote API calls should be identical
9+
if the service is local or remote, with the only difference being that local
10+
calls are faster to execute.
11+
12+
Consider the following code examples. The left-hand side shows the
13+
amount of work to start, establish a connection to, and submit an
14+
input file to MAPDL using auto-generated gRPC interface files. For
15+
more information, see `pyansys-protos-generator
16+
<https://github.com/pyansys/pyansys-protos-generator>`_. The
17+
right-hand side shows the same workflow but uses the `PyMAPDL`_ library.
18+
19+
+----------------------------------------------------------+--------------------------------------------+
20+
| Using the gRPC Auto-generated Interface | Using the `PyMAPDL`_ Library |
21+
+==========================================================+============================================+
22+
| .. code:: python | .. code:: python |
23+
| | |
24+
| import grpc | from ansys.mapdl import core as pymapdl |
25+
| | |
26+
| from ansys.mapdl import mapdl_pb2 as pb_types | # start mapdl and read the input file |
27+
| from ansys.mapdl import mapdl_pb2_grpc as mapdl_grpc | mapdl = pymapdl.launch_mapdl() |
28+
| from ansys.mapdl import kernel_pb2 as anskernel | output = mapdl.input('ds.dat') |
29+
| from ansys.client.launcher.client import Launcher | |
30+
| | |
31+
| # start MAPDL | |
32+
| sm = Launcher() | |
33+
| job = sm.create_job_by_name("mapdl-212") | |
34+
| service_name = f"grpc-{job.name}" | |
35+
| mapdl_service = sm.get_service(name=service_name) | |
36+
| | |
37+
| # create a gRPC channel | |
38+
| channel_str = '%s:%d' % (mapdl_service.host, | |
39+
| mapdl_service.port) | |
40+
| channel = grpc.insecure_channel(channel_str) | |
41+
| stub = mapdl_grpc.MapdlServiceStub(channel) | |
42+
| | |
43+
| # send an input file request | |
44+
| request = pb_types.InputRequest(filename='ds.dat') | |
45+
| response = stub.InputFileS(request) | |
46+
| # additional postprocessing to parse response | |
47+
| | |
48+
+----------------------------------------------------------+--------------------------------------------+
49+
50+
The approach on the right has a number of advantages, including:
51+
52+
- Readability due to the abstraction of the service startup
53+
- Short package names
54+
- Simplified interface for starting MAPDL
55+
- Full documentation strings for all classes, methods, and functions
56+
57+
To properly abstract a service, users must have the option to
58+
either launch the service and connect to it locally if the software exists on
59+
their machines or connect to a remote instance of the service. One
60+
way to do this is to include a function to launch the service.
61+
62+
This example includes ``launch_mapdl``, which brokers a connection via the
63+
``Mapdl`` class:
64+
65+
.. code:: python
66+
67+
>>> from ansys.mapdl.core import Mapdl
68+
>>> mapdl = Mapdl(ip=<IP Address>, port=<Port>)
69+
>>> print(mapdl)
70+
Product: Ansys Mechanical Enterprise
71+
MAPDL Version: 21.2
72+
ansys.mapdl Version: 0.59.dev0
73+
74+
This straightforward approach connects to a local or remote instance
75+
of MAPDL via gRPC by instantiating an instance of the ``Mapdl``. class.
76+
At this point, because the assumption is that MAPDL is always remote, it's
77+
possible to issue commands to MAPDL, including those requiring
78+
file transfer like ``Mapdl.input``.
79+
80+
.. _REST: https://en.wikipedia.org/wiki/Representational_state_transfer
81+
.. _gRPC: https://grpc.io/
82+
.. _PyMAPDL: https://github.com/pyansys/pymapdl

0 commit comments

Comments
 (0)