Skip to content

User Defined Ansatz

Here we present how a combinatorial optimization problem can be solved within the VQE method and a user-defined ansatz. This method enrich the platform defined ansatze: QAOAPenalty and QAOAMixer.

Lets demonstrate on the Max Vertex Cover (MVC) problem, which is described in 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

Here, we define our ansatz. It consists from 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()

Now we connect all the components using 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 solutions validity

QAOAMixer is an example for an ansatz which assures that all the resulting solutions are valid under the problem's constraints. The user defined ansatz may have this quality, or not.

CombinatorialOptimization lets the user to declare if the ansatz is "constraints-preserving", and will act accordingly. Namely, for those ansatze (e.g. QAOAMixer), we will check if all the solutions are valid. This attribute is defined using should_check_valid_solutions field.

The default value of 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 we cannot completely assure the solutions validity.

So, for example, if we first use QAOAMixer and then switch to a user-defined ansatz which doesn't preserve the constraints, we 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

Custom ansatz can be defined by supplying the model to the CombinatorialOptimization class. Similar to the previously described method using ansatz.

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

Note that while using QSolver.Custom, the field 'model' will not exist unless the user explicitly sets it.