I’ve never been very good at doing manual computations, and whenever I need to do a tedious computation for an assignment, I like to automate it by writing a computer program. Usually I implemented an ad-hoc solution using Haskell, either using a simple library or rolling my own implementation if the library didn’t have it. But I found this solution to be unsatisfactory: my Haskell programs worked with integers and floating numbers and I couldn’t easily generalize it to work with symbolic expressions. So I looked to learn a CAS (computer algebra system), so in the future I won’t have to hack together buggy code for common math operations.
I have no experience with symbolic computing, so it wasn’t clear to me where to begin. To start off, there are many different competing computer algebra systems, all incompatible with each other, and it’s far from clear which one is best for my needs. I began to experiment with several systems, but after a few days I still couldn’t decide which one was the winner.
I narrowed it down to 3 platforms. Here’s my setup (all running on Windows 7):
- Mathematica 8.0
- Maxima 5.32 with wxMaxima 13.04
- Maple 18.00
So I came up with a trial — I had a short (but nontrivial) problem representative of the type of problem I’d be looking at, and I would try to solve it in all 3 languages, to determine which one was easiest to work with.
The Problem
This problem came up as a part of a recent linear algebra assignment.
Let the field be (so all operations are taken modulo 5). Find all 2×2 matrices such that
We can break this problem into several steps:
- Enumerate all lists of length 4 of values between 0 to 4, that is, [[0,0,0,0],[0,0,0,1],…,[4,4,4,4]]. We will probably do this with a cartesian product or list comprehension.
- Figure out how to convert a list into a 2×2 matrix form that the system can perform matrix operations on. For example, [1,2,3,4] might become matrix([1,2],[3,4])
- Figure out how to do control flow, either by looping over a list (procedural) or with a map and filter (functional)
- Finally, multiply the matrices modulo 5 and check if it equals the identity matrix, and output.
This problem encompasses a lot of the challenges I have with CAS software, that is, utilize mathematical functions (in this case, we only use matrix multiplication and transpose), yet at the same time express a nontrivial control flow. There are 5^4=625 matrices to check, so performance is not a concern; I am focusing on ease of use.
For reference, here is the answer to this problem:
These are the 8 matrices that satisfy the desired property.
I have no prior experience in programming in any of the 3 languages, and I will try to solve this problem with the most straightforward way possible with each of the languages. I realize that my solutions will probably be redundant and inefficient because of my inexperience, but it will balance out in the end because I’m equally inexperienced in all of the languages.
Mathematica
I started with Mathematica, a proprietary system by Wolfram Research and the engine behind Wolfram Alpha. Mathematica is probably the most powerful out of the three, with capabilities with working with data well beyond what I’d expect from a CAS.
What I found most jarring about Mathematica is its syntax. I’ve worked with multiple procedural and functional languages before, and there are certain things that Mathematica simply does differently from everybody else. Here are a few I ran across:
- To use a pure function (equivalent of a lambda expression), you refer to the argument as #, and the function must end with the & character
- The preferred shorthand for Map is /@ (although you can write the longhand Map)
- To create a cartesian product of a list with itself n times, the function is called Tuples, which I found pretty counterintuitive
Initially I wanted to convert my flat list into a nested list by pattern matching Haskell style, ie f [a,b,c,d] = [[a,b],[c,d]], but I wasn’t sure how to do that, or if the language supports pattern matching on lists. However I ran across Partition[xs,2] which does the job, so I went with that.
Despite the language oddities, the functions are very well documented, so I was able to complete the task fairly quickly. The UI is fairly streamlined and intuitive, so I’m happy with that. I still can’t wrap my head around the syntax — I would like it more if it behaved more like traditional languages — but I suppose I’ll get the hang of it after a while.
Here’s the program I came up with:
SearchSpaceLists := Tuples[Range[0, 4], 4] SearchSpaceMatrices := Map[Function[xs, Partition[xs, 2]], SearchSpaceLists] Middle := {{2, 0}, {0, 3}} FilteredMatrices := Select[SearchSpaceMatrices, Mod[Transpose[#].Middle.#, 5] == IdentityMatrix[2] &] MatrixForm[#] & /@ FilteredMatrices
Maxima
Maxima is a lightweight, open source alternative to Mathematica; I’ve had friends recommend it as being small and easy to use.
The syntax for Maxima is more natural, with things like lists and loops and lambda functions working more or less the way I expect. However, whenever I tried to do something with a function that isn’t the most common use case, I found the documentation lacking and often ended up combing through old forum posts.
Initially I tried to generate a list with a cartesian product like my Mathematica version, but I couldn’t figure out how to do that, eventually I gave up and used 4 nested for loops because that was better documented.
Another thing I had difficulty with was transforming a nested list into a matrix using the matrix command. Normally you would create a matrix with matrix([1,2],[3,4]), so by passing in two parameters. The function doesn’t handle passing in matrix([[1,2],[3,4]]), so to get around that you need to invoke a macro: funmake(‘matrix,[[1,2],[3,4]]).
Overall I found that the lack of documentation made the system frustrating to work with. I would however use it for simpler computations that fall under the common use cases — these are usually intuitive in Maxima.
Here’s the program I came up with:
Middle:matrix([2,0],[0,3]); Ident:identfor(Middle); for a:0 thru 4 do for b:0 thru 4 do for c:0 thru 4 do for d:0 thru 4 do (P:funmake('matrix,[[a,b],[c,d]]), P2:transpose(P).Middle.P, if matrixmap(lambda([x],mod(x,5)),P2) = Ident then print(P));
Shortly after writing this I realized I didn’t actually need the funmake macro, since there’s no need to generate a nested list in the first place, I could simply do matrix([a,b],[c,d]). Oh well, the point still stands.
Maple
Maple is a proprietary system developed by Maplesoft, a company based in Waterloo. Being a Waterloo student, I’ve had some contact with Maple: professors used it for demonstrations, some classes used it for grading. Hence I felt compelled to give Maple a shot.
At first I was pleasantly surprised that matrix multiplication in a finite field was easy — the code to calculate A*B in is simply A.B mod 5. But everything went downhill after that.
The UI for Maple feels very clunky. Some problems I encountered:
- It’s not clear how to halt a computation that’s in a an infinite loop. It doesn’t seem to be possible within the UI, and the documentation suggests it’s not possible in all cases (it recommends manually terminating the process). Of course, this loses all unsaved work, so I quickly learned to save before every computation.
- I can’t figure out how to delete a cell without googling it. It turns out you have to select your cell and a portion of the previous cell, then hit Del.
- Copy and pasting doesn’t work as expected. When I tried to copy code written inside Maple to a text file, all the internal formatting and syntax highlighting information came with it.
- Not an UI issue, but error reporting is poor. For example, the = operator works for integers, but when applied to matrices, it silently returns false. You have to use Equals(a,b) to compare matrices (this is kind of like java).
In the end, I managed to complete the task but the poor UI made the whole process fairly unpleasant. I don’t really see myself using Maple in the future; if I had to, I would try the command line.
Here’s the program I came up with:
with(LinearAlgebra): with(combinat, cartprod): L := [seq(0..4)]: T := cartprod([L, L, L, L]): Middle := <2,0;0,3>: while not T[finished] do pre_matrix := T[nextvalue](); matr := Matrix(2,2,pre_matrix); if Equal(Transpose(matr).Middle.matr mod 5, IdentityMatrix(2)) then print(matr); end if end do:
Conclusion
After the brief trial, there is still no clear winner, but I have enough data to form some personal opinions:
- Mathematica is powerful and complete, but has a quirky syntax. It has the most potential — definitely the one I would go with if I were to invest more time into learning a CAS.
- Maxima is lightweight and fairly straightfoward, but because of lack of documentation, it might not be the best tool to do complicated things with. I would keep it for simpler calculations though.
- Maple may or may not be powerful compared to the other two, I don’t know enough to compare it. But its UI is clearly worse and it would take a lot to compensate for that.