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