3
$\begingroup$

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.

New contributor
Daniel Pozo is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
$\endgroup$
3
  • 2
    $\begingroup$ Add 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 avoid AppendTo as this can be slow. $\endgroup$ Commented yesterday
  • 3
    $\begingroup$ You should also make a Module inside Manipulate and use local variables. All your variables are now global. i.e. do Manipulate[ Module[{a,b,c}, .... ], controls, TrackedSymbols->{},....] where {a,b,c} are local module variables (not control variables). $\endgroup$ Commented yesterday
  • 2
    $\begingroup$ I suggest putting all the procedural stuff in an external function if you don't want to localize variables or avoid 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 the Initialization option, it will always be available in the Manipulate[]. $\endgroup$ Commented yesterday

1 Answer 1

1
$\begingroup$

It is good that you posted the whole working code. In this way you already have and will get many feedback. It is better for your training if you refactor your code on your own. Here I propose an algorithm how you can proceed.

Outside of the Manipulate, define a hSSH function

hSSH[J1_, J2_, DP_, A_, B_, s_, True] := Module[{i, j},....]
hSSH[J1_, J2_, DP_, A_, B_, s_, False] := Module[{i, j},....]

and copy in two blocks of code from the If[Defect == True,...,...] construct. These Hamiltonians can be optimised later (see this solution).

Notice 1) that hSSH always yields a 3-diagonal matrix; 2) your time propagation is a free evolution.

There are many ways to do propagation. But the best in your concrete case (Hamiltonian is sparse and time constant) is probably to stay with the ODE approach, and to avoid MatrixExp. Therefore formulate your differential equation

rhs[h_, psi_?ArrayQ]:= I h. psi;
h= hSSH[...];
eq = D[psi[t],t]== rhs[h, psi];
ic = psi[0]==psi0;

Use then NDSolve. It is much more efficient than coding the RK method on your own.

y = NDSolveValue[{eq, ic}, psi, {t, 0, tmax}]

Finally tabulate your y[t] for MatrixPlot. Once you implement these steps, we can help you further.

$\endgroup$

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.