algorithm-discovery-engine 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ads/__init__.py +85 -0
- ads/benchmark.py +84 -0
- ads/problems.py +190 -0
- ads/problems_advanced.py +203 -0
- ads/structures.py +370 -0
- ads/structures_advanced.py +385 -0
- algo_discovery/__init__.py +19 -0
- algo_discovery/__main__.py +38 -0
- algo_discovery/engine.py +64 -0
- algo_discovery/features.py +115 -0
- algo_discovery/hypotheses.py +251 -0
- algo_discovery/models.py +63 -0
- algorithm_discovery_engine-1.0.0.dist-info/METADATA +287 -0
- algorithm_discovery_engine-1.0.0.dist-info/RECORD +27 -0
- algorithm_discovery_engine-1.0.0.dist-info/WHEEL +4 -0
- algorithm_discovery_engine-1.0.0.dist-info/entry_points.txt +2 -0
- algorithm_discovery_engine-1.0.0.dist-info/licenses/LICENSE +21 -0
- gui/__init__.py +3 -0
- gui/__main__.py +26 -0
- gui/app.py +407 -0
- gui/core.py +149 -0
- synth/__init__.py +22 -0
- synth/__main__.py +66 -0
- synth/corpus.py +222 -0
- synth/discovery.py +198 -0
- synth/grammar.py +174 -0
- synth/search.py +432 -0
gui/__main__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""``python -m gui`` launcher. Runs without a display only when ``--selftest``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main() -> int:
|
|
9
|
+
if "--selftest" in sys.argv:
|
|
10
|
+
from gui import core
|
|
11
|
+
|
|
12
|
+
print(f"gui selftest ok (catalog: {core.engine_catalog_counts()})")
|
|
13
|
+
return 0
|
|
14
|
+
try:
|
|
15
|
+
import tkinter # noqa: F401
|
|
16
|
+
except ImportError:
|
|
17
|
+
print("tkinter is not available in this Python build.", file=sys.stderr)
|
|
18
|
+
return 2
|
|
19
|
+
from gui.app import main as app_main
|
|
20
|
+
|
|
21
|
+
app_main()
|
|
22
|
+
return 0
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
if __name__ == "__main__":
|
|
26
|
+
raise SystemExit(main())
|
gui/app.py
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
"""Tkinter desktop GUI for algorithm-discovery-engine.
|
|
2
|
+
|
|
3
|
+
Launch with ``python -m gui`` (or ``python engine/runner.py gui``). The UI is
|
|
4
|
+
pure stdlib — no third-party runtime dependencies.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import contextlib
|
|
10
|
+
import queue
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
import threading
|
|
14
|
+
import tkinter as tk
|
|
15
|
+
import webbrowser
|
|
16
|
+
from collections.abc import Callable
|
|
17
|
+
from tkinter import ttk
|
|
18
|
+
from typing import Any, Literal
|
|
19
|
+
|
|
20
|
+
from gui import core
|
|
21
|
+
|
|
22
|
+
BG = "#0d1117"
|
|
23
|
+
PANEL = "#161b22"
|
|
24
|
+
BORDER = "#30363d"
|
|
25
|
+
TEXT = "#e6edf3"
|
|
26
|
+
MUTED = "#8b949e"
|
|
27
|
+
ACCENT = "#1f6feb"
|
|
28
|
+
GREEN = "#3fb950"
|
|
29
|
+
RED = "#f85149"
|
|
30
|
+
|
|
31
|
+
Report = dict[str, Any]
|
|
32
|
+
Message = tuple[Callable[[Any], None], Any]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class App:
|
|
36
|
+
"""Main application window."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, root: tk.Tk) -> None:
|
|
39
|
+
self.root = root
|
|
40
|
+
self.root.title("algorithm-discovery-engine")
|
|
41
|
+
self.root.geometry("980x640")
|
|
42
|
+
self.root.minsize(800, 540)
|
|
43
|
+
self.root.configure(bg=BG)
|
|
44
|
+
self._style()
|
|
45
|
+
self._messages: queue.Queue[Message] = queue.Queue()
|
|
46
|
+
self._busy = False
|
|
47
|
+
self._tasks: list[ttk.Button] = []
|
|
48
|
+
self.root.after(100, self._poll)
|
|
49
|
+
self._build()
|
|
50
|
+
|
|
51
|
+
# ------------------------------------------------------------------ style
|
|
52
|
+
def _style(self) -> None:
|
|
53
|
+
style = ttk.Style(self.root)
|
|
54
|
+
with contextlib.suppress(tk.TclError):
|
|
55
|
+
style.theme_use("clam")
|
|
56
|
+
style.configure(
|
|
57
|
+
"TNotebook", background=BG, borderwidth=0, tabmargins=(4, 4, 4, 0)
|
|
58
|
+
)
|
|
59
|
+
style.configure(
|
|
60
|
+
"TNotebook.Tab",
|
|
61
|
+
background=PANEL, foreground=MUTED, padding=(14, 8), borderwidth=0,
|
|
62
|
+
)
|
|
63
|
+
style.map(
|
|
64
|
+
"TNotebook.Tab",
|
|
65
|
+
background=[("selected", ACCENT)],
|
|
66
|
+
foreground=[("selected", "#ffffff")],
|
|
67
|
+
)
|
|
68
|
+
style.configure("TFrame", background=BG)
|
|
69
|
+
style.configure(
|
|
70
|
+
"Treeview",
|
|
71
|
+
background=PANEL, fieldbackground=PANEL, foreground=TEXT,
|
|
72
|
+
borderwidth=0, rowheight=24,
|
|
73
|
+
)
|
|
74
|
+
style.configure(
|
|
75
|
+
"Treeview.Heading",
|
|
76
|
+
background=BORDER, foreground=TEXT, relief="flat", padding=(6, 5),
|
|
77
|
+
)
|
|
78
|
+
style.map(
|
|
79
|
+
"Treeview",
|
|
80
|
+
background=[("selected", ACCENT)],
|
|
81
|
+
foreground=[("selected", "#ffffff")],
|
|
82
|
+
)
|
|
83
|
+
style.configure("TLabel", background=BG, foreground=TEXT)
|
|
84
|
+
style.configure("Muted.TLabel", background=BG, foreground=MUTED)
|
|
85
|
+
style.configure(
|
|
86
|
+
"TButton", background=PANEL, foreground=TEXT, borderwidth=1,
|
|
87
|
+
focusthickness=0, padding=(10, 6),
|
|
88
|
+
)
|
|
89
|
+
style.configure(
|
|
90
|
+
"Accent.TButton", background=ACCENT, foreground="#ffffff",
|
|
91
|
+
borderwidth=1, padding=(10, 6),
|
|
92
|
+
)
|
|
93
|
+
style.configure(
|
|
94
|
+
"TEntry", background=PANEL, foreground=TEXT, fieldbackground=PANEL,
|
|
95
|
+
insertcolor=TEXT, bordercolor=BORDER,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# ------------------------------------------------------------ background
|
|
99
|
+
def _button(
|
|
100
|
+
self,
|
|
101
|
+
parent: ttk.Frame,
|
|
102
|
+
text: str,
|
|
103
|
+
command: Callable[[], None],
|
|
104
|
+
accent: bool = False,
|
|
105
|
+
) -> ttk.Button:
|
|
106
|
+
button = ttk.Button(
|
|
107
|
+
parent, text=text, command=command, takefocus=False,
|
|
108
|
+
style="Accent.TButton" if accent else "TButton",
|
|
109
|
+
)
|
|
110
|
+
self._tasks.append(button)
|
|
111
|
+
return button
|
|
112
|
+
|
|
113
|
+
def _set_busy(self, busy: bool) -> None:
|
|
114
|
+
self._busy = busy
|
|
115
|
+
for button in self._tasks:
|
|
116
|
+
button.configure(state=tk.DISABLED if busy else tk.NORMAL)
|
|
117
|
+
|
|
118
|
+
def _submit(
|
|
119
|
+
self,
|
|
120
|
+
task: Callable[[], Any],
|
|
121
|
+
on_done: Callable[[Any], None],
|
|
122
|
+
*labels: ttk.Label,
|
|
123
|
+
) -> None:
|
|
124
|
+
def run() -> None:
|
|
125
|
+
try:
|
|
126
|
+
result = task()
|
|
127
|
+
except Exception as exc: # surfaced to the UI
|
|
128
|
+
result = exc
|
|
129
|
+
self._messages.put((on_done, result))
|
|
130
|
+
|
|
131
|
+
self._set_busy(True)
|
|
132
|
+
for label in labels:
|
|
133
|
+
label.configure(text="working…", foreground=ACCENT)
|
|
134
|
+
threading.Thread(target=run, daemon=True).start()
|
|
135
|
+
|
|
136
|
+
def _poll(self) -> None:
|
|
137
|
+
try:
|
|
138
|
+
while True:
|
|
139
|
+
callback, payload = self._messages.get_nowait()
|
|
140
|
+
callback(payload)
|
|
141
|
+
except queue.Empty:
|
|
142
|
+
pass
|
|
143
|
+
self.root.after(100, self._poll)
|
|
144
|
+
|
|
145
|
+
# ------------------------------------------------------------------ build
|
|
146
|
+
def _build(self) -> None:
|
|
147
|
+
root_frame = ttk.Frame(self.root, padding=(12, 10, 12, 8))
|
|
148
|
+
root_frame.pack(fill=tk.BOTH, expand=True)
|
|
149
|
+
|
|
150
|
+
self.status = ttk.Label(root_frame, text="ready", style="Muted.TLabel")
|
|
151
|
+
self.status.pack(side=tk.BOTTOM, anchor=tk.W, pady=(8, 0))
|
|
152
|
+
|
|
153
|
+
notebook = ttk.Notebook(root_frame)
|
|
154
|
+
notebook.pack(fill=tk.BOTH, expand=True)
|
|
155
|
+
|
|
156
|
+
discover = ttk.Frame(notebook, padding=10)
|
|
157
|
+
patterns = ttk.Frame(notebook, padding=10)
|
|
158
|
+
engine = ttk.Frame(notebook, padding=10)
|
|
159
|
+
notebook.add(discover, text=" Discover ")
|
|
160
|
+
notebook.add(patterns, text=" Pattern discovery ")
|
|
161
|
+
notebook.add(engine, text=" Engine ")
|
|
162
|
+
|
|
163
|
+
self._build_discover(discover)
|
|
164
|
+
self._build_patterns(patterns)
|
|
165
|
+
self._build_engine(engine)
|
|
166
|
+
self.refresh_report()
|
|
167
|
+
|
|
168
|
+
def _build_discover(self, parent: ttk.Frame) -> None:
|
|
169
|
+
top = ttk.Frame(parent)
|
|
170
|
+
top.pack(fill=tk.X, pady=(0, 8))
|
|
171
|
+
ttk.Label(top, text="Local algorithm synthesizer", font=("", 16, "bold")).pack(side=tk.LEFT)
|
|
172
|
+
self.summary_label = ttk.Label(top, text="", style="Muted.TLabel")
|
|
173
|
+
self.summary_label.pack(side=tk.RIGHT)
|
|
174
|
+
|
|
175
|
+
self._tree = ttk.Treeview(
|
|
176
|
+
parent, show="headings",
|
|
177
|
+
columns=("target", "kind", "status", "novelty", "ms", "strategy"),
|
|
178
|
+
)
|
|
179
|
+
tree_cols: list[tuple[str, str, int]] = [
|
|
180
|
+
("target", "Target", 190),
|
|
181
|
+
("kind", "Kind", 120),
|
|
182
|
+
("status", "Status", 110),
|
|
183
|
+
("novelty", "Novelty", 130),
|
|
184
|
+
("ms", "Search (ms)", 90),
|
|
185
|
+
("strategy", "Strategy", 120),
|
|
186
|
+
]
|
|
187
|
+
for col, title, width in tree_cols:
|
|
188
|
+
self._tree.heading(col, text=title)
|
|
189
|
+
anchor: Literal["w", "center"] = (
|
|
190
|
+
"w" if col in ("target", "kind", "strategy") else "center"
|
|
191
|
+
)
|
|
192
|
+
self._tree.column(col, width=width, anchor=anchor)
|
|
193
|
+
self._tree.pack(fill=tk.BOTH, expand=True)
|
|
194
|
+
|
|
195
|
+
controls = ttk.Frame(parent)
|
|
196
|
+
controls.pack(fill=tk.X, pady=(8, 0))
|
|
197
|
+
self._button(controls, "Run smoke pass", self.run_smoke, accent=True).pack(side=tk.LEFT)
|
|
198
|
+
self._button(controls, "Run full pass", self.run_full).pack(side=tk.LEFT, padx=6)
|
|
199
|
+
self._button(controls, "Refresh", self.refresh_report).pack(side=tk.LEFT)
|
|
200
|
+
self._button(controls, "Open report (markdown)", self.open_report).pack(
|
|
201
|
+
side=tk.LEFT, padx=6
|
|
202
|
+
)
|
|
203
|
+
self._button(controls, "Open solutions folder", self.open_solutions).pack(side=tk.LEFT)
|
|
204
|
+
|
|
205
|
+
def _build_patterns(self, parent: ttk.Frame) -> None:
|
|
206
|
+
row = ttk.Frame(parent)
|
|
207
|
+
row.pack(fill=tk.X, pady=(0, 8))
|
|
208
|
+
ttk.Label(row, text="Integer sequence:").pack(side=tk.LEFT)
|
|
209
|
+
self.sequence_var = tk.StringVar(value="1 4 9 16 25")
|
|
210
|
+
entry = ttk.Entry(row, textvariable=self.sequence_var)
|
|
211
|
+
entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=8)
|
|
212
|
+
entry.bind("<Return>", lambda _event: self.run_patterns())
|
|
213
|
+
self._button(row, "Discover", self.run_patterns, accent=True).pack(side=tk.LEFT)
|
|
214
|
+
self.pattern_status = ttk.Label(row, text="", style="Muted.TLabel")
|
|
215
|
+
self.pattern_status.pack(side=tk.LEFT, padx=8)
|
|
216
|
+
|
|
217
|
+
self._ptree = ttk.Treeview(
|
|
218
|
+
parent, show="headings", columns=("name", "confidence", "prediction", "detail"),
|
|
219
|
+
)
|
|
220
|
+
pat_cols: list[tuple[str, str, int, Literal["w", "center"]]] = [
|
|
221
|
+
("name", "Hypothesis", 200, "w"),
|
|
222
|
+
("confidence", "Confidence", 130, "center"),
|
|
223
|
+
("prediction", "Next term", 120, "center"),
|
|
224
|
+
("detail", "Detail", 400, "w"),
|
|
225
|
+
]
|
|
226
|
+
for col, title, width, anchor in pat_cols:
|
|
227
|
+
self._ptree.heading(col, text=title)
|
|
228
|
+
self._ptree.column(col, width=width, anchor=anchor)
|
|
229
|
+
self._ptree.pack(fill=tk.BOTH, expand=True)
|
|
230
|
+
|
|
231
|
+
def _build_engine(self, parent: ttk.Frame) -> None:
|
|
232
|
+
self.engine_summary = ttk.Label(
|
|
233
|
+
parent, text="", font=("", 12), style="Muted.TLabel", justify=tk.LEFT,
|
|
234
|
+
)
|
|
235
|
+
self.engine_summary.pack(anchor=tk.W, pady=(0, 10))
|
|
236
|
+
|
|
237
|
+
controls = ttk.Frame(parent)
|
|
238
|
+
controls.pack(fill=tk.X)
|
|
239
|
+
self._button(controls, "Check vectors", self.run_check, accent=True).pack(side=tk.LEFT)
|
|
240
|
+
self._button(controls, "Run pytest", self.run_pytest).pack(side=tk.LEFT, padx=6)
|
|
241
|
+
self._button(controls, "Run build", self.run_build).pack(side=tk.LEFT)
|
|
242
|
+
self._button(controls, "Benchmark", self.run_bench).pack(side=tk.LEFT, padx=6)
|
|
243
|
+
self._button(controls, "Open docs", self.open_docs).pack(side=tk.LEFT)
|
|
244
|
+
self._button(controls, "Open GitHub", self.open_github).pack(side=tk.LEFT, padx=6)
|
|
245
|
+
|
|
246
|
+
self.output = tk.Text(
|
|
247
|
+
parent, bg=PANEL, fg=TEXT, insertbackground=TEXT, relief=tk.FLAT,
|
|
248
|
+
wrap=tk.NONE, font=("DejaVu Sans Mono", 10), state=tk.DISABLED,
|
|
249
|
+
highlightthickness=1, highlightbackground=BORDER,
|
|
250
|
+
)
|
|
251
|
+
self.output.pack(fill=tk.BOTH, expand=True, pady=(10, 0))
|
|
252
|
+
|
|
253
|
+
# ----------------------------------------------------------- discover tab
|
|
254
|
+
def _render_report(self, summary: dict[str, Any]) -> None:
|
|
255
|
+
for item in self._tree.get_children():
|
|
256
|
+
self._tree.delete(item)
|
|
257
|
+
for entry in summary.get("targets", []):
|
|
258
|
+
status = entry.get("status", "?")
|
|
259
|
+
self._tree.insert(
|
|
260
|
+
"", tk.END,
|
|
261
|
+
values=(
|
|
262
|
+
entry.get("id", ""),
|
|
263
|
+
entry.get("kind", ""),
|
|
264
|
+
status,
|
|
265
|
+
entry.get("novelty", ""),
|
|
266
|
+
entry.get("search_ms", ""),
|
|
267
|
+
entry.get("strategy", "") or entry.get("time_class", ""),
|
|
268
|
+
),
|
|
269
|
+
tags=(status,),
|
|
270
|
+
)
|
|
271
|
+
self._tree.tag_configure("verified", foreground=GREEN)
|
|
272
|
+
self._tree.tag_configure("candidate-rejected", foreground=RED)
|
|
273
|
+
self._tree.tag_configure("no-candidate-found", foreground=MUTED)
|
|
274
|
+
if summary.get("exists"):
|
|
275
|
+
self.summary_label.configure(
|
|
276
|
+
text=f"verified {summary['verified']} · rejected {summary['rejected']} · "
|
|
277
|
+
f"missing {summary['missing']}"
|
|
278
|
+
)
|
|
279
|
+
else:
|
|
280
|
+
self.summary_label.configure(text="no report yet", foreground=MUTED)
|
|
281
|
+
|
|
282
|
+
def refresh_report(self) -> None:
|
|
283
|
+
self._render_report(core.synth_summary(load_report()))
|
|
284
|
+
|
|
285
|
+
def run_smoke(self) -> None:
|
|
286
|
+
self._submit(
|
|
287
|
+
lambda: core.run_synth(smoke=True),
|
|
288
|
+
self._synth_done,
|
|
289
|
+
self.summary_label,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
def run_full(self) -> None:
|
|
293
|
+
self._submit(
|
|
294
|
+
lambda: core.run_synth(smoke=False),
|
|
295
|
+
self._synth_done,
|
|
296
|
+
self.summary_label,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
def _synth_done(self, payload: Any) -> None:
|
|
300
|
+
self._set_busy(False)
|
|
301
|
+
if isinstance(payload, Exception):
|
|
302
|
+
self.summary_label.configure(text=str(payload), foreground=RED)
|
|
303
|
+
self._render_report(core.synth_summary())
|
|
304
|
+
else:
|
|
305
|
+
self._render_report(payload)
|
|
306
|
+
self.summary_label.configure(text="done", foreground=GREEN)
|
|
307
|
+
self.refresh_report()
|
|
308
|
+
|
|
309
|
+
def open_report(self) -> None:
|
|
310
|
+
if core.REPORT_MD.exists():
|
|
311
|
+
webbrowser.open(core.REPORT_MD.resolve().as_uri())
|
|
312
|
+
|
|
313
|
+
def open_solutions(self) -> None:
|
|
314
|
+
core.SOLUTIONS_DIR.mkdir(parents=True, exist_ok=True)
|
|
315
|
+
webbrowser.open(core.SOLUTIONS_DIR.resolve().as_uri())
|
|
316
|
+
|
|
317
|
+
# -------------------------------------------------------- patterns tab
|
|
318
|
+
def run_patterns(self) -> None:
|
|
319
|
+
self._submit(
|
|
320
|
+
lambda: core.run_patterns(self.sequence_var.get()),
|
|
321
|
+
self._patterns_done,
|
|
322
|
+
self.pattern_status,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
def _patterns_done(self, payload: Any) -> None:
|
|
326
|
+
self._set_busy(False)
|
|
327
|
+
if isinstance(payload, Exception):
|
|
328
|
+
self.pattern_status.configure(text=str(payload), foreground=RED)
|
|
329
|
+
return
|
|
330
|
+
for item in self._ptree.get_children():
|
|
331
|
+
self._ptree.delete(item)
|
|
332
|
+
for row in payload:
|
|
333
|
+
self._ptree.insert(
|
|
334
|
+
"", tk.END,
|
|
335
|
+
values=(row["name"], f"{row['confidence']:.2f}", row["prediction"], row["detail"]),
|
|
336
|
+
)
|
|
337
|
+
self.pattern_status.configure(text=f"{len(payload)} hypotheses", foreground=GREEN)
|
|
338
|
+
|
|
339
|
+
# ----------------------------------------------------------- engine tab
|
|
340
|
+
def _run_cmd(self, argv: list[str]) -> dict[str, Any]:
|
|
341
|
+
result = subprocess.run(
|
|
342
|
+
argv, cwd=core.ROOT, capture_output=True, text=True, timeout=900, check=False
|
|
343
|
+
)
|
|
344
|
+
output = "".join((result.stdout or "")[-6000:]) + "\n" + "".join(
|
|
345
|
+
(result.stderr or "")[-6000:]
|
|
346
|
+
)
|
|
347
|
+
return {"ok": result.returncode == 0, "output": output.strip() or "(no output)"}
|
|
348
|
+
|
|
349
|
+
def run_check(self) -> None:
|
|
350
|
+
self._submit(
|
|
351
|
+
lambda: self._run_cmd([sys.executable, "engine/gen_tests.py", "--check"]),
|
|
352
|
+
self._engine_done,
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
def run_pytest(self) -> None:
|
|
356
|
+
self._submit(
|
|
357
|
+
lambda: self._run_cmd([sys.executable, "-m", "pytest", "-q"]),
|
|
358
|
+
self._engine_done,
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
def run_build(self) -> None:
|
|
362
|
+
self._submit(
|
|
363
|
+
lambda: self._run_cmd([sys.executable, "engine/runner.py", "build"]),
|
|
364
|
+
self._engine_done,
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
def run_bench(self) -> None:
|
|
368
|
+
self._submit(
|
|
369
|
+
lambda: self._run_cmd([sys.executable, "engine/runner.py", "bench"]),
|
|
370
|
+
self._engine_done,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
def _engine_done(self, payload: Any) -> None:
|
|
374
|
+
self._set_busy(False)
|
|
375
|
+
self.output.configure(state=tk.NORMAL)
|
|
376
|
+
self.output.delete("1.0", tk.END)
|
|
377
|
+
if isinstance(payload, Exception):
|
|
378
|
+
self.output.insert(tk.END, str(payload))
|
|
379
|
+
self.output.configure(state=tk.DISABLED)
|
|
380
|
+
return
|
|
381
|
+
body = payload["output"]
|
|
382
|
+
self.output.insert(tk.END, body)
|
|
383
|
+
self.output.configure(state=tk.DISABLED)
|
|
384
|
+
self.status.configure(
|
|
385
|
+
text="OK" if payload["ok"] else "FAILED",
|
|
386
|
+
foreground=GREEN if payload["ok"] else RED,
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
def open_docs(self) -> None:
|
|
390
|
+
webbrowser.open("https://dsk-dev-ai.github.io/algorithm-discovery-engine/")
|
|
391
|
+
|
|
392
|
+
def open_github(self) -> None:
|
|
393
|
+
webbrowser.open("https://github.com/dsk-dev-ai/algorithm-discovery-engine")
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def load_report() -> Report | None:
|
|
397
|
+
return core.load_report()
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def main() -> None:
|
|
401
|
+
root = tk.Tk()
|
|
402
|
+
App(root)
|
|
403
|
+
root.mainloop()
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
if __name__ == "__main__":
|
|
407
|
+
main()
|
gui/core.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Headless logic powering the desktop GUI.
|
|
2
|
+
|
|
3
|
+
Everything here avoids ``tkinter`` so it can run (and be tested) without a
|
|
4
|
+
display. The Tk layer in :mod:`gui.app` only renders what these helpers return.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import re
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, cast
|
|
13
|
+
|
|
14
|
+
from algo_discovery import DiscoveryEngine
|
|
15
|
+
from synth import discovery as synth_discovery
|
|
16
|
+
|
|
17
|
+
ROOT: Path = Path(__file__).resolve().parent.parent.parent
|
|
18
|
+
CATALOG_PATH: Path = ROOT / "catalog" / "problems.json"
|
|
19
|
+
TARGETS_PATH: Path = ROOT / "catalog" / "discovery_targets.json"
|
|
20
|
+
DISCOVERIES_DIR: Path = ROOT / "catalog" / "discoveries"
|
|
21
|
+
REPORT_JSON: Path = DISCOVERIES_DIR / "report.json"
|
|
22
|
+
REPORT_MD: Path = DISCOVERIES_DIR / "report.md"
|
|
23
|
+
SOLUTIONS_DIR: Path = DISCOVERIES_DIR / "solutions"
|
|
24
|
+
|
|
25
|
+
_INT_RE = re.compile(r"-?\d+")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _load_json(path: Path) -> dict[str, Any]:
|
|
29
|
+
return cast(dict[str, Any], json.loads(path.read_text(encoding="utf-8")))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def version() -> str:
|
|
33
|
+
"""Read the package version from the project metadata."""
|
|
34
|
+
try:
|
|
35
|
+
from importlib import metadata
|
|
36
|
+
|
|
37
|
+
return metadata.version("algorithm-discovery-engine")
|
|
38
|
+
except Exception: # pragma: no cover - source tree fallback
|
|
39
|
+
root_toml = ROOT / "pyproject.toml"
|
|
40
|
+
if root_toml.exists():
|
|
41
|
+
for line in root_toml.read_text(encoding="utf-8").splitlines():
|
|
42
|
+
line = line.strip()
|
|
43
|
+
if line.startswith("version"):
|
|
44
|
+
return line.split("=")[1].strip().strip('"')
|
|
45
|
+
return "0.0.0"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def catalog_summary() -> dict[str, Any]:
|
|
49
|
+
"""Summary of the shared multi-language catalog."""
|
|
50
|
+
data = _load_json(CATALOG_PATH)
|
|
51
|
+
return {
|
|
52
|
+
"version": data.get("version", "?"),
|
|
53
|
+
"algorithms": list(data.get("algorithms", [])),
|
|
54
|
+
"data_structures": list(data.get("data_structures", [])),
|
|
55
|
+
"solved_problems": len(data.get("algorithms", [])),
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def parse_sequence(text: str) -> list[int]:
|
|
60
|
+
"""Parse a spaced/comma separated integer sequence for pattern discovery."""
|
|
61
|
+
terms = [int(m) for m in _INT_RE.findall(text)]
|
|
62
|
+
if not terms:
|
|
63
|
+
raise ValueError("Enter at least one integer (e.g. 1, 4, 9, 16).")
|
|
64
|
+
return terms
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def run_patterns(text: str, top: int = 8) -> list[dict[str, Any]]:
|
|
68
|
+
"""Discover ranked hypotheses for a user-supplied integer sequence."""
|
|
69
|
+
terms = parse_sequence(text)
|
|
70
|
+
result = DiscoveryEngine().discover(terms)
|
|
71
|
+
ranked = result.ranked[:top]
|
|
72
|
+
return [
|
|
73
|
+
{
|
|
74
|
+
"name": score.name,
|
|
75
|
+
"confidence": score.confidence,
|
|
76
|
+
"prediction": score.prediction,
|
|
77
|
+
"detail": score.detail,
|
|
78
|
+
}
|
|
79
|
+
for score in ranked
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def run_synth(
|
|
84
|
+
smoke: bool = True, fuzz_count: int | None = None, scan_budget: int | None = None
|
|
85
|
+
) -> dict[str, Any]:
|
|
86
|
+
"""Run the local algorithm synthesizer and return a summarized report.
|
|
87
|
+
|
|
88
|
+
Raises ``RuntimeError`` if any target is rejected or missing, mirroring the
|
|
89
|
+
CLI's exit-code contract.
|
|
90
|
+
"""
|
|
91
|
+
report = synth_discovery.discover(
|
|
92
|
+
smoke=smoke, scan_budget=scan_budget, fuzz_count=fuzz_count
|
|
93
|
+
)
|
|
94
|
+
summary = synth_summary(report)
|
|
95
|
+
if summary["rejected"] or summary["missing"]:
|
|
96
|
+
raise RuntimeError(
|
|
97
|
+
f"Discovery incomplete: rejected={summary['rejected']} missing={summary['missing']}"
|
|
98
|
+
)
|
|
99
|
+
return summary
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def synth_summary(report: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
103
|
+
"""Summarize a report dict (or the latest on disk)."""
|
|
104
|
+
if report is None:
|
|
105
|
+
report = load_report()
|
|
106
|
+
if report is None:
|
|
107
|
+
return {
|
|
108
|
+
"verified": 0,
|
|
109
|
+
"rejected": 0,
|
|
110
|
+
"missing": 0,
|
|
111
|
+
"targets": [],
|
|
112
|
+
"exists": False,
|
|
113
|
+
}
|
|
114
|
+
entries = report.get("targets", [])
|
|
115
|
+
verified = [e for e in entries if e.get("status") == "verified"]
|
|
116
|
+
return {
|
|
117
|
+
"verified": len(verified),
|
|
118
|
+
"rejected": sum(1 for e in entries if e.get("status") == "candidate-rejected"),
|
|
119
|
+
"missing": sum(
|
|
120
|
+
1 for e in entries if e.get("status") == "no-candidate-found"
|
|
121
|
+
),
|
|
122
|
+
"targets": entries,
|
|
123
|
+
"exists": True,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def load_report() -> dict[str, Any] | None:
|
|
128
|
+
"""Load the latest discovery report from disk (``None`` if absent)."""
|
|
129
|
+
if not REPORT_JSON.exists():
|
|
130
|
+
return None
|
|
131
|
+
return _load_json(REPORT_JSON)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def discovery_targets() -> list[dict[str, Any]]:
|
|
135
|
+
"""The configured discovery targets for display."""
|
|
136
|
+
data = _load_json(TARGETS_PATH)
|
|
137
|
+
return [
|
|
138
|
+
{"id": item["id"], "kind": item.get("kind", "?"), "signature": item.get("signature", "")}
|
|
139
|
+
for item in data["targets"]
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def engine_catalog_counts() -> dict[str, int]:
|
|
144
|
+
"""Simple numeric summary for the engine tab."""
|
|
145
|
+
data = _load_json(CATALOG_PATH)
|
|
146
|
+
return {
|
|
147
|
+
"algorithms": len(data.get("algorithms", [])),
|
|
148
|
+
"data_structures": len(data.get("data_structures", [])),
|
|
149
|
+
}
|
synth/__init__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Algorithm synthesis & discovery (local).
|
|
2
|
+
|
|
3
|
+
The :mod:`synth` package searches for candidate algorithms from input/output
|
|
4
|
+
examples:
|
|
5
|
+
|
|
6
|
+
* :mod:`synth.grammar` — expression tree language + Python rendering.
|
|
7
|
+
* :mod:`synth.search` — grammar-based scanner enumeration and strategy
|
|
8
|
+
templates (vote / seen / fib / circular-Kadane).
|
|
9
|
+
* :mod:`synth.corpus` — discovery targets, reference oracles, fuzz inputs.
|
|
10
|
+
* :mod:`synth.discovery` — orchestration, verification, novelty, reporting.
|
|
11
|
+
|
|
12
|
+
CLI:
|
|
13
|
+
|
|
14
|
+
python -m synth discover # full pass over every target
|
|
15
|
+
python -m synth discover --smoke # reduced budget (CI-friendly)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from synth.discovery import discover
|
|
19
|
+
|
|
20
|
+
__version__ = "1.0.0"
|
|
21
|
+
|
|
22
|
+
__all__ = ["discover"]
|
synth/__main__.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""CLI: ``python -m synth discover [--smoke] [--scan-budget N] [--fuzz N]``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main() -> int:
|
|
11
|
+
parser = argparse.ArgumentParser(
|
|
12
|
+
prog="synth", description="Local algorithm discovery"
|
|
13
|
+
)
|
|
14
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
15
|
+
|
|
16
|
+
discover_parser = sub.add_parser("discover", help="run the discovery pass")
|
|
17
|
+
discover_parser.add_argument(
|
|
18
|
+
"--smoke", action="store_true", help="reduced budget (CI-friendly)"
|
|
19
|
+
)
|
|
20
|
+
discover_parser.add_argument(
|
|
21
|
+
"--scan-budget",
|
|
22
|
+
type=int,
|
|
23
|
+
default=None,
|
|
24
|
+
help="grammar search candidate cap",
|
|
25
|
+
)
|
|
26
|
+
discover_parser.add_argument(
|
|
27
|
+
"--fuzz", type=int, default=None, help="fuzz cases per target"
|
|
28
|
+
)
|
|
29
|
+
discover_parser.add_argument(
|
|
30
|
+
"--print", action="store_true", help="print report JSON to stdout"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
args = parser.parse_args()
|
|
34
|
+
|
|
35
|
+
if args.command == "discover":
|
|
36
|
+
from synth.discovery import DISCOVERIES_DIR, discover
|
|
37
|
+
|
|
38
|
+
report = discover(
|
|
39
|
+
smoke=args.smoke,
|
|
40
|
+
scan_budget=args.scan_budget,
|
|
41
|
+
fuzz_count=args.fuzz,
|
|
42
|
+
)
|
|
43
|
+
verified = [
|
|
44
|
+
e for e in report["targets"] if e["status"] == "verified"
|
|
45
|
+
]
|
|
46
|
+
rejected = [
|
|
47
|
+
e for e in report["targets"] if e["status"] == "candidate-rejected"
|
|
48
|
+
]
|
|
49
|
+
missing = [
|
|
50
|
+
e for e in report["targets"] if e["status"] == "no-candidate-found"
|
|
51
|
+
]
|
|
52
|
+
print(
|
|
53
|
+
f"targets={len(report['targets'])} verified={len(verified)} "
|
|
54
|
+
f"rejected={len(rejected)} missing={len(missing)}"
|
|
55
|
+
)
|
|
56
|
+
print(f"report: {DISCOVERIES_DIR / 'report.md'}")
|
|
57
|
+
if args.print:
|
|
58
|
+
print(json.dumps(report, indent=2))
|
|
59
|
+
return 0 if not missing and not rejected else 1
|
|
60
|
+
|
|
61
|
+
parser.print_help()
|
|
62
|
+
return 1
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
sys.exit(main())
|