Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Seven Sketches Through Rust

The problem this chapter solves is:

Applied category theory can feel too large to connect to code. This chapter turns the seven major themes of Seven Sketches in Compositionality into small Rust blocks with tests.

The previous chapter introduced reusable structures such as functors, natural transformations, monoids, and local derivative rules. This chapter keeps the same reading style but changes scale. Instead of following the tiny ML pipeline, it takes seven broader ideas and asks how each one can be made concrete enough to inspect in Rust.

This chapter does not reproduce the paper. It gives executable handles for the ideas.

The repeated pattern is:

mathematical structure
  -> Rust type
  -> constructor or method
  -> law check

The Rust lesson is:

newtypes + private fields + validation + explicit composition

The category-theory lesson is:

objects + relationships + composition + laws

Reader orientation: Treat each sketch as a small modeling exercise. You are not expected to know the full mathematical theory before reading the Rust. Start from the type, then read the constructor, then read the law check.

Chapter Outcomes

By the end of this chapter, you should be able to:

  • name the one law, relation, or boundary each Rust sketch preserves,
  • explain which part of the source text each sketch deliberately does not implement,
  • transfer one sketch to a small software or ML design problem without overclaiming the mathematics.

What You Already Know

If you have modeled business rules, resource limits, database relationships, or state machines in Rust, you already know the software version of this chapter. The new idea is that applied category theory gives names to those compositional structures and asks which laws make them trustworthy.

Source Snapshots

The main module:

Source snapshot: src/sketches.rs
//! Rust models for the seven applied-category-theory sketches.
//!
//! This module is a study companion for *Seven Sketches in Compositionality*.
//! It does not try to encode all of category theory. Instead, each section
//! gives one small Rust model for the main structure of a sketch: orders,
//! resources, databases, co-design, signal flow, circuits, and logic of
//! behavior.

use crate::error::{CtError, CtResult};

/// A finite order used for the first sketch: observations can be refined into
/// features, scores, and decisions.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum InformationLevel {
    Observation,
    Feature,
    Score,
    Decision,
}

impl InformationLevel {
    /// Returns true when information at this level can flow into `target`.
    pub fn can_flow_to(self, target: Self) -> bool {
        self <= target
    }

    /// Least upper bound in this small total order.
    pub fn join(self, other: Self) -> Self {
        self.max(other)
    }
}

const INFORMATION_LEVELS: [InformationLevel; 4] = [
    InformationLevel::Observation,
    InformationLevel::Feature,
    InformationLevel::Score,
    InformationLevel::Decision,
];

/// Checks reflexivity and transitivity for the finite information order.
pub fn information_order_obeys_preorder_laws() -> bool {
    for level in INFORMATION_LEVELS {
        if !level.can_flow_to(level) {
            return false;
        }
    }

    for first in INFORMATION_LEVELS {
        for second in INFORMATION_LEVELS {
            for third in INFORMATION_LEVELS {
                let premise = first.can_flow_to(second) && second.can_flow_to(third);
                if premise && !first.can_flow_to(third) {
                    return false;
                }
            }
        }
    }

    true
}

/// Number of concrete features in a tiny model.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct FeatureCount(usize);

impl FeatureCount {
    pub fn new(value: usize) -> CtResult<Self> {
        if value == 0 {
            return Err(CtError::EmptyInput("feature count"));
        }

        Ok(Self(value))
    }

    pub fn value(&self) -> usize {
        self.0
    }
}

/// Number of abstract layers used to summarize feature capacity.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct LayerBudget(usize);

impl LayerBudget {
    pub fn new(value: usize) -> CtResult<Self> {
        if value == 0 {
            return Err(CtError::EmptyInput("layer budget"));
        }

        Ok(Self(value))
    }

    pub fn value(&self) -> usize {
        self.0
    }
}

const FEATURES_PER_LAYER: usize = 4;

/// Abstracts concrete features to the minimum layer budget that can hold them.
pub fn abstract_to_layer_budget(features: FeatureCount) -> CtResult<LayerBudget> {
    LayerBudget::new(features.value().div_ceil(FEATURES_PER_LAYER))
}

/// Concretizes an abstract layer budget back to feature capacity.
pub fn concretize_layer_budget(layers: LayerBudget) -> FeatureCount {
    FeatureCount(layers.value() * FEATURES_PER_LAYER)
}

/// Checks the Galois-connection law for the feature/layer abstraction.
pub fn feature_layer_galois_law_holds(
    features: FeatureCount,
    layers: LayerBudget,
) -> CtResult<bool> {
    let abstracted_fits = abstract_to_layer_budget(features)?.value() <= layers.value();
    let concrete_fits = features.value() <= concretize_layer_budget(layers).value();

    Ok(abstracted_fits == concrete_fits)
}

/// A non-negative amount in a resource bundle.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct ResourceAmount(usize);

impl ResourceAmount {
    pub fn new(value: usize) -> Self {
        Self(value)
    }

    pub fn value(&self) -> usize {
        self.0
    }
}

/// Compute and memory resources composed by component-wise addition.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResourceBundle {
    compute: ResourceAmount,
    memory: ResourceAmount,
}

impl ResourceBundle {
    pub fn new(compute: ResourceAmount, memory: ResourceAmount) -> Self {
        Self { compute, memory }
    }

    pub fn compute(&self) -> ResourceAmount {
        self.compute
    }

    pub fn memory(&self) -> ResourceAmount {
        self.memory
    }

    /// Monoidal product for independent resources.
    pub fn tensor(&self, other: &Self) -> Self {
        Self {
            compute: ResourceAmount::new(self.compute.value() + other.compute.value()),
            memory: ResourceAmount::new(self.memory.value() + other.memory.value()),
        }
    }

    /// Resource preorder: supply can cover demand when every component is large
    /// enough.
    pub fn can_supply(&self, demand: &Self) -> bool {
        self.compute >= demand.compute && self.memory >= demand.memory
    }
}

/// Demonstrates monotonicity of the monoidal resource operation.
pub fn resource_tensor_is_monotone() -> bool {
    let small_supply = ResourceBundle::new(ResourceAmount::new(2), ResourceAmount::new(8));
    let large_supply = ResourceBundle::new(ResourceAmount::new(4), ResourceAmount::new(16));
    let fixed = ResourceBundle::new(ResourceAmount::new(1), ResourceAmount::new(2));

    large_supply.can_supply(&small_supply)
        && large_supply
            .tensor(&fixed)
            .can_supply(&small_supply.tensor(&fixed))
}

/// Identifier for a department row.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DepartmentId(usize);

impl DepartmentId {
    pub fn new(value: usize) -> Self {
        Self(value)
    }

    pub fn value(&self) -> usize {
        self.0
    }
}

/// Identifier for an employee row.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct EmployeeId(usize);

impl EmployeeId {
    pub fn new(value: usize) -> Self {
        Self(value)
    }

    pub fn value(&self) -> usize {
        self.0
    }
}

/// A row in the employee table. The department field is the schema arrow
/// `Employee -> Department`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EmployeeRecord {
    id: EmployeeId,
    department: DepartmentId,
}

impl EmployeeRecord {
    pub fn new(id: EmployeeId, department: DepartmentId) -> Self {
        Self { id, department }
    }

    pub fn id(&self) -> EmployeeId {
        self.id
    }

    pub fn department(&self) -> DepartmentId {
        self.department
    }
}

/// A tiny database instance: schema objects become sets of ids, and schema
/// arrows become functions between those sets.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CompanyInstance {
    departments: Vec<DepartmentId>,
    employees: Vec<EmployeeRecord>,
}

impl CompanyInstance {
    pub fn new(
        departments: impl IntoIterator<Item = DepartmentId>,
        employees: impl IntoIterator<Item = EmployeeRecord>,
    ) -> CtResult<Self> {
        let departments = departments.into_iter().collect::<Vec<_>>();
        let employees = employees.into_iter().collect::<Vec<_>>();

        for employee in &employees {
            if !departments.contains(&employee.department()) {
                return Err(CtError::ShapeMismatch {
                    op: "database instance",
                    expected: String::from("employee departments exist in Department"),
                    got: format!("missing department {}", employee.department().value()),
                });
            }
        }

        Ok(Self {
            departments,
            employees,
        })
    }

    pub fn departments(&self) -> &[DepartmentId] {
        &self.departments
    }

    pub fn employees(&self) -> &[EmployeeRecord] {
        &self.employees
    }

    pub fn department_of(&self, employee_id: EmployeeId) -> Option<DepartmentId> {
        self.employees
            .iter()
            .find(|employee| employee.id() == employee_id)
            .map(EmployeeRecord::department)
    }

    pub fn foreign_keys_resolve(&self) -> bool {
        self.employees
            .iter()
            .all(|employee| self.departments.contains(&employee.department()))
    }
}

/// Requests per second needed by a design.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Throughput(usize);

impl Throughput {
    pub fn new(value: usize) -> CtResult<Self> {
        if value == 0 {
            return Err(CtError::EmptyInput("throughput"));
        }

        Ok(Self(value))
    }

    pub fn value(&self) -> usize {
        self.0
    }
}

/// Millisecond latency boundary.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct LatencyMs(usize);

impl LatencyMs {
    pub fn new(value: usize) -> CtResult<Self> {
        if value == 0 {
            return Err(CtError::EmptyInput("latency"));
        }

        Ok(Self(value))
    }

    pub fn value(&self) -> usize {
        self.0
    }
}

/// Functional need in a co-design problem.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DesignRequirement {
    minimum_throughput: Throughput,
    maximum_latency: LatencyMs,
}

impl DesignRequirement {
    pub fn new(minimum_throughput: Throughput, maximum_latency: LatencyMs) -> Self {
        Self {
            minimum_throughput,
            maximum_latency,
        }
    }
}

/// Implementation offered by a candidate component.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ImplementationOffer {
    throughput: Throughput,
    latency: LatencyMs,
}

impl ImplementationOffer {
    pub fn new(throughput: Throughput, latency: LatencyMs) -> Self {
        Self {
            throughput,
            latency,
        }
    }
}

/// A Bool-valued feasibility relation between requirements and offers.
#[derive(Debug, Clone, Copy)]
pub struct FeasibilityRelation;

impl FeasibilityRelation {
    pub fn relates(requirement: DesignRequirement, offer: ImplementationOffer) -> bool {
        offer.throughput >= requirement.minimum_throughput
            && offer.latency <= requirement.maximum_latency
    }
}

/// A scalar coefficient in a signal-flow matrix.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SignalCoefficient(i32);

impl SignalCoefficient {
    pub fn new(value: i32) -> Self {
        Self(value)
    }

    pub fn zero() -> Self {
        Self(0)
    }

    pub fn value(&self) -> i32 {
        self.0
    }

    fn add(self, other: Self) -> Self {
        Self(self.value() + other.value())
    }

    fn multiply(self, other: Self) -> Self {
        Self(self.value() * other.value())
    }
}

/// Number of rows in a signal-flow matrix.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MatrixRows(usize);

impl MatrixRows {
    pub fn new(value: usize) -> CtResult<Self> {
        if value == 0 {
            return Err(CtError::EmptyInput("matrix rows"));
        }

        Ok(Self(value))
    }

    pub fn value(&self) -> usize {
        self.0
    }
}

/// Number of columns in a signal-flow matrix.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MatrixCols(usize);

impl MatrixCols {
    pub fn new(value: usize) -> CtResult<Self> {
        if value == 0 {
            return Err(CtError::EmptyInput("matrix columns"));
        }

        Ok(Self(value))
    }

    pub fn value(&self) -> usize {
        self.0
    }
}

/// Matrix semantics for a signal-flow graph.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SignalMatrix {
    rows: MatrixRows,
    cols: MatrixCols,
    coefficients: Vec<Vec<SignalCoefficient>>,
}

impl SignalMatrix {
    pub fn new(
        rows: MatrixRows,
        cols: MatrixCols,
        coefficients: Vec<Vec<SignalCoefficient>>,
    ) -> CtResult<Self> {
        if coefficients.len() != rows.value() {
            return Err(CtError::ShapeMismatch {
                op: "signal matrix",
                expected: format!("{} rows", rows.value()),
                got: format!("{} rows", coefficients.len()),
            });
        }

        for row in &coefficients {
            if row.len() != cols.value() {
                return Err(CtError::ShapeMismatch {
                    op: "signal matrix",
                    expected: format!("{} columns", cols.value()),
                    got: format!("{} columns", row.len()),
                });
            }
        }

        Ok(Self {
            rows,
            cols,
            coefficients,
        })
    }

    pub fn rows(&self) -> MatrixRows {
        self.rows
    }

    pub fn cols(&self) -> MatrixCols {
        self.cols
    }

    pub fn coefficients(&self) -> &[Vec<SignalCoefficient>] {
        &self.coefficients
    }

    /// Matrix composition. If `previous` is `A -> B` and `self` is `B -> C`,
    /// the result is `A -> C`.
    pub fn compose_after(&self, previous: &Self) -> CtResult<Self> {
        if previous.rows.value() != self.cols.value() {
            return Err(CtError::ShapeMismatch {
                op: "signal matrix composition",
                expected: format!("middle dimension {}", self.cols.value()),
                got: format!("middle dimension {}", previous.rows.value()),
            });
        }

        let mut coefficients =
            vec![vec![SignalCoefficient::zero(); previous.cols.value()]; self.rows.value()];

        for (output_row, output_coefficients) in coefficients.iter_mut().enumerate() {
            for (input_col, coefficient) in output_coefficients.iter_mut().enumerate() {
                let mut total = SignalCoefficient::zero();

                for middle in 0..self.cols.value() {
                    total = total.add(
                        self.coefficients[output_row][middle]
                            .multiply(previous.coefficients[middle][input_col]),
                    );
                }

                *coefficient = total;
            }
        }

        Self::new(self.rows, previous.cols, coefficients)
    }
}

/// A named boundary port in an open circuit.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PortName(&'static str);

impl PortName {
    pub fn new(value: &'static str) -> CtResult<Self> {
        if value.trim().is_empty() {
            return Err(CtError::EmptyInput("port name"));
        }

        Ok(Self(value))
    }

    pub fn as_str(&self) -> &'static str {
        self.0
    }
}

/// Positive resistance value.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResistanceOhms(usize);

impl ResistanceOhms {
    pub fn new(value: usize) -> CtResult<Self> {
        if value == 0 {
            return Err(CtError::InvalidQuantity {
                kind: "resistance",
                value: value as i64,
            });
        }

        Ok(Self(value))
    }

    pub fn value(&self) -> usize {
        self.0
    }
}

/// A simple resistor component between two ports.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CircuitComponent {
    from: PortName,
    to: PortName,
    resistance: ResistanceOhms,
}

impl CircuitComponent {
    pub fn resistor(from: PortName, to: PortName, resistance: ResistanceOhms) -> Self {
        Self {
            from,
            to,
            resistance,
        }
    }

    pub fn from(&self) -> PortName {
        self.from
    }

    pub fn to(&self) -> PortName {
        self.to
    }

    pub fn resistance(&self) -> ResistanceOhms {
        self.resistance
    }
}

/// An open circuit with input ports, output ports, and internal components.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OpenCircuit {
    inputs: Vec<PortName>,
    outputs: Vec<PortName>,
    components: Vec<CircuitComponent>,
}

impl OpenCircuit {
    pub fn new(
        inputs: impl IntoIterator<Item = PortName>,
        outputs: impl IntoIterator<Item = PortName>,
        components: impl IntoIterator<Item = CircuitComponent>,
    ) -> CtResult<Self> {
        let inputs = inputs.into_iter().collect::<Vec<_>>();
        let outputs = outputs.into_iter().collect::<Vec<_>>();
        let components = components.into_iter().collect::<Vec<_>>();

        if inputs.is_empty() {
            return Err(CtError::EmptyInput("circuit inputs"));
        }

        if outputs.is_empty() {
            return Err(CtError::EmptyInput("circuit outputs"));
        }

        Ok(Self {
            inputs,
            outputs,
            components,
        })
    }

    pub fn input_count(&self) -> usize {
        self.inputs.len()
    }

    pub fn output_count(&self) -> usize {
        self.outputs.len()
    }

    pub fn component_count(&self) -> usize {
        self.components.len()
    }

    /// Serial composition wires this circuit's outputs into the next circuit's
    /// inputs.
    pub fn then(&self, next: &Self) -> CtResult<Self> {
        if self.output_count() != next.input_count() {
            return Err(CtError::ShapeMismatch {
                op: "open circuit serial composition",
                expected: format!("{} next inputs", self.output_count()),
                got: format!("{} next inputs", next.input_count()),
            });
        }

        let mut components = self.components.clone();
        components.extend_from_slice(&next.components);

        Self::new(self.inputs.clone(), next.outputs.clone(), components)
    }

    /// Parallel composition keeps the two open interfaces side by side.
    pub fn parallel(&self, other: &Self) -> CtResult<Self> {
        let mut inputs = self.inputs.clone();
        inputs.extend_from_slice(&other.inputs);

        let mut outputs = self.outputs.clone();
        outputs.extend_from_slice(&other.outputs);

        let mut components = self.components.clone();
        components.extend_from_slice(&other.components);

        Self::new(inputs, outputs, components)
    }
}

/// Truth values used for behavior classification.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TruthValue {
    False,
    True,
}

impl TruthValue {
    pub fn and(self, other: Self) -> Self {
        match (self, other) {
            (TruthValue::True, TruthValue::True) => TruthValue::True,
            _ => TruthValue::False,
        }
    }

    pub fn implies(self, other: Self) -> Self {
        match (self, other) {
            (TruthValue::True, TruthValue::False) => TruthValue::False,
            _ => TruthValue::True,
        }
    }
}

/// Discrete time point in a behavior trace.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct TimeTick(usize);

impl TimeTick {
    pub fn new(value: usize) -> Self {
        Self(value)
    }

    pub fn value(&self) -> usize {
        self.0
    }
}

/// Closed interval of time ticks.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TimeInterval {
    start: TimeTick,
    end: TimeTick,
}

impl TimeInterval {
    pub fn new(start: TimeTick, end: TimeTick) -> CtResult<Self> {
        if start > end {
            return Err(CtError::InvalidInterval {
                start: start.value(),
                end: end.value(),
            });
        }

        Ok(Self { start, end })
    }

    pub fn start(&self) -> TimeTick {
        self.start
    }

    pub fn end(&self) -> TimeTick {
        self.end
    }
}

/// Local safety result on one interval.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LocalSafetyCheck {
    interval: TimeInterval,
    truth: TruthValue,
}

impl LocalSafetyCheck {
    pub fn new(interval: TimeInterval, truth: TruthValue) -> Self {
        Self { interval, truth }
    }

    pub fn interval(&self) -> TimeInterval {
        self.interval
    }

    pub fn truth(&self) -> TruthValue {
        self.truth
    }
}

/// A cover of local behavior checks. The global result is true only when all
/// local checks are true.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SafetyCover(Vec<LocalSafetyCheck>);

impl SafetyCover {
    pub fn new(checks: impl IntoIterator<Item = LocalSafetyCheck>) -> CtResult<Self> {
        let checks = checks.into_iter().collect::<Vec<_>>();

        if checks.is_empty() {
            return Err(CtError::EmptyInput("safety cover"));
        }

        Ok(Self(checks))
    }

    pub fn global_truth(&self) -> TruthValue {
        self.0
            .iter()
            .fold(TruthValue::True, |truth, check| truth.and(check.truth()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn information_order_has_preorder_laws() {
        assert!(information_order_obeys_preorder_laws());
        assert_eq!(
            InformationLevel::Feature.join(InformationLevel::Decision),
            InformationLevel::Decision
        );
    }

    #[test]
    fn feature_layer_abstraction_obeys_galois_law() -> CtResult<()> {
        let features = FeatureCount::new(9)?;
        let layers = LayerBudget::new(3)?;

        assert!(feature_layer_galois_law_holds(features, layers)?);
        assert_eq!(abstract_to_layer_budget(features)?.value(), 3);
        assert_eq!(concretize_layer_budget(layers).value(), 12);
        Ok(())
    }

    #[test]
    fn resource_tensor_is_order_preserving() {
        assert!(resource_tensor_is_monotone());
    }

    #[test]
    fn database_instance_resolves_schema_arrow() -> CtResult<()> {
        let research = DepartmentId::new(1);
        let platform = DepartmentId::new(2);
        let ada = EmployeeId::new(7);
        let instance =
            CompanyInstance::new([research, platform], [EmployeeRecord::new(ada, research)])?;

        assert!(instance.foreign_keys_resolve());
        assert_eq!(instance.department_of(ada), Some(research));
        Ok(())
    }

    #[test]
    fn database_instance_rejects_missing_department_reference() {
        let research = DepartmentId::new(1);
        let missing_department = DepartmentId::new(99);
        let ada = EmployeeId::new(7);

        assert!(matches!(
            CompanyInstance::new([research], [EmployeeRecord::new(ada, missing_department)]),
            Err(CtError::ShapeMismatch {
                op: "database instance",
                ..
            })
        ));
    }

    #[test]
    fn feasibility_relation_matches_requirement_to_offer() -> CtResult<()> {
        let requirement = DesignRequirement::new(Throughput::new(100)?, LatencyMs::new(80)?);
        let offer = ImplementationOffer::new(Throughput::new(120)?, LatencyMs::new(50)?);

        assert!(FeasibilityRelation::relates(requirement, offer));
        Ok(())
    }

    #[test]
    fn signal_matrices_compose_like_flow_graph_semantics() -> CtResult<()> {
        let duplicate = SignalMatrix::new(
            MatrixRows::new(2)?,
            MatrixCols::new(1)?,
            vec![
                vec![SignalCoefficient::new(1)],
                vec![SignalCoefficient::new(1)],
            ],
        )?;
        let add_weighted = SignalMatrix::new(
            MatrixRows::new(1)?,
            MatrixCols::new(2)?,
            vec![vec![SignalCoefficient::new(2), SignalCoefficient::new(3)]],
        )?;

        let composed = add_weighted.compose_after(&duplicate)?;

        assert_eq!(composed.coefficients(), &[vec![SignalCoefficient::new(5)]]);
        Ok(())
    }

    #[test]
    fn signal_matrix_composition_rejects_mismatched_middle_dimension() -> CtResult<()> {
        let previous = SignalMatrix::new(
            MatrixRows::new(2)?,
            MatrixCols::new(1)?,
            vec![
                vec![SignalCoefficient::new(1)],
                vec![SignalCoefficient::new(1)],
            ],
        )?;
        let next = SignalMatrix::new(
            MatrixRows::new(1)?,
            MatrixCols::new(3)?,
            vec![vec![
                SignalCoefficient::new(1),
                SignalCoefficient::new(1),
                SignalCoefficient::new(1),
            ]],
        )?;

        assert!(matches!(
            next.compose_after(&previous),
            Err(CtError::ShapeMismatch {
                op: "signal matrix composition",
                ..
            })
        ));
        Ok(())
    }

    #[test]
    fn open_circuits_compose_in_series_and_parallel() -> CtResult<()> {
        let input = PortName::new("input")?;
        let middle = PortName::new("middle")?;
        let output = PortName::new("output")?;
        let first = OpenCircuit::new(
            [input],
            [middle],
            [CircuitComponent::resistor(
                input,
                middle,
                ResistanceOhms::new(10)?,
            )],
        )?;
        let second = OpenCircuit::new(
            [middle],
            [output],
            [CircuitComponent::resistor(
                middle,
                output,
                ResistanceOhms::new(20)?,
            )],
        )?;

        let serial = first.then(&second)?;
        let parallel = first.parallel(&second)?;

        assert_eq!(serial.input_count(), 1);
        assert_eq!(serial.output_count(), 1);
        assert_eq!(serial.component_count(), 2);
        assert_eq!(parallel.input_count(), 2);
        assert_eq!(parallel.output_count(), 2);
        Ok(())
    }

    #[test]
    fn open_circuit_serial_composition_rejects_boundary_mismatch() -> CtResult<()> {
        let input = PortName::new("input")?;
        let output = PortName::new("output")?;
        let left = OpenCircuit::new([input], [output], [])?;
        let right = OpenCircuit::new([input, output], [output], [])?;

        assert!(matches!(
            left.then(&right),
            Err(CtError::ShapeMismatch {
                op: "open circuit serial composition",
                ..
            })
        ));
        Ok(())
    }

    #[test]
    fn local_behavior_truth_glues_to_global_truth() -> CtResult<()> {
        let first = LocalSafetyCheck::new(
            TimeInterval::new(TimeTick::new(0), TimeTick::new(5))?,
            TruthValue::True,
        );
        let second = LocalSafetyCheck::new(
            TimeInterval::new(TimeTick::new(5), TimeTick::new(10))?,
            TruthValue::True,
        );
        let cover = SafetyCover::new([first, second])?;

        assert_eq!(cover.global_truth(), TruthValue::True);
        assert_eq!(
            TruthValue::True.implies(TruthValue::False),
            TruthValue::False
        );
        Ok(())
    }
}

The runnable companion:

Source snapshot: examples/05_seven_sketches.rs
use category_theory_transformer_rs::{
    CircuitComponent, CompanyInstance, CtResult, DepartmentId, DesignRequirement, EmployeeId,
    EmployeeRecord, FeasibilityRelation, FeatureCount, ImplementationOffer, InformationLevel,
    LatencyMs, LayerBudget, LocalSafetyCheck, MatrixCols, MatrixRows, OpenCircuit, PortName,
    ResistanceOhms, ResourceAmount, ResourceBundle, SafetyCover, SignalCoefficient, SignalMatrix,
    Throughput, TimeInterval, TimeTick, TruthValue, abstract_to_layer_budget,
    concretize_layer_budget, feature_layer_galois_law_holds, information_order_obeys_preorder_laws,
    resource_tensor_is_monotone,
};

fn main() -> CtResult<()> {
    println!(
        "orders obey preorder laws: {}",
        information_order_obeys_preorder_laws()
    );
    println!(
        "join(feature, decision): {:?}",
        InformationLevel::Feature.join(InformationLevel::Decision)
    );

    let features = FeatureCount::new(9)?;
    let layers = LayerBudget::new(3)?;
    println!(
        "feature/layer Galois law: {}",
        feature_layer_galois_law_holds(features, layers)?
    );
    println!(
        "abstract 9 features to {} layers; concretize 3 layers to {} features",
        abstract_to_layer_budget(features)?.value(),
        concretize_layer_budget(layers).value()
    );

    let encoder = ResourceBundle::new(ResourceAmount::new(2), ResourceAmount::new(8));
    let decoder = ResourceBundle::new(ResourceAmount::new(3), ResourceAmount::new(10));
    println!("combined resource bundle: {:?}", encoder.tensor(&decoder));
    println!(
        "resource tensor monotone: {}",
        resource_tensor_is_monotone()
    );

    let research = DepartmentId::new(1);
    let platform = DepartmentId::new(2);
    let ada = EmployeeId::new(7);
    let instance =
        CompanyInstance::new([research, platform], [EmployeeRecord::new(ada, research)])?;
    println!(
        "employee {:?} belongs to department {:?}",
        ada,
        instance.department_of(ada)
    );

    let requirement = DesignRequirement::new(Throughput::new(100)?, LatencyMs::new(80)?);
    let offer = ImplementationOffer::new(Throughput::new(120)?, LatencyMs::new(50)?);
    println!(
        "co-design offer feasible: {}",
        FeasibilityRelation::relates(requirement, offer)
    );

    let duplicate = SignalMatrix::new(
        MatrixRows::new(2)?,
        MatrixCols::new(1)?,
        vec![
            vec![SignalCoefficient::new(1)],
            vec![SignalCoefficient::new(1)],
        ],
    )?;
    let add_weighted = SignalMatrix::new(
        MatrixRows::new(1)?,
        MatrixCols::new(2)?,
        vec![vec![SignalCoefficient::new(2), SignalCoefficient::new(3)]],
    )?;
    let composed = add_weighted.compose_after(&duplicate)?;
    println!(
        "signal-flow matrix semantics: {:?}",
        composed.coefficients()
    );

    let input = PortName::new("input")?;
    let middle = PortName::new("middle")?;
    let output = PortName::new("output")?;
    let first_circuit = OpenCircuit::new(
        [input],
        [middle],
        [CircuitComponent::resistor(
            input,
            middle,
            ResistanceOhms::new(10)?,
        )],
    )?;
    let second_circuit = OpenCircuit::new(
        [middle],
        [output],
        [CircuitComponent::resistor(
            middle,
            output,
            ResistanceOhms::new(20)?,
        )],
    )?;
    println!(
        "serial circuit component count: {}",
        first_circuit.then(&second_circuit)?.component_count()
    );

    let safety = SafetyCover::new([
        LocalSafetyCheck::new(
            TimeInterval::new(TimeTick::new(0), TimeTick::new(5))?,
            TruthValue::True,
        ),
        LocalSafetyCheck::new(
            TimeInterval::new(TimeTick::new(5), TimeTick::new(10))?,
            TruthValue::True,
        ),
    ])?;
    println!("global behavior truth: {:?}", safety.global_truth());
    println!();
    println!("Typed transformation:");
    println!("InformationLevel <= InformationLevel checks preorder");
    println!("FeatureCount <-> LayerBudget checks Galois law");
    println!("ResourceBundle x ResourceBundle -> ResourceBundle");
    println!("EmployeeRecord -> DepartmentId must resolve in CompanyInstance");
    println!("DesignRequirement x ImplementationOffer -> bool");
    println!("SignalMatrix x SignalMatrix -> SignalMatrix when dimensions match");
    println!("OpenCircuit x OpenCircuit -> OpenCircuit when ports match");
    println!("SafetyCover -> TruthValue");

    Ok(())
}

Paper Map To Rust

Use this table as the navigation layer.

Paper areaMain contentRust companion
Generative effectspreorders, monotone maps, Galois connectionsInformationLevel, FeatureCount, LayerBudget
Resourcesmonoidal preorders, resource composition, enrichmentResourceBundle, ResourceAmount
Databasesschemas as categories, instances as functorsCompanyInstance, EmployeeRecord, DepartmentId
Co-designfeasibility relations and profunctor-like reasoningDesignRequirement, ImplementationOffer, FeasibilityRelation
Signal flowsyntax, semantics, matrices, compositionSignalMatrix, SignalCoefficient
Circuitsopen systems, ports, serial and parallel compositionOpenCircuit, CircuitComponent, PortName
Logic of behaviortruth values, intervals, local-to-global checksTruthValue, TimeInterval, SafetyCover

Each section below uses the same three-part lens:

Rust syntax
ML or software concept
Category theory concept

Source Scope Contract

This chapter is a study companion, not a replacement for the source text. Each Rust model preserves one inspectable idea and deliberately leaves the larger mathematical development outside the tiny example.

Paper areaWhat the Rust sketch preservesWhat it does not claim
Generative effectsorder laws and one Galois-style capacity lawa complete treatment of generative effects
Resourcescomponentwise resource composition plus monotonicityfull enriched category theory
Databasesschema-like reference integrity through typed IDsa general database semantics framework
Co-designfeasibility as a relation between requirements and offersfull profunctor theory
Signal flowmatrix composition and middle-dimension checksa complete syntax-and-semantics account for signal-flow graphs
Circuitsopen interfaces and serial boundary matchinga full circuit algebra
Logic of behaviorlocal interval truth combined into a global claimsheaf theory or a full temporal logic

Use the table as a precision guard. When the Rust code checks one law, say which law it checks. When the source paper develops a larger theory, do not pretend the small Rust model has implemented all of it.

PDF-To-Rust Reading Contract

The arXiv record describes Seven Sketches in Compositionality as a long invitation to applied category theory, built around concrete examples and seven major sketches. That matters for how to use this chapter. The goal is not to compress every page into a smaller page. The goal is to give every major sketch a Rust handle that a reader can run, inspect, and test.

Complete coverage in this companion chapter means:

every major sketch area has a Rust handle;
every Rust handle names one protected law, relation, or boundary;
every protected claim says what the source text still develops beyond the code;
every reader can run one command before arguing about the abstraction.

Use this ledger while reading the PDF beside the Rust:

When the source text discussesAsk in this chapterLocal evidence
an order, relation, or refinementWhich enum, newtype, or method names the ordered world?InformationLevel::can_flow_to, FeatureCount, LayerBudget
a resource or compositional quantityWhich operation combines independent pieces?ResourceBundle::tensor
a schema, instance, or referenceWhich constructor rejects invalid references?CompanyInstance::new
a feasibility relationWhich requirement-offer pair is accepted or rejected?FeasibilityRelation::relates
a signal-flow or matrix compositionWhich middle dimension must match?SignalMatrix::compose_after
an open system or circuit interfaceWhich boundary ports must agree?OpenCircuit::then
a local-to-global behavior claimWhich local checks combine into a global result?SafetyCover::global_truth

Page-To-Rust Decision Ladder

Use this ladder when you are reading the PDF page by page and do not yet know what to implement.

Source paragraph shapeFirst Rust moveEvidence to seekSafe non-claim
definition or named objectcreate a newtype, enum, or struct with private fieldsconstructor rejects an impossible valuethis is one typed domain object, not the whole theory
relation, order, or feasibility statementwrite a method that returns bool or CtResult<T>one passing case and one rejected casethis is one relation, not a complete semantics
composition rulewrite a method that consumes two typed inputsmismatched middle object, dimension, or port returns Err(...)this is one composition boundary, not a general algebra
theorem, law, or proof stepwrite the smallest law testthe test names the exact law being checkedone passing test is local evidence, not a proof of the source text
worked example or application storybuild a tiny fixture and print one output linecargo run --example 05_seven_sketches shows the protected boundarythe fixture is an analogy, not a production model
richer machinery beyond the local handlewrite a larger-claim-not-implemented sentencethe non-claim names the missing theory explicitlythe chapter cites the source; it does not compress it

The ladder keeps the reading order concrete:

source paragraph shape
  -> first Rust move
  -> local evidence
  -> safe non-claim

Example decisions:

If the source section is about…Do not start with…Start with…
schemas and instancesa trait called CategoryCompanyInstance::new rejecting a dangling department
signal-flow semanticsa full graph languageSignalMatrix::compose_after checking the middle dimension
open-system compositiona general operad libraryOpenCircuit::then checking output/input ports

This is how the chapter can cover the whole source at the level promised by the book: not every theorem becomes a library feature, but every major reading shape has a disciplined path toward a Rust handle, a test, and a non-claim.

This is also the limit of the chapter. If a PDF section develops a richer construction than the Rust handle, record the richer construction as context, not as something the code has proved. The safe sentence is:

The source develops a larger theory here.
This Rust handle checks one executable boundary from that theory.

Source-Backed Precision Rules

This chapter uses external sources as scope guards. Each source supports a limited teaching claim, and each claim is tied to one local Rust boundary or test. The chapter does not claim that src/sketches.rs implements the full source text, a general categorical semantics library, or a production ML architecture theory.

The Bridge Back To Tiny ML section below is part of this source contract. Its check sentence is:

This sketch helps me reject this ML shortcut.
SourceWhat the source supportsLocal rule in this chapterRust evidence
Seven SketchesApplied category theory can be introduced through concrete examples such as databases, circuits, dynamical systems, and other real-world structures.Treat every sketch as one executable handle for one source idea, not as a replacement for the full mathematical development.InformationLevel, ResourceBundle, CompanyInstance, OpenCircuit, SafetyCover
MIT Applied Category Theory OCWThe seven topic areas can be studied as a course sequence: orders, resources, databases, co-design, signal flow, circuits, and logic of behavior.Keep the chapter order and Paper Map To Rust aligned with that applied-category sequence.cargo run --example 05_seven_sketches
Category Theory for ProgrammingCategory-theory vocabulary can be taught through programming-shaped structures.Explain the programming boundary before naming the category-theory pattern.information_order_obeys_preorder_laws, feature_layer_galois_law_holds, resource_tensor_is_monotone
Compositional Deep LearningCategorical schemas, functorial structure, and composition invariants can appear in neural-network settings under stated assumptions.Use the database and co-design sketches as ML transfer analogies only; do not claim the crate learns functors or implements the thesis.CompanyInstance, FeasibilityRelation, database_instance_rejects_missing_department_reference
Categorical Deep LearningArchitecture discussions can separate constraints a model should satisfy from implementations that realize them.Use co-design as a tiny Requirement x Offer -> Bool boundary, not as a theory of all neural architectures.DesignRequirement, ImplementationOffer, FeasibilityRelation::relates
Rust Book: EnumsEnums encode values that must be one variant from a finite set.Use enums for finite state-like domains before adding laws around them.InformationLevel, TruthValue
Rust Book: TraitsTraits name shared behavior and make contracts explicit.Treat laws and methods as contracts that tests must witness, not as prose-only claims.SignalMatrix::compose_after, OpenCircuit::then, SafetyCover::global_truth

The transfer pattern is:

source idea -> local Rust model -> law or boundary check

For this chapter, that means reading cargo run --example 05_seven_sketches and cargo test sketches::tests as evidence for the tiny models above, not as evidence that the full seven-sketch source text, categorical deep-learning literature, or every applied-category construction has been implemented.

Choose A Sketch Without Losing The Tiny ML Thread

The source paper deliberately tours many application areas. This companion chapter keeps that breadth, but the book still has one main learning path: small typed systems that make ML structure inspectable.

Use this table to decide how deeply to read each sketch on a first pass.

SketchRead deeply when you needTiny ML transferSafe first-pass treatment
Information orderstaged representations or approval statesraw text, tokens, features, scores, and decisions form ordered levels of processed informationCore transfer
Feature/layer planningtwo views of model capacityfeature counts and layer budgets are different descriptions of model sizeOptional but useful
Resourcesdeployment or training constraintscompute and memory limits constrain model choicesOptional but practical
Database instancestructured training databad references in source data should fail before trainingCore transfer
Co-design feasibilityrequirements versus implementationsa model may satisfy some accuracy, latency, or memory requirements and fail othersCore transfer
Signal matriceslinear maps and shape compatibilitycomposed linear stages need matching middle dimensionsCore transfer
Open circuitscomponent interfacestyped ML components compose only when output and input boundaries matchCore transfer
Logic of behaviorlocal checks and global claimsevery batch or interval must satisfy the invariant before the global claim is trustedOptional but useful

On a first reading, focus on the rows marked core transfer. They connect most directly to the tiny ML pipeline. The optional rows are not less important; they are just farther from the first runnable model.

The chapter is successful if you can leave each sketch with one sentence:

This Rust model prevents this invalid composition.

Worked Example: Ordering Information Levels

The smallest first-principles version of this chapter is an ordered enum. Rust can derive an order for enum variants, and that gives the code a concrete way to ask whether one information level can safely flow into another:

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum Level {
    Observation,
    Feature,
    Decision,
}

assert!(Level::Observation <= Level::Decision);
assert!(Level::Feature <= Level::Feature);
}

The real src/sketches.rs module uses the same idea with named domain types, validation, and law checks.

Self-Check

Before reading the first sketch, explain why Observation <= Decision is a modeling rule, not just a comparison between enum variants.

One Method Across Seven Sketches

Do not read the chapter as seven unrelated theory notes. Each sketch follows the same modeling method:

engineering problem
  -> named Rust values
  -> validated construction
  -> composition operation or relation
  -> law or boundary test

That method is the same one used in the tiny ML pipeline. The difference is the domain. Instead of tokens, logits, and parameters, this chapter uses resources, database rows, signal matrices, open circuits, and local behavior checks.

When a sketch feels abstract, ask one question:

What invalid composition should this model prevent?

The answer usually points to the real engineering value of the categorical shape.

The chapter’s applied models can be scanned by the structure they protect:

SketchRust handleValid structureRejected or checked boundary
Information orderInformationLevelinformation flows upward through refinement levelspreorder laws check reflexivity and transitivity
Feature/layer planningFeatureCount, LayerBudgetconcrete and abstract capacity views agreeGalois law checks both directions of fit
ResourcesResourceBundlecompute and memory combine componentwisemonotonicity checks supply still respects demand
DatabasesCompanyInstanceemployee records refer to known departmentsmissing department references return Err(...)
Co-designFeasibilityRelationoffers satisfy throughput and latency requirementsinfeasible offers are not related
Signal flowSignalMatrixmatrices compose when middle dimensions matchmismatched dimensions return Err(...)
Open circuitsOpenCircuitserial composition connects matching port boundariesboundary mismatch returns Err(...)
Behavior logicSafetyCoverlocal interval checks combine into global truthunknown local truth prevents a false global guarantee

This table is the chapter’s law-and-boundary index. The point is not to memorize eight rows. The point is to see that each sketch earns its abstraction by protecting one concrete relationship.

Bridge Back To Tiny ML

The source text and the MIT course both use applied examples to make category theory portable across domains. This chapter uses the same move in a smaller way: each sketch should transfer back to one tiny ML design pressure.

Use the bridge below when the chapter starts to feel like a separate category theory tour.

SketchTiny ML pressureRust handle to inspectBad shortcut the sketch helps rejectSafe non-claim
Information orderraw observations, features, scores, and decisions should not be interchangeableInformationLevel::can_flow_totreating a score as if it were already a decisionthe enum is a teaching model, not a calibrated decision theory
Feature/layer planningconcrete feature count and abstract model capacity are different viewsFeatureCount, LayerBudgettreating abstraction and concretization as inverse functionsthe Galois-style law is a tiny planning check, not a full architecture search method
Resourcestraining and inference choices depend on compute and memory togetherResourceBundle::tensorcollapsing all resource constraints into one raw numberthe bundle is a two-resource sketch, not a deployment cost model
Database instancetraining rows should not carry dangling references into feature extractionCompanyInstance::newletting malformed structured data reach the modelthis validates one schema arrow, not a full data platform
Co-design feasibilitymodel choice is often a relation between requirements and candidate implementationsFeasibilityRelation::relatesforcing every design question into a single functionone feasible offer is not proof that every implementation satisfies the constraint
Signal matriceslinear stages compose only when dimensions line upSignalMatrix::compose_aftermultiplying stages before checking the middle dimensionthis is matrix composition, not a full autodiff or neural-network framework
Open circuitscomponents need explicit input and output boundariesOpenCircuit::thenwiring pieces by name while ignoring boundary shapethe circuit model is an interface analogy, not a full circuit algebra
Logic of behaviorlocal checks must support global claimsSafetyCover::global_truthclaiming global safety while one local interval failedconjunction over intervals is a tiny behavior check, not full sheaf theory

Read each row as a transfer sentence:

This sketch helps me reject this ML shortcut.

That sentence is the practical reason to keep the sketch in the book. The formal category-theory vocabulary matters only after the engineering shortcut is visible.

Transfer Triage Card

Use this card when a source idea feels too large to turn into code. The goal is not to shrink the source. The goal is to choose one local boundary that can be inspected.

If the transfer feels like…Do this firstReady when you can write…
a broad theory claimshrink to one law, relation, or boundarySource claim -> local Rust handle
a vocabulary listchoose one constructor, method, example line, or testRust handle -> protected relationship
a passing example onlyname the invalid case it rejects or the law it checksprotected relationship -> rejected shortcut
an ML analogyname the ML object, constraint, or composition it maps totiny ML transfer -> safe non-claim
a research sourcestate what the source does not license this chapter to claimnon-claim -> evidence command

The completed transfer card has seven fields:

source idea:
local Rust handle:
protected law, relation, or boundary:
invalid shortcut rejected:
tiny ML transfer:
larger claim not implemented:
local evidence command or test:

Example:

source idea: schemas and instances
local Rust handle: CompanyInstance::new
protected law, relation, or boundary: EmployeeRecord -> DepartmentId must resolve
invalid shortcut rejected: letting a missing department reach feature extraction
tiny ML transfer: validate structured training rows before training
larger claim not implemented: a general categorical database semantics
local evidence command or test: cargo test sketches::tests --lib

Second example:

source idea: open systems have boundaries
local Rust handle: OpenCircuit::then
protected law, relation, or boundary: previous outputs must match next inputs
invalid shortcut rejected: wiring components by label while ignoring shape
tiny ML transfer: Tokenizer -> Embedder is legal only when the output object
matches the next input object
larger claim not implemented: decorated cospans, hypergraph categories, and
operads for general circuit composition
local evidence command or test: cargo test sketches::tests --lib

This follows the source discipline used above. Seven Sketches gives a broad tour through concrete examples. MIT’s course frames category theory as a way to organize formal systems and transfer knowledge between them. Categorical deep-learning work distinguishes architecture constraints from implementations. The local job here is smaller: choose one implementable handle, name one protected relationship, and state the non-claim.

Sketch 1: Information Order

The problem this block solves is:

Some concepts are ordered by refinement. An observation can be refined into a feature, a feature into a score, and a score into a decision.

The block begins:

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum InformationLevel {
    Observation,
    Feature,
    Score,
    Decision,
}

Rust Syntax

This is an enum.

The variants are ordered because the enum derives:

PartialOrd, Ord

That means Rust can compare:

InformationLevel::Observation <= InformationLevel::Decision

The methods:

pub fn can_flow_to(self, target: Self) -> bool {
    self <= target
}

pub fn join(self, other: Self) -> Self {
    self.max(other)
}

reuse that ordering.

can_flow_to checks whether information can move upward.

join returns the more informative of two levels in this total order.

ML Or Software Concept

ML systems often move through levels of processed information:

raw observation
  -> extracted feature
  -> model score
  -> final decision

The order prevents treating a low-level observation as if it were already a decision.

Category Theory Concept

This is a preorder-shaped example.

A preorder needs:

reflexivity: a <= a
transitivity: if a <= b and b <= c, then a <= c

The law check:

information_order_obeys_preorder_laws()

iterates over the finite set and verifies those rules.

Transfer Task: Ordered States

Model a workflow from your own codebase as an ordered enum, such as:

enum ReviewState {
    Draft,
    Reviewed,
    Published,
}

Then write the Rust sentence you would want to be true:

Draft can flow to Published
Published cannot flow to Draft
state can always flow to itself

The transfer is complete when you can name the invalid flow your order prevents.

Sketch 1 Continued: Feature And Layer Galois Law

The problem this block solves is:

A concrete feature count and an abstract layer budget are different worlds, but they can be coordinated by a law.

The key types:

pub struct FeatureCount(usize);
pub struct LayerBudget(usize);

The conversion functions:

pub fn abstract_to_layer_budget(features: FeatureCount) -> CtResult<LayerBudget>

pub fn concretize_layer_budget(layers: LayerBudget) -> FeatureCount

Rust Syntax

Both FeatureCount and LayerBudget are newtypes around usize.

Their constructors reject zero because neither a zero feature count nor a zero layer budget is useful in this model.

abstract_to_layer_budget divides feature count by FEATURES_PER_LAYER and rounds up:

features.value().div_ceil(FEATURES_PER_LAYER)

concretize_layer_budget multiplies layers by features per layer.

ML Or Software Concept

This models capacity planning.

Concrete features might be:

9 measured feature channels

The abstract layer budget might be:

3 layers

The law says the two planning views agree about what fits.

Category Theory Concept

The checked law is:

abstract(features) <= layers
if and only if
features <= concretize(layers)

That is the shape of a Galois connection.

The two directions are not inverses.

They are coordinated by an order law.

Transfer Task: Concrete And Abstract Capacity

Pick one concrete measure and one abstract budget from software work:

concrete: batch size, feature count, request rate
abstract: GPU count, layer budget, service tier

Write the two functions:

abstract(concrete) -> abstract budget
concretize(abstract budget) -> concrete capacity

The transfer is complete when you can state the law in both directions:

abstract(concrete) fits budget
if and only if
concrete fits concretize(budget)

Sketch 2: Resources

The problem this block solves is:

Independent resources need a way to combine, and resource supply must respect demand ordering.

The key block:

pub struct ResourceBundle {
    compute: ResourceAmount,
    memory: ResourceAmount,
}

Rust Syntax

ResourceAmount wraps a usize.

ResourceBundle stores two resource dimensions:

compute
memory

The monoidal operation is:

pub fn tensor(&self, other: &Self) -> Self

It adds compute to compute and memory to memory.

The preorder check is:

pub fn can_supply(&self, demand: &Self) -> bool

It returns true only when every resource component is large enough.

ML Or Software Concept

This is the shape of deployment capacity:

encoder resources + decoder resources = combined resources

A machine can satisfy a demand only when it has enough compute and memory.

Category Theory Concept

This is a monoidal preorder.

The preorder is can_supply.

The monoidal product is tensor.

The law check:

resource_tensor_is_monotone()

shows that adding the same fixed resource bundle to both sides preserves the order.

Transfer Task: Resource Bundle

Extend the idea to three resource dimensions:

compute
memory
disk

Name the constructor boundary and the law check:

ResourceBundle::new(compute, memory, disk)
resource_tensor_is_monotone

The transfer is complete when you can explain why componentwise addition should not turn an adequate supply into an inadequate supply.

Sketch 3: Database Instance

The problem this block solves is:

A database row with a foreign key should not point at a missing row.

The key types:

pub struct DepartmentId(usize);
pub struct EmployeeId(usize);

pub struct EmployeeRecord {
    id: EmployeeId,
    department: DepartmentId,
}

pub struct CompanyInstance {
    departments: Vec<DepartmentId>,
    employees: Vec<EmployeeRecord>,
}

Rust Syntax

DepartmentId and EmployeeId are distinct newtypes.

That prevents mixing department IDs and employee IDs.

EmployeeRecord contains:

employee id
department id

CompanyInstance::new collects departments and employees, then checks every employee department exists:

if !departments.contains(&employee.department()) {
    return Err(CtError::ShapeMismatch { ... });
}

ML Or Software Concept

Many ML systems depend on structured data.

If a row references missing data, downstream feature extraction or training can fail later in a confusing place.

This code rejects invalid relational structure at construction time.

Category Theory Concept

The schema can be read as:

Employee -> Department

An instance assigns sets of rows to schema objects.

The foreign key is a function from employees to departments.

CompanyInstance::new checks that the function is defined for every employee.

Transfer Task: Foreign Key Boundary

Translate the pattern to another schema arrow:

Task -> Project
Order -> Customer
Comment -> Post

Name two newtypes and one record:

ProjectId
TaskId
TaskRecord { id: TaskId, project: ProjectId }

The transfer is complete when you can write the constructor failure in plain English: “reject a task whose project id is missing from the project table.”

Sketch 4: Co-Design Feasibility

The problem this block solves is:

Some relationships are not functions. A requirement and an implementation offer are related only when constraints are satisfied.

The key types:

pub struct DesignRequirement {
    minimum_throughput: Throughput,
    maximum_latency: LatencyMs,
}

pub struct ImplementationOffer {
    throughput: Throughput,
    latency: LatencyMs,
}

pub struct FeasibilityRelation;

Rust Syntax

Throughput and LatencyMs are validated newtypes.

DesignRequirement stores the minimum acceptable throughput and maximum acceptable latency.

ImplementationOffer stores what an implementation actually provides.

The relation is:

pub fn relates(requirement: DesignRequirement, offer: ImplementationOffer) -> bool {
    offer.throughput >= requirement.minimum_throughput
        && offer.latency <= requirement.maximum_latency
}

ML Or Software Concept

This models feasibility:

Can this model/service/deployment satisfy this requirement?

For example:

required throughput: at least 100 requests/sec
required latency: at most 80 ms
offer: 120 requests/sec and 50 ms

The offer is feasible.

Category Theory Concept

This is relation-shaped rather than function-shaped.

It is the small Bool-valued version of profunctor-like reasoning:

Requirement x Offer -> Bool

Not every design problem should be forced into:

A -> B

Some should be modeled as constraints or relations.

This also gives a small handle for reading newer categorical deep-learning work. At architecture scale, a model can be discussed in terms of constraints it should satisfy and implementations that realize those constraints. This Rust sketch keeps only the first tiny version of that idea:

DesignRequirement x ImplementationOffer -> bool

The important distinction is:

QuestionCo-design sketch answer
What should be true?throughput is high enough and latency is low enough
What implementation evidence do we have?one ImplementationOffer value with checked throughput and latency
What does the relation decide?whether that offer satisfies that requirement

That is not a full theory of neural architectures. It is the learner-sized boundary that prevents a common overclaim: one implementation example is not the same thing as the whole constraint space.

Transfer Task: Feasibility Relation

Model a relation that is not a function:

Requirement x Offer -> Bool

For example, use:

minimum accuracy
maximum memory
maximum latency

Then write the architecture version:

ArchitectureConstraint x CandidateImplementation -> Bool

The transfer is complete when you can explain why many offers may satisfy one requirement, one offer may satisfy many requirements, and one passing offer is not proof that every future implementation satisfies the architecture constraint.

Sketch 5: Signal Matrices

The problem this block solves is:

Signal-flow diagrams need executable semantics. In this companion, matrices provide that meaning.

The key types:

pub struct SignalCoefficient(i32);
pub struct MatrixRows(usize);
pub struct MatrixCols(usize);

pub struct SignalMatrix {
    rows: MatrixRows,
    cols: MatrixCols,
    coefficients: Vec<Vec<SignalCoefficient>>,
}

Rust Syntax

MatrixRows and MatrixCols reject zero.

SignalMatrix::new validates that the coefficient matrix has the promised shape:

number of rows matches MatrixRows
number of columns in every row matches MatrixCols

The composition method:

pub fn compose_after(&self, previous: &Self) -> CtResult<Self>

requires compatible middle dimensions.

Then it performs matrix multiplication using:

add
multiply
sum over the middle dimension

ML Or Software Concept

This is the same shape as composing linear layers or signal-processing stages.

If one stage maps:

A -> B

and another maps:

B -> C

then the composite maps:

A -> C

The dimensions must line up.

Category Theory Concept

Signal-flow syntax gets matrix semantics.

The important principle is functorial semantics:

meaning(composed diagram)
=
composition of meanings

The code enforces the same middle-dimension law that ordinary morphism composition enforces.

Transfer Task: Shape-Safe Composition

Write the shape of two stages before writing any coefficients:

A -> B
B -> C

Then write one invalid pair:

A -> B
D -> C

The transfer is complete when you can name the exact middle dimension that must match for matrix composition to make sense.

Sketch 6: Open Circuits

The problem this block solves is:

A circuit is not only internal components. It also has a boundary where it can connect to other circuits.

The key types:

pub struct PortName(&'static str);
pub struct ResistanceOhms(usize);

pub struct CircuitComponent {
    from: PortName,
    to: PortName,
    resistance: ResistanceOhms,
}

pub struct OpenCircuit {
    inputs: Vec<PortName>,
    outputs: Vec<PortName>,
    components: Vec<CircuitComponent>,
}

Rust Syntax

PortName::new rejects empty names.

ResistanceOhms::new rejects zero resistance.

OpenCircuit::new rejects circuits with no inputs or no outputs.

Serial composition:

pub fn then(&self, next: &Self) -> CtResult<Self>

checks:

self output count == next input count

Parallel composition:

pub fn parallel(&self, other: &Self) -> CtResult<Self>

puts the two boundaries side by side.

ML Or Software Concept

This looks like component architecture:

input interface
internal implementation
output interface

Composition should fail when interfaces do not match.

That rule applies to services, data pipelines, neural layers, and circuit-like systems.

Category Theory Concept

This is the open-system idea.

The boundary is part of the object.

Composition is controlled by boundary compatibility.

The paper develops this with cospans, hypergraph categories, decorated cospans, and operads. The Rust code gives a small typed analogue.

Transfer Task: Interface Boundary

Model two components by boundary only:

Tokenizer: Text -> TokenSequence
Embedder: TokenSequence -> HiddenSequence

Then write one invalid serial composition:

Tokenizer: Text -> TokenSequence
Classifier: Logits -> Label

The transfer is complete when you can say which output boundary failed to match which input boundary.

Sketch 7: Logic Of Behavior

The problem this block solves is:

A system may be safe on local time intervals. The code needs a way to combine local safety checks into one global result.

The key types:

pub enum TruthValue {
    False,
    True,
}

pub struct TimeTick(usize);

pub struct TimeInterval {
    start: TimeTick,
    end: TimeTick,
}

pub struct LocalSafetyCheck {
    interval: TimeInterval,
    truth: TruthValue,
}

pub struct SafetyCover(Vec<LocalSafetyCheck>);

Rust Syntax

TruthValue implements Boolean-style operations:

and
implies

TimeInterval::new rejects intervals where start is after end.

SafetyCover::new rejects an empty list of checks.

global_truth folds all local truths with and:

self.0
    .iter()
    .fold(TruthValue::True, |truth, check| truth.and(check.truth()))

ML Or Software Concept

This models safety or behavior validation over time:

check interval 0..5
check interval 5..10
combine into global result

If every local check is true, global truth is true.

If any local check is false, global truth is false.

Category Theory Concept

This is a small analogue of local-to-global reasoning.

The sheaf-like idea is:

local facts can determine a global fact when they glue coherently

The code uses a simple conjunction model, not full sheaf theory.

The important lesson is that proof-like information becomes explicit data, not an informal comment.

Transfer Task: Local Checks To Global Claim

Pick one invariant over time:

loss is finite
latency stays below the budget
no batch has an empty token sequence

Split it into local intervals and assign a truth value to each interval.

The transfer is complete when you can explain why one false local check must make the global claim false.

Tests As Exercise Solutions

The problem this block solves is:

The laws should be runnable, not only described in prose.

The test module checks preorder laws, the feature/layer Galois law, resource tensor monotonicity, database foreign-key resolution, feasibility relation behavior, signal matrix composition, open circuit serial and parallel composition, and local-to-global truth.

It also includes negative boundary tests. A database instance with a missing department reference is rejected. Signal matrices with incompatible middle dimensions cannot compose. Open circuits with mismatched serial boundaries do not wire together. Those failures are part of the teaching point: the model is useful because it rejects incoherent structure near the boundary.

Rust Syntax

Every law is a normal Rust test marked with:

#[test]

Tests that may fail through constructors return:

CtResult<()>

so they can use ?.

ML Or Software Concept

The tests act as executable learning checks.

If a future change breaks a law, the project should fail quickly.

Category Theory Concept

The tests are small law checks.

They are not formal proofs, but they keep the implementation aligned with the claimed structure.

Run The Companion

Run:

cargo run --example 05_seven_sketches

The output gives one executable handle per sketch:

orders obey preorder laws: true
feature/layer Galois law: true
resource tensor monotone: true
employee EmployeeId(7) belongs to department Some(DepartmentId(1))
co-design offer feasible: true
signal-flow matrix semantics: [[SignalCoefficient(5)]]
serial circuit component count: 2
global behavior truth: True
Typed transformation:
InformationLevel <= InformationLevel checks preorder
FeatureCount <-> LayerBudget checks Galois law
ResourceBundle x ResourceBundle -> ResourceBundle
EmployeeRecord -> DepartmentId must resolve in CompanyInstance
DesignRequirement x ImplementationOffer -> bool
SignalMatrix x SignalMatrix -> SignalMatrix when dimensions match
OpenCircuit x OpenCircuit -> OpenCircuit when ports match
SafetyCover -> TruthValue

Example Output Transfer Checklist

Use the companion output as a boundary map. Each line should tell you which structure is being protected.

Example outputRust handleProtected structureShortcut to reject
orders obey preorder laws: trueInformationLevelinformation can flow upward through an ordered refinement pathtreating an enum order as arbitrary display order
feature/layer Galois law: trueFeatureCount, LayerBudgetconcrete and abstract capacity views agree by a two-way fit lawtreating abstraction and concretization as inverse functions
resource tensor monotone: trueResourceBundle::tensoradding resources componentwise preserves supply orderingcombining compute and memory as one raw number
employee ... belongs to department ...CompanyInstance::newevery employee department reference resolvesletting a missing foreign key reach later feature extraction
co-design offer feasible: trueFeasibilityRelation::relatesrequirements and offers form a relation, not a single functionforcing every design question into A -> B
signal-flow matrix semantics: ...SignalMatrix::compose_aftermatrix meanings compose only when middle dimensions matchmultiplying stages before checking shape
serial circuit component count: 2OpenCircuit::thenserial composition requires matching boundarieswiring components by name alone
global behavior truth: TrueSafetyCover::global_truthlocal checks combine into a global claimclaiming global safety while one local interval is false

The typed lines at the bottom of the output are not extra decoration. They name the form each sketch protects: order, Galois law, monoidal resource composition, database instance, feasibility relation, matrix composition, open system composition, and local-to-global truth.

For the full validation gate:

cargo test --all-targets --all-features

Core Mental Model

In Rust terms:

each sketch becomes concrete types, constructors, methods, and tests

In ML or software terms:

orders, resources, schemas, feasibility, signal flow, interfaces, and safety
are all engineering structures

In category-theory terms:

the useful part is compositionality: make structure visible, then make
composition obey laws

What To Remember

The seven sketches are not seven disconnected topics.

They repeat one engineering discipline:

name the objects
name the relationships
control construction
define composition
check the law

That is also the discipline used by the tiny ML pipeline in the rest of the course.

Where This Leaves Us

This chapter showed that the book’s main discipline is not limited to language modeling. Orders, resources, database instances, feasibility relations, signal matrices, open circuits, and local behavior checks can all be read in the same way: name the values, constrain construction, define composition, and test the law that makes the composition trustworthy.

The remaining practice material in Exercises asks you to use that reading method yourself. The exercises are not meant to test memorized definitions. They are meant to train the habit of translating one Rust block into its software role and its categorical shape.

Further Reading

Do not treat these sources as a separate theory shelf. Use them to improve one local Rust sentence at a time.

Start from this local evidence:

cargo run --example 05_seven_sketches
cargo test sketches::tests --lib
src/sketches.rs
examples/05_seven_sketches.rs

Then read the sources in this order:

SourceWhat to transfer back into this chapterLocal evidence to inspect
Seven SketchesEach sketch is one compositional structure: orders, resources, schemas, co-design, signal flow, open systems, or local-to-global behavior.Paper Map To Rust, Source Scope Contract, Example Output Transfer Checklist
MIT Applied Category Theory OCWThe seven topic areas form a study sequence, not a bag of unrelated examples.cargo run --example 05_seven_sketches output order
Category Theory for ProgrammingProgramming examples should carry the vocabulary before the formal name is emphasized.InformationLevel, ResourceBundle, SignalMatrix, OpenCircuit
Compositional Deep LearningCategorical schemas and composition invariants can appear in neural-network settings under explicit assumptions.CompanyInstance, FeasibilityRelation, database_instance_rejects_missing_department_reference
Categorical Deep LearningArchitecture-level claims should separate constraints from implementations that realize them.DesignRequirement x ImplementationOffer -> bool
Rust Book: EnumsFinite domains are often clearer as enums than as unstructured numbers.InformationLevel, TruthValue
Rust Book: TraitsShared behavior should become an explicit contract before laws are tested around it.SignalMatrix::compose_after, OpenCircuit::then, SafetyCover::global_truth

After reading one source, answer four questions:

  1. Which Rust handle did it clarify?
  2. Which law, relation, or boundary did it support?
  3. Which larger claim did the source not license?
  4. Which command or test shows the local evidence?

For this chapter, the commands are:

cargo run --example 05_seven_sketches
cargo test sketches::tests --lib

For terminology recovery, use:

If a source does not help you name a Rust handle, a law or boundary, a non-claim, and an evidence command, it has not transferred back into this chapter yet.

Practice After This Chapter

Use Exercise 10 to test one sketch law or inspect one negative test. The goal is to choose a structure, name the invalid state it rejects, and explain the result through Rust syntax, software or ML meaning, and category-theory shape.

Retrieval Practice

Recall

Recover the chapter map before choosing a transfer example.

Name the sketch that models ordered refinement from observation to decision.

Name the sketch that rejects an employee row whose department is missing.

Name the sketch that rejects matrix composition when the middle dimensions do not match.

Name the sketch that rejects serial component composition when output and input boundaries do not match.

Explain

Explain the protected boundary.

For InformationLevel, explain why Observation can flow to Decision but a decision should not silently flow backward to an observation.

For CompanyInstance, explain why missing department references should be rejected at construction time instead of during later feature extraction.

For SignalMatrix, explain why the middle dimension must match before matrix composition can run.

For OpenCircuit, explain why a boundary mismatch is an interface error, not a numeric error.

Apply

Use the companion output as evidence.

The example prints:

orders obey preorder laws: true
feature/layer Galois law: true
resource tensor monotone: true
signal-flow matrix semantics: [[SignalCoefficient(5)]]
serial circuit component count: 2
global behavior truth: True

Choose two printed lines. For each one, write:

Rust value or function:
software meaning:
category-theory shape:
invalid structure prevented or law checked:

Then pick one engineering concept from your own work, such as permissions, queues, schema references, resource budgets, or service interfaces. Describe the objects, relationships, and one law or boundary test you would want the code to check.

Debug

For each broken model, name the missing structure:

using raw usize for both EmployeeId and DepartmentId
letting a missing department reference enter the training data
composing signal matrices without checking the middle dimension
serially wiring components by name without checking boundary counts
claiming global safety when one local interval is false

A strong answer should use the chapter’s repeated method:

name the objects
name the relationships
control construction
define composition
check the law

The goal is not to memorize every sketch. The goal is to recognize what each model refuses to let pass as valid structure.