- 新增 irstd 核心包、scripts、tests、configs - 新增研究文档:literature_review / mathematical_analysis / novelty_matrix / experiment_plan / research_decision / research_log / manuscript_outline - 新增 evidence、results 数据与审计产物 - 新增 pyproject.toml、requirements、.gitignore
95 lines
5.0 KiB
Python
95 lines
5.0 KiB
Python
import itertools,unittest,math
|
|
import numpy as np
|
|
from irstd.core import *
|
|
|
|
class CoreTests(unittest.TestCase):
|
|
def test_matching_not_greedy(self):
|
|
self.assertEqual(len(matching([[1,1],[1,0]])),2)
|
|
def test_empty_matching(self):
|
|
self.assertEqual(matching(np.zeros((0,2),bool)),[])
|
|
self.assertEqual(matching(np.zeros((2,0),bool)),[])
|
|
def test_adjacent_targets_conflict(self):
|
|
_,selected=exact_mwis([.9,.8],[(0,1)])
|
|
self.assertEqual(len(selected),1)
|
|
self.assertEqual(len(exact_mwis([.9,.8],[])[1]),2)
|
|
def test_invalid_recall_even_with_feasible_target(self):
|
|
self.assertEqual(exact_mwis([1.,2.],[(0,1)])[1],[1])
|
|
def test_pixel_threshold_splits_false_object(self):
|
|
p=np.array([[.9,.6,.9]])
|
|
self.assertEqual(len(components(p>=.5)),1)
|
|
self.assertEqual(len(components(p>=.8)),2)
|
|
def test_matching_deficiency_exhaustive(self):
|
|
# Exhaust all 3x2 eligibility graphs and all subsets.
|
|
for flat in itertools.product([0,1],repeat=6):
|
|
e=np.array(flat).reshape(3,2)
|
|
for bits in itertools.product([0,1],repeat=3):
|
|
s=np.flatnonzero(bits);fp=len(s)-len(matching(e[s]))
|
|
for j in set(range(3))-set(s):
|
|
t=np.append(s,j);self.assertLessEqual(fp,len(t)-len(matching(e[t])))
|
|
def test_canonical_bound_exhaustive(self):
|
|
for flat in itertools.product([0,1],repeat=6):
|
|
e=np.array(flat).reshape(3,2);pos={i for i,_ in matching(e)}
|
|
for bits in itertools.product([0,1],repeat=3):
|
|
s=np.flatnonzero(bits)
|
|
self.assertLessEqual(len(s)-len(matching(e[s])),len(set(s)-pos))
|
|
def test_surrogate_upper_bound(self):
|
|
s=np.array([-100,-1,0,1,100.])
|
|
self.assertGreaterEqual(softplus_bound(s,np.ones(5,bool)),(s>=0).sum())
|
|
def test_cp_zero_and_all(self):
|
|
self.assertAlmostEqual(cp_upper(0,100,.05),1-.05**.01)
|
|
self.assertEqual(cp_upper(100,100,.05),1.)
|
|
def test_small_calibration_abstains(self):
|
|
r=calibrate(np.zeros((80,101),int),np.linspace(0,1,101),64,alpha=.01,risk='any')
|
|
self.assertTrue(r['reject_all'])
|
|
def test_large_zero_calibration_certifies_any(self):
|
|
r=calibrate(np.zeros((1000,2),int),[.1,.9],64,alpha=.01,risk='any')
|
|
self.assertFalse(r['reject_all']);self.assertEqual(r['threshold'],.1)
|
|
def test_count_and_any_distinct(self):
|
|
counts=np.zeros((1000,2),int)
|
|
self.assertTrue(calibrate(counts,[.1,.9],64,.01,risk='count')['reject_all'])
|
|
self.assertFalse(calibrate(counts,[.1,.9],64,.01,risk='any')['reject_all'])
|
|
def test_invalid_calibration(self):
|
|
for x in (np.array([[65]]),np.array([[.5]]),np.array([[np.nan]])):
|
|
with self.assertRaises(ValueError):calibrate(x,[.5],64)
|
|
def test_fixed_sequence_zero_boundary(self):
|
|
for n,expected in ((298,True),(299,False)):
|
|
r=calibrate(np.zeros((n,101),int),np.linspace(0,1,101),64,.01,risk='any',method='fixed_sequence')
|
|
self.assertEqual(r['reject_all'],expected)
|
|
self.assertEqual(r['threshold'],0.)
|
|
def test_fixed_sequence_stops_without_restart(self):
|
|
# Deliberately nonmonotone table: later passing tests must not be inspected.
|
|
x=np.zeros((1000,3),int);x[:,1]=1
|
|
r=calibrate(x,[.1,.5,.9],64,.01,risk='any',method='fixed_sequence')
|
|
self.assertEqual(r['threshold'],.9)
|
|
self.assertEqual([v['threshold'] for v in r['tested']],[.9,.5])
|
|
def test_fixed_sequence_count_zero_boundary(self):
|
|
n=math.ceil(math.log(.05)/math.log1p(-.01/64))
|
|
for size,expected in ((n-1,True),(n,False)):
|
|
r=calibrate(np.zeros((size,1),int),[.5],64,.01,method='fixed_sequence')
|
|
self.assertEqual(r['reject_all'],expected)
|
|
def test_fixed_sequence_support_endpoints(self):
|
|
x=np.array([[0],[64]])
|
|
self.assertTrue(calibrate(x,[.5],64,0,method='fixed_sequence')['reject_all'])
|
|
self.assertFalse(calibrate(x,[.5],64,64,method='fixed_sequence')['reject_all'])
|
|
def test_pool_cap_and_stability(self):
|
|
p=np.zeros((5,5));p[0,0]=.9;p[4,4]=.8
|
|
m,x=candidate_pool(p,.1,1);self.assertEqual(len(m),1);self.assertTrue(m[0][0,0])
|
|
def test_metrics_one_to_one(self):
|
|
g=np.zeros((9,9),bool);g[4,4]=1
|
|
a=np.zeros_like(g);a[4,3]=1;b=np.zeros_like(g);b[4,5]=1
|
|
r=image_stats([a,b],g);self.assertEqual((r['tp'],r['fp'],r['fn']),(1,1,0))
|
|
def test_empty_image_conventions(self):
|
|
r=aggregate([image_stats([],np.zeros((4,4),bool))])
|
|
self.assertEqual(r['nIoU'],1.);self.assertIsNone(r['Pd']);self.assertEqual(r['FP_per_image'],0.)
|
|
def test_path_matches_enumeration(self):
|
|
nodes=[[1.,2.],[3.,0.],[.5,2.]];cost=[np.array([[0.,2.],[1.,0.]]),np.array([[0.,1.],[3.,0.]])]
|
|
best,path=best_path(nodes,cost)
|
|
scores=[(sum(nodes[t][v] for t,v in enumerate(p))-sum(cost[t][p[t],p[t+1]] for t in range(2)),p) for p in itertools.product(range(2),repeat=3)]
|
|
self.assertAlmostEqual(best,max(s[0] for s in scores));self.assertEqual(len(path),3)
|
|
def test_disconnected_path(self):
|
|
self.assertEqual(best_path([[1.],[2.]],[np.array([[np.inf]])]),(-math.inf,[]))
|
|
def test_binary_penalty_exactness(self):
|
|
w=[1.,2.,3.];edges=[(0,1),(1,2)];lam=4.
|
|
values=[(sum(w[i]*b[i] for i in range(3))-lam*sum(b[i]*b[j] for i,j in edges),b) for b in itertools.product([0,1],repeat=3)]
|
|
value,b=max(values);self.assertFalse(any(b[i] and b[j] for i,j in edges));self.assertEqual(value,exact_mwis(w,edges)[0])
|