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. .. py:currentmodule:: pysph.solver.controller CommandManager -------------- The :py:class:`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 :ref:`image_controller` shows an overview of the classes and objects involved in adding an interface to the solver. .. _image_controller: .. figure:: images/controller.png :align: center :width: 900 Overview of the Solver Interfaces The basic design of the controller is as follows: #. :py:class:`~pysph.solver.solver.Solver` has a method :py:meth:`~pysph.solver.solver.Solver.set_command_handler` takes a callable and a command_interval, and calls the callable with self as an argument every `command_interval` iterations #. The method :meth:`CommandManager.execute_commands` of `CommandManager` object is set as the command_handler for the solver. Now `CommandManager` can do any operation on the solver #. Interfaces are added to the `CommandManager` by the :meth:`CommandManager.add_interface` method, which takes a callable (Interface) as an argument and calls the callable in a separate thread with a new :class:`Controller` instance as an argument #. A `Controller` instance is a proxy for the `CommandManager` which redirects its methods to call :meth:`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 :meth:`execute_commands` method #. Writing a new Interface is simply writing a function/method which calls appropriate methods on the :class:`Controller` instance passed to it. Controller ---------- The :py:class:`Controller` class is a convenience class which has various methods which redirect to the :py:meth:`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 :py:class:`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 :py:class:`CommandManager` is executed by the solver, which is after every :py:attr:`~pysph.solver.solver.Solver.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 :py:meth:`Controller.get_blocking` and :py:meth:`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 :py:meth:`CommandManager.execute_commands` function is called by the solver. .. py:currentmodule:: pysph.solver.solver_interfaces Interfaces ---------- Interfaces are functions which are called in a separate thread and receive a :py:class:`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 .. _simple_interface: :: 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 :py:mod:`~pysph.solver.solver_interfaces` module, namely :py:class:`CommandlineInterface`, :py:class:`XMLRPCInterface` and :py:class:`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 :py:class:`~pysph.solver.application.Application`), the following steps need to be taken: - Set :py:class:`~pysph.solver.controller.CommandManager` for the solver (it is not setup by default) - Add the interface to the CommandManager The following code demonstrates how the the :ref:`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 :py:class:`~pysph.solver.application.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 :py:class:`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 s | set 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 :py:class:`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 :ref:`fig_html_client` shows a screenshot of the html client in action .. _fig_html_client: .. figure:: images/html_client.png :align: center 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 :py:class:`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()