<br />

|---|
| [![](https://www.tensorflow.org/images/GitHub-Mark-32px.png) View source on GitHub](https://github.com/tensorflow/quantum/blob/v0.7.6/tensorflow_quantum/python/layers/circuit_executors/sampled_expectation.py#L30-L342) |

A layer that calculates a sampled expectation value.

    tfq.layers.SampledExpectation(
        backend='noiseless', differentiator=None, **kwargs
    )

### Used in the notebooks

| Used in the tutorials |
|---|
| - [Calculate gradients](https://www.tensorflow.org/quantum/tutorials/gradients) - [Noise](https://www.tensorflow.org/quantum/tutorials/noise) |

Given an input circuit and set of parameter values, output expectation
values of observables computed using measurement results sampled from
the input circuit.

First define a simple helper function for generating a parametrized
quantum circuit that we will use throughout:

    def _gen_single_bit_rotation_problem(bit, symbols):
        """Generate a toy problem on 1 qubit."""
        starting_state = [0.123, 0.456, 0.789]
        circuit = cirq.Circuit(
            cirq.rx(starting_state[0])(bit),
            cirq.ry(starting_state[1])(bit),
            cirq.rz(starting_state[2])(bit),
            cirq.rz(symbols[2])(bit),
            cirq.ry(symbols[1])(bit),
            cirq.rx(symbols[0])(bit)
        )
        return circuit

In quantum machine learning there are two very common use cases that
align with keras layer constructs. The first is where the circuits
represent the input data points:

    bit = cirq.GridQubit(0, 0)
    symbols = sympy.symbols('x y z')
    ops = [-1.0 * cirq.Z(bit), cirq.X(bit) + 2.0 * cirq.Z(bit)]
    num_samples = [100, 200]
    circuit_list = [
        _gen_single_bit_rotation_problem(bit, symbols),
        cirq.Circuit(
            cirq.Z(bit) ** symbols[0],
            cirq.X(bit) ** symbols[1],
            cirq.Z(bit) ** symbols[2]
        ),
        cirq.Circuit(
            cirq.X(bit) ** symbols[0],
            cirq.Z(bit) ** symbols[1],
            cirq.X(bit) ** symbols[2]
        )
    ]
    sampled_expectation_layer = tfq.layers.SampledExpectation()
    output = sampled_expectation_layer(
        circuit_list,
        symbol_names=symbols,
        operators=ops,
        repetitions=num_samples)
    # Here output[i][j] corresponds to the sampled expectation
    # of all the ops in ops w.r.t circuits[i] where Keras managed
    # variables are placed in the symbols 'x', 'y', 'z'.
    tf.shape(output)
    tf.Tensor([3 2], shape=(2,), dtype=int32)

Here, different [`cirq.Circuit`](https://quantumai.google/reference/python/cirq/Circuit) instances sharing the common symbols 'x',
'y' and 'z' are used as input. Keras uses the `symbol_names`
argument to map Keras managed variables to these circuits constructed
with `sympy.Symbol`s. The shape of `num_samples` is equal to that of `ops`.

The second most common use case is where there is a fixed circuit and
the expectation operators vary:

    bit = cirq.GridQubit(0, 0)
    symbols = sympy.symbols('x, y, z')
    ops = [-1.0 * cirq.Z(bit), cirq.X(bit) + 2.0 * cirq.Z(bit)]
    fixed_circuit = _gen_single_bit_rotation_problem(bit, symbols)
    expectation_layer = tfq.layers.SampledExpectation()
    output = expectation_layer(
        fixed_circuit,
        symbol_names=symbols,
        operators=ops,
        repetitions=5000,
        initializer=tf.keras.initializers.RandomUniform(0, 2 * np.pi))
    # Here output[i][j] corresponds to
    # the sampled expectation of operators[i][j] using 5000 samples w.r.t
    # the circuit where variable values are managed by keras and store
    # numbers in the symbols 'x', 'y', 'z'.
    tf.shape(output)
    tf.Tensor([1 2], shape=(2,), dtype=int32)

Here different [`cirq.PauliSum`](https://quantumai.google/reference/python/cirq/PauliSum) or [`cirq.PauliString`](https://quantumai.google/reference/python/cirq/PauliString) instances can be
used as input to calculate the expectation on the fixed circuit that
the layer was initially constructed with.

There are also some more complex use cases that provide greater flexibility.
Notably these configurations all make use of the `symbol_values` parameter
that causes the `SampledExpectation` layer to stop managing the
`sympy.Symbol`s in the quantum circuits and instead requires the user to
supply inputs themselves. Lets look at the case where there
is a single fixed circuit, some fixed operators and symbols that must be
common to all circuits:

    bit = cirq.GridQubit(0, 0)
    symbols = sympy.symbols('x y z')
    ops = [cirq.Z(bit), cirq.X(bit)]
    num_samples = [100, 200]
    circuit = _gen_single_bit_rotation_problem(bit, symbols)
    values = [[1,1,1], [2,2,2], [3,3,3]]
    sampled_expectation_layer = tfq.layers.SampledExpectation()
    output = sampled_expectation_layer(
        circuit,
        symbol_names=symbols,
        symbol_values=values,
        operators=ops,
        repetitions=num_samples)
    # output[i][j] = The sampled expectation of ops[j] with
    # values_tensor[i] placed into the symbols of the circuit
    # with the order specified by feed_in_params.
    # so output[1][2] = The sampled expectation of a circuit with parameter
    # values [2,2,2] w.r.t Pauli X, estimated using 200 samples per term.
    output  # Non-deterministic result. It can vary every time.
    tf.Tensor(
    [[0.52, 0.72],
     [0.34, 1.  ],
     [0.78, 0.48]], shape=(3, 2), dtype=float32)

> [!TIP]
> **Tip:** you can compare the above result with that of `Expectation`: tf.Tensor( \[\[0.63005245 0.76338404\] \[0.25707167 0.9632684 \] \[0.79086655 0.5441111 \]\], shape=(3, 2), dtype=float32)

Here is a simple model that uses this particular input signature of
[`tfq.layers.SampledExpectation`](https://www.tensorflow.org/quantum/api_docs/python/tfq/layers/SampledExpectation), that learns to undo the random rotation
of the qubit:

    bit = cirq.GridQubit(0, 0)
    symbols = sympy.symbols('x, y, z')
    circuit = _gen_single_bit_rotation_problem(bit, symbols)
    control_input = tf.keras.Input(shape=(1,))
    circuit_inputs = tf.keras.Input(shape=(), dtype=tf.dtypes.string)
    d1 = tf.keras.layers.Dense(10)(control_input)
    d2 = tf.keras.layers.Dense(3)(d1)
    expectation = tfq.layers.SampledExpectation()(
        circuit_inputs, # See note below!
        symbol_names=symbols,
        symbol_values=d2,
        operators=cirq.Z(bit),
        repetitions=5000)
    data_in = np.array([[1], [0]], dtype=np.float32)
    data_out = np.array([[1], [-1]], dtype=np.float32)
    model = tf.keras.Model(
        inputs=[circuit_inputs, control_input], outputs=expectation)
    model.compile(
        optimizer=tf.keras.optimizers.Adam(learning_rate=0.01),
        loss=tf.keras.losses.mean_squared_error)
    history = model.fit(
        x=[tfq.convert_to_tensor([circuit] * 2), data_in],
        y=data_out,
        epochs=100)

For an example featuring this layer, please check out `Taking gradients`
in our dev website <http://www.tensorflow.org/quantum/tutorials>

Lastly `symbol_values`, `operators` and circuit `inputs` can all be fed
Python `list` objects. In addition to this they can also be fed [`tf.Tensor`](https://www.tensorflow.org/api_docs/python/tf/Tensor)
inputs, meaning that you can input all of these things from other Tensor
objects (like `tf.keras.Dense` layer outputs or [`tf.keras.Input`](https://www.tensorflow.org/api_docs/python/tf/keras/Input)s etc).

> [!NOTE]
> **Note:** When specifying a new layer for a *compiled* [`tf.keras.Model`](https://www.tensorflow.org/api_docs/python/tf/keras/Model) using something like `tfq.layers.SampledExpectation()(cirq.Circuit(...), ...)` please be sure to instead use `tfq.layers.SampledExpectation()(circuit_input, ...)` where `circuit_input` is a [`tf.keras.Input`](https://www.tensorflow.org/api_docs/python/tf/keras/Input) that is filled with `tfq.conver_to_tensor([cirq.Circuit(..)] * batch_size)` at runtime. This is because compiled Keras models require non keyword layer `call` inputs to be traceable back to a [`tf.keras.Input`](https://www.tensorflow.org/api_docs/python/tf/keras/Input).

<br />

<br />

<br />

| ## Args ||
|---|---|
| `backend` | Optional Backend to use to simulate states. Can be either {'noiseless', 'noisy'} users may also specify a preconfigured [`cirq.Sampler`](https://quantumai.google/reference/python/cirq/Sampler) object to use instead. |
| `differentiator` | Optional Differentiator to use to calculate analytic derivative values of given operators_to_measure and circuit, which must inherit [`tfq.differentiators.Differentiator`](https://www.tensorflow.org/quantum/api_docs/python/tfq/differentiators/Differentiator). Defaults to `parameter_shift.ParameterShift()` (None argument). |

<br />

<br />

<br />

<br />

| ## Attributes ||
|---|---|
| `compute_dtype` | The dtype of the computations performed by the layer. |
| `dtype` | Alias of `layer.variable_dtype`. |
| `dtype_policy` | <br /> <br /> |
| `input` | Retrieves the input tensor(s) of a symbolic operation. <br /> Only returns the tensor(s) corresponding to the *first time* the operation was called. |
| `input_dtype` | The dtype layer inputs should be converted to. |
| `input_spec` | <br /> <br /> |
| `losses` | List of scalar losses from `add_loss`, regularizers and sublayers. |
| `metrics` | List of all metrics. |
| `metrics_variables` | List of all metric variables. |
| `non_trainable_variables` | List of all non-trainable layer state. <br /> This extends `layer.non_trainable_weights` to include all state used by the layer including state for metrics and `SeedGenerator`s. |
| `non_trainable_weights` | List of all non-trainable weight variables of the layer. <br /> These are the weights that should not be updated by the optimizer during training. Unlike, `layer.non_trainable_variables` this excludes metric state and random seeds. |
| `output` | Retrieves the output tensor(s) of a layer. <br /> Only returns the tensor(s) corresponding to the *first time* the operation was called. |
| `path` | The path of the layer. <br /> If the layer has not been built yet, it will be `None`. |
| `quantization_mode` | The quantization mode of this layer, `None` if not quantized. |
| `supports_masking` | Whether this layer supports computing a mask using `compute_mask`. |
| `trainable` | Settable boolean, whether this layer should be trainable or not. |
| `trainable_variables` | List of all trainable layer state. <br /> This is equivalent to `layer.trainable_weights`. |
| `trainable_weights` | List of all trainable weight variables of the layer. <br /> These are the weights that get updated by the optimizer during training. |
| `variable_dtype` | The dtype of the state (weights) of the layer. |
| `variables` | List of all layer state, including random seeds. <br /> This extends `layer.weights` to include all state used by the layer including `SeedGenerator`s. Note that metrics variables are not included here, use `metrics_variables` to visit all the metric variables. |
| `weights` | List of all weight variables of the layer. <br /> Unlike, `layer.variables` this excludes metric state and random seeds. |

<br />

## Methods

### `add_loss`

    add_loss(
        loss
    )

Can be called inside of the `call()` method to add a scalar loss.

#### Example:

    class MyLayer(Layer):
        ...
        def call(self, x):
            self.add_loss(ops.sum(x))
            return x

### `add_metric`

    add_metric(
        *args, **kwargs
    )

### `add_variable`

    add_variable(
        shape,
        initializer,
        dtype=None,
        trainable=True,
        autocast=True,
        regularizer=None,
        constraint=None,
        name=None
    )

Add a weight variable to the layer.

Alias of `add_weight()`.

### `add_weight`

    add_weight(
        *args,
        shape=None,
        initializer=None,
        dtype=None,
        trainable=True,
        autocast=True,
        regularizer=None,
        constraint=None,
        aggregation='none',
        overwrite_with_gradient=False,
        name=None
    )

Add a weight variable to the layer.

<br />

<br />

<br />

| Args ||
|---|---|
| `shape` | Shape tuple for the variable. Must be fully-defined (no `None` entries). Defaults to `()` (scalar) if unspecified. |
| `initializer` | Initializer object to use to populate the initial variable value, or string name of a built-in initializer (e.g. `"random_normal"`). If unspecified, defaults to `"glorot_uniform"` for floating-point variables and to `"zeros"` for all other types (e.g. int, bool). |
| `dtype` | Dtype of the variable to create, e.g. `"float32"`. If unspecified, defaults to the layer's variable dtype (which itself defaults to `"float32"` if unspecified). |
| `trainable` | Boolean, whether the variable should be trainable via backprop or whether its updates are managed manually. Defaults to `True`. |
| `autocast` | Boolean, whether to autocast layers variables when accessing them. Defaults to `True`. |
| `regularizer` | Regularizer object to call to apply penalty on the weight. These penalties are summed into the loss function during optimization. Defaults to `None`. |
| `constraint` | Contrainst object to call on the variable after any optimizer update, or string name of a built-in constraint. Defaults to `None`. |
| `aggregation` | Optional string, one of `None`, `"none"`, `"mean"`, `"sum"` or `"only_first_replica"`. Annotates the variable with the type of multi-replica aggregation to be used for this variable when writing custom data parallel training loops. Defaults to `"none"`. |
| `overwrite_with_gradient` | Boolean, whether to overwrite the variable with the computed gradient. This is useful for float8 training. Defaults to `False`. |
| `name` | String name of the variable. Useful for debugging purposes. |

<br />

### `build`

    build(
        input_shape
    )

### `build_from_config`

    build_from_config(
        config
    )

Builds the layer's states with the supplied config dict.

By default, this method calls the `build(config["input_shape"])` method,
which creates weights based on the layer's input shape in the supplied
config. If your config contains other information needed to load the
layer's state, you should override this method.

<br />

<br />

<br />

| Args ||
|---|---|
| `config` | Dict containing the input shape associated with this layer. |

<br />

### `call`

[View source](https://github.com/tensorflow/quantum/blob/v0.7.6/tensorflow_quantum/python/layers/circuit_executors/sampled_expectation.py#L263-L342)

    call(
        inputs,
        *,
        symbol_names=None,
        symbol_values=None,
        operators=None,
        repetitions=None,
        initializer=tf.keras.initializers.RandomUniform(0, 2 * np.pi)
    )

Keras call function.

<br />

<br />

<br />

| Input options ||
|---|---|
| `inputs`, `symbol_names`, `symbol_values`: see `input_checks.expand_circuits` `operators`: see `input_checks.expand_operators` `repetitions`: a Python `int` or a pre-converted [`tf.Tensor`](https://www.tensorflow.org/api_docs/python/tf/Tensor) containing a single `int` entry. ||

<br />

<br />

<br />

<br />

| Output shape ||
|---|---|
| [`tf.Tensor`](https://www.tensorflow.org/api_docs/python/tf/Tensor) with shape \[batch_size, n_ops\] that holds the expectation value for each circuit with each op applied to it (after resolving the corresponding parameters in). ||

<br />

### `compute_mask`

    compute_mask(
        inputs, previous_mask
    )

### `compute_output_shape`

    compute_output_shape(
        *args, **kwargs
    )

### `compute_output_spec`

    compute_output_spec(
        *args, **kwargs
    )

### `count_params`

    count_params()

Count the total number of scalars composing the weights.

<br />

<br />

<br />

| Returns ||
|---|---|
| An integer count. ||

<br />

### `from_config`

    @classmethod
    from_config(
        config
    )

Creates an operation from its config.

This method is the reverse of `get_config`, capable of instantiating the
same operation from the config dictionary.

> [!NOTE]
> **Note:** If you override this method, you might receive a serialized dtype config, which is a `dict`. You can deserialize it as follows:

    if "dtype" in config and isinstance(config["dtype"], dict):
        policy = dtype_policies.deserialize(config["dtype"])

<br />

<br />

<br />

| Args ||
|---|---|
| `config` | A Python dictionary, typically the output of `get_config`. |

<br />

<br />

<br />

<br />

| Returns ||
|---|---|
| An operation instance. ||

<br />

### `get_build_config`

    get_build_config()

Returns a dictionary with the layer's input shape.

This method returns a config dict that can be used by
`build_from_config(config)` to create all states (e.g. Variables and
Lookup tables) needed by the layer.

By default, the config only contains the input shape that the layer
was built with. If you're writing a custom layer that creates state in
an unusual way, you should override this method to make sure this state
is already created when Keras attempts to load its value upon model
loading.

<br />

<br />

<br />

| Returns ||
|---|---|
| A dict containing the input shape associated with the layer. ||

<br />

### `get_config`

    get_config()

Returns the config of the object.

An object config is a Python dictionary (serializable)
containing the information needed to re-instantiate it.

### `get_weights`

    get_weights()

Return the values of `layer.weights` as a list of NumPy arrays.

### `load_own_variables`

    load_own_variables(
        store
    )

Loads the state of the layer.

You can override this method to take full control of how the state of
the layer is loaded upon calling [`keras.models.load_model()`](https://www.tensorflow.org/api_docs/python/tf/keras/models/load_model).

<br />

<br />

<br />

| Args ||
|---|---|
| `store` | Dict from which the state of the model will be loaded. |

<br />

### `quantize`

    quantize(
        mode=None, type_check=True, config=None
    )

### `quantized_build`

    quantized_build(
        input_shape, mode
    )

### `quantized_call`

    quantized_call(
        *args, **kwargs
    )

### `rematerialized_call`

    rematerialized_call(
        layer_call, *args, **kwargs
    )

Enable rematerialization dynamically for layer's call method.

<br />

<br />

<br />

| Args ||
|---|---|
| `layer_call` | The original `call` method of a layer. |

<br />

<br />

<br />

<br />

| Returns ||
|---|---|
| Rematerialized layer's `call` method. ||

<br />

### `save_own_variables`

    save_own_variables(
        store
    )

Saves the state of the layer.

You can override this method to take full control of how the state of
the layer is saved upon calling `model.save()`.

<br />

<br />

<br />

| Args ||
|---|---|
| `store` | Dict where the state of the model will be saved. |

<br />

### `set_weights`

    set_weights(
        weights
    )

Sets the values of `layer.weights` from a list of NumPy arrays.

### `stateless_call`

    stateless_call(
        trainable_variables,
        non_trainable_variables,
        *args,
        return_losses=False,
        **kwargs
    )

Call the layer without any side effects.

<br />

<br />

<br />

| Args ||
|---|---|
| `trainable_variables` | List of trainable variables of the model. |
| `non_trainable_variables` | List of non-trainable variables of the model. |
| `*args` | Positional arguments to be passed to `call()`. |
| `return_losses` | If `True`, `stateless_call()` will return the list of losses created during `call()` as part of its return values. |
| `**kwargs` | Keyword arguments to be passed to `call()`. |

<br />

<br />

<br />

<br />

| Returns ||
|---|---|
| A tuple. By default, returns `(outputs, non_trainable_variables)`. If `return_losses = True`, then returns `(outputs, non_trainable_variables, losses)`. ||

<br />

> [!NOTE]
> **Note:** `non_trainable_variables` include not only non-trainable weights such as `BatchNormalization` statistics, but also RNG seed state (if there are any random operations part of the layer, such as dropout), and `Metric` state (if there are any metrics attached to the layer). These are all elements of state of the layer.

#### Example:

    model = ...
    data = ...
    trainable_variables = model.trainable_variables
    non_trainable_variables = model.non_trainable_variables
    # Call the model with zero side effects
    outputs, non_trainable_variables = model.stateless_call(
        trainable_variables,
        non_trainable_variables,
        data,
    )
    # Attach the updated state to the model
    # (until you do this, the model is still in its pre-call state).
    for ref_var, value in zip(
        model.non_trainable_variables, non_trainable_variables
    ):
        ref_var.assign(value)

### `symbolic_call`

    symbolic_call(
        *args, **kwargs
    )

### `__call__`

    __call__(
        *args, **kwargs
    )

Call self as a function.