Multiply Pauli Matrices
Python 2, 108 89 87 86 bytes
x=y=0
for m in map(int,raw_input()):x+=m*y and(my)%3*3/2;y^=m
print"i"[~x%4::2]+`y`
(Thanks to @grc and @xnor for the help)
Explanation
Let's split up the coefficient and the base matrix. If we focus on the base matrix only, we get this multiplication table (e.g. 13
is i2
, so we put 2
):
0123
0 0123
1 1032
2 2301
3 3210
which just happens to be the same thing as doing bitwise xor.
Now let's focus on the coefficients. If we let 0123
denote 1,i,1,i
respectively, we get:
0123
0 0000
1 0031
2 0103
3 0310
For this we first check if either number is 0 by doing m*y
, taking care of the left column and top row. Adding in (my)%3
then gives:
0123
0 0000
1 0021
2 0102
3 0210
which is close, except that we have 2
instead of 3
. We account for this by performing *3/2
.
For indexing, we notice that if we take the string "i"
and select every second character starting from indices 0123
we get "i","","i",""
.
Retina, 77 bytes
I thought I'd use this opportunity to show off a new Retina feature: multistage loops. This should shorten many tasks considerably (especially conditional replacement).
ii

+`(.)\10
(.)(\d)(\d)
$1$3$2
12
i3
23
i1
31
i2
)`(\d)i
i$1
^\D*$
$&0
Retina is my own regexbased programming language. The source code can be grouped into stages: each stage consists of two lines where the first contains the regex (and potentially some configuration) and the second line is the replacement string. The stages are then applied to STDIN in order and the final result is printed to STDOUT.
You can use the above directly as a source file with the s
commandline switch. However, I'm not counting the switch, because you can also just put each line in a separate file (then you lose 15 bytes for the newlines, but add +15 for the additional files).
Explanation
The new thing about this solution is the )
in the penultimate stage. This closes a multistage loop. There is no matching (
, which means that the loop implicitly starts at the first stage. Hence, the first 7 stages are repeated until a full pass through all 7 of them stops changing the result. These 7 stages simply perform various transformations which gradually reduce the number of matrices in the string and combine phases. Once we reach the final result, none of the seven patterns matches any more and the loop ends. Afterwards, we append a 0 if there is no digit in the result yet (as the above stages simply drop all identities, including the result).
Here is what the individual stages do:
ii

Combines all pairs of i
into 
to reduce the phase characters.
+`(.)\10
<empty>
Now if there are two consecutive identical characters left, it's either 
or two identical matrices. In either case, multiplying them gives the identity. But we don't need identities, so we just remove all of them, and the explicit identities (the 0
s) as well. This stage is repeated in itself with +
until the result stops changing. This ensures that things like 123321
get resolved completely, such that the next step can assume that all pairs of digits are distinct.
(.)(\d)(\d)
$1$3$2
This is actually two separate transformations in one (for golfitude). Note that if the first alternative matches, $2
and $3
are empty, and if the second one matches $1
is empty. So this can be decomposed into these two steps:
(\d)(\d)
$2$1
This just swaps all pairs of digits and adds a minus sign. Since we removed all 0
s and all identical pairs, this will only match 12
, 23
, 31
, 21
, 32
, 13
. This step may seem weird, but it allows me to only check for half of these cases later on, because the ones I can't process then will be swapped here in the next iteration.
The other part of the above stage was:
(.)
$1
This gradually moves 
signs all the way to the left (one position per iteration). I do this such that ultimately they are all next to each other and get resolved in the earlier step.
12
i3
23
i1
31
i2
These three stages now simply resolve the three pairs of products. As I said above, this will only catch half of the relevant cases, but the other half will be taken care of in the next iteration, after the previous step swapped all pairs.
)`(\d)i
i$1
This is the last stage of the loop. It's similar to the one that shifts 
to the left, except for i
. The main difference is that this one swaps i
only with digits. If I used (.)i
then in cases where I get a i
or i
the two would be swapped indefinitely and the program wouldn't terminate. So this only swaps them to the right of the 
signs. This is sufficient  as long as all 
and i
appear together at some point, they can be resolved correctly.
^\D*$
$&0
The final step (outside of the loop). Remember that we always deleted all identities, so if the result is actually the identity (times a phase), then we won't have the required digit in the output any more, so we add it back.
As an example, here are all the intermediate forms of 0223202330203313021301011023230323
(skipping stages that don't perform any changes):
0223202330203313021301011023230323
321321312 # Remove identities
233112132 # Swap all pairs
2331i3132 # Resolve 12
i131i3132 # Resolve 23
i1i2i3132 # Resolve 31
i1i2i3312 # Move  to the left and swap pairs
i1i2i33i3 # Resolve 12
ii1i23i33 # Move i to the left
ii1i23i # Remove identities
ii1i23i # Move  to the left
iii12i3 # Move i to the left
i12i3 # Resolve ii
i12i3 # Remove identities
i12i3 # Move  to the left
i1i23 # Move i to the left
i1i32 # Move  to the left and swap pairs
ii132 # Move i to the left
ii123 # Move  to the left and swap pairs
ii1i1 # Resolve 23
1i1 # Resolve ii
1i1 # Remove identities
1i1 # Move  to the left
i11 # Move i to the left
i # Remove identities. Now the loop can't change this any longer.
i0 # Fix the result by adding in the 0.
CJam, 58 56 bytes
I am sure this can be golfed a lot, but here it goes:
L'i'"i"]q{+:G$i:XA<X{GiB%_2m8%!+m<XB%gX3%3z*}?s}*\0=o
Try it online here or run the complete suite here