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.