a3358dfc95
Compose the live relational-neighborhood geometry OPERATORS into five reasoning
modes as pure, read-only C (engram_reason.{h,c}); each is proven with closed-form
constructed tests before it ships, not declared.
- ANALOGY (Procrustes R + residual translation, apply to C, rank candidates)
- INDUCTION (combine-pooled rule geometry + point-to-manifold membership)
- ABDUCTION (best-explaining structure by point-to-manifold fit)
- CAUSAL (centroid-cosine correlation vs directed influence: temporal
precedence + association surviving confounder control via subtract;
emits a correlation-vs-causation flag)
- PLANNING (geo-distance edges + Dijkstra → discrete geodesic path)
A shared point-to-manifold fit primitive underlies induction membership and
abduction ranking. engram/test/run_reason_tests.sh: 33/33 checks on both PERF
and ASan/UBSan passes; macOS leaks 0/0.
ANALOGY is surfaced as an el builtin (engram_reason_analogy_json) via the same
pass-through the §5 operators use — demonstrated callable from compiled El with a
container-capped fold (no self-host fold). The other four are C-layer only: their
set/point/timestamp inputs do not map to the flat-CSV el ABI without touching
codegen (deferred). engram_reason.c must join the server link line beside
engram_geometry.c at cutover. See docs/runbooks/2026-08-13-reasoning-operators-*.
162 lines
12 KiB
C
162 lines
12 KiB
C
/* engram_reason.h — the REASONING layer: compositions over the §5 geometry
|
||
* OPERATORS (engram_geometry.h). Where the operators are a relational ALGEBRA over
|
||
* neighborhood descriptors, these are reasoning MODES built by CHAINING that algebra:
|
||
*
|
||
* ANALOGY A:B :: C:? — learn the A→B transform (Procrustes), apply to C.
|
||
* INDUCTION {E_i} → rule — pool example geometries; a generalizing structure
|
||
* + a membership test.
|
||
* ABDUCTION x → best H — the structure whose geometry best PLACES an
|
||
* observation in-distribution (inverse of prediction).
|
||
* CAUSAL x ? y | Z, t — separate mere overlap (correlation) from directed
|
||
* influence (temporal precedence + association that
|
||
* SURVIVES controlling for confounders via subtract).
|
||
* PLANNING start → goal — a trajectory (sequence of neighborhoods) through the
|
||
* manifold: shortest path over geo-distance edges.
|
||
*
|
||
* PURE + READ-ONLY (stdlib + libm only): every function consumes GeoDescriptor(s)
|
||
* (+ a few scalars / timestamps) and NEVER touches the store, index, or activation.
|
||
* All geometry is delegated to the engram_geo_* primitives; this file only composes.
|
||
*
|
||
* FRAME CONTRACT (inherited): descriptors passed together MUST share emb `dim` and
|
||
* `global_mean` frame — exactly the §5 operator contract. A function returns <0 on
|
||
* a dim/frame mismatch or bad argument.
|
||
*/
|
||
#ifndef ENGRAM_REASON_H
|
||
#define ENGRAM_REASON_H
|
||
|
||
#include <stdint.h>
|
||
#include "engram_geometry.h"
|
||
|
||
/* ═══════════════════════════════════════════════════════════════════════════
|
||
* SHARED PRIMITIVE — point-to-manifold FIT. How well does a single point x sit
|
||
* inside a neighborhood's ellipsoid? Splits the residual (x − centroid) into:
|
||
* - the IN-SUBSPACE part, scaled by each axis extent → a Mahalanobis distance
|
||
* (how many "radii" out along the modeled directions), and
|
||
* - the ORTHOGONAL part outside the retained axes → energy the model does not
|
||
* explain at all (charged at the extent floor).
|
||
* This is the common engine under INDUCTION's membership test and ABDUCTION's
|
||
* explanation ranking. ext_floor (>0) guards zero-extent axes / the null model.
|
||
* ═══════════════════════════════════════════════════════════════════════════ */
|
||
typedef struct {
|
||
double mahalanobis; /* sqrt( Σ_k ((a_k·(x−c)) / max(ext_k,floor))² ) */
|
||
double ortho_residual; /* ‖(x−c) projected off the retained axes‖ (raw L2) */
|
||
double distance; /* sqrt( maha² + (ortho_residual/floor)² ) — full fit */
|
||
double score; /* 1 / (1 + distance²) ∈ (0,1] (1 = dead-center) */
|
||
} GeoFit;
|
||
int engram_reason_point_fit(const GeoDescriptor* g, const float* x,
|
||
double ext_floor, GeoFit* out);
|
||
|
||
/* ═══════════════════════════════════════════════════════════════════════════
|
||
* ANALOGY — "A:B :: C:?". Learn the transform that carries A to B (orthogonal
|
||
* Procrustes rotation R between their principal frames + the residual translation),
|
||
* apply it to C, and return the mapped point + the nearest candidate neighborhood.
|
||
* Composes: engram_geo_analogy (R) + engram_geo_analogy_apply + engram_geo_distance.
|
||
* ═══════════════════════════════════════════════════════════════════════════ */
|
||
typedef struct {
|
||
int dim;
|
||
float* mapped_point; /* predicted D location = R·c_C + (c_B − R·c_A) (owned)*/
|
||
double analogy_residual;/* Procrustes ‖A−B R‖_F — frame-alignment quality */
|
||
int best; /* index of nearest candidate to mapped_point, or −1 */
|
||
double best_distance; /* centroid L2 from mapped_point to the winner */
|
||
int n_candidates;
|
||
double* distances; /* centroid L2 mapped_point→candidate[i] (owned)*/
|
||
} GeoAnalogyResult;
|
||
/* candidates may be NULL/0 (then best=−1 and only mapped_point is filled). */
|
||
int engram_reason_analogy(const GeoDescriptor* A, const GeoDescriptor* B,
|
||
const GeoDescriptor* C,
|
||
const GeoDescriptor* const* candidates, int n_candidates,
|
||
GeoAnalogyResult* out);
|
||
void engram_reason_analogy_free(GeoAnalogyResult* r);
|
||
|
||
/* ═══════════════════════════════════════════════════════════════════════════
|
||
* INDUCTION — from a SET of example neighborhoods to the generalizing structure.
|
||
* Pools the examples (law-of-total-variance via engram_geo_combine, folded left to
|
||
* right) into a single "rule" descriptor whose top principal axes are the directions
|
||
* CONSISTENTLY present across the examples (the shared subspace surfaces as the
|
||
* dominant pooled axes; idiosyncratic per-example directions fall to the tail).
|
||
* The rule carries a membership test (point-to-manifold fit against the pool).
|
||
* ═══════════════════════════════════════════════════════════════════════════ */
|
||
typedef struct {
|
||
GeoDescriptor* rule; /* induced generalizing geometry (owned; geo_free) */
|
||
double ext_floor; /* extent floor used by the membership test */
|
||
int n_examples;/* how many examples were pooled */
|
||
} GeoInduction;
|
||
/* top_axes<=0 → 8. ext_floor<=0 → derived from the pooled radius. */
|
||
int engram_reason_induce(const GeoDescriptor* const* examples, int n_examples,
|
||
int top_axes, double ext_floor, GeoInduction* out);
|
||
/* Membership of a point in the induced rule ∈ (0,1] (the fit score). <0 on error. */
|
||
double engram_reason_membership(const GeoInduction* ind, const float* x);
|
||
void engram_reason_induction_free(GeoInduction* out);
|
||
|
||
/* ═══════════════════════════════════════════════════════════════════════════
|
||
* ABDUCTION — inference to the best explanation. Given an observation POINT, rank a
|
||
* set of candidate structures by how well each PLACES the observation in-distribution
|
||
* (min point-to-manifold distance = the structure that, if assumed, best accounts for
|
||
* the observation). The inverse of prediction.
|
||
* ═══════════════════════════════════════════════════════════════════════════ */
|
||
typedef struct {
|
||
int best; /* index of best-explaining hypothesis, or −1 */
|
||
double best_score;
|
||
int n;
|
||
double* scores; /* fit score per hypothesis (higher = better) (owned)*/
|
||
double* distances; /* explanation distance per hypothesis (owned)*/
|
||
int* rank; /* hypothesis indices sorted best→worst (owned)*/
|
||
} GeoAbduction;
|
||
int engram_reason_abduce(const float* obs, int dim,
|
||
const GeoDescriptor* const* hypotheses, int n,
|
||
double ext_floor, GeoAbduction* out);
|
||
void engram_reason_abduction_free(GeoAbduction* out);
|
||
|
||
/* ═══════════════════════════════════════════════════════════════════════════
|
||
* CAUSAL — correlation vs causation. Over two variables' geometries (+ candidate
|
||
* confounders + temporal order), distinguish:
|
||
* - mere co-occurrence / overlap (correlation), from
|
||
* - directed influence: association that (a) SURVIVES controlling for confounders
|
||
* (subtract each Z's subspace from both centroids, re-measure) and (b) is oriented
|
||
* by temporal PRECEDENCE.
|
||
* Composes: centroid cosine (correlation) + engram_geo_subtract (control) + timestamps.
|
||
* ═══════════════════════════════════════════════════════════════════════════ */
|
||
typedef enum {
|
||
GEO_CAUSAL_NONE = 0, /* no meaningful association */
|
||
GEO_CAUSAL_DIRECTED = 1, /* survives control + temporally ordered → cause→eff */
|
||
GEO_CAUSAL_CONFOUNDED = 2 /* correlated but association dies under control */
|
||
} GeoCausalVerdict;
|
||
typedef struct {
|
||
double assoc_raw; /* |cos(c_x,c_y)| — the raw correlation */
|
||
double assoc_controlled; /* |cos| of residual centroids after control */
|
||
int temporal_dir; /* +1 x→y, −1 y→x, 0 tie/unknown */
|
||
GeoCausalVerdict verdict;
|
||
int confounded; /* 1 iff verdict==CONFOUNDED (the flag) */
|
||
double strength; /* directed influence estimate ∈[0,1] (0 else)*/
|
||
} GeoCausal;
|
||
/* confounders may be NULL/0. t_x,t_y are comparable timestamps (any monotone unit);
|
||
* pass equal values for "unknown order". drop_frac∈(0,1): a controlled association
|
||
* below (1−drop_frac)·assoc_raw AND below an absolute floor ⇒ CONFOUNDED. */
|
||
int engram_reason_causal(const GeoDescriptor* x, const GeoDescriptor* y,
|
||
const GeoDescriptor* const* confounders, int n_conf,
|
||
int64_t t_x, int64_t t_y,
|
||
double drop_frac, GeoCausal* out);
|
||
|
||
/* ═══════════════════════════════════════════════════════════════════════════
|
||
* PLANNING — trajectory construction. Given a set of neighborhoods (manifold nodes),
|
||
* a start and a goal, build a PATH (sequence of intermediate neighborhoods) by
|
||
* shortest path over the graph whose edges connect neighborhoods within
|
||
* neighbor_radius, weighted by geo-distance. Long straight jumps are not edges, so
|
||
* the path follows the manifold's curvature through intermediates (a discrete geodesic).
|
||
* Composes: engram_geo_distance (edge weights) + Dijkstra.
|
||
* ═══════════════════════════════════════════════════════════════════════════ */
|
||
typedef struct {
|
||
int* path; /* node indices start..goal (owned) */
|
||
int path_len;
|
||
double total_cost; /* summed centroid-distance edge weights along path */
|
||
int reached; /* 1 if goal reachable within neighbor_radius graph */
|
||
} GeoPlan;
|
||
/* neighbor_radius>0: max centroid distance for two neighborhoods to be adjacent.
|
||
* Use "wasserstein"!=0 to weight edges by Wasserstein-2 instead of centroid L2. */
|
||
int engram_reason_plan(const GeoDescriptor* const* nodes, int n,
|
||
int start, int goal, double neighbor_radius,
|
||
int use_wasserstein, GeoPlan* out);
|
||
void engram_reason_plan_free(GeoPlan* out);
|
||
|
||
#endif /* ENGRAM_REASON_H */
|