Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python linsys solver. #11

Merged
merged 14 commits into from
Apr 5, 2019
Merged

Python linsys solver. #11

merged 14 commits into from
Apr 5, 2019

Conversation

bamos
Copy link
Contributor

@bamos bamos commented Apr 3, 2019

This is a nearly-finished PR for a linear system solver that calls back up into Python. It passes an empty A matrix into SCS and defers all operations involving A to Python. Here is some of the remaining work (if you're fine with the core content of this PR, it may make sense to merge it in sooner and move some of these to the issue tracker)

  • Add support for other precision.
  • Decide if anything else should be passed back up to Python.
  • Add normalization.
  • Add some docs and a description somewhere.
  • Add more checks to the functions passed in to make sure they are reasonable/take the correct number of arguments since otherwise errors coming from C calling back into Python can be uninformative/unnecessarily long
  • Windows support.

I've added a simple test in the same format as the other tests in test/test_scs_python_linsys.py and here's an example use of the interface:

ncon_cone, nz_cone = A.shape
rho = 1e-3
M = sp.bmat([[rho*sp.eye(nz_cone), A.T], [A, -sp.eye(ncon_cone)]])
Msolve = sla.factorized(M)

def solve_lin_sys_cb(b, s, i):
    b[:] = Msolve(b)

def accum_by_a_cb(x, y):
    y += A.dot(x)

def accum_by_atrans_cb(x, y):
    y += A.T.dot(x)

sol = scs.solve(
    data, cones, verbose=True, use_indirect=False,
    normalize=False, max_iters=10,
    linsys_cbs=(solve_lin_sys_cb,accum_by_a_cb,accum_by_atrans_cb)
)

@bamos
Copy link
Contributor Author

bamos commented Apr 3, 2019

The tests on windows are currently giving NaNs so I've disabled the test and added windows support to the issue list above

@bamos bamos mentioned this pull request Apr 3, 2019
@bamos
Copy link
Contributor Author

bamos commented Apr 3, 2019

The travis tests are failing since cvxgrp/scs#110 hasn't yet been merged, I'll re-run them once that's been merged in. This may also fix the appveyor tests.

Brandon Amos and others added 2 commits April 3, 2019 22:39
Copy link
Owner

@bodono bodono left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing to check is for memory leaks. Try running many solves sequentially with new data each time and make sure that the memory usage doesn't climb over time.

/* Note, Python3.x may require special handling for the scs_int and scs_float
* types. */
static int get_int_type(void) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a problem with these being static?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using them as extern in my linsys solver, although I could just copy them over there or share them in a cleaner way if you prefer

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok that's fine, probably good to prefix them with something like 'scs_' just so they don't collide with any other possible functions in the namespace.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import sys
import gen_random_cone_prob as tools

if platform.system() == 'Windows':
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll figure out how to fix the windows tests

#endif


#ifndef PYTHON_LINSYS
/* release the GIL */
Py_BEGIN_ALLOW_THREADS;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could release the GIL here and then reacquire once we enter each callback (then release at the end of each callback), that would probably be better for multi-threading.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I thought about this earlier. This would require sharing the python thread state from this part with the linsys calls. I think the easiest way of adding this would be to have a global _save variable in scs-module.c that private.c modifies as it's acquiring/releasing the GIL around the Python callbacks. What do you think?

Ref: https://docs.python.org/3/c-api/init.html#c.Py_BEGIN_ALLOW_THREADS

Copy link
Contributor Author

@bamos bamos Apr 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I just tried doing this and am getting a segfault and am not sure what else to try. I put this around the main scs call:

/* release the GIL */
scs_thread_state = PyEval_SaveThread();
/* Solve! */
scs(d, k, &sol, &info);
/* reacquire the GIL */
PyEval_RestoreThread(scs_thread_state);

And this around all of the callbacks:

PyEval_RestoreThread(scs_thread_state);
PyObject_CallObject(scs_solve_lin_sys_cb, arglist);
scs_thread_state = PyEval_SaveThread();

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird, ok let's just go with what you have for now then.

@bodono
Copy link
Owner

bodono commented Apr 4, 2019

Once you resolve the minor comments I have (only major question being to check for memory leakage) I will merge this and then we can iterate on it some more.

@bamos
Copy link
Contributor Author

bamos commented Apr 4, 2019

One thing to check is for memory leaks. Try running many solves sequentially with new data each time and make sure that the memory usage doesn't climb over time.

First I tracked the memory for multiple solves with the same data and there aren't any apparent leaks here:

print('--- vanilla scs ---')
gc.collect(); print('  + {} bytes'.format(process.memory_info().rss))
for _ in range(10):
    sol = scs.solve(
        data, cones, verbose=False,
        use_indirect=False, normalize=False,
        max_iters=10,
    )
    gc.collect(); print('  + {} bytes'.format(process.memory_info().rss))

...

print('\n\n--- scs with python cbs---')
gc.collect(); print('  + {} bytes'.format(process.memory_info().rss))
for _ in range(10):
    sol = scs.solve(
        data, cones, verbose=False, use_indirect=False,
        normalize=False, max_iters=10,
        linsys_cbs=(solve_lin_sys_cb,accum_by_a_cb,accum_by_atrans_cb)
    )
    gc.collect(); print('  + {} bytes'.format(process.memory_info().rss))
--- vanilla scs ---
  + 108814336 bytes
  + 112439296 bytes
  + 112439296 bytes
  + 112447488 bytes
  + 112447488 bytes
  + 112451584 bytes
  + 112451584 bytes
  + 112451584 bytes
  + 112451584 bytes
  + 112451584 bytes
  + 112451584 bytes


--- scs with python cbs---
  + 112701440 bytes
  + 112852992 bytes
  + 112852992 bytes
  + 112852992 bytes
  + 112857088 bytes
  + 112857088 bytes
  + 112857088 bytes
  + 112857088 bytes
  + 112857088 bytes
  + 112857088 bytes
  + 112857088 bytes

Next I tracked the memory for multiple solves with the different data every time and there aren't any apparent leaks here:

print('--- vanilla scs ---')
init_m = process.memory_info().rss
gc.collect(); print('  + {} bytes'.format(init_m))
for _ in range(20):
    _G.value = npr.randn(nineq, nz)
    data, chain, inv_data = prob.get_problem_data('SCS')
    sol = scs.solve(
        data, cones, verbose=False,
        use_indirect=False, normalize=False,
        max_iters=10,
    )
    gc.collect()
    m = process.memory_info().rss
    print('  + {} bytes (+ {} bytes)'.format(m, m-init_m))
    init_m = m

...

print('\n\n--- scs with python cbs---')
init_m = process.memory_info().rss
gc.collect(); print('  + {} bytes'.format(init_m))
for _ in range(20):
    _G.value = npr.randn(nineq, nz)
    data, chain, inv_data = prob.get_problem_data('SCS')
    sol = scs.solve(
        data, cones, verbose=False, use_indirect=False,
        normalize=False, max_iters=10,
        linsys_cbs=(solve_lin_sys_cb,accum_by_a_cb,accum_by_atrans_cb)
    )
    gc.collect()
    m = process.memory_info().rss
    print('  + {} bytes (+ {} bytes)'.format(m, m-init_m))
    init_m = m
--- vanilla scs ---                                                  [9/1847]
  + 108982272 bytes
  + 112644096 bytes (+ 3661824 bytes)
  + 112693248 bytes (+ 49152 bytes)
  + 112750592 bytes (+ 57344 bytes)
  + 112812032 bytes (+ 61440 bytes)
  + 112861184 bytes (+ 49152 bytes)
  + 112910336 bytes (+ 49152 bytes)
  + 112943104 bytes (+ 32768 bytes)
  + 112967680 bytes (+ 24576 bytes)
  + 112975872 bytes (+ 8192 bytes)
  + 112979968 bytes (+ 4096 bytes)
  + 112984064 bytes (+ 4096 bytes)
  + 112984064 bytes (+ 0 bytes)
  + 112984064 bytes (+ 0 bytes)
  + 112988160 bytes (+ 4096 bytes)
  + 112996352 bytes (+ 8192 bytes)
  + 113025024 bytes (+ 28672 bytes)
  + 113037312 bytes (+ 12288 bytes)
  + 113037312 bytes (+ 0 bytes)
  + 113037312 bytes (+ 0 bytes)
  + 113037312 bytes (+ 0 bytes)

--- scs with python cbs---
  + 113299456 bytes
  + 113463296 bytes (+ 163840 bytes)
  + 113504256 bytes (+ 40960 bytes)
  + 113504256 bytes (+ 0 bytes)
  + 113524736 bytes (+ 20480 bytes)
  + 113541120 bytes (+ 16384 bytes)
  + 113573888 bytes (+ 32768 bytes)
  + 113577984 bytes (+ 4096 bytes)
  + 113577984 bytes (+ 0 bytes)
  + 113610752 bytes (+ 32768 bytes)
  + 113614848 bytes (+ 4096 bytes)
  + 113618944 bytes (+ 4096 bytes)
  + 113623040 bytes (+ 4096 bytes)
  + 113623040 bytes (+ 0 bytes)
  + 113627136 bytes (+ 4096 bytes)
  + 113631232 bytes (+ 4096 bytes)
  + 113635328 bytes (+ 4096 bytes)
  + 113639424 bytes (+ 4096 bytes)
  + 113639424 bytes (+ 0 bytes)
  + 113643520 bytes (+ 4096 bytes)
  + 113647616 bytes (+ 4096 bytes)

@bodono
Copy link
Owner

bodono commented Apr 4, 2019

Cool, is this ready to be merged then?

@bamos
Copy link
Contributor Author

bamos commented Apr 4, 2019

Almost -- Python=3.6 on OSX is passing the tests for this PR now but the earlier versions of python on OSX aren't. I just tried changing the import_array call (which seems necessary somewhere in private.c for some reason, otherwise it will segfault)

@bamos
Copy link
Contributor Author

bamos commented Apr 4, 2019

Hmm, the OSX Python builds can now at least compile this without errors but are now failing with /Users/travis/.travis/functions: line 104: nosetests: command not found? https://travis-ci.org/bodono/scs-python/jobs/515741429

Seems weird since the master branch is currently running/passing https://travis-ci.org/bodono/scs-python/jobs/515100158

@bamos
Copy link
Contributor Author

bamos commented Apr 4, 2019

Hmm, in that travis build that's failing, it's setting PYTHON=3.4.7 but appears to be using pip with Python 2.

image

@bamos
Copy link
Contributor Author

bamos commented Apr 4, 2019

The pyenv install command is failing for some reason on the builds for this PR, but not for master.

image

@bamos
Copy link
Contributor Author

bamos commented Apr 4, 2019

Hmm, I've tried to fix the Python install part of the OSX travis build but it's still failing for reasons that appear to be beyond this PR

@bodono
Copy link
Owner

bodono commented Apr 5, 2019

Yeah, the tests can be a real pain, I'll merge this in then and we can try and fix them later.

@bodono bodono merged commit 3f45183 into bodono:master Apr 5, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants