Matrices in techBASIC
Matrices in techBASIC
Using Matrices and Vectors in techBASIC
Matrices and vectors are at the heart of many calculations in physics, mathematics, chemistry, and even business. techBASIC has built in support for calculations with arrays that make vector and matrix calculations almost as easy as any other numeric calculation. Let’s take a look at arrays in techBASIC so you can use them in your own programs.
If you learned BASIC after the late 70’s, it might surprise you to learn that arrays have been a part of the BASIC language since its inception. BASIC was designed way back in 1964 by John Kemeny and Thomas Kurtz at Dartmouth. The whole idea was to bring programming to people who were not professional programmers, but it was also assumed they would be deeply enough involved in science and engineering that even that first implementation had matrix arithmetic. BASIC was the second STEM (science, technology, engineering and math) programming language! (Fortran gets my vote as the first.) When Bill Gates, Paul Allen and Monte Davidoff implemented BASIC for the Altair in 1975, they dropped matrix and vector operations from the language. That made sense—they were using BASIC for general purpose programming on machines with a very small memory footprint, and the memory concerns trumped the need for matrix operations. Most microcomputer implementations of BASIC followed their lead, so a couple of generations of programmers never knew BASIC was designed as one of the first great matrix arithmetic languages.
techBASIC, though, is designed specifically for data collection and manipulation. Like Fortran, MATLAB and many other languages designed for STEM users, techBASIC needs matrices. Many of the data sets returned by sensors come back as arrays, and many of the operations you might want to do to manipulate this information involve matrix calculations, so arrays are back in the language. With a couple of extensions, notably in the way you enter array constants, techBASIC implements arrays as specified in the ANSI BASIC standard, ANSI X3.113-1987. This is no longer an active standard, but still serves as the basis of most implementations of BASIC that support matrices. As you read about matrices here, standard BASIC refers back to this ANSI standard and the other implementations of BASIC based on it, and techBASIC refers to specific extensions added to extend matrix computations in techBASIC.
Array Constants
With that in mind, let’s start by looking at how to enter an array constant in techBASIC. To create, say, a 3x3 identity matrix and store it in the variable a, we can write it this way:
a = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
That’s a lot easier than putting the matrix in a file or a series of data statements, but it is a bit difficult to read. After all, we’re used to seeing matrices written as big rectangles of numbers. Well, techBASIC supports that, too. When creating an array constant, it is legal to put a line break before or after any comma or bracket except the first one. Let’s rewrite that program so it’s easier for us to read, and print the result to make sure it worked as expected.
a = [[1, 0, 0],
[0, 1, 0],
[0, 0, 1]]
print a
If you enter this program in techBASIC and run it, you’ll see the identity matrix printed to the console.
While we’ve used a two-dimensional array here, you can use the same idea to create an array of any dimension.
There are two other features that will help you write programs faster. If a row in an array ends with zeros, you can leave them out. techBASIC will determine the size of the array from the longest row and fill in any missing values with zeros. This program does exactly the same thing as the previous one.
a = [[1],
[0, 1],
[0, 0, 1]]
print a
The other unexpected shortcut is that you don’t have to use constants when creating arrays. Here’s a rotation matrix that rotates a point about the Z axis.
alpha = 3.1415926353/6
R = [[cos(alpha), sin(alpha), 0],
[-sin(alpha), cos(alpha), 0],
[0, 0, 1]]
p = [[1.2],
[3.5],
[-6]]
print R*p
Matrix Arithmetic
Did you notice that last statement, where we casually multiplied a matrix by a vector? That’s really all there is to it. Standard BASIC supports addition, subtraction and multiplication of matrices and vectors following the usual rules of linear algebra.
As you know from linear algebra, the size of the two matrices must be compatible for an operation to work. For addition and subtraction, the number of rows and columns in the matrix or vector must match. Actually, Standard BASIC also allows addition and subtraction of arrays that have more than two dimensions, so long as the number of elements is the same. For multiplication, the usual rules apply: the number of columns in the first matrix must match the number of rows in the second, and the result is a matrix with the same number of rows as the first matrix, and the same number of columns as the second. Of course, division is not supported.
You can also add, subtract or multiply an array by a scalar. This performs the operation on each element of the array individually. For example, to get a matrix with 3 along the diagonal, you can multiply the identity matrix by 3.
a = [[1],
[0, 1],
[0, 0, 1]]
print 3*a
Addition and subtraction work the same way. It is also legal to specify the unary negation of an array.
a = [[1],
[0, 1],
[0, 0, 1]]
print -a
prints a matrix with -1 along the diagonal.
Matrix Functions
Basic matrix algebra operations are useful, but what about all the specialized ones? There are a lot of common operations on matrices that are rather challenging to code correctly. As it turns out, the common ones are part of standard BASIC. The three most common matrix manipulations I recall from physics and linear algebra are inverting a matrix, transposing a matrix, and finding the determinant. BASIC has functions for all three.
The natural way to solve a system of linear equations shown at the start of pretty much any linear algebra course starts with the matrix equation
Ax = b
Here A is a square matrix with the coefficients from the equations, and b is a column vector with the right hand sides of the equations. A short example with three unknowns is
x1 + x2 + 2x3 = 9
2x1 + 4x2 – 3x3 = 1
3x1 + 6x2 – 5x3 = 0
The idea is to solve for the x values. The natural way to do this is to invert the matrix A and multiply both sides by the inverse, yielding
A-1Ax = A-1b
x = A-1b
Now, this isn’t necessarily the best way to do the operation on a digital computer. Taking the inverse of a matrix is a numerically sensitive operation that can lead to numerical problems for interesting matrices. Still, it’s a good way to introduce the inverse function, and it works just fine for well-behaved matrices. A simple BASIC program to solve this problem from my own linear algebra text looks like this:
A = [[1, 1, 2],
[2, 4, -3],
[3, 6, -5]]
b = [[9],
[1],
[0]]
print inv(A)*b
There, I just finished the first homework problem!
The INV function takes any square matrix and returns its inverse. If there is no inverse (or if the matrix is close enough to not having an inverse that the algorithm can’t find it), BASIC stops with the error “The matrix is singular.”
The transpose of a matrix swaps the rows and columns. It’s used in all kinds of operations. For example, the transpose of
A = [[1, 2, 3],
[4, 5, 6]]
is
[[1, 4],
[2, 5],
[3, 6]]
The TRN function takes the transpose of any two-dimensional matrix, so a complete program to print the transpose of A looks like this.
A = [[1, 2, 3],
[4, 5, 6]]
print trn(A)
And who hasn’t taken the determinant of a matrix at one time or another? It’s pretty easy by hand for a 2x2 matrix, and not all that hard for a 3x3, but it’s pretty painful after that. The DET function handles it easily, though.
A = [[1, 1, 2],
[2, 4, -3],
[3, 6, -5]]
print det(A)
There are two common vector operations. The dot product is available as the DOT function. The only requirement on the two parameters is that they have the same number of elements, and be one-dimensional arrays of numbers. DOT is also a handy shortcut for finding the distance between points, like this:
p1 = [1.3, -6, 3.7]
p2 = [4.5, 7.1, -4.5]
d1 = p1 – p2
print sqr(dot(d1, d1))
Curiously, standard BASIC does not have a function for the other common vector operation, the cross product. It’s also not built into techBASIC as of version 1.2. It’s easy enough to add using this subroutine if needed, though.
a = [2.6, 2.7, -1.8]
b = [4.1, 5.1, 3]
PRINT cross(a, b)
END
FUNCTION cross (v1(), v2()) (3)
DIM c(3)
c(1) = v1(2)*v2(3) - v1(3)*v2(2)
c(2) = v1(3)*v2(1) - v1(1)*v2(3)
c(3) = v1(1)*v2(2) - v1(2)*v2(1)
cross = c
END FUNCTION
This also shows how to pass vectors as parameters, and how to create a function that returns a vector. The parenthesis after the parameters tell the compiler the parameters are singly subscripted arrays, and the 3 in parenthesis after the parameter list tell the compiler the function returns a singly subscripted array of 3 real numbers. For multiply subscripted arrays, use commas on the parameters to indicate the number of subscripts. For return values, the size of each subscript is specified explicitly, just as with this singly subscripted array. It works just like a DIM statement.
What About DIM?
And that brings up a really good point. Most of the samples so far have been complete programs you can type in and run, yet we haven’t seen a single DIM statement which, if you know BASIC, is the way all arrays are defined, right? How does BASIC know if you want a variable to be an array or scalar? How does BASIC know how big to make the arrays? The answer is what helps make BASIC such a great language for quickly writing matrix-based programs. It looks.
Whenever you assign a value to a variable that has not been used, techBASIC looks at the value you will assign to the variable to determine whether it is an array or scalar. If it is an array, there is an implied DIM statement that creates a variable with the proper number of subscripts. As far as the number of elements in each subscript, that can change! Unlike some of the implementations of BASIC that evolved from the Microsoft implementations, the number of subscripts in a standard BASIC array is not fixed. Any assignment statement can change the number of elements. For example, this program is perfectly legal.
a = [1, 2, 3]
a = [1, 2, 3, 4]
What is not legal is to change the number of subscripts. You can’t assign a two-dimensional array to a variable that has already appeared holding a one-dimensional array. This is not legal.
a = [1, 2]
a = [[1, 2],
[3, 4]] : ! This is not legal!
You also can’t assign a scalar to a variable that has been used as an array, or an array to a variable that has been used as a scalar.
As you can imagine, it is easy to loose track of just how many elements are in an array. In the case of passed parameters, you could even write subroutines and functions that would work on different sized arrays, if only you knew how large the array that was passed really was. Standard BASIC has three functions to help find the size of arrays.
SIZE tells you how many elements are in an array or, for a particular subscript, how many elements are in the subscript. To find the number of elements in the entire array, pass the name of the array, like this.
a = [[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]]
print size(a)
This program prints 12, since there are 12 elements in the array. You can add an optional second parameter specifying one of the subscripts, starting at 1, and SIZE will return the number of elements.
print size(a, 1)
print size(a, 2)
prints 3 and 4, since BASIC arrays are indexed by the row and then column.
So that tells you everything you need to know, right? Not so fast. The one dramatic difference between arrays in most microcomputer BASICs and standard BASIC is that the subscript of the first element in the array is 1, not 0, and there is a way to change it to be anything you want. That’s why standard BASIC also has the functions LBOUND and UBOUND. They work a lot like SIZE, but return the lower and upper allowed subscripts. In the case of the previous array,
print lbound(a, 1)
print lbound(a, 2)
prints 1 for both subscripts, while
print ubound(a, 1)
print ubound(a, 2)
prints 3 and 4, just like SIZE. But now we come to a use for DIM in standard BASIC. We can dimension the variable with a specific subscript range, as in
dim a(-1 to 1, 0 to 1)
print size(a, 1)
print size(a, 2)
print lbound(a, 1)
print lbound(a, 2)
print ubound(a, 1)
print ubound(a, 2)
Now the calls to SIZE print 3 and 2, the calls to LBOUND print -1 and 0, and the calls to UBOUND print 1 and 1.
There are a lot of BASIC programs out there that were written for implementations of BASIC that assume the smallest array subscript is always 0, not 1. The BASE statement helps with those programs. It can only be used once in a program, and must appear before the first use of an array. It changes—well, actually specifies—the default lower bound for array subscripts. You can use
base 0
to get your program to behave like microprocessor implementations of BASIC, or
base 1
as a reminder of the default lower bound in standard BASIC.
Other Ways to Create Arrays
There are three other utility functions in standard BASIC that make it easy to create common array values. techBASIC relaxes some of the restrictions found on these functions in standard BASIC; we’ll describe the techBASIC implementation here.
Back at the start of this article, we looked at a way to create an identity matrix.
a = [[1, 0, 0],
[0, 1, 0],
[0, 0, 1]]
Creating an identity matrix is a pretty common operation, though. After all, an identity matrix is the matrix equivalent of the number 1. The IDN function does the same thing a lot faster.
a = idn(3)
IDN returns a square matrix with 1 along the main diagonal and 0 everywhere else.
CON (for constant) creates an array filled with 1. You can then add or multiply the array by a scalar to get an array initially filled with any particular value. The parameters look sort of like a DIM statement, except that the lower bound on each element must be 1. This statement prints a 4 by 4 matrix filled with the value 1.23:
print con(4, 4)*1.23
ZER works just like CON, but returns an array filled with zeros instead of ones.
As you can see, techBASIC has a pretty good set of matrix commands. There are some details you generally don’t need to worry about, like when an array constant is single-precision and when it is double-precision. (It’s double-precision if any element of the array constant is double-precision.) These are covered in the reference manual, which also has more examples of array and matrix manipulations.
So, next time you need to do linear algebra homework, solve a set of simultaneous equations, or rotate an object in 3-space, pop into techBASIC, where all of these tasks are a lot easier than the alternatives.
Copyright 2012, Byte Works, Inc. All Rights Reserved.
Tuesday, January 31, 2012