public static Animation CreateCircuitAnimation(double span,
                                                       TimeSpan duration, 
                                                       IReadOnlyList<CircuitOperationWithStyle> ops, 
                                                       IReadOnlyList<CircuitInputWithStyle> ins, 
                                                       string desc, 
                                                       IReadOnlyList<string[][]> wireLabels = null, 
                                                       string[] stateLabels = null, 
                                                       bool showMatrix = true, 
                                                       double wireSpace = 80)
        {
            var matrixFill = (Brush)Brushes.Orange.LerpToTransparent(0.5);

            var vals = ins.Select(e => ops.Stream(e.Value, (a, x) => x.Operation*a, streamSeed: true).ToArray()).ToArray();

            var numOp = ops.Count;
            var numIn = ins.Count;
            var maxIn = numOp + 1;
            var numState = ins.First().Value.Values.Count;
            var numWire = (int)Math.Round(Math.Log(numState, 2));
            var matrixWidth = 150.0;
            var cellRadius = matrixWidth / (vals[0][0].Values.Count + 2) / 2;
            var matrixRadius = matrixWidth / 2;
            var opXs = numOp.Range().Select(i => -cellRadius+span*(i+0.75)/maxIn).ToArray();

            var sweepX = Ani.Anon(t => new Point(t.DividedBy(duration).LerpTransition(-cellRadius * 2, span - cellRadius * 2), 0).Sweep(new Vector(0, 1000)));
            var opTs = opXs.Select(x => new {s = duration.Times((x - matrixRadius + cellRadius*2)/span), f = duration.Times((x + matrixRadius)/span)}).ToArray();

            var wireYs = numWire.Range().Select(i => numWire == 1 ? 20 + wireSpace/2 : (wireSpace / 2 + i * wireSpace / 2 / (numWire - 1))).ToArray();
            var animation = new Animation {
                // top description
                new TextDesc(desc,
                             new Point(span/2.0 - (numOp == 1 ? cellRadius : 0), 5),
                             new Point(0.5, 0),
                             fontSize: 15,
                             foreground: Brushes.Gray),
                // wires
                wireYs.Select(y => new LineSegmentDesc(new Point(0, y).Sweep(new Vector(span+300, 0))))
            };
            if (showMatrix) {
                // static matrices
                animation.Add(
                    numOp.Range()
                         .Select(i => ShowMatrix(
                             new Rect(opXs[i] - matrixRadius, wireSpace+20, matrixRadius*2, matrixRadius*2),
                             ops[i].Operation,
                             matrixFill,
                             Brushes.Black)));
            }

            var offsetTimelines =
                ins.Count.Range()
                     .Select(
                         i => animation
                                  .Dilated(
                                      1.Seconds(),
                                      -duration.DividedBy(ins.Count).Times(i))
                                  .Periodic(duration))
                     .ToArray();

            // sweep line
            foreach (var p in offsetTimelines) {
                p.Add(new LineSegmentDesc(sweepX, Brushes.Red, 0.5, 4));
            }

            // state labels
            var tts = opTs.Select(e => e.s.LerpTo(e.f, 0.4));
            var wd = numIn.Range().Select(inid => numWire.Range().Select(wid => (numOp + 1).Range().Select(tid => {
                var label = wireLabels == null ? new[] {"On", "Off"} : wireLabels[wid][tid];
                var p = numState.Range().Where(i => ((i >> wid) & 1) == 0).Select(i => vals[inid][tid].Values[i].SquaredMagnitude()).Sum();
                if (p < 0.001) return label[1];
                if (p < 0.01) return string.Format("~{0}", label[1]);
                if (p < 0.49) return string.Format("~{0}:{1:0}%", label[1], (1 - p) * 100);

                if (p > 0.999) return label[0];
                if (p > 0.99) return string.Format("~{0}", label[0]);
                if (p > 0.51) return string.Format("~{0}:{1:0}%", label[0], p * 100);

                return string.Format("{0}/{1}", label[0], label[1]);
            }).ToArray()).ToArray()).ToArray();
            foreach (var i in numIn.Range()) {
                foreach (var j in numWire.Range()) {
                    animation.Add(
                        new TextDesc(
                            Ani.Anon(t => (t + duration.DividedBy(numIn).Times(i)).Mod(duration))
                               .Select(t => wd[i][j][tts.TakeWhile(c => t >= c).Count()]),
                            Ani.Anon(
                                t =>
                                new Point((t.DividedBy(duration) + i * 1.0 / numIn).ProperMod(1).LerpTransition(-cellRadius * 2, span - cellRadius * 2), wireYs[j]))));
                }
            }

            // matrix multiplications
            if (showMatrix) {
                foreach (var i in numIn.Range()) {
                    foreach (var j in numOp.Range()) {
                        var p = offsetTimelines[i].LimitedNewTime(opTs[j].s, opTs[j].f);
                        var r = new Rect(opXs[j] - matrixRadius, wireSpace + 20, matrixRadius * 2, matrixRadius * 2);
                        p.Add(
                            ShowMatrixMultiplication(
                                opTs[j].f - opTs[j].s,
                                r,
                                ops[j].Operation,
                                vals[i][j],
                                matrixFill,
                                ins[i].Color,
                                Brushes.Black));
                    }
                }
            }

            // pushed values
            if (showMatrix) {
                foreach (var i in numIn.Range()) {
                    foreach (var s in maxIn.Range()) {
                        var p = offsetTimelines[i].LimitedSameTime(s == 0 ? 0.Seconds() : opTs[s - 1].f, s == maxIn - 1 ? duration : opTs[s].s);
                        p.Add(numState.Range().Select(
                            j => ShowComplex(
                                ins[i].Color,
                                Brushes.Black,
                                vals[i][s].Values[j],
                                sweepX.Select(e => new Point(e.LerpAcross(0.3).X + cellRadius, wireSpace + 20 + cellRadius) + new Vector(0, j * cellRadius * 2)),
                                cellRadius)));
                    }
                }
            }

            // state labels
            if (showMatrix) {
                wireLabels = wireLabels ?? new[] {"On", "Off"}.Repeat(maxIn).ToArray().Repeat(numWire);
                stateLabels = stateLabels
                              ?? numState.Range().Select(
                                  i => numWire.Range().Select(
                                      w => maxIn.Range().Select(
                                          inid => wireLabels[w][inid][(i >> w) & 1]
                                               ).Distinct().StringJoin("")
                                           ).StringJoin(",")
                                     ).ToArray();
                foreach (var i in numState.Range()) {
                    animation.Add(
                        new TextDesc(
                            stateLabels[i] + ":",
                            new Point(0, wireSpace + 20 + cellRadius + i*2*cellRadius),
                            new Point(0, 0.5)));
                }
            }

            // circuit diagram
            foreach (var i in numOp.Range()) {
                var op = ops[i];
                var h = (numWire == 1 ? 50 : op.MaxWire - op.MinWire == numWire - 1 ? wireSpace*0.80 : (wireYs[1] - wireYs[0]) * (op.MaxWire - op.MinWire + 1)) * 0.9;
                var r = new Rect(
                    opXs[i] - op.Width/2,
                    (wireYs[op.MaxWire] + wireYs[op.MinWire] - h)/2,
                    op.Width,
                    h);
                foreach (var c in op.ConditionedWires ?? new int[0]) {
                    animation.Add(
                        new LineSegmentDesc(
                            new Point(opXs[i], wireYs[op.MaxWire]).To(new Point(opXs[i], wireYs[c])),
                            Brushes.Black,
                            1));
                    animation.Add(
                        new PointDesc(
                            new Point(opXs[i], wireYs[c]),
                            fill: Brushes.Black,
                            radius: 3));
                }
                animation.Add(
                    new RectDesc(
                        r,
                        Brushes.Black,
                        Brushes.White,
                        1.0));
                animation.Add(
                    new TextDesc(
                        op.Description,
                        r.TopLeft + new Vector(r.Width, r.Height) / 2,
                        new Point(0.5, 0.5),
                        fontWeight: FontWeights.ExtraBold,
                        fontSize: 20));
                if (op.WireHints != null) {
                    foreach (var j in op.WireHints.Length.Range()) {
                        animation.Add(
                            new TextDesc(
                                op.WireHints[j],
                                new Point(r.Left+5, wireYs[op.MinWire + j]),
                                new Point(0, 0.5),
                                foreground: Brushes.Gray,
                                fontSize: 10));
                    }
                }
            }

            return animation;
        }