# MoonBit solver vs SciPy reference

Python 仅作为可选开发验证工具；正式库继续使用纯 MoonBit runtime。SciPy 不参与几何建模 API、运行时求解或部署，也不是 MoonBit 模块依赖。

## 对照范围和同一输入

本次对照共 **500 个配置**：

- 400 个有定位的模型来自当前 MoonBit JS 程序的新输出，包含三角形、矩形、镜像圆与线圆相切，以及平移、旋转、缩放和初始扰动等六组。
- 100 个自由三角形取自已提交随机实验的前 100 项，沿用相同六个初始坐标和三条边长，不重新筛选。脚本先核对已提交 CSV 和源码指纹；这些是既有 MoonBit JS 实测结果，没有重复执行整个 1000 项实验。
- 五种模型类型各 100 个配置，其中 200 个配置包含圆。两边使用完全相同的几何关系、尺寸、初始点与初始半径。

验证脚本位于仓库的 `dev/scipy/`，通过 `.moonignore` 从发布源码包排除；开发依赖单独记录在该目录的 requirements.txt。打包检查另外拒绝任何 `dev/scipy/` 文件进入正式归档。结果文档和数据可随源码分发，Python / SciPy 工具本身不分发。

## SciPy 求解方式

参考端独立编写残差，使用 [SciPy least_squares 官方接口](https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.least_squares.html)：`method="trf"`、`jac="3-point"`、线性损失、`x_scale=1`、ftol / xtol / gtol 均为 1e-12、`max_nfev=200`。正半径变量使用非负边界，实测结果均严格为正。

每个模型的坐标先减去该案例的变换原点、再除以统一长度比例；半径同样除以比例，输出再转换回世界单位。长度和角度残差分别按案例对应容差加权。这个可逆的数值变换保持物理关系和初值，用于避免极端尺寸的溢出或下溢；不把解析答案写入初值。

SciPy 的终止 success 标记只表明满足其终止条件，并不等同于 CAD 约束残差达标，因此两边结果均重新计算残差比。SciPy 的 nfev 是函数求值计数，不能直接与 MoonBit 的迭代次数比较。本实验没有测量性能，也不宣称两种求解算法或收敛预算等价。

## 比较指标与边界

1. 两边各自的最大独立残差比不超过 1.00001，保留独立浮点计算的微小比值裕量。
2. 有定位模型直接比较点坐标；自由三角形先对参考点集做平移、正交旋转 / 反射对齐，再比较坐标。不拟合缩放，不改变边长，同时保存对齐前的坐标差。
3. 比较半径差；无圆的配置不计入半径指标。坐标和半径均记录世界单位差、除以模型 scale 的差及其占长度容差的倍数。
4. 本实验将两边残差均达标、坐标及半径差均不超过 100 倍长度容差的配置记为 `agree`。这个比较阈值与每条约束自身的残差容差不同，不表示两边坐标逐位相等。

不达标项按原样记录为 MoonBit 残差未达标、参考残差未达标、不同构型或参考异常。脚本先写入完整报告，再以非零状态提示不一致；不会删除失败配置或自动换初值重试。SciPy 是数值参考，不能当作任意非线性问题的正确性证明或最小冲突判定。

## 2026-10-22 实测

Windows x64、MoonBit JS / moonc 0.10.15、Python 3.12.14、NumPy 2.3.5、SciPy 1.18.0。

| 指标 | 结果 |
| --- | ---: |
| 记录的配置 | 500 |
| 达到本实验 `agree` 标准 | **500** |
| 未解决差异 | 0 |
| SciPy success 标记为真 | 500 |
| 含圆的配置 | 200 |
| MoonBit 最大独立残差比 | 0.963569 |
| SciPy 最大独立残差比 | 5.11680e-5 |
| 最大坐标差 / scale，含自由三角形刚体对齐 | 2.71526e-7 |
| 最大半径差 / scale | 1.14058e-7 |
| 最大坐标差占长度容差的倍数 | 2.71526 |
| 最大半径差占长度容差的倍数 | 1.14058 |

[汇总与环境快照](validation-results/scipy-reference-js.json)及[全部 500 项输入、两边输出与逐项指标 CSV](validation-results/scipy-reference-js.csv)均保留真实数据。记录包含源码 SHA-256、CSV 校验值、SciPy 状态、终止说明和求值次数。极端尺寸组同步缩放长度容差，不能将上述归一化误差解释为统一的世界单位绝对误差。

## 可选开发环境与轻量复现

需从 GitHub 仓库取用开发工具，发布包中没有该目录。建议使用独立虚拟环境；以下安装仅用于开发，不改动 MoonBit 依赖：

```sh
python -m venv .venv-scipy
# 激活虚拟环境后执行：
python -m pip install -r dev/scipy/requirements.txt
python dev/scipy/smoke_test.py
python dev/scipy/reference.py --count 8
# 仅在需要重新测量这项对照实验时运行 500 项并保存快照：
python dev/scipy/reference.py --write
```

工具只使用 `MOON_HOME` 或 PATH 中的 MoonBit。完整 JSON、逐项 CSV 和 MoonBit 原始输入输出保存在 `_build/scipy-reference/`。默认 500 项；`--count 8` 只运行前八项 SciPy 对照，但 MoonBit 生成器仍输出固定的 400 项，脚本没有执行原有完整验证框架或后端矩阵。

本次只做 8 项冒烟、必要的 500 项对照实验、4 项定向检查及发布包排除检查，未运行全量回归、四后端验证或源码包消费测试。提交跳过自动全量 CI。上述结果是限定语料上的开发证据，不是新运行时依赖或通用收敛率承诺。

类型化错误升级未重跑上述对照数据。此可选工具继续严格要求随机实验快照与当前源码一致；若提示 `Recorded random corpus is stale`，须先审查并刷新随机实验快照，再进行新的 SciPy 对照。历史结果不会被自动标为新源码实测。
