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 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:
Solverhas 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
- 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
- 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
Controllerinstance as an argument
- 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
- Writing a new Interface is simply writing a function/method which calls
appropriate methods on the
Controllerinstance passed to it.
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¶
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
CommandManageris executed by the solver, which is after every
commmand_intervaliterations. 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 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
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
A few simple interfaces are implemented in the
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
the following steps need to be taken:
CommandManagerfor 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
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 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>>> 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>>> g dt 1e-05 pysph>>> g tf 0.1 pysph>>> s tf 0.01 None pysph>>> g tf 0.01 pysph>>> 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.
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 figure PySPH html client using XML-RPC interface shows a screenshot of the html client in action
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.
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)
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() 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()