"""Tests for DOF counting."""

import pytest
from kindred_solver.dof import count_dof
from kindred_solver.expr import Const, Var
from kindred_solver.params import ParamTable


class TestDOF:
    def test_unconstrained(self):
        """2 free params, no residuals → DOF = 2."""
        pt = ParamTable()
        pt.add("x", 0.0)
        pt.add("y", 0.0)
        assert count_dof([], pt) == 2

    def test_fully_constrained(self):
        """1 free param, 1 independent residual → DOF = 0."""
        pt = ParamTable()
        x = pt.add("x", 1.0)
        residuals = [x - Const(3.0)]
        assert count_dof(residuals, pt) == 0

    def test_under_constrained(self):
        """2 free params, 1 residual → DOF = 1."""
        pt = ParamTable()
        x = pt.add("x", 1.0)
        y = pt.add("y", 1.0)
        residuals = [x + y - Const(5.0)]
        assert count_dof(residuals, pt) == 1

    def test_redundant_constraints(self):
        """2 free params, 3 residuals (2 independent) → DOF = 0."""
        pt = ParamTable()
        x = pt.add("x", 1.0)
        y = pt.add("y", 1.0)
        residuals = [
            x - Const(3.0),
            y - Const(4.0),
            x + y - Const(7.0),  # redundant (sum of first two)
        ]
        assert count_dof(residuals, pt) == 0

    def test_no_free_params(self):
        """All fixed → DOF = 0."""
        pt = ParamTable()
        pt.add("x", 1.0, fixed=True)
        residuals = [Const(0.0)]
        assert count_dof(residuals, pt) == 0
