import unittest
from fractions import Fraction as F
import numpy as np
from reserve import Reserve,ReserveChain,latency_floor,fluid_limit_diagnostic
from mission import AdministrationCourse,HealthyResponse,TargetDriftCertificate,assemble_mission
from population import InheritedPopulation,Action
from example import capacity_scan


class ScientificTests(unittest.TestCase):
    def test_chemical_source_and_robust_drift(self):
        s=InheritedPopulation();Q=s.generator(Action(F(29,100)))
        self.assertTrue(all(sum(row)==0 for row in Q))
        for i,(a,r) in enumerate(s.states):
            self.assertEqual(sum(p for j,k,p in s.pairs[i]),1)
            self.assertTrue(all(s.states[j][0]+s.states[k][0]==a and s.states[j][1]+s.states[k][1]==r for j,k,p in s.pairs[i]))
        d=TargetDriftCertificate().verify();self.assertEqual(d['growth'],F(59,400));self.assertEqual(d['decay'],F(171,2000))
        # Independent row perturbations: choose every factor to maximize Aw.
        w=tuple(map(F,(14,11,10,84,43,107)));e=F(29,100);q=s.generator(Action(e-F(1,100)))
        ratios,env=TargetDriftCertificate().rows(e)
        for i in range(6):
            chemical=sum(q[i][j]*(w[j]-w[i])*(1+F(1,100)*(1 if w[j]>=w[i] else -1)) for j in range(6) if j!=i)
            offspring=sum(p*(w[j]+w[k]) for j,k,p in s.pairs[i])-w[i]
            worst=chemical+s.birth[i]*offspring*(1+F(1,100)*(1 if offspring>=0 else -1))-s.death[i]*w[i]*F(99,100)
            self.assertEqual(worst/w[i],ratios[i]+F(1,100)*env[i]);self.assertLessEqual(worst/w[i],-d['decay'])

    def test_shared_delivery_and_target_mission(self):
        c=AdministrationCourse();band=c.band_certificate();self.assertGreater(band['plateau_lower'],F(28,100));self.assertLess(band['concentration_upper'],F(3,10))
        self.assertLess(HealthyResponse().ceiling(band['concentration_upper']),F(61,200))
        self.assertEqual(HealthyResponse(F(2),F(0)).mortality(0),.15)
        t=TargetDriftCertificate().certify_course(c,[0,0,0,0,0,4]);self.assertEqual(t['exponent'],F(2161,250));self.assertEqual(t['published_coarsening'],F(107,12500));self.assertLess(t['exact_rational_upper'],F(76,10000))
        for k in [.99,1,1.01]:
            self.assertGreaterEqual(float(c.concentration(4,k)),float(band['plateau_lower']))
            self.assertLess(float(c.concentration(120,k)),float(c.concentration(112,k)))
        m=assemble_mission(F(1,100),F(1,50));self.assertEqual(m['joint_success_lower'],F(97,100))
        with self.assertRaises(ValueError):TargetDriftCertificate().certify_course(AdministrationCourse(rate=F(1,10)),[0,0,0,0,0,4])

    def test_scale_function_solves_hitting_equations(self):
        R=Reserve(12,5,F(3,2),F(3,10));C=R.anchored(9,20)
        q=lambda h:C.initial_failure_before_return(h)
        self.assertEqual(q(4),1);self.assertEqual(q(9),0)
        for h in range(5,9):
            birth=R.renewal*h*(1-F(h,R.capacity));death=R.mortality_ceiling*h
            self.assertEqual(birth*(q(h+1)-q(h))+death*(q(h-1)-q(h)),0)
        self.assertEqual(q(8),C.excursion)

    def test_preparation_and_capacity(self):
        c=Reserve(400,200,1,F(61,200)).anchored(278,120)
        self.assertLess(c.evaluate(278)['simple_product_bound'],F(7,10**6));self.assertLess(c.evaluate(209)['sharp_bound'],F(1,100));self.assertGreater(c.evaluate(208)['sharp_bound'],F(1,100))
        self.assertGreaterEqual(c.random_preparation({199:F(1,100),279:F(99,100)}),F(1,100))
        rows=capacity_scan();self.assertEqual(next(K for K,h,M,s,p in rows if s<=F(1,100)),232);self.assertEqual(next(K for K,h,M,s,p in rows if p<=F(1,100)),252)

    def test_killed_chain_counts_returns_as_failures(self):
        R=Reserve(8,4,F(2),F(1,2));chain=ReserveChain(R)
        t,path,info=chain.solve(6,4,samples=21);_,terminal,_=chain.solve(6,4,samples=21,killed=False)
        exact=float(chain.high_precision_constant_risk(6,F(4)))
        self.assertAlmostEqual(path[-1],exact,places=8);self.assertGreater(path[-1],terminal[-1]);self.assertGreaterEqual(np.diff(path).min(),-1e-12);self.assertLess(info['mass_residual'],1e-8)
        self.assertLess(exact,float(R.anchored(6,4).evaluate(6)['sharp_bound']))

    def test_buffer_exponent_and_boundaries(self):
        for K,h,coefficient in [(4,3,F(8)),(4,2,F(8)),(5,2,F(625,48))]:
            R=Reserve(K,h,F(16),F(1,2));self.assertEqual(R.high_renewal_coefficient(2),coefficient)
            self.assertEqual(R.anchored(K,2).evaluate(K)['simple_product_bound'],min(F(1),coefficient/F(16)**(K-h)))
            self.assertLess(float(ReserveChain(R).high_precision_constant_risk(K,F(2))),float(coefficient/F(16)**(K-h)))
        self.assertGreater(latency_floor(20,10,F(1,2)),F(41,100))
        with self.assertRaises(ValueError):Reserve(4,4,1,F(1,2)).high_renewal_coefficient(2)
        self.assertEqual(Reserve(400,200,1,F(3,5)).capacity_envelope(F(1,2),120)['status'],'outside_capacity_law_regime')
        self.assertEqual(fluid_limit_diagnostic(1,F(3,5),F(1,2),8,400)['status'],'fluid_failure_regime')

    def test_invalid_inputs_do_not_issue_certificates(self):
        for args in [(10,11,1,1),(10,0,1,1),(10,5,0,1)]:
            with self.assertRaises(ValueError):Reserve(*args)
        c=Reserve(10,5,1,1).anchored(7,2)
        with self.assertRaises(ValueError):c.evaluate(11)
        with self.assertRaises(ValueError):c.random_preparation({7:F(1,2)})
        with self.assertRaises(ValueError):AdministrationCourse(stop=121)
        with self.assertRaises(ValueError):TargetDriftCertificate(relative_error=F(1,10)).verify()


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