Sign in

    moontopolens

    Persistent H0/H1 homology, representative cycles and diagram comparison in pure MoonBit

    topology
    persistent-homology
    tda
    point-cloud
    scientific-computing
    Download zip
    Author
    Version
    0.11.0
    License
    MIT
    Last updated
    6 hours ago
    Downloads
    2

    #MoonTopoLens · 月拓镜

    CI License: MIT

    基于 MoonBit 的持续同调与数据拓扑分析工具箱。

    让点云、距离数据、时间序列和二维网格中的连接分量与环路变得可计算、可比较、可解释。 提供可复用库、命令行分析器、JSON/CSV 导出、SVG 图表与离线交互 HTML 报告。 核心算法由 MoonBit 实现,不调用 Python/C++ 拓扑库。

    当前版本:0.11.0 外部对照与性能基准版。 Mooncakes 模块:wdgodghl/moontopolens。 已实现内容、验证证据与后续计划见 验收记录。 选题功能的落实情况与暂缓项见 功能范围。

    #能做什么

    输入/功能实现
    点云1–32 维欧氏距离、Vietoris–Rips 过滤复形
    距离/不相似度矩阵校验对称非负矩阵,构建 Rips 2-骨架
    二维网格顶点值的 lower-star 方格复形,检测连接区域和洞
    灰度图像直接解析 PGM P2/P5,保留原始灰度并进入网格过滤复形
    图像预处理可选 Otsu 自动截止值与亮暗反转,记录可复现的实际参数
    批量项目从一个 JSON 清单分析多份输入并生成指定两两比较和总索引
    全对比较可选 H0/H1 距离方阵、CSV 与离线热图,提示比较条件不一致
    时间序列指定维数、滞后、步长的延迟嵌入,随后进行 Rips 分析
    参数敏感性同一时间序列扫描多组维数/滞后值,导出环路摘要和对齐的景观向量
    持续同调GF(2) 稀疏边界矩阵约化、H0/H1 区间、出生时的代表链
    比较与特征精确瓶颈距离、Betti 数与曲线、寿命排名/筛选、有限区间持续熵
    尺度与向量精确 Betti 事件、指定尺度的连通分组、持久景观采样与特征向量
    观察尺度推荐按 H1 已观察寿命挑选区间内尺度;无环路时可提示合并前分组尺度
    解释与演示区间最优匹配明细、离线双图对比、分组着色的尺度 SVG、网格填充、存活代表链高亮
    输出完整 JSON、区间/曲线 CSV、SVG 条形码/持续图、离线 HTML 报告
    可复现验证GUDHI Rips 外部区间对照、不同点数的耗时和进程内存基准

    H0 对应连接分量,H1 对应闭合环路。长寿命特征在较多尺度存在, 可以作为进一步分析的线索;其物理意义需要结合输入数据判断。

    #安装与运行

    需要 MoonBit 官方工具链, moonc ≥ 0.10.14,以及 Node.js 22(命令行文件读写)。 本地验证版本为 moonc 0.10.14+7d59c7ec9、moon 0.1.20260920。 核心库不依赖第三方 Mooncakes 包。

    git clone https://github.com/wdgodghl/moontopolens.git cd moontopolens moon version --all moon check --target js moon build --target js moon test --target js moon run --target js cmd/demo

    库与纯 MoonBit 演示也支持 WebAssembly GC:

    moon check --target wasm-gc moon build --target wasm-gc moon test --target wasm-gc moon run --target wasm-gc cmd/demo

    命令行文件操作适配器目前仅支持 JS/Node。也可以在自己的 MoonBit 项目中运行 moon add wdgodghl/moontopolens 引入已发布的核心库。

    #三个完整使用场景

    #1. 识别点云环路并比较形状变化

    输入四个正方形顶点,计算多尺度拓扑并导出图表:

    moon run --target js cmd/main -- analyze examples/square.json --out out-square moon run --target js cmd/main -- compare examples/square.json examples/rectangle.json moon run --target js cmd/main -- summary examples/square.json

    正方形的 H1 环路在边长 1 出生,在对角线 √2 消失。 比较输出 H0/H1 的瓶颈距离,量化区间集合的变化。 examples/clusters.json 则在截止尺度 0.5 保留两个连接分量。

    #2. 从采样信号提取周期结构

    对重复 [0, 1, 0, -1] 信号进行二维、滞后 1 的延迟嵌入:

    moon run --target js cmd/main -- analyze examples/periodic-series.json --out out-series

    嵌入得到菱形顶点;尺度 1.5 有一个 H1 环路,到尺度 2 被填充。 可修改输入信号、lag 和 stride,比较周期结构在所选参数下的变化。 本工具不自动推断周期,也不输出诊断结论。

    #3. 检测二值图案中的洞及填充过程

    3×3 网格外围值为 0、中心值为 1:

    moon run --target js cmd/main -- analyze examples/ring-grid.json --out out-grid

    尺度 0 形成一个洞;尺度 1 中心进入并填充该洞,对应 H1 区间 [0,1)。 适用于小型图案、占据网格和教学数据。采用顶点 lower-star 与四方向相邻约定, 不等同于所有图像库的像素连接规则。

    examples/two-holes-grid.json 包含两个洞,可以在 HTML 报告中分别选择其代表环路:

    moon run --target js cmd/main -- analyze examples/two-holes-grid.json --out out-two-holes

    输出目录必须尚不存在,且父目录已存在。每次分析保存:

    • report.json:所有区间、细胞、边界、代表链与约化统计。
    • barcode.svg:最多显示前 80 个区间;完整数据保留在 JSON 中。
    • diagram.svg:出生/死亡散点图;顶部表示在输入截止处仍存在的类。
    • intervals.csv:所有区间、死亡状态、观察寿命和代表细胞索引。
    • betti.csv:101 个尺度上的 H0/H1,包含截止尺度。
    • betti-events.csv:全部出生/死亡尺度的精确 H0/H1,合并同尺度事件。
    • recommendations.json / recommendations.csv:建议查看的尺度、依据、区间编号和该尺度 H0/H1。
    • report.html:可离线打开的完整交互报告。

    #离线报告怎么用

    直接用浏览器打开输出目录中的 report.html,无需服务器或网络:

    1. 拖动尺度滑块,观察当前 H0/H1 数量、进入复形的顶点和边。
    2. 选择维度、最小观察寿命或“仅当前存活”,筛选并逐页查看全部区间排名。
    3. 点击区间编号,在该环路存活的尺度上查看橙色代表边。
    4. 查看 Betti 曲线,或将 CSV 导入表格/绘图工具继续分析。

    绘图坐标不参与同调计算:高维点云保留原维度距离,仅前两个坐标用于展示。 一维点云投影到横轴;网格使用 (列,-行);距离矩阵没有几何坐标时显示说明。 网格的已进入二维方格面以青绿色显示,可以观察洞的填充;Rips 点云的三角形不绘制, 应结合 H1 数字判断环路是否已死亡。HTML 表格每页显示 50 行,JSON 和 CSV 保留全部区间。

    summary 命令只精简输出,计算过程与 analyze 相同。 比较命令额外记录双方截止尺度和复形类型是否一致,供判断结果的可比性。

    #0.3.0:尺度快照与持久景观

    查看指定尺度有哪些分组、多少细胞和环路:

    moon run --target js cmd/main -- slice examples/square.json 1 moon run --target js cmd/main -- slice examples/clusters.json 0.5

    快照包含 H0/H1、各维度活跃细胞数、连通分组的原始顶点编号和存活区间索引。 正方形在尺度 1 为一个分组、一个环路;分离簇在 0.5 为两个分组。 允许查询首个细胞出现之前的空快照;超出输入截止尺度返回错误。 原始网格编号为 行×列数+列,不会把未进入的顶点重新编号。

    把有限区间转换成持久景观向量,便于后续统计分析:

    moon run --target js cmd/main -- landscape examples/two-holes-grid.json 1 0 1 --out out-landscape

    参数依次为输入、同调维度、采样起点和终点;输出目录可省略,仅向标准输出打印 JSON。 CLI 固定 101 个采样点、3 层,保存 landscape.json 和 landscape.csv。 两个 [0,1) 的洞在尺度 0.5 对应前两层各 0.5,第三层为 0。 JSON 同时提供采样网格、分层值和长度 303 的按层拼接向量。 库 API 可自定义采样点数和层数。这里使用原始三角帐篷高度,不缩放或归一化; 截止时仍未死亡的区间明确排除并记录数量,不能将截止当作死亡值。 比较向量必须使用一致维度、采样范围、点数、层数、尺度单位和可比的截断条件。 这些是采样特征,不代表已经实现分类器。

    HTML 曲线现在使用精确事件,保留短暂环路;betti.csv 仍供均匀采样分析。 examples/short-loop-grid.json 的 H1 仅在 [0.004,0.005) 存活: 101 点的均匀采样全部为 0,事件 CSV 和 HTML 曲线仍保留该环路。

    #0.4.0:解释比较结果并导出尺度图

    moon run --target js cmd/main -- compare examples/square.json examples/rectangle.json --out out-comparison moon run --target js cmd/main -- slice examples/clusters.json 0.5 --out out-clusters-slice moon run --target js cmd/main -- slice examples/square.json 1 --out out-loop-slice --interval 4

    正方形默认分析中,区间 #4 是 H1 环路;自定义输入的编号应从其 report.json 或 slice 的存活区间列表获取。 --interval 必须位于 --out 之后,且所选类必须是当前尺度仍存活的 H1。 无效选择在创建输出目录前报错,不留下伪完整的结果。

    比较输出五个文件:comparison.json、matches.csv、comparison.html、 left-diagram.svg、right-diagram.svg。直接打开 HTML 可看双侧持续图、距离、 截止条件提示及可按维度/对角线筛选的匹配表。表格最多显示 200 行,CSV/JSON 保留完整匹配。 比较命令原有 JSON 字段保留,新增匹配与摘要字段。

    匹配明细使用左右原始区间编号,区分有限配对、对角线配对、截止仍存活配对及未匹配存活类。 这是瓶颈距离的一种最优匹配,可能不唯一;不保证最小总代价,也不代表真实对象身份对应。 对角线代价为有限寿命的一半;未匹配存活类代价为空,完整距离为 null。 不能将截止条件不同造成的差异直接解释为数据结构变化。

    尺度导出保存 slice.json;有坐标时还保存 slice.svg。 SVG 顶点按 MoonBit 算出的连通分组着色,保留原始编号;七种颜色循环使用。 选中的存活 H1 出生代表链使用橙色边。投影保持横纵等比例; 网格中已进入的方格以青绿色填充,点云 Rips 三角形不绘制。 距离矩阵和空坐标输入只输出 JSON;不虚构几何位置。

    #0.5.0:直接读取 PGM 图像与批量分析

    直接分析纯文本 P2 或二进制 P5 PGM 灰度图:

    moon run --target js cmd/main -- image examples/ring-image.pgm 1 --out out-ring-image

    CUTOFF 是原始像素值尺度;默认不反色、不归一化。示例外围为 0、中心为 1, 在尺度 0 有一个 H1 洞,在尺度 1 消失。输出与网格分析相同的报告文件, 另有 image-info.json 记录 PGM 编码、尺寸、最大灰度和截止值。 支持 P2/P5、8 位及 16 位 P5;16 位样本按大端读取。 当前只读取单幅、最大 64×64 且不超过 1 MB 的 PGM;PNG/JPEG 尚未支持。 格式遵循 Netpbm PGM 规范 的单图像子集。

    一次运行多份输入与指定比较:

    moon run --target js cmd/main -- batch examples/batch-study.json --out out-study

    清单中的 cases 包含 name、相对于清单所在目录的 input; PGM 案例还需 "format":"pgm" 和数值或 "auto" 的 threshold,JSON 案例的格式默认是 json。 可选的 comparisons 数组用 name、left、right 引用案例名。 示例比较正方形与矩形,并核对 PGM 导入与等价 JSON 网格的结果。 out-study/batch.json 是汇总索引;cases/<名称>/ 保存每份完整分析, comparisons/<名称>/ 保存比较 JSON、匹配 CSV、离线 HTML 和两侧持续图。 案例名只接受 ASCII 字母、数字、-、_。清单最多 8 个案例、12 个比较, 合计最多 16000 个过滤细胞。输入路径限于清单所在目录及子目录; 所有输入和比较先完成计算,再创建全新的输出目录。

    #0.6.0:自动阈值与亮色前景

    若图像前景是亮色,可先反转灰度;若没有预先选定的截止值,可用 auto:

    moon run --target js cmd/main -- image examples/bright-ring.pgm auto --invert --out out-bright-ring

    反转按 max_value - 原像素值 计算,然后在处理后的灰度图上使用 Otsu 类间方差选择截止值。 亮环示例因此在截止值 0 出现一个 H1 洞。image-info.json 记录最终 cutoff、 cutoff_mode 和 inverted,便于复现。常量图的自动截止值是该图唯一灰度。 自动阈值只是直方图分割启发式,不保证给出拓扑上最有意义的尺度; 跨图比较应使用可比的灰度尺度和截止条件。

    批量清单中的 PGM 案例也可写为 {"format":"pgm","threshold":"auto","invert":true}。 examples/batch-study.json 同时演示手动与自动阈值,并以等价 JSON 网格核对结果。

    #0.7.0:批量全对距离热图

    在批量清单顶层添加 "all_pairs": true,运行同一条 batch 命令即可为最多八个案例 计算全部无序案例对的 H0/H1 瓶颈距离。示例清单已启用:

    moon run --target js cmd/main -- batch examples/batch-study.json --out out-study

    输出新增 distance-matrix.json、distance-h0.csv、distance-h1.csv 和 distance-heatmap.html。HTML 可离线打开,行标题链接到各案例报告; 现有 comparisons 中具名配对仍各自生成详细匹配报告。 矩阵顺序与清单中的 cases 顺序相同,主对角线为 0、矩阵对称。 若两侧截止时存活的类数量不同,完整距离没有有限匹配,JSON 使用 null、 CSV 留空、热图显示“—”,不能解释为距离 0。

    热图用橙框标出复形类型或截止值不同的案例对;即使两项相同,也仍须核对 尺度单位、灰度变换及数据含义。颜色仅在各自维度内部按有限距离缩放, 请用 CSV/JSON 数值比较。all_pairs 缺省为 false,保留旧清单行为。

    #0.8.0:推荐值得查看的尺度

    moon run --target js cmd/main -- recommend examples/ring-grid.json moon run --target js cmd/main -- recommend examples/clusters.json --out out-recommend-clusters moon run --target js cmd/main -- slice examples/ring-grid.json 0.5 --out out-recommended-slice

    第一个示例的 H1 在 [0,1) 存活,因此建议在 0.5 观察环路。 工具最多按已观察寿命排序列出五个 H1 区间的内部尺度,并附上最终截止尺度。 若没有 H1,但连接分量曾合并,还会给出一个精确的合并前 H0 事件尺度。 recommendations.json 提供原因代码、原始区间编号、实际尺度和该处 H0/H1; recommendations.csv 便于表格处理。分析、图像和批量案例的输出目录也自动包含这两份文件。

    原因 h1_finite 表示有限环路;h1_alive_at_cutoff 表示环路仍活到输入截止, 其已观察寿命仅是下界;h0_before_merges 表示更多分组尚未合并的事件尺度; final_cutoff 用作最终状态基线。可将推荐尺度传给 slice 查看细胞与分组; 仅 H1 项的 interval_index 可用于 slice --out ... --interval INDEX 高亮代表链。 这些建议用于浏览,不是最优阈值,也不自动判断数据的实际含义。

    #0.9.0:检查时间序列嵌入参数的影响

    moon run --target js cmd/main -- sweep examples/periodic-sweep.json --out out-periodic-sweep

    输入中 kind 为 series_sweep,提供同一组 series、正数 threshold、 dimensions 和 lags 数组;stride 默认为 1,max_cells 默认为 4000。 工具分析维数与滞后值的所有组合,最多 12 组,且总细胞数不得超过 16000。 每组参数若使信号太短、嵌入超过 128 点或超过细胞预算,将明确报错。

    sweep.json 记录每组嵌入点数、H1 有限区间和截止时存活区间数量、 最长已观察寿命、截止处 H1,以及 303 维持久景观向量; sweep-summary.csv 便于比较摘要,sweep-landscape.csv 每行是一组参数的向量。 所有向量都在 [0,threshold] 上以相同的 101 个采样点和 3 层计算, 按层和尺度顺序排列;存活至截止的环路不进入向量,因此应同时查看其数量。 维数和滞后值可能改变嵌入点数,扫描结果只能说明敏感性,不能自动选出最佳参数或证明周期。

    #自定义输入

    { "kind": "points", "threshold": 2, "max_cells": 4000, "points": [[0, 0], [1, 0], [1, 1], [0, 1]] }

    普通分析的 kind 支持 points / matrix / grid / series,对应数据字段同名。 series_sweep 是独立的扫描输入类型,只用于 sweep 命令。 threshold 必填;max_cells 默认 4000。 series 可指定 dimension(默认 2)、lag(默认 1)、stride(默认 1)。 未知字段、非法形状、非有限值和超出预算的输入会返回错误,CLI 退出码为 2。 全部格式见 examples。

    #MoonBit 库用法

    包导入(发布后,或通过本地路径依赖):

    import { "wdgodghl/moontopolens" @topo, }

    let points = [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]
    let filtration = @topo.rips(points, threshold=2.0)
    let analysis = @topo.analyze(filtration)
    let loops = @topo.betti(analysis, 1, 1.0) // 1
    let finite_diagram = @topo.diagram(analysis, 1)
    let json = @topo.report_json(analysis)
    let strongest = @topo.ranked_intervals(analysis, dimension=1, min_lifetime=0.1)
    let summary = @topo.summary_json(analysis)
    let html = @topo.html_report(analysis)
    let events = @topo.betti_events(analysis)
    let groups = @topo.connected_components(analysis, 1.0)
    let snapshot = @topo.snapshot_json(analysis, 1.0)
    let landscape = @topo.persistence_landscape(
    analysis, dimension=1, start=0.0, end=2.0, samples=101, layers=3,
    )
    let matching = @topo.interval_matching(analysis, analysis, dimension=1)
    let comparison = @topo.comparison_json(analysis, analysis)
    let scale_picture = @topo.scale_svg(analysis, scale=1.0)

    调用者需要处理 TopologyError。公开 API 见 pkg.generated.mbti, 可运行库样例见 cmd/demo/main.mbt。 参数扫描库入口为 @topo.series_sweep(request_text),返回 JSON 报告、摘要 CSV 和景观向量 CSV。

    #算法约定与边界

    • Rips 使用直径约定:距离 d 的边在 d 出现,无半径除二。
    • 矩阵要求严格对称、对角线为零;无需满足三角不等式,不偷偷修正输入。
    • 计算系数域为 GF(2),实现 H0/H1,不宣称完整 H2 或整数同调。
    • death: null 表示在输入截止处仍存在,不能据此证明在完整过滤中无限存活。
    • 区间为 [birth,death),默认省略零长度区间;代表环路在出生时有效,不保证最短。
    • 比较包含有限区间和未死亡区间;后者数量不等时结果为 null。 输入的尺度、单位与截止条件应具有可比性。
    • 点云/矩阵最多 128 个点,维度最多 32;网格最多 64×64;有限图匹配各最多 64 个区间。
    • 最大细胞数默认 4000、硬上限 10000;约化默认最多 2,000,000 次列加法, 存储最多 2,000,000 个稀疏条目。Rips 三角形数量可快速增长,超过预算时明确报错。
    • 持续熵仅统计有限区间,使用自然对数;无正寿命有限区间时约定为 0。 未死亡类的观察寿命为 cutoff-birth 下界,不能当作完整寿命。
    • 持久景观限 2–1001 个采样点、1–16 层,并限制 有限区间数×采样点数×层数 ≤ 2,000,000。 参数扫描固定 101 点、3 层;最多 12 组参数和 16000 个总细胞。 连通分组要求零细胞/边保留一/两个原始顶点 ID;不支持缺失该编码的自定义过滤。
    • 只处理小规模内存数据,未做大数据性能承诺;完整 H2、最短环路和在线上传应用仍属后续计划。

    算法结构与正确性说明见 architecture。

    #测试与持续集成

    moon fmt --check moon check --target js moon check --target wasm-gc moon build --target js moon build --target wasm-gc moon coverage clean moon test --target js --enable-coverage moon test --target wasm-gc moon coverage report -f summary node scripts/check-source.mjs node scripts/smoke.mjs python -m pip install numpy==2.3.5 gudhi==3.13.0 python scripts/reference_gudhi.py node scripts/benchmark.mjs moon package --list

    外部对照先运行 moon build --target js,再用 GUDHI 3.13.0 独立计算 22 组 Rips 点云、距离矩阵和延迟嵌入的 H0/H1 区间;CI 会执行这一检查。 基准使用确定性二维点云、相同截止值和三次独立进程,报告 MoonBit 构造/约化的中位耗时与分析后的进程 RSS。 RSS 包含 Node 运行时且不是峰值;基准仅说明已测规模,不代表大规模性能承诺。 测试细节见 外部对照 与 性能记录。

    测试覆盖已知形状、重复点、截止语义、稀疏预算、无效输入、网格与嵌入、导出与比较。 JS 与 Wasm GC 的最新测试数量及结果见 验收记录,另有 CLI 端到端验证。 另外使用独立的稠密行消元算法核对 12 个点云在 11 个尺度的 Betti 数, 使用穷举匹配核对瓶颈距离。CI 包含类型检查、构建、两种后端测试、覆盖率摘要和 CLI 场景。 每个示例在全部 Betti 事件处验证快照分组数,持久景观用已知帐篷函数核对。 匹配见证在 12 对形状中检查每个区间恰好出现一次、代价符合公式、最大代价等于瓶颈距离。 另有三组离线对比报告、尺度 SVG、存活选择/无坐标/输出冲突的端到端验证。 生成的 HTML 通过 DOM 替身检查滑块、筛选、选中环路与显示数值;该方法不验证真实浏览器视觉布局。

    #项目来源与许可证

    选题公开查重记录见 duplicate-review。 算法依据计算拓扑公开研究,当前源码为独立 MoonBit 实现; 未复制或移植 Ripser/GUDHI 源码。采用 OSI 认可的 MIT 许可证。

    使用了 AI 辅助设计与实现。参赛者需理解尺度约定、约化步骤、测试方法和限制, 并对提交成果负责。发布步骤见 release。

    TopologyError

    pub(all) suberror TopologyError {
    TopologyError(String)
    } derive(
    Debug
    )

    All public validation and computation errors carry an actionable message.

    TopologyError::message

    fn TopologyError::message(self : TopologyError) -> String

    Analysis

    pub struct Analysis {
    filtration : Filtration
    intervals : Array[Interval]
    column_additions : Int
    } derive(
    Debug
    )

    Reduction results retain the filtration so representatives can be decoded.

    Analysis::to_repr

    BatchCase

    pub struct BatchCase {
    name : String
    input : String
    format : String
    threshold : Double?
    auto_threshold : Bool
    invert : Bool
    } derive(
    Debug
    )

    BatchDistanceMatrix

    pub struct BatchDistanceMatrix {
    names : Array[String]
    kinds : Array[String]
    cutoffs : Array[Double]
    h0 : Array[Array[Double?]]
    h1 : Array[Array[Double?]]
    conditions_aligned : Array[Array[Bool]]
    } derive(
    Debug
    )

    Symmetric H0/H1 bottleneck distances for one bounded batch study. None means no finite match because alive interval counts differ.

    BatchPair

    pub struct BatchPair {
    name : String
    left : String
    right : String
    } derive(
    Debug
    )

    BatchPlan

    pub struct BatchPlan {
    cases : Array[BatchCase]
    comparisons : Array[BatchPair]
    all_pairs : Bool
    } derive(
    Debug
    )

    BettiEvent

    pub struct BettiEvent {
    scale : Double
    h0 : Int
    h1 : Int
    } derive(
    Debug
    )

    Exact right-continuous Betti counts at filtration event scales.

    Cell

    pub struct Cell {
    key : String
    dimension : Int
    value : Double
    boundary : Array[Int]
    vertices : Array[Int]
    } derive(
    Debug
    )

    A filtered cell. Boundary entries are indices of earlier cells.

    Cell::to_repr

    Filtration

    pub struct Filtration {
    cells : Array[Cell]
    vertex_count : Int
    kind : String
    cutoff : Double
    positions : Array[Array[Double]]
    } derive(
    Debug
    )

    A finite filtration, ordered by value, dimension, then stable cell key.

    Interval

    pub struct Interval {
    dimension : Int
    birth : Double
    death : Double?
    birth_cell : Int
    death_cell : Int?
    representative : Array[Int]
    } derive(
    Debug
    )

    An interval [birth, death); None means alive at the filtration cutoff.

    Interval::to_repr

    IntervalMatch

    pub struct IntervalMatch {
    left : Int?
    right : Int?
    cost : Double?
    kind : String
    } derive(
    Debug
    )

    Indices refer to the original analysis interval arrays, not diagram rows. A missing side in a finite match denotes the diagonal, not a deleted object.

    PgmImage

    pub struct PgmImage {
    grid : Array[Array[Double]]
    max_value : Int
    encoding : String
    } derive(
    Debug
    )

    Parsed Netpbm PGM image. Pixel values stay in the original 0..maxval scale.

    PgmImage::to_repr

    ScaleRecommendation

    pub struct ScaleRecommendation {
    scale : Double
    interval_index : Int?
    reason : String
    observed_lifetime : Double?
    censored : Bool
    } derive(
    Debug
    )

    A viewing scale derived from one observed H1 interval or the final cutoff. The interval index refers to Analysis.intervals and may be passed to slice.

    analyze

    fn analyze(filtration : Filtration, include_zero? : Bool, max_additions? : Int) -> Analysis raise TopologyError

    Standard left-to-right sparse boundary matrix reduction over GF(2). Tracks change-of-basis chains to return representatives at birth. Emits only H0 and H1. Zero-length intervals are omitted by default.

    analyze_json

    fn analyze_json(text : String) -> Analysis raise TopologyError

    Parse a bounded JSON request, construct a filtration, then analyze it. Supported kinds: points, matrix, grid, series. Unknown fields are errors.

    barcode_svg

    fn barcode_svg(analysis : Analysis) -> String

    Standalone SVG barcode. Drawing is capped at 80 rows; JSON retains all bars. Essential bars have a dashed ending, indicating the input cutoff.

    batch_distance_matrix

    fn batch_distance_matrix(plan : BatchPlan, analyses : Array[Analysis]) -> BatchDistanceMatrix raise TopologyError

    Compute every unordered pair once. Agreement on kind and cutoff is only a necessary metadata check; scale units and data meaning remain user choices.

    batch_matrix_csv

    fn batch_matrix_csv(matrix : BatchDistanceMatrix, dimension : Int) -> String raise TopologyError

    Square CSV with blanks for null distances. Batch names are CSV-safe ASCII.

    batch_matrix_html

    fn batch_matrix_html(matrix : BatchDistanceMatrix) -> String

    Static offline heatmap with no JavaScript or network dependency.

    batch_matrix_json

    fn batch_matrix_json(matrix : BatchDistanceMatrix) -> Json

    Portable matrix schema; null is deliberately distinct from a zero distance.

    betti

    fn betti(analysis : Analysis, dimension : Int, scale : Double) -> Int raise TopologyError

    Count classes alive at a scale in the analyzed (possibly truncated) complex.

    betti_csv

    fn betti_csv(analysis : Analysis, samples? : Int) -> String raise TopologyError

    Uniform samples over the filtration's observed range, including cutoff.

    betti_curve

    fn betti_curve(analysis : Analysis, dimension~ : Int, scales : Array[Double]) -> Array[Int] raise TopologyError

    Sample persistent Betti counts, suitable for downstream feature extraction.

    betti_events

    fn betti_events(analysis : Analysis) -> Array[BettiEvent]

    Aggregate births and deaths at the same scale before recording counts. Includes the first cell scale and cutoff, even when no count changes there.

    betti_events_csv

    fn betti_events_csv(analysis : Analysis) -> String

    Event rows, unlike uniform samples, retain arbitrarily short intervals.

    betti_events_json

    fn betti_events_json(analysis : Analysis) -> Json

    bottleneck

    fn bottleneck(a : Array[(Double, Double)], b : Array[(Double, Double)]) -> Double raise TopologyError

    Exact finite-diagram bottleneck distance with diagonal matching, L-infinity. Binary-searches the candidate edge costs using bipartite perfect matching.

    circle_points

    fn circle_points(count : Int, radius : Double) -> Array[Array[Double]] raise TopologyError

    Generate a deterministic 2D circle for teaching and reproducible examples.

    compare_analyses

    fn compare_analyses(a : Analysis, b : Analysis, dimension~ : Int) -> Double? raise TopologyError

    Compare complete interval collections of one dimension, including essentials. Essential intervals only match essential intervals; unequal counts => None. Under a finite cutoff, essential here means alive at that cutoff.

    comparison_html

    fn comparison_html(a : Analysis, b : Analysis) -> String raise TopologyError

    Offline side-by-side diagrams and a filterable optimal matching table.

    comparison_json

    fn comparison_json(a : Analysis, b : Analysis) -> Json raise TopologyError

    Existing comparison metadata plus a witness explaining every interval.

    connected_components

    fn connected_components(analysis : Analysis, scale : Double) -> Array[Array[Int]] raise TopologyError

    Connected components of the active one-skeleton, ordered by smallest vertex. Vertex IDs are the original input IDs, including gaps in thresholded grids.

    cubical_grid

    fn cubical_grid(grid : Array[Array[Double]], threshold~ : Double, max_cells? : Int) -> Filtration raise TopologyError

    Lower-star cubical filtration on values at grid vertices. Edges/squares inherit the maximum value of their corner vertices. For binary images pass 0 for foreground, 1 for background and cutoff 0. This convention connects foreground horizontally/vertically, not diagonally.

    cycle_edges

    fn cycle_edges(analysis : Analysis, interval : Interval) -> Array[(Int, Int)] raise TopologyError

    Decode an H1 representative into vertex pairs for plotting.

    delay_embed

    fn delay_embed(series : Array[Double], dimension~ : Int, lag~ : Int, stride? : Int) -> Array[Array[Double]] raise TopologyError

    Sliding-window delay embedding: row i is [x_i, x_(i+lag), ...]. No normalization or resampling is applied implicitly.

    diagram

    fn diagram(analysis : Analysis, dimension : Int) -> Array[(Double, Double)] raise TopologyError

    Finite points (birth, death) of one dimension in a persistence diagram.

    diagram_svg

    fn diagram_svg(analysis : Analysis) -> String

    Standalone birth/death scatter plot with essential classes shown at the top.

    distance_matrix

    fn distance_matrix(points : Array[Array[Double]]) -> Array[Array[Double]] raise TopologyError

    Validate coordinates and compute Euclidean distances in any dimension <= 32.

    html_report

    fn html_report(analysis : Analysis) -> String

    A self-contained offline report. JavaScript only displays precomputed results: slider filtering, table selection and 2D projection; no homology runs in UI. JSON escapes '<' so public cell keys cannot terminate the data script.

    interval_matching

    fn interval_matching(a : Analysis, b : Analysis, dimension~ : Int) -> Array[IntervalMatch] raise TopologyError

    One optimal finite-diagram matching, plus sorted-birth censored matching. If censored counts differ, all censored rows are marked unmatched with null cost.

    intervals_csv

    fn intervals_csv(analysis : Analysis) -> String

    Numeric CSV with complete intervals and a semicolon-separated chain column.

    invert_pgm

    fn invert_pgm(image : PgmImage) -> PgmImage

    Reverse the grayscale ordering while preserving the original max value. This lets bright foreground enter a lower-star filtration first.

    landscape_csv

    fn landscape_csv(analysis : Analysis, dimension~ : Int, start~ : Double, end~ : Double, samples? : Int, layers? : Int) -> String raise TopologyError

    landscape_json

    fn landscape_json(analysis : Analysis, dimension~ : Int, start~ : Double, end~ : Double, samples? : Int, layers? : Int) -> Json raise TopologyError

    Feature vector with explicit grid, order and censoring metadata.

    matching_csv

    fn matching_csv(a : Analysis, b : Analysis) -> String raise TopologyError

    observed_lifetime

    fn observed_lifetime(analysis : Analysis, interval : Interval) -> Double

    Observed lifetime; for a censored class this is only a lower bound.

    otsu_threshold

    fn otsu_threshold(image : PgmImage) -> Double

    Otsu cutoff in the image's original integer scale. For equal maxima, choose the smallest cutoff; a constant image uses its sole gray value. The cutoff is computed on the supplied image, after any requested inversion.

    parse_batch_manifest

    fn parse_batch_manifest(text : String) -> BatchPlan raise TopologyError

    Bounded manifest for up to eight local inputs and twelve comparisons. File paths are resolved and confined by the CLI host adapter.

    parse_pgm

    fn parse_pgm(data : Array[Int]) -> PgmImage raise TopologyError

    Read exactly one plain P2 or raw P5 grayscale image, including 16-bit P5. Reject trailing images, malformed rasters and dimensions beyond 64x64.

    persistence_landscape

    fn persistence_landscape(analysis : Analysis, dimension~ : Int, start~ : Double, end~ : Double, samples? : Int, layers? : Int) -> Array[Array[Double]] raise TopologyError

    Raw persistence landscape samples, layer-major, without normalization. lambda_k(t) is the kth largest max(0, min(t-birth, death-t)). Censored intervals are excluded; use identical ranges when comparing vectors.

    ranked_intervals

    fn ranked_intervals(analysis : Analysis, dimension~ : Int, min_lifetime? : Double, include_alive? : Bool) -> Array[Interval] raise TopologyError

    Select and rank intervals by observed lifetime without modifying the result. Undead intervals use cutoff-birth; ranking is not proof of infinite lifetime.

    recommend_scales

    fn recommend_scales(analysis : Analysis, max_loops? : Int) -> Array[ScaleRecommendation] raise TopologyError

    Rank H1 classes by observed lifetime, then select a scale within each interval. For alive classes, the observed lifetime is only a lower bound. If there are no loops, include an exact pre-merge H0 event when components do merge. The final cutoff is always appended as a baseline view.

    recommendations_csv

    fn recommendations_csv(analysis : Analysis, max_loops? : Int) -> String raise TopologyError

    recommendations_json

    fn recommendations_json(analysis : Analysis, max_loops? : Int) -> Json raise TopologyError

    Structured explanation with the exact scale and Betti counts for each view.

    report_json

    fn report_json(analysis : Analysis) -> Json

    Portable report schema. null death means alive at the input cutoff. Representative cell indices refer to the accompanying cells array.

    rips

    fn rips(points : Array[Array[Double]], threshold~ : Double, max_cells? : Int) -> Filtration raise TopologyError

    Convenience point-cloud entry point.

    rips_matrix

    fn rips_matrix(matrix : Array[Array[Double]], threshold~ : Double, max_cells? : Int) -> Filtration raise TopologyError

    Build the 2-skeleton of a Vietoris–Rips filtration using diameter convention. Edges appear at distance d; triangles at their maximum edge distance. Two-dimensional cells suffice to compute H0 and H1, but not complete H2.

    scale_svg

    fn scale_svg(analysis : Analysis, scale~ : Double, interval? : Int?) -> String raise TopologyError

    Snapshot projection with MoonBit-computed component colors and an optional live H1 birth representative. Requires coordinates; never invents matrix geometry.

    series_sweep

    fn series_sweep(text : String) -> (Json, String, String) raise TopologyError

    Evaluate a bounded Cartesian sweep of delay-embedding parameters. Every vector uses the same H1 landscape grid and excludes cutoff-censored bars. The returned CSV tables share the JSON case order.

    snapshot_json

    fn snapshot_json(analysis : Analysis, scale : Double) -> Json raise TopologyError

    Portable scale snapshot with original IDs and indices into report intervals.

    summary_json

    fn summary_json(analysis : Analysis) -> Json

    Per-dimension counts, finite lifetimes, finite persistence entropy and Betti numbers at cutoff. Censored intervals never enter finite entropy.

    validate_filtration

    fn validate_filtration(filtration : Filtration) -> Unit raise TopologyError

    Validate a public filtration before reduction, including boundary squared. Protects callers who construct or mutate the exposed data structures.

    validate_matrix

    fn validate_matrix(matrix : Array[Array[Double]]) -> Unit raise TopologyError

    Symmetric nonnegative dissimilarities are accepted without a triangle axiom. Exact symmetry is required, rather than silently changing user data.