Basic Usage and Composability¶
To briefly demonstrate the usage of computables, we compute the variance of a Hamiltonian given a quantum state. First, we load the necessary modules and data to acquire a Hamiltonian and generate an ansatz.
from inquanto.ansatzes import TrotterAnsatz
from inquanto.core import SymbolDict
from inquanto.express import load_h5
from inquanto.operators import QubitOperatorList
from inquanto.states import QubitState
# Load H2 Hamiltonian and convert to qubit basis
h2 = load_h5("h2_sto3g.h5", as_tuple=True)
qubit_hamiltonian = h2.hamiltonian_operator.qubit_encode().hermitian_part()
exponents = QubitOperatorList.from_string("theta [(1j, Y0 X1 X2 X3)]")
reference = QubitState([1, 1, 0, 0])
ansatz = TrotterAnsatz(exponents, reference)
parameters = SymbolDict(theta=-0.41)
We want to compute the variance \(\text{Var} = \langle H^2 \rangle - \langle H \rangle^2\) where \(H\) is the Hamiltonian.
We build this using the atomic computable ExpectationValue
and the primitive
ComputableFunction
. At the construction of the variance computable, the actual value of
the expectation value is not available. The ComputableFunction
class allows us to build
a new computable representing the variance.
from inquanto.computables import ExpectationValue
from inquanto.computables.primitive import ComputableFunction
c_variance = ComputableFunction(lambda x, y: x - y,
ExpectationValue(ansatz, qubit_hamiltonian ** 2),
ComputableFunction(lambda x: x ** 2, ExpectationValue(ansatz, qubit_hamiltonian))
)
To evaluate the computable c_variance
, an evaluator function is required. While this function typically entails quantum measurements,
it is possible to define a simpler custom evaluator function for demonstration purposes. Furthermore, utilizing a custom
evaluator can give you greater control over the evaluation process. Given that the variance expression is constructed
on the top of the atomic ExpectationValue
class, we need an evaluator function capable
of calculating the expectation value:
def my_evaluator_function(computable):
if isinstance(computable, ExpectationValue):
v = computable.state.get_numeric_representation(parameters)
return computable.kernel.state_expectation(v).real
return computable
The ExpectationValue
is a simple dataclass, which stores the
state
and kernel
operator
as attributes. If my_evaluator_function
is called on a computable which is an atomic
ExpectationValue
, it computes the expectation value via a simple statevector method.
With the evaluator function we can evaluate the variance expression as follows:
print(c_variance.evaluate(evaluator=my_evaluator_function))
0.23089952010568593
During the evaluation process of c_variance
, the my_evaluator_function
function will be invoked on all atomic
computables to obtain their evaluated values. In this example, there are two expectation values to be computed.
After the values for the atomic computables have been calculated, the final value of the expression
is also computed and returned by the root level evaluate()
method.
One might notice that certain portions of this calculation are redundant; fortunately, it is possible to make a
my_evaluator_function
that can further optimize the evaluation process,
for instance, by caching the results of computable.state.get_numeric_representation(parameters)
.
The advantage of using a computable expression is that the details of the optimization of the evaluation process
are separated from the actual physical quantities calculated, in this case from the variance.