Skip to main content

list manipulation - Best way to create symmetric matrices


From time to time I need to generate symmetric matrices with relatively expensive cost of element evaluation. Most frequently these are Gram matrices where elements are L2 dot products. Here are two ways of efficient implementation which come to mind: memoization and direct procedural generation.



ClearAll[el, elmem];
el[i_, j_] := Integrate[ChebyshevT[i, x] ChebyshevT[j, x], {x, -1, 1}];
elmem[i_, j_] := elmem[j, i] = el[i, j];

n = 30;
ClearSystemCache[];
a1 = Table[el[i, j], {i, n}, {j, n}]; // Timing
ClearSystemCache[];
a2 = Table[elmem[i, j], {i, n}, {j, n}]; // Timing
ClearSystemCache[];

(a3 = ConstantArray[0, {n, n}];
Do[a3[[i, j]] = a3[[j, i]] = el[i, j], {i, n}, {j, i}];) // Timing
a1 == a2 == a3



{34.75, Null}

{18.235, Null}

{18.172, Null}


True

Here a1 is a redundant version for comparison, a2 is using memoization, a3 is a procedural-style one which I don't really like but it beats the built-in function here. The results are quite good but I wonder if there are more elegant ways of generating symmetric matrices?




SUMMARY (UPDATED)


Thanks to all participants for their contributions. Now it's time to benchmark. Here is the compilation of all proposed methods with minor modifications.


array[n_, f_] := Array[f, {n, n}];
arraymem[n_, f_] :=
Block[{mem}, mem[i_, j_] := mem[j, j] = f[i, j]; Array[mem, {n, n}]];

proc[n_, f_] := Block[{res},
res = ConstantArray[0, {n, n}];
Do[res[[i, j]] = res[[j, i]] = f[i, j], {i, n}, {j, i}];
res
]

acl[size_, fn_] :=
Module[{rtmp}, rtmp = Table[fn[i, j], {i, 1, size}, {j, 1, i}];
MapThread[Join, {rtmp, Rest /@ Flatten[rtmp, {{2}, {1}}]}]];


RM1[n_, f_] :=
SparseArray[{{i_, j_} :> f[i, j] /; i >= j, {i_, j_} :> f[j, i]}, n];
RM2[n_, f_] :=
Table[{{i, j} -> #, {j, i} -> #} &@f[i, j], {i, n}, {j, i}] //
Flatten // SparseArray;

MrWizard1[n_, f_] :=
Join[#, Rest /@ #~Flatten~{2}, 2] &@Table[i~f~j, {i, n}, {j, i}];
MrWizard2[n_, f_] := Max@##~f~Min@## &~Array~{n, n};


MrWizard3[n_, f_] := Block[{f1, f2},
f1 = LowerTriangularize[#, -1] + Transpose@LowerTriangularize[#] &@
ConstantArray[Range@#, #] &;
f2 = {#, Reverse[(Length@# + 1) - #, {1, 2}]} &;
f @@ f2@f1@n
]


whuber[n_Integer, f_] /; n >= 1 :=
Module[{data, m, indexes},

data = Flatten[Table[f[i, j], {i, n}, {j, i, n}], 1];
m = Binomial[n + 1, 2] + 1;
indexes =
Table[m + Abs[j - i] - Binomial[n + 2 - Min[i, j], 2], {i, n}, {j,
n}];
Part[data, #] & /@ indexes];

JM[n_Integer, f_, ori_Integer: 1] :=
Module[{tri = Table[f[i, j], {i, ori, n + ori - 1}, {j, ori, i}]},
Fold[ArrayFlatten[{{#1, Transpose[{Most[#2]}]}, {{Most[#2]},

Last[#2]}}] &, {First[tri]}, Rest[tri]]];

generators = {array, arraymem, proc, acl, RM1, RM2, MrWizard1,
MrWizard2, MrWizard3, whuber, JM};

The first three procedures are mine, all other are named after their authors. Let's start from cheap f and (relatively) large dimensions.


fun = Cos[#1 #2] &;
ns = Range[100, 500, 50]
data = Table[ClearSystemCache[]; Timing[gen[n, fun]] // First,
{n, ns}, {gen, generators}];


Here is a logarithmic diagram for this test:


<< PlotLegends`

ListLogPlot[data // Transpose, PlotRange -> All, Joined -> True,
PlotMarkers -> {Automatic, Medium}, DataRange -> {Min@ns, Max@ns},
PlotLegend -> generators, LegendPosition -> {1, -0.5},
LegendSize -> {.5, 1}, ImageSize -> 600, Ticks -> {ns, Automatic},
Frame -> True, FrameLabel -> {"n", "time"}]


enter image description here


Now let's make f numeric:


fun = Cos[N@#1 #2] &;

The result is quite surprising:


enter image description here


As you may guess, the missed quantities are machine zeroes.


The last experiment is old: it doesn't include fresh MrWizard3 and RM's codes are with //Normal. It takes "expensive" f from above and tolerant n:


fun = Integrate[ChebyshevT[#1 , x] ChebyshevT[#2, x], {x, -1, 1}] &;
ns = Range[10, 30, 5]


The result is


enter image description here


As we see, all methods which do not recompute the elements twice behave identically.



Answer



Borrowing liberally from acl's answer:


sim = Join[#, Rest /@ # ~Flatten~ {2}, 2] & @ Table[i ~#~ j, {i, #2}, {j, i}] &;

sim[Subscript[x, ##] &, 5] // Grid


x1,1x2,1x3,1x4,1x5,1x2,1x2,2x3,2x4,2x5,2x3,1x3,2x3,3x4,3x5,3x4,1x4,2x4,3x4,4x5,4x5,1x5,2x5,3x5,4x5,5


Trading efficiency for brevity:


sim2[f_, n_] := Max@## ~f~ Min@## & ~Array~ {n, n}

sim2[Subscript[f, ##] &, 5] // Grid

x1,1x2,1x3,1x4,1x5,1x2,1x2,2x3,2x4,2x5,2x3,1x3,2x3,3x4,3x5,3x4,1x4,2x4,3x4,4x5,4x5,1x5,2x5,3x5,4x5,5




Just for fun, here's a method for fast vectorized (Listable) functions such as your "cheap f" test, showing what's possible if you keep everything packed. (Cos function given a numeric argument so that it evaluates.)


f1 = LowerTriangularize[#, -1] + Transpose@LowerTriangularize[#] & @

ConstantArray[Range@#, #] &;

f2 = {#, Reverse[(Length@# + 1) - #, {1, 2}]} &;

f3 = # @@ f2@f1 @ #2 &;

f3[Cos[N@# * #2] &, 500] // timeAvg

sim[Cos[N@# * #2] &, 500] // timeAvg



0.00712


0.1436



Comments

Popular posts from this blog

functions - Get leading series expansion term?

Given a function f[x] , I would like to have a function leadingSeries that returns just the leading term in the series around x=0 . For example: leadingSeries[(1/x + 2)/(4 + 1/x^2 + x)] x and leadingSeries[(1/x + 2 + (1 - 1/x^3)/4)/(4 + x)] -(1/(16 x^3)) Is there such a function in Mathematica? Or maybe one can implement it efficiently? EDIT I finally went with the following implementation, based on Carl Woll 's answer: lds[ex_,x_]:=( (ex/.x->(x+O[x]^2))/.SeriesData[U_,Z_,L_List,Mi_,Ma_,De_]:>SeriesData[U,Z,{L[[1]]},Mi,Mi+1,De]//Quiet//Normal) The advantage is, that this one also properly works with functions whose leading term is a constant: lds[Exp[x],x] 1 Answer Update 1 Updated to eliminate SeriesData and to not return additional terms Perhaps you could use: leadingSeries[expr_, x_] := Normal[expr /. x->(x+O[x]^2) /. a_List :> Take[a, 1]] Then for your examples: leadingSeries[(1/x + 2)/(4 + 1/x^2 + x), x] leadingSeries[Exp[x], x] leadingSeries[(1/x + 2 + (1 - 1/x...

mathematical optimization - Minimizing using indices, error: Part::pkspec1: The expression cannot be used as a part specification

I want to use Minimize where the variables to minimize are indices pointing into an array. Here a MWE that hopefully shows what my problem is. vars = u@# & /@ Range[3]; cons = Flatten@ { Table[(u[j] != #) & /@ vars[[j + 1 ;; -1]], {j, 1, 3 - 1}], 1 vec1 = {1, 2, 3}; vec2 = {1, 2, 3}; Minimize[{Total@((vec1[[#]] - vec2[[u[#]]])^2 & /@ Range[1, 3]), cons}, vars, Integers] The error I get: Part::pkspec1: The expression u[1] cannot be used as a part specification. >> Answer Ok, it seems that one can get around Mathematica trying to evaluate vec2[[u[1]]] too early by using the function Indexed[vec2,u[1]] . The working MWE would then look like the following: vars = u@# & /@ Range[3]; cons = Flatten@{ Table[(u[j] != #) & /@ vars[[j + 1 ;; -1]], {j, 1, 3 - 1}], 1 vec1 = {1, 2, 3}; vec2 = {1, 2, 3}; NMinimize[ {Total@((vec1[[#]] - Indexed[vec2, u[#]])^2 & /@ R...

plotting - Plot 4D data with color as 4th dimension

I have a list of 4D data (x position, y position, amplitude, wavelength). I want to plot x, y, and amplitude on a 3D plot and have the color of the points correspond to the wavelength. I have seen many examples using functions to define color but my wavelength cannot be expressed by an analytic function. Is there a simple way to do this? Answer Here a another possible way to visualize 4D data: data = Flatten[Table[{x, y, x^2 + y^2, Sin[x - y]}, {x, -Pi, Pi,Pi/10}, {y,-Pi,Pi, Pi/10}], 1]; You can use the function Point along with VertexColors . Now the points are places using the first three elements and the color is determined by the fourth. In this case I used Hue, but you can use whatever you prefer. Graphics3D[ Point[data[[All, 1 ;; 3]], VertexColors -> Hue /@ data[[All, 4]]], Axes -> True, BoxRatios -> {1, 1, 1/GoldenRatio}]