-
Notifications
You must be signed in to change notification settings - Fork 6
/
geocert_batch.py
115 lines (89 loc) · 4.26 KB
/
geocert_batch.py
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
""" Batched version of Geocert:
This is used for non-ML applications when one simply wishes to find the
centered Chebyshev ball found in a list of polytopes.
"""
from _polytope_ import Face
##############################################################################
# #
# BATCHED GEOCERT #
# #
##############################################################################
class BatchGeoCert(object):
def __init__(self, polytope_list, comp_method='slow',
verbose=True):
self.polytope_list = polytope_list
for poly in self.polytope_list:
poly.to_comparison_form()
assert comp_method in ['slow', 'unstable', 'fast_ReLu']
self.comp_method = comp_method
self.verbose = verbose
def _safety_setup(self, x):
"""
Just make sure this is well-posed.
For now, return True iff x is in at least one of the polytopes
Technically we also require the union of polytopes to be perfectly
glued, but this is harder to check, so we leave it for now
"""
return any(p.is_point_feasible(x) for p in self.polytope_list)
def compute_boundaries(self):
""" Generates a list of the shared and unshared (n-1 dimensional) faces
of the boundary of the polytope list
ARGS:
None
RETURNS:
(unshared_facets, shared_facets) where each is a list of Face
objects
"""
# First gather all the feasible facets from the polytope list
total_facets = [f for poly in self.polytope_list for
f in poly.generate_facets_naive(check_feasible=True)]
if self.verbose:
print('num total facets:', len(total_facets))
unshared_facets, shared_facets = [], []
# Next handle techniques for which comparison method we use
same_facet_dict = {'slow': Face.check_same_facet_pg_slow,
'unstable': Face.check_same_facet_pg,
'fast_ReLu': Face.check_same_facet_config}
same_facet = same_facet_dict[self.comp_method]
# Loop through all facets, figure out which are shared/unshared
for og_facet in total_facets:
bool_unshared = [same_facet(og_facet, ex_facet)
for ex_facet in unshared_facets]
bool_shared = [same_facet(og_facet, ex_facet)
for ex_facet in shared_facets]
# Don't add or remove anything already accounted for in shared list
if any(bool_shared):
continue
# Remove the shared facet from the 'unshared list', add to 'shared'
elif any(bool_unshared):
index = bool_unshared.index(True)
shared_facets.append(unshared_facets.pop(index))
# Otherwise, just add this facet to the 'unshared list'
else:
unshared_facets.append(og_facet)
return unshared_facets, shared_facets
def min_dist(self, x, norm='l_261'):
""" Returns the minimum distance from self.x to the boundary of the
polytopes.
ARGS:
None
RETURNS:
if self.x is not contained in the list of polytopes, returns
(-1, None, None)
else, returns (min_dist, boundary, shared_facets) where:
- min_dist is the minimum l_p dist to the boundary,
- boundary is a list of facets that define the boundary
- shared_facets is a list of facets that are shared amongst the
polytopes
"""
assert norm in ['l_inf', 'l_2']
if not self._safety_setup(x):
return -1, None, None
if self.verbose:
print('----------Computing Boundary----------')
boundary, shared_facets = self.compute_boundaries()
dist_fxn = {'l_inf': Face.linf_dist,
'l_2': Face.l2_dist}[norm]
x = x.reshape(-1, 1)
min_dist = min(dist_fxn(facet, x)[0] for facet in boundary)
return min_dist, boundary, shared_facets