> ## 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.

# Core Concepts

> Types, ports, components, solvers, and the simulation engine in Hardwave

Hardwave treats hardware as a directed acyclic graph of typed components. Understanding a few core concepts is enough to build anything from a resistor network to a robot drivetrain.

## Everything is a component

A component has:

* **Ports**: typed slots that data flows through (inputs or outputs)
* **Parameters**: static configuration values that do not change during a simulation run
* **A solver**: the function that turns inputs into outputs

Components connect into a **graph**. The simulation engine walks the graph in topological order, calls each component's solver with its current inputs, and propagates results downstream.

You never call `solve()` directly during simulation. The engine does that for you. You *can* call `solve()` directly for unit testing.

## Types

Types define the **semantic meaning** of signals. `DCVoltage` and `Current` are both floats at runtime, but Hardwave treats them as incompatible. You cannot accidentally wire a voltage output into a current input.

### Built-in types

Import the standard library to register 30 hardware signal types:

```python theme={null}
import hardwave.stdlib

from hardwave.types import TypeRegistry
reg = TypeRegistry.instance()

reg.get("DCVoltage")       # V
reg.get("Current")         # A
reg.get("Resistance")      # Ω
reg.get("Torque")          # N·m
reg.get("Temperature")     # °C
reg.get("PWMSignal")       # dict {frequency_hz, duty_cycle}

for t in reg.list_types():
    print(f"{t.name:25s} {t.unit}")
```

### Custom types

```python theme={null}
from hardwave.types import HardwaveType, TypeRegistry

Luminosity = HardwaveType(
    name="Luminosity",
    unit="lux",
    description="Illuminance",
    min_value=0.0,
)
TypeRegistry.instance().register(Luminosity)
```

For composite or bus types (CAN frames, I2C payloads), subclass `HardwaveType` and override `validate()`.

## Ports

Ports are declared with helper functions:

```python theme={null}
from hardwave.ports import input_port, output_port, aggregating_input_port, PortAggregation

ports = [
    input_port("voltage", "DCVoltage", "Applied voltage (V)"),
    output_port("current", "Current", "Current through the element (A)"),
]
```

Each port has a name, a type name (looked up in `TypeRegistry`), and a description. Input ports can be marked optional with a default value.

### Single vs aggregating inputs

| Helper                     | Connections     | Use case                                            |
| -------------------------- | --------------- | --------------------------------------------------- |
| `input_port()`             | One source only | Most signal paths                                   |
| `aggregating_input_port()` | Many sources    | Sum currents, average sensors, custom combine logic |

Fan-out (one output → many inputs) works on any port. Fan-in (many outputs → one input) requires `aggregating_input_port()` on the target.

```python theme={null}
aggregating_input_port("current_in", "Current", aggregation=PortAggregation.SUM)
```

See [Aggregating Input Ports](/guides/aggregating-ports) for built-in modes, custom callables, and connection limits.

## Components and the registry

Component classes are registered in the global `ComponentRegistry` singleton. The `@component` decorator attaches metadata and registers the class automatically:

```python theme={null}
from hardwave import component, input_port, output_port, Component, Parameter

@component(
    name="Thermistor",
    display_name="NTC Thermistor",
    version="1.0.0",
    description="NTC thermistor: resistance decreases with temperature",
    category="Sensors",
)
class Thermistor(Component):
    ports = [
        input_port("temperature", "Temperature", "Ambient temperature (°C)"),
        output_port("resistance", "Resistance", "Thermistor resistance (Ω)"),
    ]
    parameters = [
        Parameter("r_nominal", "Nominal Resistance", "Ω", float, 10_000.0),
    ]

    def solve(self, inputs: dict) -> dict:
        # ... physics here
        return {"resistance": R}
```

See [Defining Components](/guides/defining-components) for the full authoring guide.

## Solvers

Every component has a **solver** that computes outputs from inputs. The default solver is the component's own `solve()` method, but you can inject a more sophisticated one at any time:

| Solver              | Use case                                             |
| ------------------- | ---------------------------------------------------- |
| `FormulaSolver`     | Wrap any callable (Ohm's law, piecewise models)      |
| `LookupTableSolver` | Interpolate measured or datasheet data               |
| `ODESolver`         | Physics-based transient models (RC, motors, thermal) |
| `MLModelSolver`     | Local joblib-backed solver (advanced)                |
| Hosted ML model     | Cloud-trained solver via `hardwave.premium`          |

See [Solvers](/simulation/solvers) for examples of each.

## Simulation domains

Hardwave supports two simulation domains:

| Domain           | Behaviour                                                   |
| ---------------- | ----------------------------------------------------------- |
| **Steady-state** | Solve the graph once; return a single value per output port |
| **Transient**    | Step through time; return time series for every output port |

Declare the domain in `SimulationConfig`. Components with internal state (motors, capacitors, batteries) participate automatically when using `ODESolver`.

## Component health and faults

Components can report runtime health during simulation:

| `ComponentHealth` | Value | Meaning                            |
| ----------------- | ----- | ---------------------------------- |
| `OK`              | 0     | Operating normally                 |
| `WARNING`         | 1     | Out of spec; simulation continues  |
| `FAULT`           | 2     | Component failed; outputs degraded |

Fault-aware stdlib components (`DCMotor`, `LiPoCell`) expose `health` and `fault_code` output ports and emit structured `Diagnostic` objects into `SimulationResult`. Override `post_solve_diagnostics()` and `apply_fault_outputs()` on custom components to add your own limits.

<Card title="Component Faults and Health" icon="triangle-exclamation" href="/guides/component-faults">
  Full guide to fault codes, FaultMode, and authoring fault rules
</Card>

## Design principles

1. **Everything is a component.** A resistor, an Arduino, a robot arm: the abstraction never breaks.
2. **Components are defined in code.** Python classes are the source of truth.
3. **Solvers are pluggable.** Swap a formula for a lookup table or ML model without touching wiring.
4. **Strict typing at the boundary.** Type errors are caught at graph construction time.
5. **Composability is first-class.** A composite component is indistinguishable from a primitive at its interface.
6. **Data drives fidelity.** Components start as formulas and grow into empirically-calibrated ML models.
