osqp / osqp.rs

Rust interface to OSQP: The Operator Splitting QP Solver

Home Page:https://osqp.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Problem Without Constraints

georgwi opened this issue · comments

Hi there and thank you for providing this crate! I have noticed a small issue:

If the Problem is created with 0 bounds (specifically A has zero rows) the updating of the A matrix using a matrix of the same sparsity structure fails in CscMatrix::assert_same_sparsity_structure(&self, other: &CscMatrix)! I am aware that a problem without bounds can be solved more easily and does not technically require a QP solver but in my application the number of constraints is determined at runtime and I would prefer not having to handle the zero case separately.

Here is what I have found in more detail: the matrix that is created in the problems workspace will have a single data value while the original A matrix had zero:

thread 'tests::no_constraints' panicked at 'assertion failed: `(left == right)`
  left: `[]`,
 right: `[0]`', src/csc.rs:201:9
stack backtrace:
   0: rust_begin_unwind
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/panicking.rs:493:5
   1: core::panicking::panic_fmt
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/core/src/panicking.rs:92:14
   2: core::panicking::assert_failed::inner
   3: core::panicking::assert_failed
             at /home/georg/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/panicking.rs:143:5
   4: osqp::csc::CscMatrix::assert_same_sparsity_structure
             at ./src/csc.rs:201:9
   5: osqp::tests::no_constraints
             at ./src/lib.rs:552:13
   6: osqp::tests::no_constraints::{{closure}}
             at ./src/lib.rs:514:5
   7: core::ops::function::FnOnce::call_once
             at /home/georg/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:227:5
   8: core::ops::function::FnOnce::call_once
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/core/src/ops/function.rs:227:5

Here is my test that creates a workspace without constraints and then checks the sparsity structure of the matrices matches:

#[test]
fn no_constraints() {
    let n = 10;
    let m = 0;
    let p = CscMatrix::from_column_iter_dense(n, n, std::iter::repeat(0.0)).into_upper_tri();
    let a = CscMatrix::from_column_iter_dense(m, n, std::iter::repeat(0.0));
    let q = &vec![0.0; n];
    let l = &vec![0.0; m];
    let u = &vec![0.0; m];
    let settings = Settings::default();

    unsafe {
        let mut P_ffi = p.to_ffi();
        let mut A_ffi = a.to_ffi();

        // this check works
        let A_from_ffi = CscMatrix::from_ffi(&A_ffi);
        a.assert_same_sparsity_structure(&A_from_ffi);


        let data = ffi::OSQPData {
            n: n as ffi::osqp_int,
            m: m as ffi::osqp_int,
            P: &mut P_ffi,
            A: &mut A_ffi,
            q: q.as_ptr() as *mut float,
            l: l.as_ptr() as *mut float,
            u: u.as_ptr() as *mut float,
        };
        let settings = &settings.inner as *const ffi::OSQPSettings as *mut ffi::OSQPSettings;
        let mut workspace: *mut ffi::OSQPWorkspace = ptr::null_mut();
        let status = ffi::osqp_setup(&mut workspace, &data, settings);
        assert_eq!(status as ffi::osqp_error_type, 0, "setup failed");

        // this check fails
        let A_from_ffi = CscMatrix::from_ffi((*(*workspace).data).A);
        a.assert_same_sparsity_structure(&A_from_ffi);
    }
}

I think this is caused by the allocation of the native workspace A->nzmax = nzmax = c_max(nzmax, 1);? This can, of course, be avoided by never creating a problem without bounds (for example, a single constraint with infinite bounds works fine). But I was wondering if this would be something that could be handled internally in the future? My suggestions would be

  • at least fail with a helpful error message if a problem is created and A has zero rows.
  • handle this case specifically inside the assert_same_sparsity_structure method

I believe the second option is valid as the single data entry that the workspace matrix has should never be actually used but maybe someone with more insight could confirm this. If any of this sounds good and you would like I can provide a PR with this addition.

From a cursory look I'm not sure that OSQP behaves correctly with an empty A matrix, it calls malloc rather than calloc so when nnz == 0, A->i and A->x will contain random data, I may be wrong though.

I am not familiar with the implementation details but if I understand the csc layout correctly the information that the matrix is empty would already be contained in A->p which should just contain zeros. In addition, the row count (zero) is stored inside the matrix. So I'd be surprised if the random data was actually read inside the solve function and this would throw off the solver (although I am not sure of this).

You're right, I had forgotten that the A->p, aka indptr, contains ncols + 1 elements with the last being the length of A->i and A->x.

In which case it should be simple enough to special case matrices with ncols == 0 || nrows == 0 as always having the same sparsity structure even if indices or data are of different lengths.

In fact it would be even better to special-case CscMatrix::from_ffi to set nnz to zero if ncols == 0 || nrows == 0 holds.

A fix for this is in master. @georgwi can you test it and let me know if it works.

@ebarnard thank you, the problem is resolved for me on master!