AMSPipe protocol specification
******************************

The AMSPipe protocol is a remote procedure call protocol used for
communication between two processes on the same computer. One of these
processes is the "pipe master", submitting geometries and requests for
calculations and receiving the results. The other process is the "pipe
worker", which listens for calls from the master, executes the
requested calculations and returns their results.

Communication takes place over a pair of pipes, a "call pipe" (master
to worker) and a "reply pipe" (worker to master). Each pipe carries a
sequence of messages, which are processed in order. Each message is an
associative object (like a JSON object or a Python dict) containing a
single item. The key of this item denotes the name of the message,
while the value of this item is another object containing the payload.

Each message sent over the call pipe constitutes a method call. The
name of the message is equal to the name of the method being called,
while the payload object contains any arguments. For methods without
arguments, the payload is an empty object.

Once the worker receives a call message, it will execute the specified
method and potentially write a sequence of zero or more messages to
the reply pipe, containing the results of the calculation. After the
execution of the method is completed, the worker will write a "return"
message to the reply pipe, denoting the success or failure of the
method call.

Any method whose name starts with "Set" doesn't send a "return"
message. Instead, errors encountered during the execution of a Set
method are buffered by the worker to be returned the next time a
"return" message is generated. While an error is buffered, any further
method calls are ignored by the worker and are not executed. Once a
non-Set call is seen by the worker, such a call will also be discarded
without being executed, but a "return" message will be immediately
generated with the buffered error data. The buffered error will then
be cleared and normal processing of calls resumes.

As a special case, the "Exit" method may be called at any time and
will be immediately executed by the worker, terminating the protocol
session. The "Exit" method never returns a "return" message,
discarding any potentially buffered error.

All values are always in atomic units: Hartree for energies, Bohr for
distances, Hartree/Bohr for gradients, etc.


Low-level message encoding
==========================

All messages are encoded using Universal Binary JSON. AMSPipe
implementations MAY encode messages using the UBJSON optimized
container format (with explicit length and/or strong typing).
Implementations MUST support decoding UBJSON containers whether or not
they're written using optimized format.

UBJSON arrays in messages MUST NOT contain other arrays or objects.
Multi-dimensional arrays MUST be flattened on encoding (and
unflattened on decoding as necessary). Flattening SHOULD preserve the
original order of elements in memory. All arrays MUST be accompanied
by an additional integer array holding the original dimensions before
flattening. The name of this auxiliary array is equal to the name of
the main array with a "_dim_" suffix. The dimensions are written in
Fortran (column-major) order, so that the first value in the "_dim_"
array corresponds to the index that changes the fastest when iterating
over consecutive elements of the flattened array.

Arrays also MUST NOT contain elements of incompatible types. The
elements of an array MUST be either all integers, all real numbers,
all Boolean, or all strings. UBJSON type "char" is equivalent to a
UBJSON "string" of length 1 (elements of type "char" and "string" MAY
thus be mixed in an array). An empty array is equivalent to an array
that is not present at all.

When sending messages over stream transport mechanisms (such as UNIX
pipes), each message MUST be prefixed with a 32-bit native endian
integer length.


Return messages and error handling
==================================

The "return" message consists of the following fields:

* "status" (integer): Zero for success or one of the error codes
  listed below.

* "method" (string, optional): Name of the method in which the error
  occurred.

* "argument" (string, optional): Name of the argument in error.

* "message" (string, optional): Human-readable error message.

The "status" field can have one of the following values:

0: success
   No error, method executed successfully.

1: decode_error
   Message could not be decoded correctly (invalid UBJSON encoding or
   a violation of one of the constraints above).

2: logic_error
   Programmer error, such as methods called in an incorrect sequence
   or with an invalid worker state.

3: runtime_error
   An error occurred during the execution of a method (outside of the
   AMSPipe protocol).

4: unknown_version
   (Only returned from "Hello".) Requested protocol version is not
   supported by the worker.

5: unknown_method
   A method of the requested name is not supported by the worker.

6: unknown_argument
   The argument in "argument" is not known to the worker and couldn't
   be processed. If multiple arguments in a single call are unknown,
   "argument" will be set to the one at the lowest nesting depth (if
   an argument contains nested objects). If multiple fields at the
   same nesting depth are unknown, the first one in ASCII sort order
   will be returned.

7: invalid_argument
   An argument doesn't have the correct type or dimensions, or it has
   an invalid value.


Methods
=======


Hello(version)
--------------

* "version" (integer):

Attempt to activate a given version of the AMSPipe protocol. Only
version 1 is defined at the moment.

Other methods (with the exception of "Hello" and "Exit") MUST NOT be
called until a Hello has completed successfully. Pipe masters SHOULD
attempt a Hello with the highest supported protocol version and
iterate downwards if an unknown_version error is returned. Once a
Hello has succeeded, it MUST NOT be called again during the lifetime
of a pipe session.


Exit()
------

Terminate the worker and disconnect both pipes. This method never
returns.

The worker MAY also discard any remembered calculations.


SetCoords(coords)
-----------------

* "coords" (real(3,:)):

Replace the Cartesian coordinates in the current chemical system. The
number of atoms must match.


SetLattice(vectors)
-------------------

* "vectors" (real(:,:)):

Replace the lattice matrix of the current chemical system. If
"vectors" is absent or of dimensions (0,0), make the system non-
periodic.


SetSystem(atomSymbols, coords, totalCharge)
-------------------------------------------

* "atomSymbols" (string(:)):

* "coords" (real(3,:)):

* "totalCharge" (real):

Define a new chemical system.


Solve(request, keepResults, prevTitle)
--------------------------------------

* "request" (object):
     * "title" (string): Unique string key identifying this
       calculation.

     * "quiet" (bool): If true, the worker SHOULD keep any standard
       output from the calculation to a minimum.

     * "gradients" (bool): Calculate gradients on atoms.

     * "stressTensor" (bool): Calculate the stress tensor.

     * "elasticTensor" (bool):

     * "hessian" (bool):

     * "dipoleMoment" (bool):

     * "dipoleGradients" (bool):

* "keepResults" (bool, default false): Remember worker state for
  future restart.

* "prevTitle" (string, optional): Title of a previously stored
  calculation to restart from.

Run a single point calculation on the current chemical system and
return a "results" object if successful. If the calculation fails with
a runtime error, a "results" object MAY still be returned (possibly
with just some of the requested properties).

All Boolean fields in "request" default to false if not present. All
non-Boolean fields in "request" except for "title" are optional and
their default values are worker-dependent. The master SHOULD NOT
explicitly set any Boolean fields in "request" to False. The worker
MAY raise an unknown argument error if an unknown Boolean is set to
False. The worker SHOULD raise an unknown argument error as usual if
an unknown Boolean is set to True or if an unknown non-Boolean is set.
Workers SHOULD raise these errors before performing any time-consuming
calculations so that the master can efficiently retry the call.

If "keepResults" is not specified or set to false, the worker will
discard all data from the calculation after returning a "results"
object. If "keepResults" is set to true, the worker will remember any
internal state related to the calculation. This internal state can
later be reused for restart by passing the "title" of the stored
calculation as "prevTitle". The pipe master SHOULD call DeleteResults
to discard the stored state as soon as it is no longer needed.

A "results" object consists of the following fields. Workers MAY
include additional fields not listed here. A master MUST NOT signal an
error due to any fields it does not expect or understand.

* "results" (object):
     * "messages" (string(:)): Runtime error or warning messages
       generated by the calculation.

     * "energy" (real):

     * "gradients" (real(3,:)):

     * "stressTensor" (real(:,:)):

     * "elasticTensor" (real(:,:)):

     * "hessian" (real(:,:)):

     * "dipoleMoment" (real(:,:)):

     * "dipoleGradients" (real(:,:)):

     * "charges" (real(:)):


DeleteResults(title)
--------------------

* "title" (string): Title of a previously remembered calculation.

Discard any worker state corresponding to a previously remembered
calculation.


Forward and backward compatibility considerations
-------------------------------------------------

The AMSPipe protocol is designed to support combining new masters with
old workers and vice versa. Major, incompatible changes in the
protocol will be handled by increasing the protocol version number.
Negotiating a suitable protocol version is then handled by call(s) to
Hello at the beginning of a pipe session.

The following requirements are in place to ensure that the protocol
stays extensible within a single protocol version:

* Workers MUST raise an unknown_method error on any call to a method
  they don't implement. If the method name starts with Set, such an
  error will be buffered to be returned later. If the method name
  doesn't start with Set, a "return" message will be generated
  immediately.

* Workers MUST raise unknown_argument errors any time they encounter
  an argument to a known method that they don't know how to handle.
  The master SHOULD then retry the call without such an argument or
  choose an alternative sequence of calls if possible.

* The master SHOULD ignore unexpected messages of unsupported type on
  the reply pipe.

Namely, the following changes are permitted without increasing the
protocol version number:

* Adding new methods to the protocol.

* Adding new optional arguments to existing methods.

* Adding new fields to returned objects.

* Adding new reply message types.
