[
  {
    "id": "boqa-intro",
    "notebook": "notebooks/qubo_boqa_intro.ipynb",
    "image": "/assets/demo-boqa-intro.png",
    "category": "教程（基础）",
    "difficulty": 3,
    "title": "黑盒优化（1）",
    "summary": "从黑盒函数、代理模型和 CMA-ES 开始，理解 QUBO / BOQA 演示里的变量、目标和搜索过程。",
    "description": "入门案例：把昂贵实验或仿真抽象成只能输入参数、返回分数的黑盒函数，再用代理模型和连续黑盒优化寻找更好的候选。",
    "requiredAlgorithm": "cma_es",
    "requiredAlgorithmLabel": "CMA-ES",
    "setupCode": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：cma_es（CMA-ES）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"cma_es\", algorithm_label=\"CMA-ES\", key=QUBO_TEST_KEY)\n",
    "runCode": "# 这格是自包含的：如果用户没有先运行上面的变量格，这里也会补上 ranges。\nif \"ranges\" not in globals():\n    bounds = {\n        \"x0\": (0.0, 1.0),\n        \"x1\": (0.0, 1.0),\n        \"x2\": (0.0, 1.0),\n    }\n    ranges = [\n        {\"name\": name, \"min\": low, \"max\": high, \"initial\": (low + high) / 2}\n        for name, (low, high) in bounds.items()\n    ]\n\ncloud_result = run_boqa_solve(\n    problem=\"boqa_intro\",\n    engine=\"cpu\",\n    algorithm=\"cma_es\",\n    params={\n        \"case_id\": \"blackbox_1_boqa_intro\",\n        \"objective\": \"boqa_surrogate_intro_target\",\n        \"iterations\": 80,\n        \"ranges\": ranges,\n    },\n)\npprint(cloud_result[\"summary\"])\nplot_trace(cloud_result, \"BOQA API connectivity / BOQA intro\")\n",
    "lessonBlocks": [
      {
        "type": "code",
        "code": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：cma_es（CMA-ES）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"cma_es\", algorithm_label=\"CMA-ES\", key=QUBO_TEST_KEY)\n",
        "caption": "key setup"
      },
      {
        "type": "markdown",
        "title": "黑盒优化（1）：BOQA 入门与代理模型搜索",
        "body": [
          "教程（基础）",
          "从工程试验到代理模型的第一课",
          "这个 notebook 按“背景知识 -> 人工解法 -> 数学模型 -> 代码导读 -> 云端调用 -> 结果解释”的顺序展开，目标是给基础用户学习，而不只是演示一段代码。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "1. 背景、来历和相关学科",
        "body": [
          "这个案例讲的是黑盒优化的基本思想：系统内部公式未知，或者公式太贵太复杂，用户只能输入一组参数，然后得到一个实验、仿真或业务评价分数。",
          "BOQA 可以理解为“用历史评估训练一个便宜的代理模型，再让优化器在代理模型上寻找下一批候选”。它把工程师常见的试错、记录、比较、再试一轮，整理成可重复的流程。",
          "本案例是入门案例，重点不是某个具体工业设备，而是学习黑盒优化项目的通用语言：变量范围、目标函数、样本历史、代理模型、候选搜索和结果曲线。",
          "黑盒优化和实验设计、响应面法、贝叶斯优化、进化策略都有关系。早期工程实验常用少量样本估计趋势，现代做法会把历史样本喂给机器学习模型，用模型估计哪里更值得继续探索。",
          "Fixstars 的 BOQA 教学页采用的也是这种路径：先解释什么是黑盒函数，再给出变量、训练数据、代理模型和求解器。本门户借鉴这个教学顺序，但把入口换成用户自己的 QUBO 测试 key。",
          "相关知识点",
          "基础理论：黑盒优化、代理模型和试验预算",
          "黑盒优化的“黑盒”不是神秘算法，而是指目标函数不能直接写成公式。工程里常见的黑盒包括一次仿真、一次实验、一次设备试跑或一次用户策略回放。输入是一组参数，输出是成本、效率、温度、收益等指标。",
          "BOQA 的核心思想是先用少量已评估样本训练代理模型，再用优化器在代理模型上寻找下一批值得评估的候选点。这样做的目的不是替代真实实验，而是让有限预算更集中地花在可能有价值的区域。",
          "这个入门案例把参数归一化到 0 到 1，是为了把不同单位的工程量放到同一个搜索空间里。真实项目中，归一化之前仍要保留物理含义，例如厚度、流量、温度或配比。"
        ],
        "bullets": [
          "实验设计：决定先试哪些参数，避免样本全部挤在一个角落。",
          "机器学习代理模型：用已有样本近似未知目标函数。",
          "优化算法：在代理模型上寻找可能更优的候选。",
          "工程约束：变量范围、安全边界和成本限制不能被搜索过程破坏。"
        ]
      },
      {
        "type": "markdown",
        "title": "更完整的知识背景：从试验设计到 BOQA",
        "body": [
          "黑盒优化最早可以从工程试验设计里理解：当一次实验很贵时，人们不会盲目枚举所有参数，而会先设计少量代表性试验，再根据结果决定下一轮。响应面法、代理模型和现代贝叶斯优化都在解决同一个问题：用有限样本推断哪里更值得继续试。",
          "BOQA 是本演示库的品牌名称。代理模型可以采用 Factorization Machine 等方法学习变量之间的二阶相互作用，再借助 Ising/QUBO 求解器选择候选。这里介绍的是方法背景；当前案例调用远程求解接口，并未实现完整的代理模型训练、候选搜索与真实目标回评循环。",
          "初学者要抓住三句话：真实目标函数很贵；代理模型只是地图不是地形；优化器给出的候选还要回到真实实验或仿真里验证。"
        ],
        "bullets": [
          "相关学科：实验设计、统计学习、响应面法、贝叶斯优化、进化策略。",
          "核心矛盾：每次评估都花钱，所以要在探索未知区域和利用已知好区域之间取舍。",
          "工程落地：数据记录、变量边界、失败样本和安全约束往往比算法本身更决定效果。"
        ]
      },
      {
        "type": "markdown",
        "title": "读代码时要看的四条线",
        "body": [
          "第一条线是变量：代码里哪些字段代表可调参数，单位和上下界是什么。第二条线是目标：结果分数到底是要最小化成本、最大化效率，还是把最大化问题转成负号后的最小化问题。",
          "第三条线是历史：黑盒优化不是一次调用就结束，而是反复评估候选并更新“当前最好”。第四条线是接口：notebook 只发送问题名、参数范围和 key，真正求解发生在 BOQA solver 服务里。"
        ],
        "bullets": [
          "ranges 是搜索空间。",
          "run_boqa_solve 是云端调用入口。",
          "history 或 metadata.history 是画收敛曲线的依据。",
          "best_vector 要解码回业务参数，不能只看一串数字。"
        ]
      },
      {
        "type": "markdown",
        "title": "2. 如果不用代码，人会怎么解",
        "body": [
          "人工做黑盒优化时，工程师会先列出每个参数的安全范围，再做一批覆盖面较广的初始试验。",
          "得到分数后，人会观察哪些参数组合更好，继续在好区域附近加密试验，同时保留少量探索，防止错过远处更好的区域。",
          "BOQA 的价值是把这种经验流程变成程序：历史样本会变成训练数据，下一轮候选由优化器提出，结果可以复现、比较和审计。",
          "这一步很重要：人工解法不是被丢掉，而是帮助我们发现哪些规则应该变成变量、约束或惩罚项。优化模型只是把这些规则变成可重复执行的计算流程。",
          "手工推理：如果只有十次试验该怎么选",
          "人工做黑盒优化时通常不会一上来只盯着一个点，而会先在搜索空间里撒几组差异较大的参数，观察哪些方向让目标变好。",
          "看到某个区域表现好以后，人会在附近继续细调，同时保留少量探索名额去试远处区域，防止陷入局部最优。这就是“探索”和“利用”的平衡。",
          "CMA-ES 做的是类似动作：维护一群候选点，根据好点更新搜索分布，让下一轮候选更可能落在有希望的区域。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "3. 从业务问题到数学模型",
        "body": [
          "变量是连续工程参数，例如比例、温度、速度、几何尺寸或控制量。每个变量都有上下界。",
          "目标函数是黑盒评分，分数越低越好。真实项目里，这个评分可能来自仿真、实验台、客户指标或已有模型。",
          "代理模型只是一种近似，它不保证永远正确，所以黑盒优化通常要在“利用已知好区域”和“探索未知区域”之间平衡。",
          "本案例使用 cma_es key。CMA-ES 更适合连续变量和黑盒函数；如果使用 QUBO 专用 key，服务端会鉴权失败。",
          "读模型时抓住四件事"
        ],
        "bullets": [
          "变量：优化器能改变什么。",
          "目标函数：什么方案更好。",
          "硬约束：什么方案绝对不允许。",
          "解码器：如何把 bit vector 或参数向量翻译回业务语言。"
        ]
      },
      {
        "type": "markdown",
        "title": "4. 代码导读：准备数据、编码器和解码器",
        "body": [
          "bounds 定义每个参数的范围，是黑盒优化最重要的输入之一。",
          "ranges 把变量范围整理成云端 solver 能读懂的字段。",
          "这个入门案例不需要手写复杂 QUBO 矩阵，重点是让用户先看懂连续黑盒参数如何被提交给云端优化器。",
          "阅读下面代码时，可以先忽略 Python 细节，按注释找出“数据在哪里、变量怎么编号、惩罚项在哪里加入、最后怎么解码”。",
          "代码导读：bounds、problem 和 history",
          "bounds 描述每个变量的上下界。它不是装饰信息，solver 要靠它把无量纲向量还原成真实工程参数。",
          "run_boqa_solve(problem=\"boqa_intro\", algorithm=\"cma_es\") 表明这是连续黑盒搜索，不是二进制 QUBO。返回里的 best_vector 是当前最优参数向量，best_score 是被最小化的目标值。",
          "metadata.history 是算法过程记录。若服务端只返回尾部历史，图会从后半段迭代开始，这是后端返回格式限制，不代表只运行了一次。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "5. 代码导读：调用 BOQA solver",
        "body": [
          "run_boqa_solve 会把 problem、algorithm、params 和 key 一起发送到固定 solver endpoint。",
          "如果输出里出现 调用 BOQA solve: algorithm=cma_es，说明代码确实发起了云端请求。",
          "若返回 note 提示 objective_model 是占位模型，说明 API 和 key 已通，但后端还没有接入真实业务评价函数。",
          "本案例固定需要 cma_es（CMA-ES）授权。若 key 类型不匹配，错误通常会在云端鉴权处出现；若没有看到 调用 BOQA solve，说明还没有真正发出请求。",
          "算法和 key 为什么这样选",
          "本案例需要 cma_es key，因为变量是连续参数向量。kernel_qa 更适合离散 bit/QUBO，不适合作为这个入门连续调参案例的默认算法。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "# 这格是自包含的：如果用户没有先运行上面的变量格，这里也会补上 ranges。\nif \"ranges\" not in globals():\n    bounds = {\n        \"x0\": (0.0, 1.0),\n        \"x1\": (0.0, 1.0),\n        \"x2\": (0.0, 1.0),\n    }\n    ranges = [\n        {\"name\": name, \"min\": low, \"max\": high, \"initial\": (low + high) / 2}\n        for name, (low, high) in bounds.items()\n    ]\n\ncloud_result = run_boqa_solve(\n    problem=\"boqa_intro\",\n    engine=\"cpu\",\n    algorithm=\"cma_es\",\n    params={\n        \"case_id\": \"blackbox_1_boqa_intro\",\n        \"objective\": \"boqa_surrogate_intro_target\",\n        \"iterations\": 80,\n        \"ranges\": ranges,\n    },\n)\npprint(cloud_result[\"summary\"])\nplot_trace(cloud_result, \"BOQA API connectivity / BOQA intro\")\n"
      },
      {
        "type": "markdown",
        "title": "6. 结果怎么读",
        "body": [
          "结果曲线里的 eval 表示每轮候选的评分，best 表示到当前为止找到的最好评分。黑盒优化不要求每一步都变好，但 best 曲线应该整体向下。",
          "如果 best_vector 接近某个稳定区域，说明搜索已经收敛到一个候选参数附近；真实项目还需要把这个候选送回仿真或实验验证。",
          "曲线中的 eval 是每次候选解，best 是历史最好值。best 下降表示优化过程找到了更好的候选；如果只出现一个点，要查看 raw summary 是否只有单次结果或后端没有返回完整 history。"
        ],
        "bullets": []
      }
    ],
    "imageSet": {
      "width": 1672,
      "height": 941,
      "variants": [
        {
          "width": 480,
          "height": 270,
          "url": "/assets/demo-boqa-intro-480.3711cf3d66b9.webp",
          "bytes": 10466
        },
        {
          "width": 960,
          "height": 540,
          "url": "/assets/demo-boqa-intro-960.b3afb6813ed1.webp",
          "bytes": 28288
        },
        {
          "width": 1672,
          "height": 941,
          "url": "/assets/demo-boqa-intro-1672.0a42e36920cb.webp",
          "bytes": 61726
        }
      ]
    }
  },
  {
    "id": "qubo-materials",
    "notebook": "notebooks/qubo_materials.ipynb",
    "image": "/assets/demo-materials.png",
    "category": "教程（应用型）",
    "difficulty": 4,
    "title": "黑盒优化（2）",
    "summary": "材料候选筛选案例：在离散候选材料组合中寻找更优性能，适合类 BOQA 的二进制/组合变量搜索。",
    "description": "材料探索案例：真实实验或高精度仿真昂贵，因此先把候选组合转成可搜索变量，用 QUBO / BOQA 思路筛选下一批值得测试的材料。",
    "requiredAlgorithm": "kernel_qa",
    "requiredAlgorithmLabel": "Kernel-QA",
    "setupCode": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：kernel_qa（Kernel-QA）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"kernel_qa\", algorithm_label=\"Kernel-QA\", key=QUBO_TEST_KEY)\n",
    "runCode": "cloud_result = run_boqa_solve(\n    problem=\"materials\",\n    engine=\"cpu\",\n    algorithm=\"kernel_qa\",\n    params={\n        \"case_id\": \"blackbox_2_materials\",\n        \"objective\": \"select high-value materials under experiment budget\",\n        \"items\": items,\n        \"max_items\": 3,\n    },\n)\npprint(cloud_result[\"summary\"])\nplot_trace(cloud_result, \"BOQA API connectivity / materials\")\n",
    "lessonBlocks": [
      {
        "type": "code",
        "code": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：kernel_qa（Kernel-QA）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"kernel_qa\", algorithm_label=\"Kernel-QA\", key=QUBO_TEST_KEY)\n",
        "caption": "key setup"
      },
      {
        "type": "markdown",
        "title": "黑盒优化（2）：材料候选筛选",
        "body": [
          "教程（应用型）",
          "材料候选筛选，把昂贵实验前移到计算阶段",
          "这个 notebook 按“背景知识 -> 人工解法 -> 数学模型 -> 代码导读 -> 云端调用 -> 结果解释”的顺序展开，目标是给基础用户学习，而不只是演示一段代码。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "1. 背景、来历和相关学科",
        "body": [
          "这个案例把材料研发中的候选筛选问题做成一个离散优化演示。材料组合很多，但真实实验、制备和测试都昂贵，所以先用模型挑一批更值得做的候选。",
          "它接近 Fixstars 超导材料示例的思路：把候选材料或配方看成可选择变量，用已有知识或代理评分估计价值，再让优化器在组合空间里搜索。",
          "用户要学习的是：材料科学里的“试哪个组合”如何变成数学里的“选哪些二进制变量”。",
          "材料信息学把材料科学、统计学习和优化结合起来。传统材料研发依赖专家经验和实验试错，现代流程会先用历史数据、计算模拟或代理模型给候选打分。",
          "在 QUBO 视角下，材料候选可以看成一组 0/1 选择：选某元素、某比例、某结构或某工艺条件。约束来自预算、稳定性、安全性和组合数量。",
          "相关知识点",
          "基础理论：材料筛选为什么适合组合优化",
          "材料研发常见难点是候选组合巨大，而一次合成、测试或高精度仿真的成本很高。比如选择元素、比例、热处理条件时，候选数量会随维度指数增长。",
          "组合优化的作用是把“从很多候选里选一组”表达成二进制变量：选或不选。每个候选材料、元素或配方片段都可以对应一个 bit。",
          "QUBO 可以同时表达收益和冲突。收益项鼓励选择预测性能高的候选，冲突项惩罚不兼容的元素组合、预算超限或重复选择。"
        ],
        "bullets": [
          "材料科学：元素、晶格、配方、制备工艺和性能指标。",
          "实验设计：用有限预算安排下一批实验。",
          "组合优化：从候选池里挑子集。",
          "代理模型：用历史样本预测性能或风险。"
        ]
      },
      {
        "type": "markdown",
        "title": "材料发现为什么适合做黑盒优化",
        "body": [
          "材料研发常常面对组合爆炸：元素种类、掺杂比例、热处理温度、晶体结构和制备工艺都会影响性能。真实实验通常昂贵且周期长，所以研究者会先用模型、文献或低保真仿真筛掉明显不值得做的候选。",
          "Fixstars 的超导材料例子也体现了这个思想：先把候选材料表示成可搜索的变量，再用黑盒优化建议下一批更可能有高性能的候选。我们的材料案例保留这个教学思路，重点让用户理解“候选组合 -> 评分 -> 下一轮候选”的循环。"
        ],
        "bullets": [
          "材料池：可选择的元素、配方或工艺档位。",
          "评价函数：强度、导电性、临界温度、成本或稳定性。",
          "预算约束：每一轮真实实验数量有限。",
          "候选解：不是最终真理，而是下一批建议实验。"
        ]
      },
      {
        "type": "markdown",
        "title": "从化学直觉到数学编码",
        "body": [
          "人工筛材料时会看元素性质是否互补、配比是否过高、是否有已知不相容组合。QUBO 会把这些直觉变成奖励和惩罚：想鼓励的组合降低能量，想避免的冲突提高能量。",
          "这类问题常见的编码方式是 one-hot 或 subset selection：某个位置选哪种元素、某类元素最多选几个、某两个元素不能同时出现。解码器再把 0/1 向量翻译回材料配方。"
        ],
        "bullets": [
          "一次项表示单个元素或工艺档位的独立贡献。",
          "二次项表示两种选择同时出现时的协同或冲突。",
          "惩罚项表示配方规则，比如必须选满数量或不能超比例。"
        ]
      },
      {
        "type": "markdown",
        "title": "2. 如果不用代码，人会怎么解",
        "body": [
          "人工筛选时，研究员会先排除明显不稳定或成本过高的材料，再根据文献、相图和经验挑少量组合。",
          "如果候选很多，人工容易受经验偏差影响，只在熟悉区域探索。优化模型可以系统地比较更多候选，并显式加入预算和风险惩罚。",
          "最终模型不是替代实验，而是把实验机会留给更有希望的材料。",
          "这一步很重要：人工解法不是被丢掉，而是帮助我们发现哪些规则应该变成变量、约束或惩罚项。优化模型只是把这些规则变成可重复执行的计算流程。",
          "手工推理：先像材料工程师一样筛一轮",
          "人工筛选时会先排除明显不合适的候选，例如成本太高、工艺不可行、稳定性不足的材料。",
          "随后会比较“性能好但风险高”和“性能中等但可靠”的组合。QUBO 里的线性项像性能打分，二次项像两个候选放在一起后的兼容性判断。",
          "这个案例让用户看到：优化器不是凭空发明材料，而是在给定候选池和评分规则下，替我们系统地检查组合。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "3. 从业务问题到数学模型",
        "body": [
          "变量 x[i] 表示是否选择第 i 个材料候选。",
          "目标函数奖励高预测价值，也惩罚组合过多、风险过高或违反预算。",
          "如果需要选固定数量材料，可以用 one-hot 或 cardinality penalty 约束选择个数。",
          "本案例使用 kernel_qa key，适合离散 QUBO 形式；若使用 cma_es 连续黑盒 key，算法类型不匹配。",
          "读模型时抓住四件事"
        ],
        "bullets": [
          "变量：优化器能改变什么。",
          "目标函数：什么方案更好。",
          "硬约束：什么方案绝对不允许。",
          "解码器：如何把 bit vector 或参数向量翻译回业务语言。"
        ]
      },
      {
        "type": "markdown",
        "title": "4. 代码导读：准备数据、编码器和解码器",
        "body": [
          "items 是候选材料池，每个候选带有一个简化的 value。真实项目可把 value 换成 PCE、强度、稳定性或成本风险评分。",
          "max_items 控制最多选择几个候选，是预算约束的简化表达。",
          "qubo_from_materials 把“高价值但数量有限”的选择问题转成 QUBO。",
          "阅读下面代码时，可以先忽略 Python 细节，按注释找出“数据在哪里、变量怎么编号、惩罚项在哪里加入、最后怎么解码”。",
          "代码导读：候选池、配对惩罚和解码",
          "load_candidate_materials() 构造候选材料表，每一行是一个可选对象。真实项目里这部分会来自材料数据库、实验记录或仿真结果。",
          "编码器把每个候选映射到一个 bit，并用一次项放入单体得分；当两个候选不能同时出现时，用二次项增加惩罚。",
          "解码器把返回的 bit vector 转回材料名称列表。用户真正关心的是“建议先测哪几个候选”，不是 bit 的编号。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "5. 代码导读：调用 BOQA solver",
        "body": [
          "云端调用使用 algorithm=\"kernel_qa\"，因为这里提交的是离散 QUBO 数据。",
          "如果服务端返回 best_vector，解码器会把 0/1 向量翻译成被选中的材料名称。",
          "如果后端只返回占位 note，说明当前 solver 还没有接入真实材料代理模型。",
          "本案例固定需要 kernel_qa（Kernel-QA）授权。若 key 类型不匹配，错误通常会在云端鉴权处出现；若没有看到 调用 BOQA solve，说明还没有真正发出请求。",
          "算法和 key 为什么这样选",
          "本案例需要 kernel_qa key，因为它是离散 QUBO 选择问题。若用 cma_es key，服务端会认为算法权限不匹配。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "cloud_result = run_boqa_solve(\n    problem=\"materials\",\n    engine=\"cpu\",\n    algorithm=\"kernel_qa\",\n    params={\n        \"case_id\": \"blackbox_2_materials\",\n        \"objective\": \"select high-value materials under experiment budget\",\n        \"items\": items,\n        \"max_items\": 3,\n    },\n)\npprint(cloud_result[\"summary\"])\nplot_trace(cloud_result, \"BOQA API connectivity / materials\")\n"
      },
      {
        "type": "markdown",
        "title": "6. 结果怎么读",
        "body": [
          "best_score 越低表示 QUBO 能量越低，通常意味着价值更高且约束违背更少。",
          "best_vector 中接近 1 的位置代表被选择的候选。真实材料项目还要继续做化学可行性、实验安全性和制备成本检查。",
          "曲线中的 eval 是每次候选解，best 是历史最好值。best 下降表示优化过程找到了更好的候选；如果只出现一个点，要查看 raw summary 是否只有单次结果或后端没有返回完整 history。"
        ],
        "bullets": []
      }
    ],
    "imageSet": {
      "width": 1672,
      "height": 941,
      "variants": [
        {
          "width": 480,
          "height": 270,
          "url": "/assets/demo-materials-480.0344071bbe3c.webp",
          "bytes": 12878
        },
        {
          "width": 960,
          "height": 540,
          "url": "/assets/demo-materials-960.466d361ca62c.webp",
          "bytes": 36686
        },
        {
          "width": 1672,
          "height": 941,
          "url": "/assets/demo-materials-1672.cf106e1067db.webp",
          "bytes": 78776
        }
      ]
    }
  },
  {
    "id": "qubo-chemical-process",
    "notebook": "notebooks/qubo_chemical_process.ipynb",
    "image": "/assets/demo-chemical.png",
    "category": "教程（应用型）",
    "difficulty": 4,
    "title": "黑盒优化（3）",
    "summary": "化学反应工艺参数搜索：在温度、时间、催化剂比例等连续变量里寻找高产率方案。",
    "description": "化学工艺案例：反应结果通常来自实验或仿真，目标是在安全边界和成本约束下，优化温度、停留时间、催化剂比例等参数。",
    "requiredAlgorithm": "cma_es",
    "requiredAlgorithmLabel": "CMA-ES",
    "setupCode": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：cma_es（CMA-ES）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"cma_es\", algorithm_label=\"CMA-ES\", key=QUBO_TEST_KEY)\n",
    "runCode": "cloud_result = run_boqa_solve(\n    problem=\"chemical_process\",\n    engine=\"cpu\",\n    algorithm=\"cma_es\",\n    params={\n        \"case_id\": \"blackbox_3_chemical_process\",\n        \"objective\": \"maximize reaction yield with safety and residence-time penalties\",\n        \"iterations\": 80,\n        \"ranges\": ranges,\n    },\n)\npprint(cloud_result[\"summary\"])\nplot_trace(cloud_result, \"BOQA API connectivity / chemical process\")\n",
    "lessonBlocks": [
      {
        "type": "code",
        "code": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：cma_es（CMA-ES）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"cma_es\", algorithm_label=\"CMA-ES\", key=QUBO_TEST_KEY)\n",
        "caption": "key setup"
      },
      {
        "type": "markdown",
        "title": "黑盒优化（3）：化学反应工艺参数",
        "body": [
          "教程（应用型）",
          "化工过程调参，平衡转化率、选择性和安全边界",
          "这个 notebook 按“背景知识 -> 人工解法 -> 数学模型 -> 代码导读 -> 云端调用 -> 结果解释”的顺序展开，目标是给基础用户学习，而不只是演示一段代码。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "1. 背景、来历和相关学科",
        "body": [
          "这个案例模拟化工反应条件优化：温度、压力、催化剂比例、停留时间等参数共同影响产率、能耗和安全性。",
          "很多化工过程无法用一个简单公式准确描述，实际常依赖实验、过程模拟器或经验模型，因此适合黑盒优化。",
          "学习重点是理解连续变量黑盒问题：优化器不是直接解方程，而是在安全范围内试探更好的操作条件。",
          "化学工程里有反应动力学、传质传热、过程控制和安全工程。传统优化会先建机理模型，但复杂反应、催化剂失活和副反应常让模型不够准确。",
          "黑盒优化把“跑一次实验或模拟得到一个分数”当成基本接口，用数据驱动方式逐步改善条件。",
          "相关知识点",
          "基础理论：化工调参、响应面和安全约束",
          "化学反应优化通常要同时考虑转化率、选择性、副产物、安全窗口和设备约束。温度升高可能提高反应速率，也可能带来副反应或安全风险。",
          "在工程上，很多反应条件无法用一个简单公式准确描述，需要通过实验、仿真或历史数据得到反馈，这正是黑盒优化的典型场景。",
          "响应面方法、贝叶斯优化和演化策略都在解决类似问题：用有限试验逐步逼近更好的操作条件，而不是把所有组合穷举一遍。"
        ],
        "bullets": [
          "反应工程：温度和压力影响反应速率与平衡。",
          "过程控制：操作变量不能超出安全范围。",
          "统计建模：用少量实验估计趋势。",
          "多目标优化：产率、成本、安全和排放常互相冲突。"
        ]
      },
      {
        "type": "markdown",
        "title": "化工调参的工程背景",
        "body": [
          "化学反应过程常常要同时考虑转化率、选择性、安全边界和能耗。温度提高可能让反应更快，但也可能增加副反应或热失控风险；停留时间增加可能提高产率，但会降低设备吞吐。",
          "这就是典型黑盒优化：真实反应机理复杂，完整仿真或实验昂贵，工程师希望在安全范围内快速找到更好的操作点。"
        ],
        "bullets": [
          "变量：温度、压力、催化剂比例、停留时间、流量。",
          "目标：收益最大或成本/损失最小。",
          "约束：安全上限、设备能力、产品质量和环保指标。"
        ]
      },
      {
        "type": "markdown",
        "title": "为什么这里用 CMA-ES",
        "body": [
          "化工参数大多是连续变量，不是天然的 0/1 选择。CMA-ES 会维护一个连续搜索分布，根据表现好的候选逐步收缩和移动分布中心，因此适合“参数调优”类黑盒问题。",
          "如果要把连续参数硬切成离散档位，也可以转成 QUBO，但分辨率越高变量越多。MVP 阶段用 CMA-ES 更直观，也更符合普通用户对“调参数”的理解。"
        ],
        "bullets": [
          "看 ranges：这是每个参数允许搜索的工程边界。",
          "看 best_vector：它是归一化坐标，需要映射回真实单位。",
          "看曲线：best 下降说明搜索找到了更低损失的候选。"
        ]
      },
      {
        "type": "markdown",
        "title": "2. 如果不用代码，人会怎么解",
        "body": [
          "人工调参通常先固定大部分条件，只改变一个变量，看趋势后再组合调整。",
          "这种单因素实验容易漏掉变量交互，例如温度和催化剂比例同时改变才出现最佳区域。",
          "黑盒优化会同时处理多个变量，并把每轮实验结果纳入下一轮候选选择。",
          "这一步很重要：人工解法不是被丢掉，而是帮助我们发现哪些规则应该变成变量、约束或惩罚项。优化模型只是把这些规则变成可重复执行的计算流程。",
          "手工推理：先找安全区，再找高收益区",
          "人工调工艺时通常先把温度、压力、停留时间限制在安全范围，再在安全范围内寻找高转化率。",
          "如果某一轮发现高温带来更好转化率但副产物增加，下一轮就会在高温附近减小催化剂比例或缩短停留时间。",
          "CMA-ES 会把这种“根据好结果收缩搜索区域”的过程自动化，但前提仍是我们给出合理的变量边界和目标函数。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "3. 从业务问题到数学模型",
        "body": [
          "变量是连续操作条件，例如温度、压力、催化剂比例和停留时间。",
          "目标函数可以是负产率、能耗、杂质惩罚或安全风险的组合。",
          "约束包括温度上限、压力上限、设备能力和产品质量下限。",
          "本案例使用 cma_es key，适合连续黑盒参数搜索。",
          "读模型时抓住四件事"
        ],
        "bullets": [
          "变量：优化器能改变什么。",
          "目标函数：什么方案更好。",
          "硬约束：什么方案绝对不允许。",
          "解码器：如何把 bit vector 或参数向量翻译回业务语言。"
        ]
      },
      {
        "type": "markdown",
        "title": "4. 代码导读：准备数据、编码器和解码器",
        "body": [
          "ranges 给出每个操作条件的上下界。真实项目中这些范围通常来自工艺手册、设备限制和安全审查。",
          "阅读下面代码时，可以先忽略 Python 细节，按注释找出“数据在哪里、变量怎么编号、惩罚项在哪里加入、最后怎么解码”。",
          "代码导读：工艺参数和目标函数",
          "ranges 中的温度、催化剂比例、停留时间是连续变量，solver 返回的是归一化向量。",
          "params 会把这些边界和案例名称交给 BOQA solver；真实接入时，后端应把候选条件送到仿真器或实验代理。",
          "图中的 best 曲线表示目前找到的最低目标值。如果目标函数代表“负收益 + 约束惩罚”，曲线下降意味着方案更好。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "5. 代码导读：调用 BOQA solver",
        "body": [
          "run_boqa_solve 使用 algorithm=\"cma_es\" 提交连续变量问题。",
          "请求体中的 problem=\"chemical_process\" 是后端识别案例的名称。",
          "输出出现 authorized=True 时，代表 key 已通过鉴权；后续是否是业务真实结果取决于 solver 后端。",
          "本案例固定需要 cma_es（CMA-ES）授权。若 key 类型不匹配，错误通常会在云端鉴权处出现；若没有看到 调用 BOQA solve，说明还没有真正发出请求。",
          "算法和 key 为什么这样选"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "cloud_result = run_boqa_solve(\n    problem=\"chemical_process\",\n    engine=\"cpu\",\n    algorithm=\"cma_es\",\n    params={\n        \"case_id\": \"blackbox_3_chemical_process\",\n        \"objective\": \"maximize reaction yield with safety and residence-time penalties\",\n        \"iterations\": 80,\n        \"ranges\": ranges,\n    },\n)\npprint(cloud_result[\"summary\"])\nplot_trace(cloud_result, \"BOQA API connectivity / chemical process\")\n"
      },
      {
        "type": "markdown",
        "title": "6. 结果怎么读",
        "body": [
          "best 曲线下降表示优化器找到了更低损失的工艺条件。",
          "实际化工应用不能只看一个分数，还要检查安全边界、稳定运行窗口和产品质量。",
          "曲线中的 eval 是每次候选解，best 是历史最好值。best 下降表示优化过程找到了更好的候选；如果只出现一个点，要查看 raw summary 是否只有单次结果或后端没有返回完整 history。"
        ],
        "bullets": []
      }
    ],
    "imageSet": {
      "width": 1672,
      "height": 941,
      "variants": [
        {
          "width": 480,
          "height": 270,
          "url": "/assets/demo-chemical-480.5103e3d45dbb.webp",
          "bytes": 10612
        },
        {
          "width": 960,
          "height": 540,
          "url": "/assets/demo-chemical-960.cd06ef7a07af.webp",
          "bytes": 28350
        },
        {
          "width": 1672,
          "height": 941,
          "url": "/assets/demo-chemical-1672.657aac9f7c44.webp",
          "bytes": 59518
        }
      ]
    }
  },
  {
    "id": "qubo-heat-sink",
    "notebook": "notebooks/qubo_heat_sink.ipynb",
    "image": "/assets/demo-heat-sink.png",
    "category": "教程（应用型）",
    "difficulty": 4,
    "title": "黑盒优化（4）",
    "summary": "散热器设计参数搜索：用连续黑盒优化调整结构参数，降低温度或热阻。",
    "description": "散热设计案例：几何尺寸、鳍片间距和风速都会影响散热效果，真实评价可能来自 CFD 或实验台，因此适合黑盒优化流程。",
    "requiredAlgorithm": "cma_es",
    "requiredAlgorithmLabel": "CMA-ES",
    "setupCode": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：cma_es（CMA-ES）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"cma_es\", algorithm_label=\"CMA-ES\", key=QUBO_TEST_KEY)\n",
    "runCode": "cloud_result = run_boqa_solve(\n    problem=\"heat_sink\",\n    engine=\"cpu\",\n    algorithm=\"cma_es\",\n    params={\n        \"case_id\": \"blackbox_4_heat_sink\",\n        \"objective\": \"minimize thermal resistance with pressure-drop and material-cost penalties\",\n        \"iterations\": 80,\n        \"ranges\": ranges,\n    },\n)\npprint(cloud_result[\"summary\"])\nplot_trace(cloud_result, \"BOQA API connectivity / heat-sink tuning\")\n",
    "lessonBlocks": [
      {
        "type": "code",
        "code": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：cma_es（CMA-ES）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"cma_es\", algorithm_label=\"CMA-ES\", key=QUBO_TEST_KEY)\n",
        "caption": "key setup"
      },
      {
        "type": "markdown",
        "title": "黑盒优化（4）：散热器设计参数",
        "body": [
          "教程（应用型）",
          "散热结构设计，用参数搜索降低温度和压降",
          "这个 notebook 按“背景知识 -> 人工解法 -> 数学模型 -> 代码导读 -> 云端调用 -> 结果解释”的顺序展开，目标是给基础用户学习，而不只是演示一段代码。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "1. 背景、来历和相关学科",
        "body": [
          "这个案例模拟散热器或微通道结构的设计优化。通道宽度、深度、鳍片高度、流量和材料比例都会影响峰值温度、压降和成本。",
          "热设计常常需要仿真，单次 CFD 或热分析可能很慢，因此适合先在云端沙箱中体验黑盒优化流程。",
          "学习目标是把机械/热工程中的几何参数和性能指标转成可运行的优化任务。",
          "散热问题来自传热学、流体力学和结构设计。工程师希望降低温度，但增加流量或复杂结构会带来压降、噪声和成本。",
          "黑盒优化适合这种“评价函数贵、变量连续、约束多”的场景。它不要求用户写出完整物理方程，只需要提供参数范围和评价接口。",
          "相关知识点",
          "基础理论：热设计里的黑盒目标",
          "散热器设计涉及传热学、流体力学和制造约束。鳍片高度、通道宽度、流量等参数会共同影响温度、压降、噪声和成本。",
          "真实热设计常依赖 CFD 仿真或台架测试。一次评估可能很慢，所以优化目标通常是昂贵黑盒：给一组几何/工况参数，返回峰值温度或综合损失。",
          "黑盒优化适合先做快速筛选：让算法提出候选结构，再把少数有前景的结构交给高精度仿真或工程师复核。"
        ],
        "bullets": [
          "传热学：导热、对流和热阻。",
          "流体力学：流量提高可能降低温度，但会增加压降。",
          "机械设计：尺寸、材料和可制造性限制搜索空间。",
          "代理优化：用少量仿真结果估计下一步方向。"
        ]
      },
      {
        "type": "markdown",
        "title": "散热器设计的基础知识",
        "body": [
          "散热器优化属于传热学和结构设计交叉问题。散热效果受导热材料、翅片高度、厚度、间距、气流速度和压降影响。翅片越密不一定越好，因为空气阻力会变大，制造成本也会上升。",
          "真实工程常用 CFD 或热仿真评估设计，但一次仿真可能很慢，所以黑盒优化可以先在可行设计空间里提出候选，让工程师少试无效方案。"
        ],
        "bullets": [
          "热阻越低通常越好。",
          "压降和体积不能无限增加。",
          "目标函数常常是温度、成本、体积和约束惩罚的组合。"
        ]
      },
      {
        "type": "markdown",
        "title": "代码中的物理含义",
        "body": [
          "代码里的每个变量都对应一个可调设计量，例如翅片尺寸、通道间距或材料选择。归一化向量只是算法方便使用的形式，不是最终工程图纸。",
          "解码阶段要把 0 到 1 的向量映射回真实单位，再交给仿真器或实验平台评估。如果后端还没有真实仿真器，返回的只能证明接口可用，不能代表真实散热优化已经完成。"
        ],
        "bullets": [
          "变量边界来自结构尺寸和制造限制。",
          "目标函数来自热仿真或实验测温。",
          "结果解释要同时看性能和可制造性。"
        ]
      },
      {
        "type": "markdown",
        "title": "2. 如果不用代码，人会怎么解",
        "body": [
          "人工设计时，工程师通常先根据经验确定一个基准结构，再逐个调整宽度、深度、鳍片和流量。",
          "问题在于变量之间有强交互，例如更深通道可能改善散热，但也改变压力损失。",
          "优化器可以同时试探多维参数，在多个约束之间寻找折中。",
          "这一步很重要：人工解法不是被丢掉，而是帮助我们发现哪些规则应该变成变量、约束或惩罚项。优化模型只是把这些规则变成可重复执行的计算流程。",
          "手工推理：温度、压降和成本的三角关系",
          "人工设计时，如果温度太高，会想到增加散热面积或流量；但流量过大可能带来泵功和压降。",
          "如果材料太贵，会降低铜比例或简化结构；但这又可能损失导热性能。",
          "优化模型把这些折中写成一个综合目标，让算法在多目标之间寻找可接受的平衡点。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "3. 从业务问题到数学模型",
        "body": [
          "变量是连续几何和运行参数。",
          "目标函数通常把峰值温度、压降和材料成本加权合成一个分数。",
          "硬约束包括尺寸边界、压降上限、温度上限和制造工艺限制。",
          "本案例使用 cma_es key，适合连续工程参数。",
          "读模型时抓住四件事"
        ],
        "bullets": [
          "变量：优化器能改变什么。",
          "目标函数：什么方案更好。",
          "硬约束：什么方案绝对不允许。",
          "解码器：如何把 bit vector 或参数向量翻译回业务语言。"
        ]
      },
      {
        "type": "markdown",
        "title": "4. 代码导读：准备数据、编码器和解码器",
        "body": [
          "ranges 是散热设计变量的搜索范围。",
          "正式项目可把本地公式替换为 CFD、热仿真或实验台接口。",
          "阅读下面代码时，可以先忽略 Python 细节，按注释找出“数据在哪里、变量怎么编号、惩罚项在哪里加入、最后怎么解码”。",
          "代码导读：归一化向量和物理边界",
          "代码中的 ranges 对应散热结构的可调参数边界。用户看到的向量虽然在 0 到 1 附近，但解码时会映射回毫米、流量或比例。",
          "problem=\"heat_sink_tuning\" 告诉后端这是散热器调参案例。不同问题名应接入不同业务目标函数。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "5. 代码导读：调用 BOQA solver",
        "body": [
          "云端调用使用 problem=\"heat_sink\" 和 algorithm=\"cma_es\"。",
          "solver 返回 best_vector 时，它代表一组候选设计参数。",
          "如果返回占位 note，说明 API 链路通了，但后端仍在使用演示 objective。",
          "本案例固定需要 cma_es（CMA-ES）授权。若 key 类型不匹配，错误通常会在云端鉴权处出现；若没有看到 调用 BOQA solve，说明还没有真正发出请求。",
          "算法和 key 为什么这样选",
          "本案例需要 cma_es key。它搜索连续设计参数，不是 one-hot 或二进制图约束。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "cloud_result = run_boqa_solve(\n    problem=\"heat_sink\",\n    engine=\"cpu\",\n    algorithm=\"cma_es\",\n    params={\n        \"case_id\": \"blackbox_4_heat_sink\",\n        \"objective\": \"minimize thermal resistance with pressure-drop and material-cost penalties\",\n        \"iterations\": 80,\n        \"ranges\": ranges,\n    },\n)\npprint(cloud_result[\"summary\"])\nplot_trace(cloud_result, \"BOQA API connectivity / heat-sink tuning\")\n"
      },
      {
        "type": "markdown",
        "title": "6. 结果怎么读",
        "body": [
          "曲线下降说明优化器找到了更低综合代价的散热设计。",
          "真实工程验收还要查看温度云图、压降、结构强度、加工成本和可靠性。",
          "曲线中的 eval 是每次候选解，best 是历史最好值。best 下降表示优化过程找到了更好的候选；如果只出现一个点，要查看 raw summary 是否只有单次结果或后端没有返回完整 history。"
        ],
        "bullets": []
      }
    ],
    "imageSet": {
      "width": 1672,
      "height": 941,
      "variants": [
        {
          "width": 480,
          "height": 270,
          "url": "/assets/demo-heat-sink-480.d9963ce87048.webp",
          "bytes": 12064
        },
        {
          "width": 960,
          "height": 540,
          "url": "/assets/demo-heat-sink-960.353ed12e9565.webp",
          "bytes": 34566
        },
        {
          "width": 1672,
          "height": 941,
          "url": "/assets/demo-heat-sink-1672.af80f10d2666.webp",
          "bytes": 80906
        }
      ]
    }
  },
  {
    "id": "qubo-traffic",
    "notebook": "notebooks/boqa_city_traffic_timing.ipynb",
    "image": "/assets/demo-traffic-blackbox.png",
    "category": "教程（应用型）",
    "difficulty": 4,
    "title": "黑盒优化（5）",
    "summary": "用城市交通仿真评价周期、绿信比和路口偏移，再用 CMA-ES 搜索更高平均车速、更短队列的配时方案。",
    "description": "仿真驱动的交通黑盒优化：每次候选配时都交给城市交通模型评分；优化器不直接选择当前相位，而是学习一套连续的长期配时参数。",
    "requiredAlgorithm": "cma_es",
    "requiredAlgorithmLabel": "CMA-ES",
    "setupCode": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：cma_es（CMA-ES）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"cma_es\", algorithm_label=\"CMA-ES\", key=QUBO_TEST_KEY)\n",
    "runCode": "cloud_result = run_boqa_solve(\n    problem=\"city_traffic_timing\",\n    engine=\"cpu\",\n    algorithm=\"cma_es\",\n    params={\n        \"case_id\": \"blackbox_5_city_traffic_timing\",\n        \"objective\": \"maximize mean vehicle speed with queue and coordination penalties\",\n        \"evaluation\": \"simplified traffic simulation black box\",\n        \"iterations\": 80,\n        \"ranges\": ranges,\n    },\n)\npprint(cloud_result[\"summary\"])\nplot_trace(cloud_result, \"BOQA API / city traffic timing\")\nbest_vector = cloud_result[\"summary\"].get(\"best_vector\")\nif best_vector:\n    show_decoded(\"推荐交通配时参数\", decode_timing_vector(best_vector))\n",
    "lessonBlocks": [
      {
        "type": "code",
        "code": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：cma_es（CMA-ES）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"cma_es\", algorithm_label=\"CMA-ES\", key=QUBO_TEST_KEY)\n",
        "caption": "key setup"
      },
      {
        "type": "markdown",
        "title": "黑盒优化（5）：城市交通配时",
        "body": [
          "教程（应用型）",
          "用交通仿真评价配时，用连续黑盒优化搜索长期策略",
          "这个案例按“概念 -> 知识 -> 公式 -> 代码 -> 结果解释”的顺序展开：优化器不直接挑选当前绿灯相位，而是反复试验周期、绿信比和协调偏移等连续参数。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "1. 概念：城市交通仿真为什么是黑盒",
        "body": [
          "一组信号配时参数进入交通仿真器后，会经过车辆到达、排队、放行、换相和路口传播等过程，最后才得到平均车速、延误和队列长度。这个因果过程复杂、代价高，也通常没有可直接求导的显式公式。",
          "因此我们把仿真器看成黑盒：输入配时参数 theta，输出一个综合评分。黑盒优化器不需要知道仿真器内部细节，只利用历次评分提出下一批候选配时。"
        ],
        "bullets": [
          "连续变量：周期长度、主路绿信比、路口偏移、左转保护比例。",
          "评价输出：平均车速、排队长度、延误或吞吐量。",
          "工程边界：安全相位、行人放行和设备允许的周期范围。"
        ]
      },
      {
        "type": "markdown",
        "title": "2. 知识：与交通信号优化（QUBO）的区别",
        "body": [
          "交通信号优化（QUBO）处理的是离散的相位冲突：当前时刻哪些相位可同时放行、怎样降低冲突和切换代价。它的变量是 0/1，相位关系直接写成 QUBO。",
          "本案例处理的是仿真器外层的长期调参：一整套连续配时参数先被送进交通模型，再由平均车速和队列评分。两者不是重复方案，而是可串联的两个层次：离线校准配时，在线选择相位。"
        ],
        "bullets": [
          "QUBO：离散相位、冲突图、实时或短时决策。",
          "黑盒优化：连续配时、仿真评分、离线策略搜索。",
          "验证：两者都要回到独立交通场景，检查安全与拥堵。"
        ]
      },
      {
        "type": "markdown",
        "title": "3. 公式：平均车速、队列和协调惩罚",
        "body": [
          "令 theta 表示配时参数，v_k(theta) 表示第 k 辆车在仿真中的速度，q_i(theta) 表示第 i 个路口的队列。可把目标写成 J(theta) = -mean_k v_k(theta) + lambda_q sum_i q_i(theta)^2 + lambda_o offset_penalty(theta)。",
          "第一项鼓励车辆整体走得更快；第二项避免把拥堵只转移到某个路口；第三项限制相邻路口失去协调。求解器最小化 J(theta)，所以曲线下降代表综合交通表现改善。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "4. 代码：边界、轻量评分器与解码器",
        "body": [
          "ranges 把周期和比例限制在工程可接受区间。教学代码中的轻量评分器只用来展示“输入配时 -> 输出分数”的接口；接入项目时，把它替换为 SUMO、内部多智能体仿真或真实探测器数据即可。",
          "解码器把 0 到 1 的候选向量映射回秒数和比例。交通工程师据此审核配时，而不是直接使用抽象向量。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "traffic_bounds = {\n    'cycle_seconds': (60.0, 150.0),\n    'main_green_ratio': (0.42, 0.74),\n    'offset_seconds': (0.0, 75.0),\n    'left_turn_ratio': (0.08, 0.30),\n}\nranges = [\n    {'name': name, 'min': low, 'max': high, 'initial': (low + high) / 2}\n    for name, (low, high) in traffic_bounds.items()\n]\n\ndef decode_timing_vector(vector):\n    return {\n        item['name']: item['min'] + value * (item['max'] - item['min'])\n        for item, value in zip(ranges, vector)\n    }\n",
        "caption": "连续交通配时变量"
      },
      {
        "type": "markdown",
        "title": "5. 知识与代码：为什么使用 CMA-ES",
        "body": [
          "周期、绿信比和偏移量是连续变量，仿真得分还可能有噪声、不可导且昂贵。CMA-ES 会围绕当前候选分布采样参数，并依据表现较好的样本更新均值和协方差，适合这种连续黑盒搜索。",
          "因此本案例使用 cma_es；交通相位冲突图的 QUBO 则使用 kernel_qa。算法类型应与变量和模型形式相匹配。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "cloud_result = run_boqa_solve(\n    problem=\"city_traffic_timing\",\n    engine=\"cpu\",\n    algorithm=\"cma_es\",\n    params={\n        \"case_id\": \"blackbox_5_city_traffic_timing\",\n        \"objective\": \"maximize mean vehicle speed with queue and coordination penalties\",\n        \"evaluation\": \"simplified traffic simulation black box\",\n        \"iterations\": 80,\n        \"ranges\": ranges,\n    },\n)\npprint(cloud_result[\"summary\"])\nplot_trace(cloud_result, \"BOQA API / city traffic timing\")\nbest_vector = cloud_result[\"summary\"].get(\"best_vector\")\nif best_vector:\n    show_decoded(\"推荐交通配时参数\", decode_timing_vector(best_vector))\n"
      },
      {
        "type": "markdown",
        "title": "6. 结果怎么读",
        "body": [
          "best_vector 解码后才是周期、绿信比和偏移秒数。best 曲线下降表示在当前权重下，平均车速提高、队列或协调损失降低。",
          "真实落地前必须在未参与优化的流量场景、行人相位、公交优先、事故扰动和安全规则下复核，避免只对单一仿真场景有效。"
        ],
        "bullets": []
      }
    ],
    "imageSet": {
      "width": 1672,
      "height": 941,
      "variants": [
        {
          "width": 480,
          "height": 270,
          "url": "/assets/demo-traffic-blackbox-480.a317e18a85a8.webp",
          "bytes": 12790
        },
        {
          "width": 960,
          "height": 540,
          "url": "/assets/demo-traffic-blackbox-960.e952c35b2e32.webp",
          "bytes": 33954
        },
        {
          "width": 1672,
          "height": 941,
          "url": "/assets/demo-traffic-blackbox-1672.e9409727768a.webp",
          "bytes": 72188
        }
      ]
    }
  },
  {
    "id": "qubo-mixing",
    "notebook": "notebooks/qubo_mixing.ipynb",
    "image": "/assets/demo-mixing.png",
    "category": "教程（应用型）",
    "difficulty": 4,
    "title": "黑盒优化（6）",
    "summary": "混合装置参数搜索：针对搅拌速度、叶片角度和时间等变量，寻找更高混合效率。",
    "description": "混合工程案例：混合效率来自仿真或实验，代码展示如何设置参数范围、调用云端算法并解释收敛曲线。",
    "requiredAlgorithm": "cma_es",
    "requiredAlgorithmLabel": "CMA-ES",
    "setupCode": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：cma_es（CMA-ES）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"cma_es\", algorithm_label=\"CMA-ES\", key=QUBO_TEST_KEY)\n",
    "runCode": "cloud_result = run_boqa_solve(\n    problem=\"mixing_process\",\n    engine=\"cpu\",\n    algorithm=\"cma_es\",\n    params={\n        \"case_id\": \"blackbox_6_mixing_process\",\n        \"objective\": \"maximize mixing efficiency with feed-rate and stability penalties\",\n        \"iterations\": 80,\n        \"ranges\": ranges,\n    },\n)\npprint(cloud_result[\"summary\"])\nplot_trace(cloud_result, \"BOQA API connectivity / mixing process\")\n",
    "lessonBlocks": [
      {
        "type": "code",
        "code": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：cma_es（CMA-ES）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"cma_es\", algorithm_label=\"CMA-ES\", key=QUBO_TEST_KEY)\n",
        "caption": "key setup"
      },
      {
        "type": "markdown",
        "title": "黑盒优化（6）：混合装置参数搜索",
        "body": [
          "教程（应用型）",
          "混合装置参数优化，寻找效率和稳定性的折中",
          "这个 notebook 按“背景知识 -> 人工解法 -> 数学模型 -> 代码导读 -> 云端调用 -> 结果解释”的顺序展开，目标是给基础用户学习，而不只是演示一段代码。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "1. 背景、来历和相关学科",
        "body": [
          "这个案例模拟混合装置设计或运行参数优化。转速、桨叶角度、挡板位置和停留时间会共同影响混合均匀性、能耗和稳定性。",
          "真实混合过程可能需要 CFD 或实验台评价，公式并不容易准确写出，因此适合黑盒优化。",
          "学习重点是如何把设备参数、运行边界和评价指标整理成连续优化问题。",
          "混合问题涉及流体力学、传质、反应工程和设备设计。工程上常要在混合效率、能耗、剪切风险和设备寿命之间折中。",
          "黑盒优化让用户可以先把复杂仿真或实验当成评分函数，再逐步搜索参数空间。",
          "相关知识点",
          "基础理论：混合效率、流体结构和黑盒评估",
          "混合装置设计常见目标是提高混合均匀性、降低能耗并避免局部滞留。叶片角度、转速、挡板位置和入口流量会产生非线性影响。",
          "这些影响通常需要 CFD 或实验测量，难以写成简单解析公式。因此混合装置是典型黑盒优化：输入设计参数，输出混合指标。",
          "黑盒优化的价值在于减少盲目试错，把试验集中到可能改善混合效率的区域。"
        ],
        "bullets": [
          "流体力学：湍流、剪切和循环流。",
          "过程工程：混合影响反应速率和产品均一性。",
          "设备设计：结构参数决定流场。",
          "实验设计：减少昂贵试验次数。"
        ]
      },
      {
        "type": "markdown",
        "title": "混合器优化的流体工程背景",
        "body": [
          "混合器设计关注两件事：混得是否均匀，以及为达到均匀所付出的能耗和时间。叶片形状、转速、挡板、入口位置和流体黏度都会影响涡流结构。",
          "流体仿真通常很贵，实验也需要设备和时间。黑盒优化适合用来探索设计参数，帮助工程师把试验预算集中到更有希望的区域。"
        ],
        "bullets": [
          "变量：几何尺寸、转速、叶片角度、混合时间。",
          "目标：均匀度最大、能耗最小或综合评分最优。",
          "约束：设备尺寸、剪切限制、安全转速。"
        ]
      },
      {
        "type": "markdown",
        "title": "为什么结果要回到仿真验证",
        "body": [
          "黑盒优化输出的是候选设计，不是直接替代 CFD 或实验。代理模型可能在数据稀疏区域判断失误，所以每一轮候选都需要真实评估后再进入下一轮历史数据。",
          "读代码时重点看参数空间、调用次数和结果曲线。best 曲线下降表示算法在当前模型/接口下找到更优候选，但真实工程结论仍取决于后端是否接入真实混合仿真。"
        ],
        "bullets": [
          "CMA-ES 适合连续设计参数。",
          "历史样本越可靠，代理模型越有价值。",
          "结果要同时检查性能、能耗和可制造性。"
        ]
      },
      {
        "type": "markdown",
        "title": "2. 如果不用代码，人会怎么解",
        "body": [
          "人工调试通常从安全转速和常用桨型开始，再根据混合时间或均匀度逐步调节。",
          "但高转速不一定更好，可能增加能耗、发热或剪切损伤。",
          "优化模型可以把“效率更高”和“代价更低”写进同一个评分。",
          "这一步很重要：人工解法不是被丢掉，而是帮助我们发现哪些规则应该变成变量、约束或惩罚项。优化模型只是把这些规则变成可重复执行的计算流程。",
          "手工推理：先看均匀性，再看能耗",
          "人工调混合器时会先观察是否有死区、短路流或分层现象。若混合不均匀，可能提高转速或改变几何。",
          "但更激烈的搅拌会增加能耗和剪切风险。优秀方案通常不是单指标最大，而是在均匀性、能耗和设备限制之间折中。",
          "CMA-ES 用一组候选设计不断更新搜索分布，适合这种连续参数、多峰响应的问题。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "3. 从业务问题到数学模型",
        "body": [
          "变量是连续设备或运行参数。",
          "目标函数可以是负混合效率加上能耗、稳定性和安全惩罚。",
          "约束来自设备允许转速、材料强度和工艺窗口。",
          "本案例使用 cma_es key。",
          "读模型时抓住四件事"
        ],
        "bullets": [
          "变量：优化器能改变什么。",
          "目标函数：什么方案更好。",
          "硬约束：什么方案绝对不允许。",
          "解码器：如何把 bit vector 或参数向量翻译回业务语言。"
        ]
      },
      {
        "type": "markdown",
        "title": "4. 代码导读：准备数据、编码器和解码器",
        "body": [
          "ranges 定义可搜索的设备参数。",
          "真实项目中，objective 可以替换为仿真任务、实验数据库或在线仪表反馈。",
          "阅读下面代码时，可以先忽略 Python 细节，按注释找出“数据在哪里、变量怎么编号、惩罚项在哪里加入、最后怎么解码”。",
          "代码导读：参数空间代表设计空间",
          "代码里的参数向量代表可调设计变量。真实部署时，这些变量会传给仿真脚本、实验设备或历史代理模型。",
          "iterations 是评估预算。预算越大，搜索通常更稳，但云端调用次数也会增加。",
          "结果中的 best_vector 需要结合变量边界解读，不能只看 0.5 这样的归一化数字。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "5. 代码导读：调用 BOQA solver",
        "body": [
          "云端调用会提交 problem=\"mixing\" 和 algorithm=\"cma_es\"。",
          "cma_es 适合连续变量，不需要把每个参数离散成 bit。",
          "返回 best_vector 后，应把它映射回转速、角度等真实工程量。",
          "本案例固定需要 cma_es（CMA-ES）授权。若 key 类型不匹配，错误通常会在云端鉴权处出现；若没有看到 调用 BOQA solve，说明还没有真正发出请求。",
          "算法和 key 为什么这样选",
          "本案例需要 cma_es key，因为它是连续工程调参。若想做离散叶片组合，才更接近 QUBO。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "cloud_result = run_boqa_solve(\n    problem=\"mixing_process\",\n    engine=\"cpu\",\n    algorithm=\"cma_es\",\n    params={\n        \"case_id\": \"blackbox_6_mixing_process\",\n        \"objective\": \"maximize mixing efficiency with feed-rate and stability penalties\",\n        \"iterations\": 80,\n        \"ranges\": ranges,\n    },\n)\npprint(cloud_result[\"summary\"])\nplot_trace(cloud_result, \"BOQA API connectivity / mixing process\")\n"
      },
      {
        "type": "markdown",
        "title": "6. 结果怎么读",
        "body": [
          "best 曲线下降说明综合目标更优。",
          "真实混合设计还要验证流场、放大效应、剪切风险和生产稳定性。",
          "曲线中的 eval 是每次候选解，best 是历史最好值。best 下降表示优化过程找到了更好的候选；如果只出现一个点，要查看 raw summary 是否只有单次结果或后端没有返回完整 history。"
        ],
        "bullets": []
      }
    ],
    "imageSet": {
      "width": 1672,
      "height": 941,
      "variants": [
        {
          "width": 480,
          "height": 270,
          "url": "/assets/demo-mixing-480.aa665462329a.webp",
          "bytes": 12320
        },
        {
          "width": 960,
          "height": 540,
          "url": "/assets/demo-mixing-960.619eb9643895.webp",
          "bytes": 35874
        },
        {
          "width": 1672,
          "height": 941,
          "url": "/assets/demo-mixing-1672.68d06e2643fd.webp",
          "bytes": 80464
        }
      ]
    }
  },
  {
    "id": "qubo-portfolio",
    "notebook": "notebooks/qubo_portfolio.ipynb",
    "image": "/assets/qubo-portfolio.png",
    "category": "演示应用程序",
    "difficulty": 5,
    "title": "投资组合优化",
    "summary": "在收益、风险和持仓数量之间权衡，把资产选择写成 QUBO 组合优化问题。",
    "description": "金融组合案例：每个资产是否买入是二进制变量，收益是一阶项，风险相关性是二阶项，预算和持仓数量用惩罚项表达。",
    "requiredAlgorithm": "kernel_qa",
    "requiredAlgorithmLabel": "Kernel-QA",
    "setupCode": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：kernel_qa（Kernel-QA）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"kernel_qa\", algorithm_label=\"Kernel-QA\", key=QUBO_TEST_KEY)\n",
    "runCode": "cloud_result = run_qubo_cloud(\n    problem=\"portfolio_qubo\",\n    qubo=qubo,\n    n_vars=n_vars,\n    algorithm=\"kernel_qa\",\n    params={'case_id': 'combo_portfolio', 'encoding': 'binary asset selection + budget penalty', 'decoder': 'selected asset list', 'objective': 'maximize return while penalizing risk and budget violation', 'iterations': 80},\n)\nplot_trace(cloud_result, \"BOQA API / portfolio_qubo\")\n",
    "lessonBlocks": [
      {
        "type": "code",
        "code": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：kernel_qa（Kernel-QA）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"kernel_qa\", algorithm_label=\"Kernel-QA\", key=QUBO_TEST_KEY)\n",
        "caption": "key setup"
      },
      {
        "type": "markdown",
        "title": "投资组合优化",
        "body": [
          "演示应用程序",
          "投资组合选择，用收益和风险构造二次目标",
          "这个 notebook 按“背景知识 -> 人工解法 -> 数学模型 -> 代码导读 -> 云端调用 -> 结果解释”的顺序展开，目标是给基础用户学习，而不只是演示一段代码。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "1. 背景、来历和相关学科",
        "body": [
          "投资组合优化是金融工程中的经典问题：用户希望在预期收益、风险、资金上限和持仓数量之间折中。",
          "QUBO 很适合表达“选或不选某资产”的离散组合版投资问题。资产之间的相关风险天然是二次项。",
          "学习重点是看懂一次项代表收益或成本，二次项代表资产之间的相关风险。",
          "现代投资组合理论来自 Markowitz 均值方差模型。它把收益看成均值，把风险看成方差或协方差。",
          "如果资产只能买入或不买入，连续权重模型会变成组合选择问题，适合用 QUBO 或混合整数优化处理。",
          "相关知识点",
          "基础理论：从 Markowitz 投资组合到 QUBO",
          "投资组合优化的经典思想来自均值-方差框架：期望收益越高越好，波动风险越低越好。风险通常由资产之间的协方差矩阵描述。",
          "如果只能选择买或不买某个资产，变量就是二进制的；收益是一次项，协方差风险天然是二次项，因此很适合写成 QUBO。",
          "这个案例不是投资建议，而是用金融问题展示“二次项如何表示两个选择之间的关系”。"
        ],
        "bullets": [
          "金融工程：收益、波动率、协方差和风险预算。",
          "统计学：历史收益估计和不确定性。",
          "组合优化：在约束下选择资产子集。",
          "风险管理：限制集中度和相关风险。"
        ]
      },
      {
        "type": "markdown",
        "title": "投资组合优化的理论来源",
        "body": [
          "投资组合优化常从 Markowitz 均值-方差理论讲起：收益越高越好，风险越低越好，而资产之间的相关性决定了组合风险不是简单相加。",
          "离散投资组合问题会加入“是否选择某资产”“最多选择几个”“行业比例限制”等约束，因此可以自然转成 QUBO。"
        ],
        "bullets": [
          "收益：通常是一阶项，鼓励选择预期收益高的资产。",
          "风险：通常是二阶项，由协方差或相关性表示。",
          "约束：预算、持仓数量、行业暴露和交易成本。"
        ]
      },
      {
        "type": "markdown",
        "title": "非专业用户怎么读这个案例",
        "body": [
          "可以把每个资产看成一个开关：选或不选。模型希望打开一些收益高、互相分散风险的开关，同时避免超预算或过度集中。",
          "解码结果应该读成一个资产清单，而不是抽象 bit vector。真实金融落地还需要回测、风控和合规，notebook 这里只演示组合优化的建模方式。"
        ],
        "bullets": [
          "低能量解代表更符合收益-风险-约束权衡。",
          "不是投资建议，只是优化建模教学。",
          "真实系统应加入历史窗口、交易成本和风险限额。"
        ]
      },
      {
        "type": "markdown",
        "title": "2. 如果不用代码，人会怎么解",
        "body": [
          "人工选组合时，会先看收益高的资产，再剔除波动太大或高度相关的资产。",
          "问题在于相关性难以直观看出，两个单独看不错的资产放在一起可能增加组合风险。",
          "QUBO 用二次项把“两个资产同时出现”的风险写出来。",
          "这一步很重要：人工解法不是被丢掉，而是帮助我们发现哪些规则应该变成变量、约束或惩罚项。优化模型只是把这些规则变成可重复执行的计算流程。",
          "手工推理：不要只选收益最高的资产",
          "人工选组合时，如果只看历史收益，可能会把资金集中到高度相关的资产上，导致风险集中。",
          "更稳的做法是比较资产之间的相关性：两个资产单独看都不错，但如果总是同涨同跌，同时持有可能风险很高。",
          "QUBO 里的二次项正是表达这种“两个资产一起选会怎样”的工具。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "3. 从业务问题到数学模型",
        "body": [
          "变量 x[i] 表示是否选择资产 i。",
          "一次项奖励高收益或惩罚成本。",
          "二次项惩罚高相关风险，特别是两个资产同时选中时的风险贡献。",
          "本案例使用 kernel_qa key。",
          "读模型时抓住四件事"
        ],
        "bullets": [
          "变量：优化器能改变什么。",
          "目标函数：什么方案更好。",
          "硬约束：什么方案绝对不允许。",
          "解码器：如何把 bit vector 或参数向量翻译回业务语言。"
        ]
      },
      {
        "type": "markdown",
        "title": "4. 代码导读：准备数据、编码器和解码器",
        "body": [
          "assets 存放资产收益、风险和预算信息。",
          "编码器把选择数量、预算和风险协同写入 QUBO。",
          "解码器把 bit vector 翻译成资产列表，方便业务用户判断结果是否合理。",
          "阅读下面代码时，可以先忽略 Python 细节，按注释找出“数据在哪里、变量怎么编号、惩罚项在哪里加入、最后怎么解码”。",
          "代码导读：收益是一阶，风险是二阶",
          "资产列表提供预期收益、风险和相关关系。每个资产对应一个 bit。",
          "一次项鼓励选择收益高的资产；二次项惩罚相关性过高或风险叠加的资产对。",
          "解码器输出被选中的资产集合，而不是直接输出权重。若要做连续权重，需要换成不同编码。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "assets = [\n    {'name': 'solar', 'return': 0.16, 'risk': 0.08, 'cost': 3.0},\n    {'name': 'grid-ai', 'return': 0.12, 'risk': 0.05, 'cost': 2.0},\n    {'name': 'battery', 'return': 0.11, 'risk': 0.04, 'cost': 2.0},\n    {'name': 'semicon', 'return': 0.18, 'risk': 0.13, 'cost': 4.0},\n    {'name': 'health', 'return': 0.09, 'risk': 0.03, 'cost': 1.0},\n]\ncorrelation = {\n    (0, 1): 0.04, (0, 2): 0.02, (0, 3): 0.05, (1, 3): 0.03,\n    (2, 4): 0.01, (3, 4): 0.02,\n}\nbudget = 7.0\n\nqubo = {}\nreturn_weight = 8.0\nrisk_weight = 5.0\nbudget_penalty = 1.1\nfor i, asset in enumerate(assets):\n    add_qubo_term(qubo, i, i, risk_weight * asset['risk'] - return_weight * asset['return'])\n    add_qubo_term(qubo, i, i, budget_penalty * (asset['cost'] ** 2 - 2 * budget * asset['cost']))\nfor (i, j), rho in correlation.items():\n    add_qubo_term(qubo, i, j, risk_weight * rho)\nfor i in range(len(assets)):\n    for j in range(i + 1, len(assets)):\n        add_qubo_term(qubo, i, j, 2 * budget_penalty * assets[i]['cost'] * assets[j]['cost'])\n\nn_vars = len(assets)\n\ndef decode_portfolio(bits):\n    chosen = [assets[i]['name'] for i, bit in enumerate(bits) if bit]\n    cost = sum(assets[i]['cost'] for i, bit in enumerate(bits) if bit)\n    expected_return = sum(assets[i]['return'] for i, bit in enumerate(bits) if bit)\n    return {'chosen_assets': chosen, 'budget_used': cost, 'expected_return': round(expected_return, 4)}\n\nstart_bits = [0, 1, 0, 1, 1]\n"
      },
      {
        "type": "markdown",
        "title": "5. 代码导读：调用 BOQA solver",
        "body": [
          "云端调用使用 algorithm=\"kernel_qa\"，因为提交的是二进制资产选择 QUBO。",
          "如果后端返回的 vector 长度和资产数量一致，就可以直接解码。",
          "如果返回占位 vector，需要后端接入当前案例的真实 QUBO 求解。",
          "本案例固定需要 kernel_qa（Kernel-QA）授权。若 key 类型不匹配，错误通常会在云端鉴权处出现；若没有看到 调用 BOQA solve，说明还没有真正发出请求。",
          "算法和 key 为什么这样选",
          "本案例需要 kernel_qa key，因为当前模型是二进制资产选择 QUBO。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "cloud_result = run_qubo_cloud(\n    problem=\"portfolio_qubo\",\n    qubo=qubo,\n    n_vars=n_vars,\n    algorithm=\"kernel_qa\",\n    params={'case_id': 'combo_portfolio', 'encoding': 'binary asset selection + budget penalty', 'decoder': 'selected asset list', 'objective': 'maximize return while penalizing risk and budget violation', 'iterations': 80},\n)\nplot_trace(cloud_result, \"BOQA API / portfolio_qubo\")\n"
      },
      {
        "type": "markdown",
        "title": "6. 结果怎么读",
        "body": [
          "低能量不等于稳赚，它只表示模型中的收益、风险和约束权衡得分更好。",
          "金融数据有噪声，正式场景还要做回测、压力测试和合规限制。",
          "曲线中的 eval 是每次候选解，best 是历史最好值。best 下降表示优化过程找到了更好的候选；如果只出现一个点，要查看 raw summary 是否只有单次结果或后端没有返回完整 history。"
        ],
        "bullets": []
      }
    ],
    "imageSet": {
      "width": 1672,
      "height": 941,
      "variants": [
        {
          "width": 480,
          "height": 270,
          "url": "/assets/qubo-portfolio-480.5a1200c7c558.webp",
          "bytes": 8030
        },
        {
          "width": 960,
          "height": 540,
          "url": "/assets/qubo-portfolio-960.8d7bea971ccd.webp",
          "bytes": 23976
        },
        {
          "width": 1672,
          "height": 941,
          "url": "/assets/qubo-portfolio-1672.4e250c30c41d.webp",
          "bytes": 51582
        }
      ]
    }
  },
  {
    "id": "combo-traffic-signal",
    "notebook": "notebooks/qubo_combo_traffic_signal.ipynb",
    "image": "/assets/combo-traffic-signal.png",
    "category": "教程（应用型）",
    "difficulty": 4,
    "title": "交通信号优化（QUBO）",
    "summary": "把交通信号灯控制转成 QUBO，学习冲突相位、等待成本和实时控制。",
    "description": "信号控制案例：用二进制变量表示每个相位是否放行，冲突相位不能同时启用，目标是降低等待和排队。",
    "requiredAlgorithm": "kernel_qa",
    "requiredAlgorithmLabel": "Kernel-QA",
    "setupCode": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：kernel_qa（Kernel-QA）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"kernel_qa\", algorithm_label=\"Kernel-QA\", key=QUBO_TEST_KEY)\n",
    "runCode": "cloud_result = run_qubo_cloud(\n    problem=\"traffic_signal_qubo\",\n    qubo=qubo,\n    n_vars=n_vars,\n    algorithm=\"kernel_qa\",\n    params={'case_id': 'combo_traffic_signal', 'encoding': 'conflict graph max-cut QUBO', 'decoder': 'two compatible signal groups', 'objective': 'separate high-conflict phases', 'iterations': 80},\n)\nplot_trace(cloud_result, \"BOQA API / traffic_signal_qubo\")\n",
    "lessonBlocks": [
      {
        "type": "code",
        "code": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：kernel_qa（Kernel-QA）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"kernel_qa\", algorithm_label=\"Kernel-QA\", key=QUBO_TEST_KEY)\n",
        "caption": "key setup"
      },
      {
        "type": "markdown",
        "title": "交通信号优化（QUBO）",
        "body": [
          "教程（应用型）",
          "交通信号相位优化，把冲突相位分组",
          "这个 notebook 按“背景知识 -> 人工解法 -> 数学模型 -> 代码导读 -> 云端调用 -> 结果解释”的顺序展开，目标是给基础用户学习，而不只是演示一段代码。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "1. 背景、来历和相关学科",
        "body": [
          "这个案例专注交通信号相位的组合优化。相位可以理解为一组允许通行的车流方向，互相冲突的相位不能同时放行。",
          "模型把相位冲突看成图，边代表冲突强度，优化器希望把强冲突拆开，同时保留通行效率。",
          "学习重点是图论如何进入交通工程：节点是相位，边是冲突或协调关系。",
          "交通信号设计和图论有天然联系。冲突矩阵、相位分组和协调控制都可以表示成图结构。",
          "QUBO 的二次项正好能描述两个相位之间的关系，因此适合做信号相位分组的教学案例。",
          "相关知识点",
          "基础理论：实时信号控制中的离散决策",
          "交通信号优化既可以是连续问题（绿灯秒数），也可以是离散问题（相位顺序、相位组合、是否启用某个策略）。本案例聚焦离散控制。",
          "组合优化常用于快速评估相位方案，再把候选方案交给微观交通仿真或实际控制器进一步验证。",
          "用 QUBO 建模时，重点不是模拟每辆车，而是把冲突、需求权重和相位选择关系编码成可求解的能量函数。"
        ],
        "bullets": [
          "图论：节点、边、割和冲突图。",
          "交通工程：相位、流向、冲突点和绿灯分配。",
          "组合优化：从多个相位分组里选低冲突方案。",
          "仿真验证：用路网仿真检查延误和排队。"
        ]
      },
      {
        "type": "markdown",
        "title": "交通信号排程为什么是组合优化",
        "body": [
          "信号灯不是连续随便调，而是在若干相位、周期和时间片之间做选择。每个选择都会影响后续排队，因此它是典型的离散排程问题。",
          "交通工程里常用 Webster 方法、仿真软件和实时控制策略。QUBO 教学案例用更小的抽象模型展示核心：哪些相位该在什么时候被选择。"
        ],
        "bullets": [
          "状态：当前时间片选择哪个相位。",
          "需求：各方向车流量或排队长度。",
          "约束：冲突车流不能同时放行，切换不能过于频繁。"
        ]
      },
      {
        "type": "markdown",
        "title": "与交通信号优化（QUBO）的区别",
        "body": [
          "这个组合案例更强调“排程”：在一段时间内安排相位序列。交通信号优化案例更强调“控制”：根据需求选择相位和绿灯分配。",
          "两者使用类似的 QUBO 语言，但业务解释不同。用户读代码时要看变量代表的是时间片、相位、路口还是需求权重。"
        ],
        "bullets": [
          "排程模型看顺序。",
          "控制模型看状态反馈。",
          "解码结果都要回到相位表，才能被交通工程师理解。"
        ]
      },
      {
        "type": "markdown",
        "title": "2. 如果不用代码，人会怎么解",
        "body": [
          "人工做相位设计时，会先列出哪些流向互相冲突，再按主路、支路和左转需求分组。",
          "当相位很多时，人工很难同时平衡所有冲突权重。",
          "QUBO 把每对相位的关系都纳入能量函数，适合教学展示。",
          "这一步很重要：人工解法不是被丢掉，而是帮助我们发现哪些规则应该变成变量、约束或惩罚项。优化模型只是把这些规则变成可重复执行的计算流程。",
          "手工推理：一个路口先做相位表",
          "人工会先列出直行、左转、右转等相位，然后标注哪些相位互相冲突。",
          "再根据流量给相位权重：车多的方向更希望被优先照顾，但不能破坏冲突约束。",
          "优化器做的事情就是在这些可行相位组合里找低能量方案。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "3. 从业务问题到数学模型",
        "body": [
          "变量 x[i] 表示相位 i 属于某个组或采用某种状态。",
          "强冲突相位如果被放到同组，会产生大惩罚。",
          "弱冲突或可协调相位可以用较小权重处理。",
          "本案例使用 kernel_qa key。",
          "读模型时抓住四件事"
        ],
        "bullets": [
          "变量：优化器能改变什么。",
          "目标函数：什么方案更好。",
          "硬约束：什么方案绝对不允许。",
          "解码器：如何把 bit vector 或参数向量翻译回业务语言。"
        ]
      },
      {
        "type": "markdown",
        "title": "4. 代码导读：准备数据、编码器和解码器",
        "body": [
          "phases 是候选相位列表。",
          "edges 描述相位之间的冲突强度。",
          "qubo_from_max_cut 借用 max-cut 思路，把强冲突边尽量切开。",
          "阅读下面代码时，可以先忽略 Python 细节，按注释找出“数据在哪里、变量怎么编号、惩罚项在哪里加入、最后怎么解码”。",
          "代码导读：相位变量和需求权重",
          "相位选择变量通常用 bit 表示启用或分组。",
          "冲突表决定二次惩罚；需求权重决定一次奖励或成本。",
          "结果解码后要回到“哪些相位同组、哪些顺序执行”的交通语言，才有工程意义。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "phases = ['N-S straight', 'E-W straight', 'N left', 'S left', 'E left', 'W left']\nedges = [\n    (0, 1, 4.2), (0, 4, 2.5), (0, 5, 2.1),\n    (1, 2, 2.4), (1, 3, 2.2), (2, 4, 1.4), (3, 5, 1.5),\n]\nqubo = {}\nfor u, v, weight in edges:\n    # max-cut -> minimize -w*x_u -w*x_v + 2w*x_u*x_v\n    add_qubo_term(qubo, u, u, -weight)\n    add_qubo_term(qubo, v, v, -weight)\n    add_qubo_term(qubo, u, v, 2 * weight)\nn_vars = len(phases)\n\ndef decode_signal(bits):\n    return {\n        'group_A': [phases[i] for i, bit in enumerate(bits) if bit == 0],\n        'group_B': [phases[i] for i, bit in enumerate(bits) if bit == 1],\n    }\n\nstart_bits = [1, 0, 1, 0, 0, 1]\n"
      },
      {
        "type": "markdown",
        "title": "5. 代码导读：调用 BOQA solver",
        "body": [
          "云端调用固定 algorithm=\"kernel_qa\"。",
          "如果用户拿的是 kernel_qa 或 cma_es key，鉴权会失败，因为后端看到的算法名不同。",
          "运行成功后，best_vector 可被解码为相位分组。",
          "本案例固定需要 kernel_qa（Kernel-QA）授权。若 key 类型不匹配，错误通常会在云端鉴权处出现；若没有看到 调用 BOQA solve，说明还没有真正发出请求。",
          "算法和 key 为什么这样选",
          "本案例需要 kernel_qa key，因为它是相位组合 QUBO。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "cloud_result = run_qubo_cloud(\n    problem=\"traffic_signal_qubo\",\n    qubo=qubo,\n    n_vars=n_vars,\n    algorithm=\"kernel_qa\",\n    params={'case_id': 'combo_traffic_signal', 'encoding': 'conflict graph max-cut QUBO', 'decoder': 'two compatible signal groups', 'objective': 'separate high-conflict phases', 'iterations': 80},\n)\nplot_trace(cloud_result, \"BOQA API / traffic_signal_qubo\")\n"
      },
      {
        "type": "markdown",
        "title": "6. 结果怎么读",
        "body": [
          "能量越低，表示冲突关系处理得越好。",
          "最终交通效果仍要看仿真中的延误、排队和吞吐量。",
          "曲线中的 eval 是每次候选解，best 是历史最好值。best 下降表示优化过程找到了更好的候选；如果只出现一个点，要查看 raw summary 是否只有单次结果或后端没有返回完整 history。"
        ],
        "bullets": []
      }
    ],
    "imageSet": {
      "width": 1672,
      "height": 941,
      "variants": [
        {
          "width": 480,
          "height": 270,
          "url": "/assets/combo-traffic-signal-480.46cbcf7ab67a.webp",
          "bytes": 9570
        },
        {
          "width": 960,
          "height": 540,
          "url": "/assets/combo-traffic-signal-960.c760eb07352b.webp",
          "bytes": 24450
        },
        {
          "width": 1672,
          "height": 941,
          "url": "/assets/combo-traffic-signal-1672.23c8fe956266.webp",
          "bytes": 50968
        }
      ]
    }
  },
  {
    "id": "qubo-employee-assignment",
    "notebook": "notebooks/qubo_employee_assignment.ipynb",
    "image": "/assets/qubo-employee-assignment.png",
    "category": "演示应用程序",
    "difficulty": 5,
    "title": "员工分配问题",
    "summary": "把员工技能、班次需求和门店岗位匹配写成 one-hot 分配模型。",
    "description": "排班分配案例：员工、岗位和时间段形成选择变量，技能不匹配、超工时和需求缺口都会变成惩罚项。",
    "requiredAlgorithm": "kernel_qa",
    "requiredAlgorithmLabel": "Kernel-QA",
    "setupCode": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：kernel_qa（Kernel-QA）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"kernel_qa\", algorithm_label=\"Kernel-QA\", key=QUBO_TEST_KEY)\n",
    "runCode": "cloud_result = run_qubo_cloud(\n    problem=\"employee_assignment_qubo\",\n    qubo=qubo,\n    n_vars=n_vars,\n    algorithm=\"kernel_qa\",\n    params={'case_id': 'combo_employee_assignment', 'encoding': 'employee-store one-hot + demand penalty', 'decoder': 'store roster', 'objective': 'maximize skill fit while meeting demand', 'iterations': 80},\n)\nplot_trace(cloud_result, \"BOQA API / employee_assignment_qubo\")\n",
    "lessonBlocks": [
      {
        "type": "code",
        "code": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：kernel_qa（Kernel-QA）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"kernel_qa\", algorithm_label=\"Kernel-QA\", key=QUBO_TEST_KEY)\n",
        "caption": "key setup"
      },
      {
        "type": "markdown",
        "title": "员工分配问题",
        "body": [
          "演示应用程序",
          "员工排班与门店分配，把技能和需求匹配起来",
          "这个 notebook 按“背景知识 -> 人工解法 -> 数学模型 -> 代码导读 -> 云端调用 -> 结果解释”的顺序展开，目标是给基础用户学习，而不只是演示一段代码。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "1. 背景、来历和相关学科",
        "body": [
          "员工分配问题在零售、客服、运维和医疗排班中很常见。不同岗位需要不同技能，不同门店或时段也有不同需求。",
          "组合优化要回答：谁去哪里，是否满足技能，是否公平，是否避免过载。",
          "学习重点是 one-hot 分配、技能匹配和公平惩罚如何进入 QUBO。",
          "这类问题来自运筹学中的 assignment problem、排班问题和资源调度。经典 Hungarian algorithm 可以处理简单一对一分配，但真实业务常有技能、偏好和容量约束。",
          "QUBO 适合做教学，因为每个“员工-岗位”选择都是一个 0/1 变量，冲突和偏好可以逐项加入。",
          "相关知识点",
          "基础理论：匹配、排班和公平约束",
          "员工分配问题来自运筹学中的 assignment 和 scheduling。一个员工能否去某门店，取决于技能、班次、需求、距离和劳动规则。",
          "简单的二分匹配只考虑“人和岗位是否匹配”，真实排班还要考虑每个门店需要多少人、每个人最多排几班、是否公平分配。",
          "QUBO 建模时，每个“员工-门店”组合可以是一个 bit。技能匹配、需求满足和重复分配都可以写成能量项。"
        ],
        "bullets": [
          "运筹学：指派问题、排班和资源约束。",
          "人力资源管理：技能、工时、公平和偏好。",
          "组合优化：在大量可能分配中找低成本方案。",
          "约束建模：硬规则和软偏好要分开。"
        ]
      },
      {
        "type": "markdown",
        "title": "员工分配背后的运筹学",
        "body": [
          "员工分配、排班和岗位匹配是经典运筹学问题。它看起来像“谁去哪儿上班”，数学上却包含技能匹配、容量、工时、公平性和偏好等多层约束。",
          "人工做分配时，会先满足硬约束：员工有没有技能、门店需不需要人、一个人不能同时去两个地方。然后再优化软目标：减少通勤、照顾偏好、均衡工作量。"
        ],
        "bullets": [
          "硬约束：必须满足，否则方案不可用。",
          "软约束：尽量满足，用惩罚项表达。",
          "目标：覆盖需求，同时减少成本和不公平。"
        ]
      },
      {
        "type": "markdown",
        "title": "如何从表格变成 QUBO",
        "body": [
          "变量可以定义为 x[员工, 门店]，等于 1 表示把该员工分到该门店。每个员工最多选一个门店，每个门店要满足人数需求，这些都可以写成惩罚项。",
          "如果员工技能和门店需求不匹配，就给这个选择加很高惩罚。解码器最后把变量矩阵翻译成排班表。"
        ],
        "bullets": [
          "one-hot/at-most-one：防止重复分配。",
          "需求惩罚：门店缺人或超人都加成本。",
          "偏好项：喜欢的安排降低能量，不喜欢的安排提高能量。"
        ]
      },
      {
        "type": "markdown",
        "title": "2. 如果不用代码，人会怎么解",
        "body": [
          "人工排班通常先安排技能稀缺的人，再填普通岗位。",
          "如果只凭经验，容易让某些员工负担过重，或忽略门店之间的整体需求。",
          "QUBO 可以同时考虑每个员工只能去一个位置、岗位需求、技能匹配和公平性。",
          "这一步很重要：人工解法不是被丢掉，而是帮助我们发现哪些规则应该变成变量、约束或惩罚项。优化模型只是把这些规则变成可重复执行的计算流程。",
          "手工推理：先满足硬需求，再优化偏好",
          "人工排班时会先把必须有证书或经验的人放到关键岗位，再补齐普通岗位。",
          "如果某个门店缺人，惩罚应该很大；如果某个员工距离稍远，惩罚可以较小，因为它是偏好不是硬规则。",
          "这就是 QUBO 里惩罚系数的含义：硬规则用大权重，软偏好用小权重。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "3. 从业务问题到数学模型",
        "body": [
          "变量 x[employee, store] 表示员工是否分配到某门店。",
          "每个员工最多或恰好选择一个门店。",
          "门店需求未满足、技能不匹配或过载都会形成惩罚。",
          "本案例使用 kernel_qa key。",
          "读模型时抓住四件事"
        ],
        "bullets": [
          "变量：优化器能改变什么。",
          "目标函数：什么方案更好。",
          "硬约束：什么方案绝对不允许。",
          "解码器：如何把 bit vector 或参数向量翻译回业务语言。"
        ]
      },
      {
        "type": "markdown",
        "title": "4. 代码导读：准备数据、编码器和解码器",
        "body": [
          "employees 是员工集合，stores 是门店集合。",
          "skills 和 demand 体现业务规则：不是所有员工都适合所有门店。",
          "编码器把这些业务规则转成二进制选择和惩罚项。",
          "阅读下面代码时，可以先忽略 Python 细节，按注释找出“数据在哪里、变量怎么编号、惩罚项在哪里加入、最后怎么解码”。",
          "代码导读：员工-岗位 bit 和约束惩罚",
          "employees 和 stores 是业务数据。编码器会为每个可行分配创建变量。",
          "one-hot 或 capacity penalty 保证一个员工不会被重复分配，门店需求也尽量被满足。",
          "解码器把 bit vector 变成员工到门店的表格，便于非技术用户检查。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "employees = ['Aki', 'Bo', 'Chen', 'Dia', 'Eli']\nstores = ['north', 'east', 'west']\ndemand = {'north': 2, 'east': 2, 'west': 1}\nskill = {\n    ('Aki','north'): 8, ('Aki','east'): 5, ('Aki','west'): 6,\n    ('Bo','north'): 4, ('Bo','east'): 9, ('Bo','west'): 5,\n    ('Chen','north'): 6, ('Chen','east'): 5, ('Chen','west'): 9,\n    ('Dia','north'): 8, ('Dia','east'): 7, ('Dia','west'): 4,\n    ('Eli','north'): 5, ('Eli','east'): 8, ('Eli','west'): 6,\n}\n\ndef idx(e, s): return e * len(stores) + s\nqubo = {}\nfor e, name in enumerate(employees):\n    one_hot_penalty(qubo, [idx(e, s) for s in range(len(stores))], penalty=12.0)\n    for s, store in enumerate(stores):\n        add_qubo_term(qubo, idx(e, s), idx(e, s), -skill[(name, store)])\nfor s, store in enumerate(stores):\n    vars_for_store = [idx(e, s) for e in range(len(employees))]\n    target = demand[store]\n    for i in vars_for_store:\n        add_qubo_term(qubo, i, i, 8.0 * (1 - 2 * target))\n    for a, i in enumerate(vars_for_store):\n        for j in vars_for_store[a+1:]:\n            add_qubo_term(qubo, i, j, 16.0)\nn_vars = len(employees) * len(stores)\n\ndef decode_assignment(bits):\n    result = {store: [] for store in stores}\n    for e, name in enumerate(employees):\n        choices = [(stores[s], bits[idx(e, s)]) for s in range(len(stores))]\n        chosen = max(choices, key=lambda item: item[1])[0]\n        result[chosen].append(name)\n    return result\n\nstart_plan = {'Aki':'north', 'Bo':'east', 'Chen':'west', 'Dia':'north', 'Eli':'east'}\nstart_bits = [0] * n_vars\nfor e, employee in enumerate(employees):\n    start_bits[idx(e, stores.index(start_plan[employee]))] = 1\n"
      },
      {
        "type": "markdown",
        "title": "5. 代码导读：调用 BOQA solver",
        "body": [
          "云端调用使用 algorithm=\"kernel_qa\"。",
          "Kernel-QA 适合这类离散约束满足/组合分配演示。",
          "返回向量需要由解码器翻译成“员工 -> 门店”的表。",
          "本案例固定需要 kernel_qa（Kernel-QA）授权。若 key 类型不匹配，错误通常会在云端鉴权处出现；若没有看到 调用 BOQA solve，说明还没有真正发出请求。",
          "算法和 key 为什么这样选",
          "本案例需要 kernel_qa key，因为当前后端把这类小规模约束满足 QUBO 放在 Kernel-QA 授权下演示。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "cloud_result = run_qubo_cloud(\n    problem=\"employee_assignment_qubo\",\n    qubo=qubo,\n    n_vars=n_vars,\n    algorithm=\"kernel_qa\",\n    params={'case_id': 'combo_employee_assignment', 'encoding': 'employee-store one-hot + demand penalty', 'decoder': 'store roster', 'objective': 'maximize skill fit while meeting demand', 'iterations': 80},\n)\nplot_trace(cloud_result, \"BOQA API / employee_assignment_qubo\")\n"
      },
      {
        "type": "markdown",
        "title": "6. 结果怎么读",
        "body": [
          "结果首先看是否违反硬约束，再看公平性和偏好成本。",
          "低能量解是模型意义上的好排班，正式上线还需要人工审核和业务规则补充。",
          "曲线中的 eval 是每次候选解，best 是历史最好值。best 下降表示优化过程找到了更好的候选；如果只出现一个点，要查看 raw summary 是否只有单次结果或后端没有返回完整 history。"
        ],
        "bullets": []
      }
    ],
    "imageSet": {
      "width": 1672,
      "height": 941,
      "variants": [
        {
          "width": 480,
          "height": 270,
          "url": "/assets/qubo-employee-assignment-480.ad63241f02a1.webp",
          "bytes": 10104
        },
        {
          "width": 960,
          "height": 540,
          "url": "/assets/qubo-employee-assignment-960.cd468c701a8b.webp",
          "bytes": 25372
        },
        {
          "width": 1672,
          "height": 941,
          "url": "/assets/qubo-employee-assignment-1672.32568c6d3cf4.webp",
          "bytes": 50342
        }
      ]
    }
  },
  {
    "id": "qubo-cvrp",
    "notebook": "notebooks/qubo_cvrp.ipynb",
    "image": "/assets/qubo-cvrp.png",
    "category": "演示应用程序",
    "difficulty": 4,
    "title": "带容量约束的车辆路径问题（CVRP）",
    "summary": "车辆路径规划案例：在客户需求、车辆容量和行驶距离之间寻找低成本路线。",
    "description": "物流路径案例：CVRP 要决定车辆访问客户的顺序，同时满足每辆车容量不超限，适合学习路径编码和约束惩罚。",
    "requiredAlgorithm": "kernel_qa",
    "requiredAlgorithmLabel": "Kernel-QA",
    "setupCode": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：kernel_qa（Kernel-QA）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"kernel_qa\", algorithm_label=\"Kernel-QA\", key=QUBO_TEST_KEY)\n",
    "runCode": "cloud_result = run_qubo_cloud(\n    problem=\"cvrp_assignment_qubo\",\n    qubo=qubo,\n    n_vars=n_vars,\n    algorithm=\"kernel_qa\",\n    params={'case_id': 'combo_cvrp', 'encoding': 'customer-vehicle one-hot + capacity penalty', 'decoder': 'vehicle customer groups', 'objective': 'minimize route proxy cost and capacity violation', 'iterations': 80},\n)\nplot_trace(cloud_result, \"BOQA API / cvrp_assignment_qubo\")\n",
    "lessonBlocks": [
      {
        "type": "code",
        "code": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：kernel_qa（Kernel-QA）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"kernel_qa\", algorithm_label=\"Kernel-QA\", key=QUBO_TEST_KEY)\n",
        "caption": "key setup"
      },
      {
        "type": "markdown",
        "title": "带容量约束的车辆路径问题（CVRP）",
        "body": [
          "演示应用程序",
          "带容量约束车辆路径，把路线和载重一起考虑",
          "这个 notebook 按“背景知识 -> 人工解法 -> 数学模型 -> 代码导读 -> 云端调用 -> 结果解释”的顺序展开，目标是给基础用户学习，而不只是演示一段代码。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "1. 背景、来历和相关学科",
        "body": [
          "CVRP 是车辆路径问题的一种：多辆车从仓库出发服务客户，每辆车有容量限制，每个客户需要被访问。",
          "它比普通旅行商问题更接近物流配送，因为还要决定客户分给哪辆车、每辆车的访问顺序和是否超载。",
          "学习重点是路径问题为什么难，以及如何把访问顺序、容量和距离编码成优化目标。",
          "车辆路径问题来自物流、运筹学和图论。TSP 只关心一辆车访问所有城市，CVRP 加入多辆车和容量约束后更贴近快递、冷链和垃圾收运。",
          "QUBO 可以表达小规模教学版路径问题，但大规模物流通常会结合启发式、局部搜索和专业求解器。",
          "相关知识点",
          "基础理论：从 TSP 到 CVRP",
          "TSP 只要求一辆车访问所有点并回到起点。CVRP 在此基础上加入车辆容量：每条路线上的客户需求总量不能超过车辆载重。",
          "这类问题来自物流、快递、垃圾收运和冷链配送。它结合了路径选择、分组和容量约束，是运筹学的经典难题。",
          "QUBO 建模通常要把“客户在第几辆车、第几个位置被访问”编码为二进制变量，同时惩罚重复访问、漏访问和超载。"
        ],
        "bullets": [
          "图论：客户点和道路距离构成加权图。",
          "运筹学：路径、容量、分配和调度。",
          "物流工程：车辆载重、时间窗和配送成本。",
          "启发式算法：大规模 VRP 常用遗传算法、禁忌搜索和局部搜索。"
        ]
      },
      {
        "type": "markdown",
        "title": "CVRP 和物流路径优化的来历",
        "body": [
          "车辆路径问题 VRP 是旅行商问题 TSP 的扩展：不只要决定访问顺序，还要决定用哪辆车、每辆车装多少货、是否超容量。CVRP 的 C 指 capacity，也就是容量约束。",
          "物流、垃圾收运、冷链配送和上门服务都可以抽象成 VRP 家族。真实系统还会加入时间窗、司机工时、道路限行和多仓库。"
        ],
        "bullets": [
          "客户必须被服务。",
          "车辆容量不能超过上限。",
          "路线总距离或总成本尽量低。"
        ]
      },
      {
        "type": "markdown",
        "title": "人工解路线和 QUBO 解路线的关系",
        "body": [
          "人工排路线会先按地理位置聚类，再检查每辆车装载量，最后调整访问顺序。QUBO 把这些判断拆成变量：客户属于哪辆车、在路线第几个位置、是否违反容量。",
          "因为变量很多，CVRP 很快会变大。教学案例用小规模数据让用户看懂编码和解码，真实落地应把大问题拆分或结合启发式算法。"
        ],
        "bullets": [
          "距离矩阵是成本来源。",
          "容量惩罚防止不可行路线。",
          "解码结果要画成路线而不是只看能量值。"
        ]
      },
      {
        "type": "markdown",
        "title": "2. 如果不用代码，人会怎么解",
        "body": [
          "人工调度会先按区域或客户需求分组，再为每辆车安排路线。",
          "如果只按最近距离贪心，可能导致车辆超载或后续路线绕远。",
          "优化模型会同时看距离和容量，让局部选择服从全局成本。",
          "这一步很重要：人工解法不是被丢掉，而是帮助我们发现哪些规则应该变成变量、约束或惩罚项。优化模型只是把这些规则变成可重复执行的计算流程。",
          "手工推理：先分车，再排顺序",
          "人工做 CVRP 往往先按地理位置和需求量把客户分给车辆，再在每辆车内部排访问顺序。",
          "如果某条路线超载，就需要把一两个客户移到别的车；如果路线交叉太多，就交换访问顺序。",
          "优化模型把这些局部调整统一写成目标函数，让 solver 在大量组合中搜索。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "3. 从业务问题到数学模型",
        "body": [
          "变量可以表示车辆在某个顺序访问某客户，或某客户是否分给某车。",
          "每个客户必须被访问一次，车辆载重不能超过容量。",
          "距离成本通常来自相邻访问点之间的二次项。",
          "本案例使用 kernel_qa key。",
          "读模型时抓住四件事"
        ],
        "bullets": [
          "变量：优化器能改变什么。",
          "目标函数：什么方案更好。",
          "硬约束：什么方案绝对不允许。",
          "解码器：如何把 bit vector 或参数向量翻译回业务语言。"
        ]
      },
      {
        "type": "markdown",
        "title": "4. 代码导读：准备数据、编码器和解码器",
        "body": [
          "customers 存放客户需求和位置。",
          "distance 或坐标用于计算路线成本。",
          "编码器把客户访问、车辆容量和路径顺序转成 QUBO 约束。",
          "阅读下面代码时，可以先忽略 Python 细节，按注释找出“数据在哪里、变量怎么编号、惩罚项在哪里加入、最后怎么解码”。",
          "代码导读：位置编码、距离和容量惩罚",
          "距离矩阵决定行驶成本，是目标函数的核心。",
          "位置 one-hot 保证每个客户只出现在一个路线位置上；容量项惩罚超载。",
          "解码器把 bit vector 转成每辆车的客户访问序列，才是用户能理解的结果。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "customers = [\n    {'name':'C1', 'demand':2, 'dist':4.0},\n    {'name':'C2', 'demand':3, 'dist':6.0},\n    {'name':'C3', 'demand':1, 'dist':3.0},\n    {'name':'C4', 'demand':2, 'dist':5.5},\n]\nvehicles = ['van-A', 'van-B']\ncapacity = {'van-A': 4, 'van-B': 5}\n\ndef idx(c, v): return c * len(vehicles) + v\nqubo = {}\nfor c in range(len(customers)):\n    one_hot_penalty(qubo, [idx(c, v) for v in range(len(vehicles))], penalty=10.0)\n    for v, vehicle in enumerate(vehicles):\n        add_qubo_term(qubo, idx(c, v), idx(c, v), customers[c]['dist'] * 0.35)\nfor v, vehicle in enumerate(vehicles):\n    vars_for_vehicle = [idx(c, v) for c in range(len(customers))]\n    cap = capacity[vehicle]\n    for c, i in enumerate(vars_for_vehicle):\n        demand_i = customers[c]['demand']\n        add_qubo_term(qubo, i, i, 1.7 * (demand_i ** 2 - 2 * cap * demand_i))\n    for a, i in enumerate(vars_for_vehicle):\n        for b, j in enumerate(vars_for_vehicle[a+1:], start=a+1):\n            add_qubo_term(qubo, i, j, 2 * 1.7 * customers[a]['demand'] * customers[b]['demand'])\nn_vars = len(customers) * len(vehicles)\n\ndef decode_cvrp(bits):\n    routes = {vehicle: [] for vehicle in vehicles}\n    for c, cust in enumerate(customers):\n        chosen_v = max(range(len(vehicles)), key=lambda v: bits[idx(c, v)])\n        routes[vehicles[chosen_v]].append(cust['name'])\n    return routes\n\nstart_plan = {'C1':'van-A', 'C2':'van-B', 'C3':'van-B', 'C4':'van-A'}\nstart_bits = [0] * n_vars\nfor c, customer in enumerate(customers):\n    start_bits[idx(c, vehicles.index(start_plan[customer['name']]))] = 1\n"
      },
      {
        "type": "markdown",
        "title": "5. 代码导读：调用 BOQA solver",
        "body": [
          "云端调用使用 algorithm=\"kernel_qa\"，因为这里提交的是离散路径 QUBO。",
          "返回 bit vector 后，解码器会尝试还原每辆车的客户顺序。",
          "如果向量长度不匹配，通常说明后端返回的是占位结果，还没有按该 QUBO 实际求解。",
          "本案例固定需要 kernel_qa（Kernel-QA）授权。若 key 类型不匹配，错误通常会在云端鉴权处出现；若没有看到 调用 BOQA solve，说明还没有真正发出请求。",
          "算法和 key 为什么这样选",
          "本案例需要 kernel_qa key，因为 CVRP 是典型离散 QUBO 路径问题。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "cloud_result = run_qubo_cloud(\n    problem=\"cvrp_assignment_qubo\",\n    qubo=qubo,\n    n_vars=n_vars,\n    algorithm=\"kernel_qa\",\n    params={'case_id': 'combo_cvrp', 'encoding': 'customer-vehicle one-hot + capacity penalty', 'decoder': 'vehicle customer groups', 'objective': 'minimize route proxy cost and capacity violation', 'iterations': 80},\n)\nplot_trace(cloud_result, \"BOQA API / cvrp_assignment_qubo\")\n"
      },
      {
        "type": "markdown",
        "title": "6. 结果怎么读",
        "body": [
          "结果要同时看总距离和是否超载。只看距离最短但超载的路线不可用。",
          "真实物流还会加入时间窗、司机工时、装卸限制和道路实时状态。",
          "曲线中的 eval 是每次候选解，best 是历史最好值。best 下降表示优化过程找到了更好的候选；如果只出现一个点，要查看 raw summary 是否只有单次结果或后端没有返回完整 history。"
        ],
        "bullets": []
      }
    ],
    "imageSet": {
      "width": 1672,
      "height": 941,
      "variants": [
        {
          "width": 480,
          "height": 270,
          "url": "/assets/qubo-cvrp-480.5261350558aa.webp",
          "bytes": 13706
        },
        {
          "width": 960,
          "height": 540,
          "url": "/assets/qubo-cvrp-960.01d5732d5ed1.webp",
          "bytes": 35292
        },
        {
          "width": 1672,
          "height": 941,
          "url": "/assets/qubo-cvrp-1672.f465de3bef20.webp",
          "bytes": 74694
        }
      ]
    }
  },
  {
    "id": "qubo-sudoku",
    "notebook": "notebooks/qubo_sudoku.ipynb",
    "image": "/assets/qubo-sudoku.png",
    "category": "演示应用程序",
    "difficulty": 3,
    "title": "数独",
    "summary": "用 one-hot 变量表示数字填入，把行、列、宫约束写成 QUBO。",
    "description": "数独案例：每个格子只能填一个数字，每行每列每宫都不能重复，代码展示如何编码、求解和解码成棋盘。",
    "requiredAlgorithm": "kernel_qa",
    "requiredAlgorithmLabel": "Kernel-QA",
    "setupCode": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：kernel_qa（Kernel-QA）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"kernel_qa\", algorithm_label=\"Kernel-QA\", key=QUBO_TEST_KEY)\n",
    "runCode": "cloud_result = run_qubo_cloud(\n    problem=\"sudoku_qubo\",\n    qubo=qubo,\n    n_vars=n_vars,\n    algorithm=\"kernel_qa\",\n    params={'case_id': 'combo_sudoku', 'encoding': 'cell-value one-hot constraints', 'decoder': '4x4 Sudoku grid', 'objective': 'satisfy Sudoku constraints and givens', 'iterations': 80},\n)\nplot_trace(cloud_result, \"BOQA API / sudoku_qubo\")\n",
    "lessonBlocks": [
      {
        "type": "code",
        "code": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：kernel_qa（Kernel-QA）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"kernel_qa\", algorithm_label=\"Kernel-QA\", key=QUBO_TEST_KEY)\n",
        "caption": "key setup"
      },
      {
        "type": "markdown",
        "title": "数独",
        "body": [
          "演示应用程序",
          "数独的数学模型，从推理游戏到约束满足",
          "这个 notebook 按“背景知识 -> 人工解法 -> 数学模型 -> 代码导读 -> 云端调用 -> 结果解释”的顺序展开，目标是给基础用户学习，而不只是演示一段代码。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "1. 背景、来历和相关学科",
        "body": [
          "数独表面是填数字游戏，本质是一个约束满足问题：每个格子必须填一个数字，每行、每列、每个宫里的数字都不能重复。",
          "它和拉丁方、精确覆盖、图着色、SAT 求解都有关系。把数独做成 QUBO，可以让初学者直观看到“推理规则”如何变成“能量惩罚”。",
          "本案例用 4x4 教学版而不是 9x9，是为了让变量数量和代码都保持可读。规则完全相同，只是规模更小。",
          "现代数独作为报纸谜题流行起来，但它的数学结构更早就存在于拉丁方研究中。拉丁方要求每个符号在每行每列恰好出现一次，数独在此基础上加入宫格约束。",
          "从计算机角度看，数独可转成精确覆盖：每个选择同时满足“这个格子被填、这一行有这个数字、这一列有这个数字、这个宫有这个数字”四类条件。",
          "相关知识点",
          "基础理论：数独、拉丁方和精确覆盖",
          "数独的现代形式因报纸和杂志流行，但它背后的数学结构可以追溯到拉丁方。拉丁方要求每行每列各符号只出现一次，数独额外加入宫格规则。",
          "从算法角度看，数独可以表示为精确覆盖问题：每个候选填法同时覆盖格子、行数字、列数字、宫数字四类约束。",
          "QUBO 版本把这些“必须恰好一次”的规则写成 one-hot 惩罚。正确解的能量最低，因为它同时满足所有约束。"
        ],
        "bullets": [
          "离散数学：集合、排列和拉丁方。",
          "约束满足：变量、候选值和约束传播。",
          "图着色：相互冲突的格子不能取同一数字。",
          "SAT/QUBO：把逻辑规则改写成可优化的能量函数。"
        ]
      },
      {
        "type": "markdown",
        "title": "数独的来历、数学关系和推理规则",
        "body": [
          "数独可以看成拉丁方的一种带宫格约束的数字填充问题。9x9 标准数独要求每行、每列、每个 3x3 宫都包含 1 到 9 且不重复。教学案例用更小的 4x4 数独，是为了让变量数量和图示更容易理解。",
          "数独和组合数学、约束满足问题（CSP）、精确覆盖（Exact Cover）都有关系。Knuth 的 Dancing Links 常用于精确覆盖求解；QUBO 则把“必须唯一”“不能冲突”写成能量惩罚。"
        ],
        "bullets": [
          "行约束：每行每个数字出现一次。",
          "列约束：每列每个数字出现一次。",
          "宫约束：每个小宫每个数字出现一次。",
          "格子约束：每个格子只能填一个数字。"
        ]
      },
      {
        "type": "markdown",
        "title": "人工怎么解数独，模型又怎么解",
        "body": [
          "人工解数独常用候选数法：先列出每个空格可能填的数字，再根据同行、同列、同宫排除。若某格只剩一个候选，就可以确定；若某个数字在某行只剩一个位置，也可以确定。",
          "QUBO 不按人类推理顺序一步步填格，而是同时搜索整张表。变量 x[row, col, value] 表示某格是否填某个数字；违反数独规则的组合会被加惩罚，求解器寻找总惩罚最低的填法。"
        ],
        "bullets": [
          "显性唯一：某格只有一个候选。",
          "隐性唯一：某数字在某行/列/宫只有一个位置。",
          "QUBO 视角：所有候选都是开关，规则是惩罚项。"
        ]
      },
      {
        "type": "markdown",
        "title": "2. 如果不用代码，人会怎么解",
        "body": [
          "人解数独时会先看已知数字，用排除法删除同一行、列、宫中的候选。",
          "当某个格子只剩一个候选时，它就被确定；当某个数字在某行只能放一个位置时，也能确定。",
          "更复杂的技巧，如区块交互、候选链和唯一矩形，本质仍是在传播约束。QUBO 不逐条模仿这些技巧，而是把所有规则一次性写入能量函数。",
          "这一步很重要：人工解法不是被丢掉，而是帮助我们发现哪些规则应该变成变量、约束或惩罚项。优化模型只是把这些规则变成可重复执行的计算流程。",
          "手工推理：候选数和唯一位置",
          "人解数独时先根据已知数字删除候选。如果一个格子只剩一个候选，就能确定。",
          "如果某个数字在一行里只有一个可放位置，即使那个格子还有其他候选，也能确定。",
          "QUBO 不逐条模拟这些技巧，而是一次性枚举候选并用惩罚项约束它们，搜索低能量解。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "3. 从业务问题到数学模型",
        "body": [
          "变量 x[row, col, digit] 表示某个格子是否填某个数字。",
          "每个格子必须且只能选择一个数字，这是第一个 one-hot 约束。",
          "每行、每列、每个宫中，每个数字也必须且只能出现一次。",
          "已知数字用强惩罚固定。违反任一规则都会提高 QUBO 能量。本案例使用 kernel_qa key。",
          "读模型时抓住四件事"
        ],
        "bullets": [
          "变量：优化器能改变什么。",
          "目标函数：什么方案更好。",
          "硬约束：什么方案绝对不允许。",
          "解码器：如何把 bit vector 或参数向量翻译回业务语言。"
        ]
      },
      {
        "type": "markdown",
        "title": "4. 代码导读：准备数据、编码器和解码器",
        "body": [
          "givens 是题目给出的已知数字。",
          "idx(r,c,v) 把三维含义映射到一维 bit 下标，这一步是编码器的核心。",
          "one_hot_penalty 反复出现，因为数独的大部分规则都是“恰好选一个”。",
          "阅读下面代码时，可以先忽略 Python 细节，按注释找出“数据在哪里、变量怎么编号、惩罚项在哪里加入、最后怎么解码”。",
          "代码导读：三维变量和四类约束",
          "x[row, col, digit] 是核心变量，表示某格是否填某数字。",
          "每格 one-hot、每行每数字 one-hot、每列每数字 one-hot、每宫每数字 one-hot，这四类约束组成完整数独模型。",
          "decode_sudoku 从每个格子的候选 bit 中选出得分最高的数字，显示最终棋盘。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "N = 4\nvalues = range(1, N + 1)\ngivens = {(0, 1): 2, (1, 0): 3, (2, 3): 3, (3, 1): 3}\n\ndef idx(r, c, v): return (r * N + c) * N + (v - 1)\nqubo = {}\nfor r in range(N):\n    for c in range(N):\n        one_hot_penalty(qubo, [idx(r, c, v) for v in values], penalty=9.0)\nfor r in range(N):\n    for v in values:\n        one_hot_penalty(qubo, [idx(r, c, v) for c in range(N)], penalty=7.0)\nfor c in range(N):\n    for v in values:\n        one_hot_penalty(qubo, [idx(r, c, v) for r in range(N)], penalty=7.0)\nfor br in range(0, N, 2):\n    for bc in range(0, N, 2):\n        for v in values:\n            one_hot_penalty(qubo, [idx(r, c, v) for r in range(br, br+2) for c in range(bc, bc+2)], penalty=7.0)\nfor (r, c), v in givens.items():\n    add_qubo_term(qubo, idx(r, c, v), idx(r, c, v), -18.0)\nn_vars = N * N * N\n\ndef decode_sudoku(bits):\n    grid = []\n    for r in range(N):\n        row = []\n        for c in range(N):\n            chosen = max(values, key=lambda v: bits[idx(r, c, v)])\n            row.append(chosen)\n        grid.append(row)\n    return grid\n\nvalid_grid = [\n    [1, 2, 3, 4],\n    [3, 4, 1, 2],\n    [2, 1, 4, 3],\n    [4, 3, 2, 1],\n]\nstart_bits = [0] * n_vars\nfor r in range(N):\n    for c in range(N):\n        start_bits[idx(r, c, valid_grid[r][c])] = 1\n"
      },
      {
        "type": "markdown",
        "title": "5. 代码导读：调用 BOQA solver",
        "body": [
          "云端调用使用 algorithm=\"kernel_qa\"。如果 key 不是 kernel_qa，服务端会拒绝，因为算法权限不匹配。",
          "返回 bit vector 后，decode_sudoku 会为每个格子选出值最大的数字，得到最终棋盘。",
          "本案例固定需要 kernel_qa（Kernel-QA）授权。若 key 类型不匹配，错误通常会在云端鉴权处出现；若没有看到 调用 BOQA solve，说明还没有真正发出请求。",
          "算法和 key 为什么这样选",
          "本案例需要 kernel_qa key。它是小规模约束满足 QUBO，适合用 Kernel-QA 演示。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "cloud_result = run_qubo_cloud(\n    problem=\"sudoku_qubo\",\n    qubo=qubo,\n    n_vars=n_vars,\n    algorithm=\"kernel_qa\",\n    params={'case_id': 'combo_sudoku', 'encoding': 'cell-value one-hot constraints', 'decoder': '4x4 Sudoku grid', 'objective': 'satisfy Sudoku constraints and givens', 'iterations': 80},\n)\nplot_trace(cloud_result, \"BOQA API / sudoku_qubo\")\n"
      },
      {
        "type": "markdown",
        "title": "6. 结果怎么读",
        "body": [
          "正确解应该满足每行、每列、每宫都没有重复。",
          "低能量通常意味着冲突少，但教学版解码还要检查棋盘是否完全满足规则。",
          "曲线中的 eval 是每次候选解，best 是历史最好值。best 下降表示优化过程找到了更好的候选；如果只出现一个点，要查看 raw summary 是否只有单次结果或后端没有返回完整 history。"
        ],
        "bullets": []
      }
    ],
    "imageSet": {
      "width": 1672,
      "height": 941,
      "variants": [
        {
          "width": 480,
          "height": 270,
          "url": "/assets/qubo-sudoku-480.92696b740b9f.webp",
          "bytes": 20936
        },
        {
          "width": 960,
          "height": 540,
          "url": "/assets/qubo-sudoku-960.2a6506c32373.webp",
          "bytes": 55056
        },
        {
          "width": 1672,
          "height": 941,
          "url": "/assets/qubo-sudoku-1672.58317aebd732.webp",
          "bytes": 111948
        }
      ]
    }
  },
  {
    "id": "qubo-carpool",
    "notebook": "notebooks/qubo_carpool.ipynb",
    "image": "/assets/qubo-carpool.png",
    "category": "演示应用程序",
    "difficulty": 5,
    "title": "拼车",
    "summary": "把乘客和车辆的匹配关系转成分配问题，降低总行驶距离和车辆数量。",
    "description": "拼车案例：乘客上车点、车辆容量和绕路成本共同决定匹配质量，模型把乘客-车辆选择写成二进制变量。",
    "requiredAlgorithm": "kernel_qa",
    "requiredAlgorithmLabel": "Kernel-QA",
    "setupCode": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：kernel_qa（Kernel-QA）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"kernel_qa\", algorithm_label=\"Kernel-QA\", key=QUBO_TEST_KEY)\n",
    "runCode": "cloud_result = run_qubo_cloud(\n    problem=\"carpool_qubo\",\n    qubo=qubo,\n    n_vars=n_vars,\n    algorithm=\"kernel_qa\",\n    params={'case_id': 'combo_carpool', 'encoding': 'passenger-car one-hot + capacity penalty', 'decoder': 'car passenger groups', 'objective': 'minimize detour and capacity violation', 'iterations': 80},\n)\nplot_trace(cloud_result, \"BOQA API / carpool_qubo\")\n",
    "lessonBlocks": [
      {
        "type": "code",
        "code": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：kernel_qa（Kernel-QA）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"kernel_qa\", algorithm_label=\"Kernel-QA\", key=QUBO_TEST_KEY)\n",
        "caption": "key setup"
      },
      {
        "type": "markdown",
        "title": "拼车",
        "body": [
          "演示应用程序",
          "拼车匹配，把乘客和车辆的选择约束写清楚",
          "这个 notebook 按“背景知识 -> 人工解法 -> 数学模型 -> 代码导读 -> 云端调用 -> 结果解释”的顺序展开，目标是给基础用户学习，而不只是演示一段代码。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "1. 背景、来历和相关学科",
        "body": [
          "拼车问题要把乘客分配到车辆，同时考虑距离、容量、绕路和公平性。",
          "它和二分匹配、车辆调度和路径规划有关。简单版只决定谁坐哪辆车，复杂版还要决定接送顺序。",
          "学习重点是分配变量和容量约束如何进入 QUBO。",
          "拼车调度来自交通运输、共享出行和平台匹配。平台需要在用户等待时间、司机绕路、车辆利用率和服务公平之间折中。",
          "QUBO 可以表达小规模匹配，适合作为从业务规则到数学模型的入门案例。",
          "相关知识点",
          "基础理论：拼车匹配和容量约束",
          "拼车问题结合了匹配、路径和容量。乘客希望少绕路、少等待，司机或车辆有座位数、起终点和时间窗。",
          "如果只看最近距离，可能会造成某辆车超载或路线不合理；如果只看容量，又可能牺牲过多行驶距离。",
          "QUBO 可以把“乘客是否分给某辆车”作为 bit，用距离作为成本，用容量和唯一分配作为约束。"
        ],
        "bullets": [
          "匹配理论：乘客和车辆之间的可行匹配。",
          "交通工程：距离、时间和绕路成本。",
          "组合优化：容量、偏好和冲突同时出现。",
          "平台调度：用户体验和司机收益需要平衡。"
        ]
      },
      {
        "type": "markdown",
        "title": "拼车问题的学科背景",
        "body": [
          "拼车分配结合了二分匹配、路径优化和公平性约束。系统要决定哪些乘客坐哪辆车，同时考虑绕路、容量、等待时间和司机收益。",
          "人工调度会先看距离是否接近、方向是否一致、车辆座位是否够，再平衡每辆车的负载。QUBO 把这些判断转成选择变量和惩罚项。"
        ],
        "bullets": [
          "匹配：乘客和车辆之间的选择。",
          "容量：车辆座位数限制。",
          "成本：绕路距离、等待时间和服务质量。"
        ]
      },
      {
        "type": "markdown",
        "title": "为什么拼车比普通派单更难",
        "body": [
          "出租车匹配通常是一车一单，拼车则是一车多单，还要考虑乘客之间是否顺路。一个乘客的加入会改变整辆车路线，所以变量之间的二阶关系更重要。",
          "教学案例会把拼车简化成小规模组合选择，让用户先看懂容量和冲突，再扩展到真实路线。"
        ],
        "bullets": [
          "同车乘客之间会产生相容性或冲突。",
          "超过容量必须强惩罚。",
          "解码结果应该是“车辆 -> 乘客列表”。"
        ]
      },
      {
        "type": "markdown",
        "title": "2. 如果不用代码，人会怎么解",
        "body": [
          "人工分配时会先看车辆容量和乘客位置，把距离近的人放在一起。",
          "如果只看最近车辆，可能造成某辆车超载或另一些乘客等待过久。",
          "优化模型把每个乘客-车辆选择统一比较，并惩罚容量超限。",
          "这一步很重要：人工解法不是被丢掉，而是帮助我们发现哪些规则应该变成变量、约束或惩罚项。优化模型只是把这些规则变成可重复执行的计算流程。",
          "手工推理：先看能不能坐，再看划不划算",
          "人工安排拼车时，第一步是可行性：乘客人数不能超过车辆座位，路线方向不能完全相反。",
          "第二步是成本：多绕路越多，惩罚越大；同一片区的乘客一起分配通常更合理。",
          "优化器在这些候选匹配中找低成本方案，避免人工逐个组合比较。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "3. 从业务问题到数学模型",
        "body": [
          "变量 x[passenger, car] 表示乘客是否分配给某辆车。",
          "每个乘客必须选择一辆车。",
          "车辆容量、绕路距离和偏好都会进入目标函数。",
          "本案例使用 kernel_qa key。",
          "读模型时抓住四件事"
        ],
        "bullets": [
          "变量：优化器能改变什么。",
          "目标函数：什么方案更好。",
          "硬约束：什么方案绝对不允许。",
          "解码器：如何把 bit vector 或参数向量翻译回业务语言。"
        ]
      },
      {
        "type": "markdown",
        "title": "4. 代码导读：准备数据、编码器和解码器",
        "body": [
          "passengers 和 cars 是分配对象。",
          "cost 表示某乘客分给某车的距离或不便成本。",
          "容量约束通过惩罚项防止过多乘客进入同一车辆。",
          "阅读下面代码时，可以先忽略 Python 细节，按注释找出“数据在哪里、变量怎么编号、惩罚项在哪里加入、最后怎么解码”。",
          "代码导读：乘客-车辆变量和解码",
          "每个乘客-车辆组合对应一个 bit。",
          "唯一分配约束保证每位乘客最多被分给一辆车；容量约束惩罚超出座位数。",
          "解码器输出车辆到乘客的分配表，方便检查是否超载和是否绕路。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "passengers = ['P1','P2','P3','P4','P5']\ncars = ['car-A','car-B']\ncapacity = {'car-A': 3, 'car-B': 3}\ndetour = {\n    ('P1','car-A'): 4, ('P1','car-B'): 7,\n    ('P2','car-A'): 5, ('P2','car-B'): 3,\n    ('P3','car-A'): 6, ('P3','car-B'): 4,\n    ('P4','car-A'): 3, ('P4','car-B'): 6,\n    ('P5','car-A'): 7, ('P5','car-B'): 4,\n}\n\ndef idx(p, c): return p * len(cars) + c\nqubo = {}\nfor p in range(len(passengers)):\n    one_hot_penalty(qubo, [idx(p, c) for c in range(len(cars))], penalty=10.0)\n    for c, car in enumerate(cars):\n        add_qubo_term(qubo, idx(p, c), idx(p, c), detour[(passengers[p], car)])\nfor c, car in enumerate(cars):\n    ids = [idx(p, c) for p in range(len(passengers))]\n    cap = capacity[car]\n    for i in ids:\n        add_qubo_term(qubo, i, i, 3.0 * (1 - 2 * cap))\n    for a, i in enumerate(ids):\n        for j in ids[a+1:]:\n            add_qubo_term(qubo, i, j, 6.0)\nn_vars = len(passengers) * len(cars)\n\ndef decode_carpool(bits):\n    result = {car: [] for car in cars}\n    for p, passenger in enumerate(passengers):\n        car_index = max(range(len(cars)), key=lambda c: bits[idx(p, c)])\n        result[cars[car_index]].append(passenger)\n    return result\n\nstart_plan = {'P1':'car-A', 'P2':'car-B', 'P3':'car-B', 'P4':'car-A', 'P5':'car-A'}\nstart_bits = [0] * n_vars\nfor p, passenger in enumerate(passengers):\n    start_bits[idx(p, cars.index(start_plan[passenger]))] = 1\n"
      },
      {
        "type": "markdown",
        "title": "5. 代码导读：调用 BOQA solver",
        "body": [
          "云端调用使用 algorithm=\"kernel_qa\"。",
          "返回 bit vector 后，解码器把选择结果转成乘客到车辆的表。",
          "如果要做真实拼车，还需要接入地图距离和接送顺序优化。",
          "本案例固定需要 kernel_qa（Kernel-QA）授权。若 key 类型不匹配，错误通常会在云端鉴权处出现；若没有看到 调用 BOQA solve，说明还没有真正发出请求。",
          "算法和 key 为什么这样选",
          "本案例需要 kernel_qa key，因为它是离散匹配 QUBO。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "cloud_result = run_qubo_cloud(\n    problem=\"carpool_qubo\",\n    qubo=qubo,\n    n_vars=n_vars,\n    algorithm=\"kernel_qa\",\n    params={'case_id': 'combo_carpool', 'encoding': 'passenger-car one-hot + capacity penalty', 'decoder': 'car passenger groups', 'objective': 'minimize detour and capacity violation', 'iterations': 80},\n)\nplot_trace(cloud_result, \"BOQA API / carpool_qubo\")\n"
      },
      {
        "type": "markdown",
        "title": "6. 结果怎么读",
        "body": [
          "结果要看是否所有乘客都分配成功、车辆是否超载、总绕路是否可接受。",
          "低能量表示模型内的成本更低，但真实用户体验还包括等待时间和取消率。",
          "曲线中的 eval 是每次候选解，best 是历史最好值。best 下降表示优化过程找到了更好的候选；如果只出现一个点，要查看 raw summary 是否只有单次结果或后端没有返回完整 history。"
        ],
        "bullets": []
      }
    ],
    "imageSet": {
      "width": 1672,
      "height": 941,
      "variants": [
        {
          "width": 480,
          "height": 270,
          "url": "/assets/qubo-carpool-480.689adbea593a.webp",
          "bytes": 20508
        },
        {
          "width": 960,
          "height": 540,
          "url": "/assets/qubo-carpool-960.d9bdb9e307d7.webp",
          "bytes": 54604
        },
        {
          "width": 1672,
          "height": 941,
          "url": "/assets/qubo-carpool-1672.987d28494a90.webp",
          "bytes": 114146
        }
      ]
    }
  },
  {
    "id": "qubo-tsp",
    "notebook": "notebooks/qubo_tsp.ipynb",
    "image": "/assets/qubo-tsp.png",
    "category": "演示应用程序",
    "difficulty": 4,
    "title": "旅行商问题",
    "summary": "经典 TSP：用位置-城市 one-hot 编码，在访问所有城市一次的前提下缩短路线。",
    "description": "路线规划案例：旅行商问题要求从所有城市中找一条最短闭环路线，是理解 QUBO 路径编码的经典例子。",
    "requiredAlgorithm": "kernel_qa",
    "requiredAlgorithmLabel": "Kernel-QA",
    "setupCode": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：kernel_qa（Kernel-QA）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"kernel_qa\", algorithm_label=\"Kernel-QA\", key=QUBO_TEST_KEY)\n",
    "runCode": "cloud_result = run_qubo_cloud(\n    problem=\"tsp_qubo\",\n    qubo=qubo,\n    n_vars=n_vars,\n    algorithm=\"kernel_qa\",\n    params={'case_id': 'combo_tsp', 'encoding': 'city-position one-hot', 'decoder': 'ordered tour', 'objective': 'minimize cyclic route distance', 'iterations': 80},\n)\nplot_trace(cloud_result, \"BOQA API / tsp_qubo\")\n",
    "lessonBlocks": [
      {
        "type": "code",
        "code": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：kernel_qa（Kernel-QA）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"kernel_qa\", algorithm_label=\"Kernel-QA\", key=QUBO_TEST_KEY)\n",
        "caption": "key setup"
      },
      {
        "type": "markdown",
        "title": "旅行商问题",
        "body": [
          "演示应用程序",
          "旅行商问题，理解路径优化的经典模型",
          "这个 notebook 按“背景知识 -> 人工解法 -> 数学模型 -> 代码导读 -> 云端调用 -> 结果解释”的顺序展开，目标是给基础用户学习，而不只是演示一段代码。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "1. 背景、来历和相关学科",
        "body": [
          "TSP 要求从多个城市中找到一条最短闭环路线，每个城市访问一次并回到起点。",
          "它是组合优化中最著名的问题之一，常用来教学“规模稍大就很难穷举”的现象。",
          "学习重点是路径顺序变量、城市唯一约束和距离成本如何组合。",
          "旅行商问题来自路线规划和图论。n 个城市的可能路线数量增长非常快，所以不能靠人工逐条试。",
          "TSP 是许多物流、芯片布线、机器人路径和生产排程问题的基础原型。",
          "相关知识点",
          "基础理论：Hamilton 回路和排列编码",
          "旅行商问题要求从起点出发访问每个城市一次并回到起点，目标是总距离最短。它是组合优化中最经典的问题之一。",
          "TSP 的难点在于路线数量随城市数阶乘增长。即使城市不多，所有排列也会很快变得无法穷举。",
          "QUBO 建模常用“城市-位置”变量：城市 i 是否出现在路线的第 t 个位置。每个城市只能出现一次，每个位置只能放一个城市。"
        ],
        "bullets": [
          "图论：城市是节点，距离是边权。",
          "组合优化：排列数量随城市数快速增长。",
          "运筹学：路线、成本和约束建模。",
          "启发式算法：近邻、2-opt、模拟退火等常用于大规模 TSP。"
        ]
      },
      {
        "type": "markdown",
        "title": "旅行商问题为什么经典",
        "body": [
          "旅行商问题（TSP）问的是：给定若干城市和距离，如何从起点出发访问每个城市一次并回到起点，使总距离最短。它是组合优化和 NP-hard 问题的代表。",
          "TSP 重要不是因为旅行商本身，而是因为它的结构出现在电路布线、物流、机器人巡检、钻孔路径和基因测序等领域。"
        ],
        "bullets": [
          "每个城市必须访问一次。",
          "每个位置只能放一个城市。",
          "目标是总路径距离最短。"
        ]
      },
      {
        "type": "markdown",
        "title": "TSP 的 QUBO 编码",
        "body": [
          "常见编码是 x[城市, 位置]：城市 i 是否出现在路径第 t 个位置。行 one-hot 保证每个城市只出现一次，列 one-hot 保证每个位置只有一个城市。",
          "距离成本来自相邻位置的城市对，所以自然形成二次项。解码时要把 bit vector 还原成城市访问顺序。"
        ],
        "bullets": [
          "约束项负责“合法路线”。",
          "距离项负责“路线短”。",
          "惩罚系数太小会得到非法路线，太大会掩盖距离目标。"
        ]
      },
      {
        "type": "markdown",
        "title": "2. 如果不用代码，人会怎么解",
        "body": [
          "人会先画地图，尝试连接近的城市，避免路线交叉。",
          "近邻策略简单但可能陷入局部最优。",
          "优化模型把所有位置和城市选择一次性编码，让求解器搜索更低成本路径。",
          "这一步很重要：人工解法不是被丢掉，而是帮助我们发现哪些规则应该变成变量、约束或惩罚项。优化模型只是把这些规则变成可重复执行的计算流程。",
          "手工推理：交换两个城市看看路线变短吗",
          "人工做小规模 TSP 时，会先画一条路线，然后检查有没有交叉边。交叉边通常可以通过交换访问顺序缩短。",
          "再看是否有远距离跳跃，如果有，就尝试把相近城市放在相邻位置。",
          "QUBO 把距离放进相邻位置的二次项，把“排列合法性”放进 one-hot 约束。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "3. 从业务问题到数学模型",
        "body": [
          "变量 x[city, position] 表示某城市是否放在路径的某个位置。",
          "每个城市必须出现一次，每个位置也必须有一个城市。",
          "相邻位置城市之间的距离形成目标函数。",
          "本案例使用 kernel_qa key。",
          "读模型时抓住四件事"
        ],
        "bullets": [
          "变量：优化器能改变什么。",
          "目标函数：什么方案更好。",
          "硬约束：什么方案绝对不允许。",
          "解码器：如何把 bit vector 或参数向量翻译回业务语言。"
        ]
      },
      {
        "type": "markdown",
        "title": "4. 代码导读：准备数据、编码器和解码器",
        "body": [
          "cities 是城市列表，distance 是两两距离。",
          "one-hot 约束保证城市和位置一一对应。",
          "二次项把相邻位置的距离加入能量。",
          "阅读下面代码时，可以先忽略 Python 细节，按注释找出“数据在哪里、变量怎么编号、惩罚项在哪里加入、最后怎么解码”。",
          "代码导读：城市、位置和距离矩阵",
          "distance[i][j] 是城市 i 到城市 j 的距离。",
          "x[city, position] 表示城市是否在某个访问位置上。",
          "解码器把每个位置选中的城市串起来，得到路线顺序。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "cities = ['A', 'B', 'C', 'D', 'E']\ndist = [\n    [0, 4, 2, 7, 3],\n    [4, 0, 5, 6, 4],\n    [2, 5, 0, 3, 6],\n    [7, 6, 3, 0, 5],\n    [3, 4, 6, 5, 0],\n]\nN = len(cities)\ndef idx(city, pos): return city * N + pos\nqubo = {}\nfor city in range(N):\n    one_hot_penalty(qubo, [idx(city, pos) for pos in range(N)], penalty=12.0)\nfor pos in range(N):\n    one_hot_penalty(qubo, [idx(city, pos) for city in range(N)], penalty=12.0)\nfor pos in range(N):\n    nxt = (pos + 1) % N\n    for i in range(N):\n        for j in range(N):\n            if i != j:\n                add_qubo_term(qubo, idx(i, pos), idx(j, nxt), dist[i][j])\nn_vars = N * N\n\ndef decode_tsp(bits):\n    route = []\n    for pos in range(N):\n        city = max(range(N), key=lambda i: bits[idx(i, pos)])\n        route.append(cities[city])\n    return {'route': route + [route[0]]}\n\nstart_route = ['A', 'C', 'D', 'B', 'E']\nstart_bits = [0] * n_vars\nfor pos, city_name in enumerate(start_route):\n    start_bits[idx(cities.index(city_name), pos)] = 1\n"
      },
      {
        "type": "markdown",
        "title": "5. 代码导读：调用 BOQA solver",
        "body": [
          "云端调用使用 algorithm=\"kernel_qa\"。",
          "解码器要把 bit vector 转成城市访问顺序。",
          "如果结果不成合法路径，需要检查惩罚系数是否足够大。",
          "本案例固定需要 kernel_qa（Kernel-QA）授权。若 key 类型不匹配，错误通常会在云端鉴权处出现；若没有看到 调用 BOQA solve，说明还没有真正发出请求。",
          "算法和 key 为什么这样选",
          "本案例需要 kernel_qa key，因为 TSP 是典型离散 QUBO。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "cloud_result = run_qubo_cloud(\n    problem=\"tsp_qubo\",\n    qubo=qubo,\n    n_vars=n_vars,\n    algorithm=\"kernel_qa\",\n    params={'case_id': 'combo_tsp', 'encoding': 'city-position one-hot', 'decoder': 'ordered tour', 'objective': 'minimize cyclic route distance', 'iterations': 80},\n)\nplot_trace(cloud_result, \"BOQA API / tsp_qubo\")\n"
      },
      {
        "type": "markdown",
        "title": "6. 结果怎么读",
        "body": [
          "结果首先要是合法闭环，其次看总距离。",
          "教学版规模很小，正式路线规划还会加入道路、时间窗和车辆约束。",
          "曲线中的 eval 是每次候选解，best 是历史最好值。best 下降表示优化过程找到了更好的候选；如果只出现一个点，要查看 raw summary 是否只有单次结果或后端没有返回完整 history。"
        ],
        "bullets": []
      }
    ],
    "imageSet": {
      "width": 1672,
      "height": 941,
      "variants": [
        {
          "width": 480,
          "height": 270,
          "url": "/assets/qubo-tsp-480.c24bf7086814.webp",
          "bytes": 20952
        },
        {
          "width": 960,
          "height": 540,
          "url": "/assets/qubo-tsp-960.280bc95ea857.webp",
          "bytes": 60740
        },
        {
          "width": 1672,
          "height": 941,
          "url": "/assets/qubo-tsp-1672.37c752a8437d.webp",
          "bytes": 128642
        }
      ]
    }
  },
  {
    "id": "qubo-graph-coloring",
    "notebook": "notebooks/qubo_graph_coloring.ipynb",
    "image": "/assets/qubo-graph-coloring.png",
    "category": "演示应用程序",
    "difficulty": 3,
    "title": "图着色问题",
    "summary": "给图的节点分配颜色，使相邻节点不同色，学习冲突图和 one-hot 编码。",
    "description": "图论案例：节点选颜色是变量，相邻节点同色会被惩罚；它和排课、频谱分配、地图着色都有关系。",
    "requiredAlgorithm": "kernel_qa",
    "requiredAlgorithmLabel": "Kernel-QA",
    "setupCode": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：kernel_qa（Kernel-QA）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"kernel_qa\", algorithm_label=\"Kernel-QA\", key=QUBO_TEST_KEY)\n",
    "runCode": "cloud_result = run_qubo_cloud(\n    problem=\"graph_coloring_qubo\",\n    qubo=qubo,\n    n_vars=n_vars,\n    algorithm=\"kernel_qa\",\n    params={'case_id': 'combo_graph_coloring', 'encoding': 'node-color one-hot constraints', 'decoder': 'color per graph node', 'objective': 'avoid adjacent nodes sharing a color', 'iterations': 80},\n)\nplot_trace(cloud_result, \"BOQA API / graph_coloring_qubo\")\n",
    "lessonBlocks": [
      {
        "type": "code",
        "code": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：kernel_qa（Kernel-QA）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"kernel_qa\", algorithm_label=\"Kernel-QA\", key=QUBO_TEST_KEY)\n",
        "caption": "key setup"
      },
      {
        "type": "markdown",
        "title": "图着色问题",
        "body": [
          "演示应用程序",
          "图着色问题，从地图染色到冲突分配",
          "这个 notebook 按“背景知识 -> 人工解法 -> 数学模型 -> 代码导读 -> 云端调用 -> 结果解释”的顺序展开，目标是给基础用户学习，而不只是演示一段代码。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "1. 背景、来历和相关学科",
        "body": [
          "图着色要求给图上的每个节点分配颜色，使相邻节点颜色不同。",
          "它可以表示地图着色、考试排期、频率分配和寄存器分配等实际问题。",
          "学习重点是“冲突关系”如何变成二次惩罚项。",
          "图着色最著名的背景是四色问题：任何平面地图最多用四种颜色即可让相邻区域不同色。",
          "在工程中，颜色不一定是真的颜色，它可以代表时间段、频道、资源或状态。相邻边表示两个对象不能共享同一资源。",
          "相关知识点",
          "基础理论：图着色、地图染色和冲突分配",
          "图着色要求给图的每个顶点分配颜色，使得相邻顶点颜色不同。它可以表示地图染色、考试排程、频率分配和寄存器分配。",
          "四色定理说明平面地图最多四种颜色足够，但一般图着色仍是计算上困难的问题。",
          "QUBO 建模时，变量 x[node,color] 表示节点选择某颜色；每个节点必须选一个颜色，相邻节点同色会被惩罚。"
        ],
        "bullets": [
          "图论：节点、边、邻接和颜色数。",
          "离散数学：冲突关系和可行染色。",
          "编译原理：寄存器分配可转成图着色。",
          "调度问题：冲突任务不能放同一时间段。"
        ]
      },
      {
        "type": "markdown",
        "title": "图着色的理论来源",
        "body": [
          "图着色来自图论，最直观的例子是地图着色：相邻区域不能同色。四色定理说明平面地图最多四种颜色足够，但一般图的最少着色数仍是困难问题。",
          "图着色不仅用于地图，还用于考试排期、频率分配、寄存器分配和任务冲突管理。只要两个对象不能同时共享某种资源，就可以画成一条边。"
        ],
        "bullets": [
          "顶点：对象或任务。",
          "边：冲突关系。",
          "颜色：可分配的资源、时间段或类别。"
        ]
      },
      {
        "type": "markdown",
        "title": "图着色怎么变成能量最小化",
        "body": [
          "变量 x[node, color] 表示某个节点是否选择某种颜色。每个节点必须选一个颜色；如果一条边两端选了同色，就加惩罚。",
          "低能量解意味着冲突少或没有冲突。解码器输出每个节点颜色后，用户可以沿着边列表检查是否有相邻同色。"
        ],
        "bullets": [
          "节点 one-hot：保证每个节点有且只有一种颜色。",
          "边冲突：相邻节点同色会被惩罚。",
          "扩展：用最少颜色、加入颜色成本或资源容量。"
        ]
      },
      {
        "type": "markdown",
        "title": "2. 如果不用代码，人会怎么解",
        "body": [
          "人工着色会先找连接最多的节点，给它分配颜色，再处理相邻节点。",
          "如果颜色不足，后面可能出现冲突，需要回溯。",
          "QUBO 把每个节点颜色选择和每条冲突边同时编码，减少手工回溯。",
          "这一步很重要：人工解法不是被丢掉，而是帮助我们发现哪些规则应该变成变量、约束或惩罚项。优化模型只是把这些规则变成可重复执行的计算流程。",
          "手工推理：从冲突最多的节点开始",
          "人工染色通常先处理连接最多、最容易冲突的节点。",
          "当某个节点的邻居已经用了多种颜色，它的可选颜色会减少，这就是约束传播。",
          "QUBO 不规定先染哪个节点，而是把所有冲突一次性写入能量函数。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "3. 从业务问题到数学模型",
        "body": [
          "变量 x[node, color] 表示节点是否使用某颜色。",
          "每个节点必须恰好选择一个颜色。",
          "如果一条边两端选择同一颜色，会产生冲突惩罚。",
          "本案例使用 kernel_qa key。",
          "读模型时抓住四件事"
        ],
        "bullets": [
          "变量：优化器能改变什么。",
          "目标函数：什么方案更好。",
          "硬约束：什么方案绝对不允许。",
          "解码器：如何把 bit vector 或参数向量翻译回业务语言。"
        ]
      },
      {
        "type": "markdown",
        "title": "4. 代码导读：准备数据、编码器和解码器",
        "body": [
          "nodes 是图的顶点，edges 是冲突关系。",
          "colors 是可用颜色数量。",
          "编码器由节点 one-hot 约束和边冲突惩罚组成。",
          "阅读下面代码时，可以先忽略 Python 细节，按注释找出“数据在哪里、变量怎么编号、惩罚项在哪里加入、最后怎么解码”。",
          "代码导读：边列表就是冲突列表",
          "edges 表示哪些节点相邻。每条边会产生“同色惩罚”。",
          "节点颜色 one-hot 保证一个节点只选一种颜色。",
          "解码器输出每个节点的颜色，用户可以对照边列表检查是否有冲突。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "nodes = ['A','B','C','D','E','F']\ncolors = ['red','green','blue']\nedges = [('A','B'),('A','C'),('B','D'),('C','D'),('C','E'),('D','F'),('E','F')]\nnode_index = {node:i for i, node in enumerate(nodes)}\ndef idx(node, color): return node_index[node] * len(colors) + color\nqubo = {}\nfor node in nodes:\n    one_hot_penalty(qubo, [idx(node, c) for c in range(len(colors))], penalty=8.0)\nfor u, v in edges:\n    for c in range(len(colors)):\n        add_qubo_term(qubo, idx(u, c), idx(v, c), 9.0)\nn_vars = len(nodes) * len(colors)\n\ndef decode_coloring(bits):\n    return {node: colors[max(range(len(colors)), key=lambda c: bits[idx(node, c)])] for node in nodes}\n\nstart_colors = {'A':'red', 'B':'green', 'C':'blue', 'D':'red', 'E':'green', 'F':'blue'}\nstart_bits = [0] * n_vars\nfor node, color_name in start_colors.items():\n    start_bits[idx(node, colors.index(color_name))] = 1\n"
      },
      {
        "type": "markdown",
        "title": "5. 代码导读：调用 BOQA solver",
        "body": [
          "云端调用使用 algorithm=\"kernel_qa\"。",
          "返回向量会被解码为每个节点的颜色。",
          "如果使用 gray_admm_qubo 或 cma_es key，可能会因为授权不含 kernel_qa 而失败。",
          "本案例固定需要 kernel_qa（Kernel-QA）授权。若 key 类型不匹配，错误通常会在云端鉴权处出现；若没有看到 调用 BOQA solve，说明还没有真正发出请求。",
          "算法和 key 为什么这样选",
          "本案例需要 kernel_qa key，因为它是小规模约束满足 QUBO。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "cloud_result = run_qubo_cloud(\n    problem=\"graph_coloring_qubo\",\n    qubo=qubo,\n    n_vars=n_vars,\n    algorithm=\"kernel_qa\",\n    params={'case_id': 'combo_graph_coloring', 'encoding': 'node-color one-hot constraints', 'decoder': 'color per graph node', 'objective': 'avoid adjacent nodes sharing a color', 'iterations': 80},\n)\nplot_trace(cloud_result, \"BOQA API / graph_coloring_qubo\")\n"
      },
      {
        "type": "markdown",
        "title": "6. 结果怎么读",
        "body": [
          "合法结果应满足每条边两端颜色不同。",
          "如果出现冲突，可以增大冲突惩罚或增加颜色数量。",
          "曲线中的 eval 是每次候选解，best 是历史最好值。best 下降表示优化过程找到了更好的候选；如果只出现一个点，要查看 raw summary 是否只有单次结果或后端没有返回完整 history。"
        ],
        "bullets": []
      }
    ],
    "imageSet": {
      "width": 1672,
      "height": 941,
      "variants": [
        {
          "width": 480,
          "height": 270,
          "url": "/assets/qubo-graph-coloring-480.4b967e7c1d3e.webp",
          "bytes": 14446
        },
        {
          "width": 960,
          "height": 540,
          "url": "/assets/qubo-graph-coloring-960.f7f696dd5d70.webp",
          "bytes": 35116
        },
        {
          "width": 1672,
          "height": 941,
          "url": "/assets/qubo-graph-coloring-1672.ef87305b1f9c.webp",
          "bytes": 68444
        }
      ]
    }
  },
  {
    "id": "qubo-taxi-matching",
    "notebook": "notebooks/qubo_taxi_matching.ipynb",
    "image": "/assets/qubo-taxi-matching.png",
    "category": "教程（应用型）",
    "difficulty": 3,
    "title": "出租车匹配问题",
    "summary": "把乘客和出租车位置匹配成分配优化，降低调度距离和等待时间。",
    "description": "出行调度案例：每个乘客需要匹配一辆车，每辆车容量有限，距离成本和唯一匹配约束共同形成 QUBO。",
    "requiredAlgorithm": "kernel_qa",
    "requiredAlgorithmLabel": "Kernel-QA",
    "setupCode": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：kernel_qa（Kernel-QA）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"kernel_qa\", algorithm_label=\"Kernel-QA\", key=QUBO_TEST_KEY)\n",
    "runCode": "cloud_result = run_qubo_cloud(\n    problem=\"taxi_matching_qubo\",\n    qubo=qubo,\n    n_vars=n_vars,\n    algorithm=\"kernel_qa\",\n    params={'case_id': 'combo_taxi_matching', 'encoding': 'rider-taxi one-hot matching', 'decoder': 'taxi assignments', 'objective': 'minimize pickup and waiting cost', 'iterations': 80},\n)\nplot_trace(cloud_result, \"BOQA API / taxi_matching_qubo\")\n",
    "lessonBlocks": [
      {
        "type": "code",
        "code": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：kernel_qa（Kernel-QA）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"kernel_qa\", algorithm_label=\"Kernel-QA\", key=QUBO_TEST_KEY)\n",
        "caption": "key setup"
      },
      {
        "type": "markdown",
        "title": "出租车匹配问题",
        "body": [
          "教程（应用型）",
          "出租车匹配，把乘客请求分配给车辆",
          "这个 notebook 按“背景知识 -> 人工解法 -> 数学模型 -> 代码导读 -> 云端调用 -> 结果解释”的顺序展开，目标是给基础用户学习，而不只是演示一段代码。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "1. 背景、来历和相关学科",
        "body": [
          "出租车匹配问题要在多个乘客和车辆之间做分配，目标是降低等待时间、空驶距离和调度成本。",
          "它是平台实时调度中的基础问题，复杂版还会考虑未来需求预测和司机公平。",
          "学习重点是二分匹配如何转成 QUBO。",
          "出租车调度来自交通运输、在线平台和运筹学。基础模型类似 assignment problem，但真实场景是动态到达、实时更新。",
          "QUBO 教学版先处理一个静态小批量订单，帮助用户理解分配变量和冲突约束。",
          "相关知识点",
          "基础理论：派单、二分匹配和实时性",
          "出租车匹配问题是典型实时派单：乘客请求和车辆位置不断变化，系统要快速决定谁接谁。",
          "最简单模型是二分匹配：一边是车辆，一边是乘客，边权表示接驾距离、等待时间或服务收益。",
          "实际派单还会考虑公平、司机空驶、乘客等待、区域供需和取消风险。本案例用小模型展示核心匹配结构。"
        ],
        "bullets": [
          "匹配理论：乘客和车辆的一对一选择。",
          "交通地理：距离和预计到达时间。",
          "在线优化：订单不断到达，决策要滚动更新。",
          "平台经济：效率、公平和司机收益。"
        ]
      },
      {
        "type": "markdown",
        "title": "出租车匹配和二分图匹配",
        "body": [
          "出租车匹配可以看成司机集合和乘客请求集合之间的二分图匹配。每条边有一个成本，例如接驾距离、等待时间或预估收益。",
          "经典算法可以用匈牙利算法或最小费用流解决很多匹配问题。QUBO 教学案例的目的不是替代所有传统算法，而是展示如何把匹配约束写成统一的二进制优化形式。"
        ],
        "bullets": [
          "司机最多接一个请求。",
          "请求最多分给一个司机。",
          "成本越低越好，也可以加入取消率或服务等级。"
        ]
      },
      {
        "type": "markdown",
        "title": "从派单规则到 QUBO",
        "body": [
          "变量 x[车, 单] 表示某辆车是否接某个订单。若同一辆车接多个订单或同一订单被多辆车接，会产生惩罚。成本矩阵决定哪些匹配更便宜。",
          "真实系统还要考虑动态到单、司机位置更新和用户等待。教学版本用静态小例子，让用户先理解唯一性约束和解码表。"
        ],
        "bullets": [
          "一次项：单个派单成本。",
          "二次项：重复分配冲突。",
          "解码：输出司机到乘客请求的匹配。"
        ]
      },
      {
        "type": "markdown",
        "title": "2. 如果不用代码，人会怎么解",
        "body": [
          "人工派车会优先把最近车辆派给乘客。",
          "但如果多个乘客竞争同一辆近车，局部最近不一定让整体等待最小。",
          "QUBO 把所有乘客-车辆组合同时比较，并约束每辆车最多接一个订单。",
          "这一步很重要：人工解法不是被丢掉，而是帮助我们发现哪些规则应该变成变量、约束或惩罚项。优化模型只是把这些规则变成可重复执行的计算流程。",
          "手工推理：最近不一定最好",
          "人工派单可能先把最近车辆分给最近乘客，但如果这样会导致远处区域完全没车，就不是全局最优。",
          "更好的策略会同时看所有车辆和请求，把总等待时间或总空驶距离降到最低。",
          "QUBO 把每个车辆-乘客匹配作为 bit，再用约束防止一车接多人或一人被多人接。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "3. 从业务问题到数学模型",
        "body": [
          "变量 x[rider, taxi] 表示某乘客是否匹配某车。",
          "每个乘客需要一辆车，每辆车最多服务一个乘客。",
          "距离、等待时间和偏好进入一次项。",
          "本案例使用 kernel_qa key。",
          "读模型时抓住四件事"
        ],
        "bullets": [
          "变量：优化器能改变什么。",
          "目标函数：什么方案更好。",
          "硬约束：什么方案绝对不允许。",
          "解码器：如何把 bit vector 或参数向量翻译回业务语言。"
        ]
      },
      {
        "type": "markdown",
        "title": "4. 代码导读：准备数据、编码器和解码器",
        "body": [
          "riders 和 taxis 是两侧对象。",
          "cost 矩阵表示每种匹配的代价。",
          "one-hot 约束防止一个乘客被多辆车接，或一辆车接多个乘客。",
          "阅读下面代码时，可以先忽略 Python 细节，按注释找出“数据在哪里、变量怎么编号、惩罚项在哪里加入、最后怎么解码”。",
          "代码导读：匹配矩阵和唯一性约束",
          "cost[vehicle, request] 是派单成本。",
          "每辆车最多匹配一个请求，每个请求最多被一辆车服务，这两类约束都可以写成 one-hot 或 at-most-one 惩罚。",
          "解码器输出派单表，便于检查总成本和未服务请求。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "riders = ['R1','R2','R3']\ntaxis = ['T1','T2','T3']\ncost = [\n    [6, 3, 7],\n    [2, 5, 4],\n    [7, 4, 3],\n]\ndef idx(r, t): return r * len(taxis) + t\nqubo = {}\nfor r in range(len(riders)):\n    one_hot_penalty(qubo, [idx(r, t) for t in range(len(taxis))], penalty=9.0)\nfor t in range(len(taxis)):\n    one_hot_penalty(qubo, [idx(r, t) for r in range(len(riders))], penalty=9.0)\nfor r in range(len(riders)):\n    for t in range(len(taxis)):\n        add_qubo_term(qubo, idx(r, t), idx(r, t), cost[r][t])\nn_vars = len(riders) * len(taxis)\n\ndef decode_taxi(bits):\n    pairs = []\n    for r, rider in enumerate(riders):\n        t = max(range(len(taxis)), key=lambda taxi: bits[idx(r, taxi)])\n        pairs.append((rider, taxis[t]))\n    return {'assignments': pairs}\n\nstart_pairs = {'R1':'T2', 'R2':'T1', 'R3':'T3'}\nstart_bits = [0] * n_vars\nfor r, rider in enumerate(riders):\n    start_bits[idx(r, taxis.index(start_pairs[rider]))] = 1\n"
      },
      {
        "type": "markdown",
        "title": "5. 代码导读：调用 BOQA solver",
        "body": [
          "云端调用使用 algorithm=\"kernel_qa\"。",
          "解码器把 bit vector 变成乘客到车辆的匹配表。",
          "如果要处理实时订单，需要把 notebook 变成滚动调用服务。",
          "本案例固定需要 kernel_qa（Kernel-QA）授权。若 key 类型不匹配，错误通常会在云端鉴权处出现；若没有看到 调用 BOQA solve，说明还没有真正发出请求。",
          "算法和 key 为什么这样选",
          "本案例需要 kernel_qa key，因为它是离散匹配 QUBO。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "cloud_result = run_qubo_cloud(\n    problem=\"taxi_matching_qubo\",\n    qubo=qubo,\n    n_vars=n_vars,\n    algorithm=\"kernel_qa\",\n    params={'case_id': 'combo_taxi_matching', 'encoding': 'rider-taxi one-hot matching', 'decoder': 'taxi assignments', 'objective': 'minimize pickup and waiting cost', 'iterations': 80},\n)\nplot_trace(cloud_result, \"BOQA API / taxi_matching_qubo\")\n"
      },
      {
        "type": "markdown",
        "title": "6. 结果怎么读",
        "body": [
          "结果要看总等待和是否有重复派车。",
          "真实平台还要考虑取消率、司机接单概率和区域供需平衡。",
          "曲线中的 eval 是每次候选解，best 是历史最好值。best 下降表示优化过程找到了更好的候选；如果只出现一个点，要查看 raw summary 是否只有单次结果或后端没有返回完整 history。"
        ],
        "bullets": []
      }
    ],
    "imageSet": {
      "width": 1672,
      "height": 941,
      "variants": [
        {
          "width": 480,
          "height": 270,
          "url": "/assets/qubo-taxi-matching-480.dca8c9a08fe3.webp",
          "bytes": 18326
        },
        {
          "width": 960,
          "height": 540,
          "url": "/assets/qubo-taxi-matching-960.bcab84d1be8f.webp",
          "bytes": 50462
        },
        {
          "width": 1672,
          "height": 941,
          "url": "/assets/qubo-taxi-matching-1672.d3e73cdd660a.webp",
          "bytes": 104274
        }
      ]
    }
  },
  {
    "id": "qubo-meeting-room",
    "notebook": "notebooks/qubo_meeting_room.ipynb",
    "image": "/assets/qubo-meeting-room.png",
    "category": "教程（应用型）",
    "difficulty": 3,
    "title": "会议室分配问题",
    "summary": "把会议安排到房间，避免时间冲突，并兼顾容量、设备和房间偏好。",
    "description": "会议室分配案例：会议是时间区间，房间是有限资源；重叠会议不能同房，容量不足或设备不匹配会产生惩罚。",
    "requiredAlgorithm": "kernel_qa",
    "requiredAlgorithmLabel": "Kernel-QA",
    "setupCode": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：kernel_qa（Kernel-QA）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"kernel_qa\", algorithm_label=\"Kernel-QA\", key=QUBO_TEST_KEY)\n",
    "runCode": "cloud_result = run_qubo_cloud(\n    problem=\"meeting_room_qubo\",\n    qubo=qubo,\n    n_vars=n_vars,\n    algorithm=\"kernel_qa\",\n    params={'case_id': 'combo_meeting_room', 'encoding': 'meeting-room one-hot + overlap penalties', 'decoder': 'room schedule', 'objective': 'avoid overlaps and capacity violations', 'iterations': 80},\n)\nplot_trace(cloud_result, \"BOQA API / meeting_room_qubo\")\n",
    "lessonBlocks": [
      {
        "type": "code",
        "code": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：kernel_qa（Kernel-QA）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"kernel_qa\", algorithm_label=\"Kernel-QA\", key=QUBO_TEST_KEY)\n",
        "caption": "key setup"
      },
      {
        "type": "markdown",
        "title": "会议室分配问题",
        "body": [
          "教程（应用型）",
          "会议室分配，把时间冲突和容量写成约束",
          "这个 notebook 按“背景知识 -> 人工解法 -> 数学模型 -> 代码导读 -> 云端调用 -> 结果解释”的顺序展开，目标是给基础用户学习，而不只是演示一段代码。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "1. 背景、来历和相关学科",
        "body": [
          "会议室分配是办公自动化里很典型的资源调度问题：多个会议争用有限房间，每个会议有时间、人数和设备需求。",
          "它和区间图着色、排课、机器调度很接近。时间重叠的会议不能占用同一房间，容量不够的房间也不能使用。",
          "学习重点是把日常规则翻译成数学约束：每个会议选一个房间，冲突不能同房，容量不足要惩罚。",
          "会议室问题属于 resource allocation 和 scheduling。类似问题出现在学校排课、医院手术室安排、工厂机器排产和云资源调度中。",
          "如果把每个会议看成时间区间，重叠关系会形成区间图；给会议分房间就像给冲突图着色。",
          "相关知识点",
          "基础理论：区间调度、资源分配和图着色",
          "会议室分配问题看起来像日常办公问题，数学上属于区间调度和资源分配。每个会议有开始/结束时间、人数、设备需求和偏好。",
          "如果两个会议时间重叠，它们不能被分到同一个房间；如果房间容量不足，也不能分配。这个冲突关系可以看成图的边。",
          "和图着色类似，会议是节点，房间是颜色；不同之处在于房间有容量和设备属性，不是所有颜色都适合所有节点。"
        ],
        "bullets": [
          "区间图：时间重叠形成冲突边。",
          "排程理论：任务、资源、容量和时间窗。",
          "约束优化：硬冲突必须避免，偏好可以作为软约束。",
          "办公自动化：设备、楼层、部门和容量偏好。"
        ]
      },
      {
        "type": "markdown",
        "title": "会议室分配背后的区间图问题",
        "body": [
          "会议室分配看起来简单，核心是时间区间冲突：两个会议时间重叠就不能放在同一个房间。把会议看成区间，把冲突看成边，就得到一个区间图着色或资源分配问题。",
          "人工安排时会先按开始时间排序，把冲突会议分散到不同房间，再考虑容量、设备和偏好。QUBO 把这些规则转成选择变量和惩罚。"
        ],
        "bullets": [
          "会议-房间变量：会议是否放入某房间。",
          "时间冲突：重叠会议同房间会被惩罚。",
          "容量偏好：人数超过容量或设备不匹配会加成本。"
        ]
      },
      {
        "type": "markdown",
        "title": "为什么代码里要有冲突矩阵",
        "body": [
          "冲突矩阵记录任意两个会议是否重叠。它是会议室分配模型的骨架：没有冲突矩阵，模型就不知道哪些会议不能同房间。",
          "解码器最后输出“会议 -> 房间”的表。用户检查结果时，不需要看 bit vector，只要确认同一房间内没有时间重叠、容量合适即可。"
        ],
        "bullets": [
          "one-hot：每个会议只能选一个房间。",
          "冲突惩罚：重叠会议不能同房间。",
          "偏好成本：更合适的房间能量更低。"
        ]
      },
      {
        "type": "markdown",
        "title": "2. 如果不用代码，人会怎么解",
        "body": [
          "人工安排会议时，会先放人数最多或时间最长的会议，因为它们可选房间少。",
          "再把小会议填进剩余空档，同时检查同一房间有没有时间重叠。",
          "当会议很多时，人工贪心很容易造成局部堵塞。QUBO 同时比较所有会议-房间选择。",
          "这一步很重要：人工解法不是被丢掉，而是帮助我们发现哪些规则应该变成变量、约束或惩罚项。优化模型只是把这些规则变成可重复执行的计算流程。",
          "手工推理：先排冲突最多和人数最多的会议",
          "人工安排会议室时，会先处理时间跨度长、人数多或设备要求高的会议，因为它们可选房间少。",
          "小会议可选房间多，可以放到后面补空。若某两个会议重叠，它们共享房间的方案应该被强烈惩罚。",
          "QUBO 把“会议 m 是否放到房间 r”作为 bit，用冲突惩罚和容量惩罚表达规则。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "3. 从业务问题到数学模型",
        "body": [
          "变量 x[meeting, room] 表示某会议是否放到某房间。",
          "每个会议必须恰好选择一个房间。",
          "若两个会议时间重叠且选择同一房间，则产生大惩罚。",
          "容量不足或设备不匹配也会产生惩罚。本案例使用 kernel_qa key。",
          "读模型时抓住四件事"
        ],
        "bullets": [
          "变量：优化器能改变什么。",
          "目标函数：什么方案更好。",
          "硬约束：什么方案绝对不允许。",
          "解码器：如何把 bit vector 或参数向量翻译回业务语言。"
        ]
      },
      {
        "type": "markdown",
        "title": "4. 代码导读：准备数据、编码器和解码器",
        "body": [
          "meetings 包含开始时间、结束时间、人数和设备需求。",
          "rooms 包含容量和设备能力。",
          "编码器先加“每个会议选一个房间”的 one-hot 约束，再加时间冲突和容量惩罚。",
          "阅读下面代码时，可以先忽略 Python 细节，按注释找出“数据在哪里、变量怎么编号、惩罚项在哪里加入、最后怎么解码”。",
          "代码导读：会议-房间 one-hot 和冲突惩罚",
          "meetings 提供时间、人数和偏好；rooms 提供容量和设备。",
          "每个会议必须选一个房间，这是 one-hot 约束。时间重叠且同房间的两个会议会产生二次惩罚。",
          "解码器输出会议到房间的安排表，让用户能按时间顺序检查是否冲突。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "meetings = [\n    {'name':'M1','start':9,'end':11,'people':6},\n    {'name':'M2','start':10,'end':12,'people':4},\n    {'name':'M3','start':11,'end':13,'people':10},\n    {'name':'M4','start':13,'end':15,'people':5},\n]\nrooms = [{'name':'A','cap':6}, {'name':'B','cap':10}]\ndef overlap(a, b): return a['start'] < b['end'] and b['start'] < a['end']\ndef idx(m, r): return m * len(rooms) + r\nqubo = {}\nfor m in range(len(meetings)):\n    one_hot_penalty(qubo, [idx(m, r) for r in range(len(rooms))], penalty=10.0)\n    for r, room in enumerate(rooms):\n        overflow = max(0, meetings[m]['people'] - room['cap'])\n        add_qubo_term(qubo, idx(m, r), idx(m, r), 12.0 * overflow)\nfor a in range(len(meetings)):\n    for b in range(a + 1, len(meetings)):\n        if overlap(meetings[a], meetings[b]):\n            for r in range(len(rooms)):\n                add_qubo_term(qubo, idx(a, r), idx(b, r), 14.0)\nn_vars = len(meetings) * len(rooms)\n\ndef decode_meetings(bits):\n    plan = {room['name']: [] for room in rooms}\n    for m, meeting in enumerate(meetings):\n        r = max(range(len(rooms)), key=lambda room: bits[idx(m, room)])\n        plan[rooms[r]['name']].append(meeting['name'])\n    return plan\n\nstart_plan = {'M1':'B', 'M2':'A', 'M3':'B', 'M4':'A'}\nstart_bits = [0] * n_vars\nfor m, meeting in enumerate(meetings):\n    start_bits[idx(m, next(i for i, room in enumerate(rooms) if room['name'] == start_plan[meeting['name']]))] = 1\n"
      },
      {
        "type": "markdown",
        "title": "5. 代码导读：调用 BOQA solver",
        "body": [
          "云端调用使用 algorithm=\"kernel_qa\"。",
          "如果用非 kernel_qa key，服务端会提示算法权限不匹配。",
          "返回向量后，解码器生成会议室排表，业务用户可以直接检查冲突。",
          "本案例固定需要 kernel_qa（Kernel-QA）授权。若 key 类型不匹配，错误通常会在云端鉴权处出现；若没有看到 调用 BOQA solve，说明还没有真正发出请求。",
          "算法和 key 为什么这样选",
          "本案例需要 kernel_qa key，因为它是约束满足型 QUBO。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "cloud_result = run_qubo_cloud(\n    problem=\"meeting_room_qubo\",\n    qubo=qubo,\n    n_vars=n_vars,\n    algorithm=\"kernel_qa\",\n    params={'case_id': 'combo_meeting_room', 'encoding': 'meeting-room one-hot + overlap penalties', 'decoder': 'room schedule', 'objective': 'avoid overlaps and capacity violations', 'iterations': 80},\n)\nplot_trace(cloud_result, \"BOQA API / meeting_room_qubo\")\n"
      },
      {
        "type": "markdown",
        "title": "6. 结果怎么读",
        "body": [
          "结果先看有没有硬冲突：同一房间同一时间不能有两个会议。",
          "其次看容量、设备和偏好是否合理。低能量但业务不可接受的方案需要调整惩罚权重。",
          "曲线中的 eval 是每次候选解，best 是历史最好值。best 下降表示优化过程找到了更好的候选；如果只出现一个点，要查看 raw summary 是否只有单次结果或后端没有返回完整 history。"
        ],
        "bullets": []
      }
    ],
    "imageSet": {
      "width": 1672,
      "height": 941,
      "variants": [
        {
          "width": 480,
          "height": 270,
          "url": "/assets/qubo-meeting-room-480.bba62f11aed1.webp",
          "bytes": 21374
        },
        {
          "width": 960,
          "height": 540,
          "url": "/assets/qubo-meeting-room-960.62fbd5ed3152.webp",
          "bytes": 53400
        },
        {
          "width": 1672,
          "height": 941,
          "url": "/assets/qubo-meeting-room-1672.d40c8e2db5ad.webp",
          "bytes": 111004
        }
      ]
    }
  },
  {
    "id": "qubo-picross",
    "notebook": "notebooks/qubo_picross.ipynb",
    "image": "/assets/qubo-picross.png",
    "category": "演示应用程序",
    "difficulty": 3,
    "title": "Picross 图像填色谜题",
    "summary": "把行列线索转成 QUBO 约束，用二进制格子恢复一张小图案。",
    "description": "Picross/Nonogram 案例：每个像素是黑或白，行列线索约束图案结构，求解器寻找最符合线索的二进制图像。",
    "requiredAlgorithm": "kernel_qa",
    "requiredAlgorithmLabel": "Kernel-QA",
    "setupCode": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：kernel_qa（Kernel-QA）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"kernel_qa\", algorithm_label=\"Kernel-QA\", key=QUBO_TEST_KEY)\n",
    "runCode": "cloud_result = run_qubo_cloud(\n    problem=\"picross_qubo\",\n    qubo=qubo,\n    n_vars=n_vars,\n    algorithm=\"kernel_qa\",\n    params={'case_id': 'combo_picross', 'encoding': 'binary pixel grid + row/column count penalties', 'decoder': '5x5 bitmap', 'objective': 'match row and column clues', 'iterations': 80},\n)\nplot_trace(cloud_result, \"BOQA API / picross_qubo\")\n",
    "lessonBlocks": [
      {
        "type": "code",
        "code": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：kernel_qa（Kernel-QA）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"kernel_qa\", algorithm_label=\"Kernel-QA\", key=QUBO_TEST_KEY)\n",
        "caption": "key setup"
      },
      {
        "type": "markdown",
        "title": "Picross 图像填色谜题",
        "body": [
          "演示应用程序",
          "Picross 图像填色，把线索变成二进制图像约束",
          "这个 notebook 按“背景知识 -> 人工解法 -> 数学模型 -> 代码导读 -> 云端调用 -> 结果解释”的顺序展开，目标是给基础用户学习，而不只是演示一段代码。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "1. 背景、来历和相关学科",
        "body": [
          "Picross 又叫 Nonogram，是一种根据行列数字线索恢复黑白图案的逻辑谜题。每个格子只有黑或白两种状态，因此天然适合二进制变量。",
          "完整 Picross 线索通常描述连续黑格段；教学版先使用每行每列黑格数量，让初学者先理解 QUBO 惩罚如何工作。",
          "学习重点是图像、线索和约束满足之间的关系。",
          "Picross 流行于报纸和掌机游戏，它的数学结构接近二值图像重建和约束满足。",
          "从优化角度看，行列线索是对像素变量的约束；求解器要找到一张同时满足横向和纵向线索的图。",
          "相关知识点",
          "基础理论：Nonogram、二进制图像和线索约束",
          "Picross/Nonogram 是一种图像填色谜题。每行和每列给出黑格连续段线索，玩家要恢复隐藏图案。",
          "它和图像重建、约束满足、组合搜索都有关系。每个格子只有黑/白两种状态，很自然地对应二进制变量。",
          "完整 Nonogram 线索包含连续段长度；本教学版先用简化线索展示行列数量约束，让初学者先理解 bit、惩罚和解码。"
        ],
        "bullets": [
          "逻辑谜题：利用行列交叉逐步推理。",
          "图像处理：二值像素和图案重建。",
          "约束满足：每行每列都有数量或连续段限制。",
          "QUBO：违反线索会增加能量。"
        ]
      },
      {
        "type": "markdown",
        "title": "Picross/Nonogram 的规则和推理来源",
        "body": [
          "Picross 又叫 Nonogram，是用行列线索还原黑白图案的逻辑谜题。每一行和每一列的数字表示连续黑格段长度，例如 3 表示连续三个黑格，1 1 表示两个单独黑格段，中间至少隔一个白格。",
          "它和约束满足、图像重建、二进制矩阵推理都有关系。教学案例用小网格展示思想：每个格子是 0/1，行列线索变成约束。"
        ],
        "bullets": [
          "变量：每个像素是否填黑。",
          "行约束：每行黑格段匹配线索。",
          "列约束：每列黑格段匹配线索。"
        ]
      },
      {
        "type": "markdown",
        "title": "人工解 Picross 与模型解法",
        "body": [
          "人工解 Picross 会找“必黑”和“必白”位置：如果某行线索很长，所有可能摆放方式的交集就是必黑；如果某位置在所有合法摆法里都不可能黑，就是必白。",
          "QUBO 版本不逐条做人工逻辑，而是把不匹配线索的行列配置加惩罚。低能量解就是更符合行列线索的图像。"
        ],
        "bullets": [
          "解码结果应显示为图像矩阵。",
          "线索越复杂，约束越多。",
          "真实 Nonogram 需要更细的连续段编码；教学版先用小图理解流程。"
        ]
      },
      {
        "type": "markdown",
        "title": "2. 如果不用代码，人会怎么解",
        "body": [
          "玩家会先找强线索，例如一行全黑或全白，再用交叉关系推出其他格子。",
          "当线索较弱时，需要在多种可能中回溯。",
          "QUBO 把所有行列约束同时计算，让低能量解对应更符合线索的图案。",
          "这一步很重要：人工解法不是被丢掉，而是帮助我们发现哪些规则应该变成变量、约束或惩罚项。优化模型只是把这些规则变成可重复执行的计算流程。",
          "手工推理：行列线索互相校验",
          "人工解 Picross 时，会先找线索很强的行列。例如一行需要填满时，所有格子都能确定。",
          "再把已经确定的格子反馈到交叉列，逐步缩小其他格子的可能性。",
          "QUBO 把每个格子的 0/1 状态作为变量，用行列惩罚让求解器寻找最符合线索的图像。"
        ],
        "bullets": []
      },
      {
        "type": "markdown",
        "title": "3. 从业务问题到数学模型",
        "body": [
          "变量 x[row, col] 表示某像素是否填黑。",
          "行约束要求每行黑格数接近线索。",
          "列约束要求每列黑格数接近线索。",
          "本案例使用 kernel_qa key。",
          "读模型时抓住四件事"
        ],
        "bullets": [
          "变量：优化器能改变什么。",
          "目标函数：什么方案更好。",
          "硬约束：什么方案绝对不允许。",
          "解码器：如何把 bit vector 或参数向量翻译回业务语言。"
        ]
      },
      {
        "type": "markdown",
        "title": "4. 代码导读：准备数据、编码器和解码器",
        "body": [
          "rows 和 cols 是行列线索。",
          "idx(r,c) 把二维像素坐标映射成一维 bit 下标。",
          "解码器把 bit vector 还原成 5x5 黑白矩阵。",
          "阅读下面代码时，可以先忽略 Python 细节，按注释找出“数据在哪里、变量怎么编号、惩罚项在哪里加入、最后怎么解码”。",
          "代码导读：像素变量、行列惩罚和图像解码",
          "idx(row,col) 把二维像素映射到一维 bit。",
          "行列线索通过平方惩罚表达：填黑数量和目标数量越不一致，能量越高。",
          "decode_picross 把 bit vector 重新排成 5x5 矩阵，显示成图案。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "rows = [1, 3, 5, 3, 1]\ncols = [1, 3, 5, 3, 1]\nN = 5\ndef idx(r, c): return r * N + c\nqubo = {}\nfor r, clue in enumerate(rows):\n    cells = [idx(r, c) for c in range(N)]\n    for i in cells:\n        add_qubo_term(qubo, i, i, 4.0 * (1 - 2 * clue))\n    for a, i in enumerate(cells):\n        for j in cells[a+1:]:\n            add_qubo_term(qubo, i, j, 8.0)\nfor c, clue in enumerate(cols):\n    cells = [idx(r, c) for r in range(N)]\n    for i in cells:\n        add_qubo_term(qubo, i, i, 4.0 * (1 - 2 * clue))\n    for a, i in enumerate(cells):\n        for j in cells[a+1:]:\n            add_qubo_term(qubo, i, j, 8.0)\nn_vars = N * N\n\ndef decode_picross(bits):\n    return [''.join('■' if bits[idx(r, c)] else '□' for c in range(N)) for r in range(N)]\n\npattern = [\n    '00100',\n    '01110',\n    '11111',\n    '01110',\n    '00100',\n]\nstart_bits = [0] * n_vars\nfor r, line in enumerate(pattern):\n    for c, char in enumerate(line):\n        start_bits[idx(r, c)] = 1 if char == '1' else 0\n"
      },
      {
        "type": "markdown",
        "title": "5. 代码导读：调用 BOQA solver",
        "body": [
          "云端调用使用 algorithm=\"kernel_qa\"。",
          "返回向量后，decode_picross 会输出图案矩阵。",
          "完整 Nonogram 可进一步加入连续段约束，而不只是数量约束。",
          "本案例固定需要 kernel_qa（Kernel-QA）授权。若 key 类型不匹配，错误通常会在云端鉴权处出现；若没有看到 调用 BOQA solve，说明还没有真正发出请求。",
          "算法和 key 为什么这样选",
          "本案例需要 kernel_qa key，因为它是小规模二进制约束满足 QUBO。"
        ],
        "bullets": []
      },
      {
        "type": "code",
        "code": "cloud_result = run_qubo_cloud(\n    problem=\"picross_qubo\",\n    qubo=qubo,\n    n_vars=n_vars,\n    algorithm=\"kernel_qa\",\n    params={'case_id': 'combo_picross', 'encoding': 'binary pixel grid + row/column count penalties', 'decoder': '5x5 bitmap', 'objective': 'match row and column clues', 'iterations': 80},\n)\nplot_trace(cloud_result, \"BOQA API / picross_qubo\")\n"
      },
      {
        "type": "markdown",
        "title": "6. 结果怎么读",
        "body": [
          "结果要检查每行每列黑格数量是否匹配线索。",
          "如果多张图都满足数量线索，完整段线索可以进一步消除歧义。",
          "曲线中的 eval 是每次候选解，best 是历史最好值。best 下降表示优化过程找到了更好的候选；如果只出现一个点，要查看 raw summary 是否只有单次结果或后端没有返回完整 history。"
        ],
        "bullets": []
      }
    ],
    "imageSet": {
      "width": 1672,
      "height": 941,
      "variants": [
        {
          "width": 480,
          "height": 270,
          "url": "/assets/qubo-picross-480.b67b4472cb32.webp",
          "bytes": 21878
        },
        {
          "width": 960,
          "height": 540,
          "url": "/assets/qubo-picross-960.2875e1abf5cf.webp",
          "bytes": 51728
        },
        {
          "width": 1672,
          "height": 941,
          "url": "/assets/qubo-picross-1672.23ae99009571.webp",
          "bytes": 99864
        }
      ]
    }
  },
  {
    "id": "qubo-hems",
    "notebook": "/notebooks/qubo_hems.ipynb",
    "image": "/assets/thumb-dashboard.png",
    "category": "教程（应用型）",
    "difficulty": 4,
    "title": "家庭能源管理",
    "summary": "把电价、负荷、太阳能、电池和 EV 可用性转成时间序列 QUBO，在成本、峰值和储能约束之间寻找两天调度方案。",
    "description": "HEMS 的核心不是单独预测电价或发电量，而是在每个时间段决定该用电网、太阳能、电池放电还是给电池充电。这个案例用 QUBO 表达能源选择、切换成本和峰值惩罚。",
    "requiredAlgorithm": "kernel_qa",
    "requiredAlgorithmLabel": "Kernel-QA",
    "setupCode": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：kernel_qa（Kernel-QA）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"kernel_qa\", algorithm_label=\"Kernel-QA\", key=QUBO_TEST_KEY)",
    "runCode": "cloud_result = run_qubo_cloud(\n    problem=\"hems_energy_management_qubo\",\n    qubo=qubo,\n    n_vars=n_vars,\n    algorithm=\"kernel_qa\",\n    params={\n        \"case_id\": \"hems_two_day_energy_mix\",\n        \"time_slots\": len(hours),\n        \"variables\": n_vars,\n        \"encoding\": \"hour-mode one-hot + transition penalties\",\n        \"decoder\": \"hourly energy-source schedule\",\n        \"objective\": \"energy cost + peak shaving + storage preference\",\n        \"iterations\": 80,\n    },\n)\npprint(cloud_result[\"summary\"])\nplot_trace(cloud_result, \"BOQA API / HEMS energy scheduling\")\ncloud_bits = best_bits_from_result(cloud_result, n_vars)\nif cloud_bits:\n    show_decoded(\"云端调度方案\", decode_hems(cloud_bits))",
    "lessonBlocks": [
      {
        "heading": "为什么家庭能源管理适合优化",
        "body": [
          "家庭能源管理系统（HEMS）把家庭里的用电负荷、实时电价、太阳能发电、电池储能、电动汽车充电等信息放在同一张时间表里。单看某一小时并不难，难点在于当前选择会影响后面的状态：现在给电池充电，会增加这一小时成本，但可能减少晚高峰买电；现在用完电池，可能导致后面没有余量。",
          "这类问题天然带有约束：负荷必须被满足，电池容量不能越界，EV 在不在家会限制充电时段，太阳能只能在白天可用。优化的价值，就是在这些约束下找到更低成本、更低峰值、更稳定的调度方案。"
        ],
        "code": ""
      },
      {
        "heading": "时间槽、状态和能量流",
        "body": [
          "我们把一天切成 24 个时间槽，每个时间槽做一次能源来源选择。真实系统常用 15 分钟或 30 分钟粒度，教学版先用小时粒度，方便观察每个决策如何影响结果。",
          "负荷平衡可以写成：g_t + pv_t + d_t = L_t + c_t。其中 g_t 是电网购电，pv_t 是太阳能可用电量，d_t 是电池放电，L_t 是家庭负荷，c_t 是电池充电。电池状态可以写成 B_{t+1}=B_t+eta_c c_t-d_t/eta_d。这个状态方程提醒我们：优化不是只看某个时刻，而是看一整段时间的连锁影响。"
        ],
        "code": "hours = range(24)\nprice[t], demand[t], solar[t]\nmode_t in {grid, solar, battery_discharge, battery_charge}"
      },
      {
        "heading": "目标函数：成本、峰值和舒适性",
        "body": [
          "HEMS 的目标函数通常不是单一指标。最直接的是电费：sum_t p_t g_t。为了避免尖峰用电，还会加入峰值惩罚 lambda_peak P_peak。为了减少电池频繁充放电造成的损耗，可以加入 lambda_deg sum_t(c_t+d_t)。",
          "教学版把这些目标转成 QUBO 里的线性项和二次项。线性项描述某个时间点选某种模式的成本或收益；二次项描述两个选择同时出现时的冲突或切换代价。例如同一小时不能同时选择“电网供电”和“电池放电”，相邻小时频繁切换也会被轻微惩罚。"
        ],
        "code": "minimize  sum_t price[t] * grid_use[t] + peak_penalty + switch_penalty\nQUBO: E(x)=sum_i Q_ii x_i + sum_{i<j} Q_ij x_i x_j"
      },
      {
        "heading": "编码器和解码器",
        "body": [
          "编码器把“第 t 小时选择哪种能源模式”写成 one-hot 二进制变量。每个小时只能选一种模式，因此会加入 one-hot 惩罚项。如果求解器返回的 bit vector 在某小时选了两个模式，惩罚项会让这个解能量变高。",
          "解码器把求解器返回的 0/1 向量翻译回人能读懂的调度表：几点用电网，几点优先用太阳能，几点放电，几点充电。业务人员看的是调度表和成本趋势，算法人员看的是 QUBO 能量是否下降。"
        ],
        "code": "x[t, mode] = 1  表示第 t 小时采用 mode\nfor each t: sum_mode x[t, mode] = 1"
      },
      {
        "heading": "代码运行后看什么",
        "body": [
          "运行云端单元格后，返回结果里重点看 best_score 或 objective_value_minimized，它代表当前找到的最低能量。曲线里的 best 越往下，说明求解器在逐步找到更好的调度方案。",
          "如果要接入真实项目，可以把价格、天气、太阳能预测、EV 可用时间换成真实数据，把教学版的离散模式扩展成连续功率变量或混合整数模型。"
        ],
        "code": "cloud_bits = best_bits_from_result(cloud_result, n_vars)\nshow_decoded('云端调度方案', decode_hems(cloud_bits))"
      }
    ],
    "imageSet": {
      "width": 1672,
      "height": 941,
      "variants": [
        {
          "width": 480,
          "height": 270,
          "url": "/assets/thumb-dashboard-480.e5215714d7a3.webp",
          "bytes": 22070
        },
        {
          "width": 960,
          "height": 540,
          "url": "/assets/thumb-dashboard-960.a2deb9551715.webp",
          "bytes": 61332
        },
        {
          "width": 1672,
          "height": 941,
          "url": "/assets/thumb-dashboard-1672.7bb3cf6d210a.webp",
          "bytes": 134134
        }
      ]
    }
  },
  {
    "id": "boqa-aerofoil",
    "notebook": "/notebooks/boqa_aerofoil.ipynb",
    "image": "/assets/boqa-aerofoil.png",
    "category": "教程（应用型）",
    "difficulty": 4,
    "title": "翼型几何黑箱优化",
    "summary": "把翼型厚度、弯度、迎角等几何参数交给流体仿真或代理评分，用 CMA-ES 搜索更高升阻比设计。",
    "description": "翼型设计的难点在于目标函数来自仿真或实验，通常没有简单公式可直接求导。这个案例把几何参数作为连续变量，把升阻比作为黑箱评价值，演示如何用 BOQA 流程做工程搜索。",
    "requiredAlgorithm": "cma_es",
    "requiredAlgorithmLabel": "CMA-ES",
    "setupCode": "try:\n    from boqa_demo_utils import *\nexcept ModuleNotFoundError:\n    from notebooks.boqa_demo_utils import *\n\n# 运行前先在这里粘贴测试 key，然后再 Run All。\n# 本案例需要授权算法：cma_es（CMA-ES）。\nQUBO_TEST_KEY = \"\"  # 例如: boqa_live_...\n\nload_qubo_key_or_fail(required_algorithm=\"cma_es\", algorithm_label=\"CMA-ES\", key=QUBO_TEST_KEY)",
    "runCode": "cloud_result = run_boqa_solve(\n    problem=\"aerofoil_geometry\",\n    engine=\"cpu\",\n    algorithm=\"cma_es\",\n    params={\n        \"case_id\": \"aerofoil_lift_drag\",\n        \"objective\": \"maximize lift-to-drag ratio\",\n        \"variables\": variables_from_ranges(ranges),\n        \"ranges\": ranges,\n        \"iterations\": 80,\n    },\n)\npprint(cloud_result[\"summary\"])\nplot_trace(cloud_result, \"BOQA API / aerofoil geometry\")\nbest_vector = cloud_result[\"summary\"].get(\"best_vector\")\nif best_vector:\n    show_decoded(\"翼型参数候选\", decode_aerofoil_vector(best_vector))",
    "lessonBlocks": [
      {
        "heading": "翼型优化为什么是黑箱问题",
        "body": [
          "翼型是机翼、风机叶片、涡轮叶片等设备的截面形状。工程师关心的是它在流场中的升力、阻力、失速特性和结构可制造性。形状稍微改变，流线分离、压力分布和尾迹都会变化。",
          "真实项目里，评价一个翼型常常需要 CFD 流体仿真、风洞实验或已有数据训练出的代理模型。评价过程可以给出分数，但不一定给出解析梯度，所以它很适合黑箱优化。"
        ],
        "code": "candidate geometry -> CFD / surrogate model -> lift, drag -> score"
      },
      {
        "heading": "升力、阻力和升阻比",
        "body": [
          "流体里常用动压 q = 1/2 rho V^2 标准化力的大小。升力系数 C_L = F_L / (qS)，阻力系数 C_D = F_D / (qS)，其中 rho 是空气密度，V 是来流速度，S 是参考面积。",
          "很多巡航或风能场景希望升阻比 r_LD = C_L / C_D 尽可能大。升阻比越高，说明单位阻力带来的有效升力越大。优化时经常把最大化 r_LD 改写成最小化 -r_LD，方便放入统一的最小化框架。"
        ],
        "code": "r_LD = C_L / C_D\nminimize f(x) = -r_LD(x) + penalties"
      },
      {
        "heading": "几何参数与 Joukowski 思想",
        "body": [
          "经典空气动力学里，Joukowski 变换 z = zeta + c^2 / zeta 可以把复平面上的圆映射成类似翼型的形状。现代工程不会只靠这个公式设计完整机翼，但它很适合教学：少量参数就能控制厚度、弯度和前缘形状。",
          "本案例不直接做高成本 CFD，而是使用一个轻量代理评分函数模拟“几何参数 -> 升阻比”的黑箱过程。真实落地时，只需要把这个评分函数换成自己的仿真脚本、实验接口或机器学习代理模型。"
        ],
        "code": "z = zeta + c^2 / zeta\nx = [camber, thickness, leading_edge_radius, angle_of_attack, trailing_edge_angle, smoothness]"
      },
      {
        "heading": "CMA-ES 如何搜索连续变量",
        "body": [
          "CMA-ES 是常见的连续黑箱优化算法。它每一轮在当前均值附近采样一批候选点，评价这些候选点后，根据表现较好的样本更新均值和协方差矩阵。直观理解就是：算法会逐渐学会“往哪些方向改参数更可能变好”。",
          "它不要求目标函数可导，也不要求目标函数光滑，因此适合翼型、配方、工艺参数、控制参数等连续工程问题。"
        ],
        "code": "sample candidates -> evaluate objective -> update mean/covariance -> repeat"
      },
      {
        "heading": "代码运行后看什么",
        "body": [
          "运行后重点看 best_vector，它代表当前找到的一组归一化几何参数；再通过解码器把它变成弯度、厚度、迎角等工程量。best_score 越低，代表 -r_LD 越低，也就是升阻比更高。",
          "如果要接入真实翼型工具，可以把 estimate_lift_drag 替换为 OpenFOAM、SU2、自研 CFD、风洞数据库或机器学习代理模型。"
        ],
        "code": "best_vector -> decode_aerofoil_vector(best_vector)\nscore = -lift_drag_ratio + constraint_penalty"
      }
    ],
    "imageSet": {
      "width": 1672,
      "height": 941,
      "variants": [
        {
          "width": 480,
          "height": 270,
          "url": "/assets/boqa-aerofoil-480.fcfea9a3d814.webp",
          "bytes": 6650
        },
        {
          "width": 960,
          "height": 540,
          "url": "/assets/boqa-aerofoil-960.ef5d3d402cfe.webp",
          "bytes": 20214
        },
        {
          "width": 1672,
          "height": 941,
          "url": "/assets/boqa-aerofoil-1672.15e00b82820f.webp",
          "bytes": 45882
        }
      ]
    }
  }
]
