This tutorial only covers the specifics of the SCS Python interface. For background on SCS and descriptions of the optimization problem being solved and the input data format, please see the SCS README or the SCS Paper.
This tutorial covers most (but not all) of the Python interface. For more details, see the SCS_Python README.
In [1]:
from __future__ import print_function
import scs
We'll load data for an example $\ell_1$ minimization problem. data and cone are correctly-formatted input for the SCS solver.
In [2]:
m = 2000
data, cone = scs.examples.l1(m=m)
print('data: ', data)
print('cone: ', cone)
scs.solve() to compute a solution to the problemdata and cone are required argumentssol is a dictionary with keys 'x', 'y', 's' (numpy arrays corresponding to problem variables), and 'info' (a dict giving solver status information)
In [3]:
%%time
sol = scs.solve(data, cone, verbose=False)
print('---###---')
In [4]:
print(sol.keys())
sol['info']
Out[4]:
A copy of the SCS default solver settings can be seen by calling scs.default_settings(). Each of these settings can be modified for a call to scs.solve() by passing in keyword arguments.
In [5]:
scs.default_settings()
Out[5]:
The solver can be warm-started with vectors x, y, and s (which are expected to be close to the solution). This can reduce the number of iterations needed to converge.
Pass a dictionary with keys 'x', 'y', and 's' (pointing to numpy arrays of the appropriate size) to the warm_start keyword argument of scs.solve(). Note that all three vectors are required for warm-starting.
The 'x', 'y', and 's' arrays are copied so that they are not modified by SCS. The solution, sol, returned by scs.solve() contains new numpy arrays.
Below, we use the previously-computed solution to warm-start the solver on the same problem, and see that this allows the solver to exit after 0 iterations.
In [6]:
%%time
sol = scs.solve(data, cone, warm_start=sol, verbose=True)
print('---###---')
We can confirm the number of iterations by inspecting the 'info' dictionary. Note that even though
the solver needed 0 iterations to converge, it still had to perform a matrix factorization (since use_indirect=False), which took about 4 seconds.
In [7]:
sol['info']
Out[7]:
When using the direct method (use_indirect=False), we can cache the matrix factorization involving A, and reuse it across several solves. This is useful when solving a sequence or family of problems where A is fixed, but b and c may change.
The scs.Workspace object caches the matrix factorization for us, and allows us to call the solver many times with different values for b and c. We can also optionally warm-start the solver, and change some of the solver settings between solves.
Below, we initialize the Workspace object with the same data as above, and note that the setup time (factorization time) is still approximately 4 seconds. Note that the Workspace defaults to the direct (factorization) method because use_indirect=False, unless the user specifies otherwise.
In [8]:
%%time
work = scs.Workspace(data, cone)
print('---###---')
In [9]:
work.info
Out[9]:
work.settingsThe Workspace object records changes to the user-specified settings. They can be inspected and modified through the
work.settings dictionary. When the solver is run, it will operate based on the current settings.
Some settings should not be changed once the Workspace object is initialized, since the cached matrix factorization depends on them:
use_indirectnormalizescalerho_xSCS will raise an exception when calling work.solve() if these have been changed.
In [10]:
work.settings
Out[10]:
In [11]:
work.fixed
Out[11]:
work.dataThe vectors b and c can be modified through the work.data dictionary between solves. work.data is a shallow copy of the data dictionary passed to the Workspace() constructor, so changes to work.data will not affect the original dictionary. However, both dictionaries initially point to the same b and c numpy arrays.
Since the cached matrix factorization depends on the original A, and this matrix cannot be modified without invalidating the factorization,
work.data does not expose the internally copied matrix A. Since a copy of A is formed and stored in the work object, the original A matrix can be modified without affecting work.
In [12]:
work.data
Out[12]:
In [13]:
%%time
sol = work.solve()
print('---###---')
The solution is returned as a dictionary, just as with scs.solve().
In [14]:
sol
Out[14]:
settingsBy default, the Workspace will use the settings from scs.default_settings(). The user has a few opportunities to modify them:
scs.Workspace()work.settings (but making sure not to modify the settings in work.fixed)work.solve()Any changes to the settings persist in the Workspace object, including those passed to scs.solve(). For instance,
work.solve(eps=1e-5, alpha=1.1)
is exactly equivalent to
work.settings['eps'] = 1e-5
work.settings['alpha'] = 1.1
work.solve()
In addition to benefiting from the cached matrix factorization, we can also use a warm-started solution by calling
work.solve(warm_start=ws). This will warm-start from the vectors in ws. Since we previously ran the solver and the solution is already contained in sol, the following call should require 0 iterations. It should also require 0 setup time, since we've cached the factorization.
In [15]:
%%time
sol = work.solve(warm_start=sol)
print('---###---')
For a more interesting example of warm-starting, we perturb the b vector a bit and try to re-solve.
However, if we warm-start from the previous solution to the unperturbed problem, we can expect to only need a few iterations to "correct" for the perturbation and obtain the new solution.
In [16]:
%%time
# make a small perturbation to b
work.data['b'][:m] += .01
sol = work.solve(warm_start=sol)
print('---###---')
If we attempt to solve the problem without warm-starting, it will require many more iterations (and a longer solve time).
In [17]:
%%time
sol = work.solve()
print('---###---')
If we revert even futher (back to the point where we started this tutorial) and try to solve without factorization caching or warm-starting, we can expect an even longer solve time.
In [18]:
%%time
data['b'] = work.data['b']
sol = scs.solve(data, cone)
print('---###---')
In [ ]: