-
Notifications
You must be signed in to change notification settings - Fork 36
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
Feature: Basic matrix operations until QR and SVD algorithms #172
Conversation
hyiltiz
commented
Jan 21, 2024
•
edited
Loading
edited
- add basic matrix operations
- add stable QR decomposition algorithm
- add simple SVD
- finalize tests
I was going to mention using (defn dot [v1 v2]
(apply + (map * v1 v2))) expressing as: (defn dot [v1 v2]
(+ ;(map * v1 v2))) but when I got to comparing the speed difference, I got the sense that
That got me thinking though...may be using
I did something similar for $ janet
Janet 1.33.0-23b0fe9f linux/x64/gcc - '(doc)' for help
repl:1:> (do (use spork/test) :done)
:done
repl:2:> (defn subtract1 [v1 v2] (map - v1 v2))
<function subtract1>
repl:3:> (defn subtract2 [v1 v2] (seq [i :in v1 j :in v2] (- i j)))
<function subtract2>
repl:4:> (timeit-loop [:timeout 10] (subtract1 [2 3 8 9] [1 0 -2 7]))
Elapsed time: 10.000s, 0.8114µs/body
nil
repl:5:> (timeit-loop [:timeout 10] (subtract2 [2 3 8 9] [1 0 -2 7]))
Elapsed time: 10.000s, 0.7926µs/body
nil Not that different in this case I guess. But using
Hmm, may be using
Don't know if this kind of thing is worth it, but FWIW. |
Not sure how I feel about: (defn sign [x]
(if (>= x 0) 1 -1)) If we're going for this, may be we want |
I noticed there are now 2 functions named |
Thank you so much for all the comments! I worked on this to introduce basic matrix utilities to Janet, hence started adding anything that is needed until QR and SVD was possible. There are surely a lot of optimizations that are possible, as @sogaiu has identified. I am more than happy to adopt those changes. A big question is that In a minor note, the earlier |
In general I'm not a fan of trying to do the kinds of optimizations I experimented with above because:
To explain some of the motivation for earlier optimization comments...
|
Since I've been asked to comment:
> (defn trans-v [xs] (map array xs))
<function trans-v>
> (def v (range 5))
@[0 1 2 3 4]
> (trans-v v)
@[@[0] @[1] @[2] @[3] @[4]]
> (trans-v (trans-v v))
@[@[@[0]] @[@[1]] @[@[2]] @[@[3]] @[@[4]]]
Perhaps explicitly transposing 1xN or Nx1 matrices would be more clear?
|
The QR algorithm is the (one of) the best known; the SVD is (one of) the simplest one given QR, and there are a lot of other algorithms designed for efficiency. Those are usually complex enough that it is usually not re-implemented but simply wrapped/called from the LAPACK/BLAS/eigen libraries. I think this is a simple and direct first step for a purely janet-based implementation. Thank you so much all for the informative and detailed feedback! Given the valuable feedback above, I'll revise the draft and see if we can get it closer to a PR that we can consider for a merge. |
spork/math.janet
Outdated
(let [m3 @[@[1 2 3] @[4 5 6] @[7 8 9]]] | ||
(assert (m-approx= (matmul m3 (ident (rows m3))) | ||
m3) | ||
"matmul identity left: this test succeeds here but fails in suite-math.janet")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any idea why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed. Caused by stateful computation due to join-rows not being idempotent (array/concat
mutates first argument).
|
||
(assert (m-approx= (matmul m3 (ident (rows m3))) | ||
m3) | ||
"matmul identity left") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fails here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed. Caused by stateful computation due to join-rows not being idempotent (array/concat
mutates first argument).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thansk @sogaiu for digging into it.
New changes:
|
Some minor comments:
|
Adjusted for all of the points. Some of the points refer to functions such as |
Ah, sorry about those unrelated bits. Not sure what to do about those. Will think on it. Hope to look in more detail soon, but wondering about this change. I'm not too familiar with GH CI, but is this intentional? |
Yes; that change should allow it (and all other incoming PRs) to run the linting and checks without having repo admin to click Approve. Less friction for PR contributions. Probably should've been a separate PR, but it is just a single line of change so may as well... |
.github/workflows/test.yml
Outdated
@@ -3,7 +3,7 @@ name: Test | |||
on: | |||
push: | |||
branches: [ master ] | |||
pull_request: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be part of the separate PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's my feeling as well, but I think it's really up to bakpakin to decide.
I do think that separating makes later history easier to understand. I know we don't all follow this course of action all of the time though (^^;
Possibly reversion is simpler too? Not so sure about this point though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say it is just a good manner not to "hide" surprises :-).
TBH I am definitely on the side of having that CI config in. I just do not like the sneaky way :-).
There is a small issue with the tests. The following diff may fix it: diff --git a/test/suite-math.janet b/test/suite-math.janet
index 1950c79..edaa477 100644
--- a/test/suite-math.janet
+++ b/test/suite-math.janet
@@ -420,90 +420,90 @@
res-svd (svd m3)
U (res-svd :U)
S (res-svd :S)
- V (res-svd :V)
- (assert (deep= m23 m23)
- "deep= matrix")
-
- (assert (deep= (flipud m23)
- @[@[4 5 6] @[1 2 3]])
- "flipud")
-
- (assert (deep= (fliplr m23)
- @[@[3 2 1] @[6 5 4]])
- "fliplr")
-
- (assert (deep= (join-rows m3 m23)
- @[@[1 2 3]
- @[4 5 6]
- @[7 8 9]
- @[1 2 3]
- @[4 5 6]])
- "join-rows")
-
- (assert (deep= (join-cols m23 m23)
- @[@[1 2 3 1 2 3]
- @[4 5 6 4 5 6]])
- "join-cols")
-
- (assert (m-approx= (res1-m3 :Q)
- @[@[-0.123091490979333 -0.492365963917331 -0.861640436855329]
- @[-0.492365963917331 0.784145597779528 -0.377745203885826]
- @[-0.861640436855329 -0.377745203885826 0.338945893199805]])
- "qr1-q")
-
- (assert (m-approx= (res1-m3 :m^)
- @[@[-0.0859655700236277 -0.171931140047257]
- @[-0.90043974754135 -1.8008794950827]])
- "qr1-m")
-
- (assert (m-approx= (res-m3 :Q)
- @[@[-0.123091490979333 0.904534033733291 0.408248290463864]
- @[-0.492365963917331 0.301511344577765 -0.816496580927726]
- @[-0.861640436855329 -0.301511344577764 0.408248290463863]])
- "qr-q")
-
- (assert (m-approx= (res-m3 :R)
- @[@[-8.12403840463596 -9.60113629638795 -11.0782341881399]
- @[-8.88178419700125e-16 0.90453403373329 1.80906806746658]
- @[-8.88178419700125e-16 -4.44089209850063e-16 8.88178419700125e-16]])
- "qr-r")
-
- (assert (m-approx= U
- @[@[0.214837238368396 -0.887230688346371 0.408248290463863]
- @[0.520587389464737 -0.249643952988298 -0.816496580927726]
- @[0.826337540561078 0.387942782369775 0.408248290463863]])
- "svd-U")
-
- (assert (m-approx= S
- @[@[16.8481033526142 0 0]
- @[-1.1642042401554e-237 -1.06836951455471 0]
- @[-6.42285339593621e-323 0 3.62597321469472e-16]])
-
- "svd-S")
-
- (assert (m-approx= V
- @[@[0.479671177877771 -0.776690990321559 0.408248290463863]
- @[0.572367793972062 -0.0756864701045582 -0.816496580927726]
- @[0.665064410066353 0.625318050112442 0.408248290463863]])
- "svd-U")
-
- (assert (m-approx= (matmul m3 (ident (rows m3)))
- m3)
- "matmul identity left")
-
- (assert (m-approx= (matmul (ident (rows m3)) m3)
- m3)
- "matmul identity right")
-
- (assert (m-approx= m3 (matmul (res-m3 :Q) (res-m3 :R)))
- "qr-square decompose")
-
- (assert (m-approx= m23 (matmul (res-m23 :Q) (res-m23 :R)))
- "qr-non-square decompose")
-
- (assert (m-approx= m3 (reduce matmul (ident (rows U))
- (array U S (trans V))))
- "svd-USV' decompose")])
+ V (res-svd :V)]
+ (assert (deep= m23 m23)
+ "deep= matrix")
+
+ (assert (deep= (flipud m23)
+ @[@[4 5 6] @[1 2 3]])
+ "flipud")
+
+ (assert (deep= (fliplr m23)
+ @[@[3 2 1] @[6 5 4]])
+ "fliplr")
+
+ (assert (deep= (join-rows m3 m23)
+ @[@[1 2 3]
+ @[4 5 6]
+ @[7 8 9]
+ @[1 2 3]
+ @[4 5 6]])
+ "join-rows")
+
+ (assert (deep= (join-cols m23 m23)
+ @[@[1 2 3 1 2 3]
+ @[4 5 6 4 5 6]])
+ "join-cols")
+
+ (assert (m-approx= (res1-m3 :Q)
+ @[@[-0.123091490979333 -0.492365963917331 -0.861640436855329]
+ @[-0.492365963917331 0.784145597779528 -0.377745203885826]
+ @[-0.861640436855329 -0.377745203885826 0.338945893199805]])
+ "qr1-q")
+
+ (assert (m-approx= (res1-m3 :m^)
+ @[@[-0.0859655700236277 -0.171931140047257]
+ @[-0.90043974754135 -1.8008794950827]])
+ "qr1-m")
+
+ (assert (m-approx= (res-m3 :Q)
+ @[@[-0.123091490979333 0.904534033733291 0.408248290463864]
+ @[-0.492365963917331 0.301511344577765 -0.816496580927726]
+ @[-0.861640436855329 -0.301511344577764 0.408248290463863]])
+ "qr-q")
+
+ (assert (m-approx= (res-m3 :R)
+ @[@[-8.12403840463596 -9.60113629638795 -11.0782341881399]
+ @[-8.88178419700125e-16 0.90453403373329 1.80906806746658]
+ @[-8.88178419700125e-16 -4.44089209850063e-16 8.88178419700125e-16]])
+ "qr-r")
+
+ (assert (m-approx= U
+ @[@[0.214837238368396 -0.887230688346371 0.408248290463863]
+ @[0.520587389464737 -0.249643952988298 -0.816496580927726]
+ @[0.826337540561078 0.387942782369775 0.408248290463863]])
+ "svd-U")
+
+ (assert (m-approx= S
+ @[@[16.8481033526142 0 0]
+ @[-1.1642042401554e-237 -1.06836951455471 0]
+ @[-6.42285339593621e-323 0 3.62597321469472e-16]])
+
+ "svd-S")
+
+ (assert (m-approx= V
+ @[@[0.479671177877771 -0.776690990321559 0.408248290463863]
+ @[0.572367793972062 -0.0756864701045582 -0.816496580927726]
+ @[0.665064410066353 0.625318050112442 0.408248290463863]])
+ "svd-U")
+
+ (assert (m-approx= (matmul m3 (ident (rows m3)))
+ m3)
+ "matmul identity left")
+
+ (assert (m-approx= (matmul (ident (rows m3)) m3)
+ m3)
+ "matmul identity right")
+
+ (assert (m-approx= m3 (matmul (res-m3 :Q) (res-m3 :R)))
+ "qr-square decompose")
+
+ (assert (m-approx= m23 (matmul (res-m23 :Q) (res-m23 :R)))
+ "qr-non-square decompose")
+
+ (assert (m-approx= m3 (reduce matmul (ident (rows U))
+ (array U S (trans V))))
+ "svd-USV' decompose"))
(assert (= 10 (perm @[@[1 2] |
With the diff above, tests pass for me. Sorry about the Not sure what happened there (^^; |
Patch adopted. Separated workflow change into separate PR: #178. |
00f3760 passed all tests here 👍 |