id: stack_concat
name: Stack Concat
description: >
  Stack T per-axis multi-band rasters along a new axis to produce a single
  (T*D)-band raster. Output band b at offset (t * D + d) corresponds to
  axis index t and source band index d.

  The "axis" is the meaning of the new dimension being introduced —
  typically `time` (multi-year embedding stacks, used by the trajectory
  pattern in §recipes), but also valid for `model` (ensemble embeddings
  from different foundation models), `polarization` (SAR HH/HV), or any
  other discrete dimension a downstream op wants to reason over.

  This op is also the enabler for TWO-RASTER BAND MATH: `raster_calc`
  reads exactly one raster and binds bands positionally, so a dNBR or any
  cross-layer arithmetic is spelled `stack_concat` over the ordered pair
  followed by `raster_calc` with `b1`/`b2` (proven end to end in
  products/sdk/folia-engine/tests/stack_concat.rs).

  Inputs are passed as named raster layers; the optional `keys` parameter
  controls ordering. If `keys` is omitted, input keys are sorted
  lexicographically (which yields the desired chronological order for
  per-year keys like `aef_2019`, `aef_2020`, …, when `axis: time`).

  Output band names are `{key}_{NN}` where NN is the zero-padded source
  band index (e.g., `aef_2019_00`, …, `aef_2024_63`). This is the
  `{time_key}_{NN}` convention documented in OPERATIONS.md
  §input-output-types.

  All input layers must share the same spatial grid (width, height,
  bbox, CRS, transform) and the same band count D. Mismatched shapes
  fail with a clear error.

  NaN handling (strict): if any value at pixel p across any axis index
  or band is NaN, the entire stack at p is filled with NaN. This
  ensures downstream similarity / reduction computations never see
  partial vectors.

  Renamed 2026-05-01 from `embedding_trajectory_concat` per
  arch-review verdict that the math is general — see registry alias
  in `packages/compute/src/ops/index.ts` for backward compatibility.
version: 1.0.0
# Wired as the engine's first variadic combine arm (multi-provider-dag/05). ADR-0065 D4 —
# bump on numerics change (the concat kernel's stack layout / NaN rule).
op_identity: builtin:stack_concat@1
aliases: [embedding_trajectory_concat]   # pre-2026-05-01 name; the engine accepts both
category: analysis
type: raster-to-raster

inputs:
  - name: layers
    type: raster
    description: >
      T raster layers, each with D bands. Passed as named inputs (e.g.,
      `aef_2019`, `aef_2020`, … for the trajectory pattern). The op
      stacks all inputs along a new axis, sorted by key (or by the
      optional `keys` param).
    required: true
    multiple: true

# Structural: concatenates any T raster layers along a new axis. Output
# is a high-dim raster carrying whatever concept the inputs carried.
# requires: omitted.

outputs:
  - name: stack
    type: raster_stack
    format: cog
    dtype: float32
    description: >
      Single raster with T*D bands containing the concatenated values
      at each pixel. Band names follow `{key}_{NN}` convention.

params:
  axis:
    type: enum
    enum: [time, model, polarization, custom]
    default: time
    description: >
      Semantic meaning of the new axis being introduced. Default `time`
      preserves backward compatibility with the trajectory recipe (see
      OPERATIONS.md §pattern-trajectory-similarity). `model` is for
      multi-FM ensembles, `polarization` for SAR HH/HV, `custom` for
      anything else. Currently informational — recorded in provenance
      and used by display_hints; no behavioral change.
  keys:
    type: array
    items: string
    description: >
      Explicit ordering of input keys. If omitted, input names are sorted
      lexicographically. Useful when input names don't sort by the
      desired axis order. Aliased as `time_keys` for backward
      compatibility with the `axis: time` use case.
    required: false
  time_keys:
    type: array
    items: string
    description: >
      DEPRECATED alias for `keys` — preserved for backward compatibility
      with the original `embedding_trajectory_concat` API. New code
      should use `keys`.
    required: false

# backends: audited 2026-08-14 (defect 50). A key means a runtime that DISPATCHES this op —
# folia-engine `dispatch_op` (products/sdk/folia-engine/src/lib.rs), a `registerOp`/OP_TABLE
# entry in packages/compute, `_BUILTIN_OP_MAP` in folia/compute.py, or a backend manifest
# (folia/backends/*/backend.yaml).
backends:
  rust-engine:
    op: stack_concat                     # folia-engine dispatch arm (multi-provider-dag/05)
    function: temporal_ops::embedding_trajectory_concat_flat
  js:
    function: stack_concat

display_hints:
  map:
    renderer: maplibre
    type: raster
    note: >
      Output is high-dim (T*D bands); render is via downstream similarity
      / reduction op. Tile previewers expecting <=4 bands will fail on
      the raw stack output.
  info:
    fields: [n_axis_steps, source_band_count, total_bands]
    section: Stack Operations
