Skip to content

rnel.decide

From an RNEL tuple to an action (technical note, Section 5.3).

Evidence gate (v0.1.1). Version 0.1.0 abstained when the ignorance component G was high and otherwise answered with the side (T or F) that led by the margin. Because G falls whenever any report arrives, a report supporting x could lower G below its threshold and unlock the answer "refuted": with the default thresholds, 3 opposing reports -> abstain, but 3 opposing + 1 supporting -> "refuted". This violates the AGM inclusion postulate (evidence for x must never produce belief in not-x). The gate is now applied to the evidence on the side being asserted: to answer T (or F) the number of supporting (or opposing) reports must exceed the count the old total-mass gate required, W (1/G_threshold - 1). With evidence on one side only, the two gates coincide; with mixed evidence the new gate is monotone. Set side_gate=False to reproduce v0.1.0.

ACTIONS = {'T': 'answer: supported', 'F': 'answer: refuted', 'C': 'present both positions and flag the dispute', 'U': 'wait or ask for more specific evidence', 'N': 'question the premise of the claim', 'G': 'retrieve more evidence or abstain'} module-attribute

Policy dataclass

Thresholds per component; set them on validation data with the costs of the application.

Source code in src/rnel/decide.py
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
@dataclass
class Policy:
    """Thresholds per component; set them on validation data with the costs of the application."""

    C: float = 0.25
    U: float = 0.30
    N: float = 0.30
    G: float = 0.40
    margin: float = 0.15  # minimum |T - F| to answer
    W: float = DEFAULT_W  # prior weight used to build the tuple (recovers report counts from it)
    side_gate: bool = True  # False = v0.1.0 behaviour (total-mass gate, not monotone)

    def required_side_evidence(self) -> float:
        """Reports needed on the asserted side: the count at which the total-mass gate opened in v0.1.0."""
        return self.W * (1.0 / self.G - 1.0) if self.G > 0 else 0.0

    def decide(self, x: RNELTuple) -> tuple[str, str]:
        for comp in ("C", "N", "U"):
            if getattr(x, comp) >= getattr(self, comp):
                return comp, ACTIONS[comp]
        if not self.side_gate:
            if x.G >= self.G:
                return "G", ACTIONS["G"]
            if abs(x.T - x.F) >= self.margin:
                comp = "T" if x.T > x.F else "F"
                return comp, ACTIONS[comp]
            return "G", ACTIONS["G"]
        if abs(x.T - x.F) >= self.margin:
            comp = "T" if x.T > x.F else "F"
            if x.G <= 0:  # dogmatic tuple: no prior weight, counts are unbounded
                return comp, ACTIONS[comp]
            side_count = (x.T if comp == "T" else x.F) * self.W / x.G  # Sigma = W / G
            if side_count > self.required_side_evidence() + 1e-9:
                return comp, ACTIONS[comp]
        return "G", ACTIONS["G"]

C = 0.25 class-attribute instance-attribute

U = 0.3 class-attribute instance-attribute

N = 0.3 class-attribute instance-attribute

G = 0.4 class-attribute instance-attribute

margin = 0.15 class-attribute instance-attribute

W = DEFAULT_W class-attribute instance-attribute

side_gate = True class-attribute instance-attribute

required_side_evidence()

Reports needed on the asserted side: the count at which the total-mass gate opened in v0.1.0.

Source code in src/rnel/decide.py
42
43
44
def required_side_evidence(self) -> float:
    """Reports needed on the asserted side: the count at which the total-mass gate opened in v0.1.0."""
    return self.W * (1.0 / self.G - 1.0) if self.G > 0 else 0.0

decide(x)

Source code in src/rnel/decide.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
def decide(self, x: RNELTuple) -> tuple[str, str]:
    for comp in ("C", "N", "U"):
        if getattr(x, comp) >= getattr(self, comp):
            return comp, ACTIONS[comp]
    if not self.side_gate:
        if x.G >= self.G:
            return "G", ACTIONS["G"]
        if abs(x.T - x.F) >= self.margin:
            comp = "T" if x.T > x.F else "F"
            return comp, ACTIONS[comp]
        return "G", ACTIONS["G"]
    if abs(x.T - x.F) >= self.margin:
        comp = "T" if x.T > x.F else "F"
        if x.G <= 0:  # dogmatic tuple: no prior weight, counts are unbounded
            return comp, ACTIONS[comp]
        side_count = (x.T if comp == "T" else x.F) * self.W / x.G  # Sigma = W / G
        if side_count > self.required_side_evidence() + 1e-9:
            return comp, ACTIONS[comp]
    return "G", ACTIONS["G"]