|
|
|
@ -329,6 +329,9 @@ class FeaturesExtractor:
|
|
|
|
|
c_sa_down: Optional[np.ndarray] = None,
|
|
|
|
|
c_sa_up: Optional[np.ndarray] = None,
|
|
|
|
|
values: Optional[np.ndarray] = None,
|
|
|
|
|
with_m1: bool = True,
|
|
|
|
|
with_m2: bool = True,
|
|
|
|
|
with_m3: bool = True,
|
|
|
|
|
) -> np.ndarray:
|
|
|
|
|
"""
|
|
|
|
|
Computes static variable features described in:
|
|
|
|
@ -339,12 +342,8 @@ class FeaturesExtractor:
|
|
|
|
|
assert b is not None
|
|
|
|
|
assert c is not None
|
|
|
|
|
nvars = len(c)
|
|
|
|
|
|
|
|
|
|
c_pos_sum = c[c > 0].sum()
|
|
|
|
|
c_neg_sum = -c[c < 0].sum()
|
|
|
|
|
|
|
|
|
|
curr = 0
|
|
|
|
|
max_n_features = 30
|
|
|
|
|
max_n_features = 40
|
|
|
|
|
features = np.zeros((nvars, max_n_features))
|
|
|
|
|
|
|
|
|
|
def push(v: np.ndarray) -> None:
|
|
|
|
@ -356,17 +355,33 @@ class FeaturesExtractor:
|
|
|
|
|
push(np.sign(v))
|
|
|
|
|
push(np.abs(v))
|
|
|
|
|
|
|
|
|
|
def maxmin(M: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
|
|
|
|
# Compute max using regular numpy operations
|
|
|
|
|
M_max = np.ravel(M.max(axis=0).todense())
|
|
|
|
|
|
|
|
|
|
# Compute min by iterating through the sparse matrix data, so that
|
|
|
|
|
# we skip non-zero entries
|
|
|
|
|
M_min = np.array(
|
|
|
|
|
[
|
|
|
|
|
0.0 if len(M[:, j].data) == 0 else M[:, j].data.min()
|
|
|
|
|
for j in range(M.shape[1])
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
return M_max, M_min
|
|
|
|
|
|
|
|
|
|
with np.errstate(divide="ignore", invalid="ignore"):
|
|
|
|
|
# Feature 1
|
|
|
|
|
push(np.sign(c))
|
|
|
|
|
|
|
|
|
|
# Feature 2
|
|
|
|
|
c_pos_sum = c[c > 0].sum()
|
|
|
|
|
push(np.abs(c) / c_pos_sum)
|
|
|
|
|
|
|
|
|
|
# Feature 3
|
|
|
|
|
c_neg_sum = -c[c < 0].sum()
|
|
|
|
|
push(np.abs(c) / c_neg_sum)
|
|
|
|
|
|
|
|
|
|
if A is not None:
|
|
|
|
|
if A is not None and with_m1:
|
|
|
|
|
# Compute A_ji / |b_j|
|
|
|
|
|
M1 = A.T.multiply(1.0 / np.abs(b)).T.tocsr()
|
|
|
|
|
|
|
|
|
@ -394,13 +409,12 @@ class FeaturesExtractor:
|
|
|
|
|
push_sign_abs(M1_neg_min)
|
|
|
|
|
push_sign_abs(M1_neg_max)
|
|
|
|
|
|
|
|
|
|
if A is not None:
|
|
|
|
|
if A is not None and with_m2:
|
|
|
|
|
# Compute |c_i| / A_ij
|
|
|
|
|
M2 = A.power(-1).multiply(np.abs(c)).tocsc()
|
|
|
|
|
|
|
|
|
|
# Compute max/min
|
|
|
|
|
M2_max = np.ravel(M2.max(axis=0).todense())
|
|
|
|
|
M2_min = np.array([M2[:, j].data.min() for j in range(nvars)])
|
|
|
|
|
M2_max, M2_min = maxmin(M2)
|
|
|
|
|
|
|
|
|
|
# Make copies of M2 and erase elements based on sign(c)
|
|
|
|
|
M2_pos_max = M2_max.copy()
|
|
|
|
@ -418,6 +432,45 @@ class FeaturesExtractor:
|
|
|
|
|
push_sign_abs(M2_neg_min)
|
|
|
|
|
push_sign_abs(M2_neg_max)
|
|
|
|
|
|
|
|
|
|
if A is not None and with_m3:
|
|
|
|
|
# Compute row sums
|
|
|
|
|
S_pos = A.maximum(0).sum(axis=1)
|
|
|
|
|
S_neg = np.abs(A.minimum(0).sum(axis=1))
|
|
|
|
|
|
|
|
|
|
# Divide A by positive and negative row sums
|
|
|
|
|
M3_pos = A.multiply(1 / S_pos).tocsr()
|
|
|
|
|
M3_neg = A.multiply(1 / S_neg).tocsr()
|
|
|
|
|
|
|
|
|
|
# Remove +inf and -inf generated by division by zero
|
|
|
|
|
M3_pos.data[~np.isfinite(M3_pos.data)] = 0.0
|
|
|
|
|
M3_neg.data[~np.isfinite(M3_neg.data)] = 0.0
|
|
|
|
|
M3_pos.eliminate_zeros()
|
|
|
|
|
M3_neg.eliminate_zeros()
|
|
|
|
|
|
|
|
|
|
# Split each matrix into positive and negative parts
|
|
|
|
|
M3_pos_pos = M3_pos.maximum(0)
|
|
|
|
|
M3_pos_neg = -(M3_pos.minimum(0))
|
|
|
|
|
M3_neg_pos = M3_neg.maximum(0)
|
|
|
|
|
M3_neg_neg = -(M3_neg.minimum(0))
|
|
|
|
|
|
|
|
|
|
# Calculate max/min
|
|
|
|
|
M3_pos_pos_max, M3_pos_pos_min = maxmin(M3_pos_pos)
|
|
|
|
|
M3_pos_neg_max, M3_pos_neg_min = maxmin(M3_pos_neg)
|
|
|
|
|
M3_neg_pos_max, M3_neg_pos_min = maxmin(M3_neg_pos)
|
|
|
|
|
M3_neg_neg_max, M3_neg_neg_min = maxmin(M3_neg_neg)
|
|
|
|
|
|
|
|
|
|
# Features 20-35
|
|
|
|
|
push_sign_abs(M3_pos_pos_max)
|
|
|
|
|
push_sign_abs(M3_pos_pos_min)
|
|
|
|
|
push_sign_abs(M3_pos_neg_max)
|
|
|
|
|
push_sign_abs(M3_pos_neg_min)
|
|
|
|
|
push_sign_abs(M3_neg_pos_max)
|
|
|
|
|
push_sign_abs(M3_neg_pos_min)
|
|
|
|
|
push_sign_abs(M3_neg_neg_max)
|
|
|
|
|
push_sign_abs(M3_neg_neg_min)
|
|
|
|
|
|
|
|
|
|
# Feature 36: only available during B&B
|
|
|
|
|
|
|
|
|
|
# Feature 37
|
|
|
|
|
if values is not None:
|
|
|
|
|
push(
|
|
|
|
@ -427,22 +480,24 @@ class FeaturesExtractor:
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Features 38-43: only available during B&B
|
|
|
|
|
|
|
|
|
|
# Feature 44
|
|
|
|
|
if c_sa_up is not None:
|
|
|
|
|
push(np.sign(c_sa_up))
|
|
|
|
|
assert c_sa_down is not None
|
|
|
|
|
|
|
|
|
|
# Feature 46
|
|
|
|
|
if c_sa_down is not None:
|
|
|
|
|
# Features 44 and 46
|
|
|
|
|
push(np.sign(c_sa_up))
|
|
|
|
|
push(np.sign(c_sa_down))
|
|
|
|
|
|
|
|
|
|
# Feature 47
|
|
|
|
|
if c_sa_down is not None:
|
|
|
|
|
push(np.log(c - c_sa_down / np.sign(c)))
|
|
|
|
|
# Feature 45 is duplicated
|
|
|
|
|
|
|
|
|
|
# Feature 48
|
|
|
|
|
if c_sa_up is not None:
|
|
|
|
|
# Feature 47-48
|
|
|
|
|
push(np.log(c - c_sa_down / np.sign(c)))
|
|
|
|
|
push(np.log(c - c_sa_up / np.sign(c)))
|
|
|
|
|
|
|
|
|
|
# Features 49-64: only available during B&B
|
|
|
|
|
|
|
|
|
|
features = features[:, 0:curr]
|
|
|
|
|
_fix_infinity(features)
|
|
|
|
|
return features
|
|
|
|
|