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; }