Vibe coded implementation based on Tensor Logic authored by Pedro Domingos
Tensor Logic: a named-index tensor language that unifies neural and symbolic AI in a single, tiny core:
pip install tensorlogic
A program is a set of tensor equations. RHS = joins (implicit
einsum) + projection (sum over indices not in the LHS) + optional nonlinearity.
This repository provides a lightweight Python framework with swappable backends
(Numpy / optional PyTorch / optional JAX) through a thin einsum-driven abstraction.
- 🧮 Named indices: write equations with symbolic indices instead of raw axis numbers.
- ➕ Joins & projection: implicit
einsumto multiply tensors on shared indices and sum the rest. - 🧠 Neuro + Symbolic: includes helper utilities for relations (Datalog-like facts), attention, kernels, and small graphical models.
- 🔁 Forward chaining (fixpoint) and backward evaluation of queries.
- 🔌 Backends:
numpybuilt-in;torchandjaxif installed. - 🧪 Tests: cover each section of the paper with compact, didactic examples.
Learning / gradients are supported when the backend has autograd (Torch/JAX). With Numpy backend, you can evaluate programs but not differentiate them.
from tensorlogic import Tensor
# Minimal tensor logic - just like writing math equations!
W = Tensor([[2., -1.],[0.3, 0.7]], ["i","j"], name="W") # 2x2 weights
X = Tensor([1., 3.], ["j"], name="X") # 2 inputs
Y = Tensor([0., 0.], ["i"], name="Y") # output
Y["i"] = (W["i","j"] * X["j"]).step() # einsum 'ij,j->i' + step
result = Y["i"].eval() # evaluate eagerly
print(result.indices, result.data) # ('i',) [0. 1.]from tensorlogic import Tensor
import numpy as np
# Kernel computation (squared dot kernel)
X = Tensor([[1.,2.],[3.,4.]], ["i","j"], name="X")
K = Tensor(np.zeros((2,2)), ["i","i2"], name="K")
K["i","i2"] = (X["i","j"] * X["i2","j"]) ** 2
print("Kernel:", K["i","i2"].eval().numpy())
# Attention mechanism (now using a single pass, no intermediate computation)
X = Tensor(np.array([[0.1, 0.2],[0.3, 0.4],[0.1,0.8]]), ["p","d"], name="X")
WQ = Tensor(np.eye(2), ["dk","d"], name="WQ")
WK = Tensor(np.eye(2), ["dk","d"], name="WK")
WV = Tensor(np.eye(2), ["dv","d"], name="WV")
Query = Tensor(np.zeros((3,2)), ["p","dk"], name="Query")
Key = Tensor(np.zeros((3,2)), ["p","dk"], name="Key")
Val = Tensor(np.zeros((3,2)), ["p","dv"], name="Val")
Comp = Tensor(np.zeros((3,3)), ["p","p2"], name="Comp")
Attn = Tensor(np.zeros((3,2)), ["p","dv"], name="Attn")
Query["p","dk"] = WQ["dk","d"] * X["p","d"]
Key["p","dk"] = WK["dk","d"] * X["p","d"]
Val["p","dv"] = WV["dv","d"] * X["p","d"]
# Compute raw attention scores using deferred evaluation (no intermediate numpy operations)
Comp["p","p2"] = Query["p","dk"] * Key["p2","dk"] # einsum over shared 'dk'
scores = Comp["p","p2"].eval().numpy()
print("Raw scores:", scores)This compiles to efficient backend einsum on NumPy / PyTorch / JAX.
-
Native symbolic/Datalog style via
Relation:People = Domain(["Alice","Bob","Charlie"]) Parent = Relation("Parent", People, People) Sister = Relation("Sister", People, People) Aunt = Relation("Aunt", People, People) Parent["Bob","Charlie"] = 1 # facts Sister["Alice","Bob"] = 1 Aunt["x","z"] = (Sister["x","y"] * Parent["y","z"]).step() # rule
Facts are stored in the program as Boolean tensors; rules are equations (join + projection + step), and the final relation is the OR of facts and rules.
-
Learnable parameters through
Tensor:# Data tensor X = Tensor(np.random.randn(3, 5), ["i","j"], name="X") # Learnable parameter (Xavier init), marked learnable by default when no init is provided W = Tensor(idxs=["o","i"], sizes=[8, 5], name="W") # Non-learnable parameter (explicit init) C = Tensor(idxs=["i","j"], sizes=[3, 5], name="C", init="zeros", learnable=False)
-
Attention correctness in examples: scaled dot-product (
1/sqrt(dk)), normalized along the comparison axis (softmax(..., axis="p2")). -
Examples updated:
examples/attention.py,examples/symbolic_aunt.py.
Repository is under development
uv run pytest