## Exploiting the MATLAB Language in Implementing a Matrix Collection

“Concise code” may not be commonly used or defined, but as in languages, conciseness—the quality of communicating information in as few words as is feasible—can be a good guide in programming. Conciseness of code, which could mean short and self-explanatory lines or blocks of code, is easier to achieve in higher level languages such as Python or MATLAB, and more difficult when writing, for example, in RISC assembly languages with short instructions each doing very little.

For developing the recently released matrix collection, Anymatrix [1,2] (with Nick Higham), we used MATLAB and exploited its string handling routines to do complicated string manipulations in a few lines of code. This post describes some details of the implementation and further information can be found in the user’s guide [3].

### Groups of Matrices

Anymatrix enforces the organization of matrices into groups, which means a new matrix has to be placed either into one of the existing groups or a new group. For example, the built-in groups are `contest`, `core`, `gallery`, `hadamard`, `matlab`, `nessie`, and `regtools`. Each of these come with around 20 matrices, which can be fixed-sized matrices or matrix generators.

Matrices are identified by `<group_name>/<matrix_name>`. For example, `core/beta` is a matrix called `beta` in the `core` group. Matrix names are their file names minus the extension `.m`. Group names are directory names of the groups. Since the IDs of matrices are derived from the directory and file names, uniqueness is guaranteed. This also means that multiple matrices can have the same name as long as they are in different groups.

Around the time we started working on this collection, **MATLAB R2020b** introduced pattern, to search and match strings conveniently. An example usage in Anymatrix is as follows.

```
% Matrix ID pattern
matrix_ID_pat = ...
asManyOfPattern(alphanumericsPattern | ...
characterListPattern("_") | ...
characterListPattern("-")) + ...
'/' + ...
asManyOfPattern(alphanumericsPattern | ...
characterListPattern("_") | ...
characterListPattern("-"));
```

This code creates a pattern for matrix IDs, which matches sequences of letter, digit, underscore or hyphen characters, followed by a single forward slash, followed again by the sequence. This sort of pattern can then be used, for example, to match strings (using matches) and extract substrings (using extract):

```
>> S = '...core/beta...';
>> extract(S, matrix_ID_pat)
ans =
1×1 cell array
{'core/beta'}
```

The pattern functionality helped in writing concise code. We used it in various places of Anymatrix, for example in detecting matrix IDs in the commands and extracting substrings in the Boolean search expressions to separate properties from brackets and operators and/or/not.

### Properties of Matrices

Matrix properties can be specified in two ways, either as an assignment `properties = {...}` inside the matrix `.m` files, or in the `am_properties.m` files in group folders. With the latter option we may specify properties of multiple matrices in one assignment. Anymatrix contains a list of recognized properties, such as `ill conditioned` or `banded`. When Anymatrix scans the file structure, it gives a warning if a matrix contains an unrecognized property.

Properties can be searched by Boolean expressions with operators and/or/not and brackets for specifying precedence. For example:

```
>> anymatrix('p', 'integer and ill conditioned')
ans =
3×1 cell array
{'core/wilson' }
{'gallery/dramadah'}
{'matlab/pascal' }
```

The following function that transforms the supplied Boolean expression into an expression that can be evaluated in MATLAB, demonstrates a lot of MATLAB’s features that we made use of.

```
function IDs = search_by_properties(expression)
IDs = {};
% Replace 'and', 'or', and 'not' by corresponding MATLAB symbols.
expression = replace(expression, ' and ', ' & ');
expression = replace(expression, ' or ', ' | ');
expression = replace(expression, ' not ', ' ~');
expression = replace(expression, '(not ', '(~');
if startsWith(expression, 'not')
expression = expression(4:length(expression));
expression(1) = '~';
end
% Assume properties are made up letters, can include a hyphen
% or a white space character, and there is no case sensitivity.
pat = (lettersPattern + whitespacePattern + lettersPattern) ...
| (lettersPattern + characterListPattern('-') ...
+ lettersPattern) ...
| lettersPattern;
% Extract properties from the logical expression and replace
% them by function calls to test for membership.
ex_props = extract(expression, pat);
ind = 1;
new_expression = '';
for p = ex_props.'
mod_prop = strcat('ismember(''', ...
strrep(lower(p{1}), '-', ' '), ...
''', strrep(lower(matrix_properties{1}), ''-'', '' ''))');
trunc_exp = expression(ind:end);
% Find where the property is in the expression.
prop_index = strfind(trunc_exp, p{1});
% Take anything before the property and append the modified
% version of the property.
new_expression = strcat(new_expression, ...
trunc_exp(1:prop_index(1)-1), ...
mod_prop);
% Move the index after the property that was replaced.
ind = ind + prop_index(1) + length(p{1}) - 1;
end
new_expression = strcat(new_expression, expression(ind:end));
% Find matrices whose properties satisfy the specified logical
% expression.
k = 0;
for matrix_properties = properties.'
k = k + 1;
% Test if the expression is true for this matrix
% and add it's ID.
if eval(new_expression)
IDs = [IDs; matrix_IDs{k}];
end
end
end
```

On lines `4` to `11` we replace the operators and/or/not by the corresponding MATLAB ones `&`/`|`/`~`. We then extract properties from the expression, making use of patterns, modify them to test for membership of the properties and place them back into the expression (lines `14` to `38`). Finally, on lines `42` to `50` we use eval to run the Boolean expression as MATLAB code for every matrix, and return a list of suitable matrices.

In the example command `anymatrix('p', 'integer and ill conditioned')`, the expression `'integer and ill conditioned'` is transformed internally to `'ismember('integer', strrep(lower(matrix_properties{1}), '-', ' ')) & ismember('ill conditioned', strrep(lower(matrix_properties{1}), '-', ' '))'` which can then be evaluated using eval. Notice that Anymatrix replaces hyphens by white spaces to allow two ways to specify properties, and converts properties to lower case to avoid case sensitivity. This way, `ill conditioned` and `Ill-conditioned`, for example, are equivalent properties in Anymatrix.

### Testing in Anymatrix

There are two ways to implement testing in Anymatrix. One way is to add a function in file `test_run.m` in the group folder which can be invoked by an appropriate Anymatrix command. Another way is to test matrices for their properties. This is provided in the directory `testing/` in the root folder of Anymatrix.

It is worth noting that not all supported properties can be tested, so only a subset in the built-in collection are. Each property that is feasible to test has a corresponding `test_<property>.m` file. Functions in these files must return true or false, given a matrix. Anymatrix utilizes MATLAB’s unit testing framework and automatically generates function-based unit tests for every matrix in the collection. When a new matrix or a new property to an existent matrix is added, the unit testing framework picks that up automatically when run. The following is an example function-based unit test that is automatically generated by Anymatrix. It tests the properties of a set of the `core/fourier` matrices with arbitrary dimensions.

```
function test_core_fourier(testcase)
A = anymatrix('core/fourier',3);
anymatrix_check_props(A, 'core/fourier', testcase);
A = anymatrix('core/fourier',5);
anymatrix_check_props(A, 'core/fourier', testcase);
A = anymatrix('core/fourier',8);
anymatrix_check_props(A, 'core/fourier', testcase);
A = anymatrix('core/fourier',10);
anymatrix_check_props(A, 'core/fourier', testcase);
A = anymatrix('core/fourier',15);
anymatrix_check_props(A, 'core/fourier', testcase);
A = anymatrix('core/fourier',24);
anymatrix_check_props(A, 'core/fourier', testcase);
A = anymatrix('core/fourier',25);
anymatrix_check_props(A, 'core/fourier', testcase);
A = anymatrix('core/fourier',30);
anymatrix_check_props(A, 'core/fourier', testcase);
A = anymatrix('core/fourier',31);
anymatrix_check_props(A, 'core/fourier', testcase);
end
```

Above, the function `anymatrix_check_props` looks for files `test_<property>.m` and runs those tests that it finds on the given matrix:

```
for prop = P.'
test_func_name = strcat('test_', ...
strrep(strrep(lower(prop{1}), ...
'-', '_'), ' ', '_'));
if isfile(strcat(root_path, '/private/', test_func_name, '.m'))
handle = str2func(test_func_name);
verifyTrue(testcase, handle(M), ...
strcat("Matrix ", matrix_ID, " is not ", prop{1}, "."));
end
end
```

Using this, Anymatrix adds a level of almost automatic testing invoked by adding new matrices or properties to matrices. A good example of this is the MATLAB built-in `gallery` group, which we made available in Anymatrix. By appending each matrix with properties we can check that MATLAB’s gallery matrices have the expected properties.

### Summary

Anymatrix is both a collection of matrices and a tool to organize them. It is worth noting that much of the infrastructure is not specific to organizing matrices and so it can be reused to organize into groups, search, and access through IDs any kind of objects appended with properties.

Anymatrix 1.0 was released in October 2021, but the development continues. We welcome contributions to the collection either in the form of remote groups that can be downloaded into Anymatrix given a git url (an example is matrices-mp-cosm group by X. Liu), or suggestions to include matrices or groups in the built-in collection. We also welcome reports of bugs or recommendations for improvements to the clarity, correctness and conciseness of the source code.

### References

[1] N. J. Higham and M. Mikaitis. Anymatrix: An Extensible MATLAB Matrix Collection. MIMS EPrint 2021.16, Manchester Institute for Mathematical Sciences, The University of Manchester, UK. Oct. 2021 (to appear in Numer. Algorithms).

[2] N. J. Higham. Anymatrix: An Extensible MATLAB Matrix Collection. Nov. 2021.

[3] N. J. Higham and M. Mikaitis. Anymatrix: An Extensible MATLAB Matrix Collection. Users’ Guide. MIMS EPrint 2021.15, Manchester Institute for Mathematical Sciences, The University of Manchester, UK. Oct. 2021.