22
$\begingroup$

I'm using SparseArray in a notebook in which I am doing complex conjugation manually, i.e. writing $\sqrt{-1}$ as i and applying /.{i->-i} to perform complex conjugation.

I noticed that ReplaceAll or /. doesn't seem to work on SparseArrays, e.g.

m = SparseArray[{2, 2} -> i];
m /. {i -> -i}

just returns m. Any clues on how to get around this quickly?

$\endgroup$
7
  • $\begingroup$ is it necessary to do this without converting to a normal array? $\endgroup$
    – acl
    Oct 28, 2012 at 13:33
  • $\begingroup$ Hm yes I could convert it to normal, perform conjugation, and convert back to sparse. Would be nice if this can be done on the sparse array though. $\endgroup$
    – matimo2
    Oct 28, 2012 at 13:35
  • $\begingroup$ You can't use I (and then Conjugate[])? $\endgroup$ Oct 28, 2012 at 13:37
  • $\begingroup$ If You are working with own "i" why don't You write own conjugate[] function? You can pack there sparse array conversion and use Your transformation rule. $\endgroup$
    – mmal
    Oct 28, 2012 at 13:37
  • $\begingroup$ @mmal yes that's what I've been doing. I just thought this adds computational steps that might be avoided if /. can be used in a straight-forward manner on sparse arrays. $\endgroup$
    – matimo2
    Oct 28, 2012 at 13:38

3 Answers 3

27
$\begingroup$

J. M. has shown you a workaround using ArrayRules and as others mentioned, using Conjugate is more prudent. However, to answer your primary question — "Why doesn't ReplaceAll work on SparseArray?", it is because SparseArray is atomic.

In other words, SparseArray objects are "indivisible" and the data contained in them can only be accessed in specific ways (e.g., using undocumented arguments to SparseArray) and not by manipulating its FullForm. You can verify that it is indeed atomic, whereas a regular matrix is not:

AtomQ@m
(* True *)

AtomQ@Normal@m
(* False *)

A similar situation arises with Graph objects, which are also atomic. For instance, the following will not work:

Graph[{1 -> 2, 2 -> 3, 2 -> 4}] /. DirectedEdge -> UndirectedEdge

even though // FullForm will show the presence of DirectedEdge in the structure. Hence it is important for you to know which objects are atomic before you try (unsuccessfully) to use replacement rules on them.

To the best of my knowledge, the list of atomic objects (not including undocumented ones) include those with the following heads:

{Symbol, String, Integer, Real, Rational, Complex, SparseArray,
 BooleanFunction, Graph}
$\endgroup$
1
  • $\begingroup$ Thanks, that's very useful to know. $\endgroup$
    – matimo2
    Oct 28, 2012 at 14:24
31
$\begingroup$

Use ArrayRules[]:

m = SparseArray[{2, 2} -> i];
mc = SparseArray[ArrayRules[m] /. i -> -i, Dimensions[m]];
MatrixForm[mc]

$\begin{pmatrix}0&0\\0&-\mathtt{i}\end{pmatrix}$

$\endgroup$
8
$\begingroup$

Updated to work in M13

Issue

Consider the internal structure of a SparseArray object. For example:

s = SparseArray[{1, 0, 1, 0}];
s //InputForm

SparseArray[Automatic, {4}, 0, {1, {{0, 2}, {{1}, {3}}}, {1, 1}}]

Clearly doing something like:

s /. 1 -> 3

should not convert the SparseArray object into:

SparseArray[Automatic, {4}, 0, {3, {{0, 2}, {{3}, {3}}}, {3, 3}}]

which is not even a valid SparseArray object (the above replacement is prevented because SparseArray objects are atomic). Instead, to work properly, ReplaceAll needs to do a replacement on the equivalent Normal version. Getting this to work right is not easy, which is most likely why it doesn't work. There is too much variety in the patterns and levels that can be used in a replacement.

By the way, it is possible to do replacements of entire SparseArray objects, which is very convenient, as will be seen below

That doesn't mean that the only way to do a replacement on a SparseArray is to use Normal or ArrayRules and then convert back, though. Assuming you just want to apply a replacement rule on the elements of a SparseArray object, here are two possibilities.

SparseArray iterator

If you have a 1-D vector sparse array, you could use a SparseArray iterator. For example, here is a function that uses a SparseArray iterator to do replacements of a vector SparseArray:

VectorReplaceElement[s_SparseArray, rule_] := Table[Replace[i, rule], {i, s}]

The key here is that rule is applied to each element of the SparseArray, that is, the level of the replacement is constrained. Applying this function to my initial example:

r = VectorReplaceElement[s, 1 -> 3];
r //OutputForm
% //Normal

SparseArray[<2>, {4}]

{3, 0, 3, 0}

We see that the replacement has happened as desired. Note that the speed depends on the number of nonzero elements in the SparseArray. For example:

s = SparseArray[Thread[2^Range[40]->1]];
s //OutputForm

SparseArray[<40>, {1099511627776}]

It would require a computer with many TB of memory to be able to convert this SparseArray to a normal matrix.

new = VectorReplaceElement[s, 1->3]; //MaxMemoryUsed //AbsoluteTiming
new["NonzeroValues"]

{0.000049, 2360}

{3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}

Clearly the replacement worked, and the SparseArray was not converted to a normal matrix in order to do the replacement. This method doesn't work as well for higher rank SparseArray objects.

Structural replacement

The other possibility is to extract the nonzero elements, and the default element, apply the replacement rule, and then reconstruct the SparseArray. Here is a function to do this (updated to work with M13):

ReplaceElement[s_SparseArray, rule_] := With[
    {
    elem = Replace[s["NonzeroValues"], rule, {1}],
    def = Replace[s["Background"], rule]
    },
    
    SparseArray[
        Automatic, 
        s["Dimensions"], 
        def, 
        {1, {s["RowPointers"], s["ColumnIndices"]}, elem}
    ]
]

Again, this approach does not convert the SparseArray to a normal matrix:

new = ReplaceElement[s, 1->3]; //MaxMemoryUsed //AbsoluteTiming
new //OutputForm
new["NonzeroValues"]

{0.000073, 4016}

SparseArray[<40>, {1099511627776}]

{3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}

And it works fine with higher rank SparseArray objects:

s = RandomInteger[1, {10, 10}] //SparseArray;
new = ReplaceElement[s, 1->10];
new //OutputForm
new["NonzeroValues"]

SparseArray[<54>, {10, 10}]

{10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10}

$\endgroup$
2
  • $\begingroup$ The structural replacement no longer works in Mma 13. The best I could do is SparseReplaceAll[s_SparseArray, rule_] := SparseArray[s["ExplicitPositions"] -> Replace[s["ExplicitValues"], rule, Infinity], Dimensions@s, s["ImplicitValue"] /. rule]. I don't know if that's optimized, though. $\endgroup$ Apr 22, 2022 at 23:40
  • $\begingroup$ @SimonRochester Thanks for letting me know. I updated to work in M13. $\endgroup$
    – Carl Woll
    Apr 23, 2022 at 0:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.