Pure relational closure engine.
Relatum is a forward-chaining inference engine that operates exclusively on relations. There are no predefined mathematical semantics — equality, order, membership, and any other concept you need are expressed as plain relations with configurable properties.
equiv(a, b), lt(x, y), knows(alice, bob).<rel> equiv/2 : equivalence.inv(?x) create structured terms.# A minimal Relatum program <element> a, b, c <rel> equiv/2 : equivalence equiv(a, b) equiv(b, c) derive
Output: the engine derives equiv(a, c), equiv(c, a), equiv(b, a), equiv(c, b), and all reflexive pairs.
Write your first Relatum program in under a minute.
The app opens with the Workspace tab. Double-click an example or workspace file to open it, or create a new file with + File.
<element> a, b, c <rel> equiv/2 : equivalence equiv(a, b) equiv(b, c) derive
Click Run All or press Ctrl+Enter in a cell. The output appears below each cell showing:
Add more cells, try different properties, write custom rules. Each cell executes independently and contributes to a shared knowledge base.
The atoms of your universe.
Everything in Relatum is an element. Elements are declared by placing them inside a container using angle-bracket syntax.
# Declare elements in the root container <element> a, b, c # Declare elements, then use them as types <element> Person, City <Person> alice, bob, carol <City> paris, london
Every declared element automatically belongs to element. Writing <element> a, b, c is the simplest way to introduce elements.
Any element can be used as a type (container), but it must be declared first. When you write <Person> alice, bob:
Person must already exist as an element.alice and bob are declared as elements belonging to Person.<element> Animal, Cat <Animal> Cat, Dog <Cat> whiskers, mittens
When a relation declares a signature like <rel> R/2 : Person, City, only elements in Person can appear in argument 1, and only elements in City in argument 2. This applies to both user-written and derived facts.
<rel> eq/2 : Person, Person, reflexive, then eq(t, t) is only generated for elements in Person.
Named connections between terms.
A relation is a named predicate with a fixed arity. equiv/2 is binary; between/3 is ternary.
<rel> equiv/2 # plain relation <rel> equiv/2 : equivalence # with properties <rel> lt/2 : reflexive, transitive # multiple properties <rel> lives_in/2 : Person, City # membership constraints <rel> friend/2 : Person, Person, symmetric # membership + properties
| Property | Meaning | Auto-generated |
|---|---|---|
reflexive | R(t, t) for every constant t | Facts: R(a, a), R(b, b), ... |
symmetric | R(x, y) implies R(y, x) | Rule: R(?x, ?y) |- R(?y, ?x) |
transitive | R(x, y) and R(y, z) implies R(x, z) | Rule: R(?x, ?y), R(?y, ?z) |- R(?x, ?z) |
congruent | Substitutes equal terms in all facts | Substitution in existing facts |
equivalence | All four above | Everything combined |
Ground atoms — the initial knowledge base.
A fact is a ground instance of a relation. All terms must be declared constants (or compound terms).
equiv(a, b) lt(x, y) between(a, b, c)
<rel>.Inference rules for deriving new facts.
Rules express: "if these premises hold, derive these conclusions." They drive forward-chaining inference.
<rule> name: premises |- conclusions
Premises and conclusions are comma-separated atoms. Variables are prefixed with ?.
# Symmetry <rule> sym: R(?x, ?y) |- R(?y, ?x) # Transitivity <rule> trans: R(?x, ?y), R(?y, ?z) |- R(?x, ?z) # Cross-relation <rule> f2k: friend(?x, ?y) |- knows(?x, ?y) # Multi-conclusion <rule> both: eq(?x, ?y) |- leq(?x, ?y), leq(?y, ?x)
?x, ?y) are pattern wildcards matching any constant.Structured terms and Skolem witnesses.
Beyond simple atoms, Relatum supports compound terms — function-like structures that create new terms from existing ones.
# Compound term in a fact group_op(a, inv(a), identity) # Compound term in a rule (Skolem witness) <rule> has_inv: member(?x) |- group_op(?x, inv(?x), identity)
A compound term is written as name(arg1, arg2, ...) where each argument can itself be a term (atom, variable, or another compound).
When a compound term appears in a rule conclusion with a variable, it creates a Skolem witness — a new structured term that depends on the matched variable. For example, inv(?x) in a conclusion creates a unique inverse for each element.
Compound terms can nest: inv(inv(a)), inv(inv(inv(a))), etc. To prevent infinite nesting, use the depth directive:
depth 3 # max nesting depth for compound terms
The default depth is 5. Any derived fact containing a term deeper than this limit is silently dropped.
inv(?x) as inverse, op(?x, ?y) as multiplications(zero), s(s(zero))pair(?x, ?y), cons(?h, ?t)Commands to compute closure and query provenance.
The derive command triggers the closure engine, applying all rules until saturation or a limit.
derive # default: up to 100 rounds derive 10 # limit to 10 rounds
Each round, the engine:
Stops when: saturated (no new facts), round limit reached, or fact limit (10,000) exceeded.
The prove command queries the provenance of a specific fact — how it was derived.
prove equiv(a, c)
Output shows the derivation tree: which rule fired and which premises were used, recursively back to user-declared facts.
The depth directive sets the maximum nesting depth for compound terms.
depth 3
Must appear before derive. Default is 5.
How the closure engine works internally.
Relatum uses semi-naive forward chaining, similar to Datalog evaluation. Starting from initial facts, it repeatedly applies rules until a fixed point.
for each round: 1. Rule application for each rule: find all substitutions matching premises instantiate conclusions add new ground facts 2. Reflexivity for each reflexive relation R: for each constant t: add R(t, t) 3. Congruence for each congruent relation R: for each R(a, b) where a != b: for each fact S(...a...): add S(...b...) 4. Fixed-point check if no new facts: STOP (saturated)
What each property does under the hood.
For every constant t in the universe, adds R(t, t) before rule application. Respects membership constraints.
Auto-generates rule R(?x, ?y) |- R(?y, ?x).
Auto-generates rule R(?x, ?y), R(?y, ?z) |- R(?x, ?z). Two-premise join on ?y.
If R(a, b) holds and S(..., a, ...) exists, derives S(..., b, ...). Propagates equivalence through the entire fact base.
: equivalence is shorthand for : reflexive, symmetric, transitive, congruent.
Guarding against infinite derivation.
| Limit | Default | Purpose |
|---|---|---|
| Max rounds | 100 | Prevents infinite loops |
| Max facts | 10,000 | Caps memory usage |
| Max substitutions/rule | 50,000 | Prevents single-rule blowup |
| Max term depth | 5 | Prevents infinite Skolem nesting |
The engine stops and reports the current state. Output shows capped instead of saturated. Derived facts are valid but incomplete.
derive 5 to limit rounds while experimenting.depth 2 to limit Skolem term nesting.The notebook execution model.
The notebook has two cell types:
Each code cell runs independently. Click the play button on a cell or press Ctrl+Enter while editing to execute just that cell. Run All executes all code cells in order.
All cells share a single knowledge base. When you execute a cell, its declarations and facts are added to the global KB. The status bar shows total facts, derived count, and rounds.
Hover over a cell to reveal the floating toolbar:
Use + code or + markdown buttons between cells or at the bottom to add new cells.
Press Ctrl+Z when not focused in any editor to undo the last cell operation (add, delete, move, convert). Each tab has its own undo stack (up to 30 levels).
Rich text annotations for your notebook.
Click + markdown to add a markdown cell, or use the toolbar to convert an existing code cell.
Click a markdown cell to enter edit mode. Click outside or press Escape to render the preview. Supported syntax:
# Heading, ## Subheading, ### Sub-subheading**bold**, *italic*, `inline code`- unordered list, 1. ordered list```code blocks```[link text](url)> blockquoteExplore and manage the accumulated knowledge base.
Click the KB button in the status bar, or click the panel header at the bottom of the screen.
The KB panel shows all accumulated state across all executed cells:
Use the search box at the top of the KB panel to filter items. Search matches against element names, relation names, rule names, and fact contents.
Click the × next to any KB item to retract it. Retracted items appear crossed out and are excluded from future derivations. Click the + to restore a retracted item.
Use Clear All to reset the entire knowledge base.
Multi-tab workflow and file management.
Each tab is an independent notebook with its own cells, knowledge base, undo stack, and execution state. Click a tab to switch; click × to close it.
In the Workspace tab, click + File to create a new notebook. Focus a folder first to create inside it.
Double-click any file in the Workspace or Examples section to open it in a new tab.
Click Save in the header to save the current tab to the workspace. The file contains:
The file does not save the knowledge base state, execution order, or retraction state — these are recomputed by re-running cells.
{
"version": 1,
"cells": [
{ "type": "code", "source": "..." },
{ "type": "md", "source": "# Title" },
{ "type": "code", "source": "...", "output": "<html>" }
]
}
Syntax highlighting, autocomplete, and more.
Code cells have live syntax highlighting with the following color scheme:
| Element | Color |
|---|---|
Keywords (<rel>, <rule>, derive, etc.) | purple |
| Relation names | cyan |
| Elements / constants | orange |
| Containers | orange bold |
Variables (?x) | green italic |
Comments (#) | dim italic |
| Rule names | blue |
| Properties | purple |
As you type in a code cell, an autocomplete dropdown appears with context-aware suggestions:
<element>, <ele>, <rel>, <relation>, <rule>, derive, prove, depth<element> / <Container>Navigate with ↑ / ↓ arrow keys, accept with Tab or Enter, dismiss with Escape.
After executing a cell, use the search box in the output area to filter results. Matching text is highlighted.
The canonical Relatum example.
<element> a, b, c <rel> equiv/2 : equivalence equiv(a, b) equiv(b, c) derive
Saturates in 2-3 rounds with 9 total facts (complete 3×3 equivalence class).
Reflexive + transitive, but NOT symmetric.
<element> a, b, c, d <rel> leq/2 : reflexive, transitive leq(a, b) leq(b, c) leq(c, d) derive
leq(b, a) is not derived because the relation is not symmetric.
User-defined inference beyond built-in properties.
<element> alice, bob, carol <rel> friend/2 : symmetric <rel> knows/2 <rule> f2k: friend(?x, ?y) |- knows(?x, ?y) <rule> knows_trans: knows(?x, ?y), knows(?y, ?z) |- knows(?x, ?z) friend(alice, bob) friend(bob, carol) derive
Everyone ends up knowing everyone.
How congruence propagates equivalence through other relations.
<element> a, b, c <rel> equiv/2 : equivalence <rel> R/2 equiv(a, b) R(a, c) derive
Because equiv is congruent and equiv(a, b) holds, the engine sees R(a, c) and substitutes a → b, deriving R(b, c).
Restricting which elements each relation argument accepts.
<element> Person, City <Person> alice, bob, carol <City> paris, london <rel> lives_in/2 : Person, City <rel> friend/2 : Person, Person, symmetric <rel> same_city/2 : City, City, equivalence lives_in(alice, paris) lives_in(bob, paris) lives_in(carol, london) <rule> shared: lives_in(?x, ?c), lives_in(?y, ?c) |- same_city(?c, ?c) derive
lives_in(paris, alice) would be rejected.same_city(t, t) only for cities.Complete syntax of the Relatum DSL.
| Element | Pattern | Examples |
|---|---|---|
| Identifier | [a-zA-Z_][a-zA-Z0-9_]* | a, equiv, my_rel |
| Variable | ? + identifier | ?x, ?name |
| Number | [0-9]+ | 2, 100 |
| Comment | # to end of line | # this is ignored |
# Element declaration (root container) <element> name1, name2, ... <ele> name1, name2, ... # shorthand # Relation declaration (container for relation names) <relation> name1, name2, ... <rel> name1, name2, ... # shorthand # Typed element declaration (Container must be declared first) <Container> member1, member2, ... # Relation schema declaration <rel> name/arity <rel> name/arity : property1, property2, ... <rel> name/arity : Container1, Container2, ... <relation> name/arity : Container1, Container2, property, ... # Rule declaration <rule> name: atom1, atom2, ... |- atom3, atom4, ... # Fact (ground atom) name(term1, term2, ...) # Commands derive derive N prove name(term1, term2, ...) depth N
| Keyword | Expands to |
|---|---|
equivalence | reflexive, symmetric, transitive, congruent |
reflexive | R(t, t) for all t |
symmetric | R(?x, ?y) |- R(?y, ?x) |
transitive | R(?x, ?y), R(?y, ?z) |- R(?x, ?z) |
congruent | substitute equal terms in all facts |
A term is one of:
a, alice, zero?x, ?name (in rules only)f(term, ...), e.g. inv(?x), s(zero)Quick keys for the notebook interface.
| Key | Context | Action |
|---|---|---|
Ctrl+Enter | Code cell editor | Run the current cell |
Tab | Code cell editor | Insert two spaces / accept autocomplete |
↑ / ↓ | Autocomplete open | Navigate suggestions |
Enter | Autocomplete open | Accept suggestion |
Escape | Autocomplete open | Dismiss autocomplete |
Escape | Markdown cell edit | Exit edit mode, show preview |
Ctrl+Z | Not in any editor | Undo last cell operation |