Skip to content

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.