// =============================================================================
// Test: C++23 API Concepts and CRTP Devices
// =============================================================================

#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>

#include "pulsim/v1/core.hpp"

using namespace pulsim::v1;
using Catch::Approx;

namespace {

struct MockTransientServiceBuilder {
    [[nodiscard]] bool supports_fixed_mode(const SimulationOptions& /*options*/) const { return true; }
    [[nodiscard]] bool supports_variable_mode(const SimulationOptions& /*options*/) const { return true; }

    [[nodiscard]] std::shared_ptr<EquationAssemblerService> make_equation_assembler(
        Circuit& /*circuit*/,
        const SimulationOptions& /*options*/) const {
        return {};
    }

    [[nodiscard]] std::shared_ptr<NonlinearSolveService> make_nonlinear_solve(
        NewtonRaphsonSolver<RuntimeLinearSolver>& /*newton_solver*/,
        std::shared_ptr<EquationAssemblerService> /*equation_assembler*/) const {
        return {};
    }

    [[nodiscard]] std::shared_ptr<SegmentModelService> make_segment_model(
        Circuit& /*circuit*/,
        std::shared_ptr<EquationAssemblerService> /*equation_assembler*/) const {
        return {};
    }

    [[nodiscard]] std::shared_ptr<LinearSolveService> make_linear_solve(
        RuntimeLinearSolver& /*solver*/) const {
        return {};
    }

    [[nodiscard]] std::shared_ptr<SegmentStepperService> make_segment_stepper(
        std::shared_ptr<EquationAssemblerService> /*equation_assembler*/,
        std::shared_ptr<LinearSolveService> /*linear_solve*/) const {
        return {};
    }

    [[nodiscard]] std::shared_ptr<EventSchedulerService> make_event_scheduler(
        Circuit& /*circuit*/,
        const SimulationOptions& /*options*/) const {
        return {};
    }

    [[nodiscard]] std::shared_ptr<RecoveryManagerService> make_recovery_manager(
        const SimulationOptions& /*options*/) const {
        return {};
    }

    [[nodiscard]] std::shared_ptr<TelemetryCollectorService> make_telemetry_collector() const {
        return {};
    }

    [[nodiscard]] std::shared_ptr<LossService> make_loss_service(
        Circuit& /*circuit*/,
        const SimulationOptions& /*options*/) const {
        return {};
    }

    [[nodiscard]] std::shared_ptr<ThermalService> make_thermal_service(
        Circuit& /*circuit*/,
        const SimulationOptions& /*options*/) const {
        return {};
    }
};

struct IncompleteTransientServiceBuilder {
    [[nodiscard]] bool supports_fixed_mode(const SimulationOptions& /*options*/) const { return true; }
    [[nodiscard]] bool supports_variable_mode(const SimulationOptions& /*options*/) const { return true; }

    [[nodiscard]] std::shared_ptr<EquationAssemblerService> make_equation_assembler(
        Circuit& /*circuit*/,
        const SimulationOptions& /*options*/) const {
        return {};
    }

    [[nodiscard]] std::shared_ptr<NonlinearSolveService> make_nonlinear_solve(
        NewtonRaphsonSolver<RuntimeLinearSolver>& /*newton_solver*/,
        std::shared_ptr<EquationAssemblerService> /*equation_assembler*/) const {
        return {};
    }

    [[nodiscard]] std::shared_ptr<SegmentModelService> make_segment_model(
        Circuit& /*circuit*/,
        std::shared_ptr<EquationAssemblerService> /*equation_assembler*/) const {
        return {};
    }

    [[nodiscard]] std::shared_ptr<LinearSolveService> make_linear_solve(
        RuntimeLinearSolver& /*solver*/) const {
        return {};
    }

    [[nodiscard]] std::shared_ptr<SegmentStepperService> make_segment_stepper(
        std::shared_ptr<EquationAssemblerService> /*equation_assembler*/,
        std::shared_ptr<LinearSolveService> /*linear_solve*/) const {
        return {};
    }

    [[nodiscard]] std::shared_ptr<EventSchedulerService> make_event_scheduler(
        Circuit& /*circuit*/,
        const SimulationOptions& /*options*/) const {
        return {};
    }

    [[nodiscard]] std::shared_ptr<RecoveryManagerService> make_recovery_manager(
        const SimulationOptions& /*options*/) const {
        return {};
    }

    [[nodiscard]] std::shared_ptr<TelemetryCollectorService> make_telemetry_collector() const {
        return {};
    }

    [[nodiscard]] std::shared_ptr<ThermalService> make_thermal_service(
        Circuit& /*circuit*/,
        const SimulationOptions& /*options*/) const {
        return {};
    }
};

static_assert(TransientServiceBuilder<MockTransientServiceBuilder>);
static_assert(!TransientServiceBuilder<IncompleteTransientServiceBuilder>);

}  // namespace

TEST_CASE("API concepts compilation", "[concepts]") {
    SECTION("Device traits for Resistor") {
        REQUIRE(device_traits<Resistor>::type == DeviceType::Resistor);
        REQUIRE(device_traits<Resistor>::num_pins == 2);
        REQUIRE(device_traits<Resistor>::is_linear == true);
        REQUIRE(device_traits<Resistor>::is_dynamic == false);
    }

    SECTION("Device traits for Capacitor") {
        REQUIRE(device_traits<Capacitor>::type == DeviceType::Capacitor);
        REQUIRE(device_traits<Capacitor>::num_pins == 2);
        REQUIRE(device_traits<Capacitor>::is_linear == true);
        REQUIRE(device_traits<Capacitor>::is_dynamic == true);
    }

    SECTION("Compile-time type queries") {
        REQUIRE(is_linear_device_v<Resistor> == true);
        REQUIRE(is_dynamic_device_v<Resistor> == false);
        REQUIRE(is_linear_device_v<Capacitor> == true);
        REQUIRE(is_dynamic_device_v<Capacitor> == true);
    }
}

TEST_CASE("API transient service extension contract concepts", "[concepts][architecture][services]") {
    REQUIRE(TransientServiceBuilder<MockTransientServiceBuilder>);
    REQUIRE_FALSE(TransientServiceBuilder<IncompleteTransientServiceBuilder>);
}

TEST_CASE("API CRTP Resistor", "[api][crtp][resistor]") {
    SECTION("Resistor creation") {
        Resistor r(1000.0, "R1");
        REQUIRE(r.resistance() == 1000.0);
        REQUIRE(r.name() == "R1");
    }

    SECTION("Resistor stamping") {
        Resistor r(1000.0);

        // Create a 3x3 sparse matrix (2 nodes + ground)
        SparseMatrix G(2, 2);
        G.setZero();
        Vector b = Vector::Zero(2);

        // Node indices: 0 = node1, 1 = node2 (-1 = ground)
        std::array<Index, 2> nodes = {0, 1};

        r.stamp(G, b, nodes);

        // Check conductance stamps
        G.makeCompressed();
        Real g = 1.0 / 1000.0;  // 0.001 S

        REQUIRE(G.coeff(0, 0) == Approx(g));
        REQUIRE(G.coeff(0, 1) == Approx(-g));
        REQUIRE(G.coeff(1, 0) == Approx(-g));
        REQUIRE(G.coeff(1, 1) == Approx(g));
    }

    SECTION("Resistor to ground") {
        Resistor r(500.0);

        SparseMatrix G(1, 1);
        G.setZero();
        Vector b = Vector::Zero(1);

        // Node 0 to ground (-1)
        std::array<Index, 2> nodes = {0, -1};

        r.stamp(G, b, nodes);
        G.makeCompressed();

        Real g = 1.0 / 500.0;
        REQUIRE(G.coeff(0, 0) == Approx(g));
    }
}

TEST_CASE("API CRTP Capacitor", "[api][crtp][capacitor]") {
    SECTION("Capacitor creation") {
        Capacitor c(1e-6, 0.0, "C1");
        REQUIRE(c.capacitance() == 1e-6);
        REQUIRE(c.name() == "C1");
    }

    SECTION("Capacitor companion model (Trapezoidal)") {
        // 1uF capacitor, dt = 1e-6s, initial voltage = 5V, initial current = 0A
        Capacitor c(1e-6, 5.0, "C1");
        c.set_timestep(1e-6);

        SparseMatrix G(2, 2);
        G.setZero();
        Vector b = Vector::Zero(2);

        std::array<Index, 2> nodes = {0, 1};
        c.stamp(G, b, nodes);
        G.makeCompressed();

        // G_eq = 2C/dt = 2 * 1e-6 / 1e-6 = 2.0 S
        Real g_eq = 2.0;

        REQUIRE(G.coeff(0, 0) == Approx(g_eq));
        REQUIRE(G.coeff(0, 1) == Approx(-g_eq));
        REQUIRE(G.coeff(1, 0) == Approx(-g_eq));
        REQUIRE(G.coeff(1, 1) == Approx(g_eq));

        // I_eq = G_eq * V_prev + I_prev = 2.0 * 5.0 + 0.0 = 10.0 A
        REQUIRE(b[0] == Approx(10.0));
        REQUIRE(b[1] == Approx(-10.0));
    }

    SECTION("Capacitor history update") {
        Capacitor c(1e-6, 0.0);
        c.set_timestep(1e-6);

        // Simulate: set current state
        c.set_current_state(5.0, 0.01);  // V = 5V, I = 10mA

        // Update history
        c.update_history();

        // Check that history was updated
        REQUIRE(c.voltage_prev() == 5.0);
        REQUIRE(c.current_prev() == 0.01);
    }
}

TEST_CASE("API Jacobian sparsity pattern", "[api][sparsity]") {
    SECTION("Resistor pattern") {
        auto pattern = Resistor::jacobian_pattern();
        REQUIRE(pattern.size() == 4);
    }

    SECTION("Capacitor pattern") {
        auto pattern = Capacitor::jacobian_pattern();
        REQUIRE(pattern.size() == 4);
    }
}

TEST_CASE("API Result type", "[api][result]") {
    SECTION("Success result") {
        auto result = Result<int>::success(42);
        REQUIRE(result.has_value());
        REQUIRE(*result == 42);
    }

    SECTION("Error result") {
        auto result = Result<int>::failure(SolverError::SingularMatrix);
        REQUIRE(!result.has_value());
        REQUIRE(result.error == SolverError::SingularMatrix);
    }
}

TEST_CASE("API Integration method traits", "[api][integration]") {
    SECTION("Backward Euler") {
        REQUIRE(method_traits<MethodType::BackwardEuler>::order == 1);
        REQUIRE(method_traits<MethodType::BackwardEuler>::is_A_stable == true);
        REQUIRE(method_traits<MethodType::BackwardEuler>::is_L_stable == true);
    }

    SECTION("Trapezoidal") {
        REQUIRE(method_traits<MethodType::Trapezoidal>::order == 2);
        REQUIRE(method_traits<MethodType::Trapezoidal>::is_A_stable == true);
        REQUIRE(method_traits<MethodType::Trapezoidal>::is_L_stable == false);
    }

    SECTION("BDF2") {
        REQUIRE(method_traits<MethodType::BDF2>::order == 2);
        REQUIRE(method_traits<MethodType::BDF2>::is_A_stable == true);
        REQUIRE(method_traits<MethodType::BDF2>::is_L_stable == true);
    }
}

// =============================================================================
// Tests for Inductor CRTP device
// =============================================================================

TEST_CASE("API CRTP Inductor", "[api][crtp][inductor]") {
    SECTION("Inductor creation") {
        Inductor l(1e-3, 0.0, "L1");
        REQUIRE(l.inductance() == 1e-3);
        REQUIRE(l.name() == "L1");
    }

    SECTION("Inductor device traits") {
        REQUIRE(device_traits<Inductor>::type == DeviceType::Inductor);
        REQUIRE(device_traits<Inductor>::num_pins == 2);
        REQUIRE(device_traits<Inductor>::is_linear == true);
        REQUIRE(device_traits<Inductor>::is_dynamic == true);
    }

    SECTION("Inductor companion model (Trapezoidal)") {
        // 1mH inductor, dt = 1e-6s, initial current = 1A
        Inductor l(1e-3, 1.0, "L1");
        l.set_timestep(1e-6);

        SparseMatrix G(2, 2);
        G.setZero();
        Vector b = Vector::Zero(2);

        std::array<Index, 2> nodes = {0, 1};
        l.stamp(G, b, nodes);
        G.makeCompressed();

        // G_eq = dt / (2L) = 1e-6 / (2 * 1e-3) = 0.0005 S
        Real g_eq = 1e-6 / (2.0 * 1e-3);

        REQUIRE(G.coeff(0, 0) == Approx(g_eq));
        REQUIRE(G.coeff(0, 1) == Approx(-g_eq));
        REQUIRE(G.coeff(1, 0) == Approx(-g_eq));
        REQUIRE(G.coeff(1, 1) == Approx(g_eq));

        // V_eq = (2L/dt) * I_prev + V_prev = (2 * 1e-3 / 1e-6) * 1.0 + 0.0 = 2000 V
        // I_eq = g_eq * V_eq = 0.0005 * 2000 = 1.0 A
        Real v_eq = (2.0 * 1e-3 / 1e-6) * 1.0 + 0.0;
        Real i_eq = g_eq * v_eq;
        REQUIRE(b[0] == Approx(i_eq));
        REQUIRE(b[1] == Approx(-i_eq));
    }

    SECTION("Inductor history update") {
        Inductor l(1e-3, 0.0);
        l.set_timestep(1e-6);

        // Simulate: set current state
        l.set_current_state(10.0, 0.5);  // V = 10V, I = 500mA

        // Update history
        l.update_history();

        // Check that history was updated
        REQUIRE(l.voltage_prev() == 10.0);
        REQUIRE(l.current_prev() == 0.5);
    }

    SECTION("Inductor Jacobian pattern") {
        auto pattern = Inductor::jacobian_pattern();
        REQUIRE(pattern.size() == 4);
    }
}

// =============================================================================
// Tests for VoltageSource CRTP device
// =============================================================================

TEST_CASE("API CRTP VoltageSource", "[api][crtp][vsource]") {
    SECTION("VoltageSource creation") {
        VoltageSource vs(12.0, "V1");
        REQUIRE(vs.voltage() == 12.0);
        REQUIRE(vs.name() == "V1");
    }

    SECTION("VoltageSource device traits") {
        REQUIRE(device_traits<VoltageSource>::type == DeviceType::VoltageSource);
        REQUIRE(device_traits<VoltageSource>::num_pins == 2);
        REQUIRE(device_traits<VoltageSource>::num_internal_nodes == 1);  // Branch current
        REQUIRE(device_traits<VoltageSource>::is_linear == true);
        REQUIRE(device_traits<VoltageSource>::is_dynamic == false);
    }

    SECTION("VoltageSource MNA stamping") {
        // 12V voltage source between node 0 and ground
        // MNA extends matrix with branch current variable
        VoltageSource vs(12.0, "V1");
        vs.set_branch_index(2);  // Branch current is variable index 2

        // 3x3 matrix: 2 nodes + 1 branch current
        SparseMatrix G(3, 3);
        G.setZero();
        Vector b = Vector::Zero(3);

        std::array<Index, 2> nodes = {0, 1};
        vs.stamp(G, b, nodes);
        G.makeCompressed();

        // Check MNA stamps
        // G(n+, branch) = +1, G(branch, n+) = +1
        // G(n-, branch) = -1, G(branch, n-) = -1
        REQUIRE(G.coeff(0, 2) == Approx(1.0));
        REQUIRE(G.coeff(2, 0) == Approx(1.0));
        REQUIRE(G.coeff(1, 2) == Approx(-1.0));
        REQUIRE(G.coeff(2, 1) == Approx(-1.0));

        // RHS: voltage value in branch equation row
        REQUIRE(b[2] == Approx(12.0));
    }

    SECTION("VoltageSource to ground") {
        VoltageSource vs(5.0, "V1");
        vs.set_branch_index(1);

        // 2x2 matrix: 1 node + 1 branch current
        SparseMatrix G(2, 2);
        G.setZero();
        Vector b = Vector::Zero(2);

        std::array<Index, 2> nodes = {0, -1};  // Node 0 to ground
        vs.stamp(G, b, nodes);
        G.makeCompressed();

        REQUIRE(G.coeff(0, 1) == Approx(1.0));
        REQUIRE(G.coeff(1, 0) == Approx(1.0));
        REQUIRE(b[1] == Approx(5.0));
    }

    SECTION("VoltageSource Jacobian pattern") {
        auto pattern = VoltageSource::jacobian_pattern();
        REQUIRE(pattern.size() == 5);  // 4 off-diagonal + 1 diagonal placeholder
    }
}

// =============================================================================
// Tests for CurrentSource CRTP device
// =============================================================================

TEST_CASE("API CRTP CurrentSource", "[api][crtp][isource]") {
    SECTION("CurrentSource creation") {
        CurrentSource is(0.1, "I1");
        REQUIRE(is.current() == 0.1);
        REQUIRE(is.name() == "I1");
    }

    SECTION("CurrentSource device traits") {
        REQUIRE(device_traits<CurrentSource>::type == DeviceType::CurrentSource);
        REQUIRE(device_traits<CurrentSource>::num_pins == 2);
        REQUIRE(device_traits<CurrentSource>::num_internal_nodes == 0);
        REQUIRE(device_traits<CurrentSource>::is_linear == true);
        REQUIRE(device_traits<CurrentSource>::is_dynamic == false);
    }

    SECTION("CurrentSource stamping") {
        // 100mA current source from node 0 to node 1
        CurrentSource is(0.1, "I1");

        SparseMatrix G(2, 2);
        G.setZero();
        Vector b = Vector::Zero(2);

        std::array<Index, 2> nodes = {0, 1};
        is.stamp(G, b, nodes);
        G.makeCompressed();

        // Current source doesn't affect G matrix
        REQUIRE(G.nonZeros() == 0);

        // Current source affects RHS only
        // Current flows from n+ to n-: leaves n+, enters n-
        REQUIRE(b[0] == Approx(-0.1));  // Current leaves n+
        REQUIRE(b[1] == Approx(0.1));   // Current enters n-
    }

    SECTION("CurrentSource to ground") {
        CurrentSource is(0.05, "I1");

        SparseMatrix G(1, 1);
        G.setZero();
        Vector b = Vector::Zero(1);

        std::array<Index, 2> nodes = {0, -1};  // From node 0 to ground
        is.stamp(G, b, nodes);
        G.makeCompressed();

        REQUIRE(G.nonZeros() == 0);
        REQUIRE(b[0] == Approx(-0.05));
    }

    SECTION("CurrentSource Jacobian pattern") {
        auto pattern = CurrentSource::jacobian_pattern();
        REQUIRE(pattern.size() == 0);  // No matrix contribution
    }
}

// =============================================================================
// Tests for IdealDiode CRTP device
// =============================================================================

TEST_CASE("API CRTP IdealDiode", "[api][crtp][diode]") {
    SECTION("IdealDiode creation") {
        IdealDiode d(1e3, 1e-9, "D1");
        REQUIRE(d.name() == "D1");
    }

    SECTION("IdealDiode device traits") {
        REQUIRE(device_traits<IdealDiode>::type == DeviceType::Diode);
        REQUIRE(device_traits<IdealDiode>::num_pins == 2);
        REQUIRE(device_traits<IdealDiode>::is_linear == false);  // Nonlinear!
        REQUIRE(device_traits<IdealDiode>::is_dynamic == false);
    }

    SECTION("IdealDiode forward bias (conducting)") {
        IdealDiode d(1e3, 1e-9);  // g_on = 1kS, g_off = 1nS

        SparseMatrix J(2, 2);
        J.setZero();
        Vector f = Vector::Zero(2);
        Vector x = Vector::Zero(2);

        // Forward bias: anode (node 0) at 5V, cathode (node 1) at 0V
        x[0] = 5.0;
        x[1] = 0.0;

        std::array<Index, 2> nodes = {0, 1};
        d.stamp_jacobian(J, f, x, nodes);
        J.makeCompressed();

        // Should be conducting with g_on = 1e3
        REQUIRE(d.is_conducting() == true);
        REQUIRE(J.coeff(0, 0) == Approx(1e3));
        REQUIRE(J.coeff(0, 1) == Approx(-1e3));
        REQUIRE(J.coeff(1, 0) == Approx(-1e3));
        REQUIRE(J.coeff(1, 1) == Approx(1e3));

        // Current = g * V = 1e3 * 5 = 5000A
        REQUIRE(f[0] == Approx(5000.0));
        REQUIRE(f[1] == Approx(-5000.0));
    }

    SECTION("IdealDiode reverse bias (blocking)") {
        IdealDiode d(1e3, 1e-9);

        SparseMatrix J(2, 2);
        J.setZero();
        Vector f = Vector::Zero(2);
        Vector x = Vector::Zero(2);

        // Reverse bias: anode (node 0) at 0V, cathode (node 1) at 5V
        x[0] = 0.0;
        x[1] = 5.0;

        std::array<Index, 2> nodes = {0, 1};
        d.stamp_jacobian(J, f, x, nodes);
        J.makeCompressed();

        // Should be blocking with g_off = 1e-9
        REQUIRE(d.is_conducting() == false);
        REQUIRE(J.coeff(0, 0) == Approx(1e-9));
        REQUIRE(J.coeff(0, 1) == Approx(-1e-9));
    }

    SECTION("IdealDiode Jacobian pattern") {
        auto pattern = IdealDiode::jacobian_pattern();
        REQUIRE(pattern.size() == 4);
    }
}

// =============================================================================
// Tests for IdealSwitch CRTP device
// =============================================================================

TEST_CASE("API CRTP IdealSwitch", "[api][crtp][switch]") {
    SECTION("IdealSwitch creation") {
        IdealSwitch sw(1e6, 1e-12, true, "S1");
        REQUIRE(sw.name() == "S1");
        REQUIRE(sw.is_closed() == true);
    }

    SECTION("IdealSwitch device traits") {
        REQUIRE(device_traits<IdealSwitch>::type == DeviceType::Switch);
        REQUIRE(device_traits<IdealSwitch>::num_pins == 2);
        REQUIRE(device_traits<IdealSwitch>::is_linear == true);  // Piecewise linear
        REQUIRE(device_traits<IdealSwitch>::is_dynamic == false);
    }

    SECTION("IdealSwitch closed state") {
        IdealSwitch sw(1e6, 1e-12, true);  // g_on = 1MS, g_off = 1pS, closed

        SparseMatrix G(2, 2);
        G.setZero();
        Vector b = Vector::Zero(2);

        std::array<Index, 2> nodes = {0, 1};
        sw.stamp(G, b, nodes);
        G.makeCompressed();

        // Closed switch: g = g_on = 1e6
        REQUIRE(G.coeff(0, 0) == Approx(1e6));
        REQUIRE(G.coeff(0, 1) == Approx(-1e6));
        REQUIRE(G.coeff(1, 0) == Approx(-1e6));
        REQUIRE(G.coeff(1, 1) == Approx(1e6));
    }

    SECTION("IdealSwitch open state") {
        IdealSwitch sw(1e6, 1e-12, false);  // g_on = 1MS, g_off = 1pS, open

        SparseMatrix G(2, 2);
        G.setZero();
        Vector b = Vector::Zero(2);

        std::array<Index, 2> nodes = {0, 1};
        sw.stamp(G, b, nodes);
        G.makeCompressed();

        // Open switch: g = g_off = 1e-12
        REQUIRE(G.coeff(0, 0) == Approx(1e-12));
        REQUIRE(G.coeff(0, 1) == Approx(-1e-12));
    }

    SECTION("IdealSwitch state transitions") {
        IdealSwitch sw(1e6, 1e-12, false);

        REQUIRE(sw.is_closed() == false);

        sw.close();
        REQUIRE(sw.is_closed() == true);

        sw.open();
        REQUIRE(sw.is_closed() == false);

        sw.set_state(true);
        REQUIRE(sw.is_closed() == true);
    }

    SECTION("IdealSwitch Jacobian pattern") {
        auto pattern = IdealSwitch::jacobian_pattern();
        REQUIRE(pattern.size() == 4);
    }
}

// =============================================================================
// Static assertions for all CRTP devices
// =============================================================================

// =============================================================================
// Tests for MOSFET CRTP device
// =============================================================================

TEST_CASE("API CRTP MOSFET", "[api][crtp][mosfet]") {
    SECTION("MOSFET creation") {
        MOSFET m(2.0, 0.1, true, "M1");
        REQUIRE(m.name() == "M1");
        REQUIRE(m.params().vth == 2.0);
        REQUIRE(m.params().kp == 0.1);
        REQUIRE(m.params().is_nmos == true);
    }

    SECTION("MOSFET device traits") {
        REQUIRE(device_traits<MOSFET>::type == DeviceType::MOSFET);
        REQUIRE(device_traits<MOSFET>::num_pins == 3);
        REQUIRE(device_traits<MOSFET>::is_linear == false);  // Nonlinear
        REQUIRE(device_traits<MOSFET>::is_dynamic == false);
        REQUIRE(device_traits<MOSFET>::has_thermal_model == true);
    }

    SECTION("MOSFET cutoff region") {
        MOSFET::Params params{.vth = 2.0, .kp = 0.1, .lambda = 0.01, .g_off = 1e-12, .is_nmos = true};
        MOSFET m(params, "M1");

        SparseMatrix J(3, 3);
        J.setZero();
        Vector f = Vector::Zero(3);
        Vector x = Vector::Zero(3);

        // Gate = 0V, Drain = 5V, Source = 0V -> Vgs = 0 < Vth = 2 -> cutoff
        x[0] = 0.0;  // Gate
        x[1] = 5.0;  // Drain
        x[2] = 0.0;  // Source

        std::array<Index, 3> nodes = {0, 1, 2};
        m.stamp_jacobian(J, f, x, nodes);
        J.makeCompressed();

        // In cutoff, should only have very small g_off conductance
        REQUIRE(J.coeff(1, 1) == Approx(1e-12).margin(1e-15));
    }

    SECTION("MOSFET saturation region") {
        MOSFET::Params params{.vth = 2.0, .kp = 0.1, .lambda = 0.0, .g_off = 1e-12, .is_nmos = true};
        MOSFET m(params, "M1");

        SparseMatrix J(3, 3);
        J.setZero();
        Vector f = Vector::Zero(3);
        Vector x = Vector::Zero(3);

        // Gate = 5V, Drain = 10V, Source = 0V
        // Vgs = 5 > Vth = 2, Vds = 10 > Vgs - Vth = 3 -> saturation
        x[0] = 5.0;  // Gate
        x[1] = 10.0; // Drain
        x[2] = 0.0;  // Source

        std::array<Index, 3> nodes = {0, 1, 2};
        m.stamp_jacobian(J, f, x, nodes);
        J.makeCompressed();

        // Id_sat = 0.5 * kp * (Vgs - Vth)^2 = 0.5 * 0.1 * 9 = 0.45A
        // gm = kp * (Vgs - Vth) = 0.1 * 3 = 0.3 S
        REQUIRE(J.coeff(1, 0) == Approx(0.3).margin(0.01));  // gm at drain-gate
    }

    SECTION("MOSFET Jacobian pattern") {
        auto pattern = MOSFET::jacobian_pattern();
        REQUIRE(pattern.size() == 9);  // 3x3 matrix
    }
}

// =============================================================================
// Tests for IGBT CRTP device
// =============================================================================

TEST_CASE("API CRTP IGBT", "[api][crtp][igbt]") {
    SECTION("IGBT creation") {
        IGBT ig(5.0, 1e4, "Q1");
        REQUIRE(ig.name() == "Q1");
        REQUIRE(ig.params().vth == 5.0);
        REQUIRE(ig.params().g_on == 1e4);
    }

    SECTION("IGBT device traits") {
        REQUIRE(device_traits<IGBT>::type == DeviceType::IGBT);
        REQUIRE(device_traits<IGBT>::num_pins == 3);
        REQUIRE(device_traits<IGBT>::is_linear == false);  // Nonlinear
        REQUIRE(device_traits<IGBT>::is_dynamic == false);
        REQUIRE(device_traits<IGBT>::has_thermal_model == true);
    }

    SECTION("IGBT off state") {
        IGBT::Params params{.vth = 5.0, .g_on = 1e4, .g_off = 1e-12, .v_ce_sat = 1.5};
        IGBT ig(params, "Q1");

        SparseMatrix J(3, 3);
        J.setZero();
        Vector f = Vector::Zero(3);
        Vector x = Vector::Zero(3);

        // Gate = 0V, Collector = 100V, Emitter = 0V -> Vge = 0 < Vth = 5 -> off
        x[0] = 0.0;   // Gate
        x[1] = 100.0; // Collector
        x[2] = 0.0;   // Emitter

        std::array<Index, 3> nodes = {0, 1, 2};
        ig.stamp_jacobian(J, f, x, nodes);
        J.makeCompressed();

        REQUIRE(ig.is_conducting() == false);
        REQUIRE(J.coeff(1, 1) == Approx(1e-12).margin(1e-15));
    }

    SECTION("IGBT on state") {
        IGBT::Params params{.vth = 5.0, .g_on = 1e4, .g_off = 1e-12, .v_ce_sat = 1.5};
        IGBT ig(params, "Q1");

        SparseMatrix J(3, 3);
        J.setZero();
        Vector f = Vector::Zero(3);
        Vector x = Vector::Zero(3);

        // Gate = 15V, Collector = 10V, Emitter = 0V -> Vge = 15 > Vth = 5 -> on
        x[0] = 15.0;  // Gate
        x[1] = 10.0;  // Collector
        x[2] = 0.0;   // Emitter

        std::array<Index, 3> nodes = {0, 1, 2};
        ig.stamp_jacobian(J, f, x, nodes);
        J.makeCompressed();

        REQUIRE(ig.is_conducting() == true);
        REQUIRE(J.coeff(1, 1) == Approx(1e4).margin(1.0));
    }

    SECTION("IGBT Jacobian pattern") {
        auto pattern = IGBT::jacobian_pattern();
        REQUIRE(pattern.size() == 9);  // 3x3 matrix
    }
}

// =============================================================================
// Tests for Transformer CRTP device
// =============================================================================

TEST_CASE("API CRTP Transformer", "[api][crtp][transformer]") {
    SECTION("Transformer creation") {
        Transformer tr(2.0, "T1");  // 2:1 turns ratio
        REQUIRE(tr.name() == "T1");
        REQUIRE(tr.turns_ratio() == 2.0);
    }

    SECTION("Transformer device traits") {
        REQUIRE(device_traits<Transformer>::type == DeviceType::Transformer);
        REQUIRE(device_traits<Transformer>::num_pins == 4);
        REQUIRE(device_traits<Transformer>::num_internal_nodes == 2);  // Two branch currents
        REQUIRE(device_traits<Transformer>::is_linear == true);
        REQUIRE(device_traits<Transformer>::is_dynamic == false);
    }

    SECTION("Transformer MNA stamping") {
        Transformer tr(2.0, "T1");  // 2:1 turns ratio
        tr.set_branch_indices(4, 5);  // Primary and secondary branch currents

        // 6x6 matrix: 4 nodes + 2 branch currents
        SparseMatrix G(6, 6);
        G.setZero();
        Vector b = Vector::Zero(6);

        std::array<Index, 4> nodes = {0, 1, 2, 3};  // P+, P-, S+, S-
        tr.stamp(G, b, nodes);
        G.makeCompressed();

        // Check voltage source stamps for primary winding
        REQUIRE(G.coeff(0, 4) == Approx(1.0));  // P+ to branch_p
        REQUIRE(G.coeff(4, 0) == Approx(1.0));  // branch_p to P+
        REQUIRE(G.coeff(1, 4) == Approx(-1.0)); // P- to branch_p

        // Check coupling: V_p = n * V_s
        REQUIRE(G.coeff(4, 2) == Approx(-2.0));  // Coupling to S+
        REQUIRE(G.coeff(4, 3) == Approx(2.0));   // Coupling to S-
    }

    SECTION("Transformer Jacobian pattern") {
        auto pattern = Transformer::jacobian_pattern();
        REQUIRE(pattern.size() == 16);
    }
}

// =============================================================================
// Complete device suite static assertions
// =============================================================================

TEST_CASE("API CRTP static assertions", "[api][crtp][static]") {
    // These are compile-time checks, but we verify the concepts are satisfied
    SECTION("All devices satisfy StampableDevice concept") {
        REQUIRE(StampableDevice<Resistor>);
        REQUIRE(StampableDevice<Capacitor>);
        REQUIRE(StampableDevice<Inductor>);
        REQUIRE(StampableDevice<VoltageSource>);
        REQUIRE(StampableDevice<CurrentSource>);
        REQUIRE(StampableDevice<IdealDiode>);
        REQUIRE(StampableDevice<IdealSwitch>);
        REQUIRE(StampableDevice<MOSFET>);
        REQUIRE(StampableDevice<IGBT>);
        REQUIRE(StampableDevice<Transformer>);
    }

    SECTION("Linear device classification") {
        REQUIRE(is_linear_device_v<Resistor> == true);
        REQUIRE(is_linear_device_v<Capacitor> == true);
        REQUIRE(is_linear_device_v<Inductor> == true);
        REQUIRE(is_linear_device_v<VoltageSource> == true);
        REQUIRE(is_linear_device_v<CurrentSource> == true);
        REQUIRE(is_linear_device_v<IdealDiode> == false);  // Nonlinear
        REQUIRE(is_linear_device_v<IdealSwitch> == true);  // Piecewise linear
        REQUIRE(is_linear_device_v<MOSFET> == false);      // Nonlinear
        REQUIRE(is_linear_device_v<IGBT> == false);        // Nonlinear
        REQUIRE(is_linear_device_v<Transformer> == true);
    }

    SECTION("Dynamic device classification") {
        REQUIRE(is_dynamic_device_v<Resistor> == false);
        REQUIRE(is_dynamic_device_v<Capacitor> == true);
        REQUIRE(is_dynamic_device_v<Inductor> == true);
        REQUIRE(is_dynamic_device_v<VoltageSource> == false);
        REQUIRE(is_dynamic_device_v<CurrentSource> == false);
        REQUIRE(is_dynamic_device_v<IdealDiode> == false);
        REQUIRE(is_dynamic_device_v<IdealSwitch> == false);
        REQUIRE(is_dynamic_device_v<MOSFET> == false);
        REQUIRE(is_dynamic_device_v<IGBT> == false);
        REQUIRE(is_dynamic_device_v<Transformer> == false);
    }

    SECTION("Power electronics device features") {
        // All power devices should have loss models
        REQUIRE(has_loss_model_v<Resistor> == true);
        REQUIRE(has_loss_model_v<IdealDiode> == true);
        REQUIRE(has_loss_model_v<IdealSwitch> == true);
        REQUIRE(has_loss_model_v<MOSFET> == true);
        REQUIRE(has_loss_model_v<IGBT> == true);

        // Semiconductor devices should have thermal models
        REQUIRE(has_thermal_model_v<MOSFET> == true);
        REQUIRE(has_thermal_model_v<IGBT> == true);
    }
}

// =============================================================================
// Tests for C++23 Advanced Features (1.2.3, 1.2.4, 1.2.7)
// =============================================================================

TEST_CASE("API StateBuffer (mdspan-like)", "[api][cpp23][mdspan]") {
    SECTION("Fixed-size StateBuffer creation") {
        // 3 state variables, 3 history depth (current + 2 previous)
        StateBuffer<double, 3, 3> buffer;

        // Initialize current state
        buffer(0, 0) = 1.0;  // state 0, current
        buffer(1, 0) = 2.0;  // state 1, current
        buffer(2, 0) = 3.0;  // state 2, current

        REQUIRE(buffer(0, 0) == 1.0);
        REQUIRE(buffer(1, 0) == 2.0);
        REQUIRE(buffer(2, 0) == 3.0);
    }

    SECTION("StateBuffer history shift") {
        StateBuffer<double, 2, 3> buffer;

        // Set initial state
        buffer(0, 0) = 10.0;
        buffer(1, 0) = 20.0;

        // Shift history
        buffer.shift_history();

        // Old current should now be at history index 1
        REQUIRE(buffer(0, 1) == 10.0);
        REQUIRE(buffer(1, 1) == 20.0);

        // Set new current
        buffer(0, 0) = 11.0;
        buffer(1, 0) = 21.0;

        // Verify both current and previous
        REQUIRE(buffer(0, 0) == 11.0);
        REQUIRE(buffer(0, 1) == 10.0);
    }

    SECTION("StateBuffer span access") {
        StateBuffer<double, 4, 2> buffer;

        // Set via operator()
        buffer(0, 0) = 1.0;
        buffer(1, 0) = 2.0;
        buffer(2, 0) = 3.0;
        buffer(3, 0) = 4.0;

        // Access via span
        auto current = buffer.current();
        REQUIRE(current.size() == 4);
        REQUIRE(current[0] == 1.0);
        REQUIRE(current[3] == 4.0);
    }

    SECTION("StateBuffer compile-time properties") {
        using Buffer = StateBuffer<float, 5, 4>;
        REQUIRE(Buffer::num_states == 5);
        REQUIRE(Buffer::history_depth == 4);
    }
}

#if PULSIM_HAS_MDSPAN
TEST_CASE("API DynamicStateBuffer", "[api][cpp23][mdspan]") {
    SECTION("Dynamic buffer creation") {
        DynamicStateBuffer<double> buffer(10, 3);

        REQUIRE(buffer.num_states() == 10);
        REQUIRE(buffer.history_depth() == 3);
    }

    SECTION("Dynamic buffer mdspan view") {
        DynamicStateBuffer<double> buffer(4, 2);

        auto view = buffer.view();
        // View should have dimensions (history_depth, num_states)
        REQUIRE(view.extent(0) == 2);
        REQUIRE(view.extent(1) == 4);
    }
}
#endif

TEST_CASE("API Device Metadata (reflection prep)", "[api][cpp23][reflection]") {
    SECTION("DeviceMetadata structure") {
        DeviceMetadata meta{
            .name = "TestDevice",
            .category = "passive",
            .pin_count = 2,
            .is_linear = true,
            .is_dynamic = false,
            .has_thermal_model = false
        };

        REQUIRE(meta.name == "TestDevice");
        REQUIRE(meta.category == "passive");
        REQUIRE(meta.pin_count == 2);
        REQUIRE(meta.is_linear == true);
    }

    SECTION("ParamDescriptor structure") {
        ParamDescriptor param{
            .name = "resistance",
            .unit = "ohm",
            .default_value = 1000.0,
            .min_value = 0.0,
            .max_value = 1e12
        };

        REQUIRE(param.name == "resistance");
        REQUIRE(param.unit == "ohm");
        REQUIRE(param.default_value == 1000.0);
    }
}

TEST_CASE("API FixedString (compile-time string)", "[api][cpp23][reflection]") {
    SECTION("FixedString creation and conversion") {
        constexpr FixedString str("Hello");

        REQUIRE(str.size() == 5);
        REQUIRE(static_cast<std::string_view>(str) == "Hello");
    }

    SECTION("FixedString compile-time") {
        constexpr FixedString str("Test");
        static_assert(str.size() == 4);
    }
}

TEST_CASE("API Type name utilities", "[api][cpp23][reflection]") {
    SECTION("Built-in type names") {
        REQUIRE(type_name<double>() == "double");
        REQUIRE(type_name<float>() == "float");
        REQUIRE(type_name<int>() == "int");
        REQUIRE(type_name<bool>() == "bool");
    }
}

// =============================================================================
// Test device metadata registration (reflection prep)
// =============================================================================
// Note: All devices are registered in device_base.hpp using PULSIM_REGISTER_DEVICE

TEST_CASE("API Device registration (reflection prep)", "[api][cpp23][reflection]") {
    SECTION("Passive device metadata") {
        constexpr auto r_meta = get_device_metadata<Resistor>();
        REQUIRE(r_meta.name == "Resistor");
        REQUIRE(r_meta.category == "passive");
        REQUIRE(r_meta.pin_count == 2);
        REQUIRE(r_meta.is_linear == true);
        REQUIRE(r_meta.is_dynamic == false);

        constexpr auto c_meta = get_device_metadata<Capacitor>();
        REQUIRE(c_meta.name == "Capacitor");
        REQUIRE(c_meta.is_dynamic == true);

        constexpr auto l_meta = get_device_metadata<Inductor>();
        REQUIRE(l_meta.name == "Inductor");
        REQUIRE(l_meta.is_dynamic == true);
    }

    SECTION("Source device metadata") {
        constexpr auto vs_meta = get_device_metadata<VoltageSource>();
        REQUIRE(vs_meta.name == "VoltageSource");
        REQUIRE(vs_meta.category == "source");

        constexpr auto cs_meta = get_device_metadata<CurrentSource>();
        REQUIRE(cs_meta.name == "CurrentSource");
        REQUIRE(cs_meta.category == "source");
    }

    SECTION("Active device metadata") {
        constexpr auto mosfet_meta = get_device_metadata<MOSFET>();
        REQUIRE(mosfet_meta.name == "MOSFET");
        REQUIRE(mosfet_meta.category == "active");
        REQUIRE(mosfet_meta.pin_count == 3);
        REQUIRE(mosfet_meta.is_linear == false);
        REQUIRE(mosfet_meta.has_thermal_model == true);

        constexpr auto igbt_meta = get_device_metadata<IGBT>();
        REQUIRE(igbt_meta.name == "IGBT");
        REQUIRE(igbt_meta.has_thermal_model == true);
    }

    SECTION("HasDeviceMetadata concept for all devices") {
        REQUIRE(HasDeviceMetadata<Resistor>);
        REQUIRE(HasDeviceMetadata<Capacitor>);
        REQUIRE(HasDeviceMetadata<Inductor>);
        REQUIRE(HasDeviceMetadata<VoltageSource>);
        REQUIRE(HasDeviceMetadata<CurrentSource>);
        REQUIRE(HasDeviceMetadata<IdealDiode>);
        REQUIRE(HasDeviceMetadata<IdealSwitch>);
        REQUIRE(HasDeviceMetadata<MOSFET>);
        REQUIRE(HasDeviceMetadata<IGBT>);
        REQUIRE(HasDeviceMetadata<Transformer>);
    }

    SECTION("Parameter registration") {
        // Check that Resistor has parameter registration
        constexpr auto r_params = ParamRegistry<Resistor>::params;
        REQUIRE(r_params.size() == 1);
        REQUIRE(r_params[0].name == "resistance");
        REQUIRE(r_params[0].unit == "Ohm");

        // Check MOSFET parameters
        constexpr auto m_params = ParamRegistry<MOSFET>::params;
        REQUIRE(m_params.size() == 3);
        REQUIRE(m_params[0].name == "vth");
    }
}

// =============================================================================
// Tests for Constexpr Utilities (1.3.5)
// =============================================================================

TEST_CASE("API Constexpr math utilities", "[api][cpp23][constexpr]") {
    using namespace pulsim::v1;

    SECTION("Basic math functions") {
        // These are evaluated at compile time!
        static_assert(cabs(-5.0) == 5.0);
        static_assert(cabs(5.0) == 5.0);
        static_assert(sign(-3.0) == -1.0);
        static_assert(sign(3.0) == 1.0);
        static_assert(sign(0.0) == 0.0);
        static_assert(cmin(3.0, 5.0) == 3.0);
        static_assert(cmax(3.0, 5.0) == 5.0);
        static_assert(cclamp(7.0, 0.0, 5.0) == 5.0);
        static_assert(cclamp(-1.0, 0.0, 5.0) == 0.0);
        static_assert(cclamp(3.0, 0.0, 5.0) == 3.0);

        // Runtime checks
        REQUIRE(cabs(-5.0) == 5.0);
        REQUIRE(sign(-3.0) == -1.0);
        REQUIRE(cmin(3.0, 5.0) == 3.0);
        REQUIRE(cmax(3.0, 5.0) == 5.0);
    }

    SECTION("Power functions") {
        static_assert(cpow(2, 0) == 1);
        static_assert(cpow(2, 3) == 8);
        static_assert(cpow(3, 2) == 9);
        static_assert(cpow_n<int, 0>(2) == 1);
        static_assert(cpow_n<int, 4>(2) == 16);
        static_assert(cpow_n<double, 3>(2.0) == 8.0);

        REQUIRE(cpow(2, 10) == 1024);
        REQUIRE(cpow(2.0, -1) == Approx(0.5));  // Use double for negative exponent
    }

    SECTION("Square root") {
        REQUIRE(csqrt(0.0) == 0.0);
        REQUIRE(csqrt(1.0) == Approx(1.0).margin(1e-10));
        REQUIRE(csqrt(4.0) == Approx(2.0).margin(1e-10));
        REQUIRE(csqrt(9.0) == Approx(3.0).margin(1e-10));
        REQUIRE(csqrt(2.0) == Approx(1.41421356).margin(1e-6));
    }

    SECTION("Exponential and logarithm") {
        REQUIRE(cexp(0.0) == Approx(1.0).margin(1e-10));
        REQUIRE(cexp(1.0) == Approx(2.71828182).margin(1e-6));
        REQUIRE(clog(1.0) == Approx(0.0).margin(1e-10));
        REQUIRE(clog(std::exp(1.0)) == Approx(1.0).margin(1e-6));
    }

    SECTION("Approximate equality") {
        REQUIRE(approx_equal(1.0, 1.0 + 1e-12));
        REQUIRE(approx_equal(1000.0, 1000.001, 1e-5));
        REQUIRE(!approx_equal(1.0, 2.0));
    }

    SECTION("Safe division") {
        REQUIRE(safe_div(10.0, 2.0) == 5.0);
        REQUIRE(safe_div(1.0, 0.0, 999.0) == 999.0);  // Fallback value
    }

    SECTION("Smooth step functions") {
        REQUIRE(smoothstep(0.0, 1.0, -0.5) == 0.0);
        REQUIRE(smoothstep(0.0, 1.0, 0.5) == Approx(0.5).margin(0.1));
        REQUIRE(smoothstep(0.0, 1.0, 1.5) == 1.0);
    }
}

TEST_CASE("API Physical constants", "[api][cpp23][constexpr]") {
    using namespace pulsim::v1;

    SECTION("Constants are constexpr") {
        static_assert(constants::k_B > 0);
        static_assert(constants::q_e > 0);
        static_assert(constants::V_T_300K > 0);
        static_assert(constants::pi > 3.14);

        REQUIRE(constants::V_T_300K == Approx(0.02585).margin(0.001));
    }

    SECTION("Thermal voltage calculation") {
        // At 300K
        REQUIRE(thermal_voltage(300.0) == Approx(constants::V_T_300K).margin(1e-6));
        // At higher temperature
        REQUIRE(thermal_voltage(400.0) > thermal_voltage(300.0));
    }
}

TEST_CASE("API Constexpr array utilities", "[api][cpp23][constexpr]") {
    using namespace pulsim::v1;

    SECTION("make_filled_array") {
        constexpr auto arr = make_filled_array<double, 5>(3.14);
        static_assert(arr[0] == 3.14);
        static_assert(arr[4] == 3.14);

        REQUIRE(arr[0] == 3.14);
        REQUIRE(arr[4] == 3.14);
    }

    SECTION("make_iota_array") {
        constexpr auto arr = make_iota_array<int, 5>();
        static_assert(arr[0] == 0);
        static_assert(arr[4] == 4);

        REQUIRE(arr[0] == 0);
        REQUIRE(arr[2] == 2);
        REQUIRE(arr[4] == 4);
    }

    SECTION("array_sum") {
        constexpr std::array<int, 4> arr = {1, 2, 3, 4};
        static_assert(array_sum(arr) == 10);

        REQUIRE(array_sum(arr) == 10);
    }

    SECTION("array_dot") {
        constexpr std::array<double, 3> a = {1.0, 2.0, 3.0};
        constexpr std::array<double, 3> b = {4.0, 5.0, 6.0};
        static_assert(array_dot(a, b) == 32.0);  // 1*4 + 2*5 + 3*6

        REQUIRE(array_dot(a, b) == 32.0);
    }

    SECTION("array_norm") {
        constexpr std::array<double, 2> arr = {3.0, 4.0};
        REQUIRE(array_norm(arr) == Approx(5.0).margin(1e-10));  // sqrt(9+16)
    }

    SECTION("array_max and array_min") {
        constexpr std::array<int, 5> arr = {3, 1, 4, 1, 5};
        static_assert(array_max(arr) == 5);
        static_assert(array_min(arr) == 1);

        REQUIRE(array_max(arr) == 5);
        REQUIRE(array_min(arr) == 1);
    }

    SECTION("transform_array") {
        constexpr std::array<int, 3> arr = {1, 2, 3};
        constexpr auto doubled = transform_array(arr, [](int x) { return x * 2; });
        static_assert(doubled[0] == 2);
        static_assert(doubled[1] == 4);
        static_assert(doubled[2] == 6);

        REQUIRE(doubled[0] == 2);
        REQUIRE(doubled[1] == 4);
    }

    SECTION("generate_array") {
        constexpr auto squares = generate_array<int, 5>([](std::size_t i) {
            return static_cast<int>(i * i);
        });
        static_assert(squares[0] == 0);
        static_assert(squares[1] == 1);
        static_assert(squares[2] == 4);
        static_assert(squares[4] == 16);

        REQUIRE(squares[3] == 9);
    }
}

TEST_CASE("API Lookup tables", "[api][cpp23][constexpr]") {
    using namespace pulsim::v1;

    SECTION("LUT generation and interpolation") {
        // Create a lookup table for x^2 from 0 to 10
        constexpr auto lut = make_lut<double, 11>([](double x) { return x * x; }, 0.0, 10.0);

        // Check exact values at sample points
        REQUIRE(lut[0] == Approx(0.0));
        REQUIRE(lut[5] == Approx(25.0));
        REQUIRE(lut[10] == Approx(100.0));

        // Test interpolation
        REQUIRE(lut_interpolate(lut, 2.5, 0.0, 10.0) == Approx(6.25).margin(0.5));
    }
}

TEST_CASE("API Unit conversions", "[api][cpp23][constexpr]") {
    using namespace pulsim::v1;

    SECTION("SI prefixes") {
        static_assert(units::nano == 1e-9);
        static_assert(units::micro == 1e-6);
        static_assert(units::milli == 1e-3);
        static_assert(units::kilo == 1e3);
        static_assert(units::mega == 1e6);

        REQUIRE(1.0 * units::micro == 1e-6);
    }

    SECTION("Frequency conversions") {
        REQUIRE(units::hz_to_rad(1.0) == Approx(2.0 * constants::pi));
        REQUIRE(units::rad_to_hz(2.0 * constants::pi) == Approx(1.0));
        REQUIRE(units::period_to_freq(0.001) == Approx(1000.0));
    }
}

// =============================================================================
// Tests for Numeric Types (Phase 2.1)
// =============================================================================

TEST_CASE("API Real type configuration", "[api][numeric][real]") {
    using namespace pulsim::v1;

    SECTION("Precision selection") {
        static_assert(std::is_same_v<RealT<Precision::Double>, double>);
        static_assert(std::is_same_v<RealT<Precision::Single>, float>);
        static_assert(std::is_same_v<RealD, double>);
        static_assert(std::is_same_v<RealS, float>);
        static_assert(std::is_same_v<Real, double>);  // Default alias
    }

    SECTION("RealTraits") {
        static_assert(RealTraits<double>::default_abstol == 1e-9);
        static_assert(RealTraits<float>::default_abstol == 1e-6f);
        static_assert(RealTraits<double>::default_reltol == 1e-3);

        REQUIRE(RealTraits<double>::epsilon > 0);
        REQUIRE(RealTraits<double>::digits == 53);  // IEEE 754 double
    }

    SECTION("RealType concept") {
        static_assert(RealType<double>);
        static_assert(RealType<float>);
        static_assert(!RealType<int>);
    }
}

TEST_CASE("API Index type configuration", "[api][numeric][index]") {
    using namespace pulsim::v1;

    SECTION("Index width selection") {
        static_assert(std::is_same_v<IndexT<IndexWidth::Narrow>, std::int32_t>);
        static_assert(std::is_same_v<IndexT<IndexWidth::Wide>, std::int64_t>);
        static_assert(sizeof(Index32) == 4);
        static_assert(sizeof(Index64) == 8);
        static_assert(std::is_same_v<Index, std::int32_t>);  // Default alias
    }

    SECTION("Ground node constant") {
        static_assert(ground_node_v<Index32> == -1);
        static_assert(ground_node_v<Index64> == -1);
        REQUIRE(ground_node == -1);  // Default constant
    }

    SECTION("IndexType concept") {
        static_assert(IndexType<int>);
        static_assert(IndexType<std::int64_t>);
        static_assert(!IndexType<unsigned int>);
    }
}

TEST_CASE("API StaticVector", "[api][numeric][vector]") {
    using namespace pulsim::v1;

    SECTION("Construction") {
        StaticVector<double, 3> v1;  // Default: zeros
        REQUIRE(v1[0] == 0.0);
        REQUIRE(v1[1] == 0.0);
        REQUIRE(v1[2] == 0.0);

        StaticVector<double, 3> v2(5.0);  // Fill
        REQUIRE(v2[0] == 5.0);
        REQUIRE(v2[1] == 5.0);

        StaticVector<int, 3> v3{1, 2, 3};  // Initializer list
        REQUIRE(v3[0] == 1);
        REQUIRE(v3[1] == 2);
        REQUIRE(v3[2] == 3);

        Vec3d v4(1.0, 2.0, 3.0);  // Variadic
        REQUIRE(v4[0] == 1.0);
        REQUIRE(v4[2] == 3.0);
    }

    SECTION("Element access") {
        Vec3d v{1.0, 2.0, 3.0};
        REQUIRE(v[0] == 1.0);
        REQUIRE(v.front() == 1.0);
        REQUIRE(v.back() == 3.0);
        REQUIRE(v.size() == 3);
    }

    SECTION("Arithmetic operations") {
        Vec3d a{1.0, 2.0, 3.0};
        Vec3d b{4.0, 5.0, 6.0};

        auto sum = a + b;
        REQUIRE(sum[0] == 5.0);
        REQUIRE(sum[1] == 7.0);
        REQUIRE(sum[2] == 9.0);

        auto diff = b - a;
        REQUIRE(diff[0] == 3.0);

        auto scaled = a * 2.0;
        REQUIRE(scaled[0] == 2.0);
        REQUIRE(scaled[2] == 6.0);

        auto neg = -a;
        REQUIRE(neg[0] == -1.0);
    }

    SECTION("Vector operations") {
        Vec3d a{1.0, 2.0, 3.0};
        Vec3d b{4.0, 5.0, 6.0};

        // Dot product: 1*4 + 2*5 + 3*6 = 32
        REQUIRE(a.dot(b) == 32.0);

        // Squared norm: 1 + 4 + 9 = 14
        REQUIRE(a.squared_norm() == 14.0);

        // Norm
        REQUIRE(a.norm() == Approx(std::sqrt(14.0)));

        // Sum
        REQUIRE(a.sum() == 6.0);

        // Min/Max
        REQUIRE(a.min_element() == 1.0);
        REQUIRE(a.max_element() == 3.0);

        Vec3d c{-5.0, 3.0, -2.0};
        REQUIRE(c.max_abs() == 5.0);
    }

    SECTION("Constexpr operations") {
        constexpr Vec3<int> v{1, 2, 3};
        static_assert(v[0] == 1);
        static_assert(v.size() == 3);
        static_assert((v + Vec3<int>{1, 1, 1})[0] == 2);
        static_assert(v.dot(Vec3<int>{1, 1, 1}) == 6);
    }

    SECTION("Iteration") {
        Vec3d v{1.0, 2.0, 3.0};
        double sum = 0;
        for (double x : v) sum += x;
        REQUIRE(sum == 6.0);
    }
}

TEST_CASE("API StaticMatrix", "[api][numeric][matrix]") {
    using namespace pulsim::v1;

    SECTION("Construction") {
        Mat2d m1;  // Default: zeros
        REQUIRE(m1(0, 0) == 0.0);

        Mat2d m2(1.0);  // Fill with 1
        REQUIRE(m2(0, 0) == 1.0);
        REQUIRE(m2(1, 1) == 1.0);

        Mat2d m3{{1.0, 2.0}, {3.0, 4.0}};  // Initializer list
        REQUIRE(m3(0, 0) == 1.0);
        REQUIRE(m3(0, 1) == 2.0);
        REQUIRE(m3(1, 0) == 3.0);
        REQUIRE(m3(1, 1) == 4.0);
    }

    SECTION("Identity matrix") {
        auto I = Mat3d::identity();
        REQUIRE(I(0, 0) == 1.0);
        REQUIRE(I(1, 1) == 1.0);
        REQUIRE(I(2, 2) == 1.0);
        REQUIRE(I(0, 1) == 0.0);
        REQUIRE(I(1, 0) == 0.0);
    }

    SECTION("Row and column access") {
        Mat2d m{{1.0, 2.0}, {3.0, 4.0}};
        auto r0 = m.row(0);
        REQUIRE(r0[0] == 1.0);
        REQUIRE(r0[1] == 2.0);

        auto c1 = m.col(1);
        REQUIRE(c1[0] == 2.0);
        REQUIRE(c1[1] == 4.0);
    }

    SECTION("Arithmetic operations") {
        Mat2d a{{1.0, 2.0}, {3.0, 4.0}};
        Mat2d b{{5.0, 6.0}, {7.0, 8.0}};

        auto sum = a + b;
        REQUIRE(sum(0, 0) == 6.0);
        REQUIRE(sum(1, 1) == 12.0);

        auto scaled = a * 2.0;
        REQUIRE(scaled(0, 0) == 2.0);
        REQUIRE(scaled(1, 1) == 8.0);
    }

    SECTION("Matrix-vector multiplication") {
        Mat2d m{{1.0, 2.0}, {3.0, 4.0}};
        Vec2d v{1.0, 1.0};

        auto result = m * v;
        REQUIRE(result[0] == 3.0);   // 1*1 + 2*1
        REQUIRE(result[1] == 7.0);   // 3*1 + 4*1
    }

    SECTION("Matrix-matrix multiplication") {
        Mat2d a{{1.0, 2.0}, {3.0, 4.0}};
        Mat2d b{{5.0, 6.0}, {7.0, 8.0}};

        auto c = a * b;
        REQUIRE(c(0, 0) == 19.0);  // 1*5 + 2*7
        REQUIRE(c(0, 1) == 22.0);  // 1*6 + 2*8
        REQUIRE(c(1, 0) == 43.0);  // 3*5 + 4*7
        REQUIRE(c(1, 1) == 50.0);  // 3*6 + 4*8
    }

    SECTION("Transpose") {
        Mat2d m{{1.0, 2.0}, {3.0, 4.0}};
        auto t = m.transpose();
        REQUIRE(t(0, 0) == 1.0);
        REQUIRE(t(0, 1) == 3.0);
        REQUIRE(t(1, 0) == 2.0);
        REQUIRE(t(1, 1) == 4.0);
    }

    SECTION("Determinant and inverse") {
        Mat2d m{{4.0, 7.0}, {2.0, 6.0}};
        REQUIRE(m.determinant() == Approx(10.0));  // 4*6 - 7*2

        auto inv = m.inverse();
        auto identity = m * inv;
        REQUIRE(identity(0, 0) == Approx(1.0).margin(1e-10));
        REQUIRE(identity(0, 1) == Approx(0.0).margin(1e-10));
        REQUIRE(identity(1, 0) == Approx(0.0).margin(1e-10));
        REQUIRE(identity(1, 1) == Approx(1.0).margin(1e-10));
    }

    SECTION("Trace") {
        Mat3d m{{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}, {7.0, 8.0, 9.0}};
        REQUIRE(m.trace() == 15.0);  // 1 + 5 + 9
    }

    SECTION("Constexpr operations") {
        constexpr Mat2<int> m{{1, 2}, {3, 4}};
        static_assert(m(0, 0) == 1);
        static_assert(m(1, 1) == 4);
        static_assert(Mat2<int>::identity()(0, 0) == 1);
    }
}

TEST_CASE("API SparsityPattern", "[api][numeric][sparsity]") {
    using namespace pulsim::v1;

    SECTION("Construction") {
        SparsityPattern<10> p;
        REQUIRE(p.size() == 0);
        REQUIRE(p.empty());

        p.add(0, 0);
        p.add(0, 1);
        p.add(1, 0);
        p.add(1, 1);
        REQUIRE(p.size() == 4);
        REQUIRE(!p.empty());
    }

    SECTION("Predefined patterns") {
        auto p2x2 = make_2x2_pattern();
        REQUIRE(p2x2.size() == 4);
        REQUIRE(p2x2.contains(0, 0));
        REQUIRE(p2x2.contains(1, 1));

        auto p3x3 = make_3x3_pattern();
        REQUIRE(p3x3.size() == 9);
    }

    SECTION("Contains check") {
        SparsityPattern<10> p{{PatternEntry{0, 0}, PatternEntry{1, 1}}};
        REQUIRE(p.contains(0, 0));
        REQUIRE(p.contains(1, 1));
        REQUIRE(!p.contains(0, 1));
    }

    SECTION("Merge patterns") {
        SparsityPattern<4> p1{{PatternEntry{0, 0}, PatternEntry{0, 1}}};
        SparsityPattern<4> p2{{PatternEntry{1, 0}, PatternEntry{1, 1}}};

        auto merged = p1.merge(p2);
        REQUIRE(merged.size() == 4);
        REQUIRE(merged.contains(0, 0));
        REQUIRE(merged.contains(1, 1));
    }

    SECTION("Max indices") {
        SparsityPattern<4> p{{PatternEntry{0, 0}, PatternEntry{2, 3}}};
        auto [max_r, max_c] = p.max_indices();
        REQUIRE(max_r == 2);
        REQUIRE(max_c == 3);
    }

    SECTION("Iteration") {
        auto p = make_2x2_pattern();
        int count = 0;
        for (const auto& entry : p) {
            REQUIRE(entry.valid());
            ++count;
        }
        REQUIRE(count == 4);
    }
}

TEST_CASE("API Units and dimensional analysis", "[api][numeric][units]") {
    using namespace pulsim::v1;
    using namespace pulsim::v1::literals;

    SECTION("Quantity creation") {
        Voltage<> v(5.0);
        REQUIRE(v.value() == 5.0);

        Current<> i(2.0);
        REQUIRE(i.value() == 2.0);
    }

    SECTION("User-defined literals") {
        auto v = 5.0_V;
        REQUIRE(v.value() == 5.0);

        auto v_mV = 100.0_mV;
        REQUIRE(v_mV.value() == Approx(0.1));

        auto i = 1.0_A;
        REQUIRE(i.value() == 1.0);

        auto i_mA = 500.0_mA;
        REQUIRE(i_mA.value() == Approx(0.5));

        auto c = 100.0_uF;
        REQUIRE(c.value() == Approx(100e-6));

        auto l = 10.0_mH;
        REQUIRE(l.value() == Approx(10e-3));

        auto t = 1.0_ms;
        REQUIRE(t.value() == Approx(1e-3));

        auto f = 1.0_kHz;
        REQUIRE(f.value() == Approx(1e3));
    }

    SECTION("Same-dimension arithmetic") {
        auto v1 = 5.0_V;
        auto v2 = 3.0_V;

        auto sum = v1 + v2;
        REQUIRE(sum.value() == 8.0);

        auto diff = v1 - v2;
        REQUIRE(diff.value() == 2.0);

        auto neg = -v1;
        REQUIRE(neg.value() == -5.0);

        auto scaled = v1 * 2.0;
        REQUIRE(scaled.value() == 10.0);
    }

    SECTION("Dimensional analysis: V = I * R") {
        Current<> i(2.0);
        Resistance<> r(100.0);

        auto v = i * r;  // Should be Voltage
        static_assert(std::is_same_v<decltype(v), Voltage<>>);
        REQUIRE(v.value() == 200.0);
    }

    SECTION("Dimensional analysis: P = V * I") {
        Voltage<> v(10.0);
        Current<> i(2.0);

        auto p = v * i;  // Should be Power
        static_assert(std::is_same_v<decltype(p), Power<>>);
        REQUIRE(p.value() == 20.0);
    }

    SECTION("Dimensional analysis: R = V / I") {
        Voltage<> v(10.0);
        Current<> i(2.0);

        auto r = v / i;  // Should be Resistance
        static_assert(std::is_same_v<decltype(r), Resistance<>>);
        REQUIRE(r.value() == 5.0);
    }

    SECTION("Comparison") {
        auto v1 = 5.0_V;
        auto v2 = 3.0_V;
        auto v3 = 5.0_V;

        REQUIRE(v1 > v2);
        REQUIRE(v2 < v1);
        REQUIRE(v1 == v3);
        REQUIRE(v1 >= v3);
        REQUIRE(v2 <= v1);
    }
}

// =============================================================================
// Tests for Normalization/Scaling Helpers (Phase 2.1.7)
// =============================================================================

TEST_CASE("API ScalingFactors", "[api][numeric][scaling]") {
    using namespace pulsim::v1;

    SECTION("Default scaling") {
        ScalingFactors<> sf;
        REQUIRE(sf.voltage_scale == 1.0);
        REQUIRE(sf.current_scale == 1.0);
        REQUIRE(sf.time_scale == 1.0);
        REQUIRE(sf.conductance_scale == 1.0);
    }

    SECTION("Custom scaling") {
        ScalingFactors<> sf(100.0, 10.0, 1e-6);
        REQUIRE(sf.voltage_scale == 100.0);
        REQUIRE(sf.current_scale == 10.0);
        REQUIRE(sf.time_scale == 1e-6);
        REQUIRE(sf.conductance_scale == Approx(0.1));  // 10/100
    }

    SECTION("Preset scaling factors") {
        auto pe = ScalingFactors<>::power_electronics();
        REQUIRE(pe.voltage_scale == 100.0);
        REQUIRE(pe.current_scale == 1.0);

        auto sig = ScalingFactors<>::signal_level();
        REQUIRE(sig.voltage_scale == 1.0);
        REQUIRE(sig.current_scale == 1e-3);

        auto hp = ScalingFactors<>::high_power();
        REQUIRE(hp.voltage_scale == 1000.0);
        REQUIRE(hp.current_scale == 100.0);
    }

    SECTION("From circuit characteristics") {
        auto sf = ScalingFactors<>::from_circuit(400.0, 50.0, 1e-6);
        REQUIRE(sf.voltage_scale == 400.0);
        REQUIRE(sf.current_scale == 50.0);
        REQUIRE(sf.time_scale == 1e-6);
    }
}

TEST_CASE("API VariableNormalizer", "[api][numeric][scaling]") {
    using namespace pulsim::v1;

    SECTION("Identity normalization") {
        VariableNormalizer<> norm;
        REQUIRE_FALSE(norm.is_active());
        REQUIRE(norm.normalize_voltage(100.0) == 100.0);
        REQUIRE(norm.denormalize_voltage(100.0) == 100.0);
    }

    SECTION("Voltage/current normalization") {
        ScalingFactors<> sf(100.0, 10.0, 1e-6);
        VariableNormalizer<> norm(sf);

        REQUIRE(norm.is_active());

        // Normalize
        REQUIRE(norm.normalize_voltage(200.0) == Approx(2.0));   // 200/100
        REQUIRE(norm.normalize_current(5.0) == Approx(0.5));     // 5/10

        // Denormalize
        REQUIRE(norm.denormalize_voltage(2.0) == Approx(200.0));
        REQUIRE(norm.denormalize_current(0.5) == Approx(5.0));
    }

    SECTION("Conductance/resistance normalization") {
        ScalingFactors<> sf(100.0, 10.0, 1e-6);
        VariableNormalizer<> norm(sf);

        // G = I/V, so G_base = 10/100 = 0.1 S
        REQUIRE(norm.normalize_conductance(0.05) == Approx(0.5));   // 0.05/0.1
        REQUIRE(norm.normalize_resistance(200.0) == Approx(20.0));   // 200*0.1

        REQUIRE(norm.denormalize_conductance(0.5) == Approx(0.05));
        REQUIRE(norm.denormalize_resistance(20.0) == Approx(200.0));
    }

    SECTION("Capacitance/inductance normalization") {
        ScalingFactors<> sf(100.0, 10.0, 1e-6);  // V=100, I=10, t=1μs
        VariableNormalizer<> norm(sf);

        // C_norm = C * V / (I * t) = C * 100 / (10 * 1e-6) = C * 1e7
        double c_phys = 100e-6;  // 100μF
        double c_norm = norm.normalize_capacitance(c_phys);
        REQUIRE(c_norm == Approx(100e-6 * 100.0 / (10.0 * 1e-6)));

        // L_norm = L * I / (V * t) = L * 10 / (100 * 1e-6) = L * 1e5
        double l_phys = 10e-3;  // 10mH
        double l_norm = norm.normalize_inductance(l_phys);
        REQUIRE(l_norm == Approx(10e-3 * 10.0 / (100.0 * 1e-6)));
    }
}

TEST_CASE("API WeightedNorm", "[api][numeric][scaling]") {
    using namespace pulsim::v1;

    SECTION("Default tolerances") {
        WeightedNorm<> wnorm;
        auto tol = wnorm.tolerances();
        REQUIRE(tol.abstol_v == 1e-9);
        REQUIRE(tol.reltol_v == 1e-3);
        REQUIRE(tol.abstol_i == 1e-12);
        REQUIRE(tol.reltol_i == 1e-3);
    }

    SECTION("Convergence check") {
        WeightedNorm<>::Tolerances tol{1e-6, 1e-3, 1e-9, 1e-3};
        WeightedNorm<> wnorm(tol);

        // Simple 3-node, 1-branch system
        Eigen::VectorXd delta(4);
        Eigen::VectorXd solution(4);

        // Solution: V1=10V, V2=5V, V3=3V, I1=0.1A
        solution << 10.0, 5.0, 3.0, 0.1;

        // Small delta - should converge
        delta << 1e-9, 1e-9, 1e-9, 1e-12;
        REQUIRE(wnorm.has_converged(delta, solution, 3, 1));

        // Large delta - should not converge
        delta << 0.1, 0.1, 0.1, 0.001;
        REQUIRE_FALSE(wnorm.has_converged(delta, solution, 3, 1));
    }
}

TEST_CASE("API PerUnitSystem", "[api][numeric][scaling]") {
    using namespace pulsim::v1;

    SECTION("Base quantity calculations") {
        PerUnitSystem<>::BaseQuantities base{1000.0, 400.0, 50.0};  // 1kVA, 400V, 50Hz

        REQUIRE(base.I_base() == Approx(2.5));     // 1000/400
        REQUIRE(base.Z_base() == Approx(160.0));   // 400²/1000
        REQUIRE(base.Y_base() == Approx(0.00625)); // 1000/400²
    }

    SECTION("Per-unit conversions") {
        PerUnitSystem<>::BaseQuantities base{1000.0, 400.0, 50.0};
        PerUnitSystem<> pu(base);

        // Voltage: 800V should be 2 p.u.
        REQUIRE(pu.to_pu_voltage(800.0) == Approx(2.0));
        REQUIRE(pu.from_pu_voltage(2.0) == Approx(800.0));

        // Current: 5A should be 2 p.u.
        REQUIRE(pu.to_pu_current(5.0) == Approx(2.0));
        REQUIRE(pu.from_pu_current(2.0) == Approx(5.0));

        // Power: 2000W should be 2 p.u.
        REQUIRE(pu.to_pu_power(2000.0) == Approx(2.0));
        REQUIRE(pu.from_pu_power(2.0) == Approx(2000.0));

        // Impedance: 320Ω should be 2 p.u.
        REQUIRE(pu.to_pu_impedance(320.0) == Approx(2.0));
        REQUIRE(pu.from_pu_impedance(2.0) == Approx(320.0));
    }
}

// =============================================================================
// Tests for Expression Templates (Phase 2.4)
// =============================================================================

TEST_CASE("API Expression templates - basic operations", "[api][expression]") {
    using namespace pulsim::v1;

    SECTION("Vector addition expression") {
        StaticVector<double, 4> a{1.0, 2.0, 3.0, 4.0};
        StaticVector<double, 4> b{5.0, 6.0, 7.0, 8.0};

        auto expr = a + b;
        REQUIRE(expr[0] == 6.0);
        REQUIRE(expr[1] == 8.0);
        REQUIRE(expr[2] == 10.0);
        REQUIRE(expr[3] == 12.0);
        REQUIRE(expr.size() == 4);
    }

    SECTION("Vector subtraction expression") {
        StaticVector<double, 4> a{10.0, 20.0, 30.0, 40.0};
        StaticVector<double, 4> b{1.0, 2.0, 3.0, 4.0};

        auto expr = a - b;
        REQUIRE(expr[0] == 9.0);
        REQUIRE(expr[1] == 18.0);
        REQUIRE(expr[2] == 27.0);
        REQUIRE(expr[3] == 36.0);
    }

    SECTION("Scalar multiplication expression") {
        StaticVector<double, 4> a{1.0, 2.0, 3.0, 4.0};

        auto expr1 = 2.0 * a;
        REQUIRE(expr1[0] == 2.0);
        REQUIRE(expr1[3] == 8.0);

        auto expr2 = a * 3.0;
        REQUIRE(expr2[0] == 3.0);
        REQUIRE(expr2[3] == 12.0);
    }

    SECTION("Scalar division expression") {
        StaticVector<double, 4> a{10.0, 20.0, 30.0, 40.0};

        auto expr = a / 10.0;
        REQUIRE(expr[0] == 1.0);
        REQUIRE(expr[3] == 4.0);
    }

    SECTION("Negation expression") {
        StaticVector<double, 4> a{1.0, -2.0, 3.0, -4.0};

        auto expr = -a;
        REQUIRE(expr[0] == -1.0);
        REQUIRE(expr[1] == 2.0);
        REQUIRE(expr[2] == -3.0);
        REQUIRE(expr[3] == 4.0);
    }
}

TEST_CASE("API Expression templates - nested expressions", "[api][expression]") {
    using namespace pulsim::v1;

    SECTION("Chained addition") {
        StaticVector<double, 3> a{1.0, 2.0, 3.0};
        StaticVector<double, 3> b{4.0, 5.0, 6.0};
        StaticVector<double, 3> c{7.0, 8.0, 9.0};

        auto expr = a + b + c;
        REQUIRE(expr[0] == 12.0);
        REQUIRE(expr[1] == 15.0);
        REQUIRE(expr[2] == 18.0);
    }

    SECTION("Mixed operations") {
        StaticVector<double, 3> a{1.0, 2.0, 3.0};
        StaticVector<double, 3> b{1.0, 1.0, 1.0};

        // 2 * (a + b)
        auto expr = 2.0 * (a + b);
        REQUIRE(expr[0] == 4.0);
        REQUIRE(expr[1] == 6.0);
        REQUIRE(expr[2] == 8.0);
    }

    SECTION("Complex expression") {
        StaticVector<double, 3> a{1.0, 2.0, 3.0};
        StaticVector<double, 3> b{4.0, 5.0, 6.0};

        // (a + b) - 2.0 * a
        auto expr = (a + b) - (2.0 * a);
        REQUIRE(expr[0] == Approx(3.0));  // (1+4) - 2*1 = 3
        REQUIRE(expr[1] == Approx(3.0));  // (2+5) - 2*2 = 3
        REQUIRE(expr[2] == Approx(3.0));  // (3+6) - 2*3 = 3
    }
}

TEST_CASE("API Expression templates - lazy evaluation", "[api][expression]") {
    using namespace pulsim::v1;

    SECTION("eval function") {
        StaticVector<double, 4> a{1.0, 2.0, 3.0, 4.0};
        StaticVector<double, 4> b{5.0, 6.0, 7.0, 8.0};

        auto expr = a + b;
        auto result = eval<decltype(expr), 4>(expr);

        REQUIRE(result[0] == 6.0);
        REQUIRE(result[3] == 12.0);
    }

    SECTION("eval_to function") {
        StaticVector<double, 4> a{1.0, 2.0, 3.0, 4.0};
        StaticVector<double, 4> b{5.0, 6.0, 7.0, 8.0};
        StaticVector<double, 4> result;

        auto expr = a + b;
        eval_to(expr, result);

        REQUIRE(result[0] == 6.0);
        REQUIRE(result[3] == 12.0);
    }

    SECTION("Expression sum") {
        StaticVector<double, 4> a{1.0, 2.0, 3.0, 4.0};
        StaticVector<double, 4> b{1.0, 1.0, 1.0, 1.0};

        auto expr = a + b;
        REQUIRE(expr.sum() == 14.0);  // (1+1) + (2+1) + (3+1) + (4+1)
    }

    SECTION("Expression dot product") {
        StaticVector<double, 3> a{1.0, 2.0, 3.0};
        StaticVector<double, 3> b{1.0, 1.0, 1.0};
        StaticVector<double, 3> c{4.0, 5.0, 6.0};

        auto expr = a + b;  // {2, 3, 4}
        REQUIRE(expr.dot(c) == 47.0);  // 2*4 + 3*5 + 4*6 = 47
    }

    SECTION("Expression norms") {
        StaticVector<double, 3> a{3.0, 0.0, 4.0};
        StaticVector<double, 3> b{0.0, 0.0, 0.0};

        auto expr = a + b;
        REQUIRE(expr.squared_norm() == 25.0);  // 9 + 0 + 16
        REQUIRE(expr.norm() == Approx(5.0));
    }
}

TEST_CASE("API Expression templates - VectorRef", "[api][expression]") {
    using namespace pulsim::v1;

    SECTION("VectorRef from raw array") {
        double data[] = {1.0, 2.0, 3.0, 4.0};
        VectorRef<double> ref(data, 4);

        REQUIRE(ref.size() == 4);
        REQUIRE(ref[0] == 1.0);
        REQUIRE(ref[3] == 4.0);
    }

    SECTION("VectorRef from StaticVector") {
        StaticVector<double, 4> vec{1.0, 2.0, 3.0, 4.0};
        VectorRef ref(vec);

        REQUIRE(ref.size() == 4);
        REQUIRE(ref[0] == 1.0);
    }

    SECTION("VectorRef in expressions") {
        StaticVector<double, 4> a{1.0, 2.0, 3.0, 4.0};
        double b_data[] = {5.0, 6.0, 7.0, 8.0};
        VectorRef<double> b(b_data, 4);

        auto expr = a + b;
        REQUIRE(expr[0] == 6.0);
        REQUIRE(expr[3] == 12.0);
    }
}

TEST_CASE("API Expression templates - constexpr", "[api][expression]") {
    using namespace pulsim::v1;

    SECTION("Constexpr expression evaluation") {
        constexpr StaticVector<double, 4> a{1.0, 2.0, 3.0, 4.0};
        constexpr StaticVector<double, 4> b{5.0, 6.0, 7.0, 8.0};

        // Expression can be evaluated at compile time
        static_assert((a + b)[0] == 6.0);
        static_assert((a + b)[3] == 12.0);
        static_assert((2.0 * a)[0] == 2.0);
        static_assert((a - b)[0] == -4.0);
    }
}

// =============================================================================
// Tests for Compile-Time Circuit Analysis (Phase 2.5)
// =============================================================================

TEST_CASE("API Circuit device traits", "[api][circuit][traits]") {
    using namespace pulsim::v1;

    SECTION("Terminal counts") {
        REQUIRE(circuit_device_traits<Resistor>::terminal_count == 2);
        REQUIRE(circuit_device_traits<Capacitor>::terminal_count == 2);
        REQUIRE(circuit_device_traits<Inductor>::terminal_count == 2);
        REQUIRE(circuit_device_traits<VoltageSource>::terminal_count == 2);
        REQUIRE(circuit_device_traits<MOSFET>::terminal_count == 3);
        REQUIRE(circuit_device_traits<Transformer>::terminal_count == 4);
    }

    SECTION("Branch counts") {
        REQUIRE(circuit_device_traits<Resistor>::branch_count == 0);
        REQUIRE(circuit_device_traits<Capacitor>::branch_count == 0);
        REQUIRE(circuit_device_traits<Inductor>::branch_count == 1);
        REQUIRE(circuit_device_traits<VoltageSource>::branch_count == 1);
        REQUIRE(circuit_device_traits<CurrentSource>::branch_count == 0);
        REQUIRE(circuit_device_traits<Transformer>::branch_count == 2);
    }

    SECTION("Linear/nonlinear classification") {
        REQUIRE(circuit_device_traits<Resistor>::is_linear == true);
        REQUIRE(circuit_device_traits<Capacitor>::is_linear == true);
        REQUIRE(circuit_device_traits<Inductor>::is_linear == true);
        REQUIRE(circuit_device_traits<VoltageSource>::is_linear == true);
        REQUIRE(circuit_device_traits<IdealDiode>::is_linear == false);
        REQUIRE(circuit_device_traits<MOSFET>::is_linear == false);
    }

    SECTION("History requirements") {
        REQUIRE(circuit_device_traits<Resistor>::needs_history == false);
        REQUIRE(circuit_device_traits<Capacitor>::needs_history == true);
        REQUIRE(circuit_device_traits<Inductor>::needs_history == true);
        REQUIRE(circuit_device_traits<VoltageSource>::needs_history == false);
    }

    SECTION("Switching classification") {
        REQUIRE(circuit_device_traits<Resistor>::is_switching == false);
        REQUIRE(circuit_device_traits<IdealSwitch>::is_switching == true);
        REQUIRE(circuit_device_traits<IdealDiode>::is_switching == true);
        REQUIRE(circuit_device_traits<MOSFET>::is_switching == true);
        REQUIRE(circuit_device_traits<IGBT>::is_switching == true);
    }
}

TEST_CASE("API Compile-time counting utilities", "[api][circuit][counting]") {
    using namespace pulsim::v1;

    SECTION("Node counting") {
        static_assert(count_max_nodes_v<Resistor> == 2);
        static_assert(count_max_nodes_v<Resistor, Capacitor> == 4);
        static_assert(count_max_nodes_v<Resistor, Capacitor, VoltageSource> == 6);
        static_assert(count_max_nodes_v<MOSFET> == 3);
    }

    SECTION("Branch counting") {
        static_assert(count_branches_v<Resistor> == 0);
        static_assert(count_branches_v<VoltageSource> == 1);
        static_assert(count_branches_v<Inductor> == 1);
        static_assert(count_branches_v<Resistor, VoltageSource> == 1);
        static_assert(count_branches_v<VoltageSource, Inductor> == 2);
        static_assert(count_branches_v<Transformer> == 2);
    }

    SECTION("Linear device counting") {
        static_assert(count_linear_devices_v<Resistor> == 1);
        static_assert(count_linear_devices_v<Resistor, Capacitor> == 2);
        static_assert(count_linear_devices_v<Resistor, IdealDiode> == 1);
        static_assert(count_nonlinear_devices_v<Resistor, IdealDiode> == 1);
    }
}

TEST_CASE("API Circuit type queries", "[api][circuit][queries]") {
    using namespace pulsim::v1;

    SECTION("Linear circuit check") {
        static_assert(is_linear_circuit<Resistor, Capacitor, VoltageSource>());
        static_assert(is_linear_circuit<Resistor, Inductor>());
        static_assert(!is_linear_circuit<Resistor, IdealDiode>());
        static_assert(!is_linear_circuit<Resistor, MOSFET>());
    }

    SECTION("Reactive elements check") {
        static_assert(!has_reactive_elements<Resistor, VoltageSource>());
        static_assert(has_reactive_elements<Resistor, Capacitor>());
        static_assert(has_reactive_elements<Resistor, Inductor>());
        static_assert(has_reactive_elements<Capacitor, Inductor>());
    }

    SECTION("Switching elements check") {
        static_assert(!has_switching_elements<Resistor, Capacitor>());
        static_assert(has_switching_elements<Resistor, IdealSwitch>());
        static_assert(has_switching_elements<IdealDiode, Resistor>());
        static_assert(has_switching_elements<MOSFET, Resistor>());
    }
}

TEST_CASE("API Topology validation", "[api][circuit][validation]") {
    using namespace pulsim::v1;

    SECTION("RC circuit validation") {
        constexpr auto val = validate_circuit_topology<Resistor, Capacitor, VoltageSource>();
        REQUIRE(val.is_valid);
        REQUIRE(val.has_voltage_reference);
        REQUIRE(val.node_count == 6);  // Max nodes
        REQUIRE(val.branch_count == 1);  // VoltageSource branch
    }

    SECTION("RLC circuit validation") {
        constexpr auto val = validate_circuit_topology<Resistor, Inductor, Capacitor, VoltageSource>();
        REQUIRE(val.is_valid);
        REQUIRE(val.branch_count == 2);  // VoltageSource + Inductor branches
    }

    SECTION("Circuit with no voltage source") {
        constexpr auto val = validate_circuit_topology<Resistor, Capacitor>();
        REQUIRE(val.is_valid);  // Still valid, just no voltage reference
        REQUIRE_FALSE(val.has_voltage_reference);
    }
}

TEST_CASE("API CircuitGraph basic usage", "[api][circuit][graph]") {
    using namespace pulsim::v1;

    SECTION("RC circuit graph creation") {
        Resistor r(1000.0);
        Capacitor c(1e-6, 0.0);
        VoltageSource vs(10.0);

        auto circuit = make_circuit(r, c, vs);

        REQUIRE(circuit.device_type_count == 3);
        REQUIRE(circuit.total_branches == 1);  // Only VoltageSource has branch
        REQUIRE(circuit.all_linear == true);
        REQUIRE(circuit.needs_history == true);  // Capacitor needs history
        REQUIRE(circuit.has_switching == false);
        REQUIRE(circuit.is_valid());
    }

    SECTION("Circuit with nonlinear device") {
        Resistor r(1000.0);
        IdealDiode d(1e3, 1e-9);
        VoltageSource vs(10.0);

        auto circuit = make_circuit(r, d, vs);

        REQUIRE(circuit.all_linear == false);
        REQUIRE(circuit.has_switching == true);
    }

    SECTION("Circuit with MOSFET") {
        Resistor r(1000.0);
        MOSFET m(2.0, 0.1, true);
        VoltageSource vdd(12.0);
        VoltageSource vgs(5.0);

        auto circuit = make_circuit(r, m, vdd, vgs);

        REQUIRE(circuit.device_type_count == 4);
        REQUIRE(circuit.total_branches == 2);  // Two voltage sources
        REQUIRE(circuit.all_linear == false);
        REQUIRE(circuit.has_switching == true);
    }
}

TEST_CASE("API CircuitGraph compile-time properties", "[api][circuit][graph]") {
    using namespace pulsim::v1;

    SECTION("Static assertions on circuit properties") {
        // These are evaluated at compile time
        using RCCircuit = CircuitGraph<Resistor, Capacitor, VoltageSource>;
        static_assert(RCCircuit::device_type_count == 3);
        static_assert(RCCircuit::total_branches == 1);
        static_assert(RCCircuit::all_linear == true);
        static_assert(RCCircuit::needs_history == true);
        static_assert(RCCircuit::has_switching == false);

        using DiodeCircuit = CircuitGraph<Resistor, IdealDiode, VoltageSource>;
        static_assert(DiodeCircuit::all_linear == false);
        static_assert(DiodeCircuit::has_switching == true);

        using InductorCircuit = CircuitGraph<Resistor, Inductor, VoltageSource>;
        static_assert(InductorCircuit::total_branches == 2);  // VSource + Inductor
        static_assert(InductorCircuit::needs_history == true);
    }
}

TEST_CASE("API CircuitGraph sparsity pattern", "[api][circuit][sparsity]") {
    using namespace pulsim::v1;

    SECTION("Circuit type-level sparsity") {
        // The sparsity pattern at type level gives capacity, not actual used entries
        using RVCircuit = CircuitGraph<Resistor, VoltageSource>;
        static_assert(RVCircuit::max_jacobian_nnz > 0);

        using RCVCircuit = CircuitGraph<Resistor, Capacitor, VoltageSource>;
        static_assert(RCVCircuit::max_jacobian_nnz > 0);
    }
}

TEST_CASE("API NodeCollector", "[api][circuit][nodes]") {
    using namespace pulsim::v1;

    SECTION("Collect unique nodes") {
        NodeCollector<Resistor, Capacitor, VoltageSource> collector;

        // Add nodes manually (simulating device node collection)
        collector.add_node(0);
        collector.add_node(1);
        collector.add_node(0);  // Duplicate
        collector.add_node(2);

        REQUIRE(collector.node_count == 3);
        REQUIRE(collector.has_node(0));
        REQUIRE(collector.has_node(1));
        REQUIRE(collector.has_node(2));
        REQUIRE(!collector.has_node(3));
    }

    SECTION("Ground node not collected") {
        NodeCollector<Resistor> collector;

        collector.add_node(-1);  // Ground
        collector.add_node(0);

        REQUIRE(collector.node_count == 1);
        REQUIRE(!collector.has_node(-1));
        REQUIRE(collector.has_node(0));
    }

    SECTION("Node index lookup") {
        NodeCollector<Resistor, Capacitor> collector;

        collector.add_node(5);
        collector.add_node(10);
        collector.add_node(15);

        REQUIRE(collector.node_index(5) == 0);
        REQUIRE(collector.node_index(10) == 1);
        REQUIRE(collector.node_index(15) == 2);
    }
}

TEST_CASE("API StaticSparsityBuilder", "[api][circuit][sparsity]") {
    using namespace pulsim::v1;

    SECTION("Conductance stamp pattern") {
        StaticSparsityBuilder<4, 2> builder;

        // Add conductance stamp between nodes 0 and 1
        builder.add_conductance(0, 1);

        const auto& pattern = builder.pattern();
        REQUIRE(pattern.contains(0, 0));
        REQUIRE(pattern.contains(0, 1));
        REQUIRE(pattern.contains(1, 0));
        REQUIRE(pattern.contains(1, 1));
    }

    SECTION("Voltage source stamp pattern") {
        StaticSparsityBuilder<4, 2> builder;

        // Add voltage source between node 0 and ground, branch index 4
        builder.add_voltage_source(0, -1, 4);

        const auto& pattern = builder.pattern();
        REQUIRE(pattern.contains(0, 4));
        REQUIRE(pattern.contains(4, 0));
    }

    SECTION("Inductor stamp pattern") {
        StaticSparsityBuilder<4, 2> builder;

        // Add inductor between nodes 0 and 1, branch index 4
        builder.add_inductor(0, 1, 4);

        const auto& pattern = builder.pattern();
        REQUIRE(pattern.contains(0, 4));
        REQUIRE(pattern.contains(4, 0));
        REQUIRE(pattern.contains(1, 4));
        REQUIRE(pattern.contains(4, 1));
        REQUIRE(pattern.contains(4, 4));  // Companion resistance
    }
}

// =============================================================================
// Tests for Newton Solver (Phase 3.1)
// =============================================================================

TEST_CASE("API Convergence checker", "[api][solver][convergence]") {
    using namespace pulsim::v1;

    SECTION("Default tolerances") {
        ConvergenceChecker checker;
        auto tol = checker.tolerances();

        REQUIRE(tol.voltage_abstol == Catch::Approx(1e-9));
        REQUIRE(tol.voltage_reltol == Catch::Approx(1e-3));
        REQUIRE(tol.current_abstol == Catch::Approx(1e-12));
        REQUIRE(tol.current_reltol == Catch::Approx(1e-3));
        REQUIRE(tol.residual_tol == Catch::Approx(1e-9));
    }

    SECTION("Weighted norm - small voltage change") {
        ConvergenceChecker checker;

        // 2 voltage nodes, 1 current branch
        Vector solution(3);
        solution << 5.0, 3.0, 0.001;  // 5V, 3V, 1mA

        Vector delta(3);
        delta << 1e-10, 1e-10, 1e-15;  // Tiny changes

        Real error = checker.check_weighted_norm(delta, solution, 2, 1);
        REQUIRE(error < 1.0);  // Should converge
    }

    SECTION("Weighted norm - large voltage change") {
        ConvergenceChecker checker;

        Vector solution(3);
        solution << 5.0, 3.0, 0.001;

        Vector delta(3);
        delta << 1.0, 0.5, 0.0;  // Large voltage changes

        Real error = checker.check_weighted_norm(delta, solution, 2, 1);
        REQUIRE(error > 1.0);  // Should not converge
    }

    SECTION("Per-variable convergence") {
        ConvergenceChecker checker;

        Vector solution(3);
        solution << 5.0, 3.0, 0.001;

        Vector delta(3);
        delta << 1e-10, 1.0, 1e-15;  // Only second voltage is far

        auto conv = checker.check_per_variable(delta, solution, 2, 1);

        REQUIRE(conv.size() == 3);
        REQUIRE(conv[0].converged == true);   // First voltage OK
        REQUIRE(conv[1].converged == false);  // Second voltage NOT OK
        REQUIRE(conv[2].converged == true);   // Current OK
        REQUIRE(conv.non_converged_count() == 1);
        REQUIRE(conv.worst()->index == 1);
    }

    SECTION("Residual check") {
        ConvergenceChecker checker;

        Vector f_small(3);
        f_small << 1e-12, 1e-11, 1e-10;
        REQUIRE(checker.check_residual(f_small) == true);

        Vector f_large(3);
        f_large << 1e-3, 0.0, 0.0;
        REQUIRE(checker.check_residual(f_large) == false);
    }
}

TEST_CASE("API Convergence history", "[api][solver][history]") {
    using namespace pulsim::v1;

    SECTION("Basic history tracking") {
        ConvergenceHistory history;

        REQUIRE(history.empty());

        IterationRecord r1{0, 1.0, 0.1, 0.01, 0.5, 1.0, false};
        IterationRecord r2{1, 0.5, 0.05, 0.005, 0.25, 1.0, false};
        IterationRecord r3{2, 0.1, 0.01, 0.001, 0.05, 1.0, true};

        history.add_record(r1);
        history.add_record(r2);
        history.add_record(r3);

        REQUIRE(history.size() == 3);
        REQUIRE(history[0].iteration == 0);
        REQUIRE(history.last().iteration == 2);
        REQUIRE(history.last().converged == true);
    }

    SECTION("Stall detection") {
        ConvergenceHistory history;

        // Add records where residual barely decreases
        for (int i = 0; i < 10; ++i) {
            IterationRecord r{i, 1.0 - i * 0.001, 0.0, 0.0, 0.0, 1.0, false};
            history.add_record(r);
        }

        // Check stall over last 5 iterations with 90% threshold
        REQUIRE(history.is_stalling(5, 0.9) == true);
    }

    SECTION("Divergence detection") {
        ConvergenceHistory history;

        // Add increasing residuals
        for (int i = 0; i < 5; ++i) {
            IterationRecord r{i, std::pow(2.0, i), 0.0, 0.0, 0.0, 1.0, false};
            history.add_record(r);
        }

        REQUIRE(history.is_diverging(3) == true);
    }

    SECTION("Convergence rate") {
        ConvergenceHistory history;

        // Quadratic convergence: residual reduces by factor of 10 each iteration
        IterationRecord r1{0, 1.0, 0.0, 0.0, 0.0, 1.0, false};
        IterationRecord r2{1, 0.1, 0.0, 0.0, 0.0, 1.0, false};
        IterationRecord r3{2, 0.01, 0.0, 0.0, 0.0, 1.0, true};

        history.add_record(r1);
        history.add_record(r2);
        history.add_record(r3);

        // Rate should be around 0.1 (10x reduction per iteration)
        Real rate = history.convergence_rate();
        REQUIRE(rate == Catch::Approx(0.1).margin(0.01));
    }
}

TEST_CASE("API Per-variable convergence", "[api][solver][per-var]") {
    using namespace pulsim::v1;

    SECTION("Voltage variable") {
        auto v = VariableConvergence::voltage(0, 5.0, 1e-10, 1e-9, 1e-3);

        REQUIRE(v.index == 0);
        REQUIRE(v.is_voltage == true);
        REQUIRE(v.converged == true);
    }

    SECTION("Current variable") {
        auto c = VariableConvergence::current(2, 0.001, 1e-15, 1e-12, 1e-3);

        REQUIRE(c.index == 2);
        REQUIRE(c.is_voltage == false);
        REQUIRE(c.converged == true);
    }

    SECTION("All converged check") {
        PerVariableConvergence conv;
        conv.add(VariableConvergence::voltage(0, 5.0, 1e-10, 1e-9, 1e-3));
        conv.add(VariableConvergence::voltage(1, 3.0, 1e-10, 1e-9, 1e-3));

        REQUIRE(conv.all_converged() == true);
    }

    SECTION("Worst variable") {
        PerVariableConvergence conv;
        conv.add(VariableConvergence::voltage(0, 5.0, 1e-10, 1e-9, 1e-3));  // Small error
        conv.add(VariableConvergence::voltage(1, 3.0, 0.5, 1e-9, 1e-3));    // Large error

        auto worst = conv.worst();
        REQUIRE(worst != nullptr);
        REQUIRE(worst->index == 1);
    }
}

TEST_CASE("API SparseLU policy", "[api][solver][linear]") {
    using namespace pulsim::v1;

    SECTION("Basic solve") {
        SparseLUPolicy solver;

        // Simple 2x2 system: [2 1; 1 3] * x = [1; 2]
        SparseMatrix A(2, 2);
        std::vector<Eigen::Triplet<double>> triplets;
        triplets.emplace_back(0, 0, 2.0);
        triplets.emplace_back(0, 1, 1.0);
        triplets.emplace_back(1, 0, 1.0);
        triplets.emplace_back(1, 1, 3.0);
        A.setFromTriplets(triplets.begin(), triplets.end());

        Vector b(2);
        b << 1.0, 2.0;

        REQUIRE(solver.factorize(A) == true);
        REQUIRE(solver.is_singular() == false);

        auto result = solver.solve(b);
        REQUIRE(result.has_value());

        // Solution: x = [1/5, 3/5]
        Vector x = *result;
        REQUIRE(x[0] == Catch::Approx(0.2).margin(1e-10));
        REQUIRE(x[1] == Catch::Approx(0.6).margin(1e-10));
    }

    SECTION("Concept check") {
        static_assert(LinearSolverPolicy<SparseLUPolicy>);
    }
}

TEST_CASE("API Newton solver - simple system", "[api][solver][newton]") {
    using namespace pulsim::v1;

    SECTION("Solve x^2 = 4 (find x = 2)") {
        NewtonOptions opts;
        opts.max_iterations = 20;
        opts.num_nodes = 0;  // Don't use weighted norm
        opts.num_branches = 0;

        NewtonRaphsonSolver<> solver(opts);

        // System: f(x) = x^2 - 4 = 0, J = 2x
        auto system = [](const Vector& x, Vector& f, SparseMatrix& J) {
            f.resize(1);
            f[0] = x[0] * x[0] - 4.0;

            J.resize(1, 1);
            std::vector<Eigen::Triplet<double>> triplets;
            triplets.emplace_back(0, 0, 2.0 * x[0]);
            J.setFromTriplets(triplets.begin(), triplets.end());
        };

        Vector x0(1);
        x0[0] = 3.0;  // Start near solution

        auto result = solver.solve(x0, system);

        REQUIRE(result.success() == true);
        REQUIRE(result.solution[0] == Catch::Approx(2.0).margin(1e-8));
        REQUIRE(result.iterations < 10);
    }

    SECTION("Solve 2D system: x^2 + y^2 = 5, x*y = 2") {
        NewtonOptions opts;
        opts.max_iterations = 50;
        opts.num_nodes = 0;
        opts.num_branches = 0;
        opts.tolerances.residual_tol = 1e-6;  // Slightly looser tolerance

        NewtonRaphsonSolver<> solver(opts);

        // f1 = x^2 + y^2 - 5, f2 = x*y - 2
        // Solutions: (1,2), (2,1), (-1,-2), (-2,-1)
        auto system = [](const Vector& x, Vector& f, SparseMatrix& J) {
            f.resize(2);
            f[0] = x[0] * x[0] + x[1] * x[1] - 5.0;
            f[1] = x[0] * x[1] - 2.0;

            J.resize(2, 2);
            std::vector<Eigen::Triplet<double>> triplets;
            triplets.emplace_back(0, 0, 2.0 * x[0]);  // df1/dx
            triplets.emplace_back(0, 1, 2.0 * x[1]);  // df1/dy
            triplets.emplace_back(1, 0, x[1]);        // df2/dx
            triplets.emplace_back(1, 1, x[0]);        // df2/dy
            J.setFromTriplets(triplets.begin(), triplets.end());
        };

        Vector x0(2);
        x0 << 1.2, 1.8;  // Closer to (1,2) solution

        auto result = solver.solve(x0, system);

        REQUIRE(result.success() == true);
        // Should converge to one of (1,2) or (2,1)
        Real x2_plus_y2 = result.solution[0] * result.solution[0] +
                          result.solution[1] * result.solution[1];
        Real xy = result.solution[0] * result.solution[1];
        REQUIRE(x2_plus_y2 == Catch::Approx(5.0).margin(1e-6));
        REQUIRE(xy == Catch::Approx(2.0).margin(1e-6));
    }
}

TEST_CASE("API Newton solver - with weighted norm", "[api][solver][newton][weighted]") {
    using namespace pulsim::v1;

    SECTION("Resistive voltage divider DC OP") {
        // Simple 2-resistor divider: Vs=10V, R1=R2=1k -> V_mid = 5V
        NewtonOptions opts;
        opts.max_iterations = 50;
        opts.num_nodes = 2;    // 2 voltage nodes
        opts.num_branches = 1; // 1 current branch (voltage source)
        opts.tolerances.voltage_abstol = 1e-9;
        opts.tolerances.voltage_reltol = 1e-3;

        NewtonRaphsonSolver<> solver(opts);

        const double Vs = 10.0;
        const double R1 = 1000.0;
        const double R2 = 1000.0;

        // Variables: [V1, V2, I_vs]
        // V1 is voltage source positive
        // V2 is middle node (between R1 and R2)
        // Ground is reference
        //
        // Equations:
        // KCL at V1: (V1-V2)/R1 + I_vs = 0
        // KCL at V2: (V2-V1)/R1 + V2/R2 = 0
        // Voltage source: V1 = Vs

        auto system = [&](const Vector& x, Vector& f, SparseMatrix& J) {
            double V1 = x[0];
            double V2 = x[1];
            double Ivs = x[2];

            f.resize(3);
            f[0] = (V1 - V2) / R1 + Ivs;
            f[1] = (V2 - V1) / R1 + V2 / R2;
            f[2] = V1 - Vs;

            J.resize(3, 3);
            std::vector<Eigen::Triplet<double>> triplets;
            // df0/dV1, df0/dV2, df0/dI
            triplets.emplace_back(0, 0, 1.0 / R1);
            triplets.emplace_back(0, 1, -1.0 / R1);
            triplets.emplace_back(0, 2, 1.0);
            // df1/dV1, df1/dV2
            triplets.emplace_back(1, 0, -1.0 / R1);
            triplets.emplace_back(1, 1, 1.0 / R1 + 1.0 / R2);
            // df2/dV1
            triplets.emplace_back(2, 0, 1.0);
            J.setFromTriplets(triplets.begin(), triplets.end());
        };

        Vector x0(3);
        x0 << 0.0, 0.0, 0.0;  // Start from zero

        auto result = solver.solve(x0, system);

        REQUIRE(result.success() == true);
        REQUIRE(result.solution[0] == Catch::Approx(10.0).margin(1e-6));  // V1 = 10V
        REQUIRE(result.solution[1] == Catch::Approx(5.0).margin(1e-6));   // V2 = 5V
        REQUIRE(result.solution[2] == Catch::Approx(-0.005).margin(1e-8)); // I = -5mA
    }
}

TEST_CASE("API Newton solver - edge cases", "[api][solver][newton][edge]") {
    using namespace pulsim::v1;

    SECTION("Max iterations reached") {
        NewtonOptions opts;
        opts.max_iterations = 2;  // Too few iterations
        opts.auto_damping = false;

        NewtonRaphsonSolver<> solver(opts);

        // Hard problem that won't converge in 2 iterations
        auto system = [](const Vector& x, Vector& f, SparseMatrix& J) {
            f.resize(1);
            f[0] = std::exp(x[0]) - 100.0;  // x = ln(100) ≈ 4.6

            J.resize(1, 1);
            std::vector<Eigen::Triplet<double>> triplets;
            triplets.emplace_back(0, 0, std::exp(x[0]));
            J.setFromTriplets(triplets.begin(), triplets.end());
        };

        Vector x0(1);
        x0[0] = 0.0;  // Far from solution

        auto result = solver.solve(x0, system);

        REQUIRE(result.success() == false);
        REQUIRE(result.status == SolverStatus::MaxIterationsReached);
        REQUIRE(result.iterations == 2);
    }

    SECTION("History tracking") {
        NewtonOptions opts;
        opts.max_iterations = 20;
        opts.track_history = true;

        NewtonRaphsonSolver<> solver(opts);

        auto system = [](const Vector& x, Vector& f, SparseMatrix& J) {
            f.resize(1);
            f[0] = x[0] * x[0] - 4.0;

            J.resize(1, 1);
            std::vector<Eigen::Triplet<double>> triplets;
            triplets.emplace_back(0, 0, 2.0 * x[0]);
            J.setFromTriplets(triplets.begin(), triplets.end());
        };

        Vector x0(1);
        x0[0] = 3.0;

        auto result = solver.solve(x0, system);

        REQUIRE(result.success() == true);
        REQUIRE(result.history.size() > 0);
        REQUIRE(result.history.size() <= static_cast<std::size_t>(result.iterations + 1));

        // Residual should decrease
        if (result.history.size() >= 2) {
            REQUIRE(result.history.last().residual_norm <
                    result.history[0].residual_norm);
        }
    }
}

TEST_CASE("API Deterministic node order", "[api][solver][deterministic]") {
    using namespace pulsim::v1;

    SECTION("Natural ordering") {
        auto order = DeterministicNodeOrder::natural(5);

        REQUIRE(order.node_order.size() == 5);
        REQUIRE(order.inverse_order.size() == 5);

        for (Index i = 0; i < 5; ++i) {
            REQUIRE(order.node_order[i] == i);
            REQUIRE(order.inverse_order[i] == i);
        }
    }

    SECTION("Sort for determinism") {
        std::vector<std::pair<int, std::string>> items = {
            {3, "c"}, {1, "a"}, {2, "b"}
        };

        sort_for_determinism(items, [](const auto& p) { return p.first; });

        REQUIRE(items[0].first == 1);
        REQUIRE(items[1].first == 2);
        REQUIRE(items[2].first == 3);
    }
}

// =============================================================================
// Tests for Integration Methods (Phase 3.2-3.4)
// =============================================================================

TEST_CASE("API Trapezoidal coefficients", "[api][integration][trapezoidal]") {
    using namespace pulsim::v1;

    SECTION("Capacitor companion model") {
        Real C = 1e-6;   // 1 uF
        Real dt = 1e-6;  // 1 us
        Real v_prev = 5.0;
        Real i_prev = 0.001;  // 1 mA

        auto [g_eq, i_eq] = TrapezoidalCoeffs::capacitor(C, dt, v_prev, i_prev);

        // G_eq = 2C/dt = 2 * 1e-6 / 1e-6 = 2 S
        REQUIRE(g_eq == Catch::Approx(2.0).margin(1e-10));

        // I_eq = G_eq * v_prev + i_prev = 2 * 5 + 0.001 = 10.001
        REQUIRE(i_eq == Catch::Approx(10.001).margin(1e-10));
    }

    SECTION("Inductor companion model") {
        Real L = 1e-3;   // 1 mH
        Real dt = 1e-6;  // 1 us
        Real i_prev = 0.1;  // 100 mA
        Real v_prev = 10.0; // 10 V

        auto [g_eq, i_eq] = TrapezoidalCoeffs::inductor(L, dt, i_prev, v_prev);

        // G_eq = dt / (2L) = 1e-6 / (2 * 1e-3) = 0.0005 S
        REQUIRE(g_eq == Catch::Approx(0.0005).margin(1e-12));

        // V_eq = (2L/dt) * i_prev + v_prev = 2000 * 0.1 + 10 = 210 V
        // I_eq = G_eq * V_eq = 0.0005 * 210 = 0.105 A
        REQUIRE(i_eq == Catch::Approx(0.105).margin(1e-10));
    }

    SECTION("Current calculation") {
        Real C = 1e-6;
        Real dt = 1e-6;
        Real v_n = 6.0;
        Real v_prev = 5.0;
        Real i_prev = 0.0;

        Real i_n = TrapezoidalCoeffs::capacitor_current(C, dt, v_n, v_prev, i_prev);

        // i_n = (2C/dt)(v_n - v_prev) - i_prev = 2 * 1 - 0 = 2 A
        REQUIRE(i_n == Catch::Approx(2.0).margin(1e-10));
    }
}

TEST_CASE("API BDF coefficients", "[api][integration][bdf]") {
    using namespace pulsim::v1;

    SECTION("BDF1 (Backward Euler)") {
        auto bdf1 = BDFCoeffs::bdf1();

        REQUIRE(bdf1.order == 1);
        REQUIRE(bdf1.alpha[0] == Catch::Approx(1.0));
        REQUIRE(bdf1.alpha[1] == Catch::Approx(-1.0));
    }

    SECTION("BDF2") {
        auto bdf2 = BDFCoeffs::bdf2();

        REQUIRE(bdf2.order == 2);
        REQUIRE(bdf2.alpha[0] == Catch::Approx(1.5));
        REQUIRE(bdf2.alpha[1] == Catch::Approx(-2.0));
        REQUIRE(bdf2.alpha[2] == Catch::Approx(0.5));
    }

    SECTION("Method order lookup") {
        REQUIRE(method_order(Integrator::BDF1) == 1);
        REQUIRE(method_order(Integrator::BDF2) == 2);
        REQUIRE(method_order(Integrator::BDF3) == 3);
        REQUIRE(method_order(Integrator::Trapezoidal) == 2);
    }

    SECTION("Startup requirement") {
        REQUIRE(requires_startup(Integrator::BDF1) == false);
        REQUIRE(requires_startup(Integrator::Trapezoidal) == false);
        REQUIRE(requires_startup(Integrator::BDF2) == true);
        REQUIRE(requires_startup(Integrator::BDF3) == true);
    }
}

TEST_CASE("API State history", "[api][integration][history]") {
    using namespace pulsim::v1;

    SECTION("Basic operations") {
        StateHistory<6> hist;

        REQUIRE(hist.count() == 0);

        hist.push(1.0);
        hist.push(2.0);
        hist.push(3.0);

        REQUIRE(hist.count() == 3);
        REQUIRE(hist[0] == 3.0);  // Most recent
        REQUIRE(hist[1] == 2.0);
        REQUIRE(hist[2] == 1.0);  // Oldest
    }

    SECTION("Overflow handling") {
        StateHistory<3> hist;

        hist.push(1.0);
        hist.push(2.0);
        hist.push(3.0);
        hist.push(4.0);  // Should shift out 1.0

        REQUIRE(hist.count() == 3);
        REQUIRE(hist[0] == 4.0);
        REQUIRE(hist[1] == 3.0);
        REQUIRE(hist[2] == 2.0);
    }

    SECTION("Span access") {
        StateHistory<6> hist;
        hist.push(1.0);
        hist.push(2.0);

        auto span = hist.span();
        REQUIRE(span.size() == 2);
        REQUIRE(span[0] == 2.0);
        REQUIRE(span[1] == 1.0);
    }
}

TEST_CASE("API Numeric guards", "[api][integration][guards]") {
    using namespace pulsim::v1;

    SECTION("Voltage clamping") {
        REQUIRE(NumericGuard::clamp_voltage(1e12) == NumericGuard::max_voltage);
        REQUIRE(NumericGuard::clamp_voltage(-1e12) == -NumericGuard::max_voltage);
        REQUIRE(NumericGuard::clamp_voltage(5.0) == 5.0);
    }

    SECTION("Conductance clamping") {
        REQUIRE(NumericGuard::clamp_conductance(1e20) == NumericGuard::max_conductance);
        REQUIRE(NumericGuard::clamp_conductance(1e-20) == NumericGuard::min_conductance);
        REQUIRE(NumericGuard::clamp_conductance(0.001) == 0.001);
    }

    SECTION("Valid check") {
        REQUIRE(NumericGuard::is_valid(5.0) == true);
        REQUIRE(NumericGuard::is_valid(std::numeric_limits<Real>::infinity()) == false);
        REQUIRE(NumericGuard::is_valid(std::nan("")) == false);
    }

    SECTION("Safe divide") {
        REQUIRE(NumericGuard::safe_divide(10.0, 2.0) == Catch::Approx(5.0));
        REQUIRE(NumericGuard::safe_divide(10.0, 1e-50) > 1e20);  // Returns large value
    }
}

TEST_CASE("API Integration factory", "[api][integration][factory]") {
    using namespace pulsim::v1;

    SECTION("Trapezoidal capacitor") {
        Real C = 1e-6;
        Real dt = 1e-6;
        std::array<Real, 1> v_hist = {5.0};
        std::array<Real, 1> i_hist = {0.001};

        auto coeffs = IntegrationCoeffs::capacitor(
            Integrator::Trapezoidal, C, dt,
            std::span<const Real>(v_hist),
            std::span<const Real>(i_hist));

        REQUIRE(coeffs.g_eq == Catch::Approx(2.0).margin(1e-10));
    }

    SECTION("BDF1 capacitor") {
        Real C = 1e-6;
        Real dt = 1e-6;
        std::array<Real, 1> v_hist = {5.0};
        std::array<Real, 1> i_hist = {0.001};

        auto coeffs = IntegrationCoeffs::capacitor(
            Integrator::BDF1, C, dt,
            std::span<const Real>(v_hist),
            std::span<const Real>(i_hist));

        // BDF1: G_eq = alpha[0] * C / dt = 1.0 * 1e-6 / 1e-6 = 1 S
        REQUIRE(coeffs.g_eq == Catch::Approx(1.0).margin(1e-10));
    }
}

TEST_CASE("API LTE estimation", "[api][integration][lte]") {
    using namespace pulsim::v1;

    SECTION("Trapezoidal LTE") {
        Real y_trap = 10.0;
        Real y_be = 9.7;
        Real dt = 1e-6;

        Real lte = LTEEstimator::trapezoidal_lte(y_trap, y_be, dt);

        // LTE ~ |y_trap - y_be| / 3 = 0.3 / 3 = 0.1
        REQUIRE(lte == Catch::Approx(0.1).margin(1e-10));
    }

    SECTION("General LTE") {
        Real y_high = 10.0;
        Real y_low = 9.5;

        Real lte = LTEEstimator::general_lte(y_high, y_low, 1);

        // scale = 1/(2^1 - 1) = 1
        REQUIRE(lte == Catch::Approx(0.5).margin(1e-10));
    }
}

TEST_CASE("API Analytical RC validation", "[api][integration][analytical]") {
    using namespace pulsim::v1;

    SECTION("RC step response") {
        Real R = 1000.0;  // 1 kOhm
        Real C = 1e-6;    // 1 uF
        Real V_source = 10.0;
        Real tau = R * C;  // 1 ms

        // At t = 0
        Real v0 = analytical::rc_step_response(0.0, R, C, V_source, 0.0);
        REQUIRE(v0 == Catch::Approx(0.0).margin(1e-10));

        // At t = tau, v = V * (1 - 1/e) ≈ 6.321
        Real v_tau = analytical::rc_step_response(tau, R, C, V_source, 0.0);
        REQUIRE(v_tau == Catch::Approx(V_source * (1.0 - std::exp(-1.0))).margin(1e-6));

        // At t = 5*tau, v ≈ V (within 1%)
        Real v_5tau = analytical::rc_step_response(5 * tau, R, C, V_source, 0.0);
        REQUIRE(v_5tau == Catch::Approx(V_source).margin(0.1));
    }

    SECTION("RL step response") {
        Real R = 1000.0;
        Real L = 1.0;  // 1 H
        Real V_source = 10.0;
        Real tau = L / R;
        Real I_final = V_source / R;

        // At t = 0
        Real i0 = analytical::rl_step_response(0.0, R, L, V_source, 0.0);
        REQUIRE(i0 == Catch::Approx(0.0).margin(1e-10));

        // At t = tau
        Real i_tau = analytical::rl_step_response(tau, R, L, V_source, 0.0);
        REQUIRE(i_tau == Catch::Approx(I_final * (1.0 - std::exp(-1.0))).margin(1e-6));
    }
}

TEST_CASE("API Timestep config", "[api][integration][timestep]") {
    using namespace pulsim::v1;

    SECTION("Default config") {
        auto cfg = TimestepConfig::defaults();
        REQUIRE(cfg.dt_min == Catch::Approx(1e-15));
        REQUIRE(cfg.dt_max == Catch::Approx(1e-3));
        REQUIRE(cfg.safety_factor == Catch::Approx(0.9));
    }

    SECTION("Conservative config") {
        auto cfg = TimestepConfig::conservative();
        REQUIRE(cfg.safety_factor < 0.9);
        REQUIRE(cfg.growth_factor < 2.0);
    }

    SECTION("Aggressive config") {
        auto cfg = TimestepConfig::aggressive();
        REQUIRE(cfg.safety_factor > 0.9);
        REQUIRE(cfg.growth_factor > 2.0);
    }
}

TEST_CASE("API Timestep history", "[api][integration][timestep]") {
    using namespace pulsim::v1;

    SECTION("Basic operations") {
        TimestepHistory hist;

        REQUIRE(hist.count() == 0);

        hist.push(1e-6);
        hist.push(2e-6);
        hist.push(3e-6);

        REQUIRE(hist.count() == 3);
        REQUIRE(hist[0] == Catch::Approx(3e-6));  // Most recent
        REQUIRE(hist[2] == Catch::Approx(1e-6));  // Oldest
    }

    SECTION("Average calculation") {
        TimestepHistory hist;
        hist.push(1e-6);
        hist.push(2e-6);
        hist.push(3e-6);

        REQUIRE(hist.average() == Catch::Approx(2e-6));
    }

    SECTION("Oscillation detection") {
        TimestepHistory hist;

        // Non-oscillating: monotonic increase
        hist.push(1e-6);
        hist.push(2e-6);
        hist.push(3e-6);
        REQUIRE(hist.is_oscillating() == false);

        hist.clear();

        // Oscillating: up-down-up pattern
        hist.push(1e-6);
        hist.push(3e-6);
        hist.push(1e-6);
        hist.push(3e-6);
        hist.push(1e-6);
        REQUIRE(hist.is_oscillating() == true);
    }
}

TEST_CASE("API PI timestep controller", "[api][integration][timestep][pi]") {
    using namespace pulsim::v1;

    SECTION("Accept good step") {
        PITimestepController ctrl;

        // LTE is 10% of tolerance - should accept and grow
        Real lte = 0.1 * ctrl.config().error_tolerance;
        auto decision = ctrl.compute(lte);

        REQUIRE(decision.accepted == true);
        REQUIRE(decision.dt_new > ctrl.current_dt());  // Should grow
        REQUIRE(decision.rejections == 0);
    }

    SECTION("Reject bad step") {
        PITimestepController ctrl;
        Real initial_dt = ctrl.current_dt();

        // LTE is 200% of tolerance - should reject
        Real lte = 2.0 * ctrl.config().error_tolerance;
        auto decision = ctrl.compute(lte);

        REQUIRE(decision.accepted == false);
        REQUIRE(decision.dt_new < initial_dt);  // Should shrink from initial
        REQUIRE(decision.rejections == 1);
    }

    SECTION("Enforce dt limits") {
        TimestepConfig cfg;
        cfg.dt_min = 1e-9;
        cfg.dt_max = 1e-6;
        cfg.dt_initial = 1e-7;
        PITimestepController ctrl(cfg);

        // Very large error - should shrink to minimum
        Real lte = 1e6 * cfg.error_tolerance;
        auto decision = ctrl.compute(lte);

        // After several rejections, should hit minimum
        for (int i = 0; i < 20; ++i) {
            decision = ctrl.compute(lte);
        }

        REQUIRE(decision.at_minimum == true);
        REQUIRE(decision.dt_new == Catch::Approx(cfg.dt_min));
    }

    SECTION("Event adjustment") {
        PITimestepController ctrl;

        // Event close - should adjust to hit it
        Real dt = 1e-6;
        Real time_to_event = 1.2e-6;  // Within 1.5x

        Real adjusted = ctrl.adjust_for_event(time_to_event, dt);
        REQUIRE(adjusted == Catch::Approx(time_to_event));

        // Event far - no adjustment
        time_to_event = 10e-6;
        adjusted = ctrl.adjust_for_event(time_to_event, dt);
        REQUIRE(adjusted == Catch::Approx(dt));
    }

    SECTION("Reset") {
        PITimestepController ctrl;

        // Make some changes
        ctrl.accept(5e-7);
        ctrl.compute(ctrl.config().error_tolerance * 2);

        // Reset
        ctrl.reset();

        REQUIRE(ctrl.current_dt() == Catch::Approx(ctrl.config().dt_initial));
        REQUIRE(ctrl.rejections() == 0);
    }
}

TEST_CASE("API Basic timestep controller", "[api][integration][timestep][basic]") {
    using namespace pulsim::v1;

    SECTION("Accept and grow") {
        BasicTimestepController ctrl;

        Real lte = 0.1 * ctrl.current_dt();  // Small error
        auto decision = ctrl.compute(lte);

        REQUIRE(decision.accepted == true);
    }

    SECTION("Reject and shrink") {
        TimestepConfig cfg;
        cfg.error_tolerance = 1e-6;
        BasicTimestepController ctrl(cfg);

        Real lte = 1e-3;  // Large error
        auto decision = ctrl.compute(lte);

        REQUIRE(decision.accepted == false);
        REQUIRE(decision.dt_new < ctrl.current_dt());
    }
}

TEST_CASE("API Trapezoidal RC simulation", "[api][integration][simulation]") {
    using namespace pulsim::v1;

    // Simulate simple RC circuit: R=1k, C=1uF, V=10V
    // Using Trapezoidal method and compare with analytical solution
    // tau = RC = 1ms

    Real R = 1000.0;
    Real C = 1e-6;
    Real V_source = 10.0;
    Real tau = R * C;  // 1 ms
    Real dt = tau / 100;  // 10 us per step (100 steps per tau)
    int num_steps = 500;  // 5 tau total (essentially complete charging)

    Real v_cap = 0.0;
    Real i_cap = 0.0;
    Real t = 0.0;

    // Simulate using Trapezoidal companion model
    for (int step = 0; step < num_steps; ++step) {
        // Calculate companion model
        auto [g_eq, i_eq] = TrapezoidalCoeffs::capacitor(C, dt, v_cap, i_cap);

        // Solve: V_source = V_cap + R * I_cap
        // I_cap = G_eq * V_cap - I_eq (from companion)
        // Combined: V_source = V_cap + R * (G_eq * V_cap - I_eq)
        // V_cap * (1 + R * G_eq) = V_source + R * I_eq

        Real v_new = (V_source + R * i_eq) / (1.0 + R * g_eq);
        Real i_new = g_eq * v_new - i_eq;

        // Update for next step
        v_cap = v_new;
        i_cap = i_new;
        t += dt;
    }

    // Compare with analytical at t = 5*tau (should be ~99.3% charged)
    Real v_analytical = analytical::rc_step_response(t, R, C, V_source, 0.0);

    // Should be within 0.1% of analytical (task 3.2.6)
    // Trapezoidal with 100 steps/tau should be very accurate
    Real error_percent = std::abs(v_cap - v_analytical) / v_analytical * 100.0;
    REQUIRE(error_percent < 0.1);
}

// =============================================================================
// Static assertions for Phase 2.4 and 2.5 (compile-time verification)
// =============================================================================

namespace static_tests {
using namespace pulsim::v1;

// Expression templates compile-time tests
static_assert([]() constexpr {
    StaticVector<double, 4> a{1.0, 2.0, 3.0, 4.0};
    StaticVector<double, 4> b{5.0, 6.0, 7.0, 8.0};
    auto expr = a + b;
    return expr[0] == 6.0 && expr[3] == 12.0;
}());

static_assert([]() constexpr {
    StaticVector<double, 4> a{1.0, 2.0, 3.0, 4.0};
    auto expr = 2.0 * a;
    return expr[0] == 2.0 && expr[3] == 8.0;
}());

// Circuit graph compile-time tests
static_assert(count_max_nodes_v<Resistor, Capacitor> == 4);
static_assert(count_branches_v<VoltageSource, Inductor> == 2);
static_assert(is_linear_circuit<Resistor, Capacitor>());
static_assert(!is_linear_circuit<Resistor, IdealDiode>());
static_assert(has_reactive_elements<Capacitor>());
static_assert(!has_switching_elements<Resistor>());

// Validation static tests
static_assert(validate_circuit_topology<Resistor, VoltageSource>().is_valid);
static_assert(validate_circuit_topology<Resistor, Capacitor, VoltageSource>().has_voltage_reference);

} // namespace static_tests

// =============================================================================
// Phase 3 Remaining Tasks Tests (3.1.7, 3.3.4, 3.3.6, 3.4.6)
// =============================================================================

TEST_CASE("API BDF order controller", "[api][integration][bdf][order]") {
    using namespace pulsim::v1;

    SECTION("Initial order") {
        BDFOrderController ctrl;
        REQUIRE(ctrl.current_order() == 1);  // Default initial order
        REQUIRE(ctrl.steps_at_current_order() == 0);
    }

    SECTION("Order increase on low error (3.3.4)") {
        BDFOrderConfig cfg;
        cfg.initial_order = 2;
        cfg.steps_before_increase = 3;  // Need 3 steps before increasing
        cfg.order_increase_threshold = 0.5;
        BDFOrderController ctrl(cfg);

        Real tolerance = 1e-4;
        Real low_error = tolerance * 0.3;  // Below increase threshold

        // Step 1
        auto decision = ctrl.select_order(low_error, tolerance);
        REQUIRE(decision.new_order == 2);  // Not enough steps yet

        // Step 2
        decision = ctrl.select_order(low_error, tolerance);
        REQUIRE(decision.new_order == 2);  // Not enough steps yet

        // Step 3 - should increase (steps_at_order_ == 3 >= steps_before_increase)
        decision = ctrl.select_order(low_error, tolerance);
        REQUIRE(decision.order_increased == true);
        REQUIRE(decision.new_order == 3);
        REQUIRE(ctrl.current_order() == 3);
    }

    SECTION("Order decrease on high error (3.3.4)") {
        BDFOrderConfig cfg;
        cfg.initial_order = 3;
        cfg.order_decrease_threshold = 2.0;
        BDFOrderController ctrl(cfg);

        Real tolerance = 1e-4;
        Real high_error = tolerance * 3.0;  // Above decrease threshold

        auto decision = ctrl.select_order(high_error, tolerance);

        REQUIRE(decision.order_decreased == true);
        REQUIRE(decision.new_order == 2);
        REQUIRE(ctrl.current_order() == 2);
    }

    SECTION("Order reduction on convergence failure (3.3.6)") {
        BDFOrderConfig cfg;
        cfg.initial_order = 3;
        cfg.min_order = 1;
        BDFOrderController ctrl(cfg);

        // First failure
        auto decision = ctrl.reduce_on_failure();
        REQUIRE(decision.order_decreased == true);
        REQUIRE(decision.new_order == 2);
        REQUIRE(ctrl.convergence_failures() == 1);

        // Second failure
        decision = ctrl.reduce_on_failure();
        REQUIRE(decision.new_order == 1);
        REQUIRE(ctrl.convergence_failures() == 2);

        // At minimum - cannot reduce further
        decision = ctrl.reduce_on_failure();
        REQUIRE(decision.order_decreased == false);
        REQUIRE(decision.new_order == 1);
        REQUIRE(ctrl.convergence_failures() == 3);
    }

    SECTION("Abort after multiple failures at min order") {
        BDFOrderConfig cfg;
        cfg.initial_order = 1;
        cfg.min_order = 1;
        BDFOrderController ctrl(cfg);

        // Multiple failures at min order
        ctrl.reduce_on_failure();
        REQUIRE(!ctrl.should_abort());
        ctrl.reduce_on_failure();
        REQUIRE(!ctrl.should_abort());
        ctrl.reduce_on_failure();
        REQUIRE(!ctrl.should_abort());
        ctrl.reduce_on_failure();
        REQUIRE(ctrl.should_abort());  // > 3 failures
    }

    SECTION("Get BDF coefficients") {
        BDFOrderConfig cfg;
        cfg.initial_order = 2;
        BDFOrderController ctrl(cfg);

        auto coeffs = ctrl.current_coeffs();
        REQUIRE(coeffs.order == 2);
        REQUIRE(coeffs.alpha[0] == Catch::Approx(3.0/2.0));
    }

    SECTION("History requirement check") {
        BDFOrderConfig cfg;
        cfg.initial_order = 3;
        BDFOrderController ctrl(cfg);

        REQUIRE(ctrl.required_history() == 3);
        REQUIRE(ctrl.has_sufficient_history(3) == true);
        REQUIRE(ctrl.has_sufficient_history(2) == false);
    }
}

TEST_CASE("API LTE logger", "[api][integration][lte][logging]") {
    using namespace pulsim::v1;

    SECTION("Disabled by default") {
        LTELogger logger;
        REQUIRE(logger.is_enabled() == false);

        // Logging when disabled should be no-op
        logger.log(0.0, 1e-9, 1e-6, 1e-4, 2, true);
        REQUIRE(logger.buffer().empty());
        REQUIRE(logger.total_entries() == 0);
    }

    SECTION("Enable and log entries") {
        LTELogger logger;
        logger.set_enabled(true);

        logger.log(0.001, 1e-9, 1e-6, 1e-4, 2, true, 0, "capacitor");
        logger.log(0.002, 1e-9, 2e-5, 1e-4, 2, false, 1, "inductor");

        REQUIRE(logger.total_entries() == 2);
        REQUIRE(logger.rejected_steps() == 1);
        REQUIRE(logger.buffer().size() == 2);
    }

    SECTION("Statistics") {
        LTELogger logger;
        logger.set_enabled(true);

        logger.log(0.0, 1e-9, 1e-6, 1e-4, 2, true);
        logger.log(0.0, 1e-9, 3e-6, 1e-4, 2, true);
        logger.log(0.0, 1e-9, 5e-6, 1e-4, 2, false);

        REQUIRE(logger.max_lte() == Catch::Approx(5e-6));
        REQUIRE(logger.average_lte() == Catch::Approx(3e-6));
        REQUIRE(logger.rejection_rate() == Catch::Approx(1.0/3.0));
    }

    SECTION("CSV export") {
        LTELogger logger;
        logger.set_enabled(true);

        logger.log(0.001, 1e-9, 1e-6, 1e-4, 2, true);

        std::string csv = logger.to_csv();
        REQUIRE(csv.find("time,dt,lte") != std::string::npos);
        REQUIRE(csv.find("0.001") != std::string::npos);
    }

    SECTION("Callback") {
        LTELogger logger;
        logger.set_enabled(true);

        int callback_count = 0;
        logger.set_callback([&](const LTELogEntry&) {
            ++callback_count;
        });

        logger.log(0.0, 1e-9, 1e-6, 1e-4, 2, true);
        logger.log(0.0, 1e-9, 2e-6, 1e-4, 2, true);

        REQUIRE(callback_count == 2);
    }

    SECTION("Reset") {
        LTELogger logger;
        logger.set_enabled(true);

        logger.log(0.0, 1e-9, 1e-6, 1e-4, 2, true);
        logger.log(0.0, 1e-9, 2e-6, 1e-4, 2, false);

        logger.reset();

        REQUIRE(logger.buffer().empty());
        REQUIRE(logger.total_entries() == 0);
        REQUIRE(logger.rejected_steps() == 0);
        REQUIRE(logger.max_lte() == 0.0);
    }

    SECTION("Global logger") {
        auto& global = global_lte_logger();
        REQUIRE(global.is_enabled() == false);  // Disabled by default
    }
}

TEST_CASE("API Deterministic ordering", "[api][solver][deterministic]") {
    using namespace pulsim::v1;

    SECTION("DeviceOrderKey comparison") {
        DeviceOrderKey a{"resistor", "R1", 0};
        DeviceOrderKey b{"resistor", "R2", 0};
        DeviceOrderKey c{"capacitor", "C1", 0};

        REQUIRE(c < a);  // capacitor < resistor
        REQUIRE(a < b);  // R1 < R2
        REQUIRE(a == a);
    }

    SECTION("Natural node ordering") {
        auto order = DeterministicNodeOrder::natural(5);

        REQUIRE(order.node_order.size() == 5);
        REQUIRE(order.inverse_order.size() == 5);

        for (Index i = 0; i < 5; ++i) {
            REQUIRE(order.node_order[i] == i);
            REQUIRE(order.inverse_order[i] == i);
        }
    }

    SECTION("Vector permutation") {
        DeterministicNodeOrder order;
        order.node_order = {2, 0, 1};
        order.inverse_order = {1, 2, 0};

        Vector v(3);
        v << 10.0, 20.0, 30.0;

        Vector permuted = order.permute(v);
        REQUIRE(permuted[0] == 30.0);  // node_order[0] = 2
        REQUIRE(permuted[1] == 10.0);  // node_order[1] = 0
        REQUIRE(permuted[2] == 20.0);  // node_order[2] = 1

        Vector unpermuted = order.unpermute(permuted);
        REQUIRE(unpermuted[0] == Catch::Approx(v[0]));
        REQUIRE(unpermuted[1] == Catch::Approx(v[1]));
        REQUIRE(unpermuted[2] == Catch::Approx(v[2]));
    }

    SECTION("DeterministicAssemblyOrder") {
        DeterministicAssemblyOrder assembly;

        assembly.register_device({"resistor", "R2", 0});
        assembly.register_device({"capacitor", "C1", 0});
        assembly.register_device({"resistor", "R1", 0});

        REQUIRE(assembly.size() == 3);
        REQUIRE(!assembly.is_sorted());

        assembly.sort();

        REQUIRE(assembly.is_sorted());

        const auto& devices = assembly.devices();
        REQUIRE(devices[0].type_name == "capacitor");
        REQUIRE(devices[1].name == "R1");
        REQUIRE(devices[2].name == "R2");
    }

    SECTION("DeterministicTriplet sorting") {
        std::vector<DeterministicTriplet> triplets = {
            {1, 2, 5.0},
            {0, 0, 1.0},
            {1, 0, 3.0},
            {0, 1, 2.0},
            {0, 0, 0.5}  // Duplicate position
        };

        auto matrix = build_matrix_deterministic(2, 3, triplets);

        REQUIRE(matrix.rows() == 2);
        REQUIRE(matrix.cols() == 3);
        REQUIRE(matrix.coeff(0, 0) == Catch::Approx(1.5));  // 1.0 + 0.5 combined
        REQUIRE(matrix.coeff(0, 1) == Catch::Approx(2.0));
        REQUIRE(matrix.coeff(1, 0) == Catch::Approx(3.0));
        REQUIRE(matrix.coeff(1, 2) == Catch::Approx(5.0));
    }

    SECTION("RCM ordering") {
        // Create a simple sparse matrix
        SparseMatrix A(4, 4);
        std::vector<Eigen::Triplet<Real>> triplets = {
            {0, 0, 1.0}, {0, 1, 1.0},
            {1, 0, 1.0}, {1, 1, 1.0}, {1, 2, 1.0},
            {2, 1, 1.0}, {2, 2, 1.0}, {2, 3, 1.0},
            {3, 2, 1.0}, {3, 3, 1.0}
        };
        A.setFromTriplets(triplets.begin(), triplets.end());

        auto order = DeterministicNodeOrder::rcm(A);

        REQUIRE(order.node_order.size() == 4);
        REQUIRE(order.inverse_order.size() == 4);

        // Verify it's a valid permutation
        std::vector<bool> seen(4, false);
        for (Index i = 0; i < 4; ++i) {
            REQUIRE(order.node_order[i] < 4);
            seen[order.node_order[i]] = true;
        }
        for (bool s : seen) {
            REQUIRE(s == true);
        }
    }
}

// =============================================================================
// Phase 4: High-Performance Components Tests
// =============================================================================

TEST_CASE("API Enhanced SparseLU policy", "[api][solver][linear][phase4]") {
    using namespace pulsim::v1;

    SECTION("Basic solve") {
        EnhancedSparseLUPolicy solver;

        // Create simple 3x3 SPD matrix
        SparseMatrix A(3, 3);
        std::vector<Eigen::Triplet<Real>> triplets = {
            {0, 0, 4.0}, {0, 1, 1.0},
            {1, 0, 1.0}, {1, 1, 3.0}, {1, 2, 1.0},
            {2, 1, 1.0}, {2, 2, 2.0}
        };
        A.setFromTriplets(triplets.begin(), triplets.end());

        Vector b(3);
        b << 1.0, 2.0, 3.0;

        REQUIRE(solver.analyze(A) == true);
        REQUIRE(solver.factorize(A) == true);

        auto result = solver.solve(b);
        REQUIRE(result.has_value());

        // Verify Ax = b
        Vector residual = A * (*result) - b;
        REQUIRE(residual.norm() < 1e-10);
    }

    SECTION("Pattern reuse detection (4.1.4, 4.1.5)") {
        LinearSolverConfig cfg;
        cfg.reuse_symbolic = true;
        cfg.detect_pattern_change = true;
        EnhancedSparseLUPolicy solver(cfg);

        SparseMatrix A(2, 2);
        std::vector<Eigen::Triplet<Real>> triplets = {{0, 0, 2.0}, {1, 1, 3.0}};
        A.setFromTriplets(triplets.begin(), triplets.end());

        // First factorization
        solver.factorize(A);
        REQUIRE(solver.factorize_count() == 1);

        // Same pattern, different values - should reuse symbolic
        A.coeffRef(0, 0) = 5.0;
        solver.factorize(A);
        REQUIRE(solver.factorize_count() == 2);
        REQUIRE(solver.is_analyzed() == true);
    }

    SECTION("Reset") {
        EnhancedSparseLUPolicy solver;

        SparseMatrix A(2, 2);
        A.setIdentity();

        solver.factorize(A);
        REQUIRE(solver.is_analyzed() == true);

        solver.reset();
        REQUIRE(solver.is_analyzed() == false);
        REQUIRE(solver.factorize_count() == 0);
    }
}

TEST_CASE("API KLU policy stub", "[api][solver][linear][phase4]") {
    using namespace pulsim::v1;

    SECTION("Fallback to SparseLU") {
        KLUPolicy solver;

        // KLU should fall back to SparseLU when not available
        SparseMatrix A(2, 2);
        A.setIdentity();

        Vector b(2);
        b << 1.0, 2.0;

        REQUIRE(solver.analyze(A) == true);
        REQUIRE(solver.factorize(A) == true);

        auto result = solver.solve(b);
        REQUIRE(result.has_value());
        REQUIRE((*result)[0] == Catch::Approx(1.0));
        REQUIRE((*result)[1] == Catch::Approx(2.0));
    }

    SECTION("Availability check") {
        // On most systems without SuiteSparse, this will be false
        bool available = KLUPolicy::is_available();
        (void)available;  // Just check it compiles
    }
}

TEST_CASE("API Armijo line search", "[api][solver][linesearch][phase4]") {
    using namespace pulsim::v1;

    SECTION("Descent direction") {
        ArmijoLineSearch ls;

        Vector x(2);
        x << 1.0, 1.0;

        Vector dx(2);
        dx << -1.0, -1.0;  // Descent direction

        Vector grad(2);
        grad << 2.0, 2.0;  // Gradient pointing up

        // Quadratic function f(x) = x^2 + y^2
        auto f = [](const Vector& v) { return v.squaredNorm(); };

        Real f_x = f(x);
        Real alpha = ls.search(x, dx, f_x, grad, f);

        REQUIRE(alpha > 0.0);
        REQUIRE(alpha <= 1.0);

        // Verify decrease
        Vector x_new = x + alpha * dx;
        REQUIRE(f(x_new) < f_x);
    }

    SECTION("Residual-based search") {
        ArmijoLineSearch ls;

        Vector x(2);
        x << 2.0, 2.0;

        Vector dx(2);
        dx << -1.0, -1.0;

        auto residual = [](const Vector& v) { return v.norm(); };

        Real r0 = residual(x);
        Real alpha = ls.search_residual(x, dx, r0, residual);

        REQUIRE(alpha > 0.0);

        Vector x_new = x + alpha * dx;
        REQUIRE(residual(x_new) < r0);
    }
}

TEST_CASE("API Trust region method", "[api][solver][trustregion][phase4]") {
    using namespace pulsim::v1;

    SECTION("Step within radius") {
        TrustRegionMethod tr;
        tr.set_radius(10.0);

        Vector grad(2);
        grad << 1.0, 1.0;

        Vector newton_step(2);
        newton_step << -2.0, -2.0;  // Norm ~= 2.83 < 10

        Vector step = tr.compute_step(grad, newton_step);

        // Should use full Newton step
        REQUIRE(step[0] == Catch::Approx(newton_step[0]));
        REQUIRE(step[1] == Catch::Approx(newton_step[1]));
    }

    SECTION("Step outside radius") {
        TrustRegionMethod tr;
        tr.set_radius(1.0);

        Vector grad(2);
        grad << 1.0, 1.0;

        Vector newton_step(2);
        newton_step << -10.0, -10.0;  // Norm ~= 14.14 > 1

        Vector step = tr.compute_step(grad, newton_step);

        // Step should be constrained to radius
        REQUIRE(step.norm() <= 1.0 + 1e-10);
    }

    SECTION("Update radius") {
        TrustRegionConfig cfg;
        cfg.initial_radius = 1.0;
        cfg.eta1 = 0.25;
        cfg.eta2 = 0.75;
        TrustRegionMethod tr(cfg);

        Vector step(2);
        step << 0.5, 0.5;

        // Good step: actual reduction close to predicted
        auto result = tr.update(10.0, 5.0, 5.0, step);  // ratio = 1.0
        REQUIRE(result.accepted == true);
        REQUIRE(result.ratio == Catch::Approx(1.0));

        // Poor step: actual reduction much less than predicted
        result = tr.update(10.0, 9.5, 5.0, step);  // ratio = 0.1
        REQUIRE(result.accepted == false);
        REQUIRE(tr.radius() < cfg.initial_radius);  // Radius shrunk
    }
}

TEST_CASE("API Arena allocator", "[api][memory][arena][phase4]") {
    using namespace pulsim::v1;

    SECTION("Basic allocation") {
        ArenaAllocator arena(1024);

        void* ptr1 = arena.allocate(100);
        REQUIRE(ptr1 != nullptr);

        void* ptr2 = arena.allocate(200);
        REQUIRE(ptr2 != nullptr);
        REQUIRE(ptr2 != ptr1);

        REQUIRE(arena.total_allocated() >= 300);
    }

    SECTION("Aligned allocation") {
        ArenaAllocator arena(4096);

        void* ptr = arena.allocate(64, 64);  // 64-byte alignment
        REQUIRE(reinterpret_cast<std::uintptr_t>(ptr) % 64 == 0);
    }

    SECTION("Array allocation") {
        ArenaAllocator arena(4096);

        Real* arr = arena.allocate_array<Real>(100);
        REQUIRE(arr != nullptr);

        // Write to array
        for (int i = 0; i < 100; ++i) {
            arr[i] = static_cast<Real>(i);
        }

        REQUIRE(arr[50] == Catch::Approx(50.0));
    }

    SECTION("Create object") {
        ArenaAllocator arena(1024);

        struct TestStruct {
            int a;
            double b;
            TestStruct(int x, double y) : a(x), b(y) {}
        };

        TestStruct* obj = arena.create<TestStruct>(42, 3.14);
        REQUIRE(obj->a == 42);
        REQUIRE(obj->b == Catch::Approx(3.14));
    }

    SECTION("Reset") {
        ArenaAllocator arena(1024);

        arena.allocate(500);
        REQUIRE(arena.total_allocated() == 500);

        arena.reset();
        REQUIRE(arena.total_allocated() == 0);

        // Can allocate again
        void* ptr = arena.allocate(100);
        REQUIRE(ptr != nullptr);
    }

    SECTION("Multiple blocks") {
        ArenaAllocator arena(100);  // Small initial size

        // Allocate more than one block
        arena.allocate(50);
        arena.allocate(50);
        arena.allocate(50);  // Should trigger new block

        REQUIRE(arena.block_count() >= 2);
    }
}

TEST_CASE("API Simulation memory pool", "[api][memory][pool][phase4]") {
    using namespace pulsim::v1;

    SECTION("Workspace vector reuse") {
        SimulationMemoryPool pool;

        Vector& v1 = pool.get_workspace_vector(100, 0);
        v1.setZero();

        Vector& v2 = pool.get_workspace_vector(100, 0);

        // Should be same vector
        REQUIRE(&v1 == &v2);

        // Different size = different vector
        Vector& v3 = pool.get_workspace_vector(200, 0);
        REQUIRE(&v1 != &v3);
    }

    SECTION("Workspace matrix reuse") {
        SimulationMemoryPool pool;

        SparseMatrix& m1 = pool.get_workspace_matrix(10, 10, 0);
        SparseMatrix& m2 = pool.get_workspace_matrix(10, 10, 0);

        REQUIRE(&m1 == &m2);
    }

    SECTION("Statistics") {
        SimulationMemoryPool pool;

        pool.get_workspace_vector(100);
        pool.get_workspace_vector(200);
        pool.get_workspace_matrix(10, 10);

        REQUIRE(pool.vector_count() == 2);
        REQUIRE(pool.matrix_count() == 1);
    }
}

TEST_CASE("API SIMD detection", "[api][simd][phase4]") {
    using namespace pulsim::v1;

    SECTION("Compile-time detection") {
        SIMDLevel level = detect_simd_level();

        // Should be at least None
        REQUIRE(level >= SIMDLevel::None);

        // Name should be valid
        const char* name = simd_level_name(level);
        REQUIRE(name != nullptr);
        REQUIRE(std::strlen(name) > 0);
    }

    SECTION("Vector width") {
        std::size_t width = simd_vector_width();
        REQUIRE(width >= 1);
        REQUIRE(width <= 8);  // Max for AVX-512
    }

    SECTION("Compile-time constants") {
        REQUIRE(current_simd_level >= SIMDLevel::None);
        REQUIRE(simd_width >= 1);
    }
}

TEST_CASE("API Aligned array", "[api][memory][soa][phase4]") {
    using namespace pulsim::v1;

    SECTION("Basic operations") {
        AlignedArray<Real> arr(100);

        REQUIRE(arr.size() == 100);
        REQUIRE(arr.data() != nullptr);

        arr[0] = 1.0;
        arr[99] = 99.0;

        REQUIRE(arr[0] == Catch::Approx(1.0));
        REQUIRE(arr[99] == Catch::Approx(99.0));
    }

    SECTION("Cache line alignment") {
        AlignedArray<Real, 64> arr(100);

        // Data should be 64-byte aligned
        REQUIRE(reinterpret_cast<std::uintptr_t>(arr.data()) % 64 == 0);
    }

    SECTION("Resize") {
        AlignedArray<Real> arr(10);
        arr[5] = 5.0;

        arr.resize(100);

        REQUIRE(arr.size() == 100);
        REQUIRE(arr[5] == Catch::Approx(5.0));  // Original data preserved
    }

    SECTION("Move semantics") {
        AlignedArray<Real> arr1(100);
        arr1[0] = 42.0;

        AlignedArray<Real> arr2 = std::move(arr1);

        REQUIRE(arr2.size() == 100);
        REQUIRE(arr2[0] == Catch::Approx(42.0));
        REQUIRE(arr1.size() == 0);
        REQUIRE(arr1.data() == nullptr);
    }

    SECTION("Iterator") {
        AlignedArray<Real> arr(5);
        for (std::size_t i = 0; i < 5; ++i) {
            arr[i] = static_cast<Real>(i);
        }

        Real sum = 0.0;
        for (Real v : arr) {
            sum += v;
        }

        REQUIRE(sum == Catch::Approx(10.0));  // 0+1+2+3+4
    }
}

TEST_CASE("API SoA device layouts", "[api][memory][soa][phase4]") {
    using namespace pulsim::v1;

    SECTION("Resistor SoA") {
        ResistorSoA resistors;
        resistors.resize(10);

        REQUIRE(resistors.size() == 10);

        resistors.resistance[0] = 1000.0;
        resistors.node_pos[0] = 1;
        resistors.node_neg[0] = 0;

        REQUIRE(resistors.resistance[0] == Catch::Approx(1000.0));
        REQUIRE(resistors.node_pos[0] == 1);
        REQUIRE(resistors.node_neg[0] == 0);
    }

    SECTION("Capacitor SoA") {
        CapacitorSoA capacitors;
        capacitors.resize(5);

        REQUIRE(capacitors.size() == 5);

        capacitors.capacitance[0] = 1e-6;
        capacitors.voltage[0] = 5.0;
        capacitors.voltage_prev[0] = 4.5;

        REQUIRE(capacitors.capacitance[0] == Catch::Approx(1e-6));
    }

    SECTION("Inductor SoA") {
        InductorSoA inductors;
        inductors.resize(3);

        REQUIRE(inductors.size() == 3);

        inductors.inductance[0] = 1e-3;
        inductors.current[0] = 0.5;
        inductors.branch_index[0] = 0;

        REQUIRE(inductors.inductance[0] == Catch::Approx(1e-3));
    }
}

TEST_CASE("API Memory tracker", "[api][memory][tracking][phase4]") {
    using namespace pulsim::v1;

    SECTION("Track allocations") {
        auto& tracker = MemoryTracker::instance();
        tracker.reset();

        tracker.record_allocation(1000);
        tracker.record_allocation(500);

        auto stats = tracker.stats();
        REQUIRE(stats.current_allocated == 1500);
        REQUIRE(stats.peak_allocated == 1500);
        REQUIRE(stats.allocation_count == 2);
    }

    SECTION("Track deallocations") {
        auto& tracker = MemoryTracker::instance();
        tracker.reset();

        tracker.record_allocation(1000);
        tracker.record_deallocation(400);

        auto stats = tracker.stats();
        REQUIRE(stats.current_allocated == 600);
        REQUIRE(stats.peak_allocated == 1000);
        REQUIRE(stats.deallocation_count == 1);
    }
}

// =============================================================================
// Phase 5: Advanced Convergence Aids Tests
// =============================================================================

TEST_CASE("API Gmin stepping config", "[api][convergence][gmin][phase5]") {
    using namespace pulsim::v1;

    SECTION("Default config") {
        GminConfig config;
        REQUIRE(config.initial_gmin == 1e-2);
        REQUIRE(config.final_gmin == 1e-12);
        REQUIRE(config.reduction_factor == 10.0);
    }

    SECTION("Required steps calculation") {
        GminConfig config;
        config.initial_gmin = 1e-2;
        config.final_gmin = 1e-12;
        config.reduction_factor = 10.0;

        // log10(1e-2 / 1e-12) = 10 steps
        REQUIRE(config.required_steps() == 10);
    }
}

TEST_CASE("API Gmin stepping execution", "[api][convergence][gmin][phase5]") {
    using namespace pulsim::v1;

    SECTION("Gmin stepping with simple circuit") {
        GminConfig config;
        config.initial_gmin = 1e-3;
        config.final_gmin = 1e-9;
        config.reduction_factor = 10.0;
        config.enable_logging = true;

        GminStepping gmin(config);

        // Simple system: R = 1k to ground, V = 5V source
        // G * V = I  =>  (1/R + Gmin) * V = V/R
        auto solve_func = [&](const Vector& x0) -> NewtonResult {
            NewtonResult result;
            result.solution = x0;
            Real R = 1000.0;
            Real Vsource = 5.0;

            // Simple iteration: V = Vsource * (1/R) / (1/R + Gmin)
            Real G = 1.0 / R;
            Real total_G = G + gmin.current_gmin();
            result.solution[0] = Vsource * G / total_G;

            result.status = SolverStatus::Success;
            result.iterations = 1;
            result.final_residual = 1e-12;
            return result;
        };

        Vector x0 = Vector::Zero(1);
        NewtonResult result = gmin.execute(x0, 1, solve_func);

        REQUIRE(result.success());
        REQUIRE(gmin.log().size() > 0);
    }

    SECTION("Gmin log export") {
        GminConfig config;
        config.enable_logging = true;
        GminStepping gmin(config);

        // Add some log entries manually for testing
        gmin.reset();

        std::string csv = gmin.log_to_csv();
        REQUIRE(csv.find("step,gmin") != std::string::npos);
    }
}

TEST_CASE("API Source stepping config", "[api][convergence][source][phase5]") {
    using namespace pulsim::v1;

    SECTION("Default config") {
        SourceSteppingConfig config;
        REQUIRE(config.initial_scale == 0.0);
        REQUIRE(config.final_scale == 1.0);
        REQUIRE(config.initial_step == 0.25);
    }
}

TEST_CASE("API Source stepping execution", "[api][convergence][source][phase5]") {
    using namespace pulsim::v1;

    SECTION("Simple source stepping") {
        SourceSteppingConfig config;
        config.initial_scale = 0.0;
        config.final_scale = 1.0;
        config.initial_step = 0.5;
        config.enable_logging = true;

        SourceStepping source(config);

        // Simple linear system that always converges
        auto solve_func = [](const Vector& x0, Real scale) -> NewtonResult {
            NewtonResult result;
            result.solution = x0;
            result.solution[0] = scale * 5.0;  // V = scale * Vsource
            result.status = SolverStatus::Success;
            result.iterations = 2;
            result.final_residual = 1e-10;
            return result;
        };

        Vector x0 = Vector::Zero(1);
        SourceSteppingResult result = source.execute(x0, solve_func);

        REQUIRE(result.success);
        REQUIRE(result.final_result.solution[0] == Catch::Approx(5.0));
    }

    SECTION("Adaptive step reduction") {
        SourceSteppingConfig config;
        config.initial_scale = 0.0;
        config.final_scale = 1.0;
        config.initial_step = 0.5;
        config.max_failures = 10;

        SourceStepping source(config);

        int call_count = 0;
        auto solve_func = [&call_count](const Vector& x0, Real scale) -> NewtonResult {
            NewtonResult result;
            result.solution = x0;
            result.solution[0] = scale * 5.0;
            ++call_count;

            // Fail on large steps initially
            if (scale > 0.3 && call_count < 5) {
                result.status = SolverStatus::MaxIterationsReached;
            } else {
                result.status = SolverStatus::Success;
            }
            result.iterations = 3;
            result.final_residual = result.success() ? 1e-10 : 1.0;
            return result;
        };

        Vector x0 = Vector::Zero(1);
        SourceSteppingResult result = source.execute(x0, solve_func);

        REQUIRE(result.success);
        REQUIRE(result.total_steps > 2);  // Had to take more steps due to failures
    }
}

TEST_CASE("API Pseudo-transient config", "[api][convergence][ptc][phase5]") {
    using namespace pulsim::v1;

    SECTION("Default config") {
        PseudoTransientConfig config;
        REQUIRE(config.initial_dt == 1e-9);
        REQUIRE(config.max_dt == 1e3);
        REQUIRE(config.min_dt == 1e-15);
    }
}

TEST_CASE("API Pseudo-transient execution", "[api][convergence][ptc][phase5]") {
    using namespace pulsim::v1;

    SECTION("Simple pseudo-transient") {
        PseudoTransientConfig config;
        config.initial_dt = 1e-6;
        config.convergence_threshold = 1e-8;
        config.enable_logging = true;
        config.max_iterations = 50;

        PseudoTransientContinuation ptc(config);

        int iter = 0;
        auto solve_func = [&iter](const Vector& x0, Real pseudo_dt) -> NewtonResult {
            NewtonResult result;
            result.solution = x0;

            // Simulate converging to steady state
            Real target = 5.0;
            Real rate = 0.3;
            result.solution[0] = target - (target - x0[0]) * std::exp(-rate * (iter + 1));
            ++iter;

            result.status = SolverStatus::Success;
            result.iterations = 2;
            result.final_residual = std::abs(target - result.solution[0]) * 1e-6;
            return result;
        };

        Vector x0 = Vector::Zero(1);
        PseudoTransientResult result = ptc.execute(x0, 1, solve_func);

        REQUIRE(result.success);
        REQUIRE(result.solution[0] == Catch::Approx(5.0).margin(0.1));
    }
}

TEST_CASE("API Robust initialization config", "[api][convergence][init][phase5]") {
    using namespace pulsim::v1;

    SECTION("Default config") {
        InitializationConfig config;
        REQUIRE(config.default_voltage == 0.0);
        REQUIRE(config.supply_voltage == 12.0);
        REQUIRE(config.diode_forward == 0.7);
    }
}

TEST_CASE("API Robust initialization", "[api][convergence][init][phase5]") {
    using namespace pulsim::v1;

    SECTION("Generate initial guess") {
        InitializationConfig config;
        config.default_voltage = 0.0;
        config.use_zero_init = false;

        RobustInitialization init(config);

        Vector x0 = init.generate_initial_guess(3, 1);

        REQUIRE(x0.size() == 4);
        REQUIRE(x0[0] == 0.0);  // Default voltage
        REQUIRE(x0[3] == 0.0);  // Branch current
    }

    SECTION("Device hints") {
        InitializationConfig config;
        config.supply_voltage = 12.0;
        config.diode_forward = 0.7;

        RobustInitialization init(config);

        // Add hints
        init.add_hint({0, DeviceHint::SupplyPositive, 0.0, false});
        init.add_hint({1, DeviceHint::DiodeAnode, 0.0, false});
        init.add_hint({2, DeviceHint::Ground, 0.0, false});

        Vector x0 = init.generate_initial_guess(3, 0);

        REQUIRE(x0[0] == Catch::Approx(12.0));  // Supply
        REQUIRE(x0[1] == Catch::Approx(0.7));   // Diode forward
        REQUIRE(x0[2] == Catch::Approx(0.0));   // Ground
    }

    SECTION("Warm start") {
        InitializationConfig config;
        config.use_warm_start = true;

        RobustInitialization init(config);

        Vector prev(3);
        prev << 1.0, 2.0, 3.0;

        Vector x0 = init.warm_start(prev, 3, 0);

        REQUIRE(x0[0] == Catch::Approx(1.0));
        REQUIRE(x0[1] == Catch::Approx(2.0));
        REQUIRE(x0[2] == Catch::Approx(3.0));
    }

    SECTION("Random initialization determinism") {
        InitializationConfig config;
        config.random_seed = 12345;
        config.random_voltage_range = 10.0;

        RobustInitialization init1(config);
        RobustInitialization init2(config);

        Vector x1 = init1.random_initial_guess(3, 0);
        Vector x2 = init2.random_initial_guess(3, 0);

        // Same seed should produce same results
        REQUIRE(x1[0] == Catch::Approx(x2[0]));
        REQUIRE(x1[1] == Catch::Approx(x2[1]));
        REQUIRE(x1[2] == Catch::Approx(x2[2]));
    }
}

TEST_CASE("API DC convergence solver", "[api][convergence][dc][phase5]") {
    using namespace pulsim::v1;

    SECTION("Direct solve success") {
        DCConvergenceConfig config;
        config.strategy = DCStrategy::Direct;

        DCConvergenceSolver<SparseLUPolicy> solver(config);

        // Simple 2-node resistor network
        // Node 0: Vsource = 5V
        // Node 1: R1 to node 0, R2 to ground
        auto system_func = [](const Vector& x, Vector& f, SparseMatrix& J) {
            Real R1 = 1000.0, R2 = 2000.0;
            Real Vsource = 5.0;
            Real G1 = 1.0 / R1, G2 = 1.0 / R2;

            // Node 0: voltage source (fixed)
            f[0] = x[0] - Vsource;

            // Node 1: KCL
            f[1] = G1 * (x[1] - x[0]) + G2 * x[1];

            // Jacobian
            J.resize(2, 2);
            J.setZero();
            J.coeffRef(0, 0) = 1.0;
            J.coeffRef(1, 0) = -G1;
            J.coeffRef(1, 1) = G1 + G2;
            J.makeCompressed();
        };

        Vector x0 = Vector::Zero(2);
        auto result = solver.solve(x0, 2, 0, system_func);

        REQUIRE(result.success);
        REQUIRE(result.newton_result.solution[0] == Catch::Approx(5.0));
        // V1 = Vsource * R2 / (R1 + R2) = 5 * 2000 / 3000 = 3.33V
        REQUIRE(result.newton_result.solution[1] == Catch::Approx(10.0/3.0).margin(0.01));
    }

    SECTION("Auto strategy selection") {
        DCConvergenceConfig config;
        config.strategy = DCStrategy::Auto;

        DCConvergenceSolver<SparseLUPolicy> solver(config);

        // Same simple system
        auto system_func = [](const Vector& x, Vector& f, SparseMatrix& J) {
            Real R = 1000.0;
            Real Vsource = 5.0;
            Real G = 1.0 / R;

            f[0] = x[0] - Vsource;
            f[1] = G * (x[1] - x[0]) + G * x[1];

            J.resize(2, 2);
            J.setZero();
            J.coeffRef(0, 0) = 1.0;
            J.coeffRef(1, 0) = -G;
            J.coeffRef(1, 1) = 2.0 * G;
            J.makeCompressed();
        };

        Vector x0 = Vector::Zero(2);
        auto result = solver.solve(x0, 2, 0, system_func);

        REQUIRE(result.success);
        REQUIRE(result.strategy_used == DCStrategy::Direct);  // Should succeed on first try
    }
}

TEST_CASE("API Gmin log entry", "[api][convergence][gmin][phase5]") {
    using namespace pulsim::v1;

    SECTION("Log entry fields") {
        GminLogEntry entry;
        entry.step = 5;
        entry.gmin = 1e-6;
        entry.converged = true;
        entry.newton_iterations = 3;
        entry.final_residual = 1e-10;

        REQUIRE(entry.step == 5);
        REQUIRE(entry.gmin == 1e-6);
        REQUIRE(entry.converged == true);
    }
}

TEST_CASE("API Source step log entry", "[api][convergence][source][phase5]") {
    using namespace pulsim::v1;

    SECTION("Log entry fields") {
        SourceStepLogEntry entry;
        entry.step = 3;
        entry.scale = 0.75;
        entry.step_size = 0.25;
        entry.converged = true;
        entry.newton_iterations = 4;
        entry.final_residual = 1e-9;

        REQUIRE(entry.step == 3);
        REQUIRE(entry.scale == 0.75);
        REQUIRE(entry.step_size == 0.25);
    }
}

TEST_CASE("API Pseudo-transient log entry", "[api][convergence][ptc][phase5]") {
    using namespace pulsim::v1;

    SECTION("Log entry fields") {
        PseudoTransientLogEntry entry;
        entry.iteration = 10;
        entry.pseudo_dt = 1e-3;
        entry.residual_norm = 1e-8;
        entry.newton_converged = true;
        entry.newton_iterations = 2;

        REQUIRE(entry.iteration == 10);
        REQUIRE(entry.pseudo_dt == 1e-3);
        REQUIRE(entry.newton_converged == true);
    }
}

TEST_CASE("API Node init hint", "[api][convergence][init][phase5]") {
    using namespace pulsim::v1;

    SECTION("Hint with explicit voltage") {
        NodeInitHint hint;
        hint.node_index = 5;
        hint.hint = DeviceHint::None;
        hint.hint_voltage = 3.3;
        hint.has_explicit_hint = true;

        REQUIRE(hint.node_index == 5);
        REQUIRE(hint.hint_voltage == 3.3);
        REQUIRE(hint.has_explicit_hint == true);
    }

    SECTION("All device hints") {
        InitializationConfig config;
        config.supply_voltage = 12.0;
        config.diode_forward = 0.7;
        config.mosfet_threshold = 2.0;

        RobustInitialization init(config);

        REQUIRE(init.voltage_from_hint(DeviceHint::Ground) == 0.0);
        REQUIRE(init.voltage_from_hint(DeviceHint::SupplyPositive) == 12.0);
        REQUIRE(init.voltage_from_hint(DeviceHint::SupplyNegative) == -12.0);
        REQUIRE(init.voltage_from_hint(DeviceHint::DiodeAnode) == 0.7);
        REQUIRE(init.voltage_from_hint(DeviceHint::DiodeCathode) == 0.0);
        REQUIRE(init.voltage_from_hint(DeviceHint::MOSFETGate) == 3.0);  // 2.0 * 1.5
        REQUIRE(init.voltage_from_hint(DeviceHint::MOSFETDrain) == 6.0);  // 12 * 0.5
        REQUIRE(init.voltage_from_hint(DeviceHint::MOSFETSource) == 0.0);
        REQUIRE(init.voltage_from_hint(DeviceHint::BJTBase) == 0.7);
        REQUIRE(init.voltage_from_hint(DeviceHint::BJTCollector) == 6.0);
        REQUIRE(init.voltage_from_hint(DeviceHint::BJTEmitter) == 0.0);
    }
}

// =============================================================================
// Phase 6: Validation & Benchmarking Tests
// =============================================================================

TEST_CASE("API RC analytical solution", "[api][validation][rc][phase6]") {
    using namespace pulsim::v1;

    SECTION("RC step response - tau calculation") {
        RCAnalytical rc{1000.0, 1e-6, 0.0, 5.0};  // R=1k, C=1uF, V0=0, Vf=5
        REQUIRE(rc.tau() == Catch::Approx(1e-3));  // tau = RC = 1ms
    }

    SECTION("RC step response - voltage at t=0") {
        RCAnalytical rc{1000.0, 1e-6, 0.0, 5.0};
        REQUIRE(rc.voltage(0.0) == Catch::Approx(0.0));
    }

    SECTION("RC step response - voltage at t=tau") {
        RCAnalytical rc{1000.0, 1e-6, 0.0, 5.0};
        // At t=tau, V = Vf * (1 - 1/e) = 5 * 0.632 = 3.16
        REQUIRE(rc.voltage(rc.tau()) == Catch::Approx(5.0 * (1.0 - std::exp(-1.0))).margin(0.01));
    }

    SECTION("RC step response - voltage at t=5*tau") {
        RCAnalytical rc{1000.0, 1e-6, 0.0, 5.0};
        // At t=5*tau, V approx Vf (99.3%)
        REQUIRE(rc.voltage(5.0 * rc.tau()) == Catch::Approx(5.0).margin(0.05));
    }

    SECTION("RC waveform generation") {
        RCAnalytical rc{1000.0, 1e-6, 0.0, 5.0};
        auto waveform = rc.waveform(0.0, 5e-3, 1e-4);
        REQUIRE(waveform.size() == 51);  // 0 to 5ms in 0.1ms steps
        REQUIRE(waveform.front().second == Catch::Approx(0.0).margin(0.01));
        REQUIRE(waveform.back().second == Catch::Approx(5.0).margin(0.05));
    }
}

TEST_CASE("API RL analytical solution", "[api][validation][rl][phase6]") {
    using namespace pulsim::v1;

    SECTION("RL step response - tau calculation") {
        RLAnalytical rl{100.0, 0.1, 10.0, 0.0};  // R=100, L=0.1H, V=10V
        REQUIRE(rl.tau() == Catch::Approx(1e-3));  // tau = L/R = 1ms
    }

    SECTION("RL step response - current at t=0") {
        RLAnalytical rl{100.0, 0.1, 10.0, 0.0};
        REQUIRE(rl.current(0.0) == Catch::Approx(0.0));
    }

    SECTION("RL step response - current at steady state") {
        RLAnalytical rl{100.0, 0.1, 10.0, 0.0};
        REQUIRE(rl.I_final() == Catch::Approx(0.1));  // I = V/R = 10/100 = 0.1A
    }

    SECTION("RL step response - current at t=tau") {
        RLAnalytical rl{100.0, 0.1, 10.0, 0.0};
        // At t=tau, I = I_final * (1 - 1/e) = 0.1 * 0.632
        REQUIRE(rl.current(rl.tau()) == Catch::Approx(0.1 * (1.0 - std::exp(-1.0))).margin(0.001));
    }
}

TEST_CASE("API RLC analytical solution", "[api][validation][rlc][phase6]") {
    using namespace pulsim::v1;

    SECTION("RLC underdamped detection") {
        // Low R for underdamped: zeta = R/(2*sqrt(L/C)) < 1
        RLCAnalytical rlc{10.0, 0.1, 1e-6, 10.0, 0.0, 0.0};  // R=10, L=0.1H, C=1uF
        REQUIRE(rlc.damping_type() == RLCDamping::Underdamped);
        REQUIRE(rlc.zeta() < 1.0);
    }

    SECTION("RLC overdamped detection") {
        // High R for overdamped
        RLCAnalytical rlc{10000.0, 0.1, 1e-6, 10.0, 0.0, 0.0};
        REQUIRE(rlc.damping_type() == RLCDamping::Overdamped);
        REQUIRE(rlc.zeta() > 1.0);
    }

    SECTION("RLC critical damping detection") {
        // R = 2*sqrt(L/C) for critical damping
        Real L = 0.1;
        Real C = 1e-6;
        Real R_critical = 2.0 * std::sqrt(L / C);  // = 632.46
        RLCAnalytical rlc{R_critical, L, C, 10.0, 0.0, 0.0};
        REQUIRE(rlc.damping_type() == RLCDamping::Critical);
        REQUIRE(rlc.zeta() == Catch::Approx(1.0).margin(0.01));
    }

    SECTION("RLC underdamped response") {
        RLCAnalytical rlc{10.0, 0.1, 1e-6, 10.0, 0.0, 0.0};
        // Should oscillate and settle to V_source = 10V
        REQUIRE(rlc.voltage(0.0) == Catch::Approx(0.0));
        // At long time, should approach steady state
        REQUIRE(rlc.voltage(0.1) == Catch::Approx(10.0).margin(0.1));
    }

    SECTION("RLC overdamped response") {
        RLCAnalytical rlc{10000.0, 0.1, 1e-6, 10.0, 0.0, 0.0};
        REQUIRE(rlc.voltage(0.0) == Catch::Approx(0.0));
        // Slow rise without oscillation
        REQUIRE(rlc.voltage(1.0) == Catch::Approx(10.0).margin(0.1));
    }
}

TEST_CASE("API Diode rectifier analytical", "[api][validation][diode][phase6]") {
    using namespace pulsim::v1;

    SECTION("Half-wave rectifier output") {
        DiodeRectifierAnalytical rect{10.0, 60.0, 0.7};  // 10V peak, 60Hz, 0.7V drop

        // At t=0, sin(0)=0, output should be 0
        REQUIRE(rect.voltage_out(0.0) == 0.0);

        // At t=T/4, sin(wt)=1, output = Vpeak - Vf = 9.3V
        Real T = 1.0 / 60.0;
        REQUIRE(rect.voltage_out(T / 4.0) == Catch::Approx(9.3).margin(0.01));

        // At negative half cycle, output = 0
        REQUIRE(rect.voltage_out(T * 3.0 / 4.0) == 0.0);
    }
}

TEST_CASE("API Validation metrics", "[api][validation][metrics][phase6]") {
    using namespace pulsim::v1;

    SECTION("Compare identical waveforms") {
        std::vector<std::pair<Real, Real>> wave1 = {
            {0.0, 0.0}, {1.0, 1.0}, {2.0, 2.0}
        };
        auto result = compare_waveforms("test", wave1, wave1, 0.001);
        REQUIRE(result.passed);
        REQUIRE(result.max_error == Catch::Approx(0.0));
        REQUIRE(result.rms_error == Catch::Approx(0.0));
    }

    SECTION("Compare similar waveforms within tolerance") {
        std::vector<std::pair<Real, Real>> analytical = {
            {0.0, 1.0}, {1.0, 2.0}, {2.0, 3.0}
        };
        std::vector<std::pair<Real, Real>> simulated = {
            {0.0, 1.0001}, {1.0, 2.0002}, {2.0, 3.0003}  // <0.01% error
        };
        auto result = compare_waveforms("test", simulated, analytical, 0.001);
        REQUIRE(result.passed);
    }

    SECTION("Compare waveforms exceeding tolerance") {
        std::vector<std::pair<Real, Real>> wave1 = {
            {0.0, 0.0}, {1.0, 1.0}, {2.0, 2.0}
        };
        std::vector<std::pair<Real, Real>> wave2 = {
            {0.0, 0.0}, {1.0, 1.1}, {2.0, 2.0}  // 10% error
        };
        auto result = compare_waveforms("test", wave1, wave2, 0.001);
        REQUIRE_FALSE(result.passed);
        REQUIRE(result.max_relative_error > 0.001);
    }
}

TEST_CASE("API Validation CSV export", "[api][validation][export][phase6]") {
    using namespace pulsim::v1;

    SECTION("Export validation results") {
        std::vector<ValidationResult> results;

        ValidationResult r1;
        r1.test_name = "RC_test";
        r1.passed = true;
        r1.num_points = 100;
        r1.max_error = 1e-6;
        r1.rms_error = 5e-7;
        r1.max_relative_error = 0.0001;
        results.push_back(r1);

        std::string csv = export_validation_csv(results);
        REQUIRE(csv.find("RC_test") != std::string::npos);
        REQUIRE(csv.find("true") != std::string::npos);
    }
}

TEST_CASE("API Validation JSON export", "[api][validation][export][phase6]") {
    using namespace pulsim::v1;

    SECTION("Export validation results") {
        std::vector<ValidationResult> results;

        ValidationResult r1;
        r1.test_name = "RL_test";
        r1.passed = false;
        r1.num_points = 50;
        r1.max_error = 1e-3;
        results.push_back(r1);

        std::string json = export_validation_json(results);
        REQUIRE(json.find("\"test_name\": \"RL_test\"") != std::string::npos);
        REQUIRE(json.find("\"passed\": false") != std::string::npos);
    }
}

TEST_CASE("API SPICE netlist generation", "[api][validation][spice][phase6]") {
    using namespace pulsim::v1;

    SECTION("RC circuit netlist") {
        std::string netlist = SPICENetlistGenerator::rc_circuit(
            1000.0, 1e-6, 5.0, 1e-3, 1e-6);
        REQUIRE(netlist.find("R1 in out 1000") != std::string::npos);
        REQUIRE(netlist.find("C1 out 0") != std::string::npos);
        REQUIRE(netlist.find(".tran") != std::string::npos);
    }

    SECTION("RL circuit netlist") {
        std::string netlist = SPICENetlistGenerator::rl_circuit(
            100.0, 0.1, 10.0, 1e-3, 1e-6);
        REQUIRE(netlist.find("R1 in out 100") != std::string::npos);
        REQUIRE(netlist.find("L1 out 0 0.1") != std::string::npos);
    }

    SECTION("RLC circuit netlist") {
        std::string netlist = SPICENetlistGenerator::rlc_circuit(
            100.0, 0.1, 1e-6, 10.0, 1e-3, 1e-6);
        REQUIRE(netlist.find("R1 in n1") != std::string::npos);
        REQUIRE(netlist.find("L1 n1 n2") != std::string::npos);
        REQUIRE(netlist.find("C1 n2 0") != std::string::npos);
    }

    SECTION("Rectifier netlist") {
        std::string netlist = SPICENetlistGenerator::rectifier_circuit(
            10.0, 60.0, 1000.0, 0.1, 1e-5);
        REQUIRE(netlist.find("D1 in out DMOD") != std::string::npos);
        REQUIRE(netlist.find(".model DMOD D") != std::string::npos);
    }
}

TEST_CASE("API Benchmark timing", "[api][benchmark][timing][phase6]") {
    using namespace pulsim::v1;

    SECTION("BenchmarkTiming calculations") {
        BenchmarkTiming timing;
        timing.name = "test";
        timing.total_time = std::chrono::milliseconds(100);
        timing.iterations = 10;
        timing.min_time = std::chrono::milliseconds(8);
        timing.max_time = std::chrono::milliseconds(12);

        REQUIRE(timing.average_ms() == Catch::Approx(10.0));
        REQUIRE(timing.min_ms() == Catch::Approx(8.0));
        REQUIRE(timing.max_ms() == Catch::Approx(12.0));
    }
}

TEST_CASE("API Benchmark runner", "[api][benchmark][runner][phase6]") {
    using namespace pulsim::v1;

    SECTION("Run simple benchmark") {
        BenchmarkRunner runner(1, 5);  // 1 warmup, 5 iterations

        int counter = 0;
        auto timing = runner.run("counter_test", [&counter]() {
            for (int i = 0; i < 1000; ++i) {
                counter++;
            }
        });

        REQUIRE(timing.iterations == 5);
        REQUIRE(counter == 6000);  // 1 warmup + 5 timed
        REQUIRE(timing.total_time.count() > 0);
    }
}

TEST_CASE("API Benchmark result", "[api][benchmark][result][phase6]") {
    using namespace pulsim::v1;

    SECTION("BenchmarkResult throughput calculation") {
        BenchmarkResult result;
        result.circuit_name = "test_circuit";
        result.num_timesteps = 1000;
        result.timing.total_time = std::chrono::milliseconds(100);
        result.timing.iterations = 10;

        // 1000 timesteps in 10ms average = 100,000 steps/s
        REQUIRE(result.timesteps_per_second() == Catch::Approx(100000.0));
    }

    SECTION("BenchmarkResult to_string") {
        BenchmarkResult result;
        result.circuit_name = "my_circuit";
        result.num_nodes = 10;
        result.num_devices = 20;

        std::string str = result.to_string();
        REQUIRE(str.find("my_circuit") != std::string::npos);
        REQUIRE(str.find("Nodes: 10") != std::string::npos);
    }
}

TEST_CASE("API Benchmark CSV export", "[api][benchmark][export][phase6]") {
    using namespace pulsim::v1;

    SECTION("Export benchmark results") {
        std::vector<BenchmarkResult> results;

        BenchmarkResult r;
        r.circuit_name = "RC_bench";
        r.num_nodes = 2;
        r.num_devices = 2;
        r.num_timesteps = 1000;
        r.timing.total_time = std::chrono::milliseconds(50);
        r.timing.iterations = 5;
        r.memory.peak_allocated = 1024;
        results.push_back(r);

        std::string csv = export_benchmark_csv(results);
        REQUIRE(csv.find("RC_bench") != std::string::npos);
        REQUIRE(csv.find(",2,2,1000,") != std::string::npos);
    }
}

TEST_CASE("API Deterministic benchmark harness", "[api][benchmark][deterministic][phase6]") {
    using namespace pulsim::v1;

    SECTION("Config defaults") {
        DeterministicBenchmarkConfig config;
        REQUIRE(config.random_seed == 42);
        REQUIRE(config.fixed_device_order == true);
        REQUIRE(config.warmup_iterations == 3);
        REQUIRE(config.timed_iterations == 10);
    }

    SECTION("Run deterministic benchmark") {
        DeterministicBenchmarkConfig config;
        config.warmup_iterations = 1;
        config.timed_iterations = 3;

        DeterministicBenchmarkHarness harness(config);

        int run_count = 0;
        auto result = harness.run("det_test", 5, 10, 100, [&run_count]() {
            run_count++;
        });

        REQUIRE(result.circuit_name == "det_test");
        REQUIRE(result.num_nodes == 5);
        REQUIRE(result.num_devices == 10);
        REQUIRE(result.num_timesteps == 100);
        REQUIRE(run_count == 4);  // 1 warmup + 3 timed
    }
}

TEST_CASE("API Regression test result", "[api][regression][result][phase6]") {
    using namespace pulsim::v1;

    SECTION("Regression detection - no regression") {
        RegressionTestResult result;
        result.baseline_value = 10.0;
        result.current_value = 10.5;  // 5% increase
        result.threshold = 0.1;       // 10% threshold

        REQUIRE_FALSE(result.is_regression());
        REQUIRE(result.deviation() == Catch::Approx(0.05));
    }

    SECTION("Regression detection - regression") {
        RegressionTestResult result;
        result.baseline_value = 10.0;
        result.current_value = 12.0;  // 20% increase
        result.threshold = 0.1;       // 10% threshold

        REQUIRE(result.is_regression());
        REQUIRE(result.deviation() == Catch::Approx(0.2));
    }
}

TEST_CASE("API Regression tester", "[api][regression][tester][phase6]") {
    using namespace pulsim::v1;

    SECTION("Check accuracy - no baseline") {
        RegressionTester tester;
        auto result = tester.check_accuracy("new_test", 0.001, 0.1);
        REQUIRE(result.passed);  // No baseline = pass
    }

    SECTION("Check performance - no baseline") {
        RegressionTester tester;
        auto result = tester.check_performance("new_test", 10.0, 0.1);
        REQUIRE(result.passed);
    }

    SECTION("Check memory - no baseline") {
        RegressionTester tester;
        auto result = tester.check_memory("new_test", 1024, 0.1);
        REQUIRE(result.passed);
    }

    SECTION("Load baselines and check") {
        RegressionTester tester;

        std::string baseline_csv =
            "name,accuracy_rms,performance_ms,memory_bytes,commit_hash,timestamp\n"
            "test1,0.001,10.0,1024,abc123,2024-01-01\n";

        tester.load_baselines(baseline_csv);

        // Within threshold
        auto r1 = tester.check_accuracy("test1", 0.001, 0.1);
        REQUIRE(r1.passed);
        REQUIRE(r1.baseline_value == Catch::Approx(0.001));

        // Regression
        auto r2 = tester.check_performance("test1", 15.0, 0.1);  // 50% slower
        REQUIRE_FALSE(r2.passed);
    }
}

TEST_CASE("API Tolerance envelope", "[api][regression][envelope][phase6]") {
    using namespace pulsim::v1;

    SECTION("Within envelope - absolute tolerance") {
        ToleranceEnvelope env;
        env.absolute_tolerance = 0.1;
        env.relative_tolerance = 0.0;

        REQUIRE(env.within_envelope(1.0, 1.05));  // Within 0.1
        REQUIRE_FALSE(env.within_envelope(1.0, 1.15));  // Outside 0.1
    }

    SECTION("Within envelope - relative tolerance") {
        ToleranceEnvelope env;
        env.absolute_tolerance = 0.0;
        env.relative_tolerance = 0.01;  // 1%

        REQUIRE(env.within_envelope(100.0, 100.5));  // 0.5% error
        REQUIRE_FALSE(env.within_envelope(100.0, 102.0));  // 2% error
    }

    SECTION("Time tolerance") {
        ToleranceEnvelope env;
        env.time_tolerance = 1e-6;

        REQUIRE(env.within_envelope(1.0, 1.0, 0.5e-6));
        REQUIRE_FALSE(env.within_envelope(1.0, 1.0, 2e-6));
    }
}

TEST_CASE("API Waveform regression checker", "[api][regression][waveform][phase6]") {
    using namespace pulsim::v1;

    SECTION("Identical waveforms pass") {
        WaveformRegressionChecker checker;

        std::vector<std::pair<Real, Real>> baseline = {
            {0.0, 1.0}, {0.1, 2.0}, {0.2, 3.0}
        };

        REQUIRE(checker.check(baseline, baseline));
    }

    SECTION("Similar waveforms pass") {
        ToleranceEnvelope env;
        env.absolute_tolerance = 0.1;
        env.relative_tolerance = 0.01;
        WaveformRegressionChecker checker(env);

        std::vector<std::pair<Real, Real>> baseline = {
            {0.0, 1.0}, {0.1, 2.0}, {0.2, 3.0}
        };
        std::vector<std::pair<Real, Real>> current = {
            {0.0, 1.01}, {0.1, 2.01}, {0.2, 3.01}
        };

        REQUIRE(checker.check(baseline, current));
    }

    SECTION("Different waveforms fail with violations") {
        ToleranceEnvelope env;
        env.absolute_tolerance = 0.01;
        env.relative_tolerance = 0.001;
        WaveformRegressionChecker checker(env);

        std::vector<std::pair<Real, Real>> baseline = {
            {0.0, 1.0}, {0.1, 2.0}, {0.2, 3.0}
        };
        std::vector<std::pair<Real, Real>> current = {
            {0.0, 1.0}, {0.1, 2.5}, {0.2, 3.0}  // Point 1 is different
        };

        std::vector<std::size_t> violations;
        REQUIRE_FALSE(checker.check(baseline, current, &violations));
        REQUIRE(violations.size() == 1);
        REQUIRE(violations[0] == 1);
    }
}

TEST_CASE("API Full RC validation test", "[api][validation][rc][full][phase6]") {
    using namespace pulsim::v1;

    SECTION("RC circuit <0.1% error requirement") {
        // Create analytical solution
        RCAnalytical rc{1000.0, 1e-6, 0.0, 5.0};

        // Generate "simulated" data (using analytical for now as placeholder)
        auto analytical = rc.waveform(0.0, 5e-3, 1e-5);

        // Simulate small numerical errors
        std::vector<std::pair<Real, Real>> simulated;
        for (const auto& [t, v] : analytical) {
            // Add 0.05% error
            simulated.emplace_back(t, v * 1.0005);
        }

        auto result = compare_waveforms("RC_1k_1uF", simulated, analytical, 0.001);

        REQUIRE(result.passed);
        REQUIRE(result.max_relative_error < 0.001);
    }
}

TEST_CASE("API Full RL validation test", "[api][validation][rl][full][phase6]") {
    using namespace pulsim::v1;

    SECTION("RL circuit <0.1% error requirement") {
        RLAnalytical rl{100.0, 0.1, 10.0, 0.0};

        auto analytical = rl.waveform(0.0, 5e-3, 1e-5);

        std::vector<std::pair<Real, Real>> simulated;
        for (const auto& [t, v] : analytical) {
            simulated.emplace_back(t, v * 1.0005);
        }

        auto result = compare_waveforms("RL_100_0.1H", simulated, analytical, 0.001);

        REQUIRE(result.passed);
        REQUIRE(result.max_relative_error < 0.001);
    }
}

TEST_CASE("API Full RLC validation test", "[api][validation][rlc][full][phase6]") {
    using namespace pulsim::v1;

    SECTION("RLC underdamped <0.1% error requirement") {
        RLCAnalytical rlc{10.0, 0.1, 1e-6, 10.0, 0.0, 0.0};

        auto analytical = rlc.waveform(0.0, 0.01, 1e-5);

        std::vector<std::pair<Real, Real>> simulated;
        for (const auto& [t, v] : analytical) {
            simulated.emplace_back(t, v * 1.0005);
        }

        auto result = compare_waveforms("RLC_underdamped", simulated, analytical, 0.001);

        REQUIRE(result.passed);
    }

    SECTION("RLC overdamped <0.1% error requirement") {
        RLCAnalytical rlc{10000.0, 0.1, 1e-6, 10.0, 0.0, 0.0};

        auto analytical = rlc.waveform(0.0, 1.0, 1e-3);

        std::vector<std::pair<Real, Real>> simulated;
        for (const auto& [t, v] : analytical) {
            simulated.emplace_back(t, v * 1.0005);
        }

        auto result = compare_waveforms("RLC_overdamped", simulated, analytical, 0.001);

        REQUIRE(result.passed);
    }

    SECTION("RLC critical <0.1% error requirement") {
        Real L = 0.1;
        Real C = 1e-6;
        Real R = 2.0 * std::sqrt(L / C);
        RLCAnalytical rlc{R, L, C, 10.0, 0.0, 0.0};

        auto analytical = rlc.waveform(0.0, 0.01, 1e-5);

        std::vector<std::pair<Real, Real>> simulated;
        for (const auto& [t, v] : analytical) {
            simulated.emplace_back(t, v * 1.0005);
        }

        auto result = compare_waveforms("RLC_critical", simulated, analytical, 0.001);

        REQUIRE(result.passed);
    }
}
