> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hardwave.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Composite Components

> Package internal sub-graphs as reusable composite components in Hardwave

A **composite component** wraps an internal sub-graph. From the outside it looks identical to a primitive (same ports, same parameters, same `solve()` interface). Internally, Hardwave wires sub-components together and routes external ports through them.

## When to use composites

Use a composite when you have a recurring assembly (a voltage divider, a motor driver stage, or a full control loop) that you want to drop into any graph as a single node.

## Example: Voltage divider

```python theme={null}
from hardwave.components import (
    CompositeComponent, BuildContext, Connection,
    ComponentMeta, ComponentRegistry, Parameter,
)
from hardwave.ports import input_port, output_port
from hardwave.stdlib.components.passive import Resistor

class VoltageDivider(CompositeComponent):
    meta = ComponentMeta(
        name="VoltageDivider",
        display_name="Voltage Divider",
        version="1.0.0",
        description="Two-resistor voltage divider",
        category="Electrical/Passive",
    )
    ports = [
        input_port("v_in",  "DCVoltage", "Input voltage"),
        output_port("i_r1", "Current",   "Current through R1"),
        output_port("i_r2", "Current",   "Current through R2"),
    ]
    parameters = [
        Parameter("r1", "R1", "Ω", float, 1_000.0),
        Parameter("r2", "R2", "Ω", float, 2_000.0),
    ]

    def build(self, ctx: BuildContext) -> None:
        ctx.add(Resistor("r1", param_values={"resistance": self.get_param("r1")}))
        ctx.add(Resistor("r2", param_values={"resistance": self.get_param("r2")}))

        ctx.map_input("v_in", "r1", "voltage")
        ctx.map_input("v_in", "r2", "voltage")
        ctx.map_output("i_r1", "r1", "current")
        ctx.map_output("i_r2", "r2", "current")

ComponentRegistry.instance().register(VoltageDivider)
```

## BuildContext API

| Method                                  | Purpose                                             |
| --------------------------------------- | --------------------------------------------------- |
| `ctx.add(component)`                    | Add an internal component instance                  |
| `ctx.connect(Connection(...))`          | Wire two internal ports together                    |
| `ctx.map_input(ext, int_id, int_port)`  | Route an external input to an internal port         |
| `ctx.map_output(ext, int_id, int_port)` | Expose an internal output on the external interface |

Use `ctx.connect(Connection("a", "port_out", "b", "port_in"))` for internal connections between sub-components. When multiple internal connections target the same **aggregating** input port, values are combined using that port's aggregation settings before the sub-component solver runs.

<Card title="Aggregating Input Ports" icon="merge" href="/guides/aggregating-ports">
  Multi-connection ports and custom aggregation
</Card>

## Using a composite

```python theme={null}
from hardwave.simulation import SimulationGraph, SimulationEngine
from hardwave.stdlib.components.passive import VoltageSource

graph = SimulationGraph()
graph.add_component(VoltageSource("vs", param_values={"voltage": 9.0}))
graph.add_component(VoltageDivider("div"))
graph.connect("vs", "voltage_out", "div", "v_in")

result = SimulationEngine(graph).run(inputs={})
print(result.get_output("div", "i_r1"))  # 0.009 A
print(result.get_output("div", "i_r2"))  # 0.0045 A
```

## How it works internally

When `solve()` is called on a composite, Hardwave calls `build()` once (lazily on first use) to construct the internal graph. It then topologically sorts the sub-graph, feeds external inputs through `_input_map`, gathers and aggregates values on multi-connection internal ports, and reads outputs through `_output_map`. Callers never interact with internal instance IDs.

<Note>
  Every sub-component class used inside a composite must be registered in `ComponentRegistry` before the composite is deserialized from a saved graph.
</Note>

See the [Greenhouse Tutorial](/guides/greenhouse-tutorial) for a full composite assembly example (`GreenhouseVentAssembly`).
