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.
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.
The basic design of the controller is as follows:
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.
The Controller object has a notion of Blocking and Non-Blocking mode.
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 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.
To add interfaces to a plain solver (not created using Application), the following steps need to be taken:
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)
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.
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
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.
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])
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()