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

Sparse3 #1845

Open
wants to merge 64 commits into
base: main
Choose a base branch
from
Open

Sparse3 #1845

wants to merge 64 commits into from

Conversation

kartikv
Copy link

@kartikv kartikv commented Mar 18, 2024

Just the start, but converting the sparse2 stuff to the FLINT 3 generic ring system.

@fredrik-johansson
Copy link
Collaborator

fredrik-johansson commented Mar 31, 2024

This looks great so far. Obviously, the blocker for merging is that there are tests that crash and that the documentation doesn't build cleanly.

A few nits to pick:

  • gr_*_mat_mul_mat should just be gr_*_mat_mul and gr_*_mat_mul should be named gr_lil_mat_mul_entrywise
  • "Addition and subtraction are not provided because they would create dense output." Well no, matrix + scalar should just fill in the main diagonal.
  • gr_*_mat_mul_vec should maybe be named _gr_*_mat_mul_vec with a corresponding gr_*_mat_mul_vec taking a gr_vec_t as input rather than a raw pointer to elements. The documentation doesn't match the code here. There could be such dual versions for other functions as well, e.g. solvers.
  • gr_*_mat_nz_product: "set res to the product of the nonzero entries in mat". This should return GR_UNABLE if encountering any entry that is maybe zero. I suggest adding helper functions to check if sparse vectors/matrices are in a strong canonical form in the sense that there are no maybe-zero entries.
  • In the solvers: if (M->r != M->c) return 0; /* TODO */ should return GR_UNABLE (if you plan to support nonsquare systems some time in the future) or GR_DOMAIN (if this isn't allowed at all). You should also clarify in the documentation whether the matrices need to be full-rank and what happens otherwise.
  • gr_coo_mat_is_zero should not mutate its argument; you could have a separate _gr_coo_mat_is_zero that does.
  • There are some places in the code where int is used as an index (should be slong).

BTW, a great way to test the sparse vectors and matrices is to wrap them for generics like gr_vec_t and gr_mat_t (see gr/vector.c and gr/matrix.c). You can then test a wide range of operations with gr_test_ring.

@kartikv
Copy link
Author

kartikv commented Mar 31, 2024

This looks great so far. Obviously, the blocker for merging is that there are tests that crash and that the documentation doesn't build cleanly.

I may need some help with this, since the tests do not crash for me (I would not push otherwise)...are there settings I need to try when I configure/build flint to see these issues? I can try to get some other platforms to test on, I'm on an M3 Mac.

A few nits to pick:

  • gr_*_mat_mul_mat should just be gr_*_mat_mul and gr_*_mat_mul should be named gr_lil_mat_mul_entrywise

Happy with the latter, but the former was intended to convey that I'm multiplying by a gr_mat, not another gr_lil_mat: my assumption is that that with no other descriptor, one would assume that the two arguments would be the same type.

  • "Addition and subtraction are not provided because they would create dense output." Well no, matrix + scalar should just fill in the main diagonal.

Sorry, that was copied over from a previous version, yes that will change.

  • gr_*_mat_mul_vec should maybe be named _gr_*_mat_mul_vec with a corresponding gr_*_mat_mul_vec taking a gr_vec_t as input rather than a raw pointer to elements. The documentation doesn't match the code here. There could be such dual versions for other functions as well, e.g. solvers.

Sounds good, will do.

  • gr_*_mat_nz_product: "set res to the product of the nonzero entries in mat". This should return GR_UNABLE if encountering any entry that is maybe zero. I suggest adding helper functions to check if sparse vectors/matrices are in a strong canonical form in the sense that there are no maybe-zero entries.

That's fine.

  • In the solvers: if (M->r != M->c) return 0; /* TODO */ should return GR_UNABLE (if you plan to support nonsquare systems some time in the future) or GR_DOMAIN (if this isn't allowed at all). You should also clarify in the documentation whether the matrices need to be full-rank and what happens otherwise.

Sorry, those should have all been replaced with is_square, will take care of that. And nothing needs to be full rank, I'll clarify in the documentation but Wiedemann will just return a solution and Lanzcos a pseudo-solution (if one exists).

  • gr_coo_mat_is_zero should not mutate its argument; you could have a separate _gr_coo_mat_is_zero that does.

Ok, is it alright if it just returns GR_UNABLE if the matrix is not in canonical form?

  • There are some places in the code where int is used as an index (should be slong).

Thanks, I'll dig out all of those.

BTW, a great way to test the sparse vectors and matrices is to wrap them for generics like gr_vec_t and gr_mat_t (see gr/vector.c and gr/matrix.c). You can then test a wide range of operations with gr_test_ring.

Will do, once things are in a good shape for the rings they definitely should work for. :-)

int gr_lil_mat_solve_block_wiedemann(gr_ptr x, const gr_lil_mat_t M, gr_srcptr b, slong block_size, flint_rand_t state, gr_ctx_t ctx)

Solve `Mx = b` for a sparse matrix `M` and *M->c* long column vector `b`, using either Lanczos' or Wiedemann's algorithm.
Both are randomized algorithms which use a given random state machine, and thus may fail without provably no solution
Copy link
Contributor

Choose a reason for hiding this comment

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

"fail without ... no solution" is very hard to read, sort of triple negation

Both are randomized algorithms which use a given random state machine, and thus may fail without provably no solution
(returning `GR_UNABLE`). Both have block variants which are better for large matrices, and take an extra parameter of
`block_size`. The (block) Wiedemann algorithm requires the given matrix to be square, but not symmetric: the Lanczos
algorithm requires both, but assumes the given matrix may be neither, so instead solves `M^TMx = M^Tb`. Thus, it may
Copy link
Contributor

Choose a reason for hiding this comment

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

both -> both square and symmetric

@kartikv
Copy link
Author

kartikv commented Apr 4, 2024 via email

@dimpase
Copy link
Contributor

dimpase commented Apr 4, 2024

Let's be more precise here. Randomized algorithms are of two types - Las Vegas and Monte Carlo. https://en.wikipedia.org/wiki/Randomized_algorithm

What type do we have here?

@kartikv
Copy link
Author

kartikv commented Apr 5, 2024 via email

@dimpase
Copy link
Contributor

dimpase commented Apr 6, 2024

an $\epsilon$-approximation is a correct answer in the case of ball arithmetic (with balls of this radius).

@munuxi
Copy link

munuxi commented Apr 22, 2024

In the macro GR_SPV_RFL_TEMPLATE of gr_sparse_vec/arith.c, if a_nnz or b_nnz is zero, the inital a_nz_idx or a_nz_idx is -1 and it's impossible to visit (A_VEC)->inds[a_nz_idx] (or B) in this case:

    ...
    a_nz_idx = a_nnz-1;                                                                                    \
    b_nz_idx = b_nnz-1;                                                                                    \
    dest_nz_idx = new_nnz-1;                                                                               \
    while (a_nz_idx >= -1 && b_nz_idx >= -1 && status == GR_SUCCESS)                                       \
    {                                                                                                      \
        if (a_nz_idx == -1 && b_nz_idx == -1) break;                                                       \
        a_ind = (A_VEC)->inds[a_nz_idx];                                                                   \
        b_ind = (B_VEC)->inds[b_nz_idx];                                                                   \
        if (b_nz_idx == -1 || (a_nz_idx >= 0 && a_ind > b_ind))                                                                              \
        ...

This could appear when a row of a sparse matrix is empty, which leads to the crash of some tests of sparse matrix.

#include "test_helpers.h"
#include "gr_sparse_mat.h"

#define CHECK_TEST(x, name) { if (GR_SUCCESS != (x)) { flint_printf("FAIL %s\n", (name)); flint_abort(); } }
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do not abort in the test function. Instead, let the main function for the test (i.e. main.c) take care of any aborts. So instead, just return some non-zero value to the main function and it will abort appropriately.

If you can, use TEST_FUNCTION_FAIL, but this has to be put into the top-level function in this file, i.e. under TEST_FUNCTION_START(my_function, state).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also, this definition is in each file, which leads to a lot of code duplication.

@@ -0,0 +1,27 @@
#include <string.h>
Copy link
Collaborator

Choose a reason for hiding this comment

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

A lot of copyright claims are missing. It is useful to know who wrote the code, so one knows who to contact if something needs to be fixed.

Comment on lines +112 to +124
// flint_printf("\n\ncoo_mat = "); status |= gr_coo_mat_print_nz(coo_mat, ctx); flint_printf("\nnnz = %d\n", coo_mat->nnz);
status |= gr_lil_mat_set_coo_mat(lil_mat, coo_mat, ctx);
// flint_printf("\n\nlil_mat = "); status |= gr_lil_mat_print_nz(lil_mat, ctx); flint_printf("\nnnz = %d\n", lil_mat->nnz);
status |= gr_lil_mat_set(lil_mat2, lil_mat, ctx);
// flint_printf("\n\nlil_mat = "); status |= gr_lil_mat_print_nz(lil_mat2, ctx); flint_printf("\nnnz = %d\n", lil_mat2->nnz);
status |= gr_csr_mat_set_lil_mat(csr_mat, lil_mat2, ctx);
// flint_printf("\n\ncsr_mat = "); status |= gr_csr_mat_print_nz(csr_mat, ctx); flint_printf("\nnnz = %d\n", csr_mat->nnz);
status |= gr_mat_set_csr_mat(dmat, csr_mat, ctx);
// flint_printf("\n\nmat = "); status |= gr_mat_print(dmat, ctx);
status |= gr_lil_mat_set_mat(lil_mat2, dmat, ctx);
// flint_printf("\n\nlil_mat = "); status |= gr_lil_mat_print_nz(lil_mat2, ctx); flint_printf("\nnnz = %d\n", lil_mat2->nnz);
status |= gr_coo_mat_set_lil_mat(coo_mat2, lil_mat2, ctx);
// flint_printf("\n\ncoo_mat = "); status |= gr_coo_mat_print_nz(coo_mat2, ctx); flint_printf("\nnnz = %d\n", coo_mat2->nnz);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is it beneficial to have theses print statement left in the code?

@albinahlback
Copy link
Collaborator

Is there any I/O-functionality?

@fredrik-johansson
Copy link
Collaborator

In the macro GR_SPV_RFL_TEMPLATE of gr_sparse_vec/arith.c, if a_nnz or b_nnz is zero, the inital a_nz_idx or a_nz_idx is -1 and it's impossible to visit (A_VEC)->inds[a_nz_idx] (or B) in this case:

    ...
    a_nz_idx = a_nnz-1;                                                                                    \
    b_nz_idx = b_nnz-1;                                                                                    \
    dest_nz_idx = new_nnz-1;                                                                               \
    while (a_nz_idx >= -1 && b_nz_idx >= -1 && status == GR_SUCCESS)                                       \
    {                                                                                                      \
        if (a_nz_idx == -1 && b_nz_idx == -1) break;                                                       \
        a_ind = (A_VEC)->inds[a_nz_idx];                                                                   \
        b_ind = (B_VEC)->inds[b_nz_idx];                                                                   \
        if (b_nz_idx == -1 || (a_nz_idx >= 0 && a_ind > b_ind))                                                                              \
        ...

This could appear when a row of a sparse matrix is empty, which leads to the crash of some tests of sparse matrix.

Good catch. I pushed a fix and the sparse_mat tests now pass as follows:

gr_sparse_mat_init...
gr_sparse_mat_init                                0.00   (PASS)
gr_sparse_mat_conversion...
gr_sparse_mat_conversion                          0.22   (PASS)
gr_sparse_mat_randtest...
gr_sparse_mat_randtest                            0.11   (PASS)
gr_sparse_mat_arith...
gr_sparse_mat_arith                               1.79   (PASS)
gr_sparse_mat_mul...
gr_sparse_mat_mul                                 0.06   (PASS)
gr_sparse_mat_solve...
solving Ax = b........................PASS
Solved 0 with rref
Solved 0 with lu
Solved 100 with Lanczos
Solved 0 with block Lanczos
	Found no solution for 100/100 examples
Solved 100 with Wiedemann
Solved 100 with block Wiedemann
gr_sparse_mat_solve                               2.73   (PASS)

All tests passed for gr_sparse_mat.

Those "Solved 0" look suspect?

@kartikv
Copy link
Author

kartikv commented Apr 27, 2024 via email

@kartikv
Copy link
Author

kartikv commented Apr 27, 2024 via email

@kartikv
Copy link
Author

kartikv commented Apr 27, 2024 via email

@albinahlback
Copy link
Collaborator

Only write and print functions, nothing binary so far: happy to take proposals, I don't know what gr does.

Ah, I see now. What's the current print format?

@kartikv
Copy link
Author

kartikv commented Apr 27, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants