# Debugging and decision support tools for Dynamic Programming (cached definitions) in Mathematica

I'm using the Quantum Computing library with this example:

Needs["QuantumComputing"];
SetComputingAliases[];
m[a_] = {{Cos[a], -I Sin[a]}, {-I Sin[a], Cos[a]}};
mq = m[myangle] //MatrixQuantum
(* Fails both times *)
PauliExpand[mq]
Assuming[myangle ∈ Reals, PauliExpand[mq]]


Error is: PauliExpand::nonunit:: PauliExpand can only expand unitary operators

This happens because a function within the Quantum Computing library called unitaryQ uses Dynamic Programming to cache results. If it's evaluated without assumptions first then that result sticks around. Further evaluations replay the cached definition, so adding assumptions later that would allow the function to evaluate successfully does not help.

Conversely, if the kernel is reset and the functions are run this way round, the expected results are returned:

(*Succeeds both times*)
Assuming[myangle ∈ Reals, PauliExpand[mq]]
PauliExpand[mq]


Here's the part of the library in Computing.m that implements the unitaryQ function:

(* Notice the delayed assignment *)
$QuantumMaxUnitaryDefinitions:=$QuantumMaxCachedDefinitions;
uStoredDef=0;
autoresetUnitaryQ[]:=
Module[{},
If[uStoredDef>\$QuantumMaxUnitaryDefinitions,
resetUnitaryQ[] ];
uStoredDef++
];

resetUnitaryQ[]:=
Module[{},
Clear[unitaryQ];
uStoredDef=0;
unitaryQ[m_List?MatrixQ] :=
( autoresetUnitaryQ[];
unitaryQ[m] = (* dynamic programing *)
Simplify[Conjugate@Transpose@m.m ==IdentityMatrix@Length@m] );
];

(* First Time: *)
resetUnitaryQ[];


My question is: are there any support functions to help peek inside a module and see any cached assignments and perhaps clear them? Or is it all locked away as an implementation detail and the only option is a kernel reset if an undesirable definition is cached?

Additionally, are there any patterns that could be followed in the unitaryQ definition (which would require changes to the library) that would either:

• Take assumptions into account when caching definitions, allowing the two calls in my code to be evaluated differently.

or

• Only cache a definition if the function evaluates successfully. This would not resolve issues if different assumptions were specified.

?

A brute-force approach would be to access the private package function used to reset the values of unitaryQ, by executing QuantumComputingPrivateresetUnitaryQ[] before your Assuming[...] code.

This will erase the cached values of unitaryQ, thereby allowing you to get a result using your assumptions:

(* Your initializations *)
Needs["QuantumComputing"];
SetComputingAliases[];
m[a_] = {{Cos[a], -I Sin[a]}, {-I Sin[a], Cos[a]}};
mq = m[myangle] // MatrixQuantum

(* this fails because it is missing assumptions *)
PauliExpand[mq]

(* this removes the cached values *)
QuantumComputingPrivateresetUnitaryQ[]
Assuming[myangle ∈ Reals, PauliExpand[mq]]


The memoization is probably there for a purpose, though, in order to speed up computations down the line. So perhaps a less damaging alternative might be to reset the downvalues of unitaryQ to retain only those that do not contain your current input:

QuantumComputingPrivateresetUnitaryQ[]
PauliExpand[mq] (*fails because of the missing assumptions *)


At this point, unitaryQ[mq] has been cached as unevaluated. You can check by listing its DownValues:

DownValues@QuantumComputingPrivateunitaryQ[]

(* Out:
{HoldPattern[
QuantumComputingPrivateunitaryQ[{{Cos[
myangle], -I Sin[myangle]}, {-I Sin[myangle],
Cos[myangle]}}]] :> {{SuperStar[Cos[myangle]] Cos[myangle] +
SuperStar[Sin[myangle]] Sin[myangle],
I (SuperStar[Sin[myangle]] Cos[myangle] -
SuperStar[Cos[myangle]] Sin[myangle])}, {I (SuperStar[
Sin[myangle]] Cos[myangle] -
SuperStar[Cos[myangle]] Sin[myangle]),
SuperStar[Cos[myangle]] Cos[myangle] +
SuperStar[Sin[myangle]] Sin[myangle]}} == {{1, 0}, {0, 1}},
HoldPattern[
QuantumComputingPrivateunitaryQ[
QuantumComputingPrivatem_List?
MatrixQ]] :> (QuantumComputingPrivateautoresetUnitaryQ[];
QuantumComputingPrivateunitaryQ[QuantumComputingPrivatem] =
Simplify[
SuperStar[
Transpose[
QuantumComputingPrivatem]].QuantumComputingPrivatem ==
IdentityMatrix[Length[QuantumComputingPrivatem]]])}
*)


We can remove the offending cached value by manually resetting the DownValues of unitaryQ:

DownValues[QuantumComputingPrivateunitaryQ] = DeleteCases[
DownValues[QuantumComputingPrivateunitaryQ],
_?(Not[FreeQ[#, myangle]] &)
];

Assuming[myangle ∈ Reals, PauliExpand[mq]]


Finally, you could try disabling the memoization by modifying the package code, to see what an impact that would make on your calculations. If the impact is minimal, removing the memoization altogether might be the easiest and most permanent alternative.

You would do so by modifying the definition of unitaryQ in the Computing.m file inside the Quantum package directory as follows, saving and restarting Mathematica:

resetUnitaryQ[]:=
Module[{},
Clear[unitaryQ];
uStoredDef=0;
unitaryQ[m_List?MatrixQ] :=
( autoresetUnitaryQ[];
(* commenting the following out disables the memoization *)
(*  unitaryQ[m] = *)
Simplify[Conjugate@Transpose@m.m ==IdentityMatrix@Length@m] );

• Thanks for this excellent answer. When I executed "?unitaryQ" it gave me: "GlobalunitaryQ". So, I tried executing it under package Global and tried under package QuantumComputing but didn't think / know about "private" QuantumComputingPrivate. Great info about the DownValues. I understood these once but need to revisit them. With regard to the library, I'd rather leave it alone just for compatibility's sake. I can easily; work around the issue now I understand it. Just need some tools to help and now I have them ;) – David B Jul 30 '17 at 20:06
• One last thing is that I wonder whether this complication with Assumptions and memoization is common and whether there's a design pattern to apply to avoid it. – David B Jul 30 '17 at 20:07
• @DavidB Personally I find that memoization as implemented here is on the dangerous side, as you have experienced yourself. As written, the unitaryQ function simply returned unevaluated in your case without assumptions, and that unevaluated expression got saved as the cached value. This in turn triggered the downstream function PauliExpand to abort. In my opinion, the package code should check that an actual result is returned by unitaryQ` before caching it. That's obviously more complicated, but it would be that much more robust. – MarcoB Jul 30 '17 at 20:58