@@ -1205,6 +1205,99 @@ fn four_theta_deseasonalize(
12051205 }
12061206}
12071207
1208+ // ── DOT Hybrid objective (buildThetaLine + SES optimize + combine + MAE) ──
1209+ #[ pyfunction]
1210+ fn dot_hybrid_objective (
1211+ y : PyReadonlyArray1 < f64 > ,
1212+ theta_line0 : PyReadonlyArray1 < f64 > ,
1213+ theta : f64 ,
1214+ is_additive : bool ,
1215+ ) -> PyResult < f64 > {
1216+ let y = y. as_array ( ) ;
1217+ let t0 = theta_line0. as_array ( ) ;
1218+ let n = y. len ( ) ;
1219+ if n < 3 {
1220+ return Ok ( f64:: MAX ) ;
1221+ }
1222+
1223+ let mut theta_line = vec ! [ 0.0f64 ; n] ;
1224+ if is_additive {
1225+ for i in 0 ..n {
1226+ theta_line[ i] = theta * y[ i] + ( 1.0 - theta) * t0[ i] ;
1227+ }
1228+ } else {
1229+ for i in 0 ..n {
1230+ let yv = if y[ i] > 1e-10 { y[ i] } else { 1e-10 } ;
1231+ let tv = if t0[ i] > 1e-10 { t0[ i] } else { 1e-10 } ;
1232+ theta_line[ i] = yv. powf ( theta) * tv. powf ( 1.0 - theta) ;
1233+ }
1234+ }
1235+
1236+ let alpha = optimize_alpha_bounded ( & theta_line) ;
1237+
1238+ let mut filtered = vec ! [ 0.0f64 ; n] ;
1239+ filtered[ 0 ] = theta_line[ 0 ] ;
1240+ for t in 1 ..n {
1241+ filtered[ t] = alpha * theta_line[ t] + ( 1.0 - alpha) * filtered[ t - 1 ] ;
1242+ }
1243+
1244+ let mut mae = 0.0f64 ;
1245+ if is_additive {
1246+ let w = 1.0 / theta. max ( 1.0 ) ;
1247+ let w2 = 1.0 - w;
1248+ for i in 0 ..n {
1249+ let fitted = w * filtered[ i] + w2 * t0[ i] ;
1250+ mae += ( y[ i] - fitted) . abs ( ) ;
1251+ }
1252+ } else {
1253+ let inv = 1.0 / theta. max ( 1.0 ) ;
1254+ let inv2 = 1.0 - inv;
1255+ for i in 0 ..n {
1256+ let fv = if filtered[ i] > 1e-10 { filtered[ i] } else { 1e-10 } ;
1257+ let tv = if t0[ i] > 1e-10 { t0[ i] } else { 1e-10 } ;
1258+ let fitted = fv. powf ( inv) * tv. powf ( inv2) ;
1259+ mae += ( y[ i] - fitted) . abs ( ) ;
1260+ }
1261+ }
1262+
1263+ Ok ( mae / n as f64 )
1264+ }
1265+
1266+ fn ses_sse_inner ( y : & [ f64 ] , alpha : f64 ) -> f64 {
1267+ let mut level = y[ 0 ] ;
1268+ let mut sse = 0.0f64 ;
1269+ for t in 1 ..y. len ( ) {
1270+ let error = y[ t] - level;
1271+ sse += error * error;
1272+ level = alpha * y[ t] + ( 1.0 - alpha) * level;
1273+ }
1274+ sse
1275+ }
1276+
1277+ fn optimize_alpha_bounded ( y : & [ f64 ] ) -> f64 {
1278+ let n = y. len ( ) ;
1279+ if n < 3 {
1280+ return 0.3 ;
1281+ }
1282+
1283+ let ( mut lo, mut hi) = ( 0.001f64 , 0.999f64 ) ;
1284+ let gr = ( 5.0f64 . sqrt ( ) - 1.0 ) / 2.0 ;
1285+
1286+ for _ in 0 ..50 {
1287+ let x1 = hi - gr * ( hi - lo) ;
1288+ let x2 = lo + gr * ( hi - lo) ;
1289+ let f1 = ses_sse_inner ( y, x1) ;
1290+ let f2 = ses_sse_inner ( y, x2) ;
1291+ if f1 < f2 {
1292+ hi = x2;
1293+ } else {
1294+ lo = x1;
1295+ }
1296+ }
1297+
1298+ ( lo + hi) / 2.0
1299+ }
1300+
12081301#[ pymodule]
12091302fn _core ( m : & Bound < ' _ , PyModule > ) -> PyResult < ( ) > {
12101303 m. add_function ( wrap_pyfunction ! ( ets_filter, m) ?) ?;
@@ -1232,5 +1325,6 @@ fn _core(m: &Bound<'_, PyModule>) -> PyResult<()> {
12321325 m. add_function ( wrap_pyfunction ! ( esn_reservoir_update, m) ?) ?;
12331326 m. add_function ( wrap_pyfunction ! ( four_theta_fitted, m) ?) ?;
12341327 m. add_function ( wrap_pyfunction ! ( four_theta_deseasonalize, m) ?) ?;
1328+ m. add_function ( wrap_pyfunction ! ( dot_hybrid_objective, m) ?) ?;
12351329 Ok ( ( ) )
12361330}
0 commit comments