I have written a photonic-lattice simulation in Mathematica using a Manipulate. The code constructs an SSH-type Hamiltonian (with optional defect), computes its spectrum, and optionally performs time evolution of a localized excitation using a high-order Butcher Runge–Kutta method.
The problem is that:
The Manipulate keeps evaluating even when I am not changing any controls. The output flickers or recomputes repeatedly even though nothing is happening.
The evaluation is extremely slow whenever I change any parameter, because the entire Hamiltonian construction, diagonalization, matrix plots, and the full Runge–Kutta time evolution are recomputed every time a control changes — even when the change logically affects only a small part of the computation.
Manipulate[
H = Table[0, {s}, {s}];
If[Defect == True,
For[i = 1, i < s + 1, i++,
For[j = 1, j < s + 1, j++,
If[Abs[i - j] == 1,
H[[i, j]] = J2;
If[(i + j) < 2 DP,
If[(i - j == 1 && EvenQ[j]) || (i - j == -1 && EvenQ[i]),
H[[i, j]] = J2, H[[i, j]] = J1];
];
If[(i + j) > 2 DP,
If[(i - j == 1 && EvenQ[i]) || (i - j == -1 && EvenQ[j]),
H[[i, j]] = J2, H[[i, j]] = J1];
];
];
If[i == j ,
If[i < DP,
If[EvenQ[i], H[[i, j]] = B, H[[i, j]] = A]
];
If[i > DP,
If[EvenQ[i], H[[i, j]] = A, H[[i, j]] = B]
];
];
If[i == j == DP, H[[i, j]] = B];
];
];
,
For[i = 1, i < s + 1, i++,
For[j = 1, j < s + 1, j++,
If[Abs[i - j] == 1 ,
If[(i - j == 1 && EvenQ[j]) || (i - j == -1 && EvenQ[i]),
H[[i, j]] = J2, H[[i, j]] = J1];
];
If[i == j , If[EvenQ[i], H[[i, j]] = B, H[[i, j]] = A]];
]
];
];
eig = N[Eigensystem[H]];
fig1 = MatrixPlot[H, ColorFunction -> "BlueGreenYellow"];
fig2 = ListPlot[Sort[eig[[1, ;;]], Less], PlotRange -> {-PR, PR}];
fig3 = MatrixPlot[eig[[2, ;;]][[Ordering[eig[[1, ;;]]]]],
ColorFunction -> "BlueGreenYellow", DataReversed -> True];
If[Excite == True,
\[CapitalDelta]t = tmax/n;
\[Psi] = ConstantArray[0, s];
\[Psi][[EP]] = 1;
tevo = {\[Psi]};
For[i = 1, i < n, i++,
k1 = -I Transpose[H . Transpose[{\[Psi]}]][[1]];
k2 = -I Transpose[
H . Transpose[{\[Psi] + 1/4 k1 \[CapitalDelta]t}]][[1]];
k3 = -I Transpose[
H . Transpose[{\[Psi] + 1/8 k1 \[CapitalDelta]t +
1/8 k2 \[CapitalDelta]t}]][[1]];
k4 = -I Transpose[
H . Transpose[{\[Psi] - 1/2 k2 \[CapitalDelta]t +
k3 \[CapitalDelta]t}]][[1]];
k5 = -I Transpose[
H . Transpose[{\[Psi] + 3/16 k1 \[CapitalDelta]t +
9/16 k4 \[CapitalDelta]t}]][[1]];
k6 = -I Transpose[
H . Transpose[{\[Psi] - 3/7 k1 \[CapitalDelta]t +
2/7 k2 \[CapitalDelta]t + 12/7 k3 \[CapitalDelta]t +
12/7 k4 \[CapitalDelta]t + 8/7 k5 \[CapitalDelta]t}]][[1]];
\[Psi] = \[Psi] + \[CapitalDelta]t/
90 (7 k1 + 32 k3 + 12 k4 + 32 k5 + 7 k6);
AppendTo[tevo, \[Psi]];
];
fig4 =
MatrixPlot[Abs[Transpose[tevo]]^2,
ColorFunction -> "BlueGreenYellow"];
GraphicsColumn[
{GraphicsRow[{fig1, fig2, fig3}, ImageSize -> Large],
GraphicsRow[{fig4}, ImageSize -> Large]}
]
,
GraphicsColumn[
{GraphicsRow[{fig1, fig2, fig3}, ImageSize -> Large]}
]
],
{{Defect, False}, {True, False}},
{{Excite, False}, {True, False}},
{{PR, 3}, 1, 10, Appearance -> {"Open"}},
{{s, 20}, 5, 41, 1, Appearance -> {"Open"}},
{{J1, 2}, 0, 10, Appearance -> {"Open"}},
{{J2, 1}, 0, 10, Appearance -> {"Open"}},
{{A, 0.5}, -2, 2, Appearance -> {"Open"}},
{{B, -0.5}, -2, 2, Appearance -> {"Open"}},
{{DP, IntegerPart[s/2] + 1}, 3, s - 1, 2, Appearance -> {"Open"}},
{{EP, DP}, 1, s, 1, Appearance -> {"Open"}},
{{tmax, 3}, 1, 6, Appearance -> {"Open"}},
{{n, 200}, 10, 1000, 10, Appearance -> {"Open"}}
]
My questions
How can I prevent Manipulate from reevaluating everything unnecessarily? I suspect that I should use TrackedSymbols, ContinuousAction -> False, SynchronousUpdating -> False, or restructure the code using Dynamic, but I'm not sure what the best practice is.
How can I optimize the code so the Hamiltonian and the time evolution are not rebuilt every time? For example:
Is there a better way to construct the tridiagonal Hamiltonian than using nested For loops?
Is AppendTo inside the time-evolution loop slowing things down?
Should I replace the step-by-step Runge–Kutta with a more efficient method (e.g. precomputing the unitary matrix MatrixExp[-I H dt] or using NestList)?
In general, what are recommended strategies for making Manipulate fast when the internal computation is heavy (e.g. matrix diagonalization + time evolution)?
Any suggestions on restructuring the code for efficiency, or examples of best practice for heavy-computation Manipulates, would be very helpful.
TrackedSymbols :> {PR, J1, A, DP, tmax, n, s, J2, B, EP}. This will prevent flickring and may be speed it up also. (i.e. make sure all your controls are in the the above list in case I missed some. Also try to avoidAppendToas this can be slow. $\endgroup$Manipulate[ Module[{a,b,c}, .... ], controls, TrackedSymbols->{},....]where{a,b,c}are local module variables (not control variables). $\endgroup$For[]. (Generally speaking, an unlocalized variable is a bug waiting to happen, so you probably should localize them.) All the parameters will need to be arguments to the function. If you put the function in theInitializationoption, it will always be available in theManipulate[]. $\endgroup$