Skip to content

Commit deb811d

Browse files
committed
refactor(G1): rebase clean + PAD sanitization doc + real analyze_with_triangle test
- Document the PAD-model sanitization (Pleasure-Arousal-Dominance → Agency/Activity/Affection) at the qualia-distance section header, with cross-refs to lance-graph-cognitive::grammar::qualia and contract::qualia which use the academic field names. - Add `analyze_with_triangle_returns_different_qualia_for_different_pearl_masks`: the failing-test-first that would have failed under the former 0.5 placeholder. Exercises the `analyze_with_triangle` path (where 0.5 used to live), constructs two structures with the same subject and different SPO mask shapes (transitive 0b111 vs intransitive 0b110), and asserts the differing dim is dim 2 (Affection ← Object plane). - Branch was rebased onto origin/main to drop the unrelated plan-commit pollution (`18240ec` plan + stray `460329f` F6 cherry-pick); only the real G1 commit `14926f1` is preserved (now `7013116`). https://claude.ai/code/session_01NYGrxVopyszZYgLBxe4hgj
1 parent 7013116 commit deb811d

1 file changed

Lines changed: 87 additions & 0 deletions

File tree

crates/deepnsm/src/triangle_bridge.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,13 @@ pub fn analyze_without_triangle(structure: SentenceStructure) -> SpoWithGrammar
137137
}
138138
}
139139

140+
// Qualia dim mapping (PAD model — Pleasure-Arousal-Dominance, sanitized for non-academic context):
141+
// dim 0 = Agency ← Dominance (Subject plane bit; P=1 → Subject contributes)
142+
// dim 1 = Activity ← Activation (Predicate plane bit; P=1 → Predicate contributes)
143+
// dim 2 = Affection ← Arousal (Object plane bit; P=1 → Object contributes)
144+
// Cross-ref: lance-graph-cognitive::grammar::qualia uses {valence,activation,dominance};
145+
// contract::qualia uses {arousal,valence,tension}. This module uses sanitized PAD.
146+
140147
/// Normalized Hamming distance between the qualia fingerprint and the
141148
/// SPO predicate's expected qualia footprint.
142149
///
@@ -406,4 +413,84 @@ mod tests {
406413
let expected = (1u64 << 18) - 1;
407414
assert_eq!(fp[0], expected);
408415
}
416+
417+
/// PR-G1 spec test (failing-test-first): two structures with the SAME
418+
/// subject but DIFFERENT Pearl masks (one transitive 0b111, one
419+
/// intransitive 0b110) must produce different `analyze_with_triangle`
420+
/// outputs.
421+
///
422+
/// This exercises the path where the former 0.5 placeholder lived. If
423+
/// the placeholder were still in place, both calls would have produced
424+
/// the SAME `causality_footprint = 0` (zero-init / placeholder) and the
425+
/// expected_qualia_footprint would not differ on dim 2. With the real
426+
/// Pearl 2³ mask wired in:
427+
///
428+
/// - Transitive (S+P+O = 0b111) → expected_qualia_footprint bit 2 set
429+
/// - Intransitive (S+P = 0b110) → expected_qualia_footprint bit 2 unset
430+
///
431+
/// The differing dim is dim 2 (Affection ← Object plane bit 0 of mask).
432+
#[cfg(feature = "grammar-triangle")]
433+
#[test]
434+
fn analyze_with_triangle_returns_different_qualia_for_different_pearl_masks() {
435+
// Sentence A: transitive — S+P+O all present (0b111)
436+
let s_transitive = fixture_structure_with(671, 2943, 95);
437+
// Sentence B: intransitive — S+P only, no O (0b110)
438+
let s_intransitive = {
439+
let empty: Vec<crate::vocabulary::Token> = Vec::new();
440+
let mut s = crate::parser::parse(&empty);
441+
s.triples.push(SpoTriple::intransitive(671, 100));
442+
s
443+
};
444+
445+
// Same surface text so the GrammarTriangle::from_text branch is
446+
// identical for both — what differs is the structure (Pearl mask).
447+
let text = "the dog acts";
448+
449+
let out_a = analyze_with_triangle(text, s_transitive);
450+
let out_b = analyze_with_triangle(text, s_intransitive);
451+
452+
// Same subject in both
453+
assert_eq!(
454+
out_a.triples.triples[0].subject(),
455+
out_b.triples.triples[0].subject(),
456+
"both sentences must share the same subject"
457+
);
458+
459+
// Pearl masks must differ — the core PR-G1 invariant.
460+
assert_ne!(
461+
out_a.causality_footprint, out_b.causality_footprint,
462+
"transitive (0b{:03b}) vs intransitive (0b{:03b}) must differ \
463+
on the analyze_with_triangle path",
464+
out_a.causality_footprint, out_b.causality_footprint
465+
);
466+
assert_eq!(out_a.causality_footprint, 0b111, "transitive = SPO");
467+
assert_eq!(out_b.causality_footprint, 0b110, "intransitive = SP_");
468+
469+
// Expected-qualia-footprint differs in dim 2 (Affection ← Object).
470+
// The transitive sentence expects bit 2 set; the intransitive does
471+
// not. This is what the 0.5 placeholder used to mask.
472+
let exp_a = expected_qualia_footprint(&out_a.triples);
473+
let exp_b = expected_qualia_footprint(&out_b.triples);
474+
assert_ne!(
475+
exp_a, exp_b,
476+
"expected_qualia_footprint must differ for differing Pearl masks"
477+
);
478+
let dim2_mask: u64 = 1u64 << 2;
479+
assert_eq!(
480+
exp_a[0] & dim2_mask,
481+
dim2_mask,
482+
"transitive must set dim 2 (Affection from Object plane)"
483+
);
484+
assert_eq!(
485+
exp_b[0] & dim2_mask,
486+
0,
487+
"intransitive must NOT set dim 2 (no Object plane)"
488+
);
489+
490+
// Sanity: classification_distance is in [0,1] for both — a 0.5
491+
// placeholder would have been a constant; here it must respond
492+
// to the structure.
493+
assert!(out_a.classification_distance >= 0.0 && out_a.classification_distance <= 1.0);
494+
assert!(out_b.classification_distance >= 0.0 && out_b.classification_distance <= 1.0);
495+
}
409496
}

0 commit comments

Comments
 (0)