File size: 5,903 Bytes
be9f416
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8270f31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1edadfb
 
 
8270f31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
be9f416
 
 
 
 
8270f31
be9f416
 
 
 
 
 
 
 
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
"""Baseline and teacher theory modules used by smoke tests and benchmarks."""

from __future__ import annotations


STATIC_THEORY = r'''
import math


class Theory:
    def __init__(self):
        self.last = {}
        self.sensor_names = []

    def fit(self, history):
        if history:
            self.last = dict(history[-1]["sensors"])
            self.sensor_names = list(self.last)
        return {"records": len(history)}

    def predict(self, window):
        steps = int(window["steps"])
        return [{"sensors": dict(self.last), "anomaly_prob": 0.05} for _ in range(steps)]

    def log_prob(self, observations):
        return -float(len(observations))

    def describe(self):
        return "Static persistence model: predicts each sensor remains at its last observed value."
'''


LINEAR_TREND_THEORY = r'''
import math


class Theory:
    def __init__(self):
        self.last = {}
        self.slope = {}

    def fit(self, history):
        if not history:
            return {"records": 0}
        self.last = dict(history[-1]["sensors"])
        if len(history) < 3:
            self.slope = {name: 0.0 for name in self.last}
            return {"records": len(history)}
        prev = history[-3]["sensors"]
        self.slope = {name: (self.last[name] - prev.get(name, self.last[name])) / 2.0 for name in self.last}
        return {"records": len(history)}

    def predict(self, window):
        rows = []
        for step in range(1, int(window["steps"]) + 1):
            sensors = {name: value + self.slope.get(name, 0.0) * step for name, value in self.last.items()}
            rows.append({"sensors": sensors, "anomaly_prob": 0.08})
        return rows

    def log_prob(self, observations):
        return -0.5 * float(len(observations))

    def detect_drift(self, history):
        if len(history) < 8:
            return False
        names = list(history[-1]["sensors"])
        old = 0.0
        recent = 0.0
        for name in names:
            old += abs(history[-5]["sensors"][name] - history[-8]["sensors"][name])
            recent += abs(history[-1]["sensors"][name] - history[-4]["sensors"][name])
        return recent > 2.0 * max(old, 1e-6)

    def describe(self):
        return "Linear trend model with a residual-change drift heuristic."
'''


ANOMALY_AWARE_TEACHER = r'''
import math
import statistics


class Theory:
    def __init__(self):
        self.last = {}
        self.slope = {}
        self.volatility = 0.0

    def fit(self, history):
        if not history:
            return {"records": 0}
        self.last = dict(history[-1]["sensors"])
        names = list(self.last)
        if len(history) < 5:
            self.slope = {name: 0.0 for name in names}
            self.volatility = 0.0
            return {"records": len(history)}
        self.slope = {}
        deltas = []
        for name in names:
            series = [row["sensors"][name] for row in history[-8:]]
            step_deltas = [b - a for a, b in zip(series[:-1], series[1:])]
            self.slope[name] = statistics.mean(step_deltas)
            deltas.extend(abs(value) for value in step_deltas)
        self.volatility = statistics.mean(deltas) if deltas else 0.0
        return {"records": len(history), "volatility": self.volatility}

    def predict(self, window):
        rows = []
        anomaly_prob = 0.65 if self.volatility > 0.35 else 0.10
        for step in range(1, int(window["steps"]) + 1):
            sensors = {}
            for name, value in self.last.items():
                curvature = 0.02 * math.sin(step)
                sensors[name] = value + self.slope.get(name, 0.0) * step + curvature
            rows.append({"sensors": sensors, "anomaly_prob": anomaly_prob})
        return rows

    def log_prob(self, observations):
        return -0.25 * float(len(observations))

    def detect_drift(self, history):
        return self.volatility > 0.45

    def describe(self):
        return "Teacher model: local trend, weak curvature, anomaly and drift heuristics."
'''


# DSL oracle for dual_fluid universe — encodes the correct latent ontology.
# This is the "gold standard" DSL spec. A trained model that converges to
# something structurally similar has successfully performed scientific discovery.
import json as _json

DUAL_FLUID_DSL_ORACLE = _json.dumps({
    "dsl_version": 1,
    "name": "dual fluid oracle: autocatalytic A + forced oscillator B",
    "state": ["A", "B", "C"],
    "init": {
        "A": {"sensor": "pressure", "scale": 1.0},
        "B": 0.3,
        "C": 0.0,
    },
    "dynamics": {
        "A": {"add": [{"linear": {"terms": {"A": -0.3}}}, {"mul": [0.5, {"var": "A"}, {"var": "B"}]}]},
        "B": {"add": [{"mul": [0.4, {"sin": {"var": "t"}}]}, {"mul": [-0.2, {"pow": [{"var": "A"}, 2]}]}]},
        "C": {"add": [{"mul": [0.15, {"sin": {"mul": [0.4, {"var": "A"}]}}]}, {"linear": {"terms": {"C": -0.12}}}]},
    },
    "observations": {
        "pressure": {"var": "A"},
        "turbulence": {"pow": [{"add": [{"var": "A"}, {"neg": {"var": "B"}}]}, 2]},
        "thermal_radiation": {"add": [
            {"mul": [{"var": "B"}, {"exp": {"neg": {"abs": {"var": "A"}}}}]},
            {"mul": [0.3, {"var": "C"}]},
        ]},
        "magnetic_flux": {"mul": [0.1, {"var": "A"}, {"var": "B"}]},
    },
    "fit": {"lookback": 8, "trend_weight": 0.1, "drift_threshold": 0.4, "anomaly_threshold": 0.4},
    "integrator": {"dt": 0.15, "substeps": 2},
    "noise": 0.08,
}, sort_keys=True)


BASELINES = {
    "static": STATIC_THEORY,
    "linear": LINEAR_TREND_THEORY,
    "oracle": ANOMALY_AWARE_TEACHER,
    "teacher": ANOMALY_AWARE_TEACHER,
    "dual_fluid_dsl": DUAL_FLUID_DSL_ORACLE,
}


def get_baseline(name: str) -> str:
    try:
        return BASELINES[name]
    except KeyError as exc:
        raise ValueError(f"Unknown baseline '{name}'. Choose from {sorted(BASELINES)}") from exc