# mlp_probe — lightweight non-linear probe op.
#
# Sibling to linear_probe (ML.md §3.3c "Lightweight classification").
# The non-linear column in the embedding × index coverage matrix
# (.tickets/impl/embedding-index-coverage/) — distinguishes "not encoded"
# (low linear & low MLP R²) from "encoded non-linearly" (low linear,
# high MLP R²).
#
# Ticket: .tickets/impl/embedding-index-coverage/03-mlp-probe-op.md
# Spec:   docs/spec/ML.md §3.3c (lightweight-classification)
#         docs/spec/ML.md §3.3  (embed-ops)
#
# v0 implementation lives at demos/geo-fm-comparison/ops/mlp_probe.py
# (alongside linear_probe). Promote to folia/domains/ml/transforms/probes.py
# once the API stabilizes — both probes share train/test split, bootstrap CI,
# and standardization, factored into ops/_probe_common.py.

id: mlp_probe
name: MLP Probe (1-hidden-layer non-linear classifier/regressor)
description: >
  Fit a one-hidden-layer MLP on top of a frozen embedding to predict a target.
  Supports regression (mode=regress, R²) and classification (mode=classify,
  accuracy). 1000-resample bootstrap CI on the held-out metric. Stratified
  80/20 train/test split (by class for classify, by quintile of value for
  regress). sklearn MLPRegressor/MLPClassifier backend.
version: 0.1.0
category: ml
type: tabular-to-metric
default_implementation: sklearn

inputs:
  - name: embeddings
    type: tensor
    format: parquet
    dtype: float32
    description: >
      (N, D) embedding matrix. Rows with non-finite entries are dropped
      before splitting.
    required: true

  - name: targets
    type: tensor
    format: parquet
    description: >
      (N,) target vector. NaN targets are excluded (regress mode).
    required: true

outputs:
  - name: metric
    type: scalar
    dtype: float32
    description: >
      Held-out test metric. R² (regress) or accuracy (classify).

  - name: ci_low
    type: scalar
    dtype: float32
    description: Bootstrap CI lower bound at alpha=0.05.

  - name: ci_high
    type: scalar
    dtype: float32
    description: Bootstrap CI upper bound at alpha=0.05.

  - name: predictions
    type: tensor
    format: parquet
    dtype: float32
    description: Held-out predictions, paired with y_true_test for downstream analysis.

params:
  mode:
    type: string
    default: regress
    description: >
      "regress" → R² on a continuous target. "classify" → accuracy on an
      integer-labeled target.

  hidden_units:
    type: integer
    default: 64
    description: Width of the single hidden layer (ticket-locked at 64).

  dropout:
    type: float
    default: 0.1
    description: >
      sklearn does not implement dropout; mapped to L2 penalty
      `alpha = dropout * 1e-3`. Tracked separately so a future PyTorch
      backend can implement real dropout without changing recipes.

  train_frac:
    type: float
    default: 0.8
    description: Stratified train-set fraction.

  seed:
    type: integer
    default: 42
    description: Random seed (split + MLP init + bootstrap).

  n_bootstrap:
    type: integer
    default: 1000
    description: Number of bootstrap resamples for the CI.

  max_iter:
    type: integer
    default: 200
    description: Max solver iterations.

  solver:
    type: string
    default: adam
    description: >
      "adam" for 10k+ row datasets (default); "lbfgs" for small synthetic
      targets where adam under-converges (unit tests).

# backends: audited 2026-08-14 (defect 50). NOTHING dispatches this op: no arm in folia-engine
# `dispatch_op`, no `registerOp`/OP_TABLE entry in packages/compute, no `_BUILTIN_OP_MAP`
# key in folia/compute.py, no backend manifest. Declared EMPTY on purpose — an absent
# block would be indistinguishable from one nobody ever wrote.
# the declared impl lives in demos/geo-fm-comparison/ops/mlp_probe.py, outside the package; `_BUILTIN_OP_MAP` has no key for it.
backends: {}

granularity:
  min_resolution_m: 0
  max_resolution_m: 0
  output_resolution: na
  requires_continuous: false

execution:
  cost_per_km2: 0
  time_per_km2_sec: 0
  memory_profile: low
  profile:
    cpu: medium
    memory: low
  scaling:
    model: linear
    parallelizable: true

cache_policy:
  ttl_days: 30
  invalidate_on: [embedding_update, target_update, param_change]

ui:
  icon: scatter_plot
  color: "#4A6CF7"
