Defining composite Blox with @composite

A composite blox is a blox that is really a graph of sub-blox. @composite combines the two macros you already know:

  • the header is a constructor signature, exactly like @blox;
  • the body is an @nodes / @connections graph, exactly like @graph.
@composite struct StructName(; name, namespace=nothing, other_kwargs...)
    # optional setup code (runs in the constructor, before the graph is built)

    @nodes begin
        (...)
    end

    @connections begin
        (...)
    end

    [@extra_fields begin (...) end]
end

If you've read the @graph docs, the @nodes and @connections blocks behave identically here — same node forms (single call, comprehension, for-block), same connection syntax. Namespacing is handled for you: nodes inherit the composite's namespace automatically, so you don't write namespace = ... on them.

What's different from @graph

  • Supertype defaults to AbstractComposite. As with @blox, you may declare a supertype after the header (StructName(; ...) <: SuperType) to override this; omit it and the struct subtypes AbstractComposite directly.

  • Nodes become fields. Every top-level assignment in @nodes becomes a field of the struct, so sub-components are reachable by name:

    str = Striatum(; name=:str, N_inhi=25)
    str.matrisome        # the sub-blox named `matrisome`
    str.inhibs[1]        # comprehension nodes are stored as-is (here, a vector)
  • Four fields are always present: name::Symbol, namespace::Union{Symbol,Nothing}, graph::GraphSystem, metadata::Dict{Symbol,Any} — then the node fields, in declaration order.

  • metadata is automatic, just like @blox: a metadata=Dict{Symbol,Any}() keyword argument is added to the constructor for you.

Extra fields with @extra_fields

Use an optional @extra_fields block to add struct fields beyond the node fields. Each entry is an assignment field = value, optionally typed as field::T = value. These fields are appended after the node fields, and their right-hand sides are evaluated in the constructor after the graph is built — so they may reference the node variables bound by @nodes.

@extra_fields begin
    pv_cells = [wta.pv for wta ∈ wta_circuits]
    cluster_label::Symbol = cluster_label
end

This is the composite counterpart of @blox's @extra_fields: a place to cache derived handles (e.g. a flattened list of specific sub-neurons) or to stash a constructor argument on the struct for later use.

Example

@composite struct Striatum(; name, namespace=nothing, N_inhi=25,
                           E_syn_inhi=-70, G_syn_inhi=1.2, I_bg=zeros(N_inhi), τ_inhi=70.0)
    I_bg = I_bg isa AbstractArray ? I_bg : fill(I_bg, N_inhi)

    @nodes begin
        inhibs = [HHInhiNeuron(name=Symbol(:inhi, i), I_bg=I_bg[i]) for i ∈ 1:N_inhi]
        matrisome = Matrisome(; name=:matrisome)
        striosome = Striosome(; name=:striosome)
    end

    @connections begin
        # ... wiring between inhibs / matrisome / striosome ...
    end
end

inhibs, matrisome, and striosome are fields of Striatum; every neuron lands in the composite's namespace with no namespace plumbing written by hand.