File size: 7,462 Bytes
9d6c005
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import unittest,itertools,tempfile
from pathlib import Path
import numpy as np
from aureole.certificates import CertificateMemory,visibility_certificate,enclosure
from aureole.innovation import prepare,eliminate,exact_risk_two,covariance
from aureole.renderer import Scene,receiver_grid,light_grid


class InnovationTests(unittest.TestCase):
    def test_covariance_contraction_all_readouts(self):
        rng=np.random.default_rng(121)
        for _ in range(200):
            q=rng.dirichlet(np.ones(9));r=rng.normal(size=(9,5))
            a=np.arange(3);u=np.arange(3,9);mass=q[a].sum()
            old=covariance(r,q)
            new=covariance(r[u],q[u]/(1-mass))
            self.assertGreaterEqual(np.linalg.eigvalsh((1-mass)*old-new).min(),-1e-10)

    def test_exact_two_draw_enumeration(self):
        rng=np.random.default_rng(12)
        for k in (3,4,6):
            q=rng.dirichlet(np.ones(k));f=rng.uniform(size=(k,3));h=rng.normal(size=(k,3))
            outcomes=[];weights=[]
            for a,b in itertools.permutations(range(k),2):
                y1=h.sum(0)+(f[a]-h[a])/q[a]
                h2=h.copy();h2[a]=f[a]
                y2=h2.sum(0)+(f[b]-h2[b])/(q[b]/(1-q[a]))
                outcomes.append((y1+y2)/2);weights.append(q[a]*q[b]/(1-q[a]))
            outcomes=np.array(outcomes);weights=np.array(weights)
            np.testing.assert_allclose(weights@outcomes,f.sum(0),atol=1e-12)
            risk=np.sum(weights[:,None]*(outcomes-f.sum(0))**2)/3
            self.assertAlmostEqual(risk,exact_risk_two(f[None],h[None],q[None])[0],places=11)

    def test_exact_completion_fixed_budget(self):
        f=np.arange(9,dtype=float).reshape(1,3,3)
        used=[]
        def oracle(rows,j):used.extend(j.tolist());return f[rows,j]
        y,_=eliminate(np.zeros_like(f),np.full((1,3),1/3),oracle,3,np.random.default_rng(3))
        np.testing.assert_array_equal(y,f.sum(1));self.assertEqual(len(set(used)),3)

    def test_no_rays_on_complete_state(self):
        h=np.ones((4,3,2))
        def oracle(*args):raise AssertionError('Should not trace')
        y,steps=eliminate(h,np.zeros((4,3)),oracle,2,np.random.default_rng(3))
        np.testing.assert_array_equal(y,h.sum(1));self.assertEqual(steps,[])

    def test_false_zero_support_rejected_by_audit(self):
        with self.assertRaises(ValueError):exact_risk_two(np.ones((1,3,1)),np.zeros((1,3,1)),np.array([[.5,.5,0]]))

    def test_upper_uniform_endpoint_never_selects_zero_support(self):
        class UpperRng:
            def random(self,n):return np.full(n,np.nextafter(1.0,0.0))
        q=np.zeros((1000,7));q[:,:5]=np.random.default_rng(41).dirichlet(np.ones(5),size=1000)
        def oracle(rows,j):
            self.assertTrue(np.all(j<5));return np.ones((len(rows),1))
        result,steps=eliminate(np.zeros((1000,7,1)),q,oracle,2,UpperRng())
        self.assertTrue(np.isfinite(result).all())
        self.assertTrue(np.all(steps[0][1]!=steps[1][1]))

    def test_known_only_control_and_support(self):
        b=np.ones((1,4,3));v=np.array([[0.,1.,0.,0.]])
        h,q=prepare(b,np.full((1,4),.6),v,np.array([[True,True,False,False]]),np.ones((1,4)))
        np.testing.assert_array_equal(q,[[0,0,.5,.5]])
        np.testing.assert_allclose(h[0,:,0],[0,1,.6,.6])

    def test_invalid_budget_and_proposal(self):
        with self.assertRaises(ValueError):eliminate(np.ones((1,3,1)),np.full((1,3),1/3),lambda *x:None,True,np.random.default_rng())
        with self.assertRaises(ValueError):eliminate(np.ones((1,3,1)),np.array([[0,.1,.1]]),lambda *x:None,1,np.random.default_rng())

    def test_float_oracle_matches_independent_quadratic(self):
        rng=np.random.default_rng(987)
        for sid in range(800,820):
            scene=Scene.create(sid)
            p=rng.uniform([-1,-1,0],[1,1,0],(200,3));l=rng.uniform([-.9,-.9,2.2],[.9,.9,2.2],(200,3))
            v,_=visibility_certificate(scene,p,l)
            np.testing.assert_array_equal(v,scene.visibility(p,l))

    def test_moving_and_spatial_certificates_no_false_acceptance(self):
        rng=np.random.default_rng(982)
        for sid in range(820,830):
            scene=Scene.create(sid);p=receiver_grid(8,12);l=light_grid(4)
            mem=CertificateMemory(p,l,scene.spheres)
            ids=np.repeat(np.arange(len(p)),len(l));j=np.tile(np.arange(len(l)),len(p))
            v,m=visibility_certificate(scene,p[ids],l[j]);mem.commit(ids,j,v,m)
            for _ in range(12):
                g=scene.spheres.copy();g[:,:3]+=rng.uniform(-.035,.035,(3,3));g[:,3]*=rng.uniform(.96,1.04,3)
                moved=Scene(sid,g);mem.begin_geometry(g)
                query=p+rng.uniform(-.012,.012,p.shape);query[:,2]=0
                values,known=mem.lookup(np.arange(len(p)),query)
                truth=moved.visibility(query[:,None,:],l[None,:,:])
                self.assertTrue(np.all(values[known]==truth[known]))

    def test_unreported_motion_is_not_protected(self):
        scene=Scene.create(833);p=receiver_grid(16,20);l=light_grid(6)
        mem=CertificateMemory(p,l,scene.spheres)
        ids=np.repeat(np.arange(len(p)),len(l));j=np.tile(np.arange(len(l)),len(p))
        v,m=visibility_certificate(scene,p[ids],l[j]);mem.commit(ids,j,v,m)
        values,known=mem.lookup(np.arange(len(p)))
        changed=scene.changed().visibility(p[:,None,:],l[None,:,:])
        self.assertGreater(np.count_nonzero(known & (values!=changed)),0)

    def test_epoch_resets_while_margin_can_survive(self):
        s=Scene.create(20);p=receiver_grid(4,5);l=light_grid(2)
        a=CertificateMemory(p,l,s.spheres);b=CertificateMemory(p,l,s.spheres,mode='epoch')
        ids=np.arange(len(p));j=np.zeros(len(p),int);v,m=visibility_certificate(s,p,l[j])
        for mem in (a,b):mem.commit(ids,j,v,m)
        g=s.spheres.copy();g[0,0]+=1e-7
        for mem in (a,b):mem.begin_geometry(g)
        self.assertGreater(a.lookup(ids)[1].sum(),0);self.assertEqual(b.lookup(ids)[1].sum(),0)

    def test_enclosure_covers_all_binary_completions(self):
        b=np.arange(1,10,dtype=float).reshape(1,3,3);v=np.array([[1.,0.,0.]]);known=np.array([[True,False,False]])
        lo,hi=enclosure(b,v,known)
        for a,c in itertools.product((0,1),repeat=2):
            y=(b*np.array([1,a,c])[None,:,None]).sum(1)
            self.assertTrue((y>=lo).all() and (y<=hi).all())

    def test_roundtrip_and_namespace(self):
        s=Scene.create(31);p=receiver_grid(3,4);l=light_grid(2)
        a=CertificateMemory(p,l,s.spheres,'alpha');ids=np.arange(len(p));j=np.zeros(len(p),int)
        v,m=visibility_certificate(s,p,l[j]);a.commit(ids,j,v,m)
        with tempfile.TemporaryDirectory() as d:
            path=Path(d)/'memory.npz';a.save(path);b=CertificateMemory.load(path,'alpha')
            np.testing.assert_array_equal(a.lookup(ids)[1],b.lookup(ids)[1])
            with self.assertRaises(ValueError):CertificateMemory.load(path,'beta')

    def test_motion_budget_is_conservative_on_return(self):
        s=Scene.create(12);a=CertificateMemory(receiver_grid(2,2),light_grid(2),s.spheres)
        g=s.spheres.copy();g[0,0]+=.2;a.begin_geometry(g);a.begin_geometry(s.spheres)
        self.assertGreaterEqual(a.budget,.4-1e-14)

    def test_readout_cross_covariance_is_psd(self):
        rng=np.random.default_rng(22);r=rng.normal(size=(7,12));q=rng.dirichlet(np.ones(7))
        self.assertGreaterEqual(np.linalg.eigvalsh(covariance(r,q)).min(),-1e-10)


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