Source code for qbraid.programs.annealing._model

# Copyright 2025 qBraid
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Module defining AnnealingProblem Class

"""
from __future__ import annotations

import json
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import Enum
from typing import TYPE_CHECKING, Any

from qbraid.programs.program import QuantumProgram
from qbraid.programs.typer import QuboCoefficientsDict

if TYPE_CHECKING:
    import qbraid.runtime


[docs] class ProblemType(Enum): """Enumeration for different types of annealing models. Attributes: QUBO: Quadratic Unconstrained Binary Optimization model with binary variables. ISING: Ising model with spin variables (-1 or +1). """ QUBO = "qubo" ISING = "ising"
[docs] @dataclass class Problem: """Represents an annealing problem, including linear and quadratic terms. Attributes: problem_type: An instance of ProblemType indicating whether the model is QUBO or ISING. linear: A dictionary representing the linear coefficients. quadratic: A dictionary representing the quadratic coefficients. """ problem_type: ProblemType linear: dict[str, float] = field(default_factory=dict) quadratic: dict[tuple[str, str], float] = field(default_factory=dict) def num_variables(self) -> int: """Return the number of variables in the problem.""" variables = set(self.linear.keys()) for key in self.quadratic: variables.update(key) return len(variables) def __eq__(self, other) -> bool: if not isinstance(other, Problem): return False if self.problem_type != other.problem_type: return False if self.linear != other.linear or len(self.quadratic) != len(other.quadratic): return False for key, value in self.quadratic.items(): if key in other.quadratic: if other.quadratic[key] != value: return False elif (key[1], key[0]) in other.quadratic: if other.quadratic[(key[1], key[0])] != value: return False else: return False return True
[docs] @dataclass class QuboProblem(Problem): """Represents a QUBO problem, subclass of Problem that only includes quadratic coefficients."""
[docs] def __init__(self, coefficients: QuboCoefficientsDict): super().__init__( problem_type=ProblemType.QUBO, linear={}, quadratic=coefficients, )
[docs] class AnnealingProgram(QuantumProgram, ABC): """Abstract class for annealing problems.""" @property def num_qubits(self) -> int: """Number of qubits needed by a quantum device to execute this program.""" return self.to_problem().num_variables() def transform(self, device: qbraid.runtime.QuantumDevice) -> None: """Transform program according to device target profile.""" return None @abstractmethod def to_problem(self) -> Problem: """Return a Problem data class representing this annealing problem.""" def to_json(self) -> str: """Serialize the annealing problem to a JSON string.""" return json.dumps(self, cls=ProblemEncoder) def serialize(self) -> dict[str, str]: """Return the program in a format suitable for submission to the qBraid API.""" return {"problem": self.to_json()} def __eq__(self, other) -> bool: if not isinstance(other, self.__class__): return False return self.to_problem() == other.to_problem()
[docs] class ProblemEncoder(json.JSONEncoder): """Custom JSON encoder for Problem data class.""" def default(self, o: Any) -> Any: if isinstance(o, AnnealingProgram): return self.default(o.to_problem()) if isinstance(o, Problem): data = {"problem_type": o.problem_type.value} if o.linear: data["linear"] = o.linear if o.quadratic: quadratic_json = {json.dumps(key): value for key, value in o.quadratic.items()} data["quadratic"] = quadratic_json return data if isinstance(o, ProblemType): return o.value return super().default(o) # pragma: no cover