Skip to content

Commit 4778137

Browse files
committed
embedding - color cycles orange
1 parent dcea4bb commit 4778137

File tree

8 files changed

+172
-129
lines changed

8 files changed

+172
-129
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
node_modules/
22
build/
3+
.vscode/

.vscode/settings.json

Lines changed: 0 additions & 5 deletions
This file was deleted.

assembly/asconfig.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@
1414
"debug": {
1515
"outFile": "build/debug.wasm",
1616
"textFile": "build/debug.wat"
17+
},
18+
"release": {
19+
"outFile": "build/release.wasm",
20+
"textFile": "build/release.wat",
21+
"options": {
22+
"optimizeLevel": 3,
23+
"shrinkLevel": 2,
24+
"converge": true,
25+
"noAssert": true
26+
}
1727
}
1828
}
1929
}

lib/evaluator/arenaEvaluator.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { SKIExpression } from "../ski/expression.ts";
55
import { I, K, S, SKITerminalSymbol } from "../ski/terminal.ts";
66
import type { Evaluator } from "./evaluator.ts";
77
import { ArenaKind, type ArenaNodeId, ArenaSym } from "../shared/arena.ts";
8+
import type { ArenaNode } from "../../scripts/types.ts";
89

910
interface ArenaWasmExports {
1011
memory: WebAssembly.Memory;
@@ -190,3 +191,52 @@ export class ArenaEvaluatorWasm implements Evaluator {
190191
export async function initArenaEvaluator(wasmPath: string) {
191192
return await ArenaEvaluatorWasm.instantiate(wasmPath);
192193
}
194+
195+
// Homeomorphic embedding: a ⊑ b
196+
// Returns true if a embeds into b
197+
function embedsRec(
198+
nodes: ArenaNode[],
199+
a: number,
200+
b: number,
201+
visited: Set<string>
202+
): boolean {
203+
const key = `${a},${b}`;
204+
if (visited.has(key)) return false;
205+
visited.add(key);
206+
207+
const nodeA = nodes.find(n => n.id === a);
208+
const nodeB = nodes.find(n => n.id === b);
209+
210+
if (!nodeA || !nodeB) return false;
211+
212+
// If a is terminal, b must be the same terminal
213+
if (nodeA.kind === "terminal") {
214+
return nodeB.kind === "terminal" && nodeA.sym === nodeB.sym;
215+
}
216+
217+
// If a is non-terminal (APP), b must also be non-terminal
218+
if (nodeB.kind === "terminal") return false;
219+
220+
// For APP nodes, check embedding recursively
221+
return (
222+
embedsRec(nodes, nodeA.left!, nodeB.left!, visited) &&
223+
embedsRec(nodes, nodeA.right!, nodeB.right!, visited)
224+
);
225+
}
226+
227+
export function embeds(nodes: ArenaNode[], a: number, b: number): boolean {
228+
return embedsRec(nodes, a, b, new Set());
229+
}
230+
231+
export function hasEmbedding(
232+
nodes: ArenaNode[],
233+
history: number[],
234+
currentId: number
235+
): boolean {
236+
for (const prevId of history) {
237+
if (embeds(nodes, prevId, currentId)) {
238+
return true;
239+
}
240+
}
241+
return false;
242+
}

scripts/generateForest.ts renamed to scripts/genForest.ts

Lines changed: 22 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
import { cons } from "../lib/cons.ts";
22
import { I, K, S } from "../lib/ski/terminal.ts";
33
import type { SKIExpression } from "../lib/ski/expression.ts";
4-
import { initArenaEvaluator } from "../lib/evaluator/arenaEvaluator.ts";
5-
import { prettyPrint } from "../lib/ski/expression.ts";
4+
import { initArenaEvaluator, hasEmbedding } from "../lib/evaluator/arenaEvaluator.ts";
65
import type { EvaluationStep, GlobalInfo } from "./types.ts";
76

87
const memo = new Map<number, SKIExpression[]>();
98
const [nRaw, outputPath] = Deno.args;
109

1110
if (!nRaw) {
1211
console.error(
13-
"Usage: deno run -A scripts/generateforest.ts <symbolCount> [outputFile]",
12+
"Usage: deno run -A scripts/genForest.ts <symbolCount> [outputFile]",
1413
);
1514
console.error("");
1615
console.error(
@@ -62,21 +61,6 @@ function enumerateExpressions(leaves: number): SKIExpression[] {
6261
return result;
6362
}
6463

65-
async function generateLabel(expr: SKIExpression): Promise<string> {
66-
let label = prettyPrint(expr);
67-
if (label.length > 100) {
68-
const encoder = new TextEncoder();
69-
const data = encoder.encode(label);
70-
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
71-
const hashArray = Array.from(new Uint8Array(hashBuffer));
72-
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join(
73-
"",
74-
);
75-
label = `HASH:${hashHex.substring(0, 16)}`;
76-
}
77-
return label;
78-
}
79-
8064
export async function* generateEvaluationForest(
8165
symbolCount: number,
8266
wasmPath: string,
@@ -90,7 +74,6 @@ export async function* generateEvaluationForest(
9074
console.error(
9175
`Processing ${total} expressions with ${symbolCount} symbols each...`,
9276
);
93-
const labels: Record<number, string> = {};
9477
const sources = new Set<number>();
9578
const sinks = new Set<number>();
9679

@@ -105,79 +88,53 @@ export async function* generateEvaluationForest(
10588
);
10689
}
10790

108-
let cur = expr;
109-
let steps = 0;
110-
const MAX_STEPS = 10000;
111-
const curId = evaluator.toArena(cur);
112-
const label = await generateLabel(expr);
113-
114-
labels[curId] = label;
91+
const curId = evaluator.toArena(expr);
11592
sources.add(curId);
11693

117-
const encounteredNodes = new Set<number>();
118-
encounteredNodes.add(curId);
94+
// Track evaluation history for cycle detection
95+
const history: number[] = [curId];
11996
const pathSteps: EvaluationStep[] = [];
97+
let hasCycle = false;
98+
let currentId = curId;
12099

121-
while (steps < MAX_STEPS) {
122-
steps++;
123-
const { altered, expr: next } = evaluator.stepOnce(cur);
124-
if (!altered) break;
100+
// Manual evaluation with cycle detection
101+
for (let step = 0;; step++) {
102+
const { altered, expr: nextExpr } = evaluator.stepOnce(evaluator.fromArena(currentId));
125103

126-
const currentId = evaluator.toArena(cur);
127-
const nextId = evaluator.toArena(next);
128-
129-
if (!labels[currentId]) {
130-
labels[currentId] = await generateLabel(cur);
131-
}
132-
if (!labels[nextId]) {
133-
labels[nextId] = await generateLabel(next);
104+
if (!altered) {
105+
break; // No more reduction possible
134106
}
135107

136-
if (encounteredNodes.has(nextId)) {
137-
pathSteps.push({ from: currentId, to: nextId });
138-
pathSteps.push({ from: nextId, to: nextId });
108+
const nextId = evaluator.toArena(nextExpr);
109+
110+
if (hasEmbedding(evaluator.dumpArena().nodes, history, nextId)) {
111+
hasCycle = true;
139112
break;
140113
}
141114

142115
pathSteps.push({ from: currentId, to: nextId });
143-
encounteredNodes.add(nextId);
144-
cur = next;
116+
currentId = nextId;
117+
history.push(currentId);
145118
}
146119

147-
if (steps === MAX_STEPS) {
148-
console.error(
149-
`Warning: reduction for term #${count} hit step limit (${MAX_STEPS}): ${
150-
prettyPrint(expr)
151-
}`,
152-
);
153-
}
154-
155-
const finalId = evaluator.toArena(cur);
156-
labels[finalId] = await generateLabel(cur);
120+
const finalId = currentId;
157121
sinks.add(finalId);
158122

159123
yield JSON.stringify({
160124
source: curId,
161125
sink: finalId,
162126
steps: pathSteps,
127+
hasCycle,
163128
});
164129
}
165130

166131
const { nodes } = evaluator.dumpArena();
167132

168133
console.error(`Arena contains ${nodes.length} nodes`);
169134

170-
for (const node of nodes) {
171-
if (!labels[node.id]) {
172-
const expr = evaluator.fromArena(node.id);
173-
labels[node.id] = await generateLabel(expr);
174-
}
175-
}
176-
177135
const globalInfo: GlobalInfo = {
178136
type: "global",
179137
nodes,
180-
labels,
181138
sources: Array.from(sources),
182139
sinks: Array.from(sinks),
183140
};
@@ -186,7 +143,7 @@ export async function* generateEvaluationForest(
186143
}
187144

188145
async function streamToFile(symbolCount: number, outputPath: string) {
189-
const wasmPath = "assembly/build/debug.wasm";
146+
const wasmPath = "assembly/build/release.wasm";
190147
const file = await Deno.open(outputPath, {
191148
write: true,
192149
create: true,
@@ -205,7 +162,7 @@ async function streamToFile(symbolCount: number, outputPath: string) {
205162
}
206163

207164
async function streamToStdout(symbolCount: number) {
208-
const wasmPath = "assembly/build/debug.wasm";
165+
const wasmPath = "assembly/build/release.wasm";
209166

210167
for await (const data of generateEvaluationForest(symbolCount, wasmPath)) {
211168
console.log(data);

0 commit comments

Comments
 (0)