Expectation values and gradients
Replaying the propagated graph to evaluate expectation values and gradients, including paring and partial contraction.
Once the circuit has been propagated with
MajoranaPropagator.build_graph, the stored graph can be replayed at any
parameter vector without re-running the Majorana algebra — the basis of
variational workflows, where one circuit is evaluated at many parameter values.
Parameter values are given as a sequence of floats (list or numpy array) in
parameter-index order (values[i] is the angle for gates carrying param == i,
mapped by Circuit.resolved_mapping), or as the Circuit itself (its
parameters are used). To evaluate two independently-authored circuit halves
together, compose them with + and build the combined circuit in one
MajoranaPropagator.build_graph call.
Expectation value and gradient
The simplest path is to evaluate directly:
expval = sim.expectation_value(parameters)
expval, grad = sim.expectation_value_and_gradient(parameters)The gradient is returned in parameter-index order (grad[i] is the derivative with
respect to the angle at index i), and expectation_value_and_gradient computes
both quantities in one backward pass over the graph.
Reusable functionals
When the same graph is evaluated at many parameter values, build a functional once
and call it repeatedly. expectation_value_functional and
expectation_value_and_gradient_functional return callables that accept a
parameter vector:
expval_fn = sim.expectation_value_functional()
expval = expval_fn(parameters)
expval_grad_fn = sim.expectation_value_and_gradient_functional()
expval, grad = expval_grad_fn(parameters)Both functionals accept an optional pare_threshold — see Paring below.
Paring
Passing a pare_threshold to expectation_value_functional or
expectation_value_and_gradient_functional is an optional
speed-up: terms whose contribution to the expectation value falls below the
threshold are pared away, so they no longer have to be tracked through the graph
during replay. The stored graph itself is unchanged, only the replay skips the
negligible terms, so this can dramatically speed up replay cost for sparse graphs
(at the expense of some memory and accuracy):
pared_expval_fn = sim.expectation_value_functional(pare_threshold=1e-7)Partial contraction
Where paring skips terms during replay, contract_partially permanently folds
a chosen set of gates into the operator, shrinking the graph that remains to be
replayed. The gates are contracted into the initial operator (Heisenberg picture)
or into the reference state (Schrödinger picture), and by default the simulator's
internal graph is updated in place:
# Fold the graph evaluated at these parameters into the operator, in place.
sim.contract_partially(parameters)This is useful when a prefix of the circuit is fixed, so its contribution is baked in once instead of replayed on every evaluation. Subsequent functionals only need to cover the remaining, shorter graph.
Pass inplace=False to leave the stored graph untouched and only return the
contracted operator coefficients, so the same graph can be reused with different
parameters:
coeffs = sim.contract_partially(parameters, inplace=False)To read the fully evolved operator as a dictionary — without modifying the
simulator — use evolved_operator.