"""Qualification controls for the finite-source PF-001 model prototype."""

import copy
import math
import unittest

from qualification import analytic, assess, cloud, evaluate


class QualificationTests(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.coarse = evaluate(3, 24, 8)
        cls.fine = evaluate(5, 48, 16)

    def test_finite_source_work_and_reaction_match_separate_flux_path(self):
        self.assertEqual(assess(self.fine), [])
        for case in self.fine["cases"].values():
            self.assertLess(abs(case["source_work"] - case["radiated_power"]), 1e-4)
            self.assertLess(abs(case["source_reaction_z"] - case["radiation_recoil_z"]), 1e-4)

    def test_phase_and_channel_controls(self):
        cases = self.fine["cases"]
        self.assertGreater(cases["forward"]["source_reaction_z"], 0)
        self.assertAlmostEqual(cases["forward"]["source_reaction_z"], -cases["reverse"]["source_reaction_z"], places=12)
        for name in ("in_phase", "opposed", "orthogonal"):
            self.assertAlmostEqual(cases[name]["source_reaction_z"], 0, places=12)
        self.assertNotAlmostEqual(cases["in_phase"]["source_work"], cases["opposed"]["source_work"])

    def test_refinement_moves_towards_continuum_control(self):
        for quantity, control in (("radiated_power", "continuum_power"), ("radiation_recoil_z", "continuum_recoil_z")):
            coarse = self.coarse["cases"]["forward"]
            fine = self.fine["cases"]["forward"]
            self.assertLess(abs(fine[quantity] - fine[control]), abs(coarse[quantity] - coarse[control]))

    def test_analytic_zero_and_reversal_limits(self):
        self.assertAlmostEqual(analytic(0, 1)[1], 0, places=12)
        self.assertAlmostEqual(analytic(-math.pi / 2, 0)[1], 0, places=12)
        self.assertAlmostEqual(analytic(-math.pi / 2, 1)[1], -analytic(math.pi / 2, 1)[1], places=12)
        power, force = analytic(-math.pi / 2, 1)
        self.assertAlmostEqual(force / power, 4 / math.pi**2, places=12)
        self.assertLessEqual(abs(force), power)

    def test_bounds_and_mutations_fail_closed(self):
        for size in (0, 2, 11):
            with self.assertRaises(ValueError):
                cloud(size, 0)
        with self.assertRaises(ValueError):
            evaluate(3, 23, 8)
        with self.assertRaises(ValueError):
            evaluate(3, 24, 49)
        for mutate in (
            lambda run: run["cases"].pop("orthogonal"),
            lambda run: run["cases"]["forward"].pop("radiated_power"),
            lambda run: run["cases"]["forward"].update(source_work=math.nan),
            lambda run: run["cases"]["forward"].update(radiated_power=0),
            lambda run: run["cases"]["forward"].update(source_reaction_z=-0.03),
        ):
            broken = copy.deepcopy(self.fine)
            mutate(broken)
            self.assertTrue(assess(broken))


if __name__ == "__main__":
    unittest.main()
