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

Introduce make-matrix-determinant for field-based matrices #105

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

NoahStoryM
Copy link
Contributor

Checklist

  • Bugfix
  • Feature
  • tests included
  • documentation

Description of change

Adds make-matrix-determinant constructor that accepts field operations (+-*/=), and refactors existing matrix-determinant to use make-matrix-determinant internally.

Tested on 100×100 matrices with the following code:

#lang typed/racket/base

(require math/matrix)

(random-seed 1)

(: rand (-> Index Index Real))
(define (rand i j) (random 1 9))

(: n Integer)
(define n 100)

(define M (build-matrix n n rand))

(time (displayln (matrix-determinant M)))

The new implementation shows no significant performance difference.

@pavpanchekha
Copy link
Collaborator

I'm sorta ambivalent about the code but why do we want this? I understand what a matrix is and how to find determinants in various fields or whatever. I also understand that this will allow you to handle arrays of Nothing. But why is this important and why are there any users to this thing? I don't see a need to express an API surface, especially a complicated one like this (it's not just a field, it need pivoting too!), without a clear description of some users who want it.

@NoahStoryM
Copy link
Contributor Author

Motivation for This PR

The original goal was to support zero-sized matrices to demonstrate mathematical concepts like category-theoretic null objects. However, this revealed deeper limitations in math/matrix:

  • Current operators assume (Matrix Number), which conflates multiple zero representations (0, 0.0, 0.0+0.0i).

  • Operators infer types by sampling elements, which fails for empty matrices.

By generalizing to accommodate algebraic structures, we not only expand the package's functionality but also establish a more robust foundation for handling edge cases like empty matrices.

Benefits of Algebraic Abstraction

In addition to support for more general types, reimplementing math/matrix using generalized algebraic operations offers significant advantages. Abstracting away Number-specific details allows us to concentrate on the mathematical essence of matrix operations.

Although I am not privy to the original implementation context of math/matrix, I suspect that certain matrix operations may have encountered issues due to the constraints imposed by Number. For instance, we can simplify the existing code logic to naturally support empty matrices. The reason why the code wasn't simplified in this way previously is likely due to the persistent type errors when dealing with Number.

By supporting more generalized matrix operations based on algebraic structures, we can eliminate the interference caused by specific types and focus solely on the core logic from a mathematical perspective. This approach will help clarify and refine the code's logic.

API Complexity Concerns

The existing API constructs a make function and passes the necessary functions as arguments. For example, the current matrix-determinant function employs Gaussian elimination, requiring a comparison function to identify the partial pivot, another to determine if an element is zero, and a division operation to eliminate variables.

As you pointed out, the current API does appear somewhat complex. However, I believe this complexity primarily stems from the requirements of implementing Gaussian elimination. When working with matrices based on more general algebraic structures, only a few operators are typically needed. For instance, commonly used generic matrix operations like matrix* do not require as many arguments. Compared to make-matrix-determinant, make-matrix* would be much simpler, for example, (make-matrix* add neg mul). This design seems acceptable to me.

@pavpanchekha
Copy link
Collaborator

Uhh, ok. This seems like a whole new abstraction that applies to one single method in the entire math library. This would only be justified in extremely compelling situations, which this isn't.

Your primary motivation seems to be allowing one to take the determinant of a zero-size matrix; that seems like a really rare edge case. While the library being "algebraic" or "categorical" is a nice to have, the goal of the library is actually doing linear algebra; zero-size matrices are in fact rare in actual workloads and adding APIs to support them seems odd.

Do you, as contributor, actually have code that needs this? Or do you just think this will make a nicer API?

@NoahStoryM
Copy link
Contributor Author

My primary use case involves these operators: identity-matrix, matrix*, matrix-augment, matrix-stack and block-diagonal-matrix. To support zero-sized matrices and algebraic abstraction, I propose splitting math/matrix into three modules:

  1. math/matrix/base: Contains core operations independent of element types (e.g., matrix?, matrix-augment and matrix-stack).
  2. math/matrix/algebra: Supports matrix operations over arbitrary algebraic structures (including operations requiring zero or one, such as identity-matrix).
  3. math/matrix: Focus on (Matrix Number)

While I don't personally need matrix-determinant, I chose it as a starting point because:

  • It's a classic matrix operation, making it a natural candidate for generalization.
  • It's relatively simple to refactor, serving as a good entry point for broader changes.

The make-matrix-determinant API mirrors the natural requirements of Gaussian elimination, much like how identity-matrix requires zero and one. For programmers implementing Gaussian elimination, this API should feel intuitive and practical.

@pavpanchekha
Copy link
Collaborator

I'd still love to hear what the use case is. In any case it kinda sounds like what you want to do is rewrite the entire library, with a new API, new organization, and so on. In which case—perhaps you should just write a package? And then maybe there's no actual need to put it in the core math library?

@NoahStoryM
Copy link
Contributor Author

I'd still love to hear what the use case is.

My core motivation is to demonstrate abstract algebraic concepts using matrices as a pedagogical tool. For example:

  • Null Objects: Empty matrices (e.g., 0×n or n×0) as identity elements in operations like (co)pairing (e.g., matrix-stack and matrix-augment).
  • (Co)Products: Block matrices as concrete implementations of (co)product objects.

The current math/matrix library cannot naturally express these concepts due to its lack of support for empty matrices. As a workaround, I plan to use flomat for my immediate needs.

While my personal focus is theoretical, generalized matrix operations are critical in many real-world domains:

  • Boolean Matrices: Representing graph adjacency matrices for pathfinding algorithms.
  • Finite Fields (GF(256)): Cryptographic algorithms (e.g., AES MixColumns).
  • Tropical Semirings: Shortest path problems in optimization.-

These use cases require matrix operations over non-Number algebraic structures, which this PR enables.

In any case it kinda sounds like what you want to do is rewrite the entire library, with a new API, new organization, and so on.

This PR introduces a lightweight abstraction layer (e.g., make-matrix-determinant) while preserving backward compatibility. No algorithms or data structures are rewritten—only existing logic is parameterized. Once this foundation is in place, future PRs can incrementally add support for custom rings/fields without disrupting existing users.

In which case—perhaps you should just write a package? And then maybe there's no actual need to put it in the core math library?

The core logic for operations like matrix determinant and matrix multiplication is already well-implemented in math/matrix. Generalizing these (e.g., replacing Number with algebraic parameters) avoids redundant reimplementation.

If you think this direction aligns with math/matrix's goals, I'm happy to proceed with incremental PRs. If not, I'll migrate this work to a separate package.

@pavpanchekha
Copy link
Collaborator

I think a separate package is better. The idea of a math library parameterized over fields is a good one, it's how many new languages like Lean work, but it's not how Racket mostly works (we have a specific numeric tower) and I think both you and your possible users are more interested in an abstract algebra library than a linear algebra library. Nothing wrong with that, but for example pivoting is a good idea in linear algebra and a bad one in abstract algebra. Doing both from the same library doesn't sound like a good move to me. If this makes sense I'd appreciate you closing the PR/issue.

@bdeket
Copy link
Contributor

bdeket commented Mar 20, 2025

Sorry to jump in.
I wanted to add my +1 to this PR.
My personal use case would be matrices that work on Bigfloats. I've previously rolled out the some of the functions I needed. But it would be nice to use math/matrix directly and have all matrix operations available.
(if available in another package that would of course also be nice)

@NoahStoryM
Copy link
Contributor Author

@pavpanchekha Thank you for the thoughtful perspective. I completely understand the value of keeping math/matrix focused on Racket's numeric tower. That said, there seems to be community interest in algebraic extensions (as seen in @bdeket's use case). Would you be open to leaving this PR open for 1-2 weeks to gather more community feedback?

  • If no strong demand emerges, I'll happily close it and spin off a package.
  • If interest persists, perhaps we could discuss a minimal abstraction layer?

This way we let real user needs guide the decision. Either way, I deeply respect your stewardship of the library.

@pavpanchekha
Copy link
Collaborator

Yes, sure, no problem keeping it open.

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.

3 participants