Circuit Grammar Specification
Complete grammar specification for Asgard's computational circuit syntax.
Overview
Circuits in Asgard represent computational networks composed of:
- Atomic operations - Fundamental transformations (add, multiply, integrate, etc.)
- Combinators - Structural operations (composition, monoidal, trace)
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:
- First
ninputs go tof - Remaining
minputs go tog - Outputs concatenated:
[outputs_f..., outputs_g...]
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:
- Iterates until convergence (max 100 iterations)
- Tolerance: 1e-6
- May not converge for all circuits
Atomic Operations
var(name)
Signature: 1 → 1
Semantics: Variable lookup or identity
"var(x)"
"var(velocity)"
Behavior:
- If variable name in
var_values: use that value - Otherwise: pass input through (identity)
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
[a, b, c] → [0, a, b]- Mathematical:
∫f(x)dx
Calculus-specific:
- RealCalculus: Classical integration
- StochasticCalculus: Stochastic integral
- DiscreteCalculus: Cumulative sum
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
[a, b, c] → [b, c]- Mathematical:
df/dx
Calculus-specific:
- RealCalculus: Classical derivative
- StochasticCalculus: Ill-defined (non-differentiable)
- DiscreteCalculus: Finite differences
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:
register(x)≠register(X)var(myVar)≠var(MyVar)
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
- Equation Grammar - Equation syntax specification
- Operations - Detailed operation reference
- Circuits Concepts - Using circuits