Cost Functions
The qilisdk.cost_functions module turns the raw outputs returned by functionals into single scalar values that
optimizers can minimize. Each cost function inspects the FunctionalResult
produced by a backend and evaluates a problem-specific metric such as an observable expectation value or the energy of
an abstract optimization model.
You will typically use cost functions to:
score intermediate iterations inside a
VariationalProgrampost-process
SamplingResultsamples into meaningful coststranslate
TimeEvolutionResultstates into expectation values
CostFunction Interface
The abstract CostFunction base class exposes a single public method,
compute_cost(). It dispatches on the concrete type of the
functional result and calls the appropriate protected hook:
_compute_cost_sampling()receives aSamplingResult_compute_cost_time_evolution()receives aTimeEvolutionResult
If your workflow introduces new functional result types you can subclass CostFunction and register additional
handlers in self._handlers or override the protected methods above. Custom implementations must return a real or
complex number that represents the score you want to optimize.
ObservableCostFunction
The ObservableCostFunction evaluates the expectation value of
an observable against either a final state (from time evolution) or the probability distribution of sampled bitstrings.
It accepts three interchangeable representations for the observable: a
QTensor, a symbolic
Hamiltonian, or a single
PauliOperator. Internally, all inputs are converted to QTensor so the same
numeric pipeline can be reused for both analog and digital results.
Example: expectation value from time evolution
from qilisdk.analog import Schedule, X, Z
from qilisdk.backends import QutipBackend
from qilisdk.common import ket
from qilisdk.cost_functions import ObservableCostFunction
from qilisdk.functionals import TimeEvolution
# Build a linear interpolation between driver and problem Hamiltonians
T = 10.0
dt = 1
schedule = Schedule(T=T, dt=dt)
schedule.add_hamiltonian("driver", X(0), lambda t: 1 - t / ((T - dt)/dt))
schedule.add_hamiltonian("problem", Z(0), lambda t: t / ((T - dt)/dt))
schedule.draw()
functional = TimeEvolution(
schedule=schedule,
initial_state=(ket(0) - ket(1)).unit(),
observables=[Z(0)],
)
backend = QutipBackend()
evolution_result = backend.execute(functional)
cost_fn = ObservableCostFunction(Z(0))
energy = cost_fn.compute_cost(evolution_result)
print("Expectation value ⟨Z⟩ =", energy)
For sampling workflows, the cost function iterates through the probability distribution exposed by
get_probabilities() and accumulates the expectation value in
the computational basis.
ModelCostFunction
The ModelCostFunction bridges classical optimization models with
quantum result objects. It accepts any Model (including convenience subclasses such as
QUBO) and evaluates it against measured bitstrings or the amplitudes of a final quantum
state.
When the provided model is a QUBO, the cost function automatically converts it into a
Hamiltonian and computes the expectation value. Otherwise, it maps each sample to the model’s variables, feeds them
through evaluate(), and aggregates the resulting objective and constraint values.
Example: scoring samples from a variational circuit
from qilisdk.backends import CudaBackend
from qilisdk.common.model import Model
from qilisdk.common.variables import BinaryVariable, LEQ
from qilisdk.cost_functions import ModelCostFunction
from qilisdk.digital import Circuit, RX, RZ, CNOT, M
from qilisdk.functionals import Sampling
import numpy as np
# Simple 2-qubit ansatz
circuit = Circuit(2)
circuit.add(RX(0, theta=np.pi / 2))
circuit.add(CNOT(0, 1))
circuit.add(RZ(1, phi=np.pi / 3))
circuit.add(M(0))
circuit.add(M(1))
sampling = Sampling(circuit, nshots=1_000)
# Build a toy knapsack-like model
b0, b1 = (BinaryVariable("b0"), BinaryVariable("b1"))
model = Model("toy")
model.set_objective(2 * b0 + 3 * b1, label="obj")
model.add_constraint("limit", LEQ(b0 + b1, 1))
cost_fn = ModelCostFunction(model)
backend = CudaBackend()
backend_result = backend.execute(sampling)
score = cost_fn.compute_cost(backend_result)
print("Aggregated model evaluation =", score)
Cost Functions in Variational Programs
Variational workflows combine a parameterized functional, a classical optimizer, and a cost function. At each optimizer iteration the backend executes the functional, obtains a result object, and feeds it into the configured cost function to obtain the scalar that drives the optimization loop.
from qilisdk.backends import CudaBackend
from qilisdk.cost_functions import ModelCostFunction
from qilisdk.functionals import VariationalProgram, Sampling
from qilisdk.optimizers import SciPyOptimizer
variational_program = VariationalProgram(
functional=Sampling(ansatz), # parameterized circuit or schedule
optimizer=SciPyOptimizer(method="Powell"),
cost_function=ModelCostFunction(model),
)
backend = CudaBackend()
result = backend.execute(variational_program)
print("Optimal parameters:", result.optimal_parameters)
print("Optimal cost:", result.optimal_cost)
Swapping the cost function lets you explore alternative objective definitions without touching the functional itself.
For example, you can start with ObservableCostFunction to reproduce a physics-inspired energy expectation and
later try ModelCostFunction to include constraint penalties from a combinatorial problem.