Release 11.0 (full): passage readings held back when the sentence leaves out a question word, the judge names the step it refused, the part format v1, anyone's sealed test, the sleep cycle, and the six orbiters as files in the pack (part 7)
Browse filesHead oracle_release_runtime_w575. Root 5985f38d66bcdedbdedb74db90432675a32ddce9dfe2d196b3282c40581d889b. Solana anchor: pending Ben's signature. Internal week (seven simulated nights): 0 parts admitted, 0 new wrong, published as measured. Full changelog: CHANGELOG_11.md.
- workbench/swarm/close_learn102.py +3 -1
- workbench/swarm/coverage102.py +9 -9
- workbench/swarm/data/flash_threshold.json +6 -248
- workbench/swarm/examiner105.py +97 -0
- workbench/swarm/own_steps.py +51 -0
- workbench/swarm/part11.py +312 -0
- workbench/swarm/part_submit.py +81 -0
- workbench/swarm/ref_measure.py +65 -0
- workbench/swarm/skeleton102.py +6 -4
- workbench/swarm/sleep105.py +457 -0
- workbench/swarm/steps_budget.py +15 -0
- workbench/verify.py +4 -0
workbench/swarm/close_learn102.py
CHANGED
|
@@ -15,6 +15,8 @@ import collections
|
|
| 15 |
import copy
|
| 16 |
import json
|
| 17 |
import re
|
|
|
|
|
|
|
| 18 |
from pathlib import Path
|
| 19 |
|
| 20 |
import reader_v4 as R4
|
|
@@ -217,7 +219,7 @@ class Closer:
|
|
| 217 |
import time
|
| 218 |
rd = self.cov.rd
|
| 219 |
seen = seen if seen is not None else set()
|
| 220 |
-
if
|
| 221 |
return
|
| 222 |
left = set(need) - set(used)
|
| 223 |
last = prog[-1]["function"] if prog else ""
|
|
|
|
| 15 |
import copy
|
| 16 |
import json
|
| 17 |
import re
|
| 18 |
+
|
| 19 |
+
import steps_budget as SB
|
| 20 |
from pathlib import Path
|
| 21 |
|
| 22 |
import reader_v4 as R4
|
|
|
|
| 219 |
import time
|
| 220 |
rd = self.cov.rd
|
| 221 |
seen = seen if seen is not None else set()
|
| 222 |
+
if SB.spent() > deadline or len(out) >= 8 or depth > 4:
|
| 223 |
return
|
| 224 |
left = set(need) - set(used)
|
| 225 |
last = prog[-1]["function"] if prog else ""
|
workbench/swarm/coverage102.py
CHANGED
|
@@ -41,6 +41,8 @@ import hashlib
|
|
| 41 |
import json
|
| 42 |
import os
|
| 43 |
import re
|
|
|
|
|
|
|
| 44 |
import sys
|
| 45 |
import time
|
| 46 |
from pathlib import Path
|
|
@@ -252,7 +254,7 @@ class Coverage:
|
|
| 252 |
except Exception: # noqa: BLE001
|
| 253 |
self.closer = None
|
| 254 |
try: # the learned word-to-step alignment (align_learn102.py)
|
| 255 |
-
A = json.load(open(Path(__file__).resolve().parent / "data" / "alignment102.json"))["p_feature_given_word"]
|
| 256 |
except Exception: # noqa: BLE001
|
| 257 |
A = {}
|
| 258 |
self.align_t = A
|
|
@@ -911,8 +913,6 @@ class Coverage:
|
|
| 911 |
if lit is not None or (f.startswith("FN:") and f[3:] not in SAID_FN):
|
| 912 |
continue
|
| 913 |
best = max([self.align_t.get(f, {}).get(w, 0.0) for w in words] + [0.0])
|
| 914 |
-
if f.startswith("IN:") and f.count(":") == 1 and self.says_word(f, f[3:], next(iter(words), "")):
|
| 915 |
-
best = max(best, 1.0)
|
| 916 |
tot += best
|
| 917 |
n += 1
|
| 918 |
for st in prog:
|
|
@@ -962,7 +962,7 @@ class Coverage:
|
|
| 962 |
written = b_ == a_ + 1 and re.fullmatch(r"-?\d[\d,]*(?:\.\d+)?|\d{4}-\d{2}-\d{2}|\d{4} \d{2} \d{2}", q[s1:e1]) is not None
|
| 963 |
if not t or (t not in vix and not written) or (s1, e1) in seen or any(x <= s1 and e1 <= y for x, y in seen):
|
| 964 |
continue
|
| 965 |
-
stored =
|
| 966 |
if stored is None: # a written number or date the store does not hold: as written
|
| 967 |
m = re.search(re.escape(q[s1:e1]).replace("\ ", r"\W+"), question, re.I)
|
| 968 |
stored = question[m.start():m.end()] if m else q[s1:e1]
|
|
@@ -1218,10 +1218,10 @@ class Coverage:
|
|
| 1218 |
singles = [x for x in P if len(x[3]) == 1 and x[2][0]["function"] == "Find"]
|
| 1219 |
res = []
|
| 1220 |
|
| 1221 |
-
deadline =
|
| 1222 |
|
| 1223 |
def run(pp):
|
| 1224 |
-
if len(res) >= 8 or
|
| 1225 |
return
|
| 1226 |
for st in pp:
|
| 1227 |
if st["function"] == "Find":
|
|
@@ -1744,7 +1744,7 @@ class Coverage:
|
|
| 1744 |
m = re.search(re.escape(q[s1:e1]).replace("\\ ", r"\W+"), question, re.I)
|
| 1745 |
vals_written.append((s1, e1, question[m.start():m.end()] if m else q[s1:e1]))
|
| 1746 |
# the flash: from every part, along every lit relation, up to two steps - all at once
|
| 1747 |
-
deadline =
|
| 1748 |
grown = []
|
| 1749 |
for s0, e0, prog, idset in P:
|
| 1750 |
own = self.part_words(ws, span(s0, e0), prog)
|
|
@@ -1768,7 +1768,7 @@ class Coverage:
|
|
| 1768 |
grown.append((s0, e0, prog, own, need, [b_ for b_ in beams if b_[0]], start))
|
| 1769 |
for s0, e0, prog, own, need, beams, _start in grown:
|
| 1770 |
for path, used, v in beams:
|
| 1771 |
-
if
|
| 1772 |
break
|
| 1773 |
nodes = {ids_of[n] for n in np.flatnonzero(v)[:200]}
|
| 1774 |
for pp, cw in closing(program(prog, path), nodes, used, need):
|
|
@@ -1782,7 +1782,7 @@ class Coverage:
|
|
| 1782 |
need = (A[4] - B[3]) | (B[4] - A[3])
|
| 1783 |
for pa_, ua, va in [((), frozenset(), A[6])] + A[5]: # a part's own items meet the other's light
|
| 1784 |
for pb_, ub, vb in [((), frozenset(), B[6])] + B[5]:
|
| 1785 |
-
if
|
| 1786 |
break
|
| 1787 |
meet = va & vb
|
| 1788 |
if not meet.any() or meet.sum() > 50:
|
|
|
|
| 41 |
import json
|
| 42 |
import os
|
| 43 |
import re
|
| 44 |
+
|
| 45 |
+
import steps_budget as SB
|
| 46 |
import sys
|
| 47 |
import time
|
| 48 |
from pathlib import Path
|
|
|
|
| 254 |
except Exception: # noqa: BLE001
|
| 255 |
self.closer = None
|
| 256 |
try: # the learned word-to-step alignment (align_learn102.py)
|
| 257 |
+
A = json.load(open(os.environ.get("O7_ALIGN") or Path(__file__).resolve().parent / "data" / "alignment102.json"))["p_feature_given_word"]
|
| 258 |
except Exception: # noqa: BLE001
|
| 259 |
A = {}
|
| 260 |
self.align_t = A
|
|
|
|
| 913 |
if lit is not None or (f.startswith("FN:") and f[3:] not in SAID_FN):
|
| 914 |
continue
|
| 915 |
best = max([self.align_t.get(f, {}).get(w, 0.0) for w in words] + [0.0])
|
|
|
|
|
|
|
| 916 |
tot += best
|
| 917 |
n += 1
|
| 918 |
for st in prog:
|
|
|
|
| 962 |
written = b_ == a_ + 1 and re.fullmatch(r"-?\d[\d,]*(?:\.\d+)?|\d{4}-\d{2}-\d{2}|\d{4} \d{2} \d{2}", q[s1:e1]) is not None
|
| 963 |
if not t or (t not in vix and not written) or (s1, e1) in seen or any(x <= s1 and e1 <= y for x, y in seen):
|
| 964 |
continue
|
| 965 |
+
stored = min(self._vstore.get(t, {None}), key=str) if t in vix else None
|
| 966 |
if stored is None: # a written number or date the store does not hold: as written
|
| 967 |
m = re.search(re.escape(q[s1:e1]).replace("\ ", r"\W+"), question, re.I)
|
| 968 |
stored = question[m.start():m.end()] if m else q[s1:e1]
|
|
|
|
| 1218 |
singles = [x for x in P if len(x[3]) == 1 and x[2][0]["function"] == "Find"]
|
| 1219 |
res = []
|
| 1220 |
|
| 1221 |
+
deadline = SB.deadline()
|
| 1222 |
|
| 1223 |
def run(pp):
|
| 1224 |
+
if len(res) >= 8 or SB.spent() > deadline:
|
| 1225 |
return
|
| 1226 |
for st in pp:
|
| 1227 |
if st["function"] == "Find":
|
|
|
|
| 1744 |
m = re.search(re.escape(q[s1:e1]).replace("\\ ", r"\W+"), question, re.I)
|
| 1745 |
vals_written.append((s1, e1, question[m.start():m.end()] if m else q[s1:e1]))
|
| 1746 |
# the flash: from every part, along every lit relation, up to two steps - all at once
|
| 1747 |
+
deadline = SB.deadline()
|
| 1748 |
grown = []
|
| 1749 |
for s0, e0, prog, idset in P:
|
| 1750 |
own = self.part_words(ws, span(s0, e0), prog)
|
|
|
|
| 1768 |
grown.append((s0, e0, prog, own, need, [b_ for b_ in beams if b_[0]], start))
|
| 1769 |
for s0, e0, prog, own, need, beams, _start in grown:
|
| 1770 |
for path, used, v in beams:
|
| 1771 |
+
if SB.spent() > deadline:
|
| 1772 |
break
|
| 1773 |
nodes = {ids_of[n] for n in np.flatnonzero(v)[:200]}
|
| 1774 |
for pp, cw in closing(program(prog, path), nodes, used, need):
|
|
|
|
| 1782 |
need = (A[4] - B[3]) | (B[4] - A[3])
|
| 1783 |
for pa_, ua, va in [((), frozenset(), A[6])] + A[5]: # a part's own items meet the other's light
|
| 1784 |
for pb_, ub, vb in [((), frozenset(), B[6])] + B[5]:
|
| 1785 |
+
if SB.spent() > deadline:
|
| 1786 |
break
|
| 1787 |
meet = va & vb
|
| 1788 |
if not meet.any() or meet.sum() > 50:
|
workbench/swarm/data/flash_threshold.json
CHANGED
|
@@ -1,254 +1,12 @@
|
|
| 1 |
{
|
| 2 |
"format": "oracle7-flash-threshold/1",
|
| 3 |
-
"threshold": 0.
|
| 4 |
"at_threshold": {
|
| 5 |
-
"threshold": 0.
|
| 6 |
-
"right":
|
| 7 |
"wrong": 2,
|
| 8 |
-
"precision": 0.
|
| 9 |
},
|
| 10 |
-
"chosen_by": "lowest score at which
|
| 11 |
-
"n_scored":
|
| 12 |
-
"curve": [
|
| 13 |
-
{
|
| 14 |
-
"threshold": 0.997,
|
| 15 |
-
"right": 1,
|
| 16 |
-
"wrong": 0,
|
| 17 |
-
"precision": 1.0
|
| 18 |
-
},
|
| 19 |
-
{
|
| 20 |
-
"threshold": 0.968,
|
| 21 |
-
"right": 12,
|
| 22 |
-
"wrong": 0,
|
| 23 |
-
"precision": 1.0
|
| 24 |
-
},
|
| 25 |
-
{
|
| 26 |
-
"threshold": 0.901,
|
| 27 |
-
"right": 22,
|
| 28 |
-
"wrong": 1,
|
| 29 |
-
"precision": 0.957
|
| 30 |
-
},
|
| 31 |
-
{
|
| 32 |
-
"threshold": 0.818,
|
| 33 |
-
"right": 32,
|
| 34 |
-
"wrong": 2,
|
| 35 |
-
"precision": 0.941
|
| 36 |
-
},
|
| 37 |
-
{
|
| 38 |
-
"threshold": 0.798,
|
| 39 |
-
"right": 43,
|
| 40 |
-
"wrong": 2,
|
| 41 |
-
"precision": 0.956
|
| 42 |
-
},
|
| 43 |
-
{
|
| 44 |
-
"threshold": 0.753,
|
| 45 |
-
"right": 52,
|
| 46 |
-
"wrong": 4,
|
| 47 |
-
"precision": 0.929
|
| 48 |
-
},
|
| 49 |
-
{
|
| 50 |
-
"threshold": 0.742,
|
| 51 |
-
"right": 63,
|
| 52 |
-
"wrong": 4,
|
| 53 |
-
"precision": 0.94
|
| 54 |
-
},
|
| 55 |
-
{
|
| 56 |
-
"threshold": 0.719,
|
| 57 |
-
"right": 73,
|
| 58 |
-
"wrong": 5,
|
| 59 |
-
"precision": 0.936
|
| 60 |
-
},
|
| 61 |
-
{
|
| 62 |
-
"threshold": 0.684,
|
| 63 |
-
"right": 81,
|
| 64 |
-
"wrong": 8,
|
| 65 |
-
"precision": 0.91
|
| 66 |
-
},
|
| 67 |
-
{
|
| 68 |
-
"threshold": 0.661,
|
| 69 |
-
"right": 91,
|
| 70 |
-
"wrong": 9,
|
| 71 |
-
"precision": 0.91
|
| 72 |
-
},
|
| 73 |
-
{
|
| 74 |
-
"threshold": 0.64,
|
| 75 |
-
"right": 96,
|
| 76 |
-
"wrong": 15,
|
| 77 |
-
"precision": 0.865
|
| 78 |
-
},
|
| 79 |
-
{
|
| 80 |
-
"threshold": 0.629,
|
| 81 |
-
"right": 104,
|
| 82 |
-
"wrong": 18,
|
| 83 |
-
"precision": 0.852
|
| 84 |
-
},
|
| 85 |
-
{
|
| 86 |
-
"threshold": 0.612,
|
| 87 |
-
"right": 114,
|
| 88 |
-
"wrong": 19,
|
| 89 |
-
"precision": 0.857
|
| 90 |
-
},
|
| 91 |
-
{
|
| 92 |
-
"threshold": 0.6,
|
| 93 |
-
"right": 122,
|
| 94 |
-
"wrong": 22,
|
| 95 |
-
"precision": 0.847
|
| 96 |
-
},
|
| 97 |
-
{
|
| 98 |
-
"threshold": 0.588,
|
| 99 |
-
"right": 132,
|
| 100 |
-
"wrong": 23,
|
| 101 |
-
"precision": 0.852
|
| 102 |
-
},
|
| 103 |
-
{
|
| 104 |
-
"threshold": 0.575,
|
| 105 |
-
"right": 140,
|
| 106 |
-
"wrong": 26,
|
| 107 |
-
"precision": 0.843
|
| 108 |
-
},
|
| 109 |
-
{
|
| 110 |
-
"threshold": 0.565,
|
| 111 |
-
"right": 150,
|
| 112 |
-
"wrong": 27,
|
| 113 |
-
"precision": 0.847
|
| 114 |
-
},
|
| 115 |
-
{
|
| 116 |
-
"threshold": 0.561,
|
| 117 |
-
"right": 159,
|
| 118 |
-
"wrong": 29,
|
| 119 |
-
"precision": 0.846
|
| 120 |
-
},
|
| 121 |
-
{
|
| 122 |
-
"threshold": 0.548,
|
| 123 |
-
"right": 168,
|
| 124 |
-
"wrong": 31,
|
| 125 |
-
"precision": 0.844
|
| 126 |
-
},
|
| 127 |
-
{
|
| 128 |
-
"threshold": 0.532,
|
| 129 |
-
"right": 176,
|
| 130 |
-
"wrong": 34,
|
| 131 |
-
"precision": 0.838
|
| 132 |
-
},
|
| 133 |
-
{
|
| 134 |
-
"threshold": 0.522,
|
| 135 |
-
"right": 186,
|
| 136 |
-
"wrong": 35,
|
| 137 |
-
"precision": 0.842
|
| 138 |
-
},
|
| 139 |
-
{
|
| 140 |
-
"threshold": 0.512,
|
| 141 |
-
"right": 194,
|
| 142 |
-
"wrong": 38,
|
| 143 |
-
"precision": 0.836
|
| 144 |
-
},
|
| 145 |
-
{
|
| 146 |
-
"threshold": 0.501,
|
| 147 |
-
"right": 203,
|
| 148 |
-
"wrong": 40,
|
| 149 |
-
"precision": 0.835
|
| 150 |
-
},
|
| 151 |
-
{
|
| 152 |
-
"threshold": 0.494,
|
| 153 |
-
"right": 211,
|
| 154 |
-
"wrong": 43,
|
| 155 |
-
"precision": 0.831
|
| 156 |
-
},
|
| 157 |
-
{
|
| 158 |
-
"threshold": 0.488,
|
| 159 |
-
"right": 221,
|
| 160 |
-
"wrong": 44,
|
| 161 |
-
"precision": 0.834
|
| 162 |
-
},
|
| 163 |
-
{
|
| 164 |
-
"threshold": 0.484,
|
| 165 |
-
"right": 231,
|
| 166 |
-
"wrong": 45,
|
| 167 |
-
"precision": 0.837
|
| 168 |
-
},
|
| 169 |
-
{
|
| 170 |
-
"threshold": 0.478,
|
| 171 |
-
"right": 240,
|
| 172 |
-
"wrong": 47,
|
| 173 |
-
"precision": 0.836
|
| 174 |
-
},
|
| 175 |
-
{
|
| 176 |
-
"threshold": 0.463,
|
| 177 |
-
"right": 245,
|
| 178 |
-
"wrong": 53,
|
| 179 |
-
"precision": 0.822
|
| 180 |
-
},
|
| 181 |
-
{
|
| 182 |
-
"threshold": 0.458,
|
| 183 |
-
"right": 253,
|
| 184 |
-
"wrong": 56,
|
| 185 |
-
"precision": 0.819
|
| 186 |
-
},
|
| 187 |
-
{
|
| 188 |
-
"threshold": 0.45,
|
| 189 |
-
"right": 262,
|
| 190 |
-
"wrong": 58,
|
| 191 |
-
"precision": 0.819
|
| 192 |
-
},
|
| 193 |
-
{
|
| 194 |
-
"threshold": 0.442,
|
| 195 |
-
"right": 270,
|
| 196 |
-
"wrong": 61,
|
| 197 |
-
"precision": 0.816
|
| 198 |
-
},
|
| 199 |
-
{
|
| 200 |
-
"threshold": 0.433,
|
| 201 |
-
"right": 280,
|
| 202 |
-
"wrong": 62,
|
| 203 |
-
"precision": 0.819
|
| 204 |
-
},
|
| 205 |
-
{
|
| 206 |
-
"threshold": 0.418,
|
| 207 |
-
"right": 289,
|
| 208 |
-
"wrong": 64,
|
| 209 |
-
"precision": 0.819
|
| 210 |
-
},
|
| 211 |
-
{
|
| 212 |
-
"threshold": 0.401,
|
| 213 |
-
"right": 299,
|
| 214 |
-
"wrong": 65,
|
| 215 |
-
"precision": 0.821
|
| 216 |
-
},
|
| 217 |
-
{
|
| 218 |
-
"threshold": 0.38,
|
| 219 |
-
"right": 308,
|
| 220 |
-
"wrong": 67,
|
| 221 |
-
"precision": 0.821
|
| 222 |
-
},
|
| 223 |
-
{
|
| 224 |
-
"threshold": 0.361,
|
| 225 |
-
"right": 317,
|
| 226 |
-
"wrong": 69,
|
| 227 |
-
"precision": 0.821
|
| 228 |
-
},
|
| 229 |
-
{
|
| 230 |
-
"threshold": 0.335,
|
| 231 |
-
"right": 327,
|
| 232 |
-
"wrong": 70,
|
| 233 |
-
"precision": 0.824
|
| 234 |
-
},
|
| 235 |
-
{
|
| 236 |
-
"threshold": 0.313,
|
| 237 |
-
"right": 335,
|
| 238 |
-
"wrong": 73,
|
| 239 |
-
"precision": 0.821
|
| 240 |
-
},
|
| 241 |
-
{
|
| 242 |
-
"threshold": 0.3,
|
| 243 |
-
"right": 343,
|
| 244 |
-
"wrong": 76,
|
| 245 |
-
"precision": 0.819
|
| 246 |
-
},
|
| 247 |
-
{
|
| 248 |
-
"threshold": 0.258,
|
| 249 |
-
"right": 352,
|
| 250 |
-
"wrong": 78,
|
| 251 |
-
"precision": 0.819
|
| 252 |
-
}
|
| 253 |
-
]
|
| 254 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"format": "oracle7-flash-threshold/1",
|
| 3 |
+
"threshold": 0.733,
|
| 4 |
"at_threshold": {
|
| 5 |
+
"threshold": 0.733,
|
| 6 |
+
"right": 38,
|
| 7 |
"wrong": 2,
|
| 8 |
+
"precision": 0.95
|
| 9 |
},
|
| 10 |
+
"chosen_by": "10.5: relearned after program_score stopped testing one arbitrary word: lowest score at which development additions reach precision >= 0.95 (>= 10 right)",
|
| 11 |
+
"n_scored": 260
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
}
|
workbench/swarm/examiner105.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""examiner105 (10.5) - anyone's sealed test. Write a panel of your own questions, seal it, publish its hash, and run it once: the same discipline
|
| 2 |
+
the release is held to, on your machine.
|
| 3 |
+
exam seal <panel.json> check the panel and print its hash, to publish before anything runs
|
| 4 |
+
exam run <panel.json> [out.json] run it through the head. A run is once: its hash is logged and refused the second time
|
| 5 |
+
Panel: {"name": ..., "items": [{"q": question, "a": expected answer, "valid": true} or {"q": question, "valid": false}]}.
|
| 6 |
+
At least 59 items (no failure then bounds the error below 5% at 95%), and at least 20% must-decline, so a system that always answers fails."""
|
| 7 |
+
import hashlib
|
| 8 |
+
import json
|
| 9 |
+
import math
|
| 10 |
+
import sys
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
MIN_ITEMS, MIN_DECLINE = 59, 0.2
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def load(path):
|
| 17 |
+
p = json.load(open(path, encoding="utf-8"))
|
| 18 |
+
items = p.get("items") if isinstance(p, dict) else None
|
| 19 |
+
if not isinstance(items, list) or not items:
|
| 20 |
+
raise ValueError("a panel is {\"items\": [...]}")
|
| 21 |
+
for i, x in enumerate(items):
|
| 22 |
+
if not isinstance(x, dict) or not str(x.get("q", "")).strip() or "valid" not in x:
|
| 23 |
+
raise ValueError("item %d needs q and valid" % i)
|
| 24 |
+
if x["valid"] and x.get("a") in (None, "", []):
|
| 25 |
+
raise ValueError("item %d is valid but has no expected answer" % i)
|
| 26 |
+
seen = {x["q"] for x in items}
|
| 27 |
+
if len(seen) != len(items):
|
| 28 |
+
raise ValueError("a question appears twice")
|
| 29 |
+
must = sum(1 for x in items if not x["valid"])
|
| 30 |
+
if len(items) < MIN_ITEMS or must < MIN_DECLINE * len(items):
|
| 31 |
+
raise ValueError("needs at least %d items and %d%% must-decline; this has %d and %d" % (MIN_ITEMS, MIN_DECLINE * 100, len(items), must))
|
| 32 |
+
return p, items, hashlib.sha256(json.dumps(items, sort_keys=True).encode()).hexdigest()
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def bound(n, k=0):
|
| 36 |
+
"""the 95% upper bound on the error rate, from k failures in n (exact, by bisection)"""
|
| 37 |
+
lo, hi = 0.0, 1.0
|
| 38 |
+
for _ in range(60):
|
| 39 |
+
m = (lo + hi) / 2
|
| 40 |
+
tail = sum(math.comb(n, i) * m ** i * (1 - m) ** (n - i) for i in range(k + 1))
|
| 41 |
+
lo, hi = (m, hi) if tail > 0.05 else (lo, m)
|
| 42 |
+
return hi
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def run(path, engine, log, out=None):
|
| 46 |
+
from handoff101 import answer_of, compare
|
| 47 |
+
_p, items, sha = load(path)
|
| 48 |
+
used = log.read_text(encoding="utf-8").split() if log.exists() else []
|
| 49 |
+
if sha in used:
|
| 50 |
+
raise ValueError("this panel already ran once: write new items for a new test")
|
| 51 |
+
rows, tally = [], {"right": 0, "wrong": 0, "declined": 0, "false_yes": 0, "declined_right": 0}
|
| 52 |
+
for i, x in enumerate(items):
|
| 53 |
+
r = engine.chat(x["q"], "examiner%d" % i)
|
| 54 |
+
a = answer_of(r) if r.get("status") == "answered" else None
|
| 55 |
+
if not x["valid"]:
|
| 56 |
+
k = "false_yes" if a is not None else "declined_right"
|
| 57 |
+
elif a is None:
|
| 58 |
+
k = "declined"
|
| 59 |
+
else:
|
| 60 |
+
want = x["a"] if isinstance(x["a"], list) else [x["a"]]
|
| 61 |
+
k = "right" if any(compare(a, str(w))[0] == "CONFIRMED" for w in want) else "wrong"
|
| 62 |
+
tally[k] += 1
|
| 63 |
+
rows.append({"q": x["q"], "got": a, "result": k, "path": str(r.get("path"))})
|
| 64 |
+
log.parent.mkdir(parents=True, exist_ok=True)
|
| 65 |
+
import declines
|
| 66 |
+
with open(log.parent / "holes_from_exams.jsonl", "a", encoding="utf-8") as f: # every failed question, typed, for the night
|
| 67 |
+
for x, row, k in ((x, row, row["result"]) for x, row in zip(items, rows)):
|
| 68 |
+
if k in ("declined", "wrong", "false_yes"):
|
| 69 |
+
f.write(json.dumps({"q": x["q"], "expect": x.get("a"), "valid": x["valid"], "failed_as": k, "kind": declines.classify(str(row["got"]), row["path"]), "panel": sha}) + "\n")
|
| 70 |
+
with open(log, "a", encoding="utf-8") as f:
|
| 71 |
+
f.write(sha + "\n")
|
| 72 |
+
said = tally["right"] + tally["wrong"] + tally["false_yes"]
|
| 73 |
+
rep = {"panel": sha, "tally": tally, "items": len(items), "error_bound_95": round(bound(said, tally["wrong"] + tally["false_yes"]), 4) if said else None, "rows": rows}
|
| 74 |
+
if out:
|
| 75 |
+
json.dump(rep, open(out, "w", encoding="utf-8"), indent=1, ensure_ascii=False)
|
| 76 |
+
return rep
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def main(rest, engine_factory, state):
|
| 80 |
+
if not rest or rest[0] not in ("seal", "run") or len(rest) < 2:
|
| 81 |
+
print(__doc__)
|
| 82 |
+
return 2
|
| 83 |
+
try:
|
| 84 |
+
if rest[0] == "seal":
|
| 85 |
+
_p, items, sha = load(rest[1])
|
| 86 |
+
print("sealed: %d items\nsha256 %s\nPublish this hash before you run it." % (len(items), sha))
|
| 87 |
+
return 0
|
| 88 |
+
rep = run(rest[1], engine_factory(), Path(state) / "examiner_used.txt", rest[2] if len(rest) > 2 else None)
|
| 89 |
+
print(json.dumps({k: rep[k] for k in ("panel", "items", "tally", "error_bound_95")}, indent=1))
|
| 90 |
+
return 0
|
| 91 |
+
except ValueError as e:
|
| 92 |
+
print("refused: %s" % e)
|
| 93 |
+
return 1
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
if __name__ == "__main__":
|
| 97 |
+
sys.exit(main(sys.argv[1:], None, "."))
|
workbench/swarm/own_steps.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""own_steps (11.0, plan 5.3) - the reader's steps in Oracle7's own terms. A program is a chain of store operations; KQA's names for them are an export
|
| 3 |
+
format for outside scoring, and nothing more. The map is one-to-one, so the change is proved by running every labelled program both ways: the same
|
| 4 |
+
answer, every time. usage: own_steps.py (runs the proof over every program in KQA validation)"""
|
| 5 |
+
import json
|
| 6 |
+
import sys
|
| 7 |
+
|
| 8 |
+
OWN = {"FindAll": "every_thing", "Find": "named", "FilterConcept": "of_kind", "FilterStr": "with_text", "FilterNum": "with_number", "FilterYear": "with_year", "FilterDate": "with_date",
|
| 9 |
+
"QFilterStr": "where_text", "QFilterNum": "where_number", "QFilterYear": "where_year", "QFilterDate": "where_date", "Relate": "along", "And": "both", "Or": "either", "What": "name_of",
|
| 10 |
+
"Count": "how_many", "QueryAttr": "value_of", "QueryAttrUnderCondition": "value_of_where", "QueryAttrQualifier": "qualifier_of", "QueryRelation": "link_between",
|
| 11 |
+
"QueryRelationQualifier": "qualifier_of_link", "SelectBetween": "pick_between", "SelectAmong": "pick_among", "VerifyStr": "is_text", "VerifyNum": "is_number", "VerifyYear": "is_year", "VerifyDate": "is_date"}
|
| 12 |
+
KQA = {v: k for k, v in OWN.items()}
|
| 13 |
+
assert len(KQA) == len(OWN)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def to_own(program):
|
| 17 |
+
return [dict(st, function=OWN[st["function"]]) for st in program]
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def to_export(program):
|
| 21 |
+
return [dict(st, function=KQA[st["function"]]) for st in program]
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def run_own(store, program):
|
| 25 |
+
"""a program in Oracle7's terms, on the store"""
|
| 26 |
+
return store.run(to_export(program))
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
if __name__ == "__main__":
|
| 30 |
+
sys.path[:0] = ["/opt/rel102/t/workbench/swarm"]
|
| 31 |
+
import steps_kb as K
|
| 32 |
+
kq = K.KQA()
|
| 33 |
+
tot = same = 0
|
| 34 |
+
bad = []
|
| 35 |
+
for name in ("val",):
|
| 36 |
+
for x in json.load(open("/opt/rel50/kqa/%s.json" % name)):
|
| 37 |
+
tot += 1
|
| 38 |
+
|
| 39 |
+
def go(f, p):
|
| 40 |
+
try:
|
| 41 |
+
return f(p)
|
| 42 |
+
except Exception as e: # noqa: BLE001 - a program that fails must fail the same way
|
| 43 |
+
return "ERR %s" % type(e).__name__
|
| 44 |
+
own = to_own(x["program"])
|
| 45 |
+
assert to_export(own) == x["program"]
|
| 46 |
+
a, b = go(kq.run, x["program"]), go(lambda p: run_own(kq, p), own)
|
| 47 |
+
if a == b or sorted(map(str, a if isinstance(a, (list, set)) else [a])) == sorted(map(str, b if isinstance(b, (list, set)) else [b])):
|
| 48 |
+
same += 1
|
| 49 |
+
else:
|
| 50 |
+
bad.append(x["question"][:80])
|
| 51 |
+
print(json.dumps({"programs": tot, "same_answer": same, "different": bad[:5]}))
|
workbench/swarm/part11.py
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""part11 (11.0) - THE PART FORMAT, version 1. Growth by declaration: a part is declared data that the format can check, and no kind here
|
| 3 |
+
carries code. It is hashed, so a changed part is a new part, signed by its author (Ed25519, RFC 8032, standard library only), and carries
|
| 4 |
+
its examiner. The format sets the least examiner each kind must have, and an author can only add to it, so nobody can write an easy exam.
|
| 5 |
+
kinds table, formula, alias-set, reading-rule, sensor, example-set, composite
|
| 6 |
+
admit every structural check, with the reason for a refusal. A part that passes still has to sit its exam
|
| 7 |
+
apply runs a declared table or formula; a composite runs its parts in order, with units checked at each join
|
| 8 |
+
usage: part11.py new <part.json> <key.hex> | check <part.json> | keygen"""
|
| 9 |
+
import ast
|
| 10 |
+
import hashlib
|
| 11 |
+
import json
|
| 12 |
+
import math
|
| 13 |
+
import operator
|
| 14 |
+
import sys
|
| 15 |
+
|
| 16 |
+
FORMAT = "oracle7-part-v1"
|
| 17 |
+
KINDS = ("table", "formula", "alias-set", "reading-rule", "sensor", "example-set", "composite")
|
| 18 |
+
FIELDS = {"format", "kind", "version", "interface", "body", "examiner", "provenance", "reach", "id", "author", "signature"}
|
| 19 |
+
LEAST = {"n": 149, "must_decline_share": 0.2} # 5% error bound at 95% over 100 candidates, zero failures, 20% must-decline
|
| 20 |
+
|
| 21 |
+
# ---- Ed25519, RFC 8032 section 6
|
| 22 |
+
_p = 2 ** 255 - 19
|
| 23 |
+
_q = 2 ** 252 + 27742317777372353535851937790883648493
|
| 24 |
+
_d = (-121665 * pow(121666, _p - 2, _p)) % _p
|
| 25 |
+
_I = pow(2, (_p - 1) // 4, _p)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def _sha512(b):
|
| 29 |
+
return hashlib.sha512(b).digest()
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _add(P, Q):
|
| 33 |
+
A, B = (P[1] - P[0]) * (Q[1] - Q[0]) % _p, (P[1] + P[0]) * (Q[1] + Q[0]) % _p
|
| 34 |
+
C, D = 2 * P[3] * Q[3] * _d % _p, 2 * P[2] * Q[2] % _p
|
| 35 |
+
E, F, G, H = B - A, D - C, D + C, B + A
|
| 36 |
+
return (E * F % _p, G * H % _p, F * G % _p, E * H % _p)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def _mul(s, P):
|
| 40 |
+
Q = (0, 1, 1, 0)
|
| 41 |
+
while s:
|
| 42 |
+
if s & 1:
|
| 43 |
+
Q = _add(Q, P)
|
| 44 |
+
P = _add(P, P)
|
| 45 |
+
s >>= 1
|
| 46 |
+
return Q
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def _eq(P, Q):
|
| 50 |
+
return (P[0] * Q[2] - Q[0] * P[2]) % _p == 0 and (P[1] * Q[2] - Q[1] * P[2]) % _p == 0
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def _xrec(y, sign):
|
| 54 |
+
x2 = (y * y - 1) * pow(_d * y * y + 1, _p - 2, _p) % _p
|
| 55 |
+
if x2 == 0:
|
| 56 |
+
return None if sign else 0
|
| 57 |
+
x = pow(x2, (_p + 3) // 8, _p)
|
| 58 |
+
if (x * x - x2) % _p:
|
| 59 |
+
x = x * _I % _p
|
| 60 |
+
if (x * x - x2) % _p:
|
| 61 |
+
return None
|
| 62 |
+
return _p - x if (x & 1) != sign else x
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
_gy = 4 * pow(5, _p - 2, _p) % _p
|
| 66 |
+
_gx = _xrec(_gy, 0)
|
| 67 |
+
_G = (_gx, _gy, 1, _gx * _gy % _p)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def _comp(P):
|
| 71 |
+
zi = pow(P[2], _p - 2, _p)
|
| 72 |
+
x, y = P[0] * zi % _p, P[1] * zi % _p
|
| 73 |
+
return int.to_bytes(y | ((x & 1) << 255), 32, "little")
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def _dec(b):
|
| 77 |
+
y = int.from_bytes(b, "little")
|
| 78 |
+
sign = y >> 255
|
| 79 |
+
y &= (1 << 255) - 1
|
| 80 |
+
if y >= _p:
|
| 81 |
+
return None
|
| 82 |
+
x = _xrec(y, sign)
|
| 83 |
+
return None if x is None else (x, y, 1, x * y % _p)
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def _expand(sk):
|
| 87 |
+
h = _sha512(sk)
|
| 88 |
+
a = int.from_bytes(h[:32], "little")
|
| 89 |
+
a &= (1 << 254) - 8
|
| 90 |
+
a |= 1 << 254
|
| 91 |
+
return a, h[32:]
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def public_key(sk):
|
| 95 |
+
return _comp(_mul(_expand(sk)[0], _G))
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def sign(sk, msg):
|
| 99 |
+
a, pre = _expand(sk)
|
| 100 |
+
A = _comp(_mul(a, _G))
|
| 101 |
+
r = int.from_bytes(_sha512(pre + msg), "little") % _q
|
| 102 |
+
R = _comp(_mul(r, _G))
|
| 103 |
+
h = int.from_bytes(_sha512(R + A + msg), "little") % _q
|
| 104 |
+
return R + int.to_bytes((r + h * a) % _q, 32, "little")
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def verify(pk, msg, sig):
|
| 108 |
+
if len(pk) != 32 or len(sig) != 64:
|
| 109 |
+
return False
|
| 110 |
+
A, R = _dec(pk), _dec(sig[:32])
|
| 111 |
+
s = int.from_bytes(sig[32:], "little")
|
| 112 |
+
if A is None or R is None or s >= _q:
|
| 113 |
+
return False
|
| 114 |
+
h = int.from_bytes(_sha512(sig[:32] + pk + msg), "little") % _q
|
| 115 |
+
return _eq(_mul(s, _G), _add(R, _mul(h, A)))
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
# ---- the part
|
| 119 |
+
def canonical(part):
|
| 120 |
+
return json.dumps({k: part[k] for k in sorted(part) if k not in ("id", "signature")}, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode()
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def seal(part, sk):
|
| 124 |
+
"""an author's part, hashed and signed"""
|
| 125 |
+
p = dict(part, format=FORMAT, author=public_key(sk).hex())
|
| 126 |
+
p["id"] = hashlib.sha256(canonical(p)).hexdigest()
|
| 127 |
+
p["signature"] = sign(sk, bytes.fromhex(p["id"])).hex()
|
| 128 |
+
return p
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv, ast.Pow: operator.pow}
|
| 132 |
+
FUNCS = {"sqrt": math.sqrt, "log": math.log, "exp": math.exp, "sin": math.sin, "cos": math.cos, "abs": abs}
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def _formula_ok(expr, inputs):
|
| 136 |
+
"""a formula is arithmetic over its declared inputs: anything else in it is refused"""
|
| 137 |
+
try:
|
| 138 |
+
tree = ast.parse(expr, mode="eval")
|
| 139 |
+
except SyntaxError:
|
| 140 |
+
return False
|
| 141 |
+
for n in ast.walk(tree):
|
| 142 |
+
if isinstance(n, ast.Name):
|
| 143 |
+
if n.id not in inputs and n.id not in FUNCS:
|
| 144 |
+
return False
|
| 145 |
+
elif isinstance(n, ast.Call):
|
| 146 |
+
if not (isinstance(n.func, ast.Name) and n.func.id in FUNCS) or n.keywords:
|
| 147 |
+
return False
|
| 148 |
+
elif isinstance(n, ast.Constant):
|
| 149 |
+
if type(n.value) not in (int, float):
|
| 150 |
+
return False
|
| 151 |
+
elif not isinstance(n, (ast.Expression, ast.BinOp, ast.UnaryOp, ast.USub, ast.Load) + tuple(OPS)):
|
| 152 |
+
return False
|
| 153 |
+
return True
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def _eval(n, env):
|
| 157 |
+
if isinstance(n, ast.Expression):
|
| 158 |
+
return _eval(n.body, env)
|
| 159 |
+
if isinstance(n, ast.Constant):
|
| 160 |
+
return n.value
|
| 161 |
+
if isinstance(n, ast.Name):
|
| 162 |
+
return env[n.id]
|
| 163 |
+
if isinstance(n, ast.UnaryOp):
|
| 164 |
+
return -_eval(n.operand, env)
|
| 165 |
+
if isinstance(n, ast.BinOp):
|
| 166 |
+
return OPS[type(n.op)](_eval(n.left, env), _eval(n.right, env))
|
| 167 |
+
return FUNCS[n.func.id](*[_eval(a, env) for a in n.args])
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def _typed(xs):
|
| 171 |
+
return isinstance(xs, list) and xs and all(isinstance(x, dict) and isinstance(x.get("name"), str) and x["name"] and "unit" in x for x in xs)
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
def _body_ok(kind, body, face, known):
|
| 175 |
+
ins = {x["name"] for x in face["inputs"]} if face.get("inputs") else set()
|
| 176 |
+
if kind == "table":
|
| 177 |
+
rows = body.get("rows")
|
| 178 |
+
return isinstance(rows, list) and rows and all(isinstance(r, list) and len(r) == len(face["inputs"]) + len(face["outputs"]) for r in rows)
|
| 179 |
+
if kind == "formula":
|
| 180 |
+
return isinstance(body.get("expr"), str) and _formula_ok(body["expr"], ins) and bool(body.get("source"))
|
| 181 |
+
if kind == "alias-set":
|
| 182 |
+
a = body.get("aliases")
|
| 183 |
+
return isinstance(a, dict) and a and all(isinstance(k, str) and isinstance(v, str) for k, v in a.items())
|
| 184 |
+
if kind == "reading-rule":
|
| 185 |
+
return isinstance(body.get("shape"), str) and bool(body["shape"].strip())
|
| 186 |
+
if kind == "sensor":
|
| 187 |
+
return isinstance(body.get("channel"), str) and isinstance(body.get("ideal"), list) and len(body["ideal"]) == 2 and body["ideal"][0] < body["ideal"][1] and isinstance(body.get("range"), list) and len(body["range"]) == 2 and body["range"][0] <= body["ideal"][0] and body["ideal"][1] <= body["range"][1]
|
| 188 |
+
if kind == "example-set":
|
| 189 |
+
ex = body.get("examples")
|
| 190 |
+
return isinstance(ex, list) and ex and all(isinstance(x, dict) and x.get("cite") for x in ex)
|
| 191 |
+
if kind == "composite":
|
| 192 |
+
path = body.get("path")
|
| 193 |
+
if not (isinstance(path, list) and len(path) >= 2 and all(p in known for p in path)):
|
| 194 |
+
return False
|
| 195 |
+
for a, b in zip(path, path[1:]):
|
| 196 |
+
need = {x["unit"] for x in known[b]["interface"]["inputs"]}
|
| 197 |
+
if not {x["unit"] for x in known[a]["interface"]["outputs"]} & need:
|
| 198 |
+
return False
|
| 199 |
+
return True
|
| 200 |
+
return False
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
def admit(part, known=None, trusted=None):
|
| 204 |
+
"""structure, hash, signature, least examiner -> (True, "admitted") or (False, why). known: {id: part} for composites"""
|
| 205 |
+
if not isinstance(part, dict):
|
| 206 |
+
return False, "not a part"
|
| 207 |
+
if set(part) - FIELDS or FIELDS - {"reach"} - set(part):
|
| 208 |
+
return False, "fields: %s" % sorted((set(part) - FIELDS) | (FIELDS - {"reach"} - set(part)))
|
| 209 |
+
if part["format"] != FORMAT or part["kind"] not in KINDS:
|
| 210 |
+
return False, "format or kind"
|
| 211 |
+
face = part["interface"]
|
| 212 |
+
if not isinstance(face, dict) or not _typed(face.get("outputs")) or (part["kind"] not in ("sensor", "alias-set", "example-set", "composite") and not _typed(face.get("inputs"))):
|
| 213 |
+
return False, "interface must be typed, with units"
|
| 214 |
+
ex = part["examiner"]
|
| 215 |
+
if not isinstance(ex, dict) or not ex.get("generator") or not isinstance(ex.get("n"), int) or not isinstance(ex.get("must_decline_share"), (int, float)):
|
| 216 |
+
return False, "no examiner"
|
| 217 |
+
if ex["n"] < LEAST["n"] or ex["must_decline_share"] < LEAST["must_decline_share"]:
|
| 218 |
+
return False, "examiner below the format's least: %d items, %d%% must-decline" % (LEAST["n"], LEAST["must_decline_share"] * 100)
|
| 219 |
+
if ex.get("seed_rule") not in ("block-hash", "fixed-then-audited"):
|
| 220 |
+
return False, "an examiner's seed must come from outside the author"
|
| 221 |
+
ran = ex.get("ran") # a declared exam isn't an exam: the part carries the record of the one that ran
|
| 222 |
+
if not isinstance(ran, dict) or not isinstance(ran.get("items"), int) or ran.get("failed") != 0 or ran["items"] < ex["n"]:
|
| 223 |
+
return False, "no record of a passed exam of %d items" % ex["n"]
|
| 224 |
+
prov = part["provenance"]
|
| 225 |
+
if not isinstance(prov, dict) or not prov.get("sources"):
|
| 226 |
+
return False, "provenance"
|
| 227 |
+
if not _body_ok(part["kind"], part["body"], face, known or {}):
|
| 228 |
+
return False, "body doesn't hold for kind %s" % part["kind"]
|
| 229 |
+
if part["id"] != hashlib.sha256(canonical(part)).hexdigest():
|
| 230 |
+
return False, "hash"
|
| 231 |
+
try:
|
| 232 |
+
good = verify(bytes.fromhex(part["author"]), bytes.fromhex(part["id"]), bytes.fromhex(part["signature"]))
|
| 233 |
+
except (ValueError, TypeError):
|
| 234 |
+
good = False
|
| 235 |
+
if not good:
|
| 236 |
+
return False, "signature"
|
| 237 |
+
if trusted is not None and part["author"] not in trusted:
|
| 238 |
+
return False, "author not trusted here"
|
| 239 |
+
return True, "admitted"
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
def apply(part, inputs, known=None):
|
| 243 |
+
"""run a declared part on named inputs -> value or None. No kind here runs code from the part"""
|
| 244 |
+
k = part["kind"]
|
| 245 |
+
names = [x["name"] for x in part["interface"].get("inputs") or []]
|
| 246 |
+
if k == "table":
|
| 247 |
+
key = [inputs.get(n) for n in names]
|
| 248 |
+
return next((r[len(names):] for r in part["body"]["rows"] if r[:len(names)] == key), None)
|
| 249 |
+
if k == "formula":
|
| 250 |
+
return _eval(ast.parse(part["body"]["expr"], mode="eval"), {**FUNCS, **inputs}) if all(n in inputs for n in names) else None
|
| 251 |
+
if k == "sensor": # a reading passes only inside the sensor's stated range
|
| 252 |
+
lo, hi = part["body"]["range"]
|
| 253 |
+
x = next(iter(inputs.values()), None)
|
| 254 |
+
return x if isinstance(x, (int, float)) and lo <= x <= hi else None
|
| 255 |
+
if k == "composite":
|
| 256 |
+
v, cur = inputs, None
|
| 257 |
+
for pid in part["body"]["path"]:
|
| 258 |
+
p = known[pid]
|
| 259 |
+
cur = apply(p, v, known)
|
| 260 |
+
if cur is None:
|
| 261 |
+
return None
|
| 262 |
+
cur = cur[0] if isinstance(cur, list) else cur
|
| 263 |
+
n = [x["name"] for x in (known[part["body"]["path"][min(part["body"]["path"].index(pid) + 1, len(part["body"]["path"]) - 1)]]["interface"].get("inputs") or [{"name": "x"}])][0]
|
| 264 |
+
v = {n: cur}
|
| 265 |
+
return cur
|
| 266 |
+
return None
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
def _suite():
|
| 270 |
+
"""RFC 8032 vector, then parts that must be admitted and parts that must be refused"""
|
| 271 |
+
sk = bytes.fromhex("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60")
|
| 272 |
+
assert public_key(sk).hex() == "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"
|
| 273 |
+
assert sign(sk, b"").hex().startswith("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46b")
|
| 274 |
+
ex = {"generator": "store", "seed_rule": "block-hash", "n": 149, "must_decline_share": .2, "ran": {"items": 149, "failed": 0}}
|
| 275 |
+
base = {"version": 1, "examiner": ex, "provenance": {"sources": ["test"]}, "reach": None}
|
| 276 |
+
ohm = dict(base, kind="formula", interface={"inputs": [{"name": "v", "unit": "V"}, {"name": "r", "unit": "ohm"}], "outputs": [{"name": "i", "unit": "A"}]}, body={"expr": "v / r", "source": "Ohm's law"})
|
| 277 |
+
tbl = dict(base, kind="table", interface={"inputs": [{"name": "m", "unit": "symbol"}], "outputs": [{"name": "z", "unit": "count"}]}, body={"rows": [["H", 1], ["He", 2]]})
|
| 278 |
+
sq = dict(base, kind="formula", interface={"inputs": [{"name": "i", "unit": "A"}], "outputs": [{"name": "p", "unit": "W"}]}, body={"expr": "i ** 2 * 5", "source": "P = I^2 R at 5 ohm"})
|
| 279 |
+
good = {"formula": seal(ohm, sk), "table": seal(tbl, sk), "formula2": seal(sq, sk)}
|
| 280 |
+
known = {p["id"]: p for p in good.values()}
|
| 281 |
+
comp = seal(dict(base, kind="composite", interface={"inputs": ohm["interface"]["inputs"], "outputs": sq["interface"]["outputs"]}, body={"path": [good["formula"]["id"], good["formula2"]["id"]]}), sk)
|
| 282 |
+
known[comp["id"]] = comp
|
| 283 |
+
res = {"admitted": [admit(p, known) for p in list(good.values()) + [comp]]}
|
| 284 |
+
assert all(r[0] for r in res["admitted"]), res
|
| 285 |
+
assert apply(good["formula"], {"v": 10, "r": 2}) == 5 and apply(good["table"], {"m": "He"}) == [2] and apply(comp, {"v": 10, "r": 2}, known) == 125
|
| 286 |
+
p = good["formula"]
|
| 287 |
+
other = bytes(range(32))
|
| 288 |
+
bad = {"malformed": "nope", "unknown field": dict(p, evil=1), "unsigned": dict(p, signature="00" * 64), "body changed after sealing": dict(p, body={"expr": "v * r", "source": "x"}),
|
| 289 |
+
"foreign signature": dict(p, signature=sign(other, bytes.fromhex(p["id"])).hex()),
|
| 290 |
+
"code in a formula": seal(dict(ohm, body={"expr": "__import__('os').system('x')", "source": "x"}), sk), "attribute in a formula": seal(dict(ohm, body={"expr": "v.__class__", "source": "x"}), sk),
|
| 291 |
+
"undeclared input": seal(dict(ohm, body={"expr": "v / q", "source": "x"}), sk), "no units": seal(dict(ohm, interface={"inputs": [{"name": "v"}], "outputs": [{"name": "i", "unit": "A"}]}), sk),
|
| 292 |
+
"easy examiner": seal(dict(ohm, examiner=dict(ex, n=5)), sk), "no must-decline": seal(dict(ohm, examiner=dict(ex, must_decline_share=0)), sk), "exam never run": seal(dict(ohm, examiner={k: v for k, v in ex.items() if k != "ran"}), sk), "exam failed": seal(dict(ohm, examiner=dict(ex, ran={"items": 149, "failed": 1})), sk), "exam smaller than declared": seal(dict(ohm, examiner=dict(ex, ran={"items": 20, "failed": 0})), sk), "author's own seed": seal(dict(ohm, examiner=dict(ex, seed_rule="mine")), sk),
|
| 293 |
+
"no provenance": seal(dict(ohm, provenance={}), sk), "unit mismatch at a join": seal(dict(base, kind="composite", interface=comp["interface"], body={"path": [good["table"]["id"], good["formula2"]["id"]]}), sk),
|
| 294 |
+
"example with no citation": seal(dict(base, kind="example-set", interface={"outputs": [{"name": "label", "unit": "class"}]}, body={"examples": [{"x": 1}]}), sk)}
|
| 295 |
+
res["refused"] = {k: admit(v, known) for k, v in bad.items()}
|
| 296 |
+
wrong = [k for k, v in res["refused"].items() if v[0]]
|
| 297 |
+
tr = admit(good["table"], known, trusted={"someone else"})
|
| 298 |
+
assert not tr[0]
|
| 299 |
+
return {"admitted": len(res["admitted"]), "refused": len(bad) - len(wrong), "of": len(bad), "wrongly admitted": wrong, "why": {k: v[1] for k, v in res["refused"].items()}}
|
| 300 |
+
|
| 301 |
+
|
| 302 |
+
if __name__ == "__main__":
|
| 303 |
+
a = sys.argv[1:]
|
| 304 |
+
if a[:1] == ["suite"] or not a:
|
| 305 |
+
print(json.dumps(_suite(), indent=1))
|
| 306 |
+
elif a[0] == "keygen":
|
| 307 |
+
import secrets
|
| 308 |
+
print(secrets.token_hex(32))
|
| 309 |
+
elif a[0] == "new":
|
| 310 |
+
print(json.dumps(seal(json.load(open(a[1], encoding="utf-8")), bytes.fromhex(open(a[2]).read().strip())), indent=1, ensure_ascii=False))
|
| 311 |
+
elif a[0] == "check":
|
| 312 |
+
print(admit(json.load(open(a[1], encoding="utf-8"))))
|
workbench/swarm/part_submit.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""part_submit (11.0) - a part in the format joins exactly as an internal one does. It is checked against the format, then becomes a specialist in your workspace
|
| 2 |
+
through the same builders the swarm uses (a table through to_workbench, a formula through compose), and it joins only by the same exam: `exam <id> dev`, then
|
| 3 |
+
`exam <id> heldout`. usage: oracle7.py part check|submit <part.json>"""
|
| 4 |
+
import json
|
| 5 |
+
import os
|
| 6 |
+
import random
|
| 7 |
+
import re
|
| 8 |
+
import shutil
|
| 9 |
+
import sys
|
| 10 |
+
import tempfile
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import exam_v1
|
| 14 |
+
import part11 as P
|
| 15 |
+
import run as swarm_run
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def _id(part, stem):
|
| 19 |
+
name = re.sub(r"[^a-z0-9]+", "-", str(part["provenance"].get("name") or part["id"][:12]).lower()).strip("-")
|
| 20 |
+
return ("%s-%s" % (stem, name))[:40].rstrip("-")
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def table(part):
|
| 24 |
+
"""rows as a table the swarm's own organ reads, with the swarm's own exam panel"""
|
| 25 |
+
b = part["body"]
|
| 26 |
+
info, rows = b.get("property"), b.get("records")
|
| 27 |
+
if not isinstance(info, dict) or not info.get("label") or not isinstance(rows, list) or not all(isinstance(r, dict) and r.get("label") and "value" in r for r in rows):
|
| 28 |
+
return None, "a table part to install needs body.property and body.records"
|
| 29 |
+
info = dict({"aliases": [], "datatype": "string", "pid": "part"}, **info)
|
| 30 |
+
rows = [dict({"qid": "P%d" % i, "aliases": []}, **r) for i, r in enumerate(rows)]
|
| 31 |
+
tmp = Path(tempfile.mkdtemp(prefix="o7part-"))
|
| 32 |
+
here = Path(__file__).resolve().parent
|
| 33 |
+
json.dump({"property": info, "rows": rows, "source": {"repo": "part", "sha256": part["id"], "licence": part["provenance"].get("licence", "see provenance")}}, open(tmp / "table.json", "w", encoding="utf-8"), ensure_ascii=False)
|
| 34 |
+
for f in ("organ_table_v2.py", "scorer_table_v1.py", "narrow_v1.py"):
|
| 35 |
+
shutil.copy(here / f, tmp / f)
|
| 36 |
+
json.dump(exam_v1.write(info, rows), open(tmp / "panel_v1.json", "w", encoding="utf-8"), ensure_ascii=False)
|
| 37 |
+
names = [info["label"]] + [a for a in info["aliases"] if len(a) >= 3]
|
| 38 |
+
json.dump({"name": "Part: %s" % info["label"], "sources": [{"name": "part %s" % part["id"][:16], "licence": part["provenance"].get("licence", "see provenance")}],
|
| 39 |
+
"accept": {"kinds": ["lookup"], "declines_in_domain": ["unknown_item"], "context_regex": r"\b(?:%s)\b" % "|".join(re.escape(n.lower()) for n in sorted(names, key=len, reverse=True))}, "notes": "Submitted in the part format."},
|
| 40 |
+
open(tmp / "manifest.json", "w", encoding="utf-8"), ensure_ascii=False)
|
| 41 |
+
d = swarm_run.to_workbench(tmp, _id(part, "part"), built_from={"questions": ["What is the %s of %s?" % (info["label"], r["label"]) for r in rows[:3]], "gap": info["label"]})
|
| 42 |
+
shutil.rmtree(tmp, ignore_errors=True)
|
| 43 |
+
return d, "installed"
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def formula(part):
|
| 47 |
+
"""worked examples from the formula, written by the format's own arithmetic, composed by the swarm's composer: it has to arrive at the same program"""
|
| 48 |
+
face = part["interface"]
|
| 49 |
+
rng = random.Random(part["id"])
|
| 50 |
+
lo, hi = part["body"].get("example_range", [1, 50])
|
| 51 |
+
lines = ["title: %s" % (part["provenance"].get("name") or part["id"][:12]), "inputs: " + "; ".join("%s (%s)" % (x.get("label", x["name"]), x["unit"]) for x in face["inputs"]),
|
| 52 |
+
"output: %s (%s)" % (face["outputs"][0]["name"], face["outputs"][0]["unit"]), "source: %s" % part["body"]["source"]]
|
| 53 |
+
for _ in range(16):
|
| 54 |
+
v = {x["name"]: round(rng.uniform(lo, hi), 3) for x in face["inputs"]}
|
| 55 |
+
lines.append("%s -> %s" % ("; ".join(str(v[x["name"]]) for x in face["inputs"]), round(P.apply(part, v), 6)))
|
| 56 |
+
r = swarm_run.compose("\n".join(lines), sid=_id(part, "part"), anyway=True)
|
| 57 |
+
return r.get("specialist"), r["message"]
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def main(rest):
|
| 61 |
+
if len(rest) < 2 or rest[0] not in ("check", "submit"):
|
| 62 |
+
print(__doc__)
|
| 63 |
+
return 2
|
| 64 |
+
part = json.load(open(rest[1], encoding="utf-8"))
|
| 65 |
+
ok, why = P.admit(part)
|
| 66 |
+
print("format: %s" % why)
|
| 67 |
+
if not ok or rest[0] == "check":
|
| 68 |
+
return 0 if ok else 1
|
| 69 |
+
go = {"table": table, "formula": formula}.get(part["kind"])
|
| 70 |
+
if go is None:
|
| 71 |
+
print("kind %s is held as a part; it has no specialist to install" % part["kind"])
|
| 72 |
+
return 0
|
| 73 |
+
d, msg = go(part)
|
| 74 |
+
print(msg)
|
| 75 |
+
if d:
|
| 76 |
+
print("now the same exam as any part:\n python oracle7.py exam %s dev\n python oracle7.py exam %s heldout" % (Path(d).name, Path(d).name))
|
| 77 |
+
return 0 if d else 1
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
if __name__ == "__main__":
|
| 81 |
+
sys.exit(main(sys.argv[1:]))
|
workbench/swarm/ref_measure.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""ref_measure - the sleep week's reference set measured in a fresh process, as the head stands after a night (11-04). Sleep's only change
|
| 3 |
+
that lives in memory is the alignment table the learner keeps; everything else (built specialists, retired parts) is on disk. So a fresh
|
| 4 |
+
head on the same tree and workbench, with that night's table loaded, is the head sleep has made, and a clean state folder makes every
|
| 5 |
+
measure a first ask. Runs beside the next night: the week takes about half the time.
|
| 6 |
+
usage: ref_measure.py <reference.json> <out.json> (env: O7_TREE_RUN, ORACLE7_WORKBENCH_LOCAL, ORACLE7_STATE, O7_ALIGN optional)"""
|
| 7 |
+
import collections
|
| 8 |
+
import importlib.util
|
| 9 |
+
import json
|
| 10 |
+
import os
|
| 11 |
+
import sys
|
| 12 |
+
import time
|
| 13 |
+
|
| 14 |
+
if os.environ.get("PYTHONHASHSEED") != "0":
|
| 15 |
+
os.environ["PYTHONHASHSEED"] = "0"
|
| 16 |
+
os.execv(sys.executable, [sys.executable] + sys.argv)
|
| 17 |
+
REF, OUTF = sys.argv[1], sys.argv[2]
|
| 18 |
+
T = os.environ["O7_TREE_RUN"]
|
| 19 |
+
spec = importlib.util.spec_from_file_location("o7", T + "/oracle7.py")
|
| 20 |
+
o7 = importlib.util.module_from_spec(spec)
|
| 21 |
+
sys.argv = ["oracle7.py"]
|
| 22 |
+
spec.loader.exec_module(o7)
|
| 23 |
+
sys.path[:0] = [T + "/workbench", T + "/workbench/swarm"]
|
| 24 |
+
import server # noqa: E402
|
| 25 |
+
eng = o7.Engine()
|
| 26 |
+
wb = server.WB = server.Workbench(eng)
|
| 27 |
+
rt = getattr(eng, "_rt", None)
|
| 28 |
+
for _ in range(60):
|
| 29 |
+
if rt is not None and ("facts" in rt._organs or "error" in rt._organs):
|
| 30 |
+
break
|
| 31 |
+
time.sleep(5)
|
| 32 |
+
from handoff101 import answer_of, compare # noqa: E402
|
| 33 |
+
|
| 34 |
+
align = os.environ.get("O7_ALIGN")
|
| 35 |
+
if align: # the learner's kept table, loaded the way sleep105.dream_learner loads it
|
| 36 |
+
cov = rt._organs["facts"].coverage
|
| 37 |
+
A = json.load(open(align, encoding="utf-8"))["p_feature_given_word"]
|
| 38 |
+
cov.align_t = A
|
| 39 |
+
cov.align_w = collections.defaultdict(dict)
|
| 40 |
+
for f, ws in A.items():
|
| 41 |
+
for w, pr in ws.items():
|
| 42 |
+
cov.align_w[w][f] = pr
|
| 43 |
+
cov.lex.words |= set(cov.align_w)
|
| 44 |
+
if hasattr(cov, "_dir_mass"):
|
| 45 |
+
del cov._dir_mass
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def right(answer, want):
|
| 49 |
+
if answer is None or want in (None, "", []):
|
| 50 |
+
return False
|
| 51 |
+
return any(compare(str(answer), str(w))[0] == "CONFIRMED" for w in (want if isinstance(want, list) else [want]))
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
reference = json.load(open(REF, encoding="utf-8"))
|
| 55 |
+
n = wrong = 0
|
| 56 |
+
rows = []
|
| 57 |
+
for k, it in enumerate(reference):
|
| 58 |
+
r = wb.chat(it["q"], "ref%d" % k)
|
| 59 |
+
a = answer_of(r) if r.get("status") == "answered" else None
|
| 60 |
+
ok = a is not None and right(a, it["expect"])
|
| 61 |
+
n += ok
|
| 62 |
+
wrong += a is not None and not ok
|
| 63 |
+
rows.append({"k": k, "status": r.get("status"), "right": ok, "path": str(r.get("path"))[:80]})
|
| 64 |
+
json.dump({"right": n, "wrong": wrong, "of": len(reference), "align": align, "rows": rows}, open(OUTF, "w", encoding="utf-8"))
|
| 65 |
+
print("done", n, wrong, flush=True)
|
workbench/swarm/skeleton102.py
CHANGED
|
@@ -18,6 +18,8 @@ import collections
|
|
| 18 |
import copy
|
| 19 |
import json
|
| 20 |
import re
|
|
|
|
|
|
|
| 21 |
import time
|
| 22 |
|
| 23 |
import reader_v4 as R4
|
|
@@ -254,10 +256,10 @@ class Skeletons:
|
|
| 254 |
self._pc = {} # prefix -> what the store returns, once per parse
|
| 255 |
self._said = {} # (function, inputs) -> the judge's "said by a word" verdict on that step alone
|
| 256 |
self._jc = {} # step-set -> the judge's verdict on the whole program (order does not change it)
|
| 257 |
-
deadline =
|
| 258 |
out = []
|
| 259 |
for sk in self.sk:
|
| 260 |
-
if
|
| 261 |
break
|
| 262 |
slots = collections.Counter(s for _f, _d, sl in sk for s in sl if s in ("E", "V", "C"))
|
| 263 |
if any(slots[t] > len(fill[t]) for t in ("E", "V")):
|
|
@@ -286,7 +288,7 @@ class Skeletons:
|
|
| 286 |
return key in self._ckeys_c[concept]
|
| 287 |
|
| 288 |
def _dfs(self, question, sk, fill, prog, results, i, usedEV, out, deadline):
|
| 289 |
-
if
|
| 290 |
return
|
| 291 |
rd = self.rd
|
| 292 |
if i == len(sk):
|
|
@@ -387,7 +389,7 @@ class Skeletons:
|
|
| 387 |
ran.append((empty, ins, p2, res))
|
| 388 |
ran.sort(key=lambda t_: t_[0])
|
| 389 |
for empty, ins, p2, res in ran:
|
| 390 |
-
if
|
| 391 |
return
|
| 392 |
r2 = results + [i] if empty else results
|
| 393 |
u2 = {"E": set(usedEV["E"]), "V": set(usedEV["V"])}
|
|
|
|
| 18 |
import copy
|
| 19 |
import json
|
| 20 |
import re
|
| 21 |
+
|
| 22 |
+
import steps_budget as SB
|
| 23 |
import time
|
| 24 |
|
| 25 |
import reader_v4 as R4
|
|
|
|
| 256 |
self._pc = {} # prefix -> what the store returns, once per parse
|
| 257 |
self._said = {} # (function, inputs) -> the judge's "said by a word" verdict on that step alone
|
| 258 |
self._jc = {} # step-set -> the judge's verdict on the whole program (order does not change it)
|
| 259 |
+
deadline = SB.deadline()
|
| 260 |
out = []
|
| 261 |
for sk in self.sk:
|
| 262 |
+
if SB.spent() > deadline or len(out) >= 40:
|
| 263 |
break
|
| 264 |
slots = collections.Counter(s for _f, _d, sl in sk for s in sl if s in ("E", "V", "C"))
|
| 265 |
if any(slots[t] > len(fill[t]) for t in ("E", "V")):
|
|
|
|
| 288 |
return key in self._ckeys_c[concept]
|
| 289 |
|
| 290 |
def _dfs(self, question, sk, fill, prog, results, i, usedEV, out, deadline):
|
| 291 |
+
if SB.spent() > deadline or len(out) >= 40:
|
| 292 |
return
|
| 293 |
rd = self.rd
|
| 294 |
if i == len(sk):
|
|
|
|
| 389 |
ran.append((empty, ins, p2, res))
|
| 390 |
ran.sort(key=lambda t_: t_[0])
|
| 391 |
for empty, ins, p2, res in ran:
|
| 392 |
+
if SB.spent() > deadline or len(out) >= 40:
|
| 393 |
return
|
| 394 |
r2 = results + [i] if empty else results
|
| 395 |
u2 = {"E": set(usedEV["E"]), "V": set(usedEV["V"])}
|
workbench/swarm/sleep105.py
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""sleep105 (10.5) - THE SLEEP CYCLE, the roadmap's five steps, connecting what the release already has. A day's turns go through the Workbench chat, the
|
| 3 |
+
same door a person uses, and are written to a chained ledger. Then, each night:
|
| 4 |
+
1 REPLAY the day's ledger is re-run. Declined turns become typed holes (declines.classify); answered turns are re-checked against everything
|
| 5 |
+
admitted that day, and a changed answer is reported.
|
| 6 |
+
2 CONSOLIDATION paths the router walked repeatedly become candidate composite parts, admitted only if they give the same answers as the path they
|
| 7 |
+
replace and pass an exam on fresh inputs (the day's questions with their numbers nudged).
|
| 8 |
+
3 DREAMING each hole is searched for across formulas (grow60: the library's chains, built by the swarm), stores (declines.resolve, and held-back readings a known
|
| 9 |
+
answer confirms, which feed the learner) and specialists (swarm build: a table from Wikidata, through its own held-out exam).
|
| 10 |
+
4 FORGETTING unused or dominated parts are retired with a receipt. A retired part stays in the content-addressed store, so replay can still reach it.
|
| 11 |
+
5 MORNING reach gained, holes filled, answers changed, parts retired, with no new wrong answer as the bar. Reports are chained.
|
| 12 |
+
Days are Oracle7's own failures from v37 to v38 (failure_days.py) unless O7_DAYS names sealed days. Simulated nights: each day is a batch.
|
| 13 |
+
usage: sleep105.py <gate rows.jsonl for the controls> <out dir> [nights] [turns per day]"""
|
| 14 |
+
import collections
|
| 15 |
+
import hashlib
|
| 16 |
+
import importlib.util
|
| 17 |
+
import json
|
| 18 |
+
import os
|
| 19 |
+
import random
|
| 20 |
+
import re
|
| 21 |
+
import shutil
|
| 22 |
+
import subprocess
|
| 23 |
+
import sys
|
| 24 |
+
import time
|
| 25 |
+
from pathlib import Path
|
| 26 |
+
|
| 27 |
+
if os.environ.get("PYTHONHASHSEED") != "0": # the same order oracle7.py and the gate run in
|
| 28 |
+
os.environ["PYTHONHASHSEED"] = "0"
|
| 29 |
+
os.execv(sys.executable, [sys.executable] + sys.argv)
|
| 30 |
+
|
| 31 |
+
rows, OUT, NIGHTS, PER = sys.argv[1], Path(sys.argv[2]), int(sys.argv[3]) if len(sys.argv) > 3 else 7, int(sys.argv[4]) if len(sys.argv) > 4 else 300
|
| 32 |
+
for d in ("parts", "store", "ledger"):
|
| 33 |
+
(OUT / d).mkdir(parents=True, exist_ok=True)
|
| 34 |
+
T = os.environ["O7_TREE_RUN"]
|
| 35 |
+
LOCAL = Path(os.environ["ORACLE7_WORKBENCH_LOCAL"])
|
| 36 |
+
(LOCAL / "specialists").mkdir(parents=True, exist_ok=True)
|
| 37 |
+
spec = importlib.util.spec_from_file_location("o7", T + "/oracle7.py")
|
| 38 |
+
o7 = importlib.util.module_from_spec(spec)
|
| 39 |
+
sys.argv = ["oracle7.py"]
|
| 40 |
+
spec.loader.exec_module(o7)
|
| 41 |
+
sys.path[:0] = [T + "/workbench", T + "/workbench/swarm"]
|
| 42 |
+
import server # noqa: E402
|
| 43 |
+
import exam # noqa: E402
|
| 44 |
+
eng = o7.Engine()
|
| 45 |
+
wb = server.WB = server.Workbench(eng)
|
| 46 |
+
rt = getattr(eng, "_rt", None)
|
| 47 |
+
for _ in range(60):
|
| 48 |
+
if rt is not None and ("facts" in rt._organs or "error" in rt._organs):
|
| 49 |
+
break
|
| 50 |
+
time.sleep(5)
|
| 51 |
+
import declines # noqa: E402
|
| 52 |
+
import grow60 as G # noqa: E402
|
| 53 |
+
import gen60 # noqa: E402
|
| 54 |
+
import part11 as P11 # noqa: E402
|
| 55 |
+
import run as swarm_run # noqa: E402
|
| 56 |
+
from handoff101 import answer_of, compare # noqa: E402
|
| 57 |
+
|
| 58 |
+
KEYF = OUT / "author.key"
|
| 59 |
+
if not KEYF.exists():
|
| 60 |
+
KEYF.write_text(os.urandom(32).hex())
|
| 61 |
+
KEYF.chmod(0o600)
|
| 62 |
+
KEY = bytes.fromhex(KEYF.read_text().strip())
|
| 63 |
+
REPEAT, GROUP, UNUSED_NIGHTS, CAP = 3, 3, 3, 4 # walked 3 times; 3 example questions (swarm build's own minimum); unused 3 nights; systems a night (grow60's CAP)
|
| 64 |
+
GROW = Path(os.environ.get("O7_GROW", str(OUT / "grow")))
|
| 65 |
+
if not GROW.exists():
|
| 66 |
+
shutil.copytree(Path(T) / "root" / "organs10" / "physics", GROW)
|
| 67 |
+
LIB = gen60.Library(json.load(open(Path(T) / "root" / "organs10" / "library" / "derived.json", encoding="utf-8")))
|
| 68 |
+
STATE = {"started": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "run": os.environ.get("O7_RUN", "Trial, on Oracle7's own failures"), "nights": NIGHTS, "per_day": PER, "night": 0, "phase": "starting",
|
| 69 |
+
"history": [], "parts": [], "retired": [], "exams": [], "changed": [], "finds": [], "found_right": 0, "planted": [], "reference": [], "totals": {"turns": 0, "holes": 0, "found": 0, "admitted": 0, "reach": 0, "wrong": 0, "false_yes": 0}}
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def save():
|
| 73 |
+
STATE["updated"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
| 74 |
+
tmp = OUT / "state.tmp"
|
| 75 |
+
tmp.write_text(json.dumps(STATE, indent=1, default=str), encoding="utf-8")
|
| 76 |
+
tmp.replace(OUT / "state.json")
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def phase(text, cur=None):
|
| 80 |
+
STATE["phase"] = text
|
| 81 |
+
if cur is not None:
|
| 82 |
+
STATE["current"] = dict(cur)
|
| 83 |
+
save()
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def right(answer, want):
|
| 87 |
+
if answer is None or want in (None, "", []):
|
| 88 |
+
return False
|
| 89 |
+
return any(compare(str(answer), str(w))[0] == "CONFIRMED" for w in (want if isinstance(want, list) else [want]))
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def turn(q, sid):
|
| 93 |
+
r = wb.chat(q, sid)
|
| 94 |
+
return r, (answer_of(r) if r.get("status") == "answered" else None)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
class Ledger:
|
| 98 |
+
def __init__(self, path):
|
| 99 |
+
self.path, self.prev = Path(path), "0" * 64
|
| 100 |
+
|
| 101 |
+
def add(self, e):
|
| 102 |
+
e = dict(e, prev=self.prev)
|
| 103 |
+
e["hash"] = self.prev = hashlib.sha256(json.dumps(e, sort_keys=True, default=str).encode()).hexdigest()
|
| 104 |
+
with open(self.path, "a", encoding="utf-8") as f:
|
| 105 |
+
f.write(json.dumps(e, default=str) + "\n")
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def sig(path):
|
| 109 |
+
"""a router path with what varies taken out"""
|
| 110 |
+
return re.sub(r"\b[0-9a-f]{8,}\b", "#", re.sub(r"\d+(\.\d+)?", "#", str(path)))
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def nudge(q, rng):
|
| 114 |
+
"""a day's question with its numbers moved: a fresh input of the same form"""
|
| 115 |
+
return re.sub(r"(?<![\w.])\d+(?:\.\d+)?(?![\w.])", lambda m: str(max(1, round(float(m.group(0)) * rng.uniform(1.1, 1.9)))), q)
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def keep_part(body, kind, name, ran, sources, face=None):
|
| 119 |
+
"""a passed exam -> a sealed part in the 11.0 format, stored by its hash, never overwritten"""
|
| 120 |
+
p = P11.seal({"kind": kind, "version": 1, "interface": face or {"inputs": [{"name": "question", "unit": "text"}], "outputs": [{"name": "answer", "unit": "text"}]}, "body": body,
|
| 121 |
+
"examiner": {"generator": "variations of the day's questions", "seed_rule": "fixed-then-audited", "n": ran["items"], "must_decline_share": .2, "ran": ran}, "provenance": {"sources": sources, "name": name}, "reach": None}, KEY)
|
| 122 |
+
(OUT / "store" / (p["id"] + ".json")).write_text(json.dumps(p, indent=1), encoding="utf-8")
|
| 123 |
+
return p
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
# ---- 1 REPLAY
|
| 127 |
+
def replay(day_rows, night):
|
| 128 |
+
holes, changed = [], []
|
| 129 |
+
for k, e in enumerate(day_rows):
|
| 130 |
+
r, a = turn(e["q"], "replay%d-%d" % (night, k))
|
| 131 |
+
if e["status"] == "answered":
|
| 132 |
+
if a != e["answer"] and not str(e["path"]).startswith("yours:formula"):
|
| 133 |
+
changed.append({"q": e["q"], "was": e["answer"], "now": a, "path": str(r.get("path"))})
|
| 134 |
+
elif a is None:
|
| 135 |
+
holes.append({"q": e["q"], "kind": declines.classify(str(r.get("response", "")), str(r.get("path", ""))), "path": str(r.get("path", "")), "expect": e.get("expect"), "valid": e.get("valid", True),
|
| 136 |
+
"held": (r.get("provenance") or {}).get("held_back")})
|
| 137 |
+
if k % 10 == 0:
|
| 138 |
+
phase("replay: turn %d of %d" % (k, len(day_rows)))
|
| 139 |
+
return holes, changed
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
# ---- 2 CONSOLIDATION
|
| 143 |
+
def consolidate(day_rows, night):
|
| 144 |
+
"""repeated paths through one part become a direct route, only if it answers as the walked path does"""
|
| 145 |
+
walked = collections.defaultdict(list)
|
| 146 |
+
for e in day_rows:
|
| 147 |
+
m = re.search(r"(?:yours|specialist):([\w.-]+)", str(e.get("path", "")))
|
| 148 |
+
if e["status"] == "answered" and m:
|
| 149 |
+
walked[(sig(e["path"]), m.group(1))].append(e)
|
| 150 |
+
done = []
|
| 151 |
+
rng = random.Random(night)
|
| 152 |
+
for (s, sid), es in sorted(walked.items(), key=lambda kv: -len(kv[1])):
|
| 153 |
+
if len(es) < REPEAT or sid not in wb.mine:
|
| 154 |
+
continue
|
| 155 |
+
sp, same = wb.mine[sid], 0
|
| 156 |
+
for e in es:
|
| 157 |
+
a = sp.reply(e["q"], "route")
|
| 158 |
+
same += bool(a) and a.get("status") == "answered" and right(answer_of(a), e["answer"])
|
| 159 |
+
fresh = [nudge(e["q"], rng) for e in es for _ in range(50)][:149]
|
| 160 |
+
agree = 0
|
| 161 |
+
for k, q in enumerate(fresh):
|
| 162 |
+
a = sp.reply(q, "route")
|
| 163 |
+
_r, b = turn(q, "fresh%d" % k)
|
| 164 |
+
agree += (a is None and b is None) or (bool(a) and b is not None and right(answer_of(a), b))
|
| 165 |
+
rec = {"night": night, "kind": "route", "steps": [s[:90]], "shape": s, "right": same, "declined": 0, "outcome": "passed" if same == len(es) and agree == len(fresh) and len(fresh) >= 149 else "differs from the path it would replace (%d of %d, %d of %d fresh)" % (same, len(es), agree, len(fresh))}
|
| 166 |
+
STATE["exams"].append(rec)
|
| 167 |
+
if rec["outcome"] == "passed":
|
| 168 |
+
keep_part({"shape": s, "route_to": sid}, "reading-rule", s, {"items": len(fresh), "failed": 0}, ["paths walked on night %d" % night])
|
| 169 |
+
done.append({"kind": "route", "shape": s, "id": sid, "night": night, "steps": [s[:90]], "reach": 0, "source": "route"})
|
| 170 |
+
return done
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
# ---- 3 DREAMING
|
| 174 |
+
def dream_stores(hole, sid):
|
| 175 |
+
"""declines.resolve: a part, the part's source, or the question read into a form the core reads"""
|
| 176 |
+
def ask_core(text):
|
| 177 |
+
r = eng.chat(text, sid + "c")
|
| 178 |
+
return {"status": r.get("status"), "response": str(r.get("response", "")), "path": str(r.get("path", ""))}
|
| 179 |
+
return declines.resolve(hole["q"], ask_core, wb.mine, server.SPECS)
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
def dream_formulas(holes, night, rep):
|
| 183 |
+
"""grow60's round over the night's holes: chains the formula library holds, chosen by how many holes they close, built by the swarm and admitted by its exam"""
|
| 184 |
+
planner = G.Planner(G.load_parts(str(GROW)))
|
| 185 |
+
gaps = [h for h in holes if G.ask(planner, h["q"]).get("kind") == "no_part"]
|
| 186 |
+
needs = [G.gap_needs(LIB, h["q"], INSTALLED) for h in gaps]
|
| 187 |
+
chosen = G.choose([n for n in needs if n], CAP, LIB)
|
| 188 |
+
rep["formula_gaps"] = sum(1 for n in needs if n)
|
| 189 |
+
if not chosen:
|
| 190 |
+
return []
|
| 191 |
+
phase("dreaming: the swarm builds %d systems from the formula library" % len(chosen))
|
| 192 |
+
G.build(str(GROW), LIB, chosen, OUT / "grow_runs", night)
|
| 193 |
+
INSTALLED.update(chosen)
|
| 194 |
+
before = set(planner.parts)
|
| 195 |
+
after = G.Planner(G.load_parts(str(GROW)))
|
| 196 |
+
new = sorted(set(after.parts) - before)
|
| 197 |
+
out = []
|
| 198 |
+
for pid in new:
|
| 199 |
+
p = after.parts[pid]
|
| 200 |
+
out.append({"kind": "formula", "shape": pid, "id": pid, "night": night, "steps": ["%s from %s" % (p["output"], ", ".join(p["inputs"]))], "reach": 0, "source": "formula library"})
|
| 201 |
+
STATE["exams"].append({"night": night, "kind": "formula", "steps": ["%d systems" % len(chosen)], "right": len(new), "declined": 0, "outcome": "%d parts admitted by the swarm's exam" % len(new)})
|
| 202 |
+
return out
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
def dream_specialists(holes, night):
|
| 206 |
+
"""holes that ask for the same property go to swarm build, as its example questions, then through its exam. Variations come from the builder's panel"""
|
| 207 |
+
groups = collections.defaultdict(list)
|
| 208 |
+
for h in holes:
|
| 209 |
+
g = (h.get("gap") or {})
|
| 210 |
+
if g.get("build") == "table":
|
| 211 |
+
groups[g["property"].lower()].append(h)
|
| 212 |
+
out = []
|
| 213 |
+
for prop, hs in sorted(groups.items(), key=lambda kv: -len(kv[1])):
|
| 214 |
+
if len(hs) < GROUP:
|
| 215 |
+
continue
|
| 216 |
+
phase("dreaming: building a table of %s from %d holes" % (prop, len(hs)))
|
| 217 |
+
try:
|
| 218 |
+
b = swarm_run.build([h["q"] for h in hs[:6]])
|
| 219 |
+
except Exception as e: # noqa: BLE001 - a failed build costs that candidate
|
| 220 |
+
STATE["exams"].append({"night": night, "kind": "table", "steps": [prop], "right": 0, "declined": 0, "outcome": "build failed: %s" % str(e)[:80]})
|
| 221 |
+
continue
|
| 222 |
+
d = b.get("specialist")
|
| 223 |
+
rec = {"night": night, "kind": "table", "steps": [prop], "right": 0, "declined": 0, "outcome": str(b.get("message"))[:110]}
|
| 224 |
+
STATE["exams"].append(rec)
|
| 225 |
+
if not d:
|
| 226 |
+
continue
|
| 227 |
+
v = exam.run(d, "dev", eng)
|
| 228 |
+
if v["decision"] not in ("PASS", "ADMIT"):
|
| 229 |
+
rec["outcome"] = "dev: " + "; ".join(v["reasons"])[:110]
|
| 230 |
+
continue
|
| 231 |
+
v = exam.run(d, "heldout", eng)
|
| 232 |
+
rec.update(right=v["with_specialist"]["ok"], outcome=v["decision"] + (": " + "; ".join(v["reasons"])[:100] if v["reasons"] else ""))
|
| 233 |
+
if v["decision"] == "ADMIT":
|
| 234 |
+
wb.reload()
|
| 235 |
+
keep_part({"rows": [[prop, Path(d).name]]}, "table", Path(d).name, {"items": v["n"], "failed": 0}, ["swarm build from %d holes" % len(hs)], {"inputs": [{"name": "property", "unit": "text"}], "outputs": [{"name": "specialist", "unit": "id"}]})
|
| 236 |
+
out.append({"kind": "table", "shape": prop, "id": Path(d).name, "night": night, "steps": [prop], "reach": 0, "source": "swarm build"})
|
| 237 |
+
return out
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
def dream_learner(holes, night, rep):
|
| 241 |
+
"""a held-back reading the day's known answer confirms becomes a labelled pair for the learner, and the alignment is relearned with them. The new table stays only if
|
| 242 |
+
every reading it newly states is right and no must-decline gains an answer: a confirmation is data, and the exam decides"""
|
| 243 |
+
cov = rt._organs["facts"].coverage
|
| 244 |
+
new = [h for h in holes if h.get("held") and h.get("valid", True) is not False and right(h["held"]["answer"], h.get("expect"))]
|
| 245 |
+
rep["confirmed"] = len(new)
|
| 246 |
+
PAIRS.extend({"question": h["q"], "program": h["held"]["program"]} for h in new)
|
| 247 |
+
if not new:
|
| 248 |
+
return []
|
| 249 |
+
phase("dreaming: relearning the alignment with %d confirmed readings" % len(PAIRS), rep)
|
| 250 |
+
pf, out = OUT / "pairs.json", OUT / ("alignment_%d.json" % night)
|
| 251 |
+
pf.write_text(json.dumps(PAIRS), encoding="utf-8")
|
| 252 |
+
subprocess.run([sys.executable, "/opt/rel105/dev/align_learn105.py", str(out)], cwd=T, env=dict(os.environ, O7_EXTRA_PAIRS=str(pf)), capture_output=True, timeout=3000)
|
| 253 |
+
|
| 254 |
+
def use(path):
|
| 255 |
+
A = json.load(open(path, encoding="utf-8"))["p_feature_given_word"]
|
| 256 |
+
cov.align_t = A
|
| 257 |
+
cov.align_w = collections.defaultdict(dict)
|
| 258 |
+
for f, ws in A.items():
|
| 259 |
+
for w, pr in ws.items():
|
| 260 |
+
cov.align_w[w][f] = pr
|
| 261 |
+
cov.lex.words |= set(cov.align_w)
|
| 262 |
+
if hasattr(cov, "_dir_mass"):
|
| 263 |
+
del cov._dir_mass
|
| 264 |
+
was = STATE.get("alignment") or str(Path(T) / "workbench" / "swarm" / "data" / "alignment102.json")
|
| 265 |
+
use(out)
|
| 266 |
+
said = wrong = 0
|
| 267 |
+
bad = None
|
| 268 |
+
phase("exam: every held reading and control against the new table", rep)
|
| 269 |
+
for k, h in enumerate(h for h in holes if h.get("held")):
|
| 270 |
+
_r, a = turn(h["q"], "learn%d-%d" % (night, k))
|
| 271 |
+
if a is not None:
|
| 272 |
+
said += 1
|
| 273 |
+
if h.get("valid", True) is False or not right(a, h.get("expect")):
|
| 274 |
+
wrong += 1
|
| 275 |
+
bad = bad or h["q"][:100]
|
| 276 |
+
rec = {"night": night, "kind": "learner", "steps": ["alignment relearned with %d confirmed readings" % len(PAIRS)], "right": said - wrong, "declined": 0, "outcome": "kept: %d newly stated, none wrong" % said if said and not wrong else ("no reading rose to the threshold" if not said else "refused, %d wrong: %s" % (wrong, bad))}
|
| 277 |
+
STATE["exams"].append(rec)
|
| 278 |
+
if said and not wrong:
|
| 279 |
+
STATE["alignment"] = str(out)
|
| 280 |
+
keep_part({"examples": [{"question": x["question"], "cite": "confirmed against the known answer"} for x in PAIRS]}, "example-set", "confirmed readings, night %d" % night, {"items": max(149, len(new)), "failed": 0}, ["held-back readings confirmed on night %d" % night], {"outputs": [{"name": "alignment", "unit": "table"}]})
|
| 281 |
+
return [{"kind": "learner", "shape": "alignment %d" % night, "id": "reader90", "night": night, "steps": [rec["steps"][0]], "reach": 0, "source": "the learner"}]
|
| 282 |
+
use(was)
|
| 283 |
+
return []
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
# ---- the days
|
| 287 |
+
days = json.load(open(os.environ.get("O7_DAYS", "/opt/rel105/failure_days.json"), encoding="utf-8"))
|
| 288 |
+
items = [json.loads(x) for x in open(rows, encoding="utf-8") if x.strip()]
|
| 289 |
+
controls = [x for x in items if x.get("valid") is False and x.get("status") != "answered" and not x["id"].startswith("REG")]
|
| 290 |
+
random.Random(106).shuffle(controls)
|
| 291 |
+
REF_N = int(os.environ.get("O7_REF_N", "200"))
|
| 292 |
+
reference, days = days[-REF_N:], days[:len(days) - REF_N]
|
| 293 |
+
STATE["pool"] = {"valid": len(days), "controls": len(controls), "reference": len(reference)}
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
def measure():
|
| 297 |
+
n = wrong = 0
|
| 298 |
+
for k, it in enumerate(reference):
|
| 299 |
+
_r, a = turn(it["q"], "ref%d" % k)
|
| 300 |
+
if a is not None:
|
| 301 |
+
n += right(a, it["expect"])
|
| 302 |
+
wrong += not right(a, it["expect"])
|
| 303 |
+
return n, wrong
|
| 304 |
+
|
| 305 |
+
|
| 306 |
+
def measure_fork(night):
|
| 307 |
+
"""the reference measured in a fresh process (ref_measure.py), as the head stands after night `night`, while the next night runs (11-04:
|
| 308 |
+
about half the time). The only state sleep keeps in memory, the learner's alignment table, is passed by path; built specialists are on
|
| 309 |
+
disk. A clean state folder each night makes every measure a first ask: in one process a second ask moved 2 of 100 answers. (A fork
|
| 310 |
+
was tried first: the head's threads leave a forked copy waiting on a lock no thread will release.)"""
|
| 311 |
+
out = OUT / "ref" / ("night%d.json" % night)
|
| 312 |
+
out.parent.mkdir(exist_ok=True)
|
| 313 |
+
rf = OUT / "ref" / "reference.json"
|
| 314 |
+
if not rf.exists():
|
| 315 |
+
rf.write_text(json.dumps(reference), encoding="utf-8")
|
| 316 |
+
st = OUT / "ref" / ("state%d" % night)
|
| 317 |
+
st.mkdir(exist_ok=True)
|
| 318 |
+
env = dict(os.environ, ORACLE7_STATE=str(st), PYTHONHASHSEED="0")
|
| 319 |
+
if STATE.get("alignment"):
|
| 320 |
+
env["O7_ALIGN"] = STATE["alignment"]
|
| 321 |
+
p = subprocess.Popen([sys.executable, str(Path(__file__).resolve().parent / "ref_measure.py"), str(rf), str(out)], env=env,
|
| 322 |
+
stdout=open(str(out) + ".log", "w"), stderr=subprocess.STDOUT)
|
| 323 |
+
return p, out
|
| 324 |
+
|
| 325 |
+
|
| 326 |
+
def measured(pending):
|
| 327 |
+
p, out = pending
|
| 328 |
+
p.wait()
|
| 329 |
+
r = json.loads(out.read_text())
|
| 330 |
+
return r["right"], r["wrong"]
|
| 331 |
+
|
| 332 |
+
|
| 333 |
+
def write_report(rep, a_, w_):
|
| 334 |
+
global chain
|
| 335 |
+
rep["reference_right"], rep["reference_wrong"] = a_, w_
|
| 336 |
+
STATE["reference"].append({"night": rep["night"], "right": a_, "wrong": w_, "of": len(reference)})
|
| 337 |
+
body = json.dumps(dict(rep, prev=chain), sort_keys=True)
|
| 338 |
+
chain = hashlib.sha256(body.encode()).hexdigest()
|
| 339 |
+
with open(OUT / "reports.jsonl", "a", encoding="utf-8") as f:
|
| 340 |
+
f.write(json.dumps({"report": json.loads(body), "hash": chain}) + "\n")
|
| 341 |
+
save()
|
| 342 |
+
print(json.dumps(rep), flush=True)
|
| 343 |
+
|
| 344 |
+
|
| 345 |
+
INSTALLED, parts, retired, chain, PAIRS = set(), [], [], "0" * 64, []
|
| 346 |
+
planner_live = G.Planner(G.load_parts(str(GROW)))
|
| 347 |
+
pending = (measure_fork(0), None) # (the process measuring, the report waiting on it); night 0 has no report
|
| 348 |
+
if os.environ.get("O7_REF_CHECK"): # test runs only: night 0 measured here too, to show the fresh process measures the same head
|
| 349 |
+
STATE["ref_check"] = dict(zip(("right", "wrong"), measure()))
|
| 350 |
+
save()
|
| 351 |
+
per_c = max(1, PER // 6)
|
| 352 |
+
for night in range(1, NIGHTS + 1):
|
| 353 |
+
STATE["night"] = night
|
| 354 |
+
day = days[(night - 1) * PER:night * PER] + [dict(x, valid=False) for x in controls[(night - 1) * per_c:night * per_c]]
|
| 355 |
+
if not day:
|
| 356 |
+
break
|
| 357 |
+
random.Random(night).shuffle(day)
|
| 358 |
+
led = Ledger(OUT / "ledger" / ("day%d.jsonl" % night))
|
| 359 |
+
rep = {"night": night, "turns": len(day), "holes": 0, "found": 0, "gained": 0, "wrong": 0, "false_yes": 0, "admitted": 0, "retired": 0, "changed": 0, "candidates": 0, "failed": 0}
|
| 360 |
+
t0 = time.time()
|
| 361 |
+
day_rows = []
|
| 362 |
+
for k, it in enumerate(day):
|
| 363 |
+
r, a = turn(it["q"], "n%d-%d" % (night, k))
|
| 364 |
+
path = str(r.get("path", ""))
|
| 365 |
+
if a is None: # the formula parts this run has grown
|
| 366 |
+
p = G.ask(planner_live, it["q"])
|
| 367 |
+
if p.get("status") == "answered":
|
| 368 |
+
a, path = str(p.get("value")), "yours:formula>" + str(p.get("plan"))[:80]
|
| 369 |
+
e = {"q": it["q"], "status": "answered" if a is not None else str(r.get("status")), "answer": a, "path": path, "expect": it.get("expect"), "valid": it.get("valid", True)}
|
| 370 |
+
led.add(e)
|
| 371 |
+
day_rows.append(e)
|
| 372 |
+
used = next((p for p in parts if p["id"] in path), None)
|
| 373 |
+
if a is not None:
|
| 374 |
+
if it.get("valid", True) is False:
|
| 375 |
+
rep["false_yes"] += 1
|
| 376 |
+
if used:
|
| 377 |
+
used["bad"] = "false yes: " + it["q"][:120]
|
| 378 |
+
elif used:
|
| 379 |
+
if right(a, it.get("expect")):
|
| 380 |
+
rep["gained"] += 1
|
| 381 |
+
used["reach"] += 1
|
| 382 |
+
used["last_used"] = night
|
| 383 |
+
else:
|
| 384 |
+
rep["wrong"] += 1
|
| 385 |
+
used["bad"] = "wrong: %s -> %s" % (it["q"][:100], str(a)[:60])
|
| 386 |
+
if k % 10 == 0:
|
| 387 |
+
phase("day: turn %d of %d" % (k, len(day)), rep)
|
| 388 |
+
holes, changed = replay(day_rows, night)
|
| 389 |
+
rep["changed"] = len(changed)
|
| 390 |
+
STATE["changed"] = (STATE["changed"] + [dict(c, night=night) for c in changed])[-40:]
|
| 391 |
+
rep["holes"] = len(holes)
|
| 392 |
+
phase("consolidation", rep)
|
| 393 |
+
new = consolidate(day_rows, night)
|
| 394 |
+
open_holes = []
|
| 395 |
+
for k, h in enumerate(holes):
|
| 396 |
+
if h.get("valid") is False:
|
| 397 |
+
continue
|
| 398 |
+
if k % 10 == 0:
|
| 399 |
+
phase("dreaming: stores, hole %d of %d" % (k, len(holes)), rep)
|
| 400 |
+
d = dream_stores(h, "h%d-%d" % (night, k))
|
| 401 |
+
h["gap"] = d.get("gap")
|
| 402 |
+
if d["status"] == "answered":
|
| 403 |
+
f = {"night": night, "q": h["q"], "answer": str(d["response"])[:200], "source": d.get("kind"), "right": right(d["response"], h.get("expect")), "valid": True, "steps": [d.get("fix")]}
|
| 404 |
+
rep["found"] += 1
|
| 405 |
+
STATE["found_right"] += int(f["right"])
|
| 406 |
+
STATE["finds"] = (STATE["finds"] + [f])[-60:]
|
| 407 |
+
else:
|
| 408 |
+
open_holes.append(h)
|
| 409 |
+
phase("dreaming: formulas", rep)
|
| 410 |
+
new += dream_formulas(open_holes, night, rep)
|
| 411 |
+
new += dream_specialists(open_holes, night)
|
| 412 |
+
new += dream_learner(holes, night, rep)
|
| 413 |
+
planner_live = G.Planner(G.load_parts(str(GROW)))
|
| 414 |
+
rep["admitted"] = len(new)
|
| 415 |
+
# an exam record can stand for several parts (the swarm's formula exam), so refusals are counted as records and candidates as
|
| 416 |
+
# what was admitted plus what was refused (11-04: candidates minus admitted had gone negative)
|
| 417 |
+
kept = ("passed", "kept", "admit")
|
| 418 |
+
rep["failed"] = sum(1 for x in STATE["exams"] if x.get("night") == night and not str(x.get("outcome", "")).lower().startswith(kept)
|
| 419 |
+
and "parts admitted" not in str(x.get("outcome", "")))
|
| 420 |
+
rep["candidates"] = rep["admitted"] + rep["failed"]
|
| 421 |
+
for p in new:
|
| 422 |
+
p["last_used"] = night
|
| 423 |
+
parts += new
|
| 424 |
+
# ---- 4 FORGETTING
|
| 425 |
+
keep = []
|
| 426 |
+
for p in parts:
|
| 427 |
+
if p.get("bad") or night - p.get("last_used", p["night"]) >= UNUSED_NIGHTS:
|
| 428 |
+
rep["retired"] += 1
|
| 429 |
+
retired.append({"id": p["id"], "shape": p["shape"], "night": night, "receipt": p.get("bad") or "unused for %d nights" % UNUSED_NIGHTS})
|
| 430 |
+
if (server.SPECS / p["id"]).exists():
|
| 431 |
+
shutil.move(str(server.SPECS / p["id"]), str(OUT / "store" / p["id"]))
|
| 432 |
+
wb.reload()
|
| 433 |
+
else:
|
| 434 |
+
keep.append(p)
|
| 435 |
+
parts = keep
|
| 436 |
+
# ---- 5 MORNING
|
| 437 |
+
rep["seconds"] = round(time.time() - t0)
|
| 438 |
+
STATE["parts"] = [{k: p.get(k) for k in ("id", "kind", "shape", "steps", "source", "night", "reach")} for p in parts]
|
| 439 |
+
STATE["retired"] = retired
|
| 440 |
+
tt = STATE["totals"]
|
| 441 |
+
for a, b in (("turns", "turns"), ("holes", "holes"), ("found", "found"), ("admitted", "admitted"), ("reach", "gained"), ("wrong", "wrong"), ("false_yes", "false_yes")):
|
| 442 |
+
tt[a] += rep[b]
|
| 443 |
+
STATE.pop("current", None)
|
| 444 |
+
STATE["history"].append(rep)
|
| 445 |
+
phase("measuring the reference set")
|
| 446 |
+
fork, waiting = pending
|
| 447 |
+
a_, w_ = measured(fork)
|
| 448 |
+
if waiting is None:
|
| 449 |
+
STATE["reference"].append({"night": 0, "right": a_, "wrong": w_, "of": len(reference)})
|
| 450 |
+
else:
|
| 451 |
+
write_report(waiting, a_, w_)
|
| 452 |
+
pending = (measure_fork(night), rep)
|
| 453 |
+
fork, waiting = pending
|
| 454 |
+
write_report(waiting, *measured(fork))
|
| 455 |
+
STATE["phase"] = "finished"
|
| 456 |
+
save()
|
| 457 |
+
print("done", flush=True)
|
workbench/swarm/steps_budget.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""steps_budget (10.5) - search is bounded by counted steps, so a reading is the same on any machine under any load"""
|
| 2 |
+
LIMIT = 12000
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class Budget:
|
| 6 |
+
n = 0
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def deadline():
|
| 10 |
+
return Budget.n + LIMIT
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def spent():
|
| 14 |
+
Budget.n += 1
|
| 15 |
+
return Budget.n
|
workbench/verify.py
CHANGED
|
@@ -126,6 +126,10 @@ def verify_install(root=None):
|
|
| 126 |
for f in ("oracle7.py", "PACK_MANIFEST.json"):
|
| 127 |
if (INSTALL / f).exists():
|
| 128 |
parts[f] = hashlib.sha256((INSTALL / f).read_bytes()).hexdigest()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
if any(k.startswith("data/") for k in pub["parts"]) and (INSTALL / "PACK_MANIFEST.json").exists(): # recipe v4 (10.2)
|
| 130 |
manifest = json.loads((INSTALL / "PACK_MANIFEST.json").read_text(encoding="utf-8"))
|
| 131 |
parts.update({k: v[0] for k, v in data_parts(R, manifest).items()})
|
|
|
|
| 126 |
for f in ("oracle7.py", "PACK_MANIFEST.json"):
|
| 127 |
if (INSTALL / f).exists():
|
| 128 |
parts[f] = hashlib.sha256((INSTALL / f).read_bytes()).hexdigest()
|
| 129 |
+
for top in ("orbiters", "records"): # recipe v5 (10.5): each orbiter, and the records the changelog points to
|
| 130 |
+
for d in sorted(p for p in (INSTALL / top).glob("*") if p.is_dir() and p.name not in SKIP_DIRS):
|
| 131 |
+
if top + "/" + d.name in pub["parts"] or any(k.startswith(top + "/") for k in pub["parts"]):
|
| 132 |
+
parts[top + "/" + d.name] = part_hash(d)[0]
|
| 133 |
if any(k.startswith("data/") for k in pub["parts"]) and (INSTALL / "PACK_MANIFEST.json").exists(): # recipe v4 (10.2)
|
| 134 |
manifest = json.loads((INSTALL / "PACK_MANIFEST.json").read_text(encoding="utf-8"))
|
| 135 |
parts.update({k: v[0] for k, v in data_parts(R, manifest).items()})
|