# Evaluator Client

The `EvaluatorClient` object is responsible for both submitting requests to estimate a data set of properties to a running Evaluator Server instance, and for pulling back the results of those requests when complete.

An `EvaluatorClient` object may optionally be created using a set of `ConnectionOptions` which specifies the network address of the running Evaluator Server instance to connect to:

    # Specify the address of a server running on the local machine.
    connection_options = ConnectionOptions(server_address="localhost", server_port=8000)
    # Create the client object
    evaluator_client = EvaluatorClient(connection_options)

## Requesting Estimates

The client can request the estimation of a data set of properties using the `request_estimate()` function:

    # Specify the data set.
    data_set = PhysicalPropertyDataSet()
    data_set.add_properties(...)

    # Specify the force field source.
    force_field = SmirnoffForceFieldSource.from_path("openff-1.0.0.offxml")

    # Specify some estimation options (optional).
    options = client.default_request_options(data_set, force_field)

    # Specify the parameters to differentiate with respect to (optional).
    gradient_keys = [
        ParameterGradientKey(tag="vdW", smirks="[#6X4:1]", attribute="epsilon")
    ]

    # Request the estimation of the data set.
    request, errors = evaluator_client.request_estimate(
        data_set,
        force_field,
        options,
        gradient_keys
    )

A request must at minimum specify:

- the data set of physical properties to estimate.

- the force field parameters to estimate the data set using.

and may also optionally specify:

- the options to use when estimating the property set.

- the parameters to differentiate each physical property estimate with respect to.

Note

Gradients can currently only be computed for requests using a SMIRNOFF based force field.

The `request_estimate()` function returns back two objects:

- a `Request` object which can be used to retrieve the results of the request and,

- an `EvaluatorException` object which will be populated if any errors occured while submitting the request.

The `Request` object is similar to a `Future` object, in that it is an object which can be used to query the current status of a request either asynchronously:

    results = request.results(synchronous=False)

or synchronously:

    results = request.results(synchronous=True)

The results (which may currently be incomplete) are returned back as a `RequestResult` object.

The `Request` object is fully JSON serializable:

    # Save the request to JSON
    request.json(file_path="request.json", format=True)
    # Load the request from JSON
    request = Request.from_json(file_path="request.json")

making it easy to keep track of any open requests.

## Request Options

The `RequestOptions` object allows greater control over how properties are estimated by the server. It currently allows control over:

- `calculation_layers`: The calculation layers which the server should attempt to use when estimating the data set. The order which the layers are specified in this list is the order which the server will attempt to use each layer.

- `calculation_schemas`: The calculation schemas to use for each allowed calculation layer per class of property. These will be automatically populated in the cases where no user specified schema is provided, and where a default schema has been registered with the plugin system for the particular layer and property type.

If no options are passed to `request_estimate()` a default set will be generated through a call to `default_request_options()`. For more information about how default calculation schemas are registered, see the Default Schemas section.

## Force Field Sources

Different force field representations (e.g. `SMIRNOFF`, `TLeap`, `LigParGen`) are defined within the framework as `ForceFieldSource` objects. A force field source should specify all of the options which would be required by a particular force field, such as the non-bonded cutoff or the charge scheme if not specified directly in the force field itself.

Currently the framework has built in support for force fields applied via:

- the OpenFF toolkit (`SmirnoffForceFieldSource`).

- the `tleap` program from the AmberTools suite (`LigParGenForceFieldSource`).

- an instance of the LigParGen server (`LigParGenForceFieldSource`).

The client will automatically adapt any of the built-in calculation schemas which are based off of the `WorkflowCalculationSchema` to use the correct workflow protocol (`BuildSmirnoffSystem`, `BuildTLeapSystem` or `BuildLigParGenSystem`) for the requested force field.