diff --git a/dist/demo/2024_07/equality_saturation.js b/dist/demo/2024_07/equality_saturation.js new file mode 100644 index 0000000..3918c74 --- /dev/null +++ b/dist/demo/2024_07/equality_saturation.js @@ -0,0 +1 @@ +"use strict"; diff --git a/dist/demo/2024_07/rewrite.js b/dist/demo/2024_07/rewrite.js index 54bbac9..04b1575 100644 --- a/dist/demo/2024_07/rewrite.js +++ b/dist/demo/2024_07/rewrite.js @@ -1,27 +1,135 @@ "use strict"; -const match = (pattern, structure) => { - if (structure[0] !== pattern[0]) - return false; - const a = Array.isArray(pattern[1]) - ? match(pattern[1], structure[1]) - : { [pattern[1]]: structure[1] }; - if (!a) - return false; - const b = Array.isArray(pattern[2]) - ? match(pattern[2], structure[2]) - : { [pattern[2]]: structure[2] }; - if (!b) - return false; - return { - ...a, - ...b, - }; -}; -const subst = (matches, [op, a, b]) => [ - op, - Array.isArray(a) ? subst(matches, a) : matches[a], - Array.isArray(b) ? subst(matches, b) : matches[b], +const variable = ({ name, pred, match }) => ({ + kind: "variable", + name, + pred, + match, + withPred: (newPred) => variable({ name, pred: newPred, match }), + withMatch: (newMatch) => variable({ name, pred, match: newMatch }), +}); +const v = ([name]) => variable({ name, pred: () => true, match: (matches) => matches[name] }); +const isNumber = (n) => typeof n === "number"; +const MY_RULES = [ + { + from: ["=", ["+", v `a`, v `b`], v `c`], + to: ["=", v `a`, ["-", v `c`, v `b`]], + }, + { + from: ["=", ["-", v `a`, v `b`], v `c`], + to: ["=", v `a`, ["+", v `c`, v `b`]], + }, + { + from: ["=", v `a`, v `b`], + to: ["=", v `b`, v `a`], + }, + { + from: ["+", v `a`, v `b`], + to: ["+", v `b`, v `a`], + }, + { + from: ["+", v `a`.withPred(isNumber), v `b`.withPred(isNumber)], + to: v `a + b`.withMatch(({ a, b }) => a + b), + }, + { + from: ["+", v `a`, v `a`], + to: ["*", 2, v `a`], + }, + { + from: ["*", v `a`, v `b`], + to: ["*", v `b`, v `a`], + }, + { + from: ["=", ["*", v `a`, v `b`], v `c`], + to: ["=", v `a`, ["/", v `c`, v `b`]], + }, + { + from: ["=", ["/", v `a`, v `b`], v `c`], + to: ["=", v `a`, ["*", v `c`, v `b`]], + }, ]; +const MATCH_FAIL = "MATCH_FAIL"; +const match = (pattern, structure, matchesSoFar = {}) => { + if (pattern.kind === "variable") { + if (!pattern.pred(structure, matchesSoFar)) + return MATCH_FAIL; + const prevMatch = pattern.match(matchesSoFar); + if (prevMatch && JSON.stringify(prevMatch) !== JSON.stringify(structure)) + return MATCH_FAIL; + return { [pattern.name]: structure }; + } + else if (Array.isArray(pattern)) { + if (!Array.isArray(structure)) + return MATCH_FAIL; + let matches = {}; + for (let i = 0; i < pattern.length; i++) { + const p = pattern[i]; + const s = structure[i]; + const m = match(p, s, matches); + if (m === MATCH_FAIL) + return MATCH_FAIL; + matches = { ...matches, ...m }; + } + return matches; + } + else { + if (pattern !== structure) + return MATCH_FAIL; + return {}; + } +}; +const getRecursiveMatches = (pattern, structure, res = new Map()) => { + res.set(structure, match(pattern, structure)); + if (Array.isArray(structure)) + for (const substructure of structure) + getRecursiveMatches(pattern, substructure, res); + return res; +}; +const SUBST_FAIL = "SUBST_FAIL"; +const subst = (matches, structure) => { + if (structure.kind === "variable") + return structure.match(matches) ?? SUBST_FAIL; + else if (Array.isArray(structure)) { + const ms = structure.map((s) => subst(matches, s)); + if (ms.some((m) => m === SUBST_FAIL)) + return SUBST_FAIL; + return ms; + } + else + return structure; +}; +const APPLY_FAIL = "APPLY_FAIL"; +const applyRule = ({ from, to, success, failure }, structure) => { + const m = match(from, structure); + if (m === MATCH_FAIL) { + if (failure) + failure(MATCH_FAIL); + return APPLY_FAIL; + } + const s = subst(m, to); + if (s === SUBST_FAIL) { + if (failure) + failure(SUBST_FAIL); + return APPLY_FAIL; + } + if (success) + success(); + return s; +}; +const tryAllRulesRecursively = (rules, structure) => { + return [ + ...(Array.isArray(structure) + ? structure.flatMap((s, i) => tryAllRulesRecursively(rules, s).map((res) => structure.with(i, res))) + : []), + ...rules + .map((r) => { + const res = applyRule(r, structure); + if (res === APPLY_FAIL) + return APPLY_FAIL; + return res; + }) + .filter((subRes) => subRes !== APPLY_FAIL), + ]; +}; const rewriteStep = (input, rules) => { const neue = [ ...(Array.isArray(input[1]) @@ -31,44 +139,83 @@ const rewriteStep = (input, rules) => { ? rewriteStep(input[2], rules).map((res) => [input[0], input[1], res]) : []), ]; - for (const { from, to } of rules) { - const m = match(from, input); - if (!m) + for (const rule of rules) { + const res = applyRule(rule, input); + if (res === APPLY_FAIL) continue; - neue.push(subst(m, to)); + neue.push(res); } return neue; }; -const rules = [ - { - from: ["=", ["+", "a", "b"], "c"], - to: ["=", "a", ["-", "c", "b"]], - }, - { - from: ["=", ["-", "a", "b"], "c"], - to: ["=", "a", ["+", "c", "b"]], - }, - { - from: ["=", "a", "b"], - to: ["=", "b", "a"], - }, +//const myInput = ["=", ["-", "w", 2], 3]; +const myInput1 = ["=", `w`, ["-", `r`, `l`]]; +const myInput2 = ["=", `c`, ["+", `l`, ["/", `w`, 2]]]; +// GOAL: +// input: l, r, w = r - l, c = l + w/2 +// output: +// - given: solution for l and r, given w and c. +const isVar = (x) => x === "w" || x === "r" || x === "l" || x === "c"; +const sToRule = (s) => { + const m = match(["=", v `lhs`.withPred(isVar), v `rhs`], s); + if (m === MATCH_FAIL) + return []; + return [ + { + m_name: m.lhs, + from: v `lhs`.withPred((x) => x === m.lhs), + to: m.rhs, + success: () => console.log("SUCCESSFULLY APPLIED", m.lhs), + failure: (fa) => console.log("FAIL APPLIED", fa, m.lhs), + }, + ]; +}; +console.log("tryAllRulesRecursively 1", tryAllRulesRecursively(MY_RULES, "a"), tryAllRulesRecursively(MY_RULES, ["a"])); +console.log("tryAllRulesRecursively 2", tryAllRulesRecursively(MY_RULES, ["+", "a", "b"])); +console.log("sToRule 1", sToRule(["=", v `x`, ["+", 1, 2]])); +console.log("sToRule 2", sToRule(["=", ["sin", v `x`], ["+", 1, 2]])); +console.log("sToRule 3", tryAllRulesRecursively([ { - from: ["+", "a", "b"], - to: ["+", "b", "a"], + from: v `lhs`.withPred((x) => x.kind === "variable" && x.name === "x"), + to: 22, }, - /* next: { - from: ["-", { name: "a", pred: isNum }, { name: "b", isNum }], - to: { args: ["a", "b"], calc: (a, b)=>a-b }, - },*/ -]; -const mySet = new Set(); -const see = (ob) => mySet.add(JSON.stringify(ob)); -const isSeen = (ob) => mySet.has(JSON.stringify(ob)); -const myInput = ["=", ["-", "w", 2], 3]; -const toTry = [myInput]; -while (toTry.length > 0) { - const i = toTry.pop(); - see(i); - toTry.push(...rewriteStep(i, rules).filter((res) => !isSeen(res))); +], ["sin", v `x`]), tryAllRulesRecursively(sToRule(["=", v `x`, ["+", 1, 2]]), [ + "+", + 33, + ["-", 1, v `x`], +])); +const mySet1 = new Set(); +const mySet2 = new Set(); +const see = (set, ob) => set.add(JSON.stringify(ob)); +const isSeen = (set, ob) => set.has(JSON.stringify(ob)); +const rules = [...MY_RULES]; +const toTry1 = [myInput1]; +const toTry2 = [myInput2]; +for (let i = 0; i < 12; i++) { + if (toTry1.length === 0 && toTry2.length === 0) + break; + if (toTry1.length > 0) { + const i = toTry1.pop(); + see(mySet1, i); + const newStructuresToTry = tryAllRulesRecursively(rules, i).filter((res) => !isSeen(mySet1, res)); + toTry1.push(...newStructuresToTry); + rules.push(...newStructuresToTry.flatMap(sToRule)); + } + if (toTry2.length > 0) { + const i = toTry2.pop(); + see(mySet2, i); + const newStructuresToTry = tryAllRulesRecursively(rules, i).filter((res) => !isSeen(mySet2, res)); + toTry2.push(...newStructuresToTry); + rules.push(...newStructuresToTry.flatMap(sToRule)); + } + console.log("hi", toTry1.length); } -console.log([...mySet.values()].map(JSON.parse)); +console.log("go go go", [...mySet1.values()].map(JSON.parse), [...mySet2.values()].map(JSON.parse), rules); +// console.log( +// "SELECT", +// [...mySet.values()] +// .map(JSON.parse) +// .filter( +// ([[_1, v1], [_2, v2]]) => v1.kind === "variable" && v2.kind === "variable" +// ) +// .map(([[_1, v1], [_2, v2]]) => [v1.name, v2.name]) +// ); diff --git a/dist/demo/2024_08/e_graph.html b/dist/demo/2024_08/e_graph.html new file mode 100644 index 0000000..d64bca7 --- /dev/null +++ b/dist/demo/2024_08/e_graph.html @@ -0,0 +1 @@ + diff --git a/dist/demo/2024_08/e_graph.js b/dist/demo/2024_08/e_graph.js new file mode 100644 index 0000000..496a503 --- /dev/null +++ b/dist/demo/2024_08/e_graph.js @@ -0,0 +1,85 @@ +// e init (tree) => e-graph +// e match (pattern, e-graph) => e-node[] +// e sub (result, e-node, e-graph) => e-graph + +const node = (value, ...children) => { + const res = { + isNode: true, + value, + children: children, + parent: null, + }; + for (const child of children) child.parent = res; + return res; +}; +const vari = (v, ...children) => { + const res = { + isNode: true, + var: v, + children: children, + parent: null, + }; + for (const child of children) child.parent = res; + return res; +}; +const nodeEq = (n1, n2) => { + if (n1.value !== n2.value) return false; + else if (n1.children.length !== n2.children.length) return false; + else return n1.children.every((v, i) => nodeEq(v, n2.children[i])); +}; + +const a = node("f", node(1), node(2)); +const b = node("f", node(1), node(2)); + +console.log("nodeEq!", nodeEq(a, b)); + +const eClassFromNode = (node, parentENode = null) => { + const eNode = { isENode: true, value: node.value }; + eNode.children = node.children.map((n) => eClassFromNode(n, eNode)); + return { + isEClass: true, + eNodes: [eNode], + parents: [parentENode], + }; +}; + +console.log("eClassFromNode!", eClassFromNode(a)); + +const eClassMatches = (patternNode, eClass) => { + return eClass.eNodes.flatMap((en) => eNodeMatches(patternNode, en)); +}; +const eNodeMatches = (patternNode, eNode) => { + if (patternNode.var === undefined && eNode.value !== patternNode.value) + return []; + else if (patternNode.children.length !== eNode.children.length) return []; + else { + const childrenMatches = eNode.children.map((ec, i) => + eClassMatches(patternNode.children[i], ec) + ); + return [ + ...gogo( + childrenMatches, + patternNode.var ? { [patternNode.var]: eNode.value } : {} + ), + ]; + } +}; + +const gogo = function* (childrenMatches, match) { + if (childrenMatches.length === 0) { + yield { ...match }; + return; + } + for (const matches1 of childrenMatches[0]) { + for (const matches2 of gogo(childrenMatches.slice(1))) { + yield { ...match, ...matches1, ...matches2 }; + } + } +}; + +console.log( + "eClassMatches!", + eClassMatches(vari("go", vari("1"), node(2)), eClassFromNode(a)) +); + +// Aside: the implicit lifting language could maybe really help simplify this. diff --git a/dist/demo/2024_08/egg_port.js b/dist/demo/2024_08/egg_port.js new file mode 100644 index 0000000..aa87728 --- /dev/null +++ b/dist/demo/2024_08/egg_port.js @@ -0,0 +1,69 @@ +import { makeSet, find, union } from "./union_find.js"; +// map canonical e-nodes to e-class ids +// i.e. e-node 𝑛 ∈ 𝑀 [𝑎] ⇐⇒ 𝐻 [canonicalize(𝑛)] = find(𝑎) +const hashcons = new Map(); +const parents = new Map(); +const worklist = new Set(); +const mk_enode = (op, children) => ({ + op, + children, + hash: op + children.map((c) => "c" + c.id).join(), +}); +// TODO: fix canonicalize to respect hashcons +const canonicalize = (eNode) => mk_enode(eNode.op, eNode.children.map(find)); +const lookup = (eNode) => hashcons.get(eNode.hash); +// eNode -> eClassId +const add = (eNode) => { + const en = canonicalize(eNode); + if (lookup(en)) + return lookup(en); + else { + const eClassId = makeSet(); + for (const child of eNode.children) + parents.set(child, parents.get(child)?.set(en, eClassId) ?? new Map([[en, eClassId]])); + hashcons.set(en.hash, eClassId); + return eClassId; + } +}; +const merge = (eClassId1, eClassId2) => { + if (find(eClassId1) === find(eClassId2)) + return find(eClassId1); + const newEClassId = union(eClassId1, eClassId2); + worklist.add(newEClassId); + return newEClassId; +}; +const rebuild = () => { + while (worklist.size > 0) { + const todo = [...worklist]; + worklist.clear(); + for (const eClassId of todo) + repair(find(eClassId)); + } +}; +const repair = (eClassId) => { + for (const [peNode, peClassId] of parents.get(eClassId) ?? []) { + hashcons.delete(peNode.hash); + const newPeNode = canonicalize(peNode); + hashcons.set(newPeNode.hash, peClassId); + } + const newParents = new Map(); + for (const [peNode, peClassId] of parents.get(eClassId) ?? []) { + const newPeNode = canonicalize(peNode); + if (newParents.has(newPeNode)) { + merge(peClassId, newParents.get(newPeNode)); + } + newParents.set(newPeNode, find(peClassId)); + } + parents.set(eClassId, newParents); +}; +//const ematch = (pattern) => [subts, eClass][] +// To apply a rewrite l → 𝑟 to an e-graph, +// ematch finds tuples (𝜎,𝑐) where e-class 𝑐 represents l[𝜎]. +// Then, for each tuple, merge(𝑐, add(𝑟 [𝜎])) adds 𝑟 [𝜎] to the e-graph +// and unifies it with the matching e-class c. +add(mk_enode("1", [])); +add(mk_enode("2", [])); +add(mk_enode("+", [hashcons.get("1"), hashcons.get("2")])); +add(mk_enode("+", [hashcons.get("1"), hashcons.get("2")])); +rebuild(); +console.log(hashcons); diff --git a/dist/demo/2024_08/js.js b/dist/demo/2024_08/js.js new file mode 100644 index 0000000..f20dc26 --- /dev/null +++ b/dist/demo/2024_08/js.js @@ -0,0 +1,50 @@ +// source: https://github.com/manubb/union-find +export const makeSet = () => { + const singleton = { + rank: 0, + }; + singleton.parent = singleton; + return singleton; +}; +export const find = (node) => { + if (node.parent !== node) { + node.parent = find(node.parent); + } + return node.parent; +}; +export const union = (node1, node2) => { + const root1 = find(node1); + const root2 = find(node2); + if (root1 !== root2) { + if (root1.rank < root2.rank) { + root1.parent = root2; + } + else { + root2.parent = root1; + if (root1.rank === root2.rank) + root1.rank += 1; + } + } +}; +/* +MIT License + +Copyright (c) 2018 Manuel Baclet + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.*/ diff --git a/dist/demo/2024_08/union_find.js b/dist/demo/2024_08/union_find.js new file mode 100644 index 0000000..7afaa29 --- /dev/null +++ b/dist/demo/2024_08/union_find.js @@ -0,0 +1,56 @@ +// source: https://github.com/manubb/union-find + +let idCounter = 0; +export const makeSet = () => { + const singleton = { + rank: 0, + id: idCounter++, + }; + singleton.parent = singleton; + + return singleton; +}; + +export const find = (node) => { + if (node.parent !== node) { + node.parent = find(node.parent); + } + + return node.parent; +}; + +export const union = (node1, node2) => { + const root1 = find(node1); + const root2 = find(node2); + if (root1 !== root2) { + if (root1.rank < root2.rank) { + root1.parent = root2; + } else { + root2.parent = root1; + if (root1.rank === root2.rank) root1.rank += 1; + } + } +}; + +/* +MIT License + +Copyright (c) 2018 Manuel Baclet + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.*/ diff --git a/src/demo/2024_07/rewrite.ts b/src/demo/2024_07/rewrite.ts index b1d6bd6..6db6973 100644 --- a/src/demo/2024_07/rewrite.ts +++ b/src/demo/2024_07/rewrite.ts @@ -1,25 +1,134 @@ -const match = (pattern, structure) => { - if (structure[0] !== pattern[0]) return false; - const a = Array.isArray(pattern[1]) - ? match(pattern[1], structure[1]) - : { [pattern[1]]: structure[1] }; - if (!a) return false; - const b = Array.isArray(pattern[2]) - ? match(pattern[2], structure[2]) - : { [pattern[2]]: structure[2] }; - if (!b) return false; - return { - ...a, - ...b, - }; -}; +const variable = ({ name, pred, match }: any) => ({ + kind: "variable", + name, + pred, + match, + + withPred: (newPred) => variable({ name, pred: newPred, match }), + withMatch: (newMatch) => variable({ name, pred, match: newMatch }), +}); +const v = ([name]: TemplateStringsArray) => + variable({ name, pred: () => true, match: (matches) => matches[name] }); -const subst = (matches, [op, a, b]) => [ - op, - Array.isArray(a) ? subst(matches, a) : matches[a], - Array.isArray(b) ? subst(matches, b) : matches[b], +const isNumber = (n) => typeof n === "number"; + +const MY_RULES = [ + { + from: ["=", ["+", v`a`, v`b`], v`c`], + to: ["=", v`a`, ["-", v`c`, v`b`]], + }, + { + from: ["=", ["-", v`a`, v`b`], v`c`], + to: ["=", v`a`, ["+", v`c`, v`b`]], + }, + { + from: ["=", v`a`, v`b`], + to: ["=", v`b`, v`a`], + }, + { + from: ["+", v`a`, v`b`], + to: ["+", v`b`, v`a`], + }, + { + from: ["+", v`a`.withPred(isNumber), v`b`.withPred(isNumber)], + to: v`a + b`.withMatch(({ a, b }) => a + b), + }, + { + from: ["+", v`a`, v`a`], + to: ["*", 2, v`a`], + }, + { + from: ["*", v`a`, v`b`], + to: ["*", v`b`, v`a`], + }, + { + from: ["=", ["*", v`a`, v`b`], v`c`], + to: ["=", v`a`, ["/", v`c`, v`b`]], + }, + { + from: ["=", ["/", v`a`, v`b`], v`c`], + to: ["=", v`a`, ["*", v`c`, v`b`]], + }, ]; +const MATCH_FAIL = "MATCH_FAIL"; +const match = (pattern, structure, matchesSoFar = {}) => { + if (pattern.kind === "variable") { + if (!pattern.pred(structure, matchesSoFar)) return MATCH_FAIL; + const prevMatch = pattern.match(matchesSoFar); + if (prevMatch && JSON.stringify(prevMatch) !== JSON.stringify(structure)) + return MATCH_FAIL; + + return { [pattern.name]: structure }; + } else if (Array.isArray(pattern)) { + if (!Array.isArray(structure)) return MATCH_FAIL; + let matches = {}; + for (let i = 0; i < pattern.length; i++) { + const p = pattern[i]; + const s = structure[i]; + const m = match(p, s, matches); + if (m === MATCH_FAIL) return MATCH_FAIL; + matches = { ...matches, ...m }; + } + return matches; + } else { + if (pattern !== structure) return MATCH_FAIL; + return {}; + } +}; +const getRecursiveMatches = (pattern, structure, res = new Map()) => { + res.set(structure, match(pattern, structure)); + if (Array.isArray(structure)) + for (const substructure of structure) + getRecursiveMatches(pattern, substructure, res); + return res; +}; + +const SUBST_FAIL = "SUBST_FAIL"; +const subst = (matches, structure) => { + if (structure.kind === "variable") + return structure.match(matches) ?? SUBST_FAIL; + else if (Array.isArray(structure)) { + const ms = structure.map((s) => subst(matches, s)); + if (ms.some((m) => m === SUBST_FAIL)) return SUBST_FAIL; + return ms; + } else return structure; +}; + +const APPLY_FAIL = "APPLY_FAIL"; +const applyRule = ({ from, to, success, failure }, structure) => { + const m = match(from, structure); + + if (m === MATCH_FAIL) { + if (failure) failure(MATCH_FAIL); + return APPLY_FAIL; + } + const s = subst(m, to); + if (s === SUBST_FAIL) { + if (failure) failure(SUBST_FAIL); + return APPLY_FAIL; + } + if (success) success(); + return s; +}; + +const tryAllRulesRecursively = (rules, structure) => { + return [ + ...(Array.isArray(structure) + ? structure.flatMap((s, i) => + tryAllRulesRecursively(rules, s).map((res) => structure.with(i, res)) + ) + : []), + ...rules + .map((r) => { + const res = applyRule(r, structure); + if (res === APPLY_FAIL) return APPLY_FAIL; + return res; + }) + .filter((subRes) => subRes !== APPLY_FAIL), + ]; +}; + const rewriteStep = (input, rules) => { const neue = [ ...(Array.isArray(input[1]) @@ -29,48 +138,113 @@ const rewriteStep = (input, rules) => { ? rewriteStep(input[2], rules).map((res) => [input[0], input[1], res]) : []), ]; - for (const { from, to } of rules) { - const m = match(from, input); - if (!m) continue; - neue.push(subst(m, to)); + for (const rule of rules) { + const res = applyRule(rule, input); + if (res === APPLY_FAIL) continue; + neue.push(res); } return neue; }; -const rules = [ - { - from: ["=", ["+", "a", "b"], "c"], - to: ["=", "a", ["-", "c", "b"]], - }, - { - from: ["=", ["-", "a", "b"], "c"], - to: ["=", "a", ["+", "c", "b"]], - }, - { - from: ["=", "a", "b"], - to: ["=", "b", "a"], - }, - { - from: ["+", "a", "b"], - to: ["+", "b", "a"], - }, - /* next: { - from: ["-", { name: "a", pred: isNum }, { name: "b", isNum }], - to: { args: ["a", "b"], calc: (a, b)=>a-b }, - },*/ -]; +//const myInput = ["=", ["-", "w", 2], 3]; +const myInput1 = ["=", `w`, ["-", `r`, `l`]]; +const myInput2 = ["=", `c`, ["+", `l`, ["/", `w`, 2]]]; -const mySet = new Set(); -const see = (ob) => mySet.add(JSON.stringify(ob)); -const isSeen = (ob) => mySet.has(JSON.stringify(ob)); +// GOAL: +// input: l, r, w = r - l, c = l + w/2 +// output: +// - given: solution for l and r, given w and c. + +const isVar = (x) => x === "w" || x === "r" || x === "l" || x === "c"; +const sToRule = (s) => { + const m = match(["=", v`lhs`.withPred(isVar), v`rhs`], s); + if (m === MATCH_FAIL) return []; + return [ + { + m_name: m.lhs, + from: v`lhs`.withPred((x) => x === m.lhs), + to: m.rhs, + success: () => console.log("SUCCESSFULLY APPLIED", m.lhs), + failure: (fa) => console.log("FAIL APPLIED", fa, m.lhs), + }, + ]; +}; -const myInput = ["=", ["-", "w", 2], 3]; +console.log( + "tryAllRulesRecursively 1", + tryAllRulesRecursively(MY_RULES, "a"), + tryAllRulesRecursively(MY_RULES, ["a"]) +); -const toTry = [myInput]; -while (toTry.length > 0) { - const i = toTry.pop(); - see(i); - toTry.push(...rewriteStep(i, rules).filter((res) => !isSeen(res))); +console.log( + "tryAllRulesRecursively 2", + tryAllRulesRecursively(MY_RULES, ["+", "a", "b"]) +); + +console.log("sToRule 1", sToRule(["=", v`x`, ["+", 1, 2]])); +console.log("sToRule 2", sToRule(["=", ["sin", v`x`], ["+", 1, 2]])); + +console.log( + "sToRule 3", + tryAllRulesRecursively( + [ + { + from: v`lhs`.withPred((x) => x.kind === "variable" && x.name === "x"), + to: 22, + }, + ], + ["sin", v`x`] + ), + tryAllRulesRecursively(sToRule(["=", v`x`, ["+", 1, 2]]), [ + "+", + 33, + ["-", 1, v`x`], + ]) +); + +const mySet1 = new Set(); +const mySet2 = new Set(); +const see = (set, ob) => set.add(JSON.stringify(ob)); +const isSeen = (set, ob) => set.has(JSON.stringify(ob)); + +const rules = [...MY_RULES]; +const toTry1 = [myInput1]; +const toTry2 = [myInput2]; +for (let i = 0; i < 12; i++) { + if (toTry1.length === 0 && toTry2.length === 0) break; + if (toTry1.length > 0) { + const i = toTry1.pop(); + see(mySet1, i); + const newStructuresToTry = tryAllRulesRecursively(rules, i).filter( + (res) => !isSeen(mySet1, res) + ); + toTry1.push(...newStructuresToTry); + rules.push(...newStructuresToTry.flatMap(sToRule)); + } + if (toTry2.length > 0) { + const i = toTry2.pop(); + see(mySet2, i); + const newStructuresToTry = tryAllRulesRecursively(rules, i).filter( + (res) => !isSeen(mySet2, res) + ); + toTry2.push(...newStructuresToTry); + rules.push(...newStructuresToTry.flatMap(sToRule)); + } + console.log("hi", toTry1.length); } -console.log([...mySet.values()].map(JSON.parse)); +console.log( + "go go go", + [...mySet1.values()].map(JSON.parse), + [...mySet2.values()].map(JSON.parse), + rules +); +// console.log( +// "SELECT", +// [...mySet.values()] +// .map(JSON.parse) +// .filter( +// ([[_1, v1], [_2, v2]]) => v1.kind === "variable" && v2.kind === "variable" +// ) +// .map(([[_1, v1], [_2, v2]]) => [v1.name, v2.name]) +// ); diff --git a/src/demo/2024_08/e_graph.html b/src/demo/2024_08/e_graph.html new file mode 100644 index 0000000..d64bca7 --- /dev/null +++ b/src/demo/2024_08/e_graph.html @@ -0,0 +1 @@ + diff --git a/src/demo/2024_08/e_graph.js b/src/demo/2024_08/e_graph.js new file mode 100644 index 0000000..496a503 --- /dev/null +++ b/src/demo/2024_08/e_graph.js @@ -0,0 +1,85 @@ +// e init (tree) => e-graph +// e match (pattern, e-graph) => e-node[] +// e sub (result, e-node, e-graph) => e-graph + +const node = (value, ...children) => { + const res = { + isNode: true, + value, + children: children, + parent: null, + }; + for (const child of children) child.parent = res; + return res; +}; +const vari = (v, ...children) => { + const res = { + isNode: true, + var: v, + children: children, + parent: null, + }; + for (const child of children) child.parent = res; + return res; +}; +const nodeEq = (n1, n2) => { + if (n1.value !== n2.value) return false; + else if (n1.children.length !== n2.children.length) return false; + else return n1.children.every((v, i) => nodeEq(v, n2.children[i])); +}; + +const a = node("f", node(1), node(2)); +const b = node("f", node(1), node(2)); + +console.log("nodeEq!", nodeEq(a, b)); + +const eClassFromNode = (node, parentENode = null) => { + const eNode = { isENode: true, value: node.value }; + eNode.children = node.children.map((n) => eClassFromNode(n, eNode)); + return { + isEClass: true, + eNodes: [eNode], + parents: [parentENode], + }; +}; + +console.log("eClassFromNode!", eClassFromNode(a)); + +const eClassMatches = (patternNode, eClass) => { + return eClass.eNodes.flatMap((en) => eNodeMatches(patternNode, en)); +}; +const eNodeMatches = (patternNode, eNode) => { + if (patternNode.var === undefined && eNode.value !== patternNode.value) + return []; + else if (patternNode.children.length !== eNode.children.length) return []; + else { + const childrenMatches = eNode.children.map((ec, i) => + eClassMatches(patternNode.children[i], ec) + ); + return [ + ...gogo( + childrenMatches, + patternNode.var ? { [patternNode.var]: eNode.value } : {} + ), + ]; + } +}; + +const gogo = function* (childrenMatches, match) { + if (childrenMatches.length === 0) { + yield { ...match }; + return; + } + for (const matches1 of childrenMatches[0]) { + for (const matches2 of gogo(childrenMatches.slice(1))) { + yield { ...match, ...matches1, ...matches2 }; + } + } +}; + +console.log( + "eClassMatches!", + eClassMatches(vari("go", vari("1"), node(2)), eClassFromNode(a)) +); + +// Aside: the implicit lifting language could maybe really help simplify this. diff --git a/src/demo/2024_08/egg_port.ts b/src/demo/2024_08/egg_port.ts new file mode 100644 index 0000000..cdb50d0 --- /dev/null +++ b/src/demo/2024_08/egg_port.ts @@ -0,0 +1,88 @@ +import { SetMap } from "../../lib/structure/data.js"; +import { makeSet, find, union } from "./union_find.js"; + +type EClassId = { id: number }; +type ENode = { + op: string; + children: EClassId[]; + hash: string; +}; + +// map canonical e-nodes to e-class ids +// i.e. e-node 𝑛 ∈ 𝑀 [𝑎] ⇐⇒ 𝐻 [canonicalize(𝑛)] = find(𝑎) +const hashcons = new Map(); +const parents = new Map>(); +const worklist = new Set(); + +const mk_enode = (op: string, children: EClassId[]): ENode => ({ + op, + children, + hash: op + children.map((c) => "c" + c.id).join(), +}); + +// TODO: fix canonicalize to respect hashcons +const canonicalize = (eNode: ENode) => + mk_enode(eNode.op, eNode.children.map(find)); +const lookup = (eNode: ENode): EClassId | undefined => hashcons.get(eNode.hash); + +// eNode -> eClassId +const add = (eNode: ENode): EClassId => { + const en = canonicalize(eNode); + if (lookup(en)) return lookup(en)!; + else { + const eClassId = makeSet(); + for (const child of eNode.children) + parents.set( + child, + parents.get(child)?.set(en, eClassId) ?? new Map([[en, eClassId]]) + ); + hashcons.set(en.hash, eClassId); + return eClassId; + } +}; +const merge = (eClassId1, eClassId2) => { + if (find(eClassId1) === find(eClassId2)) return find(eClassId1); + const newEClassId = union(eClassId1, eClassId2); + worklist.add(newEClassId); + return newEClassId; +}; + +const rebuild = () => { + while (worklist.size > 0) { + const todo = [...worklist]; + worklist.clear(); + for (const eClassId of todo) repair(find(eClassId)); + } +}; +const repair = (eClassId) => { + for (const [peNode, peClassId] of parents.get(eClassId) ?? []) { + hashcons.delete(peNode.hash); + const newPeNode = canonicalize(peNode); + hashcons.set(newPeNode.hash, peClassId); + } + + const newParents = new Map(); + for (const [peNode, peClassId] of parents.get(eClassId) ?? []) { + const newPeNode = canonicalize(peNode); + if (newParents.has(newPeNode)) { + merge(peClassId, newParents.get(newPeNode)); + } + newParents.set(newPeNode, find(peClassId)); + } + parents.set(eClassId, newParents); +}; + +//const ematch = (pattern) => [subts, eClass][] + +// To apply a rewrite l → 𝑟 to an e-graph, +// ematch finds tuples (𝜎,𝑐) where e-class 𝑐 represents l[𝜎]. +// Then, for each tuple, merge(𝑐, add(𝑟 [𝜎])) adds 𝑟 [𝜎] to the e-graph +// and unifies it with the matching e-class c. + +add(mk_enode("1", [])); +add(mk_enode("2", [])); +add(mk_enode("+", [hashcons.get("1"), hashcons.get("2")])); +add(mk_enode("+", [hashcons.get("1"), hashcons.get("2")])); +rebuild(); + +console.log(hashcons); diff --git a/src/demo/2024_08/union_find.js b/src/demo/2024_08/union_find.js new file mode 100644 index 0000000..7afaa29 --- /dev/null +++ b/src/demo/2024_08/union_find.js @@ -0,0 +1,56 @@ +// source: https://github.com/manubb/union-find + +let idCounter = 0; +export const makeSet = () => { + const singleton = { + rank: 0, + id: idCounter++, + }; + singleton.parent = singleton; + + return singleton; +}; + +export const find = (node) => { + if (node.parent !== node) { + node.parent = find(node.parent); + } + + return node.parent; +}; + +export const union = (node1, node2) => { + const root1 = find(node1); + const root2 = find(node2); + if (root1 !== root2) { + if (root1.rank < root2.rank) { + root1.parent = root2; + } else { + root2.parent = root1; + if (root1.rank === root2.rank) root1.rank += 1; + } + } +}; + +/* +MIT License + +Copyright (c) 2018 Manuel Baclet + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.*/