{"record":{"id":"f3a0b9f7aa4df0dc","repo":"google-research/timesfm","slug":"forecast-horizon-length-inferred-from-the-dynamic","errorCode":null,"errorMessage":"Forecast horizon length inferred from the dynamic covariates is longer than themax_horizon defined in the forecast config: {test_lens[-1]} > {self.forecast_config.max_horizon=}.","messagePattern":"Forecast horizon length inferred from the dynamic covariates is longer than themax_horizon defined in the forecast config: (.+?) > (.+?)\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/timesfm/timesfm_2p5/timesfm_2p5_base.py","lineNumber":288,"sourceCode":"        train_lens.append(max(0, input_len - self.model.p))\n      elif xreg_mode == \"xreg + timesfm\":\n        train_lens.append(input_len)\n      else:\n        raise ValueError(f\"Unsupported mode: {xreg_mode}\")\n\n      if dynamic_numerical_covariates:\n        test_lens.append(\n          len(list(dynamic_numerical_covariates.values())[0][i]) - input_len\n        )\n      elif dynamic_categorical_covariates:\n        test_lens.append(\n          len(list(dynamic_categorical_covariates.values())[0][i]) - input_len\n        )\n      else:\n        test_lens.append(self.forecast_config.max_horizon)\n\n      if test_lens[-1] > self.forecast_config.max_horizon:\n        raise ValueError(\n          \"Forecast horizon length inferred from the dynamic covariates is longer than the\"\n          f\"max_horizon defined in the forecast config: {test_lens[-1]} > {self.forecast_config.max_horizon=}.\"\n        )\n\n    # Prepare the covariates into train and test.\n    train_dynamic_numerical_covariates = collections.defaultdict(list)\n    test_dynamic_numerical_covariates = collections.defaultdict(list)\n    train_dynamic_categorical_covariates = collections.defaultdict(list)\n    test_dynamic_categorical_covariates = collections.defaultdict(list)\n    for covariates, train_covariates, test_covariates in (\n      (\n        dynamic_numerical_covariates,\n        train_dynamic_numerical_covariates,\n        test_dynamic_numerical_covariates,\n      ),\n      (\n        dynamic_categorical_covariates,\n        train_dynamic_categorical_covariates,","sourceCodeStart":270,"sourceCodeEnd":306,"githubUrl":"https://github.com/google-research/timesfm/blob/331c6d33cb1ac2611de3056d0ac7164aab6301eb/src/timesfm/timesfm_2p5/timesfm_2p5_base.py#L270-L306","documentation":"When dynamic covariates are given, the forecast horizon per input is inferred as len(covariate_series) - len(input). If that inferred length exceeds the `max_horizon` baked into the compiled ForecastConfig, the compiled decode kernel cannot produce that many steps, so the library raises ValueError. max_horizon is fixed at compile time and cannot be exceeded at inference.","triggerScenarios":"Supplying dynamic covariate arrays longer than len(input) + max_horizon; compiling with a small max_horizon then passing covariates expecting a longer future; differing covariate lengths across inputs where one exceeds max_horizon.","commonSituations":"Covariate forecasts generated by another model extended further out than the compiled horizon; forgetting to recompile after increasing the desired forecast length; unit mismatch (hourly covariates vs daily expectations).","solutions":["Recompile with `ForecastConfig(max_horizon=<larger value>)` to cover the inferred horizon","Trim the dynamic covariate series so len(cov) - len(input) <= max_horizon","Note max_horizon is also bounded by context_limit (see the context+horizon check), so keep max_context + max_horizon within it"],"exampleFix":"// before\nconfig = ForecastConfig(max_context=512, max_horizon=64, return_backcast=True)\nmodel.compile(forecast_config=config)\nmodel.forecast_with_covariates(..., dynamic_numerical_covariates={'x': arr_of_len_1024})\n// after\nconfig = ForecastConfig(max_context=512, max_horizon=256, return_backcast=True)\nmodel.compile(forecast_config=config)\nmodel.forecast_with_covariates(..., dynamic_numerical_covariates={'x': arr_of_len_1024})","handlingStrategy":"validation","validationCode":"first_dyn = next(iter(dynamic_numerical_covariates.values())) if dynamic_numerical_covariates else next(iter(dynamic_categorical_covariates.values()))\ninferred_horizon = len(first_dyn[0]) - len(inputs[0])\nif inferred_horizon > model.forecast_config.max_horizon:\n    model.compile(forecast_config=dataclasses.replace(model.forecast_config, max_horizon=inferred_horizon))","typeGuard":"def horizon_fits(covariate_len: int, input_len: int, max_horizon: int) -> bool:\n    return (covariate_len - input_len) <= max_horizon","tryCatchPattern":null,"preventionTips":["Trim covariate series to input_len + desired_horizon before calling","Compile with max_horizon covering the maximum covariate-implied horizon","Keep max_context + max_horizon within model.config.context_limit when raising max_horizon"],"tags":["python","validation","covariates","horizon-limit"],"backgroundTag":"horizon-exceeds-limit","analyzedSha":"331c6d33cb1ac2611de3056d0ac7164aab6301eb","analyzedAt":"2026-08-29T01:04:23.138Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}