From 9fac34b8862f3071d4bf89ee57f168e8cea9a2a5 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 26 Nov 2025 16:47:07 +0000 Subject: [PATCH 01/12] Getting started with the tutorial --- _config.yml | 1 + playground/index.md | 2 +- spec/index.md | 2 +- tutorial/index.md | 68 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 tutorial/index.md diff --git a/_config.yml b/_config.yml index e081ba5..341ef22 100644 --- a/_config.yml +++ b/_config.yml @@ -42,6 +42,7 @@ exclude: - package-lock.json - package.json - README.md + - thirdparty sass: quiet_deps: true # https://github.com/just-the-docs/just-the-docs/issues/1541 diff --git a/playground/index.md b/playground/index.md index a4a9b64..6887023 100644 --- a/playground/index.md +++ b/playground/index.md @@ -1,7 +1,7 @@ --- title: GraphAlg Playground layout: page -nav_order: 3 +nav_order: 4 --- # GraphAlg Playground diff --git a/spec/index.md b/spec/index.md index df119e3..8eaccef 100644 --- a/spec/index.md +++ b/spec/index.md @@ -1,7 +1,7 @@ --- title: Language Specification layout: page -nav_order: 2 +nav_order: 3 --- # GraphAlg Language Specification diff --git a/tutorial/index.md b/tutorial/index.md new file mode 100644 index 0000000..25dd877 --- /dev/null +++ b/tutorial/index.md @@ -0,0 +1,68 @@ +--- +title: Tutorial +layout: page +nav_order: 2 +--- + +# Tutorial + +## Welcome +Welcome to the GraphAlg tutorial. +GraphAlg is a domain-specific programming language for writing graph algorithms. +As you will soon see, with GraphAlg you can use familiar linear algebra operations such as matrix multiplication to analyze graphs. +GraphAlg is designed to be embedded into database systems, allowing you to run complex user-defined graph algorithms without leaving your DBMS. + +This guide is designed for new users that want to learn how to write graph algorithms in GraphAlg. +To follow along, all you need are: +1. Basic programming skills in another language (Python, JavaScript, etc.) +2. Rudimentary knowledge of linear algebra (Matrix multiplication). + +## A First Example +Let us start with an example program to introduce key concepts of the GraphAlg language. +Try running it by pressing the **Run** button!. + +{: + data-ga-func="AddOne" + data-ga-arg-0=" + 1, 1, i64; + 0, 0, 42;" +} +```graphalg +func AddOne(a: int) -> int { + return a + int(1); +} +``` + +GraphAlg programs are a collection of functions. +They are defined using the `func` keyword. +GraphAlg is *statically typed*: you must assign types to the parameters of a function, and to the return type. +`a: int` on line 1 defines a parameter named `a` with type `int`. +The return type of this function is also `int`, as indicated by `-> int` following the parameters. + +After the return type and inside of the `{ .. }` is the implementation of the function. +In this case the implementation is rather trivial: Take the value of `a` and add one to it. + +```graphalg +func Reachability( + graph: Matrix, + source: Vector) -> Matrix { + v = source; + for i in graph.nrows { + v += v * graph; + } + + return v; +} +``` + +This algorithm implements a [reachability analysis](https://en.wikipedia.org/wiki/Reachability). +It determines which nodes in the graph are connected through any number of edges (reachable) from any of the *source* vertices. + +GraphAlg programs are a collection of functions. +They are defined using the `func` keyword. +GraphAlg is *statically typed*: you must assign types to the parameters of a function, and to the return type. +`graph: Matrix` on line 2 defines a parameter named `graph` with type `Matrix`. + + + + From 2ac9cf9f1743f011c8dcc6ed650408e35d3fbfe5 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 27 Nov 2025 14:24:22 +0000 Subject: [PATCH 02/12] First bit of the tutorial. --- playground/binding.mjs | 2 +- playground/editor.ts | 45 +++++++++++++++-- tutorial/index.md | 108 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 145 insertions(+), 10 deletions(-) diff --git a/playground/binding.mjs b/playground/binding.mjs index 90bc788..b2f9f07 100644 --- a/playground/binding.mjs +++ b/playground/binding.mjs @@ -24,7 +24,7 @@ export function loadPlaygroundWasm() { let bindings = new PlaygroundWasmBindings(); playgroundWasmFactory({ locateFile: function (path, prefix) { - return playgroundWasm; + return prefix + playgroundWasm; }, }).then((instance) => { bindings.ga_new = instance.cwrap('ga_new', 'number', []); diff --git a/playground/editor.ts b/playground/editor.ts index e64d369..4a6a31b 100644 --- a/playground/editor.ts +++ b/playground/editor.ts @@ -281,7 +281,46 @@ function parseMatrix(input: string): GraphAlgMatrix { } } -function buildTable(m: GraphAlgMatrix): HTMLTableElement { +function buildTableAuto(m: GraphAlgMatrix): HTMLTableElement { + // If the matrix is not too large, we can use a matrix-style display + const hasSmallDimensions = m.rows < 10 && m.cols < 10; + if (hasSmallDimensions) { + return buildTableMatrix(m); + } else { + return buildTableCOO(m); + } +} + +function buildTableMatrix(m: GraphAlgMatrix): HTMLTableElement { + // Create an output table. + const table = document.createElement("table"); + + // Body + const tbody = document.createElement("tbody"); + + // Create cells + for (let r = 0; r < m.rows; r++) { + const tr = document.createElement("tr"); + for (let c = 0; c < m.cols; c++) { + const td = document.createElement("td"); + tr.appendChild(td); + } + + tbody.appendChild(tr); + } + + // Fill non-zero cells + for (let val of m.values) { + const row = tbody.childNodes[val.row]; + const cell = row.childNodes[val.col]; + cell.textContent = val.val.toString(); + } + + table.append(tbody); + return table; +} + +function buildTableCOO(m: GraphAlgMatrix): HTMLTableElement { // Create an output table. const table = document.createElement("table"); @@ -375,7 +414,7 @@ class GraphAlgEditor { const argDetails = document.createElement("details"); const argSummary = document.createElement("summary"); argSummary.textContent = `Argument ${this.arguments.length} (${arg.ring} x ${arg.rows} x ${arg.cols})`; - const table = buildTable(arg); + const table = buildTableAuto(arg); argDetails.append(argSummary, table); this.argumentContainer.appendChild(argDetails); } @@ -446,7 +485,7 @@ function run(editor: GraphAlgEditor, inst: PlaygroundInstance) { const result = inst.run(program, editor.functionName!!, editor.arguments); let resultElem; if (result.result) { - resultElem = buildTable(result.result); + resultElem = buildTableAuto(result.result); } else { resultElem = buildErrorNote(result.diagnostics); } diff --git a/tutorial/index.md b/tutorial/index.md index 25dd877..92cd5d2 100644 --- a/tutorial/index.md +++ b/tutorial/index.md @@ -21,6 +21,15 @@ To follow along, all you need are: Let us start with an example program to introduce key concepts of the GraphAlg language. Try running it by pressing the **Run** button!. +{: .note } +> The examples shown in this tutorial run inside your own browser using +> [WebAssembly](https://webassembly.org/). +> +> You can modify and run examples as much as you want. +> If you write an invalid program, the editor will underline the part of your +> program that is incorrect. Hover over the underlined code to see the error +> message, or click *Run* to print error messages to the output. + {: data-ga-func="AddOne" data-ga-arg-0=" @@ -33,14 +42,101 @@ func AddOne(a: int) -> int { } ``` -GraphAlg programs are a collection of functions. -They are defined using the `func` keyword. +As you may have guessed, this trivial program simply increments its input by one. +Or, more accurately, the `AddOne` function increments its input by one. +A GraphAlg program is nothing more than a collection of functions. + +## Anatomy of a Function +Functions are defined using the `func` keyword. GraphAlg is *statically typed*: you must assign types to the parameters of a function, and to the return type. -`a: int` on line 1 defines a parameter named `a` with type `int`. -The return type of this function is also `int`, as indicated by `-> int` following the parameters. +The general shape of a function is: + +```graphalg +func (: , ...) -> { + + ... +} +``` + +The `AddOne` uses the `int` type for both the parameter and the return type. +This represents a signed, 64-bit integer (similar to `long` in Java and C++). +A function can have any number of parameter, but it must have **exactly one return value**. +You may be familiar with other languages that allow returning no value at all (commonly named `void` functions). +In GraphAlg such a function would be pointless: +GraphAlg programs cannot write to files, make HTTP requests, or perform any other action that has [*side effects*](https://en.wikipedia.org/wiki/Side_effect_(computer_science)). +A GraphAlg function can only perform a computation over the inputs it receives, and return the result. + +{: .note-title } +> Why no side effects? +> +> Disallowing side effects may seem like an annoying restriction to place on a competent programmer such as yourself, but it is crucial for systems that implement GraphAlg support: +> By not allowing side effects, GraphAlg programs can be heavily optimized to run as efficiently as possible. +> It also makes it possible to implement GraphAlg in highly diverse and restrictive environments, such as the query engine of a database system. + +Coming back to the shape a function, let us consider the body of the function (the part contained inside `{..}`). +The body consists of one or more statements that together define the behaviour of the function. +A function body ends with a `return` statement that defines the final result to be returned. + +## More Statements: Variables and Loops +Our `AddOne` function was simple enough that we could directly define the result inside of the `return`. +Let us now consider a more complex program that needs a few more statements: + +{: + data-ga-func="Fibonacci" + data-ga-arg-0=" + 1, 1, i64; + 0, 0, 10;" +} +```graphalg +func Fibonacci(n: int) -> int { + a = int(0); + b = int(1); + for i in int(0):n { + c = a + b; + a = b; + b = c; + } + + return b; +} +``` + +As the name implies, `Fibonacci` computes the `n`'th number in the fibonacci sequence. +This example shows how to define new variables with `=` (see line 2, where we define `a`). +The same syntax is used to update the value of an existing variable (see line 6, where we reassign `a`). + +{: .note-title } +> Experiment +> +> Try computing different numbers in the sequence by adding e.g. `n = int(5);` at the start of the function body. + +We also see a first use of the `for` construct. Like many other programming languages, `for` executes its loop body repeatedly. +Variable `i` is the *loop iteration variable*. +It is defined by loop, and assigned to the current iteration number. +The values that `i` will take are defined by the *loop range*, `int(0):n` in the example above. +Assuming you have not changed the default value of `n` (10), the loop will run for 10 iterations, where `i` takes on the values 0, 1, 2, 3, 4, 5, 6, 7, 8 and finally 9 **(not 10)**. + +A word about the scope of variables: +You can refer to variables defined earlier in the same scope (`{ .. }` defines a scope), or to variables that were defined *before* entering the current scope. +For example, `c = a + b;` on line 5 can refer to `a` and `b` even though they are defined in the outer function scope, not the loop scope. +This is okay because the loop scope is defined *inside* of the function scope. +What is not allowed however is to first define a variable in a nested scope, and then refer to it from the outer scope. +For an example, consider the (invalid) program below. + +```graphalg +func NotValid() -> int { + a = int(0); + for i in int(0):int(10) { + b = a; + a = a + int(1); + } + + return b; +} +``` -After the return type and inside of the `{ .. }` is the implementation of the function. -In this case the implementation is rather trivial: Take the value of `a` and add one to it. +Variable `b` is only defined inside of the loop, not in the outer function scope. +The statement `return b;` is invalid, as `b` is undefined in this context. ```graphalg func Reachability( From 8fcbb939a4f6cd0af49403a2087bcef49c700361 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 27 Nov 2025 15:20:24 +0000 Subject: [PATCH 03/12] Start to talk linalg. --- tutorial/index.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tutorial/index.md b/tutorial/index.md index 92cd5d2..2441d9b 100644 --- a/tutorial/index.md +++ b/tutorial/index.md @@ -138,6 +138,13 @@ func NotValid() -> int { Variable `b` is only defined inside of the loop, not in the outer function scope. The statement `return b;` is invalid, as `b` is undefined in this context. +## Bringing Linear Algebra Into the Mix +Our example programs so far have only used the `int` type so far. +There are also `bool` (`false` or `true`) and `real` (floating-point numbers, 64-bit). +To capture the connected nature of graphs, however, we need more than simple scalar types. +GraphAlg represents graphs as adjacency matrices. + +## TODO ```graphalg func Reachability( graph: Matrix, From 87feefe6c5c205aa2e55482e6cf2f99529c3c007 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 27 Nov 2025 17:09:14 +0000 Subject: [PATCH 04/12] Render graph with vis.js. --- package-lock.json | 108 ++++++++++++++++++++++++++++++++++++- playground/editor.ts | 116 +++++++++++++++++++++++++++++++++++----- playground/package.json | 3 +- tutorial/index.md | 22 ++++++++ 4 files changed, 235 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2010b78..9b9fa07 100644 --- a/package-lock.json +++ b/package-lock.json @@ -103,6 +103,19 @@ "w3c-keyname": "^2.2.4" } }, + "node_modules/@egjs/hammerjs": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", + "integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/hammerjs": "^2.0.36" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -569,6 +582,13 @@ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true }, + "node_modules/@types/hammerjs": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz", + "integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==", + "license": "MIT", + "peer": true + }, "node_modules/@types/node": { "version": "24.7.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.1.tgz", @@ -801,6 +821,19 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/component-emitter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-2.0.0.tgz", + "integrity": "sha512-4m5s3Me2xxlVKG9PkZpQqHQR7bgpnN7joDMJ4yvVkVXngjoITG76IaZmzmywSeRTeTpc6N6r3H3+KyUurV8OYw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/crelt": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", @@ -1122,6 +1155,13 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/keycharm": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/keycharm/-/keycharm-0.4.0.tgz", + "integrity": "sha512-TyQTtsabOVv3MeOpR92sIKk/br9wxS+zGj4BG7CR8YbK4jM3tyIBaF0zhzeBUMx36/Q/iQLOKKOT+3jOQtemRQ==", + "license": "(Apache-2.0 OR MIT)", + "peer": true + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -1681,6 +1721,71 @@ "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", "dev": true }, + "node_modules/uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "peer": true, + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/vis-data": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/vis-data/-/vis-data-8.0.3.tgz", + "integrity": "sha512-jhnb6rJNqkKR1Qmlay0VuDXY9ZlvAnYN1udsrP4U+krgZEq7C0yNSKdZqmnCe13mdnf9AdVcdDGFOzy2mpPoqw==", + "license": "(Apache-2.0 OR MIT)", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/visjs" + }, + "peerDependencies": { + "uuid": "^3.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^13.0.0", + "vis-util": ">=6.0.0" + } + }, + "node_modules/vis-network": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/vis-network/-/vis-network-10.0.2.tgz", + "integrity": "sha512-qPl8GLYBeHEFqiTqp4VBbYQIJ2EA8KLr7TstA2E8nJxfEHaKCU81hQLz7hhq11NUpHbMaRzBjW5uZpVKJ45/wA==", + "license": "(Apache-2.0 OR MIT)", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/visjs" + }, + "peerDependencies": { + "@egjs/hammerjs": "^2.0.0", + "component-emitter": "^1.3.0 || ^2.0.0", + "keycharm": "^0.2.0 || ^0.3.0 || ^0.4.0", + "uuid": "^3.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^13.0.0", + "vis-data": ">=8.0.0", + "vis-util": ">=6.0.0" + } + }, + "node_modules/vis-util": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/vis-util/-/vis-util-6.0.0.tgz", + "integrity": "sha512-qtpts3HRma0zPe4bO7t9A2uejkRNj8Z2Tb6do6lN85iPNWExFkUiVhdAq5uLGIUqBFduyYeqWJKv/jMkxX0R5g==", + "license": "(Apache-2.0 OR MIT)", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/visjs" + }, + "peerDependencies": { + "@egjs/hammerjs": "^2.0.0", + "component-emitter": "^1.3.0 || ^2.0.0" + } + }, "node_modules/w3c-keyname": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", @@ -1904,7 +2009,8 @@ "@codemirror/lint": "^6.9.2", "@replit/codemirror-vim": "^6.3.0", "codemirror": "^6.0.2", - "codemirror-lang-graphalg": "^0.1.0" + "codemirror-lang-graphalg": "^0.1.0", + "vis-network": "^10.0.2" }, "devDependencies": { "@lezer/generator": "^1.8.0", diff --git a/playground/editor.ts b/playground/editor.ts index 4a6a31b..e959ce3 100644 --- a/playground/editor.ts +++ b/playground/editor.ts @@ -5,6 +5,7 @@ import { indentWithTab } from "@codemirror/commands" import { linter, Diagnostic } from "@codemirror/lint" import { GraphAlg } from "codemirror-lang-graphalg" import { loadPlaygroundWasm } from "./binding.mjs" +import { DataSet, Network } from "vis-network/standalone" // Load and register all webassembly bindings let playgroundWasmBindings = loadPlaygroundWasm(); @@ -281,16 +282,6 @@ function parseMatrix(input: string): GraphAlgMatrix { } } -function buildTableAuto(m: GraphAlgMatrix): HTMLTableElement { - // If the matrix is not too large, we can use a matrix-style display - const hasSmallDimensions = m.rows < 10 && m.cols < 10; - if (hasSmallDimensions) { - return buildTableMatrix(m); - } else { - return buildTableCOO(m); - } -} - function buildTableMatrix(m: GraphAlgMatrix): HTMLTableElement { // Create an output table. const table = document.createElement("table"); @@ -355,6 +346,82 @@ function buildTableCOO(m: GraphAlgMatrix): HTMLTableElement { return table; } +function buildVisGraph(m: GraphAlgMatrix): HTMLElement { + if (m.rows != m.cols) { + throw Error("buildVisGraph called with a non-square matrix"); + } + + const container = document.createElement("div"); + container.style.width = "100%"; + container.style.height = "300px"; + + interface NodeItem { + id: number; + label: string; + } + const nodes = new DataSet(); + for (let r = 0; r < m.rows; r++) { + nodes.add({ id: r, label: r.toString() }); + } + + // create an array with edges + interface EdgeItem { + id: number; + from: number; + to: number; + label: string; + } + const edges = new DataSet(); + for (let val of m.values) { + let label = val.val.toString(); + if (m.ring == "i1" && val.val == true) { + label = ""; + } + + edges.add({ + id: edges.length, + from: val.row, + to: val.col, + label: label + }); + } + + // create a network + const data = { + nodes: nodes, + edges: edges, + }; + var options = { + layout: { + // Deterministic layout of graphs + randomSeed: 42 + }, + edges: { + arrows: { + to: { + enabled: true + } + } + } + }; + var network = new Network(container, data, options); + + return container; +} + +function buildMatrixVisualization(m: GraphAlgMatrix): HTMLElement { + if (m.rows == 1 && m.cols == 1) { + // Simple scalar + return buildTableMatrix(m); + } else if (m.rows == m.cols && m.rows < 20) { + return buildVisGraph(m); + } else if (m.rows < 50 && m.cols < 20) { + return buildTableMatrix(m); + } else { + return buildTableCOO(m); + } +} + class GraphAlgEditor { root: Element; toolbar: Element; @@ -414,7 +481,7 @@ class GraphAlgEditor { const argDetails = document.createElement("details"); const argSummary = document.createElement("summary"); argSummary.textContent = `Argument ${this.arguments.length} (${arg.ring} x ${arg.rows} x ${arg.cols})`; - const table = buildTableAuto(arg); + const table = buildMatrixVisualization(arg); argDetails.append(argSummary, table); this.argumentContainer.appendChild(argDetails); } @@ -485,7 +552,7 @@ function run(editor: GraphAlgEditor, inst: PlaygroundInstance) { const result = inst.run(program, editor.functionName!!, editor.arguments); let resultElem; if (result.result) { - resultElem = buildTableAuto(result.result); + resultElem = buildMatrixVisualization(result.result); } else { resultElem = buildErrorNote(result.diagnostics); } @@ -520,3 +587,28 @@ playgroundWasmBindings.onLoaded((bindings: any) => { editor.toolbar.appendChild(runButton); } }); + +// Initialize graph views +const graphElems = document.getElementsByClassName("language-graphalg-matrix"); +for (let elem of Array.from(graphElems)) { + const mat = parseMatrix(elem.textContent.trim()); + + let mode: string | null = null; + if (elem.parentElement?.tagName == 'PRE') { + // Have additional annotations in a pre wrapper + elem = elem.parentElement; + + mode = elem.getAttribute('data-ga-mode'); + } + + let rendered: HTMLElement; + if (mode == "vis") { + rendered = buildVisGraph(mat); + } else if (mode == "coo") { + rendered = buildTableCOO(mat); + } else { + rendered = buildTableMatrix(mat); + } + + elem.replaceWith(rendered); +} diff --git a/playground/package.json b/playground/package.json index 08e4cf2..a928449 100644 --- a/playground/package.json +++ b/playground/package.json @@ -7,7 +7,8 @@ "@codemirror/lint": "^6.9.2", "@replit/codemirror-vim": "^6.3.0", "codemirror": "^6.0.2", - "codemirror-lang-graphalg": "^0.1.0" + "codemirror-lang-graphalg": "^0.1.0", + "vis-network": "^10.0.2" }, "devDependencies": { "@lezer/generator": "^1.8.0", diff --git a/tutorial/index.md b/tutorial/index.md index 2441d9b..2bb66de 100644 --- a/tutorial/index.md +++ b/tutorial/index.md @@ -143,6 +143,28 @@ Our example programs so far have only used the `int` type so far. There are also `bool` (`false` or `true`) and `real` (floating-point numbers, 64-bit). To capture the connected nature of graphs, however, we need more than simple scalar types. GraphAlg represents graphs as adjacency matrices. +Consider the graph shown below: + +{: + data-ga-mode="vis" +} +```graphalg-matrix +4, 4, i1; +0, 1; +0, 2; +1, 3; +2, 3; +``` + +In adjacency matrix representation, the same graph looks like this: + +```graphalg-matrix +4, 4, i1; +0, 1; +0, 2; +1, 3; +2, 3; +``` ## TODO ```graphalg From 1cd18fdc5c2c45c3d64c6c7ee75b6d445295e564 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Fri, 28 Nov 2025 08:48:31 +0000 Subject: [PATCH 05/12] Pull in katex. --- package-lock.json | 26 ++++++++++++++++++++++++++ playground/editor.ts | 31 ++++++++++++++++++++++++++++--- playground/package.json | 1 + tutorial/index.md | 16 +++++++++------- 4 files changed, 64 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9b9fa07..6598248 100644 --- a/package-lock.json +++ b/package-lock.json @@ -821,6 +821,15 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/component-emitter": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-2.0.0.tgz", @@ -1155,6 +1164,22 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/katex": { + "version": "0.16.25", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.25.tgz", + "integrity": "sha512-woHRUZ/iF23GBP1dkDQMh1QBad9dmr8/PAwNA54VrSOVYgI12MAcE14TqnDdQOdzyEonGzMepYnqBMYdsoAr8Q==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, "node_modules/keycharm": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/keycharm/-/keycharm-0.4.0.tgz", @@ -2010,6 +2035,7 @@ "@replit/codemirror-vim": "^6.3.0", "codemirror": "^6.0.2", "codemirror-lang-graphalg": "^0.1.0", + "katex": "^0.16.25", "vis-network": "^10.0.2" }, "devDependencies": { diff --git a/playground/editor.ts b/playground/editor.ts index e959ce3..2c8d7bb 100644 --- a/playground/editor.ts +++ b/playground/editor.ts @@ -6,6 +6,7 @@ import { linter, Diagnostic } from "@codemirror/lint" import { GraphAlg } from "codemirror-lang-graphalg" import { loadPlaygroundWasm } from "./binding.mjs" import { DataSet, Network } from "vis-network/standalone" +import katex from "katex" // Load and register all webassembly bindings let playgroundWasmBindings = loadPlaygroundWasm(); @@ -282,18 +283,42 @@ function parseMatrix(input: string): GraphAlgMatrix { } } -function buildTableMatrix(m: GraphAlgMatrix): HTMLTableElement { +function buildTableMatrix(m: GraphAlgMatrix): HTMLElement { + const katexCont = document.createElement("div"); + katex.render("1 + 2", katexCont); + return katexCont; + // Create an output table. const table = document.createElement("table"); + const thead = document.createElement("thead"); + const headRow = document.createElement("tr"); + const corner = document.createElement("th"); + corner.textContent = "Row/Col"; + headRow.appendChild(corner); + for (let c = 0; c < m.cols; c++) { + const colHead = document.createElement("th"); + colHead.textContent = c.toString(); + headRow.appendChild(colHead); + } + thead.appendChild(headRow); + + // Body const tbody = document.createElement("tbody"); // Create cells for (let r = 0; r < m.rows; r++) { const tr = document.createElement("tr"); + const rowHead = document.createElement("td"); + // Make it look like a header cell + rowHead.style.textAlign = 'center'; + rowHead.style.fontWeight = 'bold'; + rowHead.textContent = r.toString(); + tr.appendChild(rowHead); for (let c = 0; c < m.cols; c++) { const td = document.createElement("td"); + td.style.textAlign = 'center'; tr.appendChild(td); } @@ -303,11 +328,11 @@ function buildTableMatrix(m: GraphAlgMatrix): HTMLTableElement { // Fill non-zero cells for (let val of m.values) { const row = tbody.childNodes[val.row]; - const cell = row.childNodes[val.col]; + const cell = row.childNodes[val.col + 1]; cell.textContent = val.val.toString(); } - table.append(tbody); + table.append(thead, tbody); return table; } diff --git a/playground/package.json b/playground/package.json index a928449..1fe84e0 100644 --- a/playground/package.json +++ b/playground/package.json @@ -8,6 +8,7 @@ "@replit/codemirror-vim": "^6.3.0", "codemirror": "^6.0.2", "codemirror-lang-graphalg": "^0.1.0", + "katex": "^0.16.25", "vis-network": "^10.0.2" }, "devDependencies": { diff --git a/tutorial/index.md b/tutorial/index.md index 2bb66de..dace0d1 100644 --- a/tutorial/index.md +++ b/tutorial/index.md @@ -166,6 +166,13 @@ In adjacency matrix representation, the same graph looks like this: 2, 3; ``` +$$ + \begin{bmatrix} + a & b \\ + c & d + \end{bmatrix} +$$ + ## TODO ```graphalg func Reachability( @@ -183,11 +190,6 @@ func Reachability( This algorithm implements a [reachability analysis](https://en.wikipedia.org/wiki/Reachability). It determines which nodes in the graph are connected through any number of edges (reachable) from any of the *source* vertices. -GraphAlg programs are a collection of functions. -They are defined using the `func` keyword. -GraphAlg is *statically typed*: you must assign types to the parameters of a function, and to the return type. -`graph: Matrix` on line 2 defines a parameter named `graph` with type `Matrix`. - - - + + From 40cd0b7782f0869d311f46222a61e85fe7067b49 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Fri, 28 Nov 2025 11:17:56 +0000 Subject: [PATCH 06/12] Linear algebra stuff. --- playground/editor.ts | 81 ++++++++++++++++++++++-------------------- tutorial/index.md | 84 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 118 insertions(+), 47 deletions(-) diff --git a/playground/editor.ts b/playground/editor.ts index 2c8d7bb..1e90246 100644 --- a/playground/editor.ts +++ b/playground/editor.ts @@ -284,56 +284,51 @@ function parseMatrix(input: string): GraphAlgMatrix { } function buildTableMatrix(m: GraphAlgMatrix): HTMLElement { - const katexCont = document.createElement("div"); - katex.render("1 + 2", katexCont); - return katexCont; - - // Create an output table. - const table = document.createElement("table"); - - const thead = document.createElement("thead"); - const headRow = document.createElement("tr"); - const corner = document.createElement("th"); - corner.textContent = "Row/Col"; - headRow.appendChild(corner); - for (let c = 0; c < m.cols; c++) { - const colHead = document.createElement("th"); - colHead.textContent = c.toString(); - headRow.appendChild(colHead); + let defaultCellValue; + switch (m.ring) { + case "i1": + case "i64": + case "f64": + defaultCellValue = "0"; + break; + case "!graphalg.trop_i64": + case "!graphalg.trop_f64": + case "!graphalg.trop_max_i64": + defaultCellValue = "\\infty"; + break; + default: + defaultCellValue = ""; + break; } - thead.appendChild(headRow); - - - // Body - const tbody = document.createElement("tbody"); - // Create cells + let rows: string[][] = []; for (let r = 0; r < m.rows; r++) { - const tr = document.createElement("tr"); - const rowHead = document.createElement("td"); - // Make it look like a header cell - rowHead.style.textAlign = 'center'; - rowHead.style.fontWeight = 'bold'; - rowHead.textContent = r.toString(); - tr.appendChild(rowHead); + let cols: string[] = []; for (let c = 0; c < m.cols; c++) { - const td = document.createElement("td"); - td.style.textAlign = 'center'; - tr.appendChild(td); + cols.push(defaultCellValue); } - tbody.appendChild(tr); + rows.push(cols); } - // Fill non-zero cells for (let val of m.values) { - const row = tbody.childNodes[val.row]; - const cell = row.childNodes[val.col + 1]; - cell.textContent = val.val.toString(); + let renderVal = val.val.toString(); + if (m.ring == "i1") { + renderVal = val.val ? "1" : "0"; + } + + rows[val.row][val.col] = renderVal; } - table.append(thead, tbody); - return table; + const tex = + "\\begin{bmatrix}\n" + + rows.map((row) => row.join(" & ")).join("\\\\") + + "\n\\end{bmatrix}"; + + const katexCont = document.createElement("div"); + // TODO: We could generate MathML directly, skipping katex entirely. + katex.render(tex, katexCont, { output: "mathml" }); + return katexCont; } function buildTableCOO(m: GraphAlgMatrix): HTMLTableElement { @@ -637,3 +632,11 @@ for (let elem of Array.from(graphElems)) { elem.replaceWith(rendered); } + +// Initialize math views +const mathElems = document.getElementsByClassName("language-math"); +for (let elem of Array.from(mathElems)) { + const container = document.createElement("div"); + katex.render(elem.textContent, container, { output: "mathml" }); + elem.replaceWith(container); +} diff --git a/tutorial/index.md b/tutorial/index.md index dace0d1..fb416ec 100644 --- a/tutorial/index.md +++ b/tutorial/index.md @@ -166,12 +166,82 @@ In adjacency matrix representation, the same graph looks like this: 2, 3; ``` -$$ - \begin{bmatrix} - a & b \\ - c & d - \end{bmatrix} -$$ +In the matrix representation we can use linear algebra operations to explore the graph. +Want to find all reachable nodes from node 0? +We can do that with matrix multiplication. + +First, we create an initial vector with value 1 at position 0, and multiply that with the adjacency matrix to get all nodes that are reachable from node 0 in a single hop: + +```math +\begin{bmatrix} +1 & 0 & 0 & 0 \\ +\end{bmatrix} + +\cdot + +\begin{bmatrix} +0 & 1 & 1 & 0 \\ +0 & 0 & 0 & 1 \\ +0 & 0 & 0 & 1 \\ +0 & 0 & 0 & 0 \\ +\end{bmatrix} + += + +\begin{bmatrix} +0 & 1 & 1 & 0 +\end{bmatrix} +``` + +So nodes 1 and 2 are one hop away from node 0. How about two hops? + +```math +\begin{bmatrix} +1 & 0 & 0 & 0 \\ +\end{bmatrix} + +\cdot + +\begin{bmatrix} +0 & 1 & 1 & 0 \\ +0 & 0 & 0 & 1 \\ +0 & 0 & 0 & 1 \\ +0 & 0 & 0 & 0 \\ +\end{bmatrix} + +\cdot + +\begin{bmatrix} +0 & 1 & 1 & 0 \\ +0 & 0 & 0 & 1 \\ +0 & 0 & 0 & 1 \\ +0 & 0 & 0 & 0 \\ +\end{bmatrix} + += + +\begin{bmatrix} +0 & 1 & 1 & 0 +\end{bmatrix} + +\cdot + +\begin{bmatrix} +0 & 1 & 1 & 0 \\ +0 & 0 & 0 & 1 \\ +0 & 0 & 0 & 1 \\ +0 & 0 & 0 & 0 \\ +\end{bmatrix} + += + +\begin{bmatrix} +0 & 0 & 0 & 1 +\end{bmatrix} +``` + +So node 3 is also reachable. +All nodes in this graph are reachable from node 0! ## TODO ```graphalg @@ -191,5 +261,3 @@ This algorithm implements a [reachability analysis](https://en.wikipedia.org/wik It determines which nodes in the graph are connected through any number of edges (reachable) from any of the *source* vertices. - - From b0decb666ba8e3d068ba1e05921d8e5f6839e1c1 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Fri, 28 Nov 2025 14:43:58 +0000 Subject: [PATCH 07/12] Lots more tutorial. --- playground/editor.ts | 149 +++++++++++++++++++++++++++++++++++++++---- tutorial/index.md | 112 +++++++++++++++++++++++++++++--- 2 files changed, 238 insertions(+), 23 deletions(-) diff --git a/playground/editor.ts b/playground/editor.ts index 1e90246..ae0ea23 100644 --- a/playground/editor.ts +++ b/playground/editor.ts @@ -283,7 +283,7 @@ function parseMatrix(input: string): GraphAlgMatrix { } } -function buildTableMatrix(m: GraphAlgMatrix): HTMLElement { +function renderMatrixLatex(m: GraphAlgMatrix): HTMLElement { let defaultCellValue; switch (m.ring) { case "i1": @@ -331,7 +331,7 @@ function buildTableMatrix(m: GraphAlgMatrix): HTMLElement { return katexCont; } -function buildTableCOO(m: GraphAlgMatrix): HTMLTableElement { +function renderMatrixTable(m: GraphAlgMatrix): HTMLTableElement { // Create an output table. const table = document.createElement("table"); @@ -366,7 +366,7 @@ function buildTableCOO(m: GraphAlgMatrix): HTMLTableElement { return table; } -function buildVisGraph(m: GraphAlgMatrix): HTMLElement { +function renderMatrixVisGraph(m: GraphAlgMatrix): HTMLElement { if (m.rows != m.cols) { throw Error("buildVisGraph called with a non-square matrix"); } @@ -374,6 +374,7 @@ function buildVisGraph(m: GraphAlgMatrix): HTMLElement { const container = document.createElement("div"); container.style.width = "100%"; container.style.height = "300px"; + container.style.border = "1px solid black"; interface NodeItem { id: number; @@ -429,16 +430,117 @@ function buildVisGraph(m: GraphAlgMatrix): HTMLElement { return container; } -function buildMatrixVisualization(m: GraphAlgMatrix): HTMLElement { +function renderVectorAsNodeProperty( + vector: GraphAlgMatrix, + graph: GraphAlgMatrix): HTMLElement { + if (vector.rows != graph.rows + || graph.rows != graph.cols + || vector.cols != 1) { + console.warn("cannot render as node property due to incompatible dimensions, falling back to default output rendering"); + return renderMatrixAuto(vector); + } + + const container = document.createElement("div"); + container.style.width = "100%"; + container.style.height = "300px"; + container.style.border = "1px solid black"; + + interface NodeItem { + id: number; + label: string; + color?: string; + } + let nodes: NodeItem[] = []; + for (let r = 0; r < vector.rows; r++) { + nodes.push({ id: r, label: "Node " + r.toString() }); + } + + for (let val of vector.values) { + nodes[val.row].label = `Node ${val.row}\nvalue: ${val.val}`; + if (vector.ring == 'i1' && val.val) { + // A shade of red to complement default blue. + nodes[val.row].color = '#FB7E81'; + } + } + + const nodeDataSet = new DataSet(nodes); + + // create an array with edges + interface EdgeItem { + id: number; + from: number; + to: number; + label: string; + } + const edges = new DataSet(); + for (let val of graph.values) { + let label = val.val.toString(); + if (graph.ring == "i1" && val.val == true) { + label = ""; + } + + edges.add({ + id: edges.length, + from: val.row, + to: val.col, + label: label + }); + } + + // create a network + const data = { + nodes: nodes, + edges: edges, + }; + var options = { + layout: { + // Deterministic layout of graphs + randomSeed: 42 + }, + edges: { + arrows: { + to: { + enabled: true + } + } + } + }; + var network = new Network(container, data, options); + + return container; +} + +function renderMatrixAuto(m: GraphAlgMatrix): HTMLElement { if (m.rows == 1 && m.cols == 1) { // Simple scalar - return buildTableMatrix(m); + return renderMatrixLatex(m); } else if (m.rows == m.cols && m.rows < 20) { - return buildVisGraph(m); - } else if (m.rows < 50 && m.cols < 20) { - return buildTableMatrix(m); + return renderMatrixVisGraph(m); + } else if (m.rows < 50 && m.cols < 50) { + return renderMatrixLatex(m); } else { - return buildTableCOO(m); + return renderMatrixTable(m); + } +} + +enum MatrixRenderMode { + AUTO, + LATEX, + VIS_GRAPH, + TABLE, + VERTEX_PROPERTY, +} + +function renderMatrix(m: GraphAlgMatrix, mode: MatrixRenderMode) { + switch (mode) { + case MatrixRenderMode.LATEX: + return renderMatrixLatex(m); + case MatrixRenderMode.VIS_GRAPH: + return renderMatrixVisGraph(m); + case MatrixRenderMode.TABLE: + return renderMatrixTable(m); + default: + return renderMatrixAuto(m); } } @@ -452,6 +554,8 @@ class GraphAlgEditor { initialProgram: string; functionName?: string; arguments: GraphAlgMatrix[] = []; + renderMode: MatrixRenderMode = MatrixRenderMode.AUTO; + resultRenderMode: MatrixRenderMode = MatrixRenderMode.AUTO; editorView?: EditorView; @@ -501,7 +605,7 @@ class GraphAlgEditor { const argDetails = document.createElement("details"); const argSummary = document.createElement("summary"); argSummary.textContent = `Argument ${this.arguments.length} (${arg.ring} x ${arg.rows} x ${arg.cols})`; - const table = buildMatrixVisualization(arg); + const table = renderMatrix(arg, this.renderMode); argDetails.append(argSummary, table); this.argumentContainer.appendChild(argDetails); } @@ -525,6 +629,12 @@ for (let elem of Array.from(codeElems)) { editor.functionName = func; } + // NOTE: Need to configure this before we add arguments. + const defaultRender = elem.getAttribute('data-ga-render'); + if (defaultRender == 'latex') { + editor.renderMode = MatrixRenderMode.LATEX; + } + for (let i = 0; ; i++) { const arg = elem.getAttribute('data-ga-arg-' + i); if (!arg) { @@ -534,6 +644,13 @@ for (let elem of Array.from(codeElems)) { const parsed = parseMatrix(arg); editor.addArgument(parsed); } + + const resultRender = elem.getAttribute('data-ga-result-render'); + if (!resultRender) { + editor.resultRenderMode = editor.renderMode; + } if (resultRender == 'vertex-property') { + editor.resultRenderMode = MatrixRenderMode.VERTEX_PROPERTY; + } } // Replace the code snippet with the editor view. @@ -572,7 +689,11 @@ function run(editor: GraphAlgEditor, inst: PlaygroundInstance) { const result = inst.run(program, editor.functionName!!, editor.arguments); let resultElem; if (result.result) { - resultElem = buildMatrixVisualization(result.result); + if (editor.resultRenderMode == MatrixRenderMode.VERTEX_PROPERTY) { + resultElem = renderVectorAsNodeProperty(result.result, editor.arguments[0]); + } else { + resultElem = renderMatrix(result.result, editor.resultRenderMode); + } } else { resultElem = buildErrorNote(result.diagnostics); } @@ -623,11 +744,11 @@ for (let elem of Array.from(graphElems)) { let rendered: HTMLElement; if (mode == "vis") { - rendered = buildVisGraph(mat); + rendered = renderMatrixVisGraph(mat); } else if (mode == "coo") { - rendered = buildTableCOO(mat); + rendered = renderMatrixTable(mat); } else { - rendered = buildTableMatrix(mat); + rendered = renderMatrixLatex(mat); } elem.replaceWith(rendered); diff --git a/tutorial/index.md b/tutorial/index.md index fb416ec..f3b8b92 100644 --- a/tutorial/index.md +++ b/tutorial/index.md @@ -26,9 +26,12 @@ Try running it by pressing the **Run** button!. > [WebAssembly](https://webassembly.org/). > > You can modify and run examples as much as you want. +> The name of the function to execute appears in the *Run* button above the editor. +> To see the values of the parameters passed to the function, click on the *Argument* accordions below the editor to expand them. +> > If you write an invalid program, the editor will underline the part of your > program that is incorrect. Hover over the underlined code to see the error -> message, or click *Run* to print error messages to the output. +> message, or click *Run* to show the error messages below the editor. {: data-ga-func="AddOne" @@ -60,7 +63,7 @@ func (: , ...) -> { The `AddOne` uses the `int` type for both the parameter and the return type. This represents a signed, 64-bit integer (similar to `long` in Java and C++). -A function can have any number of parameter, but it must have **exactly one return value**. +A function can have any number of parameters, but it must have **exactly one return value**. You may be familiar with other languages that allow returning no value at all (commonly named `void` functions). In GraphAlg such a function would be pointless: GraphAlg programs cannot write to files, make HTTP requests, or perform any other action that has [*side effects*](https://en.wikipedia.org/wiki/Side_effect_(computer_science)). @@ -123,6 +126,9 @@ This is okay because the loop scope is defined *inside* of the function scope. What is not allowed however is to first define a variable in a nested scope, and then refer to it from the outer scope. For an example, consider the (invalid) program below. +{: + data-ga-func="NotValid" +} ```graphalg func NotValid() -> int { a = int(0); @@ -138,6 +144,11 @@ func NotValid() -> int { Variable `b` is only defined inside of the loop, not in the outer function scope. The statement `return b;` is invalid, as `b` is undefined in this context. +{: .note-title } +> Experiment +> +> Fix the compiler error by defining `b` at the function scope before entering the loop. + ## Bringing Linear Algebra Into the Mix Our example programs so far have only used the `int` type so far. There are also `bool` (`false` or `true`) and `real` (floating-point numbers, 64-bit). @@ -243,21 +254,104 @@ So nodes 1 and 2 are one hop away from node 0. How about two hops? So node 3 is also reachable. All nodes in this graph are reachable from node 0! -## TODO +## A First Graph Algorithm +Let us now codify this strategy for finding reachable nodes in the graph by writing a GraphAlg program. +If you run the program below (click the *Run 'Reachability'* button), you will see that it finds four nodes that are reachable from node 0. +Two additional nodes 4 and 5 are not reachable from node 0. + +{: + data-ga-func="Reachability" + data-ga-arg-0=" +6, 6, i1; +0, 1; +0, 2; +1, 3; +2, 3; +4, 5;" + data-ga-arg-1="6, 1, i1; 0, 0;" + data-ga-result-render="vertex-property" +} ```graphalg func Reachability( graph: Matrix, - source: Vector) -> Matrix { - v = source; + source: Vector) -> Vector { + reach = source; for i in graph.nrows { - v += v * graph; + reach += reach * graph; } - return v; + return reach; +} +``` + +The reachability algorithm uses a few new GraphAlg features that we have not encountered so far: +- More complex types `Matrix<..>` and `Vector<..>` (line 2-3) +- Iterating over the dimensions of a matrix (line 5) +- Accumulating results using `+=` (line 6) +- Matrix multiplication using `*` (line 6) + +## Matrix Types +A `Matrix` type encodes three properties: +1. The number of rows. + This example uses a *symbolic name* `s` rather than a concrete value, so that we can apply the algorithm to matrices of any size. +2. The number of columns. + In the example this is also `s`, so parameter `graph` is a square matrix. +3. The *semiring*. + You can see this as the type of elements in the matrix, for example `int`. + We will give a more precise definition further on in the tutorial. + +`Vector` is an alias for `Matrix`, a column vector. + +{: .note-title} +> A note on scalar types +> +> Even the simple scalar types you have seen before, such as `int`, are matrices! +> `int` is a shorthand for `Matrix<1, 1, int>`. + +## Loops over Matrix Dimensions +We have previously seen loops over an integer range such as `for i in int(0):int(10) {..}`. +In graph algorithms it is very common to bound loops based on the number of vertices in the graph, so GraphAlg provides shorthand syntax `for i in graph.nrows {..}`. +It is equivalent to `for i in int(0):graph.nrows {..}`. + +{: .note-title} +> Common Restrictions on loop ranges +> +> The playground allows you to define loop range bounds based on arbitrarily complex expressions, but it is common for implementations of GraphAlg to be more restrictive. Two types of bounds are supported on all GraphAlg implementations: +> 1. Compile-time constant loop bounds (`int(0):(int(10) + int(100)`). +> 2. Loops over matrix dimensions (`M.nrows`). +> +> Support for e.g. bounds based on function parameters is implementation-dependent. + +## Accumulating Results +The `+=` operator is used to combine values of one matrix with a previously defined variable in element-wise fashion. +How entries are combined depends on the semiring. +For `int` and `real` the two elements at the same position are summed, while for `bool` logical OR is used. + +{: + data-ga-func="Accumulate" + data-ga-render="latex" + data-ga-arg-0=" +2, 2, i64; +0, 0, 1; +0, 1, 2; +1, 0, 3; +1, 1, 4;" + data-ga-arg-1=" +2, 2, i64; +0, 0, 5; +0, 1, 6; +1, 0, 7; +1, 1, 8;" +} +```graphalg +func Accumulate( + a: Matrix, + b: Matrix) -> Matrix { + a += b; + return a; } ``` -This algorithm implements a [reachability analysis](https://en.wikipedia.org/wiki/Reachability). -It determines which nodes in the graph are connected through any number of edges (reachable) from any of the *source* vertices. +## Matrix Multiplication From ae7864a5f78f3a2feb6ee1d4f48aadd43f53f5e5 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Fri, 28 Nov 2025 17:15:03 +0000 Subject: [PATCH 08/12] Almost done with the tutorial. --- playground/editor.ts | 54 ++++-- spec/core/index.md | 2 +- spec/operations.md | 9 + spec/typing.md | 2 +- tutorial/index.md | 409 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 456 insertions(+), 20 deletions(-) create mode 100644 spec/operations.md diff --git a/playground/editor.ts b/playground/editor.ts index ae0ea23..780b0a4 100644 --- a/playground/editor.ts +++ b/playground/editor.ts @@ -7,6 +7,7 @@ import { GraphAlg } from "codemirror-lang-graphalg" import { loadPlaygroundWasm } from "./binding.mjs" import { DataSet, Network } from "vis-network/standalone" import katex from "katex" +import renderMathInElement from "katex/contrib/auto-render" // Load and register all webassembly bindings let playgroundWasmBindings = loadPlaygroundWasm(); @@ -283,6 +284,18 @@ function parseMatrix(input: string): GraphAlgMatrix { } } +function renderValue(entry: boolean | bigint | number, ring: string) { + switch (ring) { + case 'f64': + case '!graphalg.trop_f64': + return (entry as number).toFixed(3); + case 'i1': + return (entry as boolean) ? "1" : "0"; + default: + return entry.toString(); + } +} + function renderMatrixLatex(m: GraphAlgMatrix): HTMLElement { let defaultCellValue; switch (m.ring) { @@ -312,12 +325,7 @@ function renderMatrixLatex(m: GraphAlgMatrix): HTMLElement { } for (let val of m.values) { - let renderVal = val.val.toString(); - if (m.ring == "i1") { - renderVal = val.val ? "1" : "0"; - } - - rows[val.row][val.col] = renderVal; + rows[val.row][val.col] = renderValue(val.val, m.ring); } const tex = @@ -357,7 +365,7 @@ function renderMatrixTable(m: GraphAlgMatrix): HTMLTableElement { tdCol.textContent = val.col.toString(); const tdVal = document.createElement("td"); - tdVal.textContent = val.val.toString(); + tdVal.textContent = renderValue(val.val, m.ring); tr.append(tdRow, tdCol, tdVal); tbody.appendChild(tr); } @@ -368,7 +376,7 @@ function renderMatrixTable(m: GraphAlgMatrix): HTMLTableElement { function renderMatrixVisGraph(m: GraphAlgMatrix): HTMLElement { if (m.rows != m.cols) { - throw Error("buildVisGraph called with a non-square matrix"); + throw Error("renderMatrixVisGraph called with a non-square matrix"); } const container = document.createElement("div"); @@ -394,7 +402,7 @@ function renderMatrixVisGraph(m: GraphAlgMatrix): HTMLElement { } const edges = new DataSet(); for (let val of m.values) { - let label = val.val.toString(); + let label = renderValue(val.val, m.ring); if (m.ring == "i1" && val.val == true) { label = ""; } @@ -452,11 +460,18 @@ function renderVectorAsNodeProperty( } let nodes: NodeItem[] = []; for (let r = 0; r < vector.rows; r++) { - nodes.push({ id: r, label: "Node " + r.toString() }); + let label = "Node " + r.toString(); + if (vector.ring == '!graphalg.trop_f64' + || vector.ring == '!graphalg.trop_i64') { + label = `Node ${r}\nvalue: ∞`; + } + + nodes.push({ id: r, label: label }); } for (let val of vector.values) { - nodes[val.row].label = `Node ${val.row}\nvalue: ${val.val}`; + const renderVal = renderValue(val.val, vector.ring); + nodes[val.row].label = `Node ${val.row}\nvalue: ${renderVal}`; if (vector.ring == 'i1' && val.val) { // A shade of red to complement default blue. nodes[val.row].color = '#FB7E81'; @@ -474,16 +489,11 @@ function renderVectorAsNodeProperty( } const edges = new DataSet(); for (let val of graph.values) { - let label = val.val.toString(); - if (graph.ring == "i1" && val.val == true) { - label = ""; - } - edges.add({ id: edges.length, from: val.row, to: val.col, - label: label + label: renderValue(val.val, graph.ring) }); } @@ -758,6 +768,14 @@ for (let elem of Array.from(graphElems)) { const mathElems = document.getElementsByClassName("language-math"); for (let elem of Array.from(mathElems)) { const container = document.createElement("div"); - katex.render(elem.textContent, container, { output: "mathml" }); + katex.render(elem.textContent, container, { output: "mathml", displayMode: true }); elem.replaceWith(container); } + +// Initialize inline math. +renderMathInElement(document.body, { + output: 'mathml', + delimiters: [ + { left: "$", right: "$", display: false }, + ] +}) diff --git a/spec/core/index.md b/spec/core/index.md index d5e4a54..a199016 100644 --- a/spec/core/index.md +++ b/spec/core/index.md @@ -2,7 +2,7 @@ title: Core Language layout: page parent: Language Specification -nav_order: 3 +nav_order: 4 --- # GraphAlg Core diff --git a/spec/operations.md b/spec/operations.md new file mode 100644 index 0000000..81e1352 --- /dev/null +++ b/spec/operations.md @@ -0,0 +1,9 @@ +--- +title: Operations +layout: page +parent: Language Specification +nav_order: 2 +--- + +# GraphAlg Operations +TODO: Describe all operations in the GraphAlg language. diff --git a/spec/typing.md b/spec/typing.md index b2585db..d4d0b66 100644 --- a/spec/typing.md +++ b/spec/typing.md @@ -2,7 +2,7 @@ title: Type System layout: page parent: Language Specification -nav_order: 2 +nav_order: 3 --- # Type System diff --git a/tutorial/index.md b/tutorial/index.md index f3b8b92..a6c92dc 100644 --- a/tutorial/index.md +++ b/tutorial/index.md @@ -347,11 +347,420 @@ For `int` and `real` the two elements at the same position are summed, while for func Accumulate( a: Matrix, b: Matrix) -> Matrix { + // EXPERIMENT: Try the equivalent statement: + // a = a (.+) b; a += b; return a; } ``` ## Matrix Multiplication +Matrix multiplication is a key building block for many graph algorithms. +As any linear algebra textbook will tell you, matrices can only be multiplied if the number of columns on the left-hand side matches the number of rows of the right-hand side. +The GraphAlg compiler will check this for you automatically, based on dimension symbols in the function parameters. + +```graphalg +func InvalidDimensions( + a: Matrix, + b: Matrix) -> Matrix { + return a * b; +} +``` + +One pattern that is very common, but does not strictly adhere to these restrictions, is *vector-matrix multiplication*. +Consider two variables: +- `a: Vector` +- `b: Matrix` + +We have discussed before how `Vector` is really just `Matrix`, so we have in fact: +- `a: Matrix` +- `b: Matrix` + +Then the expression `a * b` is not valid, because `a` has only `1` column while `b` has `s` rows. +For the multiplication to be valid, we must transpose `a` first. If we want the results to have the same shape as `a`, then we must also transpose the result again, and we end up with `(a.T * b).T` (`.T` computes a transpose in GraphAlg). +Because this pattern is very common, GraphAlg allows you to write `a * b`, and taking care of the transpose operations automatically. +You can try this yourself by modifying the example below. + +{: + data-ga-func="VectorMatrixMul" + data-ga-render="latex" + data-ga-arg-0=" +2, 1, i64; +0, 0, 1; +1, 0, 2;" + data-ga-arg-1=" +2, 2, i64; +0, 0, 3; +0, 1, 4; +1, 0, 5; +1, 1, 6;" +} +```graphalg +func VectorMatrixMul( + a: Vector, + b: Matrix) -> Vector { + return (a.T * b).T; +} +``` + +## Lord of the Semirings +Until now, we have referred to the [*semiring*](https://en.wikipedia.org/wiki/Semiring) of a matrix as the type of the elements. +While the semiring indeed defines the element type, it also defines additional properties of the matrix: +- An *addition operator* $\oplus$, with an identity element called $0$. +- A *multiplication operator* $\otimes$, with an identity element called $1$. + +Matrix operations such as the accumulation and matrix multiplication we have seen before use the addition and multiplication operators of the semiring. +Semirings `int` and `real` use the natural definitions of the addition and multiplication operators. +For `bool` the addition and multiplication operators are logical OR and logical AND, respectively. + +GraphAlg also includes a more exotic family of semirings called [*tropical semirings*](https://en.wikipedia.org/wiki/Tropical_semiring). +In a tropical semiring, the addition and multiplication operator are defined as: + +```math +\begin{align} + a \oplus b &= \min(a, b) \\ + a \otimes b &= a + b +\end{align} +``` + +At this point you may wonder, what is all this exotic math good for? +To answer that question, consider the algorithm below. + +{: + data-ga-func="SSSP" + data-ga-arg-0=" + 10, 10, !graphalg.trop_f64; + 0, 1, 0.5; + 0, 2, 5.0; + 0, 3, 5.0; + 1, 4, 0.5; + 2, 3, 2.0; + 4, 5, 0.5; + 5, 2, 0.5; + 5, 9, 23.0; + 6, 0, 1.0; + 6, 7, 3.2; + 7, 9, 0.2; + 8, 9, 0.1; + 9, 6, 8.0;" + data-ga-arg-1=" + 10, 1, !graphalg.trop_f64; + 0, 0, 0;" + data-ga-result-render="vertex-property" +} +```graphalg +func SSSP( + graph: Matrix, + source: Vector) -> Vector { + dist = source; + for i in graph.nrows { + dist += dist * graph; + } + return dist; +} +``` + +Careful comparison with the earlier `Reachability` algorithm reveals that the algorithms are structurally identical: Repeated matrix multiplication and accumulation in a loop. +The main difference is the semiring used (`trop_real` instead of `bool`). +`trop_real` is the name for the tropical semiring over real numbers, the tropical equivalent of the `real` semiring. +By using floating-point values rather than booleans, we can record not just that a node is connected, but also keep track of the distance from the source. +The use of $+$ for multiplication means we add the cost of edges the current distance from the source, whereas using $\min$ for addition ensures that we keep only the shortest distance. + +TODO: Links to learning more about tropical semirings. + +## The Real Deal: PageRank +TODO: Write about PageRank, and where to find docs on all supported operations. + +Consult the [list of all operations](../spec/operations) available in GraphAlg. + +{: + data-ga-func="PR" + data-ga-arg-0=" + 50, 50, i1; + 0, 18; + 0, 20; + 0, 21; + 0, 26; + 0, 30; + 0, 36; + 0, 44; + 0, 47; + 1, 2; + 1, 19; + 1, 38; + 1, 45; + 2, 5; + 2, 9; + 2, 31; + 2, 40; + 2, 44; + 3, 14; + 4, 14; + 4, 15; + 4, 17; + 4, 27; + 4, 46; + 5, 48; + 6, 5; + 6, 26; + 6, 42; + 6, 45; + 7, 4; + 7, 20; + 7, 28; + 7, 29; + 7, 31; + 7, 42; + 8, 15; + 8, 17; + 8, 20; + 8, 27; + 8, 29; + 8, 34; + 8, 39; + 9, 8; + 9, 12; + 9, 27; + 9, 28; + 9, 32; + 10, 2; + 10, 38; + 11, 46; + 11, 49; + 12, 6; + 12, 11; + 12, 16; + 12, 31; + 12, 47; + 13, 3; + 13, 19; + 13, 20; + 13, 34; + 13, 37; + 13, 39; + 14, 7; + 14, 23; + 14, 30; + 14, 34; + 14, 43; + 16, 4; + 16, 8; + 16, 10; + 16, 15; + 16, 25; + 16, 36; + 17, 0; + 17, 11; + 17, 27; + 17, 29; + 17, 43; + 17, 44; + 17, 46; + 17, 49; + 18, 9; + 18, 10; + 18, 12; + 18, 26; + 18, 37; + 19, 14; + 19, 24; + 20, 21; + 20, 26; + 20, 30; + 20, 31; + 20, 39; + 21, 18; + 21, 25; + 21, 26; + 21, 30; + 22, 21; + 22, 34; + 22, 35; + 22, 37; + 22, 39; + 22, 45; + 22, 46; + 23, 8; + 23, 12; + 23, 14; + 23, 33; + 23, 35; + 23, 49; + 24, 7; + 24, 23; + 24, 29; + 24, 33; + 24, 40; + 24, 46; + 25, 6; + 25, 30; + 25, 36; + 25, 39; + 25, 43; + 25, 46; + 26, 30; + 26, 32; + 26, 42; + 27, 7; + 27, 31; + 27, 41; + 27, 44; + 28, 0; + 28, 1; + 28, 11; + 28, 13; + 28, 15; + 28, 18; + 28, 19; + 28, 35; + 29, 8; + 29, 23; + 29, 33; + 29, 43; + 30, 10; + 30, 16; + 30, 31; + 30, 38; + 30, 45; + 30, 46; + 31, 1; + 31, 27; + 31, 28; + 31, 29; + 31, 30; + 32, 6; + 32, 7; + 32, 8; + 32, 9; + 32, 31; + 32, 33; + 32, 36; + 33, 25; + 33, 47; + 34, 2; + 34, 9; + 34, 16; + 34, 23; + 34, 25; + 34, 27; + 34, 32; + 34, 40; + 35, 19; + 35, 20; + 35, 28; + 35, 31; + 35, 45; + 36, 0; + 36, 4; + 36, 8; + 36, 12; + 36, 22; + 36, 23; + 37, 1; + 37, 21; + 37, 49; + 38, 5; + 38, 7; + 38, 19; + 38, 27; + 38, 29; + 38, 46; + 38, 47; + 39, 4; + 39, 6; + 39, 7; + 39, 10; + 39, 32; + 39, 33; + 39, 36; + 39, 48; + 40, 23; + 40, 42; + 42, 0; + 42, 1; + 42, 10; + 42, 14; + 42, 16; + 42, 28; + 42, 37; + 42, 46; + 43, 10; + 43, 12; + 43, 14; + 44, 4; + 44, 10; + 44, 11; + 44, 20; + 44, 23; + 45, 22; + 45, 23; + 45, 25; + 45, 30; + 45, 35; + 45, 40; + 46, 7; + 46, 13; + 46, 15; + 46, 27; + 46, 28; + 46, 33; + 46, 34; + 46, 39; + 46, 41; + 46, 45; + 46, 49; + 47, 7; + 47, 18; + 47, 29; + 47, 34; + 47, 37; + 47, 42; + 47, 49; + 48, 6; + 48, 7; + 48, 16; + 48, 17; + 49, 3; + 49, 27; + 49, 46;" + data-ga-result-render="vertex-property" +} +```graphalg +func withDamping(degree:int, damping:real) -> real { + return cast(degree) / damping; +} + +func PR(graph: Matrix) -> Vector { + damping = real(0.85); + iterations = int(10); + n = graph.nrows; + teleport = (real(1.0) - damping) / cast(n); + rdiff = real(1.0); + + d_out = reduceRows(cast(graph)); + + d = apply(withDamping, d_out, damping); + + connected = reduceRows(graph); + sinks = Vector(n); + sinks[:] = bool(true); + + pr = Vector(n); + pr[:] = real(1.0) / cast(n); + + for i in int(0):iterations { + sink_pr = Vector(n); + sink_pr = pr; + redist = (damping / cast(n)) * reduce(sink_pr); + + w = pr (./) d; + + pr[:] = teleport + redist; + pr += cast(graph).T * w; + } + + return pr; +} +``` From c10809f3f1063c8ddf95a40e506bf208ebdf99b6 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 1 Dec 2025 10:10:00 +0000 Subject: [PATCH 09/12] Structure for PageRank example. --- README.md | 4 +++- playground/editor.ts | 4 +++- playground/index.md | 5 +++++ tutorial/index.md | 39 +++++++++++++++++++++++++++++++++------ 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 9a8e8ac..a831155 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # GraphAlg: A Embeddable Language for Writing Graph Algorithm in Linear Algebra This repository contains code related to the GraphAlg language: - `codemirror-lang-graphalg`: Language Support for Codemirror -- `compiler/`: The GraphAlg compiler +- `compiler/`: The GraphAlg compiler. + Includes the parser, lowering to GraphAlg Core, high-level optimizations, and a reference backend for executing algorithms. - `playground/`: The GraphAlg online playground - `spec/`: The GraphAlg Language Specification +- `tutorial/`: A tutorial for new GraphAlg users ## Building This assumes you are using the provided [devcontainer](https://containers.dev/) development environment. diff --git a/playground/editor.ts b/playground/editor.ts index 780b0a4..7a898fc 100644 --- a/playground/editor.ts +++ b/playground/editor.ts @@ -526,7 +526,7 @@ function renderMatrixAuto(m: GraphAlgMatrix): HTMLElement { return renderMatrixLatex(m); } else if (m.rows == m.cols && m.rows < 20) { return renderMatrixVisGraph(m); - } else if (m.rows < 50 && m.cols < 50) { + } else if (m.rows < 20 && m.cols < 20) { return renderMatrixLatex(m); } else { return renderMatrixTable(m); @@ -660,6 +660,8 @@ for (let elem of Array.from(codeElems)) { editor.resultRenderMode = editor.renderMode; } if (resultRender == 'vertex-property') { editor.resultRenderMode = MatrixRenderMode.VERTEX_PROPERTY; + } if (resultRender == 'latex') { + editor.resultRenderMode = MatrixRenderMode.LATEX; } } diff --git a/playground/index.md b/playground/index.md index 6887023..72d0ebf 100644 --- a/playground/index.md +++ b/playground/index.md @@ -38,6 +38,7 @@ Compile and execute GraphAlg programs in your browser! data-ga-arg-1=" 10, 1, i1; 0, 0;" + data-ga-result-render="vertex-property" } ```graphalg func setDepth(b:bool, iter:int) -> int { @@ -91,6 +92,7 @@ func BFS( 6, 5; 6, 7; 7, 5;" + data-ga-result-render="latex" } ```graphalg func isMax(v: int, max: trop_max_int) -> bool { @@ -387,6 +389,7 @@ func CDLP(graph: Matrix) -> Matrix { 49, 3; 49, 27; 49, 46;" + data-ga-result-render="vertex-property" } ```graphalg func withDamping(degree:int, damping:real) -> real { @@ -448,6 +451,7 @@ func PR(graph: Matrix) -> Vector { data-ga-arg-1=" 10, 1, i1; 0, 0;" + data-ga-result-render="vertex-property" } ```graphalg func SSSP( @@ -477,6 +481,7 @@ func SSSP( 5, 7; 6, 5; 8, 2;" + data-ga-result-render="latex" } ```graphalg func WCC(graph: Matrix) -> Matrix { diff --git a/tutorial/index.md b/tutorial/index.md index a6c92dc..ab3f7e6 100644 --- a/tutorial/index.md +++ b/tutorial/index.md @@ -21,11 +21,13 @@ To follow along, all you need are: Let us start with an example program to introduce key concepts of the GraphAlg language. Try running it by pressing the **Run** button!. -{: .note } +{: .note-title } +> Running example programs +> > The examples shown in this tutorial run inside your own browser using > [WebAssembly](https://webassembly.org/). -> > You can modify and run examples as much as you want. +> > The name of the function to execute appears in the *Run* button above the editor. > To see the values of the parameters passed to the function, click on the *Argument* accordions below the editor to expand them. > @@ -466,12 +468,20 @@ The main difference is the semiring used (`trop_real` instead of `bool`). By using floating-point values rather than booleans, we can record not just that a node is connected, but also keep track of the distance from the source. The use of $+$ for multiplication means we add the cost of edges the current distance from the source, whereas using $\min$ for addition ensures that we keep only the shortest distance. -TODO: Links to learning more about tropical semirings. +If you are interested to learn more about the use of semirings (and linear algebra in general) in the context of graph algorithms, we recommend the book [*Graph Algorithms in the Language of Linear Algebra*](https://epubs.siam.org/doi/book/10.1137/1.9780898719918) by Jeremy Kepner and John Gilbert. +We can also recommend [various resources](https://github.com/GraphBLAS/GraphBLAS-Pointers) related to [GraphBLAS](https://graphblas.org/), an API that provides building blocks for graph algorithms also based on linear algebra. +The more conceptual of those resources are also applicable to GraphAlg. -## The Real Deal: PageRank -TODO: Write about PageRank, and where to find docs on all supported operations. +{: .note-title } +> GraphBLAS vs. GraphAlg +> +> GraphBLAS defines a C library with sparse linear algebra routines, so it operates at a lower abstraction level than GraphAlg. +> Operations are similar, but there are subtle and important differences between the two. +> Work is in progress to build a GraphBLAS target for GraphAlg, which would allow running GraphAlg programs using a runtime based on GraphBLAS. +> If you want to follow progress on the GraphBLAS integration, you can follow the [GraphAlg Repository on GitHub](https://github.com/wildarch/graphalg). -Consult the [list of all operations](../spec/operations) available in GraphAlg. +## The Real Deal: PageRank +TODO: What is PageRank? {: data-ga-func="PR" @@ -763,4 +773,21 @@ func PR(graph: Matrix) -> Vector { } ``` +New ops: +- cast +- reduceRows +- apply +- M[:] = c +- A = B +- reduce +- (.f) +- M.T + +TODO: Where to find docs on additional operations + +Consult the [list of all operations](../spec/operations) available in GraphAlg. + +TODO: More example programs at the playground. +Play around with more algorithms there. + From 59c97b886d800de95dac24673682b8093c0b8e2b Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 1 Dec 2025 13:28:22 +0000 Subject: [PATCH 10/12] Complete first version of the tutorial. --- integration/available.md | 21 ++ integration/index.md | 11 ++ integration/new_integration.md | 39 ++++ tutorial/index.md | 346 ++++++++------------------------- 4 files changed, 149 insertions(+), 268 deletions(-) create mode 100644 integration/available.md create mode 100644 integration/index.md create mode 100644 integration/new_integration.md diff --git a/integration/available.md b/integration/available.md new file mode 100644 index 0000000..d0ffbb2 --- /dev/null +++ b/integration/available.md @@ -0,0 +1,21 @@ +--- +title: Available Integrations +layout: page +parent: Integrating +nav_order: 1 +--- + +# Available GraphAlg Integrations +The following systems allow running GraphAlg programs. + +## AvantGraph +[AvantGraph](https://avantgraph.io/) is a Graph Data Management System developed by the [TU Eindhoven Database group](https://www.tue.nl/en/research/research-groups/data-science/data-and-artificial-intelligence/database-group) that also develops GraphAlg. +GraphAlg algorithms can be embedded in Cypher queries. +Queries and algorithms are optimized and executed together in a unified query pipeline, allowing for optimizations that cross the boundary between query and algorithm. + +## GraphAlg Playground +The [GraphAlg Playground](../playground) is an online platfrom for experimenting with GraphAlg. +It provides: +- An interactive editor with syntax highlighting and integrated error diagnostics +- A fully-featured GraphAlg compiler, running locally in the browser +- a WebAssembly backend to run GraphAlg programs in the browser diff --git a/integration/index.md b/integration/index.md new file mode 100644 index 0000000..e9508ab --- /dev/null +++ b/integration/index.md @@ -0,0 +1,11 @@ +--- +title: Integrating +layout: page +nav_order: 5 +--- + +# Integrating GraphAlg +GraphAlg is designed to be integrated into existing systems, particularly Database Management Systems. +If you are a (prospective) GraphAlg user looking for a way to run GraphAlg programs, see the [available GraphAlg integrations](./available). + +Are you a system developer and you want an easy way to let users run custom algorithms in your system, see our [guide to integrating GraphAlg in a new system](./new_integration). diff --git a/integration/new_integration.md b/integration/new_integration.md new file mode 100644 index 0000000..6d26f64 --- /dev/null +++ b/integration/new_integration.md @@ -0,0 +1,39 @@ +--- +title: Developing a New Integration +layout: page +parent: Integrating +nav_order: 2 +--- + +# Integrating GraphAlg Into new Systems +GraphAlg is specifically designed to be integrated into existing systems. +The GraphAlg [compiler](https://github.com/wildarch/graphalg/tree/main/compiler) source code is freely available under a permissive license. +The compiler can be integrated into other systems as a C++ library. +Below we describe the three main integration points you can use depending on the properties of the target system. + +## Integrating The Reference Backend +To minimize the required porting effort you can use the full compiler including the reference backend. +An example of this approach is the [GraphAlg Playground](https://github.com/wildarch/graphalg/tree/main/playground) (see the [C++ component](https://github.com/wildarch/graphalg/tree/main/playground/cpp) in particular). +An important caveat is that the reference is only designed to handle very small example-sized graphs. +It will be slow and use a lot of memory if you try to use it with a larger graph (>100 nodes or edges). + +## Integrating From Relational Algebra +If your system uses a relational algebra representation internally, or something akin to it, you can leverage the [conversion to relational algebra](../spec/core/relalg). +If your system supports common arithmetic operations and aggregator functions, you only need to implement suitable a loop operator. +All other GraphAlg operations can be converted into standard relational algebra operations. + +{: .note-title } +> Example integration +> +> This approach is used by [AvantGraph](https://avantgraph.io/), but source code for this integration is not publicly available (yet). +> When this code becomes freely available, or if another integration using the same approach becomes available, we will link to that here. + +## Integrating From GraphAlg Core +The [Core language](../spec/core) has only a small number of high-level [operations](../spec/core/operations) that need to be implemented. +This may be a more suitable integration point if your system does not target relational algebra. +To do this you should use the provided [parser]() and run the [pipeline](https://github.com/wildarch/graphalg/blob/main/compiler/src/graphalg/GraphAlgToCorePipeline.cpp) to lower to GraphAlg Core. + +{: .note-title } +> Example integration +> +> We do not currently have an example integration for this approach, although a GraphBLAS is planned and would use this strategy. diff --git a/tutorial/index.md b/tutorial/index.md index ab3f7e6..b5d0d0f 100644 --- a/tutorial/index.md +++ b/tutorial/index.md @@ -63,7 +63,7 @@ func (: , ...) -> { } ``` -The `AddOne` uses the `int` type for both the parameter and the return type. +The `AddOne` function uses the `int` type for both the parameter and the return type. This represents a signed, 64-bit integer (similar to `long` in Java and C++). A function can have any number of parameters, but it must have **exactly one return value**. You may be familiar with other languages that allow returning no value at all (commonly named `void` functions). @@ -312,7 +312,7 @@ A `Matrix` type encodes three properties: ## Loops over Matrix Dimensions We have previously seen loops over an integer range such as `for i in int(0):int(10) {..}`. -In graph algorithms it is very common to bound loops based on the number of vertices in the graph, so GraphAlg provides shorthand syntax `for i in graph.nrows {..}`. +In graph algorithms it is very common to bound loops based on the number of nodes in the graph, so GraphAlg provides shorthand syntax `for i in graph.nrows {..}`. It is equivalent to `for i in int(0):graph.nrows {..}`. {: .note-title} @@ -468,7 +468,7 @@ The main difference is the semiring used (`trop_real` instead of `bool`). By using floating-point values rather than booleans, we can record not just that a node is connected, but also keep track of the distance from the source. The use of $+$ for multiplication means we add the cost of edges the current distance from the source, whereas using $\min$ for addition ensures that we keep only the shortest distance. -If you are interested to learn more about the use of semirings (and linear algebra in general) in the context of graph algorithms, we recommend the book [*Graph Algorithms in the Language of Linear Algebra*](https://epubs.siam.org/doi/book/10.1137/1.9780898719918) by Jeremy Kepner and John Gilbert. +If you are interested to learn more about the use of semirings (and linear algebra more broadly) in the context of graph algorithms, we recommend the book [*Graph Algorithms in the Language of Linear Algebra*](https://epubs.siam.org/doi/book/10.1137/1.9780898719918) by Jeremy Kepner and John Gilbert. We can also recommend [various resources](https://github.com/GraphBLAS/GraphBLAS-Pointers) related to [GraphBLAS](https://graphblas.org/), an API that provides building blocks for graph algorithms also based on linear algebra. The more conceptual of those resources are also applicable to GraphAlg. @@ -481,258 +481,32 @@ The more conceptual of those resources are also applicable to GraphAlg. > If you want to follow progress on the GraphBLAS integration, you can follow the [GraphAlg Repository on GitHub](https://github.com/wildarch/graphalg). ## The Real Deal: PageRank -TODO: What is PageRank? +Let us now move to a more complex algorithm. +[PageRank](http://ilpubs.stanford.edu:8090/422/1/1999-66.pdf) is a well-known and widely used algorithm for computing the *importance* of nodes in a graph. +It was made famous by Google, who used it to the measure the importance of websites in the graph that is the World Wide Web. +The algorithm is still in wide use today, for example to [analyze the influence of scientific publications](https://graph.openaire.eu/docs/graph-production-workflow/indicators-ingestion/impact-indicators/#pagerank-pr--influence). +Below you can find an implementation of PageRank in GraphAlg. {: data-ga-func="PR" data-ga-arg-0=" - 50, 50, i1; - 0, 18; - 0, 20; - 0, 21; - 0, 26; - 0, 30; - 0, 36; - 0, 44; - 0, 47; + 11, 11, i1; 1, 2; - 1, 19; - 1, 38; - 1, 45; - 2, 5; - 2, 9; - 2, 31; - 2, 40; - 2, 44; - 3, 14; - 4, 14; - 4, 15; - 4, 17; - 4, 27; - 4, 46; - 5, 48; - 6, 5; - 6, 26; - 6, 42; - 6, 45; + 2, 1; + 3, 0; + 3, 1; + 4, 1; + 4, 3; + 5, 1; + 5, 4; + 6, 1; + 6, 4; + 7, 1; 7, 4; - 7, 20; - 7, 28; - 7, 29; - 7, 31; - 7, 42; - 8, 15; - 8, 17; - 8, 20; - 8, 27; - 8, 29; - 8, 34; - 8, 39; - 9, 8; - 9, 12; - 9, 27; - 9, 28; - 9, 32; - 10, 2; - 10, 38; - 11, 46; - 11, 49; - 12, 6; - 12, 11; - 12, 16; - 12, 31; - 12, 47; - 13, 3; - 13, 19; - 13, 20; - 13, 34; - 13, 37; - 13, 39; - 14, 7; - 14, 23; - 14, 30; - 14, 34; - 14, 43; - 16, 4; - 16, 8; - 16, 10; - 16, 15; - 16, 25; - 16, 36; - 17, 0; - 17, 11; - 17, 27; - 17, 29; - 17, 43; - 17, 44; - 17, 46; - 17, 49; - 18, 9; - 18, 10; - 18, 12; - 18, 26; - 18, 37; - 19, 14; - 19, 24; - 20, 21; - 20, 26; - 20, 30; - 20, 31; - 20, 39; - 21, 18; - 21, 25; - 21, 26; - 21, 30; - 22, 21; - 22, 34; - 22, 35; - 22, 37; - 22, 39; - 22, 45; - 22, 46; - 23, 8; - 23, 12; - 23, 14; - 23, 33; - 23, 35; - 23, 49; - 24, 7; - 24, 23; - 24, 29; - 24, 33; - 24, 40; - 24, 46; - 25, 6; - 25, 30; - 25, 36; - 25, 39; - 25, 43; - 25, 46; - 26, 30; - 26, 32; - 26, 42; - 27, 7; - 27, 31; - 27, 41; - 27, 44; - 28, 0; - 28, 1; - 28, 11; - 28, 13; - 28, 15; - 28, 18; - 28, 19; - 28, 35; - 29, 8; - 29, 23; - 29, 33; - 29, 43; - 30, 10; - 30, 16; - 30, 31; - 30, 38; - 30, 45; - 30, 46; - 31, 1; - 31, 27; - 31, 28; - 31, 29; - 31, 30; - 32, 6; - 32, 7; - 32, 8; - 32, 9; - 32, 31; - 32, 33; - 32, 36; - 33, 25; - 33, 47; - 34, 2; - 34, 9; - 34, 16; - 34, 23; - 34, 25; - 34, 27; - 34, 32; - 34, 40; - 35, 19; - 35, 20; - 35, 28; - 35, 31; - 35, 45; - 36, 0; - 36, 4; - 36, 8; - 36, 12; - 36, 22; - 36, 23; - 37, 1; - 37, 21; - 37, 49; - 38, 5; - 38, 7; - 38, 19; - 38, 27; - 38, 29; - 38, 46; - 38, 47; - 39, 4; - 39, 6; - 39, 7; - 39, 10; - 39, 32; - 39, 33; - 39, 36; - 39, 48; - 40, 23; - 40, 42; - 42, 0; - 42, 1; - 42, 10; - 42, 14; - 42, 16; - 42, 28; - 42, 37; - 42, 46; - 43, 10; - 43, 12; - 43, 14; - 44, 4; - 44, 10; - 44, 11; - 44, 20; - 44, 23; - 45, 22; - 45, 23; - 45, 25; - 45, 30; - 45, 35; - 45, 40; - 46, 7; - 46, 13; - 46, 15; - 46, 27; - 46, 28; - 46, 33; - 46, 34; - 46, 39; - 46, 41; - 46, 45; - 46, 49; - 47, 7; - 47, 18; - 47, 29; - 47, 34; - 47, 37; - 47, 42; - 47, 49; - 48, 6; - 48, 7; - 48, 16; - 48, 17; - 49, 3; - 49, 27; - 49, 46;" + 8, 1; + 8, 4; + 9, 4; + 10, 4;" data-ga-result-render="vertex-property" } ```graphalg @@ -740,32 +514,53 @@ func withDamping(degree:int, damping:real) -> real { return cast(degree) / damping; } -func PR(graph: Matrix) -> Vector { +func PR(graph: Matrix) -> Vector { + // A commonly-used value for the damping factor (85%). damping = real(0.85); + // Run for 10 iterations iterations = int(10); + + // Number of nodes in the graph n = graph.nrows; + + // Per-node probability that a random surfer will jump to that + // node. teleport = (real(1.0) - damping) / cast(n); - rdiff = real(1.0); + // Per-node out degree (number of outgoing edges) d_out = reduceRows(cast(graph)); - + // .. with damping applied. d = apply(withDamping, d_out, damping); + // Sinks are nodes that have no outgoing edges. + // Sometimes also called 'dangling nodes' or 'dead-ends'. connected = reduceRows(graph); sinks = Vector(n); sinks[:] = bool(true); + // Initial PageRank score: equally distributed over all nodes. pr = Vector(n); pr[:] = real(1.0) / cast(n); for i in int(0):iterations { + // total PageRank score across all sinks. sink_pr = Vector(n); sink_pr = pr; + + // redistribute PageRank score from sinks redist = (damping / cast(n)) * reduce(sink_pr); + // Previous PageRank score divided by the (damped) out degree. + // This gives us a per-node score amount to distributed to its + // outgoing edges. w = pr (./) d; + // Initialize next PageRank scores with the uniform teleport + // probability and the amount redistributed from the sinks. pr[:] = teleport + redist; + + // Distribute the previous PageRank scores over the outgoing + // edges (and add to the new PageRank score). pr += cast(graph).T * w; } @@ -773,21 +568,36 @@ func PR(graph: Matrix) -> Vector { } ``` -New ops: -- cast -- reduceRows -- apply -- M[:] = c -- A = B -- reduce -- (.f) -- M.T - -TODO: Where to find docs on additional operations - -Consult the [list of all operations](../spec/operations) available in GraphAlg. - -TODO: More example programs at the playground. -Play around with more algorithms there. +If you run the algorithm, you will see that node 1 is the most influentation node in the graph (it has the highest rank). +This makes sense given that many nodes have an edge to node 1. +Notice, though, that node 2 is also highly influential, yet it has very few incoming edges. +Its high ranking comes from the influential node 1, whose only outgoing edge is to node 2, boosting the influence of node 2. + +The PageRank implementation presented above uses a few new language constructs: +- `cast` (line 2, 9, etc.) casts the input to a different semiring `T`. + For example, `cast` on line 2 promotes an integer value to floating-point. +- `reduceRows(M)` (line 12) collapses a matrix `M` into a column vector, summing elements using the addition operator. +- `apply` (line 14) applies a scalar function to every element of a matrix. + An additional second scalar argument can be specified that is passed as the second argument to the function. +- `M[:] = c` (line 18, 21, 30) replaced every element of `M` by scalar value `c`. +- `A = B` (line 18, 25) assigns elements from `B` to the same position in `A` iff `M` has a nonzero value at that position. +- `reduce` (line 25) sums all elements of a matrix to scalar. +- `A (./) B` (line 28) represents elementwise division. + It applies the division operator to each position of `A` and `B`. + The output is defined as $O_{ij} = A_{ij} / B_{ij}$. + Other operators such as `+` and `*` and even arbitary functions (`A (.myFunc) B`) also support elementwise application. + +For a detailed explanation of these and other GraphAlg operations, see the [operations](../spec/operations) section of the language specification. + +## Where Next? +This concludes our introduction to the GraphAlg language. +Where you go from here depends on your needs: +- Do you want to experiment more with different algorithms? + Check out [GraphAlg Playground](../playground). +- Are you ready to move beyond experiments and use GraphAlg to analyze large graphs? + See the [available implementations](../integration/available). +- If you want to learn more about the design, features and theoretical foundations for the GraphAlg language, you can find a more details in the [language specification](../spec). + In particular, you can find a full overview of all [operations](../spec/operations) available in the GraphAlg there. +- Are you a system developer looking to integrate GraphAlg? See our guide for [new integrators](../integration/new_integration). From d77d7961f5e34ea2f49860ab61e49f4b61a31bf4 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 1 Dec 2025 13:41:47 +0000 Subject: [PATCH 11/12] Update the front page. --- index.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/index.md b/index.md index 5461435..694c971 100644 --- a/index.md +++ b/index.md @@ -7,7 +7,60 @@ nav_order: 1 # The GraphAlg Language GraphAlg is a language for graph algorithms designed to be embedded into databases. -{: .warning-title } -> This website is under construction -> -> We are working on an introduction to the language along with an interactive playground. In anticipation of that, you may be interested to read the [language specification](./spec). +{: + data-ga-func="PageRank" + data-ga-arg-0=" + 11, 11, i1; + 1, 2; + 2, 1; + 3, 0; + 3, 1; + 4, 1; + 4, 3; + 5, 1; + 5, 4; + 6, 1; + 6, 4; + 7, 1; + 7, 4; + 8, 1; + 8, 4; + 9, 4; + 10, 4;" + data-ga-result-render="vertex-property" +} +```graphalg +func withDamping(degree:int, damping:real) -> real { + return cast(degree) / damping; +} + +func PageRank(graph: Matrix) -> Vector { + damping = real(0.85); + iterations = int(10); + n = graph.nrows; + teleport = real(0.15) / cast(n); + + d_out = reduceRows(cast(graph)); + d = apply(withDamping, d_out, damping); + + pr = Vector(n); + pr[:] = real(1.0) / cast(n); + + for i in int(0):iterations { + w = pr (./) d; + pr[:] = teleport; + pr += cast(graph).T * w; + } + + return pr; +} +``` + +Are you new to GraphAlg? +Write your first GraphAlg program and learn more about the language using the interactive [tutorial](./tutorial). + +You can experiment with GraphAlg in our [Playground](./playground), or use a [system with GraphAlg support](./integration/available). + +For a detailed overview of the GraphAlg language, see the [language specification](./spec). + + From 95016004948c78341d0545fe433360a35e3a4097 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 1 Dec 2025 13:47:30 +0000 Subject: [PATCH 12/12] Under construction notes. --- integration/new_integration.md | 4 ++-- spec/core/desugar.md | 6 ++++++ spec/core/operations.md | 6 ++++++ spec/core/typing.md | 1 - spec/operations.md | 6 ++++++ spec/syntax.md | 5 +++++ 6 files changed, 25 insertions(+), 3 deletions(-) delete mode 100644 spec/core/typing.md diff --git a/integration/new_integration.md b/integration/new_integration.md index 6d26f64..e24117b 100644 --- a/integration/new_integration.md +++ b/integration/new_integration.md @@ -5,7 +5,7 @@ parent: Integrating nav_order: 2 --- -# Integrating GraphAlg Into new Systems +# Integrating GraphAlg into Existing Systems GraphAlg is specifically designed to be integrated into existing systems. The GraphAlg [compiler](https://github.com/wildarch/graphalg/tree/main/compiler) source code is freely available under a permissive license. The compiler can be integrated into other systems as a C++ library. @@ -36,4 +36,4 @@ To do this you should use the provided [parser]() and run the [pipeline](https:/ {: .note-title } > Example integration > -> We do not currently have an example integration for this approach, although a GraphBLAS is planned and would use this strategy. +> We do not currently have an example integration for this approach, although a GraphBLAS backend is planned that would use this strategy. diff --git a/spec/core/desugar.md b/spec/core/desugar.md index e31d14b..38b7021 100644 --- a/spec/core/desugar.md +++ b/spec/core/desugar.md @@ -6,6 +6,12 @@ nav_order: 2 --- # Desugaring GraphAlg to Core Operations + +{:.warning-title} +> Under Construction +> +> This part of the documentation is not yet finished. + TODO: - Which operations are not available core? - Rewrite rules for the desugaring diff --git a/spec/core/operations.md b/spec/core/operations.md index 4007525..3fae316 100644 --- a/spec/core/operations.md +++ b/spec/core/operations.md @@ -6,6 +6,12 @@ nav_order: 1 --- # Operations in GraphAlg Core + +{:.warning-title} +> Under Construction +> +> This part of the documentation is not yet finished. + TODO: - Core does not have a syntax or grammar, only definitions of operations - Describe all operations that exist in the language diff --git a/spec/core/typing.md b/spec/core/typing.md deleted file mode 100644 index 066c5b2..0000000 --- a/spec/core/typing.md +++ /dev/null @@ -1 +0,0 @@ -# Type Rules for GraphAlg Core diff --git a/spec/operations.md b/spec/operations.md index 81e1352..4aa37a8 100644 --- a/spec/operations.md +++ b/spec/operations.md @@ -6,4 +6,10 @@ nav_order: 2 --- # GraphAlg Operations + +{:.warning-title} +> Under Construction +> +> This part of the documentation is not yet finished. + TODO: Describe all operations in the GraphAlg language. diff --git a/spec/syntax.md b/spec/syntax.md index c8acec1..e4244ab 100644 --- a/spec/syntax.md +++ b/spec/syntax.md @@ -7,6 +7,11 @@ nav_order: 1 # GraphAlg Syntax +{:.warning-title} +> Under Construction +> +> This part of the documentation is not yet finished. + ## Lexical Analysis TODO: - ASCII characters only