diff --git a/docs/design.md b/docs/design.md
index a82bc9716..8f7ed18a4 100644
--- a/docs/design.md
+++ b/docs/design.md
@@ -122,3 +122,248 @@ Carefully curated problems in: quantum control, fermionic simulation,
classification near phase transitions, quantum sensing, etc are all great
candidates for addition to `tfq.datasets`. To propose a new dataset open
a GitHub issue.
+
+## Configuring execution backends
+
+Every circuit-execution layer and low-level execution op in TensorFlow Quantum
+accepts an optional `backend` argument that controls how quantum circuits are
+evaluated. By default, TFQ uses its built-in, multi-threaded C++ simulators
+(powered by
+qsim),
+which execute directly inside the TensorFlow runtime. You can also configure
+TFQ to use noisy C++ trajectory simulation, pure-Python Cirq simulators such as
+`cirq.Simulator` or `cirq.DensityMatrixSimulator`, or any custom
+`cirq.Sampler` (including noisy samplers and quantum hardware interfaces).
+
+### Backend compatibility across layers and ops
+
+Different TFQ layers and ops require different simulation capabilities
+depending on whether they compute exact expectation values, sample-based
+expectation values, raw bitstring samples, or full quantum states:
+
+| Layer or op | Default `backend` | Built-in C++ options | Custom Cirq `backend` interface | Default differentiator |
+| :--- | :--- | :--- | :--- | :--- |
+| `tfq.layers.PQC` (`repetitions=None`) | `'noiseless'` | `'noiseless'`, `None` | `cirq.sim.simulator.SimulatesExpectationValues` | `Adjoint` (C++) / `ParameterShift` (Cirq) |
+| `tfq.layers.PQC` (`repetitions=`) | `'noiseless'` | `'noiseless'`, `None` | `cirq.Sampler` | `ParameterShift` |
+| `tfq.layers.ControlledPQC` (`repetitions=None`) | `'noiseless'` | `'noiseless'`, `None` | `cirq.sim.simulator.SimulatesExpectationValues` | `Adjoint` (C++) / `ParameterShift` (Cirq) |
+| `tfq.layers.ControlledPQC` (`repetitions=`) | `'noiseless'` | `'noiseless'`, `None` | `cirq.Sampler` | `ParameterShift` |
+| `tfq.layers.NoisyPQC` | `'noisy'` (fixed) | `'noisy'` | Not applicable | `ParameterShift` |
+| `tfq.layers.NoisyControlledPQC` | `'noisy'` (fixed) | `'noisy'` | Not applicable | `ParameterShift` |
+| `tfq.layers.Expectation` | `'noiseless'` | `'noiseless'`, `'noisy'`, `None` | `cirq.sim.simulator.SimulatesExpectationValues` | `Adjoint` (`'noiseless'`) / `ParameterShift` (other) |
+| `tfq.layers.SampledExpectation` | `'noiseless'` | `'noiseless'`, `'noisy'`, `None` | `cirq.Sampler` | `ParameterShift` |
+| `tfq.layers.Sample` | `'noiseless'` | `'noiseless'`, `'noisy'`, `None` | `cirq.Sampler` | Not differentiable |
+| `tfq.layers.State` | `None` | `None` | `cirq.SimulatesFinalState` | Not differentiable |
+| `tfq.get_expectation_op` | `None` | `None` | `cirq.sim.simulator.SimulatesExpectationValues`, `cirq.DensityMatrixSimulator` | Attach via `Differentiator` |
+| `tfq.get_sampled_expectation_op` | `None` | `None` | `cirq.Sampler` | Attach via `Differentiator` |
+| `tfq.get_sampling_op` | `None` | `None` | `cirq.Sampler` | Not differentiable |
+| `tfq.get_state_op` | `None` | `None` | `cirq.SimulatesFinalState` | Not differentiable |
+
+### Built-in C++ backends
+
+TFQ provides two built-in C++ simulation backends that avoid Python callback
+overhead during TensorFlow graph execution:
+
+* *Noiseless state-vector simulator (`'noiseless'` or `None`)*: The default
+ backend for all layers (`backend='noiseless'`) and low-level ops
+ (`backend=None`). It evaluates pure quantum states in C++ using vectorized
+ SIMD instructions and OpenMP multithreading.
+* *Noisy Monte-Carlo trajectory simulator (`'noisy'`)*: Simulates circuits
+ containing noise channels (such as depolarizing, amplitude-damping, or
+ phase-damping channels listed in `tfq.util.get_supported_channels()`) by
+ averaging over quantum trajectories in C++.
+ * In mid-level layers (`tfq.layers.Expectation`,
+ `tfq.layers.SampledExpectation`, and `tfq.layers.Sample`), pass
+ `backend='noisy'` at layer construction and provide an integer
+ `repetitions` argument when calling the layer.
+ * In high-level layers, `tfq.layers.PQC` and `tfq.layers.ControlledPQC`
+ do not accept `backend='noisy'`; instead, use `tfq.layers.NoisyPQC` or
+ `tfq.layers.NoisyControlledPQC`, which configure the `'noisy'` C++
+ backend automatically and accept `repetitions` and `sample_based`
+ arguments.
+
+```python
+import cirq
+import sympy
+import tensorflow as tf
+import tensorflow_quantum as tfq
+
+q = cirq.GridQubit(0, 0)
+theta = sympy.Symbol('theta')
+noisy_circuit = cirq.Circuit(
+ cirq.rx(theta)(q),
+ cirq.depolarize(p=0.05)(q)
+)
+
+# Mid-level Expectation layer using the built-in C++ noisy trajectory backend.
+noisy_expectation = tfq.layers.Expectation(backend='noisy')
+exp_val = noisy_expectation(
+ noisy_circuit,
+ symbol_names=[theta],
+ symbol_values=[[0.5]],
+ operators=[cirq.Z(q)],
+ repetitions=1000
+)
+
+# High-level NoisyPQC layer (uses the C++ 'noisy' backend internally).
+noisy_pqc = tfq.layers.NoisyPQC(
+ noisy_circuit,
+ operators=cirq.Z(q),
+ repetitions=1000,
+ sample_based=False
+)
+pqc_val = noisy_pqc(tfq.convert_to_tensor([cirq.Circuit()]))
+```
+
+### Custom Cirq simulators and samplers
+
+To use a Cirq simulator or a custom sampler, pass an instantiated Cirq object
+to the `backend` parameter of a layer or `tfq.get_*_op` factory function:
+
+1. *Analytic expectation (`SimulatesExpectationValues`)*: When computing exact
+ expectation values without shot noise (`tfq.layers.Expectation`,
+ `tfq.get_expectation_op`, or `PQC` / `ControlledPQC` with
+ `repetitions=None`), pass an instance of
+ `cirq.sim.simulator.SimulatesExpectationValues` such as `cirq.Simulator()`
+ or `cirq.DensityMatrixSimulator()`. Using `cirq.DensityMatrixSimulator`
+ computes exact expectation values of noisy circuits from the full density
+ matrix without trajectory sampling variance:
+
+ ```python
+ q = cirq.GridQubit(0, 0)
+ theta = sympy.Symbol('theta')
+ circuit = cirq.Circuit(cirq.rx(theta)(q), cirq.depolarize(0.02)(q))
+
+ dm_simulator = cirq.DensityMatrixSimulator()
+ pqc_exact_noisy = tfq.layers.PQC(
+ circuit,
+ operators=cirq.Z(q),
+ repetitions=None,
+ backend=dm_simulator
+ )
+ ```
+
+2. *Sampled expectation and bitstring sampling (`cirq.Sampler`)*: When
+ estimating expectation values from finite measurement shots
+ (`tfq.layers.SampledExpectation`, `tfq.get_sampled_expectation_op`, or
+ `PQC` / `ControlledPQC` with `repetitions=`) or drawing bitstring
+ samples (`tfq.layers.Sample`, `tfq.get_sampling_op`), pass any object that
+ inherits from `cirq.Sampler`. This includes `cirq.Simulator()`,
+ `cirq.DensityMatrixSimulator(noise=...)`, `cirq.ZerosSampler()`, hardware
+ samplers, or custom `cirq.Sampler` subclasses:
+
+ ```python
+ class CustomNoisySampler(cirq.Sampler):
+ def __init__(self, depolarizing_prob=0.01):
+ self._sim = cirq.DensityMatrixSimulator(
+ noise=cirq.ConstantQubitNoiseModel(
+ cirq.depolarize(depolarizing_prob)
+ )
+ )
+
+ def run_sweep(self, program, params, repetitions=1):
+ return self._sim.run_sweep(program, params, repetitions)
+
+ q = cirq.GridQubit(0, 0)
+ theta = sympy.Symbol('theta')
+ circuit = cirq.Circuit(cirq.ry(theta)(q))
+ sampler = CustomNoisySampler(depolarizing_prob=0.02)
+
+ # Sample-based expectation with a custom cirq.Sampler.
+ sampled_pqc = tfq.layers.PQC(
+ circuit,
+ operators=[cirq.Z(q), cirq.X(q)],
+ repetitions=1000,
+ backend=sampler
+ )
+
+ # Bitstring sampling with a custom cirq.Sampler.
+ # TFQ automatically appends terminal measurements across all circuit qubits.
+ sample_layer = tfq.layers.Sample(backend=sampler)
+ bitstrings = sample_layer(
+ circuit,
+ symbol_names=[theta],
+ symbol_values=[[1.57]],
+ repetitions=10
+ )
+ ```
+
+3. *Quantum state simulation (`cirq.SimulatesFinalState`)*: `tfq.layers.State`
+ and `tfq.get_state_op` accept any `cirq.SimulatesFinalState` instance.
+ While the default C++ backend (`backend=None`) and `cirq.Simulator()`
+ output state vectors of shape `[batch_size, 2 ** n_qubits]`, passing
+ `cirq.DensityMatrixSimulator()` outputs full density matrices of shape
+ `[batch_size, 2 ** n_qubits, 2 ** n_qubits]`:
+
+ ```python
+ q0, q1 = cirq.GridQubit.rect(1, 2)
+ bell = cirq.Circuit(cirq.H(q0), cirq.CNOT(q0, q1))
+
+ dm_state_layer = tfq.layers.State(backend=cirq.DensityMatrixSimulator())
+ density_matrices = dm_state_layer([bell])
+ ```
+
+### Circuit serialization and concurrency control
+
+When working with custom Cirq backends or hardware samplers, keep two execution
+details in mind:
+
+* *Serialization constraints*: All circuits entering TFQ layers or ops pass
+ through `tfq.convert_to_tensor`, which serializes circuits into protocol
+ buffers. Consequently, input circuits must use `cirq.GridQubit` or
+ `cirq.LineQubit`, must contain only gates and channels listed in
+ `tfq.util.get_supported_gates()` and `tfq.util.get_supported_channels()`,
+ and must not contain pre-existing terminal measurements (TFQ appends the
+ required basis rotations and terminal measurements automatically when
+ calling a `cirq.Sampler`). If your target backend requires device-specific
+ gates, qubit routing, or compilation outside TFQ's supported gate set,
+ perform that compilation inside your custom `cirq.Sampler.run_sweep` or
+ `run_batch` method after TFQ deserializes the circuit.
+* *Controlling graph-level concurrency*: By default, TensorFlow may execute
+ multiple quantum ops concurrently across independent graph branches. When
+ dispatching jobs to a physical quantum processor or a resource-constrained
+ backend that cannot handle concurrent requests, serialize quantum op
+ execution either globally with `tfq.set_quantum_concurrent_op_mode(False)`
+ or per op via `quantum_concurrent=False`:
+
+ ```python
+ # Disable concurrent execution globally across all TFQ ops.
+ tfq.set_quantum_concurrent_op_mode(False)
+
+ # Or disable concurrency for a single low-level op.
+ serial_op = tfq.get_sampled_expectation_op(
+ backend=sampler,
+ quantum_concurrent=False
+ )
+ ```
+
+### Performance and differentiation considerations
+
+Using a custom Python `backend` changes both execution performance and default
+gradient calculation compared to the built-in `'noiseless'` (`None`) backend:
+
+1. *Built-in C++ `qsim` vs. Python callback overhead*: The default
+ `backend='noiseless'` (or `backend=None`) already runs TFQ's native,
+ multi-threaded C++ `qsim` simulator inside the TensorFlow C++ runtime.
+ Passing a Python simulator object to `backend`—even `cirq.Simulator()` or
+ `qsimcirq.QSimSimulator()`—wraps circuit execution in a `tf.py_function`
+ callback, which deserializes every `tf.string` tensor back into a Python
+ `cirq.Circuit` on every forward and backward pass. In addition, invoking an
+ externally multithreaded simulator such as `qsimcirq.QSimSimulator` inside
+ TensorFlow's worker thread pool can cause OpenMP thread contention and
+ significantly degrade performance. For noiseless state-vector simulation,
+ prefer the default `backend='noiseless'` (`backend=None`).
+2. *Automatic switch from `Adjoint` to `ParameterShift` differentiation*: When
+ `backend='noiseless'` (or `None`) and `differentiator=None`, analytic
+ expectation layers (`tfq.layers.Expectation`, `tfq.layers.PQC` with
+ `repetitions=None`, and `tfq.layers.ControlledPQC` with `repetitions=None`)
+ default to `tfq.differentiators.Adjoint()`, which computes exact gradients
+ in C++ in a single pass. Because `Adjoint` is only supported by the
+ built-in C++ simulator, supplying any custom Cirq `backend` automatically
+ switches the default differentiator to
+ `tfq.differentiators.ParameterShift()`, which evaluates two shifted
+ circuits per parameter and scales linearly with the number of circuit
+ symbols. If you use a custom `SimulatesExpectationValues` simulator and
+ want finite-difference analytic gradients instead of parameter-shift, pass
+ an explicit differentiator such as
+ `differentiator=tfq.differentiators.CentralDifference()` or
+ `tfq.differentiators.ForwardDifference()`.
diff --git a/tensorflow_quantum/core/ops/circuit_execution_ops.py b/tensorflow_quantum/core/ops/circuit_execution_ops.py
index 18e449f39..1ea8cfafb 100644
--- a/tensorflow_quantum/core/ops/circuit_execution_ops.py
+++ b/tensorflow_quantum/core/ops/circuit_execution_ops.py
@@ -80,10 +80,12 @@ def get_expectation_op(
Args:
backend: Optional Python `object` that specifies what backend this op
- should use when evaluating circuits. Can be
- `cirq.DensityMatrixSimulator` or any
- `cirq.sim.simulator.SimulatesExpectationValues`. If not provided the
- default C++ analytical expectation calculation op is returned.
+ should use when evaluating circuits. Can be `None` (default, the
+ native C++ analytical expectation op),
+ `cirq.DensityMatrixSimulator`, or any
+ `cirq.sim.simulator.SimulatesExpectationValues`. Custom Cirq
+ backends execute via `tf.py_function` callbacks and cannot be paired
+ with `tfq.differentiators.Adjoint`.
quantum_concurrent: Optional Python `bool`. True indicates that the
returned op should not block graph level parallelism on itself when
executing. False indicates that graph level parallelism on itself
@@ -182,8 +184,12 @@ def get_sampling_op(
Args:
backend: Optional Python `object` that specifies what backend this op
- should use when evaluating circuits. Can be any `cirq.Sampler`. If
- not provided the default C++ sampling op is returned.
+ should use when evaluating circuits. Can be `None` (default, the
+ native C++ sampling op) or any `cirq.Sampler` (such as
+ `cirq.Simulator()`, `cirq.DensityMatrixSimulator()`, or a custom
+ hardware/simulator sampler). For custom `cirq.Sampler` objects, TFQ
+ automatically appends terminal measurements across all circuit
+ qubits.
quantum_concurrent: Optional Python `bool`. True indicates that the
returned op should not block graph level parallelism on itself when
executing. False indicates that graph level parallelism on itself
@@ -273,9 +279,10 @@ def get_state_op(
Args:
backend: Optional Python `object` that specifies what backend this op
- should use when evaluating circuits. Can be any
- `cirq.SimulatesFinalState`. If not provided, the default C++
- state vector simulator will be used.
+ should use when evaluating circuits. Can be `None` (default, the
+ native C++ state-vector simulator) or any
+ `cirq.SimulatesFinalState` (such as `cirq.Simulator()` for state
+ vectors or `cirq.DensityMatrixSimulator()` for density matrices).
quantum_concurrent: Optional Python `bool`. True indicates that the
returned op should not block graph level parallelism on itself when
executing. False indicates that graph level parallelism on itself
@@ -372,15 +379,17 @@ def get_sampled_expectation_op(
example of how to make my_op from the above code block differentiable:
- >>> diff = tfq.differentiators.ForwardDifference()
+ >>> diff = tfq.differentiators.ParameterShift()
>>> my_differentiable_op = diff.generate_differentiable_op(
- ... analytic_op=my_op
+ ... sampled_op=my_op
... )
Args:
backend: Optional Python `object` that specifies what backend this op
- should use when evaluating circuits. Can be any `cirq.Sampler`. If
- not provided the default C++ sampled expectation op is returned.
+ should use when evaluating circuits. Can be `None` (default, the
+ native C++ sampled expectation op) or any `cirq.Sampler` (such as
+ `cirq.Simulator()`, `cirq.DensityMatrixSimulator()`, or a custom
+ hardware/simulator sampler).
quantum_concurrent: Optional Python `bool`. True indicates that the
returned op should not block graph level parallelism on itself when
executing. False indicates that graph level parallelism on itself
diff --git a/tensorflow_quantum/python/layers/circuit_executors/expectation.py b/tensorflow_quantum/python/layers/circuit_executors/expectation.py
index 8b620df50..89d1f574a 100644
--- a/tensorflow_quantum/python/layers/circuit_executors/expectation.py
+++ b/tensorflow_quantum/python/layers/circuit_executors/expectation.py
@@ -33,9 +33,16 @@ class Expectation(tf.keras.layers.Layer):
Args:
backend (Union[str, cirq.sim.simulator.SimulatesExpectationValues],
optional):
- Backend simulator to use. Default is 'noiseless'.
+ Backend simulator to use. Can be `'noiseless'` (default, native C++
+ state-vector simulator), `'noisy'` (native C++ Monte-Carlo
+ trajectory simulator; requires `repetitions` in `call`), `None`
+ (equivalent to `'noiseless'`), or a preconfigured Cirq simulator
+ inheriting `cirq.sim.simulator.SimulatesExpectationValues` (such as
+ `cirq.Simulator()` or `cirq.DensityMatrixSimulator()`).
differentiator (Optional[tfq.differentiators.Differentiator]):
- Differentiation scheme for gradients.
+ Differentiation scheme for gradients. Defaults to
+ `tfq.differentiators.Adjoint()` when `backend='noiseless'` (or
+ `None`) and `tfq.differentiators.ParameterShift()` otherwise.
**kwargs: Additional keyword arguments for the parent class.
Input shape:
@@ -236,11 +243,14 @@ def __init__(self, backend='noiseless', differentiator=None, **kwargs):
Args:
backend: Optional Backend to use to simulate states. Defaults to
- the 'noiseless' simulator, options include {'noiseless',
- 'noisy'}. In the noisy case a `repetitions` call argument
- must be provided. Users may also specify a preconfigured cirq
- object to use instead, which must inherit
- `cirq.sim.simulator.SimulatesExpectationValues`.
+ the `'noiseless'` C++ simulator; string options are
+ `{'noiseless', 'noisy'}`. In the `'noisy'` case, a
+ `repetitions` argument must be provided to `call`. Users may
+ also specify a preconfigured Cirq object inheriting
+ `cirq.sim.simulator.SimulatesExpectationValues` (such as
+ `cirq.Simulator()` or `cirq.DensityMatrixSimulator()`), which
+ executes via Python callbacks and defaults to `ParameterShift`
+ differentiation.
differentiator: Optional Differentiator to use to calculate analytic
derivative values of given operators_to_measure and circuit,
which must inherit `tfq.differentiators.Differentiator` and
diff --git a/tensorflow_quantum/python/layers/circuit_executors/sample.py b/tensorflow_quantum/python/layers/circuit_executors/sample.py
index ed99b7c81..a6ebba78e 100644
--- a/tensorflow_quantum/python/layers/circuit_executors/sample.py
+++ b/tensorflow_quantum/python/layers/circuit_executors/sample.py
@@ -146,10 +146,14 @@ def __init__(self, backend='noiseless', **kwargs):
simulated quantum state or a real quantum computer
Args:
- backend: Optional Backend to use to simulate this state. Defaults
- to the noiseless simulator. Options are {'noisy', 'noiseless'},
- however users may also specify a preconfigured cirq execution
- object to use instead, which must inherit `cirq.Sampler`.
+ backend: Optional Backend to use to sample bitstrings. Defaults to
+ `'noiseless'` (the native C++ state-vector sampler); string
+ options are `{'noisy', 'noiseless'}`. Users may also specify
+ any preconfigured `cirq.Sampler` object (such as
+ `cirq.Simulator()`, `cirq.DensityMatrixSimulator()`, a hardware
+ sampler, or a custom `cirq.Sampler` subclass). When using a
+ custom `cirq.Sampler`, TFQ automatically appends terminal
+ measurements across all circuit qubits.
"""
super().__init__(**kwargs)
used_op = None
diff --git a/tensorflow_quantum/python/layers/circuit_executors/sampled_expectation.py b/tensorflow_quantum/python/layers/circuit_executors/sampled_expectation.py
index 5c2b9b7ee..58eccae19 100644
--- a/tensorflow_quantum/python/layers/circuit_executors/sampled_expectation.py
+++ b/tensorflow_quantum/python/layers/circuit_executors/sampled_expectation.py
@@ -220,13 +220,17 @@ def __init__(self, backend='noiseless', differentiator=None, **kwargs):
sampling a quantum circuit.
Args:
- backend: Optional Backend to use to simulate states. Can be either
- {'noiseless', 'noisy'} users may also
- specify a preconfigured `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`.
- Defaults to `parameter_shift.ParameterShift()` (None argument).
+ backend: Optional Backend to use to sample expectation values.
+ Defaults to `'noiseless'` (the native C++ state-vector
+ sampler); string options are `{'noiseless', 'noisy'}`. Users
+ may also specify any preconfigured `cirq.Sampler` object (such
+ as `cirq.Simulator()`, `cirq.DensityMatrixSimulator()`, a
+ hardware sampler, or a custom `cirq.Sampler` subclass).
+ differentiator: Optional Differentiator to use to calculate
+ derivative values of given `operators` and circuit, which must
+ inherit `tfq.differentiators.Differentiator` and implement
+ `differentiate_sampled`. Defaults to
+ `tfq.differentiators.ParameterShift()` when `None`.
"""
super().__init__(**kwargs)
diff --git a/tensorflow_quantum/python/layers/circuit_executors/state.py b/tensorflow_quantum/python/layers/circuit_executors/state.py
index 6f979139a..e3fd2e679 100644
--- a/tensorflow_quantum/python/layers/circuit_executors/state.py
+++ b/tensorflow_quantum/python/layers/circuit_executors/state.py
@@ -120,12 +120,14 @@ def __init__(self, backend=None, **kwargs):
Args:
backend: Optional Backend to use to simulate this state. Defaults
- to the native TensorFlow Quantum state vector simulator,
- however users may also specify a preconfigured cirq execution
- object to use instead, which must inherit
- `cirq.SimulatesFinalState`. Note that C++ Density Matrix
- simulation is not yet supported so to do Density Matrix
- simulation please use `cirq.DensityMatrixSimulator`.
+ to `None` (the native TensorFlow Quantum C++ state-vector
+ simulator); users may also specify a preconfigured Cirq object
+ inheriting `cirq.SimulatesFinalState`. Passing
+ `cirq.Simulator()` returns state vectors of shape
+ `[batch_size, 2 ** n_qubits]`, while passing
+ `cirq.DensityMatrixSimulator()` returns density matrices of
+ shape `[batch_size, 2 ** n_qubits, 2 ** n_qubits]` (C++ density
+ matrix simulation is not supported).
"""
super().__init__(**kwargs)
self.state_op = circuit_execution_ops.get_state_op(backend)
diff --git a/tensorflow_quantum/python/layers/high_level/controlled_pqc.py b/tensorflow_quantum/python/layers/high_level/controlled_pqc.py
index d5c1f30aa..d7815194d 100644
--- a/tensorflow_quantum/python/layers/high_level/controlled_pqc.py
+++ b/tensorflow_quantum/python/layers/high_level/controlled_pqc.py
@@ -147,14 +147,20 @@ def __init__(self,
when estimating expectation values. If `None` analytic expectation
calculation is used.
backend: Optional Backend to use to simulate states. Defaults to
- the noiseless TensorFlow simulator, however users may also
- specify a preconfigured cirq simulation object to use instead.
- If a cirq object is given it must inherit `cirq.Sampler` if
- `sampled_based` is True or it must inherit
- `cirq.sim.simulator.SimulatesExpectationValues` if `sample_based` is
- False.
- differentiator: Optional `tfq.differentiator` object to specify how
- gradients of `model_circuit` should be calculated.
+ `'noiseless'` (the native TensorFlow Quantum C++ simulator); users
+ may also specify a preconfigured Cirq object instead. If a Cirq
+ object is given, it must inherit
+ `cirq.sim.simulator.SimulatesExpectationValues` when
+ `repetitions=None` (analytic expectation) or `cirq.Sampler` when
+ `repetitions` is an integer (sampled expectation). Note that custom
+ Cirq backends execute via Python callbacks and cannot use the
+ `Adjoint` differentiator. To use the built-in `'noisy'` C++
+ backend, use `tfq.layers.NoisyControlledPQC`.
+ differentiator: Optional `tfq.differentiators.Differentiator` object
+ to specify how gradients of `model_circuit` should be calculated.
+ Defaults to `tfq.differentiators.Adjoint()` when
+ `backend='noiseless'` and `repetitions=None`, or
+ `tfq.differentiators.ParameterShift()` otherwise.
"""
super().__init__(**kwargs)
# Ingest model_circuit.
diff --git a/tensorflow_quantum/python/layers/high_level/pqc.py b/tensorflow_quantum/python/layers/high_level/pqc.py
index bc34c3e97..e7af403b8 100644
--- a/tensorflow_quantum/python/layers/high_level/pqc.py
+++ b/tensorflow_quantum/python/layers/high_level/pqc.py
@@ -38,9 +38,16 @@ class PQC(tf.keras.layers.Layer):
expectation is used.
backend (Union[str, cirq.Sampler,
cirq.sim.simulator.SimulatesExpectationValues]):
- Backend simulator.
+ Backend simulator or sampler. Defaults to `'noiseless'` (the
+ native C++ qsim simulator). May also be a Cirq object inheriting
+ `cirq.sim.simulator.SimulatesExpectationValues` when
+ `repetitions=None` or `cirq.Sampler` when `repetitions` is an
+ integer. For the `'noisy'` C++ trajectory backend, use
+ `tfq.layers.NoisyPQC` instead.
differentiator (Optional[tfq.differentiators.Differentiator]):
- Differentiation scheme.
+ Differentiation scheme. Defaults to `tfq.differentiators.Adjoint()`
+ when `backend='noiseless'` and `repetitions=None`, and
+ `tfq.differentiators.ParameterShift()` otherwise.
initializer (tf.keras.initializers.Initializer):
Initializer for circuit parameters.
regularizer (Optional[tf.keras.regularizers.Regularizer]):
@@ -189,14 +196,20 @@ def __init__(
when estimating expectation values. If `None` analytic expectation
calculation is used.
backend: Optional Backend to use to simulate states. Defaults to
- the noiseless TensorFlow simulator, however users may also
- specify a preconfigured cirq simulation object to use instead.
- If a cirq object is given it must inherit either
- `cirq.sim.simulator.SimulatesExpectationValues` if analytic
- expectations are desired or `cirq.Sampler` if sampled expectations
- are desired.
- differentiator: Optional `tfq.differentiator` object to specify how
- gradients of `model_circuit` should be calculated.
+ `'noiseless'` (the native TensorFlow Quantum C++ simulator); users
+ may also specify a preconfigured Cirq object instead. If a Cirq
+ object is given, it must inherit
+ `cirq.sim.simulator.SimulatesExpectationValues` when
+ `repetitions=None` (analytic expectation) or `cirq.Sampler` when
+ `repetitions` is an integer (sampled expectation). Note that custom
+ Cirq backends execute via Python callbacks and cannot use the
+ `Adjoint` differentiator. To use the built-in `'noisy'` C++
+ backend, use `tfq.layers.NoisyPQC`.
+ differentiator: Optional `tfq.differentiators.Differentiator` object
+ to specify how gradients of `model_circuit` should be calculated.
+ Defaults to `tfq.differentiators.Adjoint()` when
+ `backend='noiseless'` and `repetitions=None`, or
+ `tfq.differentiators.ParameterShift()` otherwise.
initializer: Optional `tf.keras.initializer` object to specify how the
symbols in `model_circuit` should be initialized when creating
the managed variables.