Mantissa 使用教程 --- Python 版与 C++ 版

本教程面向使用者,手把手带你完成 Mantissa 的安装、第一次调用,以及全部算子在 Python 和 C++ 两侧的用法。
- 想在脚本 / 数据分析中使用 → 直接看 [第一部分:Python 版](#第一部分:Python 版)
- 想集成进 C++ 工程 → 直接看 [第二部分:C++ 版](#第二部分:C++ 版)
- 两边都要用 → 顺序阅读,最后有 [Python ↔ C++ 对照表](#Python ↔ C++ 对照表)
Mantissa 是什么
Mantissa 是一个高性能 C++17 数学算法库,当前聚焦线性代数,特点:
- 一套算法,两种用法:C++ 原生 API + nanobind Python 绑定(零拷贝,直接收发 NumPy 数组)
- 数值安全:输入不合法时显式抛异常,绝不静默回退
- abi3 Wheel :Python 侧一次
pip install即可用,无需 C++ 编译器
当前所有算子支持 CPU / float64(CUDA 后端在路线图中)。算子一览:
| 算子 | 分类 | 功能 | Python | C++ |
|---|---|---|---|---|
eig_sym |
eigen | 对称矩阵特征分解 A = VΛVᵀ | ✅ | ✅ |
eig |
eigen | 一般(可非对称)矩阵特征分解 | ✅ | ✅ |
cholesky |
decomposition | Cholesky 分解 A = LLᵀ | ✅ | ✅ |
svd |
decomposition | Thin SVD(支持矩形矩阵) | ✅ | ✅ |
qr |
decomposition | Thin QR 分解 A = QR | ✅ | ✅ |
matrix_sqrt |
matrix_func | 对称半正定矩阵平方根 | ✅ | ✅ |
matrix_exp |
matrix_func | 对称矩阵指数 | ✅ | ✅ |
matrix_log |
matrix_func | 对称正定矩阵对数 | ✅ | ✅ |
matrix_sign |
matrix_func | 对称矩阵符号函数 | ✅ | ✅ |
solve |
solve | 线性方程组 A·X = B | ✅ | ✅ |
不同版本发布的算子数量可能不同,以
print(dir(mantissa))(Python)或include/mantissa/linalg/下的头文件(C++)为准。
第一部分:Python 版
1.1 安装
bash
pip install mantissa-cpp numpy
包名说明 :PyPI 上
mantissa这个名字已被 Twisted 项目占用,所以本项目的分发名是mantissa-cpp;但安装后 import 名仍是mantissa:
python
import mantissa
print(mantissa.__version__)
Wheel 中已包含编译好的 .pyd / .so 及运行时 DLL,支持 Python 3.8+(Windows x64 / Linux x86_64 / macOS arm64)。
1.2 三条核心约定
用之前只需记住三件事:
- dtype 必须是
np.float64。传 float32 / int 数组会抛ValueError,不会静默转换。需要时先A = A.astype(np.float64)。 - 数组建议是 C-contiguous (NumPy 新建数组默认满足)。转置视图等非连续输入建议先
np.ascontiguousarray(A)。 - 返回值是零拷贝的 NumPy 数组 ,通过 capsule 持有 C++ 内存的生命周期,可正常读取;如果要就地修改,先
.copy()。
异常映射规则:
| 场景 | Python 异常 |
|---|---|
| shape / dtype / 对称性不合法 | ValueError |
| 数值计算失败(不收敛、奇异等) | RuntimeError |
1.3 快速上手
python
import numpy as np
import mantissa
A = np.array([[4.0, 1.0],
[1.0, 3.0]]) # 对称正定矩阵
# 特征分解
values, vectors = mantissa.eig_sym(A)
print(values) # [2.382, 4.618],升序
# SVD
U, S, Vt = mantissa.svd(A)
# 解线性方程组 A·x = b
b = np.array([1.0, 2.0])
x = mantissa.solve(A, b)
print(np.allclose(A @ x, b)) # True
1.4 算子逐个讲
特征分解:eig_sym / eig
eig_sym(A) --- 对称矩阵专用,更快更稳:
python
A = np.array([[4.0, 1.0], [1.0, 3.0]])
values, vectors = mantissa.eig_sym(A)
# values 升序排列;vectors 的第 j 列对应 values[j]
assert np.allclose(A, vectors @ np.diag(values) @ vectors.T)
assert np.allclose(A @ vectors[:, 0], values[0] * vectors[:, 0])
eig(A) --- 一般方阵(可以不对称)。特征值/特征向量可能是复数,而 Mantissa 内部只支持实数 dtype,所以返回 4 元组(实部、虚部分开):
python
A = np.array([[0.0, -1.0], [1.0, 0.0]]) # 旋转矩阵,特征值为 ±i
vr, vi, Sr, Si = mantissa.eig(A)
values = vr + 1j * vi # 复特征值
vectors = Sr + 1j * Si # 复特征向量(按列)
实输入的复特征值以共轭对形式出现。对称矩阵请优先用 eig_sym。
矩阵分解:cholesky / svd / qr
python
# Cholesky:要求对称正定,返回下三角 L,满足 A = L @ L.T
L = mantissa.cholesky(A)
assert np.allclose(L @ L.T, A)
# Thin SVD:支持矩形矩阵。对 m×n 输入,k = min(m, n)
M = np.random.randn(3, 5)
U, S, Vt = mantissa.svd(M) # U:(3,3) S:(3,) 降序 Vt:(3,5)
assert np.allclose(M, U @ np.diag(S) @ Vt)
# Thin QR:Q 列正交,R 上三角,无列主元
Q, R = mantissa.qr(M) # Q:(3,3) R:(3,5)
assert np.allclose(M, Q @ R)
SVD 的常见用途------求数值秩:
python
U, S, Vt = mantissa.svd(M)
tol = S[0] * max(M.shape) * np.finfo(np.float64).eps
rank = int((S > tol).sum())
矩阵函数:matrix_sqrt / matrix_exp / matrix_log / matrix_sign
四个函数都基于对称矩阵的特征分解实现,结果保持对称:
python
A = np.array([[4.0, 0.0], [0.0, 9.0]])
S = mantissa.matrix_sqrt(A) # 要求半正定;S @ S ≈ A
E = mantissa.matrix_exp(A) # exp(A),对称
L = mantissa.matrix_log(A) # 要求严格正定(零特征值会被拒绝)
G = mantissa.matrix_sign(A) # sign(A);A 非奇异时 G @ G ≈ I
| 函数 | 输入要求 | 备注 |
|---|---|---|
matrix_sqrt |
对称半正定 | 有明显负特征值时报 ValueError |
matrix_exp |
对称 | 内置溢出保护 |
matrix_log |
对称正定 | 特征值必须严格 > 0 |
matrix_sign |
对称(惯性任意) | 接近零的特征值(< n·eps·max|λ|)映射为 0 |
解方程:solve
solve(A, B) 求解 A·X = B(内部用 FullPivLU),要求 A 方阵且非奇异:
python
A = np.array([[4.0, 1.0], [1.0, 3.0]])
# 向量右端:B shape (n,)
b = np.array([1.0, 2.0])
x = mantissa.solve(A, b) # ≈ [0.0909, 0.6364]
# 矩阵右端:B shape (n, k),一次解多组
B = np.eye(2)
A_inv = mantissa.solve(A, B) # 传单位矩阵即得 A⁻¹
1.5 与 SciPy 对比验证
结果与 SciPy/LAPACK 一致(误差通常在 1e-12 量级):
python
import scipy.linalg as sla
vals_m, _ = mantissa.eig_sym(A)
vals_s, _ = sla.eigh(A)
assert np.allclose(vals_m, vals_s)
U_m, S_m, Vt_m = mantissa.svd(M)
U_s, S_s, Vt_s = sla.svd(M, full_matrices=False) # 注意对应 thin SVD
assert np.allclose(S_m, S_s)
1.6 Python 侧常见报错排查
| 报错 / 现象 | 原因 | 解决 |
|---|---|---|
ValueError: ... dtype |
传入了 float32 / int 数组 | A.astype(np.float64) |
ValueError: ... square |
对称类算子传入了非方阵 | 检查 shape;矩形矩阵用 svd / qr |
ValueError: ... symmetric |
eig_sym 等传入的矩阵不对称 |
对称化 (A + A.T) / 2 或改用 eig |
RuntimeError |
数值计算失败(不收敛、矩阵奇异) | 检查条件数;solve 前先确认 A 非奇异 |
pip install 报 "No matching distribution found" |
Python < 3.8 或平台不支持 | 升级 Python;确认 Windows x64 / Linux x86_64 / macOS arm64 |
| 修改返回数组后行为异常 | 返回数组是零拷贝只读语义 | 修改前 .copy() |
第二部分:C++ 版
2.1 前置条件
| 工具 | 最低版本 | 说明 |
|---|---|---|
| C++ 编译器 | C++17 | MSVC 2019+ / GCC 9+ / Clang 10+ |
| CMake | 3.24 | 需要 Presets v3 支持 |
| Conan | 2.x | 包管理器 |
2.2 获取依赖
Mantissa 通过 GitLab Conan registry 分发,依赖 Eigen 5 与 glog:
bash
# 一次性配置
conan remote add gitlab https://gitlab.com/api/v4/projects/86406866/packages/conan
conan remote login gitlab <your_user> -p <your_token>
# 在你的项目目录下安装依赖并生成 CMake toolchain
conan install . -of build --build=missing -s build_type=Release -r gitlab
2.3 集成到你的工程
CMakeLists.txt:
cmake
cmake_minimum_required(VERSION 3.24)
project(my_app LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(mantissa REQUIRED)
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE mantissa::linalg_cpu)
只需核心设施(Tensor 等)时链接
mantissa::core;用任何 linalg 算子链接mantissa::linalg_cpu(它会自动带上 core)。
配置与构建:
bash
cmake --preset conan-default
cmake --build --preset conan-release
运行(Windows):动态库需要能找到 DLL,用 Conan 生成的运行环境脚本:
powershell
cmd /c "build\build\generators\conanrun.bat && my_app.exe"
Linux / macOS 对应 source build/build/generators/conanrun.sh。
2.4 Tensor 基础
所有算子的输入输出都是 mantissa::Tensor(float64、CPU、行优先)。头文件:<mantissa/core/tensor.hpp>。
cpp
#include <mantissa/core/tensor.hpp>
#include <iostream>
#include <vector>
// 1) 从 std::vector 构造(按行优先填充)
auto A = mantissa::Tensor::from_vector({4.0, 1.0, 1.0, 3.0}, {2, 2});
// 2) 分配空张量后手动填充
auto T = mantissa::Tensor({2, 3}, mantissa::DType::Float64);
double* p = T.data_ptr<double>();
for (int i = 0; i < T.numel(); ++i) p[i] = i * 1.0;
// 3) 读取元信息
const auto& shape = A.shape(); // shape[0]=2, shape[1]=2
std::cout << A.ndim() << A.numel(); // 2 4
// 4) 遍历元素(行优先连续存储)
const double* data = A.data_ptr<double>();
for (std::int64_t i = 0; i < shape[0]; ++i)
for (std::int64_t j = 0; j < shape[1]; ++j)
std::cout << data[i * shape[1] + j] << ' ';
Tensor 还提供零拷贝视图操作(共享底层 Storage,不复制数据):
cpp
auto Tt = T.transpose(0, 1); // 交换维度,转置后非连续
auto row0 = T.slice(0, 0); // 固定第 0 维取第 0 行,降一维
auto flat = T.reshape({6}); // 要求连续
注意:视图不延长内存生命周期,源 Tensor 必须比视图活得久。
2.5 调用算子
算子位于 namespace mantissa::linalg,按分类放在不同头文件下:
| 头文件 | 算子 | 返回类型 |
|---|---|---|
mantissa/linalg/eigen/eig_sym.hpp |
eig_sym(A) |
EigenResult{values, vectors} |
mantissa/linalg/eigen/eig.hpp |
eig(A) |
EigResult{values_real, values_imag, vectors_real, vectors_imag} |
mantissa/linalg/decomposition/cholesky.hpp |
cholesky(A) |
Tensor(下三角 L) |
mantissa/linalg/decomposition/svd.hpp |
svd(A) |
SVDResult{U, S, Vt} |
mantissa/linalg/decomposition/qr.hpp |
qr(A) |
QRResult{Q, R} |
mantissa/linalg/matrix_func/matrix_sqrt.hpp |
matrix_sqrt(A) |
Tensor |
mantissa/linalg/matrix_func/matrix_exp.hpp |
matrix_exp(A) |
Tensor |
mantissa/linalg/matrix_func/matrix_log.hpp |
matrix_log(A) |
Tensor |
mantissa/linalg/matrix_func/matrix_sign.hpp |
matrix_sign(A) |
Tensor |
mantissa/linalg/solve/solve.hpp |
solve(A, B) |
Tensor |
完整示例------SVD + 解方程:
cpp
#include <mantissa/core/tensor.hpp>
#include <mantissa/linalg/decomposition/svd.hpp>
#include <mantissa/linalg/solve/solve.hpp>
#include <iostream>
int main() {
// A = [[4, 1], [1, 3]]
auto A = mantissa::Tensor::from_vector({4.0, 1.0, 1.0, 3.0}, {2, 2});
// SVD(thin,支持矩形矩阵)
auto [U, S, Vt] = [] {
auto r = mantissa::linalg::svd(A);
return std::make_tuple(std::move(r.U), std::move(r.S), std::move(r.Vt));
}();
// 或直接访问成员:auto result = mantissa::linalg::svd(A); result.U ...
const double* s = S.data_ptr<double>();
std::cout << "singular values: " << s[0] << ", " << s[1] << "\n";
// 解 A·x = b
auto b = mantissa::Tensor::from_vector({1.0, 2.0}, {2});
auto x = mantissa::linalg::solve(A, b);
const double* xp = x.data_ptr<double>();
std::cout << "x = [" << xp[0] << ", " << xp[1] << "]\n";
return 0;
}
对称特征分解:
cpp
#include <mantissa/linalg/eigen/eig_sym.hpp>
auto result = mantissa::linalg::eig_sym(A);
// result.values --- shape {n},升序
// result.vectors --- shape {n,n},第 j 列对应 values[j]
const double* w = result.values.data_ptr<double>();
一般(非对称)特征分解返回实部/虚部四个 Tensor(与 Python 侧的 4 元组一致):
cpp
#include <mantissa/linalg/eigen/eig.hpp>
auto r = mantissa::linalg::eig(A);
// r.values_real, r.values_imag, r.vectors_real, r.vectors_imag
2.6 错误处理约定
C++ 侧与 Python 侧一一对应:
| C++ 异常 | 触发条件 |
|---|---|
std::invalid_argument |
shape / dtype / 对称性不合法(对应 Python ValueError) |
std::runtime_error |
数值计算失败,如不收敛、矩阵奇异(对应 Python RuntimeError) |
cpp
try {
auto L = mantissa::linalg::cholesky(A); // A 非正定时抛异常
} catch (const std::invalid_argument& e) {
// 输入不合法
} catch (const std::runtime_error& e) {
// 数值失败
}
2.7 从源码构建(可选)
如果你需要修改库本身,或不走 Conan 分发:
bash
git clone <repo-url> && cd Mantissa
conan install . -of build --build=missing -s build_type=Release -r gitlab
cmake --preset conan-default
cmake --build --preset conan-release
产物为 mantissa_core 与 mantissa_linalg_cpu 两个动态库。运行测试(96 用例):
powershell
# Windows --- 必须先注入 DLL 路径,否则报 0xc0000135
cmd /c "build\build\generators\conanrun.bat && ctest --preset conan-release --output-on-failure"
常用 CMake 选项:MANTISSA_BUILD_TESTS(默认 ON)、MANTISSA_BUILD_PYTHON(默认 OFF)、MANTISSA_ENABLE_NATIVE(-march=native / /arch:AVX2,默认 OFF)。
第三部分:Python ↔ C++ 对照
同一套算法的两侧调用对照,方便在两种语言间迁移代码:
| 操作 | Python | C++ |
|---|---|---|
| 构造矩阵 | np.array([[4., 1.], [1., 3.]]) |
Tensor::from_vector({4., 1., 1., 3.}, {2, 2}) |
| 对称特征分解 | values, vectors = eig_sym(A) |
auto r = eig_sym(A); r.values; r.vectors |
| 一般特征分解 | vr, vi, Sr, Si = eig(A) |
auto r = eig(A); r.values_real; ... |
| Cholesky | L = cholesky(A) |
auto L = cholesky(A); |
| SVD | U, S, Vt = svd(A) |
auto r = svd(A); r.U; r.S; r.Vt |
| QR | Q, R = qr(A) |
auto r = qr(A); r.Q; r.R |
| 矩阵函数 | matrix_sqrt/exp/log/sign(A) |
同名函数,返回 Tensor |
| 解方程 | x = solve(A, b) |
auto x = solve(A, b); |
| 输入不合法 | ValueError |
std::invalid_argument |
| 数值失败 | RuntimeError |
std::runtime_error |
两侧行为完全一致的部分:
- 特征值升序(
eig_sym)、奇异值降序(svd) - 特征向量按列存放
- thin SVD / thin QR,支持矩形矩阵,k = min(m, n)
- 仅 float64、仅 CPU、行优先存储
- 显式异常,无静默回退
常见问题(FAQ)
Q:Python 和 C++ 结果会有差异吗?
不会。Python 绑定只是薄封装,底层调用同一份 C++ 实现;与 SciPy/LAPACK 的对照误差也在 1e-12 量级。
Q:什么时候选 Python,什么时候选 C++?
交互式分析、与 NumPy/SciPy 生态混用 → Python;对延迟敏感、已有 C++ 工程、需要精细控制内存 → C++。Python 侧的零拷贝绑定意味着即便走 Python,计算也是纯 C++ 速度。
Q:支持 float32 / GPU 吗?
当前仅 float64 / CPU。float32 与 CUDA 后端在路线图上(v0.3)。
Q:Windows 下运行报 DLL 加载失败(0xc0000135)?
动态库不在搜索路径。用 Conan 生成的环境脚本包一层:cmd /c "build\build\generators\conanrun.bat && <你的程序>"。
Q:import mantissa 报 ModuleNotFoundError,但我装过包了?
确认安装的是 mantissa-cpp(pip show mantissa-cpp),且当前解释器与安装环境一致(虚拟环境)。