Skip to content

Added a bandwidth feature #39978

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

Merged
merged 14 commits into from
May 11, 2025
Merged

Added a bandwidth feature #39978

merged 14 commits into from
May 11, 2025

Conversation

HenryWu2101
Copy link
Contributor

#13565
Added a bandwidth feature that gives the bandwidth of a matrix. The bandwidth of a matrix measures how far from the main diagonal the nonzero entries extend. Mostly used for storage efficiency / reordering to speed up solvers etc. For now it is just a new feature to add.

📝 Checklist

  • The title is concise and informative.
  • The description explains in detail what this PR is about.
  • I have linked a relevant issue or discussion.
  • I have created tests covering the changes.
  • I have updated the documentation and checked the documentation preview.

⌛ Dependencies

cc @tscrim

Copy link

github-actions bot commented Apr 20, 2025

Documentation preview for this PR (built with commit 2b47e90; changes) is ready! 🎉
This preview will update shortly after each push to this PR.

@dcoudert
Copy link
Contributor

dcoudert commented Apr 20, 2025

The order of operations could be improved by visiting the matrix by diagonals and stop as soon as a non zero value is found. it is best to visit simultaneously the upper and the lower diagonals.
You can use #39963 here.

I'm not sure that the method should be called bandwidth. May be get_bandwidth ? When you ask for the bandwidth, you usually expect to get an ordering of the rows/columns that minimizes the bandwidth.
See for instance method bandwidth in graphs https://doc.sagemath.org/html/en/reference/graphs/sage/graphs/graph_decompositions/bandwidth.html

@HenryWu2101
Copy link
Contributor Author

HenryWu2101 commented Apr 21, 2025

@dcoudert For the namesake, I wasn't careful to see that there is a method with the same name but different behavior implemented elsewhere. In that case, naming it the same would not be appropriate, will change to get_bandwidth. And additionally, I'm not sure if it would be necessary to propose implement bandwidth in matrix2.pyx as a follow-up issue.

As for using diagonal to improve efficiency, it was exactly what I thought when I created the diagonal PR and then got to this issue when looking for something similar in the issue pool. But as I understand it, the bandwidth of a matrix is the smallest distance for all non-zero entries to lie from the main diagonal. At that time, I felt iterating from main diagonal outward would not be an improvement (since it still needs to iterate to the end, in case a non-zero entry lies at the top corner).

So to clarify what you proposed, you're saying if I go through every diagonal starting from the corner to the main diagonal via diagonal, and I stop as soon as I reach a non-zero entry (and visit from both directions simultaneously), I would have an improvement with only $O(n^2)$ as the worst case scenario.

@dcoudert
Copy link
Contributor

@dcoudert For the namesake, I wasn't careful to see that there is a method with the same name but different behavior implemented elsewhere. In that case, naming it the same would not be appropriate, will change to get_bandwidth. And additionally, I'm not sure if it would be necessary to propose implement bandwidth in matrix2.pyx as a follow-up issue.

I don't think it's necessary.

So to clarify what you proposed, you're saying if I go through every diagonal starting from the corner to the main diagonal via diagonal, and I stop as soon as I reach a non-zero entry (and visit from both directions simultaneously), I would have an improvement with only $O(n^2)$ as the worst case scenario.

Precisely and this is what was proposed in the original patch #13565. The idea is to stop computation as soon as possible. This should be an improvement for large matrix.

@HenryWu2101
Copy link
Contributor Author

@dcoudert Hi so I've adjusted the get_bandwidth() method with the proposed algorithm. Unfortunately, I cannot come up with a cleaner way to construct the code than the status quo.

@tscrim
Copy link
Collaborator

tscrim commented Apr 30, 2025

I don't think this will work for non-square matrices. You should add a test for this case. As such, I think it is better to split it into two separate loops. This way to can cleanly break out of each one and can account for the difference in the rows and columns.

Two minor points: (1) Please include the definition in the docstring. (2) EXAMPLES:: should be followed by a blankline.

@HenryWu2101
Copy link
Contributor Author

@tscrim Hi a non-square matrix doctest is added, and I've fixed some minor bugs in the loop. They should work fine for both square and non-square now. Also adjusted for the empty line under EXAMPLE::

@dcoudert
Copy link
Contributor

I have a doubt regarding the definition of bandwidth used in this PR. According the wikipedia page https://en.wikipedia.org/wiki/Band_matrix, it is the maximum of max_up and max_down, not the sum.
This is also the case in graphs, we use the max, not the sum.

@HenryWu2101
Copy link
Contributor Author

I have a doubt regarding the definition of bandwidth used in this PR. According the wikipedia page https://en.wikipedia.org/wiki/Band_matrix, it is the maximum of max_up and max_down, not the sum. This is also the case in graphs, we use the max, not the sum.

That's a small technicality which can be easily changed. But it was by my search the sum of both. I'll look more into the page and get back on this issue.

@dcoudert
Copy link
Contributor

if it's a max, you can do:

        for i in range(diag_range, 0, -1):
            if any(x != zero for x in self.diagonal(i)):
                max_up = i
                break
        for i in range(diag_range, max_up, -1):
            if any(x != zero for x in self.diagonal(-i)):
                return i
        return max_up

@HenryWu2101
Copy link
Contributor Author

@dcoudert It would seem my initial search was misguided. I've changed the code and doctest accordingly

@dcoudert
Copy link
Contributor

May be we can do even better like this:

        for i in range(diag_range, 0, -1):
            if (any(x != zero for x in self.diagonal(i))
                    or any(x != zero for x in self.diagonal(-i))):
                return i
        return 0

@HenryWu2101
Copy link
Contributor Author

May be we can do even better like this:

@dcoudert Yes, that would significantly reduce the amount of code needed. I've pushed the latest version and tested out to make sure it works with past test cases.
I was skeptical before on creating a list each time and using the python any to check for the non-zero element because of all the extra space (though it's python so probably not a huge concern), so then I went for iteration. In this case, would adding 2 long checks in an if-statement be a good idea?

@dcoudert
Copy link
Contributor

I'm not sure what you propose.
You can test on your side which form is the most efficient.
The current code is simple, but if you have something faster, it's always welcome.

@HenryWu2101
Copy link
Contributor Author

I'm not sure what you propose.

@dcoudert I'm fine with your last suggestion, and have adjusted and pushed the code accordingly. I guess I was more talking about the coding style that I'm personally used to. But computation time wise, I think currently there's no other improvement. If it aligns with sage requirement, I'm happy with the status quo.

Copy link
Collaborator

@tscrim tscrim left a comment

Choose a reason for hiding this comment

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

Thank you. Just another small tweak suggestion from me.

Copy link
Collaborator

@tscrim tscrim left a comment

Choose a reason for hiding this comment

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

Two other minor things.

@HenryWu2101 HenryWu2101 requested a review from tscrim May 1, 2025 04:24
Copy link
Contributor

@dcoudert dcoudert left a comment

Choose a reason for hiding this comment

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

LGTM.

simple an elegant code.

vbraun pushed a commit to vbraun/sage that referenced this pull request May 4, 2025
sagemathgh-39978: Added a bandwidth feature
    
<!-- ^ Please provide a concise and informative title. -->
<!-- ^ Don't put issue numbers in the title, do this in the PR
description below. -->
<!-- ^ For example, instead of "Fixes sagemath#12345" use "Introduce new method
to calculate 1 + 2". -->
<!-- v Describe your changes below in detail. -->
<!-- v Why is this change required? What problem does it solve? -->
<!-- v If this PR resolves an open issue, please link to it here. For
example, "Fixes sagemath#12345". -->

sagemath#13565
Added a bandwidth feature that gives the bandwidth of a matrix. The
bandwidth of a matrix measures how far from the main diagonal the
nonzero entries extend. Mostly used for storage efficiency / reordering
to speed up solvers etc. For now it is just a new feature to add.

### 📝 Checklist

<!-- Put an `x` in all the boxes that apply. -->

- [x] The title is concise and informative.
- [x] The description explains in detail what this PR is about.
- [x] I have linked a relevant issue or discussion.
- [x] I have created tests covering the changes.
- [ ] I have updated the documentation and checked the documentation
preview.

### ⌛ Dependencies

<!-- List all open PRs that this PR logically depends on. For example,
-->
<!-- - sagemath#12345: short description why this is a dependency -->
<!-- - sagemath#34567: ... -->

cc @tscrim
    
URL: sagemath#39978
Reported by: Henry Wu
Reviewer(s): David Coudert, Henry Wu, Travis Scrimshaw
vbraun pushed a commit to vbraun/sage that referenced this pull request May 5, 2025
sagemathgh-39978: Added a bandwidth feature
    
<!-- ^ Please provide a concise and informative title. -->
<!-- ^ Don't put issue numbers in the title, do this in the PR
description below. -->
<!-- ^ For example, instead of "Fixes sagemath#12345" use "Introduce new method
to calculate 1 + 2". -->
<!-- v Describe your changes below in detail. -->
<!-- v Why is this change required? What problem does it solve? -->
<!-- v If this PR resolves an open issue, please link to it here. For
example, "Fixes sagemath#12345". -->

sagemath#13565
Added a bandwidth feature that gives the bandwidth of a matrix. The
bandwidth of a matrix measures how far from the main diagonal the
nonzero entries extend. Mostly used for storage efficiency / reordering
to speed up solvers etc. For now it is just a new feature to add.

### 📝 Checklist

<!-- Put an `x` in all the boxes that apply. -->

- [x] The title is concise and informative.
- [x] The description explains in detail what this PR is about.
- [x] I have linked a relevant issue or discussion.
- [x] I have created tests covering the changes.
- [ ] I have updated the documentation and checked the documentation
preview.

### ⌛ Dependencies

<!-- List all open PRs that this PR logically depends on. For example,
-->
<!-- - sagemath#12345: short description why this is a dependency -->
<!-- - sagemath#34567: ... -->

cc @tscrim
    
URL: sagemath#39978
Reported by: Henry Wu
Reviewer(s): David Coudert, Henry Wu, Travis Scrimshaw
vbraun pushed a commit to vbraun/sage that referenced this pull request May 6, 2025
sagemathgh-39978: Added a bandwidth feature
    
<!-- ^ Please provide a concise and informative title. -->
<!-- ^ Don't put issue numbers in the title, do this in the PR
description below. -->
<!-- ^ For example, instead of "Fixes sagemath#12345" use "Introduce new method
to calculate 1 + 2". -->
<!-- v Describe your changes below in detail. -->
<!-- v Why is this change required? What problem does it solve? -->
<!-- v If this PR resolves an open issue, please link to it here. For
example, "Fixes sagemath#12345". -->

sagemath#13565
Added a bandwidth feature that gives the bandwidth of a matrix. The
bandwidth of a matrix measures how far from the main diagonal the
nonzero entries extend. Mostly used for storage efficiency / reordering
to speed up solvers etc. For now it is just a new feature to add.

### 📝 Checklist

<!-- Put an `x` in all the boxes that apply. -->

- [x] The title is concise and informative.
- [x] The description explains in detail what this PR is about.
- [x] I have linked a relevant issue or discussion.
- [x] I have created tests covering the changes.
- [ ] I have updated the documentation and checked the documentation
preview.

### ⌛ Dependencies

<!-- List all open PRs that this PR logically depends on. For example,
-->
<!-- - sagemath#12345: short description why this is a dependency -->
<!-- - sagemath#34567: ... -->

cc @tscrim
    
URL: sagemath#39978
Reported by: Henry Wu
Reviewer(s): David Coudert, Henry Wu, Travis Scrimshaw
@vbraun vbraun merged commit 4719fe0 into sagemath:develop May 11, 2025
20 of 21 checks passed
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.

4 participants