Closed anedumla closed 1 year ago
Yes. Optimization level 3 gives (for 15 steps):
global phase: 2π
q_0 -> 0 ─────────
┌───────┐
q_1 -> 1 ┤ Rz(2) ├
├───────┤
q_2 -> 2 ┤ Rz(2) ├
└───────┘
ancilla_0 -> 3 ─────────
ancilla_1 -> 4 ─────────
ancilla_2 -> 5 ─────────
where as 2 gives something much longer, a small snippet of which is:
global phase: π/2
┌─────────┐ ┌────┐┌─────────┐ »
q_0 -> 0 ─┤ Rz(π/2) ├──┤ √X ├┤ Rz(π/2) ├──■────────────────────────────»
├─────────┤ ├────┤├─────────┤┌─┴─┐┌────────────────────────┐»
q_1 -> 1 ─┤ Rz(π/2) ├──┤ √X ├┤ Rz(π/2) ├┤ X ├┤ Rz(0.0666666666666667) ├»
┌┴─────────┴─┐├────┤├─────────┤└───┘└────────────────────────┘»
q_2 -> 2 ┤ Rz(1.6375) ├┤ √X ├┤ Rz(π/2) ├───────────────────────────────»
└────────────┘└────┘└─────────┘ »
It is independent of the routing method, so something else is breaking.
Do we know yet if this is due to changes in optimization_level=3
after 0.18.3? I wasn't able to directly replicate the example on 0.18.3 because it use e.g. PauliEvolutionGate
which is new with 0.19.
Here is the raw circuit used above:
Collect2qBlocks
/ConsolidateBlocks
/UnitarySynthesis
is greatly simplifying these circuits here.
def cb(pass_, dag, **kwargs):
print(type(pass_).__name__, dag.count_ops())
qct3 = transpile(qc, backend, initial_layout=layout, optimization_level=3, callback=cb)
Going from 268 cx
to 31 unitary
to 0 entangling gates....
UnitarySynthesis {'h': 3, 'circuit-2402': 1, 'measure': 3}
Unroll3qOrMore {'h': 3, 'measure': 3, 'rzz': 45, 'rxx': 45, 'rz': 45, 'ryy': 45}
RemoveResetInZeroState {'h': 3, 'measure': 3, 'rzz': 45, 'rxx': 45, 'rz': 45, 'ryy': 45}
OptimizeSwapBeforeMeasure {'h': 3, 'measure': 3, 'rzz': 45, 'rxx': 45, 'rz': 45, 'ryy': 45}
RemoveDiagonalGatesBeforeMeasure {'h': 3, 'measure': 3, 'rzz': 44, 'rxx': 45, 'rz': 44, 'ryy': 45}
SetLayout {'h': 3, 'measure': 3, 'rzz': 44, 'rxx': 45, 'rz': 44, 'ryy': 45}
FullAncillaAllocation {'h': 3, 'measure': 3, 'rzz': 44, 'rxx': 45, 'rz': 44, 'ryy': 45}
EnlargeWithAncilla {'h': 3, 'measure': 3, 'rzz': 44, 'rxx': 45, 'rz': 44, 'ryy': 45}
ApplyLayout {'h': 3, 'rxx': 45, 'ryy': 45, 'rzz': 44, 'rz': 44, 'measure': 3}
CheckMap {'h': 3, 'rxx': 45, 'ryy': 45, 'rzz': 44, 'rz': 44, 'measure': 3}
UnitarySynthesis {'h': 3, 'rxx': 45, 'ryy': 45, 'rzz': 44, 'rz': 44, 'measure': 3}
UnrollCustomDefinitions {'h': 3, 'rxx': 45, 'ryy': 45, 'rzz': 44, 'rz': 44, 'measure': 3}
BasisTranslator {'rz': 1084, 'measure': 3, 'sx': 543, 'cx': 268}
RemoveResetInZeroState {'rz': 1084, 'measure': 3, 'sx': 543, 'cx': 268}
Depth {'rz': 1084, 'measure': 3, 'sx': 543, 'cx': 268}
FixedPoint {'rz': 1084, 'measure': 3, 'sx': 543, 'cx': 268}
vvvv
Collect2qBlocks {'rz': 1084, 'measure': 3, 'sx': 543, 'cx': 268}
ConsolidateBlocks {'measure': 3, 'unitary': 31}
UnitarySynthesis {'measure': 3, 'rz': 93, 'sx': 61}
^^^^
Optimize1qGatesDecomposition {'measure': 3, 'rz': 6, 'sx': 3}
CommutationAnalysis {'measure': 3, 'rz': 6, 'sx': 3}
CommutativeCancellation {'measure': 3, 'rz': 6, 'sx': 3}
UnitarySynthesis {'measure': 3, 'rz': 6, 'sx': 3}
UnrollCustomDefinitions {'measure': 3, 'rz': 6, 'sx': 3}
BasisTranslator {'measure': 3, 'rz': 6, 'sx': 3}
Depth {'measure': 3, 'rz': 6, 'sx': 3}
FixedPoint {'measure': 3, 'rz': 6, 'sx': 3}
Collect2qBlocks {'measure': 3, 'rz': 6, 'sx': 3}
ConsolidateBlocks {'measure': 3, 'rz': 6, 'sx': 3}
UnitarySynthesis {'measure': 3, 'rz': 6, 'sx': 3}
Optimize1qGatesDecomposition {'measure': 3, 'rz': 6, 'sx': 3}
CommutationAnalysis {'measure': 3, 'rz': 6, 'sx': 3}
CommutativeCancellation {'measure': 3, 'rz': 6, 'sx': 3}
UnitarySynthesis {'measure': 3, 'rz': 6, 'sx': 3}
UnrollCustomDefinitions {'measure': 3, 'rz': 6, 'sx': 3}
BasisTranslator {'measure': 3, 'rz': 6, 'sx': 3}
Depth {'measure': 3, 'rz': 6, 'sx': 3}
FixedPoint {'measure': 3, 'rz': 6, 'sx': 3}
Collect2qBlocks {'measure': 3, 'rz': 6, 'sx': 3}
ConsolidateBlocks {'measure': 3, 'rz': 6, 'sx': 3}
UnitarySynthesis {'measure': 3, 'rz': 6, 'sx': 3}
Optimize1qGatesDecomposition {'measure': 3, 'rz': 6, 'sx': 3}
CommutationAnalysis {'measure': 3, 'rz': 6, 'sx': 3}
CommutativeCancellation {'measure': 3, 'rz': 6, 'sx': 3}
UnitarySynthesis {'measure': 3, 'rz': 6, 'sx': 3}
UnrollCustomDefinitions {'measure': 3, 'rz': 6, 'sx': 3}
BasisTranslator {'measure': 3, 'rz': 6, 'sx': 3}
ContainsInstruction {'measure': 3, 'rz': 6, 'sx': 3}
I think the problem here is the basic interaction of small-step Trotterization (which makes the interaction-per-step become small) with approximate unitary synthesis (which avoids emitting 2q gates when the interaction becomes small enough). The solution is to use fewer Trotter steps or to turn off approximate synthesis.
It appears that this happens only with the default approximation_degree=None
. Setting 1
(min approx) or 0
(max approx) both yield the same circuit.
Thinking about this a bit more though, could it be the case that this is a sound optimization to make (in the interest of best approximating the given unitary with the available device gate fidelities), even though when simulating (on an ideal simulator) you'll end up quite far from the intended operation?
Thinking about this a bit more though, could it be the case that this is a sound optimization to make (in the interest of best approximating the given unitary with the available device gate fidelities), even though when simulating (on an ideal simulator) you'll end up quite far from the intended operation?
This is the intention, that you do the best you can given available gate fidelities. It may end up far from the intended, but better than not approximating. Of course the decision is heuristic and local, so may not always succeed - something like trotterization where the repeated trotter steps are intended to add coherently would be an example where the heuristic based on assuming a depolarizing channel of equivalent infidelity might choose poorly.
In that case, I'd lean toward the transpiler behavior being not a bug. It may be a bug though that users can compile based on a set of backend_properties
and simulate without them without seeing a warning. Does that make sense to you @anedumla @nonhermitian @levbishop ?
As an user I am actually surprised that this was enabled by default. To me at least, it seems to be implicitly breaking the idea that the transpiler faithfully performs the unitary in question (up to certain permutations, transformations etc.). To me this feels akin to the fast-math
compiler option that breaks IEEE 754 floating-point rules to gain performance, but occasionally really messes things up, eg see https://github.com/JuliaLang/julia/issues/30073#issuecomment-439707503
I think approximations like this should be explicit, and turned on by the user with knowledge that bad things can some times happen.
I agree with @nonhermitian. Personally, I would not expect that the circuit returned is an approximation and would prefer to explicitly activate an option to approximate the unitary given the hardware characteristics.
Since #8595 has merged I think this has been implemented so I'm going to close this. If I'm missing something here though or misinterpreting what was needed for this issue please feel free to reopen this.
Environment
What is happening?
Using
optimization_level=3
within thetranspile
method yields a circuit with different outcomes that the one produced either from settingoptimization_level=2
or evaluating the non transpiled circuit withStatevector
.The wrong behaviour was observed while increasing the number of trotter steps in a Trotterization time evolution, and the circuits obtained from
optimization_level=3
perform as expected as long as the original circuit remains relatively shallow (see plots below).How can we reproduce the issue?
Output:
What should happen?
In the output plots the line for
optimization_level=3
should coincide with thestatevector
andoptimization_level=2
lines.Any suggestions?
No response