"""Reference-oracle contracts for pure projection and orthogonalization math."""

from __future__ import annotations

import math
from types import SimpleNamespace

import pytest
import torch

from obliteratus.abliterate import AbliterationPipeline
from obliteratus.analysis.numerical_contracts import (
    orthogonalize_subspace_rows,
    project_weight_against_direction,
    remove_harmless_principal_components,
    residualize_against_shield_atoms,
    select_projection_coefficients,
)


def _canonical_rows(rows: torch.Tensor) -> torch.Tensor:
    result = rows.clone()
    for idx in range(result.shape[0]):
        pivot = result[idx].abs().argmax()
        if result[idx, pivot] < 0:
            result[idx] = -result[idx]
    return result


def _reference_gram_schmidt(rows: torch.Tensor) -> torch.Tensor:
    basis: list[torch.Tensor] = []
    for row in rows.to(dtype=torch.float64):
        residual = row.clone()
        for prev in basis:
            residual = residual - (residual @ prev) * prev
        norm = residual.norm()
        if norm > 1e-8:
            basis.append(residual / norm)
    return torch.stack(basis)[: rows.shape[0]].to(dtype=rows.dtype, device=rows.device)


def _reference_projection(weight: torch.Tensor, direction: torch.Tensor, scale: float) -> torch.Tensor:
    work = weight.to(dtype=torch.float64)
    d = direction.reshape(-1, 1).to(dtype=torch.float64, device=weight.device)
    d_norm = d.norm()
    if d_norm < 1e-8:
        return weight.clone()
    d = d / d_norm
    if weight.shape[-1] == d.shape[0]:
        coeff = work @ d
        return (work - d.T * (scale * coeff)).to(dtype=weight.dtype)
    coeff = d.T @ work
    return (work - (scale * d) * coeff).to(dtype=weight.dtype)


def test_orthogonalize_matches_reference_and_preserves_primary_orientation():
    subspace = torch.tensor(
        [[2.0, 0.0, 0.0], [1.0, 3.0, 0.0], [1.0, 1.0, 4.0]],
        dtype=torch.float64,
    )

    actual = orthogonalize_subspace_rows(subspace)
    expected = _reference_gram_schmidt(subspace)

    assert actual.dtype == subspace.dtype
    assert torch.allclose(actual @ actual.T, torch.eye(3, dtype=torch.float64), atol=1e-12)
    assert torch.allclose(_canonical_rows(actual), _canonical_rows(expected), atol=1e-12)
    assert actual[0] @ subspace[0] > 0


def test_orthogonalize_returns_degenerate_inputs_without_allocating_new_tensor():
    single_row = torch.tensor([[1.0, 2.0, 3.0]], dtype=torch.float64)
    empty = torch.empty((2, 0), dtype=torch.float64)

    assert orthogonalize_subspace_rows(single_row) is single_row
    assert orthogonalize_subspace_rows(empty) is empty


def test_orthogonalize_non_finite_subspace_is_explicit_noop():
    subspace = torch.tensor([[1.0, 0.0], [float("nan"), 1.0]], dtype=torch.float64)

    actual = orthogonalize_subspace_rows(subspace)

    assert actual is subspace
    assert torch.isnan(actual[1, 0])


def test_orthogonalization_preserves_float64_precision_for_nearly_collinear_rows():
    subspace = torch.tensor(
        [[1.0, 1e-6, 0.0], [1.0, 0.0, 1e-6], [0.0, 1.0, 1.0]],
        dtype=torch.float64,
    )

    actual = orthogonalize_subspace_rows(subspace)
    expected = _reference_gram_schmidt(subspace)

    assert actual.dtype == torch.float64
    assert torch.allclose(_canonical_rows(actual), _canonical_rows(expected), atol=1e-10)


def test_orthogonalize_two_half_precision_rows_uses_stable_compute_dtype():
    subspace = torch.tensor(
        [[1.0, 1.0, 0.0], [1.0, 0.0, 1.0]],
        dtype=torch.float16,
    )

    actual = orthogonalize_subspace_rows(subspace)
    expected = _reference_gram_schmidt(subspace)

    assert actual is not subspace
    assert actual.dtype == torch.float16
    assert torch.allclose(_canonical_rows(actual), _canonical_rows(expected), atol=1e-3)
    gram = actual.float() @ actual.float().T
    assert torch.allclose(gram, torch.eye(2), atol=1e-3)


def test_integer_projection_uses_supported_float_compute_then_restores_weight_dtype():
    weight = torch.tensor([[2, 0], [0, 2]], dtype=torch.int64)
    direction = torch.tensor([1, 0], dtype=torch.int64)

    projected = project_weight_against_direction(weight, direction)

    assert projected.projected is True
    assert projected.weight.dtype == torch.int64
    assert torch.equal(projected.weight, torch.tensor([[0, 0], [0, 2]], dtype=torch.int64))


def test_projection_full_removal_is_idempotent_and_orthogonal_to_direction():
    weight = torch.tensor([[3.0, 4.0, 0.0], [1.0, -2.0, 2.0]], dtype=torch.float64)
    direction = torch.tensor([0.6, 0.8, 0.0], dtype=torch.float64)

    first = project_weight_against_direction(weight, direction, regularization=0.0)
    second = project_weight_against_direction(first.weight, direction, regularization=0.0)

    assert first.projected
    assert torch.allclose(first.weight @ direction, torch.zeros(2, dtype=torch.float64), atol=1e-12)
    assert torch.allclose(first.weight, second.weight, atol=1e-12)
    assert torch.allclose(first.weight, _reference_projection(weight, direction, scale=1.0), atol=1e-12)


def test_projection_normalizes_non_unit_directions_before_applying_formula():
    weight = torch.tensor([[3.0, 4.0], [-5.0, 6.0]], dtype=torch.float64)

    unit = project_weight_against_direction(weight, torch.tensor([1.0, 0.0], dtype=torch.float64))
    non_unit = project_weight_against_direction(weight, torch.tensor([2.0, 0.0], dtype=torch.float64))

    assert unit.projected
    assert non_unit.projected
    assert torch.allclose(non_unit.weight, unit.weight, atol=1e-12)
    assert torch.allclose(non_unit.weight, _reference_projection(weight, torch.tensor([2.0, 0.0]), 1.0))


def test_projection_zero_direction_is_deterministic_no_op():
    weight = torch.tensor([[3.0, 4.0], [-5.0, 6.0]], dtype=torch.float64)
    direction = torch.zeros(2, dtype=torch.float64)

    first = project_weight_against_direction(weight, direction)
    second = project_weight_against_direction(weight, direction)

    assert not first.projected
    assert first.layout is None
    assert torch.equal(first.weight, weight)
    assert torch.equal(second.weight, first.weight)
    assert first.coefficient_norm_sq == 0.0


def test_projection_projects_at_exact_tiny_direction_threshold():
    weight = torch.tensor([[3.0, 4.0]], dtype=torch.float64)
    direction = torch.tensor([1e-8, 0.0], dtype=torch.float64)

    projected = project_weight_against_direction(weight, direction)

    assert projected.projected is True
    assert projected.layout == "standard"
    assert torch.allclose(projected.weight, torch.tensor([[0.0, 4.0]], dtype=torch.float64), atol=1e-12)


def test_projection_zero_direction_metadata_uses_strict_false_flag():
    result = project_weight_against_direction(
        torch.tensor([[3.0, 4.0]], dtype=torch.float64),
        torch.zeros(2, dtype=torch.float64),
    )

    assert result.projected is False
    assert result.weight is not None
    assert result.coefficient_norm_sq == 0.0
    assert result.layout is None


@pytest.mark.parametrize("projection_row_fraction", [0.0, -0.1, 1.01])
def test_select_projection_coefficients_rejects_invalid_fraction(projection_row_fraction):
    coeff = torch.tensor([[1.0], [2.0]], dtype=torch.float64)

    with pytest.raises(ValueError, match=r"projection_row_fraction must be in"):
        select_projection_coefficients(coeff, projection_row_fraction)


def test_select_projection_coefficients_empty_and_singleton_inputs_are_noops():
    empty = torch.empty((0, 1), dtype=torch.float64)
    singleton = torch.tensor([[3.0]], dtype=torch.float64)

    assert select_projection_coefficients(empty, 0.5) is empty
    assert select_projection_coefficients(singleton, 0.5) is singleton


def test_projection_unsupported_layout_returns_complete_noop_metadata_clone():
    weight = torch.tensor([[1.0, 2.0, 3.0]], dtype=torch.float64)
    direction = torch.tensor([1.0, 0.0], dtype=torch.float64)

    projected = project_weight_against_direction(weight, direction)

    assert projected.projected is False
    assert projected.layout is None
    assert projected.coefficient_norm_sq == 0.0
    assert projected.weight is not weight
    assert torch.equal(projected.weight, weight)


def test_projection_standard_layout_non_finite_coefficients_fail_closed():
    huge = torch.finfo(torch.float32).max
    weight = torch.tensor([[huge, huge]], dtype=torch.float32)
    direction = torch.tensor([1.0, 1.0], dtype=torch.float32)

    projected = project_weight_against_direction(weight, direction)

    assert projected.projected is False
    assert projected.layout == "standard"
    assert projected.coefficient_norm_sq == 0.0
    assert torch.equal(projected.weight, weight)


def test_projection_transposed_layout_non_finite_coefficients_fail_closed():
    huge = torch.finfo(torch.float32).max
    weight = torch.tensor([[huge], [huge]], dtype=torch.float32)
    direction = torch.tensor([1.0, 1.0], dtype=torch.float32)

    projected = project_weight_against_direction(weight, direction)

    assert projected.projected is False
    assert projected.layout == "transposed"
    assert projected.coefficient_norm_sq == 0.0
    assert torch.equal(projected.weight, weight)


def test_projection_preserves_orthogonal_coordinates_and_contracts_norm_without_restore():
    direction = torch.tensor([1.0, 0.0, 0.0])
    orthogonal_probe = torch.tensor([0.0, 2.0, -1.0])
    weight = torch.tensor([[5.0, 2.0, -1.0], [-3.0, 4.0, 7.0]])

    projected = project_weight_against_direction(weight, direction, norm_preserve=False)

    assert torch.allclose(projected.weight @ direction, torch.zeros(2))
    assert torch.allclose(projected.weight @ orthogonal_probe, weight @ orthogonal_probe)
    assert projected.weight.norm() <= weight.norm()


def test_norm_preservation_uses_cap_when_projection_would_amplify_too_much():
    weight = torch.tensor([[100.0, 1.0], [0.0, 0.0]])
    direction = torch.tensor([1.0, 0.0])

    projected = project_weight_against_direction(
        weight,
        direction,
        norm_preserve=True,
        max_norm_ratio=1.10,
    )

    assert projected.projected
    assert torch.isclose(projected.weight.norm(), torch.tensor(1.10), atol=1e-6)


def test_norm_preservation_keeps_zero_projection_without_restoration():
    weight = torch.tensor([[3.0, 0.0]], dtype=torch.float64)
    direction = torch.tensor([1.0, 0.0], dtype=torch.float64)

    projected = project_weight_against_direction(weight, direction, norm_preserve=True)

    assert projected.projected is True
    assert projected.layout == "standard"
    assert projected.coefficient_norm_sq == 9.0
    assert torch.allclose(projected.weight, torch.zeros_like(weight), atol=0.0, rtol=0.0)
    assert torch.isfinite(projected.weight).all()


def test_norm_preservation_at_max_ratio_boundary_preserves_original_norm():
    direction = torch.tensor([1.0, 0.0], dtype=torch.float64)
    weight = torch.tensor([[math.sqrt(21.0), 10.0]], dtype=torch.float64)

    projected = project_weight_against_direction(
        weight,
        direction,
        norm_preserve=True,
        max_norm_ratio=1.10,
    )

    assert projected.projected
    assert torch.allclose(projected.weight.norm(), weight.norm(), atol=1e-12)


def test_transposed_norm_preservation_reports_removed_coefficient_energy():
    direction = torch.tensor([1.0, 0.0], dtype=torch.float64)
    weight = torch.tensor([[3.0, 4.0, 0.0], [10.0, 20.0, 30.0]], dtype=torch.float64)

    projected = project_weight_against_direction(weight, direction, norm_preserve=True)

    assert projected.projected is True
    assert projected.layout == "transposed"
    assert projected.coefficient_norm_sq == 25.0
    assert torch.allclose(projected.weight[0], torch.zeros(3, dtype=torch.float64), atol=1e-12)


def test_transposed_projection_without_norm_preservation_reports_zero_metadata_energy():
    direction = torch.tensor([1.0, 0.0], dtype=torch.float64)
    weight = torch.tensor([[3.0, 4.0, 0.0], [10.0, 20.0, 30.0]], dtype=torch.float64)

    projected = project_weight_against_direction(weight, direction, norm_preserve=False)

    assert projected.projected is True
    assert projected.layout == "transposed"
    assert projected.coefficient_norm_sq == 0.0
    assert torch.allclose(projected.weight[0], torch.zeros(3, dtype=torch.float64), atol=1e-12)


def test_projection_supports_standard_and_transposed_layouts():
    direction = torch.tensor([1.0, 0.0])
    standard = torch.tensor([[3.0, 4.0], [5.0, 6.0], [7.0, 8.0]])
    transposed = standard.T.contiguous()

    standard_result = project_weight_against_direction(standard, direction)
    transposed_result = project_weight_against_direction(transposed, direction)

    assert torch.allclose(standard_result.weight[:, 0], torch.zeros(3))
    assert torch.allclose(transposed_result.weight[0, :], torch.zeros(3))
    assert torch.allclose(standard_result.weight, _reference_projection(standard, direction, 1.0))
    assert torch.allclose(transposed_result.weight, _reference_projection(transposed, direction, 1.0))


def test_projection_rejects_orthogonal_direction_magnitude_as_a_signal():
    weight = torch.tensor([[4.0, 3.0], [2.0, -1.0]], dtype=torch.float64)
    unit = project_weight_against_direction(weight, torch.tensor([1.0, 0.0], dtype=torch.float64))
    scaled = project_weight_against_direction(weight, torch.tensor([5.0, 0.0], dtype=torch.float64))

    assert torch.allclose(unit.weight, scaled.weight, atol=1e-12)
    assert unit.layout == scaled.layout == "standard"


def test_row_fraction_selects_largest_coefficients_and_is_permutation_equivariant():
    coeff = torch.tensor([[0.5], [-3.0], [2.0], [0.1]])
    selected = select_projection_coefficients(coeff, 0.5)

    assert selected.tolist() == [[0.0], [-3.0], [2.0], [0.0]]

    permutation = torch.tensor([2, 0, 3, 1])
    permuted = select_projection_coefficients(coeff[permutation], 0.5)
    assert torch.allclose(permuted, selected[permutation])


def test_projection_row_fraction_removes_only_selected_rows():
    weight = torch.tensor([[10.0, 1.0], [1.0, 7.0], [-5.0, 2.0], [0.2, 9.0]])
    direction = torch.tensor([1.0, 0.0])

    projected = project_weight_against_direction(weight, direction, projection_row_fraction=0.5)

    assert torch.allclose(projected.weight[:, 0], torch.tensor([0.0, 1.0, 0.0, 0.2]))
    assert torch.allclose(projected.weight[:, 1], weight[:, 1])


def test_projection_row_fraction_keeps_only_the_two_largest_magnitudes():
    coeff = torch.tensor([[0.5], [-3.0], [2.0], [0.1]], dtype=torch.float64)
    selected = select_projection_coefficients(coeff, 0.5)

    assert torch.equal(selected != 0, torch.tensor([[False], [True], [True], [False]]))
    assert torch.allclose(selected.abs().sum(), torch.tensor(5.0, dtype=torch.float64))


@pytest.mark.parametrize("regularization", [-1.0, 0.25, 1.25])
def test_finite_regularization_values_are_applied_without_unit_interval_clamping(regularization):
    weight = torch.tensor([[4.0, 3.0]], dtype=torch.float64)
    direction = torch.tensor([1.0, 0.0], dtype=torch.float64)
    scale = 1.0 - regularization

    projected = project_weight_against_direction(weight, direction, regularization=regularization)

    assert projected.projected
    assert torch.allclose(projected.weight, _reference_projection(weight, direction, scale), atol=1e-12)


def test_harmless_pc_removal_orthogonalizes_against_dominant_component():
    subspace = torch.tensor([[1.0, 1.0, 0.0], [0.5, 0.0, 1.0]], dtype=torch.float64)
    harmless = torch.tensor(
        [[-2.0, 0.0, 0.0], [0.0, 0.0, 0.0], [2.0, 0.0, 0.0], [4.0, 0.0, 0.0]],
        dtype=torch.float64,
    )

    residual = remove_harmless_principal_components(subspace, harmless, pc_count=1)

    assert torch.allclose(residual[:, 0], torch.zeros(2, dtype=torch.float64), atol=1e-12)
    assert torch.allclose(residual.norm(dim=-1), torch.ones(2, dtype=torch.float64), atol=1e-12)
    assert torch.allclose(residual @ residual.T, torch.eye(2, dtype=torch.float64), atol=1e-12)


@pytest.mark.parametrize(
    ("subspace", "harmless", "pc_count"),
    [
        (
            torch.tensor([[1.0, 0.0]], dtype=torch.float64),
            torch.eye(3, 2, dtype=torch.float64),
            0,
        ),
        (
            torch.tensor([[1.0, 0.0]], dtype=torch.float64),
            torch.eye(2, dtype=torch.float64),
            1,
        ),
        (
            torch.empty((0, 2), dtype=torch.float64),
            torch.eye(3, 2, dtype=torch.float64),
            1,
        ),
    ],
)
def test_harmless_pc_removal_noops_when_preconditions_are_not_met(
    subspace,
    harmless,
    pc_count,
):
    residual = remove_harmless_principal_components(subspace, harmless, pc_count)

    assert residual is subspace


def test_harmless_pc_removal_returns_subspace_when_svd_fails(monkeypatch):
    subspace = torch.tensor([[1.0, 0.5]], dtype=torch.float64)
    harmless = torch.tensor([[-2.0, 0.0], [0.0, 0.0], [2.0, 0.0]], dtype=torch.float64)

    def fail_svd(*_args, **_kwargs):
        raise RuntimeError("svd fixture failure")

    monkeypatch.setattr(torch.linalg, "svd", fail_svd)

    residual = remove_harmless_principal_components(subspace, harmless, pc_count=1)

    assert residual is subspace


def test_harmless_pc_removal_noops_when_svd_returns_no_components():
    subspace = torch.tensor([[1.0]], dtype=torch.float64)
    harmless = torch.empty((3, 0), dtype=torch.float64)

    residual = remove_harmless_principal_components(subspace, harmless, pc_count=1)

    assert residual is subspace


def test_harmless_pc_removal_subtracts_pc_component_before_row_normalization():
    subspace = torch.tensor([[1.0, 0.5]], dtype=torch.float64)
    harmless = torch.tensor([[-2.0, 0.0], [0.0, 0.0], [2.0, 0.0]], dtype=torch.float64)

    residual = remove_harmless_principal_components(subspace, harmless, pc_count=1)

    assert torch.allclose(residual, torch.tensor([[0.0, 1.0]], dtype=torch.float64), atol=1e-12)
    assert torch.allclose(residual.norm(dim=-1), torch.ones(1, dtype=torch.float64), atol=1e-12)


def test_harmless_pc_removal_treats_exact_epsilon_residual_as_usable_signal():
    subspace = torch.tensor([[1.0, 1e-8]], dtype=torch.float64)
    harmless = torch.tensor([[-2.0, 0.0], [0.0, 0.0], [2.0, 0.0]], dtype=torch.float64)

    residual = remove_harmless_principal_components(subspace, harmless, pc_count=1)

    assert torch.allclose(residual, torch.tensor([[0.0, 1.0]], dtype=torch.float64), atol=1e-12)


def test_harmless_pc_removal_preserves_subunit_residuals_instead_of_restoring_original_row():
    subspace = torch.tensor([[1.0, 0.5]], dtype=torch.float64)
    harmless = torch.tensor([[-2.0, 0.0], [0.0, 0.0], [2.0, 0.0]], dtype=torch.float64)

    residual = remove_harmless_principal_components(subspace, harmless, pc_count=1)

    assert torch.allclose(residual[:, 0], torch.zeros(1, dtype=torch.float64), atol=1e-12)
    assert torch.allclose(residual[:, 1], torch.ones(1, dtype=torch.float64), atol=1e-12)


def test_harmless_pc_removal_restores_only_rows_with_near_zero_residuals():
    subspace = torch.tensor([[1.0, 0.0], [1.0, 0.5]], dtype=torch.float64)
    harmless = torch.tensor([[-2.0, 0.0], [0.0, 0.0], [2.0, 0.0]], dtype=torch.float64)

    residual = remove_harmless_principal_components(subspace, harmless, pc_count=1)

    assert torch.allclose(residual @ residual.T, torch.eye(2, dtype=torch.float64), atol=1e-12)
    assert torch.allclose(residual[0], torch.tensor([1.0, 0.0], dtype=torch.float64), atol=1e-12)
    assert torch.allclose(residual[1], torch.tensor([0.0, 1.0], dtype=torch.float64), atol=1e-12)


def test_harmless_pc_removal_near_zero_fallback_is_per_row_before_qr():
    subspace = torch.tensor([[1.0, 1e-9, 0.0], [0.2, 0.5, 1.0]], dtype=torch.float64)
    harmless = torch.tensor(
        [[-2.0, 0.0, 0.0], [0.0, 0.0, 0.0], [2.0, 0.0, 0.0]],
        dtype=torch.float64,
    )

    residual = remove_harmless_principal_components(subspace, harmless, pc_count=1)

    assert torch.allclose(residual[0], subspace[0], atol=1e-12)
    assert torch.allclose(residual @ residual.T, torch.eye(2, dtype=torch.float64), atol=1e-8)


def test_harmless_pc_removal_replays_deterministically_for_singular_inputs():
    subspace = torch.tensor([[0.0, 1.0, 1.0], [0.0, 2.0, 2.0]], dtype=torch.float64)
    harmless = torch.ones((4, 3), dtype=torch.float64)

    first = remove_harmless_principal_components(subspace, harmless, pc_count=2)
    second = remove_harmless_principal_components(subspace, harmless, pc_count=2)

    assert torch.allclose(first, second, atol=0.0, rtol=0.0)
    assert torch.isfinite(first).all()


def test_shield_atom_residualization_handles_rank_deficient_atoms():
    subspace = torch.tensor([[1.0, 1.0, 0.0], [1.0, 0.0, 1.0]], dtype=torch.float64)
    atoms = torch.tensor([[1.0, 0.0, 0.0], [2.0, 0.0, 0.0]], dtype=torch.float64)

    residual = residualize_against_shield_atoms(subspace, atoms, ridge=1e-6)

    assert torch.allclose(residual[:, 0], torch.zeros(2, dtype=torch.float64), atol=2e-6)
    assert torch.allclose(residual.norm(dim=-1), torch.ones(2, dtype=torch.float64), atol=1e-12)
    assert torch.allclose(residual @ residual.T, torch.eye(2, dtype=torch.float64), atol=1e-12)


def test_shield_atom_residualization_noops_when_atoms_or_subspace_are_empty():
    subspace = torch.tensor([[1.0, 0.0]], dtype=torch.float64)
    atoms = torch.empty((0, 2), dtype=torch.float64)
    empty_subspace = torch.empty((0, 2), dtype=torch.float64)

    assert residualize_against_shield_atoms(subspace, atoms, ridge=1e-3) is subspace
    assert residualize_against_shield_atoms(empty_subspace, torch.eye(2), ridge=1e-3) is empty_subspace


def test_shield_atom_residualization_returns_subspace_when_solve_fails(monkeypatch):
    subspace = torch.tensor([[1.0, 1.0]], dtype=torch.float64)
    atoms = torch.tensor([[1.0, 0.0]], dtype=torch.float64)

    def fail_solve(*_args, **_kwargs):
        raise RuntimeError("solve fixture failure")

    monkeypatch.setattr(torch.linalg, "solve", fail_solve)

    residual = residualize_against_shield_atoms(subspace, atoms, ridge=1e-6)

    assert residual is subspace


def test_shield_atom_residualization_uses_mixed_dtype_compute_but_returns_subspace_dtype():
    subspace = torch.tensor([[1.0, 1.0, 0.0]], dtype=torch.float32)
    atoms = torch.tensor([[1.0, 0.0, 0.0]], dtype=torch.float64)

    residual = residualize_against_shield_atoms(subspace, atoms, ridge=1e-6)

    assert residual.dtype == torch.float32
    assert residual.device == subspace.device
    assert torch.allclose(residual, torch.tensor([[0.0, 1.0, 0.0]], dtype=torch.float32), atol=2e-6)


def test_shield_atom_residualization_upcasts_atoms_to_match_float64_subspace_compute():
    subspace = torch.tensor([[1.0, 1.0, 0.0]], dtype=torch.float64)
    atoms = torch.tensor([[1.0, 0.0, 0.0]], dtype=torch.float32)

    residual = residualize_against_shield_atoms(subspace, atoms, ridge=1e-6)

    assert residual.dtype == torch.float64
    assert torch.allclose(residual, torch.tensor([[0.0, 1.0, 0.0]], dtype=torch.float64), atol=2e-6)


def test_shield_atom_residualization_keeps_float64_precision_with_float32_atoms():
    subspace = torch.tensor([[1.0, 1e-4, 1.0 - 1e-4]], dtype=torch.float64)
    atoms = torch.tensor([[1.0, 1e-4, 0.0], [1e-4, 1.0, 1e-4]], dtype=torch.float32)

    residual = residualize_against_shield_atoms(subspace, atoms, ridge=1e-12)

    assert residual.dtype == torch.float64
    assert residual[0, 0] > 1e-8
    assert torch.allclose(
        residual,
        torch.tensor(
            [[1.0000999534067183e-08, -9.9999997973787514e-05, 0.9999999950000001]],
            dtype=torch.float64,
        ),
        atol=1e-15,
    )


def test_harmless_pc_removal_upcasts_half_precision_for_svd_then_returns_input_dtype():
    subspace = torch.tensor([[1.0, 0.5]], dtype=torch.float16)
    harmless = torch.tensor([[-2.0, 0.0], [0.0, 0.0], [2.0, 0.0]], dtype=torch.float16)

    residual = remove_harmless_principal_components(subspace, harmless, pc_count=1)

    assert residual.dtype == torch.float16
    assert torch.allclose(residual.float(), torch.tensor([[0.0, 1.0]]), atol=1e-3)


def test_dtype_and_device_are_preserved_for_projection_and_residualization():
    weight = torch.tensor([[1.0, 2.0]], dtype=torch.float32)
    direction = torch.tensor([1.0, 0.0], dtype=torch.float64)
    subspace = torch.tensor([[1.0, 1.0]], dtype=torch.float32)
    harmless = torch.tensor([[-1.0, 0.0], [0.0, 0.0], [1.0, 0.0]], dtype=torch.float64)

    projected = project_weight_against_direction(weight, direction)
    residual = remove_harmless_principal_components(subspace, harmless, pc_count=1)

    assert projected.weight.dtype == torch.float32
    assert projected.weight.device == weight.device
    assert residual.dtype == torch.float32
    assert residual.device == subspace.device


@pytest.mark.parametrize("bad_value", [float("nan"), float("inf")])
def test_projection_non_finite_policy_is_skip_without_mutation(bad_value):
    weight = torch.tensor([[1.0, 2.0], [bad_value, 4.0]])
    direction = torch.tensor([1.0, 0.0])

    projected = project_weight_against_direction(weight, direction)

    assert not projected.projected
    assert torch.allclose(projected.weight, weight, equal_nan=True)


def test_zero_inputs_follow_existing_fallback_policy_without_non_finite_output():
    zero_subspace = torch.zeros((2, 3), dtype=torch.float64)
    harmless = torch.tensor([[-1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [1.0, 0.0, 0.0]])
    atoms = torch.tensor([[1.0, 0.0, 0.0]])

    pc_residual = remove_harmless_principal_components(zero_subspace, harmless, pc_count=1)
    shield_residual = residualize_against_shield_atoms(zero_subspace, atoms, ridge=1e-3)

    assert torch.isfinite(pc_residual).all()
    assert torch.isfinite(shield_residual).all()
    assert torch.allclose(pc_residual, zero_subspace)
    assert torch.allclose(shield_residual, zero_subspace)


def test_regularized_projection_replay_matches_closed_form_decay():
    weight = torch.tensor([[4.0, 3.0]], dtype=torch.float64)
    direction = torch.tensor([1.0, 0.0], dtype=torch.float64)
    regularization = 0.25
    scale = 1.0 - regularization

    first = project_weight_against_direction(weight, direction, regularization=regularization)
    second = project_weight_against_direction(first.weight, direction, regularization=regularization)

    assert torch.allclose(first.weight[:, 0], weight[:, 0] * regularization)
    assert torch.allclose(second.weight[:, 0], weight[:, 0] * math.pow(regularization, 2))
    assert torch.allclose(first.weight, _reference_projection(weight, direction, scale))


def test_abliteration_pipeline_math_wrappers_delegate_to_contract_helpers():
    subspace = torch.tensor([[1.0, 0.0], [1.0, 1.0]], dtype=torch.float64)
    harmless = torch.tensor([[-2.0, 0.0], [0.0, 0.0], [2.0, 0.0]], dtype=torch.float64)
    atoms = torch.tensor([[1.0, 0.0]], dtype=torch.float64)
    coeff = torch.tensor([[0.1], [3.0], [-2.0]], dtype=torch.float64)

    assert torch.allclose(
        AbliterationPipeline._orthogonalize_subspace(subspace),
        orthogonalize_subspace_rows(subspace),
    )
    assert torch.allclose(
        AbliterationPipeline(None)._remove_harmless_principal_components(
            subspace,
            harmless,
            1,
        ),
        remove_harmless_principal_components(subspace, harmless, 1),
    )
    assert torch.allclose(
        AbliterationPipeline(None)._residualize_against_shield_atoms(subspace, atoms, 1e-6),
        residualize_against_shield_atoms(subspace, atoms, 1e-6),
    )
    assert torch.equal(
        AbliterationPipeline._select_projection_coefficients(coeff, 0.5),
        select_projection_coefficients(coeff, 0.5),
    )


def test_project_out_advanced_replaces_quantized_weight_after_successful_projection(monkeypatch):
    linear = torch.nn.Linear(2, 2, bias=False)
    with torch.no_grad():
        linear.weight.copy_(torch.tensor([[2.0, 0.0], [0.0, 2.0]]))
    module = SimpleNamespace(o_proj=linear)
    replacement_calls = []

    monkeypatch.setattr(
        AbliterationPipeline,
        "_dequantize_weight",
        staticmethod(lambda proj: (proj.weight.data, True)),
    )
    monkeypatch.setattr(
        AbliterationPipeline,
        "_replace_quantized_weight",
        staticmethod(lambda proj, weight: replacement_calls.append((proj, weight.clone()))),
    )

    count = AbliterationPipeline._project_out_advanced(
        module,
        torch.tensor([1.0, 0.0]),
        ["o_proj"],
    )

    assert count == 1
    assert len(replacement_calls) == 1
    assert replacement_calls[0][0] is linear
    assert torch.allclose(replacement_calls[0][1][:, 0], torch.zeros(2))


def test_quantized_parameter_wrapper_uses_declared_markers_only():
    params4bit = type("Params4bit", (), {})()
    ordinary = SimpleNamespace()
    marked = SimpleNamespace(quant_state=object())

    assert AbliterationPipeline._is_quantized_param(params4bit) is True
    assert AbliterationPipeline._is_quantized_param(marked) is True
    assert AbliterationPipeline._is_quantized_param(ordinary) is False


def test_dequantize_weight_returns_float_storage_without_copying():
    linear = torch.nn.Linear(2, 2, bias=False)

    weight, requires_replacement = AbliterationPipeline._dequantize_weight(linear)

    assert weight.data_ptr() == linear.weight.data.data_ptr()
    assert requires_replacement is False


def test_dequantize_weight_promotes_integer_storage_for_safe_projection():
    module = SimpleNamespace(
        weight=torch.nn.Parameter(
            torch.tensor([[1, 2], [3, 4]], dtype=torch.uint8),
            requires_grad=False,
        ),
    )

    weight, requires_replacement = AbliterationPipeline._dequantize_weight(module)

    assert weight.dtype is torch.float32
    assert torch.equal(weight, torch.tensor([[1.0, 2.0], [3.0, 4.0]]))
    assert requires_replacement is True


def test_packed_module_dequantization_is_cloned_before_projection():
    packed_type = type("QuantLinear", (), {})
    packed = packed_type()
    source = torch.tensor([[1.0, 2.0]])
    packed.dequantize = lambda: source

    weight, requires_replacement = AbliterationPipeline._dequantize_weight(packed)

    assert torch.equal(weight, source)
    assert weight.data_ptr() != source.data_ptr()
    assert requires_replacement is True


def test_packed_module_without_safe_dequantizer_fails_closed():
    packed_type = type("WQLinear_GEMM", (), {})
    packed = packed_type()
    packed.qweight = torch.ones(1, dtype=torch.int32)
    packed.scales = torch.ones(1)

    with pytest.raises(RuntimeError, match=r"no dequantize\(\) method available"):
        AbliterationPipeline._dequantize_weight(packed)


def test_weight_replacement_preserves_float_parameter_contract():
    linear = torch.nn.Linear(2, 2, bias=False)
    modified = torch.tensor([[1.0, 2.0], [3.0, 4.0]])

    AbliterationPipeline._replace_quantized_weight(linear, modified)

    assert torch.equal(linear.weight, modified)
    assert linear.weight.requires_grad is True


def test_packed_weight_replacement_uses_the_module_repacker():
    packed_type = type("QuantLinear", (), {})
    packed = packed_type()
    packed.qweight = torch.ones(1)
    packed.scales = torch.tensor([0.5])
    calls = []
    packed.pack = lambda weight, scales: calls.append((weight.clone(), scales.clone()))
    modified = torch.tensor([[1.0, 2.0]])

    AbliterationPipeline._replace_quantized_weight(packed, modified)

    assert len(calls) == 1
    assert torch.equal(calls[0][0], modified)
    assert torch.equal(calls[0][1], packed.scales)


def test_packed_weight_replacement_without_repacker_or_weight_fails_closed():
    packed_type = type("QuantLinear", (), {})
    packed = packed_type()
    packed.qweight = torch.ones(1)
    packed.scales = torch.tensor([0.5])

    with pytest.raises(RuntimeError, match="cannot be re-packed or materialized"):
        AbliterationPipeline._replace_quantized_weight(
            packed,
            torch.tensor([[1.0, 2.0]]),
        )


def test_packed_weight_replacement_materializes_float_when_weight_is_writable():
    packed_type = type("QuantLinear", (), {})
    packed = packed_type()
    packed.qweight = torch.ones(1)
    packed.weight = torch.nn.Parameter(
        torch.ones((1, 2), dtype=torch.uint8),
        requires_grad=False,
    )
    modified = torch.tensor([[1.5, 2.5]])

    with pytest.warns(UserWarning, match="Storing as float weight"):
        AbliterationPipeline._replace_quantized_weight(packed, modified)

    assert packed.weight.dtype is torch.float32
    assert torch.equal(packed.weight, modified)
    assert packed.weight.requires_grad is False


def test_layer_norm_capture_uses_logical_float_weights_and_deduplicates_ties(monkeypatch):
    layer = torch.nn.Module()
    layer.first = torch.nn.Linear(2, 2, bias=False)
    layer.second = torch.nn.Linear(2, 2, bias=False)
    shared = torch.nn.Parameter(
        torch.tensor([[1, 2], [3, 4]], dtype=torch.uint8),
        requires_grad=False,
    )
    layer.first.weight = shared
    layer.second.weight = shared
    calls = []

    def dequantize(module):
        calls.append(module)
        return module.weight.data.float(), True

    monkeypatch.setattr(
        AbliterationPipeline,
        "_dequantize_weight",
        staticmethod(dequantize),
    )

    norms = AbliterationPipeline._capture_layer_weight_norms(layer)

    assert norms == {"first.weight": pytest.approx(torch.tensor([1, 2, 3, 4]).float().norm().item())}
    assert calls == [layer.first]


def test_integer_norm_restoration_materializes_float_and_preserves_tied_identity():
    layer = torch.nn.Module()
    layer.first = torch.nn.Linear(2, 2, bias=False)
    layer.second = torch.nn.Linear(2, 2, bias=False)
    shared = torch.nn.Parameter(torch.ones((2, 2), dtype=torch.uint8), requires_grad=False)
    layer.first.weight = shared
    layer.second.weight = shared
    target_norm = shared.data.float().norm().item() * 1.05

    AbliterationPipeline._restore_layer_weight_norms(
        layer,
        {"first.weight": target_norm},
    )

    assert layer.first.weight is shared
    assert layer.second.weight is shared
    assert shared.dtype is torch.float32
    assert shared.norm().item() == pytest.approx(target_norm, rel=1e-6)
    assert torch.equal(shared, torch.full((2, 2), 1.05))


def test_quantized_norm_restoration_uses_dequantize_and_replacement_seams(monkeypatch):
    layer = torch.nn.Module()
    layer.proj = torch.nn.Linear(2, 2, bias=False)
    layer.tied = torch.nn.Linear(2, 2, bias=False)
    layer.proj.weight.quant_state = object()
    layer.tied.weight = layer.proj.weight
    logical = torch.tensor([[1.0, 0.0], [0.0, 1.0]])
    replacements = []

    monkeypatch.setattr(
        AbliterationPipeline,
        "_dequantize_weight",
        staticmethod(lambda _module: (logical.clone(), True)),
    )
    monkeypatch.setattr(
        AbliterationPipeline,
        "_replace_quantized_weight",
        staticmethod(
            lambda module, weight: (
                replacements.append((module, weight.clone())),
                setattr(module, "weight", torch.nn.Parameter(weight.clone())),
            )
        ),
    )

    AbliterationPipeline._restore_layer_weight_norms(
        layer,
        {"proj.weight": logical.norm().item() * 0.5},
    )

    assert len(replacements) == 1
    assert replacements[0][0] is layer.proj
    assert replacements[0][1].norm().item() == pytest.approx(logical.norm().item() * 0.5)
    assert layer.proj.weight is layer.tied.weight


def test_float_norm_restoration_scales_weight_in_place():
    layer = torch.nn.Module()
    layer.proj = torch.nn.Linear(2, 2, bias=False)
    with torch.no_grad():
        layer.proj.weight.fill_(1.0)
    identity = layer.proj.weight

    AbliterationPipeline._restore_layer_weight_norms(
        layer,
        {"proj.weight": 1.0},
    )

    assert layer.proj.weight is identity
    assert layer.proj.weight.norm().item() == pytest.approx(1.0)


def test_norm_restoration_skips_degenerate_logical_weight():
    layer = torch.nn.Module()
    layer.proj = torch.nn.Linear(2, 2, bias=False)
    with torch.no_grad():
        layer.proj.weight.zero_()
    identity = layer.proj.weight

    AbliterationPipeline._restore_layer_weight_norms(
        layer,
        {"proj.weight": 2.0},
    )

    assert layer.proj.weight is identity
    assert torch.count_nonzero(layer.proj.weight).item() == 0
