File size: 5,450 Bytes
d9af7ee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
"""Constraint metadata and helper utilities for patch selection UI."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Dict, Iterable, Mapping


@dataclass(frozen=True)
class MetricRange:
    """Slider bounds for a specific metric."""

    default: float
    minimum: float
    maximum: float
    step: float
    label: str | None = None
    description: str | None = None


@dataclass(frozen=True)
class ConstraintDefinition:
    """UI metadata + semantic description for a constraint."""

    name: str
    label: str
    description: str
    entropy: MetricRange
    perplexity: MetricRange
    guard: bool = False

    def range_for_metric(self, metric_name: str) -> MetricRange:
        """Return the metric range for the given metric label."""
        key = metric_name.lower()
        if key not in ("entropy", "perplexity"):
            raise ValueError(f"Unknown metric '{metric_name}'")
        return getattr(self, key)


# Constraint slider definitions
CONSTRAINT_DEFINITIONS: Dict[str, ConstraintDefinition] = {
    "global": ConstraintDefinition(
        name="global",
        label="Global Threshold",
        description="H(t) > θ",
        entropy=MetricRange(default=5.0, minimum=0.0, maximum=20.0, step=0.5),
        perplexity=MetricRange(default=300.0, minimum=0.0, maximum=2000.0, step=5.0),
    ),
    "additive": ConstraintDefinition(
        name="additive",
        label="Additive Monotonic",
        description="H(t) - H(t-1) > θ",
        entropy=MetricRange(default=0.5, minimum=0.0, maximum=10.0, step=0.1),
        perplexity=MetricRange(default=5.0, minimum=0.0, maximum=100.0, step=1.0),
    ),
    "multiplicative": ConstraintDefinition(
        name="multiplicative",
        label="Multiplicative Monotonic",
        description="H(t) / H(t-1) > θ",
        entropy=MetricRange(default=1.5, minimum=1.0, maximum=5.0, step=0.1),
        perplexity=MetricRange(default=1.5, minimum=1.0, maximum=5.0, step=0.1),
    ),
    "additive_min": ConstraintDefinition(
        name="additive_min",
        label="Additive from Min",
        description="H(t) - min(H) > θ",
        entropy=MetricRange(default=2.0, minimum=0.0, maximum=10.0, step=0.1),
        perplexity=MetricRange(default=20.0, minimum=0.0, maximum=100.0, step=1.0),
    ),
    "multiplicative_min": ConstraintDefinition(
        name="multiplicative_min",
        label="Multiplicative from Min",
        description="H(t) / min(H) > θ",
        entropy=MetricRange(default=2.0, minimum=1.0, maximum=10.0, step=0.1),
        perplexity=MetricRange(default=3.0, minimum=1.0, maximum=10.0, step=0.1),
    ),
    "cumulative": ConstraintDefinition(
        name="cumulative",
        label="Cumulative Sum (Min)",
        description="Σ H(t) > θ (boundary after exceeding)",
        entropy=MetricRange(default=10.0, minimum=0.0, maximum=100.0, step=1.0),
        perplexity=MetricRange(default=100.0, minimum=0.0, maximum=1000.0, step=10.0),
    ),
    "cumulative_max": ConstraintDefinition(
        name="cumulative_max",
        label="Cumulative Sum (Max)",
        description="Σ H(t) ≤ θ (boundary before exceeding)",
        entropy=MetricRange(default=50.0, minimum=0.0, maximum=500.0, step=5.0),
        perplexity=MetricRange(default=500.0, minimum=0.0, maximum=10000.0, step=50.0),
    ),
}

MIN_GUARD_DEFINITION = ConstraintDefinition(
    name="min_metric",
    label="Minimum Metric for Boundary",
    description="AND guard: boundaries only trigger once metric exceeds this value",
    entropy=MetricRange(
        default=0.0,
        minimum=0.0,
        maximum=20.0,
        step=0.1,
        label="Minimum Entropy for Boundary",
        description="Guard: boundaries only trigger once entropy exceeds this value",
    ),
    perplexity=MetricRange(
        default=0.0,
        minimum=0.0,
        maximum=2000.0,
        step=5.0,
        label="Minimum Perplexity for Boundary",
        description="Guard: boundaries only trigger once perplexity exceeds this value",
    ),
    guard=True,
)


def iter_constraint_definitions() -> Iterable[ConstraintDefinition]:
    """Iterate definitions in sorted order by display label."""
    return sorted(CONSTRAINT_DEFINITIONS.values(), key=lambda c: c.label)


def get_constraint_definition(name: str) -> ConstraintDefinition:
    """Return definition by name."""
    if name == MIN_GUARD_DEFINITION.name:
        return MIN_GUARD_DEFINITION
    try:
        return CONSTRAINT_DEFINITIONS[name]
    except KeyError as exc:
        raise KeyError(f"Unknown constraint '{name}'") from exc


def default_thresholds_for_metric(metric_name: str) -> Mapping[str, float]:
    """Return default slider values for each constraint given the metric."""
    return {
        definition.name: definition.range_for_metric(metric_name).default
        for definition in iter_constraint_definitions()
    }


def guard_range_for_metric(metric_name: str) -> MetricRange:
    """Return metric range for the guard slider."""
    metric_range = MIN_GUARD_DEFINITION.range_for_metric(metric_name)
    if metric_range.label and metric_range.description:
        return metric_range
    return MetricRange(
        default=metric_range.default,
        minimum=metric_range.minimum,
        maximum=metric_range.maximum,
        step=metric_range.step,
        label=MIN_GUARD_DEFINITION.label,
        description=MIN_GUARD_DEFINITION.description,
    )