"""Independent scientific checks; no saved PASS flags or copied output fixtures."""
import unittest
from dataclasses import replace
from fractions import Fraction as F
import numpy as np
from scipy.optimize import root
from example import (nominal, exact_paper_audit, declared_parameter_box,
    ServiceDesign, QuadraticResponse, InhibitedSource, NonmonotoneExample)


class ScientificChecks(unittest.TestCase):
    def test_exact_thresholds_and_continuum_box(self):
        a=exact_paper_audit();self.assertLess(float(a['scale_bracket'][1]-a['scale_bracket'][0]),2e-8)
        b=declared_parameter_box();self.assertAlmostEqual(float(b['scale']),.125895729,places=9)
        M=nominal(True);r=M.gpx.response
        lo,hi=r.enclose(a['XT']);self.assertLessEqual(r.polynomial(a['XT'],lo),0);self.assertGreater(r.polynomial(a['XT'],hi),0)

    def test_reconstruction_against_full_nonlinear_root(self):
        M=nominal();d=M.design.decision((10,4));x=d['L'];u=M.state(x)
        # Full 8D residual solved independently, from a perturbed reconstructed state.
        guess=u.copy();guess[[0,1,4]]*=[1.1,1.01,.99]
        solved=root(lambda v:M.rhs(0,v,d['minimum']),guess,jac=lambda v:M.jacobian(v,d['minimum']),tol=1e-10)
        self.assertTrue(solved.success);np.testing.assert_allclose(solved.x,u,atol=1e-9,rtol=1e-9)
        s=M.full(u);self.assertAlmostEqual(s['g']+2*s['z']+s['e2'],M.gpx.G)
        self.assertAlmostEqual(s['e0']+s['e1']+s['e2'],M.gpx.E)
        self.assertAlmostEqual(s['r']+s['h']+s['w']+s['v'],M.trx.E)
        self.assertAlmostEqual(s['y']+s['zT'],M.trx.T)
        # Bound intermediate is real material; omitting it is not the same model.
        self.assertGreater(M.gpx.G-(s['g']+2*s['z']),.002)
        c=M.currents(u,d['minimum']);self.assertAlmostEqual(c['phi0'],c['psiG'],places=10)
        self.assertAlmostEqual(c['thetaY'],c['psiT'],places=10)

    def test_separate_joint_overdelivery_plateau_and_ceiling(self):
        M=nominal();D=M.design;d=D.decision((10,4));x=D.equilibrium(.1)
        self.assertLess(M.trx.response.current(x),4)
        for r,q in zip(D.responses,(10,4)):
            isolated=ServiceDesign((r,),M.source)
            self.assertGreater(r.current(isolated.equilibrium(.1)),q)
        self.assertAlmostEqual(d['minimum']-d['quota_only'],sum(d['overdelivery'])/M.source.unit_current(d['L']))
        self.assertEqual(D.decision((9,4))['minimum'],d['minimum'])
        self.assertFalse(D.decision((d['ceilings'][0],4))['repairable'])
        near=D.decision((d['ceilings'][0]-1e-6,4));self.assertGreater(near['minimum'],d['minimum']*10)
        x=D.equilibrium(d['minimum']*1.001)
        self.assertGreater(M.trx.response.current(x),4)
        # Three private branches and product-inhibited source use the same contract.
        three=ServiceDesign((*D.responses,D.responses[1]),InhibitedSource(M.source,5))
        dt=three.decision((10,4,2));self.assertGreater(dt['minimum'],d['minimum'])
        self.assertAlmostEqual(three.equilibrium(dt['minimum']),dt['L'],places=10)

    def test_redesign_admissibility_and_optimal_bound(self):
        M=nominal();d=M.design.decision((10,4));candidates=M.gpx.redesign(d['L'],10)
        self.assertEqual(len(candidates),2);good=candidates[0]
        self.assertTrue(good['admissible']);self.assertTrue(candidates[1]['admissible'])
        self.assertGreater(candidates[1]['E'],90000)  # rejected as a smaller-inventory redesign, not as an algebraic state
        self.assertAlmostEqual(good['E'],48.313664,places=6)
        R=replace(M,gpx=replace(M.gpx,E=good['E']));dr=R.design.decision((10,4))
        self.assertAlmostEqual(dr['minimum'],d['quota_only'],places=12)
        self.assertLess(max(abs(R.rhs(0,R.state(d['L']),dr['minimum']))),1e-10)
        # Changing enzyme abundance changes currents; relaxing quota alone does not.
        self.assertLess(R.design.demand(d['L']),M.design.demand(d['L']))
        self.assertGreater(d['quota_only'],.1)

    def test_numerical_dynamics_and_jacobian_signs(self):
        M=nominal();d=M.design.decision((10,4));u=M.state(d['L']);J=M.jacobian(u,d['minimum'])
        self.assertLess(J[2,3],0);self.assertGreater(J[3,2],0)
        self.assertLess(max(np.linalg.eigvals(J).real),0)
        for j in range(8):
            v=np.eye(8)[j]*1e-6
            np.testing.assert_allclose((M.rhs(0,u+v,d['minimum'])-M.rhs(0,u-v,d['minimum']))/2e-6,J[:,j],atol=1e-6,rtol=1e-5)
        u[[1,4]]+=[1,.02]
        states=M.integrate(u,d['minimum'],np.linspace(0,60,121))
        self.assertGreater(min(M.margins(v).min() for v in states),0)
        self.assertLess(min(M.currents(v,d['minimum'])['thetaY'] for v in states),3.7)
        c=M.currents(u,d['minimum']);self.assertGreater(abs(c['phi0']-c['psiG']),1)

    def test_stable_root_inversion_and_invalid_domain(self):
        M=nominal()
        for r in M.design.responses:
            self.assertEqual(r.current(0),0)
            for x in [1e-14,.02,.9,30,1e6]:
                u=r.carrier(x);self.assertGreater(u,0)
                self.assertLess(abs(r.polynomial(x,u)),1e-9*max(1,r.Gamma))
                if x>.01:self.assertAlmostEqual(r.invert(r.current(x))[1]/x,1,places=6)
            self.assertGreater(r.derivative(1),0)
        with self.assertRaises(ValueError): replace(M.gpx,G=1.)
        with self.assertRaises(ValueError): replace(M.trx,T=.075)
        with self.assertRaises(ValueError): replace(M.source,P0=31)
        with self.assertRaises(ValueError): M.design.decision((-1,4))
        with self.assertRaises(ValueError): M.design.robust_command(.1,1)
        with self.assertRaises(ValueError): M.integrate(np.zeros(8),.1,[0,1])
        T=replace(M.trx,b=0);self.assertEqual(T.reconstruct(1)['w'],0)

    def test_nonmonotone_success_has_upper_limit(self):
        C=NonmonotoneExample;lo,hi=C.scale_interval()
        for s in [lo,hi]:self.assertAlmostEqual(C.current(C.equilibrium(s)),.3,places=14)
        self.assertGreater(C.current(C.equilibrium(1)),.3)
        self.assertLess(C.current(C.equilibrium(3)),.3)
        for s in [.1,1,3,100]:
            x=C.equilibrium(s);self.assertTrue(0<x<1)
            self.assertAlmostEqual(s*(1-x),C.current(x),places=12)
            self.assertLess(-s-(1.2-2*x),0)  # scalar local stability, not bistability


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