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

# Solvers

> Formula, ODE, lookup table, and ML solvers in Hardwave

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 without changing the component's interface or graph wiring.

## FormulaSolver

Wrap any callable. The callable receives `(inputs, component)` and returns a dict of output port values.

```python theme={null}
from hardwave.solvers import FormulaSolver
from hardwave.stdlib.components.passive import Resistor

def my_formula(inputs, component):
    V = inputs["voltage"]
    R = component.get_param("resistance")
    return {"current": V / R, "power": V**2 / R}

r = Resistor("r1", param_values={"resistance": 470.0})
r.set_solver(FormulaSolver(my_formula))
```

## LookupTableSolver

Interpolate measured or datasheet data:

```python theme={null}
from hardwave.solvers import LookupTableSolver

table = {
    "speed_rpm": [0,   500,  1000, 1500, 2000, 2500, 3000],
    "torque_nm": [1.2, 1.15, 1.05, 0.90, 0.70, 0.42, 0.0],
    "efficiency": [0.0, 0.55, 0.72, 0.80, 0.81, 0.75, 0.0],
}

motor.set_solver(LookupTableSolver(
    table,
    input_port="speed_rpm",
    output_ports=["torque_nm", "efficiency"],
    method="linear",   # "linear" | "cubic" | "nearest"
))
```

## ODESolver

Physics-based transient models. Define state evolution and output mapping functions:

```python theme={null}
from hardwave.solvers import ODESolver

def state_fn(t, state, inputs, comp):
    R, C = comp.get_param("resistance"), comp.get_param("capacitance")
    return {"V_c": (inputs["v_in"] - state["V_c"]) / (R * C)}

def output_fn(t, state, inputs, comp):
    return {"v_out": state["V_c"]}

rc_solver = ODESolver(
    state_fn=state_fn,
    output_fn=output_fn,
    initial_state={"V_c": 0.0},
    integrator="rk4",          # "euler" | "rk4" | "scipy_ivp"
)
my_cap.set_solver(rc_solver)
```

Built-in components like `DCMotor`, `Capacitor`, `Inductor`, and `LiPoCell` use `ODESolver` internally and automatically participate in transient runs.

## Hosted ML models

Train a model in the Hardwave cloud from records owned by that model, then attach it as a remote solver. See [Hosted ML models](/reference/ml-models) for the full workflow.

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

hardwave.premium.configure(organization_id="...", secret_key="...")
model = hardwave.premium.create_model(
    "dc-motor-from-bench",
    component_class="DCMotor",
    input_ports=["voltage", "load_torque"],
    output_ports=["angular_velocity", "winding_temp"],
)
hardwave.premium.append_records(model["id"], records)
hardwave.premium.train_model(model["id"], min_samples=50)
hardwave.premium.attach_model(motor, model["id"])
```

## Simulation domains

Solvers declare which simulation domain they support:

| Domain                          | Description                        |
| ------------------------------- | ---------------------------------- |
| `SimulationDomain.STEADY_STATE` | Single evaluation per run          |
| `SimulationDomain.TRANSIENT`    | Time-stepped evaluation with state |

Set the domain in `SimulationConfig` when running the engine. Algebraic solvers participate in both domains; ODE solvers advance state only in transient mode.

## Swapping solvers without rewiring

Because solvers are injected at the instance level, you can calibrate a component against real data without changing any graph connections:

```python theme={null}
metrics = trainer.evaluate(solver, test_fraction=0.2)
print(metrics["overall_r2"])   # e.g. 0.998

motor.set_solver(solver)  # same instance, same ports, better physics
```
