The matrix extension adds a new matrix data structure to NetLogo. A matrix is a mutable 2-dimensional array containing only numbers.

Although matrices store numbers, much like a list of lists, or an array of arrays, the primary reason to use the matrix data type is to take advantage of special mathematical operations associated with matrices. For instance, matrix multiplication is a convenient way to perform geometric transformations, and the repeated application of matrix multiplication can also be used to simulate other dynamic processes (for instance, processes on graph/network structures).

If you'd like to know more about matrices and how they can be used, you might consider a course on linear algebra, or search the web for tutorials. The matrix extension also allows you to solve linear algebraic equations (specified in a matrix format), and even to identify trends in your data and perform linear (ordinary least squares) regressions on data sets with multiple explanatory variables.

The matrix extension comes preinstalled.

To use the matrix extension in your model, add a line to the top of your Code tab:

extensions [matrix]

If your model already uses other extensions, then it already has an
`extensions` line in
it, so just add `matrix` to the list.

For more information on using NetLogo extensions, see the Extensions Guide.

let m matrix:from-row-list [[1 2 3] [4 5 6]] print m => {{matrix: [ [ 1 2 3 ][ 4 5 6 ] ]}} print matrix:pretty-print-text m => [[ 1 2 3 ] [ 4 5 6 ]] print matrix:dimensions m => [2 3] ;;(NOTE: row & column indexing starts at 0, not 1) print matrix:get m 1 2 ;; what number is in row 1, column 2? => 6 matrix:set m 1 2 10 ;; change the 6 to a 10 print m => {{matrix: [ [ 1 2 3 ][ 4 5 10 ] ]}} let m2 matrix:make-identity 3 print m2 => {{matrix: [ [ 1 0 0 ][ 0 1 0 ][ 0 0 1 ] ]}} print matrix:times m m2 ;; multiplying by the identity changes nothing => {{matrix: [ [ 1 2 3 ][ 4 5 10 ] ]}} ;; make a new matrix with the middle 1 changed to -1 let m3 (matrix:set-and-report m2 1 1 -1) print m3 => {{matrix: [ [ 1 0 0 ][ 0 -1 0 ][ 0 0 1 ] ]}} print matrix:times m m3 => {{matrix: [ [ 1 -2 3 ][ 4 -5 10 ] ]}} print matrix:to-row-list (matrix:plus m2 m3) => [[2 0 0] [0 0 0] [0 0 2]]

Code Example:Matrix Example

The Matrix extension for NetLogo was originally written by Forrest Stonedahl, with significant contributions from Charles Staelin (in particular, the forecast & regression primitives). The matrix extension provides a wrapper around Jama, which is a free/open-source matrix library for Java.

The source code for the Matrix extension is hosted on GitHub You are more than welcome to modify/improve upon the existing functionality and features and we encourage you to contribute your changes back to the NetLogo community.

matrix:make-constant matrix:make-identity matrix:from-row-list matrix:from-column-list matrix:to-row-list matrix:to-column-list matrix:copy matrix:pretty-print-text

matrix:get matrix:get-row matrix:get-column matrix:set matrix:set-row matrix:set-column matrix:swap-rows matrix:swap-columns matrix:set-and-report matrix:dimensions matrix:submatrix

matrix:times-scalar matrix:times matrix:times-element-wise matrix:plus-scalar matrix:plus matrix:inverse matrix:transpose matrix:real-eigenvalues matrix:imaginary-eigenvalues matrix:eigenvectors matrix:det matrix:rank matrix:cond matrix:trace

matrix:solve matrix:forecast-linear-growth matrix:forecast-compound-growth matrix:forecast-continuous-growth matrix:regress

Reports a new n-rows by n-cols matrix object, with all entries in the matrix containing the same value (number).

Reports a new square matrix object (with dimensions n-size x n-size), consisting of the identity matrix (1s along the main diagonal, 0s elsewhere).

Reports a new matrix object, created from a NetLogo list, where each item in that list is another list (corresponding to each of the rows of the matrix.)

print matrix:from-row-list [[1 2] [3 4]] => {{matrix: [ [ 1 2 ][ 3 4 ] ]}} ;; Corresponds to this matrix: ;; 1 2 ;; 3 4

Reports a new matrix object, created from a NetLogo list containing
each of the *columns* of the matrix.

Reports a list of lists, containing each row of the matrix.

Reports a list of lists, containing each column of the matrix.

Reports a new matrix that is an exact copy of the given matrix. This primitive is important because the matrix type is mutable (changeable). Here's a code example:

let m1 matrix:from-column-list [[1 4 7][2 5 8][3 6 9]] ; a 3x3 matrix print m1 => {{matrix: [ [ 1 2 3 ][ 4 5 6 ][ 7 8 9 ] ]}} let m2 m1 ;; m2 refers to the same matrix object as m1 let m3 matrix:copy m1 ;; m3 is a new copy containing m1's data matrix:set m1 0 0 100 ;; now m1 is changed print m1 => {{matrix: [ [ 100 2 3 ][ 4 5 6 ][ 7 8 9 ] ]}} print m2 => {{matrix: [ [ 100 2 3 ][ 4 5 6 ][ 7 8 9 ] ]}} ;;Notice that m2 was also changed, when m1 was changed! print m3 => {{matrix: [ [ 1 2 3 ][ 4 5 6 ][ 7 8 9 ] ]}}

Reports a string that is a textual representation of the matrix, in a format that is reasonably human-readable when displayed.

Reports the (numeric) value at location *row-i*,*col-j*
in the given *matrix*.

Reports a simple (not nested) NetLogo list containing the elements
of *row-i* of the given *matrix*.

Reports a simple (not nested) NetLogo list containing the elements
of *col-j* of the given *matrix*.

Changes the given *matrix*, by setting the location
*row-i*,*col-j* to *new-value*

Changes the given matrix *matrix* by replacing the row at
*row-i* with the contents of the simple (not nested) NetLogo
list *simple-list*. The *simple-list* must have a length
equal to the number of columns in the matrix, i.e., the matrix row
length.

Changes the given matrix *matrix* by replacing the column at
*col-j* with the contents of the simple (not nested) NetLogo
list *simple-list*. The *simple-list* must have a length
equal to the number of rows in the matrix, i.e., the matrix column
length.

Changes the given matrix *matrix* by swapping the rows at
*row1* and *row2* with each other.

Changes the given matrix *matrix* by swapping the columns at
*col1* and *col2* with each other.

Reports a new matrix, which is a copy of the given matrix except
that the value at *row-i*,*col-j* has been changed to
*new-value*. A NetLogo statement such as "set mat
matrix:set-and-report mat 2 3 10" will result in mat pointing
to this new matrix, a copy of the old version of mat with the
element at row 2, column 3 being set to 10. The old version of mat
will be "lost".

Reports a 2-element list ([num-rows,num-cols]), containing the
number of rows and number of columns in the given *matrix*

Reports a new matrix object, consisting of a rectangular subsection
of the given matrix. The rectangular region is from row *r1*
up to (but not including) row *r2*, and from column *c1*
up to (but not including) column *c2*. Here is an example:

let m matrix:from-row-list [[1 2 3][4 5 6][7 8 9]] print matrix:submatrix m 0 1 2 3 ; matrix, row-start, col-start, row-end, col-end ; rows from 0 (inclusive) to 2 (exclusive), ; columns from 1 (inclusive) to 3 (exclusive) => {{matrix: [ [ 2 3 ][ 5 6 ] ]}}

Reports a new matrix, which is the result of multiplying every
entry in the original *matrix* by the given scaling
*factor*.

Reports a matrix, which is the result of multiplying matrix
*m1* by matrix *m2* (using standard matrix multiplication
-- make sure your matrix dimensions match up.)

Reports a matrix, which is the result of multiplying each element
of matrix *m1* by the corresponding element in *m2*.
(Note: *m1* and *m2* must have the same dimensions).

Reports a matrix, which is the result of adding the constant
*number* to each element of the given *matrix*.

Reports a matrix, which is the result of adding each element of
matrix *m1* to the corresponding element in *m2*. (Note:
*m1* and *m2* must have the same dimensions).

Reports the inverse of the given *matrix*, or results in an
error if the matrix is not invertible.

Reports a list containing the real eigenvalues of the given
*matrix*.

Reports a list containing the imaginary eigenvalues of the given
*matrix*.

Reports a matrix that contains the eigenvectors of the given
*matrix*. (Each eigenvector as a column of the resulting
matrix.)

Reports the effective numerical rank of the *matrix*,obtained
from SVD (Singular Value Decomposition).

Reports the matrix condition (2 norm), which is the ratio of largest to smallest singular value (obtained from SVD).

Reports the trace of the *matrix*, which is simply the sum of
the main diagonal elements.

Reports the solution to a linear system of equations, specified by
the *A* and *C* matrices. In general, solving a set of
linear equations is akin to matrix division. That is, the goal is
to find a matrix B such that *A* * B = *C*. (For simple
linear systems, *C* and B can both be 1-dimensional matrices
-- i.e. vectors). If A is not a square matrix, then a "least
squares" solution is returned.

;; To solve the set of equations x + 3y = 10 and 7x - 4y = 20 ;; We make our A matrix [[1 3][7 -4]], and our C matrix [[10][20]] let A matrix:from-row-list [[1 3][7 -4]] let C matrix:from-row-list [[10][20]] print matrix:solve A C => {{matrix: [ [ 4 ][ 2.0000000000000004 ] ]}} ;; NOTE: as you can see, the results may be only approximate ;; (In this case, the true solution should be x=4 and y=2.)

Reports a four-element list of the form: [ *forecast*
*constant* *slope* *R ^{2}*
]. The

Y = *constant* + *slope* * t.

The *R ^{2}* value measures the goodness of fit of the
trend-line to the data, with an R

;; a linear extrapolation of the next item in the list. print matrix:forecast-linear-growth [20 25 28 32 35 39] => [42.733333333333334 20.619047619047638 3.6857142857142824 0.9953743395474031] ;; These results tell us: ;; * the next predicted value is roughly 42.7333 ;; * the linear trend line is given by Y = 20.6190 + 3.6857 * t ;; * Y grows by approximately 3.6857 units each period ;; * the R^2 value is roughly 0.9954 (a good fit)

Reports a four-element list of the form: [ *forecast*
*constant* *growth-proportion*
*R ^{2}* ]. Whereas matrix:forecast-linear-growth
assumes growth by a constant absolute amount each period, matrix:forecast-compound-growth
assumes that Y grows by a constant

Y = *constant* * *growth-proportion*^{t}.

Note that the growth proportion is typically interpreted as
*growth-proportion* = *(1.0 + growth-rate)*. Therefore,
if matrix:forecast-compound-growth
returns a *growth-proportion* of 1.10, that implies that Y
grows by (1.10 - 1.0) = 10% each period. Note that if growth is
negative, matrix:forecast-compound-growth
will return a *growth-proportion* of less than one. E.g., a
*growth-proportion* of 0.90 implies a growth rate of -10%.

**NOTE:** The compound growth forecast is achieved by taking the
ln of Y. (See matrix:regress, below.)
Because it is impossible to take the natural log of zero or a
negative number, matrix:forecast-compound-growth
will result in an error if it finds a zero or negative number in
*data-list*.

;; a compound growth extrapolation of the next item in the list. print matrix:forecast-compound-growth [20 25 28 32 35 39] => [45.60964465307147 21.15254147944863 1.136621034423892 0.9760867518334806] ;; These results tell us: ;; * the next predicted value is approximately 45.610 ;; * the compound growth trend line is given by Y = 21.1525 * 1.1366 ^ t ;; * Y grows by approximately 13.66% each period ;; * the R^2 value is roughly 0.9761 (a good fit)

Reports a four-element list of the form: [ *forecast*
*constant* *growth-rate*
*R ^{2}* ]. Whereas matrix:forecast-compound-growth
assumes discrete time with Y growing by a given proportion each
finite period of time (e.g., a month or a year), matrix:forecast-continuous-growth
assumes that Y is compounded

Y = *constant* * e^{(growth-rate * t)}.

matrix:forecast-continuous-growth
is the "calculus" analog of matrix:forecast-compound-growth.
The two will normally yield similar (but not identical) results, as
shown in the example below. *growth-rate* may, of course, be
negative.

**NOTE:** The continuous growth forecast is achieved by taking
the ln of Y. (See matrix:regress,
below.) Because it is impossible to take the natural log of zero or
a negative number, matrix:forecast-continuous-growth
will result in an error if it finds a zero or negative number in
*data-list*.

;; a continuous growth extrapolation of the next item in the list. print matrix:forecast-continuous-growth [20 25 28 32 35 39] => [45.60964465307146 21.15254147944863 0.12805985615332668 0.9760867518334806] ;; These results tell us: ;; * the next predicted value is approximately 45.610 ;; * the compound growth trend line is given by Y = 21.1525 * e ^ (0.1281 * t) ;; * Y grows by approximately 12.81% each period if compounding takes place continuously ;; * the R^2 value is roughly 0.9761 (a good fit)

All three of the forecast primitives above are just special cases
of performing an OLS (ordinary-least-squares) linear regression --
the matrix:regress primitive provides a flexible/general-purpose
approach. The input is a matrix *data-matrix*, with the first
column being the observations on the dependent variable and each
subsequent column being the observations on the (1 or more)
independent variables. Thus each row consists of an observation of
the dependent variable followed by the corresponding observations
for each independent variable.

The output is a Logo nested list composed of two elements. The
first element is a list containing the regression constant followed
by the coefficients on each of the independent variables. The
second element is a 3-element list containing the R^{2}
statistic, the total sum of squares, and the residual sum of
squares. The following code example shows how the matrix:regress primitive can be used to
perform the same function as the code examples shown in the
matrix:forecast-*-growth primitives above. (However, keep in mind
that the matrix:regress primitive is
more powerful than this, and can have many more independent
variables in the regression, as indicated in the fourth example
below.)

;; this is equivalent to what the matrix:forecast-linear-growth does let data-list [20 25 28 32 35 39] let indep-var (n-values length data-list [?]) ; 0,1,2...,5 let lin-output matrix:regress matrix:from-column-list (list data-list indep-var) let lincnst item 0 (item 0 lin-output) let linslpe item 1 (item 0 lin-output) let linR2 item 0 (item 1 lin-output) ;;Note the "6" here is because we want to forecast the value at time t=6. print (list (lincnst + linslpe * 6) (lincnst) (linslpe) (linR2)) ;; this is equivalent to what the matrix:forecast-compound-growth does let com-log-data-list (map [ln ?] [20 25 28 32 35 39]) let com-indep-var2 (n-values length com-log-data-list [?]) ; 0,1,2...,5 let com-output matrix:regress matrix:from-column-list (list com-log-data-list com-indep-var2) let comcnst exp item 0 (item 0 com-output) let comprop exp item 1 (item 0 com-output) let comR2 item 0 (item 1 com-output) ;;Note the "6" here is because we want to forecast the value at time t=6. print (list (comcnst * comprop ^ 6) (comcnst) (comprop) (comR2)) ;; this is equivalent to what the matrix:forecast-continuous-growth does let con-log-data-list (map [ln ?] [20 25 28 32 35 39]) let con-indep-var2 (n-values length con-log-data-list [?]) ; 0,1,2...,5 let con-output matrix:regress matrix:from-column-list (list con-log-data-list con-indep-var2) let concnst exp item 0 (item 0 con-output) let conrate item 1 (item 0 con-output) let conR2 item 0 (item 1 con-output) print (list (concnst * exp (conrate * 6)) (concnst) (conrate) (conR2)) ;; example of a regression with two independent variables: ;; Pretend we have a dataset, and we want to know how well happiness ;; is correlated to snack-food consumption and accomplishing goals. let happiness [2 4 5 8 10] let snack-food-consumed [3 4 3 7 8] let goals-accomplished [2 3 5 8 9] print matrix:regress matrix:from-column-list (list happiness snack-food-consumed goals-accomplished) => [[-0.14606741573033788 0.3033707865168543 0.8202247191011234] [0.9801718440185063 40.8 0.8089887640449439]] ;; linear regression: happiness = -0.146 + 0.303*snack-food-consumed + 0.820*goals-accomplished ;; (Since the 0.820 coefficient is higher than the 0.303 coefficient, it appears that each goal ;; accomplished yields more happiness than does each snack consumed, although both are positively ;; correlated with happiness.) ;; Also, we see that R^2 = 0.98, so the two factors together provide a good fit.