User Defined Ansatz¶

This section explains how to solve a combinatorial optimization problem with the VQE method and a user-defined ansatz. This method enriches the platform defined ansatzes, QAOAPenalty and QAOAMixer.

This example uses the Max Vertex Cover (MVC) problem, described in the problem library.

import networkx as nx
import pyomo.core as pyo

def mvc(graph: nx.Graph, k: int) -> pyo.ConcreteModel:
model = pyo.ConcreteModel()
model.x = pyo.Var(graph.nodes, domain=pyo.Binary)
model.amount_constraint = pyo.Constraint(expr=sum(model.x.values()) == k)

def obj_expression(model):
# number of edges not covered
return sum((1 - model.x[i]) * (1 - model.x[j]) for i, j in graph.edges)

model.cost = pyo.Objective(rule=obj_expression, sense=pyo.minimize)

return model

mvc_model = mvc(graph=nx.star_graph(4), k=1)


Initializing the Ansatz¶

Define the ansatz using a hardware-efficient ansatz function.

from classiq import Model
from classiq.builtin_functions import HardwareEfficientAnsatz

model = Model()
model.HardwareEfficientAnsatz(HardwareEfficientAnsatz(num_qubits=5, reps=1))

ansatz = model.synthesize()


Connect all the components using the CombinatorialOptimization class and send a solve command. The appropriate QSolver for this case is QSovler.Custom.

from classiq.applications.combinatorial_optimization import (
CombinatorialOptimization,
QAOAPreferences,
QSolver,
)

problem = CombinatorialOptimization(
model=mvc_model,
qsolver_preferences=QAOAPreferences(qsolver=QSolver.Custom),
ansatz=ansatz,
)
result = problem.solve()


Changing the Ansatz¶

Another option is to initialize CombinatorialOptimization class with a builtin QSolver and then change it to a custom ansatz.

problem = CombinatorialOptimization(
model=mvc_model,
qsolver_preferences=QAOAPreferences(qsolver=QSolver.QAOAPenalty),
)
problem.ansatz = ansatz
result = problem.solve()


Checking the Solution Validity¶

QAOAMixer is an example of an ansatz that ensures all the resulting solutions are valid under the problem constraints. The user-defined ansatz may or may not have this quality.

CombinatorialOptimization lets you declare if the ansatz is "constraints-preserving" and acts accordingly. Namely, for those ansatzes (e.g., QAOAMixer), the Classiq engine checks if all the solutions are valid. Define this attribute using the should_check_valid_solutions field.

The default value of the should_check_valid_solutions field is set to True only for QSolve.QAOAMixer and for a quantum backend, which is an exact simulator. If the backend is noisy, the engine cannot completely ensure that the solution is valid.

So, for example, if you first use QAOAMixer and then switch to a user-defined ansatz that does not preserve the constraints, you have to explicitly switch off the should_check_valid_solutions field.

problem = CombinatorialOptimization(
model=mvc_model,
qsolver_preferences=QAOAPreferences(qsolver=QSolver.QAOAMixer),
)
problem.should_check_valid_solutions = False
problem.ansatz = ansatz
result = problem.solve()


Setting and Getting the Model¶

Define a custom ansatz by supplying the model to the CombinatorialOptimization class, similarly to the method described above, using ansatz.

problem = CombinatorialOptimization(
model=mvc_model,
qsolver_preferences=QAOAPreferences(qsolver=QSolver.Custom),
)
problem.model = model
result = problem.solve()


Note that when using QSolver.Custom, the field 'model' does not exist unless you explicitly set it.