Linear Algebra within Grapheme (and Nexus)

Is it possible to perform linear-algebra operations and tasks within Grapheme?

Contents

Foreground

Is it possible to perform linear-algebra operations and tasks within Grapheme?

I recall one of our last year training sections (it was June of 2017) when a clever and curious user asked me if it was possible to perform linear algebra operations within Grapheme. He believed this would have been an advantage and would open-up the software capabilities somehow allowing expert users to see Grapheme as a more appealing and easy-to-use alternative to R, MiniTab or Matlab when performing data-analysis tasks.

Unfortunately, at that time my answer was:

No – current version of API does not support Matrices and linear algebra operations such as LU Decomposition, solution of linear system and extraction of eigenvalues. But I’ll let our developers know your feedback!

Today, after less than 6 months, I’ve downloaded the internal pre-release of Grapheme 2.3 (to be officially released in February 2018) and – surprisingly – I’ve seen our scripting API enriched by a Matrix class and many linear algebra functions.
Today my answer will be:

YES!! Grapheme scripting API implements effective Matrix class and relevant linear algebra operations, including resolution of linear systems and eigenvalue decomposition. Easy and intuitive API calls allow converting Matrix into tables and vice-versa!!

I’m so keen about this news that I decided to write a dissemination post on our blog to illustrate the main capabilities of the new APIs.

>> Back to Top

Basic Concepts

As you know Grapheme APIs have both a Python and JavaScript based front-ends. In this post I’ll stick to the JavaScript one, simply because I personally feel more proficient in writing JavaScript code than Python but rest assured same functionalities and same API calls exists in the Python front end.
Grapheme (and Nexus) GUI APIs are a set of functions and classes that allow operating on Project Tables and Charts via user defined Macros. Macros can be written either in Python or in JavaScript. This latter will be used in this post.

In order to write, debug and run existing Macros, you will have to import the Macro Module in your new project or (if a new project) to create a macro-enabled project.
Compact_06

Compact_06
The Macro module and its default perspective have been designed to allow you easy access to the Scripting environment. More in particular, you have access to a main editing area where Macros can be edited. Below you can find the execution logging and the system console.
Compact_06
Among my favorite features of the macro editors there are the error highlight and the autocomplete.

Let’s now focus on the Matrix and Linear Algebra capabilities of the Macro then.

>> Back to Top

Matrix and Solver classes

interface Matrix {
  Matrix clone()
  int getNRows()
  int getNColumns()
  void resize(int r, int c)
  double getValue(int r, int c)
  void setValue(int r, int c, double v)
  Matrix getSubMatrix(
     int r0, int c0, int r1, int c1)
  Matrix getRow(int index)
  void setRow(int index, Matrix row)
  void setColumn(int index, Matrix col)
  Matrix getColumn(int index)
  void addRows(int index, Matrix rows)
  void addColumns(int index, Matrix cols)
  Matrix getDiagonal()
  Matrix getTransposed()
  
  Matrix transpose()
  Matrix sum(double v)
  Matrix sum(Matrix B)
  Matrix multiply(double v)
  Matrix multiply(Matrix B)

  Table toTable(String name)
  Table toTable(
     String name, boolean indexed) 
  Table toTable(
     String name, String[] names)
  String toString(boolean compact)
}

The Matrix class exposes methods to get and set matrix elements as well as to resize and manipulate matrix dimensions. Class members allow also performing basic operations such as sum and multiplications, either with scalars or with other matrix. Class members and main capabilities are reported on the right.

It is stressed that potentially expensive operations (from a computation standpoint) such as sum and multiplication of matrices are done in-place. This means that if A and B are two matrices:

A.multiply(B) will alter the first matrix as if A = A * B

If the caller wishes to preserve the original contents of A, a clone of A should be used:

C = A.clone().multiply(B)

Note that matrices can be created directly from the main API entry point via calls to method API.createMatrix(…).

Besides the main Matrix class described above, the main API entry point allows creating so called Solver(s). Solvers are utilities designed to perform specific linear algebra operations such as Decomposition, solution of linear systems and eigenvalue extraction.

>> Back to Top

Inverting a Matrix

This very first example is a very simple one. All I want to do is to create a pseudo-sparse matrix, fill it with random values and compute its inverse. Then I would like to spy the non-null elements of the original matrix and its inverse. The goal are on one side to show you the capabilities of Grapheme not only in performing linear algebra tasks but also in displaying data; on the other end I want to prove myself storing inverse of sparse matrix as sparse matrix is not always a good idea.

First of all, let’s have a look at the Macro I wrote.

The first section of the code, up to line 11, randomly fill matrix A, of 250 rows and 205 columns. Filling factor is 1%, which means the matrix will have around 625 non-zero values on the 62500 total ones. Note that to avoid singular matrix I force all the diagonal elements not to be zero. Hence total non-null element would be around 875.

var size = 250;
var filling = 0.01;

var A = API.createMatrix(size, size, 0.0);
for(var r=0; r<size; r++) {
	for(var c=0; c<size; c++) {
		var v = Math.random();
		if( v<filling || r==c)
			A.setValue(r, c, v);
	}		
}

Matrix A spy
Once matrix A is defined and filled with values, we convert it onto a Table (using an indexed representation) and we plot it as a bubble scatter plot where not-null element will be displayed with a marker with an area proportional to their absolute value.

Now, we create a Solver S and we use the solver to compute the inverse of A, which we save as matrix B.

var S = API.createSolver();
var B = S.inverse(A);

Matrix B spy
Finally, as done for A, we spy the non-null element of B and display them in a chart. In this case A has 6000 non-null values, which corresponds to a filling factor of about 9% (if we consider zero values with absolute values below 1.0e-6).

This relatively simple examples shows that inverse matrix do not easily preserve sparse structure and therefore a robust Matrix library for numerical applications should always be able to switch from a sparse matrix representation to a full one, depending on the results of specific operations.
The full macro is reported below:

var size = 250;
var filling = 0.01;

var A = API.createMatrix(size, size, 0.0);
for(var r=0; r<size; r++) {
	for(var c=0; c<size; c++) {
		var v = Math.random();
		if( v<filling || r==c)
			A.setValue(r, c, v);
	}		
}

var tA = A.toTable("tabA", 1.0e-6);

var s = API.createSolver();
var B = s.inverse(A);

var tB = B.toTable("tabB", 1.0e-6);

>> Back to Top

Conclusive Remarks

This post very briefly illustrates the new API scripting capabilities of Grapheme (from version 2.3 onward) and Nexus (from version 3.2) with respect of linear-algebra and Matrix.
A copy of the project used in this post can be downloaded from here.

>> Back to Top