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.