{"owner":"google-research","repo":"timesfm","hasSkills":true,"totalSkillsCount":5,"totalTokensCount":12172,"categories":["root-instruction","anthropic-skill"],"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md","timesfm-forecasting/references/system_requirements.md","timesfm-forecasting/references/data_preparation.md","timesfm-forecasting/references/api_reference.md","timesfm-forecasting/SKILL.md"],"skills":{"AGENTS.md":"# TimesFM — Agent Entry Point\n\nThis repository ships a first-party **Agent Skill** for TimesFM at:\n\n```\ntimesfm-forecasting/\n└── SKILL.md    ← read this for the full skill\n```\n\n## Install the skill\n\nCopy the skill directory into your agent's skills folder:\n\n```bash\n# Cursor / Claude Code / OpenCode / Codex (global install)\ncp -r timesfm-forecasting/ ~/.cursor/skills/\ncp -r timesfm-forecasting/ ~/.claude/skills/\n\n# Or project-level\ncp -r timesfm-forecasting/ .cursor/skills/\n```\n\nAny agent that supports the open [Agent Skills standard](https://agentskills.io) will discover it automatically.\n\n## Working in this repo\n\nIf you are developing TimesFM itself (not using it), the source lives in `src/timesfm/`.\nArchived v1/v2 code and notebooks are in `v1/`.\n\nRun tests:\n\n```bash\npytest v1/tests/\n```\n\nSee `README.md` for full developer setup.\n","timesfm-forecasting/references/system_requirements.md":"# System Requirements for TimesFM\n\n## Hardware Tiers\n\nTimesFM can run on a variety of hardware configurations. This guide helps you\nchoose the right setup and tune performance for your machine.\n\n### How Context Limits Are Determined\n\nThe `max_context` values in each tier are **conservative recommendations** based on memory-performance tradeoffs, not hard limits. TimesFM 2.5 supports up to 16,384 context points, but smaller values are recommended for most use cases.\n\n**Why 512 and 1024?**\n\n| Factor | 512 Context | 1024 Context |\n|--------|-------------|--------------|\n| **Memory per 1000 series** | ~100 MB | ~200 MB |\n| **Typical Use Case** | Daily data, ~1-2 years | Daily data, ~2-3 years |\n| **Inference Speed** | Faster | Moderate |\n| **Hardware** | 4-8 GB RAM | 16 GB RAM or GPU |\n\n**Memory Formula**: `RAM ≈ model_weights + 0.5 GB + (0.2 MB × num_series × context_length / 1000)`\n\nWhere:\n- `model_weights` = ~800 MB (TimesFM 2.5)\n- `context_length` = your `max_context` value\n- `num_series` = number of time series in your batch\n\n**You can use larger contexts** if your hardware supports it:\n- **Up to 2048**: Requires ~16 GB RAM for moderate batch sizes\n- **Up to 4096**: Requires GPU or 32+ GB RAM\n- **Up to 16384**: Maximum supported, requires significant memory\n\nSee [Data Preparation Guide](data_preparation.md) for context length recommendations by data frequency.\n\n### Tier 1: Minimal (CPU-Only, 4–8 GB RAM)\n\n- **Use case**: Light exploration, single-series forecasting, prototyping\n- **Model**: TimesFM 2.5 (200M) only\n- **Batch size**: `per_core_batch_size=4`\n- **Context**: Limit `max_context=512`\n- **Expected speed**: ~2–5 seconds per 100-point series\n\n```python\nmodel.compile(timesfm.ForecastConfig(\n    max_context=512,\n    max_horizon=128,\n    per_core_batch_size=4,\n    normalize_inputs=True,\n    use_continuous_quantile_head=True,\n    fix_quantile_crossing=True,\n))\n```\n\n### Tier 2: Standard (CPU 16 GB or GPU 4–8 GB VRAM)\n\n- **Use case**: Batch forecasting (dozens of series), evaluation, production prototypes\n- **Model**: TimesFM 2.5 (200M)\n- **Batch size**: `per_core_batch_size=32` (CPU) or `64` (GPU)\n- **Context**: `max_context=1024`\n- **Expected speed**: ~0.5–1 second per 100-point series (GPU)\n\n```python\nmodel.compile(timesfm.ForecastConfig(\n    max_context=1024,\n    max_horizon=256,\n    per_core_batch_size=64,\n    normalize_inputs=True,\n    use_continuous_quantile_head=True,\n    fix_quantile_crossing=True,\n))\n```\n\n### Tier 3: Production (GPU 16+ GB VRAM or Apple Silicon 32+ GB)\n\n- **Use case**: Large-scale batch forecasting (thousands of series), long context\n- **Model**: TimesFM 2.5 (200M)\n- **Batch size**: `per_core_batch_size=128–256`\n- **Context**: `max_context=4096` or higher\n- **Expected speed**: ~0.1–0.3 seconds per 100-point series\n\n```python\nmodel.compile(timesfm.ForecastConfig(\n    max_context=4096,\n    max_horizon=256,\n    per_core_batch_size=128,\n    normalize_inputs=True,\n    use_continuous_quantile_head=True,\n    fix_quantile_crossing=True,\n))\n```\n\n### Tier 4: Legacy Models (v1.0/v2.0 — 500M parameters)\n\n- **⚠️ WARNING**: TimesFM v2.0 (500M) requires **≥ 16 GB RAM** (CPU) or **≥ 8 GB VRAM** (GPU)\n- **⚠️ WARNING**: TimesFM v1.0 legacy JAX version may require **≥ 32 GB RAM**\n- **Recommendation**: Unless you specifically need a legacy checkpoint, use TimesFM 2.5\n\n## Memory Estimation\n\n### CPU Memory (RAM)\n\nApproximate RAM usage during inference:\n\n| Component | TimesFM 2.5 (200M) | TimesFM 2.0 (500M) |\n| --------- | ------------------- | ------------------- |\n| Model weights | ~800 MB | ~2 GB |\n| Runtime overhead | ~500 MB | ~1 GB |\n| Input/output buffers | ~200 MB per 1000 series | ~500 MB per 1000 series |\n| **Total (small batch)** | **~1.5 GB** | **~3.5 GB** |\n| **Total (large batch)** | **~3 GB** | **~6 GB** |\n\n**Formula**: `RAM ≈ model_weights + 0.5 GB + (0.2 MB × num_series × context_length / 1000)`\n\n### GPU Memory (VRAM)\n\n| Component | TimesFM 2.5 (200M) |\n| --------- | ------------------- |\n| Model weights | ~800 MB |\n| KV cache + activations | ~200–500 MB (scales with context) |\n| Batch buffers | ~100 MB per 100 series at context=1024 |\n| **Total (batch=32)** | **~1.2 GB** |\n| **Total (batch=128)** | **~1.8 GB** |\n| **Total (batch=256)** | **~2.5 GB** |\n\n### Disk Space\n\n| Item | Size |\n| ---- | ---- |\n| TimesFM 2.5 safetensors | ~800 MB |\n| Hugging Face cache overhead | ~200 MB |\n| **Total download** | **~1 GB** |\n\nModel weights are downloaded once from Hugging Face Hub and cached in\n`~/.cache/huggingface/` (or `$HF_HOME`).\n\n## GPU Selection Guide\n\n### NVIDIA GPUs (CUDA)\n\n| GPU | VRAM | Recommended batch | Notes |\n| --- | ---- | ----------------- | ----- |\n| RTX 3060 | 12 GB | 64 | Good entry-level |\n| RTX 3090 / 4090 | 24 GB | 256 | Excellent for production |\n| A100 (40 GB) | 40 GB | 512 | Cloud/HPC |\n| A100 (80 GB) | 80 GB | 1024 | Cloud/HPC |\n| T4 | 16 GB | 128 | Cloud (Colab, AWS) |\n| V100 | 16–32 GB | 128–256 | Cloud |\n\n### Apple Silicon (MPS)\n\n| Chip | Unified Memory | Recommended batch | Notes |\n| ---- | -------------- | ----------------- | ----- |\n| M1 | 8–16 GB | 16–32 | Works, slower than CUDA |\n| M1 Pro/Max | 16–64 GB | 32–128 | Good performance |\n| M2/M3/M4 Pro/Max | 18–128 GB | 64–256 | Excellent |\n\n### CPU Only\n\nWorks on any CPU with sufficient RAM. Expect 5–20× slower than GPU.\n\n## Python and Package Requirements\n\n| Requirement | Minimum | Recommended |\n| ----------- | ------- | ----------- |\n| Python | 3.10 | 3.12+ |\n| numpy | 1.26.4 | latest |\n| torch | 2.0.0 | latest |\n| huggingface_hub | 0.23.0 | latest |\n| safetensors | 0.5.3 | latest |\n\n### Optional Dependencies\n\n| Package | Purpose | Install |\n| ------- | ------- | ------- |\n| jax | Flax backend | `pip install jax[cuda]` |\n| flax | Flax backend | `pip install flax` |\n| scikit-learn | XReg covariates | `pip install scikit-learn` |\n\n## Operating System Compatibility\n\n| OS | Status | Notes |\n| -- | ------ | ----- |\n| Linux (Ubuntu 20.04+) | ✅ Fully supported | Best performance with CUDA |\n| macOS 13+ (Ventura) | ✅ Fully supported | MPS acceleration on Apple Silicon |\n| Windows 11 + WSL2 | ✅ Supported | Use WSL2 for best experience |\n| Windows (native) | ⚠️ Partial | PyTorch works, some edge cases |\n\n## Troubleshooting\n\n### Out of Memory (OOM)\n\n```python\n# Reduce batch size\nmodel.compile(timesfm.ForecastConfig(\n    per_core_batch_size=4,  # Start very small\n    max_context=512,        # Reduce context\n    ...\n))\n\n# Process in chunks\nfor i in range(0, len(inputs), 50):\n    chunk = inputs[i:i+50]\n    p, q = model.forecast(horizon=H, inputs=chunk)\n```\n\n### Slow Inference on CPU\n\n```python\n# Ensure matmul precision is set\nimport torch\ntorch.set_float32_matmul_precision(\"high\")\n\n# Use smaller context\nmodel.compile(timesfm.ForecastConfig(\n    max_context=256,  # Shorter context = faster\n    ...\n))\n```\n\n### Model Download Fails\n\n```bash\n# Set a different cache directory\nexport HF_HOME=/path/with/more/space\n\n# Or download manually\nhuggingface-cli download google/timesfm-2.5-200m-pytorch\n```\n","timesfm-forecasting/references/data_preparation.md":"# Data Preparation for TimesFM\n\n## Input Format\n\nTimesFM accepts a **list of 1-D numpy arrays**. Each array represents one\nunivariate time series.\n\n```python\ninputs = [\n    np.array([1.0, 2.0, 3.0, 4.0, 5.0]),       # Series 1\n    np.array([10.0, 20.0, 15.0, 25.0]),          # Series 2 (different length)\n    np.array([100.0, 110.0, 105.0, 115.0, 120.0, 130.0]),  # Series 3\n]\n```\n\n### Key Properties\n\n- **Variable lengths**: Series in the same batch can have different lengths\n- **Float values**: Use `np.float32` or `np.float64`\n- **1-D only**: Each array must be 1-dimensional (not 2-D matrix rows)\n- **NaN handling**: Leading NaNs are stripped; internal NaNs are linearly interpolated\n\n## Loading from Common Formats\n\n### CSV — Single Series (Long Format)\n\n```python\nimport pandas as pd\nimport numpy as np\n\ndf = pd.read_csv(\"data.csv\", parse_dates=[\"date\"])\nvalues = df[\"value\"].values.astype(np.float32)\ninputs = [values]\n```\n\n### CSV — Multiple Series (Wide Format)\n\n```python\ndf = pd.read_csv(\"data.csv\", parse_dates=[\"date\"], index_col=\"date\")\ninputs = [df[col].dropna().values.astype(np.float32) for col in df.columns]\n```\n\n### CSV — Long Format with ID Column\n\n```python\ndf = pd.read_csv(\"data.csv\", parse_dates=[\"date\"])\ninputs = []\nfor series_id, group in df.groupby(\"series_id\"):\n    values = group.sort_values(\"date\")[\"value\"].values.astype(np.float32)\n    inputs.append(values)\n```\n\n### Pandas DataFrame\n\n```python\n# Single column\ninputs = [df[\"temperature\"].values.astype(np.float32)]\n\n# Multiple columns\ninputs = [df[col].dropna().values.astype(np.float32) for col in numeric_cols]\n```\n\n### Numpy Arrays\n\n```python\n# 2-D array (rows = series, cols = time steps)\ndata = np.load(\"timeseries.npy\")  # shape (N, T)\ninputs = [data[i] for i in range(data.shape[0])]\n\n# Or from 1-D\ninputs = [np.sin(np.linspace(0, 10, 200))]\n```\n\n### Excel\n\n```python\ndf = pd.read_excel(\"data.xlsx\", sheet_name=\"Sheet1\")\ninputs = [df[col].dropna().values.astype(np.float32) for col in df.select_dtypes(include=[np.number]).columns]\n```\n\n### Parquet\n\n```python\ndf = pd.read_parquet(\"data.parquet\")\ninputs = [df[col].dropna().values.astype(np.float32) for col in df.select_dtypes(include=[np.number]).columns]\n```\n\n### JSON\n\n```python\nimport json\n\nwith open(\"data.json\") as f:\n    data = json.load(f)\n\n# Assumes {\"series_name\": [values...], ...}\ninputs = [np.array(values, dtype=np.float32) for values in data.values()]\n```\n\n## NaN Handling\n\nTimesFM handles NaN values automatically:\n\n### Leading NaNs\n\nStripped before feeding to the model:\n\n```python\n# Input:  [NaN, NaN, 1.0, 2.0, 3.0]\n# Actual: [1.0, 2.0, 3.0]\n```\n\n### Internal NaNs\n\nLinearly interpolated:\n\n```python\n# Input:  [1.0, NaN, 3.0, NaN, NaN, 6.0]\n# Actual: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]\n```\n\n### Trailing NaNs\n\n**Not handled** — drop them before passing to the model:\n\n```python\nvalues = df[\"value\"].values.astype(np.float32)\n# Remove trailing NaNs\nwhile len(values) > 0 and np.isnan(values[-1]):\n    values = values[:-1]\ninputs = [values]\n```\n\n### Best Practice\n\n```python\ndef clean_series(arr: np.ndarray) -> np.ndarray:\n    \"\"\"Clean a time series for TimesFM input.\"\"\"\n    arr = np.asarray(arr, dtype=np.float32)\n    # Remove trailing NaNs\n    while len(arr) > 0 and np.isnan(arr[-1]):\n        arr = arr[:-1]\n    # Replace inf with NaN (will be interpolated)\n    arr[np.isinf(arr)] = np.nan\n    return arr\n\ninputs = [clean_series(df[col].values) for col in cols]\n```\n\n## Context Length Considerations\n\n| Context Length | Use Case | Notes |\n| -------------- | -------- | ----- |\n| 64–256 | Quick prototyping | Minimal context, fast |\n| 256–512 | Daily data, ~1 year | Good balance |\n| 512–1024 | Daily data, ~2-3 years | Standard production |\n| 1024–4096 | Hourly data, weekly patterns | More context = better |\n| 4096–16384 | High-frequency, long patterns | TimesFM 2.5 maximum |\n\n**Rule of thumb**: Provide at least 3–5 full cycles of the dominant pattern\n(e.g., for weekly seasonality with daily data, provide at least 21–35 days).\n\n## Covariates (XReg)\n\nTimesFM 2.5 supports exogenous variables through the `forecast_with_covariates()` API.\n\n### Types of Covariates\n\n| Type | Description | Example |\n| ---- | ----------- | ------- |\n| **Dynamic numerical** | Time-varying numeric features | Temperature, price, promotion spend |\n| **Dynamic categorical** | Time-varying categorical features | Day of week, holiday flag |\n| **Static categorical** | Fixed per-series features | Store ID, region, product category |\n\n### Preparing Covariates\n\nEach covariate must have length `context + horizon` for each series:\n\n```python\nimport numpy as np\n\ncontext_len = 100   # length of historical data\nhorizon = 24        # forecast horizon\ntotal_len = context_len + horizon\n\n# Dynamic numerical: temperature forecast for each series\ntemp = [\n    np.random.randn(total_len).astype(np.float32),  # Series 1\n    np.random.randn(total_len).astype(np.float32),  # Series 2\n]\n\n# Dynamic categorical: day of week (0-6) for each series\ndow = [\n    np.tile(np.arange(7), total_len // 7 + 1)[:total_len],  # Series 1\n    np.tile(np.arange(7), total_len // 7 + 1)[:total_len],  # Series 2\n]\n\n# Static categorical: one label per series\nregions = [\"east\", \"west\"]\n\n# Forecast with covariates\npoint, quantiles = model.forecast_with_covariates(\n    inputs=[values1, values2],\n    dynamic_numerical_covariates={\"temperature\": temp},\n    dynamic_categorical_covariates={\"day_of_week\": dow},\n    static_categorical_covariates={\"region\": regions},\n    xreg_mode=\"xreg + timesfm\",\n)\n```\n\n### XReg Modes\n\n| Mode | Description |\n| ---- | ----------- |\n| `\"xreg + timesfm\"` | Covariates processed first, then combined with TimesFM forecast |\n| `\"timesfm + xreg\"` | TimesFM forecast first, then adjusted by covariates |\n\n## Common Data Issues\n\n### Issue: Series too short\n\nTimesFM needs at least 1 data point, but more context = better forecasts.\n\n```python\nMIN_LENGTH = 32  # Practical minimum for meaningful forecasts\n\ninputs = [\n    arr for arr in raw_inputs\n    if len(arr[~np.isnan(arr)]) >= MIN_LENGTH\n]\n```\n\n### Issue: Series with constant values\n\nConstant series may produce NaN or zero-width prediction intervals:\n\n```python\nfor i, arr in enumerate(inputs):\n    if np.std(arr[~np.isnan(arr)]) < 1e-10:\n        print(f\"⚠️ Series {i} is constant — forecast will be flat\")\n```\n\n### Issue: Extreme outliers\n\nLarge outliers can destabilize forecasts even with normalization:\n\n```python\ndef clip_outliers(arr: np.ndarray, n_sigma: float = 5.0) -> np.ndarray:\n    \"\"\"Clip values beyond n_sigma standard deviations.\"\"\"\n    mu = np.nanmean(arr)\n    sigma = np.nanstd(arr)\n    if sigma > 0:\n        arr = np.clip(arr, mu - n_sigma * sigma, mu + n_sigma * sigma)\n    return arr\n```\n\n### Issue: Mixed frequencies in batch\n\nTimesFM handles each series independently, so you can mix frequencies:\n\n```python\ninputs = [\n    daily_sales,      # 365 points\n    weekly_revenue,   # 52 points\n    monthly_users,    # 24 points\n]\n# All forecasted in one batch — TimesFM handles different lengths\npoint, q = model.forecast(horizon=12, inputs=inputs)\n```\n\nHowever, the `horizon` is shared. If you need different horizons per series,\nforecast in separate calls.\n","timesfm-forecasting/references/api_reference.md":"# TimesFM API Reference\n\n## Model Classes\n\n### `timesfm.TimesFM_2p5_200M_torch`\n\nThe primary model class for TimesFM 2.5 (200M parameters, PyTorch backend).\n\n#### `from_pretrained()`\n\n```python\nmodel = timesfm.TimesFM_2p5_200M_torch.from_pretrained(\n    \"google/timesfm-2.5-200m-pytorch\",\n    cache_dir=None,         # Optional: custom cache directory\n    force_download=True,    # Re-download even if cached\n)\n```\n\n| Parameter | Type | Default | Description |\n| --------- | ---- | ------- | ----------- |\n| `model_id` | str | `\"google/timesfm-2.5-200m-pytorch\"` | Hugging Face model ID |\n| `revision` | str \\| None | None | Specific model revision |\n| `cache_dir` | str \\| Path \\| None | None | Custom cache directory |\n| `force_download` | bool | True | Force re-download of weights |\n\n**Returns**: Initialized `TimesFM_2p5_200M_torch` instance (not yet compiled).\n\n#### `compile()`\n\nCompiles the model with the given forecast configuration. **Must be called before `forecast()`.**\n\n```python\nmodel.compile(\n    timesfm.ForecastConfig(\n        max_context=1024,\n        max_horizon=256,\n        normalize_inputs=True,\n        per_core_batch_size=32,\n        use_continuous_quantile_head=True,\n        force_flip_invariance=True,\n        infer_is_positive=True,\n        fix_quantile_crossing=True,\n    )\n)\n```\n\n**Raises**: Nothing (but `forecast()` will raise `RuntimeError` if not compiled).\n\n#### `forecast()`\n\nRun inference on one or more time series.\n\n```python\npoint_forecast, quantile_forecast = model.forecast(\n    horizon=24,\n    inputs=[array1, array2, ...],\n)\n```\n\n| Parameter | Type | Description |\n| --------- | ---- | ----------- |\n| `horizon` | int | Number of future steps to forecast |\n| `inputs` | list[np.ndarray] | List of 1-D numpy arrays (each is a time series) |\n\n**Returns**: `tuple[np.ndarray, np.ndarray]`\n\n- `point_forecast`: shape `(batch_size, horizon)` — median (0.5 quantile)\n- `quantile_forecast`: shape `(batch_size, horizon, 10)` — [mean, q10, q20, ..., q90]\n\n**Raises**: `RuntimeError` if model is not compiled.\n\n**Key behaviors**:\n\n- Leading NaN values are stripped automatically\n- Internal NaN values are linearly interpolated\n- Series longer than `max_context` are truncated (last `max_context` points used)\n- Series shorter than `max_context` are padded\n\n#### `forecast_with_covariates()`\n\nRun inference with exogenous variables (requires `timesfm[xreg]`).\n\n```python\npoint, quantiles = model.forecast_with_covariates(\n    inputs=inputs,\n    dynamic_numerical_covariates={\"temp\": [temp_array1, temp_array2]},\n    dynamic_categorical_covariates={\"dow\": [dow_array1, dow_array2]},\n    static_categorical_covariates={\"region\": [\"east\", \"west\"]},\n    xreg_mode=\"xreg + timesfm\",\n)\n```\n\n| Parameter | Type | Description |\n| --------- | ---- | ----------- |\n| `inputs` | list[np.ndarray] | Target time series |\n| `dynamic_numerical_covariates` | dict[str, list[np.ndarray]] | Time-varying numeric features |\n| `dynamic_categorical_covariates` | dict[str, list[np.ndarray]] | Time-varying categorical features |\n| `static_categorical_covariates` | dict[str, list[str]] | Fixed categorical features per series |\n| `xreg_mode` | str | `\"xreg + timesfm\"` or `\"timesfm + xreg\"` |\n\n**Note**: Dynamic covariates must have length `context + horizon` for each series.\n\n---\n\n## `timesfm.ForecastConfig`\n\nImmutable dataclass controlling all forecast behavior.\n\n```python\n@dataclasses.dataclass(frozen=True)\nclass ForecastConfig:\n    max_context: int = 0\n    max_horizon: int = 0\n    normalize_inputs: bool = False\n    per_core_batch_size: int = 1\n    use_continuous_quantile_head: bool = False\n    force_flip_invariance: bool = True\n    infer_is_positive: bool = True\n    fix_quantile_crossing: bool = False\n    return_backcast: bool = False\n    quantiles: list[float] = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]\n    decode_index: int = 5\n```\n\n### Parameter Details\n\n#### `max_context` (int, default=0)\n\nMaximum number of historical time points to use as context.\n\n- **0**: Use the model's maximum supported context (16,384 for v2.5)\n- **N**: Truncate series to last N points\n- **Best practice**: Set to the length of your longest series, or 512–2048 for speed\n\n#### `max_horizon` (int, default=0)\n\nMaximum forecast horizon.\n\n- **0**: Use the model's maximum\n- **N**: Forecasts up to N steps (can still call `forecast(horizon=M)` where M ≤ N)\n- **Best practice**: Set to your expected maximum forecast length\n\n#### `normalize_inputs` (bool, default=False)\n\nWhether to z-normalize each series before feeding to the model.\n\n- **True** (RECOMMENDED): Normalizes each series to zero mean, unit variance\n- **False**: Raw values are passed directly\n- **When False is OK**: Only if your series are already normalized or very close to scale 1.0\n\n#### `per_core_batch_size` (int, default=1)\n\nNumber of series processed per device in each batch.\n\n- Increase for throughput, decrease if OOM\n- See `references/system_requirements.md` for recommended values by hardware\n\n#### `use_continuous_quantile_head` (bool, default=False)\n\nUse the 30M-parameter continuous quantile head for better interval calibration.\n\n- **True** (RECOMMENDED): More accurate prediction intervals, especially for longer horizons\n- **False**: Uses fixed quantile buckets (faster but less accurate intervals)\n\n#### `force_flip_invariance` (bool, default=True)\n\nEnsures the model satisfies `f(-x) = -f(x)`.\n\n- **True** (RECOMMENDED): Mathematical consistency — forecasts are invariant to sign flip\n- **False**: Slightly faster but may produce asymmetric forecasts\n\n#### `infer_is_positive` (bool, default=True)\n\nAutomatically detect if all input values are positive and clamp forecasts ≥ 0.\n\n- **True**: Safe for sales, demand, counts, prices, volumes\n- **False**: Required for temperature, returns, PnL, any series that can be negative\n\n#### `fix_quantile_crossing` (bool, default=False)\n\nPost-process quantiles to ensure monotonicity (q10 ≤ q20 ≤ ... ≤ q90).\n\n- **True** (RECOMMENDED): Guarantees well-ordered quantiles\n- **False**: Slightly faster but quantiles may occasionally cross\n\n#### `return_backcast` (bool, default=False)\n\nReturn the model's reconstruction of the input (backcast) in addition to forecast.\n\n- **True**: Used for covariate workflows and diagnostics\n- **False**: Only return forecast\n\n---\n\n## Available Model Checkpoints\n\n| Model ID | Version | Params | Backend | Context |\n| -------- | ------- | ------ | ------- | ------- |\n| `google/timesfm-2.5-200m-pytorch` | 2.5 | 200M | PyTorch | 16,384 |\n| `google/timesfm-2.5-200m-flax` | 2.5 | 200M | JAX/Flax | 16,384 |\n| `google/timesfm-2.5-200m-transformers` | 2.5 | 200M | Transformers | 16,384 |\n| `google/timesfm-2.0-500m-pytorch` | 2.0 | 500M | PyTorch | 2,048 |\n| `google/timesfm-2.0-500m-jax` | 2.0 | 500M | JAX | 2,048 |\n| `google/timesfm-1.0-200m-pytorch` | 1.0 | 200M | PyTorch | 2,048 |\n| `google/timesfm-1.0-200m` | 1.0 | 200M | JAX | 2,048 |\n\n---\n\n## Output Shape Reference\n\n| Output | Shape | Description |\n| ------ | ----- | ----------- |\n| `point_forecast` | `(B, H)` | Median forecast for B series, H steps |\n| `quantile_forecast` | `(B, H, 10)` | Full quantile distribution |\n| `quantile_forecast[:,:,0]` | `(B, H)` | Mean |\n| `quantile_forecast[:,:,1]` | `(B, H)` | 10th percentile |\n| `quantile_forecast[:,:,5]` | `(B, H)` | 50th percentile (= point_forecast) |\n| `quantile_forecast[:,:,9]` | `(B, H)` | 90th percentile |\n\nWhere `B` = batch size (number of input series), `H` = forecast horizon.\n\n---\n\n---\n\n## Memory Estimation\n\nBefore running forecasts on large datasets, estimate memory requirements:\n\n### Formula\n\n```mermaid\nblock-beta\n    columns 3\n    ram[\"Total RAM Required\"] model[\"Model Weights<br/>~0.8 GB\"] overhead[\"Runtime Overhead<br/>~0.5 GB\"] buffers[\"I/O Buffers<br/>~0.2 MB per 1000 series<br/>per 1000 context\"]\n    \n    ram --> model\n    ram --> overhead\n    ram --> buffers\n```\n\n**Formula**:  \n`RAM (GB) ≈ 0.8 + 0.5 + (0.0002 × num_series × context_length)`\n\n**Variables**:\n- `num_series`: Number of time series in your batch\n- `context_length`: Your `max_context` value (or max series length)\n- `batch_size`: Your `per_core_batch_size` (affects parallel processing overhead)\n\n### Quick Reference\n\n| Dataset Size | Context=512 | Context=1024 | Context=2048 |\n|--------------|-------------|--------------|--------------|\n| 100 series | ~1.4 GB | ~1.5 GB | ~1.7 GB |\n| 1,000 series | ~1.9 GB | ~2.3 GB | ~3.1 GB |\n| 10,000 series| ~9.0 GB | ~17.0 GB | ~33.0 GB |\n\n### Using the Preflight Checker\n\n```bash\npython scripts/check_system.py \\\n  --num-series 1000 \\\n  --context-length 1024 \\\n  --batch-size 32\n```\n\nThis validates both system requirements AND dataset fit before loading the model.\n\n### Reducing Memory Usage\n\nIf your dataset is too large:\n\n1. **Reduce context length**: Use `max_context=512` instead of 1024+ (50% reduction)\n2. **Process in chunks**: Split large batches into smaller groups:\n\n```python\nCHUNK_SIZE = 100\nfor i in range(0, len(inputs), CHUNK_SIZE):\n    chunk = inputs[i:i+CHUNK_SIZE]\n    point, quantiles = model.forecast(horizon=H, inputs=chunk)\n    # Save chunk results\n```\n\n3. **Reduce batch size**: Lower `per_core_batch_size` (slower but less memory)\n4. **Use CPU**: If GPU OOM, the model will automatically fall back to CPU\n\n\n## Error Handling\n\n| Error | Cause | Fix |\n| ----- | ----- | --- |\n| `RuntimeError: Model is not compiled` | Called `forecast()` before `compile()` | Call `model.compile(ForecastConfig(...))` first |\n| `torch.cuda.OutOfMemoryError` | Batch too large for GPU | Reduce `per_core_batch_size` |\n| `ValueError: inputs must be list` | Passed array instead of list | Wrap in list: `[array]` |\n| `HfHubHTTPError` | Download failed | Check internet, set `HF_HOME` to writable dir |\n","timesfm-forecasting/SKILL.md":"---\nname: timesfm-forecasting\ndescription: >\n  Zero-shot time series forecasting with Google's TimesFM foundation model. Use this\n  skill when forecasting ANY univariate time series — sales, sensor readings, stock prices,\n  energy demand, patient vitals, weather, or scientific measurements — without training a\n  custom model. Supports both basic forecasting and advanced covariate forecasting (XReg)\n  with dynamic and static exogenous variables. Automatically checks system RAM/GPU before\n  loading the model, validates dataset fit before processing, supports CSV/DataFrame/array\n  inputs, and returns point forecasts with calibrated prediction intervals. Includes a\n  preflight system checker script that MUST be run before first use to verify the machine\n  can load the model and handle your specific dataset.\nlicense: Apache-2.0\nmetadata:\n  author: Clayton Young (@borealBytes)\n  version: \"1.0.0\"\n---\n\n# TimesFM Forecasting\n\n## Overview\n\nTimesFM (Time Series Foundation Model) is a pretrained decoder-only foundation model\ndeveloped by Google Research for time-series forecasting. It works **zero-shot** — feed it\nany univariate time series and it returns point forecasts with calibrated quantile\nprediction intervals, no training required.\n\nThis skill includes a **mandatory preflight system checker** that verifies RAM, GPU memory,\nand disk space before the model is ever loaded so the agent never crashes the user's machine.\n\n> **Key numbers**: TimesFM 2.5 uses 200M parameters (~800 MB on disk, ~1.5 GB in RAM on\n> CPU, ~1 GB VRAM on GPU). The archived v1/v2 500M-parameter model needs ~32 GB RAM.\n> Always run the system checker first.\n\n## When to Use This Skill\n\nUse this skill when:\n\n- Forecasting **any univariate time series** (sales, demand, sensor, vitals, price, weather)\n- You need **zero-shot forecasting** without training a custom model\n- You want **probabilistic forecasts** with calibrated prediction intervals (quantiles)\n- You have time series of **any length** (the model handles 1–16,384 context points)\n- You need to **batch-forecast** hundreds or thousands of series efficiently\n- You want a **foundation model** approach instead of hand-tuning ARIMA/ETS parameters\n- You need **covariate forecasting** with exogenous variables (price, promotions, holidays, day-of-week effects) → use `forecast_with_covariates()` (TimesFM 2.5 + `pip install timesfm[xreg]`)\n\n\nDo **not** use this skill when:\n\n- You need classical statistical models with coefficient interpretation → use `statsmodels`\n- You need time series classification or clustering → use `aeon`\n- You need multivariate vector autoregression or Granger causality → use `statsmodels`\n- Your data is tabular (not temporal) → use `scikit-learn`\n- You cannot install optional dependencies → XReg requires scikit-learn and JAX\n\n\n> **Note on Anomaly Detection**: TimesFM does not have built-in anomaly detection, but you\n> can use the **quantile forecasts as prediction intervals** — values outside the 90% CI\n> (q10–q90) are statistically unusual. See `examples/anomaly-detection/` for a full example.\n\n## ⚠️ Mandatory Preflight: System Requirements Check\n\n**CRITICAL — ALWAYS run the system checker before loading the model for the first time.**\n\n```bash\npython scripts/check_system.py\n```\n\nThis script checks:\n\n1. **Available RAM** — warns if below 4 GB, blocks if below 2 GB\n2. **GPU availability** — detects CUDA/MPS devices and VRAM\n3. **Disk space** — verifies room for the ~800 MB model download\n4. **Python version** — requires 3.10+\n5. **Existing installation** — checks if `timesfm` and `torch` are installed\n\n> **Note:** Model weights are **NOT stored in this repository**. TimesFM weights (~800 MB)\n> download on-demand from HuggingFace on first use and cache in `~/.cache/huggingface/`.\n\n```mermaid\nflowchart TD\n    start[\"🚀 Run check_system.py\"] --> ram{\"RAM ≥ 4 GB?\"}\n    ram -->|\"Yes\"| gpu{\"GPU available?\"}\n    ram -->|\"No (2-4 GB)\"| warn_ram[\"⚠️ Warning: tight RAM<br/>CPU-only, small batches\"]\n    ram -->|\"No (< 2 GB)\"| block[\"🛑 BLOCKED<br/>Insufficient memory\"]\n    warn_ram --> disk\n    gpu -->|\"CUDA / MPS\"| vram{\"VRAM ≥ 2 GB?\"}\n    gpu -->|\"CPU only\"| cpu_ok[\"✅ CPU mode<br/>Slower but works\"]\n    vram -->|\"Yes\"| gpu_ok[\"✅ GPU mode<br/>Fast inference\"]\n    vram -->|\"No\"| cpu_ok\n    gpu_ok --> disk{\"Disk ≥ 2 GB free?\"}\n    cpu_ok --> disk\n    disk -->|\"Yes\"| ready[\"✅ READY<br/>Safe to load model\"]\n    disk -->|\"No\"| block_disk[\"🛑 BLOCKED<br/>Need space for weights\"]\n```\n\n### Dataset Preflight (NEW)\n\nBefore loading your actual data, verify it will fit in memory:\n\n```bash\n# Quick estimate for your dataset\npython scripts/check_system.py \\\n  --num-series 1000 \\\n  --context-length 1024 \\\n  --horizon 24 \\\n  --batch-size 32 \\\n  --estimate-only\n```\n\nThis will show you the estimated memory requirements and warn if your dataset is too large.\n\n**Memory Estimation Formula**:\n`RAM ≈ 0.8 GB (model) + 0.5 GB (overhead) + (0.2 MB × num_series × context_length / 1000)`\n\n**Example Outputs**:\n\n✅ **Dataset Fits**:\n```\nTotal CPU memory: 2.34 GB\nTotal GPU memory: 2.15 GB\n```\n\n⚠️ **Dataset Too Large**:\n```\nDataset requires ~12.5 GB RAM but system has 8.0 GB.\nTry: context_length=512 or process in chunks of 50 series.\n```\n\n### Hardware Requirements by Model Version\n\n| Model | Parameters | RAM (CPU) | VRAM (GPU) | Disk | Context |\n| ----- | ---------- | --------- | ---------- | ---- | ------- |\n| **TimesFM 2.5** (recommended) | 200M | ≥ 4 GB | ≥ 2 GB | ~800 MB | up to 16,384 |\n| TimesFM 2.0 (archived) | 500M | ≥ 16 GB | ≥ 8 GB | ~2 GB | up to 2,048 |\n| TimesFM 1.0 (archived) | 200M | ≥ 8 GB | ≥ 4 GB | ~800 MB | up to 2,048 |\n\n> **Recommendation**: Always use TimesFM 2.5 unless you have a specific reason to use an\n> older checkpoint. It is smaller, faster, and supports 8× longer context.\n\n## 🔧 Installation\n\n### Step 1: Verify System (always first)\n\n```bash\npython scripts/check_system.py\n```\n\n### Step 2: Install TimesFM\n\n```bash\n# Using uv (fast)\nuv pip install timesfm[torch]\n\n# Or using pip\npip install timesfm[torch]\n\n# For JAX/Flax backend (faster on TPU/GPU)\nuv pip install timesfm[flax]\n```\n\n### Step 3: Install PyTorch for Your Hardware\n\n```bash\n# CUDA 12.1 (NVIDIA GPU)\npip install torch>=2.0.0 --index-url https://download.pytorch.org/whl/cu121\n\n# CPU only\npip install torch>=2.0.0 --index-url https://download.pytorch.org/whl/cpu\n\n# Apple Silicon (MPS)\npip install torch>=2.0.0  # MPS support is built-in\n```\n\n## 🎯 Quick Start\n\n### Minimal Example\n\n```python\nimport torch, numpy as np, timesfm\n\ntorch.set_float32_matmul_precision(\"high\")\n\nmodel = timesfm.TimesFM_2p5_200M_torch.from_pretrained(\n    \"google/timesfm-2.5-200m-pytorch\"\n)\nmodel.compile(timesfm.ForecastConfig(\n    max_context=1024, max_horizon=256, normalize_inputs=True,\n    use_continuous_quantile_head=True, force_flip_invariance=True,\n    infer_is_positive=True, fix_quantile_crossing=True,\n))\n\npoint, quantiles = model.forecast(horizon=24, inputs=[\n    np.sin(np.linspace(0, 20, 200)),  # any 1-D array\n])\n# point.shape == (1, 24)         — median forecast\n# quantiles.shape == (1, 24, 10) — 10th–90th percentile bands\n```\n\n### Forecast with Covariates (XReg)\n\nTimesFM 2.5+ supports exogenous variables through `forecast_with_covariates()`.\nRequires `pip install timesfm[xreg]`.\n\n```python\npoint, quantiles = model.forecast_with_covariates(\n    inputs=inputs,\n    dynamic_numerical_covariates={\"price\": price_arrays},\n    dynamic_categorical_covariates={\"holiday\": holiday_arrays},\n    static_categorical_covariates={\"region\": region_labels},\n    xreg_mode=\"xreg + timesfm\",  # or \"timesfm + xreg\"\n)\n```\n\n### Anomaly Detection (via Quantile Intervals)\n\n```python\npoint, q = model.forecast(horizon=H, inputs=[values])\n\nlower_90 = q[0, :, 1]  # 10th percentile\nupper_90 = q[0, :, 9]  # 90th percentile\n\nactual = test_values\nanomalies = (actual < lower_90) | (actual > upper_90)\n```\n\n| Severity | Condition | Interpretation |\n| -------- | --------- | -------------- |\n| **Normal** | Inside 80% CI | Expected behavior |\n| **Warning** | Outside 80% CI | Unusual but possible |\n| **Critical** | Outside 90% CI | Statistically rare (< 10% probability) |\n\n> See `examples/anomaly-detection/` for a complete worked example with visualization.\n\n## 📊 Understanding the Output\n\nTimesFM returns `(point_forecast, quantile_forecast)`:\n\n- **`point_forecast`**: shape `(batch, horizon)` — the median (0.5 quantile)\n- **`quantile_forecast`**: shape `(batch, horizon, 10)` — ten quantile slices:\n\n| Index | Quantile | Use |\n| ----- | -------- | --- |\n| 0 | Mean | Average prediction |\n| 1 | 0.1 | Lower bound of 80% PI |\n| 2 | 0.2 | Lower bound of 60% PI |\n| **5** | **0.5** | **Median (= `point_forecast`)** |\n| 8 | 0.8 | Upper bound of 60% PI |\n| 9 | 0.9 | Upper bound of 80% PI |\n\n```python\npoint, q = model.forecast(horizon=H, inputs=data)\n\nlower_80 = q[:, :, 1]  # 10th percentile\nupper_80 = q[:, :, 9]  # 90th percentile\nmedian   = q[:, :, 5]\n```\n\n## 🔧 ForecastConfig Reference\n\nAll forecasting behavior is controlled by `timesfm.ForecastConfig`:\n\n```python\ntimesfm.ForecastConfig(\n    max_context=1024,                    # Max context window\n    max_horizon=256,                     # Max forecast horizon\n    normalize_inputs=True,               # RECOMMENDED — prevents scale instability\n    per_core_batch_size=32,              # Tune for memory\n    use_continuous_quantile_head=True,   # Better quantile accuracy for long horizons\n    force_flip_invariance=True,          # Ensures f(-x) = -f(x)\n    infer_is_positive=True,              # Clamp forecasts ≥ 0 when all inputs > 0\n    fix_quantile_crossing=True,          # Ensure q10 ≤ q20 ≤ ... ≤ q90\n    return_backcast=False,               # Return backcast (for covariate workflows)\n)\n```\n\n| Parameter | Default | When to Change |\n| --------- | ------- | -------------- |\n| `max_context` | 0 | Set to match your longest historical window |\n| `normalize_inputs` | False | **Always set True** |\n| `use_continuous_quantile_head` | False | **Set True** for calibrated PIs |\n| `infer_is_positive` | True | Set False for series that can be negative |\n| `fix_quantile_crossing` | False | **Set True** for monotonic quantiles |\n\nSee `references/api_reference.md` for the complete parameter reference.\n\n## 📋 Common Workflows\n\n### Single Series Forecast\n\n```python\nimport torch, numpy as np, pandas as pd, timesfm, matplotlib\nmatplotlib.use(\"Agg\")\nimport matplotlib.pyplot as plt\n\ntorch.set_float32_matmul_precision(\"high\")\nmodel = timesfm.TimesFM_2p5_200M_torch.from_pretrained(\n    \"google/timesfm-2.5-200m-pytorch\"\n)\nmodel.compile(timesfm.ForecastConfig(\n    max_context=512, max_horizon=52, normalize_inputs=True,\n    use_continuous_quantile_head=True, fix_quantile_crossing=True,\n))\n\ndf = pd.read_csv(\"weekly_demand.csv\", parse_dates=[\"week\"])\nvalues = df[\"demand\"].values.astype(np.float32)\n\npoint, quantiles = model.forecast(horizon=52, inputs=[values])\n\nfig, ax = plt.subplots(figsize=(12, 5))\nax.plot(values[-104:], label=\"Historical\")\nx_fc = range(len(values[-104:]), len(values[-104:]) + 52)\nax.plot(x_fc, point[0], label=\"Forecast\", color=\"tab:orange\")\nax.fill_between(x_fc, quantiles[0, :, 1], quantiles[0, :, 9],\n                alpha=0.2, color=\"tab:orange\", label=\"80% PI\")\nax.legend(); ax.set_title(\"52-Week Demand Forecast\")\nplt.tight_layout(); plt.savefig(\"forecast.png\", dpi=150)\n```\n\n### Batch Forecasting (Many Series)\n\n```python\ndf = pd.read_csv(\"all_stores.csv\", parse_dates=[\"date\"], index_col=\"date\")\ninputs = [df[col].dropna().values.astype(np.float32) for col in df.columns]\n\npoint, quantiles = model.forecast(horizon=30, inputs=inputs)\n\nimport json\nresults = {col: {\"forecast\": point[i].tolist(),\n                 \"lower_80\": quantiles[i, :, 1].tolist(),\n                 \"upper_80\": quantiles[i, :, 9].tolist()}\n           for i, col in enumerate(df.columns)}\nwith open(\"batch_forecasts.json\", \"w\") as f:\n    json.dump(results, f, indent=2)\n```\n\n### Evaluate Forecast Accuracy\n\n```python\nH = 24\ntrain, actual = values[:-H], values[-H:]\npoint, quantiles = model.forecast(horizon=H, inputs=[train])\npred = point[0]\n\nmae  = np.mean(np.abs(actual - pred))\nrmse = np.sqrt(np.mean((actual - pred) ** 2))\nmape = np.mean(np.abs((actual - pred) / actual)) * 100\ncoverage = np.mean((actual >= quantiles[0, :, 1]) & (actual <= quantiles[0, :, 9])) * 100\n\nprint(f\"MAE: {mae:.2f} | RMSE: {rmse:.2f} | MAPE: {mape:.1f}% | 80% PI Coverage: {coverage:.1f}%\")\n```\n\n## ⚙️ Performance Tuning\n\n```python\n# Always set on Ampere+ GPUs (A100, RTX 3090+)\ntorch.set_float32_matmul_precision(\"high\")\n\n# Batch size guidelines:\n# GPU 8 GB VRAM:  per_core_batch_size=64\n# GPU 16 GB VRAM: per_core_batch_size=128\n# CPU 8 GB RAM:   per_core_batch_size=8\n# CPU 16 GB RAM:  per_core_batch_size=32\n\n# Memory-constrained: process in chunks\nCHUNK = 50\nresults = []\nfor i in range(0, len(inputs), CHUNK):\n    p, q = model.forecast(horizon=H, inputs=inputs[i:i+CHUNK])\n    results.append((p, q))\n```\n\n## 📚 Available Scripts\n\n### `scripts/check_system.py`\n\nMandatory preflight checker — run before first model load.\nNow includes **dataset-aware memory estimation** to prevent OOM errors before loading your data.\n\n```bash\n# Basic system check\npython scripts/check_system.py\n\n# Check if your specific dataset will fit\npython scripts/check_system.py \\\n  --num-series 1000 \\\n  --context-length 1024 \\\n  --horizon 24 \\\n  --batch-size 32\n\n# Quick memory estimate without system checks\npython scripts/check_system.py \\\n  --num-series 5000 \\\n  --context-length 2048 \\\n  --estimate-only\n```\n\n**What it checks**:\n\n1. **Available RAM** — warns if below 4 GB, blocks if below 2 GB\n2. **GPU availability** — detects CUDA/MPS devices and VRAM\n3. **Disk space** — verifies room for the ~800 MB model download\n4. **Python version** — requires 3.10+\n5. **Existing installation** — checks if `timesfm` and `torch` are installed\n6. **Dataset fit** (NEW) — estimates memory for your specific dataset and warns if it won't fit\n\n### `scripts/forecast_csv.py`\n\nEnd-to-end CSV forecasting CLI.\n\n```bash\npython scripts/forecast_csv.py input.csv \\\n    --horizon 24 \\\n    --date-col date \\\n    --value-cols sales,revenue \\\n    --output forecasts.csv\n```\n\n## 📖 Reference Documentation\n\n| File | Contents |\n| ---- | -------- |\n| `references/system_requirements.md` | Hardware tiers, GPU/CPU selection, memory estimation |\n| `references/api_reference.md` | Full `ForecastConfig` docs, output shapes, model options |\n| `references/data_preparation.md` | Input formats, NaN handling, CSV loading, covariate setup |\n\n## 🧪 Examples\n\n| Example | Directory | What It Demonstrates |\n| ------- | --------- | -------------------- |\n| **Global Temperature Forecast** | `examples/global-temperature/` | Basic `model.forecast()`, CSV → PNG → GIF pipeline |\n| **Anomaly Detection** | `examples/anomaly-detection/` | Two-phase detrend + Z-score + quantile PI, 2-panel viz |\n| **Covariates (XReg)** | `examples/covariates-forecasting/` | `forecast_with_covariates()`, 2×2 shared-axis viz |\n\n```bash\n# Run all three examples:\ncd examples/global-temperature && python run_forecast.py && python visualize_forecast.py\ncd examples/anomaly-detection  && python detect_anomalies.py\ncd examples/covariates-forecasting && python demo_covariates.py\n```\n\n### Expected Outputs\n\n| Example | Key output files | Acceptance criteria |\n| ------- | ---------------- | ------------------- |\n| global-temperature | `output/forecast_output.json`, `output/forecast_visualization.png` | `point_forecast` has 12 values; PNG shows context + forecast + PI bands |\n| anomaly-detection | `output/anomaly_detection.json`, `output/anomaly_detection.png` | Sep 2023 flagged CRITICAL (z ≥ 3.0) |\n| covariates-forecasting | `output/sales_with_covariates.csv`, `output/covariates_data.png` | 108 rows (3 stores × 36 weeks); distinct price arrays per store |\n\n## Model Versions\n\n| Version | Params | Context | Status | HuggingFace checkpoint |\n| ------- | ------ | ------- | ------ | ---------------------- |\n| **2.5** | 200M | 16,384 | **Latest** | `google/timesfm-2.5-200m-pytorch` |\n| 2.0 | 500M | 2,048 | Archived | `google/timesfm-2.0-500m-pytorch` |\n| 1.0 | 200M | 2,048 | Archived | `google/timesfm-1.0-200m-pytorch` |\n\n- TimesFM 1.0/2.0: must pass `freq=[0]` for monthly data\n- TimesFM 2.5: no frequency flag — it was removed\n\n## Resources\n\n- **Paper**: [A Decoder-Only Foundation Model for Time-Series Forecasting](https://arxiv.org/abs/2310.10688) (ICML 2024)\n- **HuggingFace**: https://huggingface.co/collections/google/timesfm-release-66e4be5fdb56e960c1e482a6\n- **Google Blog**: https://research.google/blog/a-decoder-only-foundation-model-for-time-series-forecasting/\n- **BigQuery Integration**: https://cloud.google.com/bigquery/docs/timesfm-model\n\n## Quality Checklist\n\nRun after every TimesFM task before declaring success:\n\n- [ ] **Output shape** — `point_fc` is `(n_series, horizon)`, `quant_fc` is `(n_series, horizon, 10)`\n- [ ] **Quantile indices** — index 0 = mean, 1 = q10 ... 9 = q90. NOT 0 = q0.\n- [ ] **Frequency flag** — TimesFM 1.0/2.0: pass `freq=[0]` for monthly. TimesFM 2.5: omit.\n- [ ] **Series length** — context must be ≥ 32 data points.\n- [ ] **No NaN** — `np.isnan(point_fc).any()` must be False.\n- [ ] **Axes** — multiple panels sharing data must use `sharex=True`.\n- [ ] **`matplotlib.use('Agg')`** — before any pyplot import when running headless.\n- [ ] **`infer_is_positive`** — set False for temperature, financial returns, negatives.\n\n## Common Mistakes\n\n1. **Quantile index off-by-one** — `quant_fc[..., 0]` is the **mean**, not q0. q10 = index 1, q90 = index 9. Define: `IDX_Q10, IDX_Q90 = 1, 9`.\n\n2. **Variable shadowing in covariate loops** — don't use the outer loop variable as a comprehension variable when building per-series covariate dicts.\n\n3. **Wrong CSV column name** — global-temperature CSV uses `anomaly_c`, not `anomaly`. Print `df.columns` first.\n\n4. **TimesFM 2.5 required for `forecast_with_covariates()`** — TimesFM 1.0 does NOT have this method.\n\n5. **Future covariates must span the full horizon** — dynamic covariates need values for BOTH context AND forecast windows.\n\n6. **Context anomaly detection uses residuals** — detrend first, then Z-score. Raw Z-scores mislead on trending data.\n\n## Validation & Verification\n\n```bash\n# Anomaly detection regression:\npython -c \"\nimport json\nd = json.load(open('examples/anomaly-detection/output/anomaly_detection.json'))\nassert d['context_summary']['critical'] >= 1, 'Sep 2023 must be CRITICAL'\nprint('Anomaly detection: PASS')\"\n\n# Covariates regression:\npython -c \"\nimport pandas as pd\ndf = pd.read_csv('examples/covariates-forecasting/output/sales_with_covariates.csv')\nassert len(df) == 108, f'Expected 108 rows, got {len(df)}'\nprint('Covariates: PASS')\"\n```\n"},"files":{"AGENTS.md":"# TimesFM — Agent Entry Point\n\nThis repository ships a first-party **Agent Skill** for TimesFM at:\n\n```\ntimesfm-forecasting/\n└── SKILL.md    ← read this for the full skill\n```\n\n## Install the skill\n\nCopy the skill directory into your agent's skills folder:\n\n```bash\n# Cursor / Claude Code / OpenCode / Codex (global install)\ncp -r timesfm-forecasting/ ~/.cursor/skills/\ncp -r timesfm-forecasting/ ~/.claude/skills/\n\n# Or project-level\ncp -r timesfm-forecasting/ .cursor/skills/\n```\n\nAny agent that supports the open [Agent Skills standard](https://agentskills.io) will discover it automatically.\n\n## Working in this repo\n\nIf you are developing TimesFM itself (not using it), the source lives in `src/timesfm/`.\nArchived v1/v2 code and notebooks are in `v1/`.\n\nRun tests:\n\n```bash\npytest v1/tests/\n```\n\nSee `README.md` for full developer setup.\n","timesfm-forecasting/references/system_requirements.md":"# System Requirements for TimesFM\n\n## Hardware Tiers\n\nTimesFM can run on a variety of hardware configurations. This guide helps you\nchoose the right setup and tune performance for your machine.\n\n### How Context Limits Are Determined\n\nThe `max_context` values in each tier are **conservative recommendations** based on memory-performance tradeoffs, not hard limits. TimesFM 2.5 supports up to 16,384 context points, but smaller values are recommended for most use cases.\n\n**Why 512 and 1024?**\n\n| Factor | 512 Context | 1024 Context |\n|--------|-------------|--------------|\n| **Memory per 1000 series** | ~100 MB | ~200 MB |\n| **Typical Use Case** | Daily data, ~1-2 years | Daily data, ~2-3 years |\n| **Inference Speed** | Faster | Moderate |\n| **Hardware** | 4-8 GB RAM | 16 GB RAM or GPU |\n\n**Memory Formula**: `RAM ≈ model_weights + 0.5 GB + (0.2 MB × num_series × context_length / 1000)`\n\nWhere:\n- `model_weights` = ~800 MB (TimesFM 2.5)\n- `context_length` = your `max_context` value\n- `num_series` = number of time series in your batch\n\n**You can use larger contexts** if your hardware supports it:\n- **Up to 2048**: Requires ~16 GB RAM for moderate batch sizes\n- **Up to 4096**: Requires GPU or 32+ GB RAM\n- **Up to 16384**: Maximum supported, requires significant memory\n\nSee [Data Preparation Guide](data_preparation.md) for context length recommendations by data frequency.\n\n### Tier 1: Minimal (CPU-Only, 4–8 GB RAM)\n\n- **Use case**: Light exploration, single-series forecasting, prototyping\n- **Model**: TimesFM 2.5 (200M) only\n- **Batch size**: `per_core_batch_size=4`\n- **Context**: Limit `max_context=512`\n- **Expected speed**: ~2–5 seconds per 100-point series\n\n```python\nmodel.compile(timesfm.ForecastConfig(\n    max_context=512,\n    max_horizon=128,\n    per_core_batch_size=4,\n    normalize_inputs=True,\n    use_continuous_quantile_head=True,\n    fix_quantile_crossing=True,\n))\n```\n\n### Tier 2: Standard (CPU 16 GB or GPU 4–8 GB VRAM)\n\n- **Use case**: Batch forecasting (dozens of series), evaluation, production prototypes\n- **Model**: TimesFM 2.5 (200M)\n- **Batch size**: `per_core_batch_size=32` (CPU) or `64` (GPU)\n- **Context**: `max_context=1024`\n- **Expected speed**: ~0.5–1 second per 100-point series (GPU)\n\n```python\nmodel.compile(timesfm.ForecastConfig(\n    max_context=1024,\n    max_horizon=256,\n    per_core_batch_size=64,\n    normalize_inputs=True,\n    use_continuous_quantile_head=True,\n    fix_quantile_crossing=True,\n))\n```\n\n### Tier 3: Production (GPU 16+ GB VRAM or Apple Silicon 32+ GB)\n\n- **Use case**: Large-scale batch forecasting (thousands of series), long context\n- **Model**: TimesFM 2.5 (200M)\n- **Batch size**: `per_core_batch_size=128–256`\n- **Context**: `max_context=4096` or higher\n- **Expected speed**: ~0.1–0.3 seconds per 100-point series\n\n```python\nmodel.compile(timesfm.ForecastConfig(\n    max_context=4096,\n    max_horizon=256,\n    per_core_batch_size=128,\n    normalize_inputs=True,\n    use_continuous_quantile_head=True,\n    fix_quantile_crossing=True,\n))\n```\n\n### Tier 4: Legacy Models (v1.0/v2.0 — 500M parameters)\n\n- **⚠️ WARNING**: TimesFM v2.0 (500M) requires **≥ 16 GB RAM** (CPU) or **≥ 8 GB VRAM** (GPU)\n- **⚠️ WARNING**: TimesFM v1.0 legacy JAX version may require **≥ 32 GB RAM**\n- **Recommendation**: Unless you specifically need a legacy checkpoint, use TimesFM 2.5\n\n## Memory Estimation\n\n### CPU Memory (RAM)\n\nApproximate RAM usage during inference:\n\n| Component | TimesFM 2.5 (200M) | TimesFM 2.0 (500M) |\n| --------- | ------------------- | ------------------- |\n| Model weights | ~800 MB | ~2 GB |\n| Runtime overhead | ~500 MB | ~1 GB |\n| Input/output buffers | ~200 MB per 1000 series | ~500 MB per 1000 series |\n| **Total (small batch)** | **~1.5 GB** | **~3.5 GB** |\n| **Total (large batch)** | **~3 GB** | **~6 GB** |\n\n**Formula**: `RAM ≈ model_weights + 0.5 GB + (0.2 MB × num_series × context_length / 1000)`\n\n### GPU Memory (VRAM)\n\n| Component | TimesFM 2.5 (200M) |\n| --------- | ------------------- |\n| Model weights | ~800 MB |\n| KV cache + activations | ~200–500 MB (scales with context) |\n| Batch buffers | ~100 MB per 100 series at context=1024 |\n| **Total (batch=32)** | **~1.2 GB** |\n| **Total (batch=128)** | **~1.8 GB** |\n| **Total (batch=256)** | **~2.5 GB** |\n\n### Disk Space\n\n| Item | Size |\n| ---- | ---- |\n| TimesFM 2.5 safetensors | ~800 MB |\n| Hugging Face cache overhead | ~200 MB |\n| **Total download** | **~1 GB** |\n\nModel weights are downloaded once from Hugging Face Hub and cached in\n`~/.cache/huggingface/` (or `$HF_HOME`).\n\n## GPU Selection Guide\n\n### NVIDIA GPUs (CUDA)\n\n| GPU | VRAM | Recommended batch | Notes |\n| --- | ---- | ----------------- | ----- |\n| RTX 3060 | 12 GB | 64 | Good entry-level |\n| RTX 3090 / 4090 | 24 GB | 256 | Excellent for production |\n| A100 (40 GB) | 40 GB | 512 | Cloud/HPC |\n| A100 (80 GB) | 80 GB | 1024 | Cloud/HPC |\n| T4 | 16 GB | 128 | Cloud (Colab, AWS) |\n| V100 | 16–32 GB | 128–256 | Cloud |\n\n### Apple Silicon (MPS)\n\n| Chip | Unified Memory | Recommended batch | Notes |\n| ---- | -------------- | ----------------- | ----- |\n| M1 | 8–16 GB | 16–32 | Works, slower than CUDA |\n| M1 Pro/Max | 16–64 GB | 32–128 | Good performance |\n| M2/M3/M4 Pro/Max | 18–128 GB | 64–256 | Excellent |\n\n### CPU Only\n\nWorks on any CPU with sufficient RAM. Expect 5–20× slower than GPU.\n\n## Python and Package Requirements\n\n| Requirement | Minimum | Recommended |\n| ----------- | ------- | ----------- |\n| Python | 3.10 | 3.12+ |\n| numpy | 1.26.4 | latest |\n| torch | 2.0.0 | latest |\n| huggingface_hub | 0.23.0 | latest |\n| safetensors | 0.5.3 | latest |\n\n### Optional Dependencies\n\n| Package | Purpose | Install |\n| ------- | ------- | ------- |\n| jax | Flax backend | `pip install jax[cuda]` |\n| flax | Flax backend | `pip install flax` |\n| scikit-learn | XReg covariates | `pip install scikit-learn` |\n\n## Operating System Compatibility\n\n| OS | Status | Notes |\n| -- | ------ | ----- |\n| Linux (Ubuntu 20.04+) | ✅ Fully supported | Best performance with CUDA |\n| macOS 13+ (Ventura) | ✅ Fully supported | MPS acceleration on Apple Silicon |\n| Windows 11 + WSL2 | ✅ Supported | Use WSL2 for best experience |\n| Windows (native) | ⚠️ Partial | PyTorch works, some edge cases |\n\n## Troubleshooting\n\n### Out of Memory (OOM)\n\n```python\n# Reduce batch size\nmodel.compile(timesfm.ForecastConfig(\n    per_core_batch_size=4,  # Start very small\n    max_context=512,        # Reduce context\n    ...\n))\n\n# Process in chunks\nfor i in range(0, len(inputs), 50):\n    chunk = inputs[i:i+50]\n    p, q = model.forecast(horizon=H, inputs=chunk)\n```\n\n### Slow Inference on CPU\n\n```python\n# Ensure matmul precision is set\nimport torch\ntorch.set_float32_matmul_precision(\"high\")\n\n# Use smaller context\nmodel.compile(timesfm.ForecastConfig(\n    max_context=256,  # Shorter context = faster\n    ...\n))\n```\n\n### Model Download Fails\n\n```bash\n# Set a different cache directory\nexport HF_HOME=/path/with/more/space\n\n# Or download manually\nhuggingface-cli download google/timesfm-2.5-200m-pytorch\n```\n","timesfm-forecasting/references/data_preparation.md":"# Data Preparation for TimesFM\n\n## Input Format\n\nTimesFM accepts a **list of 1-D numpy arrays**. Each array represents one\nunivariate time series.\n\n```python\ninputs = [\n    np.array([1.0, 2.0, 3.0, 4.0, 5.0]),       # Series 1\n    np.array([10.0, 20.0, 15.0, 25.0]),          # Series 2 (different length)\n    np.array([100.0, 110.0, 105.0, 115.0, 120.0, 130.0]),  # Series 3\n]\n```\n\n### Key Properties\n\n- **Variable lengths**: Series in the same batch can have different lengths\n- **Float values**: Use `np.float32` or `np.float64`\n- **1-D only**: Each array must be 1-dimensional (not 2-D matrix rows)\n- **NaN handling**: Leading NaNs are stripped; internal NaNs are linearly interpolated\n\n## Loading from Common Formats\n\n### CSV — Single Series (Long Format)\n\n```python\nimport pandas as pd\nimport numpy as np\n\ndf = pd.read_csv(\"data.csv\", parse_dates=[\"date\"])\nvalues = df[\"value\"].values.astype(np.float32)\ninputs = [values]\n```\n\n### CSV — Multiple Series (Wide Format)\n\n```python\ndf = pd.read_csv(\"data.csv\", parse_dates=[\"date\"], index_col=\"date\")\ninputs = [df[col].dropna().values.astype(np.float32) for col in df.columns]\n```\n\n### CSV — Long Format with ID Column\n\n```python\ndf = pd.read_csv(\"data.csv\", parse_dates=[\"date\"])\ninputs = []\nfor series_id, group in df.groupby(\"series_id\"):\n    values = group.sort_values(\"date\")[\"value\"].values.astype(np.float32)\n    inputs.append(values)\n```\n\n### Pandas DataFrame\n\n```python\n# Single column\ninputs = [df[\"temperature\"].values.astype(np.float32)]\n\n# Multiple columns\ninputs = [df[col].dropna().values.astype(np.float32) for col in numeric_cols]\n```\n\n### Numpy Arrays\n\n```python\n# 2-D array (rows = series, cols = time steps)\ndata = np.load(\"timeseries.npy\")  # shape (N, T)\ninputs = [data[i] for i in range(data.shape[0])]\n\n# Or from 1-D\ninputs = [np.sin(np.linspace(0, 10, 200))]\n```\n\n### Excel\n\n```python\ndf = pd.read_excel(\"data.xlsx\", sheet_name=\"Sheet1\")\ninputs = [df[col].dropna().values.astype(np.float32) for col in df.select_dtypes(include=[np.number]).columns]\n```\n\n### Parquet\n\n```python\ndf = pd.read_parquet(\"data.parquet\")\ninputs = [df[col].dropna().values.astype(np.float32) for col in df.select_dtypes(include=[np.number]).columns]\n```\n\n### JSON\n\n```python\nimport json\n\nwith open(\"data.json\") as f:\n    data = json.load(f)\n\n# Assumes {\"series_name\": [values...], ...}\ninputs = [np.array(values, dtype=np.float32) for values in data.values()]\n```\n\n## NaN Handling\n\nTimesFM handles NaN values automatically:\n\n### Leading NaNs\n\nStripped before feeding to the model:\n\n```python\n# Input:  [NaN, NaN, 1.0, 2.0, 3.0]\n# Actual: [1.0, 2.0, 3.0]\n```\n\n### Internal NaNs\n\nLinearly interpolated:\n\n```python\n# Input:  [1.0, NaN, 3.0, NaN, NaN, 6.0]\n# Actual: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]\n```\n\n### Trailing NaNs\n\n**Not handled** — drop them before passing to the model:\n\n```python\nvalues = df[\"value\"].values.astype(np.float32)\n# Remove trailing NaNs\nwhile len(values) > 0 and np.isnan(values[-1]):\n    values = values[:-1]\ninputs = [values]\n```\n\n### Best Practice\n\n```python\ndef clean_series(arr: np.ndarray) -> np.ndarray:\n    \"\"\"Clean a time series for TimesFM input.\"\"\"\n    arr = np.asarray(arr, dtype=np.float32)\n    # Remove trailing NaNs\n    while len(arr) > 0 and np.isnan(arr[-1]):\n        arr = arr[:-1]\n    # Replace inf with NaN (will be interpolated)\n    arr[np.isinf(arr)] = np.nan\n    return arr\n\ninputs = [clean_series(df[col].values) for col in cols]\n```\n\n## Context Length Considerations\n\n| Context Length | Use Case | Notes |\n| -------------- | -------- | ----- |\n| 64–256 | Quick prototyping | Minimal context, fast |\n| 256–512 | Daily data, ~1 year | Good balance |\n| 512–1024 | Daily data, ~2-3 years | Standard production |\n| 1024–4096 | Hourly data, weekly patterns | More context = better |\n| 4096–16384 | High-frequency, long patterns | TimesFM 2.5 maximum |\n\n**Rule of thumb**: Provide at least 3–5 full cycles of the dominant pattern\n(e.g., for weekly seasonality with daily data, provide at least 21–35 days).\n\n## Covariates (XReg)\n\nTimesFM 2.5 supports exogenous variables through the `forecast_with_covariates()` API.\n\n### Types of Covariates\n\n| Type | Description | Example |\n| ---- | ----------- | ------- |\n| **Dynamic numerical** | Time-varying numeric features | Temperature, price, promotion spend |\n| **Dynamic categorical** | Time-varying categorical features | Day of week, holiday flag |\n| **Static categorical** | Fixed per-series features | Store ID, region, product category |\n\n### Preparing Covariates\n\nEach covariate must have length `context + horizon` for each series:\n\n```python\nimport numpy as np\n\ncontext_len = 100   # length of historical data\nhorizon = 24        # forecast horizon\ntotal_len = context_len + horizon\n\n# Dynamic numerical: temperature forecast for each series\ntemp = [\n    np.random.randn(total_len).astype(np.float32),  # Series 1\n    np.random.randn(total_len).astype(np.float32),  # Series 2\n]\n\n# Dynamic categorical: day of week (0-6) for each series\ndow = [\n    np.tile(np.arange(7), total_len // 7 + 1)[:total_len],  # Series 1\n    np.tile(np.arange(7), total_len // 7 + 1)[:total_len],  # Series 2\n]\n\n# Static categorical: one label per series\nregions = [\"east\", \"west\"]\n\n# Forecast with covariates\npoint, quantiles = model.forecast_with_covariates(\n    inputs=[values1, values2],\n    dynamic_numerical_covariates={\"temperature\": temp},\n    dynamic_categorical_covariates={\"day_of_week\": dow},\n    static_categorical_covariates={\"region\": regions},\n    xreg_mode=\"xreg + timesfm\",\n)\n```\n\n### XReg Modes\n\n| Mode | Description |\n| ---- | ----------- |\n| `\"xreg + timesfm\"` | Covariates processed first, then combined with TimesFM forecast |\n| `\"timesfm + xreg\"` | TimesFM forecast first, then adjusted by covariates |\n\n## Common Data Issues\n\n### Issue: Series too short\n\nTimesFM needs at least 1 data point, but more context = better forecasts.\n\n```python\nMIN_LENGTH = 32  # Practical minimum for meaningful forecasts\n\ninputs = [\n    arr for arr in raw_inputs\n    if len(arr[~np.isnan(arr)]) >= MIN_LENGTH\n]\n```\n\n### Issue: Series with constant values\n\nConstant series may produce NaN or zero-width prediction intervals:\n\n```python\nfor i, arr in enumerate(inputs):\n    if np.std(arr[~np.isnan(arr)]) < 1e-10:\n        print(f\"⚠️ Series {i} is constant — forecast will be flat\")\n```\n\n### Issue: Extreme outliers\n\nLarge outliers can destabilize forecasts even with normalization:\n\n```python\ndef clip_outliers(arr: np.ndarray, n_sigma: float = 5.0) -> np.ndarray:\n    \"\"\"Clip values beyond n_sigma standard deviations.\"\"\"\n    mu = np.nanmean(arr)\n    sigma = np.nanstd(arr)\n    if sigma > 0:\n        arr = np.clip(arr, mu - n_sigma * sigma, mu + n_sigma * sigma)\n    return arr\n```\n\n### Issue: Mixed frequencies in batch\n\nTimesFM handles each series independently, so you can mix frequencies:\n\n```python\ninputs = [\n    daily_sales,      # 365 points\n    weekly_revenue,   # 52 points\n    monthly_users,    # 24 points\n]\n# All forecasted in one batch — TimesFM handles different lengths\npoint, q = model.forecast(horizon=12, inputs=inputs)\n```\n\nHowever, the `horizon` is shared. If you need different horizons per series,\nforecast in separate calls.\n","timesfm-forecasting/references/api_reference.md":"# TimesFM API Reference\n\n## Model Classes\n\n### `timesfm.TimesFM_2p5_200M_torch`\n\nThe primary model class for TimesFM 2.5 (200M parameters, PyTorch backend).\n\n#### `from_pretrained()`\n\n```python\nmodel = timesfm.TimesFM_2p5_200M_torch.from_pretrained(\n    \"google/timesfm-2.5-200m-pytorch\",\n    cache_dir=None,         # Optional: custom cache directory\n    force_download=True,    # Re-download even if cached\n)\n```\n\n| Parameter | Type | Default | Description |\n| --------- | ---- | ------- | ----------- |\n| `model_id` | str | `\"google/timesfm-2.5-200m-pytorch\"` | Hugging Face model ID |\n| `revision` | str \\| None | None | Specific model revision |\n| `cache_dir` | str \\| Path \\| None | None | Custom cache directory |\n| `force_download` | bool | True | Force re-download of weights |\n\n**Returns**: Initialized `TimesFM_2p5_200M_torch` instance (not yet compiled).\n\n#### `compile()`\n\nCompiles the model with the given forecast configuration. **Must be called before `forecast()`.**\n\n```python\nmodel.compile(\n    timesfm.ForecastConfig(\n        max_context=1024,\n        max_horizon=256,\n        normalize_inputs=True,\n        per_core_batch_size=32,\n        use_continuous_quantile_head=True,\n        force_flip_invariance=True,\n        infer_is_positive=True,\n        fix_quantile_crossing=True,\n    )\n)\n```\n\n**Raises**: Nothing (but `forecast()` will raise `RuntimeError` if not compiled).\n\n#### `forecast()`\n\nRun inference on one or more time series.\n\n```python\npoint_forecast, quantile_forecast = model.forecast(\n    horizon=24,\n    inputs=[array1, array2, ...],\n)\n```\n\n| Parameter | Type | Description |\n| --------- | ---- | ----------- |\n| `horizon` | int | Number of future steps to forecast |\n| `inputs` | list[np.ndarray] | List of 1-D numpy arrays (each is a time series) |\n\n**Returns**: `tuple[np.ndarray, np.ndarray]`\n\n- `point_forecast`: shape `(batch_size, horizon)` — median (0.5 quantile)\n- `quantile_forecast`: shape `(batch_size, horizon, 10)` — [mean, q10, q20, ..., q90]\n\n**Raises**: `RuntimeError` if model is not compiled.\n\n**Key behaviors**:\n\n- Leading NaN values are stripped automatically\n- Internal NaN values are linearly interpolated\n- Series longer than `max_context` are truncated (last `max_context` points used)\n- Series shorter than `max_context` are padded\n\n#### `forecast_with_covariates()`\n\nRun inference with exogenous variables (requires `timesfm[xreg]`).\n\n```python\npoint, quantiles = model.forecast_with_covariates(\n    inputs=inputs,\n    dynamic_numerical_covariates={\"temp\": [temp_array1, temp_array2]},\n    dynamic_categorical_covariates={\"dow\": [dow_array1, dow_array2]},\n    static_categorical_covariates={\"region\": [\"east\", \"west\"]},\n    xreg_mode=\"xreg + timesfm\",\n)\n```\n\n| Parameter | Type | Description |\n| --------- | ---- | ----------- |\n| `inputs` | list[np.ndarray] | Target time series |\n| `dynamic_numerical_covariates` | dict[str, list[np.ndarray]] | Time-varying numeric features |\n| `dynamic_categorical_covariates` | dict[str, list[np.ndarray]] | Time-varying categorical features |\n| `static_categorical_covariates` | dict[str, list[str]] | Fixed categorical features per series |\n| `xreg_mode` | str | `\"xreg + timesfm\"` or `\"timesfm + xreg\"` |\n\n**Note**: Dynamic covariates must have length `context + horizon` for each series.\n\n---\n\n## `timesfm.ForecastConfig`\n\nImmutable dataclass controlling all forecast behavior.\n\n```python\n@dataclasses.dataclass(frozen=True)\nclass ForecastConfig:\n    max_context: int = 0\n    max_horizon: int = 0\n    normalize_inputs: bool = False\n    per_core_batch_size: int = 1\n    use_continuous_quantile_head: bool = False\n    force_flip_invariance: bool = True\n    infer_is_positive: bool = True\n    fix_quantile_crossing: bool = False\n    return_backcast: bool = False\n    quantiles: list[float] = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]\n    decode_index: int = 5\n```\n\n### Parameter Details\n\n#### `max_context` (int, default=0)\n\nMaximum number of historical time points to use as context.\n\n- **0**: Use the model's maximum supported context (16,384 for v2.5)\n- **N**: Truncate series to last N points\n- **Best practice**: Set to the length of your longest series, or 512–2048 for speed\n\n#### `max_horizon` (int, default=0)\n\nMaximum forecast horizon.\n\n- **0**: Use the model's maximum\n- **N**: Forecasts up to N steps (can still call `forecast(horizon=M)` where M ≤ N)\n- **Best practice**: Set to your expected maximum forecast length\n\n#### `normalize_inputs` (bool, default=False)\n\nWhether to z-normalize each series before feeding to the model.\n\n- **True** (RECOMMENDED): Normalizes each series to zero mean, unit variance\n- **False**: Raw values are passed directly\n- **When False is OK**: Only if your series are already normalized or very close to scale 1.0\n\n#### `per_core_batch_size` (int, default=1)\n\nNumber of series processed per device in each batch.\n\n- Increase for throughput, decrease if OOM\n- See `references/system_requirements.md` for recommended values by hardware\n\n#### `use_continuous_quantile_head` (bool, default=False)\n\nUse the 30M-parameter continuous quantile head for better interval calibration.\n\n- **True** (RECOMMENDED): More accurate prediction intervals, especially for longer horizons\n- **False**: Uses fixed quantile buckets (faster but less accurate intervals)\n\n#### `force_flip_invariance` (bool, default=True)\n\nEnsures the model satisfies `f(-x) = -f(x)`.\n\n- **True** (RECOMMENDED): Mathematical consistency — forecasts are invariant to sign flip\n- **False**: Slightly faster but may produce asymmetric forecasts\n\n#### `infer_is_positive` (bool, default=True)\n\nAutomatically detect if all input values are positive and clamp forecasts ≥ 0.\n\n- **True**: Safe for sales, demand, counts, prices, volumes\n- **False**: Required for temperature, returns, PnL, any series that can be negative\n\n#### `fix_quantile_crossing` (bool, default=False)\n\nPost-process quantiles to ensure monotonicity (q10 ≤ q20 ≤ ... ≤ q90).\n\n- **True** (RECOMMENDED): Guarantees well-ordered quantiles\n- **False**: Slightly faster but quantiles may occasionally cross\n\n#### `return_backcast` (bool, default=False)\n\nReturn the model's reconstruction of the input (backcast) in addition to forecast.\n\n- **True**: Used for covariate workflows and diagnostics\n- **False**: Only return forecast\n\n---\n\n## Available Model Checkpoints\n\n| Model ID | Version | Params | Backend | Context |\n| -------- | ------- | ------ | ------- | ------- |\n| `google/timesfm-2.5-200m-pytorch` | 2.5 | 200M | PyTorch | 16,384 |\n| `google/timesfm-2.5-200m-flax` | 2.5 | 200M | JAX/Flax | 16,384 |\n| `google/timesfm-2.5-200m-transformers` | 2.5 | 200M | Transformers | 16,384 |\n| `google/timesfm-2.0-500m-pytorch` | 2.0 | 500M | PyTorch | 2,048 |\n| `google/timesfm-2.0-500m-jax` | 2.0 | 500M | JAX | 2,048 |\n| `google/timesfm-1.0-200m-pytorch` | 1.0 | 200M | PyTorch | 2,048 |\n| `google/timesfm-1.0-200m` | 1.0 | 200M | JAX | 2,048 |\n\n---\n\n## Output Shape Reference\n\n| Output | Shape | Description |\n| ------ | ----- | ----------- |\n| `point_forecast` | `(B, H)` | Median forecast for B series, H steps |\n| `quantile_forecast` | `(B, H, 10)` | Full quantile distribution |\n| `quantile_forecast[:,:,0]` | `(B, H)` | Mean |\n| `quantile_forecast[:,:,1]` | `(B, H)` | 10th percentile |\n| `quantile_forecast[:,:,5]` | `(B, H)` | 50th percentile (= point_forecast) |\n| `quantile_forecast[:,:,9]` | `(B, H)` | 90th percentile |\n\nWhere `B` = batch size (number of input series), `H` = forecast horizon.\n\n---\n\n---\n\n## Memory Estimation\n\nBefore running forecasts on large datasets, estimate memory requirements:\n\n### Formula\n\n```mermaid\nblock-beta\n    columns 3\n    ram[\"Total RAM Required\"] model[\"Model Weights<br/>~0.8 GB\"] overhead[\"Runtime Overhead<br/>~0.5 GB\"] buffers[\"I/O Buffers<br/>~0.2 MB per 1000 series<br/>per 1000 context\"]\n    \n    ram --> model\n    ram --> overhead\n    ram --> buffers\n```\n\n**Formula**:  \n`RAM (GB) ≈ 0.8 + 0.5 + (0.0002 × num_series × context_length)`\n\n**Variables**:\n- `num_series`: Number of time series in your batch\n- `context_length`: Your `max_context` value (or max series length)\n- `batch_size`: Your `per_core_batch_size` (affects parallel processing overhead)\n\n### Quick Reference\n\n| Dataset Size | Context=512 | Context=1024 | Context=2048 |\n|--------------|-------------|--------------|--------------|\n| 100 series | ~1.4 GB | ~1.5 GB | ~1.7 GB |\n| 1,000 series | ~1.9 GB | ~2.3 GB | ~3.1 GB |\n| 10,000 series| ~9.0 GB | ~17.0 GB | ~33.0 GB |\n\n### Using the Preflight Checker\n\n```bash\npython scripts/check_system.py \\\n  --num-series 1000 \\\n  --context-length 1024 \\\n  --batch-size 32\n```\n\nThis validates both system requirements AND dataset fit before loading the model.\n\n### Reducing Memory Usage\n\nIf your dataset is too large:\n\n1. **Reduce context length**: Use `max_context=512` instead of 1024+ (50% reduction)\n2. **Process in chunks**: Split large batches into smaller groups:\n\n```python\nCHUNK_SIZE = 100\nfor i in range(0, len(inputs), CHUNK_SIZE):\n    chunk = inputs[i:i+CHUNK_SIZE]\n    point, quantiles = model.forecast(horizon=H, inputs=chunk)\n    # Save chunk results\n```\n\n3. **Reduce batch size**: Lower `per_core_batch_size` (slower but less memory)\n4. **Use CPU**: If GPU OOM, the model will automatically fall back to CPU\n\n\n## Error Handling\n\n| Error | Cause | Fix |\n| ----- | ----- | --- |\n| `RuntimeError: Model is not compiled` | Called `forecast()` before `compile()` | Call `model.compile(ForecastConfig(...))` first |\n| `torch.cuda.OutOfMemoryError` | Batch too large for GPU | Reduce `per_core_batch_size` |\n| `ValueError: inputs must be list` | Passed array instead of list | Wrap in list: `[array]` |\n| `HfHubHTTPError` | Download failed | Check internet, set `HF_HOME` to writable dir |\n","timesfm-forecasting/SKILL.md":"---\nname: timesfm-forecasting\ndescription: >\n  Zero-shot time series forecasting with Google's TimesFM foundation model. Use this\n  skill when forecasting ANY univariate time series — sales, sensor readings, stock prices,\n  energy demand, patient vitals, weather, or scientific measurements — without training a\n  custom model. Supports both basic forecasting and advanced covariate forecasting (XReg)\n  with dynamic and static exogenous variables. Automatically checks system RAM/GPU before\n  loading the model, validates dataset fit before processing, supports CSV/DataFrame/array\n  inputs, and returns point forecasts with calibrated prediction intervals. Includes a\n  preflight system checker script that MUST be run before first use to verify the machine\n  can load the model and handle your specific dataset.\nlicense: Apache-2.0\nmetadata:\n  author: Clayton Young (@borealBytes)\n  version: \"1.0.0\"\n---\n\n# TimesFM Forecasting\n\n## Overview\n\nTimesFM (Time Series Foundation Model) is a pretrained decoder-only foundation model\ndeveloped by Google Research for time-series forecasting. It works **zero-shot** — feed it\nany univariate time series and it returns point forecasts with calibrated quantile\nprediction intervals, no training required.\n\nThis skill includes a **mandatory preflight system checker** that verifies RAM, GPU memory,\nand disk space before the model is ever loaded so the agent never crashes the user's machine.\n\n> **Key numbers**: TimesFM 2.5 uses 200M parameters (~800 MB on disk, ~1.5 GB in RAM on\n> CPU, ~1 GB VRAM on GPU). The archived v1/v2 500M-parameter model needs ~32 GB RAM.\n> Always run the system checker first.\n\n## When to Use This Skill\n\nUse this skill when:\n\n- Forecasting **any univariate time series** (sales, demand, sensor, vitals, price, weather)\n- You need **zero-shot forecasting** without training a custom model\n- You want **probabilistic forecasts** with calibrated prediction intervals (quantiles)\n- You have time series of **any length** (the model handles 1–16,384 context points)\n- You need to **batch-forecast** hundreds or thousands of series efficiently\n- You want a **foundation model** approach instead of hand-tuning ARIMA/ETS parameters\n- You need **covariate forecasting** with exogenous variables (price, promotions, holidays, day-of-week effects) → use `forecast_with_covariates()` (TimesFM 2.5 + `pip install timesfm[xreg]`)\n\n\nDo **not** use this skill when:\n\n- You need classical statistical models with coefficient interpretation → use `statsmodels`\n- You need time series classification or clustering → use `aeon`\n- You need multivariate vector autoregression or Granger causality → use `statsmodels`\n- Your data is tabular (not temporal) → use `scikit-learn`\n- You cannot install optional dependencies → XReg requires scikit-learn and JAX\n\n\n> **Note on Anomaly Detection**: TimesFM does not have built-in anomaly detection, but you\n> can use the **quantile forecasts as prediction intervals** — values outside the 90% CI\n> (q10–q90) are statistically unusual. See `examples/anomaly-detection/` for a full example.\n\n## ⚠️ Mandatory Preflight: System Requirements Check\n\n**CRITICAL — ALWAYS run the system checker before loading the model for the first time.**\n\n```bash\npython scripts/check_system.py\n```\n\nThis script checks:\n\n1. **Available RAM** — warns if below 4 GB, blocks if below 2 GB\n2. **GPU availability** — detects CUDA/MPS devices and VRAM\n3. **Disk space** — verifies room for the ~800 MB model download\n4. **Python version** — requires 3.10+\n5. **Existing installation** — checks if `timesfm` and `torch` are installed\n\n> **Note:** Model weights are **NOT stored in this repository**. TimesFM weights (~800 MB)\n> download on-demand from HuggingFace on first use and cache in `~/.cache/huggingface/`.\n\n```mermaid\nflowchart TD\n    start[\"🚀 Run check_system.py\"] --> ram{\"RAM ≥ 4 GB?\"}\n    ram -->|\"Yes\"| gpu{\"GPU available?\"}\n    ram -->|\"No (2-4 GB)\"| warn_ram[\"⚠️ Warning: tight RAM<br/>CPU-only, small batches\"]\n    ram -->|\"No (< 2 GB)\"| block[\"🛑 BLOCKED<br/>Insufficient memory\"]\n    warn_ram --> disk\n    gpu -->|\"CUDA / MPS\"| vram{\"VRAM ≥ 2 GB?\"}\n    gpu -->|\"CPU only\"| cpu_ok[\"✅ CPU mode<br/>Slower but works\"]\n    vram -->|\"Yes\"| gpu_ok[\"✅ GPU mode<br/>Fast inference\"]\n    vram -->|\"No\"| cpu_ok\n    gpu_ok --> disk{\"Disk ≥ 2 GB free?\"}\n    cpu_ok --> disk\n    disk -->|\"Yes\"| ready[\"✅ READY<br/>Safe to load model\"]\n    disk -->|\"No\"| block_disk[\"🛑 BLOCKED<br/>Need space for weights\"]\n```\n\n### Dataset Preflight (NEW)\n\nBefore loading your actual data, verify it will fit in memory:\n\n```bash\n# Quick estimate for your dataset\npython scripts/check_system.py \\\n  --num-series 1000 \\\n  --context-length 1024 \\\n  --horizon 24 \\\n  --batch-size 32 \\\n  --estimate-only\n```\n\nThis will show you the estimated memory requirements and warn if your dataset is too large.\n\n**Memory Estimation Formula**:\n`RAM ≈ 0.8 GB (model) + 0.5 GB (overhead) + (0.2 MB × num_series × context_length / 1000)`\n\n**Example Outputs**:\n\n✅ **Dataset Fits**:\n```\nTotal CPU memory: 2.34 GB\nTotal GPU memory: 2.15 GB\n```\n\n⚠️ **Dataset Too Large**:\n```\nDataset requires ~12.5 GB RAM but system has 8.0 GB.\nTry: context_length=512 or process in chunks of 50 series.\n```\n\n### Hardware Requirements by Model Version\n\n| Model | Parameters | RAM (CPU) | VRAM (GPU) | Disk | Context |\n| ----- | ---------- | --------- | ---------- | ---- | ------- |\n| **TimesFM 2.5** (recommended) | 200M | ≥ 4 GB | ≥ 2 GB | ~800 MB | up to 16,384 |\n| TimesFM 2.0 (archived) | 500M | ≥ 16 GB | ≥ 8 GB | ~2 GB | up to 2,048 |\n| TimesFM 1.0 (archived) | 200M | ≥ 8 GB | ≥ 4 GB | ~800 MB | up to 2,048 |\n\n> **Recommendation**: Always use TimesFM 2.5 unless you have a specific reason to use an\n> older checkpoint. It is smaller, faster, and supports 8× longer context.\n\n## 🔧 Installation\n\n### Step 1: Verify System (always first)\n\n```bash\npython scripts/check_system.py\n```\n\n### Step 2: Install TimesFM\n\n```bash\n# Using uv (fast)\nuv pip install timesfm[torch]\n\n# Or using pip\npip install timesfm[torch]\n\n# For JAX/Flax backend (faster on TPU/GPU)\nuv pip install timesfm[flax]\n```\n\n### Step 3: Install PyTorch for Your Hardware\n\n```bash\n# CUDA 12.1 (NVIDIA GPU)\npip install torch>=2.0.0 --index-url https://download.pytorch.org/whl/cu121\n\n# CPU only\npip install torch>=2.0.0 --index-url https://download.pytorch.org/whl/cpu\n\n# Apple Silicon (MPS)\npip install torch>=2.0.0  # MPS support is built-in\n```\n\n## 🎯 Quick Start\n\n### Minimal Example\n\n```python\nimport torch, numpy as np, timesfm\n\ntorch.set_float32_matmul_precision(\"high\")\n\nmodel = timesfm.TimesFM_2p5_200M_torch.from_pretrained(\n    \"google/timesfm-2.5-200m-pytorch\"\n)\nmodel.compile(timesfm.ForecastConfig(\n    max_context=1024, max_horizon=256, normalize_inputs=True,\n    use_continuous_quantile_head=True, force_flip_invariance=True,\n    infer_is_positive=True, fix_quantile_crossing=True,\n))\n\npoint, quantiles = model.forecast(horizon=24, inputs=[\n    np.sin(np.linspace(0, 20, 200)),  # any 1-D array\n])\n# point.shape == (1, 24)         — median forecast\n# quantiles.shape == (1, 24, 10) — 10th–90th percentile bands\n```\n\n### Forecast with Covariates (XReg)\n\nTimesFM 2.5+ supports exogenous variables through `forecast_with_covariates()`.\nRequires `pip install timesfm[xreg]`.\n\n```python\npoint, quantiles = model.forecast_with_covariates(\n    inputs=inputs,\n    dynamic_numerical_covariates={\"price\": price_arrays},\n    dynamic_categorical_covariates={\"holiday\": holiday_arrays},\n    static_categorical_covariates={\"region\": region_labels},\n    xreg_mode=\"xreg + timesfm\",  # or \"timesfm + xreg\"\n)\n```\n\n### Anomaly Detection (via Quantile Intervals)\n\n```python\npoint, q = model.forecast(horizon=H, inputs=[values])\n\nlower_90 = q[0, :, 1]  # 10th percentile\nupper_90 = q[0, :, 9]  # 90th percentile\n\nactual = test_values\nanomalies = (actual < lower_90) | (actual > upper_90)\n```\n\n| Severity | Condition | Interpretation |\n| -------- | --------- | -------------- |\n| **Normal** | Inside 80% CI | Expected behavior |\n| **Warning** | Outside 80% CI | Unusual but possible |\n| **Critical** | Outside 90% CI | Statistically rare (< 10% probability) |\n\n> See `examples/anomaly-detection/` for a complete worked example with visualization.\n\n## 📊 Understanding the Output\n\nTimesFM returns `(point_forecast, quantile_forecast)`:\n\n- **`point_forecast`**: shape `(batch, horizon)` — the median (0.5 quantile)\n- **`quantile_forecast`**: shape `(batch, horizon, 10)` — ten quantile slices:\n\n| Index | Quantile | Use |\n| ----- | -------- | --- |\n| 0 | Mean | Average prediction |\n| 1 | 0.1 | Lower bound of 80% PI |\n| 2 | 0.2 | Lower bound of 60% PI |\n| **5** | **0.5** | **Median (= `point_forecast`)** |\n| 8 | 0.8 | Upper bound of 60% PI |\n| 9 | 0.9 | Upper bound of 80% PI |\n\n```python\npoint, q = model.forecast(horizon=H, inputs=data)\n\nlower_80 = q[:, :, 1]  # 10th percentile\nupper_80 = q[:, :, 9]  # 90th percentile\nmedian   = q[:, :, 5]\n```\n\n## 🔧 ForecastConfig Reference\n\nAll forecasting behavior is controlled by `timesfm.ForecastConfig`:\n\n```python\ntimesfm.ForecastConfig(\n    max_context=1024,                    # Max context window\n    max_horizon=256,                     # Max forecast horizon\n    normalize_inputs=True,               # RECOMMENDED — prevents scale instability\n    per_core_batch_size=32,              # Tune for memory\n    use_continuous_quantile_head=True,   # Better quantile accuracy for long horizons\n    force_flip_invariance=True,          # Ensures f(-x) = -f(x)\n    infer_is_positive=True,              # Clamp forecasts ≥ 0 when all inputs > 0\n    fix_quantile_crossing=True,          # Ensure q10 ≤ q20 ≤ ... ≤ q90\n    return_backcast=False,               # Return backcast (for covariate workflows)\n)\n```\n\n| Parameter | Default | When to Change |\n| --------- | ------- | -------------- |\n| `max_context` | 0 | Set to match your longest historical window |\n| `normalize_inputs` | False | **Always set True** |\n| `use_continuous_quantile_head` | False | **Set True** for calibrated PIs |\n| `infer_is_positive` | True | Set False for series that can be negative |\n| `fix_quantile_crossing` | False | **Set True** for monotonic quantiles |\n\nSee `references/api_reference.md` for the complete parameter reference.\n\n## 📋 Common Workflows\n\n### Single Series Forecast\n\n```python\nimport torch, numpy as np, pandas as pd, timesfm, matplotlib\nmatplotlib.use(\"Agg\")\nimport matplotlib.pyplot as plt\n\ntorch.set_float32_matmul_precision(\"high\")\nmodel = timesfm.TimesFM_2p5_200M_torch.from_pretrained(\n    \"google/timesfm-2.5-200m-pytorch\"\n)\nmodel.compile(timesfm.ForecastConfig(\n    max_context=512, max_horizon=52, normalize_inputs=True,\n    use_continuous_quantile_head=True, fix_quantile_crossing=True,\n))\n\ndf = pd.read_csv(\"weekly_demand.csv\", parse_dates=[\"week\"])\nvalues = df[\"demand\"].values.astype(np.float32)\n\npoint, quantiles = model.forecast(horizon=52, inputs=[values])\n\nfig, ax = plt.subplots(figsize=(12, 5))\nax.plot(values[-104:], label=\"Historical\")\nx_fc = range(len(values[-104:]), len(values[-104:]) + 52)\nax.plot(x_fc, point[0], label=\"Forecast\", color=\"tab:orange\")\nax.fill_between(x_fc, quantiles[0, :, 1], quantiles[0, :, 9],\n                alpha=0.2, color=\"tab:orange\", label=\"80% PI\")\nax.legend(); ax.set_title(\"52-Week Demand Forecast\")\nplt.tight_layout(); plt.savefig(\"forecast.png\", dpi=150)\n```\n\n### Batch Forecasting (Many Series)\n\n```python\ndf = pd.read_csv(\"all_stores.csv\", parse_dates=[\"date\"], index_col=\"date\")\ninputs = [df[col].dropna().values.astype(np.float32) for col in df.columns]\n\npoint, quantiles = model.forecast(horizon=30, inputs=inputs)\n\nimport json\nresults = {col: {\"forecast\": point[i].tolist(),\n                 \"lower_80\": quantiles[i, :, 1].tolist(),\n                 \"upper_80\": quantiles[i, :, 9].tolist()}\n           for i, col in enumerate(df.columns)}\nwith open(\"batch_forecasts.json\", \"w\") as f:\n    json.dump(results, f, indent=2)\n```\n\n### Evaluate Forecast Accuracy\n\n```python\nH = 24\ntrain, actual = values[:-H], values[-H:]\npoint, quantiles = model.forecast(horizon=H, inputs=[train])\npred = point[0]\n\nmae  = np.mean(np.abs(actual - pred))\nrmse = np.sqrt(np.mean((actual - pred) ** 2))\nmape = np.mean(np.abs((actual - pred) / actual)) * 100\ncoverage = np.mean((actual >= quantiles[0, :, 1]) & (actual <= quantiles[0, :, 9])) * 100\n\nprint(f\"MAE: {mae:.2f} | RMSE: {rmse:.2f} | MAPE: {mape:.1f}% | 80% PI Coverage: {coverage:.1f}%\")\n```\n\n## ⚙️ Performance Tuning\n\n```python\n# Always set on Ampere+ GPUs (A100, RTX 3090+)\ntorch.set_float32_matmul_precision(\"high\")\n\n# Batch size guidelines:\n# GPU 8 GB VRAM:  per_core_batch_size=64\n# GPU 16 GB VRAM: per_core_batch_size=128\n# CPU 8 GB RAM:   per_core_batch_size=8\n# CPU 16 GB RAM:  per_core_batch_size=32\n\n# Memory-constrained: process in chunks\nCHUNK = 50\nresults = []\nfor i in range(0, len(inputs), CHUNK):\n    p, q = model.forecast(horizon=H, inputs=inputs[i:i+CHUNK])\n    results.append((p, q))\n```\n\n## 📚 Available Scripts\n\n### `scripts/check_system.py`\n\nMandatory preflight checker — run before first model load.\nNow includes **dataset-aware memory estimation** to prevent OOM errors before loading your data.\n\n```bash\n# Basic system check\npython scripts/check_system.py\n\n# Check if your specific dataset will fit\npython scripts/check_system.py \\\n  --num-series 1000 \\\n  --context-length 1024 \\\n  --horizon 24 \\\n  --batch-size 32\n\n# Quick memory estimate without system checks\npython scripts/check_system.py \\\n  --num-series 5000 \\\n  --context-length 2048 \\\n  --estimate-only\n```\n\n**What it checks**:\n\n1. **Available RAM** — warns if below 4 GB, blocks if below 2 GB\n2. **GPU availability** — detects CUDA/MPS devices and VRAM\n3. **Disk space** — verifies room for the ~800 MB model download\n4. **Python version** — requires 3.10+\n5. **Existing installation** — checks if `timesfm` and `torch` are installed\n6. **Dataset fit** (NEW) — estimates memory for your specific dataset and warns if it won't fit\n\n### `scripts/forecast_csv.py`\n\nEnd-to-end CSV forecasting CLI.\n\n```bash\npython scripts/forecast_csv.py input.csv \\\n    --horizon 24 \\\n    --date-col date \\\n    --value-cols sales,revenue \\\n    --output forecasts.csv\n```\n\n## 📖 Reference Documentation\n\n| File | Contents |\n| ---- | -------- |\n| `references/system_requirements.md` | Hardware tiers, GPU/CPU selection, memory estimation |\n| `references/api_reference.md` | Full `ForecastConfig` docs, output shapes, model options |\n| `references/data_preparation.md` | Input formats, NaN handling, CSV loading, covariate setup |\n\n## 🧪 Examples\n\n| Example | Directory | What It Demonstrates |\n| ------- | --------- | -------------------- |\n| **Global Temperature Forecast** | `examples/global-temperature/` | Basic `model.forecast()`, CSV → PNG → GIF pipeline |\n| **Anomaly Detection** | `examples/anomaly-detection/` | Two-phase detrend + Z-score + quantile PI, 2-panel viz |\n| **Covariates (XReg)** | `examples/covariates-forecasting/` | `forecast_with_covariates()`, 2×2 shared-axis viz |\n\n```bash\n# Run all three examples:\ncd examples/global-temperature && python run_forecast.py && python visualize_forecast.py\ncd examples/anomaly-detection  && python detect_anomalies.py\ncd examples/covariates-forecasting && python demo_covariates.py\n```\n\n### Expected Outputs\n\n| Example | Key output files | Acceptance criteria |\n| ------- | ---------------- | ------------------- |\n| global-temperature | `output/forecast_output.json`, `output/forecast_visualization.png` | `point_forecast` has 12 values; PNG shows context + forecast + PI bands |\n| anomaly-detection | `output/anomaly_detection.json`, `output/anomaly_detection.png` | Sep 2023 flagged CRITICAL (z ≥ 3.0) |\n| covariates-forecasting | `output/sales_with_covariates.csv`, `output/covariates_data.png` | 108 rows (3 stores × 36 weeks); distinct price arrays per store |\n\n## Model Versions\n\n| Version | Params | Context | Status | HuggingFace checkpoint |\n| ------- | ------ | ------- | ------ | ---------------------- |\n| **2.5** | 200M | 16,384 | **Latest** | `google/timesfm-2.5-200m-pytorch` |\n| 2.0 | 500M | 2,048 | Archived | `google/timesfm-2.0-500m-pytorch` |\n| 1.0 | 200M | 2,048 | Archived | `google/timesfm-1.0-200m-pytorch` |\n\n- TimesFM 1.0/2.0: must pass `freq=[0]` for monthly data\n- TimesFM 2.5: no frequency flag — it was removed\n\n## Resources\n\n- **Paper**: [A Decoder-Only Foundation Model for Time-Series Forecasting](https://arxiv.org/abs/2310.10688) (ICML 2024)\n- **HuggingFace**: https://huggingface.co/collections/google/timesfm-release-66e4be5fdb56e960c1e482a6\n- **Google Blog**: https://research.google/blog/a-decoder-only-foundation-model-for-time-series-forecasting/\n- **BigQuery Integration**: https://cloud.google.com/bigquery/docs/timesfm-model\n\n## Quality Checklist\n\nRun after every TimesFM task before declaring success:\n\n- [ ] **Output shape** — `point_fc` is `(n_series, horizon)`, `quant_fc` is `(n_series, horizon, 10)`\n- [ ] **Quantile indices** — index 0 = mean, 1 = q10 ... 9 = q90. NOT 0 = q0.\n- [ ] **Frequency flag** — TimesFM 1.0/2.0: pass `freq=[0]` for monthly. TimesFM 2.5: omit.\n- [ ] **Series length** — context must be ≥ 32 data points.\n- [ ] **No NaN** — `np.isnan(point_fc).any()` must be False.\n- [ ] **Axes** — multiple panels sharing data must use `sharex=True`.\n- [ ] **`matplotlib.use('Agg')`** — before any pyplot import when running headless.\n- [ ] **`infer_is_positive`** — set False for temperature, financial returns, negatives.\n\n## Common Mistakes\n\n1. **Quantile index off-by-one** — `quant_fc[..., 0]` is the **mean**, not q0. q10 = index 1, q90 = index 9. Define: `IDX_Q10, IDX_Q90 = 1, 9`.\n\n2. **Variable shadowing in covariate loops** — don't use the outer loop variable as a comprehension variable when building per-series covariate dicts.\n\n3. **Wrong CSV column name** — global-temperature CSV uses `anomaly_c`, not `anomaly`. Print `df.columns` first.\n\n4. **TimesFM 2.5 required for `forecast_with_covariates()`** — TimesFM 1.0 does NOT have this method.\n\n5. **Future covariates must span the full horizon** — dynamic covariates need values for BOTH context AND forecast windows.\n\n6. **Context anomaly detection uses residuals** — detrend first, then Z-score. Raw Z-scores mislead on trending data.\n\n## Validation & Verification\n\n```bash\n# Anomaly detection regression:\npython -c \"\nimport json\nd = json.load(open('examples/anomaly-detection/output/anomaly_detection.json'))\nassert d['context_summary']['critical'] >= 1, 'Sep 2023 must be CRITICAL'\nprint('Anomaly detection: PASS')\"\n\n# Covariates regression:\npython -c \"\nimport pandas as pd\ndf = pd.read_csv('examples/covariates-forecasting/output/sales_with_covariates.csv')\nassert len(df) == 108, f'Expected 108 rows, got {len(df)}'\nprint('Covariates: PASS')\"\n```\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/google-research/timesfm/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# TimesFM — Agent Entry Point\n\nThis repository ships a first-party **Agent Skill** for TimesFM at:\n\n```\ntimesfm-forecasting/\n└── SKILL.md    ← read this for the full skill\n```\n\n## Install the skill\n\nCopy the skill directory into your agent's skills folder:\n\n```bash\n# Cursor / Claude Code / OpenCode / Codex (global install)\ncp -r timesfm-forecasting/ ~/.cursor/skills/\ncp -r timesfm-forecasting/ ~/.claude/skills/\n\n# Or project-level\ncp -r timesfm-forecasting/ .cursor/skills/\n```\n\nAny agent that supports the open [Agent Skills standard](https://agentskills.io) will discover it automatically.\n\n## Working in this repo\n\nIf you are developing TimesFM itself (not using it), the source lives in `src/timesfm/`.\nArchived v1/v2 code and notebooks are in `v1/`.\n\nRun tests:\n\n```bash\npytest v1/tests/\n```\n\nSee `README.md` for full developer setup.\n","isInternal":false,"tokens":222,"sizeBytes":854},{"name":"api_reference.md","path":"timesfm-forecasting/references/api_reference.md","rawUrl":"https://raw.githubusercontent.com/google-research/timesfm/HEAD/timesfm-forecasting/references/api_reference.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# TimesFM API Reference\n\n## Model Classes\n\n### `timesfm.TimesFM_2p5_200M_torch`\n\nThe primary model class for TimesFM 2.5 (200M parameters, PyTorch backend).\n\n#### `from_pretrained()`\n\n```python\nmodel = timesfm.TimesFM_2p5_200M_torch.from_pretrained(\n    \"google/timesfm-2.5-200m-pytorch\",\n    cache_dir=None,         # Optional: custom cache directory\n    force_download=True,    # Re-download even if cached\n)\n```\n\n| Parameter | Type | Default | Description |\n| --------- | ---- | ------- | ----------- |\n| `model_id` | str | `\"google/timesfm-2.5-200m-pytorch\"` | Hugging Face model ID |\n| `revision` | str \\| None | None | Specific model revision |\n| `cache_dir` | str \\| Path \\| None | None | Custom cache directory |\n| `force_download` | bool | True | Force re-download of weights |\n\n**Returns**: Initialized `TimesFM_2p5_200M_torch` instance (not yet compiled).\n\n#### `compile()`\n\nCompiles the model with the given forecast configuration. **Must be called before `forecast()`.**\n\n```python\nmodel.compile(\n    timesfm.ForecastConfig(\n        max_context=1024,\n        max_horizon=256,\n        normalize_inputs=True,\n        per_core_batch_size=32,\n        use_continuous_quantile_head=True,\n        force_flip_invariance=True,\n        infer_is_positive=True,\n        fix_quantile_crossing=True,\n    )\n)\n```\n\n**Raises**: Nothing (but `forecast()` will raise `RuntimeError` if not compiled).\n\n#### `forecast()`\n\nRun inference on one or more time series.\n\n```python\npoint_forecast, quantile_forecast = model.forecast(\n    horizon=24,\n    inputs=[array1, array2, ...],\n)\n```\n\n| Parameter | Type | Description |\n| --------- | ---- | ----------- |\n| `horizon` | int | Number of future steps to forecast |\n| `inputs` | list[np.ndarray] | List of 1-D numpy arrays (each is a time series) |\n\n**Returns**: `tuple[np.ndarray, np.ndarray]`\n\n- `point_forecast`: shape `(batch_size, horizon)` — median (0.5 quantile)\n- `quantile_forecast`: shape `(batch_size, horizon, 10)` — [mean, q10, q20, ..., q90]\n\n**Raises**: `RuntimeError` if model is not compiled.\n\n**Key behaviors**:\n\n- Leading NaN values are stripped automatically\n- Internal NaN values are linearly interpolated\n- Series longer than `max_context` are truncated (last `max_context` points used)\n- Series shorter than `max_context` are padded\n\n#### `forecast_with_covariates()`\n\nRun inference with exogenous variables (requires `timesfm[xreg]`).\n\n```python\npoint, quantiles = model.forecast_with_covariates(\n    inputs=inputs,\n    dynamic_numerical_covariates={\"temp\": [temp_array1, temp_array2]},\n    dynamic_categorical_covariates={\"dow\": [dow_array1, dow_array2]},\n    static_categorical_covariates={\"region\": [\"east\", \"west\"]},\n    xreg_mode=\"xreg + timesfm\",\n)\n```\n\n| Parameter | Type | Description |\n| --------- | ---- | ----------- |\n| `inputs` | list[np.ndarray] | Target time series |\n| `dynamic_numerical_covariates` | dict[str, list[np.ndarray]] | Time-varying numeric features |\n| `dynamic_categorical_covariates` | dict[str, list[np.ndarray]] | Time-varying categorical features |\n| `static_categorical_covariates` | dict[str, list[str]] | Fixed categorical features per series |\n| `xreg_mode` | str | `\"xreg + timesfm\"` or `\"timesfm + xreg\"` |\n\n**Note**: Dynamic covariates must have length `context + horizon` for each series.\n\n---\n\n## `timesfm.ForecastConfig`\n\nImmutable dataclass controlling all forecast behavior.\n\n```python\n@dataclasses.dataclass(frozen=True)\nclass ForecastConfig:\n    max_context: int = 0\n    max_horizon: int = 0\n    normalize_inputs: bool = False\n    per_core_batch_size: int = 1\n    use_continuous_quantile_head: bool = False\n    force_flip_invariance: bool = True\n    infer_is_positive: bool = True\n    fix_quantile_crossing: bool = False\n    return_backcast: bool = False\n    quantiles: list[float] = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]\n    decode_index: int = 5\n```\n\n### Parameter Details\n\n#### `max_context` (int, default=0)\n\nMaximum number of historical time points to use as context.\n\n- **0**: Use the model's maximum supported context (16,384 for v2.5)\n- **N**: Truncate series to last N points\n- **Best practice**: Set to the length of your longest series, or 512–2048 for speed\n\n#### `max_horizon` (int, default=0)\n\nMaximum forecast horizon.\n\n- **0**: Use the model's maximum\n- **N**: Forecasts up to N steps (can still call `forecast(horizon=M)` where M ≤ N)\n- **Best practice**: Set to your expected maximum forecast length\n\n#### `normalize_inputs` (bool, default=False)\n\nWhether to z-normalize each series before feeding to the model.\n\n- **True** (RECOMMENDED): Normalizes each series to zero mean, unit variance\n- **False**: Raw values are passed directly\n- **When False is OK**: Only if your series are already normalized or very close to scale 1.0\n\n#### `per_core_batch_size` (int, default=1)\n\nNumber of series processed per device in each batch.\n\n- Increase for throughput, decrease if OOM\n- See `references/system_requirements.md` for recommended values by hardware\n\n#### `use_continuous_quantile_head` (bool, default=False)\n\nUse the 30M-parameter continuous quantile head for better interval calibration.\n\n- **True** (RECOMMENDED): More accurate prediction intervals, especially for longer horizons\n- **False**: Uses fixed quantile buckets (faster but less accurate intervals)\n\n#### `force_flip_invariance` (bool, default=True)\n\nEnsures the model satisfies `f(-x) = -f(x)`.\n\n- **True** (RECOMMENDED): Mathematical consistency — forecasts are invariant to sign flip\n- **False**: Slightly faster but may produce asymmetric forecasts\n\n#### `infer_is_positive` (bool, default=True)\n\nAutomatically detect if all input values are positive and clamp forecasts ≥ 0.\n\n- **True**: Safe for sales, demand, counts, prices, volumes\n- **False**: Required for temperature, returns, PnL, any series that can be negative\n\n#### `fix_quantile_crossing` (bool, default=False)\n\nPost-process quantiles to ensure monotonicity (q10 ≤ q20 ≤ ... ≤ q90).\n\n- **True** (RECOMMENDED): Guarantees well-ordered quantiles\n- **False**: Slightly faster but quantiles may occasionally cross\n\n#### `return_backcast` (bool, default=False)\n\nReturn the model's reconstruction of the input (backcast) in addition to forecast.\n\n- **True**: Used for covariate workflows and diagnostics\n- **False**: Only return forecast\n\n---\n\n## Available Model Checkpoints\n\n| Model ID | Version | Params | Backend | Context |\n| -------- | ------- | ------ | ------- | ------- |\n| `google/timesfm-2.5-200m-pytorch` | 2.5 | 200M | PyTorch | 16,384 |\n| `google/timesfm-2.5-200m-flax` | 2.5 | 200M | JAX/Flax | 16,384 |\n| `google/timesfm-2.5-200m-transformers` | 2.5 | 200M | Transformers | 16,384 |\n| `google/timesfm-2.0-500m-pytorch` | 2.0 | 500M | PyTorch | 2,048 |\n| `google/timesfm-2.0-500m-jax` | 2.0 | 500M | JAX | 2,048 |\n| `google/timesfm-1.0-200m-pytorch` | 1.0 | 200M | PyTorch | 2,048 |\n| `google/timesfm-1.0-200m` | 1.0 | 200M | JAX | 2,048 |\n\n---\n\n## Output Shape Reference\n\n| Output | Shape | Description |\n| ------ | ----- | ----------- |\n| `point_forecast` | `(B, H)` | Median forecast for B series, H steps |\n| `quantile_forecast` | `(B, H, 10)` | Full quantile distribution |\n| `quantile_forecast[:,:,0]` | `(B, H)` | Mean |\n| `quantile_forecast[:,:,1]` | `(B, H)` | 10th percentile |\n| `quantile_forecast[:,:,5]` | `(B, H)` | 50th percentile (= point_forecast) |\n| `quantile_forecast[:,:,9]` | `(B, H)` | 90th percentile |\n\nWhere `B` = batch size (number of input series), `H` = forecast horizon.\n\n---\n\n---\n\n## Memory Estimation\n\nBefore running forecasts on large datasets, estimate memory requirements:\n\n### Formula\n\n```mermaid\nblock-beta\n    columns 3\n    ram[\"Total RAM Required\"] model[\"Model Weights<br/>~0.8 GB\"] overhead[\"Runtime Overhead<br/>~0.5 GB\"] buffers[\"I/O Buffers<br/>~0.2 MB per 1000 series<br/>per 1000 context\"]\n    \n    ram --> model\n    ram --> overhead\n    ram --> buffers\n```\n\n**Formula**:  \n`RAM (GB) ≈ 0.8 + 0.5 + (0.0002 × num_series × context_length)`\n\n**Variables**:\n- `num_series`: Number of time series in your batch\n- `context_length`: Your `max_context` value (or max series length)\n- `batch_size`: Your `per_core_batch_size` (affects parallel processing overhead)\n\n### Quick Reference\n\n| Dataset Size | Context=512 | Context=1024 | Context=2048 |\n|--------------|-------------|--------------|--------------|\n| 100 series | ~1.4 GB | ~1.5 GB | ~1.7 GB |\n| 1,000 series | ~1.9 GB | ~2.3 GB | ~3.1 GB |\n| 10,000 series| ~9.0 GB | ~17.0 GB | ~33.0 GB |\n\n### Using the Preflight Checker\n\n```bash\npython scripts/check_system.py \\\n  --num-series 1000 \\\n  --context-length 1024 \\\n  --batch-size 32\n```\n\nThis validates both system requirements AND dataset fit before loading the model.\n\n### Reducing Memory Usage\n\nIf your dataset is too large:\n\n1. **Reduce context length**: Use `max_context=512` instead of 1024+ (50% reduction)\n2. **Process in chunks**: Split large batches into smaller groups:\n\n```python\nCHUNK_SIZE = 100\nfor i in range(0, len(inputs), CHUNK_SIZE):\n    chunk = inputs[i:i+CHUNK_SIZE]\n    point, quantiles = model.forecast(horizon=H, inputs=chunk)\n    # Save chunk results\n```\n\n3. **Reduce batch size**: Lower `per_core_batch_size` (slower but less memory)\n4. **Use CPU**: If GPU OOM, the model will automatically fall back to CPU\n\n\n## Error Handling\n\n| Error | Cause | Fix |\n| ----- | ----- | --- |\n| `RuntimeError: Model is not compiled` | Called `forecast()` before `compile()` | Call `model.compile(ForecastConfig(...))` first |\n| `torch.cuda.OutOfMemoryError` | Batch too large for GPU | Reduce `per_core_batch_size` |\n| `ValueError: inputs must be list` | Passed array instead of list | Wrap in list: `[array]` |\n| `HfHubHTTPError` | Download failed | Check internet, set `HF_HOME` to writable dir |\n","isInternal":false,"tokens":2707,"sizeBytes":9747},{"name":"data_preparation.md","path":"timesfm-forecasting/references/data_preparation.md","rawUrl":"https://raw.githubusercontent.com/google-research/timesfm/HEAD/timesfm-forecasting/references/data_preparation.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Data Preparation for TimesFM\n\n## Input Format\n\nTimesFM accepts a **list of 1-D numpy arrays**. Each array represents one\nunivariate time series.\n\n```python\ninputs = [\n    np.array([1.0, 2.0, 3.0, 4.0, 5.0]),       # Series 1\n    np.array([10.0, 20.0, 15.0, 25.0]),          # Series 2 (different length)\n    np.array([100.0, 110.0, 105.0, 115.0, 120.0, 130.0]),  # Series 3\n]\n```\n\n### Key Properties\n\n- **Variable lengths**: Series in the same batch can have different lengths\n- **Float values**: Use `np.float32` or `np.float64`\n- **1-D only**: Each array must be 1-dimensional (not 2-D matrix rows)\n- **NaN handling**: Leading NaNs are stripped; internal NaNs are linearly interpolated\n\n## Loading from Common Formats\n\n### CSV — Single Series (Long Format)\n\n```python\nimport pandas as pd\nimport numpy as np\n\ndf = pd.read_csv(\"data.csv\", parse_dates=[\"date\"])\nvalues = df[\"value\"].values.astype(np.float32)\ninputs = [values]\n```\n\n### CSV — Multiple Series (Wide Format)\n\n```python\ndf = pd.read_csv(\"data.csv\", parse_dates=[\"date\"], index_col=\"date\")\ninputs = [df[col].dropna().values.astype(np.float32) for col in df.columns]\n```\n\n### CSV — Long Format with ID Column\n\n```python\ndf = pd.read_csv(\"data.csv\", parse_dates=[\"date\"])\ninputs = []\nfor series_id, group in df.groupby(\"series_id\"):\n    values = group.sort_values(\"date\")[\"value\"].values.astype(np.float32)\n    inputs.append(values)\n```\n\n### Pandas DataFrame\n\n```python\n# Single column\ninputs = [df[\"temperature\"].values.astype(np.float32)]\n\n# Multiple columns\ninputs = [df[col].dropna().values.astype(np.float32) for col in numeric_cols]\n```\n\n### Numpy Arrays\n\n```python\n# 2-D array (rows = series, cols = time steps)\ndata = np.load(\"timeseries.npy\")  # shape (N, T)\ninputs = [data[i] for i in range(data.shape[0])]\n\n# Or from 1-D\ninputs = [np.sin(np.linspace(0, 10, 200))]\n```\n\n### Excel\n\n```python\ndf = pd.read_excel(\"data.xlsx\", sheet_name=\"Sheet1\")\ninputs = [df[col].dropna().values.astype(np.float32) for col in df.select_dtypes(include=[np.number]).columns]\n```\n\n### Parquet\n\n```python\ndf = pd.read_parquet(\"data.parquet\")\ninputs = [df[col].dropna().values.astype(np.float32) for col in df.select_dtypes(include=[np.number]).columns]\n```\n\n### JSON\n\n```python\nimport json\n\nwith open(\"data.json\") as f:\n    data = json.load(f)\n\n# Assumes {\"series_name\": [values...], ...}\ninputs = [np.array(values, dtype=np.float32) for values in data.values()]\n```\n\n## NaN Handling\n\nTimesFM handles NaN values automatically:\n\n### Leading NaNs\n\nStripped before feeding to the model:\n\n```python\n# Input:  [NaN, NaN, 1.0, 2.0, 3.0]\n# Actual: [1.0, 2.0, 3.0]\n```\n\n### Internal NaNs\n\nLinearly interpolated:\n\n```python\n# Input:  [1.0, NaN, 3.0, NaN, NaN, 6.0]\n# Actual: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]\n```\n\n### Trailing NaNs\n\n**Not handled** — drop them before passing to the model:\n\n```python\nvalues = df[\"value\"].values.astype(np.float32)\n# Remove trailing NaNs\nwhile len(values) > 0 and np.isnan(values[-1]):\n    values = values[:-1]\ninputs = [values]\n```\n\n### Best Practice\n\n```python\ndef clean_series(arr: np.ndarray) -> np.ndarray:\n    \"\"\"Clean a time series for TimesFM input.\"\"\"\n    arr = np.asarray(arr, dtype=np.float32)\n    # Remove trailing NaNs\n    while len(arr) > 0 and np.isnan(arr[-1]):\n        arr = arr[:-1]\n    # Replace inf with NaN (will be interpolated)\n    arr[np.isinf(arr)] = np.nan\n    return arr\n\ninputs = [clean_series(df[col].values) for col in cols]\n```\n\n## Context Length Considerations\n\n| Context Length | Use Case | Notes |\n| -------------- | -------- | ----- |\n| 64–256 | Quick prototyping | Minimal context, fast |\n| 256–512 | Daily data, ~1 year | Good balance |\n| 512–1024 | Daily data, ~2-3 years | Standard production |\n| 1024–4096 | Hourly data, weekly patterns | More context = better |\n| 4096–16384 | High-frequency, long patterns | TimesFM 2.5 maximum |\n\n**Rule of thumb**: Provide at least 3–5 full cycles of the dominant pattern\n(e.g., for weekly seasonality with daily data, provide at least 21–35 days).\n\n## Covariates (XReg)\n\nTimesFM 2.5 supports exogenous variables through the `forecast_with_covariates()` API.\n\n### Types of Covariates\n\n| Type | Description | Example |\n| ---- | ----------- | ------- |\n| **Dynamic numerical** | Time-varying numeric features | Temperature, price, promotion spend |\n| **Dynamic categorical** | Time-varying categorical features | Day of week, holiday flag |\n| **Static categorical** | Fixed per-series features | Store ID, region, product category |\n\n### Preparing Covariates\n\nEach covariate must have length `context + horizon` for each series:\n\n```python\nimport numpy as np\n\ncontext_len = 100   # length of historical data\nhorizon = 24        # forecast horizon\ntotal_len = context_len + horizon\n\n# Dynamic numerical: temperature forecast for each series\ntemp = [\n    np.random.randn(total_len).astype(np.float32),  # Series 1\n    np.random.randn(total_len).astype(np.float32),  # Series 2\n]\n\n# Dynamic categorical: day of week (0-6) for each series\ndow = [\n    np.tile(np.arange(7), total_len // 7 + 1)[:total_len],  # Series 1\n    np.tile(np.arange(7), total_len // 7 + 1)[:total_len],  # Series 2\n]\n\n# Static categorical: one label per series\nregions = [\"east\", \"west\"]\n\n# Forecast with covariates\npoint, quantiles = model.forecast_with_covariates(\n    inputs=[values1, values2],\n    dynamic_numerical_covariates={\"temperature\": temp},\n    dynamic_categorical_covariates={\"day_of_week\": dow},\n    static_categorical_covariates={\"region\": regions},\n    xreg_mode=\"xreg + timesfm\",\n)\n```\n\n### XReg Modes\n\n| Mode | Description |\n| ---- | ----------- |\n| `\"xreg + timesfm\"` | Covariates processed first, then combined with TimesFM forecast |\n| `\"timesfm + xreg\"` | TimesFM forecast first, then adjusted by covariates |\n\n## Common Data Issues\n\n### Issue: Series too short\n\nTimesFM needs at least 1 data point, but more context = better forecasts.\n\n```python\nMIN_LENGTH = 32  # Practical minimum for meaningful forecasts\n\ninputs = [\n    arr for arr in raw_inputs\n    if len(arr[~np.isnan(arr)]) >= MIN_LENGTH\n]\n```\n\n### Issue: Series with constant values\n\nConstant series may produce NaN or zero-width prediction intervals:\n\n```python\nfor i, arr in enumerate(inputs):\n    if np.std(arr[~np.isnan(arr)]) < 1e-10:\n        print(f\"⚠️ Series {i} is constant — forecast will be flat\")\n```\n\n### Issue: Extreme outliers\n\nLarge outliers can destabilize forecasts even with normalization:\n\n```python\ndef clip_outliers(arr: np.ndarray, n_sigma: float = 5.0) -> np.ndarray:\n    \"\"\"Clip values beyond n_sigma standard deviations.\"\"\"\n    mu = np.nanmean(arr)\n    sigma = np.nanstd(arr)\n    if sigma > 0:\n        arr = np.clip(arr, mu - n_sigma * sigma, mu + n_sigma * sigma)\n    return arr\n```\n\n### Issue: Mixed frequencies in batch\n\nTimesFM handles each series independently, so you can mix frequencies:\n\n```python\ninputs = [\n    daily_sales,      # 365 points\n    weekly_revenue,   # 52 points\n    monthly_users,    # 24 points\n]\n# All forecasted in one batch — TimesFM handles different lengths\npoint, q = model.forecast(horizon=12, inputs=inputs)\n```\n\nHowever, the `horizon` is shared. If you need different horizons per series,\nforecast in separate calls.\n","isInternal":false,"tokens":1983,"sizeBytes":7205},{"name":"system_requirements.md","path":"timesfm-forecasting/references/system_requirements.md","rawUrl":"https://raw.githubusercontent.com/google-research/timesfm/HEAD/timesfm-forecasting/references/system_requirements.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# System Requirements for TimesFM\n\n## Hardware Tiers\n\nTimesFM can run on a variety of hardware configurations. This guide helps you\nchoose the right setup and tune performance for your machine.\n\n### How Context Limits Are Determined\n\nThe `max_context` values in each tier are **conservative recommendations** based on memory-performance tradeoffs, not hard limits. TimesFM 2.5 supports up to 16,384 context points, but smaller values are recommended for most use cases.\n\n**Why 512 and 1024?**\n\n| Factor | 512 Context | 1024 Context |\n|--------|-------------|--------------|\n| **Memory per 1000 series** | ~100 MB | ~200 MB |\n| **Typical Use Case** | Daily data, ~1-2 years | Daily data, ~2-3 years |\n| **Inference Speed** | Faster | Moderate |\n| **Hardware** | 4-8 GB RAM | 16 GB RAM or GPU |\n\n**Memory Formula**: `RAM ≈ model_weights + 0.5 GB + (0.2 MB × num_series × context_length / 1000)`\n\nWhere:\n- `model_weights` = ~800 MB (TimesFM 2.5)\n- `context_length` = your `max_context` value\n- `num_series` = number of time series in your batch\n\n**You can use larger contexts** if your hardware supports it:\n- **Up to 2048**: Requires ~16 GB RAM for moderate batch sizes\n- **Up to 4096**: Requires GPU or 32+ GB RAM\n- **Up to 16384**: Maximum supported, requires significant memory\n\nSee [Data Preparation Guide](data_preparation.md) for context length recommendations by data frequency.\n\n### Tier 1: Minimal (CPU-Only, 4–8 GB RAM)\n\n- **Use case**: Light exploration, single-series forecasting, prototyping\n- **Model**: TimesFM 2.5 (200M) only\n- **Batch size**: `per_core_batch_size=4`\n- **Context**: Limit `max_context=512`\n- **Expected speed**: ~2–5 seconds per 100-point series\n\n```python\nmodel.compile(timesfm.ForecastConfig(\n    max_context=512,\n    max_horizon=128,\n    per_core_batch_size=4,\n    normalize_inputs=True,\n    use_continuous_quantile_head=True,\n    fix_quantile_crossing=True,\n))\n```\n\n### Tier 2: Standard (CPU 16 GB or GPU 4–8 GB VRAM)\n\n- **Use case**: Batch forecasting (dozens of series), evaluation, production prototypes\n- **Model**: TimesFM 2.5 (200M)\n- **Batch size**: `per_core_batch_size=32` (CPU) or `64` (GPU)\n- **Context**: `max_context=1024`\n- **Expected speed**: ~0.5–1 second per 100-point series (GPU)\n\n```python\nmodel.compile(timesfm.ForecastConfig(\n    max_context=1024,\n    max_horizon=256,\n    per_core_batch_size=64,\n    normalize_inputs=True,\n    use_continuous_quantile_head=True,\n    fix_quantile_crossing=True,\n))\n```\n\n### Tier 3: Production (GPU 16+ GB VRAM or Apple Silicon 32+ GB)\n\n- **Use case**: Large-scale batch forecasting (thousands of series), long context\n- **Model**: TimesFM 2.5 (200M)\n- **Batch size**: `per_core_batch_size=128–256`\n- **Context**: `max_context=4096` or higher\n- **Expected speed**: ~0.1–0.3 seconds per 100-point series\n\n```python\nmodel.compile(timesfm.ForecastConfig(\n    max_context=4096,\n    max_horizon=256,\n    per_core_batch_size=128,\n    normalize_inputs=True,\n    use_continuous_quantile_head=True,\n    fix_quantile_crossing=True,\n))\n```\n\n### Tier 4: Legacy Models (v1.0/v2.0 — 500M parameters)\n\n- **⚠️ WARNING**: TimesFM v2.0 (500M) requires **≥ 16 GB RAM** (CPU) or **≥ 8 GB VRAM** (GPU)\n- **⚠️ WARNING**: TimesFM v1.0 legacy JAX version may require **≥ 32 GB RAM**\n- **Recommendation**: Unless you specifically need a legacy checkpoint, use TimesFM 2.5\n\n## Memory Estimation\n\n### CPU Memory (RAM)\n\nApproximate RAM usage during inference:\n\n| Component | TimesFM 2.5 (200M) | TimesFM 2.0 (500M) |\n| --------- | ------------------- | ------------------- |\n| Model weights | ~800 MB | ~2 GB |\n| Runtime overhead | ~500 MB | ~1 GB |\n| Input/output buffers | ~200 MB per 1000 series | ~500 MB per 1000 series |\n| **Total (small batch)** | **~1.5 GB** | **~3.5 GB** |\n| **Total (large batch)** | **~3 GB** | **~6 GB** |\n\n**Formula**: `RAM ≈ model_weights + 0.5 GB + (0.2 MB × num_series × context_length / 1000)`\n\n### GPU Memory (VRAM)\n\n| Component | TimesFM 2.5 (200M) |\n| --------- | ------------------- |\n| Model weights | ~800 MB |\n| KV cache + activations | ~200–500 MB (scales with context) |\n| Batch buffers | ~100 MB per 100 series at context=1024 |\n| **Total (batch=32)** | **~1.2 GB** |\n| **Total (batch=128)** | **~1.8 GB** |\n| **Total (batch=256)** | **~2.5 GB** |\n\n### Disk Space\n\n| Item | Size |\n| ---- | ---- |\n| TimesFM 2.5 safetensors | ~800 MB |\n| Hugging Face cache overhead | ~200 MB |\n| **Total download** | **~1 GB** |\n\nModel weights are downloaded once from Hugging Face Hub and cached in\n`~/.cache/huggingface/` (or `$HF_HOME`).\n\n## GPU Selection Guide\n\n### NVIDIA GPUs (CUDA)\n\n| GPU | VRAM | Recommended batch | Notes |\n| --- | ---- | ----------------- | ----- |\n| RTX 3060 | 12 GB | 64 | Good entry-level |\n| RTX 3090 / 4090 | 24 GB | 256 | Excellent for production |\n| A100 (40 GB) | 40 GB | 512 | Cloud/HPC |\n| A100 (80 GB) | 80 GB | 1024 | Cloud/HPC |\n| T4 | 16 GB | 128 | Cloud (Colab, AWS) |\n| V100 | 16–32 GB | 128–256 | Cloud |\n\n### Apple Silicon (MPS)\n\n| Chip | Unified Memory | Recommended batch | Notes |\n| ---- | -------------- | ----------------- | ----- |\n| M1 | 8–16 GB | 16–32 | Works, slower than CUDA |\n| M1 Pro/Max | 16–64 GB | 32–128 | Good performance |\n| M2/M3/M4 Pro/Max | 18–128 GB | 64–256 | Excellent |\n\n### CPU Only\n\nWorks on any CPU with sufficient RAM. Expect 5–20× slower than GPU.\n\n## Python and Package Requirements\n\n| Requirement | Minimum | Recommended |\n| ----------- | ------- | ----------- |\n| Python | 3.10 | 3.12+ |\n| numpy | 1.26.4 | latest |\n| torch | 2.0.0 | latest |\n| huggingface_hub | 0.23.0 | latest |\n| safetensors | 0.5.3 | latest |\n\n### Optional Dependencies\n\n| Package | Purpose | Install |\n| ------- | ------- | ------- |\n| jax | Flax backend | `pip install jax[cuda]` |\n| flax | Flax backend | `pip install flax` |\n| scikit-learn | XReg covariates | `pip install scikit-learn` |\n\n## Operating System Compatibility\n\n| OS | Status | Notes |\n| -- | ------ | ----- |\n| Linux (Ubuntu 20.04+) | ✅ Fully supported | Best performance with CUDA |\n| macOS 13+ (Ventura) | ✅ Fully supported | MPS acceleration on Apple Silicon |\n| Windows 11 + WSL2 | ✅ Supported | Use WSL2 for best experience |\n| Windows (native) | ⚠️ Partial | PyTorch works, some edge cases |\n\n## Troubleshooting\n\n### Out of Memory (OOM)\n\n```python\n# Reduce batch size\nmodel.compile(timesfm.ForecastConfig(\n    per_core_batch_size=4,  # Start very small\n    max_context=512,        # Reduce context\n    ...\n))\n\n# Process in chunks\nfor i in range(0, len(inputs), 50):\n    chunk = inputs[i:i+50]\n    p, q = model.forecast(horizon=H, inputs=chunk)\n```\n\n### Slow Inference on CPU\n\n```python\n# Ensure matmul precision is set\nimport torch\ntorch.set_float32_matmul_precision(\"high\")\n\n# Use smaller context\nmodel.compile(timesfm.ForecastConfig(\n    max_context=256,  # Shorter context = faster\n    ...\n))\n```\n\n### Model Download Fails\n\n```bash\n# Set a different cache directory\nexport HF_HOME=/path/with/more/space\n\n# Or download manually\nhuggingface-cli download google/timesfm-2.5-200m-pytorch\n```\n","isInternal":false,"tokens":2072,"sizeBytes":7071},{"name":"SKILL.md","path":"timesfm-forecasting/SKILL.md","rawUrl":"https://raw.githubusercontent.com/google-research/timesfm/HEAD/timesfm-forecasting/SKILL.md","title":"timesfm-forecasting","category":"anthropic-skill","format":"markdown","content":"---\nname: timesfm-forecasting\ndescription: >\n  Zero-shot time series forecasting with Google's TimesFM foundation model. Use this\n  skill when forecasting ANY univariate time series — sales, sensor readings, stock prices,\n  energy demand, patient vitals, weather, or scientific measurements — without training a\n  custom model. Supports both basic forecasting and advanced covariate forecasting (XReg)\n  with dynamic and static exogenous variables. Automatically checks system RAM/GPU before\n  loading the model, validates dataset fit before processing, supports CSV/DataFrame/array\n  inputs, and returns point forecasts with calibrated prediction intervals. Includes a\n  preflight system checker script that MUST be run before first use to verify the machine\n  can load the model and handle your specific dataset.\nlicense: Apache-2.0\nmetadata:\n  author: Clayton Young (@borealBytes)\n  version: \"1.0.0\"\n---\n\n# TimesFM Forecasting\n\n## Overview\n\nTimesFM (Time Series Foundation Model) is a pretrained decoder-only foundation model\ndeveloped by Google Research for time-series forecasting. It works **zero-shot** — feed it\nany univariate time series and it returns point forecasts with calibrated quantile\nprediction intervals, no training required.\n\nThis skill includes a **mandatory preflight system checker** that verifies RAM, GPU memory,\nand disk space before the model is ever loaded so the agent never crashes the user's machine.\n\n> **Key numbers**: TimesFM 2.5 uses 200M parameters (~800 MB on disk, ~1.5 GB in RAM on\n> CPU, ~1 GB VRAM on GPU). The archived v1/v2 500M-parameter model needs ~32 GB RAM.\n> Always run the system checker first.\n\n## When to Use This Skill\n\nUse this skill when:\n\n- Forecasting **any univariate time series** (sales, demand, sensor, vitals, price, weather)\n- You need **zero-shot forecasting** without training a custom model\n- You want **probabilistic forecasts** with calibrated prediction intervals (quantiles)\n- You have time series of **any length** (the model handles 1–16,384 context points)\n- You need to **batch-forecast** hundreds or thousands of series efficiently\n- You want a **foundation model** approach instead of hand-tuning ARIMA/ETS parameters\n- You need **covariate forecasting** with exogenous variables (price, promotions, holidays, day-of-week effects) → use `forecast_with_covariates()` (TimesFM 2.5 + `pip install timesfm[xreg]`)\n\n\nDo **not** use this skill when:\n\n- You need classical statistical models with coefficient interpretation → use `statsmodels`\n- You need time series classification or clustering → use `aeon`\n- You need multivariate vector autoregression or Granger causality → use `statsmodels`\n- Your data is tabular (not temporal) → use `scikit-learn`\n- You cannot install optional dependencies → XReg requires scikit-learn and JAX\n\n\n> **Note on Anomaly Detection**: TimesFM does not have built-in anomaly detection, but you\n> can use the **quantile forecasts as prediction intervals** — values outside the 90% CI\n> (q10–q90) are statistically unusual. See `examples/anomaly-detection/` for a full example.\n\n## ⚠️ Mandatory Preflight: System Requirements Check\n\n**CRITICAL — ALWAYS run the system checker before loading the model for the first time.**\n\n```bash\npython scripts/check_system.py\n```\n\nThis script checks:\n\n1. **Available RAM** — warns if below 4 GB, blocks if below 2 GB\n2. **GPU availability** — detects CUDA/MPS devices and VRAM\n3. **Disk space** — verifies room for the ~800 MB model download\n4. **Python version** — requires 3.10+\n5. **Existing installation** — checks if `timesfm` and `torch` are installed\n\n> **Note:** Model weights are **NOT stored in this repository**. TimesFM weights (~800 MB)\n> download on-demand from HuggingFace on first use and cache in `~/.cache/huggingface/`.\n\n```mermaid\nflowchart TD\n    start[\"🚀 Run check_system.py\"] --> ram{\"RAM ≥ 4 GB?\"}\n    ram -->|\"Yes\"| gpu{\"GPU available?\"}\n    ram -->|\"No (2-4 GB)\"| warn_ram[\"⚠️ Warning: tight RAM<br/>CPU-only, small batches\"]\n    ram -->|\"No (< 2 GB)\"| block[\"🛑 BLOCKED<br/>Insufficient memory\"]\n    warn_ram --> disk\n    gpu -->|\"CUDA / MPS\"| vram{\"VRAM ≥ 2 GB?\"}\n    gpu -->|\"CPU only\"| cpu_ok[\"✅ CPU mode<br/>Slower but works\"]\n    vram -->|\"Yes\"| gpu_ok[\"✅ GPU mode<br/>Fast inference\"]\n    vram -->|\"No\"| cpu_ok\n    gpu_ok --> disk{\"Disk ≥ 2 GB free?\"}\n    cpu_ok --> disk\n    disk -->|\"Yes\"| ready[\"✅ READY<br/>Safe to load model\"]\n    disk -->|\"No\"| block_disk[\"🛑 BLOCKED<br/>Need space for weights\"]\n```\n\n### Dataset Preflight (NEW)\n\nBefore loading your actual data, verify it will fit in memory:\n\n```bash\n# Quick estimate for your dataset\npython scripts/check_system.py \\\n  --num-series 1000 \\\n  --context-length 1024 \\\n  --horizon 24 \\\n  --batch-size 32 \\\n  --estimate-only\n```\n\nThis will show you the estimated memory requirements and warn if your dataset is too large.\n\n**Memory Estimation Formula**:\n`RAM ≈ 0.8 GB (model) + 0.5 GB (overhead) + (0.2 MB × num_series × context_length / 1000)`\n\n**Example Outputs**:\n\n✅ **Dataset Fits**:\n```\nTotal CPU memory: 2.34 GB\nTotal GPU memory: 2.15 GB\n```\n\n⚠️ **Dataset Too Large**:\n```\nDataset requires ~12.5 GB RAM but system has 8.0 GB.\nTry: context_length=512 or process in chunks of 50 series.\n```\n\n### Hardware Requirements by Model Version\n\n| Model | Parameters | RAM (CPU) | VRAM (GPU) | Disk | Context |\n| ----- | ---------- | --------- | ---------- | ---- | ------- |\n| **TimesFM 2.5** (recommended) | 200M | ≥ 4 GB | ≥ 2 GB | ~800 MB | up to 16,384 |\n| TimesFM 2.0 (archived) | 500M | ≥ 16 GB | ≥ 8 GB | ~2 GB | up to 2,048 |\n| TimesFM 1.0 (archived) | 200M | ≥ 8 GB | ≥ 4 GB | ~800 MB | up to 2,048 |\n\n> **Recommendation**: Always use TimesFM 2.5 unless you have a specific reason to use an\n> older checkpoint. It is smaller, faster, and supports 8× longer context.\n\n## 🔧 Installation\n\n### Step 1: Verify System (always first)\n\n```bash\npython scripts/check_system.py\n```\n\n### Step 2: Install TimesFM\n\n```bash\n# Using uv (fast)\nuv pip install timesfm[torch]\n\n# Or using pip\npip install timesfm[torch]\n\n# For JAX/Flax backend (faster on TPU/GPU)\nuv pip install timesfm[flax]\n```\n\n### Step 3: Install PyTorch for Your Hardware\n\n```bash\n# CUDA 12.1 (NVIDIA GPU)\npip install torch>=2.0.0 --index-url https://download.pytorch.org/whl/cu121\n\n# CPU only\npip install torch>=2.0.0 --index-url https://download.pytorch.org/whl/cpu\n\n# Apple Silicon (MPS)\npip install torch>=2.0.0  # MPS support is built-in\n```\n\n## 🎯 Quick Start\n\n### Minimal Example\n\n```python\nimport torch, numpy as np, timesfm\n\ntorch.set_float32_matmul_precision(\"high\")\n\nmodel = timesfm.TimesFM_2p5_200M_torch.from_pretrained(\n    \"google/timesfm-2.5-200m-pytorch\"\n)\nmodel.compile(timesfm.ForecastConfig(\n    max_context=1024, max_horizon=256, normalize_inputs=True,\n    use_continuous_quantile_head=True, force_flip_invariance=True,\n    infer_is_positive=True, fix_quantile_crossing=True,\n))\n\npoint, quantiles = model.forecast(horizon=24, inputs=[\n    np.sin(np.linspace(0, 20, 200)),  # any 1-D array\n])\n# point.shape == (1, 24)         — median forecast\n# quantiles.shape == (1, 24, 10) — 10th–90th percentile bands\n```\n\n### Forecast with Covariates (XReg)\n\nTimesFM 2.5+ supports exogenous variables through `forecast_with_covariates()`.\nRequires `pip install timesfm[xreg]`.\n\n```python\npoint, quantiles = model.forecast_with_covariates(\n    inputs=inputs,\n    dynamic_numerical_covariates={\"price\": price_arrays},\n    dynamic_categorical_covariates={\"holiday\": holiday_arrays},\n    static_categorical_covariates={\"region\": region_labels},\n    xreg_mode=\"xreg + timesfm\",  # or \"timesfm + xreg\"\n)\n```\n\n### Anomaly Detection (via Quantile Intervals)\n\n```python\npoint, q = model.forecast(horizon=H, inputs=[values])\n\nlower_90 = q[0, :, 1]  # 10th percentile\nupper_90 = q[0, :, 9]  # 90th percentile\n\nactual = test_values\nanomalies = (actual < lower_90) | (actual > upper_90)\n```\n\n| Severity | Condition | Interpretation |\n| -------- | --------- | -------------- |\n| **Normal** | Inside 80% CI | Expected behavior |\n| **Warning** | Outside 80% CI | Unusual but possible |\n| **Critical** | Outside 90% CI | Statistically rare (< 10% probability) |\n\n> See `examples/anomaly-detection/` for a complete worked example with visualization.\n\n## 📊 Understanding the Output\n\nTimesFM returns `(point_forecast, quantile_forecast)`:\n\n- **`point_forecast`**: shape `(batch, horizon)` — the median (0.5 quantile)\n- **`quantile_forecast`**: shape `(batch, horizon, 10)` — ten quantile slices:\n\n| Index | Quantile | Use |\n| ----- | -------- | --- |\n| 0 | Mean | Average prediction |\n| 1 | 0.1 | Lower bound of 80% PI |\n| 2 | 0.2 | Lower bound of 60% PI |\n| **5** | **0.5** | **Median (= `point_forecast`)** |\n| 8 | 0.8 | Upper bound of 60% PI |\n| 9 | 0.9 | Upper bound of 80% PI |\n\n```python\npoint, q = model.forecast(horizon=H, inputs=data)\n\nlower_80 = q[:, :, 1]  # 10th percentile\nupper_80 = q[:, :, 9]  # 90th percentile\nmedian   = q[:, :, 5]\n```\n\n## 🔧 ForecastConfig Reference\n\nAll forecasting behavior is controlled by `timesfm.ForecastConfig`:\n\n```python\ntimesfm.ForecastConfig(\n    max_context=1024,                    # Max context window\n    max_horizon=256,                     # Max forecast horizon\n    normalize_inputs=True,               # RECOMMENDED — prevents scale instability\n    per_core_batch_size=32,              # Tune for memory\n    use_continuous_quantile_head=True,   # Better quantile accuracy for long horizons\n    force_flip_invariance=True,          # Ensures f(-x) = -f(x)\n    infer_is_positive=True,              # Clamp forecasts ≥ 0 when all inputs > 0\n    fix_quantile_crossing=True,          # Ensure q10 ≤ q20 ≤ ... ≤ q90\n    return_backcast=False,               # Return backcast (for covariate workflows)\n)\n```\n\n| Parameter | Default | When to Change |\n| --------- | ------- | -------------- |\n| `max_context` | 0 | Set to match your longest historical window |\n| `normalize_inputs` | False | **Always set True** |\n| `use_continuous_quantile_head` | False | **Set True** for calibrated PIs |\n| `infer_is_positive` | True | Set False for series that can be negative |\n| `fix_quantile_crossing` | False | **Set True** for monotonic quantiles |\n\nSee `references/api_reference.md` for the complete parameter reference.\n\n## 📋 Common Workflows\n\n### Single Series Forecast\n\n```python\nimport torch, numpy as np, pandas as pd, timesfm, matplotlib\nmatplotlib.use(\"Agg\")\nimport matplotlib.pyplot as plt\n\ntorch.set_float32_matmul_precision(\"high\")\nmodel = timesfm.TimesFM_2p5_200M_torch.from_pretrained(\n    \"google/timesfm-2.5-200m-pytorch\"\n)\nmodel.compile(timesfm.ForecastConfig(\n    max_context=512, max_horizon=52, normalize_inputs=True,\n    use_continuous_quantile_head=True, fix_quantile_crossing=True,\n))\n\ndf = pd.read_csv(\"weekly_demand.csv\", parse_dates=[\"week\"])\nvalues = df[\"demand\"].values.astype(np.float32)\n\npoint, quantiles = model.forecast(horizon=52, inputs=[values])\n\nfig, ax = plt.subplots(figsize=(12, 5))\nax.plot(values[-104:], label=\"Historical\")\nx_fc = range(len(values[-104:]), len(values[-104:]) + 52)\nax.plot(x_fc, point[0], label=\"Forecast\", color=\"tab:orange\")\nax.fill_between(x_fc, quantiles[0, :, 1], quantiles[0, :, 9],\n                alpha=0.2, color=\"tab:orange\", label=\"80% PI\")\nax.legend(); ax.set_title(\"52-Week Demand Forecast\")\nplt.tight_layout(); plt.savefig(\"forecast.png\", dpi=150)\n```\n\n### Batch Forecasting (Many Series)\n\n```python\ndf = pd.read_csv(\"all_stores.csv\", parse_dates=[\"date\"], index_col=\"date\")\ninputs = [df[col].dropna().values.astype(np.float32) for col in df.columns]\n\npoint, quantiles = model.forecast(horizon=30, inputs=inputs)\n\nimport json\nresults = {col: {\"forecast\": point[i].tolist(),\n                 \"lower_80\": quantiles[i, :, 1].tolist(),\n                 \"upper_80\": quantiles[i, :, 9].tolist()}\n           for i, col in enumerate(df.columns)}\nwith open(\"batch_forecasts.json\", \"w\") as f:\n    json.dump(results, f, indent=2)\n```\n\n### Evaluate Forecast Accuracy\n\n```python\nH = 24\ntrain, actual = values[:-H], values[-H:]\npoint, quantiles = model.forecast(horizon=H, inputs=[train])\npred = point[0]\n\nmae  = np.mean(np.abs(actual - pred))\nrmse = np.sqrt(np.mean((actual - pred) ** 2))\nmape = np.mean(np.abs((actual - pred) / actual)) * 100\ncoverage = np.mean((actual >= quantiles[0, :, 1]) & (actual <= quantiles[0, :, 9])) * 100\n\nprint(f\"MAE: {mae:.2f} | RMSE: {rmse:.2f} | MAPE: {mape:.1f}% | 80% PI Coverage: {coverage:.1f}%\")\n```\n\n## ⚙️ Performance Tuning\n\n```python\n# Always set on Ampere+ GPUs (A100, RTX 3090+)\ntorch.set_float32_matmul_precision(\"high\")\n\n# Batch size guidelines:\n# GPU 8 GB VRAM:  per_core_batch_size=64\n# GPU 16 GB VRAM: per_core_batch_size=128\n# CPU 8 GB RAM:   per_core_batch_size=8\n# CPU 16 GB RAM:  per_core_batch_size=32\n\n# Memory-constrained: process in chunks\nCHUNK = 50\nresults = []\nfor i in range(0, len(inputs), CHUNK):\n    p, q = model.forecast(horizon=H, inputs=inputs[i:i+CHUNK])\n    results.append((p, q))\n```\n\n## 📚 Available Scripts\n\n### `scripts/check_system.py`\n\nMandatory preflight checker — run before first model load.\nNow includes **dataset-aware memory estimation** to prevent OOM errors before loading your data.\n\n```bash\n# Basic system check\npython scripts/check_system.py\n\n# Check if your specific dataset will fit\npython scripts/check_system.py \\\n  --num-series 1000 \\\n  --context-length 1024 \\\n  --horizon 24 \\\n  --batch-size 32\n\n# Quick memory estimate without system checks\npython scripts/check_system.py \\\n  --num-series 5000 \\\n  --context-length 2048 \\\n  --estimate-only\n```\n\n**What it checks**:\n\n1. **Available RAM** — warns if below 4 GB, blocks if below 2 GB\n2. **GPU availability** — detects CUDA/MPS devices and VRAM\n3. **Disk space** — verifies room for the ~800 MB model download\n4. **Python version** — requires 3.10+\n5. **Existing installation** — checks if `timesfm` and `torch` are installed\n6. **Dataset fit** (NEW) — estimates memory for your specific dataset and warns if it won't fit\n\n### `scripts/forecast_csv.py`\n\nEnd-to-end CSV forecasting CLI.\n\n```bash\npython scripts/forecast_csv.py input.csv \\\n    --horizon 24 \\\n    --date-col date \\\n    --value-cols sales,revenue \\\n    --output forecasts.csv\n```\n\n## 📖 Reference Documentation\n\n| File | Contents |\n| ---- | -------- |\n| `references/system_requirements.md` | Hardware tiers, GPU/CPU selection, memory estimation |\n| `references/api_reference.md` | Full `ForecastConfig` docs, output shapes, model options |\n| `references/data_preparation.md` | Input formats, NaN handling, CSV loading, covariate setup |\n\n## 🧪 Examples\n\n| Example | Directory | What It Demonstrates |\n| ------- | --------- | -------------------- |\n| **Global Temperature Forecast** | `examples/global-temperature/` | Basic `model.forecast()`, CSV → PNG → GIF pipeline |\n| **Anomaly Detection** | `examples/anomaly-detection/` | Two-phase detrend + Z-score + quantile PI, 2-panel viz |\n| **Covariates (XReg)** | `examples/covariates-forecasting/` | `forecast_with_covariates()`, 2×2 shared-axis viz |\n\n```bash\n# Run all three examples:\ncd examples/global-temperature && python run_forecast.py && python visualize_forecast.py\ncd examples/anomaly-detection  && python detect_anomalies.py\ncd examples/covariates-forecasting && python demo_covariates.py\n```\n\n### Expected Outputs\n\n| Example | Key output files | Acceptance criteria |\n| ------- | ---------------- | ------------------- |\n| global-temperature | `output/forecast_output.json`, `output/forecast_visualization.png` | `point_forecast` has 12 values; PNG shows context + forecast + PI bands |\n| anomaly-detection | `output/anomaly_detection.json`, `output/anomaly_detection.png` | Sep 2023 flagged CRITICAL (z ≥ 3.0) |\n| covariates-forecasting | `output/sales_with_covariates.csv`, `output/covariates_data.png` | 108 rows (3 stores × 36 weeks); distinct price arrays per store |\n\n## Model Versions\n\n| Version | Params | Context | Status | HuggingFace checkpoint |\n| ------- | ------ | ------- | ------ | ---------------------- |\n| **2.5** | 200M | 16,384 | **Latest** | `google/timesfm-2.5-200m-pytorch` |\n| 2.0 | 500M | 2,048 | Archived | `google/timesfm-2.0-500m-pytorch` |\n| 1.0 | 200M | 2,048 | Archived | `google/timesfm-1.0-200m-pytorch` |\n\n- TimesFM 1.0/2.0: must pass `freq=[0]` for monthly data\n- TimesFM 2.5: no frequency flag — it was removed\n\n## Resources\n\n- **Paper**: [A Decoder-Only Foundation Model for Time-Series Forecasting](https://arxiv.org/abs/2310.10688) (ICML 2024)\n- **HuggingFace**: https://huggingface.co/collections/google/timesfm-release-66e4be5fdb56e960c1e482a6\n- **Google Blog**: https://research.google/blog/a-decoder-only-foundation-model-for-time-series-forecasting/\n- **BigQuery Integration**: https://cloud.google.com/bigquery/docs/timesfm-model\n\n## Quality Checklist\n\nRun after every TimesFM task before declaring success:\n\n- [ ] **Output shape** — `point_fc` is `(n_series, horizon)`, `quant_fc` is `(n_series, horizon, 10)`\n- [ ] **Quantile indices** — index 0 = mean, 1 = q10 ... 9 = q90. NOT 0 = q0.\n- [ ] **Frequency flag** — TimesFM 1.0/2.0: pass `freq=[0]` for monthly. TimesFM 2.5: omit.\n- [ ] **Series length** — context must be ≥ 32 data points.\n- [ ] **No NaN** — `np.isnan(point_fc).any()` must be False.\n- [ ] **Axes** — multiple panels sharing data must use `sharex=True`.\n- [ ] **`matplotlib.use('Agg')`** — before any pyplot import when running headless.\n- [ ] **`infer_is_positive`** — set False for temperature, financial returns, negatives.\n\n## Common Mistakes\n\n1. **Quantile index off-by-one** — `quant_fc[..., 0]` is the **mean**, not q0. q10 = index 1, q90 = index 9. Define: `IDX_Q10, IDX_Q90 = 1, 9`.\n\n2. **Variable shadowing in covariate loops** — don't use the outer loop variable as a comprehension variable when building per-series covariate dicts.\n\n3. **Wrong CSV column name** — global-temperature CSV uses `anomaly_c`, not `anomaly`. Print `df.columns` first.\n\n4. **TimesFM 2.5 required for `forecast_with_covariates()`** — TimesFM 1.0 does NOT have this method.\n\n5. **Future covariates must span the full horizon** — dynamic covariates need values for BOTH context AND forecast windows.\n\n6. **Context anomaly detection uses residuals** — detrend first, then Z-score. Raw Z-scores mislead on trending data.\n\n## Validation & Verification\n\n```bash\n# Anomaly detection regression:\npython -c \"\nimport json\nd = json.load(open('examples/anomaly-detection/output/anomaly_detection.json'))\nassert d['context_summary']['critical'] >= 1, 'Sep 2023 must be CRITICAL'\nprint('Anomaly detection: PASS')\"\n\n# Covariates regression:\npython -c \"\nimport pandas as pd\ndf = pd.read_csv('examples/covariates-forecasting/output/sales_with_covariates.csv')\nassert len(df) == 108, f'Expected 108 rows, got {len(df)}'\nprint('Covariates: PASS')\"\n```\n","frontmatter":{"name":"timesfm-forecasting","description":"Zero-shot time series forecasting with Google's TimesFM foundation model. Use this skill when forecasting ANY univariate time series — sales, sensor readings, stock prices, energy demand, patient vitals, weather, or scientific measurements — without training a custom model. Supports both basic forecasting and advanced covariate forecasting (XReg) with dynamic and static exogenous variables. Automatically checks system RAM/GPU before loading the model, validates dataset fit before processing, supports CSV/DataFrame/array inputs, and returns point forecasts with calibrated prediction intervals. Includes a preflight system checker script that MUST be run before first use to verify the machine can load the model and handle your specific dataset.\n","license":"Apache-2.0","metadata":{"author":"Clayton Young (@borealBytes)","version":"1.0.0"}},"isInternal":false,"tokens":5188,"sizeBytes":18897}],"systemPromptSnippet":"<yakaai_skills repo=\"google-research/timesfm\">\n<!-- File: AGENTS.md (Tokens: ~222 | Category: root-instruction) -->\n# TimesFM — Agent Entry Point\n\nThis repository ships a first-party **Agent Skill** for TimesFM at:\n\n```\ntimesfm-forecasting/\n└── SKILL.md    ← read this for the full skill\n```\n\n## Install the skill\n\nCopy the skill directory into your agent's skills folder:\n\n```bash\n# Cursor / Claude Code / OpenCode / Codex (global install)\ncp -r timesfm-forecasting/ ~/.cursor/skills/\ncp -r timesfm-forecasting/ ~/.claude/skills/\n\n# Or project-level\ncp -r timesfm-forecasting/ .cursor/skills/\n```\n\nAny agent that supports the open [Agent Skills standard](https://agentskills.io) will discover it automatically.\n\n## Working in this repo\n\nIf you are developing TimesFM itself (not using it), the source lives in `src/timesfm/`.\nArchived v1/v2 code and notebooks are in `v1/`.\n\nRun tests:\n\n```bash\npytest v1/tests/\n```\n\nSee `README.md` for full developer setup.\n\n<!-- File: timesfm-forecasting/references/api_reference.md (Tokens: ~2707 | Category: anthropic-skill) -->\n# TimesFM API Reference\n\n## Model Classes\n\n### `timesfm.TimesFM_2p5_200M_torch`\n\nThe primary model class for TimesFM 2.5 (200M parameters, PyTorch backend).\n\n#### `from_pretrained()`\n\n```python\nmodel = timesfm.TimesFM_2p5_200M_torch.from_pretrained(\n    \"google/timesfm-2.5-200m-pytorch\",\n    cache_dir=None,         # Optional: custom cache directory\n    force_download=True,    # Re-download even if cached\n)\n```\n\n| Parameter | Type | Default | Description |\n| --------- | ---- | ------- | ----------- |\n| `model_id` | str | `\"google/timesfm-2.5-200m-pytorch\"` | Hugging Face model ID |\n| `revision` | str \\| None | None | Specific model revision |\n| `cache_dir` | str \\| Path \\| None | None | Custom cache directory |\n| `force_download` | bool | True | Force re-download of weights |\n\n**Returns**: Initialized `TimesFM_2p5_200M_torch` instance (not yet compiled).\n\n#### `compile()`\n\nCompiles the model with the given forecast configuration. **Must be called before `forecast()`.**\n\n```python\nmodel.compile(\n    timesfm.ForecastConfig(\n        max_context=1024,\n        max_horizon=256,\n        normalize_inputs=True,\n        per_core_batch_size=32,\n        use_continuous_quantile_head=True,\n        force_flip_invariance=True,\n        infer_is_positive=True,\n        fix_quantile_crossing=True,\n    )\n)\n```\n\n**Raises**: Nothing (but `forecast()` will raise `RuntimeError` if not compiled).\n\n#### `forecast()`\n\nRun inference on one or more time series.\n\n```python\npoint_forecast, quantile_forecast = model.forecast(\n    horizon=24,\n    inputs=[array1, array2, ...],\n)\n```\n\n| Parameter | Type | Description |\n| --------- | ---- | ----------- |\n| `horizon` | int | Number of future steps to forecast |\n| `inputs` | list[np.ndarray] | List of 1-D numpy arrays (each is a time series) |\n\n**Returns**: `tuple[np.ndarray, np.ndarray]`\n\n- `point_forecast`: shape `(batch_size, horizon)` — median (0.5 quantile)\n- `quantile_forecast`: shape `(batch_size, horizon, 10)` — [mean, q10, q20, ..., q90]\n\n**Raises**: `RuntimeError` if model is not compiled.\n\n**Key behaviors**:\n\n- Leading NaN values are stripped automatically\n- Internal NaN values are linearly interpolated\n- Series longer than `max_context` are truncated (last `max_context` points used)\n- Series shorter than `max_context` are padded\n\n#### `forecast_with_covariates()`\n\nRun inference with exogenous variables (requires `timesfm[xreg]`).\n\n```python\npoint, quantiles = model.forecast_with_covariates(\n    inputs=inputs,\n    dynamic_numerical_covariates={\"temp\": [temp_array1, temp_array2]},\n    dynamic_categorical_covariates={\"dow\": [dow_array1, dow_array2]},\n    static_categorical_covariates={\"region\": [\"east\", \"west\"]},\n    xreg_mode=\"xreg + timesfm\",\n)\n```\n\n| Parameter | Type | Description |\n| --------- | ---- | ----------- |\n| `inputs` | list[np.ndarray] | Target time series |\n| `dynamic_numerical_covariates` | dict[str, list[np.ndarray]] | Time-varying numeric features |\n| `dynamic_categorical_covariates` | dict[str, list[np.ndarray]] | Time-varying categorical features |\n| `static_categorical_covariates` | dict[str, list[str]] | Fixed categorical features per series |\n| `xreg_mode` | str | `\"xreg + timesfm\"` or `\"timesfm + xreg\"` |\n\n**Note**: Dynamic covariates must have length `context + horizon` for each series.\n\n---\n\n## `timesfm.ForecastConfig`\n\nImmutable dataclass controlling all forecast behavior.\n\n```python\n@dataclasses.dataclass(frozen=True)\nclass ForecastConfig:\n    max_context: int = 0\n    max_horizon: int = 0\n    normalize_inputs: bool = False\n    per_core_batch_size: int = 1\n    use_continuous_quantile_head: bool = False\n    force_flip_invariance: bool = True\n    infer_is_positive: bool = True\n    fix_quantile_crossing: bool = False\n    return_backcast: bool = False\n    quantiles: list[float] = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]\n    decode_index: int = 5\n```\n\n### Parameter Details\n\n#### `max_context` (int, default=0)\n\nMaximum number of historical time points to use as context.\n\n- **0**: Use the model's maximum supported context (16,384 for v2.5)\n- **N**: Truncate series to last N points\n- **Best practice**: Set to the length of your longest series, or 512–2048 for speed\n\n#### `max_horizon` (int, default=0)\n\nMaximum forecast horizon.\n\n- **0**: Use the model's maximum\n- **N**: Forecasts up to N steps (can still call `forecast(horizon=M)` where M ≤ N)\n- **Best practice**: Set to your expected maximum forecast length\n\n#### `normalize_inputs` (bool, default=False)\n\nWhether to z-normalize each series before feeding to the model.\n\n- **True** (RECOMMENDED): Normalizes each series to zero mean, unit variance\n- **False**: Raw values are passed directly\n- **When False is OK**: Only if your series are already normalized or very close to scale 1.0\n\n#### `per_core_batch_size` (int, default=1)\n\nNumber of series processed per device in each batch.\n\n- Increase for throughput, decrease if OOM\n- See `references/system_requirements.md` for recommended values by hardware\n\n#### `use_continuous_quantile_head` (bool, default=False)\n\nUse the 30M-parameter continuous quantile head for better interval calibration.\n\n- **True** (RECOMMENDED): More accurate prediction intervals, especially for longer horizons\n- **False**: Uses fixed quantile buckets (faster but less accurate intervals)\n\n#### `force_flip_invariance` (bool, default=True)\n\nEnsures the model satisfies `f(-x) = -f(x)`.\n\n- **True** (RECOMMENDED): Mathematical consistency — forecasts are invariant to sign flip\n- **False**: Slightly faster but may produce asymmetric forecasts\n\n#### `infer_is_positive` (bool, default=True)\n\nAutomatically detect if all input values are positive and clamp forecasts ≥ 0.\n\n- **True**: Safe for sales, demand, counts, prices, volumes\n- **False**: Required for temperature, returns, PnL, any series that can be negative\n\n#### `fix_quantile_crossing` (bool, default=False)\n\nPost-process quantiles to ensure monotonicity (q10 ≤ q20 ≤ ... ≤ q90).\n\n- **True** (RECOMMENDED): Guarantees well-ordered quantiles\n- **False**: Slightly faster but quantiles may occasionally cross\n\n#### `return_backcast` (bool, default=False)\n\nReturn the model's reconstruction of the input (backcast) in addition to forecast.\n\n- **True**: Used for covariate workflows and diagnostics\n- **False**: Only return forecast\n\n---\n\n## Available Model Checkpoints\n\n| Model ID | Version | Params | Backend | Context |\n| -------- | ------- | ------ | ------- | ------- |\n| `google/timesfm-2.5-200m-pytorch` | 2.5 | 200M | PyTorch | 16,384 |\n| `google/timesfm-2.5-200m-flax` | 2.5 | 200M | JAX/Flax | 16,384 |\n| `google/timesfm-2.5-200m-transformers` | 2.5 | 200M | Transformers | 16,384 |\n| `google/timesfm-2.0-500m-pytorch` | 2.0 | 500M | PyTorch | 2,048 |\n| `google/timesfm-2.0-500m-jax` | 2.0 | 500M | JAX | 2,048 |\n| `google/timesfm-1.0-200m-pytorch` | 1.0 | 200M | PyTorch | 2,048 |\n| `google/timesfm-1.0-200m` | 1.0 | 200M | JAX | 2,048 |\n\n---\n\n## Output Shape Reference\n\n| Output | Shape | Description |\n| ------ | ----- | ----------- |\n| `point_forecast` | `(B, H)` | Median forecast for B series, H steps |\n| `quantile_forecast` | `(B, H, 10)` | Full quantile distribution |\n| `quantile_forecast[:,:,0]` | `(B, H)` | Mean |\n| `quantile_forecast[:,:,1]` | `(B, H)` | 10th percentile |\n| `quantile_forecast[:,:,5]` | `(B, H)` | 50th percentile (= point_forecast) |\n| `quantile_forecast[:,:,9]` | `(B, H)` | 90th percentile |\n\nWhere `B` = batch size (number of input series), `H` = forecast horizon.\n\n---\n\n---\n\n## Memory Estimation\n\nBefore running forecasts on large datasets, estimate memory requirements:\n\n### Formula\n\n```mermaid\nblock-beta\n    columns 3\n    ram[\"Total RAM Required\"] model[\"Model Weights<br/>~0.8 GB\"] overhead[\"Runtime Overhead<br/>~0.5 GB\"] buffers[\"I/O Buffers<br/>~0.2 MB per 1000 series<br/>per 1000 context\"]\n    \n    ram --> model\n    ram --> overhead\n    ram --> buffers\n```\n\n**Formula**:  \n`RAM (GB) ≈ 0.8 + 0.5 + (0.0002 × num_series × context_length)`\n\n**Variables**:\n- `num_series`: Number of time series in your batch\n- `context_length`: Your `max_context` value (or max series length)\n- `batch_size`: Your `per_core_batch_size` (affects parallel processing overhead)\n\n### Quick Reference\n\n| Dataset Size | Context=512 | Context=1024 | Context=2048 |\n|--------------|-------------|--------------|--------------|\n| 100 series | ~1.4 GB | ~1.5 GB | ~1.7 GB |\n| 1,000 series | ~1.9 GB | ~2.3 GB | ~3.1 GB |\n| 10,000 series| ~9.0 GB | ~17.0 GB | ~33.0 GB |\n\n### Using the Preflight Checker\n\n```bash\npython scripts/check_system.py \\\n  --num-series 1000 \\\n  --context-length 1024 \\\n  --batch-size 32\n```\n\nThis validates both system requirements AND dataset fit before loading the model.\n\n### Reducing Memory Usage\n\nIf your dataset is too large:\n\n1. **Reduce context length**: Use `max_context=512` instead of 1024+ (50% reduction)\n2. **Process in chunks**: Split large batches into smaller groups:\n\n```python\nCHUNK_SIZE = 100\nfor i in range(0, len(inputs), CHUNK_SIZE):\n    chunk = inputs[i:i+CHUNK_SIZE]\n    point, quantiles = model.forecast(horizon=H, inputs=chunk)\n    # Save chunk results\n```\n\n3. **Reduce batch size**: Lower `per_core_batch_size` (slower but less memory)\n4. **Use CPU**: If GPU OOM, the model will automatically fall back to CPU\n\n\n## Error Handling\n\n| Error | Cause | Fix |\n| ----- | ----- | --- |\n| `RuntimeError: Model is not compiled` | Called `forecast()` before `compile()` | Call `model.compile(ForecastConfig(...))` first |\n| `torch.cuda.OutOfMemoryError` | Batch too large for GPU | Reduce `per_core_batch_size` |\n| `ValueError: inputs must be list` | Passed array instead of list | Wrap in list: `[array]` |\n| `HfHubHTTPError` | Download failed | Check internet, set `HF_HOME` to writable dir |\n\n<!-- File: timesfm-forecasting/references/data_preparation.md (Tokens: ~1983 | Category: anthropic-skill) -->\n# Data Preparation for TimesFM\n\n## Input Format\n\nTimesFM accepts a **list of 1-D numpy arrays**. Each array represents one\nunivariate time series.\n\n```python\ninputs = [\n    np.array([1.0, 2.0, 3.0, 4.0, 5.0]),       # Series 1\n    np.array([10.0, 20.0, 15.0, 25.0]),          # Series 2 (different length)\n    np.array([100.0, 110.0, 105.0, 115.0, 120.0, 130.0]),  # Series 3\n]\n```\n\n### Key Properties\n\n- **Variable lengths**: Series in the same batch can have different lengths\n- **Float values**: Use `np.float32` or `np.float64`\n- **1-D only**: Each array must be 1-dimensional (not 2-D matrix rows)\n- **NaN handling**: Leading NaNs are stripped; internal NaNs are linearly interpolated\n\n## Loading from Common Formats\n\n### CSV — Single Series (Long Format)\n\n```python\nimport pandas as pd\nimport numpy as np\n\ndf = pd.read_csv(\"data.csv\", parse_dates=[\"date\"])\nvalues = df[\"value\"].values.astype(np.float32)\ninputs = [values]\n```\n\n### CSV — Multiple Series (Wide Format)\n\n```python\ndf = pd.read_csv(\"data.csv\", parse_dates=[\"date\"], index_col=\"date\")\ninputs = [df[col].dropna().values.astype(np.float32) for col in df.columns]\n```\n\n### CSV — Long Format with ID Column\n\n```python\ndf = pd.read_csv(\"data.csv\", parse_dates=[\"date\"])\ninputs = []\nfor series_id, group in df.groupby(\"series_id\"):\n    values = group.sort_values(\"date\")[\"value\"].values.astype(np.float32)\n    inputs.append(values)\n```\n\n### Pandas DataFrame\n\n```python\n# Single column\ninputs = [df[\"temperature\"].values.astype(np.float32)]\n\n# Multiple columns\ninputs = [df[col].dropna().values.astype(np.float32) for col in numeric_cols]\n```\n\n### Numpy Arrays\n\n```python\n# 2-D array (rows = series, cols = time steps)\ndata = np.load(\"timeseries.npy\")  # shape (N, T)\ninputs = [data[i] for i in range(data.shape[0])]\n\n# Or from 1-D\ninputs = [np.sin(np.linspace(0, 10, 200))]\n```\n\n### Excel\n\n```python\ndf = pd.read_excel(\"data.xlsx\", sheet_name=\"Sheet1\")\ninputs = [df[col].dropna().values.astype(np.float32) for col in df.select_dtypes(include=[np.number]).columns]\n```\n\n### Parquet\n\n```python\ndf = pd.read_parquet(\"data.parquet\")\ninputs = [df[col].dropna().values.astype(np.float32) for col in df.select_dtypes(include=[np.number]).columns]\n```\n\n### JSON\n\n```python\nimport json\n\nwith open(\"data.json\") as f:\n    data = json.load(f)\n\n# Assumes {\"series_name\": [values...], ...}\ninputs = [np.array(values, dtype=np.float32) for values in data.values()]\n```\n\n## NaN Handling\n\nTimesFM handles NaN values automatically:\n\n### Leading NaNs\n\nStripped before feeding to the model:\n\n```python\n# Input:  [NaN, NaN, 1.0, 2.0, 3.0]\n# Actual: [1.0, 2.0, 3.0]\n```\n\n### Internal NaNs\n\nLinearly interpolated:\n\n```python\n# Input:  [1.0, NaN, 3.0, NaN, NaN, 6.0]\n# Actual: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]\n```\n\n### Trailing NaNs\n\n**Not handled** — drop them before passing to the model:\n\n```python\nvalues = df[\"value\"].values.astype(np.float32)\n# Remove trailing NaNs\nwhile len(values) > 0 and np.isnan(values[-1]):\n    values = values[:-1]\ninputs = [values]\n```\n\n### Best Practice\n\n```python\ndef clean_series(arr: np.ndarray) -> np.ndarray:\n    \"\"\"Clean a time series for TimesFM input.\"\"\"\n    arr = np.asarray(arr, dtype=np.float32)\n    # Remove trailing NaNs\n    while len(arr) > 0 and np.isnan(arr[-1]):\n        arr = arr[:-1]\n    # Replace inf with NaN (will be interpolated)\n    arr[np.isinf(arr)] = np.nan\n    return arr\n\ninputs = [clean_series(df[col].values) for col in cols]\n```\n\n## Context Length Considerations\n\n| Context Length | Use Case | Notes |\n| -------------- | -------- | ----- |\n| 64–256 | Quick prototyping | Minimal context, fast |\n| 256–512 | Daily data, ~1 year | Good balance |\n| 512–1024 | Daily data, ~2-3 years | Standard production |\n| 1024–4096 | Hourly data, weekly patterns | More context = better |\n| 4096–16384 | High-frequency, long patterns | TimesFM 2.5 maximum |\n\n**Rule of thumb**: Provide at least 3–5 full cycles of the dominant pattern\n(e.g., for weekly seasonality with daily data, provide at least 21–35 days).\n\n## Covariates (XReg)\n\nTimesFM 2.5 supports exogenous variables through the `forecast_with_covariates()` API.\n\n### Types of Covariates\n\n| Type | Description | Example |\n| ---- | ----------- | ------- |\n| **Dynamic numerical** | Time-varying numeric features | Temperature, price, promotion spend |\n| **Dynamic categorical** | Time-varying categorical features | Day of week, holiday flag |\n| **Static categorical** | Fixed per-series features | Store ID, region, product category |\n\n### Preparing Covariates\n\nEach covariate must have length `context + horizon` for each series:\n\n```python\nimport numpy as np\n\ncontext_len = 100   # length of historical data\nhorizon = 24        # forecast horizon\ntotal_len = context_len + horizon\n\n# Dynamic numerical: temperature forecast for each series\ntemp = [\n    np.random.randn(total_len).astype(np.float32),  # Series 1\n    np.random.randn(total_len).astype(np.float32),  # Series 2\n]\n\n# Dynamic categorical: day of week (0-6) for each series\ndow = [\n    np.tile(np.arange(7), total_len // 7 + 1)[:total_len],  # Series 1\n    np.tile(np.arange(7), total_len // 7 + 1)[:total_len],  # Series 2\n]\n\n# Static categorical: one label per series\nregions = [\"east\", \"west\"]\n\n# Forecast with covariates\npoint, quantiles = model.forecast_with_covariates(\n    inputs=[values1, values2],\n    dynamic_numerical_covariates={\"temperature\": temp},\n    dynamic_categorical_covariates={\"day_of_week\": dow},\n    static_categorical_covariates={\"region\": regions},\n    xreg_mode=\"xreg + timesfm\",\n)\n```\n\n### XReg Modes\n\n| Mode | Description |\n| ---- | ----------- |\n| `\"xreg + timesfm\"` | Covariates processed first, then combined with TimesFM forecast |\n| `\"timesfm + xreg\"` | TimesFM forecast first, then adjusted by covariates |\n\n## Common Data Issues\n\n### Issue: Series too short\n\nTimesFM needs at least 1 data point, but more context = better forecasts.\n\n```python\nMIN_LENGTH = 32  # Practical minimum for meaningful forecasts\n\ninputs = [\n    arr for arr in raw_inputs\n    if len(arr[~np.isnan(arr)]) >= MIN_LENGTH\n]\n```\n\n### Issue: Series with constant values\n\nConstant series may produce NaN or zero-width prediction intervals:\n\n```python\nfor i, arr in enumerate(inputs):\n    if np.std(arr[~np.isnan(arr)]) < 1e-10:\n        print(f\"⚠️ Series {i} is constant — forecast will be flat\")\n```\n\n### Issue: Extreme outliers\n\nLarge outliers can destabilize forecasts even with normalization:\n\n```python\ndef clip_outliers(arr: np.ndarray, n_sigma: float = 5.0) -> np.ndarray:\n    \"\"\"Clip values beyond n_sigma standard deviations.\"\"\"\n    mu = np.nanmean(arr)\n    sigma = np.nanstd(arr)\n    if sigma > 0:\n        arr = np.clip(arr, mu - n_sigma * sigma, mu + n_sigma * sigma)\n    return arr\n```\n\n### Issue: Mixed frequencies in batch\n\nTimesFM handles each series independently, so you can mix frequencies:\n\n```python\ninputs = [\n    daily_sales,      # 365 points\n    weekly_revenue,   # 52 points\n    monthly_users,    # 24 points\n]\n# All forecasted in one batch — TimesFM handles different lengths\npoint, q = model.forecast(horizon=12, inputs=inputs)\n```\n\nHowever, the `horizon` is shared. If you need different horizons per series,\nforecast in separate calls.\n\n<!-- File: timesfm-forecasting/references/system_requirements.md (Tokens: ~2072 | Category: anthropic-skill) -->\n# System Requirements for TimesFM\n\n## Hardware Tiers\n\nTimesFM can run on a variety of hardware configurations. This guide helps you\nchoose the right setup and tune performance for your machine.\n\n### How Context Limits Are Determined\n\nThe `max_context` values in each tier are **conservative recommendations** based on memory-performance tradeoffs, not hard limits. TimesFM 2.5 supports up to 16,384 context points, but smaller values are recommended for most use cases.\n\n**Why 512 and 1024?**\n\n| Factor | 512 Context | 1024 Context |\n|--------|-------------|--------------|\n| **Memory per 1000 series** | ~100 MB | ~200 MB |\n| **Typical Use Case** | Daily data, ~1-2 years | Daily data, ~2-3 years |\n| **Inference Speed** | Faster | Moderate |\n| **Hardware** | 4-8 GB RAM | 16 GB RAM or GPU |\n\n**Memory Formula**: `RAM ≈ model_weights + 0.5 GB + (0.2 MB × num_series × context_length / 1000)`\n\nWhere:\n- `model_weights` = ~800 MB (TimesFM 2.5)\n- `context_length` = your `max_context` value\n- `num_series` = number of time series in your batch\n\n**You can use larger contexts** if your hardware supports it:\n- **Up to 2048**: Requires ~16 GB RAM for moderate batch sizes\n- **Up to 4096**: Requires GPU or 32+ GB RAM\n- **Up to 16384**: Maximum supported, requires significant memory\n\nSee [Data Preparation Guide](data_preparation.md) for context length recommendations by data frequency.\n\n### Tier 1: Minimal (CPU-Only, 4–8 GB RAM)\n\n- **Use case**: Light exploration, single-series forecasting, prototyping\n- **Model**: TimesFM 2.5 (200M) only\n- **Batch size**: `per_core_batch_size=4`\n- **Context**: Limit `max_context=512`\n- **Expected speed**: ~2–5 seconds per 100-point series\n\n```python\nmodel.compile(timesfm.ForecastConfig(\n    max_context=512,\n    max_horizon=128,\n    per_core_batch_size=4,\n    normalize_inputs=True,\n    use_continuous_quantile_head=True,\n    fix_quantile_crossing=True,\n))\n```\n\n### Tier 2: Standard (CPU 16 GB or GPU 4–8 GB VRAM)\n\n- **Use case**: Batch forecasting (dozens of series), evaluation, production prototypes\n- **Model**: TimesFM 2.5 (200M)\n- **Batch size**: `per_core_batch_size=32` (CPU) or `64` (GPU)\n- **Context**: `max_context=1024`\n- **Expected speed**: ~0.5–1 second per 100-point series (GPU)\n\n```python\nmodel.compile(timesfm.ForecastConfig(\n    max_context=1024,\n    max_horizon=256,\n    per_core_batch_size=64,\n    normalize_inputs=True,\n    use_continuous_quantile_head=True,\n    fix_quantile_crossing=True,\n))\n```\n\n### Tier 3: Production (GPU 16+ GB VRAM or Apple Silicon 32+ GB)\n\n- **Use case**: Large-scale batch forecasting (thousands of series), long context\n- **Model**: TimesFM 2.5 (200M)\n- **Batch size**: `per_core_batch_size=128–256`\n- **Context**: `max_context=4096` or higher\n- **Expected speed**: ~0.1–0.3 seconds per 100-point series\n\n```python\nmodel.compile(timesfm.ForecastConfig(\n    max_context=4096,\n    max_horizon=256,\n    per_core_batch_size=128,\n    normalize_inputs=True,\n    use_continuous_quantile_head=True,\n    fix_quantile_crossing=True,\n))\n```\n\n### Tier 4: Legacy Models (v1.0/v2.0 — 500M parameters)\n\n- **⚠️ WARNING**: TimesFM v2.0 (500M) requires **≥ 16 GB RAM** (CPU) or **≥ 8 GB VRAM** (GPU)\n- **⚠️ WARNING**: TimesFM v1.0 legacy JAX version may require **≥ 32 GB RAM**\n- **Recommendation**: Unless you specifically need a legacy checkpoint, use TimesFM 2.5\n\n## Memory Estimation\n\n### CPU Memory (RAM)\n\nApproximate RAM usage during inference:\n\n| Component | TimesFM 2.5 (200M) | TimesFM 2.0 (500M) |\n| --------- | ------------------- | ------------------- |\n| Model weights | ~800 MB | ~2 GB |\n| Runtime overhead | ~500 MB | ~1 GB |\n| Input/output buffers | ~200 MB per 1000 series | ~500 MB per 1000 series |\n| **Total (small batch)** | **~1.5 GB** | **~3.5 GB** |\n| **Total (large batch)** | **~3 GB** | **~6 GB** |\n\n**Formula**: `RAM ≈ model_weights + 0.5 GB + (0.2 MB × num_series × context_length / 1000)`\n\n### GPU Memory (VRAM)\n\n| Component | TimesFM 2.5 (200M) |\n| --------- | ------------------- |\n| Model weights | ~800 MB |\n| KV cache + activations | ~200–500 MB (scales with context) |\n| Batch buffers | ~100 MB per 100 series at context=1024 |\n| **Total (batch=32)** | **~1.2 GB** |\n| **Total (batch=128)** | **~1.8 GB** |\n| **Total (batch=256)** | **~2.5 GB** |\n\n### Disk Space\n\n| Item | Size |\n| ---- | ---- |\n| TimesFM 2.5 safetensors | ~800 MB |\n| Hugging Face cache overhead | ~200 MB |\n| **Total download** | **~1 GB** |\n\nModel weights are downloaded once from Hugging Face Hub and cached in\n`~/.cache/huggingface/` (or `$HF_HOME`).\n\n## GPU Selection Guide\n\n### NVIDIA GPUs (CUDA)\n\n| GPU | VRAM | Recommended batch | Notes |\n| --- | ---- | ----------------- | ----- |\n| RTX 3060 | 12 GB | 64 | Good entry-level |\n| RTX 3090 / 4090 | 24 GB | 256 | Excellent for production |\n| A100 (40 GB) | 40 GB | 512 | Cloud/HPC |\n| A100 (80 GB) | 80 GB | 1024 | Cloud/HPC |\n| T4 | 16 GB | 128 | Cloud (Colab, AWS) |\n| V100 | 16–32 GB | 128–256 | Cloud |\n\n### Apple Silicon (MPS)\n\n| Chip | Unified Memory | Recommended batch | Notes |\n| ---- | -------------- | ----------------- | ----- |\n| M1 | 8–16 GB | 16–32 | Works, slower than CUDA |\n| M1 Pro/Max | 16–64 GB | 32–128 | Good performance |\n| M2/M3/M4 Pro/Max | 18–128 GB | 64–256 | Excellent |\n\n### CPU Only\n\nWorks on any CPU with sufficient RAM. Expect 5–20× slower than GPU.\n\n## Python and Package Requirements\n\n| Requirement | Minimum | Recommended |\n| ----------- | ------- | ----------- |\n| Python | 3.10 | 3.12+ |\n| numpy | 1.26.4 | latest |\n| torch | 2.0.0 | latest |\n| huggingface_hub | 0.23.0 | latest |\n| safetensors | 0.5.3 | latest |\n\n### Optional Dependencies\n\n| Package | Purpose | Install |\n| ------- | ------- | ------- |\n| jax | Flax backend | `pip install jax[cuda]` |\n| flax | Flax backend | `pip install flax` |\n| scikit-learn | XReg covariates | `pip install scikit-learn` |\n\n## Operating System Compatibility\n\n| OS | Status | Notes |\n| -- | ------ | ----- |\n| Linux (Ubuntu 20.04+) | ✅ Fully supported | Best performance with CUDA |\n| macOS 13+ (Ventura) | ✅ Fully supported | MPS acceleration on Apple Silicon |\n| Windows 11 + WSL2 | ✅ Supported | Use WSL2 for best experience |\n| Windows (native) | ⚠️ Partial | PyTorch works, some edge cases |\n\n## Troubleshooting\n\n### Out of Memory (OOM)\n\n```python\n# Reduce batch size\nmodel.compile(timesfm.ForecastConfig(\n    per_core_batch_size=4,  # Start very small\n    max_context=512,        # Reduce context\n    ...\n))\n\n# Process in chunks\nfor i in range(0, len(inputs), 50):\n    chunk = inputs[i:i+50]\n    p, q = model.forecast(horizon=H, inputs=chunk)\n```\n\n### Slow Inference on CPU\n\n```python\n# Ensure matmul precision is set\nimport torch\ntorch.set_float32_matmul_precision(\"high\")\n\n# Use smaller context\nmodel.compile(timesfm.ForecastConfig(\n    max_context=256,  # Shorter context = faster\n    ...\n))\n```\n\n### Model Download Fails\n\n```bash\n# Set a different cache directory\nexport HF_HOME=/path/with/more/space\n\n# Or download manually\nhuggingface-cli download google/timesfm-2.5-200m-pytorch\n```\n\n<!-- File: timesfm-forecasting/SKILL.md (Tokens: ~5188 | Category: anthropic-skill) -->\n---\nname: timesfm-forecasting\ndescription: >\n  Zero-shot time series forecasting with Google's TimesFM foundation model. Use this\n  skill when forecasting ANY univariate time series — sales, sensor readings, stock prices,\n  energy demand, patient vitals, weather, or scientific measurements — without training a\n  custom model. Supports both basic forecasting and advanced covariate forecasting (XReg)\n  with dynamic and static exogenous variables. Automatically checks system RAM/GPU before\n  loading the model, validates dataset fit before processing, supports CSV/DataFrame/array\n  inputs, and returns point forecasts with calibrated prediction intervals. Includes a\n  preflight system checker script that MUST be run before first use to verify the machine\n  can load the model and handle your specific dataset.\nlicense: Apache-2.0\nmetadata:\n  author: Clayton Young (@borealBytes)\n  version: \"1.0.0\"\n---\n\n# TimesFM Forecasting\n\n## Overview\n\nTimesFM (Time Series Foundation Model) is a pretrained decoder-only foundation model\ndeveloped by Google Research for time-series forecasting. It works **zero-shot** — feed it\nany univariate time series and it returns point forecasts with calibrated quantile\nprediction intervals, no training required.\n\nThis skill includes a **mandatory preflight system checker** that verifies RAM, GPU memory,\nand disk space before the model is ever loaded so the agent never crashes the user's machine.\n\n> **Key numbers**: TimesFM 2.5 uses 200M parameters (~800 MB on disk, ~1.5 GB in RAM on\n> CPU, ~1 GB VRAM on GPU). The archived v1/v2 500M-parameter model needs ~32 GB RAM.\n> Always run the system checker first.\n\n## When to Use This Skill\n\nUse this skill when:\n\n- Forecasting **any univariate time series** (sales, demand, sensor, vitals, price, weather)\n- You need **zero-shot forecasting** without training a custom model\n- You want **probabilistic forecasts** with calibrated prediction intervals (quantiles)\n- You have time series of **any length** (the model handles 1–16,384 context points)\n- You need to **batch-forecast** hundreds or thousands of series efficiently\n- You want a **foundation model** approach instead of hand-tuning ARIMA/ETS parameters\n- You need **covariate forecasting** with exogenous variables (price, promotions, holidays, day-of-week effects) → use `forecast_with_covariates()` (TimesFM 2.5 + `pip install timesfm[xreg]`)\n\n\nDo **not** use this skill when:\n\n- You need classical statistical models with coefficient interpretation → use `statsmodels`\n- You need time series classification or clustering → use `aeon`\n- You need multivariate vector autoregression or Granger causality → use `statsmodels`\n- Your data is tabular (not temporal) → use `scikit-learn`\n- You cannot install optional dependencies → XReg requires scikit-learn and JAX\n\n\n> **Note on Anomaly Detection**: TimesFM does not have built-in anomaly detection, but you\n> can use the **quantile forecasts as prediction intervals** — values outside the 90% CI\n> (q10–q90) are statistically unusual. See `examples/anomaly-detection/` for a full example.\n\n## ⚠️ Mandatory Preflight: System Requirements Check\n\n**CRITICAL — ALWAYS run the system checker before loading the model for the first time.**\n\n```bash\npython scripts/check_system.py\n```\n\nThis script checks:\n\n1. **Available RAM** — warns if below 4 GB, blocks if below 2 GB\n2. **GPU availability** — detects CUDA/MPS devices and VRAM\n3. **Disk space** — verifies room for the ~800 MB model download\n4. **Python version** — requires 3.10+\n5. **Existing installation** — checks if `timesfm` and `torch` are installed\n\n> **Note:** Model weights are **NOT stored in this repository**. TimesFM weights (~800 MB)\n> download on-demand from HuggingFace on first use and cache in `~/.cache/huggingface/`.\n\n```mermaid\nflowchart TD\n    start[\"🚀 Run check_system.py\"] --> ram{\"RAM ≥ 4 GB?\"}\n    ram -->|\"Yes\"| gpu{\"GPU available?\"}\n    ram -->|\"No (2-4 GB)\"| warn_ram[\"⚠️ Warning: tight RAM<br/>CPU-only, small batches\"]\n    ram -->|\"No (< 2 GB)\"| block[\"🛑 BLOCKED<br/>Insufficient memory\"]\n    warn_ram --> disk\n    gpu -->|\"CUDA / MPS\"| vram{\"VRAM ≥ 2 GB?\"}\n    gpu -->|\"CPU only\"| cpu_ok[\"✅ CPU mode<br/>Slower but works\"]\n    vram -->|\"Yes\"| gpu_ok[\"✅ GPU mode<br/>Fast inference\"]\n    vram -->|\"No\"| cpu_ok\n    gpu_ok --> disk{\"Disk ≥ 2 GB free?\"}\n    cpu_ok --> disk\n    disk -->|\"Yes\"| ready[\"✅ READY<br/>Safe to load model\"]\n    disk -->|\"No\"| block_disk[\"🛑 BLOCKED<br/>Need space for weights\"]\n```\n\n### Dataset Preflight (NEW)\n\nBefore loading your actual data, verify it will fit in memory:\n\n```bash\n# Quick estimate for your dataset\npython scripts/check_system.py \\\n  --num-series 1000 \\\n  --context-length 1024 \\\n  --horizon 24 \\\n  --batch-size 32 \\\n  --estimate-only\n```\n\nThis will show you the estimated memory requirements and warn if your dataset is too large.\n\n**Memory Estimation Formula**:\n`RAM ≈ 0.8 GB (model) + 0.5 GB (overhead) + (0.2 MB × num_series × context_length / 1000)`\n\n**Example Outputs**:\n\n✅ **Dataset Fits**:\n```\nTotal CPU memory: 2.34 GB\nTotal GPU memory: 2.15 GB\n```\n\n⚠️ **Dataset Too Large**:\n```\nDataset requires ~12.5 GB RAM but system has 8.0 GB.\nTry: context_length=512 or process in chunks of 50 series.\n```\n\n### Hardware Requirements by Model Version\n\n| Model | Parameters | RAM (CPU) | VRAM (GPU) | Disk | Context |\n| ----- | ---------- | --------- | ---------- | ---- | ------- |\n| **TimesFM 2.5** (recommended) | 200M | ≥ 4 GB | ≥ 2 GB | ~800 MB | up to 16,384 |\n| TimesFM 2.0 (archived) | 500M | ≥ 16 GB | ≥ 8 GB | ~2 GB | up to 2,048 |\n| TimesFM 1.0 (archived) | 200M | ≥ 8 GB | ≥ 4 GB | ~800 MB | up to 2,048 |\n\n> **Recommendation**: Always use TimesFM 2.5 unless you have a specific reason to use an\n> older checkpoint. It is smaller, faster, and supports 8× longer context.\n\n## 🔧 Installation\n\n### Step 1: Verify System (always first)\n\n```bash\npython scripts/check_system.py\n```\n\n### Step 2: Install TimesFM\n\n```bash\n# Using uv (fast)\nuv pip install timesfm[torch]\n\n# Or using pip\npip install timesfm[torch]\n\n# For JAX/Flax backend (faster on TPU/GPU)\nuv pip install timesfm[flax]\n```\n\n### Step 3: Install PyTorch for Your Hardware\n\n```bash\n# CUDA 12.1 (NVIDIA GPU)\npip install torch>=2.0.0 --index-url https://download.pytorch.org/whl/cu121\n\n# CPU only\npip install torch>=2.0.0 --index-url https://download.pytorch.org/whl/cpu\n\n# Apple Silicon (MPS)\npip install torch>=2.0.0  # MPS support is built-in\n```\n\n## 🎯 Quick Start\n\n### Minimal Example\n\n```python\nimport torch, numpy as np, timesfm\n\ntorch.set_float32_matmul_precision(\"high\")\n\nmodel = timesfm.TimesFM_2p5_200M_torch.from_pretrained(\n    \"google/timesfm-2.5-200m-pytorch\"\n)\nmodel.compile(timesfm.ForecastConfig(\n    max_context=1024, max_horizon=256, normalize_inputs=True,\n    use_continuous_quantile_head=True, force_flip_invariance=True,\n    infer_is_positive=True, fix_quantile_crossing=True,\n))\n\npoint, quantiles = model.forecast(horizon=24, inputs=[\n    np.sin(np.linspace(0, 20, 200)),  # any 1-D array\n])\n# point.shape == (1, 24)         — median forecast\n# quantiles.shape == (1, 24, 10) — 10th–90th percentile bands\n```\n\n### Forecast with Covariates (XReg)\n\nTimesFM 2.5+ supports exogenous variables through `forecast_with_covariates()`.\nRequires `pip install timesfm[xreg]`.\n\n```python\npoint, quantiles = model.forecast_with_covariates(\n    inputs=inputs,\n    dynamic_numerical_covariates={\"price\": price_arrays},\n    dynamic_categorical_covariates={\"holiday\": holiday_arrays},\n    static_categorical_covariates={\"region\": region_labels},\n    xreg_mode=\"xreg + timesfm\",  # or \"timesfm + xreg\"\n)\n```\n\n### Anomaly Detection (via Quantile Intervals)\n\n```python\npoint, q = model.forecast(horizon=H, inputs=[values])\n\nlower_90 = q[0, :, 1]  # 10th percentile\nupper_90 = q[0, :, 9]  # 90th percentile\n\nactual = test_values\nanomalies = (actual < lower_90) | (actual > upper_90)\n```\n\n| Severity | Condition | Interpretation |\n| -------- | --------- | -------------- |\n| **Normal** | Inside 80% CI | Expected behavior |\n| **Warning** | Outside 80% CI | Unusual but possible |\n| **Critical** | Outside 90% CI | Statistically rare (< 10% probability) |\n\n> See `examples/anomaly-detection/` for a complete worked example with visualization.\n\n## 📊 Understanding the Output\n\nTimesFM returns `(point_forecast, quantile_forecast)`:\n\n- **`point_forecast`**: shape `(batch, horizon)` — the median (0.5 quantile)\n- **`quantile_forecast`**: shape `(batch, horizon, 10)` — ten quantile slices:\n\n| Index | Quantile | Use |\n| ----- | -------- | --- |\n| 0 | Mean | Average prediction |\n| 1 | 0.1 | Lower bound of 80% PI |\n| 2 | 0.2 | Lower bound of 60% PI |\n| **5** | **0.5** | **Median (= `point_forecast`)** |\n| 8 | 0.8 | Upper bound of 60% PI |\n| 9 | 0.9 | Upper bound of 80% PI |\n\n```python\npoint, q = model.forecast(horizon=H, inputs=data)\n\nlower_80 = q[:, :, 1]  # 10th percentile\nupper_80 = q[:, :, 9]  # 90th percentile\nmedian   = q[:, :, 5]\n```\n\n## 🔧 ForecastConfig Reference\n\nAll forecasting behavior is controlled by `timesfm.ForecastConfig`:\n\n```python\ntimesfm.ForecastConfig(\n    max_context=1024,                    # Max context window\n    max_horizon=256,                     # Max forecast horizon\n    normalize_inputs=True,               # RECOMMENDED — prevents scale instability\n    per_core_batch_size=32,              # Tune for memory\n    use_continuous_quantile_head=True,   # Better quantile accuracy for long horizons\n    force_flip_invariance=True,          # Ensures f(-x) = -f(x)\n    infer_is_positive=True,              # Clamp forecasts ≥ 0 when all inputs > 0\n    fix_quantile_crossing=True,          # Ensure q10 ≤ q20 ≤ ... ≤ q90\n    return_backcast=False,               # Return backcast (for covariate workflows)\n)\n```\n\n| Parameter | Default | When to Change |\n| --------- | ------- | -------------- |\n| `max_context` | 0 | Set to match your longest historical window |\n| `normalize_inputs` | False | **Always set True** |\n| `use_continuous_quantile_head` | False | **Set True** for calibrated PIs |\n| `infer_is_positive` | True | Set False for series that can be negative |\n| `fix_quantile_crossing` | False | **Set True** for monotonic quantiles |\n\nSee `references/api_reference.md` for the complete parameter reference.\n\n## 📋 Common Workflows\n\n### Single Series Forecast\n\n```python\nimport torch, numpy as np, pandas as pd, timesfm, matplotlib\nmatplotlib.use(\"Agg\")\nimport matplotlib.pyplot as plt\n\ntorch.set_float32_matmul_precision(\"high\")\nmodel = timesfm.TimesFM_2p5_200M_torch.from_pretrained(\n    \"google/timesfm-2.5-200m-pytorch\"\n)\nmodel.compile(timesfm.ForecastConfig(\n    max_context=512, max_horizon=52, normalize_inputs=True,\n    use_continuous_quantile_head=True, fix_quantile_crossing=True,\n))\n\ndf = pd.read_csv(\"weekly_demand.csv\", parse_dates=[\"week\"])\nvalues = df[\"demand\"].values.astype(np.float32)\n\npoint, quantiles = model.forecast(horizon=52, inputs=[values])\n\nfig, ax = plt.subplots(figsize=(12, 5))\nax.plot(values[-104:], label=\"Historical\")\nx_fc = range(len(values[-104:]), len(values[-104:]) + 52)\nax.plot(x_fc, point[0], label=\"Forecast\", color=\"tab:orange\")\nax.fill_between(x_fc, quantiles[0, :, 1], quantiles[0, :, 9],\n                alpha=0.2, color=\"tab:orange\", label=\"80% PI\")\nax.legend(); ax.set_title(\"52-Week Demand Forecast\")\nplt.tight_layout(); plt.savefig(\"forecast.png\", dpi=150)\n```\n\n### Batch Forecasting (Many Series)\n\n```python\ndf = pd.read_csv(\"all_stores.csv\", parse_dates=[\"date\"], index_col=\"date\")\ninputs = [df[col].dropna().values.astype(np.float32) for col in df.columns]\n\npoint, quantiles = model.forecast(horizon=30, inputs=inputs)\n\nimport json\nresults = {col: {\"forecast\": point[i].tolist(),\n                 \"lower_80\": quantiles[i, :, 1].tolist(),\n                 \"upper_80\": quantiles[i, :, 9].tolist()}\n           for i, col in enumerate(df.columns)}\nwith open(\"batch_forecasts.json\", \"w\") as f:\n    json.dump(results, f, indent=2)\n```\n\n### Evaluate Forecast Accuracy\n\n```python\nH = 24\ntrain, actual = values[:-H], values[-H:]\npoint, quantiles = model.forecast(horizon=H, inputs=[train])\npred = point[0]\n\nmae  = np.mean(np.abs(actual - pred))\nrmse = np.sqrt(np.mean((actual - pred) ** 2))\nmape = np.mean(np.abs((actual - pred) / actual)) * 100\ncoverage = np.mean((actual >= quantiles[0, :, 1]) & (actual <= quantiles[0, :, 9])) * 100\n\nprint(f\"MAE: {mae:.2f} | RMSE: {rmse:.2f} | MAPE: {mape:.1f}% | 80% PI Coverage: {coverage:.1f}%\")\n```\n\n## ⚙️ Performance Tuning\n\n```python\n# Always set on Ampere+ GPUs (A100, RTX 3090+)\ntorch.set_float32_matmul_precision(\"high\")\n\n# Batch size guidelines:\n# GPU 8 GB VRAM:  per_core_batch_size=64\n# GPU 16 GB VRAM: per_core_batch_size=128\n# CPU 8 GB RAM:   per_core_batch_size=8\n# CPU 16 GB RAM:  per_core_batch_size=32\n\n# Memory-constrained: process in chunks\nCHUNK = 50\nresults = []\nfor i in range(0, len(inputs), CHUNK):\n    p, q = model.forecast(horizon=H, inputs=inputs[i:i+CHUNK])\n    results.append((p, q))\n```\n\n## 📚 Available Scripts\n\n### `scripts/check_system.py`\n\nMandatory preflight checker — run before first model load.\nNow includes **dataset-aware memory estimation** to prevent OOM errors before loading your data.\n\n```bash\n# Basic system check\npython scripts/check_system.py\n\n# Check if your specific dataset will fit\npython scripts/check_system.py \\\n  --num-series 1000 \\\n  --context-length 1024 \\\n  --horizon 24 \\\n  --batch-size 32\n\n# Quick memory estimate without system checks\npython scripts/check_system.py \\\n  --num-series 5000 \\\n  --context-length 2048 \\\n  --estimate-only\n```\n\n**What it checks**:\n\n1. **Available RAM** — warns if below 4 GB, blocks if below 2 GB\n2. **GPU availability** — detects CUDA/MPS devices and VRAM\n3. **Disk space** — verifies room for the ~800 MB model download\n4. **Python version** — requires 3.10+\n5. **Existing installation** — checks if `timesfm` and `torch` are installed\n6. **Dataset fit** (NEW) — estimates memory for your specific dataset and warns if it won't fit\n\n### `scripts/forecast_csv.py`\n\nEnd-to-end CSV forecasting CLI.\n\n```bash\npython scripts/forecast_csv.py input.csv \\\n    --horizon 24 \\\n    --date-col date \\\n    --value-cols sales,revenue \\\n    --output forecasts.csv\n```\n\n## 📖 Reference Documentation\n\n| File | Contents |\n| ---- | -------- |\n| `references/system_requirements.md` | Hardware tiers, GPU/CPU selection, memory estimation |\n| `references/api_reference.md` | Full `ForecastConfig` docs, output shapes, model options |\n| `references/data_preparation.md` | Input formats, NaN handling, CSV loading, covariate setup |\n\n## 🧪 Examples\n\n| Example | Directory | What It Demonstrates |\n| ------- | --------- | -------------------- |\n| **Global Temperature Forecast** | `examples/global-temperature/` | Basic `model.forecast()`, CSV → PNG → GIF pipeline |\n| **Anomaly Detection** | `examples/anomaly-detection/` | Two-phase detrend + Z-score + quantile PI, 2-panel viz |\n| **Covariates (XReg)** | `examples/covariates-forecasting/` | `forecast_with_covariates()`, 2×2 shared-axis viz |\n\n```bash\n# Run all three examples:\ncd examples/global-temperature && python run_forecast.py && python visualize_forecast.py\ncd examples/anomaly-detection  && python detect_anomalies.py\ncd examples/covariates-forecasting && python demo_covariates.py\n```\n\n### Expected Outputs\n\n| Example | Key output files | Acceptance criteria |\n| ------- | ---------------- | ------------------- |\n| global-temperature | `output/forecast_output.json`, `output/forecast_visualization.png` | `point_forecast` has 12 values; PNG shows context + forecast + PI bands |\n| anomaly-detection | `output/anomaly_detection.json`, `output/anomaly_detection.png` | Sep 2023 flagged CRITICAL (z ≥ 3.0) |\n| covariates-forecasting | `output/sales_with_covariates.csv`, `output/covariates_data.png` | 108 rows (3 stores × 36 weeks); distinct price arrays per store |\n\n## Model Versions\n\n| Version | Params | Context | Status | HuggingFace checkpoint |\n| ------- | ------ | ------- | ------ | ---------------------- |\n| **2.5** | 200M | 16,384 | **Latest** | `google/timesfm-2.5-200m-pytorch` |\n| 2.0 | 500M | 2,048 | Archived | `google/timesfm-2.0-500m-pytorch` |\n| 1.0 | 200M | 2,048 | Archived | `google/timesfm-1.0-200m-pytorch` |\n\n- TimesFM 1.0/2.0: must pass `freq=[0]` for monthly data\n- TimesFM 2.5: no frequency flag — it was removed\n\n## Resources\n\n- **Paper**: [A Decoder-Only Foundation Model for Time-Series Forecasting](https://arxiv.org/abs/2310.10688) (ICML 2024)\n- **HuggingFace**: https://huggingface.co/collections/google/timesfm-release-66e4be5fdb56e960c1e482a6\n- **Google Blog**: https://research.google/blog/a-decoder-only-foundation-model-for-time-series-forecasting/\n- **BigQuery Integration**: https://cloud.google.com/bigquery/docs/timesfm-model\n\n## Quality Checklist\n\nRun after every TimesFM task before declaring success:\n\n- [ ] **Output shape** — `point_fc` is `(n_series, horizon)`, `quant_fc` is `(n_series, horizon, 10)`\n- [ ] **Quantile indices** — index 0 = mean, 1 = q10 ... 9 = q90. NOT 0 = q0.\n- [ ] **Frequency flag** — TimesFM 1.0/2.0: pass `freq=[0]` for monthly. TimesFM 2.5: omit.\n- [ ] **Series length** — context must be ≥ 32 data points.\n- [ ] **No NaN** — `np.isnan(point_fc).any()` must be False.\n- [ ] **Axes** — multiple panels sharing data must use `sharex=True`.\n- [ ] **`matplotlib.use('Agg')`** — before any pyplot import when running headless.\n- [ ] **`infer_is_positive`** — set False for temperature, financial returns, negatives.\n\n## Common Mistakes\n\n1. **Quantile index off-by-one** — `quant_fc[..., 0]` is the **mean**, not q0. q10 = index 1, q90 = index 9. Define: `IDX_Q10, IDX_Q90 = 1, 9`.\n\n2. **Variable shadowing in covariate loops** — don't use the outer loop variable as a comprehension variable when building per-series covariate dicts.\n\n3. **Wrong CSV column name** — global-temperature CSV uses `anomaly_c`, not `anomaly`. Print `df.columns` first.\n\n4. **TimesFM 2.5 required for `forecast_with_covariates()`** — TimesFM 1.0 does NOT have this method.\n\n5. **Future covariates must span the full horizon** — dynamic covariates need values for BOTH context AND forecast windows.\n\n6. **Context anomaly detection uses residuals** — detrend first, then Z-score. Raw Z-scores mislead on trending data.\n\n## Validation & Verification\n\n```bash\n# Anomaly detection regression:\npython -c \"\nimport json\nd = json.load(open('examples/anomaly-detection/output/anomaly_detection.json'))\nassert d['context_summary']['critical'] >= 1, 'Sep 2023 must be CRITICAL'\nprint('Anomaly detection: PASS')\"\n\n# Covariates regression:\npython -c \"\nimport pandas as pd\ndf = pd.read_csv('examples/covariates-forecasting/output/sales_with_covariates.csv')\nassert len(df) == 108, f'Expected 108 rows, got {len(df)}'\nprint('Covariates: PASS')\"\n```\n</yakaai_skills>"}