Architecture¶
Super_Hydro is a client-server application, with the superfluid dynamics calculations being done on the server, and the display/interactivity provided by the client. The client and server can run on the same machined, but the system is designed to allow the server to be run on a high-performance machine, i.e. with GPU acceleration.
For stability, we use ØMQ via the pyzmq library to implement the base level communication layer and define a simple protocol.
General Structure¶
The application is divided into three logical components:
- Computation Server
This is the high-performance python application that does the actually superfluid evolution. It generally will run on a fairly high-performance computer with a GPU accelerator, but could be launched by the client if needed. The computation server provides an interface through which clients can interact with it, requesting quantities like the density for display, and setting parameters in response to users. Multiple clients can connect to the computation server, possibly controlling different aspects of the simulation.
Clients will interact with the computation server through the
IServerinterface. This is provided by the following classes:super_hydro.server.Server: Direct interface to a running computation server. This can be used by python clients that launch a computation server themselves.super_hydro.communication.NetworkServer: Provides a network interface through which python clients can communicate with a running server vi TCP/IP.
- Client
The client provides a front-end for the user, displaying the evolution, and providing controls through which the user can interact with the computation server.
Programming API¶
The code is structured into three logical units, each defined in a separate submodule.
Interfaces are defined in super_hydro.interfaces.
super_hydro.physicsThis modules contains all of the physics. Components here define a
Modelclass that implements thesupero_hydro.interfaces.IModelinterface defining a particular physics problem. All of the detailed computations are described in these files.super_hydro.serverThis is the computation server. It will run the computation engine, and listen for connection requests from clients who want to interact or view the simulation. The server should generally be run on a fairly high-performance computer – ideally equipped with an NVIDIA GPU.
super_hydro.clientsThis is the client. Clients are generally designed to be lightweight in both their dependencies and performance characteristics so that clients can be installed and run on as many platforms as possible, including mobile devices. A more heavyweight client might be designed that offloads some of the calculations to improve UI performance. For example, updating tracer-particle positions so that they move smoothly even if background frame updates are slow due to network issues. I would really like to make a web-client, but have not yet found a suitable framework.
Configuration¶
Configuration takes place in several layers. From lowest to highest precedence, we have:
Defaults hard-coded into the program: These are coded in the
paramsdictionaries of the physics models. See for examplephysics/gpe.py::GPEBase.Environmental variables:
Configuration files:
Command-line options: E.g.:
conda activate super_hydro python bin/server --model=gpe.BECBreather --Nx=256 Ny=256
The ConfigArgParse library seems to provide this behavior out of the box, so we start with this. A couple of comments:
Options with a long name
--optioncan be set in a config file with the same name.Options without
-or--are positional.Sections are not used (by design) but can be included as comments in the config files.
Configuration options are specified in the super_hydro/config.py file which reads these from various config files (~/.config/super_hydro.conf or ./super_hydro.conf preferred), environment variables, or command-line arguments.
An alternative strategy suggested by Von
Welch combines
configparser for reading the
config files with argparse for
processing the command line.
To configure the communication layer, the server must have an IP address (URL) specifying where it is located, and a port on which it listens for connections. These are specified in the application config file.
Communication¶
The main communication pathway is between the server, which runs the computation, and the client, which provides the visualization and interaction. This communication will generally take place across a network (using ØMQ) which allows the server to run on high-performance hardware, but to simplify development and debugging, we also provide a direction python connection so that a seperate server need not be run.
Communication between the client and server is driven by the clients through a request-reply pattern where the client requests actions of or data from the server. This interface is defined through the following methods of the server:
do(msg):get(msg):set(msg, obj):get_array(msg): Sending arrays requires a bit more work, so we separate these methods.set_array(msg, array):do_*: Simple request for the server to do something such as start, stop, pause, resume, etc. No arguments are passed other than the byte-string such asb'do_start'.set_*: Set a variable on the server. This allows the client to set a parameter of the server such as the cooling, position of the external potential, etc. The message to the server contains two parts: first the byte-string such asb'set_cooling'is sent. Once the server responds, the client sends the data in a JSON encoding.set_array_*:get_*: Get a variable from the server. The message to the server contains two parts: first the byte-string such asb'set_cooling'is sent. Once the server responds, the client sends the data in a JSON encoding.
One special sentinel value is used: string starting with b'Error: ' which will
indicate that the command failed.
To simplify communication we use ØMQ via the
pyzmq library to implement the base level
communication layer and define a simple protocol through Client and Server classes
in super_hydro/communication.py.
Currently we used the
Request-reply pattern where
the client drives all communication through a send()/recv() pair and the server
responds with a recv()/send() pair. We implement the following transactions,
initiated by the client:
(Unless otherwise specified, all data discussed must be
bytes objects (ascii strings)
which can be sent without encoding.)
Request: The client requests something from the server by sending a message. This could be a request for simple information or to perform an action such as Pause. The server responds with a simple message.
Client->Server:
bytesServer->Client:
bytes
# Python eg client.send(msg) response = client.recv()
Get: The client requests for some encoded data to be returned.
Client->Server:
bytesServer->Client: (
bytes,data)
client.send(msg) result = client.recv_json()
Send: The client sends encoded data to the server.
client.send(msg) response = client.recv_json() client.send_json(data) response = client.recv_json()
GetArray: The client requests an array. For efficiency we provide a custom encoding for arrays:
client.send(msg) result = client.recv_array()
SendArray: The client sends an array to the server. For efficiency we provide a custom encoding for arrays:
client.send(msg) response = client.recv() client.send_array(data)) response = client.recv()
Note: all messages should be byte objects, not strings (i.e. msg=b"Window.size" not msg="Window.size").
Data (objs) are serialized using send_json and recv_json except for NumPy arrays which have custom serializations as suggested in the pyzmq docs.
Currently the code employs the zmq.REQ (Server) and zmq.REP (Client) socket types for which each message requires a send and receive. This is simply described here but may not allow multiple clients to connect to the server. (It definitely allows a client to connect with multiple servers, but this is not the pattern we want.)
Logging¶
Logging is currently done through the logging module through the Logger class in super_hydro/utils.py. Two mechanisms are provided in the code:
Output a message at a specified logging level (default is INFO):
log(msg, level=logging.INFO)
Frame a task with messages at the start and end with a specified logging level:
with log_task(msg, level=logging.INFO): ...
For the clients, we might want to use the logging facilities of the front end.
Synchronization¶
Here we discuss some synchronization strategies:
Client Driven: This is how the current version of the code works. The server advances the simulation by the specified number of
stepsevery time the client asks the server toget(b"Density"). This is done usingkivy.clock.Clock.schedule_interval. The advantage of this is that the clients (who do the visualization) never miss a frame. The disadvantage is that the simulation is slowed down since it must work in lock-step with the client, communication, etc.Server Driven: In this case, the server might run as fast as possible, and the clients just update when they can.
Potential¶
User Defined Models¶
Overview¶
The Flask-based Web Client (FWC) supports loading and visualizing User Defined Models
(UDMs) through the configuration option -f or --file followed by the absolute
filepath for the UDM Python script.
Example: -f \home\foo\bar\file.py
When the FWC is started, it will load the UDM and store both the file path and all classes (including inherited classes) as individual physics models to display.
For this reason, it is recommended to minimize the use of class inheritance where possible.
Structure¶
The computational backend of super_hydro explorer requires a few particular methods and initialized parameters to properly load and use a UDM.
Parameters¶
paramsdictThe following are required
paramsdict keys that need to be minimally defined for the computational server to appropriately read/operate the model.Nx, Ny: Integer values sizing the output array
Nxy: Integer 2-tuple of (Nx, Ny)
xy: 2-tuple of 1-D arrays, each of length Nx, Ny respectively
min, max: Integers
data: array of shape (Nx, Ny) for storing output data array values
pot_z: Complex value of form
x + yjfor value of external (finger) potentialfinger_x, finger_y: x,y position values of external potential, between 0 and 1
Lxy: (Nx, Ny)-shaped array for storing tracer positions (if used)
sliderslistNested list, each element sublist containing parameters for sliders/toggle boxes to provide User interaction elements.
Sub-list element definitions:
0: Slider name/id (ex:
Cooling)1: Class (
slider,toggle)2: Scale (
None,logarithmic)3: Type (
range,checkbox)4: Minimum range value (Integer)
5: Maximum range value (Integer)
6: Slider step size
Class Methods¶
get_density: Gets the density (display) arrayget: Get the value of the requested parameterset: Set the value of the requested parameterstep: Increment the calculation by one time step
References¶
Which is the best way to allow configuration options be overridden at the command line in Python?
https://github.com/lebedov/msgpack-numpy