| """ |
| Compositional matching engine. |
| |
| Takes extraction output (concepts with operational signatures) and matches |
| against the formalism knowledge base using three strategies: |
| |
| 1. DIRECT MATCH: signature matches a known formalism exactly → "≡ X" |
| 2. COMPOSITIONAL MATCH: signature = compose(f₁, f₂, ...) → "≡ X ∘ Y" |
| 3. ANALOGY MATCH: same meso/macro type as known formalism → "≈ X (Δ: ...)" |
| |
| Composition is type-checked: rules specify input/output signatures, and the |
| engine verifies that each rule's input constraints are satisfied before |
| applying it. The result is a valid composition tree, not just a bag of rules. |
| |
| Returns UNKNOWN when no match found. Returns CONFUSED when the concept's |
| own operations are internally contradictory. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import json |
| import re |
| from collections.abc import Sequence |
| from dataclasses import dataclass, field |
| from pathlib import Path |
| from typing import Any, Optional |
|
|
| import yaml |
|
|
| |
| |
| |
|
|
| Operation = str |
| DomainType = str |
| CodomainType = str |
| ObjectiveFamily = str |
| MesoType = str |
| MacroType = str |
|
|
|
|
| @dataclass |
| class Signature: |
| """Typed operational signature extracted from a concept or formalism.""" |
| operation: Operation | None = None |
| domain: DomainType | None = None |
| codomain: CodomainType | None = None |
| objective_family: ObjectiveFamily | None = None |
|
|
| @classmethod |
| def from_concept(cls, concept: dict) -> "Signature": |
| """Infer a typed signature from the LLM's extracted concept fields.""" |
| sig = cls() |
| |
| op_text = (concept.get("mathematical_operation") or "").lower() |
| sig.operation = _infer_operation(op_text) |
| sig.domain = _normalize_domain(concept.get("domain")) |
| sig.codomain = _normalize_domain(concept.get("codomain")) |
| sig.objective_family = _infer_objective(concept.get("objective") or "", op_text) |
| return sig |
|
|
| @classmethod |
| def from_formalism(cls, fm: dict) -> "Signature": |
| """Extract signature from a formalism YAML entry.""" |
| sig_raw = fm.get("signature", {}) |
| return cls( |
| operation=sig_raw.get("operation"), |
| domain=sig_raw.get("domain"), |
| codomain=sig_raw.get("codomain"), |
| objective_family=sig_raw.get("objective_family"), |
| ) |
|
|
| def matches(self, other: "Signature", *, strict: bool = True) -> float: |
| """Return a match score 0.0–1.0 between this and another signature. |
| |
| strict=True: all non-None fields must match exactly; score = proportion matched. |
| strict=False: fuzzy — operation + codomain weighted higher. |
| """ |
| fields = [ |
| ("operation", 0.35), |
| ("domain", 0.15), |
| ("codomain", 0.30), |
| ("objective_family", 0.20), |
| ] |
| total = 0.0 |
| matched = 0.0 |
| pairs = [] |
| for attr, weight in fields: |
| mine = getattr(self, attr) |
| theirs = getattr(other, attr) |
| pairs.append((attr, mine, theirs, weight)) |
|
|
| if strict: |
| |
| scorable = [(a, m, t, w) for a, m, t, w in pairs if m is not None and t is not None] |
| if not scorable: |
| return 0.0 |
| total = sum(w for _, _, _, w in scorable) |
| matched = sum(w for _, m, t, w in scorable if m == t) |
| else: |
| total = sum(weight for _, _, _, weight in pairs) |
| for _, mine_val, theirs_val, weight in pairs: |
| if mine_val is None or theirs_val is None: |
| matched += weight * 0.5 |
| elif mine_val == theirs_val: |
| matched += weight |
| |
|
|
| return matched / total if total > 0 else 0.0 |
|
|
|
|
| @dataclass |
| class Formalism: |
| """A known formalism from the KB.""" |
| id: str |
| name: str |
| year: int | None |
| origin: str |
| signature: Signature |
| meso_type: MesoType | None |
| macro_type: MacroType | None |
| canonical_reference: str |
| status: str |
|
|
| @classmethod |
| def from_yaml(cls, entry: dict) -> "Formalism": |
| return cls( |
| id=entry["id"], |
| name=entry["name"], |
| year=entry.get("year"), |
| origin=entry.get("origin", ""), |
| signature=Signature.from_formalism(entry), |
| meso_type=entry.get("meso_type"), |
| macro_type=entry.get("macro_type"), |
| canonical_reference=entry.get("canonical_reference", ""), |
| status=entry.get("status", "seed"), |
| ) |
|
|
|
|
| @dataclass |
| class CompositionRule: |
| """A composition rule from the KB.""" |
| id: str |
| name: str |
| description: str |
| decomposes_to: list[str] |
| input_constraints: dict[str, Any] |
| output_signature: dict[str, Any] |
| preserves: list[str] |
| introduces: list[str] |
| examples: list[str] |
| status: str |
|
|
| @classmethod |
| def from_yaml(cls, entry: dict) -> "CompositionRule": |
| return cls( |
| id=entry["id"], |
| name=entry.get("name", entry["id"]), |
| description=entry.get("description", ""), |
| decomposes_to=entry.get("decomposes_to", []), |
| input_constraints=entry.get("input_constraints", {}), |
| output_signature=entry.get("output_signature", {}), |
| preserves=entry.get("preserves", []), |
| introduces=entry.get("introduces", []), |
| examples=entry.get("examples", []), |
| status=entry.get("status", "seed"), |
| ) |
|
|
| def accepts(self, formalism: Formalism) -> bool: |
| """Check whether this rule can be applied to the given formalism.""" |
| constraints = self.input_constraints |
| if not constraints: |
| return True |
|
|
| |
| req_ids = constraints.get("formalism_ids") |
| if req_ids is not None: |
| if isinstance(req_ids, list) and req_ids and formalism.id not in req_ids: |
| return False |
|
|
| |
| req_meso = constraints.get("meso_types") |
| if req_meso is not None: |
| if isinstance(req_meso, list) and req_meso: |
| if formalism.meso_type not in req_meso: |
| return False |
|
|
| |
| req_macro = constraints.get("macro_types") |
| if req_macro is not None: |
| if isinstance(req_macro, list) and req_macro: |
| if formalism.macro_type not in req_macro: |
| return False |
|
|
| return True |
|
|
|
|
| @dataclass |
| class CompositionNode: |
| """A node in a composition tree: formalism + list of applied rules.""" |
| formalism: Formalism |
| rules: list[CompositionRule] = field(default_factory=list) |
|
|
| @property |
| def name(self) -> str: |
| if not self.rules: |
| return self.formalism.name |
| rule_names = " ∘ ".join(r.name for r in self.rules) |
| return f"{self.formalism.name} ∘ {rule_names}" |
|
|
| @property |
| def is_direct(self) -> bool: |
| return len(self.rules) == 0 |
|
|
|
|
| @dataclass |
| class MatchResult: |
| """The result of matching a concept against the KB.""" |
| concept_name: str |
| result_type: str |
| reduction: str |
| reduction_expanded: str |
| canonical_analog: str |
| genuine_delta: str |
| micro: str |
| meso: str |
| macro: str |
| confidence: float |
| nodes: list[CompositionNode] = field(default_factory=list) |
| match_scores: list[float] = field(default_factory=list) |
| notes: list[str] = field(default_factory=list) |
|
|
| @property |
| def display(self) -> str: |
| """Sous rature display: ~~AI Term~~ → Mathematical Operation""" |
| if self.result_type == "identity": |
| return f"~~{self.concept_name}~~ ≡ {self.reduction}" |
| elif self.result_type == "compositional": |
| base = f"~~{self.concept_name}~~ ≡ {self.reduction}" |
| if self.reduction_expanded and self.reduction_expanded != self.reduction: |
| base += f"\x00EXPAND\x00{self.reduction_expanded}\x00/EXPAND\x00" |
| return base |
| elif self.result_type == "analogy": |
| return f"~~{self.concept_name}~~ ≈ {self.reduction} (Δ: {self.genuine_delta})" |
| elif self.result_type == "confused": |
| return f"~~{self.concept_name}~~ → CONFUSED: {self.notes[0] if self.notes else 'terminology overload'}" |
| else: |
| return f"~~{self.concept_name}~~ → UNKNOWN" |
|
|
|
|
| |
| |
| |
|
|
| def _kb_dir() -> Path: |
| return Path(__file__).resolve().parent / "kb" |
|
|
|
|
| def load_formalisms(path: Path | None = None) -> list[Formalism]: |
| """Load the formalism KB.""" |
| if path is None: |
| path = _kb_dir() / "formalisms.yaml" |
| with open(path) as f: |
| data = yaml.safe_load(f) |
| return [Formalism.from_yaml(e) for e in data.get("formalisms", [])] |
|
|
|
|
| def load_composition_rules(path: Path | None = None) -> list[CompositionRule]: |
| """Load the composition rules KB.""" |
| if path is None: |
| path = _kb_dir() / "composition_rules.yaml" |
| with open(path) as f: |
| data = yaml.safe_load(f) |
| return [CompositionRule.from_yaml(e) for e in data.get("composition_rules", [])] |
|
|
|
|
| |
| |
| |
|
|
| _OP_PATTERNS: list[tuple[str, str]] = [ |
| (r"\b(minimi[zs]e|minimi[zs]ation|minimi[zs]ing)\b", "minimize"), |
| (r"\b(maximi[zs]e|maximi[zs]ation|maximi[zs]ing)\b", "maximize"), |
| (r"\b(project|projection|projecting)\b", "project"), |
| (r"\b(decompose|decomposition|factorize|factorization|eigen)\b", "decompose"), |
| (r"\b(sample|sampling|generate|generating|generative)\b", "sample"), |
| (r"\b(aggregate|aggregation|weighted\s+sum|pooling)\b", "aggregate"), |
| (r"\b(transform|transformations?|map|mapping)\b", "transform"), |
| (r"\b(match|matching|align|alignment)\b", "match"), |
| (r"\b(propagat|diffuse|random\s+walk)\b", "propagate"), |
| ] |
|
|
|
|
| def _infer_operation(text: str) -> Operation | None: |
| text_lower = text.lower() |
| for pattern, op in _OP_PATTERNS: |
| if re.search(pattern, text_lower): |
| return op |
| return None |
|
|
|
|
| _DOMAIN_MAP: dict[str, DomainType] = { |
| "vector": "vector", "vectors": "vector", "embedding": "vector", |
| "matrix": "matrix", "matrices": "matrix", |
| "graph": "graph", |
| "distribution": "distribution", "probability": "distribution", |
| "sequence": "sequence", "token": "sequence", "time series": "sequence", |
| "manifold": "manifold", |
| "set": "set", |
| "function": "function", "scalar field": "scalar_field", |
| "latent": "latent", "latent space": "latent", |
| } |
|
|
|
|
| def _normalize_domain(text: str | None) -> DomainType | None: |
| if not text: |
| return None |
| t = text.strip().lower() |
| |
| for key, val in _DOMAIN_MAP.items(): |
| if key in t: |
| return val |
| return t |
|
|
|
|
| _OBJ_PATTERNS: list[tuple[str, ObjectiveFamily]] = [ |
| (r"\b(kl\b|kullback|divergence|kl\s*divergence)\b", "divergence"), |
| (r"\b(likelihood|log\s*likelihood|mle|maximum\s*likelihood)\b", "likelihood"), |
| (r"\b(energy|free\s*energy|hamiltonian)\b", "energy"), |
| (r"\b(correlation|canonical\s*correlation|cca|cross.correlation)\b", "correlation"), |
| (r"\b(mutual\s*information|mi\b|infonce|information\s*max)\b", "information"), |
| (r"\b(adversarial|minimax|min.max|gan\b|discriminator)\b", "adversarial"), |
| (r"\b(reconstruction|autoencod|encode.decode|mse\b|squared\s*error)\b", "reconstruction"), |
| ] |
|
|
|
|
| def _infer_objective(obj_text: str, op_text: str) -> ObjectiveFamily | None: |
| combined = (obj_text + " " + op_text).lower() |
| for pattern, obj in _OBJ_PATTERNS: |
| if re.search(pattern, combined): |
| return obj |
| return None |
|
|
|
|
| def _infer_meso_type(sig: Signature, concept: dict) -> MesoType | None: |
| """Infer meso-type from signature and concept text.""" |
| text = ( |
| f"{concept.get('mathematical_operation', '')} " |
| f"{concept.get('canonical_analog', '')}" |
| ).lower() |
|
|
| if any(w in text for w in ("kernel", "rkhs", "nyström", "nystrom")): |
| return "kernel_method" |
| if any(w in text for w in ("spectral", "eigen", "laplacian", "fourier")): |
| return "spectral_method" |
| if any(w in text for w in ("energy", "free energy", "boltzmann", "hamiltonian")): |
| return "energy_model" |
| if any(w in text for w in ("diffusion", "sde", "langevin", "score-based", "ddpm")): |
| return "diffusion_process" |
| if any(w in text for w in ("variational", "elbo", "vi ")): |
| return "variational" |
| if any(w in text for w in ("optimal transport", "wasserstein", "sinkhorn")): |
| return "optimal_transport" |
| if any(w in text for w in ("contrastive", "siamese", "infonce")): |
| return "joint_embedding" |
| if any(w in text for w in ("gan", "adversarial", "minimax", "generator")): |
| return "game_theoretic" |
| if any(w in text for w in ("mean field", "mean-field")): |
| return "mean_field" |
| if any(w in text for w in ("joint embedding", "multi.view", "multiview", "cca")): |
| return "joint_embedding" |
| if any(w in text for w in ("pca", "projection", "linear", "svd")): |
| return "linear_projection" |
| if any(w in text for w in ("spin", "ising", "hopfield")): |
| return "spin_system" |
| return None |
|
|
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| _RULE_KEYWORDS: dict[str, list[str]] = { |
| "neuralize": [ |
| "learned", "learnable", "deep", "encoder", "neural", "network", |
| "parameterized", "differentiable", "end-to-end", "trained", "φ_θ", |
| "f_θ", "g_θ", "dnn", "backprop", |
| ], |
| "predict_in_codomain": [ |
| "predict", "predictive", "predicting", "prediction", |
| "latent space", "embedding space", "representation space", |
| "in latent", "in embedding", "codomain", |
| "future embedding", "future representation", |
| ], |
| "contrastivize": [ |
| "contrastive", "contrastively", "positive pair", "negative pair", |
| "infonce", "noise contrastive", "nce", |
| ], |
| "diffuse": [ |
| "diffusion", "denoising", "denoise", "score-based", |
| "reverse process", "forward process", "sde", "ddpm", |
| ], |
| "adversarize": [ |
| "adversarial", "gan", "discriminator", "generator", |
| "minimax", "min-max", |
| ], |
| "variational_bound": [ |
| "variational", "elbo", "vae", "auto-encoding", "autoencoding", |
| "amortized inference", "inference network", |
| ], |
| "regularize": [ |
| "regularize", "regularization", "l1 ", "l2 ", "weight decay", |
| "dropout", "sparsity", |
| ], |
| "attention_wrap": [ |
| "attention", "self-attention", "transformer", "attend", |
| ], |
| } |
|
|
|
|
| @dataclass |
| class MatchEngine: |
| """The compositional matching engine.""" |
|
|
| formalisms: list[Formalism] |
| rules: list[CompositionRule] |
| config: dict = field(default_factory=dict) |
|
|
| |
| _by_id: dict[str, Formalism] = field(default_factory=dict) |
| _by_meso: dict[MesoType, list[Formalism]] = field(default_factory=dict) |
| _by_macro: dict[MacroType, list[Formalism]] = field(default_factory=dict) |
| _by_name: dict[str, Formalism] = field(default_factory=dict) |
| _name_tokens: dict[str, list[Formalism]] = field(default_factory=dict) |
|
|
| def __post_init__(self): |
| self._build_indexes() |
|
|
| def _build_indexes(self): |
| for fm in self.formalisms: |
| self._by_id[fm.id] = fm |
| if fm.meso_type: |
| self._by_meso.setdefault(fm.meso_type, []).append(fm) |
| if fm.macro_type: |
| self._by_macro.setdefault(fm.macro_type, []).append(fm) |
| |
| name_lower = fm.name.lower() |
| self._by_name[name_lower] = fm |
| for token in name_lower.replace("(", "").replace(")", "").replace("/", " ").split(): |
| token = token.strip().rstrip(".,;:") |
| if len(token) >= 3: |
| self._name_tokens.setdefault(token, []).append(fm) |
|
|
| |
|
|
| def _resolve_canonical_analog(self, analog_text: str) -> Formalism | None: |
| """Parse the LLM's canonical_analog field and find the matching formalism. |
| |
| Handles formats like: |
| - "Kernel CCA (Bach & Jordan, 2002)" |
| - "Kernel Canonical Correlation Analysis" |
| - "CCA — Bach & Jordan 2002" |
| """ |
| if not analog_text: |
| return None |
| text_lower = analog_text.lower().strip() |
|
|
| |
| if text_lower in self._by_name: |
| return self._by_name[text_lower] |
|
|
| |
| no_parens = re.sub(r"\([^)]*\)", "", text_lower).strip() |
| if no_parens in self._by_name: |
| return self._by_name[no_parens] |
|
|
| |
| tokens = set(t.strip().rstrip(".,;:") for t in no_parens.replace("/", " ").split() if len(t.strip()) >= 3) |
| if not tokens: |
| return None |
|
|
| scored: list[tuple[int, Formalism]] = [] |
| for fm in self.formalisms: |
| fm_tokens = set(t.strip().rstrip(".,;:") for t in fm.name.lower().replace("(", "").replace(")", "").replace("/", " ").split() if len(t.strip()) >= 3) |
| intersection = tokens & fm_tokens |
| if intersection: |
| scored.append((len(intersection), fm)) |
|
|
| if scored: |
| scored.sort(key=lambda x: x[0], reverse=True) |
| if scored[0][0] >= 2: |
| return scored[0][1] |
| |
| top_token = max(tokens, key=len) if tokens else "" |
| for score, fm in scored: |
| if score >= 1 and len(top_token) >= 4: |
| return fm |
|
|
| return None |
|
|
| |
|
|
| def match_concept(self, concept: dict) -> MatchResult: |
| """Match a single extracted concept against the KB. |
| |
| Returns a MatchResult with the best available decomposition. |
| """ |
| name = concept.get("name", "unknown") |
| sig = Signature.from_concept(concept) |
| meso = _infer_meso_type(sig, concept) |
|
|
| |
| flags = concept.get("flags", []) or [] |
| if "cannot_determine_from_abstract" in flags: |
| return MatchResult( |
| concept_name=name, |
| result_type="unknown", |
| reduction="cannot determine from abstract", |
| reduction_expanded="cannot determine from abstract", |
| canonical_analog="", |
| genuine_delta="", |
| micro=concept.get("mathematical_operation", ""), |
| meso=meso or "unknown", |
| macro="unknown", |
| confidence=0.0, |
| notes=["LLM extraction flagged: cannot determine from abstract"], |
| ) |
| if "terminology_overload" in flags or "claim_operation_mismatch" in flags: |
| pass |
|
|
| |
| canonical_analog_text = concept.get("canonical_analog", "") or "" |
| base_fm = self._resolve_canonical_analog(canonical_analog_text) |
|
|
| if base_fm is not None: |
| |
| |
| rules = self._find_rules_to_match(base_fm, sig, concept) |
| if not rules: |
| |
| return MatchResult( |
| concept_name=name, |
| result_type="identity", |
| reduction=base_fm.name, |
| reduction_expanded=base_fm.name, |
| canonical_analog=f"{base_fm.name} ({base_fm.canonical_reference})", |
| genuine_delta="LLM-identified rebranding of known formalism", |
| micro=f"{base_fm.signature.operation}({base_fm.signature.domain} → {base_fm.signature.codomain})", |
| meso=base_fm.meso_type or "none", |
| macro=base_fm.macro_type or "none", |
| confidence=0.85, |
| nodes=[CompositionNode(formalism=base_fm)], |
| match_scores=[0.85], |
| notes=["matched via LLM canonical_analog field"], |
| ) |
| else: |
| |
| rule_names = " ∘ ".join(r.name for r in rules) |
| expanded_fms = self.expand_rules(rules) |
| expanded = base_fm.name + " ∘ " + " ∘ ".join(expanded_fms) |
| return MatchResult( |
| concept_name=name, |
| result_type="compositional", |
| reduction=f"{base_fm.name} ∘ {rule_names}", |
| reduction_expanded=expanded, |
| canonical_analog=f"{base_fm.name} ({base_fm.canonical_reference})", |
| genuine_delta=" ∘ ".join(r.name for r in rules), |
| micro=f"{base_fm.signature.operation}({base_fm.signature.domain} → {base_fm.signature.codomain})", |
| meso=base_fm.meso_type or "none", |
| macro=base_fm.macro_type or "none", |
| confidence=0.80, |
| nodes=[CompositionNode(formalism=base_fm, rules=rules)], |
| match_scores=[0.80], |
| notes=["matched via LLM canonical_analog with rule delta"], |
| ) |
|
|
| |
| direct = self._match_direct(sig) |
| if direct and direct[1] >= 0.85: |
| fm, score = direct |
| return MatchResult( |
| concept_name=name, |
| result_type="identity", |
| reduction=fm.name, |
| reduction_expanded=fm.name, |
| canonical_analog=f"{fm.name} ({fm.canonical_reference})", |
| genuine_delta="none — this is a direct rebranding", |
| micro=f"exactly {fm.name}: {fm.signature.operation}({fm.signature.domain} → {fm.signature.codomain})", |
| meso=fm.meso_type or "none", |
| macro=fm.macro_type or "none", |
| confidence=score, |
| nodes=[CompositionNode(formalism=fm)], |
| match_scores=[score], |
| ) |
|
|
| |
| comp = self._match_compositional(sig, meso) |
| if comp and comp.confidence >= 0.5: |
| return comp |
|
|
| |
| analogy = self._match_analogy(sig, meso) |
| if analogy and analogy.confidence >= 0.4: |
| return analogy |
|
|
| |
| return MatchResult( |
| concept_name=name, |
| result_type="unknown", |
| reduction="no match in KB", |
| reduction_expanded="no match in KB", |
| canonical_analog="", |
| genuine_delta="", |
| micro=concept.get("mathematical_operation", ""), |
| meso=meso or "unknown", |
| macro="unknown", |
| confidence=0.0, |
| notes=["concept signature does not match any formalism or valid composition"], |
| ) |
|
|
| |
|
|
| def _match_direct(self, sig: Signature) -> tuple[Formalism, float] | None: |
| best: tuple[Formalism, float] | None = None |
| best_score = 0.0 |
| for fm in self.formalisms: |
| score = sig.matches(fm.signature, strict=True) |
| if score > best_score: |
| best_score = score |
| best = (fm, score) |
| if best and best_score >= 0.5: |
| return best |
| return None |
|
|
| |
|
|
| def expand_rules(self, rules: list[CompositionRule]) -> list[str]: |
| """Recursively expand composition rules into their constituent formalism names. |
| |
| Each rule's decomposes_to field lists formalism IDs it's composed of. |
| This method looks up those formalisms by ID and returns their display names, |
| giving the full decomposition chain beneath what appears as a single rule. |
| """ |
| names: list[str] = [] |
| for rule in rules: |
| expanded = False |
| for fm_id in rule.decomposes_to: |
| fm = self._by_id.get(fm_id) |
| if fm: |
| names.append(fm.name) |
| expanded = True |
| if not expanded: |
| |
| names.append(rule.name) |
| return names |
|
|
| |
|
|
| |
| |
|
|
| def _find_rules_to_match( |
| self, |
| base_fm: Formalism, |
| target_sig: Signature, |
| concept: dict | None = None, |
| ) -> list[CompositionRule]: |
| """Find composition rules that describe the paper's delta from the |
| canonical analog. Uses two strategies: |
| |
| 1. Signature-distance minimization (greedy, depth ≤ 2) |
| 2. Keyword-triggered rules from concept text (when LLM has already |
| identified the base formalism — the keywords describe what the |
| paper actually changed) |
| """ |
| base_sig = base_fm.signature |
| base_dist = 1.0 - base_sig.matches(target_sig, strict=False) |
|
|
| |
| best_rules: list[CompositionRule] = [] |
| best_dist = base_dist |
|
|
| for rule in self.rules: |
| if not rule.accepts(base_fm): |
| continue |
| transformed = self._apply_rule_signature(base_sig, rule) |
| dist = 1.0 - transformed.matches(target_sig, strict=False) |
| if dist < best_dist: |
| best_dist = dist |
| best_rules = [rule] |
|
|
| for rule1 in self.rules: |
| if not rule1.accepts(base_fm): |
| continue |
| inter = self._apply_rule_signature(base_sig, rule1) |
| for rule2 in self.rules: |
| if rule2 is rule1: |
| continue |
| if not self._rule_accepts_signature(rule2, inter): |
| continue |
| transformed = self._apply_rule_signature(inter, rule2) |
| dist = 1.0 - transformed.matches(target_sig, strict=False) |
| if dist < best_dist: |
| best_dist = dist |
| best_rules = [rule1, rule2] |
|
|
| sig_improved = best_dist < base_dist - 0.05 |
|
|
| |
| if concept is not None: |
| text = " ".join([ |
| concept.get("mathematical_operation") or "", |
| concept.get("objective") or "", |
| concept.get("claimed_novelty_text") or "", |
| concept.get("confidence_rationale") or "", |
| ]).lower() |
|
|
| keyword_rules: list[CompositionRule] = [] |
| for rule in self.rules: |
| if rule in best_rules: |
| continue |
| keywords = _RULE_KEYWORDS.get(rule.id, []) |
| if any(kw in text for kw in keywords): |
| if rule.accepts(base_fm) or not rule.input_constraints: |
| keyword_rules.append(rule) |
|
|
| |
| |
| |
| if keyword_rules: |
| if sig_improved: |
| |
| merged = list(best_rules) |
| for kr in keyword_rules: |
| if kr not in merged: |
| merged.append(kr) |
| return merged |
| else: |
| |
| return keyword_rules |
|
|
| if sig_improved: |
| return best_rules |
| return [] |
|
|
| |
|
|
| def _match_compositional(self, sig: Signature, meso: MesoType | None) -> MatchResult | None: |
| """Try to decompose the concept as formalism + composition rules. |
| |
| For each formalism whose signature is close, see if applying |
| available rules transforms it toward the concept's signature. |
| """ |
| candidates: list[tuple[Formalism, list[CompositionRule], float]] = [] |
|
|
| |
| for fm in self.formalisms: |
| for rule in self.rules: |
| if not rule.accepts(fm): |
| continue |
| |
| composed_sig = self._apply_rule_signature(fm.signature, rule) |
| score = sig.matches(composed_sig, strict=True) |
| if score >= 0.5: |
| candidates.append((fm, [rule], score)) |
|
|
| |
| for rule2 in self.rules: |
| if rule2 is rule: |
| continue |
| |
| intermediate = self._apply_rule_signature(fm.signature, rule) |
| if not self._rule_accepts_signature(rule2, intermediate): |
| continue |
| composed2 = self._apply_rule_signature(intermediate, rule2) |
| score2 = sig.matches(composed2, strict=True) |
| if score2 >= 0.5: |
| candidates.append((fm, [rule, rule2], score2)) |
|
|
| if not candidates: |
| return None |
|
|
| |
| best_fm, best_rules, best_score = max(candidates, key=lambda c: c[2]) |
| rule_names = " ∘ ".join(r.name for r in best_rules) |
| reduction = f"{best_fm.name} ∘ {rule_names}" |
| expanded_fms = self.expand_rules(best_rules) |
| expanded = best_fm.name + " ∘ " + " ∘ ".join(expanded_fms) |
|
|
| return MatchResult( |
| concept_name="", |
| result_type="compositional", |
| reduction=reduction, |
| reduction_expanded=expanded, |
| canonical_analog=f"{best_fm.name} ({best_fm.canonical_reference})", |
| genuine_delta=" ∘ ".join(r.name for r in best_rules), |
| micro=f"{best_fm.signature.operation}({best_fm.signature.domain} → {best_fm.signature.codomain})", |
| meso=best_fm.meso_type or "none", |
| macro=best_fm.macro_type or "none", |
| confidence=best_score, |
| nodes=[CompositionNode(formalism=best_fm, rules=best_rules)], |
| match_scores=[best_score], |
| ) |
|
|
| def _apply_rule_signature(self, sig: Signature, rule: CompositionRule) -> Signature: |
| """Compute the approximate output signature after applying a rule. |
| |
| Rules modify operation/codomain/objective_family/meso/macro. |
| Fields not mentioned in output_signature pass through unchanged. |
| """ |
| out = rule.output_signature |
| return Signature( |
| operation=out.get("operation", sig.operation), |
| domain=sig.domain, |
| codomain=out.get("codomain", sig.codomain), |
| objective_family=out.get("objective_family", sig.objective_family), |
| ) |
|
|
| def _rule_accepts_signature(self, rule: CompositionRule, sig: Signature) -> bool: |
| """Check if a rule can be applied to an intermediate signature. |
| |
| This is a looser check than rule.accepts(formalism) since we |
| don't have a Formalism object — we check meso/macro constraints. |
| """ |
| constraints = rule.input_constraints |
| if not constraints: |
| return True |
|
|
| req_ids = constraints.get("formalism_ids") |
| if req_ids is not None and isinstance(req_ids, list) and req_ids: |
| return False |
|
|
| |
| req_meso = constraints.get("meso_types") |
| if req_meso is not None and isinstance(req_meso, list) and req_meso: |
| |
| |
| pass |
|
|
| req_macro = constraints.get("macro_types") |
| if req_macro is not None and isinstance(req_macro, list) and req_macro: |
| pass |
|
|
| return True |
|
|
| |
|
|
| def _match_analogy(self, sig: Signature, meso: MesoType | None) -> MatchResult | None: |
| """Find formalisms with the same meso/macro type but different specifics.""" |
| if not meso: |
| return None |
|
|
| candidates = self._by_meso.get(meso, []) |
| if not candidates: |
| return None |
|
|
| |
| best_score = 0.0 |
| best_fm: Formalism | None = None |
| for fm in candidates: |
| score = sig.matches(fm.signature, strict=False) |
| if score > best_score: |
| best_score = score |
| best_fm = fm |
|
|
| if best_fm is None or best_score < 0.3: |
| return None |
|
|
| |
| deltas: list[str] = [] |
| if sig.operation and sig.operation != best_fm.signature.operation: |
| deltas.append(f"operation: {best_fm.signature.operation} → {sig.operation}") |
| if sig.codomain and sig.codomain != best_fm.signature.codomain: |
| deltas.append(f"codomain: {best_fm.signature.codomain} → {sig.codomain}") |
| if sig.objective_family and sig.objective_family != best_fm.signature.objective_family: |
| deltas.append(f"objective: {best_fm.signature.objective_family} → {sig.objective_family}") |
| delta_str = "; ".join(deltas) if deltas else "minor variation" |
|
|
| return MatchResult( |
| concept_name="", |
| result_type="analogy", |
| reduction=f"{best_fm.name}", |
| reduction_expanded=f"{best_fm.name}", |
| canonical_analog=f"{best_fm.name} ({best_fm.canonical_reference})", |
| genuine_delta=delta_str, |
| micro=f"Shares meso-type '{meso}' with {best_fm.name}", |
| meso=meso, |
| macro=best_fm.macro_type or "none", |
| confidence=best_score, |
| nodes=[CompositionNode(formalism=best_fm)], |
| match_scores=[best_score], |
| ) |
|
|
| |
|
|
| def match_paper(self, extraction: dict) -> dict: |
| """Match all concepts extracted from a paper. |
| |
| Returns the extraction dict augmented with match results. |
| """ |
| concepts = extraction.get("concepts", []) |
| matched = [] |
| for concept in concepts: |
| result = self.match_concept(concept) |
| matched.append(result) |
|
|
| extraction["_matches"] = [self._result_to_dict(r) for r in matched] |
| extraction["_match_summary"] = self._summarize(matched) |
| return extraction |
|
|
| def _result_to_dict(self, r: MatchResult) -> dict: |
| return { |
| "concept_name": r.concept_name, |
| "result_type": r.result_type, |
| "reduction": r.reduction, |
| "reduction_expanded": r.reduction_expanded, |
| "canonical_analog": r.canonical_analog, |
| "genuine_delta": r.genuine_delta, |
| "micro": r.micro, |
| "meso": r.meso, |
| "macro": r.macro, |
| "confidence": r.confidence, |
| "display": r.display, |
| "notes": r.notes, |
| } |
|
|
| def _summarize(self, results: list[MatchResult]) -> dict: |
| identity = sum(1 for r in results if r.result_type == "identity") |
| compositional = sum(1 for r in results if r.result_type == "compositional") |
| analogy = sum(1 for r in results if r.result_type == "analogy") |
| unknown = sum(1 for r in results if r.result_type == "unknown") |
| confused = sum(1 for r in results if r.result_type == "confused") |
| total = len(results) |
|
|
| return { |
| "total_concepts": total, |
| "identity_reductions": identity, |
| "compositional_reductions": compositional, |
| "analogy_matches": analogy, |
| "unknown": unknown, |
| "confused": confused, |
| "reduction_rate": (identity + compositional) / total if total else 0, |
| } |
|
|
|
|
| |
| |
| |
|
|
| def load_engine( |
| formalism_path: Path | None = None, |
| rules_path: Path | None = None, |
| ) -> MatchEngine: |
| """Load the match engine from the default KB files.""" |
| formalisms = load_formalisms(formalism_path) |
| rules = load_composition_rules(rules_path) |
| return MatchEngine(formalisms=formalisms, rules=rules) |
|
|
|
|
| |
| |
| |
|
|
| def _test_jepa(): |
| """Verify the canonical JEPA decomposition: CCA ∘ neuralize ∘ predict_in_codomain.""" |
| engine = load_engine() |
|
|
| |
| jepa_concept = { |
| "name": "Joint Embedding Predictive Architecture", |
| "is_claimed_novel": True, |
| "claimed_novelty_text": "predicts representations in latent space rather than raw inputs", |
| "mathematical_operation": "maximize mutual information between joint embeddings of x and y, then predict future embedding from past embedding in the joint space", |
| "domain": "vector", |
| "codomain": "vector", |
| "objective": "maximize I(Z_x; Z_y) — mutual information between embeddings, plus prediction error in latent space", |
| "constraints": [], |
| "canonical_analog": "Kernel CCA (Bach & Jordan 2002)", |
| "deconstructive_move": "binary_overturn", |
| "confidence": "high", |
| "confidence_rationale": "abstract explicitly describes joint embedding + prediction in latent space", |
| "flags": [], |
| } |
|
|
| result = engine.match_concept(jepa_concept) |
| print("=== JEPA Canonical Decomposition Test ===") |
| print(f"Concept: {result.concept_name}") |
| print(f"Result type: {result.result_type}") |
| print(f"Reduction: {result.reduction}") |
| print(f"Display: {result.display}") |
| print(f"Micro: {result.micro}") |
| print(f"Meso: {result.meso}") |
| print(f"Macro: {result.macro}") |
| print(f"Confidence: {result.confidence}") |
| print(f"Canonical analog: {result.canonical_analog}") |
| print(f"Genuine delta: {result.genuine_delta}") |
| if result.notes: |
| print(f"Notes: {result.notes}") |
| print() |
| return result |
|
|
|
|
| if __name__ == "__main__": |
| _test_jepa() |
|
|