OpenJij/OpenJij

View on GitHub
docs/tutorial/ja/001-openjij_introduction.ipynb

Summary

Maintainability
Test Coverage
{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "Y4hVgt6yQyOa"
   },
   "source": [
    "# OpenJij 入門"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "OpenJijは イジング模型および,QUBOのヒューリステック最適化ライブラリです。  \n",
    "最適化計算のコア部分はC++で実装されていますが、Pythonインターフェースを備えているため、Pythonから簡単に最適化を行うことができます。\n",
    "\n",
    "インストールにはpipを使用します。ただし、numpy を事前にインストールしておいてください。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "JZW9IAE9QyOd"
   },
   "outputs": [],
   "source": [
    "!pip install openjij"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "wyu_ZSisQyOg",
    "outputId": "09252d51-5c0a-43c7-af4f-95edea5170af"
   },
   "outputs": [],
   "source": [
    "# 以下のコマンドでOpenJijの情報を見ることができます。実行環境によって出力は異なります。\n",
    "!pip show openjij"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "x9a_Imk1QyOn"
   },
   "source": [
    "## OpenJijによるイジング模型を用いた最適化計算\n",
    "\n",
    "[組合せ最適化とイジング模型](./000-combinatorial_optimization_and_Ising_model.ipynb)で説明したように、\n",
    "イジング模型は磁性体の振る舞いを理解するために提案された模型で、以下のように書かれます。\n",
    "\n",
    "$$H(\\{\\sigma_i\\}) = \\sum_{i > j} J_{ij}\\sigma_i \\sigma_j + \\sum_{i=1}^N h_i \\sigma_i$$\n",
    "$$\\sigma_i \\in \\{-1, 1\\}, i=1,\\cdots N$$\n",
    "\n",
    "ここで$H(\\{\\sigma_i\\})$はハミルトニアンと呼ばれます。エネルギーやコスト関数だと考えてください。  \n",
    "$\\sigma_i$は$\\{1, -1\\}$の2値を取る変数です。  \n",
    "\n",
    "> $\\sigma_i$は物理ではスピンという物理量に対応するため、スピン変数もしくは単純にスピンと呼ばれることもあります。  \n",
    "> スピンとは小さな磁石のようなものです。-1 が磁石が上向き、1が下向きのように変数の値と物理(磁石の向き)が対応します。\n",
    "\n",
    "\n",
    "$H$は変数の組み合わせ$\\{\\sigma_i\\} = \\{\\sigma_1, \\sigma_2, \\cdots, \\sigma_N\\}$に依存します。  \n",
    "$J_{ij}, h_i$が与えられる問題を表しています。それぞれ相互作用係数、縦磁場と呼ばれます。\n",
    "\n",
    "OpenJijは、インプットとして、$J_{ij}$と$h_i$が与えられたときに、Simulated AnnealingやSimulated Quantum Annealingを行う数値計算ライブラリであり、\n",
    "OpenJijを用いることで、$H(\\{\\sigma_i\\})$を最小化するスピン変数の組み$\\{\\sigma_i\\}$を探すことができます。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "j0iZqoWtQyOn"
   },
   "source": [
    "### OpenJijに問題を投げてみる\n",
    "まずは、簡単な例でどのようにOpenJijを用いるかをみていきます。\n",
    "変数の数が$N=5$で縦磁場と相互作用が\n",
    "\n",
    "$$h_i = -1~\\text{for} ~\\forall i, ~ J_{ij} = -1~\\text{for} ~\\forall i, j$$\n",
    "\n",
    "の問題を考えてみましょう。全ての相互作用がマイナスなので、各スピン変数は同じ値をとった方がエネルギーは低くなることがわかります。\n",
    "また縦磁場は全てマイナスなので、各スピンは1の値をとった方がエネルギーが低くなります。  \n",
    "よってこの問題の最適解は $\\{\\sigma_i\\} = \\{1, 1, 1, 1, 1\\}$になります。\n",
    "\n",
    "では、実際にこの結果が得られるかOpenJijを用いて計算してみましょう。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "まず初めに、インプットデータとして、$h_i$と$J_{ij}$を用意します。\n",
    "ここでは、それぞれの添字をkeyとし、値をvalueとした辞書を作成します。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "h_i:  {0: -1, 1: -1, 2: -1, 3: -1, 4: -1}\n",
      "Jij:  {(0, 1): -1, (0, 2): -1, (0, 3): -1, (0, 4): -1, (1, 2): -1, (1, 3): -1, (1, 4): -1, (2, 3): -1, (2, 4): -1, (3, 4): -1}\n"
     ]
    }
   ],
   "source": [
    "import openjij as oj\n",
    "\n",
    "# 問題を表す縦磁場と相互作用を作ります。OpenJijでは辞書型で問題を受け付けます。\n",
    "N = 5\n",
    "h = {i: -1 for i in range(N)}\n",
    "J = {(i, j): -1 for i in range(N) for j in range(i+1, N)}\n",
    "\n",
    "print('h_i: ', h)\n",
    "print('Jij: ', J)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "つづいて、実際に最適化計算を行なっていきます。\n",
    "ここでは、焼きなまし法を行うために、`oj.SASampler`のインスタンスを作成します。\n",
    "ここでは、`sample_ising`メソッドの引数に$h_i$と$J_{ij}$を渡してあげることで、焼きなまし法が実行されます。\n",
    "\n",
    "`oj.SASampler`、`sample_ising`の詳細および、その返り値については、このノートの最後に説明します。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[1 1 1 1 1]]\n",
      "[{0: 1, 1: 1, 2: 1, 3: 1, 4: 1}]\n"
     ]
    }
   ],
   "source": [
    "# まず問題を解いてくれるSamplerのインスタンスを作ります。\n",
    "# このインスタンスの選択で問題を解くアルゴリズムを選択できます。\n",
    "sampler = oj.SASampler(num_reads=1)\n",
    "# samplerのメソッドに問題(h, J)を投げて問題を解きます。\n",
    "response = sampler.sample_ising(h, J)\n",
    "\n",
    "# 計算した結果(状態)は response.states に入っています。\n",
    "print(response.states)\n",
    "\n",
    "# もしくは添字付きでみるには samples関数 を用います。\n",
    "print([s for s in response.samples()])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "確かに、我々の予想通り全てのスピンが1となった状態が得られました。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "先ほどは、$h_i$と$J_{ij}$を辞書として渡しましたが、巨大な問題を扱う際にはnumpyを用いた入力が便利となります。\n",
    "OpenJijでは、以下の形のnumpyの行列を渡すことで、OpenJijが解くことができる形に変換する`oj.BinaryQuadraticModel.from_numpy_matrix`が用意されています。\n",
    "\n",
    "$$ \\begin{pmatrix}\n",
    "h_{0} & J_{0,1} & \\cdots & J_{0,N-1}\\\\\n",
    "J_{1,0} & h_{1} & \\cdots & J_{1,N-1}\\\\\n",
    "\\vdots & \\vdots & \\vdots & \\vdots\\\\\n",
    "J_{N-1,0} & J_{N-1,1} & \\cdots & h_{N-1}\\\\\n",
    "\\end{pmatrix} $$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "#!pip install numpy -U"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[-1.  -0.5 -0.5 -0.5]\n",
      " [-0.5 -1.  -0.5 -0.5]\n",
      " [-0.5 -0.5 -1.  -0.5]\n",
      " [-0.5 -0.5 -0.5 -1. ]]\n",
      "BinaryQuadraticModel({3: -1.0, 2: -1.0, 1: -1.0, 0: -1.0}, {(1, 2): -1.0, (2, 3): -1.0, (1, 3): -1.0, (0, 3): -1.0, (0, 2): -1.0, (0, 1): -1.0}, 0.0, Vartype.SPIN, sparse=False)\n",
      "[[1 1 1 1]]\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "mat = np.array([[-1,-0.5,-0.5,-0.5],[-0.5,-1,-0.5,-0.5],[-0.5,-0.5,-1,-0.5],[-0.5,-0.5,-0.5,-1]])\n",
    "print(mat)\n",
    "\n",
    "# oj.BinaryQuadraticModelを作成し、変数タイプ (vartype)を'SPIN'にします。\n",
    "bqm = oj.BinaryQuadraticModel.from_numpy_matrix(mat, vartype='SPIN')\n",
    "# 各要素をprintで確認できます。J_{ij}とJ_{ji}は内部でまとめられます。\n",
    "print(bqm)\n",
    "\n",
    "sampler = oj.SASampler(num_reads=1)\n",
    "response = sampler.sample(bqm)\n",
    "\n",
    "print(response.states)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "辞書でデータを渡す場合には、相互作用行列の上三角だけを与えていましたが、numpy行列で与える場合には下三角も含めて与えているので、ここでは、辞書で与えた場合と問題を一致させるために、相互作用行列の非対角要素を1の半分の0.5として与えています。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "zxyro6tTQyPN"
   },
   "source": [
    "## OpenJijによるQUBOを用いた最適化計算\n",
    "\n",
    "社会の実問題を解きたい場合には、イジング模型よりも QUBO(Quadratic unconstraited binary optimization)と呼ばれる形で定式化した方が素直な場合が多いです。\n",
    "\n",
    "QUBOは以下のように書かれます。\n",
    "\n",
    "$$H(\\{q_i\\}) = \\sum_{i\\geq j} Q_{ij}q_i q_j$$\n",
    "$$q_i \\in \\{0, 1\\}$$\n",
    "\n",
    "イジング模型との違いは、2値変数が0 と 1のバイナリ変数であることです。$\\sum Q_{ij}$の総和の範囲の取り方にはさまざまなもの(例えば$Q_{ij}$を対称行列にするなど)がありますが、今回は上式のように定式化しておきましょう。\n",
    "\n",
    "> $q_i = (\\sigma_i + 1)/2$という変換式を用いることでイジング模型と QUBO は相互変換が可能であり、その意味で二つの模型は等価です。\n",
    "\n",
    "QUBOでは、インプットとして$Q_{ij}$を与え、$H(\\{q_i\\})$を最小化する0, 1の組み合わせ$\\{q_i\\}$を探しましょうという問題になります。\n",
    "ほぼ先ほどのイジング模型の場合と一緒です。\n",
    "\n",
    "また$q_i$はバイナリ変数なので、$q_i^2 = q_i$であることがわかります。よって上式を以下のように書き分けることができます。\n",
    "\n",
    "$$H(\\{q_i\\}) = \\sum_{i > j} Q_{ij}q_i q_j + \\sum_i Q_{ii} q_i$$\n",
    "\n",
    "$Q_{ij}$の添字が同じところは $q_i$の1次の項の係数に対応します。\n",
    "\n",
    "これをOpenJijで解いてみましょう。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[1 1 0]]\n"
     ]
    }
   ],
   "source": [
    "# Q_ij を辞書型でつくります。\n",
    "Q = {(0, 0): -1, (0, 1): -1, (1, 2): 1, (2, 2): 1}\n",
    "sampler = oj.SASampler()\n",
    "# QUBOを解く時は .sample_qubo を使います。\n",
    "response = sampler.sample_qubo(Q)\n",
    "print(response.states)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "CxbDNkSuQyPU"
   },
   "source": [
    "QUBOでは変数が 0, 1のため、解も 0, 1で出力されていることがわかります。  \n",
    "このようにOpenJijを用いてイジング模型でもQUBOでも、同じように最適化問題を解くことができます。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "feViwN6zQyO0"
   },
   "source": [
    "## OpenJijの機能の解説\n",
    "\n",
    "ここでは、上述のコードの詳細を解説をしていきます。\n",
    "OpenJijは現在インターフェースを2つ備えており、上記で使ったものはD-Wave Oceanと同じインターフェースになっています。そのため、OpenJijで慣れておけばD-Wave Oceanも扱うことができるようになります。\n",
    "\n",
    "> もう一つのインターフェースについてはここでは解説しませんが、OpenJijの仕組み`graph, method, algorithm`を直接使うことで拡張のしやすさを備えています。ここでは上のセルで扱ったインターフェースを使えるようになれば十分でしょう。\n",
    "\n",
    "### Sampler\n",
    "\n",
    "先ほどは問題を辞書型で作ったあとに、下のようにSamplerのインスタンスを作りました。\n",
    "```python\n",
    "sampler = oj.SASampler(num_reads=1)\n",
    "```\n",
    "ここでこのSamplerのインスタンスが、どのようなアルゴリズムを使うかを指定しています。\n",
    "他のアルゴリズムを試したい場合には、このSamplerを変更することで別のアルゴリズムを使うことができます。\n",
    "例えば、上の例で用いた`SASampler`はSimulated Annealingというアルゴリズムを用いて、解をサンプリングしてくるSamplerです。  \n",
    "他にも、Simulated Quantum Annealing(SQA) という量子アニーリングを古典コンピュータでシミュレーションするアルゴリズムを用いるための`SQASampler`が用意されています。\n",
    "\n",
    "> OpenJijで扱っているアルゴリズムはヒューリスティックな確率アルゴリズムです。問題を解くたびに返す解が違ったり、必ずしも最適解を得ることができません。\n",
    "> よって複数回問題を解き、その中でよい解を探すという手法をとります。そのため、ここでは解をサンプリングするという気持ちを込めてSamplerと呼んでいます。\n",
    "\n",
    "上記のコードでは、Samplerの引数に、`num_reads`引数を指定しています。\n",
    "この`num_reads`引数に整数を入れることで、一度に解く回数(iteration回数)を指定することができます。\n",
    "`num_reads`の値を明記しない場合、デフォルトの`num_reads=1`で実行されます。\n",
    "\n",
    "\n",
    "\n",
    "### sample_ising(h, J)、sample_qubo(Q)\n",
    "上述のとおり、問題を解く際は`.sample_ising(h, J)`のように縦磁場と相互作用を変数として代入して投入します。\n",
    "\n",
    "QUBOに対して最適化計算を行う時は`.sample_qubo(Q)`を用います。\n",
    "\n",
    "### Response\n",
    "\n",
    "`.sample_ising(h, J)`はResponseクラスを返します。ResponseクラスにはSamplerが解いて出てきた解と各解のエネルギーが入っています。\n",
    "Responseクラスは様々なプロパティを持ちますが、ここでは主要なプロパティについて説明します。\n",
    "\n",
    "- `.states` : list[list[int]]\n",
    "    - num_reads回数の解が格納されている\n",
    "    > 物理ではスピンの配列(解)を状態(state)と呼ぶことがあります。.statesにはnum_reads回だけ解いた解が格納されているので複数の状態を格納しているという気持ちを込めて .states としています。\n",
    "- `.energies` : list[float]\n",
    "    - num_reads回数分の各解のエネルギーが格納されている\n",
    "- `.indices` : list[object]\n",
    "    - 解がlistでstatesに入っているが、それに対応する各スピンの添字を格納されている\n",
    "- `.first.sample` : dict\n",
    "    - 最小エネルギー状態を取るときの状態が格納されている\n",
    "- `.first.energy` : float\n",
    "    - 最小エネルギーの値\n",
    "\n",
    "> ResponseクラスはD-WaveのdimodのSampleSetクラスを継承しています。より詳細な情報は以下のリンクに記述されています。  \n",
    "> [dimodドキュメント、SampleSet](https://docs.ocean.dwavesys.com/en/stable/docs_dimod/reference/sampleset.html#dimod.SampleSet)\n",
    "\n",
    "実際にコードを見てみましょう。\n",
    "ここでは、試しに`num_reads=10`ととして、10回分の解を得てみます。\n",
    "また、今回はSimulated Annealingではなく、Simulated Quantum Annealing用のSamplerである`SQASampler`を用いてみましょう。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'a': 1, 'b': 1, 'c': -1}\n",
      "-4.0\n"
     ]
    }
   ],
   "source": [
    "# 実は h, J の添字を示す、辞書のkeyは数値以外も扱うことができます。\n",
    "h = {'a': -1, 'b': -1}\n",
    "J = {('a', 'b'): -1, ('b', 'c'): 1}\n",
    "# num_reads 引数に値を代入することで、SQAを10回試行する計算を一度の命令で解くことができます。\n",
    "sampler = oj.SQASampler(num_reads=10)  \n",
    "response = sampler.sample_ising(h, J)\n",
    "print(response.first.sample)\n",
    "print(response.first.energy)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 1  1 -1]\n",
      " [ 1  1 -1]\n",
      " [ 1  1 -1]\n",
      " [ 1  1 -1]\n",
      " [ 1  1 -1]\n",
      " [ 1  1 -1]\n",
      " [ 1  1 -1]\n",
      " [ 1  1 -1]\n",
      " [ 1  1 -1]\n",
      " [ 1  1 -1]]\n"
     ]
    }
   ],
   "source": [
    "# response.states を見てみましょう。10回分の解が入っていることがわかります。\n",
    "print(response.states)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "4mekWlvTQyO4"
   },
   "source": [
    "> num_reads などコンストラクタで渡すパラメータは`.sample_ising`などのサンプリングを実行するメソッドで上書きすることができます。\n",
    "> ```\n",
    "> response = sampler.sample_ising(h, J, num_reads=2)\n",
    "> response.states\n",
    "> > [[1, 1, -1],[1, 1, -1]]\n",
    "> ```\n",
    "\n",
    "今回は問題が簡単なので、10回とも同じ答え [1,1,-1] になっていることがわかります。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([-4., -4., -4., -4., -4., -4., -4., -4., -4., -4.])"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 次にエネルギーを見てみましょう。\n",
    "response.energies"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "NYAnma_WQyO7"
   },
   "source": [
    "エネルギーの値を10回とも同じ値を取っていることがわかります。  \n",
    "`response.states`に入っている解はリストになっているため、問題をセットした時の `a, b, c`という文字列との対応がわかりません。それを調べるために`response.variables`を見てみましょう。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Variables(['a', 'b', 'c'])"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "response.variables"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "5rf7wB7LQyPJ"
   },
   "source": [
    "最小のエネルギー値を持った状態のみが知りたい場合には `.first` が便利です。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "-J1utQATQyPK",
    "outputId": "839e1260-3b1c-432d-a4b6-7b318ba1048c"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Sample(sample={'a': 1, 'b': 1, 'c': -1}, energy=-4.0, num_occurrences=1)"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "response.first"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "8IRw4pHOQyPV"
   },
   "source": [
    "## ランダムQUBO行列に対する最適化計算\n",
    "\n",
    "これまで解いてきた問題は簡単すぎたので、このチュートリアルの最後に少し難しい問題を解いてみましょう。\n",
    "\n",
    "今度は変数の数が50個でランダムに$Q_{ij}$が振られたQUBOを解いてみたいと思います。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "N = 50\n",
    "# ランダムにQij を作る\n",
    "import random\n",
    "Q = {(i, j): random.uniform(-1, 1) for i in range(N) for j in range(i+1, N)}\n",
    "\n",
    "# OpenJijで解く\n",
    "sampler = oj.SASampler()\n",
    "response = sampler.sample_qubo(Q, num_reads=100)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([-60.31847329, -60.31847329, -60.31847329, -60.31847329,\n",
       "       -60.50688986])"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# エネルギーを少しみてみます。\n",
    "response.energies[:5]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "syclwqyyQyPg"
   },
   "source": [
    "エネルギーを見てみると、先ほどの例とは異なり違う値をとっていることがわかります。  \n",
    "ランダムに$Q_{ij}$ を与えた場合、一般に問題は難しくなります。よってSASamplerも毎回同じ解を出しません。  \n",
    "ではどのような解がでたのかを、エネルギーのヒストグラムで可視化してみましょう。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYMAAAELCAYAAAA7h+qnAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/Z1A+gAAAACXBIWXMAAAsTAAALEwEAmpwYAAAXV0lEQVR4nO3df5xldX3f8ddbEH+gUZABVlAXDNCiragjanxojUiC2gLRGPVh7IbSxyYEraZas2piIKYtahOjiUZXQDZREUQMK1CRbCWmrREWRQRxRRAUhN0R5YfgjyKf/nHOymW4d3bOzJ17Z7iv5+NxH/ee7/lxP3P2zL7n/PqeVBWSpMn2oHEXIEkaP8NAkmQYSJIMA0kShoEkCdh53AUs1B577FGrV68edxmStKJceuml36+qqdntKzYMVq9ezebNm8ddhiStKEmu79fuYSJJkmEgSTIMJEkYBpIkDANJEoaBJAnDQJKEYSBJwjCQJLGC70CWpJVg9brzhr7M6056ydCX6Z6BJMkwkCQZBpIkDANJEiMOgyQHJbms53V7kjck2T3JhUmubt93G2VdkjTpRhoGVbWlqg6pqkOApwN3AZ8G1gGbquoAYFM7LEkakXEeJjoMuKaqrgeOAja07RuAo8dVlCRNonGGwSuB09vPe1XVTQDt+579ZkiyNsnmJJtnZmZGVKYkPfCNJQyS7AIcCXyyy3xVtb6qpqtqemrqfo/wlCQt0Lj2DF4EfLmqtrbDW5OsAmjft42pLkmaSOMKg1dx7yEigI3AmvbzGuCckVckSRNs5GGQ5OHA4cDZPc0nAYcnubodd9Ko65KkSTbyjuqq6i7gMbPabqG5ukiSNAbegSxJMgwkSYaBJAnDQJKEYSBJwjCQJGEYSJIwDCRJGAaSJAwDSRKGgSQJw0CShGEgScIwkCRhGEiSMAwkSRgGkiQMA0kShoEkiTGEQZJHJzkryTeSXJXk2Ul2T3Jhkqvb991GXZckTbJx7Bm8F/hsVf0L4CnAVcA6YFNVHQBsaoclSSMy0jBI8kvA84BTAKrqZ1V1K3AUsKGdbANw9CjrkqRJN+o9g/2BGeAjSb6S5OQkuwJ7VdVNAO37niOuS5Im2qjDYGfgacDfVNVTgTvpcEgoydokm5NsnpmZWaoaJWnijDoMbgBuqKovtcNn0YTD1iSrANr3bf1mrqr1VTVdVdNTU1MjKViSJsFIw6Cqbga+m+Sgtukw4OvARmBN27YGOGeUdUnSpNt5DN/5OuBjSXYBrgWOoQmlM5McC3wHePkY6pKkiTXyMKiqy4DpPqMOG3EpkqSWdyBLkgwDSZJhIEnCMJAkYRhIkjAMJEkYBpIkDANJEoaBJAnDQJKEYSBJwjCQJGEYSJIwDCRJGAaSJAwDSRKGgSQJw0CShGEgScIwkCQBO4/6C5NcB9wB/By4u6qmk+wOnAGsBq4Dfquqfjjq2iRpUnXaM0jy5CF9769W1SFVNd0OrwM2VdUBwKZ2WJI0Il0PE12e5JIkxyV59BDrOArY0H7eABw9xGVLknagaxgcBnwdeBfwvSSnJzk8SToso4DPJbk0ydq2ba+qugmgfd+z34xJ1ibZnGTzzMxMx9IlSYN0CoOq+nxVrQH2Bl4L7ANcAFyf5B1JnjiPxTynqp4GvAg4PsnzOnz/+qqarqrpqampLqVLkuawoKuJqurOqjq1qp4HHEhz0vetwDeT/GOS35hj3u+179uATwOHAluTrAJo37ctpC5J0sIs+NLSJKuTnAB8Dng2cD6wFtgKnJHkPX3m2TXJI7d/Bn4NuALYCKxpJ1sDnLPQuiRJ3XW6tDTJw4HfBI4Bngt8G/gwcNr2Y/7AKUmOAd4L/MGsRewFfLo9xbAz8PGq+mySS4AzkxwLfAd4+QJ/HknSAnS9z+BmYCfgbOCFVXXRgOkuAW6Z3VhV1wJP6dN+C83JaUnSGHQNg3XAx6rqtrkmqqorgP0WXJUkaaQ6hUFVfWCpCpEkjU/XO5BPTXLGgHGnJzl5OGVJkkap69VEhwNnDRj3KZqrgyRJK0zXMJgCfjBg3A8ZcOewJGl56xoG1wOD7hh+HnDD4sqRJI1D1zA4DfjDJMcneQRAkkck+X3gzYDnDCRpBep6aek7gScCfwW8L8mdwK5AgPXteEnSCtP10tJ7gP+Y5N3AC4DdaW4u+19V9c0lqE+SNAILetJZVW0Btgy5FknSmCwoDJIcCOwLPHT2uKo6f7FFSZJGq2tHdQfTPKv4YJrzBLMVTd9FkqQVpOuewYeAXYCX0jzx7GdDr0iSNHJdw+CpwCur6tylKEaSNB5d7zO4hj7nCSRJK1vXMHgj8NYk+y9FMZKk8eh6mOi/A/sA30hyHXDr7Amq6tDFlyVJGqWuYXBF+5IkPYB0vQP5mKUqRJI0Pl3PGQCQxuOS/EqSXRcw/05JvpLk3HZ49yQXJrm6fd9tIXVJkhamcxi0PZTeSNOd9T8BB7XtZyd5wzwX83rgqp7hdcCmqjoA2NQOS5JGpOtjL/8L8BfAh2k6quu9C/ki4BXzWMa+wEu4b3fXRwEb2s8bgKO71CVJWpyuJ5CPB95eVe9KMrvbiS3AgfNYxl/SPPvgkT1te1XVTQBVdVOSvk9MS7IWWAvw+Mc/vmPpkqRBuh4m2hu4dMC4e9jBDWlJ/i2wraoGLWNOVbW+qqaranpqamohi5Ak9dE1DL4F/JsB455H01/RXJ4DHNneo/AJ4AVJPgpsTbIKoH3f1rEuSdIidA2DvwTWJfkj4IC2bc8kxwL/GXjPXDNX1Vuqat+qWg28kuahOL8NbATWtJOtAc7pWJckaRG63mdwcnvZ59uBE9vm84G7gBOq6uMLrOMk4Mw2VL4DvHyBy5EkLUDnh9tU1buTfBD4FeAxwA+AL1bVbR2XcxHNFUhU1S3AYV1rkSQNx0Ife3kHcMGQa5EkjUnXJ539/o6mqaoPLLwcSdI4dN0z+Os5xlX7bhhI0grT6WqiqnrQ7BewO/Aq4Ks0z0aWJK0wCzpn0KuqbgXOSPIommckP3+xy5QkjdaCei0d4NvA9BCXJ0kakaGEQXvX8BtpAkGStMJ0vZpohntPFG+3C02ncz8BXjqkuiRJI9T1nMH7uX8Y/AS4Afhse/OYJGmF6dodxQlLVIckaYyGeQJZkrRCdT1n8G3uf5hooKrav3NFkqSR63rO4CyarqcfDlxI89yBPYHDgTuBM4ZanSRpJLqGwQ+Ba4CXVNWd2xuTPAI4F7itqv5siPVJkkag6zmD44F39wYBQFX9CPgf7XhJ0grTNQweBew1YNzewCMWV44kaRy6HibaCLw7ye3AZ6rqp0keAhwJvBP4zLALlCQtva5hcBxwGnAmUEnuoLn7ODRBcdxQq5MkjUTXm85uA34jyZOAZ9AcMroZuKSqvr4E9UmSRmChj728ErhyyLVIksak8x3ISfZM8s4km5JsafcSSPL6JM/ewbwPTXJxkq8muTLJiW377kkuTHJ1+77bwn4cSdJCdAqDJIcCVwMvA64Dfhl4SDt6ezfWc/kp8IKqegpwCHBEkmcB64BNVXUAsKkdliSNSNc9g/cAnwcOBH6X5sTxdhcDh841czV+1A4+uH0VcBSwoW3fABzdsS5J0iJ0DYOnAR+oqnu4fx9Ft9B0TTGnJDsluYymK4sLq+pLwF5VdRNA+953OUnWJtmcZPPMzEzH0iVJg3QNg9uAqQHj9ge27mgBVfXzqjoE2Bc4NMmT5/vlVbW+qqaranpqalAZkqSuuobBOcCJSXp7I60kewBvAs6e74Kq6lbgIuAIYGv76Mztj9Dc1rEuSdIidA2DdcDtwNeBL7RtHwS2AD8G3j7XzEmmkjy6/fww4IXAN2huWFvTTraGJnQkSSPS9aazH7ZX/7wGOIym2+ofACcDf1tVP93BIlYBG5LsRBNEZ1bVuUm+CJyZ5FjgO8DLO/4ckqRFmHcYJHkozV/w/62qTgFO6fplVXU58NQ+7bfQhIskaQzmfZioqn5C0wXFTktXjiRpHLqeM9iI9wBI0gNO176JLqDpwnoVcD7NpaT3ud+gqs4fUm1LZvW684a6vOtOeslQlydJo9Y1DD7avr+0fc1WeBhJklacHYZBks8Br6uqLcB+NF1QHAZ8CbhjacuTJI3CfPYMXkjzuEuq6vr2stD1wDOq6vqlLE6SNBqdu7BuZceTSJJWioWGgSTpAWS+YTC7h9JBbZKkFWi+VxNdkOTuWW2b+rRRVTvsxlqStLzMJwxOXPIqJEljtcMwqCrDQJIe4DyBLEkyDCRJhoEkCcNAkoRhIEnCMJAkYRhIkhhxGCR5XJLPJ7kqyZVJXt+2757kwiRXt++7jbIuSZp0o94zuBt4Y1X9S+BZwPFJDgbWAZuq6gBgUzssSRqRkYZBVd1UVV9uP98BXAXsAxwFbGgn24DPWZakkRrbOYMkq4Gn0jwxba+qugmawAD6dnaXZG2SzUk2z8zMjKxWSXqgG0sYJHkE8CngDVV1+3znq6r1VTVdVdNTU1NLV6AkTZiRh0GSB9MEwceq6uy2eWuSVe34VcC2UdclSZNs1FcTBTgFuKqq/qJn1EZgTft5DXDOKOuSpEk334fbDMtzgNcAX0tyWdv2VuAk4MwkxwLfAV4+4rokaaKNNAyq6n8DGTD6sFHWIkm6l3cgS5IMA0mSYSBJwjCQJGEYSJIwDCRJGAaSJAwDSRKGgSQJw0CShGEgScIwkCRhGEiSMAwkSRgGkiQMA0kShoEkCcNAkoRhIElixGGQ5NQk25Jc0dO2e5ILk1zdvu82ypokSaPfMzgNOGJW2zpgU1UdAGxqhyVJIzTSMKiqLwA/mNV8FLCh/bwBOHqUNUmSlsc5g72q6iaA9n3PQRMmWZtkc5LNMzMzIytQkh7olkMYzFtVra+q6aqanpqaGnc5kvSAsRzCYGuSVQDt+7Yx1yNJE2c5hMFGYE37eQ1wzhhrkaSJNOpLS08HvggclOSGJMcCJwGHJ7kaOLwdliSN0M6j/LKqetWAUYeNsg5J0n0th8NEkqQxMwwkSYaBJMkwkCRhGEiSMAwkSRgGkiQMA0kShoEkCcNAkoRhIEnCMJAkYRhIkjAMJEkYBpIkDANJEoaBJAnDQJKEYSBJwjCQJLGMwiDJEUm2JPlWknXjrkeSJsmyCIMkOwHvB14EHAy8KsnB461KkibHsggD4FDgW1V1bVX9DPgEcNSYa5KkibHzuAto7QN8t2f4BuCZsydKshZY2w7+KMmWEdS2B/D9uSbIO0dQxdx2WOOYWd/iLfcal3t9sPxrnHd9i/w/5wn9GpdLGKRPW92voWo9sH7py7lXks1VNT3K7+xquddofYu33Gtc7vXB8q9x3PUtl8NENwCP6xneF/jemGqRpImzXMLgEuCAJPsl2QV4JbBxzDVJ0sRYFoeJquruJK8FLgB2Ak6tqivHXNZ2Iz0stUDLvUbrW7zlXuNyrw+Wf41jrS9V9zs0L0maMMvlMJEkaYwMA0nS5IVBkte13V5cmeRdPe1vabvC2JLk1wfMe0KSG5Nc1r5evKP5kzw9ydface9L0u8y2qHU1zPtm5JUkj3a4Vf31HxZknuSHNKOu6hd5vZxe46hvtVJftxTwwd7pu20/pawxsOTXNrWcmmSF/RMO/Z1ONf8o16HSd6R5PJ2XXwuyWPb9mWxHc5R39C2wyWqb2jbYF9VNTEv4FeBfwAe0g7v2b4fDHwVeAiwH3ANsFOf+U8A3tSnfeD8wMXAs2nupfifwIuWqr522sfRnIi/Htijz/h/BVzbM3wRMD2K9TeoPmA1cMWA6ee9/pa4xqcCj20/Pxm4cZmtw6Fsg0P6Pfmlns//CfjgctoOB9U3rO1wCesbyjY46DVpewbHASdV1U8Bqmpb234U8Imq+mlVfRv4Fk0XGfPVd/4kq2j+Yb9Yzb/Y3wJHL3F97wHeTJ+b9lqvAk6f/4828vp+YQHrb8lqrKqvVNX2e1+uBB6a5CE7+hlGVd+g+cexDqvq9p7BXen/bz227XCe9f3CqH+PB9U3xG2wr0kLgwOB5yb5UpJ/TPKMtr1fdxj7DFjGa9tduFOT7LaD+fdpP89nuYuuL8mRNH8tfHWO73gF9/8l/Ei7a/nHO9j9Xcr69kvylXa5z+1Zbpf1t9Q1bvcy4Cvbf9lb416Hw9oGF11jW+d/TfJd4NXA2/tMMrbtcAf1DWM7HMX6W8w22NeyuM9gmJL8A7B3n1Fvo/l5dwOeBTwDODPJ/syzOwzgb4B3tOPeAfw58B/mmL9f+zOTXDHs+pI8vF3Gr/WZdvs0zwTuqqre7391Vd2Y5JHAp4CvDdiOlrK+m4DHV9UtSZ4O/H2SJw1a7lL9G89zHT4JeOesaZbDOuyyDS7ZOvxFY9XbgLcleQvwWuBPen6OsW2HO6ivy3a4JL/HO6gPmPc2+BqaPZh5e8CFQVW9cNC4JMcBZ7e7ehcnuYemc6h5dYdRVVt7lvVh4Nx2cND8N7Sfe9vPrqrfXYL6nkhzHPKr7S/RvsCXkxxaVTe307ySWX+NVdWN7fsdST5Oc9zxtWOob/su9aVJrqH566rf+vveoPW31DUm2Rf4NPDvq+qa7TMth3U4x/yjXoezfRw4j57/zBjvdjiwvvav7Pluh0v1ezywvnbZ890GD6VjGCz4ZMNKfAG/B/xp+/lAml22AE/ivid2rqX/iZ1VPZ//gOb4H3PNT9PVxrO498TTi5eqvlnLuo6eE8g0hwRvAPbvaduZe09APhg4C/i9UdcHTPWsr/2BG4Hdu66/Ja7x0e38L5s1zXJZh0PZBof0e3JAz+fXAWctp+1wUH3D2g6XsL6hbIMD6+46w0p+AbsAHwWuAL4MvKBn3Ntozu5voedKAeBk2rP0wN8BXwMup+k7adU85p9uv+8a4K9p7/peivpmLes67hsGzwf+edY0uwKXtj/PlcB7+22cS10fzfHPK9sN/cvAv1vI+lviGv8IuBO4rOe153JZh8PaBof0e/Kpdt7Lgc8A+yyn7XBQfcPaDpewvqFsg4NedkchSZq4q4kkSX0YBpIkw0CSZBhIkjAMJEkYBpowaXqerQGv3x53fdK4PODuQJbm4TbgiD7t3xp1IdJyYRhoEt1dVf88ji9O8rCq+vE4vluai4eJpB5pHnBSSX4ryYeS3JbkhiQnJnnQrGmfnOS8JHe0r08m2btn/PPbZf16ko1JfkRz9ypJ/nWS/5vkJ2kegPLiJJuTnNaOf0mah7/sN+s792vbj1z6taFJYhhoIiXZefZr1iTvAn4E/CZN1wJvbz9vn/+Xgf8DPJSmh8jfoel75jN9ug8+haaLgyOBU9reRy8AHkbTr/+f0Tyj4PE983yWphOzNbOW9TvADHB+959aGszDRJpEjwH+3+zGWX+Ff6Gq3th+vjDJEcBLgTPbtj8BbqbpX+Zn7fyXA98AXkzT0+R2n6yqP+75nuPbGqar7W2y7SHzS9unqaqft3sJa5KcWFXVhswa4O+q6u4F//RSH+4ZaBLdRtPP/OxXb3fCn5s1z9e5bzfGL6TpSvienj2Lb9N0Hjc9a97zZg0/A7h0exAAVNXFwNZZ050KPIGmczdoHqf4BOAjc/500gK4Z6BJdHdVbe43oucIz62zRv2M5pDQdnsAf9i+ZnvcrOHZ/8nvTXOoZ7b7tFXVtUkuAo4BPt++X1xVV/arXVoMw0BamB/Q7Bmc3Gfc92cNz+4a+GbgoD7zTfVpOxn4cPvEq5cCb+wzjbRohoG0MJuAJ9Mc7unaD/wlwKuT7NNzzuBQYK8+054NvB/4BM1h3U8svGRpMMNAk2jnJM/q0/7dPm2DnABcDJyX5FSavYF9gMOB06rqojnm/QjNg0rOTXIizVVFJ9IcJrqnd8Kq+kmSjwHHA6dX1a0dapTmzRPImkSPAr7Y53XMfBdQVd+keQziXcB6mkchnkjzDN0572Suqrto7oD+MXAGTbC8meY8xe19Zvn79v3U+dYndeWTzqRloL2s9ZvA2qr6yKxx7wJeAexXVff0m19aLA8TSWPQnhD+HnA9zc1mb6E5TPSpnmkOAg4GjgNONAi0lAwDaTyK5sa1x9IcWvon4E1V1XuY6EPAM4GNwPtGXqEmioeJJEmeQJYkGQaSJAwDSRKGgSQJw0CSBPx/O5EOnu1BlYEAAAAASUVORK5CYII=",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "plt.hist(response.energies, bins=15)\n",
    "plt.xlabel('Energy', fontsize=15)\n",
    "plt.ylabel('Frequency', fontsize=15)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "K61nEm6fQyPl"
   },
   "source": [
    "エネルギーが低いほど良い状態を算出したことになりますが、稀にエネルギーが高い状態も算出されていることが上のヒストグラムからわかります。しかし大半の計算結果はエネルギーが最低の状態を算出しています。\n",
    "解いた(サンプルした)状態のうち一番低い解が最適解に近いはずなので、その解を.statesから探しましょう。\n",
    "> 注意: SAは必ずしも最適解を導くものではありません。よってエネルギーが一番低い解を選んでも最適解であるという保証はありません。あくまで近似解です。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Sample(sample={0: 0, 1: 0, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 0, 8: 1, 9: 0, 10: 1, 11: 1, 12: 0, 13: 0, 14: 0, 15: 0, 16: 1, 17: 0, 18: 0, 19: 1, 20: 1, 21: 0, 22: 1, 23: 1, 24: 1, 25: 1, 26: 0, 27: 0, 28: 1, 29: 1, 30: 1, 31: 1, 32: 1, 33: 1, 34: 1, 35: 1, 36: 1, 37: 0, 38: 0, 39: 0, 40: 1, 41: 1, 42: 1, 43: 1, 44: 1, 45: 0, 46: 0, 47: 0, 48: 0, 49: 1}, energy=-60.506889856831975, num_occurrences=1)"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import numpy as np\n",
    "\n",
    "min_samples = response.first\n",
    "min_samples"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "XBp8gWZEQyPr"
   },
   "source": [
    "これでエネルギーが最も低い解を得ることができました。この`.first`に入っている状態が、今回得られた近似解です。これで「問題を近似的に解いた」ということになります。\n",
    "\n",
    "ここで`num_occurrences`は計算の結果その状態が何回出力されたかを表しています。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "colab": {
   "include_colab_link": true,
   "name": "1-Introduction.ipynb",
   "provenance": [],
   "version": "0.3.2"
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.6"
  },
  "vscode": {
   "interpreter": {
    "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}