lessthanoptimal / ejml

A fast and easy to use linear algebra library written in Java for dense, sparse, real, and complex matrices.
https://ejml.org
565 stars 117 forks source link

Feature/immutable #175

Closed lessthanoptimal closed 1 year ago

lessthanoptimal commented 1 year ago

Thank you for your contribution to the EJML project.

Before submitting this PR, please make sure:

lessthanoptimal commented 1 year ago

Previous requests for an ImmutableMatrix https://github.com/lessthanoptimal/ejml/issues/155 . Ok I can find only one, but I know this has come up several times previously.

lessthanoptimal commented 1 year ago

@JozsefKutas @ennerf @FlorentinD Does this look good? The PR adds an ImmutableMatrix which is an interface SimpleMatrix implements.

lessthanoptimal commented 1 year ago

Might rename it from ImmutableMatrix to ConstMatrix. Const is shorter...

JozsefKutas commented 1 year ago

Hi, I've just looked over the changes and I have a few thoughts.

At the moment, the ImmutableMatrix isn't actually immutable. A class is immutable if it comes with a gaurantee that instances can't be modified. There's a quick summary of what this implies at https://math.oxford.emory.edu/site/cs171/immutability/ (taken I think from Bloch's Effective Java).

With the current implementation, there's no guarantee that the underlying data of ImmutableMatrix will not be modified after the immutable matrix is created:

DMatrixRMaj matrix = CommonOps_DDRM.identity(2);
ImmutableMatrix immutable = SimpleMatrix.wrap(matrix);
matrix.set(1, 1, 0d);

It's closer to the unmodifiable collections returned by some methods in java.util. Unmodifiability is a weaker condition than immutablility, but is still useful (see https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/util/Collection.html#unmodifiable).

It's not strictly unmodifiable either, because it's possible to mutate elements by downcasting:

ImmutableMatrix immutable = SimpleMatrix.identity(2);
SimpleMatrix simple = (SimpleMatrix) immutable;
simple.set(1, 1, 0d);

If you go with this implementation then I'd change the name, just to make it clearer to the user what guarantees the class does and doesn't provide.

Alternative approaches would be:

Besides these issues, I don't understand the motiviation behind removing the type parameter from SimpleBase. It means errors like this aren't caught until runtime:

ImmutableMatrix matrix = SimpleMatrix.identity(2);
StatisticsMatrix scaled = matrix.scale(2d);
// Exception in thread "main" java.lang.ClassCastException: class org.ejml.simple.SimpleMatrix cannot be cast to class org.ejml.example.StatisticsMatrix ...
FlorentinD commented 1 year ago

In general I like the idea of having an immutable matrix. One use-case could be for input data which you want to make sure you dont modify by accident.

For the PR itself, I agree with Jozsef and I am also missing some tests which show the Exception is thrown.

Also, starting with the API over the SimpleMatrix makes sense, but I could also see this as useful for more concrete implementations as this is a feature more advanced users would use.

lessthanoptimal commented 1 year ago

Hi, I've just looked over the changes and I have a few thoughts.

At the moment, the ImmutableMatrix isn't actually immutable. A class is immutable if it comes with a gaurantee that instances can't be modified. There's a quick summary of what this implies at https://math.oxford.emory.edu/site/cs171/immutability/ (taken I think from Bloch's Effective Java).

Ok Immutable isn't the best description clearly. It's main design goal is to make it so that someone has to go out of their way to modify it. Kinda of like const in C++, which apparently is considered "a shallow immutable object" as you can strip it of the const property in various ways.

Trying to prevent someone from downcasting and modifying or any other "attack" I think is overkill. This is mainly to keep honest people honest and make it easy to see if someone is deliberately breaking the contract. What I don't like about Unmodifiable is that some of the checks are at runtime, which can result in a nasty surprise.

Maybe call it ConstMatrix?

  • Rename the current ImmutableMatrix interface, and use it as the parent for mutable matrices (SimpleMatrix) and immutable matrices. This is similar to the approach used in scala (https://docs.scala-lang.org/overviews/collections-2.13/overview.html). This approach is more complicated, but it avoids using runtime errors to enforce immutability.

That's what currently done I believe.

Besides these issues, I don't understand the motiviation behind removing the type parameter from SimpleBase. It means errors like this aren't caught until runtime:

Just reverted that change. It was result of an earlier experiment that didn't go anywhere and I forgot to undo it.

Thanks for all the thoughtful comments!

@JozsefKutas

lessthanoptimal commented 1 year ago

See KalmanFilterSimple for an example of how this could be put into practice.

lessthanoptimal commented 1 year ago

Unless there's some additional feedback or suggestion for a better name, I'm going to merge this in soon. Planning on doing another release soon too.

lessthanoptimal commented 1 year ago

@JozsefKutas Do you know if sealed types are syntax sugar and generate bytecode compatible with Java 11? EJML compiles Java 17 but creates bytecode for Java 11. EJML will be stuck with Java 11 bytecode for the foreseeable future. Took forever to give up on Java 8.

lessthanoptimal commented 1 year ago

As a side note, I'm not really the target audience for ConstMatrix either. I mostly work with the low level API. I think it applies to people who attempt to restrict access as much as possible and feel the need to use final ever place possible. I also liked the Equations API but I think I'm the only user of that API after all these years. That is probably a good candidate to be removed in the future, at least if it wouldn't break a bunch of my own code.

JozsefKutas commented 1 year ago

Do you know if sealed types are syntax sugar and generate bytecode compatible with Java 11?

They're not just syntax sugar, so I'd guess they're not compatible.