23

Is there a way to do symbolic matrix algebra in Mathematica for matrices where the dimensions are unknown? For example, if I have an MxL matrix A and an LxN matrix B, I would like to be able to enter

A.B

And have it give me a matrix whose element ab[i,j] is given by

Sum[a[i,l]*b[l,j],{l,1,L}]

The problem I'm working on is like this one, but involves the product of 12 matrices, including the same matrix (and its transpose) repeated several times. It will probably be possible to simplify the values of the resulting matrix, but it's not obvious whether this is possible until after I do the algebra. This may be a problem that I have to solve by hand, but it would be much easier if Mathematica could provide some help in simplifying the algebra.

jack
  • 285
  • 1
  • 2
  • 8
  • As Sasha [pointed out](http://stackoverflow.com/questions/5708208/symbolic-matrices-in-mathematica-with-unknown-dimensions/5708397#5708397), it is doable. But, I'd do this be hand, and rely on Mathematica to check it along the way. I'd use the [Einstein summation convention](http://en.wikipedia.org/wiki/Einstein_summation_convention), though, as it will save a lot of time and it makes most manipulations easier. – rcollyer Apr 18 '11 at 20:27
  • @rcollyer I think I'm missing something in your comment. What do you mean by using index contraction? If your explanation is more than a comment can hold, I could post an ad-hoc question – Dr. belisarius Apr 18 '11 at 21:58
  • @belisarius: I think @rcollyer was referring to using shorter notations when doing calculations by hand, as it is more convenient; not in mma. For example, `a_1b_1+...+a_nb_n` can be conveniently written as `a_ib_i`, with the summation over all `n` being implied. Dot products, dyadic products, traces etc can all be written compactly this way. Things get messier when exponentials are involved, as the notation gets confusing (I'm not sure if they even use this notation in that case). – abcd Apr 18 '11 at 22:20
  • @yoda I missed the _by hand_ part. So the question is: Is there a way to use index contraction in Mma? – Dr. belisarius Apr 18 '11 at 22:31
  • 3
    @belisarius: A quick google search led me to [this package](http://www.math.washington.edu/~lee/Ricci/) which lets you perform symbolic tensor computations using Einstein summation convention. I haven't tested it, but if it's any good, it should be added to the CW post on Mathematica tools. – abcd Apr 18 '11 at 22:35
  • @yoda Nice find! However, as it has not been updated since 2006, we should expect compatibility problems ... – Dr. belisarius Apr 18 '11 at 22:43
  • 4
    It doesn't help us at the moment, but Stephen Wolfram has promised that a capability is coming [Real Soon Now](http://blog.wolfram.com/2009/11/12/the-rd-pipeline-for-mathematica/) that is "going to make doing serious tensor analysis feel pretty much like doing ordinary algebra". I'm eager to see what he means by that, and whether the capability will address problems such as the one raised by this question. – WReach Apr 18 '11 at 23:11

5 Answers5

20

Here's the code [dead-link removed] that wasted my morning... It's not complete, but it basically works. You can either get the notebook from the previous link [dead] or copy the code below.

Note that a similar question turned up on ask.sagemath not so long ago.

Almost like Sasha's solution, you define a symbolic matrix using

A = SymbolicMatrix["A", {n, k}]

for some string "A" that does not have to be the same as the symbol A. Ok, here's the code:

ClearAll[SymbolicMatrix]
Options[SymbolicMatrix] = {Transpose -> False, Conjugate -> False, MatrixPower -> 1};

Short hand for entering square matrices (could make it work for different heads...)

SymbolicMatrix[name_String, n:_Symbol|_Integer, opts : OptionsPattern[]] := SymbolicMatrix[name, {n, n}, opts]

Behavior under Transpose, Conjugate, ConjugateTranspose and Inverse

SymbolicMatrix/:Transpose[SymbolicMatrix[name_String,{m_,n_},opts:OptionsPattern[]]]:=SymbolicMatrix[name,{n,m},
  Transpose->!OptionValue[SymbolicMatrix,Transpose],Sequence@@FilterRules[{opts},Except[Transpose]]]
SymbolicMatrix/:Conjugate[SymbolicMatrix[name_String,{m_,n_},opts:OptionsPattern[]]]:=SymbolicMatrix[name,{m,n},
  Conjugate->!OptionValue[SymbolicMatrix,Conjugate],Sequence@@FilterRules[{opts},Except[Conjugate]]]
SymbolicMatrix/:ConjugateTranspose[A:SymbolicMatrix[name_String,{m_,n_},opts:OptionsPattern[]]]:=Conjugate[Transpose[A]]
SymbolicMatrix/:Inverse[SymbolicMatrix[name_String,{n_,n_},opts:OptionsPattern[]]]:=SymbolicMatrix[name,{n,n},
  MatrixPower->-OptionValue[SymbolicMatrix,MatrixPower],Sequence@@FilterRules[{opts},Except[MatrixPower]]]

SymbolicMatrix/:(Transpose|Conjugate|ConjugateTranspose|Inverse)[eye:SymbolicMatrix[IdentityMatrix,{n_,n_}]]:=eye

Combining matrix powers (including the identity matrix)

SymbolicMatrix/:SymbolicMatrix[a_String,{n_,n_},opt1:OptionsPattern[]].SymbolicMatrix[a_,{n_,n_},opt2:OptionsPattern[]]:=SymbolicMatrix[a,{n,n},Sequence@@FilterRules[{opt1},Except[MatrixPower]],MatrixPower->Total[OptionValue[SymbolicMatrix,#,MatrixPower]&/@{{opt1},{opt2}}]]/;FilterRules[{opt1},Except[MatrixPower]]==FilterRules[{opt2},Except[MatrixPower]]

SymbolicMatrix[a_String,{n_,n_},opts:OptionsPattern[]]:=SymbolicMatrix[IdentityMatrix,{n,n}]/;OptionValue[SymbolicMatrix,{opts},MatrixPower]===0

SymbolicMatrix/:(A:SymbolicMatrix[a_String,{n_,m_},OptionsPattern[]]).SymbolicMatrix[IdentityMatrix,{m_,m_}]:=A
SymbolicMatrix/:SymbolicMatrix[IdentityMatrix,{n_,n_}].(A:SymbolicMatrix[a_String,{n_,m_},OptionsPattern[]]):=A

Pretty printing with the dimension as a tooltip.

Format[SymbolicMatrix[name_String,{m_,n_},opts:OptionsPattern[]]]:=With[{
  base=If[OptionValue[SymbolicMatrix,MatrixPower]===1,
    StyleBox[name,FontWeight->Bold,FontColor->Darker@Brown],
    SuperscriptBox[StyleBox[name,FontWeight->Bold,FontColor->Darker@Brown],OptionValue[SymbolicMatrix,MatrixPower]]],
  c=Which[
    OptionValue[SymbolicMatrix,Transpose]&&OptionValue[SymbolicMatrix,Conjugate],"\[ConjugateTranspose]",
    OptionValue[SymbolicMatrix,Transpose],"\[Transpose]",
    OptionValue[SymbolicMatrix,Conjugate],"\[Conjugate]",
  True,Null]},
  Interpretation[Tooltip[DisplayForm@RowBox[{base,c}/.Null->Sequence[]],{m,n}],SymbolicMatrix[name,{m,n},opts]]]

Format[SymbolicMatrix[IdentityMatrix,{n_,n_}]]:=Interpretation[Tooltip[Style[\[ScriptCapitalI],Bold,Darker@Brown],n],SymbolicMatrix[IdentityMatrix,{n,n}]]

Define some rules for Dot. Need to extend then so that it can handle scalar quantities etc... Also so that inverses of A.B can be taken if A.B is square, even if neither A nor B are square.

SymbolicMatrix::dotdims = "The dimensions of `1` and `2` are not compatible";
Unprotect[Dot]; (*Clear[Dot];*)
Dot/:(a:SymbolicMatrix[_,{_,n_},___]).(b:SymbolicMatrix[_,{m_,_},___]):=(Message[SymbolicMatrix::dotdims,HoldForm[a],HoldForm[b]];Hold[a.b])/;Not[m===n]
Dot/:Conjugate[d:Dot[A_SymbolicMatrix,B__SymbolicMatrix]]:=Map[Conjugate,d]
Dot/:(t:Transpose|ConjugateTranspose)[d:Dot[A_SymbolicMatrix,B__SymbolicMatrix]]:=Dot@@Map[t,Reverse[List@@d]]
Dot/:Inverse[HoldPattern[d:Dot[SymbolicMatrix[_,{n_,n_},___]...]]]:=Reverse@Map[Inverse,d]
A_ .(B_+C__):=A.B+A.Plus[C]
(B_+C__).A_:=B.A+Plus[C].A
Protect[Dot];

Make Transpose, Conjugate and ConjugateTranspose distribute over Plus.

Unprotect[Transpose, Conjugate, ConjugateTranspose];
Clear[Transpose, Conjugate, ConjugateTranspose];
Do[With[{c = c}, c[p : Plus[a_, b__]] := c /@ p], {c, {Transpose, Conjugate, ConjugateTranspose}}]
Protect[Transpose, Conjugate, ConjugateTranspose];

Here's some simple tests/examples

Test image 1

Test image 2

Now for code that deals with the component expansion. Like Sasha's solution, I'll overload Part.

Clear[SymbolicMatrixComponent]
Options[SymbolicMatrixComponent]={Conjugate->False,MatrixPower->1};

Some notation

Format[SymbolicMatrixComponent[A_String,{i_,j_},opts:OptionsPattern[]]]:=Interpretation[DisplayForm[SubsuperscriptBox[StyleBox[A,Darker@Brown],RowBox[{i,",",j}],
RowBox[{If[OptionValue[SymbolicMatrixComponent,{opts},MatrixPower]===1,Null,OptionValue[SymbolicMatrixComponent,{opts},MatrixPower]],If[OptionValue[SymbolicMatrixComponent,{opts},Conjugate],"*",Null]}/.Null->Sequence[]]]],
SymbolicMatrixComponent[A,{i,j},opts]]

Code to extract parts of matrices and Dot products of matrices Need to add checks to ensure that explicit summation ranges are all sensible.

SymbolicMatrix/:SymbolicMatrix[A_String,{m_,n_},opts:OptionsPattern[]][[i_,j_]]:=SymbolicMatrixComponent[A,If[OptionValue[SymbolicMatrix,{opts},Transpose],Reverse,Identity]@{i,j},Sequence@@FilterRules[{opts},Options[SymbolicMatrixComponent]]]

SymbolicMatrix/:SymbolicMatrix[IdentityMatrix,{m_,n_}][[i_,j_]]:=KroneckerDelta[i,j]

Unprotect[Part]; (*Clear[Part]*)
Part/:((c___.b:SymbolicMatrix[_,{o_,n_},OptionsPattern[]]).SymbolicMatrix[A_String,{n_,m_},opts:OptionsPattern[]])[[i_,j_]]:=With[{s=Unique["i",Temporary]},Sum[(c.b)[[i,s]]SymbolicMatrixComponent[A,If[OptionValue[SymbolicMatrix,{opts},Transpose],Reverse,Identity]@{s,j},Sequence @@ FilterRules[{opts}, Options[SymbolicMatrixComponent]]],{s,n}]]
Part/:(a_+b_)[[i_,j_]]:=a[[i,j]]+b[[i,j]]/;!And@@(FreeQ[#,SymbolicMatrix]&/@{a,b})
Part/:Hold[a_][[i_,j_]]:=Hold[a[[i,j]]]/;!FreeQ[a,SymbolicMatrix]
Protect[Part];

Some examples:

example3 example4

Ryogi
  • 5,497
  • 5
  • 26
  • 46
Simon
  • 14,631
  • 4
  • 41
  • 101
  • 1
    This is over my head, but it looks fancy, so: +1 – Mr.Wizard Apr 19 '11 at 03:27
  • @Mr.Wizard: I doubt that any of it's over your head. It's just a mess of `Option*` commands... – Simon Apr 19 '11 at 04:01
  • Wow, you should posti this in the Mathematica Tool Bag community wiki http://stackoverflow.com/q/4198961/181759, very nice work. – Timo Apr 19 '11 at 11:30
  • Thanks, @Simon ! This is super helpful. I'm not a terribly good Mathematica programmer, so I don't really understand what is going on, but I can almost use your code as-is. As part of the matrix product I'm trying to evaluate, I need to repeatedly multiply by square matrices whose entries are all ones. I tried (and failed) to implement this as a function on SymbolicMatrix, so each entry is replaced by the sum of the elements of that column. Would it be easy for you to show me code that does this? Thanks again for your help - this has inspired me to learn a lot more Mathematica! – jack Apr 19 '11 at 15:49
  • @jack: At the moment I haven't decided on the best way to implement the substitution of actual matrices. I want a simple interface where you say SymbolicMatrix -> Matrix, which then will deal with all the transposes, conjugates, MatrixPowers and component expressions for you. I also don't have too much time to play with it. For moment you can use explicit replacement rules. (Sorry... I'll write the code eventually!) – Simon Apr 20 '11 at 01:52
  • e.g. `SymbolicMatrix["B",{4,5}]->{{1,0,1,3,0},{1,0,1,1,2},{1,1,0,1,1},{0,1,1,1,0}}` and/or `SymbolicMatrixComponent["B", {i_, j_}] :> {{1, 0, 1, 3, 0}, {1, 0, 1, 1, 2}, {1, 1, 0, 1, 1}, {0, 1, 1, 1, 0}}[[i,j]]`. Conjugates and transposes of these rules can be created by manually applying them to both sides of the rules. – Simon Apr 20 '11 at 01:55
  • @Mr.Wizard: I'm thinking that it might have been easier to write this thing using optional arguments instead of `Options`. originally I thought that the named options might be clearer... but it really is quite messy and makes pattern matching a little tricky since the `Options` aren't always there (if using the defaults) and not always in the same order. It reduces me to using conditionals instead of patterns... – Simon Apr 20 '11 at 01:59
  • This is really useful functionality. I wonder if you could add the possibility 1) to make actual matrices with symbolic submatrices, like `Mi = {{1,Transpose[vi]},{vi,Ai}};` (where `vi` and `Ai` are symbolic) such that taking `M1.M2` would carry out the `2x2` matrix multiplication AND put the `.` operator between the symbolic elements in it. 2) To create symbolic matrices which implicitly depend on one or more variables, like `A[u]`, such that we get a proper result taking a derivative, instead of `D[A[u],u]=0`. This would be awesome! – Kagaratsch Oct 06 '14 at 18:02
  • Sorry, but does the 'SymbolicMatrix' Function do not supported now? My Mathematica of version 13.1 does not work with this function. – narip Feb 06 '23 at 00:42
7

I am not sure if this is really very helpful, but it could be a start:

ClearAll[SymbolicMatrix]
SymbolicMatrix /: Transpose[SymbolicMatrix[a_, {m_, n_}]] := 
     SymbolicMatrix[Evaluate[a[#2, #1]] & , {n, m}]
SymbolicMatrix /: 
 SymbolicMatrix[a_, {m_, n_}] . SymbolicMatrix[b_, {n_, p_}] := 
     With[{s = Unique[\[FormalI], Temporary]}, 
  SymbolicMatrix[Function[{\[FormalN], \[FormalM]}, 

    Evaluate[Sum[a[\[FormalN], s]*b[s, \[FormalM]], {s, 1, n}]]], {m, 
    p}]]
SymbolicMatrix /: SymbolicMatrix[a_, {m_, n_}][[i_, j_]] := a[i, j]


Now, define some symbolic matrices and do dot product:
In[109]:= amat = SymbolicMatrix[a, {n, k}]; 
bmat = SymbolicMatrix[b, {k, k}]; 

Evaluate matrix elements:

In[111]:= (amat . bmat . Transpose[amat])[[i, j]]

Out[111]= Sum[
 a[j, \[FormalI]$1485]*
  Sum[a[i, \[FormalI]$1464]*
    b[\[FormalI]$1464, \[FormalI]$1485], {\[FormalI]$1464, 1, k}], 
   {\[FormalI]$1485, 1, k}]
Sasha
  • 5,935
  • 1
  • 25
  • 33
  • 1
    I see a disadvantage of using `In` and `Out`, it makes it much harder to copy. Would you mind putting the definition in one "line" of input, so that we can access it easier? – rcollyer Apr 18 '11 at 20:20
  • +1 I like the approach, but you'll need to define an enormous amount of matrix functions such as Transpose. Additionally, for each symbolic matrix you define you'll also have to provide a function name (a,b in line 109). The user should preferably not be bothered by that. – Sjoerd C. de Vries Apr 18 '11 at 20:26
  • ToMatrix was meant to instantiate a matrix for given explicit dimensions but it was no good, so I deleted it in the edit. – Sasha Apr 18 '11 at 20:32
  • Thanks a lot for your help, @Sasha. I'm not a terribly good Mathematica programmer, so I'll need to spend some quality time with the documentation before I understand your code (I'll probably read up on modules, scope, etc.) In the meantime, I'll probably just follow @rcollyer 's advice and work it out by hand. – jack Apr 18 '11 at 21:29
2

If you're willing to switch from Mathematica to Python the functionality you need is in the development branch of SymPy. It should be in the 0.72 release.

In [1]: from sympy import *
In [2]: X = MatrixSymbol('X', 2,3)
In [3]: Y = MatrixSymbol('Y', 3,3)
In [4]: X*Y*X.T
Out[4]: X⋅Y⋅X'

In [5]: (X*Y*X.T)[0,1]
Out[5]: 
X₀₀⋅(X₁₀⋅Y₀₀ + X₁₁⋅Y₀₁ + X₁₂⋅Y₀₂) + X₀₁⋅(X₁₀⋅Y₁₀ + X₁₁⋅Y₁₁ + X₁₂⋅Y₁₂) + X₀₂⋅(X₁₀⋅Y₂₀ + X₁₁⋅Y₂₁ + X₁₂⋅Y₂₂)

These purely symbolic objects can also make use of all of the standard matrix algorithms for explicitly defined matrices

In [14]: X = MatrixSymbol('X', 2,2)
In [14]: X.as_explicit().det()
Out[14]: X₀₀⋅X₁₁ - X₀₁⋅X₁₀

Symbolic shaped matrices are doable too

In [7]: n,m,k = symbols('n,m,k')
In [8]: X = MatrixSymbol('X', n,m)
In [9]: Y = MatrixSymbol('Y', m,k)
In [10]: (X*Y)[3,4]
Out[10]: 
m - 1                
 ___                 
 ╲                   
  ╲   X(3, k)⋅Y(k, 4)
  ╱                  
 ╱                   
 ‾‾‾                 
k = 0                
MRocklin
  • 55,641
  • 23
  • 163
  • 235
  • Unfortunately, `sympy` doesn't see to be able to do much generic algebraic manipulation with matrices symbolically. For example, you can't rearrange expressions with `solve`: https://github.com/sympy/sympy/issues/11124 – mpeac Sep 26 '16 at 22:55
1

You can use NCAlgebra for that. For example:

MM = 3
LL = 2
NN = 3
AA = Table[Subscript[a, i, j], {i, 1, MM}, {j, 1, LL}]
BB = Table[Subscript[b, i, j], {i, 1, LL}, {j, 1, NN}]

will define symbolic matrices AA and BB with noncommutative entries $a_{i,j}$ and $b_{i,j}$. These can get manipulated and produce the results you're looking for. For example:

NCDot[AA, BB]

will multiply the two matrices using **,

AA ** BB // NCMatrixExpand

will do the same, and

tp[AA ** BB] // NCMatrixExpand

will transpose using tp.

0

I use this approach:

SymbolicMatrix[symbol_String, m_Integer, n_Integer] := Table[
  ToExpression[symbol <> ToString[i] <> ToString[j]],
  {i, 1, m}, {j, 1, n}
];
SymbolicMatrix[symbol_Symbol, m_Integer, n_Integer] := SymbolicMatrix[ToString[symbol], m, n];
SymbolicMatrix[symbol_, m_Integer] := SymbolicMatrix[symbol, m, 1];

When invoked as A = SymbolicMatrix["a", 2, 3]; or A = SymbolicMatrix[a, 2, 3];, it creates a matrix

{{a11, a12, a13}, {a21, a22, a23}}

So, it creates mxn symbols, but I find them descriptive, and the whole thing is easy to use (at least for my purposes).

Vedran Šego
  • 3,553
  • 3
  • 27
  • 40