"""Literal witness replay, exact LP alternatives, and strict boundary checks."""
import unittest
from fractions import Fraction as F
from itertools import product
import numpy as np
from example import (TriangleInterface,TrianglePair,Reaction,CoreFamily,Motif,
    StrictLinearFeasibility,toric_family,direction_family,dominance_family)


class CompatibilityTests(unittest.TestCase):
    def test_exact_phase_boundary_and_witness(self):
        for k in (F(1,10),F(1,2),F(501,1000),F(1),F(1999,1000),F(2),F(10)):
            pair=TrianglePair((1,1,1,k,k));w=pair.witness()
            self.assertEqual(w is not None,F(1,2)<k<2)
            decision=pair.family.filters()['independent_complex']
            self.assertEqual(decision.status,'feasible' if w else 'infeasible')
            if w:
                a,b,c,d=map(F,w['activities'])
                # Independent literal equations, not the incidence/matrix path.
                currents=(a-b,b-c,c-a*a,k*(b-d),k*(d-a*a))
                j,j1,j2,j3,j4=currents
                self.assertEqual(tuple(map(F,w['currents'])),currents)
                self.assertTrue(all(v>0 for v in (2*j2-j,j-j1,j1-j2,2*j4-j,j-j3,j3-j4)))
                self.assertLess(F(w['whole_network_production'][1]),0) # shared consumption is counted only once globally

    def test_arbitrary_factors_and_common_gain(self):
        for b1,b2,b3,b4 in product((F(1,3),F(2)),repeat=4):
            for gain in (2,3,5):
                pair=TrianglePair((F(7,3),b1,b2,b3,b4),gain);w=pair.witness(F(2,3))
                ratio=(1/b3+1/b4)/(1/b1+1/b2)
                self.assertEqual(w is not None,F(1,gain)<ratio<gain)
                if w: self.assertTrue(pair.family.evaluate(list(map(F,w['activities'])))['strictly_productive'])
        original=TrianglePair((1,2,3,4,5)).witness()
        scaled=TrianglePair((7,14,21,28,35)).witness()
        self.assertEqual(original['activities'],scaled['activities'])
        self.assertEqual([7*F(x) for x in original['currents']],list(map(F,scaled['currents'])))

    def test_interface_projection_reconstruction(self):
        for gain in (F(3,2),F(2),F(4)):
            interface=TriangleInterface(gain,F(2),F(3),F(5));lo,hi=interface.interval
            for weight in (F(1,1000),F(1,2),F(999,1000)):
                q=lo+(hi-lo)*weight;j,u,v=interface.currents(q)
                self.assertTrue(1/gain<v<u<1)
                self.assertEqual(interface.shared*(u/interface.first+v/interface.second),q)
            for q in (lo,hi):
                with self.assertRaises(ValueError): interface.currents(q)

    def test_layers_and_paper_witnesses(self):
        direction=direction_family();filters=direction.filters()
        self.assertEqual([d.status for d in filters.values()],['feasible','infeasible','feasible'])
        self.assertEqual(tuple(direction.balance_rows()@(6,5,4,7,6,7)),(1,)*8)
        self.assertTrue(all(v==0 for v in direction.direction_rows().sum(axis=0)))
        magnitude=TrianglePair((1,1,1,10,10)).family
        self.assertEqual([d.status for d in magnitude.filters().values()],['feasible','feasible','infeasible'])
        toric=toric_family();self.assertTrue(all(d.status=='feasible' for d in toric.filters().values()))
        self.assertEqual(tuple(toric.balance_rows()@(F(1),F(3,4),F(1,2))),(F(1,2),F(1,4),F(1,2),F(1,2)))
        for a in (F(1,1000),F(1,2),F(999,1000)):
            lower=(a+2*a*a)/3;upper=(a+2*a**3)/3
            self.assertEqual(lower-upper,2*a*a*(1-a)/3);self.assertGreater(lower,upper)
        self.assertEqual(len(dominance_family()),441)

    def test_pac_minimality_and_signed_currents(self):
        for family in (direction_family(),toric_family(),TrianglePair((1,1,1,10,10)).family):
            for motif in family.motifs: self.assertTrue(family.audit_pac(motif)['is_pac'])
        # A reverse-oriented two-reaction core needs negative currents for productivity.
        family=CoreFamily(('A','B'),[Reaction((0,1),(1,0),1),Reaction((2,0),(0,1),1)],
            [Motif((0,1),(0,1))],orientation=(-1,-1))
        self.assertTrue(family.audit_pac(family.motifs[0])['is_pac'])
        self.assertTrue(all(v<0 for v in family.filters()['productive_flow'].vector))

    def test_linear_certificates_and_invalid_inputs(self):
        rows=np.array([[1,-1],[-1,1]],dtype=object)
        result=StrictLinearFeasibility.solve(rows)
        self.assertEqual(result.status,'infeasible');self.assertTrue(all(v==0 for v in rows.T@result.vector))
        self.assertGreater(sum(result.vector),0);self.assertTrue(all(v>=0 for v in result.vector))
        with self.assertRaises(ValueError): TrianglePair((1,1,0,1,1))
        with self.assertRaises(ValueError): TrianglePair((1,1,1,1,1),1)
        with self.assertRaises(ValueError): TrianglePair((1,1,1,1,1)).witness(1)
        with self.assertRaises(ValueError): Reaction((F(1,2),0),(0,1),1)
        with self.assertRaises(ValueError): toric_family().currents([0,1])


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