13constexpr double kLog2 = 0.69314718055994530942;
16 std::chrono::steady_clock::time_point t1) {
17 return std::chrono::duration<double>(t1 - t0).count();
25 rls_P_(Eigen::MatrixXd::Identity(
N,
N) * 100.0),
32 bias_b_(Eigen::Vector3d::Zero()),
33 bias_P_(Eigen::Matrix3d::Identity() * 0.01),
41 theta_mean_ << 0.0, 200.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0;
51 constexpr double lo = -2147483648.0;
52 constexpr double hi = 2147483647.0;
54 return static_cast<int32_t
>(lo);
56 return static_cast<int32_t
>(hi);
57 return static_cast<int32_t
>(std::llround(adc_d));
66 Eigen::MatrixXd Phi(kFactorySeedGridPoints,
N);
67 Eigen::VectorXd y(kFactorySeedGridPoints);
68 const double lo = -2e8;
69 const double hi = 1.8e9;
70 for (
int i = 0; i < kFactorySeedGridPoints; ++i) {
71 double t =
static_cast<double>(i) /
static_cast<double>(kFactorySeedGridPoints - 1);
72 double adc_d = lo + t * (hi - lo);
73 int32_t adc_i = clamp_adc_i32(adc_d);
77 Eigen::MatrixXd normal = Phi.transpose() * Phi + kRidgeLambda * Eigen::MatrixXd::Identity(
N,
N);
78 Eigen::VectorXd
rhs = Phi.transpose() * y;
79 Eigen::LDLT<Eigen::MatrixXd> ldlt(normal);
80 if (ldlt.info() != Eigen::Success) {
82 theta_mean_ << 0.0, 200.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0;
84 rls_P_ = Eigen::MatrixXd::Identity(
N,
N) * 100.0;
89 double rmse = std::sqrt(resid.squaredNorm() /
static_cast<double>(kFactorySeedGridPoints));
90 double var = std::max(rmse * rmse, 1e-8);
92 rls_P_ = Eigen::MatrixXd::Identity(
N,
N) * 100.0;
99 const double lo = -2e8;
100 const double hi = 1.8e9;
101 for (
int i = 0; i <
n; ++i) {
102 double t =
static_cast<double>(i) /
static_cast<double>(
n - 1);
103 double adc_d = lo + t * (hi - lo);
104 int32_t adc_i = clamp_adc_i32(adc_d);
108 max_e = std::max(max_e, std::abs(pr - pf));
115 double adc_norm_scale) {
116 if (poly_coeffs.size() < 2 || adc_norm_scale <= 0)
118 double c0 = poly_coeffs[0];
119 double c1 = poly_coeffs[1];
120 double theta1 = c1 * 1e9 / adc_norm_scale;
121 double theta0 = c0 - theta1 * adc_norm_min / 1e9;
135 Eigen::Vector3d phi_b = (-dt *
bias_tau_.array().inverse()).array().exp();
136 Eigen::Matrix3d Phi = phi_b.asDiagonal();
139 Eigen::Matrix3d Q = q.asDiagonal();
146 Eigen::Vector3d h = Eigen::Vector3d::Ones();
147 double S = (h.transpose() *
bias_P_ * h)(0, 0) + sigma_meas * sigma_meas;
148 S = std::max(S, 1e-12);
149 Eigen::Vector3d K =
bias_P_ * h / S;
151 bias_P_ = (Eigen::Matrix3d::Identity() - K * h.transpose()) *
bias_P_;
159 Eigen::Vector3d h = Eigen::Vector3d::Ones();
160 return (h.transpose() *
bias_P_ * h)(0, 0);
170 const double adc_norm = adc_code / 1e9;
171 const double v = std::max(adc_norm, 1e-6);
173 Eigen::VectorXd phi(
N);
177 adc_norm * adc_norm +
physical_.alpha1 * T * adc_norm +
physical_.alpha2 * H * adc_norm;
178 phi(3) = adc_norm * adc_norm * adc_norm +
physical_.beta1 * T * adc_norm * adc_norm +
180 phi(4) = std::sqrt(v) +
physical_.gamma1 * A * std::log(v);
182 phi(6) = adc_norm * T * H;
183 phi(7) = adc_norm * adc_norm * V * M;
184 phi(8) = A * adc_norm * adc_norm * adc_norm;
188std::pair<Eigen::VectorXd, Eigen::MatrixXd>
190 const std::vector<CalibrationPoint>& points)
const {
191 if (points.size() < 3)
194 const int n =
static_cast<int>(points.size());
195 Eigen::MatrixXd Phi(
n,
N);
196 Eigen::VectorXd p_obs(
n);
197 Eigen::VectorXd weights(
n);
198 for (
int i = 0; i <
n; ++i) {
200 points[
static_cast<size_t>(i)].env)
202 p_obs(i) = points[
static_cast<size_t>(i)].pressure;
203 double u = points[
static_cast<size_t>(i)].uncertainty;
207 Eigen::MatrixXd Phi_T_W = Phi.transpose() * weights.asDiagonal();
208 Eigen::MatrixXd cov_inv = Phi_T_W * Phi + Eigen::MatrixXd::Identity(
N,
N) * 1e-6;
209 Eigen::LDLT<Eigen::MatrixXd> ldlt(cov_inv);
210 if (ldlt.info() != Eigen::Success)
212 Eigen::VectorXd
rhs = Phi_T_W * p_obs;
214 Eigen::MatrixXd I = Eigen::MatrixXd::Identity(
N,
N);
215 Eigen::MatrixXd
theta_cov = ldlt.solve(I);
220 const std::vector<CalibrationPoint>& new_points) {
221 if (new_points.empty())
224 Eigen::MatrixXd I = Eigen::MatrixXd::Identity(
N,
N) * 1e-6;
226 Eigen::MatrixXd lik_cov = theta_cov_tls + I;
227 Eigen::LDLT<Eigen::MatrixXd> ldlt_prior(prior_cov);
228 Eigen::LDLT<Eigen::MatrixXd> ldlt_lik(lik_cov);
229 if (ldlt_prior.info() != Eigen::Success || ldlt_lik.info() != Eigen::Success)
231 Eigen::MatrixXd prior_prec = ldlt_prior.solve(Eigen::MatrixXd::Identity(
N,
N));
232 Eigen::MatrixXd lik_prec = ldlt_lik.solve(Eigen::MatrixXd::Identity(
N,
N));
233 Eigen::MatrixXd post_prec = prior_prec + lik_prec;
234 Eigen::LDLT<Eigen::MatrixXd> ldlt_post(post_prec);
235 if (ldlt_post.info() != Eigen::Success)
237 Eigen::MatrixXd post_cov = ldlt_post.solve(Eigen::MatrixXd::Identity(
N,
N));
238 Eigen::VectorXd post_mean = post_cov * (prior_prec *
theta_mean_ + lik_prec * theta_tls);
239 return {post_mean, post_cov};
242std::pair<Eigen::VectorXd, Eigen::MatrixXd>
245 Eigen::VectorXd Pphi =
rls_P_ * phi;
247 if (std::abs(denom) < 1e-18)
249 Eigen::VectorXd K = Pphi / denom;
251 Eigen::VectorXd theta_new =
theta_mean_ + K * prediction_error;
253 Eigen::MatrixXd theta_cov_new =
rls_P_ + Eigen::MatrixXd::Identity(
N,
N) * 0.001;
254 return {theta_new, theta_cov_new};
258 const std::vector<CalibrationPoint>& points)
const {
262 std::vector<CalibrationPoint> recent(points.end() -
glr_window_size_, points.end());
264 double log_lik_current = 0;
265 for (
const auto& point : recent) {
268 double res = point.pressure - pred;
270 log_lik_current += -0.5 * (
res *
res / var + std::log(2.0 * std::acos(-1.0) * var));
275 double log_lik_unc = 0;
276 for (
const auto& point : recent) {
278 double pred = phi.dot(theta_tls);
279 double res = point.pressure - pred;
281 log_lik_unc += -0.5 * (
res *
res / var + std::log(2.0 * std::acos(-1.0) * var));
284 double glr = 2.0 * (log_lik_unc - log_lik_current);
294 v_min = std::min(v_min,
p.adc_code);
295 v_max = std::max(v_max,
p.adc_code);
297 const double delta_v = std::max(v_max - v_min, 1e6);
298 const double exp_cap = 100.0;
299 auto penalty = [&](
double d) {
300 double exp_val = std::exp(std::min(2.0 * d, exp_cap));
303 if (adc_code < v_min) {
304 double d = (v_min - adc_code) / delta_v;
307 if (adc_code > v_max) {
308 double d = (adc_code - v_max) / delta_v;
315 const double adc_norm = adc_code / 1e9;
319 return sigma * sigma;
326 auto now = std::chrono::steady_clock::now();
328 dt = std::clamp(dt, 0.001, 10.0);
337 double parameter_variance = (phi.transpose() *
theta_cov_ * phi)(0, 0);
341 Eigen::VectorXd ev(5);
343 double env_var = (ev.transpose() *
env_var_env_ * ev)(0, 0);
344 const double adc_norm = adc_code / 1e9;
345 double interaction_var =
349 meas_var + parameter_variance + extrap_var + bias_var + env_var + interaction_var;
355 constexpr double kPsiAbsMax = 25000.0;
356 constexpr double kPsiNegMin = -3000.0;
357 if (!std::isfinite(predicted))
360 predicted = std::clamp(predicted, kPsiNegMin, kPsiAbsMax);
361 return {predicted, sigma};
379 double residual = point.
pressure - pred_before;
383 dt_point = std::clamp(dt_point, 0.001, 10.0);
385 double sigma_meas = std::max(point.
uncertainty, 1e-4);
390 double contrib_rest =
theta_mean_.tail(
N - 1).dot(phi.tail(
N - 1));
393 bias_P_ = Eigen::Matrix3d::Identity() * 0.01;
410 Eigen::VectorXd Pphi =
rls_P_ * phi;
412 if (std::abs(denom) > 1e-18) {
413 Eigen::VectorXd K = Pphi / denom;
429 if (cov.rows() ==
N && cov.cols() ==
N)
434 if (
p.rows() ==
N &&
p.cols() ==
N)
std::pair< double, bool > generalized_likelihood_ratio_test(const std::vector< CalibrationPoint > &points) const
std::pair< Eigen::VectorXd, Eigen::MatrixXd > bayesian_update(const std::vector< CalibrationPoint > &new_points)
double forgetting_factor_
Eigen::VectorXd theta_mean() const
Eigen::MatrixXd env_var_env_
double bias_variance() const
double last_bias_update_t_
std::deque< double > recent_uncertainties_
std::vector< CalibrationPoint > calibration_points_
Eigen::MatrixXd env_var_interaction_
Eigen::VectorXd environmental_basis(double adc_code, const EnvironmentalState &env) const
std::optional< NoiseCoefficients > noise_coeffs_
void seed_from_factory_cubic(const PTCalibrationCoeffs &c)
Seed θ by ridge LS so Φ·θ ≈ factory cubic P(adc) on an ADC grid (same env as streaming).
RobustCalibrationFramework(int sensor_id)
std::deque< double > recent_residuals_
void bias_update(double residual, double sigma_meas, double dt)
Eigen::Vector3d bias_tau_
double bias_contribution() const
static constexpr size_t kCalibrationPointsMax
struct fsw::calibration::RobustCalibrationFramework::PhysicalParams physical_
double predict_pressure_psi(double adc_code)
Mean PSI for streaming (default env, used by calibration_service).
void set_theta_from_polynomial(const std::vector< double > &poly_coeffs, double adc_norm_min, double adc_norm_scale)
void set_theta_cov_for_restore(const Eigen::MatrixXd &cov)
std::pair< double, double > predict_pressure_with_uncertainty(double adc_code, const EnvironmentalState &env)
Mean + σ; updates bias propagation clock.
void set_rls_P_for_restore(const Eigen::MatrixXd &p)
void add_calibration_point(const CalibrationPoint &point)
Zero / capture / reference — full Python add_calibration_point logic.
double max_abs_error_vs_factory(const PTCalibrationCoeffs &factory_cubic) const
Max |Φ·θ − P_factory(adc)| over a dense ADC grid — sanity-check saved adjustments....
std::pair< Eigen::VectorXd, Eigen::MatrixXd > recursive_least_squares_update(const CalibrationPoint &point)
Eigen::VectorXd theta_mean_
std::chrono::steady_clock::time_point last_pred_time_
std::pair< Eigen::VectorXd, Eigen::MatrixXd > total_least_squares_calibration(const std::vector< CalibrationPoint > &points) const
void set_theta_mean_for_restore(const Eigen::VectorXd &t)
double compute_measurement_variance(double adc_code, double tau) const
Eigen::MatrixXd theta_cov() const
double extrapolation_variance(double adc_code) const
void bias_propagate(double dt)
Eigen::MatrixXd theta_cov_
double bias_process_noise_
void set_noise_coeffs(const NoiseCoefficients &c, double tau0=0.01)
double env_variance_base_
*brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer *sa const json_pointer & rhs
constexpr int kFactorySeedGridPoints
constexpr double kRidgeLambda
double steady_secs(std::chrono::steady_clock::time_point t0, std::chrono::steady_clock::time_point t1)
int32_t clamp_adc_i32(double adc_d)
Output smoothing for calibrated PT pressure, per sensor uid (ESP32 and LabJack T7 alike).
double measurement_uncertainty(double tau, const NoiseCoefficients &coeffs, double voltage=0.0, double sigma_base=1e-3)
Measurement σ from paper Section 7 (Python measurement_uncertainty).
Motorized ball valve position: raw ADC counts -> % open.
*brief user defined string literal for JSON values *sa std::size_t n
Environmental state [T, H, V, A, M] — scripts/calibration/robust_calibration.py.
Noise coefficients from Allan variance fit (scripts/calibration/allan_variance.py).
PT Calibration Coefficients.
double calculate_pressure(int32_t adc_code) const
Calculate pressure (psi) from ADC code.