LLMs fail at elementary math. Corporations spend billions, but ultimately are forced to attach calculators to computing machines of incredible power. All attempts to fix this via Chain-of-Thought, fine-tuning on arithmetic tasks, or context expansion have failed.

I conducted a series of experiments to understand why, and came to the conclusion that neural networks are simply not meant for discrete arithmetic. Their true purpose is continuous transformations.

This article describes the implementation of a novel neural network architecture that combines the precision of symbolic AI with the generalization capabilities of LLMs. As always, experiments and code are included.

I will traditionally skip the philosophical foundations that led to this solution.

Code on GitHub

TL;DR: LLMs make arithmetic mistakes not due to a lack of data or parameters—neural networks are fundamentally not designed for discrete calculations. They evolved (much like the biological brain) for continuous transformations and pattern recognition. The solution is not to teach them to count, but to embed an algebraic processor.

An architecture consisting of four components was developed: an isolated data bus (X does not pass through the weights), a neural controller (generates operator M from a command), a physical constraint (softmax guarantees conservation), and an algebraic executor (X_new = M @ X without trainable parameters). Experiments show: on the S3 group, the neural network derived a new operator via composition (M_AC = M_AB @ M_BC @ M_AB) with 99.9999% accuracy, having never seen it during training. Task accuracy degrades on Out-of-Distribution (OOD) data, but structural invariants (conservation, reversibility, group axioms) hold with machine precision regardless of scale. The key is the correct composition operation: matrix multiplication for operators (works), vector addition for parameters (does not work).

Semantic Neural Network Architecture

First, let me clarify the concepts of symbolic and semantic AI. The generally accepted name for an AI operating on symbols according to predefined rules is Symbolic AI. But as I have mentioned before, such an AI is a dead end (no generalization, computational collapse, etc.). An LLM works with probability distributions over tokens (inaccuracy, hallucinations). In essence, both types are syntax machines; neither has access to meaning per se. Therefore, I began to lean towards the idea that a hypothetical AGI will be a unification of these two variants. It could be called neurosymbolic AI (which is boring), but Semantic AI would be much more accurate.

So, we need an architecture where continuous representations (the neural network) and discrete operations (algebra) work together through a common interface:

The Data Path — "Nouns"

  • Element: Input state vector X (e.g., resources [A, B, C]).

  • Affects: Only the final result of the calculation.

  • Depends on: The initial conditions of the task.

  • Key feature: In our best experiments (S3, S4), this vector does not pass through the neural network weights at all. It flows through a separate, protected channel straight into the algebraic processor. The neural network cannot distort the numbers themselves because it has no direct access to them.

The Neural Controller — "System 1"

  • Element: Standard MLP (3 layers, 2 ReLU activations, raw logits output).

  • Input: Only the Command/Context (e.g., the vector [1, 0, 0] for "Swap AB").

  • What it does: This is the brain of the system. It looks at the task and decides which rule needs to be applied here.

  • Output: Raw logits (numbers from -infinity to +infinity) of dimension N x N (e.g., 3 x 3 or 4 x 4).

  • Key feature: The controller does not predict the answer. It predicts the action plan (the raw matrix).

Physics Constraint Layer — "The Restrictor"

  • Element: Softmax activation function, applied to the columns (or rows) of the raw matrix.

  • Input: Raw logits from the Neural Controller.

  • What it does: Transforms the chaos of raw numbers into a stochastic matrix M.

    • Forcibly makes all elements >= 0.

    • Forcibly ensures that the sum in each column is exactly 1.0.

  • Affects: This is the security core. This element physically controls the network's capacity for error.

  • Output: The final operator matrix M.

The Algebraic Executor — "System 2"

  • Element: Matrix multiplication operation (in PyTorch, this is torch.bmm).

  • Input: The final matrix M (from the restrictor) and the data vector X (from the data path).

  • What it does: Rigidly, with mathematical precision, applies the operator to the data: X_new = M * X

  • Key feature: There are no trainable parameters here. This is pure math. If matrix M says "swap 1 and 2", the processor will do it down to the exact bit.

In essence, this is a neural network with a built-in algebraic processor, allowing us to achieve the analog intuition of a neural network combined with the discrete rigor of mathematics. It is a von Neumann architecture, but with the ability to generalize knowledge.

Classical LLMs try to guess the ready-made answer token by token, so they inevitably hallucinate on large numbers. In this approach, the neural network does not predict the answer, but the action itself (the operator matrix). It merely translates the logic of the task into a precise instruction, while a rigid deterministic algorithm (matrix multiplication) performs the actual math. The AI doesn't calculate in its head; it directs the calculations without errors or crashes.

Experiment 1: Structural Reliability Through Architecture

Task: Redistribution between two containers.

A standard MLP learns the task within the bounds of the training data. But any step outside this area leads to an avalanche of errors.

In the new architecture, instead of predicting absolute numbers, the network predicts the distribution coefficients.

total = A + B                    # integration
alpha = sigmoid(network(amount)) # fraction in [0,1]
A_new = total * alpha
B_new = total * (1 - alpha)

Mathematical reliability:

A_new + B_new = total * (alpha + (1 - alpha)) = total

The law of conservation is structurally enforced; it does not depend on training.

Results:

Range

Task MSE

Conservation Error

Train (×1)

0.0004

6e-8

OOD (×10)

688.3

6e-7

OOD (×1000)

1.4e9

0.0

Task accuracy degrades on OOD data. But the invariant (the sum) is preserved with machine precision regardless of scale.

Architectural constraints guarantee invariants. A penalty loss does not.

Experiment 1 Code

Code
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import random
import warnings

warnings.filterwarnings("ignore", category=UserWarning)

# ── CONFIG ────────────────────────────────────────────────────────────────────
INPUT_DIM = 2      # [A, B]
CMD_DIM = 1        # amount (how much to transfer)
HIDDEN_DIM = 64
BATCH_SIZE = 64
LR = 0.001
STEPS = 10000 
SEED = 42
TRAIN_RANGE = 10.0
TEST_RANGE = 100.0 

DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# ── GENERATOR ─────────────────────────────────────────────────────────────────
class FlowGenerator:
    def __init__(self, val_range):
        self.val_range = val_range
    
    def get_batch(self, n):
        # Generate positive numbers (volume)
        state = torch.rand(n, INPUT_DIM) * self.val_range
        
        # Amount to transfer (can be negative)
        amount = (torch.rand(n, CMD_DIM) * 2 - 1) * (self.val_range / 3)
        
        # Target state
        target = torch.zeros_like(state)
        actual_transfer = amount[:, 0].clone()
        
        # Physical constraints
        mask_A = (state[:, 0] - actual_transfer) < 0
        actual_transfer[mask_A] = state[mask_A, 0]
        
        mask_B = (state[:, 1] + actual_transfer) < 0
        actual_transfer[mask_B] = -state[mask_B, 1]
        
        target[:, 0] = state[:, 0] - actual_transfer
        target[:, 1] = state[:, 1] + actual_transfer
        
        return state.to(DEVICE), amount.to(DEVICE), target.to(DEVICE)

# ── ARCHITECTURE: HYDRAULIC GATING ────────────────────────────────────────────
class HydraulicNet(nn.Module):
    def __init__(self):
        super().__init__()
        
        self.controller = nn.Sequential(
            nn.Linear(INPUT_DIM + CMD_DIM, HIDDEN_DIM),
            nn.ReLU(),
            nn.Linear(HIDDEN_DIM, HIDDEN_DIM),
            nn.ReLU(),
            nn.Linear(HIDDEN_DIM, 1), 
            nn.Sigmoid() 
        )
        
    def forward(self, state, amount):
        # Integration
        total_flow = state.sum(dim=1, keepdim=True)
        
        # The controller decides the proportion
        alpha = self.controller(torch.cat([state, amount], dim=1))
        
        # Physical distribution
        A_new = total_flow * alpha
        B_new = total_flow * (1 - alpha)
        
        return torch.cat([A_new, B_new], dim=1)

# ── EXPERIMENT ────────────────────────────────────────────────────────────────
def run_hydraulic_balance():
    print(f"HYDRAULIC BALANCE TEST | {DEVICE}")
    
    torch.manual_seed(SEED)
    random.seed(SEED)
    
    model = HydraulicNet().to(DEVICE)
    opt = optim.AdamW(model.parameters(), lr=LR)
    train_gen = FlowGenerator(TRAIN_RANGE)
    
    print(f"\n  [Training]...")
    
    for step in range(1, STEPS+1):
        model.train()
        opt.zero_grad()
        
        state, amount, target = train_gen.get_batch(BATCH_SIZE)
        pred = model(state, amount)
        
        task_loss = nn.MSELoss()(pred, target)
        task_loss.backward()
        opt.step()
        
        if step % 2000 == 0:
            print(f"    Step {step}/{STEPS} | Task MSE={task_loss:.5f}")

    print(f"\n  [Testing] Checking at different scales...")
    
    test_ranges = [TRAIN_RANGE, TEST_RANGE, TEST_RANGE * 1000]
    labels = ["Train (x1)", "OOD (x10)", "OOD (x1000)"]
    
    model.eval()
    print(f"  {'-'*70}")
    print(f"  {'Range':<15} | {'Task MSE':<12} | {'Conservation Error':<18}")
    print(f"  {'-'*70}")
    
    for r, label in zip(test_ranges, labels):
        test_gen = FlowGenerator(r)
        state, amount, target = test_gen.get_batch(1000)
        
        with torch.no_grad():
            pred = model(state, amount)
            
            task_err = nn.MSELoss()(pred, target).item()
            
            # Checking the law of conservation
            sum_input = state.sum(dim=1)
            sum_output = pred.sum(dim=1)
            phys_err = (sum_input.double() - sum_output.double()).abs().mean().item()
            
            print(f"  {label:<15} | {task_err:<12.4f} | {phys_err:<18.2e}")

if __name__ == "__main__":
    run_hydraulic_balance()

Execution Logs

Logs

HYDRAULIC BALANCE TEST | cuda

[Training]...
Step 2000/10000 | Task MSE=0.00140
Step 4000/10000 | Task MSE=0.00372
Step 6000/10000 | Task MSE=0.00054
Step 8000/10000 | Task MSE=0.00135
Step 10000/10000 | Task MSE=0.00036

[Testing] Checking at different scales...

Range | Task MSE | Conservation Error

Train (x1) | 0.0004 | 6.02e-08
OOD (x10) | 688.3582 | 6.64e-07
OOD (x1000) | 1375917056.0000 | 0.00e+00

Experiment 2: Algebraic Composition

Task: Learn the generators of the symmetric group S3 and test whether the network can derive new operators via composition.

Training: Only on two operations:

  • SwapAB: [A, B, C] -> [B, A, C]

  • SwapBC: [A, B, C] -> [A, C, B]

Zero-Shot test: Derive SwapAC using the formula M_AC = M_AB @ M_BC @ M_AB. The network has NEVER seen SwapAC in the data.

Architecture: The network generates stochastic matrices (column-stochastic via softmax). This guarantees mass conservation: sum(M[:, j]) = 1.

Results:

  • Training (15k steps): MSE = 8e-8

  • Learned matrices: M_AB and M_BC

  • Composition: M_AC = M_AB @ M_BC @ M_AB

Verification: [10, 20, 30] -> [30, 20, 10]

  • Error: 9.2e-6

Verification of group axioms:

  • (AB * BC)^3 = Identity: Error = 1.2e-7 ✓

  • M @ M^T = I (orthogonality): Error = 0.0 ✓

  • SwapAB ∘ SwapBC != SwapBC ∘ SwapAB (non-commutativity): Diff = 0.67 ✓

All matrices are exact permutation matrices (elements are 0 or 1, no blurring). The softmax collapsed into discrete solutions.

The neural network derived a new operator via an algebraic formula. This is not pattern memorization, but composition via matrix multiplication.

Experiment 2 Code

Code
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import numpy as np
import random
import warnings

warnings.filterwarnings("ignore", category=UserWarning)

# ── CONFIG ────────────────────────────────────────────────────────────────────
INPUT_DIM = 3      # [A, B, C]
CMD_DIM = 2        # Train ONLY 2 generators: SwapAB (0) and SwapBC (1)
HIDDEN_DIM = 64
BATCH_SIZE = 64
LR = 0.001
STEPS = 15000 
SEED = 42
TRAIN_RANGE = 10.0

DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# Atomic commands (Generators of the S3 group)
CMD_SWAP_AB = 0
CMD_SWAP_BC = 1

# ── GENERATOR: TRAINING ONLY THE BASIS ────────────────────────────────────────
class GeneratorS3_Basis:
    def __init__(self, val_range):
        self.val_range = val_range
    
    def get_batch(self, n):
        state = torch.rand(n, INPUT_DIM) * self.val_range
        
        # Choose ONLY 0 or 1. The network does not know other operations exist.
        cmd_idx = torch.randint(0, CMD_DIM, (n,))
        cmd = F.one_hot(cmd_idx, num_classes=CMD_DIM).float()
        
        target = torch.zeros_like(state)
        
        for i in range(n):
            s = state[i]
            c = cmd_idx[i].item()
            
            if c == CMD_SWAP_AB:
                target[i] = torch.tensor([s[1], s[0], s[2]])
            elif c == CMD_SWAP_BC:
                target[i] = torch.tensor([s[0], s[2], s[1]])
                
        return state.to(DEVICE), cmd.to(DEVICE), target.to(DEVICE)

# ── ARCHITECTURE: STOCHASTIC OPERATOR NET ─────────────────────────────────────
class AlchemyNet(nn.Module):
    def __init__(self):
        super().__init__()
        # Network: Command -> 3x3 Matrix
        self.controller = nn.Sequential(
            nn.Linear(CMD_DIM, HIDDEN_DIM),
            nn.ReLU(),
            nn.Linear(HIDDEN_DIM, HIDDEN_DIM),
            nn.ReLU(),
            nn.Linear(HIDDEN_DIM, INPUT_DIM * INPUT_DIM) 
        )
        
    def forward(self, cmd):
        # Generate the operator
        raw = self.controller(cmd).view(-1, INPUT_DIM, INPUT_DIM)
        # Guarantee physics (column sum = 1)
        return F.softmax(raw, dim=1) 

# ── EXPERIMENT ───────────────────────────────────────────────────��────────────
def run_algebra_proof():
    print(f"🔥 S3 GROUP ALGEBRA PROOF | {DEVICE}")
    print(f"   Goal: Derive SwapAC and Shift via composition of AB and BC.")
    print(f"   Training: ONLY SwapAB and SwapBC.")
    
    torch.manual_seed(SEED)
    random.seed(SEED)
    
    model = AlchemyNet().to(DEVICE)
    opt = optim.AdamW(model.parameters(), lr=LR)
    gen = GeneratorS3_Basis(TRAIN_RANGE)
    
    print(f"\n  [Training] Learning group generators...")
    
    for step in range(1, STEPS+1):
        model.train()
        opt.zero_grad()
        
        state, cmd, target = gen.get_batch(BATCH_SIZE)
        
        # Get matrix M
        M = model(cmd)
        
        # Apply: X_new = M @ X
        pred = torch.bmm(M, state.unsqueeze(2)).squeeze(2)
        
        loss = F.mse_loss(pred, target)
        loss.backward()
        opt.step()
        
        if step % 5000 == 0:
            print(f"    Step {step}/{STEPS} | MSE: {loss.item():.8f}")

    # ── PROOF PHASE (MATH CHECK) ──
    print(f"\n  [Proof] Extracting matrices and verifying S3 axioms...")
    model.eval()
    
    with torch.no_grad():
        # 1. Extract learned building blocks
        c_AB = torch.tensor([[1., 0.]]).to(DEVICE)
        c_BC = torch.tensor([[0., 1.]]).to(DEVICE)
        
        M_AB = model(c_AB)[0]
        M_BC = model(c_BC)[0]
        I    = torch.eye(3).to(DEVICE)
        
        print(f"  Matrix AB (Learned):\n{M_AB.cpu().numpy().round(2)}")
        print(f"  Matrix BC (Learned):\n{M_BC.cpu().numpy().round(2)}")

        # 2. CHECK 1: Group Presentation (ab)^3 = I
        # Product (AB * BC) should yield a shift.
        # Shift cubed should yield Identity.
        M_Shift = M_AB @ M_BC
        M_Identity_Check = M_Shift @ M_Shift @ M_Shift
        
        err_id = F.mse_loss(M_Identity_Check, I).item()
        print(f"\n  🔹 Axiom Check: (AB * BC)^3 == I")
        print(f"     Error: {err_id:.8f}")
        if err_id < 1e-4: print("     ✅ AXIOM SATISFIED (Cyclicity works)")
        else: print("     ❌ AXIOM VIOLATED")

        # 3. CHECK 2: Derivation of SwapAC (Conjugation)
        # Group theory: AC = AB * BC * AB
        # The network has NEVER seen AC. If this works, it's algebraic magic.
        
        M_AC_Derived = M_AB @ M_BC @ M_AB
        
        print(f"\n  🔹 Derivation Check: AC = AB * BC * AB")
        print(f"     Derived Matrix AC:\n{M_AC_Derived.cpu().numpy().round(2)}")
        
        # Testing this DERIVED operator on data
        # Input: [10, 20, 30] -> Expected AC: [30, 20, 10]
        vec_in = torch.tensor([[10., 20., 30.]]).to(DEVICE).T
        vec_out = M_AC_Derived @ vec_in
        vec_target = torch.tensor([[30., 20., 10.]]).to(DEVICE).T
        
        err_ac = F.mse_loss(vec_out, vec_target).item()
        print(f"     Data Test ([10,20,30] -> [30,20,10]):")
        print(f"     Result: {vec_out.flatten().cpu().numpy()}")
        print(f"     Error:  {err_ac:.8f}")
        
        if err_ac < 1e-4:
            print("\n  🏆 CRITICAL SUCCESS: Network derived AC operator from the basis!")
        else:
            print("\n  ❌ FAILED: Composition did not yield AC.")

if __name__ == "__main__":
    run_algebra_proof()

Execution Logs

Logs

🔥 S3 GROUP ALGEBRA PROOF | cuda
Goal: Derive SwapAC and Shift via composition of AB and BC.
Training: ONLY SwapAB and SwapBC.

[Training] Learning group generators...
Step 5000/15000 | MSE: 0.00002016
Step 10000/15000 | MSE: 0.00000131
Step 15000/15000 | MSE: 0.00000008

[Proof] Extracting matrices and verifying S3 axioms...
Matrix AB (Learned):
[[0. 1. 0.]
[1. 0. 0.]
[0. 0. 1.]]
Matrix BC (Learned):
[[1. 0. 0.]
[0. 0. 1.]
[0. 1. 0.]]

🔹 Axiom Check: (AB * BC)^3 == I
Error: 0.00000012
✅ AXIOM SATISFIED (Cyclicity works)

🔹 Derivation Check: AC = AB ∘ BC ∘ AB
Derived Matrix AC:
[[0. 0. 1.]
[0. 1. 0.]
[1. 0. 0.]]
Data Test ([10,20,30] -> [30,20,10]):
Result: [29.996683 19.999294 10.004023]
Error: 0.00000923

🏆 CRITICAL SUCCESS: Network derived AC operator from the basis!

Why This Works

1. The Correct Composition Operation

  • Success (operator algebra, 99.9999% on Zero-Shot):

    M_composed = M_AB @ M_BC // matrix multiplication

  • For operators, the correct composition is multiplication, not addition.

2. Discrete Topology

Permutations are bijections. Information is not lost. Softmax on a task with discrete solutions naturally collapses to one-hot representations:

  • Start of training: [0.35, 0.42, 0.23] (blurred)

  • End of training: [0.00, 1.00, 0.00] (discrete)

The loss surface has its minima at the vertices of the simplex. Gradients naturally push the optimization there.

3. Closed Algebra

Permutation @ Permutation = Permutation

The composition strictly remains within the same class -> there is no representation drift and no accumulation of errors.

Experiment 3: Scaling: From S3 to S4

Does the method work on larger groups?

S4 — the symmetric group of 4 elements:

  • 24 elements (versus 6 in S3)

  • Generated by three transpositions: SwapAB, SwapBC, SwapCD

New Checks

1. Disjoint Commutativity

Operations on disjoint sets must commute:

  • SwapAB acts on {0, 1}

  • SwapCD acts on {2, 3}

Disjoint -> must commute.

  • Test: M_AB @ M_CD = M_CD @ M_AB ?

  • Result: Error = 0.00000000

The network understands locality. Operations on different elements are completely independent.

2. Recursive Derivation

Derive SwapAD (swapping the outermost elements) through a chain of compositions:

  • Step 1: Derive SwapAC = SwapAB @ SwapBC @ SwapAB

  • Step 2: Derive SwapAD = SwapAC @ SwapCD @ SwapAC

This is a chain of length 5: five matrix multiplications to obtain an operator the network has never seen before.

Result:

  • Input: [10, 20, 30, 40]

  • Output: [39.998, 20.001, 29.999, 10.002]

  • Target: [40, 20, 30, 10]

  • Error: 2.67e-6

The network derived the operator via two-level recursion with machine precision.

Scaling Conclusions

Group

Size

Generators

Composable?

Max Chain

Error

Z3

3

1 (shift)

3 (f³=id)

<1e-7

S3

6

2 (AB, BC)

3 (AC derivation)

<1e-5

S4

24

3 (AB, BC, CD)

5 (AD derivation)

<1e-6

Pattern:

  • Discreteness is preserved (matrices strictly remain 0/1).

  • Composition works at arbitrary depths.

  • Locality of operations is detected automatically (commutativity of disjoint sets).

Limitation: The computational cost grows as O(n^2) for storing n x n matrices. For S10 (3,628,800 elements), other representations will be required (e.g., sparse matrices or factored forms).

Experiment 3 Code

Code
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import numpy as np
import random
import warnings
import matplotlib.pyplot as plt
import seaborn as sns

warnings.filterwarnings("ignore", category=UserWarning)

# ── CONFIG ────────────────────────────────────────────────────────────────────
INPUT_DIM = 4      # [A, B, C, D]
CMD_DIM = 3        # Three generators: SwapAB, SwapBC, SwapCD
HIDDEN_DIM = 128   # Slightly wider, as the group is more complex (24 elements)
BATCH_SIZE = 64
LR = 0.001
STEPS = 20000 
SEED = 42
TRAIN_RANGE = 10.0

DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# S4 Generators
CMD_SWAP_AB = 0
CMD_SWAP_BC = 1
CMD_SWAP_CD = 2

# ── GENERATOR: S4 BASIS ───────────────────────────────────────────────────────
class GeneratorS4:
    def __init__(self, val_range):
        self.val_range = val_range
    
    def get_batch(self, n):
        state = torch.rand(n, INPUT_DIM) * self.val_range
        cmd_idx = torch.randint(0, CMD_DIM, (n,))
        cmd = F.one_hot(cmd_idx, num_classes=CMD_DIM).float()
        target = torch.zeros_like(state)
        
        for i in range(n):
            s = state[i]
            c = cmd_idx[i].item()
            
            if c == CMD_SWAP_AB:
                target[i] = torch.tensor([s[1], s[0], s[2], s[3]])
            elif c == CMD_SWAP_BC:
                target[i] = torch.tensor([s[0], s[2], s[1], s[3]])
            elif c == CMD_SWAP_CD:
                target[i] = torch.tensor([s[0], s[1], s[3], s[2]])
                
        return state.to(DEVICE), cmd.to(DEVICE), target.to(DEVICE)

# ── ARCHITECTURE (SAME AS BEFORE) ─────────────────────────────────────────────
class AlchemyNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.controller = nn.Sequential(
            nn.Linear(CMD_DIM, HIDDEN_DIM),
            nn.ReLU(),
            nn.Linear(HIDDEN_DIM, HIDDEN_DIM),
            nn.ReLU(),
            nn.Linear(HIDDEN_DIM, INPUT_DIM * INPUT_DIM) 
        )
        
    def forward(self, cmd):
        raw = self.controller(cmd).view(-1, INPUT_DIM, INPUT_DIM)
        return F.softmax(raw, dim=1) 

# ── EXPERIMENT ────────────────────────────────────────────────────────────────
def run_s4_experiment():
    print(f"🌌 S4 HYPERCUBE ALGEBRA (4x4) | {DEVICE}")
    print(f"   Goal: Verify commutativity of independent operations (AB vs CD).")
    
    torch.manual_seed(SEED)
    random.seed(SEED)
    
    model = AlchemyNet().to(DEVICE)
    opt = optim.AdamW(model.parameters(), lr=LR)
    gen = GeneratorS4(TRAIN_RANGE)
    
    print(f"\n  [Training] Learning AB, BC, CD...")
    
    for step in range(1, STEPS+1):
        model.train()
        opt.zero_grad()
        state, cmd, target = gen.get_batch(BATCH_SIZE)
        M = model(cmd)
        pred = torch.bmm(M, state.unsqueeze(2)).squeeze(2)
        loss = F.mse_loss(pred, target)
        loss.backward()
        opt.step()
        
        if step % 5000 == 0:
            print(f"    Step {step}/{STEPS} | MSE: {loss.item():.8f}")

    # ── ANALYTICS ──
    print(f"\n  [Analysis] Extracting 4x4 matrices...")
    model.eval()
    with torch.no_grad():
        c0 = torch.tensor([[1., 0., 0.]]).to(DEVICE) # AB
        c1 = torch.tensor([[0., 1., 0.]]).to(DEVICE) # BC
        c2 = torch.tensor([[0., 0., 1.]]).to(DEVICE) # CD
        
        M_AB = model(c0)[0]
        M_BC = model(c1)[0]
        M_CD = model(c2)[0]
        I    = torch.eye(4).to(DEVICE)

        # 1. COMMUTATIVITY TEST (Disjoint Sets)
        # AB swaps (0,1), CD swaps (2,3). They do not intersect.
        # Therefore: AB * CD must equal CD * AB.
        
        Commute_1 = M_AB @ M_CD
        Commute_2 = M_CD @ M_AB
        
        err_commute = F.mse_loss(Commute_1, Commute_2).item()
        
        print(f"\n  1. Disjoint Commutativity (AB * CD == CD * AB)")
        print(f"     Error: {err_commute:.8f}")
        if err_commute < 1e-5:
            print("     ✅ SUCCESS: The network understands operation locality!")
        else:
            print("     ❌ FAIL: Operations are 'entangled'.")

        # 2. "LONG JUMP" TEST (Swap AD)
        # How to swap the 1st and 4th elements, having only adjacent swaps?
        # SwapAD = Swap(A,C) * Swap(C,D) * Swap(A,C) ... complex.
        # Simpler: "Bubble it through".
        # SwapAD = (CD * BC * AB) * (BC * CD) ... no, that's a shift.
        # Conjugation formula: 
        # Swap(0,3) = Swap(0,2) * Swap(2,3) * Swap(0,2)
        # Swap(0,2) = Swap(0,1) * Swap(1,2) * Swap(0,1)
        # Total: AD is derived via 3rd-level nesting!
        
        # First, obtain AC
        M_AC = M_AB @ M_BC @ M_AB
        # Now, obtain AD using AC and CD
        M_AD_Derived = M_AC @ M_CD @ M_AC 
        
        # Test vector [10, 20, 30, 40] -> [40, 20, 30, 10]
        vec_in = torch.tensor([[10., 20., 30., 40.]]).to(DEVICE).T
        vec_out = M_AD_Derived @ vec_in
        vec_target = torch.tensor([[40., 20., 30., 10.]]).to(DEVICE).T
        
        err_ad = F.mse_loss(vec_out, vec_target).item()
        
        print(f"\n  2. Long Jump Derivation (Swap A <-> D)")
        print(f"     Derived via recursive conjugation: AC = AB*BC*AB -> AD = AC*CD*AC")
        print(f"     Input:  {vec_in.flatten().cpu().numpy()}")
        print(f"     Output: {vec_out.flatten().cpu().numpy()}")
        print(f"     Error:  {err_ad:.8f}")
        
        if err_ad < 1e-4:
            print("     ✅ SUCCESS: Network built a chain of length 5!")
        else:
            print("     ❌ FAIL: Too complex.")

        # Plotting
        plot_s4_matrices(M_AB, M_CD, Commute_1)

def plot_s4_matrices(M1, M2, M3):
    fig, axes = plt.subplots(1, 3, figsize=(14, 4))
    list_m = [(M1, "Swap AB"), (M2, "Swap CD"), (M3, "AB * CD\n(Parallel Swap)")]
    
    for i, (M, title) in enumerate(list_m):
        sns.heatmap(M.detach().cpu().numpy(), annot=True, fmt=".1f", cmap="Greens", 
                    ax=axes[i], cbar=False, square=True, vmin=0, vmax=1,
                    linewidths=1, linecolor='black')
        axes[i].set_title(title)
        axes[i].axis('off')
    
    plt.tight_layout()
    plt.savefig('s4_hypercube.png')
    print("\n  📸 S4 matrices saved to 's4_hypercube.png'")

if __name__ == "__main__":
    run_s4_experiment()

Execution Logs

Logs

🌌 S4 HYPERCUBE ALGEBRA (4x4) | cuda
Goal: Verify commutativity of independent operations (AB vs CD).

[Training] Learning AB, BC, CD...
Step 5000/20000 | MSE: 0.00000854
Step 10000/20000 | MSE: 0.00000043
Step 15000/20000 | MSE: 0.00000004
Step 20000/20000 | MSE: 0.00000000

[Analysis] Extracting 4x4 matrices...

  1. Disjoint Commutativity (AB ∘ CD == CD ∘ AB)
    Error: 0.00000000
    ✅ SUCCESS: The network understands operation locality!

  2. Long Jump Derivation (Swap A <-> D)
    Derived via recursive conjugation: AC = AB ∘ BC ∘ AB -> AD = AC ∘ CD ∘ AC
    Input: [10. 20. 30. 40.]
    Output: [39.99779 20.000546 29.999397 10.002266]
    Error: 0.00000267
    ✅ SUCCESS: Network built a chain of length 5!

📸 S4 matrices saved to 's4_hypercube.png'

What This Means for AI System Architectures

Prospects for implementing the SPU (Semantic Processing Unit) architecture:

  1. Deterministic calculations (eliminating OOD hallucinations). Separating semantic parsing from arithmetic operations guarantees 100% calculation accuracy. The neural network generates only the transformation algorithm (a permutation or distribution operator), and its execution is delegated to hardware matrix multiplication. The accuracy of the result becomes completely independent of the scale of the input values (Out-of-Distribution).

  2. Structural safety and preservation of invariants (Hard Constraints). Applying normalizing activation functions (e.g., column-wise Softmax) to form transformation matrices imposes rigid constraints on the output operator (forming stochastic matrices). At the very level of the computational graph architecture, this prohibits the model from violating conservation (balance) laws—generating "excess" values or losing data without a trace becomes mathematically impossible.

  3. Radical parameter optimization (Data Efficiency & Zero-Shot Composition). Instead of approximating an infinite space of answers (which requires an exponential growth of parameters), the network is trained on a limited basis of group generators (as demonstrated on the S3 permutation group). Complex multi-stage operations are synthesized through the algebraic composition of base operator matrices. This allows complex logical tasks to be solved using highly compact models.

  4. Absolute interpretability (White-box AI).

    The decision-making logic is no longer hidden within the weights (hidden states). The final output of the neural controller is an explicit, sparse transformation matrix. This operator can be extracted, programmatically analyzed, and validated before being applied to real data (for example, verifying specific data routing indices), making the system fully auditable.

Boundaries of the Method

Works for:

  • Topological operations (permutations, symmetries)

  • Physical invariants (conservation of mass, energy)

  • Discrete groups (S_n, Z_n, dihedral groups)

Does not work for (requires further research):

  • Symbolic logic (if-then-else)

  • Arbitrary arithmetic (adding fractions)

  • Continuous groups without finite presentation

Conclusion

The architecture described in this article confirms a philosophical thesis: managing invariants requires naming them. A system can implicitly use a law (e.g., conservation via softmax), but to reason about a law (to compose or verify it), the law must be tokenized—represented as a distinct symbol within the computational graph.

This solves the Symbol Grounding Problem for semantic systems: symbols (commands) are grounded in operators (matrices) that have observable effects (data transformations). The connection symbol <-> operator <-> effect provides true semantics.

The described approach allows us to cover all of linear algebra, all finite groups, physical simulations, and graph algorithms. Other branches of mathematics are only partially covered and require further research.

The biological brain evolved for pattern recognition and intuitive physics, not for symbolic arithmetic. Children learn mathematics through the memorization of facts and cultural tools (the abacus, algorithms). Neural networks have these exact same limitations. However, if we embed the correct structural constraints and utilize the correct composition operations, neural networks can function as precise calculators—not through counting, but through algebra.