monoprop

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.

On this page