Skip to content

Commit f1fda0c

Browse files
committed
Update solver logic
1 parent dfbf5ea commit f1fda0c

3 files changed

Lines changed: 108 additions & 40 deletions

File tree

‎asgl/asgl.py‎

Lines changed: 106 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
class ASGL:
1515
def __init__(self, model, penalization, intercept=True, tol=1e-5, lambda1=1, alpha=0.5, tau=0.5,
16-
lasso_weights=None, gl_weights=None, parallel=False, num_cores=None, solver=None, max_iters=500):
16+
lasso_weights=None, gl_weights=None, parallel=False, num_cores=None, solver='default', max_iters=500):
1717
"""
1818
Parameters:
1919
model: model to be fit (accepts 'lm' or 'qr')
@@ -28,7 +28,7 @@ def __init__(self, model, penalization, intercept=True, tol=1e-5, lambda1=1, alp
2828
gl_weights: group lasso weights in adaptive penalizations
2929
parallel: boolean, whether to execute the code in parallel or sequentially
3030
num_cores: if parallel is set to true, the number of cores to use in the execution. Default is (max - 1)
31-
solver: solver to be used by CVXPY. Default uses optimal alternative depending on the problem
31+
solver: solver to be used by CVXPY. default uses optimal alternative depending on the problem
3232
max_iters: CVXPY parameter. Default is 500
3333
3434
Returns:
@@ -51,13 +51,9 @@ def __init__(self, model, penalization, intercept=True, tol=1e-5, lambda1=1, alp
5151
self.num_cores = num_cores
5252
self.max_iters = max_iters
5353
self.coef_ = None
54-
# Define solver param as a list of the three default solvers for CVXPY
55-
if solver is None:
56-
self.solver = ['ECOS', 'OSQP', 'SCS']
57-
elif not isinstance(solver, list):
58-
self.solver = [solver]
59-
else:
60-
self.solver = solver
54+
# CVXPY solver parameters
55+
self.solver_stats = None
56+
self.solver = solver
6157

6258
# Model checker related functions #################################################################################
6359

@@ -259,19 +255,28 @@ def unpenalized_solver(self, x, y):
259255
objective_function = (1.0 / n) * cvxpy.sum(self._quantile_function(x=(y - x @ beta_var)))
260256
objective = cvxpy.Minimize(objective_function)
261257
problem = cvxpy.Problem(objective)
262-
# Solve the problem. Try first default CVXPY option, which is usually optimal for the problem. If a ValueError
263-
# arises, try the solvers provided as input to the method.
258+
# Solve the problem. If solver is left as default, try optimal solver sugested by cvxpy.
259+
# If other name is provided, try the name provided
260+
# If these options fail, try default ECOS, OSQP, SCS options
264261
try:
265-
problem.solve(warm_start=True)
262+
if self.solver == 'default':
263+
problem.solve(warm_start=True)
264+
else:
265+
solver_dict = self._cvxpy_solver_options(solver=self.solver)
266+
problem.solve(**solver_dict)
266267
except (ValueError, cvxpy.error.SolverError):
267-
for elt in self.solver:
268+
logging.warning('Default solver failed. Using alternative options. Check solver and solver_stats for more '
269+
'details')
270+
solver = ['ECOS', 'OSQP', 'SCS']
271+
for elt in solver:
268272
solver_dict = self._cvxpy_solver_options(solver=elt)
269273
try:
270274
problem.solve(**solver_dict)
271275
if 'optimal' in problem.status:
272276
break
273277
except (ValueError, cvxpy.error.SolverError):
274278
continue
279+
self.solver_stats = problem.solver_stats
275280
if problem.status in ["infeasible", "unbounded"]:
276281
logging.warning('Optimization problem status failure')
277282
beta_sol = beta_var.value
@@ -305,19 +310,29 @@ def lasso(self, x, y, param):
305310
# Solve the problem iteratively for each parameter value
306311
for lam in param:
307312
lambda_param.value = lam
308-
# Solve the problem. Try first default CVXPY option, which is usually optimal for the problem. If a
309-
# ValueError arises, try the solvers provided as input to the method.
313+
# Solve the problem. If solver is left as default, try optimal solver sugested by cvxpy.
314+
# If other name is provided, try the name provided
315+
# If these options fail, try default ECOS, OSQP, SCS options
310316
try:
311-
problem.solve(warm_start=True)
317+
if self.solver == 'default':
318+
problem.solve(warm_start=True)
319+
else:
320+
solver_dict = self._cvxpy_solver_options(solver=self.solver)
321+
problem.solve(**solver_dict)
312322
except (ValueError, cvxpy.error.SolverError):
313-
for elt in self.solver:
323+
logging.warning(
324+
'Default solver failed. Using alternative options. Check solver and solver_stats for more '
325+
'details')
326+
solver = ['ECOS', 'OSQP', 'SCS']
327+
for elt in solver:
314328
solver_dict = self._cvxpy_solver_options(solver=elt)
315329
try:
316330
problem.solve(**solver_dict)
317331
if 'optimal' in problem.status:
318332
break
319333
except (ValueError, cvxpy.error.SolverError):
320334
continue
335+
self.solver_stats = problem.solver_stats
321336
if problem.status in ["infeasible", "unbounded"]:
322337
logging.warning('Optimization problem status failure')
323338
beta_sol = beta_var.value
@@ -332,6 +347,7 @@ def gl(self, x, y, group_index, param):
332347
"""
333348
n = x.shape[0]
334349
# Check th group_index, find the unique groups, count how many vars are in each group (this is the group size)
350+
group_index = np.asarray(group_index).astype(int)
335351
unique_group_index = np.unique(group_index)
336352
group_sizes, beta_var = self._num_beta_var_from_group_index(group_index)
337353
num_groups = len(group_sizes)
@@ -365,19 +381,29 @@ def gl(self, x, y, group_index, param):
365381
# Solve the problem iteratively for each parameter value
366382
for lam in param:
367383
lambda_param.value = lam
368-
# Solve the problem. Try first default CVXPY option, which is usually optimal for the problem. If a
369-
# ValueError arises, try the solvers provided as input to the method.
384+
# Solve the problem. If solver is left as default, try optimal solver sugested by cvxpy.
385+
# If other name is provided, try the name provided
386+
# If these options fail, try default ECOS, OSQP, SCS options
370387
try:
371-
problem.solve(warm_start=True)
388+
if self.solver == 'default':
389+
problem.solve(warm_start=True)
390+
else:
391+
solver_dict = self._cvxpy_solver_options(solver=self.solver)
392+
problem.solve(**solver_dict)
372393
except (ValueError, cvxpy.error.SolverError):
373-
for elt in self.solver:
394+
logging.warning(
395+
'Default solver failed. Using alternative options. Check solver and solver_stats for more '
396+
'details')
397+
solver = ['ECOS', 'OSQP', 'SCS']
398+
for elt in solver:
374399
solver_dict = self._cvxpy_solver_options(solver=elt)
375400
try:
376401
problem.solve(**solver_dict)
377402
if 'optimal' in problem.status:
378403
break
379404
except (ValueError, cvxpy.error.SolverError):
380405
continue
406+
self.solver_stats = problem.solver_stats
381407
if problem.status in ["infeasible", "unbounded"]:
382408
logging.warning('Optimization problem status failure')
383409
beta_sol = np.concatenate([b.value for b in beta_var], axis=0)
@@ -391,6 +417,7 @@ def sgl(self, x, y, group_index, param):
391417
"""
392418
n = x.shape[0]
393419
# Check th group_index, find the unique groups, count how many vars are in each group (this is the group size)
420+
group_index = np.asarray(group_index).astype(int)
394421
unique_group_index = np.unique(group_index)
395422
group_sizes, beta_var = self._num_beta_var_from_group_index(group_index)
396423
num_groups = len(group_sizes)
@@ -428,19 +455,29 @@ def sgl(self, x, y, group_index, param):
428455
for lam, al in param:
429456
lasso_param.value = lam * al
430457
grp_lasso_param.value = lam * (1 - al)
431-
# Solve the problem. Try first default CVXPY option, which is usually optimal for the problem. If a
432-
# ValueError arises, try the solvers provided as input to the method.
458+
# Solve the problem. If solver is left as default, try optimal solver sugested by cvxpy.
459+
# If other name is provided, try the name provided
460+
# If these options fail, try default ECOS, OSQP, SCS options
433461
try:
434-
problem.solve(warm_start=True)
462+
if self.solver == 'default':
463+
problem.solve(warm_start=True)
464+
else:
465+
solver_dict = self._cvxpy_solver_options(solver=self.solver)
466+
problem.solve(**solver_dict)
435467
except (ValueError, cvxpy.error.SolverError):
436-
for elt in self.solver:
468+
logging.warning(
469+
'Default solver failed. Using alternative options. Check solver and solver_stats for more '
470+
'details')
471+
solver = ['ECOS', 'OSQP', 'SCS']
472+
for elt in solver:
437473
solver_dict = self._cvxpy_solver_options(solver=elt)
438474
try:
439475
problem.solve(**solver_dict)
440476
if 'optimal' in problem.status:
441477
break
442478
except (ValueError, cvxpy.error.SolverError):
443479
continue
480+
self.solver_stats = problem.solver_stats
444481
if problem.status in ["infeasible", "unbounded"]:
445482
logging.warning('Optimization problem status failure')
446483
beta_sol = np.concatenate([b.value for b in beta_var], axis=0)
@@ -475,19 +512,29 @@ def alasso(self, x, y, param):
475512
# Solve the problem iteratively for each parameter value
476513
for lam, lw in param:
477514
l_weights_param.value = lam * lw
478-
# Solve the problem. Try first default CVXPY option, which is usually optimal for the problem. If a
479-
# ValueError arises, try the solvers provided as input to the method.
515+
# Solve the problem. If solver is left as default, try optimal solver sugested by cvxpy.
516+
# If other name is provided, try the name provided
517+
# If these options fail, try default ECOS, OSQP, SCS options
480518
try:
481-
problem.solve(warm_start=True)
519+
if self.solver == 'default':
520+
problem.solve(warm_start=True)
521+
else:
522+
solver_dict = self._cvxpy_solver_options(solver=self.solver)
523+
problem.solve(**solver_dict)
482524
except (ValueError, cvxpy.error.SolverError):
483-
for elt in self.solver:
525+
logging.warning(
526+
'Default solver failed. Using alternative options. Check solver and solver_stats for more '
527+
'details')
528+
solver = ['ECOS', 'OSQP', 'SCS']
529+
for elt in solver:
484530
solver_dict = self._cvxpy_solver_options(solver=elt)
485531
try:
486532
problem.solve(**solver_dict)
487533
if 'optimal' in problem.status:
488534
break
489535
except (ValueError, cvxpy.error.SolverError):
490536
continue
537+
self.solver_stats = problem.solver_stats
491538
if problem.status in ["infeasible", "unbounded"]:
492539
logging.warning('Optimization problem status failure')
493540
beta_sol = beta_var.value
@@ -502,6 +549,7 @@ def agl(self, x, y, group_index, param):
502549
"""
503550
n = x.shape[0]
504551
# Check th group_index, find the unique groups, count how many vars are in each group (this is the group size)
552+
group_index = np.asarray(group_index).astype(int)
505553
unique_group_index = np.unique(group_index)
506554
group_sizes, beta_var = self._num_beta_var_from_group_index(group_index)
507555
num_groups = len(group_sizes)
@@ -535,19 +583,29 @@ def agl(self, x, y, group_index, param):
535583
# Solve the problem iteratively for each parameter value
536584
for lam, gl in param:
537585
gl_weights_param.value = lam * gl
538-
# Solve the problem. Try first default CVXPY option, which is usually optimal for the problem. If a
539-
# ValueError arises, try the solvers provided as input to the method.
586+
# Solve the problem. If solver is left as default, try optimal solver sugested by cvxpy.
587+
# If other name is provided, try the name provided
588+
# If these options fail, try default ECOS, OSQP, SCS options
540589
try:
541-
problem.solve(warm_start=True)
590+
if self.solver == 'default':
591+
problem.solve(warm_start=True)
592+
else:
593+
solver_dict = self._cvxpy_solver_options(solver=self.solver)
594+
problem.solve(**solver_dict)
542595
except (ValueError, cvxpy.error.SolverError):
543-
for elt in self.solver:
596+
logging.warning(
597+
'Default solver failed. Using alternative options. Check solver and solver_stats for more '
598+
'details')
599+
solver = ['ECOS', 'OSQP', 'SCS']
600+
for elt in solver:
544601
solver_dict = self._cvxpy_solver_options(solver=elt)
545602
try:
546603
problem.solve(**solver_dict)
547604
if 'optimal' in problem.status:
548605
break
549606
except (ValueError, cvxpy.error.SolverError):
550607
continue
608+
self.solver_stats = problem.solver_stats
551609
if problem.status in ["infeasible", "unbounded"]:
552610
logging.warning('Optimization problem status failure')
553611
beta_sol = np.concatenate([b.value for b in beta_var], axis=0)
@@ -561,6 +619,7 @@ def asgl(self, x, y, group_index, param):
561619
"""
562620
n, m = x.shape
563621
# Check th group_index, find the unique groups, count how many vars are in each group (this is the group size)
622+
group_index = np.asarray(group_index).astype(int)
564623
unique_group_index = np.unique(group_index)
565624
group_sizes, beta_var = self._num_beta_var_from_group_index(group_index)
566625
num_groups = len(group_sizes)
@@ -600,19 +659,29 @@ def asgl(self, x, y, group_index, param):
600659
for lam, al, lw, glw in param:
601660
l_weights_param.value = lw * lam * al
602661
gl_weights_param.value = glw * lam * (1 - al)
603-
# Solve the problem. Try first default CVXPY option, which is usually optimal for the problem. If a
604-
# ValueError arises, try the solvers provided as input to the method.
662+
# Solve the problem. If solver is left as default, try optimal solver sugested by cvxpy.
663+
# If other name is provided, try the name provided
664+
# If these options fail, try default ECOS, OSQP, SCS options
605665
try:
606-
problem.solve(warm_start=True)
666+
if self.solver == 'default':
667+
problem.solve(warm_start=True)
668+
else:
669+
solver_dict = self._cvxpy_solver_options(solver=self.solver)
670+
problem.solve(**solver_dict)
607671
except (ValueError, cvxpy.error.SolverError):
608-
for elt in self.solver:
672+
logging.warning(
673+
'Default solver failed. Using alternative options. Check solver and solver_stats for more '
674+
'details')
675+
solver = ['ECOS', 'OSQP', 'SCS']
676+
for elt in solver:
609677
solver_dict = self._cvxpy_solver_options(solver=elt)
610678
try:
611679
problem.solve(**solver_dict)
612680
if 'optimal' in problem.status:
613681
break
614682
except (ValueError, cvxpy.error.SolverError):
615683
continue
684+
self.solver_stats = problem.solver_stats
616685
if problem.status in ["infeasible", "unbounded"]:
617686
logging.warning('Optimization problem status failure')
618687
beta_sol = np.concatenate([b.value for b in beta_var], axis=0)
@@ -676,7 +745,6 @@ def fit(self, x, y, group_index=None):
676745
this function solves the model and produces the regression coefficients
677746
"""
678747
param = self._preprocessing()
679-
group_index = np.asarray(group_index).astype(int)
680748
if self.penalization is None:
681749
self.coef_ = self.unpenalized_solver(x=x, y=y)
682750
else:

‎setup.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
setup(
1111
name='asgl',
12-
version='v1.0.4',
12+
version='v1.0.5',
1313
author='Alvaro Mendez Civieta',
1414
author_email='almendez@est-econ.uc3m.es',
1515
license='GNU General Public License',

‎tests/asgl_model_tests.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
boston = load_boston()
99
x = boston.data
1010
y = boston.target
11-
group_index = np.array([1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5])
11+
group_index = np.array([1.0, 1.0, 1.0, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5])
1212

1313
# TEST MODELS #########################################################################################################
1414

0 commit comments

Comments
 (0)