Remote Procedure Calls

Remote Procedure Calls (RPC) is a mechanism to transparently execute code in a remote process or computer. This is especially useful in scenarios where COMPAS runs inside an IronPython host (eg. Rhino) and needs to execute code that only runs on CPython (eg. code that requires numpy).

Basic Usage

The basic usage of RPC is simple. For example, consider a CPython script using the Force Density method.

>>> from compas.numerical import fd_numpy
>>> result = fd_numpy(...)

This script does not work in Rhino or GH because Numpy is not available. Therefore, instead of importing and calling the function directly, we call it through a proxy object.

>>> from compas.rpc import Proxy
>>> numerical = Proxy('compas.numerical')
>>> result = numerical.fd_numpy(...)

To use functions from more than one package in the same script, simply change the package attribute of the proxy object, which determines where the proxy will look for the requested function.

>>> from compas.rpc import Proxy
>>> proxy = Proxy()

>>> proxy.package = 'compas.numerical'
>>> proxy.fd_numpy(...)

>>> proxy.package = 'compas.geometry'
>>> proxy.bestfit_plane_numpy(...)

The use of compas.rpc is not restricted to COMPAS packages only.

>>> from compas.rpc import Proxy
>>> linalg = Proxy('scipy.linalg')
>>> x = linalg.solve(A, b)

Note that Numpy arrays are automatically converted to lists.

Configuration Options

The compas.rpc.Proxy object has several configuration options. We will discuss only a few of those here. For a complete overview, please refer to the API docs (compas.rpc).

python

The compas.rpc.Proxy object will automatically try to reconnect to an active instance of the command server, or start a new one if no active server can be found. By default, a newly started server will run an instance of the default Python interpreter of the active environment, for example, when running RPC from the command line; or of the Python interpreter specified in compas_bootstrapper, for example, when running RPC from Rhino.

In some cases, this might not be what you want, or might not result in the expected behaviour, for example when compas_bootstrapper does not exist.

To use a specific Python iterpreter, you can specify the path to an executable through the python parameter.

>>> from compas.rpc import Proxy
>>> proxy = Proxy(python=r"C:\\Users\\<username>\\anaconda3\\envs\\research\\python.exe")

path

Sometimes you will want the server to run custom functions that are not (yet) part of a specific package. To allow the server to find such functions, you can specify an additional search path.

For example, if you have a Python script on your desktop, defining a wrapper for the k-means clustering algorithm of scikit-learn, you can tell the command server where to find it using the path parameter.

# C:\Users\<username>\Desktop\clustering.py

from sklearn.cluster import KMeans
from numpy import array


def cluster(points, n_clusters):
    kmeans = KMeans(n_clusters=n_clusters, n_init=2000, max_iter=1000).fit(array(cloud, dtype=float))
    clusters = {}
    for label, point in zip(kmeans.labels_, cloud):
        if label not in clusters:
            clusters[label] = []
        clusters[label].append(point)
    return clusters
>>> from compas.geometry import Pointcloud
>>> from compas.rpc import Proxy
>>> cloud = Pointcloud.from_bounds(10, 5, 3, 100)
>>> proxy = Proxy(package='clustering', path=r'C:\\Users\\<username>\\Desktop')
>>> clusters = proxy.cluster(cloud, 10)

Supported data types

compas.rpc uses JSON serialization to transfer data between the “client” (your script) and the server running the selected CPython environment.

All COMPAS objects (primitives, shapes, data structures, etc.) support JSON serialization through their to_json from_json methods. On a lower level, these methods convert (complex) internal data to simple dictionaries, and vice versa, with to_data and from_data.

In combination with custom JSON encoders and decoders this allows for COMPAS objects to be serialized and de-serialized without loss of information on either side of the RPC communication network.

Therefore the data types supported by compas.rpc include all native Python data types and COMPAS objects. Numpy arrays are automatically converted to lists.

Starting and Stopping

Once a server is started it will keep running “as long as possible”. There are many reasons to stop and (re)start the server during its lifetime. For example, to load functionality from a different conda environment, or to load changes that were made to the packages in the environment after it was started. This happens frequently while a package is still under active development.

Stopping and starting the server is easy.

>>> from compas.rpc import Proxy
>>> proxy = Proxy()
>>> proxy.stop_server()
>>> proxy.start_server()

To restart the server after every call, you can use a context manager. When used in this way, RPC behaves much like its predecessor XFunc.

>>> with Proxy('compas.numerical') as numerical:
...     numerical.fd_numpy(...)
...

Starting an RPC server manually

Proxy will try to start an RPC server automatically if no server is already running, but very often it is recommended to start it manually from the command-line.

To start a new RPC server use the following command on the terminal (default port is 1753):

$ compas_rpc start [--port PORT]

Conversely, to stop an existing RPC server:

$ compas_rpc stop [--port PORT]

Note

If COMPAS is installed in a virtual environment, make sure it is activated before trying to use this command-line utility.

Note

Currently, the RPC server is launched on the localhost. However, it would also be possible to launch it on a remote computer on a network, or on a server reachable over the internet.