{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "7fb27b941602401d91542211134fc71a",
   "metadata": {},
   "source": [
    "# 曲线拟合问题\n",
    "\n",
    "**类别：** 非线性优化\n",
    "\n",
    "使用 OptAgent 的 Python 接口描述变量、约束与目标。\n",
    "\n",
    "问题与原始示例来源：[Hexaly Code Templates](https://www.hexaly.com/templates/curve-fitting-problem)。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "acae54e37e7d407bbb7b55eff062a284",
   "metadata": {},
   "source": [
    "## 问题描述\n",
    "\n",
    "解决 **曲线拟合问题** 即构建一条曲线或数学函数,使其尽可能最佳地拟合一系列数据点,并可能满足一些约束。曲线拟合既可以是插值(需要对数据进行精确拟合),也可以是平滑(构造一个“平滑”函数来近似拟合数据)。更多详细信息,请参阅 [Wikipedia](https://en.wikipedia.org/wiki/Curve_fitting)。\n",
    "\n",
    "在我们这里考虑的曲线拟合问题中,我们希望为一个已定义的函数找到最优参数,以使该函数能最好地将一组输入映射为一组输出。例如,假设映射函数具有如下形式:\n",
    "\n",
    "$$f(x) = a\\sin(b-x) + cx^2 + d$$\n",
    "\n",
    "我们希望找到参数 a、b、c 和 d,使映射函数能够最佳地拟合观测数据。\n",
    "\n",
    "### 学到的要点\n",
    "\n",
    "- 添加浮点型 [决策变量](https://optagent.pages.dev/guide/modeling/)\n",
    "- 使用 [`sin` 和 `pow` 算子](https://optagent.pages.dev/guide/modeling/) 构造一个非线性表达式\n",
    "- 最小化一个 [非线性目标](https://optagent.pages.dev/guide/modeling/)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9a63283cbaf04dbcab1f6479b197f3a8",
   "metadata": {},
   "source": [
    "## 数据\n",
    "\n",
    "每个数据文件包含:\n",
    "\n",
    "- 第一行:观测数量\n",
    "- 接下来的行:每个观测的输入和输出值\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8dd0d8092fe74a7c96281538738b07e2",
   "metadata": {},
   "source": [
    "## 建模思路\n",
    "\n",
    "曲线拟合问题的 OptAgent 模型使用浮点型决策变量来表示四个参数 $a$、$b$、$c$ 和 $d$。利用 [`sin`](https://optagent.pages.dev/api/model-reference/#optagent.OptModel.sin) 和 [`pow`](https://optagent.pages.dev/api/model-reference/#optagent.OptModel.pow) 非线性算子,我们可以将每个观测对应的函数预测值作为中间表达式计算出来。\n",
    "\n",
    "目标函数是平方误差之和。利用先前计算的中间表达式和 [`pow`](https://optagent.pages.dev/api/model-reference/#optagent.OptModel.pow) 算子,我们可以将每个观测的平方误差计算为映射函数预测输出与观测输出之差的平方。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "72eea5119410473aa328ad9291626812",
   "metadata": {},
   "source": [
    "## Python 实现\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "40b47fd5",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pathlib import Path\n",
    "\n",
    "from optagent import OptModel, solve\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "def read_float(filename):\n",
    "    return [\n",
    "        float(value)\n",
    "        for value in Path(filename).read_text(encoding=\"utf-8\").split()\n",
    "    ]\n",
    "\n",
    "\n",
    "def read_instance(instance_file):\n",
    "    file_it = iter(read_float(instance_file))\n",
    "    nb_observations = int(next(file_it))\n",
    "    inputs = []\n",
    "    outputs = []\n",
    "    for _ in range(nb_observations):\n",
    "        inputs.append(next(file_it))\n",
    "        outputs.append(next(file_it))\n",
    "    return nb_observations, inputs, outputs\n",
    "\n",
    "\n",
    "def main(instance_file, output_file=None, time_limit=3):\n",
    "    nb_observations, inputs, outputs = read_instance(instance_file)\n",
    "\n",
    "    model = OptModel()\n",
    "    a = model.float(-100.0, 100.0)\n",
    "    b = model.float(-100.0, 100.0)\n",
    "    c = model.float(-100.0, 100.0)\n",
    "    d = model.float(-100.0, 100.0)\n",
    "\n",
    "    predictions = [\n",
    "        a * model.sin(b - inputs[i]) + c * inputs[i] ** 2 + d\n",
    "        for i in range(nb_observations)\n",
    "    ]\n",
    "    errors = [\n",
    "        predictions[i] - outputs[i] for i in range(nb_observations)\n",
    "    ]\n",
    "    square_error = model.sum(\n",
    "        model.pow(errors[i], 2) for i in range(nb_observations)\n",
    "    )\n",
    "    model.minimize(square_error)\n",
    "\n",
    "    solution = solve(model, time_limit_s=float(time_limit))\n",
    "    values = {'a': a.value, 'b': b.value, 'c': c.value, 'd': d.value}\n",
    "    result = \"\\n\".join(\n",
    "        [\n",
    "            \"Optimal mapping function\",\n",
    "            f\"a = {values['a']}\",\n",
    "            f\"b = {values['b']}\",\n",
    "            f\"c = {values['c']}\",\n",
    "            f\"d = {values['d']}\",\n",
    "        ]\n",
    "    )\n",
    "    print(result)\n",
    "    if output_file is not None:\n",
    "        Path(output_file).write_text(result + \"\\n\", encoding=\"utf-8\")\n",
    "    return solution"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ff68b1cd",
   "metadata": {},
   "source": [
    "## 本地运行\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e9941064",
   "metadata": {},
   "outputs": [],
   "source": [
    "INSTANCE_DIR = Path.cwd() / \"instances\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dd05410d",
   "metadata": {},
   "outputs": [],
   "source": [
    "solution = main(INSTANCE_DIR / \"observations.in\", time_limit=3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dbf914b9",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.14.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
