from qudas.core.output_base import QdOutputBase, QdOutputBaseData
from dataclasses import dataclass
from typing import Dict, Any, Optional
# 依存ライブラリはローカル import で遅延読み込み
# NOTE: 旧 API 互換を保ちつつ多ブロック対応させる。
# - 旧: `result`/`solution` 単一ブロック辞書を保持し `.result`, `.solution`, `.result_type`
# - 新: 複数ブロックを `results` 辞書で保持
@dataclass
class QdAnnealingOutputData(QdOutputBaseData):
energy: float
[docs]class QdAnnealingOutput(QdOutputBase):
"""アニーリング系の計算結果を保持するアウトプットクラス。
1 ブロックにつき 1 つの結果辞書を保持し、複数ブロック分を
`results` という大域辞書で管理する設計とする。
Example
-------
>>> results = {
... "blockA": {
... "solution": {"x0": 1, "x1": 0},
... "energy": -1.23,
... "device": "amplify",
... },
... "blockB": {
... "solution": {"x0": 0, "x1": 1},
... "energy": -0.98,
... "device": "dimod",
... },
... }
>>> qd_out = QdAnnealingOutput(results)
>>> qd_out.get_block_solution("blockA")
{'x0': 1, 'x1': 0}
"""
def __init__(self, results: Optional[Dict[str, QdAnnealingOutputData]] = None):
"""コンストラクタ。
Parameters
----------
results : dict[str, dict[str, Any]], optional
ブロックラベルをキーに、各ブロックの計算結果辞書を
値として持つ辞書。省略時は空辞書で初期化される。
"""
self.results = results or {}
@property
def solution(self) -> Optional[Any]:
"""最初のブロックの solution を返す(辞書 or None)"""
if not self.results:
return None
return next(iter(self.results.values()))["solution"]
@property
def last_device(self) -> Optional[str]:
if not self.results:
return None
return next(reversed(self.results.values()))["device"]
@property
def result_type(self) -> Optional[str]:
return self.last_device
# ------------------------------------------------------------------
# 汎用ユーティリティ
# ------------------------------------------------------------------
[docs] def to_dict(self) -> Dict[str, Dict[str, Any]]: # noqa: D401 – 単純メソッド
"""内部保持している結果辞書をそのまま返す。"""
return self.results
[docs] def get_block_solution(self, block_label: str):
"""指定したブロックラベルの *solution* を取得する。無ければ None。"""
return self.results.get(block_label, {}).get('solution', None)
# ------------------------------------------------------------------
# 旧 API プロパティ互換
# ------------------------------------------------------------------
@property
def result(self) -> Dict[str, Any]:
"""旧 API 互換: 最初のブロックを {'variables', 'objective'} 形式で返す。"""
if not self.results:
return {}
first_block = self.results[next(iter(self.results))]
return {
'variables': first_block.get('solution', {}),
# energy -> objective 名前変換
'objective': first_block.get('energy'),
}
# ------------------------------------------------------------------
# 内部ユーティリティ
# ------------------------------------------------------------------
def _infer_last_device(self) -> Optional[str]:
"""最新ブロックの device を取得 (存在すれば)"""
if not self.results:
return None
last_block_label = next(reversed(self.results)) # py>=3.8 insertion-order dict
return self.results[last_block_label].get('device')
# ------------------------------------------------------------------
# from_* 系 (外部ライブラリ → QuDataAnnealingOutput)
# ------------------------------------------------------------------
def _set_block(
self, block_label: str, variables: Dict[str, Any], objective: Any, **extras
):
"""内部ユーティリティ: 1 ブロック分の結果を書き込む。"""
self.results[block_label] = {
'solution': variables,
'energy': objective,
**extras,
}
return self
[docs] def from_pulp(self, problem, block_label: str = 'block0'):
from pulp import value # local import
objective_value = value(problem.objective)
variables = {var.name: var.value() for var in problem.variables()}
return self._set_block(block_label, variables, objective_value, device='pulp')
[docs] def from_amplify(self, result, block_label: str = 'block0'):
variables = {str(k): v for k, v in result.best.values.items()}
return self._set_block(
block_label, variables, result.best.objective, device='amplify'
)
[docs] def from_dimod(self, result, block_label: str = 'block0'):
return self._set_block(
block_label, result.first.sample, result.first.energy, device='dimod'
)
[docs] def from_scipy(self, result, block_label: str = 'block0'):
import numpy as np # noqa: F401 – 型検査用に保持
variables = {f"q{i}": v for i, v in enumerate(result.x)}
return self._set_block(block_label, variables, result.fun, device='scipy')
# ------------------------------------------------------------------
# to_* 系 (QdAnnealingOutput → 外部ライブラリ)
# ------------------------------------------------------------------
[docs] def to_dimod(self, block_label: str = 'block0'):
import dimod
if block_label not in self.results:
raise KeyError(f"block_label '{block_label}' は存在しません。")
block = self.results[block_label]
sampleset = dimod.SampleSet.from_samples(
samples_like=dimod.as_samples(block["solution"]),
vartype='BINARY',
energy=block["energy"],
)
return sampleset
[docs] def to_scipy(self, block_label: str = 'block0'):
from scipy.optimize import OptimizeResult
import numpy as np
if block_label not in self.results:
raise KeyError(f"block_label '{block_label}' は存在しません。")
block = self.results[block_label]
x = np.array(list(block["solution"].values()))
result = OptimizeResult(
x=x,
fun=block["energy"],
success=True,
status=0,
message='Optimization terminated successfully.',
nfev=0,
nit=0,
)
return result
[docs] def visualize(self):
"""結果を可視化します。"""
try:
import matplotlib.pyplot as plt # type: ignore
for idx, (label, res) in enumerate(
self.results.items()
if isinstance(self.results, dict)
else [("", self.results)]
):
plt.figure(idx)
if "counts" in res:
plt.bar(res["counts"].keys(), res["counts"].values())
plt.title(f"Counts for {label}")
plt.show()
except Exception:
# matplotlib 無い場合、テキスト表示にフォールバック
print(
"QuDataGateOutput.visualize(): matplotlib が見つからないためテキスト出力します。"
)
print(self.results)
# エイリアス(旧クラス名を残しておく)
QdAnnOut = QdAnnealingOutput