Circuit Grammar Specification

Complete grammar specification for Asgard's computational circuit syntax.

Overview

Circuits in Asgard represent computational networks composed of:

The grammar is case-insensitive for keywords and follows a functional composition style.

Grammar Definition

start: term

term: composition
    | monoidal
    | trace
    | atomic

composition: "composition" "(" term "," term ")"
monoidal: "monoidal" "(" term "," term ")"
trace: "trace" "(" term ")"

atomic: var | const | add | multiplication | scalar
      | id | split | register | deregister | convolution

IDENTIFIER: /[a-zA-Z][a-zA-Z0-9_]*/
NUMBER: /-?[0-9]+(\.[0-9]+)?/

Combinators

Composition

Syntax:

composition: "composition"i "(" term "," term ")"

Signature: If f : A → B and g : B → C, then composition(f, g) : A → C

Semantics: Sequential execution: g ∘ f (apply f first, then g)

Examples:

# Integrate then differentiate
"composition(register(x), deregister(x))"

# Chain three operations
"composition(composition(add, scalar(2.0)), register(t))"

# Nested composition
"composition(scalar(0.5), composition(add, multiplication))"

Execution:

outputs_f, state_1 = f(inputs, state_0)
outputs_g, state_2 = g(outputs_f, state_1)
return outputs_g, state_2

Monoidal

Syntax:

monoidal: "monoidal"i "(" term "," term ")"

Signature: If f : A → B and g : C → D, then monoidal(f, g) : A⊕C → B⊕D

Semantics: Parallel execution: f ⊗ g (apply f and g independently)

Examples:

# Apply different scalars to two inputs
"monoidal(scalar(2.0), scalar(3.0))"

# One operation, one pass-through
"monoidal(register(x), id)"

# Parallel composition with operations
"monoidal(add, multiplication)"

Input/Output Split:

Trace

Syntax:

trace: "trace"i "(" term ")"

Signature: If f : A⊕X → B⊕X, then trace(f) : A → B

Semantics: Feedback loop - connect last output to last input

Examples:

# Simple trace
"trace(id)"

# Exponential function via feedback
"trace(composition(composition(add, split), monoidal(id, register(X))))"

# Solve differential equation
"trace(composition(monoidal(id, register(t)), add))"

Convergence:

Atomic Operations

var(name)

Signature: 1 → 1

Semantics: Variable lookup or identity

"var(x)"
"var(velocity)"

Behavior:

const(value)

Signature: 0 → 1

Semantics: Generate constant stream

"const(3.14)"
"const(-1.0)"

Output: Stream with constant in first coefficient: [value, 0, 0, ...]

add

Signature: 2 → 1

Semantics: Element-wise addition of two streams

"add"

# In composition
"composition(monoidal(scalar(2.0), scalar(3.0)), add)"

Behavior: [a₀, a₁, ...] + [b₀, b₁, ...] = [a₀+b₀, a₁+b₁, ...]

multiplication

Signature: 2 → 1

Semantics: Element-wise multiplication

"multiplication"

# Square a value
"composition(split, multiplication)"

Behavior: [a₀, a₁, ...] * [b₀, b₁, ...] = [a₀·b₀, a₁·b₁, ...]

scalar(factor)

Signature: 1 → 1

Semantics: Multiply all elements by constant

"scalar(2.0)"
"scalar(-0.5)"

Behavior: scalar(c)([a₀, a₁, ...]) = [c·a₀, c·a₁, ...]

id

Signature: 1 → 1

Semantics: Identity - pass input unchanged

"id"

# Placeholder in monoidal
"monoidal(register(x), id)"

split

Signature: 1 → 2

Semantics: Duplicate input (fanout)

"split"

# Square a value
"composition(split, multiplication)"

Behavior: split(x) = [x, x]

Status: Stub implementation

register(dimension)

Signature: 1 → 1

Semantics: Integration along dimension

"register(x)"
"register(t)"

Behavior: Shifts coefficients right, pads with zero

Calculus-specific:

Stateful: Yes - saves last element as boundary

deregister(dimension)

Signature: 1 → 1

Semantics: Differentiation along dimension

"deregister(x)"
"deregister(t)"

Behavior: Shifts coefficients left, drops first

Calculus-specific:

Stateful: No - differentiation loses information

convolution(kernel)

Signature: 1 → 1

Semantics: Discrete convolution with kernel

"convolution(X)"

Status: Grammar defined, implementation pending

Complete Examples

Example 1: Fundamental Theorem of Calculus

from gimle.asgard.circuit.circuit import Circuit

# d/dx(∫f dx) = f
circuit = Circuit.from_string("composition(register(x), deregister(x))")

print(circuit.input_degree)   # 1
print(circuit.output_degree)  # 1

Example 2: Arithmetic Circuit

# (2x + 3y) * 0.5
circuit = Circuit.from_string(
    "composition("
    "  monoidal(scalar(2.0), scalar(3.0)),"  # Scale inputs
    "  add,"                                  # Add them
    "  scalar(0.5)"                           # Scale result
    ")"
)

print(circuit.input_degree)   # 2
print(circuit.output_degree)  # 1

Example 3: Parallel Processing

# Apply 2x to first input, 3y to second input
circuit = Circuit.from_string("monoidal(scalar(2.0), scalar(3.0))")

print(circuit.input_degree)   # 2
print(circuit.output_degree)  # 2

Type Checking

Circuits are validated at parse time:

Valid Compositions

# add (2→1) then scalar(2.0) (1→1) ✓
"composition(add, scalar(2.0))"

# register(x) (1→1) then deregister(x) (1→1) ✓
"composition(register(x), deregister(x))"

Invalid Compositions

# add (2→1) then split (1→2) then scalar(2.0) (1→1) ✗
# split outputs 2, but scalar expects 1
"composition(add, split, scalar(2.0))"  # ERROR

# Must wrap in composition:
"composition(composition(add, split), monoidal(scalar(2.0), scalar(2.0)))"  # ✓

Case Insensitivity

All keywords are case-insensitive:

# These are all equivalent:
"add"
"ADD"
"Add"

# Applies to all operations:
"composition(REGISTER(x), deregister(X))"

Note: Identifiers (dimension names, variable names) are case-sensitive:

BNF Notation Summary

term          ::= composition | monoidal | trace | atomic

composition   ::= "composition" "(" term "," term ")"
monoidal      ::= "monoidal" "(" term "," term ")"
trace         ::= "trace" "(" term ")"

atomic        ::= var | const | add | multiplication | scalar
                | id | split | register | deregister | convolution

var           ::= "var" "(" identifier ")"
const         ::= "const" "(" number ")"
scalar        ::= "scalar" "(" number ")"
register      ::= "register" "(" identifier ")"
deregister    ::= "deregister" "(" identifier ")"
convolution   ::= "convolution" "(" identifier ")"

add           ::= "add"
multiplication::= "multiplication"
id            ::= "id"
split         ::= "split"

identifier    ::= letter (letter | digit | "_")*
number        ::= ("-")? digit+ ("." digit+)?

Operation Status

Operation Signature Status
composition - ✅ Fully implemented
monoidal - ✅ Fully implemented
trace - ✅ Fully implemented
add 2 → 1 ✅ Fully implemented
multiplication 2 → 1 ✅ Fully implemented
scalar 1 → 1 ✅ Fully implemented
id 1 → 1 ✅ Fully implemented
const 0 → 1 ✅ Fully implemented
var 1 → 1 ✅ Fully implemented
register 1 → 1 ✅ Fully implemented
deregister 1 → 1 ✅ Fully implemented
split 1 → 2 Stub
convolution 1 → 1 Grammar only

Error Handling

Common syntax errors:

# Missing comma
"composition(add scalar(2.0))"  # ERROR

# Extra parenthesis
"composition(add,)"  # ERROR

# Invalid operation name
"composition(plus, times)"  # ERROR: 'plus' unknown

# Missing argument
"scalar()"  # ERROR: Missing number

# Wrong argument type
"register(1.2)"  # ERROR: Expects identifier, got number

Next Steps