CVS MatrixSum and MatrixDifference

CVS is coordinate-wise in many ways. However, some operations are challenging. Performing CVS-to-CVS addition/subtraction is possible but with a catch (at least as of this writing). CVS's own rule about keeping distinct non-zero values and aligning their lists of linear indexes creates a challenge when performing operations that can create zeroes or repeating values.

CVSExtensions' MatrixSum() and MatrixDifference() call MatrixAddOrSubtract() to perform addition or subtraction. In its current form, MatrixAddOrSubtract() performs two loops. The first loop is on the CVS instance to initially fill a LinearIndexKeyedConcurrentSparseMatrix instance. The second loop perform the actual addition/subtraction on the input CVS instance. The results are saved in the result sparse matrix, which is then compressed to CVS and returned.

The algorithm works. It's even inherently concurrent, which is fantastic! But even though LinearIndexKeyedConcurrentSparseMatrix theoretically uses 30-50% of memory that CoordinatesKeyedConcurrentSparseMatrix may use, the operation to add or subtract still requires a huge memory jump.

I have yet to investigate the possibility of performing the operation as a CVS-pure multi-step process. CVS-pure meaning there is no "cheating" or, specifically, no internal usage of DOK sparse matrix. Multi-step meaning that the process may include data clean-up, integrity checks and what not before returning the result CVS. I can imagine this operation to be non-concurrent though. I can also imagine it to be slow. But it can solve the memory space problem. Compromises!

So now for the code. In CVSExtensions.cs, add the following:

        public static CVS<S> MatrixSum<T, A, S>(
            this CVS<T> cvs1, CVS<A> cvs2)
        {
            return MatrixAddOrSubtract<T, A, S>(cvs1, cvs2, MDAS.Add);
        }

        public static CVS<D> MatrixDifference<T, A, D>(
            this CVS<T> cvs1, CVS<A> cvs2)
        {
            return MatrixAddOrSubtract<T, A, D>(cvs1, cvs2, MDAS.Subtract);
        }

        private static CVS<R> MatrixAddOrSubtract<T, A, R>(
            CVS<T> cvs1, CVS<A> cvs2, MDAS mdas)
        {
            if (!cvs1.Size.Equals(cvs2.Size))
            {
                throw new InvalidOperationException("Matrices must have the same size.");
            }
            var r = new LinearIndexKeyedConcurrentSparseMatrix<R>(
                cvs1.Size, cvs1.LinearIndexMode);
            Parallel.For(0, cvs1.Value.Count, (i) =>
            {
                dynamic value = cvs1.Value[i];
                Parallel.ForEach(cvs1.LinearIndex[i], (li) =>
                {
                    r.SetEntry(li, value);
                });
            });
            Parallel.For(0, cvs2.Value.Count, (i) =>
            {
                dynamic value = cvs2.Value[i];
                Parallel.ForEach(cvs2.LinearIndex[i], (li) =>
                {
                    if (r.Matrix.ContainsKey(li))
                    {
                        if (mdas == MDAS.Subtract)
                        {
                            value = r.GetEntry(li) - value;
                        }
                        else
                        {
                            value = r.GetEntry(li) + value;
                        }
                    }
                    if (value == default(R))
                    {
                        r.Matrix.TryRemove(li, out R v);
                    }
                    else
                    {
                        r.SetEntry(li, value);
                    }
                });
            });
            return r.ToCVS(r.LinearIndexMode);
        }

Have fun!

Comments