I'm running into an optimization issue. The problem has a dual objective. I have 2 clouds of points, the green ones $(x_i, y_i)$ and the red ones $(x_j, y_j)$. I'm looking for the equations of 2 lines, one being above the green points and the other being below the red points, with one constraint: they have to be parallel. As the objective function, I just summed the distances of each line to its respective cloud of points, the distance being just a difference (the constraints will ensure that they meet the required criteria). Here is the formulation of the problem:
$$\min_{a,b_u,b_l} {\sum_{i=1}^m {\left( \left( ax_i+b_u \right) - y_i \right)} + \sum_{j=1}^n {\left( y_j - \left( ax_j+b_l \right) \right)}} \ \ \ \text{s.t.}\ \left\{\matrix {ax_i+b_u\ge y_i \ \ \forall \ i \in [1,m] \\y_j\ge ax_j+b_l \ \ \forall \ j \in [1,n]}\right.$$
To simplify the notations, the indices $i$ and $j$ refer to the clouds (i.e. $x_i\ne x_j$ and $y_i\ne y_j$).
The program can be put in standard form: $$\min_{a,b_u,b_l} {a\left(\sum_{i=1}^m x_i - \sum_{j=1}^n x_j\right) + b_u m - b_l n} \ \ \ \text{s.t.}\ \left\{\matrix {-ax_i-b_u\le -y_i \ \ \forall \ i \in [1,m] \\ax_j+b_l\le y_j \ \ \forall \ j \in [1,n]}\right.$$
I implemented it with scipy's linprog:
import numpy as np
from scipy.optimize import linprog
def fit_both_lines(x_u, y_u, x_l, y_l):
m = len(x_u)
n = len(x_l)
c = np.array([np.sum(x_u) - np.sum(x_l), m, -n], dtype=float)
A_ub = np.array([(-x_u[i], -1, 0) for i in range(m)] + [(x_l[j], 0, 1) for j in range(n)])
b_ub = np.array([-y_u[i] for i in range(m)] + [y_l[j] for j in range(n)])
res = linprog(c, A_ub, b_ub, method="highs-ds")
slope, intercept_u, intercept_l = res.x
return (intercept_u, slope), (intercept_l, slope)
Here is a snippet to test it:
from numpy.random import randint, random
import matplotlib.pyplot as plt
N = 10
x_u = randint(0, 100, N)
y_u = random(N) * 100
x_l = randint(0, 100, N)
y_l = random(N) * 100
upper_line, lower_line = fit_both_lines(x_u, y_u, x_l, y_l)
plt.scatter(x_u, y_u, c="green")
plt.scatter(x_l, y_l, c="red")
plt.plot(x_u, upper_line[0] + upper_line[1] * x_u, c="green")
plt.plot(x_l, lower_line[0] + lower_line[1] * x_l, c="red")
Though, most of the time, the slope $a$ gets estimated to 0 which makes me think there is a mistake somewhere.
Here is a sample plot:
I also tried the absolute values as a distance function and linearized them, but I got the very same result... What did I do wrong?