Solver Interfaces

Interfaces are a way to control, gather data and execute commands on a running solver instance. This can be useful for example to pause/continue the solver, get the iteration count, get/set the dt or final time or simply to monitor the running of the solver.

CommandManager

The CommandManager class provides functionality to control the solver in a restricted way so that adding multiple interfaces to the solver is possible in a simple way.

The figure Overview of the Solver Interfaces shows an overview of the classes and objects involved in adding an interface to the solver.

../_images/controller.png

Overview of the Solver Interfaces

The basic design of the controller is as follows:

  1. Solver has a method set_command_handler() takes a callable and a command_interval, and calls the callable with self as an argument every command_interval iterations
  2. The method CommandManager.execute_commands() of CommandManager object is set as the command_handler for the solver. Now CommandManager can do any operation on the solver
  3. Interfaces are added to the CommandManager by the CommandManager.add_interface() method, which takes a callable (Interface) as an argument and calls the callable in a separate thread with a new Controller instance as an argument
  4. A Controller instance is a proxy for the CommandManager which redirects its methods to call CommandManager.dispatch() on the CommandManager, which is synchronized in the CommandManager class so that only one thread (Interface) can call it at a time. The CommandManager queues the commands and sends them to all procs in a parallel run and executes them when the solver calls its execute_commands() method
  5. Writing a new Interface is simply writing a function/method which calls appropriate methods on the Controller instance passed to it.

Controller

The Controller class is a convenience class which has various methods which redirect to the Controller.dispatch() method to do the actual work of queuing the commands. This method is synchronized so that multiple controllers can operate in a thread-safe manner. It also restricts the operations which are possible on the solver through various interfaces. This enables adding multiple interfaces to the solver convenient and safe. Each interface gets a separate Controller instance so that the various interfaces are isolated.

Blocking and Non-Blocking mode

The Controller object has a notion of Blocking and Non-Blocking mode.

  • In Blocking mode operations wait until the command is actually executed on the solver and then return the result. This means execution stops until the execute_commands method of the CommandManager is executed by the solver, which is after every commmand_interval iterations. This mode is the default.
  • In Non-Blocking mode the Controller queues the command for execution and returns a task_id of the command. The result of the command can then be obtained anytime later by the get_result method of the Controller passing the task_id as argument. The get_result call blocks until result can be obtained.

Switching between modes

The blocking/non-blocking modes can be get/set using the methods Controller.get_blocking() and Controller.set_blocking() methods

NOTE : The blocking/non-blocking mode is not for getting/setting solver properties. These methods always return immediately, even if the setter is actually executed only when the CommandManager.execute_commands() function is called by the solver.

Interfaces

Interfaces are functions which are called in a separate thread and receive a Controller instance so that they can query the solver, get/set various properties and execute commands on the solver in a safe manner.

Here’s the example of a simple interface which simply prints out the iteration count every second to monitor the solver

import time

def simple_interface(controller):
    while True:
        print controller.get_count()
        time.sleep(1)

You can use dir(controller) to find out what methods are available on the controller instance.

A few simple interfaces are implemented in the solver_interfaces module, namely CommandlineInterface, XMLRPCInterface and MultiprocessingInterface, and also in examples/controller_elliptical_drop_client.py. You can check the code to see how to implement various kinds of interfaces.

Adding Interface to Solver

To add interfaces to a plain solver (not created using Application), the following steps need to be taken:

  • Set CommandManager for the solver (it is not setup by default)
  • Add the interface to the CommandManager

The following code demonstrates how the the Simple Interface created above can be added to a solver:

# add CommandManager to solver
command_manager = CommandManager(solver)
solver.set_command_handler(command_manager.execute_commands)

# add the interface
command_manager.add_interface(simple_interface)

For code which uses Application, you simply need to add the interface to the application’s command_manager:

app = Application()
app.set_solver(s)
...
app.command_manager.add_interface(simple_interface)

Commandline Interface

The CommandLine interface enables you to control the solver from the commandline even as it is running. Here’s a sample session of the command-line interface from the controller_elliptical_drop.py example:

$ python controller_elliptical_drop.py
pysph[0]>>>
Invalid command
Valid commands are:
    p | pause
    c | cont
    g | get <name>
    s | set <name> <value>
    q | quit -- quit commandline interface (solver keeps running)
pysph[9]>>> g dt
1e-05
pysph[64]>>> g tf
0.1
pysph[114]>>> s tf 0.01
None
pysph[141]>>> g tf
0.01
pysph[159]>>> get_particle_array_names
['fluid']

The number inside the square brackets indicates the iteration count.

Note that not all operations can be performed using the command-line interface, notably those which use complex python objects.

XML-RPC Interface

The XMLRPCInterface interface exports the controller object’s methods over an XML-RPC interface. An example html file controller_elliptical_drop_client.html uses this XML-RPC interface to control the solver from a web page.

The following code snippet shows the use of XML-RPC interface, which is not much different from any other interface, as they all export the interface of the Controller object:

import xmlrpclib

# address is a tuple of hostname, port, ex. ('localhost',8900)
client = xmlrpclib.ServerProxy(address, allow_none=True)

# client has all the methods of the controller
print client.system.listMethods()

print client.get_t()
print client.get('count')

The XML-RPC interface also implements a simple http server which serves html, javascript and image files from the directory it is started from. This enables direct use of the file controller_elliptical_drop_client.html to get an html interface without the need of a dedicated http server.

The figure PySPH html client using XML-RPC interface shows a screenshot of the html client in action

../_images/html_client.png

PySPH html client using XML-RPC interface

One limitation of XML-RPC interface is that arbitrary python objects cannot be sent across. XML-RPC standard predefines a limited set of types which can be transferred.

Multiprocessing Interface

The MultiprocessingInterface interface also exports the controller object similar to the XML-RPC interface, but it is more featured, can use authentication keys and can send arbitrary picklable objects. Usage of Multiprocessing client is also similar to the XML-RPC client:

from pysph.solver.solver_interfaces import MultiprocessingClient

# address is a tuple of hostname, port, ex. ('localhost',8900)
# authkey is authentication key set on server, defaults to 'pysph'
client = MultiprocessingClient(address, authkey)

# controller proxy
controller = client.controller

pa_names = controller.get_particle_array_names()

# arbitrary python objects can be transferred (ParticleArray)
pa = controller.get_named_particle_array(pa_names[0])

Example

Here’s an example (straight from controller_elliptical_drop_client.py) put together to show how the controller can be used to create useful interfaces for the solver. The code below plots the particle positions as a scatter map with color-mapped velocities, and updates the plot every second while maintaining user interactivity:

from pysph.solver.solver_interfaces import MultiprocessingClient

client = MultiprocessingClient(address, authkey)
controller = client.controller

pa_name = controller.get_particle_array_names()[0]
pa = controller.get_named_particle_array(pa_name)

#plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
line = ax.scatter(pa.x, pa.y, c=numpy.hypot(pa.u,pa.v))

global t
t = time.time()
def update():
    global t
    t2 = time.time()
    dt = t2 - t
    t = t2
    print 'count:', controller.get_count(), '\ttimer time:', dt,
    pa = controller.get_named_particle_array(pa_name)

    line.set_offsets(zip(pa.x, pa.y))
    line.set_array(numpy.hypot(pa.u,pa.v))
    fig.canvas.draw()

    print '\tresult & draw time:', time.time()-t

    return True

update()
gobject.timeout_add_seconds(1, update)
plt.show()