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/_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/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). + + 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..e24117b --- /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 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. +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 backend is planned that would use this strategy. diff --git a/package-lock.json b/package-lock.json index 2010b78..6598248 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,28 @@ "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", + "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 +1164,29 @@ "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", + "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 +1746,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 +2034,9 @@ "@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", + "katex": "^0.16.25", + "vis-network": "^10.0.2" }, "devDependencies": { "@lezer/generator": "^1.8.0", 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..7a898fc 100644 --- a/playground/editor.ts +++ b/playground/editor.ts @@ -5,6 +5,9 @@ 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" +import katex from "katex" +import renderMathInElement from "katex/contrib/auto-render" // Load and register all webassembly bindings let playgroundWasmBindings = loadPlaygroundWasm(); @@ -281,7 +284,62 @@ function parseMatrix(input: string): GraphAlgMatrix { } } -function buildTable(m: GraphAlgMatrix): HTMLTableElement { +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) { + 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; + } + + let rows: string[][] = []; + for (let r = 0; r < m.rows; r++) { + let cols: string[] = []; + for (let c = 0; c < m.cols; c++) { + cols.push(defaultCellValue); + } + + rows.push(cols); + } + + for (let val of m.values) { + rows[val.row][val.col] = renderValue(val.val, m.ring); + } + + 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 renderMatrixTable(m: GraphAlgMatrix): HTMLTableElement { // Create an output table. const table = document.createElement("table"); @@ -307,7 +365,7 @@ function buildTable(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); } @@ -316,6 +374,186 @@ function buildTable(m: GraphAlgMatrix): HTMLTableElement { return table; } +function renderMatrixVisGraph(m: GraphAlgMatrix): HTMLElement { + if (m.rows != m.cols) { + throw Error("renderMatrixVisGraph called with a non-square matrix"); + } + + 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; + } + 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 = renderValue(val.val, m.ring); + 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 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++) { + 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) { + 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'; + } + } + + 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) { + edges.add({ + id: edges.length, + from: val.row, + to: val.col, + label: renderValue(val.val, graph.ring) + }); + } + + // 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 renderMatrixLatex(m); + } else if (m.rows == m.cols && m.rows < 20) { + return renderMatrixVisGraph(m); + } else if (m.rows < 20 && m.cols < 20) { + return renderMatrixLatex(m); + } else { + 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); + } +} + class GraphAlgEditor { root: Element; toolbar: Element; @@ -326,6 +564,8 @@ class GraphAlgEditor { initialProgram: string; functionName?: string; arguments: GraphAlgMatrix[] = []; + renderMode: MatrixRenderMode = MatrixRenderMode.AUTO; + resultRenderMode: MatrixRenderMode = MatrixRenderMode.AUTO; editorView?: EditorView; @@ -375,7 +615,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 = renderMatrix(arg, this.renderMode); argDetails.append(argSummary, table); this.argumentContainer.appendChild(argDetails); } @@ -399,6 +639,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) { @@ -408,6 +654,15 @@ 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; + } if (resultRender == 'latex') { + editor.resultRenderMode = MatrixRenderMode.LATEX; + } } // Replace the code snippet with the editor view. @@ -446,7 +701,11 @@ function run(editor: GraphAlgEditor, inst: PlaygroundInstance) { const result = inst.run(program, editor.functionName!!, editor.arguments); let resultElem; if (result.result) { - resultElem = buildTable(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); } @@ -481,3 +740,44 @@ 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 = renderMatrixVisGraph(mat); + } else if (mode == "coo") { + rendered = renderMatrixTable(mat); + } else { + rendered = renderMatrixLatex(mat); + } + + 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", displayMode: true }); + elem.replaceWith(container); +} + +// Initialize inline math. +renderMathInElement(document.body, { + output: 'mathml', + delimiters: [ + { left: "$", right: "$", display: false }, + ] +}) diff --git a/playground/index.md b/playground/index.md index a4a9b64..72d0ebf 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 @@ -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/playground/package.json b/playground/package.json index 08e4cf2..1fe84e0 100644 --- a/playground/package.json +++ b/playground/package.json @@ -7,7 +7,9 @@ "@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", + "katex": "^0.16.25", + "vis-network": "^10.0.2" }, "devDependencies": { "@lezer/generator": "^1.8.0", 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/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/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/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/spec/operations.md b/spec/operations.md new file mode 100644 index 0000000..4aa37a8 --- /dev/null +++ b/spec/operations.md @@ -0,0 +1,15 @@ +--- +title: Operations +layout: page +parent: Language Specification +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 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 new file mode 100644 index 0000000..b5d0d0f --- /dev/null +++ b/tutorial/index.md @@ -0,0 +1,603 @@ +--- +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!. + +{: .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. +> +> 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 show the error messages below the editor. + +{: + data-ga-func="AddOne" + data-ga-arg-0=" + 1, 1, i64; + 0, 0, 42;" +} +```graphalg +func AddOne(a: int) -> int { + return a + int(1); +} +``` + +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. +The general shape of a function is: + +```graphalg +func (: , ...) -> { + + ... +} +``` + +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). +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. + +{: + data-ga-func="NotValid" +} +```graphalg +func NotValid() -> int { + a = int(0); + for i in int(0):int(10) { + b = a; + a = a + int(1); + } + + return b; +} +``` + +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). +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; +``` + +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! + +## 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) -> Vector { + reach = source; + for i in graph.nrows { + reach += reach * graph; + } + + 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 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} +> 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 { + // 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. + +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. + +{: .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). + +## The Real Deal: 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=" + 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 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); + + // 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; + } + + return pr; +} +``` + +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). + +