- Made as Course Project for Software Testing course (T1 23-24)
- Here we try out Mutation Testing on the Unit Tests that we wrote for the existing Linear Algebra library.
- The code for linear algebra functions is taken from https://github.com/danhales/linearalgebra, so I would like to thank them.
- Concepts learned - Unit Testing, Mutation Testing
- Tools learned - JUnit, PIT
- Project report can be found in Project_Report.pdf file
- Project stmt can be found in Project_Expectations.pdf file
- NOTES.md contains some additional notes to get familiar with Maven, JUnit and PIT
- The source code used for project can be found in src/main folder
- Testing strategy used for the project: Mutation Testing
- Unit test code can be found in the src/test folder
- The mutation testing report can be found in target/pit-reports folder
This is due to the absence of boundary testcases.
IMT2020038 Pratham: Understood source code, took care of JUnit unit testing (80%)
IMT2020067 Rishi: Found source code, modified it acc. to need, took care of PIT mutation testing, did some unit testing (20%)
- The project is managed with Maven
-
The code was unit tested using JUnit
-
To run the unit tests, run the following command:
mvn test
-
Mutation testing was applied on the project using PIT Mutation Testing tool
-
To run the mutation tests, run the following command:
mvn -DwithHistory test-compile org.pitest:pitest-maven:mutationCoverage
-
This will generate an HTML report of mutation testing inside the target/pit-reports folder
-
Demo driver code is written in Main.java to get familiar with the linearalgebra library
-
To run the Main.java file, run the following commands:
mvn package
java -cp ./target/project-1.0-SNAPSHOT.jar linearalgebra.Main
A linear algebra package in Java.
For flexibility, most methods are overloaded to include both static
versions (which require the object as the first parameter) and instance methods. The static
method does the heavy lifting, and the instance method is a wrapper for the static
method using this
as an argument (e.g. Matrix.transpose(this)
).
No operations are carried out in-place––all operations that modify the object return a copy of the object with the modifications.
Current classes and capabilities (as of November 1, 2020)
Because linear algebra (as it applies to machine learning) largely revolves around lists of numbers, the Vector class is a fancy wrapper for an array of doubles, and provides common functionality for working with both that array, in addition to operations involving other Vector objects.
entries
- array of doubles
-
Vector(double...)
- accepts either a list of doubles (e.g.Vector(1,2,3)
), or an array ofdouble
s (e.g.Vector(new double[] {1,2,3})
) -
Vector(Vector)
- copies the entries in the passedVector
object's private field into a new Vector object
-
getEntries()
- returns a copy of (not a reference to)entries
-
get(int)
- returns the value in the specified position of theVector
, e.g.u.get(3)
returns the entry in position 3 -
identityVector(int)
- returns the additive identityVector
with the specified length, which is just a list of zeros -
set(int, double)
- sets the value at the specified index to be equal to the passed value -
setEntries(double[])
- sets the values in theentries
field -
toString()
- returns a Python-style representation of theVector
, e.g."[1, 2, 3]
"
-
inverseVector(Vector)
/inverseVector()
- returns the additive inverse of theVector
, which is the same vector, with the signs flipped (e.g. {1, -2, 3} becomes {-1, 2, -3}) -
isCanonicalBasisVector(Vector)
- checks to see ifVector
is all zeros, except for a single 1 -
isZero(Vector)
- checks to see if theVector
is essentially zero, which I've defined as smaller than Double.MIN_VALUE * 10. -
length(Vector)
- returns the number of entries in theVector
, which is the length ofv
-
magnitude(Vector)
- returns the Euclidean norm of theVector
-
normalize()
- scales aVector
v
with the factor(1/v.magnitude())
. Throws anIllegalArgumentException
ifv.isZero()
returnstrue
-
pnorm(double)
- computes the Lp norm with the given value ofp
-
add(Vector)
/add(Vector, Vector)
- adds corresponding entries in twoVector
objects, provided they have the same length. -
angleDegrees(Vector, Vector)
/angleRadians(Vector, Vector)
- returns the angle between the twoVector
s (arccos(u1.dot(u2) / (u1.magnitude() * u2.magnitude()))) in either degrees or radians -
checkLengths(Vector, Vector)
checks to make sure the twoVector
- objects have compatible lengths and throws anIllegalArgumentException
if they have different lengths -
cross(Vector)
/cross(Vector, Vector)
- computes the cross product for two three-dimensionalVector
s. The cross product is only defined for three-dimensional vectors, so anIllegalArgumentException
will be thrown if aVector
any other dimension is passed. -
dot(Vector)
/dot(Vector, Vector)
- computes the dot product for twoVector
s of the same length, which is the sum of products of corresponding entries, e.g. {1, 2, 3} dot {4, 5, 6} = (1)(4) + (2)(5) + (3)(6). -
subtract(Vector)
/subtract(Vector,Vector)
- subtracts the passedVector
from the callingVector
, or the secondVector
from the first
-
linearCombination(Vector[], double[])
- returns a weighted sum by multiplying theVector
objects in theVector
array by the weights in thedouble
array. -
multiply(Vector, double)
/multiply(double)
- computes the scalar product by multiplying each entry in theVector
by the specifieddouble
-
scalarTripleProduct(Vector, Vector, Vector)
- computes the scalar triple product of the three vectors, e.g. a.dot(b.cros(c)). This operation is NOT commutative.
-
outerProduct(Vector, Vector)
- -
orthogonalProjection(Vector, Vector)
-
The Matrix
class takes advantage of many of the properties of the Vector
class in order to work with a 2D array of double
s. Although there are many use cases possible for a Matrix
class, I am focusing functionality in two primary areas before diving into more specific uses: machine learning and linear algebra operations. For machine learning purposes, we'll mostly be needing to multiply a Vector
object by a Matrix
object, pass the output through an activation function of some kind, and update the entries in the Matrix
according to some kind of loss function. To accomplish this, we need a variety of linear algebra tricks, such as grabbing a row or column as a Vector
, updating a single row or column, adding, subtracting, or multiplying Matrix
objects, multiplying Matrix
objects, and performing elementary operations such as adding a multiple of one row onto another row.
entries
- a 2Ddouble
array. It is assumed that the array is rectangular.
-
Matrix(double[][])
- accepts the 2D array directly and copies its entries into the private fieldentries
-
Matrix(Matrix)
- copy constructor
I'm using the terms "setters" and "getters" very loosely here in order to organize this README
, because methods like getColumn
do not return the value in a private field––they return a subset of entries in entries
as a Vector
object.
-
getEntries()
- -
minorMatrix(Matrix, int, int)
- returns a copy of theMatrix
object with the indicated row and column dropped. -
fromColumnVectors(Vector...)
- accepts either an array ofVector
objects, or a list ofVector
objects as parameters, and returns aMatrix
where thoseVector
objects make up the columns.Vector
objects must all have the same length, or anIllegalArgumentException
will be thrown. Implemented by returning thetranspose
of the value returned byfromRowVectors
-
fromRowVectors(Vector...)
- accepts an array ofVector
objects, or a list ofVector
objects as parameters, and returns aMatrix
where theVector
objects make up the rows.Vector
objects must all have the same length, or anIllegalArgumentException
will be thrown. -
dropColumn(Matrix, int)
/dropRow(Matrix, int)
- returns a copy of theMatrix
object with the specified row/column removed. Note that this operation does change the index of the entries after the dropped column/row. -
getColumn(Matrix,m int)
/getRow(Matrix, int)
- accepts anint
for the index of the desired column/row, and returns the entries in that column as aVector
object. If the column/row index is out of range, anIllegalArgumentException
is thrown -
getEntry(int, int)
- accepts anint
for the row index and anint
for the column index, and returns the entry atentries[row][col]
. If either of the values are out of range, anIllegalArgumentException
is thrown. -
getNumColumns(Matrix)
/getNumRows(Matrix)
- returns the length of the first row inentries
(columns), i.e.entries[0].length
, or the length ofentries
, i.e.entries.length
. We assume the array is rectangular, and not ragged. -
identityMatrix(int)
returns an n-by-n Matrix (wheren
is specified as a parameter) which has ones on the diagonal, and zeros elsewhere. -
setColumn(Matrix, int, double[])
/setColumn(int, Vector)
/setRow(Matrix, int, double[])
/setRow(Matrix, int, Vector)
- accepts an int for the index of the column/row we want to set and either an array of new entries or aVector
of new entries, and replaces the entries in that column/row with the new entries. If the length of the new entries is not equal to the length of the column/row being set, or if the column/row index specified is out of range, anIllegalArgumentException
will be thrown. -
setEntry(Matrix, double, int, int)
- -
toColumnVectors(Matrix)
- returns the columns of theMatrix
as an array ofVector
objects -
toRowVectors(Matrix)
- returns the rows of theMatrix
as an array ofVector
objects -
toString()
- represents entries Python-style as a stack of row vectors, e.g.
[[1, 2, 3], [4, -5, 6], [-7, 8, 9]]
-
determinant(Matrix)
- recursively computes the determinant of a square matrix using the Laplace expansion, O(n!). Not recommended for use. -
isDiagonal(Matrix)
- returnstrue
if theMatrix
is square and all nonzero entries (threshold-checked) are located on the diagonal -
isLowerTriangular(Matrix)
- returnstrue
if all values below the diagonal are zero -
isPermutationMatrix(Matrix)
- returnstrue
if the Matrix is all zeros, except for a single 1 in each row and each column -
isSparse(Matrix)
/isSparse(Matrix, double)
- counts the number of (threshold-checked) zero entries. No-arg version returnstrue
if there are no more nonzero entries than max{number of rows, number of columns}. Version that accepts adouble
allows user to specify the proportion explicitly. As of now, this is simply a check, and no optimizations for sparse matrices are carried out. -
isSquare(Matrix)
- checks to see if the number of rows equals the number of columns. All methods that operate only on square matrices call this method immediately and throw anIllegalArgumentException
if it returnsfalse
. -
isUpperTriangular(Matrix)
- checks to see if all entries above the diagonal are (threshold-checked) zero -
shape(Matrix)
- returns the shape of theMatrix
as an array of length two: {numRows, numCols} -
trace(Matrix)
- returns the sum of the values on the diagonal of a square Matrix -
transpose(Matrix)
- returns the transpose of theMatrix
, which swaps rows and columns. That is, rowi
becomes columni
and columnj
becomes rowj
. -
swapColumns(Matrix, int, int)
- accepts two column indices, and swaps the columns at those indices. Throws anIllegalArgumentException
if either index is out of range. -
swapRows(Matrix, int, int)
- accepts two row indices, and swaps the rows at those indices. Throws anIllegalArgumentException
if either index is out of range.
-
add(Matrix, Matrix)
- returns a newMatrix
whose entries are sums of the corresponding entries of twoMatrix
objects. -
subtract(Matrix, Matrix)
- subtracts the second matrix from the first (or the passed matrix from the calling matrix). -
multiply(Matrix, Matrix)
- returns a newMatrix
which is the product of matrix multiplication. The number of columns in the leftMatrix
must match the number of rows in the rightMatrix
, or anIllegalArgumentException
will be thrown. Matrix multiplication is NOT commutative, somultiply(a, b)
will generally not equalmultiply(b, a)
, andmultiply(a, b)
being defined does not implymultiply(b, a)
is even defined. In the instance method, the callingMatrix
is on the left in the multiplication, i.e.Matrix.multiply(this, m)
.
-
multiply(Matrix, double)
- multiplies every entry in theMatrix
by the passeddouble
. -
multiply(Matrix, Vector)
- multiplies theVector
object by theMatrix
object and returns the resultantVector
. -
addVectorToColumn(Matrix, Vector, int)
- adds the entries in the passedVector
object to the specified column of theMatrix
object. Length ofVector
must match the length of the column or anIllegalArgumentException
will be thrown. -
addVectorToRow(Matrix, Vector, int)
- adds the entries in the passedVector
object to the specified row of theMatrix
object. Length ofVector
must match the length of the row or anIllegalArgumentException
will be thrown.