{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "d31e7ada-7707-4f25-9058-c895d1b205fc",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Global Data Quantumのポートフォリオ・オプティマイザーによる動的なポートフォリオ最適化\"\n",
        "description: \"グローバル・データ・クォンタムの量子ポートフォリオ最適化ツールを用いて、公益事業規模の動的最適化問題を解決する。\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore fontsize rebalance patheffects labelsize */}\n",
        "\n",
        "<span id=\"dynamic-portfolio-optimization-with-global-data-quantums-portfolio-optimizer\" />\n",
        "\n",
        "# Global Data Quantumのポートフォリオ・オプティマイザーによる動的なポートフォリオ最適化\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "36a8cc7a-179a-45f7-b28e-a2b4bbeaabd3",
      "metadata": {},
      "source": [
        "<Admonition type=\"note\" title=\"注\">\n",
        "  Qiskit 関数は、 IBM Quantum® Premium Plan、Flex Plan、およびオンプレム ( IBM Quantum Platform API 経由) Plan ユーザーだけが利用できる実験的な機能です。 これらはプレビューリリースであり、変更される可能性がある。\n",
        "</Admonition>\n",
        "\n",
        "*使用時間の目安：Heron r2 プロセッサで約55分。 (注：これはあくまでも目安です。 実際の走行時間は異なるかもしれない)。*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "16f9751a-3f70-4c1c-a384-78f402567279",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## 背景\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "80d86936-8073-415e-a024-2adbd0efef2a",
      "metadata": {},
      "source": [
        "動的ポートフォリオ最適化問題は、複数の期間にわたって、期待されるポートフォリオのリターンを最大化し、リスクを最小化する最適な投資戦略を見つけることを目的としており、多くの場合、予算、取引コスト、リスク回避などの特定の制約の下で行われる。 ポートフォリオのリバランスを1回で行う標準的なポートフォリオ最適化とは異なり、ダイナミック・バージョンは資産の進化を考慮し、時間の経過に伴う資産パフォーマンスの変化に基づいて投資を適応させる。\n",
        "\n",
        "このチュートリアルでは、Quantum Portfolio Optimizer Qiskit Functionを使用して動的ポートフォリオ最適化を実行する方法を示します。 具体的には、この応用関数を使って複数の時間ステップにわたる投資配分問題を解く方法を説明する。\n",
        "\n",
        "このアプローチでは、ポートフォリオ最適化を多目的二元制約最適化（QUBO）問題として定式化する。 具体的には、4つの異なる目的を同時に最適化するためにQUBO関数 $O$ ：\n",
        "\n",
        "* 戻り関数を最大化する $F$\n",
        "* 投資のリスクを最小限に抑える $R$\n",
        "* 取引コストの最小化 $C$\n",
        "* $P$ を最小化するために追加的に設定された投資制限を遵守すること。\n",
        "\n",
        "まとめると、これらの目的に取り組むために、QUBO関数を次のように定式化する。 $O = -F + \\frac{\\gamma}{2} R + C + \\rho P,$ ここで、 $\\gamma$ はリスク回避係数、 $\\rho$ は制限強化係数（ラグランジュ乗数）である。 明示的な定式化は式（1）にある。 [\\[1\\]](#references) の(15)である。\n",
        "\n",
        "Variational Quantum Eigensolver (VQE)に基づくハイブリッド量子古典法を用いて解く。 このセットアップでは、量子回路がコスト関数を推定し、古典的最適化は差分進化アルゴリズムを用いて実行される。 必要な量子ビットの数は、3つの主な要因に依存する：資産の数 `na`、時間期間の数 `nt`、投資を表現するために使用されるビット分解能 `nq`。 具体的には、この問題の最小量子ビット数は `na*nt*nq` である。\n",
        "\n",
        "このチュートリアルでは、スペインのIBEX35指数に基づく地域別ポートフォリオの最適化に焦点を当てる。 具体的には、下表に示す7つの資産ポートフォリオを使用している：\n",
        "\n",
        "| **IBEX35ポートフォリオ** | ACS.MC | ITX.MC | FER.MC | ELE.MC | SCYR.MC | AENA.MC | AMS.MC |\n",
        "| ----------------- | ------ | ------ | ------ | ------ | ------- | ------- | ------ |\n",
        "\n",
        "ポートフォリオのリバランスは、2022年11月1日から30日間隔で4回実施する。 各投資変数は2ビットで符号化される。 この結果、問題を解くのに56量子ビットを必要とする。\n",
        "\n",
        "私たちは、この種の金融最適化問題のパフォーマンスを向上させるために特別に調整された、標準的なReal Amplitudes assatzをカスタマイズし、ハードウェア効率よく適応させたOptimized Real Amplitudes assatzを使用しています。\n",
        "\n",
        "クォンタムの実行は、 `ibm_torino` バックエンドで行われる。 問題の定式化、方法論、性能評価に関する詳細な説明は、公表された原稿 [\\[1\\]](#references) を参照されたい。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3716659d-a44f-448b-a131-e0044b05d2ed",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## 要件\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "49cddc2b-6759-4d18-aa1c-c38c344139d7",
      "metadata": {},
      "outputs": [],
      "source": [
        "!pip install qiskit-ibm-catalog\n",
        "!pip install pandas\n",
        "!pip install matplotlib\n",
        "!pip install yfinance"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ab6114f0-fad3-4f44-809d-3ff966f2b2ea",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## セットアップ\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "689bf5f3-8332-4a7c-b62e-047566c1971f",
      "metadata": {},
      "source": [
        "Quantum Portfolio Optimizerを使用するには、Qiskit Functions Catalogから機能を選択します。 この機能を実行するには、 IBM Quantum Premium PlanまたはFlex PlanアカウントとGlobal Data Quantumからのライセンスが必要です。\n",
        "\n",
        "まず、 [APIキー]() で認証を行ってください。 次に、 Qiskit Functions Catalog から目的の関数を読み込みます。 ここでは、クラス `QiskitFunctionsCatalog` を使用して、カタログ内の関数 `quantum_portfolio_optimizer` にアクセスしています。 この関数を使用すると、Quantumの Portfolio Optimization ソルバーを利用できます。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "301a8a04-3106-478b-816e-5d5f0a3c0737",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit_ibm_catalog import QiskitFunctionsCatalog\n",
        "\n",
        "catalog = QiskitFunctionsCatalog(\n",
        "    channel=\"ibm_quantum_platform\",\n",
        "    instance=\"INSTANCE_CRN\",\n",
        "    # For `token`, use the 44-character API_KEY you created\n",
        "    # and saved from the IBM Quantum Platform Home dashboard\n",
        "    token=\"YOUR_API_KEY\",\n",
        ")\n",
        "\n",
        "# Access function\n",
        "dpo_solver = catalog.load(\"global-data-quantum/quantum-portfolio-optimizer\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0856b7ca-8cf5-457d-ac08-df83a02852af",
      "metadata": {},
      "source": [
        "<span id=\"step-1-read-the-input-portfolio\" />\n",
        "\n",
        "## ステップ1: 入力ポートフォリオを読み込む\n",
        "\n",
        "このステップでは、IBEX35指数から選択した7つの資産のヒストリカルデータを、特に **2022年11月1** 日から **2023年4月1** 日までロードする。\n",
        "\n",
        "ヤフーファイナンスのAPIを使って、終値に注目してデータを取得した。 その後、すべての資産が同じ日数のデータを持つようにデータが処理される。 欠落データ（非取引日）は適切に処理され、すべての資産が同じ日付で整列される。\n",
        "\n",
        "データは、すべての資産で一貫したフォーマットで DataFrame。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "57ba1c16-4670-4557-9a16-2034be98f471",
      "metadata": {},
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "[*********************100%***********************]  1 of 1 completed"
          ]
        },
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "\n",
            "[*********************100%***********************]  1 of 1 completed\n",
            "[*********************100%***********************]  1 of 1 completed\n",
            "[*********************100%***********************]  1 of 1 completed\n",
            "[*********************100%***********************]  1 of 1 completed\n",
            "[*********************100%***********************]  1 of 1 completed\n",
            "[*********************100%***********************]  1 of 1 completed"
          ]
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Downloading data for ACS.MC...\n",
            "Downloading data for ITX.MC...\n",
            "Downloading data for FER.MC...\n",
            "Downloading data for ELE.MC...\n",
            "Downloading data for SCYR.MC...\n",
            "Downloading data for AENA.MC...\n",
            "Downloading data for AMS.MC...\n"
          ]
        },
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "\n"
          ]
        }
      ],
      "source": [
        "import yfinance as yf\n",
        "import pandas as pd\n",
        "\n",
        "# List of IBEX 35 symbols\n",
        "symbols = [\n",
        "    \"ACS.MC\",\n",
        "    \"ITX.MC\",\n",
        "    \"FER.MC\",\n",
        "    \"ELE.MC\",\n",
        "    \"SCYR.MC\",\n",
        "    \"AENA.MC\",\n",
        "    \"AMS.MC\",\n",
        "]\n",
        "\n",
        "start_date = \"2022-11-01\"\n",
        "end_date = \"2023-4-01\"\n",
        "\n",
        "series_list = []\n",
        "symbol_names = [symbol.replace(\".\", \"_\") for symbol in symbols]\n",
        "\n",
        "# Create a full date index including weekends\n",
        "full_index = pd.date_range(start=start_date, end=end_date, freq=\"D\")\n",
        "\n",
        "for symbol, name in zip(symbols, symbol_names):\n",
        "    print(f\"Downloading data for {symbol}...\")\n",
        "    data = yf.download(symbol, start=start_date, end=end_date)[\"Close\"]\n",
        "    data.name = name\n",
        "\n",
        "    # Reindex to include weekends\n",
        "    data = data.reindex(full_index)\n",
        "\n",
        "    # Fill missing values (for example, weekends or holidays)\n",
        "    # by forward/backward fill\n",
        "    data.ffill(inplace=True)\n",
        "    data.bfill(inplace=True)\n",
        "\n",
        "    series_list.append(data)\n",
        "\n",
        "# Combine all series into a single DataFrame\n",
        "assets = pd.concat(series_list, axis=1)\n",
        "\n",
        "# Convert index to string for consistency\n",
        "assets.index = assets.index.astype(str)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "574e52d6-c87e-4eb4-a2b6-eee8954231aa",
      "metadata": {},
      "source": [
        "<Admonition type=\"caution\" title=\"警告\">\n",
        "  Yahoo Finance APIからデータが正しくダウンロードされたことを確認するため、以下のセルを実行することをお勧めします。 ダウンロードが失敗した場合、通知が表示され、代わりに代替オンラインデータベースからデータを取得することが示されます。\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "be78a997-678f-4bd0-a0c5-1a04c8a94afb",
      "metadata": {},
      "outputs": [],
      "source": [
        "if (\n",
        "    assets.isna().values.any()\n",
        "):  # If has nan values, load the data from the fallback online database\n",
        "    print(\n",
        "        \"WARNING: Asset data format is invalid or incomplete. \"\n",
        "        \"Loading data from the remote backup database.\"\n",
        "    )\n",
        "\n",
        "    url = \"https://raw.githubusercontent.com/Global-Data-Quantum/PortfolioData/1f3ebb95fdef245014a04d4273f688a2951e0061/data/qpo_tutorial_ibex.csv\"\n",
        "    assets = pd.read_csv(url, index_col=0)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "fbd9f5aa-dc2d-48bb-805f-ae0d55aedc54",
      "metadata": {},
      "source": [
        "<span id=\"step-2-define-the-problem-inputs\" />\n",
        "\n",
        "## ステップ2: 問題の入力定義\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8f066377-62fb-46fd-ae1a-1058d99c5f7f",
      "metadata": {},
      "source": [
        "QUBO問題を定義するために必要なパラメータは、 `qubo_settings` 辞書で設定される。 時間ステップ数(`nt`)、投資指定のビット数(`nq`)、各時間ステップの時間窓(`dt`)を定義する。さらに、資産ごとの最大投資額、リスク回避係数、取引手数料、制限係数を設定する（問題定式化の詳細については[我々の論文を](https://arxiv.org/pdf/2412.19150)参照）。 これらの設定により、QUBO問題を特定の投資シナリオに適合させることができる。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "d5e8137c-e3ef-45a7-928f-3bd6d02402cd",
      "metadata": {},
      "outputs": [],
      "source": [
        "qubo_settings = {\n",
        "    \"nt\": 4,\n",
        "    \"nq\": 2,\n",
        "    \"dt\": 30,\n",
        "    # maximum investment per asset is 2**nq/max_investment = 80%\n",
        "    \"max_investment\": 5,\n",
        "    \"risk_aversion\": 1000.0,\n",
        "    \"transaction_fee\": 0.01,\n",
        "    \"restriction_coeff\": 1.0,\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "06608c0a-34ac-47b4-84c5-da98458e6dab",
      "metadata": {},
      "source": [
        "この `optimizer_settings` 辞書は、反復回数や1世代 `population_size` あたりの候補解 `num_generations` の数などのパラメータを含め、最適化プロセスを設定します。 その他の設定では、組換え率、並列ジョブ数、バッチサイズ、変異範囲などの項目を制御します。 さらに、、 `estimator_precision`、 `estimator_shots`などのプリミティブ設定は、最適 `sampler_shots`化プロセスにおける量子推定器およびサンプリングアルゴリズムの設定を定義します。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "d4946f9e-91bf-49a2-9970-2239c877b544",
      "metadata": {},
      "outputs": [],
      "source": [
        "optimizer_settings = {\n",
        "    \"de_optimizer_settings\": {\n",
        "        \"num_generations\": 20,\n",
        "        \"population_size\": 40,\n",
        "        \"recombination\": 0.4,\n",
        "        \"max_parallel_jobs\": 5,\n",
        "        \"max_batchsize\": 4,\n",
        "        \"mutation_range\": [0.0, 0.25],\n",
        "    },\n",
        "    \"optimizer\": \"differential_evolution\",\n",
        "    \"primitive_settings\": {\n",
        "        \"estimator_shots\": 25_000,\n",
        "        \"estimator_precision\": None,\n",
        "        \"sampler_shots\": 100_000,\n",
        "    },\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "fd9b0940-54b2-4ad1-833e-36b61b90cb46",
      "metadata": {},
      "source": [
        "<Admonition type=\"Note\">\n",
        "  回路総数は `optimizer_settings` のパラメータに依存し、 `(num_generations + 1) * population_size` として計算される。\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "866224f8-c27e-43c6-8906-dab87d7a4219",
      "metadata": {},
      "source": [
        "辞書 `ansatz_settings` は量子回路アンザッツを設定する。 この `ansatz` パラメータは、金融最適化問題向けに設計されたハードウェア効率の高い `\"optimized_real_amplitudes\"` アプローチの使用を指定します。 複数のパスマネージャー（デフォルトのローカルQiskitパスマネージャーやQiskit AI搭載トランスパイラサービスを含む）を使用して回路最適化の性能と効率を向上させるオプション `multiple_passmanager` は、現在falseに設定されています。この機能は一時的に無効化されているためです。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "0054fbb0-75f0-468a-acc9-0066eabb85dd",
      "metadata": {},
      "outputs": [],
      "source": [
        "ansatz_settings = {\n",
        "    \"ansatz\": \"optimized_real_amplitudes\",\n",
        "    \"multiple_passmanager\": False,\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "76087a8d-a1f5-4adb-a96d-7c17b7c67cdf",
      "metadata": {},
      "source": [
        "最後に、準備された入力を渡して `dpo_solver.run()` 関数を実行し、最適化を実行する。 これには、アセットデータディクショナリ(`assets`)、QUBO設定(`qubo_settings`)、最適化パラメータ(`optimizer_settings`)、量子回路ansatz設定(`ansatz_settings`)が含まれます。さらに、バックエンドや結果に後処理を適用するかどうかなど、実行の詳細を指定します。 これにより、選択された量子バックエンド上で動的ポートフォリオ最適化プロセスが開始される。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "c24af785-105d-45db-a242-3f215c845cf6",
      "metadata": {},
      "outputs": [],
      "source": [
        "dpo_job = dpo_solver.run(\n",
        "    assets=assets.to_dict(),  # Insert assets as a dictionary\n",
        "    qubo_settings=qubo_settings,\n",
        "    optimizer_settings=optimizer_settings,\n",
        "    ansatz_settings=ansatz_settings,\n",
        "    backend_name=\"ibm_torino\",\n",
        "    previous_session_id=[],\n",
        "    apply_postprocess=True,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7bc6ac05-5c0b-418e-9978-8dd5bdcd97c4",
      "metadata": {},
      "source": [
        "<span id=\"step-3-analyze-the-optimization-results\" />\n",
        "\n",
        "## ステップ3：最適化結果を分析する\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0c248fb7-0605-4456-a5ac-ced65b7593e7",
      "metadata": {},
      "source": [
        "ここでは、最適化結果から目的コストが最も低い解を抽出して表示する。 最小目的コストとともに、制限偏差、シャープレシオ、投資リターンなど、関連する解に関連する主要な指標も示す。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "3b41e88f-9f5e-4663-a225-ec732544a36a",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "{'time_step_0': {'ACS.MC': 0.11764705882352941,\n",
              "  'ITX.MC': 0.20588235294117646,\n",
              "  'FER.MC': 0.38235294117647056,\n",
              "  'ELE.MC': 0.058823529411764705,\n",
              "  'SCYR.MC': 0.0,\n",
              "  'AENA.MC': 0.058823529411764705,\n",
              "  'AMS.MC': 0.17647058823529413},\n",
              " 'time_step_1': {'ACS.MC': 0.11428571428571428,\n",
              "  'ITX.MC': 0.14285714285714285,\n",
              "  'FER.MC': 0.2,\n",
              "  'ELE.MC': 0.02857142857142857,\n",
              "  'SCYR.MC': 0.42857142857142855,\n",
              "  'AENA.MC': 0.0,\n",
              "  'AMS.MC': 0.08571428571428572},\n",
              " 'time_step_2': {'ACS.MC': 0.0,\n",
              "  'ITX.MC': 0.09375,\n",
              "  'FER.MC': 0.3125,\n",
              "  'ELE.MC': 0.34375,\n",
              "  'SCYR.MC': 0.0,\n",
              "  'AENA.MC': 0.0,\n",
              "  'AMS.MC': 0.25},\n",
              " 'time_step_3': {'ACS.MC': 0.3939393939393939,\n",
              "  'ITX.MC': 0.09090909090909091,\n",
              "  'FER.MC': 0.12121212121212122,\n",
              "  'ELE.MC': 0.18181818181818182,\n",
              "  'SCYR.MC': 0.0,\n",
              "  'AENA.MC': 0.0,\n",
              "  'AMS.MC': 0.21212121212121213}}"
            ]
          },
          "execution_count": 8,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Get the results of the job\n",
        "dpo_result = dpo_job.result()\n",
        "\n",
        "# Show the solution strategy\n",
        "dpo_result[\"result\"]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "48b545ed-5350-4a72-b792-ad4c4400c833",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Minimum Objective Cost Found: -3.67\n",
            "Best Solution:\n",
            "  - Restriction Deviation: 40.0%\n",
            "  - Sharpe Ratio: 14.54\n",
            "  - Return: 0.28\n"
          ]
        }
      ],
      "source": [
        "import pandas as pd\n",
        "\n",
        "# Get results from the job\n",
        "dpo_result = dpo_job.result()\n",
        "\n",
        "# Convert metadata to a DataFrame, excluding 'session_id'\n",
        "df = pd.DataFrame(dpo_result[\"metadata\"][\"all_samples_metrics\"])\n",
        "\n",
        "# Find the minimum objective cost\n",
        "min_cost = df[\"objective_costs\"].min()\n",
        "print(f\"Minimum Objective Cost Found: {min_cost:.2f}\")\n",
        "\n",
        "# Extract the row with the lowest cost\n",
        "best_row = df[df[\"objective_costs\"] == min_cost].iloc[0]\n",
        "\n",
        "# Display the results associated with the best solution\n",
        "print(\"Best Solution:\")\n",
        "print(f\"  - Restriction Deviation: {best_row['rest_breaches']}%\")\n",
        "print(f\"  - Sharpe Ratio: {best_row['sharpe_ratios']:.2f}\")\n",
        "print(f\"  - Return: {best_row['returns']:.2f}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "09116415-afbc-4e89-b3fd-9464b8f1d5df",
      "metadata": {},
      "source": [
        "以下のコードは、最適化アルゴリズムのコスト分布をランダムサンプリング分布と可視化して比較する方法を示しています。 同様に、ランダムな投資で評価することで、QUBO目的関数（関数出力から読み込める）のランドスケープを探索する。 両方の分布を振幅で正規化してプロットし、最適化プロセスがランダムサンプリングと比べてコスト面でどのように異なるかを比較しやすくした。 さらに、DOCPlexを用いて得られた結果は、従来のベンチマークとして機能する破線の垂直基準線として含まれている。 我々は、同じ問題を従来の手法で解くために、数学的最適化のための IBM® オープンソースライブラリ[である](https://ibmdecisionoptimization.github.io/docplex-doc/)Python DOCPlexの無料版を使用する。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "2dd1d015-3015-409f-b5d9-016c8ff592ce",
      "metadata": {},
      "outputs": [],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "from matplotlib.ticker import MultipleLocator\n",
        "import matplotlib.patheffects as patheffects\n",
        "\n",
        "\n",
        "def plot_normalized(dpo_x, dpo_y_normalized, random_x, random_y_normalized):\n",
        "    \"\"\"\n",
        "    Plots normalized results for two sampling results.\n",
        "\n",
        "    Parameters:\n",
        "        dpo_x (array-like): X-values for the VQE Post-processed curve.\n",
        "        dpo_y_normalized (array-like): Y-values (normalized) for the\n",
        "        VQE Post-processed curve.\n",
        "        random_x (array-like): X-values for the Noise (Random) curve.\n",
        "        random_y_normalized (array-like): Y-values (normalized) for the\n",
        "        Noise (Random) curve.\n",
        "    \"\"\"\n",
        "    plt.figure(figsize=(6, 3))\n",
        "    plt.tick_params(axis=\"both\", which=\"major\", labelsize=12)\n",
        "\n",
        "    # Define custom colors\n",
        "    colors = [\"#4823E8\", \"#9AA4AD\"]\n",
        "\n",
        "    # Plot DPO results\n",
        "    (line1,) = plt.plot(\n",
        "        dpo_x, dpo_y_normalized, label=\"VQE Postprocessed\", color=colors[0]\n",
        "    )\n",
        "    line1.set_path_effects(\n",
        "        [patheffects.withStroke(linewidth=3, foreground=\"white\")]\n",
        "    )\n",
        "\n",
        "    # Plot Random results\n",
        "    (line2,) = plt.plot(\n",
        "        random_x, random_y_normalized, label=\"Noise (Random)\", color=colors[1]\n",
        "    )\n",
        "    line2.set_path_effects(\n",
        "        [patheffects.withStroke(linewidth=3, foreground=\"white\")]\n",
        "    )\n",
        "\n",
        "    # Set X-axis ticks to increment by 5 units\n",
        "    plt.gca().xaxis.set_major_locator(MultipleLocator(5))\n",
        "\n",
        "    # Axis labels and legend\n",
        "    plt.xlabel(\"Objective cost\", fontsize=14)\n",
        "    plt.ylabel(\"Normalized Counts\", fontsize=14)\n",
        "\n",
        "    # Add DOCPLEX reference line\n",
        "    plt.axvline(\n",
        "        x=-4.11, color=\"black\", linestyle=\"--\", linewidth=1, label=\"DOCPlex\"\n",
        "    )  # DOCPlex value\n",
        "    plt.ylim(bottom=0)\n",
        "\n",
        "    plt.legend()\n",
        "\n",
        "    # Adjust layout\n",
        "    plt.tight_layout()\n",
        "    plt.show()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "6b662682-279b-48b5-bc61-681846cf3c00",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/global-data-quantum-optimizer/extracted-outputs/6b662682-279b-48b5-bc61-681846cf3c00-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "import numpy as np\n",
        "from collections import defaultdict\n",
        "\n",
        "# ================================\n",
        "# STEP 1: DPO COST DISTRIBUTION\n",
        "# ================================\n",
        "\n",
        "# Extract data from DPO results\n",
        "counts_list = dpo_result[\"metadata\"][\"all_samples_metrics\"][\n",
        "    \"counts\"\n",
        "]  # List of how many times each solution occurred\n",
        "cost_list = dpo_result[\"metadata\"][\"all_samples_metrics\"][\n",
        "    \"objective_costs\"\n",
        "]  # List of corresponding objective function values (costs)\n",
        "\n",
        "# Round costs to one decimal and accumulate counts for each unique cost\n",
        "dpo_counter = defaultdict(int)\n",
        "for cost, count in zip(cost_list, counts_list):\n",
        "    rounded_cost = round(cost, 1)\n",
        "    dpo_counter[rounded_cost] += count\n",
        "\n",
        "# Prepare data for plotting\n",
        "dpo_x = sorted(dpo_counter.keys())  # Sorted list of cost values\n",
        "dpo_y = [dpo_counter[c] for c in dpo_x]  # Corresponding counts\n",
        "\n",
        "# Normalize the counts to the range [0, 1] for better comparison\n",
        "dpo_min = min(dpo_y)\n",
        "dpo_max = max(dpo_y)\n",
        "dpo_y_normalized = [\n",
        "    (count - dpo_min) / (dpo_max - dpo_min) for count in dpo_y\n",
        "]\n",
        "\n",
        "# ================================\n",
        "# STEP 2: RANDOM COST DISTRIBUTION\n",
        "# ================================\n",
        "\n",
        "# Read the QUBO matrix\n",
        "qubo = np.array(dpo_result[\"metadata\"][\"qubo\"])\n",
        "\n",
        "bitstring_length = qubo.shape[0]\n",
        "num_random_samples = 100_000  # Number of random samples to generate\n",
        "random_cost_counter = defaultdict(int)\n",
        "\n",
        "# Generate random bitstrings and calculate their cost\n",
        "for _ in range(num_random_samples):\n",
        "    x = np.random.randint(0, 2, size=bitstring_length)\n",
        "    cost = float(x @ qubo @ x.T)\n",
        "    rounded_cost = round(cost, 1)\n",
        "    random_cost_counter[rounded_cost] += 1\n",
        "\n",
        "# Prepare random data for plotting\n",
        "random_x = sorted(random_cost_counter.keys())\n",
        "random_y = [random_cost_counter[c] for c in random_x]\n",
        "\n",
        "# Normalize the random cost distribution\n",
        "random_min = min(random_y)\n",
        "random_max = max(random_y)\n",
        "random_y_normalized = [\n",
        "    (count - random_min) / (random_max - random_min) for count in random_y\n",
        "]\n",
        "\n",
        "# ================================\n",
        "# STEP 3: PLOTTING\n",
        "# ================================\n",
        "\n",
        "plot_normalized(dpo_x, dpo_y_normalized, random_x, random_y_normalized)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "457ecd2e-50e5-49ea-b435-fb26e43a3266",
      "metadata": {},
      "source": [
        "グラフは、量子ポートフォリオ・オプティマイザーがいかに一貫して最適化された投資戦略を返すかを示している。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4f996a6d-b696-4d33-b832-97bda30b3969",
      "metadata": {},
      "source": [
        "<span id=\"references\" />\n",
        "\n",
        "## 参照\n",
        "\n",
        "\\[1] [Nodar, Álvaro, Irene De León, Danel Arias, Ernesto Mamedaliev, María Esperanza Molina, Manuel Martín-Cordero, Senaida Hernández-Santana ほか. 「動的 Portfolio Optimization のための変分量子固有値ソルバーのスケーリング」 arXiv プレプリント arXiv:2412.19150 (2024)](https://arxiv.org/pdf/2412.19150).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "819996e9-e98d-4753-9d6a-be0ab443088f",
      "metadata": {},
      "source": [
        "<span id=\"tutorial-survey\" />\n",
        "\n",
        "## チュートリアル調査\n",
        "\n",
        "このチュートリアルに関するフィードバックをお寄せください。 あなたの洞察は、私たちのコンテンツの提供とユーザーエクスペリエンスを向上させるのに役立ちます。\n",
        "[アンケートへのリンク](https://your.feedback.ibm.com/jfe/form/SV_3BLFkNVEuh0QBWm)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "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"
    },
    "hours": 1.5,
    "qpuSeconds": 3300
  },
  "nbformat": 4,
  "nbformat_minor": 2
}