1+ #pragma once
2+ #include < Eigen/Dense>
3+ #include < iostream>
4+ #include < numeric>
5+
6+ namespace cppx ::ar_models {
7+
8+ template <int order> class ARModel {
9+ public:
10+ using Vector = Eigen::Vector<double , order>;
11+
12+ ARModel () = default ;
13+ ARModel (double intercept, double noise_variance) : c_(intercept), sigma2_(noise_variance){};
14+
15+ [[nodiscard]] double intercept () const noexcept { return c_; }
16+ [[nodiscard]] double noise () const noexcept { return sigma2_; }
17+ [[nodiscard]] const Vector &coefficients () const noexcept { return phi_; }
18+
19+ void set_coefficients (const Vector &phi) { phi_ = phi; }
20+ void set_intercept (double c) { c_ = c; }
21+ void set_noise (double noise) { sigma2_ = noise; }
22+
23+ double forecast_one_step (const std::vector<double > &hist) const {
24+ if (static_cast <int >(hist.size ()) < order) {
25+ throw std::invalid_argument (" History shorter than model order" );
26+ }
27+ double y = c_;
28+ for (int i = 0 ; i < order; ++i) {
29+ y += phi_ (i) * hist[i];
30+ }
31+ return y;
32+ }
33+
34+ private:
35+ Vector phi_;
36+ double c_ = 0.0 ;
37+ double sigma2_ = 1.0 ;
38+ };
39+
40+ template <int order> ARModel<order> fit_ar_ols (const std::vector<double > &x) {
41+ if (static_cast <int >(x.size ()) <= order) {
42+ throw std::invalid_argument (" Time series too short for AR(order)" );
43+ }
44+
45+ const int T = static_cast <int >(x.size ());
46+ const int n = T - order;
47+
48+ // Build the design system
49+ Eigen::MatrixXd X (n, order + 1 );
50+ Eigen::VectorXd Y (n);
51+
52+ for (int t = 0 ; t < n; ++t) {
53+ Y (t) = x[order + t];
54+ X (t, 0 ) = 1.0 ;
55+ for (int j = 0 ; j < order; ++j) {
56+ X (t, j + 1 ) = x[order + t - 1 - j];
57+ }
58+ }
59+
60+ // Solve least squares
61+ Eigen::VectorXd beta = X.colPivHouseholderQr ().solve (Y);
62+ // beta(0) = intercept, beta(1..order) = AR coefficients
63+
64+ // Compute residual variance
65+ Eigen::VectorXd resid = Y - X * beta;
66+ double sigma2 = resid.squaredNorm () / static_cast <double >(n - (order + 1 ));
67+
68+ // Create AR(p)
69+ typename ARModel<order>::Vector phi;
70+ for (int j = 0 ; j < order; ++j) {
71+ phi (j) = beta (j + 1 );
72+ }
73+
74+ ARModel<order> model;
75+ model.set_coefficients (phi);
76+ model.set_intercept (beta (0 ));
77+ model.set_noise (sigma2);
78+
79+ return model;
80+ }
81+
82+ inline double _sample_mean (const std::vector<double > &x) {
83+ double mu = std::accumulate (x.begin (), x.end (), 0.0 ) / x.size ();
84+ return mu;
85+ }
86+ inline double _sample_autocov (const std::vector<double > &x, int k) {
87+ const int T = static_cast <int >(x.size ());
88+ if (k >= T) {
89+ throw std::invalid_argument (" lag too large" );
90+ }
91+ const double mu = _sample_mean (x);
92+ double acc = 0.0 ;
93+ for (int t = k; t < T; ++t) {
94+ acc += (x[t] - mu) * (x[t - k] - mu);
95+ }
96+ return acc / static_cast <double >(T);
97+ }
98+
99+ template <int order> ARModel<order> fir_ar_yule_walkter (const std::vector<double > &x) {
100+ static_assert (order >= 1 , " Yule–Walker needs order >= 1" );
101+ if (static_cast <int >(x.size ()) <= order) {
102+ throw std::invalid_argument (" Time series too short for AR(order)" );
103+ }
104+
105+ // r[0..order] sample autocovariances
106+ std::array<double , order + 1 > r{};
107+ for (int k = 0 ; k <= order; ++k) {
108+ r[k] = _sample_autocov (x, k);
109+ }
110+
111+ // Levinson–Durbin recursion
112+ typename ARModel<order>::Vector a;
113+ a.setZero ();
114+ double E = r[0 ];
115+ if (std::abs (E) < 1e-15 ) {
116+ throw std::runtime_error (" Zero variance" );
117+ }
118+
119+ for (int m = 1 ; m <= order; ++m) {
120+ double acc = r[m];
121+ for (int j = 1 ; j < m; ++j)
122+ acc -= a (j - 1 ) * r[m - j];
123+ const double kappa = acc / E;
124+
125+ // update a (reflection update)
126+ typename ARModel<order>::Vector a_new = a;
127+ a_new (m - 1 ) = kappa;
128+ for (int j = 1 ; j < m; ++j) {
129+ a_new (j - 1 ) = a (j - 1 ) - kappa * a (m - j - 1 );
130+ }
131+ a = a_new;
132+
133+ E *= (1.0 - kappa * kappa);
134+ if (E <= 0 ) {
135+ throw std::runtime_error (" Non-positive innovation variance in recursion" );
136+ }
137+ }
138+
139+ // Compute intercept so that unconditional mean matches sample mean (stationarity assumption)
140+ const double xbar = _sample_mean (x);
141+ const double one_minus_sum = 1.0 - a.sum ();
142+ const double c = one_minus_sum * xbar;
143+
144+ ARModel<order> model;
145+ model.set_coefficients (a);
146+ model.set_intercept (c);
147+ model.set_noise (E);
148+
149+ return model;
150+ }
151+
152+ } // namespace cppx::ar_models
0 commit comments