Circuits
Circuits are computational networks that transform streams. They are built from atomic operations (add, multiply, integrate, etc.) and combinators (composition, monoidal, trace).
Circuit Syntax
Circuits use a functional composition style:
from gimle.asgard.circuit.circuit import Circuit
# Simple integration circuit
circuit = Circuit.from_string("register(x)")
# Fundamental theorem: integrate then differentiate
circuit = Circuit.from_string("composition(register(x), deregister(x))")
# Parallel operations
circuit = Circuit.from_string("monoidal(scalar(2.0), scalar(3.0))")
print(circuit)
# Output: composition(register(x), deregister(x)) [1->1]
Combinators
Combinators define how circuits connect:
Composition (Sequential)
Syntax: composition(f, g) - Apply f, then apply g to the result.
Signature: If f : A -> B and g : B -> C, then composition(f, g) : A -> C
# Integrate then differentiate
circuit = Circuit.from_string("composition(register(x), deregister(x))")
# Chain three operations
circuit = Circuit.from_string("composition(composition(add, scalar(2.0)), register(t))")
Monoidal (Parallel)
Syntax: monoidal(f, g) - Apply f and g independently to separate inputs.
Signature: If f : A -> B and g : C -> D, then monoidal(f, g) : A+C -> B+D
# Apply different scalars to two inputs
circuit = Circuit.from_string("monoidal(scalar(2.0), scalar(3.0))")
# One operation, one pass-through
circuit = Circuit.from_string("monoidal(register(x), id)")
Trace (Feedback)
Syntax: trace(f) - Create a feedback loop connecting the last output to the last input.
Signature: If f : A+X -> B+X, then trace(f) : A -> B
# Feedback loop for iterative computation
circuit = Circuit.from_string("trace(composition(composition(add, split), monoidal(id, register(X))))")
Atomic Operations
| Operation | Signature | Description |
|---|---|---|
register(dim) |
1 -> 1 |
Integration along dimension |
deregister(dim) |
1 -> 1 |
Differentiation along dimension |
add |
2 -> 1 |
Element-wise addition |
multiplication |
2 -> 1 |
Element-wise multiplication |
scalar(n) |
1 -> 1 |
Multiply by constant |
id |
1 -> 1 |
Identity (pass-through) |
split |
1 -> 2 |
Duplicate input |
const(n) |
0 -> 1 |
Generate constant stream |
var(name) |
1 -> 1 |
Variable lookup |
Register (Integration)
Shifts coefficients right, mathematically equivalent to integration:
circuit = Circuit.from_string("register(x)")
# Behavior on Taylor coefficients:
# [a, b, c] -> [0, a, b]
# Represents: f(x) -> integral of f(x) dx
Deregister (Differentiation)
Shifts coefficients left, mathematically equivalent to differentiation:
circuit = Circuit.from_string("deregister(x)")
# Behavior on Taylor coefficients:
# [a, b, c] -> [b, c]
# Represents: f(x) -> df/dx
Arithmetic Operations
# Add two inputs
circuit = Circuit.from_string("add")
# Multiply two inputs
circuit = Circuit.from_string("multiplication")
# Scale by constant
circuit = Circuit.from_string("scalar(2.5)")
# Generate constant
circuit = Circuit.from_string("const(3.14)")
Executing Circuits
Circuits transform streams of coefficients:
from gimle.asgard.circuit.circuit import Circuit
from gimle.asgard.runtime.stream import Stream, StreamState
import jax.numpy as jnp
# Create integration circuit
circuit = Circuit.from_string("register(x)")
# Input: f(x) = x (Taylor coefficients [0, 1])
input_stream = Stream(
data=jnp.array([[0.0, 1.0]]),
dim_labels=("x",),
chunk_size=1
)
# Execute circuit
outputs, state = circuit.execute([input_stream], StreamState())
print(f"Input: {input_stream.data[0]}") # [0, 1] = x
print(f"Output: {outputs[0].data[0]}") # [0, 0, 1] = x^2/2
Type Checking
Circuits are validated at parse time. The output degree of f must match the input degree of g in composition(f, g):
# Valid: add (2->1) then scalar(2.0) (1->1)
circuit = Circuit.from_string("composition(add, scalar(2.0))")
# Valid: register(x) (1->1) then deregister(x) (1->1)
circuit = Circuit.from_string("composition(register(x), deregister(x))")
# Check degrees
print(f"Input degree: {circuit.input_degree}")
print(f"Output degree: {circuit.output_degree}")
Complete Examples
Example 1: Fundamental Theorem of Calculus
# d/dx(integral f dx) = f
circuit = Circuit.from_string("composition(register(x), deregister(x))")
# This circuit is the identity: integrating then differentiating returns the original function
Example 2: Arithmetic Circuit
# (2x + 3y) * 0.5
circuit = Circuit.from_string(
"composition("
" monoidal(scalar(2.0), scalar(3.0))," # Scale inputs
" composition(add, scalar(0.5))" # Add and scale result
")"
)
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
Case Insensitivity
Keywords are case-insensitive:
# These are all equivalent
"add"
"ADD"
"Add"
# Identifiers (dimension names) are case-sensitive
"register(x)" # Different from register(X)
Next Steps
- Runtime - Execute circuits with different calculi
- Equations - Define equations that compile to circuits
- Getting Started - Complete walkthrough