Skip to content

Instantly share code, notes, and snippets.

@ayah527

ayah527/matrix.c Secret

Last active May 8, 2021 20:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ayah527/e76744ae3ca5255c177bdd64df7d86da to your computer and use it in GitHub Desktop.
Save ayah527/e76744ae3ca5255c177bdd64df7d86da to your computer and use it in GitHub Desktop.
NUMC - a Python to C interpreter for matrix functions, optimized using SIMD & OpenMP
#include "matrix.h"
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <omp.h>
// Include SSE intrinsics
#if defined(_MSC_VER)
#include <intrin.h>
#elif defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__))
#include <immintrin.h>
#include <x86intrin.h>
#endif
/* Below are some intel intrinsics that might be useful
* void _mmrows56_storeu_pd (double * mem_addr, __mrows56d a)
* __mrows56d _mmrows56_set1_pd (double a)
* __mrows56d _mmrows56_set_pd (double e3, double erows, double e1, double e0)
* __mrows56d _mmrows56_loadu_pd (double const * mem_addr)
* __mrows56d _mmrows56_add_pd (__mrows56d a, __mrows56d b)
* __mrows56d _mmrows56_sub_pd (__mrows56d a, __mrows56d b)
* __mrows56d _mmrows56_fmadd_pd (__mrows56d a, __mrows56d b, __mrows56d c)
* __mrows56d _mmrows56_mul_pd (__mrows56d a, __mrows56d b)
* __mrows56d _mmrows56_cmp_pd (__mrows56d a, __mrows56d b, const int imm8)
* __mrows56d _mmrows56_and_pd (__mrows56d a, __mrows56d b)
* __mrows56d _mmrows56_max_pd (__mrows56d a, __mrows56d b)
*/
/* Generates a random double between low and high */
double rand_double(double low, double high)
{
double range = (high - low);
double div = RAND_MAX / range;
return low + (rand() / div);
}
/* Generates a random matrix */
void rand_matrix(matrix *result, unsigned int seed, double low, double high)
{
srand(seed);
for (int i = 0; i < result->rows; i++)
{
for (int j = 0; j < result->cols; j++)
{
set(result, i, j, rand_double(low, high));
}
}
}
/*
* Allocates space for a matrix struct pointed to by the double pointer mat with
* `rows` rows and `cols` columns. You should also allocate memory for the data array
* and initialize all entries to be zeros. `parent` should be set to NULL to indicate that
* this matrix is not a slice. You should also set `ref_cnt` to 1.
* You should return -1 if either `rows` or `cols` or both have invalid values, or if any
* call to allocate memory in this function fails. If you don't set python error messages here upon
* failure, then remember to set it in numc.c.
* Return 0 upon success.
*/
int allocate_matrix(matrix **mat, int rows, int cols)
{
if (rows <= 0 || cols <= 0)
{
return -1;
}
(*mat) = calloc(1, sizeof(matrix));
if (!(*mat))
{
return -1;
}
(*mat)->data = (double *)calloc((rows * cols), sizeof(double));
if (!((*mat)->data))
{
return -1;
}
(*mat)->rows = rows;
(*mat)->cols = cols;
(*mat)->parent = NULL;
(*mat)->ref_cnt = 1;
return 0;
}
/*
* Allocates space for a matrix struct pointed to by `mat` with `rows` rows and `cols` columns.
* Its data should point to the `offset`th entry of `from`'s data (you do not need to allocate memory)
* for the data field. `parent` should be set to `from` to indicate this matrix is a slice of `from`.
* You should return -1 if either `rows` or `cols` or both are non-positive or if any
* call to allocate memory in this function fails.
* If you don't set python error messages here upon failure, then remember to set it in numc.c.
* Return 0 upon success.
*/
int allocate_matrix_ref(matrix **mat, matrix *from, int offset, int rows, int cols)
{
if (rows <= 0 || cols <= 0)
{
return -1;
}
*mat = calloc(1, sizeof(matrix));
if ((*mat) == NULL)
{
return -1;
}
if (from)
{
(*mat)->data = &(from->data[offset]);
from->ref_cnt += 1;
(*mat)->rows = rows;
(*mat)->cols = cols;
(*mat)->parent = from;
(*mat)->ref_cnt = 1;
return 0;
}
else
{
return -1;
}
}
/*
* You need to make sure that you only free `mat->data` if `mat` is not a slice and has no existing slices,
* or if `mat` is the last existing slice of its parent matrix and its parent matrix has no other references
* (including itself). You cannot assume that mat is not NULL.
*/
void deallocate_matrix(matrix *mat)
{
/* pseudocode: (1) check if mat is not a slice & mat has no existing slices
(rows) mat is the last slice of its parent & parent has no other references (3) mat is null
*/
if (mat == NULL)
{ // if mat is null
return;
}
mat->ref_cnt -= 1;
if (mat->ref_cnt <= 0)
{
deallocate_matrix(mat->parent);
if (!mat->parent && mat->data)
{
free(mat->data);
}
free(mat);
}
}
/*
* Returns the double value of the matrix at the given row and column.
* You may assume `row` and `col` are valid.
*/
double get(matrix *mat, int row, int col)
{
if (!mat) {
return;
}
return (mat->data)[(row * mat->cols) + col];
}
/*
* Sets the value at the given row and column to val. You may assume `row` and
* `col` are valid
*/
void set(matrix *mat, int row, int col, double val)
{
if (!mat) {
return;
}
(mat->data)[(row * (mat->cols)) + col] = val;
}
/*
* Sets all entries in mat to val
*/
void fill_matrix(matrix *mat, double val)
{
/* to optimize:
(1) unroll
(2) load pointer at current mat
(3) set pointer to val
(4) store pointer back in mat
*/
int cols = mat->cols;
int rows = mat->rows;
__m256d newVal = _mm256_set1_pd(val);
#pragma omp parallel for
for (int i = 0; i < rows*cols / 4*4; i+=4)
{
_mm256_storeu_pd(mat->data + i, newVal);
}
#pragma omp parallel for
for (int i = rows*cols / 4*4; i < rows*cols; i+=1)
{
mat->data[i] = val;
}
}
/*
* Store the result of adding mat1 and mat2 to `result`.
* Return 0 upon success and a nonzero value upon failure.
*/
int add_matrix(matrix *result, matrix *mat1, matrix *mat2)
{
/* to optimize:
(1) load current item from mat1
(2) load current item mat2
(3) add 1 & 2
(4) store sum in result
(5) tail case
*/
int cols1 = mat1->cols;
int cols2 = mat2->cols;
int rows1 = mat1->rows;
int rows2 = mat2->rows;
if (rows1 != rows2 || cols1 != cols2 || cols1 <= 0 || cols2 <= 0 || rows1 <= 0 || rows2 <= 0)
{
return 1;
}
#pragma omp parallel for
for (int i = 0; i < rows1; i++)
{
for (int j = 0; j < cols1/(4*4); j += 4)
{
__m256d v1 = _mm256_set_pd(mat1->data[i * cols1 + j + 3], mat1->data[i * cols1 + j + 2], mat1->data[i * cols1 + j + 1], mat1->data[i * cols1 + j]);
__m256d v2 = _mm256_set_pd(mat2->data[i * cols1 + j + 3], mat2->data[i * cols1 + j + 2], mat2->data[i * cols1 + j + 1], mat2->data[i * cols1 + j]);
__m256d sum = _mm256_add_pd(v1, v2);
_mm256_storeu_pd(result->data + i * cols1 + j, sum);
}
for (int j = cols1/(4*4); j < cols1; j += 1) {
result->data[i*cols1+j] = mat1->data[i*cols1+j] + mat2->data[i*cols1+j];
}
}
return 0;
}
/*
* Store the result of subtracting mat2 from mat1 to `result`.
* Return 0 upon success and a nonzero value upon failure.
*/
int sub_matrix(matrix *result, matrix *mat1, matrix *mat2)
{
int cols1 = mat1->cols;
int cols2 = mat2->cols;
int rows1 = mat1->rows;
int rows2 = mat2->rows;
if ((mat1->rows) != (mat2->rows) || (mat1->cols) != (mat2->cols) || cols1 <= 0 || cols2 <= 0 || rows1 <= 0 || rows2 <= 0)
{
return 1;
}
#pragma omp parallel for
for (int i = 0; i < rows1; i++)
{
for (int j = 0; j < cols1/(4*4); j += 4)
{
__m256d v1 = _mm256_set_pd(mat1->data[i * cols1 + j + 3], mat1->data[i * cols1 + j + 2], mat1->data[i * cols1 + j + 1], mat1->data[i * cols1 + j]);
__m256d v2 = _mm256_set_pd(mat2->data[i * cols1 + j + 3], mat2->data[i * cols1 + j + 2], mat2->data[i * cols1 + j + 1], mat2->data[i * cols1 + j]);
__m256d diff = _mm256_sub_pd(v1, v2);
_mm256_storeu_pd(result->data + i * cols1 + j, diff);
}
for (int j = cols1/(4*4); j < cols1; j += 1) {
result->data[i*cols1+j] = mat1->data[i*cols1+j] - mat2->data[i*cols1+j];
}
}
return 0;
}
/*
* Store the result of multiplying mat1 and mat2 to `result`.
* Return 0 upon success and a nonzero value upon failure.
* Remember that matrix multiplication is not the same as multiplying individual elements.
*/
int mul_matrix(matrix *result, matrix *mat1, matrix *mat2)
{
if (!result || !mat1 || !mat2) {
return -1;
}
int cols1 = mat1->cols;
int cols2 = mat2->cols;
int rows1 = mat1->rows;
int rows2 = mat2->rows;
int area = rows1 * cols2;
if (cols1 != rows2 || cols1 <= 0 || cols2 <= 0 || rows1 <= 0 || rows2 <= 0) {
return -1;
}
int index = 0;
/* make transpose of mat2 */
double *transpose = (double*) calloc(rows2 * cols2, sizeof(double));
if (transpose == NULL) {
return -1;
}
for (int i = 0; i < cols2; i ++) {
for (int j = 0; j < rows2/4*4; j+=4) {
__m256d newVal = _mm256_set_pd(mat2->data[(j+3)*cols2+i], mat2->data[(j+2)*cols2+i], mat2->data[(j+1)*cols2+i], mat2->data[j*cols2+i]);
_mm256_storeu_pd(transpose+index, newVal);
index +=4;
}
for (int j = rows2/4*4; j < rows2; j+=1) {
transpose[index] = mat2->data[j*cols2+i];
index +=1;
}
}
/* done w transpose, begin multiplication */
for (int i = 0; i < area; i++) {
double *data = (double*) (mat1->data) + ((i/rows1)*cols1);
double *t_ptr = (double*) transpose + ((i%cols2)*rows2);
for (int j = 0; j < cols1/4*4; j+=4) {
__m256d sum = _mm256_loadu_pd((result->data)+i);
__m256d m_vals = _mm256_set_pd(data[j+3], data[j+2], data[j+1], data[j]);
__m256d t_vals = _mm256_set_pd(t_ptr[j+3], t_ptr[j+2], t_ptr[j+1], t_ptr[j]);
__m256d product = _mm256_mul_pd(t_vals, m_vals);
sum = _mm256_add_pd(sum, product);
_mm256_storeu_pd((result->data)+i, sum);
}
for (int j = cols1/4*4; j<cols1; j++) {
result->data[i] += data[j] * t_ptr[j];
}
}
free(transpose);
return 0;
}
/*
* Store the result of raising mat to the (pow)th power to `result`.
* Return 0 upon success and a nonzero value upon failure.
* Remember that pow is defined with matrix multiplication, not element-wise multiplication.
*/
int pow_matrix(matrix *result, matrix *mat, int pow)
{
int side = mat -> rows;
int area = side * side;
if (pow == 0) {
/* make identity matrix */
#pragma omp parallel for
for (int i = 0; i < side/8*8; i += 8) {
result->data[i * side + i] = 1;
result->data[(i+1) * side + (i+1)] = 1;
result->data[(i+2) * side + (i+2)] = 1;
result->data[(i+3) * side + (i+3)] = 1;
result->data[(i+4) * side + (i+4)] = 1;
result->data[(i+5) * side + (i+5)] = 1;
result->data[(i+6) * side + (i+6)] = 1;
result->data[(i+7) * side + (i+7)] = 1;
}
#pragma omp parallel for
for (int i = side/8*8; i < side; i ++) {
result->data[i * side + i] = 1;
}
if (!result -> data) {
return -1;
}
return 0;
}
if (pow == 1) {
memcpy(result->data, mat->data, sizeof(double) * area);
return 0;
}
matrix* iter = NULL;
allocate_matrix(&iter, side, side);
/* matrix multiplied */
while(pow > 0) {
if (pow % 2 == 1) {
mul_matrix(result, mat, mat);
}
mul_matrix(iter, mat, mat);
memcpy(mat->data, iter->data, sizeof(double)*area);
pow = pow/2;
}
return 0;
}
/*
* Store the result of element-wise negating mat's entries to `result`.
* Return 0 upon success and a nonzero value upon failure.
*/
int neg_matrix(matrix *result, matrix *mat)
{
/* to optimize:
(1) load current item from mat
(2) multiply 1 by -1
(3) store product in result
*/
int cols = mat->cols;
#pragma omp parallel for
for (int i = 0; i < mat->rows; i++)
{
for (int j = 0; j < cols/(4*4); j += 4)
{
__m256d v1 = _mm256_set_pd(mat->data[i * cols + j + 3], mat->data[i * cols + j + 2], mat->data[i * cols + j + 1], mat->data[i * cols + j]);
__m256d negated = _mm256_mul_pd(v1, _mm256_set1_pd(-1));
_mm256_storeu_pd((result->data) + i * cols + j, negated);
}
for (int j = cols/(4*4); j < cols; j += 1) {
result->data[i*cols+j] = mat->data[i*cols+j] * -1 ;
}
}
return 0;
}
/*
* Store the result of taking the absolute value element-wise to `result`.
* Return 0 upon success and a nonzero value upon failure.
*/
int abs_matrix(matrix *result, matrix *mat)
{
/* to optimize:
(1) load current item from mat
(2) negate item
(3) take max of item & its negation
(4) store max in result
*/
int cols = mat->cols;
#pragma omp parallel for
for (int i = 0; i < mat->rows; i++)
{
for (int j = 0; j < cols/(4*4); j += 4)
{
__m256d v1 = _mm256_set_pd(mat->data[i * cols + j + 3], mat->data[i * cols + j + 2], mat->data[i * cols + j + 1], mat->data[i * cols + j]);
__m256d negated = _mm256_mul_pd(v1, _mm256_set1_pd(-1));
__m256d maximum = _mm256_max_pd(v1, negated);
_mm256_storeu_pd((result->data) + i * cols + j, maximum);
}
for (int j = cols/(4*4); j <= cols; j += 1) {
double curr = mat->data[i*cols+j];
if (curr < 0) {
curr = curr * -1;
}
result->data[i*cols+j] = curr;
}
}
return 0;
}
#include "numc.h"
#include <structmember.h>
static PyTypeObject Matrix61cType;
/* Helper functions for initalization of matrices and vectors */
/* Matrix(rows, cols, low, high). Fill a matrix random double values */
static int init_rand(PyObject *self, int rows, int cols, unsigned int seed, double low, double high) {
matrix *new_mat;
int alloc_failed = allocate_matrix(&new_mat, rows, cols);
if (alloc_failed)
return alloc_failed;
rand_matrix(new_mat, seed, low, high);
((Matrix61c *)self)->mat = new_mat;
((Matrix61c *)self)->shape = PyTuple_Pack(2, PyLong_FromLong(rows), PyLong_FromLong(cols));
return 0;
}
/* Matrix(rows, cols, val). Fill a matrix of dimension rows * cols with val*/
static int init_fill(PyObject *self, int rows, int cols, double val) {
matrix *new_mat;
int alloc_failed = allocate_matrix(&new_mat, rows, cols);
if (alloc_failed)
return alloc_failed;
else {
fill_matrix(new_mat, val);
((Matrix61c *)self)->mat = new_mat;
((Matrix61c *)self)->shape = PyTuple_Pack(2, PyLong_FromLong(rows), PyLong_FromLong(cols));
}
return 0;
}
/* Matrix(rows, cols, 1d_list). Fill a matrix with dimension rows * cols with 1d_list values */
static int init_1d(PyObject *self, int rows, int cols, PyObject *lst) {
if (rows * cols != PyList_Size(lst)) {
PyErr_SetString(PyExc_TypeError, "Incorrect number of elements in list");
return -1;
}
matrix *new_mat;
int alloc_failed = allocate_matrix(&new_mat, rows, cols);
if (alloc_failed)
return alloc_failed;
int count = 0;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
set(new_mat, i, j, PyFloat_AsDouble(PyList_GetItem(lst, count)));
count++;
}
}
((Matrix61c *)self)->mat = new_mat;
((Matrix61c *)self)->shape = PyTuple_Pack(2, PyLong_FromLong(rows), PyLong_FromLong(cols));
return 0;
}
/* Matrix(2d_list). Fill a matrix with dimension len(2d_list) * len(2d_list[0]) */
static int init_2d(PyObject *self, PyObject *lst) {
int rows = PyList_Size(lst);
if (rows == 0) {
PyErr_SetString(PyExc_TypeError, "Cannot initialize numc.Matrix with an empty list");
return -1;
}
int cols;
if (!PyList_Check(PyList_GetItem(lst, 0))) {
PyErr_SetString(PyExc_TypeError, "List values not valid");
return -1;
} else {
cols = PyList_Size(PyList_GetItem(lst, 0));
}
for (int i = 0; i < rows; i++) {
if (!PyList_Check(PyList_GetItem(lst, i)) || PyList_Size(PyList_GetItem(lst, i)) != cols) {
PyErr_SetString(PyExc_TypeError, "List values not valid");
return -1;
}
}
matrix *new_mat;
int alloc_failed = allocate_matrix(&new_mat, rows, cols);
if (alloc_failed)
return alloc_failed;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
set(new_mat, i, j, PyFloat_AsDouble(PyList_GetItem(PyList_GetItem(lst, i), j)));
}
}
((Matrix61c *)self)->mat = new_mat;
((Matrix61c *)self)->shape = PyTuple_Pack(2, PyLong_FromLong(rows), PyLong_FromLong(cols));
return 0;
}
/* This deallocation function is called when reference count is 0*/
static void Matrix61c_dealloc(Matrix61c *self) {
deallocate_matrix(self->mat);
Py_TYPE(self)->tp_free(self);
}
/* For immutable types all initializations should take place in tp_new */
static PyObject *Matrix61c_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
/* size of allocated memory is tp_basicsize + nitems*tp_itemsize*/
Matrix61c *self = (Matrix61c *)type->tp_alloc(type, 0);
return (PyObject *)self;
}
/* This matrix61c type is mutable, so needs init function. Return 0 on success otherwise -1 */
static int Matrix61c_init(PyObject *self, PyObject *args, PyObject *kwds) {
/* Generate random matrices */
if (kwds != NULL) {
PyObject *rand = PyDict_GetItemString(kwds, "rand");
if (!rand) {
PyErr_SetString(PyExc_TypeError, "Invalid arguments");
return -1;
}
if (!PyBool_Check(rand)) {
PyErr_SetString(PyExc_TypeError, "Invalid arguments");
return -1;
}
if (rand != Py_True) {
PyErr_SetString(PyExc_TypeError, "Invalid arguments");
return -1;
}
PyObject *low = PyDict_GetItemString(kwds, "low");
PyObject *high = PyDict_GetItemString(kwds, "high");
PyObject *seed = PyDict_GetItemString(kwds, "seed");
double double_low = 0;
double double_high = 1;
unsigned int unsigned_seed = 0;
if (low) {
if (PyFloat_Check(low)) {
double_low = PyFloat_AsDouble(low);
} else if (PyLong_Check(low)) {
double_low = PyLong_AsLong(low);
}
}
if (high) {
if (PyFloat_Check(high)) {
double_high = PyFloat_AsDouble(high);
} else if (PyLong_Check(high)) {
double_high = PyLong_AsLong(high);
}
}
if (double_low >= double_high) {
PyErr_SetString(PyExc_TypeError, "Invalid arguments");
return -1;
}
// Set seed if argument exists
if (seed) {
if (PyLong_Check(seed)) {
unsigned_seed = PyLong_AsUnsignedLong(seed);
}
}
PyObject *rows = NULL;
PyObject *cols = NULL;
if (PyArg_UnpackTuple(args, "args", 2, 2, &rows, &cols)) {
if (rows && cols && PyLong_Check(rows) && PyLong_Check(cols)) {
return init_rand(self, PyLong_AsLong(rows), PyLong_AsLong(cols), unsigned_seed, double_low, double_high);
}
} else {
PyErr_SetString(PyExc_TypeError, "Invalid arguments");
return -1;
}
}
PyObject *arg1 = NULL;
PyObject *arg2 = NULL;
PyObject *arg3 = NULL;
if (PyArg_UnpackTuple(args, "args", 1, 3, &arg1, &arg2, &arg3)) {
/* arguments are (rows, cols, val) */
if (arg1 && arg2 && arg3 && PyLong_Check(arg1) && PyLong_Check(arg2) && (PyLong_Check(arg3) || PyFloat_Check(arg3))) {
if (PyLong_Check(arg3)) {
return init_fill(self, PyLong_AsLong(arg1), PyLong_AsLong(arg2), PyLong_AsLong(arg3));
}
else
return init_fill(self, PyLong_AsLong(arg1), PyLong_AsLong(arg2), PyFloat_AsDouble(arg3));
} else if (arg1 && arg2 && arg3 && PyLong_Check(arg1) && PyLong_Check(arg2) && PyList_Check(arg3)) {
/* Matrix(rows, cols, 1D list) */
return init_1d(self, PyLong_AsLong(arg1), PyLong_AsLong(arg2), arg3);
} else if (arg1 && PyList_Check(arg1) && arg2 == NULL && arg3 == NULL) {
/* Matrix(rows, cols, 1D list) */
return init_2d(self, arg1);
} else if (arg1 && arg2 && PyLong_Check(arg1) && PyLong_Check(arg2) && arg3 == NULL) {
/* Matrix(rows, cols, 1D list) */
return init_fill(self, PyLong_AsLong(arg1), PyLong_AsLong(arg2), 0);
} else {
PyErr_SetString(PyExc_TypeError, "Invalid arguments");
return -1;
}
} else {
PyErr_SetString(PyExc_TypeError, "Invalid arguments");
return -1;
}
}
/* List of lists representations for matrices */
static PyObject *Matrix61c_to_list(Matrix61c *self) {
int rows = self->mat->rows;
int cols = self->mat->cols;
PyObject *py_lst = PyList_New(rows);
for (int i = 0; i < rows; i++) {
PyList_SetItem(py_lst, i, PyList_New(cols));
PyObject *curr_row = PyList_GetItem(py_lst, i);
for (int j = 0; j < cols; j++) {
PyList_SetItem(curr_row, j, PyFloat_FromDouble(get(self->mat, i, j)));
}
}
return py_lst;
}
static PyObject *Matrix61c_class_to_list(Matrix61c *self, PyObject *args) {
PyObject *mat = NULL;
if (PyArg_UnpackTuple(args, "args", 1, 1, &mat)) {
if (!PyObject_TypeCheck(mat, &Matrix61cType)) {
PyErr_SetString(PyExc_TypeError, "Argument must of type numc.Matrix!");
return NULL;
}
Matrix61c* mat61c = (Matrix61c*)mat;
return Matrix61c_to_list(mat61c);
} else {
PyErr_SetString(PyExc_TypeError, "Invalid arguments");
return NULL;
}
}
/* Add class methods */
static PyMethodDef Matrix61c_class_methods[] = {
{"to_list", (PyCFunction)Matrix61c_class_to_list, METH_VARARGS, "Returns a list representation of numc.Matrix"},
{NULL, NULL, 0, NULL}
};
/* Matrix61c string representation. For printing purposes. */
static PyObject *Matrix61c_repr(PyObject *self) {
PyObject *py_lst = Matrix61c_to_list((Matrix61c *)self);
return PyObject_Repr(py_lst);
}
/* For __getitem__. (e.g. mat[0]) */
static PyObject *Matrix61c_subscript(Matrix61c* self, PyObject* key) {
if (!PyLong_Check(key)) {
PyErr_SetString(PyExc_TypeError, "Key is not valid");
return NULL;
}
int index = PyLong_AsLong(key);
if (index >= self->mat->rows || index < 0) {
PyErr_SetString(PyExc_IndexError, "Index out of range");
return NULL;
}
if (self->mat->cols == 1) { // if one single number, unwrap from list
return PyFloat_FromDouble(get(self->mat, index, 0));
}
matrix *new_mat;
int ref_failed = allocate_matrix_ref(&new_mat, self->mat, index * self->mat->cols, self->mat->cols, 1);
if (ref_failed != 0) {
PyErr_SetString(PyExc_RuntimeError, "Failed to allocate slice");
return NULL;
}
Matrix61c* rv = (Matrix61c*) Matrix61c_new(&Matrix61cType, NULL, NULL);
rv->mat = new_mat;
rv->shape = PyTuple_Pack(2, PyLong_FromLong(new_mat->rows), PyLong_FromLong(1));
return (PyObject*)rv;
}
/* For __setitem__ (e.g. mat[0] = 1) */
static int Matrix61c_set_subscript(Matrix61c* self, PyObject *key, PyObject *v) {
if (!PyLong_Check(key)) {
PyErr_SetString(PyExc_TypeError, "Key is not valid");
return -1;
}
int index = PyLong_AsLong(key);
if (index >= self->mat->rows || index < 0) {
PyErr_SetString(PyExc_IndexError, "Index out of range");
return -1;
}
int cols = self->mat->cols;
if (cols == 1) {
if (!PyFloat_Check(v) && !PyLong_Check(v)) {
PyErr_SetString(PyExc_TypeError, "Value is not valid");
return -1;
}
double val = PyFloat_AsDouble(v);
set(self->mat, index, 0, val);
return 0;
} else {
if (!PyList_Check(v)) {
PyErr_SetString(PyExc_TypeError, "Value is not valid");
return -1;
}
for (int i = 0; i < cols; i++) {
if (!PyFloat_Check(PyList_GetItem(v, i)) && !PyLong_Check(PyList_GetItem(v, i))) {
PyErr_SetString(PyExc_TypeError, "Value is not valid");
return -1;
}
set(self->mat, index, i, PyFloat_AsDouble(PyList_GetItem(v, i)));
}
return 0;
}
return -1;
}
static PyMappingMethods Matrix61c_mapping = {
// NULL,
.mp_subscript = (binaryfunc) Matrix61c_subscript,
.mp_ass_subscript= (objobjargproc) Matrix61c_set_subscript,
};
/* NUMBER METHODS */
/*
* Add the second numc.Matrix (Matrix61c) object to the first one. The first operand is
* self, and the second operand can be obtained by casting `args`.
*/
static PyObject *Matrix61c_add(Matrix61c* self, PyObject* args) {
if (!PyObject_TypeCheck(self, &Matrix61cType) || !PyObject_TypeCheck(args, &Matrix61cType)) {
PyErr_SetString(PyExc_TypeError, "Entry must be a numc.matrix.");
return NULL;
}
Matrix61c* argMat = (Matrix61c*) args;
if ((self->mat->rows != argMat->mat->rows)
|| (self->mat->cols != argMat->mat->cols)) {
PyErr_SetString(PyExc_ValueError, "Matrices must have equal dimensions.");
return NULL;
}
Matrix61c* result = (Matrix61c*) Matrix61c_new(&Matrix61cType, NULL, NULL);
result -> shape = PyTuple_Pack(2, PyLong_FromLong(self->mat->rows), PyLong_FromLong(self->mat->cols));
int err = allocate_matrix(&(result->mat), self->mat->rows, self->mat->cols);
int err1 = add_matrix((result->mat), self->mat, argMat -> mat);
if (err != 0 || err1 != 0) {
PyErr_SetString(PyExc_RuntimeError, "Allocation runtime error has occured");
return NULL;
}
return (PyObject*) result;
}
/*
* Substract the second numc.Matrix (Matrix61c) object from the first one. The first operand is
* self, and the second operand can be obtained by casting `args`.
*/
static PyObject *Matrix61c_sub(Matrix61c* self, PyObject* args) {
if (!PyObject_TypeCheck(self, &Matrix61cType) || !PyObject_TypeCheck(args, &Matrix61cType)) {
PyErr_SetString(PyExc_TypeError, "Entry must be a numc.matrix.");
return NULL;
}
Matrix61c* argMat = (Matrix61c*) args;
if ((self->mat->rows != argMat->mat->rows)
|| (self->mat->cols != argMat->mat->cols)) {
PyErr_SetString(PyExc_ValueError, "Matrices must have equal dimensions.");
return NULL;
}
Matrix61c* result = (Matrix61c*) Matrix61c_new(&Matrix61cType, NULL, NULL);
result -> shape = PyTuple_Pack(2, PyLong_FromLong(self->mat->rows), PyLong_FromLong(self->mat->cols));
int err = allocate_matrix(&(result->mat), self->mat->rows, self->mat->cols);
int err1 = sub_matrix((result->mat), self->mat, argMat -> mat);
if (err != 0 || err1 != 0) {
PyErr_SetString(PyExc_RuntimeError, "Allocation runtime error has occured");
return NULL;
}
return (PyObject*) result;
}
/*
* NOT element-wise multiplication. The first operand is self, and the second operand
* can be obtained by casting `args`.
*/
static PyObject *Matrix61c_multiply(Matrix61c* self, PyObject *args) {
if (!PyObject_TypeCheck(self, &Matrix61cType) || !PyObject_TypeCheck(args, &Matrix61cType)) {
PyErr_SetString(PyExc_TypeError, "Entry must be a numc.matrix.");
return NULL;
}
Matrix61c* argMat = (Matrix61c*) args;
if ((self->mat->cols != argMat->mat->rows)) {
PyErr_SetString(PyExc_ValueError, "Matrix A's number of columns must be equal to matrix B's number of rows!");
return NULL;
}
Matrix61c* result = (Matrix61c*) Matrix61c_new(&Matrix61cType, NULL, NULL);
result -> shape = PyTuple_Pack(2, PyLong_FromLong(self->mat->rows), PyLong_FromLong(argMat->mat->cols));
int err = allocate_matrix(&(result->mat), self->mat->rows, argMat->mat->cols);
int err1 = mul_matrix((result->mat), self->mat, argMat -> mat);
if (err != 0 || err1 != 0) {
PyErr_SetString(PyExc_RuntimeError, "Allocation runtime error has occured");
return NULL;
}
return (PyObject*) result;
}
/*
* Negates the given numc.Matrix.
*/
static PyObject *Matrix61c_neg(Matrix61c* self) {
Matrix61c* result = (Matrix61c*) Matrix61c_new(&Matrix61cType, NULL, NULL);
result -> shape = PyTuple_Pack(2, PyLong_FromLong(self->mat->rows), PyLong_FromLong(self->mat->cols));
int err = allocate_matrix(&(result->mat), self->mat->rows, self->mat->cols);
int err1 = neg_matrix((result->mat), self->mat);
if (err != 0 || err1 != 0) {
PyErr_SetString(PyExc_RuntimeError, "Allocation runtime error has occured");
return NULL;
}
return (PyObject*) result;
}
/*
* Take the element-wise absolute value of this numc.Matrix.
*/
static PyObject *Matrix61c_abs(Matrix61c *self) {
Matrix61c* result = (Matrix61c*) Matrix61c_new(&Matrix61cType, NULL, NULL);
result -> shape = PyTuple_Pack(2, PyLong_FromLong(self->mat->rows), PyLong_FromLong(self->mat->cols));
int err = allocate_matrix(&(result->mat), self->mat->rows, self->mat->cols);
int err1 = abs_matrix((result->mat), self->mat);
if (err != 0 || err1 != 0) {
PyErr_SetString(PyExc_RuntimeError, "Allocation runtime error has occured");
return NULL;
}
return (PyObject*) result;
}
/*
* Raise numc.Matrix (Matrix61c) to the `pow`th power. You can ignore the argument `optional`.
*/
static PyObject *Matrix61c_pow(Matrix61c *self, PyObject *pow, PyObject *optional) {
if (!PyObject_TypeCheck(self, &Matrix61cType)) {
PyErr_SetString(PyExc_TypeError, "Entry must be a numc.matrix.");
return NULL;
}
if (!PyLong_Check(pow)) {
PyErr_SetString(PyExc_TypeError, "Power must be an integer.");
return NULL;
}
int argPow = (int) PyLong_AsLong(pow);
if ((self->mat->cols != self->mat->rows)) {
PyErr_SetString(PyExc_ValueError, "The matrix must be square!");
return NULL;
}
if (argPow < 0) {
PyErr_SetString(PyExc_ValueError, "Power must be greater than or equal to 0");
return NULL;
}
Matrix61c* result = (Matrix61c*) Matrix61c_new(&Matrix61cType, NULL, NULL);
result -> shape = PyTuple_Pack(2, PyLong_FromLong(self->mat->rows), PyLong_FromLong(self->mat->cols));
int err = allocate_matrix(&(result->mat), self->mat->rows, self->mat->cols);
int err1 = pow_matrix((result->mat), self->mat, argPow);
if (err != 0 || err1 != 0) {
PyErr_SetString(PyExc_RuntimeError, "Allocation runtime error has occured");
return NULL;
}
return (PyObject*) result;
}
/*
* Create a PyNumberMethods struct for overloading operators with all the number methods you have
* defined. You might find this link helpful: https://docs.python.org/3.6/c-api/typeobj.html
*/
static PyNumberMethods Matrix61c_as_number = {
.nb_add = (binaryfunc) Matrix61c_add,
.nb_subtract = (binaryfunc) Matrix61c_sub,
.nb_multiply = (binaryfunc) Matrix61c_multiply,
.nb_power = (ternaryfunc) Matrix61c_pow,
.nb_negative = (unaryfunc) Matrix61c_neg,
.nb_absolute = (unaryfunc) Matrix61c_abs,
};
/* INSTANCE METHODS */
/*
* Given a numc.Matrix self, parse `args` to (int) row, (int) col, and (double/int) val.
* This function should return None in Python.
*/
static PyObject *Matrix61c_set_value(Matrix61c *self, PyObject* args) {
PyObject *argRow = NULL;
PyObject *argCol = NULL;
PyObject *argVal = NULL;
if (!PyObject_TypeCheck(self, &Matrix61cType)) {
PyErr_SetString(PyExc_TypeError, "Matrix must be a numc.Matrix");
return Py_None;
}
if (!PyArg_UnpackTuple(args, "args", 3, 3, &argRow, &argCol, &argVal)) {
PyErr_SetString(PyExc_TypeError, "Must have 3 arguments in args");
return Py_None;
}
if (!PyLong_Check(argRow) || !PyLong_Check(argCol)) {
PyErr_SetString(PyExc_TypeError, "Column & row must both be integers");
return Py_None;
}
int row = (int) PyLong_AsLong(argRow);
int col = (int) PyLong_AsLong(argCol);
if (!PyFloat_Check(argVal) && !PyLong_Check(argVal)) {
PyErr_SetString(PyExc_TypeError, "Val must be either a float or integer");
return Py_None;
}
if (row < 0 || row > self->mat->rows || col < 0 || col > self->mat->cols) {
PyErr_SetString(PyExc_ValueError, "Row or column is out of bounds");
return Py_None;
}
double val = (double) PyFloat_AsDouble(argVal);
set(self->mat, row, col, val);
return Py_None;
}
/*
* Given a numc.Matrix `self`, parse `args` to (int) row and (int) col.
* This function should return the value at the `row`th row and `col`th column, which is a Python
* float.
*/
static PyObject *Matrix61c_get_value(Matrix61c *self, PyObject* args) {
PyObject *argRow = NULL;
PyObject *argCol = NULL;
if (!PyObject_TypeCheck(self, &Matrix61cType)) {
PyErr_SetString(PyExc_TypeError, "Matrix must be a numc.Matrix");
return Py_None;
}
if (!PyArg_UnpackTuple(args, "args", 2, 2, &argRow, &argCol)) {
PyErr_SetString(PyExc_TypeError, "There must be two arguments in args.");
return Py_None;
}
if (!PyLong_Check(argRow) || !PyLong_Check(argCol)) {
PyErr_SetString(PyExc_TypeError, "Row & column must be integers");
return Py_None;
}
int row = (int) PyLong_AsLong(argRow);
int col = (int) PyLong_AsLong(argCol);
if (row < 0 || row >= self->mat->rows) {
PyErr_SetString(PyExc_TypeError, "Row is out of bounds");
return Py_None;
} else if (col < 0 || col >= self->mat->cols) {
PyErr_SetString(PyExc_TypeError, "Col is out of bounds");
return Py_None;
}
return PyFloat_FromDouble(get(self->mat, row, col));
}
/*
* Create an array of PyMethodDef structs to hold the instance methods.
* Name the python function corresponding to Matrix61c_get_value as "get" and Matrix61c_set_value
* as "set"
* You might find this link helpful: https://docs.python.org/3.6/c-api/structures.html
*/
static PyMethodDef Matrix61c_methods[] = {
{"set", (PyCFunction)Matrix61c_set_value, METH_VARARGS, "Sets row, column index to given value."},
{"get", (PyCFunction)Matrix61c_get_value, METH_VARARGS, "Returns value at given row, index."},
{NULL} /* Sentinel */
};
/* INSTANCE ATTRIBUTES*/
static PyMemberDef Matrix61c_members[] = {
{"shape", T_OBJECT_EX, offsetof(Matrix61c, shape), 0,
"(rows, cols)"},
{NULL} /* Sentinel */
};
static PyTypeObject Matrix61cType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "numc.Matrix",
.tp_basicsize = sizeof(Matrix61c),
.tp_dealloc = (destructor)Matrix61c_dealloc,
.tp_repr = (reprfunc)Matrix61c_repr,
.tp_as_number = &Matrix61c_as_number,
.tp_flags = Py_TPFLAGS_DEFAULT |
Py_TPFLAGS_BASETYPE,
.tp_doc = "numc.Matrix objects",
.tp_methods = Matrix61c_methods,
.tp_members = Matrix61c_members,
.tp_as_mapping = &Matrix61c_mapping,
.tp_init = (initproc)Matrix61c_init,
.tp_new = Matrix61c_new
};
static struct PyModuleDef numcmodule = {
PyModuleDef_HEAD_INIT,
"numc",
"Numc matrix operations",
-1,
Matrix61c_class_methods
};
/* Initialize the numc module */
PyMODINIT_FUNC PyInit_numc(void) {
PyObject* m;
if (PyType_Ready(&Matrix61cType) < 0)
return NULL;
m = PyModule_Create(&numcmodule);
if (m == NULL)
return NULL;
Py_INCREF(&Matrix61cType);
PyModule_AddObject(m, "Matrix", (PyObject *)&Matrix61cType);
printf("CS61C Spring 2021 Project 4: numc imported!\n");
fflush(stdout);
return m;
}
from utils import *
from unittest import TestCase
"""
- For each operation, you should write tests to test on matrices of different sizes.
- Keep in mind that the tests provided in the starter code are NOT comprehensive. That is, we strongly
advise you to modify them and add new tests.
- Hint: use dp_mc_matrix to generate dumbpy and numc matrices with the same data and use
cmp_dp_nc_matrix to compare the results
"""
class TestAdd(TestCase):
def test_small_add(self):
dp_mat1, nc_mat1 = rand_dp_nc_matrix(2, 2, seed=0)
dp_mat2, nc_mat2 = rand_dp_nc_matrix(2, 2, seed=1)
print("CORRECT ", dp_mat1+dp_mat2)
print("NUMC ", nc_mat1+nc_mat2)
is_correct, speed_up = compute([dp_mat1, dp_mat2], [nc_mat1, nc_mat2], "add")
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_medium_add(self):
dp_mat1, nc_mat1 = rand_dp_nc_matrix(50, 50, seed=0)
dp_mat2, nc_mat2 = rand_dp_nc_matrix(50, 50, seed=1)
is_correct, speed_up = compute([dp_mat1, dp_mat2], [nc_mat1, nc_mat2], "add")
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_large_add(self):
dp_mat1, nc_mat1 = rand_dp_nc_matrix(100, 100, seed=0)
dp_mat2, nc_mat2 = rand_dp_nc_matrix(100, 100, seed=1)
is_correct, speed_up = compute([dp_mat1, dp_mat2], [nc_mat1, nc_mat2], "add")
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_vlarge_add(self):
dp_mat1, nc_mat1 = rand_dp_nc_matrix(5000, 5000, seed=0)
dp_mat2, nc_mat2 = rand_dp_nc_matrix(5000, 5000, seed=1)
is_correct, speed_up = compute([dp_mat1, dp_mat2], [nc_mat1, nc_mat2], "add")
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_rand_add(self):
dp_mat1, nc_mat1 = rand_dp_nc_matrix(100, 100, 10, 1000, seed=0)
dp_mat2, nc_mat2 = rand_dp_nc_matrix(100, 100, 10, 5000, seed=1)
is_correct, speed_up = compute([dp_mat1, dp_mat2], [nc_mat1, nc_mat2], "add")
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_neg_add(self):
dp_mat1, nc_mat1 = rand_dp_nc_matrix(300, 300, -1000, 0, seed=0)
dp_mat2, nc_mat2 = rand_dp_nc_matrix(300, 300, -2000, 0, seed=1)
is_correct, speed_up = compute([dp_mat1, dp_mat2], [nc_mat1, nc_mat2], "add")
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_typecheck_add(self):
try:
nc.Matrix(3, 3) + [2, 2]
self.assertTrue(False)
except TypeError as e:
print(e)
pass
def test_dims_add(self):
try:
nc.Matrix(3, 3) + nc.Matrix(5, 5)
self.assertTrue(False)
except ValueError as e:
print(e)
pass
class TestSub(TestCase):
def test_small_sub(self):
dp_mat1, nc_mat1 = rand_dp_nc_matrix(2, 2, seed=0)
dp_mat2, nc_mat2 = rand_dp_nc_matrix(2, 2, seed=1)
is_correct, speed_up = compute([dp_mat1, dp_mat2], [nc_mat1, nc_mat2], "sub")
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_medium_sub(self):
dp_mat1, nc_mat1 = rand_dp_nc_matrix(50, 50, seed=0)
dp_mat2, nc_mat2 = rand_dp_nc_matrix(50, 50, seed=1)
is_correct, speed_up = compute([dp_mat1, dp_mat2], [nc_mat1, nc_mat2], "sub")
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_large_sub(self):
dp_mat1, nc_mat1 = rand_dp_nc_matrix(100, 100, seed=0)
dp_mat2, nc_mat2 = rand_dp_nc_matrix(100, 100, seed=1)
is_correct, speed_up = compute([dp_mat1, dp_mat2], [nc_mat1, nc_mat2], "sub")
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_rand_sub(self):
dp_mat1, nc_mat1 = rand_dp_nc_matrix(100, 100, 10, 1000, seed=0)
dp_mat2, nc_mat2 = rand_dp_nc_matrix(100, 100, 10, 5000, seed=1)
is_correct, speed_up = compute([dp_mat1, dp_mat2], [nc_mat1, nc_mat2], "sub")
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_neg_sub(self):
dp_mat1, nc_mat1 = rand_dp_nc_matrix(300, 300, -1000, 0, seed=0)
dp_mat2, nc_mat2 = rand_dp_nc_matrix(300, 300, -2000, 0, seed=1)
is_correct, speed_up = compute([dp_mat1, dp_mat2], [nc_mat1, nc_mat2], "sub")
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_typecheck_sub(self):
try:
nc.Matrix(3, 3) - [2, 2]
self.assertTrue(False)
except TypeError as e:
print(e)
pass
def test_dims_sub(self):
try:
nc.Matrix(3, 3) - nc.Matrix(5, 5)
self.assertTrue(False)
except ValueError as e:
print(e)
pass
class TestAbs(TestCase):
def test_small_abs(self):
dp_mat, nc_mat = rand_dp_nc_matrix(2, 2, -10, 0, seed=0)
is_correct, speed_up = compute([dp_mat], [nc_mat], "abs")
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_medium_abs(self):
dp_mat, nc_mat = rand_dp_nc_matrix(50, 50, -50, -10, seed=0)
is_correct, speed_up = compute([dp_mat], [nc_mat], "abs")
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_large_abs(self):
dp_mat, nc_mat = rand_dp_nc_matrix(100, 100, -50, 50, seed=0)
is_correct, speed_up = compute([dp_mat], [nc_mat], "abs")
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_rand_abs(self):
dp_mat, nc_mat = rand_dp_nc_matrix(300, 300, -1000, 1000, seed=0)
is_correct, speed_up = compute([dp_mat], [nc_mat], "abs")
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_neg_abs(self):
dp_mat, nc_mat = rand_dp_nc_matrix(300, 300, -100000, 0, seed=0)
is_correct, speed_up = compute([dp_mat], [nc_mat], "abs")
self.assertTrue(is_correct)
print_speedup(speed_up)
class TestNeg(TestCase):
def test_small_neg(self):
dp_mat, nc_mat = rand_dp_nc_matrix(2, 2, seed=0)
is_correct, speed_up = compute([dp_mat], [nc_mat], "neg")
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_medium_neg(self):
dp_mat, nc_mat = rand_dp_nc_matrix(50, 50, -50, -10, seed=0)
is_correct, speed_up = compute([dp_mat], [nc_mat], "neg")
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_large_neg(self):
dp_mat, nc_mat = rand_dp_nc_matrix(100, 100, -50, 50, seed=0)
is_correct, speed_up = compute([dp_mat], [nc_mat], "neg")
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_rand_neg(self):
dp_mat, nc_mat = rand_dp_nc_matrix(300, 300, -1000, 1000, seed=0)
is_correct, speed_up = compute([dp_mat], [nc_mat], "neg")
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_neg_neg(self):
dp_mat, nc_mat = rand_dp_nc_matrix(300, 300, -100000, 0, seed=0)
is_correct, speed_up = compute([dp_mat], [nc_mat], "neg")
self.assertTrue(is_correct)
print_speedup(speed_up)
class TestMul(TestCase):
def test_small_mul(self):
dp_mat1, nc_mat1 = rand_dp_nc_matrix(2, 3, seed=0)
dp_mat2, nc_mat2 = rand_dp_nc_matrix(3, 2, seed=1)
is_correct, speed_up = compute([dp_mat1, dp_mat2], [nc_mat1, nc_mat2], "mul")
if not(is_correct):
print("correct", dp_mat1 * dp_mat2)
print("NUMC ", nc_mat1 * nc_mat2)
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_medium_mul(self):
dp_mat1, nc_mat1 = rand_dp_nc_matrix(5, 1, seed=0)
dp_mat2, nc_mat2 = rand_dp_nc_matrix(1, 5, seed=1)
is_correct, speed_up = compute([dp_mat1, dp_mat2], [nc_mat1, nc_mat2], "mul")
if not(is_correct):
print("CORRECT 1", dp_mat1*dp_mat2)
print("NUMC ", nc_mat1 * nc_mat2)
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_large_mul(self):
dp_mat1, nc_mat1 = rand_dp_nc_matrix(100, 100, seed=0)
dp_mat2, nc_mat2 = rand_dp_nc_matrix(100, 100, seed=1)
is_correct, speed_up = compute([dp_mat1, dp_mat2], [nc_mat1, nc_mat2], "mul")
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_rand_mul(self):
dp_mat1, nc_mat1 = rand_dp_nc_matrix(100, 100, 10, 1000, seed=0)
dp_mat2, nc_mat2 = rand_dp_nc_matrix(100, 100, 10, 5000, seed=1)
is_correct, speed_up = compute([dp_mat1, dp_mat2], [nc_mat1, nc_mat2], "mul")
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_neg_mul(self):
dp_mat1, nc_mat1 = rand_dp_nc_matrix(300, 300, -1000, 0, seed=0)
dp_mat2, nc_mat2 = rand_dp_nc_matrix(300, 300, -2000, 1000, seed=1)
is_correct, speed_up = compute([dp_mat1, dp_mat2], [nc_mat1, nc_mat2], "mul")
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_mul_dim_1(self):
dp_mat1, nc_mat1 = rand_dp_nc_matrix(1, 1, -1000, 0, seed=0)
dp_mat2, nc_mat2 = rand_dp_nc_matrix(1, 1, -2000, 1000, seed=1)
is_correct, speed_up = compute([dp_mat1, dp_mat2], [nc_mat1, nc_mat2], "mul")
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_mul_dim_1000(self):
dp_mat1, nc_mat1 = rand_dp_nc_matrix(1000, 1000, -1000, 0, seed=0)
dp_mat2, nc_mat2 = rand_dp_nc_matrix(1000, 1000, -2000, 1000, seed=1)
is_correct, speed_up = compute([dp_mat1, dp_mat2], [nc_mat1, nc_mat2], "mul")
self.assertTrue(is_correct)
print_speedup(speed_up)
# def test_typecheck_mul(self):
# try:
# nc.Matrix(3, 3) * [2, 2]
# self.assertTrue(False)
# except TypeError as e:
# print(e)
# pass
# def test_dims_mul(self):
# try:
# nc.Matrix(3, 3) * nc.Matrix(5, 5)
# self.assertTrue(False)
# except ValueError as e:
# print(e)
# pass
class TestPow(TestCase):
def test_small_pow0to100(self):
for i in range(100):
print("Testing small to the power of " + str(i))
dp_mat, nc_mat = rand_dp_nc_matrix(2, 2, -1000, 1000, seed=0)
is_correct, speed_up = compute([dp_mat, i], [nc_mat, i], "pow")
if not(is_correct):
print("CORRECT ", dp_mat ** i)
print("NUMC ", nc_mat ** i)
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_medium_pow0to100(self):
for i in range(100):
print("Testing medium to the power of " + str(i))
dp_mat, nc_mat = rand_dp_nc_matrix(50, 50, -1000, 1000, seed=0)
is_correct, speed_up = compute([dp_mat, i], [nc_mat, i], "pow")
if not(is_correct):
print("CORRECT ", dp_mat ** i)
print("NUMC ", nc_mat ** i)
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_large_pow0to100(self):
for i in range(81):
print("Testing large to the power of " + str(i))
dp_mat, nc_mat = rand_dp_nc_matrix(100, 100, -1000, 1000, seed=0)
is_correct, speed_up = compute([dp_mat, i], [nc_mat, i], "pow")
if not(is_correct):
print("CORRECT ", dp_mat ** i)
print("NUMC ", nc_mat ** i)
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_changing_dims_0(self):
for i in range(1, 100):
for j in range (11):
print("Testing w dimensions of ", i, " to the power of ", j)
dp_mat, nc_mat = rand_dp_nc_matrix(i, i, -100000, 100000, seed=0)
is_correct, speed_up = compute([dp_mat, j], [nc_mat, j], "pow")
if not(is_correct):
print("ORIGINAL ", dp_mat)
print("CORRECT ", dp_mat ** j)
print("NUMC ", nc_mat ** j)
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_dim_1(self):
for i in range(1, 100):
dp_mat, nc_mat = rand_dp_nc_matrix(1, 1, -100000, 100000, seed=0)
is_correct, speed_up = compute([dp_mat, i], [nc_mat, i], "pow")
if not(is_correct):
print("CORRECT ", dp_mat ** j)
print("NUMC ", nc_mat ** j)
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_huge_pow0to20(self):
for i in range(20):
print("Testing huge to the power of " + str(i))
dp_mat, nc_mat = rand_dp_nc_matrix(1000, 1000, seed=0)
is_correct, speed_up = compute([dp_mat, i], [nc_mat, i], "pow")
self.assertTrue(is_correct)
print_speedup(speed_up)
def test_typecheck_pow(self):
try:
[[1, 2, 3], [4, 5, 6]] ** 3
self.assertTrue(False)
except TypeError as e:
print(e)
pass
def test_notnum_pow(self):
try:
nc.Matrix([[1, 2, 3], [4, 5, 6], [1, 2, 3]]) ** 5.49
self.assertTrue(False)
except TypeError as e:
print(e)
pass
def test_notsquare_pow(self):
try:
nc.Matrix([[1, 2, 3], [4, 5, 6]]) ** 5
self.assertTrue(False)
except ValueError as e:
print(e)
pass
def test_negpow_pow(self):
try:
nc.Matrix([[1, 2, 3], [4, 5, 6], [1, 2, 3]]) ** -5
self.assertTrue(False)
except ValueError as e:
print(e)
pass
class TestGet(TestCase):
def test_get(self):
dp_mat, nc_mat = rand_dp_nc_matrix(2, 2, seed=0)
rand_row = np.random.randint(dp_mat.shape[0])
rand_col = np.random.randint(dp_mat.shape[1])
self.assertEqual(round(dp_mat.get(rand_row, rand_col), decimal_places),
round(nc_mat.get(rand_row, rand_col), decimal_places))
def test_get_random(self):
dp_mat, nc_mat = rand_dp_nc_matrix(100, 100, -1000, 10000, seed=0)
rand_row = np.random.randint(dp_mat.shape[0])
rand_col = np.random.randint(dp_mat.shape[1])
self.assertEqual(round(dp_mat.get(rand_row, rand_col), decimal_places),
round(nc_mat.get(rand_row, rand_col), decimal_places))
def test_get_materror(self):
try:
dp_mat = [[1, 2, 3], [4, 5, 6]]
dp_mat[0, 0]
self.assertTrue(False)
except TypeError as e:
print(e)
pass
def test_get_argserror(self):
try:
dp_mat, nc_mat = rand_dp_nc_matrix(100, 100, -1000, 10000, seed=0)
dp_mat.get(0)
self.assertTrue(False)
except TypeError as e:
print(e)
pass
def test_get_rowcolerror(self):
try:
dp_mat, nc_mat = rand_dp_nc_matrix(100, 100, -1000, 10000, seed=0)
dp_mat.get(9.5, 100.3)
self.assertTrue(False)
except TypeError as e:
print(e)
pass
def test_get_rowcolerror1(self):
try:
dp_mat, nc_mat = rand_dp_nc_matrix(100, 100, -1000, 10000, seed=0)
dp_mat.get(200, 200)
self.assertTrue(False)
except IndexError as e:
print(e)
pass
class TestSet(TestCase):
def test_set(self):
dp_mat, nc_mat = rand_dp_nc_matrix(2, 2, seed=0)
rand_row = np.random.randint(dp_mat.shape[0])
rand_col = np.random.randint(dp_mat.shape[1])
dp_mat.set(rand_row, rand_col, 2)
nc_mat.set(rand_row, rand_col, 2)
self.assertTrue(cmp_dp_nc_matrix(dp_mat, nc_mat))
def test_set_random(self):
dp_mat, nc_mat = rand_dp_nc_matrix(1000, 1000, 1000, 20000, seed=0)
rand_row = np.random.randint(dp_mat.shape[0])
rand_col = np.random.randint(dp_mat.shape[1])
dp_mat.set(rand_row, rand_col, 5)
nc_mat.set(rand_row, rand_col, 5)
self.assertTrue(cmp_dp_nc_matrix(dp_mat, nc_mat))
def test_set_val(self):
dp_mat, nc_mat = rand_dp_nc_matrix(100, 100, -1000, 10000, seed=0)
rand_row = np.random.randint(dp_mat.shape[0])
rand_col = np.random.randint(dp_mat.shape[1])
dp_mat.set(rand_row, rand_col, 100.678)
nc_mat.set(rand_row, rand_col, 100.678)
self.assertTrue(cmp_dp_nc_matrix(dp_mat, nc_mat))
def test_set_val1(self):
dp_mat, nc_mat = rand_dp_nc_matrix(100, 100, -1000, 10000, seed=0)
rand_row = np.random.randint(dp_mat.shape[0])
rand_col = np.random.randint(dp_mat.shape[1])
dp_mat.set(rand_row, rand_col, 100)
nc_mat.set(rand_row, rand_col, 100)
self.assertTrue(cmp_dp_nc_matrix(dp_mat, nc_mat))
def test_set_materror(self):
try:
dp_mat = [[1, 2, 3], [3, 4, 5]]
dp_mat[0, 0, 5]
self.assertTrue(False)
except TypeError as e:
print(e)
pass
def test_set_args(self):
try:
dp_mat, nc_mat = rand_dp_nc_matrix(100, 100, -1000, 10000, seed=0)
dp_mat.set(0, 5)
self.assertTrue(False)
except TypeError as e:
print(e)
pass
def test_set_rowcolerror(self):
try:
dp_mat, nc_mat = rand_dp_nc_matrix(100, 100, -1000, 10000, seed=0)
dp_mat.set(3.4, 34.4, 10)
self.assertTrue(False)
except TypeError as e:
print(e)
pass
def test_set_rowcolerror1(self):
try:
dp_mat, nc_mat = rand_dp_nc_matrix(100, 100, -1000, 10000, seed=0)
dp_mat.set(-5, 0, 4)
self.assertTrue(False)
except IndexError as e:
print(e)
pass
class TestShape(TestCase):
def test_shape(self):
dp_mat, nc_mat = rand_dp_nc_matrix(2, 2, seed=0)
self.assertTrue(dp_mat.shape == nc_mat.shape)
def test_shape_mul(self):
dp_mat1, nc_mat1 = rand_dp_nc_matrix(1000, 1000, seed=0)
dp_mat2, nc_mat2 = rand_dp_nc_matrix(1000, 1000, seed=0)
is_correct, speed_up = compute([dp_mat1, dp_mat2], [nc_mat1, nc_mat2], "mul")
self.assertTrue(dp_mat1.shape == nc_mat1.shape)
self.assertTrue(dp_mat2.shape == nc_mat2.shape)
print_speedup(speed_up)
def test_shape_pow(self):
dp_mat, nc_mat = rand_dp_nc_matrix(50, 50, -50, -10, seed=0)
is_correct, speed_up = compute([dp_mat, 7], [nc_mat, 7], "pow")
self.assertTrue(dp_mat.shape == nc_mat.shape)
print_speedup(speed_up)
class TestIndexGet(TestCase):
def test_index_get(self):
dp_mat, nc_mat = rand_dp_nc_matrix(2, 2, seed=0)
rand_row = np.random.randint(dp_mat.shape[0])
rand_col = np.random.randint(dp_mat.shape[1])
self.assertEqual(round(dp_mat[rand_row][rand_col], decimal_places),
round(nc_mat[rand_row][rand_col], decimal_places))
def test_index_get_materror(self):
try:
dp_mat = [[1, 2, 3], [4, 5, 6]]
dp_mat[0, 0]
self.assertTrue(False)
except TypeError as e:
print(e)
pass
def test_index_get_rowcolerror(self):
try:
dp_mat, nc_mat = rand_dp_nc_matrix(100, 100, -1000, 10000, seed=0)
dp_mat[9.5, 100.3]
self.assertTrue(False)
except TypeError as e:
print(e)
pass
class TestIndexSet(TestCase):
def test_index_set(self):
dp_mat, nc_mat = rand_dp_nc_matrix(2, 2, seed=0)
rand_row = np.random.randint(dp_mat.shape[0])
rand_col = np.random.randint(dp_mat.shape[1])
dp_mat[rand_row][rand_col] = 2
nc_mat[rand_row][rand_col] = 2
self.assertTrue(cmp_dp_nc_matrix(dp_mat, nc_mat))
self.assertEquals(nc_mat[rand_row][rand_col], 2)
class TestSlice(TestCase):
def test_slice(self):
dp_mat, nc_mat = rand_dp_nc_matrix(2, 2, seed=0)
self.assertTrue(cmp_dp_nc_matrix(dp_mat[0], nc_mat[0]))
self.assertTrue(cmp_dp_nc_matrix(dp_mat[1], nc_mat[1]))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment