如何在Mathematica中有效地初始化这个稀疏数组?

时间:2011-10-26 22:31:27

标签: wolfram-mathematica sparse-matrix

我正在尝试解决Mathematica中一个相当大的线性规划问题,但由于某些原因,瓶颈是建立线性约束数组。

我初始化矩阵的代码如下所示:

AbsoluteTiming[S = SparseArray[{{i_, 1} -> iaa[[i]],
    {i_, j_} /; Divisible[a[[j]], aa[[i]]] -> 1.}, {2*n, n}]]

这里n是4455,iaa是实数列表,a,aa是整数列表。我得到的这一行的输出是

{2652.014773,SparseArray[<111742>,{8910,4455}]}

换句话说,初始化此矩阵需要45分钟,即使它只有111,742个非零项。为了比较,实际上解决线性程序只需要17秒。是什么给了什么?

编辑:此外,任何人都可以解释为什么这会占用如此多的内存而不是运行?因为在用户时间内,这种计算只需不到十分钟......大部分计算时间都花费在内存中进行分页。

Mathematica出于某种原因将这个矩阵存储为非稀疏矩阵,而它正在构建它吗?因为那真的很蠢。

2 个答案:

答案 0 :(得分:10)

你肯定可以做得更好。这是一个基于发布的here低级稀疏数组API的代码,我将重现它以使代码自包含:

ClearAll[spart, getIC, getJR, getSparseData, getDefaultElement, makeSparseArray];
HoldPattern[spart[SparseArray[s___], p_]] := {s}[[p]];
getIC[s_SparseArray] := spart[s, 4][[2, 1]];
getJR[s_SparseArray] := Flatten@spart[s, 4][[2, 2]];
getSparseData[s_SparseArray] := spart[s, 4][[3]];
getDefaultElement[s_SparseArray] := spart[s, 3];
makeSparseArray[dims : {_, _}, jc : {__Integer}, ir : {__Integer}, data_List, defElem_: 0] := 
    SparseArray @@ {Automatic, dims,  defElem, {1, {jc, List /@ ir}, data}};



Clear[formSparseDivisible];
formSparseDivisible[a_, aa_, iaa_, chunkSize_: 100] :=
  Module[{getDataChunkCode, i, start, ic, jr, sparseData, dims,  dataChunk, res},
    getDataChunkCode :=
      If[# === {}, {}, SparseArray[1 - Unitize@(Mod[#, aa] & /@ #)]] &@
        If[i*chunkSize >= Length[a],
           {},
           Take[a, {i*chunkSize + 1, Min[(i + 1)*chunkSize, Length[a]]}]];  
    i = 0;
    start = getDataChunkCode;
    i++;
    ic = getIC[start];
    jr = getJR[start];
    sparseData = getSparseData[start];
    dims = Dimensions[start];        
    While[True,
      dataChunk = getDataChunkCode;
      i++;
      If[dataChunk === {}, Break[]];
      ic = Join[ic, Rest@getIC[dataChunk] + Last@ic];
      jr = Join[jr, getJR[dataChunk]];
      sparseData = Join[sparseData, getSparseData[dataChunk]];
      dims[[1]] += First[Dimensions[dataChunk]];
    ];
    res = Transpose[makeSparseArray[dims, ic, jr, sparseData]];
    res[[All, 1]] = N@iaa;
    res]

现在,时间安排如下:

In[249]:= 
n = 1500;
iaa = aa = Range[2 n];
a = Range[n];
AbsoluteTiming[res = formSparseDivisible[a, aa, iaa, 100];]

Out[252]= {0.2656250, Null}

In[253]:= AbsoluteTiming[
  res1 = SparseArray[{{i_, 1} :> 
  iaa[[i]], {i_, j_} /; Divisible[a[[j]], aa[[i]]] -> 1.}, {2*n, n}];]

Out[253]= {29.1562500, Null}

所以,对于这个大小的数组,我们已经加速了100倍。当然,结果是一样的:

In[254]:= Normal@res1 == Normal@res
Out[254]= True

解决方案的主要思想是对问题进行矢量化(Mod),并使用上面的低级API以块的形式逐步构建生成的稀疏数组。

修改

代码假定列表的长度合适 - 特别是a的长度应为n,而aaiaa - {{1} }。因此,要与其他答案进行比较,必须稍微修改测试代码(仅适用于2n):

a

编辑2

你所需的矩阵大小在我不太快的笔记本电脑(M8)上大约需要3秒钟完成,而且内存使用量相当不错:

n = 500;
iaa = RandomReal[{0, 1}, 2 n];
a = Range[ n]; aa = RandomInteger[{1, 4 n}, 2 n];


In[300]:= 
AbsoluteTiming[U=SparseArray[ReplacePart[Outer[Boole[Divisible[#1,#2]]&,
a[[1;;n]],aa],1->iaa]]\[Transpose]]
AbsoluteTiming[res = formSparseDivisible[a,aa,iaa,100]]

Out[300]= {0.8281250,SparseArray[<2838>,{1000,500}]}
Out[301]= {0.0156250,SparseArray[<2838>,{1000,500}]}

In[302]:= Normal@U==Normal@res
Out[302]= True

答案 1 :(得分:3)

你的构造要求Divisible 2*n^2 ~= 40 000 000次,所以它不能很好地扩展!根据整数的大小,这将占大约三分之一到一半的时间。要检查这一点,您只需要运行

AbsoluteTiming[Table[Divisible[a[[j]], aa[[i]]], {i, 2*n}, {j, n}]]

将它与需要为稀疏数组的每个元素调用求值程序的模式匹配构造相结合,你可以看到为什么它可能有点慢。


编辑:这是一些测试代码,用于比较各种结构的时间(在我的机器上):

In[1]:= n = 500;
        iaa = RandomReal[{0, 1}, 2 n];
        a = Range[2 n]; aa = RandomInteger[{1, 4 n}, 2 n]; 

In[4]:= AbsoluteTiming[S = SparseArray[{{i_, 1} :> iaa[[i]], 
                     {i_, j_} /; Divisible[a[[j]], aa[[i]]] -> 1.}, {2*n, n}]]
Out[4]= {3.423123, SparseArray[<2499>, {1000, 500}]}

In[5]:= AbsoluteTiming[T = SparseArray[ReplacePart[Table[
                     Boole[Divisible[a[[i]], aa[[j]]]], {i, 1, n}, {j, 1, 2 n}],
                     1 -> iaa]]\[Transpose]]
Out[5]= {1.524575, SparseArray[<2499>, {1000, 500}]}

In[6]:= AbsoluteTiming[U = SparseArray[ReplacePart[Outer[
                     Boole[Divisible[#1, #2]]&, a[[1 ;; n]], aa], 
                     1 -> iaa]]\[Transpose]]
Out[6]= {0.916801, SparseArray[<2499>, {1000, 500}]}

In[7]:= S == T == U
Out[7]= True

除非您在aaa中有关于整数的其他信息,否则使用2*n^2的{​​{1}}测试将有效地限制代码的运行速度。< / p>