Source code for qbraid.programs.annealing._model

# Copyright (C) 2024 qBraid
#
# This file is part of the qBraid-SDK
#
# The qBraid-SDK is free software released under the GNU General Public License v3
# or later. You can redistribute and/or modify it under the terms of the GPL v3.
# See the LICENSE file in the project root or <https://www.gnu.org/licenses/gpl-3.0.html>.
#
# THERE IS NO WARRANTY for the qBraid-SDK, as per Section 15 of the GPL v3.

"""
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 __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