forked from snagcliffs/PDE-FIND
-
Notifications
You must be signed in to change notification settings - Fork 0
/
solvel0.py
159 lines (134 loc) · 5.74 KB
/
solvel0.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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import numpy as np
import gurobipy as gp
from gurobipy import GRB
# See /Users/pongpisit/Desktop/research/parametric-discovery
import sys; sys.path.append('/Users/pongpisit/Desktop/research/parametric-discovery')
from best_subset import backward_refinement
from sklearn.preprocessing import normalize
from tqdm import trange
nplm = np.linalg.lstsq
# Create and deploy the optimization model
# NOTE: This function assumes the design matrix features does not contain a column for the intercept
# With an intercept, L0-norm constrainted MIQP
def miqp(features, response, non_zero, verbose=False):
"""
Deploy and optimize the MIQP formulation of L0-Regression.
"""
assert isinstance(non_zero, (int, np.integer))
regressor = gp.Model()
samples, dim = features.shape
assert samples == response.shape[0]
assert non_zero <= dim
# Append a column of ones to the feature matrix to account for the y-intercept
X = np.concatenate([features, np.ones((samples, 1))], axis=1)
# Decision variables
norm_0 = regressor.addVar(lb=non_zero, ub=non_zero, name="norm")
beta = regressor.addMVar((dim + 1,), lb=-GRB.INFINITY, name="beta") # Weights
intercept = beta[dim] # Last decision variable captures the y-intercept
regressor.setObjective(beta.T @ X.T @ X @ beta
- 2*response.T @ X @ beta
+ np.dot(response, response), GRB.MINIMIZE)
# Budget constraint based on the L0-norm
regressor.addGenConstrNorm(norm_0, beta[:-1], which=0, name="budget")
if not verbose:
regressor.params.OutputFlag = 0
else:
regressor.params.OutputFlag = 1
regressor.params.timelimit = 60
regressor.params.mipgap = 0.001
regressor.optimize()
coeff = np.array([beta[i].X for i in range(dim)])
return intercept.X, coeff
# Without an intercept, L0-norm constrainted MIQP
def miqp2(features, response, non_zero, alpha=0, verbose=False):
"""
Deploy and optimize the MIQP formulation of L0-Regression.
"""
assert isinstance(non_zero, (int, np.integer))
regressor = gp.Model()
samples, dim = features.shape
assert samples == response.shape[0]
assert non_zero <= dim
# Decision variables
X = features
norm_0 = regressor.addVar(lb=non_zero, ub=non_zero, name="norm")
beta = regressor.addMVar((dim,), lb=-GRB.INFINITY, name="beta") # Weights
if alpha > 0:
# Drop the constant term and add regularization on the weights
regressor.setObjective(beta.T @ X.T @ X @ beta
- 2*response.T @ X @ beta
+ alpha*(beta.T@beta),
GRB.MINIMIZE)
else:
regressor.setObjective(beta.T @ X.T @ X @ beta
- 2*response.T @ X @ beta
+ np.dot(response, response), # Constant term
GRB.MINIMIZE)
# Budget constraint based on the L0-norm
regressor.addGenConstrNorm(norm_0, beta, which=0, name="budget")
if not verbose:
regressor.params.OutputFlag = 0
else:
regressor.params.OutputFlag = 1
regressor.params.timelimit = 60
regressor.params.mipgap = 0.001
regressor.optimize()
coeff = np.array([beta[i].X for i in range(dim)])
return coeff
# Without an intercept + MIOSR implementation
def miqp3(features, response, non_zero, alpha=0, verbose=False):
"""
Deploy and optimize SOS-1 formulated MIO-SINDy
"""
assert isinstance(non_zero, (int, np.integer))
regressor = gp.Model()
samples, dim = features.shape
assert samples == response.shape[0]
assert non_zero <= dim
X = features
beta = regressor.addMVar((dim,), lb=-GRB.INFINITY, vtype=gp.GRB.CONTINUOUS, name="beta") # Weights as a column vector
iszero = regressor.addMVar(dim, vtype=gp.GRB.BINARY, name="iszero")
for i in range(dim):
regressor.addSOS(gp.GRB.SOS_TYPE1, [beta[i], iszero[i]])
regressor.addConstr(iszero.sum() >= dim-non_zero, name="sparsity")
regressor.setObjective(beta.T @ X.T @ X @ beta
- 2*response.T @ X @ beta
+ alpha*(beta.T@beta),
GRB.MINIMIZE)
if not verbose:
regressor.params.OutputFlag = 0
else:
regressor.params.OutputFlag = 1
regressor.params.timelimit = 100
# regressor.params.mipgap = 0.001
regressor.optimize()
coeff = np.array([beta[i].X for i in range(dim)])
return coeff
def solvel0(X_pre, y_pre, is_normal=False, intercept=False, miosr=False, refine=False, ic_type='bic', max_complexity=None, verbose=False):
out = set()
if max_complexity is None:
max_complexity = X_pre.shape[1]
if is_normal:
X_pre = normalize(X_pre, axis=0)
for i in trange(1, max_complexity+1):
if intercept:
beta = miqp(X_pre, y_pre.flatten(), i)[-1]
else:
if not miosr:
beta = miqp2(X_pre, y_pre.flatten(), i)
else:
beta = miqp3(X_pre, y_pre.flatten(), i)
effective_indices = tuple(np.where(np.abs(beta)>0)[0])
if len(effective_indices) > 0:
out.add(effective_indices)
# Added backward_refinement capability
if refine:
print("Call backward_refinement...")
st = refine_solvel0(out, (X_pre, y_pre), ic_type, verbose)
out = sorted([st.track[e][0] for e in st.track], key=len)
best_subsets = sorted(out, key=len)
return best_subsets
def refine_solvel0(out, dataset, ic_type='bic', verbose=False):
st = backward_refinement(out, dataset, mode='rfe', ic_type=ic_type, verbose=verbose)
st += backward_refinement(out, dataset, mode='SelectKBest', ic_type=ic_type, verbose=verbose)
return st