// fitnlm.cpp ------ 对标 MATLAB fitnlm 的纯 C++ 实现
#include <iostream>
#include <vector>
#include <cmath>
#include <functional>
#include <limits>
#include <iomanip>
#include <cstdlib>
#include <algorithm>
#include <string>
// ============================================================
// 矩阵类型(行优先)
// ============================================================
struct Matrix {
int rows, cols;
std::vector<double> data;
Matrix(int r = 0, int c = 0, double v = 0.0) : rows(r), cols(c), data(r * c, v) {}
double& operator()(int i, int j) { return data[i * cols + j]; }
double operator()(int i, int j) const { return data[i * cols + j]; }
};
static double dot(const std::vector<double>& a, const std::vector<double>& b) {
double s = 0.0; for (size_t i = 0; i < a.size(); ++i) s += a[i] * b[i]; return s;
}
// Cholesky 分解求解对称正定方程组 Ax=b
static bool solveSPD(const Matrix& A, const std::vector<double>& b, std::vector<double>& x) {
int n = A.rows;
Matrix L(n, n, 0.0);
for (int i = 0; i < n; ++i) {
for (int j = 0; j <= i; ++j) {
double sum = A(i, j);
for (int k = 0; k < j; ++k) sum -= L(i, k) * L(j, k);
if (i == j) {
if (sum <= 0.0) return false;
L(i, j) = std::sqrt(sum);
} else L(i, j) = sum / L(j, j);
}
}
std::vector<double> y(n, 0.0);
for (int i = 0; i < n; ++i) {
double sum = b[i];
for (int k = 0; k < i; ++k) sum -= L(i, k) * y[k];
y[i] = sum / L(i, i);
}
x.assign(n, 0.0);
for (int i = n - 1; i >= 0; --i) {
double sum = y[i];
for (int k = i + 1; k < n; ++k) sum -= L(k, i) * x[k];
x[i] = sum / L(i, i);
}
return true;
}
// ============================================================
// 数值雅可比(前向差分,步长对齐 MATLAB 的 DerivStep = eps^(1/3))
// ============================================================
static Matrix numericalJacobian(
const std::function<std::vector<double>(const std::vector<double>&)>& f,
const std::vector<double>& beta, const std::vector<double>& r0)
{
int m = (int)r0.size(), n = (int)beta.size();
Matrix J(m, n, 0.0);
const double derivStep = std::cbrt(std::numeric_limits<double>::epsilon());
for (int j = 0; j < n; ++j) {
double h = derivStep * std::max(1.0, std::abs(beta[j]));
std::vector<double> betaP = beta; betaP[j] += h;
std::vector<double> rP = f(betaP);
for (int i = 0; i < m; ++i) J(i, j) = (rP[i] - r0[i]) / h;
}
return J;
}
// ============================================================
// LM 求解器(与 MATLAB nlinfit/fitnlm 非稳健估计一致)
// ============================================================
static bool levenbergMarquardt(
const std::function<std::vector<double>(const std::vector<double>&)>& f,
std::vector<double>& beta, int maxIter = 200,
double tolFun = 1e-8, double tolX = 1e-8)
{
int n = (int)beta.size();
std::vector<double> r = f(beta);
int m = (int)r.size();
double S = dot(r, r);
double lambda = 1e-3;
for (int iter = 0; iter < maxIter; ++iter) {
Matrix J = numericalJacobian(f, beta, r);
Matrix JtJ(n, n, 0.0); std::vector<double> Jtr(n, 0.0);
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
double s = 0.0; for (int k = 0; k < m; ++k) s += J(k, i) * J(k, j);
JtJ(i, j) = s;
}
double s = 0.0; for (int k = 0; k < m; ++k) s += J(k, i) * r[k];
Jtr[i] = s;
}
bool accepted = false;
std::vector<double> newBeta = beta;
std::vector<double> newR;
double newS = S;
for (int attempt = 0; attempt < 30; ++attempt) {
Matrix A = JtJ;
for (int i = 0; i < n; ++i) A(i, i) += lambda * (JtJ(i, i) + 1e-12);
std::vector<double> delta;
if (!solveSPD(A, Jtr, delta)) { lambda *= 10.0; continue; }
for (int i = 0; i < n; ++i) delta[i] = -delta[i]; /* GN/LM 步: (J'J+λI)δ = -J'r */
for (int i = 0; i < n; ++i) newBeta[i] = beta[i] + delta[i];
newR = f(newBeta);
newS = dot(newR, newR);
if (newS < S) {
accepted = true;
if (S - newS < tolFun * std::max(1.0, S)) {
beta = newBeta; r = newR; S = newS; return true;
}
lambda = std::max(lambda * 0.1, 1e-15);
break;
} else lambda *= 10.0;
}
if (!accepted) return false;
double dX = 0.0, xNorm = 0.0;
for (int i = 0; i < n; ++i) { double d = newBeta[i] - beta[i]; dX += d * d; xNorm += beta[i] * beta[i]; }
beta = newBeta; r = newR; S = newS;
if (std::sqrt(dX) < tolX * (std::sqrt(xNorm) + tolX)) return true;
}
return false;
}
// ============================================================
// 拟合结果结构体(对应 NonLinearModel 的属性)
// ============================================================
struct NonLinearModel {
std::vector<double> Estimate;
std::vector<double> SE;
std::vector<double> tStat;
std::vector<double> pValue;
std::vector<std::string> CoeffNames;
double RMSE = 0.0;
double Rsquared_Ordinary = 0.0;
double Rsquared_Adjusted = 0.0;
double SSE = 0.0;
double SST = 0.0;
double DFE = 0.0;
int NumObservations = 0;
int NumCoefficients = 0;
std::vector<double> Residuals;
std::vector<double> Fitted;
bool converged = false;
};
// t 分布双尾 p 值
static double tDistPValue(double t, double df) {
if (df <= 0) return 1.0;
double x = df / (df + t * t);
auto betacf = [](double a, double b, double x) {
const int MAXIT = 200; const double EPS = 3e-12, FPMIN = 1e-300;
double qab = a + b, qap = a + 1, qam = a - 1;
double c = 1.0, d = 1.0 - qab * x / qap;
if (std::abs(d) < FPMIN) d = FPMIN;
d = 1.0 / d; double h = d;
for (int m = 1; m <= MAXIT; ++m) {
int m2 = 2 * m;
double aa = m * (b - m) * x / ((qam + m2) * (a + m2));
d = 1.0 + aa * d; if (std::abs(d) < FPMIN) d = FPMIN;
c = 1.0 + aa / c; if (std::abs(c) < FPMIN) c = FPMIN;
d = 1.0 / d; h *= d * c;
aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2));
d = 1.0 + aa * d; if (std::abs(d) < FPMIN) d = FPMIN;
c = 1.0 + aa / c; if (std::abs(c) < FPMIN) c = FPMIN;
d = 1.0 / d; double del = d * c; h *= del;
if (std::abs(del - 1.0) < EPS) break;
}
return h;
};
auto betai = [&](double a, double b, double x) {
if (x <= 0.0) return 0.0; if (x >= 1.0) return 1.0;
double bt = std::exp(std::lgamma(a + b) - std::lgamma(a) - std::lgamma(b)
+ a * std::log(x) + b * std::log(1.0 - x));
if (x < (a + 1.0) / (a + b + 2.0)) return bt * betacf(a, b, x) / a;
return 1.0 - bt * betacf(b, a, 1.0 - x) / b;
};
return betai(0.5 * df, 0.5, x);
}
// ============================================================
// fitnlm 高层接口
// ============================================================
static NonLinearModel fitnlm(
const std::vector<double>& X, const std::vector<double>& y,
const std::function<double(double, const std::vector<double>&)>& modelfun,
const std::vector<double>& beta0,
const std::vector<std::string>& coeffNames = {})
{
int nObs = (int)X.size(), nPar = (int)beta0.size();
NonLinearModel mdl;
mdl.NumObservations = nObs;
mdl.NumCoefficients = nPar;
mdl.DFE = nObs - nPar;
mdl.CoeffNames = coeffNames;
if (mdl.CoeffNames.empty())
for (int i = 0; i < nPar; ++i) mdl.CoeffNames.push_back("b" + std::to_string(i + 1));
auto residual = [&](const std::vector<double>& beta) {
std::vector<double> r(nObs);
for (int i = 0; i < nObs; ++i) r[i] = y[i] - modelfun(X[i], beta);
return r;
};
std::vector<double> beta = beta0;
mdl.converged = levenbergMarquardt(residual, beta);
mdl.Estimate = beta;
mdl.Fitted.resize(nObs); mdl.Residuals.resize(nObs);
double yMean = 0.0; for (double v : y) yMean += v; yMean /= nObs;
mdl.SSE = 0.0; mdl.SST = 0.0;
for (int i = 0; i < nObs; ++i) {
mdl.Fitted[i] = modelfun(X[i], beta);
mdl.Residuals[i] = y[i] - mdl.Fitted[i];
mdl.SSE += mdl.Residuals[i] * mdl.Residuals[i];
mdl.SST += (y[i] - yMean) * (y[i] - yMean);
}
mdl.RMSE = std::sqrt(mdl.SSE / mdl.DFE);
mdl.Rsquared_Ordinary = 1.0 - mdl.SSE / mdl.SST;
mdl.Rsquared_Adjusted = 1.0 - (mdl.SSE / mdl.DFE) / (mdl.SST / (nObs - 1));
std::vector<double> rFinal = residual(beta);
Matrix J = numericalJacobian(residual, beta, rFinal);
Matrix JtJ(nPar, nPar, 0.0);
for (int i = 0; i < nPar; ++i)
for (int j = 0; j < nPar; ++j) {
double s = 0.0; for (int k = 0; k < nObs; ++k) s += J(k, i) * J(k, j);
JtJ(i, j) = s;
}
double MSE = mdl.SSE / mdl.DFE;
Matrix cov(nPar, nPar, 0.0);
for (int i = 0; i < nPar; ++i) {
std::vector<double> e(nPar, 0.0); e[i] = 1.0;
std::vector<double> col;
if (solveSPD(JtJ, e, col))
for (int j = 0; j < nPar; ++j) cov(i, j) = col[j] * MSE;
}
mdl.SE.resize(nPar); mdl.tStat.resize(nPar); mdl.pValue.resize(nPar);
for (int i = 0; i < nPar; ++i) {
mdl.SE[i] = std::sqrt(std::max(0.0, cov(i, i)));
mdl.tStat[i] = mdl.Estimate[i] / mdl.SE[i];
mdl.pValue[i] = tDistPValue(std::abs(mdl.tStat[i]), mdl.DFE);
}
return mdl;
}
// ============================================================
// 测试主程序
// ============================================================
int main() {
srand(42);
const int m = 20;
std::vector<double> x(m), y(m);
const double a_true = 2.5, b_true = 0.8;
for (int i = 0; i < m; ++i) {
x[i] = i * 0.2;
double noise = 0.05 * ((rand() / (double)RAND_MAX) - 0.5);
y[i] = a_true * std::exp(b_true * x[i]) + noise;
}
auto modelfun = [](double xi, const std::vector<double>& beta) {
return beta[0] * std::exp(beta[1] * xi);
};
std::vector<double> beta0 = {1.0, 0.5};
NonLinearModel mdl = fitnlm(x, y, modelfun, beta0);
std::cout << std::fixed << std::setprecision(4);
std::cout << "Nonlinear regression model:\n";
std::cout << " y ~ b1*exp(b2*x)\n\n";
std::cout << "Estimated Coefficients:\n";
std::cout << std::setw(6) << " " << std::setw(12) << "Estimate"
<< std::setw(10) << "SE" << std::setw(10) << "tStat"
<< std::setw(12) << "pValue" << "\n";
for (int i = 0; i < mdl.NumCoefficients; ++i) {
std::cout << std::setw(6) << mdl.CoeffNames[i]
<< std::setw(12) << mdl.Estimate[i]
<< std::setw(10) << mdl.SE[i]
<< std::setw(10) << mdl.tStat[i]
<< std::setw(12) << mdl.pValue[i] << "\n";
}
std::cout << "\nNumber of observations: " << mdl.NumObservations
<< ", Error degrees of freedom: " << (int)mdl.DFE << "\n";
std::cout << "Root Mean Squared Error: " << mdl.RMSE << "\n";
std::cout << "R-Squared: " << mdl.Rsquared_Ordinary
<< ", Adjusted R-Squared " << mdl.Rsquared_Adjusted << "\n";
std::cout << "\nLM 收敛: " << (mdl.converged ? "成功" : "未收敛") << "\n";
std::cout << "真实参数: a = " << a_true << ", b = " << b_true << "\n";
return 0;
}