Вот очищенная версия, которая работает. Я удалил пороговую переменную, так как она может быть поглощена знаком до вектора нормалей. Недостатком является то, что мы должны проверить два случая. Я чувствую, что скачок в нуле в пороговой переменной отбросил решатель.
import numpy as np
from scipy.optimize import linprog
N = 100
def test(X, Y):
if not (X.size and Y.size):
return True, np.full((2,), np.nan)
A = np.r_[-X, Y]
b = np.repeat((-1, 1), (X.shape[0], Y.shape[0]))
res1 = linprog(c=[0,0], A_ub=A, b_ub=b-1e-6, bounds=2*[(None, None)])
res2 = linprog(c=[0,0], A_ub=A, b_ub=-b-1e-6, bounds=2*[(None, None)])
if res1['success']:
return True, res1['x']
elif res2['success']:
return True, res2['x']
else:
return False, np.full((2,), np.nan)
def split(locations, p, margin):
proj = np.einsum('ij,j', locations, p[:2])
X = locations[proj < p[2] - margin]
Y = locations[proj > p[2] + margin]
return X, Y
def validate(locations, p, p_recon, margin):
proj = np.einsum('ij,j', locations, p[:2])
X = locations[proj < p[2] - margin]
Y = locations[proj > p[2] + margin]
if not (X.size and Y.size):
return True
return ((np.all(np.einsum('ij,j', X, p_recon) > 1)
and np.all(np.einsum('ij,j', Y, p_recon) < 1))
or
(np.all(np.einsum('ij,j', X, p_recon) > -1)
and np.all(np.einsum('ij,j', Y, p_recon) < -1)))
def random_split(locations):
inX = np.random.randint(0, 2, (locations.shape[0],), dtype=bool)
return locations[inX], locations[~inX]
for minv in range(10):
margin = 1/(2**minv)
locations = np.random.uniform(-1,1,[N,2])
P = np.random.normal(0, 1, (50, 3))
tests, P_recon = zip(*(test(*split(locations, p, margin)) for p in P))
assert all(validate(locations, *ps, margin) for ps in zip(P, P_recon))
control = [test(*random_split(locations))[0] for p in P]
print('test:', np.mean(tests), 'control:', np.mean(control))
Пример вывода:
test: 1.0 control: 0.0
test: 1.0 control: 0.0
test: 1.0 control: 0.0
test: 1.0 control: 0.0
test: 1.0 control: 0.0
test: 1.0 control: 0.0
test: 1.0 control: 0.0
test: 1.0 control: 0.0
test: 1.0 control: 0.0
test: 1.0 control: 0.0