Open Beakerboy opened 5 years ago
Thanks for thinking about how to improve the Matrix class. I've been thinking about some improvements I want to make as well. I've held off since you and I were working on things like QR and eigenvalues and didn't want to create any merge conflicts. Refactoring is always encouraged, and unit tests are there to make it safe to do so.
Also, there are some other changes I was thinking about and waiting until version 2.0 because there are some other breaking changes I want to make. But we can start now with refactoring without changing the API.
Regarding a MatrixBaseTrait kind of design, you will have to consider where class properties go. For example, most Matrix methods make use of $this->A
since that holds the matrix data. It would be hard to reason about the Trait if it isn't the one that owns the A
property. I think it would be a misuse of a Trait if it was using internal properties of classes it suspects it might be used by. There is a case where using PHPStorm is really helpful. It will flag your code immediately if your trait starts using properties it doesn't own.
The Matrix class is quite large and I agree it is long overdue for some refactoring. Making changes for the sake of making it smaller has the risk of making it more complex to reason about. Keep in mind what the intended purpose of each class/trait/interface is and keep to it. Someone looking at the code should obviously know that X is in the Y class and A is in the B trait or whatever. Good names help with this. Don't worry too much about getting it perfect. Design is iterative. And all programming involves design. I recommend doing it in small pieces over multiple pull requests. Also, let's coordinate a bit so we don't cause each other merge conflicts.
Interfaces are definitely the right choice for type declarations.
As I think about it, I think the MatrixBase could be an abstract class instead of a trait.
Please let me know your preference for which of the multiple inheritance patterns you prefer. Notice in this linked diagram that ComplexSquareMatrix and JacobianSquareMatrix are opposite each other.
And I just noticed a typo in my diagram. JacobianSquareMatrix would only have to use the JacobianBase trait, so that would make that version simpler.
I also propose getting rid of the Vandermonde matrix. Instead we could have the MatrixFactory produce the appropriate NumericMatrix given an array and a integer for the polynomial degree. I’m thinking the Vandermonde matrix should be more like the identity matrix in that it is just a numerical matrix with a special form. The Factory can handle its construction without the need of a separate class structure.
This might be a better diagram. You should be able to clone and edit your own version: https://sketchboard.me/lBx6LOlXiNoe#/
I made my own version.
FYI, instead of using PowerPoint or sketch tools or whatever, I recommend using PlantUML for these kinds of things.
Go to this Web page: https://www.planttext.com/
And copy and paste in this PlantUML diagram:
@startuml
title MathPHP - Matrix Class Diagram
interface Matrix
abstract class MatrixBase implements Matrix
class NumericMatrix extends MatrixBase
class ObjectMatrix extends MatrixBase
class NumericSquareMatrix extends NumericMatrix
class ComplexMatrix extends ObjectMatrix
class ComplexSquareMatrix extends ComplexMatrix
class ObjectSquareMatrix extends ObjectMatrix
class FunctionMatrix extends MatrixBase
class FunctionSquareMatrix extends FunctionMatrix
class JacobianMatrix extends FunctionMatrix
class JacobianSquareMatrix extends JacobianMatrix
class SquareMatrixTrait
NumericSquareMatrix *-- SquareMatrixTrait
ComplexSquareMatrix *-- SquareMatrixTrait
ObjectSquareMatrix *-- SquareMatrixTrait
FunctionSquareMatrix *-- SquareMatrixTrait
JacobianSquareMatrix *-- SquareMatrixTrait
@enduml
PlantUML syntax is pretty easy to learn. If you have to make changes, it is just changing some text. You can check it into git since it is just text. PHPStorm has a PlantUML plugin to do it in the IDE.
PlantUML documentation: http://plantuml.com/en/
Nice! Try this.
I just had the JacobianMatrix as an example of what might possibly extend the FunctionMatrix. I think this is what we would need to be set up for a successful v1.0.
I have a question about how I should be reasoning about what the relationships are with the complex matrices.
For example, a ComplexSquareMatrix is not a ComplexMatrix. Instead, they have a ComplexMatrixTrait. Since these two matrices are not related, you could not have a type declaration of a generic ComplexMatrix, because Complex and ComplexSquare do not share a common type.
What is the reason for the ComplexTrait, and for not having a direct hierarchy of Object Matrix <- ComplexMatrix <- ComplexSquareMatrix?
Good point on the Complex Hierarchy. Is this better?
Edit: also just added the ColumnVector and RowVector. Although I feel they are like the Vandermonde and add little to the library. There's nothing to them that could not be performed by the MatrixFactory and the existing Matrix class
I don't think traits can extend traits. Traits are just a means to copy and paste code into a class in scenarios where multiple inheritance might have been used but isn't a thing in PHP.
I agree with things like Column and Row Vectors. The MatrixFactory can have multiple create methods to handle this and just produce a regular matrix.
A trait can use another trait and add it's own method, so it's almost like inheritance. updated Diagram
Cool. Classes and traits seem to make sense.
I'm not sure I understand why every matrix would implement ObjectArithmetic. I thought an ObjectMatrix (and only an ObjectMatrix) would contain values that themselves where objects that implemented ObjectArithmetic.
ObjectArithmetic currently defines an object that has define add(), subtract(), and multiply() methods. Since we want all of our matrices to implement those functions, it makes sense to extend it. I mentioned this elsewhere, but if a matrix extends Object Arithmetic, then we can use a matrix as the object type within another matrix, in essence creating a block matrix.
Oh nice. An InceptionMatrix! OK. I get it. Thanks.
Looks good. I'll think it over some more and see if I can come up with any issues with this design.
Another matrix I am thinking of adding is a ZeroOneMatrix. It would probably inherit from the NumericMatrix. A zero-one matrix is a discrete math/computer science-y thing for doing boolean operations and relations.
FYI, in the develop branch I've removed the Vandermonde matrices and added a specific MatrixFactory method to create it as a regular matrix. I've also added a factory method for diagonal matrices.
I would say a zero one matrix would be a new matrix type that extends the MatrixBase, and could be called BooleanMatrix. The matrix would instead contain bool data types. The matrix would need to implement add()
, subtract()
, and multiply()
. I would say that add()
is the join of two zero-one matrixes. Would subtract()
be undefined? The Boolean product would be multiply()
.
Having a matrix of boolean values is an interesting idea. The reason I was thinking to use a NumericMatrix of integer values 1 and 0 was that since matrices are immutable, and all operations on a matrix produce a new matrix, you would be able to do all the other operations that are defined on a matrix, as 1 and 0 are values that would work with every other already-defined operation on a matrix. Whereas if you have boolean types, most of the existing methods don't work. The constructor could ensure that only integer 1s and 0s got passed in. It could implement this new BooleanInterface or something that had the new methods that only work on these new matrices.
Just something I was considering and no rush to implement a ZeroOne or Boolean Matrix or whatever it ends up being called.
What special methods would a zero-one matrix have besides meet(), join(), and multiply()?
My old Discrete Math textbook defines these operations on a Zero-One Matrix:
Thanks for continuing to iterate on this, and apologies for the delay in really looking at this.
Would it make more sense, and make for a simpler relationship if MatrixBase itself implemented the Matrix interface?
For example:
@startuml
title MathPHP - Matrix Class Diagram
interface ObjectArithmetic{
add()
subtract()
multiply()
}
interface Matrix {
getObjectType(): string
}
interface SquareMatrix extends Matrix{
det()
trace()
}
abstract class MatrixBase implements Matrix, ObjectArithmetic{
int $m
int $n
array $A
isSquare(): boolean
transpose(): Matrix
directSum(): Matrix
augment(): Matrix
submatrix(): Matrix
}
class NumericMatrix extends MatrixBase{
add(): NumericMatrix
subtract(): NumericMatrix
multiply(): NumericZatrix
getObjectType(): string
}
class ZeroOneMatrix extends MatrixBase{
add(): ZeroOneMatrix
subtract(): ZeroOneMatrix
multiply(): ZeroOneMatrix
join(): ZeroOneMatrix
meet(): ZeroOneMatrix
booleanProduct(): ZeroOneMatrix
booleanPower(): ZeroOneMatrix
getObjectType(): string
}
class ObjectMatrix extends MatrixBase{
add(): ObjectMatrix
subtract():ObjectMatrix
multiply(): Objectmatrix
getObjectType(): string
}
class NumericSquareMatrix extends NumericMatrix implements SquareMatrix{
det()
trace()
}
class ComplexMatrix extends ObjectMatrix{
conjugateTranspose()
}
class ComplexSquareMatrix extends ComplexMatrix implements SquareMatrix
class ObjectSquareMatrix extends ObjectMatrix implements SquareMatrix{
}
class FunctionMatrix extends MatrixBase {
add(): FunctionMatrix
subtract(): FunctionMatrix
multiply(): Functionmatrix
getObjectType(): string
}
class FunctionSquareMatrix extends FunctionMatrix implements SquareMatrix{
det()
trace()
}
class SquareMatrixTrait<< (T,orchid) >>{
SquareMatrix $A⁻¹
isSquare(): boolean
leadingPrincipalMinor(): Squarematrix
minorMatrix(): SquareMatrix
}
NumericSquareMatrix *-- SquareMatrixTrait
FunctionSquareMatrix *-- SquareMatrixTrait
class ObjectSquareMatrixTrait<< (T,orchid) >>{
det()
trace()
}
ObjectSquareMatrixTrait *-- SquareMatrixTrait
ObjectSquareMatrix *-- ObjectSquareMatrixTrait
ComplexSquareMatrix *-- ObjectSquareMatrixTrait
@enduml
MatrixBase can’t actually “implement” ObjectArithmetic, but it certainly could declare the functions as abstract, and force the children to provide the details.
Yeah, it could implement the interface by making the methods abstract with no implementation. It would be able to, in the PHP sense, "implement" the interface even though it didn't provide its own implementation.
For example:
Interactive shell
php > interface Test { public function test(); }
php > abstract class TestA implements Test { abstract public function test(); }
php > class TestImplA extends TestA { public function test() {} }
php > print_r(class_implements(TestImplA::class));
Array
(
[Test] => Test
)
php
The reason I had not set it up that way is because I hadn’t thought about abstract methods fulfilling the implementation obligation. That’s a much better design.
Hi @Beakerboy,
I started to implement some of this in my v2.0 migration branch by renaming Matrix
to NumericMatrix
and MatrixBase
to Matrix
. As I was looking at other classes in the class hierarchy, I was wondering what to do about the FunctionMatrix
classes. They aren't really sibling Matrices with the other Matrix classes, as most of the computation methods will error out because there are no numeric values in them. They seem to be more like NumericMatrix Factories with their evaluate
method.
Would it make more sense to take these out of the Matrix hierarchy and just make them utility helper factory classes for producing NumericMatrices? If I did that, I don't think you need the FunctionSquareMatrix
class anymore, as the new FunctionMatrixFactory
or whatever it is would just produce the appropriate NumericMatrix
or NumericSquareMatrix
based on how it was constructed with functions when evaluated with numeric parameters.
Thoughts?
Current matrix object graph looks like this in the latest v2.0.0 release.
@startuml
title MathPHP - Matrix Class Diagram
abstract class Matrix {
}
class NumericMatrix extends Matrix {
}
class NumericSquareMatrix extends NumericMatrix {
}
class NumericDiagonalMatrix extends NumericSquareMatrix {
}
class RowEchelonForm extends NumericMatrix {
}
class ReducedRowEchelonForm extends RowEchelonForm {
}
class RowVector extends NumericMatrix {
}
class ColumnVector extends NumericMatrix {
}
class ObjectMatrix extends Matrix {
}
class ObjectSquareMatrix extends ObjectMatrix {
}
interface ObjectArithmetic {
}
ObjectMatrix *-- ObjectArithmetic
@enduml
I’m moving some functions around and want to get your input. My plan is to make a MatrixBase trait. This trait will house methods that can work on any matrix regardless of shape and data type. A good example is getDiagonalElements(). It just places the diagonals in a new array, and does not matter if the items are Complex objects or floats.
The idea would be that any new type of matrix can use this base trait instead of the current Matrix class. Matrix has all methods defined with normal math operators and these will fail on most other data types.
I am also creating a SquareMatrixBase trait which houses universal Square-only methods, like minorMatrix. This function just excludes a row and column, so will work on any data type. This way, any new type of matrix like ObjectMatrix, can use the MatrixBase, and a SquareObjectMatrix can extend ObjectMatrix and use the SquareMatrixBase trait to have access to those methods. I think I’ll take it one step further and make an ObjectSquareMatrixBase, this way a ComplexSquareMatrix can be made by extending a ComplexMatrix and using the SquareMatrix, and ObjectMatrix traits.
I’m thinking it might be good to have a MatrixInterface and SquareMatrixInterface. This way the universal methods can type hint that they work on any type of matrix.