Matrix API (abstracting over dense and sparse matrices)

62 views
Skip to first unread message

Julian Michael

unread,
Dec 2, 2015, 9:40:03 PM12/2/15
to Scala Breeze
Hi scala-breeze,

I tried abstracting some of my code to use both sparse and dense matrices. I did it by just using the Matrix class instead of DenseMatrix. I was surprised and dismayed to find out that a lot of the basic functionality is missing for Matrix—slicing along rows and columns, or even multiplying the transpose of a matrix by a vector. In the former case, just wrote a typeclass implementation for Matrix[V], but I had to require V: Zero (understandably) and do runtime type tests (ew). It's a hack. It struck me that maybe the reason this wasn't already part of the Matrix API is that it would then cause DenseMatrix to also require V: Zero for slicing, because it would have to do dynamic dispatch on a method that required V: Zero and a different typeclass instance for DenseMatrix without that requirement might lead to ambiguous implicit resolution. (Not totally sure on that one, but implicit resolution for stuff like that is... well, I try to stay away from thinking too much about True Scala Complexity.)

In hindsight I think my mistake was early on when I chose to use the Matrix class. Abstraction should've been by typeclass, and after looking at VectorSpace.scala I realize that there are behemoths like MutableOptimizationSpace that might suit my purposes. Except logically I'm trying to do things with matrices like slicing individual columns or column ranges. I couldn't find that functionality in typeclasses except for the one-off UFunc style ones. And I'm not so sure if I want to import every single one...but maybe in combination with one of the big guys from breeze.math, it'd be well under control.

So I have a candidate solution, but I'm left wondering: what's the canonical way to do this? If Matrix shouldn't be used, perhaps it should be hidden outside of the package? Should I probably not be using Breeze for sparse matrices anyway? How's my candidate solution? At any rate, I might contribute implementations for CSCMatrix I need that aren't there. (If I end up needing more stuff.)

Thanks!

Julian

David Hall

unread,
Dec 2, 2015, 11:15:22 PM12/2/15
to scala-...@googlegroups.com
Hi Julian,

The generic Matrix trait is somewhat half-baked, as you noticed. It supports most of the binary operations and that's about it. So as long as you're not trying to get too fancy, it basically works. For these purposes, slices count as fancy.

One of the big impediments is that I haven't decided how I want to handle slices for general matrices, and for CSCMatrices in particular. DenseMatrix slices are views and are actually just DenseMatrices, while CSCMatrix slices would I guess also be CSCMatrices? Just more complicated? I dunno. Maybe we can make it so that there's a MatrixSlice[M <: Matrix[V], V](m: M, rowSlice: Range, colSlice: Range) type or something and make it so that both slices work the same way? That way we could maybe even get rid of DenseMatrix's majorStride and offset argument? That is appealing...

I'm always happy for people to contribute!

-- David


--
You received this message because you are subscribed to the Google Groups "Scala Breeze" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-breeze...@googlegroups.com.
To post to this group, send email to scala-...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/scala-breeze/bd44cf92-4718-4ff9-8fa5-625661acdf11%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Julian Michael

unread,
Dec 3, 2015, 12:01:59 AM12/3/15
to scala-...@googlegroups.com
David,

Thank you for the prompt reply. I see what you're saying. Sounds reasonable to me... maybe would also want "weird" matrix slice constructors for non-range slices.

But more generally I feel like we might run into these problems when we rely on inheritance instead of typeclasses for the way we do abstraction. What would you think of just using a typeclass for matrices instead? Then if something is missing, I think it'd be easier to cope with for an end user.

--
You received this message because you are subscribed to a topic in the Google Groups "Scala Breeze" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/scala-breeze/qyMLYrlbbNA/unsubscribe.
To unsubscribe from this group and all its topics, send an email to scala-breeze...@googlegroups.com.

To post to this group, send email to scala-...@googlegroups.com.

David Hall

unread,
Dec 3, 2015, 12:07:23 AM12/3/15
to scala-...@googlegroups.com
There's already a generic slice matrix (SliceMatrix), but it's for a more general kind of slice. It might make sense to have it as a trait with two cases (ranges and not-ranges), maybe.

re: typeclass-only, It's tempting. Breeze has been evolving in that direction since it's inception, but I think people can be terrified of type classes and so I try to hide them for what I consider basic cases, but maybe using Matrix is advanced enough that I shouldn't bother.

I think having the dynamic double dispatch is useful, and I think it's hard to do that without having an inheritance hierarchy, to some extent. Spark people use Matrix, and they seem to be sensitive to too many typeclasses.

--David



Julian Michael

unread,
Dec 3, 2015, 1:21:57 AM12/3/15
to scala-...@googlegroups.com
I see. I get the sense when I'm working with Breeze that there's some serious tension between having "numpy for Scala" and doing things "the Scala way." It makes sense that we might want to lean towards the former.

Hmm, it seems to me that double dispatch is something we get with multi-parameter Scala typeclasses... Would you mind pointing me to some examples of the uses of double dispatch in Breeze?

David Hall

unread,
Dec 6, 2015, 10:47:06 PM12/6/15
to scala-...@googlegroups.com
Sorry for the slow response.

There is definitely that tension. It's also the case that the "Breeze way" evolves over time as I learn things. UFuncs are now a central organizing principle for Breeze (and have been for ~2 years), but a lot of pre-UFunc code exists and.

anytime you do a binary operation on Vector[T] for one of the primitives, the code checks a BinaryRegistry with the dynamic types of the arguments to see if a better implementation has been registered. If so, that is used instead. Look for any instance of BinaryRegistry in Vector.scala


Reply all
Reply to author
Forward
0 new messages